EXTRA_DIST =
CLEANFILES =
DISTCLEANFILES =
+PROGMANS =
+LIBMANS =
confsubst = $(top_srcdir)/config/confsubst
## Utility macros.
pkginclude_HEADERS += macros.h
+LIBMANS += macros.3
## Alignment.
pkginclude_HEADERS += align.h
+LIBMANS += align.3
## Bit manipulation.
pkginclude_HEADERS += bits.h
+LIBMANS += bits.3
EXTRA_DIST += bits.in bits-testgen.c
tests:: bits.t bits.in
## Exceptions.
pkginclude_HEADERS += exc.h
libmLib_la_SOURCES += exc.c
+LIBMANS += exc.3
## String handling.
pkginclude_HEADERS += str.h
libutils_la_SOURCES += str.c
+LIBMANS += str.3
## Version comparison.
pkginclude_HEADERS += versioncmp.h
libmLib_la_SOURCES += versioncmp.c
+LIBMANS += versioncmp.3
EXTRA_DIST += versioncmp.in
tests:: versioncmp.t versioncmp.in
## Memory allocation.
pkginclude_HEADERS += alloc.h arena.h sub.h
libmLib_la_SOURCES += alloc.c arena.c sub.c
+LIBMANS += alloc.3 arena.3 sub.3
## Pool allocator.
pkginclude_HEADERS += pool.h
libmLib_la_SOURCES += pool.c pool-file.c pool-sub.c
+LIBMANS += pool.3
###--------------------------------------------------------------------------
### Hashing.
## CRC32.
pkginclude_HEADERS += crc32.h
libmLib_la_SOURCES += crc32.c crc32-tab.c
+LIBMANS += crc32.3
bin_PROGRAMS += crc-mktab
crc_mktab_SOURCES = crc-mktab.c $(UTIL_SOURCES)
crc_mktab_LDADD = libutils.la
+PROGMANS += crc-mktab.1
BUILT_SOURCES += crc32-tab.c
CLEANFILES += crc32-tab.c
libutils_la_SOURCES += unihash.c
libmLib_la_SOURCES += unihash-global.c
BUILT_SOURCES += unihash-global.c
+LIBMANS += unihash.3
bin_PROGRAMS += unihash-mkstatic
unihash_mkstatic_SOURCES = unihash-mkstatic.c
unihash_mkstatic_LDADD = libutils.la
+PROGMANS += unihash-mkstatic.1
BUILT_SOURCES += unihash-global.c
CLEANFILES += unihash-global.c
## Dynamic strings.
pkginclude_HEADERS += dstr.h dspool.h
libmLib_la_SOURCES += dstr.c dputf.c dspool.c
+LIBMANS += dstr.3 dspool.3
## Buffers.
pkginclude_HEADERS += buf.h
libmLib_la_SOURCES += buf.c buf-dstr.c
+LIBMANS += buf.3
## Dynamic arrays.
pkginclude_HEADERS += darray.h
libmLib_la_SOURCES += darray.c
+LIBMANS += darray.3
CLEANFILES += da.out
tests:: darray.t da.in da.ref
## Hash tables.
pkginclude_HEADERS += hash.h
libmLib_la_SOURCES += hash.c
+LIBMANS += hash.3
## Symbol tables.
pkginclude_HEADERS += sym.h
libmLib_la_SOURCES += sym.c
+LIBMANS += sym.3
CLEANFILES += sym.out
tests:: sym.t sym.in sym.ref
## Atoms.
pkginclude_HEADERS += atom.h
libmLib_la_SOURCES += atom.c
+LIBMANS += atom.3
## Association tables.
pkginclude_HEADERS += assoc.h
libmLib_la_SOURCES += assoc.c
+LIBMANS += assoc.3
CLEANFILES += assoc.out
tests:: assoc.t sym.in sym.ref
libmdwopt_la_SOURCES = mdwopt.c
libmdwopt_la_CPPFLAGS = $(AM_CPPFLAGS) -DBUILDING_MLIB
libutils_la_LIBADD += libmdwopt.la
+LIBMANS += mdwopt.3
## Program naming.
pkginclude_HEADERS += quis.h
libutils_la_SOURCES += quis.c pquis.c
+LIBMANS += quis.3
## Error reporting.
pkginclude_HEADERS += report.h
libutils_la_SOURCES += report.c
+LIBMANS += report.3
###--------------------------------------------------------------------------
### Encoding and decoding.
## form-urlencoded
pkginclude_HEADERS += url.h
libmLib_la_SOURCES += url.c
+LIBMANS += url.3
## base64
pkginclude_HEADERS += base64.h
libmLib_la_SOURCES += base64.c
+LIBMANS += base64.3
EXTRA_DIST += base64.in base64.ref
CLEANFILES += base64.out
## base32
pkginclude_HEADERS += base32.h
libmLib_la_SOURCES += base32.c
+LIBMANS += base32.3
EXTRA_DIST += base32.in base32.ref
CLEANFILES += base32.out
## hex
pkginclude_HEADERS += hex.h
libmLib_la_SOURCES += hex.c
+LIBMANS += hex.3
EXTRA_DIST += hex.in hex.ref
CLEANFILES += hex.out
## Daemons.
pkginclude_HEADERS += daemonize.h
libmLib_la_SOURCES += daemonize.c
+LIBMANS += daemonize.3
## Environment variables.
pkginclude_HEADERS += env.h
libmLib_la_SOURCES += env.c
+LIBMANS += env.3
## File and descriptor flags.
pkginclude_HEADERS += fdflags.h
libmLib_la_SOURCES += fdflags.c
+LIBMANS += fdflags.3
## File descriptor passing.
pkginclude_HEADERS += fdpass.h
libmLib_la_SOURCES += fdpass.c
+LIBMANS += fdpass.3
## Watching files for modification.
pkginclude_HEADERS += fwatch.h
libmLib_la_SOURCES += fwatch.c
+LIBMANS += fwatch.3
## File locking.
pkginclude_HEADERS += lock.h
libmLib_la_SOURCES += lock.c
+LIBMANS += lock.3
+
+## File descriptor juggling.
+pkginclude_HEADERS += mdup.h
+libmLib_la_SOURCES += mdup.c
+LIBMANS += mdup.3
+
+check_PROGRAMS += mdup.t
+mdup_t_SOURCES = mdup-test.c
+mdup_t_CPPFLAGS = $(TEST_CPPFLAGS)
+mdup_t_LDFLAGS = -static
+
+EXTRA_DIST += mdup-test.sh
+CLEANFILES += mdup.[0-9]*.out mdup.[0-9]*.err
+tests:: mdup.t mdup-test.sh
+ $(srcdir)/mdup-test.sh
## Time arithmetic.
pkginclude_HEADERS += tv.h
libmLib_la_SOURCES += tv.c
+LIBMANS += tv.3
###--------------------------------------------------------------------------
### Buffering.
## Line buffering.
pkginclude_HEADERS += lbuf.h
libmLib_la_SOURCES += lbuf.c
+LIBMANS += lbuf.3
## Packet buffering.
pkginclude_HEADERS += pkbuf.h
libmLib_la_SOURCES += pkbuf.c
+LIBMANS += pkbuf.3
###--------------------------------------------------------------------------
### Network utilities.
## Core event selection.
pkginclude_HEADERS += sel.h
libmLib_la_SOURCES += sel.c
+LIBMANS += sel.3
## Waiting for buffers to fill.
pkginclude_HEADERS += selbuf.h selpk.h
libmLib_la_SOURCES += selbuf.c selpk.c
+LIBMANS += selbuf.3 selpk.3
## RFC931 identification.
pkginclude_HEADERS += ident.h
libmLib_la_SOURCES += ident.c
+LIBMANS += ident.3
## Nonblocking connections.
pkginclude_HEADERS += conn.h
libmLib_la_SOURCES += conn.c
+LIBMANS += conn.3
## Signal handling
pkginclude_HEADERS += sig.h
libmLib_la_SOURCES += sig.c
+LIBMANS += sig.3
## Name resolution.
pkginclude_HEADERS += bres.h
+LIBMANS += bres.3
if WITH_ADNS
libmLib_la_SOURCES += bres-adns.c
## Tracing.
pkginclude_HEADERS += trace.h
libmLib_la_SOURCES += trace.c traceopt.c
+LIBMANS += trace.3
## Memory tracking.
pkginclude_HEADERS += track.h
## Testing.
pkginclude_HEADERS += testrig.h
libmLib_la_SOURCES += testrig.c
+LIBMANS += testrig.3
###--------------------------------------------------------------------------
### Manual.
-SUBDIRS += man
-
-install-man:
- (cd man && $(MAKE) install-man)
+EXTRA_DIST += $(LIBMANS) $(PROGMANS)
+
+install-data-local: install-man
+install-man: $(LIBMANS) $(PROGMANS)
+ @$(NORMAL_INSTALL)
+ $(mkinstalldirs) $(DESTDIR)$(mandir)
+ $(top_srcdir)/config/maninst \
+ -d $(DESTDIR)$(mandir) -s $(srcdir) \
+ -i "$(INSTALL)" \
+ install $(PROGMANS)
+ $(top_srcdir)/config/maninst \
+ -d $(DESTDIR)$(mandir) -s $(srcdir) \
+ -i "$(INSTALL)" -e $(manext) \
+ install $(LIBMANS)
+.PHONY: install-man
+
+uninstall-local: uninstall-man
uninstall-man:
- (cd man && $(MAKE) uninstall-man)
-.PHONY: install-man uninstall-man
-
+ @$(NORMAL_UNINSTALL)
+ $(top_srcdir)/config/maninst \
+ -d $(DESTDIR)$(mandir) -s $(srcdir) \
+ uninstall $(PROGMANS)
+ $(top_srcdir)/config/maninst \
+ -d $(DESTDIR)$(mandir) -s $(srcdir) -e $(manext) \
+ uninstall $(LIBMANS)
+.PHONY: uninstall-man
###--------------------------------------------------------------------------
### Distribution.
--- /dev/null
+.\" -*-nroff-*-
+.TH align 3 "4 January 2009" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+align \- alignment utilities
+.\" @ALIGN
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/align.h>"
+
+.BI "size_t ALIGN(size_t " sz ");"
+.fi
+.SH DESCRIPTION
+The
+.B ALIGN
+macro returns the value of its argument
+.I sz
+rounded up to the next multiple of the size of
+.BR "union align" ,
+which is defined as a union containing a selection of built-in types.
+The intent is to write fairly portable memory allocators, which must
+return correctly-aligned memory.
+.IR array .
+.SH "SEE ALSO"
+.BR mLib (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
AC_CONFIG_HEADER([config/config.h])
AC_CONFIG_FILES(
- [Makefile]
- [man/Makefile])
+ [Makefile])
AC_OUTPUT
dnl ----- That's all, folks -------------------------------------------------
+++ /dev/null
-Makefile.in
+++ /dev/null
-## -*-makefile-*-
-##
-## $Id$
-##
-## Makefile for mLib's manual pages
-##
-## (c) 1999 Straylight/Edgeware
-##
-
-##----- Licensing notice ----------------------------------------------------
-##
-## This file is part of the mLib utilities library.
-##
-## mLib is free software; you can redistribute it and/or modify
-## it under the terms of the GNU Library General Public License as
-## published by the Free Software Foundation; either version 2 of the
-## License, or (at your option) any later version.
-##
-## mLib is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU Library General Public License for more details.
-##
-## You should have received a copy of the GNU Library General Public
-## License along with mLib; if not, write to the Free
-## Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-## MA 02111-1307, USA.
-
-AUTOMAKE_OPTIONS = foreign
-
-manext = @manext@
-
-## --- Manual pages ---
-
-MANPAGES = \
- crc-mktab.1 unihash-mkstatic.1
-
-MANPAGESEXT = \
- alloc.3 arena.3 assoc.3 atom.3 base32.3 base64.3 bits.3 bres.3 \
- buf.3 conn.3 crc32.3 darray.3 daemonize.3 dspool.3 dstr.3 env.3 \
- exc.3 fdflags.3 fwatch.3 hash.3 ident.3 lbuf.3 lock.3 mLib.3 \
- mdwopt.3 pkbuf.3 pool.3 quis.3 report.3 sel.3 selbuf.3 selpk.3 \
- sig.3 str.3 sub.3 sym.3 testrig.3 trace.3 tv.3 url.3 hex.3 fdpass.3 \
- macros.3 unihash.3 versioncmp.3
-
-install-man: $(MANPAGES)
- @$(NORMAL_INSTALL)
- $(mkinstalldirs) $(DESTDIR)$(mandir)
- $(top_srcdir)/config/maninst \
- -d $(DESTDIR)$(mandir) -s $(srcdir) \
- -i "$(INSTALL)" \
- install $(MANPAGES)
- $(top_srcdir)/config/maninst \
- -d $(DESTDIR)$(mandir) -s $(srcdir) \
- -i "$(INSTALL)" -e $(manext) \
- install $(MANPAGESEXT)
-
-uninstall-man: $(MANPAGS)
- @$(NORMAL_UNINSTALL)
- $(top_srcdir)/config/maninst \
- -d $(DESTDIR)$(mandir) -s $(srcdir) \
- uninstall $(MANPAGES)
- $(top_srcdir)/config/maninst \
- -d $(DESTDIR)$(mandir) -s $(srcdir) -e $(manext) \
- uninstall $(MANPAGESEXT)
-
-install-data-local: install-man
-uninstall-local: uninstall-man
-
-EXTRA_DIST = $(MANPAGES) $(MANPAGESEXT)
-
-##----- That's all, folks ---------------------------------------------------
--- /dev/null
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "mdup.h"
+
+#define MAXFD 256
+
+static void fail(const char *what) { perror(what); exit(1); }
+
+int main(int argc, char *argv[])
+{
+ int i, n;
+ int fd, fd2;
+ struct stat st;
+ int ino[MAXFD];
+ int flag[MAXFD];
+ mdup_fd fds[MAXFD];
+ int win = 1;
+
+ for (i = 0; i < argc - 1; i++) {
+ if (i >= MAXFD) {
+ fprintf(stderr, "too many\n");
+ exit(1);
+ }
+ if (sscanf(argv[i + 1], "%d:%d", &fds[i].cur, &fds[i].want) < 2 ||
+ fds[i].cur >= MAXFD) {
+ fprintf(stderr, "bad syntax\n");
+ exit(1);
+ }
+ }
+ n = argc - 1;
+ for (i = 0; i < MAXFD; i++) flag[i] = -1;
+ for (i = 0; i < n; i++) {
+ fd = fds[i].cur;
+ if (flag[fd] >= 0)
+ ino[i] = ino[flag[fd]];
+ else {
+ flag[fd] = i;
+ if ((fd2 = open(",delete-me",
+ O_WRONLY | O_CREAT | O_EXCL,
+ 0700)) < 0)
+ fail("creat");
+ unlink(",delete-me");
+ if (fd2 != fd) {
+ if (dup2(fd2, fd) < 0) fail("dup2");
+ close(fd2);
+ }
+ if (fstat(fd, &st)) fail("fstat");
+ ino[i] = st.st_ino;
+ }
+ }
+
+ if (mdup(fds, n)) fail("mdup");
+
+ for (i = 0; i < n; i++) {
+ fd = fds[i].cur;
+ if (fds[i].want != -1 && fds[i].want != fd) {
+ printf("fd %d[%d] != %d\n", fd, i, fds[i].want);
+ win = 0;
+ } else if (fstat(fd, &st)) {
+ printf("fstat %d[%d] failed: %s\n", fd, i, strerror(errno));
+ win = 0;
+ } else if (st.st_ino != ino[i]) {
+ printf("ino %d[%d] wrong\n", fd, i);
+ win = 0;
+ }
+ }
+
+ return (!win);
+}
--- /dev/null
+#! /bin/sh
+
+set -e
+: ${test=./mdup.t}
+cases=
+
+###--------------------------------------------------------------------------
+### Set up the test cases.
+###
+### The mdup-test program takes a command-line representation of an mdup_fd
+### array, calls mdup, and checks the result. In particular, it ensures that
+### the file descriptors returned are the ones asked for, and that the
+### resulting file descriptors actually correspond to the requested files.
+### (It does the latter by comparing inodes before and after.)
+
+## Very simple tests.
+cases="$cases 3:4"
+cases="$cases 4:3"
+
+## Overlapping sources and destinations.
+cases="$cases 4:3,3:5,5:6"
+
+## Repeated sources.
+cases="$cases 3:4,3:3,3:-1"
+cases="$cases 5:8,3:4,3:5,4:6"
+
+## Cycles.
+cases="$cases 5:7,3:4,3:5,4:6,5:3"
+cases="$cases 5:8,3:4,3:5,4:6,5:3"
+
+###--------------------------------------------------------------------------
+### Actually run the tests.
+
+## Initialize counters.
+win=0
+lose=0
+total=0
+
+## Run the tests.
+for case in $cases; do
+ total=$(expr $total + 1)
+ case=$(echo "$case" | sed 'y/,/ /')
+ printf "%d: %-60s " $total "$case"
+ if $test $case >mdup.$total.out 2>mdup.$total.err; then
+ echo "ok"
+ win=$(expr $win + 1)
+ rm mdup.$total.out mdup.$total.err
+ else
+ echo "FAIL"
+ lose=$(expr $lose + 1)
+ fi
+done
+
+## Announce the outcome.
+if [ $win = $total ]; then
+ echo "All $total tests successful."
+ rc=0
+else
+ echo "FAILED $lose of $total tests."
+ rc=1
+fi
+
+## And exit.
+exit $rc
+
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.de hP
+.IP
+.ft B
+\h'-\w'\\$1\ 'u'\\$1\ \c
+.ft P
+..
+.ie t .ds o \(bu
+.el .ds o o
+.TH mdup 3 "4 January" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+mdup \- renumber file descriptors
+.SH SYNOPSIS
+.B "#include <mLib/mdup.h>"
+
+.BI "int mdup(mdup_fd *" v ", size_t " n ");"
+.SH DESCRIPTION
+The
+.B mdup
+function renumbers file descriptors, using the
+.BR dup (2)
+and
+.BR dup2 (2)
+system calls. Its arguments are a pointer
+.I v
+to a vector of
+.B mdup_fd
+structures, and the length
+.I n
+of this vector, in elements. The
+.B mdup_fd
+structure is defined as
+.VS
+typedef struct mdup_fd {
+ int cur;
+ int want;
+} mdup_fd;
+.VE
+Each `slot' (element) in the vector
+.I v
+represents a file. The slot's
+.B cur
+member names the current file descriptor for this file; the
+.B want
+member is the file descriptor to move it to. In order to keep a file
+alive when you don't care which descriptor it ends up with, set
+.I want
+= \-1. Several slots may specify the same
+.B cur
+descriptor; but they all have to declare different
+.BR want s
+(except that several slots may have
+.I want
+= \-1.
+.PP
+On successful exit, the function will have rearranged the file
+descriptors as requested. To reflect this, the
+.B cur
+members will all be set to match the
+.B want
+members (except where the latter are \-1).
+.PP
+If there is a failure, then some rearrangement may have been performed
+and some not; the
+.B cur
+members are set to reflect which file descriptors are to be used.
+.PP
+The old file descriptors are
+.IR closed .
+This is different from usual
+.BR dup (2)
+behaviour, of course, but essential for reliable error handling. If you
+want to keep a particular source file descriptor open as well as make a
+new copy then specify two slots with the same
+.BR cur ,
+one with
+.B want " = " cur
+and one with the desired output descriptor.
+.PP
+The
+.B mdup
+function is capable of arbitrary file descriptor remappings. In
+particular, it works correctly even if the desired remappings contain
+cycles.
+.SS "Background: the problem that mdup solves"
+The
+.B mdup
+function is intended to be used to adjust file descriptors prior to
+invoking one of the
+.B exec
+system calls. The standard use of
+.BR dup (2)
+to establish the child process's standard input/output/error files is
+prone to errors in the case where the newly opened file in fact already
+has one of the relevant file descriptors.
+.PP
+Consider the case where we want to run a process with separate pipes
+attached to each of the standard descriptors. Typical code looks like
+this.
+.VS
+#define P_INIT { \-1, \-1 }
+int p_in[2] = P_INIT, p_out[2] = P_INIT, p_err[2] = P_INIT;
+pid_t kid = -1;
+int i;
+
+if (pipe(p_in) || pipe(p_out) || pipe(p_err)) goto error;
+if ((kid = fork()) < 0) goto error;
+if (!kid) {
+ if (dup2(p_in[0], STDIN_FILENO) < 0 ||
+ dup2(p_out[1], STDOUT_FILENO) < 0 ||
+ dup2(p_err[2], STDERR_FILENO) < 0 ||
+ close(p_in[0]) || close(p_out[0]) || close(p_err[0]) ||
+ close(p_in[1]) || close(p_out[1]) || close(p_err[1]))
+ _exit(127);
+ execvp("/bin/sh", "sh", "-c", "...", (char *)0);
+}
+\&...
+.VE
+Now suppose that, in the parent process, the standard input, output and
+error descriptors are all initially closed. After the calls to
+.BR pipe (2),
+descriptors 0, 1, and 2 refer to
+.BR p_in[0] ,
+.BR p_in[1] ,
+and
+.B p_out[0]
+respectively. In the child process, the calls to
+.BR dup2 (2)
+rearrange these. But then the
+.BR close (2)
+calls will immediately close all three descriptors, before
+.BR exec ing
+the child.
+.PP
+Here's how to rewrite the above function using
+.BR mdup .
+.VS
+#define P_INIT { \-1, \-1 }
+int p_in[2] = P_INIT, p_out[2] = P_INIT, p_err[2] = P_INIT;
+pid_t kid = -1;
+mdup_fd md[3];
+int i;
+
+if (pipe(p_in) || pipe(p_out) || pipe(p_err)) goto error;
+if ((kid = fork()) < 0) goto error;
+if (!kid) {
+ if (close(p_in[1] || close(p_out[0]) || close(p_err[0]))
+ goto _exit(127);
+ md[0].cur = p_in[0]; md[0].want = STDIN_FILENO;
+ md[1].cur = p_out[1]; md[1].want = STDOUT_FILENO;
+ md[2].cur = p_err[1]; md[2].want = STDERR_FILENO;
+ if (mdup(md, 3)) _exit(127);
+ execvp("/bin/sh", "sh", "-c", "...", (char *)0);
+}
+\&...
+.VE
+One can see that, not only is the resulting program more correct, it's
+also simpler. Note that we close the unwanted ends of the pipes
+.I before
+invoking
+.BR mdup .
+Closing them afterwards risks interfering with the newly assigned
+descriptors which are meant to be passed to the child process. Note
+also that
+.B mdup
+has taken responsibility for closing the other descriptors for the
+wanted ends of the pipes.
+.SH "SEE ALSO"
+.BR dup (2),
+.BR dup2 (2),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
+
--- /dev/null
+/* -*-c-*-
+ *
+ * Duplicate multiple files
+ *
+ * (c) 2008 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include <unistd.h>
+
+#include "mdup.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct mdup_fdinfo {
+
+ mdup_fd *f;
+ /* Each @fdinfo@ structure refers to one of the caller's @fd@ structures.
+ * This is it.
+ */
+
+ struct mdup_fdinfo *eqnext, *eqprev;
+ /* The caller's request list can contain more than one entry with any given
+ * @cur@ descriptor. We group them together into an equivalence class,
+ * which is doubly linked using these fields.
+ */
+
+ struct mdup_fdinfo *up;
+ /* We require that there be at most one node with any given @want@
+ * descriptor (other than @-1@). There is therefore at most one node whose
+ * @want@ is equal to my @cur@. If such a node exists, @up@ points to it;
+ * otherwise @up@ is null.
+ */
+
+ struct mdup_fdinfo *down;
+ /* Obviously, @down@ links in the opposite direction from @up@. However,
+ * there may be several nodes whose @cur@ equals my @want@; therefore
+ * @down@ simply links to one of the nodes in the equivalence class.
+ *
+ * Unsurprisingly, @down@ is the direction we move during the depth-first
+ * traversal phase of the operation.
+ */
+
+ struct mdup_fdinfo *dlink;
+ /* Nodes with @want == -1@, and nodes where we've broken cycles, are
+ * considered `dynamic': their @cur@ has been chosen by @dup@ to be
+ * distinct from any existing descriptor, but may collide with a @want@.
+ * We check each proposed move against the list of dynamic nodes, and move
+ * them out of the way as necessary. Note that this is really a list of
+ * equivalence classes rather than single nodes.
+ */
+
+ unsigned state;
+ /* The current state of this node. One of the @ST@ constants described
+ * below.
+ */
+} mdup_fdinfo;
+
+enum {
+ ST_READY,
+ /* Node has not yet been processed.
+ */
+
+ ST_MARK,
+ /* Node has been reached by the depth-first traversal, but its descriptor
+ * has not yet been moved. This state is used to detect cycles using the
+ * depth-first traversal.
+ */
+
+ ST_DONE,
+ /* Node has been processed completely. We have @want == -1@ or
+ * @want == cur@.
+ */
+
+ ST_BROKEN,
+ /* Node has been clobbered in order to break a cycle. The node's
+ * equivalence class has been remapped to a fresh descriptor which (we
+ * hope) is not equal to any node's @want@. All broken nodes are put on
+ * the dynamic list: if our hope turns out to be misplaced we can remap the
+ * class again.
+ */
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @DO_EQUIVS@ --- *
+ *
+ * Perform @body@ once for each @g@ in the equivalence class of @f@.
+ */
+
+#define DO_EQUIVS(g, f, body) do { \
+ mdup_fdinfo *f_ = (f), *g_ = f_; \
+ do { mdup_fdinfo *g = g_; g_ = g_->eqnext; body; } while (g_ != f_); \
+} while (0)
+
+/* --- @dump@ --- *
+ *
+ * Arguments: @mdup_fdinfo *v@ = pointer to info vector
+ * @size_t n@ = size of vector
+ *
+ * Returns: ---
+ *
+ * Use: Dumps a scary-looking description of the state of @mdup@'s
+ * workings.
+ */
+
+#ifdef DEBUG
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#define D(x) x
+
+static void dump(mdup_fdinfo *v, size_t n, mdup_fdinfo *dhead,
+ const char *fmt, ...)
+{
+ int i;
+ mdup_fdinfo *f, *g;
+ static const char *state[] = { "READY", "MARK", "DONE", "BROKEN" };
+ va_list ap;
+
+#define INDEX(p) ((p) ? (int)((p) - (v)) : -1)
+
+ /* --- Dump the items, fairly raw --- */
+
+ va_start(ap, fmt);
+ fputs("*** ", stdout);
+ vprintf(fmt, ap);
+ putchar('\n');
+ for (i = 0; i < n; i++) {
+ f = &v[i];
+ printf("%3d: %-6s %3d -> %3d; "
+ "equivs: %3d, %3d; up: %3d; down: %3d; dyn: %3d\n",
+ i, state[f->state], f->f->cur, f->f->want,
+ INDEX(f->eqprev), INDEX(f->eqnext),
+ INDEX(f->up), INDEX(f->down), INDEX(f->dlink));
+ }
+ putchar('\n');
+ va_end(ap);
+
+#undef INDEX
+}
+
+#else
+
+#define D(x)
+
+#endif
+
+/* --- @dfs@ --- *
+ *
+ * Arguments: @mdup_fdinfo *f@ = which node to process
+ * @mdup_fdinfo **dhead, ***dtail@ = the dynamic list
+ *
+ * Returns: Zero on success, @-1@ on some OS failure.
+ *
+ * Use: Recursive depth-first traversal of the descriptor graph.
+ *
+ * On exit, the node @f@ will be in state @ST_DONE@ or
+ * @ST_BROKEN@.
+ */
+
+static int dfs(mdup_fdinfo *f, mdup_fdinfo **dhead, mdup_fdinfo ***dtail)
+{
+ mdup_fdinfo *d;
+ mdup_fd *ff;
+ int can_close_p = 1;
+ int fd, ofd;
+ int e;
+
+ /* --- Null pointers need no processing --- *
+ *
+ * Null pointers mark the end of descending chains.
+ */
+
+ if (!f)
+ return (0);
+
+ /* --- Otherwise our behaviour depends on the node's state --- */
+
+ switch (f->state) {
+
+ /* --- The standard processing, in several phases --- */
+
+ case ST_READY:
+
+ /* --- Mark the class as being in-progress --- */
+
+ DO_EQUIVS(g, f, { g->state = ST_MARK; });
+
+ /* --- Ensure that the our proposed destination is clear --- *
+ *
+ * The depth-first traversal will leave the node in @ST_DONE@ or
+ * @ST_BROKEN@ afterwards; either way, its @cur@ will not be same as
+ * our @want@.
+ *
+ * Note that this can move @%\emph{us}@ to @ST_BROKEN@. This is not a
+ * significant problem.
+ */
+
+ DO_EQUIVS(g, f, { if (dfs(g->down, dhead, dtail)) return (-1); });
+
+ /* --- Now the real work can begin --- *
+ *
+ * For each node in the class, copy the descriptor from @cur@ to
+ * @want@. Before doing this, we must move out of the way any (other)
+ * dynamic nodes whose @cur@ matches our @want@.
+ *
+ * Interestingly, this is the only point in the function where we need
+ * nontrivial error handling: if something goes wrong with one of the
+ * @dup2@ calls, we must close the descriptors made so far this pass
+ * before returning.
+ */
+
+ ofd = f->f->cur;
+ DO_EQUIVS(g, f, {
+ ff = g->f;
+ for (d = *dhead; d; d = d->dlink) {
+ if (d != f && d->f->cur == ff->want) {
+ if ((fd = dup(ff->want)) < 0)
+ goto fail;
+ DO_EQUIVS(dd, d, { dd->f->cur = fd; });
+ close(ff->want);
+ }
+ }
+ if (ff->cur == ff->want)
+ can_close_p = 0;
+ else if (dup2(ofd, ff->want) < 0)
+ goto fail;
+ goto ok;
+ fail:
+ e = errno;
+ for (g = g->eqprev; g != f->eqprev; g = g->eqprev) {
+ if (g->f->want != g->f->cur)
+ close(g->f->want);
+ }
+ errno = e;
+ return (-1);
+ ok:;
+ });
+
+ /* --- We're done --- *
+ *
+ * If the original descriptor isn't wanted by anyone we can (and must)
+ * close it. Nodes can now move to @ST_DONE@.
+ */
+
+ if (can_close_p)
+ close(ofd);
+ DO_EQUIVS(g, f, {
+ g->f->cur = g->f->want;
+ g->state = ST_DONE;
+ });
+ break;
+
+ /* --- We have encoutered a cycle --- *
+ *
+ * The caller wants our descriptor. We therefore shunt this entire
+ * equivalence class to a new descriptor, and link it onto the dynamic
+ * list. Mark it as broken so that we don't try to do anything
+ * complicated to it again.
+ */
+
+ case ST_MARK:
+ ofd = f->f->cur;
+ if ((fd = dup(ofd)) < 0)
+ return (-1);
+ DO_EQUIVS(g, f, {
+ g->f->cur = fd;
+ g->state = ST_BROKEN;
+ });
+ f->dlink = **dtail;
+ **dtail = f;
+ close(ofd);
+ break;
+
+ /* --- Nothing to be done here --- *
+ *
+ * @ST_DONE@ nodes have already been completely processed; @ST_BROKEN@
+ * nodes will be fixed up after the main traversal.
+ */
+
+ case ST_DONE:
+ case ST_BROKEN:
+ return (0);
+
+ }
+ return (0);
+}
+
+/* --- @mdup@ --- *
+ *
+ * Arguments: @mdup_fd *v@ = pointer to @mdup_fd@ vector
+ * @size_t n@ = size of vector
+ *
+ * Returns: Zero if successful, @-1@ on failure.
+ *
+ * Use: Rearranges file descriptors.
+ *
+ * The vector @v@ consists of a number of @mdup_fd@ structures.
+ * Each `slot' in the table represents a file. The slot's @cur@
+ * member names the current file descriptor for this file; the
+ * @want@ member is the file descriptor we want to use for it.
+ * if you want to keep a file alive but don't care which
+ * descriptor it ends up with, set @want = -1@. Several slots
+ * may specify the same @cur@ descriptor; but they all have to
+ * declare different @want@s (except that several slots may have
+ * @want = -1@.
+ *
+ * On successful exit, the function will have rearranged the
+ * file descriptors as requested. To reflect this, the @cur@
+ * members will all be set to match the (non-@-1@) @want@
+ * members.
+ *
+ * If there is a failure, then some rearrangement may have been
+ * performed and some not; the @cur@ members are set to reflect
+ * which file descriptors are to be used. The old file
+ * descriptors are closed. (This is different from usual @dup@
+ * behaviour, of course, but essential for reliable error
+ * handling.) If you want to keep a particular source file
+ * descriptor open as well as make a new copy then specify two
+ * slots with the same @cur@, one with @want = cur@ and one with
+ * the desired output descriptor.
+ *
+ * This function works correctly even if the desired remappings
+ * contain cycles.
+ */
+
+int mdup(mdup_fd *v, size_t n)
+{
+ size_t i, j;
+ mdup_fdinfo *vv;
+ mdup_fdinfo *f, *g, *dhead, **dtail;
+ mdup_fd *ff;
+ int rc = -1;
+ int can_close_p;
+ int ofd, fd;
+
+ /* --- Allocate and initialize the table of info nodes --- *
+ *
+ * Each entry @ff@ in the caller's @v@ array will have a corresponding node
+ * @f@ in @vv@ with @f->f = ff@. Initially each node's links are null, and
+ * the node is in the @ST_READY@ state.
+ *
+ * We also initialize a list given by @dhead@ and @dtail@ containing the
+ * entries with `dynamically-assigned' descriptors -- i.e., those whose
+ * values we made up using @dup@. The list lets us detect collisions with
+ * explicitly requested descriptors and move the dynamic ones out of the
+ * way.
+ */
+
+ if ((vv = malloc(sizeof(*vv) * n)) == 0)
+ return (-1);
+
+ dhead = 0;
+ dtail = &dhead;
+ for (i = 0; i < n; i++) {
+ f = &vv[i];
+ f->f = &v[i];
+ f->up = f->down = 0;
+ f->eqnext = f->eqprev = 0;
+ f->state = ST_READY;
+ }
+
+ /* --- Pass one: link the graph together --- *
+ *
+ * Once this pass is complete, the following properties will hold.
+ *
+ * * The nodes which have the same @cur@ are linked together by their
+ * @eqnext@ and @eqprev@ fields into a doubly-linked circular list
+ * representing this equivalence class.
+ *
+ * * @f->up == g@ if and only if @f->f->cur == g->f->want@. (Note that
+ * @want@ fields are unique according to our interface. We detect
+ * violations and exit with @errno == EINVAL@.)
+ *
+ * * If @f->up == g@ then there exists a @ff@ in the same equivalence
+ * class (and therefore on @f@'s @eqnext@ list) as @f@ with
+ * @g->down == ff@.
+ */
+
+ for (i = 0; i < n; i++) {
+ f = &vv[i];
+ if (!f->eqnext)
+ f->eqnext = f->eqprev = f;
+ for (j = 0; j < n; j++) {
+ if (i == j)
+ continue;
+ g = &vv[j];
+ if (f->f->cur == g->f->cur) {
+ if (!g->eqnext) {
+ g->eqnext = f->eqnext;
+ g->eqprev = f;
+ f->eqnext->eqprev = g;
+ f->eqnext = g;
+ }
+ }
+ if (g->f->want == -1)
+ /* fine */;
+ else if (f->f->want == g->f->want) {
+ errno = EINVAL;
+ goto fail;
+ } else if (f->f->cur == g->f->want) {
+ f->up = g;
+ if (!g->down)
+ g->down = f;
+ }
+ }
+ }
+
+ /* --- Pass two: handle don't-care requests --- *
+ *
+ * By the end of this pass, we have the following properties.
+ *
+ * * Every node will be marked @ST_DONE@. This is a temporary abuse of
+ * the @ST_DONE@ state which will be rectified during the next pass.
+ *
+ * * Every node with @want == -1@ will have @cur@ set to a freshly
+ * allocated file descriptor distinct from every previously open file.
+ */
+
+ for (i = 0; i < n; i++) {
+ f = &vv[i];
+ switch (f->state) {
+ case ST_DONE:
+ break;
+ case ST_READY:
+ can_close_p = 1;
+ DO_EQUIVS(g, f, {
+ ff = g->f;
+ ofd = ff->cur;
+ if (ff->want != -1)
+ can_close_p = 0;
+ else {
+ if ((fd = dup(ofd)) < 0)
+ goto fail;
+ ff->cur = fd;
+ }
+ g->state = ST_DONE;
+ });
+ if (can_close_p)
+ close(ofd);
+ break;
+ }
+ }
+
+ /* --- Pass three: restore equivalence classes and @down@ links --- *
+ *
+ * This pass re-establishes the properties from pass one. Because we've
+ * changed some @cur@ members, the equivalence classes will have changed,
+ * so we must fix up the @eqnext@ lists and @down@ links.
+ *
+ * Nodes with @want == -1@ are now finished with (modulo tweaking
+ * dynamically allocated descriptors as we process the others), so we leave
+ * them in @ST_DONE@; other nodes are restored to @ST_READY@.
+ */
+
+ for (i = 0; i < n; i++) {
+ f = &vv[i];
+ ff = f->f;
+ if (ff->want == -1) {
+ f->eqnext->eqprev = f->eqprev;
+ f->eqprev->eqnext = f->eqnext;
+ f->eqnext = f->eqprev = f;
+ f->dlink = *dtail;
+ *dtail = f;
+ } else
+ f->state = ST_READY;
+ }
+
+ /* --- Pass four: main depth-first traversal --- *
+ *
+ * See the description of the function @dfs@ above. After this pass, every
+ * node is in state @ST_DONE@ or @ST_BROKEN@.
+ */
+
+ for (i = 0; i < n; i++) {
+ if (dfs(&vv[i], &dhead, &dtail))
+ goto fail;
+ }
+
+ /* --- Finished --- */
+
+ rc = 0;
+fail:
+ free(vv);
+ return (rc);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+/* -*-c-*-
+ *
+ * Duplicate multiple files
+ *
+ * (c) 2008 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef MDUP_H
+#define MDUP_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct mdup_fd {
+ int cur; /* Current file descriptor */
+ int want; /* File descriptor wanted */
+} mdup_fd;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @mdup@ --- *
+ *
+ * Arguments: @mdup_fd *v@ = pointer to @mdup_fd@ vector
+ * @size_t n@ = size of vector
+ *
+ * Returns: Zero if successful, @-1@ on failure.
+ *
+ * Use: Rearranges file descriptors.
+ *
+ * Use: Rearranges file descriptors.
+ *
+ * The vector @v@ consists of a number of @mdup_fd@ structures.
+ * Each `slot' in the table represents a file. The slot's @cur@
+ * member names the current file descriptor for this file; the
+ * @want@ member is the file descriptor we want to use for it.
+ * if you want to keep a file alive but don't care which
+ * descriptor it ends up with, set @want = -1@. Several slots
+ * may specify the same @cur@ descriptor; but they all have to
+ * declare different @want@s (except that several slots may have
+ * @want = -1@.
+ *
+ * On successful exit, the function will have rearranged the
+ * file descriptors as requested. To reflect this, the @cur@
+ * members will all be set to match the (non-@-1@) @want@
+ * members.
+ *
+ * If there is a failure, then some rearrangement may have been
+ * performed and some not; the @cur@ members are set to reflect
+ * which file descriptors are to be used. The old file
+ * descriptors are closed. (This is different from usual @dup@
+ * behaviour, of course, but essential for reliable error
+ * handling.) If you want to keep a particular source file
+ * descriptor open as well as make a new copy then specify two
+ * slots with the same @cur@, one with @want = cur@ and one with
+ * the desired output descriptor.
+ *
+ * This function works correctly even if the desired remappings
+ * contain cycles.
+ */
+
+extern int mdup(mdup_fd */*v*/, size_t /*n*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif