summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/include/library/getopt.h38
-rw-r--r--src/include/library/gettext.h53
-rw-r--r--src/include/options.h56
-rw-r--r--src/include/pv.h52
-rw-r--r--src/library/getopt.c115
-rw-r--r--src/library/gettext.c111
-rw-r--r--src/main/help.c179
-rw-r--r--src/main/main.c164
-rw-r--r--src/main/options.c272
-rw-r--r--src/main/remote.c208
-rw-r--r--src/main/version.c37
-rw-r--r--src/nls/de.po282
-rw-r--r--src/nls/fr.po283
-rw-r--r--src/nls/pl.po320
-rw-r--r--src/nls/pt.po276
-rw-r--r--src/nls/pv.pot271
-rw-r--r--src/pv/cursor.c515
-rw-r--r--src/pv/display.c635
-rw-r--r--src/pv/file.c250
-rw-r--r--src/pv/loop.c287
-rw-r--r--src/pv/number.c206
-rw-r--r--src/pv/signal.c290
-rw-r--r--src/pv/transfer.c341
23 files changed, 5241 insertions, 0 deletions
diff --git a/src/include/library/getopt.h b/src/include/library/getopt.h
new file mode 100644
index 0000000..162c83a
--- /dev/null
+++ b/src/include/library/getopt.h
@@ -0,0 +1,38 @@
+/*
+ * Replacement getopt function's header file. Include this AFTER config.h.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#ifndef _LIBRARY_GETOPT_H
+#define _LIBRARY_GETOPT_H 1
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#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..67e6d1e
--- /dev/null
+++ b/src/include/library/gettext.h
@@ -0,0 +1,53 @@
+/*
+ * 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
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#ifndef _LIBRARY_GETTEXT_H
+#define _LIBRARY_GETTEXT_H 1
+
+#ifdef HAVE_GETTEXT
+# ifdef HAVE_LIBINTL_H
+# include <libintl.h>
+# endif
+# ifdef HAVE_LOCALE_H
+# include <locale.h>
+# 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..549aed0
--- /dev/null
+++ b/src/include/options.h
@@ -0,0 +1,56 @@
+/*
+ * Global program option structure and the parsing function prototype.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#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 rate; /* rate counter flag */
+ unsigned char average_rate; /* average rate counter flag */
+ unsigned char bytes; /* bytes transferred flag */
+ 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 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 */
+ double interval; /* interval between updates */
+ unsigned int width; /* screen width */
+ unsigned int height; /* screen height */
+ char *name; /* process name, if any */
+ int argc; /* number of non-option arguments */
+ char **argv; /* array of non-option arguments */
+ char *current_file; /* current file being read */
+ unsigned char exit_status; /* exit status to give (0=OK) */
+};
+
+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.h b/src/include/pv.h
new file mode 100644
index 0000000..a79586a
--- /dev/null
+++ b/src/include/pv.h
@@ -0,0 +1,52 @@
+/*
+ * Functions used across the program.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#ifndef _PV_H
+#define _PV_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _OPTIONS_H
+struct opts_s;
+typedef struct opts_s *opts_t;
+#endif
+
+double pv_getnum_d(char *);
+int pv_getnum_i(char *);
+long long pv_getnum_ll(char *);
+int pv_getnum_check(char *, int);
+
+void pv_screensize(opts_t);
+void pv_calc_total_size(opts_t);
+
+int pv_main_loop(opts_t);
+void pv_display(opts_t, long double, long long, long long);
+long pv_transfer(opts_t, int, int *, int *, unsigned long long, long *);
+void pv_set_buffer_size(unsigned long long, int);
+int pv_next_file(opts_t, int, int);
+
+void pv_crs_fini(opts_t);
+void pv_crs_init(opts_t);
+void pv_crs_update(opts_t, char *);
+#ifdef HAVE_IPC
+void pv_crs_needreinit(void);
+#endif
+
+void pv_sig_allowpause(void);
+void pv_sig_checkbg(void);
+void pv_sig_init(void);
+void pv_sig_nopause(void);
+
+
+#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..fb8410e
--- /dev/null
+++ b/src/library/getopt.c
@@ -0,0 +1,115 @@
+/*
+ * Small reimplementation of getopt().
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 ((minioptind == 0) && (argc > 0))
+ minioptind++;
+
+ if ((nextchar > 0) && (argv[minioptind][nextchar] == 0)) {
+ 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 (argv[minioptind][1] == 0)
+ return -1;
+
+ /*
+ * End of options if arg is "--", but don't include the "--" in the
+ * non-option arguments
+ */
+ if ((argv[minioptind][1] == '-') && (argv[minioptind][2] == 0)) {
+ minioptind++;
+ return -1;
+ }
+
+ if (nextchar == 0)
+ nextchar = 1;
+
+ optchar = argv[minioptind][nextchar++];
+
+ for (i = 0; optstring[i] != 0 && optstring[i] != optchar; i++) {
+ }
+
+ if (optstring[i] == 0) {
+ 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..b964fde
--- /dev/null
+++ b/src/library/gettext.c
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 (msgid == NULL)
+ return msgid;
+
+ if (tried_lang == 0) {
+ lang = getenv("LANGUAGE"); /* RATS: ignore */
+ if (lang)
+ table = minigettext__gettable(lang);
+
+ if (table == NULL) {
+ lang = getenv("LANG"); /* RATS: ignore */
+ if (lang)
+ table = minigettext__gettable(lang);
+ }
+
+ if (table == NULL) {
+ lang = getenv("LC_ALL"); /* RATS: ignore */
+ if (lang)
+ table = minigettext__gettable(lang);
+ }
+
+ if (table == NULL) {
+ lang = getenv("LC_MESSAGES"); /* RATS: ignore */
+ if (lang)
+ table = minigettext__gettable(lang);
+ }
+
+ tried_lang = 1;
+ }
+
+ if (table == NULL)
+ return msgid;
+
+ for (i = 0; table[i].msgid; i++) {
+ if (strcmp(table[i].msgid, msgid) == 0) {
+ if (table[i].msgstr == 0)
+ return msgid;
+ if (table[i].msgstr[0] == 0)
+ return msgid;
+ return table[i].msgstr;
+ }
+ }
+
+ return msgid;
+}
+
+#endif /* HAVE_GETTEXT */
+
+/* EOF */
diff --git a/src/main/help.c b/src/main/help.c
new file mode 100644
index 0000000..2d1b927
--- /dev/null
+++ b/src/main/help.c
@@ -0,0 +1,179 @@
+/*
+ * Output command-line help to stdout.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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)")},
+ {"-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")},
+ {"-f", "--force", 0,
+ N_("output even if standard error is not a terminal")},
+ {"-n", "--numeric", 0,
+ N_("output percentages, not visual information")},
+ {"-q", "--quiet", 0,
+ N_("do not output any transfer information at all")},
+ {"-c", "--cursor", 0,
+ N_("use cursor positioning escape sequences")},
+ {"-W", "--wait", 0,
+ N_("display nothing until first byte transferred")},
+ {"-s", "--size", N_("SIZE"),
+ N_("set estimated data size to SIZE bytes")},
+ {"-l", "--line-mode", 0,
+ N_("count lines instead of bytes")},
+ {"-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")},
+ {"", 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")},
+ {"-R", "--remote", N_("PID"),
+ N_("update settings of 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]..."), /* RATS: ignore */
+ 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); /* RATS: ignore */
+#ifdef HAVE_GETOPT_LONG
+ if (optlist[i].optlong)
+ width += 2 + strlen(optlist[i].optlong); /* RATS: ignore */
+#endif
+ param = optlist[i].param;
+ if (param)
+ param = _(param);
+ if (param)
+ width += 1 + strlen(param); /* RATS: ignore */
+
+ if (width > col1max)
+ col1max = width;
+ }
+
+ col1max++;
+
+ optbuf = malloc(col1max + 16);
+ if (optbuf == NULL) {
+ 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 (optlist[i].optshort[0] == 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", /* RATS: ignore (checked) */
+ 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 (description == NULL) {
+ printf("\n");
+ continue;
+ }
+
+ start = description;
+
+ while (strlen(start) /* RATS: ignore */ >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);
+ }
+
+ printf("\n");
+ printf(_("Please report any bugs to %s."), /* RATS: ignore */
+ BUG_REPORTS_TO);
+ printf("\n");
+}
+
+/* EOF */
diff --git a/src/main/main.c b/src/main/main.c
new file mode 100644
index 0000000..04bb816
--- /dev/null
+++ b/src/main/main.c
@@ -0,0 +1,164 @@
+/*
+ * Main program entry point - read the command line options, then perform
+ * the appropriate actions.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "options.h"
+#include "pv.h"
+
+/* #undef MAKE_STDOUT_NONBLOCKING */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+
+int remote_set(opts_t opts);
+void remote_sig_init(opts_t opts);
+
+
+/*
+ * 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;
+ int retcode = 0;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+#endif
+
+ opts = opts_parse(argc, argv);
+ if (!opts)
+ return 1;
+ if (opts->do_nothing) {
+ opts_free(opts);
+ return 0;
+ }
+
+ if (opts->remote > 0) {
+ 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;
+ retcode = remote_set(opts);
+ opts_free(opts);
+ return retcode;
+ }
+
+ /*
+ * If no files were given, pretend "-" was given (stdin).
+ */
+ if (opts->argc == 0) {
+ opts->argv[opts->argc++] = "-";
+ }
+
+ if (opts->size == 0) {
+ pv_calc_total_size(opts);
+ }
+
+ if (opts->size < 1)
+ opts->eta = 0;
+
+ if ((isatty(STDERR_FILENO) == 0)
+ && (opts->force == 0)
+ && (opts->numeric == 0)) {
+ opts->no_op = 1;
+ }
+
+ if (opts->width == 0) {
+ int tmpheight;
+ tmpheight = opts->height;
+ pv_screensize(opts);
+ if (tmpheight > 0)
+ opts->height = tmpheight;
+ }
+
+ if (opts->height == 0) {
+ int tmpwidth;
+ tmpwidth = opts->width;
+ pv_screensize(opts);
+ if (tmpwidth > 0)
+ opts->width = tmpwidth;
+ }
+
+ /*
+ * 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;
+
+#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.
+ */
+ tcgetattr(STDERR_FILENO, &t);
+ t_save = t;
+ t.c_lflag |= TOSTOP;
+ tcsetattr(STDERR_FILENO, TCSANOW, &t);
+
+ opts->current_file = "(stdin)";
+
+ pv_sig_init();
+ remote_sig_init(opts);
+
+ retcode = pv_main_loop(opts);
+
+ opts_free(opts);
+
+ tcsetattr(STDERR_FILENO, TCSANOW, &t_save);
+
+ return retcode;
+}
+
+/* EOF */
diff --git a/src/main/options.c b/src/main/options.c
new file mode 100644
index 0000000..3b180cf
--- /dev/null
+++ b/src/main/options.c
@@ -0,0 +1,272 @@
+/*
+ * Parse command-line options.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "options.h"
+#include "library/getopt.h"
+#include "pv.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+
+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'},
+ {"rate", 0, 0, 'r'},
+ {"average-rate", 0, 0, 'a'},
+ {"bytes", 0, 0, 'b'},
+ {"force", 0, 0, 'f'},
+ {"numeric", 0, 0, 'n'},
+ {"quiet", 0, 0, 'q'},
+ {"cursor", 0, 0, 'c'},
+ {"wait", 0, 0, 'W'},
+ {"size", 1, 0, 's'},
+ {"line-mode", 0, 0, 'l'},
+ {"interval", 1, 0, 'i'},
+ {"width", 1, 0, 'w'},
+ {"height", 1, 0, 'H'},
+ {"name", 1, 0, 'N'},
+ {"rate-limit", 1, 0, 'L'},
+ {"buffer-size", 1, 0, 'B'},
+ {"remote", 1, 0, 'R'},
+ {0, 0, 0, 0}
+ };
+ int option_index = 0;
+#endif
+ char *short_options = "hVpterabfnqcWs:li:w:H:N:L:B:R:";
+ int c, numopts;
+ opts_t opts;
+
+ opts = calloc(1, sizeof(*opts));
+ if (!opts) {
+ fprintf(stderr, /* RATS: ignore */
+ _("%s: option structure allocation failed (%s)"),
+ argv[0], strerror(errno));
+ fprintf(stderr, "\n");
+ return 0;
+ }
+
+ opts->program_name = argv[0];
+
+ opts->argc = 0;
+ opts->argv = calloc(argc + 1, sizeof(char *));
+ if (!opts->argv) {
+ fprintf(stderr, /* RATS: ignore */
+ _
+ ("%s: option structure argv allocation failed (%s)"),
+ argv[0], strerror(errno));
+ fprintf(stderr, "\n");
+ opts_free(opts);
+ return 0;
+ }
+
+ numopts = 0;
+
+ opts->interval = 1;
+
+ do {
+#ifdef HAVE_GETOPT_LONG
+ c = getopt_long(argc, argv, /* RATS: ignore */
+ short_options, long_options,
+ &option_index);
+#else
+ c = getopt(argc, argv, short_options); /* RATS: ignore */
+#endif
+ if (c < 0)
+ continue;
+
+ /*
+ * Check that any numeric arguments are of the right type.
+ */
+ switch (c) {
+ case 's':
+ case 'w':
+ case 'H':
+ case 'L':
+ case 'B':
+ case 'R':
+ if (pv_getnum_check(optarg, 0)) {
+ fprintf(stderr, "%s: -%c: %s\n", argv[0],
+ c, _("integer argument expected"));
+ opts_free(opts);
+ return 0;
+ }
+ break;
+ case 'i':
+ if (pv_getnum_check(optarg, 1)) {
+ fprintf(stderr, "%s: -%c: %s\n", argv[0],
+ c, _("numeric argument expected"));
+ 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 '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 '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 's':
+ opts->size = pv_getnum_ll(optarg);
+ break;
+ case 'l':
+ 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 'R':
+ opts->remote = pv_getnum_i(optarg);
+ break;
+ default:
+#ifdef HAVE_GETOPT_LONG
+ fprintf(stderr, /* RATS: ignore (OK) */
+ _("Try `%s --help' for more information."),
+ argv[0]);
+#else
+ fprintf(stderr, /* RATS: ignore (OK) */
+ _("Try `%s -h' for more information."),
+ argv[0]);
+#endif
+ fprintf(stderr, "\n");
+ opts_free(opts);
+ return 0;
+ break;
+ }
+
+ } while (c != -1);
+
+ /*
+ * Default options: -pterb
+ */
+ if (numopts == 0) {
+ 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++];
+ }
+
+ opts->exit_status = 0;
+
+ return opts;
+}
+
+/* EOF */
diff --git a/src/main/remote.c b/src/main/remote.c
new file mode 100644
index 0000000..4be342d
--- /dev/null
+++ b/src/main/remote.c
@@ -0,0 +1,208 @@
+/*
+ * Remote-control functions.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "options.h"
+#include "pv.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/msg.h>
+
+
+struct remote_msg {
+ long mtype;
+ unsigned char progress; /* progress bar flag */
+ unsigned char timer; /* timer flag */
+ unsigned char eta; /* ETA flag */
+ unsigned char rate; /* rate counter flag */
+ unsigned char average_rate; /* average rate counter flag */
+ unsigned char bytes; /* bytes transferred flag */
+ 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]; /* RATS: ignore */
+};
+
+
+static opts_t remote__opts = NULL;
+
+
+/*
+ * 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(opts_t opts)
+{
+ 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(opts_t opts)
+{
+ return msgget(remote__genkey(opts), 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 sending a SIGUSR1
+ * so the process knows it has a message to read.
+ *
+ * Returns nonzero on error.
+ */
+int remote_set(opts_t opts)
+{
+ struct remote_msg msgbuf;
+ int msgid;
+
+ memset(&msgbuf, 0, sizeof(msgbuf));
+ msgbuf.mtype = opts->remote;
+ msgbuf.progress = opts->progress;
+ msgbuf.timer = opts->timer;
+ msgbuf.eta = opts->eta;
+ msgbuf.rate = opts->rate;
+ msgbuf.average_rate = opts->average_rate;
+ 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);
+ }
+
+ msgid = remote__msgget(opts);
+ if (msgid < 0) {
+ fprintf(stderr, "%s: %s\n", opts->program_name,
+ strerror(errno));
+ return 1;
+ }
+
+ if (msgsnd(msgid, &msgbuf, sizeof(msgbuf) - sizeof(long), 0) != 0) {
+ fprintf(stderr, "%s: %s\n", opts->program_name,
+ strerror(errno));
+ return 1;
+ }
+
+ if (kill(opts->remote, SIGUSR1) != 0) {
+ fprintf(stderr, "%s: %s\n", opts->program_name,
+ strerror(errno));
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Handle SIGUSR1 by replacing the current process's options with those
+ * being passed in via IPC message. The message queue is deleted afterwards.
+ */
+static void remote__sig_usr1(int s)
+{
+ struct remote_msg msgbuf;
+ struct msqid_ds qbuf;
+ ssize_t got;
+ int msgid;
+
+ memset(&msgbuf, 0, sizeof(msgbuf));
+
+ msgid = remote__msgget(remote__opts);
+ if (msgid < 0) {
+ return;
+ }
+
+ got =
+ msgrcv(msgid, &msgbuf, sizeof(msgbuf) - sizeof(long), getpid(),
+ IPC_NOWAIT);
+ if (got < 0) {
+ msgctl(msgid, IPC_RMID, &qbuf);
+ return;
+ }
+
+ if (msgctl(msgid, IPC_RMID, &qbuf) == 0) {
+ if (qbuf.msg_qnum < 1) {
+ msgctl(msgid, IPC_RMID, &qbuf);
+ }
+ }
+
+ if ((got < 1) || (remote__opts == NULL)) {
+ return;
+ }
+
+
+ remote__opts->progress = msgbuf.progress;
+ remote__opts->timer = msgbuf.timer;
+ remote__opts->eta = msgbuf.eta;
+ remote__opts->rate = msgbuf.rate;
+ remote__opts->average_rate = msgbuf.average_rate;
+
+ if (msgbuf.rate_limit > 0)
+ remote__opts->rate_limit = msgbuf.rate_limit;
+ if (msgbuf.buffer_size > 0) {
+ remote__opts->buffer_size = msgbuf.buffer_size;
+ pv_set_buffer_size(msgbuf.buffer_size, 1);
+ }
+ if (msgbuf.size > 0)
+ remote__opts->size = msgbuf.size;
+ if (msgbuf.interval > 0)
+ remote__opts->interval = msgbuf.interval;
+ if (msgbuf.width > 0)
+ remote__opts->width = msgbuf.width;
+ if (msgbuf.height > 0)
+ remote__opts->height = msgbuf.height;
+ if (msgbuf.name[0] != 0)
+ remote__opts->name = strdup(msgbuf.name);
+}
+
+
+/*
+ * Initialise handling of SIGUSR1 so that remote control messages can be
+ * processed correctly.
+ */
+void remote_sig_init(opts_t opts)
+{
+ struct sigaction sa;
+
+ remote__opts = opts;
+
+ sa.sa_handler = remote__sig_usr1;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGUSR1, &sa, NULL);
+}
+
+/* EOF */
diff --git a/src/main/version.c b/src/main/version.c
new file mode 100644
index 0000000..77d0943
--- /dev/null
+++ b/src/main/version.c
@@ -0,0 +1,37 @@
+/*
+ * Output version information to stdout.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+
+
+/*
+ * Display current package version.
+ */
+void display_version(void)
+{
+ printf(_("%s %s - Copyright(C) %s %s"), /* RATS: ignore */
+ PROGRAM_NAME, VERSION, COPYRIGHT_YEAR, COPYRIGHT_HOLDER);
+ printf("\n\n");
+ printf(_("Web site: %s"), /* RATS: ignore */
+ 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..9de208d
--- /dev/null
+++ b/src/nls/de.po
@@ -0,0 +1,282 @@
+msgid ""
+msgstr ""
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-06-26 10:37+0100\n"
+"Content-Type: text/plain; charset=iso-8859-15\n"
+"Date: 1999-06-01 15:18:29+0100\n"
+"From: Andrew Wood <ivarch@ubik>\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/file.c:178
+msgid "failed to close file"
+msgstr "Datei konnte nicht geschlossen werden"
+
+#: src/pv/file.c:202
+msgid "failed to read file"
+msgstr "Datei konnte nicht gelesen werden"
+
+#: src/pv/file.c:212
+msgid "failed to stat file"
+msgstr "Dateiinformationen konnten nicht gelesen werden"
+
+#: src/pv/file.c:222
+msgid "failed to stat output file"
+msgstr "Dateiinformationen für Ausgabe-Datei konnten nicht gelesen werden"
+
+#: src/pv/file.c:244
+msgid "input file is output file"
+msgstr "Eingabe-Datei ist Ausgabe-Datei"
+
+#: src/pv/display.c:100
+msgid "yzafpnum kMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:350 src/pv/transfer.c:94
+msgid "buffer allocation failed"
+msgstr "Puffer konnte nicht allokiert werden"
+
+#: src/pv/display.c:404
+msgid "B"
+msgstr "B"
+
+#: src/pv/display.c:432 src/pv/display.c:441
+msgid "/s"
+msgstr ""
+
+#: src/pv/display.c:432 src/pv/display.c:441
+msgid "B/s"
+msgstr ""
+
+#: src/pv/display.c:463
+msgid "ETA"
+msgstr "ETA"
+
+#: src/pv/cursor.c:233 src/pv/cursor.c:249 src/pv/cursor.c:310
+msgid "failed to open terminal"
+msgstr "Terminal konnte nicht geöffnet werden"
+
+#: src/pv/cursor.c:241
+msgid "failed to lock terminal"
+msgstr "Terminal konnte nicht geöffnet werden"
+
+#: src/pv/transfer.c:156
+msgid "select call failed"
+msgstr "select-Aufruf fehlgeschlagen"
+
+#: src/pv/transfer.c:204
+msgid "read failed"
+msgstr "read-Aufruf fehlgeschlagen"
+
+#: src/pv/transfer.c:285
+msgid "write failed"
+msgstr "write-Aufruf fehlgeschlagen"
+
+#: src/main/help.c:33
+msgid "show progress bar"
+msgstr "Fortschritts-Anzeige"
+
+#: src/main/help.c:35
+msgid "show elapsed time"
+msgstr "zeige die verstrichene Zeit an"
+
+#: src/main/help.c:37
+msgid "show estimated time of arrival (completion)"
+msgstr "zeige die erwartete Zeit bis zum Ende an"
+
+#: src/main/help.c:39
+msgid "show data transfer rate counter"
+msgstr "zeige die Datentransferrate an"
+
+#: src/main/help.c:41
+msgid "show data transfer average rate counter"
+msgstr ""
+
+#: src/main/help.c:43
+msgid "show number of bytes transferred"
+msgstr "zeige die Anzahl von Bytes, die transferiert worden sind"
+
+#: src/main/help.c:45
+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:47
+msgid "output percentages, not visual information"
+msgstr "Ausgabe von Prozent-Angaben statt visueller Darstellung"
+
+#: src/main/help.c:49
+msgid "do not output any transfer information at all"
+msgstr "sämtliche Transferinformationen unterdrücken"
+
+#: src/main/help.c:51
+msgid "use cursor positioning escape sequences"
+msgstr "benutze Escape-Sequenzen zur Cursor-Positionierung"
+
+#: src/main/help.c:53
+msgid "display nothing until first byte transferred"
+msgstr "keine Ausgabe bevor das erste Byte übertragen wurde"
+
+#: src/main/help.c:54
+msgid "SIZE"
+msgstr ""
+
+#: src/main/help.c:55
+msgid "set estimated data size to SIZE bytes"
+msgstr "setze erwartete Daten-Länge auf SIZE Byte"
+
+#: src/main/help.c:57
+msgid "count lines instead of bytes"
+msgstr ""
+
+#: src/main/help.c:58
+msgid "SEC"
+msgstr ""
+
+#: src/main/help.c:59
+msgid "update every SEC seconds"
+msgstr "aktualisiere Ausgabe nach SEC Sekunden Intervall"
+
+#: src/main/help.c:60
+msgid "WIDTH"
+msgstr ""
+
+#: src/main/help.c:61
+msgid "assume terminal is WIDTH characters wide"
+msgstr "setze Terminal-Breite auf WIDTH Zeichen"
+
+#: src/main/help.c:62
+msgid "HEIGHT"
+msgstr ""
+
+#: src/main/help.c:63
+msgid "assume terminal is HEIGHT rows high"
+msgstr "setze Terminal-Breite auf WIDTH Zeichen"
+
+#: src/main/help.c:64
+msgid "NAME"
+msgstr ""
+
+#: src/main/help.c:65
+msgid "prefix visual information with NAME"
+msgstr "setze den NAMEn für visuelle Darstellung"
+
+#: src/main/help.c:67
+msgid "RATE"
+msgstr ""
+
+#: src/main/help.c:68
+msgid "limit transfer to RATE bytes per second"
+msgstr "beschränke die Transferrate auf RATE Byte pro Sekunde"
+
+#: src/main/help.c:69
+msgid "BYTES"
+msgstr ""
+
+#: src/main/help.c:70
+msgid "use a buffer size of BYTES"
+msgstr ""
+
+#: src/main/help.c:71
+msgid "PID"
+msgstr ""
+
+#: src/main/help.c:72
+msgid "update settings of process PID"
+msgstr ""
+
+#: src/main/help.c:75
+msgid "show this help and exit"
+msgstr "zeige diese Hilfe und beende"
+
+#: src/main/help.c:77
+msgid "show version information and exit"
+msgstr "zeige Versionsinformationen und beende"
+
+#: src/main/help.c:83
+#, c-format
+msgid "Usage: %s [OPTION] [FILE]..."
+msgstr "Syntax: %s [OPTION] [DATEI]..."
+
+#: src/main/help.c:87
+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:174
+#, c-format
+msgid "Please report any bugs to %s."
+msgstr "Bitte senden Sie Fehlerberichte an %s."
+
+#. RATS: ignore
+#: src/main/options.c:85
+#, c-format
+msgid "%s: option structure allocation failed (%s)"
+msgstr "%s: `option' konnte nicht allokiert werden (%s)"
+
+#: src/main/options.c:98
+#, c-format
+msgid "%s: option structure argv allocation failed (%s)"
+msgstr "%s: `option' (argv) konnte nicht allokiert werden (%s)"
+
+#: src/main/options.c:132
+msgid "integer argument expected"
+msgstr ""
+
+#: src/main/options.c:140
+msgid "numeric argument expected"
+msgstr ""
+
+#. RATS: ignore (OK)
+#: src/main/options.c:234
+#, c-format
+msgid "Try `%s --help' for more information."
+msgstr "`%s --help' zeigt weitere Informationen an."
+
+#. RATS: ignore (OK)
+#: src/main/options.c:238
+#, fuzzy, c-format
+msgid "Try `%s -h' for more information."
+msgstr "`%s -h' zeigt weitere Informationen an."
+
+#: src/main/version.c:19
+#, c-format
+msgid "%s %s - Copyright(C) %s %s"
+msgstr "%s %s - Copyright(C) %s %s"
+
+#: src/main/version.c:22
+#, c-format
+msgid "Web site: %s"
+msgstr "Web-Site: %s"
+
+#: src/main/version.c:26
+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:31
+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."
diff --git a/src/nls/fr.po b/src/nls/fr.po
new file mode 100644
index 0000000..8b16fa6
--- /dev/null
+++ b/src/nls/fr.po
@@ -0,0 +1,283 @@
+msgid ""
+msgstr ""
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-06-26 10:37+0100\n"
+"Content-Type: text/plain; charset=ISO-8859-15\n"
+"Date: 1999-06-01 15:18:29+0100\n"
+"From: Andrew Wood <ivarch@ubik>\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/file.c:178
+msgid "failed to close file"
+msgstr "la fermeture du fichier a échoué"
+
+#: src/pv/file.c:202
+msgid "failed to read file"
+msgstr "la lecture du fichier a échoué"
+
+#: src/pv/file.c:212
+msgid "failed to stat file"
+msgstr "échec à statuer sur le fichier"
+
+#: src/pv/file.c:222
+msgid "failed to stat output file"
+msgstr "échec à statuer sur le fichier de sortie"
+
+#: src/pv/file.c:244
+msgid "input file is output file"
+msgstr "fichiers d'entré et de sortie sont les mêmes"
+
+#: src/pv/display.c:100
+msgid "yzafpnum kMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:350 src/pv/transfer.c:94
+msgid "buffer allocation failed"
+msgstr "l'allocation de mémoire tampon a échoué"
+
+#: src/pv/display.c:404
+msgid "B"
+msgstr "O"
+
+#: src/pv/display.c:432 src/pv/display.c:441
+msgid "/s"
+msgstr "/s"
+
+#: src/pv/display.c:432 src/pv/display.c:441
+#, fuzzy
+msgid "B/s"
+msgstr "O/s"
+
+#: src/pv/display.c:463
+msgid "ETA"
+msgstr "ETA"
+
+#: src/pv/cursor.c:233 src/pv/cursor.c:249 src/pv/cursor.c:310
+msgid "failed to open terminal"
+msgstr "l'ouverture du terminal a échoué"
+
+#: src/pv/cursor.c:241
+msgid "failed to lock terminal"
+msgstr "l'ouverture du terminal a échoué"
+
+#: src/pv/transfer.c:156
+msgid "select call failed"
+msgstr "appel de sélection a échoué"
+
+#: src/pv/transfer.c:204
+msgid "read failed"
+msgstr "la lecture a échoué"
+
+#: src/pv/transfer.c:285
+msgid "write failed"
+msgstr "l'écriture a échoué"
+
+#: src/main/help.c:33
+msgid "show progress bar"
+msgstr "affiche la barre de progression"
+
+#: src/main/help.c:35
+msgid "show elapsed time"
+msgstr "affiche le temps écoulé"
+
+#: src/main/help.c:37
+msgid "show estimated time of arrival (completion)"
+msgstr "affiche l'heure approximative de l'achèvement de la tâche"
+
+#: src/main/help.c:39
+msgid "show data transfer rate counter"
+msgstr "affiche le taux de tranfert des données"
+
+#: src/main/help.c:41
+msgid "show data transfer average rate counter"
+msgstr ""
+
+#: src/main/help.c:43
+msgid "show number of bytes transferred"
+msgstr "affiche le nombre d'octets transférés"
+
+#: src/main/help.c:45
+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:47
+msgid "output percentages, not visual information"
+msgstr "imprime en pourcentage, pas les informations visuelles"
+
+#: src/main/help.c:49
+msgid "do not output any transfer information at all"
+msgstr "n'afficher aucune information de transfert"
+
+#: src/main/help.c:51
+msgid "use cursor positioning escape sequences"
+msgstr "utiliser les séquences d'échappements de positionnement de curseur"
+
+#: src/main/help.c:53
+msgid "display nothing until first byte transferred"
+msgstr "ne rien afficher avant qu'au moins un octet soit tranféré"
+
+#: src/main/help.c:54
+msgid "SIZE"
+msgstr "TAILLE"
+
+#: src/main/help.c:55
+msgid "set estimated data size to SIZE bytes"
+msgstr "ajuste la taille estimée des données à TAILLE octets"
+
+#: src/main/help.c:57
+msgid "count lines instead of bytes"
+msgstr "compte les lignes au lieu des octets"
+
+#: src/main/help.c:58
+msgid "SEC"
+msgstr "SEC"
+
+#: src/main/help.c:59
+msgid "update every SEC seconds"
+msgstr "mise-à-jour toute les SEC secondes"
+
+#: src/main/help.c:60
+msgid "WIDTH"
+msgstr "LARGEUR"
+
+#: src/main/help.c:61
+msgid "assume terminal is WIDTH characters wide"
+msgstr "présumer la largeur du terminal à LARGEUR caractères"
+
+#: src/main/help.c:62
+msgid "HEIGHT"
+msgstr "HAUTEUR"
+
+#: src/main/help.c:63
+msgid "assume terminal is HEIGHT rows high"
+msgstr "présumer la hauteur du terminal à HAUTEUR lignes"
+
+#: src/main/help.c:64
+msgid "NAME"
+msgstr "NOM"
+
+#: src/main/help.c:65
+msgid "prefix visual information with NAME"
+msgstr "préfixer les informations visuelles avec NOM"
+
+#: src/main/help.c:67
+msgid "RATE"
+msgstr "TAUX"
+
+#: src/main/help.c:68
+msgid "limit transfer to RATE bytes per second"
+msgstr "limite le taux de transfer à TAUX octets par seconde"
+
+#: src/main/help.c:69
+msgid "BYTES"
+msgstr "OCTETS"
+
+#: src/main/help.c:70
+msgid "use a buffer size of BYTES"
+msgstr "Utiliser une mémoire tampon de OCTETS octets"
+
+#: src/main/help.c:71
+msgid "PID"
+msgstr "PID"
+
+#: src/main/help.c:72
+msgid "update settings of process PID"
+msgstr "mettre-à-jour la configuration du processus PID"
+
+#: src/main/help.c:75
+msgid "show this help and exit"
+msgstr "afficher cette aide puis quitter"
+
+#: src/main/help.c:77
+msgid "show version information and exit"
+msgstr "afficher la version puis quitter"
+
+#: src/main/help.c:83
+#, c-format
+msgid "Usage: %s [OPTION] [FILE]..."
+msgstr "Utilisation : %s [OPTIONS] [FICHIER]..."
+
+#: src/main/help.c:87
+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:174
+#, c-format
+msgid "Please report any bugs to %s."
+msgstr "SVP rapporter touts bogues à %s."
+
+#. RATS: ignore
+#: src/main/options.c:85
+#, 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:98
+#, 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:132
+msgid "integer argument expected"
+msgstr "Valeur entière attendue"
+
+#: src/main/options.c:140
+msgid "numeric argument expected"
+msgstr "Valeur numérique attendue"
+
+#. RATS: ignore (OK)
+#: src/main/options.c:234
+#, c-format
+msgid "Try `%s --help' for more information."
+msgstr "Essayez `%s --help' pour plus d'information."
+
+#. RATS: ignore (OK)
+#: src/main/options.c:238
+#, fuzzy, c-format
+msgid "Try `%s -h' for more information."
+msgstr "Essayez `%s -h' pour plus d'information."
+
+#: src/main/version.c:19
+#, c-format
+msgid "%s %s - Copyright(C) %s %s"
+msgstr "%s %s - Copyright(C) %s %s"
+
+#: src/main/version.c:22
+#, c-format
+msgid "Web site: %s"
+msgstr "Site Web: %s"
+
+#: src/main/version.c:26
+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:31
+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."
diff --git a/src/nls/pl.po b/src/nls/pl.po
new file mode 100644
index 0000000..abe621c
--- /dev/null
+++ b/src/nls/pl.po
@@ -0,0 +1,320 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-06-26 10:37+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO-8859-2\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+# "Przewidywany czas ukoñczenia" for ETA is too long
+#: src/pv/file.c:178
+msgid "failed to close file"
+msgstr "nie uda³o siê zamkn±æ pliku"
+
+#: src/pv/file.c:202
+msgid "failed to read file"
+msgstr "nie uda³o siê odczytaæ pliku"
+
+#: src/pv/file.c:212
+msgid "failed to stat file"
+msgstr "nie uda³o siê wykonaæ operacji stat na pliku"
+
+#: src/pv/file.c:222
+msgid "failed to stat output file"
+msgstr "nie uda³o siê wykonaæ operacji stat na pliku wyj¶ciowym"
+
+#: src/pv/file.c:244
+msgid "input file is output file"
+msgstr "plik wej¶ciowy jest zarazem plikiem wyj¶ciowym"
+
+#: src/pv/display.c:100
+msgid "yzafpnum kMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:350 src/pv/transfer.c:94
+msgid "buffer allocation failed"
+msgstr "nie uda³o siê zaalokowaæ bufora"
+
+#: src/pv/display.c:404
+msgid "B"
+msgstr "B"
+
+#: src/pv/display.c:432 src/pv/display.c:441
+msgid "/s"
+msgstr "/s"
+
+#: src/pv/display.c:432 src/pv/display.c:441
+msgid "B/s"
+msgstr "B/s"
+
+#: src/pv/display.c:463
+msgid "ETA"
+msgstr "ETA"
+
+#: src/pv/cursor.c:233 src/pv/cursor.c:249 src/pv/cursor.c:310
+msgid "failed to open terminal"
+msgstr "nie uda³o siê otworzyæ terminala"
+
+#: src/pv/cursor.c:241
+msgid "failed to lock terminal"
+msgstr "nie uda³o siê otworzyæ terminala"
+
+#: src/pv/transfer.c:156
+msgid "select call failed"
+msgstr "nie uda³o siê wywo³aæ funkcji select"
+
+#: src/pv/transfer.c:204
+msgid "read failed"
+msgstr "b³±d odczytu"
+
+#: src/pv/transfer.c:285
+msgid "write failed"
+msgstr "b³±d zapisu"
+
+#: src/main/help.c:33
+msgid "show progress bar"
+msgstr "poka¿ pasek postêpu"
+
+#: src/main/help.c:35
+msgid "show elapsed time"
+msgstr "poka¿ up³ywaj±cy czas"
+
+#: src/main/help.c:37
+msgid "show estimated time of arrival (completion)"
+msgstr "poka¿ szacowany czas ukoñczenia"
+
+#: src/main/help.c:39
+msgid "show data transfer rate counter"
+msgstr "poka¿ licznik prêdko¶ci przesy³ania"
+
+#: src/main/help.c:41
+msgid "show data transfer average rate counter"
+msgstr ""
+
+#: src/main/help.c:43
+msgid "show number of bytes transferred"
+msgstr "poka¿ ilo¶æ przes³anych bajtów"
+
+#: src/main/help.c:45
+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:47
+msgid "output percentages, not visual information"
+msgstr "w¶wietl wyj¶cie procentowo, bez graficznej prezentacji"
+
+#: src/main/help.c:49
+msgid "do not output any transfer information at all"
+msgstr "nie wy¶wietlaj ¿adnych informacji o przesy³aniu"
+
+#: src/main/help.c:51
+msgid "use cursor positioning escape sequences"
+msgstr "u¿ywaj sekwencji escape do pozycjonowania"
+
+#: src/main/help.c:53
+msgid "display nothing until first byte transferred"
+msgstr "nie wy¶wietlaj nic a¿ do pierwszego przes³anego bajtu"
+
+#: src/main/help.c:54
+msgid "SIZE"
+msgstr "WARTO¦Æ"
+
+#: src/main/help.c:55
+msgid "set estimated data size to SIZE bytes"
+msgstr "ustaw oczekiwany rozmiar danych na WARTO¦Æ bajtów"
+
+#: src/main/help.c:57
+msgid "count lines instead of bytes"
+msgstr ""
+
+#: src/main/help.c:58
+msgid "SEC"
+msgstr "WARTO¦Æ"
+
+#: src/main/help.c:59
+msgid "update every SEC seconds"
+msgstr "aktualizuj co ka¿de WARTO¦Æ sekund"
+
+#: src/main/help.c:60
+msgid "WIDTH"
+msgstr "WARTO¦Æ"
+
+#: src/main/help.c:61
+msgid "assume terminal is WIDTH characters wide"
+msgstr "przyjmij, ¿e terminal ma szeroko¶æ WARTO¦Æ znaków"
+
+#: src/main/help.c:62
+msgid "HEIGHT"
+msgstr ""
+
+#: src/main/help.c:63
+msgid "assume terminal is HEIGHT rows high"
+msgstr "przyjmij, ¿e terminal ma szeroko¶æ WARTO¦Æ znaków"
+
+#: src/main/help.c:64
+msgid "NAME"
+msgstr "NAZWA"
+
+#: src/main/help.c:65
+msgid "prefix visual information with NAME"
+msgstr "poprzed¼ informacje prefiksem NAZWA"
+
+#: src/main/help.c:67
+msgid "RATE"
+msgstr ""
+
+#: src/main/help.c:68
+msgid "limit transfer to RATE bytes per second"
+msgstr "ogranicz przesy³ane dane do RATE bajtów na sekundê"
+
+#: src/main/help.c:69
+msgid "BYTES"
+msgstr ""
+
+#: src/main/help.c:70
+msgid "use a buffer size of BYTES"
+msgstr ""
+
+#: src/main/help.c:71
+msgid "PID"
+msgstr ""
+
+#: src/main/help.c:72
+msgid "update settings of process PID"
+msgstr ""
+
+#: src/main/help.c:75
+msgid "show this help and exit"
+msgstr "wy¶wietl te informacje z pomoc± i wyjd¼"
+
+#: src/main/help.c:77
+msgid "show version information and exit"
+msgstr "wy¶wietl informacje o wersji i wyjd¼"
+
+#: src/main/help.c:83
+#, c-format
+msgid "Usage: %s [OPTION] [FILE]..."
+msgstr "Sposób u¿ycia: %s [OPCJA] [PLIK]..."
+
+#: src/main/help.c:87
+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:174
+#, c-format
+msgid "Please report any bugs to %s."
+msgstr "Proszê przesy³aæ zg³oszenia b³êdów do %s."
+
+#. RATS: ignore
+#: src/main/options.c:85
+#, c-format
+msgid "%s: option structure allocation failed (%s)"
+msgstr "%s: nie uda³o siê zaalokowaæ struktur opcji (%s)"
+
+#: src/main/options.c:98
+#, 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:132
+msgid "integer argument expected"
+msgstr ""
+
+#: src/main/options.c:140
+msgid "numeric argument expected"
+msgstr ""
+
+#. RATS: ignore (OK)
+#: src/main/options.c:234
+#, c-format
+msgid "Try `%s --help' for more information."
+msgstr "Spróbuj `%s --help' by uzyskaæ wiêcej informacji"
+
+#. RATS: ignore (OK)
+#: src/main/options.c:238
+#, fuzzy, c-format
+msgid "Try `%s -h' for more information."
+msgstr "Spróbuj `%s -h' by uzyskaæ wiêcej informacji"
+
+#: src/main/version.c:19
+#, c-format
+msgid "%s %s - Copyright(C) %s %s"
+msgstr "%s %s - Prawa autorskie (C) %s %s"
+
+#: src/main/version.c:22
+#, c-format
+msgid "Web site: %s"
+msgstr "Strona WWW: %s"
+
+#: src/main/version.c:26
+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:31
+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 ""
+
+#~ 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..46ccb43
--- /dev/null
+++ b/src/nls/pt.po
@@ -0,0 +1,276 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-06-26 10:37+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO-8859-15\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: src/pv/file.c:178
+msgid "failed to close file"
+msgstr "erro fechando o arquivo"
+
+#: src/pv/file.c:202
+msgid "failed to read file"
+msgstr "erro lendo o arquivo"
+
+#: src/pv/file.c:212
+msgid "failed to stat file"
+msgstr "erro obtendo informações do arquivo"
+
+#: src/pv/file.c:222
+msgid "failed to stat output file"
+msgstr "erro obtendo informações do arquivo de saída"
+
+#: src/pv/file.c:244
+msgid "input file is output file"
+msgstr "os arquivos de entrada e saída são o mesmo"
+
+#: src/pv/display.c:100
+msgid "yzafpnum kMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:350 src/pv/transfer.c:94
+msgid "buffer allocation failed"
+msgstr "erro alocando o buffer"
+
+#: src/pv/display.c:404
+msgid "B"
+msgstr ""
+
+#: src/pv/display.c:432 src/pv/display.c:441
+msgid "/s"
+msgstr ""
+
+#: src/pv/display.c:432 src/pv/display.c:441
+msgid "B/s"
+msgstr "B/s"
+
+#: src/pv/display.c:463
+msgid "ETA"
+msgstr "ETA"
+
+#: src/pv/cursor.c:233 src/pv/cursor.c:249 src/pv/cursor.c:310
+msgid "failed to open terminal"
+msgstr "erro abrindo o terminal"
+
+#: src/pv/cursor.c:241
+msgid "failed to lock terminal"
+msgstr "erro abrindo o terminal"
+
+#: src/pv/transfer.c:156
+msgid "select call failed"
+msgstr "erro na chamada da função select"
+
+#: src/pv/transfer.c:204
+msgid "read failed"
+msgstr "erro de leitura"
+
+#: src/pv/transfer.c:285
+msgid "write failed"
+msgstr "erro de gravação"
+
+#: src/main/help.c:33
+msgid "show progress bar"
+msgstr "exibe barra de progressão"
+
+#: src/main/help.c:35
+msgid "show elapsed time"
+msgstr "exibe tempo passado"
+
+#: src/main/help.c:37
+msgid "show estimated time of arrival (completion)"
+msgstr "exibe o tempo estimado de término"
+
+#: src/main/help.c:39
+msgid "show data transfer rate counter"
+msgstr "exibe a taxa de transferência"
+
+#: src/main/help.c:41
+msgid "show data transfer average rate counter"
+msgstr ""
+
+#: src/main/help.c:43
+msgid "show number of bytes transferred"
+msgstr "exibe a quantidade de bytes transferidos"
+
+#: src/main/help.c:45
+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:47
+msgid "output percentages, not visual information"
+msgstr "exibe apenas as porcentagens, sem informações visuais"
+
+#: src/main/help.c:49
+msgid "do not output any transfer information at all"
+msgstr "executa programa sem exibir quaisquer informações"
+
+#: src/main/help.c:51
+msgid "use cursor positioning escape sequences"
+msgstr "utiliza caracteres de escape para posicionar o cursor"
+
+#: src/main/help.c:53
+msgid "display nothing until first byte transferred"
+msgstr "não exibe nada até iniciar o processamento"
+
+#: src/main/help.c:54
+msgid "SIZE"
+msgstr ""
+
+#: src/main/help.c:55
+msgid "set estimated data size to SIZE bytes"
+msgstr "seta a quantidade estimada de dados em SIZE bytes"
+
+#: src/main/help.c:57
+msgid "count lines instead of bytes"
+msgstr ""
+
+#: src/main/help.c:58
+msgid "SEC"
+msgstr ""
+
+#: src/main/help.c:59
+msgid "update every SEC seconds"
+msgstr "atualiza informações a cada SEC segundos"
+
+#: src/main/help.c:60
+msgid "WIDTH"
+msgstr ""
+
+#: src/main/help.c:61
+msgid "assume terminal is WIDTH characters wide"
+msgstr "assume que terminal possui WIDTH caracteres de largura"
+
+#: src/main/help.c:62
+msgid "HEIGHT"
+msgstr ""
+
+#: src/main/help.c:63
+msgid "assume terminal is HEIGHT rows high"
+msgstr "assume que terminal possui WIDTH caracteres de largura"
+
+#: src/main/help.c:64
+msgid "NAME"
+msgstr ""
+
+#: src/main/help.c:65
+msgid "prefix visual information with NAME"
+msgstr "exibe NAME antes das demais informações"
+
+#: src/main/help.c:67
+msgid "RATE"
+msgstr ""
+
+#: src/main/help.c:68
+msgid "limit transfer to RATE bytes per second"
+msgstr "limita a transferência a RATE bytes por segundo"
+
+#: src/main/help.c:69
+msgid "BYTES"
+msgstr ""
+
+#: src/main/help.c:70
+msgid "use a buffer size of BYTES"
+msgstr ""
+
+#: src/main/help.c:71
+msgid "PID"
+msgstr ""
+
+#: src/main/help.c:72
+msgid "update settings of process PID"
+msgstr ""
+
+#: src/main/help.c:75
+msgid "show this help and exit"
+msgstr "exibe esta tela de ajuda e termina"
+
+#: src/main/help.c:77
+msgid "show version information and exit"
+msgstr "exibe a versão e termina"
+
+#: src/main/help.c:83
+#, c-format
+msgid "Usage: %s [OPTION] [FILE]..."
+msgstr "Uso: %s [OPÇÕES] [ARQUIVOS]..."
+
+#: src/main/help.c:87
+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:174
+#, c-format
+msgid "Please report any bugs to %s."
+msgstr "Por favor, reporte quaisquer defeitos para %s."
+
+#. RATS: ignore
+#: src/main/options.c:85
+#, 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:98
+#, 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:132
+msgid "integer argument expected"
+msgstr ""
+
+#: src/main/options.c:140
+msgid "numeric argument expected"
+msgstr ""
+
+#. RATS: ignore (OK)
+#: src/main/options.c:234
+#, c-format
+msgid "Try `%s --help' for more information."
+msgstr "Tente `%s --help' para maiores informações."
+
+#. RATS: ignore (OK)
+#: src/main/options.c:238
+#, fuzzy, c-format
+msgid "Try `%s -h' for more information."
+msgstr "Tente `%s -h' para maiores informações."
+
+#: src/main/version.c:19
+#, c-format
+msgid "%s %s - Copyright(C) %s %s"
+msgstr "%s %s - Copyright(C) %s %s"
+
+#: src/main/version.c:22
+#, c-format
+msgid "Web site: %s"
+msgstr "Site: %s"
+
+#: src/main/version.c:26
+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:31
+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."
diff --git a/src/nls/pv.pot b/src/nls/pv.pot
new file mode 100644
index 0000000..6947454
--- /dev/null
+++ b/src/nls/pv.pot
@@ -0,0 +1,271 @@
+# 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 <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-06-26 10:37+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: src/pv/file.c:178
+msgid "failed to close file"
+msgstr ""
+
+#: src/pv/file.c:202
+msgid "failed to read file"
+msgstr ""
+
+#: src/pv/file.c:212
+msgid "failed to stat file"
+msgstr ""
+
+#: src/pv/file.c:222
+msgid "failed to stat output file"
+msgstr ""
+
+#: src/pv/file.c:244
+msgid "input file is output file"
+msgstr ""
+
+#: src/pv/display.c:100
+msgid "yzafpnum kMGTPEZY"
+msgstr ""
+
+#: src/pv/display.c:350 src/pv/transfer.c:94
+msgid "buffer allocation failed"
+msgstr ""
+
+#: src/pv/display.c:404
+msgid "B"
+msgstr ""
+
+#: src/pv/display.c:432 src/pv/display.c:441
+msgid "/s"
+msgstr ""
+
+#: src/pv/display.c:432 src/pv/display.c:441
+msgid "B/s"
+msgstr ""
+
+#: src/pv/display.c:463
+msgid "ETA"
+msgstr ""
+
+#: src/pv/cursor.c:233 src/pv/cursor.c:249 src/pv/cursor.c:310
+msgid "failed to open terminal"
+msgstr ""
+
+#: src/pv/cursor.c:241
+msgid "failed to lock terminal"
+msgstr ""
+
+#: src/pv/transfer.c:156
+msgid "select call failed"
+msgstr ""
+
+#: src/pv/transfer.c:204
+msgid "read failed"
+msgstr ""
+
+#: src/pv/transfer.c:285
+msgid "write failed"
+msgstr ""
+
+#: src/main/help.c:33
+msgid "show progress bar"
+msgstr ""
+
+#: src/main/help.c:35
+msgid "show elapsed time"
+msgstr ""
+
+#: src/main/help.c:37
+msgid "show estimated time of arrival (completion)"
+msgstr ""
+
+#: src/main/help.c:39
+msgid "show data transfer rate counter"
+msgstr ""
+
+#: src/main/help.c:41
+msgid "show data transfer average rate counter"
+msgstr ""
+
+#: src/main/help.c:43
+msgid "show number of bytes transferred"
+msgstr ""
+
+#: src/main/help.c:45
+msgid "output even if standard error is not a terminal"
+msgstr ""
+
+#: src/main/help.c:47
+msgid "output percentages, not visual information"
+msgstr ""
+
+#: src/main/help.c:49
+msgid "do not output any transfer information at all"
+msgstr ""
+
+#: src/main/help.c:51
+msgid "use cursor positioning escape sequences"
+msgstr ""
+
+#: src/main/help.c:53
+msgid "display nothing until first byte transferred"
+msgstr ""
+
+#: src/main/help.c:54
+msgid "SIZE"
+msgstr ""
+
+#: src/main/help.c:55
+msgid "set estimated data size to SIZE bytes"
+msgstr ""
+
+#: src/main/help.c:57
+msgid "count lines instead of bytes"
+msgstr ""
+
+#: src/main/help.c:58
+msgid "SEC"
+msgstr ""
+
+#: src/main/help.c:59
+msgid "update every SEC seconds"
+msgstr ""
+
+#: src/main/help.c:60
+msgid "WIDTH"
+msgstr ""
+
+#: src/main/help.c:61
+msgid "assume terminal is WIDTH characters wide"
+msgstr ""
+
+#: src/main/help.c:62
+msgid "HEIGHT"
+msgstr ""
+
+#: src/main/help.c:63
+msgid "assume terminal is HEIGHT rows high"
+msgstr ""
+
+#: src/main/help.c:64
+msgid "NAME"
+msgstr ""
+
+#: src/main/help.c:65
+msgid "prefix visual information with NAME"
+msgstr ""
+
+#: src/main/help.c:67
+msgid "RATE"
+msgstr ""
+
+#: src/main/help.c:68
+msgid "limit transfer to RATE bytes per second"
+msgstr ""
+
+#: src/main/help.c:69
+msgid "BYTES"
+msgstr ""
+
+#: src/main/help.c:70
+msgid "use a buffer size of BYTES"
+msgstr ""
+
+#: src/main/help.c:71
+msgid "PID"
+msgstr ""
+
+#: src/main/help.c:72
+msgid "update settings of process PID"
+msgstr ""
+
+#: src/main/help.c:75
+msgid "show this help and exit"
+msgstr ""
+
+#: src/main/help.c:77
+msgid "show version information and exit"
+msgstr ""
+
+#: src/main/help.c:83
+#, c-format
+msgid "Usage: %s [OPTION] [FILE]..."
+msgstr ""
+
+#: src/main/help.c:87
+msgid ""
+"Concatenate FILE(s), or standard input, to standard output,\n"
+"with monitoring."
+msgstr ""
+
+#: src/main/help.c:174
+#, c-format
+msgid "Please report any bugs to %s."
+msgstr ""
+
+#. RATS: ignore
+#: src/main/options.c:85
+#, c-format
+msgid "%s: option structure allocation failed (%s)"
+msgstr ""
+
+#: src/main/options.c:98
+#, c-format
+msgid "%s: option structure argv allocation failed (%s)"
+msgstr ""
+
+#: src/main/options.c:132
+msgid "integer argument expected"
+msgstr ""
+
+#: src/main/options.c:140
+msgid "numeric argument expected"
+msgstr ""
+
+#. RATS: ignore (OK)
+#: src/main/options.c:234
+#, c-format
+msgid "Try `%s --help' for more information."
+msgstr ""
+
+#. RATS: ignore (OK)
+#: src/main/options.c:238
+#, c-format
+msgid "Try `%s -h' for more information."
+msgstr ""
+
+#: src/main/version.c:19
+#, c-format
+msgid "%s %s - Copyright(C) %s %s"
+msgstr ""
+
+#: src/main/version.c:22
+#, c-format
+msgid "Web site: %s"
+msgstr ""
+
+#: src/main/version.c:26
+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:31
+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 ""
diff --git a/src/pv/cursor.c b/src/pv/cursor.c
new file mode 100644
index 0000000..0c6fc5b
--- /dev/null
+++ b/src/pv/cursor.c
@@ -0,0 +1,515 @@
+/*
+ * 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.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#include "options.h"
+#include "pv.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_IPC
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+# ifdef HAVE_SYS_PARAM_H
+# include <sys/param.h>
+# endif
+# ifdef HAVE_LIBGEN_H
+# include <libgen.h>
+# endif
+#endif /* HAVE_IPC */
+
+
+#ifdef HAVE_IPC
+static int pv_crs__shmid = -1; /* ID of our shared memory segment */
+static int pv_crs__pvcount = 1; /* number of `pv' processes in total */
+static int pv_crs__pvmax = 0; /* highest number of `pv's seen */
+static int *pv_crs__y_top = 0; /* pointer to Y coord of topmost `pv' */
+static int pv_crs__y_lastread = 0; /* last value of __y_top seen */
+static int pv_crs__y_offset = 0; /* our Y offset from this top position */
+static int pv_crs__needreinit = 0; /* set if we need to reinit cursor pos */
+static int pv_crs__noipc = 0; /* set if we can't use IPC */
+#endif /* HAVE_IPC */
+static int pv_crs__uselockfile = 0; /* set if we used a lockfile */
+static int pv_crs__lock_fd = -1; /* fd of lockfile, -1 if none open */
+static int pv_crs__y_start = 0; /* our initial Y coordinate */
+
+
+/*
+ * Lock the terminal on the given file descriptor by creating and locking a
+ * per-euid, per-tty, lockfile in ${TMPDIR:-${TMP:-/tmp}}.
+ */
+static void pv_crs__lock_lockfile(int fd)
+{
+#ifdef O_EXLOCK
+ char *ttydev;
+ char *tmpdir;
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 4096
+#endif
+ char lockfile[MAXPATHLEN + 1]; /* RATS: ignore */
+
+ pv_crs__uselockfile = 1;
+
+ ttydev = ttyname(fd); /* RATS: ignore */
+ if (!ttydev) {
+#ifdef HAVE_IPC
+ pv_crs__noipc = 1;
+#endif
+ return;
+ }
+
+ tmpdir = (char *) getenv("TMPDIR"); /* RATS: ignore */
+ if (!tmpdir)
+ tmpdir = (char *) getenv("TMP"); /* RATS: ignore */
+ if (!tmpdir)
+ tmpdir = "/tmp";
+
+#ifdef HAVE_SNPRINTF
+ snprintf(lockfile, MAXPATHLEN, "%s/pv-%s-%i.lock",
+ tmpdir, basename(ttydev), geteuid());
+#else
+ sprintf(lockfile, /* RATS: ignore */
+ "%.*s/pv-%8s-%i.lock",
+ MAXPATHLEN - 64, tmpdir, basename(ttydev), geteuid());
+#endif
+
+ pv_crs__lock_fd =
+ open(lockfile, O_RDWR | O_EXLOCK | O_CREAT | O_NOFOLLOW, 0600);
+#ifdef HAVE_IPC
+ if (pv_crs__lock_fd < 0)
+ pv_crs__noipc = 1;
+#endif
+
+#else /* !O_EXLOCK */
+
+ pv_crs__uselockfile = 1;
+#ifdef HAVE_IPC
+ pv_crs__noipc = 1;
+#endif
+
+#endif /* O_EXLOCK */
+}
+
+
+/*
+ * 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(int fd)
+{
+ struct flock lock;
+
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 1;
+ while (fcntl(fd, F_SETLKW, &lock) < 0) {
+ if (errno != EINTR) {
+ pv_crs__lock_lockfile(fd);
+ return;
+ }
+ }
+}
+
+
+/*
+ * Unlock the terminal on the given file descriptor. If pv_crs__lock used
+ * lockfile locking, unlock the lockfile.
+ */
+static void pv_crs__unlock(int fd)
+{
+ struct flock lock;
+
+ if (pv_crs__uselockfile) {
+ if (pv_crs__lock_fd >= 0)
+ close(pv_crs__lock_fd);
+ pv_crs__lock_fd = -1;
+ } else {
+ lock.l_type = F_UNLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 1;
+ fcntl(fd, F_SETLK, &lock);
+ }
+}
+
+
+#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(void)
+{
+ struct shmid_ds buf;
+
+ buf.shm_nattch = 0;
+
+ shmctl(pv_crs__shmid, IPC_STAT, &buf);
+ pv_crs__pvcount = buf.shm_nattch;
+
+ if (pv_crs__pvcount > pv_crs__pvmax)
+ pv_crs__pvmax = pv_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]; /* RATS: ignore (checked) */
+ int ypos;
+
+ 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));
+ read(terminalfd, cpr, 6); /* RATS: ignore (OK) */
+ ypos = pv_getnum_i(cpr + 2);
+ tcsetattr(terminalfd, TCSANOW | TCSAFLUSH, &old_tty);
+
+ 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(opts_t opts, 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 (key == -1) {
+ fprintf(stderr, "%s: %s: %s\n",
+ opts->program_name,
+ _("failed to open terminal"), strerror(errno));
+ return 1;
+ }
+
+ pv_crs__lock(terminalfd);
+ if (pv_crs__noipc) {
+ fprintf(stderr, "%s: %s: %s\n",
+ opts->program_name,
+ _("failed to lock terminal"), strerror(errno));
+ return 1;
+ }
+
+ pv_crs__shmid = shmget(key, sizeof(int), 0600 | IPC_CREAT);
+ if (pv_crs__shmid < 0) {
+ fprintf(stderr, "%s: %s: %s\n",
+ opts->program_name,
+ _("failed to open terminal"), strerror(errno));
+ pv_crs__unlock(terminalfd);
+ return 1;
+ }
+
+ pv_crs__y_top = shmat(pv_crs__shmid, 0, 0);
+
+ pv_crs__ipccount();
+
+ /*
+ * 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 (pv_crs__pvcount < 2) {
+ pv_crs__y_start = pv_crs__get_ypos(terminalfd);
+ *pv_crs__y_top = pv_crs__y_start;
+ pv_crs__y_lastread = pv_crs__y_start;
+ }
+
+ pv_crs__y_offset = pv_crs__pvcount - 1;
+ if (pv_crs__y_offset < 0)
+ pv_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 (pv_crs__pvcount > 1) {
+ pv_crs__y_start = *pv_crs__y_top;
+ pv_crs__y_lastread = pv_crs__y_start;
+ }
+
+ pv_crs__unlock(terminalfd);
+
+ return 0;
+}
+#endif /* HAVE_IPC */
+
+
+/*
+ * Initialise the terminal for cursor positioning.
+ */
+void pv_crs_init(opts_t opts)
+{
+ char *ttyfile;
+ int fd;
+
+ if (!opts->cursor)
+ return;
+
+ ttyfile = ttyname(STDERR_FILENO); /* RATS: ignore (unimportant) */
+ if (!ttyfile) {
+ opts->cursor = 0;
+ return;
+ }
+
+ fd = open(ttyfile, O_RDWR); /* RATS: ignore (no race) */
+ if (fd < 0) {
+ fprintf(stderr, "%s: %s: %s\n",
+ opts->program_name,
+ _("failed to open terminal"), strerror(errno));
+ opts->cursor = 0;
+ return;
+ }
+#ifdef HAVE_IPC
+ if (pv_crs__ipcinit(opts, ttyfile, fd)) {
+ opts->cursor = 0;
+ close(fd);
+ return;
+ }
+
+ /*
+ * 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 (pv_crs__noipc) {
+#else /* ! HAVE_IPC */
+ if (1) {
+#endif /* HAVE_IPC */
+ /*
+ * Get current cursor position + 1.
+ */
+ pv_crs__lock(fd);
+ pv_crs__y_start = pv_crs__get_ypos(fd);
+ pv_crs__unlock(fd);
+
+ if (pv_crs__y_start < 1)
+ opts->cursor = 0;
+ }
+
+ close(fd);
+}
+
+
+#ifdef HAVE_IPC
+/*
+ * Set the "we need to reinitialise cursor positioning" flag.
+ */
+void pv_crs_needreinit(void)
+{
+ pv_crs__needreinit += 2;
+ if (pv_crs__needreinit > 3)
+ pv_crs__needreinit = 3;
+}
+#endif
+
+
+#ifdef HAVE_IPC
+/*
+ * Reinitialise the cursor positioning code (called if we are backgrounded
+ * then foregrounded again).
+ */
+void pv_crs_reinit(void)
+{
+ pv_crs__lock(STDERR_FILENO);
+
+ pv_crs__needreinit--;
+ if (pv_crs__y_offset < 1)
+ pv_crs__needreinit = 0;
+
+ if (pv_crs__needreinit > 0) {
+ pv_crs__unlock(STDERR_FILENO);
+ return;
+ }
+
+ pv_crs__y_start = pv_crs__get_ypos(STDERR_FILENO);
+
+ if (pv_crs__y_offset < 1)
+ *pv_crs__y_top = pv_crs__y_start;
+ pv_crs__y_lastread = pv_crs__y_start;
+
+ pv_crs__unlock(STDERR_FILENO);
+}
+#endif
+
+
+/*
+ * Output a single-line update, moving the cursor to the correct position to
+ * do so.
+ */
+void pv_crs_update(opts_t opts, char *str)
+{
+ char pos[32]; /* RATS: ignore (checked OK) */
+ int y;
+
+#ifdef HAVE_IPC
+ if (!pv_crs__noipc) {
+ if (pv_crs__needreinit)
+ pv_crs_reinit();
+
+ pv_crs__ipccount();
+ if (pv_crs__y_lastread != *pv_crs__y_top) {
+ pv_crs__y_start = *pv_crs__y_top;
+ pv_crs__y_lastread = pv_crs__y_start;
+ }
+
+ if (pv_crs__needreinit > 0)
+ return;
+ }
+#endif /* HAVE_IPC */
+
+ y = pv_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 (((pv_crs__y_start + pv_crs__pvmax) > opts->height)
+ && (!pv_crs__noipc)
+ ) {
+ int offs;
+
+ offs = ((pv_crs__y_start + pv_crs__pvmax) - opts->height);
+
+ pv_crs__y_start -= offs;
+ if (pv_crs__y_start < 1)
+ pv_crs__y_start = 1;
+
+ /*
+ * Scroll the screen if we're the first `pv'.
+ */
+ if (pv_crs__y_offset == 0) {
+ pv_crs__lock(STDERR_FILENO);
+
+ sprintf(pos, "\033[%d;1H", opts->height);
+ write(STDERR_FILENO, pos, strlen(pos));
+ for (; offs > 0; offs--) {
+ write(STDERR_FILENO, "\n", 1);
+ }
+
+ pv_crs__unlock(STDERR_FILENO);
+ }
+ }
+
+ if (!pv_crs__noipc)
+ y = pv_crs__y_start + pv_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(STDERR_FILENO);
+
+ write(STDERR_FILENO, pos, strlen(pos)); /* RATS: ignore */
+ write(STDERR_FILENO, str, strlen(str)); /* RATS: ignore */
+
+ pv_crs__unlock(STDERR_FILENO);
+}
+
+
+/*
+ * Reposition the cursor to a final position.
+ */
+void pv_crs_fini(opts_t opts)
+{
+ char pos[32]; /* RATS: ignore (checked OK) */
+ int y;
+
+ y = pv_crs__y_start;
+
+#ifdef HAVE_IPC
+ if ((pv_crs__pvmax > 0) && (!pv_crs__noipc))
+ y += pv_crs__pvmax - 1;
+#endif /* HAVE_IPC */
+
+ if (y > opts->height)
+ y = opts->height;
+
+ /*
+ * Absolute bounds check.
+ */
+ if ((y < 1) || (y > 999999))
+ y = 1;
+
+ sprintf(pos, "\033[%d;1H\n", y); /* RATS: ignore */
+
+ pv_crs__lock(STDERR_FILENO);
+
+ write(STDERR_FILENO, pos, strlen(pos)); /* RATS: ignore */
+
+#ifdef HAVE_IPC
+ pv_crs__ipccount();
+ shmdt((void *) pv_crs__y_top);
+
+ /*
+ * If we are the last instance detaching from the shared memory,
+ * delete it so it's not left lying around.
+ */
+ if (pv_crs__pvcount < 2)
+ shmctl(pv_crs__shmid, IPC_RMID, 0);
+
+#endif /* HAVE_IPC */
+
+ pv_crs__unlock(STDERR_FILENO);
+}
+
+/* EOF */
diff --git a/src/pv/display.c b/src/pv/display.c
new file mode 100644
index 0000000..5a39bf7
--- /dev/null
+++ b/src/pv/display.c
@@ -0,0 +1,635 @@
+/*
+ * Display functions.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "options.h"
+#include "pv.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+
+
+/*
+ * Fill in opts->width and opts->height with the current terminal size,
+ * if possible.
+ */
+void pv_screensize(opts_t opts)
+{
+#ifdef TIOCGWINSZ
+ struct winsize wsz;
+
+ if (isatty(STDERR_FILENO)) {
+ if (ioctl(STDERR_FILENO, TIOCGWINSZ, &wsz) == 0) {
+ opts->width = wsz.ws_col;
+ opts->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 <hsggebhardt@googlemail.com> and then
+ * modified. Further changed after input from Thomas Rachel.
+ */
+static void pv__si_prefix(long double *value, char *prefix,
+ const long double ratio, int is_bytes)
+{
+ static char *pfx = NULL;
+ static char const *pfx_middle = NULL;
+ char const *i;
+ long double cutoff;
+
+ if (pfx == NULL) {
+ pfx = _("yzafpnum kMGTPEZY");
+ }
+
+ if (pfx_middle == NULL) {
+ /*
+ * We can't assign this in the declaration above because
+ * that wouldn't be constant, so we do it here.
+ */
+ pfx_middle = strchr(pfx, ' ');
+ }
+ 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 (*value == 0.0)
+ 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]; /* RATS: ignore (big enough) */
+ char si_prefix[8] = " "; /* RATS: ignore (big enough) */
+ 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];
+ sprintf(str_disp, "%4.3Lg", display_amount);
+ sprintf(sizestr_buffer, "%s%.2s%.16s", str_disp,
+ si_prefix, suffix);
+ }
+
+ snprintf(buffer, bufsize, format, sizestr_buffer);
+}
+
+
+/*
+ * Structure to hold the internal data for a single display.
+ */
+struct pv_display_state {
+ long percentage;
+ long double prev_elapsed_sec;
+ long double prev_rate;
+ long double prev_trans;
+ char *outbuffer;
+ long outbufsize;
+ int prev_width; /* screen width last time we were called */
+ int prev_length; /* length of last string we output */
+ opts_t opts;
+};
+
+
+/*
+ * Initialise the given display structure from the given option set. Note
+ * that this copies the given "opts" pointer, not the underlying structure,
+ * so if "opts" is freed, the state's copy is invalidated and must not be
+ * used.
+ */
+static void pv__state_init(struct pv_display_state *state, opts_t opts)
+{
+ if (state == NULL)
+ return;
+ memset(state, 0, sizeof(struct pv_display_state));
+ state->opts = opts;
+}
+
+
+/*
+ * 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(struct pv_display_state *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 component_count;
+ int static_portion_size;
+ char str_transferred[128]; /* RATS: ignore (big enough) */
+ char str_timer[128]; /* RATS: ignore (big enough) */
+ char str_rate[128]; /* RATS: ignore (big enough) */
+ char str_average_rate[128]; /* RATS: ignore (big enough) */
+ char str_eta[128]; /* RATS: ignore (big enough) */
+ int output_length;
+
+ /* Quick sanity check - state must exist */
+ if (state == NULL)
+ return NULL;
+
+ /* Negative total transfer - free memory and exit */
+ if (total_bytes < 0) {
+ if (state->outbuffer)
+ free(state->outbuffer);
+ state->outbuffer = 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->opts->average_rate)) {
+ /* Sanity check to avoid division by zero */
+ if (elapsed_sec < 0.000001)
+ elapsed_sec = 0.000001;
+ average_rate =
+ ((long double) total_bytes) /
+ (long double) elapsed_sec;
+ if (bytes_since_last < 0)
+ rate = average_rate;
+ }
+
+ if (state->opts->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->opts->numeric || state->opts->progress) {
+ /*
+ * 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->opts->size);
+ }
+
+ /*
+ * Reallocate output buffer if width changes.
+ */
+ if (state->outbuffer != NULL
+ && state->outbufsize < (state->opts->width * 2)) {
+ free(state->outbuffer);
+ state->outbuffer = NULL;
+ state->outbufsize = 0;
+ }
+
+ /*
+ * Allocate output buffer if there isn't one.
+ */
+ if (state->outbuffer == NULL) {
+ state->outbufsize = (2 * state->opts->width) + 80;
+ if (state->opts->name)
+ state->outbufsize += strlen(state->opts->name); /* RATS: ignore */
+ state->outbuffer = malloc(state->outbufsize + 16);
+ if (state->outbuffer == NULL) {
+ fprintf(stderr, "%s: %s: %s\n",
+ state->opts->program_name,
+ _("buffer allocation failed"),
+ strerror(errno));
+ state->opts->exit_status |= 64;
+ return NULL;
+ }
+ state->outbuffer[0] = 0;
+ }
+
+ /* In numeric output mode, our output is just a number. */
+ if (state->opts->numeric) {
+ if (state->percentage > 100) {
+ /* As mentioned above, we go 0-100, then 100-0. */
+ sprintf(state->outbuffer, "%ld\n",
+ 200 - state->percentage);
+ } else {
+ sprintf(state->outbuffer, "%ld\n",
+ state->percentage);
+ }
+ return state->outbuffer;
+ }
+
+ /*
+ * 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.
+ */
+
+ /* We start off with no components. */
+ component_count = 0;
+ static_portion_size = 0;
+ str_transferred[0] = 0;
+ str_timer[0] = 0;
+ str_rate[0] = 0;
+ str_average_rate[0] = 0;
+ str_eta[0] = 0;
+
+ /* If we're showing a name, add it to the list and the length. */
+ if (state->opts->name) {
+ int name_length;
+
+ name_length = strlen(state->opts->name);
+ if (name_length < 9)
+ name_length = 9;
+ if (name_length > 500)
+ name_length = 500;
+
+ component_count++;
+ static_portion_size += name_length + 1; /* +1 for ":" */
+ }
+
+ /* If we're showing bytes transferred, set up the display string. */
+ if (state->opts->bytes) {
+ pv__sizestr(str_transferred, sizeof(str_transferred), "%s",
+ (long double) total_bytes, "", _("B"),
+ state->opts->linemode ? 0 : 1);
+ component_count++;
+ static_portion_size += strlen(str_transferred);
+ }
+
+ /* Timer - set up the display string. */
+ if (state->opts->timer) {
+ /*
+ * 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(str_timer, "%ld:%02ld:%02ld",
+ ((long) elapsed_sec) / 3600,
+ (((long) elapsed_sec) / 60) % 60,
+ ((long) elapsed_sec) % 60);
+
+ component_count++;
+ static_portion_size += strlen(str_timer);
+ }
+
+ /* Rate - set up the display string. */
+ if (state->opts->rate) {
+ pv__sizestr(str_rate, sizeof(str_rate), "[%s]", rate,
+ _("/s"), _("B/s"),
+ state->opts->linemode ? 0 : 1);
+ component_count++;
+ static_portion_size += strlen(str_rate);
+ }
+
+ /* Average rate - set up the display string. */
+ if (state->opts->average_rate) {
+ pv__sizestr(str_average_rate, sizeof(str_average_rate),
+ "[%s]", average_rate, _("/s"), _("B/s"),
+ state->opts->linemode ? 0 : 1);
+ component_count++;
+ static_portion_size += strlen(str_average_rate);
+ }
+
+ /* ETA (only if size is known) - set up the display string. */
+ if (state->opts->eta && state->opts->size > 0) {
+ eta =
+ pv__calc_eta(total_bytes, state->opts->size,
+ elapsed_sec);
+
+ if (eta < 0)
+ eta = 0;
+
+ /*
+ * Bounds check, so we don't overrun the suffix buffer. This
+ * means the ETA will always be less than 100,000 hours.
+ */
+ if (eta > (long) 360000000L)
+ eta = (long) 360000000L;
+
+ sprintf(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(str_eta) && str_eta[i] != 0;
+ i++) {
+ str_eta[i] = ' ';
+ }
+ }
+
+ component_count++;
+ static_portion_size += strlen(str_eta);
+ }
+
+ /*
+ * We now have all the static portions built; all that is left is
+ * the dynamically sized progress bar. So now we assemble the
+ * output buffer, inserting the progress bar at the appropriate
+ * point with the appropriate width.
+ */
+
+ state->outbuffer[0] = 0;
+
+ if (state->opts->name) {
+ sprintf(state->outbuffer, "%9s:", state->opts->name); /* RATS: ignore (OK) */
+ }
+#define PV_APPEND(x) if (x[0] != 0) { \
+ if (state->outbuffer[0] != 0) \
+ strcat(state->outbuffer, " "); \
+ strcat(state->outbuffer, x); \
+ }
+
+ PV_APPEND(str_transferred);
+ PV_APPEND(str_timer);
+ PV_APPEND(str_rate);
+ PV_APPEND(str_average_rate);
+
+ if (state->opts->progress) {
+ char pct[16]; /* RATS: ignore (big enough) */
+ int available_width, i;
+
+ if (state->outbuffer[0] != 0)
+ strcat(state->outbuffer, " ");
+ strcat(state->outbuffer, "[");
+
+ if (state->opts->size > 0) {
+ if (state->percentage < 0)
+ state->percentage = 0;
+ if (state->percentage > 100000)
+ state->percentage = 100000;
+ sprintf(pct, "%2ld%%", state->percentage);
+ available_width =
+ state->opts->width - static_portion_size -
+ component_count - strlen(pct) - 3;
+
+ for (i = 0;
+ i <
+ (available_width * state->percentage) / 100 -
+ 1; i++) {
+ if (i < available_width)
+ strcat(state->outbuffer, "=");
+ }
+ if (i < available_width) {
+ strcat(state->outbuffer, ">");
+ i++;
+ }
+ for (; i < available_width; i++) {
+ strcat(state->outbuffer, " ");
+ }
+ strcat(state->outbuffer, "] ");
+ strcat(state->outbuffer, pct); /* RATS: ignore (OK) */
+ } else {
+ int p = state->percentage;
+ available_width =
+ state->opts->width - static_portion_size -
+ component_count - 5;
+ if (p > 100)
+ p = 200 - p;
+ for (i = 0; i < (available_width * p) / 100; i++) {
+ if (i < available_width)
+ strcat(state->outbuffer, " ");
+ }
+ strcat(state->outbuffer, "<=>");
+ for (; i < available_width; i++) {
+ strcat(state->outbuffer, " ");
+ }
+ strcat(state->outbuffer, "]");
+ }
+ }
+
+ PV_APPEND(str_eta);
+
+ /*
+ * 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->outbuffer);
+ if ((output_length < state->prev_length)
+ && (state->opts->width >= state->prev_width)) {
+ char spaces[32]; /* RATS: ignore (bounded below) */
+ 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->outbuffer, /* RATS: ignore (OK) */ spaces);
+ }
+ state->prev_width = state->opts->width;
+ state->prev_length = output_length;
+
+ return state->outbuffer;
+}
+
+
+/*
+ * 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.
+ *
+ * If "opts" is NULL, then free all allocated memory and return.
+ */
+void pv_display(opts_t opts, long double esec, long long sl, long long tot)
+{
+ static struct pv_display_state state;
+ static int initialised = 0;
+ char *display;
+
+ if (!initialised) {
+ pv__state_init(&state, opts);
+ initialised = 1;
+ }
+
+ if (opts == NULL) {
+ if (initialised)
+ (void) pv__format(&state, 0, 0, -1);
+ initialised = 0;
+ return;
+ }
+
+ pv_sig_checkbg();
+
+ display = pv__format(&state, esec, sl, tot);
+ if (display == NULL)
+ return;
+
+ if (opts->numeric) {
+ write(STDERR_FILENO, display, strlen(display)); /* RATS: ignore */
+ } else if (opts->cursor) {
+ pv_crs_update(opts, display);
+ } else {
+ write(STDERR_FILENO, display, strlen(display)); /* RATS: ignore */
+ write(STDERR_FILENO, "\r", 1);
+ }
+}
+
+/* EOF */
diff --git a/src/pv/file.c b/src/pv/file.c
new file mode 100644
index 0000000..4eadec2
--- /dev/null
+++ b/src/pv/file.c
@@ -0,0 +1,250 @@
+/*
+ * Functions for opening and closing files.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#define _GNU_SOURCE 1
+#include <limits.h>
+
+#include <stdio.h>
+#include "options.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/*
+ * 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.
+ */
+void pv_calc_total_size(opts_t opts)
+{
+ struct stat64 sb;
+ int rc, i, j, fd;
+
+ opts->size = 0;
+ rc = 0;
+
+ if (opts->argc < 1) {
+ if (fstat64(STDIN_FILENO, &sb) == 0)
+ opts->size = sb.st_size;
+ return;
+ }
+
+ for (i = 0; i < opts->argc; i++) {
+ if (strcmp(opts->argv[i], "-") == 0) {
+ rc = fstat64(STDIN_FILENO, &sb);
+ if (rc != 0) {
+ opts->size = 0;
+ return;
+ }
+ } else {
+ rc = stat64(opts->argv[i], &sb);
+ if (rc == 0)
+ rc = access(opts->argv[i], R_OK);
+ }
+
+ if (rc != 0) {
+ fprintf(stderr, "%s: %s: %s\n", opts->program_name,
+ opts->argv[i], strerror(errno));
+ for (j = i; j < opts->argc - 1; j++) {
+ opts->argv[j] = opts->argv[j + 1];
+ }
+ opts->argc--;
+ i--;
+ opts->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 (strcmp(opts->argv[i], "-") == 0) {
+ fd = open64("/dev/stdin", O_RDONLY);
+ } else {
+ fd = open64(opts->argv[i], O_RDONLY);
+ }
+ if (fd >= 0) {
+ opts->size += lseek64(fd, 0, SEEK_END);
+ close(fd);
+ } else {
+ fprintf(stderr, "%s: %s: %s\n",
+ opts->program_name, opts->argv[i],
+ strerror(errno));
+ opts->exit_status |= 2;
+ }
+ } else if (S_ISREG(sb.st_mode)) {
+ opts->size += sb.st_size;
+ } else {
+ opts->size = 0;
+ }
+ }
+
+ if (!opts->linemode)
+ return;
+
+ opts->size = 0;
+
+ for (i = 0; i < opts->argc; i++) {
+ fd = -1;
+
+ if (strcmp(opts->argv[i], "-") == 0) {
+ rc = fstat64(STDIN_FILENO, &sb);
+ if ((rc != 0) || (!S_ISREG(sb.st_mode))) {
+ opts->size = 0;
+ return;
+ }
+ fd = dup(STDIN_FILENO);
+ } else {
+ rc = stat64(opts->argv[i], &sb);
+ if ((rc != 0) || (!S_ISREG(sb.st_mode))) {
+ opts->size = 0;
+ return;
+ }
+ fd = open64(opts->argv[i], O_RDONLY);
+ }
+
+ if (fd < 0) {
+ fprintf(stderr, "%s: %s: %s\n", opts->program_name,
+ opts->argv[i], strerror(errno));
+ opts->size = 0;
+ opts->exit_status |= 2;
+ return;
+ }
+
+ while (1) {
+ unsigned char scanbuf[1024]; /* RATS: ignore (OK) */
+ int numread, j;
+
+ numread = read(fd, /* RATS: ignore (OK) */ scanbuf,
+ sizeof(scanbuf));
+ if (numread < 0) {
+ fprintf(stderr, "%s: %s: %s\n",
+ opts->program_name, opts->argv[i],
+ strerror(errno));
+ opts->exit_status |= 2;
+ break;
+ } else if (numread == 0) {
+ break;
+ }
+ for (j = 0; j < numread; j++) {
+ if (scanbuf[j] == '\n')
+ opts->size++;
+ }
+ }
+
+ lseek64(fd, 0, SEEK_SET);
+ close(fd);
+ }
+}
+
+
+/*
+ * 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.
+ */
+int pv_next_file(opts_t opts, int filenum, int oldfd)
+{
+ struct stat64 isb;
+ struct stat64 osb;
+ int fd;
+
+ if (oldfd > 0) {
+ if (close(oldfd)) {
+ fprintf(stderr, "%s: %s: %s\n",
+ opts->program_name,
+ _("failed to close file"),
+ strerror(errno));
+ opts->exit_status |= 8;
+ return -1;
+ }
+ }
+
+ if (filenum >= opts->argc) {
+ opts->exit_status |= 8;
+ return -1;
+ }
+
+ if (filenum < 0) {
+ opts->exit_status |= 8;
+ return -1;
+ }
+
+ if (strcmp(opts->argv[filenum], "-") == 0) {
+ fd = STDIN_FILENO;
+ } else {
+ fd = open64(opts->argv[filenum], O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "%s: %s: %s: %s\n",
+ opts->program_name,
+ _("failed to read file"),
+ opts->argv[filenum], strerror(errno));
+ opts->exit_status |= 2;
+ return -1;
+ }
+ }
+
+ if (fstat64(fd, &isb)) {
+ fprintf(stderr, "%s: %s: %s: %s\n",
+ opts->program_name,
+ _("failed to stat file"),
+ opts->argv[filenum], strerror(errno));
+ close(fd);
+ opts->exit_status |= 2;
+ return -1;
+ }
+
+ if (fstat64(STDOUT_FILENO, &osb)) {
+ fprintf(stderr, "%s: %s: %s\n",
+ opts->program_name,
+ _("failed to stat output file"), strerror(errno));
+ close(fd);
+ opts->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.
+ */
+ if (isb.st_dev != osb.st_dev)
+ return fd;
+ if (isb.st_ino != osb.st_ino)
+ return fd;
+ if (isatty(fd))
+ return fd;
+ if ((!S_ISREG(isb.st_mode)) && (!S_ISBLK(isb.st_mode)))
+ return fd;
+
+ fprintf(stderr, "%s: %s: %s\n",
+ opts->program_name,
+ _("input file is output file"), opts->argv[filenum]);
+ close(fd);
+ opts->exit_status |= 4;
+ return -1;
+}
+
+/* EOF */
diff --git a/src/pv/loop.c b/src/pv/loop.c
new file mode 100644
index 0000000..8812eff
--- /dev/null
+++ b/src/pv/loop.c
@@ -0,0 +1,287 @@
+/*
+ * Main program entry point - read the command line options, then perform
+ * the appropriate actions.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#include "options.h"
+#include "pv.h"
+
+#define _GNU_SOURCE 1
+#include <limits.h>
+
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define RATE_GRANULARITY 100000 /* usec between -L rate chunks */
+
+extern struct timeval pv_sig_toffset;
+extern sig_atomic_t pv_sig_newsize;
+extern sig_atomic_t pv_sig_abort;
+
+
+/*
+ * 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(opts_t opts)
+{
+ 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;
+ 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(opts);
+
+ eof_in = 0;
+ eof_out = 0;
+ total_written = 0;
+ since_last = 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;
+ pv_timeval_add_usec(&next_update,
+ (long) (1000000.0 * opts->interval));
+
+ next_ratecheck.tv_sec = start_time.tv_sec;
+ next_ratecheck.tv_usec = start_time.tv_usec;
+
+ target = 0;
+ cansend = 0;
+ final_update = 0;
+ n = 0;
+
+ fd = pv_next_file(opts, n, -1);
+ if (fd < 0) {
+ return opts->exit_status;
+ }
+
+ if (fstat64(fd, &sb) == 0) {
+ pv_set_buffer_size(sb.st_blksize * 32, 0);
+ }
+
+ if (opts->buffer_size > 0) {
+ pv_set_buffer_size(opts->buffer_size, 1);
+ }
+
+ while ((!(eof_in && eof_out)) || (!final_update)) {
+
+ if (pv_sig_abort)
+ break;
+
+ if (opts->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) (opts->rate_limit)) /
+ (long double) (1000000 /
+ RATE_GRANULARITY);
+ pv_timeval_add_usec(&next_ratecheck,
+ RATE_GRANULARITY);
+ }
+ cansend = target;
+ }
+
+ written =
+ pv_transfer(opts, fd, &eof_in, &eof_out, cansend,
+ &lineswritten);
+ if (written < 0)
+ return opts->exit_status;
+
+ if (opts->linemode) {
+ since_last += lineswritten;
+ total_written += lineswritten;
+ if (opts->rate_limit > 0)
+ target -= lineswritten;
+ } else {
+ since_last += written;
+ total_written += written;
+ if (opts->rate_limit > 0)
+ target -= written;
+ }
+
+ if (eof_in && eof_out && n < (opts->argc - 1)) {
+ n++;
+ fd = pv_next_file(opts, n, fd);
+ if (fd < 0)
+ return opts->exit_status;
+ eof_in = 0;
+ eof_out = 0;
+ }
+
+ gettimeofday(&cur_time, NULL);
+
+ if (eof_in && eof_out) {
+ final_update = 1;
+ next_update.tv_sec = cur_time.tv_sec - 1;
+ }
+
+ if (opts->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 (opts->wait) {
+ if (opts->linemode) {
+ if (lineswritten < 1)
+ continue;
+ } else {
+ if (written < 1)
+ continue;
+ }
+
+ opts->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);
+ pv_sig_toffset.tv_sec = 0;
+ 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 *
+ opts->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 * opts->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 + pv_sig_toffset.tv_sec;
+ init_time.tv_usec =
+ start_time.tv_usec + 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 (pv_sig_newsize) {
+ pv_sig_newsize = 0;
+ pv_screensize(opts);
+ }
+
+ pv_display(opts, elapsed, since_last, total_written);
+
+ since_last = 0;
+ }
+
+ if (opts->cursor) {
+ pv_crs_fini(opts);
+ } else {
+ if ((!opts->numeric) && (!opts->no_op))
+ write(STDERR_FILENO, "\n", 1);
+ }
+
+ /*
+ * Free up the buffers used by the display and data transfer
+ * routines.
+ */
+ pv_display(0, 0, 0, 0);
+ pv_transfer(0, -1, 0, 0, 0, NULL);
+
+ if (pv_sig_abort)
+ opts->exit_status |= 32;
+
+ return opts->exit_status;
+}
+
+/* EOF */
diff --git a/src/pv/number.c b/src/pv/number.c
new file mode 100644
index 0000000..0495b13
--- /dev/null
+++ b/src/pv/number.c
@@ -0,0 +1,206 @@
+/*
+ * Functions for converting strings to numbers.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+/*
+ * This function is used instead of the macro from <ctype.h> because
+ * including <ctype.h> 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(char *str)
+{
+ long long n = 0;
+ long long decimal = 0;
+ int decdivisor = 1;
+ int shift = 0;
+
+ 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] == ' ') || (str[0] == '\t'))
+ 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(char *str)
+{
+ double n = 0.0;
+ double step = 1;
+
+ 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(char *str)
+{
+ return (int) pv_getnum_ll(str);
+}
+
+
+/*
+ * Return nonzero if the given string is not a valid integer (type=0) or
+ * double (type=1).
+ */
+int pv_getnum_check(char *str, int type)
+{
+ if (!str)
+ return 1;
+
+ while ((str[0] == ' ') || (str[0] == '\t'))
+ str++;
+
+ if (!pv__isdigit(str[0]))
+ return 1;
+
+ for (; pv__isdigit(str[0]); str++);
+
+ if (str[0] == '.') {
+ if (type == 0)
+ return 1;
+ str++;
+ for (; pv__isdigit(str[0]); str++);
+ }
+
+ if (str[0] == 0)
+ return 0;
+
+ /*
+ * Suffixes are not allowed for doubles, only for integers.
+ */
+ if (type == 1)
+ return 1;
+
+ while ((str[0] == ' ') || (str[0] == '\t'))
+ 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])
+ return 1;
+
+ return 0;
+}
+
+/* EOF */
diff --git a/src/pv/signal.c b/src/pv/signal.c
new file mode 100644
index 0000000..a22137b
--- /dev/null
+++ b/src/pv/signal.c
@@ -0,0 +1,290 @@
+/*
+ * Signal handling functions.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#include "pv.h"
+
+#include <signal.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/time.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+static int pv__sig_old_stderr; /* see pv__sig_ttou() */
+static struct timeval pv__sig_tstp_time; /* see pv__sig_tstp() / __cont() */
+
+struct timeval pv_sig_toffset; /* total time spent stopped */
+sig_atomic_t pv_sig_newsize = 0; /* whether we need to get term size again */
+sig_atomic_t pv_sig_abort = 0; /* whether we need to abort right now */
+
+#ifdef HAVE_IPC
+void pv_crs_needreinit(void);
+#endif
+
+
+/*
+ * 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); /* RATS: ignore (no race) */
+ if (fd < 0)
+ return;
+
+ if (pv__sig_old_stderr == -1)
+ 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_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_newsize = 1;
+
+ if (pv__sig_tstp_time.tv_sec == 0) {
+ tcgetattr(STDERR_FILENO, &t);
+ t.c_lflag |= TOSTOP;
+ tcsetattr(STDERR_FILENO, TCSANOW, &t);
+#ifdef HAVE_IPC
+ pv_crs_needreinit();
+#endif
+ return;
+ }
+
+ gettimeofday(&tv, NULL);
+
+ pv_sig_toffset.tv_sec += (tv.tv_sec - pv__sig_tstp_time.tv_sec);
+ pv_sig_toffset.tv_usec += (tv.tv_usec - pv__sig_tstp_time.tv_usec);
+ if (pv_sig_toffset.tv_usec >= 1000000) {
+ pv_sig_toffset.tv_sec++;
+ pv_sig_toffset.tv_usec -= 1000000;
+ }
+ if (pv_sig_toffset.tv_usec < 0) {
+ pv_sig_toffset.tv_sec--;
+ pv_sig_toffset.tv_usec += 1000000;
+ }
+
+ pv__sig_tstp_time.tv_sec = 0;
+ pv__sig_tstp_time.tv_usec = 0;
+
+ if (pv__sig_old_stderr != -1) {
+ dup2(pv__sig_old_stderr, STDERR_FILENO);
+ close(pv__sig_old_stderr);
+ pv__sig_old_stderr = -1;
+ }
+
+ tcgetattr(STDERR_FILENO, &t);
+ t.c_lflag |= TOSTOP;
+ tcsetattr(STDERR_FILENO, TCSANOW, &t);
+
+#ifdef HAVE_IPC
+ pv_crs_needreinit();
+#endif
+}
+
+
+/*
+ * Handle SIGWINCH (window size changed) by setting a flag.
+ */
+static void pv__sig_winch(int s)
+{
+ pv_sig_newsize = 1;
+}
+
+
+/*
+ * Handle termination signals by setting the abort flag.
+ */
+static void pv__sig_term(int s)
+{
+ pv_sig_abort = 1;
+}
+
+
+/*
+ * Initialise signal handling.
+ */
+void pv_sig_init(void)
+{
+ struct sigaction sa;
+
+ pv__sig_old_stderr = -1;
+ pv__sig_tstp_time.tv_sec = 0;
+ pv__sig_tstp_time.tv_usec = 0;
+ pv_sig_toffset.tv_sec = 0;
+ 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, NULL);
+
+ /*
+ * 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, NULL);
+
+ /*
+ * 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, NULL);
+
+ /*
+ * 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, NULL);
+
+ /*
+ * 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, NULL);
+
+ /*
+ * 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, NULL);
+
+ sa.sa_handler = pv__sig_term;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGHUP, &sa, NULL);
+
+ sa.sa_handler = pv__sig_term;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+ sigaction(SIGTERM, &sa, 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 (pv__sig_old_stderr == -1)
+ return;
+
+ dup2(pv__sig_old_stderr, STDERR_FILENO);
+ close(pv__sig_old_stderr);
+ pv__sig_old_stderr = -1;
+
+ tcgetattr(STDERR_FILENO, &t);
+ t.c_lflag |= TOSTOP;
+ tcsetattr(STDERR_FILENO, TCSANOW, &t);
+
+#ifdef HAVE_IPC
+ pv_crs_needreinit();
+#endif
+}
+
+/* EOF */
diff --git a/src/pv/transfer.c b/src/pv/transfer.c
new file mode 100644
index 0000000..55d61b5
--- /dev/null
+++ b/src/pv/transfer.c
@@ -0,0 +1,341 @@
+/*
+ * Functions for transferring between file descriptors.
+ *
+ * Copyright 2012 Andrew Wood, distributed under the Artistic License 2.0.
+ */
+
+#include "options.h"
+
+#define BUFFER_SIZE 409600
+#define BUFFER_SIZE_MAX 524288
+
+#define MAXIMISE_BUFFER_FILL 1
+
+#define _GNU_SOURCE 1 /* for splice() */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+static unsigned long long pv__bufsize = BUFFER_SIZE;
+
+
+/*
+ * Set the buffer size for transfers.
+ */
+void pv_set_buffer_size(unsigned long long sz, int force)
+{
+ if ((sz > BUFFER_SIZE_MAX) && (!force))
+ sz = BUFFER_SIZE_MAX;
+ pv__bufsize = sz;
+}
+
+
+/*
+ * Transfer some data from "fd" to standard output, timing out after 9/100
+ * of a second. If opts->rate_limit 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
+ * opts->exit_status is updated). In line mode, the number of lines written
+ * will be put into *lineswritten.
+ *
+ * If "opts" is NULL, then the transfer buffer is freed, and zero is
+ * returned.
+ */
+long pv_transfer(opts_t opts, int fd, int *eof_in, int *eof_out,
+ unsigned long long allowed, long *lineswritten)
+{
+ static unsigned char *buf = NULL;
+ static unsigned long long buf_alloced = 0;
+ static unsigned long in_buffer = 0;
+ static unsigned long bytes_written = 0;
+ struct timeval tv;
+ fd_set readfds;
+ fd_set writefds;
+ int max_fd;
+ long to_write, written;
+ ssize_t r, w;
+#ifdef HAVE_SPLICE
+ static int splice_failed_fd = -1;
+ int splice_used = 0;
+#endif
+ int n;
+
+ if (opts == NULL) {
+ if (buf)
+ free(buf);
+ buf = NULL;
+ in_buffer = 0;
+ bytes_written = 0;
+ return 0;
+ }
+
+ if (buf == NULL) {
+ buf_alloced = pv__bufsize;
+ buf = (unsigned char *) malloc(pv__bufsize + 32);
+ if (buf == NULL) {
+ fprintf(stderr, "%s: %s: %s\n",
+ opts->program_name,
+ _("buffer allocation failed"),
+ strerror(errno));
+ opts->exit_status |= 64;
+ return -1;
+ }
+ }
+
+ /*
+ * Reallocate the buffer if the buffer size has changed mid-transfer.
+ */
+ if (buf_alloced < pv__bufsize) {
+ unsigned char *newptr;
+ newptr =
+ realloc( /* RATS: ignore */ buf, pv__bufsize + 32);
+ if (newptr == NULL) {
+ pv__bufsize = buf_alloced;
+ } else {
+ buf = newptr;
+ buf_alloced = pv__bufsize;
+ }
+ }
+
+ if ((opts->linemode) && (lineswritten != NULL))
+ *lineswritten = 0;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 90000;
+
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+
+ max_fd = 0;
+
+ if ((!(*eof_in)) && (in_buffer < pv__bufsize)) {
+ FD_SET(fd, &readfds);
+ if (fd > max_fd)
+ max_fd = fd;
+ }
+
+ to_write = in_buffer - bytes_written;
+ if (opts->rate_limit > 0) {
+ if (to_write > allowed) {
+ to_write = allowed;
+ }
+ }
+
+ if ((!(*eof_out)) && (to_write > 0)) {
+ FD_SET(STDOUT_FILENO, &writefds);
+ if (STDOUT_FILENO > max_fd)
+ max_fd = STDOUT_FILENO;
+ }
+
+ if ((*eof_in) && (*eof_out))
+ return 0;
+
+ n = select(max_fd + 1, &readfds, &writefds, NULL, &tv);
+
+ if (n < 0) {
+ if (errno == EINTR)
+ return 0;
+ fprintf(stderr, "%s: %s: %s: %d: %s\n",
+ opts->program_name, opts->current_file,
+ _("select call failed"), n, strerror(errno));
+ opts->exit_status |= 16;
+ return -1;
+ }
+
+ written = 0;
+
+ if (FD_ISSET(fd, &readfds)) {
+#ifdef HAVE_SPLICE
+ splice_used = 0;
+ if ((!opts->linemode) && (fd != splice_failed_fd)
+ && (to_write == 0)) {
+ r = splice(fd, NULL, STDOUT_FILENO, NULL, allowed,
+ SPLICE_F_MORE);
+ splice_used = 1;
+ if ((r < 0) && (errno == EINVAL)) {
+ splice_failed_fd = fd;
+ splice_used = 0;
+ } else if (r > 0) {
+ written = r;
+ } else {
+ /* EOF might not really be EOF, it seems */
+ splice_used = 0;
+ }
+ }
+ if (splice_used == 0) {
+ r = read( /* RATS: ignore (checked OK) */ fd,
+ buf + in_buffer, pv__bufsize - in_buffer);
+ }
+#else
+ r = read( /* RATS: ignore (checked OK) */ fd,
+ buf + in_buffer, pv__bufsize - in_buffer);
+#endif /* HAVE_SPLICE */
+ if (r < 0) {
+ /*
+ * 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 ((errno == EINTR) || (errno == EAGAIN)) {
+ tv.tv_sec = 0;
+ tv.tv_usec = 10000;
+ select(0, NULL, NULL, NULL, &tv);
+ return 0;
+ }
+ fprintf(stderr, "%s: %s: %s: %s\n",
+ opts->program_name,
+ opts->current_file,
+ _("read failed"), strerror(errno));
+ opts->exit_status |= 16;
+ *eof_in = 1;
+ if (bytes_written >= in_buffer)
+ *eof_out = 1;
+ } else if (r == 0) {
+ *eof_in = 1;
+ if (bytes_written >= in_buffer)
+ *eof_out = 1;
+ } else {
+#ifdef HAVE_SPLICE
+ if (splice_used == 0)
+ in_buffer += r;
+#else
+ in_buffer += r;
+#endif /* HAVE_SPLICE */
+
+ }
+ }
+
+ /*
+ * In line mode, only write up to and including the last newline,
+ * so that we're writing output line-by-line.
+ */
+ if ((to_write > 0) && (opts->linemode)) {
+ /*
+ * Guillaume Marcais: use strrchr to find last \n
+ */
+ unsigned char save;
+ char *start;
+ char *end;
+
+ save = buf[bytes_written + to_write];
+ buf[bytes_written + to_write] = 0;
+
+ start = (char *) (buf + bytes_written);
+ end = strrchr(start, '\n');
+ buf[bytes_written + to_write] = save;
+
+ if (end != NULL) {
+ to_write = (end - start) + 1;
+ }
+ }
+
+ if (FD_ISSET(STDOUT_FILENO, &writefds)
+#ifdef HAVE_SPLICE
+ && (splice_used == 0)
+#endif /* HAVE_SPLICE */
+ && (in_buffer > bytes_written)
+ && (to_write > 0)) {
+
+ signal(SIGALRM, SIG_IGN); /* RATS: ignore */
+ alarm(1);
+
+ w = write(STDOUT_FILENO, buf + bytes_written, to_write);
+
+ alarm(0);
+
+ if (w < 0) {
+ /*
+ * 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 ((errno == EINTR) || (errno == EAGAIN)) {
+ 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 (errno == EPIPE) {
+ *eof_in = 1;
+ *eof_out = 1;
+ return 0;
+ }
+ fprintf(stderr, "%s: %s: %s\n",
+ opts->program_name,
+ _("write failed"), strerror(errno));
+ opts->exit_status |= 16;
+ *eof_out = 1;
+ written = -1;
+ } else if (w == 0) {
+ *eof_out = 1;
+ } else {
+ if ((opts->linemode) && (lineswritten != NULL)) {
+ /*
+ * Guillaume Marcais: use strchr to count \n
+ */
+ unsigned char save;
+ char *ptr;
+ long lines = 0;
+
+ save = buf[bytes_written + w];
+ buf[bytes_written + w] = 0;
+ ptr = (char *) (buf + bytes_written - 1);
+
+ while ((ptr =
+ strchr((char *) (ptr + 1), '\n')))
+ ++lines;
+
+ *lineswritten += lines;
+ buf[bytes_written + w] = save;
+ }
+ bytes_written += w;
+ written += w;
+ if (bytes_written >= in_buffer) {
+ bytes_written = 0;
+ in_buffer = 0;
+ if (*eof_in)
+ *eof_out = 1;
+ }
+ }
+ }
+#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 (bytes_written > 0) {
+ if (bytes_written < in_buffer) {
+ memmove(buf, buf + bytes_written,
+ in_buffer - bytes_written);
+ in_buffer -= bytes_written;
+ bytes_written = 0;
+ } else {
+ bytes_written = 0;
+ in_buffer = 0;
+ }
+ }
+#endif /* MAXIMISE_BUFFER_FILL */
+ return written;
+}
+
+/* EOF */