1 /** \file   fsdevice-open.c
2  * \brief   File system device
3  *
4  * \author  Andreas Boose <viceteam@t-online.de>
5  * \author  Teemu Rantanen <tvr@cs.hut.fi>
6  * \author  Jarkko Sonninen <sonninen@lut.fi>
7  * \author  Jouko Valta <jopi@stekt.oulu.fi>
8  * \author  Olaf Seibert <rhialto@mbfys.kun.nl>
9  * \author  Andre Fachat <a.fachat@physik.tu-chemnitz.de>
10  * \author  Ettore Perazzoli <ettore@comm2000.it>
11  * \author  pottendo <pottendo@gmx.net>
12  */
13 
14 /*
15  * This file is part of VICE, the Versatile Commodore Emulator.
16  * See README for copyright notice.
17  *
18  *  This program is free software; you can redistribute it and/or modify
19  *  it under the terms of the GNU General Public License as published by
20  *  the Free Software Foundation; either version 2 of the License, or
21  *  (at your option) any later version.
22  *
23  *  This program is distributed in the hope that it will be useful,
24  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
25  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26  *  GNU General Public License for more details.
27  *
28  *  You should have received a copy of the GNU General Public License
29  *  along with this program; if not, write to the Free Software
30  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
31  *  02111-1307  USA.
32  *
33  */
34 
35 #include "vice.h"
36 
37 /* #define DEBUG_DRIVEOPEN */
38 
39 #include <ctype.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 
44 #include "archdep.h"
45 #include "cbmdos.h"
46 #include "charset.h"
47 #include "fileio.h"
48 #include "fsdevice-filename.h"
49 #include "fsdevice-open.h"
50 #include "fsdevice-read.h"
51 #include "fsdevice-resources.h"
52 #include "fsdevice-write.h"
53 #include "fsdevicetypes.h"
54 #include "ioutil.h"
55 #include "lib.h"
56 #include "log.h"
57 #include "resources.h"
58 #include "tape.h"
59 #include "vdrive-command.h"
60 #include "vdrive.h"
61 #include "util.h"
62 
63 #ifdef DEBUG_DRIVEOPEN
64 #define DBG(x)  printf x
65 #else
66 #define DBG(x)
67 #endif
68 
69 /* shorten the path shown in the disk header to 16 characters */
makeshortheader(uint8_t * p)70 static uint8_t *makeshortheader(uint8_t *p)
71 {
72     int longnames = 0;
73     size_t n;
74     uint8_t *d;
75 
76     if (resources_get_int("FSDeviceLongNames", &longnames) < 0) {
77         return p;
78     }
79     n = strlen((char*)p);
80     if (!longnames && (n > 16)) {
81         d = p + (n - 1);
82         /* scan backwards until path seperator */
83         while (d != p) {
84             if (*d == '/') { /* FIXME: use macro */
85                 d++; n = 0;
86                 /* copy last part to the beginning */
87                 while (d) {
88                     p[n] = *d;
89                     n++;
90                     if (n == 16) {
91                         break;
92                     }
93                     d++;
94                 }
95                 p[n] = 0;
96                 return p;
97             }
98             d--;
99         }
100     }
101     return p;
102 }
103 
fsdevice_open_directory(vdrive_t * vdrive,unsigned int secondary,bufinfo_t * bufinfo,cbmdos_cmd_parse_t * cmd_parse,char * rname)104 static int fsdevice_open_directory(vdrive_t *vdrive, unsigned int secondary,
105                                    bufinfo_t *bufinfo,
106                                    cbmdos_cmd_parse_t *cmd_parse, char *rname)
107 {
108     struct ioutil_dir_s *ioutil_dir;
109     char *mask;
110     uint8_t *p;
111     int i;
112 
113     if ((secondary != 0) || (bufinfo[secondary].mode != Read)) {
114         fsdevice_error(vdrive, CBMDOS_IPE_NOT_WRITE);
115         return FLOPPY_ERROR;
116     }
117 
118     if (!(mask = strrchr(rname, '/'))) {
119         mask = rname;
120     }
121 
122     /* Test on wildcards.  */
123     if (cbmdos_parse_wildcard_check(mask, (unsigned int)strlen(mask))) {
124         if (*mask == '/') {
125             strcpy(bufinfo[secondary].dirmask, mask + 1);
126             *mask++ = 0;
127         } else {
128             strcpy(bufinfo[secondary].dirmask, mask);
129             lib_free(cmd_parse->parsecmd);
130             cmd_parse->parsecmd = lib_strdup(fsdevice_get_path(vdrive->unit));
131         }
132     } else {
133         bufinfo[secondary].dirmask[0] = '\0';
134         if (!*(cmd_parse->parsecmd)) {
135             lib_free(cmd_parse->parsecmd);
136             cmd_parse->parsecmd = lib_strdup(fsdevice_get_path(vdrive->unit));
137         }
138     }
139 
140     /* trying to open */
141     ioutil_dir = ioutil_opendir((char *)(cmd_parse->parsecmd), IOUTIL_OPENDIR_ALL_FILES);
142     if (ioutil_dir == NULL) {
143         for (p = (uint8_t *)(cmd_parse->parsecmd); *p; p++) {
144             if (isupper((int)*p)) {
145                 *p = tolower((int)*p);
146             }
147         }
148         ioutil_dir = ioutil_opendir((char *)(cmd_parse->parsecmd), IOUTIL_OPENDIR_ALL_FILES);
149         if (ioutil_dir == NULL) {
150             fsdevice_error(vdrive, CBMDOS_IPE_NOT_FOUND);
151             return FLOPPY_ERROR;
152         }
153     }
154     strcpy(bufinfo[secondary].dir, cmd_parse->parsecmd);
155     /*
156      * Start Address, Line Link and Line number 0
157      */
158 
159     p = bufinfo[secondary].name;
160 
161     *p++ = 1;
162     *p++ = 4;
163 
164     *p++ = 1;
165     *p++ = 1;
166 
167     *p++ = 0;
168     *p++ = 0;
169 
170     *p++ = (uint8_t)0x12;     /* Reverse on */
171 
172     *p++ = '"';
173     strcpy((char *)p, bufinfo[secondary].dir); /* Dir name */
174     charset_petconvstring((uint8_t *)p, 0);   /* ASCII name to PETSCII */
175     i = 0;
176 
177     makeshortheader(p);
178 
179     while (*p) {
180         ++p;
181         i++;
182     }
183     while (i < 16) {
184         *p++ = ' ';
185         i++;
186     }
187 
188     /* put the drive-unit and drive number into the "format id" */
189     *p++ = '"';
190     *p++ = ' ';
191     if (vdrive->drive < 10) {
192         *p++ = ' ';
193         *p++ = '#';
194         *p++ = '0' + vdrive->unit;
195     } else {
196         *p++ = '#';
197         *p++ = '1';
198         *p++ = '0' + (vdrive->unit - 10);
199     }
200     *p++ = ':';
201     *p++ = '0' + vdrive->drive;
202     *p++ = 0;
203 
204     bufinfo[secondary].buflen = (int)(p - bufinfo[secondary].name);
205     bufinfo[secondary].bufp = bufinfo[secondary].name;
206     bufinfo[secondary].mode = Directory;
207     bufinfo[secondary].ioutil_dir = ioutil_dir;
208     bufinfo[secondary].eof = 0;
209 
210     return FLOPPY_COMMAND_OK;
211 }
212 
fsdevice_open_file(vdrive_t * vdrive,unsigned int secondary,bufinfo_t * bufinfo,cbmdos_cmd_parse_t * cmd_parse,char * rname)213 static int fsdevice_open_file(vdrive_t *vdrive, unsigned int secondary,
214                               bufinfo_t *bufinfo,
215                               cbmdos_cmd_parse_t *cmd_parse, char *rname)
216 {
217     char *comma;
218     char *newrname;
219     tape_image_t *tape;
220     unsigned int format = 0;
221     fileio_info_t *finfo;
222     int fileio_command;
223 
224     if (fsdevice_convert_p00_enabled[(vdrive->unit) - 8]) {
225         format |= FILEIO_FORMAT_P00;
226     }
227     if (!fsdevice_hide_cbm_files_enabled[vdrive->unit - 8]) {
228         format |= FILEIO_FORMAT_RAW;
229     }
230 
231     /* Remove comma.  */
232     if ((cmd_parse->parsecmd)[0] == ',') {
233         (cmd_parse->parsecmd)[1] = '\0';
234     } else {
235         comma = strchr(cmd_parse->parsecmd, ',');
236         if (comma != NULL) {
237             *comma = '\0';
238         }
239     }
240 
241     /* Test on wildcards.  */
242     if (cbmdos_parse_wildcard_check(cmd_parse->parsecmd,
243                                     (unsigned int)strlen(cmd_parse->parsecmd)) > 0) {
244         if (bufinfo[secondary].mode == Write
245             || bufinfo[secondary].mode == Append) {
246             fsdevice_error(vdrive, CBMDOS_IPE_BAD_NAME);
247             return FLOPPY_ERROR;
248         }
249     }
250 
251     /* Open file for write mode access.  */
252     if (bufinfo[secondary].mode == Write) {
253 
254         if (fsdevice_save_p00_enabled[vdrive->unit - 8]) {
255             format = FILEIO_FORMAT_P00;
256         } else {
257             format = FILEIO_FORMAT_RAW;
258         }
259 
260         DBG(("fsdevice_open_file write '%s'\n", rname));
261         fsdevice_limit_createnamelength(vdrive, rname);
262         DBG(("fsdevice_open_file write limited: '%s'\n", rname));
263 
264         if (cmd_parse->atsign) {
265             /* TODO: maybe rename to a backup name */
266             DBG(("fsdevice_open_file overwrite @'%s'\n", rname));
267             fileio_command = FILEIO_COMMAND_OVERWRITE;
268         } else if (fsdevice_overwrite_existing_files) {
269             fileio_command = FILEIO_COMMAND_OVERWRITE;
270         } else {
271             fileio_command = FILEIO_COMMAND_WRITE;
272         }
273 
274         finfo = fileio_open(rname, fsdevice_get_path(vdrive->unit), format,
275                             fileio_command, bufinfo[secondary].type,
276                             &bufinfo[secondary].reclen);
277 
278         if (finfo != NULL) {
279             bufinfo[secondary].fileio_info = finfo;
280             fsdevice_error(vdrive, CBMDOS_IPE_OK);
281             return FLOPPY_COMMAND_OK;
282         } else {
283             fsdevice_error(vdrive, CBMDOS_IPE_FILE_EXISTS);
284             return FLOPPY_ERROR;
285         }
286     }
287 
288     if (bufinfo[secondary].mode == Append) {
289         /* Open file for append mode access.  */
290         DBG(("fsdevice_open_file append '%s'\n", rname));
291         newrname = fsdevice_expand_shortname(vdrive, rname);
292         DBG(("fsdevice_open_file append expanded '%s'\n", newrname));
293         finfo = fileio_open(newrname, fsdevice_get_path(vdrive->unit), format,
294                             FILEIO_COMMAND_APPEND_READ,
295                             bufinfo[secondary].type,
296                             &bufinfo[secondary].reclen);
297         lib_free(newrname);
298 
299         if (finfo != NULL) {
300             bufinfo[secondary].fileio_info = finfo;
301             fsdevice_error(vdrive, CBMDOS_IPE_OK);
302             return FLOPPY_COMMAND_OK;
303         } else {
304             fsdevice_error(vdrive, CBMDOS_IPE_NOT_FOUND);
305             return FLOPPY_ERROR;
306         }
307     }
308 
309     /* Open file for read or relative mode access.  */
310     tape = bufinfo[secondary].tape;
311     tape->name = util_concat(fsdevice_get_path(vdrive->unit),
312                              FSDEV_DIR_SEP_STR, rname, NULL);
313     charset_petconvstring((uint8_t *)(tape->name) +
314                           strlen(fsdevice_get_path(vdrive->unit)) +
315                           strlen(FSDEV_DIR_SEP_STR), 1);
316     tape->read_only = 1;
317     /* Prepare for buffered reads */
318     bufinfo[secondary].isbuffered = 0;
319     bufinfo[secondary].iseof = 0;
320     if (tape_image_open(tape) < 0) {
321         lib_free(tape->name);
322         tape->name = NULL;
323     } else {
324         tape_file_record_t *r;
325         static uint8_t startaddr[2];
326         tape_seek_start(tape);
327         tape_seek_to_file(tape, 0);
328         r = tape_get_current_file_record(tape);
329         if ((r->type == 1) || (r->type == 3)) {
330             startaddr[0] = r->start_addr & 255;
331             startaddr[1] = r->start_addr >> 8;
332             bufinfo[secondary].bufp = startaddr;
333             bufinfo[secondary].buflen = 2;
334         } else {
335             bufinfo[secondary].buflen = 0;
336         }
337 
338         return FLOPPY_COMMAND_OK;
339     }
340 
341 
342     DBG(("fsdevice_open_file read '%s'\n", rname));
343     newrname = fsdevice_expand_shortname(vdrive, rname);
344     DBG(("fsdevice_open_file read expanded '%s'\n", newrname));
345 
346     fileio_command =
347         bufinfo[secondary].mode == Relative ? FILEIO_COMMAND_READ_WRITE
348                                             : FILEIO_COMMAND_READ;
349 
350     finfo = fileio_open(newrname, fsdevice_get_path(vdrive->unit), format,
351                         fileio_command, bufinfo[secondary].type,
352                         &bufinfo[secondary].reclen);
353 
354     lib_free(newrname);
355 
356     if (finfo != NULL) {
357         bufinfo[secondary].fileio_info = finfo;
358         fsdevice_error(vdrive, CBMDOS_IPE_OK);
359 
360         if (bufinfo[secondary].mode == Relative) {
361             fsdevice_relative_switch_record(vdrive, &bufinfo[secondary], 0, 0);
362         }
363 
364         return FLOPPY_COMMAND_OK;
365     }
366 
367     fsdevice_error(vdrive, CBMDOS_IPE_NOT_FOUND);
368     return FLOPPY_ERROR;
369 }
370 
fsdevice_open_buffer(vdrive_t * vdrive,unsigned int secondary,bufinfo_t * bufinfo,cbmdos_cmd_parse_t * cmd_parse,char * rname)371 static int fsdevice_open_buffer(vdrive_t *vdrive, unsigned int secondary,
372                                 bufinfo_t *bufinfo,
373                                 cbmdos_cmd_parse_t *cmd_parse, char *rname)
374 {
375     log_message(LOG_DEFAULT, "Fsdevice: Warning - open channel '%s'. (block access needs disk image)", rname);
376     fsdevice_error(vdrive, CBMDOS_IPE_OK);
377     return FLOPPY_COMMAND_OK;
378 }
379 
fsdevice_open(vdrive_t * vdrive,const uint8_t * name,unsigned int length,unsigned int secondary,cbmdos_cmd_parse_t * cmd_parse_ext)380 int fsdevice_open(vdrive_t *vdrive, const uint8_t *name, unsigned int length,
381                   unsigned int secondary, cbmdos_cmd_parse_t *cmd_parse_ext)
382 {
383     char *rname;
384     int status = 0, rc;
385     unsigned int i;
386     cbmdos_cmd_parse_t cmd_parse;
387     bufinfo_t *bufinfo;
388 
389     DBG(("fsdevice_open name:'%s' (secondary:%u)\n", name, secondary));
390 
391     bufinfo = fsdevice_dev[vdrive->unit - 8].bufinfo;
392 
393     if (bufinfo[secondary].fileio_info != NULL) {
394         return FLOPPY_ERROR;
395     }
396 
397     if (secondary == 15) {
398         for (i = 0; i < length; i++) {
399             status = fsdevice_write(vdrive, name[i], 15);
400         }
401         return status;
402     }
403     cmd_parse.cmd = name;
404     cmd_parse.cmdlength = length;
405     cmd_parse.secondary = secondary;
406 
407     DBG(("fsdevice_open cmd_parse '%s'\n", name));
408 
409     rc = cbmdos_command_parse(&cmd_parse);
410     if (rc != SERIAL_OK) {
411         status = SERIAL_ERROR;
412         goto out;
413     }
414 
415     /*
416        Check for '@0:filename' or '@:filename'. Must include a ':'.
417        (original filename starts with '@', but parsed version doesn't)
418        '@filename' will open a file starting with '@'!
419      */
420     if (length > 0 && name[0] == '@' &&
421           !(cmd_parse.parselength > 0 && cmd_parse.parsecmd[0] == '@')) {
422         cmd_parse.atsign = 1;
423     }
424 
425     bufinfo[secondary].type = cmd_parse.filetype;
426     bufinfo[secondary].reclen = cmd_parse.recordlength;
427     bufinfo[secondary].num_records = -1;
428 
429     rname = lib_malloc(ioutil_maxpathlen());
430 
431     cmd_parse.parsecmd[cmd_parse.parselength] = 0;
432     strncpy(rname, cmd_parse.parsecmd, cmd_parse.parselength + 1);
433 
434     /* CBM name to FSname */
435     charset_petconvstring((uint8_t *)(cmd_parse.parsecmd), 1);
436     DBG(("fsdevice_open rname: %s\n", rname));
437 
438     if (cmd_parse.filetype == CBMDOS_FT_REL) {
439         /* REL files override whatever rwmode has been inferred by
440          * parsecmd() */
441         bufinfo[secondary].mode = Relative;
442     } else {
443         switch (cmd_parse.readmode) {
444             case CBMDOS_FAM_WRITE:
445                 bufinfo[secondary].mode = Write;
446                 break;
447             case CBMDOS_FAM_READ:
448                 bufinfo[secondary].mode = Read;
449                 break;
450             case CBMDOS_FAM_APPEND:
451                 bufinfo[secondary].mode = Append;
452                 break;
453         }
454     }
455 
456     /*
457         check wether the length of the name string does not match the
458         passed length. this might happen if a) the cbm filename in the
459         running program is zero terminated and b) a wrong namelen is
460         passed to SETNAM. this would almost certainly result in a "file
461         not found" - so it is the best we can do in that case, too.
462     */
463     if (strlen((const char*)name) != length) {
464         log_message(LOG_DEFAULT,
465                 "Fsdevice: Warning - filename '%s' with bogus length '%u'.",
466                 cmd_parse.parsecmd, length);
467         status = CBMDOS_IPE_NOT_FOUND;
468         goto out;
469     }
470 
471     if (*name == '$') {
472         status = fsdevice_open_directory(vdrive, secondary, bufinfo, &cmd_parse, rname);
473     } else if (*name == '#') {
474         status = fsdevice_open_buffer(vdrive, secondary, bufinfo, &cmd_parse, rname);
475     } else {
476         status = fsdevice_open_file(vdrive, secondary, bufinfo, &cmd_parse, rname);
477     }
478 
479     lib_free(rname);
480 
481     if (status != FLOPPY_COMMAND_OK) {
482         goto out;
483     }
484 
485     fsdevice_error(vdrive, CBMDOS_IPE_OK);
486 
487 out:
488     lib_free(cmd_parse.parsecmd);
489 
490     return status;
491 }
492