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