1/*-
2 * Copyright (c) 2023 Klara, Inc.
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <err.h>
8#include <fcntl.h>
9#include <stdbool.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <sysexits.h>
13#include <unistd.h>
14
15static bool verbose;
16
17/*
18 * Returns true if the file named by its argument is sparse, i.e. if
19 * seeking to SEEK_HOLE returns a different value than seeking to
20 * SEEK_END.
21 */
22static bool
23sparse(const char *filename)
24{
25	off_t hole, end;
26	int fd;
27
28	if ((fd = open(filename, O_RDONLY)) < 0 ||
29	    (hole = lseek(fd, 0, SEEK_HOLE)) < 0 ||
30	    (end = lseek(fd, 0, SEEK_END)) < 0)
31		err(1, "%s", filename);
32	close(fd);
33	if (end > hole) {
34		if (verbose)
35			printf("%s: hole at %zu\n", filename, (size_t)hole);
36		return (true);
37	}
38	return (false);
39}
40
41static void
42usage(void)
43{
44
45	fprintf(stderr, "usage: sparse [-v] file [...]\n");
46	exit(EX_USAGE);
47}
48
49int
50main(int argc, char *argv[])
51{
52	int opt, rv;
53
54	while ((opt = getopt(argc, argv, "v")) != -1) {
55		switch (opt) {
56		case 'v':
57			verbose = true;
58			break;
59		default:
60			usage();
61			break;
62		}
63	}
64	argc -= optind;
65	argv += optind;
66	if (argc == 0)
67		usage();
68	rv = EXIT_SUCCESS;
69	while (argc-- > 0)
70		if (!sparse(*argv++))
71			rv = EXIT_FAILURE;
72	exit(rv);
73}
74