Subject: bug#6268: Suggestion: truncate should allow -r and -s options together

From: Pádraig Brady <[email-masked]>
Date: Wed, 26 May 2010 09:54:47 +0100

On 26/05/10 02:01, Pádraig Brady wrote:
> On 25/05/10 15:32, Richard W.M. Jones wrote:
>> The manpage for truncate says:
>>
>>   Note that the -r and -s options are mutually exclusive.
>>
>> and indeed you cannot have both:
>>
>>   $ truncate -r /tmp/ref -s +10M /tmp/new
>>   truncate: you must specify one of `--size' or `--reference'
>>
>> We suggest using the 'truncate' command in the 'virt-resize'
>> documentation[1].  We want users to create a file which is the size of
>> an existing file + some extra amount.  Naturally this would be:
>>
>>   truncate -r old-disk.img -s +1G new-disk.img
>>
>> but this does not work and we have to suggest that users use two
>> separate 'truncate' commands[2].
> 
> That seems like a worthwhile change.
> I.E. allow relative --size with -r.
> I'll cook up a patch in the morning

Attached.

>> Related to this feature request, it would be nice if you could suffix
>> a size with a % sign to indicate that you want to increase a file by a
>> certain percentage of the old or reference size, eg:
>>
>>   truncate -r old-disk.img -s +10% new-disk.img
>>
>>   truncate -r old-disk.img -s 110% new-disk.img
> 
> I'm a little less enthusiastic about the need for that.
> Given how awkward it is to achieve then I might do it
> (as a separate patch). Here's how you'd might do it
> handling overflow currently:
> 
> truncate -s$(echo "$(stat --format %s file) * 1.1" | bc | cut -d. -f1) file
> 
> I guess it would be OK to truncate the result
> rather than round to nearest?

With multi-precision expr now in fedora one could simplify to:

truncate -s$(expr $(stat --format %s file) '*' 110 / 100) file

I'm still not convinced it's worth bringing % calculations within truncate.

cheers,
Pádraig.
>From 63786cdaf2189d326d837a868d936e62e855abcc Mon Sep 17 00:00:00 2001
From: =?utf-8?q?P=C3=A1draig=20Brady?= <[email-masked]>
Date: Wed, 26 May 2010 09:27:53 +0100
Subject: [PATCH] truncate: support sizes relative to an existing file

* doc/coreutils.texi (truncate invocation): Mention that --reference
bases the --size rather than just setting it.
* src/truncate.c (usage): Likewise. Also remove the clause
describing --size and --reference as being mutually exclusive.
(do_truncate): Add an extra parameter to hold the size
of a referenced file, and use it if positive.
(main): Pass the size of a referenced file to do_truncate().
* tests/misc/truncate-parameters: Adjust for the new combinations.
* NEWS: Mention the change
Suggested by Richard W.M. Jones
---
 NEWS                           |    1 +
 doc/coreutils.texi             |    4 ++--
 src/truncate.c                 |   36 ++++++++++++++++++++++--------------
 tests/misc/truncate-parameters |   12 +++++++++++-
 4 files changed, 36 insertions(+), 17 deletions(-)

diff --git a/NEWS b/NEWS
index 19436fe..7a294f4 100644
--- a/NEWS
+++ b/NEWS
@@ -14,6 +14,7 @@ GNU coreutils NEWS                                    -*- outline -*-
 
   sort -g now uses long doubles for greater range and precision.
 
+  truncate now supports setting file sizes relative to a reference file.
 
 * Noteworthy changes in release 8.5 (2010-04-23) [stable]
 
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 115e5fb..d1c3085 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -10767,13 +10767,13 @@ Treat @var{size} as number of I/O blocks of the @var{file} rather than bytes.
 @itemx --reference=@var{rfile}
 @opindex -r
 @opindex --reference
-Set the size of each @var{file} to the same size as @var{rfile}.
+Base the size of each @var{file} on the size of @var{rfile}.
 
 @item -s @var{size}
 @itemx --size=@var{size}
 @opindex -s
 @opindex --size
-Set the size of each @var{file} to this @var{size}.
+Set or adjust the size of each @var{file} according to @var{size}.
 @multiplierSuffixesNoBlocks{size}
 
 @var{size} may also be prefixed by one of the following to adjust
diff --git a/src/truncate.c b/src/truncate.c
index ece52ee..08090ab 100644
--- a/src/truncate.c
+++ b/src/truncate.c
@@ -115,8 +115,8 @@ Mandatory arguments to long options are mandatory for short options too.\n\
   -o, --io-blocks        treat SIZE as number of IO blocks instead of bytes\n\
 "), stdout);
       fputs (_("\
-  -r, --reference=FILE   use this FILE's size\n\
-  -s, --size=SIZE        use this SIZE\n"), stdout);
+  -r, --reference=RFILE  base size on RFILE\n\
+  -s, --size=SIZE        set or adjust the file size by SIZE\n"), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
       emit_size_note ();
