summaryrefslogtreecommitdiff
path: root/firmware/target/hosted/filesystem-app.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/target/hosted/filesystem-app.c')
-rw-r--r--firmware/target/hosted/filesystem-app.c562
1 files changed, 562 insertions, 0 deletions
diff --git a/firmware/target/hosted/filesystem-app.c b/firmware/target/hosted/filesystem-app.c
new file mode 100644
index 0000000000..7ef8d3109b
--- /dev/null
+++ b/firmware/target/hosted/filesystem-app.c
@@ -0,0 +1,562 @@
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#define RB_FILESYSTEM_OS
22#include <stdio.h> /* snprintf */
23#include <stdlib.h>
24#include <stdarg.h>
25#include <time.h>
26#include <errno.h>
27#include <string.h>
28#include <limits.h>
29#include "config.h"
30#include "system.h"
31#include "file.h"
32#include "dir.h"
33#include "file_internal.h"
34#include "pathfuncs.h"
35#include "string-extra.h"
36#include "rbpaths.h"
37#include "logf.h"
38
39
40#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
41static const char rbhome[] = "/sdcard";
42#elif (CONFIG_PLATFORM & (PLATFORM_SDL|PLATFORM_MAEMO|PLATFORM_PANDORA)) \
43 && !defined(__PCTOOL__)
44const char *rbhome;
45#else
46/* YPR0, YPR1 */
47static const char rbhome[] = HOME_DIR;
48#endif
49
50#if !(defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1)) && !defined(__PCTOOL__)
51/* Special dirs are user-accessible (and user-writable) dirs which take priority
52 * over the ones where Rockbox is installed to. Classic example would be
53 * $HOME/.config/rockbox.org vs /usr/share/rockbox */
54#define HAVE_SPECIAL_DIRS
55#endif
56
57#ifdef HAVE_MULTIDRIVE
58/* This is to compare any opened directories with the home directory so that
59 the special drive links may be returned for it only */
60static int rbhome_fildes = -1;
61
62/* A special link is created under e.g. HOME_DIR/<microSD1>, e.g. to access
63 * external storage in a convenient location, much similar to the mount
64 * point on our native targets. Here they are treated as symlink (one which
65 * doesn't actually exist in the filesystem and therefore we have to override
66 * readlink() */
67static const char *handle_special_links(const char* link, unsigned flags,
68 char *buf, const size_t bufsize)
69{
70 (void) flags;
71 char vol_string[VOL_MAX_LEN + 1];
72 get_volume_name(-1, vol_string);
73
74 /* link might be passed with or without HOME_DIR expanded. To handle
75 * both perform substring matching (VOL_NAMES is unique enough) */
76 const char *begin = strstr(link, vol_string);
77 if (begin)
78 {
79 /* begin now points to the start of vol_string within link,
80 * we want to copy the remainder of the paths, prefixed by
81 * the actual mount point (the remainder might be "") */
82 snprintf(buf, bufsize, MULTIDRIVE_DIR"%s", begin + len);
83 return buf;
84 }
85
86 return link;
87}
88#endif
89
90#ifdef HAVE_MULTIDRIVE
91/* we keep an open descriptor of the home directory to detect when it has been
92 opened by opendir() so that its "symlinks" may be enumerated */
93static void cleanup_rbhome(void)
94{
95 os_close(rbhome_fildes);
96 rbhome_fildes = -1;
97}
98#endif /* HAVE_MULTIDRIVE */
99
100void paths_init(void)
101{
102#ifdef HAVE_SPECIAL_DIRS
103 /* make sure $HOME/.config/rockbox.org exists, it's needed for config.cfg */
104#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
105 os_mkdir("/sdcard/rockbox" __MKDIR_MODE_ARG);
106 os_mkdir("/sdcard/rockbox/rocks.data" __MKDIR_MODE_ARG);
107#else
108 char config_dir[MAX_PATH];
109
110 const char *home = getenv("RBROOT");
111 if (!home)
112 {
113 home = getenv("HOME");
114 }
115
116 if (!home)
117 {
118 logf("HOME environment var not set. Can't write config");
119 return;
120 }
121
122 rbhome = home;
123 snprintf(config_dir, sizeof(config_dir), "%s/.config", home);
124 os_mkdir(config_dir __MKDIR_MODE_ARG);
125 snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org", home);
126 os_mkdir(config_dir __MKDIR_MODE_ARG);
127 /* Plugin data directory */
128 snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org/rocks.data", home);
129 os_mkdir(config_dir __MKDIR_MODE_ARG);
130#endif
131#endif /* HAVE_SPECIAL_DIRS */
132
133#ifdef HAVE_MULTIDRIVE
134 /* if this fails then alternate volumes will not work, but this function
135 cannot return that fact */
136 rbhome_fildes = os_opendirfd(rbhome);
137 if (rbhome_fildes >= 0)
138 atexit(cleanup_rbhome);
139#endif /* HAVE_MULTIDRIVE */
140}
141
142#ifdef HAVE_SPECIAL_DIRS
143static const char* _get_user_file_path(const char *path,
144 unsigned flags,
145 char* buf,
146 const size_t bufsize)
147{
148 const char *ret = path;
149 const char *pos = path;
150 /* replace ROCKBOX_DIR in path with $HOME/.config/rockbox.org */
151 pos += ROCKBOX_DIR_LEN;
152 if (*pos == '/') pos += 1;
153
154#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
155 if (path_append(buf, "/sdcard/rockbox", pos, bufsize) >= bufsize)
156 return NULL;
157#else
158 if (path_append(buf, rbhome, ".config/rockbox.org", bufsize) >= bufsize ||
159 path_append(buf, PA_SEP_SOFT, pos, bufsize) >= bufsize)
160 return NULL;
161#endif
162
163 /* always return the replacement buffer (pointing to $HOME) if
164 * write access is needed */
165 if (flags & NEED_WRITE)
166 ret = buf;
167 else if (os_file_exists(buf))
168 ret = buf;
169
170 if (ret != buf) /* not found in $HOME, try ROCKBOX_BASE_DIR, !NEED_WRITE only */
171 {
172 if (path_append(buf, ROCKBOX_SHARE_PATH, pos, bufsize) >= bufsize)
173 return NULL;
174
175 if (os_file_exists(buf))
176 ret = buf;
177 }
178
179 return ret;
180}
181
182#endif
183
184const char * handle_special_dirs(const char *dir, unsigned flags,
185 char *buf, const size_t bufsize)
186{
187 (void) flags; (void) buf; (void) bufsize;
188#ifdef HAVE_SPECIAL_DIRS
189 if (!strncmp(HOME_DIR, dir, HOME_DIR_LEN))
190 {
191 const char *p = dir + HOME_DIR_LEN;
192 while (*p == '/') p++;
193 snprintf(buf, bufsize, "%s/%s", rbhome, p);
194 dir = buf;
195 }
196 else if (!strncmp(ROCKBOX_DIR, dir, ROCKBOX_DIR_LEN))
197 dir = _get_user_file_path(dir, flags, buf, bufsize);
198#endif
199#ifdef HAVE_MULTIDRIVE
200 dir = handle_special_links(dir, flags, buf, bufsize);
201#endif
202 return dir;
203}
204
205int app_open(const char *path, int oflag, ...)
206{
207 int flags = IS_FILE;
208 if (oflag & O_ACCMODE)
209 flags |= NEED_WRITE;
210
211 char realpath[MAX_PATH];
212 const char *fpath = handle_special_dirs(path, flags, realpath,
213 sizeof (realpath));
214 if (!fpath)
215 FILE_ERROR_RETURN(ENAMETOOLONG, -1);
216
217 return os_open(fpath, oflag __OPEN_MODE_ARG);
218}
219
220int app_creat(const char *path, mode_t mode)
221{
222 return app_open(path, O_CREAT|O_WRONLY|O_TRUNC, mode);
223}
224
225int app_remove(const char *path)
226{
227 char realpath[MAX_PATH];
228 const char *fpath = handle_special_dirs(path, NEED_WRITE, realpath,
229 sizeof (realpath));
230 if (!fpath)
231 FILE_ERROR_RETURN(ENAMETOOLONG, -1);
232
233 return os_remove(fpath);
234}
235
236int app_rename(const char *old, const char *new)
237{
238 char realpath_old[MAX_PATH], realpath_new[MAX_PATH];
239 const char *fold = handle_special_dirs(old, NEED_WRITE, realpath_old,
240 sizeof (realpath_old));
241 const char *fnew = handle_special_dirs(new, NEED_WRITE, realpath_new,
242 sizeof (realpath_new));
243 if (!fold || !fnew)
244 FILE_ERROR_RETURN(ENAMETOOLONG, -1);
245
246 return os_rename(fold, fnew);
247}
248
249#ifdef HAVE_SDL_THREADS
250ssize_t app_read(int fd, void *buf, size_t nbyte)
251{
252 return os_read(fd, buf, nbyte);
253}
254
255ssize_t app_write(int fd, const void *buf, size_t nbyte)
256{
257 return os_write(fd, buf, nbyte);
258}
259#endif /* HAVE_SDL_THREADS */
260
261int app_relate(const char *path1, const char *path2)
262{
263 char realpath_1[MAX_PATH], realpath_2[MAX_PATH];
264 const char *fpath1 = handle_special_dirs(path1, 0, realpath_1,
265 sizeof (realpath_1));
266 const char *fpath2 = handle_special_dirs(path2, 0, realpath_2,
267 sizeof (realpath_2));
268
269 if (!fpath1 || !fpath2)
270 FILE_ERROR_RETURN(ENAMETOOLONG, -1);
271
272 return os_relate(fpath1, fpath2);
273}
274
275bool app_file_exists(const char *path)
276{
277 char realpath[MAX_PATH];
278 const char *fpath = handle_special_dirs(path, NEED_WRITE, realpath,
279 sizeof (realpath));
280 if (!fpath)
281 FILE_ERROR_RETURN(ENAMETOOLONG, false);
282
283 return os_file_exists(fpath);
284}
285
286/* need to wrap around DIR* because we need to save the parent's directory
287 * path in order to determine dirinfo for volumes or convert the path to UTF-8;
288 * also is required to implement get_dir_info() */
289struct __dir
290{
291 OS_DIR_T *osdirp;
292#ifdef HAVE_MULTIDRIVE
293 int volumes_returned;
294#endif
295 int osfd;
296 bool osfd_is_opened;
297#if defined(OS_DIRENT_CONVERT) || defined (HAVE_MULTIDRIVE)
298 #define USE_DIRENTP
299 struct dirent *direntp;
300 size_t d_name_size;
301#endif
302 char path[];
303};
304
305static void __dir_free(struct __dir *this)
306{
307 if (!this)
308 return;
309
310#ifdef USE_DIRENTP
311 free(this->direntp);
312#endif
313
314 if (this->osfd_is_opened)
315 os_close(this->osfd);
316
317 free(this);
318}
319
320DIR * app_opendir(const char *dirname)
321{
322 int rc;
323 char realpath[MAX_PATH];
324 const char *fname = handle_special_dirs(dirname, 0, realpath,
325 sizeof (realpath));
326 if (!fname)
327 FILE_ERROR_RETURN(ENAMETOOLONG, NULL);
328
329 size_t name_len = path_strip_trailing_separators(fname, &fname);
330 struct __dir *this = calloc(1, sizeof (*this) + name_len + 1);
331 if (!this)
332 FILE_ERROR(ENOMEM, RC);
333
334#ifdef USE_DIRENTP
335 /* allocate what we're really going to return to callers, making certain
336 it has at least the d_name size we want */
337 this->d_name_size = MAX(MAX_PATH, sizeof (this->direntp->d_name));
338 this->direntp = calloc(1, offsetof(typeof (*this->direntp), d_name) +
339 this->d_name_size);
340 if (!this->direntp)
341 FILE_ERROR(ENOMEM, RC);
342
343 /* only the d_name field will be valid but that is all that anyone may
344 truely count on portably existing */
345#endif /* USE_DIRENTP */
346
347 strmemcpy(this->path, fname, name_len);
348
349 rc = os_opendir_and_fd(this->path, &this->osdirp, &this->osfd);
350 if (rc < 0)
351 FILE_ERROR(ERRNO, RC);
352
353 this->osfd_is_opened = rc > 0;
354
355#ifdef HAVE_MULTIDRIVE
356 this->volumes_returned = INT_MAX; /* assume NOT $HOME */
357 if (rbhome_fildes >= 0 && os_samefile(rbhome_fildes, fd) > 0)
358 this->volumes_returned = 0; /* there's no place like $HOME */
359#endif /* HAVE_MULTIDRIVE */
360
361 return (DIR *)this;
362file_error:
363 __dir_free(this);
364 return NULL;
365}
366
367int app_closedir(DIR *dirp)
368{
369 struct __dir *this = (struct __dir *)dirp;
370 if (!this)
371 FILE_ERROR_RETURN(EBADF, -1);
372
373 OS_DIR_T *osdirp = this->osdirp;
374 __dir_free(this);
375
376 return os_closedir(osdirp);
377}
378
379struct dirent * app_readdir(DIR *dirp)
380{
381 struct __dir *this = (struct __dir *)dirp;
382 if (!this)
383 FILE_ERROR_RETURN(EBADF, NULL);
384
385#ifdef HAVE_MULTIDRIVE
386 if (this->volumes_returned < NUM_VOLUMES)
387 {
388 while (++this->volumes_returned < NUM_VOLUMES)
389 {
390 if (!volume_present(this->volumes_returned))
391 continue;
392
393 get_volume_name(this->volumes_returned, this->direntp->d_name);
394 return this->direntp;
395 }
396 }
397 /* do normal directory reads */
398#endif /* HAVE_MULTIDRIVE */
399
400 OS_DIRENT_T *osdirent = os_readdir(this->osdirp);
401
402#ifdef OS_DIRENT_CONVERT
403 if (strlcpy_from_os(this->direntp->d_name, osdirent->d_name,
404 this->d_name_size) >= this->d_name_size)
405 {
406 this->direntp->d_name[0] = '\0';
407 errno = EOVERFLOW;
408 return NULL;
409 }
410
411 osdirent = (OS_DIRENT_T *)this->direntp;
412#endif /* OS_DIRENT_CONVERT */
413
414 return (struct dirent *)osdirent;
415}
416
417int app_mkdir(const char *path)
418{
419 char realpath[MAX_PATH];
420 const char *fname = handle_special_dirs(path, NEED_WRITE, realpath,
421 sizeof (realpath));
422 if (!fname)
423 FILE_ERROR_RETURN(ENAMETOOLONG, -1);
424
425 return os_mkdir(fname __MKDIR_MODE_ARG);
426}
427
428int app_rmdir(const char *path)
429{
430 char realpath[MAX_PATH];
431 const char *fname = handle_special_dirs(path, NEED_WRITE, realpath,
432 sizeof (realpath));
433 if (!fname)
434 FILE_ERROR_RETURN(ENAMETOOLONG, -1);
435
436 return os_rmdir(fname);
437}
438
439int app_samedir(DIR *dirp1, DIR *dirp2)
440{
441 struct __dir *this1 = (struct __dir *)dirp1;
442 struct __dir *this2 = (struct __dir *)dirp2;
443
444 if (!this1 || !this2)
445 {
446 errno = EBADF;
447 return -1;
448 }
449
450 return os_fsamefile(this1->osfd, this2->osfd);
451}
452
453bool app_dir_exists(const char *dirname)
454{
455 char realpath[MAX_PATH];
456 const char *fname = handle_special_dirs(dirname, 0, realpath,
457 sizeof (realpath));
458 if (!fname)
459 FILE_ERROR_RETURN(ENAMETOOLONG, false);
460
461 OS_DIR_T *osdirp = os_opendir(fname);
462 if (!osdirp)
463 return false;
464
465 os_closedir(osdirp);
466 return true;
467}
468
469struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry)
470{
471 struct __dir *this = (struct __dir *)dirp;
472 struct dirinfo ret = { .mtime = 0 };
473
474 if (!this)
475 FILE_ERROR_RETURN(EBADF, ret);
476
477 if (!entry || entry->d_name[0] == '\0')
478 FILE_ERROR_RETURN(ENOENT, ret);
479
480 char path[MAX_PATH];
481
482#ifdef HAVE_MULTIDRIVE
483 if (this->volumes_returned < NUM_VOLUMES)
484 {
485 /* last thing read was a "symlink" */
486 ret.attribute = ATTR_LINK;
487 strcpy(path, MULTIDRIVE_DIR);
488 }
489 else
490#endif
491 if (path_append(path, this->path, entry->d_name, sizeof (path))
492 >= sizeof (path))
493 {
494 FILE_ERROR_RETURN(ENAMETOOLONG, ret);
495 }
496
497 struct stat s;
498 if (os_lstat(path, &s) < 0)
499 FILE_ERROR_RETURN(ERRNO, ret);
500
501 int err = 0;
502 if (S_ISLNK(s.st_mode))
503 {
504 ret.attribute |= ATTR_LINK;
505 err = os_stat(path, &s);
506 }
507
508 if (err < 0)
509 FILE_ERROR_RETURN(ERRNO, ret);
510
511 if (S_ISDIR(s.st_mode))
512 ret.attribute |= ATTR_DIRECTORY;
513
514 ret.size = s.st_size;
515
516 struct tm tm;
517 if (!localtime_r(&s.st_mtime, &tm))
518 FILE_ERROR_RETURN(ERRNO, ret);
519
520 ret.mtime = mktime(&tm);
521 return ret;
522}
523
524/* On MD we create a virtual symlink for the external drive,
525 * for this we need to override readlink(). */
526ssize_t app_readlink(const char *path, char *buf, size_t bufsiz)
527{
528 char _buf[MAX_PATH];
529 path = handle_special_dirs(path, 0, _buf, sizeof(_buf));
530#ifdef HAVE_MULTIDRIVE
531 /* if path == _buf then we can be sure handle_special_dir() did something
532 * and path is not an ordinary directory */
533 if (path == _buf && !strncmp(path, MULTIDRIVE_DIR, sizeof(MULTIDRIVE_DIR)-1))
534 {
535 /* copying NUL is not required as per readlink specification */
536 ssize_t len = strlen(path);
537 memcpy(buf, path, len);
538 return len;
539 }
540#endif
541 /* does not append NUL !! */
542 return os_readlink(path, buf, bufsiz);
543 (void) path; (void) buf; (void) bufsiz;
544}
545
546int os_volume_path(IF_MV(int volume, ) char *buffer, size_t bufsize)
547{
548#ifdef HAVE_MULTIVOLUME
549 char volname[VOL_MAX_LEN + 1];
550 get_volume_name(volume, volname);
551#else
552 const char *volname = "/";
553#endif
554
555 if (!handle_special_dirs(volname, NEED_WRITE, buffer, bufsize))
556 {
557 errno = ENAMETOOLONG;
558 return -1;
559 }
560
561 return 0;
562}