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 }