@@ -124,10 +124,6 @@ Mandatory arguments to long options are mandatory for short options too.\n\
 SIZE may also be prefixed by one of the following modifying characters:\n\
 `+' extend by, `-' reduce by, `<' at most, `>' at least,\n\
 `/' round down to multiple of, `%' round up to multiple of.\n"), stdout);
-      fputs (_("\
-\n\
-Note that the -r and -s options are mutually exclusive.\n\
-"), stdout);
       emit_ancillary_info ();
     }
   exit (status);
@@ -135,12 +131,13 @@ Note that the -r and -s options are mutually exclusive.\n\
 
 /* return 1 on error, 0 on success */
 static int
-do_ftruncate (int fd, char const *fname, off_t ssize, rel_mode_t rel_mode)
+do_ftruncate (int fd, char const *fname, off_t ssize, off_t rsize,
+              rel_mode_t rel_mode)
 {
   struct stat sb;
   off_t nsize;
 
-  if ((block_mode || rel_mode) && fstat (fd, &sb) != 0)
+  if ((block_mode || (rel_mode && rsize < 0)) && fstat (fd, &sb) != 0)
     {
       error (0, errno, _("cannot fstat %s"), quote (fname));
       return 1;
@@ -161,9 +158,9 @@ do_ftruncate (int fd, char const *fname, off_t ssize, rel_mode_t rel_mode)
     }
   if (rel_mode)
     {
-      uintmax_t const fsize = sb.st_size;
+      uintmax_t const fsize = rsize < 0 ? sb.st_size : rsize;
 
-      if (sb.st_size < 0)
+      if (rsize < 0 && sb.st_size < 0)
         {
           /* Complain only for a regular file, a directory,
              or a shared memory object, as POSIX 1003.1-2004 specifies
@@ -248,6 +245,7 @@ main (int argc, char **argv)
 {
   bool got_size = false;
   off_t size IF_LINT (= 0);
+  off_t rsize = -1;
   rel_mode_t rel_mode = rm_abs;
   mode_t omode;
   int c, errors = 0, fd = -1, oflags;
@@ -335,9 +333,16 @@ main (int argc, char **argv)
   argc -= optind;
 
   /* must specify either size or reference file */
-  if ((ref_file && got_size) || (!ref_file && !got_size))
+  if (!ref_file && !got_size)
+    {
+      error (0, 0, _("you must specify either %s or %s"),
+             quote_n (0, "--size"), quote_n (1, "--reference"));
+      usage (EXIT_FAILURE);
+    }
+  /* must specify a relative size with a reference file */
+  if (ref_file && got_size && !rel_mode)
     {
-      error (0, 0, _("you must specify one of %s or %s"),
+      error (0, 0, _("you must specify a relative %s with %s"),
              quote_n (0, "--size"), quote_n (1, "--reference"));
       usage (EXIT_FAILURE);
     }
@@ -360,7 +365,10 @@ main (int argc, char **argv)
       struct stat sb;
       if (stat (ref_file, &sb) != 0)
         error (EXIT_FAILURE, errno, _("cannot stat %s"), quote (ref_file));
-      size = sb.st_size;
+      if (!got_size)
+        size = sb.st_size;
+      else
+        rsize = sb.st_size;
     }
 
   oflags = O_WRONLY | (no_create ? 0 : O_CREAT) | O_NONBLOCK;
@@ -397,7 +405,7 @@ main (int argc, char **argv)
 
       if (fd != -1)
         {
-          errors += do_ftruncate (fd, fname, size, rel_mode);
+          errors += do_ftruncate (fd, fname, size, rsize, rel_mode);
           if (close (fd) != 0)
             {
               error (0, errno, _("closing %s"), quote (fname));
diff --git a/tests/misc/truncate-parameters b/tests/misc/truncate-parameters
index 6524070..c2f7019 100755
--- a/tests/misc/truncate-parameters
+++ b/tests/misc/truncate-parameters
@@ -30,7 +30,7 @@ truncate --size=0 && fail=1
 # must specify size. don't default to 0
 truncate file && fail=1
 
-# mixture of size & reference not allowed
+# mixture of absolute size & reference not allowed
 truncate --size=0 --reference=file file && fail=1
 
 # blocks without size is not valid
@@ -45,4 +45,14 @@ truncate --size=" >1" file || fail=1 #file now 1
 truncate --size=" +1" file || fail=1 #file now 2
 test $(stat --format %s file) = 2 || fail=1
 
+# reference allowed with relative size
+truncate --size=" +1" -r file file || fail=1 #file now 3
+test $(stat --format %s file) = 3 || fail=1
+
+# reference allowed alone also
+truncate -r file file || fail=1 #file still 3
+test $(stat --format %s file) = 3 || fail=1
+truncate -r file file2 || fail=1 #file2 now 3
+test $(stat --format %s file2) = 3 || fail=1
+
 Exit $fail
-- 
1.6.2.5