[hackers] [sbase][PATCH v4] Add man(1) and manpp(1)

From: Mattias Andrée <maandree_AT_kth.se>
Date: Mon, 6 Feb 2017 06:40:59 +0100

manpp(1) preprocesses a man page which can be used
with installing man pages. If this done and man(1)
is configured to not preprocess files, less work will
be involved in displaying man pages. It can also
be used to preprocess man pages that need special
preprocessing, namely chem(1) and grap(1), or removing
conflicting preprocessing (which requires that man(1)
is configured to not preprocess install man pages).

Signed-off-by: Mattias Andrée <maandree_AT_kth.se>
---
 Makefile     |   6 +
 README       |   2 +
 config.def.h |  21 ++
 man.1        | 157 ++++++++++++++
 man.c        | 687 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 manpp.1      |  39 ++++
 manpp.c      | 120 +++++++++++
 7 files changed, 1032 insertions(+)
 create mode 100644 config.def.h
 create mode 100644 man.1
 create mode 100644 man.c
 create mode 100644 manpp.1
 create mode 100644 manpp.c
diff --git a/Makefile b/Makefile
index 9ec9990..9deed7a 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,8 @@ BIN =\
 	logger\
 	logname\
 	ls\
+	man\
+	manpp\
 	md5sum\
 	mkdir\
 	mkfifo\
_AT_@ -192,6 +195,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..14f3343 100644
--- a/README
+++ b/README
_AT_@ -50,6 +50,8 @@ 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 manpp           .
 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..d7c3da2
