catman.c revision 1.17 1 /* $NetBSD: catman.c,v 1.17 2002/06/11 04:39:53 lukem 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 defp = getlist("_default", 1);
170
171 subp = getlist("_subdir", 1);
172
173 /*
174 * 0: If one or more sections was specified, rewrite _subdir list.
175 */
176 if (sections != NULL) {
177 sectnewp = getlist("_section_new", 1);
178 for(p=sections; *p;) {
179 i = snprintf(buf, sizeof(buf), "man%c", *p++);
180 for(; *p && !isdigit(*p) && i<sizeof(buf)-1; i++)
181 buf[i] = *p++;
182 buf[i] = '\0';
183 addentry(sectnewp, buf, 0);
184 }
185 subp = sectnewp;
186 }
187
188 /*
189 * 1: If the user specified a MANPATH variable, or set the -M
190 * option, we replace the _default list with the user's list,
191 * appending the entries in the _subdir list and the machine.
192 */
193 if (m_path == NULL)
194 m_path = getenv("MANPATH");
195 if (m_path != NULL) {
196 while ((e_defp = TAILQ_FIRST(&defp->list)) != NULL) {
197 free(e_defp->s);
198 TAILQ_REMOVE(&defp->list, e_defp, q);
199 }
200 for (p = strtok(m_path, ":");
201 p != NULL; p = strtok(NULL, ":")) {
202 slashp = p[strlen(p) - 1] == '/' ? "" : "/";
203 TAILQ_FOREACH(e_subp, &subp->list, q) {
204 if(!strncmp(e_subp->s, "cat", 3))
205 continue;
206 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
207 p, slashp, e_subp->s, machine);
208 addentry(defp, buf, 0);
209 }
210 }
211 }
212
213 /*
214 * 2: If the user did not specify MANPATH, -M or a section, rewrite
215 * the _default list to include the _subdir list and the machine.
216 */
217 if (m_path == NULL) {
218 defp = getlist("_default", 1);
219 defnewp = getlist("_default_new1", 1);
220
221 TAILQ_FOREACH(e_defp, &defp->list, q) {
222 slashp =
223 e_defp->s[strlen(e_defp->s) - 1] == '/' ? "" : "/";
224 TAILQ_FOREACH(e_subp, &subp->list, q) {
225 if(!strncmp(e_subp->s, "cat", 3))
226 continue;
227 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
228 e_defp->s, slashp, e_subp->s, machine);
229 addentry(defnewp, buf, 0);
230 }
231 }
232 defp = defnewp;
233 }
234
235 /*
236 * 3: If the user set the -m option, insert the user's list before
237 * whatever list we have, again appending the _subdir list and
238 * the machine.
239 */
240 if (m_add != NULL)
241 for (p = strtok(m_add, ":"); p != NULL; p = strtok(NULL, ":")) {
242 slashp = p[strlen(p) - 1] == '/' ? "" : "/";
243 TAILQ_FOREACH(e_subp, &subp->list, q) {
244 if(!strncmp(e_subp->s, "cat", 3))
245 continue;
246 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
247 p, slashp, e_subp->s, machine);
248 addentry(defp, buf, 1);
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 i,j,len,lnk, gflags;
270 char path[PATH_MAX], *p;
271
272
273 gflags = 0;
274 TAILQ_FOREACH(e_defp, &defp->list, q) {
275 glob(e_defp->s, GLOB_BRACE | GLOB_NOSORT | gflags, NULL,
276 &manpaths);
277 gflags = GLOB_APPEND;
278 }
279
280 defnewp = getlist("_default_new2", 1);
281
282 for(i=0; i<manpaths.gl_pathc; i++) {
283 lnk = 0;
284 lstat(manpaths.gl_pathv[i], &st1);
285 for(j=0; j<manpaths.gl_pathc; j++) {
286 if (i != j) {
287 lstat(manpaths.gl_pathv[j], &st2);
288 if (st1.st_ino == st2.st_ino) {
289 strcpy(path, manpaths.gl_pathv[i]);
290 for(p = path; *(p+1) != '\0';) {
291 p = dirname(p);
292 lstat(p, &st3);
293 if (S_ISLNK(st3.st_mode)) {
294 lnk = 1;
295 break;
296 }
297 }
298 } else {
299 len = readlink(manpaths.gl_pathv[i],
300 path, PATH_MAX);
301 if (len == -1)
302 continue;
303 if (!strcmp(path, manpaths.gl_pathv[j]))
304 lnk = 1;
305 }
306 if (lnk)
307 break;
308 }
309 }
310
311 if (!lnk)
312 addentry(defnewp, manpaths.gl_pathv[i], 0);
313 }
314
315 globfree(&manpaths);
316
317 defp = defnewp;
318 }
319
320 static void
321 catman(void)
322 {
323 ENTRY *e_path;
324 const char *mandir;
325 char catdir[PATH_MAX], *cp;
326
327 TAILQ_FOREACH(e_path, &defp->list, q) {
328 mandir = e_path->s;
329 strcpy(catdir, mandir);
330 if(!(cp = strstr(catdir, "man/man")))
331 continue;
332 cp+=4; *cp++ = 'c'; *cp++ = 'a'; *cp = 't';
333 scanmandir(catdir, mandir);
334 }
335 }
336
337 static void
338 scanmandir(catdir, mandir)
339 const char *catdir;
340 const char *mandir;
341 {
342 TAG *buildp, *crunchp;
343 ENTRY *e_build, *e_crunch;
344 char manpage[PATH_MAX];
345 char catpage[PATH_MAX];
346 char linkname[PATH_MAX];
347 char buffer[PATH_MAX], *bp;
348 char tmp[PATH_MAX];
349 char buildsuff[256], buildcmd[256];
350 char crunchsuff[256], crunchcmd[256];
351 char match[256];
352 struct stat manstat;
353 struct stat catstat;
354 struct stat lnkstat;
355 struct dirent *dp;
356 DIR *dirp;
357 int len, error;
358
359 if ((dirp = opendir(mandir)) == 0) {
360 warn("can't open %s", mandir);
361 return;
362 }
363
364 if (stat(catdir, &catstat) < 0) {
365 if (errno != ENOENT) {
366 warn("can't stat %s", catdir);
367 closedir(dirp);
368 return;
369 }
370 if (f_noprint == 0)
371 printf("mkdir %s\n", catdir);
372 if (f_noaction == 0 && mkdir(catdir,0755) < 0) {
373 warn("can't create %s", catdir);
374 closedir(dirp);
375 return;
376 }
377 }
378
379 while ((dp = readdir(dirp)) != NULL) {
380 if (strcmp(dp->d_name, ".") == 0 ||
381 strcmp(dp->d_name, "..") == 0)
382 continue;
383
384 snprintf(manpage, sizeof(manpage), "%s/%s", mandir, dp->d_name);
385 snprintf(catpage, sizeof(catpage), "%s/%s", catdir, dp->d_name);
386
387 e_build = NULL;
388 buildp = getlist("_build", 1);
389 TAILQ_FOREACH(e_build, &buildp->list, q) {
390 splitentry(e_build->s, buildsuff, buildcmd);
391 snprintf(match, sizeof(match), "*%s",
392 buildsuff);
393 if(!fnmatch(match, manpage, 0))
394 break;
395 }
396
397 if(e_build == NULL)
398 continue;
399
400 e_crunch = NULL;
401 crunchp = getlist("_crunch", 1);
402 TAILQ_FOREACH(e_crunch, &crunchp->list, q) {
403 splitentry(e_crunch->s, crunchsuff, crunchcmd);
404 snprintf(match, sizeof(match), "*%s", crunchsuff);
405 if(!fnmatch(match, manpage, 0))
406 break;
407 }
408
409 if (lstat(manpage, &manstat) <0) {
410 warn("can't stat %s", manpage);
411 continue;
412 } else {
413 if(S_ISLNK(manstat.st_mode)) {
414 strcpy(buffer, catpage);
415 strcpy(linkname, basename(buffer));
416 len = readlink(manpage, buffer, PATH_MAX);
417 if(len == -1) {
418 warn("can't stat read symbolic link %s",
419 manpage);
420 continue;
421 }
422 buffer[len] = '\0';
423 bp = basename(buffer);
424 strcpy(tmp, manpage);
425 snprintf(manpage, sizeof(manpage), "%s/%s",
426 dirname(tmp), bp);
427 strcpy(tmp, catpage);
428 snprintf(catpage, sizeof(catpage), "%s/%s",
429 dirname(tmp), buffer);
430 }
431 else
432 *linkname='\0';
433 }
434
435 if(!e_crunch) {
436 *crunchsuff = *crunchcmd = '\0';
437 }
438 setcatsuffix(catpage, buildsuff, crunchsuff);
439 if(*linkname != '\0')
440 setcatsuffix(linkname, buildsuff, crunchsuff);
441
442 if (stat(manpage, &manstat) < 0) {
443 warn("can't stat %s", manpage);
444 continue;
445 }
446
447 if (!S_ISREG(manstat.st_mode)) {
448 warnx("not a regular file %s",manpage);
449 continue;
450 }
451
452 if ((error = stat(catpage, &catstat)) &&
453 errno != ENOENT) {
454 warn("can't stat %s", catpage);
455 continue;
456 }
457
458 if ((error && errno == ENOENT) ||
459 manstat.st_mtime > catstat.st_mtime) {
460 if (f_noformat) {
461 dowhatis = 1;
462 } else {
463 /*
464 * reformat out of date manpage
465 */
466 makecat(manpage, catpage, buildcmd, crunchcmd);
467 dowhatis = 1;
468 }
469 }
470
471 if(*linkname != '\0') {
472 strcpy(tmp, catpage);
473 snprintf(tmp, sizeof(tmp), "%s/%s", dirname(tmp),
474 linkname);
475 if ((error = lstat(tmp, &lnkstat)) &&
476 errno != ENOENT) {
477 warn("can't stat %s", tmp);
478 continue;
479 }
480
481 if (error && errno == ENOENT) {
482 if (f_noformat) {
483 dowhatis = 1;
484 } else {
485 /*
486 * create symbolic link
487 */
488 if (f_noprint == 0)
489 printf("ln -s %s %s\n", catpage,
490 linkname);
491 if (f_noaction == 0) {
492 strcpy(tmp, catpage);
493 if(chdir(dirname(tmp)) == -1) {
494 warn("can't chdir");
495 continue;
496 }
497
498 if (symlink(catpage, linkname)
499 == -1) {
500 warn("can't create"
501 " symbolic"
502 " link %s",
503 linkname);
504 continue;
505 }
506 }
507 dowhatis = 1;
508 }
509 }
510 }
511 }
512 closedir(dirp);
513 }
514
515 static int
516 splitentry(s, first, second)
517 char *s;
518 char *first;
519 char *second;
520 {
521 char *c;
522
523 for(c = s; *c != '\0' && !isspace(*c); ++c);
524 if(*c == '\0')
525 return(0);
526 strncpy(first, s, c-s);
527 first[c-s] = '\0';
528 for(; *c != '\0' && isspace(*c); ++c);
529 strcpy(second, c);
530 return(1);
531 }
532
533 static void
534 setcatsuffix(catpage, suffix, crunchsuff)
535 char *catpage;
536 const char *suffix;
537 const char *crunchsuff;
538 {
539 TAG *tp;
540 char *p;
541
542 for(p = catpage + strlen(catpage); p!=catpage; p--)
543 if(!fnmatch(suffix, p, 0)) {
544 tp = getlist("_suffix", 1);
545 if (! TAILQ_EMPTY(&tp->list)) {
546 sprintf(p, "%s%s",
547 TAILQ_FIRST(&tp->list)->s, crunchsuff);
548 } else {
549 sprintf(p, ".0%s", crunchsuff);
550 }
551 break;
552 }
553 }
554
555 static void
556 makecat(manpage, catpage, buildcmd, crunchcmd)
557 const char *manpage;
558 const char *catpage;
559 const char *buildcmd;
560 const char *crunchcmd;
561 {
562 char crunchbuf[1024];
563 char sysbuf[2048];
564
565 snprintf(sysbuf, sizeof(sysbuf), buildcmd, manpage);
566
567 if(*crunchcmd != '\0') {
568 snprintf(crunchbuf, sizeof(crunchbuf), crunchcmd, catpage);
569 snprintf(sysbuf, sizeof(sysbuf), "%s | %s", sysbuf, crunchbuf);
570 } else {
571 snprintf(sysbuf, sizeof(sysbuf), "%s > %s", sysbuf, catpage);
572 }
573
574 if (f_noprint == 0)
575 printf("%s\n", sysbuf);
576 if (f_noaction == 0)
577 dosystem(sysbuf);
578 }
579
580 static void
581 makewhatis(void)
582 {
583 TAG *whatdbp;
584 ENTRY *e_whatdb;
585 char sysbuf[1024];
586
587 whatdbp = getlist("_whatdb", 1);
588 TAILQ_FOREACH(e_whatdb, &whatdbp->list, q) {
589 snprintf(sysbuf, sizeof(sysbuf), "%s %s",
590 _PATH_WHATIS, dirname(e_whatdb->s));
591 if (f_noprint == 0)
592 printf("%s\n", sysbuf);
593 if (f_noaction == 0)
594 dosystem(sysbuf);
595 }
596 }
597
598 static void
599 dosystem(cmd)
600 const char *cmd;
601 {
602 int status;
603
604 if ((status = system(cmd)) == 0)
605 return;
606
607 if (status == -1)
608 err(1, "cannot execute action");
609 if (WIFSIGNALED(status))
610 errx(1, "child was signaled to quit. aborting");
611 if (WIFSTOPPED(status))
612 errx(1, "child was stopped. aborting");
613 if (f_ignerr == 0)
614 errx(1, "*** Exited %d", status);
615 warnx("*** Exited %d (continuing)", status);
616 }
617
618 static void
619 usage(void)
620 {
621 (void)fprintf(stderr,
622 "usage: catman [-knpsw] [-m manpath] [sections]\n");
623 (void)fprintf(stderr,
624 " catman [-knpsw] [-M manpath] [sections]\n");
625 exit(1);
626 }
627