1/*
2 * copy_sparse.c -- copy a very large sparse files efficiently
3 * 	(requires root privileges)
4 *
5 * Copyright 2003, 2004 by Theodore Ts'o.
6 *
7 * %Begin-Header%
8 * This file may be redistributed under the terms of the GNU Public
9 * License.
10 * %End-Header%
11 */
12
13#ifndef __linux__
14#include <stdio.h>
15#include <stdlib.h>
16
17int main(void) {
18    fputs("This program is only supported on Linux!\n", stderr);
19    exit(EXIT_FAILURE);
20}
21#else
22#define _LARGEFILE64_SOURCE
23
24#include <stdio.h>
25#include <stdlib.h>
26#include <unistd.h>
27#include <string.h>
28#include <time.h>
29#include <fcntl.h>
30#include <errno.h>
31#ifdef HAVE_GETOPT_H
32#include <getopt.h>
33#else
34extern char *optarg;
35extern int optind;
36#endif
37#include <sys/types.h>
38#include <sys/stat.h>
39#include <sys/vfs.h>
40#include <sys/ioctl.h>
41#include <linux/fd.h>
42
43int verbose = 0;
44
45#define FIBMAP	   _IO(0x00,1)	/* bmap access */
46#define FIGETBSZ   _IO(0x00,2)	/* get the block size used for bmap */
47
48static unsigned long get_bmap(int fd, unsigned long block)
49{
50	int	ret;
51	unsigned long b;
52
53	b = block;
54	ret = ioctl(fd, FIBMAP, &b);
55	if (ret < 0) {
56		if (errno == EPERM) {
57			fprintf(stderr, "No permission to use FIBMAP ioctl; must have root privileges\n");
58			exit(1);
59		}
60		perror("FIBMAP");
61	}
62	return b;
63}
64
65static int full_read(int fd, char *buf, size_t count)
66{
67	int got, total = 0;
68	int pass = 0;
69
70	while (count > 0) {
71		got = read(fd, buf, count);
72		if (got == -1) {
73			if ((errno == EINTR) || (errno == EAGAIN))
74				continue;
75			return total ? total : -1;
76		}
77		if (got == 0) {
78			if (pass++ >= 3)
79				return total;
80			continue;
81		}
82		pass = 0;
83		buf += got;
84		total += got;
85		count -= got;
86	}
87	return total;
88}
89
90static void copy_sparse_file(const char *src, const char *dest)
91{
92	struct stat64	fileinfo;
93	long		lb, i, fd, ofd, bs, block, numblocks;
94	ssize_t		got, got2;
95	off64_t		offset = 0, should_be;
96	char		*buf;
97
98	if (verbose)
99		printf("Copying sparse file from %s to %s\n", src, dest);
100
101	if (strcmp(src, "-")) {
102		if (stat64(src, &fileinfo) < 0) {
103			perror("stat");
104			exit(1);
105		}
106		if (!S_ISREG(fileinfo.st_mode)) {
107			printf("%s: Not a regular file\n", src);
108			exit(1);
109		}
110		fd = open(src, O_RDONLY | O_LARGEFILE);
111		if (fd < 0) {
112			perror("open");
113			exit(1);
114		}
115		if (ioctl(fd, FIGETBSZ, &bs) < 0) {
116			perror("FIGETBSZ");
117			close(fd);
118			exit(1);
119		}
120		if (bs < 0) {
121			printf("%s: Invalid block size: %ld\n", src, bs);
122			exit(1);
123		}
124		if (verbose)
125			printf("Blocksize of file %s is %ld\n", src, bs);
126		numblocks = (fileinfo.st_size + (bs-1)) / bs;
127		if (verbose)
128			printf("File size of %s is %lld (%ld blocks)\n", src,
129			       (long long) fileinfo.st_size, numblocks);
130	} else {
131		fd = 0;
132		bs = 1024;
133	}
134
135	ofd = open(dest, O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0777);
136	if (ofd < 0) {
137		perror(dest);
138		exit(1);
139	}
140
141	buf = malloc(bs);
142	if (!buf) {
143		fprintf(stderr, "Couldn't allocate buffer");
144		exit(1);
145	}
146
147	for (lb = 0; !fd || lb < numblocks; lb++) {
148		if (fd) {
149			block = get_bmap(fd, lb);
150			if (!block)
151				continue;
152			should_be = ((off64_t) lb) * bs;
153			if (offset != should_be) {
154				if (verbose)
155					printf("Seeking to %lld\n", should_be);
156				if (lseek64(fd, should_be, SEEK_SET) == (off_t) -1) {
157					perror("lseek src");
158					exit(1);
159				}
160				if (lseek64(ofd, should_be, SEEK_SET) == (off_t) -1) {
161					perror("lseek dest");
162					exit(1);
163				}
164				offset = should_be;
165			}
166		}
167		got = full_read(fd, buf, bs);
168
169		if (fd == 0 && got == 0)
170			break;
171
172		if (got == bs) {
173			for (i=0; i < bs; i++)
174				if (buf[i])
175					break;
176			if (i == bs) {
177				lseek(ofd, bs, SEEK_CUR);
178				offset += bs;
179				continue;
180			}
181		}
182		got2 = write(ofd, buf, got);
183		if (got != got2) {
184			printf("short write\n");
185			exit(1);
186		}
187		offset += got;
188	}
189	offset = fileinfo.st_size;
190	if (fstat64(ofd, &fileinfo) < 0) {
191		perror("fstat");
192		exit(1);
193	}
194	if (fileinfo.st_size != offset) {
195		lseek64(ofd, offset-1, SEEK_CUR);
196		buf[0] = 0;
197		write(ofd, buf, 1);
198	}
199	close(fd);
200	close(ofd);
201}
202
203static void usage(const char *progname)
204{
205	fprintf(stderr, "Usage: %s [-v] source_file destination_file\n", progname);
206	exit(1);
207}
208
209int main(int argc, char**argv)
210{
211	int c;
212
213	while ((c = getopt(argc, argv, "v")) != EOF)
214		switch (c) {
215		case 'v':
216			verbose++;
217			break;
218		default:
219			usage(argv[0]);
220			break;
221		}
222	if (optind+2 != argc)
223		usage(argv[0]);
224	copy_sparse_file(argv[optind], argv[optind+1]);
225
226	return 0;
227}
228#endif
229