/* * This file is subject to the terms of the GFX License. If a copy of * the license was not distributed with this file, you can obtain one at: * * http://ugfx.org/license.html */ /** * @file src/gfile/gfile.c * @brief GFILE code. * */ #define GFILE_IMPLEMENTATION #include "gfx.h" #if GFX_USE_GFILE struct GFILE { const struct GFILEVMT * vmt; uint16_t flags; #define GFILEFLG_OPEN 0x0001 // File is open #define GFILEFLG_READ 0x0002 // Read the file #define GFILEFLG_WRITE 0x0004 // Write the file #define GFILEFLG_APPEND 0x0008 // Append on each write #define GFILEFLG_BINARY 0x0010 // Treat as a binary file #define GFILEFLG_DELONCLOSE 0x0020 // Delete on close #define GFILEFLG_CANSEEK 0x0040 // Seek operations are valid #define GFILEFLG_FAILONBLOCK 0x0080 // Fail on a blocking call #define GFILEFLG_MUSTEXIST 0x0100 // On open file must exist #define GFILEFLG_MUSTNOTEXIST 0x0200 // On open file must not exist #define GFILEFLG_TRUNC 0x0400 // On open truncate the file void * obj; long int pos; }; typedef struct GFILEVMT { const struct GFILEVMT * next; uint8_t flags; #define GFSFLG_WRITEABLE 0x0001 #define GFSFLG_CASESENSITIVE 0x0002 #define GFSFLG_SEEKABLE 0x0004 #define GFSFLG_FAST 0x0010 #define GFSFLG_SMALL 0x0020 #define GFSFLG_TEXTMODES 0x0040 char prefix; bool_t (*del) (const char *fname); bool_t (*exists) (const char *fname); long int (*filesize) (const char *fname); bool_t (*ren) (const char *oldname, const char *newname); bool_t (*open) (GFILE *f, const char *fname); void (*close) (GFILE *f); int (*read) (GFILE *f, void *buf, int size); int (*write) (GFILE *f, const void *buf, int size); bool_t (*setpos) (GFILE *f, long int pos); long int (*getsize) (GFILE *f); bool_t (*eof) (GFILE *f); } GFILEVMT; // The chain of FileSystems #define GFILE_CHAINHEAD 0 // The table of GFILE's static GFILE gfileArr[GFILE_MAX_GFILES]; GFILE *gfileStdIn; GFILE *gfileStdOut; GFILE *gfileStdErr; /** * The order of the file-systems below determines the order * that they are searched to find a file. * The last defined is the first searched. */ /******************************************************** * The ChibiOS BaseFileStream VMT ********************************************************/ #if GFILE_NEED_CHIBIOSFS && GFX_USE_OS_CHIBIOS #include "src/gfile/inc_chibiosfs.c" #endif /******************************************************** * The Memory Pointer VMT ********************************************************/ #if GFILE_NEED_MEMFS #include "src/gfile/inc_memfs.c" #endif /******************************************************** * The RAM file-system VMT ********************************************************/ #if GFILE_NEED_RAMFS #include "src/gfile/inc_ramfs.c" #endif /******************************************************** * The FAT file-system VMT ********************************************************/ #if GFILE_NEED_FATFS #include "src/gfile/inc_fatfs.c" #endif /******************************************************** * The native file-system ********************************************************/ #if GFILE_NEED_NATIVEFS #include "src/gfile/inc_nativefs.c" #endif /******************************************************** * The ROM file-system VMT ********************************************************/ #if GFILE_NEED_ROMFS #include "src/gfile/inc_romfs.c" #endif /******************************************************** * IO routines ********************************************************/ /** * The chain of file systems. */ static const GFILEVMT const * FsChain = GFILE_CHAINHEAD; /** * The init routine */ void _gfileInit(void) { #if GFILE_NEED_NATIVEFS NativeStdIn.flags = GFILEFLG_OPEN|GFILEFLG_READ; NativeStdIn.vmt = &FsNativeVMT; NativeStdIn.obj = (void *)stdin; NativeStdIn.pos = 0; gfileStdIn = &NativeStdIn; NativeStdOut.flags = GFILEFLG_OPEN|GFILEFLG_WRITE|GFILEFLG_APPEND; NativeStdOut.vmt = &FsNativeVMT; NativeStdOut.obj = (void *)stdout; NativeStdOut.pos = 0; gfileStdOut = &NativeStdOut; NativeStdErr.flags = GFILEFLG_OPEN|GFILEFLG_WRITE|GFILEFLG_APPEND; NativeStdErr.vmt = &FsNativeVMT; NativeStdErr.obj = (void *)stderr; NativeStdErr.pos = 0; gfileStdErr = &NativeStdErr; #endif } void _gfileDeinit(void) { /* ToDo */ } bool_t gfileExists(const char *fname) { const GFILEVMT *p; #if GFILE_ALLOW_DEVICESPECIFIC if (fname[0] && fname[1] == '|') { for(p = FsChain; p; p = p->next) { if (p->prefix == fname[0]) return p->exists && p->exists(fname+2); } return FALSE; } #endif for(p = FsChain; p; p = p->next) { if (p->exists && p->exists(fname)) return TRUE; } return FALSE; } bool_t gfileDelete(const char *fname) { const GFILEVMT *p; #if GFILE_ALLOW_DEVICESPECIFIC if (fname[0] && fname[1] == '|') { for(p = FsChain; p; p = p->next) { if (p->prefix == fname[0]) return p->del && p->del(fname+2); } return FALSE; } #endif for(p = FsChain; p; p = p->next) { if (p->del && p->del(fname)) return TRUE; } return FALSE; } long int gfileGetFilesize(const char *fname) { const GFILEVMT *p; long int res; #if GFILE_ALLOW_DEVICESPECIFIC if (fname[0] && fname[1] == '|') { for(p = FsChain; p; p = p->next) { if (p->prefix == fname[0]) return p->filesize ? p->filesize(fname+2) : -1; } return -1; } #endif for(p = FsChain; p; p = p->next) { if (p->filesize && (res = p->filesize(fname)) != -1) return res; } return -1; } bool_t gfileRename(const char *oldname, const char *newname) { const GFILEVMT *p; #if GFILE_ALLOW_DEVICESPECIFIC if ((oldname[0] && oldname[1] == '|') || (newname[0] && newname[1] == '|')) { char ch; if (oldname[0] && oldname[1] == '|') { ch = oldname[0]; oldname += 2; if (newname[0] && newname[1] == '|') { if (newname[0] != ch) // Both oldname and newname are fs specific but different ones. return FALSE; newname += 2; } } else { ch = newname[0]; newname += 2; } for(p = FsChain; p; p = p->next) { if (p->prefix == ch) return p->ren && p->ren(oldname, newname); } return FALSE; } #endif for(p = FsChain; p; p = p->next) { if (p->ren && p->ren(oldname,newname)) return TRUE; } return FALSE; } static uint16_t mode2flags(const char *mode) { uint16_t flags; switch(mode[0]) { case 'r': flags = GFILEFLG_READ|GFILEFLG_MUSTEXIST; while (*++mode) { switch(mode[0]) { case '+': flags |= GFILEFLG_WRITE; break; case 'b': flags |= GFILEFLG_BINARY; break; } } return flags; case 'w': flags = GFILEFLG_WRITE|GFILEFLG_TRUNC; while (*++mode) { switch(mode[0]) { case '+': flags |= GFILEFLG_READ; break; case 'b': flags |= GFILEFLG_BINARY; break; case 'x': flags |= GFILEFLG_MUSTNOTEXIST; break; } } return flags; case 'a': flags = GFILEFLG_WRITE|GFILEFLG_APPEND; while (*++mode) { switch(mode[0]) { case '+': flags |= GFILEFLG_READ; break; case 'b': flags |= GFILEFLG_BINARY; break; case 'x': flags |= GFILEFLG_MUSTNOTEXIST; break; } } return flags; } return 0; } static bool_t testopen(const GFILEVMT *p, GFILE *f, const char *fname) { // If we want write but the fs doesn't allow it then return if ((f->flags & GFILEFLG_WRITE) && !(p->flags & GFSFLG_WRITEABLE)) return FALSE; // Try to open if (!p->open || !p->open(f, fname)) return FALSE; // File is open - fill in all the details f->vmt = p; f->pos = 0; f->flags |= GFILEFLG_OPEN; if (p->flags & GFSFLG_SEEKABLE) f->flags |= GFILEFLG_CANSEEK; return TRUE; } GFILE *gfileOpen(const char *fname, const char *mode) { uint16_t flags; GFILE * f; const GFILEVMT *p; // Get the requested mode if (!(flags = mode2flags(mode))) return 0; #if GFILE_ALLOW_DEVICESPECIFIC if (fname[0] && fname[1] == '|') { // First find an available GFILE slot. for (f = gfileArr; f < &gfileArr[GFILE_MAX_GFILES]; f++) { if (!(f->flags & GFILEFLG_OPEN)) { // Try to open the file f->flags = flags; for(p = FsChain; p; p = p->next) { if (p->prefix == fname[0]) return testopen(p, f, fname+2) ? f : 0; } // File not found break; } } // No available slot return 0; } #endif // First find an available GFILE slot. for (f = gfileArr; f < &gfileArr[GFILE_MAX_GFILES]; f++) { if (!(f->flags & GFILEFLG_OPEN)) { // Try to open the file f->flags = flags; for(p = FsChain; p; p = p->next) { if (testopen(p, f, fname)) return f; } // File not found break; } } // No available slot return 0; } #if GFILE_NEED_CHIBIOSFS && GFX_USE_OS_CHIBIOS GFILE * gfileOpenBaseFileStream(void *BaseFileStreamPtr, const char *mode) { GFILE * f; // First find an available GFILE slot. for (f = gfileArr; f < &gfileArr[GFILE_MAX_GFILES]; f++) { if (!(f->flags & GFILEFLG_OPEN)) { // Get the flags if (!(f->flags = mode2flags(mode))) return 0; // If we want write but the fs doesn't allow it then return if ((f->flags & GFILEFLG_WRITE) && !(FsCHIBIOSVMT.flags & GFSFLG_WRITEABLE)) return 0; // File is open - fill in all the details f->vmt = &FsCHIBIOSVMT; f->obj = BaseFileStreamPtr; f->pos = 0; f->flags |= GFILEFLG_OPEN|GFILEFLG_CANSEEK; return f; } } // No available slot return 0; } #endif #if GFILE_NEED_MEMFS GFILE * gfileOpenMemory(void *memptr, const char *mode) { GFILE * f; // First find an available GFILE slot. for (f = gfileArr; f < &gfileArr[GFILE_MAX_GFILES]; f++) { if (!(f->flags & GFILEFLG_OPEN)) { // Get the flags if (!(f->flags = mode2flags(mode))) return 0; // If we want write but the fs doesn't allow it then return if ((f->flags & GFILEFLG_WRITE) && !(FsMemVMT.flags & GFSFLG_WRITEABLE)) return 0; // File is open - fill in all the details f->vmt = &FsMemVMT; f->obj = memptr; f->pos = 0; f->flags |= GFILEFLG_OPEN|GFILEFLG_CANSEEK; return f; } } // No available slot return 0; } #endif void gfileClose(GFILE *f) { if (!f || !(f->flags & GFILEFLG_OPEN)) return; if (f->vmt->close) f->vmt->close(f); f->flags = 0; } size_t gfileRead(GFILE *f, void *buf, size_t len) { size_t res; if (!f || (f->flags & (GFILEFLG_OPEN|GFILEFLG_READ)) != (GFILEFLG_OPEN|GFILEFLG_READ)) return 0; if (!f->vmt->read) return 0; if ((res = f->vmt->read(f, buf, len)) <= 0) return 0; f->pos += res; return res; } size_t gfileWrite(GFILE *f, const void *buf, size_t len) { size_t res; if (!f || (f->flags & (GFILEFLG_OPEN|GFILEFLG_WRITE)) != (GFILEFLG_OPEN|GFILEFLG_WRITE)) return 0; if (!f->vmt->write) return 0; if ((res = f->vmt->write(f, buf, len)) <= 0) return 0; f->pos += res; return res; } long int gfileGetPos(GFILE *f) { if (!f || !(f->flags & GFILEFLG_OPEN)) return 0; return f->pos; } bool_t gfileSetPos(GFILE *f, long int pos) { if (!f || !(f->flags & GFILEFLG_OPEN)) return FALSE; if (!f->vmt->setpos || !f->vmt->setpos(f, pos)) return FALSE; f->pos = pos; return TRUE; } long int gfileGetSize(GFILE *f) { if (!f || !(f->flags & GFILEFLG_OPEN)) return 0; if (!f->vmt->getsize) return 0; return f->vmt->getsize(f); } bool_t gfileEOF(GFILE *f) { if (!f || !(f->flags & GFILEFLG_OPEN)) return TRUE; if (!f->vmt->eof) return FALSE; return f->vmt->eof(f); } /******************************************************** * String VMT routines ********************************************************/ #if GFILE_NEED_STRINGS && (GFILE_NEED_PRINTG || GFILE_NEED_SCANG) #include // Special String VMT static int StringRead(GFILE *f, void *buf, int size) { (void) size; // size must be 1 for a complete read if (!((char *)f->obj)[f->pos]) return 0; ((char *)buf)[0] = ((char *)f->obj)[f->pos]; return 1; } static int StringWrite(GFILE *f, const void *buf, int size) { (void) size; // size must be 1 for a complete write ((char *)f->obj)[f->pos] = ((char *)buf)[0]; return 1; } static const GFILEVMT StringVMT = { 0, // next 0, // flags '_', // prefix 0, 0, 0, 0, 0, 0, StringRead, StringWrite, 0, 0, 0, }; #endif /******************************************************** * printg routines ********************************************************/ #if GFILE_NEED_PRINTG #include #define MAX_FILLER 11 #define FLOAT_PRECISION 100000 int fnprintg(GFILE *f, int maxlen, const char *fmt, ...) { int res; va_list ap; va_start(ap, fmt); res = vfnprintg(f, maxlen, fmt, ap); va_end(ap); return res; } static char *ltoa_wd(char *p, long num, unsigned radix, long divisor) { int i; char * q; if (!divisor) divisor = num; q = p + MAX_FILLER; do { i = (int)(num % radix); i += '0'; if (i > '9') i += 'A' - '0' - 10; *--q = i; num /= radix; } while ((divisor /= radix) != 0); i = (int)(p + MAX_FILLER - q); do { *p++ = *q++; } while (--i); return p; } int vfnprintg(GFILE *f, int maxlen, const char *fmt, va_list arg) { int ret; char *p, *s, c, filler; int i, precision, width; bool_t is_long, left_align; long l; #if GFILE_ALLOW_FLOATS float f; char tmpbuf[2*MAX_FILLER + 1]; #else char tmpbuf[MAX_FILLER + 1]; #endif ret = 0; if (maxlen < 0) return 0; if (!maxlen) maxlen = -1; while (*fmt) { if (*fmt != '%') { gfileWrite(f, fmt, 1); ret++; if (!--maxlen) return ret; fmt++; continue; } fmt++; p = s = tmpbuf; left_align = FALSE; filler = ' '; width = 0; precision = 0; if (*fmt == '-') { fmt++; left_align = TRUE; } if (*fmt == '.') { fmt++; filler = '0'; } while (1) { c = *fmt++; if (c >= '0' && c <= '9') { c -= '0'; width = width * 10 + c; } else if (c == '*') width = va_arg(arg, int); else break; } if (c == '.') { while (1) { c = *fmt++; if (c >= '0' && c <= '9') { c -= '0'; precision = precision * 10 + c; } else if (c == '*') precision = va_arg(arg, int); else break; } } /* Long modifier.*/ if (c == 'l' || c == 'L') { is_long = TRUE; if (*fmt) c = *fmt++; } else is_long = (c >= 'A') && (c <= 'Z'); /* Command decoding.*/ switch (c) { case 0: return ret; case 'c': filler = ' '; *p++ = va_arg(arg, int); break; case 's': filler = ' '; if ((s = va_arg(arg, char *)) == 0) s = "(null)"; if (precision == 0) precision = 32767; for (p = s; *p && (--precision >= 0); p++); break; case 'D': case 'd': if (is_long) l = va_arg(arg, long); else l = va_arg(arg, int); if (l < 0) { *p++ = '-'; l = -l; } p = ltoa_wd(p, l, 10, 0); break; #if GFILE_ALLOW_FLOATS case 'f': f = (float) va_arg(arg, double); if (f < 0) { *p++ = '-'; f = -f; } l = f; p = ltoa_wd(p, l, 10, 0); *p++ = '.'; l = (f - l) * FLOAT_PRECISION; p = ltoa_wd(p, l, 10, FLOAT_PRECISION / 10); break; #endif case 'X': case 'x': c = 16; goto unsigned_common; case 'U': case 'u': c = 10; goto unsigned_common; case 'O': case 'o': c = 8; unsigned_common: if (is_long) l = va_arg(arg, long); else l = va_arg(arg, int); p = ltoa_wd(p, l, c, 0); break; default: *p++ = c; break; } i = (int)(p - s); if ((width -= i) < 0) width = 0; if (left_align == FALSE) width = -width; if (width < 0) { if (*s == '-' && filler == '0') { gfileWrite(f, s++, 1); ret++; if (!--maxlen) return ret; i--; } do { gfileWrite(f, &filler, 1); ret++; if (!--maxlen) return ret; } while (++width != 0); } while (--i >= 0) { gfileWrite(f, s++, 1); ret++; if (!--maxlen) return ret; } while (width) { gfileWrite(f, &filler, 1); ret++; if (!--maxlen) return ret; width--; } } return ret; } #if GFILE_NEED_STRINGS int snprintg(char *buf, int maxlen, const char *fmt, ...) { int res; GFILE f; va_list ap; if (maxlen <= 1) { if (maxlen == 1) { *buf = 0; return 0; } maxlen += 1; } f.flags = GFILEFLG_OPEN|GFILEFLG_WRITE; f.vmt = &StringVMT; f.pos = 0; f.obj = buf; va_start(ap, fmt); res = vfnprintg(&f, maxlen-1, fmt, ap); va_end(ap); buf[res] = 0; return res; } int vsnprintg(char *buf, int maxlen, const char *fmt, va_list arg) { int res; GFILE f; if (maxlen <= 1) { if (maxlen == 1) { *buf = 0; return 0; } maxlen += 1; } f.flags = GFILEFLG_OPEN|GFILEFLG_WRITE; f.vmt = &StringVMT; f.pos = 0; f.obj = buf; res = vfnprintg(&f, maxlen-1, fmt, arg); buf[res] = 0; return res; } #endif #endif /******************************************************** * scang routines ********************************************************/ #if GFILE_NEED_SCANG int fscang(GFILE *f, const char *fmt, ...) { int res; va_list ap; va_start(ap, fmt); res = vfscang(f, fmt, ap); va_end(ap); return res; } int vfscang(GFILE *f, const char *fmt, va_list arg) { int res, width, size, base; unsigned long num; char c; bool_t assign, negate; char *p; for(res = 0; *fmt; fmt++) { switch(*fmt) { case ' ': case '\t': case '\r': case '\n': case '\v': case '\f': break; case '%': fmt++; assign = TRUE; negate = FALSE; width = 0; size = 1; num = 0; if (*fmt == '*') { fmt++; assign = FALSE; } while(*fmt >= '0' && *fmt <= '9') width = width * 10 + (*fmt++ - '0'); if (*fmt == 'h') { fmt++; size = 0; } else if (*fmt == 'l') { fmt++; size = 2; } else if (*fmt == 'L') { fmt++; size = 3; } switch(*fmt) { case 0: return res; case '%': goto matchchar; case 'c': if (!width) { while(1) { if (!gfileRead(f, &c, 1)) return res; switch(c) { case ' ': case '\t': case '\r': case '\n': case '\v': case '\f': continue; } break; } width = 1; } else { if (!gfileRead(f, &c, 1)) return res; } if (assign) { p = va_arg(arg, char *); res++; *p++ = c; } while(--width) { if (!gfileRead(f, &c, 1)) return res; if (assign) *p++ = c; } break; case 's': while(1) { if (!gfileRead(f, &c, 1)) return res; switch(c) { case ' ': case '\t': case '\r': case '\n': case '\v': case '\f': continue; } break; } if (assign) { p = va_arg(arg, char *); res++; *p++ = c; } if (width) { while(--width) { if (!gfileRead(f, &c, 1)) { if (assign) *((char *)p) = 0; return res; } if (assign) *p++ = c; } } else { while(1) { if (!gfileRead(f, &c, 1)) { if (assign) *((char *)p) = 0; return res; } switch(c) { case ' ': case '\t': case '\r': case '\n': case '\v': case '\f': break; default: if (assign) *p++ = c; continue; } break; } //ungetch(c); } if (assign) *p = 0; break; case 'd': base = 10; goto getnum; case 'i': base = -1; goto getnum; case 'o': base = 8; goto getnum; case 'u': base = 10; goto getnum; case 'x': base = 16; goto getnum; case 'b': base = 2; getnum: while(1) { if (!gfileRead(f, &c, 1)) return res; switch(c) { case ' ': case '\t': case '\r': case '\n': case '\v': case '\f': continue; } break; } if (c == '-' && *fmt != 'u') { negate = TRUE; if ((width && !--width) || !gfileRead(f, &c, 1)) return res; } if (base == -1) { if (c == '0') { if ((width && !--width) || !gfileRead(f, &c, 1)) goto assignnum; switch(c) { case 'x': case 'X': base = 16; if ((width && !--width) || !gfileRead(f, &c, 1)) return res; break; case 'b': case 'B': base = 2; if ((width && !--width) || !gfileRead(f, &c, 1)) return res; break; default: base = 8; break; } } else base = 10; } while(1) { if (c >= '0' && c <= '9' && c - '0' < base) num = num * base + (c - '0'); else if (c >= 'A' && c <= 'F' && base == 16) num = num * base + (c - ('A'-10)); else if (c >= 'a' && c <= 'f' && base == 16) num = num * base + (c - ('a'-10)); else { // ungetch(c) break; } if ((width && !--width) || !gfileRead(f, &c, 1)) break; } assignnum: if (negate) num = -num; if (assign) { switch(size) { case 0: // short p = (char *)va_arg(arg, short *); res++; *((short *)p) = (short)num; case 1: // int p = (char *)va_arg(arg, int *); res++; *((int *)p) = (int)num; case 2: case 3: // long p = (char *)va_arg(arg, long *); res++; *((long *)p) = (long)num; } } break; #if GFILE_ALLOW_FLOATS case 'e': case 'f': case 'g': // TODO #endif default: return res; } break; default: matchchar: while(1) { if (!gfileRead(f, &c, 1)) return res; switch(c) { case ' ': case '\t': case '\r': case '\n': case '\v': case '\f': continue; } break; } if (c != *fmt) return res; break; } } return res; } #if GFILE_NEED_STRINGS int sscang(const char *buf, const char *fmt, ...) { int res; GFILE f; va_list ap; f.flags = GFILEFLG_OPEN|GFILEFLG_READ; f.vmt = &StringVMT; f.pos = 0; f.obj = (void *)buf; va_start(ap, fmt); res = vfscang(&f, fmt, ap); va_end(ap); return res; } int vsscang(const char *buf, const char *fmt, va_list arg) { int res; GFILE f; f.flags = GFILEFLG_OPEN|GFILEFLG_READ; f.vmt = &StringVMT; f.pos = 0; f.obj = (void *)buf; res = vfscang(&f, fmt, arg); return res; } #endif #endif /******************************************************** * stdio emulation routines ********************************************************/ #if GFILE_NEED_STDIO size_t gstdioRead(void * ptr, size_t size, size_t count, FILE *f) { return gfileRead(f, ptr, size*count)/size; } size_t gstdioWrite(const void * ptr, size_t size, size_t count, FILE *f) { return gfileWrite(f, ptr, size*count)/size; } int gstdioSeek(FILE *f, size_t offset, int origin) { switch(origin) { case SEEK_SET: break; case SEEK_CUR: offset += f->pos; break; case SEEK_END: offset += gfileGetSize(f); break; default: return -1; } return gfileSetPos(f, offset) ? 0 : -1; } int gstdioGetpos(FILE *f, long int *pos) { if (!(f->flags & GFILEFLG_OPEN)) return -1; *pos = f->pos; return 0; } #endif #endif /* GFX_USE_GFILE */