mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
Add rw(1).
This commit is contained in:
parent
d393b67d72
commit
d1c3433353
6 changed files with 1360 additions and 0 deletions
1
Makefile
1
Makefile
|
@ -22,6 +22,7 @@ kblayout-compiler \
|
||||||
login \
|
login \
|
||||||
mkinitrd \
|
mkinitrd \
|
||||||
regress \
|
regress \
|
||||||
|
rw \
|
||||||
sf \
|
sf \
|
||||||
sh \
|
sh \
|
||||||
sysinstall \
|
sysinstall \
|
||||||
|
|
1
rw/.gitignore
vendored
Normal file
1
rw/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
rw
|
28
rw/Makefile
Normal file
28
rw/Makefile
Normal 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
453
rw/rw.1
Normal 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
869
rw/rw.c
Normal 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;
|
||||||
|
}
|
|
@ -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[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
const char* filename = 2 <= argc ? argv[1] : argv[0];
|
const char* filename = 2 <= argc ? argv[1] : argv[0];
|
||||||
|
@ -134,6 +140,8 @@ int main(int argc, char* argv[])
|
||||||
suggest_poweroff(filename);
|
suggest_poweroff(filename);
|
||||||
else if ( !strcmp(filename, "reboot") )
|
else if ( !strcmp(filename, "reboot") )
|
||||||
suggest_reboot(filename);
|
suggest_reboot(filename);
|
||||||
|
else if ( !strcmp(filename, "dd") )
|
||||||
|
suggest_rw(filename);
|
||||||
fprintf(stderr, "%s: command not found\n", filename);
|
fprintf(stderr, "%s: command not found\n", filename);
|
||||||
return 127;
|
return 127;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue