ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/Frodo4/Src/1541fs.cpp
Revision: 1.2
Committed: 2003-07-01T17:51:17Z (21 years, 3 months ago) by cebix
Branch: MAIN
Changes since 1.1: +1 -1 lines
Log Message:
updated copyright date

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