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, 9 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

# User Rev Content
1 cebix 1.1 /*
2     * 1541fs.cpp - 1541 emulation in host file system
3     *
4 cebix 1.2 * Frodo (C) 1994-1997,2002-2003 Christian Bauer
5 cebix 1.1 *
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 cebix 1.3 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 cebix 1.1 #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 cebix 1.3 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 cebix 1.1 #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 cebix 1.3 unsigned char c;
461 cebix 1.1
462     // Much of this is very similar to the original
463     if ((filename[0] == '0') && (filename[1] == 0)) {filename++;}
464 cebix 1.3
465 cebix 1.1 // Concatenate dir_path and pattern in buffer pattern ==> read subdirs!
466 cebix 1.3 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 cebix 1.1
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 cebix 1.3 return(ST_OK);
477     de.offset = 0; de.buffsize = NAMEBUF_LENGTH; de.match = p;
478 cebix 1.1
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 cebix 1.3 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 cebix 1.1 #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 cebix 1.3 if (c == ' ') {return(0xa0);} // space --> hard space
715 cebix 1.1 #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 cebix 1.3 if (c == 0xa0) {return(' ');} // hard space -> space
739 cebix 1.1 #endif
740     return c;
741     }