Home | History | Annotate | Download | only in util
      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 
     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) {
     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 
     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 
    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
    229