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