--- /dev/null
+++ b/config.def.h
_AT_@ -0,0 +1,21 @@
+/* See LICENSE file for copyright and license details. */
+
+#define GROFF "/usr/bin/groff"
+
+#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"
+
+#define MANPAGES_ARE_PREPROCESSED 0
diff --git a/man.1 b/man.1
new file mode 100644
index 0000000..970c52d
--- /dev/null
+++ b/man.1
_AT_@ -0,0 +1,157 @@
+.Dd 2017-02-06
+.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 UTF-8 output.
+.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
+Similar to
+.Ev MANSECT ,
+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 manpp 1 ,
+.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..5437d78
--- /dev/null
+++ b/man.c
_AT_@ -0,0 +1,687 @@
+/* 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;
+	if (!(d = fdopendir(dirfd)))
+		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;
+		if ((p = strchr(p + n, '.')))
+			*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, int *by_filename)
+{
+	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;
+	*by_filename = 0;
+
+#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:
+		*by_filename = 1;
+		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 minpreproc, int null_stderr, pid_t *roffpid)
+{
+	char arg1[3 * sizeof(size_t) + sizeof("-rLL=n")];
+	char arg2[3 * sizeof(size_t) + sizeof("-rLT=n")];
+	int rw[2];
+
+	*roffpid = -1;
+
+	if (pipe(rw))
+		eprintf("pipe:");
+
+	switch ((*roffpid = 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);
+		/* groff does not provide -G(grap) */
+		/* chem (-j) can misformat pages */
+#if MANPAGES_ARE_PREPROCESSED
+		if (minpreproc)
+			execv(GROFF, (char *[]){GROFF, arg1, arg2, "-eg", "-mtty-char", "-mandoc",
+						ascii ? "-Tascii" : "-Tutf8", NULL});
+		else
+#endif
+		execv(GROFF, (char *[]){GROFF, arg1, arg2, "-egkpRst", "-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, by_filename = 0;
+	int null_stderr;
+	pid_t zippid = -1, pagerpid = -1, roffpid = -1;
+	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, &zippid, &by_filename);
+		*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, &zippid, &by_filename);
+	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, !by_filename, null_stderr, &roffpid);
+	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(zippid);
+	xwaitpid(roffpid);
+
+	if (null_stderr) {
+		fd = open("/dev/null", O_WRONLY);
+		if (fd != STDERR_FILENO) {
+			dup2(fd, STDERR_FILENO);
+			close(fd);
+		}
+	}
+
+	if (pager) {
+		if (pipe(rw))
+			eprintf("pipe:");
+		switch ((pagerpid = 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(pagerpid);
+
+	return 0;
+}
diff --git a/manpp.1 b/manpp.1
new file mode 100644
index 0000000..9c9acf8
--- /dev/null
+++ b/manpp.1
_AT_@ -0,0 +1,39 @@
+.Dd 2017-02-06
+.Dt MANPP 1
+.Os sbase
+.Sh NAME
+.Nm manpp
+.Nd preprocesses a man pages
+.Sh SYNOPSIS
+.Nm
+.Op Fl GjkpRst
+.Sh DESCRIPTION
+.Nm
+reads a man pages from stdin and preprocesses it
+for display with
+.Xr man 1 .
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl G
+Preprocess with
+.Xr grap 1 .
+.It Fl j
+Preprocess with
+.Xr chem 1 .
+.It Fl k
+Do not preprocess with
+.Xr preconv 1 .
+.It Fl p
+Do not preprocess with
+.Xr pic 1 .
+.It Fl R
+Do not preprocess with
+.Xr refer 1 .
+.It Fl s
+Do not preprocess with
+.Xr soelim 1 .
+.It Fl t
+Do not preprocess with
+.Xr tbl 1 .
+.Sh SEE ALSO
+.Xr man 1
diff --git a/manpp.c b/manpp.c
new file mode 100644
index 0000000..ef62558
--- /dev/null
+++ b/manpp.c
_AT_@ -0,0 +1,120 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/wait.h>
+
+#include <unistd.h>
+
+#include "util.h"
+
+static char flags[] = {
+	['G'] = 0,
+	['j'] = 0,
+	['k'] = 1,
+	['p'] = 1,
+	['R'] = 1,
+	['s'] = 1,
+	['t'] = 1,
+};
+
+static const char *cmds[] = {
+	['G'] = "grap",
+	['j'] = "chem",
+	['k'] = "preconv",
+	['p'] = "pic",
+	['R'] = "refer",
+	['s'] = "soelim",
+	['t'] = "tbl",
+};
+
+static void
+usage(void)
+{
+	eprintf("usage: %s [-GjkpRst]\n", argv0);
+}
+
+static int
+pipeto(int fd, const char *cmd, pid_t *pid)
+{
+	int rw[2];
+	if (pipe(rw))
+		eprintf("pipe:");
+	switch ((*pid = fork())) {
+	case -1:
+		eprintf("fork:");
+	case 0:
+		break;
+	default:
+		close(fd);
+		close(rw[1]);
+		return rw[0];
+	}
+	close(rw[0]);
+	if (fd != STDIN_FILENO) {
+		if (dup2(fd, STDIN_FILENO) == -1)
+			eprintf("dup2:");
+		close(fd);
+	}
+	if (rw[1] != STDOUT_FILENO) {
+		if (dup2(rw[1], STDOUT_FILENO) == -1)
+			eprintf("dup2:");
+		close(rw[1]);
+	}
+	execlp(cmd, cmd, NULL);
+	eprintf("execlp %s:", cmd);
+	return -1;
+}
+
+int
+main(int argc, char *argv[])
+{
+	const char *f, **t, *tools[sizeof(cmds) / sizeof(*cmds) + 1];
+	pid_t *p, pids[sizeof(cmds) / sizeof(*cmds) + 1];
+	int fd = STDIN_FILENO, status;
+	char *b, buf[BUFSIZ];
+	ssize_t r, n;
+
+	ARGBEGIN {
+	case 'G':
+	case 'j':
+	case 'k':
+	case 'p':
+	case 'R':
+	case 's':
+	case 't':
+		flags[(int)ARGC()] ^= 1;
+		break;
+	default:
+		usage();
+	} ARGEND
+	if (argc)
+		usage();
+
+	t = tools, p = pids;
+	for (f = "ksRGjpt"; *f; f++) {
+		if (flags[(int)*f])
+			*t++ = cmds[(int)*f];
+	}
+	*t = NULL, *p = -1;
+
+	for (t = tools, p = pids; *t; t++, p++)
+		fd = pipeto(fd, *t, p);
+
+	for (;;) {
+		n = read(fd, buf, sizeof(buf));
+		if (n < 0)
+			eprintf("read %s:", fd == STDIN_FILENO ? "<stdin>" : "<subprocess>");
+		if (!n)
+			break;
+		for (b = buf; n; n -= r, b += r)
+			if ((r = write(STDOUT_FILENO, b, n)) < 0)
+				eprintf("write <stdout>:");
+	}
+
+	for (p = pids; *p != -1; p++) {
+		if (waitpid(*p, &status, 0) != *p)
+			eprintf("waitpid:");
+		if (status)
+			return 1;
+	}
+
+	return 0;
+}
-- 
2.11.1
Received on Mon Feb 06 2017 - 06:40:59 CET

This archive was generated by hypermail 2.3.0 : Mon Feb 06 2017 - 06:48:19 CET