]>
Commit | Line | Data |
---|---|---|
0d2993db TT |
1 | /* |
2 | * copy_sparse.c -- copy a very large sparse files efficiently | |
3 | * (requires root privileges) | |
efc6f628 | 4 | * |
0d2993db TT |
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 | ||
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 | ||
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 | ||
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) { | |
efc6f628 | 73 | if ((errno == EINTR) || (errno == EAGAIN)) |
0d2993db TT |
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 | ||
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); | |
efc6f628 | 100 | |
0d2993db TT |
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) | |
efc6f628 | 128 | printf("File size of %s is %lld (%ld blocks)\n", src, |
0d2993db TT |
129 | (long long) fileinfo.st_size, numblocks); |
130 | } else { | |
131 | fd = 0; | |
132 | bs = 1024; | |
133 | } | |
efc6f628 | 134 | |
0d2993db TT |
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 | } | |
efc6f628 | 146 | |
0d2993db TT |
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) { | |
efc6f628 | 173 | for (i=0; i < bs; i++) |
0d2993db TT |
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 | ||
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 | ||
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 |