Add rw(1).

This commit is contained in:
Jonas 'Sortie' Termansen 2018-03-30 23:44:08 +02:00
parent d393b67d72
commit d1c3433353
6 changed files with 1360 additions and 0 deletions

View File

@ -22,6 +22,7 @@ kblayout-compiler \
login \
mkinitrd \
regress \
rw \
sf \
sh \
sysinstall \

1
rw/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
rw

28
rw/Makefile Normal file
View File

@ -0,0 +1,28 @@
include ../build-aux/platform.mak
include ../build-aux/compiler.mak
include ../build-aux/version.mak
include ../build-aux/dirs.mak
OPTLEVEL?=$(DEFAULT_OPTLEVEL)
CFLAGS?=$(OPTLEVEL)
CFLAGS += -Wall -Wextra
BINARIES = rw
MANPAGES1 = rw.1
all: $(BINARIES)
.PHONY: all install clean
install: all
mkdir -p $(DESTDIR)$(BINDIR)
install $(BINARIES) $(DESTDIR)$(BINDIR)
mkdir -p $(DESTDIR)$(MANDIR)/man1
cp $(MANPAGES1) $(DESTDIR)$(MANDIR)/man1
%: %.c
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $< -o $@
clean:
rm -f $(BINARIES)

453
rw/rw.1 Normal file
View File

