Wednesday, January 27, 2010

Resizing QEMU images with Win7 installed under Linux

Here's another Windows 7 post. I started with a 16GB virtual image for Windows and almost immediately ran out of space. It turns out that it's not that hard to resize your partition; I found a very helpful forum post on the subject written a few years ago, and a couple things have changed for Windows 7, so I'll post an updated method here.

First, I'm working from the poster's second set of instructions. He claims they're more dangerous, but they're really not. You never have to work on your only copy of your data; in fact, by the end you'll have three working copies of the drive. It's probably a good idea not to delete the extras until then, but if you are crunched for disk space that's fine. Make sure you have the following commands available: qemu-img, ntfsresize, hexedit. They all should be available in packages supplied by your distro. I'm starting from an image called win7.img, using an intermediate raw image called win7.raw, and ending with an image called win7.qcow2. Obviously, you should use whatever names you're comfortable with.

Before you start, be sure you shut down Windows normally last time you used it. Otherwise ntfsresize will refuse to touch the data.

First, your disk image is probably in qcow2 format. You need to convert it to "raw" format, which is just a flat readout of the data. Check the format:

$ qemu-img info win7.img

If necessary, convert to raw; this will take several minutes (where I have qcow2 you should put the format of your image):

$ qemu-img convert -f qcow2 win7.img -O raw win7.raw

Now expand the raw image by writing to a large offset in the file. This should run very quickly on most Linux filesystems, and not consume much extra space. The bs parameter specifies the "block size" for writes (in bytes) and the seek parameter the number of blocks to skip. So I will write 83886080 × 512 = 42949672960 bytes (40 GiB) in.

$ dd if=/dev/zero of=winb.raw bs=512 count=0 seek=83886080
0+0 records in
0+0 records out
0 bytes (0 B) copied, 1.2997e-05 s, 0.0 kB/s


Become root and use Linux's loopback device to operate on the raw image as if it were a real hard drive.

$ sudo su
(enter password)
# losetup /dev/loop0 win7.raw


Now you'll want to check out the fake disk geometry and edit the partition table using fdisk. You'll want to switch fdisk to display in units of sectors, print the partition table, delete the main partition, and remake a larger one in its place. Your input is in bold.

# fdisk /dev/loop0

The number of cylinders for this disk is set to 5221.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
(e.g., DOS FDISK, OS/2 FDISK)

Command (m for help): u
Changing display/entry units to sectors

Command (m for help): p

Disk /dev/loop0: 42.9 GB, 42949672960 bytes
255 heads, 63 sectors/track, 5221 cylinders, total 83886080 sectors
Units = sectors of 1 * 512 = 512 bytes
Disk identifier: 0x23830da9


      Device Boot      Start         End      Blocks   Id  System
/dev/loop0p1 * 2048 206847 102400 7 HPFS/NTFS
Partition 1 does not end on cylinder boundary.
/dev/loop0p2 206848 31455231 15624192 7 HPFS/NTFS

Windows 7 has created two NTFS partitions on my disk. The second one is the one we're interested in. Also note the disk geometry: 255 heads. If it doesn't say 255 heads, and your disk is large, it should say 255 heads. Obviously the disk geometry is completely made up, and I'm not really sure why Linux's loopback device should be expected to fake the same geometry as Qemu... comments on the forum suggest this value should always be 255. Anyway, on to editing the partition table entry. The number you use for the first sector must be the same as in the original partition table; for me that's 206848.

Command (m for help): d
Partition number (1-4): 2

Command (m for help): n
Command action
e extended
p primary partition (1-4)
p
Partition number (1-4): 2
First sector (63-83886079, default 63): 206848
Last sector, +sectors or +size{K,M,G} (206848-83886079, default 83886079):
Using default value 83886079

Command (m for help): t
Partition number (1-4): 2
Hex code (type L to list codes): 7
Changed system type of partition 2 to 7 (HPFS/NTFS)

Command (m for help): p

Disk /dev/loop0: 42.9 GB, 42949672960 bytes
255 heads, 63 sectors/track, 5221 cylinders, total 83886080 sectors
Units = sectors of 1 * 512 = 512 bytes
Disk identifier: 0x23830da9


      Device Boot      Start         End      Blocks   Id  System
/dev/loop0p1 * 2048 206847 102400 7 HPFS/NTFS
Partition 1 does not end on cylinder boundary.
/dev/loop0p2 206848 83886079 41839616 7 HPFS/NTFS

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: Re-reading the partition table failed with error 22: Invalid argument.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
Syncing disks.


Now re-connect the partition table and check that it worked.

# losetup -d /dev/loop0
# losetup /dev/loop0 win7.raw
# fdisk -ul /dev/loop0

Disk /dev/loop0: 42.9 GB, 42949672960 bytes
255 heads, 63 sectors/track, 5221 cylinders, total 83886080 sectors
Units = sectors of 1 * 512 = 512 bytes
Disk identifier: 0x23830da9


      Device Boot      Start         End      Blocks   Id  System
/dev/loop0p1 * 2048 206847 102400 7 HPFS/NTFS
Partition 1 does not end on cylinder boundary.
/dev/loop0p2 206848 83886079 41839616 7 HPFS/NTFS

# losetup -d /dev/loop0

