(root)/
man-db-2.12.0/
src/
mandb.c
       1  /*
       2   * mandb.c: used to create and initialise global man database.
       3   *
       4   * Copyright (C) 1994, 1995 Graeme W. Wilford. (Wilf.)
       5   * Copyright (C) 2001, 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011,
       6   *               2012 Colin Watson.
       7   *
       8   * This file is part of man-db.
       9   *
      10   * man-db is free software; you can redistribute it and/or modify it
      11   * under the terms of the GNU General Public License as published by
      12   * the Free Software Foundation; either version 2 of the License, or
      13   * (at your option) any later version.
      14   *
      15   * man-db is distributed in the hope that it will be useful, but
      16   * WITHOUT ANY WARRANTY; without even the implied warranty of
      17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      18   * GNU General Public License for more details.
      19   *
      20   * You should have received a copy of the GNU General Public License
      21   * along with man-db; if not, write to the Free Software Foundation,
      22   * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      23   *
      24   * Tue Apr 26 12:56:44 BST 1994  Wilf. (G.Wilford@ee.surrey.ac.uk)
      25   *
      26   * CJW: Security fixes. Make --test work. Purge old database entries.
      27   */
      28  
      29  #ifdef HAVE_CONFIG_H
      30  #  include "config.h"
      31  #endif /* HAVE_CONFIG_H */
      32  
      33  #include <stdbool.h>
      34  #include <string.h>
      35  #include <stdlib.h>
      36  #include <stdio.h>
      37  #include <assert.h>
      38  #include <errno.h>
      39  #include <sys/types.h>
      40  #include <sys/stat.h>	/* for chmod() */
      41  #include <dirent.h>
      42  #include <fcntl.h>
      43  #include <unistd.h>
      44  #include <signal.h>
      45  
      46  #ifdef MAN_OWNER
      47  #  include <pwd.h>
      48  #endif /* MAN_OWNER */
      49  
      50  #include "argp.h"
      51  #include "dirname.h"
      52  #include "error.h"
      53  #include "gl_hash_map.h"
      54  #include "gl_list.h"
      55  #include "gl_xmap.h"
      56  #include "progname.h"
      57  #include "stat-time.h"
      58  #include "timespec.h"
      59  #include "utimens.h"
      60  #include "xalloc.h"
      61  #include "xgetcwd.h"
      62  #include "xvasprintf.h"
      63  
      64  #include "gettext.h"
      65  #define _(String) gettext (String)
      66  #define N_(String) gettext_noop (String)
      67  
      68  #include "manconfig.h"
      69  
      70  #include "cleanup.h"
      71  #include "debug.h"
      72  #include "filenames.h"
      73  #include "glcontainers.h"
      74  #include "pipeline.h"
      75  #include "sandbox.h"
      76  #include "security.h"
      77  #include "util.h"
      78  
      79  #include "db_storage.h"
      80  #include "mydbm.h"
      81  
      82  #include "check_mandirs.h"
      83  #include "manp.h"
      84  #include "straycats.h"
      85  
      86  int quiet = 1;
      87  extern bool opt_test;		/* don't update db */
      88  char *manp;
      89  extern char *extension;		/* for globbing.c */
      90  extern bool force_rescan;	/* for check_mandirs.c */
      91  static char *single_filename = NULL;
      92  extern char *user_config_file;	/* for manp.c */
      93  #ifdef MAN_OWNER
      94  struct passwd *man_owner;
      95  #endif
      96  man_sandbox *sandbox;
      97  
      98  static int purged = 0;
      99  static int strays = 0;
     100  
     101  static bool check_for_strays = true;
     102  static bool purge = true;
     103  static bool user;
     104  static bool create;
     105  static const char *arg_manp;
     106  
     107  struct tried_catdirs_entry {
     108  	char *manpath;
     109  	bool seen;
     110  };
     111  
     112  const char *argp_program_version = "mandb " PACKAGE_VERSION;
     113  const char *argp_program_bug_address = PACKAGE_BUGREPORT;
     114  error_t argp_err_exit_status = FAIL;
     115  
     116  static const char args_doc[] = N_("[MANPATH]");
     117  
     118  static struct argp_option options[] = {
     119  	OPT ("debug", 'd', 0, N_("emit debugging messages")),
     120  	OPT ("quiet", 'q', 0, N_("work quietly, except for 'bogus' warning")),
     121  	OPT ("no-straycats", 's', 0,
     122  	     N_("don't look for or add stray cats to the dbs")),
     123  	OPT ("no-purge", 'p', 0,
     124  	     N_("don't purge obsolete entries from the dbs")),
     125  	OPT ("user-db", 'u', 0, N_("produce user databases only")),
     126  	OPT ("create", 'c', 0,
     127  	     N_("create dbs from scratch, rather than updating")),
     128  	OPT ("test", 't', 0, N_("check manual pages for correctness")),
     129  	OPT ("filename", 'f', N_("FILENAME"),
     130  	     N_("update just the entry for this filename")),
     131  	OPT ("config-file", 'C', N_("FILE"),
     132  	     N_("use this user configuration file")),
     133  	OPT_HELP_COMPAT,
     134  	{ 0 }
     135  };
     136  
     137  static error_t parse_opt (int key, char *arg, struct argp_state *state)
     138  {
     139  	static int quiet_temp = 0;
     140  
     141  	switch (key) {
     142  		case 'd':
     143  			debug_level = true;
     144  			return 0;
     145  		case 'q':
     146  			++quiet_temp;
     147  			return 0;
     148  		case 's':
     149  			check_for_strays = false;
     150  			return 0;
     151  		case 'p':
     152  			purge = false;
     153  			return 0;
     154  		case 'u':
     155  			user = true;
     156  			return 0;
     157  		case 'c':
     158  			create = true;
     159  			purge = false;
     160  			return 0;
     161  		case 't':
     162  			opt_test = true;
     163  			return 0;
     164  		case 'f':
     165  			single_filename = arg;
     166  			create = false;
     167  			purge = false;
     168  			check_for_strays = false;
     169  			return 0;
     170  		case 'C':
     171  			user_config_file = arg;
     172  			return 0;
     173  		case 'h':
     174  			argp_state_help (state, state->out_stream,
     175  					 ARGP_HELP_STD_HELP);
     176  			break;
     177  		case ARGP_KEY_ARG:
     178  			if (arg_manp)
     179  				argp_usage (state);
     180  			arg_manp = arg;
     181  			return 0;
     182  		case ARGP_KEY_SUCCESS:
     183  			if (opt_test && !debug_level)
     184  				quiet = 1;
     185  			else if (quiet_temp == 1)
     186  				quiet = 2;
     187  			else
     188  				quiet = quiet_temp;
     189  			return 0;
     190  	}
     191  	return ARGP_ERR_UNKNOWN;
     192  }
     193  
     194  static struct argp argp = { options, parse_opt, args_doc };
     195  
     196  struct dbpaths {
     197  #ifdef NDBM
     198  #  ifdef BERKELEY_DB
     199  	char *dbfile;
     200  	char *tmpdbfile;
     201  #  else /* !BERKELEY_DB NDBM */
     202  	char *dirfile;
     203  	char *pagfile;
     204  	char *tmpdirfile;
     205  	char *tmppagfile;
     206  #  endif /* BERKELEY_DB */
     207  #else /* !NDBM */
     208  	char *xfile;
     209  	char *xtmpfile;
     210  #endif /* NDBM */
     211  };
     212  
     213  #ifdef MAN_OWNER
     214  extern uid_t ruid;
     215  extern uid_t euid;
     216  #endif /* MAN_OWNER */
     217  
     218  static gl_list_t manpathlist;
     219  
     220  extern int pages;
     221  
     222  /* remove() with error checking */
     223  static void check_remove (const char *path)
     224  {
     225  	if (remove (path) == -1 && errno != ENOENT)
     226  		error (0, errno, _("can't remove %s"), path);
     227  }
     228  
     229  /* rename() with error checking */
     230  static void check_rename (const char *from, const char *to)
     231  {
     232  	if (rename (from, to) == -1 && errno != ENOENT) {
     233  		error (0, errno, _("can't rename %s to %s"), from, to);
     234  		check_remove (from);
     235  	}
     236  }
     237  
     238  /* chmod() with error checking */
     239  static void check_chmod (const char *path, mode_t mode)
     240  {
     241  	if (chmod (path, mode) == -1) {
     242  		error (0, errno, _("can't chmod %s"), path);
     243  		check_remove (path);
     244  	}
     245  }
     246  
     247  /* CPhipps 2000/02/24 - Copy a file. */
     248  static int xcopy (const char *from, const char *to)
     249  {
     250  	FILE *ifp, *ofp;
     251  	struct stat st;
     252  	struct timespec times[2];
     253  	static const size_t buf_size = 32 * 1024;
     254  	char *buf;
     255  	int ret = 0;
     256  
     257  	ifp = fopen (from, "r");
     258  	if (!ifp) {
     259  		ret = -errno;
     260  		if (errno == ENOENT)
     261  			return 0;
     262  		error (0, errno, "fopen %s", from);
     263  		return ret;
     264  	}
     265  
     266  	if (fstat (fileno (ifp), &st) >= 0) {
     267  		times[0] = get_stat_atime (&st);
     268  		times[1] = get_stat_mtime (&st);
     269  	} else {
     270  		times[0].tv_sec = 0;
     271  		times[0].tv_nsec = UTIME_OMIT;
     272  		times[1].tv_sec = 0;
     273  		times[1].tv_nsec = UTIME_OMIT;
     274  	}
     275  
     276  	ofp = fopen (to, "w");
     277  	if (!ofp) {
     278  		ret = -errno;
     279  		error (0, errno, "fopen %s", to);
     280  		fclose (ifp);
     281  		return ret;
     282  	}
     283  
     284  	buf = xmalloc (buf_size);
     285  	while (!feof (ifp) && !ferror (ifp)) {
     286  		size_t in = fread (buf, 1, buf_size, ifp);
     287  		if (in > 0) {
     288  			if (fwrite (buf, 1, in, ofp) == 0 && ferror (ofp)) {
     289  				ret = -errno;
     290  				error (0, errno, _("can't write to %s"), to);
     291  				break;
     292  			}
     293  		} else if (ferror (ifp)) {
     294  			ret = -errno;
     295  			error (0, errno, _("can't read from %s"), from);
     296  			break;
     297  		}
     298  	}
     299  	free (buf);
     300  
     301  	fclose (ifp);
     302  	fclose (ofp);
     303  
     304  	if (ret < 0)
     305  		check_remove (to);
     306  	else {
     307  		check_chmod (to, DBMODE);
     308  		utimens (to, times);
     309  	}
     310  
     311  	return ret;
     312  }
     313  
     314  static void dbpaths_init (struct dbpaths *dbpaths,
     315  			  const char *base, const char *tmpbase)
     316  {
     317  #ifdef NDBM
     318  #  ifdef BERKELEY_DB
     319  	dbpaths->dbfile = xasprintf ("%s.db", base);
     320  	dbpaths->tmpdbfile = xasprintf ("%s.db", tmpbase);
     321  #  else /* !BERKELEY_DB NDBM */
     322  	dbpaths->dirfile = xasprintf ("%s.dir", base);
     323  	dbpaths->pagfile = xasprintf ("%s.pag", base);
     324  	dbpaths->tmpdirfile = xasprintf ("%s.dir", tmpbase);
     325  	dbpaths->tmppagfile = xasprintf ("%s.pag", tmpbase);
     326  #  endif /* BERKELEY_DB NDBM */
     327  #else /* !NDBM */
     328  	dbpaths->xfile = xstrdup (base);
     329  	dbpaths->xtmpfile = xstrdup (tmpbase);
     330  #endif /* NDBM */
     331  }
     332  
     333  static int dbpaths_copy_to_tmp (struct dbpaths *dbpaths)
     334  {
     335  #ifdef NDBM
     336  #  ifdef BERKELEY_DB
     337  	return xcopy (dbpaths->dbfile, dbpaths->tmpdbfile);
     338  #  else /* !BERKELEY_DB NDBM */
     339  	int ret = xcopy (dbpaths->dirfile, dbpaths->tmpdirfile);
     340  	if (ret < 0)
     341  		return ret;
     342  	return xcopy (dbpaths->pagfile, dbpaths->tmppagfile);
     343  #  endif /* BERKELEY_DB NDBM */
     344  #else /* !NDBM */
     345  	return xcopy (dbpaths->xfile, dbpaths->xtmpfile);
     346  #endif /* NDBM */
     347  }
     348  
     349  static void dbpaths_remove_tmp (struct dbpaths *dbpaths)
     350  {
     351  #ifdef NDBM
     352  #  ifdef BERKELEY_DB
     353  	check_remove (dbpaths->tmpdbfile);
     354  #  else /* !BERKELEY_DB NDBM */
     355  	check_remove (dbpaths->tmpdirfile);
     356  	check_remove (dbpaths->tmppagfile);
     357  #  endif /* BERKELEY_DB NDBM */
     358  #else /* !NDBM */
     359  	check_remove (dbpaths->xtmpfile);
     360  #endif /* NDBM */
     361  }
     362  
     363  static void dbpaths_rename_from_tmp (struct dbpaths *dbpaths)
     364  {
     365  #ifdef NDBM
     366  #  ifdef BERKELEY_DB
     367  	check_rename (dbpaths->tmpdbfile, dbpaths->dbfile);
     368  	check_chmod (dbpaths->dbfile, DBMODE);
     369  	free (dbpaths->tmpdbfile);
     370  	dbpaths->tmpdbfile = NULL;
     371  #  else /* not BERKELEY_DB */
     372  	check_rename (dbpaths->tmpdirfile, dbpaths->dirfile);
     373  	check_chmod (dbpaths->dirfile, DBMODE);
     374  	check_rename (dbpaths->tmppagfile, dbpaths->pagfile);
     375  	check_chmod (dbpaths->pagfile, DBMODE);
     376  	free (dbpaths->tmpdirfile);
     377  	free (dbpaths->tmppagfile);
     378  	dbpaths->tmpdirfile = dbpaths->tmppagfile = NULL;
     379  #  endif /* BERKELEY_DB */
     380  #else /* not NDBM */
     381  	check_rename (dbpaths->xtmpfile, dbpaths->xfile);
     382  	check_chmod (dbpaths->xfile, DBMODE);
     383  	free (dbpaths->xtmpfile);
     384  	dbpaths->xtmpfile = NULL;
     385  #endif /* NDBM */
     386  }
     387  
     388  #ifdef MAN_OWNER
     389  /* Change the owner of global man databases. */
     390  static void dbpaths_chown_if_possible (struct dbpaths *dbpaths)
     391  {
     392  #  ifdef NDBM
     393  #    ifdef BERKELEY_DB
     394  	chown_if_possible (dbpaths->dbfile);
     395  #    else /* not BERKELEY_DB */
     396  	chown_if_possible (dbpaths->dirfile);
     397  	chown_if_possible (dbpaths->pagfile);
     398  #    endif /* BERKELEY_DB */
     399  #  else /* not NDBM */
     400  	chown_if_possible (dbpaths->xfile);
     401  #  endif /* NDBM */
     402  }
     403  #endif /* MAN_OWNER */
     404  
     405  /* Remove incomplete databases.  This is async-signal-safe. */
     406  static void dbpaths_unlink_tmp (struct dbpaths *dbpaths)
     407  {
     408  #ifdef NDBM
     409  #  ifdef BERKELEY_DB
     410  	if (dbpaths->tmpdbfile)
     411  		unlink (dbpaths->tmpdbfile);
     412  #  else /* !BERKELEY_DB NDBM */
     413  	if (dbpaths->tmpdirfile)
     414  		unlink (dbpaths->tmpdirfile);
     415  	if (dbpaths->tmppagfile)
     416  		unlink (dbpaths->tmppagfile);
     417  #  endif /* BERKELEY_DB NDBM */
     418  #else /* !NDBM */
     419  	if (dbpaths->xtmpfile)
     420  		unlink (dbpaths->xtmpfile);
     421  #endif /* NDBM */
     422  }
     423  
     424  static void dbpaths_free_elements (struct dbpaths *dbpaths)
     425  {
     426  #ifdef NDBM
     427  #  ifdef BERKELEY_DB
     428  	free (dbpaths->dbfile);
     429  	free (dbpaths->tmpdbfile);
     430  	dbpaths->dbfile = dbpaths->tmpdbfile = NULL;
     431  #  else /* !BERKELEY_DB NDBM */
     432  	free (dbpaths->dirfile);
     433  	free (dbpaths->pagfile);
     434  	free (dbpaths->tmpdirfile);
     435  	free (dbpaths->tmppagfile);
     436  	dbpaths->dirfile = dbpaths->pagfile = NULL;
     437  	dbpaths->tmpdirfile = dbpaths->tmppagfile = NULL;
     438  #  endif /* BERKELEY_DB NDBM */
     439  #else /* !NDBM */
     440  	free (dbpaths->xfile);
     441  	free (dbpaths->xtmpfile);
     442  	dbpaths->xfile = dbpaths->xtmpfile = NULL;
     443  #endif /* NDBM */
     444  }
     445  
     446  /* Reorganize a database by reading in all the items (assuming that the
     447   * database layer provides them in sorted order) and writing them back out.
     448   * This has the effect of giving the underlying database the best chance to
     449   * produce deterministic output files based only on the set of items and not
     450   * on their insertion order, although we may not be able to guarantee that
     451   * for all database types.
     452   */
     453  static void reorganize (const char *catpath, bool global_manpath MAYBE_UNUSED)
     454  {
     455  	char *dbname, *tmpdbname;
     456  	struct dbpaths *dbpaths;
     457  	MYDBM_FILE dbf, tmpdbf;
     458  	datum key;
     459  
     460  	dbname = mkdbname (catpath);
     461  	tmpdbname = xasprintf ("%s/%d", catpath, getpid ());
     462  	dbpaths = XZALLOC (struct dbpaths);
     463  	dbpaths_init (dbpaths, dbname, tmpdbname);
     464  	dbf = MYDBM_NEW (dbname);
     465  	tmpdbf = MYDBM_NEW (tmpdbname);
     466  	if (!MYDBM_RDOPEN (dbf) || dbver_rd (dbf)) {
     467  		debug ("Failed to open %s read-only\n", dbname);
     468  		goto out;
     469  	}
     470  	if (!MYDBM_CTRWOPEN (tmpdbf)) {
     471  		debug ("Failed to create %s\n", tmpdbname);
     472  		goto out;
     473  	}
     474  
     475  	key = MYDBM_FIRSTKEY (dbf);
     476  	while (MYDBM_DPTR (key)) {
     477  		datum content, nextkey;
     478  		int insert_status;
     479  
     480  		content = MYDBM_FETCH (dbf, key);
     481  		insert_status = MYDBM_INSERT (tmpdbf, key, content);
     482  		MYDBM_FREE_DPTR (content);
     483  		if (insert_status != 0) {
     484  			MYDBM_FREE_DPTR (key);
     485  			goto out;
     486  		}
     487  		nextkey = MYDBM_NEXTKEY (dbf, key);
     488  		MYDBM_FREE_DPTR (key);
     489  		key = nextkey;
     490  	}
     491  
     492  	dbpaths_rename_from_tmp (dbpaths);
     493  #ifdef MAN_OWNER
     494  	if (global_manpath)
     495  		dbpaths_chown_if_possible (dbpaths);
     496  #endif /* MAN_OWNER */
     497  
     498  out:
     499  	MYDBM_FREE (tmpdbf);
     500  	MYDBM_FREE (dbf);
     501  	dbpaths_unlink_tmp (dbpaths);
     502  	dbpaths_free_elements (dbpaths);
     503  	free (dbpaths);
     504  	free (tmpdbname);
     505  	free (dbname);
     506  }
     507  
     508  /* Update a single file in an existing database. */
     509  static int update_one_file (MYDBM_FILE dbf,
     510  			    const char *manpath, const char *filename)
     511  {
     512  	if (dbf->file || MYDBM_RWOPEN (dbf)) {
     513  		struct mandata *info;
     514  
     515  		info = filename_info (filename, quiet < 2);
     516  		if (info) {
     517  			dbdelete (dbf, info->name, info);
     518  			purge_pointers (dbf, info->name);
     519  		}
     520  		free_mandata_struct (info);
     521  
     522  		test_manfile (dbf, filename, manpath);
     523  	}
     524  
     525  	return 1;
     526  }
     527  
     528  /* dont actually create any dbs, just do an update */
     529  static int update_db_wrapper (MYDBM_FILE dbf,
     530  			      const char *manpath, const char *catpath)
     531  {
     532  	int amount;
     533  
     534  	if (single_filename)
     535  		return update_one_file (dbf, manpath, single_filename);
     536  
     537  	amount = update_db (dbf, manpath, catpath);
     538  	if (amount >= 0)
     539  		return amount;
     540  
     541  	return create_db (dbf, manpath, catpath);
     542  }
     543  
     544  #define CACHEDIR_TAG \
     545  	"Signature: 8a477f597d28d172789f06886806bc55\n" \
     546  	"# This file is a cache directory tag created by man-db.\n" \
     547  	"# For information about cache directory tags, see:\n" \
     548  	"#\thttp://www.brynosaurus.com/cachedir/\n"
     549  
     550  /* sort out the database names */
     551  static int mandb (struct dbpaths *dbpaths,
     552  		  const char *catpath, const char *manpath,
     553  		  bool global_manpath)
     554  {
     555  	char *database;
     556  	int amount;
     557  	char *dbname;
     558  	MYDBM_FILE dbf;
     559  	bool should_create;
     560  
     561  	dbname = mkdbname (catpath);
     562  	database = xasprintf ("%s/%d", catpath, getpid ());
     563  	dbf = MYDBM_NEW (database);
     564  
     565  	if (!STREQ (catpath, manpath)) {
     566  		char *cachedir_tag;
     567  		int fd;
     568  		bool cachedir_tag_exists = false;
     569  
     570  		cachedir_tag = xasprintf ("%s/CACHEDIR.TAG", catpath);
     571  		assert (cachedir_tag);
     572  		fd = open (cachedir_tag, O_RDONLY);
     573  		if (fd < 0) {
     574  			FILE *cachedir_tag_file;
     575  
     576  			if (errno != ENOENT)
     577  				check_remove (cachedir_tag);
     578  			cachedir_tag_file = fopen (cachedir_tag, "w");
     579  			if (cachedir_tag_file) {
     580  				cachedir_tag_exists = true;
     581  				fputs (CACHEDIR_TAG, cachedir_tag_file);
     582  				fclose (cachedir_tag_file);
     583  			}
     584  		} else {
     585  			cachedir_tag_exists = true;
     586  			close (fd);
     587  		}
     588  		if (cachedir_tag_exists) {
     589  			if (global_manpath)
     590  				chown_if_possible (cachedir_tag);
     591  			check_chmod (cachedir_tag, DBMODE);
     592  		}
     593  		free (cachedir_tag);
     594  	}
     595  
     596  	should_create = (create || opt_test);
     597  
     598  	dbpaths_init (dbpaths, dbname, database);
     599  	if (!should_create && dbpaths_copy_to_tmp (dbpaths) < 0)
     600  		should_create = true;
     601  	if (should_create)
     602  		dbpaths_remove_tmp (dbpaths);
     603  
     604  	if (!should_create) {
     605  		force_rescan = false;
     606  		if (purge)
     607  			purged += purge_missing (dbf, manpath, catpath);
     608  
     609  		if (force_rescan) {
     610  			/* We have an existing database and hadn't been
     611  			 * going to recreate it, but purge_missing has
     612  			 * discovered some kind of consistency problem and
     613  			 * requested that we do so anyway.  Close the
     614  			 * database and remove temporary copies so that we
     615  			 * start from scratch.
     616  			 */
     617  			MYDBM_FREE (dbf);
     618  			dbpaths_remove_tmp (dbpaths);
     619  			dbf = MYDBM_NEW (database);
     620  			should_create = true;
     621  		}
     622  	}
     623  
     624  	if (!quiet)
     625  		printf (_("Processing manual pages under %s...\n"), manpath);
     626  
     627  	if (should_create)
     628  		amount = create_db (dbf, manpath, catpath);
     629  	else
     630  		amount = update_db_wrapper (dbf, manpath, catpath);
     631  
     632  	if (check_for_strays && dbf->file)
     633  		strays += straycats (dbf, manpath);
     634  
     635  	MYDBM_FREE (dbf);
     636  	free (database);
     637  	free (dbname);
     638  	return amount;
     639  }
     640  
     641  static int process_manpath (const char *manpath, bool global_manpath,
     642  			    gl_map_t tried_catdirs)
     643  {
     644  	char *catpath;
     645  	struct tried_catdirs_entry *tried;
     646  	struct stat st;
     647  	bool run_mandb = false;
     648  	struct dbpaths *dbpaths = NULL;
     649  	int amount = 0;
     650  	bool new_purged = false;
     651  	bool new_strays = false;
     652  
     653  	if (global_manpath) { 	/* system db */
     654  		catpath = get_catpath (manpath, SYSTEM_CAT);
     655  		assert (catpath);
     656  	} else {		/* user db */
     657  		catpath = get_catpath (manpath, USER_CAT);
     658  		if (!catpath)
     659  			catpath = xstrdup (manpath);
     660  	}
     661  	tried = XMALLOC (struct tried_catdirs_entry);
     662  	tried->manpath = xstrdup (manpath);
     663  	tried->seen = false;
     664  	gl_map_put (tried_catdirs, xstrdup (catpath), tried);
     665  
     666  	if (stat (manpath, &st) < 0 || !S_ISDIR (st.st_mode))
     667  		goto out;
     668  	tried->seen = true;
     669  
     670  	if (single_filename) {
     671  		/* The file might be in a per-locale subdirectory that we
     672  		 * aren't processing right now.
     673  		 */
     674  		char *manpath_prefix = xasprintf ("%s/man", manpath);
     675  		if (STRNEQ (manpath_prefix, single_filename,
     676  		    strlen (manpath_prefix)))
     677  			run_mandb = true;
     678  		free (manpath_prefix);
     679  	} else
     680  		run_mandb = true;
     681  
     682  	dbpaths = XZALLOC (struct dbpaths);
     683  	push_cleanup ((cleanup_fun) dbpaths_free_elements, dbpaths, 0);
     684  	push_cleanup ((cleanup_fun) dbpaths_unlink_tmp, dbpaths, 1);
     685  	if (run_mandb) {
     686  		int purged_before = purged;
     687  		int strays_before = strays;
     688  		int ret = mandb (dbpaths, catpath, manpath, global_manpath);
     689  		if (ret < 0) {
     690  			amount = ret;
     691  			goto out;
     692  		}
     693  		amount += ret;
     694  		new_purged = purged != purged_before;
     695  		new_strays = strays != strays_before;
     696  
     697  		if (!opt_test && (amount || new_purged || new_strays)) {
     698  			dbpaths_rename_from_tmp (dbpaths);
     699  #ifdef MAN_OWNER
     700  			if (global_manpath)
     701  				dbpaths_chown_if_possible (dbpaths);
     702  #endif /* MAN_OWNER */
     703  			reorganize (catpath, global_manpath);
     704  		}
     705  	}
     706  
     707  out:
     708  	if (dbpaths) {
     709  		dbpaths_unlink_tmp (dbpaths);
     710  		pop_cleanup ((cleanup_fun) dbpaths_unlink_tmp, dbpaths);
     711  		dbpaths_free_elements (dbpaths);
     712  		pop_cleanup ((cleanup_fun) dbpaths_free_elements, dbpaths);
     713  		free (dbpaths);
     714  	}
     715  
     716  	free (catpath);
     717  
     718  	return amount;
     719  }
     720  
     721  static bool is_lang_dir (const char *base)
     722  {
     723  	return strlen (base) >= 2 &&
     724  	       base[0] >= 'a' && base[0] <= 'z' &&
     725  	       base[1] >= 'a' && base[1] <= 'z' &&
     726  	       (!base[2] || base[2] < 'a' || base[2] > 'z');
     727  }
     728  
     729  static void tried_catdirs_free (const void *value)
     730  {
     731  	struct tried_catdirs_entry *tried =
     732  		(struct tried_catdirs_entry *) value;
     733  
     734  	free (tried->manpath);
     735  	free (tried);
     736  }
     737  
     738  static void purge_catdir (gl_map_t tried_catdirs, const char *path)
     739  {
     740  	struct stat st;
     741  
     742  	if (stat (path, &st) == 0 && S_ISDIR (st.st_mode) &&
     743  	    !gl_map_get (tried_catdirs, path)) {
     744  		if (!quiet)
     745  			printf (_("Removing obsolete cat directory %s...\n"),
     746  				path);
     747  		remove_directory (path, true);
     748  	}
     749  }
     750  
     751  static void purge_catsubdirs (const char *manpath, const char *catpath)
     752  {
     753  	DIR *dir;
     754  	struct dirent *ent;
     755  	struct stat st;
     756  
     757  	dir = opendir (catpath);
     758  	if (!dir)
     759  		return;
     760  	while ((ent = readdir (dir)) != NULL) {
     761  		char *mandir, *catdir;
     762  
     763  		if (!STRNEQ (ent->d_name, "cat", 3))
     764  			continue;
     765  
     766  		mandir = xasprintf ("%s/man%s", manpath, ent->d_name + 3);
     767  		assert (mandir);
     768  		catdir = xasprintf ("%s/%s", catpath, ent->d_name);
     769  		assert (catdir);
     770  
     771  		if (stat (mandir, &st) != 0 && errno == ENOENT) {
     772  			if (!quiet)
     773  				printf (_("Removing obsolete cat directory "
     774  					  "%s...\n"), catdir);
     775  			remove_directory (catdir, true);
     776  		}
     777  
     778  		free (catdir);
     779  		free (mandir);
     780  	}
     781  	closedir (dir);
     782  }
     783  
     784  /* Remove catdirs whose corresponding mandirs no longer exist.  For safety,
     785   * in case people set catdirs to silly locations, we only do this for the
     786   * cat* and NLS subdirectories of catdirs, but not for the top-level catdir
     787   * itself (which might contain other data, or which might be difficult for
     788   * mandb to recreate with the proper permissions).
     789   *
     790   * We need to be careful here to avoid removing catdirs just because we
     791   * happened not to inspect the corresponding mandir this time round.  If a
     792   * mandir was inspected and turned out not to exist, then its catdir is
     793   * clearly fair game for removal of NLS subdirectories.  These must match
     794   * the usual NLS pattern (two lower-case letters followed by nothing or a
     795   * non-letter).
     796   */
     797  static void purge_catdirs (gl_map_t tried_catdirs)
     798  {
     799  	const char *path;
     800  	struct tried_catdirs_entry *tried;
     801  
     802  	GL_MAP_FOREACH (tried_catdirs, path, tried) {
     803  		char *base;
     804  		DIR *dir;
     805  		struct dirent *subdirent;
     806  
     807  		base = base_name (path);
     808  		if (is_lang_dir (base)) {
     809  			/* expect to check this as a subdirectory later */
     810  			free (base);
     811  			continue;
     812  		}
     813  		free (base);
     814  
     815  		purge_catsubdirs (tried->manpath, path);
     816  
     817  		dir = opendir (path);
     818  		if (!dir)
     819  			continue;
     820  		while ((subdirent = readdir (dir)) != NULL) {
     821  			char *subdirpath;
     822  			const struct tried_catdirs_entry *subtried;
     823  
     824  			if (STREQ (subdirent->d_name, ".") ||
     825  			    STREQ (subdirent->d_name, ".."))
     826  				continue;
     827  			if (STRNEQ (subdirent->d_name, "cat", 3))
     828  				continue;
     829  			if (!is_lang_dir (subdirent->d_name))
     830  				continue;
     831  
     832  			subdirpath = xasprintf ("%s/%s", path,
     833  					        subdirent->d_name);
     834  
     835  			subtried = gl_map_get (tried_catdirs, subdirpath);
     836  			if (subtried && subtried->seen) {
     837  				debug ("Seen mandir for %s; not deleting\n",
     838  				       subdirpath);
     839  				/* However, we may still need to purge cat*
     840  				 * subdirectories.
     841  				 */
     842  				purge_catsubdirs (subtried->manpath,
     843  						  subdirpath);
     844  			} else
     845  				purge_catdir (tried_catdirs, subdirpath);
     846  
     847  			free (subdirpath);
     848  		}
     849  		closedir (dir);
     850  	}
     851  }
     852  
     853  int main (int argc, char *argv[])
     854  {
     855  	char *sys_manp;
     856  	int amount = 0;
     857  	char *mp;
     858  	gl_map_t tried_catdirs;
     859  	struct sigaction sa;
     860  
     861  #ifdef __profile__
     862  	char *cwd;
     863  #endif /* __profile__ */
     864  
     865  	set_program_name (argv[0]);
     866  
     867  	init_debug ();
     868  	pipeline_install_post_fork (pop_all_cleanups);
     869  	sandbox = sandbox_init ();
     870  	init_locale ();
     871  
     872  	/* Reset SIGPIPE to its default disposition.  Too many broken pieces
     873  	 * of software (Python << 3.2, gnome-session, etc.) spawn child
     874  	 * processes with SIGPIPE ignored, and this produces noise in cron
     875  	 * mail.
     876  	 */
     877  	memset (&sa, 0, sizeof sa);
     878  	sa.sa_handler = SIG_DFL;
     879  	sigemptyset (&sa.sa_mask);
     880  	sa.sa_flags = 0;
     881  	sigaction (SIGPIPE, &sa, NULL);
     882  
     883  	if (argp_parse (&argp, argc, argv, 0, 0, 0))
     884  		exit (FAIL);
     885  
     886  #ifdef __profile__
     887  	cwd = xgetcwd ();
     888  	if (!cwd) {
     889  		cwd = xmalloc (1);
     890  		cwd[0] = '\0';
     891  	}
     892  #endif /* __profile__ */
     893  
     894  	/* record who we are and drop effective privs for later use */
     895  	init_security ();
     896  
     897  #ifdef MAN_OWNER
     898  	man_owner = get_man_owner ();
     899  	if (!user && euid != 0 && euid != man_owner->pw_uid) {
     900  		user = true;
     901  		if (!quiet)
     902  			fprintf (stderr,
     903  				 _("Only the '%s' user can create or update "
     904  				   "system-wide databases; acting as if the "
     905  				   "--user-db option was used.\n"),
     906  				 man_owner->pw_name);
     907  	}
     908  #endif /* MAN_OWNER */
     909  
     910  	read_config_file (user);
     911  
     912  	/* This is required for get_catpath(), regardless */
     913  	manp = get_manpath (NULL);	/* also calls read_config_file() */
     914  
     915  	/* pick up the system manpath or use the supplied one */
     916  	if (arg_manp) {
     917  		free (manp);
     918  		manp = xstrdup (arg_manp);
     919  	} else if (!user) {
     920  		sys_manp = get_mandb_manpath ();
     921  		if (sys_manp) {
     922  			free (manp);
     923  			manp = sys_manp;
     924  		} else
     925  			error (0, 0,
     926  			       _("warning: no MANDB_MAP directives in %s, "
     927  				 "using your manpath"),
     928  			       CONFIG_FILE);
     929  	}
     930  
     931  	/* get the manpath as a list of pointers */
     932  	manpathlist = create_pathlist (manp);
     933  
     934  	/* finished manpath processing, regain privs */
     935  	regain_effective_privs ();
     936  
     937  	tried_catdirs = new_string_map (GL_HASH_MAP, tried_catdirs_free);
     938  
     939  	GL_LIST_FOREACH (manpathlist, mp) {
     940  		bool global_manpath = is_global_mandir (mp);
     941  		int ret;
     942  		DIR *dir;
     943  		struct dirent *subdirent;
     944  
     945  		if (global_manpath) {	/* system db */
     946  			if (user)
     947  				continue;
     948  		} else {		/* user db */
     949  			drop_effective_privs ();
     950  		}
     951  
     952  		ret = process_manpath (mp, global_manpath, tried_catdirs);
     953  		if (ret < 0)
     954  			exit (FATAL);
     955  		amount += ret;
     956  
     957  		dir = opendir (mp);
     958  		if (!dir) {
     959  			error (0, errno, _("can't search directory %s"), mp);
     960  			goto next_manpath;
     961  		}
     962  
     963  		while ((subdirent = readdir (dir)) != NULL) {
     964  			char *subdirpath;
     965  
     966  			/* Look for per-locale subdirectories. */
     967  			if (STREQ (subdirent->d_name, ".") ||
     968  			    STREQ (subdirent->d_name, ".."))
     969  				continue;
     970  			if (STRNEQ (subdirent->d_name, "man", 3))
     971  				continue;
     972  
     973  			subdirpath = xasprintf ("%s/%s", mp,
     974  						subdirent->d_name);
     975  			assert (subdirpath);
     976  			ret = process_manpath (subdirpath, global_manpath,
     977  					       tried_catdirs);
     978  			if (ret < 0)
     979  				exit (FATAL);
     980  			amount += ret;
     981  			free (subdirpath);
     982  		}
     983  
     984  		closedir (dir);
     985  
     986  next_manpath:
     987  		if (!global_manpath)
     988  			regain_effective_privs ();
     989  	}
     990  
     991  	purge_catdirs (tried_catdirs);
     992  	gl_map_free (tried_catdirs);
     993  
     994  	if (!quiet) {
     995  		printf (ngettext ("%d man subdirectory contained newer "
     996  				  "manual pages.\n",
     997  				  "%d man subdirectories contained newer "
     998  				  "manual pages.\n", amount),
     999  			amount);
    1000  		printf (ngettext ("%d manual page was added.\n",
    1001  				  "%d manual pages were added.\n", pages),
    1002  			pages);
    1003  		if (check_for_strays)
    1004  			printf (ngettext ("%d stray cat was added.\n",
    1005  					  "%d stray cats were added.\n",
    1006  					  strays),
    1007  			        strays);
    1008  		if (purge)
    1009  			printf (ngettext ("%d old database entry was "
    1010  					  "purged.\n",
    1011  					  "%d old database entries were "
    1012  					  "purged.\n", purged),
    1013  				purged);
    1014  	}
    1015  
    1016  #ifdef __profile__
    1017  	/* For profiling */
    1018  	if (cwd[0])
    1019  		chdir (cwd);
    1020  #endif /* __profile__ */
    1021  
    1022  	free_pathlist (manpathlist);
    1023  	free (manp);
    1024  	if (create && !amount) {
    1025  		const char *must_create;
    1026  		if (!quiet)
    1027  			fprintf (stderr, _("No databases created."));
    1028  		must_create = getenv ("MAN_MUST_CREATE");
    1029  		if (must_create && STREQ (must_create, "1"))
    1030  			exit (FAIL);
    1031  	}
    1032  	sandbox_free (sandbox);
    1033  	exit (OK);
    1034  }