]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
fallocate: add --report-holes
authorsyokensyo <syokensyo@hotmail.com>
Wed, 10 Dec 2025 11:53:23 +0000 (12:53 +0100)
committerKarel Zak <kzak@redhat.com>
Wed, 10 Dec 2025 11:56:02 +0000 (12:56 +0100)
[kzak@redhat.com: - cleanup commit message, rebase]

Fixes: #3822
Signed-off-by: Karel Zak <kzak@redhat.com>
bash-completion/fallocate
sys-utils/fallocate.1.adoc
sys-utils/fallocate.c

index dd0d07d07f5199f2d9d74915bc38ccdfc7de670b..e4dd2a2c068de7e70480a15258b2052893cfbdcd 100644 (file)
@@ -18,6 +18,7 @@ _fallocate_module()
                        OPTS="
                                --collapse-range
                                --dig-holes
+                               --report-holes
                                --insert-range
                                --length
                                --keep-size
index 4a96d009d2b48859db7a0cea574a7b5ed28aa2d3..c253fa79a576663bb38279fe90b22482b553d17f 100644 (file)
@@ -16,6 +16,8 @@ fallocate - preallocate or deallocate space to a file
 
 *fallocate* *-d* [*-o* _offset_] [*-l* _length_] _filename_
 
+*fallocate* *-r* [*-o* _offset_] _filename_
+
 *fallocate* *-x* [*-o* _offset_] *-l* _length filename_
 
 == DESCRIPTION
@@ -28,7 +30,7 @@ The exit status returned by *fallocate* is 0 on success and 1 on failure.
 
 The _length_ and _offset_ arguments may be followed by the multiplicative suffixes KiB (=1024), MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB, and YiB (the "iB" is optional, e.g., "K" has the same meaning as "KiB") or the suffixes KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB, and YB.
 
-The options *--collapse-range*, *--dig-holes*, *--punch-hole*, *--zero-range*, *--write-zeroes* and *--posix* are mutually exclusive.
+The options *--collapse-range*, *--dig-holes*, *--report-holes*, *--punch-hole*, *--zero-range*, *--write-zeroes* and *--posix* are mutually exclusive.
 
 *-c*, *--collapse-range*::
 Removes a byte range from a file, without leaving a hole. The byte range to be collapsed starts at _offset_ and continues for _length_ bytes. At the completion of the operation, the contents of the file starting at the location __offset__+_length_ will be appended at the location _offset_, and the file will be _length_ bytes smaller. The option *--keep-size* may not be specified for the collapse-range operation.
@@ -44,6 +46,19 @@ You can think of this option as doing a "*cp --sparse*" and then renaming the de
 +
 See *--punch-hole* for a list of supported filesystems.
 
+*-r*, *--report-holes*::
+Scan the file and report the distribution of holes without modifying it.
+Two kinds of holes are shown:
+file holes – blocks that the filesystem has not allocated (reported by SEEK_HOLE);
+data holes – ranges inside the file which are logically filled with zero bytes but still occupy disk space.
++
+For each hole the offset, end and size in bytes are printed in verbose mode, followed by a summary that gives the total size and percentage of the file that each hole type represents by default.
+If no offset is specified with *--offset*, the whole file is scanned.
++
+This option is read-only and can be used on any file to estimate how much space could be reclaimed by --dig-holes.
++
+See --punch-hole for a list of supported filesystems.
+
 *-i*, *--insert-range*::
 Insert a hole of _length_ bytes from _offset_, shifting existing data.
 
index aedb091cfbdbbf9745f1d5cfaf889c3d4e48ea44..5404a9558910840ca5be9c8c8585763fba6efd50 100644 (file)
@@ -93,6 +93,7 @@ static void __attribute__((__noreturn__)) usage(void)
        fputs(_(" -n, --keep-size      maintain the apparent size of the file\n"), out);
        fputs(_(" -o, --offset <num>   offset for range operations, in bytes\n"), out);
        fputs(_(" -p, --punch-hole     replace a range with a hole (implies -n)\n"), out);
+       fputs(_(" -r, --report-holes   report file holes and data holes info\n"), out);
        fputs(_(" -v, --verbose        verbose mode\n"), out);
        fputs(_(" -w, --write-zeroes   write zeroes and ensure allocation of a range\n"), out);
        fputs(_(" -x, --posix          use posix_fallocate(3) instead of fallocate(2)\n"), out);
@@ -169,11 +170,14 @@ static int is_nul(void *buf, size_t bufsize)
        return cbuf + bufsize < cp;
 }
 
