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