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 
main(void)17 int 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
34 extern char *optarg;
35 extern 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 
43 int 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 
get_bmap(int fd,unsigned long block)48 static 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 
full_read(int fd,char * buf,size_t count)65 static 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 
copy_sparse_file(const char * src,const char * dest)90 static 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 
usage(const char * progname)203 static void usage(const char *progname)
204 {
205 	fprintf(stderr, "Usage: %s [-v] source_file destination_file\n", progname);
206 	exit(1);
207 }
208 
main(int argc,char ** argv)209 int 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