From 9d308df13271a852aee7d46c65432fa84145ea31 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Wed, 9 May 2012 23:53:16 -0700 Subject: maint: handle file sizes more reliably Problem reported by Samuel Thibault in . * 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. --- NEWS | 3 +++ src/dd.c | 8 +++---- src/du.c | 12 ++++++---- src/od.c | 3 +-- src/split.c | 14 +++++++---- src/system.h | 3 ++- src/truncate.c | 57 +++++++++++++++++++++++++++++++------------- tests/misc/truncate-dir-fail | 3 --- 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 -- cgit v1.2.3-54-g00ecf