blob: 378e824a7ded2e69f86a6fe84e54977d453b0b5e [file] [log] [blame]
rjw1f884582022-01-06 17:20:42 +08001/*
2 * Copyright (c) 2009-2015 Travis Geiselbrecht
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files
6 * (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23#include <debug.h>
24#include <trace.h>
25#include <list.h>
26#include <err.h>
27#include <string.h>
28#include <stdlib.h>
29#include <lib/fs.h>
30#include <lib/bio.h>
31#include <lk/init.h>
32#include <kernel/mutex.h>
33
34#define LOCAL_TRACE 0
35
36struct fs_mount {
37 struct list_node node;
38
39 char *path;
40 bdev_t *dev;
41 fscookie *cookie;
42 int ref;
43 const struct fs_api *api;
44};
45
46struct filehandle {
47 filecookie *cookie;
48 struct fs_mount *mount;
49};
50
51struct dirhandle {
52 dircookie *cookie;
53 struct fs_mount *mount;
54};
55
56static mutex_t mount_lock = MUTEX_INITIAL_VALUE(mount_lock);
57static struct list_node mounts = LIST_INITIAL_VALUE(mounts);
58static struct list_node fses = LIST_INITIAL_VALUE(fses);
59
60// defined in the linker script
61extern const struct fs_impl __fs_impl_start;
62extern const struct fs_impl __fs_impl_end;
63
64static const struct fs_impl *find_fs(const char *name)
65{
66 for (const struct fs_impl *fs = &__fs_impl_start; fs != &__fs_impl_end; fs++) {
67 if (!strcmp(name, fs->name))
68 return fs;
69 }
70 return NULL;
71}
72
73
74// find a mount structure based on the prefix of this path
75// bump the ref to the mount structure before returning
76static struct fs_mount *find_mount(const char *path, const char **trimmed_path)
77{
78 struct fs_mount *mount;
79 size_t pathlen = strlen(path);
80
81 mutex_acquire(&mount_lock);
82 list_for_every_entry(&mounts, mount, struct fs_mount, node) {
83 size_t mountpathlen = strlen(mount->path);
84 if (pathlen < mountpathlen)
85 continue;
86
87 LTRACEF("comparing %s with %s\n", path, mount->path);
88
89 if (memcmp(path, mount->path, mountpathlen) == 0) {
90 if (trimmed_path)
91 *trimmed_path = &path[mountpathlen];
92
93 mount->ref++;
94
95 mutex_release(&mount_lock);
96 return mount;
97 }
98 }
99
100 mutex_release(&mount_lock);
101 return NULL;
102}
103
104// decrement the ref to the mount structure, which may
105// cause an unmount operation
106static void put_mount(struct fs_mount *mount)
107{
108 mutex_acquire(&mount_lock);
109 if ((--mount->ref) == 0) {
110 list_delete(&mount->node);
111 mount->api->unmount(mount->cookie);
112 free(mount->path);
113 if (mount->dev)
114 bio_close(mount->dev);
115 free(mount);
116 }
117 mutex_release(&mount_lock);
118}
119
120static status_t mount(const char *path, const char *device, const struct fs_api *api)
121{
122 struct fs_mount *mount;
123 char temppath[FS_MAX_PATH_LEN];
124
125 strlcpy(temppath, path, sizeof(temppath));
126 fs_normalize_path(temppath);
127
128 if (temppath[0] != '/')
129 return ERR_BAD_PATH;
130
131 /* see if there's already something at this path, abort if there is */
132 mount = find_mount(temppath, NULL);
133 if (mount) {
134 put_mount(mount);
135 return ERR_ALREADY_MOUNTED;
136 }
137
138 /* open a bio device if the string is nonnull */
139 bdev_t *dev = NULL;
140 if (device && device[0] != '\0') {
141 dev = bio_open(device);
142 if (!dev)
143 return ERR_NOT_FOUND;
144 }
145
146 /* call into the fs implementation */
147 fscookie *cookie;
148 status_t err = api->mount(dev, &cookie);
149 if (err < 0) {
150 if (dev) bio_close(dev);
151 return err;
152 }
153
154 /* create the mount structure and add it to the list */
155 mount = malloc(sizeof(struct fs_mount));
156 mount->path = strdup(temppath);
157 mount->dev = dev;
158 mount->cookie = cookie;
159 mount->ref = 1;
160 mount->api = api;
161
162 list_add_head(&mounts, &mount->node);
163
164 return 0;
165
166}
167
168status_t fs_format_device(const char *fsname, const char *device, const void *args)
169{
170 const struct fs_impl *fs = find_fs(fsname);
171 if (!fs) {
172 return ERR_NOT_FOUND;
173 }
174
175 if (fs->api->format == NULL) {
176 return ERR_NOT_SUPPORTED;
177 }
178
179 bdev_t *dev = NULL;
180 if (device && device[0] != '\0') {
181 dev = bio_open(device);
182 if (!dev)
183 return ERR_NOT_FOUND;
184 }
185
186 return fs->api->format(dev, args);
187}
188
189status_t fs_mount(const char *path, const char *fsname, const char *device)
190{
191 const struct fs_impl *fs = find_fs(fsname);
192 if (!fs)
193 return ERR_NOT_FOUND;
194
195 return mount(path, device, fs->api);
196}
197
198status_t fs_unmount(const char *path)
199{
200 char temppath[FS_MAX_PATH_LEN];
201
202 strlcpy(temppath, path, sizeof(temppath));
203 fs_normalize_path(temppath);
204
205 struct fs_mount *mount = find_mount(temppath, NULL);
206 if (!mount)
207 return ERR_NOT_FOUND;
208
209 // return the ref that find_mount added and one extra
210 put_mount(mount);
211 put_mount(mount);
212
213 return 0;
214}
215
216
217status_t fs_open_file(const char *path, filehandle **handle)
218{
219 char temppath[FS_MAX_PATH_LEN];
220
221 strlcpy(temppath, path, sizeof(temppath));
222 fs_normalize_path(temppath);
223
224 LTRACEF("path %s temppath %s\n", path, temppath);
225
226 const char *newpath;
227 struct fs_mount *mount = find_mount(temppath, &newpath);
228 if (!mount)
229 return ERR_NOT_FOUND;
230
231 LTRACEF("path %s temppath %s newpath %s\n", path, temppath, newpath);
232
233 filecookie *cookie;
234 status_t err = mount->api->open(mount->cookie, newpath, &cookie);
235 if (err < 0) {
236 put_mount(mount);
237 return err;
238 }
239
240 filehandle *f = malloc(sizeof(*f));
241 f->cookie = cookie;
242 f->mount = mount;
243 *handle = f;
244
245 return 0;
246}
247
248status_t fs_file_ioctl(filehandle *handle, int request, void *argp)
249{
250 LTRACEF("filehandle %p, request %d, argp, %p\n", handle, request, argp);
251
252 if (unlikely(!handle || !handle->mount ||
253 !handle->mount->api || !handle->mount->api->file_ioctl)) {
254 return ERR_INVALID_ARGS;
255 }
256
257 return handle->mount->api->file_ioctl(handle->cookie, request, argp);
258}
259
260status_t fs_create_file(const char *path, filehandle **handle, uint64_t len)
261{
262 char temppath[FS_MAX_PATH_LEN];
263
264 strlcpy(temppath, path, sizeof(temppath));
265 fs_normalize_path(temppath);
266
267 const char *newpath;
268 struct fs_mount *mount = find_mount(temppath, &newpath);
269 if (!mount)
270 return ERR_NOT_FOUND;
271
272 if (!mount->api->create) {
273 put_mount(mount);
274 return ERR_NOT_SUPPORTED;
275 }
276
277 filecookie *cookie;
278 status_t err = mount->api->create(mount->cookie, newpath, &cookie, len);
279 if (err < 0) {
280 put_mount(mount);
281 return err;
282 }
283
284 filehandle *f = malloc(sizeof(*f));
285 f->cookie = cookie;
286 f->mount = mount;
287 *handle = f;
288
289 return 0;
290}
291
292status_t fs_remove_file(const char *path)
293{
294 char temppath[FS_MAX_PATH_LEN];
295
296 strlcpy(temppath, path, sizeof(temppath));
297 fs_normalize_path(temppath);
298
299 const char *newpath;
300 struct fs_mount *mount = find_mount(temppath, &newpath);
301 if (!mount)
302 return ERR_NOT_FOUND;
303
304 if (!mount->api->remove) {
305 put_mount(mount);
306 return ERR_NOT_SUPPORTED;
307 }
308
309 status_t err = mount->api->remove(mount->cookie, newpath);
310
311 put_mount(mount);
312
313 return err;
314}
315
316ssize_t fs_read_file(filehandle *handle, void *buf, off_t offset, size_t len)
317{
318 return handle->mount->api->read(handle->cookie, buf, offset, len);
319}
320
321ssize_t fs_write_file(filehandle *handle, const void *buf, off_t offset, size_t len)
322{
323 if (!handle->mount->api->write)
324 return ERR_NOT_SUPPORTED;
325
326 return handle->mount->api->write(handle->cookie, buf, offset, len);
327}
328
329status_t fs_close_file(filehandle *handle)
330{
331 status_t err = handle->mount->api->close(handle->cookie);
332 if (err < 0)
333 return err;
334
335 put_mount(handle->mount);
336 free(handle);
337 return 0;
338}
339
340status_t fs_stat_file(filehandle *handle, struct file_stat *stat)
341{
342 return handle->mount->api->stat(handle->cookie, stat);
343}
344
345status_t fs_make_dir(const char *path)
346{
347 char temppath[FS_MAX_PATH_LEN];
348
349 strlcpy(temppath, path, sizeof(temppath));
350 fs_normalize_path(temppath);
351
352 const char *newpath;
353 struct fs_mount *mount = find_mount(temppath, &newpath);
354 if (!mount)
355 return ERR_NOT_FOUND;
356
357 if (!mount->api->mkdir) {
358 put_mount(mount);
359 return ERR_NOT_SUPPORTED;
360 }
361
362 status_t err = mount->api->mkdir(mount->cookie, newpath);
363
364 put_mount(mount);
365
366 return err;
367}
368
369status_t fs_open_dir(const char *path, dirhandle **handle)
370{
371 char temppath[FS_MAX_PATH_LEN];
372
373 strlcpy(temppath, path, sizeof(temppath));
374 fs_normalize_path(temppath);
375
376 LTRACEF("path %s temppath %s\n", path, temppath);
377
378 const char *newpath;
379 struct fs_mount *mount = find_mount(temppath, &newpath);
380 if (!mount)
381 return ERR_NOT_FOUND;
382
383 LTRACEF("path %s temppath %s newpath %s\n", path, temppath, newpath);
384
385 if (!mount->api->opendir) {
386 put_mount(mount);
387 return ERR_NOT_SUPPORTED;
388 }
389
390 dircookie *cookie;
391 status_t err = mount->api->opendir(mount->cookie, newpath, &cookie);
392 if (err < 0) {
393 put_mount(mount);
394 return err;
395 }
396
397 dirhandle *d = malloc(sizeof(*d));
398 d->cookie = cookie;
399 d->mount = mount;
400 *handle = d;
401
402 return 0;
403}
404
405status_t fs_read_dir(dirhandle *handle, struct dirent *ent)
406{
407 if (!handle->mount->api->readdir)
408 return ERR_NOT_SUPPORTED;
409
410 return handle->mount->api->readdir(handle->cookie, ent);
411}
412
413status_t fs_close_dir(dirhandle *handle)
414{
415 if (!handle->mount->api->closedir)
416 return ERR_NOT_SUPPORTED;
417
418 status_t err = handle->mount->api->closedir(handle->cookie);
419 if (err < 0)
420 return err;
421
422 put_mount(handle->mount);
423 free(handle);
424 return 0;
425}
426
427status_t fs_stat_fs(const char *mountpoint, struct fs_stat *stat)
428{
429 LTRACEF("mountpoint %s stat %p\n", mountpoint, stat);
430
431 if (!stat) {
432 return ERR_INVALID_ARGS;
433 }
434
435 const char *newpath;
436 struct fs_mount *mount = find_mount(mountpoint, &newpath);
437 if (!mount) {
438 return ERR_NOT_FOUND;
439 }
440
441 if (!mount->api->fs_stat) {
442 put_mount(mount);
443 return ERR_NOT_SUPPORTED;
444 }
445
446 status_t result = mount->api->fs_stat(mount->cookie, stat);
447
448 put_mount(mount);
449
450 return result;
451}
452
453
454ssize_t fs_load_file(const char *path, void *ptr, size_t maxlen)
455{
456 filehandle *handle;
457
458 /* open the file */
459 status_t err = fs_open_file(path, &handle);
460 if (err < 0)
461 return err;
462
463 /* stat it for size, see how much we need to read */
464 struct file_stat stat;
465 fs_stat_file(handle, &stat);
466
467 ssize_t read_bytes = fs_read_file(handle, ptr, 0, MIN(maxlen, stat.size));
468
469 fs_close_file(handle);
470
471 return read_bytes;
472}
473
474const char *trim_name(const char *_name)
475{
476 const char *name = &_name[0];
477 // chew up leading spaces
478 while (*name == ' ')
479 name++;
480
481 // chew up leading slashes
482 while (*name == '/')
483 name++;
484
485 return name;
486}
487
488
489void fs_normalize_path(char *path)
490{
491 int outpos;
492 int pos;
493 char c;
494 bool done;
495 enum {
496 INITIAL,
497 FIELD_START,
498 IN_FIELD,
499 SEP,
500 SEEN_SEP,
501 DOT,
502 SEEN_DOT,
503 DOTDOT,
504 SEEN_DOTDOT,
505 } state;
506
507 state = INITIAL;
508 pos = 0;
509 outpos = 0;
510 done = false;
511
512 /* remove duplicate path seperators, flatten empty fields (only composed of .), backtrack fields with .., remove trailing slashes */
513 while (!done) {
514 c = path[pos];
515 switch (state) {
516 case INITIAL:
517 if (c == '/') {
518 state = SEP;
519 } else if (c == '.') {
520 state = DOT;
521 } else {
522 state = FIELD_START;
523 }
524 break;
525 case FIELD_START:
526 if (c == '.') {
527 state = DOT;
528 } else if (c == 0) {
529 done = true;
530 } else {
531 state = IN_FIELD;
532 }
533 break;
534 case IN_FIELD:
535 if (c == '/') {
536 state = SEP;
537 } else if (c == 0) {
538 done = true;
539 } else {
540 path[outpos++] = c;
541 pos++;
542 }
543 break;
544 case SEP:
545 pos++;
546 path[outpos++] = '/';
547 state = SEEN_SEP;
548 break;
549 case SEEN_SEP:
550 if (c == '/') {
551 // eat it
552 pos++;
553 } else if (c == 0) {
554 done = true;
555 } else {
556 state = FIELD_START;
557 }
558 break;
559 case DOT:
560 pos++; // consume the dot
561 state = SEEN_DOT;
562 break;
563 case SEEN_DOT:
564 if (c == '.') {
565 // dotdot now
566 state = DOTDOT;
567 } else if (c == '/') {
568 // a field composed entirely of a .
569 // consume the / and move directly to the SEEN_SEP state
570 pos++;
571 state = SEEN_SEP;
572 } else if (c == 0) {
573 done = true;
574 } else {
575 // a field prefixed with a .
576 // emit a . and move directly into the IN_FIELD state
577 path[outpos++] = '.';
578 state = IN_FIELD;
579 }
580 break;
581 case DOTDOT:
582 pos++; // consume the dot
583 state = SEEN_DOTDOT;
584 break;
585 case SEEN_DOTDOT:
586 if (c == '/' || c == 0) {
587 // a field composed entirely of '..'
588 // search back and consume a field we've already emitted
589 if (outpos > 0) {
590 // we have already consumed at least one field
591 outpos--;
592
593 // walk backwards until we find the next field boundary
594 while (outpos > 0) {
595 if (path[outpos - 1] == '/') {
596 break;
597 }
598 outpos--;
599 }
600 }
601 pos++;
602 state = SEEN_SEP;
603 if (c == 0)
604 done = true;
605 } else {
606 // a field prefixed with ..
607 // emit the .. and move directly to the IN_FIELD state
608 path[outpos++] = '.';
609 path[outpos++] = '.';
610 state = IN_FIELD;
611 }
612 break;
613 }
614 }
615
616 /* dont end with trailing slashes */
617 if (outpos > 0 && path[outpos - 1] == '/')
618 outpos--;
619
620 path[outpos++] = 0;
621}
622