growdisk.c 12.2 KB
Newer Older
Leigh Stoller's avatar
Leigh Stoller committed
1
/*
2
 * Copyright (c) 2000-2014 University of Utah and the Flux Group.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 * 
 * {{{EMULAB-LICENSE
 * 
 * This file is part of the Emulab network testbed software.
 * 
 * This file is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 * 
 * This file is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
 * License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this file.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * }}}
Leigh Stoller's avatar
Leigh Stoller committed
22 23
 */

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
/*
 * "Grow" a testbed disk
 *
 * Used to expand the final (DOS) partition in a testbed image to
 * fill the remainder of the disk.  A typical testbed disk image
 * is sized to fit in the least-common-denominator disk we have,
 * currently 13GB.  This current image is laid out as:
 *
 *	       0 to       62: bootarea
 *	      63 to  6281414: FreeBSD (3GB)
 *	 6281415 to 12562829: Linux (3GB)
 *	12562830 to 12819869: Linux swap (128MB)
 *	12819870 to 26700029: unused
 *
 * for multi-OS disks, or:
 *
 *	       0 to       62: bootarea
 *	      63 to      N-1: some OS
 *	       N to 26700029: unused
 *
 * The goal of this program is to locate the final, unused partition and
 * resize it to match the actual disk size.  This program does *not* know
 * how to extend a filesystem, it only works on unused partitions and only
 * adjusts the size of the partition in the DOS partition table.
 *
 * The tricky part is determining how large the disk is.  Currently we do
 * this by reading the value out of a FreeBSD partition table using
 * DIOCGDINFO.  Even if there is no FreeBSD partition table, it should
 * attempt to fake one with a single partition whose size is the entire
 * disk.
 */

#include <stdio.h>
57 58
#include <stdlib.h>
#include <string.h>
59
#include <unistd.h>
60
#include <errno.h>
61 62
#include <fcntl.h>
#include <err.h>
Mike Hibler's avatar
Mike Hibler committed
63 64
#ifdef __FreeBSD__
#define DEFDISK "/dev/ad0"
65 66 67
#if __FreeBSD__ >= 5
#include <sys/disk.h>
#else
68
#include <sys/disklabel.h>
69
#endif
Mike Hibler's avatar
Mike Hibler committed
70 71 72 73 74 75
#else
#ifdef __linux__
#define DEFDISK "/dev/hda"
#include <linux/fs.h>
#endif
#endif
76
#include "sliceinfo.h"
77 78 79 80 81

struct diskinfo {
	char bootblock[512];
	int cpu, tpc, spt;
	unsigned long disksize;
82
	struct dospart *parts;
83 84 85
} diskinfo;

char optionstr[] =
86
"[-fhvW] [disk]\n"
87
"Create or extend a DOS partition to contain all trailing unallocated space\n"
88 89
"	-h print usage message\n"
"	-v verbose output\n"
90 91 92
"	-f output fdisk style partition entry\n"
"	   (sets slice type=FreeBSD if not already set)\n"
"	-N create a new partition to include the extra space (the default)\n"
93 94
"	-W actually change the partition table\n"
"	   (default is to just show what would be done)\n"
95 96
"	-X extend the final partition to include the extra space\n"
"	   (alternative to -N)\n"
Mike Hibler's avatar
Mike Hibler committed
97
"	[disk] is the disk special file to operate on";
98

Mike Hibler's avatar
Mike Hibler committed
99 100
#define usage()	errx(1, "Usage: %s %s\nDefault disk is %s", \
		     progname, optionstr, DEFDISK);
101 102 103 104 105 106 107

void getdiskinfo(char *disk);
int setdiskinfo(char *disk);
int tweakdiskinfo(char *disk);
int showdiskinfo(char *disk);

char *progname;
108
int list = 1, verbose, fdisk, usenewpart = 1;
109