@ -0,0 +1,453 @@
.Dd March 6, 2018
.Dt RW 1
.Os
.Sh NAME
.Nm rw
.Nd blockwise input/output
.Sh SYNOPSIS
.Nm
.Op Fl afhPstvx
.Op Fl b Ar block-size
.Op Fl c Ar count
.Op Fl I Ar input-offset
.Op Fl i Ar input-file
.Op Fl O Ar output-offset
.Op Fl o Ar output-file
.Op Fl p Ar interval
.Op Fl r Ar input-block-size
.Op Fl w Ar output-block-size
.Sh DESCRIPTION
.Nm
reads blocks from the standard input and copies them to the standard output
until the end of the standard input.
The input block size and output block sizes default to appropriate values for
efficiently reading from the input and writing to the output.
Input and output will be done as aligned as possible on the block size
boundaries.
The final input block can be partial.
Output blocks are written whenever enough input blocks have been read, or
partially written whenever the end of the input is reached.
The output file is not truncated after the copy is done.
.Pp
Byte quantities can be specified as a non-negative count of bytes in decimal
format, hexadecimal format (with leading
.Sy 0x ) ,
or octal format (with leading
.Sy 0 ) ;
optionally followed by any amount of whitespace, and then suffixed with any of
the following case-insensitive suffixes that multiplies the specified quantity
by that magnitude:
.Pp
.Bl -tag -width "12345678" -compact
.It Sy B
Magnitude of a byte (1 byte).
.It Sy K , Sy KiB
Magnitude of kibibytes (1,024 bytes).
.It Sy M , Sy MiB
Magnitude of mebibytes (1,048,576 bytes).
.It Sy G , Sy GiB
Magnitude of gibibytes (1,073,741,824 bytes).
.It Sy T , Sy TiB
Magnitude of tebibytes (1,099,511,627,776 bytes).
.It Sy P , Sy PiB
Magnitude of pebibytes (1,125,899,906,842,624 bytes).
.It Sy E , Sy EiB
Magnitude of exbibytes (1,152,921,504,606,846,976 bytes).
.It Sy r
Magnitude of input blocks (the default input block size when setting block
sizes).
.It Sy w
Magnitude of output blocks (the default output block size when setting block
sizes
.It Sy x
Magnitude of input and output blocks (if they have the same size) (the default
block size when setting block sizes).
.El
.Pp
The options are as follows:
.Bl -tag -width "12345678"
.It Fl a
In combination with
.Fl o ,
open the output file in append mode.
This option must be used with
.Fl o
and is incompatible with
.Fl t .
The output offset is set to the size of the output file.
.It Fl b Ar block-size
Set both the input and output block sizes to
.Ar block-size .
.It Fl c Ar count
Stop after copying the byte quantify specified by
.Ar count ,
in addition to stopping when the end of the input is reached.
If the
.Ar count
starts with a leading
.Sq - ,
stop that many bytes before the end of the input (only works if the input size
is known).
.It Fl f
Continue as much as possible in the event of I/O errors and exit unsuccessfully
afterwards.
For each input failure, skip forward to the next input block or the next
native-size input block or the end of the file (whichever comes first), write an
error to the standard error, and replace the failed input with NUL bytes when
writing it to the output.
For each output failure, skip forward to the next output block or the next
native-size output block (whichever comes first) and write an error to the
standard error.
The native-size input and output blocks are those defined by the preferred
input/output size for the input and output.
This option only works for the input if it is seekable and the input size is
known.
This option only works for the output if it is seekable (and
.Fl a
is not set).
Beware that the default preferred input/output sizes may be larger than the
underlying storage sector sizes: If this option is used, the
.Fl r
and
.Fl w
options should be set to the appropriate input/output sector sizes, or more than
just the bad sector may be skipped.
.It Fl h
Write statistics in the human readable format where byte amounts and time
amounts are formatted according to their magnitude as described in the
.Sx DIAGNOSTICS
section.
.It Fl I Ar offset
Skip past the first
.Ar offset
bytes in the input before the copying begins.
If the
.Ar offset
starts with a leading
.Sq -
it is interpreted as that many bytes before the end of the input (if the size is
known), and if it starts with a leading
.Sq +
it is interpreted as that many bytes after the end of the input (if the size is
known).
If the input is not seekable, the first
.Ar offset
bytes are read and discarded before the copying begins.
If the
.Ar offset
is not a multiple of the input block size, the first input block is reduced in
size such that it ends at a input-block-size-aligned position in the input.
.It Fl i Ar input-file
Read the input from
.Ar input-file
instead of the standard input.
.It Fl O Ar offset
Seek past the first
.Ar offset
bytes in the output before the copying begins.
If the
.Ar offset
starts with a leading
.Sq -
it is interpreted as that many bytes before the end of the output (if the size
is known), and if it starts with a leading
.Sq +
it is interpreted as that many bytes after the end of the output (if the size
is
known).
If the output is not seekable, the number of NUL bytes specified in
.Ar offset
are written to the output before the copying begins.
This option cannot be set to a non-zero value if
.Fl a
is set.
If the
.Ar offset
is not a multiple of the output block size, the first output block is reduced in
size such that it ends at a output block size aligned position in the output.
.It Fl o Ar output-file
Write the output to
.Ar output-file
instead of the standard output, creating the file if it doesn't exist.
If
.Ar output-file
already exists, the existing data is not discarded.
Use
.Fl t
if you want to truncate the output afterwards.
.It Fl P
Pad the final output block with NUL bytes, such that the final output offset
(counting the initial offset with
.Fl O )
is a multiple of the output block size.
.It Fl p Ar interval
Write occasional statistics to the standard error during the transfer and on
completion, or when being terminated by
.Dv SIGINT .
.Ar interval
is a non-negative integer of how long the interval is in whole seconds between
statistics being written.
Statistics are written for every read and write if the
.Ar interval
is zero.
The format is described in the
.Sx DIAGNOSTICS
section.
.It Fl r Ar input-block-size
Set the input block size to
.Ar input-block-size .
.It Fl s
Sync the output on successful completion.
.It Fl t
Truncate the output to the final output position after the copy has completed.
This option requires the output to be truncatable.
This option is incompatible with
.Fl a .
.It Fl v
Write statistics to the standard error upon completion, or when being terminated
by
.Dv SIGINT .
The format is described in the
.Sx DIAGNOSTICS
section.
.It Fl w Ar output-block-size
Set the output block size to
.Ar output-block-size .
.It Fl x
In combination with
.Fl o ,
fail if the output file already exists.
.El
.Sh ASYNCHRONOUS EVENTS
.Bl -tag -width "SIGUSR1"
.It Dv SIGINT
If
.Fl v
or
.Fl p
is set, abort the copy, write statistics to the standard error, and then exit as
if killed by
.Dv SIGINT .
.It Dv SIGUSR1
Write statistics to the standard error and continue the copy.
If
.Dv SIGUSR1
is not ignored, this handler is installed and this signal is unblocked.
To use this signal without a race condition before the signal handler is
installed (as
.Dv SIGUSR1
is deadly by default), block the signal before loading this program.
To disable the handling of this signal, ignore the signal before loading this
program.
.El
.Sh EXIT STATUS
.Nm
will exit 0 on success and non-zero otherwise.
.Sh EXAMPLES
Copy from the standard input to the standard output:
.Bd -literal
rw
.Ed
.Pp
Copy the first 256 bytes from the input to the output:
.Bd -literal
rw -c 256
.Ed
.Pp
Copy from the input file
.Pa foo
to the beginning of the output file
.Pa bar
(preserving any data in the output file beyond the final output position after
the copy is finished).
.Bd -literal
rw -i foo -o bar
.Ed
.Pp
Copy from the input file to the beginning of the output file, truncating the
output file to the final output position afterwards:
.Bd -literal
rw -i foo -o bar -t
.Ed
.Pp
Copy from the input file
.Pa foo
to the beginning of the output block device
.Pa /dev/bar
(preserving any existing data on the output block device beyond the copied
area), while writing progress statistics every 10 seconds in the human readable
format, and sync the output block device afterwards:
.Bd -literal
rw -i foo -o /dev/bar -p 10 -h -s
.Ed
.Pp
Skip the first 512 bytes of the input, and then append the next 1024 bytes to
the output file
.Pa bar :
.Bd -literal
rw -I 512 -c 1024 -o bar -a
.Ed
.Pp
Copy 2 KiB from offset 768 in the input file
.Pa foo
to offset 256 MiB in the output file
.Pa bar .
.Bd -literal
rw -c 2K -i foo -I 768 -o bar -O 256M
.Ed
.Pp
Copy from sector 32 and 4 sectors onwards from a block device
.Pa /dev/foo
(with the sector size being 512 bytes)
to the output file
.Pa bar :
.Bd -literal
rw -r 512 -i /dev/foo -I 32r -c 4r -o bar
.Ed
.Pp
With a block size of 4096 bytes, copy 64 blocks from the input from offset 32
blocks in the input to offset 65536 blocks in the output:
.Bd -literal
rw -b 4096 -c 64x -I 32x -O 65536x
.Ed
.Pp
Back up the
.Pa /dev/foo
block device (with the sector size being 512 bytes) to the
.Pa bar
output file, continuing despite I/O errors by writing error messages to the
standard error and writing NUL bytes to the output instead, truncating the
output file to the size of the input, writing progress statistics every 10
seconds in the human readable format to the standard error:
.Bd -literal
rw -f -i /dev/foo -r 512 -o bar -t -p 10 -h
.Ed
.Pp
With the input block size of 512 bytes and the output block size of 8192 bytes,
copy 16384 input blocks from input block 65536 onwards to output block 1048576:
.Bd -literal
rw -r 512 -w 8192 -c 16384r -I 65536r -O 1048576w
.Ed
.Pp
Copy 512 bytes from 1024 bytes before the end of the input to 2048 bytes after
the current size of the output file:
.Bd -literal
rw -c 512 -I -1024 -o bar -O +2048
.Ed
.Pp
Skip the first 100 bytes of the input and copy until 200 bytes are left in the
input file:
.Bd -literal
rw -i foo -I 100 -c -200
.Ed
.Sh DIAGNOSTICS
Statistics about the copy are written to the standard error upon completion
if either
.Fl v
or
.Fl p
are set; occasionally if
.Fl p
is set; upon
.Dv SIGINT
(if not ignored when the program was loaded) if
.Fl v
is set; and upon
.Dv SIGUSR1
(if not ignored when the program was loaded).
.Pp
The statistics are in this format:
.Bd -literal
<time-elapsed> s <done> B / <total> B <percent>% <speed> B/s <time-left> s
.Ed
.Pp
.Ar time-elapsed
is the number of seconds since the copying began.
.Ar done
is the number of bytes copied so far.
.Ar total
is an estimate of how many bytes will be copied, or
.Sq "?"
if not known.
.Ar percent
is how many percent complete the copy is, or
.Sq "?"
if not known.
.Ar speed
the average speed of copying so far in bytes per second, or
.Sq "?"
if it is too early to tell.
.Ar time-left
is the number of seconds left, assuming the remaining data is copied at the
current average speed, or
.Sq "?"
is not known.
.Pp
For instance, the statistics could look like this:
.Bd -literal
7 s 714682368 B / 1238364160 B 57% 102097481 B/s 5 s
.Ed
.Pp
The statistics are printed with human readable byte units (B, KiB, MiB, GiB,
TiB, PiB, EiB) and time units (s, m, h, d) if the
.Fl h
option is set:
.Bd -literal
7 s 714.4 MiB / 1.1 GiB 60% 102.0 MiB/s 4 s
.Ed
.Sh SEE ALSO
.Xr cat 1 ,
.Xr cp 1 ,
.Xr dd 1
.Sh HISTORY
.Nm
originally appeared in Sortix 1.1.
.Pp
.Nm
is similar to
.Xr dd 1 ,
but has a distinct design and improvements:
.Bl -bullet
.It
The command line options use the conventional option format.
.It
The output file is not truncated by default.
One has to use
.Fl t .
.It
The input and output block sizes default to the preferred I/O block sizes
instead of 512 bytes.
.Pp
The
.Fl c , I ,
and
.Fl O
options accept byte quantities by default instead of block counts, but can
be specified in block counts by using the
.Sq r , w ,
and
.Sq x
suffixes.
.It
Statistics are not written by default.
One has to use
.Fl v
or
.Fl p .
The statistics contain more useful information and is machine readable as it
contains no localized information.
A human readable statistics format is available using
.Fl h .
Statistics can occasionally be written out using
.Fl p .
.It
There is no support for converting ASCII to EBCDIC, converting ASCII to a
different EBCDIC, EBCDIC to ASCII, swapping pairs of bytes, converting the bytes
to lower-case or upper-case, converting line-delimited data into fixed-size
blocks, or converting fixed-sized blocks into line-delimited data.
.It
Offsets can be specified relative to the end of the input/output.
.It
Input errors stop the copying immediately rather than writing out a partial
output block.
.El

869
rw/rw.c Normal file
View File

@ -0,0 +1,869 @@
/*
* Copyright (c) 2016, 2017, 2018 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* rw.c
* Blockwise input/output.
*/
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifndef OFF_MAX
#define OFF_MAX ((off_t) ((UINTMAX_C(1) << (sizeof(off_t) * 8 - 1)) - 1))
#endif
static uintmax_t parse_quantity(const char* string,
blksize_t input_blksize,
blksize_t output_blksize)
{
const char* end;
if ( *string < '0' || '9' < *string )
errx(1, "invalid quantity: %s", string);
errno = 0;
uintmax_t value = strtoumax(string, (char**) &end, 0);
if ( value == UINTMAX_MAX && errno == ERANGE )
errx(1, "argument overflow: %s", string);
if ( *end )
{
while ( isspace((unsigned char) *end) )
end++;
uintmax_t magnitude = 1;
const char* unit = end;
unsigned char magc = tolower((unsigned char) *end);
switch ( magc )
{
case '\0': errx(1, "trailing whitespace in quantity: %s", string);
case 'b': magnitude = 1; break;
case 'k': magnitude = UINTMAX_C(1024) << (0 * 10); break;
case 'm': magnitude = UINTMAX_C(1024) << (1 * 10); break;
case 'g': magnitude = UINTMAX_C(1024) << (2 * 10); break;
case 't': magnitude = UINTMAX_C(1024) << (3 * 10); break;
case 'p': magnitude = UINTMAX_C(1024) << (4 * 10); break;
case 'e': magnitude = UINTMAX_C(1024) << (5 * 10); break;
case 'r': magnitude = input_blksize; break;
case 'w': magnitude = output_blksize; break;
case 'x':
if ( input_blksize != output_blksize )
errx(1, "input block size is not output block size: %s",
string);
magnitude = input_blksize;
break;
default: errx(1, "unsupported unit: %s", unit);
}
end++;
if ( (tolower(magc) != 'b' &&
tolower(magc) != 'r' &&
tolower(magc) != 'w' &&
tolower(magc) != 'x') &&
strcasecmp(end, "iB") == 0 )
end += 2;
if ( *end != '\0' )
errx(1, "unsupported unit: %s", unit);
if ( magnitude != 0 && UINTMAX_MAX / magnitude < value )
errx(1, "argument overflow: %s", string);
value *= magnitude;
}
return value;
}
static size_t parse_size_t(const char* string,
blksize_t input_blksize,
blksize_t output_blksize)
{
uintmax_t result = parse_quantity(string, input_blksize, output_blksize);
if ( result != (size_t) result || SSIZE_MAX < result )
errx(1, "argument overflow: %s", string);
return (size_t) result;
}
static off_t parse_off_t(const char* string,
blksize_t input_blksize,
blksize_t output_blksize)
{
uintmax_t result = parse_quantity(string, input_blksize, output_blksize);
if ( result != (uintmax_t) (off_t) result )
errx(1, "argument overflow: %s", string);
return (off_t) result;
}
static off_t parse_offset(const char* string,
blksize_t input_blksize,
blksize_t output_blksize,
off_t size)
{
if ( string[0] == '-' )
{
off_t result = parse_off_t(string + 1, input_blksize, output_blksize);
if ( size < result )
errx(1, "value smaller than file size: %s", string);
return size - result;
}
else if ( string[0] == '+' )
{
off_t result = parse_off_t(string + 1, input_blksize, output_blksize);
if ( OFF_MAX - size < result )
errx(1, "argument overflow: %s", string);
return size + result;
}
return parse_off_t(string, input_blksize, output_blksize);
}
static time_t parse_time_t(const char* string)
{
const char* end;
if ( *string < '0' || '9' < *string )
errx(1, "invalid duration: %s", string);
errno = 0;
uintmax_t value = strtoumax(string, (char**) &end, 0);
if ( value == UINTMAX_MAX && errno == ERANGE )
errx(1, "argument overflow: %s", string);
if ( *end )
errx(1, "invalid duration: %s", string);
if ( value != (uintmax_t) (time_t) value )
errx(1, "argument overflow: %s", string);
return (time_t) value;
}
static time_t timediff(struct timespec now, struct timespec then)
{
time_t result = now.tv_sec - then.tv_sec;
if ( now.tv_nsec < then.tv_nsec )
result--;
return result;
}
static int percent_done(off_t done, off_t total)
{
if ( total < 0 || total < done )
return -1;
// Avoid overflow when multiplying by 100 by reducing the problem.
if ( OFF_MAX / 65536 <= done )
{
done /= 65536;
total /= 65536;
}
if ( total == 0 )
return 100;
return (done * 100) / total;
}
static void format_bytes_amount(char* buf,
size_t len,
uintmax_t value,
bool human_readable)
{
if ( !human_readable )
{
snprintf(buf, len, "%ju B", value);
return;
}
uintmax_t value_fraction = 0;
uintmax_t exponent = 1024;
char suffixes[] = { 'B', 'K', 'M', 'G', 'T', 'P', 'E' };
size_t num_suffixes = sizeof(suffixes) / sizeof(suffixes[0]);
size_t suffix_index = 0;
while ( exponent <= value && suffix_index + 1 < num_suffixes)
{
value_fraction = value % exponent;
value /= exponent;
suffix_index++;
}
char suffix_str[] =
{ suffixes[suffix_index], 0 < suffix_index ? 'i' : '\0', 'B', '\0' };
char fraction_char = '0' + (value_fraction / (1024 / 10 + 1)) % 10;
snprintf(buf, len, "%ju.%c %s", value, fraction_char, suffix_str);
}
static void format_time_amount(char* buf,
size_t len,
uintmax_t value,
bool human_readable)
{
if ( !human_readable || value < 60 )
snprintf(buf, len, "%ju s", value);
else if ( value < 60 * 60 )
{
int seconds = value % 60;
int fraction = (seconds * 10) / 60;
uintmax_t minutes = value / 60;
snprintf(buf, len, "%ju.%i m", minutes, fraction);
}
else if ( value < 24 * 60 * 60 )
{
int minutes = (value / 60) % 60;
int fraction = (minutes * 10) / 60;
uintmax_t hours = value / (60 * 60);
snprintf(buf, len, "%ju.%i h", hours, fraction);
}
else
{
int minutes = (value / 60) % (24 * 60);
int fraction = (minutes * 10) / (24 * 60);
uintmax_t days = value / (24 * 60 * 60);
snprintf(buf, len, "%ju.%i d", days, fraction);
}
}
static volatile sig_atomic_t signaled = 0;
static volatile sig_atomic_t interrupted = 0;
static void on_signal(int signum)
{
signaled = 1;
if ( signum == SIGINT )
interrupted = 1;
}
static void progress(struct timespec start,
off_t done,
off_t total,
bool human_readable,
struct timespec* last_statistic,
time_t interval)
{
// Write statistics if signaled or if an interval has been set with -p.
bool handling_signal = signaled || interrupted;
struct timespec now;
if ( !handling_signal )
{
if ( interval < 0 )
return;
clock_gettime(CLOCK_MONOTONIC, &now);
if ( 0 < interval )
{
time_t since_last = timediff(now, *last_statistic);
if ( since_last <= 0 )
return;
last_statistic->tv_sec += interval;
}
}
// Avoid system calls being interrupted when writing statistics, but ensure
// that we die by SIGINT if it happens for the second twice.
sigset_t sigset, oldsigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGUSR1);
if ( !interrupted )
sigaddset(&sigset, SIGINT);
sigprocmask(SIG_BLOCK, &sigset, &oldsigset);
if ( handling_signal )
clock_gettime(CLOCK_MONOTONIC, &now);
time_t duration = timediff(now, start);
int percent = percent_done(done, total);
off_t speed = -1;
if ( 0 < duration )
speed = done / duration;
time_t countdown = -1;
if ( 0 < speed && 0 <= total && done <= total )
{
off_t countdown_off = (total - done) / speed;
if ( (time_t) countdown_off == countdown_off )
countdown = countdown_off;
}
char duration_str[3 * sizeof(duration) + 2];
format_time_amount(duration_str, sizeof(duration_str), duration,
human_readable);
char done_str[3 * sizeof(done) + 2];
format_bytes_amount(done_str, sizeof(done_str), done, human_readable);
char total_str[3 * sizeof(total) + 2] = "? B";
if ( 0 <= total )
format_bytes_amount(total_str, sizeof(total_str), total,
human_readable);
char percent_str[5] = "?%";
if ( 0 <= percent )
snprintf(percent_str, sizeof(percent_str), "%i%%", percent);
char speed_str[3 * sizeof(speed) + 2] = "? B";
if ( 0 <= speed )
format_bytes_amount(speed_str, sizeof(speed_str), speed,
human_readable);
char countdown_str[3 * sizeof(countdown) + 2] = "? s";
if ( 0 <= countdown )
format_time_amount(countdown_str, sizeof(countdown_str), countdown,
human_readable);
fprintf(stderr, "%s %s / %s %s %s/s %s\n",
duration_str,
done_str,
total_str,
percent_str,
speed_str,
countdown_str);
if ( interrupted )
raise(SIGINT);
if ( handling_signal )
signaled = 0;
sigprocmask(SIG_SETMASK, &oldsigset, NULL);
}
int main(int argc, char *argv[])
{
// SIGUSR1 is deadly by default until a handler is installed, let users
// avoid the race condition by letting them block if before loading this
// program and then it's unblocked after a handler is installed. Allow
// disabling SIGUSR1 handling by setting the handler to ignore before
// loading this program.
struct sigaction sa;
sigaction(SIGUSR1, NULL, &sa);
bool handle_sigusr1 = sa.sa_handler != SIG_IGN;
if ( handle_sigusr1 )
{
memset(&sa, 0, sizeof(sa));
sa.sa_handler = on_signal;
sa.sa_flags = 0; // Don't restart system calls.
sigaction(SIGUSR1, &sa, NULL);
sigset_t usr1_set;
sigemptyset(&usr1_set);
sigaddset(&usr1_set, SIGUSR1);
sigset_t old_sigset;
sigprocmask(SIG_UNBLOCK, &usr1_set, &old_sigset);
}
bool append = false;
bool force = false;
bool human_readable = false;
bool no_create = false;
bool pad = false;
bool sync = false;
bool truncate = false;
bool verbose = false;
const char* count_str = NULL;
const char* input_path = NULL;
const char* output_path = NULL;
const char* input_blksize_str = NULL;
const char* output_blksize_str = NULL;
const char* input_offset_str = NULL;
const char* output_offset_str = NULL;
const char* progress_str = NULL;
int opt;
while ( (opt = getopt(argc, argv, "ab:c:fhI:i:O:o:Pp:r:stvw:x")) != -1 )
{
switch ( opt )
{
case 'a': append = true; break;
case 'b': input_blksize_str = output_blksize_str = optarg; break;
case 'c': count_str = optarg; break;
case 'f': force = true; break;
case 'h': human_readable = true; break;
case 'I': input_offset_str = optarg; break;
case 'i': input_path = optarg; break;
case 'O': output_offset_str = optarg; break;
case 'o': output_path = optarg; break;
case 'P': pad = true; break;
case 'p': progress_str = optarg; verbose = true; break;
case 'r': input_blksize_str = optarg; break;
case 's': sync = true; break;
case 't': truncate = true; break;
case 'v': verbose = true; break;
case 'w': output_blksize_str = optarg; break;
case 'x': no_create = true; break;
default: return 1;
}
}
if ( optind < argc )
errx(1, "unexpected extra operand");
if ( append && truncate )
errx(1, "the -a and -t options are mutually incompatible");
int input_fd = 0;
if ( input_path )
{
input_fd = open(input_path, O_RDONLY);
if ( input_fd < 0 )
err(1, "%s", input_path);
}
else
input_path = "<stdin>";
int output_fd = 1;
if ( output_path )
{
int flags = O_WRONLY | O_CREAT;
if ( append )
flags |= O_APPEND;
if ( no_create )
flags |= O_EXCL;
output_fd = open(output_path, flags, 0666);
if ( output_fd < 0 )
err(1, "%s", output_path);
}
else
{
if ( append )
errx(1, "the -a option requires -o");
output_path = "<stdout>";
}
struct stat input_st;
if ( fstat(input_fd, &input_st) < 0 )
err(1, "stat: %s", input_path);
#if !defined(__sortix__)
if ( S_ISBLK(input_st.st_mode) && input_st.st_size == 0 )
{
if ( (input_st.st_size = lseek(input_fd, 0, SEEK_END)) < 0 )
err(1, "%s: lseek", input_path);
lseek(input_fd, 0, SEEK_SET);
}
#endif
struct stat output_st;
if ( fstat(output_fd, &output_st) < 0 )
err(1, "stat: %s", output_path);
#if !defined(__sortix__)
if ( S_ISBLK(output_st.st_mode) && output_st.st_size == 0 )
{
if ( (output_st.st_size = lseek(output_fd, 0, SEEK_END)) < 0 )
err(1, "%s: lseek", output_path);
lseek(output_fd, 0, SEEK_SET);
}
#endif
size_t input_blksize = input_st.st_blksize;
if ( input_blksize_str )
input_blksize = parse_size_t(input_blksize_str,
input_st.st_blksize,
output_st.st_blksize);
if ( input_blksize == 0 )
errx(1, "the input block size cannot be zero");
size_t output_blksize = output_st.st_blksize;
if ( output_blksize_str )
output_blksize = parse_size_t(output_blksize_str,
input_st.st_blksize,
output_st.st_blksize);
if ( input_blksize == 0 )
errx(1, "the output block size cannot be zero");
off_t input_offset = 0;
if ( input_offset_str )
input_offset = parse_offset(input_offset_str,
input_blksize,
output_blksize,
input_st.st_size);
off_t output_offset = 0;
if ( output_offset_str )
output_offset = parse_offset(output_offset_str,
input_blksize,
output_blksize,
output_st.st_size);
if ( append )
{
if ( output_offset != 0 )
errx(1, "-O cannot be set to a non-zero value if -a is set");
output_offset = output_st.st_size;
}
off_t count = -1; // No limit.
if ( count_str )
{
off_t left = input_offset <= input_st.st_size ?
input_st.st_size - input_offset : 0;
count = parse_offset(count_str, input_blksize, output_blksize, left);
}
time_t interval = -1; // No interval.
if ( progress_str )
interval = parse_time_t(progress_str);
// Input and output are done only with aligned reads/writes, unless not
// possible. The buffer works in two modes depending on the parameters:
//
// 1) If
//
// * The input block size and output block sizes are a multiple of each
// other, and
// * the input offset and output offsets are equal modulo the block
// sizes;
//
// then the buffer size is the largest of the input block size and the
// output block size, and it will always be possible to fill the buffer
// of that size with input and write it out.
//
// 2) Otherwise, the buffer size is the input block size plus the output
// block size, working as a ring buffer. This buffer will ensure
// efficient forward progress can be made even with worst case block
// sizes and offsets.
bool use_largest_blksize = (input_blksize > output_blksize ?
input_blksize % output_blksize == 0 :
output_blksize % input_blksize == 0) &&
input_offset % input_blksize ==
output_offset % output_blksize;
size_t buffer_size = use_largest_blksize ?
input_blksize > output_blksize ?
input_blksize :
output_blksize :
input_blksize + output_blksize;
// Allocate a page aligned buffer.
unsigned char* buffer = mmap(NULL, buffer_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if ( buffer == MAP_FAILED )
err(1, "allocating %zu byte buffer", buffer_size);
struct timespec start;
clock_gettime(CLOCK_MONOTONIC, &start);
struct timespec last_statistic = start;
if ( verbose )
{
memset(&sa, 0, sizeof(sa));
sa.sa_handler = on_signal;
sa.sa_flags = SA_RESETHAND; // Second SIGINT is deadly.
sigaction(SIGINT, &sa, NULL);
}
// Whether an end of file condition has been met, kept track of it in a
// variable to handle devices like terminals that don't have sticky EOF
// conditions (where the next read will also fail with an EOF condition).
bool input_eof = false;
// Estimate of how much will be written to the output for statistics. This
// is set to -1 if not known or if the guess turns out to be wrong.
off_t estimated_total_out;
if ( S_ISREG(input_st.st_mode) || S_ISBLK(input_st.st_mode) )
{
off_t remaining = input_offset <= input_st.st_size ?
input_st.st_size - input_offset : 0;
estimated_total_out =
count == -1 || remaining < count ? remaining : count;
}
else
estimated_total_out = count;
// Skip past the initial input offset. If the input isn't seekable, read and
// discard that many bytes from the input. Fail hard even if -f as there is
// no way to recover.
if ( input_offset != 0 && lseek(input_fd, input_offset, SEEK_SET) < 0 )
{
if ( errno != ESPIPE )
err(1, "%s: lseek", input_path);
off_t offset = 0;
while ( !input_eof && offset < input_offset )
{
size_t amount = input_blksize;
if ( (uintmax_t) (input_offset - offset) < (uintmax_t) amount )
amount = input_offset - offset;
size_t so_far = 0;
while ( so_far < amount )
{
progress(start, 0, estimated_total_out, human_readable,
&last_statistic, interval);
ssize_t done = read(input_fd, buffer + so_far, amount - so_far);
if ( done < 0 && errno == EINTR )
done = 0;
else if ( done < 0 )
err(1, "%s: offset %ji", input_path, (intmax_t) offset);
else if ( done == 0 )
{
input_eof = true;
estimated_total_out = 0;
break;
}
so_far += done;
offset += done;
}
}
}
// The size of the next block to read, set such that after a block of this
// size has been read, all subsequent reads will be aligned.
size_t next_input_blksize = input_blksize - (input_offset % input_blksize);
// Skip past the initial output offset. If the output isn't seekable, write
// that many NUL bytes to the output. Fail hard even if -f as there is no
// way to recover. If in append mode, -O is required to be zero and
// output_offset is already set to the size of the output.
if ( !append &&
output_offset != 0 &&
lseek(output_fd, output_offset, SEEK_SET) < 0 )
{
if ( errno != ESPIPE )
err(1, "%s: lseek", output_path);
memset(buffer, 0, output_blksize);
off_t offset = 0;
while ( offset < output_offset )
{
size_t amount = output_blksize;
if ( (uintmax_t) (output_offset - offset) < (uintmax_t) amount )
amount = output_offset - offset;
size_t so_far = 0;
while ( so_far < amount )
{
progress(start, 0, estimated_total_out, human_readable,
&last_statistic, interval);
ssize_t done =
write(output_fd, buffer + so_far, amount - so_far);
if ( done < 0 && errno == EINTR )
done = 0;
else if ( done < 0 )
err(1, "%s: offset %ji", output_path, (intmax_t) offset);
so_far += done;
offset += done;
}
}
}
// The size of the next block to write, set such that after a block of this
// size has been written, all subsequent writes will be aligned.
size_t next_output_blksize =
output_blksize - (output_offset % output_blksize);
// The total amount of bytes that has been read.
off_t total_in = 0;
// The total amount of bytes that has been written.
off_t total_out = 0;
// The offset in the ring buffer where data begins.
size_t buffer_offset = 0;
// The amount of data bytes in the in the ring buffer.
size_t buffer_used = 0;
// IO vector for efficient IO in case the ring buffer data wraps.
struct iovec iov[2];
memset(iov, 0, sizeof(iov));
// The main loop. If an output block can't be written, read another input
// block. If an output block can be written, write it.
int exit_status = 0;
do
{
// Read another input block, unless enough data has already been read,
// or an end of file condition has been encountered.
if ( !input_eof && count != -1 && count <= total_in )
{
input_eof = true;
estimated_total_out = total_in;
}
else if ( !input_eof && buffer_used < next_output_blksize )
{
size_t left = next_input_blksize;
next_input_blksize = input_blksize;
if ( count != -1 &&
(uintmax_t) (count - total_in) < (uintmax_t) left )
left = count - total_in;
while ( left )
{
progress(start, total_out, estimated_total_out, human_readable,
&last_statistic, interval);
assert(left <= buffer_size - buffer_used);
size_t buffer_end = buffer_offset + buffer_used;
if ( buffer_size < buffer_end )
buffer_end -= buffer_size;
size_t sequential = buffer_size - buffer_end;
ssize_t done;
if ( left <= sequential )
done = read(input_fd, buffer + buffer_end, left);
else
{
iov[0].iov_base = buffer + buffer_end;
iov[0].iov_len = sequential;
iov[1].iov_base = buffer;
iov[1].iov_len = left - sequential;
done = readv(input_fd, iov, 2);
}
if ( done < 0 && errno == EINTR )
;
else if ( done < 0 && !force )
err(1, "%s: offset %ji", input_path,
(intmax_t) input_offset);
else if ( done == 0 )
{
input_eof = true;
estimated_total_out = total_in;
break;
}
else
{
if ( done < 0 && force )
{
warn("%s: offset %ji", input_path,
(intmax_t) input_offset);
// Skip until the next input block, or native input block
// (whichever comes first).
size_t until_next_native_block =
input_st.st_blksize -
(input_offset % input_st.st_blksize);
size_t skip = left < until_next_native_block ?
left : until_next_native_block;
// But don't skip past the end of the input.
off_t possible = input_offset <= input_st.st_size ?
input_st.st_size - input_offset : 0;
if ( (uintmax_t) possible < (uintmax_t) skip )
skip = possible;
if ( lseek(input_fd, left, SEEK_CUR) < 0 )
err(1, "%s: lseek", input_path);
// Check if we reached the end of the file.
if ( skip == 0 )
{
input_eof = true;
estimated_total_out = total_in;
break;
}
if ( skip <= sequential )
memset(buffer + buffer_end, 0, skip);
else
{
memset(buffer + buffer_end, 0, sequential);
memset(buffer, 0, skip - sequential);
}
done = skip;
exit_status = 1;
}
if ( OFF_MAX - input_offset < done )
{
errno = EOVERFLOW;
err(1, "%s: offset", input_path);
}
left -= done;
input_offset += done;
buffer_used += done;
total_in += done;
// The estimate is wrong if too much has been read.
if ( estimated_total_out < total_in )
estimated_total_out = -1;
}
}
}
// If requested, pad the final block with NUL bytes until the next
// output block size boundrary in the output.
if ( pad && (input_eof && 0 < buffer_used) &&
buffer_used < next_output_blksize )
{
size_t left = next_output_blksize - buffer_used;
size_t buffer_end = buffer_offset + buffer_used;
if ( buffer_size < buffer_end )
buffer_end -= buffer_size;
size_t sequential = buffer_size - buffer_end;
if ( left <= sequential )
memset(buffer + buffer_end, 0, left);
else
{
memset(buffer + buffer_end, 0, sequential);
memset(buffer, 0, left - sequential);
}
buffer_used = next_output_blksize;
estimated_total_out = total_out + buffer_used;
pad = false;
}
// If the end of the input has been reached or an fuæll output block can
// written out, write out an output block.
if ( (input_eof && 0 < buffer_used) ||
next_output_blksize <= buffer_used )
{
size_t left = next_output_blksize < buffer_used ?
next_output_blksize : buffer_used;
next_output_blksize = output_blksize;
while ( left )
{
progress(start, total_out, estimated_total_out, human_readable,
&last_statistic, interval);
size_t sequential = buffer_size - buffer_offset;
ssize_t done;
if ( left <= sequential )
done = write(output_fd, buffer + buffer_offset, left);
else
{
iov[0].iov_base = buffer + buffer_offset;
iov[0].iov_len = sequential;
iov[1].iov_base = buffer;
iov[1].iov_len = left - sequential;
done = writev(output_fd, iov, 2);
}
if ( done < 0 && errno == EINTR )
;
else if ( done < 0 && (!force || append) )
err(1, "%s: offset %ji", output_path,
(intmax_t) output_offset);
else
{
// -f doesn't make sense in append mode as the error can't
// be skipped past.
if ( done < 0 && force && !append )
{
warn("%s: offset %ji", output_path,
(intmax_t) output_offset);
// Skip until the next output block or native output
// block (whichever comes first).
size_t until_next_native_block =
output_st.st_blksize -
(output_offset % output_st.st_blksize);
size_t skip = left < until_next_native_block ?
left : until_next_native_block;
if ( lseek(output_fd, skip, SEEK_CUR) < 0 )
err(1, "%s: lseek", output_path);
done = skip;
exit_status = 1;
}
if ( OFF_MAX - output_offset < done )
{
errno = EOVERFLOW;
err(1, "%s: offset", output_path);
}
left -= done;
buffer_offset += done;
if ( buffer_size <= buffer_offset )
buffer_offset -= buffer_size;
buffer_used -= done;
if ( buffer_used == 0 )
buffer_offset = 0;
output_offset += done;
total_out += done;
// The estimate is wrong if too much has been written.
if ( estimated_total_out < total_out )
estimated_total_out = -1;
}
}
}
} while ( !(input_eof && buffer_used == 0) );
munmap(buffer, buffer_size);
if ( truncate && ftruncate(output_fd, output_offset) < 0 )
err(1, "truncate: %s", output_path);
if ( sync && fsync(output_fd) < 0 )
err(1, "sync: %s", output_path);
if ( close(input_fd) < 0 )
err(1, "close: %s", input_path);
if ( close(output_fd) < 0 )
err(1, "close: %s", output_path);
if ( verbose || interrupted || signaled )
{
signaled = 1;
progress(start, total_out, total_out, human_readable, &last_statistic,
interval);
}
return exit_status;
}

View File

@ -109,6 +109,12 @@ void suggest_reboot(const char* filename)
}
}
void suggest_rw(const char* filename)
{
fprintf(stderr, "No command '%s' found, did you mean:\n", filename);
fprintf(stderr, " Command 'rw' from package 'rw'\n");
}
int main(int argc, char* argv[])
{
const char* filename = 2 <= argc ? argv[1] : argv[0];
@ -134,6 +140,8 @@ int main(int argc, char* argv[])
suggest_poweroff(filename);
else if ( !strcmp(filename, "reboot") )
suggest_reboot(filename);
else if ( !strcmp(filename, "dd") )
suggest_rw(filename);
fprintf(stderr, "%s: command not found\n", filename);
return 127;
}