diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/include/library/getopt.h | 38 | ||||
-rw-r--r-- | src/include/library/gettext.h | 53 | ||||
-rw-r--r-- | src/include/options.h | 56 | ||||
-rw-r--r-- | src/include/pv.h | 52 | ||||
-rw-r--r-- | src/library/getopt.c | 115 | ||||
-rw-r--r-- | src/library/gettext.c | 111 | ||||
-rw-r--r-- | src/main/help.c | 179 | ||||
-rw-r--r-- | src/main/main.c | 164 | ||||
-rw-r--r-- | src/main/options.c | 272 | ||||
-rw-r--r-- | src/main/remote.c | 208 | ||||
-rw-r--r-- | src/main/version.c | 37 | ||||
-rw-r--r-- | src/nls/de.po | 282 | ||||
-rw-r--r-- | src/nls/fr.po | 283 | ||||
-rw-r--r-- | src/nls/pl.po | 320 | ||||
-rw-r--r-- | src/nls/pt.po | 276 | ||||
-rw-r--r-- | src/nls/pv.pot | 271 | ||||
-rw-r--r-- | src/pv/cursor.c | 515 | ||||
-rw-r--r-- | src/pv/display.c | 635 | ||||
-rw-r--r-- | src/pv/file.c | 250 | ||||
-rw-r--r-- | src/pv/loop.c | 287 | ||||
-rw-r--r-- | src/pv/number.c | 206 | ||||
-rw-r--r-- | src/pv/signal.c | 290 | ||||
-rw-r--r-- | src/pv/transfer.c | 341 |
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 */ |