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