1 /** \file   src/c1541.c
2  * \brief   Stand-alone disk image maintenance program
3  *
4  *
5  * \author  Ettore Perazzoli <ettore@comm2000.it>
6  * \author  Teemu Rantanen <tvr@cs.hut.fi>
7  * \author  Jouko Valta <jopi@zombie.oulu.fi>
8  * \author  Gerhard Wesp <gwesp@cosy.sbg.ac.at>
9  * \author  Daniel Sladic <sladic@eecg.toronto.edu>
10  * \author  Ricardo Ferreira <storm@esoterica.pt>
11  * \author  Andreas Boose <viceteam@t-online.de>
12  * \author  Bas Wassink <b.wassink@ziggo.nl>
13  *
14  * Patches by
15  *  Olaf Seibert <rhialto@falu.nl>
16  *  Dirk Schnorpfeil <D.Schnorpfeil@web.de> (GEOS stuff)
17  *
18  * Zipcode implementation based on `zip2disk' by
19  *  Paul David Doherty <h0142kdd@rz.hu-berlin.de>
20  */
21 
22 /*
23  * This file is part of VICE, the Versatile Commodore Emulator.
24  * See README for copyright notice.
25  *
26  *  This program is free software; you can redistribute it and/or modify
27  *  it under the terms of the GNU General Public License as published by
28  *  the Free Software Foundation; either version 2 of the License, or
29  *  (at your option) any later version.
30  *
31  *  This program is distributed in the hope that it will be useful,
32  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
33  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
34  *  GNU General Public License for more details.
35  *
36  *  You should have received a copy of the GNU General Public License
37  *  along with this program; if not, write to the Free Software
38  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
39  *  02111-1307  USA.
40  *
41  */
42 
43 #include "vice.h"
44 #include "diskimage.h"
45 #include "fsimage.h"
46 #include "diskcontents.h"
47 #include "machine.h"
48 
49 #include "version.h"
50 
51 #ifdef USE_SVN_REVISION
52 # include "svnversion.h"
53 #endif
54 
55 #include <ctype.h>
56 #include <stdarg.h>
57 #include <stdbool.h>
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <stdint.h>
61 #include <limits.h>
62 
63 #include <string.h>
64 
65 #ifdef HAVE_ERRNO_H
66 #include <errno.h>
67 #endif
68 
69 #ifdef HAVE_FCNTL_H
70 #include <fcntl.h>
71 #endif
72 
73 #ifdef HAVE_STRINGS_H
74 #include <strings.h>
75 #endif
76 
77 #include "archdep.h"
78 #include "archdep_defs.h"
79 #include "cbmdos.h"
80 #include "cbmimage.h"
81 #include "charset.h"
82 #include "cmdline.h"
83 #include "drive.h"
84 #include "diskimage.h"
85 #include "fileio.h"
86 #include "fsimage-check.h"
87 #include "gcr.h"
88 #include "info.h"
89 #include "imagecontents.h"
90 #include "ioutil.h"
91 #include "lib.h"
92 #include "log.h"
93 #include "serial.h"
94 #include "snapshot.h"
95 #include "tape.h"
96 #include "util.h"
97 #include "uiapi.h"
98 #include "vdrive-bam.h"
99 #include "vdrive-command.h"
100 #include "vdrive-dir.h"
101 #include "vdrive-iec.h"
102 #include "vdrive-rel.h"
103 #include "vdrive.h"
104 #include "vice-event.h"
105 #include "zipcode.h"
106 #include "p64.h"
107 #include "fileio/p00.h"
108 
109 #include "lib/linenoise-ng/linenoise.h"
110 
111 #ifdef ARCHDEP_OS_UNIX
112 #include <unistd.h>
113 #endif
114 
115 /* #define DEBUG_DRIVE */
116 
117 #define MAXARG          256 + 5 /**< maximum number arguments to a command,
118                                      the +5 comes from the bpoke() command,
119                                      that one uses 5 args for its name,
120                                      unit, track, sector, offset. And another
121                                      max 256 for its data */
122 
123 #define RAW_BLOCK_SIZE  256     /**< size of a block/sector, including the
124                                      (track,sector) pointer */
125 
126 /** \brief  Number of bytes to display per line for the `block` command
127  *
128  * Normally, on 80 character terminals, 16 bytes is a decent number.
129  */
130 #define BLOCK_CMD_WIDTH     16
131 
132 
133 /** \brief  Magic bytes for a P00 header
134  */
135 const char p00_header[] = "C64File";
136 
137 /** \brief  Flags for each virtual drive indicating P00 mode
138  *
139  * When zero, reading files from the host OS is done with FILEIO_MODE_RAW, if
140  * non-zero, reading files is done with FILEIO_MODE_P00
141  */
142 static unsigned int p00save[NUM_DISK_UNITS] = { 0, 0, 0, 0 };
143 
144 /** \brief  Current virtual drive used
145  *
146  * This is an index into the `drives` array, NOT a device/unit number
147  */
148 static int drive_index = 0;
149 
150 /** \brief  Flag indicating if c1541 is used in interactive mode
151  */
152 static int interactive_mode = 0;
153 
154 
155 /*
156  * forward declaration of functions
157  */
158 
159 /* helpers */
160 static int check_drive_unit(int unit);
161 static int check_drive_index(int index);
162 static int check_drive_ready(int index);
163 static int parse_track_sector(const char *trk_str, const char *sec_str,
164                               unsigned int *trk_num, unsigned int *sec_num);
165 static int translate_fsimage_error(int err);
166 static const char *image_format_name(unsigned int type);
167 
168 
169 /* command handlers */
170 static int attach_cmd(int nargs, char **args);
171 static int bam_cmd(int nargs, char **args);
172 static int bcopy_cmd(int nargs, char **args);
173 static int bfill_cmd(int nargs, char **args);
174 static int block_cmd(int nargs, char **args);
175 static int bpoke_cmd(int nargs, char **args);
176 static int bread_cmd(int nargs, char **args);
177 static int bwrite_cmd(int nargs, char **args);
178 static int chain_cmd(int nargs, char **args);
179 static int copy_cmd(int nargs, char **args);
180 static int delete_cmd(int nargs, char **args);
181 static int entry_cmd(int nargs, char **args);
182 static int extract_cmd(int nargs, char **args);
183 static int extract_geos_cmd(int nargs, char **args);
184 static int format_cmd(int nargs, char **args);
185 static int help_cmd(int nargs, char **args);
186 static int info_cmd(int nargs, char **args);
187 static int list_cmd(int nargs, char **args);
188 static int name_cmd(int nargs, char **args);
189 static int p00save_cmd(int nargs, char **args);
190 static int pwd_cmd(int nargs, char **args);
191 static int quit_cmd(int nargs, char **args);
192 static int raw_cmd(int nargs, char **args); /* @ */
193 static int read_cmd(int nargs, char **args);
194 static int read_geos_cmd(int nargs, char **args);
195 static int rename_cmd(int nargs, char **args);
196 static int silent_cmd(int narg, char **args);
197 static int show_cmd(int nargs, char **args);
198 static int tape_cmd(int nargs, char **args);
199 static int unit_cmd(int nargs, char **args);
200 static int unlynx_cmd(int nargs, char **args);
201 static int unzip_cmd(int nargs, char **args);
202 static int validate_cmd(int nargs, char **args);
203 static int verbose_cmd(int nargs, char **args);
204 static int version_cmd(int nargs, char **args);
205 static int write_cmd(int nargs, char **args);
206 static int write_geos_cmd(int nargs, char **args);
207 
208 /* other functions */
209 static int open_image(int dev, char *name, int create, int disktype);
210 
211 int internal_read_geos_file(int unit, FILE* outf, char* src_name_ascii);
212 static int fix_ts(int unit, unsigned int trk, unsigned int sec,
213                   unsigned int next_trk, unsigned int next_sec,
214                   unsigned int blk_offset);
215 static int internal_write_geos_file(int unit, FILE* f);
216 
217 /* ------------------------------------------------------------------------- */
218 
219 /** \brief  Array of virtual drives
220  *
221  * FIXME: this should be in c1541-stubs.c
222  *
223  */
224 static vdrive_t *drives[NUM_DISK_UNITS] = { NULL, NULL, NULL, NULL };
225 
file_system_get_vdrive(unsigned int unit)226 struct vdrive_s *file_system_get_vdrive(unsigned int unit)
227 {
228     if (unit < DRIVE_UNIT_MIN || unit > DRIVE_UNIT_MAX) {
229         printf("Wrong unit for vdrive");
230         return NULL;
231     }
232 
233     return drives[unit - 8];
234 }
235 
236 /* ------------------------------------------------------------------------- */
237 
238 /** \brief  c1541 command data struct
239  */
240 struct command_s {
241     const char *name;                       /**< command name */
242     const char *syntax;                     /**< command syntax help */
243     const char *description;                /**< command description */
244     unsigned int min_args;                  /**< minimum number of arguments */
245     unsigned int max_args;                  /**< maximum number of arguments */
246     int (*func)(int nargs, char **args);    /**< command handler */
247 };
248 typedef struct command_s command_t;
249 
250 
251 /** \brief  List of commands
252  */
253 const command_t command_list[] = {
254     { "@",
255       "@ [<command>]",
256       "Execute specified CBM DOS command and print the current status of the\n"
257       "drive.  If no <command> is specified, just print the status.",
258       0, 1,
259       raw_cmd },
260     { "?",
261       "? [<command>]",
262       "Explain specified command.  If no command is specified, "
263       "list available\n"      "ones.",
264       0, 1,
265       help_cmd },
266     { "attach",
267       "attach <diskimage> [<unit>]",
268       "Attach <diskimage> to <unit> (default unit is 8).",
269       1, 2,
270       attach_cmd },
271     { "bam",
272       "bam [<unit>] | <track-min> <track-max> [<unit>]",
273       "Show BAM of disk image, optionally limiting the dump to <track-min> - "
274       "<track-max>",
275       0, 3,
276       bam_cmd },
277     { "bcopy",
278       "bcopy <src-track> <src-sector> <dst-track> <dst-sector> [<src-unit> "
279       "[<dst-unit>]]",
280       "Copy a block to another block, optionally using different units. "
281       "If no unit\nis given, the current unit is used; if only one unit is "
282       "given, that unit\nis used for both source and destination.",
283       4, 6,
284       bcopy_cmd },
285     { "bfill",
286       "bfill <track> <sector> <value> [<unit>]",
287       "Fill a block with a single value.",
288       3, 4,
289       bfill_cmd },
290     { "block",
291       "block <track> <sector> [<offset>] [<unit>]",
292       "Show specified disk block in hex form.",
293       2, 4,
294       block_cmd },
295     { "bpoke",
296       "bpoke [@<unit>:] <track> <sector> <offset> <data ...>",
297       "Poke <data> into block at (<track>,<sector>), starting at <offset>",
298       4, MAXARG,
299       bpoke_cmd },
300     { "bread",
301       "bread <filename> <track> <sector> [<unit>]",
302       "Read block data from (<track>,<sector>) and write as <filename> to "
303       "the host file system",
304       3, 4,
305       bread_cmd },
306     { "bwrite",
307       "bwrite <filename> <track> <sector> [<unit>]",
308       "Write block data to (<track>,<sector>) from <filename> at the host "
309       "file system",
310       3, 4,
311       bwrite_cmd },
312     { "chain",
313       "chain <track> <sector> [<unit>]",
314       "Follow and print block chain starting at (<track>,<sector>)",
315       2, 3,
316       chain_cmd },
317     { "copy",
318       "copy <source1> [<source2> ... <sourceN>] <destination>",
319       "Copy `source1' ... `sourceN' into destination.  If N > 1, "
320       "`destination'\n must be a simple drive specifier (@n:).",
321       2, MAXARG,
322       copy_cmd },
323     { "delete",
324       "delete <file1> [<file2> ... <fileN>]",
325       "Delete the specified files.",
326       1, MAXARG,
327       delete_cmd },
328     { "dir",
329       "dir [<pattern>]",
330       "List files matching <pattern> (default is all files).",
331       0, 1,
332       list_cmd },
333     { "entry",
334       "entry [+side] <file1> [<file2> ... <fileN>]",
335       "Show detailed directory entry of each <file>.",
336       1, MAXARG,
337       entry_cmd },
338     { "exit",
339       "exit",
340       "Exit (same as `quit').",
341       0, 0,
342       quit_cmd },
343     { "extract",
344       "extract [<unit>]",
345       "Extract all the files to the file system.",
346       0, 1,
347       extract_cmd },
348     { "format",
349       "format <diskname,id> [<type> <imagename>] [<unit>]",
350       "If <unit> is specified, format the disk in unit <unit>.\n"
351       "If <type> and <imagename> are specified, create a new image named\n"
352       "<imagename>, attach it to unit 8 and format it.  <type> is a disk image\n"
353       "type, and must be either "
354 #ifdef HAVE_X64_IMAGE
355       "`x64', "
356 #endif
357       "`d64' (both VC1541/2031), `g64' (VC1541/2031,\n"
358       "but in GCR coding), `d67' (2040 DOS1), `d71' (VC1571), `g71' (VC1571,\n"
359       "but in GCR coding), `d81' (VC1581), `d80' (CBM8050) or `d82' (CBM8250).\n"
360       "Otherwise, format the disk in the current unit, if any.",
361       1, 4,
362       format_cmd },
363     { "geosread",
364       "geosread <source> [<destination>]",
365       "Read GEOS <source> from the disk image and copy it as a Convert file "
366       "into \n<destination> in the file system.  If <destination> is not "
367       "specified, copy \nit into a file with the same name as <source>."
368       "\nPlease note that due to GEOS using ASCII, not PETSCII, the name should"
369       " be\bentered in inverted case (ie to read 'rEADmE', use 'ReadMe'",
370       1, 2,
371       read_geos_cmd },
372     { "geoswrite",
373       "geoswrite <source>",
374       "Write GEOS Convert file <source> from the file system on a disk image.",
375       1, 1,
376       write_geos_cmd },
377     { "geosextract",
378       "geosextract <source>",
379       "Extract all the files to the file system and GEOS Convert them.",
380       0, 1,
381       extract_geos_cmd },
382     { "help",
383       "help [<command>]",
384       "Explain specified command.  If no command is specified, list "
385       "available\n"      "ones.",
386       0, 1,
387       help_cmd },
388     { "info",
389       "info [<unit>]",
390       "Display information about unit <unit> (if unspecified, use the "
391       "current\none).",
392       0, 1,
393       info_cmd },
394     { "list",
395       "list [<pattern>]",
396       "List files matching <pattern> (default is all files).",
397       0, 1,
398       list_cmd },
399     { "name",
400       "name <diskname>[,<id>] <unit>",
401       "Change image name.",
402       1, 2,
403       name_cmd },
404     { "p00save",
405       "p00save [<enable> [<unit>]]",
406       "Save P00 files to the file system. If no argument is given, print the "
407       "state for all drives.\n"
408       "The <enable> argument should be either 0 or 1.",
409       0, 2,
410       p00save_cmd },
411     { "pwd",
412       "pwd",
413       "Show current host directory path",
414       0, 0,
415       pwd_cmd },
416     { "quit",
417       "quit",
418       "Exit (same as `exit').",
419       0, 0,
420       quit_cmd },
421     { "read",
422       "read <source> [<destination>]",
423       "Read <source> from the disk image and copy it into <destination> in\n"
424       "the file system.  If <destination> is not specified, copy it into a\n"
425       "file with the same name as <source>.",
426       1, 2,
427       read_cmd },
428     { "rename",
429       "rename <oldname> <newname>",
430       "Rename <oldname> into <newname>.  The files must be on the same drive.",
431       2, 2,
432       rename_cmd },
433     { "silent",
434       "silent <off>",
435       "Disable all logging",
436       0, 1,
437       silent_cmd },
438     { "show",
439       "show [copying | warranty]",
440       "Show conditions for redistributing copies of C1541 (`copying') or the\n"
441       "various kinds of warranty you do not have with C1541 (`warranty').",
442       1, 1,
443       show_cmd },
444     { "tape",
445       "tape <t64name> [<file1> ... <fileN>]",
446       "Extract files from a T64 image into the current drive.",
447       1, MAXARG,
448       tape_cmd },
449     { "unit",
450       "unit <number>",
451       "Make unit <number> the current unit.",
452       1, 1,
453       unit_cmd },
454     { "unlynx",
455       "unlynx <lynxname> [<unit>]",
456       "Extract the specified Lynx image file into the specified unit "
457       "(default\nis the current unit).",
458       1, 2,
459       unlynx_cmd },
460     { "unzip",
461       "unzip <d64name> <zipname> [<label,id>]",
462       "Create a D64 disk image out of a set of four Zipcoded files named\n"
463       "`1!<zipname>', `2!<zipname>', `3!<zipname>' and `4!<zipname>'.",
464       2, 3,
465       unzip_cmd },
466     { "validate",
467       "validate [<unit>]",
468       "Validate the disk in unit <unit>.  If <unit> is not specified, "
469       "validate\nthe disk in the current unit.",
470       0, 1,
471       validate_cmd },
472     { "verbose",
473       "verbose [off]",
474       "Enable verbose output. Use 'verbose off' to disable.",
475       0, 1,
476       verbose_cmd },
477     { "version",
478       "version",
479       "Display C1541 version",
480       0, 0,
481       version_cmd },
482     { "write",
483       "write <source> [<destination>]",
484       "Write <source> from the file system into <destination> on a disk "
485       "image.",
486       1, 2,
487       write_cmd },
488     { "x",
489       "x",
490       "Exit (same as 'quit', mirrors monitor 'x')",
491       0, 0,
492       quit_cmd },
493     /* FIXME: name is wrong: this doesn't create a zipcoded archive, but
494      *        dissolves one, so a better name would be 'unzip' or 'zdecode'.
495      * (BW) */
496 #if 0
497     { "zcreate",
498       "zcreate <d64name> <zipname> [<label,id>]",
499       "Create a D64 disk image out of a set of four Zipcoded files named\n"
500       "`1!<zipname>', `2!<zipname>', `3!<zipname>' and `4!<zipname>'.",
501       2, 3,
502       zcreate_cmd },
503 #endif
504     { NULL, NULL, NULL, 0, 0, NULL }
505 };
506 
507 /* ------------------------------------------------------------------------- */
508 
509 #if 0
510 #if defined(HAVE_READLINE) && defined(HAVE_READLINE_READLINE_H)
511 #include <readline/readline.h>
512 #include <readline/history.h>
513 
514 /** \brief  Read a line of input from stdin
515  *
516  * \param[in]   prompt  prompt to display to the user
517  *
518  * \return  line read from stdin
519  */
520 static char *read_line(const char *prompt)
521 {
522     static char *line = NULL;
523 
524     if (line != NULL) {
525         free(line);
526     }
527     line = readline(prompt);
528     if (line != NULL && *line != 0) {
529         add_history(line);
530     }
531     return line;
532 }
533 
534 #else
535 
536 /** \brief  Read a line of input from stdin
537  *
538  * \param[in]   prompt  prompt to display to the user
539  *
540  * \return  line read from stdin
541  */
542 static char *read_line(const char *prompt)
543 {
544     static char line[1024];
545 
546     /* Make sure there is a 0 at the end of the string */
547     line[sizeof(line) - 1] = 0;
548 
549     fputs(prompt, stdout);
550     fflush(stdout);
551     return fgets(line, sizeof(line) - 1, stdin);
552 }
553 
554 #endif
555 #endif
556 
557 
558 /** \brief  Split \a line into a list of arguments
559  *
560  * The \a args array is reused on each call, (re)allocating elements when
561  * required.
562  *
563  * \param[in]   line    input string
564  * \param[out]  nargs   number of arguments parsed from \a line
565  * \param[out]  args    arguments parsed from \a line
566  *
567  * \return  0 on success, -1 on failure
568  */
split_args(const char * line,int * nargs,char ** args)569 static int split_args(const char *line, int *nargs, char **args)
570 {
571     const char *s;
572     char *d;
573     char tmp[256];
574     int begin_of_arg, in_quote;
575 
576     *nargs = 0;
577 
578     in_quote = 0;
579     d = tmp;
580     begin_of_arg = 1;
581 
582     for (s = line;; s++) {
583         switch (*s) {
584             case '"':
585                 begin_of_arg = 0;
586                 in_quote = !in_quote;
587                 continue;
588 #ifndef WIN32_COMPILE
589             case '\\':
590                 begin_of_arg = 0;
591                 *(d++) = *(++s);
592                 continue;
593 #endif
594             case ' ':   /* fallthrough */
595             case '\t':  /* fallthrough */
596             case '\n':  /* fallthrough */
597             case '\r':  /* fallthrough */
598             case 0:
599                 if (*s == 0 && in_quote) {
600                     fprintf(stderr, "unbalanced quotes\n");
601                     return -1;
602                 }
603                 if (!in_quote && !begin_of_arg) {
604                     if (*nargs == MAXARG) {
605                         fprintf(stderr, "too many arguments\n");
606                         return -1;
607                     } else {
608                         size_t len = (size_t)(d - tmp);
609                         if (args[*nargs] != NULL) {
610                             args[*nargs] = lib_realloc(args[*nargs], len + 1);
611                         } else {
612                             args[*nargs] = lib_malloc(len + 1);
613                         }
614                         memcpy(args[*nargs], tmp, len);
615                         args[*nargs][len] = 0;
616                         begin_of_arg = 1;
617                         (*nargs)++;
618                         d = tmp;
619                     }
620                 }
621                 if (*s == 0) {
622                     return 0;
623                 }
624                 if (!(*s == ' ' && in_quote)) {
625                     break;
626                 }       /* fallthrough */
627             default:
628                 begin_of_arg = 0;
629                 *(d++) = *s;
630         }
631     }
632 
633     return 0;
634 }
635 
636 
637 /** \brief  Convert \a arg to int
638  *
639  * This function accepts multiple bases, depending on the prefix of the arg.
640  *
641  * Basically it comes down to this (all resolve to 42 decimal)
642  *
643  * | prefix | base | example   |
644  * |:------:| ----:| --------- |
645  * | [none] | 10   | 042       |
646  * | %      | 2    | %101010   |
647  * | &      | 8    | &52       |
648  * | $      | 16   | $2a       |
649  * | 0[bB]  | 2    | 0b%101010 |
650  * | 0[xX]  | 16   | 0x2a      |
651  *
652  * \param[in]   arg             string containing a possible integer literal
653  * \param[out]  return_value    \a arg convert to int
654  *
655  * \return  0 on success, -1 on failure
656  *
657  * \note    on error, \a return_value is set to `INT_MIN`
658  */
arg_to_int(const char * arg,int * return_value)659 static int arg_to_int(const char *arg, int *return_value)
660 {
661     char *tailptr;
662     int counter = 0;
663     int base = 10;
664 
665     /* set value for error conditions, keeps compiler happy */
666     *return_value = INT_MIN;
667 
668     if (arg == NULL || *arg == '\0') {
669         return -1;
670     }
671     /* determine base */
672     switch (*arg) {
673         case '%':
674             base = 2;
675             arg++;
676             break;
677         case '&':
678             base = 8;
679             arg++;
680             break;
681         case '$':
682             arg++;
683             base = 16;
684             break;
685         case '0':
686             arg++;
687             if (*arg == 'b' || *arg == 'B') {
688                 base = 2;
689                 arg++;
690             } else if (*arg == 'x' || *arg == 'X') {
691                 base = 16;
692                 arg++;
693             }
694             break;
695         default:
696             break;  /* base is already 10 */
697     }
698 
699 
700     *return_value = (int)strtol(arg, &tailptr, base);
701 
702     if (ioutil_errno(IOUTIL_ERRNO_ERANGE)) {
703         return -1;
704     }
705 
706     /* Only whitespace is allowed after the last valid character.  */
707     if (!util_check_null_string(tailptr)) {
708         while (isspace((int)tailptr[counter])) {
709             counter++;
710         }
711         tailptr += counter;
712         if (*tailptr != 0) {
713             return -1;
714         }
715     }
716     return 0;
717 }
718 
719 
720 /** \brief  Print error message for \a errval on stderr
721  *
722  * \param[in]   errval  error code
723  */
print_error_message(int errval)724 static void print_error_message(int errval)
725 {
726     if (errval < 0) {
727         switch (errval) {
728             case FD_OK:
729                 break;
730             case FD_NOTREADY:
731                 fprintf(stderr, "drive not ready\n");
732                 break;
733             case FD_CHANGED:
734                 fprintf(stderr, "image file has changed on disk\n");
735                 break;
736             case FD_NOTRD:
737                 fprintf(stderr, "cannot read file\n");
738                 break;
739             case FD_NOTWRT:
740                 fprintf(stderr, "cannot write file\n");
741                 break;
742             case FD_WRTERR:
743                 fprintf(stderr, "floppy write failed\n");
744                 break;
745             case FD_RDERR:
746                 fprintf(stderr, "floppy read failed\n");
747                 break;
748             case FD_INCOMP:
749                 fprintf(stderr, "incompatible DOS version\n");
750                 break;
751             case FD_BADIMAGE:
752                 fprintf(stderr, "invalid image\n"); /* Disk or tape */
753                 break;
754             case FD_BADNAME:
755                 fprintf(stderr, "invalid filename\n");
756                 break;
757             case FD_BADVAL:
758                 fprintf(stderr, "illegal value\n");
759                 break;
760             case FD_BADDEV:
761                 fprintf(stderr, "illegal device number\n");
762                 break;
763             case FD_BAD_TS:
764                 fprintf(stderr, "inaccessible track or sector\n");
765                 break;
766             case FD_BAD_TRKNUM:
767                 fprintf(stderr, "illegal track number\n");
768                 break;
769             case FD_BAD_SECNUM:
770                 fprintf(stderr, "illegal sector number\n");
771                 break;
772             default:
773                 fprintf(stderr, "<unknown error>\n");
774         }
775     }
776 }
777 
778 
779 /** \brief  Translate error codes from fsimage-check.c to FD_ error codes
780  *
781  * \param[in]   err error code from fsimage-check.c
782  *
783  * \return      translated error code
784  *
785  * \todo        Use this whenever disk_image_check_sector() is used. Now the
786  *              error code returned is usually FD_BAD_TS, which results in an
787  *              "Inaccesible track or sector" message, which is confusing.
788  */
translate_fsimage_error(int err)789 static int translate_fsimage_error(int err)
790 {
791     switch (err) {
792         case FSIMAGE_BAD_TRKNUM:
793             return FD_BAD_TRKNUM;
794         case FSIMAGE_BAD_SECNUM:
795             return FD_BAD_SECNUM;
796         default:
797             return err;
798     }
799 }
800 
801 
802 /** \brief  Return code for lookup_command(): command not found
803  */
804 #define LOOKUP_NOTFOUND -1
805 
806 /** \brief  Return code for lookup_command(): input matches multiple commands
807  */
808 #define LOOKUP_AMBIGUOUS -2
809 
810 /** \brief  Look up \a cmd in the command list
811  *
812  * \param[in]   cmd command name or part of command name
813  *
814  * \return  index in command array on success, < 0 on failure
815  */
lookup_command(const char * cmd)816 static int lookup_command(const char *cmd)
817 {
818     size_t cmd_len = strlen(cmd);
819     int match = LOOKUP_NOTFOUND;
820     int i;
821 
822     for (i = 0; command_list[i].name != NULL; i++) {
823         size_t len = strlen(command_list[i].name);
824 
825         if (len < cmd_len) {
826             /* cmd will never match current command in list */
827             continue;
828         }
829         if (memcmp(command_list[i].name, cmd, cmd_len) == 0) {
830             if (match != LOOKUP_NOTFOUND) {
831                 return LOOKUP_AMBIGUOUS;
832             }
833             match = i;
834             if (len == cmd_len) {
835                 break;  /* exact match */
836             }
837         }
838     }
839     return match;
840 }
841 
842 
843 /** \brief  Get image type name from type number
844  *
845  * \param[in]   type    on of the VDRIVE_FORMAT_TYPE constants
846  *
847  * \return  image type string or `NULL` when not found
848  *
849  * XXX:     Should probably be moved to vdrive somewhere
850  */
image_format_name(unsigned int type)851 static const char *image_format_name(unsigned int type)
852 {
853     /* I use a switch here since the constants in vdrive.h are defines. Right
854      * now they could be an enum, but if someone decides to change the defines
855      * with gaps in them, a table-based lookup will fail badly */
856     switch (type) {
857         case VDRIVE_IMAGE_FORMAT_1541:
858             return "1541";
859         case VDRIVE_IMAGE_FORMAT_1571:
860             return "1571";
861         case VDRIVE_IMAGE_FORMAT_1581:
862             return "1581";
863         case VDRIVE_IMAGE_FORMAT_8050:
864             return "8050";
865         case VDRIVE_IMAGE_FORMAT_8250:
866             return "8250";
867         case VDRIVE_IMAGE_FORMAT_2040:
868             return "2040";
869         case VDRIVE_IMAGE_FORMAT_4000:
870             return "4000";
871         default:
872             return NULL;
873     }
874 }
875 
876 
877 
878 
879 /** \brief  Look up \a cmd and execute
880  *
881  * \param[in]   nargs   number of arguments in \a args
882  * \param[in]   args    arguments
883  *
884  * \return  0 on success, -1 on failure
885  */
lookup_and_execute_command(int nargs,char ** args)886 static int lookup_and_execute_command(int nargs, char **args)
887 {
888     int match = lookup_command(args[0]);
889 
890     if (match >= 0) {
891         const command_t *cp;
892 
893         cp = &command_list[match];
894         if (nargs - 1 < (int)(cp->min_args)
895             || nargs - 1 > (int)(cp->max_args)) {
896             fprintf(stderr, "wrong number of arguments\n");
897             fprintf(stderr, "syntax: %s\n", cp->syntax);
898             return -1;
899         } else {
900             int retval;
901 
902             retval = command_list[match].func(nargs, args);
903             print_error_message(retval);
904             if (retval == FD_OK) {
905                 return 0;
906             } else {
907                 return -1;
908             }
909         }
910     } else {
911         if (match == LOOKUP_AMBIGUOUS) {
912             fprintf(stderr, "command `%s' is ambiguous.  Try `help'\n",
913                     args[0]);
914         } else {
915             fprintf(stderr, "command `%s' unrecognized.  Try `help'\n",
916                     args[0]);
917         }
918         return -1;
919     }
920 }
921 
922 
923 /** \brief  Parse and validate unit number from a '@<unit>:' string
924  *
925  * \param[in]   name    string to parse
926  * \param[out]  endptr  pointer to string after the '@<unit>:' stuff
927  *
928  * \return  unit number if successful, 0 if no '@<unit>:' was found, -1 if the
929  *          parsed unit number is invalid
930  */
extract_unit_from_file_name(char * name,char ** endptr)931 static int extract_unit_from_file_name(char *name, char **endptr)
932 {
933     long result;
934 
935     if (name == NULL || *name == '\0' || *name != '@') {
936         *endptr = name;
937         return 0;
938     }
939 
940     /* try to parse an integer between '@' and ':' */
941     result = strtol(name + 1, endptr, 10);
942     if (*endptr != NULL && **endptr == ':') {
943         /* got something */
944         (*endptr)++;
945         if (check_drive_unit((int)result) == FD_OK) {
946             return (int)result;
947         } else {
948             return -1;
949         }
950     }
951     *endptr = name;
952     return 0;
953 }
954 
955 
956 
957 
958 /** \brief  Check if \a name is a valid filename
959  *
960  * \param[in]   name    filename
961  *
962  * \return  bool
963  */
is_valid_cbm_file_name(const char * name)964 static int is_valid_cbm_file_name(const char *name)
965 {
966     /* Notice that ':' is the same on PETSCII and ASCII.  */
967     return strchr(name, ':') == NULL;
968 }
969 
970 /* ------------------------------------------------------------------------- */
971 
972 
973 /** \brief  Open a disk image
974  *
975  * Depending on \a name, this either opens a block device (a seekable device,
976  * such as an USB stick), a character device (a real drive using OpenCBM)
977  * or an image stored on the host file system.
978  *
979  * \param[in,out]   vdrive  virtual drive
980  * \param[in]       name    path to disk image file/data
981  * \param[in]       unit    unit to attach disk to
982  *
983  * \return 0 on success <0 on failure
984  */
open_disk_image(vdrive_t * vdrive,const char * name,unsigned int unit)985 static int open_disk_image(vdrive_t *vdrive, const char *name,
986                            unsigned int unit)
987 {
988     disk_image_t *image;
989 
990     image = disk_image_create();
991 
992     if (archdep_file_is_blockdev(name)) {
993         image->device = DISK_IMAGE_DEVICE_RAW;
994         serial_device_type_set(SERIAL_DEVICE_RAW, unit);
995         serial_realdevice_disable();
996     } else {
997         if (archdep_file_is_chardev(name)) {
998             image->device = DISK_IMAGE_DEVICE_REAL;
999             serial_device_type_set(SERIAL_DEVICE_REAL, unit);
1000             serial_realdevice_enable();
1001         } else {
1002             image->device = DISK_IMAGE_DEVICE_FS;
1003             serial_device_type_set(SERIAL_DEVICE_FS, unit);
1004             serial_realdevice_disable();
1005         }
1006     }
1007 
1008     disk_image_media_create(image);
1009 
1010     image->gcr = NULL;
1011     image->p64 = lib_calloc(1, sizeof(TP64Image));
1012     P64ImageCreate((PP64Image)image->p64);
1013     image->read_only = 0;
1014 
1015     disk_image_name_set(image, name);
1016 
1017     if (disk_image_open(image) < 0) {
1018         P64ImageDestroy((PP64Image)image->p64);
1019         lib_free(image->p64);
1020         disk_image_media_destroy(image);
1021         disk_image_destroy(image);
1022         fprintf(stderr, "cannot open file `%s'\n", name);
1023         return -1;
1024     }
1025 
1026     vdrive_device_setup(vdrive, unit, 0);
1027     vdrive->image = image;
1028     /* TODO: do we need a drive 1 here? */
1029     vdrive_attach_image(image, unit, 0, vdrive);
1030     return 0;
1031 }
1032 
1033 
1034 /** \brief  Close disk (image) attached to \a vdrive
1035  *
1036  * \param[in,out]   vdrive  virtual drive
1037  * \param[in]       unit    unit number
1038  */
close_disk_image(vdrive_t * vdrive,int unit)1039 static void close_disk_image(vdrive_t *vdrive, int unit)
1040 {
1041     disk_image_t *image;
1042 
1043     image = vdrive->image;
1044 
1045     /* TODO: do we need a drive 1 here? */
1046     if (image != NULL) {
1047         vdrive_detach_image(image, (unsigned int)unit, 0, vdrive);
1048         P64ImageDestroy((PP64Image)image->p64);
1049         lib_free(image->p64);
1050         if (image->device == DISK_IMAGE_DEVICE_REAL) {
1051             serial_realdevice_disable();
1052         }
1053         disk_image_close(image);
1054         disk_image_media_destroy(image);
1055         disk_image_destroy(image);
1056         vdrive->image = NULL;
1057         /* also clean up buffer used by the vdrive */
1058         vdrive_device_shutdown(vdrive);
1059     }
1060 }
1061 
1062 /** \brief  Open image or create a new one
1063  *
1064  * If the file exists, it must have valid header.
1065  *
1066  * The \a dev parameter is also used as a unit number by add 8 to it, meaning
1067  * units 8-11 can be used.
1068  *
1069  * \param[in]   dev         index in the virtual drive array, must be in the
1070  *                          range [0 .. NUM_DISK_UNITS-1]
1071  * \param[in]   name        disk/device name
1072  * \param[in]   create      create image (boolean)
1073  * \param[in]   disktype    disk type enumerator
1074  *
1075  * \return  0 on success, < 0 on failure
1076  */
open_image(int dev,char * name,int create,int disktype)1077 static int open_image(int dev, char *name, int create, int disktype)
1078 {
1079     if (dev < 0 || dev >= NUM_DISK_UNITS) {
1080         return -1;
1081     }
1082 
1083     if (create) {
1084         if (cbmimage_create_image(name, (unsigned int)disktype) < 0) {
1085             printf("cannot create disk image\n");
1086             return -1;
1087         }
1088     }
1089 
1090     if (open_disk_image(drives[dev], name, (unsigned int)dev + DRIVE_UNIT_MIN) < 0) {
1091         printf("cannot open disk image\n");
1092         return -1;
1093     }
1094     return 0;
1095 }
1096 
1097 
1098 /** \brief  Check if \a unit is a valid drive unit number
1099  *
1100  * \param[in]   unit number as on the real machine
1101  *
1102  * \return  0 (`FD_OK`) on success, < 0 (`FD_BADDEV`) on failure
1103  */
check_drive_unit(int unit)1104 static int check_drive_unit(int unit)
1105 {
1106     return (unit >= DRIVE_UNIT_MIN && unit <= DRIVE_UNIT_MAX) ? FD_OK : FD_BADDEV;
1107 }
1108 
1109 
1110 /** \brief  Check if \a index is a valid index into the vdrive array
1111  *
1112  * \param[in]   index   index in vdrive array
1113  *
1114  * \return  0 (`FD_OK`) on success, < 0 (`FD_BADDEV`) on failure
1115  */
check_drive_index(int index)1116 static int check_drive_index(int index)
1117 {
1118     return (index >= 0 && index < NUM_DISK_UNITS) ? FD_OK : FD_BADDEV;
1119 }
1120 
1121 
1122 /** \brief  Check if vdrive at \a index is ready
1123  *
1124  * \param[in]   index   index in the vdrive array
1125  *
1126  * \return  0 (`FD_OK`) on success, < 0 (`FD_BADDEV` or `FD_NOTREADY`) on
1127  *          failure
1128  */
check_drive_ready(int index)1129 static int check_drive_ready(int index)
1130 {
1131     int status = check_drive_index(index);
1132     if (status == FD_OK) {
1133         if (drives[index] == NULL || drives[index]->image == NULL) {
1134             status = FD_NOTREADY;
1135         }
1136     }
1137     return status;
1138 }
1139 
1140 
1141 /** \brief  Parse \a trk_str and \a sec_str for track and sector numbers
1142  *
1143  * Parses its string arguments for track and sector numbers, no actual checking
1144  * against an image is performed, so a track and sector of 100 will pass. Use
1145  * drive_image_check_sector() to check if the track and sector are valid for
1146  * a given image.
1147  *
1148  * \param[in]   trk_str string containing a track number
1149  * \param[in]   sec_str string containing a sector number
1150  * \param[out]  trk_num object to store track number
1151  * \param[out]  sec_num object to store sector number
1152  *
1153  * \return  FD_OK on success, FD_BAD_TRKNUM or FD_BAD_SECNUM on failure
1154  */
parse_track_sector(const char * trk_str,const char * sec_str,unsigned int * trk_num,unsigned int * sec_num)1155 static int parse_track_sector(const char *trk_str, const char *sec_str,
1156                               unsigned int *trk_num, unsigned int *sec_num)
1157 {
1158     int tmp;
1159 
1160     if (arg_to_int(trk_str, &tmp) < 0 || tmp < 1) {
1161         return FD_BAD_TRKNUM;
1162     }
1163     *trk_num = (unsigned int)tmp;
1164     if (arg_to_int(sec_str, &tmp) < 0 || tmp < 0) {
1165         return FD_BAD_SECNUM;
1166     }
1167     *sec_num = (unsigned int)tmp;
1168     return FD_OK;
1169 }
1170 
1171 
1172 /** \brief  Parse \a s for a unit number
1173  *
1174  * Parses string \a s for a unit number, and checks if it's a valid unit
1175  * number. No check if performed if the unit is actually ready (wouldn't make
1176  * sense when creating/attaching to a unit)
1177  *
1178  * \param[in]   s   string to parse
1179  *
1180  * \return  unit number or FD_BADDEV on error
1181  */
parse_unit_number(const char * s)1182 static int parse_unit_number(const char *s)
1183 {
1184     int u;
1185 
1186     if (arg_to_int(s, &u) < 0 || check_drive_unit(u) < 0) {
1187         return FD_BADDEV;
1188     }
1189     return u;
1190 }
1191 
1192 
1193 /* ------------------------------------------------------------------------- */
1194 
1195 /* Here are the commands.  */
1196 
1197 /* Note: The double ASCII/PETSCII copies of file names we keep in some
1198    functions are needed because we want to print the names of the files being
1199    copied in ASCII and we don't trust `charset_petconvstring()' to be
1200    reliable to get the original value back when we convert ASCII -> PETSCII
1201    and then PETSCII -> ASCII again.  */
1202 
1203 
1204 /** \brief  'attach' command handler
1205  *
1206  * Attach a disk image to a virtual drive.
1207  * Syntax: `attach <image-file> [<unit-number>]`, where unit-number must be
1208  * 8-11.
1209  *
1210  * \param[in]   nargs   number of arguments
1211  * \param[in]   args    argument list
1212  *
1213  * \return  0 on success, `FD_BADDEV` on failure
1214  */
attach_cmd(int nargs,char ** args)1215 static int attach_cmd(int nargs, char **args)
1216 {
1217     int dev = 0;
1218     char *path = NULL;
1219 
1220     switch (nargs) {
1221         case 2:
1222             /* attach <image> */
1223             dev = drive_index;
1224             break;
1225         case 3:
1226             /* attach <image> <unit> */
1227             if (arg_to_int(args[2], &dev) < 0) {
1228                 return FD_BADDEV;
1229             }
1230             if (check_drive_unit(dev) != FD_OK) {
1231                 return FD_BADDEV;
1232             }
1233             dev -= DRIVE_UNIT_MIN;
1234             break;
1235         default:
1236             return FD_BADDEV;
1237     }
1238 
1239     archdep_expand_path(&path, args[1]);
1240     open_disk_image(drives[dev], path, (unsigned int)dev + DRIVE_UNIT_MIN);
1241     lib_free(path);
1242     return FD_OK;
1243 }
1244 
1245 /** \brief  Maximum sector numbers to print in the sector header */
1246 #define BAM_SECTOR_HEADER_MAX_SECTORS   256
1247 /** \brief  Size of a sector number header line */
1248 #define BAM_SECTOR_HEADER_MAX_STRLEN   (BAM_SECTOR_HEADER_MAX_SECTORS + \
1249         ((BAM_SECTOR_HEADER_MAX_SECTORS / 8) - 1))
1250 
1251 
1252 /** \brief  Print a sector numbers header for a BAM dump
1253  *
1254  * Prints a two line header with sector numbers, with a space between each
1255  * block of eight sectors
1256  *
1257  * \param[in]   sectors number of sector number to print
1258  */
bam_print_sector_header(int sectors)1259 static void bam_print_sector_header(int sectors)
1260 {
1261     char line0[BAM_SECTOR_HEADER_MAX_STRLEN + 1];
1262     char line1[BAM_SECTOR_HEADER_MAX_STRLEN + 1];
1263     char line2[BAM_SECTOR_HEADER_MAX_STRLEN + 1];
1264     char tmp[10];
1265 
1266     int i = 0;
1267     int p = 0;
1268 
1269     while (i < sectors && p < BAM_SECTOR_HEADER_MAX_STRLEN) {
1270         snprintf(tmp, 9, "%3d", i % 1000);
1271 //        line0[p] = (char)(((i / 100) % 10) + '0');
1272 //        line1[p] = (char)(((i / 10) % 10) + '0');
1273 //        line2[p] = (char)((i % 10) + '0');
1274         line0[p] = tmp[0];
1275         line1[p] = tmp[1];
1276         line2[p] = tmp[2];
1277         i++;
1278         p++;
1279         if ((i % 8 == 0) && (i <sectors)) {
1280             line0[p] = ' ';
1281             line1[p] = ' ';
1282             line2[p] = ' ';
1283             p++;
1284         }
1285     }
1286     line0[p] = '\0';
1287     line1[p] = '\0';
1288     line2[p] = '\0';
1289 
1290     if (sectors > 100) {
1291         printf("     %s\n", line0);
1292     }
1293     printf("     %s\n     %s\n", line1, line2);
1294 }
1295 
1296 
1297 /** \brief  Print BAM bitmap for tracks \a track_min to \a track_max inclusive
1298  *
1299  * \param[in]   vdrive      vdrive object
1300  * \param[in]   track_min   starting track number
1301  * \param[in]   track_max   last track number
1302  *
1303  * \return      FD_OK on success, < 0 on failure
1304  */
bam_print_tracks(vdrive_t * vdrive,unsigned int track_min,unsigned int track_max)1305 static int bam_print_tracks(vdrive_t *vdrive,
1306                              unsigned int track_min,
1307                              unsigned int track_max)
1308 {
1309     unsigned int track;
1310 
1311     for (track = track_min; track <= track_max; track++) {
1312         unsigned int sectors = (unsigned int)vdrive_get_max_sectors(vdrive, track);
1313 //        unsigned char *bitmap = vdrive_bam_get_track_entry(vdrive, track, 0);
1314         unsigned int s = 0;
1315 
1316 /*
1317         if (bitmap == NULL) {
1318             fprintf(stderr,
1319                     "error: got NULL for bam entry for track %u\n",
1320                     track);
1321             return FD_BADVAL;
1322         }
1323 */
1324 
1325         printf("%3u  ", track);
1326         while (s < sectors) {
1327             putchar(vdrive_bam_is_sector_allocated(vdrive, track, s) ? '*' : '.');
1328             s++;
1329             if ((s % 8 == 0) && (s < sectors)) {
1330                 putchar(' ');
1331             }
1332         }
1333         putchar('\n');
1334     }
1335     return FD_OK;
1336 }
1337 
1338 
1339 /** \brief  Show BAM of an attached image
1340  *
1341  * Display a bitmap of used/free sectors for each track in the image
1342  *
1343  * Syntax:
1344  *
1345  * \param[in]   nargs   argument count
1346  * \param[in]   args    argument list
1347  *
1348  * \return  FD_OK on success, < 0 on failure
1349  */
bam_cmd(int nargs,char ** args)1350 static int bam_cmd(int nargs, char **args)
1351 {
1352     int unit = drive_index + DRIVE_UNIT_MIN;
1353     vdrive_t *vdrive;
1354     int max_sectors;
1355 
1356     unsigned int track_min = 0; /* 0 means first track in image */
1357     unsigned int track_max = 0; /* 0 means last track in image */
1358 
1359     int result = FD_OK;
1360 
1361     /* get unit number, if provided by the user */
1362     if (nargs == 2) {
1363         unit = parse_unit_number(args[1]);
1364         if (unit < 0) {
1365             return unit;
1366         }
1367     } else if (nargs > 2) {
1368         /* default unit, track-min and track-max */
1369         result = parse_track_sector(args[1], args[2], &track_min, &track_max);
1370         if (result < 0) {
1371             return result;
1372         }
1373         if (nargs == 4) {
1374             /* parse unit number */
1375             unit = parse_unit_number(args[3]);
1376             if (unit < 0) {
1377                 return unit;
1378             }
1379         }
1380     }
1381 
1382 #if 0
1383     printf("bam_cmd(): unit #%d\n", unit);
1384 #endif
1385     /* get vdrive instance */
1386     result = check_drive_ready(unit - DRIVE_UNIT_MIN);
1387     if (result < 0) {
1388         return result;
1389     }
1390     vdrive = drives[unit - DRIVE_UNIT_MIN];
1391 #if 0
1392     printf("bam_cmd(): image format: %s\n", image_format_name(vdrive->image_format));
1393     printf("bam_cmd(): BAM size: $%x\n", vdrive->bam_size);
1394 #endif
1395 
1396     /* set track min and max */
1397     if (track_min == 0) {
1398         track_min = 1;
1399     }
1400     if (track_max == 0) {
1401         track_max = vdrive->image->tracks;
1402     }
1403 
1404     if (track_min < 1 || track_max > vdrive->image->tracks) {
1405         return FD_BAD_TRKNUM;
1406     }
1407     if (track_min > track_max) {
1408         return FD_BADVAL;
1409     }
1410 
1411     /* print sector numbers header
1412      * XXX: this assumes track 1 always has the maximum number of sectors for
1413      *      an image */
1414     max_sectors = vdrive_get_max_sectors(vdrive, 1);
1415     if (max_sectors < 0) {
1416         return FD_BADVAL;
1417     }
1418 
1419     /* print sector numbers header and the actual BAM bitmap per track */
1420     bam_print_sector_header(max_sectors);
1421     bam_print_tracks(vdrive, track_min, track_max);
1422     return FD_OK;
1423 }
1424 
1425 
1426 /** \brief  Copy block to another block
1427  *
1428  * Copies a single block (sector) to another block, optionally between different
1429  * units.
1430  *
1431  * Syntax: `bcopy <src_trk> <src_sec> <dst_trk> <dst_sec> [<src_unit> [<dst_unit>]]`
1432  *
1433  * \param[in]   nargs   argument count
1434  * \param[in]   args    argument list
1435  *
1436  * \return  FD_OK on success < 0 on failure
1437  */
bcopy_cmd(int nargs,char ** args)1438 static int bcopy_cmd(int nargs, char **args)
1439 {
1440     unsigned char buffer[RAW_BLOCK_SIZE];
1441     unsigned int src_trk;
1442     unsigned int src_sec;
1443     unsigned int dst_trk;
1444     unsigned int dst_sec;
1445     int src_unit = drive_index + DRIVE_UNIT_MIN;
1446     int dst_unit = drive_index + DRIVE_UNIT_MIN;
1447     vdrive_t *src_vdrive;
1448     vdrive_t *dst_vdrive;
1449     int err;
1450 
1451     /* get source and destination blocks */
1452     err = parse_track_sector(args[1], args[2], &src_trk, &src_sec);
1453     if (err < 0) {
1454         return err;
1455     }
1456     err = parse_track_sector(args[3], args[4], &dst_trk, &dst_sec);
1457     if (err < 0) {
1458         return err;
1459     }
1460 
1461     /* get optional unit numbers */
1462     if (nargs > 5) {
1463         if (arg_to_int(args[5], &src_unit) < 0
1464                 || check_drive_unit(src_unit) < 0) {
1465             return FD_BADDEV;
1466         }
1467         if (nargs > 6) {
1468             if (arg_to_int(args[6], &dst_unit) < 0
1469                     || check_drive_unit(dst_unit) < 0) {
1470                 return FD_BADDEV;
1471             }
1472         } else {
1473             dst_unit = src_unit;
1474         }
1475     }
1476 
1477 #if 0
1478     printf("bcopy(): from #%d (%2u,%2u) to #%d (%2u,%2u)\n",
1479             src_unit, src_trk, src_sec, dst_unit, dst_trk, dst_sec);
1480 #endif
1481     /* don't do anything if source and dest are the same */
1482     if ((src_unit == dst_unit) && (src_trk == dst_trk) && (src_sec == dst_sec)) {
1483         return FD_OK;
1484     }
1485 
1486 
1487     /* check if the units are ready */
1488     if (check_drive_ready(src_unit - DRIVE_UNIT_MIN) < 0
1489             || check_drive_ready(dst_unit - DRIVE_UNIT_MIN) < 0) {
1490         return FD_NOTREADY;
1491     }
1492 
1493     src_vdrive = drives[src_unit - DRIVE_UNIT_MIN];
1494     dst_vdrive = drives[dst_unit - DRIVE_UNIT_MIN];
1495 
1496     /* check unit(s) for valid track/sector) */
1497     err = disk_image_check_sector(src_vdrive->image, src_trk, src_sec);
1498     if (err < 0) {
1499         return translate_fsimage_error(err);
1500     }
1501     if (dst_unit != src_unit) {
1502         if (disk_image_check_sector(dst_vdrive->image, dst_trk, dst_sec) < 0) {
1503             return translate_fsimage_error(err);
1504         }
1505     }
1506 
1507     /* finally we can actually do what this command is supposed to do */
1508     err = vdrive_read_sector(src_vdrive, buffer, src_trk, src_sec);
1509     if (err < 0) {
1510         return err;
1511     }
1512     err = vdrive_write_sector(dst_vdrive, buffer, dst_trk, dst_sec);
1513     if (err < 0) {
1514         return err;
1515     }
1516     return FD_OK;
1517 }
1518 
1519 
1520 /** \brief  Fill a block using a single value
1521  *
1522  * Syntax:  bfill <track> <sector> <value> [<unit>]
1523  *
1524  * \param   nargs   number of args (including the command name)
1525  * \param   args    argument list
1526  *
1527  * \return  FD_OK on success, < 0 on failure
1528  */
bfill_cmd(int nargs,char ** args)1529 static int bfill_cmd(int nargs, char **args)
1530 {
1531     vdrive_t *vdrive;
1532     int unit;
1533     unsigned int track;
1534     unsigned int sector;
1535     int err;
1536     int fill;
1537     unsigned char buffer[RAW_BLOCK_SIZE];
1538 
1539     /* get track and sector number */
1540     err = parse_track_sector(args[1], args[2], &track, &sector);
1541     if (err < 0) {
1542         return err;
1543     }
1544 
1545     /* parse and check fill byte */
1546     if (arg_to_int(args[3], &fill) < 0 || fill < 0 || fill > 255) {
1547         return FD_BADVAL;
1548     }
1549 
1550     /* check for optional unit number */
1551     if (nargs > 4) {
1552         if (arg_to_int(args[4], &unit) < 0 || check_drive_unit(unit) < 0) {
1553             return FD_BADDEV;
1554         }
1555     } else {
1556         unit = drive_index + DRIVE_UNIT_MIN;  /* default to current unit */
1557     }
1558 
1559 #if 0
1560     /* debugging info: */
1561     printf("bfill_cmd(): track %u, sector %u, fill $%02x, unit %d\n",
1562             track, sector, (unsigned int)fill, unit);
1563 #endif
1564 
1565     /* check that the drive is ready */
1566     if (check_drive_ready(unit - DRIVE_UNIT_MIN) < 0) {
1567         return FD_NOTREADY;
1568     }
1569 
1570     /* get the virtual drive */
1571     vdrive = drives[unit - DRIVE_UNIT_MIN];
1572 
1573     /* use this to get a meaningful error message for illegal track,sector
1574      *
1575      * XXX: checks sector properly, but lets track numbers larger than the
1576      *      image's track count pass, leading to vdrive expanding the D64
1577      *      attached. And the expansion goes wrong: if using (37,2), the
1578      *      D64 becomes 179968 bytes: 36 full tracks, and three sectors (0-2)
1579      *      in track 37, so somewhere in vdrive/diskimage/fsimage things go
1580      *      a little bit wrong.
1581      * */
1582     err = disk_image_check_sector(vdrive->image, track, sector);
1583     if (err < 0) {
1584         return translate_fsimage_error(err);
1585     }
1586 
1587     /* fill and write the block (vdrive doesn't have a vdrive_fill_sector()
1588      * function, so this will have to do */
1589     memset(buffer, fill, RAW_BLOCK_SIZE);
1590     /* should this still fail after al the checks, -1 is returned, which results
1591      * in an "<unknown error>", which all we can do, -1 is returned for various
1592      * error conditions */
1593     return vdrive_write_sector(vdrive, buffer, track, sector);
1594 }
1595 
1596 
1597 /** \brief  'block' command handler
1598  *
1599  * Display a hex dump of a block on a device
1600  * Syntax: `block <track> <sector> [<offset>] [<unit>]`
1601  *
1602  * \param[in]   nargs   number of arguments
1603  * \param[in]   args    argument list
1604  *
1605  * \return  0 on success, < 0 on failure: `FD_BAD_TS`, `FD_BADVAL`, `FD_BADDEC`,
1606  *          `FD_NOTREADY`, `FD_RDERR`
1607  */
block_cmd(int nargs,char ** args)1608 static int block_cmd(int nargs, char **args)
1609 {
1610     int drive;  /* index into the drives array */
1611     int offset = 0;
1612     unsigned int track;
1613     unsigned int sector;
1614     vdrive_t *vdrive;
1615     uint8_t *buf, chrbuf[BLOCK_CMD_WIDTH + 1], sector_data[RAW_BLOCK_SIZE];
1616     int cnt;
1617     int err;
1618 
1619     /* block <track> <sector> [offset] [<drive>] show disk blocks in hex form */
1620     err = parse_track_sector(args[1], args[2], &track, &sector);
1621     if (err != FD_OK) {
1622         return err;
1623     }
1624 
1625     if (nargs >= 4) {
1626         if (arg_to_int(args[3], &offset) < 0) {
1627             return FD_BADVAL;
1628         }
1629         if (offset < 0 || offset >= RAW_BLOCK_SIZE) {
1630             fprintf(stderr,
1631                     "error: invalid value for `offset` argument: %d, valid "
1632                     "values are 0-255\n",
1633                     offset);
1634             return FD_BADVAL;
1635         }
1636     }
1637 
1638     if (nargs == 5) {
1639         if (arg_to_int(args[4], &drive) < 0) {
1640             return FD_BADDEV;
1641         }
1642         if (check_drive_unit(drive) < 0) {
1643             return FD_BADDEV;
1644         }
1645         drive -= DRIVE_UNIT_MIN;
1646     } else {
1647         drive = drive_index;
1648     }
1649 
1650     if (check_drive_ready(drive) < 0) {
1651         return FD_NOTREADY;
1652     }
1653 
1654     vdrive = drives[drive];
1655 
1656     if (disk_image_check_sector(vdrive->image, track, sector) < 0) {
1657         return FD_BAD_TS;
1658     }
1659 
1660     /* Read one block */
1661     if (vdrive_read_sector(vdrive, sector_data, track, sector) != 0) {
1662         fprintf(stderr, "cannot read track %u sector %u.", track, sector);
1663         return FD_RDERR;
1664     }
1665 
1666     buf = sector_data;
1667 
1668     /* Show block */
1669 
1670     printf("<#%2d: %2u %2u>\n", drive + DRIVE_UNIT_MIN, track, sector);
1671     while (offset < RAW_BLOCK_SIZE) {
1672         printf("> %02X ", (unsigned int)offset);
1673         memset(chrbuf, '\0', BLOCK_CMD_WIDTH + 1);
1674         for (cnt = 0; cnt < BLOCK_CMD_WIDTH && offset < RAW_BLOCK_SIZE;
1675                 cnt++, offset++) {
1676             printf(" %02X", buf[offset]);
1677             chrbuf[cnt] = (buf[offset] < ' ' ?
1678                         '.' : charset_p_toascii(buf[offset], 0));
1679         }
1680         /* fix indentation in case the last line is less than the max width */
1681         while (cnt++ < BLOCK_CMD_WIDTH) {
1682             printf("   ");
1683         }
1684         printf("  ;%s\n", chrbuf);
1685     }
1686     return FD_OK;
1687 }
1688 
1689 
1690 /** \brief 'poke' some data into a block
1691  *
1692  * Syntax: bpoke [unit-specifier] track sector data ...
1693  *
1694  * For example: `bpoke @11: 18 0 $90 $43 $4f $4d $50 $58 $59`, this will write
1695  * 'compyx' to the disk name of a 1541 image attached at unit 11.
1696  *
1697  * \param[in]   nargs   argument count
1698  * \param[in]   args    argument list
1699  *
1700  * \return  FD_OK, or < 0 on failure
1701  */
bpoke_cmd(int nargs,char ** args)1702 static int bpoke_cmd(int nargs, char **args)
1703 {
1704     vdrive_t *vdrive;
1705     int unit;
1706     unsigned int track;
1707     unsigned int sector;
1708     int offset;
1709     int arg_idx = 1;
1710     int err;
1711     int i;
1712     char *endptr;
1713     unsigned char buffer[RAW_BLOCK_SIZE];
1714 
1715     /* first check for a unit number (@<unit>:) */
1716     unit = extract_unit_from_file_name(args[arg_idx], &endptr);
1717     if (unit == 0) {
1718         /* use current unit */
1719         unit = drive_index + DRIVE_UNIT_MIN;
1720     } else {
1721         if (unit < 0) {
1722             return FD_BADDEV;
1723         }
1724         arg_idx++;
1725     }
1726 
1727     err = parse_track_sector(args[arg_idx], args[arg_idx + 1], &track, &sector);
1728     if (err < 0) {
1729         return err;
1730     }
1731 
1732     if (arg_to_int(args[arg_idx + 2], &offset) < 0) {
1733         return FD_BADVAL;
1734     }
1735 #if 0
1736     printf("bpoke_cmd(): unit #%d: (%2u,%2u), offset %d\n",
1737             unit, track, sector, offset);
1738 #endif
1739     arg_idx += 3;
1740 
1741     /* check drive ready */
1742     if (check_drive_ready(unit - DRIVE_UNIT_MIN) < 0) {
1743         return FD_NOTREADY;
1744     }
1745 
1746     vdrive = drives[unit - DRIVE_UNIT_MIN];
1747     err = disk_image_check_sector(vdrive->image, track, sector);
1748     if (err < 0) {
1749         return err;
1750     }
1751 
1752     /* get sector data */
1753     err = vdrive_read_sector(vdrive, buffer, track, sector);
1754     if (err < 0) {
1755         return err;
1756     }
1757 
1758     i = offset;
1759     while (i < RAW_BLOCK_SIZE && arg_idx < nargs) {
1760         int b;
1761         if (arg_to_int(args[arg_idx], &b) < 0) {
1762             return FD_BADVAL;
1763         }
1764         buffer[i++] = (unsigned char)b;
1765         arg_idx++;
1766     }
1767 
1768     /* write back block */
1769     return vdrive_write_sector(vdrive, buffer, track, sector);
1770 }
1771 
1772 
1773 /** \brief  Read a block from an image and write it to the host file system
1774  *
1775  * Syntax: bread \<filename\> \<track\> \<sector\> [\<unit\>]
1776  *
1777  * \param[in]   nargs   argument count
1778  * \param[in]   args    argument list
1779  *
1780  * \return  FD_OK on success, < 0 on failure
1781  *
1782  * \todo    Add tilde expansion on filename for *nix systems
1783  */
bread_cmd(int nargs,char ** args)1784 static int bread_cmd(int nargs, char **args)
1785 {
1786     unsigned char buffer[RAW_BLOCK_SIZE];
1787     unsigned int track;
1788     unsigned int sector;
1789     int unit = drive_index + DRIVE_UNIT_MIN;
1790     vdrive_t *vdrive;
1791     FILE *fd;
1792     char *path;
1793     int result;
1794 
1795     /* get track & sector */
1796     result = parse_track_sector(args[2], args[3], &track, &sector);
1797     if (result < 0) {
1798         return result;
1799     }
1800 
1801     /* get unit number, if specified */
1802     if (nargs == 5) {
1803         if (arg_to_int(args[4], &unit) < 0 || check_drive_unit(unit) < 0) {
1804             return FD_BADDEV;
1805         }
1806     }
1807 
1808     /* check drive ready */
1809     if (check_drive_ready(unit - DRIVE_UNIT_MIN) < 0) {
1810         return FD_NOTREADY;
1811     }
1812 
1813     vdrive = drives[unit - DRIVE_UNIT_MIN];
1814 
1815     /* check track,sector */
1816     result = disk_image_check_sector(vdrive->image, track, sector);
1817     if (result < 0) {
1818         return result;
1819     }
1820 
1821     /* copy sector to buffer */
1822     if (vdrive_read_sector(vdrive, buffer, track, sector) != 0) {
1823         fprintf(stderr, "cannot read track %u sector %u.", track, sector);
1824         return FD_RDERR;
1825     }
1826 
1827     /* open file and try to write to it */
1828     result = FD_OK;
1829     archdep_expand_path(&path, args[1]);
1830     fd = fopen(path, "wb");
1831     if (fd == NULL) {
1832         result = FD_WRTERR;
1833     } else {
1834         if (fwrite(buffer, 1, RAW_BLOCK_SIZE, fd) != RAW_BLOCK_SIZE) {
1835             fclose(fd);
1836             result = FD_WRTERR;
1837         }
1838         fclose(fd);
1839     }
1840     lib_free(path);
1841 
1842     return result;
1843 }
1844 
1845 
1846 /** \brief Read a block from the host file system and write it to an image
1847  *
1848  * Syntax: bwrite \<filename\> \<track\> \<sector\> [\<unit\>]
1849  *
1850  * \param[in]   nargs   argument count
1851  * \param[in]   args    argument list
1852  *
1853  * \return  FD_OK on success, < 0 on failure
1854  *
1855  * \todo    Add tilde expansion on filename for *nix systems
1856  */
bwrite_cmd(int nargs,char ** args)1857 static int bwrite_cmd(int nargs, char **args)
1858 {
1859     unsigned char buffer[RAW_BLOCK_SIZE];
1860     unsigned int track;
1861     unsigned int sector;
1862     int unit = drive_index + DRIVE_UNIT_MIN;
1863     vdrive_t *vdrive;
1864     FILE *fd;
1865     char *path;
1866     int result;
1867 
1868     /* get track & sector */
1869     result = parse_track_sector(args[2], args[3], &track, &sector);
1870     if (result < 0) {
1871         return result;
1872     }
1873 
1874     /* get unit number, if specified */
1875     if (nargs == 5) {
1876         if (arg_to_int(args[4], &unit) < 0 || check_drive_unit(unit) < 0) {
1877             return FD_BADDEV;
1878         }
1879     }
1880 
1881     /* check drive ready */
1882     if (check_drive_ready(unit - DRIVE_UNIT_MIN) < 0) {
1883         return FD_NOTREADY;
1884     }
1885 
1886     vdrive = drives[unit - DRIVE_UNIT_MIN];
1887 
1888     /* check track,sector */
1889     result = disk_image_check_sector(vdrive->image, track, sector);
1890     if (result < 0) {
1891         return result;
1892     }
1893 
1894     /* open file for reading */
1895     result = FD_OK;
1896     archdep_expand_path(&path, args[1]);
1897     fd = fopen(path, "rb");
1898     if (fd == NULL) {
1899         result = FD_RDERR;
1900     } else {
1901         /* read data */
1902         if (fread(buffer, 1, RAW_BLOCK_SIZE, fd) != RAW_BLOCK_SIZE) {
1903             result = FD_RDERR;
1904         }
1905         fclose(fd);
1906     }
1907     if (result == FD_OK) {
1908         /* write to image */
1909         result = vdrive_write_sector(vdrive, buffer, track, sector);
1910         if (result < 0) {
1911             result = FD_WRTERR;
1912         }
1913     }
1914 
1915     lib_free(path);
1916 
1917     return result;
1918 }
1919 
1920 
1921 /** \brief  Follow and print a block chain
1922  *
1923  * \param[in]   nargs   number of arguments
1924  * \param[in]   args    argument list
1925  *
1926  * \return  FD_OK on success, < 0 on failure
1927  *
1928  * \todo    proper layout, it's a bit ugly now
1929  */
chain_cmd(int nargs,char ** args)1930 static int chain_cmd(int nargs, char **args)
1931 {
1932     int unit = drive_index + DRIVE_UNIT_MIN;
1933     unsigned int track;
1934     unsigned int sector;
1935     vdrive_t *vdrive;
1936     int err;
1937 
1938     /* parse track and sector number */
1939     err = parse_track_sector(args[1], args[2], &track, &sector);
1940     if (err != FD_OK) {
1941         return err;
1942     }
1943 
1944     /* get drive index */
1945     if (nargs == 4) {
1946         if (arg_to_int(args[3], &unit) < 0) {
1947             return FD_BADDEV;
1948         }
1949         if (check_drive_unit(unit) < 0) {
1950             return FD_BADDEV;
1951         }
1952     }
1953 
1954     /* check drive to see if it's ready */
1955     if (check_drive_ready(unit - DRIVE_UNIT_MIN) < 0) {
1956         return FD_NOTREADY;
1957     }
1958 
1959     /* now check if the (track,sector) is valid for the current image */
1960     vdrive = drives[unit - DRIVE_UNIT_MIN];
1961     if (disk_image_check_sector(vdrive->image, track, sector) < 0) {
1962         return FD_BAD_TS;
1963     }
1964 
1965     /* XXX: needs check for circular pattern, or perhaps some counter that
1966      *      checks the number of blocks against the maximum block size of the
1967      *      largest image type.
1968      */
1969     do {
1970         unsigned char buffer[RAW_BLOCK_SIZE];
1971 
1972         printf("(%2u,%2u) -> ", track, sector);
1973 
1974         /* read sector data */
1975         err = vdrive_read_sector(vdrive, buffer, track, sector);
1976         if (err < 0) {
1977             return err;
1978         }
1979         track = buffer[0];
1980         sector = buffer[1];
1981     } while (track > 0);
1982     printf("%u\n", sector);
1983 
1984     return FD_OK;
1985 }
1986 
1987 
1988 /** \brief  Copy one or more files
1989  *
1990  * \param[in]   nargs   argument count
1991  * \param[in]   args    argument list
1992  *
1993  * \return  FD_OK on success, or < 0 on failure
1994  */
copy_cmd(int nargs,char ** args)1995 static int copy_cmd(int nargs, char **args)
1996 {
1997     char *p;
1998     char *dest_name_ascii, *dest_name_petscii;
1999     int dest_unit = drive_index + DRIVE_UNIT_MIN;
2000     int src_unit = drive_index + DRIVE_UNIT_MIN;
2001     int i;
2002 
2003     dest_unit = extract_unit_from_file_name(args[nargs - 1], &p);
2004     if (dest_unit <= 0) {
2005         if (nargs > 3) {
2006             fprintf(stderr,
2007                     "the destination must be a drive if multiple sources are specified\n");
2008             return FD_BADDEV;
2009         }
2010         dest_name_ascii = lib_strdup(args[nargs - 1]);
2011         dest_name_petscii = lib_strdup(dest_name_ascii);
2012         charset_petconvstring((uint8_t *)dest_name_petscii, 0);
2013         dest_unit = drive_index + DRIVE_UNIT_MIN;
2014     } else {
2015         if (*p != 0) {
2016             if (nargs > 3) {
2017                 fprintf(stderr,
2018                         "the destination must be a drive if multiple sources are specified\n");
2019                 return FD_BADDEV;
2020             }
2021             dest_name_ascii = lib_strdup(p);
2022             dest_name_petscii = lib_strdup(dest_name_ascii);
2023             charset_petconvstring((uint8_t *)dest_name_petscii, 0);
2024         } else {
2025             dest_name_ascii = dest_name_petscii = NULL;
2026         }
2027     }
2028 
2029     if (dest_name_ascii != NULL && !is_valid_cbm_file_name(dest_name_ascii)) {
2030         fprintf(stderr,
2031                 "`%s' is not a valid CBM DOS file name\n", dest_name_ascii);
2032         return FD_BADNAME;
2033     }
2034 
2035     if (check_drive_ready(dest_unit - DRIVE_UNIT_MIN) < 0) {
2036         return FD_NOTREADY;
2037     }
2038 #if 0
2039     printf("src unit = %d, dest unit = %d\n", src_unit, dest_unit);
2040 #endif
2041     for (i = 1; i < nargs - 1; i++) {
2042         char *src_name_ascii, *src_name_petscii;
2043 
2044         src_unit = extract_unit_from_file_name(args[i], &p);
2045         if (src_unit <= 0) {
2046             src_name_ascii = lib_strdup(args[i]);
2047             src_unit = drive_index + DRIVE_UNIT_MIN;
2048         } else {
2049             if (check_drive_ready(src_unit - DRIVE_UNIT_MIN) < 0) {
2050                 return FD_NOTREADY;
2051             }
2052             src_name_ascii = lib_strdup(p);
2053         }
2054 
2055         if (!is_valid_cbm_file_name(src_name_ascii)) {
2056             fprintf(stderr,
2057                     "`%s' is not a valid CBM DOS file name: ignored\n",
2058                     src_name_ascii);
2059             lib_free(src_name_ascii);
2060             continue;
2061         }
2062 
2063         src_name_petscii = lib_strdup(src_name_ascii);
2064         charset_petconvstring((uint8_t *)src_name_petscii, 0);
2065 
2066         if (vdrive_iec_open(drives[src_unit - DRIVE_UNIT_MIN], (uint8_t *)src_name_petscii,
2067                             (unsigned int)strlen(src_name_petscii), 0, NULL)) {
2068             fprintf(stderr, "cannot read `%s'\n", src_name_ascii);
2069             if (dest_name_ascii != NULL) {
2070                 lib_free(dest_name_ascii);
2071                 lib_free(dest_name_petscii);
2072             }
2073 
2074             lib_free(src_name_ascii);
2075             lib_free(src_name_petscii);
2076             return FD_RDERR;
2077         }
2078 
2079         bufferinfo_t *bufferinfo = &drives[src_unit - DRIVE_UNIT_MIN]->buffers[0];        /* 0 = secadr of src */
2080         uint8_t *slot = bufferinfo->slot;
2081         unsigned int file_type = slot[SLOT_TYPE_OFFSET] & 7;
2082         unsigned int rel_record_length = slot[SLOT_RECORD_LENGTH];
2083 
2084         /*
2085          * If we're copying a REL file, create a proper destination file
2086          * name including the record length.
2087          */
2088         if (file_type == CBMDOS_FT_REL) {
2089             char *oldname, *newname, *comma;
2090 
2091             if (dest_name_petscii) {
2092                 oldname = dest_name_petscii;
2093             } else {
2094                 oldname = src_name_petscii;
2095             }
2096 
2097             /* Append ",L," and the record length to the destination name.
2098              * If there is a comma in it already, truncate there
2099              * but restore afterwards.
2100              */
2101             comma = strchr(oldname, ',');
2102             if (comma) {
2103                 *comma = '\0';
2104             }
2105 
2106             newname = lib_msprintf("%s,L,%c", oldname, rel_record_length);
2107 
2108             if (comma) {
2109                 *comma = ',';
2110             }
2111 
2112             /* Make sure both dest_name_petscii and dest_name_ascii are set. */
2113             if (dest_name_petscii) {
2114                 lib_free(dest_name_petscii);
2115             } else {
2116                 dest_name_ascii = lib_strdup(src_name_ascii);
2117             }
2118             dest_name_petscii = newname;
2119         }
2120 
2121         if (dest_name_ascii != NULL) {
2122             if (vdrive_iec_open(drives[dest_unit - DRIVE_UNIT_MIN],
2123                         (uint8_t *)dest_name_petscii,
2124                         (unsigned int)strlen(dest_name_petscii), 1, NULL)) {
2125                 fprintf(stderr, "cannot write `%s'\n", dest_name_petscii);
2126                 vdrive_iec_close(drives[src_unit - DRIVE_UNIT_MIN], 0);
2127                 lib_free(dest_name_ascii);
2128                 lib_free(dest_name_petscii);
2129                 lib_free(src_name_ascii);
2130                 lib_free(src_name_petscii);
2131                 return FD_WRTERR;
2132             }
2133         } else {
2134             if (vdrive_iec_open(drives[dest_unit - DRIVE_UNIT_MIN],
2135                         (uint8_t *)src_name_petscii,
2136                                 (unsigned int)strlen(src_name_petscii), 1, NULL)) {
2137                 fprintf(stderr, "cannot write `%s'\n", src_name_petscii);
2138                 vdrive_iec_close(drives[src_unit - DRIVE_UNIT_MIN], 0);
2139                 lib_free(src_name_ascii);
2140                 lib_free(src_name_petscii);
2141                 return FD_WRTERR;
2142             }
2143         }
2144 
2145         printf("copying `%s' ...\n", args[i]); /* FIXME */
2146 
2147         if (file_type == CBMDOS_FT_REL) {
2148             unsigned int num_rel_records = bufferinfo->record_max;
2149             unsigned int record;
2150             int dnr = src_unit - DRIVE_UNIT_MIN;
2151             int status;
2152 
2153             /* First allocate space for the whole file */
2154             status = vdrive_rel_position(drives[dnr], 1,
2155                     (num_rel_records & 0xFF), (num_rel_records >> 8) & 0xFF,
2156                     1);
2157             if (status && status != CBMDOS_IPE_NO_RECORD) {
2158                 fprintf(stderr, "Cannot Position to record %u (err %d)\n",
2159                         num_rel_records, status);
2160             }
2161             status = vdrive_iec_write(drives[dnr], 0, 1);
2162             if (status != SERIAL_OK) {
2163                 fprintf(stderr, "Cannot write in record %u (err %d)\n",
2164                         num_rel_records, status);
2165             }
2166 
2167             for (record = 1; record <= num_rel_records; record++) {
2168                 int bytes = 0;
2169 
2170                 status = vdrive_rel_position(drives[dnr], 1,   /* 1 = secadr dest */
2171                         (record & 0xFF), (record >> 8) & 0xFF,
2172                         1);
2173                 if (status && status != CBMDOS_IPE_NO_RECORD) {
2174                     fprintf(stderr, "Cannot Position to record %u (err %d)\n",
2175                             record, status);
2176                 }
2177 
2178                 do {
2179                     uint8_t c;
2180 
2181                     status = vdrive_iec_read(drives[dnr], &c, 0);
2182                     if (status == SERIAL_ERROR && bytes == 0) {
2183                         fprintf(stderr, "dummy record CR; should not happen.\n");
2184                         break; /* record after EOF; we get a dummy CR */
2185                     }
2186                     if (vdrive_iec_write(drives[dest_unit - DRIVE_UNIT_MIN], c, 1)) {
2187                         fprintf(stderr, "no space on image ?\n");
2188                         break;
2189                     }
2190                     bytes++;
2191                 } while (status == SERIAL_OK);
2192             }
2193         } else {
2194             uint8_t c;
2195             int status = 0;
2196 
2197             do {
2198                 status = vdrive_iec_read(drives[src_unit - DRIVE_UNIT_MIN],
2199                         ((uint8_t *)&c), 0);
2200                 if (vdrive_iec_write(drives[dest_unit - DRIVE_UNIT_MIN], c, 1)) {
2201                     fprintf(stderr, "no space on image ?\n");
2202                     break;
2203                 }
2204             } while (status == SERIAL_OK);
2205         }
2206 
2207         vdrive_iec_close(drives[src_unit - DRIVE_UNIT_MIN], 0);
2208         vdrive_iec_close(drives[dest_unit - DRIVE_UNIT_MIN], 1);
2209 
2210         lib_free(src_name_ascii);
2211         lib_free(src_name_petscii);
2212     }
2213 
2214     lib_free(dest_name_ascii);
2215     lib_free(dest_name_petscii);
2216     return FD_OK;
2217 }
2218 
2219 /** \brief  Print a side-sector-group, which is at most 6 side sectors.
2220  *
2221  * \param[in]   vdrive  the virtual drive from which we read the sectors
2222  * \param[in]   track   the track number of the first side sector.
2223  * \param[in]   sector  the sector number of the first side sector.
2224  * \param[in]   group   when iterating over a super side sector, this is
2225  *                      the number of the group within it.
2226  * \param[in,out] side_sector_number   a pointer to a running counter for
2227  *                      all side sectors belonging to the same file.
2228  * \param[in,out] data_block_number    a pointer to a running counter for
2229  *                      all data blocks belonging to the same file.
2230  *
2231  * \return  FD_OK on success, or < 0 on failure
2232  */
print_side_sector_group(vdrive_t * vdrive,unsigned track,unsigned sector,int group,unsigned * side_sector_number,unsigned * data_block_number)2233 static void print_side_sector_group(vdrive_t *vdrive,
2234                                     unsigned track, unsigned sector,
2235                                     int group,
2236                                     unsigned *side_sector_number,
2237                                     unsigned *data_block_number)
2238 {
2239     int count = 0;
2240     int i;
2241 
2242     while (track != 0 && count++ < SIDE_SECTORS_MAX) {
2243         uint8_t buffer[256];
2244 
2245         /* check track,sector */
2246         int result = disk_image_check_sector(vdrive->image, track, sector);
2247         if (result < 0) {
2248             fprintf(stderr, "Invalid track %u sector %u.", track, sector);
2249             break;
2250         }
2251 
2252         /* copy sector to buffer */
2253         if (vdrive_read_sector(vdrive, buffer, track, sector) != 0) {
2254             fprintf(stderr, "Cannot read track %u sector %u.", track, sector);
2255             break;
2256         }
2257 
2258         printf("-------------------------------\n");
2259         printf("Side sector at T/S: %u/%u", track, sector);
2260         if (group >= 0) {
2261             printf(" part of group %d", group);
2262         }
2263         printf("\n\nNext side sector T/S: %d/%d\n",
2264                 buffer[OFFSET_NEXT_TRACK], buffer[OFFSET_NEXT_SECTOR]);
2265         printf("Sector number: %d (counted: %u)\n",
2266                 buffer[OFFSET_SECTOR_NUM],
2267                 *side_sector_number);
2268         ++*side_sector_number;
2269         printf("Record length: %d\n", buffer[OFFSET_RECORD_LEN]);
2270 
2271         printf("Side sector group:\n");
2272         for (i = 0; i < 6; i++) {
2273             printf("%d: %u/%u  ",
2274                     i,
2275                     buffer[OFFSET_SIDE_SECTOR + 2*i],
2276                     buffer[OFFSET_SIDE_SECTOR + 2*i + 1]);
2277         }
2278         printf("\nFile data sectors:\n");
2279 
2280         for (i = OFFSET_POINTER; i < 255; i += 2) {
2281             if (buffer[i] || buffer[i + 1]) {
2282                 printf("%u: %d/%d  ",
2283                         *data_block_number,
2284                         buffer[i], buffer[i + 1]);
2285                 ++*data_block_number;
2286             }
2287         }
2288         printf("\n-------------------------------\n");
2289 
2290         track = buffer[OFFSET_NEXT_TRACK];
2291         sector = buffer[OFFSET_NEXT_SECTOR];
2292     }
2293 }
2294 
2295 /** \brief  Show directory entries of file(s) on disk image(s) in
2296  * low-level detail.
2297  *
2298  * \param[in]   nargs   argument count
2299  * \param[in]   args    argument list
2300  *
2301  * \return  0 on success, < 0 on failure
2302  */
entry_cmd(int nargs,char ** args)2303 static int entry_cmd(int nargs, char **args)
2304 {
2305     int show_side_sector = 0;
2306     int arg = 1;
2307     int i, j;
2308 
2309     if (arg < nargs && strcmp(args[arg], "+side") == 0) {
2310         arg++;
2311         show_side_sector++;
2312     }
2313 
2314     for (; arg < nargs; arg++) {
2315         int unit;   /* unit number */
2316         int dnr;    /* index in drives array */
2317         char *p;
2318         char *name_ascii, *name_petscii;
2319         const int secadr = 2;
2320         uint8_t *slot;
2321         uint8_t v;
2322         vdrive_t *vdrive;
2323 
2324         unit = extract_unit_from_file_name(args[arg], &p);
2325         if (unit < 0) {
2326             /* illegal unit between '@' and ':' */
2327             return FD_BADDEV;
2328         }
2329         if (unit == 0) {
2330             /* no '@<unit>:' found, use current device */
2331             dnr = drive_index;
2332         } else {
2333             dnr = unit - DRIVE_UNIT_MIN;    /* set proper device index */
2334         }
2335         if (check_drive_ready(dnr) < 0) {
2336             return FD_NOTREADY;
2337         }
2338         name_ascii = p;   /* update pointer to name_ascii */
2339 
2340         if (!is_valid_cbm_file_name(name_ascii)) {
2341             fprintf(stderr,
2342                     "`%s' is not a valid CBM DOS file name: ignored\n", name_ascii);
2343             continue;
2344         }
2345 
2346         name_petscii = lib_strdup(name_ascii);
2347         charset_petconvstring((uint8_t *)name_petscii, 0);
2348 
2349         if (vdrive_iec_open(drives[dnr], (uint8_t *)name_petscii,
2350                             (unsigned int)strlen(name_petscii), secadr, NULL)) {
2351             fprintf(stderr, "cannot read `%s'\n", name_ascii);
2352 
2353             lib_free(name_petscii);
2354             continue;
2355         }
2356 
2357         vdrive = drives[dnr];
2358         bufferinfo_t *bufferinfo = &vdrive->buffers[secadr];
2359         slot = bufferinfo->slot;
2360 
2361         printf(" 0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F\n");
2362         printf("-----------------------------------------------\n");
2363         for (j = 0; j < 2; j++) {
2364             for (i = 0; i < SLOT_SIZE / 2; i++) {
2365                 printf("%02x ", slot[j * (SLOT_SIZE / 2) + i]);
2366             }
2367             printf("\n");
2368         }
2369         v = slot[SLOT_TYPE_OFFSET];
2370         printf("\nNext directory T/S: %d/%d\n", slot[0], slot[1]);
2371         static char *types[] = {
2372             "del", "seq", "prg", "usr", "rel", "cbm", "dir", "007",
2373         };
2374         printf("Type: 0x%02x: %s", v, types[v & 7]);
2375         if (v & CBMDOS_FT_REPLACEMENT) {
2376             printf(" @replacement");
2377         }
2378         if (v & CBMDOS_FT_LOCKED) {
2379             printf(" <locked");
2380         }
2381         if (!(v & CBMDOS_FT_CLOSED)) {
2382             printf(" *unclosed");
2383         }
2384         printf("\n");
2385         printf("T/S: %d/%d,  %d blocks\n",
2386                 slot[SLOT_FIRST_TRACK],
2387                 slot[SLOT_FIRST_SECTOR],
2388                 slot[SLOT_NR_BLOCKS] + 256 * slot[SLOT_NR_BLOCKS+1]);
2389         printf("Name: ");
2390         for (i = 0; i < CBMDOS_SLOT_NAME_LENGTH; i++) {
2391             printf("%02x ", slot[SLOT_NAME_OFFSET + i]);
2392         }
2393         printf("\n");
2394         printf("%side sector T/S: %d/%d,  Record length: %d\n",
2395                 (bufferinfo->super_side_sector_track ? "Super s" : "S"),
2396                 slot[SLOT_SIDE_TRACK], slot[SLOT_SIDE_SECTOR],
2397                 slot[SLOT_RECORD_LENGTH]);
2398         printf("@-replacement T/S: %d/%d\n",
2399                 slot[SLOT_REPLACE_TRACK], slot[SLOT_REPLACE_SECTOR]);
2400 
2401         printf("GEOS: IT/S: %d/%d\n", slot[SLOT_GEOS_ITRACK], slot[SLOT_GEOS_ISECTOR]);
2402         printf("GEOS: struct: %02x,  type: %02x\n",
2403                 slot[SLOT_GEOS_STRUCT], slot[SLOT_GEOS_TYPE]);
2404         printf("GEOS: YY/mm/dd hh:mm %d/%d/%d %d:%d\n",
2405                 slot[SLOT_GEOS_YEAR],
2406                 slot[SLOT_GEOS_MONTH],
2407                 slot[SLOT_GEOS_DATE],
2408                 slot[SLOT_GEOS_HOUR],
2409                 slot[SLOT_GEOS_MINUTE]);
2410 
2411         /* If this is a REL file, print the side sectors */
2412         if (show_side_sector &&
2413             (slot[SLOT_TYPE_OFFSET] & 7) == CBMDOS_FT_REL &&
2414             slot[SLOT_SIDE_TRACK] != 0) {
2415             unsigned int super_track = bufferinfo->super_side_sector_track;
2416             unsigned int super_sector = bufferinfo->super_side_sector_sector;
2417             unsigned int side_sector_number = 0;
2418             unsigned int data_block_number = 0;
2419 
2420             if (super_track) {
2421                 int o, side;
2422 
2423                 printf("===============================\n");
2424                 printf("Super side sector at T/S: %u/%u\n\n", super_track, super_sector);
2425                 unsigned int track, sector;
2426 
2427                 printf("Next T/S: %u/%u,  seq# or 254: %u,  unused at 255: %u\n",
2428                         bufferinfo->super_side_sector[0],
2429                         bufferinfo->super_side_sector[1],
2430                         bufferinfo->super_side_sector[OFFSET_SUPER_254],
2431                         bufferinfo->super_side_sector[255]);
2432 
2433                 printf("Side sector groups:\n");
2434                 o = OFFSET_SUPER_POINTER;
2435                 for (side = 0; side < SIDE_SUPER_MAX; side++, o += 2) {
2436                     track = bufferinfo->super_side_sector[o],
2437                     sector = bufferinfo->super_side_sector[o + 1];
2438 
2439                     if (track || sector) {
2440                         printf("%d: %u/%u  ", side, track, sector);
2441                     }
2442                 }
2443 
2444                 printf("\n===============================\n");
2445 
2446                 o = OFFSET_SUPER_POINTER;
2447                 for (side = 0; side < SIDE_SUPER_MAX; side++, o += 2) {
2448                     track = bufferinfo->super_side_sector[o],
2449                     sector = bufferinfo->super_side_sector[o + 1];
2450 
2451                     if (track != 0) {
2452                         print_side_sector_group(vdrive, track, sector,
2453                                                 side,
2454                                                 &side_sector_number,
2455                                                 &data_block_number);
2456                     }
2457                 }
2458             } else {
2459                 unsigned int track = bufferinfo->side_sector_track[0];
2460                 unsigned int sector = bufferinfo->side_sector_sector[0];
2461 
2462                 print_side_sector_group(vdrive, track, sector,
2463                                         -1,
2464                                         &side_sector_number,
2465                                         &data_block_number);
2466             }
2467         } else if (!show_side_sector &&
2468             (slot[SLOT_TYPE_OFFSET] & 7) == CBMDOS_FT_REL &&
2469             slot[SLOT_SIDE_TRACK] != 0) {
2470             printf("This file seems to have side sector(s). Use the +side option to show them.\n");
2471         }
2472 
2473         vdrive_iec_close(drives[dnr], secadr);
2474 
2475         lib_free(name_petscii);
2476     }
2477 
2478     return FD_OK;
2479 }
2480 
2481 
2482 /** \brief  Delete (scratch) file(s) from disk image(s)
2483  *
2484  * Delete one or more files. Each file can have a unit number (@\<unit>:) in
2485  * front of it to indicate which unit to use.
2486  *
2487  * \param[in]   nargs   argument count
2488  * \param[in]   args    argument list
2489  *
2490  * \return  0 on success, < 0 on failure
2491  */
delete_cmd(int nargs,char ** args)2492 static int delete_cmd(int nargs, char **args)
2493 {
2494     int i;
2495 
2496     for (i = 1; i < nargs; i++) {
2497         int unit;   /* unit number */
2498         int dnr;    /* index in drives array */
2499         char *p;
2500         char *name;
2501         char *command;
2502         int status;
2503 
2504         unit = extract_unit_from_file_name(args[i], &p);
2505         if (unit < 0) {
2506             /* illegal unit between '@' and ':' */
2507             return FD_BADDEV;
2508         }
2509         if (unit == 0) {
2510             /* no '@<unit>:' found, use current device */
2511             dnr = drive_index;
2512         } else {
2513             dnr = unit - DRIVE_UNIT_MIN;    /* set proper device index */
2514         }
2515         if (check_drive_ready(dnr) < 0) {
2516             return FD_NOTREADY;
2517         }
2518         name = p;   /* update pointer to name */
2519 
2520 
2521         if (!is_valid_cbm_file_name(name)) {
2522             fprintf(stderr,
2523                     "`%s' is not a valid CBM DOS file name: ignored\n", name);
2524             continue;
2525         }
2526 
2527         command = util_concat("s:", name, NULL);
2528         charset_petconvstring((uint8_t *)command, 0);
2529 
2530         printf("deleting `%s' on unit %d\n", name, unit);
2531 
2532         status = vdrive_command_execute(drives[dnr], (uint8_t *)command,
2533                                         (unsigned int)strlen(command));
2534 
2535         lib_free(command);
2536 
2537         /* vdrive_command_execute() returns CBMDOS_IPE_DELETED even if no
2538          * files where actually scratched, so just display error messages that
2539          * actual mean something, not "ERRORCODE 1" */
2540         if (status != CBMDOS_IPE_OK && status != CBMDOS_IPE_DELETED) {
2541             printf("%02d, %s, 00, 00\n",
2542                     status, cbmdos_errortext((unsigned int)status));
2543         }
2544     }
2545 
2546     return FD_OK;
2547 }
2548 
2549 
2550 #define P00_HDR_LEN         0x1a
2551 #define P00_HDR_MAGIC       0x00
2552 #define P00_HDR_MAGIC_LEN   0x08
2553 #define P00_HDR_NAME        0x08
2554 #define P00_HDR_NAME_LEN    0x10
2555 #define P00_HDR_REL_RECLEN  0x19
2556 
2557 
write_p00_header(FILE * fd,const uint8_t * petname)2558 static int write_p00_header(FILE *fd, const uint8_t *petname)
2559 {
2560     uint8_t hdr[P00_HDR_LEN];
2561     int i;
2562 
2563     /* copy "C64File" and nul char as magic */
2564     memcpy(hdr + P00_HDR_MAGIC, p00_header, P00_HDR_MAGIC_LEN);
2565     /* copy CBMDOS filename in PETSCII */
2566     memcpy(hdr + P00_HDR_NAME, petname, P00_HDR_NAME_LEN);
2567     /* fix up name padding, P00 uses 0x00 for padding instead of the standard
2568      * 0xa0 padding of CBMDOS
2569      */
2570     i = P00_HDR_NAME_LEN - 1;
2571     while (i >=0 && hdr[P00_HDR_NAME + i] == 0xa0) {
2572         hdr[P00_HDR_NAME + i] = 0;
2573         i--;
2574     }
2575     /* REL file info, unsupported for now */
2576     hdr[0x18] = 0;  /* always 0 */
2577     hdr[P00_HDR_REL_RECLEN] = 0;
2578 
2579     return fwrite(hdr, 1U, P00_HDR_LEN, fd) == P00_HDR_LEN;
2580 }
2581 
2582 
2583 /* Extract all files <gwesp@cosy.sbg.ac.at>.  */
2584 /* FIXME: This does not work with non-standard file names.  */
2585 /* FIXME: I added P00 support, but it's a little shitty */
extract_cmd_common(int nargs,char ** args,int geos)2586 static int extract_cmd_common(int nargs, char **args, int geos)
2587 {
2588     int dnr = 0;
2589     unsigned int track, sector;
2590     vdrive_t *floppy;
2591     uint8_t *buf, *str;
2592     unsigned int channel = 2;
2593     char *p00_name = NULL;
2594 
2595     if (nargs == 2) {
2596         if (arg_to_int(args[1], &dnr) < 0) {
2597             return FD_BADDEV;
2598         }
2599         if (check_drive_unit(dnr) < 0) {
2600             return FD_BADDEV;
2601         }
2602         dnr -= DRIVE_UNIT_MIN;
2603     }
2604 
2605     if (check_drive_ready(dnr) < 0) {
2606         return FD_NOTREADY;
2607     }
2608 
2609     floppy = drives[dnr];
2610 
2611     if (vdrive_iec_open(floppy, (const uint8_t *)"#", 1, channel, NULL)) {
2612         fprintf(stderr, "cannot open buffer #%u in unit %d\n", channel,
2613                 dnr + DRIVE_UNIT_MIN);
2614         return FD_RDERR;
2615     }
2616 
2617     track = floppy->Dir_Track;
2618     sector = floppy->Dir_Sector;
2619 
2620     while (1) {
2621         int i, res;
2622 
2623         str = (uint8_t *)lib_msprintf("B-R:%u 0 %u %u", channel, track, sector);
2624         res = vdrive_command_execute(floppy, str, (unsigned int)strlen((char *)str));
2625 
2626         lib_free(str);
2627 
2628         if (res) {
2629             return FD_RDERR;
2630         }
2631 
2632         buf = floppy->buffers[channel].buffer;
2633 
2634         for (i = 0; i < 256; i += SLOT_SIZE) {
2635             uint8_t file_type = buf[i + SLOT_TYPE_OFFSET];
2636 
2637             if (((file_type & 7) == CBMDOS_FT_SEQ
2638                         || (file_type & 7) == CBMDOS_FT_PRG
2639                         || (file_type & 7) == CBMDOS_FT_USR)
2640                     && (file_type & CBMDOS_FT_CLOSED)) {
2641                 uint8_t *file_name = buf + i + SLOT_NAME_OFFSET;
2642                 int status = 0;
2643                 uint8_t c;
2644                 uint8_t name[IMAGE_CONTENTS_FILE_NAME_LEN + 1];
2645                 uint8_t cbm_name[IMAGE_CONTENTS_FILE_NAME_LEN + 1 + 2];
2646                 FILE *fd;
2647                 unsigned int len;
2648 
2649                 memset(name, 0, sizeof(name));
2650                 memset(cbm_name, 0, sizeof(cbm_name));
2651                 for (len = 0; len < IMAGE_CONTENTS_FILE_NAME_LEN; len++) {
2652                     if (file_name[len] == 0xa0) {
2653                         break;
2654                     } else {
2655                         name[len] = file_name[len];
2656                         cbm_name[len] = file_name[len];
2657                     }
2658                 }
2659 
2660                 /* Hack to support SEQ/USR
2661                  *
2662                  * FIXME: not sure this is the proper way.
2663                  */
2664                 if ((file_type & 7) == CBMDOS_FT_SEQ) {
2665                     memcpy(cbm_name + len, ",S", 2);
2666                     len +=2;
2667                 }
2668                 if ((file_type & 7) == CBMDOS_FT_USR) {
2669                     memcpy(cbm_name + len, ",U", 2);
2670                     len +=2;
2671                 }
2672 
2673 
2674                 charset_petconvstring((uint8_t *)name, 1);
2675 
2676                 /* translate illegal chars for the host OS to '_' */
2677                 archdep_sanitize_filename((char *)name);
2678 #if 0
2679                 printf("cbm_name = '%s', len = %u\n", cbm_name, len);
2680 #endif
2681                 if (vdrive_iec_open(floppy, cbm_name, len, 0, NULL)) {
2682                     fprintf(stderr,
2683                             "cannot open `%s' on unit %d\n",
2684                             name, dnr + DRIVE_UNIT_MIN);
2685                     continue;
2686                 }
2687 
2688                 if (p00save[dnr]) {
2689                     char cwd[4096];
2690                     char *total;
2691                     long idx = 0;
2692 
2693                     p00_name = p00_filename_create((const char *)name,
2694                             file_type & 7);
2695 #ifdef ARCHDEP_OS_UNIX
2696                     if (getcwd(cwd, sizeof(cwd)) == NULL) {
2697                         fprintf(stderr,
2698                                 "Couldn't get the cwd, all bets are off. "
2699                                 "Aborting to get a stack dump.\n");
2700                         abort();
2701                     }
2702 #else
2703                     /* Assume crap */
2704 #ifdef ARCHDEP_OS_HAIKU
2705                     getcwd(cwd, sizeof(cwd));
2706 #else
2707                     _getcwd(cwd, sizeof(cwd));
2708 #endif
2709 #endif
2710                     total = archdep_join_paths(cwd, p00_name, NULL);
2711 
2712 
2713                     printf("Trying filename '%s'\n", total);
2714                     while (archdep_file_exists(total) && idx < 100) {
2715                         /* TODO: clean up this mess */
2716                         char *endptr;
2717                         size_t pathlen = strlen(total);
2718 #if 0
2719                         printf("file exists, increment index\n");
2720 #endif
2721                         idx = strtol(total + pathlen - 2, &endptr, 10);
2722 #if 0
2723                         printf("got index %ld\n", idx);
2724 #endif
2725                         snprintf(total + pathlen - 2, 3, "%02d", (int)(idx + 1));
2726 #if 0
2727                         printf("new name = '%s'\n", total);
2728 #endif
2729                     }
2730                     fd = fopen(total, MODE_WRITE);
2731                     lib_free(total);
2732 
2733                 } else {
2734                     fd = fopen((const char *)name, MODE_WRITE);
2735                 }
2736                 if (fd == NULL) {
2737                     fprintf(stderr, "cannot create file `%s': %s.",
2738                             name, strerror(errno));
2739                     vdrive_iec_close(floppy, 0);
2740                     continue;
2741                 }
2742                 if (geos) {
2743                     status = internal_read_geos_file(dnr, fd, (char *)name);
2744                 } else {
2745                     /* do we have P00save? */
2746                     if (p00save[dnr]) {
2747                         if (!write_p00_header(fd, cbm_name)) {
2748                             fprintf(stderr, "failed to write P00 header\n");
2749                             lib_free(p00_name);
2750                             return FD_WRTERR;
2751                         }
2752                     }
2753                     do {
2754                         status = vdrive_iec_read(floppy, &c, 0);
2755                         fputc(c, fd);
2756                     } while (status == SERIAL_OK);
2757                 }
2758                 if (p00_name != NULL) {
2759                     lib_free(p00_name);
2760                 }
2761 
2762                 vdrive_iec_close(floppy, 0);
2763 
2764                 if (fclose(fd)) {
2765                     return FD_RDERR;
2766                 }
2767             }
2768         }
2769         if (buf[0] && buf[1]) {
2770             track = buf[0];
2771             sector = buf[1];
2772         } else {
2773             break;
2774         }
2775     }
2776     vdrive_iec_close(floppy, channel);
2777     return FD_OK;
2778 }
2779 
extract_cmd(int nargs,char ** args)2780 static int extract_cmd(int nargs, char **args)
2781 {
2782     return extract_cmd_common(nargs, args, 0);
2783 }
extract_geos_cmd(int nargs,char ** args)2784 static int extract_geos_cmd(int nargs, char **args)
2785 {
2786     return extract_cmd_common(nargs, args, 1);
2787 }
2788 
2789 
2790 /** \brief  Format a virtual floppy
2791  *
2792  * \param[in]   nargs   argument count
2793  * \param[in]   args    argument list
2794  *
2795  * \return  FD_OK on success, or < 0 on failure
2796  */
format_cmd(int nargs,char ** args)2797 static int format_cmd(int nargs, char **args)
2798 {
2799     char *command;
2800     int disk_type;
2801     int dev = -1;   /* index into the drives array */
2802     int unit;       /* unit number */
2803 
2804     switch (nargs) {
2805         case 2:
2806             /* format <diskname,id> */
2807             dev = drive_index;
2808             break;
2809         case 3:
2810             /* format <diskname,id> <unit> */
2811             /* Format the disk image in unit <unit>.  */
2812             if (arg_to_int(args[2], &unit) >= 0
2813                 && check_drive_unit(unit) >= 0) {
2814                 /* It's a valid unit number.  */
2815                 dev = unit -DRIVE_UNIT_MIN;
2816             } else {
2817                 return FD_BADDEV;
2818             }
2819             break;
2820         case 4: /* fallthrough */
2821         case 5:
2822             /* format <diskname,id> <type> <imagename> [<unit>] */
2823             /* Create a new image.  */
2824             /* TODO: change into table lookup */
2825             *args[2] = util_tolower(*args[2]);
2826             if (strcmp(args[2], "d64") == 0) {
2827                 disk_type = DISK_IMAGE_TYPE_D64;
2828             } else if (strcmp(args[2], "d67") == 0) {
2829                 disk_type = DISK_IMAGE_TYPE_D67;
2830             } else if (strcmp(args[2], "d71") == 0) {
2831                 disk_type = DISK_IMAGE_TYPE_D71;
2832             } else if (strcmp(args[2], "d81") == 0) {
2833                 disk_type = DISK_IMAGE_TYPE_D81;
2834             } else if (strcmp(args[2], "d80") == 0) {
2835                 disk_type = DISK_IMAGE_TYPE_D80;
2836             } else if (strcmp(args[2], "d82") == 0) {
2837                 disk_type = DISK_IMAGE_TYPE_D82;
2838             } else if (strcmp(args[2], "g64") == 0) {
2839                 disk_type = DISK_IMAGE_TYPE_G64;
2840             } else if (strcmp(args[2], "g71") == 0) {
2841                 disk_type = DISK_IMAGE_TYPE_G71;
2842 #ifdef HAVE_X64_IMAGE
2843             } else if (strcmp(args[2], "x64") == 0) {
2844                 disk_type = DISK_IMAGE_TYPE_X64;
2845 #endif
2846             } else if (strcmp(args[2], "d1m") == 0) {
2847                 disk_type = DISK_IMAGE_TYPE_D1M;
2848             } else if (strcmp(args[2], "d2m") == 0) {
2849                 disk_type = DISK_IMAGE_TYPE_D2M;
2850             } else if (strcmp(args[2], "d4m") == 0) {
2851                 disk_type = DISK_IMAGE_TYPE_D4M;
2852             } else if (strcmp(args[2], "d90") == 0) {
2853                 disk_type = DISK_IMAGE_TYPE_D90;
2854             } else {
2855                 return FD_BADVAL;
2856             }
2857             if (nargs > 4) {
2858                 arg_to_int(args[4], &unit);
2859                 if (check_drive_unit(unit) >= 0) {
2860                     dev = unit - DRIVE_UNIT_MIN;
2861                 } else {
2862                     return FD_BADDEV;
2863                 }
2864             } else {
2865                 dev = 0;
2866             }
2867             if (open_image(dev, args[3], 1, disk_type) < 0) {
2868                 return FD_BADIMAGE;
2869             }
2870             break;
2871         default:
2872             /* Shouldn't happen.  */
2873             return FD_BADVAL;
2874     }
2875 #if 0
2876     printf("Unit: %i\n", unit);
2877 #endif
2878     if (!strchr(args[1], ',')) {
2879         fprintf(stderr, "no ID given, use <name,id>\n");
2880         return FD_BADNAME;
2881     }
2882 
2883     if (check_drive_ready(dev) < 0) {
2884         return FD_NOTREADY;
2885     }
2886 
2887     command = util_concat("n:", args[1], NULL);
2888     charset_petconvstring((uint8_t *)command, 0);
2889 
2890     printf("formatting in unit %d ...\n", dev + DRIVE_UNIT_MIN);
2891     vdrive_command_execute(drives[dev], (uint8_t *)command,
2892             (unsigned int)strlen(command));
2893 
2894     lib_free(command);
2895     return FD_OK;
2896 }
2897 
help_cmd(int nargs,char ** args)2898 static int help_cmd(int nargs, char **args)
2899 {
2900     if (nargs == 1) {
2901         int i;
2902 
2903         printf("Available commands are:\n");
2904         for (i = 0; command_list[i].name != NULL; i++) {
2905             printf("  %s\n", command_list[i].syntax);
2906         }
2907     } else {
2908         int match;
2909 
2910         match = lookup_command(args[1]);
2911         if (match < 0) {
2912             fprintf(stderr, "unknown command `%s'\n", args[1]);
2913         } else {
2914             printf("Syntax: %s\n%s\n",
2915                     command_list[match].syntax,
2916                     command_list[match].description);
2917         }
2918     }
2919 
2920     return FD_OK;
2921 }
2922 
2923 /** \brief  'info' command handler
2924  *
2925  * \param[in]   nargs   argument count
2926  * \param[in]   args    argument list
2927  *
2928  * \return  0 on success, < 0 on failure (`FD_BADDEV`, `FD_NOTREADY`)
2929  */
info_cmd(int nargs,char ** args)2930 static int info_cmd(int nargs, char **args)
2931 {
2932     vdrive_t *vdrive;
2933     const char *format_name;
2934     int dnr;
2935 
2936     if (nargs == 2) {
2937         int unit;
2938 
2939         if (arg_to_int(args[1], &unit) < 0) {
2940             return FD_BADDEV;
2941         }
2942         if (check_drive_unit(unit) < 0) {
2943             return FD_BADDEV;
2944         }
2945         dnr = unit - DRIVE_UNIT_MIN;
2946     } else {
2947         dnr = drive_index;
2948     }
2949 
2950     if (check_drive_ready(dnr) < 0) {
2951         return FD_NOTREADY;
2952     }
2953 
2954     vdrive = drives[dnr];
2955     format_name = image_format_name(vdrive->image_format);
2956     if (format_name == NULL) {
2957         return FD_NOTREADY; /* not quite a proper error code, but it was already
2958                                here in the code */
2959     }
2960 
2961     /* pretty useless (BW)
2962     printf("Description  : %s\n", "None.");
2963      */
2964     printf("disk format  : %s\n", format_name);
2965     /* printf("Sides\t   : %d.\n", hdr.sides);*/
2966     printf("track count  : %u\n", vdrive->image->tracks);
2967     if (vdrive->image->device == DISK_IMAGE_DEVICE_FS) {
2968         printf("error block  : %s\n",
2969                 ((vdrive->image->media.fsimage)->error_info.map)
2970                 ? "Yes" : "No");
2971     }
2972     printf("write protect: %s\n", vdrive->image->read_only ? "On" : "Off");
2973 
2974     return FD_OK;
2975 }
2976 
2977 #if 0
2978 static int list_match_pattern(char *pat, char *str)
2979 {
2980     int n;
2981 
2982     if (*str == '"') {
2983         str++;
2984     }
2985 
2986     if ((*str == 0) && (*pat != 0)) {
2987         return 0;
2988     }
2989 
2990     n = strlen(str);
2991     while (n) {
2992         n--;
2993         if (str[n] == '"') {
2994             str[n] = 0;
2995             break;
2996         }
2997     }
2998 
2999     while (*str) {
3000         if (*pat == '*') {
3001             return 1;
3002         } else if ((*pat != '?') && (*pat != *str)) {
3003             return 0;
3004         }
3005         str++; pat++;
3006     }
3007     if ((*pat != 0) && (*pat != '*')) {
3008         return 0;
3009     }
3010     return 1;
3011 }
3012 #endif
3013 
3014 
3015 /** \brief  Match \a name and \a type against \a pattern
3016  *
3017  * Matches \a name against \a pattern and optionally against filetype \a type
3018  * if present in the \a pattern.
3019  * The pattern is the standard CBM wildcard pattern: '*' and '?' with optional
3020  * '=X' where X is one of S, P or R.
3021  *
3022  * \param[in]   name    filename
3023  * \param[in]   type    filetype
3024  * \param[in]   pattern wildcard pattern
3025  * \param]in]   plen    length of pattern
3026  *
3027  * return   bool
3028  */
match_sub_pattern(const char * name,int type,const char * pattern,int plen)3029 static int match_sub_pattern(const char *name, int type,
3030                              const char *pattern, int plen)
3031 {
3032     int n;
3033     int p;
3034 
3035     p = 0;
3036 #if 0
3037     printf(".. name = '%s'\n", name);
3038     printf(".. subpattern = '");
3039     while (p < plen) {
3040         putchar(pattern[p]);
3041         p++;
3042     }
3043     printf("'\n");
3044 #endif
3045     /* first check if we have a filetype specifier */
3046     if (plen > 2 && pattern[plen - 2] == '=') {
3047         if (toupper((int)(pattern[plen -1])) != type) {
3048             return 0;
3049         } else {
3050             /* reduce pattern size (strip off '=X') */
3051             plen -= 2;
3052         }
3053     }
3054 
3055     /* match using * and ? */
3056     p = 0;
3057     n = 1;  /* skip '"'" */
3058     while (p < plen && name[n] != '\0' && name[n] != '"') {
3059         if (pattern[p] == '*') {
3060             return 1;
3061         }
3062         if (pattern[p] != name[n] && pattern[p] != '?') {
3063             return 0;
3064         }
3065         p++;
3066         n++;
3067     }
3068     if (name[n] != '"' && name[n] != '\0') {
3069         /* leftover chars in name, but pattern had ended, no match */
3070         return 0;
3071     }
3072     return 1;
3073 }
3074 
3075 
3076 
3077 /** \brief  Test a file \a name and \a type against \a pattern
3078  *
3079  * This function can handle multiple patterns separated by comma's, and supports
3080  * specifying a file type per sub pattern.
3081  *
3082  * Example: "foo*=p,b?r*=s", will match PRG files against "foo*" and SEQ files
3083  * against "b?r*". A match is found if any of the sub patterns matches (in other
3084  * words: OR).
3085  *
3086  * WARNING: It appears CBM DOS only allows a single file type test, use two or
3087  * more results in a `?FILE NOT FOUND  ERROR`. The =X specifier can also only
3088  * come last, anything after =X results in an error. (In C1541 this doesn't
3089  * matter, but in VDrive it does).
3090  *
3091  * \param[in]   name    filename
3092  * \param[in]   type    filetype (eg " prg<")
3093  * \param[in]   pattern pattern to match against
3094  *
3095  * \return  bool
3096  */
list_file_matches_pattern(const char * name,const char * type,const char * pattern)3097 static int list_file_matches_pattern(const char *name,
3098                                      const char *type,
3099                                      const char *pattern)
3100 {
3101     int i;
3102     int ftype;
3103     int plen;
3104 
3105     /* get filetype as single token */
3106     ftype = toupper((int)(type[1]));   /* P, S, D, R, U */
3107 
3108     /* pattern length */
3109     plen = (int)strlen(pattern);
3110 
3111     i = 0;
3112     /* check quotes */
3113     if (pattern[0] == '"') {
3114         if (pattern[plen - 1] != '"') {
3115             return 0;   /* unmatched quotes, fail */
3116         }
3117         i++;
3118     }
3119 
3120     while (i < plen) {
3121         int k = i;
3122         while (k < plen && pattern[k] != ',') {
3123             k++;
3124         }
3125         /* got sub pattern */
3126         if (k - i > 0) {
3127             if (match_sub_pattern(name, ftype, pattern + i, k - i)) {
3128                 return 1;
3129             }
3130         } else {
3131             k++;    /* empty sub pattern, fail or continue */
3132         }
3133         if (pattern[k] == ',' || pattern[k] == '"') {
3134             k++;
3135         }
3136         i = k;
3137     }
3138 
3139     return 0;
3140 }
3141 
3142 
3143 
3144 /** \brief  Show directory listing of a drive
3145  *
3146  * \param[in]   nargs   number of arguments
3147  * \param[in]   args    argument list
3148  *
3149  * \return  0 on success, < 0 on failure (`FD_NOTREADY`)
3150  *
3151  * FIXME: diskcontents_read internally opens/closes the disk image, including
3152  *        a complete reset of internal vdrive variables. this makes things like
3153  *        changing sub partitions and sub directories inside images impossible
3154  *        from the c1541 shell.
3155  */
list_cmd(int nargs,char ** args)3156 static int list_cmd(int nargs, char **args)
3157 {
3158     char *pattern;
3159     const char *name;
3160     char *type;
3161     image_contents_t *listing;
3162     int dnr = drive_index;
3163     vdrive_t *vdrive;
3164     int unit = DRIVE_UNIT_MIN;
3165 
3166     unsigned int drive = 0;
3167 
3168     if (nargs > 1) {
3169         /* use new version call untill all old calls are replaced */
3170         unit = extract_unit_from_file_name(args[1], &pattern);
3171         if (unit == 0) {
3172             dnr = (int)drive_index;
3173         } else if (unit > 0) {
3174             dnr = (int)(unit - DRIVE_UNIT_MIN);
3175         } else {
3176             return FD_BADDEV;
3177         }
3178 
3179     } else {
3180         /* list */
3181         pattern = NULL;
3182         dnr = drive_index;
3183     }
3184 
3185     if (check_drive_index(dnr) < 0) {
3186         return FD_BADDEV;
3187     }
3188 
3189     if (check_drive_ready(dnr) < 0) {
3190         return FD_NOTREADY;
3191     }
3192 
3193     vdrive = drives[dnr];
3194     name = disk_image_name_get(vdrive->image);
3195 
3196     listing = diskcontents_read(name, (unsigned int)(dnr + DRIVE_UNIT_MIN), drive);
3197 
3198     if (listing != NULL) {
3199         char *string = image_contents_to_string(listing, 1);
3200         image_contents_file_list_t *element = listing->file_list;
3201 
3202         printf("%s\n", string);
3203         lib_free(string);
3204         if (element == NULL) {
3205             printf("Empty image\n");
3206         } else {
3207             do {
3208                 string = image_contents_filename_to_string(element, 1);
3209                 type = image_contents_filetype_to_string(element, 1);
3210                 if ((pattern == NULL) || list_file_matches_pattern(string,
3211                             type, pattern)) {
3212                     lib_free(string);
3213                     string = image_contents_file_to_string(element, 1);
3214                     printf("%s\n", string);
3215                 }
3216                 lib_free(string);
3217                 lib_free(type);
3218             } while ((element = element->next) != NULL);
3219         }
3220         if (listing->blocks_free >= 0) {
3221             printf("%d blocks free.\n", listing->blocks_free);
3222         }
3223         /* free image contents */
3224         image_contents_destroy(listing);
3225     }
3226     return FD_OK;
3227 }
3228 
3229 
3230 /** \brief  Change disk name and id
3231  *
3232  * Syntax: name "diskname[,id]" [unit]
3233  *
3234  * \param[in]   nargs   argument count
3235  * \param[in]   args    argument list
3236  *
3237  * \return 0 on succes, < 0 on failure (`FD_BADEV` or `FD_NOTREADY`)
3238  */
name_cmd(int nargs,char ** args)3239 static int name_cmd(int nargs, char **args)
3240 {
3241     char *id;
3242     char *name;
3243     uint8_t *dst;
3244     int i;
3245     int unit;
3246     vdrive_t *vdrive;
3247 
3248     if (nargs > 2) {
3249         if (arg_to_int(args[2], &unit) < 0) {
3250             return FD_BADDEV;
3251         }
3252         if (check_drive_unit(unit) < 0) {
3253             return FD_BADDEV;
3254         }
3255         unit -= DRIVE_UNIT_MIN;
3256     } else {
3257         unit = drive_index;
3258     }
3259 
3260     if (check_drive_ready(unit) < 0) {
3261         return FD_NOTREADY;
3262     }
3263 
3264     vdrive = drives[unit];
3265     vdrive_bam_read_bam(vdrive);
3266     name = args[1];
3267     charset_petconvstring((uint8_t *)name, 0);
3268     id = strrchr(args[1], ',');
3269     if (id) {
3270         *id++ = '\0';
3271     }
3272 
3273     dst = &vdrive->bam[vdrive->bam_name];
3274     for (i = 0; i < IMAGE_CONTENTS_NAME_LEN; i++) {
3275         *dst++ = (unsigned char)(*name ? *name++ : 0xa0);
3276     }
3277 
3278     if (id) {
3279         dst = &vdrive->bam[vdrive->bam_id];
3280         for (i = 0; i < IMAGE_CONTENTS_ID_LEN && *id; i++) {
3281             *dst++ = (unsigned char)(*id++);
3282         }
3283     }
3284 
3285     vdrive_bam_write_bam(vdrive);
3286     return FD_OK;
3287 }
3288 
quit_cmd(int nargs,char ** args)3289 static int quit_cmd(int nargs, char **args)
3290 {
3291     int i;
3292 
3293     for (i = 0; i < NUM_DISK_UNITS; i++) {
3294         if (drives[i] != NULL) {
3295             close_disk_image(drives[i], i + DRIVE_UNIT_MIN);
3296             lib_free(drives[i]);
3297         }
3298 
3299     }
3300 
3301     /* weird: free **args */
3302     for (i = 0; i < nargs; i++) {
3303         lib_free(args[i]);
3304     }
3305 
3306     if (interactive_mode) {
3307         archdep_shutdown();
3308         log_close_all();    /* do we need this? */
3309     }
3310     exit(0);
3311     return 0;   /* OSF1 cc complains */
3312 }
3313 
3314 
3315 /** \brief  Be verbose - enable output of extra logging information
3316  *
3317  * If 'off' is given as an argument, verbose is turned off again.
3318  *
3319  * \param[in]   nargs   argument count
3320  * \param[in]   args    argument list
3321  *
3322  * \return  0
3323  */
verbose_cmd(int nargs,char ** args)3324 static int verbose_cmd(int nargs, char **args)
3325 {
3326     if (nargs >= 2 && strcmp(args[1], "off") == 0) {
3327         return log_set_verbose(0);
3328     } else {
3329         return log_set_verbose(1);
3330     }
3331 }
3332 
3333 
3334 /** \brief  Disable all logging
3335  */
silent_cmd(int nargs,char ** args)3336 static int silent_cmd(int nargs, char **args)
3337 {
3338     if (nargs >= 2 && strcmp(args[1], "off") == 0) {
3339         return log_set_silent(0);
3340     } else {
3341         return log_set_silent(1);
3342     }
3343 }
3344 
read_cmd(int nargs,char ** args)3345 static int read_cmd(int nargs, char **args)
3346 {
3347     char *src_name_petscii, *src_name_ascii;
3348     char *dest_name_ascii;
3349     char *actual_name;
3350     char *p;
3351     int dnr;
3352     int unit;
3353     FILE *outf = NULL;
3354     fileio_info_t *finfo = NULL;
3355     unsigned int format = FILEIO_FORMAT_RAW;
3356     uint8_t c;
3357     int status = 0;
3358     uint8_t *slot;
3359     uint8_t file_type;
3360 
3361     unit = extract_unit_from_file_name(args[1], &p);
3362     if (unit <= 0) {
3363         dnr = drive_index;
3364     } else {
3365         dnr = unit - DRIVE_UNIT_MIN;
3366     }
3367 
3368     if (check_drive_ready(dnr) < 0) {
3369         return FD_NOTREADY;
3370     }
3371 
3372     /* check for P00 save mode */
3373     if (p00save[dnr]) {
3374         format = FILEIO_FORMAT_P00;
3375     }
3376 
3377     if (p == NULL) {
3378         src_name_ascii = lib_strdup(args[1]);
3379     } else {
3380         src_name_ascii = lib_strdup(p);
3381     }
3382 
3383     if (!is_valid_cbm_file_name(src_name_ascii)) {
3384         fprintf(stderr, "`%s' is not a valid CBM DOS file name\n",
3385                 src_name_ascii);
3386         lib_free(src_name_ascii);
3387         return FD_BADNAME;
3388     }
3389 
3390     src_name_petscii = lib_strdup(src_name_ascii);
3391     charset_petconvstring((uint8_t *)src_name_petscii, 0);
3392 
3393     if (vdrive_iec_open(drives[dnr], (uint8_t *)src_name_petscii,
3394                         (unsigned int)strlen(src_name_petscii), 0, NULL)) {
3395         fprintf(stderr,
3396                 "cannot read `%s' on unit %d\n", src_name_ascii, dnr + 8);
3397         lib_free(src_name_ascii);
3398         lib_free(src_name_petscii);
3399         return FD_BADNAME;
3400     }
3401 
3402     /* Get real filename from the disk file.  Slot must be defined by
3403        vdrive_iec_open().  */
3404     bufferinfo_t *bufferinfo = &drives[dnr]->buffers[0];        /* 0 = secadr */
3405     slot = bufferinfo->slot;
3406     actual_name = lib_malloc(IMAGE_CONTENTS_FILE_NAME_LEN + 1);
3407     memcpy(actual_name, slot + SLOT_NAME_OFFSET, IMAGE_CONTENTS_FILE_NAME_LEN);
3408     actual_name[IMAGE_CONTENTS_FILE_NAME_LEN] = 0;
3409 
3410     file_type = slot[SLOT_TYPE_OFFSET] & 7;
3411 
3412     if (nargs == 3) {
3413         if (strcmp(args[2], "-") == 0) {
3414             dest_name_ascii = NULL;      /* stdout */
3415         } else {
3416             char *open_petscii_name;
3417             int reclen = slot[SLOT_RECORD_LENGTH];
3418 
3419             dest_name_ascii = args[2];
3420             open_petscii_name = lib_strdup(dest_name_ascii);
3421             charset_petconvstring((uint8_t *)open_petscii_name, 0);
3422             finfo = fileio_open(open_petscii_name, NULL, format,
3423                                 FILEIO_COMMAND_OVERWRITE, file_type, &reclen);
3424             lib_free(open_petscii_name);
3425         }
3426     } else {
3427         int reclen = slot[SLOT_RECORD_LENGTH];
3428         size_t l;
3429 
3430         dest_name_ascii = actual_name;
3431         vdrive_dir_no_a0_pads((uint8_t *)dest_name_ascii, CBMDOS_SLOT_NAME_LENGTH);
3432         l = strlen(dest_name_ascii) - 1;
3433         while (dest_name_ascii[l] == ' ') {
3434             dest_name_ascii[l] = 0;
3435             l--;
3436         }
3437         /* remove illegal chars from output filename */
3438         archdep_sanitize_filename(dest_name_ascii);
3439 
3440         finfo = fileio_open(dest_name_ascii, NULL, format,
3441                             FILEIO_COMMAND_OVERWRITE, file_type, &reclen);
3442     }
3443 
3444     if (dest_name_ascii == NULL) {
3445         outf = stdout;
3446     } else {
3447         if (finfo == NULL) {
3448             fprintf(stderr, "cannot create output file `%s': %s\n",
3449                     dest_name_ascii, strerror(errno));
3450             vdrive_iec_close(drives[dnr], 0);
3451             lib_free(src_name_petscii);
3452             lib_free(src_name_ascii);
3453             lib_free(actual_name);
3454             return FD_NOTWRT;
3455         }
3456     }                           /* stdout */
3457 
3458     printf("reading file `%s' from unit %d\n", src_name_ascii, dnr + DRIVE_UNIT_MIN);
3459 
3460     if (file_type == CBMDOS_FT_REL) {
3461         unsigned int rel_record_length = slot[SLOT_RECORD_LENGTH];
3462         unsigned int num_rel_records = bufferinfo->record_max;
3463         unsigned int record;
3464 
3465         for (record = 1; record <= num_rel_records; record++) {
3466             int bytes = 0;
3467             do {
3468                 status = vdrive_iec_read(drives[dnr], &c, 0);
3469                 if (status == SERIAL_ERROR && bytes == 0) {
3470                     fprintf(stderr, "dummy record CR; should not happen.\n");
3471                     break; /* record after EOF; we get a dummy CR */
3472                 }
3473                 if (dest_name_ascii == NULL) {
3474                     fputc(c, outf);
3475                 } else {
3476                     fileio_write(finfo, &c, 1);
3477                 }
3478                 bytes++;
3479             } while (status == SERIAL_OK);
3480 
3481             /*
3482              * Pad REL records to full record length.
3483              * For the last record the drive may in theory know how many
3484              * bytes are really used (rather than relying on 0-padding),
3485              * but it has no way of letting the user know.
3486              */
3487             c = 0;
3488             while (bytes < rel_record_length) {
3489                 if (dest_name_ascii == NULL) {
3490                     fputc(c, outf);
3491                 } else {
3492                     fileio_write(finfo, &c, 1);
3493                 }
3494                 bytes++;
3495             }
3496         }
3497     } else {
3498         do {
3499             status = vdrive_iec_read(drives[dnr], &c, 0);
3500             if (dest_name_ascii == NULL) {
3501                 fputc(c, outf);
3502             } else {
3503                 fileio_write(finfo, &c, 1);
3504             }
3505         } while (status == SERIAL_OK);
3506     }
3507 
3508     if (dest_name_ascii != NULL) {
3509         fileio_close(finfo);
3510     }
3511 
3512     vdrive_iec_close(drives[dnr], 0);
3513 
3514     lib_free(src_name_petscii);
3515     lib_free(src_name_ascii);
3516     lib_free(actual_name);
3517 
3518     return FD_OK;
3519 }
3520 
3521 #define SLOT_GEOS_FILE_STRUC  23  /* Offset to geos file structure byte */
3522 #define SLOT_GEOS_FILE_TYPE   24  /* Offset to geos file type
3523           */
3524 
3525 /* Geos file structure sequential (no vlir) */
3526 #define GEOS_FILE_STRUC_SEQ   0
3527 /* Geos file structure VLIR (index block, max. 127 chains) */
3528 #define GEOS_FILE_STRUC_VLIR  1
3529 
3530 /* Author:      DiSc
3531  * Date:        2000-07-28
3532  *
3533  * companion to geos_read_cmd.
3534  * Expects an opened file in drives[unit]->buffers[0].slot
3535  */
internal_read_geos_file(int unit,FILE * outf,char * src_name_ascii)3536 int internal_read_geos_file(int unit, FILE* outf, char* src_name_ascii)
3537 {
3538     unsigned int n;
3539 
3540     /* Get TS of info block */
3541     uint8_t infoTrk = drives[unit]->buffers[0].slot[SLOT_SIDE_TRACK];
3542     uint8_t infoSec = drives[unit]->buffers[0].slot[SLOT_SIDE_SECTOR];
3543 
3544     /* Get TS of first data block or vlir block
3545        (depends on the geos file type) */
3546     uint8_t firstTrk = drives[unit]->buffers[0].slot[SLOT_FIRST_TRACK];
3547     uint8_t firstSec = drives[unit]->buffers[0].slot[SLOT_FIRST_SECTOR];
3548 
3549     /* get geos file structure and geos file type */
3550     uint8_t geosFileStruc = drives[unit]->buffers[0].slot[SLOT_GEOS_FILE_STRUC];
3551     /*uint8_t geosFileType = drives[unit]->buffers[0].slot[SLOT_GEOS_FILE_TYPE];*/
3552 
3553     uint8_t infoBlock[256];
3554     uint8_t vlirBlock[256];
3555     uint8_t block[256];
3556     uint8_t vlirTrans[256];
3557 
3558     unsigned int aktTrk, aktSec, vlirIdx, NoOfBlocks, NoOfChains, BytesInLastSector;
3559 
3560     /* the first block in a cvt file is the directory entry padded with
3561        zeros */
3562     for (n = 2; n < 32; n++) {
3563         uint8_t c = drives[unit]->buffers[0].slot[n];
3564         fputc(c, outf);
3565     }
3566     /* signature */
3567     fprintf(outf, "%s formatted GEOS file V1.0", (geosFileStruc == GEOS_FILE_STRUC_SEQ) ? "SEQ" : "PRG");
3568     /* pad with zeros */
3569     for (n = 0x3a; n < 0xfe; n++) {
3570         fputc(0, outf);
3571     }
3572 
3573     /* read info block */
3574     if (vdrive_read_sector(drives[unit], infoBlock, infoTrk, infoSec) != 0) {
3575         fprintf(stderr,
3576                 "cannot read input file info block `%s': %s\n",
3577                 src_name_ascii, strerror(errno));
3578         return FD_RDERR;
3579     }
3580     /* the next block is the info sector
3581      * write info block
3582      */
3583     for (n = 2; n < 256; n++) {
3584         fputc(infoBlock[n], outf);
3585     }
3586 
3587     /* read first data block or vlir block */
3588     if (vdrive_read_sector(drives[unit], vlirBlock, firstTrk, firstSec) != 0) {
3589         fprintf(stderr,
3590                 "cannot read input file data `%s': %s\n",
3591                 src_name_ascii, strerror(errno));
3592         return FD_RDERR;
3593     }
3594 
3595     if (geosFileStruc == GEOS_FILE_STRUC_SEQ) {
3596 #ifdef DEBUG_DRIVE
3597         log_debug("DEBUG: GEOS_FILE_STRUC_SEQ (%d:%d)", infoTrk, infoSec);
3598 #endif
3599         /* sequential file contained in cvt file
3600          * since vlir block is the first data block simply put it to
3601          * disk
3602          */
3603 
3604         for (n = 2; n < 256; n++) {
3605             fputc(vlirBlock[n], outf);
3606         }
3607 
3608         /* the rest is like standard cbm file TS-Chains.
3609            Put them to disk */
3610 
3611         aktTrk = vlirBlock[0];
3612         aktSec = vlirBlock[1];
3613         while (aktTrk != 0) {
3614             if (vdrive_read_sector(drives[unit], block, aktTrk, aktSec) != 0) {
3615                 fprintf(stderr,
3616                         "cannot read input file data block `%s': %s\n",
3617                         src_name_ascii, strerror(errno));
3618                 return FD_RDERR;
3619             }
3620             aktTrk = block[0];
3621             aktSec = block[1];
3622             BytesInLastSector = aktTrk != 0 ? 256 : aktSec + 1;
3623             for (n = 2; n < BytesInLastSector; n++) {
3624                 fputc(block[n], outf);
3625             }
3626         }
3627     } else if (geosFileStruc == GEOS_FILE_STRUC_VLIR) {
3628 #ifdef DEBUG_DRIVE
3629         log_debug("DEBUG: GEOS_FILE_STRUC_VLIR (%d:%d)", infoTrk, infoSec);
3630 #endif
3631         /* The vlir block in cvt files is a conversion of the vlir
3632          * block on cbm disks.
3633          * Every non empty or non existent TS pointer is replaced with
3634          * (NoOfBlocks in Record, Bytes in last Record block).
3635          * Therefore the vlirBlock
3636          * is translated to vlirTrans according this rule.
3637          *
3638          * Copy vlir block to vlirTrans
3639          */
3640         for (n = 2; n < 256; n++) {
3641             vlirTrans[n] = vlirBlock[n];
3642         }
3643 
3644 #ifdef DEBUG_DRIVE
3645         log_debug("DEBUG: VLIR scan record chains");
3646 #endif
3647 
3648         /* Replace the TS-chain-origins with NoOfBlocks/BytesInLastSector */
3649 
3650         vlirIdx = 2;
3651         aktTrk = vlirBlock[vlirIdx];
3652         aktSec = vlirBlock[vlirIdx + 1];
3653         NoOfBlocks = 0;
3654         NoOfChains = 0;
3655         BytesInLastSector = 255;
3656         while (aktTrk != 0 && vlirIdx <= 254) {
3657             if (aktTrk != 0) { /* Record exists and is not empty */
3658 #ifdef DEBUG_DRIVE
3659                 log_debug("DEBUG: VLIR IDX %u", vlirIdx);
3660 #endif
3661                 NoOfChains++;
3662                 while (aktTrk != 0) {
3663                     /* Read the chain and collect No Of Blocks */
3664 #ifdef DEBUG_DRIVE
3665                     log_debug("DEBUG: VLIR BLOCK (%u:%u)", aktTrk, aktSec);
3666 #endif
3667 
3668                     if (vdrive_read_sector(drives[unit], block, aktTrk, aktSec) != 0) {
3669                         fprintf(stderr,
3670                                 "cannot read input file data block `%s': %s\n",
3671                                 src_name_ascii, strerror(errno));
3672                         return FD_RDERR;
3673                     }
3674 
3675                     /* Read TS for next sector*/
3676 
3677                     aktTrk = block[0];
3678                     aktSec = block[1];
3679 
3680                     if (aktTrk != 0) { /* Next Sector exists */
3681                         NoOfBlocks++;
3682                     } else { /* Current was last sector of chain */
3683                         NoOfBlocks++;
3684                         BytesInLastSector = aktSec;
3685                     }
3686                 }
3687             }
3688             if (NoOfBlocks != 0) {
3689                 /* Entries for empty or non existing entries
3690                  * in vlir block are the same in cvt and vlir files */
3691                 vlirTrans[vlirIdx] = (unsigned char)NoOfBlocks;
3692                 vlirTrans[vlirIdx + 1] = (unsigned char)BytesInLastSector;
3693             }
3694 
3695             /* Prepare for next loop iteration */
3696 
3697             vlirIdx += 2;
3698             if (vlirIdx <= 254) {
3699                 aktTrk = vlirBlock[vlirIdx];
3700                 aktSec = vlirBlock[vlirIdx + 1];
3701             }
3702             NoOfBlocks = 0;
3703             BytesInLastSector = 255;
3704         }
3705 
3706         /* output transformed vlir block */
3707 
3708         for (n = 2; n < 256; n++) {
3709             fputc(vlirTrans[n], outf);
3710         }
3711 
3712 #ifdef DEBUG_DRIVE
3713         log_debug("DEBUG: VLIR output record chains");
3714 #endif
3715         /* now output the record chains
3716            (leave the TS-Pointers since they are usesless now) */
3717 
3718         vlirIdx = 2;
3719         aktTrk = vlirBlock[vlirIdx];
3720         aktSec = vlirBlock[vlirIdx + 1];
3721         while (aktTrk != 0 && vlirIdx <= 254) {
3722             if (aktTrk != 0) {
3723 #ifdef DEBUG_DRIVE
3724                 log_debug("DEBUG: VLIR IDX %u", vlirIdx);
3725 #endif
3726                 NoOfChains--;
3727                 /* Record exists */
3728                 while (aktTrk != 0) {
3729 #ifdef DEBUG_DRIVE
3730                     log_debug("DEBUG: VLIR BLOCK (%u:%u)", aktTrk, aktSec);
3731 #endif
3732                     if (vdrive_read_sector(drives[unit], block, aktTrk, aktSec) != 0) {
3733                         fprintf(stderr,
3734                                 "cannot read input file data block `%s': %s\n",
3735                                 src_name_ascii, strerror(errno));
3736                         return FD_RDERR;
3737                     }
3738                     aktTrk = block[0];
3739                     aktSec = block[1];
3740                     BytesInLastSector = ((NoOfChains != 0) || (aktTrk != 0)) ? 256 : aktSec + 1;
3741                     for (n = 2; n < BytesInLastSector; n++) {
3742                         fputc(block[n], outf);
3743                     }
3744                 }
3745             }
3746             vlirIdx += 2;
3747             if (vlirIdx <= 254) {
3748                 aktTrk = vlirBlock[vlirIdx];
3749                 aktSec = vlirBlock[vlirIdx + 1];
3750             }
3751         }
3752     } else {
3753         fprintf(stderr, "unknown GEOS-File structure\n");
3754         return FD_RDERR;
3755     }
3756     return FD_OK;
3757 }
3758 
3759 /* Author:      DiSc
3760  * Date:        2000-07-28
3761  * Reads a geos file from the diskimage and writes it to a convert file
3762  * This code was copied from the write_cmd function.
3763  *
3764  *
3765  * This function was completely broken and assumed GEOS files are PRG files
3766  * with PETSCII filenames. So when people complain this command doesnt work
3767  * anymore, they have incorrect GEOS files.
3768  *
3769  * Also note that the directory display routine of c1541 assumes PETSCII file
3770  * names and as such the GEOS file names are display with their case inverted,
3771  * so to extract a file called 'rEADmE' in the directory, use 'ReadMe' to
3772  * extract it.
3773  *
3774  * -- compyx, 2018-05-18
3775  */
read_geos_cmd(int nargs,char ** args)3776 static int read_geos_cmd(int nargs, char **args)
3777 {
3778     char *src_name_ascii;
3779     char *dest_name_ascii;
3780     char *actual_name;
3781     char *p;
3782     FILE *outf;
3783     int err_code;
3784     int dev;
3785     int unit;
3786     cbmdos_cmd_parse_t *parse_cmd;
3787     size_t namelen;
3788 
3789 
3790     unit = extract_unit_from_file_name(args[1], &p);
3791     if (unit > 0) {
3792         dev = unit - DRIVE_UNIT_MIN;
3793     } else if (unit == 0) {
3794         /* no @<unit>: found */
3795         dev = drive_index;
3796         unit = drive_index + DRIVE_UNIT_MIN;
3797     } else {
3798         /* -1, invalid unit number */
3799         return FD_BADDEV;
3800     }
3801 
3802     if (check_drive_ready(dev) < 0) {
3803         return FD_NOTREADY;
3804     }
3805 
3806     if (p == NULL || *p == '\0') {
3807         fprintf(stderr,
3808                 "missing filename\n");
3809         return FD_BADNAME;
3810     } else {
3811         src_name_ascii = lib_strdup(p);
3812     }
3813 
3814     if (!is_valid_cbm_file_name(src_name_ascii)) {
3815         fprintf(stderr,
3816                 "`%s' is not a valid CBM DOS file name\n", src_name_ascii);
3817         lib_free(src_name_ascii);
3818         return FD_BADNAME;
3819     }
3820 
3821 
3822     /*
3823      * We use this to pass to vdrive_iec_open() as its `cmd_parse_ext` argument
3824      * to tell the function to look for USR files. Without this the function
3825      * defaults to looking for PRG files and will fail to locate the GEOS file
3826      * requested.
3827      */
3828     namelen = strlen(src_name_ascii);
3829     parse_cmd = lib_calloc(1, sizeof *parse_cmd);
3830     parse_cmd->cmd = (const uint8_t *)src_name_ascii;
3831     parse_cmd->cmdlength = (unsigned int)namelen;
3832     parse_cmd->parsecmd = lib_strdup(src_name_ascii); /* freed in
3833                                                            vdrive_iec_open() */
3834     parse_cmd->parselength = (unsigned int)namelen;
3835     parse_cmd->secondary = 0;
3836     parse_cmd->filetype = CBMDOS_FT_USR;
3837     parse_cmd->readmode = CBMDOS_FAM_READ;
3838 
3839     if (vdrive_iec_open(drives[dev], (uint8_t *)src_name_ascii,
3840                         (unsigned int)strlen(src_name_ascii), 0,
3841                         parse_cmd)) {
3842         fprintf(stderr,
3843                 "cannot read `%s' on unit %d\n", src_name_ascii, unit);
3844         lib_free(src_name_ascii);
3845         lib_free(parse_cmd);
3846         return FD_BADNAME;
3847     }
3848 
3849     lib_free(parse_cmd);
3850 
3851     /* Get real filename from the disk file.
3852        Slot must be defined by vdrive_iec_open().  */
3853     actual_name = lib_malloc(CBMDOS_SLOT_NAME_LENGTH + 1);
3854     memcpy(actual_name,
3855             drives[dev]->buffers[0].slot + SLOT_NAME_OFFSET,
3856             CBMDOS_SLOT_NAME_LENGTH);
3857     actual_name[CBMDOS_SLOT_NAME_LENGTH] = '\0';
3858 
3859     if (nargs == 3) {
3860         dest_name_ascii = args[2];
3861     } else {
3862         int l;
3863 
3864         dest_name_ascii = actual_name;
3865         vdrive_dir_no_a0_pads((uint8_t *) dest_name_ascii, CBMDOS_SLOT_NAME_LENGTH);
3866         l = (int)strlen(dest_name_ascii) - 1;
3867         while (dest_name_ascii[l] == ' ') {
3868             dest_name_ascii[l] = 0;
3869             l--;
3870         }
3871         /*
3872          * Don't convert, GEOS uses ASCII
3873          */
3874 #if 0
3875         charset_petconvstring((uint8_t *)dest_name_ascii, 1);
3876 #endif
3877     }
3878 
3879     outf = fopen(dest_name_ascii, MODE_WRITE);
3880     if (outf == NULL) {
3881         fprintf(stderr,
3882                 "cannot create output file `%s': %s\n",
3883                 dest_name_ascii, strerror(errno));
3884         vdrive_iec_close(drives[dev], 0);
3885         lib_free(src_name_ascii);
3886         lib_free(actual_name);
3887         return FD_NOTWRT;
3888     }
3889 
3890     printf("reading file `%s' from unit %d\n", src_name_ascii, unit);
3891 
3892     err_code = internal_read_geos_file(dev, outf, src_name_ascii);
3893 
3894     fclose(outf);
3895     vdrive_iec_close(drives[dev], 0);
3896 
3897     lib_free(src_name_ascii);
3898     lib_free(actual_name);
3899 
3900     return err_code;
3901 }
3902 
3903 /* Author: DiSc
3904  * Date:   2000-07-28
3905  * Put the NTS-Chain on a block. Can also be a vlir block if blk_offset is > 1
3906  */
fix_ts(int unit,unsigned int trk,unsigned int sec,unsigned int next_trk,unsigned int next_sec,unsigned int blk_offset)3907 static int fix_ts(int unit, unsigned int trk, unsigned int sec,
3908                   unsigned int next_trk, unsigned int next_sec,
3909                   unsigned int blk_offset)
3910 {
3911     uint8_t block[256];
3912     if (vdrive_read_sector(drives[unit], block, trk, sec) == 0) {
3913         block[blk_offset] = (unsigned char)next_trk;
3914         block[blk_offset + 1] = (unsigned char)next_sec;
3915         if (vdrive_write_sector(drives[unit], block, trk, sec) == 0) {
3916             return 1;
3917         }
3918     }
3919     return 0;
3920 }
3921 
3922 /* Author:      DiSc
3923  * Date:        2000-07-28
3924  * Companion to write_geos_cmd. Expects an file opened for writing in
3925  * drives[unit]->buffers[1].slot
3926  */
internal_write_geos_file(int unit,FILE * f)3927 static int internal_write_geos_file(int unit, FILE* f)
3928 {
3929     uint8_t dirBlock[256];
3930     uint8_t infoBlock[256];
3931     uint8_t vlirBlock[256];
3932     uint8_t block[256];
3933     unsigned int vlirTrk, vlirSec;
3934     unsigned int aktTrk, aktSec;
3935     unsigned int lastTrk, lastSec;
3936     uint8_t geosFileStruc;
3937     int c = 0;
3938     unsigned int n;
3939     int bContinue;
3940     int numBlks, bytesInLastBlock;
3941 
3942     /* First block of cvt file is the directory entry, rest padded with zeros */
3943 
3944     for (n = 2; n < 256; n++) {
3945         c = fgetc(f);
3946         dirBlock[n] = (unsigned char)c;
3947     }
3948 
3949     /* copy to the already created slot */
3950 
3951     for (n = 2; n < 32; n++) {
3952         drives[unit]->buffers[1].slot[n] = dirBlock[n];
3953     }
3954 
3955     geosFileStruc = drives[unit]->buffers[1].slot[SLOT_GEOS_FILE_STRUC];
3956 
3957     /* next is the geos info block */
3958 
3959     infoBlock[0] = 0;
3960     infoBlock[1] = 0xFF;
3961     for (n = 2; n < 256; n++) {
3962         c = fgetc(f);
3963         infoBlock[n] = (unsigned char)c;
3964     }
3965 
3966     /* put it on disk */
3967 
3968     if (vdrive_bam_alloc_first_free_sector(drives[unit], &vlirTrk, &vlirSec) < 0) {
3969         fprintf(stderr, "disk full\n");
3970         return FD_WRTERR;
3971     }
3972     if (vdrive_write_sector(drives[unit], infoBlock, vlirTrk, vlirSec) != 0) {
3973         fprintf(stderr, "disk full\n");
3974         return FD_WRTERR;
3975     }
3976 
3977     /* and put the blk/sec in the dir entry */
3978 
3979     drives[unit]->buffers[1].slot[SLOT_SIDE_TRACK] = (unsigned char)vlirTrk;
3980     drives[unit]->buffers[1].slot[SLOT_SIDE_SECTOR] = (unsigned char)vlirSec;
3981 
3982     /* now read the first data block. if its a vlir-file its the vlir index
3983      * block
3984      * else its already a data block */
3985     for (n = 2; n < 256; n++) {
3986         c = fgetc(f);
3987         vlirBlock[n] = (unsigned char)c;
3988     }
3989 
3990     /* the vlir index block always has a NTS-chain of (0, FF) */
3991 
3992     if (geosFileStruc == GEOS_FILE_STRUC_VLIR) {
3993         vlirBlock[0] = 0;
3994         vlirBlock[1] = 0xFF;
3995     }
3996 
3997     /* write the block */
3998 
3999     if (vdrive_bam_alloc_next_free_sector(drives[unit], &vlirTrk, &vlirSec) < 0) {
4000         fprintf(stderr, "disk full\n");
4001         return FD_WRTERR;
4002     }
4003 
4004     if (vdrive_write_sector(drives[unit], vlirBlock, vlirTrk, vlirSec) != 0) {
4005         fprintf(stderr, "disk full\n");
4006         return FD_WRTERR;
4007     }
4008 
4009     /* put the TS in the dir entry */
4010 
4011     drives[unit]->buffers[1].slot[SLOT_FIRST_TRACK] = (unsigned char)vlirTrk;
4012     drives[unit]->buffers[1].slot[SLOT_FIRST_SECTOR] = (unsigned char)vlirSec;
4013 
4014     if (geosFileStruc == GEOS_FILE_STRUC_SEQ) {
4015 #ifdef DEBUG_DRIVE
4016         log_debug("DEBUG: GEOS_FILE_STRUC_SEQ (%u:%u)", vlirTrk, vlirSec);
4017 #endif
4018         /* normal seq file (rest like standard files) */
4019         lastTrk = vlirTrk;
4020         lastSec = vlirSec;
4021         aktTrk = vlirTrk;
4022         aktSec = vlirSec;
4023         bContinue = (vlirBlock[0] != 0);
4024         while (bContinue) {
4025             block[0] = 0;
4026             block[1] = 255;
4027 
4028             /* read next block */
4029 
4030             for (n = 2; n < 256; n++) {
4031                 c = fgetc(f);
4032                 if (c == EOF) {
4033                     block[0] = 0;
4034                     block[1] = (unsigned char)(n - 1);
4035                     while (n < 256) {
4036                         block[n++] = 0x00;
4037                     }
4038                     bContinue = 0;
4039                     break;
4040                 }
4041                 block[n] = (unsigned char)c;
4042             }
4043 
4044             /* allocate it */
4045 
4046             if (vdrive_bam_alloc_next_free_sector(drives[unit], &aktTrk, &aktSec) < 0) {
4047                 fprintf(stderr, "disk full\n");
4048                 return FD_WRTERR;
4049             }
4050 
4051             /* write it to disk */
4052 
4053             if (vdrive_write_sector(drives[unit], block, aktTrk, aktSec) != 0) {
4054                 fprintf(stderr, "disk full\n");
4055                 return FD_WRTERR;
4056             }
4057 
4058             /* put the TS of the current block to the predecessor block */
4059 
4060             if (!fix_ts(unit, lastTrk, lastSec, aktTrk, aktSec, 0)) {
4061                 fprintf(stderr, "internal error\n");    /* not clear at all */
4062                 return FD_WRTERR;
4063             }
4064             lastTrk = aktTrk;
4065             lastSec = aktSec;
4066         }
4067     } else if (geosFileStruc == GEOS_FILE_STRUC_VLIR) {
4068 #ifdef DEBUG_DRIVE
4069         log_debug("DEBUG: GEOS_FILE_STRUC_VLIR (%u:%u)", vlirTrk, vlirSec);
4070 #endif
4071         /* in a cvt file containing a vlir file the vlir block contains
4072          * a pair (NoOfBlocksForChain, BytesInLastBlock + 2) for every vlir
4073          * record that exists. Non-existing record have a (0, 0) pair,
4074          * empty records a (0, 255) pair. There can be empty or non
4075          * existing records between data records.
4076          */
4077 
4078         int vlirIdx = 2;
4079         while (vlirIdx <= 254) {
4080             if (vlirBlock[vlirIdx] != 0) {
4081 #ifdef DEBUG_DRIVE
4082                 log_debug("DEBUG: VLIR IDX %d (%u:%u)", vlirIdx, vlirTrk, vlirSec);
4083 #endif
4084                 lastTrk = vlirTrk;
4085                 lastSec = vlirSec;
4086                 aktTrk = vlirTrk;
4087                 aktSec = vlirSec;
4088                 numBlks = vlirBlock[vlirIdx];
4089                 /* How many blocks in record */
4090                 bytesInLastBlock = vlirBlock[vlirIdx + 1];
4091                 /* Bytes in last blocks */
4092                 while (numBlks > 0) {
4093                     /* read next block */
4094 
4095                     block[0] = 0;
4096                     block[1] = 0xFF;
4097                     for (n = 2; n < 256; n++) {
4098                         c = fgetc(f);
4099                         if (c == EOF) {
4100                             if (numBlks > 1) {
4101                                 fprintf(stderr, "unexpected EOF encountered\n");
4102                                 return FD_RDERR;
4103                             }
4104                             while (n < 256) {
4105                                 block[n++] = 0x00;
4106                             }
4107                             break;
4108                         } else {
4109                             block[n] = (unsigned char)c;
4110                         }
4111                     }
4112                     if (numBlks == 1) { /* last block */
4113                         block[0] = 0;
4114                         block[1] = (unsigned char)bytesInLastBlock;
4115                     }
4116 
4117                     /* allocate it */
4118 
4119                     if (vdrive_bam_alloc_next_free_sector(drives[unit], &aktTrk, &aktSec) < 0) {
4120                         fprintf(stderr, "disk full\n");
4121                         return FD_WRTERR;
4122                     }
4123 
4124                     /* write it to disk */
4125 #ifdef DEBUG_DRIVE
4126                     log_debug("DEBUG: VLIR BLOCK (%u:%u)", aktTrk, aktSec);
4127 #endif
4128 
4129                     if (vdrive_write_sector(drives[unit], block, aktTrk, aktSec) != 0) {
4130                         fprintf(stderr, "disk full\n");
4131                         return FD_WRTERR;
4132                     }
4133                     /*
4134                      * write the TS to the predecessor block or if this is
4135                      * the first block of a vlir record write it to the vlir
4136                      * index block at the correct offset.
4137                      */
4138                     if (!fix_ts(unit, lastTrk, lastSec, aktTrk, aktSec,
4139                                 (unsigned int)(lastTrk == vlirTrk
4140                                     && lastSec == vlirSec
4141                                     ? vlirIdx : 0))) {
4142                         fprintf(stderr, "internal error\n");
4143                         return FD_WRTERR;
4144                     }
4145                     lastTrk = aktTrk;
4146                     lastSec = aktSec;
4147                     numBlks--;
4148                 }
4149             }
4150             vlirIdx += 2;
4151         }
4152     }
4153     return FD_OK;
4154 }
4155 
4156 
4157 /* Author:      DiSc
4158  * Date:        2000-07-28
4159  * Write a geos .cvt file to the diskimage
4160  * This code was copied from the write_cmd function.
4161  */
write_geos_cmd(int nargs,char ** args)4162 static int write_geos_cmd(int nargs, char **args)
4163 {
4164     int dev;
4165     int erg;
4166     char *dest_name_ascii, *dest_name_petscii;
4167     FILE *f;
4168     uint8_t* e;
4169     char *slashp;
4170     vdrive_dir_context_t dir;
4171 
4172     /* geoswrite <source> */
4173     dest_name_ascii = NULL;
4174     dev = drive_index;
4175 
4176     if (check_drive_ready(dev) < 0) {
4177         return FD_NOTREADY;
4178     }
4179 
4180     f = fopen(args[1], MODE_READ);
4181     if (f == NULL) {
4182         fprintf(stderr,
4183                 "cannot read file `%s': %s\n", args[1], strerror(errno));
4184         return FD_NOTRD;
4185     }
4186 
4187     /* User did not specify a destination name...  Let's try to make an
4188        educated guess at what she expects.
4189 
4190        Convert file have a directory and info block which contains the
4191        resulting filename. Because i am using the vdrive functions to create
4192        a directory entry lets get a temporary name out of the source file for
4193        initial creating of the USR file.
4194      */
4195 
4196     slashp = strrchr(args[1], '/');
4197     if (slashp == NULL) {
4198         dest_name_ascii = lib_strdup(args[1]);
4199     } else {
4200         dest_name_ascii = lib_strdup(slashp + 1);
4201     }
4202     dest_name_petscii = lib_strdup(dest_name_ascii);
4203     charset_petconvstring((uint8_t *)dest_name_petscii, 0);
4204 
4205     if (vdrive_iec_open(drives[dev], (uint8_t *)dest_name_petscii,
4206                         (unsigned int)strlen(dest_name_petscii), 1, NULL)) {
4207         fprintf(stderr, "cannot open `%s' for writing on image\n",
4208                 dest_name_ascii);
4209         fclose(f);
4210         return FD_WRTERR;
4211     }
4212 
4213     /* the following function reuses the fresh created dir entry ... */
4214     erg = internal_write_geos_file(dev, f);
4215     fclose(f);
4216 
4217     /* Start: copied from vdrive_iec_close
4218      * The bam and directory entry must be copied to the disk. the code
4219      * from the vdrive routines does that thing.
4220      */
4221     vdrive_dir_find_first_slot(drives[dev], dest_name_petscii,
4222                                (int)strlen(dest_name_petscii), 0, &dir);
4223     e = vdrive_dir_find_next_slot(&dir);
4224 
4225     if (!e) {
4226         drives[dev]->buffers[1].mode = BUFFER_NOT_IN_USE;
4227         lib_free(drives[dev]->buffers[1].buffer);
4228         drives[dev]->buffers[1].buffer = NULL;
4229 
4230         vdrive_command_set_error(drives[dev], CBMDOS_IPE_DISK_FULL, 0, 0);
4231         return SERIAL_ERROR;
4232     }
4233     /* The buffer MUST be mark as closed to avoid the vdrive functions
4234      * to add an empty block at the end of the file that was allocated
4235      * when the file was created!!!!
4236      */
4237     drives[dev]->buffers[1].slot[SLOT_TYPE_OFFSET] |= 0x80; /* Closed */
4238 
4239     memcpy(&dir.buffer[dir.slot * SLOT_SIZE + 2],
4240            drives[dev]->buffers[1].slot + 2,
4241            30);
4242 
4243 #ifdef DEBUG_DRIVE
4244     log_debug("DEBUG: closing, write DIR slot (%u %u) and BAM.",
4245             dir.track, dir.sector);
4246 #endif
4247     vdrive_write_sector(drives[dev], dir.buffer, dir.track, dir.sector);
4248     vdrive_bam_write_bam(drives[dev]);
4249     drives[dev]->buffers[1].mode = BUFFER_NOT_IN_USE;
4250     lib_free((char *)drives[dev]->buffers[1].buffer);
4251     drives[dev]->buffers[1].buffer = NULL;
4252 
4253     /* End: copied from vdrive_iec_close */
4254 
4255     lib_free(dest_name_ascii);
4256     lib_free(dest_name_petscii);
4257 
4258     return erg;
4259 }
4260 
4261 
4262 /** \brief  Rename a file
4263  *
4264  * \param[in]   nargs   argument count
4265  * \param[in]   args    argument list
4266  *
4267  * \return  FD_OK on success, < 0 on failure
4268  */
rename_cmd(int nargs,char ** args)4269 static int rename_cmd(int nargs, char **args)
4270 {
4271     char *src_name;
4272     char *dest_name;
4273     int src_unit;
4274     int dest_unit;
4275     int dev;
4276     char *command;
4277     char *p;
4278     int unit;
4279 
4280     unit = extract_unit_from_file_name(args[1], &p);
4281     if (unit > 0) {
4282         src_unit = unit;
4283     } else if (unit == 0) {
4284         src_unit = drive_index + DRIVE_UNIT_MIN;
4285     } else {
4286         return FD_BADDEV;
4287     }
4288     src_name = lib_strdup(p);
4289 
4290 
4291     unit = extract_unit_from_file_name(args[2], &p);
4292     if (unit > 0) {
4293         dest_unit = unit;
4294     } else if (unit == 0) {
4295         dest_unit = drive_index + DRIVE_UNIT_MIN;
4296     } else {
4297         return FD_BADDEV;
4298     }
4299     dest_name = lib_strdup(p);
4300 
4301     dev = dest_unit - DRIVE_UNIT_MIN;
4302 
4303     if (dest_unit != src_unit) {
4304         fprintf(stderr, "source and destination must be on the same unit\n");
4305         lib_free(src_name);
4306         lib_free(dest_name);
4307         return FD_BADDEV;
4308     }
4309 
4310     if (check_drive_ready(dev) < 0) {
4311         lib_free(src_name);
4312         lib_free(dest_name);
4313         return FD_NOTREADY;
4314     }
4315 
4316     if (!is_valid_cbm_file_name(src_name)) {
4317         fprintf(stderr, "`%s' is not a valid CBM DOS file name\n", src_name);
4318         lib_free(src_name);
4319         lib_free(dest_name);
4320         return FD_BADNAME;
4321     }
4322 
4323     if (!is_valid_cbm_file_name(dest_name)) {
4324         fprintf(stderr, "`%s' is not a valid CBM DOS file name\n", dest_name);
4325         lib_free(src_name);
4326         lib_free(dest_name);
4327         return FD_BADNAME;
4328     }
4329 
4330     printf("renaming `%s' to `%s'\n", src_name, dest_name);
4331 
4332     command = util_concat("r:", dest_name, "=", src_name, NULL);
4333     charset_petconvstring((uint8_t *)command, 0);
4334 
4335     vdrive_command_execute(drives[dev],
4336                            (uint8_t *)command, (unsigned int)strlen(command));
4337 
4338     lib_free(command);
4339     lib_free(dest_name);
4340     lib_free(src_name);
4341 
4342     return FD_OK;
4343 }
4344 
4345 
4346 /** \brief  Show license or warranty information
4347  *
4348  * Syntax: show copying|warranty
4349  *
4350  * \param[in]   nargs   argument count
4351  * \param[in]   args    argument list
4352  *
4353  * \returns 0
4354  */
show_cmd(int nargs,char ** args)4355 static int show_cmd(int nargs, char **args)
4356 {
4357     if (strcasecmp(args[1], "copying") == 0) {
4358         printf("%s", info_license_text);
4359     } else if (strcasecmp(args[1], "warranty") == 0) {
4360         printf("%s", info_warranty_text);
4361     } else {
4362         fprintf(stderr, "Use either `show copying' or `show warranty'\n");
4363         return FD_OK;           /* FIXME? */
4364     }
4365 
4366     return FD_OK;
4367 }
4368 
4369 /** \brief  Copy files from a tape image to current drive unit
4370  *
4371  * \param[in]   nargs   argument count
4372  * \param[in]   args    argument list
4373  *
4374  * \return  FD_OK on success, < 0 on failure
4375  */
tape_cmd(int nargs,char ** args)4376 static int tape_cmd(int nargs, char **args)
4377 {
4378     tape_image_t *tape_image;
4379     vdrive_t *drive;
4380     int count;
4381 
4382     if (check_drive_ready(drive_index) < 0) {
4383         return FD_NOTREADY;
4384     }
4385 
4386     drive = drives[drive_index];
4387 
4388     tape_image = tape_internal_open_tape_image(args[1], 1);
4389 
4390     if (tape_image == NULL) {
4391         fprintf(stderr, "cannot read tape file `%s'\n", args[1]);
4392         return FD_BADNAME;
4393     }
4394 
4395     for (count = 0; tape_seek_to_next_file(tape_image, 0) >= 0; ) {
4396         tape_file_record_t *rec;
4397 
4398         rec = tape_get_current_file_record(tape_image);
4399 
4400         if (rec->type) {
4401             char *dest_name_ascii;
4402             char *dest_name_petscii;
4403             uint8_t *buf;
4404             size_t name_len;
4405             uint16_t file_size;
4406             int i;
4407             int retval;
4408 
4409             /* Ignore traling spaces and 0xa0's.  */
4410             name_len = strlen((char *)(rec->name));
4411             while (name_len > 0 && (rec->name[name_len - 1] == 0xa0
4412                                     || rec->name[name_len - 1] == 0x20)) {
4413                 name_len--;
4414             }
4415 
4416             dest_name_petscii = lib_calloc(1, name_len + 1);
4417             memcpy(dest_name_petscii, rec->name, name_len);
4418 
4419             dest_name_ascii = lib_calloc(1, name_len + 1);
4420             memcpy(dest_name_ascii, dest_name_petscii, name_len);
4421             charset_petconvstring((uint8_t *)dest_name_ascii, 1);
4422 
4423             if (nargs > 2) {
4424                 int k;
4425                 int found;
4426 
4427                 for (k = 2, found = 0; k < nargs; k++) {
4428                     if (name_len == strlen(args[k])
4429                         && memcmp(args[k], dest_name_ascii, name_len) == 0) {
4430                         found = 1;
4431                         break;
4432                     }
4433                 }
4434 
4435                 if (!found) {
4436                     continue;
4437                 }
4438             }
4439 
4440             if (rec->type == 1 || rec->type == 3) {
4441                 if (vdrive_iec_open(drive, (uint8_t *)dest_name_petscii,
4442                                     (unsigned int)name_len, 1, NULL)) {
4443                     fprintf(stderr,
4444                             "cannot open `%s' for writing on drive %d\n",
4445                             dest_name_ascii, drive_index + 8);
4446                     lib_free(dest_name_petscii);
4447                     lib_free(dest_name_ascii);
4448                     continue;
4449                 }
4450 
4451                 fprintf(stderr, "writing `%s' ($%04X - $%04X) to drive %d\n",
4452                         dest_name_ascii, rec->start_addr, rec->end_addr,
4453                         drive_index + 8);
4454 
4455                 vdrive_iec_write(drive, ((uint8_t)(rec->start_addr & 0xff)), 1);
4456                 vdrive_iec_write(drive, ((uint8_t)(rec->start_addr >> 8)), 1);
4457 
4458                 file_size = (uint16_t)(rec->end_addr - rec->start_addr);
4459 
4460                 buf = lib_calloc((size_t)file_size, 1);
4461 
4462                 retval = tape_read(tape_image, buf, file_size);
4463 
4464                 if (retval < 0 || retval != (int) file_size) {
4465                     fprintf(stderr,
4466                             "unexpected end of tape: file may be truncated\n");
4467                 }
4468 
4469                 for (i = 0; i < file_size; i++) {
4470                     if (vdrive_iec_write(drives[drive_index],
4471                                          ((uint8_t)(buf[i])), 1)) {
4472                         tape_internal_close_tape_image(tape_image);
4473                         lib_free(dest_name_petscii);
4474                         lib_free(dest_name_ascii);
4475                         lib_free(buf);
4476                         return FD_WRTERR;
4477                     }
4478                 }
4479 
4480                 lib_free(buf);
4481             } else if (rec->type == 4) {
4482                 uint8_t b;
4483                 char *dest_name_plustype;
4484                 dest_name_plustype = util_concat(dest_name_petscii, ",S,W", NULL);
4485                 retval = vdrive_iec_open(drive, (uint8_t *)dest_name_plustype,
4486                                          (unsigned int)name_len + 4, 2, NULL);
4487                 lib_free(dest_name_plustype);
4488 
4489                 if (retval) {
4490                     fprintf(stderr,
4491                             "cannot open `%s' for writing on drive %d\n",
4492                             dest_name_ascii, drive_index + 8);
4493                     lib_free(dest_name_petscii);
4494                     lib_free(dest_name_ascii);
4495                     continue;
4496                 }
4497 
4498                 fprintf(stderr, "writing SEQ file `%s' to drive %d\n",
4499                         dest_name_ascii, drive_index + 8);
4500 
4501                 do {
4502                     retval = tape_read(tape_image, &b, 1);
4503 
4504                     if (retval < 0) {
4505                         fprintf(stderr,
4506                                 "unexpected end of tape: file may be truncated\n");
4507                         break;
4508                     } else if (vdrive_iec_write(drives[drive_index], b, 2)) {
4509                         tape_internal_close_tape_image(tape_image);
4510                         lib_free(dest_name_petscii);
4511                         lib_free(dest_name_ascii);
4512                         return FD_WRTERR;
4513                     }
4514                 } while (retval == 1);
4515             }
4516 
4517             vdrive_iec_close(drive, 1);
4518             lib_free(dest_name_petscii);
4519             lib_free(dest_name_ascii);
4520             count++;
4521         }
4522     }
4523 
4524     tape_internal_close_tape_image(tape_image);
4525 
4526     printf("\n%d files copied\n", count);
4527 
4528     return FD_OK;
4529 }
4530 
4531 
4532 /** \brief  Select current device
4533  *
4534  * \param[in]   nargs   number of arguments
4535  * \param[in]   args    argument list
4536  *
4537  * \return  0 on success, < 0 on failure
4538  */
unit_cmd(int nargs,char ** args)4539 static int unit_cmd(int nargs, char **args)
4540 {
4541     int dev;
4542 
4543     if (arg_to_int(args[1], &dev) < 0 || check_drive_unit(dev) < 0) {
4544         return FD_BADDEV;
4545     }
4546 
4547     drive_index = dev - DRIVE_UNIT_MIN;
4548     return FD_OK;
4549 }
4550 
unlynx_loop(FILE * f,FILE * f2,vdrive_t * vdrive,long dentries)4551 static int unlynx_loop(FILE *f, FILE *f2, vdrive_t *vdrive, long dentries)
4552 {
4553     long lbsize, bsize;
4554     char cname[20]; /* FIXME: remove magic number */
4555     int ftype;
4556     uint8_t val;
4557     long cnt;
4558     size_t len;
4559     char buff[256];
4560     cbmdos_cmd_parse_t cmd_parse;
4561 
4562     while (dentries != 0) {
4563         int filetype, rc;
4564 
4565         /* Read CBM filename */
4566         cnt = 0;
4567         while (1) {
4568             /* FIXME: using fread() to read a single byte? */
4569             if (fread(&val, 1, 1, f) != 1) {
4570                 return FD_RDERR;
4571             }
4572             if (val != 13 && cnt < 20 - 1) {
4573                 cname[cnt++] = (char)val;
4574             } else {
4575                 break;
4576             }
4577         }
4578         cname[cnt] = 0;
4579 
4580         /* Read the block size */
4581         cnt = 0;
4582         while (1) {
4583             if (fread(&val, 1, 1, f) != 1) {
4584                 return FD_RDERR;
4585             }
4586             if (val != 13) {
4587                 buff[cnt++] = (char)val;
4588             } else {
4589                 break;
4590             }
4591         }
4592         buff[cnt] = 0;
4593 
4594         if (util_string_to_long(buff, NULL, 10, &bsize) < 0) {
4595             fprintf(stderr, "invalid Lynx file\n");
4596             return FD_RDERR;
4597         }
4598         /* Get the file type (P[RG], S[EQ], R[EL], U[SR]) */
4599         ftype = fgetc(f);
4600         fgetc(f);
4601 
4602         switch (ftype) {
4603             case 'D':
4604                 filetype = CBMDOS_FT_DEL;
4605                 break;
4606             case 'P':
4607                 filetype = CBMDOS_FT_PRG;
4608                 break;
4609             case 'S':
4610                 filetype = CBMDOS_FT_SEQ;
4611                 break;
4612             case 'U':
4613                 filetype = CBMDOS_FT_USR;
4614                 break;
4615             case 'R':
4616                 fprintf(stderr, "REL not supported\n");
4617                 return FD_RDERR;
4618             default:
4619                 filetype = CBMDOS_FT_PRG;
4620         }
4621         /* Get the byte size of the last block +1 */
4622         cnt = 0;
4623         while (1) {
4624             if (fread(&val, 1, 1, f) != 1) {
4625                 return FD_RDERR;
4626             }
4627             if (val != 13) {
4628                 buff[cnt++] = (char)val;
4629             } else {
4630                 break;
4631             }
4632         }
4633         buff[cnt] = 0;
4634 
4635         if (util_string_to_long(buff, NULL, 10, &lbsize) < 0) {
4636             fprintf(stderr, "invalid Lynx file\n");
4637             return FD_RDERR;
4638         }
4639         /* Calculate byte size of file */
4640         cnt = (bsize - 1) * 254 + lbsize - 1;
4641 
4642         printf("writing file '%s' to image\n", cname);
4643 
4644         cmd_parse.parsecmd = lib_strdup(cname);
4645         cmd_parse.secondary = 1;
4646         cmd_parse.parselength = (unsigned int)strlen(cname);
4647         cmd_parse.readmode = CBMDOS_FAM_WRITE;
4648         cmd_parse.filetype = (unsigned int)filetype;
4649 
4650         rc = vdrive_iec_open(vdrive, NULL, 0, 1, &cmd_parse);
4651 
4652         if (rc != SERIAL_OK) {
4653             fprintf(stderr, "error writing file %s\n", cname);
4654             break;
4655         }
4656 
4657         while (cnt != 0) {
4658             if (fread(&val, 1, 1, f2) != 1) {
4659                 return FD_RDERR;
4660             }
4661             if (vdrive_iec_write(vdrive, val, 1)) {
4662                 fprintf(stderr, "no space on image ?\n");
4663                 break;
4664             }
4665             cnt--;
4666         }
4667         vdrive_iec_close(vdrive, 1);
4668 
4669         /* Adjust for the last block, but not for the last file */
4670         if (lbsize < 255 && dentries > 1) {
4671             len = (size_t)(254 + 1 - lbsize);
4672             if (fread(buff, 1, len, f2) != len) {
4673                 return FD_RDERR;
4674             }
4675         }
4676         dentries--;
4677     }
4678 
4679     return FD_OK;
4680 }
4681 
4682 
4683 /** \brief  Unlynx a Lynx container onto a virtual device
4684  *
4685  * Syntax: unlynx \<filename> [\<unit>]
4686  *
4687  * \param[in]   nargs   argument count
4688  * \param[in]   args    argument list
4689  *
4690  * \return  0 on success, < 0 on failure
4691  */
unlynx_cmd(int nargs,char ** args)4692 static int unlynx_cmd(int nargs, char **args)
4693 {
4694     vdrive_t *vdrive;
4695     FILE *f, *f2;
4696     int dev, cnt;
4697     long dentries, dirsize;
4698     uint8_t val;
4699     char buff[256];
4700     int rc;
4701     char *path;
4702 
4703     if (nargs < 3) {
4704         dev = drive_index;
4705     } else {
4706         if (arg_to_int(args[2], &dev) < 0) {
4707             return FD_BADDEV;
4708         }
4709         if (check_drive_unit(dev) < 0) {
4710             return FD_BADDEV;
4711         }
4712         dev -= DRIVE_UNIT_MIN;
4713     }
4714 
4715     if (check_drive_ready(dev) < 0) {
4716         return FD_NOTREADY;
4717     }
4718 
4719     vdrive = drives[dev];
4720     archdep_expand_path(&path, args[1]);
4721 
4722     f = fopen(path, MODE_READ);
4723 
4724     if (f == NULL) {
4725         fprintf(stderr, "cannot open `%s' for reading\n", path);
4726         lib_free(path);
4727         return FD_NOTRD;
4728     }
4729 
4730     /* Look for the 0, 0, 0 sign of the end of BASIC.  */
4731     cnt = 0;
4732     while (1) {
4733         if (fread(&val, 1, 1, f) != 1) {
4734             return FD_RDERR;
4735         }
4736         if (val == 0) {
4737             cnt++;
4738         } else {
4739             cnt = 0;
4740         }
4741         if (cnt == 3) {
4742             break;
4743         }
4744     }
4745 
4746     /* Bypass the 1st return in the file */
4747     fgetc(f);
4748 
4749     /* Get the directory block size */
4750     cnt = 0;
4751     while (1) {
4752         if (fread(&val, 1, 1, f) != 1) {
4753             lib_free(path);
4754             return FD_RDERR;
4755         }
4756         if (val != 13) {
4757             buff[cnt++] = (char)val;
4758         } else {
4759             break;
4760         }
4761     }
4762 
4763     buff[cnt] = 0;
4764 
4765     if (util_string_to_long(buff, NULL, 10, &dirsize) < 0 || dirsize <= 0) {
4766         fprintf(stderr, "invalid Lynx file\n");
4767         fclose(f);
4768         lib_free(path);
4769         return FD_RDERR;
4770     }
4771 
4772     /* Get the number of dir entries */
4773     cnt = 0;
4774     while (1) {
4775         if (fread(&val, 1, 1, f) != 1) {
4776             lib_free(path);
4777             return FD_RDERR;
4778         }
4779         if (val != 13 && cnt < 256 - 1) {
4780             buff[cnt++] = (char)val;
4781         } else {
4782             break;
4783         }
4784     }
4785 
4786     buff[cnt] = 0;
4787 
4788     if (util_string_to_long(buff, NULL, 10, &dentries) < 0 || dentries <= 0) {
4789         fprintf(stderr, "invalid Lynx file\n");
4790         fclose(f);
4791         lib_free(path);
4792         return FD_RDERR;
4793     }
4794 
4795     /* Open the file for reading of the chained data */
4796     f2 = fopen(path, MODE_READ);
4797 
4798     if (f2 == NULL) {
4799         fprintf(stderr, "cannot open `%s' for reading\n", path);
4800         fclose(f);
4801         lib_free(path);
4802         return FD_NOTRD;
4803     }
4804 
4805     fseek(f2, (dirsize * 254), SEEK_SET);
4806 
4807     rc = unlynx_loop(f, f2, vdrive, dentries);
4808 
4809     fclose(f);
4810     fclose(f2);
4811     lib_free(path);
4812     return rc;
4813 }
4814 
4815 
4816 /** \brief  Validate contents of a drive
4817  *
4818  * \param[in]   nargs   argument count
4819  * \param[in]   args    argument list
4820  *
4821  * \return  0 on success, < 0 on failure
4822  */
validate_cmd(int nargs,char ** args)4823 static int validate_cmd(int nargs, char **args)
4824 {
4825     int dnr = drive_index;
4826 
4827     /* get unit number from args */
4828     if (nargs >= 2) {
4829         int unit;
4830 
4831         if (arg_to_int(args[1], &unit) < 0) {
4832             return FD_BADDEV;
4833         }
4834         /* check for valid unit number */
4835         if (check_drive_unit(unit) < 0) {
4836             return FD_BADDEV;
4837         }
4838         dnr = unit - DRIVE_UNIT_MIN;
4839     }
4840 
4841     /* check if drive is ready */
4842     if (check_drive_ready(dnr) < 0) {
4843         return FD_NOTREADY;
4844     }
4845 
4846     printf("validating in unit %d ...\n", dnr + 8);
4847     vdrive_command_validate(drives[dnr]);
4848 
4849     return FD_OK;
4850 }
4851 
4852 
4853 /** \brief  Display version number
4854  *
4855  * \param[in]   nargs   argument count
4856  * \param[in]   args    argument list
4857  *
4858  * \return  FD_OK
4859  */
version_cmd(int nargs,char ** args)4860 static int version_cmd(int nargs, char **args)
4861 {
4862 #ifdef USE_SVN_REVISION
4863     printf("c1541 (VICE %s SVN r%d)\n",
4864             VERSION, VICE_SVN_REV_NUMBER);
4865 #else
4866     printf("c1541 (VICE %s)\n",
4867             VERSION);
4868 #endif
4869     return FD_OK;
4870 }
4871 
4872 
4873 /** \brief  Write a file from the host FS to a virtual device
4874  *
4875  * \param[in]   nargs   argument count
4876  * \param[in]   args    argument list
4877  *
4878  * \return  FD_OK on success, < 0 on failure
4879  */
write_cmd(int nargs,char ** args)4880 static int write_cmd(int nargs, char **args)
4881 {
4882     int dnr;
4883     int unit;
4884     char *dest_name;
4885     unsigned int dest_len;
4886     char *p;
4887     fileio_info_t *finfo;
4888     char *src_name;
4889     long rel_record_length = 0;
4890 
4891     if (nargs == 3) {
4892         /* write <source> <dest> */
4893 
4894         unit = extract_unit_from_file_name(args[2], &p);
4895         if (unit == 0) {
4896             unit = drive_index + DRIVE_UNIT_MIN;
4897         } else if (unit < 0) {
4898             printf("Got unit < 0\n");
4899             return FD_BADDEV;
4900         }
4901         if (p != NULL && *p != '\0') {
4902             dest_name = lib_strdup(p);
4903         } else {
4904             dest_name = NULL;
4905         }
4906 
4907         if (dest_name != NULL) {
4908             char *lenptr;
4909 
4910             charset_petconvstring((uint8_t *)dest_name, 0);
4911 
4912             /* Convert "NAME,L,100" to "NAME,L,"+CHR$(100) */
4913             if ((lenptr = strstr(dest_name, ",L,"))) {
4914                 char *endptr;
4915                 lenptr += 3;      /* after the second comma */
4916 
4917                 rel_record_length = strtol(lenptr, &endptr, 10);
4918                 if (endptr > lenptr && endptr[0] == '\0' &&
4919                     rel_record_length >= 1 && rel_record_length <= 254) {
4920                     lenptr[0] = (char)(uint8_t)rel_record_length;
4921                     if ((endptr - lenptr) > 1) {
4922                         lenptr[1] = '\0';
4923                     }
4924                     fprintf(stderr, "Converted record length %lu\n",
4925                             (unsigned long)rel_record_length);
4926                 } else {
4927                     rel_record_length = 0;
4928                 }
4929             }
4930         }
4931     } else {
4932         /* write <source> */
4933         dest_name = NULL;
4934         unit = drive_index + DRIVE_UNIT_MIN;
4935     }
4936 
4937     dnr = unit - DRIVE_UNIT_MIN;
4938 
4939     if (check_drive_index(dnr) < 0) {
4940         printf("check_drive_index() failed\n");
4941         return FD_BADDEV;
4942     }
4943     if (check_drive_ready(dnr) < 0) {
4944         return FD_NOTREADY;
4945     }
4946 
4947     /* ~ expand path on Unix */
4948     archdep_expand_path(&src_name, args[1]);
4949 
4950     finfo = fileio_open(src_name, NULL, FILEIO_FORMAT_RAW | FILEIO_FORMAT_P00,
4951                         FILEIO_COMMAND_READ | FILEIO_COMMAND_FSNAME,
4952                         FILEIO_TYPE_PRG, NULL);
4953 
4954     if (finfo == NULL) {
4955         fprintf(stderr, "cannot read file `%s': %s\n", args[1],
4956                 strerror(errno));
4957         lib_free(src_name);
4958         return FD_NOTRD;
4959     }
4960 
4961     if (dest_name == NULL) {
4962         dest_name = lib_strdup((char *)(finfo->name));
4963         dest_len = finfo->length;
4964     } else {
4965         dest_len = (unsigned int)strlen(dest_name);
4966     }
4967 
4968     if (vdrive_iec_open(drives[dnr], (uint8_t *)dest_name, (unsigned int)dest_len,
4969                 1, NULL)) {
4970         fprintf(stderr, "cannot open `%s' for writing on image\n",
4971                 dest_name);
4972         fileio_close(finfo);
4973         lib_free(dest_name);
4974         lib_free(src_name);
4975         return FD_WRTERR;
4976     }
4977 
4978     if (dest_name == (char *)finfo->name) {
4979         printf("writing file `%s' to unit %d\n", finfo->name, dnr + 8);
4980     } else {
4981         printf("writing file `%s' as `%s' to unit %d\n", finfo->name,
4982                dest_name, dnr + 8);
4983     }
4984 
4985     if (rel_record_length == 0) {
4986         while (1) {
4987             uint8_t c;
4988 
4989             if (fileio_read(finfo, &c, 1) != 1) {
4990                 break;
4991             }
4992 
4993             if (vdrive_iec_write(drives[dnr], c, 1)) {
4994                 fprintf(stderr, "no space on image?\n");
4995                 break;
4996             }
4997         }
4998     } else {
4999         unsigned int length = fileio_get_bytes_left(finfo);
5000         /* Records and positions start counting at 1 */
5001         unsigned int max_record =
5002             (unsigned int)((length + rel_record_length - 1) / rel_record_length);
5003         unsigned int record;
5004         int err;
5005 
5006         /* First allocate space for the whole file */
5007         err = vdrive_rel_position(drives[dnr], 1,
5008                 (max_record & 0xFF), (max_record >> 8) & 0xFF,
5009                 1);
5010         if (err && err != CBMDOS_IPE_NO_RECORD) {
5011             fprintf(stderr, "Cannot Position to record %u (err %d)\n",
5012                     max_record, err);
5013         }
5014         err = vdrive_iec_write(drives[dnr], 0, 1);
5015         if (err != SERIAL_OK) {
5016             fprintf(stderr, "Cannot write in record %u (err %d)\n",
5017                     max_record, err);
5018         }
5019         /*
5020          * Bug/annoyance: given the way that sectors are added at the
5021          * end of a REL file, filled with as many records as will fit,
5022          * there may be too many records in the file now. This is not
5023          * avoided by growing the file piece by piece.
5024          */
5025 
5026         for (record = 1; record <= max_record; record++) {
5027             int pos;
5028 
5029             err = vdrive_rel_position(drives[dnr], 1,   /* 1 = secadr */
5030                     (record & 0xFF), (record >> 8) & 0xFF,
5031                     1);
5032             if (err && err != CBMDOS_IPE_NO_RECORD) {
5033                 fprintf(stderr, "Cannot Position to record %u (err %d)\n",
5034                         record, err);
5035             }
5036             for (pos = 0; pos < rel_record_length; pos++) {
5037                 uint8_t c;
5038 
5039                 if (fileio_read(finfo, &c, 1) != 1) {
5040                     break;
5041                 }
5042 
5043                 if ((err = vdrive_iec_write(drives[dnr], c, 1)) != SERIAL_OK) {
5044                     fprintf(stderr, "no space on image? (err %d)\n", err);
5045                     break;
5046                 }
5047             }
5048         }
5049     }
5050 
5051     fileio_close(finfo);
5052     vdrive_iec_close(drives[dnr], 1);
5053 
5054     lib_free(dest_name);
5055     lib_free(src_name);
5056     return FD_OK;
5057 }
5058 
unzip_cmd(int nargs,char ** args)5059 static int unzip_cmd(int nargs, char **args)
5060 {
5061     vdrive_t *vdrive = drives[drive_index];
5062     FILE *fsfd = NULL;
5063     unsigned int track, sector;
5064     unsigned int count;
5065     char *p, *fname, *dirname, *oname;
5066     int singlefilemode = 0, err;
5067     uint8_t sector_data[256];
5068 
5069     /* Open image or create a new one.  If the file exists, it must have
5070        valid header.  */
5071     if (open_image(drive_index, args[1], 1, DISK_IMAGE_TYPE_D64) < 0) {
5072         return FD_BADIMAGE;
5073     }
5074 
5075     fname = lib_malloc((size_t)ioutil_maxpathlen());
5076     dirname = lib_malloc((size_t)ioutil_maxpathlen());
5077 
5078     p = strrchr(args[2], FSDEV_DIR_SEP_CHR);
5079     if (p == NULL) {
5080         /* ignore '[0-4]!' if found */
5081         if (args[2][0] >= '1' && args[2][0] <= '4' && args[2][1] == '!') {
5082             strcpy(fname + 2, args[2] + 2);
5083         } else {
5084             strcpy(fname + 2, args[2]);
5085         }
5086         fname[0] = '0';
5087         fname[1] = '!';
5088         strcpy(dirname, "");
5089     } else {
5090         size_t len_path;
5091 
5092         len_path = (size_t)(p - args[2]);
5093         if (len_path == strlen(args[2]) - 1) {
5094             /* FIXME: Close image?  */
5095             lib_free(fname);
5096             lib_free(dirname);
5097             return FD_RDERR;
5098         }
5099         strncpy(dirname, args[2], len_path + 1);
5100         dirname[len_path + 1] = '\0';
5101 
5102         /* ignore '[0-4]!' if found */
5103         if (args[2][len_path + 1] >= '1' && args[2][len_path + 1] <= '4'
5104             && args[2][len_path + 1 + 1] == '!') {
5105             strcpy(fname + 2, &(args[2][len_path + 1]) + 2);
5106         } else {
5107             strcpy(fname + 2, &(args[2][len_path + 1]));
5108         }
5109         fname[0] = '0';
5110         fname[1] = '!';
5111     }
5112 
5113     oname = lib_malloc((size_t)ioutil_maxpathlen());
5114 
5115     printf("copying blocks to image\n");
5116 
5117     for (track = 1; track <= 35; track++) {
5118         if (singlefilemode || track == 1) {
5119             if (track == 1) {
5120                 /* For now we disable one-file more, because it is not detected
5121                    correctly.  */
5122                 strcpy(oname, dirname);
5123                 strcat(oname, fname + 2);
5124                 fsfd = fopen(oname, MODE_READ);
5125                 if (fsfd != NULL) {
5126                     printf("reading zipfile on one-file mode\n");
5127                     singlefilemode = 1;
5128                     fseek(fsfd, 4, SEEK_SET);
5129                 }
5130             } else if (track == 9 || track == 17 || track == 26) {
5131                 fseek(fsfd, 2, SEEK_CUR);
5132             }
5133         }
5134         if (!singlefilemode) {
5135             switch (track) {
5136                 case 1:     /* fallthrough */
5137                 case 9:     /* fallthrough */
5138                 case 17:    /* fallthrough */
5139                 case 26:    /* fallthrough */
5140                     fname[0]++;
5141                     if (fsfd != NULL) {
5142                         fclose(fsfd);
5143                     }
5144                     strcpy(oname, dirname);
5145                     strcat(oname, fname);
5146                     if ((fsfd = fopen(oname, MODE_READ)) == NULL) {
5147                         fprintf(stderr, "cannot open `%s'\n", fname);
5148                         lib_free(fname);
5149                         lib_free(dirname);
5150                         lib_free(oname);
5151                         return FD_NOTRD;
5152                     }
5153                     fseek(fsfd, (track == 1) ? 4 : 2, SEEK_SET);
5154                     break;
5155                 default:
5156                     break;
5157             }
5158         }
5159         for (count = 0;
5160              count < disk_image_sector_per_track(DISK_IMAGE_TYPE_D64, track);
5161              count++) {
5162             err = zipcode_read_sector(fsfd, (int)track, (int *)&sector,
5163                                       (char *)sector_data);
5164             if (err) {
5165                 fclose(fsfd);
5166                 lib_free(fname);
5167                 lib_free(dirname);
5168                 lib_free(oname);
5169                 return FD_BADIMAGE;
5170             }
5171             /* Write one block */
5172             if (vdrive_write_sector(vdrive, sector_data, track, sector) < 0) {
5173                 fclose(fsfd);
5174                 lib_free(fname);
5175                 lib_free(dirname);
5176                 lib_free(oname);
5177                 return FD_RDERR;
5178             }
5179         }
5180     }
5181 
5182     fclose(fsfd);
5183 
5184     vdrive_command_execute(vdrive, (uint8_t *)"I", 1);
5185 
5186     lib_free(fname);
5187     lib_free(dirname);
5188     lib_free(oname);
5189 
5190     return FD_OK;
5191 }
5192 
raw_cmd(int nargs,char ** args)5193 static int raw_cmd(int nargs, char **args)
5194 {
5195     vdrive_t *vdrive = drives[drive_index];
5196 
5197     if (vdrive == NULL || vdrive->buffers[15].buffer == NULL) {
5198         return FD_NOTREADY;
5199     }
5200 
5201     /* Write to the command channel.  */
5202     if (nargs >= 2) {
5203         char *command = lib_strdup(args[1]);
5204 
5205         charset_petconvstring((uint8_t *)command, 0);
5206         vdrive_command_execute(vdrive, (uint8_t *)command, (unsigned int)strlen(command));
5207         lib_free(command);
5208     }
5209 
5210     /* Print the error now.  */
5211     puts((char *)vdrive->buffers[15].buffer);
5212     return FD_OK;
5213 }
5214 
5215 /* ------------------------------------------------------------------------- */
5216 
5217 /** \brief  Program driver
5218  *
5219  * \param[in]   argc    argument count
5220  * \param[in]   argv    argument vector
5221  *
5222  * \return  EXIT_SUCCESS or EXIT_FAILURE
5223  */
main(int argc,char ** argv)5224 int main(int argc, char **argv)
5225 {
5226     char *args[MAXARG];
5227     int nargs;
5228     int i;
5229     int retval = EXIT_SUCCESS;
5230 
5231     /* Needed to initialize the threading stuff in lib.c when creating a debug
5232      * build. We don't need any threading for c1541, but c1541 gets built as
5233      * an emulator, so now we do.
5234      */
5235     lib_init();
5236 
5237     archdep_init(&argc, argv);
5238 
5239     /* This causes all the logging messages from the various VICE modules to
5240        appear on stdout.  */
5241     log_init_with_fd(stdout);
5242 
5243     serial_iec_bus_init();
5244 
5245     for (i = 0; i < MAXARG; i++) {
5246         args[i] = NULL;
5247     }
5248     nargs = 0;
5249 
5250     for (i = 0; i < NUM_DISK_UNITS; i++) {
5251         drives[i] = lib_calloc(1, sizeof *drives[i]);
5252     }
5253 
5254     /* The first arguments without leading `-' are interpreted as disk images
5255        to attach.  */
5256     for (i = 1; i < argc && *argv[i] != '-'; i++) {
5257         if ((i - 1) == NUM_DISK_UNITS) {
5258             fprintf(stderr, "Ignoring disk image `%s'\n", argv[i]);
5259         } else {
5260             open_disk_image(drives[i - 1], argv[i], (unsigned int)(i - 1 + 8));
5261         }
5262     }
5263 
5264     if (i == argc) {
5265         char *line;
5266         char *buf = NULL;
5267 
5268         /* Interactive mode.  */
5269         interactive_mode = 1;
5270 #if 0
5271         /* properly init GNU readline, if available */
5272 #ifdef HAVE_READLINE_READLINE_H
5273         using_history();
5274 #endif
5275 #endif
5276         /* init linenoise-ng */
5277         linenoiseHistorySetMaxLen(100);
5278 
5279         /* TODO: Add completions on Windows, somehow, or perhaps not */
5280 
5281         version_cmd(0, NULL);
5282         printf("Copyright 1995-2020 The VICE Development Team.\n"
5283                "C1541 is free software, covered by the GNU General Public License,"
5284                " and you are\n"
5285                "welcome to change it and/or distribute copies of it under certain"
5286                " conditions.\n"
5287                "Type `show copying' to see the conditions.\n"
5288                "There is absolutely no warranty for C1541.  Type `show warranty'"
5289                " for details.\n");
5290 #if 0
5291         fflush(stdout); /* needs flushing on windows, it seems */
5292 #endif
5293 
5294         while (1) {
5295             fflush(stderr);
5296             lib_free(buf);
5297             buf = lib_msprintf("c1541 #%d> ", drive_index | 8);
5298 
5299             line = linenoise(buf);
5300 
5301             if (line == NULL) {
5302                 putchar('\n');
5303                 fflush(stdout);
5304                 fflush(stderr);
5305                 break;
5306             }
5307 
5308             if (*line == '!') {
5309                 retval = system(line + 1);
5310                 printf("exit code: %d\n", retval);
5311             } else {
5312                 split_args(line, &nargs, args);
5313                 if (nargs > 0) {
5314                     /* This is obviously seriously fucked, but currently the
5315                      * only way to free `buf` in interactive mode without
5316                      * rewriting large portions of code.
5317                      *
5318                      * It would be better if look_and_execute_command() would
5319                      * return some value to indicate x/exit/q/quit was the
5320                      * command, to properly clean up.
5321                      *
5322                      * Also: linenoise-ng leaks like hell
5323                      */
5324                     if ((strcmp(args[0], "x") == 0)
5325                         || strcmp(args[0], "q") == 0
5326                         || strcmp(args[0], "quit") == 0
5327                         || strcmp(args[0], "exit") == 0) {
5328                         lib_free(buf);
5329                     }
5330                     lookup_and_execute_command(nargs, args);
5331                }
5332             }
5333             linenoiseHistoryAdd(line);
5334         }
5335         lib_free(buf);
5336         /* free memory used by the argument 'parser' */
5337         for (i = 0; i < MAXARG; i++) {
5338             if (args[i] != NULL) {
5339                 lib_free(args[i]);
5340             }
5341         }
5342         /* properly clean up GNU readline's history, if used */
5343 #if 0
5344 #ifdef HAVE_READLINE_READLINE_H
5345         clear_history();
5346 #endif
5347 #endif
5348         linenoiseHistoryFree();
5349     } else {
5350         while (i < argc) {
5351             args[0] = argv[i] + 1;
5352             nargs = 1;
5353             i++;
5354             for (; i < argc && *argv[i] != '-'; i++) {
5355                 args[nargs++] = argv[i];
5356             }
5357             if (lookup_and_execute_command(nargs, args) < 0) {
5358                 retval = EXIT_FAILURE;
5359                 break;
5360             }
5361         }
5362     }
5363 
5364     /* free memory used by the virtual drives */
5365     for (i = 0; i < NUM_DISK_UNITS; i++) {
5366         if (drives[i]) {
5367             close_disk_image(drives[i], i + 8);
5368             lib_free(drives[i]);
5369         }
5370     }
5371     /* free memory used by archdep */
5372     archdep_shutdown();
5373     /* free memory used by the log module */
5374     log_close_all();
5375 
5376     return retval;
5377 }
5378 
5379 
5380 /** \brief  Enable\\disable saving of files as P00
5381  *
5382  * Syntax: p00save [\<enable> [\<unit>]]
5383  *
5384  * Where \a enable is either 0 or 1.
5385  *
5386  * \param[in]   nargs   argument count
5387  * \param[in]   args    argument list
5388  *
5389  * \return  0 on success, < 0 on failure
5390  */
p00save_cmd(int nargs,char ** args)5391 static int p00save_cmd(int nargs, char **args)
5392 {
5393     int dnr = 0, enable = 0;
5394 
5395     if (nargs == 1) {
5396         /* display `p00save` state for all drives */
5397         int i;
5398         for (i = 0; i < NUM_DISK_UNITS; i++) {
5399             printf("#%2d: %s\n",
5400                     i + DRIVE_UNIT_MIN, p00save[i] ? "enabled" : "disabled");
5401         }
5402         return FD_OK;
5403     }
5404 
5405     arg_to_int(args[1], &enable);
5406 
5407     if (nargs == 3) {
5408         if (arg_to_int(args[2], &dnr) < 0) {
5409             return FD_BADDEV;
5410         }
5411         if (check_drive_unit(dnr) < 0) {
5412             return FD_BADDEV;
5413         }
5414         dnr -= 8;
5415     }
5416 
5417     p00save[dnr] = (unsigned int)enable;
5418     return FD_OK;
5419 }
5420 
5421 
5422 /** \brief  Print current working directory
5423  *
5424  * \param[in]   nargs   number of arguments
5425  * \param[in]   args    optional arguments (unused
5426  *
5427  * \return 0
5428  */
pwd_cmd(int nargs,char ** args)5429 static int pwd_cmd(int nargs, char **args)
5430 {
5431     char buffer[4096];
5432 
5433     ioutil_getcwd(buffer, (int)(sizeof(buffer) - 1));
5434     printf("%s\n", buffer);
5435     return FD_OK;
5436 }
5437