ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/Frodo4/Src/1541fs.cpp
Revision: 1.3
Committed: 2003-07-09T13:54:22Z (20 years, 8 months ago) by cebix
Branch: MAIN
Changes since 1.2: +53 -61 lines
Log Message:
applied misc fixes that have accumulated over the time

File Contents

# Content
1 /*
2 * 1541fs.cpp - 1541 emulation in host file system
3 *
4 * Frodo (C) 1994-1997,2002-2003 Christian Bauer
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 /*
22 * Notes:
23 * ------
24 *
25 * - If the directory is opened (file name "$"), a temporary file
26 * with the structure of a 1541 directory file is created and
27 * opened. It can then be accessed in the same way as all other
28 * files.
29 *
30 * Incompatibilities:
31 * ------------------
32 *
33 * - No "raw" directory reading
34 * - No relative/sequential/user files
35 * - Only "I" and "UJ" commands implemented
36 */
37
38 #include "sysdeps.h"
39
40 #include "1541fs.h"
41 #include "IEC.h"
42 #include "main.h"
43 #include "Prefs.h"
44
45 #ifdef __riscos__
46 #include "ROlib.h"
47 #endif
48
49
50 // Access modes
51 enum {
52 FMODE_READ, FMODE_WRITE, FMODE_APPEND
53 };
54
55 // File types
56 enum {
57 FTYPE_PRG, FTYPE_SEQ
58 };
59
60 // Prototypes
61 static bool match(char *p, char *n);
62
63
64 /*
65 * Constructor: Prepare emulation
66 */
67
68 FSDrive::FSDrive(IEC *iec, char *path) : Drive(iec)
69 {
70 strcpy(orig_dir_path, path);
71 dir_path[0] = 0;
72
73 if (change_dir(orig_dir_path)) {
74 for (int i=0; i<16; i++)
75 file[i] = NULL;
76
77 Reset();
78
79 Ready = true;
80 }
81 }
82
83
84 /*
85 * Destructor
86 */
87
88 FSDrive::~FSDrive()
89 {
90 if (Ready) {
91 close_all_channels();
92 Ready = false;
93 }
94 }
95
96
97 /*
98 * Change emulation directory
99 */
100
101 bool FSDrive::change_dir(char *dirpath)
102 {
103 #ifndef __riscos__
104 DIR *dir;
105
106 if ((dir = opendir(dirpath)) != NULL) {
107 closedir(dir);
108 strcpy(dir_path, dirpath);
109 strncpy(dir_title, dir_path, 16);
110 return true;
111 } else
112 return false;
113 #else
114 int Info[4];
115
116 if ((ReadCatalogueInfo(dirpath,Info) & 2) != 0) { // Directory or image file
117 strcpy(dir_path, dirpath);
118 strncpy(dir_title, dir_path, 16);
119 return true;
120 } else
121 return false;
122 #endif
123 }
124
125
126 /*
127 * Open channel
128 */
129
130 uint8 FSDrive::Open(int channel, char *filename)
131 {
132 set_error(ERR_OK);
133
134 // Channel 15: Execute file name as command
135 if (channel == 15) {
136 execute_command(filename);
137 return ST_OK;
138 }
139
140 // Close previous file if still open
141 if (file[channel]) {
142 fclose(file[channel]);
143 file[channel] = NULL;
144 }
145
146 if (filename[0] == '$')
147 return open_directory(channel, filename+1);
148
149 if (filename[0] == '#') {
150 set_error(ERR_NOCHANNEL);
151 return ST_OK;
152 }
153
154 return open_file(channel, filename);
155 }
156
157
158 /*
159 * Open file
160 */
161
162 uint8 FSDrive::open_file(int channel, char *filename)
163 {
164 char plainname[NAMEBUF_LENGTH];
165 int filemode = FMODE_READ;
166 int filetype = FTYPE_PRG;
167 bool wildflag = false;
168 char *mode = "rb";
169
170 convert_filename(filename, plainname, &filemode, &filetype, &wildflag);
171
172 // Channel 0 is READ PRG, channel 1 is WRITE PRG
173 if (!channel) {
174 filemode = FMODE_READ;
175 filetype = FTYPE_PRG;
176 }
177 if (channel == 1) {
178 filemode = FMODE_WRITE;
179 filetype = FTYPE_PRG;
180 }
181
182 // Wildcards are only allowed on reading
183 if (wildflag) {
184 if (filemode != FMODE_READ) {
185 set_error(ERR_SYNTAX33);
186 return ST_OK;
187 }
188 find_first_file(plainname);
189 }
190
191 // Select fopen() mode according to file mode
192 switch (filemode) {
193 case FMODE_READ:
194 mode = "rb";
195 break;
196 case FMODE_WRITE:
197 mode = "wb";
198 break;
199 case FMODE_APPEND:
200 mode = "ab";
201 break;
202 }
203
204 // Open file
205 #ifndef __riscos__
206 if (chdir(dir_path))
207 set_error(ERR_NOTREADY);
208 else if ((file[channel] = fopen(plainname, mode)) != NULL) {
209 if (filemode == FMODE_READ) // Read and buffer first byte
210 read_char[channel] = fgetc(file[channel]);
211 } else
212 set_error(ERR_FILENOTFOUND);
213 chdir(AppDirPath);
214 #else
215 {
216 char fullname[NAMEBUF_LENGTH];
217
218 // On RISC OS make a full filename
219 sprintf(fullname,"%s.%s",dir_path,plainname);
220 if ((file[channel] = fopen(fullname, mode)) != NULL)
221 {
222 if (filemode == FMODE_READ)
223 {
224 read_char[channel] = fgetc(file[channel]);
225 }
226 }
227 else
228 {
229 set_error(ERR_FILENOTFOUND);
230 }
231 }
232 #endif
233
234 return ST_OK;
235 }
236
237
238 /*
239 * Analyze file name, get access mode and type
240 */
241
242 void FSDrive::convert_filename(char *srcname, char *destname, int *filemode, int *filetype, bool *wildflag)
243 {
244 char *p, *q;
245 int i;
246
247 // Search for ':', p points to first character after ':'
248 if ((p = strchr(srcname, ':')) != NULL)
249 p++;
250 else
251 p = srcname;
252
253 // Convert char set of the remaining string -> destname
254 q = destname;
255 for (i=0; i<NAMEBUF_LENGTH && (*q++ = conv_from_64(*p++, true)); i++) ;
256
257 // Look for mode parameters seperated by ','
258 p = destname;
259 while ((p = strchr(p, ',')) != NULL) {
260
261 // Cut string after the first ','
262 *p++ = 0;
263
264 switch (*p) {
265 case 'p':
266 *filetype = FTYPE_PRG;
267 break;
268 case 's':
269 *filetype = FTYPE_SEQ;
270 break;
271 case 'r':
272 *filemode = FMODE_READ;
273 break;
274 case 'w':
275 *filemode = FMODE_WRITE;
276 break;
277 case 'a':
278 *filemode = FMODE_APPEND;
279 break;
280 }
281 }
282
283 // Search for wildcards
284 *wildflag = (strchr(destname, '?') != NULL) || (strchr(destname, '*') != NULL);
285 }
286
287
288 /*
289 * Find first file matching wildcard pattern and get its real name
290 */
291
292 // Return true if name 'n' matches pattern 'p'
293 static bool match(char *p, char *n)
294 {
295 if (!*p) // Null pattern matches everything
296 return true;
297
298 do {
299 if (*p == '*') // Wildcard '*' matches all following characters
300 return true;
301 if ((*p != *n) && (*p != '?')) // Wildcard '?' matches single character
302 return false;
303 p++; n++;
304 } while (*p);
305
306 return !*n;
307 }
308
309 void FSDrive::find_first_file(char *name)
310 {
311 #ifndef __riscos__
312 DIR *dir;
313 struct dirent *de;
314
315 // Open directory for reading and skip '.' and '..'
316 if ((dir = opendir(dir_path)) == NULL)
317 return;
318 de = readdir(dir);
319 while (de && (0 == strcmp(".", de->d_name) || 0 == strcmp("..", de->d_name)))
320 de = readdir(dir);
321
322 while (de) {
323
324 // Match found? Then copy real file name
325 if (match(name, de->d_name)) {
326 strncpy(name, de->d_name, NAMEBUF_LENGTH);
327 closedir(dir);
328 return;
329 }
330
331 // Get next directory entry
332 de = readdir(dir);
333 }
334
335 closedir(dir);
336 #else
337 dir_env de;
338 char Buffer[NAMEBUF_LENGTH];
339
340 de.offset = 0; de.buffsize = NAMEBUF_LENGTH; de.match = name;
341 do {
342 de.readno = 1;
343 if (ReadDirName(dir_path,Buffer,&de) != NULL)
344 de.offset = -1;
345 else if (de.offset != -1 && match(name,Buffer)) {
346 strncpy(name, Buffer, NAMEBUF_LENGTH);
347 return;
348 }
349 } while (de.readno > 0);
350 #endif
351 }
352
353
354 /*
355 * Open directory, create temporary file
356 */
357
358 uint8 FSDrive::open_directory(int channel, char *filename)
359 {
360 char buf[] = "\001\004\001\001\0\0\022\042 \042 00 2A";
361 char str[NAMEBUF_LENGTH];
362 char pattern[NAMEBUF_LENGTH];
363 char *p, *q;
364 int i;
365 int filemode;
366 int filetype;
367 bool wildflag;
368
369 #ifndef __riscos__
370 DIR *dir;
371 struct dirent *de;
372 struct stat statbuf;
373
374 // Special treatment for "$0"
375 if (filename[0] == '0' && filename[1] == 0)
376 filename += 1;
377
378 // Convert filename ('$' already stripped), filemode/type are ignored
379 convert_filename(filename, pattern, &filemode, &filetype, &wildflag);
380
381 // Open directory for reading and skip '.' and '..'
382 if ((dir = opendir(dir_path)) == NULL) {
383 set_error(ERR_NOTREADY);
384 return ST_OK;
385 }
386 de = readdir(dir);
387 while (de && (0 == strcmp(".", de->d_name) || 0 == strcmp("..", de->d_name)))
388 de = readdir(dir);
389
390 // Create temporary file
391 if ((file[channel] = tmpfile()) == NULL) {
392 closedir(dir);
393 return ST_OK;
394 }
395
396 // Create directory title
397 p = &buf[8];
398 for (i=0; i<16 && dir_title[i]; i++)
399 *p++ = conv_to_64(dir_title[i], false);
400 fwrite(buf, 1, 32, file[channel]);
401
402 // Create and write one line for every directory entry
403 while (de) {
404
405 // Include only files matching the pattern
406 if (match(pattern, de->d_name)) {
407
408 // Get file statistics
409 chdir(dir_path);
410 stat(de->d_name, &statbuf);
411 chdir(AppDirPath);
412
413 // Clear line with spaces and terminate with null byte
414 memset(buf, ' ', 31);
415 buf[31] = 0;
416
417 p = buf;
418 *p++ = 0x01; // Dummy line link
419 *p++ = 0x01;
420
421 // Calculate size in blocks (254 bytes each)
422 i = (statbuf.st_size + 254) / 254;
423 *p++ = i & 0xff;
424 *p++ = (i >> 8) & 0xff;
425
426 p++;
427 if (i < 10) p++; // Less than 10: add one space
428 if (i < 100) p++; // Less than 100: add another space
429
430 // Convert and insert file name
431 strcpy(str, de->d_name);
432 *p++ = '\"';
433 q = p;
434 for (i=0; i<16 && str[i]; i++)
435 *q++ = conv_to_64(str[i], true);
436 *q++ = '\"';
437 p += 18;
438
439 // File type
440 if (S_ISDIR(statbuf.st_mode)) {
441 *p++ = 'D';
442 *p++ = 'I';
443 *p++ = 'R';
444 } else {
445 *p++ = 'P';
446 *p++ = 'R';
447 *p++ = 'G';
448 }
449
450 // Write line
451 fwrite(buf, 1, 32, file[channel]);
452 }
453
454 // Get next directory entry
455 de = readdir(dir);
456 }
457 #else
458 dir_full_info di;
459 dir_env de;
460 unsigned char c;
461
462 // Much of this is very similar to the original
463 if ((filename[0] == '0') && (filename[1] == 0)) {filename++;}
464
465 // Concatenate dir_path and pattern in buffer pattern ==> read subdirs!
466 strcpy(pattern,dir_path); i = strlen(pattern); pattern[i++] = '.'; pattern[i] = 0;
467 convert_filename(filename, pattern + i, &filemode, &filetype, &wildflag);
468 p = pattern + i; q = p;
469 do {c = *q++; if (c == '.') p = q;} while (c >= 32);
470 *(p-1) = 0; // separate directory-path and pattern
471 if ((uint8)(*p) < 32) {*p = '*'; *(p+1) = 0;}
472
473 // We don't use tmpfile() -- problems involved!
474 DeleteFile(RO_TEMPFILE); // first delete it, if it exists
475 if ((file[channel] = fopen(RO_TEMPFILE,"wb+")) == NULL)
476 return(ST_OK);
477 de.offset = 0; de.buffsize = NAMEBUF_LENGTH; de.match = p;
478
479 // Create directory title - copied from above
480 p = &buf[8];
481 for (i=0; i<16 && dir_title[i]; i++)
482 *p++ = conv_to_64(dir_title[i], false);
483 fwrite(buf, 1, 32, file[channel]);
484
485 do {
486 de.readno = 1;
487 if (ReadDirNameInfo(pattern,&di,&de) != NULL)
488 de.offset = -1;
489 else if (de.readno > 0) { // don't have to check for match here
490 memset(buf,' ',31); buf[31] = 0; // most of this: see above
491 p = buf; *p++ = 0x01; *p++ = 0x01;
492 i = (di.length + 254) / 254; *p++ = i & 0xff; *p++ = (i>>8) & 0xff;
493 p++;
494 if (i < 10)
495 *p++ = ' ';
496 if (i < 100)
497 *p++ = ' ';
498 strcpy(str, di.name);
499 *p++ = '\"'; q = p;
500 for (i=0; (i<16 && str[i]); i++)
501 *q++ = conv_to_64(str[i], true);
502 *q++ = '\"'; p += 18;
503 if ((di.otype & 2) == 0) {
504 *p++ = 'P'; *p++ = 'R'; *p++ = 'G';
505 } else {
506 *p++ = 'D'; *p++ = 'I'; *p++ = 'R';
507 }
508 fwrite(buf, 1, 32, file[channel]);
509 }
510 } while (de.offset != -1);
511 #endif
512
513 // Final line
514 fwrite("\001\001\0\0BLOCKS FREE. \0\0", 1, 32, file[channel]);
515
516 // Rewind file for reading and read first byte
517 rewind(file[channel]);
518 read_char[channel] = fgetc(file[channel]);
519
520 #ifndef __riscos
521 // Close directory
522 closedir(dir);
523 #endif
524
525 return ST_OK;
526 }
527
528
529 /*
530 * Close channel
531 */
532
533 uint8 FSDrive::Close(int channel)
534 {
535 if (channel == 15) {
536 close_all_channels();
537 return ST_OK;
538 }
539
540 if (file[channel]) {
541 fclose(file[channel]);
542 file[channel] = NULL;
543 }
544
545 return ST_OK;
546 }
547
548
549 /*
550 * Close all channels
551 */
552
553 void FSDrive::close_all_channels(void)
554 {
555 for (int i=0; i<15; i++)
556 Close(i);
557
558 cmd_len = 0;
559 }
560
561
562 /*
563 * Read from channel
564 */
565
566 uint8 FSDrive::Read(int channel, uint8 *byte)
567 {
568 int c;
569
570 // Channel 15: Error channel
571 if (channel == 15) {
572 *byte = *error_ptr++;
573
574 if (*byte != '\r')
575 return ST_OK;
576 else { // End of message
577 set_error(ERR_OK);
578 return ST_EOF;
579 }
580 }
581
582 if (!file[channel]) return ST_READ_TIMEOUT;
583
584 // Read one byte
585 *byte = read_char[channel];
586 c = fgetc(file[channel]);
587 if (c == EOF)
588 return ST_EOF;
589 else {
590 read_char[channel] = c;
591 return ST_OK;
592 }
593 }
594
595
596 /*
597 * Write to channel
598 */
599
600 uint8 FSDrive::Write(int channel, uint8 byte, bool eoi)
601 {
602 // Channel 15: Collect chars and execute command on EOI
603 if (channel == 15) {
604 if (cmd_len >= 40)
605 return ST_TIMEOUT;
606
607 cmd_buffer[cmd_len++] = byte;
608
609 if (eoi) {
610 cmd_buffer[cmd_len] = 0;
611 cmd_len = 0;
612 execute_command(cmd_buffer);
613 }
614 return ST_OK;
615 }
616
617 if (!file[channel]) {
618 set_error(ERR_FILENOTOPEN);
619 return ST_TIMEOUT;
620 }
621
622 if (fputc(byte, file[channel]) == EOF) {
623 set_error(ERR_WRITEERROR);
624 return ST_TIMEOUT;
625 }
626
627 return ST_OK;
628 }
629
630
631 /*
632 * Execute command string
633 */
634
635 void FSDrive::execute_command(char *command)
636 {
637 switch (command[0]) {
638 case 'I':
639 close_all_channels();
640 set_error(ERR_OK);
641 break;
642
643 case 'U':
644 if ((command[1] & 0x0f) == 0x0a) {
645 Reset();
646 } else
647 set_error(ERR_SYNTAX30);
648 break;
649
650 case 'G':
651 if (command[1] != ':')
652 set_error(ERR_SYNTAX30);
653 else
654 chdir_cmd(&command[2]);
655 break;
656
657 default:
658 set_error(ERR_SYNTAX30);
659 }
660 }
661
662
663 /*
664 * Execute 'G' command
665 */
666
667 void FSDrive::chdir_cmd(char *dirpath)
668 {
669 char str[NAMEBUF_LENGTH];
670 char *p = str;
671
672 close_all_channels();
673
674 // G:. resets the directory path to its original setting
675 if (dirpath[0] == '.' && dirpath[1] == 0) {
676 change_dir(orig_dir_path);
677 } else {
678
679 // Convert directory name
680 for (int i=0; i<NAMEBUF_LENGTH && (*p++ = conv_from_64(*dirpath++, false)); i++) ;
681
682 if (!change_dir(str))
683 set_error(ERR_NOTREADY);
684 }
685 }
686
687
688 /*
689 * Reset drive
690 */
691
692 void FSDrive::Reset(void)
693 {
694 close_all_channels();
695 cmd_len = 0;
696 set_error(ERR_STARTUP);
697 }
698
699
700 /*
701 * Conversion PETSCII->ASCII
702 */
703
704 uint8 FSDrive::conv_from_64(uint8 c, bool map_slash)
705 {
706 if ((c >= 'A') && (c <= 'Z') || (c >= 'a') && (c <= 'z'))
707 return c ^ 0x20;
708 if ((c >= 0xc1) && (c <= 0xda))
709 return c ^ 0x80;
710 if ((c == '/') && map_slash && ThePrefs.MapSlash)
711 #ifdef __riscos__
712 return '.'; // directory separator is '.' in RO
713 if (c == '.') {return('_');} // convert dot to underscore
714 if (c == ' ') {return(0xa0);} // space --> hard space
715 #else
716 return '\\';
717 #endif
718 return c;
719 }
720
721
722 /*
723 * Conversion ASCII->PETSCII
724 */
725
726 uint8 FSDrive::conv_to_64(uint8 c, bool map_slash)
727 {
728 if ((c >= 'A') && (c <= 'Z') || (c >= 'a') && (c <= 'z'))
729 return c ^ 0x20;
730 #ifdef __riscos__
731 if ((c == '.') && map_slash && ThePrefs.MapSlash)
732 #else
733 if ((c == '\\') && map_slash && ThePrefs.MapSlash)
734 #endif
735 return '/';
736 #ifdef __riscos__
737 if (c == '_') {return('.');} // convert underscore to dot
738 if (c == 0xa0) {return(' ');} // hard space -> space
739 #endif
740 return c;
741 }