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