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