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, §or);
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, §or);
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, §or);
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, §or);
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, §or);
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, §or);
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 *)§or,
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