diff options
Diffstat (limited to 'firmware')
-rw-r--r-- | firmware/SOURCES | 1 | ||||
-rw-r--r-- | firmware/common/dir.c | 92 | ||||
-rw-r--r-- | firmware/common/dircache.c | 9 | ||||
-rw-r--r-- | firmware/common/disk.c | 2 | ||||
-rw-r--r-- | firmware/common/file.c | 2 | ||||
-rw-r--r-- | firmware/common/file_internal.c | 95 | ||||
-rw-r--r-- | firmware/common/fileobj_mgr.c | 114 | ||||
-rw-r--r-- | firmware/common/pathfuncs.c | 43 | ||||
-rw-r--r-- | firmware/common/rb_namespace.c | 289 | ||||
-rw-r--r-- | firmware/export/mv.h | 4 | ||||
-rw-r--r-- | firmware/export/pathfuncs.h | 6 | ||||
-rw-r--r-- | firmware/export/rbpaths.h | 3 | ||||
-rw-r--r-- | firmware/include/dircache_redirect.h | 16 | ||||
-rw-r--r-- | firmware/include/file_internal.h | 22 | ||||
-rw-r--r-- | firmware/include/fileobj_mgr.h | 5 | ||||
-rw-r--r-- | firmware/include/fs_defines.h | 11 | ||||
-rw-r--r-- | firmware/include/rb_namespace.h | 79 |
17 files changed, 589 insertions, 204 deletions
diff --git a/firmware/SOURCES b/firmware/SOURCES index a68d10ec76..f1c7621244 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES | |||
@@ -238,6 +238,7 @@ common/dircache.c | |||
238 | common/pathfuncs.c | 238 | common/pathfuncs.c |
239 | common/fdprintf.c | 239 | common/fdprintf.c |
240 | common/linked_list.c | 240 | common/linked_list.c |
241 | common/rb_namespace.c | ||
241 | common/strcasecmp.c | 242 | common/strcasecmp.c |
242 | common/strcasestr.c | 243 | common/strcasestr.c |
243 | common/strnatcmp.c | 244 | common/strnatcmp.c |
diff --git a/firmware/common/dir.c b/firmware/common/dir.c index f89129ae34..85e6ff316b 100644 --- a/firmware/common/dir.c +++ b/firmware/common/dir.c | |||
@@ -27,17 +27,14 @@ | |||
27 | #include "dir.h" | 27 | #include "dir.h" |
28 | #include "pathfuncs.h" | 28 | #include "pathfuncs.h" |
29 | #include "fileobj_mgr.h" | 29 | #include "fileobj_mgr.h" |
30 | #include "dircache_redirect.h" | 30 | #include "rb_namespace.h" |
31 | 31 | ||
32 | /* structure used for open directory streams */ | 32 | /* structure used for open directory streams */ |
33 | static struct dirstr_desc | 33 | static struct dirstr_desc |
34 | { | 34 | { |
35 | struct filestr_base stream; /* basic stream info (first!) */ | 35 | struct filestr_base stream; /* basic stream info (first!) */ |
36 | struct dirscan_info scan; /* directory scan cursor */ | 36 | struct ns_scan_info scan; /* directory scan cursor */ |
37 | struct dirent entry; /* current parsed entry information */ | 37 | struct dirent entry; /* current parsed entry information */ |
38 | #ifdef HAVE_MULTIVOLUME | ||
39 | int volumecounter; /* counter for root volume entries */ | ||
40 | #endif | ||
41 | } open_streams[MAX_OPEN_DIRS]; | 38 | } open_streams[MAX_OPEN_DIRS]; |
42 | 39 | ||
43 | /* check and return a struct dirstr_desc* from a DIR* */ | 40 | /* check and return a struct dirstr_desc* from a DIR* */ |
@@ -47,7 +44,7 @@ static struct dirstr_desc * get_dirstr(DIR *dirp) | |||
47 | 44 | ||
48 | if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS)) | 45 | if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS)) |
49 | dir = NULL; | 46 | dir = NULL; |
50 | else if (dir->stream.flags & FDO_BUSY) | 47 | else if (dir->stream.flags & (FDO_BUSY|FD_VALID)) |
51 | return dir; | 48 | return dir; |
52 | 49 | ||
53 | int errnum; | 50 | int errnum; |
@@ -104,49 +101,6 @@ static struct dirstr_desc * alloc_dirstr(void) | |||
104 | return NULL; | 101 | return NULL; |
105 | } | 102 | } |
106 | 103 | ||
107 | #ifdef HAVE_MULTIVOLUME | ||
108 | static int readdir_volume_inner(struct dirstr_desc *dir, struct dirent *entry) | ||
109 | { | ||
110 | /* Volumes (secondary file systems) get inserted into the system root | ||
111 | * directory. If the path specified volume 0, enumeration will not | ||
112 | * include other volumes, but just its own files and directories. | ||
113 | * | ||
114 | * Fake special directories, which don't really exist, that will get | ||
115 | * redirected upon opendir() | ||
116 | */ | ||
117 | while (++dir->volumecounter < NUM_VOLUMES) | ||
118 | { | ||
119 | /* on the system root */ | ||
120 | if (!fat_ismounted(dir->volumecounter)) | ||
121 | continue; | ||
122 | |||
123 | get_volume_name(dir->volumecounter, entry->d_name); | ||
124 | dir->entry.info.attr = ATTR_MOUNT_POINT; | ||
125 | dir->entry.info.size = 0; | ||
126 | dir->entry.info.wrtdate = 0; | ||
127 | dir->entry.info.wrttime = 0; | ||
128 | return 1; | ||
129 | } | ||
130 | |||
131 | /* do normal directory entry fetching */ | ||
132 | return 0; | ||
133 | } | ||
134 | #endif /* HAVE_MULTIVOLUME */ | ||
135 | |||
136 | static inline int readdir_volume(struct dirstr_desc *dir, | ||
137 | struct dirent *entry) | ||
138 | { | ||
139 | #ifdef HAVE_MULTIVOLUME | ||
140 | /* fetch virtual volume entries? */ | ||
141 | if (dir->volumecounter < NUM_VOLUMES) | ||
142 | return readdir_volume_inner(dir, entry); | ||
143 | #endif /* HAVE_MULTIVOLUME */ | ||
144 | |||
145 | /* do normal directory entry fetching */ | ||
146 | return 0; | ||
147 | (void)dir; (void)entry; | ||
148 | } | ||
149 | |||
150 | 104 | ||
151 | /** POSIX interface **/ | 105 | /** POSIX interface **/ |
152 | 106 | ||
@@ -165,21 +119,13 @@ DIR * opendir(const char *dirname) | |||
165 | if (!dir) | 119 | if (!dir) |
166 | FILE_ERROR(EMFILE, RC); | 120 | FILE_ERROR(EMFILE, RC); |
167 | 121 | ||
168 | rc = open_stream_internal(dirname, FF_DIR, &dir->stream, NULL); | 122 | rc = ns_open_stream(dirname, FF_DIR, &dir->stream, &dir->scan); |
169 | if (rc < 0) | 123 | if (rc < 0) |
170 | { | 124 | { |
171 | DEBUGF("Open failed: %d\n", rc); | 125 | DEBUGF("Open failed: %d\n", rc); |
172 | FILE_ERROR(ERRNO, RC); | 126 | FILE_ERROR(ERRNO, RC); |
173 | } | 127 | } |
174 | 128 | ||
175 | #ifdef HAVE_MULTIVOLUME | ||
176 | /* volume counter is relevant only to the system root */ | ||
177 | dir->volumecounter = rc > 1 ? 0 : INT_MAX; | ||
178 | #endif /* HAVE_MULTIVOLUME */ | ||
179 | |||
180 | fat_rewind(&dir->stream.fatstr); | ||
181 | rewinddir_dirent(&dir->scan); | ||
182 | |||
183 | dirp = (DIR *)dir; | 129 | dirp = (DIR *)dir; |
184 | file_error: | 130 | file_error: |
185 | file_internal_unlock_WRITER(); | 131 | file_internal_unlock_WRITER(); |
@@ -204,7 +150,7 @@ int closedir(DIR *dirp) | |||
204 | FILE_ERROR(EBADF, -2); | 150 | FILE_ERROR(EBADF, -2); |
205 | } | 151 | } |
206 | 152 | ||
207 | rc = close_stream_internal(&dir->stream); | 153 | rc = ns_close_stream(&dir->stream); |
208 | if (rc < 0) | 154 | if (rc < 0) |
209 | FILE_ERROR(ERRNO, rc * 10 - 3); | 155 | FILE_ERROR(ERRNO, rc * 10 - 3); |
210 | 156 | ||
@@ -222,16 +168,11 @@ struct dirent * readdir(DIR *dirp) | |||
222 | 168 | ||
223 | struct dirent *res = NULL; | 169 | struct dirent *res = NULL; |
224 | 170 | ||
225 | int rc = readdir_volume(dir, &dir->entry); | 171 | int rc = ns_readdir_dirent(&dir->stream, &dir->scan, &dir->entry); |
226 | if (rc == 0) | ||
227 | { | ||
228 | rc = readdir_dirent(&dir->stream, &dir->scan, &dir->entry); | ||
229 | if (rc < 0) | ||
230 | FILE_ERROR(EIO, RC); | ||
231 | } | ||
232 | |||
233 | if (rc > 0) | 172 | if (rc > 0) |
234 | res = &dir->entry; | 173 | res = &dir->entry; |
174 | else if (rc < 0) | ||
175 | FILE_ERROR(EIO, RC); | ||
235 | 176 | ||
236 | file_error: | 177 | file_error: |
237 | RELEASE_DIRSTR(READER, dir); | 178 | RELEASE_DIRSTR(READER, dir); |
@@ -258,13 +199,9 @@ int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) | |||
258 | if (!dir) | 199 | if (!dir) |
259 | FILE_ERROR_RETURN(ERRNO, -1); | 200 | FILE_ERROR_RETURN(ERRNO, -1); |
260 | 201 | ||
261 | int rc = readdir_volume(dir, entry); | 202 | int rc = ns_readdir_dirent(&dir->stream, &dir->scan, entry); |
262 | if (rc == 0) | 203 | if (rc < 0) |
263 | { | 204 | FILE_ERROR(EIO, rc * 10 - 4); |
264 | rc = readdir_dirent(&dir->stream, &dir->scan, entry); | ||
265 | if (rc < 0) | ||
266 | FILE_ERROR(EIO, rc * 10 - 4); | ||
267 | } | ||
268 | 205 | ||
269 | file_error: | 206 | file_error: |
270 | RELEASE_DIRSTR(READER, dir); | 207 | RELEASE_DIRSTR(READER, dir); |
@@ -288,12 +225,7 @@ void rewinddir(DIR *dirp) | |||
288 | if (!dir) | 225 | if (!dir) |
289 | FILE_ERROR_RETURN(ERRNO); | 226 | FILE_ERROR_RETURN(ERRNO); |
290 | 227 | ||
291 | rewinddir_dirent(&dir->scan); | 228 | ns_dirscan_rewind(&dir->scan); |
292 | |||
293 | #ifdef HAVE_MULTIVOLUME | ||
294 | if (dir->volumecounter != INT_MAX) | ||
295 | dir->volumecounter = 0; | ||
296 | #endif /* HAVE_MULTIVOLUME */ | ||
297 | 229 | ||
298 | RELEASE_DIRSTR(READER, dir); | 230 | RELEASE_DIRSTR(READER, dir); |
299 | } | 231 | } |
diff --git a/firmware/common/dircache.c b/firmware/common/dircache.c index 0cdaf1bd4a..cc65d2d540 100644 --- a/firmware/common/dircache.c +++ b/firmware/common/dircache.c | |||
@@ -2541,13 +2541,10 @@ static ssize_t get_path_sub(int idx, struct get_path_sub_data *data) | |||
2541 | cename = ""; | 2541 | cename = ""; |
2542 | 2542 | ||
2543 | #ifdef HAVE_MULTIVOLUME | 2543 | #ifdef HAVE_MULTIVOLUME |
2544 | /* prepend the volume specifier */ | ||
2544 | int volume = IF_MV_VOL(-idx - 1); | 2545 | int volume = IF_MV_VOL(-idx - 1); |
2545 | if (volume > 0) | 2546 | cename = alloca(VOL_MAX_LEN+1); |
2546 | { | 2547 | get_volume_name(volume, cename); |
2547 | /* prepend the volume specifier for volumes > 0 */ | ||
2548 | cename = alloca(VOL_MAX_LEN+1); | ||
2549 | get_volume_name(volume, cename); | ||
2550 | } | ||
2551 | #endif /* HAVE_MULTIVOLUME */ | 2548 | #endif /* HAVE_MULTIVOLUME */ |
2552 | 2549 | ||
2553 | data->serialhash = dc_hash_serialnum(get_idx_dcvolp(idx)->serialnum, | 2550 | data->serialhash = dc_hash_serialnum(get_idx_dcvolp(idx)->serialnum, |
diff --git a/firmware/common/disk.c b/firmware/common/disk.c index 98c273b26d..3bd88f66a8 100644 --- a/firmware/common/disk.c +++ b/firmware/common/disk.c | |||
@@ -27,7 +27,7 @@ | |||
27 | #include "disk_cache.h" | 27 | #include "disk_cache.h" |
28 | #include "fileobj_mgr.h" | 28 | #include "fileobj_mgr.h" |
29 | #include "dir.h" | 29 | #include "dir.h" |
30 | #include "dircache_redirect.h" | 30 | #include "rb_namespace.h" |
31 | #include "disk.h" | 31 | #include "disk.h" |
32 | 32 | ||
33 | 33 | ||
diff --git a/firmware/common/file.c b/firmware/common/file.c index cb918c6eab..893e475a32 100644 --- a/firmware/common/file.c +++ b/firmware/common/file.c | |||
@@ -28,7 +28,7 @@ | |||
28 | #include "file.h" | 28 | #include "file.h" |
29 | #include "fileobj_mgr.h" | 29 | #include "fileobj_mgr.h" |
30 | #include "disk_cache.h" | 30 | #include "disk_cache.h" |
31 | #include "dircache_redirect.h" | 31 | #include "rb_namespace.h" |
32 | #include "string-extra.h" | 32 | #include "string-extra.h" |
33 | 33 | ||
34 | /** | 34 | /** |
diff --git a/firmware/common/file_internal.c b/firmware/common/file_internal.c index fe18f90056..45f412e166 100644 --- a/firmware/common/file_internal.c +++ b/firmware/common/file_internal.c | |||
@@ -26,9 +26,7 @@ | |||
26 | #include "pathfuncs.h" | 26 | #include "pathfuncs.h" |
27 | #include "disk_cache.h" | 27 | #include "disk_cache.h" |
28 | #include "fileobj_mgr.h" | 28 | #include "fileobj_mgr.h" |
29 | #include "dir.h" | 29 | #include "rb_namespace.h" |
30 | #include "dircache_redirect.h" | ||
31 | #include "dircache.h" | ||
32 | #include "string-extra.h" | 30 | #include "string-extra.h" |
33 | #include "rbunicode.h" | 31 | #include "rbunicode.h" |
34 | 32 | ||
@@ -87,9 +85,10 @@ void file_cache_free(struct filestr_cache *cachep) | |||
87 | 85 | ||
88 | /** Stream base APIs **/ | 86 | /** Stream base APIs **/ |
89 | 87 | ||
90 | static inline void filestr_clear(struct filestr_base *stream) | 88 | static inline void filestr_clear(struct filestr_base *stream, |
89 | unsigned int flags) | ||
91 | { | 90 | { |
92 | stream->flags = 0; | 91 | stream->flags = flags; |
93 | stream->bindp = NULL; | 92 | stream->bindp = NULL; |
94 | #if 0 | 93 | #if 0 |
95 | stream->mtx = NULL; | 94 | stream->mtx = NULL; |
@@ -153,7 +152,7 @@ void filestr_discard_cache(struct filestr_base *stream) | |||
153 | /* Initialize the base descriptor */ | 152 | /* Initialize the base descriptor */ |
154 | void filestr_base_init(struct filestr_base *stream) | 153 | void filestr_base_init(struct filestr_base *stream) |
155 | { | 154 | { |
156 | filestr_clear(stream); | 155 | filestr_clear(stream, FD_VALID); |
157 | file_cache_init(&stream->cache); | 156 | file_cache_init(&stream->cache); |
158 | stream->cachep = &stream->cache; | 157 | stream->cachep = &stream->cache; |
159 | } | 158 | } |
@@ -161,7 +160,7 @@ void filestr_base_init(struct filestr_base *stream) | |||
161 | /* free base descriptor resources */ | 160 | /* free base descriptor resources */ |
162 | void filestr_base_destroy(struct filestr_base *stream) | 161 | void filestr_base_destroy(struct filestr_base *stream) |
163 | { | 162 | { |
164 | filestr_clear(stream); | 163 | filestr_clear(stream, 0); |
165 | filestr_free_cache(stream); | 164 | filestr_free_cache(stream); |
166 | } | 165 | } |
167 | 166 | ||
@@ -293,7 +292,7 @@ struct pathwalk_component | |||
293 | 292 | ||
294 | #define WALK_RC_NOT_FOUND 0 /* successfully not found (aid for file creation) */ | 293 | #define WALK_RC_NOT_FOUND 0 /* successfully not found (aid for file creation) */ |
295 | #define WALK_RC_FOUND 1 /* found and opened */ | 294 | #define WALK_RC_FOUND 1 /* found and opened */ |
296 | #define WALK_RC_FOUND_ROOT 2 /* found and opened sys/volume root */ | 295 | #define WALK_RC_FOUND_ROOT 2 /* found and opened sys root */ |
297 | #define WALK_RC_CONT_AT_ROOT 3 /* continue at root level */ | 296 | #define WALK_RC_CONT_AT_ROOT 3 /* continue at root level */ |
298 | 297 | ||
299 | /* return another struct pathwalk_component from the pool, or NULL if the | 298 | /* return another struct pathwalk_component from the pool, or NULL if the |
@@ -397,10 +396,10 @@ static int walk_open_info(struct pathwalk *walkp, | |||
397 | 396 | ||
398 | /* make open official if not simply probing for presence - must do it here | 397 | /* make open official if not simply probing for presence - must do it here |
399 | or compp->info on stack will get destroyed before it was copied */ | 398 | or compp->info on stack will get destroyed before it was copied */ |
400 | if (!(callflags & FF_PROBE)) | 399 | if (!(callflags & (FF_PROBE|FF_NOFS))) |
401 | fileop_onopen_internal(stream, &compp->info, callflags); | 400 | fileop_onopen_internal(stream, &compp->info, callflags); |
402 | 401 | ||
403 | return compp->nextp ? WALK_RC_FOUND : WALK_RC_FOUND_ROOT; | 402 | return compp->attr == ATTR_SYSTEM_ROOT ? WALK_RC_FOUND_ROOT : WALK_RC_FOUND; |
404 | } | 403 | } |
405 | 404 | ||
406 | /* check the component against the prefix test info */ | 405 | /* check the component against the prefix test info */ |
@@ -507,6 +506,10 @@ walk_path(struct pathwalk *walkp, struct pathwalk_component *compp, | |||
507 | if (len > MAX_COMPNAME) | 506 | if (len > MAX_COMPNAME) |
508 | return -ENAMETOOLONG; | 507 | return -ENAMETOOLONG; |
509 | 508 | ||
509 | /* no filesystem is mounted here */ | ||
510 | if (walkp->callflags & FF_NOFS) | ||
511 | return -ENOENT; | ||
512 | |||
510 | /* check for "." and ".." */ | 513 | /* check for "." and ".." */ |
511 | if (name[0] == '.') | 514 | if (name[0] == '.') |
512 | { | 515 | { |
@@ -575,7 +578,7 @@ int open_stream_internal(const char *path, unsigned int callflags, | |||
575 | callflags &= ~(FF_INFO | FF_PARENTINFO | FF_CHECKPREFIX); | 578 | callflags &= ~(FF_INFO | FF_PARENTINFO | FF_CHECKPREFIX); |
576 | 579 | ||
577 | /* This lets it be passed quietly to directory scanning */ | 580 | /* This lets it be passed quietly to directory scanning */ |
578 | stream->flags = callflags & FF_MASK; | 581 | stream->flags |= callflags & FF_MASK; |
579 | 582 | ||
580 | struct pathwalk walk; | 583 | struct pathwalk walk; |
581 | walk.path = path; | 584 | walk.path = path; |
@@ -585,80 +588,36 @@ int open_stream_internal(const char *path, unsigned int callflags, | |||
585 | 588 | ||
586 | struct pathwalk_component *rootp = pathwalk_comp_alloc(NULL); | 589 | struct pathwalk_component *rootp = pathwalk_comp_alloc(NULL); |
587 | rootp->nextp = NULL; | 590 | rootp->nextp = NULL; |
588 | rootp->attr = ATTR_SYSTEM_ROOT; | ||
589 | |||
590 | #ifdef HAVE_MULTIVOLUME | ||
591 | int volume = 0, rootrc = WALK_RC_FOUND; | ||
592 | #endif /* HAVE_MULTIVOLUME */ | ||
593 | 591 | ||
594 | while (1) | 592 | while (1) |
595 | { | 593 | { |
596 | const char *pathptr = walk.path; | 594 | rc = ns_parse_root(walk.path, &rootp->name, &rootp->length); |
597 | 595 | if (rc < 0) | |
598 | #ifdef HAVE_MULTIVOLUME | 596 | break; |
599 | /* this seamlessly integrates secondary filesystems into the | ||
600 | root namespace (e.g. "/<0>/../../<1>/../foo/." :<=> "/foo") */ | ||
601 | const char *p; | ||
602 | volume = path_strip_volume(pathptr, &p, false); | ||
603 | if (!CHECK_VOL(volume)) | ||
604 | { | ||
605 | DEBUGF("No such device or address: %d\n", volume); | ||
606 | FILE_ERROR(ENXIO, -2); | ||
607 | } | ||
608 | |||
609 | if (p == pathptr) | ||
610 | { | ||
611 | /* the root of this subpath is the system root */ | ||
612 | rootp->attr = ATTR_SYSTEM_ROOT; | ||
613 | rootrc = WALK_RC_FOUND_ROOT; | ||
614 | } | ||
615 | else | ||
616 | { | ||
617 | /* this subpath specifies a mount point */ | ||
618 | rootp->attr = ATTR_MOUNT_POINT; | ||
619 | rootrc = WALK_RC_FOUND; | ||
620 | } | ||
621 | |||
622 | walk.path = p; | ||
623 | #endif /* HAVE_MULTIVOLUME */ | ||
624 | |||
625 | /* set name to start at last leading separator; names of volume | ||
626 | specifiers will be returned as "/<fooN>" */ | ||
627 | rootp->name = GOBBLE_PATH_SEPCH(pathptr) - 1; | ||
628 | rootp->length = | ||
629 | IF_MV( rootrc == WALK_RC_FOUND ? p - rootp->name : ) 1; | ||
630 | 597 | ||
631 | rc = fat_open_rootdir(IF_MV(volume,) &rootp->info.fatfile); | 598 | rc = ns_open_root(IF_MV(rc,) &walk.callflags, &rootp->info, &rootp->attr); |
632 | if (rc < 0) | 599 | if (rc < 0) |
633 | { | ||
634 | /* not mounted */ | ||
635 | DEBUGF("No such device or address: %d\n", IF_MV_VOL(volume)); | ||
636 | rc = -ENXIO; | ||
637 | break; | 600 | break; |
638 | } | ||
639 | 601 | ||
640 | get_rootinfo_internal(&rootp->info); | 602 | walk.path = rootp->name + rootp->length; |
603 | |||
641 | rc = walk_path(&walk, rootp, stream); | 604 | rc = walk_path(&walk, rootp, stream); |
642 | if (rc != WALK_RC_CONT_AT_ROOT) | 605 | if (rc != WALK_RC_CONT_AT_ROOT) |
643 | break; | 606 | break; |
644 | } | 607 | } |
645 | 608 | ||
646 | switch (rc) | 609 | if (rc >= 0) |
647 | { | 610 | { |
648 | case WALK_RC_FOUND_ROOT: | ||
649 | IF_MV( rc = rootrc; ) | ||
650 | case WALK_RC_NOT_FOUND: | ||
651 | case WALK_RC_FOUND: | ||
652 | /* FF_PROBE leaves nothing for caller to clean up */ | 611 | /* FF_PROBE leaves nothing for caller to clean up */ |
653 | if (callflags & FF_PROBE) | 612 | if (walk.callflags & FF_PROBE) |
654 | filestr_base_destroy(stream); | 613 | filestr_base_destroy(stream); |
655 | 614 | } | |
656 | break; | 615 | else |
657 | 616 | { | |
658 | default: /* utter, abject failure :`( */ | 617 | /* utter, abject failure :`( */ |
659 | DEBUGF("Open failed: rc=%d, errno=%d\n", rc, errno); | 618 | DEBUGF("Open failed: rc=%d, errno=%d\n", rc, errno); |
660 | filestr_base_destroy(stream); | 619 | filestr_base_destroy(stream); |
661 | FILE_ERROR(-rc, -3); | 620 | FILE_ERROR(-rc, -1); |
662 | } | 621 | } |
663 | 622 | ||
664 | file_error: | 623 | file_error: |
diff --git a/firmware/common/fileobj_mgr.c b/firmware/common/fileobj_mgr.c index e34a460e10..37452fbbe1 100644 --- a/firmware/common/fileobj_mgr.c +++ b/firmware/common/fileobj_mgr.c | |||
@@ -20,12 +20,13 @@ | |||
20 | ****************************************************************************/ | 20 | ****************************************************************************/ |
21 | #include "config.h" | 21 | #include "config.h" |
22 | #include "system.h" | 22 | #include "system.h" |
23 | #include <errno.h> | ||
23 | #include "debug.h" | 24 | #include "debug.h" |
24 | #include "file.h" | 25 | #include "file.h" |
25 | #include "dir.h" | 26 | #include "dir.h" |
26 | #include "disk_cache.h" | 27 | #include "disk_cache.h" |
27 | #include "fileobj_mgr.h" | 28 | #include "fileobj_mgr.h" |
28 | #include "dircache_redirect.h" | 29 | #include "rb_namespace.h" |
29 | 30 | ||
30 | /** | 31 | /** |
31 | * Manages file and directory streams on all volumes | 32 | * Manages file and directory streams on all volumes |
@@ -34,8 +35,8 @@ | |||
34 | */ | 35 | */ |
35 | 36 | ||
36 | 37 | ||
37 | /* there will always be enough of these for all user handles, thus these | 38 | /* there will always be enough of these for all user handles, thus most of |
38 | functions don't return failure codes */ | 39 | these functions don't return failure codes */ |
39 | #define MAX_FILEOBJS (MAX_OPEN_HANDLES + AUX_FILEOBJS) | 40 | #define MAX_FILEOBJS (MAX_OPEN_HANDLES + AUX_FILEOBJS) |
40 | 41 | ||
41 | /* describes the file as an image on the storage medium */ | 42 | /* describes the file as an image on the storage medium */ |
@@ -84,6 +85,15 @@ static struct ll_head busy_bindings[NUM_VOLUMES]; | |||
84 | for (struct filestr_base *s = STREAM_##what(start); \ | 85 | for (struct filestr_base *s = STREAM_##what(start); \ |
85 | s; s = STREAM_NEXT(s)) | 86 | s; s = STREAM_NEXT(s)) |
86 | 87 | ||
88 | /* once a file/directory, always a file/directory; such a change | ||
89 | is a bug */ | ||
90 | #define CHECK_FO_DIRECTORY(callflags, fobp) \ | ||
91 | if (((callflags) ^ (fobp)->flags) & FO_DIRECTORY) \ | ||
92 | { \ | ||
93 | DEBUGF("%s - FO_DIRECTORY flag does not match: %p %u\n", \ | ||
94 | __func__, (fobp), (callflags)); \ | ||
95 | } | ||
96 | |||
87 | 97 | ||
88 | /* syncs information for the stream's old and new parent directory if any are | 98 | /* syncs information for the stream's old and new parent directory if any are |
89 | currently opened */ | 99 | currently opened */ |
@@ -96,6 +106,10 @@ static void fileobj_sync_parent(const struct file_base_info *infop[], | |||
96 | continue; /* not directory or removed can't be parent of anything */ | 106 | continue; /* not directory or removed can't be parent of anything */ |
97 | 107 | ||
98 | struct filestr_base *parentstrp = STREAM_FIRST(fobp); | 108 | struct filestr_base *parentstrp = STREAM_FIRST(fobp); |
109 | |||
110 | if (!parentstrp) | ||
111 | continue; | ||
112 | |||
99 | struct fat_file *parentfilep = &parentstrp->infop->fatfile; | 113 | struct fat_file *parentfilep = &parentstrp->infop->fatfile; |
100 | 114 | ||
101 | for (int i = 0; i < count; i++) | 115 | for (int i = 0; i < count; i++) |
@@ -111,8 +125,7 @@ static void fileobj_sync_parent(const struct file_base_info *infop[], | |||
111 | } | 125 | } |
112 | 126 | ||
113 | /* see if this file has open streams and return that fileobj_binding if so, | 127 | /* see if this file has open streams and return that fileobj_binding if so, |
114 | else grab a new one from the free list; returns true if this stream is | 128 | else grab a new one from the free list; returns true if this is new */ |
115 | the only open one */ | ||
116 | static bool binding_assign(const struct file_base_info *srcinfop, | 129 | static bool binding_assign(const struct file_base_info *srcinfop, |
117 | struct fileobj_binding **fobpp) | 130 | struct fileobj_binding **fobpp) |
118 | { | 131 | { |
@@ -123,7 +136,7 @@ static bool binding_assign(const struct file_base_info *srcinfop, | |||
123 | 136 | ||
124 | if (fat_file_is_same(&srcinfop->fatfile, &fobp->bind.info.fatfile)) | 137 | if (fat_file_is_same(&srcinfop->fatfile, &fobp->bind.info.fatfile)) |
125 | { | 138 | { |
126 | /* already has open streams */ | 139 | /* already has open streams/mounts */ |
127 | *fobpp = fobp; | 140 | *fobpp = fobp; |
128 | return false; | 141 | return false; |
129 | } | 142 | } |
@@ -143,6 +156,23 @@ static void binding_add_to_free_list(struct fileobj_binding *fobp) | |||
143 | ll_insert_last(FREE_BINDINGS(), &fobp->bind.node); | 156 | ll_insert_last(FREE_BINDINGS(), &fobp->bind.node); |
144 | } | 157 | } |
145 | 158 | ||
159 | static void bind_source_info(const struct file_base_info *srcinfop, | ||
160 | struct fileobj_binding **fobpp) | ||
161 | { | ||
162 | if (!binding_assign(srcinfop, fobpp)) | ||
163 | return; /* already in use */ | ||
164 | |||
165 | /* is new */ | ||
166 | (*fobpp)->bind.info = *srcinfop; | ||
167 | fileobj_bind_file(&(*fobpp)->bind); | ||
168 | } | ||
169 | |||
170 | static void release_binding(struct fileobj_binding *fobp) | ||
171 | { | ||
172 | fileobj_unbind_file(&fobp->bind); | ||
173 | binding_add_to_free_list(fobp); | ||
174 | } | ||
175 | |||
146 | /** File and directory internal interface **/ | 176 | /** File and directory internal interface **/ |
147 | 177 | ||
148 | void file_binding_insert_last(struct file_base_binding *bindp) | 178 | void file_binding_insert_last(struct file_base_binding *bindp) |
@@ -169,6 +199,41 @@ void file_binding_remove_next(struct file_base_binding *prevp, | |||
169 | } | 199 | } |
170 | #endif /* HAVE_DIRCACHE */ | 200 | #endif /* HAVE_DIRCACHE */ |
171 | 201 | ||
202 | /* mounts a file object as a target from elsewhere */ | ||
203 | bool fileobj_mount(const struct file_base_info *srcinfop, | ||
204 | unsigned int callflags, | ||
205 | struct file_base_binding **bindpp) | ||
206 | { | ||
207 | struct fileobj_binding *fobp; | ||
208 | bind_source_info(srcinfop, &fobp); | ||
209 | |||
210 | CHECK_FO_DIRECTORY(callflags, fobp); | ||
211 | |||
212 | if (fobp->flags & FO_MOUNTTARGET) | ||
213 | return false; /* already mounted */ | ||
214 | |||
215 | fobp->flags |= FDO_BUSY | FO_MOUNTTARGET | | ||
216 | (callflags & FO_DIRECTORY); | ||
217 | |||
218 | *bindpp = &fobp->bind; | ||
219 | |||
220 | return true; | ||
221 | } | ||
222 | |||
223 | /* unmounts the file object and frees it if now unusued */ | ||
224 | void fileobj_unmount(struct file_base_binding *bindp) | ||
225 | { | ||
226 | struct fileobj_binding *fobp = (struct fileobj_binding *)bindp; | ||
227 | |||
228 | if (!(fobp->flags & FO_MOUNTTARGET)) | ||
229 | return; /* not mounted */ | ||
230 | |||
231 | if (STREAM_FIRST(fobp) == NULL) | ||
232 | release_binding(fobp); /* no longer in use */ | ||
233 | else | ||
234 | fobp->flags &= ~FO_MOUNTTARGET; | ||
235 | } | ||
236 | |||
172 | /* opens the file object for a new stream and sets up the caches; | 237 | /* opens the file object for a new stream and sets up the caches; |
173 | * the stream must already be opened at the FS driver level and *stream | 238 | * the stream must already be opened at the FS driver level and *stream |
174 | * initialized. | 239 | * initialized. |
@@ -180,10 +245,14 @@ void fileobj_fileop_open(struct filestr_base *stream, | |||
180 | const struct file_base_info *srcinfop, | 245 | const struct file_base_info *srcinfop, |
181 | unsigned int callflags) | 246 | unsigned int callflags) |
182 | { | 247 | { |
248 | /* assign base file information */ | ||
183 | struct fileobj_binding *fobp; | 249 | struct fileobj_binding *fobp; |
184 | bool first = binding_assign(srcinfop, &fobp); | 250 | bind_source_info(srcinfop, &fobp); |
251 | |||
252 | unsigned int foflags = fobp->flags; | ||
185 | 253 | ||
186 | /* add stream to this file's list */ | 254 | /* add stream to this file's list */ |
255 | bool first = STREAM_FIRST(fobp) == NULL; | ||
187 | ll_insert_last(&fobp->list, &stream->node); | 256 | ll_insert_last(&fobp->list, &stream->node); |
188 | 257 | ||
189 | /* initiate the new stream into the enclave */ | 258 | /* initiate the new stream into the enclave */ |
@@ -197,27 +266,16 @@ void fileobj_fileop_open(struct filestr_base *stream, | |||
197 | if (first) | 266 | if (first) |
198 | { | 267 | { |
199 | /* first stream for file */ | 268 | /* first stream for file */ |
200 | fobp->bind.info = *srcinfop; | 269 | fobp->flags = foflags | FDO_BUSY | FO_SINGLE | |
201 | fobp->flags = FDO_BUSY | FO_SINGLE | | 270 | (callflags & (FO_DIRECTORY|FO_TRUNC)); |
202 | (callflags & (FO_DIRECTORY|FO_TRUNC)); | 271 | fobp->writers = 0; |
203 | fobp->writers = 0; | 272 | fobp->size = 0; |
204 | fobp->size = 0; | ||
205 | |||
206 | fileobj_bind_file(&fobp->bind); | ||
207 | } | 273 | } |
208 | else | 274 | else |
209 | { | 275 | { |
210 | /* additional stream for file */ | 276 | /* additional stream for file */ |
211 | fobp->flags &= ~FO_SINGLE; | 277 | fobp->flags = (foflags & ~FO_SINGLE) | (callflags & FO_TRUNC); |
212 | fobp->flags |= callflags & FO_TRUNC; | 278 | CHECK_FO_DIRECTORY(callflags, fobp); |
213 | |||
214 | /* once a file/directory, always a file/directory; such a change | ||
215 | is a bug */ | ||
216 | if ((callflags ^ fobp->flags) & FO_DIRECTORY) | ||
217 | { | ||
218 | DEBUGF("%s - FO_DIRECTORY flag does not match: %p %u\n", | ||
219 | __func__, stream, callflags); | ||
220 | } | ||
221 | } | 279 | } |
222 | 280 | ||
223 | if ((callflags & FD_WRITE) && ++fobp->writers == 1) | 281 | if ((callflags & FD_WRITE) && ++fobp->writers == 1) |
@@ -257,12 +315,14 @@ void fileobj_fileop_close(struct filestr_base *stream) | |||
257 | if (foflags & FO_SINGLE) | 315 | if (foflags & FO_SINGLE) |
258 | { | 316 | { |
259 | /* last stream for file; close everything */ | 317 | /* last stream for file; close everything */ |
260 | fileobj_unbind_file(&fobp->bind); | ||
261 | |||
262 | if (fobp->writers) | 318 | if (fobp->writers) |
263 | file_cache_free(&fobp->cache); | 319 | file_cache_free(&fobp->cache); |
264 | 320 | ||
265 | binding_add_to_free_list(fobp); | 321 | /* binding must stay valid if something is mounted to here */ |
322 | if (foflags & FO_MOUNTTARGET) | ||
323 | fobp->flags = foflags & (FDO_BUSY|FO_DIRECTORY|FO_MOUNTTARGET); | ||
324 | else | ||
325 | release_binding(fobp); | ||
266 | } | 326 | } |
267 | else | 327 | else |
268 | { | 328 | { |
diff --git a/firmware/common/pathfuncs.c b/firmware/common/pathfuncs.c index 0935a9a6e3..078c0b6938 100644 --- a/firmware/common/pathfuncs.c +++ b/firmware/common/pathfuncs.c | |||
@@ -105,7 +105,7 @@ static const unsigned char storage_dec_indexes[STORAGE_NUM_TYPES+1] = | |||
105 | */ | 105 | */ |
106 | int path_strip_volume(const char *name, const char **nameptr, bool greedy) | 106 | int path_strip_volume(const char *name, const char **nameptr, bool greedy) |
107 | { | 107 | { |
108 | int volume = 0; | 108 | int volume = ROOT_VOLUME; |
109 | const char *t = name; | 109 | const char *t = name; |
110 | int c, v = 0; | 110 | int c, v = 0; |
111 | 111 | ||
@@ -114,9 +114,16 @@ int path_strip_volume(const char *name, const char **nameptr, bool greedy) | |||
114 | * digits within the brackets is parsed as the volume number and of | 114 | * digits within the brackets is parsed as the volume number and of |
115 | * those, only the last ones VOL_MUM_MAX allows. | 115 | * those, only the last ones VOL_MUM_MAX allows. |
116 | */ | 116 | */ |
117 | c = *(t = GOBBLE_PATH_SEPCH(t)); /* skip all leading slashes */ | 117 | t = GOBBLE_PATH_SEPCH(t); /* skip all leading slashes */ |
118 | if (t == name) | ||
119 | { | ||
120 | volume = -1; /* relative path; don't know */ | ||
121 | goto psv_out; | ||
122 | } | ||
123 | |||
124 | c = *t; | ||
118 | if (c != VOL_START_TOK) /* missing start token? no volume */ | 125 | if (c != VOL_START_TOK) /* missing start token? no volume */ |
119 | goto volume0; | 126 | goto psv_out; |
120 | 127 | ||
121 | do | 128 | do |
122 | { | 129 | { |
@@ -127,7 +134,7 @@ int path_strip_volume(const char *name, const char **nameptr, bool greedy) | |||
127 | break; | 134 | break; |
128 | case '\0': | 135 | case '\0': |
129 | case PATH_SEPCH: /* no closing bracket; no volume */ | 136 | case PATH_SEPCH: /* no closing bracket; no volume */ |
130 | goto volume0; | 137 | goto psv_out; |
131 | default: /* something else; reset volume */ | 138 | default: /* something else; reset volume */ |
132 | v = 0; | 139 | v = 0; |
133 | } | 140 | } |
@@ -137,7 +144,7 @@ int path_strip_volume(const char *name, const char **nameptr, bool greedy) | |||
137 | if (!(c = *++t)) /* no more path and no '/' is ok */ | 144 | if (!(c = *++t)) /* no more path and no '/' is ok */ |
138 | ; | 145 | ; |
139 | else if (c != PATH_SEPCH) /* more path and no separator after end */ | 146 | else if (c != PATH_SEPCH) /* more path and no separator after end */ |
140 | goto volume0; | 147 | goto psv_out; |
141 | else if (greedy) | 148 | else if (greedy) |
142 | t = GOBBLE_PATH_SEPCH(++t); /* strip remaining separators */ | 149 | t = GOBBLE_PATH_SEPCH(++t); /* strip remaining separators */ |
143 | 150 | ||
@@ -146,7 +153,7 @@ int path_strip_volume(const char *name, const char **nameptr, bool greedy) | |||
146 | 153 | ||
147 | volume = v; | 154 | volume = v; |
148 | name = t; | 155 | name = t; |
149 | volume0: | 156 | psv_out: |
150 | if (nameptr) | 157 | if (nameptr) |
151 | *nameptr = name; | 158 | *nameptr = name; |
152 | return volume; | 159 | return volume; |
@@ -157,10 +164,14 @@ volume0: | |||
157 | */ | 164 | */ |
158 | int get_volume_name(int volume, char *buffer) | 165 | int get_volume_name(int volume, char *buffer) |
159 | { | 166 | { |
160 | if (volume < 0) | 167 | if (volume < 0 || volume == ROOT_VOLUME) |
161 | { | 168 | { |
162 | *buffer = '\0'; | 169 | char *t = buffer; |
163 | return 0; | 170 | if (volume == ROOT_VOLUME) |
171 | *t++ = PATH_ROOTCHR; | ||
172 | |||
173 | *t = '\0'; | ||
174 | return t - buffer; | ||
164 | } | 175 | } |
165 | 176 | ||
166 | volume %= VOL_NUM_MAX; /* as path parser would have it */ | 177 | volume %= VOL_NUM_MAX; /* as path parser would have it */ |
@@ -173,6 +184,20 @@ int get_volume_name(int volume, char *buffer) | |||
173 | return snprintf(buffer, VOL_MAX_LEN + 1, "%c%s%d%c", | 184 | return snprintf(buffer, VOL_MAX_LEN + 1, "%c%s%d%c", |
174 | VOL_START_TOK, voldec, volume, VOL_END_TOK); | 185 | VOL_START_TOK, voldec, volume, VOL_END_TOK); |
175 | } | 186 | } |
187 | |||
188 | /* Returns volume name formatted with the root. Assumes buffer size is at | ||
189 | * least {VOL_MAX_LEN}+2 */ | ||
190 | int make_volume_root(int volume, char *buffer) | ||
191 | { | ||
192 | char *t = buffer; | ||
193 | |||
194 | if (volume >= 0 && volume != ROOT_VOLUME) | ||
195 | *t++ = PATH_ROOTCHR; | ||
196 | |||
197 | t += get_volume_name(volume, t); | ||
198 | |||
199 | return t - buffer; | ||
200 | } | ||
176 | #endif /* HAVE_MULTIVOLUME */ | 201 | #endif /* HAVE_MULTIVOLUME */ |
177 | 202 | ||
178 | /* Just like path_strip_volume() but strips a leading drive specifier and | 203 | /* Just like path_strip_volume() but strips a leading drive specifier and |
diff --git a/firmware/common/rb_namespace.c b/firmware/common/rb_namespace.c new file mode 100644 index 0000000000..04f92e97af --- /dev/null +++ b/firmware/common/rb_namespace.c | |||
@@ -0,0 +1,289 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2017 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 "fileobj_mgr.h" | ||
24 | #include "rb_namespace.h" | ||
25 | |||
26 | #define ROOT_CONTENTS_INDEX (NUM_VOLUMES) | ||
27 | #define NUM_ROOT_ITEMS (NUM_VOLUMES+1) | ||
28 | |||
29 | static uint8_t root_entry_flags[NUM_VOLUMES+1]; | ||
30 | static struct file_base_binding *root_bindp; | ||
31 | |||
32 | static inline unsigned int get_root_item_state(int item) | ||
33 | { | ||
34 | return root_entry_flags[item]; | ||
35 | } | ||
36 | |||
37 | static inline void set_root_item_state(int item, unsigned int state) | ||
38 | { | ||
39 | root_entry_flags[item] = state; | ||
40 | } | ||
41 | |||
42 | static void get_mount_point_entry(IF_MV(int volume,) struct dirent *entry) | ||
43 | { | ||
44 | #ifdef HAVE_MULTIVOLUME | ||
45 | get_volume_name(volume, entry->d_name); | ||
46 | #else /* */ | ||
47 | strcpy(entry->d_name, PATH_ROOTSTR); | ||
48 | #endif /* HAVE_MULTIVOLUME */ | ||
49 | |||
50 | /* is dirinfo_native */ | ||
51 | entry->info.attr = ATTR_MOUNT_POINT; | ||
52 | entry->info.size = 0; | ||
53 | entry->info.wrtdate = 0; | ||
54 | entry->info.wrttime = 0; | ||
55 | } | ||
56 | |||
57 | /* unmount the directory that enumerates into the root namespace */ | ||
58 | static void unmount_item(int item) | ||
59 | { | ||
60 | unsigned int state = get_root_item_state(item); | ||
61 | if (!state) | ||
62 | return; | ||
63 | |||
64 | if (state & NSITEM_CONTENTS) | ||
65 | { | ||
66 | fileobj_unmount(root_bindp); | ||
67 | root_bindp = NULL; | ||
68 | } | ||
69 | |||
70 | set_root_item_state(item, 0); | ||
71 | } | ||
72 | |||
73 | /* mount the directory that enumerates into the root namespace */ | ||
74 | int root_mount_path(const char *path, unsigned int flags) | ||
75 | { | ||
76 | #ifdef HAVE_MULTIVOLUME | ||
77 | int volume = path_strip_volume(path, NULL, false); | ||
78 | if (volume == ROOT_VOLUME) | ||
79 | return -EINVAL; | ||
80 | |||
81 | if (!CHECK_VOL(volume)) | ||
82 | return -ENOENT; | ||
83 | #else | ||
84 | if (!path_is_absolute(path)) | ||
85 | return -ENOENT; | ||
86 | #endif /* HAVE_MULTIVOLUME */ | ||
87 | |||
88 | bool contents = flags & NSITEM_CONTENTS; | ||
89 | int item = contents ? ROOT_CONTENTS_INDEX : IF_MV_VOL(volume); | ||
90 | unsigned int state = get_root_item_state(item); | ||
91 | |||
92 | if (state) | ||
93 | return -EBUSY; | ||
94 | |||
95 | if (contents) | ||
96 | { | ||
97 | /* cache information about the target */ | ||
98 | struct filestr_base stream; | ||
99 | struct path_component_info compinfo; | ||
100 | |||
101 | int e = errno; | ||
102 | int rc = open_stream_internal(path, FF_DIR | FF_PROBE | FF_INFO | | ||
103 | FF_DEVPATH, &stream, &compinfo); | ||
104 | if (rc <= 0) | ||
105 | { | ||
106 | rc = rc ? -errno : -ENOENT; | ||
107 | errno = e; | ||
108 | return rc; | ||
109 | } | ||
110 | |||
111 | if (!fileobj_mount(&compinfo.info, FO_DIRECTORY, &root_bindp)) | ||
112 | return -EBUSY; | ||
113 | } | ||
114 | |||
115 | state = NSITEM_MOUNTED | (flags & (NSITEM_HIDDEN|NSITEM_CONTENTS)); | ||
116 | set_root_item_state(item, state); | ||
117 | |||
118 | return 0; | ||
119 | } | ||
120 | |||
121 | /* inform root that an entire volume is being unmounted */ | ||
122 | void root_unmount_volume(IF_MV_NONVOID(int volume)) | ||
123 | { | ||
124 | FOR_EACH_VOLUME(volume, item) | ||
125 | { | ||
126 | #ifdef HAVE_MULTIVOLUME | ||
127 | uint32_t state = get_root_item_state(item); | ||
128 | if (state && (volume < 0 || item == volume)) | ||
129 | #endif /* HAVE_MULTIVOLUME */ | ||
130 | unmount_item(item); | ||
131 | } | ||
132 | |||
133 | /* if the volume unmounted contains the root directory contents then | ||
134 | the contents must also be unmounted */ | ||
135 | #ifdef HAVE_MULTIVOLUME | ||
136 | uint32_t state = get_root_item_state(ROOT_CONTENTS_INDEX); | ||
137 | if (state && (volume < 0 || BASEINFO_VOL(&root_bindp->info) == volume)) | ||
138 | #endif | ||
139 | unmount_item(ROOT_CONTENTS_INDEX); | ||
140 | } | ||
141 | |||
142 | /* parse the root part of a path */ | ||
143 | int ns_parse_root(const char *path, const char **pathp, uint16_t *lenp) | ||
144 | { | ||
145 | int volume = ROOT_VOLUME; | ||
146 | |||
147 | #ifdef HAVE_MULTIVOLUME | ||
148 | /* this seamlessly integrates secondary filesystems into the | ||
149 | root namespace (e.g. "/<0>/../../<1>/../foo/." :<=> "/foo") */ | ||
150 | const char *p; | ||
151 | volume = path_strip_volume(path, &p, false); | ||
152 | if (volume != ROOT_VOLUME && !CHECK_VOL(volume)) | ||
153 | return -ENOENT; | ||
154 | #endif /* HAVE_MULTIVOLUME */ | ||
155 | |||
156 | /* set name to start at last leading separator; name of root will | ||
157 | * be returned as "/", volume specifiers as "/<fooN>" */ | ||
158 | *pathp = GOBBLE_PATH_SEPCH(path) - 1; | ||
159 | *lenp = IF_MV( volume < NUM_VOLUMES ? p - *pathp : ) 1; | ||
160 | |||
161 | #ifdef HAVE_MULTIVOLUME | ||
162 | if (*lenp > MAX_COMPNAME+1) | ||
163 | return -ENAMETOOLONG; | ||
164 | #endif | ||
165 | |||
166 | return volume; | ||
167 | } | ||
168 | |||
169 | /* open one of the items in the root */ | ||
170 | int ns_open_root(IF_MV(int volume,) unsigned int *callflagsp, | ||
171 | struct file_base_info *infop, uint16_t *attrp) | ||
172 | { | ||
173 | unsigned int callflags = *callflagsp; | ||
174 | bool devpath = !!(callflags & FF_DEVPATH); | ||
175 | #ifdef HAVE_MULTIVOLUME | ||
176 | bool sysroot = volume == ROOT_VOLUME; | ||
177 | if (devpath && sysroot) | ||
178 | return -ENOENT; /* devpath needs volume spec */ | ||
179 | #else | ||
180 | bool sysroot = !devpath; /* always sysroot unless devpath */ | ||
181 | #endif | ||
182 | |||
183 | int item = sysroot ? ROOT_CONTENTS_INDEX : IF_MV_VOL(volume); | ||
184 | unsigned int state = get_root_item_state(item); | ||
185 | |||
186 | if (sysroot) | ||
187 | { | ||
188 | *attrp = ATTR_SYSTEM_ROOT; | ||
189 | |||
190 | if (state) | ||
191 | *infop = root_bindp->info; | ||
192 | else | ||
193 | *callflagsp = callflags | FF_NOFS; /* contents not mounted */ | ||
194 | } | ||
195 | else | ||
196 | { | ||
197 | *attrp = ATTR_MOUNT_POINT; | ||
198 | |||
199 | if (!devpath && !state) | ||
200 | return -ENOENT; /* regular open requires having been mounted */ | ||
201 | |||
202 | if (fat_open_rootdir(IF_MV(volume,) &infop->fatfile) < 0) | ||
203 | return -ENOENT; /* not mounted */ | ||
204 | |||
205 | get_rootinfo_internal(infop); | ||
206 | } | ||
207 | |||
208 | return 0; | ||
209 | } | ||
210 | |||
211 | /* read root directory entries */ | ||
212 | int root_readdir_dirent(struct filestr_base *stream, | ||
213 | struct ns_scan_info *scanp, struct dirent *entry) | ||
214 | { | ||
215 | int rc = 0; | ||
216 | |||
217 | int item = scanp->item; | ||
218 | |||
219 | /* skip any not-mounted or hidden items */ | ||
220 | unsigned int state; | ||
221 | while (1) | ||
222 | { | ||
223 | if (item >= NUM_ROOT_ITEMS) | ||
224 | goto file_eod; | ||
225 | |||
226 | state = get_root_item_state(item); | ||
227 | if ((state & (NSITEM_MOUNTED|NSITEM_HIDDEN)) == NSITEM_MOUNTED) | ||
228 | break; | ||
229 | |||
230 | item++; | ||
231 | } | ||
232 | |||
233 | if (item == ROOT_CONTENTS_INDEX) | ||
234 | { | ||
235 | rc = readdir_dirent(stream, &scanp->scan, entry); | ||
236 | if (rc < 0) | ||
237 | FILE_ERROR(ERRNO, rc * 10 - 1); | ||
238 | |||
239 | if (rc == 0) | ||
240 | item++; | ||
241 | } | ||
242 | else | ||
243 | { | ||
244 | get_mount_point_entry(IF_MV(item,) entry); | ||
245 | item++; | ||
246 | rc = 1; | ||
247 | } | ||
248 | |||
249 | scanp->item = item; | ||
250 | |||
251 | file_eod: | ||
252 | if (rc == 0) | ||
253 | empty_dirent(entry); | ||
254 | |||
255 | file_error: | ||
256 | return rc; | ||
257 | } | ||
258 | |||
259 | /* opens a stream to enumerate items in a namespace container */ | ||
260 | int ns_open_stream(const char *path, unsigned int callflags, | ||
261 | struct filestr_base *stream, struct ns_scan_info *scanp) | ||
262 | { | ||
263 | /* stream still needs synchronization even if we don't have a stream */ | ||
264 | static struct mutex no_contents_mtx SHAREDBSS_ATTR; | ||
265 | |||
266 | int rc = open_stream_internal(path, callflags, stream, NULL); | ||
267 | if (rc < 0) | ||
268 | FILE_ERROR(ERRNO, rc * 10 - 1); | ||
269 | |||
270 | scanp->item = rc > 1 ? 0 : -1; | ||
271 | |||
272 | if (stream->flags & FDO_BUSY) | ||
273 | { | ||
274 | /* root contents are mounted */ | ||
275 | fat_rewind(&stream->fatstr); | ||
276 | } | ||
277 | else | ||
278 | { | ||
279 | /* root contents not mounted */ | ||
280 | mutex_init(&no_contents_mtx); | ||
281 | stream->mtx = &no_contents_mtx; | ||
282 | } | ||
283 | |||
284 | ns_dirscan_rewind(scanp); | ||
285 | |||
286 | rc = 0; | ||
287 | file_error: | ||
288 | return rc; | ||
289 | } | ||
diff --git a/firmware/export/mv.h b/firmware/export/mv.h index ec7b2efdbd..5aa0ff8b4d 100644 --- a/firmware/export/mv.h +++ b/firmware/export/mv.h | |||
@@ -84,6 +84,10 @@ | |||
84 | #define VOL_MAX_LEN (1 + VOL_DEC_MAX_LEN + 2 + 1) | 84 | #define VOL_MAX_LEN (1 + VOL_DEC_MAX_LEN + 2 + 1) |
85 | #define VOL_NUM_MAX 100 | 85 | #define VOL_NUM_MAX 100 |
86 | 86 | ||
87 | #ifndef ROOT_VOLUME | ||
88 | #define ROOT_VOLUME INT_MAX | ||
89 | #endif | ||
90 | |||
87 | #else /* empty definitions if no multi-volume */ | 91 | #else /* empty definitions if no multi-volume */ |
88 | #define IF_MV(x...) | 92 | #define IF_MV(x...) |
89 | #define IF_MV_NONVOID(x...) void | 93 | #define IF_MV_NONVOID(x...) void |
diff --git a/firmware/export/pathfuncs.h b/firmware/export/pathfuncs.h index 92539c54c1..8858d85d24 100644 --- a/firmware/export/pathfuncs.h +++ b/firmware/export/pathfuncs.h | |||
@@ -30,10 +30,15 @@ | |||
30 | /* useful char constants that could be reconfigured if desired */ | 30 | /* useful char constants that could be reconfigured if desired */ |
31 | #define PATH_SEPCH '/' | 31 | #define PATH_SEPCH '/' |
32 | #define PATH_SEPSTR "/" | 32 | #define PATH_SEPSTR "/" |
33 | #define PATH_ROOTCHR '/' | ||
33 | #define PATH_ROOTSTR "/" | 34 | #define PATH_ROOTSTR "/" |
34 | #define PATH_BADSEPCH '\\' | 35 | #define PATH_BADSEPCH '\\' |
35 | #define PATH_DRVSEPCH ':' | 36 | #define PATH_DRVSEPCH ':' |
36 | 37 | ||
38 | #ifndef ROOT_VOLUME | ||
39 | #define ROOT_VOLUME INT_MAX | ||
40 | #endif | ||
41 | |||
37 | /* a nicer way to check for "." and ".." than two strcmp() calls */ | 42 | /* a nicer way to check for "." and ".." than two strcmp() calls */ |
38 | static inline bool is_dotdir_name(const char *name) | 43 | static inline bool is_dotdir_name(const char *name) |
39 | { | 44 | { |
@@ -75,6 +80,7 @@ static inline bool name_is_dot_dot(const char *name) | |||
75 | #ifdef HAVE_MULTIVOLUME | 80 | #ifdef HAVE_MULTIVOLUME |
76 | int path_strip_volume(const char *name, const char **nameptr, bool greedy); | 81 | int path_strip_volume(const char *name, const char **nameptr, bool greedy); |
77 | int get_volume_name(int volume, char *name); | 82 | int get_volume_name(int volume, char *name); |
83 | int make_volume_root(int volume, char *dst); | ||
78 | #endif | 84 | #endif |
79 | 85 | ||
80 | int path_strip_drive(const char *name, const char **nameptr, bool greedy); | 86 | int path_strip_drive(const char *name, const char **nameptr, bool greedy); |
diff --git a/firmware/export/rbpaths.h b/firmware/export/rbpaths.h index de591f0ec1..458a070f92 100644 --- a/firmware/export/rbpaths.h +++ b/firmware/export/rbpaths.h | |||
@@ -64,6 +64,9 @@ | |||
64 | #define PLUGIN_DIR ROCKBOX_DIR "/rocks" | 64 | #define PLUGIN_DIR ROCKBOX_DIR "/rocks" |
65 | #define CODECS_DIR ROCKBOX_DIR "/codecs" | 65 | #define CODECS_DIR ROCKBOX_DIR "/codecs" |
66 | 66 | ||
67 | #define RB_ROOT_VOL_HIDDEN(v) (IF_MV_VOL(v) == 0) | ||
68 | #define RB_ROOT_CONTENTS_DIR "/" IF_MV("<0>") | ||
69 | |||
67 | #else /* APPLICATION */ | 70 | #else /* APPLICATION */ |
68 | 71 | ||
69 | #define HOME_DIR "<HOME>" /* replaced at runtime */ | 72 | #define HOME_DIR "<HOME>" /* replaced at runtime */ |
diff --git a/firmware/include/dircache_redirect.h b/firmware/include/dircache_redirect.h index 9fae16b551..9a8de2fecd 100644 --- a/firmware/include/dircache_redirect.h +++ b/firmware/include/dircache_redirect.h | |||
@@ -20,7 +20,10 @@ | |||
20 | ****************************************************************************/ | 20 | ****************************************************************************/ |
21 | #ifndef _DIRCACHE_REDIRECT_H_ | 21 | #ifndef _DIRCACHE_REDIRECT_H_ |
22 | 22 | ||
23 | #include "rbpaths.h" | ||
24 | #include "pathfuncs.h" | ||
23 | #include "dir.h" | 25 | #include "dir.h" |
26 | #include "dircache.h" | ||
24 | 27 | ||
25 | /*** | 28 | /*** |
26 | ** Internal redirects that depend upon whether or not dircache is made | 29 | ** Internal redirects that depend upon whether or not dircache is made |
@@ -123,10 +126,20 @@ static inline void fileop_onsync_internal(struct filestr_base *stream) | |||
123 | 126 | ||
124 | static inline void volume_onmount_internal(IF_MV_NONVOID(int volume)) | 127 | static inline void volume_onmount_internal(IF_MV_NONVOID(int volume)) |
125 | { | 128 | { |
129 | #ifdef HAVE_MULTIVOLUME | ||
130 | char path[VOL_MAX_LEN+2]; | ||
131 | make_volume_root(volume, path); | ||
132 | #else | ||
133 | const char *path = PATH_ROOTSTR; | ||
134 | #endif | ||
135 | root_mount_path(path, RB_ROOT_VOL_HIDDEN(volume) ? NSITEM_HIDDEN : 0); | ||
136 | #ifdef HAVE_MULTIVOLUME | ||
137 | if (volume == path_strip_volume(RB_ROOT_CONTENTS_DIR, NULL, false)) | ||
138 | #endif | ||
139 | root_mount_path(RB_ROOT_CONTENTS_DIR, NSITEM_CONTENTS); | ||
126 | #ifdef HAVE_DIRCACHE | 140 | #ifdef HAVE_DIRCACHE |
127 | dircache_mount(); | 141 | dircache_mount(); |
128 | #endif | 142 | #endif |
129 | IF_MV( (void)volume; ) | ||
130 | } | 143 | } |
131 | 144 | ||
132 | static inline void volume_onunmount_internal(IF_MV_NONVOID(int volume)) | 145 | static inline void volume_onunmount_internal(IF_MV_NONVOID(int volume)) |
@@ -135,6 +148,7 @@ static inline void volume_onunmount_internal(IF_MV_NONVOID(int volume)) | |||
135 | /* First, to avoid update of something about to be destroyed anyway */ | 148 | /* First, to avoid update of something about to be destroyed anyway */ |
136 | dircache_unmount(IF_MV(volume)); | 149 | dircache_unmount(IF_MV(volume)); |
137 | #endif | 150 | #endif |
151 | root_unmount_volume(IF_MV(volume)); | ||
138 | fileobj_mgr_unmount(IF_MV(volume)); | 152 | fileobj_mgr_unmount(IF_MV(volume)); |
139 | } | 153 | } |
140 | 154 | ||
diff --git a/firmware/include/file_internal.h b/firmware/include/file_internal.h index d62b5a8541..f4bd8bb8c2 100644 --- a/firmware/include/file_internal.h +++ b/firmware/include/file_internal.h | |||
@@ -72,16 +72,18 @@ enum fildes_and_obj_flags | |||
72 | /* used in descriptor and common */ | 72 | /* used in descriptor and common */ |
73 | FDO_BUSY = 0x0001, /* descriptor/object is in use */ | 73 | FDO_BUSY = 0x0001, /* descriptor/object is in use */ |
74 | /* only used in individual stream descriptor */ | 74 | /* only used in individual stream descriptor */ |
75 | FD_WRITE = 0x0002, /* descriptor has write mode */ | 75 | FD_VALID = 0x0002, /* descriptor is valid but not registered */ |
76 | FD_WRONLY = 0x0004, /* descriptor is write mode only */ | 76 | FD_WRITE = 0x0004, /* descriptor has write mode */ |
77 | FD_APPEND = 0x0008, /* descriptor is append mode */ | 77 | FD_WRONLY = 0x0008, /* descriptor is write mode only */ |
78 | FD_APPEND = 0x0010, /* descriptor is append mode */ | ||
78 | FD_NONEXIST = 0x8000, /* closed but not freed (uncombined) */ | 79 | FD_NONEXIST = 0x8000, /* closed but not freed (uncombined) */ |
79 | /* only used as common flags */ | 80 | /* only used as common flags */ |
80 | FO_DIRECTORY = 0x0010, /* fileobj is a directory */ | 81 | FO_DIRECTORY = 0x0020, /* fileobj is a directory */ |
81 | FO_TRUNC = 0x0020, /* fileobj is opened to be truncated */ | 82 | FO_TRUNC = 0x0040, /* fileobj is opened to be truncated */ |
82 | FO_REMOVED = 0x0040, /* fileobj was deleted while open */ | 83 | FO_REMOVED = 0x0080, /* fileobj was deleted while open */ |
83 | FO_SINGLE = 0x0080, /* fileobj has only one stream open */ | 84 | FO_SINGLE = 0x0100, /* fileobj has only one stream open */ |
84 | FDO_MASK = 0x00ff, | 85 | FO_MOUNTTARGET = 0x0200, /* fileobj kept open as a mount target */ |
86 | FDO_MASK = 0x03ff, | ||
85 | FDO_CHG_MASK = FO_TRUNC, /* fileobj permitted external change */ | 87 | FDO_CHG_MASK = FO_TRUNC, /* fileobj permitted external change */ |
86 | /* bitflags that instruct various 'open' functions how to behave; | 88 | /* bitflags that instruct various 'open' functions how to behave; |
87 | * saved in stream flags (only) but not used by manager */ | 89 | * saved in stream flags (only) but not used by manager */ |
@@ -95,7 +97,9 @@ enum fildes_and_obj_flags | |||
95 | FF_CACHEONLY = 0x00200000, /* succeed only if in dircache */ | 97 | FF_CACHEONLY = 0x00200000, /* succeed only if in dircache */ |
96 | FF_INFO = 0x00400000, /* return info on self */ | 98 | FF_INFO = 0x00400000, /* return info on self */ |
97 | FF_PARENTINFO = 0x00800000, /* return info on parent */ | 99 | FF_PARENTINFO = 0x00800000, /* return info on parent */ |
98 | FF_MASK = 0x00ff0000, | 100 | FF_DEVPATH = 0x01000000, /* path is a device path, not root-based */ |
101 | FF_NOFS = 0x02000000, /* no filesystem mounted here */ | ||
102 | FF_MASK = 0x03ff0000, | ||
99 | }; | 103 | }; |
100 | 104 | ||
101 | /** Common data structures used throughout **/ | 105 | /** Common data structures used throughout **/ |
diff --git a/firmware/include/fileobj_mgr.h b/firmware/include/fileobj_mgr.h index 627d2df341..0db3520d34 100644 --- a/firmware/include/fileobj_mgr.h +++ b/firmware/include/fileobj_mgr.h | |||
@@ -29,6 +29,11 @@ void file_binding_remove(struct file_base_binding *bindp); | |||
29 | void file_binding_remove_next(struct file_base_binding *prevp, | 29 | void file_binding_remove_next(struct file_base_binding *prevp, |
30 | struct file_base_binding *bindp); | 30 | struct file_base_binding *bindp); |
31 | 31 | ||
32 | bool fileobj_mount(const struct file_base_info *srcinfop, | ||
33 | unsigned int callflags, | ||
34 | struct file_base_binding **bindpp); | ||
35 | void fileobj_unmount(struct file_base_binding *bindp); | ||
36 | |||
32 | void fileobj_fileop_open(struct filestr_base *stream, | 37 | void fileobj_fileop_open(struct filestr_base *stream, |
33 | const struct file_base_info *srcinfop, | 38 | const struct file_base_info *srcinfop, |
34 | unsigned int callflags); | 39 | unsigned int callflags); |
diff --git a/firmware/include/fs_defines.h b/firmware/include/fs_defines.h index 538c4b36cd..aee6daff6a 100644 --- a/firmware/include/fs_defines.h +++ b/firmware/include/fs_defines.h | |||
@@ -51,12 +51,19 @@ | |||
51 | /* internal functions open streams as well; make sure they don't fail if all | 51 | /* internal functions open streams as well; make sure they don't fail if all |
52 | user descs are busy; this needs to be at least the greatest quantity needed | 52 | user descs are busy; this needs to be at least the greatest quantity needed |
53 | at once by all internal functions */ | 53 | at once by all internal functions */ |
54 | /* internal functions open streams as well; make sure they don't fail if all | ||
55 | user descs are busy; this needs to be at least the greatest quantity needed | ||
56 | at once by all internal functions */ | ||
57 | #define MOUNT_AUX_FILEOBJS 1 | ||
58 | |||
54 | #ifdef HAVE_DIRCACHE | 59 | #ifdef HAVE_DIRCACHE |
55 | #define AUX_FILEOBJS 3 | 60 | #define DIRCACHE_AUX_FILEOBJS 1 |
56 | #else | 61 | #else |
57 | #define AUX_FILEOBJS 2 | 62 | #define DIRCACHE_AUX_FILEOBJS 0 |
58 | #endif | 63 | #endif |
59 | 64 | ||
65 | #define AUX_FILEOBJS (2+DIRCACHE_AUX_FILEOBJS+MOUNT_AUX_FILEOBJS) | ||
66 | |||
60 | /* number of components statically allocated to handle the vast majority | 67 | /* number of components statically allocated to handle the vast majority |
61 | of path depths; should maybe be tuned for >= 90th percentile but for now, | 68 | of path depths; should maybe be tuned for >= 90th percentile but for now, |
62 | imma just guessing based on something like: | 69 | imma just guessing based on something like: |
diff --git a/firmware/include/rb_namespace.h b/firmware/include/rb_namespace.h new file mode 100644 index 0000000000..4d7a125c7b --- /dev/null +++ b/firmware/include/rb_namespace.h | |||
@@ -0,0 +1,79 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2017 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 | #ifndef RB_NAMESPACE_H | ||
22 | #define RB_NAMESPACE_H | ||
23 | |||
24 | #include "file_internal.h" | ||
25 | |||
26 | enum ns_item_flags | ||
27 | { | ||
28 | NSITEM_MOUNTED = 0x01, /* item is mounted */ | ||
29 | NSITEM_HIDDEN = 0x02, /* item is not enumerated */ | ||
30 | NSITEM_CONTENTS = 0x04, /* contents enumerate */ | ||
31 | }; | ||
32 | |||
33 | struct ns_scan_info | ||
34 | { | ||
35 | struct dirscan_info scan; /* dirscan info - first! */ | ||
36 | int item; /* current item in parent */ | ||
37 | }; | ||
38 | |||
39 | /* root functions */ | ||
40 | int root_mount_path(const char *path, unsigned int flags); | ||
41 | void root_unmount_volume(IF_MV_NONVOID(int volume)); | ||
42 | int root_readdir_dirent(struct filestr_base *stream, | ||
43 | struct ns_scan_info *scanp, | ||
44 | struct dirent *entry); | ||
45 | |||
46 | /* namespace functions */ | ||
47 | int ns_parse_root(const char *path, const char **pathp, uint16_t *lenp); | ||
48 | int ns_open_root(IF_MV(int volume,) unsigned int *callflagsp, | ||
49 | struct file_base_info *infop, uint16_t *attrp); | ||
50 | int ns_open_stream(const char *path, unsigned int callflags, | ||
51 | struct filestr_base *stream, struct ns_scan_info *scanp); | ||
52 | |||
53 | /* closes the namespace stream */ | ||
54 | static inline int ns_close_stream(struct filestr_base *stream) | ||
55 | { | ||
56 | return close_stream_internal(stream); | ||
57 | } | ||
58 | |||
59 | #include "dircache_redirect.h" | ||
60 | |||
61 | static inline void ns_dirscan_rewind(struct ns_scan_info *scanp) | ||
62 | { | ||
63 | rewinddir_dirent(&scanp->scan); | ||
64 | if (scanp->item != -1) | ||
65 | scanp->item = 0; | ||
66 | } | ||
67 | |||
68 | static inline int ns_readdir_dirent(struct filestr_base *stream, | ||
69 | struct ns_scan_info *scanp, | ||
70 | struct dirent *entry) | ||
71 | |||
72 | { | ||
73 | if (scanp->item == -1) | ||
74 | return readdir_dirent(stream, &scanp->scan, entry); | ||
75 | else | ||
76 | return root_readdir_dirent(stream, scanp, entry); | ||
77 | } | ||
78 | |||
79 | #endif /* RB_NAMESPACE_H */ | ||