summaryrefslogtreecommitdiff
path: root/src/truncate.c
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 /src/truncate.c
parent2c436decf8bc57a9173c940a26c80358d499e1b6 (diff)
downloadcoreutils-9d308df13271a852aee7d46c65432fa84145ea31.tar.xz
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.
Diffstat (limited to 'src/truncate.c')
-rw-r--r--src/truncate.c57
1 files changed, 41 insertions, 16 deletions
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;