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