Blame src/iso-read.c

Packit dd8086
/*
Packit dd8086
  Copyright (C) 2004-2006, 2008, 2012-2013, 2017 Rocky Bernstein <rocky@gnu.org>
Packit dd8086
Packit dd8086
  This program is free software: you can redistribute it and/or modify
Packit dd8086
  it under the terms of the GNU General Public License as published by
Packit dd8086
  the Free Software Foundation, either version 3 of the License, or
Packit dd8086
  (at your option) any later version.
Packit dd8086
Packit dd8086
  This program is distributed in the hope that it will be useful,
Packit dd8086
  but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit dd8086
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit dd8086
  GNU General Public License for more details.
Packit dd8086
Packit dd8086
  You should have received a copy of the GNU General Public License
Packit dd8086
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit dd8086
*/
Packit dd8086
Packit dd8086
/* Program to read ISO-9660 images. */
Packit dd8086
Packit dd8086
#include "util.h"
Packit dd8086
#include "portable.h"
Packit dd8086
Packit dd8086
#ifdef HAVE_CONFIG_H
Packit dd8086
# include "config.h"
Packit dd8086
#endif
Packit dd8086
#ifdef HAVE_SYS_TYPES_H
Packit dd8086
#include <sys/types.h>
Packit dd8086
#endif
Packit dd8086
#include <cdio/cdio.h>
Packit dd8086
#include <cdio/iso9660.h>
Packit dd8086
#include <cdio/udf.h>
Packit dd8086
Packit dd8086
#ifdef HAVE_STDIO_H
Packit dd8086
#include <stdio.h>
Packit dd8086
#endif
Packit dd8086
#ifdef HAVE_ERRNO_H
Packit dd8086
#include <errno.h>
Packit dd8086
#endif
Packit dd8086
#ifdef HAVE_STRING_H
Packit dd8086
#include <string.h>
Packit dd8086
#endif
Packit dd8086
#ifdef HAVE_UNISTD_H
Packit dd8086
#include <unistd.h>
Packit dd8086
#endif
Packit dd8086
Packit dd8086
#include "getopt.h"
Packit dd8086
Packit dd8086
#define CEILING(x, y) ((x+(y-1))/y)
Packit dd8086
Packit dd8086
/* Used by `main' to communicate with `parse_opt'. And global options
Packit dd8086
 */
