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