less -> more
[unix-history] / usr / src / usr.bin / man / man.c
index 9779fc4..c1bea03 100644 (file)
 /*
  * Copyright (c) 1987 Regents of the University of California.
 /*
  * Copyright (c) 1987 Regents of the University of California.
- * All rights reserved.  The Berkeley software License Agreement
- * specifies the terms and conditions for redistribution.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the University of California, Berkeley.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  */
 
 #ifndef lint
 char copyright[] =
 "@(#) Copyright (c) 1987 Regents of the University of California.\n\
  All rights reserved.\n";
  */
 
 #ifndef lint
 char copyright[] =
 "@(#) Copyright (c) 1987 Regents of the University of California.\n\
  All rights reserved.\n";
-#endif not lint
+#endif /* not lint */
 
 #ifndef lint
 
 #ifndef lint
-static char sccsid[] = "@(#)man.c      5.3 (Berkeley) %G%";
-#endif not lint
+static char sccsid[] = "@(#)man.c      5.17 (Berkeley) %G%";
+#endif /* not lint */
 
 #include <sys/param.h>
 #include <sys/file.h>
 #include <ctype.h>
 
 
 #include <sys/param.h>
 #include <sys/file.h>
 #include <ctype.h>
 
-#define        DEF_PAGER       "more -s"
-#define        DEF_PATH        "/usr/man:/usr/local/man"
+#define        DEF_PAGER       "/usr/ucb/more -s"
+#define        DEF_PATH        "/usr/man:/usr/new/man:/usr/local/man"
 #define        LOCAL_PATH      "/usr/local/man"
 #define        LOCAL_PATH      "/usr/local/man"
-#define        LOCAL_NAME      "local"
-#define        NO              0
-#define        YES             1
+#define        NEW_PATH        "/usr/new/man"
 
 
-#define        NO_SECTION      0
-#define        S_THREEF        9
-#define        S_NEW           10
-#define        S_OLD           11
+#define        NO      0
+#define        YES     1
 
 
-/* this array maps a character (ex: '4') to an offset in dirlist */
-#define        secno(x)        (seclist[(int)(x - '0')])
-static int     seclist[] = { -1, 1, 4, 5, 6, 7, 3, 8, 2, -1, -1 };
-
-/* sub directory list, ordered for searching */
-typedef struct something_meaningful {
-       char    *name,
-               *msg;
-} DIR;
-
-DIR    dirlist[] = {           /* sub-directory list */
-       "notused", "",          "cat1", "1st",          "cat8", "8th",
-       "cat6", "6th",          "cat2", "2nd",          "cat3", "3rd",
-       "cat4", "4th",          "cat5", "5th",          "cat7", "7th",
-       "cat3f", "3rd (F)",     "new", "new",           "old", "old",
-       NULL, NULL,
-};
-
-static int     nomore;                 /* copy file to stdout */
-static char    *defpath,               /* default search path */
+static char    *command,               /* command buffer */
+               *defpath,               /* default search path */
                *locpath,               /* local search path */
                *machine,               /* machine type */
                *manpath,               /* current search path */
                *locpath,               /* local search path */
                *machine,               /* machine type */
                *manpath,               /* current search path */
-               *pager;                 /* requested pager */
+               *newpath,               /* new search path */
+               *pager,                 /* requested pager */
+               how;                    /* how to display */
+
+#define        ALL     0x1                     /* show all man pages */
+#define        CAT     0x2                     /* copy file to stdout */
+#define        WHERE   0x4                     /* just tell me where */
 
 main(argc, argv)
 
 main(argc, argv)
