First version
[3rdparty/ote_partner/tlk.git] / lib / fs / fs.c
1 /*
2  * Copyright (c) 2009 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 <list.h>
25 #include <err.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <lib/fs.h>
29 #include <lib/bio.h>
30
31 #if WITH_LIB_FS_EXT2
32 #include <lib/fs/ext2.h>
33 #endif
34 #if WITH_LIB_FS_FAT32
35 #include <lib/fs/fat32.h>
36 #endif
37
38 #define LOCAL_TRACE 0
39
40 struct fs_type {
41         const char *name;
42         int (*mount)(bdev_t *, fscookie *);
43         int (*unmount)(fscookie);
44         int (*open)(fscookie, const char *, filecookie *);
45         int (*create)(fscookie, const char *, filecookie *);
46         int (*mkdir)(fscookie, const char *);
47         int (*stat)(filecookie, struct file_stat *);
48         int (*read)(filecookie, void *, off_t, size_t);
49         int (*write)(filecookie, const void *, off_t, size_t);
50         int (*close)(filecookie);
51 };
52
53 struct fs_mount {
54         struct list_node node;
55         char *path;
56         bdev_t *dev;
57         fscookie cookie;
58         int refs;
59         struct fs_type *type;
60 };
61
62 struct fs_file {
63         filecookie cookie;
64         struct fs_mount *mount;
65 };
66
67 static struct list_node mounts;
68
69 static struct fs_type types[] = {
70 #if WITH_LIB_FS_EXT2
71         {
72                 .name = "ext2",
73                 .mount = ext2_mount,
74                 .unmount = ext2_unmount,
75                 .open = ext2_open_file,
76                 .stat = ext2_stat_file,
77                 .read = ext2_read_file,
78                 .close = ext2_close_file,
79         },
80 #endif
81 #if WITH_LIB_FS_FAT32
82         {
83                 .name = "fat32",
84                 .mount = fat32_mount,
85                 .unmount = fat32_unmount,
86                 .open = fat32_open_file,
87                 .create = fat32_create_file,
88                 .mkdir = fat32_make_dir,
89                 .stat = fat32_stat_file,
90                 .read = fat32_read_file,
91                 .write = fat32_write_file,
92                 .close = fat32_close_file,
93         },
94 #endif
95 };
96
97 static void test_normalize(const char *in);
98 static struct fs_mount *find_mount(const char *path, const char **trimmed_path);
99
100 void fs_init(void)
101 {
102         list_initialize(&mounts);
103 #if 0
104         test_normalize("/");
105         test_normalize("/test");
106         test_normalize("/test/");
107         test_normalize("test/");
108         test_normalize("test");
109         test_normalize("/test//");
110         test_normalize("/test/foo");
111         test_normalize("/test/foo/");
112         test_normalize("/test/foo/bar");
113         test_normalize("/test/foo/bar//");
114         test_normalize("/test//foo/bar//");
115         test_normalize("/test//./foo/bar//");
116         test_normalize("/test//./.foo/bar//");
117         test_normalize("/test//./..foo/bar//");
118         test_normalize("/test//./../foo/bar//");
119         test_normalize("/test/../foo");
120         test_normalize("/test/bar/../foo");
121         test_normalize("../foo");
122         test_normalize("../foo/");
123         test_normalize("/../foo");
124         test_normalize("/../foo/");
125         test_normalize("/../../foo");
126         test_normalize("/bleh/../../foo");
127         test_normalize("/bleh/bar/../../foo");
128         test_normalize("/bleh/bar/../../foo/..");
129         test_normalize("/bleh/bar/../../foo/../meh");
130 #endif
131 }
132
133 static struct fs_mount *find_mount(const char *path, const char **trimmed_path)
134 {
135         struct fs_mount *mount;
136         size_t pathlen = strlen(path);
137
138         list_for_every_entry(&mounts, mount, struct fs_mount, node) {
139                 size_t mountpathlen = strlen(mount->path);
140                 if (pathlen < mountpathlen)
141                         continue;
142
143                 LTRACEF("comparing %s with %s\n", path, mount->path);
144
145                 if (memcmp(path, mount->path, mountpathlen) == 0) {
146                         if (trimmed_path)
147                                 *trimmed_path = &path[mountpathlen];
148
149                         return mount;
150                 }
151         }
152
153         return NULL;
154 }
155
156 static int mount(const char *path, const char *device, struct fs_type *type)
157 {
158         char temppath[512];
159
160         strlcpy(temppath, path, sizeof(temppath));
161         fs_normalize_path(temppath);
162
163         if(temppath[0] != '/')
164                 return ERR_BAD_PATH;
165
166         if (find_mount(temppath, NULL))
167                 return ERR_ALREADY_MOUNTED;
168
169         bdev_t *dev = bio_open(device);
170         if (!dev)
171                 return ERR_NOT_FOUND;
172
173         fscookie cookie;
174         int err = type->mount(dev, &cookie);
175         if (err < 0) {
176                 bio_close(dev);
177                 return err;
178         }
179
180         /* create the mount structure and add it to the list */
181         struct fs_mount *mount = malloc(sizeof(struct fs_mount));
182         mount->path = strdup(temppath);
183         mount->dev = dev;
184         mount->cookie = cookie;
185         mount->refs = 1;
186         mount->type = type;
187
188         list_add_head(&mounts, &mount->node);
189
190         return 0;
191 }
192
193 int fs_mount(const char *path, const char *device)
194 {
195         return mount(path, device, &types[0]);
196 }
197
198 int fs_mount_type(const char *path, const char *device, const char *name)
199 {
200         size_t i;
201
202         for (i = 0; i < countof(types); i++) {
203                 if (!strcmp(name, types[i].name))
204                         return mount(path, device, &types[i]);
205         }
206
207         return ERR_NOT_FOUND;
208 }
209
210 static void put_mount(struct fs_mount *mount)
211 {
212         if (!(--mount->refs)) {
213                 list_delete(&mount->node);
214                 mount->type->unmount(mount->cookie);
215                 free(mount->path);
216                 bio_close(mount->dev);
217                 free(mount);
218         }
219 }
220
221 int fs_unmount(const char *path)
222 {
223         char temppath[512];
224
225         strlcpy(temppath, path, sizeof(temppath));
226         fs_normalize_path(temppath);
227
228         struct fs_mount *mount = find_mount(temppath, NULL);
229         if (!mount)
230                 return ERR_NOT_FOUND;
231
232         put_mount(mount);
233
234         return 0;
235 }
236
237
238 int fs_open_file(const char *path, filecookie *fcookie)
239 {
240         int err;
241         char temppath[512];
242         filecookie cookie;
243
244         strlcpy(temppath, path, sizeof(temppath));
245         fs_normalize_path(temppath);
246
247         LTRACEF("path %s temppath %s\n", path, temppath);
248
249         const char *newpath;
250         struct fs_mount *mount = find_mount(temppath, &newpath);
251         if (!mount)
252                 return ERR_NOT_FOUND;
253
254         LTRACEF("path %s temppath %s newpath %s\n", path, temppath, newpath);
255
256         err = mount->type->open(mount->cookie, newpath, &cookie);
257         if (err < 0)
258                 return err;
259
260         struct fs_file *f = malloc(sizeof(*f));
261         f->cookie = cookie;
262         f->mount = mount;
263         mount->refs++;
264         *fcookie = f;
265
266         return 0;
267 }
268
269 int fs_create_file(const char *path, filecookie *fcookie)
270 {
271         int err;
272         char temppath[512];
273         filecookie cookie;
274
275         strlcpy(temppath, path, sizeof(temppath));
276         fs_normalize_path(temppath);
277
278         const char *newpath;
279         struct fs_mount *mount = find_mount(temppath, &newpath);
280         if (!mount)
281                 return ERR_NOT_FOUND;
282
283         if (!mount->type->create)
284                 return ERR_NOT_SUPPORTED;
285
286         err = mount->type->create(mount->cookie, newpath, &cookie);
287         if (err < 0)
288                 return err;
289
290         struct fs_file *f = malloc(sizeof(*f));
291         f->cookie = cookie;
292         f->mount = mount;
293         mount->refs++;
294         *fcookie = f;
295
296         return 0;
297 }
298
299 int fs_make_dir(const char *path)
300 {
301         char temppath[512];
302
303         strlcpy(temppath, path, sizeof(temppath));
304         fs_normalize_path(temppath);
305
306         const char *newpath;
307         struct fs_mount *mount = find_mount(temppath, &newpath);
308         if (!mount)
309                 return ERR_NOT_FOUND;
310
311         if (!mount->type->mkdir)
312                 return ERR_NOT_SUPPORTED;
313
314         return mount->type->mkdir(mount->cookie, newpath);
315 }
316
317 int fs_read_file(filecookie fcookie, void *buf, off_t offset, size_t len)
318 {
319         struct fs_file *f = fcookie;
320
321         return f->mount->type->read(f->cookie, buf, offset, len);
322 }
323
324 int fs_write_file(filecookie fcookie, const void *buf, off_t offset, size_t len)
325 {
326         struct fs_file *f = fcookie;
327
328         if (!f->mount->type->write)
329                 return ERR_NOT_SUPPORTED;
330
331         return f->mount->type->write(f->cookie, buf, offset, len);
332 }
333
334 int fs_close_file(filecookie fcookie)
335 {
336         int err;
337         struct fs_file *f = fcookie;
338
339         err = f->mount->type->close(f->cookie);
340         if (err < 0)
341                 return err;
342
343         put_mount(f->mount);
344         free(f);
345         return 0;
346 }
347
348 int fs_stat_file(filecookie fcookie, struct file_stat *stat)
349 {
350         struct fs_file *f = fcookie;
351
352         return f->mount->type->stat(f->cookie, stat);
353 }
354
355 ssize_t fs_load_file(const char *path, void *ptr, size_t maxlen)
356 {
357         int err;
358         filecookie cookie;
359
360         /* open the file */
361         err = fs_open_file(path, &cookie);
362         if (err < 0)
363                 return err;
364
365         /* stat it for size, see how much we need to read */
366         struct file_stat stat;
367         fs_stat_file(cookie, &stat);
368
369         err = fs_read_file(cookie, ptr, 0, MIN(maxlen, stat.size));
370
371         fs_close_file(cookie);
372
373         return err;
374 }
375
376 static void test_normalize(const char *in)
377 {
378         char path[1024];
379
380         strlcpy(path, in, sizeof(path));
381         fs_normalize_path(path);
382         printf("'%s' -> '%s'\n", in, path);
383 }
384
385 void fs_normalize_path(char *path)
386 {
387         int outpos;
388         int pos;
389         char c;
390         bool done;
391         enum {
392                 INITIAL,
393                 FIELD_START,
394                 IN_FIELD,
395                 SEP,
396                 SEEN_SEP,
397                 DOT,
398                 SEEN_DOT,
399                 DOTDOT,
400                 SEEN_DOTDOT,
401         } state;
402
403         state = INITIAL;
404         pos = 0;
405         outpos = 0;
406         done = false;
407
408         /* remove duplicate path seperators, flatten empty fields (only composed of .), backtrack fields with .., remove trailing slashes */
409         while (!done) {
410                 c = path[pos];
411                 switch (state) {
412                         case INITIAL:
413                                 if (c == '/') {
414                                         state = SEP;
415                                 } else if (c == '.') {
416                                         state = DOT;
417                                 } else {
418                                         state = FIELD_START;
419                                 }
420                                 break;
421                         case FIELD_START:
422                                 if (c == '.') {
423                                         state = DOT;
424                                 } else if (c == 0) {
425                                         done = true;
426                                 } else {
427                                         state = IN_FIELD;
428                                 }
429                                 break;
430                         case IN_FIELD:
431                                 if (c == '/') {
432                                         state = SEP;
433                                 } else if (c == 0) {
434                                         done = true;
435                                 } else {
436                                         path[outpos++] = c;
437                                         pos++;
438                                 }
439                                 break;
440                         case SEP:
441                                 pos++;
442                                 path[outpos++] = '/';
443                                 state = SEEN_SEP;
444                                 break;
445                         case SEEN_SEP:
446                                 if (c == '/') {
447                                         // eat it
448                                         pos++;
449                                 } else if (c == 0) {
450                                         done = true;
451                                 } else {
452                                         state = FIELD_START;
453                                 }
454                                 break;
455                         case DOT:
456                                 pos++; // consume the dot
457                                 state = SEEN_DOT;
458                                 break;
459                         case SEEN_DOT:
460                                 if (c == '.') {
461                                         // dotdot now
462                                         state = DOTDOT;
463                                 } else if (c == '/') {
464                                         // a field composed entirely of a .
465                                         // consume the / and move directly to the SEEN_SEP state
466                                         pos++;
467                                         state = SEEN_SEP;
468                                 } else if (c == 0) {
469                                         done = true;
470                                 } else {
471                                         // a field prefixed with a .
472                                         // emit a . and move directly into the IN_FIELD state
473                                         path[outpos++] = '.';
474                                         state = IN_FIELD;
475                                 }
476                                 break;
477                         case DOTDOT:
478                                 pos++; // consume the dot
479                                 state = SEEN_DOTDOT;
480                                 break;
481                         case SEEN_DOTDOT:
482                                 if (c == '/' || c == 0) {
483                                         // a field composed entirely of '..'
484                                         // search back and consume a field we've already emitted
485                                         if (outpos > 0) {
486                                                 // we have already consumed at least one field
487                                                 outpos--;
488
489                                                 // walk backwards until we find the next field boundary
490                                                 while (outpos > 0) {
491                                                         if (path[outpos - 1] == '/') {
492                                                                 break;
493                                                         }
494                                                         outpos--;
495                                                 }
496                                         }
497                                         pos++;
498                                         state = SEEN_SEP;
499                                         if (c == 0)
500                                                 done = true;
501                                 } else {
502                                         // a field prefixed with ..
503                                         // emit the .. and move directly to the IN_FIELD state
504                                         path[outpos++] = '.';
505                                         path[outpos++] = '.';
506                                         state = IN_FIELD;
507                                 }
508                                 break;
509                 }
510         }
511
512         /* dont end with trailing slashes */
513         if (outpos > 0 && path[outpos - 1] == '/')
514                 outpos--;
515
516         path[outpos++] = 0;
517 }
518