diff options
Diffstat (limited to 'firmware/common/file_internal.c')
-rw-r--r-- | firmware/common/file_internal.c | 776 |
1 files changed, 776 insertions, 0 deletions
diff --git a/firmware/common/file_internal.c b/firmware/common/file_internal.c new file mode 100644 index 0000000000..ebe77f0c9f --- /dev/null +++ b/firmware/common/file_internal.c | |||
@@ -0,0 +1,776 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 by Michael Sevakis | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | #include "config.h" | ||
22 | #include <errno.h> | ||
23 | #include "system.h" | ||
24 | #include "debug.h" | ||
25 | #include "panic.h" | ||
26 | #include "pathfuncs.h" | ||
27 | #include "disk_cache.h" | ||
28 | #include "fileobj_mgr.h" | ||
29 | #include "dir.h" | ||
30 | #include "dircache_redirect.h" | ||
31 | #include "dircache.h" | ||
32 | #include "string-extra.h" | ||
33 | #include "rbunicode.h" | ||
34 | |||
35 | /** Internal common filesystem service functions **/ | ||
36 | |||
37 | /* for internal functions' scanning use to save quite a bit of stack space - | ||
38 | access must be serialized by the writer lock */ | ||
39 | #if defined(CPU_SH) || defined(IAUDIO_M5) | ||
40 | /* otherwise, out of IRAM */ | ||
41 | struct fat_direntry dir_fatent; | ||
42 | #else | ||
43 | struct fat_direntry dir_fatent IBSS_ATTR; | ||
44 | #endif | ||
45 | |||
46 | struct mrsw_lock file_internal_mrsw SHAREDBSS_ATTR; | ||
47 | |||
48 | |||
49 | /** File stream sector caching **/ | ||
50 | |||
51 | /* initialize a new cache structure */ | ||
52 | void file_cache_init(struct filestr_cache *cachep) | ||
53 | { | ||
54 | cachep->buffer = NULL; | ||
55 | cachep->sector = INVALID_SECNUM; | ||
56 | cachep->flags = 0; | ||
57 | } | ||
58 | |||
59 | /* discard and mark the cache buffer as unused */ | ||
60 | void file_cache_reset(struct filestr_cache *cachep) | ||
61 | { | ||
62 | cachep->sector = INVALID_SECNUM; | ||
63 | cachep->flags = 0; | ||
64 | } | ||
65 | |||
66 | /* allocate resources attached to the cache */ | ||
67 | void file_cache_alloc(struct filestr_cache *cachep) | ||
68 | { | ||
69 | /* if this fails, it is a bug; check for leaks and that the cache has | ||
70 | enough buffers for the worst case */ | ||
71 | if (!cachep->buffer && !(cachep->buffer = dc_get_buffer())) | ||
72 | panicf("file_cache_alloc - OOM"); | ||
73 | } | ||
74 | |||
75 | /* free resources attached to the cache */ | ||
76 | void file_cache_free(struct filestr_cache *cachep) | ||
77 | { | ||
78 | if (cachep && cachep->buffer) | ||
79 | { | ||
80 | dc_release_buffer(cachep->buffer); | ||
81 | cachep->buffer = NULL; | ||
82 | } | ||
83 | |||
84 | file_cache_reset(cachep); | ||
85 | } | ||
86 | |||
87 | |||
88 | /** Stream base APIs **/ | ||
89 | |||
90 | static inline void filestr_clear(struct filestr_base *stream) | ||
91 | { | ||
92 | stream->flags = 0; | ||
93 | stream->bindp = NULL; | ||
94 | #if 0 | ||
95 | stream->mtx = NULL; | ||
96 | #endif | ||
97 | } | ||
98 | |||
99 | /* actually late-allocate the assigned cache */ | ||
100 | void filestr_alloc_cache(struct filestr_base *stream) | ||
101 | { | ||
102 | file_cache_alloc(stream->cachep); | ||
103 | } | ||
104 | |||
105 | /* free the stream's cache buffer if it's its own */ | ||
106 | void filestr_free_cache(struct filestr_base *stream) | ||
107 | { | ||
108 | if (stream->cachep == &stream->cache) | ||
109 | file_cache_free(stream->cachep); | ||
110 | } | ||
111 | |||
112 | /* assign a cache to the stream */ | ||
113 | void filestr_assign_cache(struct filestr_base *stream, | ||
114 | struct filestr_cache *cachep) | ||
115 | { | ||
116 | if (cachep) | ||
117 | { | ||
118 | filestr_free_cache(stream); | ||
119 | stream->cachep = cachep; | ||
120 | } | ||
121 | else /* assign own cache */ | ||
122 | { | ||
123 | file_cache_reset(&stream->cache); | ||
124 | stream->cachep = &stream->cache; | ||
125 | } | ||
126 | } | ||
127 | |||
128 | /* duplicate a cache into a stream's local cache */ | ||
129 | void filestr_copy_cache(struct filestr_base *stream, | ||
130 | struct filestr_cache *cachep) | ||
131 | { | ||
132 | stream->cachep = &stream->cache; | ||
133 | stream->cache.sector = cachep->sector; | ||
134 | stream->cache.flags = cachep->flags; | ||
135 | |||
136 | if (cachep->buffer) | ||
137 | { | ||
138 | file_cache_alloc(&stream->cache); | ||
139 | memcpy(stream->cache.buffer, cachep->buffer, DC_CACHE_BUFSIZE); | ||
140 | } | ||
141 | else | ||
142 | { | ||
143 | file_cache_free(&stream->cache); | ||
144 | } | ||
145 | } | ||
146 | |||
147 | /* discard cache contents and invalidate it */ | ||
148 | void filestr_discard_cache(struct filestr_base *stream) | ||
149 | { | ||
150 | file_cache_reset(stream->cachep); | ||
151 | } | ||
152 | |||
153 | /* Initialize the base descriptor */ | ||
154 | void filestr_base_init(struct filestr_base *stream) | ||
155 | { | ||
156 | filestr_clear(stream); | ||
157 | file_cache_init(&stream->cache); | ||
158 | stream->cachep = &stream->cache; | ||
159 | } | ||
160 | |||
161 | /* free base descriptor resources */ | ||
162 | void filestr_base_destroy(struct filestr_base *stream) | ||
163 | { | ||
164 | filestr_clear(stream); | ||
165 | filestr_free_cache(stream); | ||
166 | } | ||
167 | |||
168 | |||
169 | /** Internal directory service functions **/ | ||
170 | |||
171 | /* read the next directory entry and return its FS info */ | ||
172 | int uncached_readdir_internal(struct filestr_base *stream, | ||
173 | struct file_base_info *infop, | ||
174 | struct fat_direntry *fatent) | ||
175 | { | ||
176 | return fat_readdir(&stream->fatstr, &infop->fatfile.e, | ||
177 | filestr_get_cache(stream), fatent); | ||
178 | } | ||
179 | |||
180 | /* rewind the FS directory to the beginning */ | ||
181 | void uncached_rewinddir_internal(struct file_base_info *infop) | ||
182 | { | ||
183 | fat_rewinddir(&infop->fatfile.e); | ||
184 | } | ||
185 | |||
186 | /* check if the directory is empty (ie. only "." and/or ".." entries | ||
187 | exist at most) */ | ||
188 | int test_dir_empty_internal(struct filestr_base *stream) | ||
189 | { | ||
190 | int rc; | ||
191 | |||
192 | struct file_base_info info; | ||
193 | fat_rewind(&stream->fatstr); | ||
194 | rewinddir_internal(&info); | ||
195 | |||
196 | while ((rc = readdir_internal(stream, &info, &dir_fatent)) > 0) | ||
197 | { | ||
198 | /* no OEM decoding is recessary for this simple check */ | ||
199 | if (!is_dotdir_name(dir_fatent.name)) | ||
200 | { | ||
201 | DEBUGF("Directory not empty\n"); | ||
202 | FILE_ERROR_RETURN(ENOTEMPTY, -1); | ||
203 | } | ||
204 | } | ||
205 | |||
206 | if (rc < 0) | ||
207 | { | ||
208 | DEBUGF("I/O error checking directory: %d\n", rc); | ||
209 | FILE_ERROR_RETURN(EIO, rc * 10 - 2); | ||
210 | } | ||
211 | |||
212 | return 0; | ||
213 | } | ||
214 | |||
215 | /* iso decode the name to UTF-8 */ | ||
216 | void iso_decode_d_name(char *d_name) | ||
217 | { | ||
218 | if (is_dotdir_name(d_name)) | ||
219 | return; | ||
220 | |||
221 | char shortname[13]; | ||
222 | size_t len = strlcpy(shortname, d_name, sizeof (shortname)); | ||
223 | /* This MUST be the default codepage thus not something that could be | ||
224 | loaded on call */ | ||
225 | iso_decode(shortname, d_name, -1, len + 1); | ||
226 | } | ||
227 | |||
228 | #ifdef HAVE_DIRCACHE | ||
229 | /* nullify all the fields of the struct dirent */ | ||
230 | void empty_dirent(struct dirent *entry) | ||
231 | { | ||
232 | entry->d_name[0] = '\0'; | ||
233 | entry->info.attr = 0; | ||
234 | entry->info.size = 0; | ||
235 | entry->info.wrtdate = 0; | ||
236 | entry->info.wrttime = 0; | ||
237 | } | ||
238 | |||
239 | /* fill the native dirinfo from the static dir_fatent */ | ||
240 | void fill_dirinfo_native(struct dirinfo_native *dinp) | ||
241 | { | ||
242 | struct fat_direntry *fatent = get_dir_fatent(); | ||
243 | dinp->attr = fatent->attr; | ||
244 | dinp->size = fatent->filesize; | ||
245 | dinp->wrtdate = fatent->wrtdate; | ||
246 | dinp->wrttime = fatent->wrttime; | ||
247 | } | ||
248 | #endif /* HAVE_DIRCACHE */ | ||
249 | |||
250 | int uncached_readdir_dirent(struct filestr_base *stream, | ||
251 | struct dirscan_info *scanp, | ||
252 | struct dirent *entry) | ||
253 | { | ||
254 | struct fat_direntry fatent; | ||
255 | int rc = fat_readdir(&stream->fatstr, &scanp->fatscan, | ||
256 | filestr_get_cache(stream), &fatent); | ||
257 | |||
258 | /* FAT driver clears the struct fat_dirent if nothing is returned */ | ||
259 | strcpy(entry->d_name, fatent.name); | ||
260 | entry->info.attr = fatent.attr; | ||
261 | entry->info.size = fatent.filesize; | ||
262 | entry->info.wrtdate = fatent.wrtdate; | ||
263 | entry->info.wrttime = fatent.wrttime; | ||
264 | |||
265 | return rc; | ||
266 | } | ||
267 | |||
268 | /* rewind the FS directory pointer */ | ||
269 | void uncached_rewinddir_dirent(struct dirscan_info *scanp) | ||
270 | { | ||
271 | fat_rewinddir(&scanp->fatscan); | ||
272 | } | ||
273 | |||
274 | |||
275 | /** open_stream_internal() helpers and types **/ | ||
276 | |||
277 | struct pathwalk | ||
278 | { | ||
279 | const char *path; /* current location in input path */ | ||
280 | unsigned int callflags; /* callflags parameter */ | ||
281 | struct path_component_info *compinfo; /* compinfo parameter */ | ||
282 | file_size_t filesize; /* size of the file */ | ||
283 | }; | ||
284 | |||
285 | struct pathwalk_component | ||
286 | { | ||
287 | struct file_base_info info; /* basic file information */ | ||
288 | const char *name; /* component name location in path */ | ||
289 | uint16_t length; /* length of name of component */ | ||
290 | uint16_t attr; /* attributes of this component */ | ||
291 | struct pathwalk_component *nextp; /* parent if in use else next free */ | ||
292 | }; | ||
293 | |||
294 | #define WALK_RC_NOT_FOUND 0 /* successfully not found */ | ||
295 | #define WALK_RC_FOUND 1 /* found and opened */ | ||
296 | #define WALK_RC_FOUND_ROOT 2 /* found and opened sys/volume root */ | ||
297 | #define WALK_RC_CONT_AT_ROOT 3 /* continue at root level */ | ||
298 | |||
299 | /* return another struct pathwalk_component from the pool, or NULL if the | ||
300 | pool is completely used */ | ||
301 | static void * pathwalk_comp_alloc_(struct pathwalk_component *parentp) | ||
302 | { | ||
303 | /* static pool that goes to a depth of STATIC_COMP_NUM before allocating | ||
304 | elements from the stack */ | ||
305 | static struct pathwalk_component aux_pathwalk[STATIC_PATHCOMP_NUM]; | ||
306 | struct pathwalk_component *compp = NULL; | ||
307 | |||
308 | if (!parentp) | ||
309 | compp = &aux_pathwalk[0]; /* root */ | ||
310 | else if (PTR_IN_ARRAY(aux_pathwalk, parentp, STATIC_PATHCOMP_NUM-1)) | ||
311 | compp = parentp + 1; | ||
312 | |||
313 | return compp; | ||
314 | } | ||
315 | |||
316 | /* allocates components from the pool or stack depending upon the depth */ | ||
317 | #define pathwalk_comp_alloc(parentp) \ | ||
318 | ({ \ | ||
319 | void *__c = pathwalk_comp_alloc_(parentp); \ | ||
320 | if (!__builtin_constant_p(parentp) && !__c) \ | ||
321 | __c = alloca(sizeof (struct pathwalk_component)); \ | ||
322 | (struct pathwalk_component *)__c; \ | ||
323 | }) | ||
324 | |||
325 | /* fill in the details of the struct path_component_info for caller */ | ||
326 | static int fill_path_compinfo(struct pathwalk *walkp, | ||
327 | struct pathwalk_component *compp, | ||
328 | int rc) | ||
329 | { | ||
330 | if (rc == -ENOENT) | ||
331 | { | ||
332 | /* this component wasn't found; see if more of them exist or path | ||
333 | has trailing separators; if it does, this component should be | ||
334 | interpreted as a directory even if it doesn't exist and it's the | ||
335 | final one; also, this has to be the last part or it's an error*/ | ||
336 | const char *p = GOBBLE_PATH_SEPCH(walkp->path); | ||
337 | if (!*p) | ||
338 | { | ||
339 | if (p > walkp->path) | ||
340 | compp->attr |= ATTR_DIRECTORY; | ||
341 | |||
342 | rc = WALK_RC_NOT_FOUND; /* successfully not found */ | ||
343 | } | ||
344 | } | ||
345 | |||
346 | if (rc >= 0) | ||
347 | { | ||
348 | struct path_component_info *compinfo = walkp->compinfo; | ||
349 | compinfo->name = compp->name; | ||
350 | compinfo->length = compp->length; | ||
351 | compinfo->attr = compp->attr; | ||
352 | compinfo->filesize = walkp->filesize; | ||
353 | compinfo->parentinfo = (compp->nextp ?: compp)->info; | ||
354 | } | ||
355 | |||
356 | return rc; | ||
357 | } | ||
358 | |||
359 | /* open the final stream itself, if found */ | ||
360 | static int walk_open_info(struct pathwalk *walkp, | ||
361 | struct pathwalk_component *compp, | ||
362 | int rc, | ||
363 | struct filestr_base *stream) | ||
364 | { | ||
365 | /* this may make adjustments to things; do it first */ | ||
366 | if (walkp->compinfo) | ||
367 | rc = fill_path_compinfo(walkp, compp, rc); | ||
368 | |||
369 | if (rc < 0 || rc == WALK_RC_NOT_FOUND) | ||
370 | return rc; | ||
371 | |||
372 | unsigned int callflags = walkp->callflags; | ||
373 | bool isdir = compp->attr & ATTR_DIRECTORY; | ||
374 | |||
375 | /* type must match what is called for */ | ||
376 | switch (callflags & FF_TYPEMASK) | ||
377 | { | ||
378 | case FF_FILE: | ||
379 | if (!isdir) break; | ||
380 | DEBUGF("File is a directory\n"); | ||
381 | return -EISDIR; | ||
382 | case FF_DIR: | ||
383 | if (isdir) break; | ||
384 | DEBUGF("File is not a directory\n"); | ||
385 | return -ENOTDIR; | ||
386 | /* FF_ANYTYPE: basically, ignore FF_FILE/FF_DIR */ | ||
387 | } | ||
388 | |||
389 | /* FO_DIRECTORY must match type */ | ||
390 | if (isdir) | ||
391 | callflags |= FO_DIRECTORY; | ||
392 | else | ||
393 | callflags &= ~FO_DIRECTORY; | ||
394 | |||
395 | fileop_onopen_internal(stream, &compp->info, callflags); | ||
396 | return compp->nextp ? WALK_RC_FOUND : WALK_RC_FOUND_ROOT; | ||
397 | } | ||
398 | |||
399 | /* check the component against the prefix test info */ | ||
400 | static void walk_check_prefix(struct pathwalk *walkp, | ||
401 | struct pathwalk_component *compp) | ||
402 | { | ||
403 | if (compp->attr & ATTR_PREFIX) | ||
404 | return; | ||
405 | |||
406 | if (!fat_file_is_same(&compp->info.fatfile, | ||
407 | &walkp->compinfo->prefixp->fatfile)) | ||
408 | return; | ||
409 | |||
410 | compp->attr |= ATTR_PREFIX; | ||
411 | } | ||
412 | |||
413 | /* opens the component named by 'comp' in the directory 'parent' */ | ||
414 | static NO_INLINE int open_path_component(struct pathwalk *walkp, | ||
415 | struct pathwalk_component *compp, | ||
416 | struct filestr_base *stream) | ||
417 | { | ||
418 | int rc; | ||
419 | |||
420 | /* create a null-terminated copy of the component name */ | ||
421 | char *compname = strmemdupa(compp->name, compp->length); | ||
422 | |||
423 | unsigned int callflags = walkp->callflags; | ||
424 | struct pathwalk_component *parentp = compp->nextp; | ||
425 | |||
426 | /* children inherit the prefix coloring from the parent */ | ||
427 | compp->attr = parentp->attr & ATTR_PREFIX; | ||
428 | |||
429 | /* most of the next would be abstracted elsewhere if doing other | ||
430 | filesystems */ | ||
431 | |||
432 | /* scan parent for name; stream is converted to this parent */ | ||
433 | file_cache_reset(stream->cachep); | ||
434 | stream->infop = &parentp->info; | ||
435 | fat_filestr_init(&stream->fatstr, &parentp->info.fatfile); | ||
436 | rewinddir_internal(&compp->info); | ||
437 | |||
438 | while ((rc = readdir_internal(stream, &compp->info, &dir_fatent)) > 0) | ||
439 | { | ||
440 | if (rc > 1 && !(callflags & FF_NOISO)) | ||
441 | iso_decode_d_name(dir_fatent.name); | ||
442 | |||
443 | if (!strcasecmp(compname, dir_fatent.name)) | ||
444 | break; | ||
445 | } | ||
446 | |||
447 | if (rc == 0) | ||
448 | { | ||
449 | DEBUGF("File/directory not found\n"); | ||
450 | return -ENOENT; | ||
451 | } | ||
452 | else if (rc < 0) | ||
453 | { | ||
454 | DEBUGF("I/O error reading directory %d\n", rc); | ||
455 | return -EIO; | ||
456 | } | ||
457 | |||
458 | rc = fat_open(stream->fatstr.fatfilep, dir_fatent.firstcluster, | ||
459 | &compp->info.fatfile); | ||
460 | if (rc < 0) | ||
461 | { | ||
462 | DEBUGF("I/O error opening file/directory %s (%d)\n", | ||
463 | compname, rc); | ||
464 | return -EIO; | ||
465 | } | ||
466 | |||
467 | walkp->filesize = dir_fatent.filesize; | ||
468 | compp->attr |= dir_fatent.attr; | ||
469 | |||
470 | if (callflags & FF_CHECKPREFIX) | ||
471 | walk_check_prefix(walkp, compp); | ||
472 | |||
473 | return WALK_RC_FOUND; | ||
474 | } | ||
475 | |||
476 | /* parse a path component, open it and process the next */ | ||
477 | static NO_INLINE int | ||
478 | walk_path(struct pathwalk *walkp, struct pathwalk_component *compp, | ||
479 | struct filestr_base *stream) | ||
480 | { | ||
481 | int rc = WALK_RC_FOUND; | ||
482 | |||
483 | if (walkp->callflags & FF_CHECKPREFIX) | ||
484 | walk_check_prefix(walkp, compp); | ||
485 | |||
486 | /* alloca is used in a loop, but we reuse any blocks previously allocated | ||
487 | if we went up then back down; if the path takes us back to the root, then | ||
488 | everything is cleaned automatically */ | ||
489 | struct pathwalk_component *freep = NULL; | ||
490 | |||
491 | const char *name; | ||
492 | ssize_t len; | ||
493 | |||
494 | while ((len = parse_path_component(&walkp->path, &name))) | ||
495 | { | ||
496 | /* whatever is to be a parent must be a directory */ | ||
497 | if (!(compp->attr & ATTR_DIRECTORY)) | ||
498 | return -ENOTDIR; | ||
499 | |||
500 | switch (len) | ||
501 | { | ||
502 | case 1: | ||
503 | case 2: | ||
504 | /* check for "." and ".." */ | ||
505 | if (name[0] == '.') | ||
506 | { | ||
507 | if (len == 2 && name[1] == '.') | ||
508 | { | ||
509 | struct pathwalk_component *parentp = compp->nextp; | ||
510 | if (!parentp) | ||
511 | return WALK_RC_CONT_AT_ROOT; | ||
512 | |||
513 | compp->nextp = freep; | ||
514 | freep = compp; | ||
515 | compp = parentp; | ||
516 | } | ||
517 | |||
518 | break; | ||
519 | } | ||
520 | |||
521 | /* fallthrough */ | ||
522 | default: | ||
523 | if (len >= MAX_NAME) | ||
524 | return -ENAMETOOLONG; | ||
525 | |||
526 | struct pathwalk_component *newp = freep; | ||
527 | if (!newp) | ||
528 | newp = pathwalk_comp_alloc(compp); | ||
529 | else | ||
530 | freep = freep->nextp; | ||
531 | |||
532 | newp->nextp = compp; | ||
533 | compp = newp; | ||
534 | |||
535 | compp->name = name; | ||
536 | compp->length = len; | ||
537 | |||
538 | rc = open_path_component(walkp, compp, stream); | ||
539 | if (rc < 0) | ||
540 | break; | ||
541 | } | ||
542 | } | ||
543 | |||
544 | return walk_open_info(walkp, compp, rc, stream); | ||
545 | } | ||
546 | |||
547 | /* open a stream given a path to the resource */ | ||
548 | int open_stream_internal(const char *path, unsigned int callflags, | ||
549 | struct filestr_base *stream, | ||
550 | struct path_component_info *compinfo) | ||
551 | { | ||
552 | DEBUGF("%s(path=\"%s\",flg=%X,str=%p,compinfo=%p)\n", path, callflags, | ||
553 | stream, compinfo); | ||
554 | int rc; | ||
555 | |||
556 | filestr_base_init(stream); | ||
557 | |||
558 | if (!path_is_absolute(path)) | ||
559 | { | ||
560 | /* while this supports relative components, there is currently no | ||
561 | current working directory concept at this level by which to | ||
562 | fully qualify the path (though that would not be excessively | ||
563 | difficult to add) */ | ||
564 | DEBUGF("\"%s\" is not an absolute path\n" | ||
565 | "Only absolute paths currently supported.\n", path); | ||
566 | FILE_ERROR(path ? ENOENT : EFAULT, -1); | ||
567 | } | ||
568 | |||
569 | /* if !compinfo, then the result of this check is not visible anyway */ | ||
570 | if (!compinfo) | ||
571 | callflags &= ~FF_CHECKPREFIX; | ||
572 | |||
573 | struct pathwalk walk; | ||
574 | walk.path = path; | ||
575 | walk.callflags = callflags; | ||
576 | walk.compinfo = compinfo; | ||
577 | walk.filesize = 0; | ||
578 | |||
579 | struct pathwalk_component *rootp = pathwalk_comp_alloc(NULL); | ||
580 | rootp->nextp = NULL; | ||
581 | rootp->attr = ATTR_DIRECTORY; | ||
582 | |||
583 | #ifdef HAVE_MULTIVOLUME | ||
584 | int volume = 0, rootrc = WALK_RC_FOUND; | ||
585 | #endif /* HAVE_MULTIVOLUME */ | ||
586 | |||
587 | while (1) | ||
588 | { | ||
589 | const char *pathptr = walk.path; | ||
590 | |||
591 | #ifdef HAVE_MULTIVOLUME | ||
592 | /* this seamlessly integrates secondary filesystems into the | ||
593 | root namespace (e.g. "/<0>/../../<1>/../foo/." :<=> "/foo") */ | ||
594 | const char *p; | ||
595 | volume = path_strip_volume(pathptr, &p, false); | ||
596 | if (!CHECK_VOL(volume)) | ||
597 | { | ||
598 | DEBUGF("No such device or address: %d\n", volume); | ||
599 | FILE_ERROR(ENXIO, -2); | ||
600 | } | ||
601 | |||
602 | /* the root of this subpath is the system root? */ | ||
603 | rootrc = p == pathptr ? WALK_RC_FOUND_ROOT : WALK_RC_FOUND; | ||
604 | walk.path = p; | ||
605 | #endif /* HAVE_MULTIVOLUME */ | ||
606 | |||
607 | /* set name to start at last leading separator; names of volume | ||
608 | specifiers will be returned as "/<fooN>" */ | ||
609 | rootp->name = GOBBLE_PATH_SEPCH(pathptr) - 1; | ||
610 | rootp->length = | ||
611 | IF_MV( rootrc == WALK_RC_FOUND ? p - rootp->name : ) 1; | ||
612 | |||
613 | rc = fat_open_rootdir(IF_MV(volume,) &rootp->info.fatfile); | ||
614 | if (rc < 0) | ||
615 | { | ||
616 | /* not mounted */ | ||
617 | DEBUGF("No such device or address: %d\n", IF_MV_VOL(volume)); | ||
618 | rc = -ENXIO; | ||
619 | break; | ||
620 | } | ||
621 | |||
622 | get_rootinfo_internal(&rootp->info); | ||
623 | rc = walk_path(&walk, rootp, stream); | ||
624 | if (rc != WALK_RC_CONT_AT_ROOT) | ||
625 | break; | ||
626 | } | ||
627 | |||
628 | switch (rc) | ||
629 | { | ||
630 | case WALK_RC_FOUND_ROOT: | ||
631 | IF_MV( rc = rootrc; ) | ||
632 | case WALK_RC_NOT_FOUND: | ||
633 | case WALK_RC_FOUND: | ||
634 | break; | ||
635 | |||
636 | default: /* utter, abject failure :`( */ | ||
637 | DEBUGF("Open failed: rc=%d, errno=%d\n", rc, errno); | ||
638 | filestr_base_destroy(stream); | ||
639 | FILE_ERROR(-rc, -2); | ||
640 | } | ||
641 | |||
642 | file_cache_reset(stream->cachep); | ||
643 | |||
644 | file_error: | ||
645 | return rc; | ||
646 | } | ||
647 | |||
648 | /* close the stream referenced by 'stream' */ | ||
649 | int close_stream_internal(struct filestr_base *stream) | ||
650 | { | ||
651 | int rc; | ||
652 | unsigned int foflags = fileobj_get_flags(stream); | ||
653 | |||
654 | if ((foflags & (FO_SINGLE|FO_REMOVED)) == (FO_SINGLE|FO_REMOVED)) | ||
655 | { | ||
656 | /* nothing is referencing it so now remove the file's data */ | ||
657 | rc = fat_remove(&stream->infop->fatfile, FAT_RM_DATA); | ||
658 | if (rc < 0) | ||
659 | { | ||
660 | DEBUGF("I/O error removing file data: %d\n", rc); | ||
661 | FILE_ERROR(EIO, rc * 10 - 1); | ||
662 | } | ||
663 | } | ||
664 | |||
665 | rc = 0; | ||
666 | file_error: | ||
667 | /* close no matter what */ | ||
668 | fileop_onclose_internal(stream); | ||
669 | return rc; | ||
670 | } | ||
671 | |||
672 | /* create a new stream in the parent directory */ | ||
673 | int create_stream_internal(struct file_base_info *parentinfop, | ||
674 | const char *basename, size_t length, | ||
675 | unsigned int attr, unsigned int callflags, | ||
676 | struct filestr_base *stream) | ||
677 | { | ||
678 | /* assumes an attempt was made beforehand to open *stream with | ||
679 | open_stream_internal() which returned zero (successfully not found), | ||
680 | so does not initialize it here */ | ||
681 | const char * const name = strmemdupa(basename, length); | ||
682 | DEBUGF("Creating \"%s\"\n", name); | ||
683 | |||
684 | struct file_base_info info; | ||
685 | int rc = fat_create_file(&parentinfop->fatfile, name, attr, | ||
686 | &info.fatfile, get_dir_fatent_dircache()); | ||
687 | if (rc < 0) | ||
688 | { | ||
689 | DEBUGF("Create failed: %d\n", rc); | ||
690 | FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 1); | ||
691 | } | ||
692 | |||
693 | /* dir_fatent is implicit arg */ | ||
694 | fileop_oncreate_internal(stream, &info, callflags, parentinfop, name); | ||
695 | rc = 0; | ||
696 | file_error: | ||
697 | return rc; | ||
698 | } | ||
699 | |||
700 | /* removes files and directories - back-end to remove() and rmdir() */ | ||
701 | int remove_stream_internal(const char *path, struct filestr_base *stream, | ||
702 | unsigned int callflags) | ||
703 | { | ||
704 | /* Only FF_* flags should be in callflags */ | ||
705 | int rc; | ||
706 | |||
707 | struct filestr_base opened_stream; | ||
708 | if (!stream) | ||
709 | stream = &opened_stream; | ||
710 | |||
711 | if (stream == &opened_stream) | ||
712 | { | ||
713 | /* no stream provided so open local one */ | ||
714 | rc = open_stream_internal(path, callflags, stream, NULL); | ||
715 | if (rc < 0) | ||
716 | { | ||
717 | DEBUGF("Failed opening path: %d\n", rc); | ||
718 | FILE_ERROR(ERRNO, rc * 10 - 1); | ||
719 | } | ||
720 | } | ||
721 | /* else ignore the 'path' argument */ | ||
722 | |||
723 | if (callflags & FF_DIR) | ||
724 | { | ||
725 | /* directory to be removed must be empty */ | ||
726 | rc = test_dir_empty_internal(stream); | ||
727 | if (rc < 0) | ||
728 | FILE_ERROR(ERRNO, rc * 10 - 2); | ||
729 | } | ||
730 | |||
731 | /* save old info since fat_remove() will destroy the dir info */ | ||
732 | struct file_base_info oldinfo = *stream->infop; | ||
733 | rc = fat_remove(&stream->infop->fatfile, FAT_RM_DIRENTRIES); | ||
734 | if (rc < 0) | ||
735 | { | ||
736 | DEBUGF("I/O error removing dir entries: %d\n", rc); | ||
737 | FILE_ERROR(EIO, rc * 10 - 3); | ||
738 | } | ||
739 | |||
740 | fileop_onremove_internal(stream, &oldinfo); | ||
741 | |||
742 | rc = 0; | ||
743 | file_error: | ||
744 | if (stream == &opened_stream) | ||
745 | { | ||
746 | /* will do removal of data below if this is the only reference */ | ||
747 | int rc2 = close_stream_internal(stream); | ||
748 | if (rc2 < 0 && rc >= 0) | ||
749 | { | ||
750 | rc = rc2 * 10 - 4; | ||
751 | DEBUGF("Success but failed closing stream: %d\n", rc); | ||
752 | } | ||
753 | } | ||
754 | |||
755 | return rc; | ||
756 | } | ||
757 | |||
758 | /* test file/directory existence with constraints */ | ||
759 | int test_stream_exists_internal(const char *path, unsigned int callflags) | ||
760 | { | ||
761 | /* only FF_* flags should be in callflags */ | ||
762 | struct filestr_base stream; | ||
763 | int rc = open_stream_internal(path, callflags, &stream, NULL); | ||
764 | if (rc > 0) | ||
765 | close_stream_internal(&stream); | ||
766 | |||
767 | return rc; | ||
768 | } | ||
769 | |||
770 | /* one-time init at startup */ | ||
771 | void filesystem_init(void) | ||
772 | { | ||
773 | mrsw_init(&file_internal_mrsw); | ||
774 | dc_init(); | ||
775 | fileobj_mgr_init(); | ||
776 | } | ||