-       int     argc;
-       register char   **argv;
+       int argc;
+       register char **argv;
 {
 {
-       int     section;
-       char    **arg_start, **arg,
-               *getenv();
+       extern char *optarg;
+       extern int optind;
+       int ch;
+       char *getenv(), *malloc();
 
 
-       arg_start = argv;
-       for (--argc, ++argv; argc && (*argv)[0] == '-'; --argc, ++argv)
-               switch((*argv)[1]) {
-               case 0:                 /* just write to stdout */
-                       nomore = YES;
+       while ((ch = getopt(argc, argv, "-M:P:afkw")) != EOF)
+               switch((char)ch) {
+               case '-':
+                       how |= CAT;
                        break;
                case 'M':
                case 'P':               /* backward compatibility */
                        break;
                case 'M':
                case 'P':               /* backward compatibility */
-                       if ((*argv)[2])
-                               manpath = *argv + 2;
-                       else {
-                               if (argc < 2) {
-                                       fprintf(stderr, "%s: missing path\n", *argv);
-                                       exit(1);
-                               }
-                               --argc;
-                               manpath = *++argv;
-                       }
+                       defpath = optarg;
+                       break;
+               case 'a':
+                       how |= ALL;
                        break;
                /*
                        break;
                /*
-                * "man -f" and "man -k" are undocumented ways of calling
-                * whatis(1) and apropos(1).  Just strip out the flag
-                * argument and jump.
+                * "man -f" and "man -k" are backward contemptible,
+                * undocumented ways of calling whatis(1) and apropos(1).
                 */
                case 'f':
                 */
                case 'f':
-                       for (arg = argv; arg[0] = arg[1]; ++arg);
-                       *arg_start = "whatis";
-                       execvp(*arg_start, arg_start);
-                       fputs("whatis: Command not found.\n", stderr);
-                       exit(1);
+                       jump(argv, "-f", "whatis");
+                       /*NOTREACHED*/
                case 'k':
                case 'k':
-                       for (arg = argv; *arg = arg[1]; ++arg);
-                       *arg_start = "apropos";
-                       execvp(*arg_start, arg_start);
-                       fputs("apropos: Command not found.\n", stderr);
-                       exit(1);
+                       jump(argv, "-k", "apropos");
+                       /*NOTREACHED*/
+               /*
+                * Deliberately undocumented; really only useful when
+                * you're moving man pages around.  Not worth adding.
+                */
+               case 'w':
+                       how |= WHERE | ALL;
+                       break;
                case '?':
                default:
                        usage();
                }
                case '?':
                default:
                        usage();
                }
-       if (!argc)
+       argv += optind;
+
+       if (!*argv)
                usage();
 
                usage();
 
-       if (!nomore)
+       if (!(how & CAT))
                if (!isatty(1))
                if (!isatty(1))
-                       nomore = YES;
-               else if (!(pager = getenv("PAGER")))
+                       how |= CAT;
+               else if (pager = getenv("PAGER")) {
+                       register char *p;
+
+                       /*
+                        * if the user uses "more", we make it "more -s"
+                        * watch out for PAGER = "mypager /usr/ucb/more"
+                        */
+                       for (p = pager; *p && !isspace(*p); ++p);
+                       for (; p > pager && *p != '/'; --p);
+                       if (p != pager)
+                               ++p;
+                       /* make sure it's "more", not "morex" */
+                       if (!strncmp(p, "more", 4) && (!p[4] || isspace(p[4]))){
+                               char *opager = pager;
+                               /*
+                                * allocate space to add the "-s"
+                                */
+                               if (!(pager = malloc((u_int)(strlen(opager) 
+                                   + sizeof("-s") + 1)))) {
+                                       fputs("man: out of space.\n", stderr);
+                                       exit(1);
+                               }
+                               (void)sprintf(pager, "%s %s", opager, "-s");
+                       }
+               }
+               else
                        pager = DEF_PAGER;
        if (!(machine = getenv("MACHINE")))
                machine = MACHINE;
        if (!defpath && !(defpath = getenv("MANPATH")))
                defpath = DEF_PATH;
        locpath = LOCAL_PATH;
                        pager = DEF_PAGER;
        if (!(machine = getenv("MACHINE")))
                machine = MACHINE;
        if (!defpath && !(defpath = getenv("MANPATH")))
                defpath = DEF_PATH;
        locpath = LOCAL_PATH;
-       for (; *defpath && *defpath == ':'; ++defpath);
+       newpath = NEW_PATH;
+       man(argv);
+       /* use system(3) in case someone's pager is "pager arg1 arg2" */
+       if (command)
+               (void)system(command);
+       exit(0);
+}
+
+typedef struct {
+       char    *name, *msg;
+} DIR;
+static DIR     list1[] = {             /* section one list */
+       "cat1", "1st",          "cat8", "8th",          "cat6", "6th",
+       "cat.old", "old",       NULL, NULL,
+},             list2[] = {             /* rest of the list */
+       "cat2", "2nd",          "cat3", "3rd",          "cat4", "4th",
+       "cat5", "5th",          "cat7", "7th",          "cat3f", "3rd (F)",
+       NULL, NULL,
+},             list3[2];               /* single section */
+
+static
+man(argv)
+       char **argv;
+{
+       register char *p;
+       DIR *section, *getsect();
+       int res;
 
        for (; *argv; ++argv) {
 
        for (; *argv; ++argv) {
-               section = NO_SECTION;
-               manpath = DEF_PATH;
+               manpath = defpath;
+               section = NULL;
                switch(**argv) {
                switch(**argv) {
-               /* hardwired section numbers, fix here if they change */
-               case '1': case '2': case '4': case '5': case '6':
-               case '7': case '8':
-                       if (!(*argv)[1]) {
-                               section = secno((*argv)[0]);
-                               goto numtest;
-                       }
-                       break;
-               case '3':
-                       if (!(*argv)[1]) {                      /* "3" */
-                               section = secno((*argv)[0]);
-numtest:                       if (!*++argv) {
-                                       fprintf(stderr, "man: what do you want from the %s section of the manual?\n", dirlist[section].msg);
-                                       exit(1);
-                               }
-                       }                                       /* "3[fF]" */
-                       if (((*argv)[1] == 'f'  || (*argv)[1] == 'F') && !(*argv)[2]) {
-                               section = S_THREEF;
-                               if (!*++argv) {
-                                       fprintf(stderr, "man: what do you want from the %s section of the manual?\n", dirlist[S_THREEF].msg);
-                                       exit(1);
-                               }
+               case 'l':                               /* local */
+                       /* support the "{l,local,n,new}###"  syntax */
+                       for (p = *argv; isalpha(*p); ++p);
+                       if (!strncmp(*argv, "l", p - *argv) ||
+                           !strncmp(*argv, "local", p - *argv)) {
+                               ++argv;
+                               manpath = locpath;
+                               section = getsect(p);
                        }
                        break;
                        }
                        break;
-               case 'l':                                       /* local */
-                       if (!(*argv)[1])                        /* "l" */
-                               section = NO_SECTION;           /* "l2" */
-                       else if (isdigit((*argv)[1]) && !(*argv)[2])
-                               section = secno((*argv)[1]);
-                       else {
-                               int     lex;
-                               lex = strcmp(LOCAL_NAME, *argv);
-                               if (!lex)                       /* "local" */
-                                       section = NO_SECTION;   /* "local2" */
-                               else if (lex < 0 && isdigit((*argv)[sizeof(LOCAL_NAME) - 1]) && !(*argv)[sizeof(LOCAL_NAME)])
-                                       section = secno((*argv)[sizeof(LOCAL_NAME) - 1]);
-                               else
-                                       break;
-                       }
-                       if (!*++argv) {
-                               fputs("man: what do you want from the local section of the manual?\n", stderr);
-                               exit(1);
+               case 'n':                               /* new */
+                       for (p = *argv; isalpha(*p); ++p);
+                       if (!strncmp(*argv, "n", p - *argv) ||
+                           !strncmp(*argv, "new", p - *argv)) {
+                               ++argv;
+                               manpath = newpath;
+                               section = getsect(p);
                        }
                        }
-                       manpath = locpath;
                        break;
                        break;
-               case 'n':                                       /* new */
-                       if (!(*argv)[1] || !strcmp(*argv, dirlist[S_NEW].name)) {
-                               section = S_NEW;
-                               goto strtest;
+               /*
+                * old isn't really a separate section of the manual,
+                * and its entries are all in a single directory.
+                */
+               case 'o':                               /* old */
+                       for (p = *argv; isalpha(*p); ++p);
+                       if (!strncmp(*argv, "o", p - *argv) ||
+                           !strncmp(*argv, "old", p - *argv)) {
+                               ++argv;
+                               list3[0] = list1[3];
+                               section = list3;
                        }
                        break;
                        }
                        break;
-               case 'o':                                       /* old */
-                       if (!(*argv)[1] || !strcmp(*argv, dirlist[S_OLD].name)) {
-                               section = S_OLD;
-strtest:                       if (!*++argv) {
-                                       fprintf(stderr, "man: what do you want from the %s section of the manual?\n", dirlist[section].msg);
-                                       exit(1);
-                               }
+               case '1': case '2': case '3': case '4':
+               case '5': case '6': case '7': case '8':
+                       if (section = getsect(*argv))
+                               ++argv;
+               }
+
+               if (*argv) {
+                       if (section)
+                               res = manual(section, *argv);
+                       else {
+                               res = manual(list1, *argv);
+                               if (!res || (how & ALL))
+                                       res += manual(list2, *argv);
                        }
                        }
-                       break;
+                       if (res || how&WHERE)
+                               continue;
                }
                }
-               if (!manual(section, *argv))
-                       if (manpath == locpath)
-                               fprintf(stderr, "No entry for %s in the %s section of the local manual.\n", *argv, dirlist[section].msg);
-                       else if (section == NO_SECTION)
-                               fprintf(stderr, "No entry for %s in the manual.\n", *argv);
-                       else
-                               fprintf(stderr, "No entry for %s in the %s section of the manual.\n", *argv, dirlist[section].msg);
+
+               fputs("man: ", stderr);
+               if (*argv)
+                       fprintf(stderr, "no entry for %s in the ", *argv);
+               else
+                       fputs("what do you want from the ", stderr);
+               if (section)
+                       fprintf(stderr, "%s section of the ", section->msg);
+               if (manpath == locpath)
+                       fputs("local ", stderr);
+               else if (manpath == newpath)
+                       fputs("new ", stderr);
+               if (*argv)
+                       fputs("manual.\n", stderr);
+               else
+                       fputs("manual?\n", stderr);
+               exit(1);
        }
        }
-       exit(0);
 }
 
 }
 
