catman.c revision 1.26 1 /* $NetBSD: catman.c,v 1.26 2006/04/10 14:39:06 chuck Exp $ */
2
3 /*
4 * Copyright (c) 1998 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * Author: Baldassare Dante Profeta <dante (at) mclink.it>
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 * must display the following acknowledgement:
19 * This product includes software developed by the NetBSD
20 * Foundation, Inc. and its contributors.
21 * 4. Neither the name of The NetBSD Foundation nor the names of its
22 * contributors may be used to endorse or promote products derived
23 * from this software without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
26 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
36 */
37
38 #include <sys/types.h>
39 #include <sys/queue.h>
40 #include <sys/cdefs.h>
41 #include <sys/param.h>
42 #include <sys/stat.h>
43 #include <sys/wait.h>
44 #include <sys/utsname.h>
45 #include <ctype.h>
46 #include <dirent.h>
47 #include <err.h>
48 #include <errno.h>
49 #include <fnmatch.h>
50 #include <limits.h>
51 #include <libgen.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <unistd.h>
56 #include <glob.h>
57
58 #include "manconf.h"
59 #include "pathnames.h"
60
61 int f_nowhatis = 0;
62 int f_noaction = 0;
63 int f_noformat = 0;
64 int f_ignerr = 0;
65 int f_noprint = 0;
66 int dowhatis = 0;
67
68 TAG *defp; /* pointer to _default list */
69
70 int main(int, char * const *);
71 static void setdefentries(char *, char *, const char *);
72 static void uniquepath(void);
73 static void catman(void);
74 static void scanmandir(const char *, const char *);
75 static int splitentry(char *, char *, size_t, char *, size_t);
76 static void setcatsuffix(char *, const char *, const char *);
77 static void makecat(const char *, const char *, const char *, const char *);
78 static void makewhatis(void);
79 static void dosystem(const char *);
80 static void usage(void);
81
82
83 int
84 main(int argc, char * const *argv)
85 {
86 char *m_path = NULL;
87 char *m_add = NULL;
88 int c;
89
90 while ((c = getopt(argc, argv, "km:M:npsw")) != -1) {
91 switch (c) {
92 case 'k':
93 f_ignerr = 1;
94 break;
95 case 'n':
96 f_nowhatis = 1;
97 break;
98 case 'p':
99 f_noaction = 1;
100 break;
101 case 's':
102 f_noprint = 1;
103 break;
104 case 'w':
105 f_noformat = 1;
106 break;
107 case 'm':
108 m_add = optarg;
109 break;
110 case 'M':
111 m_path = optarg;
112 break;
113 default:
114 usage();
115 }
116 }
117
118 argc -= optind;
119 argv += optind;
120
121 if (f_noprint && f_noaction)
122 f_noprint = 0;
123
124 if (argc > 1)
125 usage();
126
127 config(_PATH_MANCONF);
128 setdefentries(m_path, m_add, (argc == 0) ? NULL : argv[argc-1]);
129 uniquepath();
130
131 if (f_noformat == 0 || f_nowhatis == 0)
132 catman();
133 if (f_nowhatis == 0 && dowhatis)
134 makewhatis();
135
136 return(0);
137 }
138
139 static void
140 setdefentries(char *m_path, char *m_add, const char *sections)
141 {
142 TAG *defnewp, *sectnewp, *subp;
143 ENTRY *e_defp, *e_subp;
144 const char *p;
145 char *slashp, *machine;
146 char buf[MAXPATHLEN * 2];
147 int i;
148
149 /* Get the machine type. */
150 if ((machine = getenv("MACHINE")) == NULL) {
151 struct utsname utsname;
152
153 if (uname(&utsname) == -1) {
154 perror("uname");
155 exit(1);
156 }
157 machine = utsname.machine;
158 }
159
160 /* If there's no _default list, create an empty one. */
161 defp = gettag("_default", 1);
162 subp = gettag("_subdir", 1);
163 if (defp == NULL || subp == NULL)
164 err(1, "malloc");
165
166 /*
167 * 0: If one or more sections was specified, rewrite _subdir list.
168 */
169 if (sections != NULL) {
170 if ((sectnewp = gettag("_section_new", 1)) == NULL)
171 err(1, "malloc");
172 for (p = sections; *p;) {
173 i = snprintf(buf, sizeof(buf), "man%c", *p++);
174 for (; *p && !isdigit((unsigned char)*p) && i < sizeof(buf) - 1; i++)
175 buf[i] = *p++;
176 buf[i] = '\0';
177 if (addentry(sectnewp, buf, 0) < 0)
178 err(1, "malloc");
179 }
180 subp = sectnewp;
181 }
182
183 /*
184 * 1: If the user specified a MANPATH variable, or set the -M
185 * option, we replace the _default list with the user's list,
186 * appending the entries in the _subdir list and the machine.
187 */
188 if (m_path == NULL)
189 m_path = getenv("MANPATH");
190 if (m_path != NULL) {
191 while ((e_defp = TAILQ_FIRST(&defp->entrylist)) != NULL) {
192 free(e_defp->s);
193 TAILQ_REMOVE(&defp->entrylist, e_defp, q);
194 }
195 for (p = strtok(m_path, ":");
196 p != NULL; p = strtok(NULL, ":")) {
197 slashp = p[strlen(p) - 1] == '/' ? "" : "/";
198 TAILQ_FOREACH(e_subp, &subp->entrylist, q) {
199 if (!strncmp(e_subp->s, "cat", 3))
200 continue;
201 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
202 p, slashp, e_subp->s, machine);
203 if (addentry(defp, buf, 0) < 0)
204 err(1, "malloc");
205 }
206 }
207 }
208
209 /*
210 * 2: If the user did not specify MANPATH, -M or a section, rewrite
211 * the _default list to include the _subdir list and the machine.
212 */
213 if (m_path == NULL) {
214 defp = gettag("_default", 1);
215 defnewp = gettag("_default_new1", 1);
216 if (defp == NULL || defnewp == NULL)
217 err(1, "malloc");
218
219 TAILQ_FOREACH(e_defp, &defp->entrylist, q) {
220 slashp =
221 e_defp->s[strlen(e_defp->s) - 1] == '/' ? "" : "/";
222 TAILQ_FOREACH(e_subp, &subp->entrylist, q) {
223 if (!strncmp(e_subp->s, "cat", 3))
224 continue;
225 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
226 e_defp->s, slashp, e_subp->s, machine);
227 if (addentry(defnewp, buf, 0) < 0)
228 err(1, "malloc");
229 }
230 }
231 defp = defnewp;
232 }
233
234 /*
235 * 3: If the user set the -m option, insert the user's list before
236 * whatever list we have, again appending the _subdir list and
237 * the machine.
238 */
239 if (m_add != NULL)
240 for (p = strtok(m_add, ":"); p != NULL; p = strtok(NULL, ":")) {
241 slashp = p[strlen(p) - 1] == '/' ? "" : "/";
242 TAILQ_FOREACH(e_subp, &subp->entrylist, q) {
243 if (!strncmp(e_subp->s, "cat", 3))
244 continue;
245 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
246 p, slashp, e_subp->s, machine);
247 if (addentry(defp, buf, 1) < 0)
248 err(1, "malloc");
249 }
250 }
251 }
252
253 /*
254 * Remove entries (directory) which are symbolic links to other entries.
255 * Some examples are showed below:
256 * 1) if /usr/X11 -> /usr/X11R6 then remove all /usr/X11/man entries.
257 * 2) if /usr/local/man -> /usr/share/man then remove all /usr/local/man
258 * entries
259 */
260 static void
261 uniquepath(void)
262 {
263 TAG *defnewp;
264 ENTRY *e_defp;
265 glob_t manpaths;
266 struct stat st1;
267 struct stat st2;
268 struct stat st3;
269 int len, lnk, gflags;
270 size_t i, j;
271 char path[PATH_MAX], *p;
272
273 gflags = 0;
274 TAILQ_FOREACH(e_defp, &defp->entrylist, q) {
275 glob(e_defp->s, GLOB_BRACE | GLOB_NOSORT | gflags, NULL,
276 &manpaths);
277 gflags = GLOB_APPEND;
278 }
279
280 if ((defnewp = gettag("_default_new2", 1)) == NULL)
281 err(1, "malloc");
282
283 for (i = 0; i < manpaths.gl_pathc; i++) {
284 lnk = 0;
285 lstat(manpaths.gl_pathv[i], &st1);
286 for (j = 0; j < manpaths.gl_pathc; j++) {
287 if (i != j) {
288 lstat(manpaths.gl_pathv[j], &st2);
289 if (st1.st_ino == st2.st_ino) {
290 strlcpy(path, manpaths.gl_pathv[i],
291 sizeof(path));
292 for (p = path; *(p+1) != '\0';) {
293 p = dirname(p);
294 lstat(p, &st3);
295 if (S_ISLNK(st3.st_mode)) {
296 lnk = 1;
297 break;
298 }
299 }
300 } else {
301 len = readlink(manpaths.gl_pathv[i],
302 path, sizeof(path) - 1);
303 if (len == -1)
304 continue;
305 path[len] = '\0';
306 if (!strcmp(path, manpaths.gl_pathv[j]))
307 lnk = 1;
308 }
309 if (lnk)
310 break;
311 }
312 }
313
314 if (!lnk) {
315 if (addentry(defnewp, manpaths.gl_pathv[i], 0) < 0)
316 err(1, "malloc");
317 }
318 }
319
320 globfree(&manpaths);
321
322 defp = defnewp;
323 }
324
325 static void
326 catman(void)
327 {
328 ENTRY *e_path;
329 const char *mandir;
330 char catdir[PATH_MAX], *cp;
331
332 TAILQ_FOREACH(e_path, &defp->entrylist, q) {
333 mandir = e_path->s;
334 strlcpy(catdir, mandir, sizeof(catdir));
335 if (!(cp = strstr(catdir, "man/man")))
336 continue;
337 cp += 4; *cp++ = 'c'; *cp++ = 'a'; *cp = 't';
338 scanmandir(catdir, mandir);
339 }
340 }
341
342 static void
343 scanmandir(const char *catdir, const char *mandir)
344 {
345 TAG *buildp, *crunchp;
346 ENTRY *e_build, *e_crunch;
347 char manpage[PATH_MAX];
348 char catpage[PATH_MAX];
349 char linkname[PATH_MAX];
350 char buffer[PATH_MAX], *bp;
351 char tmp[PATH_MAX];
352 char buildsuff[256], buildcmd[256];
353 char crunchsuff[256], crunchcmd[256];
354 char match[256];
355 struct stat manstat;
356 struct stat catstat;
357 struct stat lnkstat;
358 struct dirent *dp;
359 DIR *dirp;
360 int len, error;
361
362 if ((dirp = opendir(mandir)) == 0) {
363 warn("can't open %s", mandir);
364 return;
365 }
366
367 if (stat(catdir, &catstat) < 0) {
368 if (errno != ENOENT) {
369 warn("can't stat %s", catdir);
370 closedir(dirp);
371 return;
372 }
373 if (f_noprint == 0)
374 printf("mkdir %s\n", catdir);
375 if (f_noaction == 0 && mkdir(catdir, 0755) < 0) {
376 warn("can't create %s", catdir);
377 closedir(dirp);
378 return;
379 }
380 }
381
382 while ((dp = readdir(dirp)) != NULL) {
383 if (strcmp(dp->d_name, ".") == 0 ||
384 strcmp(dp->d_name, "..") == 0)
385 continue;
386
387 snprintf(manpage, sizeof(manpage), "%s/%s", mandir, dp->d_name);
388 snprintf(catpage, sizeof(catpage), "%s/%s", catdir, dp->d_name);
389
390 e_build = NULL;
391 if ((buildp = gettag("_build", 1)) == NULL)
392 err(1, "malloc");
393 TAILQ_FOREACH(e_build, &buildp->entrylist, q) {
394 splitentry(e_build->s, buildsuff, sizeof(buildsuff),
395 buildcmd, sizeof(buildcmd));
396 snprintf(match, sizeof(match), "*%s",
397 buildsuff);
398 if (!fnmatch(match, manpage, 0))
399 break;
400 }
401
402 if (e_build == NULL)
403 continue;
404
405 e_crunch = NULL;
406 if ((crunchp = gettag("_crunch", 1)) == NULL)
407 err(1, "malloc");
408 TAILQ_FOREACH(e_crunch, &crunchp->entrylist, q) {
409 splitentry(e_crunch->s, crunchsuff, sizeof(crunchsuff),
410 crunchcmd, sizeof(crunchcmd));
411 snprintf(match, sizeof(match), "*%s", crunchsuff);
412 if (!fnmatch(match, manpage, 0))
413 break;
414 }
415
416 if (lstat(manpage, &manstat) <0) {
417 warn("can't stat %s", manpage);
418 continue;
419 } else {
420 if (S_ISLNK(manstat.st_mode)) {
421 strlcpy(buffer, catpage, sizeof(buffer));
422 strlcpy(linkname, basename(buffer),
423 sizeof(linkname));
424 len = readlink(manpage, buffer,
425 sizeof(buffer) - 1);
426 if (len == -1) {
427 warn("can't stat read symbolic link %s",
428 manpage);
429 continue;
430 }
431 buffer[len] = '\0';
432 bp = basename(buffer);
433 strlcpy(tmp, manpage, sizeof(tmp));
434 snprintf(manpage, sizeof(manpage), "%s/%s",
435 dirname(tmp), bp);
436 strlcpy(tmp, catpage, sizeof(tmp));
437 snprintf(catpage, sizeof(catpage), "%s/%s",
438 dirname(tmp), buffer);
439 }
440 else
441 *linkname = '\0';
442 }
443
444 if (!e_crunch) {
445 *crunchsuff = *crunchcmd = '\0';
446 }
447 setcatsuffix(catpage, buildsuff, crunchsuff);
448 if (*linkname != '\0')
449 setcatsuffix(linkname, buildsuff, crunchsuff);
450
451 if (stat(manpage, &manstat) < 0) {
452 warn("can't stat %s", manpage);
453 continue;
454 }
455
456 if (!S_ISREG(manstat.st_mode)) {
457 warnx("not a regular file %s", manpage);
458 continue;
459 }
460
461 if ((error = stat(catpage, &catstat)) &&
462 errno != ENOENT) {
463 warn("can't stat %s", catpage);
464 continue;
465 }
466
467 if ((error && errno == ENOENT) ||
468 manstat.st_mtime > catstat.st_mtime) {
469 if (f_noformat) {
470 dowhatis = 1;
471 } else {
472 /*
473 * reformat out of date manpage
474 */
475 makecat(manpage, catpage, buildcmd, crunchcmd);
476 dowhatis = 1;
477 }
478 }
479
480 if (*linkname != '\0') {
481 strlcpy(tmp, catpage, sizeof(tmp));
482 snprintf(tmp, sizeof(tmp), "%s/%s", dirname(tmp),
483 linkname);
484 if ((error = lstat(tmp, &lnkstat)) &&
485 errno != ENOENT) {
486 warn("can't stat %s", tmp);
487 continue;
488 }
489
490 if (error && errno == ENOENT) {
491 if (f_noformat) {
492 dowhatis = 1;
493 } else {
494 /*
495 * create symbolic link
496 */
497 if (f_noprint == 0)
498 printf("ln -s %s %s\n", catpage,
499 linkname);
500 if (f_noaction == 0) {
501 strlcpy(tmp, catpage,
502 sizeof(tmp));
503 if (chdir(dirname(tmp)) == -1) {
504 warn("can't chdir");
505 continue;
506 }
507
508 if (symlink(catpage, linkname)
509 == -1) {
510 warn("can't create"
511 " symbolic"
512 " link %s",
513 linkname);
514 continue;
515 }
516 }
517 dowhatis = 1;
518 }
519 }
520 }
521 }
522 closedir(dirp);
523 }
524
525 static int
526 splitentry(char *s, char *first, size_t firstlen, char *second,
527 size_t secondlen)
528 {
529 char *c;
530
531 for (c = s; *c != '\0' && !isspace((unsigned char)*c); ++c)
532 ;
533 if (*c == '\0')
534 return(0);
535 if (c - s + 1 > firstlen)
536 return(0);
537 strncpy(first, s, c-s);
538 first[c-s] = '\0';
539 for (; *c != '\0' && isspace((unsigned char)*c); ++c)
540 ;
541 if (strlcpy(second, c, secondlen) >= secondlen)
542 return(0);
543 return(1);
544 }
545
546 static void
547 setcatsuffix(char *catpage, const char *suffix, const char *crunchsuff)
548 {
549 TAG *tp;
550 char *p;
551
552 for (p = catpage + strlen(catpage); p != catpage; p--)
553 if (!fnmatch(suffix, p, 0)) {
554 if ((tp = gettag("_suffix", 1)) == NULL)
555 err(1, "malloc");
556 if (! TAILQ_EMPTY(&tp->entrylist)) {
557 sprintf(p, "%s%s",
558 TAILQ_FIRST(&tp->entrylist)->s, crunchsuff);
559 } else {
560 sprintf(p, ".0%s", crunchsuff);
561 }
562 break;
563 }
564 }
565
566 static void
567 makecat(const char *manpage, const char *catpage, const char *buildcmd,
568 const char *crunchcmd)
569 {
570 char crunchbuf[1024];
571 char sysbuf[2048];
572
573 snprintf(sysbuf, sizeof(sysbuf), buildcmd, manpage);
574
575 if (*crunchcmd != '\0') {
576 snprintf(crunchbuf, sizeof(crunchbuf), crunchcmd, catpage);
577 snprintf(sysbuf, sizeof(sysbuf), "%s | %s", sysbuf, crunchbuf);
578 } else {
579 snprintf(sysbuf, sizeof(sysbuf), "%s > %s", sysbuf, catpage);
580 }
581
582 if (f_noprint == 0)
583 printf("%s\n", sysbuf);
584 if (f_noaction == 0)
585 dosystem(sysbuf);
586 }
587
588 static void
589 makewhatis(void)
590 {
591 TAG *whatdbp;
592 ENTRY *e_whatdb;
593 char sysbuf[1024];
594
595 if ((whatdbp = gettag("_whatdb", 1)) == NULL)
596 err(1, "malloc");
597 TAILQ_FOREACH(e_whatdb, &whatdbp->entrylist, q) {
598 snprintf(sysbuf, sizeof(sysbuf), "%s %s",
599 _PATH_WHATIS, dirname(e_whatdb->s));
600 if (f_noprint == 0)
601 printf("%s\n", sysbuf);
602 if (f_noaction == 0)
603 dosystem(sysbuf);
604 }
605 }
606
607 static void
608 dosystem(const char *cmd)
609 {
610 int status;
611
612 if ((status = system(cmd)) == 0)
613 return;
614
615 if (status == -1)
616 err(1, "cannot execute action");
617 if (WIFSIGNALED(status))
618 errx(1, "child was signaled to quit. aborting");
619 if (WIFSTOPPED(status))
620 errx(1, "child was stopped. aborting");
621 if (f_ignerr == 0)
622 errx(1, "*** Exited %d", status);
623 warnx("*** Exited %d (continuing)", status);
624 }
625
626 static void
627 usage(void)
628 {
629 (void)fprintf(stderr,
630 "usage: catman [-knpsw] [-m manpath] [sections]\n");
631 (void)fprintf(stderr,
632 " catman [-knpsw] [-M manpath] [sections]\n");
633 exit(1);
634 }
635