+char *path_basename(PATH_T *p)
+{
+ char *basename;
+
+ basename = strrchr(p->p_path, '/');
+ return(basename ? basename + 1 : p->p_path);
+}
+
+
+static uint8_t flags;
+PATH_T *from;
+PATH_T *to;
+
+#define R_FLAG (1<<0) // copy directories recursively
+#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) // if an existing destination file cannot be opened, remove it and try again (this option is ignored when the -n option is also used)
+#define P_FLAG (1<<4) // preserve attributes and timestamps
+#define V_FLAG (1<<5) // explain what is being done
+
+static void
+setfile(FILINFO *fs, FIL *fd)
+{
+ (void) fs;(void) fd;
+#if 0 /* TODO: */
+ static struct timeval tv[2];
+
+ fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
+
+ tv[0].tv_sec = fs->st_atime;
+ tv[1].tv_sec = fs->st_mtime;
+ if (utimes(to->p_path, tv))
+ err(PSTR("utimes: %s: %s"), to->p_path, strerror(errno));
+#endif
+}
+
+void copy_file(FILINFO *fs, uint_fast8_t dne)
+{
+ static char buf[MAXBSIZE];
+ FIL from_fd, to_fd;
+ UINT rcount, wcount;
+ //FILINFO to_stat;
+ //char *p;
+ FRESULT fr;
+
+debug("==== copy_file(): dne: %u\n", dne);
+debug(" from:'%s' to:'%s'\n", from->p_path, to->p_path);
+
+ if ((fr = f_open(&from_fd, from->p_path, FA_READ)) != FR_OK) {
+ err(PSTR("%s: %S"), from->p_path, rctostr(fr));
+ return;
+ }
+
+ /*
+ * If the file exists and we're interactive, verify with the user.
+ * If the file DNE, set the mode to be the from file, minus setuid
+ * bits, modified by the umask; arguably wrong, but it makes copying
+ * executables work right and it's been that way forever. (The
+ * other choice is 666 or'ed with the execute bits on the from file
+ * modified by the umask.)
+ */
+ if (!dne) {
+ if (flags & I_FLAG) {
+ printf_P(PSTR("overwrite %s? "), to->p_path);
+ if (!confirm_yes()) {
+ f_close(&from_fd);
+ return;
+ }
+ }
+ fr = f_open(&to_fd, to->p_path, FA_WRITE|FA_CREATE_ALWAYS);
+ } else
+ fr = f_open(&to_fd, to->p_path, FA_WRITE|FA_CREATE_NEW);
+
+ if (fr != FR_OK) {
+ err(PSTR("%s: %S"), to->p_path, rctostr(fr));
+ f_close(&from_fd);
+ return;
+ }
+
+ while ((fr = f_read(&from_fd, buf, MAXBSIZE, &rcount)) == FR_OK &&
+ rcount > 0) {
+ fr = f_write(&to_fd, buf, rcount, &wcount);
+ if (fr || wcount < rcount) {
+ err(PSTR("%s: %S"), to->p_path, rctostr(fr));
+ break;
+ }
+ }
+ if (fr != FR_OK)
+ err(PSTR("%s: S"), from->p_path, rctostr(fr));
+
+ if (flags & P_FLAG)
+ setfile(fs, &to_fd);
+
+ f_close(&from_fd);
+ if ((fr = f_close(&to_fd)) != FR_OK)
+ err(PSTR("%s: %S"), to->p_path, rctostr(fr));
+}
+
+#if 1
+static void copy_dir(void)
+{
+debug("==== copy_dir()");
+debug(" from:'%s' to:'%s'\n", from->p_path, to->p_path);
+
+ printf_P(PSTR("directory copy not supported, ommitting dir '%s'\n"),
+ from->p_path);
+ command_ret = CMD_RET_FAILURE;
+}
+#else
+static void copy_dir(void)
+{
+ FILINFO from_stat;
+ struct dirent *dp, **dir_list;
+ int dir_cnt, i;
+ char *old_from, *old_to;
+
+debug("==== copy_file(): dne: %u\n", dne);
+debug(" from:'%s' to:'%s'\n", from->p_path, to->p_path);
+
+ dir_cnt = scandir(from->p_path, &dir_list, NULL, NULL);
+ if (dir_cnt == -1) {
+ (void)fprintf(stderr, "%s: can't read directory %s.\n",
+ progname, from->p_path);
+ command_ret = CMD_RET_FAILURE;
+ }
+
+ /*
+ * Instead of handling directory entries in the order they appear
+ * on disk, do non-directory files before directory files.
+ * There are two reasons to do directories last. The first is
+ * efficiency. Files tend to be in the same cylinder group as
+ * their parent, whereas directories tend not to be. Copying files
+ * all at once reduces seeking. Second, deeply nested tree's
+ * could use up all the file descriptors if we didn't close one
+ * directory before recursivly starting on the next.
+ */
+ /* copy files */
+ for (i = 0; i < dir_cnt; ++i) {
+ dp = dir_list[i];
+ if (dp->d_namlen <= 2 && dp->d_name[0] == '.'
+ && (dp->d_name[1] == NULL || dp->d_name[1] == '.'))
+ goto done;
+ if (!(old_from =
+ path_append(&from, dp->d_name, (int)dp->d_namlen)))
+ goto done;
+
+ if (statfcn(from->p_path, &from_stat) < 0) {
+ err(PSTR("%s: %s"), dp->d_name, strerror(errno));
+ path_restore(&from, old_from);
+ goto done;
+ }
+ if (S_ISDIR(from_stat.st_mode)) {
+ path_restore(&from, old_from);
+ continue;
+ }
+ if (old_to = path_append(&to, dp->d_name, (int)dp->d_namlen)) {
+ copy();
+ path_restore(&to, old_to);
+ }
+ path_restore(&from, old_from);
+done: dir_list[i] = NULL;
+ free(dp);
+ }
+
+ /* copy directories */
+ for (i = 0; i < dir_cnt; ++i) {
+ dp = dir_list[i];
+ if (!dp)
+ continue;
+ if (!(old_from =
+ path_append(&from, dp->d_name, (int)dp->d_namlen))) {
+ free(dp);
+ continue;
+ }
+ if (!(old_to =
+ path_append(&to, dp->d_name, (int)dp->d_namlen))) {
+ free(dp);
+ path_restore(&from, old_from);
+ continue;
+ }
+ copy();
+ free(dp);
+ path_restore(&from, old_from);
+ path_restore(&to, old_to);
+ }
+ free(dir_list);
+}
+#endif
+
+/*
+ * copy file or directory at "from" to "to".
+ */
+static void copy()
+{
+ FILINFO from_stat, to_stat;
+ uint_fast8_t dne;
+ FRESULT fr;
+
+debug("==== copy()\n");
+debug(" from:'%s' to:'%s'\n", from->p_path, to->p_path);
+
+ fr = f_stat(from->p_path, &from_stat);
+ if (fr != FR_OK) {
+ err(PSTR("%s: %S"), from->p_path, rctostr(fr));
+ return;
+ }
+
+ /* not an error, but need to remember it happened */
+ if (f_stat(to->p_path, &to_stat) != FR_OK)
+ dne = 1;
+ else {
+ if (strcmp(to->p_path, from->p_path) == 0) {
+ (void)printf_P(PSTR("%s and %s are identical (not copied).\n"),
+ to->p_path, from->p_path);
+ command_ret = CMD_RET_FAILURE;
+ return;
+ }
+ dne = 0;
+ }
+
+ if(from_stat.fattrib & AM_DIR) {
+ if (!(flags & R_FLAG)) {
+ (void)printf_P(PSTR("-r not specified; ommitting dir '%s'\n"),
+ from->p_path);
+ command_ret = CMD_RET_FAILURE;
+ return;
+ }
+ if (dne) {
+ /*
+ * If the directory doesn't exist, create the new one.
+ */
+ if ((fr = f_mkdir(to->p_path)) != FR_OK) {
+ err(PSTR("%s: %S"), to->p_path, rctostr(fr));
+ return;
+ }
+ }
+ else if (!(to_stat.fattrib & AM_DIR)) {
+ (void)printf_P(PSTR("%s: not a directory.\n"), to->p_path);
+ return;
+ }
+ copy_dir();
+ /*
+ * If not -p and directory didn't exist, set it to be the
+ * same as the from directory, umodified by the umask;
+ * arguably wrong, but it's been that way forever.
+ */
+#if 0
+ if (flags & P_FLAG)
+ setfile(&from_stat, 0);
+ else if (dne)
+ (void)chmod(to->p_path, from_stat.st_mode);
+#endif
+ return;
+ }
+ copy_file(&from_stat, dne);
+}
+
+command_ret_t do_cp(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
+{
+
+ FRESULT fr; /* Return value */
+ //DIR dj; /* Directory search object */
+ FILINFO to_stat; /* File information */
+ char *old_to;
+
+
+ uint8_t tflags = 0;
+ command_ret = CMD_RET_SUCCESS;
+
+ /* reset getopt() */
+ optind = 0;
+
+ int opt;
+ while ((opt = getopt(argc, argv, PSTR("rfipv"))) != -1) {
+ switch (opt) {
+ case 'f':
+ tflags &= ~I_FLAG;
+ tflags |= F_FLAG;
+ break;
+ case 'i':
+ tflags &= ~F_FLAG;
+ tflags |= I_FLAG;
+ break;
+ case 'p':
+ tflags |= P_FLAG;
+ break;
+ case 'r':
+ tflags |= R_FLAG;
+ break;
+ case 'v':
+ tflags |= V_FLAG;
+ break;
+ default:
+ return CMD_RET_USAGE;
+ break;
+ }
+ }
+ flags = tflags;
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 2)
+ return CMD_RET_USAGE;
+
+ from = (PATH_T *) malloc(sizeof(PATH_T));
+ to = (PATH_T *) malloc(sizeof(PATH_T));
+ if (from == NULL || to == NULL) {
+ printf_P(PSTR("cp: Out of Memory!\n"));
+ command_ret = CMD_RET_FAILURE;
+ goto cleanup;
+ }
+ from->p_end = from->p_path; *from->p_path = '\0';
+ to->p_end = to->p_path; *to->p_path = '\0';
+
+ /* consume last argument first. */
+ if (!path_set(to, argv[--argc])) {
+ command_ret = CMD_RET_FAILURE;
+ goto cleanup;
+ }
+
+ /*
+ * Cp has two distinct cases:
+ *
+ * % cp [-rip] source target
+ * % cp [-rip] source1 ... directory
+ *
+ * In both cases, source can be either a file or a directory.
+ *
+ * In (1), the target becomes a copy of the source. That is, if the
+ * source is a file, the target will be a file, and likewise for
+ * directories.
+ *
+ * In (2), the real target is not directory, but "directory/source".
+ */
+
+ fr = f_stat(to->p_path, &to_stat);
+debug("==== main, stat to: fr: %d, attr: %02x, flags:%02x\n", fr, to_stat.fattrib, flags);
+debug(" from:'%s' to:'%s'\n", from->p_path, to->p_path);
+
+ if (fr != FR_OK && fr != FR_NO_FILE && fr != FR_NO_PATH) {
+ err(PSTR("Test1: %s: %S"), to->p_path, rctostr(fr));
+ command_ret = CMD_RET_FAILURE;
+ goto cleanup;
+ }
+ if (!(fr == FR_OK && (to_stat.fattrib & AM_DIR))) {
+ /*
+ * Case (1). Target is not a directory.
+ */
+ if (argc > 1) {
+ err(PSTR("target '%s' is not a directory"), to->p_path);
+ //command_ret = CMD_RET_USAGE;
+ goto cleanup;
+ }
+ if (!path_set(from, *argv)) {
+ command_ret = CMD_RET_FAILURE;
+ goto cleanup;
+ }
+ copy();
+ }
+ else {
+ /*
+ * Case (2). Target is a directory.
+ */
+ for (;; ++argv) {
+ if (!path_set(from, *argv))
+ continue;
+ if (!(old_to = path_append(to, path_basename(from), -1)))
+ continue;
+ copy();
+ if (!--argc)
+ break;
+ path_restore(to, old_to);
+ }
+ }
+
+cleanup:
+ free(to);
+ free(from);
+
+ return command_ret;
+}
+
+#if 0
+ if (flags & V_FLAG)
+ printf_P((PSTR("%s %s -> %s\n", badcp ? : "ERR:" : " ", curr->fts_path, to->p_path)));
+
+#endif
+
+
+
+
+
+
+/******************************************************************************/
+
+/*
+ * Work register for stat 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 (_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(BUFFER_SIZE);
+ if (buf == NULL) {
+ printf_P(PSTR("fat stat: Out of Memory!\n"));
+ return CMD_RET_FAILURE;
+ }
+
+ 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 _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) {
+ my_puts_P(PSTR("\nCounting... "));
+ statp.AccSize = statp.AccFiles = statp.AccDirs = 0;
+ strcpy(buf, path);
+
+ res = scan_files(buf, &statp);
+ }
+ if (!res) {
+ printf_P(PSTR("\r%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) {
+ put_rc(res);
+ return CMD_RET_FAILURE;
+ }
+ return CMD_RET_SUCCESS;
+}
+
+/*
+ * fatread/write - load binary file to/from a dos filesystem
+ * read <d:/path/filename> <addr> [bytes [pos]]
+ * write <d:/path/filename> <addr> <bytes>
+ */
+command_ret_t do_rw(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])