(root)/
man-db-2.12.0/
src/
man-recode.c
       1  /*
       2   * man-recode.c: convert manual pages to another encoding
       3   *
       4   * Copyright (C) 2019 Colin Watson.
       5   *
       6   * This file is part of man-db.
       7   *
       8   * man-db is free software; you can redistribute it and/or modify it
       9   * under the terms of the GNU General Public License as published by
      10   * the Free Software Foundation; either version 2 of the License, or
      11   * (at your option) any later version.
      12   *
      13   * man-db is distributed in the hope that it will be useful, but
      14   * WITHOUT ANY WARRANTY; without even the implied warranty of
      15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16   * GNU General Public License for more details.
      17   *
      18   * You should have received a copy of the GNU General Public License
      19   * along with man-db; if not, write to the Free Software Foundation,
      20   * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      21   */
      22  
      23  #ifdef HAVE_CONFIG_H
      24  #  include "config.h"
      25  #endif /* HAVE_CONFIG_H */
      26  
      27  #include <stdbool.h>
      28  #include <stdlib.h>
      29  #include <string.h>
      30  #include <assert.h>
      31  #include <unistd.h>
      32  #include <fcntl.h>
      33  #include <sys/types.h>
      34  #include <sys/stat.h>
      35  
      36  #include "argp.h"
      37  #include "dirname.h"
      38  #include "error.h"
      39  #include "gl_array_list.h"
      40  #include "gl_xlist.h"
      41  #include "progname.h"
      42  #include "tempname.h"
      43  #include "xalloc.h"
      44  #include "xvasprintf.h"
      45  
      46  #include "gettext.h"
      47  #define _(String) gettext (String)
      48  #define N_(String) gettext_noop (String)
      49  
      50  #include "manconfig.h"
      51  
      52  #include "pipeline.h"
      53  
      54  #include "cleanup.h"
      55  #include "compression.h"
      56  #include "debug.h"
      57  #include "encodings.h"
      58  #include "fatal.h"
      59  #include "glcontainers.h"
      60  #include "sandbox.h"
      61  #include "util.h"
      62  
      63  #include "decompress.h"
      64  #include "manconv.h"
      65  #include "manconv_client.h"
      66  
      67  int quiet = 0;
      68  man_sandbox *sandbox;
      69  
      70  static char *to_code;
      71  static gl_list_t filenames;
      72  static const char *suffix;
      73  static bool in_place;
      74  
      75  struct try_file_at_args {
      76  	int dir_fd;
      77  	int flags;
      78  };
      79  
      80  static int
      81  try_file_at (char *tmpl, void *flags)
      82  {
      83  	struct try_file_at_args *args = flags;
      84  	return openat (args->dir_fd, tmpl,
      85  		       (args->flags & ~O_ACCMODE) | O_RDWR | O_CREAT | O_EXCL,
      86  		       S_IRUSR | S_IWUSR);
      87  }
      88  
      89  static int
      90  mkstempat (int dir_fd, char *xtemplate)
      91  {
      92  	struct try_file_at_args args;
      93  
      94  	args.dir_fd = dir_fd;
      95  	args.flags = 0;
      96  	return try_tempname (xtemplate, 0, &args, try_file_at);
      97  }
      98  
      99  enum opts {
     100  	OPT_SUFFIX = 256,
     101  	OPT_IN_PLACE = 257,
     102  	OPT_MAX
     103  };
     104  
     105  const char *argp_program_version = "man-recode " PACKAGE_VERSION;
     106  const char *argp_program_bug_address = PACKAGE_BUGREPORT;
     107  error_t argp_err_exit_status = FAIL;
     108  
     109  static const char args_doc[] =
     110  	N_("-t CODE {--suffix SUFFIX | --in-place} FILENAME...");
     111  
     112  static struct argp_option options[] = {
     113  	OPT ("to-code", 't', N_("CODE"), N_("encoding for output")),
     114  	OPT ("suffix", OPT_SUFFIX, N_("SUFFIX"),
     115  	     N_("suffix to append to output file name")),
     116  	OPT ("in-place", OPT_IN_PLACE, 0,
     117  	     N_("overwrite input files in place")),
     118  	OPT ("debug", 'd', 0, N_("emit debugging messages")),
     119  	OPT ("quiet", 'q', 0, N_("produce fewer warnings")),
     120  	OPT_HELP_COMPAT,
     121  	{ 0 }
     122  };
     123  
     124  static error_t parse_opt (int key, char *arg, struct argp_state *state)
     125  {
     126  	switch (key) {
     127  		case 't':
     128  			to_code = xstrdup (arg);
     129  			return 0;
     130  		case OPT_SUFFIX:
     131  			suffix = arg;
     132  			return 0;
     133  		case OPT_IN_PLACE:
     134  			in_place = true;
     135  			return 0;
     136  		case 'd':
     137  			debug_level = true;
     138  			return 0;
     139  		case 'q':
     140  			quiet = 1;
     141  			return 0;
     142  		case 'h':
     143  			argp_state_help (state, state->out_stream,
     144  					 ARGP_HELP_STD_HELP);
     145  			break;
     146  		case ARGP_KEY_ARG:
     147  			gl_list_add_last (filenames, xstrdup (arg));
     148  			return 0;
     149  		case ARGP_KEY_NO_ARGS:
     150  			argp_usage (state);
     151  			break;
     152  		case ARGP_KEY_SUCCESS:
     153  			if (!to_code)
     154  				argp_error (state,
     155  					    _("must specify an output "
     156  					      "encoding"));
     157  			if (!suffix && !in_place)
     158  				argp_error (state,
     159  					    _("must use either --suffix or "
     160  					      "--in-place"));
     161  			if (suffix && in_place)
     162  				argp_error (state,
     163  					    _("--suffix and --in-place are "
     164  					      "mutually exclusive"));
     165  			return 0;
     166  	}
     167  	return ARGP_ERR_UNKNOWN;
     168  }
     169  
     170  static struct argp argp = { options, parse_opt, args_doc };
     171  
     172  static void recode (const char *filename)
     173  {
     174  	decompress *decomp;
     175  	pipeline *convert, *decomp_p;
     176  	struct compression *comp;
     177  	int dir_fd = -1;
     178  	char *dirname, *basename, *stem, *outfilename;
     179  	char *page_encoding;
     180  	int status;
     181  
     182  	decomp = decompress_open (filename, 0);
     183  	if (!decomp)
     184  		error (FAIL, 0, _("can't open %s"), filename);
     185  
     186  	dirname = dir_name (filename);
     187  	basename = base_name (filename);
     188  	comp = comp_info (basename, true);
     189  	if (comp)
     190  		stem = comp->stem;	/* steal memory */
     191  	else
     192  		stem = xstrdup (basename);
     193  
     194  	convert = pipeline_new ();
     195  	if (suffix) {
     196  		outfilename = xasprintf ("%s/%s%s", dirname, stem, suffix);
     197  		pipeline_want_outfile (convert, outfilename);
     198  	} else {
     199  		int dir_fd_open_flags;
     200  		char *template_path;
     201  		int outfd;
     202  
     203  		dir_fd_open_flags = O_SEARCH | O_DIRECTORY;
     204  #ifdef O_PATH
     205  		dir_fd_open_flags |= O_PATH;
     206  #endif
     207  		dir_fd = open (dirname, dir_fd_open_flags);
     208  		if (dir_fd < 0)
     209  			fatal (errno, _("can't open %s"), dirname);
     210  
     211  		outfilename = xasprintf ("%s.XXXXXX", stem);
     212  		/* For error messages. */
     213  		template_path = xasprintf ("%s/%s", dirname, outfilename);
     214  		outfd = mkstempat (dir_fd, outfilename);
     215  		if (outfd == -1)
     216  			fatal (errno,
     217  			       _("can't open temporary file %s"),
     218  			       template_path);
     219  		free (template_path);
     220  		pipeline_want_out (convert, outfd);
     221  	}
     222  
     223  	decompress_start (decomp);
     224  	page_encoding = check_preprocessor_encoding (decomp, NULL, NULL);
     225  	if (!page_encoding) {
     226  		char *lang = lang_dir (filename);
     227  		page_encoding = get_page_encoding (lang);
     228  		free (lang);
     229  	}
     230  	debug ("guessed input encoding %s for %s\n", page_encoding, filename);
     231  	add_manconv (convert, page_encoding, to_code);
     232  
     233  	if (!pipeline_get_ncommands (convert))
     234  		pipeline_command (convert, pipecmd_new_passthrough ());
     235  
     236  	decomp_p = decompress_get_pipeline (decomp);
     237  	pipeline_connect (decomp_p, convert, (void *) 0);
     238  	pipeline_pump (decomp_p, convert, (void *) 0);
     239  	pipeline_wait (decomp_p);
     240  	status = pipeline_wait (convert);
     241  	if (status != 0)
     242  		error (CHILD_FAIL, 0, _("command exited with status %d: %s"),
     243  		       status, pipeline_tostring (convert));
     244  
     245  	if (in_place) {
     246  		assert (dir_fd != -1);
     247  		if (renameat (dir_fd, outfilename, dir_fd, stem) == -1) {
     248  			char *outfilepath = xasprintf
     249  				("%s/%s", dirname, outfilename);
     250  			unlink (outfilename);
     251  			fatal (errno, _("can't rename %s to %s"),
     252  			       outfilepath, filename);
     253  		}
     254  		debug ("stem: %s, basename: %s\n", stem, basename);
     255  		if (!STREQ (stem, basename)) {
     256  			if (unlinkat (dir_fd, basename, 0) == -1)
     257  				fatal (errno, _("can't remove %s"), filename);
     258  		}
     259  	}
     260  
     261  	free (page_encoding);
     262  	free (outfilename);
     263  	free (stem);
     264  	free (basename);
     265  	free (dirname);
     266  	if (dir_fd)
     267  		close (dir_fd);
     268  	pipeline_free (convert);
     269  	decompress_free (decomp);
     270  }
     271  
     272  int main (int argc, char *argv[])
     273  {
     274  	const char *filename;
     275  
     276  	set_program_name (argv[0]);
     277  
     278  	init_debug ();
     279  	pipeline_install_post_fork (pop_all_cleanups);
     280  	sandbox = sandbox_init ();
     281  	init_locale ();
     282  	filenames = new_string_list (GL_ARRAY_LIST, true);
     283  
     284  	if (argp_parse (&argp, argc, argv, 0, 0, 0))
     285  		exit (FAIL);
     286  
     287  	GL_LIST_FOREACH (filenames, filename)
     288  		recode (filename);
     289  
     290  	free (to_code);
     291  
     292  	gl_list_free (filenames);
     293  	sandbox_free (sandbox);
     294  
     295  	return 0;
     296  }