110
int
111 112 113
main(int argc, char *argv[])
{
	int ch;
Mike Hibler's avatar
Mike Hibler committed
114
	char *disk = DEFDISK;
115 116

	progname = argv[0];
117
	while ((ch = getopt(argc, argv, "fvhNWX")) != -1)
118 119 120 121
		switch(ch) {
		case 'v':
			verbose++;
			break;
122 123 124
		case 'f':
			fdisk++;
			break;
125 126 127
		case 'N':
			usenewpart = 1;
			break;
128 129 130
		case 'W':
			list = 0;
			break;
131 132 133
		case 'X':
			usenewpart = 0;
			break;
134 135 136 137 138 139 140 141 142 143 144
		case 'h':
		case '?':
		default:
			usage();
		}
	argc -= optind;
	argv += optind;
	if (argc == 1)
		disk = argv[0];

	getdiskinfo(disk);
145
	exit((list || fdisk) ? showdiskinfo(disk) : setdiskinfo(disk));
146 147 148 149 150 151
}

void
getdiskinfo(char *disk)
{
	int fd;
152
	unsigned long chs = 0;
153 154 155 156 157 158 159 160 161 162
	struct sector0 {
		char stuff[DOSPARTOFF];
		char parts[512-2-DOSPARTOFF];
		unsigned short magic;
	} *s0;

	memset(&diskinfo, 0, sizeof(diskinfo));
	fd = open(disk, O_RDONLY);
	if (fd < 0)
		err(1, "%s: opening for read", disk);
Mike Hibler's avatar
Mike Hibler committed
163
#ifdef __linux__
164 165
	if (verbose)
		warnx("using BLKGETSIZE to determine size");
Mike Hibler's avatar
Mike Hibler committed
166 167 168
	if (ioctl(fd, BLKGETSIZE, &diskinfo.disksize) < 0)
		err(1, "%s: BLKGETSIZE", disk);
	diskinfo.cpu = diskinfo.tpc = diskinfo.spt = 0;
169
	chs = diskinfo.disksize;
Mike Hibler's avatar
Mike Hibler committed
170
#else
171
#ifdef DIOCGMEDIASIZE
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
	/*
	 * FreeBSD 5.
	 *
	 * Note we still use the contrived geometry here rather than the
	 * simple "gimme the disk size" DIOCGMEDIASIZE.  Why?  I'm glad you
	 * asked...
	 *
	 * FreeBSD fdisk is adamant about DOS partitions starting/ending
	 * on "cylinder" boundaries.  If we try to resize the final partition
	 * here so that it is not a multiple of the cylinder size and later
	 * run fdisk, fdisk will automatically resize the rogue partition.
	 * However, the new size will only appear on disk and not "in core"
	 * until after a reboot.  So after running fdisk and before rebooting,
	 * there is an inconsisency that can create chaos.  For example,
	 * the mkextrafs script will use fdisk info to create a BSD disklabel.
	 * If it uses the older, larger size it will work, but will suddenly
	 * be too large for the DOS partition after rebooting.  If we try to
	 * use the newer, smaller size disklabel will complain about trying
	 * to shrink the 'c' partition and will fail.  If we only create a
	 * new 'e' partition that is the smaller size and don't try to resize
	 * 'c', then after reboot the 'c' partition will be too big and the
	 * kernel will reject the whole disklabel.
	 */
195
	{
196
		unsigned nsect, nhead, ssize;
197 198
		off_t dsize;

199 200
		if (verbose)
			warnx("using DIOCG* to determine size/geometry");
201 202 203 204 205
		if (ioctl(fd, DIOCGSECTORSIZE, &ssize) < 0)
			err(1, "%s: DIOCGSECTORSIZE", disk);
		if (ioctl(fd, DIOCGMEDIASIZE, &dsize) < 0)
			err(1, "%s: DIOCGMEDIASIZE", disk);
		diskinfo.disksize = (unsigned long)(dsize / ssize);
206 207 208 209 210 211 212 213
		if (ioctl(fd, DIOCGFWSECTORS, &nsect) < 0)
			err(1, "%s: DIOCGFWSECTORS", disk);
		diskinfo.spt = nsect;
		if (ioctl(fd, DIOCGFWHEADS, &nhead) < 0)
			err(1, "%s: DIOCGFWHEADS", disk);
		diskinfo.tpc = nhead;
		diskinfo.cpu = diskinfo.disksize / (nsect * nhead);
		chs = diskinfo.cpu * diskinfo.tpc * diskinfo.spt;
214 215
	}
#else
216
#ifdef DIOCGDINFO
217 218 219
	{
		struct disklabel label;

220 221
		if (verbose)
			warnx("using DIOCGDINFO to determine size/geometry");
222 223 224 225 226 227
		if (ioctl(fd, DIOCGDINFO, &label) < 0)
			err(1, "%s: DIOCGDINFO", disk);
		diskinfo.cpu = label.d_ncylinders;
		diskinfo.tpc = label.d_ntracks;
		diskinfo.spt = label.d_nsectors;
		diskinfo.disksize = label.d_secperunit;
228
		chs = diskinfo.cpu * diskinfo.tpc * diskinfo.spt;
229
	}
Mike Hibler's avatar
Mike Hibler committed
230 231
#endif
#endif
232
#endif
233 234
	if (chs == 0)
		errx(1, "%s: could not get disk size/geometry!?", disk);
235 236 237 238 239 240 241 242 243 244
	if (diskinfo.disksize < chs)
		errx(1, "%s: secperunit (%lu) < CxHxS (%lu)",
		     disk, diskinfo.disksize, chs);
	else if (diskinfo.disksize > chs) {
		if (verbose)
			warnx("%s: only using %lu of %lu reported sectors",
			      disk, chs, diskinfo.disksize);
		diskinfo.disksize = chs;
	}

245 246 247 248 249
	if (read(fd, diskinfo.bootblock, sizeof(diskinfo.bootblock)) < 0)
		err(1, "%s: error reading bootblock", disk);
	s0 = (struct sector0 *)diskinfo.bootblock;
	if (s0->magic != 0xAA55)
		errx(1, "%s: invalid bootblock", disk);
250
	diskinfo.parts = (struct dospart *)s0->parts;
251 252 253 254 255 256 257 258 259
	close(fd);
}

