tail: seek to the end of block devices

* src/tail.c (tail_bytes): Try lseek(..., SEEK_END) when
we can't determine the file size.
* tests/tail-2/end-of-device.sh: Add a new root only test.
* tests/local.mk: Reference the new test.
* NEWS: Mention the improvement.
Paul Eggert suggested using lseek() (rather than ioctl(BLKGETSIZE64)).
Fixes https://bugs.gnu.org/29259
This commit is contained in:
Pádraig Brady 2017-11-12 23:06:08 -08:00
parent 25030d942d
commit 31dd7a0de2
4 changed files with 71 additions and 10 deletions

5
NEWS
View File

@ -31,6 +31,11 @@ GNU coreutils NEWS -*- outline -*-
timeout would have then waited for the time limit to expire.
[bug introduced in coreutils-8.27]
** Improvements
tail --bytes=NUM will efficiently seek to the end of block devices,
rather than reading from the start.
** Build-related
Default man pages are now distributed which are used if perl is

View File

@ -1830,12 +1830,11 @@ tail_bytes (const char *pretty_filename, int fd, uintmax_t n_bytes,
if (from_start)
{
if ( ! presume_input_pipe
&& S_ISREG (stats.st_mode) && n_bytes <= OFF_T_MAX)
{
xlseek (fd, n_bytes, SEEK_CUR, pretty_filename);
*read_pos += n_bytes;
}
if (! presume_input_pipe && n_bytes <= OFF_T_MAX
&& ((S_ISREG (stats.st_mode)
&& xlseek (fd, n_bytes, SEEK_CUR, pretty_filename) >= 0)
|| lseek (fd, n_bytes, SEEK_CUR) != -1))
*read_pos += n_bytes;
else
{
int t = start_bytes (pretty_filename, fd, n_bytes, read_pos);
@ -1846,12 +1845,20 @@ tail_bytes (const char *pretty_filename, int fd, uintmax_t n_bytes,
}
else
{
off_t end_pos = ((! presume_input_pipe && usable_st_size (&stats)
&& n_bytes <= OFF_T_MAX)
? stats.st_size : -1);
off_t end_pos = -1;
off_t current_pos = -1;
if (! presume_input_pipe && n_bytes <= OFF_T_MAX)
{
if (usable_st_size (&stats))
end_pos = stats.st_size;
else if ((current_pos = lseek (fd, -n_bytes, SEEK_END)) != -1)
end_pos = current_pos + n_bytes;
}
if (end_pos <= ST_BLKSIZE (stats))
return pipe_bytes (pretty_filename, fd, n_bytes, read_pos);
off_t current_pos = xlseek (fd, 0, SEEK_CUR, pretty_filename);
if (current_pos == -1)
current_pos = xlseek (fd, 0, SEEK_CUR, pretty_filename);
if (current_pos < end_pos)
{
off_t bytes_remaining = end_pos - current_pos;

View File

@ -135,6 +135,7 @@ all_root_tests = \
tests/rm/one-file-system.sh \
tests/rm/read-only.sh \
tests/tail-2/append-only.sh \
tests/tail-2/end-of-device.sh \
tests/touch/now-owned-by-other.sh
ALL_RECURSIVE_TARGETS += check-root

48
tests/tail-2/end-of-device.sh Executable file
View File

@ -0,0 +1,48 @@
#!/bin/sh
# Ensure that tail seeks to the end of a device
# Copyright (C) 2017 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
print_ver_ tail
# need write access to local device
# (even though we don't actually write anything)
require_root_
require_local_dir_
get_device_size() {
BLOCKDEV=blockdev
$BLOCKDEV -V >/dev/null 2>&1 || BLOCKDEV=/sbin/blockdev
$BLOCKDEV --getsize64 "$1"
}
# Get path to device the current dir is on.
# Note df can only get fs size, not device size.
device=$(df --output=source . | tail -n1) || framework_failure_
dev_size=$(get_device_size "$device") ||
skip_ "failed to determine size of $device"
tail_offset=$(expr $dev_size - 1023) ||
skip_ "failed to determine tail offset"
timeout 10 tail -c 1024 "$device" > end1 || fail=1
timeout 10 tail -c +"$tail_offset" "$device" > end2 || fail=1
test $(wc -c < end1) = 1024 || fail=1
cmp end1 end2 || fail=1
Exit $fail