diff options
Diffstat (limited to 'firmware/common')
-rw-r--r-- | firmware/common/dir.c | 413 | ||||
-rw-r--r-- | firmware/common/dir_uncached.c | 312 | ||||
-rw-r--r-- | firmware/common/dircache.c | 3981 | ||||
-rw-r--r-- | firmware/common/disk.c | 451 | ||||
-rw-r--r-- | firmware/common/disk_cache.c | 343 | ||||
-rw-r--r-- | firmware/common/file.c | 1637 | ||||
-rw-r--r-- | firmware/common/file_internal.c | 776 | ||||
-rw-r--r-- | firmware/common/filefuncs.c | 102 | ||||
-rw-r--r-- | firmware/common/fileobj_mgr.c | 396 | ||||
-rw-r--r-- | firmware/common/pathfuncs.c | 421 | ||||
-rw-r--r-- | firmware/common/rbpaths.c | 432 | ||||
-rw-r--r-- | firmware/common/unicode.c | 451 |
12 files changed, 6732 insertions, 2983 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 | } | ||
diff --git a/firmware/common/dir_uncached.c b/firmware/common/dir_uncached.c deleted file mode 100644 index b850a514e7..0000000000 --- a/firmware/common/dir_uncached.c +++ /dev/null | |||
@@ -1,312 +0,0 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id: dir.c 13741 2007-06-30 02:08:27Z jethead71 $ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Björn Stenberg | ||
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 <stdio.h> | ||
22 | #include <errno.h> | ||
23 | #include <string.h> | ||
24 | #include <stdbool.h> | ||
25 | #include <stdlib.h> | ||
26 | #include "fat.h" | ||
27 | #include "dir.h" | ||
28 | #include "debug.h" | ||
29 | #include "filefuncs.h" | ||
30 | |||
31 | #if (MEMORYSIZE > 8) | ||
32 | #define MAX_OPEN_DIRS 12 | ||
33 | #else | ||
34 | #define MAX_OPEN_DIRS 8 | ||
35 | #endif | ||
36 | |||
37 | static DIR_UNCACHED opendirs[MAX_OPEN_DIRS]; | ||
38 | |||
39 | // release all dir handles on a given volume "by force", to avoid leaks | ||
40 | int release_dirs(int volume) | ||
41 | { | ||
42 | DIR_UNCACHED* pdir = opendirs; | ||
43 | int dd; | ||
44 | int closed = 0; | ||
45 | for ( dd=0; dd<MAX_OPEN_DIRS; dd++, pdir++) | ||
46 | { | ||
47 | #ifdef HAVE_MULTIVOLUME | ||
48 | if (pdir->fatdir.file.volume == volume) | ||
49 | #else | ||
50 | (void)volume; | ||
51 | #endif | ||
52 | { | ||
53 | pdir->busy = false; /* mark as available, no further action */ | ||
54 | closed++; | ||
55 | } | ||
56 | } | ||
57 | return closed; /* return how many we did */ | ||
58 | } | ||
59 | |||
60 | DIR_UNCACHED* opendir_uncached(const char* name) | ||
61 | { | ||
62 | char namecopy[MAX_PATH]; | ||
63 | char* part; | ||
64 | char* end; | ||
65 | struct fat_direntry entry; | ||
66 | int dd; | ||
67 | DIR_UNCACHED* pdir = opendirs; | ||
68 | #ifdef HAVE_MULTIVOLUME | ||
69 | int volume; | ||
70 | #endif | ||
71 | |||
72 | if ( name[0] != '/' ) { | ||
73 | DEBUGF("Only absolute paths supported right now\n"); | ||
74 | return NULL; | ||
75 | } | ||
76 | |||
77 | /* find a free dir descriptor */ | ||
78 | for ( dd=0; dd<MAX_OPEN_DIRS; dd++, pdir++) | ||
79 | if ( !pdir->busy ) | ||
80 | break; | ||
81 | |||
82 | if ( dd == MAX_OPEN_DIRS ) { | ||
83 | DEBUGF("Too many dirs open\n"); | ||
84 | errno = EMFILE; | ||
85 | return NULL; | ||
86 | } | ||
87 | |||
88 | pdir->busy = true; | ||
89 | |||
90 | #ifdef HAVE_MULTIVOLUME | ||
91 | /* try to extract a heading volume name, if present */ | ||
92 | volume = strip_volume(name, namecopy); | ||
93 | pdir->volumecounter = 0; | ||
94 | #else | ||
95 | strlcpy(namecopy, name, sizeof(namecopy)); /* just copy */ | ||
96 | #endif | ||
97 | |||
98 | if ( fat_opendir(IF_MV(volume,) &pdir->fatdir, 0, NULL) < 0 ) { | ||
99 | DEBUGF("Failed opening root dir\n"); | ||
100 | pdir->busy = false; | ||
101 | return NULL; | ||
102 | } | ||
103 | |||
104 | for ( part = strtok_r(namecopy, "/", &end); part; | ||
105 | part = strtok_r(NULL, "/", &end)) { | ||
106 | /* scan dir for name */ | ||
107 | while (1) { | ||
108 | if ((fat_getnext(&pdir->fatdir,&entry) < 0) || | ||
109 | (!entry.name[0])) { | ||
110 | pdir->busy = false; | ||
111 | return NULL; | ||
112 | } | ||
113 | if ( (entry.attr & FAT_ATTR_DIRECTORY) && | ||
114 | (!strcasecmp(part, entry.name)) ) { | ||
115 | /* In reality, the parent_dir parameter of fat_opendir seems | ||
116 | * useless because it's sole purpose it to have a way to | ||
117 | * update the file metadata, but here we are only reading | ||
118 | * a directory so there's no need for that kind of stuff. | ||
119 | * However, the rmdir_uncached function uses a ugly hack to | ||
120 | * avoid opening a directory twice when deleting it and thus | ||
121 | * needs those information. That's why we pass pdir->fatdir both | ||
122 | * as the parent directory and the resulting one (this is safe, | ||
123 | * in doubt, check fat_open(dir) code) which will allow this kind of | ||
124 | * (ugly) things */ | ||
125 | if ( fat_opendir(IF_MV(volume,) | ||
126 | &pdir->fatdir, | ||
127 | entry.firstcluster, | ||
128 | &pdir->fatdir) < 0 ) { | ||
129 | DEBUGF("Failed opening dir '%s' (%ld)\n", | ||
130 | part, entry.firstcluster); | ||
131 | pdir->busy = false; | ||
132 | return NULL; | ||
133 | } | ||
134 | #ifdef HAVE_MULTIVOLUME | ||
135 | pdir->volumecounter = -1; /* n.a. to subdirs */ | ||
136 | #endif | ||
137 | break; | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | |||
142 | return pdir; | ||
143 | } | ||
144 | |||
145 | int closedir_uncached(DIR_UNCACHED* dir) | ||
146 | { | ||
147 | dir->busy=false; | ||
148 | return 0; | ||
149 | } | ||
150 | |||
151 | struct dirent_uncached* readdir_uncached(DIR_UNCACHED* dir) | ||
152 | { | ||
153 | struct fat_direntry entry; | ||
154 | struct dirent_uncached* theent = &(dir->theent); | ||
155 | |||
156 | if (!dir->busy) | ||
157 | return NULL; | ||
158 | |||
159 | #ifdef HAVE_MULTIVOLUME | ||
160 | /* Volumes (secondary file systems) get inserted into the root directory | ||
161 | of the first volume, since we have no separate top level. */ | ||
162 | if (dir->volumecounter >= 0 /* on a root dir */ | ||
163 | && dir->volumecounter < NUM_VOLUMES /* in range */ | ||
164 | && dir->fatdir.file.volume == 0) /* at volume 0 */ | ||
165 | { /* fake special directories, which don't really exist, but | ||
166 | will get redirected upon opendir_uncached() */ | ||
167 | while (++dir->volumecounter < NUM_VOLUMES) | ||
168 | { | ||
169 | if (fat_ismounted(dir->volumecounter)) | ||
170 | { | ||
171 | memset(theent, 0, sizeof(*theent)); | ||
172 | theent->info.attribute = FAT_ATTR_DIRECTORY | FAT_ATTR_VOLUME; | ||
173 | snprintf(theent->d_name, sizeof(theent->d_name), | ||
174 | VOL_NAMES, dir->volumecounter); | ||
175 | return theent; | ||
176 | } | ||
177 | } | ||
178 | } | ||
179 | #endif | ||
180 | /* normal directory entry fetching follows here */ | ||
181 | if (fat_getnext(&(dir->fatdir),&entry) < 0) | ||
182 | return NULL; | ||
183 | |||
184 | if ( !entry.name[0] ) | ||
185 | return NULL; | ||
186 | |||
187 | strlcpy(theent->d_name, entry.name, sizeof(theent->d_name)); | ||
188 | theent->info.attribute = entry.attr; | ||
189 | theent->info.wrtdate = entry.wrtdate; | ||
190 | theent->info.wrttime = entry.wrttime; | ||
191 | theent->info.size = entry.filesize; | ||
192 | theent->startcluster = entry.firstcluster; | ||
193 | |||
194 | return theent; | ||
195 | } | ||
196 | |||
197 | int mkdir_uncached(const char *name) | ||
198 | { | ||
199 | DIR_UNCACHED *dir; | ||
200 | char namecopy[MAX_PATH]; | ||
201 | char* end; | ||
202 | char *basename; | ||
203 | char *parent; | ||
204 | struct dirent_uncached *entry; | ||
205 | int dd; | ||
206 | DIR_UNCACHED* pdir = opendirs; | ||
207 | struct fat_dir *newdir; | ||
208 | int rc; | ||
209 | |||
210 | if ( name[0] != '/' ) { | ||
211 | DEBUGF("mkdir: Only absolute paths supported right now\n"); | ||
212 | return -1; | ||
213 | } | ||
214 | /* find a free dir descriptor */ | ||
215 | for ( dd=0; dd<MAX_OPEN_DIRS; dd++, pdir++) | ||
216 | if ( !pdir->busy ) | ||
217 | break; | ||
218 | |||
219 | if ( dd == MAX_OPEN_DIRS ) { | ||
220 | DEBUGF("Too many dirs open\n"); | ||
221 | errno = EMFILE; | ||
222 | return -5; | ||
223 | } | ||
224 | |||
225 | pdir->busy = true; | ||
226 | newdir = &pdir->fatdir; | ||
227 | |||
228 | strlcpy(namecopy, name, sizeof(namecopy)); | ||
229 | |||
230 | /* Split the base name and the path */ | ||
231 | end = strrchr(namecopy, '/'); | ||
232 | *end = 0; | ||
233 | basename = end+1; | ||
234 | |||
235 | if(namecopy == end) /* Root dir? */ | ||
236 | parent = "/"; | ||
237 | else | ||
238 | parent = namecopy; | ||
239 | |||
240 | DEBUGF("mkdir: parent: %s, name: %s\n", parent, basename); | ||
241 | |||
242 | dir = opendir_uncached(parent); | ||
243 | |||
244 | if(!dir) { | ||
245 | DEBUGF("mkdir: can't open parent dir\n"); | ||
246 | pdir->busy = false; | ||
247 | return -2; | ||
248 | } | ||
249 | |||
250 | if(basename[0] == 0) { | ||
251 | DEBUGF("mkdir: Empty dir name\n"); | ||
252 | pdir->busy = false; | ||
253 | errno = EINVAL; | ||
254 | return -3; | ||
255 | } | ||
256 | |||
257 | /* Now check if the name already exists */ | ||
258 | while ((entry = readdir_uncached(dir))) { | ||
259 | if ( !strcasecmp(basename, entry->d_name) ) { | ||
260 | DEBUGF("mkdir error: file exists\n"); | ||
261 | errno = EEXIST; | ||
262 | closedir_uncached(dir); | ||
263 | pdir->busy = false; | ||
264 | return - 4; | ||
265 | } | ||
266 | } | ||
267 | |||
268 | memset(newdir, 0, sizeof(struct fat_dir)); | ||
269 | |||
270 | rc = fat_create_dir(basename, newdir, &(dir->fatdir)); | ||
271 | closedir_uncached(dir); | ||
272 | pdir->busy = false; | ||
273 | |||
274 | return rc; | ||
275 | } | ||
276 | |||
277 | int rmdir_uncached(const char* name) | ||
278 | { | ||
279 | int rc; | ||
280 | DIR_UNCACHED* dir; | ||
281 | struct dirent_uncached* entry; | ||
282 | |||
283 | dir = opendir_uncached(name); | ||
284 | if (!dir) | ||
285 | { | ||
286 | errno = ENOENT; /* open error */ | ||
287 | return -1; | ||
288 | } | ||
289 | |||
290 | /* check if the directory is empty */ | ||
291 | while ((entry = readdir_uncached(dir))) | ||
292 | { | ||
293 | if (strcmp(entry->d_name, ".") && | ||
294 | strcmp(entry->d_name, "..")) | ||
295 | { | ||
296 | DEBUGF("rmdir error: not empty\n"); | ||
297 | errno = ENOTEMPTY; | ||
298 | closedir_uncached(dir); | ||
299 | return -2; | ||
300 | } | ||
301 | } | ||
302 | |||
303 | rc = fat_remove(&(dir->fatdir.file)); | ||
304 | if ( rc < 0 ) { | ||
305 | DEBUGF("Failed removing dir: %d\n", rc); | ||
306 | errno = EIO; | ||
307 | rc = rc * 10 - 3; | ||
308 | } | ||
309 | |||
310 | closedir_uncached(dir); | ||
311 | return rc; | ||
312 | } | ||
diff --git a/firmware/common/dircache.c b/firmware/common/dircache.c index b53fc4d7a6..1e580bf3af 100644 --- a/firmware/common/dircache.c +++ b/firmware/common/dircache.c | |||
@@ -8,6 +8,7 @@ | |||
8 | * $Id$ | 8 | * $Id$ |
9 | * | 9 | * |
10 | * Copyright (C) 2005 by Miika Pekkarinen | 10 | * Copyright (C) 2005 by Miika Pekkarinen |
11 | * Copyright (C) 2014 by Michael Sevakis | ||
11 | * | 12 | * |
12 | * This program is free software; you can redistribute it and/or | 13 | * This program is free software; you can redistribute it and/or |
13 | * modify it under the terms of the GNU General Public License | 14 | * modify it under the terms of the GNU General Public License |
@@ -18,13 +19,7 @@ | |||
18 | * KIND, either express or implied. | 19 | * KIND, either express or implied. |
19 | * | 20 | * |
20 | ****************************************************************************/ | 21 | ****************************************************************************/ |
21 | |||
22 | /* TODO: | ||
23 | - Allow cache live updating while transparent rebuild is running. | ||
24 | */ | ||
25 | |||
26 | #include "config.h" | 22 | #include "config.h" |
27 | |||
28 | #include <stdio.h> | 23 | #include <stdio.h> |
29 | #include <errno.h> | 24 | #include <errno.h> |
30 | #include "string-extra.h" | 25 | #include "string-extra.h" |
@@ -33,6 +28,8 @@ | |||
33 | #include "debug.h" | 28 | #include "debug.h" |
34 | #include "system.h" | 29 | #include "system.h" |
35 | #include "logf.h" | 30 | #include "logf.h" |
31 | #include "fileobj_mgr.h" | ||
32 | #include "pathfuncs.h" | ||
36 | #include "dircache.h" | 33 | #include "dircache.h" |
37 | #include "thread.h" | 34 | #include "thread.h" |
38 | #include "kernel.h" | 35 | #include "kernel.h" |
@@ -42,393 +39,1626 @@ | |||
42 | #include "dir.h" | 39 | #include "dir.h" |
43 | #include "storage.h" | 40 | #include "storage.h" |
44 | #include "audio.h" | 41 | #include "audio.h" |
45 | #if CONFIG_RTC | ||
46 | #include "time.h" | ||
47 | #include "timefuncs.h" | ||
48 | #endif | ||
49 | #include "rbpaths.h" | 42 | #include "rbpaths.h" |
43 | #include "linked_list.h" | ||
44 | #ifdef HAVE_EEPROM_SETTINGS | ||
45 | #include "crc32.h" | ||
46 | #endif | ||
50 | 47 | ||
48 | /** | ||
49 | * Cache memory layout: | ||
50 | * x - array of struct dircache_entry | ||
51 | * r - reserved buffer | ||
52 | * d - name buffer for the name entry of the struct dircache_entry | ||
53 | * 0 - zero bytes to assist free name block sentinel scanning (not 0xfe or 0xff) | ||
54 | * |xxxxxx|rrrrrrrrr|0|dddddd|0| | ||
55 | * | ||
56 | * Subsequent x are allocated from the front, d are allocated from the back, | ||
57 | * using the reserve buffer for entries added after initial scan. | ||
58 | * | ||
59 | * After a while the cache may look like: | ||
60 | * |xxxxxxxx|rrrrr|0|dddddddd|0| | ||
61 | * | ||
62 | * After a reboot, the reserve buffer is restored in it's size, so that the | ||
63 | * total allocation size grows: | ||
64 | * |xxxxxxxx|rrrrrrrrr|0|dddddddd|0| | ||
65 | * | ||
66 | * | ||
67 | * Cache structure: | ||
68 | * Format is memory position independent and uses only indexes as links. The | ||
69 | * buffer pointers are offset back by one entry to make the array 1-based so | ||
70 | * that an index of 0 may be considered an analog of a NULL pointer. | ||
71 | * | ||
72 | * Entry elements are linked together analagously to the filesystem directory | ||
73 | * structure with minor variations that are helpful to the cache's algorithms. | ||
74 | * Each volume has a special root structure in the dircache structure, not an | ||
75 | * entry in the cache, comprising a forest of volume trees which facilitates | ||
76 | * mounting or dismounting of specified volumes on the fly. | ||
77 | * | ||
78 | * Indexes identifying a volume are computed as: index = -volume - 1 | ||
79 | * Returning the volume from these indexes is thus: volume = -index - 1 | ||
80 | * Such indexes are used in root binding and as the 'up' index for an entry | ||
81 | * who's parent is the root directory. | ||
82 | * | ||
83 | * Open files list: | ||
84 | * When dircache is made it is the maintainer of the main volume open files | ||
85 | * lists, even when it is off. Any files open before dircache is enabled or | ||
86 | * initialized must be bound to cache entries by the scan and build operation. | ||
87 | * It maintains these lists in a special way. | ||
88 | * | ||
89 | * Queued (unresolved) bindings are at the back and resolved at the front. | ||
90 | * A pointer to the first of each kind of binding is provided to skip to help | ||
91 | * iterating one sublist or another. | ||
92 | * | ||
93 | * r0->r1->r2->q0->q1->q2->NULL | ||
94 | * ^resolved0 ^queued0 | ||
95 | */ | ||
51 | 96 | ||
52 | /* Queue commands. */ | 97 | #ifdef DIRCACHE_NATIVE |
53 | #define DIRCACHE_BUILD 1 | 98 | #define dircache_lock() file_internal_lock_WRITER() |
54 | #define DIRCACHE_STOP 2 | 99 | #define dircache_unlock() file_internal_unlock_WRITER() |
55 | 100 | ||
56 | #if (MEMORYSIZE > 8) | 101 | /* scan and build parameter data */ |
57 | #define MAX_OPEN_DIRS 12 | 102 | struct sab_component; |
58 | #else | 103 | struct sab |
59 | #define MAX_OPEN_DIRS 8 | 104 | { |
60 | #endif | 105 | struct filestr_base stream; /* scan directory stream */ |
61 | static DIR_CACHED opendirs[MAX_OPEN_DIRS]; | 106 | struct file_base_info info; /* scanned entry info */ |
62 | static char opendir_dnames[MAX_OPEN_DIRS][MAX_PATH]; | 107 | bool volatile quit; /* halt all scanning */ |
108 | struct sab_component *stackend; /* end of stack pointer */ | ||
109 | struct sab_component *top; /* current top of stack */ | ||
110 | struct sab_component | ||
111 | { | ||
112 | int volatile idx; /* cache index of directory */ | ||
113 | int *downp; /* pointer to ce->down */ | ||
114 | int *volatile prevp; /* previous item accessed */ | ||
115 | } stack[]; /* "recursion" stack */ | ||
116 | }; | ||
117 | |||
118 | #else /* !DIRCACHE_NATIVE */ | ||
119 | |||
120 | #error need locking scheme | ||
121 | #define FILESYS_WRITER_LOCK() | ||
122 | #define FILESYS_WRITER_UNLOCK() | ||
63 | 123 | ||
64 | #define MAX_PENDING_BINDINGS 2 | 124 | struct sab_component |
65 | struct fdbind_queue { | 125 | { |
126 | }; | ||
127 | |||
128 | struct sab | ||
129 | { | ||
130 | #ifdef HAVE_MUTLIVOLUME | ||
131 | int volume; | ||
132 | #endif /* HAVE_MULTIVOLUME */ | ||
66 | char path[MAX_PATH]; | 133 | char path[MAX_PATH]; |
67 | int fd; | 134 | unsigned int append; |
135 | }; | ||
136 | #endif /* DIRCACHE_NATIVE */ | ||
137 | |||
138 | enum | ||
139 | { | ||
140 | FRONTIER_SETTLED = 0x0, /* dir entry contents are complete */ | ||
141 | FRONTIER_NEW = 0x1, /* dir entry contents are in-progress */ | ||
142 | FRONTIER_ZONED = 0x2, /* frontier entry permanent mark (very sticky!) */ | ||
143 | FRONTIER_RENEW = 0x4, /* override FRONTIER_ZONED sticky (not stored) */ | ||
68 | }; | 144 | }; |
69 | 145 | ||
70 | /* Unions with char to make pointer arithmetic simpler and avoid casting */ | 146 | enum |
71 | struct dircache_entry { | 147 | { |
72 | struct dirinfo info; | 148 | DCM_BUILD, /* build a volume */ |
149 | DCM_PROCEED, /* merged DCM_BUILD messages */ | ||
150 | DCM_FIRST = DCM_BUILD, | ||
151 | DCM_LAST = DCM_PROCEED, | ||
152 | }; | ||
153 | |||
154 | #define MAX_TINYNAME sizeof (uint32_t) | ||
155 | #define DC_MAX_NAME MIN(MAX_NAME, UINT8_MAX) | ||
156 | |||
157 | /* Throw some warnings if about the limits if things may not work */ | ||
158 | #if MAX_NAME > UINT8_MAX | ||
159 | #warning Need more than 8 bits in name length bitfield | ||
160 | #endif | ||
161 | |||
162 | #if DIRCACHE_LIMIT > ((1 << 24)-255) | ||
163 | #warning Names may not be addressable with 24 bits | ||
164 | #endif | ||
165 | |||
166 | /* data structure used by cache entries */ | ||
167 | struct dircache_entry | ||
168 | { | ||
169 | int next; /* next at same level */ | ||
73 | union { | 170 | union { |
74 | struct dircache_entry *next; | 171 | int down; /* first at child level (if directory) */ |
75 | char* next_char; | 172 | file_size_t filesize; /* size of file in bytes (if file) */ |
76 | }; | 173 | }; |
174 | int up; /* parent index (-volume-1 if root) */ | ||
77 | union { | 175 | union { |
78 | struct dircache_entry *up; | 176 | struct { |
79 | char* up_char; | 177 | uint32_t name : 24; /* indirect storage (.tinyname == 0) */ |
178 | uint32_t length : 8; /* length of name indexed by 'name' */ | ||
80 | }; | 179 | }; |
81 | union { | 180 | unsigned char namebuf[MAX_TINYNAME]; /* direct storage (.tinyname == 1) */ |
82 | struct dircache_entry *down; | ||
83 | char* down_char; | ||
84 | }; | 181 | }; |
85 | long startcluster; | 182 | uint32_t direntry : 16; /* entry # in parent - max 0xffff */ |
86 | char *d_name; | 183 | uint32_t direntries : 5; /* # of entries used - max 21 */ |
87 | }; | 184 | uint32_t tinyname : 1; /* if == 1, name fits in .namebuf */ |
88 | 185 | uint32_t frontier : 2; /* (FRONTIER_* bitflags) */ | |
89 | /* Cache Layout: | 186 | uint32_t attr : 8; /* entry file attributes */ |
90 | * | 187 | #ifdef DIRCACHE_NATIVE |
91 | * x - array of struct dircache_entry | 188 | long firstcluster; /* first file cluster - max 0x0ffffff4 */ |
92 | * r - reserved buffer | 189 | uint16_t wrtdate; /* FAT write date */ |
93 | * d - name buffer for the d_name entry of the struct dircache_entry | 190 | uint16_t wrttime; /* FAT write time */ |
94 | * |xxxxxx|rrrrrrrrr|dddddd| | 191 | #else |
95 | * | 192 | time_t mtime; /* file last-modified time */ |
96 | * subsequent x are allocated from the front, d are allocated from the back, | ||
97 | * using the reserve buffer for entries added after initial scan | ||
98 | * | ||
99 | * after a while the cache may look like: | ||
100 | * |xxxxxxxx|rrrrr|dddddddd| | ||
101 | * | ||
102 | * after a reboot, the reserve buffer is restored in it's size, so that the | ||
103 | * total allocation size grows | ||
104 | * |xxxxxxxx|rrrrrrrrr|dddddddd| | ||
105 | */ | ||
106 | /* this points to the beginnging of the buffer and the first entry */ | ||
107 | static struct dircache_entry *dircache_root; | ||
108 | /* these point to the start and end of the name buffer (d above) */ | ||
109 | static char *d_names_start, *d_names_end; | ||
110 | /* put "." and ".." into the d_names buffer to enable easy pointer logic */ | ||
111 | static char *dot, *dotdot; | ||
112 | #ifdef HAVE_MULTIVOLUME | ||
113 | static struct dircache_entry *append_position; | ||
114 | #endif | 193 | #endif |
194 | dc_serial_t serialnum; /* entry serial number */ | ||
195 | }; | ||
115 | 196 | ||
116 | static DIR_CACHED opendirs[MAX_OPEN_DIRS]; | 197 | /* spare us some tedium */ |
117 | static struct dircache_entry *fd_bindings[MAX_OPEN_FILES]; | 198 | #define ENTRYSIZE (sizeof (struct dircache_entry)) |
118 | |||
119 | static bool dircache_initialized = false; | ||
120 | static bool dircache_initializing = false; | ||
121 | static bool thread_enabled = false; | ||
122 | static unsigned long allocated_size = 0; | ||
123 | static unsigned long dircache_size = 0; | ||
124 | static unsigned long entry_count = 0; | ||
125 | static unsigned long reserve_used = 0; | ||
126 | static unsigned int cache_build_ticks = 0; | ||
127 | static unsigned long appflags = 0; | ||
128 | 199 | ||
200 | /* thread and kernel stuff */ | ||
129 | static struct event_queue dircache_queue SHAREDBSS_ATTR; | 201 | static struct event_queue dircache_queue SHAREDBSS_ATTR; |
130 | static long dircache_stack[(DEFAULT_STACK_SIZE + 0x400)/sizeof(long)]; | 202 | static uintptr_t dircache_stack[DIRCACHE_STACK_SIZE / sizeof (uintptr_t)]; |
131 | static const char dircache_thread_name[] = "dircache"; | 203 | static const char dircache_thread_name[] = "dircache"; |
132 | 204 | ||
133 | static struct fdbind_queue fdbind_cache[MAX_PENDING_BINDINGS]; | 205 | /* struct that is both used during run time and for persistent storage */ |
134 | static int fdbind_idx = 0; | 206 | static struct dircache |
207 | { | ||
208 | /* cache-wide data */ | ||
209 | int free_list; /* first index of free entry list */ | ||
210 | size_t size; /* total size of data (including holes) */ | ||
211 | size_t sizeused; /* bytes of .size bytes actually used */ | ||
212 | union { | ||
213 | unsigned int numentries; /* entry count (including holes) */ | ||
214 | #ifdef HAVE_EEPROM_SETTINGS | ||
215 | size_t sizeentries; /* used when persisting */ | ||
216 | #endif | ||
217 | }; | ||
218 | int names; /* index of first name in name block */ | ||
219 | size_t sizenames; /* size of all names (including holes) */ | ||
220 | size_t namesfree; /* amount of wasted name space */ | ||
221 | int nextnamefree; /* hint of next free name in buffer */ | ||
222 | /* per-volume data */ | ||
223 | struct dircache_volume /* per volume cache data */ | ||
224 | { | ||
225 | uint32_t status : 2; /* cache status of this volume */ | ||
226 | uint32_t frontier : 2; /* (FRONTIER_* bitflags) */ | ||
227 | dc_serial_t serialnum; /* dircache serial number of root */ | ||
228 | int root_down; /* index of first entry of volume root */ | ||
229 | union { | ||
230 | long start_tick; /* when did scan start (scanning) */ | ||
231 | long build_ticks; /* how long to build volume? (ready) */ | ||
232 | }; | ||
233 | } dcvol[NUM_VOLUMES]; | ||
234 | /* these remain unchanged between cache resets */ | ||
235 | size_t last_size; /* last reported size at boot */ | ||
236 | size_t reserve_used; /* reserved used at last build */ | ||
237 | dc_serial_t last_serialnum; /* last serialnumber generated */ | ||
238 | } dircache; | ||
239 | |||
240 | /* struct that is used only for the cache in main memory */ | ||
241 | struct dircache_runinfo | ||
242 | { | ||
243 | /* cache setting and build info */ | ||
244 | int suspended; /* dircache suspend count */ | ||
245 | bool enabled; /* dircache master enable switch */ | ||
246 | unsigned int thread_id; /* current/last thread id */ | ||
247 | bool thread_done; /* thread has exited */ | ||
248 | /* cache buffer info */ | ||
249 | int handle; /* buflib buffer handle */ | ||
250 | size_t bufsize; /* size of buflib allocation - 1 */ | ||
251 | int buflocked; /* don't move due to other allocs */ | ||
252 | union { | ||
253 | void *p; /* address of buffer - ENTRYSIZE */ | ||
254 | struct dircache_entry *pentry; /* alias of .p to assist entry resolution */ | ||
255 | unsigned char *pname; /* alias of .p to assist name resolution */ | ||
256 | }; | ||
257 | struct buflib_callbacks ops; /* buflib ops callbacks */ | ||
258 | /* per-volume data */ | ||
259 | struct dircache_runinfo_volume | ||
260 | { | ||
261 | struct file_base_binding *resolved0; /* first resolved binding in list */ | ||
262 | struct file_base_binding *queued0; /* first queued binding in list */ | ||
263 | struct sab *sabp; /* if building, struct sab in use */ | ||
264 | } dcrivol[NUM_VOLUMES]; | ||
265 | } dircache_runinfo; | ||
266 | |||
267 | #define BINDING_NEXT(bindp) \ | ||
268 | ((struct file_base_binding *)(bindp)->node.next) | ||
269 | |||
270 | #define FOR_EACH_BINDING(start, p) \ | ||
271 | for (struct file_base_binding *p = (start); p; p = BINDING_NEXT(p)) | ||
272 | |||
273 | #define FOR_EACH_CACHE_ENTRY(ce) \ | ||
274 | for (struct dircache_entry *ce = &dircache_runinfo.pentry[1], \ | ||
275 | *_ceend = ce + dircache.numentries; \ | ||
276 | ce < _ceend; ce++) if (ce->serialnum) | ||
277 | |||
278 | #define FOR_EACH_SAB_COMP(sabp, p) \ | ||
279 | for (struct sab_component *p = (sabp)->top; p < (sabp)->stackend; p++) | ||
280 | |||
281 | /* "overloaded" macros to get volume structures */ | ||
282 | #define DCVOL_i(i) (&dircache.dcvol[i]) | ||
283 | #define DCVOL_volume(volume) (&dircache.dcvol[volume]) | ||
284 | #define DCVOL_infop(infop) (&dircache.dcvol[BASEINFO_VOL(infop)]) | ||
285 | #define DCVOL_dirinfop(dirinfop) (&dircache.dcvol[BASEINFO_VOL(dirinfop)]) | ||
286 | #define DCVOL(x) DCVOL_##x(x) | ||
287 | |||
288 | #define DCRIVOL_i(i) (&dircache_runinfo.dcrivol[i]) | ||
289 | #define DCRIVOL_infop(infop) (&dircache_runinfo.dcrivol[BASEINFO_VOL(infop)]) | ||
290 | #define DCRIVOL_bindp(bindp) (&dircache_runinfo.dcrivol[BASEBINDING_VOL(bindp)]) | ||
291 | #define DCRIVOL(x) DCRIVOL_##x(x) | ||
292 | |||
293 | /* reserve over 75% full? */ | ||
294 | #define DIRCACHE_STUFFED(reserve_used) \ | ||
295 | ((reserve_used) > 3*DIRCACHE_RESERVE / 4) | ||
135 | 296 | ||
136 | /* --- Internal cache structure control functions --- */ | 297 | #ifdef HAVE_EEPROM_SETTINGS |
298 | /** | ||
299 | * remove the snapshot file | ||
300 | */ | ||
301 | static int remove_dircache_file(void) | ||
302 | { | ||
303 | return remove(DIRCACHE_FILE); | ||
304 | } | ||
305 | |||
306 | /** | ||
307 | * open or create the snapshot file | ||
308 | */ | ||
309 | static int open_dircache_file(int oflag) | ||
310 | { | ||
311 | return open(DIRCACHE_FILE, oflag, 0666); | ||
312 | } | ||
313 | #endif /* HAVE_EEPROM_SETTINGS */ | ||
137 | 314 | ||
138 | static inline struct dircache_entry* get_entry(int id) | 315 | #ifdef DIRCACHE_DUMPSTER |
316 | /** | ||
317 | * clean up the memory allocation to make viewing in a hex editor more friendly | ||
318 | * and highlight problems | ||
319 | */ | ||
320 | static inline void dumpster_clean_buffer(void *p, size_t size) | ||
139 | { | 321 | { |
140 | return &dircache_root[id]; | 322 | memset(p, 0xAA, size); |
141 | } | 323 | } |
324 | #endif /* DIRCACHE_DUMPSTER */ | ||
142 | 325 | ||
143 | /* flag to make sure buffer doesn't move due to other allocs. | 326 | /** |
144 | * this is set to true completely during dircache build */ | 327 | * relocate the cache when the buffer has moved |
145 | static bool dont_move = false; | 328 | */ |
146 | static int dircache_handle; | 329 | static int move_callback(int handle, void *current, void *new) |
147 | static int move_callback(int handle, void* current, void* new) | ||
148 | { | 330 | { |
149 | (void)handle; | 331 | if (dircache_runinfo.buflocked) |
150 | if (dont_move) | ||
151 | return BUFLIB_CB_CANNOT_MOVE; | 332 | return BUFLIB_CB_CANNOT_MOVE; |
152 | 333 | ||
153 | #define UPDATE(x) if (x) { x = PTR_ADD(x, diff); } | 334 | dircache_runinfo.p = new - ENTRYSIZE; |
154 | /* relocate the cache */ | 335 | |
155 | ptrdiff_t diff = new - current; | 336 | return BUFLIB_CB_OK; |
156 | for(unsigned i = 0; i < entry_count; i++) | 337 | (void)handle; (void)current; |
338 | } | ||
339 | |||
340 | /** | ||
341 | * add a "don't move" lock count | ||
342 | */ | ||
343 | static inline void buffer_lock(void) | ||
344 | { | ||
345 | dircache_runinfo.buflocked++; | ||
346 | } | ||
347 | |||
348 | /** | ||
349 | * remove a "don't move" lock count | ||
350 | */ | ||
351 | static inline void buffer_unlock(void) | ||
352 | { | ||
353 | dircache_runinfo.buflocked--; | ||
354 | } | ||
355 | |||
356 | |||
357 | /** Open file bindings management **/ | ||
358 | |||
359 | /* compare the basic file information and return 'true' if they are logically | ||
360 | equivalent or the same item, else return 'false' if not */ | ||
361 | static inline bool binding_compare(const struct file_base_info *infop1, | ||
362 | const struct file_base_info *infop2) | ||
363 | { | ||
364 | #ifdef DIRCACHE_NATIVE | ||
365 | return fat_file_is_same(&infop1->fatfile, &infop2->fatfile); | ||
366 | #else | ||
367 | #error hey watch it! | ||
368 | #endif | ||
369 | } | ||
370 | |||
371 | /** | ||
372 | * bind a file to the cache; "queued" or "resolved" depending upon whether or | ||
373 | * not it has entry information | ||
374 | */ | ||
375 | static void binding_open(struct file_base_binding *bindp) | ||
376 | { | ||
377 | struct dircache_runinfo_volume *dcrivolp = DCRIVOL(bindp); | ||
378 | if (bindp->info.dcfile.serialnum) | ||
157 | { | 379 | { |
158 | UPDATE(dircache_root[i].d_name); | 380 | /* already resolved */ |
159 | UPDATE(dircache_root[i].next_char); | 381 | dcrivolp->resolved0 = bindp; |
160 | UPDATE(dircache_root[i].up_char); | 382 | file_binding_insert_first(bindp); |
161 | UPDATE(dircache_root[i].down_char); | ||
162 | } | 383 | } |
163 | dircache_root = new; | 384 | else |
164 | UPDATE(d_names_start); | 385 | { |
165 | UPDATE(d_names_end); | 386 | if (dcrivolp->queued0 == NULL) |
166 | UPDATE(dot); | 387 | dcrivolp->queued0 = bindp; |
167 | UPDATE(dotdot); | ||
168 | 388 | ||
169 | for(unsigned i = 0; i < MAX_OPEN_FILES; i++) | 389 | file_binding_insert_last(bindp); |
170 | UPDATE(fd_bindings[i]); | 390 | } |
391 | } | ||
171 | 392 | ||
172 | #ifdef HAVE_MULTIVOLUME | 393 | /** |
173 | UPDATE(append_position); | 394 | * remove a binding from the cache |
174 | #endif | 395 | */ |
175 | return BUFLIB_CB_OK; | 396 | static void binding_close(struct file_base_binding *bindp) |
397 | { | ||
398 | struct dircache_runinfo_volume *dcrivolp = DCRIVOL(bindp); | ||
399 | |||
400 | if (bindp == dcrivolp->queued0) | ||
401 | dcrivolp->queued0 = BINDING_NEXT(bindp); | ||
402 | else if (bindp == dcrivolp->resolved0) | ||
403 | { | ||
404 | struct file_base_binding *nextp = BINDING_NEXT(bindp); | ||
405 | dcrivolp->resolved0 = (nextp == dcrivolp->queued0) ? NULL : nextp; | ||
406 | } | ||
407 | |||
408 | file_binding_remove(bindp); | ||
409 | /* no need to reset it */ | ||
176 | } | 410 | } |
177 | 411 | ||
178 | static struct buflib_callbacks ops = { | 412 | /** |
179 | .move_callback = move_callback, | 413 | * resolve a queued binding with the information from the given source file |
180 | .shrink_callback = NULL, | 414 | */ |
181 | }; | 415 | static void binding_resolve(const struct file_base_info *infop) |
416 | { | ||
417 | struct dircache_runinfo_volume *dcrivolp = DCRIVOL(infop); | ||
418 | |||
419 | /* quickly check the queued list to see if it's there */ | ||
420 | struct file_base_binding *prevp = NULL; | ||
421 | FOR_EACH_BINDING(dcrivolp->queued0, p) | ||
422 | { | ||
423 | if (!binding_compare(infop, &p->info)) | ||
424 | { | ||
425 | prevp = p; | ||
426 | continue; | ||
427 | } | ||
428 | |||
429 | if (p == dcrivolp->queued0) | ||
430 | dcrivolp->queued0 = BINDING_NEXT(p); | ||
431 | else | ||
432 | { | ||
433 | file_binding_remove_next(prevp, p); | ||
434 | file_binding_insert_first(p); | ||
435 | } | ||
436 | |||
437 | dcrivolp->resolved0 = p; | ||
438 | |||
439 | /* srcinfop may be the actual one */ | ||
440 | if (&p->info != infop) | ||
441 | p->info.dcfile = infop->dcfile; | ||
442 | |||
443 | break; | ||
444 | } | ||
445 | } | ||
182 | 446 | ||
183 | #ifdef HAVE_EEPROM_SETTINGS | ||
184 | /** | 447 | /** |
185 | * Open the dircache file to save a snapshot on disk | 448 | * dissolve a resolved binding on its volume |
186 | */ | 449 | */ |
187 | static int open_dircache_file(unsigned flags, int permissions) | 450 | static void binding_dissolve(struct file_base_binding *prevp, |
451 | struct file_base_binding *bindp) | ||
188 | { | 452 | { |
189 | if (permissions != 0) | 453 | struct dircache_runinfo_volume *dcrivolp = DCRIVOL(bindp); |
190 | return open(DIRCACHE_FILE, flags, permissions); | ||
191 | 454 | ||
192 | return open(DIRCACHE_FILE, flags); | 455 | if (bindp == dcrivolp->resolved0) |
456 | { | ||
457 | struct file_base_binding *nextp = BINDING_NEXT(bindp); | ||
458 | dcrivolp->resolved0 = (nextp == dcrivolp->queued0) ? NULL : nextp; | ||
459 | } | ||
460 | |||
461 | if (dcrivolp->queued0 == NULL) | ||
462 | dcrivolp->queued0 = bindp; | ||
463 | |||
464 | file_binding_remove_next(prevp, bindp); | ||
465 | file_binding_insert_last(bindp); | ||
466 | |||
467 | dircache_dcfile_init(&bindp->info.dcfile); | ||
193 | } | 468 | } |
194 | 469 | ||
195 | /** | 470 | /** |
196 | * Remove the snapshot file | 471 | * dissolve all resolved bindings on a given volume |
197 | */ | 472 | */ |
198 | static int remove_dircache_file(void) | 473 | static void binding_dissolve_volume(struct dircache_runinfo_volume *dcrivolp) |
199 | { | 474 | { |
200 | return remove(DIRCACHE_FILE); | 475 | if (!dcrivolp->resolved0) |
476 | return; | ||
477 | |||
478 | FOR_EACH_BINDING(dcrivolp->resolved0, p) | ||
479 | { | ||
480 | if (p == dcrivolp->queued0) | ||
481 | break; | ||
482 | |||
483 | dircache_dcfile_init(&p->info.dcfile); | ||
484 | } | ||
485 | |||
486 | dcrivolp->queued0 = dcrivolp->resolved0; | ||
487 | dcrivolp->resolved0 = NULL; | ||
201 | } | 488 | } |
202 | #endif | 489 | |
203 | /** | 490 | |
204 | * Internal function to allocate a new dircache_entry from memory. | 491 | /** Dircache buffer management **/ |
492 | |||
493 | /** | ||
494 | * allocate the cache's memory block | ||
205 | */ | 495 | */ |
206 | static struct dircache_entry* allocate_entry(void) | 496 | static int alloc_cache(size_t size) |
207 | { | 497 | { |
208 | struct dircache_entry *next_entry; | 498 | /* pad with one extra-- see alloc_name() and free_name() */ |
209 | 499 | return core_alloc_ex("dircache", size + 1, &dircache_runinfo.ops); | |
210 | if (dircache_size > allocated_size - MAX_PATH*2) | 500 | } |
501 | |||
502 | /** | ||
503 | * put the allocation in dircache control | ||
504 | */ | ||
505 | static void set_buffer(int handle, size_t size) | ||
506 | { | ||
507 | void *p = core_get_data(handle); | ||
508 | |||
509 | #ifdef DIRCACHE_DUMPSTER | ||
510 | dumpster_clean_buffer(p, size); | ||
511 | #endif /* DIRCACHE_DUMPSTER */ | ||
512 | |||
513 | /* set it up as a 1-based array */ | ||
514 | dircache_runinfo.p = p - ENTRYSIZE; | ||
515 | |||
516 | if (dircache_runinfo.handle != handle) | ||
211 | { | 517 | { |
212 | logf("size limit reached"); | 518 | /* new buffer */ |
213 | return NULL; | 519 | dircache_runinfo.handle = handle; |
520 | dircache_runinfo.bufsize = size; | ||
521 | dircache.names = size + ENTRYSIZE; | ||
522 | dircache_runinfo.pname[dircache.names - 1] = 0; | ||
523 | dircache_runinfo.pname[dircache.names ] = 0; | ||
214 | } | 524 | } |
215 | 525 | } | |
216 | next_entry = &dircache_root[entry_count++]; | ||
217 | next_entry->d_name = NULL; | ||
218 | next_entry->up = NULL; | ||
219 | next_entry->down = NULL; | ||
220 | next_entry->next = NULL; | ||
221 | 526 | ||
222 | dircache_size += sizeof(struct dircache_entry); | 527 | /** |
528 | * remove the allocation from dircache control and return the handle | ||
529 | */ | ||
530 | static int reset_buffer(void) | ||
531 | { | ||
532 | int handle = dircache_runinfo.handle; | ||
533 | if (handle > 0) | ||
534 | { | ||
535 | /* don't mind .p; it might get changed by the callback even after | ||
536 | this call; buffer presence is determined by the following: */ | ||
537 | dircache_runinfo.handle = 0; | ||
538 | dircache_runinfo.bufsize = 0; | ||
539 | } | ||
540 | |||
541 | return handle; | ||
542 | } | ||
543 | |||
544 | /** | ||
545 | * return the number of bytes remaining in the buffer | ||
546 | */ | ||
547 | static size_t dircache_buf_remaining(void) | ||
548 | { | ||
549 | if (!dircache_runinfo.handle) | ||
550 | return 0; | ||
551 | |||
552 | return dircache_runinfo.bufsize - dircache.size; | ||
553 | } | ||
554 | |||
555 | /** | ||
556 | * return the amount of reserve space used | ||
557 | */ | ||
558 | static size_t reserve_buf_used(void) | ||
559 | { | ||
560 | size_t remaining = dircache_buf_remaining(); | ||
561 | return (remaining < DIRCACHE_RESERVE) ? | ||
562 | DIRCACHE_RESERVE - remaining : 0; | ||
563 | } | ||
564 | |||
565 | |||
566 | /** Internal cache structure control functions **/ | ||
567 | |||
568 | /** | ||
569 | * generate the next serial number in the sequence | ||
570 | */ | ||
571 | static dc_serial_t next_serialnum(void) | ||
572 | { | ||
573 | dc_serial_t serialnum = MAX(dircache.last_serialnum + 1, 1); | ||
574 | dircache.last_serialnum = serialnum; | ||
575 | return serialnum; | ||
576 | } | ||
577 | |||
578 | /** | ||
579 | * return the dircache volume pointer for the special index | ||
580 | */ | ||
581 | static struct dircache_volume * get_idx_dcvolp(int idx) | ||
582 | { | ||
583 | if (idx >= 0) | ||
584 | return NULL; | ||
585 | |||
586 | return &dircache.dcvol[IF_MV_VOL(-idx - 1)]; | ||
587 | } | ||
588 | |||
589 | /** | ||
590 | * return the cache entry referenced by idx (NULL if outside buffer) | ||
591 | */ | ||
592 | static struct dircache_entry * get_entry(int idx) | ||
593 | { | ||
594 | if (idx <= 0 || (unsigned int)idx > dircache.numentries) | ||
595 | return NULL; | ||
223 | 596 | ||
224 | return next_entry; | 597 | return &dircache_runinfo.pentry[idx]; |
225 | } | 598 | } |
226 | 599 | ||
227 | /** | 600 | /** |
228 | * Internal function to allocate a dircache_entry and set | 601 | * return the index of the cache entry (0 if outside buffer) |
229 | * ->next entry pointers. | ||
230 | */ | 602 | */ |
231 | static struct dircache_entry* dircache_gen_next(struct dircache_entry *ce) | 603 | static int get_index(const struct dircache_entry *ce) |
232 | { | 604 | { |
233 | struct dircache_entry *next_entry; | 605 | if (!PTR_IN_ARRAY(dircache_runinfo.pentry + 1, ce, |
606 | dircache.numentries + 1)) | ||
607 | { | ||
608 | return 0; | ||
609 | } | ||
610 | |||
611 | return ce - dircache_runinfo.pentry; | ||
612 | } | ||
234 | 613 | ||
235 | if ( (next_entry = allocate_entry()) == NULL) | 614 | /** |
615 | * return the sublist down pointer for the sublist that contains entry 'idx' | ||
616 | */ | ||
617 | static int * get_downidxp(int idx) | ||
618 | { | ||
619 | /* NOTE: 'idx' must refer to a directory or the result is undefined */ | ||
620 | if (idx == 0 || idx < -NUM_VOLUMES) | ||
236 | return NULL; | 621 | return NULL; |
237 | next_entry->up = ce->up; | 622 | |
238 | ce->next = next_entry; | 623 | if (idx > 0) |
239 | 624 | { | |
240 | return next_entry; | 625 | /* a normal entry */ |
626 | struct dircache_entry *ce = get_entry(idx); | ||
627 | return ce ? &ce->down : NULL; | ||
628 | } | ||
629 | else | ||
630 | { | ||
631 | /* a volume root */ | ||
632 | return &get_idx_dcvolp(idx)->root_down; | ||
633 | } | ||
241 | } | 634 | } |
242 | 635 | ||
243 | /* | 636 | /** |
244 | * Internal function to allocate a dircache_entry and set | 637 | * return a pointer to the index referencing the cache entry that 'idx' |
245 | * ->down entry pointers. | 638 | * references |
246 | */ | 639 | */ |
247 | static struct dircache_entry* dircache_gen_down(struct dircache_entry *ce) | 640 | static int * get_previdxp(int idx) |
248 | { | 641 | { |
249 | struct dircache_entry *next_entry; | 642 | struct dircache_entry *ce = get_entry(idx); |
250 | 643 | ||
251 | if ( (next_entry = allocate_entry()) == NULL) | 644 | int *prevp = get_downidxp(ce->up); |
645 | if (!prevp) | ||
252 | return NULL; | 646 | return NULL; |
253 | next_entry->up = ce; | 647 | |
254 | ce->down = next_entry; | 648 | while (1) |
255 | 649 | { | |
256 | return next_entry; | 650 | int next = *prevp; |
651 | if (!next || next == idx) | ||
652 | break; | ||
653 | |||
654 | prevp = &get_entry(next)->next; | ||
655 | } | ||
656 | |||
657 | return prevp; | ||
658 | } | ||
659 | |||
660 | /** | ||
661 | * if required, adjust the lists and directory read of any scan and build in | ||
662 | * progress | ||
663 | */ | ||
664 | static void sab_sync_scan(struct sab *sabp, int *prevp, int *nextp) | ||
665 | { | ||
666 | struct sab_component *abovep = NULL; | ||
667 | FOR_EACH_SAB_COMP(sabp, p) | ||
668 | { | ||
669 | if (nextp != p->prevp) | ||
670 | { | ||
671 | abovep = p; | ||
672 | continue; | ||
673 | } | ||
674 | |||
675 | /* removing an item being scanned; set the component position to the | ||
676 | entry before this */ | ||
677 | p->prevp = prevp; | ||
678 | |||
679 | if (p == sabp->top) | ||
680 | { | ||
681 | /* removed at item in the directory who's immediate contents are | ||
682 | being scanned */ | ||
683 | if (prevp == p->downp) | ||
684 | { | ||
685 | /* was first item; rewind it */ | ||
686 | dircache_rewinddir_internal(&sabp->info); | ||
687 | } | ||
688 | else | ||
689 | { | ||
690 | struct dircache_entry *ceprev = | ||
691 | container_of(prevp, struct dircache_entry, next); | ||
692 | #ifdef DIRCACHE_NATIVE | ||
693 | sabp->info.fatfile.e.entry = ceprev->direntry; | ||
694 | sabp->info.fatfile.e.entries = ceprev->direntries; | ||
695 | #endif | ||
696 | } | ||
697 | } | ||
698 | else if (abovep) | ||
699 | { | ||
700 | /* the directory being scanned or a parent of it has been removed; | ||
701 | abort its build or cache traversal */ | ||
702 | abovep->idx = 0; | ||
703 | } | ||
704 | |||
705 | break; | ||
706 | } | ||
707 | } | ||
708 | |||
709 | /** | ||
710 | * get a pointer to an allocated name given a cache index | ||
711 | */ | ||
712 | static inline unsigned char * get_name(int nameidx) | ||
713 | { | ||
714 | return &dircache_runinfo.pname[nameidx]; | ||
715 | } | ||
716 | |||
717 | /** | ||
718 | * get the cache buffer index of the given name | ||
719 | */ | ||
720 | static inline int get_nameidx(const unsigned char *pname) | ||
721 | { | ||
722 | return pname - dircache_runinfo.pname; | ||
723 | } | ||
724 | |||
725 | /** | ||
726 | * copy the entry's name to a buffer (which assumed to be of sufficient size) | ||
727 | */ | ||
728 | static void entry_name_copy(char *dst, const struct dircache_entry *ce) | ||
729 | { | ||
730 | if (LIKELY(!ce->tinyname)) | ||
731 | { | ||
732 | strmemcpy(dst, get_name(ce->name), ce->length); | ||
733 | return; | ||
734 | } | ||
735 | |||
736 | const unsigned char *src = ce->namebuf; | ||
737 | size_t len = 0; | ||
738 | while (len++ < MAX_TINYNAME && *src) | ||
739 | *dst++ = *src++; | ||
740 | |||
741 | *dst = '\0'; | ||
742 | } | ||
743 | |||
744 | /** | ||
745 | * set the namesfree hint to a new position | ||
746 | */ | ||
747 | static void set_namesfree_hint(const unsigned char *hintp) | ||
748 | { | ||
749 | int hintidx = get_nameidx(hintp); | ||
750 | |||
751 | if (hintidx >= (int)(dircache.names + dircache.sizenames)) | ||
752 | hintidx = dircache.names; | ||
753 | |||
754 | dircache.nextnamefree = hintidx; | ||
257 | } | 755 | } |
258 | 756 | ||
259 | /** | 757 | /** |
260 | * Returns true if there is an event waiting in the queue | 758 | * allocate a buffer to use for a new name |
261 | * that requires the current operation to be aborted. | ||
262 | */ | 759 | */ |
263 | static bool check_event_queue(void) | 760 | static int alloc_name(size_t size) |
761 | { | ||
762 | int nameidx = 0; | ||
763 | |||
764 | if (dircache.namesfree >= size) | ||
765 | { | ||
766 | /* scan for a free gap starting at the hint point - first fit */ | ||
767 | unsigned char *start = get_name(dircache.nextnamefree), *p = start; | ||
768 | unsigned char *namesend = get_name(dircache.names + dircache.sizenames); | ||
769 | size_t gapsize = 0; | ||
770 | |||
771 | while (gapsize < size) | ||
772 | { | ||
773 | if ((p = memchr(p, 0xff, namesend - p))) | ||
774 | { | ||
775 | /* found a sentinel; see if there are enough in a row */ | ||
776 | gapsize = 1; | ||
777 | while (*++p == 0xff && gapsize < size) | ||
778 | gapsize++; | ||
779 | } | ||
780 | else | ||
781 | { | ||
782 | if (namesend == start) | ||
783 | break; /* exhausted */ | ||
784 | |||
785 | /* wrap */ | ||
786 | namesend = start; | ||
787 | p = get_name(dircache.names); | ||
788 | |||
789 | if (p == namesend) | ||
790 | break; /* initial hint was at names start */ | ||
791 | } | ||
792 | } | ||
793 | |||
794 | if (gapsize >= size) | ||
795 | { | ||
796 | unsigned char *namep = p - gapsize; | ||
797 | nameidx = get_nameidx(namep); | ||
798 | |||
799 | if (*p == 0xff) | ||
800 | { | ||
801 | /* if only a tiny block remains after buffer, claim it too */ | ||
802 | size_t tinysize = 1; | ||
803 | while (*++p == 0xff && tinysize <= MAX_TINYNAME) | ||
804 | tinysize++; | ||
805 | |||
806 | if (tinysize <= MAX_TINYNAME) | ||
807 | { | ||
808 | /* mark with tiny block sentinel */ | ||
809 | memset(p - tinysize, 0xfe, tinysize); | ||
810 | size += tinysize; | ||
811 | } | ||
812 | } | ||
813 | |||
814 | dircache.namesfree -= size; | ||
815 | dircache.sizeused += size; | ||
816 | set_namesfree_hint(namep + size); | ||
817 | } | ||
818 | } | ||
819 | |||
820 | if (!nameidx) | ||
821 | { | ||
822 | /* no sufficiently long free gaps; allocate anew */ | ||
823 | if (dircache_buf_remaining() <= size) | ||
824 | { | ||
825 | dircache.last_size = 0; | ||
826 | return 0; | ||
827 | } | ||
828 | |||
829 | dircache.names -= size; | ||
830 | dircache.sizenames += size; | ||
831 | nameidx = dircache.names; | ||
832 | dircache.size += size; | ||
833 | dircache.sizeused += size; | ||
834 | *get_name(dircache.names - 1) = 0; | ||
835 | } | ||
836 | |||
837 | return nameidx; | ||
838 | } | ||
839 | |||
840 | /** | ||
841 | * mark a name as free and note that its bytes are available | ||
842 | */ | ||
843 | static void free_name(int nameidx, size_t size) | ||
844 | { | ||
845 | unsigned char *beg = get_name(nameidx); | ||
846 | unsigned char *end = beg + size; | ||
847 | |||
848 | /* merge with any adjacent tiny blocks */ | ||
849 | while (beg[-1] == 0xfe) | ||
850 | --beg; | ||
851 | |||
852 | while (end[1] == 0xfe) | ||
853 | ++end; | ||
854 | |||
855 | size = end - beg; | ||
856 | memset(beg, 0xff, size); | ||
857 | dircache.namesfree += size; | ||
858 | dircache.sizeused -= size; | ||
859 | set_namesfree_hint(beg); | ||
860 | } | ||
861 | |||
862 | /** | ||
863 | * allocate and assign a name to the entry | ||
864 | */ | ||
865 | static bool entry_assign_name(struct dircache_entry *ce, | ||
866 | const unsigned char *name, size_t size) | ||
867 | { | ||
868 | unsigned char *copyto; | ||
869 | |||
870 | if (size <= MAX_TINYNAME) | ||
871 | { | ||
872 | copyto = ce->namebuf; | ||
873 | |||
874 | if (size < MAX_TINYNAME) | ||
875 | copyto[size] = '\0'; | ||
876 | |||
877 | ce->tinyname = 1; | ||
878 | } | ||
879 | else | ||
880 | { | ||
881 | if (size > DC_MAX_NAME) | ||
882 | size = DC_MAX_NAME; | ||
883 | |||
884 | int nameidx = alloc_name(size); | ||
885 | if (!nameidx) | ||
886 | return false; | ||
887 | |||
888 | copyto = get_name(nameidx); | ||
889 | |||
890 | ce->tinyname = 0; | ||
891 | ce->name = nameidx; | ||
892 | ce->length = size; | ||
893 | } | ||
894 | |||
895 | memcpy(copyto, name, size); | ||
896 | return true; | ||
897 | } | ||
898 | |||
899 | /** | ||
900 | * free the name for the entry | ||
901 | */ | ||
902 | static void entry_unassign_name(struct dircache_entry *ce) | ||
903 | { | ||
904 | if (!ce->tinyname) | ||
905 | free_name(ce->name, ce->length); | ||
906 | } | ||
907 | |||
908 | /** | ||
909 | * assign a new name to the entry | ||
910 | */ | ||
911 | static bool entry_reassign_name(struct dircache_entry *ce, | ||
912 | const unsigned char *newname) | ||
913 | { | ||
914 | size_t oldlen = ce->tinyname ? 0 : ce->length; | ||
915 | size_t newlen = strlen(newname); | ||
916 | |||
917 | if (oldlen == newlen || (oldlen == 0 && newlen <= MAX_TINYNAME)) | ||
918 | { | ||
919 | char *p = mempcpy(oldlen == 0 ? ce->namebuf : get_name(ce->name), | ||
920 | newname, newlen); | ||
921 | if (newlen < MAX_TINYNAME) | ||
922 | *p = '\0'; | ||
923 | return true; | ||
924 | } | ||
925 | |||
926 | /* needs a new name allocation; if the new name fits in the freed block, | ||
927 | it will use it immediately without a lengthy search */ | ||
928 | entry_unassign_name(ce); | ||
929 | return entry_assign_name(ce, newname, newlen); | ||
930 | } | ||
931 | |||
932 | /** | ||
933 | * allocate a dircache_entry from memory using freed ones if available | ||
934 | */ | ||
935 | static int alloc_entry(struct dircache_entry **res) | ||
936 | { | ||
937 | struct dircache_entry *ce; | ||
938 | int idx = dircache.free_list; | ||
939 | |||
940 | if (idx) | ||
941 | { | ||
942 | /* reuse a freed entry */ | ||
943 | ce = get_entry(idx); | ||
944 | dircache.free_list = ce->next; | ||
945 | } | ||
946 | else if (dircache_buf_remaining() > ENTRYSIZE) | ||
947 | { | ||
948 | /* allocate a new one */ | ||
949 | idx = ++dircache.numentries; | ||
950 | dircache.size += ENTRYSIZE; | ||
951 | ce = get_entry(idx); | ||
952 | } | ||
953 | else | ||
954 | { | ||
955 | dircache.last_size = 0; | ||
956 | *res = NULL; | ||
957 | return 0; | ||
958 | } | ||
959 | |||
960 | dircache.sizeused += ENTRYSIZE; | ||
961 | |||
962 | ce->next = 0; | ||
963 | ce->up = 0; | ||
964 | ce->down = 0; | ||
965 | ce->length = 0; | ||
966 | ce->frontier = FRONTIER_SETTLED; | ||
967 | ce->serialnum = next_serialnum(); | ||
968 | |||
969 | *res = ce; | ||
970 | return idx; | ||
971 | } | ||
972 | |||
973 | /** | ||
974 | * free an entry's allocations in the cache; must not be linked to anything | ||
975 | * by this time (orphan!) | ||
976 | */ | ||
977 | static void free_orphan_entry(struct dircache_runinfo_volume *dcrivolp, | ||
978 | struct dircache_entry *ce, int idx) | ||
979 | { | ||
980 | if (dcrivolp) | ||
981 | { | ||
982 | /* was an established entry; find any associated resolved binding and | ||
983 | dissolve it; bindings are kept strictly synchronized with changes | ||
984 | to the storage so a simple serial number comparison is sufficient */ | ||
985 | struct file_base_binding *prevp = NULL; | ||
986 | FOR_EACH_BINDING(dcrivolp->resolved0, p) | ||
987 | { | ||
988 | if (p == dcrivolp->queued0) | ||
989 | break; | ||
990 | |||
991 | if (ce->serialnum == p->info.dcfile.serialnum) | ||
992 | { | ||
993 | binding_dissolve(prevp, p); | ||
994 | break; | ||
995 | } | ||
996 | |||
997 | prevp = p; | ||
998 | } | ||
999 | } | ||
1000 | |||
1001 | entry_unassign_name(ce); | ||
1002 | |||
1003 | /* no serialnum says "it's free" (for cache-wide iterators) */ | ||
1004 | ce->serialnum = 0; | ||
1005 | |||
1006 | /* add to free list */ | ||
1007 | ce->next = dircache.free_list; | ||
1008 | dircache.free_list = idx; | ||
1009 | dircache.sizeused -= ENTRYSIZE; | ||
1010 | } | ||
1011 | |||
1012 | /** | ||
1013 | * allocates a new entry of with the name specified by 'basename' | ||
1014 | */ | ||
1015 | static int create_entry(const char *basename, | ||
1016 | struct dircache_entry **res) | ||
1017 | { | ||
1018 | int idx = alloc_entry(res); | ||
1019 | if (!idx) | ||
1020 | { | ||
1021 | logf("size limit reached (entry)"); | ||
1022 | return 0; /* full */ | ||
1023 | } | ||
1024 | |||
1025 | if (!entry_assign_name(*res, basename, strlen(basename))) | ||
1026 | { | ||
1027 | free_orphan_entry(NULL, *res, idx); | ||
1028 | logf("size limit reached (name)"); | ||
1029 | return 0; /* really full! */ | ||
1030 | } | ||
1031 | |||
1032 | return idx; | ||
1033 | } | ||
1034 | |||
1035 | /** | ||
1036 | * unlink the entry at *prevp and adjust the scanner if needed | ||
1037 | */ | ||
1038 | static void remove_entry(struct dircache_runinfo_volume *dcrivolp, | ||
1039 | struct dircache_entry *ce, int *prevp) | ||
1040 | { | ||
1041 | /* unlink it from its list */ | ||
1042 | *prevp = ce->next; | ||
1043 | |||
1044 | if (dcrivolp) | ||
1045 | { | ||
1046 | /* adjust scanner iterator if needed */ | ||
1047 | struct sab *sabp = dcrivolp->sabp; | ||
1048 | if (sabp) | ||
1049 | sab_sync_scan(sabp, prevp, &ce->next); | ||
1050 | } | ||
1051 | } | ||
1052 | |||
1053 | /** | ||
1054 | * free the entire subtree in the referenced parent down index | ||
1055 | */ | ||
1056 | static void free_subentries(struct dircache_runinfo_volume *dcrivolp, int *downp) | ||
1057 | { | ||
1058 | while (1) | ||
1059 | { | ||
1060 | int idx = *downp; | ||
1061 | struct dircache_entry *ce = get_entry(idx); | ||
1062 | if (!ce) | ||
1063 | break; | ||
1064 | |||
1065 | if ((ce->attr & ATTR_DIRECTORY) && ce->down) | ||
1066 | free_subentries(dcrivolp, &ce->down); | ||
1067 | |||
1068 | remove_entry(dcrivolp, ce, downp); | ||
1069 | free_orphan_entry(dcrivolp, ce, idx); | ||
1070 | } | ||
1071 | } | ||
1072 | |||
1073 | /** | ||
1074 | * free the specified file entry and its children | ||
1075 | */ | ||
1076 | static void free_file_entry(struct file_base_info *infop) | ||
1077 | { | ||
1078 | int idx = infop->dcfile.idx; | ||
1079 | if (idx <= 0) | ||
1080 | return; /* can't remove a root/invalid */ | ||
1081 | |||
1082 | struct dircache_runinfo_volume *dcrivolp = DCRIVOL(infop); | ||
1083 | |||
1084 | struct dircache_entry *ce = get_entry(idx); | ||
1085 | if ((ce->attr & ATTR_DIRECTORY) && ce->down) | ||
1086 | { | ||
1087 | /* gonna get all this contents (normally the "." and "..") */ | ||
1088 | free_subentries(dcrivolp, &ce->down); | ||
1089 | } | ||
1090 | |||
1091 | remove_entry(dcrivolp, ce, get_previdxp(idx)); | ||
1092 | free_orphan_entry(dcrivolp, ce, idx); | ||
1093 | } | ||
1094 | |||
1095 | /** | ||
1096 | * insert the new entry into the parent, sorted into position | ||
1097 | */ | ||
1098 | static void insert_file_entry(struct file_base_info *dirinfop, | ||
1099 | struct dircache_entry *ce) | ||
1100 | { | ||
1101 | /* DIRCACHE_NATIVE: the entires are sorted into the spot it would be on | ||
1102 | * the storage medium based upon the directory entry number, in-progress | ||
1103 | * scans will catch it or miss it in just the same way they would if | ||
1104 | * directly scanning the disk. If this is behind an init scan, it gets | ||
1105 | * added anyway; if in front of it, then scanning will compare what it | ||
1106 | * finds in order to avoid adding a duplicate. | ||
1107 | * | ||
1108 | * All others: the order of entries of the host filesystem is not known so | ||
1109 | * this must be placed at the end so that a build scan won't miss it and | ||
1110 | * add a duplicate since it will be comparing any entries it finds in front | ||
1111 | * of it. | ||
1112 | */ | ||
1113 | int diridx = dirinfop->dcfile.idx; | ||
1114 | int *nextp = get_downidxp(diridx); | ||
1115 | |||
1116 | while (8675309) | ||
1117 | { | ||
1118 | struct dircache_entry *nextce = get_entry(*nextp); | ||
1119 | if (!nextce) | ||
1120 | break; | ||
1121 | |||
1122 | #ifdef DIRCACHE_NATIVE | ||
1123 | if (nextce->direntry > ce->direntry) | ||
1124 | break; | ||
1125 | |||
1126 | /* now, nothing should be equal to ours or that is a bug since it | ||
1127 | would already exist (and it shouldn't because it's just been | ||
1128 | created or moved) */ | ||
1129 | #endif /* DIRCACHE_NATIVE */ | ||
1130 | |||
1131 | nextp = &nextce->next; | ||
1132 | } | ||
1133 | |||
1134 | ce->up = diridx; | ||
1135 | ce->next = *nextp; | ||
1136 | *nextp = get_index(ce); | ||
1137 | } | ||
1138 | |||
1139 | /** | ||
1140 | * unlink the entry from its parent and return its pointer to the caller | ||
1141 | */ | ||
1142 | static struct dircache_entry * remove_file_entry(struct file_base_info *infop) | ||
1143 | { | ||
1144 | struct dircache_runinfo_volume *dcrivolp = DCRIVOL(infop); | ||
1145 | struct dircache_entry *ce = get_entry(infop->dcfile.idx); | ||
1146 | remove_entry(dcrivolp, ce, get_previdxp(infop->dcfile.idx)); | ||
1147 | return ce; | ||
1148 | } | ||
1149 | |||
1150 | /** | ||
1151 | * set the frontier indicator for the given cache index | ||
1152 | */ | ||
1153 | static void establish_frontier(int idx, uint32_t code) | ||
1154 | { | ||
1155 | if (idx < 0) | ||
1156 | { | ||
1157 | int volume = IF_MV_VOL(-idx - 1); | ||
1158 | uint32_t val = dircache.dcvol[volume].frontier; | ||
1159 | if (code & FRONTIER_RENEW) | ||
1160 | val &= ~FRONTIER_ZONED; | ||
1161 | dircache.dcvol[volume].frontier = code | (val & FRONTIER_ZONED); | ||
1162 | } | ||
1163 | else if (idx > 0) | ||
1164 | { | ||
1165 | struct dircache_entry *ce = get_entry(idx); | ||
1166 | uint32_t val = ce->frontier; | ||
1167 | if (code & FRONTIER_RENEW) | ||
1168 | val &= ~FRONTIER_ZONED; | ||
1169 | ce->frontier = code | (val & FRONTIER_ZONED); | ||
1170 | } | ||
1171 | } | ||
1172 | |||
1173 | /** | ||
1174 | * remove all messages from the queue, responding to anyone waiting | ||
1175 | */ | ||
1176 | static void clear_dircache_queue(void) | ||
264 | { | 1177 | { |
265 | struct queue_event ev; | 1178 | struct queue_event ev; |
266 | 1179 | ||
267 | if(!queue_peek(&dircache_queue, &ev)) | 1180 | while (1) |
268 | return false; | 1181 | { |
269 | 1182 | queue_wait_w_tmo(&dircache_queue, &ev, 0); | |
270 | switch (ev.id) | 1183 | if (ev.id == SYS_TIMEOUT) |
271 | { | 1184 | break; |
272 | case DIRCACHE_STOP: | 1185 | |
273 | case SYS_USB_CONNECTED: | 1186 | /* respond to any synchronous build queries; since we're already |
274 | #ifdef HAVE_HOTSWAP | 1187 | building and thusly allocated, any additional requests can be |
275 | case SYS_FS_CHANGED: | 1188 | processed async */ |
276 | #endif | 1189 | if (ev.id == DCM_BUILD) |
277 | return true; | 1190 | { |
1191 | int *rcp = (int *)ev.data; | ||
1192 | if (rcp) | ||
1193 | *rcp = 0; | ||
1194 | } | ||
278 | } | 1195 | } |
279 | |||
280 | return false; | ||
281 | } | 1196 | } |
282 | 1197 | ||
283 | #if (CONFIG_PLATFORM & PLATFORM_NATIVE) | 1198 | /** |
284 | /* scan and build static data (avoid redundancy on stack) */ | 1199 | * service dircache_queue during a scan and build |
285 | static struct | 1200 | */ |
1201 | static void process_events(void) | ||
286 | { | 1202 | { |
287 | #ifdef HAVE_MULTIVOLUME | 1203 | yield(); |
288 | int volume; | 1204 | |
289 | #endif | 1205 | /* only count externally generated commands */ |
290 | struct fat_dir *dir; | 1206 | if (!queue_peek_ex(&dircache_queue, NULL, 0, QPEEK_FILTER1(DCM_BUILD))) |
291 | struct fat_direntry *direntry; | 1207 | return; |
292 | }sab; | 1208 | |
293 | 1209 | clear_dircache_queue(); | |
294 | static int sab_process_dir(unsigned long startcluster, struct dircache_entry *ce) | 1210 | |
295 | { | 1211 | /* this reminds us to keep moving after we're done here; a volume we passed |
296 | /* normally, opendir expects a full fat_dir as parent but in our case, | 1212 | up earlier could have been mounted and need refreshing; it just condenses |
297 | * it's completely useless because we don't modify anything | 1213 | a slew of requests into one and isn't mistaken for an externally generated |
298 | * WARNING: this heavily relies on current FAT implementation ! */ | 1214 | command */ |
299 | 1215 | queue_post(&dircache_queue, DCM_PROCEED, 0); | |
300 | /* those field are necessary to update the FAT entry in case of modification | 1216 | } |
301 | here we don't touch anything so we put dummy values */ | 1217 | |
302 | sab.dir->entry = 0; | 1218 | #if defined (DIRCACHE_NATIVE) |
303 | sab.dir->entrycount = 0; | 1219 | /** |
304 | sab.dir->file.firstcluster = 0; | 1220 | * scan and build the contents of a subdirectory |
305 | /* open directory */ | 1221 | */ |
306 | int rc = fat_opendir(IF_MV(sab.volume,) sab.dir, startcluster, sab.dir); | 1222 | static void sab_process_sub(struct sab *sabp) |
307 | if(rc < 0) | 1223 | { |
308 | { | 1224 | struct fat_direntry *const fatentp = get_dir_fatent(); |
309 | logf("fat_opendir failed: %d", rc); | 1225 | struct filestr_base *const streamp = &sabp->stream; |
310 | return rc; | 1226 | struct file_base_info *const infop = &sabp->info; |
1227 | |||
1228 | int idx = infop->dcfile.idx; | ||
1229 | int *downp = get_downidxp(idx); | ||
1230 | if (!downp) | ||
1231 | return; | ||
1232 | |||
1233 | while (1) | ||
1234 | { | ||
1235 | struct sab_component *compp = --sabp->top; | ||
1236 | compp->idx = idx; | ||
1237 | compp->downp = downp; | ||
1238 | compp->prevp = downp; | ||
1239 | |||
1240 | /* open directory stream */ | ||
1241 | filestr_base_init(streamp); | ||
1242 | fileobj_fileop_open(streamp, infop, FO_DIRECTORY); | ||
1243 | fat_rewind(&streamp->fatstr); | ||
1244 | uncached_rewinddir_internal(infop); | ||
1245 | |||
1246 | const long dircluster = streamp->infop->fatfile.firstcluster; | ||
1247 | |||
1248 | /* first pass: read directory */ | ||
1249 | while (1) | ||
1250 | { | ||
1251 | if (sabp->stack + 1 < sabp->stackend) | ||
1252 | { | ||
1253 | /* release control and process queued events */ | ||
1254 | dircache_unlock(); | ||
1255 | process_events(); | ||
1256 | dircache_lock(); | ||
1257 | |||
1258 | if (sabp->quit || !compp->idx) | ||
1259 | break; | ||
1260 | } | ||
1261 | /* else an immediate-contents directory scan */ | ||
1262 | |||
1263 | int rc = uncached_readdir_internal(streamp, infop, fatentp); | ||
1264 | if (rc <= 0) | ||
1265 | { | ||
1266 | if (rc < 0) | ||
1267 | sabp->quit = true; | ||
1268 | else | ||
1269 | compp->prevp = downp; /* rewind list */ | ||
1270 | |||
1271 | break; | ||
1272 | } | ||
1273 | |||
1274 | struct dircache_entry *ce; | ||
1275 | int prev = *compp->prevp; | ||
1276 | |||
1277 | if (prev) | ||
1278 | { | ||
1279 | /* there are entries ahead of us; they will be what was just | ||
1280 | read or something to be subsequently read; if it belongs | ||
1281 | ahead of this one, insert a new entry before it; if it's | ||
1282 | the entry just scanned, do nothing further and continue | ||
1283 | with the next */ | ||
1284 | ce = get_entry(prev); | ||
1285 | if (ce->direntry == infop->fatfile.e.entry) | ||
1286 | { | ||
1287 | compp->prevp = &ce->next; | ||
1288 | continue; /* already there */ | ||
1289 | } | ||
1290 | } | ||
1291 | |||
1292 | int idx = create_entry(fatentp->name, &ce); | ||
1293 | if (!idx) | ||
1294 | { | ||
1295 | sabp->quit = true; | ||
1296 | break; | ||
1297 | } | ||
1298 | |||
1299 | /* link it in */ | ||
1300 | ce->up = compp->idx; | ||
1301 | ce->next = prev; | ||
1302 | *compp->prevp = idx; | ||
1303 | compp->prevp = &ce->next; | ||
1304 | |||
1305 | if (!(fatentp->attr & ATTR_DIRECTORY)) | ||
1306 | ce->filesize = fatentp->filesize; | ||
1307 | else if (!is_dotdir_name(fatentp->name)) | ||
1308 | ce->frontier = FRONTIER_NEW; /* this needs scanning */ | ||
1309 | |||
1310 | /* copy remaining FS info */ | ||
1311 | ce->direntry = infop->fatfile.e.entry; | ||
1312 | ce->direntries = infop->fatfile.e.entries; | ||
1313 | ce->attr = fatentp->attr; | ||
1314 | ce->firstcluster = fatentp->firstcluster; | ||
1315 | ce->wrtdate = fatentp->wrtdate; | ||
1316 | ce->wrttime = fatentp->wrttime; | ||
1317 | |||
1318 | /* resolve queued user bindings */ | ||
1319 | infop->fatfile.firstcluster = fatentp->firstcluster; | ||
1320 | infop->fatfile.dircluster = dircluster; | ||
1321 | infop->dcfile.idx = idx; | ||
1322 | infop->dcfile.serialnum = ce->serialnum; | ||
1323 | binding_resolve(infop); | ||
1324 | } /* end while */ | ||
1325 | |||
1326 | close_stream_internal(streamp); | ||
1327 | |||
1328 | if (sabp->quit) | ||
1329 | return; | ||
1330 | |||
1331 | establish_frontier(compp->idx, FRONTIER_SETTLED); | ||
1332 | |||
1333 | /* second pass: "recurse!" */ | ||
1334 | struct dircache_entry *ce = NULL; | ||
1335 | |||
1336 | while (1) | ||
1337 | { | ||
1338 | idx = compp->idx && compp > sabp->stack ? *compp->prevp : 0; | ||
1339 | if (idx) | ||
1340 | { | ||
1341 | ce = get_entry(idx); | ||
1342 | compp->prevp = &ce->next; | ||
1343 | |||
1344 | if (ce->frontier != FRONTIER_SETTLED) | ||
1345 | break; | ||
1346 | } | ||
1347 | else | ||
1348 | { | ||
1349 | /* directory completed or removed/deepest level */ | ||
1350 | compp = ++sabp->top; | ||
1351 | if (compp >= sabp->stackend) | ||
1352 | return; /* scan completed/initial directory removed */ | ||
1353 | } | ||
1354 | } | ||
1355 | |||
1356 | /* even if it got zoned from outside it is about to be scanned in | ||
1357 | its entirety and may be considered new again */ | ||
1358 | ce->frontier = FRONTIER_NEW; | ||
1359 | downp = &ce->down; | ||
1360 | |||
1361 | /* set up info for next open | ||
1362 | * IF_MV: "volume" was set when scan began */ | ||
1363 | infop->fatfile.firstcluster = ce->firstcluster; | ||
1364 | infop->fatfile.dircluster = dircluster; | ||
1365 | infop->fatfile.e.entry = ce->direntry; | ||
1366 | infop->fatfile.e.entries = ce->direntries; | ||
1367 | } /* end while */ | ||
1368 | } | ||
1369 | |||
1370 | /** | ||
1371 | * scan and build the contents of a directory or volume root | ||
1372 | */ | ||
1373 | static void sab_process_dir(struct file_base_info *infop, bool issab) | ||
1374 | { | ||
1375 | /* infop should have been fully opened meaning that all its parent | ||
1376 | directory information is filled in and intact; the binding information | ||
1377 | should also filled in beforehand */ | ||
1378 | |||
1379 | /* allocate the stack right now to the max demand */ | ||
1380 | struct dirsab | ||
1381 | { | ||
1382 | struct sab sab; | ||
1383 | struct sab_component stack[issab ? DIRCACHE_MAX_DEPTH : 1]; | ||
1384 | } dirsab; | ||
1385 | struct sab *sabp = &dirsab.sab; | ||
1386 | |||
1387 | sabp->quit = false; | ||
1388 | sabp->stackend = &sabp->stack[ARRAYLEN(dirsab.stack)]; | ||
1389 | sabp->top = sabp->stackend; | ||
1390 | sabp->info = *infop; | ||
1391 | |||
1392 | if (issab) | ||
1393 | DCRIVOL(infop)->sabp = sabp; | ||
1394 | |||
1395 | establish_frontier(infop->dcfile.idx, FRONTIER_NEW | FRONTIER_RENEW); | ||
1396 | sab_process_sub(sabp); | ||
1397 | |||
1398 | if (issab) | ||
1399 | DCRIVOL(infop)->sabp = NULL; | ||
1400 | } | ||
1401 | |||
1402 | /** | ||
1403 | * scan and build the entire tree for a volume | ||
1404 | */ | ||
1405 | static void sab_process_volume(struct dircache_volume *dcvolp) | ||
1406 | { | ||
1407 | int rc; | ||
1408 | |||
1409 | int volume = IF_MV_VOL(dcvolp - dircache.dcvol); | ||
1410 | int idx = -volume - 1; | ||
1411 | |||
1412 | logf("dircache - building volume %d", volume); | ||
1413 | |||
1414 | /* gather everything sab_process_dir() needs in order to begin a scan */ | ||
1415 | struct file_base_info info; | ||
1416 | rc = fat_open_rootdir(IF_MV(volume,) &info.fatfile); | ||
1417 | if (rc < 0) | ||
1418 | { | ||
1419 | /* probably not mounted */ | ||
1420 | logf("SAB - no root %d: %d", volume, rc); | ||
1421 | establish_frontier(idx, FRONTIER_NEW); | ||
1422 | return; | ||
311 | } | 1423 | } |
312 | 1424 | ||
313 | /* first pass : read dir */ | 1425 | info.dcfile.idx = idx; |
314 | struct dircache_entry *first_ce = ce; | 1426 | info.dcfile.serialnum = dcvolp->serialnum; |
315 | 1427 | sab_process_dir(&info, true); | |
316 | /* read through directory */ | 1428 | } |
317 | while((rc = fat_getnext(sab.dir, sab.direntry)) >= 0 && sab.direntry->name[0]) | 1429 | |
1430 | /** | ||
1431 | * this function is the back end to the public API's like readdir() | ||
1432 | */ | ||
1433 | int dircache_readdir_dirent(struct filestr_base *stream, | ||
1434 | struct dirscan_info *scanp, | ||
1435 | struct dirent *entry) | ||
1436 | { | ||
1437 | struct file_base_info *dirinfop = stream->infop; | ||
1438 | |||
1439 | if (!dirinfop->dcfile.serialnum) | ||
1440 | goto read_uncached; /* no parent cached => no entries cached */ | ||
1441 | |||
1442 | struct dircache_volume *dcvolp = DCVOL(dirinfop); | ||
1443 | |||
1444 | int diridx = dirinfop->dcfile.idx; | ||
1445 | unsigned int frontier = diridx <= 0 ? dcvolp->frontier : | ||
1446 | get_entry(diridx)->frontier; | ||
1447 | |||
1448 | /* if not settled, just readthrough; no binding information is needed for | ||
1449 | this; if it becomes settled, we'll get scan->dcfile caught up and do | ||
1450 | subsequent reads with the cache */ | ||
1451 | if (frontier != FRONTIER_SETTLED) | ||
1452 | goto read_uncached; | ||
1453 | |||
1454 | int idx = scanp->dcscan.idx; | ||
1455 | struct dircache_entry *ce; | ||
1456 | |||
1457 | unsigned int direntry = scanp->fatscan.entry; | ||
1458 | while (1) | ||
318 | { | 1459 | { |
319 | if(!strcmp(".", sab.direntry->name) || | 1460 | if (idx == 0 || direntry == FAT_RW_VAL) /* rewound? */ |
320 | !strcmp("..", sab.direntry->name)) | 1461 | { |
321 | continue; | 1462 | idx = diridx <= 0 ? dcvolp->root_down : get_entry(diridx)->down; |
1463 | break; | ||
1464 | } | ||
322 | 1465 | ||
323 | size_t size = strlen(sab.direntry->name) + 1; | 1466 | /* validate last entry scanned; it might have been replaced between |
324 | ce->d_name = (d_names_start -= size); | 1467 | calls or not there at all any more; if so, start the cache reader |
325 | ce->startcluster = sab.direntry->firstcluster; | 1468 | at the beginning and fast-forward to the correct point as indicated |
326 | ce->info.size = sab.direntry->filesize; | 1469 | by the FS scanner */ |
327 | ce->info.attribute = sab.direntry->attr; | 1470 | ce = get_entry(idx); |
328 | ce->info.wrtdate = sab.direntry->wrtdate; | 1471 | if (ce && ce->serialnum == scanp->dcscan.serialnum) |
329 | ce->info.wrttime = sab.direntry->wrttime; | 1472 | { |
1473 | idx = ce->next; | ||
1474 | break; | ||
1475 | } | ||
330 | 1476 | ||
331 | strcpy(ce->d_name, sab.direntry->name); | 1477 | idx = 0; |
332 | dircache_size += size; | 1478 | } |
333 | 1479 | ||
334 | if(ce->info.attribute & FAT_ATTR_DIRECTORY) | 1480 | while (1) |
335 | dircache_gen_down(ce); | 1481 | { |
336 | 1482 | ce = get_entry(idx); | |
337 | ce = dircache_gen_next(ce); | 1483 | if (!ce) |
338 | if(ce == NULL) | ||
339 | return -5; | ||
340 | |||
341 | /* When simulator is used, it's only safe to yield here. */ | ||
342 | if(thread_enabled) | ||
343 | { | 1484 | { |
344 | /* Stop if we got an external signal. */ | 1485 | empty_dirent(entry); |
345 | if(check_event_queue()) | 1486 | scanp->fatscan.entries = 0; |
346 | return -6; | 1487 | return 0; /* end of dir */ |
347 | yield(); | ||
348 | } | 1488 | } |
1489 | |||
1490 | if (ce->direntry > direntry || direntry == FAT_RW_VAL) | ||
1491 | break; /* cache reader is caught up to FS scan */ | ||
1492 | |||
1493 | idx = ce->next; | ||
349 | } | 1494 | } |
350 | 1495 | ||
351 | /* add "." and ".." */ | 1496 | /* basic dirent information */ |
352 | ce->d_name = dot; | 1497 | entry_name_copy(entry->d_name, ce); |
353 | ce->info.attribute = FAT_ATTR_DIRECTORY; | 1498 | entry->info.attr = ce->attr; |
354 | ce->startcluster = startcluster; | 1499 | entry->info.size = (ce->attr & ATTR_DIRECTORY) ? 0 : ce->filesize; |
355 | ce->info.size = 0; | 1500 | entry->info.wrtdate = ce->wrtdate; |
356 | ce->down = first_ce; | 1501 | entry->info.wrttime = ce->wrttime; |
357 | 1502 | ||
358 | ce = dircache_gen_next(ce); | 1503 | /* FS scan information */ |
359 | 1504 | scanp->fatscan.entry = ce->direntry; | |
360 | ce->d_name = dotdot; | 1505 | scanp->fatscan.entries = ce->direntries; |
361 | ce->info.attribute = FAT_ATTR_DIRECTORY; | 1506 | |
362 | ce->startcluster = (first_ce->up ? first_ce->up->startcluster : 0); | 1507 | /* dircache scan information */ |
363 | ce->info.size = 0; | 1508 | scanp->dcscan.idx = idx; |
364 | ce->down = first_ce->up; | 1509 | scanp->dcscan.serialnum = ce->serialnum; |
365 | 1510 | ||
366 | /* second pass: recurse ! */ | 1511 | /* return whether this needs decoding */ |
367 | ce = first_ce; | 1512 | int rc = ce->direntries == 1 ? 2 : 1; |
368 | 1513 | ||
369 | while(rc >= 0 && ce) | 1514 | yield(); |
370 | { | ||
371 | if(ce->d_name != NULL && ce->down != NULL && strcmp(ce->d_name, ".") | ||
372 | && strcmp(ce->d_name, "..")) | ||
373 | rc = sab_process_dir(ce->startcluster, ce->down); | ||
374 | |||
375 | ce = ce->next; | ||
376 | } | ||
377 | |||
378 | return rc; | 1515 | return rc; |
1516 | |||
1517 | read_uncached: | ||
1518 | dircache_dcfile_init(&scanp->dcscan); | ||
1519 | return uncached_readdir_dirent(stream, scanp, entry); | ||
379 | } | 1520 | } |
380 | 1521 | ||
381 | /* used during the generation */ | 1522 | /** |
382 | static struct fat_dir sab_fat_dir; | 1523 | * rewind the directory scan cursor |
1524 | */ | ||
1525 | void dircache_rewinddir_dirent(struct dirscan_info *scanp) | ||
1526 | { | ||
1527 | uncached_rewinddir_dirent(scanp); | ||
1528 | dircache_dcfile_init(&scanp->dcscan); | ||
1529 | } | ||
383 | 1530 | ||
384 | static int dircache_scan_and_build(IF_MV(int volume,) struct dircache_entry *ce) | 1531 | /** |
1532 | * this function is the back end to file API internal scanning, which requires | ||
1533 | * much more detail about the directory entries; this is allowed to make | ||
1534 | * assumptions about cache state because the cache will not be altered during | ||
1535 | * the scan process; an additional important property of internal scanning is | ||
1536 | * that any available binding information is not ignored even when a scan | ||
1537 | * directory is frontier zoned. | ||
1538 | */ | ||
1539 | int dircache_readdir_internal(struct filestr_base *stream, | ||
1540 | struct file_base_info *infop, | ||
1541 | struct fat_direntry *fatent) | ||
385 | { | 1542 | { |
386 | memset(ce, 0, sizeof(struct dircache_entry)); | 1543 | /* call with writer exclusion */ |
1544 | struct file_base_info *dirinfop = stream->infop; | ||
1545 | struct dircache_volume *dcvolp = DCVOL(dirinfop); | ||
387 | 1546 | ||
388 | #ifdef HAVE_MULTIVOLUME | 1547 | /* assume binding "not found" */ |
389 | if (volume > 0) | 1548 | infop->dcfile.serialnum = 0; |
390 | { | 1549 | |
391 | /* broken for 100+ volumes because the format string is too small | 1550 | /* is parent cached? if not, readthrough because nothing is here yet */ |
392 | * and we use that for size calculation */ | 1551 | if (!dirinfop->dcfile.serialnum) |
393 | const size_t max_len = VOL_ENUM_POS + 3; | 1552 | return uncached_readdir_internal(stream, infop, fatent); |
394 | ce->d_name = (d_names_start -= max_len); | 1553 | |
395 | snprintf(ce->d_name, max_len, VOL_NAMES, volume); | 1554 | int diridx = dirinfop->dcfile.idx; |
396 | dircache_size += max_len; | 1555 | unsigned int frontier = diridx < 0 ? |
397 | ce->info.attribute = FAT_ATTR_DIRECTORY | FAT_ATTR_VOLUME; | 1556 | dcvolp->frontier : get_entry(diridx)->frontier; |
398 | ce->info.size = 0; | 1557 | |
399 | append_position = dircache_gen_next(ce); | 1558 | int idx = infop->dcfile.idx; |
400 | ce = dircache_gen_down(ce); | 1559 | if (idx == 0) /* rewound? */ |
1560 | idx = diridx <= 0 ? dcvolp->root_down : get_entry(diridx)->down; | ||
1561 | else | ||
1562 | idx = get_entry(idx)->next; | ||
1563 | |||
1564 | struct dircache_entry *ce = get_entry(idx); | ||
1565 | if (frontier != FRONTIER_SETTLED) | ||
1566 | { | ||
1567 | /* the directory being read is reported to be incompletely cached; | ||
1568 | readthrough and if the entry exists, return it with its binding | ||
1569 | information; otherwise return the uncached read result while | ||
1570 | maintaining the last index */ | ||
1571 | int rc = uncached_readdir_internal(stream, infop, fatent); | ||
1572 | if (rc <= 0 || !ce || ce->direntry > infop->fatfile.e.entry) | ||
1573 | return rc; | ||
1574 | |||
1575 | /* entry matches next one to read */ | ||
1576 | } | ||
1577 | else if (!ce) | ||
1578 | { | ||
1579 | /* end of dir */ | ||
1580 | fat_empty_fat_direntry(fatent); | ||
1581 | infop->fatfile.e.entries = 0; | ||
1582 | return 0; | ||
401 | } | 1583 | } |
402 | #endif | ||
403 | 1584 | ||
404 | struct fat_direntry direntry; /* ditto */ | 1585 | /* FS entry information that we maintain */ |
405 | #ifdef HAVE_MULTIVOLUME | 1586 | entry_name_copy(fatent->name, ce); |
406 | sab.volume = volume; | 1587 | fatent->shortname[0] = '\0'; |
407 | #endif | 1588 | fatent->attr = ce->attr; |
408 | sab.dir = &sab_fat_dir; | 1589 | /* file code file scanning does not need time information */ |
409 | sab.direntry = &direntry; | 1590 | fatent->filesize = (ce->attr & ATTR_DIRECTORY) ? 0 : ce->filesize; |
410 | 1591 | fatent->firstcluster = ce->firstcluster; | |
411 | return sab_process_dir(0, ce); | 1592 | |
1593 | /* FS entry directory information */ | ||
1594 | infop->fatfile.e.entry = ce->direntry; | ||
1595 | infop->fatfile.e.entries = ce->direntries; | ||
1596 | |||
1597 | /* dircache file binding information */ | ||
1598 | infop->dcfile.idx = idx; | ||
1599 | infop->dcfile.serialnum = ce->serialnum; | ||
1600 | |||
1601 | /* return whether this needs decoding */ | ||
1602 | int rc = ce->direntries == 1 ? 2 : 1; | ||
1603 | |||
1604 | if (frontier == FRONTIER_SETTLED) | ||
1605 | { | ||
1606 | static long next_yield; | ||
1607 | if (TIME_AFTER(current_tick, next_yield)) | ||
1608 | { | ||
1609 | yield(); | ||
1610 | next_yield = current_tick + HZ/50; | ||
1611 | } | ||
1612 | } | ||
1613 | |||
1614 | return rc; | ||
412 | } | 1615 | } |
413 | #elif (CONFIG_PLATFORM & PLATFORM_HOSTED) /* PLATFORM_HOSTED */ | 1616 | |
1617 | /** | ||
1618 | * rewind the scan position for an internal scan | ||
1619 | */ | ||
1620 | void dircache_rewinddir_internal(struct file_base_info *infop) | ||
1621 | { | ||
1622 | uncached_rewinddir_internal(infop); | ||
1623 | dircache_dcfile_init(&infop->dcfile); | ||
1624 | } | ||
1625 | |||
1626 | #else /* !DIRCACHE_NATIVE (for all others) */ | ||
1627 | |||
1628 | ##################### | ||
1629 | /* we require access to the host functions */ | ||
1630 | #undef opendir | ||
1631 | #undef readdir | ||
1632 | #undef closedir | ||
1633 | #undef rewinddir | ||
1634 | |||
414 | static char sab_path[MAX_PATH]; | 1635 | static char sab_path[MAX_PATH]; |
415 | 1636 | ||
416 | static int sab_process_dir(struct dircache_entry *ce) | 1637 | static int sab_process_dir(struct dircache_entry *ce) |
417 | { | 1638 | { |
418 | struct dirent_uncached *entry; | 1639 | struct dirent_uncached *entry; |
419 | struct dircache_entry *first_ce = ce; | 1640 | struct dircache_entry *first_ce = ce; |
420 | DIR_UNCACHED *dir = opendir_uncached(sab_path); | 1641 | DIR *dir = opendir(sab_path); |
421 | if(dir == NULL) | 1642 | if(dir == NULL) |
422 | { | 1643 | { |
423 | logf("Failed to opendir_uncached(%s)", sab_path); | 1644 | logf("Failed to opendir_uncached(%s)", sab_path); |
424 | return -1; | 1645 | return -1; |
425 | } | 1646 | } |
426 | 1647 | ||
427 | while((entry = readdir_uncached(dir))) | 1648 | while (1) |
428 | { | 1649 | { |
429 | if(!strcmp(".", entry->d_name) || | 1650 | if (!(entry = readdir(dir))) |
430 | !strcmp("..", entry->d_name)) | 1651 | break; |
1652 | |||
1653 | if (IS_DOTDIR_NAME(entry->d_name)) | ||
1654 | { | ||
1655 | /* add "." and ".." */ | ||
1656 | ce->info.attribute = ATTR_DIRECTORY; | ||
1657 | ce->info.size = 0; | ||
1658 | ce->down = entry->d_name[1] == '\0' ? first_ce : first_ce->up; | ||
1659 | strcpy(ce->dot_d_name, entry->d_name); | ||
431 | continue; | 1660 | continue; |
1661 | } | ||
432 | 1662 | ||
433 | size_t size = strlen(entry->d_name) + 1; | 1663 | size_t size = strlen(entry->d_name) + 1; |
434 | ce->d_name = (d_names_start -= size); | 1664 | ce->d_name = (d_names_start -= size); |
@@ -436,10 +1666,10 @@ static int sab_process_dir(struct dircache_entry *ce) | |||
436 | 1666 | ||
437 | strcpy(ce->d_name, entry->d_name); | 1667 | strcpy(ce->d_name, entry->d_name); |
438 | dircache_size += size; | 1668 | dircache_size += size; |
439 | 1669 | ||
440 | if(entry->info.attribute & ATTR_DIRECTORY) | 1670 | if(entry->info.attribute & ATTR_DIRECTORY) |
441 | { | 1671 | { |
442 | dircache_gen_down(ce); | 1672 | dircache_gen_down(ce, ce); |
443 | if(ce->down == NULL) | 1673 | if(ce->down == NULL) |
444 | { | 1674 | { |
445 | closedir_uncached(dir); | 1675 | closedir_uncached(dir); |
@@ -450,1189 +1680,1504 @@ static int sab_process_dir(struct dircache_entry *ce) | |||
450 | /* append entry */ | 1680 | /* append entry */ |
451 | strlcpy(&sab_path[pathpos], "/", sizeof(sab_path) - pathpos); | 1681 | strlcpy(&sab_path[pathpos], "/", sizeof(sab_path) - pathpos); |
452 | strlcpy(&sab_path[pathpos+1], entry->d_name, sizeof(sab_path) - pathpos - 1); | 1682 | strlcpy(&sab_path[pathpos+1], entry->d_name, sizeof(sab_path) - pathpos - 1); |
453 | 1683 | ||
454 | int rc = sab_process_dir(ce->down); | 1684 | int rc = sab_process_dir(ce->down); |
455 | /* restore path */ | 1685 | /* restore path */ |
456 | sab_path[pathpos] = '\0'; | 1686 | sab_path[pathpos] = '\0'; |
457 | 1687 | ||
458 | if(rc < 0) | 1688 | if(rc < 0) |
459 | { | 1689 | { |
460 | closedir_uncached(dir); | 1690 | closedir_uncached(dir); |
461 | return rc; | 1691 | return rc; |
462 | } | 1692 | } |
463 | } | 1693 | } |
464 | 1694 | ||
465 | ce = dircache_gen_next(ce); | 1695 | ce = dircache_gen_entry(ce); |
466 | if(ce == NULL) | 1696 | if (ce == NULL) |
467 | return -5; | 1697 | return -5; |
468 | 1698 | ||
469 | /* When simulator is used, it's only safe to yield here. */ | 1699 | yield(); |
470 | if(thread_enabled) | ||
471 | { | ||
472 | /* Stop if we got an external signal. */ | ||
473 | if(check_event_queue()) | ||
474 | return -1; | ||
475 | yield(); | ||
476 | } | ||
477 | } | 1700 | } |
478 | 1701 | ||
479 | /* add "." and ".." */ | ||
480 | ce->d_name = dot; | ||
481 | ce->info.attribute = ATTR_DIRECTORY; | ||
482 | ce->info.size = 0; | ||
483 | ce->down = first_ce; | ||
484 | |||
485 | ce = dircache_gen_next(ce); | ||
486 | |||
487 | ce->d_name = dotdot; | ||
488 | ce->info.attribute = ATTR_DIRECTORY; | ||
489 | ce->info.size = 0; | ||
490 | ce->down = first_ce->up; | ||
491 | |||
492 | closedir_uncached(dir); | 1702 | closedir_uncached(dir); |
493 | return 0; | 1703 | return 1; |
494 | } | 1704 | } |
495 | 1705 | ||
496 | static int dircache_scan_and_build(IF_MV(int volume,) struct dircache_entry *ce) | 1706 | static int sab_process_volume(IF_MV(int volume,) struct dircache_entry *ce) |
497 | { | 1707 | { |
498 | #ifdef HAVE_MULTIVOLUME | ||
499 | (void) volume; | ||
500 | #endif | ||
501 | memset(ce, 0, sizeof(struct dircache_entry)); | 1708 | memset(ce, 0, sizeof(struct dircache_entry)); |
502 | |||
503 | strlcpy(sab_path, "/", sizeof sab_path); | 1709 | strlcpy(sab_path, "/", sizeof sab_path); |
504 | return sab_process_dir(ce); | 1710 | return sab_process_dir(ce); |
505 | } | 1711 | } |
506 | #endif /* PLATFORM_NATIVE */ | ||
507 | 1712 | ||
508 | /** | 1713 | int dircache_readdir_r(struct dircache_dirscan *dir, struct dirent *result) |
509 | * Internal function to get a pointer to dircache_entry for a given filename. | 1714 | { |
510 | * path: Absolute path to a file or directory (see comment) | 1715 | if (dircache_state != DIRCACHE_READY) |
511 | * go_down: Returns the first entry of the directory given by the path (see comment) | 1716 | return readdir_r(dir->###########3, result, &result); |
512 | * | ||
513 | * As a a special case, accept path="" as an alias for "/". | ||
514 | * Also if the path omits the first '/', it will be accepted. | ||
515 | * | ||
516 | * * If get_down=true: | ||
517 | * If path="/", the returned entry is the first of root directory (ie dircache_root) | ||
518 | * Otherwise, if 'entry' is the returned value when get_down=false, | ||
519 | * the functions returns entry->down (which can be NULL) | ||
520 | * | ||
521 | * * If get_down=false: | ||
522 | * If path="/chunk_1/chunk_2/.../chunk_n" then this functions returns the entry | ||
523 | * root_entry()->chunk_1->chunk_2->...->chunk_(n-1) | ||
524 | * Which means that | ||
525 | * dircache_get_entry(path)->d_name == chunk_n | ||
526 | * | ||
527 | * If path="/", the returned entry is NULL. | ||
528 | * If the entry doesn't exist, return NULL | ||
529 | * | ||
530 | * NOTE: this functions silently handles double '/' | ||
531 | */ | ||
532 | static struct dircache_entry* dircache_get_entry(const char *path, bool go_down) | ||
533 | { | ||
534 | char namecopy[MAX_PATH]; | ||
535 | char* part; | ||
536 | char* end; | ||
537 | |||
538 | bool at_root = true; | ||
539 | struct dircache_entry *cache_entry = dircache_root; | ||
540 | |||
541 | strlcpy(namecopy, path, sizeof(namecopy)); | ||
542 | |||
543 | for(part = strtok_r(namecopy, "/", &end); part; part = strtok_r(NULL, "/", &end)) | ||
544 | { | ||
545 | /* If request another chunk, the current entry has to be directory | ||
546 | * and so cache_entry->down has to be non-NULL/ | ||
547 | * Special case of root because it's already the first entry of the root directory | ||
548 | * | ||
549 | * NOTE: this is safe even if cache_entry->down is NULL */ | ||
550 | if(!at_root) | ||
551 | cache_entry = cache_entry->down; | ||
552 | else | ||
553 | at_root = false; | ||
554 | |||
555 | /* scan dir for name */ | ||
556 | while(cache_entry != NULL) | ||
557 | { | ||
558 | /* skip unused entries */ | ||
559 | if(cache_entry->d_name == NULL) | ||
560 | { | ||
561 | cache_entry = cache_entry->next; | ||
562 | continue; | ||
563 | } | ||
564 | /* compare names */ | ||
565 | if(!strcasecmp(part, cache_entry->d_name)) | ||
566 | break; | ||
567 | /* go to next entry */ | ||
568 | cache_entry = cache_entry->next; | ||
569 | } | ||
570 | |||
571 | /* handle not found case */ | ||
572 | if(cache_entry == NULL) | ||
573 | return NULL; | ||
574 | } | ||
575 | 1717 | ||
576 | /* NOTE: here cache_entry!=NULL so taking ->down is safe */ | 1718 | bool first = dir->dcinfo.scanidx == REWIND_INDEX; |
577 | if(go_down) | 1719 | struct dircache_entry *ce = get_entry(first ? dir->dcinfo.index : |
578 | return at_root ? cache_entry : cache_entry->down; | 1720 | dir->dcinfo.scanidx); |
579 | else | ||
580 | return at_root ? NULL : cache_entry; | ||
581 | } | ||
582 | 1721 | ||
583 | #ifdef HAVE_EEPROM_SETTINGS | 1722 | ce = first ? ce->down : ce->next; |
584 | 1723 | ||
585 | #define DIRCACHE_MAGIC 0x00d0c0a1 | 1724 | if (ce == NULL) |
586 | struct dircache_maindata { | 1725 | return 0; |
587 | long magic; | 1726 | |
588 | long size; | 1727 | dir->scanidx = ce - dircache_root; |
589 | long entry_count; | 1728 | |
590 | long appflags; | 1729 | strlcpy(result->d_name, ce->d_name, sizeof (result->d_name)); |
591 | struct dircache_entry *root_entry; | 1730 | result->info = ce->dirinfo; |
592 | char *d_names_start; | 1731 | |
593 | }; | 1732 | return 1; |
1733 | } | ||
1734 | |||
1735 | #endif /* DIRCACHE_* */ | ||
594 | 1736 | ||
595 | /** | 1737 | /** |
596 | * Function to load the internal cache structure from disk to initialize | 1738 | * reset the cache for the specified volume |
597 | * the dircache really fast and little disk access. | ||
598 | */ | 1739 | */ |
599 | int dircache_load(void) | 1740 | static void reset_volume(IF_MV_NONVOID(int volume)) |
600 | { | 1741 | { |
601 | struct dircache_maindata maindata; | 1742 | FOR_EACH_VOLUME(volume, i) |
602 | ssize_t bytes_read; | ||
603 | int fd; | ||
604 | |||
605 | if (dircache_initialized) | ||
606 | return -1; | ||
607 | |||
608 | logf("Loading directory cache"); | ||
609 | dircache_size = 0; | ||
610 | |||
611 | fd = open_dircache_file(O_RDONLY, 0); | ||
612 | if (fd < 0) | ||
613 | return -2; | ||
614 | |||
615 | bytes_read = read(fd, &maindata, sizeof(struct dircache_maindata)); | ||
616 | if (bytes_read != sizeof(struct dircache_maindata) | ||
617 | || maindata.magic != DIRCACHE_MAGIC || maindata.size <= 0) | ||
618 | { | 1743 | { |
619 | logf("Dircache file header error"); | 1744 | struct dircache_volume *dcvolp = DCVOL(i); |
620 | close(fd); | 1745 | |
621 | remove_dircache_file(); | 1746 | if (dcvolp->status == DIRCACHE_IDLE) |
622 | return -3; | 1747 | continue; /* idle => nothing happening there */ |
623 | } | 1748 | |
624 | 1749 | struct dircache_runinfo_volume *dcrivolp = DCRIVOL(i); | |
625 | allocated_size = maindata.size + DIRCACHE_RESERVE; | 1750 | |
626 | dircache_handle = core_alloc_ex("dircache", allocated_size, &ops); | 1751 | /* stop any scan and build on this one */ |
627 | /* block movement during upcoming I/O */ | 1752 | if (dcrivolp->sabp) |
628 | dont_move = true; | 1753 | dcrivolp->sabp->quit = true; |
629 | dircache_root = core_get_data(dircache_handle); | 1754 | |
630 | ALIGN_BUFFER(dircache_root, allocated_size, sizeof(struct dircache_entry*)); | 1755 | #ifdef HAVE_MULTIVOLUME |
631 | entry_count = maindata.entry_count; | 1756 | /* if this call is for all volumes, subsequent code will just reset |
632 | appflags = maindata.appflags; | 1757 | the cache memory usage and the freeing of individual entries may |
633 | 1758 | be skipped */ | |
634 | /* read the dircache file into memory, | 1759 | if (volume >= 0) |
635 | * start with the struct dircache_entries */ | 1760 | free_subentries(NULL, &dcvolp->root_down); |
636 | ssize_t bytes_to_read = entry_count*sizeof(struct dircache_entry); | 1761 | else |
637 | bytes_read = read(fd, dircache_root, bytes_to_read); | 1762 | #endif |
638 | 1763 | binding_dissolve_volume(dcrivolp); | |
639 | if (bytes_read != bytes_to_read) | 1764 | |
640 | { | 1765 | /* set it back to unscanned */ |
641 | logf("Dircache read failed #1"); | 1766 | dcvolp->status = DIRCACHE_IDLE; |
642 | return -6; | 1767 | dcvolp->frontier = FRONTIER_NEW; |
643 | } | 1768 | dcvolp->root_down = 0; |
644 | 1769 | dcvolp->build_ticks = 0; | |
645 | /* continue with the d_names. Fix up pointers to them if needed */ | 1770 | dcvolp->serialnum = 0; |
646 | bytes_to_read = maindata.size - bytes_to_read; | ||
647 | d_names_start = (char*)dircache_root + allocated_size - bytes_to_read; | ||
648 | bytes_read = read(fd, d_names_start, bytes_to_read); | ||
649 | close(fd); | ||
650 | remove_dircache_file(); | ||
651 | if (bytes_read != bytes_to_read) | ||
652 | { | ||
653 | logf("Dircache read failed #2"); | ||
654 | return -7; | ||
655 | } | 1771 | } |
1772 | } | ||
656 | 1773 | ||
657 | d_names_end = d_names_start + bytes_read; | 1774 | /** |
658 | dot = d_names_end - sizeof("."); | 1775 | * reset the entire cache state for all volumes |
659 | dotdot = dot - sizeof(".."); | 1776 | */ |
1777 | static void reset_cache(void) | ||
1778 | { | ||
1779 | if (!dircache_runinfo.handle) | ||
1780 | return; /* no buffer => nothing cached */ | ||
1781 | |||
1782 | /* blast all the volumes */ | ||
1783 | reset_volume(IF_MV(-1)); | ||
1784 | |||
1785 | #ifdef DIRCACHE_DUMPSTER | ||
1786 | dumpster_clean_buffer(dircache_runinfo.p + ENTRYSIZE, | ||
1787 | dircache_runinfo.bufsize); | ||
1788 | #endif /* DIRCACHE_DUMPSTER */ | ||
1789 | |||
1790 | /* reset the memory */ | ||
1791 | dircache.free_list = 0; | ||
1792 | dircache.size = 0; | ||
1793 | dircache.sizeused = 0; | ||
1794 | dircache.numentries = 0; | ||
1795 | dircache.names = dircache_runinfo.bufsize + ENTRYSIZE; | ||
1796 | dircache.sizenames = 0; | ||
1797 | dircache.namesfree = 0; | ||
1798 | dircache.nextnamefree = 0; | ||
1799 | *get_name(dircache.names - 1) = 0; | ||
1800 | /* dircache.last_serialnum stays */ | ||
1801 | /* dircache.reserve_used stays */ | ||
1802 | /* dircache.last_size stays */ | ||
1803 | } | ||
660 | 1804 | ||
661 | /* d_names are in reverse order, so the last entry points to the first string */ | 1805 | /** |
662 | ptrdiff_t offset_d_names = maindata.d_names_start - d_names_start; | 1806 | * checks each "idle" volume and builds it |
663 | ptrdiff_t offset_entries = maindata.root_entry - dircache_root; | 1807 | */ |
664 | offset_entries *= sizeof(struct dircache_entry); /* make it bytes */ | 1808 | static void build_volumes(void) |
1809 | { | ||
1810 | buffer_lock(); | ||
665 | 1811 | ||
666 | /* offset_entries is less likely to differ, so check if it's 0 in the loop | 1812 | for (int i = 0; i < NUM_VOLUMES; i++) |
667 | * offset_d_names however is almost always non-zero, since dircache_save() | ||
668 | * creates a file which causes the reserve buffer to be used. since | ||
669 | * we allocate a new, empty DIRCACHE_RESERVE here, the strings are | ||
670 | * farther behind */ | ||
671 | if (offset_entries != 0 || offset_d_names != 0) | ||
672 | { | 1813 | { |
673 | for(unsigned i = 0; i < entry_count; i++) | 1814 | /* this does reader locking but we already own that */ |
674 | { | 1815 | if (!volume_ismounted(IF_MV(i))) |
675 | if (dircache_root[i].d_name) | 1816 | continue; |
676 | dircache_root[i].d_name -= offset_d_names; | ||
677 | 1817 | ||
678 | if (offset_entries == 0) | 1818 | struct dircache_volume *dcvolp = DCVOL(i); |
679 | continue; | 1819 | |
680 | if (dircache_root[i].next_char) | 1820 | /* can't already be "scanning" because that's us; doesn't retry |
681 | dircache_root[i].next_char -= offset_entries; | 1821 | "ready" volumes */ |
682 | if (dircache_root[i].up_char) | 1822 | if (dcvolp->status == DIRCACHE_READY) |
683 | dircache_root[i].up_char -= offset_entries; | 1823 | continue; |
684 | if (dircache_root[i].down_char) | 1824 | |
685 | dircache_root[i].down_char -= offset_entries; | 1825 | /* measure how long it takes to build the cache for each volume */ |
686 | } | 1826 | if (!dcvolp->serialnum) |
1827 | dcvolp->serialnum = next_serialnum(); | ||
1828 | |||
1829 | dcvolp->status = DIRCACHE_SCANNING; | ||
1830 | dcvolp->start_tick = current_tick; | ||
1831 | |||
1832 | sab_process_volume(dcvolp); | ||
1833 | |||
1834 | if (dircache_runinfo.suspended) | ||
1835 | break; | ||
1836 | |||
1837 | /* whatever happened, it's ready unless reset */ | ||
1838 | dcvolp->build_ticks = current_tick - dcvolp->start_tick; | ||
1839 | dcvolp->status = DIRCACHE_READY; | ||
687 | } | 1840 | } |
688 | 1841 | ||
689 | /* Cache successfully loaded. */ | 1842 | size_t reserve_used = reserve_buf_used(); |
690 | dircache_size = maindata.size; | 1843 | if (reserve_used > dircache.reserve_used) |
691 | reserve_used = 0; | 1844 | dircache.reserve_used = reserve_used; |
692 | logf("Done, %ld KiB used", dircache_size / 1024); | 1845 | |
693 | dircache_initialized = true; | 1846 | if (DIRCACHE_STUFFED(reserve_used)) |
694 | memset(fd_bindings, 0, sizeof(fd_bindings)); | 1847 | dircache.last_size = 0; /* reset */ |
695 | dont_move = false; | 1848 | else if (dircache.size > dircache.last_size || |
1849 | dircache.last_size - dircache.size > DIRCACHE_RESERVE) | ||
1850 | dircache.last_size = dircache.size; | ||
1851 | |||
1852 | logf("Done, %ld KiB used", dircache.size / 1024); | ||
696 | 1853 | ||
697 | return 0; | 1854 | buffer_unlock(); |
698 | } | 1855 | } |
699 | 1856 | ||
700 | /** | 1857 | /** |
701 | * Function to save the internal cache stucture to disk for fast loading | 1858 | * allocate buffer and return whether or not a synchronous build should take |
702 | * on boot. | 1859 | * place; if 'realloced' is NULL, it's just a query about what will happen |
703 | */ | 1860 | */ |
704 | int dircache_save(void) | 1861 | static int prepare_build(bool *realloced) |
705 | { | 1862 | { |
706 | struct dircache_maindata maindata; | 1863 | /* called holding dircache lock */ |
707 | int fd; | 1864 | size_t size = dircache.last_size; |
708 | unsigned long bytes_written; | ||
709 | 1865 | ||
710 | remove_dircache_file(); | 1866 | #ifdef HAVE_EEPROM_SETTINGS |
711 | 1867 | if (realloced) | |
712 | if (!dircache_initialized) | 1868 | { |
713 | return -1; | 1869 | dircache_unlock(); |
1870 | remove_dircache_file(); | ||
1871 | dircache_lock(); | ||
714 | 1872 | ||
715 | logf("Saving directory cache"); | 1873 | if (dircache_runinfo.suspended) |
716 | dont_move = true; | 1874 | return -1; |
717 | fd = open_dircache_file(O_WRONLY | O_CREAT | O_TRUNC, 0666); | 1875 | } |
1876 | #endif /* HAVE_EEPROM_SETTINGS */ | ||
1877 | |||
1878 | bool stuffed = DIRCACHE_STUFFED(dircache.reserve_used); | ||
1879 | if (dircache_runinfo.bufsize > size && !stuffed) | ||
1880 | { | ||
1881 | if (realloced) | ||
1882 | *realloced = false; | ||
718 | 1883 | ||
719 | maindata.magic = DIRCACHE_MAGIC; | 1884 | return 0; /* start a transparent rebuild */ |
720 | maindata.size = dircache_size; | 1885 | } |
721 | maindata.root_entry = dircache_root; | ||
722 | maindata.d_names_start = d_names_start; | ||
723 | maindata.entry_count = entry_count; | ||
724 | maindata.appflags = appflags; | ||
725 | 1886 | ||
726 | /* Save the info structure */ | 1887 | int syncbuild = size > 0 && !stuffed ? 0 : 1; |
727 | bytes_written = write(fd, &maindata, sizeof(struct dircache_maindata)); | 1888 | |
728 | if (bytes_written != sizeof(struct dircache_maindata)) | 1889 | if (!realloced) |
1890 | return syncbuild; | ||
1891 | |||
1892 | if (syncbuild) | ||
729 | { | 1893 | { |
730 | close(fd); | 1894 | /* start a non-transparent rebuild */ |
731 | logf("dircache: write failed #1"); | 1895 | /* we'll use the entire audiobuf to allocate the dircache */ |
732 | return -2; | 1896 | size = audio_buffer_available() + dircache_runinfo.bufsize; |
1897 | /* try to allocate at least the min and no more than the limit */ | ||
1898 | size = MAX(DIRCACHE_MIN, MIN(size, DIRCACHE_LIMIT)); | ||
733 | } | 1899 | } |
1900 | else | ||
1901 | { | ||
1902 | /* start a transparent rebuild */ | ||
1903 | size = MAX(size, DIRCACHE_RESERVE) + DIRCACHE_RESERVE*2; | ||
1904 | } | ||
1905 | |||
1906 | *realloced = true; | ||
1907 | reset_cache(); | ||
734 | 1908 | ||
735 | /* Dump whole directory cache to disk | 1909 | buffer_lock(); |
736 | * start by writing the dircache_entries */ | 1910 | |
737 | size_t bytes_to_write = entry_count*sizeof(struct dircache_entry); | 1911 | int handle = reset_buffer(); |
738 | bytes_written = write(fd, dircache_root, bytes_to_write); | 1912 | dircache_unlock(); |
739 | if (bytes_written != bytes_to_write) | 1913 | |
1914 | if (handle > 0) | ||
1915 | core_free(handle); | ||
1916 | |||
1917 | handle = alloc_cache(size); | ||
1918 | |||
1919 | dircache_lock(); | ||
1920 | |||
1921 | if (dircache_runinfo.suspended && handle > 0) | ||
740 | { | 1922 | { |
741 | logf("dircache: write failed #2"); | 1923 | /* if we got suspended, don't keep this huge buffer around */ |
742 | return -3; | 1924 | dircache_unlock(); |
1925 | core_free(handle); | ||
1926 | handle = 0; | ||
1927 | dircache_lock(); | ||
743 | } | 1928 | } |
744 | 1929 | ||
745 | /* continue with the d_names */ | 1930 | if (handle <= 0) |
746 | bytes_to_write = d_names_end - d_names_start; | ||
747 | bytes_written = write(fd, d_names_start, bytes_to_write); | ||
748 | close(fd); | ||
749 | if (bytes_written != bytes_to_write) | ||
750 | { | 1931 | { |
751 | logf("dircache: write failed #3"); | 1932 | buffer_unlock(); |
752 | return -4; | 1933 | return -1; |
753 | } | 1934 | } |
754 | 1935 | ||
755 | dont_move = false; | 1936 | set_buffer(handle, size); |
756 | return 0; | 1937 | buffer_unlock(); |
1938 | |||
1939 | return syncbuild; | ||
757 | } | 1940 | } |
758 | #endif /* HAVE_EEPROM_SETTINGS */ | ||
759 | 1941 | ||
760 | /** | 1942 | /** |
761 | * Internal function which scans the disk and creates the dircache structure. | 1943 | * compact the dircache buffer after a successful full build |
762 | */ | 1944 | */ |
763 | static int dircache_do_rebuild(void) | 1945 | static void compact_cache(void) |
764 | { | 1946 | { |
765 | struct dircache_entry* root_entry; | 1947 | /* called holding dircache lock */ |
766 | unsigned int start_tick; | 1948 | if (dircache_runinfo.suspended) |
767 | int i; | 1949 | return; |
768 | |||
769 | /* Measure how long it takes build the cache. */ | ||
770 | start_tick = current_tick; | ||
771 | dircache_initializing = true; | ||
772 | appflags = 0; | ||
773 | 1950 | ||
774 | /* reset dircache and alloc root entry */ | 1951 | void *p = dircache_runinfo.p + ENTRYSIZE; |
775 | entry_count = 0; | 1952 | size_t leadsize = dircache.numentries * ENTRYSIZE + DIRCACHE_RESERVE; |
776 | root_entry = allocate_entry(); | ||
777 | dont_move = true; | ||
778 | 1953 | ||
779 | #ifdef HAVE_MULTIVOLUME | 1954 | void *dst = p + leadsize; |
780 | append_position = root_entry; | 1955 | void *src = get_name(dircache.names - 1); |
1956 | if (dst >= src) | ||
1957 | return; /* cache got bigger than expected; never mind that */ | ||
1958 | |||
1959 | /* slide the names up in memory */ | ||
1960 | memmove(dst, src, dircache.sizenames + 2); | ||
781 | 1961 | ||
782 | for (i = NUM_VOLUMES; i >= 0; i--) | 1962 | /* fix up name indexes */ |
1963 | ptrdiff_t offset = dst - src; | ||
1964 | |||
1965 | FOR_EACH_CACHE_ENTRY(ce) | ||
783 | { | 1966 | { |
784 | if (fat_ismounted(i)) | 1967 | if (!ce->tinyname) |
785 | { | 1968 | ce->name += offset; |
786 | #endif | ||
787 | cpu_boost(true); | ||
788 | #ifdef HAVE_MULTIVOLUME | ||
789 | if (dircache_scan_and_build(IF_MV(i,) append_position) < 0) | ||
790 | #else | ||
791 | if (dircache_scan_and_build(IF_MV(0,) root_entry) < 0) | ||
792 | #endif /* HAVE_MULTIVOLUME */ | ||
793 | { | ||
794 | logf("dircache_scan_and_build failed"); | ||
795 | cpu_boost(false); | ||
796 | dircache_size = 0; | ||
797 | dircache_initializing = false; | ||
798 | dont_move = false; | ||
799 | return -2; | ||
800 | } | ||
801 | cpu_boost(false); | ||
802 | #ifdef HAVE_MULTIVOLUME | ||
803 | } | ||
804 | } | 1969 | } |
805 | #endif | ||
806 | 1970 | ||
807 | logf("Done, %ld KiB used", dircache_size / 1024); | 1971 | dircache.names += offset; |
808 | |||
809 | dircache_initialized = true; | ||
810 | dircache_initializing = false; | ||
811 | cache_build_ticks = current_tick - start_tick; | ||
812 | |||
813 | /* Initialized fd bindings. */ | ||
814 | memset(fd_bindings, 0, sizeof(fd_bindings)); | ||
815 | for (i = 0; i < fdbind_idx; i++) | ||
816 | dircache_bind(fdbind_cache[i].fd, fdbind_cache[i].path); | ||
817 | fdbind_idx = 0; | ||
818 | |||
819 | if (thread_enabled) | ||
820 | { | ||
821 | if (allocated_size - dircache_size < DIRCACHE_RESERVE) | ||
822 | reserve_used = DIRCACHE_RESERVE - (allocated_size - dircache_size); | ||
823 | } | ||
824 | |||
825 | dont_move = false; | ||
826 | return 1; | ||
827 | } | ||
828 | 1972 | ||
829 | /* | 1973 | /* assumes beelzelib doesn't do things like calling callbacks or changing |
830 | * Free all associated resources, if any */ | 1974 | the pointer as a result of the shrink operation; it doesn't as of now |
831 | static void dircache_free(void) | 1975 | but if it ever does that may very well cause deadlock problems since |
832 | { | 1976 | we're holding filesystem locks */ |
833 | if (dircache_handle > 0) | 1977 | size_t newsize = leadsize + dircache.sizenames + 1; |
834 | dircache_handle = core_free(dircache_handle); | 1978 | core_shrink(dircache_runinfo.handle, p, newsize + 1); |
835 | dircache_size = allocated_size = 0; | 1979 | dircache_runinfo.bufsize = newsize; |
1980 | dircache.reserve_used = 0; | ||
836 | } | 1981 | } |
837 | 1982 | ||
838 | /** | 1983 | /** |
839 | * Internal thread that controls transparent cache building. | 1984 | * internal thread that controls cache building; exits when no more requests |
1985 | * are pending or the cache is suspended | ||
840 | */ | 1986 | */ |
841 | static void dircache_thread(void) | 1987 | static void dircache_thread(void) |
842 | { | 1988 | { |
843 | struct queue_event ev; | 1989 | struct queue_event ev; |
844 | 1990 | ||
1991 | /* calls made within the loop reopen the lock */ | ||
1992 | dircache_lock(); | ||
1993 | |||
845 | while (1) | 1994 | while (1) |
846 | { | 1995 | { |
847 | queue_wait(&dircache_queue, &ev); | 1996 | queue_wait_w_tmo(&dircache_queue, &ev, 0); |
848 | 1997 | if (ev.id == SYS_TIMEOUT || dircache_runinfo.suspended) | |
849 | switch (ev.id) | ||
850 | { | 1998 | { |
851 | #ifdef HAVE_HOTSWAP | 1999 | /* nothing left to do/suspended */ |
852 | case SYS_FS_CHANGED: | 2000 | if (dircache_runinfo.suspended) |
853 | if (!dircache_initialized) | 2001 | clear_dircache_queue(); |
854 | break; | 2002 | dircache_runinfo.thread_done = true; |
855 | dircache_initialized = false; | 2003 | break; |
856 | #endif | ||
857 | case DIRCACHE_BUILD: | ||
858 | thread_enabled = true; | ||
859 | if (dircache_do_rebuild() < 0) | ||
860 | dircache_free(); | ||
861 | thread_enabled = false; | ||
862 | break ; | ||
863 | |||
864 | case DIRCACHE_STOP: | ||
865 | logf("Stopped the rebuilding."); | ||
866 | dircache_initialized = false; | ||
867 | break ; | ||
868 | |||
869 | case SYS_USB_CONNECTED: | ||
870 | usb_acknowledge(SYS_USB_CONNECTED_ACK); | ||
871 | usb_wait_for_disconnect(&dircache_queue); | ||
872 | break ; | ||
873 | } | 2004 | } |
874 | } | ||
875 | } | ||
876 | 2005 | ||
877 | static void generate_dot_d_names(void) | 2006 | /* background-only builds are not allowed if a synchronous build is |
878 | { | 2007 | required first; test what needs to be done and if it checks out, |
879 | dot = (d_names_start -= sizeof(".")); | 2008 | do it for real below */ |
880 | dotdot = (d_names_start -= sizeof("..")); | 2009 | int *rcp = (int *)ev.data; |
881 | dircache_size += sizeof(".") + sizeof(".."); | 2010 | |
882 | strcpy(dot, "."); | 2011 | if (!rcp && prepare_build(NULL) > 0) |
883 | strcpy(dotdot, ".."); | 2012 | continue; |
2013 | |||
2014 | bool realloced; | ||
2015 | int rc = prepare_build(&realloced); | ||
2016 | if (rcp) | ||
2017 | *rcp = rc; | ||
2018 | |||
2019 | if (rc < 0) | ||
2020 | continue; | ||
2021 | |||
2022 | trigger_cpu_boost(); | ||
2023 | build_volumes(); | ||
2024 | |||
2025 | /* if it was reallocated, compact it */ | ||
2026 | if (realloced) | ||
2027 | compact_cache(); | ||
2028 | } | ||
2029 | |||
2030 | dircache_unlock(); | ||
884 | } | 2031 | } |
885 | 2032 | ||
886 | /** | 2033 | /** |
887 | * Start scanning the disk to build the dircache. | 2034 | * post a scan and build message to the thread, starting it if required |
888 | * Either transparent or non-transparent build method is used. | ||
889 | */ | 2035 | */ |
890 | int dircache_build(int last_size) | 2036 | static bool dircache_thread_post(int volatile *rcp) |
891 | { | 2037 | { |
892 | if (dircache_initialized || thread_enabled) | 2038 | if (dircache_runinfo.thread_done) |
893 | return -3; | 2039 | { |
2040 | /* mustn't recreate until it exits so that the stack isn't reused */ | ||
2041 | thread_wait(dircache_runinfo.thread_id); | ||
2042 | dircache_runinfo.thread_done = false; | ||
2043 | dircache_runinfo.thread_id = create_thread( | ||
2044 | dircache_thread, dircache_stack, sizeof (dircache_stack), 0, | ||
2045 | dircache_thread_name IF_PRIO(, PRIORITY_BACKGROUND) | ||
2046 | IF_COP(, CPU)); | ||
2047 | } | ||
894 | 2048 | ||
895 | logf("Building directory cache"); | 2049 | bool started = dircache_runinfo.thread_id != 0; |
896 | #ifdef HAVE_EEPROM_SETTINGS | ||
897 | remove_dircache_file(); | ||
898 | #endif | ||
899 | 2050 | ||
900 | /* Background build, dircache has been previously allocated and */ | 2051 | if (started) |
901 | if (allocated_size > MAX(last_size, 0)) | 2052 | queue_post(&dircache_queue, DCM_BUILD, (intptr_t)rcp); |
902 | { | ||
903 | d_names_start = d_names_end; | ||
904 | dircache_size = 0; | ||
905 | reserve_used = 0; | ||
906 | thread_enabled = true; | ||
907 | dircache_initializing = true; | ||
908 | generate_dot_d_names(); | ||
909 | |||
910 | queue_post(&dircache_queue, DIRCACHE_BUILD, 0); | ||
911 | return 2; | ||
912 | } | ||
913 | |||
914 | /* start by freeing, as we possibly re-allocate */ | ||
915 | dircache_free(); | ||
916 | |||
917 | if (last_size > DIRCACHE_RESERVE && last_size < DIRCACHE_LIMIT ) | ||
918 | { | ||
919 | allocated_size = last_size + DIRCACHE_RESERVE; | ||
920 | dircache_handle = core_alloc_ex("dircache", allocated_size, &ops); | ||
921 | dircache_root = core_get_data(dircache_handle); | ||
922 | ALIGN_BUFFER(dircache_root, allocated_size, sizeof(struct dircache_entry*)); | ||
923 | d_names_start = d_names_end = ((char*)dircache_root)+allocated_size-1; | ||
924 | dircache_size = 0; | ||
925 | thread_enabled = true; | ||
926 | generate_dot_d_names(); | ||
927 | |||
928 | /* Start a transparent rebuild. */ | ||
929 | queue_post(&dircache_queue, DIRCACHE_BUILD, 0); | ||
930 | return 3; | ||
931 | } | ||
932 | |||
933 | /* We'll use the entire audiobuf to allocate the dircache | ||
934 | * struct dircache_entrys are allocated from the beginning | ||
935 | * and their corresponding d_name from the end | ||
936 | * after generation the buffer will be compacted with DIRCACHE_RESERVE | ||
937 | * free bytes inbetween */ | ||
938 | size_t available = audio_buffer_available(); | ||
939 | /* try to allocate at least 1MB, the more the better */ | ||
940 | if (available < 1<<20) available = 1<<20; | ||
941 | if (available > DIRCACHE_LIMIT) available = DIRCACHE_LIMIT; | ||
942 | |||
943 | dircache_handle = core_alloc_ex("dircache", available, &ops); | ||
944 | if (dircache_handle <= 0) | ||
945 | return -1; /* that was not successful, should try rebooting */ | ||
946 | char* buf = core_get_data(dircache_handle); | ||
947 | dircache_root = (struct dircache_entry*)ALIGN_UP(buf, | ||
948 | sizeof(struct dircache_entry*)); | ||
949 | d_names_start = d_names_end = buf + available - 1; | ||
950 | dircache_size = 0; | ||
951 | generate_dot_d_names(); | ||
952 | |||
953 | /* Start a non-transparent rebuild. */ | ||
954 | int res = dircache_do_rebuild(); | ||
955 | if (res < 0) | ||
956 | goto fail; | ||
957 | |||
958 | /* now compact the dircache buffer */ | ||
959 | char* dst = ((char*)&dircache_root[entry_count] + DIRCACHE_RESERVE); | ||
960 | ptrdiff_t offset = d_names_start - dst; | ||
961 | if (offset <= 0) /* something went wrong */ | ||
962 | { | ||
963 | res = -1; | ||
964 | goto fail; | ||
965 | } | ||
966 | |||
967 | /* memmove d_names down, there's a possibility of overlap | ||
968 | * equivaent to dircache_size - entry_count*sizeof(struct dircache_entry) */ | ||
969 | ptrdiff_t size_to_move = d_names_end - d_names_start; | ||
970 | memmove(dst, d_names_start, size_to_move); | ||
971 | |||
972 | /* fix up pointers to the d_names */ | ||
973 | for(unsigned i = 0; i < entry_count; i++) | ||
974 | dircache_root[i].d_name -= offset; | ||
975 | |||
976 | d_names_start -= offset; | ||
977 | d_names_end -= offset; | ||
978 | dot -= offset; | ||
979 | dotdot -= offset; | ||
980 | |||
981 | /* equivalent to dircache_size + DIRCACHE_RESERVE + align */ | ||
982 | allocated_size = (d_names_end - buf); | ||
983 | reserve_used = 0; | ||
984 | |||
985 | core_shrink(dircache_handle, dircache_root, allocated_size); | ||
986 | return res; | ||
987 | fail: | ||
988 | dircache_disable(); | ||
989 | return res; | ||
990 | } | ||
991 | |||
992 | /** | ||
993 | * Main initialization function that must be called before any other | ||
994 | * operations within the dircache. | ||
995 | */ | ||
996 | void dircache_init(void) | ||
997 | { | ||
998 | int i; | ||
999 | int thread_id __attribute__((unused)); | ||
1000 | |||
1001 | dircache_initialized = false; | ||
1002 | dircache_initializing = false; | ||
1003 | |||
1004 | memset(opendirs, 0, sizeof(opendirs)); | ||
1005 | for (i = 0; i < MAX_OPEN_DIRS; i++) | ||
1006 | { | ||
1007 | opendirs[i].theent.d_name = opendir_dnames[i]; | ||
1008 | } | ||
1009 | |||
1010 | queue_init(&dircache_queue, true); | ||
1011 | thread_id = create_thread(dircache_thread, dircache_stack, | ||
1012 | sizeof(dircache_stack), 0, dircache_thread_name | ||
1013 | IF_PRIO(, PRIORITY_BACKGROUND) | ||
1014 | IF_COP(, CPU)); | ||
1015 | #ifdef HAVE_IO_PRIORITY | ||
1016 | thread_set_io_priority(thread_id,IO_PRIORITY_BACKGROUND); | ||
1017 | #endif | ||
1018 | 2053 | ||
2054 | return started; | ||
1019 | } | 2055 | } |
1020 | 2056 | ||
1021 | /** | 2057 | /** |
1022 | * Returns true if dircache has been initialized and is ready to be used. | 2058 | * wait for the dircache thread to finish building; intended for waiting for a |
2059 | * non-transparent build to finish when dircache_resume() returns > 0 | ||
1023 | */ | 2060 | */ |
1024 | bool dircache_is_enabled(void) | 2061 | void dircache_wait(void) |
1025 | { | 2062 | { |
1026 | return dircache_initialized; | 2063 | thread_wait(dircache_runinfo.thread_id); |
1027 | } | 2064 | } |
1028 | 2065 | ||
1029 | /** | 2066 | /** |
1030 | * Returns true if dircache is being initialized. | 2067 | * call after mounting a volume or all volumes |
1031 | */ | 2068 | */ |
1032 | bool dircache_is_initializing(void) | 2069 | void dircache_mount(void) |
1033 | { | 2070 | { |
1034 | return dircache_initializing || thread_enabled; | 2071 | /* call with writer exclusion */ |
2072 | if (dircache_runinfo.suspended) | ||
2073 | return; | ||
2074 | |||
2075 | dircache_thread_post(NULL); | ||
1035 | } | 2076 | } |
1036 | 2077 | ||
1037 | /** | 2078 | /** |
1038 | * Set application flags used to determine if dircache is still intact. | 2079 | * call after unmounting a volume; specifying < 0 for all or >= 0 for the |
2080 | * specific one | ||
1039 | */ | 2081 | */ |
1040 | void dircache_set_appflag(long mask) | 2082 | void dircache_unmount(IF_MV_NONVOID(int volume)) |
1041 | { | 2083 | { |
1042 | appflags |= mask; | 2084 | /* call with writer exclusion */ |
2085 | if (dircache_runinfo.suspended) | ||
2086 | return; | ||
2087 | |||
2088 | #ifdef HAVE_MULTIVOLUME | ||
2089 | if (volume >= 0) | ||
2090 | reset_volume(volume); | ||
2091 | else | ||
2092 | #endif /* HAVE_MULTIVOLUME */ | ||
2093 | reset_cache(); | ||
2094 | } | ||
2095 | |||
2096 | /* backend to dircache_suspend() and dircache_disable() */ | ||
2097 | static void dircache_suspend_internal(bool freeit) | ||
2098 | { | ||
2099 | if (dircache_runinfo.suspended++ > 0 && | ||
2100 | (!freeit || dircache_runinfo.handle <= 0)) | ||
2101 | return; | ||
2102 | |||
2103 | unsigned int thread_id = dircache_runinfo.thread_id; | ||
2104 | |||
2105 | reset_cache(); | ||
2106 | clear_dircache_queue(); | ||
2107 | |||
2108 | /* grab the buffer away into our control; the cache won't need it now */ | ||
2109 | int handle = 0; | ||
2110 | if (freeit) | ||
2111 | handle = reset_buffer(); | ||
2112 | |||
2113 | dircache_unlock(); | ||
2114 | |||
2115 | if (handle > 0) | ||
2116 | core_free(handle); | ||
2117 | |||
2118 | thread_wait(thread_id); | ||
2119 | |||
2120 | dircache_lock(); | ||
2121 | } | ||
2122 | |||
2123 | /* backend to dircache_resume() and dircache_enable() */ | ||
2124 | static int dircache_resume_internal(bool build_now) | ||
2125 | { | ||
2126 | int volatile rc = 0; | ||
2127 | |||
2128 | if (dircache_runinfo.suspended == 0 || --dircache_runinfo.suspended == 0) | ||
2129 | rc = build_now && dircache_runinfo.enabled ? 1 : 0; | ||
2130 | |||
2131 | if (rc) | ||
2132 | rc = dircache_thread_post(&rc) ? INT_MIN : -1; | ||
2133 | |||
2134 | if (rc == INT_MIN) | ||
2135 | { | ||
2136 | dircache_unlock(); | ||
2137 | |||
2138 | while (rc == INT_MIN) /* poll for response */ | ||
2139 | sleep(0); | ||
2140 | |||
2141 | dircache_lock(); | ||
2142 | } | ||
2143 | |||
2144 | return rc < 0 ? rc * 10 - 2 : rc; | ||
1043 | } | 2145 | } |
1044 | 2146 | ||
1045 | /** | 2147 | /** |
1046 | * Get application flags used to determine if dircache is still intact. | 2148 | * service to dircache_enable() and dircache_load(); "build_now" starts a build |
2149 | * immediately if the cache was not enabled | ||
1047 | */ | 2150 | */ |
1048 | bool dircache_get_appflag(long mask) | 2151 | static int dircache_enable_internal(bool build_now) |
1049 | { | 2152 | { |
1050 | return dircache_is_enabled() && (appflags & mask); | 2153 | int rc = 0; |
2154 | |||
2155 | if (!dircache_runinfo.enabled) | ||
2156 | { | ||
2157 | dircache_runinfo.enabled = true; | ||
2158 | rc = dircache_resume_internal(build_now); | ||
2159 | } | ||
2160 | |||
2161 | return rc; | ||
1051 | } | 2162 | } |
1052 | 2163 | ||
1053 | /** | 2164 | /** |
1054 | * Returns the current number of entries (directories and files) in the cache. | 2165 | * service to dircache_disable() |
1055 | */ | 2166 | */ |
1056 | int dircache_get_entry_count(void) | 2167 | static void dircache_disable_internal(void) |
1057 | { | 2168 | { |
1058 | return entry_count; | 2169 | if (dircache_runinfo.enabled) |
2170 | { | ||
2171 | dircache_runinfo.enabled = false; | ||
2172 | dircache_suspend_internal(true); | ||
2173 | } | ||
1059 | } | 2174 | } |
1060 | 2175 | ||
1061 | /** | 2176 | /** |
1062 | * Returns the allocated space for dircache (without reserve space). | 2177 | * disables dircache without freeing the buffer (so it can be re-enabled |
2178 | * afterwards with dircache_resume(); usually called when accepting an USB | ||
2179 | * connection | ||
1063 | */ | 2180 | */ |
1064 | int dircache_get_cache_size(void) | 2181 | void dircache_suspend(void) |
1065 | { | 2182 | { |
1066 | return dircache_is_enabled() ? dircache_size : 0; | 2183 | dircache_lock(); |
2184 | dircache_suspend_internal(false); | ||
2185 | dircache_unlock(); | ||
1067 | } | 2186 | } |
1068 | 2187 | ||
1069 | /** | 2188 | /** |
1070 | * Returns how many bytes of the reserve allocation for live cache | 2189 | * re-enables the dircache if previously suspended by dircache_suspend |
1071 | * updates have been used. | 2190 | * or dircache_steal_buffer(), re-using the already allocated buffer if |
2191 | * available | ||
2192 | * | ||
2193 | * returns: 0 if the background build is started or dircache is still | ||
2194 | * suspended | ||
2195 | * > 0 if the build is non-background | ||
2196 | * < 0 upon failure | ||
1072 | */ | 2197 | */ |
1073 | int dircache_get_reserve_used(void) | 2198 | int dircache_resume(void) |
1074 | { | 2199 | { |
1075 | return dircache_is_enabled() ? reserve_used : 0; | 2200 | dircache_lock(); |
2201 | int rc = dircache_resume_internal(true); | ||
2202 | dircache_unlock(); | ||
2203 | return rc; | ||
1076 | } | 2204 | } |
1077 | 2205 | ||
1078 | /** | 2206 | /** |
1079 | * Returns the time in kernel ticks that took to build the cache. | 2207 | * as dircache_resume() but globally enables it; called by settings and init |
1080 | */ | 2208 | */ |
1081 | int dircache_get_build_ticks(void) | 2209 | int dircache_enable(void) |
1082 | { | 2210 | { |
1083 | return dircache_is_enabled() ? cache_build_ticks : 0; | 2211 | dircache_lock(); |
2212 | int rc = dircache_enable_internal(true); | ||
2213 | dircache_unlock(); | ||
2214 | return rc; | ||
1084 | } | 2215 | } |
1085 | 2216 | ||
1086 | /** | 2217 | /** |
1087 | * Disables dircache without freeing the buffer (so it can be re-enabled | 2218 | * as dircache_suspend() but also frees the buffer; usually called on shutdown |
1088 | * afterwards with dircache_resume() or dircache_build()), usually | 2219 | * or when deactivated |
1089 | * called when accepting an usb connection */ | 2220 | */ |
1090 | void dircache_suspend(void) | 2221 | void dircache_disable(void) |
1091 | { | 2222 | { |
1092 | int i; | 2223 | dircache_lock(); |
1093 | bool cache_in_use; | 2224 | dircache_disable_internal(); |
1094 | 2225 | dircache_unlock(); | |
1095 | if (thread_enabled) | ||
1096 | queue_post(&dircache_queue, DIRCACHE_STOP, 0); | ||
1097 | |||
1098 | while (thread_enabled) | ||
1099 | sleep(1); | ||
1100 | dircache_initialized = false; | ||
1101 | |||
1102 | logf("Waiting for cached dirs to release"); | ||
1103 | do { | ||
1104 | cache_in_use = false; | ||
1105 | for (i = 0; i < MAX_OPEN_DIRS; i++) { | ||
1106 | if (!opendirs[i].regulardir && opendirs[i].busy) | ||
1107 | { | ||
1108 | cache_in_use = true; | ||
1109 | sleep(1); | ||
1110 | break ; | ||
1111 | } | ||
1112 | } | ||
1113 | } while (cache_in_use) ; | ||
1114 | |||
1115 | logf("Cache released"); | ||
1116 | entry_count = 0; | ||
1117 | } | 2226 | } |
1118 | 2227 | ||
1119 | /** | 2228 | /** |
1120 | * Re-enables the dircache if previous suspended by dircache_suspend | 2229 | * have dircache give up its allocation; call dircache_resume() to restart it |
1121 | * or dircache_steal_buffer(), re-using the already allocated buffer | ||
1122 | * | ||
1123 | * Returns true if the background build is started, false otherwise | ||
1124 | * (e.g. if no buffer was previously allocated) | ||
1125 | */ | 2230 | */ |
1126 | bool dircache_resume(void) | 2231 | void dircache_free_buffer(void) |
1127 | { | 2232 | { |
1128 | bool ret = allocated_size > 0; | 2233 | dircache_lock(); |
1129 | if (ret) /* only resume if already allocated */ | 2234 | dircache_suspend_internal(true); |
1130 | ret = (dircache_build(0) > 0); | 2235 | dircache_unlock(); |
2236 | } | ||
2237 | |||
2238 | |||
2239 | /** Dircache live updating **/ | ||
1131 | 2240 | ||
1132 | return (allocated_size > 0); | 2241 | /** |
2242 | * obtain binding information for the file's root volume; this is the starting | ||
2243 | * point for internal path parsing and binding | ||
2244 | */ | ||
2245 | void dircache_get_rootinfo(struct file_base_info *infop) | ||
2246 | { | ||
2247 | int volume = BASEINFO_VOL(infop); | ||
2248 | struct dircache_volume *dcvolp = DCVOL(volume); | ||
2249 | |||
2250 | if (dcvolp->serialnum) | ||
2251 | { | ||
2252 | /* root has a binding */ | ||
2253 | infop->dcfile.idx = -volume - 1; | ||
2254 | infop->dcfile.serialnum = dcvolp->serialnum; | ||
2255 | } | ||
2256 | else | ||
2257 | { | ||
2258 | /* root is idle */ | ||
2259 | dircache_dcfile_init(&infop->dcfile); | ||
2260 | } | ||
1133 | } | 2261 | } |
1134 | 2262 | ||
1135 | /** | 2263 | /** |
1136 | * Disables the dircache entirely. Usually called on shutdown or when | 2264 | * called by file code when the first reference to a file or directory is |
1137 | * deactivated | 2265 | * opened |
1138 | */ | 2266 | */ |
1139 | void dircache_disable(void) | 2267 | void dircache_bind_file(struct file_base_binding *bindp) |
2268 | { | ||
2269 | /* requires write exclusion */ | ||
2270 | logf("dc open: %u", (unsigned int)bindp->info.dcfile.serialnum); | ||
2271 | binding_open(bindp); | ||
2272 | } | ||
2273 | |||
2274 | /** | ||
2275 | * called by file code when the last reference to a file or directory is | ||
2276 | * closed | ||
2277 | */ | ||
2278 | void dircache_unbind_file(struct file_base_binding *bindp) | ||
1140 | { | 2279 | { |
1141 | dircache_suspend(); | 2280 | /* requires write exclusion */ |
1142 | dircache_free(); | 2281 | logf("dc close: %u", (unsigned int)bindp->info.dcfile.serialnum); |
2282 | binding_close(bindp); | ||
1143 | } | 2283 | } |
1144 | 2284 | ||
1145 | /** | 2285 | /** |
1146 | * Steal the allocated dircache buffer and disable dircache. | 2286 | * called by file code when a file is newly created |
1147 | */ | 2287 | */ |
1148 | void* dircache_steal_buffer(size_t *size) | 2288 | void dircache_fileop_create(struct file_base_info *dirinfop, |
2289 | struct file_base_binding *bindp, | ||
2290 | const char *basename, | ||
2291 | const struct dirinfo_native *dinp) | ||
1149 | { | 2292 | { |
1150 | dircache_suspend(); | 2293 | /* requires write exclusion */ |
1151 | if (dircache_size == 0) | 2294 | logf("dc create: %u \"%s\"", |
2295 | (unsigned int)bindp->info.dcfile.serialnum, basename); | ||
2296 | |||
2297 | if (!dirinfop->dcfile.serialnum) | ||
1152 | { | 2298 | { |
1153 | *size = 0; | 2299 | /* no parent binding => no child binding */ |
1154 | return NULL; | 2300 | return; |
1155 | } | 2301 | } |
1156 | 2302 | ||
1157 | /* since we give up the buffer (without freeing), it must not move anymore */ | 2303 | struct dircache_entry *ce; |
1158 | dont_move = true; | 2304 | int idx = create_entry(basename, &ce); |
1159 | *size = dircache_size + (DIRCACHE_RESERVE-reserve_used); | 2305 | if (idx == 0) |
1160 | 2306 | { | |
1161 | return dircache_root; | 2307 | /* failed allocation; parent cache contents are not complete */ |
2308 | establish_frontier(dirinfop->dcfile.idx, FRONTIER_ZONED); | ||
2309 | return; | ||
2310 | } | ||
2311 | |||
2312 | struct file_base_info *infop = &bindp->info; | ||
2313 | |||
2314 | #ifdef DIRCACHE_NATIVE | ||
2315 | ce->firstcluster = infop->fatfile.firstcluster; | ||
2316 | ce->direntry = infop->fatfile.e.entry; | ||
2317 | ce->direntries = infop->fatfile.e.entries; | ||
2318 | ce->wrtdate = dinp->wrtdate; | ||
2319 | ce->wrttime = dinp->wrttime; | ||
2320 | #else | ||
2321 | ce->mtime = dinp->mtime; | ||
2322 | #endif | ||
2323 | ce->attr = dinp->attr; | ||
2324 | if (!(dinp->attr & ATTR_DIRECTORY)) | ||
2325 | ce->filesize = dinp->size; | ||
2326 | |||
2327 | insert_file_entry(dirinfop, ce); | ||
2328 | |||
2329 | /* file binding will have been queued when it was opened; just resolve */ | ||
2330 | infop->dcfile.idx = idx; | ||
2331 | infop->dcfile.serialnum = ce->serialnum; | ||
2332 | binding_resolve(infop); | ||
2333 | |||
2334 | if ((dinp->attr & ATTR_DIRECTORY) && !is_dotdir_name(basename)) | ||
2335 | { | ||
2336 | /* scan-in the contents of the new directory at this level only */ | ||
2337 | buffer_lock(); | ||
2338 | sab_process_dir(infop, false); | ||
2339 | buffer_unlock(); | ||
2340 | } | ||
1162 | } | 2341 | } |
1163 | 2342 | ||
1164 | /** | 2343 | /** |
1165 | * Usermode function to return dircache_entry index to the given path. | 2344 | * called by file code when a file or directory is removed |
1166 | */ | 2345 | */ |
1167 | static int dircache_get_entry_id_ex(const char *filename, bool go_down) | 2346 | void dircache_fileop_remove(struct file_base_binding *bindp) |
1168 | { | 2347 | { |
1169 | if (!dircache_initialized || filename == NULL) | 2348 | /* requires write exclusion */ |
1170 | return -1; | 2349 | logf("dc remove: %u\n", (unsigned int)bindp->info.dcfile.serialnum); |
1171 | |||
1172 | struct dircache_entry* res = dircache_get_entry(filename, go_down); | ||
1173 | return res ? res - dircache_root : -1; | ||
1174 | } | ||
1175 | 2350 | ||
1176 | int dircache_get_entry_id(const char* filename) | 2351 | if (!bindp->info.dcfile.serialnum) |
1177 | { | 2352 | return; /* no binding yet */ |
1178 | return dircache_get_entry_id_ex(filename, false); | 2353 | |
2354 | free_file_entry(&bindp->info); | ||
2355 | |||
2356 | /* if binding was resolved; it should now be queued via above call */ | ||
1179 | } | 2357 | } |
1180 | 2358 | ||
1181 | /** | 2359 | /** |
1182 | * Internal: Get the startcluster for the index | 2360 | * called by file code when a file is renamed |
1183 | */ | 2361 | */ |
1184 | long _dircache_get_entry_startcluster(int id) | 2362 | void dircache_fileop_rename(struct file_base_info *dirinfop, |
2363 | struct file_base_binding *bindp, | ||
2364 | const char *basename) | ||
1185 | { | 2365 | { |
1186 | return get_entry(id)->startcluster; | 2366 | /* requires write exclusion */ |
2367 | logf("dc rename: %u \"%s\"", | ||
2368 | (unsigned int)bindp->info.dcfile.serialnum, basename); | ||
2369 | |||
2370 | if (!dirinfop->dcfile.serialnum) | ||
2371 | { | ||
2372 | /* new parent directory not cached; there is nowhere to put it so | ||
2373 | nuke it */ | ||
2374 | if (bindp->info.dcfile.serialnum) | ||
2375 | free_file_entry(&bindp->info); | ||
2376 | /* else no entry anyway */ | ||
2377 | |||
2378 | return; | ||
2379 | } | ||
2380 | |||
2381 | if (!bindp->info.dcfile.serialnum) | ||
2382 | { | ||
2383 | /* binding not resolved on the old file but it's going into a resolved | ||
2384 | parent which means the parent would be missing an entry in the cache; | ||
2385 | downgrade the parent */ | ||
2386 | establish_frontier(dirinfop->dcfile.idx, FRONTIER_ZONED); | ||
2387 | return; | ||
2388 | } | ||
2389 | |||
2390 | /* unlink the entry but keep it; it needs to be re-sorted since the | ||
2391 | underlying FS probably changed the order */ | ||
2392 | struct dircache_entry *ce = remove_file_entry(&bindp->info); | ||
2393 | |||
2394 | #ifdef DIRCACHE_NATIVE | ||
2395 | /* update other name-related information before inserting */ | ||
2396 | ce->direntry = bindp->info.fatfile.e.entry; | ||
2397 | ce->direntries = bindp->info.fatfile.e.entries; | ||
2398 | #endif | ||
2399 | |||
2400 | /* place it into its new home */ | ||
2401 | insert_file_entry(dirinfop, ce); | ||
2402 | |||
2403 | /* lastly, update the entry name itself */ | ||
2404 | if (entry_reassign_name(ce, basename)) | ||
2405 | { | ||
2406 | /* it's not really the same one now so re-stamp it */ | ||
2407 | dc_serial_t serialnum = next_serialnum(); | ||
2408 | ce->serialnum = serialnum; | ||
2409 | bindp->info.dcfile.serialnum = serialnum; | ||
2410 | } | ||
2411 | else | ||
2412 | { | ||
2413 | /* it cannot be kept around without a valid name */ | ||
2414 | free_file_entry(&bindp->info); | ||
2415 | establish_frontier(dirinfop->dcfile.idx, FRONTIER_ZONED); | ||
2416 | } | ||
1187 | } | 2417 | } |
1188 | 2418 | ||
1189 | /** | 2419 | /** |
1190 | * Internal: Get the struct dirinfo for the index | 2420 | * called by file code to synchronize file entry information |
1191 | */ | 2421 | */ |
1192 | struct dirinfo* _dircache_get_entry_dirinfo(int id) | 2422 | void dircache_fileop_sync(struct file_base_binding *bindp, |
2423 | const struct dirinfo_native *dinp) | ||
1193 | { | 2424 | { |
1194 | return &get_entry(id)->info; | 2425 | /* requires write exclusion */ |
2426 | struct file_base_info *infop = &bindp->info; | ||
2427 | logf("dc sync: %u\n", (unsigned int)infop->dcfile.serialnum); | ||
2428 | |||
2429 | if (!infop->dcfile.serialnum) | ||
2430 | return; /* binding unresolved */ | ||
2431 | |||
2432 | struct dircache_entry *ce = get_entry(infop->dcfile.idx); | ||
2433 | if (!ce) | ||
2434 | { | ||
2435 | logf(" bad index %d", infop->dcfile.idx); | ||
2436 | return; /* a root (should never be called for this) */ | ||
2437 | } | ||
2438 | |||
2439 | #ifdef DIRCACHE_NATIVE | ||
2440 | ce->firstcluster = infop->fatfile.firstcluster; | ||
2441 | ce->wrtdate = dinp->wrtdate; | ||
2442 | ce->wrttime = dinp->wrttime; | ||
2443 | #else | ||
2444 | ce->mtime = dinp->mtime; | ||
2445 | #endif | ||
2446 | ce->attr = dinp->attr; | ||
2447 | if (!(dinp->attr & ATTR_DIRECTORY)) | ||
2448 | ce->filesize = dinp->size; | ||
1195 | } | 2449 | } |
1196 | 2450 | ||
1197 | /* | 2451 | |
1198 | * build a path from an entry upto the root using recursion | 2452 | /** Dircache paths and files **/ |
1199 | * | 2453 | |
1200 | * it appends '/' after strlcat, therefore buf[0] needs to be prepared with '/' | 2454 | #ifdef DIRCACHE_DUMPSTER |
1201 | * and it will leave a trailing '/' | 2455 | /* helper for dircache_get_path() */ |
1202 | * | 2456 | static ssize_t get_path_sub(int idx, char *buf, size_t size) |
1203 | * returns the position of that trailing '/' so it can be deleted afterwards | ||
1204 | * (or, in case of truncation, the position of the nul byte */ | ||
1205 | static size_t copy_path_helper(const struct dircache_entry *entry, char *buf, size_t size) | ||
1206 | { | 2457 | { |
1207 | int offset = 1; | 2458 | if (idx == 0) |
1208 | /* has parent? */ | 2459 | return -2; /* entry is an orphan split from any root */ |
1209 | if (entry->up) | ||
1210 | offset += copy_path_helper(entry->up, buf, size); | ||
1211 | 2460 | ||
1212 | size_t len = strlcpy(buf+offset, entry->d_name, size - offset) + offset; | 2461 | ssize_t offset; |
1213 | if (len < size) | 2462 | char *cename; |
2463 | |||
2464 | if (idx > 0) | ||
1214 | { | 2465 | { |
1215 | buf[len++] = '/'; | 2466 | /* go all the way up then move back down from the root */ |
1216 | buf[len] = '\0'; | 2467 | struct dircache_entry *ce = get_entry(idx); |
2468 | offset = get_path_sub(ce->up, buf, size) - 1; | ||
2469 | if (offset < 0) | ||
2470 | return -3; | ||
2471 | |||
2472 | cename = alloca(MAX_NAME + 1); | ||
2473 | entry_name_copy(cename, ce); | ||
1217 | } | 2474 | } |
1218 | return len-1; | 2475 | else /* idx < 0 */ |
2476 | { | ||
2477 | offset = 0; | ||
2478 | cename = ""; | ||
2479 | |||
2480 | #ifdef HAVE_MULTIVOLUME | ||
2481 | int volume = IF_MV_VOL(-idx - 1); | ||
2482 | if (volume > 0) | ||
2483 | { | ||
2484 | /* prepend the volume specifier for volumes > 0 */ | ||
2485 | cename = alloca(VOL_MAX_LEN+1); | ||
2486 | get_volume_name(volume, cename); | ||
2487 | } | ||
2488 | #endif /* HAVE_MULTIVOLUME */ | ||
2489 | } | ||
2490 | |||
2491 | return offset + path_append(buf + offset, PA_SEP_HARD, cename, | ||
2492 | size > (size_t)offset ? size - offset : 0); | ||
1219 | } | 2493 | } |
2494 | #endif /* DIRCACHE_DUMPSTER */ | ||
2495 | |||
2496 | #if 0 | ||
2497 | |||
1220 | /** | 2498 | /** |
1221 | * Function to copy the full absolute path from dircache to the given buffer | 2499 | * retrieve and validate the file's entry/binding serial number |
1222 | * using the given dircache_entry pointer. | 2500 | * the dircache file's serial number must match the indexed entry's or the |
1223 | * | 2501 | * file reference is stale |
1224 | * Returns the size of the resulting string, or 0 if an error occured | ||
1225 | */ | 2502 | */ |
1226 | size_t dircache_copy_path(int index, char *buf, size_t size) | 2503 | static dc_serial_t get_file_serialnum(const struct dircache_file *dcfilep) |
1227 | { | 2504 | { |
1228 | if (!size || !buf || index < 0) | 2505 | int idx = dcfilep->idx; |
2506 | |||
2507 | if (idx == 0 || idx < -NUM_VOLUMES) | ||
1229 | return 0; | 2508 | return 0; |
1230 | 2509 | ||
1231 | buf[0] = '/'; | 2510 | dc_serial_t serialnum; |
1232 | size_t res = copy_path_helper(&dircache_root[index], buf, size - 1); | 2511 | |
1233 | /* fixup trailing '/' */ | 2512 | if (idx > 0) |
1234 | buf[res] = '\0'; | 2513 | { |
1235 | return res; | 2514 | struct dircache_entry *ce = get_entry(idx); |
2515 | serialnum = ce ? ce->serialnum : 0; | ||
2516 | } | ||
2517 | else | ||
2518 | { | ||
2519 | serialnum = get_idx_dcvolp(idx)->serialnum; | ||
2520 | } | ||
2521 | |||
2522 | return serialnum == dcfilep->serialnum ? serialnum : 0; | ||
1236 | } | 2523 | } |
1237 | 2524 | ||
1238 | /* --- Directory cache live updating functions --- */ | 2525 | /** |
1239 | static int block_until_ready(void) | 2526 | * usermode function to construct a full absolute path from dircache into the |
2527 | * given buffer given the dircache file info | ||
2528 | * | ||
2529 | * returns: | ||
2530 | * success - the length of the string, not including the trailing null | ||
2531 | * failure - a negative value | ||
2532 | * | ||
2533 | * successful return value is as strlcpy() | ||
2534 | * | ||
2535 | * errors: | ||
2536 | * ENOENT - the file or directory does not exist | ||
2537 | */ | ||
2538 | ssize_t dircache_get_path(const struct dircache_file *dcfilep, char *buf, | ||
2539 | size_t size) | ||
1240 | { | 2540 | { |
1241 | /* Block until dircache has been built. */ | 2541 | /* if missing buffer space, still return what's needed a la strlcpy */ |
1242 | while (!dircache_initialized && dircache_is_initializing()) | 2542 | if (!buf) |
1243 | sleep(1); | 2543 | size = 0; |
1244 | 2544 | else if (size) | |
1245 | if (!dircache_initialized) | 2545 | *buf = '\0'; |
1246 | return -1; | 2546 | |
1247 | 2547 | ssize_t len = -1; | |
1248 | return 0; | 2548 | |
2549 | dircache_lock(); | ||
2550 | |||
2551 | /* first and foremost, there must be a cache and the serial number must | ||
2552 | check out */ | ||
2553 | if (dircache_runinfo.handle && get_file_serialnum(dcfilep)) | ||
2554 | len = get_path_sub(dcfilep->idx, buf, size); | ||
2555 | |||
2556 | if (len < 0) | ||
2557 | errno = ENOENT; | ||
2558 | |||
2559 | dircache_unlock(); | ||
2560 | return len; | ||
1249 | } | 2561 | } |
1250 | 2562 | ||
1251 | static struct dircache_entry* dircache_new_entry(const char *path, int attribute) | 2563 | /** |
1252 | { | 2564 | * searches the sublist starting at 'idx' for the named component |
1253 | struct dircache_entry *entry; | 2565 | */ |
1254 | char basedir[MAX_PATH*2]; | ||
1255 | char *new; | ||
1256 | long last_cache_size = dircache_size; | ||
1257 | 2566 | ||
1258 | strlcpy(basedir, path, sizeof(basedir)); | 2567 | /* helper for get_file_sub() */ |
1259 | new = strrchr(basedir, '/'); | 2568 | static struct dircache_entry * |
1260 | if (new == NULL) | 2569 | get_file_sub_scan(int idx, const char *name, size_t length, int *idxp) |
2570 | { | ||
2571 | struct dircache_entry *ce = get_entry(idx); | ||
2572 | if (ce) | ||
1261 | { | 2573 | { |
1262 | logf("error occurred"); | 2574 | char entname[MAX_NAME+1]; |
1263 | dircache_initialized = false; | 2575 | name = strmemdupa(name, length); |
1264 | return NULL; | 2576 | |
2577 | do | ||
2578 | { | ||
2579 | entry_name_copy(entname, ce); | ||
2580 | if (!strcasecmp(entname, name)) | ||
2581 | { | ||
2582 | *idxp = idx; | ||
2583 | break; | ||
2584 | } | ||
2585 | |||
2586 | idx = ce->next; | ||
2587 | } | ||
2588 | while ((ce = get_entry(idx))); | ||
1265 | } | 2589 | } |
1266 | 2590 | ||
1267 | *new = '\0'; | 2591 | return ce; |
1268 | new++; | 2592 | } |
2593 | |||
2594 | /** | ||
2595 | * searches for the subcomponent of *pathp | ||
2596 | */ | ||
2597 | |||
2598 | /* helper for dircache_get_file() */ | ||
2599 | static int get_file_sub(const char **pathp, int *downp, int *idxp) | ||
2600 | { | ||
2601 | int rc; | ||
2602 | const char *name; | ||
2603 | rc = parse_path_component(pathp, &name, false); | ||
2604 | if (rc <= 0) | ||
2605 | return rc; | ||
2606 | else if (rc >= MAX_PATH) | ||
2607 | return ENAMETOOLONG; /* that's just unpossible, man */ | ||
2608 | |||
2609 | struct dircache_entry *ce = get_file_sub_scan(*downp, name, rc, idxp); | ||
1269 | 2610 | ||
1270 | entry = dircache_get_entry(basedir, true); | 2611 | if (!ce) |
1271 | if (entry == NULL) | 2612 | rc = RC_NOT_FOUND; /* not there; tellibry solly */ |
2613 | else if (!*pathp) | ||
2614 | rc = RC_PATH_ENDED; /* done */ | ||
2615 | else if (!(ce->attr & ATTR_DIRECTORY)) | ||
2616 | rc = ENOTDIR; /* a parent component must be a directory */ | ||
2617 | else | ||
2618 | while ((rc = get_file_sub(pathp, &ce->down, idxp)) == RC_CONTINUE); | ||
2619 | |||
2620 | switch (rc) | ||
1272 | { | 2621 | { |
1273 | logf("basedir not found!"); | 2622 | case RC_GO_UP: /* hit ".."; drop to previous level */ |
1274 | logf("%s", basedir); | 2623 | return RC_CONTINUE; |
1275 | dircache_initialized = false; | 2624 | case RC_PATH_ENDED: /* success! */ |
1276 | return NULL; | 2625 | return RC_FOUND; |
2626 | default: /* component not found or error */ | ||
2627 | return rc; | ||
1277 | } | 2628 | } |
2629 | } | ||
1278 | 2630 | ||
1279 | if (reserve_used + 2*sizeof(struct dircache_entry) + strlen(new)+1 | 2631 | /** |
1280 | >= DIRCACHE_RESERVE) | 2632 | * usermode function to return dircache file info for the given path |
2633 | * | ||
2634 | * returns: | ||
2635 | * success: the volume number that is specified for the file | ||
2636 | * failure: a negative value | ||
2637 | * | ||
2638 | * errors: | ||
2639 | * ENOENT - the file or directory does not exist or path is empty | ||
2640 | * ENAMETOOLONG - a component of the path is too long | ||
2641 | * ENOTDIR - a component of the path is not a directory | ||
2642 | */ | ||
2643 | int dircache_get_file(const char *path, struct dircache_file *dcfilep) | ||
2644 | { | ||
2645 | if (!path_is_absolute(path) || !dcfilep) | ||
1281 | { | 2646 | { |
1282 | logf("not enough space"); | 2647 | errno = ENOENT; |
1283 | dircache_initialized = false; | 2648 | return -1; |
1284 | return NULL; | ||
1285 | } | 2649 | } |
1286 | |||
1287 | while (entry->next != NULL) | ||
1288 | entry = entry->next; | ||
1289 | 2650 | ||
1290 | if (entry->d_name != NULL) | 2651 | dircache_lock(); |
2652 | |||
2653 | if (!dircache_runinfo.handle) | ||
1291 | { | 2654 | { |
1292 | entry = dircache_gen_next(entry); | 2655 | dircache_unlock(); |
1293 | if (entry == NULL) | 2656 | errno = ENOENT; |
2657 | return -2; | ||
2658 | } | ||
2659 | |||
2660 | int volume = 0; | ||
2661 | int idx = 0; | ||
2662 | dc_serial_t serialnum = 0; | ||
2663 | struct dircache_volume *dcvolp = NULL; | ||
2664 | struct dircache_entry *ce = NULL; | ||
2665 | |||
2666 | int rc = RC_GO_UP; | ||
2667 | |||
2668 | while (rc == RC_CONTINUE || rc == RC_GO_UP) | ||
2669 | { | ||
2670 | #ifdef HAVE_MULTIVOLUME | ||
2671 | if (rc == RC_GO_UP) | ||
1294 | { | 2672 | { |
1295 | dircache_initialized = false; | 2673 | volume = path_strip_volume(path, &path, false); |
1296 | return NULL; | 2674 | if (!CHECK_VOL(volume)) |
2675 | { | ||
2676 | rc = ENXIO; | ||
2677 | break; | ||
2678 | } | ||
1297 | } | 2679 | } |
1298 | } | 2680 | #endif /* HAVE_MULTIVOLUME */ |
2681 | |||
2682 | dcvolp = DCVOL(volume); | ||
1299 | 2683 | ||
1300 | size_t size = strlen(new) + 1; | 2684 | int *downp = &dcvolp->root_down; |
1301 | entry->d_name = (d_names_start -= size); | 2685 | if (*downp <= 0) |
1302 | entry->startcluster = 0; | 2686 | { |
1303 | memset(&entry->info, 0, sizeof(entry->info)); | 2687 | rc = ENXIO; |
1304 | entry->info.attribute = attribute; | 2688 | break; |
2689 | } | ||
1305 | 2690 | ||
1306 | strcpy(entry->d_name, new); | 2691 | rc = get_file_sub(&path, downp, &idx); |
1307 | dircache_size += size; | 2692 | } |
1308 | 2693 | ||
1309 | if (attribute & ATTR_DIRECTORY) | 2694 | switch (rc) |
1310 | { | 2695 | { |
1311 | logf("gen_down"); | 2696 | case RC_FOUND: /* hit: component found */ |
1312 | dircache_gen_down(entry); | 2697 | serialnum = ce->serialnum; |
2698 | rc = volume; | ||
2699 | break; | ||
2700 | case RC_PATH_ENDED: /* hit: it's a root (volume or system) */ | ||
2701 | idx = -volume - 1; | ||
2702 | serialnum = dcvolp->serialnum; | ||
2703 | rc = volume; | ||
2704 | break; | ||
2705 | case RC_NOT_FOUND: /* miss */ | ||
2706 | rc = ENOENT; | ||
2707 | default: | ||
2708 | idx = 0; | ||
2709 | errno = rc; | ||
2710 | rc = -3; | ||
2711 | break; | ||
1313 | } | 2712 | } |
1314 | |||
1315 | reserve_used += dircache_size - last_cache_size; | ||
1316 | 2713 | ||
1317 | return entry; | 2714 | dcfilep->idx = idx; |
2715 | dcfilep->serialnum = serialnum; | ||
2716 | |||
2717 | dircache_unlock(); | ||
2718 | return rc; | ||
1318 | } | 2719 | } |
2720 | #endif /* 0 */ | ||
2721 | |||
1319 | 2722 | ||
1320 | void dircache_bind(int fd, const char *path) | 2723 | /** Debug screen/info stuff **/ |
2724 | |||
2725 | /** | ||
2726 | * return cache state parameters | ||
2727 | */ | ||
2728 | void dircache_get_info(struct dircache_info *info) | ||
1321 | { | 2729 | { |
1322 | struct dircache_entry *entry; | 2730 | static const char * const status_descriptions[] = |
1323 | 2731 | { | |
1324 | /* Queue requests until dircache has been built. */ | 2732 | [DIRCACHE_IDLE] = "Idle", |
1325 | if (!dircache_initialized && dircache_is_initializing()) | 2733 | [DIRCACHE_SCANNING] = "Scanning", |
2734 | [DIRCACHE_READY] = "Ready", | ||
2735 | }; | ||
2736 | |||
2737 | if (!info) | ||
2738 | return; | ||
2739 | |||
2740 | memset(info, 0, sizeof (*info)); | ||
2741 | |||
2742 | dircache_lock(); | ||
2743 | |||
2744 | enum dircache_status status = DIRCACHE_IDLE; | ||
2745 | |||
2746 | for (unsigned int volume = 0; volume < NUM_VOLUMES; volume++) | ||
1326 | { | 2747 | { |
1327 | if (fdbind_idx >= MAX_PENDING_BINDINGS) | 2748 | struct dircache_volume *dcvolp = DCVOL(volume); |
1328 | return ; | 2749 | enum dircache_status volstatus = dcvolp->status; |
1329 | strlcpy(fdbind_cache[fdbind_idx].path, path, | 2750 | |
1330 | sizeof(fdbind_cache[fdbind_idx].path)); | 2751 | switch (volstatus) |
1331 | fdbind_cache[fdbind_idx].fd = fd; | 2752 | { |
1332 | fdbind_idx++; | 2753 | case DIRCACHE_SCANNING: |
1333 | return ; | 2754 | /* if any one is scanning then overall status is "scanning" */ |
2755 | status = volstatus; | ||
2756 | |||
2757 | /* sum the time the scanning has taken so far */ | ||
2758 | info->build_ticks += current_tick - dcvolp->start_tick; | ||
2759 | break; | ||
2760 | case DIRCACHE_READY: | ||
2761 | /* if all the rest are idle and at least one is ready, then | ||
2762 | status is "ready". */ | ||
2763 | if (status == DIRCACHE_IDLE) | ||
2764 | status = DIRCACHE_READY; | ||
2765 | |||
2766 | /* sum the build ticks of all "ready" volumes */ | ||
2767 | info->build_ticks += dcvolp->build_ticks; | ||
2768 | break; | ||
2769 | case DIRCACHE_IDLE: | ||
2770 | /* if all are idle; then the whole cache is "idle" */ | ||
2771 | break; | ||
2772 | } | ||
1334 | } | 2773 | } |
1335 | |||
1336 | if (!dircache_initialized) | ||
1337 | return ; | ||
1338 | 2774 | ||
1339 | logf("bind: %d/%s", fd, path); | 2775 | info->status = status; |
1340 | entry = dircache_get_entry(path, false); | 2776 | info->statusdesc = status_descriptions[status]; |
1341 | if (entry == NULL) | 2777 | info->last_size = dircache.last_size; |
2778 | info->size_limit = DIRCACHE_LIMIT; | ||
2779 | info->reserve = DIRCACHE_RESERVE; | ||
2780 | |||
2781 | /* report usage only if there is something ready or being built */ | ||
2782 | if (status != DIRCACHE_IDLE) | ||
1342 | { | 2783 | { |
1343 | logf("not found!"); | 2784 | info->reserve_used = reserve_buf_used(); |
1344 | dircache_initialized = false; | 2785 | info->size = dircache.size; |
1345 | return ; | 2786 | info->sizeused = dircache.sizeused; |
2787 | info->entry_count = dircache.numentries; | ||
1346 | } | 2788 | } |
1347 | 2789 | ||
1348 | fd_bindings[fd] = entry; | 2790 | dircache_unlock(); |
1349 | } | 2791 | } |
1350 | 2792 | ||
1351 | void dircache_update_filesize(int fd, long newsize, long startcluster) | 2793 | #ifdef DIRCACHE_DUMPSTER |
2794 | /** | ||
2795 | * dump RAW binary of buffer and CSV of all valid paths and volumes to disk | ||
2796 | */ | ||
2797 | void dircache_dump(void) | ||
1352 | { | 2798 | { |
1353 | if (!dircache_initialized || fd < 0) | 2799 | /* open both now so they're in the cache */ |
1354 | return ; | 2800 | int fdbin = open(DIRCACHE_DUMPSTER_BIN, O_WRONLY|O_CREAT|O_TRUNC, 0666); |
1355 | 2801 | int fdcsv = open(DIRCACHE_DUMPSTER_CSV, O_WRONLY|O_CREAT|O_TRUNC, 0666); | |
1356 | if (fd_bindings[fd] == NULL) | 2802 | if (fdbin < 0 || fdcsv < 0) |
1357 | { | 2803 | { |
1358 | logf("dircache fd(%d) access error", fd); | 2804 | close(fdbin); |
1359 | dircache_initialized = false; | 2805 | return; |
1360 | return ; | ||
1361 | } | 2806 | } |
1362 | |||
1363 | fd_bindings[fd]->info.size = newsize; | ||
1364 | fd_bindings[fd]->startcluster = startcluster; | ||
1365 | } | ||
1366 | void dircache_update_filetime(int fd) | ||
1367 | { | ||
1368 | #if CONFIG_RTC == 0 | ||
1369 | (void)fd; | ||
1370 | #else | ||
1371 | short year; | ||
1372 | struct tm *now = get_time(); | ||
1373 | if (!dircache_initialized || fd < 0) | ||
1374 | return ; | ||
1375 | |||
1376 | if (fd_bindings[fd] == NULL) | ||
1377 | { | ||
1378 | logf("dircache fd access error"); | ||
1379 | dircache_initialized = false; | ||
1380 | return ; | ||
1381 | } | ||
1382 | year = now->tm_year+1900-1980; | ||
1383 | fd_bindings[fd]->info.wrtdate = (((year)&0x7f)<<9) | | ||
1384 | (((now->tm_mon+1)&0xf)<<5) | | ||
1385 | (((now->tm_mday)&0x1f)); | ||
1386 | fd_bindings[fd]->info.wrttime = (((now->tm_hour)&0x1f)<<11) | | ||
1387 | (((now->tm_min)&0x3f)<<5) | | ||
1388 | (((now->tm_sec/2)&0x1f)); | ||
1389 | #endif | ||
1390 | } | ||
1391 | 2807 | ||
1392 | void dircache_mkdir(const char *path) | 2808 | trigger_cpu_boost(); |
1393 | { /* Test ok. */ | 2809 | dircache_lock(); |
1394 | if (block_until_ready()) | 2810 | |
1395 | return ; | 2811 | if (dircache_runinfo.handle) |
1396 | 2812 | { | |
1397 | 2813 | buffer_lock(); | |
1398 | logf("mkdir: %s", path); | 2814 | |
1399 | dircache_new_entry(path, ATTR_DIRECTORY); | 2815 | /* bin */ |
1400 | } | 2816 | write(fdbin, dircache_runinfo.p + ENTRYSIZE, |
1401 | 2817 | dircache_runinfo.bufsize + 1); | |
1402 | void dircache_rmdir(const char *path) | 2818 | |
1403 | { /* Test ok. */ | 2819 | /* CSV */ |
1404 | struct dircache_entry *entry; | 2820 | fdprintf(fdcsv, "\"Index\",\"Serialnum\"," |
1405 | 2821 | "\"Path\",\"Frontier\"," | |
1406 | if (block_until_ready()) | 2822 | "\"Attribute\",\"File Size\"," |
1407 | return ; | 2823 | "\"Mod Date\",\"Mod Time\"\n"); |
1408 | 2824 | ||
1409 | logf("rmdir: %s", path); | 2825 | FOR_EACH_VOLUME(-1, volume) |
1410 | entry = dircache_get_entry(path, false); | ||
1411 | if (entry == NULL || entry->down == NULL) | ||
1412 | { | ||
1413 | logf("not found or not a directory!"); | ||
1414 | dircache_initialized = false; | ||
1415 | return ; | ||
1416 | } | ||
1417 | |||
1418 | entry->down = NULL; | ||
1419 | entry->d_name = NULL; | ||
1420 | } | ||
1421 | |||
1422 | /* Remove a file from cache */ | ||
1423 | void dircache_remove(const char *name) | ||
1424 | { /* Test ok. */ | ||
1425 | struct dircache_entry *entry; | ||
1426 | |||
1427 | if (block_until_ready()) | ||
1428 | return ; | ||
1429 | |||
1430 | logf("remove: %s", name); | ||
1431 | |||
1432 | entry = dircache_get_entry(name, false); | ||
1433 | |||
1434 | if (entry == NULL) | ||
1435 | { | ||
1436 | logf("not found!"); | ||
1437 | dircache_initialized = false; | ||
1438 | return ; | ||
1439 | } | ||
1440 | |||
1441 | entry->d_name = NULL; | ||
1442 | } | ||
1443 | |||
1444 | void dircache_rename(const char *oldpath, const char *newpath) | ||
1445 | { /* Test ok. */ | ||
1446 | struct dircache_entry *entry, *newentry; | ||
1447 | struct dircache_entry oldentry; | ||
1448 | char absolute_path[MAX_PATH*2]; | ||
1449 | char *p; | ||
1450 | |||
1451 | if (block_until_ready()) | ||
1452 | return ; | ||
1453 | |||
1454 | logf("rename: %s->%s", oldpath, newpath); | ||
1455 | |||
1456 | entry = dircache_get_entry(oldpath, false); | ||
1457 | if (entry == NULL) | ||
1458 | { | ||
1459 | logf("not found!"); | ||
1460 | dircache_initialized = false; | ||
1461 | return ; | ||
1462 | } | ||
1463 | |||
1464 | /* Delete the old entry. */ | ||
1465 | entry->d_name = NULL; | ||
1466 | |||
1467 | /** If we rename the same filename twice in a row, we need to | ||
1468 | * save the data, because the entry will be re-used. */ | ||
1469 | oldentry = *entry; | ||
1470 | |||
1471 | /* Generate the absolute path for destination if necessary. */ | ||
1472 | if (newpath[0] != '/') | ||
1473 | { | ||
1474 | strlcpy(absolute_path, oldpath, sizeof(absolute_path)); | ||
1475 | p = strrchr(absolute_path, '/'); | ||
1476 | if (!p) | ||
1477 | { | 2826 | { |
1478 | logf("Invalid path"); | 2827 | struct dircache_volume *dcvolp = DCVOL(volume); |
1479 | dircache_initialized = false; | 2828 | if (dcvolp->status == DIRCACHE_IDLE) |
1480 | return ; | 2829 | continue; |
2830 | |||
2831 | #ifdef HAVE_MULTIVOLUME | ||
2832 | char name[VOL_MAX_LEN+1]; | ||
2833 | get_volume_name(volume, name); | ||
2834 | #endif | ||
2835 | fdprintf(fdcsv, | ||
2836 | "%d,%lu," | ||
2837 | "\"%c" IF_MV("%s") "\",%u," | ||
2838 | "0x%08X,0," | ||
2839 | "\"\",\"\"\n", | ||
2840 | -volume-1, dcvolp->serialnum, | ||
2841 | PATH_SEPCH, IF_MV(name,) dcvolp->frontier, | ||
2842 | ATTR_DIRECTORY | ATTR_VOLUME); | ||
1481 | } | 2843 | } |
1482 | 2844 | ||
1483 | *p = '\0'; | 2845 | FOR_EACH_CACHE_ENTRY(ce) |
1484 | strlcpy(p, absolute_path, sizeof(absolute_path)-strlen(p)); | 2846 | { |
1485 | newpath = absolute_path; | 2847 | #ifdef DIRCACHE_NATIVE |
1486 | } | 2848 | time_t mtime = fattime_mktime(ce->wrtdate, ce->wrttime); |
1487 | 2849 | #else | |
1488 | newentry = dircache_new_entry(newpath, entry->info.attribute); | 2850 | time_t mtime = ce->mtime; |
1489 | if (newentry == NULL) | 2851 | #endif |
1490 | { | 2852 | struct tm tm; |
1491 | dircache_initialized = false; | 2853 | gmtime_r(&mtime, &tm); |
1492 | return ; | 2854 | |
2855 | char buf[DC_MAX_NAME + 2]; | ||
2856 | *buf = '\0'; | ||
2857 | int idx = get_index(ce); | ||
2858 | get_path_sub(idx, buf, sizeof (buf)); | ||
2859 | |||
2860 | fdprintf(fdcsv, | ||
2861 | "%d,%lu," | ||
2862 | "\"%s\",%u," | ||
2863 | "0x%08X,%lu," | ||
2864 | "%04d/%02d/%02d," | ||
2865 | "%02d:%02d:%02d\n", | ||
2866 | idx, ce->serialnum, | ||
2867 | buf, ce->frontier, | ||
2868 | ce->attr, (ce->attr & ATTR_DIRECTORY) ? | ||
2869 | 0ul : (unsigned long)ce->filesize, | ||
2870 | tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, | ||
2871 | tm.tm_hour, tm.tm_min, tm.tm_sec); | ||
2872 | } | ||
2873 | |||
2874 | buffer_unlock(); | ||
1493 | } | 2875 | } |
1494 | 2876 | ||
1495 | newentry->down = oldentry.down; | 2877 | dircache_unlock(); |
1496 | newentry->startcluster = oldentry.startcluster; | 2878 | cancel_cpu_boost(); |
1497 | newentry->info.size = oldentry.info.size; | 2879 | |
1498 | newentry->info.wrtdate = oldentry.info.wrtdate; | 2880 | close(fdbin); |
1499 | newentry->info.wrttime = oldentry.info.wrttime; | 2881 | close(fdcsv); |
1500 | } | 2882 | } |
2883 | #endif /* DIRCACHE_DUMPSTER */ | ||
2884 | |||
1501 | 2885 | ||
1502 | void dircache_add_file(const char *path, long startcluster) | 2886 | /** Misc. stuff **/ |
2887 | |||
2888 | /** | ||
2889 | * set the dircache file to initial values | ||
2890 | */ | ||
2891 | void dircache_dcfile_init(struct dircache_file *dcfilep) | ||
1503 | { | 2892 | { |
1504 | struct dircache_entry *entry; | 2893 | dcfilep->idx = 0; |
1505 | 2894 | dcfilep->serialnum = 0; | |
1506 | if (block_until_ready()) | ||
1507 | return ; | ||
1508 | |||
1509 | logf("add file: %s", path); | ||
1510 | entry = dircache_new_entry(path, 0); | ||
1511 | if (entry == NULL) | ||
1512 | return ; | ||
1513 | |||
1514 | entry->startcluster = startcluster; | ||
1515 | } | 2895 | } |
1516 | 2896 | ||
1517 | static bool is_disable_msg_pending(void) | 2897 | #ifdef HAVE_EEPROM_SETTINGS |
2898 | |||
2899 | #ifdef HAVE_HOTSWAP | ||
2900 | /* NOTE: This is hazardous to the filesystem of any sort of removable | ||
2901 | storage unless it may be determined that the filesystem from save | ||
2902 | to load is identical. If it's not possible to do so in a timely | ||
2903 | manner, it's not worth persisting the cache. */ | ||
2904 | #warning "Don't do this; you'll find the consequences unpleasant." | ||
2905 | #endif | ||
2906 | |||
2907 | /* dircache persistence file header magic */ | ||
2908 | #define DIRCACHE_MAGIC 0x00d0c0a1 | ||
2909 | |||
2910 | /* dircache persistence file header */ | ||
2911 | struct dircache_maindata | ||
1518 | { | 2912 | { |
1519 | return check_event_queue(); | 2913 | uint32_t magic; /* DIRCACHE_MAGIC */ |
2914 | struct dircache dircache; /* metadata of the cache! */ | ||
2915 | uint32_t datacrc; /* CRC32 of data */ | ||
2916 | uint32_t hdrcrc; /* CRC32 of header through datacrc */ | ||
2917 | } __attribute__((packed, aligned (4))); | ||
2918 | |||
2919 | /** | ||
2920 | * verify that the clean status is A-ok | ||
2921 | */ | ||
2922 | static bool dircache_is_clean(bool saving) | ||
2923 | { | ||
2924 | if (saving) | ||
2925 | return dircache.dcvol[0].status == DIRCACHE_READY; | ||
2926 | else | ||
2927 | { | ||
2928 | return dircache.dcvol[0].status == DIRCACHE_IDLE && | ||
2929 | !dircache_runinfo.enabled; | ||
2930 | } | ||
1520 | } | 2931 | } |
1521 | 2932 | ||
1522 | DIR_CACHED* opendir_cached(const char* name) | 2933 | /** |
2934 | * function to load the internal cache structure from disk to initialize | ||
2935 | * the dircache really fast with little disk access. | ||
2936 | */ | ||
2937 | int dircache_load(void) | ||
1523 | { | 2938 | { |
1524 | int dd; | 2939 | logf("Loading directory cache"); |
1525 | DIR_CACHED* pdir = opendirs; | 2940 | int fd = open_dircache_file(O_RDONLY); |
2941 | if (fd < 0) | ||
2942 | return -1; | ||
2943 | |||
2944 | int rc = -1; | ||
2945 | |||
2946 | ssize_t size; | ||
2947 | struct dircache_maindata maindata; | ||
2948 | uint32_t crc; | ||
2949 | int handle = 0; | ||
2950 | bool hasbuffer = false; | ||
1526 | 2951 | ||
1527 | if ( name[0] != '/' ) | 2952 | size = sizeof (maindata); |
2953 | if (read(fd, &maindata, size) != size) | ||
1528 | { | 2954 | { |
1529 | DEBUGF("Only absolute paths supported right now\n"); | 2955 | logf("dircache: header read failed"); |
1530 | return NULL; | 2956 | goto error_nolock; |
1531 | } | 2957 | } |
1532 | 2958 | ||
1533 | /* find a free dir descriptor */ | 2959 | /* sanity check the header */ |
1534 | for ( dd=0; dd<MAX_OPEN_DIRS; dd++, pdir++) | 2960 | if (maindata.magic != DIRCACHE_MAGIC) |
1535 | if ( !pdir->busy ) | 2961 | { |
1536 | break; | 2962 | logf("dircache: invalid header magic"); |
2963 | goto error_nolock; | ||
2964 | } | ||
1537 | 2965 | ||
1538 | if ( dd == MAX_OPEN_DIRS ) | 2966 | crc = crc_32(&maindata, offsetof(struct dircache_maindata, hdrcrc), |
2967 | 0xffffffff); | ||
2968 | if (crc != maindata.hdrcrc) | ||
1539 | { | 2969 | { |
1540 | DEBUGF("Too many dirs open\n"); | 2970 | logf("dircache: invalid header CRC32"); |
1541 | errno = EMFILE; | 2971 | goto error_nolock; |
1542 | return NULL; | ||
1543 | } | 2972 | } |
1544 | |||
1545 | pdir->busy = true; | ||
1546 | 2973 | ||
1547 | if (!dircache_initialized || is_disable_msg_pending()) | 2974 | if (maindata.dircache.size != |
2975 | maindata.dircache.sizeentries + maindata.dircache.sizenames || | ||
2976 | ALIGN_DOWN(maindata.dircache.size, ENTRYSIZE) != maindata.dircache.size || | ||
2977 | filesize(fd) - sizeof (maindata) != maindata.dircache.size) | ||
1548 | { | 2978 | { |
1549 | pdir->internal_entry = -1; | 2979 | logf("dircache: file header error"); |
1550 | pdir->regulardir = opendir_uncached(name); | 2980 | goto error_nolock; |
1551 | } | 2981 | } |
1552 | else | 2982 | |
2983 | /* allocate so that exactly the reserve size remains */ | ||
2984 | size_t bufsize = maindata.dircache.size + DIRCACHE_RESERVE + 1; | ||
2985 | handle = alloc_cache(bufsize); | ||
2986 | if (handle <= 0) | ||
2987 | { | ||
2988 | logf("dircache: failed load allocation"); | ||
2989 | goto error_nolock; | ||
2990 | } | ||
2991 | |||
2992 | dircache_lock(); | ||
2993 | buffer_lock(); | ||
2994 | |||
2995 | if (!dircache_is_clean(false)) | ||
2996 | goto error; | ||
2997 | |||
2998 | /* from this point on, we're actually dealing with the cache in RAM */ | ||
2999 | dircache = maindata.dircache; | ||
3000 | |||
3001 | set_buffer(handle, bufsize); | ||
3002 | hasbuffer = true; | ||
3003 | |||
3004 | /* convert back to in-RAM representation */ | ||
3005 | dircache.numentries = maindata.dircache.sizeentries / ENTRYSIZE; | ||
3006 | |||
3007 | /* read the dircache file into memory; start with the entries */ | ||
3008 | size = maindata.dircache.sizeentries; | ||
3009 | if (read(fd, dircache_runinfo.pentry + 1, size) != size) | ||
1553 | { | 3010 | { |
1554 | pdir->regulardir = NULL; | 3011 | logf("dircache read failed #1"); |
1555 | pdir->internal_entry = dircache_get_entry_id_ex(name, true); | 3012 | goto error; |
1556 | pdir->theent.info.attribute = -1; /* used to make readdir_cached aware of the first call */ | ||
1557 | } | 3013 | } |
1558 | 3014 | ||
1559 | if (pdir->internal_entry == -1 && pdir->regulardir == NULL) | 3015 | crc = crc_32(dircache_runinfo.pentry + 1, size, 0xffffffff); |
3016 | |||
3017 | /* continue with the names; fix up indexes to them if needed */ | ||
3018 | dircache.names -= maindata.dircache.sizenames; | ||
3019 | *get_name(dircache.names - 1) = 0; | ||
3020 | |||
3021 | size = maindata.dircache.sizenames; | ||
3022 | if (read(fd, get_name(dircache.names), size) != size) | ||
1560 | { | 3023 | { |
1561 | pdir->busy = false; | 3024 | logf("dircache read failed #2"); |
1562 | return NULL; | 3025 | goto error; |
1563 | } | 3026 | } |
1564 | 3027 | ||
1565 | return pdir; | 3028 | crc = crc_32(get_name(dircache.names), size, crc); |
1566 | } | 3029 | if (crc != maindata.datacrc) |
3030 | { | ||
3031 | logf("dircache: data failed CRC32"); | ||
3032 | goto error; | ||
3033 | } | ||
1567 | 3034 | ||
1568 | struct dirent_cached* readdir_cached(DIR_CACHED* dir) | 3035 | /* only names will be changed in relative position so fix up those |
1569 | { | 3036 | references */ |
1570 | struct dircache_entry *ce = get_entry(dir->internal_entry); | 3037 | ssize_t offset = dircache.names - maindata.dircache.names; |
1571 | struct dirent_uncached *regentry; | 3038 | if (offset != 0) |
1572 | 3039 | { | |
1573 | if (!dir->busy) | 3040 | /* nothing should be open besides the dircache file itself therefore |
1574 | return NULL; | 3041 | no bindings need be resolved; the cache will have its own entry |
3042 | but that should get cleaned up when removing the file */ | ||
3043 | FOR_EACH_CACHE_ENTRY(ce) | ||
3044 | { | ||
3045 | if (!ce->tinyname) | ||
3046 | ce->name += offset; | ||
3047 | } | ||
3048 | } | ||
1575 | 3049 | ||
1576 | if (dir->regulardir != NULL) | 3050 | dircache.reserve_used = 0; |
1577 | { | 3051 | |
1578 | regentry = readdir_uncached(dir->regulardir); | 3052 | /* enable the cache but do not try to build it */ |
1579 | if (regentry == NULL) | 3053 | dircache_enable_internal(false); |
1580 | return NULL; | 3054 | |
1581 | 3055 | /* cache successfully loaded */ | |
1582 | strlcpy(dir->theent.d_name, regentry->d_name, MAX_PATH); | 3056 | logf("Done, %ld KiB used", dircache.size / 1024); |
1583 | dir->theent.startcluster = regentry->startcluster; | 3057 | rc = 0; |
1584 | dir->theent.info = regentry->info; | 3058 | error: |
1585 | 3059 | if (rc < 0 && hasbuffer) | |
1586 | return &dir->theent; | 3060 | reset_buffer(); |
1587 | } | ||
1588 | |||
1589 | /* if theent.attribute=-1 then this is the first call */ | ||
1590 | /* otherwise, this is is not so we first take the entry's ->next */ | ||
1591 | /* NOTE: normal file can't have attribute=-1 */ | ||
1592 | if(dir->theent.info.attribute != -1) | ||
1593 | ce = ce->next; | ||
1594 | /* skip unused entries */ | ||
1595 | while(ce != NULL && ce->d_name == NULL) | ||
1596 | ce = ce->next; | ||
1597 | |||
1598 | if (ce == NULL) | ||
1599 | return NULL; | ||
1600 | 3061 | ||
1601 | strlcpy(dir->theent.d_name, ce->d_name, MAX_PATH); | 3062 | buffer_unlock(); |
1602 | /* Can't do `dir->theent = *ce` | 3063 | dircache_unlock(); |
1603 | because that modifies the d_name pointer. */ | ||
1604 | dir->theent.startcluster = ce->startcluster; | ||
1605 | dir->theent.info = ce->info; | ||
1606 | dir->internal_entry = ce - dircache_root; | ||
1607 | 3064 | ||
1608 | //logf("-> %s", ce->d_name); | 3065 | error_nolock: |
1609 | return &dir->theent; | 3066 | if (rc < 0 && handle > 0) |
3067 | core_free(handle); | ||
3068 | |||
3069 | if (fd >= 0) | ||
3070 | close(fd); | ||
3071 | |||
3072 | remove_dircache_file(); | ||
3073 | return rc; | ||
1610 | } | 3074 | } |
1611 | 3075 | ||
1612 | int closedir_cached(DIR_CACHED* dir) | 3076 | /** |
3077 | * function to save the internal cache stucture to disk for fast loading | ||
3078 | * on boot | ||
3079 | */ | ||
3080 | int dircache_save(void) | ||
1613 | { | 3081 | { |
1614 | if (!dir->busy) | 3082 | logf("Saving directory cache"); |
3083 | |||
3084 | int fd = open_dircache_file(O_WRONLY|O_CREAT|O_TRUNC|O_APPEND); | ||
3085 | if (fd < 0) | ||
1615 | return -1; | 3086 | return -1; |
1616 | |||
1617 | dir->busy=false; | ||
1618 | if (dir->regulardir != NULL) | ||
1619 | return closedir_uncached(dir->regulardir); | ||
1620 | |||
1621 | return 0; | ||
1622 | } | ||
1623 | 3087 | ||
1624 | int mkdir_cached(const char *name) | 3088 | dircache_lock(); |
1625 | { | 3089 | buffer_lock(); |
1626 | int rc=mkdir_uncached(name); | 3090 | |
1627 | if (rc >= 0) | 3091 | int rc = -1; |
1628 | dircache_mkdir(name); | 3092 | |
1629 | return(rc); | 3093 | if (!dircache_is_clean(true)) |
3094 | goto error; | ||
3095 | |||
3096 | /* save the header structure along with the cache metadata */ | ||
3097 | ssize_t size; | ||
3098 | uint32_t crc; | ||
3099 | struct dircache_maindata maindata = | ||
3100 | { | ||
3101 | .magic = DIRCACHE_MAGIC, | ||
3102 | .dircache = dircache, | ||
3103 | }; | ||
3104 | |||
3105 | /* store the size since it better detects an invalid header */ | ||
3106 | maindata.dircache.sizeentries = maindata.dircache.numentries * ENTRYSIZE; | ||
3107 | |||
3108 | /* write the template header */ | ||
3109 | size = sizeof (maindata); | ||
3110 | if (write(fd, &maindata, size) != size) | ||
3111 | { | ||
3112 | logf("dircache: write failed #1"); | ||
3113 | goto error; | ||
3114 | } | ||
3115 | |||
3116 | /* write the dircache entries */ | ||
3117 | size = maindata.dircache.sizeentries; | ||
3118 | if (write(fd, dircache_runinfo.pentry + 1, size) != size) | ||
3119 | { | ||
3120 | logf("dircache: write failed #2"); | ||
3121 | goto error; | ||
3122 | } | ||
3123 | |||
3124 | crc = crc_32(dircache_runinfo.pentry + 1, size, 0xffffffff); | ||
3125 | |||
3126 | /* continue with the names */ | ||
3127 | size = maindata.dircache.sizenames; | ||
3128 | if (write(fd, get_name(dircache.names), size) != size) | ||
3129 | { | ||
3130 | logf("dircache: write failed #3"); | ||
3131 | goto error; | ||
3132 | } | ||
3133 | |||
3134 | crc = crc_32(get_name(dircache.names), size, crc); | ||
3135 | maindata.datacrc = crc; | ||
3136 | |||
3137 | /* rewrite the header with CRC info */ | ||
3138 | if (lseek(fd, 0, SEEK_SET) != 0) | ||
3139 | { | ||
3140 | logf("dircache: seek failed"); | ||
3141 | goto error; | ||
3142 | } | ||
3143 | |||
3144 | crc = crc_32(&maindata, offsetof(struct dircache_maindata, hdrcrc), | ||
3145 | 0xffffffff); | ||
3146 | maindata.hdrcrc = crc; | ||
3147 | |||
3148 | if (write(fd, &maindata, sizeof (maindata)) != sizeof (maindata)) | ||
3149 | { | ||
3150 | logf("dircache: write failed #4"); | ||
3151 | goto error; | ||
3152 | } | ||
3153 | |||
3154 | /* as of now, no changes to the volumes should be allowed at all since | ||
3155 | that makes what was saved completely invalid */ | ||
3156 | rc = 0; | ||
3157 | error: | ||
3158 | buffer_unlock(); | ||
3159 | dircache_unlock(); | ||
3160 | |||
3161 | if (rc < 0) | ||
3162 | remove_dircache_file(); | ||
3163 | |||
3164 | close(fd); | ||
3165 | return rc; | ||
1630 | } | 3166 | } |
3167 | #endif /* HAVE_EEPROM_SETTINGS */ | ||
1631 | 3168 | ||
1632 | int rmdir_cached(const char* name) | 3169 | /** |
3170 | * main one-time initialization function that must be called before any other | ||
3171 | * operations within the dircache | ||
3172 | */ | ||
3173 | void dircache_init(size_t last_size) | ||
1633 | { | 3174 | { |
1634 | int rc=rmdir_uncached(name); | 3175 | queue_init(&dircache_queue, false); |
1635 | if(rc >= 0) | 3176 | |
1636 | dircache_rmdir(name); | 3177 | dircache.last_size = MIN(last_size, DIRCACHE_LIMIT); |
1637 | return(rc); | 3178 | |
3179 | struct dircache_runinfo *dcrip = &dircache_runinfo; | ||
3180 | dcrip->suspended = 1; | ||
3181 | dcrip->thread_done = true; | ||
3182 | dcrip->ops.move_callback = move_callback; | ||
1638 | } | 3183 | } |
diff --git a/firmware/common/disk.c b/firmware/common/disk.c index 5a55a3b6ac..3a2d27e0d7 100644 --- a/firmware/common/disk.c +++ b/firmware/common/disk.c | |||
@@ -19,14 +19,25 @@ | |||
19 | * | 19 | * |
20 | ****************************************************************************/ | 20 | ****************************************************************************/ |
21 | #include <stdio.h> | 21 | #include <stdio.h> |
22 | #include <string.h> | ||
23 | #include "config.h" | ||
22 | #include "kernel.h" | 24 | #include "kernel.h" |
23 | #include "storage.h" | 25 | #include "storage.h" |
24 | #include "debug.h" | 26 | #include "debug.h" |
25 | #include "fat.h" | 27 | #include "disk_cache.h" |
26 | #include "dir.h" /* for release_dirs() */ | 28 | #include "fileobj_mgr.h" |
27 | #include "file.h" /* for release_files() */ | 29 | #include "dir.h" |
30 | #include "dircache_redirect.h" | ||
28 | #include "disk.h" | 31 | #include "disk.h" |
29 | #include <string.h> | 32 | |
33 | #ifndef CONFIG_DEFAULT_PARTNUM | ||
34 | #define CONFIG_DEFAULT_PARTNUM 0 | ||
35 | #endif | ||
36 | |||
37 | #define disk_reader_lock() file_internal_lock_READER() | ||
38 | #define disk_reader_unlock() file_internal_unlock_READER() | ||
39 | #define disk_writer_lock() file_internal_lock_WRITER() | ||
40 | #define disk_writer_unlock() file_internal_unlock_WRITER() | ||
30 | 41 | ||
31 | /* Partition table entry layout: | 42 | /* Partition table entry layout: |
32 | ----------------------- | 43 | ----------------------- |
@@ -42,11 +53,18 @@ | |||
42 | 12-15: nr of sectors in partition | 53 | 12-15: nr of sectors in partition |
43 | */ | 54 | */ |
44 | 55 | ||
45 | #define BYTES2INT32(array,pos) \ | 56 | #define BYTES2INT32(array, pos) \ |
46 | ((long)array[pos] | ((long)array[pos+1] << 8 ) | \ | 57 | (((uint32_t)array[pos+0] << 0) | \ |
47 | ((long)array[pos+2] << 16 ) | ((long)array[pos+3] << 24 )) | 58 | ((uint32_t)array[pos+1] << 8) | \ |
59 | ((uint32_t)array[pos+2] << 16) | \ | ||
60 | ((uint32_t)array[pos+3] << 24)) | ||
61 | |||
62 | #define BYTES2INT16(array, pos) \ | ||
63 | (((uint32_t)array[pos+0] << 0) | \ | ||
64 | ((uint32_t)array[pos+1] << 8)) | ||
48 | 65 | ||
49 | static const unsigned char fat_partition_types[] = { | 66 | static const unsigned char fat_partition_types[] = |
67 | { | ||
50 | 0x0b, 0x1b, /* FAT32 + hidden variant */ | 68 | 0x0b, 0x1b, /* FAT32 + hidden variant */ |
51 | 0x0c, 0x1c, /* FAT32 (LBA) + hidden variant */ | 69 | 0x0c, 0x1c, /* FAT32 (LBA) + hidden variant */ |
52 | #ifdef HAVE_FAT16SUPPORT | 70 | #ifdef HAVE_FAT16SUPPORT |
@@ -56,159 +74,135 @@ static const unsigned char fat_partition_types[] = { | |||
56 | #endif | 74 | #endif |
57 | }; | 75 | }; |
58 | 76 | ||
59 | static struct partinfo part[NUM_DRIVES*4]; /* space for 4 partitions on 2 drives */ | 77 | /* space for 4 partitions on 2 drives */ |
60 | static int vol_drive[NUM_VOLUMES]; /* mounted to which drive (-1 if none) */ | 78 | static struct partinfo part[NUM_DRIVES*4]; |
61 | static struct mutex disk_mutex; | 79 | /* mounted to which drive (-1 if none) */ |
80 | static int vol_drive[NUM_VOLUMES]; | ||
81 | |||
82 | static int get_free_volume(void) | ||
83 | { | ||
84 | for (int i = 0; i < NUM_VOLUMES; i++) | ||
85 | { | ||
86 | if (vol_drive[i] == -1) /* unassigned? */ | ||
87 | return i; | ||
88 | } | ||
89 | |||
90 | return -1; /* none found */ | ||
91 | } | ||
62 | 92 | ||
63 | #ifdef MAX_LOG_SECTOR_SIZE | 93 | #ifdef MAX_LOG_SECTOR_SIZE |
64 | static int disk_sector_multiplier[NUM_DRIVES] = {[0 ... NUM_DRIVES-1] = 1}; | 94 | static int disk_sector_multiplier[NUM_DRIVES] = |
95 | { [0 ... NUM_DRIVES-1] = 1 }; | ||
65 | 96 | ||
66 | int disk_get_sector_multiplier(IF_MD_NONVOID(int drive)) | 97 | int disk_get_sector_multiplier(IF_MD_NONVOID(int drive)) |
67 | { | 98 | { |
68 | #ifdef HAVE_MULTIDRIVE | 99 | if (!CHECK_DRV(drive)) |
69 | return disk_sector_multiplier[drive]; | 100 | return 0; |
70 | #else | 101 | |
71 | return disk_sector_multiplier[0]; | 102 | disk_reader_lock(); |
72 | #endif | 103 | int multiplier = disk_sector_multiplier[IF_MD_DRV(drive)]; |
104 | disk_reader_unlock(); | ||
105 | return multiplier; | ||
73 | } | 106 | } |
74 | #endif | 107 | #endif /* MAX_LOG_SECTOR_SIZE */ |
75 | 108 | ||
76 | struct partinfo* disk_init(IF_MD_NONVOID(int drive)) | 109 | bool disk_init(IF_MD_NONVOID(int drive)) |
77 | { | 110 | { |
78 | int i; | 111 | if (!CHECK_DRV(drive)) |
79 | #ifdef HAVE_MULTIDRIVE | 112 | return false; /* out of space in table */ |
80 | /* For each drive, start at a different position, in order not to destroy | ||
81 | the first entry of drive 0. | ||
82 | That one is needed to calculate config sector position. */ | ||
83 | struct partinfo* pinfo = &part[drive*4]; | ||
84 | if ((size_t)drive >= sizeof(part)/sizeof(*part)/4) | ||
85 | return NULL; /* out of space in table */ | ||
86 | #else | ||
87 | struct partinfo* pinfo = part; | ||
88 | const int drive = 0; | ||
89 | (void)drive; | ||
90 | #endif | ||
91 | 113 | ||
92 | unsigned char* sector = fat_get_sector_buffer(); | 114 | unsigned char *sector = dc_get_buffer(); |
93 | storage_read_sectors(IF_MD(drive,) 0,1, sector); | 115 | if (!sector) |
94 | /* check that the boot sector is initialized */ | 116 | return false; |
95 | if ( (sector[510] != 0x55) || | ||
96 | (sector[511] != 0xaa)) { | ||
97 | fat_release_sector_buffer(); | ||
98 | DEBUGF("Bad boot sector signature\n"); | ||
99 | return NULL; | ||
100 | } | ||
101 | 117 | ||
102 | /* parse partitions */ | 118 | memset(sector, 0, SECTOR_SIZE); |
103 | for ( i=0; i<4; i++ ) { | 119 | storage_read_sectors(IF_MD(drive,) 0, 1, sector); |
104 | unsigned char* ptr = sector + 0x1be + 16*i; | ||
105 | pinfo[i].type = ptr[4]; | ||
106 | pinfo[i].start = BYTES2INT32(ptr, 8); | ||
107 | pinfo[i].size = BYTES2INT32(ptr, 12); | ||
108 | 120 | ||
109 | DEBUGF("Part%d: Type %02x, start: %08lx size: %08lx\n", | 121 | bool init = false; |
110 | i,pinfo[i].type,pinfo[i].start,pinfo[i].size); | ||
111 | 122 | ||
112 | /* extended? */ | 123 | /* check that the boot sector is initialized */ |
113 | if ( pinfo[i].type == 5 ) { | 124 | if (BYTES2INT16(sector, 510) == 0xaa55) |
114 | /* not handled yet */ | 125 | { |
115 | } | 126 | /* For each drive, start at a different position, in order not to |
116 | } | 127 | destroy the first entry of drive 0. That one is needed to calculate |
117 | fat_release_sector_buffer(); | 128 | config sector position. */ |
118 | return pinfo; | 129 | struct partinfo *pinfo = &part[IF_MD_DRV(drive)*4]; |
119 | } | ||
120 | 130 | ||
121 | struct partinfo* disk_partinfo(int partition) | 131 | disk_writer_lock(); |
122 | { | ||
123 | return &part[partition]; | ||
124 | } | ||
125 | 132 | ||
126 | void disk_init_subsystem(void) | 133 | /* parse partitions */ |
127 | { | 134 | for (int i = 0; i < 4; i++) |
128 | mutex_init(&disk_mutex); | 135 | { |
129 | } | 136 | unsigned char* ptr = sector + 0x1be + 16*i; |
137 | pinfo[i].type = ptr[4]; | ||
138 | pinfo[i].start = BYTES2INT32(ptr, 8); | ||
139 | pinfo[i].size = BYTES2INT32(ptr, 12); | ||
130 | 140 | ||
131 | int disk_mount_all(void) | 141 | DEBUGF("Part%d: Type %02x, start: %08lx size: %08lx\n", |
132 | { | 142 | i,pinfo[i].type,pinfo[i].start,pinfo[i].size); |
133 | int mounted=0; | ||
134 | int i; | ||
135 | |||
136 | #ifdef HAVE_HOTSWAP | ||
137 | mutex_lock(&disk_mutex); | ||
138 | #endif | ||
139 | 143 | ||
140 | fat_init(); /* reset all mounted partitions */ | 144 | /* extended? */ |
141 | for (i=0; i<NUM_VOLUMES; i++) | 145 | if ( pinfo[i].type == 5 ) |
142 | vol_drive[i] = -1; /* mark all as unassigned */ | 146 | { |
147 | /* not handled yet */ | ||
148 | } | ||
149 | } | ||
143 | 150 | ||
144 | #ifndef HAVE_MULTIDRIVE | 151 | disk_writer_unlock(); |
145 | mounted = disk_mount(0); | 152 | |
146 | #else | 153 | init = true; |
147 | for(i=0;i<NUM_DRIVES;i++) | 154 | } |
155 | else | ||
148 | { | 156 | { |
149 | #ifdef HAVE_HOTSWAP | 157 | DEBUGF("Bad boot sector signature\n"); |
150 | if (storage_present(i)) | ||
151 | #endif | ||
152 | mounted += disk_mount(i); | ||
153 | } | 158 | } |
154 | #endif | ||
155 | 159 | ||
156 | #ifdef HAVE_HOTSWAP | 160 | dc_release_buffer(sector); |
157 | mutex_unlock(&disk_mutex); | 161 | return init; |
158 | #endif | ||
159 | return mounted; | ||
160 | } | 162 | } |
161 | 163 | ||
162 | static int get_free_volume(void) | 164 | bool disk_partinfo(int partition, struct partinfo *info) |
163 | { | 165 | { |
164 | int i; | 166 | if (partition < 0 || partition >= (int)ARRAYLEN(part) || !info) |
165 | for (i=0; i<NUM_VOLUMES; i++) | 167 | return false; |
166 | { | ||
167 | if (vol_drive[i] == -1) /* unassigned? */ | ||
168 | return i; | ||
169 | } | ||
170 | 168 | ||
171 | return -1; /* none found */ | 169 | disk_reader_lock(); |
170 | *info = part[partition]; | ||
171 | disk_reader_unlock(); | ||
172 | return true; | ||
172 | } | 173 | } |
173 | 174 | ||
174 | int disk_mount(int drive) | 175 | int disk_mount(int drive) |
175 | { | 176 | { |
176 | int mounted = 0; /* reset partition-on-drive flag */ | 177 | int mounted = 0; /* reset partition-on-drive flag */ |
177 | int volume; | ||
178 | struct partinfo* pinfo; | ||
179 | 178 | ||
180 | #ifdef HAVE_HOTSWAP | 179 | disk_writer_lock(); |
181 | mutex_lock(&disk_mutex); | ||
182 | #endif | ||
183 | 180 | ||
184 | volume = get_free_volume(); | 181 | int volume = get_free_volume(); |
185 | pinfo = disk_init(IF_MD(drive)); | ||
186 | #ifdef MAX_LOG_SECTOR_SIZE | ||
187 | disk_sector_multiplier[drive] = 1; | ||
188 | #endif | ||
189 | 182 | ||
190 | if (pinfo == NULL) | 183 | if (!disk_init(IF_MD(drive))) |
191 | { | 184 | { |
192 | #ifdef HAVE_HOTSWAP | 185 | disk_writer_unlock(); |
193 | mutex_unlock(&disk_mutex); | ||
194 | #endif | ||
195 | return 0; | 186 | return 0; |
196 | } | 187 | } |
197 | #if defined(TOSHIBA_GIGABEAT_S) | 188 | |
198 | int i = 1; /* For the Gigabeat S, we mount the second partition */ | 189 | struct partinfo *pinfo = &part[IF_MD_DRV(drive)*4]; |
199 | #else | 190 | #ifdef MAX_LOG_SECTOR_SIZE |
200 | int i = 0; | 191 | disk_sector_multiplier[IF_MD_DRV(drive)] = 1; |
201 | #endif | 192 | #endif |
202 | for (; volume != -1 && i<4 && mounted<NUM_VOLUMES_PER_DRIVE; i++) | 193 | |
194 | for (int i = CONFIG_DEFAULT_PARTNUM; | ||
195 | volume != -1 && i < 4 && mounted < NUM_VOLUMES_PER_DRIVE; | ||
196 | i++) | ||
203 | { | 197 | { |
204 | if (memchr(fat_partition_types, pinfo[i].type, | 198 | if (memchr(fat_partition_types, pinfo[i].type, |
205 | sizeof(fat_partition_types)) == NULL) | 199 | sizeof(fat_partition_types)) == NULL) |
206 | continue; /* not an accepted partition type */ | 200 | continue; /* not an accepted partition type */ |
207 | 201 | ||
208 | #ifdef MAX_LOG_SECTOR_SIZE | 202 | bool success = false; |
209 | int j; | 203 | |
210 | 204 | #ifdef MAX_LOG_SECTOR_SIZE | |
211 | for (j = 1; j <= (MAX_LOG_SECTOR_SIZE/SECTOR_SIZE); j <<= 1) | 205 | for (int j = 1; j <= (MAX_LOG_SECTOR_SIZE/SECTOR_SIZE); j <<= 1) |
212 | { | 206 | { |
213 | if (!fat_mount(IF_MV(volume,) IF_MD(drive,) pinfo[i].start * j)) | 207 | if (!fat_mount(IF_MV(volume,) IF_MD(drive,) pinfo[i].start * j)) |
214 | { | 208 | { |
@@ -218,93 +212,242 @@ int disk_mount(int drive) | |||
218 | vol_drive[volume] = drive; /* remember the drive for this volume */ | 212 | vol_drive[volume] = drive; /* remember the drive for this volume */ |
219 | volume = get_free_volume(); /* prepare next entry */ | 213 | volume = get_free_volume(); /* prepare next entry */ |
220 | disk_sector_multiplier[drive] = j; | 214 | disk_sector_multiplier[drive] = j; |
215 | success = true; | ||
221 | break; | 216 | break; |
222 | } | 217 | } |
223 | } | 218 | } |
224 | #else | 219 | #else /* ndef MAX_LOG_SECTOR_SIZE */ |
225 | if (!fat_mount(IF_MV(volume,) IF_MD(drive,) pinfo[i].start)) | 220 | if (!fat_mount(IF_MV(volume,) IF_MD(drive,) pinfo[i].start)) |
226 | { | 221 | { |
227 | mounted++; | 222 | mounted++; |
228 | vol_drive[volume] = drive; /* remember the drive for this volume */ | 223 | vol_drive[volume] = drive; /* remember the drive for this volume */ |
229 | volume = get_free_volume(); /* prepare next entry */ | 224 | volume = get_free_volume(); /* prepare next entry */ |
225 | success = true; | ||
230 | } | 226 | } |
231 | #endif | 227 | #endif /* MAX_LOG_SECTOR_SIZE */ |
228 | |||
229 | if (success) | ||
230 | volume_onmount_internal(IF_MV(volume)); | ||
232 | } | 231 | } |
233 | 232 | ||
234 | if (mounted == 0 && volume != -1) /* none of the 4 entries worked? */ | 233 | if (mounted == 0 && volume != -1) /* none of the 4 entries worked? */ |
235 | { /* try "superfloppy" mode */ | 234 | { /* try "superfloppy" mode */ |
236 | DEBUGF("No partition found, trying to mount sector 0.\n"); | 235 | DEBUGF("No partition found, trying to mount sector 0.\n"); |
236 | |||
237 | if (!fat_mount(IF_MV(volume,) IF_MD(drive,) 0)) | 237 | if (!fat_mount(IF_MV(volume,) IF_MD(drive,) 0)) |
238 | { | 238 | { |
239 | #ifdef MAX_LOG_SECTOR_SIZE | 239 | #ifdef MAX_LOG_SECTOR_SIZE |
240 | disk_sector_multiplier[drive] = fat_get_bytes_per_sector(IF_MV(volume))/SECTOR_SIZE; | 240 | disk_sector_multiplier[drive] = |
241 | #endif | 241 | fat_get_bytes_per_sector(IF_MV(volume)) / SECTOR_SIZE; |
242 | #endif | ||
242 | mounted = 1; | 243 | mounted = 1; |
243 | vol_drive[volume] = drive; /* remember the drive for this volume */ | 244 | vol_drive[volume] = drive; /* remember the drive for this volume */ |
245 | volume_onmount_internal(IF_MV(volume)); | ||
244 | } | 246 | } |
245 | } | 247 | } |
246 | #ifdef HAVE_HOTSWAP | 248 | |
247 | mutex_unlock(&disk_mutex); | 249 | disk_writer_unlock(); |
248 | #endif | 250 | return mounted; |
251 | } | ||
252 | |||
253 | int disk_mount_all(void) | ||
254 | { | ||
255 | int mounted = 0; | ||
256 | |||
257 | disk_writer_lock(); | ||
258 | |||
259 | /* reset all mounted partitions */ | ||
260 | volume_onunmount_internal(IF_MV(-1)); | ||
261 | fat_init(); | ||
262 | |||
263 | for (int i = 0; i < NUM_VOLUMES; i++) | ||
264 | vol_drive[i] = -1; /* mark all as unassigned */ | ||
265 | |||
266 | for (int i = 0; i < NUM_DRIVES; i++) | ||
267 | { | ||
268 | #ifdef HAVE_HOTSWAP | ||
269 | if (storage_present(i)) | ||
270 | #endif | ||
271 | mounted += disk_mount(i); | ||
272 | } | ||
273 | |||
274 | disk_writer_unlock(); | ||
249 | return mounted; | 275 | return mounted; |
250 | } | 276 | } |
251 | 277 | ||
252 | int disk_unmount(int drive) | 278 | int disk_unmount(int drive) |
253 | { | 279 | { |
280 | if (!CHECK_DRV(drive)) | ||
281 | return 0; | ||
282 | |||
254 | int unmounted = 0; | 283 | int unmounted = 0; |
255 | int i; | 284 | |
256 | #ifdef HAVE_HOTSWAP | 285 | disk_writer_lock(); |
257 | mutex_lock(&disk_mutex); | 286 | |
258 | #endif | 287 | for (int i = 0; i < NUM_VOLUMES; i++) |
259 | for (i=0; i<NUM_VOLUMES; i++) | ||
260 | { | 288 | { |
261 | if (vol_drive[i] == drive) | 289 | if (vol_drive[i] == drive) |
262 | { /* force releasing resources */ | 290 | { /* force releasing resources */ |
263 | vol_drive[i] = -1; /* mark unused */ | 291 | vol_drive[i] = -1; /* mark unused */ |
292 | |||
293 | volume_onunmount_internal(IF_MV(i)); | ||
294 | fat_unmount(IF_MV(i)); | ||
295 | |||
264 | unmounted++; | 296 | unmounted++; |
265 | release_files(i); | ||
266 | release_dirs(i); | ||
267 | fat_unmount(i, false); | ||
268 | } | 297 | } |
269 | } | 298 | } |
270 | #ifdef HAVE_HOTSWAP | ||
271 | mutex_unlock(&disk_mutex); | ||
272 | #endif | ||
273 | 299 | ||
300 | disk_writer_unlock(); | ||
274 | return unmounted; | 301 | return unmounted; |
275 | } | 302 | } |
276 | 303 | ||
277 | int disk_unmount_all(void) | 304 | int disk_unmount_all(void) |
278 | { | 305 | { |
279 | #ifndef HAVE_MULTIDRIVE | ||
280 | return disk_unmount(0); | ||
281 | #else /* HAVE_MULTIDRIVE */ | ||
282 | int unmounted = 0; | 306 | int unmounted = 0; |
283 | int i; | 307 | |
284 | for (i = 0; i < NUM_DRIVES; i++) | 308 | disk_writer_lock(); |
309 | |||
310 | volume_onunmount_internal(IF_MV(-1)); | ||
311 | |||
312 | for (int i = 0; i < NUM_DRIVES; i++) | ||
285 | { | 313 | { |
286 | #ifdef HAVE_HOTSWAP | 314 | #ifdef HAVE_HOTSWAP |
287 | if (storage_present(i)) | 315 | if (storage_present(i)) |
288 | #endif | 316 | #endif |
289 | unmounted += disk_unmount(i); | 317 | unmounted += disk_unmount(i); |
290 | } | 318 | } |
291 | 319 | ||
320 | disk_writer_unlock(); | ||
292 | return unmounted; | 321 | return unmounted; |
293 | #endif /* HAVE_MULTIDRIVE */ | 322 | } |
323 | |||
324 | bool disk_present(IF_MD_NONVOID(int drive)) | ||
325 | { | ||
326 | int rc = -1; | ||
327 | |||
328 | if (CHECK_DRV(drive)) | ||
329 | { | ||
330 | void *sector = dc_get_buffer(); | ||
331 | if (sector) | ||
332 | { | ||
333 | rc = storage_read_sectors(IF_MD(drive,) 0, 1, sector); | ||
334 | dc_release_buffer(sector); | ||
335 | } | ||
336 | } | ||
337 | |||
338 | return rc == 0; | ||
339 | } | ||
340 | |||
341 | |||
342 | /** Volume-centric functions **/ | ||
343 | |||
344 | void volume_recalc_free(IF_MV_NONVOID(int volume)) | ||
345 | { | ||
346 | if (!CHECK_VOL(volume)) | ||
347 | return; | ||
348 | |||
349 | /* FIXME: this is crummy but the only way to ensure a correct freecount | ||
350 | if other threads are writing and changing the fsinfo; it is possible | ||
351 | to get multiple threads calling here and also writing and get correct | ||
352 | freespace counts, however a bit complicated to do; if thou desireth I | ||
353 | shall implement the concurrent version -- jethead71 */ | ||
354 | disk_writer_lock(); | ||
355 | fat_recalc_free(IF_MV(volume)); | ||
356 | disk_writer_unlock(); | ||
357 | } | ||
358 | |||
359 | unsigned int volume_get_cluster_size(IF_MV_NONVOID(int volume)) | ||
360 | { | ||
361 | if (!CHECK_VOL(volume)) | ||
362 | return 0; | ||
363 | |||
364 | disk_reader_lock(); | ||
365 | unsigned int clustersize = fat_get_cluster_size(IF_MV(volume)); | ||
366 | disk_reader_unlock(); | ||
367 | return clustersize; | ||
368 | } | ||
369 | |||
370 | void volume_size(IF_MV(int volume,) unsigned long *sizep, unsigned long *freep) | ||
371 | { | ||
372 | disk_reader_lock(); | ||
373 | |||
374 | if (!CHECK_VOL(volume) || !fat_size(IF_MV(volume,) sizep, freep)) | ||
375 | { | ||
376 | if (freep) *sizep = 0; | ||
377 | if (freep) *freep = 0; | ||
378 | } | ||
379 | |||
380 | disk_reader_unlock(); | ||
381 | } | ||
382 | |||
383 | #if defined (HAVE_HOTSWAP) || defined (HAVE_MULTIDRIVE) \ | ||
384 | || defined (HAVE_DIRCACHE) | ||
385 | enum volume_info_type | ||
386 | { | ||
387 | #ifdef HAVE_HOTSWAP | ||
388 | VP_REMOVABLE, | ||
389 | VP_PRESENT, | ||
390 | #endif | ||
391 | #if defined (HAVE_MULTIDRIVE) || defined (HAVE_DIRCACHE) | ||
392 | VP_DRIVE, | ||
393 | #endif | ||
394 | }; | ||
395 | |||
396 | static int volume_properties(int volume, enum volume_info_type infotype) | ||
397 | { | ||
398 | int res = -1; | ||
399 | |||
400 | disk_reader_lock(); | ||
401 | |||
402 | if (CHECK_VOL(volume)) | ||
403 | { | ||
404 | int vd = vol_drive[volume]; | ||
405 | switch (infotype) | ||
406 | { | ||
407 | #ifdef HAVE_HOTSWAP | ||
408 | case VP_REMOVABLE: | ||
409 | res = storage_removable(vd) ? 1 : 0; | ||
410 | break; | ||
411 | case VP_PRESENT: | ||
412 | res = storage_present(vd) ? 1 : 0; | ||
413 | break; | ||
414 | #endif | ||
415 | #if defined(HAVE_MULTIDRIVE) || defined(HAVE_DIRCACHE) | ||
416 | case VP_DRIVE: | ||
417 | res = vd; | ||
418 | break; | ||
419 | #endif | ||
420 | } | ||
421 | } | ||
422 | |||
423 | disk_reader_unlock(); | ||
424 | return res; | ||
294 | } | 425 | } |
295 | 426 | ||
296 | #ifdef HAVE_HOTSWAP | 427 | #ifdef HAVE_HOTSWAP |
297 | bool volume_removable(int volume) | 428 | bool volume_removable(int volume) |
298 | { | 429 | { |
299 | if(vol_drive[volume] == -1) | 430 | return volume_properties(volume, VP_REMOVABLE) > 0; |
300 | return false; | ||
301 | return storage_removable(vol_drive[volume]); | ||
302 | } | 431 | } |
303 | 432 | ||
304 | bool volume_present(int volume) | 433 | bool volume_present(int volume) |
305 | { | 434 | { |
306 | if(vol_drive[volume] == -1) | 435 | return volume_properties(volume, VP_PRESENT) > 0; |
307 | return false; | ||
308 | return storage_present(vol_drive[volume]); | ||
309 | } | 436 | } |
310 | #endif | 437 | #endif /* HAVE_HOTSWAP */ |
438 | |||
439 | #ifdef HAVE_MULTIDRIVE | ||
440 | int volume_drive(int volume) | ||
441 | { | ||
442 | return volume_properties(volume, VP_DRIVE); | ||
443 | } | ||
444 | #endif /* HAVE_MULTIDRIVE */ | ||
445 | |||
446 | #ifdef HAVE_DIRCACHE | ||
447 | bool volume_ismounted(IF_MV_NONVOID(int volume)) | ||
448 | { | ||
449 | return volume_properties(IF_MV_VOL(volume), VP_DRIVE) >= 0; | ||
450 | } | ||
451 | #endif /* HAVE_DIRCACHE */ | ||
452 | |||
453 | #endif /* HAVE_HOTSWAP || HAVE_MULTIDRIVE || HAVE_DIRCACHE */ | ||
diff --git a/firmware/common/disk_cache.c b/firmware/common/disk_cache.c new file mode 100644 index 0000000000..0e842e7796 --- /dev/null +++ b/firmware/common/disk_cache.c | |||
@@ -0,0 +1,343 @@ | |||
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 "debug.h" | ||
23 | #include "system.h" | ||
24 | #include "linked_list.h" | ||
25 | #include "disk_cache.h" | ||
26 | #include "fat.h" /* for SECTOR_SIZE */ | ||
27 | #include "bitarray.h" | ||
28 | |||
29 | /* Cache: LRU cache with separately-chained hashtable | ||
30 | * | ||
31 | * Each entry of the map is the mapped location of the hashed sector value | ||
32 | * where each bit in each map entry indicates which corresponding cache | ||
33 | * entries are occupied by sector values that collide in that map entry. | ||
34 | * | ||
35 | * Each volume is given its own bit map. | ||
36 | * | ||
37 | * To probe for a specific key, each bit in the map entry must be examined, | ||
38 | * its position used as an index into the cache_entry array and the actual | ||
39 | * sector information compared for that cache entry. If the search exhausts | ||
40 | * all bits, the sector is not cached. | ||
41 | * | ||
42 | * To avoid long chains, the map entry count should be much greater than the | ||
43 | * number of cache entries. Since the cache is an LRU design, no buffer entry | ||
44 | * in the array is intrinsically associated with any particular sector number | ||
45 | * or volume. | ||
46 | * | ||
47 | * Example 6-sector cache with 8-entry map: | ||
48 | * cache entry 543210 | ||
49 | * cache map 100000 <- sector number hashes into map | ||
50 | * 000000 | ||
51 | * 000100 | ||
52 | * 000000 | ||
53 | * 010000 | ||
54 | * 000000 | ||
55 | * 001001 <- collision | ||
56 | * 000000 | ||
57 | * volume map 111101 <- entry usage by the volume (OR of all map entries) | ||
58 | */ | ||
59 | |||
60 | enum dce_flags /* flags for each cache entry */ | ||
61 | { | ||
62 | DCE_INUSE = 0x01, /* entry in use and valid */ | ||
63 | DCE_DIRTY = 0x02, /* entry is dirty in need of writeback */ | ||
64 | DCE_BUF = 0x04, /* entry is being used as a general buffer */ | ||
65 | }; | ||
66 | |||
67 | struct disk_cache_entry | ||
68 | { | ||
69 | struct lldc_node node; /* LRU list links */ | ||
70 | unsigned char flags; /* entry flags */ | ||
71 | #ifdef HAVE_MULTIVOLUME | ||
72 | unsigned char volume; /* volume of sector */ | ||
73 | #endif | ||
74 | unsigned long sector; /* cached disk sector number */ | ||
75 | }; | ||
76 | |||
77 | BITARRAY_TYPE_DECLARE(cache_map_entry_t, cache_map, DC_NUM_ENTRIES) | ||
78 | |||
79 | static inline unsigned int map_sector(unsigned long sector) | ||
80 | { | ||
81 | /* keep sector hash simple for now */ | ||
82 | return sector % DC_MAP_NUM_ENTRIES; | ||
83 | } | ||
84 | |||
85 | static struct lldc_head cache_lru; /* LRU cache list (head = LRU item) */ | ||
86 | static struct disk_cache_entry cache_entry[DC_NUM_ENTRIES]; | ||
87 | static cache_map_entry_t cache_map_entry[NUM_VOLUMES][DC_MAP_NUM_ENTRIES]; | ||
88 | static cache_map_entry_t cache_vol_map[NUM_VOLUMES] IBSS_ATTR; | ||
89 | static uint8_t cache_buffer[DC_NUM_ENTRIES][DC_CACHE_BUFSIZE] CACHEALIGN_ATTR; | ||
90 | struct mutex disk_cache_mutex SHAREDBSS_ATTR; | ||
91 | |||
92 | #define CACHE_MAP_ENTRY(volume, mapnum) \ | ||
93 | cache_map_entry[IF_MV_VOL(volume)][mapnum] | ||
94 | #define CACHE_VOL_MAP(volume) \ | ||
95 | cache_vol_map[IF_MV_VOL(volume)] | ||
96 | |||
97 | #define DCE_LRU() ((struct disk_cache_entry *)cache_lru.head) | ||
98 | #define DCE_NEXT(fce) ((struct disk_cache_entry *)(fce)->node.next) | ||
99 | #define NODE_DCE(node) ((struct disk_cache_entry *)(node)) | ||
100 | |||
101 | /* get the cache index from a pointer to a buffer */ | ||
102 | #define DCIDX_FROM_BUF(buf) \ | ||
103 | ((uint8_t (*)[DC_CACHE_BUFSIZE])(buf) - cache_buffer) | ||
104 | |||
105 | #define DCIDX_FROM_DCE(dce) \ | ||
106 | ((dce) - cache_entry) | ||
107 | |||
108 | /* set the in-use bit in the map */ | ||
109 | static inline void cache_bitmap_set_bit(int volume, unsigned int mapnum, | ||
110 | unsigned int bitnum) | ||
111 | { | ||
112 | cache_map_set_bit(&CACHE_MAP_ENTRY(volume, mapnum), bitnum); | ||
113 | cache_map_set_bit(&CACHE_VOL_MAP(volume), bitnum); | ||
114 | (void)volume; | ||
115 | } | ||
116 | |||
117 | /* clear the in-use bit in the map */ | ||
118 | static inline void cache_bitmap_clear_bit(int volume, unsigned int mapnum, | ||
119 | unsigned int bitnum) | ||
120 | { | ||
121 | cache_map_clear_bit(&CACHE_MAP_ENTRY(volume, mapnum), bitnum); | ||
122 | cache_map_clear_bit(&CACHE_VOL_MAP(volume), bitnum); | ||
123 | (void)volume; | ||
124 | } | ||
125 | |||
126 | /* make entry MRU by moving it to the list tail */ | ||
127 | static inline void touch_cache_entry(struct disk_cache_entry *which) | ||
128 | { | ||
129 | struct lldc_node *lru = cache_lru.head; | ||
130 | struct lldc_node *node = &which->node; | ||
131 | |||
132 | if (node == lru->prev) /* already MRU */ | ||
133 | ; /**/ | ||
134 | else if (node == lru) /* is the LRU? just rotate list */ | ||
135 | cache_lru.head = lru->next; | ||
136 | else /* somewhere else; move it */ | ||
137 | { | ||
138 | lldc_remove(&cache_lru, node); | ||
139 | lldc_insert_last(&cache_lru, node); | ||
140 | } | ||
141 | } | ||
142 | |||
143 | /* remove LRU entry from the cache list to use as a buffer */ | ||
144 | static struct disk_cache_entry * cache_remove_lru_entry(void) | ||
145 | { | ||
146 | struct lldc_node *lru = cache_lru.head; | ||
147 | |||
148 | /* at least one is reserved for client */ | ||
149 | if (lru == lru->next) | ||
150 | return NULL; | ||
151 | |||
152 | /* remove it; next-LRU becomes the LRU */ | ||
153 | lldc_remove(&cache_lru, lru); | ||
154 | return NODE_DCE(lru); | ||
155 | } | ||
156 | |||
157 | /* return entry to the cache list and set it LRU */ | ||
158 | static void cache_return_lru_entry(struct disk_cache_entry *fce) | ||
159 | { | ||
160 | lldc_insert_first(&cache_lru, &fce->node); | ||
161 | } | ||
162 | |||
163 | /* discard the entry's data and mark it unused */ | ||
164 | static inline void cache_discard_entry(struct disk_cache_entry *dce, | ||
165 | unsigned int index) | ||
166 | { | ||
167 | cache_bitmap_clear_bit(IF_MV_VOL(dce->volume), map_sector(dce->sector), | ||
168 | index); | ||
169 | dce->flags = 0; | ||
170 | } | ||
171 | |||
172 | /* search the cache for the specified sector, returning a buffer, either | ||
173 | to the specified sector, if it exists, or a new/evicted entry that must | ||
174 | be filled */ | ||
175 | void * dc_cache_probe(IF_MV(int volume,) unsigned long sector, | ||
176 | unsigned int *flagsp) | ||
177 | { | ||
178 | unsigned int mapnum = map_sector(sector); | ||
179 | |||
180 | FOR_EACH_BITARRAY_SET_BIT(&CACHE_MAP_ENTRY(volume, mapnum), index) | ||
181 | { | ||
182 | struct disk_cache_entry *dce = &cache_entry[index]; | ||
183 | |||
184 | if (dce->sector == sector) | ||
185 | { | ||
186 | *flagsp = DCE_INUSE; | ||
187 | touch_cache_entry(dce); | ||
188 | return cache_buffer[index]; | ||
189 | } | ||
190 | } | ||
191 | |||
192 | /* sector not found so the LRU is the victim */ | ||
193 | struct disk_cache_entry *dce = DCE_LRU(); | ||
194 | cache_lru.head = dce->node.next; | ||
195 | |||
196 | unsigned int index = DCIDX_FROM_DCE(dce); | ||
197 | void *buf = cache_buffer[index]; | ||
198 | unsigned int old_flags = dce->flags; | ||
199 | |||
200 | if (old_flags) | ||
201 | { | ||
202 | int old_volume = IF_MV_VOL(dce->volume); | ||
203 | unsigned long sector = dce->sector; | ||
204 | unsigned int old_mapnum = map_sector(sector); | ||
205 | |||
206 | if (old_flags & DCE_DIRTY) | ||
207 | dc_writeback_callback(IF_MV(old_volume,) sector, buf); | ||
208 | |||
209 | if (mapnum == old_mapnum IF_MV( && volume == old_volume )) | ||
210 | goto finish_setup; | ||
211 | |||
212 | cache_bitmap_clear_bit(old_volume, old_mapnum, index); | ||
213 | } | ||
214 | |||
215 | cache_bitmap_set_bit(IF_MV_VOL(volume), mapnum, index); | ||
216 | |||
217 | finish_setup: | ||
218 | dce->flags = DCE_INUSE; | ||
219 | #ifdef HAVE_MULTIVOLUME | ||
220 | dce->volume = volume; | ||
221 | #endif | ||
222 | dce->sector = sector; | ||
223 | |||
224 | *flagsp = 0; | ||
225 | return buf; | ||
226 | } | ||
227 | |||
228 | /* mark in-use cache entry as dirty by buffer */ | ||
229 | void dc_dirty_buf(void *buf) | ||
230 | { | ||
231 | unsigned int index = DCIDX_FROM_BUF(buf); | ||
232 | |||
233 | if (index >= DC_NUM_ENTRIES) | ||
234 | return; | ||
235 | |||
236 | /* dirt remains, sticky until flushed */ | ||
237 | struct disk_cache_entry *fce = &cache_entry[index]; | ||
238 | if (fce->flags & DCE_INUSE) | ||
239 | fce->flags |= DCE_DIRTY; | ||
240 | } | ||
241 | |||
242 | /* discard in-use cache entry by buffer */ | ||
243 | void dc_discard_buf(void *buf) | ||
244 | { | ||
245 | unsigned int index = DCIDX_FROM_BUF(buf); | ||
246 | |||
247 | if (index >= DC_NUM_ENTRIES) | ||
248 | return; | ||
249 | |||
250 | struct disk_cache_entry *dce = &cache_entry[index]; | ||
251 | if (dce->flags & DCE_INUSE) | ||
252 | cache_discard_entry(dce, index); | ||
253 | } | ||
254 | |||
255 | /* commit all dirty cache entries to storage for a specified volume */ | ||
256 | void dc_commit_all(IF_MV_NONVOID(int volume)) | ||
257 | { | ||
258 | DEBUGF("dc_commit_all()\n"); | ||
259 | |||
260 | FOR_EACH_BITARRAY_SET_BIT(&CACHE_VOL_MAP(volume), index) | ||
261 | { | ||
262 | struct disk_cache_entry *dce = &cache_entry[index]; | ||
263 | unsigned int flags = dce->flags; | ||
264 | |||
265 | if (flags & DCE_DIRTY) | ||
266 | { | ||
267 | dc_writeback_callback(IF_MV(volume,) dce->sector, | ||
268 | cache_buffer[index]); | ||
269 | dce->flags = flags & ~DCE_DIRTY; | ||
270 | } | ||
271 | } | ||
272 | } | ||
273 | |||
274 | /* discard all cache entries from the specified volume */ | ||
275 | void dc_discard_all(IF_MV_NONVOID(int volume)) | ||
276 | { | ||
277 | DEBUGF("dc_discard_all()\n"); | ||
278 | |||
279 | FOR_EACH_BITARRAY_SET_BIT(&CACHE_VOL_MAP(volume), index) | ||
280 | cache_discard_entry(&cache_entry[index], index); | ||
281 | } | ||
282 | |||
283 | /* expropriate a buffer from the cache */ | ||
284 | void * dc_get_buffer(void) | ||
285 | { | ||
286 | dc_lock_cache(); | ||
287 | |||
288 | void *buf = NULL; | ||
289 | struct disk_cache_entry *dce = cache_remove_lru_entry(); | ||
290 | |||
291 | if (dce) | ||
292 | { | ||
293 | unsigned int index = DCIDX_FROM_DCE(dce); | ||
294 | unsigned int flags = dce->flags; | ||
295 | |||
296 | buf = cache_buffer[index]; | ||
297 | |||
298 | if (flags) | ||
299 | { | ||
300 | /* must first commit this sector if dirty */ | ||
301 | if (flags & DCE_DIRTY) | ||
302 | dc_writeback_callback(IF_MV(dce->volume,) dce->sector, buf); | ||
303 | |||
304 | cache_discard_entry(dce, index); | ||
305 | } | ||
306 | |||
307 | dce->flags = DCE_BUF; | ||
308 | } | ||
309 | /* cache is out of buffers */ | ||
310 | |||
311 | dc_unlock_cache(); | ||
312 | return buf; | ||
313 | } | ||
314 | |||
315 | /* return buffer to the cache by buffer */ | ||
316 | void dc_release_buffer(void *buf) | ||
317 | { | ||
318 | unsigned int index = DCIDX_FROM_BUF(buf); | ||
319 | |||
320 | if (index >= DC_NUM_ENTRIES) | ||
321 | return; | ||
322 | |||
323 | dc_lock_cache(); | ||
324 | |||
325 | struct disk_cache_entry *dce = &cache_entry[index]; | ||
326 | |||
327 | if (dce->flags & DCE_BUF) | ||
328 | { | ||
329 | dce->flags = 0; | ||
330 | cache_return_lru_entry(dce); | ||
331 | } | ||
332 | |||
333 | dc_unlock_cache(); | ||
334 | } | ||
335 | |||
336 | /* one-time init at startup */ | ||
337 | void dc_init(void) | ||
338 | { | ||
339 | mutex_init(&disk_cache_mutex); | ||
340 | lldc_init(&cache_lru); | ||
341 | for (unsigned int i = 0; i < DC_NUM_ENTRIES; i++) | ||
342 | lldc_insert_last(&cache_lru, &cache_entry[i].node); | ||
343 | } | ||
diff --git a/firmware/common/file.c b/firmware/common/file.c index 920eada84e..7d3b5092ae 100644 --- a/firmware/common/file.c +++ b/firmware/common/file.c | |||
@@ -8,6 +8,7 @@ | |||
8 | * $Id$ | 8 | * $Id$ |
9 | * | 9 | * |
10 | * Copyright (C) 2002 by Björn Stenberg | 10 | * Copyright (C) 2002 by Björn Stenberg |
11 | * Copyright (C) 2014 by Michael Sevakis | ||
11 | * | 12 | * |
12 | * This program is free software; you can redistribute it and/or | 13 | * This program is free software; you can redistribute it and/or |
13 | * modify it under the terms of the GNU General Public License | 14 | * modify it under the terms of the GNU General Public License |
@@ -18,819 +19,1201 @@ | |||
18 | * KIND, either express or implied. | 19 | * KIND, either express or implied. |
19 | * | 20 | * |
20 | ****************************************************************************/ | 21 | ****************************************************************************/ |
22 | #define RB_FILESYSTEM_OS | ||
23 | #include "config.h" | ||
24 | #include "system.h" | ||
21 | #include <string.h> | 25 | #include <string.h> |
22 | #include <errno.h> | 26 | #include <errno.h> |
23 | #include <stdbool.h> | ||
24 | #include "file.h" | ||
25 | #include "fat.h" | ||
26 | #include "dir_uncached.h" | ||
27 | #include "debug.h" | 27 | #include "debug.h" |
28 | #include "dircache.h" | 28 | #include "file.h" |
29 | #include "filefuncs.h" | 29 | #include "fileobj_mgr.h" |
30 | #include "system.h" | 30 | #include "disk_cache.h" |
31 | #include "dircache_redirect.h" | ||
32 | #include "string-extra.h" | ||
33 | |||
34 | /** | ||
35 | * These functions provide a roughly POSIX-compatible file I/O API. | ||
36 | */ | ||
37 | |||
38 | /* structure used for open file descriptors */ | ||
39 | static struct filestr_desc | ||
40 | { | ||
41 | struct filestr_base stream; /* basic stream info (first!) */ | ||
42 | file_size_t offset; /* current offset for stream */ | ||
43 | file_size_t *sizep; /* shortcut to file size in fileobj */ | ||
44 | } open_streams[MAX_OPEN_FILES]; | ||
31 | 45 | ||
32 | /* | 46 | /* check and return a struct filestr_desc* from a file descriptor number */ |
33 | These functions provide a roughly POSIX-compatible file IO API. | 47 | static struct filestr_desc * get_filestr(int fildes) |
48 | { | ||
49 | struct filestr_desc *file = &open_streams[fildes]; | ||
34 | 50 | ||
35 | Since the fat32 driver only manages sectors, we maintain a one-sector | 51 | if ((unsigned int)fildes >= MAX_OPEN_FILES) |
36 | cache for each open file. This way we can provide byte access without | 52 | file = NULL; |
37 | having to re-read the sector each time. | 53 | else if (file->stream.flags & FDO_BUSY) |
38 | The penalty is the RAM used for the cache and slightly more complex code. | 54 | return file; |
39 | */ | ||
40 | 55 | ||
41 | struct filedesc { | 56 | DEBUGF("fildes %d: bad file number\n", fildes); |
42 | unsigned char cache[SECTOR_SIZE] CACHEALIGN_ATTR; | 57 | errno = (file && file->stream.flags == FV_NONEXIST) ? ENXIO : EBADF; |
43 | int cacheoffset; /* invariant: 0 <= cacheoffset <= SECTOR_SIZE */ | 58 | return NULL; |
44 | long fileoffset; | 59 | } |
45 | long size; | ||
46 | int attr; | ||
47 | struct fat_file fatfile; | ||
48 | bool busy; | ||
49 | bool write; | ||
50 | bool dirty; | ||
51 | bool trunc; | ||
52 | } CACHEALIGN_ATTR; | ||
53 | 60 | ||
54 | static struct filedesc openfiles[MAX_OPEN_FILES] CACHEALIGN_ATTR; | 61 | #define GET_FILESTR(type, fildes) \ |
62 | ({ \ | ||
63 | file_internal_lock_##type(); \ | ||
64 | struct filestr_desc * _file = get_filestr(fildes); \ | ||
65 | if (_file) \ | ||
66 | FILESTR_LOCK(type, &_file->stream); \ | ||
67 | else \ | ||
68 | file_internal_unlock_##type(); \ | ||
69 | _file; \ | ||
70 | }) | ||
71 | |||
72 | /* release the lock on the filestr_desc* */ | ||
73 | #define RELEASE_FILESTR(type, file) \ | ||
74 | ({ \ | ||
75 | FILESTR_UNLOCK(type, &(file)->stream); \ | ||
76 | file_internal_unlock_##type(); \ | ||
77 | }) | ||
78 | |||
79 | /* find a free file descriptor */ | ||
80 | static int alloc_filestr(struct filestr_desc **filep) | ||
81 | { | ||
82 | for (int fildes = 0; fildes < MAX_OPEN_FILES; fildes++) | ||
83 | { | ||
84 | struct filestr_desc *file = &open_streams[fildes]; | ||
85 | if (!file->stream.flags) | ||
86 | { | ||
87 | *filep = file; | ||
88 | return fildes; | ||
89 | } | ||
90 | } | ||
55 | 91 | ||
56 | static int flush_cache(int fd); | 92 | DEBUGF("Too many files open\n"); |
93 | return -1; | ||
94 | } | ||
57 | 95 | ||
58 | int file_creat(const char *pathname) | 96 | /* return the file size in sectors */ |
97 | static inline unsigned long filesize_sectors(file_size_t size) | ||
59 | { | 98 | { |
60 | return open(pathname, O_WRONLY|O_CREAT|O_TRUNC, 0666); | 99 | /* overflow proof whereas "(x + y - 1) / y" is not */ |
100 | unsigned long numsectors = size / SECTOR_SIZE; | ||
101 | |||
102 | if (size % SECTOR_SIZE) | ||
103 | numsectors++; | ||
104 | |||
105 | return numsectors; | ||
61 | } | 106 | } |
62 | 107 | ||
63 | static int open_internal(const char* pathname, int flags, bool use_cache) | 108 | /* flush a dirty cache buffer */ |
109 | static int flush_cache(struct filestr_desc *file) | ||
64 | { | 110 | { |
65 | DIR_UNCACHED* dir; | ||
66 | struct dirent_uncached* entry; | ||
67 | int fd; | ||
68 | int pathnamesize = strlen(pathname) + 1; | ||
69 | char pathnamecopy[pathnamesize]; | ||
70 | char* name; | ||
71 | struct filedesc* file = NULL; | ||
72 | int rc; | 111 | int rc; |
73 | #ifndef HAVE_DIRCACHE | 112 | struct filestr_cache *cachep = file->stream.cachep; |
74 | (void)use_cache; | ||
75 | #endif | ||
76 | 113 | ||
77 | LDEBUGF("open(\"%s\",%d)\n",pathname,flags); | 114 | DEBUGF("Flushing dirty sector cache (%lu)\n", cachep->sector); |
78 | 115 | ||
79 | if ( pathname[0] != '/' ) { | 116 | if (fat_query_sectornum(&file->stream.fatstr) != cachep->sector) |
80 | DEBUGF("'%s' is not an absolute path.\n",pathname); | 117 | { |
81 | DEBUGF("Only absolute pathnames supported at the moment\n"); | 118 | /* get on the correct sector */ |
82 | errno = EINVAL; | 119 | rc = fat_seek(&file->stream.fatstr, cachep->sector); |
83 | return -1; | 120 | if (rc < 0) |
121 | FILE_ERROR(EIO, rc * 10 - 1); | ||
84 | } | 122 | } |
85 | 123 | ||
86 | /* find a free file descriptor */ | 124 | rc = fat_readwrite(&file->stream.fatstr, 1, cachep->buffer, true); |
87 | for ( fd=0; fd<MAX_OPEN_FILES; fd++ ) | 125 | if (rc < 0) |
88 | if ( !openfiles[fd].busy ) | 126 | { |
89 | break; | 127 | if (rc == FAT_RC_ENOSPC) |
90 | 128 | FILE_ERROR(ENOSPC, RC); | |
91 | if ( fd == MAX_OPEN_FILES ) { | 129 | else |
92 | DEBUGF("Too many files open\n"); | 130 | FILE_ERROR(EIO, rc * 10 - 2); |
93 | errno = EMFILE; | ||
94 | return -2; | ||
95 | } | 131 | } |
96 | 132 | ||
97 | file = &openfiles[fd]; | 133 | cachep->flags = 0; |
98 | memset(file, 0, sizeof(struct filedesc)); | 134 | return 1; |
135 | file_error: | ||
136 | DEBUGF("Failed flushing cache: %d\n", rc); | ||
137 | return rc; | ||
138 | } | ||
139 | |||
140 | static void discard_cache(struct filestr_desc *file) | ||
141 | { | ||
142 | struct filestr_cache *const cachep = file->stream.cachep; | ||
143 | cachep->flags = 0; | ||
144 | } | ||
99 | 145 | ||
100 | if (flags & (O_RDWR | O_WRONLY)) { | 146 | /* set the file pointer */ |
101 | file->write = true; | 147 | static off_t lseek_internal(struct filestr_desc *file, off_t offset, |
148 | int whence) | ||
149 | { | ||
150 | off_t rc; | ||
151 | file_size_t pos; | ||
102 | 152 | ||
103 | if (flags & O_TRUNC) | 153 | file_size_t size = MIN(*file->sizep, FILE_SIZE_MAX); |
104 | file->trunc = true; | ||
105 | } | ||
106 | file->busy = true; | ||
107 | 154 | ||
108 | #ifdef HAVE_DIRCACHE | 155 | switch (whence) |
109 | if (dircache_is_enabled() && !file->write && use_cache) | ||
110 | { | 156 | { |
111 | # ifdef HAVE_MULTIVOLUME | 157 | case SEEK_SET: |
112 | int volume = strip_volume(pathname, pathnamecopy); | 158 | if (offset < 0 || (file_size_t)offset > size) |
113 | # endif | 159 | FILE_ERROR(EINVAL, -1); |
114 | 160 | ||
115 | int ce = dircache_get_entry_id(pathname); | 161 | pos = offset; |
116 | if (ce < 0) | 162 | break; |
117 | { | ||
118 | errno = ENOENT; | ||
119 | file->busy = false; | ||
120 | return -7; | ||
121 | } | ||
122 | 163 | ||
123 | long startcluster = _dircache_get_entry_startcluster(ce); | 164 | case SEEK_CUR: |
124 | fat_open(IF_MV(volume,) | 165 | if ((offset < 0 && (file_size_t)-offset > file->offset) || |
125 | startcluster, | 166 | (offset > 0 && (file_size_t)offset > size - file->offset)) |
126 | &(file->fatfile), | 167 | FILE_ERROR(EINVAL, -1); |
127 | NULL); | ||
128 | struct dirinfo *info = _dircache_get_entry_dirinfo(ce); | ||
129 | file->size = info->size; | ||
130 | file->attr = info->attribute; | ||
131 | file->cacheoffset = -1; | ||
132 | file->fileoffset = 0; | ||
133 | 168 | ||
134 | return fd; | 169 | pos = file->offset + offset; |
135 | } | 170 | break; |
136 | #endif | ||
137 | 171 | ||
138 | strlcpy(pathnamecopy, pathname, pathnamesize); | 172 | case SEEK_END: |
173 | if (offset > 0 || (file_size_t)-offset > size) | ||
174 | FILE_ERROR(EINVAL, -1); | ||
139 | 175 | ||
140 | /* locate filename */ | 176 | pos = size + offset; |
141 | name=strrchr(pathnamecopy+1,'/'); | 177 | break; |
142 | if ( name ) { | 178 | |
143 | *name = 0; | 179 | default: |
144 | dir = opendir_uncached(pathnamecopy); | 180 | FILE_ERROR(EINVAL, -1); |
145 | *name = '/'; | ||
146 | name++; | ||
147 | } | ||
148 | else { | ||
149 | dir = opendir_uncached("/"); | ||
150 | name = pathnamecopy+1; | ||
151 | } | ||
152 | if (!dir) { | ||
153 | DEBUGF("Failed opening dir\n"); | ||
154 | errno = EIO; | ||
155 | file->busy = false; | ||
156 | return -4; | ||
157 | } | ||
158 | |||
159 | if(name[0] == 0) { | ||
160 | DEBUGF("Empty file name\n"); | ||
161 | errno = EINVAL; | ||
162 | file->busy = false; | ||
163 | closedir_uncached(dir); | ||
164 | return -5; | ||
165 | } | ||
166 | |||
167 | /* scan dir for name */ | ||
168 | while ((entry = readdir_uncached(dir))) { | ||
169 | if ( !strcasecmp(name, entry->d_name) ) { | ||
170 | fat_open(IF_MV(dir->fatdir.file.volume,) | ||
171 | entry->startcluster, | ||
172 | &(file->fatfile), | ||
173 | &(dir->fatdir)); | ||
174 | file->size = file->trunc ? 0 : entry->info.size; | ||
175 | file->attr = entry->info.attribute; | ||
176 | break; | ||
177 | } | ||
178 | } | 181 | } |
179 | 182 | ||
180 | if ( !entry ) { | 183 | file->offset = pos; |
181 | LDEBUGF("Didn't find file %s\n",name); | 184 | |
182 | if ( file->write && (flags & O_CREAT) ) { | 185 | return pos; |
183 | rc = fat_create_file(name, | 186 | file_error: |
184 | &(file->fatfile), | 187 | return rc; |
185 | &(dir->fatdir)); | 188 | } |
186 | if (rc < 0) { | 189 | |
187 | DEBUGF("Couldn't create %s in %s\n",name,pathnamecopy); | 190 | /* callback for each file stream to make sure all data is in sync with new |
188 | errno = EIO; | 191 | size */ |
189 | file->busy = false; | 192 | void ftruncate_internal_callback(struct filestr_base *stream, |
190 | closedir_uncached(dir); | 193 | struct filestr_base *s) |
191 | return rc * 10 - 6; | 194 | { |
195 | struct filestr_desc *file = (struct filestr_desc *)s; | ||
196 | file_size_t size = *file->sizep; | ||
197 | |||
198 | /* caches with data beyond new extents are invalid */ | ||
199 | unsigned long sector = file->stream.cachep->sector; | ||
200 | if (sector != INVALID_SECNUM && sector >= filesize_sectors(size)) | ||
201 | filestr_discard_cache(&file->stream); | ||
202 | |||
203 | /* keep all positions within bounds */ | ||
204 | if (file->offset > size) | ||
205 | file->offset = size; | ||
206 | |||
207 | (void)stream; | ||
208 | } | ||
209 | |||
210 | /* truncate the file to the specified length */ | ||
211 | static int ftruncate_internal(struct filestr_desc *file, file_size_t size, | ||
212 | bool write_now) | ||
213 | { | ||
214 | int rc = 0, rc2 = 1; | ||
215 | |||
216 | file_size_t cursize = *file->sizep; | ||
217 | file_size_t truncsize = MIN(size, cursize); | ||
218 | |||
219 | if (write_now) | ||
220 | { | ||
221 | unsigned long sector = filesize_sectors(truncsize); | ||
222 | struct filestr_cache *const cachep = file->stream.cachep; | ||
223 | |||
224 | if (cachep->flags == (FSC_NEW|FSC_DIRTY) && | ||
225 | cachep->sector + 1 == sector) | ||
226 | { | ||
227 | /* sector created but may have never been added to the cluster | ||
228 | chain; flush it now or the subsequent may fail */ | ||
229 | rc2 = flush_cache(file); | ||
230 | if (rc2 == FAT_RC_ENOSPC) | ||
231 | { | ||
232 | /* no space left on device; further truncation needed */ | ||
233 | discard_cache(file); | ||
234 | truncsize = ALIGN_DOWN(truncsize - 1, SECTOR_SIZE); | ||
235 | sector--; | ||
236 | rc = rc2; | ||
192 | } | 237 | } |
193 | #ifdef HAVE_DIRCACHE | 238 | else if (rc2 < 0) |
194 | dircache_add_file(pathname, file->fatfile.firstcluster); | 239 | FILE_ERROR(ERRNO, rc2 * 10 - 1); |
195 | #endif | ||
196 | file->size = 0; | ||
197 | file->attr = 0; | ||
198 | } | ||
199 | else { | ||
200 | DEBUGF("Couldn't find %s in %s\n",name,pathnamecopy); | ||
201 | errno = ENOENT; | ||
202 | file->busy = false; | ||
203 | closedir_uncached(dir); | ||
204 | return -7; | ||
205 | } | ||
206 | } else { | ||
207 | if(file->write && (file->attr & FAT_ATTR_DIRECTORY)) { | ||
208 | errno = EISDIR; | ||
209 | file->busy = false; | ||
210 | closedir_uncached(dir); | ||
211 | return -8; | ||
212 | } | 240 | } |
213 | } | ||
214 | closedir_uncached(dir); | ||
215 | 241 | ||
216 | file->cacheoffset = -1; | 242 | rc2 = fat_seek(&file->stream.fatstr, sector); |
217 | file->fileoffset = 0; | 243 | if (rc2 < 0) |
244 | FILE_ERROR(EIO, rc2 * 10 - 2); | ||
218 | 245 | ||
219 | if (file->write && (flags & O_APPEND)) { | 246 | rc2 = fat_truncate(&file->stream.fatstr); |
220 | rc = lseek(fd,0,SEEK_END); | 247 | if (rc2 < 0) |
221 | if (rc < 0 ) | 248 | FILE_ERROR(EIO, rc2 * 10 - 3); |
222 | return rc * 10 - 9; | ||
223 | } | 249 | } |
250 | /* else just change the cached file size */ | ||
224 | 251 | ||
225 | #ifdef HAVE_DIRCACHE | 252 | if (truncsize < cursize) |
226 | if (file->write) | 253 | { |
227 | dircache_bind(fd, pathname); | 254 | *file->sizep = truncsize; |
228 | #endif | 255 | fileop_ontruncate_internal(&file->stream); |
256 | } | ||
229 | 257 | ||
230 | return fd; | 258 | /* if truncation was partially successful, it effectively destroyed |
231 | } | 259 | everything after the truncation point; still, indicate failure |
260 | after adjusting size */ | ||
261 | if (rc2 == 0) | ||
262 | FILE_ERROR(EIO, -4); | ||
263 | else if (rc2 < 0) | ||
264 | FILE_ERROR(ERRNO, rc2); | ||
232 | 265 | ||
233 | int file_open(const char* pathname, int flags) | 266 | file_error: |
234 | { | 267 | return rc; |
235 | /* By default, use the dircache if available. */ | ||
236 | return open_internal(pathname, flags, true); | ||
237 | } | 268 | } |
238 | 269 | ||
239 | int close(int fd) | 270 | /* flush back all outstanding writes to the file */ |
271 | static int fsync_internal(struct filestr_desc *file) | ||
240 | { | 272 | { |
241 | struct filedesc* file = &openfiles[fd]; | 273 | /* call only when holding WRITER lock (updates directory entries) */ |
242 | int rc = 0; | 274 | int rc = 0; |
243 | 275 | ||
244 | LDEBUGF("close(%d)\n", fd); | 276 | file_size_t size = *file->sizep; |
277 | unsigned int foflags = fileobj_get_flags(&file->stream); | ||
278 | |||
279 | /* flush sector cache? */ | ||
280 | struct filestr_cache *const cachep = file->stream.cachep; | ||
281 | if (cachep->flags & FSC_DIRTY) | ||
282 | { | ||
283 | int rc2 = flush_cache(file); | ||
284 | if (rc2 == FAT_RC_ENOSPC && (cachep->flags & FSC_NEW)) | ||
285 | { | ||
286 | /* no space left on device so this must be dropped */ | ||
287 | discard_cache(file); | ||
288 | size = ALIGN_DOWN(size - 1, SECTOR_SIZE); | ||
289 | foflags |= FO_TRUNC; | ||
290 | rc = rc2; | ||
291 | } | ||
292 | else if (rc2 < 0) | ||
293 | FILE_ERROR(ERRNO, rc2 * 10 - 1); | ||
294 | } | ||
295 | |||
296 | /* truncate? */ | ||
297 | if (foflags & FO_TRUNC) | ||
298 | { | ||
299 | int rc2 = ftruncate_internal(file, size, rc == 0); | ||
300 | if (rc2 < 0) | ||
301 | FILE_ERROR(ERRNO, rc2 * 10 - 2); | ||
245 | 302 | ||
246 | if (fd < 0 || fd > MAX_OPEN_FILES-1) { | 303 | /* never needs to be done this way again since any data beyond the |
247 | errno = EINVAL; | 304 | cached size is now gone */ |
248 | return -1; | 305 | fileobj_change_flags(&file->stream, 0, FO_TRUNC); |
249 | } | 306 | } |
250 | if (!file->busy) { | 307 | |
251 | errno = EBADF; | 308 | file_error:; |
252 | return -2; | 309 | /* tie up all loose ends (try to close the file even if failing) */ |
310 | int rc2 = fat_closewrite(&file->stream.fatstr, size, | ||
311 | get_dir_fatent_dircache()); | ||
312 | if (rc2 >= 0) | ||
313 | fileop_onsync_internal(&file->stream); /* dir_fatent is implicit arg */ | ||
314 | |||
315 | if (rc2 < 0 && rc >= 0) | ||
316 | { | ||
317 | errno = EIO; | ||
318 | rc = rc2 * 10 - 3; | ||
253 | } | 319 | } |
254 | if (file->write) { | 320 | |
255 | rc = fsync(fd); | 321 | return rc; |
322 | } | ||
323 | |||
324 | /* finish with the file and free resources */ | ||
325 | static int close_internal(struct filestr_desc *file) | ||
326 | { | ||
327 | /* call only when holding WRITER lock (updates directory entries) */ | ||
328 | int rc; | ||
329 | |||
330 | if ((file->stream.flags & FD_WRITE) && | ||
331 | !(fileobj_get_flags(&file->stream) & FO_REMOVED)) | ||
332 | { | ||
333 | rc = fsync_internal(file); | ||
256 | if (rc < 0) | 334 | if (rc < 0) |
257 | return rc * 10 - 3; | 335 | FILE_ERROR(ERRNO, rc * 10 - 1); |
258 | #ifdef HAVE_DIRCACHE | ||
259 | dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); | ||
260 | dircache_update_filetime(fd); | ||
261 | #endif | ||
262 | } | 336 | } |
263 | 337 | ||
264 | file->busy = false; | 338 | rc = 0; |
265 | return 0; | 339 | file_error:; |
340 | int rc2 = close_stream_internal(&file->stream); | ||
341 | if (rc2 < 0 && rc >= 0) | ||
342 | rc = rc2 * 10 - 2; | ||
343 | return rc; | ||
266 | } | 344 | } |
267 | 345 | ||
268 | int fsync(int fd) | 346 | /* actually do the open gruntwork */ |
347 | static int open_internal_inner2(const char *path, | ||
348 | struct filestr_desc *file, | ||
349 | unsigned int callflags) | ||
269 | { | 350 | { |
270 | struct filedesc* file = &openfiles[fd]; | 351 | int rc; |
271 | int rc = 0; | ||
272 | |||
273 | LDEBUGF("fsync(%d)\n", fd); | ||
274 | 352 | ||
275 | if (fd < 0 || fd > MAX_OPEN_FILES-1) { | 353 | struct path_component_info compinfo; |
276 | errno = EINVAL; | 354 | rc = open_stream_internal(path, callflags, &file->stream, &compinfo); |
277 | return -1; | 355 | if (rc < 0) |
278 | } | 356 | { |
279 | if (!file->busy) { | 357 | DEBUGF("Open failed: %d\n", rc); |
280 | errno = EBADF; | 358 | FILE_ERROR_RETURN(ERRNO, rc * 10 - 1); |
281 | return -2; | ||
282 | } | 359 | } |
283 | if (file->write) { | 360 | |
284 | /* flush sector cache */ | 361 | bool created = false; |
285 | if ( file->dirty ) { | 362 | |
286 | rc = flush_cache(fd); | 363 | if (rc > 0) |
287 | if (rc < 0) | 364 | { |
288 | { | 365 | if (callflags & FF_EXCL) |
289 | /* when failing, try to close the file anyway */ | 366 | { |
290 | fat_closewrite(&(file->fatfile), file->size, file->attr); | 367 | DEBUGF("File exists\n"); |
291 | return rc * 10 - 3; | 368 | FILE_ERROR(EEXIST, -2); |
292 | } | ||
293 | } | 369 | } |
294 | 370 | ||
295 | /* truncate? */ | 371 | if (compinfo.attr & ATTR_DIRECTORY) |
296 | if (file->trunc) { | 372 | { |
297 | rc = ftruncate(fd, file->size); | 373 | if ((callflags & FD_WRITE) || !(callflags & FF_ANYTYPE)) |
298 | if (rc < 0) | ||
299 | { | 374 | { |
300 | /* when failing, try to close the file anyway */ | 375 | DEBUGF("File is a directory\n"); |
301 | fat_closewrite(&(file->fatfile), file->size, file->attr); | 376 | FILE_ERROR(EISDIR, -3); |
302 | return rc * 10 - 4; | ||
303 | } | 377 | } |
378 | |||
379 | compinfo.filesize = MAX_DIRECTORY_SIZE; /* allow file ops */ | ||
380 | } | ||
381 | } | ||
382 | else if (callflags & FF_CREAT) | ||
383 | { | ||
384 | if (compinfo.attr & ATTR_DIRECTORY) | ||
385 | { | ||
386 | DEBUGF("File is a directory\n"); | ||
387 | FILE_ERROR(EISDIR, -5); | ||
304 | } | 388 | } |
305 | 389 | ||
306 | /* tie up all loose ends */ | 390 | /* not found; try to create it */ |
307 | rc = fat_closewrite(&(file->fatfile), file->size, file->attr); | 391 | |
392 | callflags &= ~FO_TRUNC; | ||
393 | rc = create_stream_internal(&compinfo.parentinfo, compinfo.name, | ||
394 | compinfo.length, ATTR_NEW_FILE, callflags, | ||
395 | &file->stream); | ||
308 | if (rc < 0) | 396 | if (rc < 0) |
309 | return rc * 10 - 5; | 397 | FILE_ERROR(ERRNO, rc * 10 - 6); |
310 | } | ||
311 | return 0; | ||
312 | } | ||
313 | 398 | ||
314 | int remove(const char* name) | 399 | created = true; |
315 | { | ||
316 | int rc; | ||
317 | struct filedesc* file; | ||
318 | /* Can't use dircache now, because we need to access the fat structures. */ | ||
319 | int fd = open_internal(name, O_WRONLY, false); | ||
320 | if ( fd < 0 ) | ||
321 | return fd * 10 - 1; | ||
322 | |||
323 | file = &openfiles[fd]; | ||
324 | #ifdef HAVE_DIRCACHE | ||
325 | dircache_remove(name); | ||
326 | #endif | ||
327 | rc = fat_remove(&(file->fatfile)); | ||
328 | if ( rc < 0 ) { | ||
329 | DEBUGF("Failed removing file: %d\n", rc); | ||
330 | errno = EIO; | ||
331 | return rc * 10 - 3; | ||
332 | } | 400 | } |
401 | else | ||
402 | { | ||
403 | DEBUGF("File not found\n"); | ||
404 | FILE_ERROR(ENOENT, -7); | ||
405 | } | ||
406 | |||
407 | fat_rewind(&file->stream.fatstr); | ||
408 | file->sizep = fileobj_get_sizep(&file->stream); | ||
409 | file->offset = 0; | ||
410 | |||
411 | if (!created) | ||
412 | { | ||
413 | /* size from storage applies to first stream only otherwise it's | ||
414 | already up to date */ | ||
415 | const bool first = fileobj_get_flags(&file->stream) & FO_SINGLE; | ||
416 | if (first) | ||
417 | *file->sizep = compinfo.filesize; | ||
333 | 418 | ||
334 | file->size = 0; | 419 | if (callflags & FO_TRUNC) |
420 | { | ||
421 | /* if the file is kind of "big" then free some space now */ | ||
422 | rc = ftruncate_internal(file, 0, *file->sizep >= O_TRUNC_THRESH); | ||
423 | if (rc < 0) | ||
424 | { | ||
425 | DEBUGF("O_TRUNC failed: %d\n", rc); | ||
426 | FILE_ERROR(ERRNO, rc * 10 - 4); | ||
427 | } | ||
428 | } | ||
429 | } | ||
335 | 430 | ||
336 | rc = close(fd); | 431 | rc = 0; |
337 | if (rc<0) | 432 | file_error: |
338 | return rc * 10 - 4; | 433 | if (rc < 0) |
434 | close_stream_internal(&file->stream); | ||
339 | 435 | ||
340 | return 0; | 436 | return rc; |
341 | } | 437 | } |
342 | 438 | ||
343 | int rename(const char* path, const char* newpath) | 439 | /* allocate a file descriptor, if needed, assemble stream flags and open |
440 | a new stream */ | ||
441 | static int open_internal_inner1(const char *path, int oflag, | ||
442 | unsigned int callflags) | ||
344 | { | 443 | { |
345 | int rc, fd; | 444 | DEBUGF("%s(path=\"%s\",oflag=%X,callflags=%X)\n", __func__, |
346 | DIR_UNCACHED* dir; | 445 | path, oflag, callflags); |
347 | char* nameptr; | ||
348 | char* dirptr; | ||
349 | struct filedesc* file; | ||
350 | char newpath2[MAX_PATH]; | ||
351 | 446 | ||
352 | /* verify new path does not already exist */ | 447 | int rc; |
353 | /* If it is a directory, errno == EISDIR if the name exists */ | ||
354 | fd = open(newpath, O_RDONLY); | ||
355 | if ( fd >= 0 || errno == EISDIR) { | ||
356 | close(fd); | ||
357 | errno = EBUSY; | ||
358 | return -1; | ||
359 | } | ||
360 | close(fd); | ||
361 | 448 | ||
362 | fd = open_internal(path, O_RDONLY, false); | 449 | struct filestr_desc *file; |
363 | if ( fd < 0 ) { | 450 | int fildes = alloc_filestr(&file); |
364 | errno = EIO; | 451 | if (fildes < 0) |
365 | return fd * 10 - 2; | 452 | FILE_ERROR(EMFILE, -1); |
366 | } | ||
367 | 453 | ||
368 | /* extract new file name */ | 454 | callflags &= ~FDO_MASK; |
369 | nameptr = strrchr(newpath,'/'); | ||
370 | if (nameptr) | ||
371 | nameptr++; | ||
372 | else { | ||
373 | close(fd); | ||
374 | return - 3; | ||
375 | } | ||
376 | 455 | ||
377 | /* Extract new path */ | 456 | if (oflag & O_ACCMODE) |
378 | strcpy(newpath2, newpath); | 457 | { |
458 | callflags |= FD_WRITE; | ||
379 | 459 | ||
380 | dirptr = strrchr(newpath2,'/'); | 460 | if ((oflag & O_ACCMODE) == O_WRONLY) |
381 | if(dirptr) | 461 | callflags |= FD_WRONLY; |
382 | *dirptr = 0; | ||
383 | else { | ||
384 | close(fd); | ||
385 | return - 4; | ||
386 | } | ||
387 | 462 | ||
388 | dirptr = newpath2; | 463 | if (oflag & O_APPEND) |
464 | callflags |= FD_APPEND; | ||
389 | 465 | ||
390 | if(strlen(dirptr) == 0) { | 466 | if (oflag & O_TRUNC) |
391 | dirptr = "/"; | 467 | callflags |= FO_TRUNC; |
392 | } | 468 | } |
393 | 469 | else if (oflag & O_TRUNC) | |
394 | dir = opendir_uncached(dirptr); | 470 | { |
395 | if(!dir) { | 471 | /* O_TRUNC requires write mode */ |
396 | close(fd); | 472 | DEBUGF("No write mode but have O_TRUNC\n"); |
397 | return - 5; | 473 | FILE_ERROR(EINVAL, -2); |
398 | } | 474 | } |
399 | 475 | ||
400 | file = &openfiles[fd]; | 476 | /* O_CREAT and O_APPEND are fine without write mode |
477 | * for the former, an empty file is created but no data may be written | ||
478 | * for the latter, no append will be allowed anyway */ | ||
479 | if (oflag & O_CREAT) | ||
480 | { | ||
481 | callflags |= FF_CREAT; | ||
401 | 482 | ||
402 | rc = fat_rename(&file->fatfile, &dir->fatdir, nameptr, | 483 | if (oflag & O_EXCL) |
403 | file->size, file->attr); | 484 | callflags |= FF_EXCL; |
404 | #ifdef HAVE_MULTIVOLUME | ||
405 | if ( rc == -1) { | ||
406 | close(fd); | ||
407 | closedir_uncached(dir); | ||
408 | DEBUGF("Failed renaming file across volumnes: %d\n", rc); | ||
409 | errno = EXDEV; | ||
410 | return -6; | ||
411 | } | ||
412 | #endif | ||
413 | if ( rc < 0 ) { | ||
414 | close(fd); | ||
415 | closedir_uncached(dir); | ||
416 | DEBUGF("Failed renaming file: %d\n", rc); | ||
417 | errno = EIO; | ||
418 | return rc * 10 - 7; | ||
419 | } | 485 | } |
420 | 486 | ||
421 | #ifdef HAVE_DIRCACHE | 487 | rc = open_internal_inner2(path, file, callflags); |
422 | dircache_rename(path, newpath); | 488 | if (rc < 0) |
423 | #endif | 489 | FILE_ERROR(ERRNO, rc * 10 - 3); |
424 | 490 | ||
425 | rc = close(fd); | 491 | return fildes; |
426 | if (rc<0) { | ||
427 | closedir_uncached(dir); | ||
428 | errno = EIO; | ||
429 | return rc * 10 - 8; | ||
430 | } | ||
431 | 492 | ||
432 | rc = closedir_uncached(dir); | 493 | file_error: |
433 | if (rc<0) { | 494 | return rc; |
434 | errno = EIO; | 495 | } |
435 | return rc * 10 - 9; | ||
436 | } | ||
437 | 496 | ||
438 | return 0; | 497 | static int open_internal_locked(const char *path, int oflag, |
498 | unsigned int callflags) | ||
499 | { | ||
500 | file_internal_lock_WRITER(); | ||
501 | int rc = open_internal_inner1(path, oflag, callflags); | ||
502 | file_internal_unlock_WRITER(); | ||
503 | return rc; | ||
439 | } | 504 | } |
440 | 505 | ||
441 | int ftruncate(int fd, off_t size) | 506 | /* fill a cache buffer with a new sector */ |
507 | static int readwrite_fill_cache(struct filestr_desc *file, unsigned long sector, | ||
508 | unsigned long filesectors, bool write) | ||
442 | { | 509 | { |
443 | int rc, sector; | 510 | /* sector != cachep->sector should have been checked by now */ |
444 | struct filedesc* file = &openfiles[fd]; | ||
445 | 511 | ||
446 | sector = size / SECTOR_SIZE; | 512 | int rc; |
447 | if (size % SECTOR_SIZE) | 513 | struct filestr_cache *cachep = filestr_get_cache(&file->stream); |
448 | sector++; | ||
449 | 514 | ||
450 | rc = fat_seek(&(file->fatfile), sector); | 515 | if (cachep->flags & FSC_DIRTY) |
451 | if (rc < 0) { | 516 | { |
452 | errno = EIO; | 517 | rc = flush_cache(file); |
453 | return rc * 10 - 1; | 518 | if (rc < 0) |
519 | FILE_ERROR(ERRNO, rc * 10 - 1); | ||
454 | } | 520 | } |
455 | 521 | ||
456 | rc = fat_truncate(&(file->fatfile)); | 522 | if (fat_query_sectornum(&file->stream.fatstr) != sector) |
457 | if (rc < 0) { | 523 | { |
458 | errno = EIO; | 524 | /* get on the correct sector */ |
459 | return rc * 10 - 2; | 525 | rc = fat_seek(&file->stream.fatstr, sector); |
526 | if (rc < 0) | ||
527 | FILE_ERROR(EIO, rc * 10 - 2); | ||
460 | } | 528 | } |
461 | 529 | ||
462 | file->size = size; | 530 | if (!write || sector < filesectors) |
463 | #ifdef HAVE_DIRCACHE | 531 | { |
464 | dircache_update_filesize(fd, size, file->fatfile.firstcluster); | 532 | /* only reading or this sector would have been flushed if the cache |
465 | #endif | 533 | was previously needed for a different sector */ |
534 | rc = fat_readwrite(&file->stream.fatstr, 1, cachep->buffer, false); | ||
535 | if (rc < 0) | ||
536 | FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 3); | ||
537 | } | ||
538 | else | ||
539 | { | ||
540 | /* create a fresh, shiny, new sector with that new sector smell */ | ||
541 | cachep->flags = FSC_NEW; | ||
542 | } | ||
466 | 543 | ||
467 | return 0; | 544 | cachep->sector = sector; |
545 | return 1; | ||
546 | file_error: | ||
547 | DEBUGF("Failed caching sector: %d\n", rc); | ||
548 | return rc; | ||
468 | } | 549 | } |
469 | 550 | ||
470 | static int flush_cache(int fd) | 551 | /* read or write to part or all of the cache buffer */ |
552 | static inline void readwrite_cache(struct filestr_cache *cachep, void *buf, | ||
553 | unsigned long secoffset, size_t nbyte, | ||
554 | bool write) | ||
471 | { | 555 | { |
472 | int rc; | 556 | void *dst, *cbufp = cachep->buffer + secoffset; |
473 | struct filedesc* file = &openfiles[fd]; | ||
474 | long sector = file->fileoffset / SECTOR_SIZE; | ||
475 | |||
476 | DEBUGF("Flushing dirty sector cache\n"); | ||
477 | 557 | ||
478 | /* make sure we are on correct sector */ | 558 | if (write) |
479 | rc = fat_seek(&(file->fatfile), sector); | 559 | { |
480 | if ( rc < 0 ) | 560 | dst = cbufp; |
481 | return rc * 10 - 3; | 561 | cachep->flags |= FSC_DIRTY; |
482 | 562 | } | |
483 | rc = fat_readwrite(&(file->fatfile), 1, file->cache, true ); | 563 | else |
564 | { | ||
565 | dst = buf; | ||
566 | buf = cbufp; | ||
567 | } | ||
484 | 568 | ||
485 | if ( rc < 0 ) { | 569 | memcpy(dst, buf, nbyte); |
486 | if(file->fatfile.eof) | 570 | } |
487 | errno = ENOSPC; | ||
488 | 571 | ||
489 | return rc * 10 - 2; | 572 | /* read or write a partial sector using the file's cache */ |
573 | static inline ssize_t readwrite_partial(struct filestr_desc *file, | ||
574 | struct filestr_cache *cachep, | ||
575 | unsigned long sector, | ||
576 | unsigned long secoffset, | ||
577 | void *buf, | ||
578 | size_t nbyte, | ||
579 | unsigned long filesectors, | ||
580 | unsigned int flags) | ||
581 | { | ||
582 | if (sector != cachep->sector) | ||
583 | { | ||
584 | /* wrong sector in buffer */ | ||
585 | int rc = readwrite_fill_cache(file, sector, filesectors, flags); | ||
586 | if (rc <= 0) | ||
587 | return rc; | ||
490 | } | 588 | } |
491 | 589 | ||
492 | file->dirty = false; | 590 | readwrite_cache(cachep, buf, secoffset, nbyte, flags); |
493 | 591 | return nbyte; | |
494 | return 0; | ||
495 | } | 592 | } |
496 | 593 | ||
497 | static int readwrite(int fd, void* buf, long count, bool write) | 594 | /* read from or write to the file; back end to read() and write() */ |
595 | static ssize_t readwrite(struct filestr_desc *file, void *buf, size_t nbyte, | ||
596 | bool write) | ||
498 | { | 597 | { |
499 | long sectors; | 598 | DEBUGF("readwrite(%p,%lx,%ld,%s)\n", |
500 | long nread=0; | 599 | file, (long)buf, nbyte, write ? "write" : "read"); |
501 | struct filedesc* file; | ||
502 | int rc; | ||
503 | #ifdef STORAGE_NEEDS_ALIGN | ||
504 | long i; | ||
505 | int rc2; | ||
506 | #endif | ||
507 | 600 | ||
508 | if (fd < 0 || fd > MAX_OPEN_FILES-1) { | 601 | const file_size_t size = *file->sizep; |
509 | errno = EINVAL; | 602 | file_size_t filerem; |
510 | return -1; | ||
511 | } | ||
512 | 603 | ||
513 | file = &openfiles[fd]; | 604 | if (write) |
605 | { | ||
606 | /* if opened in append mode, move pointer to end */ | ||
607 | if (file->stream.flags & FD_APPEND) | ||
608 | file->offset = MIN(size, FILE_SIZE_MAX); | ||
514 | 609 | ||
515 | if ( !file->busy ) { | 610 | filerem = FILE_SIZE_MAX - file->offset; |
516 | errno = EBADF; | 611 | } |
517 | return -1; | 612 | else |
613 | { | ||
614 | /* limit to maximum possible offset (EOF or FILE_SIZE_MAX) */ | ||
615 | filerem = MIN(size, FILE_SIZE_MAX) - file->offset; | ||
518 | } | 616 | } |
519 | 617 | ||
520 | if(file->attr & FAT_ATTR_DIRECTORY) { | 618 | if (nbyte > filerem) |
521 | errno = EISDIR; | 619 | { |
522 | return -1; | 620 | nbyte = filerem; |
621 | if (nbyte > 0) | ||
622 | {} | ||
623 | else if (write) | ||
624 | FILE_ERROR_RETURN(EFBIG, -1); /* would get too large */ | ||
625 | else if (file->offset >= FILE_SIZE_MAX) | ||
626 | FILE_ERROR_RETURN(EOVERFLOW, -2); /* can't read here */ | ||
523 | } | 627 | } |
524 | 628 | ||
525 | LDEBUGF( "readwrite(%d,%lx,%ld,%s)\n", | 629 | if (nbyte == 0) |
526 | fd,(long)buf,count,write?"write":"read"); | 630 | return 0; |
527 | 631 | ||
528 | /* attempt to read past EOF? */ | 632 | int rc = 0; |
529 | if (!write && count > file->size - file->fileoffset) | 633 | |
530 | count = file->size - file->fileoffset; | 634 | struct filestr_cache * const cachep = file->stream.cachep; |
635 | void * const bufstart = buf; | ||
636 | |||
637 | const unsigned long filesectors = filesize_sectors(size); | ||
638 | unsigned long sector = file->offset / SECTOR_SIZE; | ||
639 | unsigned long sectoroffs = file->offset % SECTOR_SIZE; | ||
531 | 640 | ||
532 | /* any head bytes? */ | 641 | /* any head bytes? */ |
533 | if ( file->cacheoffset != -1 ) { | 642 | if (sectoroffs) |
534 | int offs = file->cacheoffset; | 643 | { |
535 | int headbytes = MIN(count, SECTOR_SIZE - offs); | 644 | size_t headbytes = MIN(nbyte, SECTOR_SIZE - sectoroffs); |
645 | rc = readwrite_partial(file, cachep, sector, sectoroffs, buf, headbytes, | ||
646 | filesectors, write); | ||
647 | if (rc <= 0) | ||
648 | { | ||
649 | if (rc < 0) | ||
650 | FILE_ERROR(ERRNO, rc * 10 - 3); | ||
536 | 651 | ||
537 | if (write) { | 652 | nbyte = 0; /* eof, skip the rest */ |
538 | memcpy( file->cache + offs, buf, headbytes ); | ||
539 | file->dirty = true; | ||
540 | } | 653 | } |
541 | else { | 654 | else |
542 | memcpy( buf, file->cache + offs, headbytes ); | 655 | { |
656 | buf += rc; | ||
657 | nbyte -= rc; | ||
658 | sector++; /* if nbyte goes to 0, the rest is skipped anyway */ | ||
543 | } | 659 | } |
660 | } | ||
544 | 661 | ||
545 | if (offs + headbytes == SECTOR_SIZE) { | 662 | /* read/write whole sectors right into/from the supplied buffer */ |
546 | if (file->dirty) { | 663 | unsigned long sectorcount = nbyte / SECTOR_SIZE; |
547 | rc = flush_cache(fd); | ||
548 | if ( rc < 0 ) { | ||
549 | errno = EIO; | ||
550 | return rc * 10 - 2; | ||
551 | } | ||
552 | } | ||
553 | file->cacheoffset = -1; | ||
554 | } | ||
555 | else { | ||
556 | file->cacheoffset += headbytes; | ||
557 | } | ||
558 | 664 | ||
559 | nread = headbytes; | 665 | while (sectorcount) |
560 | count -= headbytes; | 666 | { |
561 | } | 667 | unsigned long runlen = sectorcount; |
562 | 668 | ||
563 | /* If the buffer has been modified, either it has been flushed already | 669 | /* if a cached sector is inside the transfer range, split the transfer |
564 | * (if (offs+headbytes == SECTOR_SIZE)...) or does not need to be (no | 670 | into two parts and use the cache for that sector to keep it coherent |
565 | * more data to follow in this call). Do NOT flush here. */ | 671 | without writeback */ |
672 | if (UNLIKELY(cachep->sector >= sector && | ||
673 | cachep->sector < sector + sectorcount)) | ||
674 | { | ||
675 | runlen = cachep->sector - sector; | ||
676 | } | ||
566 | 677 | ||
567 | /* read/write whole sectors right into/from the supplied buffer */ | 678 | if (runlen) |
568 | sectors = count / SECTOR_SIZE; | 679 | { |
569 | rc = 0; | 680 | if (fat_query_sectornum(&file->stream.fatstr) != sector) |
570 | if ( sectors ) { | ||
571 | #ifdef STORAGE_NEEDS_ALIGN | ||
572 | if (((uint32_t)buf + nread) & (CACHEALIGN_SIZE - 1)) | ||
573 | for (i = 0; i < sectors; i++) | ||
574 | { | 681 | { |
575 | if (write) memcpy(file->cache, buf+nread+i*SECTOR_SIZE, SECTOR_SIZE); | 682 | /* get on the correct sector */ |
576 | rc2 = fat_readwrite(&(file->fatfile), 1, file->cache, write ); | 683 | rc = 0; |
577 | if (rc2 < 0) | 684 | |
685 | /* If the dirty bit isn't set, we're somehow beyond the file | ||
686 | size and you can't explain _that_ */ | ||
687 | if (sector >= filesectors && cachep->flags == (FSC_NEW|FSC_DIRTY)) | ||
578 | { | 688 | { |
579 | rc = rc2; | 689 | rc = flush_cache(file); |
580 | break; | 690 | if (rc < 0) |
691 | FILE_ERROR(ERRNO, rc * 10 - 4); | ||
692 | |||
693 | if (cachep->sector + 1 == sector) | ||
694 | rc = 1; /* if now ok, don't seek */ | ||
695 | } | ||
696 | |||
697 | if (rc == 0) | ||
698 | { | ||
699 | rc = fat_seek(&file->stream.fatstr, sector); | ||
700 | if (rc < 0) | ||
701 | FILE_ERROR(EIO, rc * 10 - 5); | ||
581 | } | 702 | } |
582 | else rc += rc2; | ||
583 | if (!write) memcpy(buf+nread+i*SECTOR_SIZE, file->cache, SECTOR_SIZE); | ||
584 | } | ||
585 | else | ||
586 | #endif | ||
587 | rc = fat_readwrite(&(file->fatfile), sectors, (unsigned char*)buf+nread, write ); | ||
588 | if ( rc < 0 ) { | ||
589 | DEBUGF("Failed read/writing %ld sectors\n",sectors); | ||
590 | errno = EIO; | ||
591 | if(write && file->fatfile.eof) { | ||
592 | DEBUGF("No space left on device\n"); | ||
593 | errno = ENOSPC; | ||
594 | } else { | ||
595 | file->fileoffset += nread; | ||
596 | } | 703 | } |
597 | file->cacheoffset = -1; | 704 | |
598 | /* adjust file size to length written */ | 705 | rc = fat_readwrite(&file->stream.fatstr, runlen, buf, write); |
599 | if ( write && file->fileoffset > file->size ) | 706 | if (rc < 0) |
600 | { | 707 | { |
601 | file->size = file->fileoffset; | 708 | DEBUGF("I/O error %sing %ld sectors\n", sectors, |
602 | #ifdef HAVE_DIRCACHE | 709 | write ? "writ" : "read"); |
603 | dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); | 710 | FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, |
604 | #endif | 711 | rc * 10 - 6); |
605 | } | 712 | } |
606 | return nread ? nread : rc * 10 - 4; | 713 | else |
607 | } | 714 | { |
608 | else { | 715 | buf += rc * SECTOR_SIZE; |
609 | if ( rc > 0 ) { | 716 | nbyte -= rc * SECTOR_SIZE; |
610 | nread += rc * SECTOR_SIZE; | 717 | sector += rc; |
611 | count -= sectors * SECTOR_SIZE; | 718 | sectorcount -= rc; |
612 | 719 | ||
613 | /* if eof, skip tail bytes */ | 720 | /* if eof, skip tail bytes */ |
614 | if ( rc < sectors ) | 721 | if ((unsigned long)rc < runlen) |
615 | count = 0; | 722 | nbyte = 0; |
616 | } | 723 | |
617 | else { | 724 | if (!nbyte) |
618 | /* eof */ | 725 | break; |
619 | count=0; | ||
620 | } | 726 | } |
727 | } | ||
621 | 728 | ||
622 | file->cacheoffset = -1; | 729 | if (UNLIKELY(sectorcount && sector == cachep->sector)) |
730 | { | ||
731 | /* do this one sector with the cache */ | ||
732 | readwrite_cache(cachep, buf, 0, SECTOR_SIZE, write); | ||
733 | buf += SECTOR_SIZE; | ||
734 | nbyte -= SECTOR_SIZE; | ||
735 | sector++; | ||
736 | sectorcount--; | ||
623 | } | 737 | } |
624 | } | 738 | } |
625 | 739 | ||
626 | /* any tail bytes? */ | 740 | /* any tail bytes? */ |
627 | if ( count ) { | 741 | if (nbyte) |
628 | if (write) { | 742 | { |
629 | if ( file->fileoffset + nread < file->size ) { | 743 | /* tail bytes always start at sector offset 0 */ |
630 | /* sector is only partially filled. copy-back from disk */ | 744 | rc = readwrite_partial(file, cachep, sector, 0, buf, nbyte, |
631 | LDEBUGF("Copy-back tail cache\n"); | 745 | filesectors, write); |
632 | rc = fat_readwrite(&(file->fatfile), 1, file->cache, false ); | 746 | if (rc < 0) |
633 | if ( rc < 0 ) { | 747 | FILE_ERROR(ERRNO, rc * 10 - 7); |
634 | DEBUGF("Failed writing\n"); | ||
635 | errno = EIO; | ||
636 | file->fileoffset += nread; | ||
637 | file->cacheoffset = -1; | ||
638 | /* adjust file size to length written */ | ||
639 | if ( file->fileoffset > file->size ) | ||
640 | { | ||
641 | file->size = file->fileoffset; | ||
642 | #ifdef HAVE_DIRCACHE | ||
643 | dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); | ||
644 | #endif | ||
645 | } | ||
646 | return nread ? nread : rc * 10 - 5; | ||
647 | } | ||
648 | /* seek back one sector to put file position right */ | ||
649 | rc = fat_seek(&(file->fatfile), | ||
650 | (file->fileoffset + nread) / | ||
651 | SECTOR_SIZE); | ||
652 | if ( rc < 0 ) { | ||
653 | DEBUGF("fat_seek() failed\n"); | ||
654 | errno = EIO; | ||
655 | file->fileoffset += nread; | ||
656 | file->cacheoffset = -1; | ||
657 | /* adjust file size to length written */ | ||
658 | if ( file->fileoffset > file->size ) | ||
659 | { | ||
660 | file->size = file->fileoffset; | ||
661 | #ifdef HAVE_DIRCACHE | ||
662 | dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); | ||
663 | #endif | ||
664 | } | ||
665 | return nread ? nread : rc * 10 - 6; | ||
666 | } | ||
667 | } | ||
668 | memcpy( file->cache, (unsigned char*)buf + nread, count ); | ||
669 | file->dirty = true; | ||
670 | } | ||
671 | else { | ||
672 | rc = fat_readwrite(&(file->fatfile), 1, file->cache,false); | ||
673 | if (rc < 1 ) { | ||
674 | DEBUGF("Failed caching sector\n"); | ||
675 | errno = EIO; | ||
676 | file->fileoffset += nread; | ||
677 | file->cacheoffset = -1; | ||
678 | return nread ? nread : rc * 10 - 7; | ||
679 | } | ||
680 | memcpy( (unsigned char*)buf + nread, file->cache, count ); | ||
681 | } | ||
682 | 748 | ||
683 | nread += count; | 749 | buf += rc; |
684 | file->cacheoffset = count; | ||
685 | } | 750 | } |
686 | 751 | ||
687 | file->fileoffset += nread; | 752 | file_error:; |
688 | LDEBUGF("fileoffset: %ld\n", file->fileoffset); | 753 | #ifdef DEBUG |
754 | if (errno == ENOSPC) | ||
755 | DEBUGF("No space left on device\n"); | ||
756 | #endif | ||
689 | 757 | ||
690 | /* adjust file size to length written */ | 758 | size_t done = buf - bufstart; |
691 | if ( write && file->fileoffset > file->size ) | 759 | if (done) |
692 | { | 760 | { |
693 | file->size = file->fileoffset; | 761 | /* error or not, update the file offset and size if anything was |
694 | #ifdef HAVE_DIRCACHE | 762 | transferred */ |
695 | dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); | 763 | file->offset += done; |
696 | #endif | 764 | DEBUGF("file offset: %ld\n", file->offset); |
765 | |||
766 | /* adjust file size to length written */ | ||
767 | if (write && file->offset > size) | ||
768 | *file->sizep = file->offset; | ||
769 | |||
770 | if (rc > 0) | ||
771 | return done; | ||
697 | } | 772 | } |
698 | 773 | ||
699 | return nread; | 774 | return rc; |
700 | } | 775 | } |
701 | 776 | ||
702 | ssize_t write(int fd, const void* buf, size_t count) | 777 | |
778 | /** Internal interface **/ | ||
779 | |||
780 | /* open a file without codepage conversion during the directory search; | ||
781 | required to avoid any reentrancy when opening codepages and when scanning | ||
782 | directories internally, which could infinitely recurse and would corrupt | ||
783 | the static data */ | ||
784 | int open_noiso_internal(const char *path, int oflag) | ||
703 | { | 785 | { |
704 | if (!openfiles[fd].write) { | 786 | return open_internal_locked(path, oflag, FF_ANYTYPE | FF_NOISO); |
705 | errno = EACCES; | ||
706 | return -1; | ||
707 | } | ||
708 | return readwrite(fd, (void *)buf, count, true); | ||
709 | } | 787 | } |
710 | 788 | ||
711 | ssize_t read(int fd, void* buf, size_t count) | 789 | |
790 | /** POSIX **/ | ||
791 | |||
792 | /* open a file */ | ||
793 | int open(const char *path, int oflag) | ||
712 | { | 794 | { |
713 | return readwrite(fd, buf, count, false); | 795 | DEBUGF("open(path=\"%s\",oflag=%X)\n", path, (unsigned)oflag); |
796 | return open_internal_locked(path, oflag, FF_ANYTYPE); | ||
714 | } | 797 | } |
715 | 798 | ||
799 | /* create a new file or rewrite an existing one */ | ||
800 | int creat(const char *path) | ||
801 | { | ||
802 | DEBUGF("creat(path=\"%s\")\n", path); | ||
803 | return open_internal_locked(path, O_WRONLY|O_CREAT|O_TRUNC, FF_ANYTYPE); | ||
804 | } | ||
716 | 805 | ||
717 | off_t lseek(int fd, off_t offset, int whence) | 806 | /* close a file descriptor */ |
807 | int close(int fildes) | ||
718 | { | 808 | { |
719 | off_t pos; | 809 | DEBUGF("close(fd=%d)\n", fildes); |
720 | long newsector; | 810 | |
721 | long oldsector; | ||
722 | int sectoroffset; | ||
723 | int rc; | 811 | int rc; |
724 | struct filedesc* file = &openfiles[fd]; | ||
725 | 812 | ||
726 | LDEBUGF("lseek(%d,%ld,%d)\n",fd,offset,whence); | 813 | file_internal_lock_WRITER(); |
727 | 814 | ||
728 | if (fd < 0 || fd > MAX_OPEN_FILES-1) { | 815 | /* needs to work even if marked "nonexistant" */ |
729 | errno = EINVAL; | 816 | struct filestr_desc *file = &open_streams[fildes]; |
730 | return -1; | 817 | if ((unsigned int)fildes >= MAX_OPEN_FILES || !file->stream.flags) |
731 | } | 818 | { |
732 | if ( !file->busy ) { | 819 | DEBUGF("filedes %d not open\n", fildes); |
733 | errno = EBADF; | 820 | FILE_ERROR(EBADF, -2); |
734 | return -1; | ||
735 | } | 821 | } |
736 | 822 | ||
737 | switch ( whence ) { | 823 | rc = close_internal(file); |
738 | case SEEK_SET: | 824 | if (rc < 0) |
739 | pos = offset; | 825 | FILE_ERROR(ERRNO, rc * 10 - 3); |
740 | break; | ||
741 | 826 | ||
742 | case SEEK_CUR: | 827 | file_error: |
743 | pos = file->fileoffset + offset; | 828 | file_internal_unlock_WRITER(); |
744 | break; | 829 | return rc; |
830 | } | ||
831 | |||
832 | /* truncate a file to a specified length */ | ||
833 | int ftruncate(int fildes, off_t length) | ||
834 | { | ||
835 | DEBUGF("ftruncate(fd=%d,len=%ld)\n", fildes, (long)length); | ||
745 | 836 | ||
746 | case SEEK_END: | 837 | struct filestr_desc * const file = GET_FILESTR(READER, fildes); |
747 | pos = file->size + offset; | 838 | if (!file) |
748 | break; | 839 | FILE_ERROR_RETURN(ERRNO, -1); |
749 | 840 | ||
750 | default: | 841 | int rc; |
751 | errno = EINVAL; | 842 | |
752 | return -2; | 843 | if (!(file->stream.flags & FD_WRITE)) |
844 | { | ||
845 | DEBUGF("Descriptor is read-only mode\n"); | ||
846 | FILE_ERROR(EBADF, -2); | ||
753 | } | 847 | } |
754 | if ((pos < 0) || (pos > file->size)) { | 848 | |
755 | errno = EINVAL; | 849 | if (length < 0) |
756 | return -3; | 850 | { |
851 | DEBUGF("Length %ld is invalid\n", (long)length); | ||
852 | FILE_ERROR(EINVAL, -3); | ||
757 | } | 853 | } |
758 | 854 | ||
759 | /* new sector? */ | 855 | rc = ftruncate_internal(file, length, true); |
760 | newsector = pos / SECTOR_SIZE; | 856 | if (rc < 0) |
761 | oldsector = file->fileoffset / SECTOR_SIZE; | 857 | FILE_ERROR(ERRNO, rc * 10 - 4); |
762 | sectoroffset = pos % SECTOR_SIZE; | ||
763 | 858 | ||
764 | if ( (newsector != oldsector) || | 859 | file_error: |
765 | ((file->cacheoffset==-1) && sectoroffset) ) { | 860 | RELEASE_FILESTR(READER, file); |
861 | return rc; | ||
862 | } | ||
766 | 863 | ||
767 | if ( newsector != oldsector ) { | 864 | /* synchronize changes to a file */ |
768 | if (file->dirty) { | 865 | int fsync(int fildes) |
769 | rc = flush_cache(fd); | 866 | { |
770 | if (rc < 0) | 867 | DEBUGF("fsync(fd=%d)\n", fildes); |
771 | return rc * 10 - 5; | ||
772 | } | ||
773 | 868 | ||
774 | rc = fat_seek(&(file->fatfile), newsector); | 869 | struct filestr_desc * const file = GET_FILESTR(WRITER, fildes); |
775 | if ( rc < 0 ) { | 870 | if (!file) |
776 | errno = EIO; | 871 | FILE_ERROR_RETURN(ERRNO, -1); |
777 | return rc * 10 - 4; | 872 | |
778 | } | 873 | int rc; |
779 | } | 874 | |
780 | if ( sectoroffset ) { | 875 | if (!(file->stream.flags & FD_WRITE)) |
781 | rc = fat_readwrite(&(file->fatfile), 1, file->cache ,false); | 876 | { |
782 | if ( rc < 0 ) { | 877 | DEBUGF("Descriptor is read-only mode\n", fd); |
783 | errno = EIO; | 878 | FILE_ERROR(EINVAL, -2); |
784 | return rc * 10 - 6; | ||
785 | } | ||
786 | file->cacheoffset = sectoroffset; | ||
787 | } | ||
788 | else | ||
789 | file->cacheoffset = -1; | ||
790 | } | 879 | } |
791 | else | ||
792 | if ( file->cacheoffset != -1 ) | ||
793 | file->cacheoffset = sectoroffset; | ||
794 | 880 | ||
795 | file->fileoffset = pos; | 881 | rc = fsync_internal(file); |
882 | if (rc < 0) | ||
883 | FILE_ERROR(ERRNO, rc * 10 - 3); | ||
796 | 884 | ||
797 | return pos; | 885 | file_error: |
886 | RELEASE_FILESTR(WRITER, file); | ||
887 | return rc; | ||
798 | } | 888 | } |
799 | 889 | ||
800 | off_t filesize(int fd) | 890 | /* move the read/write file offset */ |
891 | off_t lseek(int fildes, off_t offset, int whence) | ||
801 | { | 892 | { |
802 | struct filedesc* file = &openfiles[fd]; | 893 | DEBUGF("lseek(fd=%d,ofs=%ld,wh=%d)\n", fildes, (long)offset, whence); |
803 | 894 | ||
804 | if (fd < 0 || fd > MAX_OPEN_FILES-1) { | 895 | struct filestr_desc * const file = GET_FILESTR(READER, fildes); |
805 | errno = EINVAL; | 896 | if (!file) |
806 | return -1; | 897 | FILE_ERROR_RETURN(ERRNO, -1); |
898 | |||
899 | off_t rc = lseek_internal(file, offset, whence); | ||
900 | if (rc < 0) | ||
901 | FILE_ERROR(ERRNO, rc * 10 - 2); | ||
902 | |||
903 | file_error: | ||
904 | RELEASE_FILESTR(READER, file); | ||
905 | return rc; | ||
906 | } | ||
907 | |||
908 | /* read from a file */ | ||
909 | ssize_t read(int fildes, void *buf, size_t nbyte) | ||
910 | { | ||
911 | struct filestr_desc * const file = GET_FILESTR(READER, fildes); | ||
912 | if (!file) | ||
913 | FILE_ERROR_RETURN(ERRNO, -1); | ||
914 | |||
915 | ssize_t rc; | ||
916 | |||
917 | if (file->stream.flags & FD_WRONLY) | ||
918 | { | ||
919 | DEBUGF("read(fd=%d,buf=%p,nb=%lu) - " | ||
920 | "descriptor is write-only mode\n", fildes, buf, nbyte); | ||
921 | FILE_ERROR(EBADF, -2); | ||
807 | } | 922 | } |
808 | if ( !file->busy ) { | 923 | |
809 | errno = EBADF; | 924 | rc = readwrite(file, buf, nbyte, false); |
810 | return -1; | 925 | if (rc < 0) |
926 | FILE_ERROR(ERRNO, rc * 10 - 3); | ||
927 | |||
928 | file_error: | ||
929 | RELEASE_FILESTR(READER, file); | ||
930 | return rc; | ||
931 | } | ||
932 | |||
933 | /* write on a file */ | ||
934 | ssize_t write(int fildes, const void *buf, size_t nbyte) | ||
935 | { | ||
936 | struct filestr_desc * const file = GET_FILESTR(READER, fildes); | ||
937 | if (!file) | ||
938 | FILE_ERROR_RETURN(ERRNO, -1); | ||
939 | |||
940 | ssize_t rc; | ||
941 | |||
942 | if (!(file->stream.flags & FD_WRITE)) | ||
943 | { | ||
944 | DEBUGF("write(fd=%d,buf=%p,nb=%lu) - " | ||
945 | "descriptor is read-only mode\n", fildes, buf, nbyte); | ||
946 | FILE_ERROR(EBADF, -2); | ||
811 | } | 947 | } |
812 | 948 | ||
813 | return file->size; | 949 | rc = readwrite(file, (void *)buf, nbyte, true); |
950 | if (rc < 0) | ||
951 | FILE_ERROR(ERRNO, rc * 10 - 3); | ||
952 | |||
953 | file_error: | ||
954 | RELEASE_FILESTR(READER, file); | ||
955 | return rc; | ||
814 | } | 956 | } |
815 | 957 | ||
958 | /* remove a file */ | ||
959 | int remove(const char *path) | ||
960 | { | ||
961 | DEBUGF("remove(path=\"%s\")\n", path); | ||
962 | |||
963 | file_internal_lock_WRITER(); | ||
964 | int rc = remove_stream_internal(path, NULL, FF_FILE); | ||
965 | file_internal_unlock_WRITER(); | ||
966 | return rc; | ||
967 | } | ||
816 | 968 | ||
817 | /* release all file handles on a given volume "by force", to avoid leaks */ | 969 | /* rename a file */ |
818 | int release_files(int volume) | 970 | int rename(const char *old, const char *new) |
819 | { | 971 | { |
820 | struct filedesc* pfile = openfiles; | 972 | DEBUGF("rename(old=\"%s\",new=\"%s\")\n", old, new); |
821 | int fd; | 973 | |
822 | int closed = 0; | 974 | int rc, open1rc = -1, open2rc = -1; |
823 | for ( fd=0; fd<MAX_OPEN_FILES; fd++, pfile++) | 975 | struct filestr_base oldstr, newstr; |
976 | struct path_component_info oldinfo, newinfo; | ||
977 | |||
978 | file_internal_lock_WRITER(); | ||
979 | |||
980 | /* open 'old'; it must exist */ | ||
981 | open1rc = open_stream_internal(old, FF_ANYTYPE, &oldstr, &oldinfo); | ||
982 | if (open1rc <= 0) | ||
824 | { | 983 | { |
984 | DEBUGF("Failed opening old: %d\n", rc); | ||
985 | if (open1rc == 0) | ||
986 | FILE_ERROR(ENOENT, -1); | ||
987 | else | ||
988 | FILE_ERROR(ERRNO, open1rc * 10 - 1); | ||
989 | } | ||
990 | |||
991 | /* if 'old' is a directory then 'new' is also required to be one if 'new' | ||
992 | is to be overwritten */ | ||
993 | const bool are_dirs = oldinfo.attr & ATTR_DIRECTORY; | ||
994 | |||
995 | /* open new (may or may not exist) */ | ||
996 | unsigned int callflags = FF_FILE; | ||
997 | if (are_dirs) | ||
998 | { | ||
999 | /* if 'old' is found while parsing the new directory components then | ||
1000 | 'new' contains path prefix that names 'old'; if new and old are in | ||
1001 | the same directory, this tests positive but that is checked later */ | ||
1002 | callflags = FF_DIR | FF_CHECKPREFIX; | ||
1003 | newinfo.prefixp = oldstr.infop; | ||
1004 | } | ||
1005 | |||
1006 | open2rc = open_stream_internal(new, callflags, &newstr, &newinfo); | ||
1007 | if (open2rc < 0) | ||
1008 | { | ||
1009 | DEBUGF("Failed opening new file: %d\n", rc); | ||
1010 | FILE_ERROR(ERRNO, open2rc * 10 - 2); | ||
1011 | } | ||
1012 | |||
825 | #ifdef HAVE_MULTIVOLUME | 1013 | #ifdef HAVE_MULTIVOLUME |
826 | if (pfile->fatfile.volume == volume) | 1014 | if (oldinfo.parentinfo.volume != newinfo.parentinfo.volume) |
827 | #else | 1015 | { |
828 | (void)volume; | 1016 | DEBUGF("Cross-device link\n"); |
829 | #endif | 1017 | FILE_ERROR(EXDEV, -3); |
1018 | } | ||
1019 | #endif /* HAVE_MULTIVOLUME */ | ||
1020 | |||
1021 | /* if the parent is changing then this is a move, not a simple rename */ | ||
1022 | const bool is_move = !fat_file_is_same(&oldinfo.parentinfo.fatfile, | ||
1023 | &newinfo.parentinfo.fatfile); | ||
1024 | /* prefix found and moving? */ | ||
1025 | if (is_move && (newinfo.attr & ATTR_PREFIX)) | ||
1026 | { | ||
1027 | DEBUGF("New contains prefix that names old\n"); | ||
1028 | FILE_ERROR(EINVAL, -4); | ||
1029 | } | ||
1030 | |||
1031 | const char * const oldname = strmemdupa(oldinfo.name, oldinfo.length); | ||
1032 | const char * const newname = strmemdupa(newinfo.name, newinfo.length); | ||
1033 | bool is_overwrite = false; | ||
1034 | |||
1035 | if (open2rc > 0) | ||
1036 | { | ||
1037 | /* new name exists in parent; check if 'old' is overwriting 'new'; | ||
1038 | if it's the very same file, then it's just a rename */ | ||
1039 | is_overwrite = oldstr.bindp != newstr.bindp; | ||
1040 | |||
1041 | if (is_overwrite) | ||
1042 | { | ||
1043 | if (are_dirs) | ||
1044 | { | ||
1045 | /* the directory to be overwritten must be empty */ | ||
1046 | rc = test_dir_empty_internal(&newstr); | ||
1047 | if (rc < 0) | ||
1048 | FILE_ERROR(ERRNO, rc * 10 - 5); | ||
1049 | } | ||
1050 | } | ||
1051 | else if (!strcmp(newname, oldname)) /* case-only is ok */ | ||
1052 | { | ||
1053 | DEBUGF("No name change (success)\n"); | ||
1054 | rc = 0; | ||
1055 | FILE_ERROR(ERRNO, RC); | ||
1056 | } | ||
1057 | } | ||
1058 | else if (!are_dirs && (newinfo.attr & ATTR_DIRECTORY)) | ||
1059 | { | ||
1060 | /* even if new doesn't exist, canonical path type must match | ||
1061 | (ie. a directory path such as "/foo/bar/" when old names a file) */ | ||
1062 | DEBUGF("New path is a directory\n"); | ||
1063 | FILE_ERROR(EISDIR, -6); | ||
1064 | } | ||
1065 | |||
1066 | /* first, create the new entry so that there's never a time that the | ||
1067 | victim's data has no reference in the directory tree, that is, until | ||
1068 | everything else first succeeds */ | ||
1069 | struct file_base_info old_fileinfo = *oldstr.infop; | ||
1070 | rc = fat_rename(&newinfo.parentinfo.fatfile, &oldstr.infop->fatfile, | ||
1071 | newname); | ||
1072 | if (rc < 0) | ||
1073 | { | ||
1074 | DEBUGF("I/O error renaming file: %d\n", rc); | ||
1075 | FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 7); | ||
1076 | } | ||
1077 | |||
1078 | if (is_overwrite) | ||
1079 | { | ||
1080 | /* 'new' would have been assigned its own directory entry and | ||
1081 | succeeded so at this point it is treated like a remove() call | ||
1082 | on the victim which preserves data until the last reference is | ||
1083 | closed */ | ||
1084 | rc = remove_stream_internal(NULL, &newstr, callflags); | ||
1085 | if (rc < 0) | ||
1086 | FILE_ERROR(ERRNO, rc * 10 - 8); | ||
1087 | } | ||
1088 | |||
1089 | fileop_onrename_internal(&oldstr, is_move ? &old_fileinfo : NULL, | ||
1090 | &newinfo.parentinfo, newname); | ||
1091 | |||
1092 | file_error: | ||
1093 | /* for now, there is nothing to fail upon closing the old stream */ | ||
1094 | if (open1rc >= 0) | ||
1095 | close_stream_internal(&oldstr); | ||
1096 | |||
1097 | /* the 'new' stream could fail to close cleanly because it became | ||
1098 | impossible to remove its data if this was an overwrite operation */ | ||
1099 | if (open2rc >= 0) | ||
1100 | { | ||
1101 | int rc2 = close_stream_internal(&newstr); | ||
1102 | if (rc2 < 0 && rc >= 0) | ||
830 | { | 1103 | { |
831 | pfile->busy = false; /* mark as available, no further action */ | 1104 | DEBUGF("Success but failed closing new: %d\n", rc2); |
832 | closed++; | 1105 | rc = rc2 * 10 - 9; |
833 | } | 1106 | } |
834 | } | 1107 | } |
835 | return closed; /* return how many we did */ | 1108 | |
1109 | file_internal_unlock_WRITER(); | ||
1110 | return rc; | ||
1111 | } | ||
1112 | |||
1113 | |||
1114 | /** Extensions **/ | ||
1115 | |||
1116 | /* get the binary size of a file (in bytes) */ | ||
1117 | off_t filesize(int fildes) | ||
1118 | { | ||
1119 | struct filestr_desc * const file = GET_FILESTR(READER, fildes); | ||
1120 | if (!file) | ||
1121 | FILE_ERROR_RETURN(ERRNO, -1); | ||
1122 | |||
1123 | off_t rc; | ||
1124 | file_size_t size = *file->sizep; | ||
1125 | |||
1126 | if (size > FILE_SIZE_MAX) | ||
1127 | FILE_ERROR(EOVERFLOW, -2); | ||
1128 | |||
1129 | rc = (off_t)size; | ||
1130 | file_error: | ||
1131 | RELEASE_FILESTR(READER, file); | ||
1132 | return rc; | ||
1133 | } | ||
1134 | |||
1135 | /* test if two file descriptors refer to the same file */ | ||
1136 | int fsamefile(int fildes1, int fildes2) | ||
1137 | { | ||
1138 | struct filestr_desc * const file1 = GET_FILESTR(WRITER, fildes1); | ||
1139 | if (!file1) | ||
1140 | FILE_ERROR_RETURN(ERRNO, -1); | ||
1141 | |||
1142 | int rc = -2; | ||
1143 | |||
1144 | struct filestr_desc * const file2 = get_filestr(fildes2); | ||
1145 | if (file2) | ||
1146 | rc = file1->stream.bindp == file2->stream.bindp ? 1 : 0; | ||
1147 | |||
1148 | RELEASE_FILESTR(WRITER, file1); | ||
1149 | return rc; | ||
1150 | } | ||
1151 | |||
1152 | /* tell the relationship of path1 to path2 */ | ||
1153 | int relate(const char *path1, const char *path2) | ||
1154 | { | ||
1155 | /* this is basically what rename() does but reduced to the relationship | ||
1156 | determination */ | ||
1157 | DEBUGF("relate(path1=\"%s\",path2=\"%s\")\n", path1, path2); | ||
1158 | |||
1159 | int rc, open1rc = -1, open2rc = -1; | ||
1160 | struct filestr_base str1, str2; | ||
1161 | struct path_component_info info1, info2; | ||
1162 | |||
1163 | file_internal_lock_WRITER(); | ||
1164 | |||
1165 | open1rc = open_stream_internal(path1, FF_ANYTYPE, &str1, &info1); | ||
1166 | if (open1rc <= 0) | ||
1167 | { | ||
1168 | DEBUGF("Failed opening path1: %d\n", rc); | ||
1169 | if (open1rc < 0) | ||
1170 | FILE_ERROR(ERRNO, open1rc * 10 - 1); | ||
1171 | else | ||
1172 | FILE_ERROR(ENOENT, -1); | ||
1173 | } | ||
1174 | |||
1175 | info2.prefixp = str1.infop; | ||
1176 | open2rc = open_stream_internal(path2, FF_ANYTYPE | FF_CHECKPREFIX, | ||
1177 | &str2, &info2); | ||
1178 | if (open2rc < 0) | ||
1179 | { | ||
1180 | DEBUGF("Failed opening path2: %d\n", rc); | ||
1181 | FILE_ERROR(ERRNO, open2rc * 10 - 2); | ||
1182 | } | ||
1183 | |||
1184 | rc = RELATE_DIFFERENT; | ||
1185 | |||
1186 | if (open2rc > 0) | ||
1187 | { | ||
1188 | if (str1.bindp == str2.bindp) | ||
1189 | rc = RELATE_SAME; | ||
1190 | else if (info2.attr & ATTR_PREFIX) | ||
1191 | rc = RELATE_PREFIX; | ||
1192 | } | ||
1193 | else /* open2rc == 0 */ | ||
1194 | { | ||
1195 | /* path1 existing and path2's final part not can only be a prefix or | ||
1196 | different */ | ||
1197 | if (info2.attr & ATTR_PREFIX) | ||
1198 | rc = RELATE_PREFIX; | ||
1199 | } | ||
1200 | |||
1201 | file_error: | ||
1202 | if (open1rc >= 0) | ||
1203 | close_stream_internal(&str1); | ||
1204 | |||
1205 | if (open2rc >= 0) | ||
1206 | close_stream_internal(&str2); | ||
1207 | |||
1208 | file_internal_unlock_WRITER(); | ||
1209 | return rc; | ||
1210 | } | ||
1211 | |||
1212 | /* test file or directory existence */ | ||
1213 | bool file_exists(const char *path) | ||
1214 | { | ||
1215 | file_internal_lock_WRITER(); | ||
1216 | bool rc = test_stream_exists_internal(path, FF_ANYTYPE) > 0; | ||
1217 | file_internal_unlock_WRITER(); | ||
1218 | return rc; | ||
836 | } | 1219 | } |
diff --git a/firmware/common/file_internal.c b/firmware/common/file_internal.c new file mode 100644 index 0000000000..ebe77f0c9f --- /dev/null +++ b/firmware/common/file_internal.c | |||
@@ -0,0 +1,776 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 by Michael Sevakis | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | #include "config.h" | ||
22 | #include <errno.h> | ||
23 | #include "system.h" | ||
24 | #include "debug.h" | ||
25 | #include "panic.h" | ||
26 | #include "pathfuncs.h" | ||
27 | #include "disk_cache.h" | ||
28 | #include "fileobj_mgr.h" | ||
29 | #include "dir.h" | ||
30 | #include "dircache_redirect.h" | ||
31 | #include "dircache.h" | ||
32 | #include "string-extra.h" | ||
33 | #include "rbunicode.h" | ||
34 | |||
35 | /** Internal common filesystem service functions **/ | ||
36 | |||
37 | /* for internal functions' scanning use to save quite a bit of stack space - | ||
38 | access must be serialized by the writer lock */ | ||
39 | #if defined(CPU_SH) || defined(IAUDIO_M5) | ||
40 | /* otherwise, out of IRAM */ | ||
41 | struct fat_direntry dir_fatent; | ||
42 | #else | ||
43 | struct fat_direntry dir_fatent IBSS_ATTR; | ||
44 | #endif | ||
45 | |||
46 | struct mrsw_lock file_internal_mrsw SHAREDBSS_ATTR; | ||
47 | |||
48 | |||
49 | /** File stream sector caching **/ | ||
50 | |||
51 | /* initialize a new cache structure */ | ||
52 | void file_cache_init(struct filestr_cache *cachep) | ||
53 | { | ||
54 | cachep->buffer = NULL; | ||
55 | cachep->sector = INVALID_SECNUM; | ||
56 | cachep->flags = 0; | ||
57 | } | ||
58 | |||
59 | /* discard and mark the cache buffer as unused */ | ||
60 | void file_cache_reset(struct filestr_cache *cachep) | ||
61 | { | ||
62 | cachep->sector = INVALID_SECNUM; | ||
63 | cachep->flags = 0; | ||
64 | } | ||
65 | |||
66 | /* allocate resources attached to the cache */ | ||
67 | void file_cache_alloc(struct filestr_cache *cachep) | ||
68 | { | ||
69 | /* if this fails, it is a bug; check for leaks and that the cache has | ||
70 | enough buffers for the worst case */ | ||
71 | if (!cachep->buffer && !(cachep->buffer = dc_get_buffer())) | ||
72 | panicf("file_cache_alloc - OOM"); | ||
73 | } | ||
74 | |||
75 | /* free resources attached to the cache */ | ||
76 | void file_cache_free(struct filestr_cache *cachep) | ||
77 | { | ||
78 | if (cachep && cachep->buffer) | ||
79 | { | ||
80 | dc_release_buffer(cachep->buffer); | ||
81 | cachep->buffer = NULL; | ||
82 | } | ||
83 | |||
84 | file_cache_reset(cachep); | ||
85 | } | ||
86 | |||
87 | |||
88 | /** Stream base APIs **/ | ||
89 | |||
90 | static inline void filestr_clear(struct filestr_base *stream) | ||
91 | { | ||
92 | stream->flags = 0; | ||
93 | stream->bindp = NULL; | ||
94 | #if 0 | ||
95 | stream->mtx = NULL; | ||
96 | #endif | ||
97 | } | ||
98 | |||
99 | /* actually late-allocate the assigned cache */ | ||
100 | void filestr_alloc_cache(struct filestr_base *stream) | ||
101 | { | ||
102 | file_cache_alloc(stream->cachep); | ||
103 | } | ||
104 | |||
105 | /* free the stream's cache buffer if it's its own */ | ||
106 | void filestr_free_cache(struct filestr_base *stream) | ||
107 | { | ||
108 | if (stream->cachep == &stream->cache) | ||
109 | file_cache_free(stream->cachep); | ||
110 | } | ||
111 | |||
112 | /* assign a cache to the stream */ | ||
113 | void filestr_assign_cache(struct filestr_base *stream, | ||
114 | struct filestr_cache *cachep) | ||
115 | { | ||
116 | if (cachep) | ||
117 | { | ||
118 | filestr_free_cache(stream); | ||
119 | stream->cachep = cachep; | ||
120 | } | ||
121 | else /* assign own cache */ | ||
122 | { | ||
123 | file_cache_reset(&stream->cache); | ||
124 | stream->cachep = &stream->cache; | ||
125 | } | ||
126 | } | ||
127 | |||
128 | /* duplicate a cache into a stream's local cache */ | ||
129 | void filestr_copy_cache(struct filestr_base *stream, | ||
130 | struct filestr_cache *cachep) | ||
131 | { | ||
132 | stream->cachep = &stream->cache; | ||
133 | stream->cache.sector = cachep->sector; | ||
134 | stream->cache.flags = cachep->flags; | ||
135 | |||
136 | if (cachep->buffer) | ||
137 | { | ||
138 | file_cache_alloc(&stream->cache); | ||
139 | memcpy(stream->cache.buffer, cachep->buffer, DC_CACHE_BUFSIZE); | ||
140 | } | ||
141 | else | ||
142 | { | ||
143 | file_cache_free(&stream->cache); | ||
144 | } | ||
145 | } | ||
146 | |||
147 | /* discard cache contents and invalidate it */ | ||
148 | void filestr_discard_cache(struct filestr_base *stream) | ||
149 | { | ||
150 | file_cache_reset(stream->cachep); | ||
151 | } | ||
152 | |||
153 | /* Initialize the base descriptor */ | ||
154 | void filestr_base_init(struct filestr_base *stream) | ||
155 | { | ||
156 | filestr_clear(stream); | ||
157 | file_cache_init(&stream->cache); | ||
158 | stream->cachep = &stream->cache; | ||
159 | } | ||
160 | |||
161 | /* free base descriptor resources */ | ||
162 | void filestr_base_destroy(struct filestr_base *stream) | ||
163 | { | ||
164 | filestr_clear(stream); | ||
165 | filestr_free_cache(stream); | ||
166 | } | ||
167 | |||
168 | |||
169 | /** Internal directory service functions **/ | ||
170 | |||
171 | /* read the next directory entry and return its FS info */ | ||
172 | int uncached_readdir_internal(struct filestr_base *stream, | ||
173 | struct file_base_info *infop, | ||
174 | struct fat_direntry *fatent) | ||
175 | { | ||
176 | return fat_readdir(&stream->fatstr, &infop->fatfile.e, | ||
177 | filestr_get_cache(stream), fatent); | ||
178 | } | ||
179 | |||
180 | /* rewind the FS directory to the beginning */ | ||
181 | void uncached_rewinddir_internal(struct file_base_info *infop) | ||
182 | { | ||
183 | fat_rewinddir(&infop->fatfile.e); | ||
184 | } | ||
185 | |||
186 | /* check if the directory is empty (ie. only "." and/or ".." entries | ||
187 | exist at most) */ | ||
188 | int test_dir_empty_internal(struct filestr_base *stream) | ||
189 | { | ||
190 | int rc; | ||
191 | |||
192 | struct file_base_info info; | ||
193 | fat_rewind(&stream->fatstr); | ||
194 | rewinddir_internal(&info); | ||
195 | |||
196 | while ((rc = readdir_internal(stream, &info, &dir_fatent)) > 0) | ||
197 | { | ||
198 | /* no OEM decoding is recessary for this simple check */ | ||
199 | if (!is_dotdir_name(dir_fatent.name)) | ||
200 | { | ||
201 | DEBUGF("Directory not empty\n"); | ||
202 | FILE_ERROR_RETURN(ENOTEMPTY, -1); | ||
203 | } | ||
204 | } | ||
205 | |||
206 | if (rc < 0) | ||
207 | { | ||
208 | DEBUGF("I/O error checking directory: %d\n", rc); | ||
209 | FILE_ERROR_RETURN(EIO, rc * 10 - 2); | ||
210 | } | ||
211 | |||
212 | return 0; | ||
213 | } | ||
214 | |||
215 | /* iso decode the name to UTF-8 */ | ||
216 | void iso_decode_d_name(char *d_name) | ||
217 | { | ||
218 | if (is_dotdir_name(d_name)) | ||
219 | return; | ||
220 | |||
221 | char shortname[13]; | ||
222 | size_t len = strlcpy(shortname, d_name, sizeof (shortname)); | ||
223 | /* This MUST be the default codepage thus not something that could be | ||
224 | loaded on call */ | ||
225 | iso_decode(shortname, d_name, -1, len + 1); | ||
226 | } | ||
227 | |||
228 | #ifdef HAVE_DIRCACHE | ||
229 | /* nullify all the fields of the struct dirent */ | ||
230 | void empty_dirent(struct dirent *entry) | ||
231 | { | ||
232 | entry->d_name[0] = '\0'; | ||
233 | entry->info.attr = 0; | ||
234 | entry->info.size = 0; | ||
235 | entry->info.wrtdate = 0; | ||
236 | entry->info.wrttime = 0; | ||
237 | } | ||
238 | |||
239 | /* fill the native dirinfo from the static dir_fatent */ | ||
240 | void fill_dirinfo_native(struct dirinfo_native *dinp) | ||
241 | { | ||
242 | struct fat_direntry *fatent = get_dir_fatent(); | ||
243 | dinp->attr = fatent->attr; | ||
244 | dinp->size = fatent->filesize; | ||
245 | dinp->wrtdate = fatent->wrtdate; | ||
246 | dinp->wrttime = fatent->wrttime; | ||
247 | } | ||
248 | #endif /* HAVE_DIRCACHE */ | ||
249 | |||
250 | int uncached_readdir_dirent(struct filestr_base *stream, | ||
251 | struct dirscan_info *scanp, | ||
252 | struct dirent *entry) | ||
253 | { | ||
254 | struct fat_direntry fatent; | ||
255 | int rc = fat_readdir(&stream->fatstr, &scanp->fatscan, | ||
256 | filestr_get_cache(stream), &fatent); | ||
257 | |||
258 | /* FAT driver clears the struct fat_dirent if nothing is returned */ | ||
259 | strcpy(entry->d_name, fatent.name); | ||
260 | entry->info.attr = fatent.attr; | ||
261 | entry->info.size = fatent.filesize; | ||
262 | entry->info.wrtdate = fatent.wrtdate; | ||
263 | entry->info.wrttime = fatent.wrttime; | ||
264 | |||
265 | return rc; | ||
266 | } | ||
267 | |||
268 | /* rewind the FS directory pointer */ | ||
269 | void uncached_rewinddir_dirent(struct dirscan_info *scanp) | ||
270 | { | ||
271 | fat_rewinddir(&scanp->fatscan); | ||
272 | } | ||
273 | |||
274 | |||
275 | /** open_stream_internal() helpers and types **/ | ||
276 | |||
277 | struct pathwalk | ||
278 | { | ||
279 | const char *path; /* current location in input path */ | ||
280 | unsigned int callflags; /* callflags parameter */ | ||
281 | struct path_component_info *compinfo; /* compinfo parameter */ | ||
282 | file_size_t filesize; /* size of the file */ | ||
283 | }; | ||
284 | |||
285 | struct pathwalk_component | ||
286 | { | ||
287 | struct file_base_info info; /* basic file information */ | ||
288 | const char *name; /* component name location in path */ | ||
289 | uint16_t length; /* length of name of component */ | ||
290 | uint16_t attr; /* attributes of this component */ | ||
291 | struct pathwalk_component *nextp; /* parent if in use else next free */ | ||
292 | }; | ||
293 | |||
294 | #define WALK_RC_NOT_FOUND 0 /* successfully not found */ | ||
295 | #define WALK_RC_FOUND 1 /* found and opened */ | ||
296 | #define WALK_RC_FOUND_ROOT 2 /* found and opened sys/volume root */ | ||
297 | #define WALK_RC_CONT_AT_ROOT 3 /* continue at root level */ | ||
298 | |||
299 | /* return another struct pathwalk_component from the pool, or NULL if the | ||
300 | pool is completely used */ | ||
301 | static void * pathwalk_comp_alloc_(struct pathwalk_component *parentp) | ||
302 | { | ||
303 | /* static pool that goes to a depth of STATIC_COMP_NUM before allocating | ||
304 | elements from the stack */ | ||
305 | static struct pathwalk_component aux_pathwalk[STATIC_PATHCOMP_NUM]; | ||
306 | struct pathwalk_component *compp = NULL; | ||
307 | |||
308 | if (!parentp) | ||
309 | compp = &aux_pathwalk[0]; /* root */ | ||
310 | else if (PTR_IN_ARRAY(aux_pathwalk, parentp, STATIC_PATHCOMP_NUM-1)) | ||
311 | compp = parentp + 1; | ||
312 | |||
313 | return compp; | ||
314 | } | ||
315 | |||
316 | /* allocates components from the pool or stack depending upon the depth */ | ||
317 | #define pathwalk_comp_alloc(parentp) \ | ||
318 | ({ \ | ||
319 | void *__c = pathwalk_comp_alloc_(parentp); \ | ||
320 | if (!__builtin_constant_p(parentp) && !__c) \ | ||
321 | __c = alloca(sizeof (struct pathwalk_component)); \ | ||
322 | (struct pathwalk_component *)__c; \ | ||
323 | }) | ||
324 | |||
325 | /* fill in the details of the struct path_component_info for caller */ | ||
326 | static int fill_path_compinfo(struct pathwalk *walkp, | ||
327 | struct pathwalk_component *compp, | ||
328 | int rc) | ||
329 | { | ||
330 | if (rc == -ENOENT) | ||
331 | { | ||
332 | /* this component wasn't found; see if more of them exist or path | ||
333 | has trailing separators; if it does, this component should be | ||
334 | interpreted as a directory even if it doesn't exist and it's the | ||
335 | final one; also, this has to be the last part or it's an error*/ | ||
336 | const char *p = GOBBLE_PATH_SEPCH(walkp->path); | ||
337 | if (!*p) | ||
338 | { | ||
339 | if (p > walkp->path) | ||
340 | compp->attr |= ATTR_DIRECTORY; | ||
341 | |||
342 | rc = WALK_RC_NOT_FOUND; /* successfully not found */ | ||
343 | } | ||
344 | } | ||
345 | |||
346 | if (rc >= 0) | ||
347 | { | ||
348 | struct path_component_info *compinfo = walkp->compinfo; | ||
349 | compinfo->name = compp->name; | ||
350 | compinfo->length = compp->length; | ||
351 | compinfo->attr = compp->attr; | ||
352 | compinfo->filesize = walkp->filesize; | ||
353 | compinfo->parentinfo = (compp->nextp ?: compp)->info; | ||
354 | } | ||
355 | |||
356 | return rc; | ||
357 | } | ||
358 | |||
359 | /* open the final stream itself, if found */ | ||
360 | static int walk_open_info(struct pathwalk *walkp, | ||
361 | struct pathwalk_component *compp, | ||
362 | int rc, | ||
363 | struct filestr_base *stream) | ||
364 | { | ||
365 | /* this may make adjustments to things; do it first */ | ||
366 | if (walkp->compinfo) | ||
367 | rc = fill_path_compinfo(walkp, compp, rc); | ||
368 | |||
369 | if (rc < 0 || rc == WALK_RC_NOT_FOUND) | ||
370 | return rc; | ||
371 | |||
372 | unsigned int callflags = walkp->callflags; | ||
373 | bool isdir = compp->attr & ATTR_DIRECTORY; | ||
374 | |||
375 | /* type must match what is called for */ | ||
376 | switch (callflags & FF_TYPEMASK) | ||
377 | { | ||
378 | case FF_FILE: | ||
379 | if (!isdir) break; | ||
380 | DEBUGF("File is a directory\n"); | ||
381 | return -EISDIR; | ||
382 | case FF_DIR: | ||
383 | if (isdir) break; | ||
384 | DEBUGF("File is not a directory\n"); | ||
385 | return -ENOTDIR; | ||
386 | /* FF_ANYTYPE: basically, ignore FF_FILE/FF_DIR */ | ||
387 | } | ||
388 | |||
389 | /* FO_DIRECTORY must match type */ | ||
390 | if (isdir) | ||
391 | callflags |= FO_DIRECTORY; | ||
392 | else | ||
393 | callflags &= ~FO_DIRECTORY; | ||
394 | |||
395 | fileop_onopen_internal(stream, &compp->info, callflags); | ||
396 | return compp->nextp ? WALK_RC_FOUND : WALK_RC_FOUND_ROOT; | ||
397 | } | ||
398 | |||
399 | /* check the component against the prefix test info */ | ||
400 | static void walk_check_prefix(struct pathwalk *walkp, | ||
401 | struct pathwalk_component *compp) | ||
402 | { | ||
403 | if (compp->attr & ATTR_PREFIX) | ||
404 | return; | ||
405 | |||
406 | if (!fat_file_is_same(&compp->info.fatfile, | ||
407 | &walkp->compinfo->prefixp->fatfile)) | ||
408 | return; | ||
409 | |||
410 | compp->attr |= ATTR_PREFIX; | ||
411 | } | ||
412 | |||
413 | /* opens the component named by 'comp' in the directory 'parent' */ | ||
414 | static NO_INLINE int open_path_component(struct pathwalk *walkp, | ||
415 | struct pathwalk_component *compp, | ||
416 | struct filestr_base *stream) | ||
417 | { | ||
418 | int rc; | ||
419 | |||
420 | /* create a null-terminated copy of the component name */ | ||
421 | char *compname = strmemdupa(compp->name, compp->length); | ||
422 | |||
423 | unsigned int callflags = walkp->callflags; | ||
424 | struct pathwalk_component *parentp = compp->nextp; | ||
425 | |||
426 | /* children inherit the prefix coloring from the parent */ | ||
427 | compp->attr = parentp->attr & ATTR_PREFIX; | ||
428 | |||
429 | /* most of the next would be abstracted elsewhere if doing other | ||
430 | filesystems */ | ||
431 | |||
432 | /* scan parent for name; stream is converted to this parent */ | ||
433 | file_cache_reset(stream->cachep); | ||
434 | stream->infop = &parentp->info; | ||
435 | fat_filestr_init(&stream->fatstr, &parentp->info.fatfile); | ||
436 | rewinddir_internal(&compp->info); | ||
437 | |||
438 | while ((rc = readdir_internal(stream, &compp->info, &dir_fatent)) > 0) | ||
439 | { | ||
440 | if (rc > 1 && !(callflags & FF_NOISO)) | ||
441 | iso_decode_d_name(dir_fatent.name); | ||
442 | |||
443 | if (!strcasecmp(compname, dir_fatent.name)) | ||
444 | break; | ||
445 | } | ||
446 | |||
447 | if (rc == 0) | ||
448 | { | ||
449 | DEBUGF("File/directory not found\n"); | ||
450 | return -ENOENT; | ||
451 | } | ||
452 | else if (rc < 0) | ||
453 | { | ||
454 | DEBUGF("I/O error reading directory %d\n", rc); | ||
455 | return -EIO; | ||
456 | } | ||
457 | |||
458 | rc = fat_open(stream->fatstr.fatfilep, dir_fatent.firstcluster, | ||
459 | &compp->info.fatfile); | ||
460 | if (rc < 0) | ||
461 | { | ||
462 | DEBUGF("I/O error opening file/directory %s (%d)\n", | ||
463 | compname, rc); | ||
464 | return -EIO; | ||
465 | } | ||
466 | |||
467 | walkp->filesize = dir_fatent.filesize; | ||
468 | compp->attr |= dir_fatent.attr; | ||
469 | |||
470 | if (callflags & FF_CHECKPREFIX) | ||
471 | walk_check_prefix(walkp, compp); | ||
472 | |||
473 | return WALK_RC_FOUND; | ||
474 | } | ||
475 | |||
476 | /* parse a path component, open it and process the next */ | ||
477 | static NO_INLINE int | ||
478 | walk_path(struct pathwalk *walkp, struct pathwalk_component *compp, | ||
479 | struct filestr_base *stream) | ||
480 | { | ||
481 | int rc = WALK_RC_FOUND; | ||
482 | |||
483 | if (walkp->callflags & FF_CHECKPREFIX) | ||
484 | walk_check_prefix(walkp, compp); | ||
485 | |||
486 | /* alloca is used in a loop, but we reuse any blocks previously allocated | ||
487 | if we went up then back down; if the path takes us back to the root, then | ||
488 | everything is cleaned automatically */ | ||
489 | struct pathwalk_component *freep = NULL; | ||
490 | |||
491 | const char *name; | ||
492 | ssize_t len; | ||
493 | |||
494 | while ((len = parse_path_component(&walkp->path, &name))) | ||
495 | { | ||
496 | /* whatever is to be a parent must be a directory */ | ||
497 | if (!(compp->attr & ATTR_DIRECTORY)) | ||
498 | return -ENOTDIR; | ||
499 | |||
500 | switch (len) | ||
501 | { | ||
502 | case 1: | ||
503 | case 2: | ||
504 | /* check for "." and ".." */ | ||
505 | if (name[0] == '.') | ||
506 | { | ||
507 | if (len == 2 && name[1] == '.') | ||
508 | { | ||
509 | struct pathwalk_component *parentp = compp->nextp; | ||
510 | if (!parentp) | ||
511 | return WALK_RC_CONT_AT_ROOT; | ||
512 | |||
513 | compp->nextp = freep; | ||
514 | freep = compp; | ||
515 | compp = parentp; | ||
516 | } | ||
517 | |||
518 | break; | ||
519 | } | ||
520 | |||
521 | /* fallthrough */ | ||
522 | default: | ||
523 | if (len >= MAX_NAME) | ||
524 | return -ENAMETOOLONG; | ||
525 | |||
526 | struct pathwalk_component *newp = freep; | ||
527 | if (!newp) | ||
528 | newp = pathwalk_comp_alloc(compp); | ||
529 | else | ||
530 | freep = freep->nextp; | ||
531 | |||
532 | newp->nextp = compp; | ||
533 | compp = newp; | ||
534 | |||
535 | compp->name = name; | ||
536 | compp->length = len; | ||
537 | |||
538 | rc = open_path_component(walkp, compp, stream); | ||
539 | if (rc < 0) | ||
540 | break; | ||
541 | } | ||
542 | } | ||
543 | |||
544 | return walk_open_info(walkp, compp, rc, stream); | ||
545 | } | ||
546 | |||
547 | /* open a stream given a path to the resource */ | ||
548 | int open_stream_internal(const char *path, unsigned int callflags, | ||
549 | struct filestr_base *stream, | ||
550 | struct path_component_info *compinfo) | ||
551 | { | ||
552 | DEBUGF("%s(path=\"%s\",flg=%X,str=%p,compinfo=%p)\n", path, callflags, | ||
553 | stream, compinfo); | ||
554 | int rc; | ||
555 | |||
556 | filestr_base_init(stream); | ||
557 | |||
558 | if (!path_is_absolute(path)) | ||
559 | { | ||
560 | /* while this supports relative components, there is currently no | ||
561 | current working directory concept at this level by which to | ||
562 | fully qualify the path (though that would not be excessively | ||
563 | difficult to add) */ | ||
564 | DEBUGF("\"%s\" is not an absolute path\n" | ||
565 | "Only absolute paths currently supported.\n", path); | ||
566 | FILE_ERROR(path ? ENOENT : EFAULT, -1); | ||
567 | } | ||
568 | |||
569 | /* if !compinfo, then the result of this check is not visible anyway */ | ||
570 | if (!compinfo) | ||
571 | callflags &= ~FF_CHECKPREFIX; | ||
572 | |||
573 | struct pathwalk walk; | ||
574 | walk.path = path; | ||
575 | walk.callflags = callflags; | ||
576 | walk.compinfo = compinfo; | ||
577 | walk.filesize = 0; | ||
578 | |||
579 | struct pathwalk_component *rootp = pathwalk_comp_alloc(NULL); | ||
580 | rootp->nextp = NULL; | ||
581 | rootp->attr = ATTR_DIRECTORY; | ||
582 | |||
583 | #ifdef HAVE_MULTIVOLUME | ||
584 | int volume = 0, rootrc = WALK_RC_FOUND; | ||
585 | #endif /* HAVE_MULTIVOLUME */ | ||
586 | |||
587 | while (1) | ||
588 | { | ||
589 | const char *pathptr = walk.path; | ||
590 | |||
591 | #ifdef HAVE_MULTIVOLUME | ||
592 | /* this seamlessly integrates secondary filesystems into the | ||
593 | root namespace (e.g. "/<0>/../../<1>/../foo/." :<=> "/foo") */ | ||
594 | const char *p; | ||
595 | volume = path_strip_volume(pathptr, &p, false); | ||
596 | if (!CHECK_VOL(volume)) | ||
597 | { | ||
598 | DEBUGF("No such device or address: %d\n", volume); | ||
599 | FILE_ERROR(ENXIO, -2); | ||
600 | } | ||
601 | |||
602 | /* the root of this subpath is the system root? */ | ||
603 | rootrc = p == pathptr ? WALK_RC_FOUND_ROOT : WALK_RC_FOUND; | ||
604 | walk.path = p; | ||
605 | #endif /* HAVE_MULTIVOLUME */ | ||
606 | |||
607 | /* set name to start at last leading separator; names of volume | ||
608 | specifiers will be returned as "/<fooN>" */ | ||
609 | rootp->name = GOBBLE_PATH_SEPCH(pathptr) - 1; | ||
610 | rootp->length = | ||
611 | IF_MV( rootrc == WALK_RC_FOUND ? p - rootp->name : ) 1; | ||
612 | |||
613 | rc = fat_open_rootdir(IF_MV(volume,) &rootp->info.fatfile); | ||
614 | if (rc < 0) | ||
615 | { | ||
616 | /* not mounted */ | ||
617 | DEBUGF("No such device or address: %d\n", IF_MV_VOL(volume)); | ||
618 | rc = -ENXIO; | ||
619 | break; | ||
620 | } | ||
621 | |||
622 | get_rootinfo_internal(&rootp->info); | ||
623 | rc = walk_path(&walk, rootp, stream); | ||
624 | if (rc != WALK_RC_CONT_AT_ROOT) | ||
625 | break; | ||
626 | } | ||
627 | |||
628 | switch (rc) | ||
629 | { | ||
630 | case WALK_RC_FOUND_ROOT: | ||
631 | IF_MV( rc = rootrc; ) | ||
632 | case WALK_RC_NOT_FOUND: | ||
633 | case WALK_RC_FOUND: | ||
634 | break; | ||
635 | |||
636 | default: /* utter, abject failure :`( */ | ||
637 | DEBUGF("Open failed: rc=%d, errno=%d\n", rc, errno); | ||
638 | filestr_base_destroy(stream); | ||
639 | FILE_ERROR(-rc, -2); | ||
640 | } | ||
641 | |||
642 | file_cache_reset(stream->cachep); | ||
643 | |||
644 | file_error: | ||
645 | return rc; | ||
646 | } | ||
647 | |||
648 | /* close the stream referenced by 'stream' */ | ||
649 | int close_stream_internal(struct filestr_base *stream) | ||
650 | { | ||
651 | int rc; | ||
652 | unsigned int foflags = fileobj_get_flags(stream); | ||
653 | |||
654 | if ((foflags & (FO_SINGLE|FO_REMOVED)) == (FO_SINGLE|FO_REMOVED)) | ||
655 | { | ||
656 | /* nothing is referencing it so now remove the file's data */ | ||
657 | rc = fat_remove(&stream->infop->fatfile, FAT_RM_DATA); | ||
658 | if (rc < 0) | ||
659 | { | ||
660 | DEBUGF("I/O error removing file data: %d\n", rc); | ||
661 | FILE_ERROR(EIO, rc * 10 - 1); | ||
662 | } | ||
663 | } | ||
664 | |||
665 | rc = 0; | ||
666 | file_error: | ||
667 | /* close no matter what */ | ||
668 | fileop_onclose_internal(stream); | ||
669 | return rc; | ||
670 | } | ||
671 | |||
672 | /* create a new stream in the parent directory */ | ||
673 | int create_stream_internal(struct file_base_info *parentinfop, | ||
674 | const char *basename, size_t length, | ||
675 | unsigned int attr, unsigned int callflags, | ||
676 | struct filestr_base *stream) | ||
677 | { | ||
678 | /* assumes an attempt was made beforehand to open *stream with | ||
679 | open_stream_internal() which returned zero (successfully not found), | ||
680 | so does not initialize it here */ | ||
681 | const char * const name = strmemdupa(basename, length); | ||
682 | DEBUGF("Creating \"%s\"\n", name); | ||
683 | |||
684 | struct file_base_info info; | ||
685 | int rc = fat_create_file(&parentinfop->fatfile, name, attr, | ||
686 | &info.fatfile, get_dir_fatent_dircache()); | ||
687 | if (rc < 0) | ||
688 | { | ||
689 | DEBUGF("Create failed: %d\n", rc); | ||
690 | FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 1); | ||
691 | } | ||
692 | |||
693 | /* dir_fatent is implicit arg */ | ||
694 | fileop_oncreate_internal(stream, &info, callflags, parentinfop, name); | ||
695 | rc = 0; | ||
696 | file_error: | ||
697 | return rc; | ||
698 | } | ||
699 | |||
700 | /* removes files and directories - back-end to remove() and rmdir() */ | ||
701 | int remove_stream_internal(const char *path, struct filestr_base *stream, | ||
702 | unsigned int callflags) | ||
703 | { | ||
704 | /* Only FF_* flags should be in callflags */ | ||
705 | int rc; | ||
706 | |||
707 | struct filestr_base opened_stream; | ||
708 | if (!stream) | ||
709 | stream = &opened_stream; | ||
710 | |||
711 | if (stream == &opened_stream) | ||
712 | { | ||
713 | /* no stream provided so open local one */ | ||
714 | rc = open_stream_internal(path, callflags, stream, NULL); | ||
715 | if (rc < 0) | ||
716 | { | ||
717 | DEBUGF("Failed opening path: %d\n", rc); | ||
718 | FILE_ERROR(ERRNO, rc * 10 - 1); | ||
719 | } | ||
720 | } | ||
721 | /* else ignore the 'path' argument */ | ||
722 | |||
723 | if (callflags & FF_DIR) | ||
724 | { | ||
725 | /* directory to be removed must be empty */ | ||
726 | rc = test_dir_empty_internal(stream); | ||
727 | if (rc < 0) | ||
728 | FILE_ERROR(ERRNO, rc * 10 - 2); | ||
729 | } | ||
730 | |||
731 | /* save old info since fat_remove() will destroy the dir info */ | ||
732 | struct file_base_info oldinfo = *stream->infop; | ||
733 | rc = fat_remove(&stream->infop->fatfile, FAT_RM_DIRENTRIES); | ||
734 | if (rc < 0) | ||
735 | { | ||
736 | DEBUGF("I/O error removing dir entries: %d\n", rc); | ||
737 | FILE_ERROR(EIO, rc * 10 - 3); | ||
738 | } | ||
739 | |||
740 | fileop_onremove_internal(stream, &oldinfo); | ||
741 | |||
742 | rc = 0; | ||
743 | file_error: | ||
744 | if (stream == &opened_stream) | ||
745 | { | ||
746 | /* will do removal of data below if this is the only reference */ | ||
747 | int rc2 = close_stream_internal(stream); | ||
748 | if (rc2 < 0 && rc >= 0) | ||
749 | { | ||
750 | rc = rc2 * 10 - 4; | ||
751 | DEBUGF("Success but failed closing stream: %d\n", rc); | ||
752 | } | ||
753 | } | ||
754 | |||
755 | return rc; | ||
756 | } | ||
757 | |||
758 | /* test file/directory existence with constraints */ | ||
759 | int test_stream_exists_internal(const char *path, unsigned int callflags) | ||
760 | { | ||
761 | /* only FF_* flags should be in callflags */ | ||
762 | struct filestr_base stream; | ||
763 | int rc = open_stream_internal(path, callflags, &stream, NULL); | ||
764 | if (rc > 0) | ||
765 | close_stream_internal(&stream); | ||
766 | |||
767 | return rc; | ||
768 | } | ||
769 | |||
770 | /* one-time init at startup */ | ||
771 | void filesystem_init(void) | ||
772 | { | ||
773 | mrsw_init(&file_internal_mrsw); | ||
774 | dc_init(); | ||
775 | fileobj_mgr_init(); | ||
776 | } | ||
diff --git a/firmware/common/filefuncs.c b/firmware/common/filefuncs.c deleted file mode 100644 index 16f8d88684..0000000000 --- a/firmware/common/filefuncs.c +++ /dev/null | |||
@@ -1,102 +0,0 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Björn Stenberg | ||
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 "dir.h" | ||
23 | #include "stdlib.h" | ||
24 | #include "string.h" | ||
25 | #include "debug.h" | ||
26 | #include "file.h" | ||
27 | #include "filefuncs.h" | ||
28 | #include "string-extra.h" | ||
29 | |||
30 | #ifndef __PCTOOL__ | ||
31 | #ifdef HAVE_MULTIVOLUME | ||
32 | |||
33 | /* returns on which volume this is, and copies the reduced name | ||
34 | (sortof a preprocessor for volume-decorated pathnames) */ | ||
35 | int strip_volume(const char* name, char* namecopy) | ||
36 | { | ||
37 | int volume = 0; | ||
38 | const char *temp = name; | ||
39 | |||
40 | while (*temp == '/') /* skip all leading slashes */ | ||
41 | ++temp; | ||
42 | |||
43 | if (*temp && !strncmp(temp, VOL_NAMES, VOL_ENUM_POS)) | ||
44 | { | ||
45 | temp += VOL_ENUM_POS; /* behind special name */ | ||
46 | volume = atoi(temp); /* number is following */ | ||
47 | temp = strchr(temp, '/'); /* search for slash behind */ | ||
48 | if (temp != NULL) | ||
49 | name = temp; /* use the part behind the volume */ | ||
50 | else | ||
51 | name = "/"; /* else this must be the root dir */ | ||
52 | } | ||
53 | |||
54 | strlcpy(namecopy, name, MAX_PATH); | ||
55 | |||
56 | return volume; | ||
57 | } | ||
58 | #endif /* #ifdef HAVE_MULTIVOLUME */ | ||
59 | |||
60 | #endif /* __PCTOOL__ */ | ||
61 | /* Test file existence, using dircache of possible */ | ||
62 | bool file_exists(const char *file) | ||
63 | { | ||
64 | int fd; | ||
65 | |||
66 | #ifdef DEBUG | ||
67 | if (!file || !*file) | ||
68 | { | ||
69 | DEBUGF("%s(%p): Invalid parameter!\n", __func__, file); | ||
70 | return false; | ||
71 | } | ||
72 | #endif | ||
73 | |||
74 | #ifdef HAVE_DIRCACHE | ||
75 | if (dircache_is_enabled()) | ||
76 | return (dircache_get_entry_id(file) >= 0); | ||
77 | #endif | ||
78 | |||
79 | fd = open(file, O_RDONLY); | ||
80 | if (fd < 0) | ||
81 | return false; | ||
82 | close(fd); | ||
83 | return true; | ||
84 | } | ||
85 | |||
86 | bool dir_exists(const char *path) | ||
87 | { | ||
88 | DIR* d = opendir(path); | ||
89 | if (!d) | ||
90 | return false; | ||
91 | closedir(d); | ||
92 | return true; | ||
93 | } | ||
94 | |||
95 | |||
96 | #if (CONFIG_PLATFORM & PLATFORM_NATIVE) || defined(SIMULATOR) | ||
97 | struct dirinfo dir_get_info(DIR* parent, struct dirent *entry) | ||
98 | { | ||
99 | (void)parent; | ||
100 | return entry->info; | ||
101 | } | ||
102 | #endif | ||
diff --git a/firmware/common/fileobj_mgr.c b/firmware/common/fileobj_mgr.c new file mode 100644 index 0000000000..8e7831d36c --- /dev/null +++ b/firmware/common/fileobj_mgr.c | |||
@@ -0,0 +1,396 @@ | |||
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 "system.h" | ||
23 | #include "debug.h" | ||
24 | #include "file.h" | ||
25 | #include "dir.h" | ||
26 | #include "disk_cache.h" | ||
27 | #include "fileobj_mgr.h" | ||
28 | #include "dircache_redirect.h" | ||
29 | |||
30 | /** | ||
31 | * Manages file and directory streams on all volumes | ||
32 | * | ||
33 | * Intended for internal use by disk, file and directory code | ||
34 | */ | ||
35 | |||
36 | |||
37 | /* there will always be enough of these for all user handles, thus these | ||
38 | functions don't return failure codes */ | ||
39 | #define MAX_FILEOBJS (MAX_OPEN_HANDLES + AUX_FILEOBJS) | ||
40 | |||
41 | /* describes the file as an image on the storage medium */ | ||
42 | static struct fileobj_binding | ||
43 | { | ||
44 | struct file_base_binding bind; /* base info list item (first!) */ | ||
45 | uint16_t flags; /* F(D)(O)_* bits of this file/dir */ | ||
46 | uint16_t writers; /* number of writer streams */ | ||
47 | struct filestr_cache cache; /* write mode shared cache */ | ||
48 | file_size_t size; /* size of this file */ | ||
49 | struct ll_head list; /* open streams for this file/dir */ | ||
50 | } fobindings[MAX_FILEOBJS]; | ||
51 | static struct mutex stream_mutexes[MAX_FILEOBJS] SHAREDBSS_ATTR; | ||
52 | static struct ll_head free_bindings; | ||
53 | static struct ll_head busy_bindings[NUM_VOLUMES]; | ||
54 | |||
55 | #define BUSY_BINDINGS(volume) \ | ||
56 | (&busy_bindings[IF_MV_VOL(volume)]) | ||
57 | |||
58 | #define BASEBINDING_LIST(bindp) \ | ||
59 | (BUSY_BINDINGS(BASEBINDING_VOL(bindp))) | ||
60 | |||
61 | #define FREE_BINDINGS() \ | ||
62 | (&free_bindings) | ||
63 | |||
64 | #define BINDING_FIRST(type, volume...) \ | ||
65 | ((struct fileobj_binding *)type##_BINDINGS(volume)->head) | ||
66 | |||
67 | #define BINDING_NEXT(fobp) \ | ||
68 | ((struct fileobj_binding *)(fobp)->bind.node.next) | ||
69 | |||
70 | #define FOR_EACH_BINDING(volume, fobp) \ | ||
71 | for (struct fileobj_binding *fobp = BINDING_FIRST(BUSY, volume); \ | ||
72 | fobp; fobp = BINDING_NEXT(fobp)) | ||
73 | |||
74 | #define STREAM_FIRST(fobp) \ | ||
75 | ((struct filestr_base *)(fobp)->list.head) | ||
76 | |||
77 | #define STREAM_NEXT(s) \ | ||
78 | ((struct filestr_base *)(s)->node.next) | ||
79 | |||
80 | #define STREAM_THIS(s) \ | ||
81 | (s) | ||
82 | |||
83 | #define FOR_EACH_STREAM(what, start, s) \ | ||
84 | for (struct filestr_base *s = STREAM_##what(start); \ | ||
85 | s; s = STREAM_NEXT(s)) | ||
86 | |||
87 | |||
88 | /* syncs information for the stream's old and new parent directory if any are | ||
89 | currently opened */ | ||
90 | static void fileobj_sync_parent(const struct file_base_info *infop[], | ||
91 | int count) | ||
92 | { | ||
93 | FOR_EACH_BINDING(infop[0]->volume, fobp) | ||
94 | { | ||
95 | if ((fobp->flags & (FO_DIRECTORY|FO_REMOVED)) != FO_DIRECTORY) | ||
96 | continue; /* not directory or removed can't be parent of anything */ | ||
97 | |||
98 | struct filestr_base *parentstrp = STREAM_FIRST(fobp); | ||
99 | struct fat_file *parentfilep = &parentstrp->infop->fatfile; | ||
100 | |||
101 | for (int i = 0; i < count; i++) | ||
102 | { | ||
103 | if (!fat_dir_is_parent(parentfilep, &infop[i]->fatfile)) | ||
104 | continue; | ||
105 | |||
106 | /* discard scan/read caches' parent dir info */ | ||
107 | FOR_EACH_STREAM(THIS, parentstrp, s) | ||
108 | filestr_discard_cache(s); | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | |||
113 | /* 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 | ||
115 | the only open one */ | ||
116 | static bool binding_assign(const struct file_base_info *srcinfop, | ||
117 | struct fileobj_binding **fobpp) | ||
118 | { | ||
119 | FOR_EACH_BINDING(srcinfop->fatfile.volume, fobp) | ||
120 | { | ||
121 | if (fobp->flags & FO_REMOVED) | ||
122 | continue; | ||
123 | |||
124 | if (fat_file_is_same(&srcinfop->fatfile, &fobp->bind.info.fatfile)) | ||
125 | { | ||
126 | /* already has open streams */ | ||
127 | *fobpp = fobp; | ||
128 | return false; | ||
129 | } | ||
130 | } | ||
131 | |||
132 | /* not found - allocate anew */ | ||
133 | *fobpp = BINDING_FIRST(FREE); | ||
134 | ll_remove_first(FREE_BINDINGS()); | ||
135 | ll_init(&(*fobpp)->list); | ||
136 | return true; | ||
137 | } | ||
138 | |||
139 | /* mark descriptor as unused and return to the free list */ | ||
140 | static void binding_add_to_free_list(struct fileobj_binding *fobp) | ||
141 | { | ||
142 | fobp->flags = 0; | ||
143 | ll_insert_last(FREE_BINDINGS(), &fobp->bind.node); | ||
144 | } | ||
145 | |||
146 | /** File and directory internal interface **/ | ||
147 | |||
148 | void file_binding_insert_last(struct file_base_binding *bindp) | ||
149 | { | ||
150 | ll_insert_last(BASEBINDING_LIST(bindp), &bindp->node); | ||
151 | } | ||
152 | |||
153 | void file_binding_remove(struct file_base_binding *bindp) | ||
154 | { | ||
155 | ll_remove(BASEBINDING_LIST(bindp), &bindp->node); | ||
156 | } | ||
157 | |||
158 | #ifdef HAVE_DIRCACHE | ||
159 | void file_binding_insert_first(struct file_base_binding *bindp) | ||
160 | { | ||
161 | ll_insert_first(BASEBINDING_LIST(bindp), &bindp->node); | ||
162 | } | ||
163 | |||
164 | void file_binding_remove_next(struct file_base_binding *prevp, | ||
165 | struct file_base_binding *bindp) | ||
166 | { | ||
167 | ll_remove_next(BASEBINDING_LIST(bindp), &prevp->node); | ||
168 | (void)bindp; | ||
169 | } | ||
170 | #endif /* HAVE_DIRCACHE */ | ||
171 | |||
172 | /* 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 | ||
174 | * initialized. | ||
175 | * | ||
176 | * NOTE: switches stream->infop to the one kept in common for all streams of | ||
177 | * the same file, making a copy for only the first stream | ||
178 | */ | ||
179 | void fileobj_fileop_open(struct filestr_base *stream, | ||
180 | const struct file_base_info *srcinfop, | ||
181 | unsigned int callflags) | ||
182 | { | ||
183 | struct fileobj_binding *fobp; | ||
184 | bool first = binding_assign(srcinfop, &fobp); | ||
185 | |||
186 | /* add stream to this file's list */ | ||
187 | ll_insert_last(&fobp->list, &stream->node); | ||
188 | |||
189 | /* initiate the new stream into the enclave */ | ||
190 | stream->flags = FDO_BUSY | (callflags & (FD_WRITE|FD_WRONLY|FD_APPEND)); | ||
191 | stream->infop = &fobp->bind.info; | ||
192 | stream->fatstr.fatfilep = &fobp->bind.info.fatfile; | ||
193 | stream->bindp = &fobp->bind; | ||
194 | stream->mtx = &stream_mutexes[fobp - fobindings]; | ||
195 | |||
196 | if (first) | ||
197 | { | ||
198 | /* first stream for file */ | ||
199 | fobp->bind.info = *srcinfop; | ||
200 | fobp->flags = FDO_BUSY | FO_SINGLE | | ||
201 | (callflags & (FO_DIRECTORY|FO_TRUNC)); | ||
202 | fobp->writers = 0; | ||
203 | fobp->size = 0; | ||
204 | |||
205 | if (callflags & FD_WRITE) | ||
206 | { | ||
207 | /* first one is a writer */ | ||
208 | fobp->writers = 1; | ||
209 | file_cache_init(&fobp->cache); | ||
210 | filestr_assign_cache(stream, &fobp->cache); | ||
211 | } | ||
212 | |||
213 | fileobj_bind_file(&fobp->bind); | ||
214 | } | ||
215 | else | ||
216 | { | ||
217 | /* additional stream for file */ | ||
218 | fobp->flags &= ~FO_SINGLE; | ||
219 | fobp->flags |= callflags & FO_TRUNC; | ||
220 | |||
221 | /* once a file/directory, always a file/directory; such a change | ||
222 | is a bug */ | ||
223 | if ((callflags ^ fobp->flags) & FO_DIRECTORY) | ||
224 | { | ||
225 | DEBUGF("%s - FO_DIRECTORY flag does not match: %p %u\n", | ||
226 | __func__, stream, callflags); | ||
227 | } | ||
228 | |||
229 | if (fobp->writers) | ||
230 | { | ||
231 | /* already writers present */ | ||
232 | fobp->writers++; | ||
233 | filestr_assign_cache(stream, &fobp->cache); | ||
234 | } | ||
235 | else if (callflags & FD_WRITE) | ||
236 | { | ||
237 | /* first writer */ | ||
238 | fobp->writers = 1; | ||
239 | file_cache_init(&fobp->cache); | ||
240 | FOR_EACH_STREAM(FIRST, fobp, s) | ||
241 | filestr_assign_cache(s, &fobp->cache); | ||
242 | } | ||
243 | /* else another reader */ | ||
244 | } | ||
245 | } | ||
246 | |||
247 | /* close the stream and free associated resources */ | ||
248 | void fileobj_fileop_close(struct filestr_base *stream) | ||
249 | { | ||
250 | switch (stream->flags) | ||
251 | { | ||
252 | case 0: /* not added to manager */ | ||
253 | case FV_NONEXIST: /* forced-closed by unmounting */ | ||
254 | filestr_base_destroy(stream); | ||
255 | return; | ||
256 | } | ||
257 | |||
258 | struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp; | ||
259 | unsigned int foflags = fobp->flags; | ||
260 | |||
261 | ll_remove(&fobp->list, &stream->node); | ||
262 | |||
263 | if ((foflags & FO_SINGLE) || fobp->writers == 0) | ||
264 | { | ||
265 | if (foflags & FO_SINGLE) | ||
266 | { | ||
267 | /* last stream for file; close everything */ | ||
268 | fileobj_unbind_file(&fobp->bind); | ||
269 | |||
270 | if (fobp->writers) | ||
271 | file_cache_free(&fobp->cache); | ||
272 | |||
273 | binding_add_to_free_list(fobp); | ||
274 | } | ||
275 | } | ||
276 | else if ((stream->flags & FD_WRITE) && --fobp->writers == 0) | ||
277 | { | ||
278 | /* only readers remain; switch back to stream-local caching */ | ||
279 | FOR_EACH_STREAM(FIRST, fobp, s) | ||
280 | filestr_copy_cache(s, &fobp->cache); | ||
281 | |||
282 | file_cache_free(&fobp->cache); | ||
283 | } | ||
284 | |||
285 | if (!(foflags & FO_SINGLE) && fobp->list.head == fobp->list.tail) | ||
286 | fobp->flags |= FO_SINGLE; /* only one open stream remaining */ | ||
287 | |||
288 | filestr_base_destroy(stream); | ||
289 | } | ||
290 | |||
291 | /* informs manager that file has been created */ | ||
292 | void fileobj_fileop_create(struct filestr_base *stream, | ||
293 | const struct file_base_info *srcinfop, | ||
294 | unsigned int callflags) | ||
295 | { | ||
296 | fileobj_fileop_open(stream, srcinfop, callflags); | ||
297 | fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1); | ||
298 | } | ||
299 | |||
300 | /* informs manager that file has been removed */ | ||
301 | void fileobj_fileop_remove(struct filestr_base *stream, | ||
302 | const struct file_base_info *oldinfop) | ||
303 | { | ||
304 | ((struct fileobj_binding *)stream->bindp)->flags |= FO_REMOVED; | ||
305 | fileobj_sync_parent((const struct file_base_info *[]){ oldinfop }, 1); | ||
306 | } | ||
307 | |||
308 | /* informs manager that file has been renamed */ | ||
309 | void fileobj_fileop_rename(struct filestr_base *stream, | ||
310 | const struct file_base_info *oldinfop) | ||
311 | { | ||
312 | /* if there is old info then this was a move and the old parent has to be | ||
313 | informed */ | ||
314 | int count = oldinfop ? 2 : 1; | ||
315 | fileobj_sync_parent(&(const struct file_base_info *[]) | ||
316 | { oldinfop, stream->infop }[2 - count], | ||
317 | count); | ||
318 | } | ||
319 | |||
320 | /* informs manager than directory entries have been updated */ | ||
321 | void fileobj_fileop_sync(struct filestr_base *stream) | ||
322 | { | ||
323 | fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1); | ||
324 | } | ||
325 | |||
326 | /* inform manager that file has been truncated */ | ||
327 | void fileobj_fileop_truncate(struct filestr_base *stream) | ||
328 | { | ||
329 | /* let caller update internal info */ | ||
330 | FOR_EACH_STREAM(FIRST, (struct fileobj_binding *)stream->bindp, s) | ||
331 | ftruncate_internal_callback(stream, s); | ||
332 | } | ||
333 | |||
334 | /* query for the pointer to the size storage for the file object */ | ||
335 | file_size_t * fileobj_get_sizep(const struct filestr_base *stream) | ||
336 | { | ||
337 | if (!stream->bindp) | ||
338 | return NULL; | ||
339 | |||
340 | return &((struct fileobj_binding *)stream->bindp)->size; | ||
341 | } | ||
342 | |||
343 | /* query manager bitflags for the file object */ | ||
344 | unsigned int fileobj_get_flags(const struct filestr_base *stream) | ||
345 | { | ||
346 | if (!stream->bindp) | ||
347 | return 0; | ||
348 | |||
349 | return ((struct fileobj_binding *)stream->bindp)->flags; | ||
350 | } | ||
351 | |||
352 | /* change manager bitflags for the file object */ | ||
353 | void fileobj_change_flags(struct filestr_base *stream, | ||
354 | unsigned int flags, unsigned int mask) | ||
355 | { | ||
356 | struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp; | ||
357 | if (fobp) | ||
358 | fobp->flags = (fobp->flags & ~mask) | (flags & mask); | ||
359 | } | ||
360 | |||
361 | /* mark all open streams on a device as "nonexistant" */ | ||
362 | void fileobj_mgr_unmount(IF_MV_NONVOID(int volume)) | ||
363 | { | ||
364 | /* right now, there is nothing else to be freed when marking a descriptor | ||
365 | as "nonexistant" but a callback could be added if that changes */ | ||
366 | FOR_EACH_VOLUME(volume, v) | ||
367 | { | ||
368 | struct fileobj_binding *fobp; | ||
369 | while ((fobp = BINDING_FIRST(BUSY, v))) | ||
370 | { | ||
371 | struct filestr_base *s; | ||
372 | while ((s = STREAM_FIRST(fobp))) | ||
373 | { | ||
374 | /* keep it "busy" to avoid races; any valid file/directory | ||
375 | descriptor returned by an open call should always be | ||
376 | closed by whomever opened it (of course!) */ | ||
377 | fileop_onclose_internal(s); | ||
378 | s->flags = FV_NONEXIST; | ||
379 | } | ||
380 | } | ||
381 | } | ||
382 | } | ||
383 | |||
384 | /* one-time init at startup */ | ||
385 | void fileobj_mgr_init(void) | ||
386 | { | ||
387 | for (unsigned int i = 0; i < NUM_VOLUMES; i++) | ||
388 | ll_init(BUSY_BINDINGS(i)); | ||
389 | |||
390 | ll_init(FREE_BINDINGS()); | ||
391 | for (unsigned int i = 0; i < MAX_FILEOBJS; i++) | ||
392 | { | ||
393 | mutex_init(&stream_mutexes[i]); | ||
394 | binding_add_to_free_list(&fobindings[i]); | ||
395 | } | ||
396 | } | ||
diff --git a/firmware/common/pathfuncs.c b/firmware/common/pathfuncs.c new file mode 100644 index 0000000000..4410275adb --- /dev/null +++ b/firmware/common/pathfuncs.c | |||
@@ -0,0 +1,421 @@ | |||
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 <string.h> | ||
22 | #include <ctype.h> | ||
23 | #include "system.h" | ||
24 | #include "pathfuncs.h" | ||
25 | #include "string-extra.h" | ||
26 | |||
27 | #ifdef HAVE_MULTIVOLUME | ||
28 | #include <stdio.h> | ||
29 | #include "storage.h" | ||
30 | |||
31 | enum storage_name_dec_indexes | ||
32 | { | ||
33 | #if (CONFIG_STORAGE & STORAGE_ATA) | ||
34 | STORAGE_DEC_IDX_ATA, | ||
35 | #endif | ||
36 | #if (CONFIG_STORAGE & STORAGE_MMC) | ||
37 | STORAGE_DEC_IDX_MMC, | ||
38 | #endif | ||
39 | #if (CONFIG_STORAGE & STORAGE_SD) | ||
40 | STORAGE_DEC_IDX_SD, | ||
41 | #endif | ||
42 | #if (CONFIG_STORAGE & STORAGE_NAND) | ||
43 | STORAGE_DEC_IDX_NAND, | ||
44 | #endif | ||
45 | #if (CONFIG_STORAGE & STORAGE_RAMDISK) | ||
46 | STORAGE_DEC_IDX_RAMDISK, | ||
47 | #endif | ||
48 | #if (CONFIG_STORAGE & STORAGE_HOSTFS) | ||
49 | STORAGE_DEC_IDX_HOSTFS, | ||
50 | #endif | ||
51 | STORAGE_NUM_DEC_IDX, | ||
52 | }; | ||
53 | |||
54 | static const char * const storage_dec_names[STORAGE_NUM_DEC_IDX+1] = | ||
55 | { | ||
56 | #if (CONFIG_STORAGE & STORAGE_ATA) | ||
57 | [STORAGE_DEC_IDX_ATA] = ATA_VOL_DEC, | ||
58 | #endif | ||
59 | #if (CONFIG_STORAGE & STORAGE_MMC) | ||
60 | [STORAGE_DEC_IDX_MMC] = MMC_VOL_DEC, | ||
61 | #endif | ||
62 | #if (CONFIG_STORAGE & STORAGE_SD) | ||
63 | [STORAGE_DEC_IDX_SD] = SD_VOL_DEC, | ||
64 | #endif | ||
65 | #if (CONFIG_STORAGE & STORAGE_NAND) | ||
66 | [STORAGE_DEC_IDX_NAND] = NAND_VOL_DEC, | ||
67 | #endif | ||
68 | #if (CONFIG_STORAGE & STORAGE_RAMDISK) | ||
69 | [STORAGE_DEC_IDX_RAMDISK] = RAMDISK_VOL_DEC, | ||
70 | #endif | ||
71 | #if (CONFIG_STORAGE & STORAGE_HOSTFS) | ||
72 | [STORAGE_DEC_IDX_HOSTFS] = HOSTFS_VOL_DEC, | ||
73 | #endif | ||
74 | [STORAGE_NUM_DEC_IDX] = DEFAULT_VOL_DEC, | ||
75 | }; | ||
76 | |||
77 | static const unsigned char storage_dec_indexes[STORAGE_NUM_TYPES+1] = | ||
78 | { | ||
79 | [0 ... STORAGE_NUM_TYPES] = STORAGE_NUM_DEC_IDX, | ||
80 | #if (CONFIG_STORAGE & STORAGE_ATA) | ||
81 | [STORAGE_ATA_NUM] = STORAGE_DEC_IDX_ATA, | ||
82 | #endif | ||
83 | #if (CONFIG_STORAGE & STORAGE_MMC) | ||
84 | [STORAGE_MMC_NUM] = STORAGE_DEC_IDX_MMC, | ||
85 | #endif | ||
86 | #if (CONFIG_STORAGE & STORAGE_SD) | ||
87 | [STORAGE_SD_NUM] = STORAGE_DEC_IDX_SD, | ||
88 | #endif | ||
89 | #if (CONFIG_STORAGE & STORAGE_NAND) | ||
90 | [STORAGE_NAND_NUM] = STORAGE_DEC_IDX_NAND, | ||
91 | #endif | ||
92 | #if (CONFIG_STORAGE & STORAGE_RAMDISK) | ||
93 | [STORAGE_RAMDISK_NUM] = STORAGE_DEC_IDX_RAMDISK, | ||
94 | #endif | ||
95 | #if (CONFIG_STORAGE & STORAGE_HOSTFS) | ||
96 | [STORAGE_HOSTFS_NUM] = STORAGE_DEC_IDX_HOSTFS, | ||
97 | #endif | ||
98 | }; | ||
99 | |||
100 | /* Returns on which volume this is and sets *nameptr to the portion of the | ||
101 | * path after the volume specifier, which could be the null if the path is | ||
102 | * just a volume root. If *nameptr > name, then a volume specifier was | ||
103 | * found. If 'greedy' is 'true', then it all separators after the volume | ||
104 | * specifier are consumed, if one was found. | ||
105 | */ | ||
106 | int path_strip_volume(const char *name, const char **nameptr, bool greedy) | ||
107 | { | ||
108 | int volume = 0; | ||
109 | const char *t = name; | ||
110 | int c, v = 0; | ||
111 | |||
112 | /* format: "/<xxx##>/foo/bar" | ||
113 | * the "xxx" is pure decoration; only an unbroken trailing string of | ||
114 | * digits within the brackets is parsed as the volume number and of | ||
115 | * those, only the last ones VOL_MUM_MAX allows. | ||
116 | */ | ||
117 | c = *(t = GOBBLE_PATH_SEPCH(t)); /* skip all leading slashes */ | ||
118 | if (c != VOL_START_TOK) /* missing start token? no volume */ | ||
119 | goto volume0; | ||
120 | |||
121 | do | ||
122 | { | ||
123 | switch (c) | ||
124 | { | ||
125 | case '0' ... '9': /* digit; parse volume number */ | ||
126 | v = (v * 10 + c - '0') % VOL_NUM_MAX; | ||
127 | break; | ||
128 | case '\0': | ||
129 | case PATH_SEPCH: /* no closing bracket; no volume */ | ||
130 | goto volume0; | ||
131 | default: /* something else; reset volume */ | ||
132 | v = 0; | ||
133 | } | ||
134 | } | ||
135 | while ((c = *++t) != VOL_END_TOK); /* found end token? */ | ||
136 | |||
137 | if (!(c = *++t)) /* no more path and no '/' is ok */ | ||
138 | ; | ||
139 | else if (c != PATH_SEPCH) /* more path and no separator after end */ | ||
140 | goto volume0; | ||
141 | else if (greedy) | ||
142 | t = GOBBLE_PATH_SEPCH(++t); /* strip remaining separators */ | ||
143 | |||
144 | /* if 'greedy' is true and **nameptr == '\0' then it's only a volume | ||
145 | root whether or not it has trailing separators */ | ||
146 | |||
147 | volume = v; | ||
148 | name = t; | ||
149 | volume0: | ||
150 | if (nameptr) | ||
151 | *nameptr = name; | ||
152 | return volume; | ||
153 | } | ||
154 | |||
155 | /* Returns the volume specifier decorated with the storage type name. | ||
156 | * Assumes the supplied buffer size is at least {VOL_MAX_LEN}+1. | ||
157 | */ | ||
158 | int get_volume_name(int volume, char *buffer) | ||
159 | { | ||
160 | if (volume < 0) | ||
161 | { | ||
162 | *buffer = '\0'; | ||
163 | return 0; | ||
164 | } | ||
165 | |||
166 | volume %= VOL_NUM_MAX; /* as path parser would have it */ | ||
167 | |||
168 | int type = storage_driver_type(volume_drive(volume)); | ||
169 | if (type < 0 || type > STORAGE_NUM_TYPES) | ||
170 | type = STORAGE_NUM_TYPES; | ||
171 | |||
172 | const char *voldec = storage_dec_names[storage_dec_indexes[type]]; | ||
173 | return snprintf(buffer, VOL_MAX_LEN + 1, "%c%s%d%c", | ||
174 | VOL_START_TOK, voldec, volume, VOL_END_TOK); | ||
175 | } | ||
176 | #endif /* HAVE_MULTIVOLUME */ | ||
177 | |||
178 | /* Just like path_strip_volume() but strips a leading drive specifier and | ||
179 | * returns the drive number (A=0, B=1, etc.). -1 means no drive was found. | ||
180 | * If 'greedy' is 'true', all separators after the volume are consumed. | ||
181 | */ | ||
182 | int path_strip_drive(const char *name, const char **nameptr, bool greedy) | ||
183 | { | ||
184 | int c = toupper(*name); | ||
185 | |||
186 | if (c >= 'A' && c <= 'Z' && name[1] == PATH_DRVSEPCH) | ||
187 | { | ||
188 | name = &name[2]; | ||
189 | if (greedy) | ||
190 | name = GOBBLE_PATH_SEPCH(name); | ||
191 | |||
192 | *nameptr = name; | ||
193 | return c - 'A'; | ||
194 | } | ||
195 | |||
196 | *nameptr = name; | ||
197 | return -1; | ||
198 | } | ||
199 | |||
200 | /* Strips leading and trailing whitespace from a path | ||
201 | * " a/b \txyz" *nameptr->a, len=3: "a/b" | ||
202 | */ | ||
203 | size_t path_trim_whitespace(const char *name, const char **nameptr) | ||
204 | { | ||
205 | int c; | ||
206 | while ((c = *name) <= ' ' && c) | ||
207 | ++name; | ||
208 | |||
209 | const char *first = name; | ||
210 | const char *last = name; | ||
211 | |||
212 | while (1) | ||
213 | { | ||
214 | if (c < ' ') | ||
215 | { | ||
216 | *nameptr = first; | ||
217 | return last - first; | ||
218 | } | ||
219 | |||
220 | while ((c = *++name) > ' '); | ||
221 | last = name; | ||
222 | while (c == ' ') c = *++name; | ||
223 | } | ||
224 | } | ||
225 | |||
226 | /* Strips directory components from the path | ||
227 | * "" *nameptr->NUL, len=0: "" | ||
228 | * "/" *nameptr->/, len=1: "/" | ||
229 | * "//" *nameptr->2nd /, len=1: "/" | ||
230 | * "/a" *nameptr->a, len=1: "a" | ||
231 | * "/a/bc" *nameptr->b, len=2: "bc" | ||
232 | * "d" *nameptr->d, len=1: "d" | ||
233 | * "ef/gh" *nameptr->g, len=2: "gh" | ||
234 | */ | ||
235 | size_t path_basename(const char *name, const char **nameptr) | ||
236 | { | ||
237 | const char *p = name; | ||
238 | const char *q = p; | ||
239 | const char *r = q; | ||
240 | |||
241 | while (*(p = GOBBLE_PATH_SEPCH(p))) | ||
242 | { | ||
243 | q = p; | ||
244 | p = GOBBLE_PATH_COMP(++p); | ||
245 | r = p; | ||
246 | } | ||
247 | |||
248 | if (r == name && p > name) | ||
249 | q = p, r = q--; /* root - return last slash */ | ||
250 | /* else path is an empty string */ | ||
251 | |||
252 | *nameptr = q; | ||
253 | return r - q; | ||
254 | } | ||
255 | |||
256 | /* Strips the trailing component from the path | ||
257 | * "" *nameptr->NUL, len=0: "" | ||
258 | * "/" *nameptr->/, len=1: "/" | ||
259 | * "//" *nameptr->2nd /, len=1: "/" | ||
260 | * "/a" *nameptr->/, len=1: "/" | ||
261 | * "/a/bc" *nameptr->/, len=2: "/a" | ||
262 | * "d" *nameptr->d, len=0: "" | ||
263 | * "ef/gh" *nameptr->e, len=2: "ef" | ||
264 | */ | ||
265 | size_t path_dirname(const char *name, const char **nameptr) | ||
266 | { | ||
267 | const char *p = GOBBLE_PATH_SEPCH(name); | ||
268 | const char *q = name; | ||
269 | const char *r = p; | ||
270 | |||
271 | while (*(p = GOBBLE_PATH_COMP(p))) | ||
272 | { | ||
273 | const char *s = p; | ||
274 | |||
275 | if (!*(p = GOBBLE_PATH_SEPCH(p))) | ||
276 | break; | ||
277 | |||
278 | q = s; | ||
279 | } | ||
280 | |||
281 | if (q == name && r > name) | ||
282 | name = r, q = name--; /* root - return last slash */ | ||
283 | |||
284 | *nameptr = name; | ||
285 | return q - name; | ||
286 | } | ||
287 | |||
288 | /* Removes trailing separators from a path | ||
289 | * "" *nameptr->NUL, len=0: "" | ||
290 | * "/" *nameptr->/, len=1: "/" | ||
291 | * "//" *nameptr->2nd /, len=1: "/" | ||
292 | * "/a/" *nameptr->/, len=2: "/a" | ||
293 | * "//b/" *nameptr->1st /, len=3: "//b" | ||
294 | * "/c/" *nameptr->/, len=2: "/c" | ||
295 | */ | ||
296 | size_t path_strip_trailing_separators(const char *name, const char **nameptr) | ||
297 | { | ||
298 | const char *p; | ||
299 | size_t len = path_basename(name, &p); | ||
300 | |||
301 | if (len == 1 && *p == '/' && p > name) | ||
302 | { | ||
303 | *nameptr = p; | ||
304 | name = p - 1; /* root with multiple separators */ | ||
305 | } | ||
306 | else | ||
307 | { | ||
308 | *nameptr = name; | ||
309 | p += len; /* length to end of basename */ | ||
310 | } | ||
311 | |||
312 | return p - name; | ||
313 | } | ||
314 | |||
315 | /* Transforms "wrong" separators into the correct ones | ||
316 | * "c:\windows\system32" -> "c:/windows/system32" | ||
317 | * | ||
318 | * 'path' and 'dstpath' may either be the same buffer or non-overlapping | ||
319 | */ | ||
320 | void path_correct_separators(char *dstpath, const char *path) | ||
321 | { | ||
322 | char *dstp = dstpath; | ||
323 | const char *p = path; | ||
324 | |||
325 | while (1) | ||
326 | { | ||
327 | const char *next = strchr(p, PATH_BADSEPCH); | ||
328 | if (!next) | ||
329 | break; | ||
330 | |||
331 | size_t size = next - p; | ||
332 | |||
333 | if (dstpath != path) | ||
334 | memcpy(dstp, p, size); /* not in-place */ | ||
335 | |||
336 | dstp += size; | ||
337 | *dstp++ = PATH_SEPCH; | ||
338 | p = next + 1; | ||
339 | } | ||
340 | |||
341 | if (dstpath != path) | ||
342 | strcpy(dstp, p); | ||
343 | } | ||
344 | |||
345 | /* Appends one path to another, adding separators between components if needed. | ||
346 | * Return value and behavior is otherwise as strlcpy so that truncation may be | ||
347 | * detected. | ||
348 | * | ||
349 | * For basepath and component: | ||
350 | * PA_SEP_HARD adds a separator even if the base path is empty | ||
351 | * PA_SEP_SOFT adds a separator only if the base path is not empty | ||
352 | */ | ||
353 | size_t path_append(char *buf, const char *basepath, | ||
354 | const char *component, size_t bufsize) | ||
355 | { | ||
356 | const char *base = basepath && basepath[0] ? basepath : buf; | ||
357 | if (!base) | ||
358 | return bufsize; /* won't work to get lengths from buf */ | ||
359 | |||
360 | if (!buf) | ||
361 | bufsize = 0; | ||
362 | |||
363 | if (path_is_absolute(component)) | ||
364 | { | ||
365 | /* 'component' is absolute; replace all */ | ||
366 | basepath = component; | ||
367 | component = ""; | ||
368 | } | ||
369 | |||
370 | /* if basepath is not null or empty, buffer contents are replaced, | ||
371 | otherwise buf contains the base path */ | ||
372 | size_t len = base == buf ? strlen(buf) : strlcpy(buf, basepath, bufsize); | ||
373 | |||
374 | bool separate = false; | ||
375 | |||
376 | if (!basepath || !component) | ||
377 | separate = !len || base[len-1] != PATH_SEPCH; | ||
378 | else if (component[0]) | ||
379 | separate = len && base[len-1] != PATH_SEPCH; | ||
380 | |||
381 | /* caller might lie about size of buf yet use buf as the base */ | ||
382 | if (base == buf && bufsize && len >= bufsize) | ||
383 | buf[bufsize - 1] = '\0'; | ||
384 | |||
385 | buf += len; | ||
386 | bufsize -= MIN(len, bufsize); | ||
387 | |||
388 | if (separate && (len++, bufsize > 0) && --bufsize > 0) | ||
389 | *buf++ = PATH_SEPCH; | ||
390 | |||
391 | return len + strlcpy(buf, component ?: "", bufsize); | ||
392 | } | ||
393 | |||
394 | /* Returns the location and length of the next path component, consuming the | ||
395 | * input in the process. | ||
396 | * | ||
397 | * "/a/bc/d" breaks into: | ||
398 | * start: *namep->1st / | ||
399 | * call 1: *namep->a, *pathp->2nd / len=1: "a" | ||
400 | * call 2: *namep->b, *pathp->3rd / len=2: "bc" | ||
401 | * call 3: *namep->d, *pathp->NUL, len=1: "d" | ||
402 | * call 4: *namep->NUL, *pathp->NUL, len=0: "" | ||
403 | * | ||
404 | * Returns: 0 if the input has been consumed | ||
405 | * The length of the component otherwise | ||
406 | */ | ||
407 | ssize_t parse_path_component(const char **pathp, const char **namep) | ||
408 | { | ||
409 | /* a component starts at a non-separator and continues until the next | ||
410 | separator or null */ | ||
411 | const char *p = GOBBLE_PATH_SEPCH(*pathp); | ||
412 | const char *name = p; | ||
413 | |||
414 | if (*p) | ||
415 | p = GOBBLE_PATH_COMP(++p); | ||
416 | |||
417 | *pathp = p; | ||
418 | *namep = name; | ||
419 | |||
420 | return p - name; | ||
421 | } | ||
diff --git a/firmware/common/rbpaths.c b/firmware/common/rbpaths.c deleted file mode 100644 index 69543bc3a7..0000000000 --- a/firmware/common/rbpaths.c +++ /dev/null | |||
@@ -1,432 +0,0 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2010 Thomas Martitz | ||
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 | |||
22 | |||
23 | #include <stdio.h> /* snprintf */ | ||
24 | #include <stdlib.h> | ||
25 | #include <stdarg.h> | ||
26 | #include <sys/stat.h> | ||
27 | #include <time.h> | ||
28 | #include <unistd.h> | ||
29 | #include "config.h" | ||
30 | #include "rbpaths.h" | ||
31 | #include "crc32.h" | ||
32 | #include "file.h" /* MAX_PATH */ | ||
33 | #include "logf.h" | ||
34 | #include "gcc_extensions.h" | ||
35 | #include "string-extra.h" | ||
36 | #include "filefuncs.h" | ||
37 | |||
38 | /* In this file we need the actual OS library functions, not the shadowed | ||
39 | * wrapper used within Rockbox' application code (except SDL adds | ||
40 | * another layer) */ | ||
41 | #undef open | ||
42 | #undef creat | ||
43 | #undef remove | ||
44 | #undef rename | ||
45 | #undef opendir | ||
46 | #undef closedir | ||
47 | #undef readdir | ||
48 | #undef mkdir | ||
49 | #undef rmdir | ||
50 | #undef dirent | ||
51 | #undef DIR | ||
52 | #undef readlink | ||
53 | |||
54 | #if (CONFIG_PLATFORM & PLATFORM_ANDROID) | ||
55 | static const char rbhome[] = "/sdcard"; | ||
56 | #elif (CONFIG_PLATFORM & (PLATFORM_SDL|PLATFORM_MAEMO|PLATFORM_PANDORA)) && !defined(__PCTOOL__) | ||
57 | const char *rbhome; | ||
58 | #else | ||
59 | /* YPR0, YPR1 */ | ||
60 | static const char rbhome[] = HOME_DIR; | ||
61 | #endif | ||
62 | |||
63 | #if !(defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1)) && !defined(__PCTOOL__) | ||
64 | /* Special dirs are user-accessible (and user-writable) dirs which take priority | ||
65 | * over the ones where Rockbox is installed to. Classic example would be | ||
66 | * $HOME/.config/rockbox.org vs /usr/share/rockbox */ | ||
67 | #define HAVE_SPECIAL_DIRS | ||
68 | #endif | ||
69 | |||
70 | /* flags for get_user_file_path() */ | ||
71 | /* whether you need write access to that file/dir, especially true | ||
72 | * for runtime generated files (config.cfg) */ | ||
73 | #define NEED_WRITE (1<<0) | ||
74 | /* file or directory? */ | ||
75 | #define IS_FILE (1<<1) | ||
76 | |||
77 | #ifdef HAVE_MULTIDRIVE | ||
78 | static uint32_t rbhome_hash; | ||
79 | /* A special link is created under e.g. HOME_DIR/<microSD1>, e.g. to access | ||
80 | * external storage in a convinient location, much similar to the mount | ||
81 | * point on our native targets. Here they are treated as symlink (one which | ||
82 | * doesn't actually exist in the filesystem and therefore we have to override | ||
83 | * readlink() */ | ||
84 | static const char *handle_special_links(const char* link, unsigned flags, | ||
85 | char *buf, const size_t bufsize) | ||
86 | { | ||
87 | (void) flags; | ||
88 | char vol_string[VOL_ENUM_POS + 8]; | ||
89 | int len = sprintf(vol_string, VOL_NAMES, 1); | ||
90 | |||
91 | /* link might be passed with or without HOME_DIR expanded. To handle | ||
92 | * both perform substring matching (VOL_NAMES is unique enough) */ | ||
93 | const char *begin = strstr(link, vol_string); | ||
94 | if (begin) | ||
95 | { | ||
96 | /* begin now points to the start of vol_string within link, | ||
97 | * we want to copy the remainder of the paths, prefixed by | ||
98 | * the actual mount point (the remainder might be "") */ | ||
99 | snprintf(buf, bufsize, MULTIDRIVE_DIR"%s", begin + len); | ||
100 | return buf; | ||
101 | } | ||
102 | |||
103 | return link; | ||
104 | } | ||
105 | #endif | ||
106 | |||
107 | void paths_init(void) | ||
108 | { | ||
109 | #ifdef HAVE_SPECIAL_DIRS | ||
110 | /* make sure $HOME/.config/rockbox.org exists, it's needed for config.cfg */ | ||
111 | #if (CONFIG_PLATFORM & PLATFORM_ANDROID) | ||
112 | mkdir("/sdcard/rockbox", 0777); | ||
113 | mkdir("/sdcard/rockbox/rocks.data", 0777); | ||
114 | #else | ||
115 | char config_dir[MAX_PATH]; | ||
116 | |||
117 | const char *home = getenv("RBROOT"); | ||
118 | if (!home) | ||
119 | { | ||
120 | home = getenv("HOME"); | ||
121 | } | ||
122 | if (!home) | ||
123 | { | ||
124 | logf("HOME environment var not set. Can't write config"); | ||
125 | return; | ||
126 | } | ||
127 | |||
128 | rbhome = home; | ||
129 | snprintf(config_dir, sizeof(config_dir), "%s/.config", home); | ||
130 | mkdir(config_dir, 0777); | ||
131 | snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org", home); | ||
132 | mkdir(config_dir, 0777); | ||
133 | /* Plugin data directory */ | ||
134 | snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org/rocks.data", home); | ||
135 | mkdir(config_dir, 0777); | ||
136 | #endif | ||
137 | #endif /* HAVE_SPECIAL_DIRS */ | ||
138 | |||
139 | #ifdef HAVE_MULTIDRIVE | ||
140 | rbhome_hash = crc_32((const void *) rbhome, strlen(rbhome), 0xffffffff); | ||
141 | #endif /* HAVE_MULTIDRIVE */ | ||
142 | } | ||
143 | |||
144 | #ifdef HAVE_SPECIAL_DIRS | ||
145 | static bool try_path(const char* filename, unsigned flags) | ||
146 | { | ||
147 | if (flags & IS_FILE) | ||
148 | { | ||
149 | if (file_exists(filename)) | ||
150 | return true; | ||
151 | } | ||
152 | else | ||
153 | { | ||
154 | if (dir_exists(filename)) | ||
155 | return true; | ||
156 | } | ||
157 | return false; | ||
158 | } | ||
159 | |||
160 | static const char* _get_user_file_path(const char *path, | ||
161 | unsigned flags, | ||
162 | char* buf, | ||
163 | const size_t bufsize) | ||
164 | { | ||
165 | const char *ret = path; | ||
166 | const char *pos = path; | ||
167 | /* replace ROCKBOX_DIR in path with $HOME/.config/rockbox.org */ | ||
168 | pos += ROCKBOX_DIR_LEN; | ||
169 | if (*pos == '/') pos += 1; | ||
170 | |||
171 | #if (CONFIG_PLATFORM & PLATFORM_ANDROID) | ||
172 | if (snprintf(buf, bufsize, "/sdcard/rockbox/%s", pos) | ||
173 | #else | ||
174 | if (snprintf(buf, bufsize, "%s/.config/rockbox.org/%s", rbhome, pos) | ||
175 | #endif | ||
176 | >= (int)bufsize) | ||
177 | return NULL; | ||
178 | |||
179 | /* always return the replacement buffer (pointing to $HOME) if | ||
180 | * write access is needed */ | ||
181 | if (flags & NEED_WRITE) | ||
182 | ret = buf; | ||
183 | else if (try_path(buf, flags)) | ||
184 | ret = buf; | ||
185 | |||
186 | if (ret != buf) /* not found in $HOME, try ROCKBOX_BASE_DIR, !NEED_WRITE only */ | ||
187 | { | ||
188 | if (snprintf(buf, bufsize, ROCKBOX_SHARE_PATH "/%s", pos) >= (int)bufsize) | ||
189 | return NULL; | ||
190 | |||
191 | if (try_path(buf, flags)) | ||
192 | ret = buf; | ||
193 | } | ||
194 | |||
195 | return ret; | ||
196 | } | ||
197 | |||
198 | #endif | ||
199 | |||
200 | static const char* handle_special_dirs(const char* dir, unsigned flags, | ||
201 | char *buf, const size_t bufsize) | ||
202 | { | ||
203 | (void) flags; (void) buf; (void) bufsize; | ||
204 | #ifdef HAVE_SPECIAL_DIRS | ||
205 | if (!strncmp(HOME_DIR, dir, HOME_DIR_LEN)) | ||
206 | { | ||
207 | const char *p = dir + HOME_DIR_LEN; | ||
208 | while (*p == '/') p++; | ||
209 | snprintf(buf, bufsize, "%s/%s", rbhome, p); | ||
210 | dir = buf; | ||
211 | } | ||
212 | else if (!strncmp(ROCKBOX_DIR, dir, ROCKBOX_DIR_LEN)) | ||
213 | dir = _get_user_file_path(dir, flags, buf, bufsize); | ||
214 | #endif | ||
215 | #ifdef HAVE_MULTIDRIVE | ||
216 | dir = handle_special_links(dir, flags, buf, bufsize); | ||
217 | #endif | ||
218 | return dir; | ||
219 | } | ||
220 | |||
221 | int app_open(const char *name, int o, ...) | ||
222 | { | ||
223 | char realpath[MAX_PATH]; | ||
224 | va_list ap; | ||
225 | int fd; | ||
226 | int flags = IS_FILE; | ||
227 | if (o & (O_CREAT|O_RDWR|O_TRUNC|O_WRONLY)) | ||
228 | flags |= NEED_WRITE; | ||
229 | |||
230 | name = handle_special_dirs(name, flags, realpath, sizeof(realpath)); | ||
231 | |||
232 | va_start(ap, o); | ||
233 | fd = open(name, o, va_arg(ap, unsigned int)); | ||
234 | va_end(ap); | ||
235 | |||
236 | return fd; | ||
237 | } | ||
238 | |||
239 | int app_creat(const char* name, mode_t mode) | ||
240 | { | ||
241 | return app_open(name, O_CREAT|O_WRONLY|O_TRUNC, mode); | ||
242 | } | ||
243 | |||
244 | int app_remove(const char *name) | ||
245 | { | ||
246 | char realpath[MAX_PATH]; | ||
247 | const char *fname = handle_special_dirs(name, NEED_WRITE, realpath, sizeof(realpath)); | ||
248 | |||
249 | return remove(fname); | ||
250 | } | ||
251 | |||
252 | int app_rename(const char *old, const char *new) | ||
253 | { | ||
254 | char realpath_old[MAX_PATH], realpath_new[MAX_PATH]; | ||
255 | const char *final_old, *final_new; | ||
256 | |||
257 | final_old = handle_special_dirs(old, NEED_WRITE, realpath_old, sizeof(realpath_old)); | ||
258 | final_new = handle_special_dirs(new, NEED_WRITE, realpath_new, sizeof(realpath_new)); | ||
259 | |||
260 | return rename(final_old, final_new); | ||
261 | } | ||
262 | |||
263 | /* need to wrap around DIR* because we need to save the parent's | ||
264 | * directory path in order to determine dirinfo, required to implement | ||
265 | * get_dir_info() */ | ||
266 | struct __dir { | ||
267 | DIR *dir; | ||
268 | #ifdef HAVE_MULTIDRIVE | ||
269 | int volumes_returned; | ||
270 | /* A crc of rbhome is used to speed op the common case where | ||
271 | * readdir()/get_dir_info() is called on non-rbhome paths, because | ||
272 | * each call needs to check against rbhome for the virtual | ||
273 | * mount point of the external storage */ | ||
274 | uint32_t path_hash; | ||
275 | #endif | ||
276 | char path[]; | ||
277 | }; | ||
278 | |||
279 | struct dirinfo dir_get_info(DIR* _parent, struct dirent *dir) | ||
280 | { | ||
281 | struct __dir *parent = (struct __dir*)_parent; | ||
282 | struct stat s; | ||
283 | struct tm *tm = NULL; | ||
284 | struct dirinfo ret; | ||
285 | char path[MAX_PATH]; | ||
286 | |||
287 | memset(&ret, 0, sizeof(ret)); | ||
288 | |||
289 | #ifdef HAVE_MULTIDRIVE | ||
290 | char vol_string[VOL_ENUM_POS + 8]; | ||
291 | sprintf(vol_string, VOL_NAMES, 1); | ||
292 | if (UNLIKELY(rbhome_hash == parent->path_hash) && | ||
293 | /* compare path anyway because of possible hash collision */ | ||
294 | !strcmp(vol_string, dir->d_name)) | ||
295 | { | ||
296 | ret.attribute = ATTR_LINK; | ||
297 | strcpy(path, MULTIDRIVE_DIR); | ||
298 | } | ||
299 | else | ||
300 | #endif | ||
301 | snprintf(path, sizeof(path), "%s/%s", parent->path, dir->d_name); | ||
302 | |||
303 | |||
304 | if (!lstat(path, &s)) | ||
305 | { | ||
306 | int err = 0; | ||
307 | if (S_ISLNK(s.st_mode)) | ||
308 | { | ||
309 | ret.attribute |= ATTR_LINK; | ||
310 | err = stat(path, &s); | ||
311 | } | ||
312 | if (!err) | ||
313 | { | ||
314 | if (S_ISDIR(s.st_mode)) | ||
315 | ret.attribute |= ATTR_DIRECTORY; | ||
316 | |||
317 | ret.size = s.st_size; | ||
318 | if ((tm = localtime(&(s.st_mtime)))) | ||
319 | { | ||
320 | ret.wrtdate = ((tm->tm_year - 80) << 9) | | ||
321 | ((tm->tm_mon + 1) << 5) | | ||
322 | tm->tm_mday; | ||
323 | ret.wrttime = (tm->tm_hour << 11) | | ||
324 | (tm->tm_min << 5) | | ||
325 | (tm->tm_sec >> 1); | ||
326 | } | ||
327 | } | ||
328 | } | ||
329 | |||
330 | return ret; | ||
331 | } | ||
332 | |||
333 | DIR* app_opendir(const char *_name) | ||
334 | { | ||
335 | size_t name_len; | ||
336 | char realpath[MAX_PATH]; | ||
337 | const char *name = handle_special_dirs(_name, 0, realpath, sizeof(realpath)); | ||
338 | name_len = strlen(name); | ||
339 | char *buf = malloc(sizeof(struct __dir) + name_len+1); | ||
340 | if (!buf) | ||
341 | return NULL; | ||
342 | |||
343 | struct __dir *this = (struct __dir*)buf; | ||
344 | /* carefully remove any trailing slash from the input, so that | ||
345 | * hash/path matching in readdir() works properly */ | ||
346 | while (name[name_len-1] == '/' && name_len > 1) | ||
347 | name_len -= 1; | ||
348 | /* strcpy cannot be used because of trailing slashes */ | ||
349 | memcpy(this->path, name, name_len); | ||
350 | this->path[name_len] = 0; | ||
351 | this->dir = opendir(this->path); | ||
352 | |||
353 | if (!this->dir) | ||
354 | { | ||
355 | free(buf); | ||
356 | return NULL; | ||
357 | } | ||
358 | #ifdef HAVE_MULTIDRIVE | ||
359 | this->volumes_returned = 0; | ||
360 | this->path_hash = crc_32((const void *)this->path, name_len, 0xffffffff); | ||
361 | #endif | ||
362 | |||
363 | return (DIR*)this; | ||
364 | } | ||
365 | |||
366 | int app_closedir(DIR *dir) | ||
367 | { | ||
368 | struct __dir *this = (struct __dir*)dir; | ||
369 | int ret = closedir(this->dir); | ||
370 | free(this); | ||
371 | return ret; | ||
372 | } | ||
373 | |||
374 | |||
375 | struct dirent* app_readdir(DIR* dir) | ||
376 | { | ||
377 | struct __dir *d = (struct __dir*)dir; | ||
378 | #ifdef HAVE_MULTIDRIVE | ||
379 | /* this is not MT-safe but OK according to man readdir */ | ||
380 | static struct dirent voldir; | ||
381 | if (UNLIKELY(rbhome_hash == d->path_hash) | ||
382 | && d->volumes_returned < (NUM_VOLUMES-1) | ||
383 | && volume_present(d->volumes_returned+1) | ||
384 | /* compare path anyway because of possible hash collision */ | ||
385 | && !strcmp(d->path, rbhome)) | ||
386 | { | ||
387 | d->volumes_returned += 1; | ||
388 | sprintf(voldir.d_name, VOL_NAMES, d->volumes_returned); | ||
389 | return &voldir; | ||
390 | } | ||
391 | #endif | ||
392 | return readdir(d->dir); | ||
393 | } | ||
394 | |||
395 | |||
396 | int app_mkdir(const char* name) | ||
397 | { | ||
398 | char realpath[MAX_PATH]; | ||
399 | const char *fname = handle_special_dirs(name, NEED_WRITE, realpath, sizeof(realpath)); | ||
400 | return mkdir(fname, 0777); | ||
401 | } | ||
402 | |||
403 | |||
404 | int app_rmdir(const char* name) | ||
405 | { | ||
406 | char realpath[MAX_PATH]; | ||
407 | const char *fname = handle_special_dirs(name, NEED_WRITE, realpath, sizeof(realpath)); | ||
408 | return rmdir(fname); | ||
409 | } | ||
410 | |||
411 | |||
412 | /* On MD we create a virtual symlink for the external drive, | ||
413 | * for this we need to override readlink(). */ | ||
414 | ssize_t app_readlink(const char *path, char *buf, size_t bufsiz) | ||
415 | { | ||
416 | char _buf[MAX_PATH]; | ||
417 | (void) path; (void) buf; (void) bufsiz; | ||
418 | path = handle_special_dirs(path, 0, _buf, sizeof(_buf)); | ||
419 | #ifdef HAVE_MULTIDRIVE | ||
420 | /* if path == _buf then we can be sure handle_special_dir() did something | ||
421 | * and path is not an ordinary directory */ | ||
422 | if (path == _buf && !strncmp(path, MULTIDRIVE_DIR, sizeof(MULTIDRIVE_DIR)-1)) | ||
423 | { | ||
424 | /* copying NUL is not required as per readlink specification */ | ||
425 | ssize_t len = strlen(path); | ||
426 | memcpy(buf, path, len); | ||
427 | return len; | ||
428 | } | ||
429 | #endif | ||
430 | /* does not append NUL !! */ | ||
431 | return readlink(path, buf, bufsiz); | ||
432 | } | ||
diff --git a/firmware/common/unicode.c b/firmware/common/unicode.c index 3ff1814c4b..954ad47e1d 100644 --- a/firmware/common/unicode.c +++ b/firmware/common/unicode.c | |||
@@ -28,161 +28,227 @@ | |||
28 | 28 | ||
29 | #include <stdio.h> | 29 | #include <stdio.h> |
30 | #include "config.h" | 30 | #include "config.h" |
31 | #include "system.h" | ||
32 | #include "thread.h" | ||
31 | #include "file.h" | 33 | #include "file.h" |
32 | #include "debug.h" | 34 | #include "debug.h" |
33 | #include "rbunicode.h" | 35 | #include "rbunicode.h" |
34 | #include "rbpaths.h" | 36 | #include "rbpaths.h" |
37 | #include "pathfuncs.h" | ||
38 | #include "core_alloc.h" | ||
35 | 39 | ||
36 | #ifndef O_BINARY | 40 | #ifndef O_BINARY |
37 | #define O_BINARY 0 | 41 | #define O_BINARY 0 |
38 | #endif | 42 | #endif |
43 | #ifndef O_NOISODECODE | ||
44 | #define O_NOISODECODE 0 | ||
45 | #endif | ||
39 | 46 | ||
40 | static int default_codepage = 0; | 47 | #define getle16(p) (p[0] | (p[1] >> 8)) |
41 | static int loaded_cp_table = 0; | 48 | #define getbe16(p) ((p[1] << 8) | p[0]) |
42 | |||
43 | #ifdef HAVE_LCD_BITMAP | ||
44 | 49 | ||
45 | #define MAX_CP_TABLE_SIZE 32768 | 50 | #if !defined (__PCTOOL__) && (CONFIG_PLATFORM & PLATFORM_NATIVE) |
46 | #define NUM_TABLES 5 | 51 | /* Because file scanning uses the default CP table when matching entries, |
52 | on-demand loading is not feasible; we also must use the filesystem lock */ | ||
53 | #include "file_internal.h" | ||
54 | #else /* APPLICATION */ | ||
55 | #ifdef __PCTOOL__ | ||
56 | #define yield() | ||
57 | #endif | ||
58 | #define open_noiso_internal open | ||
59 | #endif /* !APPLICATION */ | ||
60 | |||
61 | #if 0 /* not needed just now (will probably end up a spinlock) */ | ||
62 | #include "mutex.h" | ||
63 | static struct mutex cp_mutex SHAREDBSS_ATTR; | ||
64 | #define cp_lock_init() mutex_init(&cp_mutex) | ||
65 | #define cp_lock_enter() mutex_lock(&cp_mutex) | ||
66 | #define cp_lock_leave() mutex_unlock(&cp_mutex) | ||
67 | #else | ||
68 | #define cp_lock_init() do {} while (0) | ||
69 | #define cp_lock_enter() asm volatile ("") | ||
70 | #define cp_lock_leave() asm volatile ("") | ||
71 | #endif | ||
47 | 72 | ||
48 | static const char * const filename[NUM_TABLES] = | 73 | enum cp_tid |
49 | { | 74 | { |
50 | CODEPAGE_DIR"/iso.cp", | 75 | CP_TID_NONE = -1, |
51 | CODEPAGE_DIR"/932.cp", /* SJIS */ | 76 | CP_TID_ISO, |
52 | CODEPAGE_DIR"/936.cp", /* GB2312 */ | 77 | CP_TID_932, |
53 | CODEPAGE_DIR"/949.cp", /* KSX1001 */ | 78 | CP_TID_936, |
54 | CODEPAGE_DIR"/950.cp" /* BIG5 */ | 79 | CP_TID_949, |
80 | CP_TID_950, | ||
55 | }; | 81 | }; |
56 | 82 | ||
57 | static const char cp_2_table[NUM_CODEPAGES] = | 83 | struct cp_info |
58 | { | 84 | { |
59 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 0 | 85 | int8_t tid; |
86 | const char *filename; | ||
87 | const char *name; | ||
60 | }; | 88 | }; |
61 | 89 | ||
62 | static const char * const name_codepages[NUM_CODEPAGES+1] = | 90 | #ifdef HAVE_LCD_BITMAP |
63 | { | ||
64 | "ISO-8859-1", | ||
65 | "ISO-8859-7", | ||
66 | "ISO-8859-8", | ||
67 | "CP1251", | ||
68 | "ISO-8859-11", | ||
69 | "CP1256", | ||
70 | "ISO-8859-9", | ||
71 | "ISO-8859-2", | ||
72 | "CP1250", | ||
73 | "CP1252", | ||
74 | "SJIS", | ||
75 | "GB-2312", | ||
76 | "KSX-1001", | ||
77 | "BIG5", | ||
78 | "UTF-8", | ||
79 | "unknown" | ||
80 | }; | ||
81 | 91 | ||
82 | #if defined(APPLICATION) && defined(__linux__) | 92 | #define MAX_CP_TABLE_SIZE 32768 |
83 | static const char * const name_codepages_linux[NUM_CODEPAGES+1] = | ||
84 | { | ||
85 | /* "ISO-8859-1" */ "iso8859-1", | ||
86 | /* "ISO-8859-7" */ "iso8859-7", | ||
87 | /* "ISO-8859-8" */ "iso8859-8", | ||
88 | /* "CP1251" */ "cp1251", | ||
89 | /* "ISO-8859-11"*/ "iso8859-11", | ||
90 | /* "CP1256" */ "cp1256", | ||
91 | /* "ISO-8859-9" */ "iso8859-9", | ||
92 | /* "ISO-8859-2" */ "iso8859-2", | ||
93 | /* "CP1250" */ "cp1250", | ||
94 | /* "CP1252" */ "iso8859-15", /* closest, linux doesnt have a codepage named cp1252 */ | ||
95 | /* "SJIS" */ "cp932", | ||
96 | /* "GB-2312" */ "cp936", | ||
97 | /* "KSX-1001" */ "cp949", | ||
98 | /* "BIG5" */ "cp950", | ||
99 | /* "UTF-8" */ "utf8", | ||
100 | /* "unknown" */ "cp437" | ||
101 | }; | ||
102 | 93 | ||
103 | const char *get_current_codepage_name_linux(void) | 94 | #define CPF_ISO "iso.cp" |
95 | #define CPF_932 "932.cp" /* SJIS */ | ||
96 | #define CPF_936 "936.cp" /* GB2312 */ | ||
97 | #define CPF_949 "949.cp" /* KSX1001 */ | ||
98 | #define CPF_950 "950.cp" /* BIG5 */ | ||
99 | |||
100 | static const struct cp_info cp_info[NUM_CODEPAGES+1] = | ||
104 | { | 101 | { |
105 | if (default_codepage < 0 || default_codepage >= NUM_CODEPAGES) | 102 | [0 ... NUM_CODEPAGES] = { CP_TID_NONE, NULL , "unknown" }, |
106 | return name_codepages_linux[NUM_CODEPAGES]; | 103 | [ISO_8859_1] = { CP_TID_NONE, NULL , "ISO-8859-1" }, |
107 | return name_codepages_linux[default_codepage]; | 104 | [ISO_8859_7] = { CP_TID_ISO , CPF_ISO, "ISO-8859-7" }, |
108 | } | 105 | [ISO_8859_8] = { CP_TID_ISO , CPF_ISO, "ISO-8859-8" }, |
109 | #endif | 106 | [WIN_1251] = { CP_TID_ISO , CPF_ISO, "CP1251" }, |
107 | [ISO_8859_11] = { CP_TID_ISO , CPF_ISO, "ISO-8859-11" }, | ||
108 | [WIN_1256] = { CP_TID_ISO , CPF_ISO, "CP1256" }, | ||
109 | [ISO_8859_9] = { CP_TID_ISO , CPF_ISO, "ISO-8859-9" }, | ||
110 | [ISO_8859_2] = { CP_TID_ISO , CPF_ISO, "ISO-8859-2" }, | ||
111 | [WIN_1250] = { CP_TID_ISO , CPF_ISO, "CP1250" }, | ||
112 | [WIN_1252] = { CP_TID_ISO , CPF_ISO, "CP1252" }, | ||
113 | [SJIS] = { CP_TID_932 , CPF_932, "SJIS" }, | ||
114 | [GB_2312] = { CP_TID_936 , CPF_936, "GB-2312" }, | ||
115 | [KSX_1001] = { CP_TID_949 , CPF_949, "KSX-1001" }, | ||
116 | [BIG_5] = { CP_TID_950 , CPF_950, "BIG5" }, | ||
117 | [UTF_8] = { CP_TID_NONE, NULL , "UTF-8" }, | ||
118 | }; | ||
110 | 119 | ||
111 | #else /* !HAVE_LCD_BITMAP, reduced support */ | 120 | #else /* !HAVE_LCD_BITMAP, reduced support */ |
112 | 121 | ||
113 | #define MAX_CP_TABLE_SIZE 768 | 122 | #define MAX_CP_TABLE_SIZE 768 |
114 | #define NUM_TABLES 1 | ||
115 | 123 | ||
116 | static const char * const filename[NUM_TABLES] = { | 124 | #define CPF_ISOMINI "isomini.cp" |
117 | CODEPAGE_DIR"/isomini.cp" | ||
118 | }; | ||
119 | 125 | ||
120 | static const char cp_2_table[NUM_CODEPAGES] = | 126 | static const struct cp_info cp_info[NUM_CODEPAGES+1] = |
121 | { | 127 | { |
122 | 0, 1, 1, 1, 1, 1, 1, 0 | 128 | [0 ... NUM_CODEPAGES] = { CP_TID_NONE, NULL , "unknown" }, |
129 | [ISO_8859_1] = { CP_TID_NONE, NULL , "ISO-8859-1" }, | ||
130 | [ISO_8859_7] = { CP_TID_ISO , CPF_ISOMINI, "ISO-8859-7" }, | ||
131 | [WIN_1251] = { CP_TID_ISO , CPF_ISOMINI, "CP1251" }, | ||
132 | [ISO_8859_9] = { CP_TID_ISO , CPF_ISOMINI, "ISO-8859-9" }, | ||
133 | [ISO_8859_2] = { CP_TID_ISO , CPF_ISOMINI, "ISO-8859-2" }, | ||
134 | [WIN_1250] = { CP_TID_ISO , CPF_ISOMINI, "CP1250" }, | ||
135 | [WIN_1252] = { CP_TID_ISO , CPF_ISOMINI, "CP1252" }, | ||
136 | [UTF_8] = { CP_TID_ISO , NULL , "UTF-8" }, | ||
123 | }; | 137 | }; |
124 | 138 | ||
125 | static const char * const name_codepages[NUM_CODEPAGES+1] = | 139 | #endif /* HAVE_LCD_BITMAP */ |
140 | |||
141 | static int default_cp = INIT_CODEPAGE; | ||
142 | static int default_cp_tid = CP_TID_NONE; | ||
143 | static int default_cp_handle = 0; | ||
144 | static int volatile default_cp_table_ref = 0; | ||
145 | |||
146 | static int loaded_cp_tid = CP_TID_NONE; | ||
147 | static int volatile cp_table_ref = 0; | ||
148 | #define CP_LOADING BIT_N(sizeof(int)*8-1) /* guard against multi loaders */ | ||
149 | |||
150 | /* non-default codepage table buffer (cannot be bufalloced! playback itself | ||
151 | may be making the load request) */ | ||
152 | static unsigned short codepage_table[MAX_CP_TABLE_SIZE+1]; | ||
153 | |||
154 | #if defined(APPLICATION) && defined(__linux__) | ||
155 | static const char * const name_codepages_linux[NUM_CODEPAGES+1] = | ||
126 | { | 156 | { |
127 | "ISO-8859-1", | 157 | [0 ... NUM_CODEPAGES] = "unknown", |
128 | "ISO-8859-7", | 158 | [ISO_8859_1] = "iso8859-1", |
129 | "CP1251", | 159 | [ISO_8859_7] = "iso8859-7", |
130 | "ISO-8859-9", | 160 | [ISO_8859_8] = "iso8859-8", |
131 | "ISO-8859-2", | 161 | [WIN_1251] = "cp1251", |
132 | "CP1250", | 162 | [ISO_8859_11] = "iso8859-11", |
133 | "CP1252", | 163 | [WIN_1256] = "cp1256", |
134 | "UTF-8", | 164 | [ISO_8859_9] = "iso8859-9", |
135 | "unknown" | 165 | [ISO_8859_2] = "iso8859-2", |
166 | [WIN_1250] = "cp1250", | ||
167 | /* iso8859-15 is closest, linux doesnt have a codepage named cp1252 */ | ||
168 | [WIN_1252] = "iso8859-15", | ||
169 | [SJIS] = "cp932", | ||
170 | [GB_2312] = "cp936", | ||
171 | [KSX_1001] = "cp949", | ||
172 | [BIG_5] = "cp950", | ||
173 | [UTF_8] = "utf8", | ||
136 | }; | 174 | }; |
137 | 175 | ||
138 | #endif | 176 | const char *get_current_codepage_name_linux(void) |
139 | 177 | { | |
140 | static unsigned short codepage_table[MAX_CP_TABLE_SIZE]; | 178 | int cp = default_cp; |
179 | if (cp < 0 || cp>= NUM_CODEPAGES) | ||
180 | cp = NUM_CODEPAGES; | ||
181 | return name_codepages_linux[cp]; | ||
182 | } | ||
183 | #endif /* defined(APPLICATION) && defined(__linux__) */ | ||
141 | 184 | ||
142 | static const unsigned char utf8comp[6] = | 185 | static const unsigned char utf8comp[6] = |
143 | { | 186 | { |
144 | 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC | 187 | 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC |
145 | }; | 188 | }; |
146 | 189 | ||
147 | /* Load codepage file into memory */ | 190 | static inline void cptable_tohw16(uint16_t *buf, unsigned int count) |
148 | static int load_cp_table(int cp) | ||
149 | { | 191 | { |
150 | int i = 0; | 192 | #ifdef ROCKBOX_BIG_ENDIAN |
151 | int table = cp_2_table[cp]; | 193 | for (unsigned int i = 0; i < count; i++) |
152 | int file, tablesize; | 194 | buf[i] = letoh16(buf[i]); |
153 | unsigned char tmp[2]; | 195 | #endif |
196 | (void)buf; (void)count; | ||
197 | } | ||
154 | 198 | ||
155 | if (table == 0 || table == loaded_cp_table) | 199 | static int move_callback(int handle, void *current, void *new) |
156 | return 1; | 200 | { |
201 | /* we don't keep a pointer but we have to stop it if this applies to a | ||
202 | buffer not yet swapped-in since it will likely be in use in an I/O | ||
203 | call */ | ||
204 | return (handle != default_cp_handle || default_cp_table_ref != 0) ? | ||
205 | BUFLIB_CB_CANNOT_MOVE : BUFLIB_CB_OK; | ||
206 | (void)current; (void)new; | ||
207 | } | ||
157 | 208 | ||
158 | file = open(filename[table-1], O_RDONLY|O_BINARY); | 209 | static int alloc_and_load_cp_table(int cp, void *buf) |
210 | { | ||
211 | static struct buflib_callbacks ops = | ||
212 | { .move_callback = move_callback }; | ||
159 | 213 | ||
160 | if (file < 0) { | 214 | /* alloc and read only if there is an associated file */ |
161 | DEBUGF("Can't open codepage file: %s.cp\n", filename[table-1]); | 215 | const char *filename = cp_info[cp].filename; |
216 | if (!filename) | ||
162 | return 0; | 217 | return 0; |
218 | |||
219 | char path[MAX_PATH]; | ||
220 | if (path_append(path, CODEPAGE_DIR, filename, sizeof (path)) | ||
221 | >= sizeof (path)) { | ||
222 | return -1; | ||
163 | } | 223 | } |
164 | 224 | ||
165 | tablesize = filesize(file) / 2; | 225 | /* must be opened without a chance of reentering from FS code */ |
226 | int fd = open_noiso_internal(path, O_RDONLY); | ||
227 | if (fd < 0) | ||
228 | return -1; | ||
166 | 229 | ||
167 | if (tablesize > MAX_CP_TABLE_SIZE) { | 230 | off_t size = filesize(fd); |
168 | DEBUGF("Invalid codepage file: %s.cp\n", filename[table-1]); | ||
169 | close(file); | ||
170 | return 0; | ||
171 | } | ||
172 | 231 | ||
173 | while (i < tablesize) { | 232 | if (size > 0 && size <= MAX_CP_TABLE_SIZE*2 && |
174 | if (!read(file, tmp, 2)) { | 233 | !(size % (off_t)sizeof (uint16_t))) { |
175 | DEBUGF("Can't read from codepage file: %s.cp\n", | 234 | |
176 | filename[table-1]); | 235 | /* if the buffer is provided, use that but don't alloc */ |
177 | loaded_cp_table = 0; | 236 | int handle = buf ? 0 : core_alloc_ex(filename, size, &ops); |
178 | return 0; | 237 | if (handle > 0) |
238 | buf = core_get_data(handle); | ||
239 | |||
240 | if (buf && read(fd, buf, size) == size) { | ||
241 | close(fd); | ||
242 | cptable_tohw16(buf, size / sizeof (uint16_t)); | ||
243 | return handle; | ||
179 | } | 244 | } |
180 | codepage_table[i++] = (tmp[1] << 8) | tmp[0]; | 245 | |
246 | if (handle > 0) | ||
247 | core_free(handle); | ||
181 | } | 248 | } |
182 | 249 | ||
183 | loaded_cp_table = table; | 250 | close(fd); |
184 | close(file); | 251 | return -1; |
185 | return 1; | ||
186 | } | 252 | } |
187 | 253 | ||
188 | /* Encode a UCS value as UTF-8 and return a pointer after this UTF-8 char. */ | 254 | /* Encode a UCS value as UTF-8 and return a pointer after this UTF-8 char. */ |
@@ -205,47 +271,96 @@ unsigned char* utf8encode(unsigned long ucs, unsigned char *utf8) | |||
205 | unsigned char* iso_decode(const unsigned char *iso, unsigned char *utf8, | 271 | unsigned char* iso_decode(const unsigned char *iso, unsigned char *utf8, |
206 | int cp, int count) | 272 | int cp, int count) |
207 | { | 273 | { |
208 | unsigned short ucs, tmp; | 274 | uint16_t *table = NULL; |
275 | |||
276 | cp_lock_enter(); | ||
277 | |||
278 | if (cp < 0 || cp >= NUM_CODEPAGES) | ||
279 | cp = default_cp; | ||
209 | 280 | ||
210 | if (cp == -1) /* use default codepage */ | 281 | int tid = cp_info[cp].tid; |
211 | cp = default_codepage; | ||
212 | 282 | ||
213 | if (!load_cp_table(cp)) cp = 0; | 283 | while (1) { |
284 | if (tid == default_cp_tid) { | ||
285 | /* use default table */ | ||
286 | if (default_cp_handle > 0) { | ||
287 | table = core_get_data(default_cp_handle); | ||
288 | default_cp_table_ref++; | ||
289 | } | ||
290 | |||
291 | break; | ||
292 | } | ||
293 | |||
294 | bool load = false; | ||
295 | |||
296 | if (tid == loaded_cp_tid) { | ||
297 | /* use loaded table */ | ||
298 | if (!(cp_table_ref & CP_LOADING)) { | ||
299 | if (tid != CP_TID_NONE) { | ||
300 | table = codepage_table; | ||
301 | cp_table_ref++; | ||
302 | } | ||
303 | |||
304 | break; | ||
305 | } | ||
306 | } else if (cp_table_ref == 0) { | ||
307 | load = true; | ||
308 | cp_table_ref |= CP_LOADING; | ||
309 | } | ||
310 | |||
311 | /* alloc and load must be done outside the lock */ | ||
312 | cp_lock_leave(); | ||
313 | |||
314 | if (!load) { | ||
315 | yield(); | ||
316 | } else if (alloc_and_load_cp_table(cp, codepage_table) < 0) { | ||
317 | cp = INIT_CODEPAGE; /* table may be clobbered now */ | ||
318 | tid = cp_info[cp].tid; | ||
319 | } | ||
320 | |||
321 | cp_lock_enter(); | ||
322 | |||
323 | if (load) { | ||
324 | loaded_cp_tid = tid; | ||
325 | cp_table_ref &= ~CP_LOADING; | ||
326 | } | ||
327 | } | ||
328 | |||
329 | cp_lock_leave(); | ||
214 | 330 | ||
215 | while (count--) { | 331 | while (count--) { |
332 | unsigned short ucs, tmp; | ||
333 | |||
216 | if (*iso < 128 || cp == UTF_8) /* Already UTF-8 */ | 334 | if (*iso < 128 || cp == UTF_8) /* Already UTF-8 */ |
217 | *utf8++ = *iso++; | 335 | *utf8++ = *iso++; |
218 | 336 | ||
219 | else { | 337 | else { |
220 | 338 | /* tid tells us which table to use and how */ | |
221 | /* cp tells us which codepage to convert from */ | 339 | switch (tid) { |
222 | switch (cp) { | 340 | case CP_TID_ISO: /* Greek */ |
223 | case ISO_8859_7: /* Greek */ | 341 | /* Hebrew */ |
224 | case WIN_1252: /* Western European */ | 342 | /* Cyrillic */ |
225 | case WIN_1251: /* Cyrillic */ | 343 | /* Thai */ |
226 | case ISO_8859_9: /* Turkish */ | 344 | /* Arabic */ |
227 | case ISO_8859_2: /* Latin Extended */ | 345 | /* Turkish */ |
228 | case WIN_1250: /* Central European */ | 346 | /* Latin Extended */ |
229 | #ifdef HAVE_LCD_BITMAP | 347 | /* Central European */ |
230 | case ISO_8859_8: /* Hebrew */ | 348 | /* Western European */ |
231 | case ISO_8859_11: /* Thai */ | ||
232 | case WIN_1256: /* Arabic */ | ||
233 | #endif | ||
234 | tmp = ((cp-1)*128) + (*iso++ - 128); | 349 | tmp = ((cp-1)*128) + (*iso++ - 128); |
235 | ucs = codepage_table[tmp]; | 350 | ucs = table[tmp]; |
236 | break; | 351 | break; |
237 | 352 | ||
238 | #ifdef HAVE_LCD_BITMAP | 353 | #ifdef HAVE_LCD_BITMAP |
239 | case SJIS: /* Japanese */ | 354 | case CP_TID_932: /* Japanese */ |
240 | if (*iso > 0xA0 && *iso < 0xE0) { | 355 | if (*iso > 0xA0 && *iso < 0xE0) { |
241 | tmp = *iso++ | (0xA100 - 0x8000); | 356 | tmp = *iso++ | (0xA100 - 0x8000); |
242 | ucs = codepage_table[tmp]; | 357 | ucs = table[tmp]; |
243 | break; | 358 | break; |
244 | } | 359 | } |
245 | 360 | ||
246 | case GB_2312: /* Simplified Chinese */ | 361 | case CP_TID_936: /* Simplified Chinese */ |
247 | case KSX_1001: /* Korean */ | 362 | case CP_TID_949: /* Korean */ |
248 | case BIG_5: /* Traditional Chinese */ | 363 | case CP_TID_950: /* Traditional Chinese */ |
249 | if (count < 1 || !iso[1]) { | 364 | if (count < 1 || !iso[1]) { |
250 | ucs = *iso++; | 365 | ucs = *iso++; |
251 | break; | 366 | break; |
@@ -256,7 +371,7 @@ unsigned char* iso_decode(const unsigned char *iso, unsigned char *utf8, | |||
256 | tmp = *iso++ << 8; | 371 | tmp = *iso++ << 8; |
257 | tmp |= *iso++; | 372 | tmp |= *iso++; |
258 | tmp -= 0x8000; | 373 | tmp -= 0x8000; |
259 | ucs = codepage_table[tmp]; | 374 | ucs = table[tmp]; |
260 | count--; | 375 | count--; |
261 | break; | 376 | break; |
262 | #endif /* HAVE_LCD_BITMAP */ | 377 | #endif /* HAVE_LCD_BITMAP */ |
@@ -271,6 +386,17 @@ unsigned char* iso_decode(const unsigned char *iso, unsigned char *utf8, | |||
271 | utf8 = utf8encode(ucs, utf8); | 386 | utf8 = utf8encode(ucs, utf8); |
272 | } | 387 | } |
273 | } | 388 | } |
389 | |||
390 | if (table) { | ||
391 | cp_lock_enter(); | ||
392 | if (table == codepage_table) { | ||
393 | cp_table_ref--; | ||
394 | } else { | ||
395 | default_cp_table_ref--; | ||
396 | } | ||
397 | cp_lock_leave(); | ||
398 | } | ||
399 | |||
274 | return utf8; | 400 | return utf8; |
275 | } | 401 | } |
276 | 402 | ||
@@ -288,7 +414,7 @@ unsigned char* utf16LEdecode(const unsigned char *utf16, unsigned char *utf8, | |||
288 | utf16 += 4; | 414 | utf16 += 4; |
289 | count -= 2; | 415 | count -= 2; |
290 | } else { | 416 | } else { |
291 | ucs = (utf16[0] | (utf16[1] << 8)); | 417 | ucs = getle16(utf16); |
292 | utf16 += 2; | 418 | utf16 += 2; |
293 | count -= 1; | 419 | count -= 1; |
294 | } | 420 | } |
@@ -310,7 +436,7 @@ unsigned char* utf16BEdecode(const unsigned char *utf16, unsigned char *utf8, | |||
310 | utf16 += 4; | 436 | utf16 += 4; |
311 | count -= 2; | 437 | count -= 2; |
312 | } else { | 438 | } else { |
313 | ucs = (utf16[0] << 8) | utf16[1]; | 439 | ucs = getbe16(utf16); |
314 | utf16 += 2; | 440 | utf16 += 2; |
315 | count -= 1; | 441 | count -= 1; |
316 | } | 442 | } |
@@ -400,8 +526,50 @@ const unsigned char* utf8decode(const unsigned char *utf8, unsigned short *ucs) | |||
400 | 526 | ||
401 | void set_codepage(int cp) | 527 | void set_codepage(int cp) |
402 | { | 528 | { |
403 | default_codepage = cp; | 529 | if (cp < 0 || cp >= NUM_CODEPAGES) |
404 | return; | 530 | cp = NUM_CODEPAGES; |
531 | |||
532 | /* load first then swap if load is successful, else just leave it; if | ||
533 | handle is 0 then we just free the current one; this won't happen often | ||
534 | thus we don't worry about reusing it and consequently avoid possible | ||
535 | clobbering of the existing one */ | ||
536 | |||
537 | int handle = -1; | ||
538 | int tid = cp_info[cp].tid; | ||
539 | |||
540 | while (1) { | ||
541 | cp_lock_enter(); | ||
542 | |||
543 | if (default_cp_tid == tid) | ||
544 | break; | ||
545 | |||
546 | if (handle >= 0 && default_cp_table_ref == 0) { | ||
547 | int hold = default_cp_handle; | ||
548 | default_cp_handle = handle; | ||
549 | handle = hold; | ||
550 | default_cp_tid = tid; | ||
551 | break; | ||
552 | } | ||
553 | |||
554 | /* alloc and load must be done outside the lock */ | ||
555 | cp_lock_leave(); | ||
556 | |||
557 | if (handle < 0 && (handle = alloc_and_load_cp_table(cp, NULL)) < 0) | ||
558 | return; /* OOM; change nothing */ | ||
559 | |||
560 | yield(); | ||
561 | } | ||
562 | |||
563 | default_cp = cp; | ||
564 | cp_lock_leave(); | ||
565 | |||
566 | if (handle > 0) | ||
567 | core_free(handle); | ||
568 | } | ||
569 | |||
570 | int get_codepage(void) | ||
571 | { | ||
572 | return default_cp; | ||
405 | } | 573 | } |
406 | 574 | ||
407 | /* seek to a given char in a utf8 string and | 575 | /* seek to a given char in a utf8 string and |
@@ -418,9 +586,16 @@ int utf8seek(const unsigned char* utf8, int offset) | |||
418 | return pos; | 586 | return pos; |
419 | } | 587 | } |
420 | 588 | ||
421 | const char* get_codepage_name(int cp) | 589 | const char * get_codepage_name(int cp) |
422 | { | 590 | { |
423 | if (cp < 0 || cp>= NUM_CODEPAGES) | 591 | if (cp < 0 || cp >= NUM_CODEPAGES) |
424 | return name_codepages[NUM_CODEPAGES]; | 592 | cp = NUM_CODEPAGES; |
425 | return name_codepages[cp]; | 593 | return cp_info[cp].name; |
426 | } | 594 | } |
595 | |||
596 | #if 0 /* not needed just now */ | ||
597 | void unicode_init(void) | ||
598 | { | ||
599 | cp_lock_init(); | ||
600 | } | ||
601 | #endif | ||