truncate(1): Add hole-punching support

This commit adds hole-punching support to the truncate(1) utility. If
the option -d is specified, truncate(1) performs zeroing, and if
possible hole-punching in case the operation is supported by the
underlying file system of the specified files.

Sponsored by:	The FreeBSD Foundation
Reviewed by:	kib
Differential Revision:	https://reviews.freebsd.org/D31556
This commit is contained in:
Ka Ho Ng 2021-08-19 18:30:41 +08:00
parent 78267c2e70
commit 5ee2c35751
2 changed files with 115 additions and 27 deletions

View file

@ -1,6 +1,10 @@
.\" .\"
.\" Copyright (c) 2000 Sheldon Hearn <sheldonh@FreeBSD.org>. .\" Copyright (c) 2000 Sheldon Hearn <sheldonh@FreeBSD.org>.
.\" All rights reserved. .\" All rights reserved.
.\" Copyright (c) 2021 The FreeBSD Foundation
.\"
.\" Portions of this manual page were written by Ka Ho Ng <khng@FreeBSD.org>
.\" under sponsorship from the FreeBSD Foundation.
.\" .\"
.\" Redistribution and use in source and binary forms, with or without .\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions .\" modification, are permitted provided that the following conditions
@ -25,12 +29,12 @@
.\" .\"
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd July 27, 2020 .Dd August 18, 2021
.Dt TRUNCATE 1 .Dt TRUNCATE 1
.Os .Os
.Sh NAME .Sh NAME
.Nm truncate .Nm truncate
.Nd truncate or extend the length of files .Nd truncate, extend the length of files, or perform space management in files
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl c .Op Fl c
@ -39,7 +43,7 @@
.Sm off .Sm off
.Op Cm + | - | % | / .Op Cm + | - | % | /
.Ar size .Ar size
.Op Cm K | k | M | m | G | g | T | t .Op Cm SUFFIX
.Sm on .Sm on
.Xc .Xc
.Ek .Ek
@ -50,10 +54,32 @@
.Fl r Ar rfile .Fl r Ar rfile
.Ek .Ek
.Ar .Ar
.Nm
.Op Fl c
.Bk -words
.Fl d
.Oo
.Fl o Xo
.Sm off
.Ar offset
.Op Cm SUFFIX
.Sm on
.Xc
.Oc
.Fl l Xo
.Sm off
.Ar length
.Op Cm SUFFIX
.Sm on
.Xc
.Ek
.Ar
.Sh DESCRIPTION .Sh DESCRIPTION
The The
.Nm .Nm
utility adjusts the length of each regular file given on the command-line. utility adjusts the length of each regular file given on the command-line, or
performs space management with the given offset and the length over a regular
file given on the command-line.
.Pp .Pp
The following options are available: The following options are available:
.Bl -tag -width indent .Bl -tag -width indent
@ -71,7 +97,7 @@ Truncate or extend files to the length of the file
.Sm off .Sm off
.Op Cm + | - | % | / .Op Cm + | - | % | /
.Ar size .Ar size
.Op Cm K | k | M | m | G | g | T | t .Op Cm SUFFIX
.Sm on .Sm on
.Xc .Xc
If the If the
@ -100,10 +126,28 @@ Otherwise, the
.Ar size .Ar size
argument specifies an absolute length to which all files argument specifies an absolute length to which all files
should be extended or reduced as appropriate. should be extended or reduced as appropriate.
.It Fl d
Zero a region in the specified file.
If the underlying file system of the given file supports hole-punching,
file system space deallocation may be performed in the operation region.
.It Fl o Ar offset
The space management operation is performed at the given
.Ar offset
bytes in the file.
If this option is not specified, the operation is performed at the beginning of the file.
.It Fl l Ar length
The length of the operation range in bytes.
This option must always be specified if option
.Fl d
is specified, and must be greater than 0.
.El
.Pp .Pp
The The
.Ar size .Ar size ,
argument may be suffixed with one of .Ar offset
and
.Ar length
arguments may be suffixed with one of
.Cm K , .Cm K ,
.Cm M , .Cm M ,
.Cm G .Cm G
@ -112,7 +156,6 @@ or
(either upper or lower case) to indicate a multiple of (either upper or lower case) to indicate a multiple of
Kilobytes, Megabytes, Gigabytes or Terabytes Kilobytes, Megabytes, Gigabytes or Terabytes
respectively. respectively.
.El
.Pp .Pp
Exactly one of the Exactly one of the
.Fl r .Fl r
@ -183,6 +226,7 @@ ls -l test_file*
.Sh SEE ALSO .Sh SEE ALSO
.Xr dd 1 , .Xr dd 1 ,
.Xr touch 1 , .Xr touch 1 ,
.Xr fspacectl 2 ,
.Xr truncate 2 .Xr truncate 2
.Sh STANDARDS .Sh STANDARDS
The The
@ -198,3 +242,6 @@ The
.Nm .Nm
utility was written by utility was written by
.An Sheldon Hearn Aq Mt sheldonh@starjuice.net . .An Sheldon Hearn Aq Mt sheldonh@starjuice.net .
Hole-punching support of this
utility was developed by
.An Ka Ho Ng Aq Mt khng@FreeBSD.org .

View file

