diff --git a/bin/cp/tests/Makefile b/bin/cp/tests/Makefile --- a/bin/cp/tests/Makefile +++ b/bin/cp/tests/Makefile @@ -3,5 +3,7 @@ PACKAGE= tests ATF_TESTS_SH= cp_test +PROGS+= sparse +BINDIR= ${TESTSDIR} .include diff --git a/bin/cp/tests/cp_test.sh b/bin/cp/tests/cp_test.sh --- a/bin/cp/tests/cp_test.sh +++ b/bin/cp/tests/cp_test.sh @@ -201,7 +201,7 @@ file_is_sparse() { - atf_check test "$(stat -f "%b" "$1")" != "$(stat -f "%z" "$1")" + atf_check ${0%/*}/sparse "$1" } files_are_equal() @@ -213,8 +213,8 @@ atf_test_case sparse_leading_hole sparse_leading_hole_body() { - # A one-megabyte hole followed by one megabyte of data - truncate -s 1M foo + # A 16-megabyte hole followed by one megabyte of data + truncate -s 16M foo seq -f%015g 65536 >>foo file_is_sparse foo @@ -227,14 +227,14 @@ sparse_multiple_holes_body() { # Three one-megabyte blocks of data preceded, separated, and - # followed by one-megabyte holes - truncate -s 1M foo - seq -f%015g >>foo - truncate -s 3M foo - seq -f%015g >>foo - truncate -s 5M foo - seq -f%015g >>foo - truncate -s 7M foo + # followed by 16-megabyte holes + truncate -s 16M foo + seq -f%015g 65536 >>foo + truncate -s 33M foo + seq -f%015g 65536 >>foo + truncate -s 50M foo + seq -f%015g 65536 >>foo + truncate -s 67M foo file_is_sparse foo atf_check cp foo bar @@ -245,8 +245,8 @@ atf_test_case sparse_only_hole sparse_only_hole_body() { - # A one-megabyte hole - truncate -s 1M foo + # A 16-megabyte hole + truncate -s 16M foo file_is_sparse foo atf_check cp foo bar @@ -258,14 +258,14 @@ sparse_to_dev_body() { # Three one-megabyte blocks of data preceded, separated, and - # followed by one-megabyte holes - truncate -s 1M foo - seq -f%015g >>foo - truncate -s 3M foo - seq -f%015g >>foo - truncate -s 5M foo - seq -f%015g >>foo - truncate -s 7M foo + # followed by 16-megabyte holes + truncate -s 16M foo + seq -f%015g 65536 >>foo + truncate -s 33M foo + seq -f%015g 65536 >>foo + truncate -s 50M foo + seq -f%015g 65536 >>foo + truncate -s 67M foo file_is_sparse foo atf_check -o file:foo cp foo /dev/stdout @@ -274,9 +274,9 @@ atf_test_case sparse_trailing_hole sparse_trailing_hole_body() { - # One megabyte of data followed by a one-megabyte hole + # One megabyte of data followed by a 16-megabyte hole seq -f%015g 65536 >foo - truncate -s 2M foo + truncate -s 17M foo file_is_sparse foo atf_check cp foo bar diff --git a/bin/cp/tests/sparse.c b/bin/cp/tests/sparse.c new file mode 100644 --- /dev/null +++ b/bin/cp/tests/sparse.c @@ -0,0 +1,73 @@ +/*- + * Copyright (c) 2023 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +static bool verbose; + +/* + * Returns true if the file named by its argument is sparse, i.e. if + * seeking to SEEK_HOLE returns a different value than seeking to + * SEEK_END. + */ +static bool +sparse(const char *filename) +{ + off_t hole, end; + int fd; + + if ((fd = open(filename, O_RDONLY)) < 0 || + (hole = lseek(fd, 0, SEEK_HOLE)) < 0 || + (end = lseek(fd, 0, SEEK_END)) < 0) + err(1, "%s", filename); + close(fd); + if (end > hole) { + if (verbose) + printf("%s: hole at %zu\n", filename, (size_t)hole); + return (true); + } + return (false); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: sparse [-v] file [...]\n"); + exit(EX_USAGE); +} + +int +main(int argc, char *argv[]) +{ + int opt, rv; + + while ((opt = getopt(argc, argv, "v")) != -1) { + switch (opt) { + case 'v': + verbose = true; + break; + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + if (argc == 0) + usage(); + rv = EXIT_SUCCESS; + while (argc-- > 0) + if (!sparse(*argv++)) + rv = EXIT_FAILURE; + exit(rv); +}