Packit dd8086
static struct arguments
Packit dd8086
{
Packit dd8086
  char          *file_name;
Packit dd8086
  char          *output_file;
Packit dd8086
  char          *iso9660_image;
Packit dd8086
  int            debug_level;
Packit dd8086
  int            no_header;
Packit dd8086
  int            ignore;
Packit dd8086
  int            udf;
Packit dd8086
} opts;
Packit dd8086
Packit dd8086
/* Parse a options. */
Packit dd8086
static bool
Packit dd8086
parse_options (int argc, char *argv[])
Packit dd8086
{
Packit dd8086
Packit dd8086
  int opt;
Packit dd8086
  int rc = EXIT_FAILURE;
Packit dd8086
Packit dd8086
  /* Configuration option codes */
Packit dd8086
  enum {
Packit dd8086
    OP_HANDLED = 0,
Packit dd8086
    OP_VERSION=1,
Packit dd8086
    OP_USAGE
Packit dd8086
  };
Packit dd8086
Packit dd8086
  static const char helpText[] =
Packit dd8086
    "Usage: %s [OPTION...]\n"
Packit dd8086
    "  -d, --debug=INT            Set debugging to LEVEL.\n"
Packit dd8086
    "  -i, --image=FILE           Read from ISO-9660 image. This option is mandatory\n"
Packit dd8086
    "  -e, --extract=FILE         Extract FILE from ISO-9660 image. This option is\n"
Packit dd8086
    "                             mandatory.\n"
Packit dd8086
    "  -k, --ignore               Ignore read error(s), i.e. keep going\n"
Packit dd8086
    "  --no-header                Don't display header and copyright (for\n"
Packit dd8086
    "                             regression testing)\n"
Packit dd8086
    "  -o, --output-file=FILE     Output file. This option is mandatory.\n"
Packit dd8086
    "  -U  --udf                  Contents are in UDF format\n"
Packit dd8086
    "  -V, --version              display version and copyright information and exit\n"
Packit dd8086
    "\n"
Packit dd8086
    "Help options:\n"
Packit dd8086
    "  -?, --help                 Show this help message\n"
Packit dd8086
    "  --usage                    Display brief usage message\n";
Packit dd8086
Packit dd8086
  static const char usageText[] =
Packit dd8086
    "Usage: %s [-d|--debug INT] [-i|--image FILE] [-e|--extract FILE]\n"
Packit dd8086
    "        [--no-header] [-o|--output-file FILE]  [-U|--udf]\n"
Packit dd8086
    "        [-V|--version] [-?|--help] [--usage]\n";
Packit dd8086
Packit dd8086
  /* Command-line options */
Packit dd8086
  static const char* optionsString = "d:i:e:o:VUk?";
Packit dd8086
  static const struct option optionsTable[] = {
Packit dd8086
    {"debug",       required_argument, NULL,   'd' },
Packit dd8086
    {"image",       required_argument, NULL,   'i' },
Packit dd8086
    {"extract",     required_argument, NULL,   'e' },
Packit dd8086
    {"no-header",   no_argument, &opts.no_header, 1 },
Packit dd8086
    {"ignore",      no_argument, &opts.ignore, 'k' },
Packit dd8086
    {"output-file", required_argument, NULL,   'o' },
Packit dd8086
    {"udf",         no_argument, &opts.udf,    'U' },
Packit dd8086
    {"version",     no_argument, NULL,         'V' },
Packit dd8086
Packit dd8086
    {"help", no_argument, NULL, '?' },
Packit dd8086
    {"usage", no_argument, NULL, OP_USAGE },
Packit dd8086
    { NULL, 0, NULL, 0 }
Packit dd8086
  };
Packit dd8086
Packit dd8086
  program_name = strrchr(argv[0],'/');
Packit dd8086
  program_name = program_name ? strdup(program_name+1) : strdup(argv[0]);
Packit dd8086
Packit dd8086
  while ((opt = getopt_long(argc, argv, optionsString, optionsTable, NULL)) != -1)
Packit dd8086
    switch (opt)
Packit dd8086
      {
Packit dd8086
      case 'd': opts.debug_level = atoi(optarg); break;
Packit dd8086
      case 'i': opts.iso9660_image = strdup(optarg); break;
Packit dd8086
      case 'k': opts.ignore        = 1; break;
Packit dd8086
      case 'e': opts.file_name = strdup(optarg); break;
Packit dd8086
      case 'o': opts.output_file = strdup(optarg); break;
Packit dd8086
      case 'U': opts.udf = 1; break;
Packit dd8086
Packit dd8086
      case 'V':
Packit dd8086
        print_version(program_name, CDIO_VERSION, 0, true);
Packit dd8086
	rc = EXIT_SUCCESS;
Packit dd8086
	goto error_exit;
Packit dd8086
Packit dd8086
      case '?':
Packit dd8086
        fprintf(stdout, helpText, program_name);
Packit dd8086
	rc = EXIT_INFO;
Packit dd8086
	goto error_exit;
Packit dd8086
Packit dd8086
      case OP_USAGE:
Packit dd8086
        fprintf(stderr, usageText, program_name);
Packit dd8086
	rc = EXIT_INFO;
Packit dd8086
	goto error_exit;
Packit dd8086
Packit dd8086
      case OP_HANDLED:
Packit dd8086
        break;
Packit dd8086
      }
Packit dd8086
Packit dd8086
  if (optind < argc) {
Packit dd8086
    const char *remaining_arg = argv[optind++];
Packit dd8086
    if (opts.iso9660_image != NULL) {
Packit dd8086
      report( stderr, "%s: Source specified as --image %s and as %s\n",
Packit dd8086
              program_name, opts.iso9660_image, remaining_arg );
Packit dd8086
      goto error_exit;
Packit dd8086
    }
Packit dd8086
Packit dd8086
    opts.iso9660_image = strdup(remaining_arg);
Packit dd8086
Packit dd8086
    if (optind < argc ) {
Packit dd8086
      report( stderr,
Packit dd8086
              "%s: use only one unnamed argument for the ISO 9660 "
Packit dd8086
              "image name\n",
Packit dd8086
              program_name );
Packit dd8086
      goto error_exit;
Packit dd8086
    }
Packit dd8086
  }
Packit dd8086
Packit dd8086
  if (NULL == opts.iso9660_image) {
Packit dd8086
    report( stderr, "%s: you need to specify an ISO-9660 image name.\n",
Packit dd8086
            program_name );
Packit dd8086
    report( stderr, "%s: Use option --image or try --help.\n",
Packit dd8086
            program_name );
Packit dd8086
    goto error_exit;
Packit dd8086
  }
Packit dd8086
Packit dd8086
  if (NULL == opts.file_name) {
Packit dd8086
    report( stderr, "%s: you need to specify a filename to extract.\n",
Packit dd8086
            program_name );
Packit dd8086
    report( stderr, "%s: Use option --extract or try --help.\n",
Packit dd8086
            program_name );
Packit dd8086
    goto error_exit;
Packit dd8086
  }
Packit dd8086
Packit dd8086
  if (NULL == opts.output_file) {
Packit dd8086
    report( stderr,
Packit dd8086
            "%s: you need to specify a place write filename extraction to.\n",
Packit dd8086
             program_name );
Packit dd8086
    report( stderr, "%s: Use option --output-file or try --help.\n",
Packit dd8086
            program_name );
Packit dd8086
    goto error_exit;
Packit dd8086
  }
Packit dd8086
Packit dd8086
  return true;
Packit dd8086
 error_exit:
Packit dd8086
  free(program_name);
Packit dd8086
  exit(rc);
Packit dd8086
}
Packit dd8086
Packit dd8086
static void
Packit dd8086
init(void)
Packit dd8086
{
Packit dd8086
  opts.debug_level   = 0;
Packit dd8086
  opts.ignore        = 0;
Packit dd8086
  opts.file_name     = NULL;
Packit dd8086
  opts.output_file   = NULL;
Packit dd8086
  opts.iso9660_image = NULL;
Packit dd8086
}
Packit dd8086
Packit dd8086
static int read_iso_file(const char *iso_name, const char *src,
Packit dd8086
                         FILE *outfd, size_t *bytes_written)
