]> cloudbase.mooo.com Git - z180-stamp.git/blob - avr/cmd_fat.c
wildcards for ls command (*, ?)
[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 "common.h"
12 #include "cmd_fat.h"
13 #include <time.h>
14 uint32_t fat_time(const struct tm * timeptr);
15
16 #include "ff.h"
17 #include "z80-if.h"
18 #include "eval_arg.h"
19 #include "con-utils.h"
20 #include "print-utils.h"
21 #include "timer.h"
22 #include "debug.h"
23 #include "env.h"
24
25 #define DEBUG_FA 0 /* set to 1 to debug */
26 #define DEBUG_LS 1 /* set to 1 to debug */
27 #define DEBUG_RM 1 /* set to 1 to debug */
28
29 #define debug_fa(fmt, args...) \
30 debug_cond(DEBUG_FA, fmt, ##args)
31 #define debug_ls(fmt, args...) \
32 debug_cond(DEBUG_LS, fmt, ##args)
33 #define debug_rm(fmt, args...) \
34 debug_cond(DEBUG_RM, fmt, ##args)
35
36
37 /* TODO: use memory size test function (z80_memsize_detect() ) */
38 #define MAX_MEMORY CONFIG_SYS_RAMSIZE_MAX
39 #define BUFFER_SIZE FF_MAX_SS
40 #define PATH_MAX CONFIG_SYS_MAX_PATHLEN
41
42
43 /*
44 * Multible (fat) partitions per physical drive are not supported,
45 * but we have up to 2 sdcard slots.
46 */
47 static FATFS FatFs0;
48 static FATFS FatFs1;
49
50 void setup_fatfs(void)
51 {
52 f_mount(&FatFs0, "0:", 0);
53 f_mount(&FatFs1, "1:", 0);
54 }
55
56 DWORD get_fattime (void)
57 {
58 time_t timer;
59 struct tm tm_timer;
60
61 time(&timer);
62 gmtime_r(&timer, &tm_timer);
63
64 return fat_time(&tm_timer);
65 }
66
67
68 static
69 bool check_abort(void)
70 {
71 bool ret = ctrlc();
72
73 if (ret)
74 printf_P(PSTR("Abort\n"));
75
76 return ret;
77 }
78
79
80 static const FLASH char * const FLASH rc_strings[] = {
81 FSTR("Success"),
82 FSTR("Disk error"),
83 FSTR("Internal error"),
84 FSTR("Not ready"),
85 FSTR("No file"),
86 FSTR("No path"),
87 FSTR("Invalid name"),
88 FSTR("Denied"),
89 FSTR("Exist"),
90 FSTR("Invalid object"),
91 FSTR("Write protected"),
92 FSTR("Invalid drive"),
93 FSTR("Not enabled"),
94 FSTR("No file system"),
95 FSTR("Mkfs aborted"),
96 FSTR("Timeout"),
97 FSTR("Locked"),
98 FSTR("Not enough core"),
99 FSTR("Too many open files"),
100 FSTR("Invalid parameter")
101 };
102
103
104 const FLASH char * fat_rctostr(FRESULT rc)
105 {
106 return rc < ARRAY_SIZE(rc_strings) ? rc_strings[rc] : PSTR(" Unknown Error");
107 }
108
109 static
110 void swirl(void)
111 {
112 static const FLASH char swirlchar[] = { '-','\\','|','/' };
113 static uint_fast8_t cnt;
114 static uint32_t tstamp;
115
116 if (get_timer(0) > tstamp) {
117 tstamp = get_timer(0) + 250;
118 putchar('\b');
119 cnt = (cnt+1) & 3;
120 putchar(swirlchar[cnt]);
121 }
122 }
123
124 /*
125 * pwd - Print current directory of the current drive.
126 *
127 */
128 command_ret_t do_pwd(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc UNUSED, char * const argv[] UNUSED)
129 {
130 FRESULT res;
131 TCHAR buf[PATH_MAX];
132
133 res = f_getcwd(buf, PATH_MAX); /* Get current directory path */
134
135 if (res != FR_OK)
136 cmd_error(CMD_RET_FAILURE, res, NULL);
137
138 puts(buf);
139
140 return CMD_RET_SUCCESS;
141 }
142
143
144 /*
145 * cd - Change the current/working directory.
146 *
147 */
148 command_ret_t do_cd(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
149 {
150 TCHAR *path;
151 FRESULT res = FR_OK;
152
153 if (argc < 2) {
154 path = getenv_str(PSTR(ENV_HOME));
155 if (path == NULL) {
156 cmd_error(CMD_RET_FAILURE, 0, PSTR("\"%S\" not set"), PSTR(ENV_HOME));
157 }
158 } else
159 path = argv[1];
160
161 if (strlen(path) > 1 && path[1] == ':')
162 res = f_chdrive(path);
163
164 if (res == FR_OK)
165 res = f_chdir(path);
166
167 if (res != FR_OK)
168 cmd_error(CMD_RET_FAILURE, res, NULL);
169
170 return CMD_RET_SUCCESS;
171 }
172
173
174 static
175 void print_dirent(FILINFO *f)
176 {
177 printf_P(PSTR("%c%c%c%c%c %u/%02u/%02u %02u:%02u %9lu %s\n"),
178 (f->fattrib & AM_DIR) ? 'D' : '-',
179 (f->fattrib & AM_RDO) ? 'R' : '-',
180 (f->fattrib & AM_HID) ? 'H' : '-',
181 (f->fattrib & AM_SYS) ? 'S' : '-',
182 (f->fattrib & AM_ARC) ? 'A' : '-',
183 (f->fdate >> 9) + 1980, (f->fdate >> 5) & 15, f->fdate & 31,
184 (f->ftime >> 11), (f->ftime >> 5) & 63,
185 f->fsize, f->fname);
186 }
187
188 char *path_split(char *p)
189 {
190 if (isdigit(p[0]) && (p[1] == ':'))
191 p += 2;
192
193 char *ps = strrchr(p, '/');
194 if (ps) {
195 if (ps == p) {
196 ++ps;
197 memmove(ps+1, ps, strlen(ps)+1);
198 }
199 *ps++ = '\0';
200 }
201
202 return ps;
203 }
204
205 /*
206 * ls path - Directory listing
207 *
208 */
209 command_ret_t do_ls(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
210 {
211 FATFS *fs;
212 DIR dir; /* Directory object */
213 FILINFO finfo;
214 unsigned long p1;
215 unsigned int s1, s2;
216 FRESULT res = FR_OK;
217 char buf[PATH_MAX];
218 char *path = buf;
219 char *pattern;
220
221
222 memset(buf, 0, PATH_MAX);
223 if (argc < 2) {
224 buf[0] = '.';
225 } else {
226 strncpy(path, argv[1], PATH_MAX-1);
227 }
228 pattern = path_split(path);
229 debug_ls("### path, pattern: '%s', '%s'\n", path[0] ? path : "<NULL>", pattern ? pattern : "<NULL>");
230
231 if (pattern == NULL) {
232 res = f_opendir(&dir, path);
233 if (res == FR_NO_PATH || res == FR_INVALID_NAME) {
234 pattern = path;
235 path = ".";
236 } else if (res != FR_OK) {
237 cmd_error(CMD_RET_FAILURE, res, PSTR("'%s'"), path);
238 }
239 }
240 debug_ls("### path, pattern: '%s', '%s'\n", path, pattern ? pattern : "<NULL>");
241
242 if (pattern == NULL || *pattern == '\0')
243 pattern = "*";
244
245 debug_ls("### path, pattern: '%s', '%s'\n", path, pattern ? pattern : "<NULL>");
246
247 res = f_findfirst(&dir, &finfo, path, pattern);
248
249 p1 = s1 = s2 = 0;
250 for(;;) {
251 if (res != FR_OK)
252 cmd_error(CMD_RET_FAILURE, res, NULL);
253 if (!finfo.fname[0])
254 break;
255 if (finfo.fattrib & AM_DIR) {
256 s2++;
257 } else {
258 s1++; p1 += finfo.fsize;
259 }
260 print_dirent(&finfo);
261 if (check_abort())
262 break;
263 res = f_findnext(&dir, &finfo);
264 }
265
266 if (res == FR_OK) {
267 printf_P(PSTR("%4u File(s),%10lu bytes total\n%4u Dir(s)"), s1, p1, s2);
268 if (f_getfree(path, (DWORD*)&p1, &fs) == FR_OK)
269 printf_P(PSTR(", %10luK bytes free\n"), p1 * fs->csize / 2);
270 }
271
272 if (res)
273 cmd_error(CMD_RET_FAILURE, res, NULL);
274
275 return CMD_RET_SUCCESS;
276 }
277
278
279 /*
280 * tst path - for debugging: test access with different functions
281 *
282 */
283 command_ret_t do_tst(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
284 {
285 DIR Dir; /* Directory object */
286 FILINFO finfo;
287 FRESULT res = FR_OK;
288 char *path = "";
289 char *pattern = "*";
290
291 printf_P(PSTR("sizeof DIR: %u, sizeof FIL: %u\n"), sizeof (DIR), sizeof (FILINFO));
292
293 char * buf = (char *) malloc(BUFFER_SIZE);
294 if (buf == NULL) {
295 cmd_error(CMD_RET_FAILURE, ENOMEM, NULL);
296 }
297 res = f_getcwd(buf, BUFFER_SIZE); /* Get current directory path */
298
299 if (!res) {
300 printf_P(PSTR("cwd: '%s'\n"), buf);
301 }
302 free(buf);
303 if (res)
304 cmd_error(CMD_RET_FAILURE, res, NULL);
305
306 if (argc > 1)
307 path = argv[1];
308 if (argc > 2)
309 pattern = argv[2];
310
311 printf_P(PSTR("arg: '%s' '%s'\n"), path, pattern);
312 printf_P(PSTR("==== f_stat: "));
313 res = f_stat(path, &finfo);
314 cmd_error(0, res, NULL);
315 if (res == FR_OK) {
316 print_dirent(&finfo);
317 }
318
319 printf_P(PSTR("==== f_findfirst: "));
320 res = f_findfirst(&Dir, &finfo, path, pattern); /* Start to search for files */
321 cmd_error(CMD_RET_FAILURE, res, NULL);
322 if (res == FR_OK) {
323 print_dirent(&finfo);
324 }
325 f_closedir(&Dir);
326
327 printf_P(PSTR("==== f_opendir: "));
328 res = f_opendir(&Dir, path);
329 cmd_error(CMD_RET_FAILURE, res, NULL);
330 f_closedir(&Dir);
331
332 return CMD_RET_SUCCESS;
333 }
334
335 /* Work register for fs command */
336 struct stat_dat_s {
337 DWORD AccSize;
338 WORD AccFiles, AccDirs;
339 FILINFO finfo;
340 };
341
342 static
343 FRESULT scan_files (
344 char *path, /* Pointer to the working buffer with start path */
345 struct stat_dat_s *statp
346 )
347 {
348 DIR dirs;
349 FRESULT res;
350 int i;
351 char *fn;
352
353 res = f_opendir(&dirs, path);
354 swirl();
355 if (res == FR_OK) {
356 i = strlen(path);
357 while (((res = f_readdir(&dirs, &statp->finfo)) == FR_OK) &&
358 statp->finfo.fname[0]) {
359 if (FF_FS_RPATH && statp->finfo.fname[0] == '.')
360 continue;
361 fn = statp->finfo.fname;
362 if (statp->finfo.fattrib & AM_DIR) {
363 statp->AccDirs++;
364 path[i] = '/';
365 strcpy(path+i+1, fn);
366 res = scan_files(path, statp);
367 path[i] = '\0';
368 if (res != FR_OK)
369 break;
370 } else {
371 //printf_P(PSTR("%s/%s\n"), path, fn);
372 statp->AccFiles++;
373 statp->AccSize += statp->finfo.fsize;
374 }
375 if (check_abort()) {
376 res = 255;
377 break;
378 }
379 }
380 }
381
382 return res;
383 }
384
385
386 /*
387 * fatstat path - Show logical drive status
388 *
389 */
390 command_ret_t do_stat(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
391 {
392 FATFS *fs;
393 DWORD nfreeclst;
394 FRESULT res;
395 char *buf;
396 char *path = "";
397 struct stat_dat_s statp;
398
399 buf = (char *) malloc(PATH_MAX);
400 if (buf == NULL)
401 cmd_error(CMD_RET_FAILURE, ENOMEM, NULL);
402
403 if (argc > 1)
404 path = argv[1];
405 res = f_getfree(path, &nfreeclst, &fs);
406 if (!res) {
407 printf_P(PSTR(
408 "FAT type: %u\n"
409 "Bytes/Cluster: %lu\n"
410 "Number of FATs: %u\n"
411 "Root DIR entries: %u\n"
412 "Sectors/FAT: %lu\n"
413 "Number of clusters: %lu\n"
414 "FAT start (lba): %lu\n"
415 "DIR start (lba,cluster): %lu\n"
416 "Data start (lba): %lu\n"),
417 fs->fs_type, (DWORD)fs->csize * 512, fs->n_fats,
418 fs->n_rootdir, fs->fsize, fs->n_fatent - 2,
419 fs->fatbase, fs->dirbase, fs->database);
420
421 #if FF_USE_LABEL
422 DWORD serial;
423 res = f_getlabel(path, buf, &serial);
424 if (!res) {
425 printf_P(PSTR(
426 "Volume name: %s\n"
427 "Volume S/N: %04X-%04X\n"),
428 buf, (WORD)(serial >> 16), (WORD)(serial & 0xFFFF));
429 }
430 #endif
431 if (!res) {
432 statp.AccSize = statp.AccFiles = statp.AccDirs = 0;
433 strcpy(buf, path);
434
435 my_puts_P(PSTR("\nCounting... "));
436 res = scan_files(buf, &statp);
437 putchar('\r');
438 }
439 if (!res) {
440 printf_P(PSTR("%u files, %lu bytes.\n%u folders.\n"
441 "%lu KB total disk space.\n%lu KB available.\n"),
442 statp.AccFiles, statp.AccSize, statp.AccDirs,
443 (fs->n_fatent - 2) * (fs->csize / 2), nfreeclst * (fs->csize / 2)
444 );
445 }
446 }
447
448 free(buf);
449 if (res)
450 cmd_error(CMD_RET_FAILURE, res, NULL);
451
452 return CMD_RET_SUCCESS;
453 }
454
455 /*
456 * fatread/write - load binary file to/from a dos filesystem
457 * read <d:/path/filename> <addr> [bytes [pos]]
458 * write <d:/path/filename> <addr> <bytes>
459 */
460 command_ret_t do_rw(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
461 {
462 FIL File;
463 uint32_t addr;
464 unsigned long bytes;
465 unsigned long pos;
466 unsigned long bytes_rw;
467
468 bool dowrite = (argv[0][0] == 'w');
469 FRESULT res = FR_OK;
470 bool buserr = 0;
471 uint32_t timer;
472 uint8_t *buffer;
473
474 if (argc < (dowrite ? 4 : 3))
475 return CMD_RET_USAGE;
476
477 addr = eval_arg(argv[2], NULL);
478 if (addr >= MAX_MEMORY)
479 cmd_error(CMD_RET_FAILURE, 0, PSTR("Address too high: %#lx"), addr);
480
481 if (argc > 3)
482 bytes = eval_arg(argv[3], NULL);
483 else
484 bytes = MAX_MEMORY;
485 if (argc > 4)
486 pos = eval_arg(argv[4], NULL);
487 else
488 pos = 0;
489
490 if (addr + bytes > MAX_MEMORY)
491 bytes = MAX_MEMORY - addr;
492
493 buffer = (uint8_t *) malloc(BUFFER_SIZE);
494 if (buffer == NULL)
495 cmd_error(CMD_RET_FAILURE, ENOMEM, NULL);
496
497 res = f_open(&File, argv[1], dowrite ? FA_WRITE | FA_CREATE_ALWAYS
498 : FA_READ );
499
500 if (!res) {
501 res = f_lseek(&File, pos);
502 if (!res) {
503 bytes_rw = 0;
504 timer = get_timer(0);
505 while (bytes) {
506 unsigned int cnt, br;
507
508 if (bytes >= BUFFER_SIZE) {
509 cnt = BUFFER_SIZE;
510 bytes -= BUFFER_SIZE;
511 } else {
512 cnt = bytes; bytes = 0;
513 }
514 if (dowrite) {
515 if (!(z80_bus_cmd(Request) & ZST_ACQUIRED)) {
516 buserr = 1;
517 break;
518 }
519 z80_read_block(buffer, addr, cnt);
520 z80_bus_cmd(Release);
521 res = f_write(&File, buffer, cnt, &br);
522 if (res != FR_OK)
523 break;
524 } else {
525 res = f_read(&File, buffer, cnt, &br);
526 if (res != FR_OK)
527 break;
528 if (!(z80_bus_cmd(Request) & ZST_ACQUIRED)) {
529 buserr = 1;
530 break;
531 }
532 z80_write_block(buffer, addr, br);
533 z80_bus_cmd(Release);
534 }
535 addr += br;
536
537 bytes_rw += br;
538 if (cnt != br) {
539 if (dowrite)
540 printf_P(PSTR("Disk full?\n"));
541 break;
542 }
543 if (check_abort())
544 break;
545 }
546
547 FRESULT fr = f_close(&File);
548 if (!res)
549 res = fr;
550 timer = get_timer(timer);
551 printf_P(PSTR("%lu (%#lx) bytes read/written with %lu bytes/sec.\n"),
552 bytes_rw, bytes_rw, timer ? (bytes_rw * 1000 / timer) : 0);
553 }
554 }
555
556 free(buffer);
557
558 if (res)
559 cmd_error(CMD_RET_FAILURE, res, PSTR("'%s'"), argv[1]);
560 if (buserr)
561 cmd_error(CMD_RET_FAILURE, EBUSTO, NULL);
562
563 return CMD_RET_SUCCESS;
564 }
565
566 /*
567 * command table for fat subcommands
568 */
569
570 cmd_tbl_t cmd_tbl_fat[] = {
571 CMD_TBL_ITEM(
572 status, 2, CTBL_RPT, do_stat,
573 "Show logical drive status",
574 "dev"
575 ),
576 CMD_TBL_ITEM(
577 pwd, 2, CTBL_RPT|CTBL_SUBCMDAUTO, do_pwd,
578 "Print name of current/working directory",
579 ""
580 ),
581 CMD_TBL_ITEM(
582 cd, 2, 0|CTBL_SUBCMDAUTO, do_cd,
583 "Change the current/working directory.",
584 "path"
585 ),
586 CMD_TBL_ITEM(
587 ls, 2, CTBL_RPT|CTBL_SUBCMDAUTO, do_ls,
588 "Directory listing",
589 "path"
590 ),
591 CMD_TBL_ITEM(
592 tst, 3, CTBL_DBG|CTBL_RPT, do_tst,
593 "FatFS test function",
594 "[path [pattern]]"
595 ),
596 CMD_TBL_ITEM(
597 load, 5, 0, do_rw,
598 "load binary file from a dos filesystem",
599 "<d:/path/filename> <addr> [bytes [pos]]\n"
600 " - Load binary file 'path/filename' on logical drive 'd'\n"
601 " to address 'addr' from dos filesystem.\n"
602 " 'pos' gives the file position to start loading from.\n"
603 " If 'pos' is omitted, 0 is used. 'pos' requires 'bytes'.\n"
604 " 'bytes' gives the size to load. If 'bytes' is 0 or omitted,\n"
605 " the load stops on end of file."
606 ),
607 CMD_TBL_ITEM(
608 write, 4, 0, do_rw,
609 "write file into a dos filesystem",
610 "<d:/path/filename> <addr> <bytes>\n"
611 " - Write file to 'path/filename' on logical drive 'd' from RAM\n"
612 " starting at address 'addr'.\n"
613 ),
614
615 CMD_TBL_ITEM(
616 help, CONFIG_SYS_MAXARGS, CTBL_RPT, do_help,
617 "Print sub command description/usage",
618 "\n"
619 " - print brief description of all sub commands\n"
620 "fat help command ...\n"
621 " - print detailed usage of sub cmd 'command'"
622 ),
623
624 /* This does not use the CMD_TBL_ITEM macro as ? can't be used in symbol names */
625 {FSTR("?"), CONFIG_SYS_MAXARGS, 1, do_help,
626 NULL,
627 #ifdef CONFIG_SYS_LONGHELP
628 FSTR(""),
629 #endif /* CONFIG_SYS_LONGHELP */
630 NULL,
631 #ifdef CONFIG_AUTO_COMPLETE
632 NULL,
633 #endif
634 },
635 /* Mark end of table */
636 CMD_TBL_END(cmd_tbl_fat)
637 };
638
639
640 command_ret_t do_fat(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc UNUSED, char * const argv[] UNUSED)
641 {
642 puts_P(PSTR("Huch?"));
643
644 return CMD_RET_USAGE;
645 }