1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2012, Josef 'Jeff' Sipek <jeffpc@31bits.net>. All rights reserved.
25 * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
26 * Copyright 2014 Garrett D'Amore <garrett@damore.org>
27 */
28
29 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T. */
30 /* All rights reserved. */
31
32 /*
33 * University Copyright- Copyright (c) 1982, 1986, 1988
34 * The Regents of the University of California
35 * All Rights Reserved
36 *
37 * University Acknowledgment- Portions of this document are derived from
38 * software developed by the University of California, Berkeley, and its
39 * contributors.
40 */
41
42 /*
43 * Find and display reference manual pages. This version includes makewhatis
44 * functionality as well.
45 */
46
47 #include <sys/param.h>
48 #include <sys/stat.h>
49 #include <sys/termios.h>
50 #include <sys/types.h>
51
52 #include <ctype.h>
53 #include <dirent.h>
54 #include <err.h>
55 #include <errno.h>
56 #include <fcntl.h>
57 #include <fnmatch.h>
58 #include <limits.h>
59 #include <locale.h>
60 #include <malloc.h>
61 #include <memory.h>
62 #include <regex.h>
63 #include <stdio.h>
64 #include <stdlib.h>
65 #include <string.h>
66 #include <unistd.h>
67
68 #include "man.h"
69
70
71 /* Mapping of old directories to new directories */
72 static const struct map_entry {
73 char *old_name;
74 char *new_name;
75 } map[] = {
76 { "3b", "3ucb" },
77 { "3e", "3elf" },
78 { "3g", "3gen" },
79 { "3k", "3kstat" },
80 { "3n", "3socket" },
81 { "3r", "3rt" },
82 { "3s", "3c" },
83 { "3t", "3thr" },
84 { "3x", "3curses" },
85 { "3xc", "3xcurses" },
86 { "3xn", "3xnet" },
87 { NULL, NULL }
88 };
89
90 struct suffix {
91 char *ds;
92 char *fs;
93 };
94
95 /*
96 * Flags that control behavior of build_manpath()
97 *
98 * BMP_ISPATH pathv is a vector constructed from PATH.
99 * Perform appropriate path translations for
100 * manpath.
101 * BMP_APPEND_DEFMANDIR Add DEFMANDIR to the end if it hasn't
102 * already appeared earlier.
103 * BMP_FALLBACK_DEFMANDIR Append /usr/share/man only if no other
104 * manpath (including derived from PATH)
105 * elements are valid.
106 */
107 #define BMP_ISPATH 1
108 #define BMP_APPEND_DEFMANDIR 2
109 #define BMP_FALLBACK_DEFMANDIR 4
110
111 /*
112 * When doing equality comparisons of directories, device and inode
113 * comparisons are done. The secnode and dupnode structures are used
114 * to form a list of lists for this processing.
115 */
116 struct secnode {
117 char *secp;
118 struct secnode *next;
119 };
120 struct dupnode {
121 dev_t dev; /* from struct stat st_dev */
122 ino_t ino; /* from struct stat st_ino */
123 struct secnode *secl; /* sections already considered */
124 struct dupnode *next;
125 };
126
127 /*
128 * Map directories that may appear in PATH to the corresponding
129 * man directory.
130 */
131 static struct pathmap {
132 char *bindir;
133 char *mandir;
134 dev_t dev;
135 ino_t ino;
136 } bintoman[] = {
137 { "/sbin", "/usr/share/man,1m", 0, 0 },
138 { "/usr/sbin", "/usr/share/man,1m", 0, 0 },
139 { "/usr/ucb", "/usr/share/man,1b", 0, 0 },
140 { "/usr/bin", "/usr/share/man,1,1m,1s,1t,1c", 0, 0 },
141 { "/usr/xpg4/bin", "/usr/share/man,1", 0, 0 },
142 { "/usr/xpg6/bin", "/usr/share/man,1", 0, 0 },
143 { NULL, NULL, 0, 0 }
144 };
145
146 struct man_node {
147 char *path; /* mandir path */
148 char **secv; /* submandir suffices */
149 int defsrch; /* hint for man -p */
150 int frompath; /* hint for man -d */
151 struct man_node *next;
152 };
153
154 static int all = 0;
155 static int apropos = 0;
156 static int debug = 0;
157 static int found = 0;
158 static int list = 0;
159 static int makewhatis = 0;
160 static int printmp = 0;
161 static int sargs = 0;
162 static int psoutput = 0;
163 static int lintout = 0;
164 static int whatis = 0;
165 static int makewhatishere = 0;
166
167 static char *mansec;
168 static char *pager = NULL;
169
170 static char *addlocale(char *);
171 static struct man_node *build_manpath(char **, int);
172 static void do_makewhatis(struct man_node *);
173 static char *check_config(char *);
174 static int cmp(const void *, const void *);
175 static int dupcheck(struct man_node *, struct dupnode **);
176 static int format(char *, char *, char *, char *);
177 static void free_dupnode(struct dupnode *);
178 static void free_manp(struct man_node *manp);
179 static void freev(char **);
180 static void fullpaths(struct man_node **);
181 static void get_all_sect(struct man_node *);
182 static int getdirs(char *, char ***, int);
183 static void getpath(struct man_node *, char **);
184 static void getsect(struct man_node *, char **);
185 static void init_bintoman(void);
186 static void lower(char *);
187 static void mandir(char **, char *, char *, int);
188 static int manual(struct man_node *, char *);
189 static char *map_section(char *, char *);
190 static char *path_to_manpath(char *);
191 static void print_manpath(struct man_node *);
192 static void search_whatis(char *, char *);
193 static int searchdir(char *, char *, char *);
194 static void sortdir(DIR *, char ***);
195 static char **split(char *, char);
196 static void usage_man(void);
197 static void usage_whatapro(void);
198 static void usage_catman(void);
199 static void usage_makewhatis(void);
200 static void whatapro(struct man_node *, char *);
201
202 static char language[MAXPATHLEN]; /* LC_MESSAGES */
203 static char localedir[MAXPATHLEN]; /* locale specific path component */
204
205 static char *newsection = NULL;
206
207 static int manwidth = 0;
208
209 extern const char *__progname;
210
211 int
main(int argc,char ** argv)212 main(int argc, char **argv)
213 {
214 int c, i;
215 char **pathv;
216 char *manpath = NULL;
217 static struct man_node *mandirs = NULL;
218 int bmp_flags = 0;
219 int ret = 0;
220 char *opts;
221 char *mwstr;
222 int catman = 0;
223
224 (void) setlocale(LC_ALL, "");
225 (void) strcpy(language, setlocale(LC_MESSAGES, (char *)NULL));
226 if (strcmp("C", language) != 0)
227 (void) strlcpy(localedir, language, MAXPATHLEN);
228
229 #if !defined(TEXT_DOMAIN)
230 #define TEXT_DOMAIN "SYS_TEST"
231 #endif
232 (void) textdomain(TEXT_DOMAIN);
233
234 if (strcmp(__progname, "apropos") == 0) {
235 apropos++;
236 opts = "M:ds:";
237 } else if (strcmp(__progname, "whatis") == 0) {
238 apropos++;
239 whatis++;
240 opts = "M:ds:";
241 } else if (strcmp(__progname, "catman") == 0) {
242 catman++;
243 makewhatis++;
244 opts = "P:M:w";
245 } else if (strcmp(__progname, "makewhatis") == 0) {
246 makewhatis++;
247 makewhatishere++;
248 manpath = ".";
249 opts = "";
250 } else {
251 opts = "FM:P:T:adfklprs:tw";
252 if (argc > 1 && strcmp(argv[1], "-") == 0) {
253 pager = "cat";
254 optind++;
255 }
256 }
257
258 opterr = 0;
259 while ((c = getopt(argc, argv, opts)) != -1) {
260 switch (c) {
261 case 'M': /* Respecify path for man pages */
262 manpath = optarg;
263 break;
264 case 'a':
265 all++;
266 break;
267 case 'd':
268 debug++;
269 break;
270 case 'f':
271 whatis++;
272 /*FALLTHROUGH*/
273 case 'k':
274 apropos++;
275 break;
276 case 'l':
277 list++;
278 all++;
279 break;
280 case 'p':
281 printmp++;
282 break;
283 case 's':
284 mansec = optarg;
285 sargs++;
286 break;
287 case 'r':
288 lintout++;
289 break;
290 case 't':
291 psoutput++;
292 break;
293 case 'T':
294 case 'P':
295 case 'F':
296 /* legacy options, compatibility only and ignored */
297 break;
298 case 'w':
299 makewhatis++;
300 break;
301 case '?':
302 default:
303 if (apropos)
304 usage_whatapro();
305 else if (catman)
306 usage_catman();
307 else if (makewhatishere)
308 usage_makewhatis();
309 else
310 usage_man();
311 }
312 }
313 argc -= optind;
314 argv += optind;
315
316 if (argc == 0) {
317 if (apropos) {
318 (void) fprintf(stderr, gettext("%s what?\n"),
319 __progname);
320 exit(1);
321 } else if (!printmp && !makewhatis) {
322 (void) fprintf(stderr,
323 gettext("What manual page do you want?\n"));
324 exit(1);
325 }
326 }
327
328 init_bintoman();
329 if (manpath == NULL && (manpath = getenv("MANPATH")) == NULL) {
330 if ((manpath = getenv("PATH")) != NULL)
331 bmp_flags = BMP_ISPATH | BMP_APPEND_DEFMANDIR;
332 else
333 manpath = DEFMANDIR;
334 }
335 pathv = split(manpath, ':');
336 mandirs = build_manpath(pathv, bmp_flags);
337 freev(pathv);
338 fullpaths(&mandirs);
339
340 if (makewhatis) {
341 do_makewhatis(mandirs);
342 exit(0);
343 }
344
345 if (printmp) {
346 print_manpath(mandirs);
347 exit(0);
348 }
349
350 /* Collect environment information */
351 if (isatty(STDOUT_FILENO) && (mwstr = getenv("MANWIDTH")) != NULL &&
352 *mwstr != '\0') {
353 if (strcasecmp(mwstr, "tty") == 0) {
354 struct winsize ws;
355
356 if (ioctl(0, TIOCGWINSZ, &ws) != 0)
357 warn("TIOCGWINSZ");
358 else
359 manwidth = ws.ws_col;
360 } else {
361 manwidth = (int)strtol(mwstr, (char **)NULL, 10);
362 if (manwidth < 0)
363 manwidth = 0;
364 }
365 }
366 if (manwidth != 0) {
367 DPRINTF("-- Using non-standard page width: %d\n", manwidth);
368 }
369
370 if (pager == NULL) {
371 if ((pager = getenv("PAGER")) == NULL || *pager == '\0')
372 pager = PAGER;
373 }
374 DPRINTF("-- Using pager: %s\n", pager);
375
376 for (i = 0; i < argc; i++) {
377 char *cmd;
378 static struct man_node *mp;
379 char *pv[2];
380
381 /*
382 * If full path to command specified, customize
383 * the manpath accordingly.
384 */
385 if ((cmd = strrchr(argv[i], '/')) != NULL) {
386 *cmd = '\0';
387 if ((pv[0] = strdup(argv[i])) == NULL)
388 err(1, "strdup");
389 pv[1] = NULL;
390 *cmd = '/';
391 mp = build_manpath(pv,
392 BMP_ISPATH | BMP_FALLBACK_DEFMANDIR);
393 } else {
394 mp = mandirs;
395 }
396
397 if (apropos)
398 whatapro(mp, argv[i]);
399 else
400 ret += manual(mp, argv[i]);
401
402 if (mp != NULL && mp != mandirs) {
403 free(pv[0]);
404 free_manp(mp);
405 }
406 }
407
408 return (ret == 0 ? 0 : 1);
409 }
410
411 /*
412 * This routine builds the manpage structure from MANPATH or PATH,
413 * depending on flags. See BMP_* definitions above for valid
414 * flags.
415 */
416 static struct man_node *
build_manpath(char ** pathv,int flags)417 build_manpath(char **pathv, int flags)
418 {
419 struct man_node *manpage = NULL;
420 struct man_node *currp = NULL;
421 struct man_node *lastp = NULL;
422 char **p;
423 char **q;
424 char *mand = NULL;
425 char *mandir = DEFMANDIR;
426 int s;
427 struct dupnode *didup = NULL;
428 struct stat sb;
429
430 s = sizeof (struct man_node);
431 for (p = pathv; *p != NULL; ) {
432 if (flags & BMP_ISPATH) {
433 if ((mand = path_to_manpath(*p)) == NULL)
434 goto next;
435 free(*p);
436 *p = mand;
437 }
438 q = split(*p, ',');
439 if (stat(q[0], &sb) != 0 || (sb.st_mode & S_IFDIR) == 0) {
440 freev(q);
441 goto next;
442 }
443
444 if (access(q[0], R_OK | X_OK) == 0) {
445 /*
446 * Some element exists. Do not append DEFMANDIR as a
447 * fallback.
448 */
449 flags &= ~BMP_FALLBACK_DEFMANDIR;
450
451 if ((currp = (struct man_node *)calloc(1, s)) == NULL)
452 err(1, "calloc");
453
454 currp->frompath = (flags & BMP_ISPATH);
455
456 if (manpage == NULL)
457 lastp = manpage = currp;
458
459 getpath(currp, p);
460 getsect(currp, p);
461
462 /*
463 * If there are no new elements in this path,
464 * do not add it to the manpage list.
465 */
466 if (dupcheck(currp, &didup) != 0) {
467 freev(currp->secv);
468 free(currp);
469 } else {
470 currp->next = NULL;
471 if (currp != manpage)
472 lastp->next = currp;
473 lastp = currp;
474 }
475 }
476 freev(q);
477 next:
478 /*
479 * Special handling of appending DEFMANDIR. After all pathv
480 * elements have been processed, append DEFMANDIR if needed.
481 */
482 if (p == &mandir)
483 break;
484 p++;
485 if (*p != NULL)
486 continue;
487 if (flags & (BMP_APPEND_DEFMANDIR | BMP_FALLBACK_DEFMANDIR)) {
488 p = &mandir;
489 flags &= ~BMP_ISPATH;
490 }
491 }
492
493 free_dupnode(didup);
494
495 return (manpage);
496 }
497
498 /*
499 * Store the mandir path into the manp structure.
500 */
501 static void
getpath(struct man_node * manp,char ** pv)502 getpath(struct man_node *manp, char **pv)
503 {
504 char *s = *pv;
505 int i = 0;
506
507 while (*s != '\0' && *s != ',')
508 i++, s++;
509
510 if ((manp->path = (char *)malloc(i + 1)) == NULL)
511 err(1, "malloc");
512 (void) strlcpy(manp->path, *pv, i + 1);
513 }
514
515 /*
516 * Store the mandir's corresponding sections (submandir
517 * directories) into the manp structure.
518 */
519 static void
getsect(struct man_node * manp,char ** pv)520 getsect(struct man_node *manp, char **pv)
521 {
522 char *sections;
523 char **sectp;
524
525 /* Just store all sections when doing makewhatis or apropos/whatis */
526 if (makewhatis || apropos) {
527 manp->defsrch = 1;
528 DPRINTF("-- Adding %s\n", manp->path);
529 manp->secv = NULL;
530 get_all_sect(manp);
531 } else if (sargs) {
532 manp->secv = split(mansec, ',');
533 for (sectp = manp->secv; *sectp; sectp++)
534 lower(*sectp);
535 } else if ((sections = strchr(*pv, ',')) != NULL) {
536 DPRINTF("-- Adding %s: MANSECTS=%s\n", manp->path, sections);
537 manp->secv = split(++sections, ',');
538 for (sectp = manp->secv; *sectp; sectp++)
539 lower(*sectp);
540 if (*manp->secv == NULL)
541 get_all_sect(manp);
542 } else if ((sections = check_config(*pv)) != NULL) {
543 manp->defsrch = 1;
544 DPRINTF("-- Adding %s: from %s, MANSECTS=%s\n", manp->path,
545 CONFIG, sections);
546 manp->secv = split(sections, ',');
547 for (sectp = manp->secv; *sectp; sectp++)
548 lower(*sectp);
549 if (*manp->secv == NULL)
550 get_all_sect(manp);
551 } else {
552 manp->defsrch = 1;
553 DPRINTF("-- Adding %s: default sort order\n", manp->path);
554 manp->secv = NULL;
555 get_all_sect(manp);
556 }
557 }
558
559 /*
560 * Get suffices of all sub-mandir directories in a mandir.
561 */
562 static void
get_all_sect(struct man_node * manp)563 get_all_sect(struct man_node *manp)
564 {
565 DIR *dp;
566 char **dirv;
567 char **dv;
568 char **p;
569 char *prev = NULL;
570 char *tmp = NULL;
571 int maxentries = MAXTOKENS;
572 int entries = 0;
573
574 if ((dp = opendir(manp->path)) == 0)
575 return;
576
577 sortdir(dp, &dirv);
578
579 (void) closedir(dp);
580
581 if (manp->secv == NULL) {
582 if ((manp->secv = malloc(maxentries * sizeof (char *))) == NULL)
583 err(1, "malloc");
584 }
585
586 for (dv = dirv, p = manp->secv; *dv; dv++) {
587 if (strcmp(*dv, CONFIG) == 0) {
588 free(*dv);
589 continue;
590 }
591
592 free(tmp);
593 if ((tmp = strdup(*dv + 3)) == NULL)
594 err(1, "strdup");
595
596 if (prev != NULL && strcmp(prev, tmp) == 0) {
597 free(*dv);
598 continue;
599 }
600
601 free(prev);
602 if ((prev = strdup(*dv + 3)) == NULL)
603 err(1, "strdup");
604
605 if ((*p = strdup(*dv + 3)) == NULL)
606 err(1, "strdup");
607
608 p++; entries++;
609
610 if (entries == maxentries) {
611 maxentries += MAXTOKENS;
612 if ((manp->secv = realloc(manp->secv,
613 sizeof (char *) * maxentries)) == NULL)
614 err(1, "realloc");
615 p = manp->secv + entries;
616 }
617 free(*dv);
618 }
619 free(tmp);
620 free(prev);
621 *p = NULL;
622 free(dirv);
623 }
624
625 /*
626 * Build whatis databases.
627 */
628 static void
do_makewhatis(struct man_node * manp)629 do_makewhatis(struct man_node *manp)
630 {
631 struct man_node *p;
632 char *ldir;
633
634 for (p = manp; p != NULL; p = p->next) {
635 ldir = addlocale(p->path);
636 if (*localedir != '\0' && getdirs(ldir, NULL, 0) > 0)
637 mwpath(ldir);
638 free(ldir);
639 mwpath(p->path);
640 }
641 }
642
643 /*
644 * Count mandirs under the given manpath
645 */
646 static int
getdirs(char * path,char *** dirv,int flag)647 getdirs(char *path, char ***dirv, int flag)
648 {
649 DIR *dp;
650 struct dirent *d;
651 int n = 0;
652 int maxentries = MAXDIRS;
653 char **dv = NULL;
654
655 if ((dp = opendir(path)) == NULL)
656 return (0);
657
658 if (flag) {
659 if ((*dirv = malloc(sizeof (char *) *
660 maxentries)) == NULL)
661 err(1, "malloc");
662 dv = *dirv;
663 }
664 while ((d = readdir(dp))) {
665 if (strncmp(d->d_name, "man", 3) != 0)
666 continue;
667 n++;
668
669 if (flag) {
670 if ((*dv = strdup(d->d_name + 3)) == NULL)
671 err(1, "strdup");
672 dv++;
673 if ((dv - *dirv) == maxentries) {
674 int entries = maxentries;
675
676 maxentries += MAXTOKENS;
677 if ((*dirv = realloc(*dirv,
678 sizeof (char *) * maxentries)) == NULL)
679 err(1, "realloc");
680 dv = *dirv + entries;
681 }
682 }
683 }
684
685 (void) closedir(dp);
686 return (n);
687 }
688
689
690 /*
691 * Find matching whatis or apropos entries.
692 */
693 static void
whatapro(struct man_node * manp,char * word)694 whatapro(struct man_node *manp, char *word)
695 {
696 char whatpath[MAXPATHLEN];
697 struct man_node *b;
698 char *ldir;
699
700 for (b = manp; b != NULL; b = b->next) {
701 if (*localedir != '\0') {
702 ldir = addlocale(b->path);
703 if (getdirs(ldir, NULL, 0) != 0) {
704 (void) snprintf(whatpath, sizeof (whatpath),
705 "%s/%s", ldir, WHATIS);
706 search_whatis(whatpath, word);
707 }
708 free(ldir);
709 }
710 (void) snprintf(whatpath, sizeof (whatpath), "%s/%s", b->path,
711 WHATIS);
712 search_whatis(whatpath, word);
713 }
714 }
715
716 static void
search_whatis(char * whatpath,char * word)717 search_whatis(char *whatpath, char *word)
718 {
719 FILE *fp;
720 char *line = NULL;
721 size_t linecap = 0;
722 char *pkwd;
723 regex_t preg;
724 char **ss = NULL;
725 char s[MAXNAMELEN];
726 int i;
727
728 if ((fp = fopen(whatpath, "r")) == NULL) {
729 perror(whatpath);
730 return;
731 }
732
733 DPRINTF("-- Found %s: %s\n", WHATIS, whatpath);
734
735 /* Build keyword regex */
736 if (asprintf(&pkwd, "%s%s%s", (whatis) ? "\\<" : "",
737 word, (whatis) ? "\\>" : "") == -1)
738 err(1, "asprintf");
739
740 if (regcomp(&preg, pkwd, REG_BASIC | REG_ICASE | REG_NOSUB) != 0)
741 err(1, "regcomp");
742
743 if (sargs)
744 ss = split(mansec, ',');
745
746 while (getline(&line, &linecap, fp) > 0) {
747 if (regexec(&preg, line, 0, NULL, 0) == 0) {
748 if (sargs) {
749 /* Section-restricted search */
750 for (i = 0; ss[i] != NULL; i++) {
751 (void) snprintf(s, sizeof (s), "(%s)",
752 ss[i]);
753 if (strstr(line, s) != NULL) {
754 (void) printf("%s", line);
755 break;
756 }
757 }
758 } else {
759 (void) printf("%s", line);
760 }
761 }
762 }
763
764 if (ss != NULL)
765 freev(ss);
766 free(pkwd);
767 (void) fclose(fp);
768 }
769
770
771 /*
772 * Split a string by specified separator.
773 */
774 static char **
split(char * s1,char sep)775 split(char *s1, char sep)
776 {
777 char **tokv, **vp;
778 char *mp = s1, *tp;
779 int maxentries = MAXTOKENS;
780 int entries = 0;
781
782 if ((tokv = vp = malloc(maxentries * sizeof (char *))) == NULL)
783 err(1, "malloc");
784
785 for (; mp && *mp; mp = tp) {
786 tp = strchr(mp, sep);
787 if (mp == tp) {
788 tp++;
789 continue;
790 }
791 if (tp) {
792 size_t len;
793
794 len = tp - mp;
795 if ((*vp = (char *)malloc(sizeof (char) *
796 len + 1)) == NULL)
797 err(1, "malloc");
798 (void) strncpy(*vp, mp, len);
799 *(*vp + len) = '\0';
800 tp++;
801 vp++;
802 } else {
803 if ((*vp = strdup(mp)) == NULL)
804 err(1, "strdup");
805 vp++;
806 }
807 entries++;
808 if (entries == maxentries) {
809 maxentries += MAXTOKENS;
810 if ((tokv = realloc(tokv,
811 maxentries * sizeof (char *))) == NULL)
812 err(1, "realloc");
813 vp = tokv + entries;
814 }
815 }
816 *vp = 0;
817
818 return (tokv);
819 }
820
821 /*
822 * Free a vector allocated by split()
823 */
824 static void
freev(char ** v)825 freev(char **v)
826 {
827 int i;
828 if (v != NULL) {
829 for (i = 0; v[i] != NULL; i++) {
830 free(v[i]);
831 }
832 free(v);
833 }
834 }
835
836 /*
837 * Convert paths to full paths if necessary
838 */
839 static void
fullpaths(struct man_node ** manp_head)840 fullpaths(struct man_node **manp_head)
841 {
842 char *cwd = NULL;
843 char *p;
844 int cwd_gotten = 0;
845 struct man_node *manp = *manp_head;
846 struct man_node *b;
847 struct man_node *prev = NULL;
848
849 for (b = manp; b != NULL; b = b->next) {
850 if (*(b->path) == '/') {
851 prev = b;
852 continue;
853 }
854
855 if (!cwd_gotten) {
856 cwd = getcwd(NULL, MAXPATHLEN);
857 cwd_gotten = 1;
858 }
859
860 if (cwd) {
861 /* Relative manpath with cwd: make absolute */
862 if (asprintf(&p, "%s/%s", cwd, b->path) == -1)
863 err(1, "asprintf");
864 free(b->path);
865 b->path = p;
866 } else {
867 /* Relative manpath but no cwd: omit path entry */
868 if (prev)
869 prev->next = b->next;
870 else
871 *manp_head = b->next;
872
873 free_manp(b);
874 }
875 }
876 free(cwd);
877 }
878
879 /*
880 * Free a man_node structure and its contents
881 */
882 static void
free_manp(struct man_node * manp)883 free_manp(struct man_node *manp)
884 {
885 char **p;
886
887 free(manp->path);
888 p = manp->secv;
889 while ((p != NULL) && (*p != NULL)) {
890 free(*p);
891 p++;
892 }
893 free(manp->secv);
894 free(manp);
895 }
896
897
898 /*
899 * Map (in place) to lower case.
900 */
901 static void
lower(char * s)902 lower(char *s)
903 {
904
905 if (s == 0)
906 return;
907 while (*s) {
908 if (isupper(*s))
909 *s = tolower(*s);
910 s++;
911 }
912 }
913
914
915 /*
916 * Compare function for qsort().
917 * Sort first by section, then by prefix.
918 */
919 static int
cmp(const void * arg1,const void * arg2)920 cmp(const void *arg1, const void *arg2)
921 {
922 int n;
923 char **p1 = (char **)arg1;
924 char **p2 = (char **)arg2;
925
926 /* By section */
927 if ((n = strcmp(*p1 + 3, *p2 + 3)) != 0)
928 return (n);
929
930 /* By prefix reversed */
931 return (strncmp(*p2, *p1, 3));
932 }
933
934
935 /*
936 * Find a manpage.
937 */
938 static int
manual(struct man_node * manp,char * name)939 manual(struct man_node *manp, char *name)
940 {
941 struct man_node *p;
942 struct man_node *local;
943 int ndirs = 0;
944 char *ldir;
945 char *ldirs[2];
946 char *fullname = name;
947 char *slash;
948
949 if ((slash = strrchr(name, '/')) != NULL)
950 name = slash + 1;
951
952 /* For each path in MANPATH */
953 found = 0;
954
955 for (p = manp; p != NULL; p = p->next) {
956 DPRINTF("-- Searching mandir: %s\n", p->path);
957
958 if (*localedir != '\0') {
959 ldir = addlocale(p->path);
960 ndirs = getdirs(ldir, NULL, 0);
961 if (ndirs != 0) {
962 ldirs[0] = ldir;
963 ldirs[1] = NULL;
964 local = build_manpath(ldirs, 0);
965 DPRINTF("-- Locale specific subdir: %s\n",
966 ldir);
967 mandir(local->secv, ldir, name, 1);
968 free_manp(local);
969 }
970 free(ldir);
971 }
972
973 /*
974 * Locale mandir not valid, man page in locale
975 * mandir not found, or -a option present
976 */
977 if (ndirs == 0 || !found || all)
978 mandir(p->secv, p->path, name, 0);
979
980 if (found && !all)
981 break;
982 }
983
984 if (!found) {
985 if (sargs) {
986 (void) fprintf(stderr, gettext(
987 "No manual entry for %s in section(s) %s\n"),
988 fullname, mansec);
989 } else {
990 (void) fprintf(stderr,
991 gettext("No manual entry for %s\n"), fullname);
992 }
993
994 }
995
996 return (!found);
997 }
998
999
1000 /*
1001 * For a specified manual directory, read, store and sort section subdirs.
1002 * For each section specified, find and search matching subdirs.
1003 */
1004 static void
mandir(char ** secv,char * path,char * name,int lspec)1005 mandir(char **secv, char *path, char *name, int lspec)
1006 {
1007 DIR *dp;
1008 char **dirv;
1009 char **dv, **pdv;
1010 int len, dslen;
1011
1012 if ((dp = opendir(path)) == NULL)
1013 return;
1014
1015 if (lspec)
1016 DPRINTF("-- Searching mandir: %s\n", path);
1017
1018 sortdir(dp, &dirv);
1019
1020 /* Search in the order specified by MANSECTS */
1021 for (; *secv; secv++) {
1022 len = strlen(*secv);
1023 for (dv = dirv; *dv; dv++) {
1024 dslen = strlen(*dv + 3);
1025 if (dslen > len)
1026 len = dslen;
1027 if (**secv == '\\') {
1028 if (strcmp(*secv + 1, *dv + 3) != 0)
1029 continue;
1030 } else if (strncasecmp(*secv, *dv + 3, len) != 0) {
1031 if (!all &&
1032 (newsection = map_section(*secv, path))
1033 == NULL) {
1034 continue;
1035 }
1036 if (newsection == NULL)
1037 newsection = "";
1038 if (strncmp(newsection, *dv + 3, len) != 0) {
1039 continue;
1040 }
1041 }
1042
1043 if (searchdir(path, *dv, name) == 0)
1044 continue;
1045
1046 if (!all) {
1047 pdv = dirv;
1048 while (*pdv) {
1049 free(*pdv);
1050 pdv++;
1051 }
1052 (void) closedir(dp);
1053 free(dirv);
1054 return;
1055 }
1056
1057 if (all && **dv == 'm' && *(dv + 1) &&
1058 strcmp(*(dv + 1) + 3, *dv + 3) == 0)
1059 dv++;
1060 }
1061 }
1062 pdv = dirv;
1063 while (*pdv != NULL) {
1064 free(*pdv);
1065 pdv++;
1066 }
1067 free(dirv);
1068 (void) closedir(dp);
1069 }
1070
1071 /*
1072 * Sort directories.
1073 */
1074 static void
sortdir(DIR * dp,char *** dirv)1075 sortdir(DIR *dp, char ***dirv)
1076 {
1077 struct dirent *d;
1078 char **dv;
1079 int maxentries = MAXDIRS;
1080 int entries = 0;
1081
1082 if ((dv = *dirv = malloc(sizeof (char *) *
1083 maxentries)) == NULL)
1084 err(1, "malloc");
1085 dv = *dirv;
1086
1087 while ((d = readdir(dp))) {
1088 if (strcmp(d->d_name, ".") == 0 ||
1089 strcmp(d->d_name, "..") == 0)
1090 continue;
1091
1092 if (strncmp(d->d_name, "man", 3) == 0 ||
1093 strncmp(d->d_name, "cat", 3) == 0) {
1094 if ((*dv = strdup(d->d_name)) == NULL)
1095 err(1, "strdup");
1096 dv++;
1097 entries++;
1098 if (entries == maxentries) {
1099 maxentries += MAXDIRS;
1100 if ((*dirv = realloc(*dirv,
1101 sizeof (char *) * maxentries)) == NULL)
1102 err(1, "realloc");
1103 dv = *dirv + entries;
1104 }
1105 }
1106 }
1107 *dv = 0;
1108
1109 qsort((void *)*dirv, dv - *dirv, sizeof (char *), cmp);
1110
1111 }
1112
1113
1114 /*
1115 * Search a section subdir for a given manpage.
1116 */
1117 static int
searchdir(char * path,char * dir,char * name)1118 searchdir(char *path, char *dir, char *name)
1119 {
1120 DIR *sdp;
1121 struct dirent *sd;
1122 char sectpath[MAXPATHLEN];
1123 char file[MAXNAMLEN];
1124 char dname[MAXPATHLEN];
1125 char *last;
1126 int nlen;
1127
1128 (void) snprintf(sectpath, sizeof (sectpath), "%s/%s", path, dir);
1129 (void) snprintf(file, sizeof (file), "%s.", name);
1130
1131 if ((sdp = opendir(sectpath)) == NULL)
1132 return (0);
1133
1134 while ((sd = readdir(sdp))) {
1135 char *pname;
1136
1137 if ((pname = strdup(sd->d_name)) == NULL)
1138 err(1, "strdup");
1139 if ((last = strrchr(pname, '.')) != NULL &&
1140 (strcmp(last, ".gz") == 0 || strcmp(last, ".bz2") == 0))
1141 *last = '\0';
1142 last = strrchr(pname, '.');
1143 nlen = last - pname;
1144 (void) snprintf(dname, sizeof (dname), "%.*s.", nlen, pname);
1145 if (strcmp(dname, file) == 0 ||
1146 strcmp(pname, name) == 0) {
1147 (void) format(path, dir, name, sd->d_name);
1148 (void) closedir(sdp);
1149 free(pname);
1150 return (1);
1151 }
1152 free(pname);
1153 }
1154 (void) closedir(sdp);
1155
1156 return (0);
1157 }
1158
1159 /*
1160 * Check the hash table of old directory names to see if there is a
1161 * new directory name.
1162 */
1163 static char *
map_section(char * section,char * path)1164 map_section(char *section, char *path)
1165 {
1166 int i;
1167 char fullpath[MAXPATHLEN];
1168
1169 if (list) /* -l option fall through */
1170 return (NULL);
1171
1172 for (i = 0; map[i].new_name != NULL; i++) {
1173 if (strcmp(section, map[i].old_name) == 0) {
1174 (void) snprintf(fullpath, sizeof (fullpath),
1175 "%s/man%s", path, map[i].new_name);
1176 if (!access(fullpath, R_OK | X_OK)) {
1177 return (map[i].new_name);
1178 } else {
1179 return (NULL);
1180 }
1181 }
1182 }
1183
1184 return (NULL);
1185 }
1186
1187 /*
1188 * Format the manpage.
1189 */
1190 static int
format(char * path,char * dir,char * name,char * pg)1191 format(char *path, char *dir, char *name, char *pg)
1192 {
1193 char manpname[MAXPATHLEN], catpname[MAXPATHLEN];
1194 char cmdbuf[BUFSIZ], tmpbuf[BUFSIZ];
1195 char *cattool;
1196 int utf8 = 0;
1197 struct stat sbman, sbcat;
1198
1199 found++;
1200
1201 if (list) {
1202 (void) printf(gettext("%s(%s)\t-M %s\n"), name, dir + 3, path);
1203 return (-1);
1204 }
1205
1206 (void) snprintf(manpname, sizeof (manpname), "%s/man%s/%s", path,
1207 dir + 3, pg);
1208 (void) snprintf(catpname, sizeof (catpname), "%s/cat%s/%s", path,
1209 dir + 3, pg);
1210
1211 /* Can't do PS output if manpage doesn't exist */
1212 if (stat(manpname, &sbman) != 0 && (psoutput|lintout))
1213 return (-1);
1214
1215 /*
1216 * If both manpage and catpage do not exist, manpname is
1217 * broken symlink, most likely.
1218 */
1219 if (stat(catpname, &sbcat) != 0 && stat(manpname, &sbman) != 0)
1220 err(1, "%s", manpname);
1221
1222 /* Setup cattool */
1223 if (fnmatch("*.gz", manpname, 0) == 0)
1224 cattool = "gzcat";
1225 else if (fnmatch("*.bz2", manpname, 0) == 0)
1226 cattool = "bzcat";
1227 else
1228 cattool = "cat";
1229
1230 /* Preprocess UTF-8 input with preconv (could be smarter) */
1231 if (strstr(path, "UTF-8") != NULL)
1232 utf8 = 1;
1233
1234 if (psoutput) {
1235 (void) snprintf(cmdbuf, BUFSIZ,
1236 "cd %s; %s %s%s | mandoc -Tps | lp -Tpostscript",
1237 path, cattool, manpname,
1238 utf8 ? " | " PRECONV " -e UTF-8" : "");
1239 DPRINTF("-- Using manpage: %s\n", manpname);
1240 goto cmd;
1241 } else if (lintout) {
1242 (void) snprintf(cmdbuf, BUFSIZ,
1243 "cd %s; %s %s%s | mandoc -Tlint",
1244 path, cattool, manpname,
1245 utf8 ? " | " PRECONV " -e UTF-8" : "");
1246 DPRINTF("-- Linting manpage: %s\n", manpname);
1247 goto cmd;
1248 }
1249
1250 /*
1251 * Output catpage if:
1252 * - manpage doesn't exist
1253 * - output width is standard and catpage is recent enough
1254 */
1255 if (stat(manpname, &sbman) != 0 || (manwidth == 0 &&
1256 stat(catpname, &sbcat) == 0 && sbcat.st_mtime >= sbman.st_mtime)) {
1257 DPRINTF("-- Using catpage: %s\n", catpname);
1258 (void) snprintf(cmdbuf, BUFSIZ, "%s %s", pager, catpname);
1259 goto cmd;
1260 }
1261
1262 DPRINTF("-- Using manpage: %s\n", manpname);
1263 if (manwidth > 0)
1264 (void) snprintf(tmpbuf, BUFSIZ, "-Owidth=%d ", manwidth);
1265 (void) snprintf(cmdbuf, BUFSIZ, "cd %s; %s %s%s | mandoc -T%s %s| %s",
1266 path, cattool, manpname,
1267 utf8 ? " | " PRECONV " -e UTF-8 " : "",
1268 utf8 ? "utf8" : "ascii", (manwidth > 0) ? tmpbuf : "", pager);
1269
1270 cmd:
1271 DPRINTF("-- Command: %s\n", cmdbuf);
1272
1273 if (!debug)
1274 return (system(cmdbuf) == 0);
1275 else
1276 return (0);
1277 }
1278
1279 /*
1280 * Add <localedir> to the path.
1281 */
1282 static char *
addlocale(char * path)1283 addlocale(char *path)
1284 {
1285 char *tmp;
1286
1287 if (asprintf(&tmp, "%s/%s", path, localedir) == -1)
1288 err(1, "asprintf");
1289
1290 return (tmp);
1291 }
1292
1293 /*
1294 * Get the order of sections from man.cf.
1295 */
1296 static char *
check_config(char * path)1297 check_config(char *path)
1298 {
1299 FILE *fp;
1300 char *rc = NULL;
1301 char *sect;
1302 char fname[MAXPATHLEN];
1303 char *line = NULL;
1304 size_t linecap = 0;
1305
1306 (void) snprintf(fname, MAXPATHLEN, "%s/%s", path, CONFIG);
1307
1308 if ((fp = fopen(fname, "r")) == NULL)
1309 return (NULL);
1310
1311 while (getline(&line, &linecap, fp) > 0) {
1312 if ((rc = strstr(line, "MANSECTS")) != NULL)
1313 break;
1314 }
1315
1316 (void) fclose(fp);
1317
1318 if (rc == NULL || (sect = strchr(line, '=')) == NULL)
1319 return (NULL);
1320 else
1321 return (++sect);
1322 }
1323
1324
1325 /*
1326 * Initialize the bintoman array with appropriate device and inode info.
1327 */
1328 static void
init_bintoman(void)1329 init_bintoman(void)
1330 {
1331 int i;
1332 struct stat sb;
1333
1334 for (i = 0; bintoman[i].bindir != NULL; i++) {
1335 if (stat(bintoman[i].bindir, &sb) == 0) {
1336 bintoman[i].dev = sb.st_dev;
1337 bintoman[i].ino = sb.st_ino;
1338 } else {
1339 bintoman[i].dev = NODEV;
1340 }
1341 }
1342 }
1343
1344 /*
1345 * If a duplicate is found, return 1.
1346 * If a duplicate is not found, add it to the dupnode list and return 0.
1347 */
1348 static int
dupcheck(struct man_node * mnp,struct dupnode ** dnp)1349 dupcheck(struct man_node *mnp, struct dupnode **dnp)
1350 {
1351 struct dupnode *curdnp;
1352 struct secnode *cursnp;
1353 struct stat sb;
1354 int i;
1355 int rv = 1;
1356 int dupfound;
1357
1358 /* If the path doesn't exist, treat it as a duplicate */
1359 if (stat(mnp->path, &sb) != 0)
1360 return (1);
1361
1362 /* If no sections were found in the man dir, treat it as duplicate */
1363 if (mnp->secv == NULL)
1364 return (1);
1365
1366 /*
1367 * Find the dupnode structure for the previous time this directory
1368 * was looked at. Device and inode numbers are compared so that
1369 * directories that are reached via different paths (e.g. /usr/man and
1370 * /usr/share/man) are treated as equivalent.
1371 */
1372 for (curdnp = *dnp; curdnp != NULL; curdnp = curdnp->next) {
1373 if (curdnp->dev == sb.st_dev && curdnp->ino == sb.st_ino)
1374 break;
1375 }
1376
1377 /*
1378 * First time this directory has been seen. Add a new node to the
1379 * head of the list. Since all entries are guaranteed to be unique
1380 * copy all sections to new node.
1381 */
1382 if (curdnp == NULL) {
1383 if ((curdnp = calloc(1, sizeof (struct dupnode))) == NULL)
1384 err(1, "calloc");
1385 for (i = 0; mnp->secv[i] != NULL; i++) {
1386 if ((cursnp = calloc(1, sizeof (struct secnode)))
1387 == NULL)
1388 err(1, "calloc");
1389 cursnp->next = curdnp->secl;
1390 curdnp->secl = cursnp;
1391 if ((cursnp->secp = strdup(mnp->secv[i])) == NULL)
1392 err(1, "strdup");
1393 }
1394 curdnp->dev = sb.st_dev;
1395 curdnp->ino = sb.st_ino;
1396 curdnp->next = *dnp;
1397 *dnp = curdnp;
1398 return (0);
1399 }
1400
1401 /*
1402 * Traverse the section vector in the man_node and the section list
1403 * in dupnode cache to eliminate all duplicates from man_node.
1404 */
1405 for (i = 0; mnp->secv[i] != NULL; i++) {
1406 dupfound = 0;
1407 for (cursnp = curdnp->secl; cursnp != NULL;
1408 cursnp = cursnp->next) {
1409 if (strcmp(mnp->secv[i], cursnp->secp) == 0) {
1410 dupfound = 1;
1411 break;
1412 }
1413 }
1414 if (dupfound) {
1415 mnp->secv[i][0] = '\0';
1416 continue;
1417 }
1418
1419
1420 /*
1421 * Update curdnp and set return value to indicate that this
1422 * was not all duplicates.
1423 */
1424 if ((cursnp = calloc(1, sizeof (struct secnode))) == NULL)
1425 err(1, "calloc");
1426 cursnp->next = curdnp->secl;
1427 curdnp->secl = cursnp;
1428 if ((cursnp->secp = strdup(mnp->secv[i])) == NULL)
1429 err(1, "strdup");
1430 rv = 0;
1431 }
1432
1433 return (rv);
1434 }
1435
1436 /*
1437 * Given a bindir, return corresponding mandir.
1438 */
1439 static char *
path_to_manpath(char * bindir)1440 path_to_manpath(char *bindir)
1441 {
1442 char *mand, *p;
1443 int i;
1444 struct stat sb;
1445
1446 /* First look for known translations for specific bin paths */
1447 if (stat(bindir, &sb) != 0) {
1448 return (NULL);
1449 }
1450 for (i = 0; bintoman[i].bindir != NULL; i++) {
1451 if (sb.st_dev == bintoman[i].dev &&
1452 sb.st_ino == bintoman[i].ino) {
1453 if ((mand = strdup(bintoman[i].mandir)) == NULL)
1454 err(1, "strdup");
1455 if ((p = strchr(mand, ',')) != NULL)
1456 *p = '\0';
1457 if (stat(mand, &sb) != 0) {
1458 free(mand);
1459 return (NULL);
1460 }
1461 if (p != NULL)
1462 *p = ',';
1463 return (mand);
1464 }
1465 }
1466
1467 /*
1468 * No specific translation found. Try `dirname $bindir`/share/man
1469 * and `dirname $bindir`/man
1470 */
1471 if ((mand = malloc(MAXPATHLEN)) == NULL)
1472 err(1, "malloc");
1473 if (strlcpy(mand, bindir, MAXPATHLEN) >= MAXPATHLEN) {
1474 free(mand);
1475 return (NULL);
1476 }
1477
1478 /*
1479 * Advance to end of buffer, strip trailing /'s then remove last
1480 * directory component.
1481 */
1482 for (p = mand; *p != '\0'; p++)
1483 ;
1484 for (; p > mand && *p == '/'; p--)
1485 ;
1486 for (; p > mand && *p != '/'; p--)
1487 ;
1488 if (p == mand && *p == '.') {
1489 if (realpath("..", mand) == NULL) {
1490 free(mand);
1491 return (NULL);
1492 }
1493 for (; *p != '\0'; p++)
1494 ;
1495 } else {
1496 *p = '\0';
1497 }
1498
1499 if (strlcat(mand, "/share/man", MAXPATHLEN) >= MAXPATHLEN) {
1500 free(mand);
1501 return (NULL);
1502 }
1503
1504 if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) {
1505 return (mand);
1506 }
1507
1508 /*
1509 * Strip the /share/man off and try /man
1510 */
1511 *p = '\0';
1512 if (strlcat(mand, "/man", MAXPATHLEN) >= MAXPATHLEN) {
1513 free(mand);
1514 return (NULL);
1515 }
1516 if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) {
1517 return (mand);
1518 }
1519
1520 /*
1521 * No man or share/man directory found
1522 */
1523 free(mand);
1524 return (NULL);
1525 }
1526
1527 /*
1528 * Free a linked list of dupnode structs.
1529 */
1530 void
free_dupnode(struct dupnode * dnp)1531 free_dupnode(struct dupnode *dnp) {
1532 struct dupnode *dnp2;
1533 struct secnode *snp;
1534
1535 while (dnp != NULL) {
1536 dnp2 = dnp;
1537 dnp = dnp->next;
1538 while (dnp2->secl != NULL) {
1539 snp = dnp2->secl;
1540 dnp2->secl = dnp2->secl->next;
1541 free(snp->secp);
1542 free(snp);
1543 }
1544 free(dnp2);
1545 }
1546 }
1547
1548 /*
1549 * Print manp linked list to stdout.
1550 */
1551 void
print_manpath(struct man_node * manp)1552 print_manpath(struct man_node *manp)
1553 {
1554 char colon[2] = "\0\0";
1555 char **secp;
1556
1557 for (; manp != NULL; manp = manp->next) {
1558 (void) printf("%s%s", colon, manp->path);
1559 colon[0] = ':';
1560
1561 /*
1562 * If man.cf or a directory scan was used to create section
1563 * list, do not print section list again. If the output of
1564 * man -p is used to set MANPATH, subsequent runs of man
1565 * will re-read man.cf and/or scan man directories as
1566 * required.
1567 */
1568 if (manp->defsrch != 0)
1569 continue;
1570
1571 for (secp = manp->secv; *secp != NULL; secp++) {
1572 /*
1573 * Section deduplication may have eliminated some
1574 * sections from the vector. Avoid displaying this
1575 * detail which would appear as ",," in output
1576 */
1577 if ((*secp)[0] != '\0')
1578 (void) printf(",%s", *secp);
1579 }
1580 }
1581 (void) printf("\n");
1582 }
1583
1584 static void
usage_man(void)1585 usage_man(void)
1586 {
1587
1588 (void) fprintf(stderr, gettext(
1589 "usage: man [-alptw] [-M path] [-s section] name ...\n"
1590 " man [-M path] [-s section] -k keyword ...\n"
1591 " man [-M path] [-s section] -f keyword ...\n"));
1592
1593 exit(1);
1594 }
1595
1596 static void
usage_whatapro(void)1597 usage_whatapro(void)
1598 {
1599
1600 (void) fprintf(stderr, gettext(
1601 "usage: %s [-M path] [-s section] keyword ...\n"),
1602 whatis ? "whatis" : "apropos");
1603
1604 exit(1);
1605 }
1606
1607 static void
usage_catman(void)1608 usage_catman(void)
1609 {
1610 (void) fprintf(stderr, gettext(
1611 "usage: catman [-M path] [-w]\n"));
1612
1613 exit(1);
1614 }
1615
1616 static void
usage_makewhatis(void)1617 usage_makewhatis(void)
1618 {
1619 (void) fprintf(stderr, gettext("usage: makewhatis\n"));
1620
1621 exit(1);
1622 }
1623