summaryrefslogtreecommitdiff
path: root/firmware/common/file_internal.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/common/file_internal.c')
-rw-r--r--firmware/common/file_internal.c776
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 */
41struct fat_direntry dir_fatent;
42#else
43struct fat_direntry dir_fatent IBSS_ATTR;
44#endif
45
46struct mrsw_lock file_internal_mrsw SHAREDBSS_ATTR;
47
48
49/** File stream sector caching **/
50
51/* initialize a new cache structure */
52void 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 */
60void 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 */
67void 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 */
76void 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
90static 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 */
100void 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 */
106void 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 */
113void 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 */
129void 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 */
148void filestr_discard_cache(struct filestr_base *stream)
149{
150 file_cache_reset(stream->cachep);
151}
152
153/* Initialize the base descriptor */
154void 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 */
162void 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 */
172int 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 */
181void 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) */
188int 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 */
216void 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 */
230void 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 */
240void 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
250int 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 */
269void uncached_rewinddir_dirent(struct dirscan_info *scanp)
270{
271 fat_rewinddir(&scanp->fatscan);
272}
273
274
275/** open_stream_internal() helpers and types **/
276
277struct 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
285struct 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 */
301static 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 */
326static 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 */
360static 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 */
400static 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' */
414static 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 */
477static NO_INLINE int
478walk_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 */
548int 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
644file_error:
645 return rc;
646}
647
648/* close the stream referenced by 'stream' */
649int 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;
666file_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 */
673int 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;
696file_error:
697 return rc;
698}
699
700/* removes files and directories - back-end to remove() and rmdir() */
701int 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;
743file_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 */
759int 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 */
771void filesystem_init(void)
772{
773 mrsw_init(&file_internal_mrsw);
774 dc_init();
775 fileobj_mgr_init();
776}