diff --git a/docs/NEWS.md b/docs/NEWS.md index 0409f3f..f53a80a 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -1,5 +1,6 @@ ### UNRELEASED + * feature: new "`--store-and-forward`" option to read input to a file first, then write it to the output ([#100](https://codeberg.org/a-j-wood/pv/issues/100)) * feature: new "`--stats`" option to show transfer stats at the end, like "`ping`" ([#49](https://codeberg.org/a-j-wood/pv/issues/49)) * feature: "`--rate`" can now be used with "`--numeric`" ([#17](https://codeberg.org/a-j-wood/pv/issues/17)) * feature: "`--gauge`" with "`--progress`" to show rate gauge when size is unknown ([#46](https://codeberg.org/a-j-wood/pv/issues/46)) diff --git a/docs/pv.1 b/docs/pv.1 index 095d971..5882b8b 100644 --- a/docs/pv.1 +++ b/docs/pv.1 @@ -491,6 +491,30 @@ except that is never called. Implies .BR \-C . .TP +.BI \-U\ FILE \fR,\ \fB\-\-store\-and\-forward\ FILE +Instead of passing data through immediately, do it in two stages - first +read all input and write it to +.IR FILE , +and then once the input is exhausted, read all of +.I FILE +and write it to the output. +.I FILE +remains in place afterwards, unless it is +"\fB-\fR", in which case +.B pv +creates a temporary file for this purpose, and automatically removes it +afterwards. +.TP +.B "" +This can be useful if you have a pipeline which generates data (your +input) quickly but you don't know the size, and you wish to pass it to some +slower process, once all of the input has been generated and you know its +size, so you can see its progress. Note that when doing this with +relatively small amounts of data, +.B \-\-no-splice +may be preferable so that pipe buffering doesn't affect the progress +display. +.TP .BI \-d\ PID\fR[\fB:\fR\fIFD\fR],\ \fB\-\-watchfd\ PID\fR[\fB:\fR\fIFD\fR] Instead of transferring data, watch file descriptor .I FD diff --git a/docs/pv.1.md b/docs/pv.1.md index 49ab081..53a519c 100644 --- a/docs/pv.1.md +++ b/docs/pv.1.md @@ -364,6 +364,23 @@ display types that are explicitly switched on will be shown. This is equivalent to redirecting standard output to */dev/null*, except that **write**(2) is never called. Implies **-C**. +**-U FILE, \--store-and-forward FILE** + +: Instead of passing data through immediately, do it in two stages - + first read all input and write it to *FILE*, and then once the input + is exhausted, read all of *FILE* and write it to the output. *FILE* + remains in place afterwards, unless it is \"**-**\", in which case + **pv** creates a temporary file for this purpose, and automatically + removes it afterwards. + +: This can be useful if you have a pipeline which generates data (your + input) quickly but you don\'t know the size, and you wish to pass it + to some slower process, once all of the input has been generated and + you know its size, so you can see its progress. Note that when doing + this with relatively small amounts of data, **\--no-splice** may be + preferable so that pipe buffering doesn\'t affect the progress + display. + **-d PID\[:FD\], \--watchfd PID\[:FD\]** : Instead of transferring data, watch file descriptor *FD* of process diff --git a/po/de.po b/po/de.po index 3aa0f06..21d95a3 100644 --- a/po/de.po +++ b/po/de.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: pv@ivarch.com\n" -"POT-Creation-Date: 2024-10-04 22:08+0100\n" +"POT-Creation-Date: 2024-10-05 16:04+0100\n" "PO-Revision-Date: 2024-09-29 11:07+0000\n" "Last-Translator: a-j-wood \n" "Language-Team: German \n" @@ -169,7 +169,8 @@ msgstr "" msgid "use cursor positioning escape sequences" msgstr "benutze Escape-Sequenzen zur Cursor-Positionierung" -#: src/main/help.c:369 src/main/help.c:405 src/main/help.c:420 +#: src/main/help.c:369 src/main/help.c:399 src/main/help.c:408 +#: src/main/help.c:423 msgid "FILE" msgstr "DATEI" @@ -222,117 +223,125 @@ msgid "discard input instead of writing to output" msgstr "verwirf die Eingabe anstatt in die Ausgabe zu schreiben" #: src/main/help.c:400 +msgid "write all input to FILE before writing to output" +msgstr "" + +#: src/main/help.c:403 msgid "PID" msgstr "PID" -#: src/main/help.c:401 +#: src/main/help.c:404 msgid "update settings of process PID" msgstr "Einstellungen der Prozess-PID updaten" -#: src/main/help.c:406 +#: src/main/help.c:409 msgid "save process ID in FILE" msgstr "Prozess-ID in DATEI speichern" -#: src/main/help.c:409 +#: src/main/help.c:412 msgid "PID[:FD]" msgstr "PID[:FD]" -#: src/main/help.c:410 +#: src/main/help.c:413 msgid "watch file FD opened by process PID" msgstr "die vom Prozess PID geöffnete Datei FD beobachten" -#: src/main/help.c:414 +#: src/main/help.c:417 msgid "show this help and exit" msgstr "zeige diese Hilfe und beende" -#: src/main/help.c:417 +#: src/main/help.c:420 msgid "show version information and exit" msgstr "zeige Versionsinformationen und beende" -#: src/main/help.c:421 +#: src/main/help.c:424 msgid "write debug logs to FILE" msgstr "schreibe Debug-Protokolle in DATEI" -#: src/main/help.c:446 +#: src/main/help.c:449 #, c-format msgid "Usage: %s [OPTION] [FILE]..." msgstr "Aufruf: %s [OPTION] [DATEI]..." -#: src/main/help.c:457 +#: src/main/help.c:460 msgid "" "Concatenate FILE(s), or standard input, to standard output, with monitoring." msgstr "" "Verbindet DATEI(en) oder den Standard-Eingabe-Kanal mit dem Standard-Ausgabe-" "Kanal und misst den Datenstrom." -#: src/main/help.c:594 +#: src/main/help.c:597 #, c-format msgid "Please report any bugs to: %s" msgstr "Bitte senden Sie Fehlerberichte an %s" -#: src/main/main.c:71 +#: src/main/main.c:214 +msgid "(input)" +msgstr "" + +#: src/main/main.c:299 msgid "state allocation failed" msgstr "Speicherzuweisung für `state' fehlgeschlagen" -#: src/main/options.c:138 src/pv/file.c:324 +#: src/main/options.c:140 src/pv/file.c:324 msgid "failed to stat file" msgstr "konnte Dateiinformationen nicht lesen" -#: src/main/options.c:152 +#: src/main/options.c:154 msgid "is a directory" msgstr "ist ein Verzeichnis" -#: src/main/options.c:181 +#: src/main/options.c:183 msgid "failed to generate sysfs filename" msgstr "konnte Sysfs-Dateinamen nicht erzeugen" -#: src/main/options.c:203 +#: src/main/options.c:205 msgid "failed to read sysfs size file" msgstr "konnte Größe der Sysfs-Datei nicht lesen" -#: src/main/options.c:223 +#: src/main/options.c:225 msgid "failed to open block device" msgstr "konnte Blockgerät nicht öffnen" -#: src/main/options.c:233 +#: src/main/options.c:235 msgid "failed to determine size of block device" msgstr "konnte Größe des Blockgeräts nicht ermitteln" -#: src/main/options.c:336 +#: src/main/options.c:339 msgid "option structure allocation failed" msgstr "Speicherzuweisung für `option' fehlgeschlagen" -#: src/main/options.c:355 +#: src/main/options.c:358 msgid "option structure argv allocation failed" msgstr "Speicherzuweisung für `argv' der Option fehlgeschlagen" -#: src/main/options.c:417 +#: src/main/options.c:420 msgid "integer argument expected" msgstr "Ganzzahliges Argument erwartet" -#: src/main/options.c:428 +#: src/main/options.c:431 msgid "numeric argument expected" msgstr "numerisches Argument erwartet" -#: src/main/options.c:439 +#: src/main/options.c:442 msgid "process ID or pid:fd pair expected" msgstr "Prozess-ID oder PID:FD-Paar erwartet" -#: src/main/options.c:446 +#: src/main/options.c:449 msgid "invalid process ID" msgstr "ungültige Prozess-ID" -#: src/main/options.c:655 +#: src/main/options.c:666 #, c-format msgid "Try `%s --help' for more information." msgstr "`%s --help' zeigt weitere Informationen an." -#: src/main/options.c:657 +#: src/main/options.c:668 #, c-format msgid "Try `%s -h' for more information." msgstr "`%s -h' zeigt weitere Informationen an." -#: src/main/options.c:688 +#: src/main/options.c:699 msgid "" "cannot use line mode or transfer modifier options when watching file " "descriptors" @@ -340,24 +349,24 @@ msgstr "" "Beim Beobachten von Dateideskriptoren können keine Optionen für den " "Zeilenmodus oder den Übertragungsmodifikator verwendet werden" -#: src/main/options.c:697 +#: src/main/options.c:708 msgid "cannot use cursor positioning when watching file descriptors" msgstr "" "Beim Beobachten von Dateideskriptoren kann Cursor-Positionierung nicht " "verwendet werden" -#: src/main/options.c:706 +#: src/main/options.c:717 msgid "cannot use remote control when watching file descriptors" msgstr "" "Beim Beobachten von Dateideskriptoren kann die Fernbedienung nicht verwenden " "werden" -#: src/main/options.c:715 +#: src/main/options.c:726 msgid "cannot transfer files when watching file descriptors" msgstr "" "Beim Beobachten von Dateideskriptoren können Dateien nicht übertragen werden" -#: src/main/options.c:730 +#: src/main/options.c:741 msgid "not available on systems without /proc/self/fdinfo" msgstr "Nicht verfügbar auf Systemen ohne /proc/self/fdinfo" @@ -401,45 +410,45 @@ msgstr "Sperrversuch fehlgeschlagen" msgid "failed to open terminal" msgstr "konnte Terminal nicht öffnen" -#: src/pv/display.c:216 +#: src/pv/display.c:230 msgid "yzafpnum kMGTPEZY" msgstr "yzafpnµm kMGTPEZY" -#: src/pv/display.c:234 +#: src/pv/display.c:248 msgid "yzafpnum KMGTPEZY" msgstr "yzafpnµm KMGTPEZY" -#: src/pv/display.c:661 src/pv/transfer.c:970 +#: src/pv/display.c:675 src/pv/transfer.c:970 msgid "buffer allocation failed" msgstr "Speicherzuweisung für den Puffer fehlgeschlagen" -#: src/pv/display.c:790 +#: src/pv/display.c:804 msgid "b" msgstr "b" -#: src/pv/display.c:793 src/pv/transfer.c:602 +#: src/pv/display.c:807 src/pv/transfer.c:602 msgid "B" msgstr "B" -#: src/pv/display.c:837 src/pv/display.c:852 src/pv/display.c:1071 -#: src/pv/loop.c:418 +#: src/pv/display.c:851 src/pv/display.c:866 src/pv/display.c:1085 +#: src/pv/loop.c:494 msgid "b/s" msgstr "b/s" -#: src/pv/display.c:841 src/pv/display.c:856 src/pv/display.c:1075 +#: src/pv/display.c:855 src/pv/display.c:870 src/pv/display.c:1089 msgid "/s" msgstr "/s" -#: src/pv/display.c:841 src/pv/display.c:856 src/pv/display.c:1075 -#: src/pv/loop.c:418 +#: src/pv/display.c:855 src/pv/display.c:870 src/pv/display.c:1089 +#: src/pv/loop.c:494 msgid "B/s" msgstr "B/s" -#: src/pv/display.c:884 src/pv/display.c:888 +#: src/pv/display.c:898 src/pv/display.c:902 msgid "ETA" msgstr "ETA" -#: src/pv/display.c:954 +#: src/pv/display.c:968 msgid "FIN" msgstr "" @@ -471,15 +480,15 @@ msgstr "(keine)" msgid "(stdin)" msgstr "(stdin)" -#: src/pv/loop.c:417 +#: src/pv/loop.c:493 msgid "rate min/avg/max/mdev" msgstr "" -#: src/pv/loop.c:429 +#: src/pv/loop.c:505 msgid "rate not measured" msgstr "" -#: src/pv/loop.c:635 src/pv/loop.c:686 src/pv/loop.c:727 src/pv/watchpid.c:99 +#: src/pv/loop.c:711 src/pv/loop.c:762 src/pv/loop.c:803 src/pv/watchpid.c:99 #: src/pv/watchpid.c:109 src/pv/watchpid.c:120 src/pv/watchpid.c:128 #: src/pv/watchpid.c:165 src/pv/watchpid.c:184 src/pv/watchpid.c:192 #: src/pv/watchpid.c:202 src/pv/watchpid.c:293 src/pv/watchpid.c:301 @@ -491,7 +500,7 @@ msgstr "pid" msgid "history structure allocation failed" msgstr "Speicherzuweisung für `history' fehlgeschlagen" -#: src/pv/state.c:470 src/pv/state.c:481 +#: src/pv/state.c:501 src/pv/state.c:512 msgid "file list allocation failed" msgstr "Speicherzuweisung für die Dateiliste fehlgeschlagen" diff --git a/po/fr.po b/po/fr.po index 128cec1..fbe7a1d 100644 --- a/po/fr.po +++ b/po/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: pv@ivarch.com\n" -"POT-Creation-Date: 2024-10-04 22:08+0100\n" +"POT-Creation-Date: 2024-10-05 16:04+0100\n" "Language: fr\n" "Content-Type: text/plain; charset=UTF-8\n" "Date: 1999-06-01 15:18:29+0100\n" @@ -162,7 +162,8 @@ msgstr "" msgid "use cursor positioning escape sequences" msgstr "utiliser les séquences d'échappements de positionnement de curseur" -#: src/main/help.c:369 src/main/help.c:405 src/main/help.c:420 +#: src/main/help.c:369 src/main/help.c:399 src/main/help.c:408 +#: src/main/help.c:423 msgid "FILE" msgstr "FICHIER" @@ -215,117 +216,125 @@ msgid "discard input instead of writing to output" msgstr "rejeter l'entrée au lieu de l'écrire vers la sortie" #: src/main/help.c:400 +msgid "write all input to FILE before writing to output" +msgstr "" + +#: src/main/help.c:403 msgid "PID" msgstr "PID" -#: src/main/help.c:401 +#: src/main/help.c:404 msgid "update settings of process PID" msgstr "mettre à jour la configuration du processus PID" -#: src/main/help.c:406 +#: src/main/help.c:409 msgid "save process ID in FILE" msgstr "enregistrer l'ID de processus dans FICHIER" -#: src/main/help.c:409 +#: src/main/help.c:412 msgid "PID[:FD]" msgstr "PID[:FD]" -#: src/main/help.c:410 +#: src/main/help.c:413 msgid "watch file FD opened by process PID" msgstr "surveiller le fichier FD ouvert par le processus PID" -#: src/main/help.c:414 +#: src/main/help.c:417 msgid "show this help and exit" msgstr "afficher cette aide puis quitter" -#: src/main/help.c:417 +#: src/main/help.c:420 msgid "show version information and exit" msgstr "afficher la version puis quitter" -#: src/main/help.c:421 +#: src/main/help.c:424 msgid "write debug logs to FILE" msgstr "écrire les journaux de déboggage vers FICHIER" -#: src/main/help.c:446 +#: src/main/help.c:449 #, c-format msgid "Usage: %s [OPTION] [FILE]..." msgstr "Utilisation : %s [OPTIONS] [FICHIER]..." -#: src/main/help.c:457 +#: src/main/help.c:460 msgid "" "Concatenate FILE(s), or standard input, to standard output, with monitoring." msgstr "" "Concaténer FICHIER(s), ou l'entrée standard, sur la sortie standard avec " "monitorage." -#: src/main/help.c:594 +#: src/main/help.c:597 #, c-format msgid "Please report any bugs to: %s" msgstr "SVP rapporter tous bogues à %s" -#: src/main/main.c:71 +#: src/main/main.c:214 +msgid "(input)" +msgstr "" + +#: src/main/main.c:299 msgid "state allocation failed" msgstr "échec de l'allocation de mémoire de condition" -#: src/main/options.c:138 src/pv/file.c:324 +#: src/main/options.c:140 src/pv/file.c:324 msgid "failed to stat file" msgstr "échec à statuer sur le fichier" -#: src/main/options.c:152 +#: src/main/options.c:154 msgid "is a directory" msgstr "" -#: src/main/options.c:181 +#: src/main/options.c:183 msgid "failed to generate sysfs filename" msgstr "" -#: src/main/options.c:203 +#: src/main/options.c:205 msgid "failed to read sysfs size file" msgstr "" -#: src/main/options.c:223 +#: src/main/options.c:225 msgid "failed to open block device" msgstr "" -#: src/main/options.c:233 +#: src/main/options.c:235 msgid "failed to determine size of block device" msgstr "" -#: src/main/options.c:336 +#: src/main/options.c:339 msgid "option structure allocation failed" msgstr "l'allocation pour la structure d'une option a échoué" -#: src/main/options.c:355 +#: src/main/options.c:358 msgid "option structure argv allocation failed" msgstr "l'allocation pour la structure de l'option argv a échoué" -#: src/main/options.c:417 +#: src/main/options.c:420 msgid "integer argument expected" msgstr "Valeur entière attendue" -#: src/main/options.c:428 +#: src/main/options.c:431 msgid "numeric argument expected" msgstr "Valeur numérique attendue" -#: src/main/options.c:439 +#: src/main/options.c:442 msgid "process ID or pid:fd pair expected" msgstr "ID de processus ou paire pid:fd attendue" -#: src/main/options.c:446 +#: src/main/options.c:449 msgid "invalid process ID" msgstr "ID de processus non valide" -#: src/main/options.c:655 +#: src/main/options.c:666 #, c-format msgid "Try `%s --help' for more information." msgstr "Essayez `%s --help' pour plus d'informations." -#: src/main/options.c:657 +#: src/main/options.c:668 #, c-format msgid "Try `%s -h' for more information." msgstr "Essayez `%s -h' pour plus d'informations." -#: src/main/options.c:688 +#: src/main/options.c:699 msgid "" "cannot use line mode or transfer modifier options when watching file " "descriptors" @@ -333,25 +342,25 @@ msgstr "" "impossible d'utiliser le mode ligne ou les options de modificateur de " "transfert lors de la visualisation des descripteurs de fichiers" -#: src/main/options.c:697 +#: src/main/options.c:708 msgid "cannot use cursor positioning when watching file descriptors" msgstr "" "impossible d'utiliser le positionnement du curseur lors de la visualisation " "des descripteurs de fichier" -#: src/main/options.c:706 +#: src/main/options.c:717 msgid "cannot use remote control when watching file descriptors" msgstr "" "impossible d'utiliser la télécommande lors de la visualisation des " "descripteurs de fichiers" -#: src/main/options.c:715 +#: src/main/options.c:726 msgid "cannot transfer files when watching file descriptors" msgstr "" "impossible de transférer des fichiers lorsque vous regardez des descripteurs " "de fichiers" -#: src/main/options.c:730 +#: src/main/options.c:741 msgid "not available on systems without /proc/self/fdinfo" msgstr "non disponible sur les systèmes sans /proc/self/fdinfo" @@ -397,45 +406,45 @@ msgstr "échec de la tentative de verrouillage" msgid "failed to open terminal" msgstr "échec de l'ouverture du terminal" -#: src/pv/display.c:216 +#: src/pv/display.c:230 msgid "yzafpnum kMGTPEZY" msgstr "" -#: src/pv/display.c:234 +#: src/pv/display.c:248 msgid "yzafpnum KMGTPEZY" msgstr "" -#: src/pv/display.c:661 src/pv/transfer.c:970 +#: src/pv/display.c:675 src/pv/transfer.c:970 msgid "buffer allocation failed" msgstr "échec de l'allocation de mémoire tampon" -#: src/pv/display.c:790 +#: src/pv/display.c:804 msgid "b" msgstr "" -#: src/pv/display.c:793 src/pv/transfer.c:602 +#: src/pv/display.c:807 src/pv/transfer.c:602 msgid "B" msgstr "O" -#: src/pv/display.c:837 src/pv/display.c:852 src/pv/display.c:1071 -#: src/pv/loop.c:418 +#: src/pv/display.c:851 src/pv/display.c:866 src/pv/display.c:1085 +#: src/pv/loop.c:494 msgid "b/s" msgstr "" -#: src/pv/display.c:841 src/pv/display.c:856 src/pv/display.c:1075 +#: src/pv/display.c:855 src/pv/display.c:870 src/pv/display.c:1089 msgid "/s" msgstr "/s" -#: src/pv/display.c:841 src/pv/display.c:856 src/pv/display.c:1075 -#: src/pv/loop.c:418 +#: src/pv/display.c:855 src/pv/display.c:870 src/pv/display.c:1089 +#: src/pv/loop.c:494 msgid "B/s" msgstr "O/s" -#: src/pv/display.c:884 src/pv/display.c:888 +#: src/pv/display.c:898 src/pv/display.c:902 msgid "ETA" msgstr "ETA" -#: src/pv/display.c:954 +#: src/pv/display.c:968 msgid "FIN" msgstr "" @@ -467,15 +476,15 @@ msgstr "(aucun)" msgid "(stdin)" msgstr "" -#: src/pv/loop.c:417 +#: src/pv/loop.c:493 msgid "rate min/avg/max/mdev" msgstr "" -#: src/pv/loop.c:429 +#: src/pv/loop.c:505 msgid "rate not measured" msgstr "" -#: src/pv/loop.c:635 src/pv/loop.c:686 src/pv/loop.c:727 src/pv/watchpid.c:99 +#: src/pv/loop.c:711 src/pv/loop.c:762 src/pv/loop.c:803 src/pv/watchpid.c:99 #: src/pv/watchpid.c:109 src/pv/watchpid.c:120 src/pv/watchpid.c:128 #: src/pv/watchpid.c:165 src/pv/watchpid.c:184 src/pv/watchpid.c:192 #: src/pv/watchpid.c:202 src/pv/watchpid.c:293 src/pv/watchpid.c:301 @@ -487,7 +496,7 @@ msgstr "" msgid "history structure allocation failed" msgstr "l'allocation de la structure de l'historique a échoué" -#: src/pv/state.c:470 src/pv/state.c:481 +#: src/pv/state.c:501 src/pv/state.c:512 msgid "file list allocation failed" msgstr "échec de l'allocation de la liste de fichiers" diff --git a/po/pl.po b/po/pl.po index ffddd4d..efe9e41 100644 --- a/po/pl.po +++ b/po/pl.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: pv@ivarch.com\n" -"POT-Creation-Date: 2024-10-04 22:08+0100\n" +"POT-Creation-Date: 2024-10-05 16:04+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -155,7 +155,8 @@ msgstr "pokaż wyjście nawet gdy błędy nie są typu terminal" msgid "use cursor positioning escape sequences" msgstr "używaj sekwencji escape do pozycjonowania" -#: src/main/help.c:369 src/main/help.c:405 src/main/help.c:420 +#: src/main/help.c:369 src/main/help.c:399 src/main/help.c:408 +#: src/main/help.c:423 msgid "FILE" msgstr "" @@ -208,135 +209,143 @@ msgid "discard input instead of writing to output" msgstr "" #: src/main/help.c:400 +msgid "write all input to FILE before writing to output" +msgstr "" + +#: src/main/help.c:403 msgid "PID" msgstr "" -#: src/main/help.c:401 +#: src/main/help.c:404 msgid "update settings of process PID" msgstr "" -#: src/main/help.c:406 +#: src/main/help.c:409 msgid "save process ID in FILE" msgstr "" -#: src/main/help.c:409 +#: src/main/help.c:412 msgid "PID[:FD]" msgstr "" -#: src/main/help.c:410 +#: src/main/help.c:413 msgid "watch file FD opened by process PID" msgstr "" -#: src/main/help.c:414 +#: src/main/help.c:417 msgid "show this help and exit" msgstr "wyświetl te informacje z pomocą i wyjdź" -#: src/main/help.c:417 +#: src/main/help.c:420 msgid "show version information and exit" msgstr "wyświetl informacje o wersji i wyjdź" -#: src/main/help.c:421 +#: src/main/help.c:424 msgid "write debug logs to FILE" msgstr "" -#: src/main/help.c:446 +#: src/main/help.c:449 #, c-format msgid "Usage: %s [OPTION] [FILE]..." msgstr "Sposób użycia: %s [OPCJA] [PLIK]..." -#: src/main/help.c:457 +#: src/main/help.c:460 msgid "" "Concatenate FILE(s), or standard input, to standard output, with monitoring." msgstr "" "Łączenie PLIK(ów) lub wejścia standardowego, do wyjścia standardowego z " "monitoringiem." -#: src/main/help.c:594 +#: src/main/help.c:597 #, c-format msgid "Please report any bugs to: %s" msgstr "Proszę przesyłać zgłoszenia błędów do %s" -#: src/main/main.c:71 +#: src/main/main.c:214 +msgid "(input)" +msgstr "" + +#: src/main/main.c:299 msgid "state allocation failed" msgstr "" -#: src/main/options.c:138 src/pv/file.c:324 +#: src/main/options.c:140 src/pv/file.c:324 msgid "failed to stat file" msgstr "nie udało się wykonać operacji stat na pliku" -#: src/main/options.c:152 +#: src/main/options.c:154 msgid "is a directory" msgstr "" -#: src/main/options.c:181 +#: src/main/options.c:183 msgid "failed to generate sysfs filename" msgstr "" -#: src/main/options.c:203 +#: src/main/options.c:205 msgid "failed to read sysfs size file" msgstr "" -#: src/main/options.c:223 +#: src/main/options.c:225 msgid "failed to open block device" msgstr "" -#: src/main/options.c:233 +#: src/main/options.c:235 msgid "failed to determine size of block device" msgstr "" -#: src/main/options.c:336 +#: src/main/options.c:339 msgid "option structure allocation failed" msgstr "nie udało się zaalokować struktur opcji" -#: src/main/options.c:355 +#: src/main/options.c:358 msgid "option structure argv allocation failed" msgstr "nie udało się zaalokować struktur opcji argv" -#: src/main/options.c:417 +#: src/main/options.c:420 msgid "integer argument expected" msgstr "" -#: src/main/options.c:428 +#: src/main/options.c:431 msgid "numeric argument expected" msgstr "" -#: src/main/options.c:439 +#: src/main/options.c:442 msgid "process ID or pid:fd pair expected" msgstr "" -#: src/main/options.c:446 +#: src/main/options.c:449 msgid "invalid process ID" msgstr "" -#: src/main/options.c:655 +#: src/main/options.c:666 #, c-format msgid "Try `%s --help' for more information." msgstr "Spróbuj `%s --help' by uzyskać więcej informacji" -#: src/main/options.c:657 +#: src/main/options.c:668 #, c-format msgid "Try `%s -h' for more information." msgstr "Spróbuj `%s -h' by uzyskać więcej informacji" -#: src/main/options.c:688 +#: src/main/options.c:699 msgid "" "cannot use line mode or transfer modifier options when watching file " "descriptors" msgstr "" -#: src/main/options.c:697 +#: src/main/options.c:708 msgid "cannot use cursor positioning when watching file descriptors" msgstr "" -#: src/main/options.c:706 +#: src/main/options.c:717 msgid "cannot use remote control when watching file descriptors" msgstr "" -#: src/main/options.c:715 +#: src/main/options.c:726 msgid "cannot transfer files when watching file descriptors" msgstr "" -#: src/main/options.c:730 +#: src/main/options.c:741 msgid "not available on systems without /proc/self/fdinfo" msgstr "" @@ -381,45 +390,45 @@ msgstr "" msgid "failed to open terminal" msgstr "nie udało się otworzyć terminala" -#: src/pv/display.c:216 +#: src/pv/display.c:230 msgid "yzafpnum kMGTPEZY" msgstr "" -#: src/pv/display.c:234 +#: src/pv/display.c:248 msgid "yzafpnum KMGTPEZY" msgstr "" -#: src/pv/display.c:661 src/pv/transfer.c:970 +#: src/pv/display.c:675 src/pv/transfer.c:970 msgid "buffer allocation failed" msgstr "nie udało się zaalokować bufora" -#: src/pv/display.c:790 +#: src/pv/display.c:804 msgid "b" msgstr "" -#: src/pv/display.c:793 src/pv/transfer.c:602 +#: src/pv/display.c:807 src/pv/transfer.c:602 msgid "B" msgstr "B" -#: src/pv/display.c:837 src/pv/display.c:852 src/pv/display.c:1071 -#: src/pv/loop.c:418 +#: src/pv/display.c:851 src/pv/display.c:866 src/pv/display.c:1085 +#: src/pv/loop.c:494 msgid "b/s" msgstr "" -#: src/pv/display.c:841 src/pv/display.c:856 src/pv/display.c:1075 +#: src/pv/display.c:855 src/pv/display.c:870 src/pv/display.c:1089 msgid "/s" msgstr "/s" -#: src/pv/display.c:841 src/pv/display.c:856 src/pv/display.c:1075 -#: src/pv/loop.c:418 +#: src/pv/display.c:855 src/pv/display.c:870 src/pv/display.c:1089 +#: src/pv/loop.c:494 msgid "B/s" msgstr "B/s" -#: src/pv/display.c:884 src/pv/display.c:888 +#: src/pv/display.c:898 src/pv/display.c:902 msgid "ETA" msgstr "ETA" -#: src/pv/display.c:954 +#: src/pv/display.c:968 msgid "FIN" msgstr "" @@ -452,15 +461,15 @@ msgstr "" msgid "(stdin)" msgstr "" -#: src/pv/loop.c:417 +#: src/pv/loop.c:493 msgid "rate min/avg/max/mdev" msgstr "" -#: src/pv/loop.c:429 +#: src/pv/loop.c:505 msgid "rate not measured" msgstr "" -#: src/pv/loop.c:635 src/pv/loop.c:686 src/pv/loop.c:727 src/pv/watchpid.c:99 +#: src/pv/loop.c:711 src/pv/loop.c:762 src/pv/loop.c:803 src/pv/watchpid.c:99 #: src/pv/watchpid.c:109 src/pv/watchpid.c:120 src/pv/watchpid.c:128 #: src/pv/watchpid.c:165 src/pv/watchpid.c:184 src/pv/watchpid.c:192 #: src/pv/watchpid.c:202 src/pv/watchpid.c:293 src/pv/watchpid.c:301 @@ -472,7 +481,7 @@ msgstr "" msgid "history structure allocation failed" msgstr "" -#: src/pv/state.c:470 src/pv/state.c:481 +#: src/pv/state.c:501 src/pv/state.c:512 msgid "file list allocation failed" msgstr "" diff --git a/po/pt.po b/po/pt.po index 6f0d2ee..ca2872b 100644 --- a/po/pt.po +++ b/po/pt.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: pv@ivarch.com\n" -"POT-Creation-Date: 2024-10-04 22:08+0100\n" +"POT-Creation-Date: 2024-10-05 16:04+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -155,7 +155,8 @@ msgstr "gera dados mesmo que a saída de erro seja redirecionada" msgid "use cursor positioning escape sequences" msgstr "utiliza caracteres de escape para posicionar o cursor" -#: src/main/help.c:369 src/main/help.c:405 src/main/help.c:420 +#: src/main/help.c:369 src/main/help.c:399 src/main/help.c:408 +#: src/main/help.c:423 msgid "FILE" msgstr "" @@ -208,117 +209,125 @@ msgid "discard input instead of writing to output" msgstr "" #: src/main/help.c:400 +msgid "write all input to FILE before writing to output" +msgstr "" + +#: src/main/help.c:403 msgid "PID" msgstr "" -#: src/main/help.c:401 +#: src/main/help.c:404 msgid "update settings of process PID" msgstr "atualizar as configurações do processo PID" -#: src/main/help.c:406 +#: src/main/help.c:409 msgid "save process ID in FILE" msgstr "salvar ID do processo em FILE" -#: src/main/help.c:409 +#: src/main/help.c:412 msgid "PID[:FD]" msgstr "" -#: src/main/help.c:410 +#: src/main/help.c:413 msgid "watch file FD opened by process PID" msgstr "assistir arquivo FD aberto pelo processo PID" -#: src/main/help.c:414 +#: src/main/help.c:417 msgid "show this help and exit" msgstr "exibe esta tela de ajuda e termina" -#: src/main/help.c:417 +#: src/main/help.c:420 msgid "show version information and exit" msgstr "exibe a versão e termina" -#: src/main/help.c:421 +#: src/main/help.c:424 msgid "write debug logs to FILE" msgstr "" -#: src/main/help.c:446 +#: src/main/help.c:449 #, c-format msgid "Usage: %s [OPTION] [FILE]..." msgstr "Uso: %s [OPÇÕES] [ARQUIVOS]..." -#: src/main/help.c:457 +#: src/main/help.c:460 msgid "" "Concatenate FILE(s), or standard input, to standard output, with monitoring." msgstr "" "Concatena ARQUIVO(s) ou a entrada padrão e grava na saída padrão, com " "monitoramento." -#: src/main/help.c:594 +#: src/main/help.c:597 #, c-format msgid "Please report any bugs to: %s" msgstr "Por favor, reporte quaisquer defeitos para %s" -#: src/main/main.c:71 +#: src/main/main.c:214 +msgid "(input)" +msgstr "" + +#: src/main/main.c:299 msgid "state allocation failed" msgstr "alocação de memória de status falhou" -#: src/main/options.c:138 src/pv/file.c:324 +#: src/main/options.c:140 src/pv/file.c:324 msgid "failed to stat file" msgstr "erro obtendo informações do arquivo" -#: src/main/options.c:152 +#: src/main/options.c:154 msgid "is a directory" msgstr "" -#: src/main/options.c:181 +#: src/main/options.c:183 msgid "failed to generate sysfs filename" msgstr "" -#: src/main/options.c:203 +#: src/main/options.c:205 msgid "failed to read sysfs size file" msgstr "" -#: src/main/options.c:223 +#: src/main/options.c:225 msgid "failed to open block device" msgstr "" -#: src/main/options.c:233 +#: src/main/options.c:235 msgid "failed to determine size of block device" msgstr "" -#: src/main/options.c:336 +#: src/main/options.c:339 msgid "option structure allocation failed" msgstr "erro na alocação da estrutura de opções" -#: src/main/options.c:355 +#: src/main/options.c:358 msgid "option structure argv allocation failed" msgstr "erro na alocação da estrutura de opções argv" -#: src/main/options.c:417 +#: src/main/options.c:420 msgid "integer argument expected" msgstr "argumento inteiro esperado" -#: src/main/options.c:428 +#: src/main/options.c:431 msgid "numeric argument expected" msgstr "argumento numérico esperado" -#: src/main/options.c:439 +#: src/main/options.c:442 msgid "process ID or pid:fd pair expected" msgstr "ID do processo ou par pid:fd esperado" -#: src/main/options.c:446 +#: src/main/options.c:449 msgid "invalid process ID" msgstr "ID de processo inválido" -#: src/main/options.c:655 +#: src/main/options.c:666 #, c-format msgid "Try `%s --help' for more information." msgstr "Tente `%s --help' para maiores informações." -#: src/main/options.c:657 +#: src/main/options.c:668 #, c-format msgid "Try `%s -h' for more information." msgstr "Tente `%s -h' para maiores informações." -#: src/main/options.c:688 +#: src/main/options.c:699 msgid "" "cannot use line mode or transfer modifier options when watching file " "descriptors" @@ -326,21 +335,21 @@ msgstr "" "não pode usar o modo de linha ou opções de modificador de transferência ao " "assistir os descritores de arquivo" -#: src/main/options.c:697 +#: src/main/options.c:708 msgid "cannot use cursor positioning when watching file descriptors" msgstr "" "não pode usar o posicionamento do cursor ao observar os descritores de " "arquivo" -#: src/main/options.c:706 +#: src/main/options.c:717 msgid "cannot use remote control when watching file descriptors" msgstr "não pode usar o controle remoto ao assistir os descritores de arquivo" -#: src/main/options.c:715 +#: src/main/options.c:726 msgid "cannot transfer files when watching file descriptors" msgstr "não pode transferir arquivos ao assistir descritores de arquivo" -#: src/main/options.c:730 +#: src/main/options.c:741 msgid "not available on systems without /proc/self/fdinfo" msgstr "não disponível em sistemas sem /proc/self/fdinfo" @@ -384,45 +393,45 @@ msgstr "tentativa de bloqueio falhou" msgid "failed to open terminal" msgstr "erro abrindo o terminal" -#: src/pv/display.c:216 +#: src/pv/display.c:230 msgid "yzafpnum kMGTPEZY" msgstr "" -#: src/pv/display.c:234 +#: src/pv/display.c:248 msgid "yzafpnum KMGTPEZY" msgstr "" -#: src/pv/display.c:661 src/pv/transfer.c:970 +#: src/pv/display.c:675 src/pv/transfer.c:970 msgid "buffer allocation failed" msgstr "erro alocando o buffer" -#: src/pv/display.c:790 +#: src/pv/display.c:804 msgid "b" msgstr "" -#: src/pv/display.c:793 src/pv/transfer.c:602 +#: src/pv/display.c:807 src/pv/transfer.c:602 msgid "B" msgstr "" -#: src/pv/display.c:837 src/pv/display.c:852 src/pv/display.c:1071 -#: src/pv/loop.c:418 +#: src/pv/display.c:851 src/pv/display.c:866 src/pv/display.c:1085 +#: src/pv/loop.c:494 msgid "b/s" msgstr "" -#: src/pv/display.c:841 src/pv/display.c:856 src/pv/display.c:1075 +#: src/pv/display.c:855 src/pv/display.c:870 src/pv/display.c:1089 msgid "/s" msgstr "" -#: src/pv/display.c:841 src/pv/display.c:856 src/pv/display.c:1075 -#: src/pv/loop.c:418 +#: src/pv/display.c:855 src/pv/display.c:870 src/pv/display.c:1089 +#: src/pv/loop.c:494 msgid "B/s" msgstr "B/s" -#: src/pv/display.c:884 src/pv/display.c:888 +#: src/pv/display.c:898 src/pv/display.c:902 msgid "ETA" msgstr "ETA" -#: src/pv/display.c:954 +#: src/pv/display.c:968 msgid "FIN" msgstr "" @@ -454,15 +463,15 @@ msgstr "" msgid "(stdin)" msgstr "" -#: src/pv/loop.c:417 +#: src/pv/loop.c:493 msgid "rate min/avg/max/mdev" msgstr "" -#: src/pv/loop.c:429 +#: src/pv/loop.c:505 msgid "rate not measured" msgstr "" -#: src/pv/loop.c:635 src/pv/loop.c:686 src/pv/loop.c:727 src/pv/watchpid.c:99 +#: src/pv/loop.c:711 src/pv/loop.c:762 src/pv/loop.c:803 src/pv/watchpid.c:99 #: src/pv/watchpid.c:109 src/pv/watchpid.c:120 src/pv/watchpid.c:128 #: src/pv/watchpid.c:165 src/pv/watchpid.c:184 src/pv/watchpid.c:192 #: src/pv/watchpid.c:202 src/pv/watchpid.c:293 src/pv/watchpid.c:301 @@ -474,7 +483,7 @@ msgstr "" msgid "history structure allocation failed" msgstr "" -#: src/pv/state.c:470 src/pv/state.c:481 +#: src/pv/state.c:501 src/pv/state.c:512 msgid "file list allocation failed" msgstr "" diff --git a/src/include/options.h b/src/include/options.h index b2f5412..4d0c5bb 100644 --- a/src/include/options.h +++ b/src/include/options.h @@ -36,6 +36,7 @@ struct opts_s { /*@keep@*/ /*@null@*/ char *name; /* display name, if any */ /*@keep@*/ /*@null@*/ char *format; /* output format, if any */ /*@keep@*/ /*@null@*/ char *pidfile; /* PID file, if any */ + /*@keep@*/ /*@null@*/ char *store_and_forward_file; /* store and forward file, if any */ /*@keep@*/ /*@null@*/ const char **argv; /* array of non-option arguments */ size_t lastwritten; /* show N bytes last written */ off_t rate_limit; /* rate limit, in bytes per second */ diff --git a/src/include/pv-internal.h b/src/include/pv-internal.h index 865f630..693ad15 100644 --- a/src/include/pv-internal.h +++ b/src/include/pv-internal.h @@ -230,13 +230,13 @@ struct pvstate_s { /* Keep track of progress over last intervals to compute current average rate. */ /*@null@*/ struct { /* state at previous intervals (circular buffer) */ long double elapsed_sec; /* time since start of transfer */ - off_t total_written; /* amount transferred by that time */ + off_t transferred; /* amount transferred by that time */ } *history; size_t history_len; /* total size of history array */ size_t history_first; /* index of oldest entry */ size_t history_last; /* index of newest entry */ - off_t prev_total_written; /* total amount transferred when called last time */ + off_t prev_transferred; /* total amount transferred when called last time */ int percentage; /* transfer percentage completion */ } calc; @@ -290,7 +290,10 @@ struct pvstate_s { ssize_t to_write; /* max to write this time around */ ssize_t written; /* bytes sent to stdout this time */ - off_t total_written; /* total bytes or lines transferred */ + size_t written_but_not_consumed; /* bytes in the output pipe, unread */ + + off_t total_written; /* total bytes or lines written */ + off_t transferred; /* amount transferred (written - unconsumed) */ /* * While reading from a file descriptor we keep track of how diff --git a/src/include/pv.h b/src/include/pv.h index cfc9f6f..f468b9a 100644 --- a/src/include/pv.h +++ b/src/include/pv.h @@ -27,6 +27,7 @@ extern "C" { * Exit status bitmask values. */ #define PV_ERROREXIT_REMOTE_OR_PID 1 +#define PV_ERROREXIT_SAF 1 /* store and forward error */ #define PV_ERROREXIT_ACCESS 2 #define PV_ERROREXIT_OUROBOROS 4 #define PV_ERROREXIT_TRANSITION 8 @@ -163,6 +164,11 @@ void pv_nanosleep(long long); */ extern /*@null@*/ /*@only@*/ pvstate_t pv_state_alloc(const char *); +/* + * Clear the calculated parts of a state structure. + */ +extern void pv_state_reset(pvstate_t state); + /* * Set the formatting string, given a set of old-style formatting options. */ diff --git a/src/main/help.c b/src/main/help.c index eda41ae..e0c8533 100644 --- a/src/main/help.c +++ b/src/main/help.c @@ -396,6 +396,9 @@ void display_help(void) { "-X", "--discard", NULL, N_("discard input instead of writing to output"), { 0, 0, 0, 0} }, + { "-U", "--store-and-forward", N_("FILE"), + N_("write all input to FILE before writing to output"), + { 0, 0, 0, 0} }, #ifdef SA_SIGINFO { "-R", "--remote", N_("PID"), N_("update settings of process PID"), diff --git a/src/main/main.c b/src/main/main.c index f3034e5..46550aa 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -25,6 +25,233 @@ int pv_remote_set(opts_t, pvstate_t); void pv_remote_init(void); void pv_remote_fini(void); +/* + * Write a PID file, returning nonzero on error. + */ +static int pv__write_pidfile(opts_t opts) +{ + char *pidfile_tmp_name; + size_t pidfile_tmp_bufsize; + int pidfile_tmp_fd; + FILE *pidfile_tmp_fptr; + mode_t prev_umask; + + if (NULL == opts->pidfile) + return 0; + + pidfile_tmp_bufsize = 16 + strlen(opts->pidfile); /* flawfinder: ignore */ + /* + * flawfinder rationale: pidfile was supplied as an argument + * so we have to assume it is \0 terminated. + */ + pidfile_tmp_name = malloc(pidfile_tmp_bufsize); + if (NULL == pidfile_tmp_name) { + fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno)); + return PV_ERROREXIT_REMOTE_OR_PID; + } + memset(pidfile_tmp_name, 0, pidfile_tmp_bufsize); + (void) pv_snprintf(pidfile_tmp_name, pidfile_tmp_bufsize, "%s.XXXXXX", opts->pidfile); + + /*@-type@ *//* splint doesn't like mode_t */ + prev_umask = umask(0000); /* flawfinder: ignore */ + (void) umask(prev_umask | 0133); /* flawfinder: ignore */ + + /*@-unrecog@ *//* splint doesn't know mkstemp() */ + pidfile_tmp_fd = mkstemp(pidfile_tmp_name); /* flawfinder: ignore */ + /*@+unrecog@ */ + if (pidfile_tmp_fd < 0) { + fprintf(stderr, "%s: %s: %s\n", opts->program_name, pidfile_tmp_name, strerror(errno)); + (void) umask(prev_umask); /* flawfinder: ignore */ + free(pidfile_tmp_name); + return PV_ERROREXIT_REMOTE_OR_PID; + } + + (void) umask(prev_umask); /* flawfinder: ignore */ + + /* + * flawfinder rationale (umask, mkstemp) - flawfinder + * recommends setting the most restrictive umask possible + * when calling mkstemp(), so this is what we have done. + * + * We get the original umask and OR it with 0133 to make + * sure new files will be at least chmod 644. Then we put + * the umask back to what it was, after creating the + * temporary file. + */ + + /*@+type@ */ + + pidfile_tmp_fptr = fdopen(pidfile_tmp_fd, "w"); + if (NULL == pidfile_tmp_fptr) { + fprintf(stderr, "%s: %s: %s\n", opts->program_name, pidfile_tmp_name, strerror(errno)); + (void) close(pidfile_tmp_fd); + (void) remove(pidfile_tmp_name); + free(pidfile_tmp_name); + return PV_ERROREXIT_REMOTE_OR_PID; + } + + fprintf(pidfile_tmp_fptr, "%d\n", getpid()); + if (0 != fclose(pidfile_tmp_fptr)) { + fprintf(stderr, "%s: %s: %s\n", opts->program_name, opts->pidfile, strerror(errno)); + } + + if (rename(pidfile_tmp_name, opts->pidfile) < 0) { + fprintf(stderr, "%s: %s: %s\n", opts->program_name, opts->pidfile, strerror(errno)); + (void) remove(pidfile_tmp_name); + } + + free(pidfile_tmp_name); + + return 0; +} + + +/* + * Set the output file, if applicable. Returns nonzero on error. + */ +static int pv__set_output(pvstate_t state, opts_t opts, /*@null@ */ const char *output_file) +{ + int output_fd; + + if ((NULL == state) || (NULL == opts)) + return 0; + + if (NULL == output_file || 0 == strcmp(output_file, "-")) { + pv_state_output_set(state, STDOUT_FILENO, "(stdout)"); + return 0; + } + + output_fd = open(output_file, O_WRONLY | O_CREAT | O_TRUNC, 0600); /* flawfinder: ignore */ + /* + * flawfinder rationale: the output filename has been + * explicitly provided, and in many cases the operator will + * want to write to device files and other special + * destinations, so there is no sense-checking we can do to + * make this safer. + */ + if (output_fd < 0) { + fprintf(stderr, "%s: %s: %s\n", opts->program_name, output_file, strerror(errno)); + return PV_ERROREXIT_ACCESS; + } + + pv_state_output_set(state, output_fd, output_file); + return 0; +} + + +/* + * Run in store-and-forward mode: run the main loop once with the output + * forced to the store-and-forward file (taking care of creation and removal + * of a temporary file if "-" was specified); then run the main loop again + * with the input file list forced to be just the store-and-forward file. + * Returns nonzero on error. + */ +static int pv__store_and_forward(pvstate_t state, opts_t opts, bool can_have_eta) +{ + char tmp_filename[4096]; /* flawfinder: ignore */ + bool use_temporary_file; + char *real_store_and_forward_file; + int retcode; + + /* flawfinder: zeroed with memset and bounded by pv_snprintf. */ + + if ((NULL == state) || (NULL == opts) || (NULL == opts->store_and_forward_file)) + return 0; + + memset(tmp_filename, 0, sizeof(tmp_filename)); + + use_temporary_file = false; + if (0 == strcmp(opts->store_and_forward_file, "-")) + use_temporary_file = true; + + /* + * Create a temporary file if the specified file was "-". + */ + if (use_temporary_file) { + char *tmpdir; + int tmp_fd; + + tmpdir = (char *) getenv("TMPDIR"); /* flawfinder: ignore */ + if ((NULL == tmpdir) || ('\0' == tmpdir[0])) + tmpdir = (char *) getenv("TMP"); /* flawfinder: ignore */ + if ((NULL == tmpdir) || ('\0' == tmpdir[0])) + tmpdir = "/tmp"; + + /* + * flawfinder rationale: null and zero-size values of $TMPDIR and + * $TMP are rejected, and the destination buffer is bounded. + */ + + (void) pv_snprintf(tmp_filename, sizeof(tmp_filename), "%s/pv.XXXXXX", tmpdir); + /*@-unrecog@ *//* splint doesn't know mkstemp() */ + tmp_fd = mkstemp(tmp_filename); /* flawfinder: ignore */ + /*@+unrecog@ */ + if (tmp_fd < 0) { + fprintf(stderr, "%s: %s: %s\n", opts->program_name, tmp_filename, strerror(errno)); + return PV_ERROREXIT_SAF; + } + (void) close(tmp_fd); + } + + /* + * Real store-and-forward file: either the one we were given, or the + * temporary file we created if we were given "-". + */ + real_store_and_forward_file = use_temporary_file ? tmp_filename : opts->store_and_forward_file; + + /* + * First, set the output file to the store-and-forward file. + */ + debug("%s: %s", "setting output to store-and-forward file", real_store_and_forward_file); + retcode = pv__set_output(state, opts, real_store_and_forward_file); + if (0 != retcode) + goto end_store_and_forward; + + /* Reset the formatting to set the displayed name to "(input)". */ + /*@-mustfreefresh@ */ + pv_state_set_format(state, opts->progress, opts->timer, can_have_eta ? opts->eta : false, + can_have_eta ? opts->fineta : false, opts->rate, opts->average_rate, + opts->bytes, opts->bufpercent, opts->lastwritten, _("(input)")); + /*@+mustfreefresh@ *//* see below about gettext _() calls. */ + + /* Run the main loop as normal. */ + debug("%s", "running store-and-forward receiver"); + retcode = pv_main_loop(state); + if (0 != retcode) + goto end_store_and_forward; + + /* Set the output file back to what it originally was. */ + debug("%s: %s", "setting output to original value", NULL == opts->output ? "(null)" : opts->output); + retcode = pv__set_output(state, opts, opts->output); + if (0 != retcode) + goto end_store_and_forward; + + /* Replace the list of input files with the store-and-forward file. */ + debug("%s", "resetting input file list"); + pv_state_inputfiles(state, 1, (const char **) &real_store_and_forward_file); + + /* Recalculate the input size. */ + pv_state_size_set(state, pv_calc_total_size(state)); + + /* Reset the format, since we might have been asked to show ETA. */ + pv_state_set_format(state, opts->progress, opts->timer, opts->eta, + opts->fineta, opts->rate, opts->average_rate, + opts->bytes, opts->bufpercent, opts->lastwritten, opts->name); + + /* Reset calculated values in the state. */ + pv_state_reset(state); + + /* Run the main loop again. */ + debug("%s", "running store-and-forward transmitter"); + retcode = pv_main_loop(state); + + end_store_and_forward: + if (use_temporary_file) + (void) remove(tmp_filename); + + return retcode; +} + /* * Process command-line arguments and set option flags, then call functions @@ -35,6 +262,7 @@ int main(int argc, char **argv) /*@only@ */ opts_t opts = NULL; /*@only@ */ pvstate_t state = NULL; int retcode = 0; + bool can_have_eta = true; #ifdef ENABLE_NLS /* Initialise language translation. */ @@ -96,80 +324,13 @@ int main(int argc, char **argv) * Write a PID file if -P was specified. */ if (opts->pidfile != NULL) { - char *pidfile_tmp_name; - size_t pidfile_tmp_bufsize; - int pidfile_tmp_fd; - FILE *pidfile_tmp_fptr; - mode_t prev_umask; - - pidfile_tmp_bufsize = 16 + strlen(opts->pidfile); /* flawfinder: ignore */ - /* - * flawfinder rationale: pidfile was supplied as an argument - * so we have to assume it is \0 terminated. - */ - pidfile_tmp_name = malloc(pidfile_tmp_bufsize); - if (NULL == pidfile_tmp_name) { - fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno)); + int pidfile_rc; + pidfile_rc = pv__write_pidfile(opts); + if (0 != pidfile_rc) { pv_state_free(state); opts_free(opts); - return PV_ERROREXIT_REMOTE_OR_PID; + return pidfile_rc; } - memset(pidfile_tmp_name, 0, pidfile_tmp_bufsize); - (void) pv_snprintf(pidfile_tmp_name, pidfile_tmp_bufsize, "%s.XXXXXX", opts->pidfile); - - /*@-type@ *//* splint doesn't like mode_t */ - prev_umask = umask(0000); /* flawfinder: ignore */ - (void) umask(prev_umask | 0133); /* flawfinder: ignore */ - - /*@-unrecog@ *//* splint doesn't know mkstemp() */ - pidfile_tmp_fd = mkstemp(pidfile_tmp_name); /* flawfinder: ignore */ - /*@+unrecog@ */ - if (pidfile_tmp_fd < 0) { - fprintf(stderr, "%s: %s: %s\n", opts->program_name, pidfile_tmp_name, strerror(errno)); - (void) umask(prev_umask); /* flawfinder: ignore */ - free(pidfile_tmp_name); - pv_state_free(state); - opts_free(opts); - return PV_ERROREXIT_REMOTE_OR_PID; - } - - (void) umask(prev_umask); /* flawfinder: ignore */ - - /* - * flawfinder rationale (umask, mkstemp) - flawfinder - * recommends setting the most restrictive umask possible - * when calling mkstemp(), so this is what we have done. - * - * We get the original umask and OR it with 0133 to make - * sure new files will be at least chmod 644. Then we put - * the umask back to what it was, after creating the - * temporary file. - */ - - /*@+type@ */ - - pidfile_tmp_fptr = fdopen(pidfile_tmp_fd, "w"); - if (NULL == pidfile_tmp_fptr) { - fprintf(stderr, "%s: %s: %s\n", opts->program_name, pidfile_tmp_name, strerror(errno)); - (void) close(pidfile_tmp_fd); - (void) remove(pidfile_tmp_name); - free(pidfile_tmp_name); - pv_state_free(state); - opts_free(opts); - return PV_ERROREXIT_REMOTE_OR_PID; - } - - fprintf(pidfile_tmp_fptr, "%d\n", getpid()); - if (0 != fclose(pidfile_tmp_fptr)) { - fprintf(stderr, "%s: %s: %s\n", opts->program_name, opts->pidfile, strerror(errno)); - } - - if (rename(pidfile_tmp_name, opts->pidfile) < 0) { - fprintf(stderr, "%s: %s: %s\n", opts->program_name, opts->pidfile, strerror(errno)); - (void) remove(pidfile_tmp_name); - } - - free(pidfile_tmp_name); } /* @@ -246,24 +407,11 @@ int main(int argc, char **argv) * calculation looks at the output file if the input size can't be * calculated (issue #91). */ - if (NULL == opts->output || 0 == strcmp(opts->output, "-")) { - pv_state_output_set(state, STDOUT_FILENO, "(stdout)"); - } else { - int fd = open(opts->output, O_WRONLY | O_CREAT | O_TRUNC, 0600); /* flawfinder: ignore */ - /* - * flawfinder rationale: the output filename has been - * explicitly provided, and in many cases the operator will - * want to write to device files and other special - * destinations, so there is no sense-checking we can do to - * make this safer. - */ - if (fd < 0) { - fprintf(stderr, "%s: %s: %s\n", opts->program_name, opts->output, strerror(errno)); - pv_state_free(state); - opts_free(opts); - return PV_ERROREXIT_ACCESS; - } - pv_state_output_set(state, fd, opts->output); + retcode = pv__set_output(state, opts, opts->output); + if (0 != retcode) { + pv_state_free(state); + opts_free(opts); + return retcode; } /* @@ -290,7 +438,7 @@ int main(int argc, char **argv) * If the size is unknown, we cannot have an ETA. */ if (opts->size < 1) { - opts->eta = false; + can_have_eta = false; debug("%s", "size unknown - ETA disabled"); } } @@ -330,19 +478,24 @@ int main(int argc, char **argv) pv_state_watch_fd_set(state, opts->watch_fd); pv_state_average_rate_window_set(state, opts->average_rate_window); - pv_state_set_format(state, opts->progress, opts->timer, opts->eta, - opts->fineta, opts->rate, opts->average_rate, + pv_state_set_format(state, opts->progress, opts->timer, can_have_eta ? opts->eta : false, + can_have_eta ? opts->fineta : false, opts->rate, opts->average_rate, opts->bytes, opts->bufpercent, opts->lastwritten, opts->name); /* Initialise the signal handling. */ pv_sig_init(state); /* Run the appropriate main loop. */ - if (0 == opts->watch_pid) { + if (0 == opts->watch_pid && NULL == opts->store_and_forward_file) { /* Normal "transfer data" mode. */ pv_remote_init(); retcode = pv_main_loop(state); pv_remote_fini(); + } else if (0 == opts->watch_pid && NULL != opts->store_and_forward_file) { + /* Store-and-forward transfer mode. */ + pv_remote_init(); + retcode = pv__store_and_forward(state, opts, can_have_eta); + pv_remote_fini(); } else if (0 != opts->watch_pid && -1 == opts->watch_fd) { /* "Watch all file descriptors of another process" mode. */ retcode = pv_watchpid_loop(state); diff --git a/src/main/options.c b/src/main/options.c index c54c9bb..f6bd831 100644 --- a/src/main/options.c +++ b/src/main/options.c @@ -55,6 +55,8 @@ void opts_free( /*@only@ */ opts_t opts) free(opts->pidfile); if (NULL != opts->output) free(opts->output); + if (NULL != opts->store_and_forward_file) + free(opts->store_and_forward_file); if (NULL != opts->argv) free(opts->argv); /*@+keeptrans@ */ @@ -301,6 +303,7 @@ opts_t opts_parse(unsigned int argc, char **argv) { "sync", 0, NULL, (int) 'Y' }, { "direct-io", 0, NULL, (int) 'K' }, { "discard", 0, NULL, (int) 'X' }, + { "store-and-forward", 1, NULL, (int) 'U' }, { "remote", 1, NULL, (int) 'R' }, { "pidfile", 1, NULL, (int) 'P' }, { "watchfd", 1, NULL, (int) 'd' }, @@ -314,7 +317,7 @@ opts_t opts_parse(unsigned int argc, char **argv) /*@+nullassign@ */ int option_index = 0; #endif /* HAVE_GETOPT_LONG */ - char *short_options = "hVpteIrab8kTA:fvnqcWD:s:gl0i:w:H:N:F:L:B:CEZ:SYKXR:P:d:m:o:" + char *short_options = "hVpteIrab8kTA:fvnqcWD:s:gl0i:w:H:N:F:L:B:CEZ:SYKXU:R:P:d:m:o:" #ifdef ENABLE_DEBUGGING "!:" #endif @@ -605,6 +608,14 @@ opts_t opts_parse(unsigned int argc, char **argv) opts->discard_input = true; opts->no_splice = true; break; + case 'U': + opts->store_and_forward_file = pv_strdup(optarg); + if (NULL == opts->store_and_forward_file) { + fprintf(stderr, "%s: -U: %s\n", opts->program_name, strerror(errno)); + opts_free(opts); + return NULL; + } + break; case 'R': opts->remote = pv_getnum_count(optarg, false); break; diff --git a/src/pv/calc.c b/src/pv/calc.c index 5272a25..25ebe0c 100644 --- a/src/pv/calc.c +++ b/src/pv/calc.c @@ -51,12 +51,12 @@ static void pv__update_average_rate_history(pvstate_t state, long double rate) } state->calc.history[last].elapsed_sec = state->transfer.elapsed_seconds; - state->calc.history[last].total_written = state->transfer.total_written; + state->calc.history[last].transferred = state->transfer.transferred; if (first == last) { state->calc.current_avg_rate = rate; } else { - off_t bytes = (state->calc.history[last].total_written - state->calc.history[first].total_written); + off_t bytes = (state->calc.history[last].transferred - state->calc.history[first].transferred); long double sec = (state->calc.history[last].elapsed_sec - state->calc.history[first].elapsed_sec); state->calc.current_avg_rate = (long double) bytes / sec; } @@ -85,9 +85,9 @@ void pv_calculate_transfer_rate(pvstate_t state, bool final) return; bytes_since_last = 0; - if (state->transfer.total_written >= 0) { - bytes_since_last = state->transfer.total_written - state->calc.prev_total_written; - state->calc.prev_total_written = state->transfer.total_written; + if (state->transfer.transferred >= 0) { + bytes_since_last = state->transfer.transferred - state->calc.prev_transferred; + state->calc.prev_transferred = state->transfer.transferred; } /* @@ -138,7 +138,7 @@ void pv_calculate_transfer_rate(pvstate_t state, bool final) if (state->transfer.elapsed_seconds < 0.000001) state->transfer.elapsed_seconds = 0.000001; average_rate = - (((long double) state->transfer.total_written) - + (((long double) (state->transfer.transferred)) - ((long double) state->display.initial_offset)) / (long double) (state->transfer.elapsed_seconds); transfer_rate = average_rate; } @@ -160,7 +160,7 @@ void pv_calculate_transfer_rate(pvstate_t state, bool final) if (state->calc.percentage > 199) state->calc.percentage = 0; } else { - state->calc.percentage = pv_percentage(state->transfer.total_written, state->control.size); + state->calc.percentage = pv_percentage(state->transfer.transferred, state->control.size); } /* Ensure the percentage is never negative or huge. */ diff --git a/src/pv/display.c b/src/pv/display.c index 8e4fc2b..7fcf595 100644 --- a/src/pv/display.c +++ b/src/pv/display.c @@ -119,14 +119,28 @@ void pv_write_retry(int fd, const char *buf, size_t count) /* - * Write the given buffer to the terminal with pv_write_retry(), unless + * Write the given buffer to the terminal, like pv_write_retry(), unless * stderr is suspended. */ void pv_tty_write(pvstate_t state, const char *buf, size_t count) { - if (1 == state->flag.suspend_stderr) - return; - pv_write_retry(STDERR_FILENO, buf, count); + while (0 == state->flag.suspend_stderr && count > 0) { + ssize_t nwritten; + + nwritten = write(STDERR_FILENO, buf, count); + + if (nwritten < 0) { + if ((EINTR == errno) || (EAGAIN == errno)) { + continue; + } + return; + } + if (nwritten < 1) + return; + + count -= nwritten; + buf += nwritten; + } } @@ -706,7 +720,7 @@ bool pv_format(pvstate_t state, bool final) if (state->display.component[PV_COMPONENT_BYTES].required) { (void) pv_snprintf(msg_bytes, sizeof(msg_bytes), "%s%lld", first_item ? "" : " ", - (long long) ((state->control.bits ? 8 : 1) * state->transfer.total_written)); + (long long) ((state->control.bits ? 8 : 1) * state->transfer.transferred)); first_item = false; show_percentage = false; } @@ -787,10 +801,10 @@ bool pv_format(pvstate_t state, bool final) /*@-mustfreefresh @ */ if (state->control.bits && !state->control.linemode) { pv__sizestr(component_content, component_buf_size, "%s", - (long double) (state->transfer.total_written * 8), "", _("b"), count_type); + (long double) (state->transfer.transferred * 8), "", _("b"), count_type); } else { pv__sizestr(component_content, component_buf_size, "%s", - (long double) (state->transfer.total_written), "", _("B"), count_type); + (long double) (state->transfer.transferred), "", _("B"), count_type); } /*@+mustfreefresh @ */ /* splint: we trust gettext() not to really leak memory. */ @@ -862,7 +876,7 @@ bool pv_format(pvstate_t state, bool final) case PV_COMPONENT_ETA: /* Estimated time remaining until completion - if size is known. */ eta = - pv__seconds_remaining((state->transfer.total_written - state->display.initial_offset), + pv__seconds_remaining((state->transfer.transferred - state->display.initial_offset), state->control.size - state->display.initial_offset, state->calc.current_avg_rate); @@ -917,7 +931,7 @@ bool pv_format(pvstate_t state, bool final) */ eta = - pv__seconds_remaining(state->transfer.total_written - state->display.initial_offset, + pv__seconds_remaining(state->transfer.transferred - state->display.initial_offset, state->control.size - state->display.initial_offset, state->calc.current_avg_rate); diff --git a/src/pv/loop.c b/src/pv/loop.c index eb69cd5..1949ab8 100644 --- a/src/pv/loop.c +++ b/src/pv/loop.c @@ -68,6 +68,7 @@ int pv_main_loop(pvstate_t state) struct timespec init_time, next_remotecheck, transfer_elapsed; int input_fd, output_fd; unsigned int file_idx; + bool output_is_pipe; /* * "written" is ALWAYS bytes written by the last transfer. @@ -88,6 +89,35 @@ int pv_main_loop(pvstate_t state) if (output_fd < 0) output_fd = STDOUT_FILENO; + /* Determine whether the output is a pipe. */ + output_is_pipe = false; + { + struct stat sb; + memset(&sb, 0, sizeof(sb)); + if (0 == fstat(output_fd, &sb)) { + /*@-type@*/ + if ((sb.st_mode & S_IFMT) == S_IFIFO) { + output_is_pipe = true; + debug("%s", "output is a pipe"); + } + /*@+type@*/ /* splint says st_mode is __mode_t, not mode_t */ + } else { + debug("%s(%d): %s", "fstat", output_fd, strerror(errno)); + } + } + + /* + * Note that we could reduce the size of the output pipe buffer like + * this, to avoid the case where we write a load of data and it just + * goes into the buffer so we think we're done, but the consumer + * takes ages to process it: + * + * fcntl(output_fd, F_SETPIPE_SZ, 4096); + * + * If we can peek at how much has been consumed by the other end, we + * don't need to. + */ + pv_crs_init(state); eof_in = false; @@ -95,6 +125,7 @@ int pv_main_loop(pvstate_t state) state->transfer.total_written = 0; lineswritten = 0; state->display.initial_offset = 0; + state->transfer.written_but_not_consumed = 0; memset(&cur_time, 0, sizeof(cur_time)); memset(&start_time, 0, sizeof(start_time)); @@ -147,8 +178,7 @@ int pv_main_loop(pvstate_t state) /* * Set or clear O_DIRECT on the output. */ - if (0 != - fcntl(output_fd, F_SETFL, (state->control.direct_io ? O_DIRECT : 0) | fcntl(output_fd, F_GETFL))) { + if (0 != fcntl(output_fd, F_SETFL, (state->control.direct_io ? O_DIRECT : 0) | fcntl(output_fd, F_GETFL))) { debug("%s: %s", "fcntl", strerror(errno)); } state->control.direct_io_changed = false; @@ -176,6 +206,11 @@ int pv_main_loop(pvstate_t state) if (0 == state->control.target_buffer_size) state->control.target_buffer_size = BUFFER_SIZE; + /* + * Repeat until eof_in is true, eof_out is true, and final_update is + * true. + */ + while ((!(eof_in && eof_out)) || (!final_update)) { cansend = 0; @@ -249,6 +284,36 @@ int pv_main_loop(pvstate_t state) target -= written; } +#ifdef FIONREAD + /* + * If writing to a pipe, look at how much is sitting in the + * pipe buffer waiting for the receiver to read. + */ + if (output_is_pipe) { + int nbytes; + nbytes = 0; + if (0 == ioctl(output_fd, FIONREAD, &nbytes)) { + if (nbytes >= 0) { + if (((size_t) nbytes) != state->transfer.written_but_not_consumed) + debug("%s: %d", "written_but_not_consumed is now", nbytes); + state->transfer.written_but_not_consumed = (size_t) nbytes; + } else { + debug("%s: %d", "FIONREAD gave a negative byte count", nbytes); + state->transfer.written_but_not_consumed = 0; + } + } else { + debug("%s(%d,%s): %s", "ioctl", output_fd, "FIONREAD", strerror(errno)); + state->transfer.written_but_not_consumed = 0; + } + } +#endif + + state->transfer.transferred = state->transfer.total_written; + /* TODO: work out what to do in line mode. */ + if (output_is_pipe && !state->control.linemode) { + state->transfer.transferred -= state->transfer.written_but_not_consumed; + } + /* * EOF, and files remain - advance to the next file. */ @@ -268,8 +333,12 @@ int pv_main_loop(pvstate_t state) /* Now check the current time. */ pv_elapsedtime_read(&cur_time); - /* If full EOF, final update, and force a display updaate. */ - if (eof_in && eof_out) { + /* + * If we've read everything and written everything, and the + * output pipe buffer is empty, then set the final update + * flag, and force a display update. + */ + if (eof_in && eof_out && 0 == state->transfer.written_but_not_consumed) { final_update = true; if ((state->display.display_visible) || (state->control.delay_start < 0.001)) { diff --git a/src/pv/state.c b/src/pv/state.c index 3938037..3d202d8 100644 --- a/src/pv/state.c +++ b/src/pv/state.c @@ -46,6 +46,58 @@ static void pv_alloc_history(pvstate_t state) state->calc.history[0].elapsed_sec = 0.0; /* to be safe, memset() not recommended for doubles */ } + +/* + * Clear the calculated parts of a state structure. + */ +void pv_state_reset(pvstate_t state) +{ + if (NULL == state) + return; + + state->flag.reparse_display = 1; + state->status.current_input_file = -1; + + state->display.initial_offset = 0; + state->display.display_visible = false; + + /* + * Explicitly set important floating point values to 0, as memset() + * is not recommended for this. + */ + state->calc.transfer_rate = 0.0; + state->calc.average_rate = 0.0; + state->calc.prev_elapsed_sec = 0.0; + state->calc.prev_rate = 0.0; + state->calc.prev_trans = 0.0; + state->calc.current_avg_rate = 0.0; + state->calc.rate_min = 0.0; + state->calc.rate_max = 0.0; + state->calc.rate_sum = 0.0; + state->calc.ratesquared_sum = 0.0; + state->calc.measurements_taken = 0; + state->calc.prev_transferred = 0; + state->calc.percentage = 0; + state->calc.history_first = state->calc.history_last = 0; + if (NULL != state->calc.history) { + state->calc.history[0].elapsed_sec = 0.0; + } + + state->transfer.elapsed_seconds = 0.0; + state->transfer.read_position = 0; + state->transfer.write_position = 0; + state->transfer.to_write = 0; + state->transfer.written = 0; + state->transfer.total_written = 0; + state->transfer.written_but_not_consumed = 0; + state->transfer.read_errors_in_a_row = 0; + state->transfer.last_read_skip_fd = 0; +#ifdef HAVE_SPLICE + state->transfer.splice_failed_fd = -1; +#endif /* HAVE_SPLICE */ +} + + /* * Create a new state structure, and return it, or 0 (NULL) on error. */ @@ -78,28 +130,7 @@ pvstate_t pv_state_alloc(const char *program_name) #endif /* HAVE_IPC */ state->cursor.lock_fd = -1; - state->flag.reparse_display = 1; - state->status.current_input_file = -1; -#ifdef HAVE_SPLICE - state->transfer.splice_failed_fd = -1; -#endif /* HAVE_SPLICE */ - state->display.display_visible = false; - - /* - * Explicitly set important floating point values to 0, as memset() - * is not recommended for this. - */ - state->calc.transfer_rate = 0.0; - state->calc.average_rate = 0.0; - state->calc.prev_elapsed_sec = 0.0; - state->calc.prev_rate = 0.0; - state->calc.prev_trans = 0.0; - state->calc.current_avg_rate = 0.0; - state->calc.rate_min = 0.0; - state->calc.rate_max = 0.0; - state->calc.rate_sum = 0.0; - state->calc.ratesquared_sum = 0.0; - state->transfer.elapsed_seconds = 0.0; + pv_state_reset(state); /* * Get the current working directory, if possible, as a base for