Fix mistake in dnf-repoquery-by-srpm documentation.
[utils/utils.git] / ntfsresizecopy
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
26 use strict;
27 use warnings;
28 use Fcntl qw(SEEK_SET SEEK_CUR SEEK_END);
29 use List::Util qw(min);
30 use 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
55 sub 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
66 sub 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 }
72 sub dm_remove($) {
73         my ($name) = @_;
74         system('dmsetup', 'remove', $name) and warn "dmsetup remove $name failed";
75 }
76 sub 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 }
84 sub losetup_d($) {
85         my ($dev) = @_;
86         system('losetup', '-d', $dev) and warn "losetup -d $dev failed";
87 }
88
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.
92 EOU
93 my ($src, $dest) = @ARGV[0..1];
94
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";
103
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;
108
109 print "Going to copy $src ($srcSize bytes) => $dest ($destSize bytes).\n";
110
111 if ($shrinkBlocks > 0) {
112
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.
123 eval {
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 };
129 dm_remove($mdn);
130 die $@ if $@;
131
132 print "\n", <<EOM;
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).
137 EOM
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.
145 my $cowdev;
146 eval {
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 };
157 dm_remove($mdn);
158 losetup_d($cowdev) if defined($cowdev);
159 die $@ if $@;
160
161 } else {
162
163 print "\nSTEP 1: ntfsclone the src to the dest.\n";
164 system('ntfsclone', '--overwrite', $dest, $src) and die 'ntfsclone failed.';
165
166 print "\n", <<EOM;
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).
170 EOM
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.';
174
175 }
176
177 print "\nFinished!\n";
178