summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2012-05-09 23:53:16 -0700
committerJim Meyering <meyering@redhat.com>2012-05-10 11:02:42 +0200
commit9d308df13271a852aee7d46c65432fa84145ea31 (patch)
tree4973d91ba1d10e86875e8c2be8079dca131dad85
parent2c436decf8bc57a9173c940a26c80358d499e1b6 (diff)
downloadcoreutils-9d308df13271a852aee7d46c65432fa84145ea31.tar.gz
coreutils-9d308df13271a852aee7d46c65432fa84145ea31.tar.bz2
coreutils-9d308df13271a852aee7d46c65432fa84145ea31.zip
maint: handle file sizes more reliably
Problem reported by Samuel Thibault in <http://bugs.gnu.org/11424>. * NEWS: Document this. * src/dd.c (skip): Handle skipping past EOF on shared or typed memory objects the same way as with regular files. (dd_copy): It's OK to truncate shared memory objects. * src/du.c (duinfo_add): Check for overflow. (print_only_size): Report overflow. (process_file): Ignore negative file sizes in the --apparent-size case. * src/od.c (skip): Fix comment about st_size. * src/split.c (main): * src/truncate.c (do_ftruncate, main): On files where st_size is not portable, fall back on using lseek with SEEK_END to determine the size. Although strictly speaking POSIX says the behavior is implementation-defined, in practice if lseek returns a nonnegative value it's a reasonable one to use for the file size. * src/system.h (usable_st_size): Symlinks have reliable st_size too. * tests/misc/truncate-dir-fail: Don't assume that getting the size of a dir is not allowed, as it's now allowed on many platforms, e.g., GNU/Linux.
-rw-r--r--NEWS3
-rw-r--r--src/dd.c8
-rw-r--r--src/du.c12
-rw-r--r--src/od.c3
-rw-r--r--src/split.c14
-rw-r--r--src/system.h3
-rw-r--r--src/truncate.c57
-rwxr-xr-xtests/misc/truncate-dir-fail3
8 files changed, 69 insertions, 34 deletions
diff --git a/NEWS b/NEWS
index 7ef2f5476..e56f8fae3 100644
--- a/NEWS
+++ b/NEWS
@@ -27,6 +27,9 @@ GNU coreutils NEWS -*- outline -*-
** New features
+ split and truncate now allow any seekable files in situations where
+ the file size is needed, instead of insisting on regular files.
+
fmt now accepts the --goal=WIDTH (-g) option.
** Changes in behavior
diff --git a/src/dd.c b/src/dd.c
index 4626de2ab..163d514c0 100644
--- a/src/dd.c
+++ b/src/dd.c
@@ -1544,7 +1544,7 @@ skip (int fdesc, char const *file, uintmax_t records, size_t blocksize,
struct stat st;
if (fstat (STDIN_FILENO, &st) != 0)
error (EXIT_FAILURE, errno, _("cannot fstat %s"), quote (file));
- if (S_ISREG (st.st_mode) && st.st_size < (input_offset + offset))
+ if (usable_st_size (&st) && st.st_size < input_offset + offset)
{
/* When skipping past EOF, return the number of _full_ blocks
* that are not skipped, and set offset to EOF, so the caller
@@ -2104,8 +2104,8 @@ dd_copy (void)
}
}
- /* If the last write was converted to a seek, then for a regular file,
- ftruncate to extend the size. */
+ /* If the last write was converted to a seek, then for a regular file
+ or shared memory object, ftruncate to extend the size. */
if (final_op_was_seek)
{
struct stat stdout_stat;
@@ -2114,7 +2114,7 @@ dd_copy (void)
error (0, errno, _("cannot fstat %s"), quote (output_file));
return EXIT_FAILURE;
}
- if (S_ISREG (stdout_stat.st_mode))
+ if (S_ISREG (stdout_stat.st_mode) || S_TYPEISSHM (&stdout_stat))
{
off_t output_offset = lseek (STDOUT_FILENO, 0, SEEK_CUR);
if (output_offset > stdout_stat.st_size)
diff --git a/src/du.c b/src/du.c
index 41c953541..733394126 100644
--- a/src/du.c
+++ b/src/du.c
@@ -99,7 +99,8 @@ duinfo_set (struct duinfo *a, uintmax_t size, struct timespec tmax)
static inline void
duinfo_add (struct duinfo *a, struct duinfo const *b)
{
- a->size += b->size;
+ uintmax_t sum = a->size + b->size;
+ a->size = a->size <= sum ? sum : UINTMAX_MAX;
if (timespec_cmp (a->tmax, b->tmax) < 0)
a->tmax = b->tmax;
}
@@ -370,8 +371,11 @@ static void
print_only_size (uintmax_t n_bytes)
{
char buf[LONGEST_HUMAN_READABLE + 1];
- fputs (human_readable (n_bytes, buf, human_output_opts,
- 1, output_block_size), stdout);
+ fputs ((n_bytes == UINTMAX_MAX
+ ? _("Infinity")
+ : human_readable (n_bytes, buf, human_output_opts,
+ 1, output_block_size)),
+ stdout);
}
/* Print size (and optionally time) indicated by *PDUI, followed by STRING. */
@@ -495,7 +499,7 @@ process_file (FTS *fts, FTSENT *ent)
duinfo_set (&dui,
(apparent_size
- ? sb->st_size
+ ? MAX (0, sb->st_size)
: (uintmax_t) ST_NBLOCKS (*sb) * ST_NBLOCKSIZE),
(time_type == time_mtime ? get_stat_mtime (sb)
: time_type == time_atime ? get_stat_atime (sb)
diff --git a/src/od.c b/src/od.c
index 759379692..a25f96501 100644
--- a/src/od.c
+++ b/src/od.c
@@ -983,8 +983,7 @@ skip (uintmax_t n_skip)
if (fstat (fileno (in_stream), &file_stats) == 0)
{
- /* The st_size field is valid only for regular files
- (and for symbolic links, which cannot occur here).
+ /* The st_size field is valid for regular files.
If the number of bytes left to skip is larger than
the size of the current file, we can decrement n_skip
and go on to the next file. Skip this optimization also
diff --git a/src/split.c b/src/split.c
index 062aedea9..53ee2719d 100644
--- a/src/split.c
+++ b/src/split.c
@@ -1069,7 +1069,7 @@ main (int argc, char **argv)
static char const multipliers[] = "bEGKkMmPTYZ0";
int c;
int digits_optind = 0;
- off_t file_size;
+ off_t file_size IF_LINT (= 0);
initialize_main (&argc, &argv);
set_program_name (argv[0]);
@@ -1340,12 +1340,18 @@ main (int argc, char **argv)
if (in_blk_size == 0)
in_blk_size = io_blksize (stat_buf);
- /* stat.st_size is valid only for regular files. For others, use 0. */
- file_size = S_ISREG (stat_buf.st_mode) ? stat_buf.st_size : 0;
-
if (split_type == type_chunk_bytes || split_type == type_chunk_lines)
{
off_t input_offset = lseek (STDIN_FILENO, 0, SEEK_CUR);
+ if (usable_st_size (&stat_buf))
+ file_size = stat_buf.st_size;
+ else if (0 <= input_offset)
+ {
+ file_size = lseek (STDIN_FILENO, 0, SEEK_END);
+ input_offset = (file_size < 0
+ ? file_size
+ : lseek (STDIN_FILENO, input_offset, SEEK_SET));
+ }
if (input_offset < 0)
error (EXIT_FAILURE, 0, _("%s: cannot determine file size"),
quote (infile));
diff --git a/src/system.h b/src/system.h
index e3d31563c..06f09cba6 100644
--- a/src/system.h
+++ b/src/system.h
@@ -605,7 +605,8 @@ bad_cast (char const *s)
static inline bool
usable_st_size (struct stat const *sb)
{
- return S_ISREG (sb->st_mode) || S_TYPEISSHM (sb) || S_TYPEISTMO (sb);
+ return (S_ISREG (sb->st_mode) || S_ISLNK (sb->st_mode)
+ || S_TYPEISSHM (sb) || S_TYPEISTMO (sb));
}
void usage (int status) ATTRIBUTE_NORETURN;
diff --git a/src/truncate.c b/src/truncate.c
index 9b847d22a..e37ab3800 100644
--- a/src/truncate.c
+++ b/src/truncate.c
@@ -157,23 +157,36 @@ do_ftruncate (int fd, char const *fname, off_t ssize, off_t rsize,
}
if (rel_mode)
{
- uintmax_t const fsize = rsize < 0 ? sb.st_size : rsize;
+ uintmax_t fsize;
- if (rsize < 0) /* fstat used above to get size. */
+ if (0 <= rsize)
+ fsize = rsize;
+ else
{
- if (!S_ISREG (sb.st_mode) && !S_TYPEISSHM (&sb))
+ off_t file_size;
+ if (usable_st_size (&sb))
{
- error (0, 0, _("cannot get the size of %s"), quote (fname));
- return false;
+ file_size = sb.st_size;
+ if (file_size < 0)
+ {
+ /* Sanity check. Overflow is the only reason I can think
+ this would ever go negative. */
+ error (0, 0, _("%s has unusable, apparently negative size"),
+ quote (fname));
+ return false;
+ }
}
- if (sb.st_size < 0)
+ else
{
- /* Sanity check. Overflow is the only reason I can think
- this would ever go negative. */
- error (0, 0, _("%s has unusable, apparently negative size"),
- quote (fname));
- return false;
+ file_size = lseek (fd, 0, SEEK_END);
+ if (file_size < 0)
+ {
+ error (0, errno, _("cannot get the size of %s"),
+ quote (fname));
+ return false;
+ }
}
+ fsize = file_size;
}
if (rel_mode == rm_min)
@@ -346,17 +359,29 @@ main (int argc, char **argv)
if (ref_file)
{
- /* FIXME: Maybe support getting size of block devices. */
struct stat sb;
+ off_t file_size = -1;
if (stat (ref_file, &sb) != 0)
error (EXIT_FAILURE, errno, _("cannot stat %s"), quote (ref_file));
- if (!S_ISREG (sb.st_mode) && !S_TYPEISSHM (&sb))
- error (EXIT_FAILURE, 0, _("cannot get the size of %s"),
+ if (usable_st_size (&sb))
+ file_size = sb.st_size;
+ else
+ {
+ int ref_fd = open (ref_file, O_RDONLY);
+ if (0 <= ref_fd)
+ {
+ off_t file_end = lseek (ref_fd, 0, SEEK_END);
+ if (0 <= file_end && close (ref_fd) == 0)
+ file_size = file_end;
+ }
+ }
+ if (file_size < 0)
+ error (EXIT_FAILURE, errno, _("cannot get the size of %s"),
quote (ref_file));
if (!got_size)
- size = sb.st_size;
+ size = file_size;
else
- rsize = sb.st_size;
+ rsize = file_size;
}
oflags = O_WRONLY | (no_create ? 0 : O_CREAT) | O_NONBLOCK;
diff --git a/tests/misc/truncate-dir-fail b/tests/misc/truncate-dir-fail
index 116735208..54a31479f 100755
--- a/tests/misc/truncate-dir-fail
+++ b/tests/misc/truncate-dir-fail
@@ -22,7 +22,4 @@ print_ver_ truncate
# truncate on dir not allowed
truncate -s+0 . && fail=1
-# getting the size of a dir is not allowed
-truncate -r. file && fail=1
-
Exit $fail