diff options
Diffstat (limited to 'firmware/common/dir.c')
-rw-r--r-- | firmware/common/dir.c | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/firmware/common/dir.c b/firmware/common/dir.c new file mode 100644 index 0000000000..da798c71d5 --- /dev/null +++ b/firmware/common/dir.c | |||
@@ -0,0 +1,413 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Björn Stenberg | ||
11 | * Copyright (C) 2014 by Michael Sevakis | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ****************************************************************************/ | ||
22 | #define DIRFUNCTIONS_DEFINED | ||
23 | #include "config.h" | ||
24 | #include <errno.h> | ||
25 | #include <string.h> | ||
26 | #include "debug.h" | ||
27 | #include "dir.h" | ||
28 | #include "pathfuncs.h" | ||
29 | #include "fileobj_mgr.h" | ||
30 | #include "dircache_redirect.h" | ||
31 | |||
32 | /* structure used for open directory streams */ | ||
33 | static struct dirstr_desc | ||
34 | { | ||
35 | struct filestr_base stream; /* basic stream info (first!) */ | ||
36 | struct dirscan_info scan; /* directory scan cursor */ | ||
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]; | ||
42 | |||
43 | /* check and return a struct dirstr_desc* from a DIR* */ | ||
44 | static struct dirstr_desc * get_dirstr(DIR *dirp) | ||
45 | { | ||
46 | struct dirstr_desc *dir = (struct dirstr_desc *)dirp; | ||
47 | |||
48 | if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS)) | ||
49 | dir = NULL; | ||
50 | else if (dir->stream.flags & FDO_BUSY) | ||
51 | return dir; | ||
52 | |||
53 | int errnum; | ||
54 | |||
55 | if (!dir) | ||
56 | { | ||
57 | errnum = EFAULT; | ||
58 | } | ||
59 | else if (dir->stream.flags == FV_NONEXIST) | ||
60 | { | ||
61 | DEBUGF("dir #%d: nonexistant device\n", (int)(dir - open_streams)); | ||
62 | errnum = ENXIO; | ||
63 | } | ||
64 | else | ||
65 | { | ||
66 | DEBUGF("dir #%d: dir not open\n", (int)(dir - open_streams)); | ||
67 | errnum = EBADF; | ||
68 | } | ||
69 | |||
70 | errno = errnum; | ||
71 | return NULL; | ||
72 | } | ||
73 | |||
74 | #define GET_DIRSTR(type, dirp) \ | ||
75 | ({ \ | ||
76 | file_internal_lock_##type(); \ | ||
77 | struct dirstr_desc *_dir = get_dirstr(dirp); \ | ||
78 | if (_dir) \ | ||
79 | FILESTR_LOCK(type, &_dir->stream); \ | ||
80 | else \ | ||
81 | file_internal_unlock_##type(); \ | ||
82 | _dir; \ | ||
83 | }) | ||
84 | |||
85 | /* release the lock on the dirstr_desc* */ | ||
86 | #define RELEASE_DIRSTR(type, dir) \ | ||
87 | ({ \ | ||
88 | FILESTR_UNLOCK(type, &(dir)->stream); \ | ||
89 | file_internal_unlock_##type(); \ | ||
90 | }) | ||
91 | |||
92 | |||
93 | /* find a free dir stream descriptor */ | ||
94 | static struct dirstr_desc * alloc_dirstr(void) | ||
95 | { | ||
96 | for (unsigned int dd = 0; dd < MAX_OPEN_DIRS; dd++) | ||
97 | { | ||
98 | struct dirstr_desc *dir = &open_streams[dd]; | ||
99 | if (!dir->stream.flags) | ||
100 | return dir; | ||
101 | } | ||
102 | |||
103 | DEBUGF("Too many dirs open\n"); | ||
104 | return NULL; | ||
105 | } | ||
106 | |||
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 | |||
151 | /** POSIX interface **/ | ||
152 | |||
153 | /* open a directory */ | ||
154 | DIR * opendir(const char *dirname) | ||
155 | { | ||
156 | DEBUGF("opendir(dirname=\"%s\"\n", dirname); | ||
157 | |||
158 | DIR *dirp = NULL; | ||
159 | |||
160 | file_internal_lock_WRITER(); | ||
161 | |||
162 | int rc; | ||
163 | |||
164 | struct dirstr_desc * const dir = alloc_dirstr(); | ||
165 | if (!dir) | ||
166 | FILE_ERROR(EMFILE, RC); | ||
167 | |||
168 | rc = open_stream_internal(dirname, FF_DIR, &dir->stream, NULL); | ||
169 | if (rc < 0) | ||
170 | { | ||
171 | DEBUGF("Open failed: %d\n", rc); | ||
172 | FILE_ERROR(ERRNO, RC); | ||
173 | } | ||
174 | |||
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; | ||
184 | file_error: | ||
185 | file_internal_unlock_WRITER(); | ||
186 | return dirp; | ||
187 | } | ||
188 | |||
189 | /* close a directory stream */ | ||
190 | int closedir(DIR *dirp) | ||
191 | { | ||
192 | int rc; | ||
193 | |||
194 | file_internal_lock_WRITER(); | ||
195 | |||
196 | /* needs to work even if marked "nonexistant" */ | ||
197 | struct dirstr_desc * const dir = (struct dirstr_desc *)dirp; | ||
198 | if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS)) | ||
199 | FILE_ERROR(EFAULT, -1); | ||
200 | |||
201 | if (!dir->stream.flags) | ||
202 | { | ||
203 | DEBUGF("dir #%d: dir not open\n", (int)(dir - open_streams)); | ||
204 | FILE_ERROR(EBADF, -2); | ||
205 | } | ||
206 | |||
207 | rc = close_stream_internal(&dir->stream); | ||
208 | if (rc < 0) | ||
209 | FILE_ERROR(ERRNO, rc * 10 - 3); | ||
210 | |||
211 | file_error: | ||
212 | file_internal_unlock_WRITER(); | ||
213 | return rc; | ||
214 | } | ||
215 | |||
216 | /* read a directory */ | ||
217 | struct dirent * readdir(DIR *dirp) | ||
218 | { | ||
219 | struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp); | ||
220 | if (!dir) | ||
221 | FILE_ERROR_RETURN(ERRNO, NULL); | ||
222 | |||
223 | struct dirent *res = NULL; | ||
224 | |||
225 | int rc = readdir_volume(dir, &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) | ||
234 | res = &dir->entry; | ||
235 | |||
236 | file_error: | ||
237 | RELEASE_DIRSTR(READER, dir); | ||
238 | |||
239 | if (rc > 1) | ||
240 | iso_decode_d_name(res->d_name); | ||
241 | |||
242 | return res; | ||
243 | } | ||
244 | |||
245 | #if 0 /* not included now but probably should be */ | ||
246 | /* read a directory (reentrant) */ | ||
247 | int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) | ||
248 | { | ||
249 | if (!result) | ||
250 | FILE_ERROR_RETURN(EFAULT, -2); | ||
251 | |||
252 | *result = NULL; | ||
253 | |||
254 | if (!entry) | ||
255 | FILE_ERROR_RETURN(EFAULT, -3); | ||
256 | |||
257 | struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp); | ||
258 | if (!dir) | ||
259 | FILE_ERROR_RETURN(ERRNO, -1); | ||
260 | |||
261 | int rc = readdir_volume(dir, entry); | ||
262 | if (rc == 0) | ||
263 | { | ||
264 | rc = readdir_dirent(&dir->stream, &dir->scan, entry); | ||
265 | if (rc < 0) | ||
266 | FILE_ERROR(EIO, rc * 10 - 4); | ||
267 | } | ||
268 | |||
269 | file_error: | ||
270 | RELEASE_DIRSTR(READER, dir); | ||
271 | |||
272 | if (rc > 0) | ||
273 | { | ||
274 | if (rc > 1) | ||
275 | iso_decode_d_name(entry->d_name); | ||
276 | |||
277 | *result = entry; | ||
278 | rc = 0; | ||
279 | } | ||
280 | |||
281 | return rc; | ||
282 | } | ||
283 | |||
284 | /* reset the position of a directory stream to the beginning of a directory */ | ||
285 | void rewinddir(DIR *dirp) | ||
286 | { | ||
287 | struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp); | ||
288 | if (!dir) | ||
289 | FILE_ERROR_RETURN(ERRNO); | ||
290 | |||
291 | rewinddir_dirent(&dir->scan); | ||
292 | |||
293 | #ifdef HAVE_MULTIVOLUME | ||
294 | if (dir->volumecounter != INT_MAX) | ||
295 | dir->volumecounter = 0; | ||
296 | #endif /* HAVE_MULTIVOLUME */ | ||
297 | |||
298 | RELEASE_DIRSTR(READER, dir); | ||
299 | } | ||
300 | |||
301 | #endif /* 0 */ | ||
302 | |||
303 | /* make a directory */ | ||
304 | int mkdir(const char *path) | ||
305 | { | ||
306 | DEBUGF("mkdir(path=\"%s\")\n", path); | ||
307 | |||
308 | int rc; | ||
309 | |||
310 | file_internal_lock_WRITER(); | ||
311 | |||
312 | struct filestr_base stream; | ||
313 | struct path_component_info compinfo; | ||
314 | rc = open_stream_internal(path, FF_DIR, &stream, &compinfo); | ||
315 | if (rc < 0) | ||
316 | { | ||
317 | DEBUGF("Can't open parent dir or path is not a directory\n"); | ||
318 | FILE_ERROR(ERRNO, rc * 10 - 1); | ||
319 | } | ||
320 | else if (rc > 0) | ||
321 | { | ||
322 | DEBUGF("File exists\n"); | ||
323 | FILE_ERROR(EEXIST, -2); | ||
324 | } | ||
325 | |||
326 | rc = create_stream_internal(&compinfo.parentinfo, compinfo.name, | ||
327 | compinfo.length, ATTR_NEW_DIRECTORY, | ||
328 | FO_DIRECTORY, &stream); | ||
329 | if (rc < 0) | ||
330 | FILE_ERROR(ERRNO, rc * 10 - 3); | ||
331 | |||
332 | rc = 0; | ||
333 | file_error: | ||
334 | close_stream_internal(&stream); | ||
335 | file_internal_unlock_WRITER(); | ||
336 | return rc; | ||
337 | } | ||
338 | |||
339 | /* remove a directory */ | ||
340 | int rmdir(const char *name) | ||
341 | { | ||
342 | DEBUGF("rmdir(name=\"%s\")\n", name); | ||
343 | |||
344 | if (name) | ||
345 | { | ||
346 | /* path may not end with "." */ | ||
347 | const char *basename; | ||
348 | size_t len = path_basename(name, &basename); | ||
349 | if (basename[0] == '.' && len == 1) | ||
350 | { | ||
351 | DEBUGF("Invalid path; last component is \".\"\n"); | ||
352 | FILE_ERROR_RETURN(EINVAL, -9); | ||
353 | } | ||
354 | } | ||
355 | |||
356 | file_internal_lock_WRITER(); | ||
357 | int rc = remove_stream_internal(name, NULL, FF_DIR); | ||
358 | file_internal_unlock_WRITER(); | ||
359 | return rc; | ||
360 | } | ||
361 | |||
362 | |||
363 | /** Extended interface **/ | ||
364 | |||
365 | /* return if two directory streams refer to the same directory */ | ||
366 | int samedir(DIR *dirp1, DIR *dirp2) | ||
367 | { | ||
368 | struct dirstr_desc * const dir1 = GET_DIRSTR(WRITER, dirp1); | ||
369 | if (!dir1) | ||
370 | FILE_ERROR_RETURN(ERRNO, -1); | ||
371 | |||
372 | int rc = -2; | ||
373 | |||
374 | struct dirstr_desc * const dir2 = get_dirstr(dirp2); | ||
375 | if (dir2) | ||
376 | rc = dir1->stream.bindp == dir2->stream.bindp ? 1 : 0; | ||
377 | |||
378 | RELEASE_DIRSTR(WRITER, dir1); | ||
379 | return rc; | ||
380 | } | ||
381 | |||
382 | /* test directory existence (returns 'false' if a file) */ | ||
383 | bool dir_exists(const char *dirname) | ||
384 | { | ||
385 | file_internal_lock_WRITER(); | ||
386 | bool rc = test_stream_exists_internal(dirname, FF_DIR) > 0; | ||
387 | file_internal_unlock_WRITER(); | ||
388 | return rc; | ||
389 | } | ||
390 | |||
391 | /* get the portable info from the native entry */ | ||
392 | struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry) | ||
393 | { | ||
394 | int rc; | ||
395 | if (!dirp || !entry) | ||
396 | FILE_ERROR(EFAULT, RC); | ||
397 | |||
398 | if (entry->d_name[0] == '\0') | ||
399 | FILE_ERROR(ENOENT, RC); | ||
400 | |||
401 | if ((file_size_t)entry->info.size > FILE_SIZE_MAX) | ||
402 | FILE_ERROR(EOVERFLOW, RC); | ||
403 | |||
404 | return (struct dirinfo) | ||
405 | { | ||
406 | .attribute = entry->info.attr, | ||
407 | .size = entry->info.size, | ||
408 | .mtime = fattime_mktime(entry->info.wrtdate, entry->info.wrttime), | ||
409 | }; | ||
410 | |||
411 | file_error: | ||
412 | return (struct dirinfo){ .attribute = 0 }; | ||
413 | } | ||