+/*
+ * manual --
+ *     given a directory list and a file name find a file that
+ *     matches; check ${directory}/${dir}/{file name} and
+ *     ${directory}/${dir}/${machine}/${file name}.
+ */
 static
 manual(section, name)
 static
 manual(section, name)
-       int     section;
-       char    *name;
+       DIR *section;
+       char *name;
 {
 {
-       register DIR    *dir;
-       register char   *beg, *end;
-       char    *index();
+       register char *beg, *end;
+       register DIR *dp;
+       register int res;
+       char fname[MAXPATHLEN + 1], *index();
 
 
-       for (beg = manpath;; beg = end + 1) {
+       for (beg = manpath, res = 0;; beg = end + 1) {
                if (end = index(beg, ':'))
                        *end = '\0';
                if (end = index(beg, ':'))
                        *end = '\0';
-               if (section == NO_SECTION) {
-                       for (dir = dirlist + 1; dir->name; ++dir)
-                               if (find(beg, dir->name, name))
-                                       return(YES);
+               for (dp = section; dp->name; ++dp) {
+                       (void)sprintf(fname, "%s/%s/%s.0", beg, dp->name, name);
+                       if (access(fname, R_OK)) {
+                               (void)sprintf(fname, "%s/%s/%s/%s.0", beg,
+                                   dp->name, machine, name);
+                               if (access(fname, R_OK))
+                                       continue;
+                       }
+                       if (how & WHERE)
+                               printf("man: found in %s.\n", fname);
+                       else if (how & CAT)
+                               cat(fname);
+                       else
+                               add(fname);
+                       if (!(how & ALL))
+                               return(1);
+                       res = 1;
                }
                }
-               else if (find(beg, dirlist[section].name, name))
-                       return(YES);
                if (!end)
                if (!end)
-                       return(NO);
+                       return(res);
+               *end = ':';
        }
        /*NOTREACHED*/
 }
 
        }
        /*NOTREACHED*/
 }
 
