growdisk.c 8.91 KB
Newer Older
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1
2
/*
 * EMULAB-COPYRIGHT
Mike Hibler's avatar
Mike Hibler committed
3
 * Copyright (c) 2000-2005 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
4
5
6
 * All rights reserved.
 */

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
 * "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>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
Mike Hibler's avatar
Mike Hibler committed
43
44
#ifdef __FreeBSD__
#define DEFDISK "/dev/ad0"
45
46
47
#if __FreeBSD__ >= 5
#include <sys/disk.h>
#else
48
#include <sys/disklabel.h>
49
#endif
Mike Hibler's avatar
Mike Hibler committed
50
51
52
53
54
55
#else
#ifdef __linux__
#define DEFDISK "/dev/hda"
#include <linux/fs.h>
#endif
#endif
56
#include "sliceinfo.h"
57
58
59
60
61

struct diskinfo {
	char bootblock[512];
	int cpu, tpc, spt;
	unsigned long disksize;
62
	struct dospart *parts;
63
64
65
} diskinfo;

char optionstr[] =
66
"[-fhvW] [disk]\n"
67
"Create or extend a DOS partition to contain all trailing unallocated space\n"
68
69
"	-h print usage message\n"
"	-v verbose output\n"
70
71
72
"	-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"
73
74
"	-W actually change the partition table\n"
"	   (default is to just show what would be done)\n"
75
76
"	-X extend the final partition to include the extra space\n"
"	   (alternative to -N)\n"
Mike Hibler's avatar
Mike Hibler committed
77
"	[disk] is the disk special file to operate on";
78

Mike Hibler's avatar
Mike Hibler committed
79
80
#define usage()	errx(1, "Usage: %s %s\nDefault disk is %s", \
		     progname, optionstr, DEFDISK);
81
82
83
84
85
86
87

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

char *progname;
88
int list = 1, verbose, fdisk, usenewpart = 1;
89
90
91
92

main(int argc, char *argv[])
{
	int ch;
Mike Hibler's avatar
Mike Hibler committed
93
	char *disk = DEFDISK;
94
95

	progname = argv[0];
96
	while ((ch = getopt(argc, argv, "fvhNWX")) != -1)
97
98
99
100
		switch(ch) {
		case 'v':
			verbose++;
			break;
101
102
103
		case 'f':
			fdisk++;
			break;
104
105
106
		case 'N':
			usenewpart = 1;
			break;
107
108
109
		case 'W':
			list = 0;
			break;
110
111
112
		case 'X':
			usenewpart = 0;
			break;
113
114
115
116
117
118
119
120
121
122
123
		case 'h':
		case '?':
		default:
			usage();
		}
	argc -= optind;
	argv += optind;
	if (argc == 1)
		disk = argv[0];

	getdiskinfo(disk);
124
	exit((list || fdisk) ? showdiskinfo(disk) : setdiskinfo(disk));
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
}

void
getdiskinfo(char *disk)
{
	int fd;
	unsigned long chs = 1;
	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
142
143
144
145
146
#ifdef __linux__
	if (ioctl(fd, BLKGETSIZE, &diskinfo.disksize) < 0)
		err(1, "%s: BLKGETSIZE", disk);
	diskinfo.cpu = diskinfo.tpc = diskinfo.spt = 0;
#else
147
148
149
150
151
152
153
154
155
156
157
158
159
#ifdef DIOCGMEDIASIZE
	{
		unsigned ssize;
		off_t dsize;

		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);
		diskinfo.cpu = diskinfo.tpc = diskinfo.spt = 0;
	}
#else
Mike Hibler's avatar
Mike Hibler committed
160
#ifdef GIOCGDINFO
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
	{
		struct disklabel label;

		if (ioctl(fd, DIOCGDINFO, &label) < 0)
			err(1, "%s: DIOCGDINFO", disk);
		diskinfo.cpu = label.d_ncylinders;
		chs *= diskinfo.cpu;
		diskinfo.tpc = label.d_ntracks;
		chs *= diskinfo.tpc;
		diskinfo.spt = label.d_nsectors;
		chs *= diskinfo.spt;
		diskinfo.disksize = label.d_secperunit;
		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;
		}
182
	}
Mike Hibler's avatar
Mike Hibler committed
183
184
#endif
#endif
185
#endif
186
187
188
189
190
	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);
191
	diskinfo.parts = (struct dospart *)s0->parts;
192
193
194
195
196
197
198
199
200
	close(fd);
}

/*
 * Return non-zero if a partition was modified, zero otherwise.
 */
int
tweakdiskinfo(char *disk)
{
201
	int i, lastspace = NDOSPART, lastunused = NDOSPART;
202
	struct dospart *dp;
203
204
205
206
207
208
	long firstfree = -1;

	for (i = 0; i < NDOSPART; i++) {
		dp = &diskinfo.parts[i];
		if (dp->dp_typ != 0) {
			if (firstfree < 0 ||
209
210
			    dp->dp_start + dp->dp_size > firstfree) {
				lastspace = i;
211
				firstfree = dp->dp_start + dp->dp_size;
212
			}
213
214
215
216
		}
	}

	/*
217
218
219
220
221
222
223
224
	 * 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
225
	 */
226
	if (firstfree >= diskinfo.disksize) {
227
228
229
230
231
232
233
234
235
236
237
238
239
		/*
		 * 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;
	}

240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
	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");
284
		return 0;
285
286
	}
	dp = &diskinfo.parts[lastunused];
287

288
289
	if (fdisk) {
		printf("p %d %d %d %d\n",
290
291
		       lastunused+1, dp->dp_typ ? dp->dp_typ : DOSPTYP_386BSD,
		       dp->dp_start ? dp->dp_start : firstfree,
292
293
294
		       diskinfo.disksize-firstfree);
		return 1;
	}
295
296
297
298
299
	if (verbose || list) {
		if (dp->dp_start)
			printf("%s: %s size of partition %d "
			       "from %lu to %lu\n", disk,
			       list ? "would change" : "changing",
300
			       lastunused+1, dp->dp_size,
301
302
303
304
305
			       diskinfo.disksize-firstfree);
		else
			printf("%s: %s partition %d "
			       "as start=%lu, size=%lu\n", disk,
			       list ? "would define" : "defining",
306
			       lastunused+1, firstfree,
307
308
309
310
311
312
313
314
315
316
317
			       diskinfo.disksize-firstfree);
	}
	dp->dp_start = firstfree;
	dp->dp_size = diskinfo.disksize - firstfree;
	return 1;
}

int
showdiskinfo(char *disk)
{
	int i;
318
	struct dospart *dp;
319

320
	if (!fdisk) {
321
322
323
324
325
		printf("%s: %lu sectors", disk, diskinfo.disksize);
		if (diskinfo.cpu)
			printf(" (%dx%dx%d CHS)",
			       diskinfo.cpu, diskinfo.tpc, diskinfo.spt);
		printf("\n");
326
327
328
329
330
		for (i = 0; i < NDOSPART; i++) {
			dp = &diskinfo.parts[i];
			printf("  %d: start=%9lu, size=%9lu, type=0x%02x\n",
			       i+1, dp->dp_start, dp->dp_size, dp->dp_typ);
		}
331
	}
332
333
	if (!tweakdiskinfo(disk))
		return 1;
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
	return 0;
}

int
setdiskinfo(char *disk)
{
	int fd, cc;

	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,
		      cc, sizeof(diskinfo.bootblock));
	}
	close(fd);
	if (verbose)
		printf("%s: partition table modified\n", disk);
	return 0;
}