2 # ntfsresizecopy: Copy an NTFS filesystem from one block device to another,
3 # resizing it to the size of the destination device in the process. (Uses
4 # ntfsprogs from http://linux-ntfs.org/doku.php?id=ntfsprogs .) This is
5 # EXPERIMENTAL; after using this script, you should mount the destination
6 # read-only and check that everything looks intact.
8 # usage: ntfsresizecopy SRC DEST
10 # An expanding copy is just done with ntfsclone followed by ntfsresize.
11 # A shrinking copy is done by running ntfsclone and ntfsresize on devices
12 # specially crafted with the Linux device-mapper (requires dmsetup and losetup);
13 # you may save time by checking first that the shrinkage is possible with
14 # `ntfsresize -n -s SIZE SRC'.
16 # The special shrinking technique should be applicable to any filesystem type
17 # that has an in-place shrinking command that doesn't write outside the new
18 # size. Just change the calls to ntfsclone and ntfsresize; ntfsclone can be
19 # replaced by a dd of the beginning of the source for filesystems that don't
20 # have a sparse clone command.
23 # Maintained at http://mattmccutchen.net/utils/#ntfsresizecopy .
24 # -- Matt McCutchen <matt@mattmccutchen.net>
28 use Fcntl qw(SEEK_SET SEEK_CUR SEEK_END);
29 use List::Util qw(min);
30 use filetest 'access';
33 # These are not currently used but might be useful when modifying this script
34 # for a filesystem that doesn't have an ntfsclone analogue.
35 #my $shownProgress = ''; # cursor at its end
36 #sub showProgress($) {
37 # my ($newProgress) = @_;
38 # my $shrink = length($shownProgress) - length($newProgress);
39 # print("\b" x length($shownProgress), $newProgress,
40 # $shrink > 0 ? (" " x $shrink, "\b" x $shrink) : ());
41 # $shownProgress = $newProgress;
44 # my ($srcfh, $destfh, $len) = @_;
46 # showProgress("$len bytes left");
47 # my $chunkLen = min(1048576, $len);
48 # sysread($srcfh, my $data, $chunkLen) == $chunkLen or die 'read error';
49 # syswrite($destfh, $data, $chunkLen) == $chunkLen or die 'write error';
56 # Determine the size of a device by seeking to its end. The ioctl used
57 # by `blockdev --getsize64' might be more official, but this one is
58 # easy and perhaps more portable.
60 my $origPos = sysseek($fh, 0, SEEK_CUR) or die;
61 my $size = sysseek($fh, 0, SEEK_END) or die;
62 sysseek($fh, $origPos, SEEK_SET) or die;
65 # Wrappers for dmsetup and losetup
67 my ($name, @table) = @_;
68 open(my $toDms, '|-', 'dmsetup', 'create', $name) or die;
69 print $toDms map(join(' ', @{$_}) . "\n", @table);
70 close($toDms) or die "dmsetup create $name failed";
74 system('dmsetup', 'remove', $name) and warn "dmsetup remove $name failed";
78 open(my $fromLs, '-|', 'losetup', '-fs', $file);
80 close($fromLs) and defined($dev) or die "losetup -fs $file failed";
86 system('losetup', '-d', $dev) and warn "losetup -d $dev failed";
89 scalar(@ARGV) == 2 or die <<EOU;
90 usage: ntfsresizecopy SRC DEST
91 See the comment at the top of the script for more information.
93 my ($src, $dest) = @ARGV[0..1];
95 open(my $srcfh, '<', $src) or die "open($src) for reading failed: $!";
96 -b $srcfh or die "Source $src must be a block device.\n";
97 my $srcRdev = (stat(_))[6];
98 open(my $destfh, '+<', $dest) or die "open($dest) for reading/writing failed: $!";
99 -b $destfh or die "Destination $dest must be a block device.\n";
100 my $destRdev = (stat(_))[6];
101 $srcRdev == $destRdev and die "Source $src and destination $dest must not be "
102 . "the same block device.\nUse ntfsresize for in-place resizing.\n";
104 my ($srcSize, $destSize) = (deviceSize($srcfh), deviceSize($destfh));
105 # Assume that, since src and dest are block devices, sizes are divisible by 512.
106 my ($srcBlocks, $destBlocks) = map($_ / 512, $srcSize, $destSize);
107 my $shrinkBlocks = $srcBlocks - $destBlocks;
109 print "Going to copy $src ($srcSize bytes) => $dest ($destSize bytes).\n";
111 if ($shrinkBlocks > 0) {
113 print "\nSTEP 1: ntfsclone the beginning of the src to the dest.\n";
114 # Really, clone the whole src to a magical dest consisting of the real dest
115 # followed by a zero target to make up the size difference.
116 # Writes outside the dest's size will be lost to the zero target, but that
117 # doesn't hurt anything. And under the assumption that the shrinkage is
118 # possible, ntfsclone copies at most as much data as a simple dd of the
119 # beginning of the src to the dest would.
120 my $mdn = "ntfsresizecopy.$$.magicdest";
121 my $magicDest = "/dev/mapper/$mdn";
122 # If something in the "eval" fails, still clean up as much as possible.
125 [0, $destBlocks, 'linear', $dest, 0],
126 [$destBlocks, $shrinkBlocks, 'zero']);
127 system('ntfsclone', '--overwrite', $magicDest, $src) and die 'ntfsclone failed.';
133 STEP 2: ntfsresize the dest, bringing in the end of the src.
134 NOTE: Please ignore ntfsresize's remarks about data loss (the src isn't being
135 written so you haven't lost anything if this fails) and about shrinking the
136 device (the device is already smaller).
138 # Really, resize a magical dest consisting of the real one plus the end of the
139 # src. This leaves a shrunken filesystem on the beginning of the magical dest,
140 # i.e., on the real dest.
141 # ntfsresize doesn't seem to write outside the new size, but we use a snapshot
142 # layer to be extra sure we don't mess up the src. The snapshot layer needs a
143 # COW file that is at least one page in size, even though we expect no data to
147 open(my $cowfh, "+>", undef) or die 'failed to create temporary COW file';
148 truncate($cowfh, 4096) or die 'failed to expand temporary COW file';
149 $cowdev = losetup("/proc/$$/fd/" . fileno($cowfh));
151 [0, $destBlocks, 'linear', $dest, 0],
152 [$destBlocks, $shrinkBlocks, 'snapshot', $src, $cowdev, 'N', 1]);
153 open(my $toNr, '|-', 'ntfsresize', '-s', $destSize, $magicDest) or die 'ntfsresize failed.';
154 print $toNr "y\n"; # Confirm automatically because we aren't endangering the src.
155 close($toNr) or die 'ntfsresize failed.';
158 losetup_d($cowdev) if defined($cowdev);
163 print "\nSTEP 1: ntfsclone the src to the dest.\n";
164 system('ntfsclone', '--overwrite', $dest, $src) and die 'ntfsclone failed.';
167 STEP 2: ntfsresize the dest.
168 NOTE: Please ignore ntfsresize's remarks about data loss (the src isn't being
169 written so you haven't lost anything if this fails).
171 open(my $toNr, '|-', 'ntfsresize', '-s', $destSize, $dest) or die 'ntfsresize failed.';
172 print $toNr "y\n"; # Confirm automatically because we aren't endangering the src.
173 close($toNr) or die 'ntfsresize failed.';
177 print "\nFinished!\n";