1 /******************************************************************************
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
18 #include <stddef.h> /* ptrdiff_t */
20 #include <stdint.h> /* uint64_t */
27 #include <sys/types.h>
31 #include <sys/ioctl.h>
32 #include <linux/fs.h> /* BLKGETSIZE64, BLKFLSBUF */
38 #if __BYTE_ORDER == __BIG_ENDIAN
39 #include <linux/byteorder/big_endian.h>
41 #include <linux/byteorder/little_endian.h>
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))
60 uint32_t chunk_size
=512, *cs
;
63 struct disk_exception
{
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;
74 size_t input_length
=0, output_length
=0;
76 struct disk_exception de
;
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
));
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 */
107 /* called for each exception */
108 inline void read_write_chunk()
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);
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
);
126 perror("pread(inputfd)");
131 fputs("pread(inputfd): early EOF!\n", stderr
);
134 else if(err
!= chunk_size
)
138 fputs("pread(inputfd): incomplete read!\n", stderr
);
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
);
157 perror("pwrite(outputfd)");
160 else if(err
!= chunk_size
)
164 fputs("pwrite(outputfd): incomplete write!\n", stderr
);
171 } /* read_write_chunk() */
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
);
191 int main(int argc
, char ** argv
)
197 while( (c
= getopt(argc
, argv
, "fdi:o:Dvh")) != -1 )
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() */
215 fputs("Error: input filename not specified\n\n", stderr
);
220 if(!dry
&& !output_filename
)
222 fputs("Error: no dry run and no output filename\n\n", stderr
);
228 /* better safe than sorry */
231 /* check and open snapshot */
235 err
= stat(input_filename
, &st
);
238 perror("stat(snapshot)");
242 /* block device; will set O_DIRECT */
243 if(S_ISBLK(st
.st_mode
) && do_direct_io
)
246 inputfd
= open(input_filename
, flags
);
249 perror("open(snapshot)");
253 /* determine size & flush buffers */
254 if(S_ISBLK(st
.st_mode
))
256 err
= ioctl(inputfd
, BLKGETSIZE64
, &input_length
);
259 perror("ioctl(snapshot, BLKGETSIZE64)");
263 err
= ioctl(inputfd
, BLKFLSBUF
, 0);
266 perror("ioctl(snapshot, BLKFLSBUF)");
271 input_length
= st
.st_size
;
273 /* now the same for the output */
278 err
= stat(output_filename
, &st
);
281 perror("stat(output)");
285 /* block device; will set O_DIRECT */
286 if(S_ISBLK(st
.st_mode
) && do_direct_io
)
289 outputfd
= open(output_filename
, flags
);
292 perror("open(output)");
296 /* determine size & flush buffers */
297 if(S_ISBLK(st
.st_mode
))
299 err
= ioctl(outputfd
, BLKGETSIZE64
, &output_length
);
302 perror("ioctl(output, BLKGETSIZE64)");
306 err
= ioctl(outputfd
, BLKFLSBUF
, 0);
309 perror("ioctl(output, BLKFLSBUF)");
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
);
326 /* the allocations; special care for O_DIRECT */
329 header_orig
= malloc(512 + PAGE_SIZE
);
335 header
= (unsigned char *) PAGE_ALIGN((ptrdiff_t) header_orig
);
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
);
341 header_orig
= header
= malloc(512);
349 /* Not sure if BLKFLSBUF waits for the flushing to finish; better safe than sorry */
350 fputs("Artificial sleep (1 second)\n", stdout
);
354 /* FIXME: do retries here as well? */
355 err
= pread(inputfd
, header
, 512, 0);
358 perror("read(snapshot, header)");
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
);
370 fprintf(stdout
, "Found a proper MAGIC header: %#x\n", *magic
);
372 valid
= (uint32_t *) (header
+4);
373 *valid
= __le32_to_cpu(*valid
);
376 fputs("valid == 0\n", stderr
);
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
);
388 fprintf(stdout
, "version = %u\n", *version
);
390 cs
= (uint32_t *) (header
+12);
391 *cs
= __le32_to_cpu(*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
);
398 fprintf(stdout
, "chunk_size = %u (%u bytes)\n", chunk_size
, chunk_size
*512);
402 /* the allocations; special care for O_DIRECT */
405 buf_orig
= buf
= malloc(chunk_size
+ PAGE_SIZE
);
406 chunk_orig
= chunk
= malloc(chunk_size
+ PAGE_SIZE
);
407 if(!buf_orig
|| !chunk_orig
)
412 buf
= (unsigned char *) PAGE_ALIGN((ptrdiff_t) buf_orig
);
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
);
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
);
421 buf_orig
= buf
= malloc(chunk_size
);
422 chunk_orig
= chunk
= malloc(chunk_size
);
423 if(!buf_orig
|| !chunk_orig
)
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
);
447 perror("pread(inputfd)");
452 fputs("pread(inputfd): early EOF!\n", stderr
);
455 else if(err
!= chunk_size
)
459 fputs("pread(inputfd): incomplete read!\n", stderr
);
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
);
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
)
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
);
491 /* the data transfer */
496 chunk_now
+= chunk_size
/16 + 1;
498 fprintf(stdout
, "Seeking into exception area in chunk %llu\n", chunk_now
);
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 */
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));
511 if(-1 != outputfd
) close(outputfd
);
513 memset(buf
, 0, chunk_size
);
514 memset(chunk
, 0, chunk_size
);
516 /* cleanup() will do the rest */