Packit dd8086
{
Packit dd8086
  iso9660_stat_t *statbuf;
Packit dd8086
  int i;
Packit dd8086
  iso9660_t *iso;
Packit dd8086
Packit dd8086
  iso = iso9660_open (iso_name);
Packit dd8086
Packit dd8086
  if (NULL == iso) {
Packit dd8086
    report(stderr,
Packit dd8086
           "%s: Sorry, couldn't open ISO-9660 image file '%s'.\n",
Packit dd8086
           program_name, src);
Packit dd8086
    return 1;
Packit dd8086
  }
Packit dd8086
Packit dd8086
  statbuf = iso9660_ifs_stat_translate (iso, src);
Packit dd8086
Packit dd8086
  if (NULL == statbuf)
Packit dd8086
    {
Packit dd8086
      report(stderr,
Packit dd8086
             "%s: Could not get ISO-9660 file information out of %s"
Packit dd8086
             " for file %s.\n",
Packit dd8086
             program_name, iso_name, src);
Packit dd8086
      report(stderr,
Packit dd8086
             "%s: iso-info may be able to show the contents of %s.\n",
Packit dd8086
             program_name, iso_name);
Packit dd8086
      return 2;
Packit dd8086
    }
Packit dd8086
Packit dd8086
Packit dd8086
  /* Copy the blocks from the ISO-9660 filesystem to the local filesystem. */
Packit dd8086
  for (i = 0; i < statbuf->size; i += ISO_BLOCKSIZE)
Packit dd8086
    {
Packit dd8086
      char buf[ISO_BLOCKSIZE];
Packit dd8086
Packit dd8086
      memset (buf, 0, ISO_BLOCKSIZE);
Packit dd8086
Packit dd8086
      if ( ISO_BLOCKSIZE != iso9660_iso_seek_read (iso, buf, statbuf->lsn
Packit dd8086
                                                   + (i / ISO_BLOCKSIZE),
Packit dd8086
                                                   1) )
Packit dd8086
      {
Packit dd8086
        report(stderr, "Error reading ISO 9660 file at lsn %lu\n",
Packit dd8086
               (long unsigned int) statbuf->lsn + (i / ISO_BLOCKSIZE));
Packit dd8086
        if (!opts.ignore) return 4;
Packit dd8086
      }
Packit dd8086
Packit dd8086
Packit dd8086
      fwrite (buf, ISO_BLOCKSIZE, 1, outfd);
Packit dd8086
Packit dd8086
      if (ferror (outfd))
Packit dd8086
        {
Packit dd8086
          perror ("fwrite()");
Packit dd8086
          return 5;
Packit dd8086
        }
Packit dd8086
    }
Packit dd8086
  iso9660_close(iso);
Packit dd8086
Packit dd8086
  *bytes_written = statbuf->size;
Packit dd8086
  return 0;
Packit dd8086
}
Packit dd8086
Packit dd8086
static int read_udf_file(const char *iso_name, const char *src,
Packit dd8086
                         FILE *outfd, size_t *bytes_written)
