]> cloudbase.mooo.com Git - z180-stamp.git/blob - avr/cmd_fat.c
039ec9039d753deb63cefc9c3bc7b2eb942f093f
[z180-stamp.git] / avr / cmd_fat.c
1 /*
2 * (C) Copyright 2014,2016,2018 Leo C. <erbl259-lmu@yahoo.de>
3 *
4 * SPDX-License-Identifier: GPL-2.0
5 */
6
7 /*
8 * FAT filesystem commands
9 */
10
11 #include "cmd_fat.h"
12 #include <util/delay.h>
13
14 #include "ff.h"
15 #include "z80-if.h"
16 #include "eval_arg.h"
17 #include "con-utils.h"
18 #include "print-utils.h"
19 #include "time.h"
20 #include "timer.h"
21 #include "debug.h"
22 #include "env.h"
23 #include "getopt-min.h"
24
25
26 #define DEBUG_CP 1 /* set to 1 to debug */
27 #define DEBUG_LS 1 /* set to 1 to debug */
28 #define DEBUG_RM 1 /* set to 1 to debug */
29 #define DEBUG_FA 1 /* set to 1 to debug */
30
31 #define debug_cp(fmt, args...) \
32 debug_cond(DEBUG_CP, fmt, ##args)
33 #define debug_ls(fmt, args...) \
34 debug_cond(DEBUG_LS, fmt, ##args)
35 #define debug_rm(fmt, args...) \
36 debug_cond(DEBUG_RM, fmt, ##args)
37 #define debug_fa(fmt, args...) \
38 debug_cond(DEBUG_FA, fmt, ##args)
39
40
41
42 /* TODO: use memory size test function (detect_ramsize() in cmd_loadihex.c) */
43 /* TODO: detect_ramsize() should be moved to z80-if.c */
44 #define MAX_MEMORY CONFIG_SYS_RAMSIZE_MAX
45 #define BUFFER_SIZE 512
46 #define MAX_PATHLEN CONFIG_SYS_MAX_PATHLEN
47
48
49 typedef struct {
50 char *p_end; /* pointer to NULL at end of path */
51 char p_path[MAX_PATHLEN + 1]; /* pointer to the start of a path */
52 } PATH_T;
53
54 /*
55 * Multible (fat) partitions per physical drive are not supported,
56 * but we have up to 2 sdcard slots.
57 */
58 FATFS FatFs0;
59 FATFS FatFs1;
60
61 uint8_t *blockbuf;
62 int blockbuf_size;
63 PATH_T from;
64 PATH_T to;
65 command_ret_t command_ret;
66 char *cmdname;
67
68 static uint8_t flags;
69 #define F_FLAG (1<<3) // overwrite existing file ignoring write protection
70 #define I_FLAG (1<<1) // prompt before overwrite (overrides a previous -n option)
71 #define N_FLAG (1<<2) // do not overwrite an existing file (overrides a previous -i option)
72 #define P_FLAG (1<<4) // preserve attributes and timestamps
73 #define R_FLAG (1<<0) // copy directories recursively
74 #define V_FLAG (1<<5) // explain what is being done
75
76
77
78 void setup_fatfs(void)
79 {
80 f_mount(&FatFs0, "0:", 0);
81 f_mount(&FatFs1, "1:", 0);
82 }
83
84 DWORD get_fattime (void)
85 {
86 time_t timer;
87 struct tm tm_timer;
88
89 time(&timer);
90 gmtime_r(&timer, &tm_timer);
91
92 return fatfs_time(&tm_timer);
93 }
94
95
96 static bool check_abort(void)
97 {
98 bool ret = ctrlc();
99
100 if (ret)
101 printf_P(PSTR("Abort\n"));
102
103 return ret;
104 }
105
106
107 static const FLASH char * const FLASH rc_strings[] = {
108 FSTR("OK"),
109 FSTR("disk error"),
110 FSTR("internal error"),
111 FSTR("not ready"),
112 FSTR("no file"),
113 FSTR("no path"),
114 FSTR("invalid name"),
115 FSTR("denied"),
116 FSTR("exist"),
117 FSTR("invalid object"),
118 FSTR("write protected"),
119 FSTR("invalid drive"),
120 FSTR("not enabled"),
121 FSTR("no file system"),
122 FSTR("mkfs aborted"),
123 FSTR("timeout"),
124 FSTR("locked"),
125 FSTR("not enough core"),
126 FSTR("too many open files"),
127 FSTR("invalid parameter")
128 };
129
130 static const FLASH char * const FLASH rc_names[] = {
131 FSTR("OK"),
132 FSTR("DISK_ERR"),
133 FSTR("INT_ERR"),
134 FSTR("NOT_READY"),
135 FSTR("NO_FILE"),
136 FSTR("NO_PATH"),
137 FSTR("INVALID_NAME"),
138 FSTR("DENIED"),
139 FSTR("EXIST"),
140 FSTR("INVALID_OBJECT"),
141 FSTR("WRITE_PROTECTED"),
142 FSTR("INVALID_DRIVE"),
143 FSTR("NOT_ENABLED"),
144 FSTR("NO_FILE_SYSTEM"),
145 FSTR("MKFS_ABORTED"),
146 FSTR("TIMEOUT"),
147 FSTR("LOCKED"),
148 FSTR("NOT_ENOUGH_CORE"),
149 FSTR("TOO_MANY_OPEN_FILES"),
150 FSTR("INVALID_PARAMETER")
151 };
152
153 static
154 void put_rc (FRESULT rc)
155 {
156 #if GCC_BUG_61443
157 printf_P(PSTR("rc=%u FR_"), rc);
158 my_puts_P(rc < ARRAY_SIZE(rc_names) ? rc_names[rc] : PSTR(" Unknown Error"));
159 my_puts_P(PSTR("\n"));
160 #else
161 printf_P(PSTR("rc=%u FR_%S\n"), rc,
162 rc < ARRAY_SIZE(rc_names) ? rc_names[rc] : PSTR(" Unknown Error"));
163 #endif
164 }
165
166 const FLASH char * rctostr(FRESULT rc)
167 {
168 return rc < ARRAY_SIZE(rc_strings) ? rc_strings[rc] : PSTR(" Unknown Error");
169 }
170
171 void err(const char *fmt, ...)
172 {
173 va_list ap;
174 va_start(ap, fmt);
175 printf_P(PSTR("fat %s: "), cmdname);
176 vfprintf_P(stdout, fmt, ap);
177 va_end(ap);
178 printf_P(PSTR("\n"));
179 _delay_ms(20);
180 command_ret = CMD_RET_FAILURE;
181 }
182
183 /******************************************************************************/
184
185 /*
186 * These functions manipulate paths in PATH_T structures.
187 *
188 * They eliminate multiple slashes in paths when they notice them,
189 * and keep the path non-slash terminated.
190 *
191 * Both path_set() and path_append() return 0 if the requested name
192 * would be too long.
193 */
194
195
196 static void path_init(void)
197 {
198 from.p_path[0] = '\0'; from.p_end = from.p_path;
199 to.p_path[0] = '\0'; to.p_end = to.p_path;
200 }
201
202 static char *path_skip_heading(char *p)
203 {
204 if ((p[0] & 0x38) == '0' && p[1] == ':') {
205 p += 2;
206 }
207 if (*p == '/')
208 ++p;
209
210 return p;
211 }
212
213 static void strip_trailing_slash(PATH_T *p)
214 {
215 char *beg = path_skip_heading(p->p_path);
216 char *end = p->p_end;
217
218 while (end > beg && end[-1] == '/')
219 *--end = '\0';
220
221 p->p_end =end;
222 }
223
224 /*
225 * Move specified string into path. Convert "" to "." to handle BSD
226 * semantics for a null path. Strip trailing slashes.
227 */
228 int
229 path_set(PATH_T *p, char *string)
230 {
231 if (strlen(string) > MAX_PATHLEN) {
232 err(PSTR("set: '%s': name too long"), string);
233 return 0;
234 }
235
236 (void)strcpy(p->p_path, string);
237 p->p_end = p->p_path + strlen(p->p_path);
238
239 if (p->p_path == p->p_end) {
240 *p->p_end++ = '.';
241 *p->p_end = '\0';
242 }
243
244 strip_trailing_slash(p);
245 return 1;
246 }
247
248 /*
249 * Append specified string to path, inserting '/' if necessary. Return a
250 * pointer to the old end of path for restoration.
251 */
252 char *
253 path_append(PATH_T *p, char *name)
254 {
255 char *old = p->p_end;
256 int len = strlen(name);
257
258 /* The "+ 1" accounts for the '/' between old path and name. */
259 if ((len + p->p_end - p->p_path + 1) > MAX_PATHLEN) {
260 err(PSTR("append: '%s/%s': name too long"), p->p_path, name);
261 return NULL;
262 }
263
264 /*
265 * This code should always be executed, since paths shouldn't
266 * end in '/'.
267 */
268 if (p->p_end[-1] != '/') {
269 *p->p_end++ = '/';
270 *p->p_end = '\0';
271 }
272
273 strncat(p->p_end, name, len);
274 p->p_end += len;
275 *p->p_end = '\0';
276
277 strip_trailing_slash(p);
278 return old;
279 }
280
281 /*
282 * Restore path to previous value. (As returned by path_append.)
283 */
284 void
285 path_restore(PATH_T *p, char *old)
286 {
287 p->p_end = old;
288 *p->p_end = '\0';
289 }
290
291 /*
292 * Return basename of path.
293 */
294 char *path_basename(PATH_T *p)
295 {
296 char *basename = strrchr(p->p_path, '/');
297
298 if (basename) {
299 ++basename;
300 } else {
301 basename = p->p_path;
302 if ((basename[0] & 0x38) == '0' && basename[1] == ':')
303 basename += 2;
304 }
305
306 return basename;
307 }
308
309 #if 0
310 char *path_basename_pattern(PATH_T *p)
311 {
312 char *pattern = path_basename(p);
313 if (strpbrk_P(pattern, PSTR("*?"))) {
314 memmove(pattern+1, pattern, strlen(pattern)+1);
315 *pattern++ = '\0';
316 } else {
317 //p->p_pattern = p->p_end + 1;
318 pattern = p->p_end + 1;
319 pattern[0] = '*';
320 pattern[1] = '\0';
321 }
322 return pattern;
323 }
324 #endif
325
326 /*
327 * Split path
328 * Return basename/pattern of path.
329 */
330
331 char *path_split_pattern(PATH_T *p)
332 {
333 char *pp = path_skip_heading(p->p_path);
334 char *pattern = strrchr(pp, '/');
335
336 if (pattern == NULL) {
337 pattern = pp;
338 p->p_end = pattern;
339 } else {
340 p->p_end = pattern;
341 pattern++;
342 }
343 memmove(pattern+2, pattern, strlen(pattern)+1);
344 pattern += 2;
345 *p->p_end = '\0' ;
346
347 return pattern;
348 }
349
350 void path_fix(PATH_T *p)
351 {
352 char *pp = path_skip_heading(p->p_path);
353
354 if (pp != p->p_end) {
355 *p->p_end++ = '/';
356 *p->p_end = '\0' ;
357 }
358 }
359
360 void path_unfix(PATH_T *p)
361 {
362 char *pp = path_skip_heading(p->p_path);
363
364 if (pp != p->p_end) {
365 *--p->p_end = '\0' ;
366 }
367 }
368
369 static void swirl(void)
370 {
371 static const FLASH char swirlchar[] = { '-','\\','|','/' };
372 static uint_fast8_t cnt;
373 static uint32_t tstamp;
374
375 if (get_timer(0) > tstamp) {
376 printf_P(PSTR("\b%c"), swirlchar[cnt]);
377 cnt = (cnt+1) % ARRAY_SIZE(swirlchar);
378 tstamp = get_timer(0) + 250;
379 }
380 }
381
382 /*
383 * pwd - Print current directory of the current drive.
384 *
385 */
386 command_ret_t do_pwd(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc UNUSED, char * const argv[] UNUSED)
387 {
388 FRESULT res;
389
390 cmdname = argv[0];
391 command_ret = CMD_RET_SUCCESS;
392
393 res = f_getcwd(from.p_path, MAX_PATHLEN); /* Get current directory path */
394
395 if (res == FR_OK)
396 puts(from.p_path);
397 else
398 err(PSTR("Error: %S"), rctostr(res));
399
400 return command_ret;
401 }
402
403
404 /*
405 * cd - Change the current/working directory.
406 *
407 */
408 command_ret_t do_cd(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
409 {
410 char *arg;
411 FRESULT res = FR_OK;
412
413 cmdname = argv[0];
414 command_ret = CMD_RET_SUCCESS;
415
416 if (argc < 2) {
417 arg = getenv_str(PSTR(ENV_HOME));
418 if (arg == NULL) {
419 err(PSTR("'%S' is not set"), PSTR(ENV_HOME));
420 return command_ret;
421 }
422 } else
423 arg = argv[1];
424
425 if (arg[1] == ':')
426 res = f_chdrive(arg);
427 if (res == FR_OK)
428 res = f_chdir(arg);
429 if (res != FR_OK)
430 err(PSTR("'%s': %S"), arg, rctostr(res));
431
432 return command_ret;
433 }
434
435
436 static int decode_arg(const char *arg)
437 {
438 BYTE attr = 0;
439 char c;
440
441 while ((c = *++arg) != '\0') {
442 switch (c) {
443 case 'a':
444 attr |= AM_ARC; /* Archive */
445 break;
446 case 'h':
447 attr |= AM_HID; /* Hidden */
448 break;
449 case 'r':
450 attr |= AM_RDO; /* Read only */
451 break;
452 case 's':
453 attr |= AM_SYS; /* System */
454 break;
455 default:
456 err(PSTR("unknown attribute: '%c'"), c);
457 return -1;
458 }
459 }
460 return attr;
461 }
462
463 static void print_attrib(char *path, FILINFO *f)
464 {
465 printf_P(PSTR("%c%c%c%c%c %s%s\n"),
466 (f->fattrib & AM_DIR) ? 'D' : '-',
467 (f->fattrib & AM_RDO) ? 'R' : '-',
468 (f->fattrib & AM_HID) ? 'H' : '-',
469 (f->fattrib & AM_SYS) ? 'S' : '-',
470 (f->fattrib & AM_ARC) ? 'A' : '-',
471 path, f->fname);
472 }
473
474 command_ret_t do_attrib(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, UNUSED char * const argv[])
475 {
476 DIR Dir; /* Directory object */
477 FILINFO Finfo;
478 FRESULT res;
479 BYTE set_mask = 0;
480 BYTE clear_mask = 0;
481
482 cmdname = argv[0];
483 command_ret = CMD_RET_SUCCESS;
484
485
486 for (;;) {
487 int attr;
488 char *arg = *++argv;
489
490 if (!arg)
491 return CMD_RET_USAGE;
492 if (arg[0] != '-' && arg[0] != '+')
493 break;
494 attr = decode_arg(arg);
495 if (attr < 0)
496 return CMD_RET_FAILURE;
497 if (arg[0] == '+')
498 set_mask |= attr;
499 else
500 clear_mask |= attr;
501 }
502
503 do {
504 if (!path_set(&from, *argv)) {
505 /* TODO: error out*/
506 }
507 char *pattern = path_split_pattern(&from);
508 if (*pattern == '\0')
509 pattern = "*";
510 debug_fa("==== path: '%s', pattern: '%s'\n", from.p_path ? from.p_path : "<NULL>", pattern ? pattern : "<NULL>");
511 res = f_findfirst(&Dir, &Finfo, from.p_path, pattern);
512 debug_fa("==== findfirst %d\n", res);
513 if (res != FR_OK || !Finfo.fname[0]) {
514 path_fix(&from);
515 err(PSTR("'%s%s': No such file or directory"), from.p_path, pattern);
516 } else {
517 do {
518 if (set_mask | clear_mask) {
519 if ((res = f_chmod(Finfo.fname, set_mask, set_mask | clear_mask)) != FR_OK) {
520 path_fix(&from);
521 err(PSTR("'%s%s': %S"), from.p_path, Finfo.fname, rctostr(res));
522 path_unfix(&from);
523 }
524 } else {
525 path_fix(&from);
526 print_attrib(from.p_path, &Finfo);
527 path_unfix(&from);
528 }
529
530 res = f_findnext(&Dir, &Finfo);
531 //debug_fa("==== findnext %d\n", res);
532 } while (res == FR_OK && Finfo.fname[0]);
533 }
534 f_closedir(&Dir);
535 } while (*++argv);
536
537 return command_ret;
538 }
539
540 command_ret_t do_rm(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
541 {
542 DIR Dir; /* Directory object */
543 FILINFO Finfo;
544 FRESULT res;
545
546 cmdname = argv[0];
547 command_ret = CMD_RET_SUCCESS;
548
549 /* reset getopt() */
550 optind = 0;
551 flags = 0;
552
553 int opt;
554 while ((opt = getopt(argc, argv, PSTR("nv"))) != -1) {
555 switch (opt) {
556 case 'n':
557 flags |= N_FLAG;
558 break;
559 case 'v':
560 flags |= V_FLAG;
561 break;
562 default:
563 return CMD_RET_USAGE;
564 break;
565 }
566 }
567 argc -= optind;
568 argv += optind;
569
570 if (argc < 1) {
571 err(PSTR("missing operand"));
572 } else {
573 for (int i = 0; i < argc; i++) {
574 if (!path_set(&from, argv[i])) {
575 /* TODO: error out*/
576 }
577 char *pattern = path_split_pattern(&from);
578
579 debug_rm("==== path: '%s', pattern: '%s'\n", from.p_path ? from.p_path : "<NULL>", pattern ? pattern : "<NULL>");
580
581 res = f_findfirst(&Dir, &Finfo, from.p_path, pattern);
582 debug_rm("==== findfirst %d\n", res);
583
584 if (res != FR_OK || !Finfo.fname[0]) {
585 path_fix(&from);
586 err(PSTR("cannot remove '%s%s': No such file or directory"), from.p_path, pattern);
587 } else {
588 do {
589 if (Finfo.fattrib & AM_DIR) {
590 path_fix(&from);
591 err(PSTR("cannot remove '%s%s': Is a directory"), from.p_path, Finfo.fname);
592 } else {
593 if (!(flags & N_FLAG)) {
594 if ((res = f_unlink(Finfo.fname)) == FR_OK) {
595 if (flags & V_FLAG)
596 path_fix(&from);
597 printf_P(PSTR("removed '%s%s'\n"), from.p_path, Finfo.fname);
598 path_unfix(&from);
599 } else {
600 path_fix(&from);
601 err(PSTR("cannot remove '%s%s': %S"), from.p_path, Finfo.fname, rctostr(res));
602 }
603 } else {
604 path_fix(&from);
605 printf_P(PSTR("not removed '%s%s'\n"), from.p_path, Finfo.fname);
606 path_unfix(&from);
607 }
608 }
609 res = f_findnext(&Dir, &Finfo);
610 //debug_rm("==== findnext %d\n", res);
611 } while (res == FR_OK && Finfo.fname[0]);
612 }
613 f_closedir(&Dir);
614 }
615
616 /* TODO */
617 if (res) {
618 put_rc(res);
619 return CMD_RET_FAILURE;
620 }
621 }
622 return command_ret;
623 }
624
625 command_ret_t do_rmdir(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
626 {
627 cmdname = argv[0];
628 command_ret = CMD_RET_SUCCESS;
629
630 return command_ret;
631 }
632
633 command_ret_t do_mkdir(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
634 {
635 cmdname = argv[0];
636 command_ret = CMD_RET_SUCCESS;
637
638 return command_ret;
639 }
640
641
642 static void print_dirent(FILINFO *f)
643 {
644 printf_P(PSTR("%c%c%c%c%c %u/%02u/%02u %02u:%02u %9lu %s\n"),
645 (f->fattrib & AM_DIR) ? 'D' : '-',
646 (f->fattrib & AM_RDO) ? 'R' : '-',
647 (f->fattrib & AM_HID) ? 'H' : '-',
648 (f->fattrib & AM_SYS) ? 'S' : '-',
649 (f->fattrib & AM_ARC) ? 'A' : '-',
650 (f->fdate >> 9) + 1980, (f->fdate >> 5) & 15, f->fdate & 31,
651 (f->ftime >> 11), (f->ftime >> 5) & 63,
652 f->fsize, f->fname);
653 }
654
655 /*
656 * ls path - Directory listing
657 *
658 */
659 command_ret_t do_ls(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
660 {
661 FATFS *fs;
662 DIR Dir; /* Directory object */
663 FILINFO Finfo;
664 unsigned long p1;
665 unsigned int s1, s2;
666 FRESULT res;
667
668 cmdname = argv[0];
669 command_ret = CMD_RET_SUCCESS;
670
671 path_init();
672 if (argc > 1)
673 if (!path_set(&from, argv[1])) {
674 /* TODO: error out*/
675 }
676
677 #if 0
678 char *pattern = path_basename_pattern(&from);
679 #else
680 char *pattern = path_split_pattern(&from);
681 if (*pattern == '\0')
682 pattern = "*";
683 #endif
684 debug_ls("==== path: '%s', pattern: '%s'\n", from.p_path ? from.p_path : "<NULL>", pattern ? pattern : "<NULL>");
685
686 p1 = s1 = s2 = 0;
687 res = f_findfirst(&Dir, &Finfo, from.p_path, pattern); /* Start to search for files */
688 if (res != FR_OK || !Finfo.fname[0]) {
689 path_fix(&from);
690 err(PSTR("'%s%s': No such file or directory"), from.p_path, pattern);
691 } else {
692 do {
693 if (Finfo.fattrib & AM_DIR) {
694 s2++;
695 } else {
696 s1++; p1 += Finfo.fsize;
697 }
698 print_dirent(&Finfo);
699 if (check_abort())
700 break;
701 res = f_findnext(&Dir, &Finfo);
702 } while (res == FR_OK && Finfo.fname[0]);
703 }
704 f_closedir(&Dir);
705
706 if (res == FR_OK && command_ret == CMD_RET_SUCCESS) {
707 printf_P(PSTR("%4u File(s),%10lu bytes total\n%4u Dir(s)"), s1, p1, s2);
708 if (f_getfree(from.p_path, (DWORD*)&p1, &fs) == FR_OK)
709 printf_P(PSTR(", %10luK bytes free\n"), p1 * fs->csize / 2);
710 }
711
712 if (res && command_ret == CMD_RET_SUCCESS) {
713 put_rc(res);
714 return CMD_RET_FAILURE;
715 }
716
717 return command_ret;
718 }
719
720 /*
721 * tst path - for debugging: test access with different functions
722 *
723 */
724 command_ret_t do_tst(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
725 {
726 DIR Dir; /* Directory object */
727 FILINFO Finfo;
728 FRESULT res = FR_OK;
729 char *path = "";
730 char *pattern = "*";
731
732 printf_P(PSTR("sizeof DIR: %u, sizeof FIL: %u\n"), sizeof (DIR), sizeof (FILINFO));
733
734 char * buf = (char *) malloc(BUFFER_SIZE);
735 if (buf == NULL) {
736 printf_P(PSTR("tst: Out of Memory!\n"));
737 return CMD_RET_FAILURE;
738 }
739 res = f_getcwd(buf, BUFFER_SIZE); /* Get current directory path */
740
741 if (!res) {
742 printf_P(PSTR("cwd: '%s'\n"), buf);
743 }
744 free(buf);
745 if (res) {
746 put_rc(res);
747 return CMD_RET_FAILURE;
748 }
749
750 if (argc > 1)
751 path = argv[1];
752 if (argc > 2)
753 pattern = argv[2];
754
755 printf_P(PSTR("arg: '%s' '%s'\n"), path, pattern);
756 printf_P(PSTR("==== f_stat: "));
757 res = f_stat(path, &Finfo);
758 put_rc(res);
759 if (res == FR_OK) {
760 print_dirent(&Finfo);
761 }
762
763 printf_P(PSTR("==== f_findfirst: "));
764 res = f_findfirst(&Dir, &Finfo, path, pattern); /* Start to search for files */
765 put_rc(res);
766 if (res == FR_OK) {
767 print_dirent(&Finfo);
768 }
769 f_closedir(&Dir);
770
771 printf_P(PSTR("==== f_opendir: "));
772 res = f_opendir(&Dir, path);
773 put_rc(res);
774 f_closedir(&Dir);
775
776 return CMD_RET_SUCCESS;
777 }
778
779 /******************************************************************************/
780
781 static void
782 setfile(FILINFO *fs)
783 {
784 FRESULT fr;
785
786 fr = f_utime(to.p_path, fs);
787 if (fr != FR_OK)
788 err(PSTR("f_utime: %s: %S"), to.p_path, rctostr(fr));
789 fr = f_chmod(to.p_path, fs->fattrib, AM_RDO|AM_ARC|AM_SYS|AM_HID);
790 if (fr != FR_OK)
791 err(PSTR("f_chmod: %s: %S"), to.p_path, rctostr(fr));
792
793 }
794
795 void copy_file(FILINFO *fs, uint_fast8_t dne)
796 {
797 FIL from_fd, to_fd;
798 UINT rcount, wcount;
799 FRESULT fr;
800 BYTE open_mode;
801
802 if (blockbuf == NULL) {
803 blockbuf_size = get_freemem() / 512 * 512;
804 if (blockbuf_size != 0)
805 blockbuf = (uint8_t *) malloc(blockbuf_size);
806 if (blockbuf == NULL) {
807 err(PSTR("Not enough memory!\n"));
808 return;
809 }
810 }
811
812 debug_cp("==== copy_file(): dne: %u, blockbuf_size: %d, freemem: %u\n", dne, blockbuf_size, get_freemem());
813 debug_cp(" from:'%s' to:'%s'\n", from.p_path, to.p_path);
814
815
816 if ((fr = f_open(&from_fd, from.p_path, FA_READ)) != FR_OK) {
817 err(PSTR("%s: %S"), from.p_path, rctostr(fr));
818 return;
819 }
820
821 /*
822 * If the file exists and we're interactive, verify with the user.
823 */
824 if (!dne) {
825 if (flags & N_FLAG) {
826 if (flags & V_FLAG)
827 printf_P(PSTR("%s not overwritten\n"), to.p_path);
828 f_close(&from_fd);
829 return;
830 } if (flags & I_FLAG) {
831 printf_P(PSTR("overwrite '%s'? "), to.p_path);
832 if (!confirm_yes()) {
833 f_close(&from_fd);
834 return;
835 }
836 }
837 if (flags & F_FLAG) {
838 /* Remove existing destination file name create a new file. */
839 f_chmod(to.p_path,0, AM_RDO);
840 f_unlink(to.p_path);
841 open_mode = FA_WRITE|FA_CREATE_NEW;
842 } else {
843 /* Overwrite existing destination file name. */
844 open_mode = FA_WRITE|FA_CREATE_ALWAYS;
845 }
846 } else {
847 open_mode = FA_WRITE|FA_CREATE_NEW;
848 }
849 fr = f_open(&to_fd, to.p_path, open_mode);
850
851 if (fr != FR_OK) {
852 err(PSTR("%s: %S"), to.p_path, rctostr(fr));
853 f_close(&from_fd);
854 return;
855 }
856
857 while ((fr = f_read(&from_fd, blockbuf, blockbuf_size, &rcount)) == FR_OK &&
858 rcount > 0) {
859 fr = f_write(&to_fd, blockbuf, rcount, &wcount);
860 if (fr || wcount < rcount) {
861 err(PSTR("%s: %S"), to.p_path, rctostr(fr));
862 break;
863 }
864 }
865 if (fr != FR_OK)
866 err(PSTR("%s: S"), from.p_path, rctostr(fr));
867
868 f_close(&from_fd);
869 if ((fr = f_close(&to_fd)) != FR_OK)
870 err(PSTR("%s: %S"), to.p_path, rctostr(fr));
871
872 if (flags & P_FLAG)
873 setfile(fs);
874 }
875
876 static void copy();
877
878 static void copy_dir(void)
879 {
880 DIR Dir; /* Directory object */
881 FILINFO Finfo;
882 char *old_from, *old_to;
883 FRESULT res;
884 char *pattern = {"*"};
885
886 debug_cp("==== copy_dir(): freemem: %u\n", get_freemem());
887 debug_cp(" from:'%s' to:'%s'\n", from.p_path, to.p_path);
888
889 #if 0
890
891 printf_P(PSTR("directory copy not supported, ommitting dir '%s'\n"),
892 from->p_path);
893 command_ret = CMD_RET_FAILURE;
894
895 #else
896
897 for (res = f_findfirst(&Dir, &Finfo, from.p_path, pattern);
898 res == FR_OK && Finfo.fname[0];
899 res = f_findnext(&Dir, &Finfo)) {
900
901 if (!(Finfo.fattrib & AM_DIR) &&
902 (old_from = path_append(&from, Finfo.fname))) {
903 if ((old_to = path_append(&to, Finfo.fname))) {
904 copy();
905 path_restore(&to, old_to);
906 }
907 path_restore(&from, old_from);
908 }
909 }
910
911 for (res = f_findfirst(&Dir, &Finfo, from.p_path, pattern);
912 res == FR_OK && Finfo.fname[0];
913 res = f_findnext(&Dir, &Finfo)) {
914
915 if ((Finfo.fattrib & AM_DIR) &&
916 (old_from = path_append(&from, Finfo.fname))) {
917 if ((old_to = path_append(&to, Finfo.fname))) {
918 copy();
919 path_restore(&to, old_to);
920 }
921 path_restore(&from, old_from);
922 }
923 }
924 }
925 #endif
926
927 /*
928 * copy file or directory at "from" to "to".
929 */
930 static void copy()
931 {
932 FILINFO from_stat, to_stat;
933 uint_fast8_t dne;
934 FRESULT fr;
935
936 debug_cp("==== copy(); freemem: %u\n", get_freemem());
937 debug_cp(" from:'%s' to:'%s'\n", from.p_path, to.p_path);
938
939 fr = f_stat(from.p_path, &from_stat);
940 if (fr != FR_OK) {
941 err(PSTR("%s: %S"), from.p_path, rctostr(fr));
942 return;
943 }
944
945 /* not an error, but need to remember it happened */
946 if (f_stat(to.p_path, &to_stat) != FR_OK)
947 dne = 1;
948 else {
949 if (strcmp(to.p_path, from.p_path) == 0) {
950 (void)printf_P(PSTR("%s and %s are identical (not copied).\n"),
951 to.p_path, from.p_path);
952 command_ret = CMD_RET_FAILURE;
953 return;
954 }
955 dne = 0;
956 }
957
958 if(from_stat.fattrib & AM_DIR) {
959 if (!(flags & R_FLAG)) {
960 (void)printf_P(PSTR("-r not specified; ommitting dir '%s'\n"),
961 from.p_path);
962 command_ret = CMD_RET_FAILURE;
963 return;
964 }
965 if (dne) {
966 /*
967 * If the directory doesn't exist, create the new one.
968 */
969 if ((fr = f_mkdir(to.p_path)) != FR_OK) {
970 err(PSTR("%s: %S"), to.p_path, rctostr(fr));
971 return;
972 }
973 } else if (!(to_stat.fattrib & AM_DIR)) {
974 (void)printf_P(PSTR("%s: not a directory.\n"), to.p_path);
975 return;
976 }
977 copy_dir();
978 if (flags & P_FLAG)
979 setfile(&from_stat);
980
981 return;
982 }
983 copy_file(&from_stat, dne);
984 }
985
986 command_ret_t do_cp(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
987 {
988
989 FRESULT fr; /* Return value */
990 //DIR dj; /* Directory search object */
991 FILINFO to_stat; /* File information */
992 char *old_to;
993
994
995 cmdname = argv[0];
996 uint8_t tflags = 0;
997 command_ret = CMD_RET_SUCCESS;
998
999 /* reset getopt() */
1000 optind = 0;
1001
1002 int opt;
1003 while ((opt = getopt(argc, argv, PSTR("finprv"))) != -1) {
1004 switch (opt) {
1005 case 'f':
1006 tflags &= ~(I_FLAG | N_FLAG);
1007 tflags |= F_FLAG;
1008 break;
1009 case 'i':
1010 tflags &= ~(F_FLAG | N_FLAG);
1011 tflags |= I_FLAG;
1012 break;
1013 case 'n':
1014 tflags &= ~(F_FLAG | I_FLAG);
1015 tflags |= N_FLAG;
1016 break;
1017 case 'p':
1018 tflags |= P_FLAG;
1019 break;
1020 case 'r':
1021 tflags |= R_FLAG;
1022 break;
1023 case 'v':
1024 tflags |= V_FLAG;
1025 break;
1026 default:
1027 return CMD_RET_USAGE;
1028 break;
1029 }
1030 }
1031 flags = tflags;
1032 argc -= optind;
1033 argv += optind;
1034
1035 if (argc < 2)
1036 return CMD_RET_USAGE;
1037
1038 path_init();
1039
1040 /* last argument is destination */
1041 if (!path_set(&to, argv[--argc]))
1042 goto cleanup;
1043
1044 /*
1045 * Cp has two distinct cases:
1046 *
1047 * % cp [-rip] source target
1048 * % cp [-rip] source1 ... directory
1049 *
1050 * In both cases, source can be either a file or a directory.
1051 *
1052 * In (1), the target becomes a copy of the source. That is, if the
1053 * source is a file, the target will be a file, and likewise for
1054 * directories.
1055 *
1056 * In (2), the real target is not directory, but "directory/source".
1057 */
1058
1059 fr = f_stat(to.p_path, &to_stat);
1060 debug_cp("==== main, stat to: fr: %d, attr: %02x, flags:%02x, freemem: %u\n",
1061 fr, to_stat.fattrib, flags, get_freemem());
1062 debug_cp(" from:'%s' to:'%s'\n", from.p_path, to.p_path);
1063
1064 if (fr != FR_OK && fr != FR_NO_FILE && fr != FR_NO_PATH) {
1065 err(PSTR("Test1: %s: %S"), to.p_path, rctostr(fr));
1066 command_ret = CMD_RET_FAILURE;
1067 goto cleanup;
1068 }
1069 if (!(fr == FR_OK && (to_stat.fattrib & AM_DIR))) {
1070 /*
1071 * Case (1). Target is not a directory.
1072 */
1073 if (argc > 1) {
1074 err(PSTR("target '%s' is not a directory"), to.p_path);
1075 //command_ret = CMD_RET_USAGE;
1076 goto cleanup;
1077 }
1078 if (!path_set(&from, *argv)) {
1079 command_ret = CMD_RET_FAILURE;
1080 goto cleanup;
1081 }
1082 copy();
1083 }
1084 else {
1085 /*
1086 * Case (2). Target is a directory.
1087 */
1088 for (;; ++argv) {
1089 if (!path_set(&from, *argv))
1090 continue;
1091 if (!(old_to = path_append(&to, path_basename(&from))))
1092 continue;
1093 copy();
1094 if (!--argc)
1095 break;
1096 path_restore(&to, old_to);
1097 }
1098 }
1099
1100 cleanup:
1101 free(blockbuf);
1102 blockbuf = NULL;
1103 blockbuf_size = 0;
1104
1105 return command_ret;
1106 }
1107
1108 #if 0
1109 if (flags & V_FLAG)
1110 printf_P((PSTR("%s %s -> %s\n", badcp ? : "ERR:" : " ", curr->fts_path, to.p_path)));
1111
1112 #endif
1113
1114
1115 /******************************************************************************/
1116
1117 /*
1118 * Work register for stat command
1119 */
1120 struct stat_dat_s {
1121 DWORD AccSize;
1122 WORD AccFiles, AccDirs;
1123 FILINFO Finfo;
1124 };
1125
1126 static
1127 FRESULT scan_files (
1128 char *path, /* Pointer to the working buffer with start path */
1129 struct stat_dat_s *statp
1130 )
1131 {
1132 DIR dirs;
1133 FRESULT res;
1134 int i;
1135 char *fn;
1136
1137 res = f_opendir(&dirs, path);
1138 swirl();
1139 if (res == FR_OK) {
1140 i = strlen(path);
1141 while (((res = f_readdir(&dirs, &statp->Finfo)) == FR_OK) &&
1142 statp->Finfo.fname[0]) {
1143 if (_FS_RPATH && statp->Finfo.fname[0] == '.')
1144 continue;
1145 fn = statp->Finfo.fname;
1146 if (statp->Finfo.fattrib & AM_DIR) {
1147 statp->AccDirs++;
1148 path[i] = '/';
1149 strcpy(path+i+1, fn);
1150 res = scan_files(path, statp);
1151 path[i] = '\0';
1152 if (res != FR_OK)
1153 break;
1154 } else {
1155 //printf_P(PSTR("%s/%s\n"), path, fn);
1156 statp->AccFiles++;
1157 statp->AccSize += statp->Finfo.fsize;
1158 }
1159 if (check_abort()) {
1160 res = 255;
1161 break;
1162 }
1163 }
1164 }
1165
1166 return res;
1167 }
1168
1169
1170 /*
1171 * fatstat path - Show logical drive status
1172 *
1173 */
1174 command_ret_t do_stat(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
1175 {
1176 FATFS *fs;
1177 DWORD nfreeclst;
1178 FRESULT res;
1179 char *buf;
1180 char *path = "";
1181 struct stat_dat_s statp;
1182
1183 buf = (char *) malloc(BUFFER_SIZE);
1184 if (buf == NULL) {
1185 printf_P(PSTR("fat stat: Out of Memory!\n"));
1186 return CMD_RET_FAILURE;
1187 }
1188
1189 if (argc > 1)
1190 path = argv[1];
1191 res = f_getfree(path, &nfreeclst, &fs);
1192 if (!res) {
1193 printf_P(PSTR(
1194 "FAT type: %u\n"
1195 "Bytes/Cluster: %lu\n"
1196 "Number of FATs: %u\n"
1197 "Root DIR entries: %u\n"
1198 "Sectors/FAT: %lu\n"
1199 "Number of clusters: %lu\n"
1200 "FAT start (lba): %lu\n"
1201 "DIR start (lba,cluster): %lu\n"
1202 "Data start (lba): %lu\n"),
1203 fs->fs_type, (DWORD)fs->csize * 512, fs->n_fats,
1204 fs->n_rootdir, fs->fsize, fs->n_fatent - 2,
1205 fs->fatbase, fs->dirbase, fs->database);
1206
1207 #if _USE_LABEL
1208 DWORD serial;
1209 res = f_getlabel(path, buf, &serial);
1210 if (!res) {
1211 printf_P(PSTR(
1212 "Volume name: %s\n"
1213 "Volume S/N: %04X-%04X\n"),
1214 buf, (WORD)(serial >> 16), (WORD)(serial & 0xFFFF));
1215 }
1216 #endif
1217 if (!res) {
1218 my_puts_P(PSTR("\nCounting... "));
1219 statp.AccSize = statp.AccFiles = statp.AccDirs = 0;
1220 strcpy(buf, path);
1221
1222 res = scan_files(buf, &statp);
1223 }
1224 if (!res) {
1225 printf_P(PSTR("\r%u files, %lu bytes.\n%u folders.\n"
1226 "%lu KB total disk space.\n%lu KB available.\n"),
1227 statp.AccFiles, statp.AccSize, statp.AccDirs,
1228 (fs->n_fatent - 2) * (fs->csize / 2), nfreeclst * (fs->csize / 2)
1229 );
1230 }
1231 }
1232
1233 free(buf);
1234 if (res) {
1235 put_rc(res);
1236 return CMD_RET_FAILURE;
1237 }
1238 return CMD_RET_SUCCESS;
1239 }
1240
1241 /*
1242 * fatread/write - load binary file to/from a dos filesystem
1243 * read <d:/path/filename> <addr> [bytes [pos]]
1244 * write <d:/path/filename> <addr> <bytes>
1245 */
1246 command_ret_t do_rw(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
1247 {
1248 FIL File;
1249 uint32_t addr;
1250 unsigned long bytes;
1251 unsigned long pos;
1252 unsigned long bytes_rw;
1253
1254 bool dowrite = (argv[0][0] == 'w');
1255 FRESULT res = FR_OK;
1256 bool buserr = 0;
1257 uint32_t timer;
1258 uint8_t *buffer;
1259
1260 if (argc < (dowrite ? 4 : 3))
1261 return CMD_RET_USAGE;
1262
1263 addr = eval_arg(argv[2], NULL);
1264 if (addr >= MAX_MEMORY) {
1265 printf_P(PSTR("address too high: 0x%0lx\n"), addr);
1266 return CMD_RET_FAILURE;
1267 }
1268 if (argc > 3)
1269 bytes = eval_arg(argv[3], NULL);
1270 else
1271 bytes = MAX_MEMORY;
1272 if (argc > 4)
1273 pos = eval_arg(argv[4], NULL);
1274 else
1275 pos = 0;
1276
1277 if (addr + bytes > MAX_MEMORY)
1278 bytes = MAX_MEMORY - addr;
1279
1280 buffer = (uint8_t *) malloc(BUFFER_SIZE);
1281 if (buffer == NULL) {
1282 printf_P(PSTR("fatstat: Out of Memory!\n"));
1283 free(buffer);
1284 return CMD_RET_FAILURE;
1285 }
1286
1287 if (!res) {
1288 res = f_open(&File, argv[1], dowrite ? FA_WRITE | FA_CREATE_ALWAYS
1289 : FA_READ );
1290
1291 if (!res) {
1292 res = f_lseek(&File, pos);
1293 if (!res) {
1294 bytes_rw = 0;
1295 timer = get_timer(0);
1296 while (bytes) {
1297 unsigned int cnt, br;
1298
1299 if (bytes >= BUFFER_SIZE) {
1300 cnt = BUFFER_SIZE;
1301 bytes -= BUFFER_SIZE;
1302 } else {
1303 cnt = bytes; bytes = 0;
1304 }
1305 if (dowrite) {
1306 if (!(z80_bus_cmd(Request) & ZST_ACQUIRED)) {
1307 buserr = 1;
1308 break;
1309 }
1310 z80_read_block(buffer, addr, cnt);
1311 z80_bus_cmd(Release);
1312 res = f_write(&File, buffer, cnt, &br);
1313 if (res != FR_OK)
1314 break;
1315 } else {
1316 res = f_read(&File, buffer, cnt, &br);
1317 if (res != FR_OK)
1318 break;
1319 if (!(z80_bus_cmd(Request) & ZST_ACQUIRED)) {
1320 buserr = 1;
1321 break;
1322 }
1323 z80_write_block(buffer, addr, br);
1324 z80_bus_cmd(Release);
1325 }
1326 addr += br;
1327
1328 bytes_rw += br;
1329 if (cnt != br) {
1330 if (dowrite)
1331 printf_P(PSTR("Disk full?\n"));
1332 break;
1333 }
1334 if (check_abort())
1335 break;
1336 }
1337
1338 FRESULT fr = f_close(&File);
1339 if (!res)
1340 res = fr;
1341 timer = get_timer(timer);
1342 printf_P(PSTR("%lu (0x%lx) bytes read/written with %lu bytes/sec.\n"),
1343 bytes_rw, bytes_rw, timer ? (bytes_rw * 1000 / timer) : 0);
1344 }
1345 }
1346 }
1347
1348 free(buffer);
1349
1350 if (buserr)
1351 my_puts_P(PSTR("Bus timeout\n"));
1352 if (res)
1353 put_rc(res);
1354 if (buserr || res)
1355 return CMD_RET_FAILURE;
1356
1357 return CMD_RET_SUCCESS;
1358 }
1359
1360 /*
1361 * command table for fat subcommands
1362 */
1363
1364 cmd_tbl_t cmd_tbl_fat[] = {
1365 CMD_TBL_ITEM(
1366 stat, 2, CTBL_RPT, do_stat,
1367 "Show logical drive status",
1368 "dev"
1369 ),
1370 CMD_TBL_ITEM(
1371 pwd, 1, CTBL_RPT, do_pwd,
1372 "Print name of current/working directory",
1373 ""
1374 ),
1375 CMD_TBL_ITEM(
1376 attrib, CONFIG_SYS_MAXARGS, 0, do_attrib,
1377 "Display or change attributes on a FAT filesystem",
1378 "[+-ahrs] files...\n"
1379 "\n"
1380 " - Clear attributes\n"
1381 " + Set attributes\n"
1382 " a Archive\n"
1383 " h Hidden\n"
1384 " r Read only\n"
1385 " s System\n"
1386 "Display only:\n"
1387 " v Volume label\n"
1388 " d Directory\n"
1389 ),
1390 CMD_TBL_ITEM(
1391 cd, 2, 0, do_cd,
1392 "Change the current/working directory.",
1393 "path"
1394 ),
1395 CMD_TBL_ITEM(
1396 rm, CONFIG_SYS_MAXARGS, 0, do_rm,
1397 "Remove FILE(s)",
1398 "[OPTION]... [FILE]...\n"
1399 //" -i prompt before removal\n"
1400 " -v explain what is being done\n"
1401 "\n"
1402 "rm does not remove directories."
1403 ),
1404 CMD_TBL_ITEM(
1405 rmdir, CONFIG_SYS_MAXARGS, 0, do_rmdir,
1406 "Remove the DIRECTORY(ies), if they are empty",
1407 "[OPTION]... DIRECTORY..."
1408 ),
1409 CMD_TBL_ITEM(
1410 mkdir, CONFIG_SYS_MAXARGS, 0, do_mkdir,
1411 "Create the DIRECTORY(ies), if they do not already exist.",
1412 "[OPTION]... DIRECTORY..."
1413 ),
1414 CMD_TBL_ITEM(
1415 ls, 2, CTBL_RPT, do_ls,
1416 "Directory listing",
1417 "path"
1418 ),
1419 CMD_TBL_ITEM(
1420 tst, 3, CTBL_DBG|CTBL_RPT, do_tst,
1421 "FatFS test function",
1422 "path"
1423 ),
1424 CMD_TBL_ITEM(
1425 load, 5, 0, do_rw,
1426 "load binary file from a dos filesystem",
1427 "<d:/path/filename> <addr> [bytes [pos]]\n"
1428 " - Load binary file 'path/filename' on logical drive 'd'\n"
1429 " to address 'addr' from dos filesystem.\n"
1430 " 'pos' gives the file position to start loading from.\n"
1431 " If 'pos' is omitted, 0 is used. 'pos' requires 'bytes'.\n"
1432 " 'bytes' gives the size to load. If 'bytes' is 0 or omitted,\n"
1433 " the load stops on end of file."
1434 ),
1435 CMD_TBL_ITEM(
1436 write, 4, 0, do_rw,
1437 "write file into a dos filesystem",
1438 "<d:/path/filename> <addr> <bytes>\n"
1439 " - Write file to 'path/filename' on logical drive 'd' from RAM\n"
1440 " starting at address 'addr'.\n"
1441 ),
1442
1443 CMD_TBL_ITEM(
1444 cp, CONFIG_SYS_MAXARGS, CTBL_DBG, do_cp,
1445 "copy files",
1446 "[-f | -i | -n] [-prv] source_file target_file\n"
1447 // "[-f | -i | -n] [-prv] source_file target_file\n"
1448 // "cp [-f | -i | -n] [-prv] source_file ... target_dir\n"
1449 " -f overwrite existing file ignoring write protection\n"
1450 " this option is ignored when the -n option is also used\n"
1451 " -i prompt before overwrite (overrides a previous -n option)\n"
1452 " -n do not overwrite an existing file (overrides a previous -i option)\n"
1453 " -p preserve attributes and timestamps\n"
1454 " -r copy directories recursively\n"
1455 " -v explain what is being done\n"
1456 ),
1457
1458 CMD_TBL_ITEM(
1459 help, CONFIG_SYS_MAXARGS, CTBL_RPT, do_help,
1460 "Print sub command description/usage",
1461 "\n"
1462 " - print brief description of all sub commands\n"
1463 "fat help command ...\n"
1464 " - print detailed usage of sub cmd 'command'"
1465 ),
1466
1467 /* This does not use the CMD_TBL_ITEM macro as ? can't be used in symbol names */
1468 {FSTR("?"), CONFIG_SYS_MAXARGS, 1, do_help,
1469 FSTR("Alias for 'help'"),
1470 #ifdef CONFIG_SYS_LONGHELP
1471 FSTR(""),
1472 #endif /* CONFIG_SYS_LONGHELP */
1473 NULL,
1474 #ifdef CONFIG_AUTO_COMPLETE
1475 NULL,
1476 #endif
1477 },
1478 /* Mark end of table */
1479 CMD_TBL_END(cmd_tbl_fat)
1480 };
1481
1482
1483 command_ret_t do_fat(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc UNUSED, char * const argv[] UNUSED)
1484 {
1485 puts_P(PSTR("Huch?"));
1486
1487 return CMD_RET_USAGE;
1488 }