eb/ebrefile/ebrefile.c
2016-10-28 20:02:30 -07:00

811 lines
20 KiB
C

/* -*- C -*-
* Copyright (c) 2001-2006 Motoyuki Kasahara
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the project nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#include <utime.h>
#ifdef ENABLE_NLS
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include <libintl.h>
#endif
#ifndef HAVE_STRCASECMP
int strcasecmp(const char *, const char *);
int strncasecmp(const char *, const char *, size_t);
#endif
#ifndef O_BINARY
#define O_BINARY 0
#endif
/*
* stat macros.
*/
#ifndef S_ISREG
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif
#ifndef S_ISDIR
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif
/*
* The maximum length of path name.
*/
#ifndef PATH_MAX
#ifdef MAXPATHLEN
#define PATH_MAX MAXPATHLEN
#else /* not MAXPATHLEN */
#define PATH_MAX 1024
#endif /* not MAXPATHLEN */
#endif /* not PATH_MAX */
/*
* rename() on Windows complains if the new file already exists.
* We fake rename() here for Windows.
*/
#ifdef WIN32
#include <windows.h>
#define rename(old, new) \
(MoveFileEx((old), (new), MOVEFILE_REPLACE_EXISTING) ? 0 : -1)
#endif
#include "eb.h"
#include "error.h"
#include "build-post.h"
#include "ebutils.h"
#include "getopt.h"
#include "getumask.h"
#include "makedir.h"
#include "samefile.h"
#include "yesno.h"
/*
* Tricks for gettext.
*/
#ifdef ENABLE_NLS
#define _(string) gettext(string)
#ifdef gettext_noop
#define N_(string) gettext_noop(string)
#else
#define N_(string) (string)
#endif
#else
#define _(string) (string)
#define N_(string) (string)
#endif
#define DEFAULT_BOOK_DIRECTORY "."
#define DEFAULT_OUTPUT_DIRECTORY "."
/*
* Unexported functions.
*/
static void output_help(void);
static int refile_book(const char *out_path, const char *in_path,
char subbook_name_list[][EB_MAX_DIRECTORY_NAME_LENGTH + 1],
int subbook_name_count);
static int refile_catalog(const char *out_catalog_name,
const char *in_catalog_name, EB_Disc_Code disc_code,
char subbook_name_list[][EB_MAX_DIRECTORY_NAME_LENGTH + 1],
int subbook_name_count);
static int copy_file(const char *out_file_name, const char *in_file_name);
static void trap(int signal_number);
static int find_subbook_name(char
subbook_name_list[][EB_MAX_DIRECTORY_NAME_LENGTH + 1],
int subbook_name_count, const char *pattern);
/*
* Command line options.
*/
static const char *short_options = "ho:S:v";
static struct option long_options[] = {
{"help", no_argument, NULL, 'h'},
{"output-directory", required_argument, NULL, 'o'},
{"subbook", required_argument, NULL, 'S'},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0}
};
/*
* Program name and version.
*/
const char *program_name = "ebrefile";
const char *program_version = VERSION;
const char *invoked_name;
/*
* File names to be deleted when signal is received.
*/
static const char *trap_file_name = NULL;
static int trap_file = -1;
int
main(int argc, char *argv[])
{
EB_Error_Code error_code;
char out_path[PATH_MAX + 1];
char book_path[PATH_MAX + 1];
char subbook_name_list[EB_MAX_SUBBOOKS][EB_MAX_DIRECTORY_NAME_LENGTH + 1];
int subbook_name_count = 0;
int ch;
invoked_name = argv[0];
strcpy(out_path, DEFAULT_OUTPUT_DIRECTORY);
/*
* Initialize locale data.
*/
#ifdef ENABLE_NLS
#ifdef HAVE_SETLOCALE
setlocale(LC_ALL, "");
#endif
bindtextdomain(TEXT_DOMAIN_NAME, LOCALEDIR);
textdomain(TEXT_DOMAIN_NAME);
#endif
/*
* Initialize `book'.
*/
error_code = eb_initialize_library();
if (error_code != EB_SUCCESS) {
fprintf(stderr, "%s: %s\n", invoked_name,
eb_error_message(error_code));
goto die;
}
/*
* Parse command line options.
*/
for (;;) {
ch = getopt_long(argc, argv, short_options, long_options, NULL);
if (ch == -1)
break;
switch (ch) {
case 'h':
/*
* Option `-h'. Display help message, then exit.
*/
output_help();
exit(0);
case 'o':
/*
* Option `-o'. Output files under DIRECOTRY.
* The length of the file name
* "<out_path>/catalogs.bak;1"
* must not exceed PATH_MAX.
*/
if (PATH_MAX < strlen(optarg)) {
fprintf(stderr, _("%s: too long output directory path\n"),
invoked_name);
exit(1);
}
strcpy(out_path, optarg);
canonicalize_path(out_path);
if (PATH_MAX
< strlen(out_path) + (1 + EB_MAX_DIRECTORY_NAME_LENGTH + 6)) {
fprintf(stderr, _("%s: too long output directory path\n"),
invoked_name);
goto die;
}
break;
case 'S':
/*
* Option `-S'. Specify target subbooks.
*/
if (parse_subbook_name_argument(invoked_name, optarg,
subbook_name_list, &subbook_name_count) < 0)
exit(1);
break;
case 'v':
/*
* Option `-v'. Display version number, then exit.
*/
output_version(program_name, program_version);
exit(0);
default:
output_try_help(invoked_name);
goto die;
}
}
/*
* Check the number of rest arguments.
*/
if (1 < argc - optind) {
fprintf(stderr, _("%s: too many arguments\n"), invoked_name);
output_try_help(invoked_name);
goto die;
}
/*
* Set a book path.
*/
if (argc == optind)
strcpy(book_path, DEFAULT_BOOK_DIRECTORY);
else
strcpy(book_path, argv[optind]);
if (is_ebnet_url(book_path)) {
fprintf(stderr, "%s: %s\n", invoked_name,
eb_error_message(EB_ERR_EBNET_UNSUPPORTED));
goto die;
}
canonicalize_path(book_path);
if (PATH_MAX
< strlen(book_path) + (1 + EB_MAX_DIRECTORY_NAME_LENGTH + 6)) {
fprintf(stderr, _("%s: too long book directory path\n"),
invoked_name);
goto die;
}
/*
* Set signals.
*/
#ifdef SIGHUP
signal(SIGHUP, trap);
#endif
signal(SIGINT, trap);
#ifdef SIGQUIT
signal(SIGQUIT, trap);
#endif
#ifdef SIGTERM
signal(SIGTERM, trap);
#endif
/*
* Refile a catalog.
*/
if (refile_book(out_path, book_path, subbook_name_list,
subbook_name_count) < 0)
goto die;
eb_finalize_library();
return 0;
/*
* A critical error occurs...
*/
die:
eb_finalize_library();
exit(1);
}
/*
* Output help message to stdandard out.
*/
static void
output_help(void)
{
printf(_("Usage: %s [option...] [book-directory]\n"), program_name);
printf(_("Options:\n"));
printf(_(" -h --help display this help, then exit\n"));
printf(_(" -o DIRECTORY --output-directory DIRECTORY\n"));
printf(_(" ouput files under DIRECTORY\n"));
printf(_(" (default: %s)\n"),
DEFAULT_OUTPUT_DIRECTORY);
printf(_(" -S SUBBOOK[,SUBBOOK...] --subbook SUBBOOK[,SUBBOOK...]\n"));
printf(_(" target subbook\n"));
printf(_(" (default: all subbooks)\n"));
printf(_(" -v --version display version number, then exit\n"));
printf(_("\nArgument:\n"));
printf(_(" book-directory top directory of a CD-ROM book\n"));
printf(_(" (default: %s)\n"),
DEFAULT_BOOK_DIRECTORY);
printf(_("\nReport bugs to %s.\n"), MAILING_ADDRESS);
fflush(stdout);
}
/*
* Read a catalog file in `in_path' and create refiled catalog file
* in `out_path'.
*/
static int
refile_book(const char *out_path, const char *in_path,
char subbook_name_list[][EB_MAX_DIRECTORY_NAME_LENGTH + 1],
int subbook_name_count)
{
char in_file_name[PATH_MAX + 1];
char out_file_name[PATH_MAX + 1];
char tmp_file_name[PATH_MAX + 1];
char old_file_name[PATH_MAX + 1];
char in_base_name[EB_MAX_FILE_NAME_LENGTH + 1];
EB_Disc_Code disc_code;
struct stat out_status;
struct stat old_status;
/*
* Find a catalog file.
*/
if (eb_find_file_name(in_path, "catalog", in_base_name)
== EB_SUCCESS) {
disc_code = EB_DISC_EB;
} else if (eb_find_file_name(in_path, "catalogs", in_base_name)
== EB_SUCCESS) {
disc_code = EB_DISC_EPWING;
} else {
fprintf(stderr, _("%s: no catalog file: %s\n"), invoked_name, in_path);
return -1;
}
/*
* Set file names.
*/
eb_compose_path_name(in_path, in_base_name, in_file_name);
eb_compose_path_name(out_path, in_base_name, out_file_name);
strcpy(old_file_name, out_file_name);
eb_fix_path_name_suffix(old_file_name, ".old");
strcpy(tmp_file_name, out_file_name);
eb_fix_path_name_suffix(tmp_file_name, ".tmp");
/*
* Copy the original catalog file.
*/
if (stat(old_file_name, &old_status) < 0
&& errno == ENOENT
&& stat(out_file_name, &out_status) == 0
&& S_ISREG(out_status.st_mode)) {
trap_file_name = old_file_name;
if (copy_file(old_file_name, out_file_name) < 0)
return -1;
trap_file_name = NULL;
}
/*
* Refile the catalog file.
*/
trap_file_name = tmp_file_name;
if (refile_catalog(tmp_file_name, in_file_name, disc_code,
subbook_name_list, subbook_name_count) < 0) {
unlink(tmp_file_name);
rename(old_file_name, out_file_name);
return -1;
}
if (rename(tmp_file_name, out_file_name) < 0) {
fprintf(stderr, _("%s: failed to move the file, %s: %s -> %s\n"),
invoked_name, strerror(errno), tmp_file_name, out_file_name);
unlink(tmp_file_name);
return -1;
}
trap_file_name = NULL;
return 0;
}
/*
* Read a catalog file `in_catalog_name' and create refiled catalog file
* as `out_catalog_name'.
*/
static int
refile_catalog(const char *out_catalog_name, const char *in_catalog_name,
EB_Disc_Code disc_code,
char subbook_name_list[][EB_MAX_DIRECTORY_NAME_LENGTH + 1],
int subbook_name_count)
{
char buffer[EB_SIZE_PAGE];
char directory_name[EB_MAX_DIRECTORY_NAME_LENGTH + 1];
int in_subbook_count;
int in_file = -1;
int out_file = -1;
int subbbook_map_table[EB_MAX_SUBBOOKS];
off_t out_file_offset;
size_t catalog_size;
int i, j;
for (i = 0; i < EB_MAX_SUBBOOKS; i++)
subbbook_map_table[i] = EB_SUBBOOK_INVALID;
if (disc_code == EB_DISC_EB)
catalog_size = EB_SIZE_EB_CATALOG;
else
catalog_size = EB_SIZE_EPWING_CATALOG;
/*
* Open input file.
*/
in_file = open(in_catalog_name, O_RDONLY | O_BINARY);
if (in_file < 0) {
fprintf(stderr, _("%s: failed to open the file, %s: %s\n"),
invoked_name, strerror(errno), in_catalog_name);
goto failed;
}
/*
* Open output file.
*/
#ifdef O_CREAT
out_file = open(out_catalog_name, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY,
0666 ^ get_umask());
#else
out_file = creat(out_catalog_name, 0666 ^ get_umask());
#endif
trap_file = out_file;
if (out_file < 0) {
fprintf(stderr, _("%s: failed to open the file, %s: %s\n"),
invoked_name, strerror(errno), out_catalog_name);
goto failed;
}
/*
* Copy header.
*/
if (read(in_file, buffer, 16) != 16) {
fprintf(stderr, _("%s: failed to read the file, %s: %s\n"),
invoked_name, strerror(errno), in_catalog_name);
goto failed;
}
in_subbook_count = eb_uint2(buffer);
if (write(out_file, buffer, 16) != 16) {
fprintf(stderr, _("%s: failed to write the file, %s: %s\n"),
invoked_name, strerror(errno), out_catalog_name);
goto failed;
}
out_file_offset = 16;
/*
* Copy basic information of subbooks.
*/
for (i = 0; i < in_subbook_count; i++) {
/*
* Read subbook entry.
*/
if (read(in_file, buffer, catalog_size) != catalog_size) {
fprintf(stderr, _("%s: failed to read the file, %s: %s\n"),
invoked_name, strerror(errno), in_catalog_name);
goto failed;
}
/*
* Check whether `subbook_name_list' has a directory name of
* this subbook. If not, we ignore this subbook.
*/
if (disc_code == EB_DISC_EB) {
strncpy(directory_name, buffer + 2 + EB_MAX_EB_TITLE_LENGTH,
EB_MAX_DIRECTORY_NAME_LENGTH);
} else {
strncpy(directory_name, buffer + 2 + EB_MAX_EPWING_TITLE_LENGTH,
EB_MAX_DIRECTORY_NAME_LENGTH);
}
directory_name[EB_MAX_DIRECTORY_NAME_LENGTH] = '\0';
if (subbook_name_count == 0)
subbbook_map_table[i] = i;
else {
int subbook_index;
subbook_index = find_subbook_name(subbook_name_list,
subbook_name_count, directory_name);
if (subbook_index < 0)
continue;
subbbook_map_table[i] = subbook_index;
}
/*
* Write the subbook entry.
*/
if (write(out_file, buffer, catalog_size) != catalog_size) {
fprintf(stderr, _("%s: failed to write the file, %s: %s\n"),
invoked_name, strerror(errno), out_catalog_name);
goto failed;
}
out_file_offset += catalog_size;
}
/*
* Copy extended information of subbooks.
*/
if (disc_code == EB_DISC_EPWING) {
for (i = 0; i < in_subbook_count; i++) {
if (read(in_file, buffer, catalog_size) != catalog_size) {
fprintf(stderr, _("%s: failed to read the file, %s: %s\n"),
invoked_name, strerror(errno), in_catalog_name);
goto failed;
}
if (subbbook_map_table[i] == EB_SUBBOOK_INVALID)
continue;
if (write(out_file, buffer, catalog_size) != catalog_size) {
fprintf(stderr, _("%s: failed to write the file, %s: %s\n"),
invoked_name, strerror(errno), out_catalog_name);
goto failed;
}
out_file_offset += catalog_size;
}
}
/*
* Check whether all subbooks in `subbook_name_list' are found.
*/
for (i = 0; i < subbook_name_count; i++) {
for (j = 0; j < in_subbook_count; j++) {
if (subbbook_map_table[j] == i)
break;
}
if (in_subbook_count <= j) {
fprintf(stderr, _("%s: warning: no such subbook: %s\n"),
invoked_name, subbook_name_list[i]);
}
}
/*
* Copy rest of the catalog file.
*/
for (;;) {
ssize_t read_length;
read_length = read(in_file, buffer, EB_SIZE_PAGE);
if (read_length == 0) {
break;
} else if (read_length < 0) {
fprintf(stderr, _("%s: failed to read the file, %s: %s\n"),
invoked_name, strerror(errno), in_catalog_name);
goto failed;
}
if (write(out_file, buffer, read_length) != read_length) {
fprintf(stderr, _("%s: failed to write the file, %s: %s\n"),
invoked_name, strerror(errno), out_catalog_name);
goto failed;
}
out_file_offset += read_length;
}
/*
* Fill the current page with 0.
*/
if (0 < out_file_offset % EB_SIZE_PAGE) {
size_t pad_length;
pad_length = EB_SIZE_PAGE - (out_file_offset % EB_SIZE_PAGE);
memset(buffer, 0, EB_SIZE_PAGE);
if (write(out_file, buffer, pad_length) != pad_length) {
fprintf(stderr, _("%s: failed to write the file, %s: %s\n"),
invoked_name, strerror(errno), out_catalog_name);
goto failed;
}
}
/*
* Fix the number of subbook.
*/
if (subbook_name_count == 0) {
buffer[0] = (in_subbook_count >> 8) & 0xff;
buffer[1] = in_subbook_count & 0xff;
} else {
buffer[0] = (subbook_name_count >> 8) & 0xff;
buffer[1] = subbook_name_count & 0xff;
}
if (lseek(out_file, 0, SEEK_SET) < 0) {
fprintf(stderr, _("%s: failed to seek the file, %s: %s\n"),
invoked_name, strerror(errno), out_catalog_name);
goto failed;
}
if (write(out_file, buffer, 2) != 2) {
fprintf(stderr, _("%s: failed to write the file, %s: %s\n"),
invoked_name, strerror(errno), out_catalog_name);
goto failed;
}
/*
* Close files.
*/
close(in_file);
close(out_file);
trap_file = -1;
return 0;
/*
* An error occurs...
*/
failed:
if (0 <= in_file)
close(in_file);
if (0 <= out_file)
close(out_file);
return -1;
}
/*
* Copy a file from `in_file_name' to `out_file_name'.
* If it succeeds, 0 is returned. Otherwise -1 is returned.
*/
static int
copy_file(const char *out_file_name, const char *in_file_name)
{
unsigned char buffer[EB_SIZE_PAGE];
size_t copied_length;
struct stat in_status;
int in_file = -1, out_file = -1;
ssize_t read_result;
struct utimbuf utim;
/*
* Check for the input file.
*/
if (stat(in_file_name, &in_status) != 0 || !S_ISREG(in_status.st_mode)) {
fprintf(stderr, _("%s: no such file: %s\n"), invoked_name,
in_file_name);
goto failed;
}
/*
* Open files.
*/
in_file = open(in_file_name, O_RDONLY | O_BINARY);
if (in_file < 0) {
fprintf(stderr, _("%s: failed to open the file, %s: %s\n"),
invoked_name, strerror(errno), in_file_name);
goto failed;
}
#ifdef O_CREAT
out_file = open(out_file_name, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY,
0666 ^ get_umask());
#else
out_file = creat(out_file_name, 0666 ^ get_umask());
#endif
trap_file = out_file;
if (out_file < 0) {
fprintf(stderr, _("%s: failed to open the file, %s: %s\n"),
invoked_name, strerror(errno), out_file_name);
goto failed;
}
/*
* Read data from the input file, compress the data, and then
* write them to the output file.
*/
copied_length = 0;
for (;;) {
/*
* Read data from `in_file', and write them to `out_file'.
*/
read_result = read(in_file, buffer, EB_SIZE_PAGE);
if (read_result == 0) {
break;
} else if (read_result < 0) {
fprintf(stderr, _("%s: failed to read from the file, %s: %s\n"),
invoked_name, strerror(errno), in_file_name);
goto failed;
}
/*
* Write decoded data to `out_file'.
*/
if (write(out_file, buffer, read_result) != read_result) {
fprintf(stderr, _("%s: failed to write to the file, %s: %s\n"),
invoked_name, strerror(errno), out_file_name);
goto failed;
}
copied_length += read_result;
}
/*
* Close files.
*/
close(in_file);
close(out_file);
trap_file = -1;
/*
* Set owner, group, permission, atime and mtime of `out_file'.
* We ignore return values of `chown', `chmod' and `utime'.
*/
utim.actime = in_status.st_atime;
utim.modtime = in_status.st_mtime;
utime(out_file_name, &utim);
return 0;
/*
* An error occurs...
*/
failed:
if (0 <= in_file)
close(in_file);
if (0 <= out_file)
close(out_file);
return -1;
}
/*
* Signal handler.
*/
static void
trap(int signal_number)
{
if (0 <= trap_file)
close(trap_file);
if (trap_file_name != NULL)
unlink(trap_file_name);
exit(1);
}
/*
* Search `subbook_name_list[]' for `pattern'.
* `subbook_name_count' is length of `subbook_name_list[]'.
*
* If found, the function returns index of the element. Otherwise it
* returns -1.
*/
static int
find_subbook_name(char subbook_name_list[][EB_MAX_DIRECTORY_NAME_LENGTH + 1],
int subbook_name_count, const char *pattern)
{
char canonicalized_pattern[EB_MAX_FILE_NAME_LENGTH + 1];
char *space;
int i;
strcpy(canonicalized_pattern, pattern);
space = strchr(canonicalized_pattern, ' ');
if (space != NULL)
*space = '\0';
for (i = 0; i < subbook_name_count; i++) {
if (strcasecmp(subbook_name_list[i], canonicalized_pattern) == 0)
return i;
}
return -1;
}