Packit dd8086
{
Packit dd8086
  udf_t *p_udf;
Packit dd8086
Packit dd8086
  p_udf = udf_open (iso_name);
Packit dd8086
Packit dd8086
  if (NULL == p_udf) {
Packit dd8086
    fprintf(stderr, "Sorry, couldn't open %s as something using UDF\n",
Packit dd8086
            iso_name);
Packit dd8086
    return 1;
Packit dd8086
  } else {
Packit dd8086
    udf_dirent_t *p_udf_root = udf_get_root(p_udf, true, 0);
Packit dd8086
    udf_dirent_t *p_udf_file = NULL;
Packit dd8086
    if (NULL == p_udf_root) {
Packit dd8086
      fprintf(stderr, "Sorry, couldn't find / in %s\n",
Packit dd8086
              iso_name);
Packit dd8086
      return 1;
Packit dd8086
    }
Packit dd8086
Packit dd8086
    p_udf_file = udf_fopen(p_udf_root, src);
Packit dd8086
    if (!p_udf_file) {
Packit dd8086
      fprintf(stderr, "Sorry, couldn't find %s in %s\n",
Packit dd8086
              src, iso_name);
Packit dd8086
      udf_dirent_free(p_udf_root);
Packit dd8086
      return 2;
Packit dd8086
Packit dd8086
    }
Packit dd8086
Packit dd8086
    {
Packit dd8086
      uint64_t i_file_length = udf_get_file_length(p_udf_file);
Packit dd8086
      const unsigned int i_blocks = (unsigned int) CEILING(i_file_length, UDF_BLOCKSIZE);
Packit dd8086
      unsigned int i;
Packit dd8086
      for (i = 0; i < i_blocks ; i++) {
Packit dd8086
        char buf[UDF_BLOCKSIZE] = {'\0',};
Packit dd8086
        ssize_t i_read = udf_read_block(p_udf_file, buf, 1);
Packit dd8086
Packit dd8086
        if ( i_read < 0 ) {
Packit dd8086
          fprintf(stderr, "Error reading UDF file %s at block %u\n",
Packit dd8086
                  src, i);
Packit dd8086
	  free(p_udf_file);
Packit dd8086
	  udf_dirent_free(p_udf_root);
Packit dd8086
          return 4;
Packit dd8086
        }
Packit dd8086
Packit dd8086
        fwrite (buf, i_read, 1, outfd);
Packit dd8086
Packit dd8086
        if (ferror (outfd)) {
Packit dd8086
          perror ("fwrite()");
Packit dd8086
	  free(p_udf_file);
Packit dd8086
	  udf_dirent_free(p_udf_root);
Packit dd8086
          return 5;
Packit dd8086
        }
Packit dd8086
      }
Packit dd8086
Packit dd8086
      free(p_udf_file);
Packit dd8086
      udf_dirent_free(p_udf_root);
Packit dd8086
      udf_close(p_udf);
Packit dd8086
      *bytes_written = i_file_length;
Packit dd8086
    }
Packit dd8086
  }
Packit dd8086
  return 0;
Packit dd8086
}
Packit dd8086
Packit dd8086
int
Packit dd8086
main(int argc, char *argv[])
Packit dd8086
{
Packit dd8086
  FILE *outfd;
Packit dd8086
  int ret;
Packit dd8086
  size_t bytes_written = 0;
Packit dd8086
Packit dd8086
  init();
Packit dd8086
Packit dd8086
  /* Parse our arguments; every option seen by `parse_opt' will
Packit dd8086
     be reflected in `arguments'. */
Packit dd8086
  if (!parse_options(argc, argv)) {
Packit dd8086
    report(stderr,
Packit dd8086
           "error while parsing command line - try --help\n");
Packit dd8086
    return 2;
Packit dd8086
  }
Packit dd8086
Packit dd8086
  if (!(outfd = fopen (opts.output_file, "wb")))
Packit dd8086
    {
Packit dd8086
      report(stderr,
Packit dd8086
             "%s: Could not open %s for writing: %s\n",
Packit dd8086
             program_name, opts.output_file, strerror(errno));
Packit dd8086
      return 3;
Packit dd8086
    }
Packit dd8086
Packit dd8086
  if (opts.udf) {
Packit dd8086
      ret = read_udf_file (opts.iso9660_image, opts.file_name,
Packit dd8086
			   outfd, &bytes_written);
Packit dd8086
  } else  {
Packit dd8086
      ret = read_iso_file (opts.iso9660_image, opts.file_name,
Packit dd8086
                           outfd, &bytes_written);
Packit dd8086
  }
Packit dd8086
  if (ret != 0) return ret;
Packit dd8086
Packit dd8086
  fflush (outfd);
Packit dd8086
Packit dd8086
  /* Make sure the file size has the exact same byte size. Without the
Packit dd8086
     truncate below, the file will a multiple of ISO_BLOCKSIZE.
Packit dd8086
   */
Packit dd8086
  if (ftruncate (fileno (outfd), bytes_written))
Packit dd8086
    perror ("ftruncate()");
Packit dd8086
Packit dd8086
  fclose (outfd);
Packit dd8086
  return 0;
Packit dd8086
}