/*
 * Return non-zero if a partition was modified, zero otherwise.
 */
int
tweakdiskinfo(char *disk)
{
260
	int i, lastspace = NDOSPART, lastunused = NDOSPART;
261
	struct dospart *dp;
262 263 264 265 266 267
	long firstfree = -1;

	for (i = 0; i < NDOSPART; i++) {
		dp = &diskinfo.parts[i];
		if (dp->dp_typ != 0) {
			if (firstfree < 0 ||
268 269
			    dp->dp_start + dp->dp_size > firstfree) {
				lastspace = i;
270
				firstfree = dp->dp_start + dp->dp_size;
271
			}
272 273 274 275
		}
	}

	/*
276 277 278 279 280 281 282 283
	 * If wanting to extend the final used partition but there is
	 * no such partition, just create a new partition instead.
	 */
	if (!usenewpart && lastspace == NDOSPART)
		usenewpart = 1;

	/*
	 * No trailing free space, nothing to do
284
	 */
285
	if (firstfree >= diskinfo.disksize) {
286 287 288 289 290 291 292 293 294 295 296 297 298
		/*
		 * Warn about an allocated partition that exceeds the
		 * physical disk size.  This can happen if someone
		 * creates a disk image on a large disk and attempts
		 * to load it on a smaller one.  Not much we can do
		 * at this point except set off alarms...
		 */
		if (firstfree > diskinfo.disksize)
			warnx("WARNING! WARNING! "
			      "Allocated partitions too large for disk");
		return 0;
	}

299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
	if (usenewpart) {
		int found = 0;

		/*
		 * Look for unused partitions already correctly defined.
		 * If we don't find one of those, we pick the first unused
		 * partition after the last used partition if possible.
		 * This prevents us from unintuitive behavior like defining
		 * partition 4 when no other partition is defined.
		 */
		for (i = NDOSPART-1; i >= 0; i--) {
			dp = &diskinfo.parts[i];
			if (dp->dp_typ != 0) {
				if (!found && lastunused != NDOSPART)
					found = 1;
			} else {
				if (dp->dp_start == firstfree &&
				    dp->dp_size == diskinfo.disksize-firstfree)
					return 0;
				/*
				 * Paranoia: avoid partially defined but
				 * unused partitions unless the start
				 * corresponds to the beginning of the
				 * unused space.
				 */
				if (!found &&
				    ((dp->dp_start == 0 && dp->dp_size == 0) ||
				     dp->dp_start == firstfree))
					lastunused = i;
			}
		}
	} else {
		/*
		 * Only change if:
		 *	- um...nothing else to check
		 *
		 * But tweak variables for the rest of this function.
		 */
		firstfree = diskinfo.parts[lastspace].dp_start;
		lastunused = lastspace;
	}

	if (lastunused == NDOSPART) {
		warnx("WARNING! No usable partition for free space");
343
		return 0;
344 345
	}
	dp = &diskinfo.parts[lastunused];
346

347
	if (fdisk) {
348
		printf("p %d %d %ld %ld\n",
349 350
		       lastunused+1, dp->dp_typ ? dp->dp_typ : DOSPTYP_386BSD,
		       dp->dp_start ? dp->dp_start : firstfree,
351 352 353
		       diskinfo.disksize-firstfree);
		return 1;
	}
354 355 356
	if (verbose || list) {
		if (dp->dp_start)
			printf("%s: %s size of partition %d "
357
			       "from %u to %lu\n", disk,
358
			       list ? "would change" : "changing",
359
			       lastunused+1, dp->dp_size,
360 361 362 363 364
			       diskinfo.disksize-firstfree);
		else
			printf("%s: %s partition %d "
			       "as start=%lu, size=%lu\n", disk,
			       list ? "would define" : "defining",
365
			       lastunused+1, firstfree,
366 367 368 369 370 371 372 373 374 375 376
			       diskinfo.disksize-firstfree);
	}
	dp->dp_start = firstfree;
	dp->dp_size = diskinfo.disksize - firstfree;
	return 1;
}