-static void dig_holes(int fd, off_t file_off, off_t len)
+static void dig_holes(int fd, off_t file_off, off_t len, bool report)
 {
        off_t file_end = len ? file_off + len : 0;
        off_t hole_start = 0, hole_sz = 0;
        uintmax_t ct = 0;
+       uintmax_t total_file_hole_sz = 0;
+       off_t file_size = 0;
+       off_t last_end = 0;
        size_t  bufsz;
        char *buf;
        struct stat st;
@@ -194,6 +198,7 @@ static void dig_holes(int fd, off_t file_off, off_t len)
                err(EXIT_FAILURE, _("stat of %s failed"), filename);
 
        bufsz = st.st_blksize;
+       file_size = st.st_size;
 
        if (lseek(fd, file_off, SEEK_SET) < 0)
                err(EXIT_FAILURE, _("seek on %s failed"), filename);
@@ -212,6 +217,24 @@ static void dig_holes(int fd, off_t file_off, off_t len)
                        break;
 
                end = lseek(fd, off, SEEK_HOLE);
+               if (report && end > off && last_end < off) {
+                       /* file hole position found from last_end to off */
+                       off_t hole_size = off - last_end;
+                       total_file_hole_sz += hole_size;
+                       if (verbose)
+                               printf("file hole: offset %ju - %ju: (%ju bytes)\n",
+                                               (uintmax_t)last_end, (uintmax_t)off, (uintmax_t)hole_size);
+               } else if (report && off > end) {
+                       /* file hole position found from end to off */
+                       off_t hole_size = off - end;
+                       total_file_hole_sz += hole_size;
+                       if (verbose)
+                               printf("file hole: offset %ju - %ju: (%ju bytes)\n",
+                                               (uintmax_t)end, (uintmax_t)off, (uintmax_t)hole_size);
+               }
+               if (report) {
+                       last_end = end;
+               }
                if (file_end && end > file_end)
                        end = file_end;
 
@@ -238,8 +261,13 @@ static void dig_holes(int fd, off_t file_off, off_t len)
                                        hole_start = off;
                                hole_sz += rsz;
                         } else if (hole_sz) {
-                               xfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE,
-                                          hole_start, hole_sz);
+                               if (!report)
+                                       xfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE,
+                                                  hole_start, hole_sz);
+                               if (report && verbose)
+                                       printf("data hole: offset %ju - %ju: (%ju bytes)\n",
+                                                       (uintmax_t)hole_start, (uintmax_t)(hole_start+hole_sz),
+                                                       (uintmax_t)hole_sz);
                                ct += hole_sz;
                                hole_sz = hole_start = 0;
                        }
@@ -257,24 +285,46 @@ static void dig_holes(int fd, off_t file_off, off_t len)
                        off += rsz;
                }
                if (hole_sz) {
+                       if (report && verbose)
+                               printf("data hole: offset %ju - %ju: (%ju bytes)\n",
+                                               (uintmax_t)hole_start, (uintmax_t)(hole_start+hole_sz),
+                                               (uintmax_t)hole_sz);
                        off_t alloc_sz = hole_sz;
                        if (off >= end)
                                alloc_sz += st.st_blksize;              /* meet block boundary */
-                       xfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE,
-                                       hole_start, alloc_sz);
+                       if (!report)
+                               xfallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE,
+                                               hole_start, alloc_sz);
                        ct += hole_sz;
+                       if(report)
+                               hole_sz = 0;
                }
                file_off = off;
        }
 
        free(buf);
 
-       if (verbose) {
+       if (!report && verbose) {
                char *str = size_to_human_string(SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE, ct);
                fprintf(stdout, _("%s: %s (%ju bytes) converted to sparse holes.\n"),
                                filename, str, ct);
                free(str);
        }
+       if (report) {
+               printf("\nSummary:\n");
+               /* file holes summary */
+               double file_pct = (file_size == 0) ? 0.0 : (double)total_file_hole_sz / (double)file_size * 100.0;
+               char *str = size_to_human_string(SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE, total_file_hole_sz);
+               printf("file holes: %s (%ju bytes) %.2f%% of the file\n",
+                       str, total_file_hole_sz, file_pct);
+               free(str);
+               /* data holes summary */
+               char *str1 = size_to_human_string(SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE, ct);
+               double data_pct = (file_size == 0) ? 0.0 : (double)ct / (double)file_size * 100.0;
+               printf("data holes: %s (%ju bytes) %.2f%% of the file\n",
+                       str1, ct, data_pct);
+               free(str1);
+       }
 }
 
 int main(int argc, char **argv)
@@ -283,6 +333,7 @@ int main(int argc, char **argv)
        int     fd;
        int     mode = 0;
        int     dig = 0;
+       int     report = 0;
        int     posix = 0;
        loff_t  length = -2LL;
        loff_t  offset = 0;
@@ -294,6 +345,7 @@ int main(int argc, char **argv)
            { "punch-hole",     no_argument,       NULL, 'p' },
            { "collapse-range", no_argument,       NULL, 'c' },
            { "dig-holes",      no_argument,       NULL, 'd' },
+           { "report-holes",   no_argument,       NULL, 'r' },
            { "insert-range",   no_argument,       NULL, 'i' },
            { "zero-range",     no_argument,       NULL, 'z' },
            { "write-zeroes",   no_argument,       NULL, 'w' },
@@ -316,7 +368,7 @@ int main(int argc, char **argv)
        textdomain(PACKAGE);
        close_stdout_atexit();
 
-       while ((c = getopt_long(argc, argv, "hvVncpdizwxl:o:", longopts, NULL))
+       while ((c = getopt_long(argc, argv, "hvVncpdrizwxl:o:", longopts, NULL))
                        != -1) {
 
                err_exclusive_options(c, longopts, excl, excl_st);
@@ -343,6 +395,9 @@ int main(int argc, char **argv)
                case 'p':
                        mode |= FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
                        break;
+               case 'r':
+                       report = 1;
+                       break;
                case 'z':
                        mode |= FALLOC_FL_ZERO_RANGE;
                        break;
@@ -379,7 +434,7 @@ int main(int argc, char **argv)
                        length = 0;
                if (length < 0)
                        errx(EXIT_FAILURE, _("invalid length"));
-       } else {
+       } else if (!report) {
                /* it's safer to require the range specification (--length --offset) */
                if (length == -2LL)
                        errx(EXIT_FAILURE, _("no length argument specified"));
@@ -397,7 +452,9 @@ int main(int argc, char **argv)
                err(EXIT_FAILURE, _("cannot open %s"), filename);
 
        if (dig)
-               dig_holes(fd, offset, length);
+               dig_holes(fd, offset, length, false);
+       else if (report)
+               dig_holes(fd, offset, 0, true);
        else {
                if (posix)
                        xposix_fallocate(fd, offset, length);