Now, especially if you're resizing from a much smaller drive, we should check that each of the NTFS partitions has the correct number of heads stored in it (the forum post has an explanation of why this is important; to make a long story short, Windows won't start if it's wrong). To find the address of each partition, take its start value from fdisk and multiply by the sector size of 512 bytes. Then convert to hex.

# bc
2048*512
1048576
(this is the start of the first partition in bytes
206848*512
105906176
(this is the start of the second partition in bytes)
quit
# printf "%x %x\n" 1048576 105906176
100000 6500000
(these are the hexadecimal addresses)

Open win7.raw in hexedit; hit return to enter an address, enter the first address, hit return again. Now you're at the start of the first NTFS partition. At an offset 0x1A bytes from that is the head count. Navigate to that byte (if the original address was 0x100000 you'll be at 0x10001A). Make sure it's FF (the hex value of 255). If not, type over that byte with FF. Then do the exact same thing for the second address. Hit ctrl-x to exit, confirming changes if necessary.

Now you can use ntfsresize to actually resize the Windows partition. You have to connect the loopback device to the start of the partition you're resizing this time; this is the decimal version of the second offset you found in the last step. Before doing anything with ntfsresize we check that everything is OK using the -n flag.

# losetup -o105906176 /dev/loop0 win7.raw
# ntfsresize -n /dev/loop0
ntfsresize v2.0.0 (libntfs 10:0:0)
Device name : /dev/loop0
NTFS volume version: 3.1
Cluster size : 4096 bytes
Current volume size: 15999169024 bytes (16000 MB)
Current device size: 42843766784 bytes (42844 MB)
New volume size : 42843763200 bytes (42844 MB)
Checking filesystem consistency ...
100.00 percent completed
Accounting clusters ...
Space in use : 15875 MB (99.2%)
Collecting resizing constraints ...
Schedule chkdsk for NTFS consistency check at Windows boot time ...
Resetting $LogFile ... (this might take a while)
Updating $BadClust file ...
Updating $Bitmap file ...
Updating Boot record ...
The read-only test run ended successfully.


If your output is something like that you're ready to go. The default action is to expand the partition to its largest size, so you only need to run:

# ntfsresize /dev/loop0
Device name : /dev/loop0
NTFS volume version: 3.1
Cluster size : 4096 bytes
Current volume size: 15999169024 bytes (16000 MB)
Current device size: 42843766784 bytes (42844 MB)
New volume size : 42843763200 bytes (42844 MB)
Checking filesystem consistency ...
100.00 percent completed
Accounting clusters ...
Space in use : 15875 MB (99.2%)
Collecting resizing constraints ...
WARNING: Every sanity check passed and only the dangerous operations left.
Make sure that important data has been backed up! Power outage or computer
crash may result major data loss!
Are you sure you want to proceed (y/[n])? y
Schedule chkdsk for NTFS consistency check at Windows boot time ...
Resetting $LogFile ... (this might take a while)
Updating $BadClust file ...
Updating $Bitmap file ...
Updating Boot record ...
Syncing device ...
Successfully resized NTFS on device '/dev/loop0'.


Hopefully your result is something like the above. Now you can disconnect the loopback device and end the sudo session.

# losetup -d /dev/loop0
# exit


You should try out the new image by starting Qemu/KVM the way you usually would, except substituting the name of the new image for that of the old one. Windows will check the disk (because ntfsresize told it to), and everything should come out OK. If not, you still have the original disk image. If everything works you can delete your original image and go on -- if everything is broken you should delete the raw image, figure out what went wrong, and start over.

Now you probably don't want to keep the image around as a raw; qcow2 takes up less space on the host system and has a lot of nice features. You can easily convert it to qcow2 like this:

$ qemu-img convert -f raw win7.raw -O qcow2 win7.qcow2

Make sure the new qcow2 image works. If so, delete the raw image and you're done.

Thanks to poster IntuitiveNipple on the QEMU forum for writing a nice explanation of the bit-hacking necessary on NTFS, and the overall procedure for resizing images with Windows installed.

EDIT: I edited this post to fix table formatting in fdisk output.

5 comments:

Anonymous said...

Nice dispatch and this enter helped me alot in my college assignement. Thanks you seeking your information.

Anonymous said...

Easily I agree but I think the post should have more info then it has.

avibrazil said...

There is a very confusing typo in 'dd' command that expands the disk image.

You use “winb.raw” where the correct would be “win7.raw”

avibrazil said...

On the expanding 'dd' command, it easier for the reader to understand, and avoids uneccessary calculations if you explain it like this:

dd if=/dev/zero of=myvdisk.img bs=1 count=0 seek=FINAL_DESIRED_SIZE_OF_THE_FILE_IN_BYTES

so for a 40GB final vdisk, FINAL_DESIRED_SIZE_OF_THE_FILE_IN_BYTES would be 40×1024×1024×1024=42949672960 bytes.

Or even:

dd if=/dev/zero of=myvdisk.img bs=1073741824 count=0 seek=FINAL_DESIRED_SIZE_OF_THE_FILE_IN_GYTES


so for a 40GB final vdisk, FINAL_DESIRED_SIZE_OF_THE_FILE_IN_GBYTES would be simply '40' because I'm using a block size (bs) of 1GB (or 1073741824 or 1024×1024×1024 bytes).

Miriam said...

Thanks a lot! It went quite smooth :) and it took me less than 1 hour.