Imported Upstream version 2.8.4
[platform/upstream/man-db.git] / src / check_mandirs.c
1 /*
2  * check_mandirs.c: used to auto-update the database caches
3  *
4  * Copyright (C) 1994, 1995 Graeme W. Wilford. (Wilf.)
5  * Copyright (C) 2001, 2002, 2003, 2004, 2007, 2008, 2009, 2010, 2011
6  *               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  * Mon May  2 17:36:33 BST 1994  Wilf. (G.Wilford@ee.surrey.ac.uk)
25  *
26  * CJW: Many changes to whatis parsing. Added database purging.
27  * See ChangeLog for details.
28  */
29
30 #ifdef HAVE_CONFIG_H
31 #  include "config.h"
32 #endif /* HAVE_CONFIG_H */
33
34 #include <string.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <time.h>
40 #include <errno.h>
41 #include <ctype.h>
42 #include <dirent.h>
43 #include <unistd.h>
44
45 #include "dirname.h"
46 #include "stat-time.h"
47 #include "timespec.h"
48 #include "xvasprintf.h"
49
50 #include "gettext.h"
51 #define _(String) gettext (String)
52
53 #include "manconfig.h"
54
55 #include "error.h"
56 #include "hashtable.h"
57 #include "orderfiles.h"
58 #include "security.h"
59 #include "xchown.h"
60
61 #include "mydbm.h"
62 #include "db_storage.h"
63
64 #include "descriptions.h"
65 #include "filenames.h"
66 #include "globbing.h"
67 #include "manp.h"
68 #include "ult_src.h"
69 #include "check_mandirs.h"
70
71 int opt_test;           /* don't update db */
72 int pages;
73 int force_rescan = 0;
74
75 static struct hashtable *whatis_hash = NULL;
76
77 struct whatis_hashent {
78         char *whatis;
79         struct ult_trace trace;
80 };
81
82 static void whatis_hashtable_free (void *defn)
83 {
84         struct whatis_hashent *hashent = defn;
85
86         free (hashent->whatis);
87         free_ult_trace (&hashent->trace);
88         free (hashent);
89 }
90
91 static void gripe_multi_extensions (const char *path, const char *sec, 
92                                     const char *name, const char *ext)
93 {
94         if (quiet < 2)
95                 error (0, 0,
96                        _("warning: %s/man%s/%s.%s*: competing extensions"),
97                        path, sec, name, ext);
98 }
99
100 static void gripe_rwopen_failed (void)
101 {
102         if (errno == EACCES || errno == EROFS)
103                 debug ("database %s is read-only\n", database);
104         else if (errno == EAGAIN || errno == EWOULDBLOCK)
105                 debug ("database %s is locked by another process\n", database);
106         else {
107 #ifdef MAN_DB_UPDATES
108                 if (!quiet)
109 #endif /* MAN_DB_UPDATES */
110                         error (0, errno, _("can't update index cache %s"),
111                                database);
112         }
113 }
114
115 /* Take absolute filename and path (for ult_src) and do sanity checks on
116  * file. Also check that file is non-zero in length and is not already in
117  * the db. If not, find its ult_src() and see if we have the whatis cached,
118  * otherwise cache it in case we trace another manpage back to it. Next,
119  * store it in the db along with any references found in the whatis.
120  */
121 void test_manfile (MYDBM_FILE dbf, const char *file, const char *path)
122 {
123         char *manpage_base;
124         const char *ult;
125         struct lexgrog lg;
126         char *manpage;
127         struct mandata info, *exists;
128         struct stat buf;
129         size_t len;
130         struct ult_trace ult_trace;
131         struct whatis_hashent *whatis;
132
133         memset (&lg, 0, sizeof (struct lexgrog));
134         memset (&info, 0, sizeof (struct mandata));
135         memset (&ult_trace, 0, sizeof (struct ult_trace));
136
137         manpage = filename_info (file, &info, NULL);
138         if (!manpage)
139                 return;
140         manpage_base = manpage + strlen (manpage) + 1;
141
142         len  = strlen (manpage) + 1;            /* skip over directory name */
143         len += strlen (manpage + len) + 1;      /* skip over base name */
144         len += strlen (manpage + len);          /* skip over section ext */
145
146         /* to get mtime info */
147         (void) lstat (file, &buf);
148         info.mtime = get_stat_mtime (&buf);
149
150         /* check that our file actually contains some data */
151         if (buf.st_size == 0) {
152                 /* man-db pre 2.3 place holder ? */
153                 free (manpage);
154                 return;
155         }
156
157         /* See if we already have it, before going any further. This will
158          * save both an ult_src() and a find_name(), amongst other wastes of
159          * time.
160          */
161         exists = dblookup_exact (dbf, manpage_base, info.ext, 1);
162
163         /* Ensure we really have the actual page. Gzip keeps the mtime the
164          * same when it compresses, so we have to compare compression
165          * extensions as well.
166          */
167         if (exists) {
168                 if (strcmp (exists->comp, info.comp ? info.comp : "-") == 0) {
169                         if (timespec_cmp (exists->mtime, info.mtime) == 0 &&
170                             exists->id < WHATIS_MAN) {
171                                 free_mandata_struct (exists);
172                                 free (manpage);
173                                 return;
174                         }
175                 } else {
176                         char *abs_filename;
177
178                         /* see if the cached file actually exists. It's 
179                            evident at this point that we have multiple 
180                            comp extensions */
181                         abs_filename = make_filename (path, NULL,
182                                                       exists, "man");
183                         if (!abs_filename) {
184                                 if (!opt_test)
185                                         dbdelete (dbf, manpage_base, exists);
186                         } else {
187                                 gripe_multi_extensions (path, exists->sec,
188                                                         manpage_base,
189                                                         exists->ext);
190                                 free (abs_filename);
191                                 free_mandata_struct (exists);
192                                 free (manpage);
193                                 return;
194                         }
195                 }
196                 free_mandata_struct (exists);
197         }
198
199         /* Check if it happens to be a symlink/hardlink to something already
200          * in our cache. This just does some extra checks to avoid scanning
201          * links quite so many times.
202          */
203         {
204                 /* Avoid too much noise in debug output */
205                 int save_debug = debug_level;
206                 debug_level = 0;
207                 ult = ult_src (file, path, &buf, SOFT_LINK | HARD_LINK, NULL);
208                 debug_level = save_debug;
209         }
210
211         if (!ult) {
212                 /* already warned about this, don't do so again */
213                 debug ("test_manfile(): bad link %s\n", file);
214                 free (manpage);
215                 return;
216         }
217
218         if (!whatis_hash)
219                 whatis_hash = hashtable_create (&whatis_hashtable_free);
220
221         whatis = hashtable_lookup (whatis_hash, ult, strlen (ult));
222         if (!whatis) {
223                 if (!STRNEQ (ult, file, len))
224                         debug ("\ntest_manfile(): link not in cache:\n"
225                                " source = %s\n"
226                                " target = %s\n", file, ult);
227                 /* Trace the file to its ultimate source, otherwise we'll be
228                  * looking for whatis info in files containing only '.so
229                  * manx/foo.x', which will give us an unobtainable whatis
230                  * for the entry. */
231                 ult = ult_src (file, path, &buf,
232                                SO_LINK | SOFT_LINK | HARD_LINK, &ult_trace);
233         }
234
235         if (!ult) {
236                 if (quiet < 2)
237                         error (0, 0,
238                                _("warning: %s: bad symlink or ROFF `.so' request"),
239                                file);
240                 free (manpage);
241                 return;
242         }
243
244         pages++;                        /* pages seen so far */
245
246         if (strncmp (ult, file, len) == 0)
247                 info.id = ULT_MAN;      /* ultimate source file */
248         else
249                 info.id = SO_MAN;       /* .so, sym or hard linked file */
250
251         /* Ok, here goes: Use a hash tree to store the ult_srcs with
252          * their whatis. Anytime after, check the hash tree, if it's there, 
253          * use it. This saves us a find_name() which is a real hog.
254          *
255          * Use the full path in ult as the hash key so we don't have to
256          * clear the hash between calls.
257          */
258
259         if (whatis)
260                 lg.whatis = whatis->whatis ? xstrdup (whatis->whatis) : NULL;
261         else {
262                 /* Cache miss; go and get the whatis info in its raw state. */
263                 char *file_base = base_name (file);
264
265                 lg.type = MANPAGE;
266                 drop_effective_privs ();
267                 find_name (ult, file_base, &lg, NULL);
268                 free (file_base);
269                 regain_effective_privs ();
270
271                 whatis = XMALLOC (struct whatis_hashent);
272                 whatis->whatis = lg.whatis ? xstrdup (lg.whatis) : NULL;
273                 /* We filled out ult_trace above. */
274                 memcpy (&whatis->trace, &ult_trace, sizeof (ult_trace));
275                 hashtable_install (whatis_hash, ult, strlen (ult), whatis);
276         }
277
278         debug ("\"%s\"\n", lg.whatis);
279
280         /* split up the raw whatis data and store references */
281         info.pointer = NULL;    /* direct page, so far */
282         info.filter = lg.filters;
283         if (lg.whatis) {
284                 struct page_description *descs =
285                         parse_descriptions (manpage_base, lg.whatis);
286                 if (descs) {
287                         if (!opt_test)
288                                 store_descriptions (dbf, descs, &info,
289                                                     path, manpage_base,
290                                                     &whatis->trace);
291                         free_descriptions (descs);
292                 }
293         } else if (quiet < 2) {
294                 (void) stat (ult, &buf);
295                 if (buf.st_size == 0)
296                         error (0, 0, _("warning: %s: ignoring empty file"),
297                                ult);
298                 else
299                         error (0, 0,
300                                _("warning: %s: whatis parse for %s(%s) failed"),
301                                ult, manpage_base, info.ext);
302         }
303
304         free (manpage);
305         free (lg.whatis);
306 }
307
308 static void add_dir_entries (MYDBM_FILE dbf, const char *path, char *infile)
309 {
310         char *manpage;
311         int len;
312         struct dirent *newdir;
313         DIR *dir;
314         char **names;
315         size_t names_len, names_max, i;
316
317         manpage = xasprintf ("%s/%s/", path, infile);
318         len = strlen (manpage);
319
320         /*
321          * All filename entries in this dir should either be valid manpages
322          * or . files (such as current, parent dir).
323          */
324
325         dir = opendir (infile);
326         if (!dir) {
327                 error (0, errno, _("can't search directory %s"), manpage);
328                 free (manpage);
329                 return;
330         }
331
332         names_len = 0;
333         names_max = 1024;
334         names = XNMALLOC (names_max, char *);
335
336         /* strlen(newdir->d_name) could be replaced by newdir->d_reclen */
337
338         while ((newdir = readdir (dir)) != NULL) {
339                 if (*newdir->d_name == '.' &&
340                     strlen (newdir->d_name) < (size_t) 3)
341                         continue;
342                 if (names_len >= names_max) {
343                         names_max *= 2;
344                         names = xnrealloc (names, names_max, sizeof (char *));
345                 }
346                 names[names_len++] = xstrdup (newdir->d_name);
347         }
348         closedir (dir);
349
350         order_files (infile, names, names_len);
351
352         for (i = 0; i < names_len; ++i) {
353                 manpage = appendstr (manpage, names[i], NULL);
354                 test_manfile (dbf, manpage, path);
355                 *(manpage + len) = '\0';
356                 free (names[i]);
357         }
358
359         free (names);
360         free (manpage);
361 }
362
363 #ifdef MAN_OWNER
364 extern uid_t uid;                       /* current effective user id */
365 extern gid_t gid;                       /* current effective group id */
366
367 /* Fix a path's ownership if possible and necessary. */
368 void chown_if_possible (const char *path)
369 {
370         struct stat st;
371         struct passwd *man_owner = get_man_owner ();
372
373         if (lstat (path, &st) != 0)
374                 return;
375
376         if ((uid == 0 ||
377              (uid == man_owner->pw_uid && st.st_uid == man_owner->pw_uid &&
378               gid == man_owner->pw_gid)) &&
379             (st.st_uid != man_owner->pw_uid ||
380              st.st_gid != man_owner->pw_gid)) {
381                 debug ("fixing ownership of %s\n", path);
382 #ifdef HAVE_LCHOWN
383                 xlchown (path, man_owner->pw_uid, man_owner->pw_gid);
384 #else
385                 xchown (path, man_owner->pw_uid, man_owner->pw_gid);
386 #endif
387         }
388 }
389 #else /* !MAN_OWNER */
390 void chown_if_possible (const char *path ATTRIBUTE_UNUSED)
391 {
392 }
393 #endif /* MAN_OWNER */
394
395 /* create the catman hierarchy if it doesn't exist */
396 static void mkcatdirs (const char *mandir, const char *catdir)
397 {
398         char *manname, *catname;
399
400         if (catdir) {
401                 int oldmask = umask (022);
402                 /* first the base catdir */
403                 if (is_directory (catdir) != 1) {
404                         regain_effective_privs ();
405                         if (mkdir (catdir, 0755) < 0) {
406                                 if (!quiet)
407                                         error (0, 0,
408                                                _("warning: cannot create catdir %s"),
409                                                catdir);
410                                 debug ("warning: cannot create catdir %s\n",
411                                        catdir);
412                         } else
413                                 debug ("created base catdir %s\n", catdir);
414                         chown_if_possible (catdir);
415                         drop_effective_privs ();
416                 }
417                 /* then the hierarchy */
418                 catname = xasprintf ("%s/cat1", catdir);
419                 manname = xasprintf ("%s/man1", mandir);
420                 if (is_directory (catdir) == 1) {
421                         int j;
422                         regain_effective_privs ();
423                         debug ("creating catdir hierarchy %s    ", catdir);
424                         for (j = 1; j <= 9; j++) {
425                                 catname[strlen (catname) - 1] = '0' + j;
426                                 manname[strlen (manname) - 1] = '0' + j;
427                                 if ((is_directory (manname) == 1)
428                                  && (is_directory (catname) != 1)) {
429                                         if (mkdir (catname, 0755) < 0) {
430                                                 if (!quiet)
431                                                         error (0, 0, _("warning: cannot create catdir %s"), catname);
432                                                 debug ("warning: cannot create catdir %s\n", catname);
433                                         } else
434                                                 debug (" cat%d", j);
435                                         chown_if_possible (catname);
436                                 }
437                         }
438                         debug ("\n");
439                         drop_effective_privs ();
440                 }
441                 free (catname);
442                 free (manname);
443                 umask (oldmask);
444         }
445 }
446
447 /* We used to install cat directories with the setgid bit set, but this
448  * wasn't very useful and introduces the ability to escalate privileges to
449  * that group:
450  *   https://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/
451  */
452 static void fix_permissions (const char *dir)
453 {
454         struct stat st;
455
456         if (stat (dir, &st) == 0) {
457                 if ((st.st_mode & S_ISGID) != 0) {
458                         int status;
459
460                         debug ("removing setgid bit from %s\n", dir);
461                         status = chmod (dir, st.st_mode & ~S_ISGID);
462                         if (status)
463                                 error (0, errno, _("can't chmod %s"), dir);
464                 }
465
466                 chown_if_possible (dir);
467         }
468 }
469
470 static void fix_permissions_tree (const char *catdir)
471 {
472         if (is_directory (catdir) == 1) {
473                 char *catname;
474                 int i;
475
476                 fix_permissions (catdir);
477                 catname = xasprintf ("%s/cat1", catdir);
478                 for (i = 1; i <= 9; ++i) {
479                         catname[strlen (catname) - 1] = '0' + i;
480                         fix_permissions (catname);
481                 }
482                 free (catname);
483         }
484 }
485
486 /*
487  * accepts the raw man dir tree eg. "/usr/man" and the time stored in the db
488  * any dirs of the tree that have been modified (ie added to) will then be
489  * scanned for new files, which are then added to the db.
490  */
491 static int testmandirs (const char *path, const char *catpath,
492                         struct timespec last, int create)
493 {
494         DIR *dir;
495         struct dirent *mandir;
496         int amount = 0;
497         int created = 0;
498
499         debug ("Testing %s for new files\n", path);
500
501         if (catpath)
502                 fix_permissions_tree (catpath);
503
504         dir = opendir (path);
505         if (!dir) {
506                 error (0, errno, _("can't search directory %s"), path);
507                 return 0;
508         }
509
510         if (chdir (path) != 0) {
511                 error (0, errno, _("can't change to directory %s"), path);
512                 closedir (dir);
513                 return 0;
514         }
515
516         while( (mandir = readdir (dir)) ) {
517                 struct stat stbuf;
518                 struct timespec mtime;
519                 MYDBM_FILE dbf;
520
521                 if (strncmp (mandir->d_name, "man", 3) != 0)
522                         continue;
523
524                 debug ("Examining %s\n", mandir->d_name);
525
526                 if (stat (mandir->d_name, &stbuf) != 0) /* stat failed */
527                         continue;
528                 if (!S_ISDIR(stbuf.st_mode))            /* not a directory */
529                         continue;
530                 mtime = get_stat_mtime (&stbuf);
531                 if (last.tv_sec && timespec_cmp (mtime, last) <= 0) {
532                         /* scanned already */
533                         debug ("%s modified %ld.%09ld, "
534                                "db modified %ld.%09ld\n",
535                                mandir->d_name,
536                                (long) mtime.tv_sec, (long) mtime.tv_nsec,
537                                (long) last.tv_sec, (long) last.tv_nsec);
538                         continue;
539                 }
540
541                 debug ("\tsubdirectory %s has been 'modified'\n",
542                        mandir->d_name);
543
544                 if (create && !created) {
545                         /* We seem to have something to do, so create the
546                          * database now.
547                          */
548                         mkcatdirs (path, catpath);
549
550                         /* Open the db in CTRW mode to store the $ver$ ID */
551
552                         dbf = MYDBM_CTRWOPEN (database);
553                         if (dbf == NULL) {
554                                 if (errno == EACCES || errno == EROFS) {
555                                         debug ("database %s is read-only\n",
556                                                database);
557                                         closedir (dir);
558                                         return 0;
559                                 } else {
560                                         error (0, errno,
561                                                _("can't create index cache %s"),
562                                                database);
563                                         closedir (dir);
564                                         return -errno;
565                                 }
566                         }
567
568                         dbver_wr (dbf);
569
570                         created = 1;
571                 } else
572                         dbf = MYDBM_RWOPEN(database);
573
574                 if (!dbf) {
575                         gripe_rwopen_failed ();
576                         closedir (dir);
577                         return 0;
578                 }
579
580                 if (!quiet) {
581                         int tty = isatty (STDERR_FILENO);
582
583                         if (tty)
584                                 fprintf (stderr, "\r");
585                         fprintf (stderr,
586                                  _("Updating index cache for path "
587                                    "`%s/%s'. Wait..."), path, mandir->d_name);
588                         if (!tty)
589                                 fprintf (stderr, "\n");
590                 }
591                 add_dir_entries (dbf, path, mandir->d_name);
592                 MYDBM_CLOSE (dbf);
593                 amount++;
594         }
595         closedir (dir);
596
597         return amount;
598 }
599
600 /* update the modification timestamp of `database' */
601 static void update_db_time (void)
602 {
603         MYDBM_FILE dbf;
604         struct timespec now;
605
606         /* Open the db in RW to update its mtime */
607         /* we know that this should succeed because we just updated the db! */
608         dbf = MYDBM_RWOPEN (database);
609         if (dbf == NULL) {
610                 if (errno == EAGAIN || errno == EWOULDBLOCK)
611                         /* Another mandb process is probably running.  With
612                          * any luck it will update the mtime ...
613                          */
614                         debug ("database %s is locked by another process\n",
615                                database);
616                 else {
617 #ifdef MAN_DB_UPDATES
618                         if (!quiet)
619 #endif /* MAN_DB_UPDATES */
620                                 error (0, errno,
621                                        _("can't update index cache %s"),
622                                        database);
623                 }
624                 return;
625         }
626         now.tv_sec = 0;
627         now.tv_nsec = UTIME_NOW;
628         MYDBM_SET_TIME (dbf, now);
629
630         MYDBM_CLOSE (dbf);
631 }
632
633 /* routine to prepare/create the db prior to calling testmandirs() */
634 int create_db (const char *manpath, const char *catpath)
635 {
636         struct timespec time_zero;
637         int amount;
638
639         debug ("create_db(%s): %s\n", manpath, database);
640
641         time_zero.tv_sec = 0;
642         time_zero.tv_nsec = 0;
643         amount = testmandirs (manpath, catpath, time_zero, 1);
644
645         if (amount) {
646                 update_db_time ();
647                 if (!quiet)
648                         fputs (_("done.\n"), stderr);
649         }
650
651         return amount;
652 }
653
654 /* Make sure an existing database is essentially sane. */
655 static int sanity_check_db (MYDBM_FILE dbf)
656 {
657         datum key;
658
659         if (dbver_rd (dbf))
660                 return 0;
661
662         key = MYDBM_FIRSTKEY (dbf);
663         while (MYDBM_DPTR (key) != NULL) {
664                 datum content, nextkey;
665
666                 content = MYDBM_FETCH (dbf, key);
667                 if (!MYDBM_DPTR (content)) {
668                         debug ("warning: %s has a key with no content (%s); "
669                                "rebuilding\n", database, MYDBM_DPTR (key));
670                         MYDBM_FREE_DPTR (key);
671                         return 0;
672                 }
673                 MYDBM_FREE_DPTR (content);
674                 nextkey = MYDBM_NEXTKEY (dbf, key);
675                 MYDBM_FREE_DPTR (key);
676                 key = nextkey;
677         }
678
679         return 1;
680 }
681
682 /* routine to update the db, ensure that it is consistent with the 
683    filesystem */
684 int update_db (const char *manpath, const char *catpath)
685 {
686         MYDBM_FILE dbf;
687         struct timespec mtime;
688         int new;
689
690         dbf = MYDBM_RDOPEN (database);
691         if (dbf && !sanity_check_db (dbf)) {
692                 MYDBM_CLOSE (dbf);
693                 dbf = NULL;
694         }
695         if (!dbf) {
696                 debug ("failed to open %s O_RDONLY\n", database);
697                 return EOF;
698         }
699         mtime = MYDBM_GET_TIME (dbf);
700         MYDBM_CLOSE (dbf);
701
702         debug ("update_db(): %ld.%09ld\n",
703                (long) mtime.tv_sec, (long) mtime.tv_nsec);
704         new = testmandirs (manpath, catpath, mtime, 0);
705
706         if (new) {
707                 update_db_time ();
708                 if (!quiet)
709                         fputs (_("done.\n"), stderr);
710         }
711
712         return new;
713 }
714
715 /* Purge any entries pointing to name. This currently assumes that pointers
716  * are always shallow, which may not be a good assumption yet; it should be
717  * close, though.
718  */
719 void purge_pointers (MYDBM_FILE dbf, const char *name)
720 {
721         datum key = MYDBM_FIRSTKEY (dbf);
722
723         debug ("Purging pointers to vanished page \"%s\"\n", name);
724
725         while (MYDBM_DPTR (key) != NULL) {
726                 datum content, nextkey;
727                 struct mandata entry;
728                 char *nicekey, *tab;
729
730                 /* Ignore db identifier keys. */
731                 if (*MYDBM_DPTR (key) == '$')
732                         goto pointers_next;
733
734                 content = MYDBM_FETCH (dbf, key);
735                 if (!MYDBM_DPTR (content))
736                         return;
737
738                 /* Get just the name. */
739                 nicekey = xstrdup (MYDBM_DPTR (key));
740                 tab = strchr (nicekey, '\t');
741                 if (tab)
742                         *tab = '\0';
743
744                 if (*MYDBM_DPTR (content) == '\t')
745                         goto pointers_contentnext;
746
747                 split_content (MYDBM_DPTR (content), &entry);
748                 if (entry.id != SO_MAN && entry.id != WHATIS_MAN)
749                         goto pointers_contentnext;
750
751                 if (STREQ (entry.pointer, name)) {
752                         if (!opt_test)
753                                 dbdelete (dbf, nicekey, &entry);
754                         else
755                                 debug ("%s(%s): pointer vanished, "
756                                        "would delete\n", nicekey, entry.ext);
757                 }
758
759 pointers_contentnext:
760                 free (nicekey);
761                 MYDBM_FREE_DPTR (content);
762 pointers_next:
763                 nextkey = MYDBM_NEXTKEY (dbf, key);
764                 MYDBM_FREE_DPTR (key);
765                 key = nextkey;
766         }
767 }
768
769 /* Count the number of exact extension matches returned from look_for_file()
770  * (which may return inexact extension matches in some cases). It may turn
771  * out that this is better handled in look_for_file() itself.
772  */
773 static int count_glob_matches (const char *name, const char *ext,
774                                char **source, struct timespec db_mtime)
775 {
776         char **walk;
777         int count = 0;
778
779         for (walk = source; walk && *walk; ++walk) {
780                 struct mandata info;
781                 struct stat statbuf;
782                 char *buf;
783
784                 memset (&info, 0, sizeof (struct mandata));
785
786                 if (stat (*walk, &statbuf) == -1) {
787                         debug ("count_glob_matches: excluding %s "
788                                "because stat failed\n", *walk);
789                         continue;
790                 }
791                 if (db_mtime.tv_sec != (time_t) -1 &&
792                     timespec_cmp (get_stat_mtime (&statbuf), db_mtime) <= 0) {
793                         debug ("count_glob_matches: excluding %s, "
794                                "no newer than database\n", *walk);
795                         continue;
796                 }
797
798                 buf = filename_info (*walk, &info, name);
799                 if (buf) {
800                         if (STREQ (ext, info.ext))
801                                 ++count;
802                         free (info.name);
803                         free (buf);
804                 }
805         }
806
807         return count;
808 }
809
810 /* Decide whether to purge a reference to a "normal" (ULT_MAN or SO_MAN)
811  * page.
812  */
813 static int purge_normal (MYDBM_FILE dbf, const char *name,
814                          struct mandata *info, char **found)
815 {
816         struct timespec t;
817
818         /* TODO: On some systems, the cat page extension differs from the
819          * man page extension, so this may be too strict.
820          */
821         t.tv_sec = -1;
822         t.tv_nsec = -1;
823         if (count_glob_matches (name, info->ext, found, t))
824                 return 0;
825
826         if (!opt_test)
827                 dbdelete (dbf, name, info);
828         else
829                 debug ("%s(%s): missing page, would delete\n",
830                        name, info->ext);
831
832         return 1;
833 }
834
835 /* Decide whether to purge a reference to a WHATIS_MAN or WHATIS_CAT page. */
836 static int purge_whatis (MYDBM_FILE dbf, const char *path, int cat,
837                          const char *name, struct mandata *info, char **found,
838                          struct timespec db_mtime)
839 {
840         /* TODO: On some systems, the cat page extension differs from the
841          * man page extension, so this may be too strict.
842          */
843         if (count_glob_matches (name, info->ext, found, db_mtime)) {
844                 /* If the page exists and didn't beforehand, then presumably
845                  * we're about to rescan, which will replace the WHATIS_MAN
846                  * entry with something better. However, there have been
847                  * bugs that created false WHATIS_MAN entries, so force the
848                  * rescan just to be sure; since in the absence of a bug we
849                  * would rescan anyway, this isn't a problem.
850                  */
851                 if (!force_rescan)
852                         debug ("%s(%s): whatis replaced by real page; "
853                                "forcing a rescan just in case\n",
854                                name, info->ext);
855                 force_rescan = 1;
856                 return 0;
857         } else if (STREQ (info->pointer, "-")) {
858                 /* This is broken; a WHATIS_MAN should never have an empty
859                  * pointer field. This might have happened due to the first
860                  * name in a page being different from what the file name
861                  * says; that's fixed now, so delete and force a rescan.
862                  */
863                 if (!opt_test)
864                         dbdelete (dbf, name, info);
865                 else
866                         debug ("%s(%s): whatis with empty pointer, "
867                                "would delete\n", name, info->ext);
868
869                 if (!force_rescan)
870                         debug ("%s(%s): whatis had empty pointer; "
871                                "forcing a rescan just in case\n",
872                                name, info->ext);
873                 force_rescan = 1;
874                 return 1;
875         } else {
876                 /* Does the real page still exist? */
877                 char **real_found;
878                 int save_debug = debug_level;
879                 struct timespec t;
880
881                 debug_level = 0;
882                 real_found = look_for_file (path, info->ext,
883                                             info->pointer, cat, LFF_MATCHCASE);
884                 debug_level = save_debug;
885
886                 t.tv_sec = -1;
887                 t.tv_nsec = -1;
888                 if (count_glob_matches (info->pointer, info->ext, real_found,
889                                         t))
890                         return 0;
891
892                 if (!opt_test)
893                         dbdelete (dbf, name, info);
894                 else
895                         debug ("%s(%s): whatis target was deleted, "
896                                "would delete\n", name, info->ext);
897                 return 1;
898         }
899 }
900
901 /* Check that multi keys are correctly constructed. */
902 static int check_multi_key (const char *name, const char *content)
903 {
904         const char *walk, *next;
905
906         if (!*content)
907                 return 0;
908
909         for (walk = content; walk && *walk; walk = next) {
910                 /* The name in the multi key should only differ from the
911                  * name of the key itself in its case, if at all.
912                  */
913                 int valid = 1;
914                 ++walk; /* skip over initial tab */
915                 next = strchr (walk, '\t');
916                 if (next) {
917                         if (strncasecmp (name, walk, next - walk))
918                                 valid = 0;
919                 } else {
920                         if (strcasecmp (name, walk))
921                                 valid = 0;
922                 }
923                 if (!valid) {
924                         debug ("%s: broken multi key \"%s\", "
925                                "forcing a rescan\n", name, content);
926                         force_rescan = 1;
927                         return 1;
928                 }
929
930                 /* If the name was valid, skip over the extension and
931                  * continue the scan.
932                  */
933                 walk = next;
934                 next = walk ? strchr (walk + 1, '\t') : NULL;
935         }
936
937         return 0;
938 }
939
940 /* Go through the database and purge references to man pages that no longer
941  * exist.
942  */
943 int purge_missing (const char *manpath, const char *catpath,
944                    int will_run_mandb)
945 {
946 #ifdef NDBM
947         char *dirfile;
948 #endif
949         struct stat st;
950         int db_exists;
951         MYDBM_FILE dbf;
952         datum key;
953         int count = 0;
954         struct timespec db_mtime;
955
956 #ifdef NDBM
957         dirfile = xasprintf ("%s.dir", database);
958         db_exists = stat (dirfile, &st) == 0;
959         free (dirfile);
960 #else
961         db_exists = stat (database, &st) == 0;
962 #endif
963         if (!db_exists)
964                 /* nothing to purge */
965                 return 0;
966
967         if (!quiet)
968                 printf (_("Purging old database entries in %s...\n"), manpath);
969
970         dbf = MYDBM_RWOPEN (database);
971         if (!dbf) {
972                 gripe_rwopen_failed ();
973                 return 0;
974         }
975         if (!sanity_check_db (dbf)) {
976                 MYDBM_CLOSE (dbf);
977                 dbf = NULL;
978                 return 0;
979         }
980         db_mtime = MYDBM_GET_TIME (dbf);
981
982         key = MYDBM_FIRSTKEY (dbf);
983
984         while (MYDBM_DPTR (key) != NULL) {
985                 datum content, nextkey;
986                 struct mandata entry;
987                 char *nicekey, *tab;
988                 int save_debug;
989                 char **found;
990
991                 /* Ignore db identifier keys. */
992                 if (*MYDBM_DPTR (key) == '$') {
993                         nextkey = MYDBM_NEXTKEY (dbf, key);
994                         MYDBM_FREE_DPTR (key);
995                         key = nextkey;
996                         continue;
997                 }
998
999                 content = MYDBM_FETCH (dbf, key);
1000                 if (!MYDBM_DPTR (content)) {
1001                         nextkey = MYDBM_NEXTKEY (dbf, key);
1002                         MYDBM_FREE_DPTR (key);
1003                         key = nextkey;
1004                         continue;
1005                 }
1006
1007                 /* Get just the name. */
1008                 nicekey = xstrdup (MYDBM_DPTR (key));
1009                 tab = strchr (nicekey, '\t');
1010                 if (tab)
1011                         *tab = '\0';
1012
1013                 /* Deal with multi keys. */
1014                 if (*MYDBM_DPTR (content) == '\t') {
1015                         if (check_multi_key (nicekey, MYDBM_DPTR (content)))
1016                                 MYDBM_DELETE (dbf, key);
1017                         free (nicekey);
1018                         MYDBM_FREE_DPTR (content);
1019                         nextkey = MYDBM_NEXTKEY (dbf, key);
1020                         MYDBM_FREE_DPTR (key);
1021                         key = nextkey;
1022                         continue;
1023                 }
1024
1025                 split_content (MYDBM_DPTR (content), &entry);
1026
1027                 save_debug = debug_level;
1028                 debug_level = 0;        /* look_for_file() is quite noisy */
1029                 if (entry.id <= WHATIS_MAN)
1030                         found = look_for_file (manpath, entry.ext,
1031                                                entry.name ? entry.name
1032                                                           : nicekey,
1033                                                0, LFF_MATCHCASE);
1034                 else
1035                         found = look_for_file (catpath, entry.ext,
1036                                                entry.name ? entry.name
1037                                                           : nicekey,
1038                                                1, LFF_MATCHCASE);
1039                 debug_level = save_debug;
1040
1041                 /* Now actually decide whether to purge, depending on the
1042                  * type of entry.
1043                  */
1044                 if (entry.id == ULT_MAN || entry.id == SO_MAN ||
1045                     entry.id == STRAY_CAT)
1046                         count += purge_normal (dbf, nicekey, &entry, found);
1047                 else if (entry.id == WHATIS_MAN)
1048                         count += purge_whatis (dbf, manpath, 0, nicekey,
1049                                                &entry, found, db_mtime);
1050                 else    /* entry.id == WHATIS_CAT */
1051                         count += purge_whatis (dbf, catpath, 1, nicekey,
1052                                                &entry, found, db_mtime);
1053
1054                 free (nicekey);
1055
1056                 free_mandata_elements (&entry);
1057                 nextkey = MYDBM_NEXTKEY (dbf, key);
1058                 MYDBM_FREE_DPTR (key);
1059                 key = nextkey;
1060         }
1061
1062         MYDBM_REORG (dbf);
1063         if (will_run_mandb)
1064                 /* Reset mtime to avoid confusing mandb into not running.
1065                  * TODO: It would be better to avoid this by only opening
1066                  * the database once between here and mandb.
1067                  */
1068                 MYDBM_SET_TIME (dbf, db_mtime);
1069         MYDBM_CLOSE (dbf);
1070         return count;
1071 }