[hackers] [PATCH][sbase] Add man(1)

From: Mattias Andrée <maandree_AT_kth.se>
Date: Wed, 1 Feb 2017 10:25:18 +0100

Signed-off-by: Mattias Andrée <maandree_AT_kth.se>
---
 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
_AT_@ -6,6 +6,7 @@ include config.mk
 HDR =\
 	arg.h\
 	compat.h\
+	config.h\
 	crypt.h\
 	fs.h\
 	md5.h\
_AT_@ -121,6 +122,7 @@ BIN =\
 	logger\
 	logname\
 	ls\
+	man\
 	md5sum\
 	mkdir\
 	mkfifo\
_AT_@ -192,6 +194,9 @@ $(BIN): $(LIB) $(@:=.o)
 
 $(OBJ): $(HDR) config.mk
 
+config.h:
+	cp config.def.h $_AT_
+
 .o:
 	$(CC) $(LDFLAGS) -o $_AT_ $< $(LIB)
 
diff --git a/README b/README
index da2e500..e518796 100644
--- a/README
+++ b/README
_AT_@ -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
_AT_@ -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
_AT_@ -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
_AT_@ -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
Received on Wed Feb 01 2017 - 10:25:18 CET

This archive was generated by hypermail 2.3.0 : Wed Feb 01 2017 - 10:36:18 CET