summaryrefslogtreecommitdiff
path: root/firmware/common
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/common')
-rw-r--r--firmware/common/dir.c413
-rw-r--r--firmware/common/dir_uncached.c312
-rw-r--r--firmware/common/dircache.c3981
-rw-r--r--firmware/common/disk.c451
-rw-r--r--firmware/common/disk_cache.c343
-rw-r--r--firmware/common/file.c1637
-rw-r--r--firmware/common/file_internal.c776
-rw-r--r--firmware/common/filefuncs.c102
-rw-r--r--firmware/common/fileobj_mgr.c396
-rw-r--r--firmware/common/pathfuncs.c421
-rw-r--r--firmware/common/rbpaths.c432
-rw-r--r--firmware/common/unicode.c451
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 */
33static 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* */
44static 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 */
94static 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
108static 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
136static 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 */
154DIR * 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;
184file_error:
185 file_internal_unlock_WRITER();
186 return dirp;
187}
188
189/* close a directory stream */
190int 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
211file_error:
212 file_internal_unlock_WRITER();
213 return rc;
214}
215
216/* read a directory */
217struct 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
236file_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) */
247int 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
269file_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 */
285void 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 */
304int 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;
333file_error:
334 close_stream_internal(&stream);
335 file_internal_unlock_WRITER();
336 return rc;
337}
338
339/* remove a directory */
340int 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 */
366int 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) */
383bool 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 */
392struct 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
411file_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
37static DIR_UNCACHED opendirs[MAX_OPEN_DIRS];
38
39// release all dir handles on a given volume "by force", to avoid leaks
40int 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
60DIR_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
145int closedir_uncached(DIR_UNCACHED* dir)
146{
147 dir->busy=false;
148 return 0;
149}
150
151struct 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
197int 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
277int 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 102struct sab_component;
58#else 103struct sab
59#define MAX_OPEN_DIRS 8 104{
60#endif 105 struct filestr_base stream; /* scan directory stream */
61static DIR_CACHED opendirs[MAX_OPEN_DIRS]; 106 struct file_base_info info; /* scanned entry info */
62static 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 124struct sab_component
65struct fdbind_queue { 125{
126};
127
128struct 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
138enum
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 */ 146enum
71struct 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 */
167struct 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 */
107static struct dircache_entry *dircache_root;
108/* these point to the start and end of the name buffer (d above) */
109static char *d_names_start, *d_names_end;
110/* put "." and ".." into the d_names buffer to enable easy pointer logic */
111static char *dot, *dotdot;
112#ifdef HAVE_MULTIVOLUME
113static struct dircache_entry *append_position;
114#endif 193#endif
194 dc_serial_t serialnum; /* entry serial number */
195};
115 196
116static DIR_CACHED opendirs[MAX_OPEN_DIRS]; 197/* spare us some tedium */
117static struct dircache_entry *fd_bindings[MAX_OPEN_FILES]; 198#define ENTRYSIZE (sizeof (struct dircache_entry))
118
119static bool dircache_initialized = false;
120static bool dircache_initializing = false;
121static bool thread_enabled = false;
122static unsigned long allocated_size = 0;
123static unsigned long dircache_size = 0;
124static unsigned long entry_count = 0;
125static unsigned long reserve_used = 0;
126static unsigned int cache_build_ticks = 0;
127static unsigned long appflags = 0;
128 199
200/* thread and kernel stuff */
129static struct event_queue dircache_queue SHAREDBSS_ATTR; 201static struct event_queue dircache_queue SHAREDBSS_ATTR;
130static long dircache_stack[(DEFAULT_STACK_SIZE + 0x400)/sizeof(long)]; 202static uintptr_t dircache_stack[DIRCACHE_STACK_SIZE / sizeof (uintptr_t)];
131static const char dircache_thread_name[] = "dircache"; 203static const char dircache_thread_name[] = "dircache";
132 204
133static struct fdbind_queue fdbind_cache[MAX_PENDING_BINDINGS]; 205/* struct that is both used during run time and for persistent storage */
134static int fdbind_idx = 0; 206static 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 */
241struct 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 */
301static int remove_dircache_file(void)
302{
303 return remove(DIRCACHE_FILE);
304}
305
306/**
307 * open or create the snapshot file
308 */
309static int open_dircache_file(int oflag)
310{
311 return open(DIRCACHE_FILE, oflag, 0666);
312}
313#endif /* HAVE_EEPROM_SETTINGS */
137 314
138static 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 */
320static 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
145static bool dont_move = false; 328 */
146static int dircache_handle; 329static int move_callback(int handle, void *current, void *new)
147static 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 */
343static inline void buffer_lock(void)
344{
345 dircache_runinfo.buflocked++;
346}
347
348/**
349 * remove a "don't move" lock count
350 */
351static 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 */
361static 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 */
375static 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; 396static 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
178static 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}; 415static 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 */
187static int open_dircache_file(unsigned flags, int permissions) 450static 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 */
198static int remove_dircache_file(void) 473static 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 */
206static struct dircache_entry* allocate_entry(void) 496static 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 */
505static 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 */
530static 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 */
547static 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 */
558static 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 */
571static 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 */
581static 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 */
592static 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 */
231static struct dircache_entry* dircache_gen_next(struct dircache_entry *ce) 603static 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 */
617static 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 */
247static struct dircache_entry* dircache_gen_down(struct dircache_entry *ce) 640static 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 */
664static 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 */
712static 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 */
720static 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 */
728static 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 */
747static 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 */
263static bool check_event_queue(void) 760static 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 */
843static 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 */
865static 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 */
902static 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 */
911static 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 */
935static 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 */
977static 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 */
1015static 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 */
1038static 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 */
1056static 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 */
1076static 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 */
1098static 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 */
1142static 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 */
1153static 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 */
1176static 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
285static struct 1200 */
1201static 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();
294static 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); 1222static 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 */
1373static 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 */
1405static 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 */
1433int 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
1517read_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/**
382static struct fat_dir sab_fat_dir; 1523 * rewind the directory scan cursor
1524 */
1525void dircache_rewinddir_dirent(struct dirscan_info *scanp)
1526{
1527 uncached_rewinddir_dirent(scanp);
1528 dircache_dcfile_init(&scanp->dcscan);
1529}
383 1530
384static 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 */
1539int 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 */
1620void 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
414static char sab_path[MAX_PATH]; 1635static char sab_path[MAX_PATH];
415 1636
416static int sab_process_dir(struct dircache_entry *ce) 1637static 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
496static int dircache_scan_and_build(IF_MV(int volume,) struct dircache_entry *ce) 1706static 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/** 1713int 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 */
532static 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)
586struct 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 */
599int dircache_load(void) 1740static 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 */
1777static 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 */ 1808static 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 */
704int dircache_save(void) 1861static 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 */
763static int dircache_do_rebuild(void) 1945static 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
831static 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 */
841static void dircache_thread(void) 1987static 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
877static 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 */
890int dircache_build(int last_size) 2036static 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;
987fail:
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 */
996void 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 */
1024bool dircache_is_enabled(void) 2061void 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 */
1032bool dircache_is_initializing(void) 2069void 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 */
1040void dircache_set_appflag(long mask) 2082void 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() */
2097static 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() */
2124static 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 */
1048bool dircache_get_appflag(long mask) 2151static 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 */
1056int dircache_get_entry_count(void) 2167static 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 */
1064int dircache_get_cache_size(void) 2181void 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 */
1073int dircache_get_reserve_used(void) 2198int 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 */
1081int dircache_get_build_ticks(void) 2209int 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 */
1090void dircache_suspend(void) 2221void 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 */
1126bool dircache_resume(void) 2231void 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 */
2245void 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 */
1139void dircache_disable(void) 2267void 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 */
2278void 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 */
1148void* dircache_steal_buffer(size_t *size) 2288void 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 */
1167static int dircache_get_entry_id_ex(const char *filename, bool go_down) 2346void 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
1176int 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 */
1184long _dircache_get_entry_startcluster(int id) 2362void 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 */
1192struct dirinfo* _dircache_get_entry_dirinfo(int id) 2422void 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 * 2456static 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 */
1205static 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 */
1226size_t dircache_copy_path(int index, char *buf, size_t size) 2503static 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/**
1239static 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 */
2538ssize_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
1251static 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, '/'); 2568static struct dircache_entry *
1260 if (new == NULL) 2569get_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() */
2599static 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 */
2643int 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
1320void dircache_bind(int fd, const char *path) 2723/** Debug screen/info stuff **/
2724
2725/**
2726 * return cache state parameters
2727 */
2728void 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
1351void 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 */
2797void 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}
1366void 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
1392void 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);
1402void 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 */
1423void 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
1444void 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
1502void dircache_add_file(const char *path, long startcluster) 2886/** Misc. stuff **/
2887
2888/**
2889 * set the dircache file to initial values
2890 */
2891void 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
1517static 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 */
2911struct 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 */
2922static 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
1522DIR_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 */
2937int 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
1568struct 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; 3058error:
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); 3065error_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
1612int closedir_cached(DIR_CACHED* dir) 3076/**
3077 * function to save the internal cache stucture to disk for fast loading
3078 * on boot
3079 */
3080int 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
1624int 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;
3157error:
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
1632int 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 */
3173void 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
49static const unsigned char fat_partition_types[] = { 66static 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
59static struct partinfo part[NUM_DRIVES*4]; /* space for 4 partitions on 2 drives */ 77/* space for 4 partitions on 2 drives */
60static int vol_drive[NUM_VOLUMES]; /* mounted to which drive (-1 if none) */ 78static struct partinfo part[NUM_DRIVES*4];
61static struct mutex disk_mutex; 79/* mounted to which drive (-1 if none) */
80static int vol_drive[NUM_VOLUMES];
81
82static 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
64static int disk_sector_multiplier[NUM_DRIVES] = {[0 ... NUM_DRIVES-1] = 1}; 94static int disk_sector_multiplier[NUM_DRIVES] =
95 { [0 ... NUM_DRIVES-1] = 1 };
65 96
66int disk_get_sector_multiplier(IF_MD_NONVOID(int drive)) 97int 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
76struct partinfo* disk_init(IF_MD_NONVOID(int drive)) 109bool 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
121struct partinfo* disk_partinfo(int partition) 131 disk_writer_lock();
122{
123 return &part[partition];
124}
125 132
126void 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
131int 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
162static int get_free_volume(void) 164bool 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
174int disk_mount(int drive) 175int 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
253int 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
252int disk_unmount(int drive) 278int 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
277int disk_unmount_all(void) 304int 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
324bool 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
344void 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
359unsigned 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
370void 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)
385enum 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
396static 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
297bool volume_removable(int volume) 428bool 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
304bool volume_present(int volume) 433bool 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
440int volume_drive(int volume)
441{
442 return volume_properties(volume, VP_DRIVE);
443}
444#endif /* HAVE_MULTIDRIVE */
445
446#ifdef HAVE_DIRCACHE
447bool 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
60enum 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
67struct 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
77BITARRAY_TYPE_DECLARE(cache_map_entry_t, cache_map, DC_NUM_ENTRIES)
78
79static 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
85static struct lldc_head cache_lru; /* LRU cache list (head = LRU item) */
86static struct disk_cache_entry cache_entry[DC_NUM_ENTRIES];
87static cache_map_entry_t cache_map_entry[NUM_VOLUMES][DC_MAP_NUM_ENTRIES];
88static cache_map_entry_t cache_vol_map[NUM_VOLUMES] IBSS_ATTR;
89static uint8_t cache_buffer[DC_NUM_ENTRIES][DC_CACHE_BUFSIZE] CACHEALIGN_ATTR;
90struct 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 */
109static 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 */
118static 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 */
127static 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 */
144static 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 */
158static 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 */
164static 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 */
175void * 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
217finish_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 */
229void 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 */
243void 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 */
256void 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 */
275void 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 */
284void * 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 */
316void 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 */
337void 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 */
39static 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. 47static 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
41struct 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
54static 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 */
80static 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
56static int flush_cache(int fd); 92 DEBUGF("Too many files open\n");
93 return -1;
94}
57 95
58int file_creat(const char *pathname) 96/* return the file size in sectors */
97static 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
63static int open_internal(const char* pathname, int flags, bool use_cache) 108/* flush a dirty cache buffer */
109static 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;
135file_error:
136 DEBUGF("Failed flushing cache: %d\n", rc);
137 return rc;
138}
139
140static 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; 147static 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, 186file_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; 192void 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 */
211static 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
233int file_open(const char* pathname, int flags) 266file_error:
234{ 267 return rc;
235 /* By default, use the dircache if available. */
236 return open_internal(pathname, flags, true);
237} 268}
238 269
239int close(int fd) 270/* flush back all outstanding writes to the file */
271static 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; 308file_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 */
325static 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; 339file_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
268int fsync(int fd) 346/* actually do the open gruntwork */
347static 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
314int 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) 432file_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
343int rename(const char* path, const char* newpath) 439/* allocate a file descriptor, if needed, assemble stream flags and open
440 a new stream */
441static 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); 493file_error:
433 if (rc<0) { 494 return rc;
434 errno = EIO; 495}
435 return rc * 10 - 9;
436 }
437 496
438 return 0; 497static 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
441int ftruncate(int fd, off_t size) 506/* fill a cache buffer with a new sector */
507static 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;
546file_error:
547 DEBUGF("Failed caching sector: %d\n", rc);
548 return rc;
468} 549}
469 550
470static int flush_cache(int fd) 551/* read or write to part or all of the cache buffer */
552static 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 */
573static 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
497static int readwrite(int fd, void* buf, long count, bool write) 594/* read from or write to the file; back end to read() and write() */
595static 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; 752file_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
702ssize_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 */
784int 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
711ssize_t read(int fd, void* buf, size_t count) 789
790/** POSIX **/
791
792/* open a file */
793int 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 */
800int 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
717off_t lseek(int fd, off_t offset, int whence) 806/* close a file descriptor */
807int 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: 827file_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 */
833int 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) || 859file_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) { 865int 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; 885file_error:
886 RELEASE_FILESTR(WRITER, file);
887 return rc;
798} 888}
799 889
800off_t filesize(int fd) 890/* move the read/write file offset */
891off_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
903file_error:
904 RELEASE_FILESTR(READER, file);
905 return rc;
906}
907
908/* read from a file */
909ssize_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
928file_error:
929 RELEASE_FILESTR(READER, file);
930 return rc;
931}
932
933/* write on a file */
934ssize_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
953file_error:
954 RELEASE_FILESTR(READER, file);
955 return rc;
814} 956}
815 957
958/* remove a file */
959int 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 */
818int release_files(int volume) 970int 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
1092file_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) */
1117off_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;
1130file_error:
1131 RELEASE_FILESTR(READER, file);
1132 return rc;
1133}
1134
1135/* test if two file descriptors refer to the same file */
1136int 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 */
1153int 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
1201file_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 */
1213bool 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 */
41struct fat_direntry dir_fatent;
42#else
43struct fat_direntry dir_fatent IBSS_ATTR;
44#endif
45
46struct mrsw_lock file_internal_mrsw SHAREDBSS_ATTR;
47
48
49/** File stream sector caching **/
50
51/* initialize a new cache structure */
52void file_cache_init(struct filestr_cache *cachep)
53{
54 cachep->buffer = NULL;
55 cachep->sector = INVALID_SECNUM;
56 cachep->flags = 0;
57}
58
59/* discard and mark the cache buffer as unused */
60void file_cache_reset(struct filestr_cache *cachep)
61{
62 cachep->sector = INVALID_SECNUM;
63 cachep->flags = 0;
64}
65
66/* allocate resources attached to the cache */
67void file_cache_alloc(struct filestr_cache *cachep)
68{
69 /* if this fails, it is a bug; check for leaks and that the cache has
70 enough buffers for the worst case */
71 if (!cachep->buffer && !(cachep->buffer = dc_get_buffer()))
72 panicf("file_cache_alloc - OOM");
73}
74
75/* free resources attached to the cache */
76void file_cache_free(struct filestr_cache *cachep)
77{
78 if (cachep && cachep->buffer)
79 {
80 dc_release_buffer(cachep->buffer);
81 cachep->buffer = NULL;
82 }
83
84 file_cache_reset(cachep);
85}
86
87
88/** Stream base APIs **/
89
90static inline void filestr_clear(struct filestr_base *stream)
91{
92 stream->flags = 0;
93 stream->bindp = NULL;
94#if 0
95 stream->mtx = NULL;
96#endif
97}
98
99/* actually late-allocate the assigned cache */
100void filestr_alloc_cache(struct filestr_base *stream)
101{
102 file_cache_alloc(stream->cachep);
103}
104
105/* free the stream's cache buffer if it's its own */
106void filestr_free_cache(struct filestr_base *stream)
107{
108 if (stream->cachep == &stream->cache)
109 file_cache_free(stream->cachep);
110}
111
112/* assign a cache to the stream */
113void filestr_assign_cache(struct filestr_base *stream,
114 struct filestr_cache *cachep)
115{
116 if (cachep)
117 {
118 filestr_free_cache(stream);
119 stream->cachep = cachep;
120 }
121 else /* assign own cache */
122 {
123 file_cache_reset(&stream->cache);
124 stream->cachep = &stream->cache;
125 }
126}
127
128/* duplicate a cache into a stream's local cache */
129void filestr_copy_cache(struct filestr_base *stream,
130 struct filestr_cache *cachep)
131{
132 stream->cachep = &stream->cache;
133 stream->cache.sector = cachep->sector;
134 stream->cache.flags = cachep->flags;
135
136 if (cachep->buffer)
137 {
138 file_cache_alloc(&stream->cache);
139 memcpy(stream->cache.buffer, cachep->buffer, DC_CACHE_BUFSIZE);
140 }
141 else
142 {
143 file_cache_free(&stream->cache);
144 }
145}
146
147/* discard cache contents and invalidate it */
148void filestr_discard_cache(struct filestr_base *stream)
149{
150 file_cache_reset(stream->cachep);
151}
152
153/* Initialize the base descriptor */
154void filestr_base_init(struct filestr_base *stream)
155{
156 filestr_clear(stream);
157 file_cache_init(&stream->cache);
158 stream->cachep = &stream->cache;
159}
160
161/* free base descriptor resources */
162void filestr_base_destroy(struct filestr_base *stream)
163{
164 filestr_clear(stream);
165 filestr_free_cache(stream);
166}
167
168
169/** Internal directory service functions **/
170
171/* read the next directory entry and return its FS info */
172int uncached_readdir_internal(struct filestr_base *stream,
173 struct file_base_info *infop,
174 struct fat_direntry *fatent)
175{
176 return fat_readdir(&stream->fatstr, &infop->fatfile.e,
177 filestr_get_cache(stream), fatent);
178}
179
180/* rewind the FS directory to the beginning */
181void uncached_rewinddir_internal(struct file_base_info *infop)
182{
183 fat_rewinddir(&infop->fatfile.e);
184}
185
186/* check if the directory is empty (ie. only "." and/or ".." entries
187 exist at most) */
188int test_dir_empty_internal(struct filestr_base *stream)
189{
190 int rc;
191
192 struct file_base_info info;
193 fat_rewind(&stream->fatstr);
194 rewinddir_internal(&info);
195
196 while ((rc = readdir_internal(stream, &info, &dir_fatent)) > 0)
197 {
198 /* no OEM decoding is recessary for this simple check */
199 if (!is_dotdir_name(dir_fatent.name))
200 {
201 DEBUGF("Directory not empty\n");
202 FILE_ERROR_RETURN(ENOTEMPTY, -1);
203 }
204 }
205
206 if (rc < 0)
207 {
208 DEBUGF("I/O error checking directory: %d\n", rc);
209 FILE_ERROR_RETURN(EIO, rc * 10 - 2);
210 }
211
212 return 0;
213}
214
215/* iso decode the name to UTF-8 */
216void iso_decode_d_name(char *d_name)
217{
218 if (is_dotdir_name(d_name))
219 return;
220
221 char shortname[13];
222 size_t len = strlcpy(shortname, d_name, sizeof (shortname));
223 /* This MUST be the default codepage thus not something that could be
224 loaded on call */
225 iso_decode(shortname, d_name, -1, len + 1);
226}
227
228#ifdef HAVE_DIRCACHE
229/* nullify all the fields of the struct dirent */
230void empty_dirent(struct dirent *entry)
231{
232 entry->d_name[0] = '\0';
233 entry->info.attr = 0;
234 entry->info.size = 0;
235 entry->info.wrtdate = 0;
236 entry->info.wrttime = 0;
237}
238
239/* fill the native dirinfo from the static dir_fatent */
240void fill_dirinfo_native(struct dirinfo_native *dinp)
241{
242 struct fat_direntry *fatent = get_dir_fatent();
243 dinp->attr = fatent->attr;
244 dinp->size = fatent->filesize;
245 dinp->wrtdate = fatent->wrtdate;
246 dinp->wrttime = fatent->wrttime;
247}
248#endif /* HAVE_DIRCACHE */
249
250int uncached_readdir_dirent(struct filestr_base *stream,
251 struct dirscan_info *scanp,
252 struct dirent *entry)
253{
254 struct fat_direntry fatent;
255 int rc = fat_readdir(&stream->fatstr, &scanp->fatscan,
256 filestr_get_cache(stream), &fatent);
257
258 /* FAT driver clears the struct fat_dirent if nothing is returned */
259 strcpy(entry->d_name, fatent.name);
260 entry->info.attr = fatent.attr;
261 entry->info.size = fatent.filesize;
262 entry->info.wrtdate = fatent.wrtdate;
263 entry->info.wrttime = fatent.wrttime;
264
265 return rc;
266}
267
268/* rewind the FS directory pointer */
269void uncached_rewinddir_dirent(struct dirscan_info *scanp)
270{
271 fat_rewinddir(&scanp->fatscan);
272}
273
274
275/** open_stream_internal() helpers and types **/
276
277struct pathwalk
278{
279 const char *path; /* current location in input path */
280 unsigned int callflags; /* callflags parameter */
281 struct path_component_info *compinfo; /* compinfo parameter */
282 file_size_t filesize; /* size of the file */
283};
284
285struct pathwalk_component
286{
287 struct file_base_info info; /* basic file information */
288 const char *name; /* component name location in path */
289 uint16_t length; /* length of name of component */
290 uint16_t attr; /* attributes of this component */
291 struct pathwalk_component *nextp; /* parent if in use else next free */
292};
293
294#define WALK_RC_NOT_FOUND 0 /* successfully not found */
295#define WALK_RC_FOUND 1 /* found and opened */
296#define WALK_RC_FOUND_ROOT 2 /* found and opened sys/volume root */
297#define WALK_RC_CONT_AT_ROOT 3 /* continue at root level */
298
299/* return another struct pathwalk_component from the pool, or NULL if the
300 pool is completely used */
301static void * pathwalk_comp_alloc_(struct pathwalk_component *parentp)
302{
303 /* static pool that goes to a depth of STATIC_COMP_NUM before allocating
304 elements from the stack */
305 static struct pathwalk_component aux_pathwalk[STATIC_PATHCOMP_NUM];
306 struct pathwalk_component *compp = NULL;
307
308 if (!parentp)
309 compp = &aux_pathwalk[0]; /* root */
310 else if (PTR_IN_ARRAY(aux_pathwalk, parentp, STATIC_PATHCOMP_NUM-1))
311 compp = parentp + 1;
312
313 return compp;
314}
315
316/* allocates components from the pool or stack depending upon the depth */
317#define pathwalk_comp_alloc(parentp) \
318 ({ \
319 void *__c = pathwalk_comp_alloc_(parentp); \
320 if (!__builtin_constant_p(parentp) && !__c) \
321 __c = alloca(sizeof (struct pathwalk_component)); \
322 (struct pathwalk_component *)__c; \
323 })
324
325/* fill in the details of the struct path_component_info for caller */
326static int fill_path_compinfo(struct pathwalk *walkp,
327 struct pathwalk_component *compp,
328 int rc)
329{
330 if (rc == -ENOENT)
331 {
332 /* this component wasn't found; see if more of them exist or path
333 has trailing separators; if it does, this component should be
334 interpreted as a directory even if it doesn't exist and it's the
335 final one; also, this has to be the last part or it's an error*/
336 const char *p = GOBBLE_PATH_SEPCH(walkp->path);
337 if (!*p)
338 {
339 if (p > walkp->path)
340 compp->attr |= ATTR_DIRECTORY;
341
342 rc = WALK_RC_NOT_FOUND; /* successfully not found */
343 }
344 }
345
346 if (rc >= 0)
347 {
348 struct path_component_info *compinfo = walkp->compinfo;
349 compinfo->name = compp->name;
350 compinfo->length = compp->length;
351 compinfo->attr = compp->attr;
352 compinfo->filesize = walkp->filesize;
353 compinfo->parentinfo = (compp->nextp ?: compp)->info;
354 }
355
356 return rc;
357}
358
359/* open the final stream itself, if found */
360static int walk_open_info(struct pathwalk *walkp,
361 struct pathwalk_component *compp,
362 int rc,
363 struct filestr_base *stream)
364{
365 /* this may make adjustments to things; do it first */
366 if (walkp->compinfo)
367 rc = fill_path_compinfo(walkp, compp, rc);
368
369 if (rc < 0 || rc == WALK_RC_NOT_FOUND)
370 return rc;
371
372 unsigned int callflags = walkp->callflags;
373 bool isdir = compp->attr & ATTR_DIRECTORY;
374
375 /* type must match what is called for */
376 switch (callflags & FF_TYPEMASK)
377 {
378 case FF_FILE:
379 if (!isdir) break;
380 DEBUGF("File is a directory\n");
381 return -EISDIR;
382 case FF_DIR:
383 if (isdir) break;
384 DEBUGF("File is not a directory\n");
385 return -ENOTDIR;
386 /* FF_ANYTYPE: basically, ignore FF_FILE/FF_DIR */
387 }
388
389 /* FO_DIRECTORY must match type */
390 if (isdir)
391 callflags |= FO_DIRECTORY;
392 else
393 callflags &= ~FO_DIRECTORY;
394
395 fileop_onopen_internal(stream, &compp->info, callflags);
396 return compp->nextp ? WALK_RC_FOUND : WALK_RC_FOUND_ROOT;
397}
398
399/* check the component against the prefix test info */
400static void walk_check_prefix(struct pathwalk *walkp,
401 struct pathwalk_component *compp)
402{
403 if (compp->attr & ATTR_PREFIX)
404 return;
405
406 if (!fat_file_is_same(&compp->info.fatfile,
407 &walkp->compinfo->prefixp->fatfile))
408 return;
409
410 compp->attr |= ATTR_PREFIX;
411}
412
413/* opens the component named by 'comp' in the directory 'parent' */
414static NO_INLINE int open_path_component(struct pathwalk *walkp,
415 struct pathwalk_component *compp,
416 struct filestr_base *stream)
417{
418 int rc;
419
420 /* create a null-terminated copy of the component name */
421 char *compname = strmemdupa(compp->name, compp->length);
422
423 unsigned int callflags = walkp->callflags;
424 struct pathwalk_component *parentp = compp->nextp;
425
426 /* children inherit the prefix coloring from the parent */
427 compp->attr = parentp->attr & ATTR_PREFIX;
428
429 /* most of the next would be abstracted elsewhere if doing other
430 filesystems */
431
432 /* scan parent for name; stream is converted to this parent */
433 file_cache_reset(stream->cachep);
434 stream->infop = &parentp->info;
435 fat_filestr_init(&stream->fatstr, &parentp->info.fatfile);
436 rewinddir_internal(&compp->info);
437
438 while ((rc = readdir_internal(stream, &compp->info, &dir_fatent)) > 0)
439 {
440 if (rc > 1 && !(callflags & FF_NOISO))
441 iso_decode_d_name(dir_fatent.name);
442
443 if (!strcasecmp(compname, dir_fatent.name))
444 break;
445 }
446
447 if (rc == 0)
448 {
449 DEBUGF("File/directory not found\n");
450 return -ENOENT;
451 }
452 else if (rc < 0)
453 {
454 DEBUGF("I/O error reading directory %d\n", rc);
455 return -EIO;
456 }
457
458 rc = fat_open(stream->fatstr.fatfilep, dir_fatent.firstcluster,
459 &compp->info.fatfile);
460 if (rc < 0)
461 {
462 DEBUGF("I/O error opening file/directory %s (%d)\n",
463 compname, rc);
464 return -EIO;
465 }
466
467 walkp->filesize = dir_fatent.filesize;
468 compp->attr |= dir_fatent.attr;
469
470 if (callflags & FF_CHECKPREFIX)
471 walk_check_prefix(walkp, compp);
472
473 return WALK_RC_FOUND;
474}
475
476/* parse a path component, open it and process the next */
477static NO_INLINE int
478walk_path(struct pathwalk *walkp, struct pathwalk_component *compp,
479 struct filestr_base *stream)
480{
481 int rc = WALK_RC_FOUND;
482
483 if (walkp->callflags & FF_CHECKPREFIX)
484 walk_check_prefix(walkp, compp);
485
486 /* alloca is used in a loop, but we reuse any blocks previously allocated
487 if we went up then back down; if the path takes us back to the root, then
488 everything is cleaned automatically */
489 struct pathwalk_component *freep = NULL;
490
491 const char *name;
492 ssize_t len;
493
494 while ((len = parse_path_component(&walkp->path, &name)))
495 {
496 /* whatever is to be a parent must be a directory */
497 if (!(compp->attr & ATTR_DIRECTORY))
498 return -ENOTDIR;
499
500 switch (len)
501 {
502 case 1:
503 case 2:
504 /* check for "." and ".." */
505 if (name[0] == '.')
506 {
507 if (len == 2 && name[1] == '.')
508 {
509 struct pathwalk_component *parentp = compp->nextp;
510 if (!parentp)
511 return WALK_RC_CONT_AT_ROOT;
512
513 compp->nextp = freep;
514 freep = compp;
515 compp = parentp;
516 }
517
518 break;
519 }
520
521 /* fallthrough */
522 default:
523 if (len >= MAX_NAME)
524 return -ENAMETOOLONG;
525
526 struct pathwalk_component *newp = freep;
527 if (!newp)
528 newp = pathwalk_comp_alloc(compp);
529 else
530 freep = freep->nextp;
531
532 newp->nextp = compp;
533 compp = newp;
534
535 compp->name = name;
536 compp->length = len;
537
538 rc = open_path_component(walkp, compp, stream);
539 if (rc < 0)
540 break;
541 }
542 }
543
544 return walk_open_info(walkp, compp, rc, stream);
545}
546
547/* open a stream given a path to the resource */
548int open_stream_internal(const char *path, unsigned int callflags,
549 struct filestr_base *stream,
550 struct path_component_info *compinfo)
551{
552 DEBUGF("%s(path=\"%s\",flg=%X,str=%p,compinfo=%p)\n", path, callflags,
553 stream, compinfo);
554 int rc;
555
556 filestr_base_init(stream);
557
558 if (!path_is_absolute(path))
559 {
560 /* while this supports relative components, there is currently no
561 current working directory concept at this level by which to
562 fully qualify the path (though that would not be excessively
563 difficult to add) */
564 DEBUGF("\"%s\" is not an absolute path\n"
565 "Only absolute paths currently supported.\n", path);
566 FILE_ERROR(path ? ENOENT : EFAULT, -1);
567 }
568
569 /* if !compinfo, then the result of this check is not visible anyway */
570 if (!compinfo)
571 callflags &= ~FF_CHECKPREFIX;
572
573 struct pathwalk walk;
574 walk.path = path;
575 walk.callflags = callflags;
576 walk.compinfo = compinfo;
577 walk.filesize = 0;
578
579 struct pathwalk_component *rootp = pathwalk_comp_alloc(NULL);
580 rootp->nextp = NULL;
581 rootp->attr = ATTR_DIRECTORY;
582
583#ifdef HAVE_MULTIVOLUME
584 int volume = 0, rootrc = WALK_RC_FOUND;
585#endif /* HAVE_MULTIVOLUME */
586
587 while (1)
588 {
589 const char *pathptr = walk.path;
590
591 #ifdef HAVE_MULTIVOLUME
592 /* this seamlessly integrates secondary filesystems into the
593 root namespace (e.g. "/<0>/../../<1>/../foo/." :<=> "/foo") */
594 const char *p;
595 volume = path_strip_volume(pathptr, &p, false);
596 if (!CHECK_VOL(volume))
597 {
598 DEBUGF("No such device or address: %d\n", volume);
599 FILE_ERROR(ENXIO, -2);
600 }
601
602 /* the root of this subpath is the system root? */
603 rootrc = p == pathptr ? WALK_RC_FOUND_ROOT : WALK_RC_FOUND;
604 walk.path = p;
605 #endif /* HAVE_MULTIVOLUME */
606
607 /* set name to start at last leading separator; names of volume
608 specifiers will be returned as "/<fooN>" */
609 rootp->name = GOBBLE_PATH_SEPCH(pathptr) - 1;
610 rootp->length =
611 IF_MV( rootrc == WALK_RC_FOUND ? p - rootp->name : ) 1;
612
613 rc = fat_open_rootdir(IF_MV(volume,) &rootp->info.fatfile);
614 if (rc < 0)
615 {
616 /* not mounted */
617 DEBUGF("No such device or address: %d\n", IF_MV_VOL(volume));
618 rc = -ENXIO;
619 break;
620 }
621
622 get_rootinfo_internal(&rootp->info);
623 rc = walk_path(&walk, rootp, stream);
624 if (rc != WALK_RC_CONT_AT_ROOT)
625 break;
626 }
627
628 switch (rc)
629 {
630 case WALK_RC_FOUND_ROOT:
631 IF_MV( rc = rootrc; )
632 case WALK_RC_NOT_FOUND:
633 case WALK_RC_FOUND:
634 break;
635
636 default: /* utter, abject failure :`( */
637 DEBUGF("Open failed: rc=%d, errno=%d\n", rc, errno);
638 filestr_base_destroy(stream);
639 FILE_ERROR(-rc, -2);
640 }
641
642 file_cache_reset(stream->cachep);
643
644file_error:
645 return rc;
646}
647
648/* close the stream referenced by 'stream' */
649int close_stream_internal(struct filestr_base *stream)
650{
651 int rc;
652 unsigned int foflags = fileobj_get_flags(stream);
653
654 if ((foflags & (FO_SINGLE|FO_REMOVED)) == (FO_SINGLE|FO_REMOVED))
655 {
656 /* nothing is referencing it so now remove the file's data */
657 rc = fat_remove(&stream->infop->fatfile, FAT_RM_DATA);
658 if (rc < 0)
659 {
660 DEBUGF("I/O error removing file data: %d\n", rc);
661 FILE_ERROR(EIO, rc * 10 - 1);
662 }
663 }
664
665 rc = 0;
666file_error:
667 /* close no matter what */
668 fileop_onclose_internal(stream);
669 return rc;
670}
671
672/* create a new stream in the parent directory */
673int create_stream_internal(struct file_base_info *parentinfop,
674 const char *basename, size_t length,
675 unsigned int attr, unsigned int callflags,
676 struct filestr_base *stream)
677{
678 /* assumes an attempt was made beforehand to open *stream with
679 open_stream_internal() which returned zero (successfully not found),
680 so does not initialize it here */
681 const char * const name = strmemdupa(basename, length);
682 DEBUGF("Creating \"%s\"\n", name);
683
684 struct file_base_info info;
685 int rc = fat_create_file(&parentinfop->fatfile, name, attr,
686 &info.fatfile, get_dir_fatent_dircache());
687 if (rc < 0)
688 {
689 DEBUGF("Create failed: %d\n", rc);
690 FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 1);
691 }
692
693 /* dir_fatent is implicit arg */
694 fileop_oncreate_internal(stream, &info, callflags, parentinfop, name);
695 rc = 0;
696file_error:
697 return rc;
698}
699
700/* removes files and directories - back-end to remove() and rmdir() */
701int remove_stream_internal(const char *path, struct filestr_base *stream,
702 unsigned int callflags)
703{
704 /* Only FF_* flags should be in callflags */
705 int rc;
706
707 struct filestr_base opened_stream;
708 if (!stream)
709 stream = &opened_stream;
710
711 if (stream == &opened_stream)
712 {
713 /* no stream provided so open local one */
714 rc = open_stream_internal(path, callflags, stream, NULL);
715 if (rc < 0)
716 {
717 DEBUGF("Failed opening path: %d\n", rc);
718 FILE_ERROR(ERRNO, rc * 10 - 1);
719 }
720 }
721 /* else ignore the 'path' argument */
722
723 if (callflags & FF_DIR)
724 {
725 /* directory to be removed must be empty */
726 rc = test_dir_empty_internal(stream);
727 if (rc < 0)
728 FILE_ERROR(ERRNO, rc * 10 - 2);
729 }
730
731 /* save old info since fat_remove() will destroy the dir info */
732 struct file_base_info oldinfo = *stream->infop;
733 rc = fat_remove(&stream->infop->fatfile, FAT_RM_DIRENTRIES);
734 if (rc < 0)
735 {
736 DEBUGF("I/O error removing dir entries: %d\n", rc);
737 FILE_ERROR(EIO, rc * 10 - 3);
738 }
739
740 fileop_onremove_internal(stream, &oldinfo);
741
742 rc = 0;
743file_error:
744 if (stream == &opened_stream)
745 {
746 /* will do removal of data below if this is the only reference */
747 int rc2 = close_stream_internal(stream);
748 if (rc2 < 0 && rc >= 0)
749 {
750 rc = rc2 * 10 - 4;
751 DEBUGF("Success but failed closing stream: %d\n", rc);
752 }
753 }
754
755 return rc;
756}
757
758/* test file/directory existence with constraints */
759int test_stream_exists_internal(const char *path, unsigned int callflags)
760{
761 /* only FF_* flags should be in callflags */
762 struct filestr_base stream;
763 int rc = open_stream_internal(path, callflags, &stream, NULL);
764 if (rc > 0)
765 close_stream_internal(&stream);
766
767 return rc;
768}
769
770/* one-time init at startup */
771void filesystem_init(void)
772{
773 mrsw_init(&file_internal_mrsw);
774 dc_init();
775 fileobj_mgr_init();
776}
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) */
35int 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 */
62bool 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
86bool 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)
97struct 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 */
42static 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];
51static struct mutex stream_mutexes[MAX_FILEOBJS] SHAREDBSS_ATTR;
52static struct ll_head free_bindings;
53static 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 */
90static 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 */
116static 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 */
140static 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
148void file_binding_insert_last(struct file_base_binding *bindp)
149{
150 ll_insert_last(BASEBINDING_LIST(bindp), &bindp->node);
151}
152
153void file_binding_remove(struct file_base_binding *bindp)
154{
155 ll_remove(BASEBINDING_LIST(bindp), &bindp->node);
156}
157
158#ifdef HAVE_DIRCACHE
159void file_binding_insert_first(struct file_base_binding *bindp)
160{
161 ll_insert_first(BASEBINDING_LIST(bindp), &bindp->node);
162}
163
164void 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 */
179void 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 */
248void 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 */
292void 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 */
301void 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 */
309void 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 */
321void 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 */
327void 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 */
335file_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 */
344unsigned 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 */
353void 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" */
362void 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 */
385void 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
31enum 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
54static 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
77static 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 */
106int 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;
149volume0:
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 */
158int 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 */
182int 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 */
203size_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 */
235size_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 */
265size_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 */
296size_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 */
320void 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 */
353size_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 */
407ssize_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)
55static const char rbhome[] = "/sdcard";
56#elif (CONFIG_PLATFORM & (PLATFORM_SDL|PLATFORM_MAEMO|PLATFORM_PANDORA)) && !defined(__PCTOOL__)
57const char *rbhome;
58#else
59/* YPR0, YPR1 */
60static 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
78static 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() */
84static 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
107void 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
145static 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
160static 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
200static 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
221int 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
239int app_creat(const char* name, mode_t mode)
240{
241 return app_open(name, O_CREAT|O_WRONLY|O_TRUNC, mode);
242}
243
244int 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
252int 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() */
266struct __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
279struct 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
333DIR* 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
366int 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
375struct 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
396int 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
404int 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(). */
414ssize_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
40static int default_codepage = 0; 47#define getle16(p) (p[0] | (p[1] >> 8))
41static 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"
63static 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
48static const char * const filename[NUM_TABLES] = 73enum 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
57static const char cp_2_table[NUM_CODEPAGES] = 83struct 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
62static 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
83static 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
103const 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
100static 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
116static const char * const filename[NUM_TABLES] = { 124#define CPF_ISOMINI "isomini.cp"
117 CODEPAGE_DIR"/isomini.cp"
118};
119 125
120static const char cp_2_table[NUM_CODEPAGES] = 126static 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
125static const char * const name_codepages[NUM_CODEPAGES+1] = 139#endif /* HAVE_LCD_BITMAP */
140
141static int default_cp = INIT_CODEPAGE;
142static int default_cp_tid = CP_TID_NONE;
143static int default_cp_handle = 0;
144static int volatile default_cp_table_ref = 0;
145
146static int loaded_cp_tid = CP_TID_NONE;
147static 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) */
152static unsigned short codepage_table[MAX_CP_TABLE_SIZE+1];
153
154#if defined(APPLICATION) && defined(__linux__)
155static 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 176const char *get_current_codepage_name_linux(void)
139 177{
140static 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
142static const unsigned char utf8comp[6] = 185static 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 */ 190static inline void cptable_tohw16(uint16_t *buf, unsigned int count)
148static 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) 199static 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); 209static 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)
205unsigned char* iso_decode(const unsigned char *iso, unsigned char *utf8, 271unsigned 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
401void set_codepage(int cp) 527void 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
570int 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
421const char* get_codepage_name(int cp) 589const 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 */
597void unicode_init(void)
598{
599 cp_lock_init();
600}
601#endif