+/*
+ * cat --
+ *     cat out the file
+ */
 static
 static
-find(beg, dir, name)
-       char    *beg, *dir, *name;
+cat(fname)
+       char *fname;
 {
 {
-       char    fname[MAXPATHLEN + 1];
+       register int fd, n;
+       char buf[BUFSIZ];
 
 
-       (void)sprintf(fname, "%s/%s/%s.0", beg, dir, name);
-       if (!access(fname, R_OK)) {
-               show(fname);
-               return(YES);
+       if (!(fd = open(fname, O_RDONLY, 0))) {
+               perror("man: open");
+               exit(1);
        }
        }
-       (void)sprintf(fname, "%s/%s/%s/%s.0", beg, dir, machine, name);
-       if (!access(fname, R_OK)) {
-               show(fname);
-               return(YES);
+       while ((n = read(fd, buf, sizeof(buf))) > 0)
+               if (write(1, buf, n) != n) {
+                       perror("man: write");
+                       exit(1);
+               }
+       if (n == -1) {
+               perror("man: read");
+               exit(1);
        }
        }
-       return(NO);
+       (void)close(fd);
 }
 
 }
 
+/*
+ * add --
+ *     add a file name to the list for future paging
+ */
 static
 static
-show(fname)
-       char    *fname;
+add(fname)
+       char *fname;
 {
 {
-       register int    fd, n;
-       char    buf[BUFSIZ];
+       static u_int buflen;
+       static int len;
+       static char *cp;
+       int flen;
+       char *malloc(), *realloc(), *strcpy();
 
 
-       if (nomore) {
-               if (!(fd = open(fname, O_RDONLY, 0))) {
-                       perror("man: open");
+       if (!command) {
+               if (!(command = malloc(buflen = 1024))) {
+                       fputs("man: out of space.\n", stderr);
                        exit(1);
                }
                        exit(1);
                }
-               while ((n = read(fd, buf, sizeof(buf))) > 0)
-                       if (write(1, buf, n) != n) {
-                               perror("man: write");
-                               exit(1);
-                       }
-               if (n == -1) {
-                       perror("man: read");
+               len = strlen(strcpy(command, pager));
+               cp = command + len;
+       }
+       flen = strlen(fname);
+       if (len + flen + 2 > buflen) {          /* +2 == space, EOS */
+               if (!(command = realloc(command, buflen += 1024))) {
+                       fputs("man: out of space.\n", stderr);
                        exit(1);
                }
                        exit(1);
                }
-               (void)close(fd);
+               cp = command + len;
        }
        }
-       else {
-               /*
-                * use system(2) in case someone's pager is
-                * "command arg1 arg2"
-                */
-               (void)sprintf(buf, "%s %s", pager, fname);
-               (void)system(buf);
+       *cp++ = ' ';
+       len += flen + 1;                        /* +1 = space */
+       (void)strcpy(cp, fname);
+       cp += flen;
+}
+
+/*
+ * getsect --
+ *     return a point to the section structure for a particular suffix
+ */
+static DIR *
+getsect(s)
+       char *s;
+{
+       switch(*s++) {
+       case '1':
+               if (!*s)
+                       return(list1);
+               break;
+       case '2':
+               if (!*s) {
+                       list3[0] = list2[0];
+                       return(list3);
+               }
+               break;
+       /* sect. 3 requests are for either section 3, or section 3[fF]. */
+       case '3':
+               if (!*s) {
+                       list3[0] = list2[1];
+                       return(list3);
+               }
+               else if ((*s == 'f'  || *s == 'F') && !*++s) {
+                       list3[0] = list2[5];
+                       return(list3);
+               }
+               break;
+       case '4':
+               if (!*s) {
+                       list3[0] = list2[2];
+                       return(list3);
+               }
+               break;
+       case '5':
+               if (!*s) {
+                       list3[0] = list2[3];
+                       return(list3);
+               }
+               break;
+       case '6':
+               if (!*s) {
+                       list3[0] = list1[2];
+                       return(list3);
+               }
+               break;
+       case '7':
+               if (!*s) {
+                       list3[0] = list2[4];
+                       return(list3);
+               }
+               break;
+       case '8':
+               if (!*s) {
+                       list3[0] = list1[1];
+                       return(list3);
+               }
        }
        }
+       return((DIR *)NULL);
 }
 
 }
 
+/*
+ * jump --
+ *     strip out flag argument and jump
+ */
+static
+jump(argv, flag, name)
+       char **argv, *name;
+       register char *flag;
+{
+       register char **arg;
+
+       argv[0] = name;
+       for (arg = argv + 1; *arg; ++arg)
+               if (!strcmp(*arg, flag))
+                       break;
+       for (; *arg; ++arg)
+               arg[0] = arg[1];
+       execvp(name, argv);
+       fprintf(stderr, "%s: Command not found.\n", name);
+       exit(1);
+}
+
+/*
+ * usage --
+ *     print usage and die
+ */
 static
 usage()
 {
 static
 usage()
 {
-       fputs("usage: man [-] [-M path] [section] title ...\n", stderr);
+       fputs("usage: man [-] [-a] [-M path] [section] title ...\n", stderr);
        exit(1);
 }
        exit(1);
 }