OpenCBM
cbmcopy/main.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version
5  * 2 of the License, or (at your option) any later version.
6  *
7  * Copyright 2001-2004 Michael Klein <michael(dot)klein(at)puffin(dot)lb(dot)shuttle(dot)de>
8  * Copyright 2004-2007 Spiro Trikaliotis
9  * Copyright 2011 Thomas Winkler
10  * Copyright 2011 Wolfgang Moser (http://d81.de)
11  */
12 
13 #include <ctype.h>
14 #include <getopt.h>
15 #include <stdarg.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 
20 #include "arch.h"
21 #include "libmisc.h"
22 
23 #include "opencbm.h"
24 #include "cbmcopy.h"
25 #include "inputfiles.h"
26 
27 #ifdef LIBCBMCOPY_DEBUG
28 # define DEBUG_STATEDEBUG
29 #endif
30 #include "statedebug.h"
31 
32 
33 /* global, because of signal handler */
34 static CBM_FILE fd_cbm;
35 
36 static cbmcopy_severity_e verbosity = sev_info;
37 static int no_progress = 0;
38 
39 static void my_message_cb(cbmcopy_severity_e severity,
40  const char *format, ...)
41 {
42  va_list args;
43 
44  static const char *severities[4] =
45  {
46  "Fatal",
47  "Warning",
48  "Info",
49  "Debug"
50  };
51 
52  if(verbosity >= severity)
53  {
54  fprintf(stderr, "[%s] ", severities[severity]);
55  va_start(args, format);
56  vfprintf(stderr, format, args);
57  va_end(args);
58  fprintf(stderr, "\n");
59  }
60 }
61 
62 static int my_status_cb(int blocks_written)
63 {
64  char *statstr;
65 
66  // if(!no_progress) printf("."); fflush(stdout);
67  if(!no_progress)
68  {
69  switch (blocks_written%4)
70  {
71  case 3: statstr="\010-"; break;
72  case 2: statstr="\010/"; break;
73  case 1: statstr="\010|"; break;
74  default: statstr="\010.\\";
75  }
76  printf("%s",statstr);
77  fflush(stdout);
78  }
79  return 0;
80 }
81 
82 
83 static void help(const char *prog)
84 {
85  printf(
86 "Usage: %s [OPTION]... [DRIVE] [FILE]...\n"
87 "Copy files to a CBM-15[478]1 or compatible drive and vice versa\n"
88 "\n"
89 "Options:\n"
90 " -r, --read transfer 15x1->PC\n"
91 " (default when started as 'cbmread')\n"
92 " -w, --write transfer PC->15x1\n"
93 " (default when started as 'cbmwrite')\n"
94 " -r and -w are mutually exclusive\n"
95 "\n"
96 " -h, --help display this help and exit\n"
97 " -V, --version display version information and exit\n"
98 " -@, --adapter=plugin:bus tell OpenCBM which backend plugin and bus to use\n"
99 " -q, --quiet quiet output\n"
100 " -v, --verbose control verbosity (repeatedly, up to 3 times)\n"
101 " -n, --no-progress do not display progress information\n"
102 "\n"
103 " -t, --transfer=TRANSFER set transfermode; valid modes:\n"
104 " auto (default)\n"
105 " original or o (slowest)\n"
106 " serial1 or s1\n"
107 " serial2 or s2\n"
108 " parallel (fastest)\n"
109 " (can be abbreviated, if unambiguous)\n"
110 " `serial1' should work in any case;\n"
111 " `serial2' won't work if more than one device is\n"
112 " connected to the IEC bus;\n"
113 " `parallel' needs a XP1541/XP1571 cable in addition\n"
114 " to the serial one.\n"
115 " `auto' tries to determine the best option.\n"
116 " -d, --drive-type=TYPE specify drive type, one of:\n"
117 " 1541, 1570, 1571, 1581\n"
118 " -a, --address=ADDRESS override file start address\n"
119 " -o, --output=NAME specifies target name (ASCII, even for writing).\n"
120 "\n"
121 "Options for writing:\n"
122 " -f, --file-type specify CBM file type (D,P,S,U)\n"
123 " -R, --raw skip test for PC64 (.p00) and T64 input file\n"
124 "\n", prog);
125 }
126 
127 static void hint(char *prog)
128 {
129  printf("Try `%s' --help for more information.\n", prog);
130 }
131 
132 static void ARCH_SIGNALDECL reset(int dummy)
133 {
134  CBM_FILE fd_cbm_local;
135 
136  /*
137  * remember fd_cbm, and make the global one invalid
138  * so that no routine can call a cbm_...() routine
139  * once we have cancelled another one
140  */
141  fd_cbm_local = fd_cbm;
142  fd_cbm = CBM_FILE_INVALID;
143 
144  fprintf(stderr, "\nSIGINT caught X-( Resetting IEC bus...\n");
145 
146  DEBUG_PRINTDEBUGCOUNTERS();
147 
148  cbm_reset(fd_cbm_local);
149  cbm_driver_close(fd_cbm_local);
150  exit(1);
151 }
152 
153 
154 extern input_reader cbmwrite_raw;
155 extern input_reader cbmwrite_pc64;
156 extern input_reader cbmwrite_t64;
157 
158 
159 static void char_star_opt_once(const char **arg,
160  const char *long_name,
161  char **argv)
162 {
163  if(*arg)
164  {
165  my_message_cb(sev_fatal, "%s given more than once.", long_name);
166  hint(argv[0]);
167  exit(1);
168  }
169  *arg = optarg;
170 }
171 
172 
173 int ARCH_MAINDECL main(int argc, char **argv)
174 {
175  CBM_FILE fd;
176  FILE *file;
177  char *fname;
178 
179  int mode;
180  int option;
181  unsigned char *filedata;
182  size_t filesize;
183  char buf[48];
184  int num_entries;
185  int num_files;
186  int rv;
187  int i;
188  int write;
189  cbmcopy_settings *settings;
190  char auto_name[17];
191  char auto_type = '\0';
192  char output_type = '\0';
193  char *tail;
194  char *ext;
195  char *adapter = NULL;
196 
197  unsigned char drive;
198  const char *tm = NULL;
199  const char *dt = NULL;
200  int force_raw = 0;
201  int address = -1;
202  const char *output_name = NULL;
203  const char *address_str = NULL;
204  char *fs_name;
205 
206  input_reader *readers[] =
207  {
208  &cbmwrite_raw, /* must be first, as it is default */
209  &cbmwrite_pc64,
210  &cbmwrite_t64,
211  NULL
212  };
213 
214  input_reader *rd;
215 
216  struct option longopts[] =
217  {
218  { "help" , no_argument , NULL, 'h' },
219  { "verbose" , no_argument , NULL, 'v' },
220  { "adapter" , required_argument, NULL, '@' },
221  { "quiet" , no_argument , NULL, 'q' },
222  { "version" , no_argument , NULL, 'V' },
223  { "no-progress" , no_argument , NULL, 'n' },
224  { "read" , no_argument , NULL, 'r' },
225  { "write" , no_argument , NULL, 'w' },
226  { "transfer" , required_argument, NULL, 't' },
227  { "drive-type" , required_argument, NULL, 'd' },
228  { "file-type" , required_argument, NULL, 'f' },
229  { "output" , required_argument, NULL, 'o' },
230  { "raw" , no_argument , NULL, 'R' },
231  { "address" , no_argument , NULL, 'a' },
232  { NULL , 0 , NULL, 0 }
233  };
234 
235  const char shortopts[] ="hVqvrwnt:d:f:o:Ra:@:";
236 
237  if(NULL == (tail = strrchr(argv[0], '/')))
238  {
239  tail = argv[0];
240  }
241  else
242  {
243  tail++;
244  }
245  if(strcmp(tail, "cbmread") == 0)
246  {
247  mode = 'r'; /* read */
248  }
249  else if(strcmp(tail, "cbmwrite") == 0)
250  {
251  mode = 'w'; /* write */
252  }
253  else
254  {
255  mode = EOF; /* mode must be given later */
256  }
257 
258  settings = cbmcopy_get_default_settings();
259 
260  /* loop over cmd line opts */
261  while((option = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1)
262  {
263  switch(option)
264  {
265  case 'h': /* --help */
266  help(argv[0]);
267  return 0;
268  case 'V': /* --version */
269  printf("cbmcopy %s\n", OPENCBM_VERSION);
270  return 0;
271  case 'q': /* -- quiet */
272  if(verbosity > sev_fatal)
273  verbosity--;
274  break;
275  case 'v': /* --verbose */
276  if(verbosity < sev_debug)
277  verbosity++;
278  break;
279  case 'n': /* --no-progress */
280  no_progress = 1;
281  break;
282 
283  case 'r': /* --read */
284  case 'w': /* --write */
285  if(mode != EOF)
286  {
287  my_message_cb(sev_fatal, "-r/-w given more than once");
288  hint(argv[0]);
289  return 1;
290  }
291  mode = option;
292  break;
293 
294  case 't': /* --transfer */
295  char_star_opt_once(&tm, "--transfer", argv);
296  break;
297  case 'd': /* --drive-type */
298  char_star_opt_once(&dt, "--drive-type", argv);
299  break;
300  case 'o': /* --output */
301  char_star_opt_once(&output_name, "--output", argv);
302  break;
303  case 'f': /* --file-type */
304  output_type = (char) toupper(*optarg);
305  break;
306  case 'R': /* --raw */
307  force_raw = 1;
308  break;
309  case 'a': /* override-address */
310  char_star_opt_once(&address_str, "--address", argv);
311  break;
312  case '@': /* choose adapter */
313  if (adapter == NULL)
314  adapter = cbmlibmisc_strdup(optarg);
315  else {
316  my_message_cb(sev_fatal, "--adapter/-@ given more than once.");
317  hint(argv[0]);
318  exit(1);
319  }
320  break;
321  default : /* unknown */
322  hint(argv[0]);
323  return 1;
324  }
325  }
326 
327  /* check -r/-w */
328  switch(mode)
329  {
330  case 'r' :
331  write = 0;
332  break;
333  case 'w' :
334  write = 1;
335  break;
336  default:
337  my_message_cb(sev_fatal,
338  "-r or -w must be given when started as `%s'",
339  argv[0]);
340  hint(argv[0]);
341  return 1;
342  }
343 
344  /* check transfer mode */
345  settings->transfer_mode = cbmcopy_get_transfer_mode_index(tm);
346  if(settings->transfer_mode < 0)
347  {
348  my_message_cb(sev_fatal, "Unknown transfer mode: %s", tm);
349  return 1;
350  }
351 
352  /* check device type */
353  if(dt)
354  {
355  const struct
356  {
357  const char *str;
358  enum cbm_device_type_e type;
359  }
360  *p, types[] =
361  {
362  { "1541", cbm_dt_cbm1541 }, { "1571", cbm_dt_cbm1571 },
363  { "1570", cbm_dt_cbm1570 }, { "1581", cbm_dt_cbm1581 },
364  { NULL , cbm_dt_unknown }
365  };
366 
367  for(p = types; p->str && strcmp(dt, p->str); p++)
368  ; /* nothing */
369 
370  if(!p->str)
371  {
372  my_message_cb(sev_fatal, "Unknown drive type: %s", dt);
373  return 1;
374  }
375  settings->drive_type = p->type;
376  }
377 
378  /* check CBM file type */
379  if(output_type)
380  {
381  if(write)
382  {
383  if(strchr("DSPU", output_type) == NULL)
384  {
385  my_message_cb(sev_fatal, "Invalid file type : %c", output_type);
386  }
387  }
388  else
389  {
390  my_message_cb(sev_warning, "--file-type ignored");
391  }
392  }
393 
394  /* check load address override */
395  if(address_str)
396  {
397  address = strtol(address_str, &tail, 0);
398  if(*tail || address < 0 || address > 0xffff)
399  {
400  my_message_cb(sev_fatal, "--address invalid: %s", address_str);
401  hint(argv[0]);
402  return 1;
403  }
404  }
405 
406  /* first non-option is device number */
407  if(optind == argc)
408  {
409  my_message_cb(sev_fatal, "%s: No drive number given", argv[0]);
410  hint(argv[0]);
411  return 1;
412  }
413 
414  drive = (unsigned char) strtol(argv[optind], &tail, 0);
415  if(drive < 8 || drive > 11 || *tail)
416  {
417  my_message_cb(sev_fatal, "invalid drive: `%s'", argv[optind]);
418  return 1;
419  }
420 
421  /* remaining args are file names */
422  num_files = argc - optind - 1;
423 
424  if(num_files == 0)
425  {
426  my_message_cb(sev_fatal, "%s: No files?", argv[0]);
427  hint(argv[0]);
428  return 1;
429  }
430 
431  /* more than one file name given, avoid -o option */
432  if(num_files > 1 && output_name)
433  {
434  my_message_cb(sev_fatal, "--output requires exactly one file name");
435  return 1;
436  }
437 
438  rv = cbm_driver_open_ex( &fd, adapter );
439  cbmlibmisc_strfree(adapter);
440 
441  if(0 == rv)
442  {
443  fd_cbm = fd;
444 
445  /*
446  * If the user specified auto transfer mode, find out
447  * which transfer mode to use.
448  */
449  settings->transfer_mode =
450  cbmcopy_check_auto_transfer_mode(fd_cbm,
451  settings->transfer_mode,
452  drive);
453 
455 
456  while(++optind < argc)
457  {
458  fname = argv[optind];
459  if(write)
460  {
461  rd = readers[0];
462 
463  file = fopen(fname, "rb");
464  if(file)
465  {
466  num_entries = 0;
467  if(!force_raw)
468  {
469  /* try to detect file format */
470  for(i = 1; readers[i] && !num_entries; i++)
471  {
472  num_entries =
473  readers[i]->probe( file, fname, my_message_cb );
474  if(num_entries)
475  rd = readers[i];
476  }
477  }
478  if(!num_entries) num_entries = 1; /* raw file */
479 
480  for(i = 0; i < num_entries; i++)
481  {
482  my_message_cb( sev_debug,
483  "processing entry %d from %s",
484  i, fname );
485  if(rd->read(file, fname, i,
486  auto_name, &auto_type,
487  &filedata, &filesize, my_message_cb ) == 0)
488  {
489  buf[16] = '\0';
490  if(output_name)
491  {
492  strncpy(buf, output_name, 16);
493  cbm_ascii2petscii(buf);
494  }
495  else
496  {
497  /* no charset conversion */
498  strncpy(buf, auto_name, 16);
499  }
500  strcat(buf, ",x");
501  buf[strlen(buf)-1] =
502  output_type ? output_type : auto_type;
503  strcat(buf, ",W");
504 
505  my_message_cb( sev_info,
506  "writing %s -> %s", fname, buf );
507 
508  if(address >= 0 && filesize > 1)
509  {
510  filedata[0] = address % 0x100;
511  filedata[1] = address / 0x100;
512 
513  my_message_cb( sev_debug,
514  "override address: $%02x%02x",
515  filedata[1], filedata[0] );
516 
517  }
518  if(cbmcopy_write_file(fd, settings, drive,
519  buf, strlen(buf),
520  filedata, filesize,
521  my_message_cb,
522  my_status_cb) == 0)
523  {
524  printf("\n");
525  rv = cbm_device_status( fd, drive,
526  buf, sizeof(buf) );
527  my_message_cb( rv ? sev_warning : sev_info,
528  "%s", buf );
529  }
530  else
531  printf("\n");
532 
533  if(filedata)
534  {
535  free(filedata);
536  }
537  }
538  else
539  {
540  my_message_cb( sev_warning,
541  "error processing entry %d from %s",
542  i, fname );
543  }
544  }
545  }
546  else
547  {
548  my_message_cb( sev_warning,
549  "warning could not read %s: %s",
550  fname, arch_strerror(arch_get_errno()) );
551  }
552  }
553  else
554  {
555  strncpy(buf, fname, 16);
556  buf[16] = '\0';
557  cbm_ascii2petscii(buf);
558 
559  if(output_name)
560  {
561  fs_name = arch_strdup(output_name);
562  }
563  else
564  {
565  tail = strrchr(fname, ',');
566 
567  ext = "prg"; /* default */
568 
569  if(tail)
570  {
571  *tail++ = '\0';
572  switch(*tail)
573  {
574  case 'd': ext = "del"; break;
575  case 's': ext = "seq"; break;
576  case 'u': ext = "usr"; break;
577  }
578  }
579  fs_name = malloc(strlen(fname) + strlen(ext) + 2);
580  if(fs_name) sprintf(fs_name, "%s.%s", fname, ext);
581  }
582 
583  if(fs_name)
584  {
585  for(tail = fs_name; *tail; tail++)
586  {
587  if(*tail == '/') *tail = '_';
588  }
589  }
590  else
591  {
592  /* should not happen... */
593  cbm_driver_close( fd );
594  my_message_cb(sev_fatal, "Out of memory");
595  exit(1);
596  }
597 
598  my_message_cb( sev_info, "reading %s -> %s", buf, fs_name );
599 
600  if(cbmcopy_read_file(fd, settings, drive, buf, strlen(buf),
601  &filedata, &filesize,
602  my_message_cb, my_status_cb) == 0)
603  {
604  rv = cbm_device_status( fd, drive, buf, sizeof(buf) );
605  my_message_cb( rv ? sev_warning : sev_info, "%s", buf );
606 
607  file = fopen(fs_name, "wb");
608  if(file)
609  {
610  if(filedata)
611  {
612  if(address >= 0 && filesize > 1)
613  {
614  filedata[0] = address % 0x100;
615  filedata[1] = address / 0x100;
616 
617  my_message_cb( sev_debug,
618  "override address: $%02x%02x",
619  filedata[1], filedata[0] );
620  }
621  if(fwrite(filedata, filesize, 1, file) != 1)
622  {
623  my_message_cb(sev_warning,
624  "could not write %s: %s",
625  fs_name, arch_strerror(arch_get_errno()));
626  }
627  }
628  fclose(file);
629  }
630  else
631  {
632  my_message_cb(sev_warning,
633  "could not open %s: %s",
634  fs_name, arch_strerror(arch_get_errno()));
635  }
636 
637  if(filedata)
638  {
639  free(filedata);
640  }
641  }
642  else
643  {
644  my_message_cb(sev_warning, "error reading %s", buf);
645  }
646  if(fs_name)
647  {
648  free(fs_name);
649  }
650  }
651  }
652  cbm_driver_close( fd );
653 
654  if(rv)
655  {
656  my_message_cb(sev_warning, "there was at least one error" );
657  }
658  }
659 
660  return rv;
661 }
char * cbmlibmisc_strdup(const char *const OldString)
Duplicate a given string.
Definition: libstring.c:84
void cbmlibmisc_strfree(const char *String)
Free a string.
Definition: libstring.c:172
int CBMAPIDECL cbm_device_status(CBM_FILE HandleDevice, unsigned char DeviceAddress, void *Buffer, size_t BufferLength)
Read the drive status from a floppy.
Definition: cbm.c:1525
#define CBM_FILE_INVALID
Definition: opencbm.h:88
int ARCH_MAINDECL main(int argc, char **argv)
Initialize the xum1541 device This function tries to find and identify the xum1541 device...
Definition: usbcfg.c:38
int CBMAPIDECL cbm_driver_open_ex(CBM_FILE *HandleDevice, char *Adapter)
Opens the driver, extended version.
Definition: cbm.c:683
Definition: getopt.h:94
void CBMAPIDECL cbm_driver_close(CBM_FILE HandleDevice)
Closes the driver.
Definition: cbm.c:768
#define CBM_FILE
Definition: opencbm.h:87
void arch_set_ctrlbreak_handler(ARCH_CTRLBREAK_HANDLER Handler)
Set the Ctrl+C / Ctrl+Break handler.
Definition: ctrlbreak.c:62
Definition: cbmctrl.c:1340
int CBMAPIDECL cbm_reset(CBM_FILE HandleDevice)
RESET all devices.
Definition: cbm.c:1209
char *CBMAPIDECL cbm_ascii2petscii(char *Str)
Convert an null-termined ASCII string to PETSCII.
Definition: petscii.c:160
cbm_device_type_e
Definition: opencbm.h:114
DLL interface for accessing the driver.
Define makros and functions which account for differences between the different architectures.
Some functions for string handling.