int
showdiskinfo(char *disk)
{
	int i;
377
	struct dospart *dp;
378

379
	if (!fdisk) {
380 381 382 383 384
		printf("%s: %lu sectors", disk, diskinfo.disksize);
		if (diskinfo.cpu)
			printf(" (%dx%dx%d CHS)",
			       diskinfo.cpu, diskinfo.tpc, diskinfo.spt);
		printf("\n");
385 386
		for (i = 0; i < NDOSPART; i++) {
			dp = &diskinfo.parts[i];
387
			printf("  %d: start=%9u, size=%9u, type=0x%02x\n",
388 389
			       i+1, dp->dp_start, dp->dp_size, dp->dp_typ);
		}
390
	}
391 392
	if (!tweakdiskinfo(disk))
		return 1;
393 394 395 396 397 398
	return 0;
}

int
setdiskinfo(char *disk)
{
399
	int fd, cc, i, error;
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418

	if (!tweakdiskinfo(disk)) {
		if (verbose)
			printf("%s: no change made\n", disk);
		return 0;
	}

	fd = open(disk, O_RDWR);
	if (fd < 0) {
		warn("%s: opening for write", disk);
		return 1;
	}
	cc = write(fd, diskinfo.bootblock, sizeof(diskinfo.bootblock));
	if (cc < 0) {
		warn("%s: bootblock write", disk);
		return 1;
	}
	if (cc != sizeof(diskinfo.bootblock)) {
		warnx("%s: partial write (%d != %d)\n", disk,
419
		      cc, (int)sizeof(diskinfo.bootblock));
420
	}
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
#ifdef __linux__
	printf("Calling ioctl() to re-read partition table.\n");
	sync();
	sleep(2);
	if ((i = ioctl(fd, BLKRRPART)) != 0) {
                error = errno;
        } else {
                /* some kernel versions (1.2.x) seem to have trouble
                   rereading the partition table, but if asked to do it
		   twice, the second time works. - biro@yggdrasil.com */
                sync();
                sleep(2);
                if ((i = ioctl(fd, BLKRRPART)) != 0)
                        error = errno;
        }

	if (i) {
		printf("\nWARNING: Re-reading the partition table "
			 "failed with error %d: %s.\n"
			 "The kernel still uses the old table.\n"
			 "The new table will be used "
			 "at the next reboot.\n"),
			error, strerror(error);
	}
#endif

447 448 449 450 451
	close(fd);
	if (verbose)
		printf("%s: partition table modified\n", disk);
	return 0;
}