1 | // This file is part of SmallBASIC |
2 | // |
3 | // SmallBASIC - file.c - Low-level file system support |
4 | // |
5 | // This program is distributed under the terms of the GPL v2.0 or later |
6 | // Download the GNU Public License (GPL) from www.gnu.org |
7 | // |
8 | // Copyright(C) 2000 Nicholas Christopoulos |
9 | |
10 | #include "common/sys.h" |
11 | #include "common/device.h" |
12 | #include "common/pproc.h" |
13 | #include "common/extlib.h" |
14 | #include "common/messages.h" |
15 | |
16 | #include <errno.h> |
17 | #include <dirent.h> |
18 | |
19 | #if USE_TERM_IO |
20 | #include <sys/time.h> |
21 | #include <termios.h> |
22 | #include <unistd.h> |
23 | #endif |
24 | |
25 | // drivers |
26 | #include "common/fs_stream.h" |
27 | #include "common/fs_serial.h" |
28 | #include "common/fs_socket_client.h" |
29 | #include "lib/match.h" |
30 | |
31 | // FILE TABLE |
32 | static dev_file_t file_table[OS_FILEHANDLES]; |
33 | |
34 | /** |
35 | * Basic wild-cards |
36 | */ |
37 | int wc_match(const char *mask, char *name) { |
38 | if (mask == NULL) { |
39 | return 1; |
40 | } |
41 | if (*mask == '\0') { |
42 | return 1; |
43 | } |
44 | if (strcmp(mask, "*" ) == 0) { |
45 | return 1; |
46 | } |
47 | return (reg_match(mask, name) == 0); |
48 | } |
49 | |
50 | /** |
51 | * initialize file system |
52 | */ |
53 | int dev_initfs() { |
54 | for (int i = 0; i < OS_FILEHANDLES; i++) { |
55 | file_table[i].handle = -1; |
56 | } |
57 | |
58 | return 1; |
59 | } |
60 | |
61 | /** |
62 | * cleanup file system |
63 | */ |
64 | void dev_closefs() { |
65 | for (int i = 0; i < OS_FILEHANDLES; i++) { |
66 | if (file_table[i].handle != -1) { |
67 | dev_fclose(i + 1); |
68 | } |
69 | } |
70 | } |
71 | |
72 | /** |
73 | * returns a free file handle for user's commands |
74 | */ |
75 | int dev_freefilehandle() { |
76 | for (int i = 0; i < OS_FILEHANDLES; i++) { |
77 | if (file_table[i].handle == -1) { |
78 | // Note: BASIC's handles starting from 1 |
79 | return i + 1; |
80 | } |
81 | } |
82 | |
83 | rt_raise(FSERR_TOO_MANY_FILES); |
84 | return -1; |
85 | } |
86 | |
87 | /** |
88 | * returns a file pointer for the given BASIC handle |
89 | */ |
90 | dev_file_t *dev_getfileptr(const int handle) { |
91 | dev_file_t *result; |
92 | // BASIC handles start from 1 |
93 | int hnd = handle - 1; |
94 | if (hnd < 0 || hnd >= OS_FILEHANDLES) { |
95 | rt_raise(FSERR_HANDLE); |
96 | result = NULL; |
97 | } else { |
98 | result = &file_table[hnd]; |
99 | } |
100 | return result; |
101 | } |
102 | |
103 | /** |
104 | * returns true if the file is opened |
105 | */ |
106 | int dev_fstatus(int handle) { |
107 | dev_file_t *f; |
108 | |
109 | if ((f = dev_getfileptr(handle)) == NULL) { |
110 | return 0; |
111 | } |
112 | |
113 | return (f->handle != -1); |
114 | } |
115 | |
116 | /** |
117 | * terminal speed |
118 | * select the correct system constant |
119 | */ |
120 | #if USE_TERM_IO |
121 | int select_unix_serial_speed(int n) { |
122 | switch (n) { |
123 | case 300: |
124 | return B300; |
125 | case 600: |
126 | return B600; |
127 | case 1200: |
128 | return B1200; |
129 | case 2400: |
130 | return B2400; |
131 | case 4800: |
132 | return B4800; |
133 | case 9600: |
134 | return B9600; |
135 | case 19200: |
136 | return B19200; |
137 | case 38400: |
138 | return B38400; |
139 | } |
140 | return B9600; |
141 | } |
142 | #endif |
143 | |
144 | /** |
145 | * opens a file |
146 | * |
147 | * returns true on success |
148 | */ |
149 | int dev_fopen(int sb_handle, const char *name, int flags) { |
150 | dev_file_t *f; |
151 | |
152 | if ((f = dev_getfileptr(sb_handle)) == NULL) { |
153 | return 0; |
154 | } |
155 | |
156 | memset(f, 0, sizeof(dev_file_t)); |
157 | |
158 | f->handle = -1; |
159 | f->open_flags = flags; |
160 | f->drv_data = NULL; |
161 | strlcpy(f->name, name, sizeof(f->name)); |
162 | |
163 | f->type = ft_stream; |
164 | |
165 | // |
166 | // special devices |
167 | // |
168 | if (strlen(f->name) > 4) { |
169 | if (f->name[4] == ':') { |
170 | for (int i = 0; i < 5; i++) { |
171 | f->name[i] = to_upper(f->name[i]); |
172 | } |
173 | if (strncmp(f->name, "COM" , 3) == 0) { |
174 | f->type = ft_serial_port; |
175 | f->port = f->name[3] - '1'; |
176 | if (f->port < 0) { |
177 | f->port = 10; |
178 | } |
179 | if (strlen(f->name) > 5) { |
180 | f->devspeed = xstrtol(f->name + 5); |
181 | } else { |
182 | f->devspeed = 9600; |
183 | } |
184 | |
185 | #if USE_TERM_IO |
186 | f->devspeed = select_unix_serial_speed(f->devspeed); |
187 | #endif |
188 | } else if (strncmp(f->name, "SOCL:" , 5) == 0) { |
189 | f->type = ft_socket_client; |
190 | } else if (strncasecmp(f->name, "HTTP:" , 5) == 0) { |
191 | f->type = ft_http_client; |
192 | } else if (strncmp(f->name, "SOUT:" , 5) == 0 || |
193 | strncmp(f->name, "SDIN:" , 5) == 0 || |
194 | strncmp(f->name, "SERR:" , 5) == 0) { |
195 | f->type = ft_stream; |
196 | } |
197 | } else if (f->name[3] == ':') { |
198 | for (int i = 0; i < 4; i++) { |
199 | f->name[i] = to_upper(f->name[i]); |
200 | } |
201 | |
202 | if (strncmp(f->name, "CON:" , 4) == 0) { |
203 | strcpy(f->name, "SOUT:" ); |
204 | f->type = ft_stream; |
205 | } else if (strncmp(f->name, "KBD:" , 4) == 0) { |
206 | strcpy(f->name, "SDIN:" ); |
207 | f->type = ft_stream; |
208 | } |
209 | } |
210 | } // device |
211 | |
212 | if (!opt_file_permitted && f->type != ft_http_client) { |
213 | rt_raise(ERR_FILE_PERM); |
214 | return 0; |
215 | } |
216 | |
217 | // |
218 | // open |
219 | // |
220 | switch (f->type) { |
221 | case ft_stream: |
222 | return stream_open(f); |
223 | case ft_socket_client: |
224 | return sockcl_open(f); |
225 | case ft_http_client: |
226 | return http_open(f); |
227 | case ft_serial_port: |
228 | return serial_open(f); |
229 | default: |
230 | err_unsup(); |
231 | }; |
232 | |
233 | return 0; |
234 | } |
235 | |
236 | /** |
237 | * returns true on success |
238 | */ |
239 | int dev_fclose(int sb_handle) { |
240 | dev_file_t *f; |
241 | |
242 | if ((f = dev_getfileptr(sb_handle)) == NULL) { |
243 | return 0; |
244 | } |
245 | |
246 | switch (f->type) { |
247 | case ft_stream: |
248 | return stream_close(f); |
249 | case ft_serial_port: |
250 | return serial_close(f); |
251 | case ft_socket_client: |
252 | case ft_http_client: |
253 | return sockcl_close(f); |
254 | default: |
255 | err_unsup(); |
256 | } |
257 | return 0; |
258 | } |
259 | |
260 | /** |
261 | * returns true on success |
262 | */ |
263 | int dev_fwrite(int sb_handle, byte *data, uint32_t size) { |
264 | dev_file_t *f; |
265 | |
266 | if ((f = dev_getfileptr(sb_handle)) == NULL) { |
267 | return 0; |
268 | } |
269 | |
270 | switch (f->type) { |
271 | case ft_stream: |
272 | return stream_write(f, data, size); |
273 | case ft_serial_port: |
274 | return serial_write(f, data, size); |
275 | case ft_socket_client: |
276 | case ft_http_client: |
277 | return sockcl_write(f, data, size); |
278 | default: |
279 | err_unsup(); |
280 | }; |
281 | return 0; |
282 | } |
283 | |
284 | /** |
285 | * returns true on success |
286 | */ |
287 | int dev_fread(int sb_handle, byte *data, uint32_t size) { |
288 | dev_file_t *f; |
289 | |
290 | if ((f = dev_getfileptr(sb_handle)) == NULL) { |
291 | return 0; |
292 | } |
293 | |
294 | switch (f->type) { |
295 | case ft_stream: |
296 | return stream_read(f, data, size); |
297 | case ft_serial_port: |
298 | return serial_read(f, data, size); |
299 | case ft_socket_client: |
300 | case ft_http_client: |
301 | return sockcl_read(f, data, size); |
302 | default: |
303 | err_unsup(); |
304 | } |
305 | return 0; |
306 | } |
307 | |
308 | /** |
309 | * |
310 | */ |
311 | uint32_t dev_ftell(int sb_handle) { |
312 | dev_file_t *f; |
313 | |
314 | if ((f = dev_getfileptr(sb_handle)) == NULL) { |
315 | return 0; |
316 | } |
317 | |
318 | switch (f->type) { |
319 | case ft_stream: |
320 | return stream_tell(f); |
321 | default: |
322 | err_unsup(); |
323 | }; |
324 | return 0; |
325 | } |
326 | |
327 | /** |
328 | * |
329 | */ |
330 | uint32_t dev_flength(int sb_handle) { |
331 | dev_file_t *f; |
332 | |
333 | if ((f = dev_getfileptr(sb_handle)) == NULL) { |
334 | return 0; |
335 | } |
336 | |
337 | switch (f->type) { |
338 | case ft_stream: |
339 | return stream_length(f); |
340 | case ft_serial_port: |
341 | return serial_length(f); |
342 | case ft_socket_client: |
343 | case ft_http_client: |
344 | return sockcl_length(f); |
345 | default: |
346 | err_unsup(); |
347 | }; |
348 | return 0; |
349 | } |
350 | |
351 | /** |
352 | * |
353 | */ |
354 | uint32_t dev_fseek(int sb_handle, uint32_t offset) { |
355 | dev_file_t *f; |
356 | |
357 | if ((f = dev_getfileptr(sb_handle)) == NULL) { |
358 | return 0; |
359 | } |
360 | |
361 | switch (f->type) { |
362 | case ft_stream: |
363 | return stream_seek(f, offset); |
364 | default: |
365 | err_unsup(); |
366 | }; |
367 | return -1; |
368 | } |
369 | |
370 | /** |
371 | * |
372 | */ |
373 | int dev_feof(int sb_handle) { |
374 | dev_file_t *f; |
375 | |
376 | if ((f = dev_getfileptr(sb_handle)) == NULL) { |
377 | return 0; |
378 | } |
379 | |
380 | switch (f->type) { |
381 | case ft_stream: |
382 | return stream_eof(f); |
383 | case ft_serial_port: |
384 | return serial_eof(f); |
385 | case ft_socket_client: |
386 | case ft_http_client: |
387 | return sockcl_eof(f); |
388 | default: |
389 | err_unsup(); |
390 | }; |
391 | |
392 | return 0; |
393 | } |
394 | |
395 | /** |
396 | * deletes a file |
397 | * returns true on success |
398 | */ |
399 | int dev_fremove(const char *file) { |
400 | int success; |
401 | |
402 | if (!opt_file_permitted) { |
403 | rt_raise(ERR_FILE_PERM); |
404 | return 0; |
405 | } |
406 | |
407 | success = (remove(file) == 0); |
408 | if (!success) { |
409 | err_throw(FSERR_ACCESS); |
410 | } |
411 | return success; |
412 | } |
413 | |
414 | /** |
415 | * returns true if the file exists |
416 | */ |
417 | int dev_fexists(const char *file) { |
418 | if (!opt_file_permitted) { |
419 | rt_raise(ERR_FILE_PERM); |
420 | return 0; |
421 | } |
422 | |
423 | return (access(file, 0) == 0); |
424 | } |
425 | |
426 | /** |
427 | * copy file |
428 | * returns true on success |
429 | */ |
430 | int dev_fcopy(const char *file, const char *newfile) { |
431 | if (!opt_file_permitted) { |
432 | rt_raise(ERR_FILE_PERM); |
433 | return 0; |
434 | } |
435 | |
436 | if (dev_fexists(file)) { |
437 | if (dev_fexists(newfile)) { |
438 | if (!dev_fremove(newfile)) { |
439 | return 0; // cannot delete target-file |
440 | } |
441 | } |
442 | |
443 | int src = dev_freefilehandle(); |
444 | if (prog_error) { |
445 | return 0; |
446 | } |
447 | dev_fopen(src, file, DEV_FILE_INPUT); |
448 | if (prog_error) { |
449 | return 0; |
450 | } |
451 | int dst = dev_freefilehandle(); |
452 | if (prog_error) { |
453 | return 0; |
454 | } |
455 | dev_fopen(dst, newfile, DEV_FILE_OUTPUT); |
456 | if (prog_error) { |
457 | return 0; |
458 | } |
459 | |
460 | uint32_t file_len = dev_flength(src); |
461 | if (file_len != -1 && file_len > 0) { |
462 | uint32_t block_size = 1024; |
463 | uint32_t block_num = file_len / block_size; |
464 | uint32_t remain = file_len - (block_num * block_size); |
465 | byte *buf = malloc(block_size); |
466 | |
467 | for (int i = 0; i < block_num; i++) { |
468 | dev_fread(src, buf, block_size); |
469 | if (prog_error) { |
470 | free(buf); |
471 | return 0; |
472 | } |
473 | dev_fwrite(dst, buf, block_size); |
474 | if (prog_error) { |
475 | free(buf); |
476 | return 0; |
477 | } |
478 | } |
479 | |
480 | if (remain) { |
481 | dev_fread(src, buf, remain); |
482 | if (prog_error) { |
483 | free(buf); |
484 | return 0; |
485 | } |
486 | dev_fwrite(dst, buf, remain); |
487 | if (prog_error) { |
488 | free(buf); |
489 | return 0; |
490 | } |
491 | } |
492 | free(buf); |
493 | } |
494 | |
495 | dev_fclose(src); |
496 | if (prog_error) { |
497 | return 0; |
498 | } |
499 | dev_fclose(dst); |
500 | if (prog_error) { |
501 | return 0; |
502 | } |
503 | return 1; |
504 | } |
505 | return 0; // source file does not exists |
506 | } |
507 | |
508 | /** |
509 | * rename file |
510 | * returns true on success |
511 | */ |
512 | int dev_frename(const char *file, const char *newname) { |
513 | if (!opt_file_permitted) { |
514 | rt_raise(ERR_FILE_PERM); |
515 | return 0; |
516 | } |
517 | if (dev_fcopy(file, newname)) { |
518 | return dev_fremove(file); |
519 | } |
520 | return 0; |
521 | } |
522 | |
523 | /** |
524 | * create a directory |
525 | * BUG: no drivers supported |
526 | */ |
527 | void dev_mkdir(const char *dir) { |
528 | if (!opt_file_permitted) { |
529 | rt_raise(ERR_FILE_PERM); |
530 | return; |
531 | } |
532 | #if (defined(_Win32) || defined(__MINGW32__)) && !defined(__CYGWIN__) |
533 | if (mkdir(dir) != 0) { |
534 | err_file(errno); |
535 | } |
536 | #else |
537 | if (mkdir(dir, 0777) != 0) { |
538 | err_file(errno); |
539 | } |
540 | #endif |
541 | } |
542 | |
543 | /** |
544 | * removes a directory |
545 | */ |
546 | void dev_rmdir(const char *dir) { |
547 | if (!opt_file_permitted) { |
548 | rt_raise(ERR_FILE_PERM); |
549 | return; |
550 | } |
551 | if (rmdir(dir) != 0) { |
552 | err_file(errno); |
553 | } |
554 | } |
555 | |
556 | /** |
557 | * changes the current directory |
558 | */ |
559 | void dev_chdir(const char *dir) { |
560 | if (chdir(dir) != 0) { |
561 | err_file(errno); |
562 | } |
563 | setsysvar_str(SYSVAR_CWD, dev_getcwd()); |
564 | } |
565 | |
566 | /** |
567 | * create a file-list using wildcards |
568 | */ |
569 | char_p_t *dev_create_file_list(const char *wc, int *count) { |
570 | DIR *dp; |
571 | struct dirent *e; |
572 | char wc2[OS_FILENAME_SIZE + 1]; |
573 | char path[OS_PATHNAME_SIZE + 1]; |
574 | int l, size; |
575 | char_p_t *list; |
576 | |
577 | if (!opt_file_permitted) { |
578 | rt_raise(ERR_FILE_PERM); |
579 | return NULL; |
580 | } |
581 | |
582 | if (wc) { |
583 | strlcpy(path, wc, sizeof(path)); |
584 | char *p = strrchr(path, OS_DIRSEP); |
585 | if (p == NULL) { |
586 | getcwd(path, OS_PATHNAME_SIZE); |
587 | if (path[(l = strlen(path))] != OS_DIRSEP) { |
588 | path[l] = OS_DIRSEP; |
589 | path[l + 1] = '\0'; |
590 | } |
591 | strlcpy(wc2, wc, sizeof(wc2)); |
592 | } else { |
593 | strlcpy(wc2, p + 1, sizeof(wc2)); |
594 | *(p + 1) = '\0'; |
595 | if (strlen(wc2) == 0) { |
596 | strcpy(wc2, "*" ); |
597 | } |
598 | } |
599 | } else { |
600 | getcwd(path, OS_PATHNAME_SIZE); |
601 | if (path[(l = strlen(path))] != OS_DIRSEP) { |
602 | path[l] = OS_DIRSEP; |
603 | path[l + 1] = '\0'; |
604 | } |
605 | wc2[0] = '\0'; |
606 | } |
607 | |
608 | *count = 0; |
609 | size = 256; |
610 | list = malloc(sizeof(char_p_t) * size); |
611 | |
612 | if ((dp = opendir(path)) == NULL) { |
613 | return list; |
614 | } |
615 | |
616 | while ((e = readdir(dp)) != NULL) { |
617 | char *name = e->d_name; |
618 | if ((strcmp(name, "." ) == 0) || (strcmp(name, ".." ) == 0)) { |
619 | continue; |
620 | } |
621 | if (wc_match(wc2, name)) { |
622 | if ((*count + 1) == size) { |
623 | size += 256; |
624 | list = realloc(list, sizeof(char_p_t) * size); |
625 | } |
626 | list[*count] = (char *) malloc(strlen(name) + 1); |
627 | strcpy(list[*count], name); |
628 | *count = *count + 1; |
629 | } |
630 | } |
631 | |
632 | closedir(dp); |
633 | |
634 | // common for all, if there are no files, return NULL |
635 | if (*count == 0) { |
636 | if (list) { |
637 | free(list); |
638 | } |
639 | list = NULL; |
640 | } |
641 | return list; |
642 | } |
643 | |
644 | /** |
645 | * destroy the file-list |
646 | */ |
647 | void dev_destroy_file_list(char_p_t *list, int count) { |
648 | for (int i = 0; i < count; i++) { |
649 | free(list[i]); |
650 | } |
651 | free(list); |
652 | } |
653 | |
654 | /** |
655 | * returns the current directory |
656 | * BUG: no drivers supported |
657 | */ |
658 | char *dev_getcwd() { |
659 | static char retbuf[OS_PATHNAME_SIZE + 1]; |
660 | getcwd(retbuf, OS_PATHNAME_SIZE); |
661 | int l = strlen(retbuf); |
662 | if (retbuf[l - 1] != OS_DIRSEP) { |
663 | retbuf[l] = OS_DIRSEP; |
664 | retbuf[l + 1] = '\0'; |
665 | } |
666 | #if defined(_Win32) |
667 | for (int i = 0; i < l; i++) { |
668 | if (retbuf[i] == '\\') { |
669 | retbuf[i] = OS_DIRSEP; |
670 | } |
671 | } |
672 | #endif |
673 | return retbuf; |
674 | } |
675 | |
676 | /** |
677 | * returns the file attributes |
678 | * 1-1-1 = link - directory - regular file |
679 | */ |
680 | int dev_fattr(const char *file) { |
681 | struct stat st; |
682 | int r = 0; |
683 | |
684 | if (stat(file, &st) == 0) { |
685 | r |= ((S_ISREG(st.st_mode)) ? VFS_ATTR_FILE : 0); |
686 | r |= ((S_ISDIR(st.st_mode)) ? VFS_ATTR_DIR : 0); |
687 | #if defined(_UnixOS) && !defined(__MINGW32__) |
688 | r |= ((S_ISLNK(st.st_mode)) ? VFS_ATTR_LINK : 0); |
689 | #endif |
690 | } |
691 | return r; |
692 | } |
693 | |
694 | /** |
695 | * returns the access rights of the file |
696 | */ |
697 | int dev_faccess(const char *file) { |
698 | struct stat st; |
699 | |
700 | if (!opt_file_permitted) { |
701 | rt_raise(ERR_FILE_PERM); |
702 | return 0; |
703 | } |
704 | |
705 | if (stat(file, &st) == 0) { |
706 | return st.st_mode; |
707 | } |
708 | return 0; |
709 | } |
710 | |
711 | /** |
712 | * returns the last-modified time for a file as a string |
713 | */ |
714 | int dev_filemtime(var_t *v, char **buffer) { |
715 | time_t time = 0; |
716 | int size = 0; |
717 | |
718 | if (v_is_type(v, V_INT)) { |
719 | time = v->v.i; |
720 | } else if (v_is_type(v, V_STR)) { |
721 | const char *file = v_str(v); |
722 | struct stat st; |
723 | if (!opt_file_permitted) { |
724 | rt_raise(ERR_FILE_PERM); |
725 | } else if (stat(file, &st) == 0) { |
726 | time = st.st_mtime; |
727 | } else { |
728 | err_file_not_found(); |
729 | } |
730 | } else { |
731 | err_argerr(); |
732 | } |
733 | |
734 | if (prog_error) { |
735 | *buffer = malloc(1); |
736 | *buffer[0] = '\0'; |
737 | } else { |
738 | // size for '2016-02-20 05:23 PM' |
739 | size = 20; |
740 | *buffer = malloc(size); |
741 | size = strftime(*buffer, size, "%Y-%m-%d %I:%M %p" , localtime(&time)); |
742 | } |
743 | return size; |
744 | } |
745 | |