/* interface to external converter programs Copyright (C) 2000-2003 David Necas (Yeti) This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include "common.h" #ifdef ENABLE_EXTERNAL #ifdef HAVE_SYS_WAIT_H # include #else pid_t waitpid(pid_t pid, int *status, int options); #endif /* We can't go on w/o this, defining struct stat manually is braindamaged. */ #include #include #include #ifdef HAVE_LIMITS_H # include #endif /* HAVE_LIMITS_H */ /* Resolve all the forking mess. */ #ifdef HAVE_WORKING_VFORK # ifdef HAVE_VFORK_H # include # endif /* HAVE_VFORK_H */ #else /* HAVE_WORKING_VFORK */ # define vfork fork #endif /* HAVE_WORKING_VFORK */ /* external converter command */ static char *extern_converter = NULL; /* fork and the child executes Settings.Converter on fname create temporary file containing stdin when fname == NULL and convert it passing special option STDOUT to converter (that is assumed to delete the temporary file itself) from_enc, to_enc are encoding names as should be passed to converter returns 0 on success, nonzero on failure; on critical failure (like we cannot fork()) it simply aborts */ int convert_external(File *file, const EncaEncoding from_enc) { /* special fourth parameter passed to external converter to instruct it to send result to stdout */ static const char *STDOUT_CONV = "-"; pid_t pid; int status; File *tempfile = NULL; char *from_name, *target_name; if (*extern_converter == '\0') { fprintf(stderr, "%s: No external converter defined!\n", program_name); return ERR_CANNOT; } if (options.verbosity_level > 2) fprintf(stderr, " launching `%s' to convert `%s'\n", extern_converter, ffname_r(file->name)); /* Is conversion of stdin requested? */ if (file->name == NULL) { /* Then we have to copy it to a temporary file. */ tempfile = file_temporary(file->buffer, 0); if (tempfile == NULL) return ERR_IOFAIL; if (copy_and_convert(file, tempfile, NULL) != 0) { file_unlink(tempfile->name); file_free(tempfile); return ERR_IOFAIL; } } /* Construct the charset names before fork() */ from_name = enca_strconcat(enca_charset_name(from_enc.charset, ENCA_NAME_STYLE_ENCA), enca_get_surface_name(from_enc.surface, ENCA_NAME_STYLE_ENCA), NULL); if (enca_charset_is_known(options.target_enc.charset) && (options.target_enc.surface & ENCA_SURFACE_UNKNOWN) == 0) { target_name = enca_strconcat(enca_charset_name(options.target_enc.charset, ENCA_NAME_STYLE_ENCA), enca_get_surface_name(options.target_enc.surface, ENCA_NAME_STYLE_ENCA), NULL); } else target_name = enca_strdup(options.target_enc_str); /* Fork. */ pid = vfork(); if (pid == 0) { /* Child. */ if (tempfile) execlp(extern_converter, extern_converter, from_name, target_name, tempfile->name, STDOUT_CONV, NULL); else execlp(extern_converter, extern_converter, from_name, target_name, file->name, NULL); exit(ERR_EXEC); } /* Parent. */ if (pid == -1) { fprintf(stderr, "%s: Cannot fork() to execute converter: %s\n", program_name, strerror(errno)); exit(EXIT_TROUBLE); } /* Wait until the child returns. */ if (waitpid(pid, &status, 0) == -1) { /* Error. */ fprintf(stderr, "%s: wait_pid() error while waiting for converter: %s\n", program_name, strerror(errno)); exit(EXIT_TROUBLE); } if (!WIFEXITED(status)) { /* Child exited abnormally. */ fprintf(stderr, "%s: Child converter process has been murdered.\n", program_name); exit(EXIT_TROUBLE); } enca_free(from_name); enca_free(target_name); if (tempfile) { unlink(tempfile->name); file_free(tempfile); } /* Child exited normally, test exit status. */ if (WEXITSTATUS(status) != EXIT_SUCCESS) { /* This means child was unable to execute converter or converter failed. */ fprintf(stderr, "%s: External converter failed (error code %d)\n", program_name, WEXITSTATUS(status)); if (WEXITSTATUS(status) == ERR_EXEC) return ERR_EXEC; else return ERR_CANNOT; } /* Success! Wow! */ return ERR_OK; } /* set external converter to extc */ void set_external_converter(const char *extc) { enca_free(extern_converter); if (strchr(extc, '/') == NULL) { if (extc[0] == 'b' && extc[1] == '-') { extc += 2; fprintf(stderr, "%s: The `b-' prefix for standard external converters " "is deprecated.\n" "I'll pretend you said `%s'.\n", program_name, extc); } extern_converter = enca_strconcat(EXTCONV_DIR, "/", extc, NULL); } else extern_converter = enca_strdup(extc); } /* return nonzero if external converter seems ok */ int check_external_converter(void) { /* FIXME: This creates a race condition. However we don't want to do all * the checking before every execlp() when conveting 500 files in a row, * and even if doing that, something can still sneak between stat() and * execlp(), so what. This is just a simple sanity check, nothing strict. */ if (*extern_converter == '\0' || access(extern_converter, X_OK) != 0) { fprintf(stderr, "%s: Converter `%s' doesn't seem to be executable.\n" "Note as of enca-1.3 external converters must be\n" "(a) one of the standard ones residing in %s\n" "(b) specified with full path\n", program_name, extern_converter, EXTCONV_DIR); return 0; } return 1; } #endif /* ENABLE_EXTERNAL */ /* vim: ts=2 */