Add xsltdepcomp and makedepfile.
[utils/utils.git] / ntfsresizecopy
CommitLineData
273c3903
MM
1#!/usr/bin/perl
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.
7#
8# usage: ntfsresizecopy SRC DEST
9#
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'.
15#
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.
21#
22# Version 2008.06.01
23# Maintained at http://mattmccutchen.net/utils/#ntfsresizecopy .
24# -- Matt McCutchen <matt@mattmccutchen.net>
25
26use strict;
27use warnings;
28use Fcntl qw(SEEK_SET SEEK_CUR SEEK_END);
29use List::Util qw(min);
30use filetest 'access';
31$| = 1;
32
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;
42#}
43#sub dd(**$) {
44# my ($srcfh, $destfh, $len) = @_;
45# while ($len > 0) {
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';
50# $len -= $chunkLen;
51# }
52# showProgress('');
53#}
54
55sub deviceSize(*) {
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.
59 my ($fh) = @_;
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;
63 return 0 + $size;
64}
65# Wrappers for dmsetup and losetup
66sub dm_create($@) {
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";
71}
72sub dm_remove($) {
73 my ($name) = @_;
74 system('dmsetup', 'remove', $name) and warn "dmsetup remove $name failed";
75}
76sub losetup($) {
77 my ($file) = @_;
78 open(my $fromLs, '-|', 'losetup', '-fs', $file);
79 my $dev = <$fromLs>;
80 close($fromLs) and defined($dev) or die "losetup -fs $file failed";
81 chomp($dev);
82 return $dev;
83}
84sub losetup_d($) {
85 my ($dev) = @_;
86 system('losetup', '-d', $dev) and warn "losetup -d $dev failed";
87}
88
89scalar(@ARGV) == 2 or die <<EOU;
90usage: ntfsresizecopy SRC DEST
91See the comment at the top of the script for more information.
92EOU
93my ($src, $dest) = @ARGV[0..1];
94
95open(my $srcfh, '<', $src) or die "open($src) for reading failed: $!";
96-b $srcfh or die "Source $src must be a block device.\n";
97my $srcRdev = (stat(_))[6];
98open(my $destfh, '+<', $dest) or die "open($dest) for reading/writing failed: $!";
99-b $destfh or die "Destination $dest must be a block device.\n";
100my $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";
103
104my ($srcSize, $destSize) = (deviceSize($srcfh), deviceSize($destfh));
105# Assume that, since src and dest are block devices, sizes are divisible by 512.
106my ($srcBlocks, $destBlocks) = map($_ / 512, $srcSize, $destSize);
107my $shrinkBlocks = $srcBlocks - $destBlocks;
108
109print "Going to copy $src ($srcSize bytes) => $dest ($destSize bytes).\n";
110
111if ($shrinkBlocks > 0) {
112
113print "\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.
120my $mdn = "ntfsresizecopy.$$.magicdest";
121my $magicDest = "/dev/mapper/$mdn";
122# If something in the "eval" fails, still clean up as much as possible.
123eval {
124 dm_create($mdn,
125 [0, $destBlocks, 'linear', $dest, 0],
126 [$destBlocks, $shrinkBlocks, 'zero']);
127 system('ntfsclone', '--overwrite', $magicDest, $src) and die 'ntfsclone failed.';
128};
129dm_remove($mdn);
130die $@ if $@;
131
132print "\n", <<EOM;
133STEP 2: ntfsresize the dest, bringing in the end of the src.
134NOTE: Please ignore ntfsresize's remarks about data loss (the src isn't being
135written so you haven't lost anything if this fails) and about shrinking the
136device (the device is already smaller).
137EOM
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
144# be written to it.
145my $cowdev;
146eval {
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));
150 dm_create($mdn,
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.';
156};
157dm_remove($mdn);
158losetup_d($cowdev) if defined($cowdev);
159die $@ if $@;
160
161} else {
162
163print "\nSTEP 1: ntfsclone the src to the dest.\n";
164system('ntfsclone', '--overwrite', $dest, $src) and die 'ntfsclone failed.';
165
166print "\n", <<EOM;
167STEP 2: ntfsresize the dest.
168NOTE: Please ignore ntfsresize's remarks about data loss (the src isn't being
169written so you haven't lost anything if this fails).
170EOM
171open(my $toNr, '|-', 'ntfsresize', '-s', $destSize, $dest) or die 'ntfsresize failed.';
172print $toNr "y\n"; # Confirm automatically because we aren't endangering the src.
173close($toNr) or die 'ntfsresize failed.';
174
175}
176
177print "\nFinished!\n";
178