/* * (C) Copyright 2014,2016,2018 Leo C. * * SPDX-License-Identifier: GPL-2.0 */ /* * FAT filesystem commands */ #include "common.h" #include "cmd_fat.h" #include uint32_t fat_time(const struct tm * timeptr); #include "ff.h" #include "z80-if.h" #include "eval_arg.h" #include "con-utils.h" #include "print-utils.h" #include "timer.h" #include "debug.h" #include "env.h" #include "getopt-min.h" #define DEBUG_FA 0 /* set to 1 to debug */ #define DEBUG_LS 1 /* set to 1 to debug */ #define DEBUG_RM 0 /* set to 1 to debug */ #define debug_fa(fmt, args...) \ debug_cond(DEBUG_FA, fmt, ##args) #define debug_ls(fmt, args...) \ debug_cond(DEBUG_LS, fmt, ##args) #define debug_rm(fmt, args...) \ debug_cond(DEBUG_RM, fmt, ##args) /* TODO: use memory size test function (z80_memsize_detect() ) */ #define MAX_MEMORY CONFIG_SYS_RAMSIZE_MAX #define BUFFER_SIZE FF_MAX_SS #define PATH_MAX CONFIG_SYS_MAX_PATHLEN /* * Multible (fat) partitions per physical drive are not supported, * but we have up to 2 sdcard slots. */ static FATFS FatFs0; static FATFS FatFs1; void setup_fatfs(void) { f_mount(&FatFs0, "0:", 0); f_mount(&FatFs1, "1:", 0); } DWORD get_fattime (void) { time_t timer; struct tm tm_timer; time(&timer); gmtime_r(&timer, &tm_timer); return fat_time(&tm_timer); } static bool check_abort(void) { bool ret = ctrlc(); if (ret) printf_P(PSTR("Abort\n")); return ret; } static const FLASH char * const FLASH rc_strings[] = { FSTR("Success"), FSTR("Disk error"), FSTR("Internal error"), FSTR("Not ready"), FSTR("No file"), FSTR("No path"), FSTR("Invalid name"), FSTR("Denied"), FSTR("Exist"), FSTR("Invalid object"), FSTR("Write protected"), FSTR("Invalid drive"), FSTR("Not enabled"), FSTR("No file system"), FSTR("Mkfs aborted"), FSTR("Timeout"), FSTR("Locked"), FSTR("Not enough core"), FSTR("Too many open files"), FSTR("Invalid parameter") }; const FLASH char * fat_rctostr(FRESULT rc) { return rc < ARRAY_SIZE(rc_strings) ? rc_strings[rc] : PSTR(" Unknown Error"); } static void swirl(void) { static const FLASH char swirlchar[] = { '-','\\','|','/' }; static uint_fast8_t cnt; static uint32_t tstamp; if (get_timer(0) > tstamp) { tstamp = get_timer(0) + 250; putchar('\b'); cnt = (cnt+1) & 3; putchar(swirlchar[cnt]); } } /* * pwd - Print current directory of the current drive. * */ command_ret_t do_pwd(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc UNUSED, char * const argv[] UNUSED) { FRESULT res; TCHAR buf[PATH_MAX]; res = f_getcwd(buf, PATH_MAX); /* Get current directory path */ if (res != FR_OK) cmd_error(CMD_RET_FAILURE, res, NULL); puts(buf); return CMD_RET_SUCCESS; } /* * cd - Change the current/working directory. * */ command_ret_t do_cd(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[]) { TCHAR *path; FRESULT res = FR_OK; if (argc < 2) { path = getenv_str(PSTR(ENV_HOME)); if (path == NULL) { cmd_error(CMD_RET_FAILURE, 0, PSTR("\"%S\" not set"), PSTR(ENV_HOME)); } } else path = argv[1]; if (strlen(path) > 1 && path[1] == ':') res = f_chdrive(path); if (res == FR_OK) res = f_chdir(path); if (res != FR_OK) cmd_error(CMD_RET_FAILURE, res, NULL); return CMD_RET_SUCCESS; } static void print_dirent(FILINFO *f) { printf_P(PSTR("%c%c%c%c%c %u/%02u/%02u %02u:%02u %9lu %s\n"), (f->fattrib & AM_DIR) ? 'D' : '-', (f->fattrib & AM_RDO) ? 'R' : '-', (f->fattrib & AM_HID) ? 'H' : '-', (f->fattrib & AM_SYS) ? 'S' : '-', (f->fattrib & AM_ARC) ? 'A' : '-', (f->fdate >> 9) + 1980, (f->fdate >> 5) & 15, f->fdate & 31, (f->ftime >> 11), (f->ftime >> 5) & 63, f->fsize, f->fname); } char *path_split(char *p) { if (isdigit(p[0]) && (p[1] == ':')) p += 2; char *ps = strrchr(p, '/'); if (ps) { if (ps == p) { ++ps; memmove(ps+1, ps, strlen(ps)+1); } *ps++ = '\0'; } return ps; } command_ret_t do_mkdir(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[]) { int ret = CMD_RET_SUCCESS; FRESULT res; if (argc < 1) return CMD_RET_USAGE; for (uint8_t i = 1; i < argc; i++) { if ((res = f_mkdir(argv[i])) != FR_OK) { ret = CMD_RET_FAILURE; cmd_error(0, res, PSTR("cannot create directory '%s'"), argv[i]); } } return ret; } /* * ls path - Directory listing * */ command_ret_t do_ls(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[]) { FATFS *fs; DIR dir; /* Directory object */ FILINFO finfo; unsigned long p1; unsigned int s1, s2; FRESULT res = FR_OK; char buf[PATH_MAX]; char *path = buf; char *pattern; memset(buf, 0, PATH_MAX); if (argc < 2) { buf[0] = '.'; } else { strncpy(path, argv[1], PATH_MAX-1); } pattern = path_split(path); debug_ls("### path, pattern: '%s', '%s'\n", path[0] ? path : "", pattern ? pattern : ""); if (pattern == NULL) { res = f_opendir(&dir, path); if (res == FR_NO_PATH || res == FR_INVALID_NAME) { pattern = path; path = "."; } else if (res != FR_OK) { cmd_error(CMD_RET_FAILURE, res, PSTR("'%s'"), path); } } debug_ls("### path, pattern: '%s', '%s'\n", path, pattern ? pattern : ""); if (pattern == NULL || *pattern == '\0') pattern = "*"; debug_ls("### path, pattern: '%s', '%s'\n", path, pattern ? pattern : ""); res = f_findfirst(&dir, &finfo, path, pattern); p1 = s1 = s2 = 0; for(;;) { if (res != FR_OK) cmd_error(CMD_RET_FAILURE, res, NULL); if (!finfo.fname[0]) break; if (finfo.fattrib & AM_DIR) { s2++; } else { s1++; p1 += finfo.fsize; } print_dirent(&finfo); if (check_abort()) break; res = f_findnext(&dir, &finfo); } if (res == FR_OK) { printf_P(PSTR("%4u File(s),%10lu bytes total\n%4u Dir(s)"), s1, p1, s2); if (f_getfree(path, (DWORD*)&p1, &fs) == FR_OK) printf_P(PSTR(", %10luK bytes free\n"), p1 * fs->csize / 2); } if (res) cmd_error(CMD_RET_FAILURE, res, NULL); return CMD_RET_SUCCESS; } typedef FRESULT (*fatfunc_t)(const TCHAR* path_old, const TCHAR* path_new); uint8_t cmd_flags; #define I_FLAG (1<<1) // prompt before overwrite (overrides a previous -n option) #define N_FLAG (1<<2) // do not overwrite an existing file (overrides a previous -i option) #define F_FLAG (1<<3) // overwrite existing file ignoring write protection #define P_FLAG (1<<4) // preserve attributes and timestamps #define V_FLAG (1<<5) // explain what is being done char* splitpath(const char* path) { char* fs = strrchr(path, '/'); char* bs = strrchr(path, '\\'); if (fs > bs) { *fs = '\0'; return fs + 1; } else if (bs != NULL) { *bs = '\0'; return bs + 1; } return NULL; } FRESULT ff_remove(const TCHAR *file, const TCHAR *dest UNUSED) { FRESULT res = FR_OK; debug_rm("==== ff_remove: '%s'\n", file); if (!(cmd_flags & N_FLAG)) { if ((res = f_unlink(file)) == FR_OK) { if (cmd_flags & V_FLAG) printf_P(PSTR("removed '%s'\n"), file); } else { cmd_error(0, res, PSTR("cannot remove '%s'"), file); } } else { printf_P(PSTR("not removed '%s'\n"), file); } return res; } void ff_iterate(fatfunc_t fatfunc, int count, char* const file[], char* dest) { FRESULT res = 0; DIR dir; FILINFO finfo; char srcpath[PATH_MAX], destpath[PATH_MAX]; uint8_t dest_is_dir = dest != NULL && f_stat(dest, &finfo) == FR_OK && finfo.fattrib & AM_DIR; for (uint8_t i = 0; i < count; i++) { char* pattern = NULL; strcpy(srcpath, file[i]); char* p1 = path_split(srcpath); if (p1 != NULL) { pattern = (char *) malloc(strlen(p1)+1); strcpy(pattern, p1); } debug_rm("### iter1 srcpath, pattern: '%s', '%s'\n", srcpath, pattern ? pattern : ""); if (pattern != NULL) { res = f_findfirst(&dir, &finfo, srcpath, pattern); p1 = srcpath+strlen(srcpath)-1; if (*p1++ != '/') *(p1++) = '/'; } else { res = f_findfirst(&dir, &finfo, ".", file[i]); p1 = srcpath; } if (finfo.fname[0] == 0) cmd_error(0, res, PSTR("cannot remove '%s': no such file or directory"), file[i]); while (res == FR_OK && finfo.fname[0] != 0) { strcpy(p1, finfo.fname); debug_rm("### iter3 srcpath: '%s'\n", srcpath); if (dest != NULL) { strcpy(destpath, dest); if (dest_is_dir) { strcat_P(destpath, PSTR("/")); strcat(destpath, finfo.fname); } } fatfunc(srcpath, destpath); res = f_findnext(&dir, &finfo); debug_rm("### iter4 res, .fname: %d, '%s'\n", res, finfo.fname); } res = f_closedir(&dir); free(pattern); } if (res != FR_OK) cmd_error(CMD_RET_FAILURE, res, PSTR("error enumerating files")); //printf_P(PSTR("error enumerating files: %S\n"), ffw_error(res)); } command_ret_t do_rm(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[]) { cmd_flags = 0; int opt; while ((opt = getopt(argc, argv, PSTR("nv"))) != -1) { switch (opt) { case 'n': cmd_flags |= N_FLAG; break; case 'v': cmd_flags |= V_FLAG; break; default: return CMD_RET_USAGE; break; } } argc -= optind; argv += optind; debug_rm("==== do_rm: argc, argv[0]: %d, '%s'\n", argc, argv[0]); if (argc < 1) return CMD_RET_USAGE; ff_iterate(ff_remove, argc, argv, NULL); return CMD_RET_SUCCESS; } /* * tst path - for debugging: test access with different functions * */ command_ret_t do_tst(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[]) { DIR Dir; /* Directory object */ FILINFO finfo; FRESULT res = FR_OK; char *path = ""; char *pattern = "*"; printf_P(PSTR("sizeof DIR: %u, sizeof FIL: %u\n"), sizeof (DIR), sizeof (FILINFO)); char * buf = (char *) malloc(BUFFER_SIZE); if (buf == NULL) { cmd_error(CMD_RET_FAILURE, ENOMEM, NULL); } res = f_getcwd(buf, BUFFER_SIZE); /* Get current directory path */ if (!res) { printf_P(PSTR("cwd: '%s'\n"), buf); } free(buf); if (res) cmd_error(CMD_RET_FAILURE, res, NULL); if (argc > 1) path = argv[1]; if (argc > 2) pattern = argv[2]; printf_P(PSTR("arg: '%s' '%s'\n"), path, pattern); printf_P(PSTR("==== f_stat: ")); res = f_stat(path, &finfo); cmd_error(0, res, NULL); if (res == FR_OK) { print_dirent(&finfo); } printf_P(PSTR("==== f_findfirst: ")); res = f_findfirst(&Dir, &finfo, path, pattern); /* Start to search for files */ cmd_error(CMD_RET_FAILURE, res, NULL); if (res == FR_OK) { print_dirent(&finfo); } f_closedir(&Dir); printf_P(PSTR("==== f_opendir: ")); res = f_opendir(&Dir, path); cmd_error(CMD_RET_FAILURE, res, NULL); f_closedir(&Dir); return CMD_RET_SUCCESS; } /* Work register for fs command */ struct stat_dat_s { DWORD AccSize; WORD AccFiles, AccDirs; FILINFO finfo; }; static FRESULT scan_files ( char *path, /* Pointer to the working buffer with start path */ struct stat_dat_s *statp ) { DIR dirs; FRESULT res; int i; char *fn; res = f_opendir(&dirs, path); swirl(); if (res == FR_OK) { i = strlen(path); while (((res = f_readdir(&dirs, &statp->finfo)) == FR_OK) && statp->finfo.fname[0]) { if (FF_FS_RPATH && statp->finfo.fname[0] == '.') continue; fn = statp->finfo.fname; if (statp->finfo.fattrib & AM_DIR) { statp->AccDirs++; path[i] = '/'; strcpy(path+i+1, fn); res = scan_files(path, statp); path[i] = '\0'; if (res != FR_OK) break; } else { //printf_P(PSTR("%s/%s\n"), path, fn); statp->AccFiles++; statp->AccSize += statp->finfo.fsize; } if (check_abort()) { res = 255; break; } } } return res; } /* * fatstat path - Show logical drive status * */ command_ret_t do_stat(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[]) { FATFS *fs; DWORD nfreeclst; FRESULT res; char *buf; char *path = ""; struct stat_dat_s statp; buf = (char *) malloc(PATH_MAX); if (buf == NULL) cmd_error(CMD_RET_FAILURE, ENOMEM, NULL); if (argc > 1) path = argv[1]; res = f_getfree(path, &nfreeclst, &fs); if (!res) { printf_P(PSTR( "FAT type: %u\n" "Bytes/Cluster: %lu\n" "Number of FATs: %u\n" "Root DIR entries: %u\n" "Sectors/FAT: %lu\n" "Number of clusters: %lu\n" "FAT start (lba): %lu\n" "DIR start (lba,cluster): %lu\n" "Data start (lba): %lu\n"), fs->fs_type, (DWORD)fs->csize * 512, fs->n_fats, fs->n_rootdir, fs->fsize, fs->n_fatent - 2, fs->fatbase, fs->dirbase, fs->database); #if FF_USE_LABEL DWORD serial; res = f_getlabel(path, buf, &serial); if (!res) { printf_P(PSTR( "Volume name: %s\n" "Volume S/N: %04X-%04X\n"), buf, (WORD)(serial >> 16), (WORD)(serial & 0xFFFF)); } #endif if (!res) { statp.AccSize = statp.AccFiles = statp.AccDirs = 0; strcpy(buf, path); my_puts_P(PSTR("\nCounting... ")); res = scan_files(buf, &statp); putchar('\r'); } if (!res) { printf_P(PSTR("%u files, %lu bytes.\n%u folders.\n" "%lu KB total disk space.\n%lu KB available.\n"), statp.AccFiles, statp.AccSize, statp.AccDirs, (fs->n_fatent - 2) * (fs->csize / 2), nfreeclst * (fs->csize / 2) ); } } free(buf); if (res) cmd_error(CMD_RET_FAILURE, res, NULL); return CMD_RET_SUCCESS; } /* * fatread/write - load binary file to/from a dos filesystem * read [bytes [pos]] * write */ command_ret_t do_rw(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[]) { FIL File; uint32_t addr; unsigned long bytes; unsigned long pos; unsigned long bytes_rw; bool dowrite = (argv[0][0] == 'w'); FRESULT res = FR_OK; bool buserr = 0; uint32_t timer; uint8_t *buffer; if (argc < (dowrite ? 4 : 3)) return CMD_RET_USAGE; addr = eval_arg(argv[2], NULL); if (addr >= MAX_MEMORY) cmd_error(CMD_RET_FAILURE, 0, PSTR("Address too high: %#lx"), addr); if (argc > 3) bytes = eval_arg(argv[3], NULL); else bytes = MAX_MEMORY; if (argc > 4) pos = eval_arg(argv[4], NULL); else pos = 0; if (addr + bytes > MAX_MEMORY) bytes = MAX_MEMORY - addr; buffer = (uint8_t *) malloc(BUFFER_SIZE); if (buffer == NULL) cmd_error(CMD_RET_FAILURE, ENOMEM, NULL); res = f_open(&File, argv[1], dowrite ? FA_WRITE | FA_CREATE_ALWAYS : FA_READ ); if (!res) { res = f_lseek(&File, pos); if (!res) { bytes_rw = 0; timer = get_timer(0); while (bytes) { unsigned int cnt, br; if (bytes >= BUFFER_SIZE) { cnt = BUFFER_SIZE; bytes -= BUFFER_SIZE; } else { cnt = bytes; bytes = 0; } if (dowrite) { if (!(z80_bus_cmd(Request) & ZST_ACQUIRED)) { buserr = 1; break; } z80_read_block(buffer, addr, cnt); z80_bus_cmd(Release); res = f_write(&File, buffer, cnt, &br); if (res != FR_OK) break; } else { res = f_read(&File, buffer, cnt, &br); if (res != FR_OK) break; if (!(z80_bus_cmd(Request) & ZST_ACQUIRED)) { buserr = 1; break; } z80_write_block(buffer, addr, br); z80_bus_cmd(Release); } addr += br; bytes_rw += br; if (cnt != br) { if (dowrite) printf_P(PSTR("Disk full?\n")); break; } if (check_abort()) break; } FRESULT fr = f_close(&File); if (!res) res = fr; timer = get_timer(timer); printf_P(PSTR("%lu (%#lx) bytes read/written with %lu bytes/sec.\n"), bytes_rw, bytes_rw, timer ? (bytes_rw * 1000 / timer) : 0); } } free(buffer); if (res) cmd_error(CMD_RET_FAILURE, res, PSTR("'%s'"), argv[1]); if (buserr) cmd_error(CMD_RET_FAILURE, EBUSTO, NULL); return CMD_RET_SUCCESS; } /* * command table for fat subcommands */ cmd_tbl_t cmd_tbl_fat[] = { CMD_TBL_ITEM( status, 2, CTBL_RPT, do_stat, "Show logical drive status", "dev" ), CMD_TBL_ITEM( pwd, 2, CTBL_RPT|CTBL_SUBCMDAUTO, do_pwd, "Print name of current/working directory", "" ), CMD_TBL_ITEM( cd, 2, 0|CTBL_SUBCMDAUTO, do_cd, "Change the current/working directory.", "path" ), CMD_TBL_ITEM( mkdir, CONFIG_SYS_MAXARGS, 0, do_mkdir, "Create the DIRECTORY(ies), if they do not already exist.", "DIRECTORY..." ), CMD_TBL_ITEM( ls, 2, CTBL_RPT|CTBL_SUBCMDAUTO, do_ls, "Directory listing", "path" ), CMD_TBL_ITEM( tst, 3, CTBL_DBG|CTBL_RPT, do_tst, "FatFS test function", "[path [pattern]]" ), CMD_TBL_ITEM( rm, CONFIG_SYS_MAXARGS, 0, do_rm, "Remove FILE(s)", "[OPTION]... [FILE]...\n" //" -i prompt before removal\n" " -v explain what is being done\n" "\n" "rm removes directories, if they are empty." ), CMD_TBL_ITEM( load, 5, 0, do_rw, "load binary file from a dos filesystem", " [bytes [pos]]\n" " - Load binary file 'path/filename' on logical drive 'd'\n" " to address 'addr' from dos filesystem.\n" " 'pos' gives the file position to start loading from.\n" " If 'pos' is omitted, 0 is used. 'pos' requires 'bytes'.\n" " 'bytes' gives the size to load. If 'bytes' is 0 or omitted,\n" " the load stops on end of file." ), CMD_TBL_ITEM( write, 4, 0, do_rw, "write file into a dos filesystem", " \n" " - Write file to 'path/filename' on logical drive 'd' from RAM\n" " starting at address 'addr'.\n" ), CMD_TBL_ITEM( help, CONFIG_SYS_MAXARGS, CTBL_RPT, do_help, "Print sub command description/usage", "\n" " - print brief description of all sub commands\n" "fat help command ...\n" " - print detailed usage of sub cmd 'command'" ), /* This does not use the CMD_TBL_ITEM macro as ? can't be used in symbol names */ {FSTR("?"), CONFIG_SYS_MAXARGS, 1, do_help, NULL, #ifdef CONFIG_SYS_LONGHELP FSTR(""), #endif /* CONFIG_SYS_LONGHELP */ NULL, #ifdef CONFIG_AUTO_COMPLETE NULL, #endif }, /* Mark end of table */ CMD_TBL_END(cmd_tbl_fat) }; command_ret_t do_fat(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc UNUSED, char * const argv[] UNUSED) { puts_P(PSTR("Huch?")); return CMD_RET_USAGE; }