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
32static dev_file_t file_table[OS_FILEHANDLES];
33
34/**
35 * Basic wild-cards
36 */
37int 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 */
53int 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 */
64void 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 */
75int 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 */
90dev_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 */
106int 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
121int 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 */
149int 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 */
239int 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 */
263int 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 */
287int 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 */
311uint32_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 */
330uint32_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 */
354uint32_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 */
373int 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 */
399int 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 */
417int 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 */
430int 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 */
512int 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 */
527void 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 */
546void 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 */
559void 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 */
569char_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 */
647void 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 */
658char *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 */
680int 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 */
697int 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 */
714int 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