From 12f28d0689b05dd21f212d815c4187ed3c17d78f Mon Sep 17 00:00:00 2001
From: Andrew Wood
Date: Sat, 4 Sep 2021 20:51:25 +0100
Subject: [PATCH] Initial commit based on v1.6.6
---
README | 230 ++++++++
autoconf/Makefile.in | 14 +
autoconf/configure.in | 199 +++++++
autoconf/header.in | 96 ++++
autoconf/make/depend.mk~ | 21 +
autoconf/make/filelist.mk~ | 62 +++
autoconf/make/link.mk | 11 +
autoconf/make/modules.mk~ | 14 +
autoconf/make/package.mk | 17 +
autoconf/make/rules.mk | 43 ++
autoconf/make/unreal.mk | 201 +++++++
autoconf/make/vars.mk | 46 ++
autoconf/scripts/benchmark.sh | 39 ++
autoconf/scripts/depend.sh | 30 +
autoconf/scripts/index.sh | 171 ++++++
autoconf/scripts/install.sh | 250 +++++++++
autoconf/scripts/makemake.sh | 113 ++++
autoconf/scripts/mkinstalldirs | 33 ++
autoconf/scripts/po2table.sh | 65 +++
autoconf/scripts/run-test.sh | 45 ++
doc/COPYING | 171 ++++++
doc/INSTALL | 183 +++++++
doc/NEWS | 304 +++++++++++
doc/PACKAGE | 1 +
doc/TODO | 35 ++
doc/VERSION | 1 +
doc/lsm.in | 19 +
doc/quickref.1.in | 600 ++++++++++++++++++++
doc/release-checklist | 20 +
doc/spec.in | 249 +++++++++
generate.sh | 32 ++
src/include/library/getopt.h | 36 ++
src/include/library/gettext.h | 52 ++
src/include/options.h | 64 +++
src/include/pv-internal.h | 263 +++++++++
src/include/pv.h | 165 ++++++
src/library/getopt.c | 113 ++++
src/library/gettext.c | 109 ++++
src/main/debug.c | 72 +++
src/main/help.c | 209 +++++++
src/main/main.c | 270 +++++++++
src/main/options.c | 407 ++++++++++++++
src/main/remote.c | 330 +++++++++++
src/main/version.c | 34 ++
src/nls/de.po | 452 +++++++++++++++
src/nls/fr.po | 454 ++++++++++++++++
src/nls/pl.po | 491 +++++++++++++++++
src/nls/pt.po | 446 +++++++++++++++
src/nls/pv.pot | 430 +++++++++++++++
src/pv/cursor.c | 602 ++++++++++++++++++++
src/pv/display.c | 968 +++++++++++++++++++++++++++++++++
src/pv/file.c | 293 ++++++++++
src/pv/loop.c | 780 ++++++++++++++++++++++++++
src/pv/number.c | 211 +++++++
src/pv/signal.c | 297 ++++++++++
src/pv/state.c | 219 ++++++++
src/pv/transfer.c | 861 +++++++++++++++++++++++++++++
src/pv/watchpid.c | 385 +++++++++++++
tests/000-cat | 8 +
tests/001-interval | 12 +
tests/002-rate | 13 +
tests/003-progress | 13 +
tests/004-timer | 14 +
tests/005a-eta | 20 +
tests/005b-fineta | 20 +
tests/006-ratecount | 22 +
tests/007-bytes | 10 +
tests/008-numeric | 18 +
tests/009-quiet | 9 +
tests/010-pipe | 26 +
tests/011-cksum | 26 +
tests/012-averagerate | 24 +
tests/013-1mboundary | 25 +
tests/014-1mboundary2 | 25 +
tests/015-cksumpipe | 53 ++
tests/016-numeric-timer | 20 +
tests/017-numeric-bytes | 19 +
tests/018-remote-format | 32 ++
tests/019-remote-cksum | 54 ++
tests/020-stop-at-size | 28 +
80 files changed, 12819 insertions(+)
create mode 100644 README
create mode 100644 autoconf/Makefile.in
create mode 100644 autoconf/configure.in
create mode 100644 autoconf/header.in
create mode 100644 autoconf/make/depend.mk~
create mode 100644 autoconf/make/filelist.mk~
create mode 100644 autoconf/make/link.mk
create mode 100644 autoconf/make/modules.mk~
create mode 100644 autoconf/make/package.mk
create mode 100644 autoconf/make/rules.mk
create mode 100644 autoconf/make/unreal.mk
create mode 100644 autoconf/make/vars.mk
create mode 100755 autoconf/scripts/benchmark.sh
create mode 100755 autoconf/scripts/depend.sh
create mode 100755 autoconf/scripts/index.sh
create mode 100755 autoconf/scripts/install.sh
create mode 100755 autoconf/scripts/makemake.sh
create mode 100755 autoconf/scripts/mkinstalldirs
create mode 100755 autoconf/scripts/po2table.sh
create mode 100755 autoconf/scripts/run-test.sh
create mode 100644 doc/COPYING
create mode 100644 doc/INSTALL
create mode 100644 doc/NEWS
create mode 100644 doc/PACKAGE
create mode 100644 doc/TODO
create mode 100644 doc/VERSION
create mode 100644 doc/lsm.in
create mode 100644 doc/quickref.1.in
create mode 100644 doc/release-checklist
create mode 100644 doc/spec.in
create mode 100755 generate.sh
create mode 100644 src/include/library/getopt.h
create mode 100644 src/include/library/gettext.h
create mode 100644 src/include/options.h
create mode 100644 src/include/pv-internal.h
create mode 100644 src/include/pv.h
create mode 100644 src/library/getopt.c
create mode 100644 src/library/gettext.c
create mode 100644 src/main/debug.c
create mode 100644 src/main/help.c
create mode 100644 src/main/main.c
create mode 100644 src/main/options.c
create mode 100644 src/main/remote.c
create mode 100644 src/main/version.c
create mode 100644 src/nls/de.po
create mode 100644 src/nls/fr.po
create mode 100644 src/nls/pl.po
create mode 100644 src/nls/pt.po
create mode 100644 src/nls/pv.pot
create mode 100644 src/pv/cursor.c
create mode 100644 src/pv/display.c
create mode 100644 src/pv/file.c
create mode 100644 src/pv/loop.c
create mode 100644 src/pv/number.c
create mode 100644 src/pv/signal.c
create mode 100644 src/pv/state.c
create mode 100644 src/pv/transfer.c
create mode 100644 src/pv/watchpid.c
create mode 100644 tests/000-cat
create mode 100644 tests/001-interval
create mode 100644 tests/002-rate
create mode 100644 tests/003-progress
create mode 100644 tests/004-timer
create mode 100644 tests/005a-eta
create mode 100644 tests/005b-fineta
create mode 100644 tests/006-ratecount
create mode 100644 tests/007-bytes
create mode 100644 tests/008-numeric
create mode 100644 tests/009-quiet
create mode 100644 tests/010-pipe
create mode 100644 tests/011-cksum
create mode 100644 tests/012-averagerate
create mode 100644 tests/013-1mboundary
create mode 100644 tests/014-1mboundary2
create mode 100644 tests/015-cksumpipe
create mode 100644 tests/016-numeric-timer
create mode 100644 tests/017-numeric-bytes
create mode 100644 tests/018-remote-format
create mode 100644 tests/019-remote-cksum
create mode 100644 tests/020-stop-at-size
diff --git a/README b/README
new file mode 100644
index 0000000..efcef69
--- /dev/null
+++ b/README
@@ -0,0 +1,230 @@
+Introduction
+************
+
+This is the README for `pv' ("Pipe Viewer"), a terminal-based tool for
+monitoring the progress of data through a pipeline. It can be inserted into
+any normal pipeline between two processes to give a visual indication of how
+quickly data is passing through, how long it has taken, how near to
+completion it is, and an estimate of how long it will be until completion.
+
+
+Documentation
+*************
+
+A manual page is included in this distribution. See `man ./doc/quickref.1',
+or `man pv' after installation.
+
+
+Compilation
+***********
+
+If you have downloaded `pv' from Subversion, first run "./generate.sh".
+
+To compile the package, type "sh ./configure", which should generate a
+Makefile for your system. You may then type "make" to build everything.
+Note that GNU `make' is required; this may be installed as `gmake' on some
+systems, so if typing "make" gives an error, try "gmake" instead.
+
+See the file `doc/INSTALL' for more about the `configure' script.
+
+Developers note that you can do "./configure --enable-debugging" to cause
+debugging support to be built in, and "--enable-profiling" builds in
+profiling support (see "man gprof"). Also note that doing "make index" will
+generate an HTML code index (using "ctags" and "cproto"); this index lists
+all files used, all functions defined, and all TODOs marked in the code.
+
+
+Author and acknowledgements
+***************************
+
+This package is copyright 2015 Andrew Wood, and is being distributed under
+the terms of the Artistic License 2.0. For more details of this license,
+see the file `doc/COPYING'.
+
+Report bugs in `pv' using the contact form linked from the home page.
+
+The `pv' home page is at:
+
+ http://www.ivarch.com/programs/pv.shtml
+
+The latest version can always be found here.
+
+Credit is also due to:
+
+ Jakub Hrozek
+ - Fedora package maintainer
+
+ Antoine Beaupré
+ - Debian package maintainer
+
+ Kevin Coyner
+ Cédric Delfosse
+ - previous Debian package maintainers
+
+ Eduardo Aguiar
+ - provided Portuguese (Brazilian) translation
+
+ Stéphane Lacasse
+ - provided French translation
+
+ Marcos Kreinacke
+ - provided German translation
+
+ Bartosz Feñski
+ - provided Polish translation along with Krystian Zubel
+
+ Joshua Jensen
+ - reported RPM installation bug
+
+ Boris Folgmann
+ - reported cursor handling bug
+
+ Mathias Gumz
+ - reported NLS bug
+
+ Daniel Roethlisberger
+ - submitted patch to use lockfiles for -c if terminal locking fails
+
+ Adam Buchbinder
+ - lots of help with a Cygwin port of -c
+
+ Mark Tomich
+ - suggested -B option
+
+ Gert Menke
+ - reported bug when piping to dd with a large input buffer size
+
+ Ville Herva
+ - informative bug report about rate limiting performance
+
+ Elias Pipping
+ - patch to compile properly on Darwin 9; potential NULL deref report
+
+ Patrick Collison
+ - similar patch for OS X
+
+ Boris Lohner
+ - reported problem that "-L" does not complain if given non-numeric value
+
+ Sebastian Kayser
+ - supplied testing for SIGPIPE, demonstrated internationalisation problem
+
+ Laszlo Ersek
+ - reported shared memory leak on SIGINT with -c
+
+ Phil Rutschman
+ - provided a patch for fully restoring terminal state on exit
+
+ Henry Precheur
+ - reporting and suggestions for --rate-limit bug when rate is under 10
+
+ E. Rosten
+ - supplied patch for block buffering in line mode
+
+ Kjetil Torgrim Homme
+ - reported compilation error with default CFLAGS on non-GCC compilers
+
+ Alexandre de Verteuil
+ - reported bug in OS X build and supplied test environment to fix in
+
+ Martin Baum
+ - supplied patch to return nonzero exit status if terminated by signal
+
+ Sam Nelson
+ - supplied patch to fix trailing slash on DESTDIR
+
+ Daniel Pape
+ - reported Cygwin installation problem due to DESTDIR
+
+ Philipp Beckers
+ - ported to the Syabas PopcornHour A-100 series
+
+ Henry Gebhard
+ - supplied patches to improve SI prefixes and add --average-rate
+
+ Vladimir Kokarev, Alexander Leo
+ - reported that exit status did not reflect file errors
+
+ Thomas Rachel
+ - submitted patches for IEEE1541 (MiB suffixes), 1+e03 bug
+
+ Guillaume Marcais
+ - submitted speedup patch for line mode
+
+ Moritz Barsnick
+ - submitted patch for compile warning in size calculation
+
+ Pawel Piatek
+ - submitted RPM and patches for AIX
+
+ Sami Liedes
+ - submitted patch for --timer and --bytes with --numeric
+
+ Steven Willis
+ - reported problem with "-R" killing non-PV remote processes
+
+ Vladimir Pal, Vladimir Ermakov
+ - submitted patch which led to development of --format option
+
+ Peter Samuelson
+ - submitted patch to calculate size if stdout is a block device
+
+ Miguel Diaz
+ - much Cygwin help (and packaging), found narrow-terminal bug
+
+ Jim Salter
+ - commissioned work on the --skip-errors option
+
+ Wouter Pronk
+ - reported build problem on SCO
+
+ Bryan Dongray
+ - provided patches for test scripts failing on older Red Hats
+
+ Zev Weiss
+ - provided patch to fix splice() not using stdin
+
+ Zing Shishak
+ - provided patch for --null / -0 (count null terminated lines)
+
+ Jacek Wielemborek
+ - implemented fdwatch in Python, suggested PV port
+ - reported bug with "-l" and ETA / size
+ - suggested common switches
+
+ Kim Krecht
+ - suggested buffer fill status and last bytes output display options
+
+ Cristian Ciupitu , Josh Stone
+ - pointed out file descriptor leak with helpful suggestions
+ (Josh Stone initially noticed the missing close)
+
+ Jan Seda
+ - found issue with splice() and SPLICE_F_NONBLOCK causing slowdown
+
+ André Stapf
+ - pointed out formatting problem e.g. 13GB -> 13.1GB which should be
+ shown 13.0GB -> 13.1GB; highlighted on-startup row swapping in -c
+
+ Damon Harper
+ - suggested "-D" / "--delay-start" option
+
+ Ganaël Laplanche
+ - provided patch for lstat64 on systems that do not support it
+
+ Peter Korsgaard
+ - provided similar patch for lstat64, specifically for uClibc support
+
+ Ralf Ramsauer
+ - reported bug which dropped transfer rate on terminal resize
+
+ Michiel Van Herwegen
+ - reported and discussed bug with "-l" and ETA / size
+
+ Erkki Seppälä
+ - provided patch implementing "-I"
+
+ Eric A. Borisch
+ - provided details of compatibility fix for "%Lu" in watchpid code
+
+-----------------------------------------------------------------------------
diff --git a/autoconf/Makefile.in b/autoconf/Makefile.in
new file mode 100644
index 0000000..aee4384
--- /dev/null
+++ b/autoconf/Makefile.in
@@ -0,0 +1,14 @@
+#
+# Files from which this is generated (inside directory `autoconf/make'):
+#
+# package.mk # package name and distribution details
+# vars.mk # compilation, shell and linking variables
+# filelist.mk~ # lists of files
+# unreal.mk # phony targets
+# modules.mk~ # module linking rules
+# rules.mk # compilation rules
+# link.mk # real top-level targets
+# depend.mk~ # dependencies
+#
+#
+
diff --git a/autoconf/configure.in b/autoconf/configure.in
new file mode 100644
index 0000000..bd45004
--- /dev/null
+++ b/autoconf/configure.in
@@ -0,0 +1,199 @@
+dnl Process this file with autoconf to produce a configure script.
+
+AC_INIT(src/main/version.c)
+
+dnl We're using C.
+dnl
+AC_LANG_C
+
+dnl Output a header file.
+dnl
+AC_CONFIG_HEADER(src/include/config.h:autoconf/header.in)
+
+dnl Set directory to check for Configure scripts in.
+dnl
+AC_CONFIG_AUX_DIR(autoconf/scripts)
+
+dnl Read in package details.
+dnl
+PACKAGE=`cat $srcdir/doc/PACKAGE`
+VERSION=`cat $srcdir/doc/VERSION`
+UCPACKAGE=`tr a-z A-Z < $srcdir/doc/PACKAGE`
+AC_SUBST(PACKAGE)
+AC_SUBST(VERSION)
+AC_SUBST(UCPACKAGE)
+AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE")
+AC_DEFINE_UNQUOTED(PROGRAM_NAME, "$PACKAGE")
+AC_DEFINE_UNQUOTED(VERSION, "$VERSION")
+
+dnl Check for compile-time options.
+dnl
+AC_ARG_ENABLE(debugging,
+ [ --enable-debugging compile with debugging support],
+ if test "$enable_debugging" = "yes"; then
+ CFLAGS="-g -Wall"
+ AC_DEFINE(ENABLE_DEBUGGING)
+ fi
+)
+AC_ARG_ENABLE(profiling,
+ [ --enable-profiling compile with profiling support],
+ if test "$enable_profiling" = "yes"; then
+ CFLAGS="-pg $CFLAGS"
+ fi
+)
+LFS_SUPPORT="no"
+AC_ARG_ENABLE(lfs, [ --disable-lfs disable LFS support],
+ if test "$enable_lfs" = "yes"; then
+ LFS_SUPPORT="yes"
+ fi,
+ LFS_SUPPORT="yes"
+)
+STATIC_NLS="no"
+AC_ARG_ENABLE(static-nls, [ --enable-static-nls hardcode NLS with no support files],
+ if test "$enable_static_nls" = "yes"; then
+ STATIC_NLS="yes"
+ fi,
+ STATIC_NLS="no"
+)
+NLS_SUPPORT="no"
+AC_ARG_ENABLE(nls, [ --disable-nls do not use Native Language Support],
+ if test "$enable_nls" = "yes"; then
+ NLS_SUPPORT="yes"
+ fi,
+ NLS_SUPPORT="yes"
+)
+SPLICE_SUPPORT="no"
+AC_ARG_ENABLE(splice, [ --disable-splice do not use splice system call],
+ if test "$enable_splice" = "yes"; then
+ SPLICE_SUPPORT="yes"
+ fi,
+ SPLICE_SUPPORT="yes"
+)
+IPC_SUPPORT="no"
+AC_ARG_ENABLE(ipc, [ --disable-ipc turn off IPC messaging],
+ if test "$enable_ipc" = "yes"; then
+ IPC_SUPPORT="yes"
+ fi,
+ IPC_SUPPORT="yes"
+)
+
+dnl Check for various programs.
+dnl
+CFLAGS=${CFLAGS:-"-O"}
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+
+dnl AIX needs -lc128
+dnl
+AC_EGREP_CPP([yes], [#ifdef _AIX
+yes
+#endif
+], [LIBS="$LIBS -lc128"])
+
+dnl NLS stuff.
+dnl
+ALL_LINGUAS="de fr pl pt"
+if test "$NLS_SUPPORT" = "yes"; then
+ AC_DEFINE(ENABLE_NLS)
+ AC_PATH_PROG(MSGFMT, msgfmt, NOMSGFMT)
+ AC_PATH_PROG(GMSGFMT, gmsgfmt, $MSGFMT)
+ AC_PATH_PROG(XGETTEXT, xgettext, xgettext)
+ if test "x$MSGFMT" = "xNOMSGFMT"; then
+ MSGFMT=""
+ STATIC_NLS="yes"
+ fi
+ if test "$STATIC_NLS" = "yes"; then
+ CATALOGS=""
+ NLSOBJ="src/nls/table.o"
+ else
+ AC_CHECK_LIB(intl, main)
+ AC_CHECK_LIB(i, main)
+ fi
+ CATOBJEXT=.mo
+ INSTOBJEXT=.mo
+ for lang in $ALL_LINGUAS; do
+ GMOFILES="$GMOFILES $lang.gmo"
+ POFILES="$POFILES \$(srcdir)/src/nls/$lang.po"
+ CATALOGS="$CATALOGS src/nls/$lang$CATOBJEXT";
+ done
+ if test "$STATIC_NLS" = "yes"; then
+ CATALOGS=""
+ else
+ AC_CHECK_FUNC(gettext,
+ [AC_DEFINE(HAVE_GETTEXT)
+ NLSOBJ=""
+ ], [CATALOGS=""; NLSOBJ="src/nls/table.o"
+ ]
+ )
+ fi
+fi
+AC_CHECK_HEADERS(libintl.h)
+AC_CHECK_HEADERS(locale.h)
+AC_SUBST(MSGFMT)
+AC_SUBST(GMSGFMT)
+AC_SUBST(XGETTEXT)
+AC_SUBST(CATOBJEXT)
+AC_SUBST(INSTOBJEXT)
+AC_SUBST(GMOFILES)
+AC_SUBST(POFILES)
+AC_SUBST(CATALOGS)
+AC_SUBST(NLSOBJ)
+
+dnl Getopt checks.
+dnl
+AC_CHECK_FUNCS(getopt_long getopt)
+AC_CHECK_HEADERS(getopt.h)
+
+dnl LFS checks.
+dnl
+if test "$LFS_SUPPORT" = "yes"; then
+ AC_CHECK_FUNCS(open64, AC_DEFINE(ENABLE_LARGEFILE))
+fi
+
+dnl Check for various header files and set various other macros.
+dnl
+AC_DEFINE(HAVE_CONFIG_H)
+AC_HEADER_STDC
+AC_CHECK_FUNCS(memcpy basename snprintf stat64)
+AC_CHECK_HEADERS(limits.h)
+
+if test "$IPC_SUPPORT" = "yes"; then
+ AC_CHECK_HEADERS(sys/ipc.h sys/param.h libgen.h)
+fi
+
+if test "$SPLICE_SUPPORT" = "yes"; then
+ AC_CHECK_FUNCS(splice)
+fi
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+AC_SUBST(INSTALL_DATA)
+
+dnl Fudging for separate build directories.
+dnl
+subdirs=""
+for i in `find $srcdir/src -type d -print | sed s,$srcdir/,,`; do
+ subdirs="$subdirs $i"
+done
+
+dnl Stitch together the Makefile fragments.
+dnl
+mk_segments="autoconf/Makefile.in"
+for i in vars.mk package.mk filelist.mk~ unreal.mk modules.mk~ \
+ rules.mk link.mk depend.mk~; do
+ mk_segments="$mk_segments:autoconf/make/$i"
+done
+
+dnl Output files (and create build directory structure too).
+dnl
+AC_OUTPUT(Makefile:$mk_segments doc/lsm:doc/lsm.in
+ doc/quickref.1:doc/quickref.1.in
+ doc/$PACKAGE.spec:doc/spec.in
+ src/.dummy:doc/NEWS,
+ rm -f src/.dummy
+ for i in $subdirs; do
+ test -d $i || mkdir $i
+ done
+, subdirs="$subdirs")
+
+dnl EOF
diff --git a/autoconf/header.in b/autoconf/header.in
new file mode 100644
index 0000000..70ddedb
--- /dev/null
+++ b/autoconf/header.in
@@ -0,0 +1,96 @@
+/*!NOINDEX*/
+/* Define if you have standard C headers. */
+#undef STDC_HEADERS
+
+/* Define if you have "config.h" (yes, you have). */
+#undef HAVE_CONFIG_H
+
+/* Various other header files. */
+#undef HAVE_GETOPT_H
+#undef HAVE_LIMITS_H
+#undef HAVE_SYS_IPC_H
+#undef HAVE_SYS_PARAM_H
+#undef HAVE_LIBGEN_H
+
+/* Functions. */
+#undef HAVE_GETOPT
+#undef HAVE_GETOPT_LONG
+#undef HAVE_MEMCPY
+#undef HAVE_BASENAME
+#undef HAVE_SNPRINTF
+#undef HAVE_STAT64
+
+#undef HAVE_SPLICE
+#ifdef HAVE_SPLICE
+# define _GNU_SOURCE 1
+#endif
+/* NB the above must come before NLS, as NLS includes other system headers. */
+
+/* NLS stuff. */
+#undef ENABLE_NLS
+#undef HAVE_LIBINTL_H
+#undef HAVE_LOCALE_H
+#undef HAVE_GETTEXT
+#ifdef ENABLE_NLS
+# include "library/gettext.h"
+#else
+# define _(String) (String)
+# define N_(String) (String)
+#endif
+
+/* The name of the program. */
+#define PROGRAM_NAME "progname"
+
+/* The name of the package. */
+#define PACKAGE ""
+
+/* The current package version. */
+#define VERSION "0.0.0"
+
+/* Various identification and legal stuff. */
+#define COPYRIGHT_YEAR _("2015")
+#define COPYRIGHT_HOLDER _("Andrew Wood ")
+#define PROJECT_HOMEPAGE "http://www.ivarch.com/programs/" PROGRAM_NAME ".shtml"
+#define BUG_REPORTS_TO _("")
+
+/* LFS support. */
+#undef ENABLE_LARGEFILE
+#ifdef ENABLE_LARGEFILE
+# define __USE_LARGEFILE64 1
+# define _LARGEFILE64_SOURCE 1
+#else
+/*
+ * Some Macs have stat64 despite not having open64 while others don't have
+ * either, so here even if we don't have open64 or LFS is disabled, we have
+ * to check whether stat64 exists and only redefine it if it doesn't
+ * otherwise some Macs fail to compile.
+ */
+# ifdef __APPLE__
+# ifndef HAVE_STAT64
+# define stat64 stat
+# define fstat64 fstat
+# define lstat64 lstat
+# endif
+# else
+# define stat64 stat
+# define fstat64 fstat
+# define lstat64 lstat
+# endif
+# define open64 open
+# define lseek64 lseek
+#endif
+
+#undef HAVE_IPC
+#ifdef HAVE_SYS_IPC_H
+#define HAVE_IPC 1
+#endif
+
+#undef CURSOR_ANSWERBACK_BYTE_BY_BYTE
+#ifndef _AIX
+#define CURSOR_ANSWERBACK_BYTE_BY_BYTE 1
+#endif
+
+/* Support for debugging output. */
+#undef ENABLE_DEBUGGING
+
+/* EOF */
diff --git a/autoconf/make/depend.mk~ b/autoconf/make/depend.mk~
new file mode 100644
index 0000000..6093936
--- /dev/null
+++ b/autoconf/make/depend.mk~
@@ -0,0 +1,21 @@
+#
+# Dependencies.
+#
+
+src/library/getopt.d src/library/getopt.o: src/library/getopt.c src/include/config.h src/include/library/gettext.h
+src/library/gettext.d src/library/gettext.o: src/library/gettext.c src/include/config.h src/include/library/gettext.h
+src/pv/signal.d src/pv/signal.o: src/pv/signal.c src/include/pv-internal.h src/include/config.h src/include/library/gettext.h src/include/pv.h
+src/pv/watchpid.d src/pv/watchpid.o: src/pv/watchpid.c src/include/pv-internal.h src/include/config.h src/include/library/gettext.h src/include/pv.h
+src/pv/cursor.d src/pv/cursor.o: src/pv/cursor.c src/include/pv-internal.h src/include/config.h src/include/library/gettext.h src/include/pv.h
+src/pv/file.d src/pv/file.o: src/pv/file.c src/include/pv-internal.h src/include/config.h src/include/library/gettext.h src/include/pv.h
+src/pv/display.d src/pv/display.o: src/pv/display.c src/include/pv-internal.h src/include/config.h src/include/library/gettext.h src/include/pv.h
+src/pv/loop.d src/pv/loop.o: src/pv/loop.c src/include/pv-internal.h src/include/config.h src/include/library/gettext.h src/include/pv.h
+src/pv/number.d src/pv/number.o: src/pv/number.c src/include/config.h src/include/library/gettext.h src/include/pv.h
+src/pv/transfer.d src/pv/transfer.o: src/pv/transfer.c src/include/pv-internal.h src/include/config.h src/include/library/gettext.h src/include/pv.h
+src/pv/state.d src/pv/state.o: src/pv/state.c src/include/pv-internal.h src/include/config.h src/include/library/gettext.h src/include/pv.h
+src/main/version.d src/main/version.o: src/main/version.c src/include/config.h src/include/library/gettext.h
+src/main/debug.d src/main/debug.o: src/main/debug.c src/include/config.h src/include/library/gettext.h src/include/pv.h
+src/main/main.d src/main/main.o: src/main/main.c src/include/config.h src/include/library/gettext.h src/include/options.h src/include/pv.h
+src/main/options.d src/main/options.o: src/main/options.c src/include/config.h src/include/library/gettext.h src/include/options.h src/include/library/getopt.h src/include/pv.h
+src/main/remote.d src/main/remote.o: src/main/remote.c src/include/config.h src/include/library/gettext.h src/include/options.h src/include/pv.h
+src/main/help.d src/main/help.o: src/main/help.c src/include/config.h src/include/library/gettext.h
diff --git a/autoconf/make/filelist.mk~ b/autoconf/make/filelist.mk~
new file mode 100644
index 0000000..bb44965
--- /dev/null
+++ b/autoconf/make/filelist.mk~
@@ -0,0 +1,62 @@
+# Automatically generated file listings
+#
+# Creation time: Fri Jun 30 22:23:37 BST 2017
+
+allsrc = src/library/getopt.c \
+src/library/gettext.c \
+src/pv/signal.c \
+src/pv/watchpid.c \
+src/pv/cursor.c \
+src/pv/file.c \
+src/pv/display.c \
+src/pv/loop.c \
+src/pv/number.c \
+src/pv/transfer.c \
+src/pv/state.c \
+src/main/version.c \
+src/main/debug.c \
+src/main/main.c \
+src/main/options.c \
+src/main/remote.c \
+src/main/help.c
+
+allobj = src/library/getopt.o \
+src/library/gettext.o \
+src/pv/signal.o \
+src/pv/watchpid.o \
+src/pv/cursor.o \
+src/pv/file.o \
+src/pv/display.o \
+src/pv/loop.o \
+src/pv/number.o \
+src/pv/transfer.o \
+src/pv/state.o \
+src/main/version.o \
+src/main/debug.o \
+src/main/main.o \
+src/main/options.o \
+src/main/remote.o \
+src/main/help.o \
+src/library.o \
+src/pv.o \
+src/nls.o \
+src/main.o
+
+alldep = src/library/getopt.d \
+src/library/gettext.d \
+src/pv/signal.d \
+src/pv/watchpid.d \
+src/pv/cursor.d \
+src/pv/file.d \
+src/pv/display.d \
+src/pv/loop.d \
+src/pv/number.d \
+src/pv/transfer.d \
+src/pv/state.d \
+src/main/version.d \
+src/main/debug.d \
+src/main/main.d \
+src/main/options.d \
+src/main/remote.d \
+src/main/help.d
+
diff --git a/autoconf/make/link.mk b/autoconf/make/link.mk
new file mode 100644
index 0000000..9f861cd
--- /dev/null
+++ b/autoconf/make/link.mk
@@ -0,0 +1,11 @@
+#
+# Targets.
+#
+
+$(package): src/main.o src/library.o src/pv.o @NLSOBJ@
+ $(CC) $(LINKFLAGS) $(CFLAGS) -o $@ src/main.o src/library.o src/pv.o @NLSOBJ@ $(LIBS)
+
+$(package)-static: src/main.o src/library.o src/pv.o @NLSOBJ@
+ $(CC) $(LINKFLAGS) $(CFLAGS) -static -o $@ src/main.o src/library.o src/pv.o @NLSOBJ@ $(LIBS)
+
+# EOF
diff --git a/autoconf/make/modules.mk~ b/autoconf/make/modules.mk~
new file mode 100644
index 0000000..20f830f
--- /dev/null
+++ b/autoconf/make/modules.mk~
@@ -0,0 +1,14 @@
+# Automatically generated module linking rules
+#
+# Creation time: Fri Jun 30 22:23:37 BST 2017
+
+src/library.o: src/library/getopt.o src/library/gettext.o
+ $(LD) $(LDFLAGS) -o $@ src/library/getopt.o src/library/gettext.o
+
+src/pv.o: src/pv/cursor.o src/pv/display.o src/pv/file.o src/pv/loop.o src/pv/number.o src/pv/signal.o src/pv/state.o src/pv/transfer.o src/pv/watchpid.o
+ $(LD) $(LDFLAGS) -o $@ src/pv/cursor.o src/pv/display.o src/pv/file.o src/pv/loop.o src/pv/number.o src/pv/signal.o src/pv/state.o src/pv/transfer.o src/pv/watchpid.o
+
+src/main.o: src/main/debug.o src/main/help.o src/main/main.o src/main/options.o src/main/remote.o src/main/version.o
+ $(LD) $(LDFLAGS) -o $@ src/main/debug.o src/main/help.o src/main/main.o src/main/options.o src/main/remote.o src/main/version.o
+
+
diff --git a/autoconf/make/package.mk b/autoconf/make/package.mk
new file mode 100644
index 0000000..82036de
--- /dev/null
+++ b/autoconf/make/package.mk
@@ -0,0 +1,17 @@
+#
+# Package name, version, and distribution files.
+#
+
+package = @PACKAGE@
+version = @VERSION@
+PACKAGE = @PACKAGE@
+
+distfiles = \
+$(srcdir)/README \
+$(srcdir)/autoconf \
+$(srcdir)/configure \
+$(srcdir)/doc \
+$(srcdir)/src \
+$(srcdir)/tests
+
+# EOF
diff --git a/autoconf/make/rules.mk b/autoconf/make/rules.mk
new file mode 100644
index 0000000..96ed511
--- /dev/null
+++ b/autoconf/make/rules.mk
@@ -0,0 +1,43 @@
+#
+# Compilation rules.
+#
+
+.SUFFIXES: .c .d .o
+
+.c.o:
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
+
+.c.d:
+ sh $(srcdir)/autoconf/scripts/depend.sh \
+ $(CC) $< $(<:%.c=%) $(srcdir) $(CFLAGS) $(CPPFLAGS) > $@
+
+#
+# NLS stuff
+#
+
+%.mo: %.po
+ $(MSGFMT) -o $@ $<
+ @touch $@
+ @chmod 644 $@
+
+%.gmo: %.po
+ rm -f $@
+ $(GMSGFMT) -o $@ $<
+ @touch $@
+ @chmod 644 $@
+
+$(srcdir)/src/nls/$(PACKAGE).pot: $(allsrc)
+ $(XGETTEXT) --default-domain=$(PACKAGE) --directory=$(srcdir) \
+ --add-comments --keyword=_ --keyword=N_ \
+ $(allsrc)
+ if cmp -s $(PACKAGE).po $@; then \
+ rm -f $(PACKAGE).po; \
+ else \
+ rm -f $@; \
+ mv $(PACKAGE).po $@; \
+ chmod 644 $@; \
+ fi
+
+src/nls/table.c: $(POFILES)
+ sh $(srcdir)/autoconf/scripts/po2table.sh $(POFILES) > src/nls/table.c
+
diff --git a/autoconf/make/unreal.mk b/autoconf/make/unreal.mk
new file mode 100644
index 0000000..24793cf
--- /dev/null
+++ b/autoconf/make/unreal.mk
@@ -0,0 +1,201 @@
+#
+# Rules for all phony targets.
+#
+
+.PHONY: all help make dep depend test check \
+ clean depclean indentclean distclean cvsclean svnclean \
+ index manhtml indent update-po \
+ doc dist release \
+ install uninstall \
+ rpm srpm
+
+all: $(alltarg) $(CATALOGS)
+
+help:
+ @echo 'This Makefile has the following utility targets:'
+ @echo
+ @echo ' all build all binary targets'
+ @echo ' install install compiled package and manual'
+ @echo ' uninstall uninstall the package'
+ @echo ' check / test run standardised tests on the compiled binary'
+ @echo
+ @echo 'Developer targets:'
+ @echo
+ @echo ' make rebuild the Makefile (after adding new files)'
+ @echo ' dep / depend rebuild .d (dependency) files'
+ @echo ' clean remove .o (object) and .c~ (backup) files'
+ @echo ' depclean remove .d (dependency) files'
+ @echo ' indentclean remove files left over from "make indent"'
+ @echo ' distclean remove everything not distributed'
+ @echo ' cvsclean remove everything not in CVS/SVN'
+ @echo
+ @echo ' index generate an HTML index of source code'
+ @echo ' manhtml output HTML man page to stdout'
+ @echo ' indent reformat all source files with "indent"'
+ @echo ' update-po update the .po files'
+ @echo
+ @echo ' dist create a source tarball for distribution'
+ @echo ' rpm build a binary RPM (passes $$RPMFLAGS to RPM)'
+ @echo ' srpm build a source RPM (passes $$RPMFLAGS to RPM)'
+ @echo ' release dist+rpm+srpm'
+ @echo
+
+make:
+ echo > $(srcdir)/autoconf/make/filelist.mk~
+ echo > $(srcdir)/autoconf/make/modules.mk~
+ cd $(srcdir); \
+ bash autoconf/scripts/makemake.sh \
+ autoconf/make/filelist.mk~ \
+ autoconf/make/modules.mk~
+ sh ./config.status
+
+dep depend: $(alldep)
+ echo '#' > $(srcdir)/autoconf/make/depend.mk~
+ echo '# Dependencies.' >> $(srcdir)/autoconf/make/depend.mk~
+ echo '#' >> $(srcdir)/autoconf/make/depend.mk~
+ echo >> $(srcdir)/autoconf/make/depend.mk~
+ cat $(alldep) >> $(srcdir)/autoconf/make/depend.mk~
+ sh ./config.status
+
+clean:
+ rm -f $(allobj)
+
+depclean:
+ rm -f $(alldep)
+
+indentclean:
+ cd $(srcdir) && for FILE in $(allsrc); do rm -f ./$${FILE}~; done
+
+update-po: $(srcdir)/src/nls/$(PACKAGE).pot
+ catalogs='$(CATALOGS)'; \
+ for cat in $$catalogs; do \
+ lang=$(srcdir)/`echo $$cat | sed 's/$(CATOBJEXT)$$//'`; \
+ mv $$lang.po $$lang.old.po; \
+ if $(MSGMERGE) $$lang.old.po $(srcdir)/src/nls/$(PACKAGE).pot > $$lang.po; then \
+ rm -f $$lang.old.po; \
+ else \
+ echo "msgmerge for $$cat failed!"; \
+ rm -f $$lang.po; \
+ mv $$lang.old.po $$lang.po; \
+ chmod 644 $$lang.po; \
+ fi; \
+ done
+
+distclean: clean depclean
+ rm -f $(alltarg) src/include/config.h
+ rm -rf $(package)-$(version).tar* $(package)-$(version) $(package)-$(version)-*.rpm
+ rm -f *.html config.*
+ rm Makefile
+
+cvsclean svnclean: distclean
+ rm -f doc/lsm
+ rm -f doc/$(package).spec
+ rm -f doc/quickref.1
+ rm -f configure
+ rm -f src/nls/*.gmo src/nls/*.mo
+ echo > $(srcdir)/autoconf/make/depend.mk~
+ echo > $(srcdir)/autoconf/make/filelist.mk~
+ echo > $(srcdir)/autoconf/make/modules.mk~
+
+doc:
+ :
+
+index:
+ (cd $(srcdir); sh autoconf/scripts/index.sh $(srcdir)) > index.html
+
+manhtml:
+ @man2html ./doc/quickref.1 \
+ | sed -e '1,/]*> ||ig' \
+ -e 's|]*>\([^<]*\)|\1|ig' \
+ -e '/\)|\1
|ig' \
+ -e 's/
//ig' \
+ -e 's/<[0-9A-Za-z_.-]\+@[0-9A-Za-z_.-]\+>//g' \
+ -e 's|\(http://.*\)|\1|ig' \
+ | sed -e '1,/
Index/,/
$(package)-$(version).tar.bz2
+ -grep -Fq '%_gpg_name' ~/.rpmmacros 2>/dev/null && rpm --addsign *.rpm
+ -gpg --list-secret-keys 2>&1 | grep -Fq 'uid' && gpg -ab *.tar.gz && rename .asc .txt *.tar.gz.asc
+ -gpg --list-secret-keys 2>&1 | grep -Fq 'uid' && gpg -ab *.tar.bz2 && rename .asc .txt *.tar.bz2.asc
+ chmod 644 $(package)-$(version)*
diff --git a/autoconf/make/vars.mk b/autoconf/make/vars.mk
new file mode 100644
index 0000000..262f463
--- /dev/null
+++ b/autoconf/make/vars.mk
@@ -0,0 +1,46 @@
+#
+# Variables for Make.
+#
+
+srcdir = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+infodir = @infodir@
+mandir = @mandir@
+etcdir = @prefix@/etc
+datadir = @datadir@
+sbindir = @sbindir@
+
+VPATH = $(srcdir)
+
+localedir = $(datadir)/locale
+gnulocaledir = $(prefix)/share/locale
+
+CATALOGS = @CATALOGS@
+POFILES = @POFILES@
+GMSGFMT = @GMSGFMT@
+MSGFMT = @MSGFMT@
+XGETTEXT = @XGETTEXT@
+MSGMERGE = msgmerge
+CATOBJEXT = @CATOBJEXT@
+INSTOBJEXT = @INSTOBJEXT@
+
+@SET_MAKE@
+SHELL = /bin/sh
+CC = @CC@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+UNINSTALL = rm -f
+
+LDFLAGS = -r
+LINKFLAGS = @LDFLAGS@
+DEFS = @DEFS@ -DLOCALEDIR=\"$(localedir)\"
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@ -I$(srcdir)/src/include -Isrc/include $(DEFS)
+LIBS = @LIBS@
+
+alltarg = @PACKAGE@
+
+# EOF
diff --git a/autoconf/scripts/benchmark.sh b/autoconf/scripts/benchmark.sh
new file mode 100755
index 0000000..37ef35d
--- /dev/null
+++ b/autoconf/scripts/benchmark.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+#
+# Benchmark the read/write performance of pv by looking at the number of
+# read() and write() calls and the average amount of data transferred each
+# time, as suggested by Ville Herva .
+#
+
+test_input=`mktemp /tmp/pvbench1XXXXXX`
+strace_output=`mktemp /tmp/pvbench2XXXXXX`
+
+trap "rm -f ${test_input} ${strace_output}" 0
+
+pv=${pv:-./pv}
+test -x ${pv} || pv=pv
+
+dd if=/dev/zero of=${test_input} bs=1k count=1k >/dev/null 2>&1
+
+echo -e "Buf(k)\tRate(k)\tReads\tRsize\tWrites\tWsize"
+
+for ((buffer=100; buffer<=1000; buffer+=100)); do
+ for ((rate=0; rate<=1000; rate+=100)); do
+ rateparm="-L ${rate}k"
+ test ${rate} -eq 0 && rateparm=""
+ strace -tt -o ${strace_output} \
+ ${pv} ${rateparm} -B ${buffer}k \
+ -f < ${test_input} > /dev/null 2>&1
+ rdata=$(
+ awk '$2~/^read\(0,/{c++;t+=$NF}END{print c "\t" t/c}' \
+ ${strace_output}
+ )
+ wdata=$(
+ awk '$2~/^write\(1,/{c++;t+=$NF}END{print c "\t" t/c}' \
+ ${strace_output}
+ )
+ echo -e "${buffer}\t${rate}\t${rdata}\t${wdata}"
+ done
+done
+
+# EOF
diff --git a/autoconf/scripts/depend.sh b/autoconf/scripts/depend.sh
new file mode 100755
index 0000000..f17f81e
--- /dev/null
+++ b/autoconf/scripts/depend.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# Generate dependencies for a C source file.
+#
+
+CC=$1
+shift
+file=$1
+shift
+stem=$1
+shift
+srcdir=$1
+abssrc=`echo $srcdir | sed ':1
+s,^\./,,g
+t1'`
+shift
+
+abssrc=`echo "$abssrc" | sed 's,\\.,\\\\.,g'`
+srcdir=`echo "$srcdir" | sed 's,\\.,\\\\.,g'`
+
+$CC -M -MG $* $file \
+| sed -e 's, /[^ ]*,,g' -e "s,^.*\.o:,${stem}.d ${stem}.o:," \
+ -e '/^ \\$/d' -e 's/ \\$//' \
+ -e 's,'"$srcdir"'/,,g' -e 's,'"$abssrc"'/,,g' \
+| tr '\n' ' ' \
+| tr -s ' '
+
+echo
+
+# EOF
diff --git a/autoconf/scripts/index.sh b/autoconf/scripts/index.sh
new file mode 100755
index 0000000..e0a0c07
--- /dev/null
+++ b/autoconf/scripts/index.sh
@@ -0,0 +1,171 @@
+#!/bin/ash
+#
+# Script to generate an HTML index of all C code from the current directory
+# downwards (skipping directories ending in ~). The header comment in each
+# file is listed, and each function's prototype and comment are given. A
+# list of "TODO:" comments is also generated.
+#
+# Outputs the HTML on standard output.
+#
+# If a parameter is given, it is the prefix to put before any "view file"
+# links, eg ".." to link to "../dir/file.c" instead of "dir/file.c".
+#
+# Skips any files containing the string !NOINDEX.
+#
+# Requires ctags and cproto.
+#
+
+OFFS=$1
+
+
+# Convert the given string to HTML-escaped values (<, >, & escaped) on
+# stdout.
+#
+html_safe () {
+ echo "$*" \
+ | sed -e 's|&|\&|g;s|<|\<|g;s|>|\>|g'
+}
+
+
+# Convert the given string to HTML-escaped values (<, >, & escaped) on
+# stdout, also adding a
to the end of each line.
+#
+html_safebr () {
+ echo "$*" \
+ | sed -e 's|&|\&|g;s|<|\<|g;s|>|\>|g;s/$/
/'
+}
+
+ALLFILES=`find . -name '*~' -prune -o -type f -name '*.c' \
+ -exec grep -FL '!NOINDEX' /dev/null '{}' ';'`
+
+CTAGDATA=`echo "$ALLFILES" \
+ | ctags -nRf- -L- --c-types=f \
+ | sed 's/ .\// /;s/;" .*$//'`
+
+FILELIST=`echo "$CTAGDATA" | cut -d ' ' -f 2 | sort | uniq`
+
+echo ''
+echo 'Source Code Index'
+echo ''
+echo ''
+echo '
'
+
+echo ''
+echo ''
+echo "$FILELIST" \
+| sed -e \
+ 's|^.*$|\0
|'
+echo '
'
+
+for FILE in $FILELIST; do
+
+ DIR=`dirname $FILE`
+ FUNCDEFS=`cproto -f1 -I. -Isrc/include -I$DIR $FILE 2>/dev/null \
+ | sed -n 's/^.*[ *]\([^ *(]*\)(.*$/\1/p'`
+ FILEHEAD="`sed -n -e \
+ '1,/\*\//{/\/\*/,/\*\//{s/^[\/ *]//;s/^\*[\/]*//;p;};}' \
+ < $FILE`"
+ FILESHORTDESC=`echo "$FILEHEAD" | sed -n '1,/^ *$/{/^ *[^ ]*/p;}'`
+ FILELONGDESC=`echo "$FILEHEAD" | sed '1,/^ *$/d'`
+
+ echo '
'
+ echo ''
+ echo ''
+ echo ''"$FILE"' | '
+ echo ' - | '
+ echo ''`html_safe "$FILESHORTDESC"`' | '
+ echo '
'
+ echo '[View File]
'
+ echo ''
+ echo "`html_safebr "$FILELONGDESC"`"
+ echo '
'
+
+ if [ -n "$FUNCDEFS" ]; then
+ echo 'Functions defined:
'
+ echo ''
+ echo "$FUNCDEFS" \
+ | sed 's|^.*$|\0|' \
+ | sed 's/^//;s|$|
|'
+ echo '
'
+ fi
+
+ echo '['
+ echo 'Top |'
+ echo 'To Do |'
+ echo 'Functions ]
'
+done
+
+echo ''
+echo ''
+echo "$CTAGDATA" | while read FUNC FILE LINENUM REST; do
+ echo -n ''
+ echo -n ''"$FUNC"'
'
+ echo '['"$FILE"'
] '
+done
+echo '
'
+
+echo "$CTAGDATA" | while read FUNC FILE LINENUM REST; do
+
+ FUNCDEF=`sed -n "$LINENUM,/{/p" < $FILE \
+ | tr '\n' ' ' \
+ | tr -d '{'`
+
+ LASTCOMLINE=`sed -n '1,'"$LINENUM"'{/\/\*/=;}' < $FILE | sed -n '$p'`
+ [ -z "$LASTCOMLINE" ] && LASTCOMLINE=1
+ LASTENDFUNCLINE=`sed -n '1,'"$LINENUM"'{/}/=;}' < $FILE | sed -n '$p'`
+ [ -z "$LASTENDFUNCLINE" ] && LASTENDFUNCLINE=1
+ FUNCHEAD="`sed -n -e \
+ "$LASTCOMLINE,"'/\*\//{h;s/^[\/ *]//;s/^\*[\/]*//;p;x;/\*\//q;}' \
+ < $FILE`"
+ [ "$LASTCOMLINE" -le "$LASTENDFUNCLINE" ] && FUNCHEAD=""
+
+ echo '
'
+ echo ''
+ echo -n ''
+ echo -n "$FUNC"'
'
+ echo -n '['
+ echo "$FILE"'
]'
+ echo '
'
+
+ echo ''"`html_safe "$FUNCDEF"`"'
'
+
+ echo ''
+ echo "`html_safebr "$FUNCHEAD"`"
+ echo '
'
+
+ echo '['
+ echo 'Top |'
+ echo 'To Do |'
+ echo 'Files ]
'
+done
+
+echo ''
+echo ''
+for FILE in $FILELIST; do
+
+ TODOLINES=`sed -n \
+ -e '/\/\*.*\*\//!{/\/\*/,/\*\//{/TODO:/{=;};};}' \
+ -e '/\/\*.*\*\//{/TODO:/{=;};}' \
+ < $FILE`
+
+ [ -z "$TODOLINES" ] && continue
+
+ echo -n ''
+ echo ''"$FILE"'
'
+ echo ''
+
+ for NUM in $TODOLINES; do
+ TODO=`sed -n "$NUM"'{s/^.*TODO://;s/\*\/.*$//;p;}' < $FILE`
+ echo "- [$NUM] `html_safe "$TODO"`
"
+ done
+
+ echo '
'
+done
+
+echo ''
+
+# EOF
diff --git a/autoconf/scripts/install.sh b/autoconf/scripts/install.sh
new file mode 100755
index 0000000..ebc6691
--- /dev/null
+++ b/autoconf/scripts/install.sh
@@ -0,0 +1,250 @@
+#! /bin/sh
+#
+# install - install a program, script, or datafile
+# This comes from X11R5 (mit/util/scripts/install.sh).
+#
+# Copyright 1991 by the Massachusetts Institute of Technology
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation, and that the name of M.I.T. not be used in advertising or
+# publicity pertaining to distribution of the software without specific,
+# written prior permission. M.I.T. makes no representations about the
+# suitability of this software for any purpose. It is provided "as is"
+# without express or implied warranty.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch. It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+ case $1 in
+ -c) instcmd="$cpprog"
+ shift
+ continue;;
+
+ -d) dir_arg=true
+ shift
+ continue;;
+
+ -m) chmodcmd="$chmodprog $2"
+ shift
+ shift
+ continue;;
+
+ -o) chowncmd="$chownprog $2"
+ shift
+ shift
+ continue;;
+
+ -g) chgrpcmd="$chgrpprog $2"
+ shift
+ shift
+ continue;;
+
+ -s) stripcmd="$stripprog"
+ shift
+ continue;;
+
+ -t=*) transformarg=`echo $1 | sed 's/-t=//'`
+ shift
+ continue;;
+
+ -b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+ shift
+ continue;;
+
+ *) if [ x"$src" = x ]
+ then
+ src=$1
+ else
+ # this colon is to work around a 386BSD /bin/sh bug
+ :
+ dst=$1
+ fi
+ shift
+ continue;;
+ esac
+done
+
+if [ x"$src" = x ]
+then
+ echo "install: no input file specified"
+ exit 1
+else
+ true
+fi
+
+if [ x"$dir_arg" != x ]; then
+ dst=$src
+ src=""
+
+ if [ -d $dst ]; then
+ instcmd=:
+ else
+ instcmd=mkdir
+ fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad
+# if $src (and thus $dsttmp) contains '*'.
+
+ if [ -f $src -o -d $src ]
+ then
+ true
+ else
+ echo "install: $src does not exist"
+ exit 1
+ fi
+
+ if [ x"$dst" = x ]
+ then
+ echo "install: no destination specified"
+ exit 1
+ else
+ true
+ fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+ if [ -d $dst ]
+ then
+ dst="$dst"/`basename $src`
+ else
+ true
+ fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+# this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS='
+'
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+ pathcomp="${pathcomp}${1}"
+ shift
+
+ if [ ! -d "${pathcomp}" ] ;
+ then
+ $mkdirprog "${pathcomp}"
+ else
+ true
+ fi
+
+ pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+ $doit $instcmd $dst &&
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+ if [ x"$transformarg" = x ]
+ then
+ dstfile=`basename $dst`
+ else
+ dstfile=`basename $dst $transformbasename |
+ sed $transformarg`$transformbasename
+ fi
+
+# don't allow the sed command to completely eliminate the filename
+
+ if [ x"$dstfile" = x ]
+ then
+ dstfile=`basename $dst`
+ else
+ true
+ fi
+
+# Make a temp file name in the proper directory.
+
+ dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+ $doit $instcmd $src $dsttmp &&
+
+ trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing. If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
+
+# Now rename the file to the real destination.
+
+ $doit $rmcmd -f $dstdir/$dstfile &&
+ $doit $mvcmd $dsttmp $dstdir/$dstfile
+
+fi &&
+
+
+exit 0
diff --git a/autoconf/scripts/makemake.sh b/autoconf/scripts/makemake.sh
new file mode 100755
index 0000000..3eca6c6
--- /dev/null
+++ b/autoconf/scripts/makemake.sh
@@ -0,0 +1,113 @@
+#!/bin/sh
+#
+# Generate Makefile dependencies inclusion and module target file "depend.mk~"
+# by scanning the directory "src" for files ending in ".c" and ".d", and for
+# subdirectories not starting with "_".
+#
+# Modules live inside subdirectories called [^_]* - i.e. a directory "foo" will
+# have a rule created which links all code inside it to "foo.o".
+#
+# The directory "src/include" is never scanned; neither are CVS or SVN
+# directories.
+#
+
+outlist=$1
+outlink=$2
+
+FIND=find
+GREP=grep
+which gfind 2>/dev/null | grep /gfind >/dev/null && FIND=gfind
+which ggrep 2>/dev/null | grep /ggrep >/dev/null && GREP=ggrep
+
+echo '# Automatically generated file listings' > $outlist
+echo '#' >> $outlist
+echo "# Creation time: `date`" >> $outlist
+echo >> $outlist
+
+echo '# Automatically generated module linking rules' > $outlink
+echo '#' >> $outlink
+echo "# Creation time: `date`" >> $outlink
+echo >> $outlink
+
+echo -n "Scanning for source files: "
+
+allsrc=`$FIND src -type f -name "*.c" -print`
+allobj=`echo $allsrc | tr ' ' '\n' | sed 's/\.c$/.o/'`
+alldep=`echo $allsrc | tr ' ' '\n' | sed 's/\.c$/.d/'`
+
+echo `echo $allsrc | wc -w | tr -d ' '` found
+
+echo -n "Scanning for modules: "
+
+modules=`$FIND src -type d -print \
+ | $GREP -v '^src$' \
+ | $GREP -v '/_' \
+ | $GREP -v '^src/include' \
+ | $GREP -v 'CVS' \
+ | $GREP -v '.svn' \
+ | while read DIR; do \
+ CONTENT=\$(/bin/ls -d \$DIR/* \
+ | $GREP -v '.po$' \
+ | $GREP -v '.gmo$' \
+ | $GREP -v '.mo$' \
+ | $GREP -v '.h$' \
+ | sed -n '$p'); \
+ [ -n "\$CONTENT" ] || continue; \
+ echo \$DIR; \
+ done
+ `
+
+echo up to `echo $modules | wc -w | tr -d ' '` found
+
+echo "Writing module linking rules"
+
+echo -n [
+for i in $modules; do echo -n ' '; done
+echo -n -e ']\r['
+
+for i in $modules; do
+ echo -n '.'
+ allobj="$allobj $i.o"
+ deps=""
+ for j in $i/*.c; do
+ [ -f $j ] || continue
+ newobj=`echo $j | sed -e 's@\.c$@.o@'`
+ deps="$deps $newobj"
+ done
+ for j in $i/*; do
+ [ -d "$j" ] || continue
+ [ `basename $j` = "CVS" ] && continue
+ [ `basename $j` = ".svn" ] && continue
+ CONTENT=`/bin/ls -d $j/* \
+ | $GREP -v '.po$' \
+ | $GREP -v '.gmo$' \
+ | $GREP -v '.mo$' \
+ | $GREP -v '.h$' \
+ | sed -n '$p'`
+ [ -n "$CONTENT" ] || continue
+ deps="$deps $j.o"
+ done
+ [ -n "$deps" ] || continue
+ echo "$i.o: $deps" >> $outlink
+ echo ' $(LD) $(LDFLAGS) -o $@' "$deps" >> $outlink
+ echo >> $outlink
+done
+
+echo ']'
+
+echo "Listing source, object and dependency files"
+
+echo -n "allsrc = " >> $outlist
+echo $allsrc | sed 's,src/nls/cat-id-tbl.c,,' | sed -e 's/ / \\!/g'\
+| tr '!' '\n' >> $outlist
+echo >> $outlist
+echo -n "allobj = " >> $outlist
+echo $allobj | sed -e 's/ / \\!/g' | tr '!' '\n' >> $outlist
+echo >> $outlist
+echo -n "alldep = " >> $outlist
+echo $alldep | sed -e 's/ / \\!/g' | tr '!' '\n' >> $outlist
+
+echo >> $outlist
+echo >> $outlink
+
+# EOF
diff --git a/autoconf/scripts/mkinstalldirs b/autoconf/scripts/mkinstalldirs
new file mode 100755
index 0000000..ff059dd
--- /dev/null
+++ b/autoconf/scripts/mkinstalldirs
@@ -0,0 +1,33 @@
+#!/bin/sh
+# mkinstalldirs --- make directory hierarchy
+# Author: Noah Friedman
+# Created: 1993-05-16
+# Last modified: 1994-03-25
+# Public domain
+
+errstatus=0
+
+for file in ${1+"$@"} ; do
+ set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'`
+ shift
+
+ pathcomp=
+ for d in ${1+"$@"} ; do
+ pathcomp="$pathcomp$d"
+ case "$pathcomp" in
+ -* ) pathcomp=./$pathcomp ;;
+ esac
+
+ if test ! -d "$pathcomp"; then
+ echo "mkdir $pathcomp" 1>&2
+ mkdir "$pathcomp" || errstatus=$?
+ chmod 755 $pathcomp 2>/dev/null
+ fi
+
+ pathcomp="$pathcomp/"
+ done
+done
+
+exit $errstatus
+
+# mkinstalldirs ends here
diff --git a/autoconf/scripts/po2table.sh b/autoconf/scripts/po2table.sh
new file mode 100755
index 0000000..78f583b
--- /dev/null
+++ b/autoconf/scripts/po2table.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+#
+# Messy script to convert all of the given .po files to a single C file on
+# stdout.
+
+cat <
+
+struct msgtable_s {
+ char *msgid;
+ char *msgstr;
+};
+
+
+struct msgtable_s *minigettext__gettable(char *lang)
+{
+ if (0 == lang)
+ return 0;
+
+EOF
+
+for POFILE in $*; do
+ LANG=`basename "$POFILE" | sed 's/.po$//'`
+ echo " if (strncmp(lang, \"$LANG\", 2) == 0) {"
+ echo " static struct msgtable_s data[] = {";
+
+ awk 'BEGIN{i=0;s=0;}
+ /^msgid[ ]+/ {
+ if (s) print " }, ";
+ print " {";
+ print " " substr($0,7);
+ i=1;
+ s=0;
+ }
+ /^msgstr[ ]+/ {
+ print " ,";
+ i=0;s=1;
+ print " " substr($0,8);
+ }
+ /^[ ]*"/ {
+ if (i||s) print " " $0;
+ }
+ END {if (i||s) print " }\n";}
+ ' < "$POFILE"
+ echo ' , { 0, 0 } };'
+ echo " return data;"
+ echo " }"
+done
+
+cat </dev/null` || TMP1=.tmp1
+TMP2=`mktemp 2>/dev/null` || TMP2=.tmp2
+TMP3=`mktemp 2>/dev/null` || TMP3=.tmp3
+TMP4=`mktemp 2>/dev/null` || TMP4=.tmp4
+
+export PROG TMP1 TMP2 TMP3 TMP4 # variables used by test scripts
+
+FAIL=0
+
+test -n "$TESTS" || TESTS=`ls "$SRCDIR/tests" | sort -n`
+
+for SCRIPT in $TESTS; do
+ test -f "$SCRIPT" || SCRIPT="$SRCDIR/tests/$SCRIPT"
+ test -f "$SCRIPT" || SCRIPT=`ls "$SRCDIR/tests/$SCRIPT"*`
+ test -f "$SCRIPT" || continue
+
+ echo `basename "$SCRIPT"`: " " | cut -b1-20 | sed 's/-/ - /' | tr "\n" ' '
+
+ STATUS=0
+ sh -e "$SCRIPT" || STATUS=1
+ test $STATUS -eq 1 && FAIL=1
+
+ test $STATUS -eq 1 && echo "FAILED" || echo "OK"
+done
+
+rm -f $TMP1 $TMP2 $TMP3 $TMP4
+
+exit $FAIL
+
+# EOF
diff --git a/doc/COPYING b/doc/COPYING
new file mode 100644
index 0000000..a1afc69
--- /dev/null
+++ b/doc/COPYING
@@ -0,0 +1,171 @@
+This package is free software, and is being distributed under the terms
+of the Artistic License 2.0.
+
+----------------------------------------------------------
+
+Artistic License 2.0
+
+Copyright (c) 2000-2006, The Perl Foundation.
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed. Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed. The
+intent is that the Copyright Holder maintains some artistic control
+over the development of that Package while still keeping the Package
+available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement. Definitions
+
+"Copyright Holder" means the individual(s) or organization(s) named in
+the copyright notice for the entire Package.
+
+"Contributor" means any party that has contributed code or other material
+to the Package, in accordance with the Copyright Holder's procedures.
+
+"You" and "your" means any person who would like to copy, distribute,
+or modify the Package.
+
+"Package" means the collection of files distributed by the Copyright
+Holder, and derivatives of that collection and/or of those files. A given
+Package may consist of either the Standard Version, or a Modified Version.
+
+"Distribute" means providing a copy of the Package or making it accessible
+to anyone else, or in the case of a company or organization, to others
+outside of your company or organization.
+
+"Distributor Fee" means any fee that you charge for Distributing this
+Package or providing support for this Package to another party. It does
+not mean licensing fees.
+
+"Standard Version" refers to the Package if it has not been modified,
+or has been modified only in ways explicitly requested by the Copyright
+Holder.
+
+"Modified Version" means the Package, if it has been changed, and such
+changes were not explicitly requested by the Copyright Holder.
+
+"Original License" means this Artistic License as Distributed with the
+Standard Version of the Package, in its current version or as it may be
+modified by The Perl Foundation in the future.
+
+"Source" form means the source code, documentation source, and
+configuration files for the Package.
+
+"Compiled" form means the compiled bytecode, object code, binary, or any
+other form resulting from mechanical transformation or translation of
+the Source form. Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that you
+do not Distribute the Modified Version. Permissions for Redistribution
+of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the Standard
+Version of this Package in any medium without restriction, either gratis
+or for a Distributor Fee, provided that you duplicate all of the original
+copyright notices and associated disclaimers. At your discretion, such
+verbatim copies may or may not include a Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such will
+be subject to the Original License. Distribution of Modified Versions
+of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+(a) make the Modified Version available to the Copyright Holder of the
+Standard Version, under the Original License, so that the Copyright Holder
+may include your modifications in the Standard Version.
+(b) ensure that installation of your Modified Version does not prevent the
+user installing or running the Standard Version. In addition, the Modified
+Version must bear a name that is different from the name of the Standard
+Version.
+(c) allow anyone who receives a copy of the Modified Version to make
+the Source form of the Modified Version available to others under
+(i) the Original License or
+(ii) a license that permits the licensee to freely copy, modify and
+redistribute the Modified Version using the same licensing terms that apply
+to the copy that the licensee received, and requires that the Source form of
+the Modified Version, and of any works derived from it, be made freely
+available in that license fees are prohibited but Distributor Fees are
+allowed. Distribution of Compiled Forms of the Standard Version or Modified
+Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be valid
+at the time of your distribution. If these instructions, at any time
+while you are carrying out such distribution, become invalid, you must
+provide new instructions on demand or cease further distribution. If
+you provide valid instructions or cease distribution within thirty days
+after you become aware that the instructions are invalid, then you do
+not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without the
+Source, provided that you comply with Section 4 with respect to the
+Source of the Modified Version. Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with other
+works, to embed the Package in a larger work of your own, or to build
+stand-alone binary or bytecode versions of applications that include the
+Package, and Distribute the result without restriction, provided the
+result does not expose a direct interface to the Package. Items That
+are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the terms
+of this license. General Provisions
+
+(10) Any use, modification, and distribution of the Standard or Modified
+Versions is governed by this Artistic License. By using, modifying or
+distributing the Package, you accept this license. Do not use, modify,
+or distribute the Package, if you do not accept this license.
+
+(11) If your Modified Version has been derived from a Modified Version
+made by someone other than you, you are nevertheless required to ensure
+that your Modified Version complies with the requirements of this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide, free-of-charge
+patent license to make, have made, use, offer to sell, sell, import
+and otherwise transfer the Package with respect to any patent claims
+licensable by the Copyright Holder that are necessarily infringed by the
+Package. If you institute patent litigation (including a cross-claim or
+counterclaim) against any party alleging that the Package constitutes
+direct or contributory patent infringement, then this Artistic License
+to you shall terminate on the date that such litigation is filed.
+
+(14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT
+HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT
+PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER
+OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
+CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------
diff --git a/doc/INSTALL b/doc/INSTALL
new file mode 100644
index 0000000..50dbe43
--- /dev/null
+++ b/doc/INSTALL
@@ -0,0 +1,183 @@
+Basic Installation
+==================
+
+ These are generic installation instructions.
+
+ The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation. It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions. Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, a file
+`config.cache' that saves the results of its tests to speed up
+reconfiguring, and a file `config.log' containing compiler output
+(useful mainly for debugging `configure').
+
+ If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release. If at some point `config.cache'
+contains results you don't want to keep, you may remove or edit it.
+
+ The file `configure.in' is used to create `configure' by a program
+called `autoconf'. You only need `configure.in' if you want to change
+it or regenerate `configure' using a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+ 1. `cd' to the directory containing the package's source code and type
+ `./configure' to configure the package for your system. If you're
+ using `csh' on an old version of System V, you might need to type
+ `sh ./configure' instead to prevent `csh' from trying to execute
+ `configure' itself.
+
+ Running `configure' takes awhile. While running, it prints some
+ messages telling which features it is checking for.
+
+ 2. Type `make' to compile the package.
+
+ 3. Optionally, type `make check' to run any self-tests that come with
+ the package.
+
+ 4. Type `make install' to install the programs and any data files and
+ documentation.
+
+ 5. You can remove the program binaries and object files from the
+ source code directory by typing `make clean'. To also remove the
+ files that `configure' created (so you can compile the package for
+ a different kind of computer), type `make distclean'. There is
+ also a `make maintainer-clean' target, but that is intended mainly
+ for the package's developers. If you use it, you may have to get
+ all sorts of other programs in order to regenerate files that came
+ with the distribution.
+
+Compilers and Options
+=====================
+
+ Some systems require unusual options for compilation or linking that
+the `configure' script does not know about. You can give `configure'
+initial values for variables by setting them in the environment. Using
+a Bourne-compatible shell, you can do that on the command line like
+this:
+ CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
+
+Or on systems that have the `env' program, you can do it like this:
+ env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
+
+Compiling For Multiple Architectures
+====================================
+
+ You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory. To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'. `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script. `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+ If you have to use a `make' that does not supports the `VPATH'
+variable, you have to compile the package for one architecture at a time
+in the source code directory. After you have installed the package for
+one architecture, use `make distclean' before reconfiguring for another
+architecture.
+
+Installation Names
+==================
+
+ By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc. You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PATH'.
+
+ You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files. If you
+give `configure' the option `--exec-prefix=PATH', the package will use
+PATH as the prefix for installing programs and libraries.
+Documentation and other data files will still use the regular prefix.
+
+ In addition, if you use an unusual directory layout you can give
+options like `--bindir=PATH' to specify different values for particular
+kinds of files. Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+ If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+ Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System). The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+ For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+ There may be some features `configure' can not figure out
+automatically, but needs to determine by the type of host the package
+will run on. Usually `configure' can figure that out, but if it prints
+a message saying it can not guess the host type, give it the
+`--host=TYPE' option. TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name with three fields:
+ CPU-COMPANY-SYSTEM
+
+See the file `config.sub' for the possible values of each field. If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the host type.
+
+ If you are building compiler tools for cross-compiling, you can also
+use the `--target=TYPE' option to select the type of system they will
+produce code for and the `--build=TYPE' option to select the type of
+system on which you are compiling the package.
+
+Sharing Defaults
+================
+
+ If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists. Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Operation Controls
+==================
+
+ `configure' recognizes the following options to control how it
+operates.
+
+`--cache-file=FILE'
+ Use and save the results of the tests in FILE instead of
+ `./config.cache'. Set FILE to `/dev/null' to disable caching, for
+ debugging `configure'.
+
+`--help'
+ Print a summary of the options to `configure', and exit.
+
+`--quiet'
+`--silent'
+`-q'
+ Do not print messages saying which checks are being made. To
+ suppress all normal output, redirect it to `/dev/null' (any error
+ messages will still be shown).
+
+`--srcdir=DIR'
+ Look for the package's source code in directory DIR. Usually
+ `configure' can determine that directory automatically.
+
+`--version'
+ Print the version of Autoconf used to generate the `configure'
+ script, and exit.
+
+`configure' also accepts some other, not widely useful, options.
+
diff --git a/doc/NEWS b/doc/NEWS
new file mode 100644
index 0000000..172e2e1
--- /dev/null
+++ b/doc/NEWS
@@ -0,0 +1,304 @@
+1.6.6 - 30 June 2017
+ - (r161) use %llu instead of %Lu for better compatibility (Eric A. Borisch)
+ - (r162) (#1532) fix target buffer size (-B) being ignored (AndCycle, Ilya
+ Basin, Antoine Beaupré)
+ - (r164) cap read/write sizes, and check elapsed time during read/write
+ cycles, to avoid display hangs with large buffers or slow media; also
+ remove select() call from repeated_write function as it slows the
+ transfer down and the wrapping alarm() means it is unnecessary
+ - (r169) (#1477) use alternate form for transfer counter, such that 13GB
+ is shown as 13.0GB so it's the same width as 13.1GB (André Stapf)
+ - (r171) cleanup: units corrections in man page, of the form kb -> KiB
+ - (r175) report error in "-d" if process fd directory is unreadable, or if
+ process disappears before we start the main loop (Jacek Wielemborek)
+
+1.6.0 - 15 March 2015
+ - fix lstat64 support when unavailable - separate patches supplied by
+ Ganael Laplanche and Peter Korsgaard
+ - (#1506) new option "-D" / "--delay-start" to only show bar after N
+ seconds (Damon Harper)
+ - new option "--fineta" / "-I" to show ETA as time of day rather than time
+ remaining - patch supplied by Erkki Seppälä (r147)
+ - (#1509) change ETA (--eta / -e) so that days are given if the hours
+ remaining are 24 or more (Jacek Wielemborek)
+ - (#1499) repeat read and write attempts on partial buffer fill/empty to
+ work around post-signal transfer rate drop reported by Ralf Ramsauer
+ - (#1507) do not try to calculate total size in line mode, due to bug
+ reported by Jacek Wielemborek and Michiel Van Herwegen
+ - cleanup: removed defunct RATS comments and unnecessary copyright notices
+ - clean up displayed lines when using --watchfd PID, when PID exits
+ - output errors on a new line to avoid overwriting transfer bar
+
+1.5.7 - 26 August 2014
+ - show KiB instead of incorrect kiB (Debian bug #706175)
+ - (#1284) do not gzip man page, for non-Linux OSes (Bob Friesenhahn)
+ - work around "awk" bug in tests/016-numeric-timer in decimal "," locales
+ - fix "make rpm" and "make srpm", extend "make release" to sign releases
+
+1.5.3 - 4 May 2014
+ - remove SPLICE_F_NONBLOCK to fix problem with slow splice() (Jan Seda)
+
+1.5.2 - 10 February 2014
+ - allow "--watchfd" to look at block devices
+ - let "--watchfd PID:FD" work with "--size N"
+ - moved contributors out of the manual as the list was too long
+ (NB everyone is still listed in the README and always will be)
+
+1.5.1 - 23 January 2014
+ - new option "--watchfd" - suggested by Jacek Wielemborek and "fdwatch"
+ - use non-block flag with splice()
+ - new display option "--buffer-percent", suggested by Kim Krecht
+ - new display option "--last-written", suggested by Kim Krecht
+ - new transfer option "--no-splice"
+ - fix for minor bug which dropped display elements after one empty one
+ - fix for single fd leak on exit (Cristian Ciupitu)
+
+1.4.12 - 5 August 2013
+ - new option "--null" - patch supplied by Zing Shishak
+ - AIX build fix (add "-lc128") - with help from Pawel Piatek
+ - AIX "-c" fixes - with help from Pawel Piatek
+ - SCO build fix (po2table.sh) - reported by Wouter Pronk
+ - test scripts fix for older distributions - patch from Bryan Dongray
+ - fix for splice() not using stdin - patch from Zev Weiss
+
+1.4.6 - 22 January 2013
+ - added patch from Pawel Piatek to omit O_NOFOLLOW in AIX
+
+1.4.5 - 10 January 2013
+ - updated manual page to show known problem with "-R" on Cygwin
+
+1.4.4 - 11 December 2012
+ - added debugging, see `pv -h' when configure run with "--enable-debugging"
+ - rewrote cursor positioning code used when IPC is unavailable (Cygwin)
+ - fixed cursor positioning cursor read answerback problem (Cygwin/Solaris)
+ - fixed bug causing crash when progress displayed with too-small terminal
+
+1.4.0 - 6 December 2012
+ - new option "--skip-errors" commissioned by Jim Salter
+ - if stdout is a block device, and we don't know the total size, use the
+ size of that block device as the total (Peter Samuelson)
+ - new option "--stop-at-size" to stop after "--size" bytes
+ - report correct filename on read errors
+ - fix use-after-free bug in remote PID cleanup code
+ - refactored large chunks of code to make it more readable and to replace
+ most static variables with a state structure
+
+1.3.9 - 5 November 2012
+ - allow "--format" parameters to be sent with "--remote"
+ - configure option "--disable-ipc"
+ - added tests for --numeric with --timer and --bytes
+ - added tests for --remote
+
+1.3.8 - 29 October 2012
+ - new "--pidfile" option to save process ID to a file
+ - integrated patch for --numeric with --timer and --bytes (Sami Liedes)
+ - removed signalling from --remote to prevent accidental process kills
+ - new "--format" option (originally Vladimir Pal / Vladimir Ermakov)
+
+1.3.4 - 27 June 2012
+ - new "--disable-splice" configure script option
+ - fixed line mode size count with multiple files (Moritz Barsnick)
+ - fixes for AIX core dumps (Pawel Piatek)
+
+1.3.1 - 9 June 2012
+ - do not use splice() if the write buffer is not empty (Thomas Rachel)
+ - added test 15 (pipe transfers), and new test script
+
+1.3.0 - 5 June 2012
+ - added Tiger build patch from Olle Jonsson
+ - fix 1024-boundary display garble (Debian bug #586763)
+ - use splice(2) where available (Debian bug #601683)
+ - added known bugs section of the manual page
+ - fixed average rate test, 12 (Andrew Macheret)
+ - use IEEE1541 units (Thomas Rachel)
+ - bug with rate limit under 10 fixed (Henry Precheur)
+ - speed up PV line mode (patch: Guillaume Marcais)
+ - remove LD=ld from vars.mk to fix cross-compilation (paintitgray/PV#1291)
+
+1.2.0 - 14 December 2010
+ - integrated improved SI prefixes and --average-rate (Henry Gebhardt)
+ - return nonzero if exiting due to SIGTERM (Martin Baum)
+ - patch from Phil Rutschman to restore terminal properly on exit
+ - fix i18n especially for --help (Sebastian Kayser)
+ - refactored pv_display
+ - we now have a coherent, documented, exit status
+ - modified pipe test and new cksum test from Sebastian Kayser
+ - default CFLAGS to just "-O" for non-GCC (Kjetil Torgrim Homme)
+ - LFS compile fix for OS X 10.4 (Alexandre de Verteuil)
+ - remove DESTDIR / suffix (Sam Nelson, Daniel Pape)
+ - fixed potential NULL deref in transfer (Elias Pipping / LLVM/Clang)
+
+1.1.4 - 6 March 2008
+ - patch from Elias Pipping correcting compilation failure on Darwin 9
+ - patch from Patrick Collison correcting similar problems on OS X
+ - trap SIGINT/SIGHUP/SIGTERM so we clean up IPCs on exit (Laszlo Ersek)
+ - abort if numeric option, eg -L, has non-numeric value (Boris Lohner)
+
+1.1.0 - 30 August 2007
+ - new option --remote (-R) to control an already-running process
+ - new option --line-mode (-l) to count lines instead of bytes
+ - fix for "-L" to be less resource intensive
+ - fix for input/output equivalence check on Mac OS X
+ - fix for size calculation in pipelines on Mac OS X
+ - fixed "make uninstall"
+ - removed /debian directory at request of new Debian maintainer
+
+1.0.1 - 4 August 2007
+ - licensing change from Artistic to Artistic 2.0
+ - removed the "-l" / "--license" option
+
+1.0.0 - 2 August 2007
+ - act more like "cat" - just skip unreadable files, don't abort
+ - removed text version of manual page, and obsolete Info file generation
+ - code cleanup and separation of PV internals from CLI front-end
+
+0.9.9 - 5 February 2007
+ - new option --buffer-size (-B) suggested by Mark Tomich
+ - build fix: HP/UX largefile compile fix from Timo Savinen
+ - maintain better buffer filling during transfers
+ - workaround: pv /dev/zero | dd bs=1M count=1k bug (reported by Gert Menke)
+ - dropped support for the Texinfo manual
+
+0.9.6 - 27 February 2006
+ - bugfix: key_t incompatibility with Cygwin
+ - bugfix: interval (-i) parameter parses numbers after decimal point
+ - build fix: use static NLS if msgfmt is unavailable
+ - on the final update, blank out the now-zero ETA
+
+0.9.2 - 1 September 2005
+ - Daniel Roethlisberger patch: use lockfiles if terminal locking fails
+
+0.9.1 - 16 June 2005
+ - minor RPM spec file fix for Fedora Core 4
+
+0.9.0 - 15 November 2004
+ - minor NLS bugfix
+
+0.8.9 - 6 November 2004
+ - decimal values now accepted for rate and size, eg "-L 1.23M"
+ - code cleanup
+ - developers: "make help" now lists Makefile targets
+
+0.8.6 - 29 June 2004
+ - use uu_lock() for terminal locking on FreeBSD
+
+0.8.5 - 2 May 2004
+ - cursor positioning (-c) reliability improved on systems with IPC
+ - minor fix: made test 005 more reliable
+ - new option --height (-H)
+
+0.8.2 - 24 April 2004
+ - allow k,m,g,t suffixes on numbers
+ - added "srpm" and "release" Makefile targets
+
+0.8.1 - 19 April 2004
+ - bugfix in cursor positioning (-c)
+
+0.8.0 - 12 February 2004
+ - replaced GNU getopt with my library code
+ - replaced GNU gettext with my very minimal replacement
+ - use DESTDIR instead of RPM_BUILD_ROOT for optional installation prefix
+ - looked for flaws using RATS, cleaned up code
+
+0.7.0 - 8 February 2004
+ - display buffer management fixes (thanks Cédric Delfosse)
+ - replaced --enable-debug with --enable-debugging and --enable-profiling
+
+0.6.4 - 14 January 2004
+ - fixed minor bug in RPM installation
+ - bugfix in "make index" (only of interest to developers)
+
+0.6.3 - 22 December 2003
+ - fixed transient bug that reported "resource unavailable" occasionally
+
+0.6.2 - 6 August 2003
+ - block devices now have their size read correctly, so pv /dev/hda1 works
+ - minor code cleanups (mainly removal of CVS "Id" tags)
+
+0.6.0 - 3 August 2003
+ - doing ^Z then "bg" then "fg" now continues displaying
+
+0.5.9 - 23 July 2003
+ - fix for test 007 when not in C locale
+ - fix for build process to use CPPFLAGS
+ - fix for build process to use correct i18n libraries
+ - fix for build process - more portable sed in dependency generator
+ - fix for install process - remember to mkinstalldirs before installing
+ - fixes for building on Mac OS X
+
+0.5.3 - 4 May 2003
+ - added Polish translation thanks to Bartosz Feñski
+ and Krystian Zubel
+ - moved doc/debian to ./debian at insistence of common sense
+ - minor Solaris 8 compatibility fixes
+ - seems to compile and test OK on Mac OS X
+
+0.5.0 - 15 April 2003
+ - added French translation thanks to Stéphane Lacasse
+
+ - added German translation thanks to Marcos Kreinacke
+
+ - switched LGPL reference from "Library" to "Lesser"
+
+0.4.9 - 18 February 2003
+ - support for >2GB files added where available (Debian bug #180986)
+ - added doc/debian dir (from Cédric Delfosse)
+ - added "make rpm" and "make deb" targets to build RPM and Debian packages
+ - added a "make pv-static" rule to build a statically linked version
+
+0.4.5 - 13 December 2002
+ - added Portuguese (Brazilian) translation thanks to Eduardo Aguiar
+
+0.4.4 - 7 December 2002
+ - pause/resume support - don't count time while stopped
+ - stop output when resumed in the background
+ - terminal size change support
+ - bugfix: <=> indicator no longer sticks at right hand edge
+
+0.4.0 - 27 November 2002
+ - allow decimal interval values, eg 0.1, 0.5, etc
+ - some simple tests added (`make check')
+ - smoother throughput limiting (--rate-limit), now done in 0.1sec chunks
+ - bounds-check interval values (-i) - max update interval now 10 minutes
+ - more reliable non-blocking output to keep display updated
+ - no longer rely on atoll()
+ - don't output final blank line if --numeric
+ - use fcntl() instead of flock() for Solaris compatibility
+
+0.3.0 - 25 November 2002
+ - handle broken output pipe gracefully
+ - continue updating display even when output pipe is blocking
+
+0.2.6 - 21 October 2002
+ - we now ignore EINTR on select()
+ - variable-size buffer (still need to add code to change size)
+ - added (tentative) support for internationalisation
+ - removed superfluous --no-progress, etc options
+ - optimised transfer by using bigger buffers, based on st_blksize
+ - added --wait option to wait until transfer begins before showing progress
+ - added --rate-limit option to limit rate to a maximum throughput
+ - added --quiet option (no output at all) to be used with --rate-limit
+
+0.2.5 - 23 July 2002
+ - added [FILE]... arguments, like `cat'
+ - function separation in code
+ - some bug fixes related to numeric overflow
+
+0.2.3 - 19 July 2002
+ - Texinfo manual written, man page updated
+ - byte counter added
+
+0.2.0 - 18 July 2002
+ - ETA counter added
+ - screen width estimation added
+ - progress bar added
+
+0.1.0 - 17 July 2002
+ - main loop created
+ - rate counter added
+ - elapsed time counter added
+ - percentage calculation added
+
+0.0.1 - 16 July 2002
+ - package created
+ - first draft of man page written
diff --git a/doc/PACKAGE b/doc/PACKAGE
new file mode 100644
index 0000000..f1345ae
--- /dev/null
+++ b/doc/PACKAGE
@@ -0,0 +1 @@
+pv
diff --git a/doc/TODO b/doc/TODO
new file mode 100644
index 0000000..ceca7c4
--- /dev/null
+++ b/doc/TODO
@@ -0,0 +1,35 @@
+Things still to do:
+
+ - allow -E to take a block size argument so errors cause a skip to the
+ next block (Anthony DeRobertis)
+ - (#1559) momentary ETA option (Luc Gommans)
+ - (#1556) correct German translations (Richard Fonfara)
+ - (#1557) use clock_gettime() in ETA calculation (Mateju Miroslav)
+ - (#1561) show days in same format in ETA as in elapsed time
+ - (#1562) allow -r with -l and -n to output lines/sec (Roland Kletzing)
+ - (#1563) make -B imply -C (Johannes Gerer)
+ - document zsh <() incompatibility (frederik@ofb.net - Frederik Eaton)
+ - do not check terminal in -q/-n mode (zsh <(pv -n) fails)
+ - if -w/-H was specified, ignore SIGWINCH
+ - option to enable O_DIRECT (Romain Kang, Jacek Wielemborek)
+ - use posix_fadvise() like cat(1) does (Jacek Wielemborek)
+ - (#1508) add watchfd tests
+ - (#1534) allow multiple -d options
+ - (#1533) one-shot option (Jacek Wielemborek)
+ - (#1505) option to switch rate to per minute if really slow
+ - (#1510) option "--progress-from FILE", read last number and use it as bytes read:
+ pv --progress-from <( while sleep 1; do du -sb somedir; done ) -s 123g
+ (Jacek Wielemborek)
+ - (#1476) (Debian #507682) adjustable averaging window for rate display
+ - (#1286) option for process title (Martin Sarsale) as "pv - name:FooProcess -xyz - transferred: 1.3GB - 500KB/s - running: 10:15:30s"
+ - (#1290) look at effect of O_SYNC or fsync on performance
+ - (#1281) option (-x?) to use xterm title line for status (Joachim Haga)
+ - (#1287) if the first pv exits, should the second become IPC leader?
+ - (#1560) use Unicode for more granular progress bar (Alexander Petrossian)
+
+ - add development support for http://clang-analyzer.llvm.org/
+ - stats for avg/min/max/stddev throughput (Venky.N.Iyer)
+ - pv-ify a command line (Will Entriken) - "pv FOO | BAR | BAZ"
+ - get more translations
+
+Any assistance would be appreciated.
diff --git a/doc/VERSION b/doc/VERSION
new file mode 100644
index 0000000..ec70f75
--- /dev/null
+++ b/doc/VERSION
@@ -0,0 +1 @@
+1.6.6
diff --git a/doc/lsm.in b/doc/lsm.in
new file mode 100644
index 0000000..ca20559
--- /dev/null
+++ b/doc/lsm.in
@@ -0,0 +1,19 @@
+Begin3
+Title: @PACKAGE@
+Version: @VERSION@
+Entered-date: 30JUN17
+Description: A tool for monitoring the progress of data through a
+ pipeline. It can be inserted into any normal pipeline
+ between two processes to give a visual indication of how
+ quickly data is passing through, how long it has taken, how
+ near to completion it is, and an estimate of how long it
+ will be until completion.
+Keywords: progress bar, console, pipe, transfer rate
+Author: Andrew Wood
+Maintained-by: Andrew Wood
+Primary-site: http://www.ivarch.com/programs/@PACKAGE@.shtml
+Alternate-site:
+Original-site:
+Platforms:
+Copying-policy: Artistic 2.0
+End
diff --git a/doc/quickref.1.in b/doc/quickref.1.in
new file mode 100644
index 0000000..5245e3f
--- /dev/null
+++ b/doc/quickref.1.in
@@ -0,0 +1,600 @@
+.TH @UCPACKAGE@ 1 "June 2017" Linux "User Manuals"
+.SH NAME
+@PACKAGE@ \- monitor the progress of data through a pipe
+.SH SYNOPSIS
+.B @PACKAGE@
+[\fIOPTION\fR]
+[\fIFILE\fR]...
+.br
+.B @PACKAGE@
+[\fI\-h\fR|\fI\-V\fR]
+
+
+.SH DESCRIPTION
+.B @PACKAGE@
+shows the progress of data through a pipeline by giving information such as
+time elapsed, percentage completed (with progress bar), current throughput
+rate, total data transferred, and ETA.
+
+To use it, insert it in a pipeline between two processes, with the
+appropriate options. Its standard input will be passed through to its
+standard output and progress will be shown on standard error.
+
+.B @PACKAGE@
+will copy each supplied
+.B FILE
+in turn to standard output
+.BR "" "(" -
+means standard input), or if no
+.BR FILE s
+are specified just standard input is copied. This is the same behaviour
+as
+.BR cat (1).
+
+A simple example to watch how quickly a file is transferred using
+.BR nc (1):
+
+.RS
+.B @PACKAGE@ file | nc -w 1 somewhere.com 3000
+.RE
+
+A similar example, transferring a file from another process and passing the
+expected size to
+.BR @PACKAGE@ :
+
+.RS
+.B cat file | @PACKAGE@ -s 12345 | nc -w 1 somewhere.com 3000
+.RE
+
+A more complicated example using numeric output to feed into the
+.BR dialog (1)
+program for a full-screen progress display:
+
+.RS
+.B (tar cf - . \e
+.br
+.B " | @PACKAGE@ -n -s $(du -sb . | awk '{print $1}') \e"
+.br
+.B " | gzip -9 > out.tgz) 2>&1 \e"
+.br
+.B | dialog --gauge 'Progress' 7 70
+.RE
+
+Taking an image of a disk, skipping errors:
+
+.RS
+.B @PACKAGE@ -EE /dev/sda > disk-image.img
+.RE
+
+Writing an image back to a disk:
+
+.RS
+.B @PACKAGE@ disk-image.img > /dev/sda
+.RE
+
+Zeroing a disk:
+
+.RS
+.B @PACKAGE@ < /dev/zero > /dev/sda
+.RE
+
+Note that if the input size cannot be calculated, and the output is a block
+device, then the size of the block device will be used and
+.B @PACKAGE@
+will automatically stop at that size as if
+.B \-S
+had been given.
+
+(Linux only): Watching file descriptor 3 opened by another process 1234:
+
+.RS
+.B @PACKAGE@ -d 1234:3
+.RE
+
+(Linux only): Watching all file descriptors used by process 1234:
+
+.RS
+.B @PACKAGE@ -d 1234
+.RE
+
+
+.SH OPTIONS
+.B @PACKAGE@
+takes many options, which are divided into display switches, output
+modifiers, and general options.
+
+
+.SH DISPLAY SWITCHES
+If no display switches are specified,
+.B @PACKAGE@
+behaves as if
+.BR \-p ", " \-t ", " \-e ", " \-r ", and " \-b
+had been given (i.e. everything except average rate is switched on).
+Otherwise, only those display types that are explicitly switched on will be
+shown.
+.TP
+.B \-p, \-\-progress
+Turn the progress bar on. If standard input is not a file and no
+size was given (with the
+.B \-s
+modifier), the progress bar cannot indicate how close to completion the
+transfer is, so it will just move left and right to indicate that data is
+moving.
+.TP
+.B \-t, \-\-timer
+Turn the timer on. This will display the total elapsed time that
+.B @PACKAGE@
+has been running for.
+.TP
+.B \-e, \-\-eta
+Turn the ETA timer on. This will attempt to guess, based on previous
+transfer rates and the total data size, how long it will be before
+completion. This option will have no effect if the total data size cannot
+be determined.
+.TP
+.B \-I, \-\-fineta
+Turn the ETA timer on, but display the estimated local time of arrival
+instead of time left. When the estimated time is more than 6 hours in the
+future, the date is shown as well.
+.TP
+.B \-r, \-\-rate
+Turn the rate counter on. This will display the current rate of data
+transfer.
+.TP
+.B \-a, \-\-average\-rate
+Turn the average rate counter on. This will display the average rate of
+data transfer so far.
+.TP
+.B \-b, \-\-bytes
+Turn the total byte counter on. This will display the total amount of
+data transferred so far.
+.TP
+.B \-T, \-\-buffer\-percent
+Turn on the transfer buffer percentage display. This will show the
+percentage of the transfer buffer in use - but see the caveat under
+.B %T
+in the
+.B FORMATTING
+section below.
+.TP
+.B \-A, \-\-last\-written NUM
+Show the last
+.B NUM
+bytes written - but see the caveat under
+.B %nA
+in the
+.B FORMATTING
+section below.
+.TP
+.B \-F, \-\-format FORMAT
+Ignore the options
+.BR \-p ,
+.BR \-t ,
+.BR \-e ,
+.BR \-r ,
+.BR \-a ,
+.BR \-b ,
+.BR \-T ,
+and
+.BR \-A ,
+and instead use the format string
+.B FORMAT
+to determine the output format. See the
+.B FORMATTING
+section below.
+.TP
+.B \-n, \-\-numeric
+Numeric output. Instead of giving a visual indication of progress,
+.B @PACKAGE@
+will give an integer percentage, one per line, on standard error, suitable
+for piping (via convoluted redirection) into
+.BR dialog (1).
+Note that
+.B \-f
+is not required if
+.B \-n
+is being used.
+.TP
+.B ""
+Note that if
+.B \-\-numeric
+is in use, then adding
+.B \-\-bytes
+will cause the number of bytes processed so far to be output instead of a
+percentage; if
+.B \-\-line\-mode
+is also in use, then instead of bytes or a percentage, the number of lines
+so far is output. And finally, if
+.B \-\-timer
+is also in use, then each output line is prefixed with the elapsed time
+so far, as a decimal number of seconds.
+.TP
+.B \-q, \-\-quiet
+No output. Useful if the
+.B \-L
+option is being used on its own to just limit the transfer rate of a pipe.
+
+
+.SH OUTPUT MODIFIERS
+.TP
+.B \-W, \-\-wait
+Wait until the first byte has been transferred before showing any progress
+information or calculating any ETAs. Useful if the program you are piping to
+or from requires extra information before it starts, eg piping data into
+.BR gpg (1)
+or
+.BR mcrypt (1)
+which require a passphrase before data can be processed.
+.TP
+.B \-D, \-\-delay-start SEC
+Wait until
+.B SEC
+seconds have passed before showing any progress information, for example in
+a script where you only want to show a progress bar if it starts taking a
+long time. Note that this can be a decimal such as 0.5.
+.TP
+.B \-s SIZE, \-\-size SIZE
+Assume the total amount of data to be transferred is
+.B SIZE
+bytes when calculating percentages and ETAs. The same suffixes of "k", "m"
+etc can be used as with
+.BR -L .
+.TP
+.B ""
+Has no effect if used with
+.B -d PID
+to watch all file descriptors of a process, but will work with
+.BR "-d PID:FD" .
+.TP
+.B \-l, \-\-line\-mode
+Instead of counting bytes, count lines (newline characters). The progress
+bar will only move when a new line is found, and the value passed to the
+.B \-s
+option will be interpreted as a line count. Note that file sizes are not
+automatically calculated when this option is used, to avoid having to read
+all files twice.
+.TP
+.B \-0, \-\-null
+Count lines as null terminated. This option implies \-\-line\-mode.
+.TP
+.B \-i SEC, \-\-interval SEC
+Wait
+.B SEC
+seconds between updates. The default is to update every second.
+Note that this can be a decimal such as 0.1.
+.TP
+.B \-w WIDTH, \-\-width WIDTH
+Assume the terminal is
+.B WIDTH
+characters wide, instead of trying to work it out (or assuming 80 if it
+cannot be guessed).
+.TP
+.B \-H HEIGHT, \-\-height HEIGHT
+Assume the terminal is
+.B HEIGHT
+rows high, instead of trying to work it out (or assuming 25 if it
+cannot be guessed).
+.TP
+.B \-N NAME, \-\-name NAME
+Prefix the output information with
+.BR NAME .
+Useful in conjunction with
+.B \-c
+if you have a complicated pipeline and you want to be able to tell different
+parts of it apart.
+.TP
+.B \-f, \-\-force
+Force output. Normally,
+.B @PACKAGE@
+will not output any visual display if standard error is not a terminal.
+This option forces it to do so.
+.TP
+.B \-c, \-\-cursor
+Use cursor positioning escape sequences instead of just using carriage
+returns. This is useful in conjunction with
+.B \-N
+(name) if you are using multiple
+.B @PACKAGE@
+invocations in a single, long, pipeline.
+
+
+.SH DATA TRANSFER MODIFIERS
+.TP
+.B \-L RATE, \-\-rate-limit RATE
+Limit the transfer to a maximum of
+.B RATE
+bytes per second. A suffix of "K", "M", "G", or "T" can be added to denote
+kibibytes (*1024), mebibytes, and so on.
+.TP
+.B \-B BYTES, \-\-buffer-size BYTES
+Use a transfer buffer size of
+.B BYTES
+bytes. A suffix of "K", "M", "G", or "T" can be added to denote
+kibibytes (*1024), mebibytes, and so on. The default buffer size is the
+block size of the input file's filesystem multiplied by 32 (512KiB max), or
+400KiB if the block size cannot be determined.
+.TP
+.B \-C, \-\-no-splice
+Never use
+.BR splice (2),
+even if it would normally be possible. The
+.BR splice (2)
+system call is a more efficient way of transferring data from or to a pipe
+than regular
+.BR read (2)
+and
+.BR write (2),
+but means that the transfer buffer may not be used. This prevents
+.B \-A
+and
+.B \-T
+from working, so if you want to use
+.B \-A
+or
+.B \-T
+then you will need to use
+.BR \-C ,
+at the cost of a small loss in transfer efficiency.
+(This option has no effect on systems where
+.BR splice (2)
+is unavailable).
+.TP
+.B \-E, \-\-skip-errors
+Ignore read errors by attempting to skip past the offending sections. The
+corresponding parts of the output will be null bytes. At first only a few
+bytes will be skipped, but if there are many errors in a row then the skips
+will move up to chunks of 512. This is intended to be similar to
+.B dd conv=sync,noerror
+but has not been as thoroughly tested.
+.TP
+.B ""
+Specify
+.B \-E
+twice to only report a read error once per file, instead of reporting each
+byte range skipped.
+.TP
+.B \-S, \-\-stop-at-size
+If a size was specified with
+.BR \-s ,
+stop transferring data once that many bytes have been written, instead of
+continuing to the end of input.
+.TP
+.B \-d PID[:FD], \-\-watchfd PID[:FD]
+Instead of transferring data, watch file descriptor
+.B FD
+of process
+.BR PID ,
+and show its progress. The
+.B @PACKAGE@
+process will exit when
+.B FD
+either changes to a different file, changes read/write mode, or is closed;
+other data transfer modifiers - and remote control - may not be used with
+this option.
+.TP
+.B ""
+If only a
+.B PID
+is specified, then that process will be watched, and all regular files and
+block devices it opens will be shown with a progress bar. The
+.B @PACKAGE@
+process will exit when process
+.B PID
+exits.
+.TP
+.B \-R PID, \-\-remote PID
+If
+.B PID
+is an instance of
+.B @PACKAGE@
+that is already running,
+.B \-R PID
+will cause that instance to act as though it had been given
+this instance's command line instead. For example, if
+.B @PACKAGE@ -L 123K
+is running with process ID 9876, then running
+.B @PACKAGE@ -R 9876 -L 321K
+will cause it to start using a rate limit of 321KiB instead of 123KiB.
+Note that some options cannot be changed while running, such as
+.BR \-c ,
+.BR \-l ,
+.BR \-f ,
+.BR \-D ,
+.BR \-E ,
+and
+.BR \-S .
+
+.SH GENERAL OPTIONS
+.TP
+.B \-P FILE, \-\-pidfile FILE
+Save the process ID of
+.B @PACKAGE@
+in
+.BR FILE .
+The file will be truncated if it already exists, and will be removed when
+.B @PACKAGE@
+exits. While
+.B @PACKAGE@
+is running, it will contain a single number - the process ID of
+.B @PACKAGE@
+- followed by a newline.
+.TP
+.B \-h, \-\-help
+Print a usage message on standard output and exit successfully.
+.TP
+.B \-V, \-\-version
+Print version information on standard output and exit successfully.
+
+
+.SH FORMATTING
+If the
+.B \-F
+option is given, then the output format is determined by the given format
+string. Within that string, the following sequences can be used:
+.TP
+.B %p
+Progress bar.
+Expands to fill the remaining space. Should only be specified once.
+Equivalent to
+.BR \-p .
+.TP
+.B %t
+Elapsed time. Equivalent to
+.BR \-t .
+.TP
+.B %e
+ETA as time remaining. Equivalent to
+.BR \-e .
+.TP
+.B %I
+ETA as local time of completion. Equivalent to
+.BR \-I .
+.TP
+.B %r
+Current data transfer rate. Equivalent to
+.BR \-r .
+.TP
+.B %a
+Average data transfer rate. Equivalent to
+.BR \-a .
+.TP
+.B %b
+Bytes transferred so far (or lines if
+.B \-l
+was specified). Equivalent to
+.BR \-b .
+.TP
+.B %T
+Percentage of the transfer buffer in use. Equivalent to
+.BR \-T .
+Shows "{----}" if the transfer is being done with
+.BR splice (2),
+since splicing to or from pipes does not use the buffer.
+.TP
+.B %nA
+Show the last
+.B n
+bytes written (e.g.
+.B %16A
+for the last 16 bytes).
+Shows only dots if the transfer is being done with
+.BR splice (2),
+since splicing to or from pipes does not use the buffer.
+.TP
+.B %N
+Name prefix given by
+.BR -N .
+Padded to 9 characters with spaces, and suffixed with :.
+.TP
+.B %%
+A single %.
+.P
+The format string equivalent of turning on all display switches is
+.BR "\`%N %b %T %t %r %a %p %e'" .
+
+
+.SH COMMON SWITCHES
+Some suggested common switch combinations:
+
+.TP
+.B @PACKAGE@ -ptebar
+Show a progress bar, elapsed time, estimated completion time, byte counter,
+average rate, and current rate.
+.TP
+.B @PACKAGE@ -betlap
+Show a progress bar, elapsed time, estimated completion time, line counter,
+and average rate, counting lines instead of bytes.
+.TP
+.B @PACKAGE@ -t
+Show only the elapsed time - useful as a simple timer, e.g.
+.BR "sleep 10m | pv -t" .
+.TP
+.B @PACKAGE@ -pterb
+The default behaviour: progress bar, elapsed time, estimated completion
+time, current rate, and byte counter.
+
+
+.SH EXIT STATUS
+An exit status of 1 indicates a problem with the
+.B \-R
+or
+.B \-P
+options.
+
+Any other exit status is a bitmask of the following:
+
+.TP
+.B 2
+One or more files could not be accessed,
+.BR stat (2)ed,
+or opened.
+.TP
+.B 4
+An input file was the same as the output file.
+.TP
+.B 8
+Internal error with closing a file or moving to the next file.
+.TP
+.B 16
+There was an error while transferring data from one or more input files.
+.TP
+.B 32
+A signal was caught that caused an early exit.
+.TP
+.B 64
+Memory allocation failed.
+
+A zero exit status indicates no problems.
+
+
+.SH AUTHOR
+Written by Andrew Wood, with patches submitted by various other people.
+Please see the package README for a complete list of contributors.
+
+
+.SH KNOWN PROBLEMS
+The following problems are known to exist in
+.BR @PACKAGE@ :
+.TP
+.B *
+The
+.B -c
+option does not work properly on Cygwin without
+.B cygserver
+running, if started near the bottom of the screen (IPC is needed to handle
+the terminal scrolling). To fix this, start
+.B cygserver
+before using
+.BR "@PACKAGE@ -c" .
+.P
+.TP
+.B *
+The
+.B -R
+option is not available on Cygwin without
+.B cygserver
+running (SYSV IPC is needed). To fix this, start
+.B cygserver
+before running the instance of
+.B @PACKAGE@
+you want, at runtime, to change the parameters of.
+.P
+If you find any other problems, please report them.
+
+
+.SH REPORTING BUGS
+Report bugs in
+.B @PACKAGE@
+to pv@ivarch.com or use the contact form linked from the
+.B @PACKAGE@
+home page:
+
+
+.SH "SEE ALSO"
+.BR cat (1),
+.BR dialog (1),
+.BR splice (2)
+
+
+.SH LICENSE
+This is free software, distributed under the ARTISTIC 2.0 license.
diff --git a/doc/release-checklist b/doc/release-checklist
new file mode 100644
index 0000000..6215e2e
--- /dev/null
+++ b/doc/release-checklist
@@ -0,0 +1,20 @@
+Before releasing a new version, go through this checklist:
+
+ - check for patches from http://packages.qa.debian.org/p/pv.html
+ - bump doc/VERSION
+ - bump doc/lsm.in
+ - check doc/NEWS is up to date
+ - check doc/spec.in is up to date (changelog)
+ - check manual is up to date
+ - make indent indentclean
+ - make update-po
+ - make test
+ - commit
+ - wipe build directory, re-run generate.sh and configure
+ - make release
+ - make manhtml | tidy -asxhtml | sed -e '1,//d' -e '/<\/body>/,$d'
+ - update HTML for todo and news
+ - copy and sign tar.gz to HTML directory
+ - submit new release
+ - upload HTML
+ - check validator results for project page and manual
diff --git a/doc/spec.in b/doc/spec.in
new file mode 100644
index 0000000..51fb4f9
--- /dev/null
+++ b/doc/spec.in
@@ -0,0 +1,249 @@
+Summary: Monitor the progress of data through a pipe
+Name: @PACKAGE@
+Version: @VERSION@
+Release: 1%{?dist}
+License: Artistic 2.0
+Group: Development/Tools
+Source: http://www.ivarch.com/programs/sources/@PACKAGE@-@VERSION@.tar.gz
+Url: http://www.ivarch.com/programs/pv.shtml
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+BuildRequires: gettext
+
+%description
+PV ("Pipe Viewer") is a tool for monitoring the progress of data through a
+pipeline. It can be inserted into any normal pipeline between two processes
+to give a visual indication of how quickly data is passing through, how long
+it has taken, how near to completion it is, and an estimate of how long it
+will be until completion.
+
+%prep
+%setup -q
+
+%build
+%configure
+make %{?_smp_mflags}
+
+%install
+[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"
+mkdir -p "$RPM_BUILD_ROOT"%{_bindir}
+mkdir -p "$RPM_BUILD_ROOT"%{_mandir}/man1
+mkdir -p "$RPM_BUILD_ROOT"/usr/share/locale
+
+make DESTDIR="$RPM_BUILD_ROOT" install
+%find_lang %{name}
+
+%check
+make test
+
+%clean
+[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"
+
+%files -f %{name}.lang
+%defattr(-, root, root)
+%{_bindir}/%{name}
+%{_mandir}/man1/%{name}.1.gz
+
+%doc README doc/NEWS doc/TODO doc/COPYING
+
+%changelog
+* Fri Jun 30 2017 Andrew Wood 1.6.6-1
+- (r161) use %llu instead of %Lu for better compatibility (Eric A. Borisch)
+- (r162) (#1532) fix target buffer size (-B) being ignored (AndCycle, Ilya
+- Basin, Antoine Beaupré)
+- (r164) cap read/write sizes, and check elapsed time during read/write
+- cycles, to avoid display hangs with large buffers or slow media; also
+- remove select() call from repeated_write function as it slows the
+- transfer down and the wrapping alarm() means it is unnecessary
+- (r169) (#1477) use alternate form for transfer counter, such that 13GB
+- is shown as 13.0GB so it's the same width as 13.1GB (André Stapf)
+- (r171) cleanup: units corrections in man page, of the form kb -> KiB
+- (r175) report error in "-d" if process fd directory is unreadable, or if
+- process disappears before we start the main loop (Jacek Wielemborek)
+
+* Sun Mar 15 2015 Andrew Wood 1.6.0-1
+- fix lstat64 support when unavailable - separate patches supplied by Ganael
+- Laplanche and Peter Korsgaard
+- (#1506) new option "-D" / "--delay-start" to only show bar after N seconds
+- (Damon Harper)
+- new option "--fineta" / "-I" to show ETA as time of day rather than time
+- remaining - patch supplied by Erkki Seppälä (r147)
+- (#1509) change ETA (--eta / -e) so that days are given if the hours
+- remaining are 24 or more (Jacek Wielemborek)
+- (#1499) repeat read and write attempts on partial buffer fill/empty to
+- work around post-signal transfer rate drop reported by Ralf Ramsauer
+- (#1507) do not try to calculate total size in line mode, due to bug
+- reported by Jacek Wielemborek and Michiel Van Herwegen
+- cleanup: removed defunct RATS comments and unnecessary copyright notices
+- clean up displayed lines when using --watchfd PID, when PID exits
+- output errors on a new line to avoid overwriting transfer bar
+
+* Tue Aug 26 2014 Andrew Wood 1.5.7-1
+- show KiB instead of incorrect kiB (Debian bug #706175)
+- (#1284) do not gzip man page, for non-Linux OSes (Bob Friesenhahn)
+- work around "awk" bug in tests/016-numeric-timer in decimal "," locales
+- fix "make rpm" and "make srpm", extend "make release" to sign releases
+
+* Sun May 4 2014 Andrew Wood 1.5.3-1
+- remove SPLICE_F_NONBLOCK to fix problem with slow splice() (Jan Seda)
+
+* Mon Feb 10 2014 Andrew Wood 1.5.2-1
+- allow "--watchfd" to look at block devices
+- let "--watchfd PID:FD" work with "--size N"
+- moved contributors out of the manual as the list was too long
+- (NB everyone is still listed in the README and always will be)
+
+* Thu Jan 23 2014 Andrew Wood 1.5.1-1
+- new option "--watchfd" - suggested by Jacek Wielemborek and "fdwatch"
+- use non-block flag with splice()
+- new display option "--buffer-percent", suggested by Kim Krecht
+- new display option "--last-written", suggested by Kim Krecht
+- new transfer option "--no-splice"
+- fix for minor bug which dropped display elements after one empty one
+- fix for single fd leak on exit (Cristian Ciupitu, Josh Stone)
+
+* Mon Aug 5 2013 Andrew Wood 1.4.12-1
+- new option "--null" - patch supplied by Zing Shishak
+- AIX build fix (add "-lc128") - with help from Pawel Piatek
+- AIX "-c" fixes - with help from Pawel Piatek
+- SCO build fix (po2table.sh) - reported by Wouter Pronk
+- test scripts fix for older distributions - patch from Bryan Dongray
+- fix for splice() not using stdin - patch from Zev Weiss
+
+* Tue Jan 22 2013 Andrew Wood 1.4.6-1
+- added patch from Pawel Piatek to omit O_NOFOLLOW in AIX
+
+* Thu Jan 10 2013 Andrew Wood 1.4.5-1
+- updated manual page to show known problem with "-R" on Cygwin
+
+* Tue Dec 11 2012 Andrew Wood 1.4.4-1
+- added debugging, see `pv -h' when configure run with "--enable-debugging"
+- rewrote cursor positioning code used when IPC is unavailable (Cygwin)
+- fixed cursor positioning cursor read answerback problem (Cygwin/Solaris)
+- fixed bug causing crash when progress displayed with too-small terminal
+
+* Thu Dec 6 2012 Andrew Wood 1.4.0-1
+- new option "--skip-errors" commissioned by Jim Salter
+- if stdout is a block device, and we don't know the total size, use the
+- size of that block device as the total (Peter Samuelson)
+- new option "--stop-at-size" to stop after "--size" bytes
+- report correct filename on read errors
+- fix use-after-free bug in remote PID cleanup code
+- refactored large chunks of code to make it more readable and to replace
+- most static variables with a state structure
+
+* Mon Nov 5 2012 Andrew Wood 1.3.9-1
+- allow "--format" parameters to be sent with "--remote"
+- configure option "--disable-ipc"
+- added tests for --numeric with --timer and --bytes
+- added tests for --remote
+
+* Mon Oct 29 2012 Andrew Wood 1.3.8-1
+- new "--pidfile" option to save process ID to a file
+- integrated patch for --numeric with --timer and --bytes (Sami Liedes)
+- removed signalling from --remote to prevent accidental process kills
+- new "--format" option (originally Vladimir Pal / Vladimir Ermakov)
+
+* Wed Jun 27 2012 Andrew Wood 1.3.4-1
+- new "--disable-splice" configure script option
+- fixed line mode size count with multiple files (Moritz Barsnick)
+- fixes for AIX core dumps (Pawel Piatek)
+
+* Sat Jun 9 2012 Andrew Wood 1.3.1-1
+- do not use splice() if the write buffer is not empty (Thomas Rachel)
+- added test 15 (pipe transfers), and new test script
+
+* Tue Jun 5 2012 Andrew Wood 1.3.0-1
+- added Tiger build patch from Olle Jonsson.
+- fix 1024-boundary display garble (Debian bug #586763).
+- use splice(2) where available (Debian bug #601683).
+- added known bugs section of the manual page.
+- fixed average rate test, 12 (Andrew Macheret).
+- use IEEE1541 units (Thomas Rachel).
+- bug with rate limit under 10 fixed (Henry Precheur).
+- speed up PV line mode (patch: Guillaume Marcais).
+- remove LD=ld from vars.mk to fix cross-compilation (paintitgray/PV#1291).
+
+* Tue Dec 14 2010 Andrew Wood 1.2.0-1
+- Integrated improved SI prefixes and --average-rate (Henry Gebhardt).
+- Return nonzero if exiting due to SIGTERM (Martin Baum).
+- Patch from Phil Rutschman to restore terminal properly on exit.
+- Fix i18n especially for --help (Sebastian Kayser).
+- Refactored pv_display.
+- We now have a coherent, documented, exit status.
+- Modified pipe test and new cksum test from Sebastian Kayser.
+- Default CFLAGS to just "-O" for non-GCC (Kjetil Torgrim Homme).
+- LFS compile fix for OS X 10.4 (Alexandre de Verteuil).
+- Remove DESTDIR / suffix (Sam Nelson, Daniel Pape).
+- Fixed potential NULL deref in transfer (Elias Pipping / LLVM/Clang).
+
+* Thu Mar 6 2008 Andrew Wood 1.1.4-1
+- Trap SIGINT/SIGHUP/SIGTERM so we clean up IPCs on exit (Laszlo Ersek).
+- Abort if numeric option, eg -L, has non-numeric value (Boris Lohner).
+- Compilation fixes for Darwin 9 and OS X.
+
+* Thu Aug 30 2007 Andrew Wood 1.1.0-1
+- New option "-R" to remotely control another @PACKAGE@ process.
+- New option "-l" to count lines instead of bytes.
+- Performance improvement for "-L" (rate) option.
+- Some Mac OS X fixes, and packaging cleanups.
+
+* Sat Aug 4 2007 Andrew Wood 1.0.1-1
+- Changed license from Artistic to Artistic 2.0.
+- Removed "--license" option.
+
+* Thu Aug 2 2007 Andrew Wood 1.0.0-1
+- We now act more like "cat" - just skip unreadable files, don't abort.
+- Various code cleanups were done.
+
+* Mon Feb 5 2007 Andrew Wood 0.9.9-1
+- New option "-B" to set the buffer size, and a workaround for problems
+- piping to dd(1).
+
+* Mon Feb 27 2006 Andrew Wood
+- Minor bugfixes, and on the final update, blank out the now-zero ETA.
+
+* Thu Sep 1 2005 Andrew Wood
+- Terminal locking now uses lockfiles if the terminal itself cannot be locked.
+
+* Thu Jun 16 2005 Andrew Wood
+- A minor problem with the spec file was fixed.
+
+* Mon Nov 15 2004 Andrew Wood
+- A minor bug in the NLS code was fixed.
+
+* Sat Nov 6 2004 Andrew Wood
+- Code cleanups and minor usability fixes.
+
+* Tue Jun 29 2004 Andrew Wood
+- A port of the terminal locking code to FreeBSD.
+
+* Sun May 2 2004 Andrew Wood
+- Major reliability improvements to the cursor positioning.
+
+* Sat Apr 24 2004 Andrew Wood
+- Rate and size parameters can now take suffixes such as "k", "m" etc.
+
+* Mon Apr 19 2004 Andrew Wood
+- A bug in the cursor positioning was fixed.
+
+* Thu Feb 12 2004 Andrew Wood
+- Code cleanups and portability fixes.
+
+* Sun Feb 8 2004 Andrew Wood
+- The display buffer is now dynamically allocated, fixing an overflow bug.
+
+* Wed Jan 14 2004 Andrew Wood
+- A minor bug triggered when installing the RPM was fixed.
+
+* Mon Dec 22 2003 Andrew Wood
+- Fixed a minor bug that occasionally reported "resource unavailable".
+
+* Wed Aug 6 2003 Andrew Wood
+- Block devices now have their size read correctly, so pv /dev/hda1 works
+- Minor code cleanups (mainly removal of CVS "Id" tags)
+
+* Sun Aug 3 2003 Andrew Wood
+- Doing ^Z then "bg" then "fg" now continues displaying
+
+* Tue Jul 16 2002 Andrew Wood
+- First draft of spec file created.
diff --git a/generate.sh b/generate.sh
new file mode 100755
index 0000000..4ed5c19
--- /dev/null
+++ b/generate.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Script to autogenerate the `configure' script, and rebuild the dependency
+# list.
+
+set -e
+
+cd `dirname $0`
+
+cd autoconf
+autoconf configure.in > ../configure
+cd ..
+chmod 755 configure
+
+echo > autoconf/make/depend.mk~
+echo > autoconf/make/filelist.mk~
+echo > autoconf/make/modules.mk~
+
+MAKE=make
+which gmake >/dev/null 2>&1 && MAKE=gmake
+
+rm -rf .gen
+mkdir .gen
+cd .gen
+sh ../configure
+$MAKE make
+$MAKE dep
+cd ..
+rm -rf .gen
+rm -rf autoconf/autom4te.cache
+
+# EOF
diff --git a/src/include/library/getopt.h b/src/include/library/getopt.h
new file mode 100644
index 0000000..0f468b1
--- /dev/null
+++ b/src/include/library/getopt.h
@@ -0,0 +1,36 @@
+/*
+ * Replacement getopt function's header file. Include this AFTER config.h.
+ */
+
+#ifndef _LIBRARY_GETOPT_H
+#define _LIBRARY_GETOPT_H 1
+
+#ifdef HAVE_GETOPT_H
+#include
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef HAVE_GETOPT
+
+int minigetopt(int, char **, char *);
+extern char *minioptarg;
+extern int minioptind, miniopterr, minioptopt;
+
+#define getopt minigetopt /* Flawfinder: ignore */
+#define optarg minioptarg
+#define optind minioptind
+#define opterr miniopterr
+#define optopt minioptopt
+
+#endif /* !HAVE_GETOPT */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBRARY_GETOPT_H */
+
+/* EOF */
diff --git a/src/include/library/gettext.h b/src/include/library/gettext.h
new file mode 100644
index 0000000..fef5ffa
--- /dev/null
+++ b/src/include/library/gettext.h
@@ -0,0 +1,52 @@
+/*
+ * Replacement gettext library header file. Include this within config.h,
+ * like this:
+ *
+ * #ifdef ENABLE_NLS
+ * # include "library/gettext.h"
+ * #else
+ * # define _(String) (String)
+ * # define N_(String) (String)
+ * #endif
+ *
+ */
+
+#ifndef _LIBRARY_GETTEXT_H
+#define _LIBRARY_GETTEXT_H 1
+
+#ifdef HAVE_GETTEXT
+# ifdef HAVE_LIBINTL_H
+# include
+# endif
+# ifdef HAVE_LOCALE_H
+# include
+# endif
+# define _(String) gettext (String)
+# define N_(String) (String)
+#else
+# define _(String) minigettext (String)
+# define N_(String) (String)
+# define setlocale minisetlocale
+# define bindtextdomain minibindtextdomain
+# define textdomain minitextdomain
+# ifndef LC_ALL
+# define LC_ALL ""
+# endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+char *minisetlocale(char *, char *);
+char *minibindtextdomain(char *, char *);
+char *minitextdomain(char *);
+char *minigettext(char *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBRARY_GETTEXT_H */
+
+/* EOF */
diff --git a/src/include/options.h b/src/include/options.h
new file mode 100644
index 0000000..0f8029e
--- /dev/null
+++ b/src/include/options.h
@@ -0,0 +1,64 @@
+/*
+ * Global program option structure and the parsing function prototype.
+ */
+
+#ifndef _OPTIONS_H
+#define _OPTIONS_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct opts_s;
+typedef struct opts_s *opts_t;
+
+struct opts_s { /* structure describing run-time options */
+ char *program_name; /* name the program is running as */
+ unsigned char do_nothing; /* exit-without-doing-anything flag */
+ unsigned char progress; /* progress bar flag */
+ unsigned char timer; /* timer flag */
+ unsigned char eta; /* ETA flag */
+ unsigned char fineta; /* absolute ETA flag */
+ unsigned char rate; /* rate counter flag */
+ unsigned char average_rate; /* average rate counter flag */
+ unsigned char bytes; /* bytes transferred flag */
+ unsigned char bufpercent; /* transfer buffer percentage flag */
+ unsigned int lastwritten; /* show N bytes last written */
+ unsigned char force; /* force-if-not-terminal flag */
+ unsigned char cursor; /* whether to use cursor positioning */
+ unsigned char numeric; /* numeric output only */
+ unsigned char wait; /* wait for transfer before display */
+ unsigned char linemode; /* count lines instead of bytes */
+ unsigned char null; /* lines are null-terminated */
+ unsigned char no_op; /* do nothing other than pipe data */
+ unsigned long long rate_limit; /* rate limit, in bytes per second */
+ unsigned long long buffer_size;/* buffer size, in bytes (0=default) */
+ unsigned int remote; /* PID of pv to update settings of */
+ unsigned long long size; /* total size of data */
+ unsigned char no_splice; /* flag set if never to use splice */
+ unsigned char skip_errors; /* skip read errors flag */
+ unsigned char stop_at_size; /* set if we stop at "size" bytes */
+ double interval; /* interval between updates */
+ double delay_start; /* delay before first display */
+ unsigned int watch_pid; /* process to watch fds of */
+ int watch_fd; /* fd to watch */
+ unsigned int width; /* screen width */
+ unsigned int height; /* screen height */
+ char *name; /* process name, if any */
+ char *format; /* output format, if any */
+ char *pidfile; /* PID file, if any */
+ int argc; /* number of non-option arguments */
+ char **argv; /* array of non-option arguments */
+};
+
+extern opts_t opts_parse(int, char **);
+extern void opts_free(opts_t);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _OPTIONS_H */
+
+/* EOF */
diff --git a/src/include/pv-internal.h b/src/include/pv-internal.h
new file mode 100644
index 0000000..46d7496
--- /dev/null
+++ b/src/include/pv-internal.h
@@ -0,0 +1,263 @@
+/*
+ * Functions internal to the PV library.
+ */
+
+#ifndef _PV_INTERNAL_H
+#define _PV_INTERNAL_H 1
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef _PV_H
+#include "pv.h"
+#endif /* _PV_H */
+
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PV_DISPLAY_PROGRESS 1
+#define PV_DISPLAY_TIMER 2
+#define PV_DISPLAY_ETA 4
+#define PV_DISPLAY_RATE 8
+#define PV_DISPLAY_AVERAGERATE 16
+#define PV_DISPLAY_BYTES 32
+#define PV_DISPLAY_NAME 64
+#define PV_DISPLAY_BUFPERCENT 128
+#define PV_DISPLAY_OUTPUTBUF 256
+#define PV_DISPLAY_FINETA 512
+
+#define RATE_GRANULARITY 100000 /* usec between -L rate chunks */
+#define REMOTE_INTERVAL 100000 /* usec between checks for -R */
+#define BUFFER_SIZE 409600 /* default transfer buffer size */
+#define BUFFER_SIZE_MAX 524288 /* max auto transfer buffer size */
+#define MAX_READ_AT_ONCE 524288 /* max to read() in one go */
+#define MAX_WRITE_AT_ONCE 524288 /* max to write() in one go */
+#define TRANSFER_READ_TIMEOUT 90000 /* usec to time reads out at */
+#define TRANSFER_WRITE_TIMEOUT 900000 /* usec to time writes out at */
+
+#define MAXIMISE_BUFFER_FILL 1
+
+
+/*
+ * Structure for holding PV internal state. Opaque outside the PV library.
+ */
+struct pvstate_s {
+ /***************
+ * Input files *
+ ***************/
+ int input_file_count; /* number of input files */
+ const char **input_files; /* input files (0=first) */
+
+ /*******************
+ * Program control *
+ *******************/
+ unsigned char force; /* display even if not on terminal */
+ unsigned char cursor; /* use cursor positioning */
+ unsigned char numeric; /* numeric output only */
+ unsigned char wait; /* wait for data before display */
+ unsigned char linemode; /* count lines instead of bytes */
+ unsigned char null; /* lines are null-terminated */
+ unsigned char no_op; /* do nothing other than pipe data */
+ unsigned char skip_errors; /* skip read errors flag */
+ unsigned char stop_at_size; /* set if we stop at "size" bytes */
+ unsigned char no_splice; /* never use splice() */
+ unsigned long long rate_limit; /* rate limit, in bytes per second */
+ unsigned long long target_buffer_size; /* buffer size (0=default) */
+ unsigned long long size; /* total size of data */
+ double interval; /* interval between updates */
+ double delay_start; /* delay before first display */
+ unsigned int watch_pid; /* process to watch fds of */
+ int watch_fd; /* fd to watch */
+ unsigned int width; /* screen width */
+ unsigned int height; /* screen height */
+ const char *name; /* display name */
+ char default_format[512]; /* default format string */
+ const char *format_string; /* output format string */
+
+ /******************
+ * Program status *
+ ******************/
+ const char *program_name; /* program name for error reporting */
+ const char *current_file; /* current file being read */
+ int exit_status; /* exit status to give (0=OK) */
+
+ /*******************
+ * Signal handling *
+ *******************/
+ int pv_sig_old_stderr; /* see pv_sig_ttou() */
+ struct timeval pv_sig_tstp_time; /* see pv_sig_tstp() / __cont() */
+ struct timeval pv_sig_toffset; /* total time spent stopped */
+ volatile sig_atomic_t pv_sig_newsize; /* whether we need to get term size again */
+ volatile sig_atomic_t pv_sig_abort; /* whether we need to abort right now */
+ volatile sig_atomic_t reparse_display; /* whether to re-check format string */
+ struct sigaction pv_sig_old_sigpipe;
+ struct sigaction pv_sig_old_sigttou;
+ struct sigaction pv_sig_old_sigtstp;
+ struct sigaction pv_sig_old_sigcont;
+ struct sigaction pv_sig_old_sigwinch;
+ struct sigaction pv_sig_old_sigint;
+ struct sigaction pv_sig_old_sighup;
+ struct sigaction pv_sig_old_sigterm;
+
+ /*****************
+ * Display state *
+ *****************/
+ long percentage;
+ long double prev_elapsed_sec;
+ long double prev_rate;
+ long double prev_trans;
+ unsigned long long initial_offset;
+ char *display_buffer;
+ long display_buffer_size;
+ int lastoutput_length; /* number of last-output bytes to show */
+ unsigned char lastoutput_buffer[256];
+ int prev_width; /* screen width last time we were called */
+ int prev_length; /* length of last string we output */
+ char str_name[512];
+ char str_transferred[128];
+ char str_bufpercent[128];
+ char str_timer[128];
+ char str_rate[128];
+ char str_average_rate[128];
+ char str_progress[1024];
+ char str_lastoutput[512];
+ char str_eta[128];
+ char str_fineta[128];
+ unsigned long components_used; /* bitmask of components used */
+ struct {
+ const char *string;
+ int length;
+ } format[100];
+ unsigned char display_visible; /* set once anything written to terminal */
+
+ /********************
+ * Cursor/IPC state *
+ ********************/
+#ifdef HAVE_IPC
+ int crs_shmid; /* ID of our shared memory segment */
+ int crs_pvcount; /* number of `pv' processes in total */
+ int crs_pvmax; /* highest number of `pv's seen */
+ int *crs_y_top; /* pointer to Y coord of topmost `pv' */
+ int crs_y_lastread; /* last value of _y_top seen */
+ int crs_y_offset; /* our Y offset from this top position */
+ int crs_needreinit; /* set if we need to reinit cursor pos */
+ int crs_noipc; /* set if we can't use IPC */
+#endif /* HAVE_IPC */
+ int crs_lock_fd; /* fd of lockfile, -1 if none open */
+ char crs_lock_file[1024];
+ int crs_y_start; /* our initial Y coordinate */
+
+ /*******************
+ * Transfer state *
+ *******************/
+ /*
+ * The transfer buffer is used for moving data from the input files
+ * to the output when splice() is not available.
+ *
+ * If buffer_size is smaller than pv__target_bufsize, then
+ * pv_transfer will try to reallocate transfer_buffer to make
+ * buffer_size equal to pv__target_bufsize.
+ *
+ * Data from the input files is read into the buffer; read_position
+ * is the offset in the buffer that we've read data up to.
+ *
+ * Data is written to the output from the buffer, and write_position
+ * is the offset in the buffer that we've written data up to. It
+ * will always be less than or equal to read_position.
+ */
+ unsigned char *transfer_buffer; /* data transfer buffer */
+ unsigned long long buffer_size; /* size of buffer */
+ unsigned long read_position; /* amount of data in buffer */
+ unsigned long write_position; /* buffered data written */
+
+ /*
+ * While reading from a file descriptor we keep track of how many
+ * times in a row we've seen errors (read_errors_in_a_row), and
+ * whether or not we have put a warning on stderr about read errors
+ * on this fd (read_error_warning_shown).
+ *
+ * Whenever the active file descriptor changes from
+ * last_read_skip_fd, we reset read_errors_in_a_row and
+ * read_error_warning_shown to 0 for the new file descriptor and set
+ * last_read_skip_fd to the new fd number.
+ *
+ * This way, we're treating each input file separately.
+ */
+ int last_read_skip_fd;
+ unsigned long read_errors_in_a_row;
+ int read_error_warning_shown;
+#ifdef HAVE_SPLICE
+ /*
+ * These variables are used to keep track of whether splice() was
+ * used; splice_failed_fd is the file descriptor that splice() last
+ * failed on, so that we don't keep trying to use it on an fd that
+ * doesn't support it, and splice_used is set to 1 if splice() was
+ * used this time within pv_transfer().
+ */
+ int splice_failed_fd;
+ int splice_used;
+#endif
+ long to_write; /* max to write this time around */
+ long written; /* bytes sent to stdout this time */
+};
+
+
+struct pvwatchfd_s {
+ unsigned int watch_pid; /* PID to watch */
+ int watch_fd; /* fd to watch, -1 = not displayed */
+ char file_fdinfo[4096]; /* path to /proc fdinfo file */
+ char file_fd[4096]; /* path to /proc fd symlink */
+ char file_fdpath[4096]; /* path to file that was opened */
+ char display_name[512]; /* name to show on progress bar */
+ struct stat64 sb_fd; /* stat of fd symlink */
+ struct stat64 sb_fd_link; /* lstat of fd symlink */
+ unsigned long long size; /* size of whole file, 0 if unknown */
+ long long position; /* position last seen at */
+ struct timeval start_time; /* time we started watching the fd */
+};
+typedef struct pvwatchfd_s *pvwatchfd_t;
+
+void pv_error(pvstate_t, char *, ...);
+
+int pv_main_loop(pvstate_t);
+void pv_display(pvstate_t, long double, long long, long long);
+long pv_transfer(pvstate_t, int, int *, int *, unsigned long long, long *);
+void pv_set_buffer_size(unsigned long long, int);
+int pv_next_file(pvstate_t, int, int);
+
+void pv_crs_fini(pvstate_t);
+void pv_crs_init(pvstate_t);
+void pv_crs_update(pvstate_t, char *);
+#ifdef HAVE_IPC
+void pv_crs_needreinit(pvstate_t);
+#endif
+
+void pv_sig_allowpause(void);
+void pv_sig_checkbg(void);
+void pv_sig_nopause(void);
+
+void pv_remote_init(pvstate_t);
+void pv_remote_check(pvstate_t);
+void pv_remote_fini(pvstate_t);
+int pv_remote_set(pvstate_t);
+
+int pv_watchfd_info(pvstate_t, pvwatchfd_t, int);
+int pv_watchfd_changed(pvwatchfd_t);
+long long pv_watchfd_position(pvwatchfd_t);
+int pv_watchpid_scanfds(pvstate_t, pvstate_t, unsigned int, int *, pvwatchfd_t *, pvstate_t *, int *);
+void pv_watchpid_setname(pvstate_t, pvwatchfd_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PV_INTERNAL_H */
+
+/* EOF */
diff --git a/src/include/pv.h b/src/include/pv.h
new file mode 100644
index 0000000..7a3970c
--- /dev/null
+++ b/src/include/pv.h
@@ -0,0 +1,165 @@
+/*
+ * Functions used across the program.
+ */
+
+#ifndef _PV_H
+#define _PV_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Opaque structure for PV internal state.
+ */
+struct pvstate_s;
+typedef struct pvstate_s *pvstate_t;
+
+/*
+ * Valid number types for pv_getnum_check().
+ */
+typedef enum {
+ PV_NUMTYPE_INTEGER,
+ PV_NUMTYPE_DOUBLE
+} pv_numtype_t;
+
+
+/*
+ * Simple string functions for processing numbers.
+ */
+
+/*
+ * Return the given string converted to a double.
+ */
+extern double pv_getnum_d(const char *);
+
+/*
+ * Return the given string converted to an integer.
+ */
+extern int pv_getnum_i(const char *);
+
+/*
+ * Return the given string converted to a long long.
+ */
+extern long long pv_getnum_ll(const char *);
+
+/*
+ * Return zero if the given string is a number of the given type. NB an
+ * integer is both a valid integer and a valid double.
+ */
+extern int pv_getnum_check(const char *, pv_numtype_t);
+
+/*
+ * Main PV functions.
+ */
+
+/*
+ * Create a new state structure, and return it, or 0 (NULL) on error.
+ */
+extern pvstate_t pv_state_alloc(const char *);
+
+/*
+ * Set the formatting string, given a set of old-style formatting options.
+ */
+extern void pv_state_set_format(pvstate_t state, unsigned char progress,
+ unsigned char timer, unsigned char eta,
+ unsigned char fineta, unsigned char rate,
+ unsigned char average_rate, unsigned char bytes,
+ unsigned char bufpercent,
+ unsigned int lastwritten,
+ const char *name);
+
+/*
+ * Set the various options.
+ */
+extern void pv_state_force_set(pvstate_t, unsigned char);
+extern void pv_state_cursor_set(pvstate_t, unsigned char);
+extern void pv_state_numeric_set(pvstate_t, unsigned char);
+extern void pv_state_wait_set(pvstate_t, unsigned char);
+extern void pv_state_delay_start_set(pvstate_t, double);
+extern void pv_state_linemode_set(pvstate_t, unsigned char);
+extern void pv_state_null_set(pvstate_t, unsigned char);
+extern void pv_state_no_op_set(pvstate_t, unsigned char);
+extern void pv_state_skip_errors_set(pvstate_t, unsigned char);
+extern void pv_state_stop_at_size_set(pvstate_t, unsigned char);
+extern void pv_state_rate_limit_set(pvstate_t, unsigned long long);
+extern void pv_state_target_buffer_size_set(pvstate_t, unsigned long long);
+extern void pv_state_no_splice_set(pvstate_t, unsigned char);
+extern void pv_state_size_set(pvstate_t, unsigned long long);
+extern void pv_state_interval_set(pvstate_t, double);
+extern void pv_state_width_set(pvstate_t, unsigned int);
+extern void pv_state_height_set(pvstate_t, unsigned int);
+extern void pv_state_name_set(pvstate_t, const char *);
+extern void pv_state_format_string_set(pvstate_t, const char *);
+extern void pv_state_watch_pid_set(pvstate_t, unsigned int);
+extern void pv_state_watch_fd_set(pvstate_t, int);
+
+extern void pv_state_inputfiles(pvstate_t, int, const char **);
+
+/*
+ * Work out the terminal size.
+ */
+extern void pv_screensize(unsigned int *width, unsigned int *height);
+
+/*
+ * Calculate the total size of all input files.
+ */
+extern unsigned long long pv_calc_total_size(pvstate_t);
+
+/*
+ * Set up signal handlers ready for running the main loop.
+ */
+extern void pv_sig_init(pvstate_t);
+
+/*
+ * Enter the main transfer loop, transferring all input files to the output.
+ */
+extern int pv_main_loop(pvstate_t);
+
+/*
+ * Watch the selected file descriptor of the selected process.
+ */
+extern int pv_watchfd_loop(pvstate_t);
+
+/*
+ * Watch the selected process.
+ */
+extern int pv_watchpid_loop(pvstate_t);
+
+/*
+ * Shut down signal handlers after running the main loop.
+ */
+extern void pv_sig_fini(pvstate_t);
+
+/*
+ * Free a state structure, after which it can no longer be used.
+ */
+extern void pv_state_free(pvstate_t);
+
+
+#ifdef ENABLE_DEBUGGING
+# if __STDC_VERSION__ < 199901L
+# if __GNUC__ >= 2
+# define __func__ __FUNCTION__
+# else
+# define __func__ ""
+# endif
+# endif
+# define debug(x,...) debugging_output(__func__, __FILE__, __LINE__, x, __VA_ARGS__)
+#else
+# define debug(x,...) do { } while (0)
+#endif
+
+/*
+ * Output debugging information, if debugging is enabled.
+ */
+void debugging_output(const char *, const char *, int, const char *, ...);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PV_H */
+
+/* EOF */
diff --git a/src/library/getopt.c b/src/library/getopt.c
new file mode 100644
index 0000000..fe53a15
--- /dev/null
+++ b/src/library/getopt.c
@@ -0,0 +1,113 @@
+/*
+ * Small reimplementation of getopt().
+ */
+
+#include "config.h"
+#include
+#include
+#include
+
+#ifndef HAVE_GETOPT
+
+char *minioptarg = NULL;
+int minioptind = 0;
+int miniopterr = 1;
+int minioptopt = 0;
+
+
+/*
+ * Minimalist getopt() clone, which handles short options only and doesn't
+ * permute argv[].
+ */
+int minigetopt(int argc, char **argv, char *optstring)
+{
+ static int nextchar = 0;
+ int optchar;
+ int i;
+
+ if ((0 == minioptind) && (argc > 0))
+ minioptind++;
+
+ if ((nextchar > 0) && (0 == argv[minioptind][nextchar])) {
+ minioptind++;
+ nextchar = 0;
+ }
+
+ if (minioptind >= argc)
+ return -1;
+
+ /*
+ * End of options if arg doesn't start with "-"
+ */
+ if (argv[minioptind][0] != '-')
+ return -1;
+
+ /*
+ * End of options if arg is just "-"
+ */
+ if (0 == argv[minioptind][1])
+ return -1;
+
+ /*
+ * End of options if arg is "--", but don't include the "--" in the
+ * non-option arguments
+ */
+ if (('-' == argv[minioptind][1]) && (0 == argv[minioptind][2])) {
+ minioptind++;
+ return -1;
+ }
+
+ if (0 == nextchar)
+ nextchar = 1;
+
+ optchar = argv[minioptind][nextchar++];
+
+ for (i = 0; optstring[i] != 0 && optstring[i] != optchar; i++) {
+ }
+
+ if (0 == optstring[i]) {
+ minioptopt = optchar;
+ if (miniopterr)
+ fprintf(stderr, "%s: invalid option -- %c\n",
+ argv[0], optchar);
+ return '?';
+ }
+
+ if (optstring[i + 1] != ':') {
+ minioptarg = NULL;
+ return optchar;
+ }
+
+ /*
+ * At this point we've got an option that takes an argument.
+ */
+
+ /*
+ * Next character isn't 0, so the argument is within this array
+ * element (i.e. "-dFOO").
+ */
+ if (argv[minioptind][nextchar] != 0) {
+ minioptarg = &(argv[minioptind][nextchar]);
+ nextchar = 0;
+ minioptind++;
+ return optchar;
+ }
+
+ /*
+ * Argument is in the next array element (i.e. "-d FOO").
+ */
+ nextchar = 0;
+ minioptind++;
+ if (minioptind >= argc) {
+ fprintf(stderr, "%s: option `-%c' requires an argument\n",
+ argv[0], optchar);
+ return ':';
+ }
+ minioptarg = argv[minioptind++];
+
+ return optchar;
+}
+
+#endif /* HAVE_GETOPT */
+
+/* EOF */
diff --git a/src/library/gettext.c b/src/library/gettext.c
new file mode 100644
index 0000000..7a31796
--- /dev/null
+++ b/src/library/gettext.c
@@ -0,0 +1,109 @@
+/*
+ * Very minimal (and stupid) implementation of gettext, with a fixed lookup
+ * table.
+ *
+ * This library ONLY handles gettext(), and that only for the basic form (it
+ * translates strings to other strings with no other modification, so %2$d
+ * style constructs are not dealt with). The setlocale(), bindtextdomain(),
+ * and textdomain() functions are ignored.
+ *
+ * To use this library, create a function that, given a language string,
+ * returns a struct msg_table_s[] of msgid and msgstr pairs, with the end
+ * of the table being marked by a NULL msgid. The po2table.sh script will do
+ * this.
+ */
+
+#include "config.h"
+#include
+#include
+#include
+
+#ifndef HAVE_GETTEXT
+
+struct msgtable_s {
+ char *msgid;
+ char *msgstr;
+};
+
+#if ENABLE_NLS
+struct msgtable_s *minigettext__gettable(char *);
+#else /* ENABLE_NLS */
+struct msgtable_s *minigettext__gettable(char *a)
+{
+ return NULL;
+}
+#endif /* ENABLE_NLS */
+
+char *minisetlocale(char *a, char *b)
+{
+ return NULL;
+}
+
+
+char *minibindtextdomain(char *a, char *b)
+{
+ return NULL;
+}
+
+
+char *minitextdomain(char *a)
+{
+ return NULL;
+}
+
+
+char *minigettext(char *msgid)
+{
+ static struct msgtable_s *table = NULL;
+ static int tried_lang = 0;
+ char *lang;
+ int i;
+
+ if (NULL == msgid)
+ return msgid;
+
+ if (0 == tried_lang) {
+ lang = getenv("LANGUAGE");
+ if (lang)
+ table = minigettext__gettable(lang);
+
+ if (NULL == table) {
+ lang = getenv("LANG");
+ if (lang)
+ table = minigettext__gettable(lang);
+ }
+
+ if (NULL == table) {
+ lang = getenv("LC_ALL");
+ if (lang)
+ table = minigettext__gettable(lang);
+ }
+
+ if (NULL == table) {
+ lang = getenv("LC_MESSAGES");
+ if (lang)
+ table = minigettext__gettable(lang);
+ }
+
+ tried_lang = 1;
+ }
+
+ if (NULL == table)
+ return msgid;
+
+ for (i = 0; table[i].msgid; i++) {
+ if (0 == strcmp(table[i].msgid, msgid)) {
+ if (0 == table[i].msgstr)
+ return msgid;
+ if (0 == table[i].msgstr[0])
+ return msgid;
+ return table[i].msgstr;
+ }
+ }
+
+ return msgid;
+}
+
+#endif /* HAVE_GETTEXT */
+
+/* EOF */
diff --git a/src/main/debug.c b/src/main/debug.c
new file mode 100644
index 0000000..3ac005a
--- /dev/null
+++ b/src/main/debug.c
@@ -0,0 +1,72 @@
+/*
+ * Output debugging information.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "pv.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+#ifdef ENABLE_DEBUGGING
+/*
+ * Output debugging information to the file given in the DEBUG environment
+ * variable, if it is defined.
+ */
+void debugging_output(const char *function, const char *file, int line,
+ const char *format, ...)
+{
+ static int tried_open = 0;
+ static FILE *debugfptr = NULL;
+ char *debugfile;
+ va_list ap;
+ time_t t;
+ struct tm *tm;
+ char tbuf[128];
+
+ if (0 == tried_open) {
+ debugfile = getenv("DEBUG");
+ if (NULL != debugfile)
+ debugfptr = fopen(debugfile, "a");
+ tried_open = 1;
+ }
+
+ if (NULL == debugfptr)
+ return;
+
+ time(&t);
+ tm = localtime(&t);
+ tbuf[0] = 0;
+ strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", tm);
+
+ fprintf(debugfptr, "[%s] (%d) %s (%s:%d): ", tbuf, getpid(),
+ function, file, line);
+
+ va_start(ap, format);
+ vfprintf(debugfptr, format, ap);
+ va_end(ap);
+
+ fprintf(debugfptr, "\n");
+ fflush(debugfptr);
+}
+
+#else /* ! ENABLE_DEBUGGING */
+
+/*
+ * Stub debugging output function.
+ */
+void debugging_output(const char *function, const char *file, int line,
+ const char *format, ...)
+{
+}
+
+#endif /* ENABLE_DEBUGGING */
+
+/* EOF */
diff --git a/src/main/help.c b/src/main/help.c
new file mode 100644
index 0000000..d33bdec
--- /dev/null
+++ b/src/main/help.c
@@ -0,0 +1,209 @@
+/*
+ * Output command-line help to stdout.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include
+#include
+#include
+#include
+
+#define N_(String) (String)
+
+struct optdesc_s {
+ char *optshort;
+ char *optlong;
+ char *param;
+ char *description;
+};
+
+
+/*
+ * Display command-line help.
+ */
+void display_help(void)
+{
+ struct optdesc_s optlist[] = {
+ {"-p", "--progress", 0,
+ N_("show progress bar")},
+ {"-t", "--timer", 0,
+ N_("show elapsed time")},
+ {"-e", "--eta", 0,
+ N_("show estimated time of arrival (completion)")},
+ {"-I", "--fineta", 0,
+ N_
+ ("show absolute estimated time of arrival (completion)")},
+ {"-r", "--rate", 0,
+ N_("show data transfer rate counter")},
+ {"-a", "--average-rate", 0,
+ N_("show data transfer average rate counter")},
+ {"-b", "--bytes", 0,
+ N_("show number of bytes transferred")},
+ {"-T", "--buffer-percent", 0,
+ N_("show percentage of transfer buffer in use")},
+ {"-A", "--last-written", _("NUM"),
+ N_("show NUM bytes last written")},
+ {"-F", "--format", N_("FORMAT"),
+ N_("set output format to FORMAT")},
+ {"-n", "--numeric", 0,
+ N_("output percentages, not visual information")},
+ {"-q", "--quiet", 0,
+ N_("do not output any transfer information at all")},
+ {"", 0, 0, 0},
+ {"-W", "--wait", 0,
+ N_("display nothing until first byte transferred")},
+ {"-D", "--delay-start", N_("SEC"),
+ N_("display nothing until SEC seconds have passed")},
+ {"-s", "--size", N_("SIZE"),
+ N_("set estimated data size to SIZE bytes")},
+ {"-l", "--line-mode", 0,
+ N_("count lines instead of bytes")},
+ {"-0", "--null", 0,
+ N_("lines are null-terminated")},
+ {"-i", "--interval", N_("SEC"),
+ N_("update every SEC seconds")},
+ {"-w", "--width", N_("WIDTH"),
+ N_("assume terminal is WIDTH characters wide")},
+ {"-H", "--height", N_("HEIGHT"),
+ N_("assume terminal is HEIGHT rows high")},
+ {"-N", "--name", N_("NAME"),
+ N_("prefix visual information with NAME")},
+ {"-f", "--force", 0,
+ N_("output even if standard error is not a terminal")},
+ {"-c", "--cursor", 0,
+ N_("use cursor positioning escape sequences")},
+ {"", 0, 0, 0},
+ {"-L", "--rate-limit", N_("RATE"),
+ N_("limit transfer to RATE bytes per second")},
+ {"-B", "--buffer-size", N_("BYTES"),
+ N_("use a buffer size of BYTES")},
+ {"-C", "--no-splice", 0,
+ N_("never use splice(), always use read/write")},
+ {"-E", "--skip-errors", 0,
+ N_("skip read errors in input")},
+ {"-S", "--stop-at-size", 0,
+ N_("stop after --size bytes have been transferred")},
+#ifdef HAVE_IPC
+ {"-R", "--remote", N_("PID"),
+ N_("update settings of process PID")},
+#endif /* HAVE_IPC */
+ {"", 0, 0, 0},
+ {"-P", "--pidfile", N_("FILE"),
+ N_("save process ID in FILE")},
+ {"", 0, 0, 0},
+ {"-d", "--watchfd", N_("PID[:FD]"),
+ N_("watch file FD opened by process PID")},
+ {"", 0, 0, 0},
+ {"-h", "--help", 0,
+ N_("show this help and exit")},
+ {"-V", "--version", 0,
+ N_("show version information and exit")},
+ {0, 0, 0, 0}
+ };
+ int i, col1max = 0, tw = 77;
+ char *optbuf;
+
+ printf(_("Usage: %s [OPTION] [FILE]..."), PROGRAM_NAME);
+ printf("\n%s\n\n",
+ _
+ ("Concatenate FILE(s), or standard input, to standard output,\n"
+ "with monitoring."));
+
+ for (i = 0; optlist[i].optshort; i++) {
+ int width = 0;
+ char *param;
+
+ width = 2 + strlen(optlist[i].optshort);
+#ifdef HAVE_GETOPT_LONG
+ if (optlist[i].optlong)
+ width += 2 + strlen(optlist[i].optlong);
+#endif
+ param = optlist[i].param;
+ if (param)
+ param = _(param);
+ if (param)
+ width += 1 + strlen(param);
+
+ if (width > col1max)
+ col1max = width;
+ }
+
+ col1max++;
+
+ optbuf = malloc(col1max + 16);
+ if (NULL == optbuf) {
+ fprintf(stderr, "%s: %s\n", PROGRAM_NAME, strerror(errno));
+ exit(1);
+ }
+
+ for (i = 0; optlist[i].optshort; i++) {
+ char *param;
+ char *description;
+ char *start;
+ char *end;
+
+ if (0 == optlist[i].optshort[0]) {
+ printf("\n");
+ continue;
+ }
+
+ param = optlist[i].param;
+ if (param)
+ param = _(param);
+ description = optlist[i].description;
+ if (description)
+ description = _(description);
+
+ sprintf(optbuf, "%s%s%s%s%s", optlist[i].optshort,
+#ifdef HAVE_GETOPT_LONG
+ optlist[i].optlong ? ", " : "",
+ optlist[i].optlong ? optlist[i].optlong : "",
+#else
+ "", "",
+#endif
+ param ? " " : "", param ? param : "");
+
+ printf(" %-*s ", col1max - 2, optbuf);
+
+ if (NULL == description) {
+ printf("\n");
+ continue;
+ }
+
+ start = description;
+
+ while (strlen(start) > tw - col1max) {
+ end = start + tw - col1max;
+ while ((end > start) && (end[0] != ' '))
+ end--;
+ if (end == start) {
+ end = start + tw - col1max;
+ } else {
+ end++;
+ }
+ printf("%.*s\n%*s ", (int) (end - start), start,
+ col1max, "");
+ if (end == start)
+ end++;
+ start = end;
+ }
+
+ printf("%s\n", start);
+ }
+
+#ifdef ENABLE_DEBUGGING
+ printf("\n");
+ printf("%s",
+ _
+ ("Debugging is enabled; export the DEBUG environment variable to define the\noutput filename.\n"));
+#endif
+
+ printf("\n");
+ printf(_("Please report any bugs to %s."), BUG_REPORTS_TO);
+ printf("\n");
+}
+
+/* EOF */
diff --git a/src/main/main.c b/src/main/main.c
new file mode 100644
index 0000000..24366ee
--- /dev/null
+++ b/src/main/main.c
@@ -0,0 +1,270 @@
+/*
+ * Main program entry point - read the command line options, then perform
+ * the appropriate actions.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "options.h"
+#include "pv.h"
+
+/* #undef MAKE_STDOUT_NONBLOCKING */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+int pv_remote_set(opts_t);
+void pv_remote_init(void);
+void pv_remote_fini(void);
+
+
+/*
+ * Process command-line arguments and set option flags, then call functions
+ * to initialise, and finally enter the main loop.
+ */
+int main(int argc, char **argv)
+{
+ struct termios t, t_save;
+ opts_t opts;
+ pvstate_t state;
+ int retcode = 0;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+#endif
+
+ opts = opts_parse(argc, argv);
+ if (NULL == opts) {
+ debug("%s: %d", "exiting with status", 64);
+ return 64;
+ }
+
+ if (opts->do_nothing) {
+ debug("%s", "nothing to do - exiting with status 0");
+ opts_free(opts);
+ return 0;
+ }
+
+ /*
+ * -R specified - send the message, then exit.
+ */
+ if (opts->remote > 0) {
+ retcode = pv_remote_set(opts);
+ opts_free(opts);
+ return retcode;
+ }
+
+ /*
+ * Allocate our internal state buffer.
+ */
+ state = pv_state_alloc(opts->program_name);
+ if (NULL == state) {
+ fprintf(stderr, "%s: %s: %s\n", opts->program_name,
+ _("state allocation failed"), strerror(errno));
+ opts_free(opts);
+ debug("%s: %d", "exiting with status", 64);
+ return 64;
+ }
+
+ /*
+ * Write a PID file if -P was specified.
+ */
+ if (opts->pidfile != NULL) {
+ FILE *pidfptr;
+ pidfptr = fopen(opts->pidfile, "w");
+ if (NULL == pidfptr) {
+ fprintf(stderr, "%s: %s: %s\n", opts->program_name,
+ opts->pidfile, strerror(errno));
+ pv_state_free(state);
+ opts_free(opts);
+ return 1;
+ }
+ fprintf(pidfptr, "%d\n", getpid());
+ fclose(pidfptr);
+ }
+
+ /*
+ * If no files were given, pretend "-" was given (stdin).
+ */
+ if (0 == opts->argc) {
+ debug("%s", "no files given - adding fake argument `-'");
+ opts->argv[opts->argc++] = "-";
+ }
+
+ /*
+ * Put our list of files into the PV internal state.
+ */
+ pv_state_inputfiles(state, opts->argc,
+ (const char **) (opts->argv));
+
+ if (0 == opts->watch_pid) {
+ /*
+ * If no size was given, and we're not in line mode, try to
+ * calculate the total size.
+ */
+ if ((0 == opts->size) && (0 == opts->linemode)) {
+ opts->size = pv_calc_total_size(state);
+ debug("%s: %llu", "no size given - calculated",
+ opts->size);
+ }
+
+ /*
+ * If the size is unknown, we cannot have an ETA.
+ */
+ if (opts->size < 1) {
+ opts->eta = 0;
+ debug("%s", "size unknown - ETA disabled");
+ }
+ }
+
+ /*
+ * If stderr is not a terminal and we're neither forcing output nor
+ * outputting numerically, we will have nothing to display at all.
+ */
+ if ((0 == isatty(STDERR_FILENO))
+ && (0 == opts->force)
+ && (0 == opts->numeric)) {
+ opts->no_op = 1;
+ debug("%s", "nothing to display - setting no_op");
+ }
+
+ /*
+ * Auto-detect width or height if either are unspecified.
+ */
+ if ((0 == opts->width) || (0 == opts->height)) {
+ unsigned int width, height;
+ width = 0;
+ height = 0;
+ pv_screensize(&width, &height);
+ if (0 == opts->width) {
+ opts->width = width;
+ debug("%s: %u", "auto-detected terminal width",
+ width);
+ }
+ if (0 == opts->height) {
+ opts->height = height;
+ debug("%s: %u", "auto-detected terminal height",
+ height);
+ }
+ }
+
+ /*
+ * Width and height bounds checking (and defaults).
+ */
+ if (opts->width < 1)
+ opts->width = 80;
+ if (opts->height < 1)
+ opts->height = 25;
+ if (opts->width > 999999)
+ opts->width = 999999;
+ if (opts->height > 999999)
+ opts->height = 999999;
+
+ /*
+ * Interval must be at least 0.1 second, and at most 10 minutes.
+ */
+ if (opts->interval < 0.1)
+ opts->interval = 0.1;
+ if (opts->interval > 600)
+ opts->interval = 600;
+
+ /*
+ * Copy parameters from options into main state.
+ */
+ pv_state_interval_set(state, opts->interval);
+ pv_state_width_set(state, opts->width);
+ pv_state_height_set(state, opts->height);
+ pv_state_no_op_set(state, opts->no_op);
+ pv_state_force_set(state, opts->force);
+ pv_state_cursor_set(state, opts->cursor);
+ pv_state_numeric_set(state, opts->numeric);
+ pv_state_wait_set(state, opts->wait);
+ pv_state_delay_start_set(state, opts->delay_start);
+ pv_state_linemode_set(state, opts->linemode);
+ pv_state_null_set(state, opts->null);
+ pv_state_skip_errors_set(state, opts->skip_errors);
+ pv_state_stop_at_size_set(state, opts->stop_at_size);
+ pv_state_rate_limit_set(state, opts->rate_limit);
+ pv_state_target_buffer_size_set(state, opts->buffer_size);
+ pv_state_no_splice_set(state, opts->no_splice);
+ pv_state_size_set(state, opts->size);
+ pv_state_name_set(state, opts->name);
+ pv_state_format_string_set(state, opts->format);
+ pv_state_watch_pid_set(state, opts->watch_pid);
+ pv_state_watch_fd_set(state, opts->watch_fd);
+
+ pv_state_set_format(state, opts->progress, opts->timer, opts->eta,
+ opts->fineta, opts->rate, opts->average_rate,
+ opts->bytes, opts->bufpercent,
+ opts->lastwritten, opts->name);
+
+#ifdef MAKE_STDOUT_NONBLOCKING
+ /*
+ * Try and make standard output use non-blocking I/O.
+ *
+ * Note that this can cause problems with (broken) applications
+ * such as dd.
+ */
+ fcntl(STDOUT_FILENO, F_SETFL,
+ O_NONBLOCK | fcntl(STDOUT_FILENO, F_GETFL));
+#endif /* MAKE_STDOUT_NONBLOCKING */
+
+ /*
+ * Set terminal option TOSTOP so we get signal SIGTTOU if we try to
+ * write to the terminal while backgrounded.
+ *
+ * Also, save the current terminal attributes for later restoration.
+ */
+ memset(&t, 0, sizeof(t));
+ tcgetattr(STDERR_FILENO, &t);
+ t_save = t;
+ t.c_lflag |= TOSTOP;
+ tcsetattr(STDERR_FILENO, TCSANOW, &t);
+
+ if (0 != opts->watch_pid) {
+ if (0 <= opts->watch_fd) {
+ pv_sig_init(state);
+ retcode = pv_watchfd_loop(state);
+ tcsetattr(STDERR_FILENO, TCSANOW, &t_save);
+ if (opts->pidfile != NULL)
+ remove(opts->pidfile);
+ pv_sig_fini(state);
+ } else {
+ pv_sig_init(state);
+ retcode = pv_watchpid_loop(state);
+ tcsetattr(STDERR_FILENO, TCSANOW, &t_save);
+ if (opts->pidfile != NULL)
+ remove(opts->pidfile);
+ pv_sig_fini(state);
+ }
+ } else {
+ pv_sig_init(state);
+ pv_remote_init();
+ retcode = pv_main_loop(state);
+ pv_remote_fini();
+ tcsetattr(STDERR_FILENO, TCSANOW, &t_save);
+ if (opts->pidfile != NULL)
+ remove(opts->pidfile);
+ pv_sig_fini(state);
+ }
+
+ pv_state_free(state);
+
+ opts_free(opts);
+
+ debug("%s: %d", "exiting with status", retcode);
+
+ return retcode;
+}
+
+/* EOF */
diff --git a/src/main/options.c b/src/main/options.c
new file mode 100644
index 0000000..847d63c
--- /dev/null
+++ b/src/main/options.c
@@ -0,0 +1,407 @@
+/*
+ * Parse command-line options.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "options.h"
+#include "library/getopt.h"
+#include "pv.h"
+
+#include
+#include
+#include
+#include
+#include
+
+
+void display_help(void);
+void display_version(void);
+
+
+/*
+ * Free an opts_t object.
+ */
+void opts_free(opts_t opts)
+{
+ if (!opts)
+ return;
+ if (opts->argv)
+ free(opts->argv);
+ free(opts);
+}
+
+
+/*
+ * Parse the given command-line arguments into an opts_t object, handling
+ * "help", "license" and "version" options internally.
+ *
+ * Returns an opts_t, or 0 on error.
+ *
+ * Note that the contents of *argv[] (i.e. the command line parameters)
+ * aren't copied anywhere, just the pointers are copied, so make sure the
+ * command line data isn't overwritten or argv[1] free()d or whatever.
+ */
+opts_t opts_parse(int argc, char **argv)
+{
+#ifdef HAVE_GETOPT_LONG
+ struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"version", 0, 0, 'V'},
+ {"progress", 0, 0, 'p'},
+ {"timer", 0, 0, 't'},
+ {"eta", 0, 0, 'e'},
+ {"fineta", 0, 0, 'I'},
+ {"rate", 0, 0, 'r'},
+ {"average-rate", 0, 0, 'a'},
+ {"bytes", 0, 0, 'b'},
+ {"buffer-percent", 0, 0, 'T'},
+ {"last-written", 1, 0, 'A'},
+ {"force", 0, 0, 'f'},
+ {"numeric", 0, 0, 'n'},
+ {"quiet", 0, 0, 'q'},
+ {"cursor", 0, 0, 'c'},
+ {"wait", 0, 0, 'W'},
+ {"delay-start", 1, 0, 'D'},
+ {"size", 1, 0, 's'},
+ {"line-mode", 0, 0, 'l'},
+ {"null", 0, 0, '0'},
+ {"interval", 1, 0, 'i'},
+ {"width", 1, 0, 'w'},
+ {"height", 1, 0, 'H'},
+ {"name", 1, 0, 'N'},
+ {"format", 1, 0, 'F'},
+ {"rate-limit", 1, 0, 'L'},
+ {"buffer-size", 1, 0, 'B'},
+ {"no-splice", 0, 0, 'C'},
+ {"skip-errors", 0, 0, 'E'},
+ {"stop-at-size", 0, 0, 'S'},
+ {"remote", 1, 0, 'R'},
+ {"pidfile", 1, 0, 'P'},
+ {"watchfd", 1, 0, 'd'},
+ {0, 0, 0, 0}
+ };
+ int option_index = 0;
+#endif
+ char *short_options =
+ "hVpteIrabTA:fnqcWD:s:l0i:w:H:N:F:L:B:CESR:P:d:";
+ int c, numopts;
+ unsigned int check_pid;
+ int check_fd;
+ opts_t opts;
+ char *ptr;
+
+ opts = calloc(1, sizeof(*opts));
+ if (!opts) {
+ fprintf(stderr,
+ _("%s: option structure allocation failed (%s)"),
+ argv[0], strerror(errno));
+ fprintf(stderr, "\n");
+ return 0;
+ }
+
+ opts->program_name = argv[0];
+ ptr = strrchr(opts->program_name, '/');
+ if (NULL != ptr)
+ opts->program_name = &(ptr[1]);
+
+ opts->argc = 0;
+ opts->argv = calloc(argc + 1, sizeof(char *));
+ if (!opts->argv) {
+ fprintf(stderr,
+ _
+ ("%s: option structure argv allocation failed (%s)"),
+ opts->program_name, strerror(errno));
+ fprintf(stderr, "\n");
+ opts_free(opts);
+ return 0;
+ }
+
+ numopts = 0;
+
+ opts->interval = 1;
+ opts->delay_start = 0;
+ opts->watch_pid = 0;
+ opts->watch_fd = -1;
+
+ do {
+#ifdef HAVE_GETOPT_LONG
+ c = getopt_long(argc, argv,
+ short_options, long_options,
+ &option_index);
+#else
+ c = getopt(argc, argv, short_options);
+#endif
+ if (c < 0)
+ continue;
+
+ /*
+ * Check that any numeric arguments are of the right type.
+ */
+ switch (c) {
+ case 's':
+ case 'A':
+ case 'w':
+ case 'H':
+ case 'L':
+ case 'B':
+ case 'R':
+ if (pv_getnum_check(optarg, PV_NUMTYPE_INTEGER) !=
+ 0) {
+ fprintf(stderr, "%s: -%c: %s\n",
+ opts->program_name, c,
+ _("integer argument expected"));
+ opts_free(opts);
+ return 0;
+ }
+ break;
+ case 'i':
+ case 'D':
+ if (pv_getnum_check(optarg, PV_NUMTYPE_DOUBLE) !=
+ 0) {
+ fprintf(stderr, "%s: -%c: %s\n",
+ opts->program_name, c,
+ _("numeric argument expected"));
+ opts_free(opts);
+ return 0;
+ }
+ break;
+ case 'd':
+ if (sscanf(optarg, "%u:%d", &check_pid, &check_fd)
+ < 1) {
+ fprintf(stderr, "%s: -%c: %s\n",
+ opts->program_name, c,
+ _
+ ("process ID or pid:fd pair expected"));
+ opts_free(opts);
+ return 0;
+ }
+ if (check_pid < 1) {
+ fprintf(stderr, "%s: -%c: %s\n",
+ opts->program_name, c,
+ _("invalid process ID"));
+ opts_free(opts);
+ return 0;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Parse each command line option.
+ */
+ switch (c) {
+ case 'h':
+ display_help();
+ opts->do_nothing = 1;
+ return opts;
+ break;
+ case 'V':
+ display_version();
+ opts->do_nothing = 1;
+ return opts;
+ break;
+ case 'p':
+ opts->progress = 1;
+ numopts++;
+ break;
+ case 't':
+ opts->timer = 1;
+ numopts++;
+ break;
+ case 'I':
+ opts->fineta = 1;
+ numopts++;
+ break;
+ case 'e':
+ opts->eta = 1;
+ numopts++;
+ break;
+ case 'r':
+ opts->rate = 1;
+ numopts++;
+ break;
+ case 'a':
+ opts->average_rate = 1;
+ numopts++;
+ break;
+ case 'b':
+ opts->bytes = 1;
+ numopts++;
+ break;
+ case 'T':
+ opts->bufpercent = 1;
+ numopts++;
+ break;
+ case 'A':
+ opts->lastwritten = pv_getnum_i(optarg);
+ numopts++;
+ break;
+ case 'f':
+ opts->force = 1;
+ break;
+ case 'n':
+ opts->numeric = 1;
+ numopts++;
+ break;
+ case 'q':
+ opts->no_op = 1;
+ numopts++;
+ break;
+ case 'c':
+ opts->cursor = 1;
+ break;
+ case 'W':
+ opts->wait = 1;
+ break;
+ case 'D':
+ opts->delay_start = pv_getnum_d(optarg);
+ break;
+ case 's':
+ opts->size = pv_getnum_ll(optarg);
+ break;
+ case 'l':
+ opts->linemode = 1;
+ break;
+ case '0':
+ opts->null = 1;
+ opts->linemode = 1;
+ break;
+ case 'i':
+ opts->interval = pv_getnum_d(optarg);
+ break;
+ case 'w':
+ opts->width = pv_getnum_i(optarg);
+ break;
+ case 'H':
+ opts->height = pv_getnum_i(optarg);
+ break;
+ case 'N':
+ opts->name = optarg;
+ break;
+ case 'L':
+ opts->rate_limit = pv_getnum_ll(optarg);
+ break;
+ case 'B':
+ opts->buffer_size = pv_getnum_ll(optarg);
+ break;
+ case 'C':
+ opts->no_splice = 1;
+ break;
+ case 'E':
+ opts->skip_errors++;
+ break;
+ case 'S':
+ opts->stop_at_size = 1;
+ break;
+ case 'R':
+ opts->remote = pv_getnum_i(optarg);
+ break;
+ case 'P':
+ opts->pidfile = optarg;
+ break;
+ case 'F':
+ opts->format = optarg;
+ break;
+ case 'd':
+ opts->watch_pid = 0;
+ opts->watch_fd = -1;
+ sscanf(optarg, "%u:%d", &(opts->watch_pid),
+ &(opts->watch_fd));
+ break;
+ default:
+#ifdef HAVE_GETOPT_LONG
+ fprintf(stderr,
+ _("Try `%s --help' for more information."),
+ opts->program_name);
+#else
+ fprintf(stderr,
+ _("Try `%s -h' for more information."),
+ opts->program_name);
+#endif
+ fprintf(stderr, "\n");
+ opts_free(opts);
+ return 0;
+ break;
+ }
+
+ } while (c != -1);
+
+ if (0 != opts->watch_pid) {
+ if (opts->linemode || opts->null || opts->stop_at_size
+ || (opts->skip_errors > 0) || (opts->buffer_size > 0)
+ || (opts->rate_limit > 0)) {
+ fprintf(stderr,
+ _
+ ("%s: cannot use line mode or transfer modifier options when watching file descriptors"),
+ opts->program_name);
+ fprintf(stderr, "\n");
+ opts_free(opts);
+ return 0;
+ }
+
+ if (opts->cursor) {
+ fprintf(stderr,
+ _
+ ("%s: cannot use cursor positioning when watching file descriptors"),
+ opts->program_name);
+ fprintf(stderr, "\n");
+ opts_free(opts);
+ return 0;
+ }
+
+ if (0 != opts->remote) {
+ fprintf(stderr,
+ _
+ ("%s: cannot use remote control when watching file descriptors"),
+ opts->program_name);
+ fprintf(stderr, "\n");
+ opts_free(opts);
+ return 0;
+ }
+
+ if (optind < argc) {
+ fprintf(stderr,
+ _
+ ("%s: cannot transfer files when watching file descriptors"),
+ opts->program_name);
+ fprintf(stderr, "\n");
+ opts_free(opts);
+ return 0;
+ }
+
+ if (0 != access("/proc/self/fdinfo", X_OK)) {
+ fprintf(stderr,
+ _
+ ("%s: -d: not available on systems without /proc/self/fdinfo"),
+ opts->program_name);
+ fprintf(stderr, "\n");
+ opts_free(opts);
+ return 0;
+ }
+ }
+
+ /*
+ * Default options: -pterb
+ */
+ if (0 == numopts) {
+ opts->progress = 1;
+ opts->timer = 1;
+ opts->eta = 1;
+ opts->rate = 1;
+ opts->bytes = 1;
+ }
+
+ /*
+ * Store remaining command-line arguments.
+ */
+ while (optind < argc) {
+ opts->argv[opts->argc++] = argv[optind++];
+ }
+
+ return opts;
+}
+
+/* EOF */
diff --git a/src/main/remote.c b/src/main/remote.c
new file mode 100644
index 0000000..4d90b1c
--- /dev/null
+++ b/src/main/remote.c
@@ -0,0 +1,330 @@
+/*
+ * Remote-control functions.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "options.h"
+#include "pv.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef HAVE_IPC
+#include
+#include
+#endif /* HAVE_IPC */
+
+
+#ifdef HAVE_IPC
+struct remote_msg {
+ long mtype;
+ unsigned char progress; /* progress bar flag */
+ unsigned char timer; /* timer flag */
+ unsigned char eta; /* ETA flag */
+ unsigned char fineta; /* absolute ETA flag */
+ unsigned char rate; /* rate counter flag */
+ unsigned char average_rate; /* average rate counter flag */
+ unsigned char bytes; /* bytes transferred flag */
+ unsigned char bufpercent; /* transfer buffer percentage flag */
+ unsigned int lastwritten; /* last-written bytes count */
+ unsigned long long rate_limit; /* rate limit, in bytes per second */
+ unsigned long long buffer_size; /* buffer size, in bytes (0=default) */
+ unsigned long long size; /* total size of data */
+ double interval; /* interval between updates */
+ unsigned int width; /* screen width */
+ unsigned int height; /* screen height */
+ char name[256];
+ char format[256];
+};
+
+
+static int remote__msgid = -1;
+
+
+/*
+ * Return a key for use with msgget() which will be unique to the current
+ * user.
+ *
+ * We can't just use ftok() because the queue needs to be user-specific
+ * so that a user cannot send messages to another user's process, and we
+ * can't easily find out the terminal a given process is connected to in a
+ * cross-platform way.
+ */
+static key_t remote__genkey(void)
+{
+ int uid;
+ key_t key;
+
+ uid = geteuid();
+ if (uid < 0)
+ uid = 0;
+
+ key = ftok("/tmp", 'P') | uid;
+
+ return key;
+}
+
+
+/*
+ * Return a message queue ID that is unique to the current user and the
+ * given process ID, or -1 on error.
+ */
+static int remote__msgget(void)
+{
+ /* Catch SIGSYS in case msgget() raises it, so we get ENOSYS */
+ signal(SIGSYS, SIG_IGN);
+ return msgget(remote__genkey(), IPC_CREAT | 0600);
+}
+
+
+/*
+ * Set the options of a remote process by setting up an IPC message queue,
+ * sending a message containing the new options, and then waiting for the
+ * message to be consumed by the remote process.
+ *
+ * Returns nonzero on error.
+ */
+int pv_remote_set(opts_t opts)
+{
+ struct remote_msg msgbuf;
+ struct msqid_ds qbuf;
+ long timeout;
+ int msgid;
+ long initial_qnum;
+
+ /*
+ * Check that the remote process exists.
+ */
+ if (kill(opts->remote, 0) != 0) {
+ fprintf(stderr, "%s: %d: %s\n", opts->program_name,
+ opts->remote, strerror(errno));
+ return 1;
+ }
+
+ /*
+ * Make sure parameters are within sensible bounds.
+ */
+ if (opts->width < 0)
+ opts->width = 80;
+ if (opts->height < 0)
+ opts->height = 25;
+ if (opts->width > 999999)
+ opts->width = 999999;
+ if (opts->height > 999999)
+ opts->height = 999999;
+ if ((opts->interval != 0) && (opts->interval < 0.1))
+ opts->interval = 0.1;
+ if (opts->interval > 600)
+ opts->interval = 600;
+
+ /*
+ * Copy parameters into message buffer.
+ */
+ memset(&msgbuf, 0, sizeof(msgbuf));
+ msgbuf.mtype = opts->remote;
+ msgbuf.progress = opts->progress;
+ msgbuf.timer = opts->timer;
+ msgbuf.eta = opts->eta;
+ msgbuf.fineta = opts->fineta;
+ msgbuf.rate = opts->rate;
+ msgbuf.average_rate = opts->average_rate;
+ msgbuf.bytes = opts->bytes;
+ msgbuf.bufpercent = opts->bufpercent;
+ msgbuf.lastwritten = opts->lastwritten;
+ msgbuf.rate_limit = opts->rate_limit;
+ msgbuf.buffer_size = opts->buffer_size;
+ msgbuf.size = opts->size;
+ msgbuf.interval = opts->interval;
+ msgbuf.width = opts->width;
+ msgbuf.height = opts->height;
+ if (opts->name != NULL) {
+ strncpy(msgbuf.name, opts->name, sizeof(msgbuf.name) - 1);
+ }
+ if (opts->format != NULL) {
+ strncpy(msgbuf.format, opts->format,
+ sizeof(msgbuf.format) - 1);
+ }
+
+ msgid = remote__msgget();
+ if (msgid < 0) {
+ fprintf(stderr, "%s: %s\n", opts->program_name,
+ strerror(errno));
+ return 1;
+ }
+
+ if (msgctl(msgid, IPC_STAT, &qbuf) < 0) {
+ fprintf(stderr, "%s: %s\n", opts->program_name,
+ strerror(errno));
+ return 1;
+ }
+
+ initial_qnum = qbuf.msg_qnum;
+
+ if (msgsnd(msgid, &msgbuf, sizeof(msgbuf) - sizeof(long), 0) != 0) {
+ fprintf(stderr, "%s: %s\n", opts->program_name,
+ strerror(errno));
+ return 1;
+ }
+
+ timeout = 1100000;
+
+ while (timeout > 10000) {
+ struct timeval tv;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 10000;
+ select(0, NULL, NULL, NULL, &tv);
+ timeout -= 10000;
+
+ /*
+ * If we can't stat the queue, it must have been deleted.
+ */
+ if (msgctl(msgid, IPC_STAT, &qbuf) < 0)
+ break;
+
+ /*
+ * If the message count is at or below the message count
+ * before we sent our message, assume it was received.
+ */
+ if (qbuf.msg_qnum <= initial_qnum) {
+ return 0;
+ }
+ }
+
+ /*
+ * Message not received - delete it.
+ */
+ if (msgctl(msgid, IPC_STAT, &qbuf) >= 0) {
+ msgrcv(msgid, &msgbuf, sizeof(msgbuf) - sizeof(long),
+ opts->remote, IPC_NOWAIT);
+ /*
+ * If this leaves nothing on the queue, remove the
+ * queue, in case we created one for no reason.
+ */
+ if (msgctl(msgid, IPC_STAT, &qbuf) >= 0) {
+ if (qbuf.msg_qnum < 1)
+ msgctl(msgid, IPC_RMID, &qbuf);
+ }
+ }
+
+ fprintf(stderr, "%s: %d: %s\n", opts->program_name, opts->remote,
+ _("message not received"));
+ return 1;
+}
+
+
+/*
+ * Check for an IPC remote handling message and, if there is one, replace
+ * the current process's options with those being passed in.
+ *
+ * NB relies on pv_state_set_format() causing the output format to be
+ * reparsed.
+ */
+void pv_remote_check(pvstate_t state)
+{
+ struct remote_msg msgbuf;
+ int got;
+
+ if (remote__msgid < 0)
+ return;
+
+ memset(&msgbuf, 0, sizeof(msgbuf));
+
+ got =
+ msgrcv(remote__msgid, &msgbuf, sizeof(msgbuf) - sizeof(long),
+ getpid(), IPC_NOWAIT);
+ if (got < 0) {
+ /*
+ * If our queue had been deleted, re-create it.
+ */
+ if (errno != EAGAIN && errno != ENOMSG) {
+ remote__msgid = remote__msgget();
+ }
+ }
+ if (got < 1)
+ return;
+
+ debug("%s", "received remote message");
+
+ pv_state_format_string_set(state, NULL);
+ pv_state_name_set(state, NULL);
+
+ pv_state_set_format(state, msgbuf.progress, msgbuf.timer,
+ msgbuf.eta, msgbuf.fineta, msgbuf.rate,
+ msgbuf.average_rate,
+ msgbuf.bytes, msgbuf.bufpercent,
+ msgbuf.lastwritten,
+ 0 ==
+ msgbuf.name[0] ? NULL : strdup(msgbuf.name));
+
+ if (msgbuf.rate_limit > 0)
+ pv_state_rate_limit_set(state, msgbuf.rate_limit);
+ if (msgbuf.buffer_size > 0) {
+ pv_state_target_buffer_size_set(state, msgbuf.buffer_size);
+ }
+ if (msgbuf.size > 0)
+ pv_state_size_set(state, msgbuf.size);
+ if (msgbuf.interval > 0)
+ pv_state_interval_set(state, msgbuf.interval);
+ if (msgbuf.width > 0)
+ pv_state_width_set(state, msgbuf.width);
+ if (msgbuf.height > 0)
+ pv_state_height_set(state, msgbuf.height);
+ if (msgbuf.format[0] != 0)
+ pv_state_format_string_set(state, strdup(msgbuf.format));
+}
+
+
+/*
+ * Initialise remote message reception handling.
+ */
+void pv_remote_init(void)
+{
+ remote__msgid = remote__msgget();
+}
+
+
+/*
+ * Clean up after remote message reception handling.
+ */
+void pv_remote_fini(void)
+{
+ if (remote__msgid >= 0) {
+ struct msqid_ds qbuf;
+ msgctl(remote__msgid, IPC_RMID, &qbuf);
+ }
+}
+
+#else /* !HAVE_IPC */
+
+/*
+ * Dummy stubs for remote control when we don't have IPC.
+ */
+void pv_remote_init(void)
+{
+}
+
+void pv_remote_check(pvstate_t state)
+{
+}
+
+void pv_remote_fini(void)
+{
+}
+
+int pv_remote_set(pvstate_t state)
+{
+ fprintf(stderr, "%s\n", _("IPC not supported on this system"));
+ return 1;
+}
+
+#endif /* HAVE_IPC */
+
+/* EOF */
diff --git a/src/main/version.c b/src/main/version.c
new file mode 100644
index 0000000..6dcdc18
--- /dev/null
+++ b/src/main/version.c
@@ -0,0 +1,34 @@
+/*
+ * Output version information to stdout.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include
+
+
+/*
+ * Display current package version.
+ */
+void display_version(void)
+{
+ printf(_("%s %s - Copyright %s %s"),
+ PROGRAM_NAME, VERSION, COPYRIGHT_YEAR, COPYRIGHT_HOLDER);
+ printf("\n\n");
+ printf(_("Web site: %s"), PROJECT_HOMEPAGE);
+ printf("\n\n");
+ printf("%s",
+ _("This program is free software, and is being distributed "
+ "under the\nterms of the Artistic License 2.0."));
+ printf("\n\n");
+ printf("%s",
+ _
+ ("This program is distributed in the hope that it will be useful,\n"
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."));
+ printf("\n\n");
+}
+
+/* EOF */
diff --git a/src/nls/de.po b/src/nls/de.po
new file mode 100644
index 0000000..2b8a2e8
--- /dev/null
+++ b/src/nls/de.po
@@ -0,0 +1,452 @@
+msgid ""
+msgstr ""
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-06-30 22:21+0100\n"
+"Content-Type: text/plain; charset=iso-8859-15\n"
+"Date: 1999-06-01 15:18:29+0100\n"
+"From: Andrew Wood \n"
+"Xgettext-Options: --default-domain=pkgbuild --directory=./.pkgdir --add-"
+"comments --keyword=_ --keyword=N_\n"
+"Files: src/getopt/getopt.c src/getopt/getopt1.c src/version.c src/main/init."
+"c src/main/help.c src/main/xpmptr.c src/guts/load.c src/guts/stop.c src/guts/"
+"configure.c src/guts/make.c src/guts/install.c src/guts/prefs.c src/ui/"
+"generate.c src/ui/main.c src/ui/xpm.c src/ui/status.c src/ui/activate.c src/"
+"ui/handlers/main.c src/ui/handlers/package.c src/ui/handlers/prefs.c src/ui/"
+"handlers/dirsel.c src/ui/handlers/popup.c src/ui/handlers/help.c src/ui/"
+"textout.c src/ui/report.c src/ui/callback.c src/nls/intl/bindtextdom.c src/"
+"nls/intl/dcgettext.c src/nls/intl/dgettext.c src/nls/intl/finddomain.c src/"
+"nls/intl/gettext.c src/nls/intl/loadmsgcat.c src/nls/intl/localealias.c src/"
+"nls/intl/textdomain.c src/nls/intl-cat/cat-compat.c src/nls/intl-gett/intl-"
+"compat.c\n"
+
+#: src/pv/watchpid.c:51 src/pv/watchpid.c:66 src/pv/watchpid.c:76
+#: src/pv/watchpid.c:107 src/pv/loop.c:519 src/pv/loop.c:580 src/pv/loop.c:632
+msgid "pid"
+msgstr ""
+
+#: src/pv/watchpid.c:68 src/pv/watchpid.c:78 src/pv/watchpid.c:109
+msgid "fd"
+msgstr ""
+
+#: src/pv/watchpid.c:112
+msgid "not a regular file or block device"
+msgstr ""
+
+#: src/pv/cursor.c:53
+#, fuzzy
+msgid "failed to get terminal name"
+msgstr "Terminal konnte nicht geöffnet werden"
+
+#: src/pv/cursor.c:96
+#, fuzzy
+msgid "failed to open lock file"
+msgstr "Datei konnte nicht geschlossen werden"
+
+#: src/pv/cursor.c:129
+msgid "lock attempt failed"
+msgstr ""
+
+#: src/pv/cursor.c:369
+msgid "failed to open terminal"
+msgstr "Terminal konnte nicht geöffnet werden"
+
+#: src/pv/file.c:118
+#, fuzzy
+msgid "failed to seek to start of output"
+msgstr "Dateiinformationen für Ausgabe-Datei konnten nicht gelesen werden"
+
+#: src/pv/file.c:214
+msgid "failed to close file"
+msgstr "Datei konnte nicht geschlossen werden"
+
+#: src/pv/file.c:237
+msgid "failed to read file"
+msgstr "Datei konnte nicht gelesen werden"
+
+#: src/pv/file.c:247
+msgid "failed to stat file"
+msgstr "Dateiinformationen konnten nicht gelesen werden"
+
+#: src/pv/file.c:256
+msgid "failed to stat output file"
+msgstr "Dateiinformationen für Ausgabe-Datei konnten nicht gelesen werden"
+
+#: src/pv/file.c:279
+msgid "input file is output file"
+msgstr "Eingabe-Datei ist Ausgabe-Datei"
+
+#: src/pv/display.c:117
+msgid "yzafpnum kMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:122
+msgid "yzafpnum KMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:532 src/pv/transfer.c:679
+msgid "buffer allocation failed"
+msgstr "Puffer konnte nicht allokiert werden"
+
+#: src/pv/display.c:592 src/pv/transfer.c:458
+msgid "B"
+msgstr "B"
+
+#: src/pv/display.c:628 src/pv/display.c:636
+msgid "/s"
+msgstr ""
+
+#: src/pv/display.c:628 src/pv/display.c:636
+msgid "B/s"
+msgstr ""
+
+#: src/pv/display.c:671 src/pv/display.c:676 src/pv/display.c:738
+msgid "ETA"
+msgstr "ETA"
+
+#: src/pv/transfer.c:341
+msgid "read failed"
+msgstr "read-Aufruf fehlgeschlagen"
+
+#: src/pv/transfer.c:359
+msgid "warning: read errors detected"
+msgstr ""
+
+#: src/pv/transfer.c:374
+msgid "file is not seekable"
+msgstr ""
+
+#: src/pv/transfer.c:437
+#, fuzzy
+msgid "failed to seek past error"
+msgstr "Dateiinformationen konnten nicht gelesen werden"
+
+#: src/pv/transfer.c:456
+msgid "skipped past read error"
+msgstr ""
+
+#: src/pv/transfer.c:631
+msgid "write failed"
+msgstr "write-Aufruf fehlgeschlagen"
+
+#: src/pv/transfer.c:770
+msgid "select call failed"
+msgstr "select-Aufruf fehlgeschlagen"
+
+#: src/pv/state.c:33
+msgid "none"
+msgstr ""
+
+#: src/main/version.c:17
+#, fuzzy, c-format
+msgid "%s %s - Copyright %s %s"
+msgstr "%s %s - Copyright(C) %s %s"
+
+#: src/main/version.c:20
+#, c-format
+msgid "Web site: %s"
+msgstr "Web-Site: %s"
+
+#: src/main/version.c:23
+msgid ""
+"This program is free software, and is being distributed under the\n"
+"terms of the Artistic License 2.0."
+msgstr ""
+"Dieses Programm ist freie Software und wird unter den Bedingungen\n"
+"der Artistic License verbreitet."
+
+#: src/main/version.c:28
+msgid ""
+"This program is distributed in the hope that it will be useful,\n"
+"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+msgstr ""
+"Dieses Programm wird verbreitet unter der Annahme dass es nützlich ist,\n"
+"aber OHNE JEGLICHE GARANTIE; insbesondere ohne der impliziten Garantie\n"
+"einer MARKTGÄNGIGKEIT oder EIGNUNG ZU EINEM BESTIMMTEN ZWECK."
+
+#: src/main/main.c:73
+#, fuzzy
+msgid "state allocation failed"
+msgstr "Puffer konnte nicht allokiert werden"
+
+#: src/main/options.c:98
+#, c-format
+msgid "%s: option structure allocation failed (%s)"
+msgstr "%s: `option' konnte nicht allokiert werden (%s)"
+
+#: src/main/options.c:114
+#, c-format
+msgid "%s: option structure argv allocation failed (%s)"
+msgstr "%s: `option' (argv) konnte nicht allokiert werden (%s)"
+
+#: src/main/options.c:154
+msgid "integer argument expected"
+msgstr ""
+
+#: src/main/options.c:165
+msgid "numeric argument expected"
+msgstr ""
+
+#: src/main/options.c:176
+msgid "process ID or pid:fd pair expected"
+msgstr ""
+
+#: src/main/options.c:183
+msgid "invalid process ID"
+msgstr ""
+
+#: src/main/options.c:317
+#, c-format
+msgid "Try `%s --help' for more information."
+msgstr "`%s --help' zeigt weitere Informationen an."
+
+#: src/main/options.c:321
+#, fuzzy, c-format
+msgid "Try `%s -h' for more information."
+msgstr "`%s -h' zeigt weitere Informationen an."
+
+#: src/main/options.c:338
+#, c-format
+msgid ""
+"%s: cannot use line mode or transfer modifier options when watching file "
+"descriptors"
+msgstr ""
+
+#: src/main/options.c:348
+#, c-format
+msgid "%s: cannot use cursor positioning when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:358
+#, c-format
+msgid "%s: cannot use remote control when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:368
+#, c-format
+msgid "%s: cannot transfer files when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:378
+#, c-format
+msgid "%s: -d: not available on systems without /proc/self/fdinfo"
+msgstr ""
+
+#: src/main/remote.c:218
+msgid "message not received"
+msgstr ""
+
+#: src/main/remote.c:324
+msgid "IPC not supported on this system"
+msgstr ""
+
+#: src/main/help.c:31
+msgid "show progress bar"
+msgstr "Fortschritts-Anzeige"
+
+#: src/main/help.c:33
+msgid "show elapsed time"
+msgstr "zeige die verstrichene Zeit an"
+
+#: src/main/help.c:35
+msgid "show estimated time of arrival (completion)"
+msgstr "zeige die erwartete Zeit bis zum Ende an"
+
+#: src/main/help.c:38
+#, fuzzy
+msgid "show absolute estimated time of arrival (completion)"
+msgstr "zeige die erwartete Zeit bis zum Ende an"
+
+#: src/main/help.c:40
+msgid "show data transfer rate counter"
+msgstr "zeige die Datentransferrate an"
+
+#: src/main/help.c:42
+msgid "show data transfer average rate counter"
+msgstr ""
+
+#: src/main/help.c:44
+msgid "show number of bytes transferred"
+msgstr "zeige die Anzahl von Bytes, die transferiert worden sind"
+
+#: src/main/help.c:46
+msgid "show percentage of transfer buffer in use"
+msgstr ""
+
+#: src/main/help.c:47
+msgid "NUM"
+msgstr ""
+
+#: src/main/help.c:48
+msgid "show NUM bytes last written"
+msgstr ""
+
+#: src/main/help.c:49
+msgid "FORMAT"
+msgstr ""
+
+#: src/main/help.c:50
+msgid "set output format to FORMAT"
+msgstr ""
+
+#: src/main/help.c:52
+msgid "output percentages, not visual information"
+msgstr "Ausgabe von Prozent-Angaben statt visueller Darstellung"
+
+#: src/main/help.c:54
+msgid "do not output any transfer information at all"
+msgstr "sämtliche Transferinformationen unterdrücken"
+
+#: src/main/help.c:57
+msgid "display nothing until first byte transferred"
+msgstr "keine Ausgabe bevor das erste Byte übertragen wurde"
+
+#: src/main/help.c:58 src/main/help.c:66
+msgid "SEC"
+msgstr ""
+
+#: src/main/help.c:59
+#, fuzzy
+msgid "display nothing until SEC seconds have passed"
+msgstr "keine Ausgabe bevor das erste Byte übertragen wurde"
+
+#: src/main/help.c:60
+msgid "SIZE"
+msgstr ""
+
+#: src/main/help.c:61
+msgid "set estimated data size to SIZE bytes"
+msgstr "setze erwartete Daten-Länge auf SIZE Byte"
+
+#: src/main/help.c:63
+msgid "count lines instead of bytes"
+msgstr ""
+
+#: src/main/help.c:65
+msgid "lines are null-terminated"
+msgstr ""
+
+#: src/main/help.c:67
+msgid "update every SEC seconds"
+msgstr "aktualisiere Ausgabe nach SEC Sekunden Intervall"
+
+#: src/main/help.c:68
+msgid "WIDTH"
+msgstr ""
+
+#: src/main/help.c:69
+msgid "assume terminal is WIDTH characters wide"
+msgstr "setze Terminal-Breite auf WIDTH Zeichen"
+
+#: src/main/help.c:70
+msgid "HEIGHT"
+msgstr ""
+
+#: src/main/help.c:71
+msgid "assume terminal is HEIGHT rows high"
+msgstr "setze Terminal-Breite auf WIDTH Zeichen"
+
+#: src/main/help.c:72
+msgid "NAME"
+msgstr ""
+
+#: src/main/help.c:73
+msgid "prefix visual information with NAME"
+msgstr "setze den NAMEn für visuelle Darstellung"
+
+#: src/main/help.c:75
+msgid "output even if standard error is not a terminal"
+msgstr ""
+"Ausgabe auch dann erzwingen, wenn der Fehlerausgabe-Kanal kein Terminal ist"
+
+#: src/main/help.c:77
+msgid "use cursor positioning escape sequences"
+msgstr "benutze Escape-Sequenzen zur Cursor-Positionierung"
+
+#: src/main/help.c:79
+msgid "RATE"
+msgstr ""
+
+#: src/main/help.c:80
+msgid "limit transfer to RATE bytes per second"
+msgstr "beschränke die Transferrate auf RATE Byte pro Sekunde"
+
+#: src/main/help.c:81
+msgid "BYTES"
+msgstr ""
+
+#: src/main/help.c:82
+msgid "use a buffer size of BYTES"
+msgstr ""
+
+#: src/main/help.c:84
+msgid "never use splice(), always use read/write"
+msgstr ""
+
+#: src/main/help.c:86
+msgid "skip read errors in input"
+msgstr ""
+
+#: src/main/help.c:88
+#, fuzzy
+msgid "stop after --size bytes have been transferred"
+msgstr "zeige die Anzahl von Bytes, die transferiert worden sind"
+
+#: src/main/help.c:90
+msgid "PID"
+msgstr ""
+
+#: src/main/help.c:91
+msgid "update settings of process PID"
+msgstr ""
+
+#: src/main/help.c:94
+msgid "FILE"
+msgstr ""
+
+#: src/main/help.c:95
+msgid "save process ID in FILE"
+msgstr ""
+
+#: src/main/help.c:97
+msgid "PID[:FD]"
+msgstr ""
+
+#: src/main/help.c:98
+msgid "watch file FD opened by process PID"
+msgstr ""
+
+#: src/main/help.c:101
+msgid "show this help and exit"
+msgstr "zeige diese Hilfe und beende"
+
+#: src/main/help.c:103
+msgid "show version information and exit"
+msgstr "zeige Versionsinformationen und beende"
+
+#: src/main/help.c:109
+#, c-format
+msgid "Usage: %s [OPTION] [FILE]..."
+msgstr "Syntax: %s [OPTION] [DATEI]..."
+
+#: src/main/help.c:112
+msgid ""
+"Concatenate FILE(s), or standard input, to standard output,\n"
+"with monitoring."
+msgstr ""
+"Verbindet DATEI(en) oder den Standard-Eingabe-Kanal mit dem\n"
+"Standard-Ausgabe-Kanal und misst den Datenstrom."
+
+#: src/main/help.c:201
+msgid ""
+"Debugging is enabled; export the DEBUG environment variable to define the\n"
+"output filename.\n"
+msgstr ""
+
+#: src/main/help.c:205
+#, c-format
+msgid "Please report any bugs to %s."
+msgstr "Bitte senden Sie Fehlerberichte an %s."
+
+#~ msgid "failed to lock terminal"
+#~ msgstr "Terminal konnte nicht geöffnet werden"
diff --git a/src/nls/fr.po b/src/nls/fr.po
new file mode 100644
index 0000000..06ec5f9
--- /dev/null
+++ b/src/nls/fr.po
@@ -0,0 +1,454 @@
+msgid ""
+msgstr ""
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-06-30 22:21+0100\n"
+"Content-Type: text/plain; charset=ISO-8859-15\n"
+"Date: 1999-06-01 15:18:29+0100\n"
+"From: Andrew Wood \n"
+"Xgettext-Options: --default-domain=pkgbuild --directory=./.pkgdir --add-"
+"comments --keyword=_ --keyword=N_\n"
+"Files: src/getopt/getopt.c src/getopt/getopt1.c src/version.c src/main/init."
+"c src/main/help.c src/main/xpmptr.c src/guts/load.c src/guts/stop.c src/guts/"
+"configure.c src/guts/make.c src/guts/install.c src/guts/prefs.c src/ui/"
+"generate.c src/ui/main.c src/ui/xpm.c src/ui/status.c src/ui/activate.c src/"
+"ui/handlers/main.c src/ui/handlers/package.c src/ui/handlers/prefs.c src/ui/"
+"handlers/dirsel.c src/ui/handlers/popup.c src/ui/handlers/help.c src/ui/"
+"textout.c src/ui/report.c src/ui/callback.c src/nls/intl/bindtextdom.c src/"
+"nls/intl/dcgettext.c src/nls/intl/dgettext.c src/nls/intl/finddomain.c src/"
+"nls/intl/gettext.c src/nls/intl/loadmsgcat.c src/nls/intl/localealias.c src/"
+"nls/intl/textdomain.c src/nls/intl-cat/cat-compat.c src/nls/intl-gett/intl-"
+"compat.c\n"
+
+#: src/pv/watchpid.c:51 src/pv/watchpid.c:66 src/pv/watchpid.c:76
+#: src/pv/watchpid.c:107 src/pv/loop.c:519 src/pv/loop.c:580 src/pv/loop.c:632
+msgid "pid"
+msgstr ""
+
+#: src/pv/watchpid.c:68 src/pv/watchpid.c:78 src/pv/watchpid.c:109
+msgid "fd"
+msgstr ""
+
+#: src/pv/watchpid.c:112
+msgid "not a regular file or block device"
+msgstr ""
+
+#: src/pv/cursor.c:53
+#, fuzzy
+msgid "failed to get terminal name"
+msgstr "l'ouverture du terminal a échoué"
+
+#: src/pv/cursor.c:96
+#, fuzzy
+msgid "failed to open lock file"
+msgstr "la fermeture du fichier a échoué"
+
+#: src/pv/cursor.c:129
+msgid "lock attempt failed"
+msgstr ""
+
+#: src/pv/cursor.c:369
+msgid "failed to open terminal"
+msgstr "l'ouverture du terminal a échoué"
+
+#: src/pv/file.c:118
+#, fuzzy
+msgid "failed to seek to start of output"
+msgstr "échec à statuer sur le fichier de sortie"
+
+#: src/pv/file.c:214
+msgid "failed to close file"
+msgstr "la fermeture du fichier a échoué"
+
+#: src/pv/file.c:237
+msgid "failed to read file"
+msgstr "la lecture du fichier a échoué"
+
+#: src/pv/file.c:247
+msgid "failed to stat file"
+msgstr "échec à statuer sur le fichier"
+
+#: src/pv/file.c:256
+msgid "failed to stat output file"
+msgstr "échec à statuer sur le fichier de sortie"
+
+#: src/pv/file.c:279
+msgid "input file is output file"
+msgstr "fichiers d'entré et de sortie sont les mêmes"
+
+#: src/pv/display.c:117
+msgid "yzafpnum kMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:122
+msgid "yzafpnum KMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:532 src/pv/transfer.c:679
+msgid "buffer allocation failed"
+msgstr "l'allocation de mémoire tampon a échoué"
+
+#: src/pv/display.c:592 src/pv/transfer.c:458
+msgid "B"
+msgstr "O"
+
+#: src/pv/display.c:628 src/pv/display.c:636
+msgid "/s"
+msgstr "/s"
+
+#: src/pv/display.c:628 src/pv/display.c:636
+#, fuzzy
+msgid "B/s"
+msgstr "O/s"
+
+#: src/pv/display.c:671 src/pv/display.c:676 src/pv/display.c:738
+msgid "ETA"
+msgstr "ETA"
+
+#: src/pv/transfer.c:341
+msgid "read failed"
+msgstr "la lecture a échoué"
+
+#: src/pv/transfer.c:359
+msgid "warning: read errors detected"
+msgstr ""
+
+#: src/pv/transfer.c:374
+msgid "file is not seekable"
+msgstr ""
+
+#: src/pv/transfer.c:437
+#, fuzzy
+msgid "failed to seek past error"
+msgstr "échec à statuer sur le fichier"
+
+#: src/pv/transfer.c:456
+msgid "skipped past read error"
+msgstr ""
+
+#: src/pv/transfer.c:631
+msgid "write failed"
+msgstr "l'écriture a échoué"
+
+#: src/pv/transfer.c:770
+msgid "select call failed"
+msgstr "appel de sélection a échoué"
+
+#: src/pv/state.c:33
+msgid "none"
+msgstr ""
+
+#: src/main/version.c:17
+#, fuzzy, c-format
+msgid "%s %s - Copyright %s %s"
+msgstr "%s %s - Copyright(C) %s %s"
+
+#: src/main/version.c:20
+#, c-format
+msgid "Web site: %s"
+msgstr "Site Web: %s"
+
+#: src/main/version.c:23
+msgid ""
+"This program is free software, and is being distributed under the\n"
+"terms of the Artistic License 2.0."
+msgstr ""
+"Ce programme est un logiciel libre et est distribué sous les termes\n"
+"anglophone de la Licence Artistique 2.0 (Artistic License 2.0)."
+
+#: src/main/version.c:28
+msgid ""
+"This program is distributed in the hope that it will be useful,\n"
+"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+msgstr ""
+"Ce programme est distribué dans l'espoir qu'il sera utile, mais\n"
+"SANS GARANTIE AUCUNE; ni même de garantie sous-entendu que le\n"
+"programme soit COMMERCIALISABLE ou APPLICABLE À UNE TÂCHE\n"
+"PARTICULIÈRE."
+
+#: src/main/main.c:73
+#, fuzzy
+msgid "state allocation failed"
+msgstr "l'allocation de mémoire tampon a échoué"
+
+#: src/main/options.c:98
+#, c-format
+msgid "%s: option structure allocation failed (%s)"
+msgstr "%s: l'allocation pour la structure d'une option a echoué (%s)"
+
+#: src/main/options.c:114
+#, c-format
+msgid "%s: option structure argv allocation failed (%s)"
+msgstr "%s: l'allocation pour la structure de l'option argv a echoué (%s)"
+
+#: src/main/options.c:154
+msgid "integer argument expected"
+msgstr "Valeur entière attendue"
+
+#: src/main/options.c:165
+msgid "numeric argument expected"
+msgstr "Valeur numérique attendue"
+
+#: src/main/options.c:176
+msgid "process ID or pid:fd pair expected"
+msgstr ""
+
+#: src/main/options.c:183
+msgid "invalid process ID"
+msgstr ""
+
+#: src/main/options.c:317
+#, c-format
+msgid "Try `%s --help' for more information."
+msgstr "Essayez `%s --help' pour plus d'information."
+
+#: src/main/options.c:321
+#, fuzzy, c-format
+msgid "Try `%s -h' for more information."
+msgstr "Essayez `%s -h' pour plus d'information."
+
+#: src/main/options.c:338
+#, c-format
+msgid ""
+"%s: cannot use line mode or transfer modifier options when watching file "
+"descriptors"
+msgstr ""
+
+#: src/main/options.c:348
+#, c-format
+msgid "%s: cannot use cursor positioning when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:358
+#, c-format
+msgid "%s: cannot use remote control when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:368
+#, c-format
+msgid "%s: cannot transfer files when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:378
+#, c-format
+msgid "%s: -d: not available on systems without /proc/self/fdinfo"
+msgstr ""
+
+#: src/main/remote.c:218
+msgid "message not received"
+msgstr ""
+
+#: src/main/remote.c:324
+msgid "IPC not supported on this system"
+msgstr ""
+
+#: src/main/help.c:31
+msgid "show progress bar"
+msgstr "affiche la barre de progression"
+
+#: src/main/help.c:33
+msgid "show elapsed time"
+msgstr "affiche le temps écoulé"
+
+#: src/main/help.c:35
+msgid "show estimated time of arrival (completion)"
+msgstr "affiche l'heure approximative de l'achèvement de la tâche"
+
+#: src/main/help.c:38
+#, fuzzy
+msgid "show absolute estimated time of arrival (completion)"
+msgstr "affiche l'heure approximative de l'achèvement de la tâche"
+
+#: src/main/help.c:40
+msgid "show data transfer rate counter"
+msgstr "affiche le taux de tranfert des données"
+
+#: src/main/help.c:42
+msgid "show data transfer average rate counter"
+msgstr ""
+
+#: src/main/help.c:44
+msgid "show number of bytes transferred"
+msgstr "affiche le nombre d'octets transférés"
+
+#: src/main/help.c:46
+msgid "show percentage of transfer buffer in use"
+msgstr ""
+
+#: src/main/help.c:47
+msgid "NUM"
+msgstr ""
+
+#: src/main/help.c:48
+msgid "show NUM bytes last written"
+msgstr ""
+
+#: src/main/help.c:49
+#, fuzzy
+msgid "FORMAT"
+msgstr "TAUX"
+
+#: src/main/help.c:50
+msgid "set output format to FORMAT"
+msgstr ""
+
+#: src/main/help.c:52
+msgid "output percentages, not visual information"
+msgstr "imprime en pourcentage, pas les informations visuelles"
+
+#: src/main/help.c:54
+msgid "do not output any transfer information at all"
+msgstr "n'afficher aucune information de transfert"
+
+#: src/main/help.c:57
+msgid "display nothing until first byte transferred"
+msgstr "ne rien afficher avant qu'au moins un octet soit tranféré"
+
+#: src/main/help.c:58 src/main/help.c:66
+msgid "SEC"
+msgstr "SEC"
+
+#: src/main/help.c:59
+#, fuzzy
+msgid "display nothing until SEC seconds have passed"
+msgstr "ne rien afficher avant qu'au moins un octet soit tranféré"
+
+#: src/main/help.c:60
+msgid "SIZE"
+msgstr "TAILLE"
+
+#: src/main/help.c:61
+msgid "set estimated data size to SIZE bytes"
+msgstr "ajuste la taille estimée des données à TAILLE octets"
+
+#: src/main/help.c:63
+msgid "count lines instead of bytes"
+msgstr "compte les lignes au lieu des octets"
+
+#: src/main/help.c:65
+msgid "lines are null-terminated"
+msgstr ""
+
+#: src/main/help.c:67
+msgid "update every SEC seconds"
+msgstr "mise-à-jour toute les SEC secondes"
+
+#: src/main/help.c:68
+msgid "WIDTH"
+msgstr "LARGEUR"
+
+#: src/main/help.c:69
+msgid "assume terminal is WIDTH characters wide"
+msgstr "présumer la largeur du terminal à LARGEUR caractères"
+
+#: src/main/help.c:70
+msgid "HEIGHT"
+msgstr "HAUTEUR"
+
+#: src/main/help.c:71
+msgid "assume terminal is HEIGHT rows high"
+msgstr "présumer la hauteur du terminal à HAUTEUR lignes"
+
+#: src/main/help.c:72
+msgid "NAME"
+msgstr "NOM"
+
+#: src/main/help.c:73
+msgid "prefix visual information with NAME"
+msgstr "préfixer les informations visuelles avec NOM"
+
+#: src/main/help.c:75
+msgid "output even if standard error is not a terminal"
+msgstr "imprime vers la sortie d'erreur même si ce n'est pas un terminal"
+
+#: src/main/help.c:77
+msgid "use cursor positioning escape sequences"
+msgstr "utiliser les séquences d'échappements de positionnement de curseur"
+
+#: src/main/help.c:79
+msgid "RATE"
+msgstr "TAUX"
+
+#: src/main/help.c:80
+msgid "limit transfer to RATE bytes per second"
+msgstr "limite le taux de transfer à TAUX octets par seconde"
+
+#: src/main/help.c:81
+msgid "BYTES"
+msgstr "OCTETS"
+
+#: src/main/help.c:82
+msgid "use a buffer size of BYTES"
+msgstr "Utiliser une mémoire tampon de OCTETS octets"
+
+#: src/main/help.c:84
+msgid "never use splice(), always use read/write"
+msgstr ""
+
+#: src/main/help.c:86
+msgid "skip read errors in input"
+msgstr ""
+
+#: src/main/help.c:88
+#, fuzzy
+msgid "stop after --size bytes have been transferred"
+msgstr "affiche le nombre d'octets transférés"
+
+#: src/main/help.c:90
+msgid "PID"
+msgstr "PID"
+
+#: src/main/help.c:91
+msgid "update settings of process PID"
+msgstr "mettre-à-jour la configuration du processus PID"
+
+#: src/main/help.c:94
+msgid "FILE"
+msgstr ""
+
+#: src/main/help.c:95
+msgid "save process ID in FILE"
+msgstr ""
+
+#: src/main/help.c:97
+msgid "PID[:FD]"
+msgstr ""
+
+#: src/main/help.c:98
+msgid "watch file FD opened by process PID"
+msgstr ""
+
+#: src/main/help.c:101
+msgid "show this help and exit"
+msgstr "afficher cette aide puis quitter"
+
+#: src/main/help.c:103
+msgid "show version information and exit"
+msgstr "afficher la version puis quitter"
+
+#: src/main/help.c:109
+#, c-format
+msgid "Usage: %s [OPTION] [FILE]..."
+msgstr "Utilisation : %s [OPTIONS] [FICHIER]..."
+
+#: src/main/help.c:112
+msgid ""
+"Concatenate FILE(s), or standard input, to standard output,\n"
+"with monitoring."
+msgstr ""
+"Concatène FICHIER(s), ou l'entrée standard, sur la sortie standard\n"
+"avec monitorage."
+
+#: src/main/help.c:201
+msgid ""
+"Debugging is enabled; export the DEBUG environment variable to define the\n"
+"output filename.\n"
+msgstr ""
+
+#: src/main/help.c:205
+#, c-format
+msgid "Please report any bugs to %s."
+msgstr "SVP rapporter touts bogues à %s."
+
+#~ msgid "failed to lock terminal"
+#~ msgstr "l'ouverture du terminal a échoué"
diff --git a/src/nls/pl.po b/src/nls/pl.po
new file mode 100644
index 0000000..51867a8
--- /dev/null
+++ b/src/nls/pl.po
@@ -0,0 +1,491 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-06-30 22:21+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO-8859-2\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: src/pv/watchpid.c:51 src/pv/watchpid.c:66 src/pv/watchpid.c:76
+#: src/pv/watchpid.c:107 src/pv/loop.c:519 src/pv/loop.c:580 src/pv/loop.c:632
+msgid "pid"
+msgstr ""
+
+#: src/pv/watchpid.c:68 src/pv/watchpid.c:78 src/pv/watchpid.c:109
+msgid "fd"
+msgstr ""
+
+#: src/pv/watchpid.c:112
+msgid "not a regular file or block device"
+msgstr ""
+
+#: src/pv/cursor.c:53
+#, fuzzy
+msgid "failed to get terminal name"
+msgstr "nie uda³o siê otworzyæ terminala"
+
+# "Przewidywany czas ukoñczenia" for ETA is too long
+#: src/pv/cursor.c:96
+#, fuzzy
+msgid "failed to open lock file"
+msgstr "nie uda³o siê zamkn±æ pliku"
+
+#: src/pv/cursor.c:129
+msgid "lock attempt failed"
+msgstr ""
+
+#: src/pv/cursor.c:369
+msgid "failed to open terminal"
+msgstr "nie uda³o siê otworzyæ terminala"
+
+#: src/pv/file.c:118
+#, fuzzy
+msgid "failed to seek to start of output"
+msgstr "nie uda³o siê wykonaæ operacji stat na pliku wyj¶ciowym"
+
+# "Przewidywany czas ukoñczenia" for ETA is too long
+#: src/pv/file.c:214
+msgid "failed to close file"
+msgstr "nie uda³o siê zamkn±æ pliku"
+
+#: src/pv/file.c:237
+msgid "failed to read file"
+msgstr "nie uda³o siê odczytaæ pliku"
+
+#: src/pv/file.c:247
+msgid "failed to stat file"
+msgstr "nie uda³o siê wykonaæ operacji stat na pliku"
+
+#: src/pv/file.c:256
+msgid "failed to stat output file"
+msgstr "nie uda³o siê wykonaæ operacji stat na pliku wyj¶ciowym"
+
+#: src/pv/file.c:279
+msgid "input file is output file"
+msgstr "plik wej¶ciowy jest zarazem plikiem wyj¶ciowym"
+
+#: src/pv/display.c:117
+msgid "yzafpnum kMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:122
+msgid "yzafpnum KMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:532 src/pv/transfer.c:679
+msgid "buffer allocation failed"
+msgstr "nie uda³o siê zaalokowaæ bufora"
+
+#: src/pv/display.c:592 src/pv/transfer.c:458
+msgid "B"
+msgstr "B"
+
+#: src/pv/display.c:628 src/pv/display.c:636
+msgid "/s"
+msgstr "/s"
+
+#: src/pv/display.c:628 src/pv/display.c:636
+msgid "B/s"
+msgstr "B/s"
+
+#: src/pv/display.c:671 src/pv/display.c:676 src/pv/display.c:738
+msgid "ETA"
+msgstr "ETA"
+
+#: src/pv/transfer.c:341
+msgid "read failed"
+msgstr "b³±d odczytu"
+
+#: src/pv/transfer.c:359
+msgid "warning: read errors detected"
+msgstr ""
+
+#: src/pv/transfer.c:374
+msgid "file is not seekable"
+msgstr ""
+
+#: src/pv/transfer.c:437
+#, fuzzy
+msgid "failed to seek past error"
+msgstr "nie uda³o siê wykonaæ operacji stat na pliku"
+
+#: src/pv/transfer.c:456
+msgid "skipped past read error"
+msgstr ""
+
+#: src/pv/transfer.c:631
+msgid "write failed"
+msgstr "b³±d zapisu"
+
+#: src/pv/transfer.c:770
+msgid "select call failed"
+msgstr "nie uda³o siê wywo³aæ funkcji select"
+
+#: src/pv/state.c:33
+msgid "none"
+msgstr ""
+
+#: src/main/version.c:17
+#, fuzzy, c-format
+msgid "%s %s - Copyright %s %s"
+msgstr "%s %s - Prawa autorskie (C) %s %s"
+
+#: src/main/version.c:20
+#, c-format
+msgid "Web site: %s"
+msgstr "Strona WWW: %s"
+
+#: src/main/version.c:23
+msgid ""
+"This program is free software, and is being distributed under the\n"
+"terms of the Artistic License 2.0."
+msgstr ""
+
+#: src/main/version.c:28
+msgid ""
+"This program is distributed in the hope that it will be useful,\n"
+"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+msgstr ""
+
+#: src/main/main.c:73
+#, fuzzy
+msgid "state allocation failed"
+msgstr "nie uda³o siê zaalokowaæ bufora"
+
+#: src/main/options.c:98
+#, c-format
+msgid "%s: option structure allocation failed (%s)"
+msgstr "%s: nie uda³o siê zaalokowaæ struktur opcji (%s)"
+
+#: src/main/options.c:114
+#, c-format
+msgid "%s: option structure argv allocation failed (%s)"
+msgstr "%s: nie uda³o siê zaalokowaæ struktur opcji argv (%s)"
+
+#: src/main/options.c:154
+msgid "integer argument expected"
+msgstr ""
+
+#: src/main/options.c:165
+msgid "numeric argument expected"
+msgstr ""
+
+#: src/main/options.c:176
+msgid "process ID or pid:fd pair expected"
+msgstr ""
+
+#: src/main/options.c:183
+msgid "invalid process ID"
+msgstr ""
+
+#: src/main/options.c:317
+#, c-format
+msgid "Try `%s --help' for more information."
+msgstr "Spróbuj `%s --help' by uzyskaæ wiêcej informacji"
+
+#: src/main/options.c:321
+#, fuzzy, c-format
+msgid "Try `%s -h' for more information."
+msgstr "Spróbuj `%s -h' by uzyskaæ wiêcej informacji"
+
+#: src/main/options.c:338
+#, c-format
+msgid ""
+"%s: cannot use line mode or transfer modifier options when watching file "
+"descriptors"
+msgstr ""
+
+#: src/main/options.c:348
+#, c-format
+msgid "%s: cannot use cursor positioning when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:358
+#, c-format
+msgid "%s: cannot use remote control when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:368
+#, c-format
+msgid "%s: cannot transfer files when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:378
+#, c-format
+msgid "%s: -d: not available on systems without /proc/self/fdinfo"
+msgstr ""
+
+#: src/main/remote.c:218
+msgid "message not received"
+msgstr ""
+
+#: src/main/remote.c:324
+msgid "IPC not supported on this system"
+msgstr ""
+
+#: src/main/help.c:31
+msgid "show progress bar"
+msgstr "poka¿ pasek postêpu"
+
+#: src/main/help.c:33
+msgid "show elapsed time"
+msgstr "poka¿ up³ywaj±cy czas"
+
+#: src/main/help.c:35
+msgid "show estimated time of arrival (completion)"
+msgstr "poka¿ szacowany czas ukoñczenia"
+
+#: src/main/help.c:38
+#, fuzzy
+msgid "show absolute estimated time of arrival (completion)"
+msgstr "poka¿ szacowany czas ukoñczenia"
+
+#: src/main/help.c:40
+msgid "show data transfer rate counter"
+msgstr "poka¿ licznik prêdko¶ci przesy³ania"
+
+#: src/main/help.c:42
+msgid "show data transfer average rate counter"
+msgstr ""
+
+#: src/main/help.c:44
+msgid "show number of bytes transferred"
+msgstr "poka¿ ilo¶æ przes³anych bajtów"
+
+#: src/main/help.c:46
+msgid "show percentage of transfer buffer in use"
+msgstr ""
+
+#: src/main/help.c:47
+msgid "NUM"
+msgstr ""
+
+#: src/main/help.c:48
+msgid "show NUM bytes last written"
+msgstr ""
+
+#: src/main/help.c:49
+msgid "FORMAT"
+msgstr ""
+
+#: src/main/help.c:50
+msgid "set output format to FORMAT"
+msgstr ""
+
+#: src/main/help.c:52
+msgid "output percentages, not visual information"
+msgstr "w¶wietl wyj¶cie procentowo, bez graficznej prezentacji"
+
+#: src/main/help.c:54
+msgid "do not output any transfer information at all"
+msgstr "nie wy¶wietlaj ¿adnych informacji o przesy³aniu"
+
+#: src/main/help.c:57
+msgid "display nothing until first byte transferred"
+msgstr "nie wy¶wietlaj nic a¿ do pierwszego przes³anego bajtu"
+
+#: src/main/help.c:58 src/main/help.c:66
+msgid "SEC"
+msgstr "WARTO¦Æ"
+
+#: src/main/help.c:59
+#, fuzzy
+msgid "display nothing until SEC seconds have passed"
+msgstr "nie wy¶wietlaj nic a¿ do pierwszego przes³anego bajtu"
+
+#: src/main/help.c:60
+msgid "SIZE"
+msgstr "WARTO¦Æ"
+
+#: src/main/help.c:61
+msgid "set estimated data size to SIZE bytes"
+msgstr "ustaw oczekiwany rozmiar danych na WARTO¦Æ bajtów"
+
+#: src/main/help.c:63
+msgid "count lines instead of bytes"
+msgstr ""
+
+#: src/main/help.c:65
+msgid "lines are null-terminated"
+msgstr ""
+
+#: src/main/help.c:67
+msgid "update every SEC seconds"
+msgstr "aktualizuj co ka¿de WARTO¦Æ sekund"
+
+#: src/main/help.c:68
+msgid "WIDTH"
+msgstr "WARTO¦Æ"
+
+#: src/main/help.c:69
+msgid "assume terminal is WIDTH characters wide"
+msgstr "przyjmij, ¿e terminal ma szeroko¶æ WARTO¦Æ znaków"
+
+#: src/main/help.c:70
+msgid "HEIGHT"
+msgstr ""
+
+#: src/main/help.c:71
+msgid "assume terminal is HEIGHT rows high"
+msgstr "przyjmij, ¿e terminal ma szeroko¶æ WARTO¦Æ znaków"
+
+#: src/main/help.c:72
+msgid "NAME"
+msgstr "NAZWA"
+
+#: src/main/help.c:73
+msgid "prefix visual information with NAME"
+msgstr "poprzed¼ informacje prefiksem NAZWA"
+
+#: src/main/help.c:75
+msgid "output even if standard error is not a terminal"
+msgstr "poka¿ wyj¶cie nawet gdy b³êdy nie s± typu terminal"
+
+#: src/main/help.c:77
+msgid "use cursor positioning escape sequences"
+msgstr "u¿ywaj sekwencji escape do pozycjonowania"
+
+#: src/main/help.c:79
+msgid "RATE"
+msgstr ""
+
+#: src/main/help.c:80
+msgid "limit transfer to RATE bytes per second"
+msgstr "ogranicz przesy³ane dane do RATE bajtów na sekundê"
+
+#: src/main/help.c:81
+msgid "BYTES"
+msgstr ""
+
+#: src/main/help.c:82
+msgid "use a buffer size of BYTES"
+msgstr ""
+
+#: src/main/help.c:84
+msgid "never use splice(), always use read/write"
+msgstr ""
+
+#: src/main/help.c:86
+msgid "skip read errors in input"
+msgstr ""
+
+#: src/main/help.c:88
+#, fuzzy
+msgid "stop after --size bytes have been transferred"
+msgstr "poka¿ ilo¶æ przes³anych bajtów"
+
+#: src/main/help.c:90
+msgid "PID"
+msgstr ""
+
+#: src/main/help.c:91
+msgid "update settings of process PID"
+msgstr ""
+
+#: src/main/help.c:94
+msgid "FILE"
+msgstr ""
+
+#: src/main/help.c:95
+msgid "save process ID in FILE"
+msgstr ""
+
+#: src/main/help.c:97
+msgid "PID[:FD]"
+msgstr ""
+
+#: src/main/help.c:98
+msgid "watch file FD opened by process PID"
+msgstr ""
+
+#: src/main/help.c:101
+msgid "show this help and exit"
+msgstr "wy¶wietl te informacje z pomoc± i wyjd¼"
+
+#: src/main/help.c:103
+msgid "show version information and exit"
+msgstr "wy¶wietl informacje o wersji i wyjd¼"
+
+#: src/main/help.c:109
+#, c-format
+msgid "Usage: %s [OPTION] [FILE]..."
+msgstr "Sposób u¿ycia: %s [OPCJA] [PLIK]..."
+
+#: src/main/help.c:112
+msgid ""
+"Concatenate FILE(s), or standard input, to standard output,\n"
+"with monitoring."
+msgstr ""
+"£±czenie PLIK(ów) lub wej¶cia standardowego, do wyj¶cia standardowego\n"
+"z monitoringiem."
+
+#: src/main/help.c:201
+msgid ""
+"Debugging is enabled; export the DEBUG environment variable to define the\n"
+"output filename.\n"
+msgstr ""
+
+#: src/main/help.c:205
+#, c-format
+msgid "Please report any bugs to %s."
+msgstr "Proszê przesy³aæ zg³oszenia b³êdów do %s."
+
+#~ msgid "failed to lock terminal"
+#~ msgstr "nie uda³o siê otworzyæ terminala"
+
+#~ msgid "G"
+#~ msgstr "GB"
+
+#~ msgid "M"
+#~ msgstr "MB"
+
+#~ msgid "k"
+#~ msgstr "kB"
+
+#~ msgid "GB"
+#~ msgstr "GB"
+
+#~ msgid "MB"
+#~ msgstr "MB"
+
+#~ msgid "kB"
+#~ msgstr "kB"
+
+#~ msgid "M/s"
+#~ msgstr "MB/s"
+
+#~ msgid "k/s"
+#~ msgstr "kB/s"
+
+#~ msgid "/s "
+#~ msgstr "B/s"
+
+#~ msgid "GB/s"
+#~ msgstr "GB/s"
+
+#~ msgid "MB/s"
+#~ msgstr "MB/s"
+
+#~ msgid "kB/s"
+#~ msgstr "kB/s"
+
+#~ msgid "For more information, please run `%s --license'."
+#~ msgstr "By uzyskaæ wiêcej informacji wprowad¼ `%s --license'."
+
+#~ msgid "For more information, please run `%s -l'."
+#~ msgstr "By uzyskaæ wiêcej informacji wprowad¼ `%s -l'."
+
+#~ msgid "show the license this program is distributed under"
+#~ msgstr "wy¶wietl informacje o licencji pod któr± program jest rozprowadzany"
+
+#~ msgid "%s %s - Copyright (C) %s %s"
+#~ msgstr "%s %s - Prawa autorskie (C) %s %s"
diff --git a/src/nls/pt.po b/src/nls/pt.po
new file mode 100644
index 0000000..35535c9
--- /dev/null
+++ b/src/nls/pt.po
@@ -0,0 +1,446 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-06-30 22:21+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO-8859-15\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: src/pv/watchpid.c:51 src/pv/watchpid.c:66 src/pv/watchpid.c:76
+#: src/pv/watchpid.c:107 src/pv/loop.c:519 src/pv/loop.c:580 src/pv/loop.c:632
+msgid "pid"
+msgstr ""
+
+#: src/pv/watchpid.c:68 src/pv/watchpid.c:78 src/pv/watchpid.c:109
+msgid "fd"
+msgstr ""
+
+#: src/pv/watchpid.c:112
+msgid "not a regular file or block device"
+msgstr ""
+
+#: src/pv/cursor.c:53
+#, fuzzy
+msgid "failed to get terminal name"
+msgstr "erro abrindo o terminal"
+
+#: src/pv/cursor.c:96
+#, fuzzy
+msgid "failed to open lock file"
+msgstr "erro fechando o arquivo"
+
+#: src/pv/cursor.c:129
+msgid "lock attempt failed"
+msgstr ""
+
+#: src/pv/cursor.c:369
+msgid "failed to open terminal"
+msgstr "erro abrindo o terminal"
+
+#: src/pv/file.c:118
+#, fuzzy
+msgid "failed to seek to start of output"
+msgstr "erro obtendo informações do arquivo de saída"
+
+#: src/pv/file.c:214
+msgid "failed to close file"
+msgstr "erro fechando o arquivo"
+
+#: src/pv/file.c:237
+msgid "failed to read file"
+msgstr "erro lendo o arquivo"
+
+#: src/pv/file.c:247
+msgid "failed to stat file"
+msgstr "erro obtendo informações do arquivo"
+
+#: src/pv/file.c:256
+msgid "failed to stat output file"
+msgstr "erro obtendo informações do arquivo de saída"
+
+#: src/pv/file.c:279
+msgid "input file is output file"
+msgstr "os arquivos de entrada e saída são o mesmo"
+
+#: src/pv/display.c:117
+msgid "yzafpnum kMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:122
+msgid "yzafpnum KMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:532 src/pv/transfer.c:679
+msgid "buffer allocation failed"
+msgstr "erro alocando o buffer"
+
+#: src/pv/display.c:592 src/pv/transfer.c:458
+msgid "B"
+msgstr ""
+
+#: src/pv/display.c:628 src/pv/display.c:636
+msgid "/s"
+msgstr ""
+
+#: src/pv/display.c:628 src/pv/display.c:636
+msgid "B/s"
+msgstr "B/s"
+
+#: src/pv/display.c:671 src/pv/display.c:676 src/pv/display.c:738
+msgid "ETA"
+msgstr "ETA"
+
+#: src/pv/transfer.c:341
+msgid "read failed"
+msgstr "erro de leitura"
+
+#: src/pv/transfer.c:359
+msgid "warning: read errors detected"
+msgstr ""
+
+#: src/pv/transfer.c:374
+msgid "file is not seekable"
+msgstr ""
+
+#: src/pv/transfer.c:437
+#, fuzzy
+msgid "failed to seek past error"
+msgstr "erro obtendo informações do arquivo"
+
+#: src/pv/transfer.c:456
+msgid "skipped past read error"
+msgstr ""
+
+#: src/pv/transfer.c:631
+msgid "write failed"
+msgstr "erro de gravação"
+
+#: src/pv/transfer.c:770
+msgid "select call failed"
+msgstr "erro na chamada da função select"
+
+#: src/pv/state.c:33
+msgid "none"
+msgstr ""
+
+#: src/main/version.c:17
+#, fuzzy, c-format
+msgid "%s %s - Copyright %s %s"
+msgstr "%s %s - Copyright(C) %s %s"
+
+#: src/main/version.c:20
+#, c-format
+msgid "Web site: %s"
+msgstr "Site: %s"
+
+#: src/main/version.c:23
+msgid ""
+"This program is free software, and is being distributed under the\n"
+"terms of the Artistic License 2.0."
+msgstr ""
+"Este programa é um software livre, distribuído sob os termos da\n"
+"Licença Artística."
+
+#: src/main/version.c:28
+msgid ""
+"This program is distributed in the hope that it will be useful,\n"
+"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+msgstr ""
+"Este programa é distribuído com o intuito de que seja útil,\n"
+"porém SEM QUAISQUER GARANTIAS; sem inclusive as garantias implícitas de\n"
+"COMERCIALIZAÇÃO e ADEQUAÇÃO A OBJETIVOS PARTICULARES."
+
+#: src/main/main.c:73
+#, fuzzy
+msgid "state allocation failed"
+msgstr "erro alocando o buffer"
+
+#: src/main/options.c:98
+#, c-format
+msgid "%s: option structure allocation failed (%s)"
+msgstr "%s: erro na alocação da estrutura de opções (%s)"
+
+#: src/main/options.c:114
+#, c-format
+msgid "%s: option structure argv allocation failed (%s)"
+msgstr "%s: erro na alocação da estrutura de opções argv (%s)"
+
+#: src/main/options.c:154
+msgid "integer argument expected"
+msgstr ""
+
+#: src/main/options.c:165
+msgid "numeric argument expected"
+msgstr ""
+
+#: src/main/options.c:176
+msgid "process ID or pid:fd pair expected"
+msgstr ""
+
+#: src/main/options.c:183
+msgid "invalid process ID"
+msgstr ""
+
+#: src/main/options.c:317
+#, c-format
+msgid "Try `%s --help' for more information."
+msgstr "Tente `%s --help' para maiores informações."
+
+#: src/main/options.c:321
+#, fuzzy, c-format
+msgid "Try `%s -h' for more information."
+msgstr "Tente `%s -h' para maiores informações."
+
+#: src/main/options.c:338
+#, c-format
+msgid ""
+"%s: cannot use line mode or transfer modifier options when watching file "
+"descriptors"
+msgstr ""
+
+#: src/main/options.c:348
+#, c-format
+msgid "%s: cannot use cursor positioning when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:358
+#, c-format
+msgid "%s: cannot use remote control when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:368
+#, c-format
+msgid "%s: cannot transfer files when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:378
+#, c-format
+msgid "%s: -d: not available on systems without /proc/self/fdinfo"
+msgstr ""
+
+#: src/main/remote.c:218
+msgid "message not received"
+msgstr ""
+
+#: src/main/remote.c:324
+msgid "IPC not supported on this system"
+msgstr ""
+
+#: src/main/help.c:31
+msgid "show progress bar"
+msgstr "exibe barra de progressão"
+
+#: src/main/help.c:33
+msgid "show elapsed time"
+msgstr "exibe tempo passado"
+
+#: src/main/help.c:35
+msgid "show estimated time of arrival (completion)"
+msgstr "exibe o tempo estimado de término"
+
+#: src/main/help.c:38
+#, fuzzy
+msgid "show absolute estimated time of arrival (completion)"
+msgstr "exibe o tempo estimado de término"
+
+#: src/main/help.c:40
+msgid "show data transfer rate counter"
+msgstr "exibe a taxa de transferência"
+
+#: src/main/help.c:42
+msgid "show data transfer average rate counter"
+msgstr ""
+
+#: src/main/help.c:44
+msgid "show number of bytes transferred"
+msgstr "exibe a quantidade de bytes transferidos"
+
+#: src/main/help.c:46
+msgid "show percentage of transfer buffer in use"
+msgstr ""
+
+#: src/main/help.c:47
+msgid "NUM"
+msgstr ""
+
+#: src/main/help.c:48
+msgid "show NUM bytes last written"
+msgstr ""
+
+#: src/main/help.c:49
+msgid "FORMAT"
+msgstr ""
+
+#: src/main/help.c:50
+msgid "set output format to FORMAT"
+msgstr ""
+
+#: src/main/help.c:52
+msgid "output percentages, not visual information"
+msgstr "exibe apenas as porcentagens, sem informações visuais"
+
+#: src/main/help.c:54
+msgid "do not output any transfer information at all"
+msgstr "executa programa sem exibir quaisquer informações"
+
+#: src/main/help.c:57
+msgid "display nothing until first byte transferred"
+msgstr "não exibe nada até iniciar o processamento"
+
+#: src/main/help.c:58 src/main/help.c:66
+msgid "SEC"
+msgstr ""
+
+#: src/main/help.c:59
+#, fuzzy
+msgid "display nothing until SEC seconds have passed"
+msgstr "não exibe nada até iniciar o processamento"
+
+#: src/main/help.c:60
+msgid "SIZE"
+msgstr ""
+
+#: src/main/help.c:61
+msgid "set estimated data size to SIZE bytes"
+msgstr "seta a quantidade estimada de dados em SIZE bytes"
+
+#: src/main/help.c:63
+msgid "count lines instead of bytes"
+msgstr ""
+
+#: src/main/help.c:65
+msgid "lines are null-terminated"
+msgstr ""
+
+#: src/main/help.c:67
+msgid "update every SEC seconds"
+msgstr "atualiza informações a cada SEC segundos"
+
+#: src/main/help.c:68
+msgid "WIDTH"
+msgstr ""
+
+#: src/main/help.c:69
+msgid "assume terminal is WIDTH characters wide"
+msgstr "assume que terminal possui WIDTH caracteres de largura"
+
+#: src/main/help.c:70
+msgid "HEIGHT"
+msgstr ""
+
+#: src/main/help.c:71
+msgid "assume terminal is HEIGHT rows high"
+msgstr "assume que terminal possui WIDTH caracteres de largura"
+
+#: src/main/help.c:72
+msgid "NAME"
+msgstr ""
+
+#: src/main/help.c:73
+msgid "prefix visual information with NAME"
+msgstr "exibe NAME antes das demais informações"
+
+#: src/main/help.c:75
+msgid "output even if standard error is not a terminal"
+msgstr "gera dados mesmo que a saída de erro seja redirecionada"
+
+#: src/main/help.c:77
+msgid "use cursor positioning escape sequences"
+msgstr "utiliza caracteres de escape para posicionar o cursor"
+
+#: src/main/help.c:79
+msgid "RATE"
+msgstr ""
+
+#: src/main/help.c:80
+msgid "limit transfer to RATE bytes per second"
+msgstr "limita a transferência a RATE bytes por segundo"
+
+#: src/main/help.c:81
+msgid "BYTES"
+msgstr ""
+
+#: src/main/help.c:82
+msgid "use a buffer size of BYTES"
+msgstr ""
+
+#: src/main/help.c:84
+msgid "never use splice(), always use read/write"
+msgstr ""
+
+#: src/main/help.c:86
+msgid "skip read errors in input"
+msgstr ""
+
+#: src/main/help.c:88
+#, fuzzy
+msgid "stop after --size bytes have been transferred"
+msgstr "exibe a quantidade de bytes transferidos"
+
+#: src/main/help.c:90
+msgid "PID"
+msgstr ""
+
+#: src/main/help.c:91
+msgid "update settings of process PID"
+msgstr ""
+
+#: src/main/help.c:94
+msgid "FILE"
+msgstr ""
+
+#: src/main/help.c:95
+msgid "save process ID in FILE"
+msgstr ""
+
+#: src/main/help.c:97
+msgid "PID[:FD]"
+msgstr ""
+
+#: src/main/help.c:98
+msgid "watch file FD opened by process PID"
+msgstr ""
+
+#: src/main/help.c:101
+msgid "show this help and exit"
+msgstr "exibe esta tela de ajuda e termina"
+
+#: src/main/help.c:103
+msgid "show version information and exit"
+msgstr "exibe a versão e termina"
+
+#: src/main/help.c:109
+#, c-format
+msgid "Usage: %s [OPTION] [FILE]..."
+msgstr "Uso: %s [OPÇÕES] [ARQUIVOS]..."
+
+#: src/main/help.c:112
+msgid ""
+"Concatenate FILE(s), or standard input, to standard output,\n"
+"with monitoring."
+msgstr ""
+"Concatena ARQUIVO(s) ou a entrada padrão e grava na saída padrão,\n"
+"com monitoramento."
+
+#: src/main/help.c:201
+msgid ""
+"Debugging is enabled; export the DEBUG environment variable to define the\n"
+"output filename.\n"
+msgstr ""
+
+#: src/main/help.c:205
+#, c-format
+msgid "Please report any bugs to %s."
+msgstr "Por favor, reporte quaisquer defeitos para %s."
+
+#~ msgid "failed to lock terminal"
+#~ msgstr "erro abrindo o terminal"
diff --git a/src/nls/pv.pot b/src/nls/pv.pot
new file mode 100644
index 0000000..671c0fb
--- /dev/null
+++ b/src/nls/pv.pot
@@ -0,0 +1,430 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-06-30 22:21+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: src/pv/watchpid.c:51 src/pv/watchpid.c:66 src/pv/watchpid.c:76
+#: src/pv/watchpid.c:107 src/pv/loop.c:519 src/pv/loop.c:580 src/pv/loop.c:632
+msgid "pid"
+msgstr ""
+
+#: src/pv/watchpid.c:68 src/pv/watchpid.c:78 src/pv/watchpid.c:109
+msgid "fd"
+msgstr ""
+
+#: src/pv/watchpid.c:112
+msgid "not a regular file or block device"
+msgstr ""
+
+#: src/pv/cursor.c:53
+msgid "failed to get terminal name"
+msgstr ""
+
+#: src/pv/cursor.c:96
+msgid "failed to open lock file"
+msgstr ""
+
+#: src/pv/cursor.c:129
+msgid "lock attempt failed"
+msgstr ""
+
+#: src/pv/cursor.c:369
+msgid "failed to open terminal"
+msgstr ""
+
+#: src/pv/file.c:118
+msgid "failed to seek to start of output"
+msgstr ""
+
+#: src/pv/file.c:214
+msgid "failed to close file"
+msgstr ""
+
+#: src/pv/file.c:237
+msgid "failed to read file"
+msgstr ""
+
+#: src/pv/file.c:247
+msgid "failed to stat file"
+msgstr ""
+
+#: src/pv/file.c:256
+msgid "failed to stat output file"
+msgstr ""
+
+#: src/pv/file.c:279
+msgid "input file is output file"
+msgstr ""
+
+#: src/pv/display.c:117
+msgid "yzafpnum kMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:122
+msgid "yzafpnum KMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:532 src/pv/transfer.c:679
+msgid "buffer allocation failed"
+msgstr ""
+
+#: src/pv/display.c:592 src/pv/transfer.c:458
+msgid "B"
+msgstr ""
+
+#: src/pv/display.c:628 src/pv/display.c:636
+msgid "/s"
+msgstr ""
+
+#: src/pv/display.c:628 src/pv/display.c:636
+msgid "B/s"
+msgstr ""
+
+#: src/pv/display.c:671 src/pv/display.c:676 src/pv/display.c:738
+msgid "ETA"
+msgstr ""
+
+#: src/pv/transfer.c:341
+msgid "read failed"
+msgstr ""
+
+#: src/pv/transfer.c:359
+msgid "warning: read errors detected"
+msgstr ""
+
+#: src/pv/transfer.c:374
+msgid "file is not seekable"
+msgstr ""
+
+#: src/pv/transfer.c:437
+msgid "failed to seek past error"
+msgstr ""
+
+#: src/pv/transfer.c:456
+msgid "skipped past read error"
+msgstr ""
+
+#: src/pv/transfer.c:631
+msgid "write failed"
+msgstr ""
+
+#: src/pv/transfer.c:770
+msgid "select call failed"
+msgstr ""
+
+#: src/pv/state.c:33
+msgid "none"
+msgstr ""
+
+#: src/main/version.c:17
+#, c-format
+msgid "%s %s - Copyright %s %s"
+msgstr ""
+
+#: src/main/version.c:20
+#, c-format
+msgid "Web site: %s"
+msgstr ""
+
+#: src/main/version.c:23
+msgid ""
+"This program is free software, and is being distributed under the\n"
+"terms of the Artistic License 2.0."
+msgstr ""
+
+#: src/main/version.c:28
+msgid ""
+"This program is distributed in the hope that it will be useful,\n"
+"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+msgstr ""
+
+#: src/main/main.c:73
+msgid "state allocation failed"
+msgstr ""
+
+#: src/main/options.c:98
+#, c-format
+msgid "%s: option structure allocation failed (%s)"
+msgstr ""
+
+#: src/main/options.c:114
+#, c-format
+msgid "%s: option structure argv allocation failed (%s)"
+msgstr ""
+
+#: src/main/options.c:154
+msgid "integer argument expected"
+msgstr ""
+
+#: src/main/options.c:165
+msgid "numeric argument expected"
+msgstr ""
+
+#: src/main/options.c:176
+msgid "process ID or pid:fd pair expected"
+msgstr ""
+
+#: src/main/options.c:183
+msgid "invalid process ID"
+msgstr ""
+
+#: src/main/options.c:317
+#, c-format
+msgid "Try `%s --help' for more information."
+msgstr ""
+
+#: src/main/options.c:321
+#, c-format
+msgid "Try `%s -h' for more information."
+msgstr ""
+
+#: src/main/options.c:338
+#, c-format
+msgid ""
+"%s: cannot use line mode or transfer modifier options when watching file "
+"descriptors"
+msgstr ""
+
+#: src/main/options.c:348
+#, c-format
+msgid "%s: cannot use cursor positioning when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:358
+#, c-format
+msgid "%s: cannot use remote control when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:368
+#, c-format
+msgid "%s: cannot transfer files when watching file descriptors"
+msgstr ""
+
+#: src/main/options.c:378
+#, c-format
+msgid "%s: -d: not available on systems without /proc/self/fdinfo"
+msgstr ""
+
+#: src/main/remote.c:218
+msgid "message not received"
+msgstr ""
+
+#: src/main/remote.c:324
+msgid "IPC not supported on this system"
+msgstr ""
+
+#: src/main/help.c:31
+msgid "show progress bar"
+msgstr ""
+
+#: src/main/help.c:33
+msgid "show elapsed time"
+msgstr ""
+
+#: src/main/help.c:35
+msgid "show estimated time of arrival (completion)"
+msgstr ""
+
+#: src/main/help.c:38
+msgid "show absolute estimated time of arrival (completion)"
+msgstr ""
+
+#: src/main/help.c:40
+msgid "show data transfer rate counter"
+msgstr ""
+
+#: src/main/help.c:42
+msgid "show data transfer average rate counter"
+msgstr ""
+
+#: src/main/help.c:44
+msgid "show number of bytes transferred"
+msgstr ""
+
+#: src/main/help.c:46
+msgid "show percentage of transfer buffer in use"
+msgstr ""
+
+#: src/main/help.c:47
+msgid "NUM"
+msgstr ""
+
+#: src/main/help.c:48
+msgid "show NUM bytes last written"
+msgstr ""
+
+#: src/main/help.c:49
+msgid "FORMAT"
+msgstr ""
+
+#: src/main/help.c:50
+msgid "set output format to FORMAT"
+msgstr ""
+
+#: src/main/help.c:52
+msgid "output percentages, not visual information"
+msgstr ""
+
+#: src/main/help.c:54
+msgid "do not output any transfer information at all"
+msgstr ""
+
+#: src/main/help.c:57
+msgid "display nothing until first byte transferred"
+msgstr ""
+
+#: src/main/help.c:58 src/main/help.c:66
+msgid "SEC"
+msgstr ""
+
+#: src/main/help.c:59
+msgid "display nothing until SEC seconds have passed"
+msgstr ""
+
+#: src/main/help.c:60
+msgid "SIZE"
+msgstr ""
+
+#: src/main/help.c:61
+msgid "set estimated data size to SIZE bytes"
+msgstr ""
+
+#: src/main/help.c:63
+msgid "count lines instead of bytes"
+msgstr ""
+
+#: src/main/help.c:65
+msgid "lines are null-terminated"
+msgstr ""
+
+#: src/main/help.c:67
+msgid "update every SEC seconds"
+msgstr ""
+
+#: src/main/help.c:68
+msgid "WIDTH"
+msgstr ""
+
+#: src/main/help.c:69
+msgid "assume terminal is WIDTH characters wide"
+msgstr ""
+
+#: src/main/help.c:70
+msgid "HEIGHT"
+msgstr ""
+
+#: src/main/help.c:71
+msgid "assume terminal is HEIGHT rows high"
+msgstr ""
+
+#: src/main/help.c:72
+msgid "NAME"
+msgstr ""
+
+#: src/main/help.c:73
+msgid "prefix visual information with NAME"
+msgstr ""
+
+#: src/main/help.c:75
+msgid "output even if standard error is not a terminal"
+msgstr ""
+
+#: src/main/help.c:77
+msgid "use cursor positioning escape sequences"
+msgstr ""
+
+#: src/main/help.c:79
+msgid "RATE"
+msgstr ""
+
+#: src/main/help.c:80
+msgid "limit transfer to RATE bytes per second"
+msgstr ""
+
+#: src/main/help.c:81
+msgid "BYTES"
+msgstr ""
+
+#: src/main/help.c:82
+msgid "use a buffer size of BYTES"
+msgstr ""
+
+#: src/main/help.c:84
+msgid "never use splice(), always use read/write"
+msgstr ""
+
+#: src/main/help.c:86
+msgid "skip read errors in input"
+msgstr ""
+
+#: src/main/help.c:88
+msgid "stop after --size bytes have been transferred"
+msgstr ""
+
+#: src/main/help.c:90
+msgid "PID"
+msgstr ""
+
+#: src/main/help.c:91
+msgid "update settings of process PID"
+msgstr ""
+
+#: src/main/help.c:94
+msgid "FILE"
+msgstr ""
+
+#: src/main/help.c:95
+msgid "save process ID in FILE"
+msgstr ""
+
+#: src/main/help.c:97
+msgid "PID[:FD]"
+msgstr ""
+
+#: src/main/help.c:98
+msgid "watch file FD opened by process PID"
+msgstr ""
+
+#: src/main/help.c:101
+msgid "show this help and exit"
+msgstr ""
+
+#: src/main/help.c:103
+msgid "show version information and exit"
+msgstr ""
+
+#: src/main/help.c:109
+#, c-format
+msgid "Usage: %s [OPTION] [FILE]..."
+msgstr ""
+
+#: src/main/help.c:112
+msgid ""
+"Concatenate FILE(s), or standard input, to standard output,\n"
+"with monitoring."
+msgstr ""
+
+#: src/main/help.c:201
+msgid ""
+"Debugging is enabled; export the DEBUG environment variable to define the\n"
+"output filename.\n"
+msgstr ""
+
+#: src/main/help.c:205
+#, c-format
+msgid "Please report any bugs to %s."
+msgstr ""
diff --git a/src/pv/cursor.c b/src/pv/cursor.c
new file mode 100644
index 0000000..f030fff
--- /dev/null
+++ b/src/pv/cursor.c
@@ -0,0 +1,602 @@
+/*
+ * Cursor positioning functions.
+ *
+ * If IPC is available, then a shared memory segment is used to co-ordinate
+ * cursor positioning across multiple instances of `pv'. The shared memory
+ * segment contains an integer which is the original "y" co-ordinate of the
+ * first `pv' process.
+ *
+ * However, some OSes (FreeBSD and MacOS X so far) don't allow locking of a
+ * terminal, so we try to use a lockfile if terminal locking doesn't work,
+ * and finally abort if even that is unavailable.
+ */
+
+#include "pv-internal.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef HAVE_IPC
+#include
+#include
+#ifdef HAVE_SYS_PARAM_H
+#include
+#endif
+#ifdef HAVE_LIBGEN_H
+#include
+#endif
+#endif /* HAVE_IPC */
+
+
+/*
+ * Create a per-euid, per-tty, lockfile in ${TMPDIR:-${TMP:-/tmp}} for the
+ * tty on the given file descriptor.
+ */
+static void pv_crs_open_lockfile(pvstate_t state, int fd)
+{
+ char *ttydev;
+ char *tmpdir;
+ int openflags;
+
+ state->crs_lock_fd = -1;
+
+ ttydev = ttyname(fd);
+ if (!ttydev) {
+ if (!state->force) {
+ pv_error(state, "%s: %s",
+ _("failed to get terminal name"),
+ strerror(errno));
+ }
+ /*
+ * If we don't know our terminal name, we can neither do IPC
+ * nor make a lock file, so turn off cursor positioning.
+ */
+ state->cursor = 0;
+ debug("%s",
+ "ttyname failed - cursor positioning disabled");
+ return;
+ }
+
+ tmpdir = (char *) getenv("TMPDIR");
+ if (!tmpdir)
+ tmpdir = (char *) getenv("TMP");
+ if (!tmpdir)
+ tmpdir = "/tmp";
+
+#ifdef HAVE_SNPRINTF
+ snprintf(state->crs_lock_file, sizeof(state->crs_lock_file) - 1,
+ "%s/pv-%s-%i.lock", tmpdir, basename(ttydev),
+ (int) geteuid());
+#else
+ sprintf(state->crs_lock_file,
+ "%.*s/pv-%8s-%i.lock",
+ sizeof(state->crs_lock_file) - 64, tmpdir,
+ basename(ttydev), (int) geteuid());
+#endif
+
+ /*
+ * Pawel Piatek - not everyone has O_NOFOLLOW, e.g. AIX doesn't
+ */
+#ifdef O_NOFOLLOW
+ openflags = O_RDWR | O_CREAT | O_NOFOLLOW;
+#else
+ openflags = O_RDWR | O_CREAT;
+#endif
+
+ state->crs_lock_fd = open(state->crs_lock_file, openflags, 0600);
+ if (state->crs_lock_fd < 0) {
+ pv_error(state, "%s: %s: %s",
+ state->crs_lock_file,
+ _("failed to open lock file"), strerror(errno));
+ state->cursor = 0;
+ return;
+ }
+}
+
+
+/*
+ * Lock the terminal on the given file descriptor, falling back to using a
+ * lockfile if the terminal itself cannot be locked.
+ */
+static void pv_crs_lock(pvstate_t state, int fd)
+{
+ struct flock lock;
+ int lock_fd;
+
+ lock_fd = fd;
+ if (state->crs_lock_fd >= 0)
+ lock_fd = state->crs_lock_fd;
+
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 1;
+ while (fcntl(lock_fd, F_SETLKW, &lock) < 0) {
+ if (errno != EINTR) {
+ if (state->crs_lock_fd == -2) {
+ pv_crs_open_lockfile(state, fd);
+ if (state->crs_lock_fd >= 0) {
+ lock_fd = state->crs_lock_fd;
+ }
+ } else {
+ pv_error(state, "%s: %s",
+ _("lock attempt failed"),
+ strerror(errno));
+ return;
+ }
+ }
+ }
+
+ if (state->crs_lock_fd >= 0) {
+ debug("%s: %s", state->crs_lock_file,
+ "terminal lockfile acquired");
+ } else {
+ debug("%s", "terminal lock acquired");
+ }
+}
+
+
+/*
+ * Unlock the terminal on the given file descriptor. If pv_crs_lock used
+ * lockfile locking, unlock the lockfile.
+ */
+static void pv_crs_unlock(pvstate_t state, int fd)
+{
+ struct flock lock;
+ int lock_fd;
+
+ lock_fd = fd;
+ if (state->crs_lock_fd >= 0)
+ lock_fd = state->crs_lock_fd;
+
+ lock.l_type = F_UNLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 1;
+ fcntl(lock_fd, F_SETLK, &lock);
+
+ if (state->crs_lock_fd >= 0) {
+ debug("%s: %s", state->crs_lock_file,
+ "terminal lockfile released");
+ } else {
+ debug("%s", "terminal lock released");
+ }
+}
+
+
+#ifdef HAVE_IPC
+/*
+ * Get the current number of processes attached to our shared memory
+ * segment, i.e. find out how many `pv' processes in total are running in
+ * cursor mode (including us), and store it in pv_crs_pvcount. If this is
+ * larger than pv_crs_pvmax, update pv_crs_pvmax.
+ */
+static void pv_crs_ipccount(pvstate_t state)
+{
+ struct shmid_ds buf;
+
+ buf.shm_nattch = 0;
+
+ shmctl(state->crs_shmid, IPC_STAT, &buf);
+ state->crs_pvcount = buf.shm_nattch;
+
+ if (state->crs_pvcount > state->crs_pvmax)
+ state->crs_pvmax = state->crs_pvcount;
+
+ debug("%s: %d", "pvcount", state->crs_pvcount);
+}
+#endif /* HAVE_IPC */
+
+
+/*
+ * Get the current cursor Y co-ordinate by sending the ECMA-48 CPR code to
+ * the terminal connected to the given file descriptor.
+ */
+static int pv_crs_get_ypos(int terminalfd)
+{
+ struct termios tty;
+ struct termios old_tty;
+ char cpr[32];
+ int ypos;
+ int r;
+#ifdef CURSOR_ANSWERBACK_BYTE_BY_BYTE
+ int got;
+#endif /* CURSOR_ANSWERBACK_BYTE_BY_BYTE */
+
+ tcgetattr(terminalfd, &tty);
+ tcgetattr(terminalfd, &old_tty);
+ tty.c_lflag &= ~(ICANON | ECHO);
+ tcsetattr(terminalfd, TCSANOW | TCSAFLUSH, &tty);
+
+ write(terminalfd, "\033[6n", 4);
+
+ memset(cpr, 0, sizeof(cpr));
+
+#ifdef CURSOR_ANSWERBACK_BYTE_BY_BYTE
+ /* Read answerback byte by byte - fails on AIX */
+ for (got = 0, r = 0; got < sizeof(cpr) - 2; got += r) {
+ r = read(terminalfd, cpr + got, 1);
+ if (r <= 0) {
+ debug("got=%d, r=%d: %s", got, r, strerror(errno));
+ break;
+ }
+ if (cpr[got] == 'R')
+ break;
+ }
+
+ debug
+ ("read answerback message from fd %d, length %d - buf = %02X %02X %02X %02X %02X %02X",
+ terminalfd, got, cpr[0], cpr[1], cpr[2], cpr[3], cpr[4],
+ cpr[5]);
+
+#else /* !CURSOR_ANSWERBACK_BYTE_BY_BYTE */
+ /* Read answerback in one big lump - may fail on Solaris */
+ r = read(terminalfd, cpr, sizeof(cpr));
+ if (r <= 0) {
+ debug("r=%d: %s", r, strerror(errno));
+ } else {
+ debug
+ ("read answerback message from fd %d, length %d - buf = %02X %02X %02X %02X %02X %02X",
+ terminalfd, r, cpr[0], cpr[1], cpr[2], cpr[3], cpr[4],
+ cpr[5]);
+
+ }
+#endif /* CURSOR_ANSWERBACK_BYTE_BY_BYTE */
+
+ ypos = pv_getnum_i(cpr + 2);
+
+ tcsetattr(terminalfd, TCSANOW | TCSAFLUSH, &old_tty);
+
+ debug("%s: %d", "ypos", ypos);
+
+ return ypos;
+}
+
+
+#ifdef HAVE_IPC
+/*
+ * Initialise the IPC data, returning nonzero on error.
+ *
+ * To do this, we attach to the shared memory segment (creating it if it
+ * does not exist). If we are the only process attached to it, then we
+ * initialise it with the current cursor position.
+ *
+ * There is a race condition here: another process could attach before we've
+ * had a chance to check, such that no process ends up getting an "attach
+ * count" of one, and so no initialisation occurs. So, we lock the terminal
+ * with pv_crs_lock() while we are attaching and checking.
+ */
+static int pv_crs_ipcinit(pvstate_t state, char *ttyfile, int terminalfd)
+{
+ key_t key;
+
+ /*
+ * Base the key for the shared memory segment on our current tty, so
+ * we don't end up interfering in any way with instances of `pv'
+ * running on another terminal.
+ */
+ key = ftok(ttyfile, 'p');
+ if (-1 == key) {
+ debug("%s: %s\n", "ftok failed", strerror(errno));
+ return 1;
+ }
+
+ pv_crs_lock(state, terminalfd);
+ if (!state->cursor) {
+ debug("%s", "early return - cursor has been disabled");
+ return 1;
+ }
+
+ state->crs_shmid = shmget(key, sizeof(int), 0600 | IPC_CREAT);
+ if (state->crs_shmid < 0) {
+ debug("%s: %s", "shmget failed", strerror(errno));
+ pv_crs_unlock(state, terminalfd);
+ return 1;
+ }
+
+ state->crs_y_top = shmat(state->crs_shmid, 0, 0);
+
+ pv_crs_ipccount(state);
+
+ /*
+ * If nobody else is attached to the shared memory segment, we're
+ * the first, so we need to initialise the shared memory with our
+ * current Y cursor co-ordinate.
+ */
+ if (state->crs_pvcount < 2) {
+ state->crs_y_start = pv_crs_get_ypos(terminalfd);
+ *(state->crs_y_top) = state->crs_y_start;
+ state->crs_y_lastread = state->crs_y_start;
+ debug("%s", "we are the first to attach");
+ }
+
+ state->crs_y_offset = state->crs_pvcount - 1;
+ if (state->crs_y_offset < 0)
+ state->crs_y_offset = 0;
+
+ /*
+ * If anyone else had attached to the shared memory segment, we need
+ * to read the top Y co-ordinate from it.
+ */
+ if (state->crs_pvcount > 1) {
+ state->crs_y_start = *(state->crs_y_top);
+ state->crs_y_lastread = state->crs_y_start;
+ debug("%s: %d", "not the first to attach - got top y",
+ state->crs_y_start);
+ }
+
+ pv_crs_unlock(state, terminalfd);
+
+ return 0;
+}
+#endif /* HAVE_IPC */
+
+
+/*
+ * Initialise the terminal for cursor positioning.
+ */
+void pv_crs_init(pvstate_t state)
+{
+ char *ttyfile;
+ int fd;
+
+ state->crs_lock_fd = -2;
+ state->crs_lock_file[0] = 0;
+
+ if (!state->cursor)
+ return;
+
+ debug("%s", "init");
+
+ ttyfile = ttyname(STDERR_FILENO);
+ if (!ttyfile) {
+ debug("%s: %s",
+ "disabling cursor positioning because ttyname failed",
+ strerror(errno));
+ state->cursor = 0;
+ return;
+ }
+
+ fd = open(ttyfile, O_RDWR);
+ if (fd < 0) {
+ pv_error(state, "%s: %s: %s",
+ _("failed to open terminal"), ttyfile,
+ strerror(errno));
+ state->cursor = 0;
+ return;
+ }
+#ifdef HAVE_IPC
+ if (pv_crs_ipcinit(state, ttyfile, fd) != 0) {
+ debug("%s", "ipcinit failed, setting noipc flag");
+ state->crs_noipc = 1;
+ }
+
+ /*
+ * If we are not using IPC, then we need to get the current Y
+ * co-ordinate. If we are using IPC, then the pv_crs_ipcinit()
+ * function takes care of this in a more multi-process-friendly way.
+ */
+ if (state->crs_noipc) {
+#else /* ! HAVE_IPC */
+ if (1) {
+#endif /* HAVE_IPC */
+ /*
+ * Get current cursor position + 1.
+ */
+ pv_crs_lock(state, fd);
+ state->crs_y_start = pv_crs_get_ypos(fd);
+ /*
+ * Move down a line while the terminal is locked, so that
+ * other processes in the pipeline will get a different
+ * initial ypos.
+ */
+ if (state->crs_y_start > 0)
+ write(STDERR_FILENO, "\n", 1);
+ pv_crs_unlock(state, fd);
+
+ if (state->crs_y_start < 1)
+ state->cursor = 0;
+ }
+
+ close(fd);
+}
+
+
+#ifdef HAVE_IPC
+/*
+ * Set the "we need to reinitialise cursor positioning" flag.
+ */
+void pv_crs_needreinit(pvstate_t state)
+{
+ state->crs_needreinit += 2;
+ if (state->crs_needreinit > 3)
+ state->crs_needreinit = 3;
+}
+#endif
+
+
+#ifdef HAVE_IPC
+/*
+ * Reinitialise the cursor positioning code (called if we are backgrounded
+ * then foregrounded again).
+ */
+void pv_crs_reinit(pvstate_t state)
+{
+ debug("%s", "reinit");
+
+ pv_crs_lock(state, STDERR_FILENO);
+
+ state->crs_needreinit--;
+ if (state->crs_y_offset < 1)
+ state->crs_needreinit = 0;
+
+ if (state->crs_needreinit > 0) {
+ pv_crs_unlock(state, STDERR_FILENO);
+ return;
+ }
+
+ debug("%s", "reinit full");
+
+ state->crs_y_start = pv_crs_get_ypos(STDERR_FILENO);
+
+ if (state->crs_y_offset < 1)
+ *(state->crs_y_top) = state->crs_y_start;
+ state->crs_y_lastread = state->crs_y_start;
+
+ pv_crs_unlock(state, STDERR_FILENO);
+}
+#endif
+
+
+/*
+ * Output a single-line update, moving the cursor to the correct position to
+ * do so.
+ */
+void pv_crs_update(pvstate_t state, char *str)
+{
+ char pos[32];
+ int y;
+
+#ifdef HAVE_IPC
+ if (!state->crs_noipc) {
+ if (state->crs_needreinit)
+ pv_crs_reinit(state);
+
+ pv_crs_ipccount(state);
+ if (state->crs_y_lastread != *(state->crs_y_top)) {
+ state->crs_y_start = *(state->crs_y_top);
+ state->crs_y_lastread = state->crs_y_start;
+ }
+
+ if (state->crs_needreinit > 0)
+ return;
+ }
+#endif /* HAVE_IPC */
+
+ y = state->crs_y_start;
+
+#ifdef HAVE_IPC
+ /*
+ * If the screen has scrolled, or is about to scroll, due to
+ * multiple `pv' instances taking us near the bottom of the screen,
+ * scroll the screen (only if we're the first `pv'), and then move
+ * our initial Y co-ordinate up.
+ */
+ if (((state->crs_y_start + state->crs_pvmax) > state->height)
+ && (!state->crs_noipc)
+ ) {
+ int offs;
+
+ offs =
+ ((state->crs_y_start + state->crs_pvmax) -
+ state->height);
+
+ state->crs_y_start -= offs;
+ if (state->crs_y_start < 1)
+ state->crs_y_start = 1;
+
+ debug("%s: %d", "scroll offset", offs);
+
+ /*
+ * Scroll the screen if we're the first `pv'.
+ */
+ if (0 == state->crs_y_offset) {
+ pv_crs_lock(state, STDERR_FILENO);
+
+ sprintf(pos, "\033[%d;1H", state->height);
+ write(STDERR_FILENO, pos, strlen(pos));
+ for (; offs > 0; offs--) {
+ write(STDERR_FILENO, "\n", 1);
+ }
+
+ pv_crs_unlock(state, STDERR_FILENO);
+
+ debug("%s", "we are the first - scrolled screen");
+ }
+ }
+
+ if (!state->crs_noipc)
+ y = state->crs_y_start + state->crs_y_offset;
+#endif /* HAVE_IPC */
+
+ /*
+ * Keep the Y co-ordinate within sensible bounds, so we can never
+ * overflow the "pos" buffer.
+ */
+ if ((y < 1) || (y > 999999))
+ y = 1;
+ sprintf(pos, "\033[%d;1H", y);
+
+ pv_crs_lock(state, STDERR_FILENO);
+
+ write(STDERR_FILENO, pos, strlen(pos));
+ write(STDERR_FILENO, str, strlen(str));
+
+ pv_crs_unlock(state, STDERR_FILENO);
+}
+
+
+/*
+ * Reposition the cursor to a final position.
+ */
+void pv_crs_fini(pvstate_t state)
+{
+ char pos[32];
+ int y;
+
+ debug("%s", "fini");
+
+ y = state->crs_y_start;
+
+#ifdef HAVE_IPC
+ if ((state->crs_pvmax > 0) && (!state->crs_noipc))
+ y += state->crs_pvmax - 1;
+#endif /* HAVE_IPC */
+
+ if (y > state->height)
+ y = state->height;
+
+ /*
+ * Absolute bounds check.
+ */
+ if ((y < 1) || (y > 999999))
+ y = 1;
+
+ sprintf(pos, "\033[%d;1H\n", y);
+
+ pv_crs_lock(state, STDERR_FILENO);
+
+ write(STDERR_FILENO, pos, strlen(pos));
+
+#ifdef HAVE_IPC
+ pv_crs_ipccount(state);
+ shmdt((void *) state->crs_y_top);
+
+ /*
+ * If we are the last instance detaching from the shared memory,
+ * delete it so it's not left lying around.
+ */
+ if (state->crs_pvcount < 2)
+ shmctl(state->crs_shmid, IPC_RMID, 0);
+
+#endif /* HAVE_IPC */
+
+ pv_crs_unlock(state, STDERR_FILENO);
+
+ if (state->crs_lock_fd >= 0) {
+ close(state->crs_lock_fd);
+ /*
+ * We can get away with removing this on exit because all
+ * the other PVs will be finishing at the same sort of time.
+ */
+ remove(state->crs_lock_file);
+ }
+}
+
+/* EOF */
diff --git a/src/pv/display.c b/src/pv/display.c
new file mode 100644
index 0000000..2627152
--- /dev/null
+++ b/src/pv/display.c
@@ -0,0 +1,968 @@
+/*
+ * Display functions.
+ */
+
+#include "pv-internal.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/*
+ * Output an error message. If we've displayed anything to the terminal
+ * already, then put a newline before our error so we don't write over what
+ * we've written.
+ */
+void pv_error(pvstate_t state, char *format, ...)
+{
+ va_list ap;
+ if (state->display_visible)
+ fprintf(stderr, "\n");
+ fprintf(stderr, "%s: ", state->program_name);
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+}
+
+
+/*
+ * Fill in *width and *height with the current terminal size,
+ * if possible.
+ */
+void pv_screensize(unsigned int *width, unsigned int *height)
+{
+#ifdef TIOCGWINSZ
+ struct winsize wsz;
+
+ if (isatty(STDERR_FILENO)) {
+ if (0 == ioctl(STDERR_FILENO, TIOCGWINSZ, &wsz)) {
+ *width = wsz.ws_col;
+ *height = wsz.ws_row;
+ }
+ }
+#endif
+}
+
+
+/*
+ * Calculate the percentage transferred so far and return it.
+ */
+static long pv__calc_percentage(long long so_far, const long long total)
+{
+ if (total < 1)
+ return 0;
+
+ so_far *= 100;
+ so_far /= total;
+
+ return (long) so_far;
+}
+
+
+/*
+ * Given how many bytes have been transferred, the total byte count to
+ * transfer, and how long it's taken so far in seconds, return the estimated
+ * number of seconds until completion.
+ */
+static long pv__calc_eta(const long long so_far, const long long total,
+ const long elapsed)
+{
+ long long amount_left;
+
+ if (so_far < 1)
+ return 0;
+
+ amount_left = total - so_far;
+ amount_left *= (long long) elapsed;
+ amount_left /= so_far;
+
+ return (long) amount_left;
+}
+
+/*
+ * Given a long double value, it is divided or multiplied by the ratio until
+ * a value in the range 1.0 to 999.999... is found. The string "prefix" to
+ * is updated to the corresponding SI prefix.
+ *
+ * If "is_bytes" is 1, then the second byte of "prefix" is set to "i" to
+ * denote MiB etc (IEEE1541). Thus "prefix" should be at least 3 bytes long
+ * (to include the terminating null).
+ *
+ * Submitted by Henry Gebhardt and then
+ * modified. Further changed after input from Thomas Rachel; changed still
+ * further after Debian bug #706175.
+ */
+static void pv__si_prefix(long double *value, char *prefix,
+ const long double ratio, int is_bytes)
+{
+ static char *pfx_000 = NULL; /* kilo, mega, etc */
+ static char *pfx_024 = NULL; /* kibi, mibi, etc */
+ static char const *pfx_middle_000 = NULL;
+ static char const *pfx_middle_024 = NULL;
+ char *pfx;
+ char const *pfx_middle;
+ char const *i;
+ long double cutoff;
+
+ if (NULL == pfx_000) {
+ pfx_000 = _("yzafpnum kMGTPEZY");
+ pfx_middle_000 = strchr(pfx_000, ' ');
+ }
+
+ if (NULL == pfx_024) {
+ pfx_024 = _("yzafpnum KMGTPEZY");
+ pfx_middle_024 = strchr(pfx_024, ' ');
+ }
+
+ pfx = pfx_000;
+ pfx_middle = pfx_middle_000;
+ if (is_bytes) {
+ pfx = pfx_024;
+ pfx_middle = pfx_middle_024;
+ }
+
+ i = pfx_middle;
+
+ prefix[0] = ' '; /* Make the prefix start blank. */
+ prefix[1] = 0;
+
+ /*
+ * Force an empty prefix if the value is zero to avoid "0yB".
+ */
+ if (0.0 == *value)
+ return;
+
+ cutoff = ratio * 0.97;
+
+ while ((*value > cutoff) && (*(i += 1) != '\0')) {
+ *value /= ratio;
+ prefix[0] = *i;
+ }
+
+ while ((*value < 1.0) && ((i -= 1) != (pfx - 1))) {
+ *value *= ratio;
+ prefix[0] = *i;
+ }
+
+ if (is_bytes && prefix[0] != ' ') {
+ prefix[1] = 'i';
+ prefix[2] = 0;
+ }
+}
+
+
+/*
+ * Put a string in "buffer" (max length "bufsize") containing "amount"
+ * formatted such that it's 3 or 4 digits followed by an SI suffix and then
+ * whichever of "suffix_basic" or "suffix_bytes" is appropriate (whether
+ * "is_bytes" is 0 for non-byte amounts or 1 for byte amounts). If
+ * "is_bytes" is 1 then the SI units are KiB, MiB etc and the divisor is
+ * 1024 instead of 1000.
+ *
+ * The "format" string is in sprintf format and must contain exactly one %
+ * parameter (a %s) which will expand to the string described above.
+ */
+static void pv__sizestr(char *buffer, int bufsize, char *format,
+ long double amount, char *suffix_basic,
+ char *suffix_bytes, int is_bytes)
+{
+ char sizestr_buffer[256];
+ char si_prefix[8] = " ";
+ long double divider;
+ long double display_amount;
+ char *suffix;
+
+ if (is_bytes) {
+ suffix = suffix_bytes;
+ divider = 1024.0;
+ } else {
+ suffix = suffix_basic;
+ divider = 1000.0;
+ }
+
+ display_amount = amount;
+
+ pv__si_prefix(&display_amount, si_prefix, divider, is_bytes);
+
+ /* Make sure we don't overrun our buffer. */
+ if (display_amount > 100000)
+ display_amount = 100000;
+
+ /* Fix for display of "1.01e+03" instead of "1010" */
+ if (display_amount > 99.9) {
+ sprintf(sizestr_buffer, "%4ld%.2s%.16s",
+ (long) display_amount, si_prefix, suffix);
+ } else {
+ /*
+ * AIX blows up with %4.3Lg%.2s%.16s for some reason, so we
+ * write display_amount separately first.
+ */
+ char str_disp[64];
+ /* # to get 13.0GB instead of 13GB (#1477) */
+ sprintf(str_disp, "%#4.3Lg", display_amount);
+ sprintf(sizestr_buffer, "%s%.2s%.16s",
+ str_disp, si_prefix, suffix);
+ }
+
+ snprintf(buffer, bufsize, format, sizestr_buffer);
+}
+
+
+/*
+ * Initialise the output format structure, based on the current options.
+ */
+static void pv__format_init(pvstate_t state)
+{
+ const char *formatstr;
+ const char *searchptr;
+ int strpos;
+ int segment;
+
+ if (NULL == state)
+ return;
+
+ state->str_name[0] = 0;
+ state->str_transferred[0] = 0;
+ state->str_timer[0] = 0;
+ state->str_rate[0] = 0;
+ state->str_average_rate[0] = 0;
+ state->str_progress[0] = 0;
+ state->str_eta[0] = 0;
+ memset(state->format, 0, sizeof(state->format));
+
+ if (state->name) {
+ sprintf(state->str_name, "%9.500s:", state->name);
+ }
+
+ formatstr =
+ state->format_string ? state->
+ format_string : state->default_format;
+
+ state->components_used = 0;
+
+ /*
+ * Split the format string into segments. Each segment consists
+ * of a string pointer and a length.
+ *
+ * A length of zero indicates that the segment is a fixed-size
+ * component updated by pv__format(), so it is a pointer to one
+ * of the state->str_* buffers that pv__format() updates.
+ *
+ * A length below zero indicates that the segment is a variable
+ * sized component which will be recalculated by pv__format()
+ * after the length of all fixed-size segments is known, and so
+ * the string is a pointer to another state->str_* buffer
+ * (currently it will only ever be state->str_progress).
+ *
+ * A length above zero indicates that the segment is a constant
+ * string of the given length (not necessarily null terminated).
+ *
+ * In pv__format(), after the state->str_* buffers have all been
+ * filled in, the output string is generated by sticking all of
+ * these segments together.
+ */
+ segment = 0;
+ for (strpos = 0; formatstr[strpos] != 0 && segment < 99;
+ strpos++, segment++) {
+ if ('%' == formatstr[strpos]) {
+ int num;
+ strpos++;
+ num = 0;
+ while (isdigit(formatstr[strpos])) {
+ num = num * 10;
+ num += formatstr[strpos] - '0';
+ strpos++;
+ }
+ switch (formatstr[strpos]) {
+ case 'p':
+ state->format[segment].string =
+ state->str_progress;
+ state->format[segment].length = -1;
+ state->components_used |=
+ PV_DISPLAY_PROGRESS;
+ break;
+ case 't':
+ state->format[segment].string =
+ state->str_timer;
+ state->format[segment].length = 0;
+ state->components_used |= PV_DISPLAY_TIMER;
+ break;
+ case 'e':
+ state->format[segment].string =
+ state->str_eta;
+ state->format[segment].length = 0;
+ state->components_used |= PV_DISPLAY_ETA;
+ break;
+ case 'I':
+ state->format[segment].string =
+ state->str_fineta;
+ state->format[segment].length = 0;
+ state->components_used |=
+ PV_DISPLAY_FINETA;
+ break;
+ case 'A':
+ state->format[segment].string =
+ state->str_lastoutput;
+ state->format[segment].length = 0;
+ if (num > sizeof(state->lastoutput_buffer))
+ num =
+ sizeof
+ (state->lastoutput_buffer);
+ if (num < 1)
+ num = 1;
+ state->lastoutput_length = num;
+ state->components_used |=
+ PV_DISPLAY_OUTPUTBUF;
+ break;
+ case 'r':
+ state->format[segment].string =
+ state->str_rate;
+ state->format[segment].length = 0;
+ state->components_used |= PV_DISPLAY_RATE;
+ break;
+ case 'a':
+ state->format[segment].string =
+ state->str_average_rate;
+ state->format[segment].length = 0;
+ state->components_used |=
+ PV_DISPLAY_AVERAGERATE;
+ break;
+ case 'b':
+ state->format[segment].string =
+ state->str_transferred;
+ state->format[segment].length = 0;
+ state->components_used |= PV_DISPLAY_BYTES;
+ break;
+ case 'T':
+ state->format[segment].string =
+ state->str_bufpercent;
+ state->format[segment].length = 0;
+ state->components_used |=
+ PV_DISPLAY_BUFPERCENT;
+ break;
+ case 'N':
+ state->format[segment].string =
+ state->str_name;
+ state->format[segment].length =
+ strlen(state->str_name);
+ state->components_used |= PV_DISPLAY_NAME;
+ break;
+ case '%':
+ /* %% => % */
+ state->format[segment].string =
+ &(formatstr[strpos]);
+ state->format[segment].length = 1;
+ break;
+ case 0:
+ /* % at end => just % */
+ state->format[segment].string =
+ &(formatstr[--strpos]);
+ state->format[segment].length = 1;
+ break;
+ default:
+ /* %z (unknown) => %z */
+ state->format[segment].string =
+ &(formatstr[--strpos]);
+ state->format[segment].length = 2;
+ strpos++;
+ break;
+ }
+ } else {
+ int foundlength;
+ searchptr = strchr(&(formatstr[strpos]), '%');
+ if (NULL == searchptr) {
+ foundlength = strlen(&(formatstr[strpos]));
+ } else {
+ foundlength =
+ searchptr - &(formatstr[strpos]);
+ }
+ state->format[segment].string =
+ &(formatstr[strpos]);
+ state->format[segment].length = foundlength;
+ strpos += foundlength - 1;
+ }
+ }
+
+ state->format[segment].string = 0;
+ state->format[segment].length = 0;
+}
+
+/*
+ * Return the original value x so that it has been clamped between
+ * [min..max]
+ */
+static long bound_long(long x, long min, long max)
+{
+ return x < min ? min : x > max ? max : x;
+}
+
+
+/*
+ * Return a pointer to a string (which must not be freed), containing status
+ * information formatted according to the state held within the given
+ * structure, where "elapsed_sec" is the seconds elapsed since the transfer
+ * started, "bytes_since_last" is the number of bytes transferred since the
+ * last update, and "total_bytes" is the total number of bytes transferred
+ * so far.
+ *
+ * If "bytes_since_last" is negative, this is the final update so the rate
+ * is given as an an average over the whole transfer; otherwise the current
+ * rate is shown.
+ *
+ * In line mode, "bytes_since_last" and "total_bytes" are in lines, not bytes.
+ *
+ * If "total_bytes" is negative, then free all allocated memory and return
+ * NULL.
+ */
+static char *pv__format(pvstate_t state,
+ long double elapsed_sec,
+ long long bytes_since_last, long long total_bytes)
+{
+ long double time_since_last, rate, average_rate;
+ long eta;
+ int static_portion_size;
+ int segment;
+ int output_length;
+ int display_string_length;
+
+ /* Quick sanity check - state must exist */
+ if (NULL == state)
+ return NULL;
+
+ /* Negative total transfer - free memory and exit */
+ if (total_bytes < 0) {
+ if (state->display_buffer)
+ free(state->display_buffer);
+ state->display_buffer = NULL;
+ return NULL;
+ }
+
+ /*
+ * In case the time since the last update is very small, we keep
+ * track of amount transferred since the last update, and just keep
+ * adding to that until a reasonable amount of time has passed to
+ * avoid rate spikes or division by zero.
+ */
+ time_since_last = elapsed_sec - state->prev_elapsed_sec;
+ if (time_since_last <= 0.01) {
+ rate = state->prev_rate;
+ state->prev_trans += bytes_since_last;
+ } else {
+ rate =
+ ((long double) bytes_since_last +
+ state->prev_trans) / time_since_last;
+ state->prev_elapsed_sec = elapsed_sec;
+ state->prev_trans = 0;
+ }
+ state->prev_rate = rate;
+
+ /*
+ * We only calculate the overall average rate if this is the last
+ * update or if the average rate display is enabled. Otherwise it's
+ * not worth the extra CPU cycles.
+ */
+ if ((bytes_since_last < 0)
+ || ((state->components_used & PV_DISPLAY_AVERAGERATE) != 0)) {
+ /* Sanity check to avoid division by zero */
+ if (elapsed_sec < 0.000001)
+ elapsed_sec = 0.000001;
+ average_rate =
+ (((long double) total_bytes) -
+ ((long double) state->initial_offset)) /
+ (long double) elapsed_sec;
+ if (bytes_since_last < 0)
+ rate = average_rate;
+ }
+
+ if (state->size <= 0) {
+ /*
+ * If we don't know the total size of the incoming data,
+ * then for a percentage, we gradually increase the
+ * percentage completion as data arrives, to a maximum of
+ * 200, then reset it - we use this if we can't calculate
+ * it, so that the numeric percentage output will go
+ * 0%-100%, 100%-0%, 0%-100%, and so on.
+ */
+ if (rate > 0)
+ state->percentage += 2;
+ if (state->percentage > 199)
+ state->percentage = 0;
+ } else if (state->numeric
+ || ((state->components_used & PV_DISPLAY_PROGRESS) !=
+ 0)) {
+ /*
+ * If we do know the total size, and we're going to show
+ * the percentage (numeric mode or a progress bar),
+ * calculate the percentage completion.
+ */
+ state->percentage =
+ pv__calc_percentage(total_bytes, state->size);
+ }
+
+ /*
+ * Reallocate output buffer if width changes.
+ */
+ if (state->display_buffer != NULL
+ && state->display_buffer_size < (state->width * 2)) {
+ free(state->display_buffer);
+ state->display_buffer = NULL;
+ state->display_buffer_size = 0;
+ }
+
+ /*
+ * Allocate output buffer if there isn't one.
+ */
+ if (NULL == state->display_buffer) {
+ state->display_buffer_size = (2 * state->width) + 80;
+ if (state->name)
+ state->display_buffer_size += strlen(state->name);
+ state->display_buffer =
+ malloc(state->display_buffer_size + 16);
+ if (NULL == state->display_buffer) {
+ pv_error(state, "%s: %s",
+ _("buffer allocation failed"),
+ strerror(errno));
+ state->exit_status |= 64;
+ return NULL;
+ }
+ state->display_buffer[0] = 0;
+ }
+
+ /*
+ * In numeric output mode, our output is just a number.
+ *
+ * Patch from Sami Liedes:
+ * With --timer we prefix the output with the elapsed time.
+ * With --bytes we output the bytes transferred so far instead
+ * of the percentage. (Or lines, if --lines was given with --bytes).
+ */
+ if (state->numeric) {
+ char numericprefix[128];
+
+ numericprefix[0] = 0;
+
+ if ((state->components_used & PV_DISPLAY_TIMER) != 0)
+ sprintf(numericprefix, "%.4Lf ", elapsed_sec);
+
+ if ((state->components_used & PV_DISPLAY_BYTES) != 0) {
+ sprintf(state->display_buffer, "%.99s%lld\n",
+ numericprefix, total_bytes);
+ } else if (state->percentage > 100) {
+ /* As mentioned above, we go 0-100, then 100-0. */
+ sprintf(state->display_buffer, "%.99s%ld\n",
+ numericprefix, 200 - state->percentage);
+ } else {
+ sprintf(state->display_buffer, "%.99s%ld\n",
+ numericprefix, state->percentage);
+ }
+
+ return state->display_buffer;
+ }
+
+ /*
+ * First, work out what components we will be putting in the output
+ * buffer, and for those that don't depend on the total width
+ * available (i.e. all but the progress bar), prepare their strings
+ * to be placed in the output buffer.
+ */
+
+ state->str_transferred[0] = 0;
+ state->str_bufpercent[0] = 0;
+ state->str_timer[0] = 0;
+ state->str_rate[0] = 0;
+ state->str_average_rate[0] = 0;
+ state->str_progress[0] = 0;
+ state->str_lastoutput[0] = 0;
+ state->str_eta[0] = 0;
+ state->str_fineta[0] = 0;
+
+ /* If we're showing bytes transferred, set up the display string. */
+ if ((state->components_used & PV_DISPLAY_BYTES) != 0) {
+ pv__sizestr(state->str_transferred,
+ sizeof(state->str_transferred), "%s",
+ (long double) total_bytes, "", _("B"),
+ state->linemode ? 0 : 1);
+ }
+
+ /* Transfer buffer percentage - set up the display string. */
+ if ((state->components_used & PV_DISPLAY_BUFPERCENT) != 0) {
+ if (state->buffer_size > 0)
+ sprintf(state->str_bufpercent, "{%3ld%%}",
+ pv__calc_percentage(state->read_position -
+ state->write_position,
+ state->buffer_size));
+#ifdef HAVE_SPLICE
+ if (state->splice_used)
+ strcpy(state->str_bufpercent, "{----}");
+#endif
+ }
+
+ /* Timer - set up the display string. */
+ if ((state->components_used & PV_DISPLAY_TIMER) != 0) {
+ /*
+ * Bounds check, so we don't overrun the prefix buffer. This
+ * does mean that the timer will stop at a 100,000 hours,
+ * but since that's 11 years, it shouldn't be a problem.
+ */
+ if (elapsed_sec > (long double) 360000000.0L)
+ elapsed_sec = (long double) 360000000.0L;
+
+ sprintf(state->str_timer, "%ld:%02ld:%02ld",
+ ((long) elapsed_sec) / 3600,
+ (((long) elapsed_sec) / 60) % 60,
+ ((long) elapsed_sec) % 60);
+ }
+
+ /* Rate - set up the display string. */
+ if ((state->components_used & PV_DISPLAY_RATE) != 0) {
+ pv__sizestr(state->str_rate, sizeof(state->str_rate),
+ "[%s]", rate, _("/s"), _("B/s"),
+ state->linemode ? 0 : 1);
+ }
+
+ /* Average rate - set up the display string. */
+ if ((state->components_used & PV_DISPLAY_AVERAGERATE) != 0) {
+ pv__sizestr(state->str_average_rate,
+ sizeof(state->str_average_rate), "[%s]",
+ average_rate, _("/s"), _("B/s"),
+ state->linemode ? 0 : 1);
+ }
+
+ /* Last output bytes - set up the display string. */
+ if ((state->components_used & PV_DISPLAY_OUTPUTBUF) != 0) {
+ int idx;
+ for (idx = 0; idx < state->lastoutput_length; idx++) {
+ int c;
+ c = state->lastoutput_buffer[idx];
+ state->str_lastoutput[idx] = isprint(c) ? c : '.';
+ }
+ state->str_lastoutput[idx] = 0;
+ }
+
+ /* ETA (only if size is known) - set up the display string. */
+ if (((state->components_used & PV_DISPLAY_ETA) != 0)
+ && (state->size > 0)) {
+ eta =
+ pv__calc_eta(total_bytes - state->initial_offset,
+ state->size - state->initial_offset,
+ elapsed_sec);
+
+ /*
+ * Bounds check, so we don't overrun the suffix buffer. This
+ * means the ETA will always be less than 100,000 hours.
+ */
+ eta = bound_long(eta, 0, (long) 360000000L);
+
+ /*
+ * If the ETA is more than a day, include a day count as
+ * well as hours, minutes, and seconds.
+ */
+ if (eta > 86400L) {
+ sprintf(state->str_eta,
+ "%.16s %ld:%02ld:%02ld:%02ld", _("ETA"),
+ eta / 86400, (eta / 3600) % 24,
+ (eta / 60) % 60, eta % 60);
+ } else {
+ sprintf(state->str_eta, "%.16s %ld:%02ld:%02ld",
+ _("ETA"), eta / 3600, (eta / 60) % 60,
+ eta % 60);
+ }
+
+ /*
+ * If this is the final update, show a blank space where the
+ * ETA used to be.
+ */
+ if (bytes_since_last < 0) {
+ int i;
+ for (i = 0; i < sizeof(state->str_eta)
+ && state->str_eta[i] != 0; i++) {
+ state->str_eta[i] = ' ';
+ }
+ }
+ }
+
+ /* ETA as clock time (as above) - set up the display string. */
+ if (((state->components_used & PV_DISPLAY_FINETA) != 0)
+ && (state->size > 0)) {
+ /*
+ * The ETA may be hidden by a failed ETA string
+ * generation.
+ */
+ int show_eta = 1;
+ time_t now = time(NULL);
+ time_t then;
+ struct tm *time_ptr;
+ char *time_format = NULL;
+
+ eta =
+ pv__calc_eta(total_bytes - state->initial_offset,
+ state->size - state->initial_offset,
+ elapsed_sec);
+
+ /*
+ * Bounds check, so we don't overrun the suffix buffer. This
+ * means the ETA will always be less than 100,000 hours.
+ */
+ eta = bound_long(eta, 0, (long) 360000000L);
+
+ /*
+ * Only include the date if the ETA is more than 6 hours
+ * away.
+ */
+ if (eta > (long) (6 * 3600)) {
+ time_format = "%Y-%m-%d %H:%M:%S";
+ } else {
+ time_format = "%H:%M:%S";
+ }
+
+ then = now + eta;
+ time_ptr = localtime(&then);
+
+ if (NULL == time_ptr) {
+ show_eta = 0;
+ } else {
+ /* Localtime keeps data stored in a
+ * static buffer that gets overwritten
+ * by time functions. */
+ struct tm time = *time_ptr;
+
+ sprintf(state->str_fineta, "%.16s ", _("ETA"));
+ strftime(state->str_fineta +
+ strlen(state->str_fineta),
+ sizeof(state->str_fineta) - 1 -
+ strlen(state->str_fineta),
+ time_format, &time);
+ }
+
+ if (!show_eta) {
+ int i;
+ for (i = 0; i < sizeof(state->str_fineta)
+ && state->str_fineta[i] != 0; i++) {
+ state->str_fineta[i] = ' ';
+ }
+ }
+ }
+
+ /*
+ * Now go through all the static portions of the format to work
+ * out how much space will be left for any dynamic portions
+ * (i.e. the progress bar).
+ */
+ static_portion_size = 0;
+ for (segment = 0; state->format[segment].string; segment++) {
+ if (state->format[segment].length < 0) {
+ continue;
+ } else if (state->format[segment].length > 0) {
+ static_portion_size +=
+ state->format[segment].length;
+ } else {
+ static_portion_size +=
+ strlen(state->format[segment].string);
+ }
+ }
+
+ debug("static_portion_size: %d", static_portion_size);
+
+ /*
+ * Assemble the progress bar now we know how big it should be.
+ */
+ if ((state->components_used & PV_DISPLAY_PROGRESS) != 0) {
+ char pct[16];
+ int available_width, i;
+
+ strcpy(state->str_progress, "[");
+
+ if (state->size > 0) {
+ if (state->percentage < 0)
+ state->percentage = 0;
+ if (state->percentage > 100000)
+ state->percentage = 100000;
+ sprintf(pct, "%2ld%%", state->percentage);
+
+ available_width =
+ state->width - static_portion_size -
+ strlen(pct) - 3;
+
+ if (available_width < 0)
+ available_width = 0;
+
+ if (available_width >
+ sizeof(state->str_progress) - 16)
+ available_width =
+ sizeof(state->str_progress) - 16;
+
+ for (i = 0;
+ i <
+ (available_width * state->percentage) / 100 -
+ 1; i++) {
+ if (i < available_width)
+ strcat(state->str_progress, "=");
+ }
+ if (i < available_width) {
+ strcat(state->str_progress, ">");
+ i++;
+ }
+ for (; i < available_width; i++) {
+ strcat(state->str_progress, " ");
+ }
+ strcat(state->str_progress, "] ");
+ strcat(state->str_progress, pct);
+ } else {
+ int p = state->percentage;
+
+ available_width =
+ state->width - static_portion_size - 5;
+
+ if (available_width < 0)
+ available_width = 0;
+
+ if (available_width >
+ sizeof(state->str_progress) - 16)
+ available_width =
+ sizeof(state->str_progress) - 16;
+
+ debug("available_width: %d", available_width);
+
+ if (p > 100)
+ p = 200 - p;
+ for (i = 0; i < (available_width * p) / 100; i++) {
+ if (i < available_width)
+ strcat(state->str_progress, " ");
+ }
+ strcat(state->str_progress, "<=>");
+ for (; i < available_width; i++) {
+ strcat(state->str_progress, " ");
+ }
+ strcat(state->str_progress, "]");
+ }
+
+ /*
+ * If the progress bar won't fit, drop it.
+ */
+ if (strlen(state->str_progress) + static_portion_size >
+ state->width)
+ state->str_progress[0] = 0;
+ }
+
+ /*
+ * We can now build the output string using the format structure.
+ */
+
+ state->display_buffer[0] = 0;
+ display_string_length = 0;
+ for (segment = 0; state->format[segment].string; segment++) {
+ int segment_length;
+ if (state->format[segment].length > 0) {
+ segment_length = state->format[segment].length;
+ } else {
+ segment_length =
+ strlen(state->format[segment].string);
+ }
+ /* Skip empty segments */
+ if (segment_length == 0)
+ continue;
+ /*
+ * Truncate segment if it would make the display string
+ * overflow the buffer
+ */
+ if (segment_length + display_string_length >
+ state->display_buffer_size - 2)
+ segment_length =
+ state->display_buffer_size -
+ display_string_length - 2;
+ if (segment_length < 1)
+ break;
+ /* Skip segment if it would make the display too wide */
+ if (segment_length + display_string_length > state->width)
+ break;
+ strncat(state->display_buffer,
+ state->format[segment].string, segment_length);
+ display_string_length += segment_length;
+ }
+
+ /*
+ * If the size of our output shrinks, we need to keep appending
+ * spaces at the end, so that we don't leave dangling bits behind.
+ */
+ output_length = strlen(state->display_buffer);
+ if ((output_length < state->prev_length)
+ && (state->width >= state->prev_width)) {
+ char spaces[32];
+ int spaces_to_add;
+ spaces_to_add = state->prev_length - output_length;
+ /* Upper boundary on number of spaces */
+ if (spaces_to_add > 15) {
+ spaces_to_add = 15;
+ }
+ output_length += spaces_to_add;
+ spaces[spaces_to_add] = 0;
+ while (--spaces_to_add >= 0) {
+ spaces[spaces_to_add] = ' ';
+ }
+ strcat(state->display_buffer, spaces);
+ }
+ state->prev_width = state->width;
+ state->prev_length = output_length;
+
+ return state->display_buffer;
+}
+
+
+/*
+ * Output status information on standard error, where "esec" is the seconds
+ * elapsed since the transfer started, "sl" is the number of bytes transferred
+ * since the last update, and "tot" is the total number of bytes transferred
+ * so far.
+ *
+ * If "sl" is negative, this is the final update so the rate is given as an
+ * an average over the whole transfer; otherwise the current rate is shown.
+ *
+ * In line mode, "sl" and "tot" are in lines, not bytes.
+ */
+void pv_display(pvstate_t state, long double esec, long long sl,
+ long long tot)
+{
+ char *display;
+
+ if (NULL == state)
+ return;
+
+ /*
+ * If the display options need reparsing, do so to generate new
+ * formatting parameters.
+ */
+ if (state->reparse_display) {
+ pv__format_init(state);
+ state->reparse_display = 0;
+ }
+
+ pv_sig_checkbg();
+
+ display = pv__format(state, esec, sl, tot);
+ if (NULL == display)
+ return;
+
+ if (state->numeric) {
+ write(STDERR_FILENO, display, strlen(display));
+ } else if (state->cursor) {
+ pv_crs_update(state, display);
+ state->display_visible = 1;
+ } else {
+ write(STDERR_FILENO, display, strlen(display));
+ write(STDERR_FILENO, "\r", 1);
+ state->display_visible = 1;
+ }
+
+ debug("%s: [%s]", "display", display);
+}
+
+/* EOF */
diff --git a/src/pv/file.c b/src/pv/file.c
new file mode 100644
index 0000000..aab1cb4
--- /dev/null
+++ b/src/pv/file.c
@@ -0,0 +1,293 @@
+/*
+ * Functions for opening and closing files.
+ */
+
+#include "pv-internal.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/*
+ * Try to work out the total size of all data by adding up the sizes of all
+ * input files. If any of the input files are of indeterminate size (i.e.
+ * they are a pipe), the total size is set to zero.
+ *
+ * Any files that cannot be stat()ed or that access() says we can't read
+ * will cause a warning to be output and will be removed from the list.
+ *
+ * In line mode, any files that pass the above checks will then be read to
+ * determine how many lines they contain, and the total size will be set to
+ * the total line count. Only regular files will be read.
+ *
+ * Returns the total size, or 0 if it is unknown.
+ */
+unsigned long long pv_calc_total_size(pvstate_t state)
+{
+ unsigned long long total;
+ struct stat64 sb;
+ int rc, i, j, fd;
+
+ total = 0;
+ rc = 0;
+
+ /*
+ * No files specified - check stdin.
+ */
+ if (state->input_file_count < 1) {
+ if (0 == fstat64(STDIN_FILENO, &sb))
+ total = sb.st_size;
+ return total;
+ }
+
+ for (i = 0; i < state->input_file_count; i++) {
+ if (0 == strcmp(state->input_files[i], "-")) {
+ rc = fstat64(STDIN_FILENO, &sb);
+ if (rc != 0) {
+ total = 0;
+ return total;
+ }
+ } else {
+ rc = stat64(state->input_files[i], &sb);
+ if (0 == rc)
+ rc = access(state->input_files[i], R_OK);
+ }
+
+ if (rc != 0) {
+ pv_error(state, "%s: %s",
+ state->input_files[i], strerror(errno));
+ for (j = i; j < state->input_file_count - 1; j++) {
+ state->input_files[j] =
+ state->input_files[j + 1];
+ }
+ state->input_file_count--;
+ i--;
+ state->exit_status |= 2;
+ continue;
+ }
+
+ if (S_ISBLK(sb.st_mode)) {
+ /*
+ * Get the size of block devices by opening
+ * them and seeking to the end.
+ */
+ if (0 == strcmp(state->input_files[i], "-")) {
+ fd = open64("/dev/stdin", O_RDONLY);
+ } else {
+ fd = open64(state->input_files[i],
+ O_RDONLY);
+ }
+ if (fd >= 0) {
+ total += lseek64(fd, 0, SEEK_END);
+ close(fd);
+ } else {
+ pv_error(state, "%s: %s",
+ state->input_files[i],
+ strerror(errno));
+ state->exit_status |= 2;
+ }
+ } else if (S_ISREG(sb.st_mode)) {
+ total += sb.st_size;
+ } else {
+ total = 0;
+ }
+ }
+
+ /*
+ * Patch from Peter Samuelson: if we cannot work out the size of the
+ * input, but we are writing to a block device, then use the size of
+ * the output block device.
+ *
+ * Further modified to check that stdout is not in append-only mode
+ * and that we can seek back to the start after getting the size.
+ */
+ if (total <= 0) {
+ rc = fstat64(STDOUT_FILENO, &sb);
+ if ((0 == rc) && S_ISBLK(sb.st_mode)
+ && (0 == (fcntl(STDOUT_FILENO, F_GETFL) & O_APPEND))) {
+ total = lseek64(STDOUT_FILENO, 0, SEEK_END);
+ if (lseek64(STDOUT_FILENO, 0, SEEK_SET) != 0) {
+ pv_error(state, "%s: %s: %s", "(stdout)",
+ _
+ ("failed to seek to start of output"),
+ strerror(errno));
+ state->exit_status |= 2;
+ }
+ /*
+ * If we worked out a size, then set the
+ * stop-at-size flag to prevent a "no space left on
+ * device" error when we reach the end of the output
+ * device.
+ */
+ if (total > 0) {
+ state->stop_at_size = 1;
+ }
+ }
+ }
+
+ if (!state->linemode)
+ return total;
+
+ /*
+ * In line mode, we count input lines to work out the total size.
+ */
+ total = 0;
+
+ for (i = 0; i < state->input_file_count; i++) {
+ fd = -1;
+
+ if (0 == strcmp(state->input_files[i], "-")) {
+ rc = fstat64(STDIN_FILENO, &sb);
+ if ((rc != 0) || (!S_ISREG(sb.st_mode))) {
+ total = 0;
+ return total;
+ }
+ fd = dup(STDIN_FILENO);
+ } else {
+ rc = stat64(state->input_files[i], &sb);
+ if ((rc != 0) || (!S_ISREG(sb.st_mode))) {
+ total = 0;
+ return total;
+ }
+ fd = open64(state->input_files[i], O_RDONLY);
+ }
+
+ if (fd < 0) {
+ pv_error(state, "%s: %s", state->input_files[i],
+ strerror(errno));
+ total = 0;
+ state->exit_status |= 2;
+ return total;
+ }
+
+ while (1) {
+ unsigned char scanbuf[1024];
+ int numread, j;
+
+ numread = read(fd, scanbuf, sizeof(scanbuf));
+ if (numread < 0) {
+ pv_error(state, "%s: %s",
+ state->input_files[i],
+ strerror(errno));
+ state->exit_status |= 2;
+ break;
+ } else if (0 == numread) {
+ break;
+ }
+ for (j = 0; j < numread; j++) {
+ if ('\n' == scanbuf[j])
+ total++;
+ }
+ }
+
+ lseek64(fd, 0, SEEK_SET);
+ close(fd);
+ }
+
+ return total;
+}
+
+
+/*
+ * Close the given file descriptor and open the next one, whose number in
+ * the list is "filenum", returning the new file descriptor (or negative on
+ * error). It is an error if the next input file is the same as the file
+ * stdout is pointing to.
+ *
+ * Updates state->current_file in the process.
+ */
+int pv_next_file(pvstate_t state, int filenum, int oldfd)
+{
+ struct stat64 isb;
+ struct stat64 osb;
+ int fd, input_file_is_stdout;
+
+ if (oldfd > 0) {
+ if (close(oldfd)) {
+ pv_error(state, "%s: %s",
+ _("failed to close file"),
+ strerror(errno));
+ state->exit_status |= 8;
+ return -1;
+ }
+ }
+
+ if (filenum >= state->input_file_count) {
+ state->exit_status |= 8;
+ return -1;
+ }
+
+ if (filenum < 0) {
+ state->exit_status |= 8;
+ return -1;
+ }
+
+ if (0 == strcmp(state->input_files[filenum], "-")) {
+ fd = STDIN_FILENO;
+ } else {
+ fd = open64(state->input_files[filenum], O_RDONLY);
+ if (fd < 0) {
+ pv_error(state, "%s: %s: %s",
+ _("failed to read file"),
+ state->input_files[filenum],
+ strerror(errno));
+ state->exit_status |= 2;
+ return -1;
+ }
+ }
+
+ if (fstat64(fd, &isb)) {
+ pv_error(state, "%s: %s: %s",
+ _("failed to stat file"),
+ state->input_files[filenum], strerror(errno));
+ close(fd);
+ state->exit_status |= 2;
+ return -1;
+ }
+
+ if (fstat64(STDOUT_FILENO, &osb)) {
+ pv_error(state, "%s: %s",
+ _("failed to stat output file"), strerror(errno));
+ close(fd);
+ state->exit_status |= 2;
+ return -1;
+ }
+
+ /*
+ * Check that this new input file is not the same as stdout's
+ * destination. This restriction is ignored for anything other
+ * than a regular file or block device.
+ */
+ input_file_is_stdout = 1;
+ if (isb.st_dev != osb.st_dev)
+ input_file_is_stdout = 0;
+ if (isb.st_ino != osb.st_ino)
+ input_file_is_stdout = 0;
+ if (isatty(fd))
+ input_file_is_stdout = 0;
+ if ((!S_ISREG(isb.st_mode)) && (!S_ISBLK(isb.st_mode)))
+ input_file_is_stdout = 0;
+
+ if (input_file_is_stdout) {
+ pv_error(state, "%s: %s",
+ _("input file is output file"),
+ state->input_files[filenum]);
+ close(fd);
+ state->exit_status |= 4;
+ return -1;
+ }
+
+ state->current_file = state->input_files[filenum];
+ if (0 == strcmp(state->input_files[filenum], "-")) {
+ state->current_file = "(stdin)";
+ }
+ return fd;
+}
+
+/* EOF */
diff --git a/src/pv/loop.c b/src/pv/loop.c
new file mode 100644
index 0000000..59aac10
--- /dev/null
+++ b/src/pv/loop.c
@@ -0,0 +1,780 @@
+/*
+ * Main program entry point - read the command line options, then perform
+ * the appropriate actions.
+ */
+
+#include "pv-internal.h"
+
+#include
+#include
+#include
+#include
+
+#define _GNU_SOURCE 1
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+
+/*
+ * Add the given number of microseconds (which may be negative) to the given
+ * timeval.
+ */
+static void pv_timeval_add_usec(struct timeval *val, long usec)
+{
+ val->tv_usec += usec;
+ while (val->tv_usec < 0) {
+ val->tv_sec--;
+ val->tv_usec += 1000000;
+ }
+ while (val->tv_usec >= 1000000) {
+ val->tv_sec++;
+ val->tv_usec -= 1000000;
+ }
+}
+
+
+/*
+ * Pipe data from a list of files to standard output, giving information
+ * about the transfer on standard error according to the given options.
+ *
+ * Returns nonzero on error.
+ */
+int pv_main_loop(pvstate_t state)
+{
+ long written, lineswritten;
+ long long total_written, since_last, cansend;
+ long double target;
+ int eof_in, eof_out, final_update;
+ struct timeval start_time, next_update, next_ratecheck, cur_time;
+ struct timeval init_time, next_remotecheck;
+ long double elapsed;
+ struct stat64 sb;
+ int fd, n;
+
+ /*
+ * "written" is ALWAYS bytes written by the last transfer.
+ *
+ * "lineswritten" is the lines written by the last transfer,
+ * but is only updated in line mode.
+ *
+ * "total_written" is the total bytes written since the start,
+ * or in line mode, the total lines written since the start.
+ *
+ * "since_last" is the bytes written since the last display,
+ * or in line mode, the lines written since the last display.
+ *
+ * The remaining variables are all unchanged by linemode.
+ */
+
+ fd = -1;
+
+ pv_crs_init(state);
+
+ eof_in = 0;
+ eof_out = 0;
+ total_written = 0;
+ since_last = 0;
+ state->initial_offset = 0;
+
+ gettimeofday(&start_time, NULL);
+ gettimeofday(&cur_time, NULL);
+
+ next_update.tv_sec = start_time.tv_sec;
+ next_update.tv_usec = start_time.tv_usec;
+ if ((state->delay_start > 0)
+ && (state->delay_start > state->interval)) {
+ pv_timeval_add_usec(&next_update,
+ (long) (1000000.0 *
+ state->delay_start));
+ } else {
+ pv_timeval_add_usec(&next_update,
+ (long) (1000000.0 * state->interval));
+ }
+
+ next_ratecheck.tv_sec = start_time.tv_sec;
+ next_ratecheck.tv_usec = start_time.tv_usec;
+ next_remotecheck.tv_sec = start_time.tv_sec;
+ next_remotecheck.tv_usec = start_time.tv_usec;
+
+ target = 0;
+ final_update = 0;
+ n = 0;
+
+ fd = pv_next_file(state, n, -1);
+ if (fd < 0) {
+ if (state->cursor)
+ pv_crs_fini(state);
+ return state->exit_status;
+ }
+
+ /*
+ * Set target buffer size if the initial file's block size can be
+ * read and we weren't given a target buffer size.
+ */
+ if ((0 == fstat64(fd, &sb)) && (0 == state->target_buffer_size)) {
+ unsigned long long sz;
+ sz = sb.st_blksize * 32;
+ if (sz > BUFFER_SIZE_MAX)
+ sz = BUFFER_SIZE_MAX;
+ state->target_buffer_size = sz;
+ }
+
+ if (0 == state->target_buffer_size)
+ state->target_buffer_size = BUFFER_SIZE;
+
+ while ((!(eof_in && eof_out)) || (!final_update)) {
+
+ cansend = 0;
+
+ /*
+ * Check for remote messages from -R every short while
+ */
+ if ((cur_time.tv_sec > next_remotecheck.tv_sec)
+ || (cur_time.tv_sec == next_remotecheck.tv_sec
+ && cur_time.tv_usec >= next_remotecheck.tv_usec)) {
+ pv_remote_check(state);
+ pv_timeval_add_usec(&next_remotecheck,
+ REMOTE_INTERVAL);
+ }
+
+ if (state->pv_sig_abort)
+ break;
+
+ if (state->rate_limit > 0) {
+ gettimeofday(&cur_time, NULL);
+ if ((cur_time.tv_sec > next_ratecheck.tv_sec)
+ || (cur_time.tv_sec == next_ratecheck.tv_sec
+ && cur_time.tv_usec >=
+ next_ratecheck.tv_usec)) {
+ target +=
+ ((long double) (state->rate_limit)) /
+ (long double) (1000000 /
+ RATE_GRANULARITY);
+ pv_timeval_add_usec(&next_ratecheck,
+ RATE_GRANULARITY);
+ }
+ cansend = target;
+ }
+
+ /*
+ * If we have to stop at "size" bytes, make sure we don't
+ * try to write more than we're allowed to.
+ */
+ if ((0 < state->size) && (state->stop_at_size)) {
+ if ((state->size < (total_written + cansend))
+ || ((0 == cansend)
+ && (0 == state->rate_limit))) {
+ cansend = state->size - total_written;
+ if (0 >= cansend) {
+ eof_in = 1;
+ eof_out = 1;
+ }
+ }
+ }
+
+ if ((0 < state->size) && (state->stop_at_size)
+ && (0 >= cansend) && eof_in && eof_out) {
+ written = 0;
+ } else {
+ written =
+ pv_transfer(state, fd, &eof_in, &eof_out,
+ cansend, &lineswritten);
+ }
+
+ if (written < 0) {
+ if (state->cursor)
+ pv_crs_fini(state);
+ return state->exit_status;
+ }
+
+ if (state->linemode) {
+ since_last += lineswritten;
+ total_written += lineswritten;
+ if (state->rate_limit > 0)
+ target -= lineswritten;
+ } else {
+ since_last += written;
+ total_written += written;
+ if (state->rate_limit > 0)
+ target -= written;
+ }
+
+ if (eof_in && eof_out && n < (state->input_file_count - 1)) {
+ n++;
+ fd = pv_next_file(state, n, fd);
+ if (fd < 0) {
+ if (state->cursor)
+ pv_crs_fini(state);
+ return state->exit_status;
+ }
+ eof_in = 0;
+ eof_out = 0;
+ }
+
+ gettimeofday(&cur_time, NULL);
+
+ if (eof_in && eof_out) {
+ final_update = 1;
+ if ((state->display_visible)
+ || (0 == state->delay_start))
+ next_update.tv_sec = cur_time.tv_sec - 1;
+ }
+
+ if (state->no_op)
+ continue;
+
+ /*
+ * If -W was given, we don't output anything until we have
+ * written a byte (or line, in line mode), at which point
+ * we then count time as if we started when the first byte
+ * was received.
+ */
+ if (state->wait) {
+ if (state->linemode) {
+ if (lineswritten < 1)
+ continue;
+ } else {
+ if (written < 1)
+ continue;
+ }
+
+ state->wait = 0;
+
+ /*
+ * Reset the timer offset counter now that data
+ * transfer has begun, otherwise if we had been
+ * stopped and started (with ^Z / SIGTSTOP)
+ * previously (while waiting for data), the timers
+ * will be wrongly offset.
+ *
+ * While we reset the offset counter we must disable
+ * SIGTSTOP so things don't mess up.
+ */
+ pv_sig_nopause();
+ gettimeofday(&start_time, NULL);
+ state->pv_sig_toffset.tv_sec = 0;
+ state->pv_sig_toffset.tv_usec = 0;
+ pv_sig_allowpause();
+
+ next_update.tv_sec = start_time.tv_sec;
+ next_update.tv_usec = start_time.tv_usec;
+ pv_timeval_add_usec(&next_update,
+ (long) (1000000.0 *
+ state->interval));
+ }
+
+ if ((cur_time.tv_sec < next_update.tv_sec)
+ || (cur_time.tv_sec == next_update.tv_sec
+ && cur_time.tv_usec < next_update.tv_usec)) {
+ continue;
+ }
+
+ pv_timeval_add_usec(&next_update,
+ (long) (1000000.0 * state->interval));
+
+ if (next_update.tv_sec < cur_time.tv_sec) {
+ next_update.tv_sec = cur_time.tv_sec;
+ next_update.tv_usec = cur_time.tv_usec;
+ } else if (next_update.tv_sec == cur_time.tv_sec
+ && next_update.tv_usec < cur_time.tv_usec) {
+ next_update.tv_usec = cur_time.tv_usec;
+ }
+
+ init_time.tv_sec =
+ start_time.tv_sec + state->pv_sig_toffset.tv_sec;
+ init_time.tv_usec =
+ start_time.tv_usec + state->pv_sig_toffset.tv_usec;
+ if (init_time.tv_usec >= 1000000) {
+ init_time.tv_sec++;
+ init_time.tv_usec -= 1000000;
+ }
+ if (init_time.tv_usec < 0) {
+ init_time.tv_sec--;
+ init_time.tv_usec += 1000000;
+ }
+
+ elapsed = cur_time.tv_sec - init_time.tv_sec;
+ elapsed +=
+ (cur_time.tv_usec - init_time.tv_usec) / 1000000.0;
+
+ if (final_update)
+ since_last = -1;
+
+ if (state->pv_sig_newsize) {
+ state->pv_sig_newsize = 0;
+ pv_screensize(&(state->width), &(state->height));
+ }
+
+ pv_display(state, elapsed, since_last, total_written);
+
+ since_last = 0;
+ }
+
+ if (state->cursor) {
+ pv_crs_fini(state);
+ } else {
+ if ((!state->numeric) && (!state->no_op)
+ && (state->display_visible))
+ write(STDERR_FILENO, "\n", 1);
+ }
+
+ if (state->pv_sig_abort)
+ state->exit_status |= 32;
+
+ if (fd >= 0)
+ close(fd);
+
+ return state->exit_status;
+}
+
+
+/*
+ * Watch the progress of file descriptor state->watch_fd in process
+ * state->watch_pid and show details about the transfer on standard error
+ * according to the given options.
+ *
+ * Returns nonzero on error.
+ */
+int pv_watchfd_loop(pvstate_t state)
+{
+ struct pvwatchfd_s info;
+ long long position_now, total_written, since_last;
+ struct timeval next_update, cur_time;
+ struct timeval init_time, next_remotecheck;
+ long double elapsed;
+ int ended;
+ int first_check;
+ int rc;
+
+ info.watch_pid = state->watch_pid;
+ info.watch_fd = state->watch_fd;
+ rc = pv_watchfd_info(state, &info, 0);
+ if (0 != rc) {
+ state->exit_status |= 2;
+ return state->exit_status;
+ }
+
+ /*
+ * Use a size if one was passed, otherwise use the total size
+ * calculated.
+ */
+ if (0 >= state->size)
+ state->size = info.size;
+
+ if (state->size < 1) {
+ char *fmt;
+ while (NULL != (fmt = strstr(state->default_format, "%e"))) {
+ debug("%s", "zero size - removing ETA");
+ /* strlen-1 here to include trailing NUL */
+ memmove(fmt, fmt + 2, strlen(fmt) - 1);
+ state->reparse_display = 1;
+ }
+ }
+
+ gettimeofday(&(info.start_time), NULL);
+ gettimeofday(&cur_time, NULL);
+
+ next_update.tv_sec = info.start_time.tv_sec;
+ next_update.tv_usec = info.start_time.tv_usec;
+ pv_timeval_add_usec(&next_update,
+ (long) (1000000.0 * state->interval));
+
+ next_remotecheck.tv_sec = info.start_time.tv_sec;
+ next_remotecheck.tv_usec = info.start_time.tv_usec;
+
+ ended = 0;
+ total_written = 0;
+ since_last = 0;
+ first_check = 1;
+
+ while (!ended) {
+ /*
+ * Check for remote messages from -R every short while
+ */
+ if ((cur_time.tv_sec > next_remotecheck.tv_sec)
+ || (cur_time.tv_sec == next_remotecheck.tv_sec
+ && cur_time.tv_usec >= next_remotecheck.tv_usec)) {
+ pv_remote_check(state);
+ pv_timeval_add_usec(&next_remotecheck,
+ REMOTE_INTERVAL);
+ }
+
+ if (state->pv_sig_abort)
+ break;
+
+ position_now = pv_watchfd_position(&info);
+
+ if (position_now < 0) {
+ ended = 1;
+ } else {
+ since_last += position_now - total_written;
+ total_written = position_now;
+ if (first_check) {
+ state->initial_offset = position_now;
+ first_check = 0;
+ }
+ }
+
+ gettimeofday(&cur_time, NULL);
+
+ if (ended) {
+ ended = 1;
+ next_update.tv_sec = cur_time.tv_sec - 1;
+ }
+
+ if ((cur_time.tv_sec < next_update.tv_sec)
+ || (cur_time.tv_sec == next_update.tv_sec
+ && cur_time.tv_usec < next_update.tv_usec)) {
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 50000;
+ select(0, NULL, NULL, NULL, &tv);
+ continue;
+ }
+
+ pv_timeval_add_usec(&next_update,
+ (long) (1000000.0 * state->interval));
+
+ if (next_update.tv_sec < cur_time.tv_sec) {
+ next_update.tv_sec = cur_time.tv_sec;
+ next_update.tv_usec = cur_time.tv_usec;
+ } else if (next_update.tv_sec == cur_time.tv_sec
+ && next_update.tv_usec < cur_time.tv_usec) {
+ next_update.tv_usec = cur_time.tv_usec;
+ }
+
+ init_time.tv_sec =
+ info.start_time.tv_sec + state->pv_sig_toffset.tv_sec;
+ init_time.tv_usec =
+ info.start_time.tv_usec +
+ state->pv_sig_toffset.tv_usec;
+ if (init_time.tv_usec >= 1000000) {
+ init_time.tv_sec++;
+ init_time.tv_usec -= 1000000;
+ }
+ if (init_time.tv_usec < 0) {
+ init_time.tv_sec--;
+ init_time.tv_usec += 1000000;
+ }
+
+ elapsed = cur_time.tv_sec - init_time.tv_sec;
+ elapsed +=
+ (cur_time.tv_usec - init_time.tv_usec) / 1000000.0;
+
+ if (ended)
+ since_last = -1;
+
+ if (state->pv_sig_newsize) {
+ state->pv_sig_newsize = 0;
+ pv_screensize(&(state->width), &(state->height));
+ }
+
+ pv_display(state, elapsed, since_last, total_written);
+
+ since_last = 0;
+ }
+
+ if (!state->numeric)
+ write(STDERR_FILENO, "\n", 1);
+
+ if (state->pv_sig_abort)
+ state->exit_status |= 32;
+
+ return state->exit_status;
+}
+
+
+/*
+ * Watch the progress of all file descriptors in process state->watch_pid
+ * and show details about the transfers on standard error according to the
+ * given options.
+ *
+ * Returns nonzero on error.
+ */
+int pv_watchpid_loop(pvstate_t state)
+{
+ struct pvstate_s state_copy;
+ const char *original_format_string;
+ char new_format_string[512] = { 0, };
+ struct pvwatchfd_s *info_array = NULL;
+ struct pvstate_s *state_array = NULL;
+ int array_length = 0;
+ int fd_to_idx[FD_SETSIZE] = { 0, };
+ struct timeval next_update, cur_time, next_remotecheck;
+ int idx;
+ int prev_displayed_lines, blank_lines;
+ int first_pass = 1;
+
+ /*
+ * Make sure the process exists first, so we can give an error if
+ * it's not there at the start.
+ */
+ if (kill(state->watch_pid, 0) != 0) {
+ pv_error(state, "%s %u: %s",
+ _("pid"), state->watch_pid, strerror(errno));
+ state->exit_status |= 2;
+ return 2;
+ }
+
+ /*
+ * Make a copy of our state, ready to change in preparation for
+ * duplication.
+ */
+ memcpy(&state_copy, state, sizeof(state_copy));
+
+ /*
+ * Make sure there's a format string, and then insert %N into it if
+ * it's not present.
+ */
+ original_format_string =
+ state->format_string ? state->
+ format_string : state->default_format;
+ if (NULL == strstr(original_format_string, "%N")) {
+ snprintf(new_format_string, sizeof(new_format_string) - 1,
+ "%%N %s", original_format_string);
+ } else {
+ snprintf(new_format_string, sizeof(new_format_string) - 1,
+ "%s", original_format_string);
+ }
+ state_copy.format_string = NULL;
+ snprintf(state_copy.default_format,
+ sizeof(state_copy.default_format) - 1, "%s",
+ new_format_string);
+
+ /*
+ * Get things ready for the main loop.
+ */
+
+ gettimeofday(&cur_time, NULL);
+
+ next_remotecheck.tv_sec = cur_time.tv_sec;
+ next_remotecheck.tv_usec = cur_time.tv_usec;
+
+ next_update.tv_sec = cur_time.tv_sec;
+ next_update.tv_usec = cur_time.tv_usec;
+ pv_timeval_add_usec(&next_update,
+ (long) (1000000.0 * state->interval));
+
+ for (idx = 0; idx < FD_SETSIZE; idx++) {
+ fd_to_idx[idx] = -1;
+ }
+
+ prev_displayed_lines = 0;
+
+ while (1) {
+ int rc, fd, displayed_lines;
+
+ if (state->pv_sig_abort)
+ break;
+
+ gettimeofday(&cur_time, NULL);
+
+ if (kill(state->watch_pid, 0) != 0) {
+ if (first_pass) {
+ pv_error(state, "%s %u: %s",
+ _("pid"), state->watch_pid,
+ strerror(errno));
+ state->exit_status |= 2;
+ if (NULL != info_array)
+ free(info_array);
+ if (NULL != state_array)
+ free(state_array);
+ return 2;
+ }
+ break;
+ }
+
+ if ((cur_time.tv_sec < next_update.tv_sec)
+ || (cur_time.tv_sec == next_update.tv_sec
+ && cur_time.tv_usec < next_update.tv_usec)) {
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 50000;
+ select(0, NULL, NULL, NULL, &tv);
+ continue;
+ }
+
+ pv_timeval_add_usec(&next_update,
+ (long) (1000000.0 * state->interval));
+
+ if (next_update.tv_sec < cur_time.tv_sec) {
+ next_update.tv_sec = cur_time.tv_sec;
+ next_update.tv_usec = cur_time.tv_usec;
+ } else if (next_update.tv_sec == cur_time.tv_sec
+ && next_update.tv_usec < cur_time.tv_usec) {
+ next_update.tv_usec = cur_time.tv_usec;
+ }
+
+ if (state->pv_sig_newsize) {
+ state->pv_sig_newsize = 0;
+ pv_screensize(&(state->width), &(state->height));
+ for (idx = 0; idx < array_length; idx++) {
+ state_array[idx].width = state->width;
+ state_array[idx].height = state->height;
+ pv_watchpid_setname(state,
+ &(info_array[idx]));
+ state_array[idx].reparse_display = 1;
+ }
+ }
+
+ rc = pv_watchpid_scanfds(state, &state_copy,
+ state->watch_pid, &array_length,
+ &info_array, &state_array,
+ fd_to_idx);
+ if (rc != 0) {
+ if (first_pass) {
+ pv_error(state, "%s %u: %s",
+ _("pid"), state->watch_pid,
+ strerror(errno));
+ state->exit_status |= 2;
+ if (NULL != info_array)
+ free(info_array);
+ if (NULL != state_array)
+ free(state_array);
+ return 2;
+ }
+ break;
+ }
+
+ first_pass = 0;
+ displayed_lines = 0;
+
+ for (fd = 0; fd < FD_SETSIZE; fd++) {
+ long long position_now, since_last;
+ struct timeval init_time;
+ long double elapsed;
+
+ if (displayed_lines >= state->height)
+ break;
+
+ idx = fd_to_idx[fd];
+
+ if (idx < 0)
+ continue;
+
+ if (info_array[idx].watch_fd < 0) {
+ /*
+ * Non-displayable fd - just remove if
+ * changed
+ */
+ if (pv_watchfd_changed(&(info_array[idx]))) {
+ fd_to_idx[fd] = -1;
+ info_array[idx].watch_pid = 0;
+ debug("%s %d: %s", "fd", fd,
+ "removing");
+ }
+ continue;
+ }
+
+ /*
+ * Displayable fd - display, or remove if changed
+ */
+
+ position_now =
+ pv_watchfd_position(&(info_array[idx]));
+
+ if (position_now < 0) {
+ fd_to_idx[fd] = -1;
+ info_array[idx].watch_pid = 0;
+ debug("%s %d: %s", "fd", fd, "removing");
+ continue;
+ }
+
+ since_last =
+ position_now - info_array[idx].position;
+ info_array[idx].position = position_now;
+
+ init_time.tv_sec =
+ info_array[idx].start_time.tv_sec +
+ state->pv_sig_toffset.tv_sec;
+ init_time.tv_usec =
+ info_array[idx].start_time.tv_usec +
+ state->pv_sig_toffset.tv_usec;
+ if (init_time.tv_usec >= 1000000) {
+ init_time.tv_sec++;
+ init_time.tv_usec -= 1000000;
+ }
+ if (init_time.tv_usec < 0) {
+ init_time.tv_sec--;
+ init_time.tv_usec += 1000000;
+ }
+
+ elapsed = cur_time.tv_sec - init_time.tv_sec;
+ elapsed +=
+ (cur_time.tv_usec -
+ init_time.tv_usec) / 1000000.0;
+
+ if (displayed_lines > 0) {
+ debug("%s", "adding newline");
+ write(STDERR_FILENO, "\n", 1);
+ }
+
+ debug("%s %d [%d]: %Lf / %Ld / %Ld", "fd", fd, idx,
+ elapsed, since_last, position_now);
+
+ pv_display(&(state_array[idx]), elapsed,
+ since_last, position_now);
+ displayed_lines++;
+ }
+
+ /*
+ * Write blank lines if we're writing fewer lines than last
+ * time.
+ */
+ blank_lines = prev_displayed_lines - displayed_lines;
+ prev_displayed_lines = displayed_lines;
+
+ if (blank_lines > 0)
+ debug("%s: %d", "adding blank lines", blank_lines);
+
+ while (blank_lines > 0) {
+ int x;
+ if (displayed_lines > 0)
+ write(STDERR_FILENO, "\n", 1);
+ for (x = 0; x < state->width; x++)
+ write(STDERR_FILENO, " ", 1);
+ write(STDERR_FILENO, "\r", 1);
+ blank_lines--;
+ displayed_lines++;
+ }
+
+ debug("%s: %d", "displayed lines", displayed_lines);
+
+ while (displayed_lines > 1) {
+ write(STDERR_FILENO, "\033[A", 3);
+ displayed_lines--;
+ }
+ }
+
+ /*
+ * Clean up our displayed lines on exit.
+ */
+ blank_lines = prev_displayed_lines;
+ while (blank_lines > 0) {
+ int x;
+ for (x = 0; x < state->width; x++)
+ write(STDERR_FILENO, " ", 1);
+ write(STDERR_FILENO, "\r", 1);
+ blank_lines--;
+ if (blank_lines > 0)
+ write(STDERR_FILENO, "\n", 1);
+ }
+ while (prev_displayed_lines > 1) {
+ write(STDERR_FILENO, "\033[A", 3);
+ prev_displayed_lines--;
+ }
+
+ if (NULL != info_array)
+ free(info_array);
+ if (NULL != state_array)
+ free(state_array);
+
+ return 0;
+}
+
+/* EOF */
diff --git a/src/pv/number.c b/src/pv/number.c
new file mode 100644
index 0000000..b0c5572
--- /dev/null
+++ b/src/pv/number.c
@@ -0,0 +1,211 @@
+/*
+ * Functions for converting strings to numbers.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "pv.h"
+
+
+/*
+ * This function is used instead of the macro from because
+ * including causes weird versioned glibc dependencies on certain
+ * Red Hat systems, complicating package management.
+ */
+static int pv__isdigit(char c)
+{
+ return ((c >= '0') && (c <= '9'));
+}
+
+
+/*
+ * Return the numeric value of "str", as a long long.
+ */
+long long pv_getnum_ll(const char *str)
+{
+ long long n = 0;
+ long long decimal = 0;
+ int decdivisor = 1;
+ int shift = 0;
+
+ if (0 == str)
+ return n;
+
+ while (str[0] != 0 && (!pv__isdigit(str[0])))
+ str++;
+
+ for (; pv__isdigit(str[0]); str++) {
+ n = n * 10;
+ n += (str[0] - '0');
+ }
+
+ /*
+ * If a decimal value was given, skip the decimal part.
+ */
+ if (('.' == str[0]) || (',' == str[0])) {
+ str++;
+ for (; pv__isdigit(str[0]); str++) {
+ if (decdivisor < 10000) {
+ decimal = decimal * 10;
+ decimal += (str[0] - '0');
+ decdivisor = decdivisor * 10;
+ }
+ }
+ }
+
+ /*
+ * Parse any units given (K=KiB=*1024, M=MiB=1024KiB, G=GiB=1024MiB,
+ * T=TiB=1024GiB).
+ */
+ if (str[0]) {
+ while ((' ' == str[0]) || ('\t' == str[0]))
+ str++;
+ switch (str[0]) {
+ case 'k':
+ case 'K':
+ shift = 10;
+ break;
+ case 'm':
+ case 'M':
+ shift = 20;
+ break;
+ case 'g':
+ case 'G':
+ shift = 30;
+ break;
+ case 't':
+ case 'T':
+ shift = 40;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Binary left-shift the supplied number by "shift" times, i.e.
+ * apply the given units (KiB, MiB, etc) to it, but never shift left
+ * more than 30 at a time to avoid overflows.
+ */
+ while (shift > 0) {
+ int shiftby;
+
+ shiftby = shift;
+ if (shiftby > 30)
+ shiftby = 30;
+
+ n = n << shiftby;
+ decimal = decimal << shiftby;
+ shift -= shiftby;
+ }
+
+ /*
+ * Add any decimal component.
+ */
+ decimal = decimal / decdivisor;
+ n += decimal;
+
+ return n;
+}
+
+
+/*
+ * Return the numeric value of "str", as a double.
+ */
+double pv_getnum_d(const char *str)
+{
+ double n = 0.0;
+ double step = 1;
+
+ if (0 == str)
+ return n;
+
+ while (str[0] != 0 && (!pv__isdigit(str[0])))
+ str++;
+
+ for (; pv__isdigit(str[0]); str++) {
+ n = n * 10;
+ n += (str[0] - '0');
+ }
+
+ if ((str[0] != '.') && (str[0] != ','))
+ return n;
+
+ str++;
+
+ for (; pv__isdigit(str[0]) && step < 1000000; str++) {
+ step = step * 10;
+ n += (str[0] - '0') / step;
+ }
+
+ return n;
+}
+
+
+/*
+ * Return the numeric value of "str", as an int.
+ */
+int pv_getnum_i(const char *str)
+{
+ return (int) pv_getnum_ll(str);
+}
+
+
+/*
+ * Return nonzero if the given string is not a valid number of the given
+ * type.
+ */
+int pv_getnum_check(const char *str, pv_numtype_t type)
+{
+ if (0 == str)
+ return 1;
+
+ while ((' ' == str[0]) || ('\t' == str[0]))
+ str++;
+
+ if (!pv__isdigit(str[0]))
+ return 1;
+
+ for (; pv__isdigit(str[0]); str++);
+
+ if (('.' == str[0]) || (',' == str[0])) {
+ if (type == PV_NUMTYPE_INTEGER)
+ return 1;
+ str++;
+ for (; pv__isdigit(str[0]); str++);
+ }
+
+ if (0 == str[0])
+ return 0;
+
+ /*
+ * Suffixes are not allowed for doubles, only for integers.
+ */
+ if (type == PV_NUMTYPE_DOUBLE)
+ return 1;
+
+ while ((' ' == str[0]) || ('\t' == str[0]))
+ str++;
+ switch (str[0]) {
+ case 'k':
+ case 'K':
+ case 'm':
+ case 'M':
+ case 'g':
+ case 'G':
+ case 't':
+ case 'T':
+ str++;
+ break;
+ default:
+ return 1;
+ }
+
+ if (str[0] != 0)
+ return 1;
+
+ return 0;
+}
+
+/* EOF */
diff --git a/src/pv/signal.c b/src/pv/signal.c
new file mode 100644
index 0000000..e4b7aac
--- /dev/null
+++ b/src/pv/signal.c
@@ -0,0 +1,297 @@
+/*
+ * Signal handling functions.
+ */
+
+#include "pv-internal.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef HAVE_IPC
+void pv_crs_needreinit(pvstate_t);
+#endif
+
+static pvstate_t pv_sig_state = NULL;
+
+
+/*
+ * Handle SIGTTOU (tty output for background process) by redirecting stderr
+ * to /dev/null, so that we can be stopped and backgrounded without messing
+ * up the terminal. We store the old stderr file descriptor so that on a
+ * subsequent SIGCONT we can try writing to the terminal again, in case we
+ * get backgrounded and later get foregrounded again.
+ */
+static void pv_sig_ttou(int s)
+{
+ int fd;
+
+ fd = open("/dev/null", O_RDWR);
+ if (fd < 0)
+ return;
+
+ if (-1 == pv_sig_state->pv_sig_old_stderr)
+ pv_sig_state->pv_sig_old_stderr = dup(STDERR_FILENO);
+
+ dup2(fd, STDERR_FILENO);
+ close(fd);
+}
+
+
+/*
+ * Handle SIGTSTP (stop typed at tty) by storing the time the signal
+ * happened for later use by pv_sig_cont(), and then stopping the process.
+ */
+static void pv_sig_tstp(int s)
+{
+ gettimeofday(&(pv_sig_state->pv_sig_tstp_time), NULL);
+ raise(SIGSTOP);
+}
+
+
+/*
+ * Handle SIGCONT (continue if stopped) by adding the elapsed time since the
+ * last SIGTSTP to the elapsed time offset, and by trying to write to the
+ * terminal again (by replacing the /dev/null stderr with the old stderr).
+ */
+static void pv_sig_cont(int s)
+{
+ struct timeval tv;
+ struct termios t;
+
+ pv_sig_state->pv_sig_newsize = 1;
+
+ if (0 == pv_sig_state->pv_sig_tstp_time.tv_sec) {
+ tcgetattr(STDERR_FILENO, &t);
+ t.c_lflag |= TOSTOP;
+ tcsetattr(STDERR_FILENO, TCSANOW, &t);
+#ifdef HAVE_IPC
+ pv_crs_needreinit(pv_sig_state);
+#endif
+ return;
+ }
+
+ gettimeofday(&tv, NULL);
+
+ pv_sig_state->pv_sig_toffset.tv_sec +=
+ (tv.tv_sec - pv_sig_state->pv_sig_tstp_time.tv_sec);
+ pv_sig_state->pv_sig_toffset.tv_usec +=
+ (tv.tv_usec - pv_sig_state->pv_sig_tstp_time.tv_usec);
+ if (pv_sig_state->pv_sig_toffset.tv_usec >= 1000000) {
+ pv_sig_state->pv_sig_toffset.tv_sec++;
+ pv_sig_state->pv_sig_toffset.tv_usec -= 1000000;
+ }
+ if (pv_sig_state->pv_sig_toffset.tv_usec < 0) {
+ pv_sig_state->pv_sig_toffset.tv_sec--;
+ pv_sig_state->pv_sig_toffset.tv_usec += 1000000;
+ }
+
+ pv_sig_state->pv_sig_tstp_time.tv_sec = 0;
+ pv_sig_state->pv_sig_tstp_time.tv_usec = 0;
+
+ if (pv_sig_state->pv_sig_old_stderr != -1) {
+ dup2(pv_sig_state->pv_sig_old_stderr, STDERR_FILENO);
+ close(pv_sig_state->pv_sig_old_stderr);
+ pv_sig_state->pv_sig_old_stderr = -1;
+ }
+
+ tcgetattr(STDERR_FILENO, &t);
+ t.c_lflag |= TOSTOP;
+ tcsetattr(STDERR_FILENO, TCSANOW, &t);
+
+#ifdef HAVE_IPC
+ pv_crs_needreinit(pv_sig_state);
+#endif
+}
+
+
+/*
+ * Handle SIGWINCH (window size changed) by setting a flag.
+ */
+static void pv_sig_winch(int s)
+{
+ pv_sig_state->pv_sig_newsize = 1;
+}
+
+
+/*
+ * Handle termination signals by setting the abort flag.
+ */
+static void pv_sig_term(int s)
+{
+ pv_sig_state->pv_sig_abort = 1;
+}
+
+
+/*
+ * Initialise signal handling.
+ */
+void pv_sig_init(pvstate_t state)
+{
+ struct sigaction sa;
+
+ pv_sig_state = state;
+
+ pv_sig_state->pv_sig_old_stderr = -1;
+ pv_sig_state->pv_sig_tstp_time.tv_sec = 0;
+ pv_sig_state->pv_sig_tstp_time.tv_usec = 0;
+ pv_sig_state->pv_sig_toffset.tv_sec = 0;
+ pv_sig_state->pv_sig_toffset.tv_usec = 0;
+
+ /*
+ * Ignore SIGPIPE, so we don't die if stdout is a pipe and the other
+ * end closes unexpectedly.
+ */
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGPIPE, &sa, &(pv_sig_state->pv_sig_old_sigpipe));
+
+ /*
+ * Handle SIGTTOU by continuing with output switched off, so that we
+ * can be stopped and backgrounded without messing up the terminal.
+ */
+ sa.sa_handler = pv_sig_ttou;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGTTOU, &sa, &(pv_sig_state->pv_sig_old_sigttou));
+
+ /*
+ * Handle SIGTSTP by storing the time the signal happened for later
+ * use by pv_sig_cont(), and then stopping the process.
+ */
+ sa.sa_handler = pv_sig_tstp;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGTSTP, &sa, &(pv_sig_state->pv_sig_old_sigtstp));
+
+ /*
+ * Handle SIGCONT by adding the elapsed time since the last SIGTSTP
+ * to the elapsed time offset, and by trying to write to the
+ * terminal again.
+ */
+ sa.sa_handler = pv_sig_cont;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGCONT, &sa, &(pv_sig_state->pv_sig_old_sigcont));
+
+ /*
+ * Handle SIGWINCH by setting a flag to let the main loop know it
+ * has to reread the terminal size.
+ */
+ sa.sa_handler = pv_sig_winch;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGWINCH, &sa, &(pv_sig_state->pv_sig_old_sigwinch));
+
+ /*
+ * Handle SIGINT, SIGHUP, SIGTERM by setting a flag to let the
+ * main loop know it should quit now.
+ */
+ sa.sa_handler = pv_sig_term;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGINT, &sa, &(pv_sig_state->pv_sig_old_sigint));
+
+ sa.sa_handler = pv_sig_term;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGHUP, &sa, &(pv_sig_state->pv_sig_old_sighup));
+
+ sa.sa_handler = pv_sig_term;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGTERM, &sa, &(pv_sig_state->pv_sig_old_sigterm));
+}
+
+
+/*
+ * Shut down signal handling.
+ */
+void pv_sig_fini(pvstate_t state)
+{
+ sigaction(SIGPIPE, &(pv_sig_state->pv_sig_old_sigpipe), NULL);
+ sigaction(SIGTTOU, &(pv_sig_state->pv_sig_old_sigttou), NULL);
+ sigaction(SIGTSTP, &(pv_sig_state->pv_sig_old_sigtstp), NULL);
+ sigaction(SIGCONT, &(pv_sig_state->pv_sig_old_sigcont), NULL);
+ sigaction(SIGWINCH, &(pv_sig_state->pv_sig_old_sigwinch), NULL);
+ sigaction(SIGINT, &(pv_sig_state->pv_sig_old_sigint), NULL);
+ sigaction(SIGHUP, &(pv_sig_state->pv_sig_old_sighup), NULL);
+ sigaction(SIGTERM, &(pv_sig_state->pv_sig_old_sigterm), NULL);
+}
+
+
+/*
+ * Stop reacting to SIGTSTP and SIGCONT.
+ */
+void pv_sig_nopause(void)
+{
+ struct sigaction sa;
+
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGTSTP, &sa, NULL);
+
+ sa.sa_handler = SIG_DFL;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGCONT, &sa, NULL);
+}
+
+
+/*
+ * Start catching SIGTSTP and SIGCONT again.
+ */
+void pv_sig_allowpause(void)
+{
+ struct sigaction sa;
+
+ sa.sa_handler = pv_sig_tstp;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGTSTP, &sa, NULL);
+
+ sa.sa_handler = pv_sig_cont;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGCONT, &sa, NULL);
+}
+
+
+/*
+ * If we have redirected stderr to /dev/null, check every second or so to
+ * see whether we can write to the terminal again - this is so that if we
+ * get backgrounded, then foregrounded again, we start writing to the
+ * terminal again.
+ */
+void pv_sig_checkbg(void)
+{
+ static time_t next_check = 0;
+ struct termios t;
+
+ if (time(NULL) < next_check)
+ return;
+
+ next_check = time(NULL) + 1;
+
+ if (-1 == pv_sig_state->pv_sig_old_stderr)
+ return;
+
+ dup2(pv_sig_state->pv_sig_old_stderr, STDERR_FILENO);
+ close(pv_sig_state->pv_sig_old_stderr);
+ pv_sig_state->pv_sig_old_stderr = -1;
+
+ tcgetattr(STDERR_FILENO, &t);
+ t.c_lflag |= TOSTOP;
+ tcsetattr(STDERR_FILENO, TCSANOW, &t);
+
+#ifdef HAVE_IPC
+ pv_crs_needreinit(pv_sig_state);
+#endif
+}
+
+/* EOF */
diff --git a/src/pv/state.c b/src/pv/state.c
new file mode 100644
index 0000000..5673c5b
--- /dev/null
+++ b/src/pv/state.c
@@ -0,0 +1,219 @@
+/*
+ * State management functions.
+ */
+
+#include "pv-internal.h"
+
+#include
+#include
+#include
+
+
+/*
+ * Create a new state structure, and return it, or 0 (NULL) on error.
+ */
+pvstate_t pv_state_alloc(const char *program_name)
+{
+ pvstate_t state;
+
+ state = calloc(1, sizeof(*state));
+ if (0 == state)
+ return 0;
+
+ state->program_name = program_name;
+ state->watch_pid = 0;
+ state->watch_fd = -1;
+#ifdef HAVE_IPC
+ state->crs_shmid = -1;
+ state->crs_pvcount = 1;
+#endif /* HAVE_IPC */
+ state->crs_lock_fd = -1;
+
+ state->reparse_display = 1;
+ state->current_file = _("none");
+#ifdef HAVE_SPLICE
+ state->splice_failed_fd = -1;
+#endif /* HAVE_SPLICE */
+ state->display_visible = 0;
+
+ return state;
+}
+
+
+/*
+ * Free a state structure, after which it can no longer be used.
+ */
+void pv_state_free(pvstate_t state)
+{
+ if (0 == state)
+ return;
+
+ if (state->display_buffer)
+ free(state->display_buffer);
+ state->display_buffer = NULL;
+
+ if (state->transfer_buffer)
+ free(state->transfer_buffer);
+ state->transfer_buffer = NULL;
+
+ free(state);
+
+ return;
+}
+
+
+/*
+ * Set the formatting string, given a set of old-style formatting options.
+ */
+void pv_state_set_format(pvstate_t state, unsigned char progress,
+ unsigned char timer, unsigned char eta,
+ unsigned char fineta, unsigned char rate,
+ unsigned char average_rate, unsigned char bytes,
+ unsigned char bufpercent,
+ unsigned int lastwritten, const char *name)
+{
+#define PV_ADDFORMAT(x,y) if (x) { \
+ if (state->default_format[0] != 0) \
+ strcat(state->default_format, " "); \
+ strcat(state->default_format, y); \
+ }
+
+ state->default_format[0] = 0;
+ PV_ADDFORMAT(name, "%N");
+ PV_ADDFORMAT(bytes, "%b");
+ PV_ADDFORMAT(bufpercent, "%T");
+ PV_ADDFORMAT(timer, "%t");
+ PV_ADDFORMAT(rate, "%r");
+ PV_ADDFORMAT(average_rate, "%a");
+ PV_ADDFORMAT(progress, "%p");
+ PV_ADDFORMAT(eta, "%e");
+ PV_ADDFORMAT(fineta, "%I");
+ if (lastwritten > 0) {
+ char buf[16];
+ sprintf(buf, "%%%uA", lastwritten);
+ PV_ADDFORMAT(lastwritten, buf);
+ }
+
+ state->name = name;
+ state->reparse_display = 1;
+}
+
+
+void pv_state_force_set(pvstate_t state, unsigned char val)
+{
+ state->force = val;
+}
+
+void pv_state_cursor_set(pvstate_t state, unsigned char val)
+{
+ state->cursor = val;
+};
+
+void pv_state_numeric_set(pvstate_t state, unsigned char val)
+{
+ state->numeric = val;
+};
+
+void pv_state_wait_set(pvstate_t state, unsigned char val)
+{
+ state->wait = val;
+};
+
+void pv_state_delay_start_set(pvstate_t state, double val)
+{
+ state->delay_start = val;
+};
+
+void pv_state_linemode_set(pvstate_t state, unsigned char val)
+{
+ state->linemode = val;
+};
+
+void pv_state_null_set(pvstate_t state, unsigned char val)
+{
+ state->null = val;
+};
+
+void pv_state_no_op_set(pvstate_t state, unsigned char val)
+{
+ state->no_op = val;
+};
+
+void pv_state_skip_errors_set(pvstate_t state, unsigned char val)
+{
+ state->skip_errors = val;
+};
+
+void pv_state_stop_at_size_set(pvstate_t state, unsigned char val)
+{
+ state->stop_at_size = val;
+};
+
+void pv_state_rate_limit_set(pvstate_t state, unsigned long long val)
+{
+ state->rate_limit = val;
+};
+
+void pv_state_target_buffer_size_set(pvstate_t state,
+ unsigned long long val)
+{
+ state->target_buffer_size = val;
+};
+
+void pv_state_no_splice_set(pvstate_t state, unsigned char val)
+{
+ state->no_splice = val;
+};
+
+void pv_state_size_set(pvstate_t state, unsigned long long val)
+{
+ state->size = val;
+};
+
+void pv_state_interval_set(pvstate_t state, double val)
+{
+ state->interval = val;
+};
+
+void pv_state_width_set(pvstate_t state, unsigned int val)
+{
+ state->width = val;
+};
+
+void pv_state_height_set(pvstate_t state, unsigned int val)
+{
+ state->height = val;
+};
+
+void pv_state_name_set(pvstate_t state, const char *val)
+{
+ state->name = val;
+};
+
+void pv_state_format_string_set(pvstate_t state, const char *val)
+{
+ state->format_string = val;
+};
+
+void pv_state_watch_pid_set(pvstate_t state, unsigned int val)
+{
+ state->watch_pid = val;
+};
+
+void pv_state_watch_fd_set(pvstate_t state, int val)
+{
+ state->watch_fd = val;
+};
+
+
+/*
+ * Set the array of input files.
+ */
+void pv_state_inputfiles(pvstate_t state, int input_file_count,
+ const char **input_files)
+{
+ state->input_file_count = input_file_count;
+ state->input_files = input_files;
+}
+
+/* EOF */
diff --git a/src/pv/transfer.c b/src/pv/transfer.c
new file mode 100644
index 0000000..ee33df0
--- /dev/null
+++ b/src/pv/transfer.c
@@ -0,0 +1,861 @@
+/*
+ * Functions for transferring between file descriptors.
+ */
+
+#include "pv-internal.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/*
+ * Read up to "count" bytes from file descriptor "fd" into the buffer "buf",
+ * and return the number of bytes read, like read().
+ *
+ * Unlike read(), if we have read less than "count" bytes, we check to see
+ * if there's any more to read, and keep trying, to make sure we fill the
+ * buffer as full as we can.
+ *
+ * We stop retrying if the time elapsed since this function was entered
+ * reaches TRANSFER_READ_TIMEOUT microseconds.
+ */
+static ssize_t pv__transfer_read_repeated(int fd, void *buf, size_t count)
+{
+ struct timeval start_time;
+ ssize_t total_read;
+
+ gettimeofday(&start_time, NULL);
+
+ total_read = 0;
+
+ while (count > 0) {
+ ssize_t nread;
+ struct timeval now;
+ long elapsed_usec;
+
+ nread =
+ read(fd, buf,
+ count >
+ MAX_READ_AT_ONCE ? MAX_READ_AT_ONCE : count);
+ if (nread < 0)
+ return nread;
+
+ total_read += nread;
+ buf += nread;
+ count -= nread;
+
+ if (0 == nread)
+ return total_read;
+
+ gettimeofday(&now, NULL);
+ elapsed_usec =
+ 1000000 * (now.tv_sec - start_time.tv_sec) +
+ (now.tv_usec - start_time.tv_usec);
+ if (elapsed_usec > TRANSFER_READ_TIMEOUT) {
+ debug("%s %d: %s (%ld %s)", "fd", fd,
+ "stopping read - timer expired",
+ elapsed_usec, "usec elapsed");
+ return total_read;
+ }
+
+ if (count > 0) {
+ fd_set readfds;
+ struct timeval tv;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ FD_ZERO(&readfds);
+ FD_SET(fd, &readfds);
+
+ debug("%s %d: %s (%ld %s, %ld %s)", "fd", fd,
+ "trying another read after partial buffer fill",
+ nread, "read", count, "remaining");
+
+ if (select(fd + 1, &readfds, NULL, NULL, &tv) < 1)
+ break;
+ }
+ }
+
+ return total_read;
+}
+
+
+/*
+ * Write up to "count" bytes to file descriptor "fd" from the buffer "buf",
+ * and return the number of bytes written, like write().
+ *
+ * Unlike write(), if we have written less than "count" bytes, we check to
+ * see if we can write any more, and keep trying, to make sure we empty the
+ * buffer as much as we can.
+ *
+ * We stop retrying if the time elapsed since this function was entered
+ * reaches TRANSFER_WRITE_TIMEOUT microseconds.
+ */
+static ssize_t pv__transfer_write_repeated(int fd, void *buf, size_t count)
+{
+ struct timeval start_time;
+ ssize_t total_written;
+
+ gettimeofday(&start_time, NULL);
+
+ total_written = 0;
+
+ while (count > 0) {
+ ssize_t nwritten;
+ struct timeval now;
+ long elapsed_usec;
+ size_t asked_to_write;
+
+ asked_to_write = count >
+ MAX_WRITE_AT_ONCE ? MAX_WRITE_AT_ONCE : count;
+
+ nwritten = write(fd, buf, asked_to_write);
+ if (nwritten < 0) {
+ if ((EINTR == errno) || (EAGAIN == errno)) {
+ /*
+ * Interrupted by a signal - probably our
+ * alarm() - so just return what we've
+ * written so far.
+ */
+ return total_written;
+ } else {
+ /*
+ * Legitimate error - return negative.
+ */
+ return nwritten;
+ }
+ }
+
+ total_written += nwritten;
+ buf += nwritten;
+ count -= nwritten;
+
+ if (0 == nwritten)
+ return total_written;
+
+ gettimeofday(&now, NULL);
+ elapsed_usec =
+ 1000000 * (now.tv_sec - start_time.tv_sec) +
+ (now.tv_usec - start_time.tv_usec);
+ if (elapsed_usec > TRANSFER_WRITE_TIMEOUT) {
+ debug("%s %d: %s (%ld %s)", "fd", fd,
+ "stopping write - timer expired",
+ elapsed_usec, "usec elapsed");
+ return total_written;
+ }
+
+ /*
+ * Running the select() here seems to make PV eat a lot of
+ * CPU in some cases, so instead we just go round the loop
+ * again and rely on our alarm() to interrupt us if we run
+ * out of time - also on our gettimeofday() check.
+ */
+ if (count > 0) {
+ fd_set writefds;
+ struct timeval tv;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ FD_ZERO(&writefds);
+ FD_SET(fd, &writefds);
+
+ debug("%s %d: %s (%ld %s, %ld %s)", "fd", fd,
+ "trying another write after partial buffer flush",
+ nwritten, "written", count, "remaining");
+
+# if 0 /* disabled after 1.6.0 - see comment above */
+ if (select(fd + 1, NULL, &writefds, NULL, &tv) < 1)
+ break;
+# endif
+ }
+ }
+
+ return total_written;
+}
+
+
+/*
+ * Read some data from the given file descriptor. Returns zero if there was
+ * a transient error and we need to return 0 from pv_transfer, otherwise
+ * returns 1.
+ *
+ * At most, the number of bytes read will be the number of bytes remaining
+ * in the input buffer. If state->rate_limit is >0, and/or "allowed" is >0,
+ * then the maximum number of bytes read will be the number remaining unused
+ * in the input buffer or the value of "allowed", whichever is smaller.
+ *
+ * If splice() was successfully used, sets state->splice_used to 1; if it
+ * failed, then state->splice_failed_fd is updated to the current fd so
+ * splice() won't be tried again until the next input file.
+ *
+ * Updates state->read_position by the number of bytes read, unless splice()
+ * was used, in which case it does not since there's nothing in the buffer
+ * (and it also adds the bytes to state->written since they've been written
+ * to the output).
+ *
+ * On read error, updates state->exit_status, and if allowed by
+ * state->skip_errors, tries to skip past the problem.
+ *
+ * If the end of the input file is reached or the error is unrecoverable,
+ * sets *eof_in to 1. If all data in the buffer has been written at this
+ * point, then also sets *eof_out.
+ */
+static int pv__transfer_read(pvstate_t state, int fd,
+ int *eof_in, int *eof_out,
+ unsigned long long allowed,
+ long *lineswritten)
+{
+ unsigned long bytes_can_read;
+ unsigned long amount_to_skip;
+ long amount_skipped;
+ long orig_offset;
+ long skip_offset;
+ ssize_t nread;
+#ifdef HAVE_SPLICE
+ size_t bytes_to_splice;
+#endif /* HAVE_SPLICE */
+
+ bytes_can_read = state->buffer_size - state->read_position;
+
+#ifdef HAVE_SPLICE
+ state->splice_used = 0;
+ if ((!state->linemode) && (!state->no_splice)
+ && (fd != state->splice_failed_fd)
+ && (0 == state->to_write)) {
+ if (state->rate_limit || allowed != 0)
+ bytes_to_splice = allowed;
+ else
+ bytes_to_splice = bytes_can_read;
+
+ nread = splice(fd, NULL, STDOUT_FILENO, NULL,
+ bytes_to_splice, SPLICE_F_MORE);
+
+ state->splice_used = 1;
+ if ((nread < 0) && (EINVAL == errno)) {
+ debug("%s %d: %s", "fd", fd,
+ "splice failed with EINVAL - disabling");
+ state->splice_failed_fd = fd;
+ state->splice_used = 0;
+ /*
+ * Fall through to read() below.
+ */
+ } else if (nread > 0) {
+ state->written = nread;
+ } else if ((-1 == nread) && (EAGAIN == errno)) {
+ /* nothing read yet - do nothing */
+ } else {
+ /* EOF might not really be EOF, it seems */
+ state->splice_used = 0;
+ }
+ }
+ if (0 == state->splice_used) {
+ nread =
+ pv__transfer_read_repeated(fd,
+ state->transfer_buffer +
+ state->read_position,
+ bytes_can_read);
+ }
+#else
+ nread =
+ pv__transfer_read_repeated(fd,
+ state->transfer_buffer +
+ state->read_position,
+ bytes_can_read);
+#endif /* HAVE_SPLICE */
+
+
+ if (0 == nread) {
+ /*
+ * If read returned 0, we've reached the end of this input
+ * file. If we've also written all the data in the transfer
+ * buffer, we set eof_out as well, so that the main loop can
+ * move on to the next input file.
+ */
+ *eof_in = 1;
+ if (state->write_position >= state->read_position)
+ *eof_out = 1;
+ return 1;
+ } else if (nread > 0) {
+ /*
+ * Read returned >0, so we successfully read data - clear
+ * the error counter and update our record of how much data
+ * we've got in the buffer.
+ */
+ state->read_errors_in_a_row = 0;
+#ifdef HAVE_SPLICE
+ /*
+ * If we used splice(), there isn't any more data in the
+ * buffer than there was before.
+ */
+ if (0 == state->splice_used)
+ state->read_position += nread;
+#else
+ state->read_position += nread;
+#endif /* HAVE_SPLICE */
+ return 1;
+ }
+
+ /*
+ * If we reach this point, nread<0, so there was an error.
+ */
+
+ /*
+ * If a read error occurred but it was EINTR or EAGAIN, just wait a
+ * bit and then return zero, since this was a transient error.
+ */
+ if ((EINTR == errno) || (EAGAIN == errno)) {
+ struct timeval tv;
+ debug("%s %d: %s: %s", "fd", fd,
+ "transient error - waiting briefly",
+ strerror(errno));
+ tv.tv_sec = 0;
+ tv.tv_usec = 10000;
+ select(0, NULL, NULL, NULL, &tv);
+ return 0;
+ }
+
+ /*
+ * The read error is not transient, so update the program's final
+ * exit status, regardless of whether we're skipping errors, and
+ * increment the error counter.
+ */
+ state->exit_status |= 16;
+ state->read_errors_in_a_row++;
+
+ /*
+ * If we aren't skipping errors, show the error and pretend we
+ * reached the end of this file.
+ */
+ if (0 == state->skip_errors) {
+ pv_error(state, "%s: %s: %s",
+ state->current_file,
+ _("read failed"), strerror(errno));
+ *eof_in = 1;
+ if (state->write_position >= state->read_position) {
+ *eof_out = 1;
+ }
+ return 1;
+ }
+
+ /*
+ * Try to skip past the error.
+ */
+
+ amount_skipped = -1;
+
+ if (!state->read_error_warning_shown) {
+ pv_error(state, "%s: %s: %s",
+ state->current_file,
+ _
+ ("warning: read errors detected"),
+ strerror(errno));
+ state->read_error_warning_shown = 1;
+ }
+
+ orig_offset = lseek64(fd, 0, SEEK_CUR);
+
+ /*
+ * If the file is not seekable, we can't skip past the error, so we
+ * will have to abandon the attempt and pretend we reached the end
+ * of the file.
+ */
+ if (0 > orig_offset) {
+ pv_error(state, "%s: %s: %s",
+ state->current_file,
+ _("file is not seekable"), strerror(errno));
+ *eof_in = 1;
+ if (state->write_position >= state->read_position) {
+ *eof_out = 1;
+ }
+ return 1;
+ }
+
+ if (state->read_errors_in_a_row < 10) {
+ amount_to_skip = state->read_errors_in_a_row < 5 ? 1 : 2;
+ } else if (state->read_errors_in_a_row < 20) {
+ amount_to_skip = 1 << (state->read_errors_in_a_row - 10);
+ } else {
+ amount_to_skip = 512;
+ }
+
+ /*
+ * Round the skip amount down to the start of the next block of the
+ * skip amount size. For instance if the skip amount is 512, but
+ * our file offset is 257, we'll jump to 512 instead of 769.
+ */
+ if (amount_to_skip > 1) {
+ skip_offset = orig_offset + amount_to_skip;
+ skip_offset -= (skip_offset % amount_to_skip);
+ if (skip_offset > orig_offset) {
+ amount_to_skip = skip_offset - orig_offset;
+ }
+ }
+
+ /*
+ * Trim the skip amount so we wouldn't read too much.
+ */
+ if (amount_to_skip > bytes_can_read)
+ amount_to_skip = bytes_can_read;
+
+ skip_offset = lseek64(fd, orig_offset + amount_to_skip, SEEK_SET);
+
+ /*
+ * If the skip we just tried didn't work, try only skipping 1 byte
+ * in case we were trying to go past the end of the input file.
+ */
+ if (skip_offset < 0) {
+ amount_to_skip = 1;
+ skip_offset =
+ lseek64(fd, orig_offset + amount_to_skip, SEEK_SET);
+ }
+
+ if (skip_offset < 0) {
+ /*
+ * Failed to skip - lseek() returned an error, so mark the
+ * file as having ended.
+ */
+ *eof_in = 1;
+ /*
+ * EINVAL means the file has ended since we've tried to go
+ * past the end of it, so we don't bother with a warning
+ * since it just means we've reached the end anyway.
+ */
+ if (EINVAL != errno) {
+ pv_error(state,
+ "%s: %s: %s",
+ state->current_file,
+ _
+ ("failed to seek past error"),
+ strerror(errno));
+ }
+ } else {
+ amount_skipped = skip_offset - orig_offset;
+ }
+
+ /*
+ * If we succeeded in skipping some bytes, zero the equivalent part
+ * of the transfer buffer, and update the buffer position.
+ */
+ if (amount_skipped > 0) {
+ memset(state->transfer_buffer +
+ state->read_position, 0, amount_skipped);
+ state->read_position += amount_skipped;
+ if (state->skip_errors < 2) {
+ pv_error(state, "%s: %s: %ld - %ld (%ld %s)",
+ state->current_file,
+ _
+ ("skipped past read error"),
+ orig_offset,
+ skip_offset, amount_skipped, _("B"));
+ }
+ } else {
+ /*
+ * Failed to skip - mark file as ended.
+ */
+ *eof_in = 1;
+ if (state->write_position >= state->read_position) {
+ *eof_out = 1;
+ }
+ }
+
+ return 1;
+}
+
+
+/*
+ * Write state->to_write bytes of data from the transfer buffer to stdout.
+ * Returns zero if there was a transient error and we need to return 0 from
+ * pv_transfer, otherwise returns 1.
+ *
+ * Updates state->write_position by moving it on by the number of bytes
+ * written; adds the number of bytes written to state->written; sets
+ * *eof_out on stdout EOF or when the write position catches up with the
+ * read position AND *eof_in is 1 (meaning we've reached the end of data).
+ *
+ * On error, sets *eof_out to 1, sets state->written to -1, and updates
+ * state->exit_status.
+ */
+static int pv__transfer_write(pvstate_t state, int fd,
+ int *eof_in, int *eof_out,
+ long *lineswritten)
+{
+ ssize_t nwritten;
+
+ signal(SIGALRM, SIG_IGN);
+ alarm(1);
+
+ nwritten = pv__transfer_write_repeated(STDOUT_FILENO,
+ state->transfer_buffer +
+ state->write_position,
+ state->to_write);
+
+ alarm(0);
+
+ if (0 == nwritten) {
+ /*
+ * Write returned 0 - EOF on stdout.
+ */
+ *eof_out = 1;
+ return 1;
+ } else if (nwritten > 0) {
+ /*
+ * Write returned >0 - data successfully written.
+ */
+ if ((state->linemode) && (lineswritten != NULL)) {
+ /*
+ * Guillaume Marcais: use strchr to count \n
+ */
+ unsigned char save;
+ char *ptr;
+ long lines = 0;
+
+ save =
+ state->transfer_buffer[state->write_position +
+ nwritten];
+ state->transfer_buffer[state->write_position +
+ nwritten] = 0;
+ ptr =
+ (char *) (state->transfer_buffer +
+ state->write_position - 1);
+
+ if (state->null) {
+ for (ptr++;
+ ptr -
+ (char *) state->transfer_buffer -
+ state->write_position < nwritten;
+ ptr++) {
+ if (*ptr == '\0')
+ ++lines;
+ }
+ } else {
+ while ((ptr =
+ strchr((char *) (ptr + 1), '\n')))
+ ++lines;
+ }
+
+ *lineswritten += lines;
+ state->transfer_buffer[state->write_position +
+ nwritten] = save;
+ }
+
+ state->write_position += nwritten;
+ state->written += nwritten;
+
+ /*
+ * If we're monitoring the output, update our copy of the
+ * last few bytes we've written.
+ */
+ if (((state->components_used & PV_DISPLAY_OUTPUTBUF) != 0)
+ && (nwritten > 0)) {
+ long new_portion_length, old_portion_length;
+
+ new_portion_length = nwritten;
+ if (new_portion_length > state->lastoutput_length)
+ new_portion_length =
+ state->lastoutput_length;
+
+ old_portion_length =
+ state->lastoutput_length - new_portion_length;
+
+ /*
+ * Make room for the new portion.
+ */
+ if (old_portion_length > 0) {
+ memmove(state->lastoutput_buffer,
+ state->lastoutput_buffer +
+ new_portion_length,
+ old_portion_length);
+ }
+
+ /*
+ * Copy the new data in.
+ */
+ memcpy(state->lastoutput_buffer +
+ old_portion_length,
+ state->transfer_buffer +
+ state->write_position - new_portion_length,
+ new_portion_length);
+ }
+
+ /*
+ * If we've written all the data in the buffer, reset the
+ * read pointer to the start, and if the input file is at
+ * EOF, set eof_out as well to indicate that we've written
+ * everything for this input file.
+ */
+ if (state->write_position >= state->read_position) {
+ state->write_position = 0;
+ state->read_position = 0;
+ if (*eof_in)
+ *eof_out = 1;
+ }
+
+ return 1;
+ }
+
+ /*
+ * If we reach this point, nwritten<0, so there was an error.
+ */
+
+ /*
+ * If a write error occurred but it was EINTR or EAGAIN, just wait a
+ * bit and then return zero, since this was a transient error.
+ */
+ if ((EINTR == errno) || (EAGAIN == errno)) {
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 10000;
+ select(0, NULL, NULL, NULL, &tv);
+ return 0;
+ }
+
+ /*
+ * SIGPIPE means we've finished. Don't output an error because it's
+ * not really our error to report.
+ */
+ if (EPIPE == errno) {
+ *eof_in = 1;
+ *eof_out = 1;
+ return 0;
+ }
+
+ pv_error(state, "%s: %s", _("write failed"), strerror(errno));
+ state->exit_status |= 16;
+ *eof_out = 1;
+ state->written = -1;
+
+ return 1;
+}
+
+
+/*
+ * Transfer some data from "fd" to standard output, timing out after 9/100
+ * of a second. If state->rate_limit is >0, and/or "allowed" is >0, only up
+ * to "allowed" bytes can be written. The variables that "eof_in" and
+ * "eof_out" point to are used to flag that we've finished reading and
+ * writing respectively.
+ *
+ * Returns the number of bytes written, or negative on error (in which case
+ * state->exit_status is updated). In line mode, the number of lines written
+ * will be put into *lineswritten.
+ */
+long pv_transfer(pvstate_t state, int fd, int *eof_in, int *eof_out,
+ unsigned long long allowed, long *lineswritten)
+{
+ struct timeval tv;
+ fd_set readfds;
+ fd_set writefds;
+ int max_fd;
+ int n;
+
+ if (NULL == state)
+ return 0;
+
+ /*
+ * Reinitialise the error skipping variables if the file descriptor
+ * has changed since the last time we were called.
+ */
+ if (fd != state->last_read_skip_fd) {
+ state->last_read_skip_fd = fd;
+ state->read_errors_in_a_row = 0;
+ state->read_error_warning_shown = 0;
+ }
+
+ if (NULL == state->transfer_buffer) {
+ state->buffer_size = state->target_buffer_size;
+ state->transfer_buffer =
+ (unsigned char *) malloc(state->buffer_size + 32);
+ if (NULL == state->transfer_buffer) {
+ pv_error(state, "%s: %s",
+ _("buffer allocation failed"),
+ strerror(errno));
+ state->exit_status |= 64;
+ return -1;
+ }
+ }
+
+ /*
+ * Reallocate the buffer if the buffer size has changed mid-transfer.
+ */
+ if (state->buffer_size < state->target_buffer_size) {
+ unsigned char *newptr;
+ newptr =
+ realloc(state->transfer_buffer,
+ state->target_buffer_size + 32);
+ if (NULL == newptr) {
+ /*
+ * Reset target if realloc failed so we don't keep
+ * trying to realloc over and over.
+ */
+ debug("realloc: %s", strerror(errno));
+ state->target_buffer_size = state->buffer_size;
+ } else {
+ debug("%s: %ld", "buffer resized",
+ state->buffer_size);
+ state->transfer_buffer = newptr;
+ state->buffer_size = state->target_buffer_size;
+ }
+ }
+
+ if ((state->linemode) && (lineswritten != NULL))
+ *lineswritten = 0;
+
+ if ((*eof_in) && (*eof_out))
+ return 0;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 90000;
+
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+
+ max_fd = 0;
+
+ /*
+ * If the input file is not at EOF and there's room in the buffer,
+ * look for incoming data from it.
+ */
+ if ((!(*eof_in)) && (state->read_position < state->buffer_size)) {
+ FD_SET(fd, &readfds);
+ if (fd > max_fd)
+ max_fd = fd;
+ }
+
+ /*
+ * Work out how much we're allowed to write, based on the amount of
+ * data left in the buffer. If rate limiting is active or "allowed"
+ * is >0, then this puts an upper limit on how much we're allowed to
+ * write.
+ */
+ state->to_write = state->read_position - state->write_position;
+ if ((state->rate_limit > 0) || (allowed > 0)) {
+ if (state->to_write > allowed) {
+ state->to_write = allowed;
+ }
+ }
+
+ /*
+ * If we don't think we've finished writing and there's anything
+ * we're allowed to write, look for the stdout becoming writable.
+ */
+ if ((!(*eof_out)) && (state->to_write > 0)) {
+ FD_SET(STDOUT_FILENO, &writefds);
+ if (STDOUT_FILENO > max_fd)
+ max_fd = STDOUT_FILENO;
+ }
+
+ n = select(max_fd + 1, &readfds, &writefds, NULL, &tv);
+
+ if (n < 0) {
+ /*
+ * Ignore transient errors by returning 0 immediately.
+ */
+ if (EINTR == errno)
+ return 0;
+
+ /*
+ * Any other error is a problem and we must report back.
+ */
+ pv_error(state, "%s: %s: %d: %s",
+ state->current_file,
+ _("select call failed"), n, strerror(errno));
+
+ state->exit_status |= 16;
+
+ return -1;
+ }
+
+ state->written = 0;
+
+ /*
+ * If there is data to read, try to read some in. Return early if
+ * there was a transient read error.
+ *
+ * NB this can update state->written because of splice().
+ */
+ if (FD_ISSET(fd, &readfds)) {
+ if (pv__transfer_read
+ (state, fd, eof_in, eof_out, allowed,
+ lineswritten) == 0)
+ return 0;
+ }
+
+ /*
+ * In line mode, only write up to and including the last newline,
+ * so that we're writing output line-by-line.
+ */
+ if ((state->to_write > 0) && (state->linemode) && !(state->null)) {
+ /*
+ * Guillaume Marcais: use strrchr to find last \n
+ */
+ unsigned char save;
+ char *start;
+ char *end;
+
+ save =
+ state->transfer_buffer[state->write_position +
+ state->to_write];
+ state->transfer_buffer[state->write_position +
+ state->to_write] = 0;
+
+ start =
+ (char *) (state->transfer_buffer +
+ state->write_position);
+ end = strrchr(start, '\n');
+ state->transfer_buffer[state->write_position +
+ state->to_write] = save;
+
+ if (end != NULL) {
+ state->to_write = (end - start) + 1;
+ }
+ }
+
+ /*
+ * If there is data to write, and stdout is ready to receive it, and
+ * we didn't use splice() this time, write some data. Return early
+ * if there was a transient write error.
+ */
+ if (FD_ISSET(STDOUT_FILENO, &writefds)
+#ifdef HAVE_SPLICE
+ && (0 == state->splice_used)
+#endif /* HAVE_SPLICE */
+ && (state->read_position > state->write_position)
+ && (state->to_write > 0)) {
+ if (pv__transfer_write
+ (state, fd, eof_in, eof_out, lineswritten) == 0)
+ return 0;
+ }
+#ifdef MAXIMISE_BUFFER_FILL
+ /*
+ * Rotate the written bytes out of the buffer so that it can be
+ * filled up completely by the next read.
+ */
+ if (state->write_position > 0) {
+ if (state->write_position < state->read_position) {
+ memmove(state->transfer_buffer,
+ state->transfer_buffer +
+ state->write_position,
+ state->read_position -
+ state->write_position);
+ state->read_position -= state->write_position;
+ state->write_position = 0;
+ } else {
+ state->write_position = 0;
+ state->read_position = 0;
+ }
+ }
+#endif /* MAXIMISE_BUFFER_FILL */
+
+ return state->written;
+}
+
+/* EOF */
diff --git a/src/pv/watchpid.c b/src/pv/watchpid.c
new file mode 100644
index 0000000..0fa147e
--- /dev/null
+++ b/src/pv/watchpid.c
@@ -0,0 +1,385 @@
+/*
+ * Functions for watching file descriptors in other processes.
+ */
+
+#include "pv-internal.h"
+
+#include
+#include
+#include
+#include
+
+#define _GNU_SOURCE 1
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/*
+ * Fill in the given information structure with the file paths and stat
+ * details of the given file descriptor within the given process (given
+ * within the info structure).
+ *
+ * Returns nonzero on error - error codes are:
+ *
+ * -1 - info or state were NULL
+ * 1 - process does not exist
+ * 2 - readlink on /proc/pid/fd/N failed
+ * 3 - stat or lstat on /proc/pid/fd/N failed
+ * 4 - file descriptor is not opened on a regular file
+ *
+ * If "automatic" is nonzero, then this fd was picked automatically, and so
+ * if it's not readable or not a regular file, no error is displayed and the
+ * function just returns an error code.
+ */
+int pv_watchfd_info(pvstate_t state, pvwatchfd_t info, int automatic)
+{
+ if (NULL == state)
+ return -1;
+ if (NULL == info)
+ return -1;
+
+ if (kill(info->watch_pid, 0) != 0) {
+ if (!automatic)
+ pv_error(state, "%s %u: %s",
+ _("pid"),
+ info->watch_pid, strerror(errno));
+ return 1;
+ }
+
+ snprintf(info->file_fdinfo, sizeof(info->file_fdinfo) - 1,
+ "/proc/%u/fdinfo/%d", info->watch_pid, info->watch_fd);
+ snprintf(info->file_fd, sizeof(info->file_fd) - 1,
+ "/proc/%u/fd/%d", info->watch_pid, info->watch_fd);
+
+ if (readlink
+ (info->file_fd, info->file_fdpath,
+ sizeof(info->file_fdpath) - 1) < 0) {
+ if (!automatic)
+ pv_error(state, "%s %u: %s %d: %s",
+ _("pid"),
+ info->watch_pid,
+ _("fd"), info->watch_fd, strerror(errno));
+ return 2;
+ }
+
+ if (!((0 == stat64(info->file_fd, &(info->sb_fd)))
+ && (0 == lstat64(info->file_fd, &(info->sb_fd_link))))) {
+ if (!automatic)
+ pv_error(state, "%s %u: %s %d: %s: %s",
+ _("pid"),
+ info->watch_pid,
+ _("fd"),
+ info->watch_fd, info->file_fdpath,
+ strerror(errno));
+ return 3;
+ }
+
+ info->size = 0;
+
+ if (S_ISBLK(info->sb_fd.st_mode)) {
+ int fd;
+
+ /*
+ * Get the size of block devices by opening
+ * them and seeking to the end.
+ */
+ fd = open64(info->file_fdpath, O_RDONLY);
+ if (fd >= 0) {
+ info->size = lseek64(fd, 0, SEEK_END);
+ close(fd);
+ } else {
+ info->size = 0;
+ }
+ } else if (S_ISREG(info->sb_fd.st_mode)) {
+ if ((info->sb_fd_link.st_mode & S_IWUSR) == 0) {
+ info->size = info->sb_fd.st_size;
+ }
+ } else {
+ if (!automatic)
+ pv_error(state, "%s %u: %s %d: %s: %s",
+ _("pid"),
+ info->watch_pid,
+ _("fd"),
+ info->watch_fd,
+ info->file_fdpath,
+ _("not a regular file or block device"));
+ return 4;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Return nonzero if the given file descriptor has changed in some way since
+ * we started looking at it (i.e. changed destination or permissions).
+ */
+int pv_watchfd_changed(pvwatchfd_t info)
+{
+ struct stat64 sb_fd, sb_fd_link;
+
+ if ((0 == stat64(info->file_fd, &sb_fd))
+ && (0 == lstat64(info->file_fd, &sb_fd_link))) {
+ if ((sb_fd.st_dev != info->sb_fd.st_dev)
+ || (sb_fd.st_ino != info->sb_fd.st_ino)
+ || (sb_fd_link.st_mode != info->sb_fd_link.st_mode)
+ ) {
+ return 1;
+ }
+ } else {
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Return the current file position of the given file descriptor, or -1 if
+ * the fd has closed or has changed in some way.
+ */
+long long pv_watchfd_position(pvwatchfd_t info)
+{
+ long long position;
+ FILE *fptr;
+
+ if (pv_watchfd_changed(info))
+ return -1;
+
+ fptr = fopen(info->file_fdinfo, "r");
+ if (NULL == fptr)
+ return -1;
+ position = -1;
+ fscanf(fptr, "pos: %llu", &position);
+ fclose(fptr);
+
+ return position;
+}
+
+
+/*
+ * Scan the given process and update the arrays with any new file
+ * descriptors.
+ *
+ * Returns 0 on success, 1 if the process no longer exists or could not be
+ * read, or 2 for a memory allocation error.
+ */
+int pv_watchpid_scanfds(pvstate_t state, pvstate_t pristine,
+ unsigned int watch_pid, int *array_length_ptr,
+ pvwatchfd_t * info_array_ptr,
+ pvstate_t * state_array_ptr, int *fd_to_idx)
+{
+ char fd_dir[512] = { 0, };
+ DIR *dptr;
+ struct dirent *d;
+ int array_length = 0;
+ struct pvwatchfd_s *info_array = NULL;
+ struct pvstate_s *state_array = NULL;
+
+ snprintf(fd_dir, sizeof(fd_dir) - 1, "/proc/%u/fd", watch_pid);
+ dptr = opendir(fd_dir);
+ if (NULL == dptr)
+ return 1;
+
+ array_length = *array_length_ptr;
+ info_array = *info_array_ptr;
+ state_array = *state_array_ptr;
+
+ while ((d = readdir(dptr)) != NULL) {
+ int fd, check_idx, use_idx, rc;
+ long long position_now;
+
+ fd = -1;
+ if (sscanf(d->d_name, "%d", &fd) != 1)
+ continue;
+ if ((fd < 0) || (fd >= FD_SETSIZE))
+ continue;
+
+ /*
+ * Skip if this fd is already known to us.
+ */
+ if (fd_to_idx[fd] != -1) {
+ continue;
+ }
+
+ /*
+ * See if there's an empty slot we can re-use. An empty slot
+ * has a watch_pid of 0.
+ */
+ use_idx = -1;
+ for (check_idx = 0; check_idx < array_length; check_idx++) {
+ if (info_array[check_idx].watch_pid == 0) {
+ use_idx = check_idx;
+ break;
+ }
+ }
+
+ /*
+ * If there's no empty slot, extend the arrays.
+ */
+ if (use_idx < 0) {
+ struct pvwatchfd_s *new_info_array;
+ struct pvstate_s *new_state_array;
+
+ array_length++;
+ use_idx = array_length - 1;
+
+ if (NULL == info_array) {
+ new_info_array =
+ malloc(array_length *
+ sizeof(*info_array));
+ } else {
+ new_info_array =
+ realloc(info_array,
+ array_length *
+ sizeof(*info_array));
+ }
+ if (NULL == new_info_array)
+ return 2;
+ info_array = new_info_array;
+ *info_array_ptr = info_array;
+ info_array[use_idx].watch_pid = 0;
+
+ if (NULL == state_array) {
+ new_state_array =
+ malloc(array_length *
+ sizeof(*state_array));
+ } else {
+ new_state_array =
+ realloc(state_array,
+ array_length *
+ sizeof(*state_array));
+ }
+ if (NULL == new_state_array)
+ return 2;
+ state_array = new_state_array;
+ *state_array_ptr = state_array;
+
+ *array_length_ptr = array_length;
+
+ for (check_idx = 0; check_idx < array_length;
+ check_idx++) {
+ state_array[check_idx].name =
+ info_array[check_idx].display_name;
+ state_array[check_idx].reparse_display = 1;
+ }
+ }
+
+ debug("%s: %d => index %d", "found new fd", fd, use_idx);
+
+ /*
+ * Initialise the details of this new entry.
+ */
+ memcpy(&(state_array[use_idx]), pristine,
+ sizeof(*pristine));
+ memset(&(info_array[use_idx]), 0,
+ sizeof(info_array[use_idx]));
+
+ info_array[use_idx].watch_pid = watch_pid;
+ info_array[use_idx].watch_fd = fd;
+ rc = pv_watchfd_info(state, &(info_array[use_idx]), 1);
+
+ /*
+ * Lookup failed - mark this slot as being free for re-use.
+ */
+ if ((rc != 0) && (rc != 4)) {
+ info_array[use_idx].watch_pid = 0;
+ debug("%s %d: %s: %d", "fd", fd,
+ "lookup failed - marking slot for re-use",
+ use_idx);
+ continue;
+ }
+
+ fd_to_idx[fd] = use_idx;
+
+ /*
+ * Not displayable - set fd to -1 so the main loop doesn't
+ * show it.
+ */
+ if (rc != 0) {
+ debug("%s %d: %s", "fd", fd,
+ "marking as not displayable");
+ info_array[use_idx].watch_fd = -1;
+ }
+
+ state_array[use_idx].size = info_array[use_idx].size;
+ if (state_array[use_idx].size < 1) {
+ char *fmt;
+ while (NULL !=
+ (fmt =
+ strstr(state_array[use_idx].default_format,
+ "%e"))) {
+ debug("%s", "zero size - removing ETA");
+ /* strlen-1 here to include trailing NUL */
+ memmove(fmt, fmt + 2, strlen(fmt) - 1);
+ state_array[use_idx].reparse_display = 1;
+ }
+ }
+
+ state_array[use_idx].name =
+ info_array[use_idx].display_name;
+
+ pv_watchpid_setname(state, &(info_array[use_idx]));
+
+ state_array[use_idx].reparse_display = 1;
+
+ gettimeofday(&(info_array[use_idx].start_time), NULL);
+
+ state_array[use_idx].initial_offset = 0;
+ info_array[use_idx].position = 0;
+
+ position_now = pv_watchfd_position(&(info_array[use_idx]));
+ if (position_now >= 0) {
+ state_array[use_idx].initial_offset = position_now;
+ info_array[use_idx].position = position_now;
+ }
+ }
+
+ closedir(dptr);
+
+ return 0;
+}
+
+
+/*
+ * Set the display name for the given watched file descriptor, truncating at
+ * the relevant places according to the current screen width.
+ */
+void pv_watchpid_setname(pvstate_t state, pvwatchfd_t info)
+{
+ int path_length, max_display_length;
+
+ memset(info->display_name, 0, sizeof(info->display_name));
+
+ path_length = strlen(info->file_fdpath);
+
+ max_display_length = (state->width / 2) - 6;
+ if (max_display_length >= path_length) {
+ snprintf(info->display_name,
+ sizeof(info->display_name) - 1, "%4d:%s",
+ info->watch_fd, info->file_fdpath);
+ } else {
+ int prefix_length, suffix_length;
+
+ prefix_length = max_display_length / 4;
+ suffix_length = max_display_length - prefix_length - 3;
+
+ snprintf(info->display_name,
+ sizeof(info->display_name) - 1, "%4d:%.*s...%.*s",
+ info->watch_fd, prefix_length, info->file_fdpath,
+ suffix_length,
+ info->file_fdpath + path_length - suffix_length);
+ }
+
+ debug("%s: %d: [%s]", "set name for fd", info->watch_fd,
+ info->display_name);
+}
+
+/* EOF */
diff --git a/tests/000-cat b/tests/000-cat
new file mode 100644
index 0000000..1b07549
--- /dev/null
+++ b/tests/000-cat
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# Check that data can be just passed straight through.
+
+VALUE=`echo TESTING | $PROG 2>/dev/null` || exit 1
+test "$VALUE" = "TESTING"
+
+# EOF
diff --git a/tests/001-interval b/tests/001-interval
new file mode 100644
index 0000000..2903cb8
--- /dev/null
+++ b/tests/001-interval
@@ -0,0 +1,12 @@
+#!/bin/sh
+#
+# Check that the update interval can be set.
+
+sleep 1 | $PROG -f -i 0.1 >/dev/null 2>$TMP1
+
+# There should be more than 6 lines of output.
+#
+NUM=`tr '\r' '\n' < $TMP1 | wc -l | tr -d ' '`
+test $NUM -gt 6
+
+# EOF
diff --git a/tests/002-rate b/tests/002-rate
new file mode 100644
index 0000000..2609ce4
--- /dev/null
+++ b/tests/002-rate
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+# A simple test of rate limiting.
+
+# Transfer 102 bytes at 100 bytes/sec. It should take at least 1 second.
+#
+START=`date +%S`
+dd if=/dev/zero bs=102 count=1 2>/dev/null | $PROG -L 100 2>/dev/null | cat >/dev/null
+END=`date +%S`
+
+test $START -ne $END
+
+# EOF
diff --git a/tests/003-progress b/tests/003-progress
new file mode 100644
index 0000000..d6438f7
--- /dev/null
+++ b/tests/003-progress
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+# Check that the progress bar moves when data is coming in.
+
+dd if=/dev/zero bs=100 count=1 2>/dev/null \
+| $PROG -f -p -i 0.1 -L 500 >/dev/null 2>$TMP1
+
+# There should be more than 2 different lines of output.
+#
+NUM=`tr '\r' '\n' < $TMP1 | sort | uniq -u | wc -l | tr -d ' '`
+test $NUM -gt 2
+
+# EOF
diff --git a/tests/004-timer b/tests/004-timer
new file mode 100644
index 0000000..ab1c134
--- /dev/null
+++ b/tests/004-timer
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# Check that the elapsed time counter does count up.
+
+# Transfer a zero amount of data, but take 3 seconds to do it.
+#
+(sleep 3 | $PROG -f -t >/dev/null) 2>&1 | tr '\r' '\n' > $TMP1
+
+# Count the number of different timer values; it should be >1.
+#
+NUM=`sort < $TMP1 | uniq -u | wc -l | tr -d ' '`
+test $NUM -gt 1
+
+# EOF
diff --git a/tests/005a-eta b/tests/005a-eta
new file mode 100644
index 0000000..0f0eb40
--- /dev/null
+++ b/tests/005a-eta
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Check that the estimated time counter counts.
+
+dd if=/dev/zero bs=100 count=1 2>/dev/null \
+| $PROG -f -e -s 100 -i 0.1 -L 25 >/dev/null 2>$TMP1
+
+# Count the number of different ETA values there have been.
+#
+NUM=`tr '\r' '\n' < $TMP1 | tr -d ' ' | sed '/^$/d' | sort | uniq | wc -l | tr -d ' '`
+
+# 3 or less - not OK, since it should have taken 4 seconds.
+#
+test $NUM -gt 3 || exit 1
+
+# 8 or more - not OK, since even on a heavily loaded system that's too long.
+#
+test $NUM -lt 8
+
+# EOF
diff --git a/tests/005b-fineta b/tests/005b-fineta
new file mode 100644
index 0000000..0e030b3
--- /dev/null
+++ b/tests/005b-fineta
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Check that the estimated time counter can show the end time of day.
+
+dd if=/dev/zero bs=100 count=1 2>/dev/null \
+| $PROG -f -I -s 100 -i 0.1 -L 25 >/dev/null 2>$TMP1
+
+# Count the number of different ETA values there have been.
+#
+NUM=`tr '\r' '\n' < $TMP1 | tr -d ' ' | sed '/^$/d' | sort | uniq | wc -l | tr -d ' '`
+
+# There should be at least 1 line of output.
+#
+test $NUM -gt 0 || exit 1
+
+# 8 or more different values - not OK.
+#
+test $NUM -lt 8
+
+# EOF
diff --git a/tests/006-ratecount b/tests/006-ratecount
new file mode 100644
index 0000000..1d4ea86
--- /dev/null
+++ b/tests/006-ratecount
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# Check that the transfer rate counter changes.
+
+# Transfer 200 bytes as two 100-byte blocks with a 2-second gap between.
+#
+(dd if=/dev/zero bs=100 count=1 2>/dev/null;
+ sleep 2;
+ dd if=/dev/zero bs=100 count=1 2>/dev/null;
+) | $PROG -f -i 0.5 -r >/dev/null 2>$TMP1
+
+# Count the number of different rates output.
+#
+NUM=`tr '\r' '\n' < $TMP1 | sort | uniq -u | wc -l | tr -d ' '`
+
+# There should be more than 2 different rates counted (around 100 bytes/sec
+# for the each block, 0 bytes/sec for the gap in the middle, and around 50
+# bytes/sec for the average time reported at the end).
+#
+test $NUM -gt 2
+
+# EOF
diff --git a/tests/007-bytes b/tests/007-bytes
new file mode 100644
index 0000000..ca0cfc6
--- /dev/null
+++ b/tests/007-bytes
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# Check that the byte counter counts.
+
+dd if=/dev/zero bs=100 count=1 2>/dev/null \
+| LANG=C $PROG -f -b >/dev/null 2>$TMP1
+NUM=`tr '\r' '\n' < $TMP1 | tr -d ' '`
+test "$NUM" = "100B"
+
+# EOF
diff --git a/tests/008-numeric b/tests/008-numeric
new file mode 100644
index 0000000..baa44f2
--- /dev/null
+++ b/tests/008-numeric
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# Check that numeric output outputs some percentages.
+
+# Process 100 bytes at 100 bytes per second, updating every 0.1 seconds for
+# around 10 output lines.
+#
+dd if=/dev/zero bs=100 count=1 2>/dev/null \
+| $PROG -s 100 -n -i 0.1 -L 100 >/dev/null 2>$TMP1
+
+# The number of output lines should be >8 and <13, and the final percentage
+# should be 100.
+#
+test `wc -l < $TMP1` -gt 8
+test `wc -l < $TMP1` -lt 13
+test `sed -n '$p' < $TMP1` -eq 100
+
+# EOF
diff --git a/tests/009-quiet b/tests/009-quiet
new file mode 100644
index 0000000..a75da36
--- /dev/null
+++ b/tests/009-quiet
@@ -0,0 +1,9 @@
+#!/bin/sh
+#
+# Check that the -q option shuts everything up.
+
+dd if=/dev/zero bs=1000 count=5 2>/dev/null \
+| $PROG -f -q -i 0.1 -L 5000 >/dev/null 2>$TMP1
+test ! -s $TMP1
+
+# EOF
diff --git a/tests/010-pipe b/tests/010-pipe
new file mode 100644
index 0000000..8e569dd
--- /dev/null
+++ b/tests/010-pipe
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Check that there is no SIGPIPE or dropped data on bigger data transfers.
+
+# We nead GNU head. On some platforms it is named ghead instead of head.
+HEAD=head
+for p in `echo $PATH | tr ':' '\n'`
+do
+ if test -x $p/ghead
+ then
+ HEAD=$p/ghead
+ break
+ fi
+done
+
+# Don't use dd. See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=324308
+COUNT1=100000000
+#COUNT2=`$PROG -B 100000 -q /dev/zero | $HEAD -c $COUNT1 | wc -c | tr -d ' '`
+# Remove \n to fix the test on AIX
+COUNT2=`$PROG -B 100000 -q /dev/zero | $HEAD -c $COUNT1 | tr -d '\n' | wc -c | tr -d ' '`
+
+#echo "[$COUNT1] [$COUNT2]"
+
+test "x$COUNT1" = "x$COUNT2"
+
+# EOF
diff --git a/tests/011-cksum b/tests/011-cksum
new file mode 100644
index 0000000..beb8443
--- /dev/null
+++ b/tests/011-cksum
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Transfer a large chunk of data through pv and check data correctness
+# afterwards.
+
+rm -f $TMP1 $TMP2 2>/dev/null
+
+# exit on non-zero return codes
+set -e
+
+# generate some data
+dd if=/dev/urandom of=$TMP1 bs=1024 count=10240 2>/dev/null
+
+CKSUM1=`cksum $TMP1 | awk '{print $1}'`
+
+# read through pv and test afterwards
+$PROG -B 100000 -q $TMP1 > $TMP2
+
+CKSUM2=`cksum $TMP2 | awk '{print $1}'`
+
+test "x$CKSUM1" = "x$CKSUM2"
+
+# clean up
+rm -f $TMP1 $TMP2 2>/dev/null
+
+# EOF
diff --git a/tests/012-averagerate b/tests/012-averagerate
new file mode 100644
index 0000000..89b77be
--- /dev/null
+++ b/tests/012-averagerate
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# Check that the average transfer rate counter changes, but not more than it
+# should.
+
+# Transfer 210 bytes as 100 bytes, a 1 second gap, 110 bytes, and another 1
+# second gap.
+#
+(dd if=/dev/zero bs=100 count=1 2>/dev/null;
+ sleep 1;
+ dd if=/dev/zero bs=110 count=1 2>/dev/null;
+ sleep 1;
+) | $PROG -f -i 0.5 -a >/dev/null 2>$TMP1
+
+# Count the number of rates output that are below 80.
+#
+NUM=`tr '\r' '\n' < $TMP1 | tr -dc '0-9.\n' | sed '/^$/d' | awk '$1<80{print}' | wc -l | tr -d ' '`
+
+# Nearly all of the output rates should be above 80 since the average rate
+# will always be around 100 bytes per second, except for pauses.
+#
+test $NUM -lt 2
+
+# EOF
diff --git a/tests/013-1mboundary b/tests/013-1mboundary
new file mode 100644
index 0000000..89e2523
--- /dev/null
+++ b/tests/013-1mboundary
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Check that the bytes count doesn't increase the line length as it passes
+# 1MiB, described here:
+# http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=586763
+
+# Transfer 1500kB of data in a bursty fashion.
+#
+(dd if=/dev/zero bs=1k count=999;
+ sleep 1;
+ dd if=/dev/zero bs=1k count=1;
+ sleep 1;
+ dd if=/dev/zero bs=1k count=500;
+ sleep 1;
+) 2>/dev/null | ($PROG -btef -s 1500k >/dev/null) 2>$TMP1
+
+# Count how many different line lengths we've seen.
+#
+NUM=`tr '\r' '\n' < $TMP1 | awk '{x=length($0);if(x>0)print length($0)}' | sort | uniq | wc -l`
+
+# There should only be one length (not counting 0).
+#
+test $NUM -eq 1 || { echo; tr '\r' '\n' < $TMP1; exit 1; }
+
+# EOF
diff --git a/tests/014-1mboundary2 b/tests/014-1mboundary2
new file mode 100644
index 0000000..00738cc
--- /dev/null
+++ b/tests/014-1mboundary2
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Same as test 13 (1mboundary) but for rate, not bytes transferred.
+
+# Transfer 1500kB of data in a bursty fashion.
+#
+(dd if=/dev/zero bs=1k count=999;
+ sleep 1;
+ dd if=/dev/zero bs=1k count=1;
+ sleep 1;
+ dd if=/dev/zero bs=3 count=1;
+ sleep 1;
+ dd if=/dev/zero bs=1k count=500;
+ sleep 1;
+) 2>/dev/null | ($PROG -rtef -s 1500k >/dev/null) 2>$TMP1
+
+# Count how many different line lengths we've seen.
+#
+NUM=`tr '\r' '\n' < $TMP1 | awk '{x=length($0);if(x>0)print length($0)}' | sort | uniq | wc -l`
+
+# There should only be one length (not counting 0).
+#
+test $NUM -eq 1 || { echo; tr '\r' '\n' < $TMP1; exit 1; }
+
+# EOF
diff --git a/tests/015-cksumpipe b/tests/015-cksumpipe
new file mode 100644
index 0000000..665d729
--- /dev/null
+++ b/tests/015-cksumpipe
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# Transfer a large chunk of data through pv using pipes, sending it in a
+# bursty fashion, and check data correctness afterwards.
+
+rm -f $TMP1 $TMP2 2>/dev/null
+
+# exit on non-zero return codes
+set -e
+
+# generate some data
+dd if=/dev/urandom of=$TMP1 bs=1024 count=10240 2>/dev/null
+
+CKSUM1=`cksum $TMP1 | awk '{print $1}'`
+
+# read through pv and test afterwards
+(
+dd if=$TMP1 bs=1 count=9000
+sleep 1
+dd if=$TMP1 bs=1 skip=9000 count=1240
+sleep 1
+dd if=$TMP1 bs=1024 skip=10 count=1014
+sleep 1
+dd if=$TMP1 bs=1024 skip=1024 count=1024
+sleep 1
+dd if=$TMP1 bs=1024 skip=2048
+) 2>/dev/null | $PROG -q -L 2M | cat > $TMP2
+
+CKSUM2=`cksum $TMP2 | awk '{print $1}'`
+
+test "x$CKSUM1" = "x$CKSUM2"
+
+# same again but with one less pipe
+(
+dd if=$TMP1 bs=1 count=9000
+sleep 1
+dd if=$TMP1 bs=1 skip=9000 count=1240
+sleep 1
+dd if=$TMP1 bs=1024 skip=10 count=1014
+sleep 1
+dd if=$TMP1 bs=1024 skip=1024 count=1024
+sleep 1
+dd if=$TMP1 bs=1024 skip=2048
+) 2>/dev/null | $PROG -q -L 2M > $TMP2
+
+CKSUM2=`cksum $TMP2 | awk '{print $1}'`
+
+test "x$CKSUM1" = "x$CKSUM2"
+
+# clean up
+rm -f $TMP1 $TMP2 2>/dev/null
+
+# EOF
diff --git a/tests/016-numeric-timer b/tests/016-numeric-timer
new file mode 100644
index 0000000..2637778
--- /dev/null
+++ b/tests/016-numeric-timer
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Check that numeric output gives a timer when used with -t.
+
+# Process 100 bytes at 100 bytes per second, updating every 0.1 seconds for
+# around 10 output lines.
+#
+dd if=/dev/zero bs=100 count=1 2>/dev/null \
+| $PROG -s 100 -n -t -i 0.1 -L 100 >/dev/null 2>$TMP1
+
+# The number of output lines should be >8 and <13, and the number of
+# different elapsed times should be at least 7. The last percentage should
+# be 100.
+#
+test `wc -l < $TMP1` -gt 8
+test `wc -l < $TMP1` -lt 13
+test `tr , . < "$TMP1" | awk '{print int(10*$1)}' | sort -n | uniq | wc -l` -gt 7
+test `sed -n '$p' < $TMP1 | awk '{print $2}'` -eq 100
+
+# EOF
diff --git a/tests/017-numeric-bytes b/tests/017-numeric-bytes
new file mode 100644
index 0000000..c0c32d1
--- /dev/null
+++ b/tests/017-numeric-bytes
@@ -0,0 +1,19 @@
+#!/bin/sh
+#
+# Check that numeric output gives a byte count instead of a percentage when
+# used with -b.
+
+# Process 500 bytes at 500 bytes per second, updating every 0.1 seconds for
+# around 10 output lines.
+#
+dd if=/dev/zero bs=500 count=1 2>/dev/null \
+| $PROG -s 500 -n -b -i 0.1 -L 500 >/dev/null 2>$TMP1
+
+# The number of output lines should be >8 and <13, and the final byte count
+# should be 500.
+#
+test `wc -l < $TMP1` -gt 8
+test `wc -l < $TMP1` -lt 13
+test `sed -n '$p' < $TMP1` -eq 500
+
+# EOF
diff --git a/tests/018-remote-format b/tests/018-remote-format
new file mode 100644
index 0000000..6944f0d
--- /dev/null
+++ b/tests/018-remote-format
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Try changing the format of a transfer remotely.
+
+# Do nothing if IPC is not supported.
+if ! $PROG -h | grep -Eq "^ -R,"; then
+ echo "SKIPPED" | tr "\n" ' '
+ exit 0
+fi
+
+rm -f $TMP1 $TMP2 $TMP3 $TMP4 2>/dev/null
+
+# Exit on non-zero return codes.
+set -e
+
+# Generate an empty test file.
+dd if=/dev/zero of=$TMP1 bs=1024 count=10240 2>/dev/null
+
+(
+sleep 1
+$PROG -R `cat $TMP4` -a
+sleep 2
+$PROG -R `cat $TMP4` -L 10M
+) &
+
+$PROG -L 2M -f -P $TMP4 $TMP1 > $TMP2 2>$TMP3
+
+# Make sure there is more than one length of line (excluding blank lines).
+line_lengths=`tr '\r' '\n' < "$TMP3" | awk '{print length($0)}' | grep -Fvx 0 | sort -n | uniq | wc -l`
+test $line_lengths -gt 1
+
+# EOF
diff --git a/tests/019-remote-cksum b/tests/019-remote-cksum
new file mode 100644
index 0000000..b8e1184
--- /dev/null
+++ b/tests/019-remote-cksum
@@ -0,0 +1,54 @@
+#!/bin/sh
+#
+# Try repeatedly messaging a transfer process, and make sure the data stays
+# intact.
+
+# Do nothing if IPC is not supported.
+if ! $PROG -h | grep -Eq "^ -R,"; then
+ echo "SKIPPED" | tr "\n" ' '
+ exit 0
+fi
+
+rm -f $TMP1 $TMP2 $TMP3 $TMP4 2>/dev/null
+
+# Exit on non-zero return codes.
+set -e
+
+# Generate some data.
+dd if=/dev/urandom of=$TMP1 bs=1024 count=10240 2>/dev/null
+
+# Run a few remote control commands in the background.
+#
+echo FAIL > $TMP3
+(
+set +e
+sleep 2
+for x in 1 2 3; do
+ $PROG -R `cat $TMP4` -apterb || exit 1
+ (usleep 200000 || sleep 1) 2>/dev/null
+ $PROG -R `cat $TMP4` -p || exit 1
+ (usleep 200000 || sleep 1) 2>/dev/null
+ $PROG -R `cat $TMP4` -N "test" || exit 1
+ (usleep 200000 || sleep 1) 2>/dev/null
+ $PROG -R `cat $TMP4` -F "%e" || exit 1
+ (usleep 200000 || sleep 1) 2>/dev/null
+ $PROG -R `cat $TMP4` -N "." || exit 1
+ (usleep 200000 || sleep 1) 2>/dev/null
+done
+$PROG -R `cat $TMP4` -L 10M
+echo OK > $TMP3
+) &
+
+# Run our data transfer.
+$PROG -L 100k -i 0.1 -f -P $TMP4 $TMP1 > $TMP2 2>/dev/null
+
+# Check our remote control calls ran OK.
+BGSTATUS=`cat $TMP3`
+test "x$BGSTATUS" = "xOK"
+
+# Check data integrity.
+CKSUM1=`cksum $TMP1 | awk '{print $1}'`
+CKSUM2=`cksum $TMP2 | awk '{print $1}'`
+test "x$CKSUM1" = "x$CKSUM2"
+
+# EOF
diff --git a/tests/020-stop-at-size b/tests/020-stop-at-size
new file mode 100644
index 0000000..95b90af
--- /dev/null
+++ b/tests/020-stop-at-size
@@ -0,0 +1,28 @@
+#!/bin/sh
+#
+# Make sure -S stops at the given size.
+
+rm -f $TMP1 $TMP2 2>/dev/null
+
+# exit on non-zero return codes
+set -e
+
+# generate some data
+dd if=/dev/urandom of=$TMP1 bs=1024 count=10 2>/dev/null
+
+# read through pv and test afterwards
+$PROG -S -s 5120 -q $TMP1 > $TMP2
+
+CKSUM2=`cksum $TMP2 | awk '{print $1}'`
+
+# take the first 5120 bytes of TMP1 and cksum them
+rm -f $TMP2
+dd if=$TMP1 of=$TMP2 bs=1024 count=5 2>/dev/null
+CKSUM1=`cksum $TMP2 | awk '{print $1}'`
+
+test "x$CKSUM1" = "x$CKSUM2"
+
+# clean up
+rm -f $TMP1 $TMP2 2>/dev/null
+
+# EOF