/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2002 Daniel Stenberg * * 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. * ****************************************************************************/ #include #include #include #include #include #include #include #include "config.h" #define HAVE_STATVFS (!defined(WIN32)) #define HAVE_LSTAT (!defined(WIN32)) #if HAVE_STATVFS #include #endif #ifdef WIN32 #include #endif #ifndef _MSC_VER #include #include #else #include "dir-win32.h" #endif #include #ifdef HAVE_SDL_THREADS #include "thread-sdl.h" #else #define sim_thread_unlock() NULL #define sim_thread_lock(a) #endif #include "thread.h" #include "kernel.h" #include "debug.h" #include "ata.h" /* for IF_MV2 et al. */ #include "rbpaths.h" #include "load_code.h" /* keep this in sync with file.h! */ #undef MAX_PATH /* this avoids problems when building simulator */ #define MAX_PATH 260 #define MAX_OPEN_FILES 11 /* Windows (and potentially other OSes) distinguish binary and text files. * Define a dummy for the others. */ #ifndef O_BINARY #define O_BINARY 0 #endif /* Unicode compatibility for win32 */ #if defined __MINGW32__ /* Rockbox unicode functions */ extern const unsigned char* utf8decode(const unsigned char *utf8, unsigned short *ucs); extern unsigned char* utf8encode(unsigned long ucs, unsigned char *utf8); /* Static buffers for the conversion results. This isn't thread safe, * but it's sufficient for rockbox. */ static unsigned char convbuf1[3*MAX_PATH]; static unsigned char convbuf2[3*MAX_PATH]; static wchar_t* utf8_to_ucs2(const unsigned char *utf8, void *buffer) { wchar_t *ucs = buffer; while (*utf8) utf8 = utf8decode(utf8, ucs++); *ucs = 0; return buffer; } static unsigned char *ucs2_to_utf8(const wchar_t *ucs, unsigned char *buffer) { unsigned char *utf8 = buffer; while (*ucs) utf8 = utf8encode(*ucs++, utf8); *utf8 = 0; return buffer; } #define UTF8_TO_OS(a) utf8_to_ucs2(a,convbuf1) #define OS_TO_UTF8(a) ucs2_to_utf8(a,convbuf1) #define DIR_T _WDIR #define DIRENT_T struct _wdirent #define STAT_T struct _stat extern int _wmkdir(const wchar_t*); extern int _wrmdir(const wchar_t*); #define MKDIR(a,b) (_wmkdir)(UTF8_TO_OS(a)) #define RMDIR(a) (_wrmdir)(UTF8_TO_OS(a)) #define OPENDIR(a) (_wopendir)(UTF8_TO_OS(a)) #define READDIR(a) (_wreaddir)(a) #define CLOSEDIR(a) (_wclosedir)(a) #define STAT(a,b) (_wstat)(UTF8_TO_OS(a),b) /* empty variable parameter list doesn't work for variadic macros, * so pretend the second parameter is variable too */ #define OPEN(a,...) (_wopen)(UTF8_TO_OS(a), __VA_ARGS__) #define CLOSE(a) (close)(a) #define REMOVE(a) (_wremove)(UTF8_TO_OS(a)) #define RENAME(a,b) (_wrename)(UTF8_TO_OS(a),utf8_to_ucs2(b,convbuf2)) #else /* !__MINGW32__ */ #define UTF8_TO_OS(a) (a) #define OS_TO_UTF8(a) (a) #define DIR_T DIR #define DIRENT_T struct dirent #define STAT_T struct stat #define MKDIR(a,b) (mkdir)(a,b) #define RMDIR(a) (rmdir)(a) #define OPENDIR(a) (opendir)(a) #define READDIR(a) (readdir)(a) #define CLOSEDIR(a) (closedir)(a) #define STAT(a,b) (stat)(a,b) /* empty variable parameter list doesn't work for variadic macros, * so pretend the second parameter is variable too */ #define OPEN(a, ...) (open)(a, __VA_ARGS__) #define CLOSE(x) (close)(x) #define REMOVE(a) (remove)(a) #define RENAME(a,b) (rename)(a,b) #endif /* !__MINGW32__ */ #ifdef HAVE_DIRCACHE int dircache_get_entry_id(const char *filename); void dircache_add_file(const char *name, long startcluster); void dircache_remove(const char *name); void dircache_rename(const char *oldname, const char *newname); #endif #define SIMULATOR_DEFAULT_ROOT "simdisk" extern const char *sim_root_dir; static int num_openfiles = 0; /* from dir.h */ struct dirinfo { int attribute; long size; unsigned short wrtdate; unsigned short wrttime; }; struct sim_dirent { unsigned char d_name[MAX_PATH]; struct dirinfo info; long startcluster; }; struct dirstruct { void *dir; /* actually a DIR* dir */ char *name; } SIM_DIR; struct mydir { DIR_T *dir; char *name; }; typedef struct mydir MYDIR; static unsigned int rockbox2sim(int opt) { #if 0 /* this shouldn't be needed since we use the host's versions */ int newopt = O_BINARY; if(opt & 1) newopt |= O_WRONLY; if(opt & 2) newopt |= O_RDWR; if(opt & 4) newopt |= O_CREAT; if(opt & 8) newopt |= O_APPEND; if(opt & 0x10) newopt |= O_TRUNC; return newopt; #else return opt|O_BINARY; #endif } /** Simulator I/O engine routines **/ #define IO_YIELD_THRESHOLD 512 enum io_dir { IO_READ, IO_WRITE, }; struct sim_io { struct mutex sim_mutex; /* Rockbox mutex */ int cmd; /* The command to perform */ int ready; /* I/O ready flag - 1= ready */ int fd; /* The file to read/write */ void *buf; /* The buffer to read/write */ size_t count; /* Number of bytes to read/write */ size_t accum; /* Acculated bytes transferred */ }; static struct sim_io io; int ata_init(void) { /* Initialize the rockbox kernel objects on a rockbox thread */ mutex_init(&io.sim_mutex); io.accum = 0; return 1; } int ata_spinup_time(void) { return HZ; } static ssize_t io_trigger_and_wait(enum io_dir cmd) { void *mythread = NULL; ssize_t result; if (io.count > IO_YIELD_THRESHOLD || (io.accum += io.count) >= IO_YIELD_THRESHOLD) { /* Allow other rockbox threads to run */ io.accum = 0; mythread = sim_thread_unlock(); } switch (cmd) { case IO_READ: result = read(io.fd, io.buf, io.count); break; case IO_WRITE: result = write(io.fd, io.buf, io.count); break; /* shut up gcc */ default: result = -1; } /* Regain our status as current */ if (mythread != NULL) { sim_thread_lock(mythread); } return result; } #if !defined(__PCTOOL__) && !defined(APPLICATION) static const char *get_sim_pathname(const char *name) { static char buffer[MAX_PATH]; /* sufficiently big */ if(name[0] == '/') { snprintf(buffer, sizeof(buffer), "%s%s", sim_root_dir != NULL ? sim_root_dir : SIMULATOR_DEFAULT_ROOT, name); return buffer; } fprintf(stderr, "WARNING, bad file name lacks slash: %s\n", name); return name; } #else #define get_sim_pathname(name) name #endif MYDIR *sim_opendir(const char *name) { DIR_T *dir; dir = (DIR_T *) OPENDIR(get_sim_pathname(name)); if (dir) { MYDIR *my = (MYDIR *)malloc(sizeof(MYDIR)); my->dir = dir; my->name = (char *)malloc(strlen(name)+1); strcpy(my->name, name); return my; } /* failed open, return NULL */ return (MYDIR *)0; } #if defined(WIN32) static inline struct tm* localtime_r (const time_t *clock, struct tm *result) { if (!clock || !result) return NULL; memcpy(result,localtime(clock),sizeof(*result)); return result; } #endif struct sim_dirent *sim_readdir(MYDIR *dir) { char buffer[MAX_PATH]; /* sufficiently big */ static struct sim_dirent secret; STAT_T s; struct tm tm; DIRENT_T *x11; #ifdef EOVERFLOW read_next: #endif x11 = READDIR(dir->dir); if(!x11) return (struct sim_dirent *)0; strcpy((char *)secret.d_name, OS_TO_UTF8(x11->d_name)); /* build file name */ snprintf(buffer, sizeof(buffer), "%s/%s", get_sim_pathname(dir->name), secret.d_name); if (STAT(buffer, &s)) /* get info */ { #ifdef EOVERFLOW /* File size larger than 2 GB? */ if (errno == EOVERFLOW) { DEBUGF("stat() overflow for %s. Skipping\n", buffer); goto read_next; } #endif return NULL; } #define ATTR_DIRECTORY 0x10 secret.info.attribute = 0; if (S_ISDIR(s.st_mode)) secret.info.attribute = ATTR_DIRECTORY; secret.info.size = s.st_size; if (localtime_r(&(s.st_mtime), &tm) == NULL) return NULL; secret.info.wrtdate = ((tm.tm_year - 80) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday; secret.info.wrttime = (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); #if HAVE_LSTAT #define ATTR_LINK 0x80 if (!lstat(buffer, &s) && S_ISLNK(s.st_mode)) { secret.info.attribute |= ATTR_LINK; } #endif return &secret; } void sim_closedir(MYDIR *dir) { free(dir->name); CLOSEDIR(dir->dir); free(dir); } int sim_open(const char *name, int o, ...) { int opts = rockbox2sim(o); int ret; if (num_openfiles >= MAX_OPEN_FILES) return -2; if (opts & O_CREAT) { va_list ap; va_start(ap, o); mode_t mode = va_arg(ap, unsigned int); ret = OPEN(get_sim_pathname(name), opts, mode); #ifdef HAVE_DIRCACHE if (ret >= 0 && (dircache_get_entry_id(name) < 0)) dircache_add_file(name, 0); #endif va_end(ap); } else ret = OPEN(get_sim_pathname(name), opts); if (ret >= 0) num_openfiles++; return ret; } int sim_close(int fd) { int ret; ret = CLOSE(fd); if (ret == 0) num_openfiles--; return ret; } int sim_creat(const char *name, mode_t mode) { int ret = OPEN(get_sim_pathname(name), O_BINARY | O_WRONLY | O_CREAT | O_TRUNC, mode); #ifdef HAVE_DIRCACHE if (ret >= 0 && (dircache_get_entry_id(name) < 0)) dircache_add_file(name, 0); #endif return ret; } ssize_t sim_read(int fd, void *buf, size_t count) { ssize_t result; mutex_lock(&io.sim_mutex); /* Setup parameters */ io.fd = fd; io.buf = buf; io.count = count; result = io_trigger_and_wait(IO_READ); mutex_unlock(&io.sim_mutex); return result; } ssize_t sim_write(int fd, const void *buf, size_t count) { ssize_t result; mutex_lock(&io.sim_mutex); io.fd = fd; io.buf = (void*)buf; io.count = count; result = io_trigger_and_wait(IO_WRITE); mutex_unlock(&io.sim_mutex); return result; } int sim_mkdir(const char *name) { return MKDIR(get_sim_pathname(name), 0777); } int sim_rmdir(const char *name) { return RMDIR(get_sim_pathname(name)); } int sim_remove(const char *name) { int ret = REMOVE(get_sim_pathname(name)); #ifdef HAVE_DIRCACHE if (ret >= 0) dircache_remove(name); #endif return ret; } int sim_rename(const char *oldname, const char *newname) { char sim_old[MAX_PATH]; char sim_new[MAX_PATH]; #ifdef HAVE_DIRCACHE dircache_rename(oldname, newname); #endif // This is needed as get_sim_pathname() has a static buffer strncpy(sim_old, get_sim_pathname(oldname), MAX_PATH); strncpy(sim_new, get_sim_pathname(newname), MAX_PATH); return RENAME(sim_old, sim_new); } /* rockbox off_t may be different from system off_t */ long sim_lseek(int fildes, long offset, int whence) { return lseek(fildes, offset, whence); } long sim_filesize(int fd) { #ifdef WIN32 return _filelength(fd); #else struct stat buf; if (!fstat(fd, &buf)) return buf.st_size; else return -1; #endif } void fat_size(IF_MV2(int volume,) unsigned long* size, unsigned long* free) { #ifdef HAVE_MULTIVOLUME if (volume != 0) { /* debugf("io.c: fat_size(volume=%d); simulator only supports volume 0\n",volume); */ if (size) *size = 0; if (free) *free = 0; return; } #endif #ifdef WIN32 long secperclus, bytespersec, free_clusters, num_clusters; if (GetDiskFreeSpace(NULL, &secperclus, &bytespersec, &free_clusters, &num_clusters)) { if (size) *size = num_clusters * secperclus / 2 * (bytespersec / 512); if (free) *free = free_clusters * secperclus / 2 * (bytespersec / 512); } else #elif HAVE_STATVFS struct statvfs vfs; if (!statvfs(".", &vfs)) { DEBUGF("statvfs: frsize=%d blocks=%ld free=%ld\n", (int)vfs.f_frsize, (long)vfs.f_blocks, (long)vfs.f_bfree); if (size) *size = vfs.f_blocks / 2 * (vfs.f_frsize / 512); if (free) *free = vfs.f_bfree / 2 * (vfs.f_frsize / 512); } else #endif { if (size) *size = 0; if (free) *free = 0; } } int sim_fsync(int fd) { #ifdef WIN32 return _commit(fd); #else return fsync(fd); #endif } #ifndef __PCTOOL__ void *lc_open(const char *filename, unsigned char *buf, size_t buf_size) { const char *sim_path = get_sim_pathname(filename); void *handle = _lc_open(UTF8_TO_OS(sim_path), buf, buf_size); if (handle == NULL) { DEBUGF("failed to load %s\n", filename); DEBUGF("lc_open(%s): %s\n", filename, lc_last_error()); } return handle; } void *lc_get_header(void *handle) { return _lc_get_header(handle); } void lc_close(void *handle) { _lc_close(handle); } #endif /* __PCTOOL__ */ #ifdef WIN32 static unsigned old_cp; void debug_exit(void) { /* Reset console output codepage */ SetConsoleOutputCP(old_cp); } void debug_init(void) { old_cp = GetConsoleOutputCP(); /* Set console output codepage to UTF8. Only works * correctly when the console uses a truetype font. */ SetConsoleOutputCP(65001); atexit(debug_exit); } #else void debug_init(void) { /* nothing to be done */ } #endif void debugf(const char *fmt, ...) { va_list ap; va_start( ap, fmt ); vfprintf( stderr, fmt, ap ); va_end( ap ); } void ldebugf(const char* file, int line, const char *fmt, ...) { va_list ap; va_start( ap, fmt ); fprintf( stderr, "%s:%d ", file, line ); vfprintf( stderr, fmt, ap ); va_end( ap ); } /* rockbox off_t may be different from system off_t */ int sim_ftruncate(int fd, long length) { #ifdef WIN32 return _chsize(fd, length); #else return ftruncate(fd, length); #endif }