@ -4,6 +4,11 @@
* Copyright (c) 2000 Sheldon Hearn <sheldonh@FreeBSD.org>. * Copyright (c) 2000 Sheldon Hearn <sheldonh@FreeBSD.org>.
* All rights reserved. * All rights reserved.
* *
* Copyright (c) 2021 The FreeBSD Foundation
*
* Portions of this software were developed by Ka Ho Ng <khng@FreeBSD.org>
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
* are met: * are met:
@ -49,26 +54,36 @@ main(int argc, char **argv)
{ {
struct stat sb; struct stat sb;
mode_t omode; mode_t omode;
off_t oflow, rsize, sz, tsize, round; off_t oflow, rsize, sz, tsize, round, off, len;
uint64_t usz; uint64_t usz;
int ch, error, fd, oflags; int ch, error, fd, oflags, r;
int do_dealloc;
int do_truncate;
int no_create; int no_create;
int do_relative; int do_relative;
int do_round; int do_round;
int do_refer; int do_refer;
int got_size; int got_size;
char *fname, *rname; char *fname, *rname;
struct spacectl_range sr;
fd = -1; fd = -1;
rsize = tsize = sz = 0; rsize = tsize = sz = off = 0;
no_create = do_relative = do_round = do_refer = got_size = 0; len = -1;
error = 0; do_dealloc = no_create = do_relative = do_round = do_refer =
got_size = 0;
do_truncate = 1;
error = r = 0;
rname = NULL; rname = NULL;
while ((ch = getopt(argc, argv, "cr:s:")) != -1) while ((ch = getopt(argc, argv, "cdr:s:o:l:")) != -1)
switch (ch) { switch (ch) {
case 'c': case 'c':
no_create = 1; no_create = 1;
break; break;
case 'd':
do_dealloc = 1;
do_truncate = 0;
break;
case 'r': case 'r':
do_refer = 1; do_refer = 1;
rname = optarg; rname = optarg;
@ -89,6 +104,22 @@ main(int argc, char **argv)
-(off_t)usz : (off_t)usz; -(off_t)usz : (off_t)usz;
got_size = 1; got_size = 1;
break; break;
case 'o':
if (expand_number(optarg, &usz) == -1 ||
(off_t)usz < 0)
errx(EXIT_FAILURE,
"invalid offset argument `%s'", optarg);
off = usz;
break;
case 'l':
if (expand_number(optarg, &usz) == -1 ||
(off_t)usz <= 0)
errx(EXIT_FAILURE,
"invalid length argument `%s'", optarg);
len = usz;
break;
default: default:
usage(); usage();
/* NOTREACHED */ /* NOTREACHED */
@ -98,19 +129,22 @@ main(int argc, char **argv)
argc -= optind; argc -= optind;
/* /*
* Exactly one of do_refer or got_size must be specified. Since * Exactly one of do_refer, got_size or do_dealloc must be specified.
* do_relative implies got_size, do_relative and do_refer are * Since do_relative implies got_size, do_relative, do_refer and
* also mutually exclusive. See usage() for allowed invocations. * do_dealloc are also mutually exclusive. If do_dealloc is specified,
* the length argument must be set. See usage() for allowed
* invocations.
*/ */
if (do_refer + got_size != 1 || argc < 1) if (argc < 1 || do_refer + got_size + do_dealloc != 1 ||
(do_dealloc == 1 && len == -1))
usage(); usage();
if (do_refer) { if (do_refer == 1) {
if (stat(rname, &sb) == -1) if (stat(rname, &sb) == -1)
err(EXIT_FAILURE, "%s", rname); err(EXIT_FAILURE, "%s", rname);
tsize = sb.st_size; tsize = sb.st_size;
} else if (do_relative || do_round) } else if (do_relative == 1 || do_round == 1)
rsize = sz; rsize = sz;
else else if (do_dealloc == 0)
tsize = sz; tsize = sz;
if (no_create) if (no_create)
@ -129,7 +163,7 @@ main(int argc, char **argv)
} }
continue; continue;
} }
if (do_relative) { if (do_relative == 1) {
if (fstat(fd, &sb) == -1) { if (fstat(fd, &sb) == -1) {
warn("%s", fname); warn("%s", fname);
error++; error++;
@ -144,7 +178,7 @@ main(int argc, char **argv)
} }
tsize = oflow; tsize = oflow;
} }
if (do_round) { if (do_round == 1) {
if (fstat(fd, &sb) == -1) { if (fstat(fd, &sb) == -1) {
warn("%s", fname); warn("%s", fname);
error++; error++;
@ -166,10 +200,16 @@ main(int argc, char **argv)
if (tsize < 0) if (tsize < 0)
tsize = 0; tsize = 0;
if (ftruncate(fd, tsize) == -1) { if (do_dealloc == 1) {
sr.r_offset = off;
sr.r_len = len;
r = fspacectl(fd, SPACECTL_DEALLOC, &sr, 0, &sr);
}
if (do_truncate == 1)
r = ftruncate(fd, tsize);
if (r == -1) {
warn("%s", fname); warn("%s", fname);
error++; error++;
continue;
} }
} }
if (fd != -1) if (fd != -1)
@ -181,8 +221,9 @@ main(int argc, char **argv)
static void static void
usage(void) usage(void)
{ {
fprintf(stderr, "%s\n%s\n", fprintf(stderr, "%s\n%s\n%s\n",
"usage: truncate [-c] -s [+|-|%|/]size[K|k|M|m|G|g|T|t] file ...", "usage: truncate [-c] -s [+|-|%|/]size[K|k|M|m|G|g|T|t] file ...",
" truncate [-c] -r rfile file ..."); " truncate [-c] -r rfile file ...",
" truncate [-c] -d [-o offset[K|k|M|m|G|g|T|t]] -l length[K|k|M|m|G|g|T|t] file ...");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }