Signed-off-by: Mattias Andrée <[email protected]>
---
 Makefile     |   5 +
 README       |   1 +
 config.def.h |  17 ++
 man.1        | 154 ++++++++++++++
 man.c        | 677 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 854 insertions(+)
 create mode 100644 config.def.h
 create mode 100644 man.1
 create mode 100644 man.c

diff --git a/Makefile b/Makefile
index 9ec9990..1e507f9 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,7 @@ include config.mk
 HDR =\
        arg.h\
        compat.h\
+       config.h\
        crypt.h\
        fs.h\
        md5.h\
@@ -121,6 +122,7 @@ BIN =\
        logger\
        logname\
        ls\
+       man\
        md5sum\
        mkdir\
        mkfifo\
@@ -192,6 +194,9 @@ $(BIN): $(LIB) $(@:=.o)
 
 $(OBJ): $(HDR) config.mk
 
+config.h:
+       cp config.def.h $@
+
 .o:
        $(CC) $(LDFLAGS) -o $@ $< $(LIB)
 
diff --git a/README b/README
index da2e500..e518796 100644
--- a/README
+++ b/README
@@ -50,6 +50,7 @@ The following tools are implemented:
 0=*|o logger          .
 0=*|o logname         .
 0#* o ls              (-C, -k, -m, -p, -s, -x)
+0#* o man             (-k)
 0=*|x md5sum          .
 0=*|o mkdir           .
 0=*|o mkfifo          .
