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