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 '

Source Code Index

' +echo '

' + +echo '

File Listing

' +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 '' + echo '' + echo '
' + echo ''"$FILE"' - '`html_safe "$FILESHORTDESC"`'

' + 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 '

Function Listing

' +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 '

To Do Listing

' +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