diff --git a/config.def.h b/config.def.h
new file mode 100644
index 0000000..503fd2d
--- /dev/null
+++ b/config.def.h
@@ -0,0 +1,17 @@
+/* See LICENSE file for copyright and license details. */
+
+#define GUNZIP     "exec /usr/bin/gunzip"
+#define UNCOMPRESS "exec /usr/bin/uncompress"
+#define BUNZIP2    "exec /usr/bin/bunzip2"
+#define UNXZ       "exec /usr/bin/unxz"
+#define UNLZMA     "exec /usr/bin/unlzma"
+#define LUNZIP     "exec /usr/bin/lzip -d"
+#define UNLZ4      "exec /usr/bin/unlz4"
+
+#define MANPATHTRANS "/=/usr"
+#define MANPATH "/usr/local/share/man:/usr/share/man"
+#define MANSECT 
"3p:3:0:2:1:8:n:l:5:4:6:7:x:9:a:b:c:d:e:f:g:h:i:j:k:l:m:o:p:q:r:s:t:u:v:w:u:y:z"
+#define MANBINSECT "1:8"
+#define MANETCSECT "5"
+#define MANCOMP 
"gz="GUNZIP":z="GUNZIP":Z="UNCOMPRESS":bz2="BUNZIP2":xz="UNXZ":lzma="UNLZMA":lz="LUNZIP":lz4="UNLZ4
+#define MANPAGER "exec /usr/bin/less"
diff --git a/man.1 b/man.1
new file mode 100644
index 0000000..9985b2b
--- /dev/null
+++ b/man.1
@@ -0,0 +1,154 @@
+.Dd 2017-02-01
+.Dt MAN 1
+.Os sbase
+.Sh NAME
+.Nm man
+.Nd display online reference manuals
+.Sh SYNOPSIS
+.Nm
+.Op Fl 7 | Fl 8
+.Op Ar section
+.Ar name
+.Op Ar subcommand ...
+.Sh DESCRIPTION
+.Nm
+displays the manual with the selected
+.Ar name .
+.Ar name
+is usually a command, C function or system call.
+All
+.Ar subcommand
+are appended to
+.Ar name
+with \fB-\fP joining the arguments.
+Multiple manuals have the same name, in this case
+.Ar section
+can be used to specify which of them to display.
+.P
+The table below shows common
+.Ar section
+numbers.
+.Bd -literal -offset left
+1      Executable commands, as implemented
+1p     Executable commands, as specified by POSIX
+2      System calls
+3      Library calls, as implemented
+3p     Library calls, as specified by POSIX
+4      Special files
+5      Configuration files, file formats and conventions
+6      Entertainment
+7      Miscellanea
+8      System administration commands
+9      Kernel routines
+0      Header files, as implemented
+0p     Header files, as specified by POSIX
+l      Tcl/Tk commands
+n      SQL commands
+.Ed
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl 7
+Convert output to ASCII.
+.It Fl 8
+Allow to UTF-8.
+.El
+.Sh ENVIRONMENT VARIABLES
+.Bl -tag -width Ds
+.It Ev MANPATH
+Colon-separated list of where manual are stored.
+Each listed directory shall directions for each
+section rather than the manuals themself.
+.It Ev MANPATHTRANS
+Colon-separated list of directory remappings used
+to find the manual when command is given by its
+path. Each entry is of the format
+.Va bindir Ns Cm = Ns Va mandir .
+If the parent directory of the directory
+.Ar name
+matches
+.Va bindir ,
+.Va mandir
+is used as
+.Ar MANPATH .
+.It Ev MANSECT
+Colon-separated list of
+.Ar section
+numbers. If a manual is found under two or more
+sections, the first named section in
+.Ev MANSECT
+is selected.
+.It Ev MANBINSECT
+Similar to
+.Ev MANSECT ,
+but is used when
+.Ar name
+is the path to an executable file.
+.It Ev MANETCSECT ,
+but is used when
+.Ar name
+is the path to file in
+.Pa /etc .
+.It Ev MANCOMP
+Colon-separated list of compressions. Each entry
+is formated as
+.Va extension Ns Cm = Ns Va command ,
+where
+.Va extension
+is the typical file name extension used on files
+compressed such that
+.Nm sh Fl c Li ' Ns Va command Ns Li '
+decompresses the files.
+This variable is ignored when running as root.
+.It Ev MANPAGER
+The pager to used. By default
+.Xr less 1
+is used.
+.It Ev PAGER
+Used instead of
+.Ev MANPAGER
+if
+.Ev MANPAGER
+is not set.
+.It Ev MANWIDTH
+The display-width of the manual, in columns.
+.It Ev COLUMNS
+Used instead of
+.Ev MANWIDTH
+if
+.Ev MANWIDTH
+is not set.
+.It Ev MANMAXWIDTH
+The maximum display-width of the manual, in columns.
+.It Ev MAN_KEEP_STDERR
+If set and not empty,
+.Nm
+will not redirect stderr to
+.Pa /dev/null
+before parsing the file and before starting the pager.
+.It Ev MAN_KEEP_FORMATTING
+If set and not empty,
+.Nm
+will not remove formatting information from the output.
+Formatting is always keeped when stdout is a terminal
+(which causes
+.Nm
+to start a pager).
+.Ed
+.Sh SEE ALSO
+.Xr less 1
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2013
+specification except from the
+.Op Fl k
+flag and support for looking up multiple manuals.
+.Pp
+The
+.Op Fl 78
+flags,
+.Ar section
+and all environment variables except
+.Ev PAGER
+are an extension to that specification.
diff --git a/man.c b/man.c
new file mode 100644
index 0000000..0e02aea
--- /dev/null
+++ b/man.c
@@ -0,0 +1,677 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "util.h"
+
+static void
+usage(void)
+{
+       eprintf("usage: %s [-7 | -8] [section] name [subcommand ...]\n", argv0);
+}
+
+static void
+backslash(char *out, const char *str, const char *esc)
+{
+       while (*str) {
+               if (strchr(esc, *str))
+                       *out++ = '\\';
+               *out++ = *str++;
+       }
+       *out = '\0';
+}
+
+static void
+setprompt(const char *name, const char *sect)
+{
+       char *buf1, *buf2;
+       const char *p;
+       size_t n;
+
+#define PATTERN "-ix8RmPm Manual page %s ?ltline %%lt?L/%%L.:byte 
%%bB?s/%%s..?e (END):?pB %%pB\\%%.. (press h for help or q to quit)$PM Manual 
page %s ?ltline %%lt?L/%%L.:byte %%bB?s/%%s..?e (END):?pB %%pB\\%%.. (press h 
for help or q to quit)$"
+
+       if ((p = strchr(name, '/')))
+               name = p + 1;
+
+       n = strlen(name) + (sect ? strlen(sect) : 0) + 3;
+       buf1 = emalloc(n + sizeof(PATTERN));
+       buf2 = emalloc(n);
+
+       if (sect)
+               sprintf(buf1, "%s(%s)", name, sect);
+       else
+               sprintf(buf1, "%s", name);
+       if (setenv("MAN_PN", buf1, 1))
+               weprintf("setenv MAN_PN:");
+
+       backslash(buf2, buf1, "$?:.%\\");
+       sprintf(buf1, PATTERN, buf2, buf2);
+       if (setenv("LESS", buf1, 1))
+               weprintf("setenv LESS:");
+
+       free(buf1);
+       free(buf2);
+
+#undef PATTERN
+}
+
+static void
+checkmap(const char *s, const char *name)
+{
+       char last = ':';
+       int need_eq = 0;
+       for (; *s; last = *s++) {
+               if (*s == ':') {
+                       if (need_eq || last == '=')
+                               goto bad;
+                       need_eq = 1;
+               } else if (*s == '=') {
+                       need_eq = 0;
+                       if (last == ':')
+                               goto bad;
+               }
+       }
+       return;
+bad:
+       eprintf("%s is malformatted\n", name);
+}
+
+static void
+xdup2(int old, int new)
+{
+       if (old != new) {
+               if (dup2(old, new) == -1)
+                       eprintf("dup2:");
+               close(old);
+       }
+}
+
+static void
+xwaitpid(pid_t pid)
+{
+       int status;
+       if (pid < 0)
+               return;
+       if (waitpid(pid, &status, 0) != pid)
+               eprintf("waitpid:");
+       if (status)
+               exit(1);
+}
+
+static char *
+xgetenv(const char *var, const char *def, int secure)
+{
+       const char *env = NULL;
+       if (secure || getuid())
+               env = getenv(var);
+       return estrdup(env ? env : def);
+}
+
+static char *
+xstrchr(char *s, int c)
+{
+       char *r = strchr(s, c);
+       return r ? r : strchr(s, '\0');
+}
+
+static int
+hasextension(const char *path, const char *ext)
+{
+       char *p;
+       if (strlen(path) <= strlen(ext))
+               return 0;
+       p = strchr(path, '\0');
+       p -= strlen(ext);
+       return p[-1] == '.' && !strcmp(p, ext);
+}
+
+static int
+find_man_in_dir(const char *dir, const char *name, const char *sectprefix, 
const char *ext)
+{
+       DIR *d;
+       struct dirent *f;
+       char *p, *sect;
+       int dirfd, fd;
+       size_t n;
+
+       dirfd = open(dir, O_DIRECTORY);
+       if (dirfd < 0)
+               return -1;
+       d = fdopendir(dirfd);
+       if (!d)
+               return -1;
+
+       while ((f = readdir(d))) {
+               p = f->d_name;
+               n = strlen(name);
+               if (strncmp(p, name, n))
+                       continue;
+               p += n;
+               if (*p++ != '.')
+                       continue;
+               n = strlen(sectprefix);
+               if (strncmp(p, sectprefix, n))
+                       continue;
+               sect = p;
+               p = strchr(p + n, '.');
+               if (p)
+                       *p = '\0';
+               if (ext ? (p && !strcmp(p + 1, ext)) : !p) {
+                       setprompt(name, sect);
+                       if (p)
+                               *p = '.';
+                       fd = openat(dirfd, f->d_name, O_RDONLY);
+                       if (fd >= 0) {
+                               closedir(d);
+                               close(dirfd);
+                               return fd;
+                       }
+               }
+       }
+
+       closedir(d);
+       close(dirfd);
+       return -1;
+}
+
+static int
+cleanabspath(char *path, size_t size, const char *cwd)
+{
+       char *p, *q;
+       size_t i, n, len;
+       char *parts[PATH_MAX];
+
+       if (*path != '/') {
+               if (strlen(path) + strlen(cwd) + 2 > size)
+                       return -1;
+               memmove(path + strlen(cwd) + 1, path, strlen(path) + 1);
+               *stpcpy(path, cwd) = '/';
+       }
+
+       for (n = 0, p = q = path; *p; p++) {
+               if (*p == '/') {
+                       *p = '\0';
+                       parts[n++] = q;
+                       q = p + 1;
+               }
+       }
+
+       for (i = 0; i < n;) {
+               if (!parts[i][parts[i][0] == '.']) {
+                       memmove(parts + i, parts + i + 1, --n - i);
+               } else if (parts[i][0] == '.' && parts[i][1] == '.' && 
!parts[i][2]) {
+                       memmove(parts + i, parts + i + 1, --n - i);
+                       if (i) {
+                               i--;
+                               memmove(parts + i, parts + i + 1, --n - i);
+                       }
+               } else {
+                       i++;
+               }
+       }
+
+       for (p = path, i = 0; i < n; i++) {
+               *p++ = '/';
+               len = strlen(parts[i]);
+               memmove(p, parts[i], len);
+               p += len;
+       }
+       *p = '\0';
+       if (!*path)
+               path[0] = '/', path[1] = '\0';
+
+       return 0;
+}
+
+static char *
+getmanpath(const char *name, char *manpathtrans)
+{
+       char *p, *q, *t, *ret, cwd[PATH_MAX], path[PATH_MAX], clean[PATH_MAX];
+       int done = 0;
+
+       if (strlen(name) >= sizeof(path))
+               return NULL;
+
+       if (!getcwd(cwd, sizeof(cwd)))
+               eprintf("getcwd:");
+
+       strcpy(path, name);
+       p = strrchr(path, '/');
+       *p = '\0';
+       p = strrchr(path, '/');
+       if (p && p != path)
+               *p = '\0';
+       if (cleanabspath(path, sizeof(path), cwd))
+               return NULL;
+
+       for (p = manpathtrans; !done; p = q + 1) {
+               q = xstrchr(p, ':');
+               done = !*q;
+               *q = '\0';
+               t = strchr(p, '=');
+               if (!t)
+                       break;
+               *t++ = '\0';
+
+               if (strlen(p) >= sizeof(clean))
+                       continue;
+               strcpy(clean, p);
+               if (cleanabspath(clean, sizeof(clean), cwd))
+                       continue;
+
+               if (strcmp(path, clean))
+                       continue;
+
+               ret = emalloc(strlen(t) + sizeof("/share/man"));
+               stpcpy(stpcpy(ret, t), "/share/man");
+               return ret;
+       }
+
+       ret = emalloc(strlen(path) + sizeof("/share/man"));
+       stpcpy(stpcpy(ret, path), "/share/man");
+       return ret;
+}
+
+static int
+openman(const char *name, char *manpath, char *manpathtrans, char *mansect,
+       char *manbinsect, char *manetcsect, char *mancomp, char *pathbuf, pid_t 
*pid)
+{
+       char *pp = NULL, *ps = NULL, *pc, *qp, *qs, *qc, cp, cs, cc, *p;
+       char *temp_manpath = NULL;
+       const char *orig_name = NULL;
+       int fd, rw[2];
+       struct stat st;
+
+       *pid = -1;
+
+#define FOR(s, p, q, c)\
+       for (p = s, c = 1;\
+            c ? (q = xstrchr(p, ':'), c = *q, *q = '\0', 1) : 0;\
+            *q = c, p = q + 1)
+
+       if (strchr(name, '/')) {
+               if (!stat(name, &st) && S_ISREG(st.st_mode) && (st.st_mode & 
0111)) {
+                       manpath = temp_manpath = getmanpath(name, manpathtrans);
+                       if (!manpath)
+                               return -1;
+                       mansect = manbinsect;
+                       name = strrchr(name, '/') + 1;
+                       goto search;
+               }
+
+               /* This part is a just a convenience, nothing in the POSIX
+                * standard suggests that this feature many be available. */
+               if (strstr(name, "/etc/") == name) {
+                       orig_name = name;
+                       name = strrchr(name, '/') + 1;
+                       mansect = manetcsect;
+                       goto search;
+               }
+
+       try_file:
+               strcpy(pathbuf, name);
+               fd = open(pathbuf, O_RDONLY);
+               if (fd < 0)
+                       eprintf("open %s:", pathbuf);
+               setprompt(pathbuf, NULL);
+               FOR(mancomp, pc, qc, cc) {
+                       *(p = strchr(pc, '=')) = '\0';
+                       if (hasextension(name, pc)) {
+                               *p = '=';
+                               goto match;
+                       }
+                       *p = '=';
+               }
+               pc = NULL;
+               goto match;
+       }
+
+#define SEARCH\
+       FOR(mansect, ps, qs, cs) {\
+               if (!*ps)\
+                       continue;\
+               FOR(manpath, pp, qp, cp) {\
+                       sprintf(pathbuf, "%s/man%c/%s.%s", pp, *ps, name, ps);\
+                       if (pc) {\
+                               *(p = strchr(pc, '=')) = '\0';\
+                               stpcpy(stpcpy(strchr(pathbuf, '\0'), "."), pc);\
+                               *p = '=';\
+                       }\
+                       fd = open(pathbuf, O_RDONLY);\
+                       if (fd >= 0) {\
+                               setprompt(name, ps);\
+                               goto match;\
+                       }\
+                       if (!ps[1]) {\
+                               sprintf(pathbuf, "%s/man%c", pp, *ps);\
+                               if (pc)\
+                                       *(p = strchr(pc, '=')) = '\0';\
+                               fd = find_man_in_dir(pathbuf, name, ps, pc);\
+                               if (pc)\
+                                       *p = '=';\
+                               if (fd >= 0)\
+                                       goto match;\
+                       }\
+               }\
+       }
+
+search:
+       FOR(mancomp, pc, qc, cc)
+               SEARCH
+       pc = NULL;
+       SEARCH
+
+#undef SEARCH
+#undef FOR
+
+       free(temp_manpath);
+       if (orig_name) {
+               name = orig_name;
+               goto try_file;
+       }
+       return -1;
+match:
+
+       free(temp_manpath);
+       if (!pc)
+               return fd;
+       pc = strchr(pc, '=') + 1;
+
+       if (pipe(rw))
+               eprintf("pipe:");
+
+       switch ((*pid = fork())) {
+       case -1:
+               eprintf("fork:");
+       case 0:
+               close(rw[0]);
+               xdup2(fd, STDIN_FILENO);
+               xdup2(rw[1], STDOUT_FILENO);
+               execl("/bin/sh", "sh", "-c", pc, NULL);
+               eprintf("execl /bin/sh:");
+       default:
+               close(rw[1]);
+               close(fd);
+               break;
+       }
+
+       return rw[0];
+}
+
+static int
+convman(int fd, size_t columns, int ascii, int null_stderr, pid_t *pid)
+{
+       char arg1[3 * sizeof(size_t) + sizeof("-rLL=n")];
+       char arg2[3 * sizeof(size_t) + sizeof("-rLT=n")];
+       int rw[2];
+
+       if (pipe(rw))
+               eprintf("pipe:");
+
+       switch ((*pid = fork())) {
+       case -1:
+               eprintf("fork:");
+       case 0:
+               close(rw[0]);
+               xdup2(fd, STDIN_FILENO);
+               xdup2(rw[1], STDOUT_FILENO);
+               if (null_stderr) {
+                       fd = open("/dev/null", O_WRONLY);
+                       if (fd != STDERR_FILENO) {
+                               dup2(fd, STDERR_FILENO);
+                               close(fd);
+                       }
+               }
+               sprintf(arg1, "-rLL=%zun", columns);
+               sprintf(arg2, "-rLT=%zun", columns);
+               execv(GROFF, (char *[]){GROFF, arg1, arg2, "-mtty-char", 
"-mandoc",
+                                       ascii ? "-Tascii" : "-Tutf8", NULL});
+               eprintf("exec %s:", GROFF);
+       default:
+               close(rw[1]);
+               break;
+       }
+
+       return rw[0];
+}
+
+static size_t
+remove_lfs_head(char *str, size_t n)
+{
+       size_t off;
+       char *p;
+       for (p = str; n && *p == '\n'; p++, n--);
+       if (p != str)
+               memmove(str, p, n);
+       p = memchr(str, '\n', n);
+       if (!p)
+               return n;
+       if (*p == '\n')
+               p++;
+       n -= off = (size_t)(++p - str);
+       while (n && *p == '\n')
+               n--, p++;
+       memmove(str + off, p, n);
+       return off + n;
+}
+
+static size_t
+remove_lfs_foot(char *str, size_t n)
+{
+       char *p, *q;
+       p = str + n;
+       if (n && p[-1] == '\n') {
+               while (p != str && p[-1] == '\n')
+                       p--, n--;
+               n++;
+       }
+       while (p != str && p[-1] != '\n')
+               p--;
+       if (p-- == str)
+               return n;
+       if (p == str || p[-1] != '\n')
+               return n;
+       q = --p;
+       while (p != str && p[-1] == '\n')
+               p--;
+       memmove(p, q, n - (size_t)(q - str));
+       return n - (size_t)(q - p);
+}
+
+static size_t
+rmfmt(char *str, size_t n)
+{
+       char *r, *w;
+       for (r = w = str; n--; r++) {
+               if (*r == '\b') {
+                       while (w != str && (w[-1] & 0xC0) == 0x80)
+                               w--;
+                       w -= w != str;
+               } else {
+                       *w++ = *r;
+               }
+       }
+       return (size_t)(w - str);
+}
+
+int
+main(int argc, char *argv[])
+{
+       char *manpathtrans = xgetenv("MANPATHTRANS", MANPATHTRANS, 1);
+       char *manpath = xgetenv("MANPATH", MANPATH, 1);
+       char *mansect = xgetenv("MANSECT", MANSECT, 1);
+       char *manbinsect = xgetenv("MANBINSECT", MANBINSECT, 1);
+       char *manetcsect = xgetenv("MANETCSECT", MANETCSECT, 1);
+       char *mancomp = xgetenv("MANCOMP", MANCOMP, 0);
+       const char *pager = NULL, *env;
+       char *name, *p, *buf;
+       size_t len, ptr, siz, max, columns = 0;
+       int i, rfd = -1, cfd, fd, rw[2];
+       int ascii = 0;
+       int null_stderr;
+       pid_t zpid, ppid;
+       struct winsize winsize;
+       ssize_t r;
+
+       if ((env = getenv("TERM")))
+               ascii = !strcmp(env, "linux");
+
+       ARGBEGIN {
+       case '7':
+               ascii = 1;
+               break;
+       case '8':
+               ascii = 0;
+               break;
+       default:
+               usage();
+       } ARGEND
+       if (!argc)
+               usage();
+
+       checkmap(manpathtrans, "MANPATHTRANS");
+       checkmap(mancomp, "MANCOMP");
+
+       if (isatty(STDOUT_FILENO)) {
+               if (getuid()) {
+                       pager = getenv("MANPAGER");
+                       pager = pager ? pager : getenv("PAGER");
+               }
+               pager = pager ? pager : MANPAGER;
+       }
+
+       len = 0;
+       for (i = 0; i < argc; i++)
+               len += strlen(argv[i]) + 1;
+       p = name = emalloc(len);
+       p = stpcpy(p, argv[0]);
+       for (i = 1; i < argc; i++)
+               *p++ = '-', p = stpcpy(p, argv[i]);
+       *p = '\0';
+
+       buf = emalloc(strlen(name) + strlen(manpath) +
+                     strlen(mansect) + strlen(mancomp) + 9);
+       if (argc > 1 && !strchr(argv[0], '/')) {
+               p = name + strlen(argv[0]);
+               *p = '\0';
+               rfd = openman(p + 1, manpath, manpathtrans, name,
+                             name, name, mancomp, buf, &zpid);
+               *p = '-';
+               if (rfd < 0 && strchr(p + 1, '/'))
+                       eprintf("could not find a manual\n");
+       }
+       if (rfd < 0)
+               rfd = openman(name, manpath, manpathtrans, mansect,
+                             manbinsect, manetcsect, mancomp, buf, &zpid);
+       if (rfd < 0)
+               eprintf("could not find a manual\n");
+
+       free(name);
+       free(manpathtrans);
+       free(manpath);
+       free(mansect);
+       free(manbinsect);
+       free(manetcsect);
+       free(mancomp);
+       free(buf);
+
+       if ((env = getenv("MANWIDTH")))
+               columns = strtonum(env, 0, SIZE_MAX, NULL);
+       if (!columns && (env = getenv("COLUMNS")))
+               columns = strtonum(env, 0, SIZE_MAX, NULL);
+       if (!columns) {
+               if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) == -1)
+                       columns = 80;
+               else
+                       columns = winsize.ws_col;
+       }
+       if ((env = getenv("MANMAXWIDTH"))) {
+               max = strtonum(env, 0, SIZE_MAX, NULL);
+               columns = columns < max ? columns : max;
+       }
+
+       null_stderr = (env = getenv("MAN_KEEP_STDERR"), !env || !*env);
+
+       cfd = convman(rfd, columns, ascii, null_stderr, &ppid);
+       close(rfd);
+
+       buf = emalloc(siz = BUFSIZ);
+       for (len = 0;;) {
+               if (len == siz)
+                       buf = erealloc(buf, siz <<= 1);
+               r = read(cfd, buf + len, siz - len);
+               if (r < 0)
+                       eprintf("read <subprocess>:");
+               if (r == 0)
+                       break;
+               len += (size_t)r;
+       }
+
+       len = remove_lfs_head(buf, len);
+       len = remove_lfs_foot(buf, len);
+       if (!pager && (env = getenv("MAN_KEEP_FORMATTING"), !env || !*env))
+               len = rmfmt(buf, len);
+
+       close(cfd);
+       xwaitpid(zpid);
+       xwaitpid(ppid);
+
+       if (null_stderr) {
+               fd = open("/dev/null", O_WRONLY);
+               if (fd != STDERR_FILENO) {
+                       dup2(fd, STDERR_FILENO);
+                       close(fd);
+               }
+       }
+
+       ppid = -1;
+       if (pager) {
+               if (pipe(rw))
+                       eprintf("pipe:");
+               switch ((ppid = fork())) {
+               case -1:
+                       eprintf("fork:");
+               case 0:
+                       close(rw[1]);
+                       xdup2(rw[0], STDIN_FILENO);
+                       execl("/bin/sh", "sh", "-c", pager, NULL);
+                       eprintf("execl /bin/sh:");
+               default:
+                       close(rw[0]);
+                       xdup2(rw[1], STDOUT_FILENO);
+                       close(STDIN_FILENO);
+                       break;
+               }
+       }
+
+       if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
+               weprintf("signal SIGPIPE:");
+
+       for (ptr = 0; ptr < len;) {
+               r = write(STDOUT_FILENO, buf + ptr, len - ptr);
+               if (r < 0) {
+                       if (errno == EPIPE)
+                               break;
+                       eprintf("write %s:", pager ? "<pager>" : "<stdout>");
+               }
+               ptr += (size_t)r;
+       }
+       free(buf);
+       close(STDOUT_FILENO);
+
+       xwaitpid(ppid);
+
+       return 0;
+}
-- 
2.11.0


Reply via email to