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