/*
  Copyright (C) 2005-2006, 2008-2011, 2017, 2019
   Rocky Bernstein <rocky@gnu.org>

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/* Simple program to show using libudf to extract a file.

   This program can be compiled with either a C or C++ compiler. In
   the distribution we prefer C++ just to make sure we haven't broken
   things on the C++ side.
 */

/* config.h has to come first else _FILE_OFFSET_BITS are redefined in
   say opensolaris. */
#ifdef HAVE_CONFIG_H
#include "config.h"
#define __CDIO_CONFIG_H__ 1
#endif

/* This is the UDF image. */
#define UDF_IMAGE_PATH "../"
#define UDF_IMAGE "../test/data/udf102.iso"
#define UDF_FILENAME "/COPYING"
#define LOCAL_FILENAME "copying"

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#include <cdio/cdio.h>
#include <cdio/udf.h>

#define CEILING(x, y) ((x+(y-1))/y)

#define udf_PATH_DELIMITERS "/\\"

int
main(int argc, const char *argv[])
{
  udf_t *p_udf;
  FILE *p_outfd;
  char const *psz_udf_image;
  char const *psz_udf_fname;
  char const *psz_local_fname;

  if (argc > 1)
    psz_udf_image = argv[1];
  else
    psz_udf_image = UDF_IMAGE;

  if (argc > 2)
    psz_udf_fname = argv[2];
  else
    psz_udf_fname = UDF_FILENAME;

  if (argc > 3)
    psz_local_fname = argv[3];
  else
    psz_local_fname = LOCAL_FILENAME;


  p_udf = udf_open (psz_udf_image);

  if (NULL == p_udf) {
    fprintf(stderr, "Sorry, couldn't open %s as something using UDF\n",
	    psz_udf_image);
    return 1;
  } else {
    udf_dirent_t *p_udf_root = udf_get_root(p_udf, true, 0);
    udf_dirent_t *p_udf_file = NULL;
    if (NULL == p_udf_root) {
      fprintf(stderr, "Sorry, couldn't find / in %s\n",
	      psz_udf_image);
      udf_dirent_free(p_udf_file);
      udf_close(p_udf);
      return 1;
    }

    p_udf_file = udf_fopen(p_udf_root, psz_udf_fname);
    if (!p_udf_file) {
      fprintf(stderr, "Sorry, couldn't find %s in %s\n",
	      psz_udf_fname, psz_udf_image);
      udf_dirent_free(p_udf_root);
      udf_dirent_free(p_udf_file);
      udf_close(p_udf);
      return 2;

    }

    if (!(p_outfd = fopen (psz_local_fname, "wb")))
      {
	perror ("fopen()");
	udf_close(p_udf);
	udf_dirent_free(p_udf_file);
	return 3;
    }

    {
      uint64_t i_file_length = udf_get_file_length(p_udf_file);
      const unsigned int i_blocks = (unsigned int) CEILING(i_file_length, UDF_BLOCKSIZE);
      unsigned int i;
      for (i = 0; i < i_blocks ; i++) {
	char buf[UDF_BLOCKSIZE] = {'\0',};
	ssize_t i_read = udf_read_block(p_udf_file, buf, 1);

	if ( i_read < 0 ) {
	  fprintf(stderr, "Error reading UDF file %s at block %u\n",
		  psz_local_fname, i);
	  return 4;
	}

	fwrite (buf, i_read, 1, p_outfd);

	if (ferror (p_outfd)) {
	  perror ("fwrite()");
	  return 5;
	}
      }

      fflush (p_outfd);
      udf_dirent_free(p_udf_file);
      udf_dirent_free(p_udf_root);
      udf_close(p_udf);
      /* Make sure the file size has the exact same byte size. Without the
	 truncate below, the file will a multiple of UDF_BLOCKSIZE.
      */
      if (ftruncate (fileno (p_outfd), i_file_length))
	perror ("ftruncate()");

      printf("Extraction of file '%s' from %s successful.\n",
	     psz_local_fname, psz_udf_image);

      return 0;
    }
  }
}
