BLKFLSBUF only if it is a block device. +sync()
[dm-merge.git] / dm-merge.c
blobdc7db4c8ee8afb8174a558c6f8b1d2833d708b47
1 /******************************************************************************
2 * dm-merge.c
4 * Written by hondza. As far as I'm concerned public domain. Several (GPL'd)
5 * lines (PAGE_ALIGN stuff...) taken from linux kernel source.
7 * Do whatever you wish with it, but don't blame me.
9 *****************************************************************************/
11 #define _FILE_OFFSET_BITS 64
12 #define _GNU_SOURCE
14 #include <stdio.h>
15 #include <string.h>
16 #include <stdlib.h>
18 #include <stddef.h> /* ptrdiff_t */
20 #include <stdint.h> /* uint64_t */
22 #include <errno.h>
24 #include <getopt.h>
26 #include <unistd.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
31 #include <sys/ioctl.h>
32 #include <linux/fs.h> /* BLKGETSIZE64, BLKFLSBUF */
34 #include <endian.h>
37 /* endian stuff */
38 #if __BYTE_ORDER == __BIG_ENDIAN
39 #include <linux/byteorder/big_endian.h>
40 #else
41 #include <linux/byteorder/little_endian.h>
42 #endif
45 #define SNAP_MAGIC 0x70416e53 /* 'SnAp' */
46 #define SNAPSHOT_DISK_VERSION 1
49 /* doubtful stuff; only used with O_DIRECT */
50 #define MAX_CHUNKSIZE 524288
51 #define PAGE_SIZE 4096
52 #define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE)
53 #define ALIGN(x,a) __ALIGN_MASK(x,(typeof(x))(a)-1)
54 #define __ALIGN_MASK(x,mask) (((x)+(mask))&~(mask))
57 uint32_t *magic;
58 uint32_t *valid;
59 uint32_t *version;
60 uint32_t chunk_size=512, *cs;
63 struct disk_exception {
64 uint64_t old_chunk;
65 uint64_t new_chunk;
69 unsigned char *header=NULL, *header_orig=NULL, *buf=NULL, *buf_orig=NULL, *chunk=NULL, *chunk_orig=NULL;
70 char *input_filename=NULL, *output_filename=NULL;
71 int inputfd=-1, outputfd=-1, dry=1, print_dds=0, do_direct_io = 0, verbose = 0, err=-1, err2=-1, flags=0, go=1;
72 unsigned int i=0, retries=0;
73 struct stat st;
74 size_t input_length=0, output_length=0;
75 off_t chunk_now=1;
76 struct disk_exception de;
77 uint64_t *temp_u64;
78 uint64_t total_count=0;
81 /* taken from libtomcrypt */
82 void burn_stack(unsigned long len)
84 unsigned char buf[32];
85 memset(buf, 0, sizeof(buf));
86 if (len > (unsigned long)sizeof(buf))
87 burn_stack(len - sizeof(buf));
91 /* atexit calls it */
92 void cleanup()
94 if(input_filename) free(input_filename);
95 if(output_filename) free(output_filename);
96 if(buf) memset(buf, 0, chunk_size);
97 if(buf_orig) free(buf_orig);
98 if(chunk) memset(chunk, 0, chunk_size);
99 if(chunk_orig) free(chunk_orig);
100 if(header) memset(header, 0, 512);
101 if(header_orig) free(header_orig);
102 /* obfuscate it a little */
103 burn_stack(10*4096);
104 } /* cleanup */
107 /* called for each exception */
108 inline void read_write_chunk()
110 retries=0;
112 if(print_dds) fprintf(stdout, "dd of=\"%s\" seek=%llu if='%s' iflag=direct skip=%llu count=1 bs=%db\n", output_filename ? output_filename : "${origin}", de.old_chunk, input_filename, de.new_chunk, chunk_size/512);
114 if(dry) return;
116 /* read */
117 do {
118 if(0 != retries)
119 fprintf(stderr, "Warning: retrying pread() on exception chunk at %llu\n", de.new_chunk*chunk_size);
121 err = pread(inputfd, chunk, chunk_size, de.new_chunk*chunk_size);
122 if(-1 == err)
124 if(EINTR == errno)
125 continue;
126 perror("pread(inputfd)");
127 exit(1);
129 else if(0 == err)
131 fputs("pread(inputfd): early EOF!\n", stderr);
132 exit(1);
134 else if(err != chunk_size)
136 if(retries++ < 2)
137 continue;
138 fputs("pread(inputfd): incomplete read!\n", stderr);
139 exit(1);
142 break;
143 } while(1);
146 /* write */
147 retries = 0;
148 do {
149 if(0 != retries)
150 fprintf(stderr, "Warning: retrying pwrite() at %llu\n", de.old_chunk*chunk_size);
152 err = pwrite(outputfd, chunk, chunk_size, de.old_chunk*chunk_size);
153 if(-1 == err)
155 if(EINTR == errno)
156 continue;
157 perror("pwrite(outputfd)");
158 exit(1);
160 else if(err != chunk_size)
162 if(retries++ < 2)
163 continue;
164 fputs("pwrite(outputfd): incomplete write!\n", stderr);
165 exit(1);
168 break;
169 } while(1);
171 } /* read_write_chunk() */
175 void help()
177 fputs("Usage: dm-merge [options] -i <snapshot_device> [ -o <output_device> ]\n\n", stderr);
178 fputs("Options:\n", stderr);
179 fputs("-f\t\tREALLY do it (no dry run)\n", stderr);
180 fputs("-d\t\tPrint dd lines as list_exception_chunks.pl would\n", stderr);
181 fputs("-D\t\tTry to use O_DIRECT\n", stderr);
182 fputs("-v\t\tBe verbose (more '-v's increase verbosity)\n", stderr);
183 fputs("-i <file>\tInput (snapshot) COW (copy-on-write) filename\n", stderr);
184 fputs("-o <file>\tOutput (the device being snapshoted) filename\n\n", stderr);
185 fputs("This program is still experimental. USE WITH CARE! Read the README!\n\n", stderr);
186 } /* help() */
191 int main(int argc, char ** argv)
193 int c;
195 atexit(cleanup);
197 while( (c = getopt(argc, argv, "fdi:o:Dvh")) != -1 )
199 switch(c)
201 case 'f': dry = 0; break;
202 case 'd': print_dds = 1; break;
203 case 'D': do_direct_io = 1; break;
204 case 'v': verbose++; break;
205 case 'i': input_filename = strdup(optarg); break;
206 case 'o': output_filename = strdup(optarg); break;
207 case 'h': help(); exit(0);
208 default: help(); exit(1);
209 } /* switch argument */
210 } /* while getopt() */
213 if(!input_filename)
215 fputs("Error: input filename not specified\n\n", stderr);
216 help();
217 exit(1);
220 if(!dry && !output_filename)
222 fputs("Error: no dry run and no output filename\n\n", stderr);
223 help();
224 exit(1);
228 /* better safe than sorry */
229 sync();
231 /* check and open snapshot */
233 flags = O_RDONLY;
235 err = stat(input_filename, &st);
236 if(-1 == err)
238 perror("stat(snapshot)");
239 exit(1);
242 /* block device; will set O_DIRECT */
243 if(S_ISBLK(st.st_mode) && do_direct_io)
244 flags |= O_DIRECT;
246 inputfd = open(input_filename, flags);
247 if(-1 == inputfd)
249 perror("open(snapshot)");
250 exit(1);
253 /* determine size & flush buffers */
254 if(S_ISBLK(st.st_mode))
256 err = ioctl(inputfd, BLKGETSIZE64, &input_length);
257 if(-1 == err)
259 perror("ioctl(snapshot, BLKGETSIZE64)");
260 exit(1);
263 err = ioctl(inputfd, BLKFLSBUF, 0);
264 if(-1 == err)
266 perror("ioctl(snapshot, BLKFLSBUF)");
267 exit(1);
270 else
271 input_length = st.st_size;
273 /* now the same for the output */
274 if(!dry)
276 flags = O_WRONLY;
278 err = stat(output_filename, &st);
279 if(-1 == err)
281 perror("stat(output)");
282 exit(1);
285 /* block device; will set O_DIRECT */
286 if(S_ISBLK(st.st_mode) && do_direct_io)
287 flags |= O_DIRECT;
289 outputfd = open(output_filename, flags);
290 if(-1 == outputfd)
292 perror("open(output)");
293 exit(1);
296 /* determine size & flush buffers */
297 if(S_ISBLK(st.st_mode))
299 err = ioctl(outputfd, BLKGETSIZE64, &output_length);
300 if(-1 == err)
302 perror("ioctl(output, BLKGETSIZE64)");
303 exit(1);
306 err = ioctl(outputfd, BLKFLSBUF, 0);
307 if(-1 == err)
309 perror("ioctl(output, BLKFLSBUF)");
310 exit(1);
313 else
314 output_length = st.st_size;
318 /* FIXME perhaps add an override option? */
319 if(input_length < 4096 || (!dry && output_length < (4 * 1024 * 1024)))
321 fputs("Error: suspicious file/device sizes\n", stderr);
322 exit(1);
326 /* the allocations; special care for O_DIRECT */
327 if(do_direct_io)
329 header_orig = malloc(512 + PAGE_SIZE);
330 if(!header_orig)
332 perror("malloc()");
333 exit(1);
335 header = (unsigned char *) PAGE_ALIGN((ptrdiff_t) header_orig);
336 if(verbose)
337 fprintf(stdout, "header_orig = %p (%lu), header = %p (%lu)\n", header_orig, (unsigned long) header_orig % PAGE_SIZE, header, (unsigned long) header % PAGE_SIZE);
339 else
341 header_orig = header = malloc(512);
342 if(!header_orig)
344 perror("malloc()");
345 exit(1);
349 /* Not sure if BLKFLSBUF waits for the flushing to finish; better safe than sorry */
350 fputs("Artificial sleep (1 second)\n", stdout);
351 sleep(1);
354 /* FIXME: do retries here as well? */
355 err = pread(inputfd, header, 512, 0);
356 if(-1 == err)
358 perror("read(snapshot, header)");
359 exit(1);
362 magic = (uint32_t *) header;
363 *magic = __le32_to_cpu(*magic);
364 if(SNAP_MAGIC != *magic)
366 fputs("Invalid header MAGIC\n", stderr);
367 fprintf(stderr, "%#x != %#x\n", *magic, SNAP_MAGIC);
368 exit(1);
370 fprintf(stdout, "Found a proper MAGIC header: %#x\n", *magic);
372 valid = (uint32_t *) (header+4);
373 *valid = __le32_to_cpu(*valid);
374 if(0 == *valid)
376 fputs("valid == 0\n", stderr);
377 exit(1);
379 fprintf(stdout, "valid = %u\n", *valid);
381 version = (uint32_t *) (header+8);
382 *version = __le32_to_cpu(*version);
383 if(*version != SNAPSHOT_DISK_VERSION)
385 fputs("version != 1\n", stderr);
386 exit(1);
388 fprintf(stdout, "version = %u\n", *version);
390 cs = (uint32_t *) (header+12);
391 *cs = __le32_to_cpu(*cs);
392 chunk_size = *cs;
393 if(chunk_size < 1 || chunk_size > 1024 || (0 != (chunk_size & (chunk_size-1))))
395 fputs("chunk size has to be >=1 and <=1024 and a power of 2\n", stderr);
396 exit(1);
398 fprintf(stdout, "chunk_size = %u (%u bytes)\n", chunk_size, chunk_size*512);
400 chunk_size *= 512;
402 /* the allocations; special care for O_DIRECT */
403 if(do_direct_io)
405 buf_orig = buf = malloc(chunk_size + PAGE_SIZE);
406 chunk_orig = chunk = malloc(chunk_size + PAGE_SIZE);
407 if(!buf_orig || !chunk_orig)
409 perror("malloc()");
410 exit(1);
412 buf = (unsigned char *) PAGE_ALIGN((ptrdiff_t) buf_orig);
413 if(verbose)
414 fprintf(stdout, "buf_orig = %p (%lu), buf = %p (%lu)\n", buf_orig, (unsigned long) buf_orig % PAGE_SIZE, buf, (unsigned long) buf % PAGE_SIZE);
415 chunk = (unsigned char *) PAGE_ALIGN((ptrdiff_t) chunk_orig);
416 if(verbose)
417 fprintf(stdout, "chunk_orig = %p (%lu), chunk = %p (%lu)\n", chunk_orig, (unsigned long) chunk_orig % PAGE_SIZE, chunk, (unsigned long) chunk % PAGE_SIZE);
419 else
421 buf_orig = buf = malloc(chunk_size);
422 chunk_orig = chunk = malloc(chunk_size);
423 if(!buf_orig || !chunk_orig)
425 perror("malloc()");
426 exit(1);
432 * do the work
436 retries=0;
438 do {
439 if(0 != retries)
440 fprintf(stderr, "Warning: retrying pread() on exception area %llu at %llu\n", chunk_now, chunk_now*chunk_size);
442 err = pread(inputfd, buf, chunk_size, chunk_now*chunk_size);
443 if(-1 == err)
445 if(EINTR == errno)
446 continue;
447 perror("pread(inputfd)");
448 exit(1);
450 else if(0 == err)
452 fputs("pread(inputfd): early EOF!\n", stderr);
453 exit(1);
455 else if(err != chunk_size)
457 if(retries++ < 2)
458 continue;
459 fputs("pread(inputfd): incomplete read!\n", stderr);
460 exit(1);
463 break;
464 } while(1);
466 /* process the exception area */
467 for(i=0; i < chunk_size/16; i++)
469 temp_u64 = (uint64_t *)(buf+(i*16));
470 de.old_chunk = __le64_to_cpu(*temp_u64);
471 temp_u64 = (uint64_t *)(buf+(i*16)+8);
472 de.new_chunk = __le64_to_cpu(*temp_u64);
474 if(verbose >= 2)
475 fprintf(stdout, "... chunk_now = %llu, i = %u, de.old_chunk = %llu, de.new_chunk = %llu, old %p, new %p\n", chunk_now, i, de.old_chunk, de.new_chunk, buf+(i*16), buf+(i*16)+8);
477 /* 0 as a new chunk means "we've reached the end" */
478 if(0 == de.new_chunk)
480 go = 0;
481 break;
483 else if((1 == chunk_now && 0 == i && de.new_chunk != 2) || (de.new_chunk < 2))
485 fputs("(1 == chunk_now && 0 == i && de.new_chunk != 2) || (de.new_chunk < 2), perhaps not a snapshot?\n", stderr);
486 exit(1);
489 total_count++;
491 /* the data transfer */
492 read_write_chunk();
493 } /* for i */
495 /* next hop */
496 chunk_now += chunk_size/16 + 1;
497 if(verbose && go)
498 fprintf(stdout, "Seeking into exception area in chunk %llu\n", chunk_now);
500 } while(go);
502 /* flush buffers again (no error handling this time as there's nothing to do anyway) */
503 if(S_ISBLK(st.st_mode)) err = ioctl(outputfd, BLKFLSBUF, 0);
505 /* better safe than sorry */
506 sync();
508 fprintf(stdout, "Found %llu exceptions of chunksize %u, total size %llu bytes (%llu KiB, %.3Lf MiB, %.3Lf GiB).\n", total_count, chunk_size, total_count*chunk_size, (total_count*chunk_size)/1024, ((long double)(total_count*chunk_size))/(1024*1024), ((long double)(total_count*chunk_size))/(1024*1024*1024));
510 close(inputfd);
511 if(-1 != outputfd) close(outputfd);
513 memset(buf, 0, chunk_size);
514 memset(chunk, 0, chunk_size);
516 /* cleanup() will do the rest */
518 return 0;
519 } /* main() */