New option "--direct-io".

This commit is contained in:
Andrew Wood
2023-07-27 21:44:30 +01:00
parent 9f07a28065
commit ae81f83e9b
18 changed files with 262 additions and 45 deletions
+3 -1
View File
@@ -39,7 +39,9 @@ AC_SYS_LARGEFILE
AC_HEADER_TIOCGWINSZ
AC_CHECK_FUNCS(getopt_long getopt)
AC_CHECK_HEADERS(getopt.h)
AC_CHECK_FUNCS(memcpy basename vsnprintf strlcat fdatasync)
AC_CHECK_FUNCS(memcpy basename vsnprintf strlcat)
AC_CHECK_FUNCS(fdatasync)
AC_CHECK_FUNCS(fpathconf sysconf posix_memalign)
AC_CHECK_HEADERS(limits.h)
AC_ARG_ENABLE(debugging,
+9
View File
@@ -24,6 +24,9 @@
/* Define to 1 if you have the `fdatasync' function. */
#undef HAVE_FDATASYNC
/* Define to 1 if you have the `fpathconf' function. */
#undef HAVE_FPATHCONF
/* Define to 1 if you have the `getopt' function. */
#undef HAVE_GETOPT
@@ -63,6 +66,9 @@
/* Define to 1 if you have the <minix/config.h> header file. */
#undef HAVE_MINIX_CONFIG_H
/* Define to 1 if you have the `posix_memalign' function. */
#undef HAVE_POSIX_MEMALIGN
/* Define to 1 if you have the `splice' function. */
#undef HAVE_SPLICE
@@ -87,6 +93,9 @@
/* Define to 1 if you have the `strlcat' function. */
#undef HAVE_STRLCAT
/* Define to 1 if you have the `sysconf' function. */
#undef HAVE_SYSCONF
/* Define to 1 if you have the <sys/ipc.h> header file. */
#undef HAVE_SYS_IPC_H
+2 -1
View File
@@ -1,5 +1,6 @@
0.0.20230726-UNRELEASED
0.0.20230727-UNRELEASED
* feature: new "`--sync`" option to flush cache to disk after every write (related to [GH#6](https://github.com/a-j-wood/pv/issues/6), to improve accuracy when writing to slow disks)
* feature: new "`--direct-io`" option to bypass cache - implements [GH#29 "Option to enable *O_DIRECT*"](https://github.com/a-j-wood/pv/issues/29) - requested by Romain Kang, Jacek Wielemborek
* fix: correct byte prefix size to 2 spaces in rate display, so progress display size remains constant at low transfer rates
* cleanup: rewrote `configure.in` as per suggestions in newer "`autoconf`" manuals
* cleanup: replaced `header.in` with one generated by "`autoheader`", moving custom logic to a separate header file "`config-aux.h`"
-1
View File
@@ -26,7 +26,6 @@ Feature requests
* ([GH#17](https://github.com/a-j-wood/pv/issues/17)) Allow "`-r`" with "`-l`" and "`-n`" to output lines/sec (Roland Kletzing)
* ([GH#22](https://github.com/a-j-wood/pv/issues/22)) Options to skip input and seek on output (Jason A. Pfeil, Feb 2022)
* ([GH#25](https://github.com/a-j-wood/pv/issues/25)) Normalise progress to 100% on overrun (Andrej Gantvorg)
* ([GH#29](https://github.com/a-j-wood/pv/issues/29)) Option to enable *O_DIRECT* (Romain Kang, Jacek Wielemborek)
* ([GH#35](https://github.com/a-j-wood/pv/issues/35)) Allow decimal values for "`-s`", "`-L`", "`-B`" (Thomas Watson - Aug 2020)
* ([GH#36](https://github.com/a-j-wood/pv/issues/36)) Ignore *SIGWINCH* (window size change) if "`-w`" / "`-H`" provided
* ([GH#37](https://github.com/a-j-wood/pv/issues/37)) Allow "`-E`" to take a block size argument so errors cause a skip to the next block (Anthony DeRobertis - Oct 2016)
+1 -1
View File
@@ -1 +1 @@
0.0.20230726-UNRELEASED
0.0.20230727-UNRELEASED
+15 -2
View File
@@ -402,6 +402,17 @@ This has no effect when the output is a pipe. Using
.B \-Y
may improve the accuracy of the progress bar when writing to a slow disk.
.TP
.B \-K, \-\-direct-io
Set the
.I O_DIRECT
flag on all inputs and outputs, if it is available. This will minimise
the effect of caches, at the cost of performance. Due to memory alignment
requirements, it also may cause read or write failures with an error of
"Invalid argument", especially if reading and writing files across a variety
of filesystems in a single
.B @PACKAGE@
call. Use this option with caution.
.TP
.B \-d PID[:FD], \-\-watchfd PID[:FD]
Instead of transferring data, watch file descriptor
.B FD
@@ -661,8 +672,10 @@ home page: <http://www.ivarch.com/programs/pv.shtml>
.SH "SEE ALSO"
.BR cat (1),
.BR dialog (1),
.BR splice (2)
.BR splice (2),
.BR open (2)
(for
.IR O_DIRECT )
.SH LICENSE
This is free software, distributed under the ARTISTIC 2.0 license.
+1
View File
@@ -44,6 +44,7 @@ struct opts_s { /* structure describing run-time options */
unsigned int skip_errors; /* skip read errors counter */
bool stop_at_size; /* set if we stop at "size" bytes */
bool sync_after_write; /* set if we sync after every write */
bool direct_io; /* set if O_DIRECT is to be used */
double interval; /* interval between updates */
double delay_start; /* delay before first display */
unsigned int watch_pid; /* process to watch fds of */
+2
View File
@@ -94,6 +94,8 @@ struct pvstate_s {
unsigned int skip_errors; /* skip read errors counter */
bool stop_at_size; /* set if we stop at "size" bytes */
bool sync_after_write; /* set if we sync after every write */
bool direct_io; /* set if O_DIRECT is to be used */
bool direct_io_changed; /* set when direct_io is changed */
bool no_splice; /* never use splice() */
unsigned long long rate_limit; /* rate limit, in bytes per second */
unsigned long long target_buffer_size; /* buffer size (0=default) */
+1
View File
@@ -107,6 +107,7 @@ extern void pv_state_no_op_set(pvstate_t, bool);
extern void pv_state_skip_errors_set(pvstate_t, unsigned int);
extern void pv_state_stop_at_size_set(pvstate_t, bool);
extern void pv_state_sync_after_write_set(pvstate_t, bool);
extern void pv_state_direct_io_set(pvstate_t, bool);
extern void pv_state_rate_limit_set(pvstate_t, unsigned long long);
extern void pv_state_target_buffer_size_set(pvstate_t, unsigned long long);
extern void pv_state_no_splice_set(pvstate_t, bool);
+2
View File
@@ -93,6 +93,8 @@ void display_help(void)
N_("stop after --size bytes have been transferred")},
{"-Y", "--sync", 0,
N_("flush cache to disk after every write")},
{"-K", "--direct-io", 0,
N_("use direct I/O to bypass cache")},
#ifdef HAVE_IPC
{"-R", "--remote", N_("PID"),
N_("update settings of process PID")},
+1
View File
@@ -202,6 +202,7 @@ int main(int argc, char **argv)
pv_state_skip_errors_set(state, opts->skip_errors);
pv_state_stop_at_size_set(state, opts->stop_at_size);
pv_state_sync_after_write_set(state, opts->sync_after_write);
pv_state_direct_io_set(state, opts->direct_io);
pv_state_rate_limit_set(state, opts->rate_limit);
pv_state_target_buffer_size_set(state, opts->buffer_size);
pv_state_no_splice_set(state, opts->no_splice);
+5 -1
View File
@@ -83,6 +83,7 @@ opts_t opts_parse(int argc, char **argv)
{"skip-errors", 0, NULL, (int) 'E'},
{"stop-at-size", 0, NULL, (int) 'S'},
{"sync", 0, NULL, (int) 'Y'},
{"direct-io", 0, NULL, (int) 'K'},
{"remote", 1, NULL, (int) 'R'},
{"pidfile", 1, NULL, (int) 'P'},
{"watchfd", 1, NULL, (int) 'd'},
@@ -92,7 +93,7 @@ opts_t opts_parse(int argc, char **argv)
int option_index = 0;
#endif
char *short_options =
"hVpteIrab8TA:fnqcWD:s:l0i:w:H:N:F:L:B:CESYR:P:d:m:";
"hVpteIrab8TA:fnqcWD:s:l0i:w:H:N:F:L:B:CESYKR:P:d:m:";
int c, numopts;
unsigned int check_pid;
int check_fd;
@@ -337,6 +338,9 @@ opts_t opts_parse(int argc, char **argv)
case 'Y':
opts->sync_after_write = true;
break;
case 'K':
opts->direct_io = true;
break;
case 'R':
opts->remote = pv_getnum_ui(optarg);
break;
+37 -33
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-26 22:09+0100\n"
"POT-Creation-Date: 2023-07-27 20:49+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"
@@ -44,7 +44,7 @@ msgstr ""
msgid "failed to read file"
msgstr ""
#: src/pv/file.c:253 src/main/options.c:292
#: src/pv/file.c:253 src/main/options.c:293
msgid "failed to stat file"
msgstr ""
@@ -84,11 +84,11 @@ msgstr ""
msgid "write failed"
msgstr ""
#: src/pv/transfer.c:726 src/pv/display.c:646
#: src/pv/transfer.c:808 src/pv/display.c:646
msgid "buffer allocation failed"
msgstr ""
#: src/pv/transfer.c:817
#: src/pv/transfer.c:913
msgid "select call failed"
msgstr ""
@@ -96,7 +96,7 @@ msgstr ""
#: src/pv/watchpid.c:110 src/pv/watchpid.c:151 src/pv/watchpid.c:168
#: src/pv/watchpid.c:178 src/pv/watchpid.c:192 src/pv/watchpid.c:280
#: src/pv/watchpid.c:289 src/pv/watchpid.c:322 src/pv/watchpid.c:343
#: src/pv/loop.c:533 src/pv/loop.c:595 src/pv/loop.c:647
#: src/pv/loop.c:542 src/pv/loop.c:604 src/pv/loop.c:656
msgid "pid"
msgstr ""
@@ -331,55 +331,59 @@ msgid "flush cache to disk after every write"
msgstr ""
#: src/main/help.c:97
msgid "use direct I/O to bypass cache"
msgstr ""
#: src/main/help.c:99
msgid "PID"
msgstr ""
#: src/main/help.c:98
#: src/main/help.c:100
msgid "update settings of process PID"
msgstr ""
#: src/main/help.c:101
#: src/main/help.c:103
msgid "FILE"
msgstr ""
#: src/main/help.c:102
#: src/main/help.c:104
msgid "save process ID in FILE"
msgstr ""
#: src/main/help.c:104
#: src/main/help.c:106
msgid "PID[:FD]"
msgstr ""
#: src/main/help.c:105
#: src/main/help.c:107
msgid "watch file FD opened by process PID"
msgstr ""
#: src/main/help.c:108
#: src/main/help.c:110
msgid "show this help and exit"
msgstr ""
#: src/main/help.c:110
#: src/main/help.c:112
msgid "show version information and exit"
msgstr ""
#: src/main/help.c:117
#: src/main/help.c:119
#, c-format
msgid "Usage: %s [OPTION] [FILE]..."
msgstr ""
#: src/main/help.c:120
#: src/main/help.c:122
msgid ""
"Concatenate FILE(s), or standard input, to standard output,\n"
"with monitoring."
msgstr ""
#: src/main/help.c:213
#: src/main/help.c:215
msgid ""
"Debugging is enabled; export the DEBUG environment variable to define the\n"
"output filename.\n"
msgstr ""
#: src/main/help.c:217
#: src/main/help.c:219
#, c-format
msgid "Please report any bugs to %s."
msgstr ""
@@ -388,62 +392,62 @@ msgstr ""
msgid "state allocation failed"
msgstr ""
#: src/main/main.c:251
#: src/main/main.c:252
msgid "failed to read terminal attributes"
msgstr ""
#: src/main/options.c:105
#: src/main/options.c:106
msgid "option structure allocation failed"
msgstr ""
#: src/main/options.c:119
#: src/main/options.c:120
msgid "option structure argv allocation failed"
msgstr ""
#: src/main/options.c:164
#: src/main/options.c:165
msgid "integer argument expected"
msgstr ""
#: src/main/options.c:175
#: src/main/options.c:176
msgid "numeric argument expected"
msgstr ""
#: src/main/options.c:186
#: src/main/options.c:187
msgid "process ID or pid:fd pair expected"
msgstr ""
#: src/main/options.c:193
#: src/main/options.c:194
msgid "invalid process ID"
msgstr ""
#: src/main/options.c:362
#, c-format
msgid "Try `%s --help' for more information."
msgstr ""
#: src/main/options.c:366
#, c-format
msgid "Try `%s --help' for more information."
msgstr ""
#: src/main/options.c:370
#, c-format
msgid "Try `%s -h' for more information."
msgstr ""
#: src/main/options.c:382
#: src/main/options.c:386
msgid ""
"cannot use line mode or transfer modifier options when watching file "
"descriptors"
msgstr ""
#: src/main/options.c:390
#: src/main/options.c:394
msgid "cannot use cursor positioning when watching file descriptors"
msgstr ""
#: src/main/options.c:398
#: src/main/options.c:402
msgid "cannot use remote control when watching file descriptors"
msgstr ""
#: src/main/options.c:406
#: src/main/options.c:410
msgid "cannot transfer files when watching file descriptors"
msgstr ""
#: src/main/options.c:414
#: src/main/options.c:418
msgid "not available on systems without /proc/self/fdinfo"
msgstr ""
+12
View File
@@ -293,6 +293,18 @@ int pv_next_file(pvstate_t state, int filenum, int oldfd)
if (0 == strcmp(state->input_files[filenum], "-")) {
state->current_file = "(stdin)";
}
#ifdef O_DIRECT
/*
* Set or clear O_DIRECT on the file descriptor.
*/
fcntl(fd, F_SETFL,
(state->direct_io ? O_DIRECT : 0) | fcntl(fd, F_GETFL));
/*
* We don't clear direct_io_changed here, to avoid race conditions
* that could cause the input and output settings to differ.
*/
#endif /* O_DIRECT */
return fd;
}
+9
View File
@@ -117,6 +117,15 @@ int pv_main_loop(pvstate_t state)
pv_crs_fini(state);
return state->exit_status;
}
#ifdef O_DIRECT
/*
* Set or clear O_DIRECT on the output.
*/
fcntl(STDOUT_FILENO, F_SETFL,
(state->direct_io ? O_DIRECT : 0) | fcntl(STDOUT_FILENO,
F_GETFL));
state->direct_io_changed = false;
#endif /* O_DIRECT */
/*
* Set target buffer size if the initial file's block size can be
+6
View File
@@ -206,6 +206,12 @@ void pv_state_sync_after_write_set(pvstate_t state, bool val)
state->sync_after_write = val;
};
void pv_state_direct_io_set(pvstate_t state, bool val)
{
state->direct_io = val;
state->direct_io_changed = true;
};
void pv_state_rate_limit_set(pvstate_t state, unsigned long long val)
{
state->rate_limit = val;
+101 -5
View File
@@ -684,6 +684,63 @@ static int pv__transfer_write(pvstate_t state,
}
/*
* Return a pointer to a newly allocated buffer of the given size, aligned
* appropriately for the current input and output file descriptors
* (important if using O_DIRECT).
*
* Falls back to unaligned allocation if it was not possible to get an
* aligned buffer, or if the relevant operating system features were not
* available. With O_DIRECT, this means that transfers could fail with an
* "Invalid argument" error (EINVAL).
*
* Returns NULL on complete allocation failure.
*/
static unsigned char *pv__allocate_aligned_buffer(int fd,
size_t target_size)
{
unsigned char *newptr;
#if defined(HAVE_FPATHCONF) && defined(HAVE_POSIX_MEMALIGN) && defined(_PC_REC_XFER_ALIGN)
long input_alignment, output_alignment, min_alignment;
size_t required_alignment;
input_alignment = fd >= 0 ? fpathconf(fd, _PC_REC_XFER_ALIGN) : -1;
output_alignment = fpathconf(STDOUT_FILENO, _PC_REC_XFER_ALIGN);
# if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
min_alignment = sysconf(_SC_PAGESIZE);
# else /* ! defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE) */
min_alignment = 8192;
# endif /* defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE) */
if (input_alignment > output_alignment) {
required_alignment = input_alignment;
} else if (output_alignment > input_alignment) {
required_alignment = output_alignment;
} else if (input_alignment < min_alignment) {
required_alignment = min_alignment;
} else {
required_alignment = input_alignment;
}
/* Ensure the alignment is at least the page size. */
if (required_alignment < min_alignment) {
required_alignment = min_alignment;
}
if (0 !=
posix_memalign((void **) (&newptr), required_alignment,
target_size)) {
newptr = (unsigned char *) malloc(target_size);
}
#else /* ! defined(HAVE_FPATHCONF) && defined(HAVE_POSIX_MEMALIGN) && defined(_PC_REC_XFER_ALIGN) */
newptr = (unsigned char *) malloc(target_size);
#endif /* defined(HAVE_FPATHCONF) && defined(HAVE_POSIX_MEMALIGN) && defined(_PC_REC_XFER_ALIGN) */
return newptr;
}
/*
* Transfer some data from "fd" to standard output, timing out after 9/100
* of a second. If state->rate_limit is >0, and/or "allowed" is >0, only up
@@ -707,6 +764,26 @@ long pv_transfer(pvstate_t state, int fd, int *eof_in, int *eof_out,
if (NULL == state)
return 0;
#ifdef O_DIRECT
/*
* Set or clear O_DIRECT on the input and output file descriptors,
* if the setting has changed.
*/
if (state->direct_io_changed) {
if (!(*eof_in)) {
fcntl(fd, F_SETFL,
(state->direct_io ? O_DIRECT : 0) | fcntl(fd,
F_GETFL));
}
if (!(*eof_out)) {
fcntl(STDOUT_FILENO, F_SETFL,
(state->direct_io ? O_DIRECT : 0) |
fcntl(STDOUT_FILENO, F_GETFL));
}
state->direct_io_changed = false;
}
#endif /* O_DIRECT */
/*
* Reinitialise the error skipping variables if the file descriptor
* has changed since the last time we were called.
@@ -717,10 +794,15 @@ long pv_transfer(pvstate_t state, int fd, int *eof_in, int *eof_out,
state->read_error_warning_shown = 0;
}
/*
* Allocate a new buffer, aligned appropriately for the input file
* (important if using O_DIRECT).
*/
if (NULL == state->transfer_buffer) {
state->buffer_size = state->target_buffer_size;
state->transfer_buffer =
(unsigned char *) malloc(state->buffer_size + 32);
pv__allocate_aligned_buffer(fd,
state->target_buffer_size +
32);
if (NULL == state->transfer_buffer) {
pv_error(state, "%s: %s",
_("buffer allocation failed"),
@@ -728,16 +810,22 @@ long pv_transfer(pvstate_t state, int fd, int *eof_in, int *eof_out,
state->exit_status |= 64;
return -1;
}
state->buffer_size = state->target_buffer_size;
}
/*
* Reallocate the buffer if the buffer size has changed mid-transfer.
* Reallocate the buffer if the buffer size has changed
* mid-transfer. We have to do this by allocating a new buffer,
* copying to it, and freeing the old one (potentially leaking
* memory) because the buffer may need to be aligned for O_DIRECT,
* and we can't realloc() an aligned buffer.
*/
if (state->buffer_size < state->target_buffer_size) {
unsigned char *newptr;
newptr =
realloc(state->transfer_buffer,
state->target_buffer_size + 32);
pv__allocate_aligned_buffer(fd,
state->target_buffer_size +
32);
if (NULL == newptr) {
/*
* Reset target if realloc failed so we don't keep
@@ -748,6 +836,14 @@ long pv_transfer(pvstate_t state, int fd, int *eof_in, int *eof_out,
} else {
debug("%s: %ld", "buffer resized",
state->buffer_size);
/*
* Copy the old buffer contents into the new buffer,
* and free the old one.
*/
if (state->buffer_size > 0)
memcpy(newptr, state->transfer_buffer,
state->buffer_size);
free(state->transfer_buffer);
state->transfer_buffer = newptr;
state->buffer_size = state->target_buffer_size;
}
+55
View File
@@ -0,0 +1,55 @@
#!/bin/sh
#
# Transfer a large chunk of data with "--direct-io" and check data
# correctness afterwards, both with and without rate limits. Note that this
# doesn't check that O_DIRECT is actually being used, it just checks that
# data is not being corrupted.
# Dummy assignments for "shellcheck".
testSubject="${testSubject:-false}"; workFile1="${workFile1:-.tmp1}"; workFile2="${workFile2:-.tmp2}"
# generate some data
dd if=/dev/urandom of="${workFile1}" bs=1024 count=2560 2>/dev/null
inputChecksum=$(cksum "${workFile1}" | awk '{print $1}')
doubleInputChecksum=$(cat "${workFile1}" "${workFile1}" | cksum - | awk '{print $1}')
# read through pv and test afterwards
"${testSubject}" -q "${workFile1}" > "${workFile2}"
outputChecksum=$(cksum "${workFile2}" | awk '{print $1}')
if ! test "${inputChecksum}" = "${outputChecksum}"; then
echo "checksum mismatched even without \"--direct-io\""
exit 1
fi
# Same again but with "--direct-io"
"${testSubject}" -K -q "${workFile1}" > "${workFile2}"
outputChecksum=$(cksum "${workFile2}" | awk '{print $1}')
if ! test "${inputChecksum}" = "${outputChecksum}"; then
echo "checksum mismatched with \"--direct-io\""
exit 1
fi
# Now with "--direct-io" and "--rate-limit"
"${testSubject}" -K -L 800K -q "${workFile1}" > "${workFile2}"
outputChecksum=$(cksum "${workFile2}" | awk '{print $1}')
if ! test "${inputChecksum}" = "${outputChecksum}"; then
echo "checksum mismatched with \"--direct-io\" + \"--rate-limit\""
exit 1
fi
# Now with "--direct-io" on the same file twice
"${testSubject}" -K -q "${workFile1}" "${workFile1}" > "${workFile2}"
outputChecksum=$(cksum "${workFile2}" | awk '{print $1}')
if ! test "${doubleInputChecksum}" = "${outputChecksum}"; then
echo "checksum mismatched with \"--direct-io\" on two files"
exit 1
fi
exit 0
# EOF