| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * $Id$ |
| * |
| * Copyright (C) 2002 Daniel Stenberg |
| * Copyright (C) 2014 Michael Sevakis |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| ****************************************************************************/ |
| #define RB_FILESYSTEM_OS |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <time.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include "config.h" |
| #include "system.h" |
| #include "file.h" |
| #include "dir.h" |
| #include "file_internal.h" |
| #include "pathfuncs.h" |
| #include "string-extra.h" |
| #include "debug.h" |
| |
| #ifndef os_fstatat |
| #define USE_OSDIRNAME /* we need to remember the open directory path */ |
| #endif |
| |
| extern const char *sim_root_dir; |
| |
| /* Windows (and potentially other OSes) distinguish binary and text files. |
| * Define a dummy for the others. */ |
| #ifndef O_BINARY |
| #define O_BINARY 0 |
| #endif |
| |
| struct filestr_desc |
| { |
| int osfd; /* The host OS file descriptor */ |
| bool mounted; /* Is host volume still mounted? */ |
| #ifdef HAVE_MULTIVOLUME |
| int volume; /* The virtual volume number */ |
| #endif |
| } openfiles[MAX_OPEN_FILES] = |
| { |
| [0 ... MAX_OPEN_FILES-1] = { .osfd = -1 } |
| }; |
| |
| static struct filestr_desc * alloc_filestr(int *fildesp) |
| { |
| for (unsigned int i = 0; i < MAX_OPEN_FILES; i++) |
| { |
| struct filestr_desc *filestr = &openfiles[i]; |
| if (filestr->osfd < 0) |
| { |
| *fildesp = i; |
| return filestr; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static struct filestr_desc * get_filestr(int fildes) |
| { |
| struct filestr_desc *filestr = &openfiles[fildes]; |
| |
| if ((unsigned int)fildes >= MAX_OPEN_FILES || filestr->osfd < 0) |
| filestr = NULL; |
| else if (filestr->mounted) |
| return filestr; |
| |
| errno = filestr ? ENXIO : EBADF; |
| DEBUGF("fildes %d: %s\n", fildes, strerror(errno)); |
| return NULL; |
| } |
| |
| struct dirstr_desc |
| { |
| int osfd; /* Host OS directory file descriptor */ |
| bool osfd_opened; /* Host fd is another open file */ |
| OS_DIR_T *osdirp; /* Host OS directory stream */ |
| #ifdef USE_OSDIRNAME |
| char *osdirname; /* Host OS directory path */ |
| #endif |
| struct sim_dirent entry; /* Rockbox directory entry */ |
| #ifdef HAVE_MULTIVOLUME |
| int volume; /* Virtual volume number */ |
| int volumecounter; /* Counter for root volume entries */ |
| #endif |
| bool mounted; /* Is the virtual volume still mounted? */ |
| } opendirs[MAX_OPEN_DIRS]; |
| |
| static struct dirstr_desc * alloc_dirstr(void) |
| { |
| for (unsigned int i = 0; i < MAX_OPEN_DIRS; i++) |
| { |
| struct dirstr_desc *dirstr = &opendirs[i]; |
| if (dirstr->osdirp == NULL) |
| return dirstr; |
| } |
| |
| return NULL; |
| } |
| |
| static struct dirstr_desc * get_dirstr(DIR *dirp) |
| { |
| struct dirstr_desc *dirstr = (struct dirstr_desc *)dirp; |
| |
| if (!PTR_IN_ARRAY(opendirs, dirstr, MAX_OPEN_DIRS) || !dirstr->osdirp) |
| dirstr = NULL; |
| else if (dirstr->mounted) |
| return dirstr; |
| |
| errno = dirstr ? ENXIO : EBADF; |
| DEBUGF("dir #%d: %s\n", (int)(dirstr - opendirs), strerror(errno)); |
| return NULL; |
| } |
| |
| static int close_dirstr(struct dirstr_desc *dirstr) |
| { |
| OS_DIR_T *osdirp = dirstr->osdirp; |
| |
| dirstr->mounted = false; |
| |
| #ifdef USE_OSDIRNAME |
| free(dirstr->osdirname); |
| #endif |
| if (dirstr->osfd_opened) |
| { |
| os_close(dirstr->osfd); |
| dirstr->osfd_opened = false; |
| } |
| |
| int rc = os_closedir(osdirp); |
| dirstr->osdirp = NULL; |
| |
| return rc; |
| } |
| |
| #ifdef HAVE_MULTIVOLUME |
| static int readdir_volume_inner(struct dirstr_desc *dirstr, |
| struct sim_dirent *entry) |
| { |
| /* Volumes (secondary file systems) get inserted into the system root |
| * directory. If the path specified volume 0, enumeration will not |
| * include other volumes, but just its own files and directories. |
| * |
| * Fake special directories, which don't really exist, that will get |
| * redirected upon opendir() |
| */ |
| while (++dirstr->volumecounter < NUM_VOLUMES) |
| { |
| /* on the system root */ |
| if (!volume_present(dirstr->volumecounter)) |
| continue; |
| |
| get_volume_name(dirstr->volumecounter, entry->d_name); |
| return 1; |
| } |
| |
| /* do normal directory entry fetching */ |
| return 0; |
| } |
| #endif /* HAVE_MULTIVOLUME */ |
| |
| static inline int readdir_volume(struct dirstr_desc *dirstr, |
| struct sim_dirent *entry) |
| { |
| #ifdef HAVE_MULTIVOLUME |
| if (dirstr->volumecounter < NUM_VOLUMES) |
| return readdir_volume_inner(dirstr, entry); |
| #endif /* HAVE_MULTIVOLUME */ |
| |
| /* do normal directory entry fetching */ |
| return 0; |
| (void)dirstr; (void)entry; |
| } |
| |
| |
| /** Internal functions **/ |
| |
| #ifdef HAVE_MULTIDRIVE |
| /** |
| * Handle drive extraction by pretending the files' volumes no longer exist |
| * and invalidating their I/O for the remainder of their lifetimes as would |
| * happen on a native target |
| */ |
| void sim_ext_extracted(int drive) |
| { |
| for (unsigned int i = 0; i < MAX_OPEN_FILES; i++) |
| { |
| struct filestr_desc *filestr = &openfiles[i]; |
| if (filestr->osfd >= 0 && volume_drive(filestr->volume) == drive) |
| filestr->mounted = false; |
| } |
| |
| for (unsigned int i = 0; i < MAX_OPEN_DIRS; i++) |
| { |
| struct dirstr_desc *dirstr = &opendirs[i]; |
| if (dirstr->osdirp && volume_drive(dirstr->volume) == drive) |
| dirstr->mounted = false; |
| } |
| |
| (void)drive; |
| } |
| #endif /* HAVE_MULTIDRIVE */ |
| |
| /** |
| * Provides target-like path parsing behavior with single and multiple volumes |
| * while performing minimal transforming of the input. |
| * |
| * Paths are sandboxed to the simulated namespace: |
| * e.g. "{simdir}/foo/../../../../bar" becomes "{simdir}/foo/../bar" |
| */ |
| int sim_get_os_path(char *buffer, const char *path, size_t bufsize) |
| { |
| #define ADVBUF(amt) \ |
| ({ buffer += (amt); bufsize -= (amt); }) |
| |
| #define PPP_SHOWPATHS 0 |
| |
| if (!path_is_absolute(path)) |
| { |
| DEBUGF("ERROR: path is not absolute: \"%s\"\n", path); |
| errno = ENOENT; |
| return -1; |
| } |
| |
| #if PPP_SHOWPATHS |
| const char *const buffer0 = buffer; |
| DEBUGF("PPP (pre): \"%s\"\n", path); |
| #endif |
| |
| /* Prepend sim root */ |
| size_t size = strlcpy(buffer, sim_root_dir, bufsize); |
| if (size >= bufsize) |
| { |
| errno = ENAMETOOLONG; |
| return -1; |
| } |
| ADVBUF(size); |
| |
| #ifdef HAVE_MULTIVOLUME |
| /* Track the last valid volume spec encountered */ |
| int volume = -1; |
| bool sysroot = true; |
| |
| /* Basename of sim dir to switch back to simdisk from ext */ |
| #define DIRBASE_FMT ".." PATH_SEPSTR "%s" |
| ssize_t dirbase_len = 0; |
| char dirbase[size + 3 + 1]; |
| |
| /* Basename of ext directory to switch to alternate volume */ |
| #define SIMEXT_FMT ".." PATH_SEPSTR "simext%d" |
| char extbuf[sizeof (SIMEXT_FMT) + 20 + 1]; |
| #endif /* HAVE_MULTIVOLUME */ |
| |
| int level = 0; |
| bool done = false; |
| while (!done) |
| { |
| const char *p; |
| ssize_t len = parse_path_component(&path, &p); |
| |
| |
| switch (len) |
| { |
| case 0: |
| done = true; |
| if (path[-1] != PATH_SEPCH) |
| continue; |
| |
| /* Path ends with a separator; preserve that */ |
| p = &path[-1]; |
| len = 1; |
| break; |
| |
| case 1: |
| case 2: |
| if (p[0] == '.') |
| { |
| if (len == 1) |
| break; |
| |
| if (p[1] == '.') |
| goto is_dot_dot; |
| } |
| |
| default: |
| level++; |
| |
| #ifdef HAVE_MULTIVOLUME |
| if (level != 1) |
| break; /* Volume spec only valid @ root level */ |
| |
| const char *next; |
| volume = path_strip_volume(p, &next, true); |
| |
| if (next > p) |
| { |
| #ifdef HAVE_MULTIDRIVE |
| /* Feign failure if the volume isn't "mounted" */ |
| if (!volume_present(volume)) |
| { |
| errno = ENXIO; |
| return -1; |
| } |
| #endif /* HAVE_MULTIDRIVE */ |
| |
| sysroot = false; |
| |
| if (volume == 0) |
| continue; |
| |
| p = extbuf; |
| len = snprintf(extbuf, sizeof (extbuf), SIMEXT_FMT, volume); |
| } |
| #endif /* HAVE_MULTIVOLUME */ |
| break; |
| |
| is_dot_dot: |
| if (level <= 0) |
| continue; /* Can't go above root; erase */ |
| |
| level--; |
| |
| #ifdef HAVE_MULTIVOLUME |
| if (level == 0) |
| { |
| int v = volume; |
| bool sr = sysroot; |
| volume = -1; |
| sysroot = true; |
| |
| if (v <= 0) |
| { |
| if (sr) |
| break; |
| |
| continue; |
| } |
| |
| /* Going up from a volume root and back down to the sys root */ |
| if (dirbase_len == 0) |
| { |
| /* Get the main simdisk directory so it can be reentered */ |
| char tmpbuf[sizeof (dirbase)]; |
| #ifdef WIN32 |
| path_correct_separators(tmpbuf, sim_root_dir); |
| path_strip_drive(tmpbuf, &p, false); |
| #else |
| p = tmpbuf; |
| strcpy(tmpbuf, sim_root_dir); |
| #endif |
| size = path_basename(p, &p); |
| ((char *)p)[size] = '\0'; |
| |
| if (size == 0 || is_dotdir_name(p)) |
| { |
| /* This is nonsense and won't work */ |
| DEBUGF("ERROR: sim root dir basname is dotdir or" |
| " empty: \"%s\"\n", sim_root_dir); |
| errno = ENOENT; |
| return -1; |
| } |
| |
| dirbase_len = snprintf(dirbase, sizeof (dirbase), |
| DIRBASE_FMT, p); |
| } |
| |
| p = dirbase; |
| len = dirbase_len; |
| break; |
| } |
| #endif /* HAVE_MULTIVOLUME */ |
| break; |
| } /* end switch */ |
| |
| char compname[len + 1]; |
| strmemcpy(compname, p, len); |
| |
| size = path_append(buffer, PA_SEP_HARD, compname, bufsize); |
| if (size >= bufsize) |
| { |
| errno = ENAMETOOLONG; |
| return -1; |
| } |
| ADVBUF(size); |
| } |
| |
| #if PPP_SHOWPATHS |
| DEBUGF("PPP (post): \"%s\"" IF_MV(" vol:%d") "\n", |
| buffer0 IF_MV(, volume)); |
| #endif |
| |
| return IF_MV(volume) +1; |
| } |
| |
| |
| /** File functions **/ |
| |
| int sim_open(const char *path, int oflag, ...) |
| { |
| int fildes; |
| struct filestr_desc *filestr = alloc_filestr(&fildes); |
| if (!filestr) |
| { |
| errno = EMFILE; |
| return -1; |
| } |
| |
| char ospath[SIM_TMPBUF_MAX_PATH]; |
| int pprc = sim_get_os_path(ospath, path, sizeof (ospath)); |
| if (pprc < 0) |
| return -2; |
| |
| filestr->osfd = os_open(ospath, oflag | O_BINARY __OPEN_MODE_ARG); |
| if (filestr->osfd < 0) |
| return -3; |
| |
| #ifdef HAVE_MULTIVOLUME |
| filestr->volume = MAX(pprc - 1, 0); |
| #endif |
| filestr->mounted = true; |
| return fildes; |
| } |
| |
| int sim_creat(const char *path, mode_t mode) |
| { |
| return sim_open(path, O_WRONLY|O_CREAT|O_TRUNC, mode); |
| } |
| |
| int sim_close(int fildes) |
| { |
| struct filestr_desc *filestr = &openfiles[fildes]; |
| if ((unsigned int)fildes >= MAX_OPEN_FILES || filestr->osfd < 0) |
| { |
| errno = EBADF; |
| return -1; |
| } |
| |
| int osfd = filestr->osfd; |
| filestr->osfd = -1; |
| return os_close(osfd); |
| } |
| |
| int sim_ftruncate(int fildes, off_t length) |
| { |
| struct filestr_desc *filestr = get_filestr(fildes); |
| if (!filestr) |
| return -1; |
| |
| off_t size = os_filesize(filestr->osfd); |
| if (size < 0) |
| return -1; |
| |
| if (length >= size) |
| return 0; |
| |
| int rc = os_ftruncate(filestr->osfd, length); |
| |
| #ifdef HAVE_DIRCACHE |
| if (rc >= 0) |
| dircache_ftruncate(xxxxxx); |
| #endif |
| |
| return rc; |
| } |
| |
| int sim_fsync(int fildes) |
| { |
| struct filestr_desc *filestr = get_filestr(fildes); |
| if (!filestr) |
| return -1; |
| |
| int rc = os_fsync(filestr->osfd); |
| |
| #ifdef HAVE_DIRCACHE |
| if (rc >= 0) |
| dircache_fsync(xxxxxx); |
| #endif |
| |
| return rc; |
| } |
| |
| off_t sim_lseek(int fildes, off_t offset, int whence) |
| { |
| struct filestr_desc *filestr = get_filestr(fildes); |
| if (!filestr) |
| return -1; |
| |
| return os_lseek(filestr->osfd, offset, whence); |
| } |
| |
| ssize_t sim_read(int fildes, void *buf, size_t nbyte) |
| { |
| struct filestr_desc *filestr = get_filestr(fildes); |
| if (!filestr) |
| return -1; |
| |
| return os_read(filestr->osfd, buf, nbyte); |
| } |
| |
| ssize_t sim_write(int fildes, const void *buf, size_t nbyte) |
| { |
| struct filestr_desc *filestr = get_filestr(fildes); |
| if (!filestr) |
| return -1; |
| |
| return os_write(filestr->osfd, buf, nbyte); |
| } |
| |
| int sim_remove(const char *path) |
| { |
| char ospath[SIM_TMPBUF_MAX_PATH]; |
| if (sim_get_os_path(ospath, path, sizeof (ospath)) < 0) |
| return -1; |
| |
| int rc = os_remove(ospath); |
| |
| #ifdef HAVE_DIRCACHE |
| if (rc >= 0) |
| dircache_remove(xxxxxx); |
| #endif |
| |
| return rc; |
| } |
| |
| int sim_rename(const char *old, const char *new) |
| { |
| char osold[SIM_TMPBUF_MAX_PATH]; |
| int pprc1 = sim_get_os_path(osold, old, sizeof (osold)); |
| if (pprc1 < 0) |
| return -1; |
| |
| char osnew[SIM_TMPBUF_MAX_PATH]; |
| int pprc2 = sim_get_os_path(osnew, new, sizeof (osnew)); |
| if (pprc2 < 0) |
| return -1; |
| |
| if (MAX(pprc1 - 1, 0) != MAX(pprc2 - 1, 0)) |
| { |
| /* Pretend they're different devices */ |
| errno = EXDEV; |
| return -1; |
| } |
| |
| int rc = os_rename(osold, osnew); |
| |
| #ifdef HAVE_DIRCACHE |
| if (rc >= 0) |
| dircache_rename(xxxxxx); |
| #endif |
| |
| return rc; |
| } |
| |
| off_t sim_filesize(int fildes) |
| { |
| struct filestr_desc *filestr = get_filestr(fildes); |
| if (!filestr) |
| return -1; |
| |
| return os_filesize(filestr->osfd); |
| } |
| |
| int sim_fsamefile(int fildes1, int fildes2) |
| { |
| struct filestr_desc *filestr1 = get_filestr(fildes1); |
| if (!filestr1) |
| return -1; |
| |
| struct filestr_desc *filestr2 = get_filestr(fildes2); |
| if (!filestr2) |
| return -1; |
| |
| if (filestr1 == filestr2) |
| return 1; |
| |
| return os_fsamefile(filestr1->osfd, filestr2->osfd); |
| } |
| |
| int sim_relate(const char *path1, const char *path2) |
| { |
| char ospath1[SIM_TMPBUF_MAX_PATH]; |
| if (sim_get_os_path(ospath1, path1, sizeof (ospath1)) < 0) |
| return -1; |
| |
| char ospath2[SIM_TMPBUF_MAX_PATH]; |
| if (sim_get_os_path(ospath2, path2, sizeof (ospath2)) < 0) |
| return -1; |
| |
| return os_relate(ospath1, ospath2); |
| } |
| |
| bool sim_file_exists(const char *path) |
| { |
| char ospath[SIM_TMPBUF_MAX_PATH]; |
| if (sim_get_os_path(ospath, path, sizeof (ospath)) < 0) |
| return false; |
| |
| return os_file_exists(ospath); |
| } |
| |
| |
| /** Directory functions **/ |
| DIR * sim_opendir(const char *dirname) |
| { |
| struct dirstr_desc *dirstr = alloc_dirstr(); |
| if (!dirstr) |
| { |
| errno = EMFILE; |
| return NULL; |
| } |
| |
| char osdirname[SIM_TMPBUF_MAX_PATH]; |
| int pprc = sim_get_os_path(osdirname, dirname, sizeof (osdirname)); |
| if (pprc < 0) |
| return NULL; |
| |
| int rc = os_opendir_and_fd(osdirname, &dirstr->osdirp, &dirstr->osfd); |
| if (rc < 0) |
| return NULL; |
| |
| dirstr->osfd_opened = rc > 0; |
| |
| #ifdef USE_OSDIRNAME |
| dirstr->osdirname = strdup(osdirname); |
| if (!dirstr->osdirname) |
| { |
| close_dirstr(dirstr); |
| return NULL; |
| } |
| #endif |
| |
| dirstr->entry.d_name[0] = 0; /* Mark as invalid */ |
| #ifdef HAVE_MULTIVOLUME |
| dirstr->volume = MAX(pprc - 1, 0); |
| dirstr->volumecounter = pprc == 0 ? 0 : INT_MAX; |
| #endif |
| dirstr->mounted = true; |
| return (DIR *)dirstr; /* A-Okay */ |
| } |
| |
| int sim_closedir(DIR *dirp) |
| { |
| struct dirstr_desc *dirstr = (struct dirstr_desc *)dirp; |
| if (!PTR_IN_ARRAY(opendirs, dirstr, MAX_OPEN_DIRS) || !dirstr->osdirp) |
| { |
| errno = EBADF; |
| return -1; |
| } |
| |
| return close_dirstr(dirstr); |
| } |
| |
| struct sim_dirent * sim_readdir(DIR *dirp) |
| { |
| struct dirstr_desc *dirstr = get_dirstr(dirp); |
| if (!dirstr) |
| return NULL; |
| |
| struct sim_dirent *entry = &dirstr->entry; |
| entry->info.osdirent = NULL; |
| |
| if (readdir_volume(dirstr, entry)) |
| return entry; |
| |
| OS_DIRENT_T *osdirent = os_readdir(dirstr->osdirp); |
| if (!osdirent) |
| return NULL; |
| |
| size_t size = sizeof (entry->d_name); |
| if (strlcpy_from_os(entry->d_name, osdirent->d_name, size) >= size) |
| FILE_ERROR_RETURN(ENAMETOOLONG, NULL); |
| |
| entry->info.osdirent = osdirent; |
| return entry; |
| } |
| |
| int sim_mkdir(const char *path) |
| { |
| char ospath[SIM_TMPBUF_MAX_PATH]; |
| if (sim_get_os_path(ospath, path, sizeof (ospath)) < 0) |
| return -1; |
| |
| int rc = os_mkdir(ospath __MKDIR_MODE_ARG); |
| |
| #ifdef HAVE_DIRCACHE |
| if (rc >= 0) |
| dircache_mkdir(xxxxxx); |
| #endif |
| |
| return rc; |
| } |
| |
| int sim_rmdir(const char *path) |
| { |
| char ospath[SIM_TMPBUF_MAX_PATH]; |
| if (sim_get_os_path(ospath, path, sizeof (ospath)) < 0) |
| return -1; |
| |
| int rc = os_rmdir(ospath); |
| |
| #ifdef HAVE_DIRCACHE |
| if (rc >= 0) |
| dircache_rmdir(xxxxxx); |
| #endif |
| |
| return rc; |
| } |
| |
| int sim_samedir(DIR *dirp1, DIR *dirp2) |
| { |
| struct dirstr_desc *dirstr1 = get_dirstr(dirp1); |
| if (!dirstr1) |
| return -1; |
| |
| struct dirstr_desc *dirstr2 = get_dirstr(dirp2); |
| if (!dirstr2) |
| return -1; |
| |
| return os_fsamefile(dirstr1->osfd, dirstr2->osfd); |
| } |
| |
| bool sim_dir_exists(const char *dirname) |
| { |
| char osdirname[SIM_TMPBUF_MAX_PATH]; |
| if (sim_get_os_path(osdirname, dirname, sizeof (osdirname)) < 0) |
| return false; |
| |
| OS_DIR_T *dirp = os_opendir(osdirname); |
| if (!dirp) |
| return false; |
| |
| os_closedir(dirp); |
| return true; |
| } |
| |
| struct dirinfo dir_get_info(DIR *dirp, struct sim_dirent *entry) |
| { |
| int rc; |
| struct dirstr_desc *dirstr = get_dirstr(dirp); |
| if (!dirstr) |
| FILE_ERROR(ERRNO, RC); |
| |
| if (entry->d_name[0] == 0) |
| FILE_ERROR(ENOENT, RC); |
| |
| OS_DIRENT_T *osdirent = entry->info.osdirent; |
| if (osdirent == NULL) |
| return (struct dirinfo){ .attribute = ATTR_MOUNT_POINT }; |
| |
| struct dirinfo info; |
| info.attribute = 0; |
| |
| OS_STAT_T s; |
| |
| #ifdef os_fstatat |
| if (os_fstatat(dirstr->osfd, entry->d_name, &s, 0)) |
| #else /* no fstatat; build file name for stat() */ |
| char statpath[SIM_TMPBUF_MAX_PATH]; |
| if (path_append(statpath, dirstr->osdirname, entry->d_name, |
| sizeof (statpath)) >= sizeof (statpath)) |
| { |
| FILE_ERROR(ENAMETOOLONG, RC); |
| } |
| |
| if (os_stat(statpath, &s)) /* get info */ |
| #endif /* os_fstatat */ |
| { |
| /* File size larger than 2 GB? */ |
| #ifdef EOVERFLOW |
| if (errno == EOVERFLOW) |
| DEBUGF("stat overflow for \"%s\"\n", entry->d_name); |
| #endif |
| FILE_ERROR(ERRNO, RC); |
| } |
| |
| if (S_ISDIR(s.st_mode)) |
| info.attribute |= ATTR_DIRECTORY; |
| |
| info.size = s.st_size; |
| |
| struct tm tm; |
| if (localtime_r(&s.st_mtime, &tm) == NULL) |
| FILE_ERROR(ERRNO, RC); |
| |
| info.mtime = mktime(&tm); |
| |
| return info; |
| |
| file_error: |
| return (struct dirinfo){ .size = 0 }; |
| } |
| |
| int os_volume_path(IF_MV(int volume, ) char *buffer, size_t bufsize) |
| { |
| if (!buffer || !bufsize IF_MV( || !volume_present(volume) )) |
| return -1; |
| |
| char tmpbuf[SIM_TMPBUF_MAX_PATH]; |
| tmpbuf[0] = '\0'; |
| |
| #ifdef HAVE_MULTIVOLUME |
| char volname[VOL_MAX_LEN + 1]; |
| get_volume_name(volume, volname); |
| |
| if (path_append(tmpbuf, PA_SEP_HARD, volname, sizeof (volname)) |
| >= sizeof (volname)) |
| return -1; |
| #endif /* HAVE_MULTIVOLUME */ |
| |
| if (path_append(tmpbuf, PA_SEP_HARD, ".", sizeof (tmpbuf)) |
| >= sizeof (tmpbuf)) |
| return -1; |
| |
| return sim_get_os_path(buffer, tmpbuf, bufsize); |
| } |