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