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