xref: /illumos-gate/usr/src/cmd/boot/installgrub/installgrub.c (revision 4bc0a2ef2b7ba50a7a717e7ddbf31472ad28e358)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <libgen.h>
32 #include <malloc.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 #include <unistd.h>
38 #include <strings.h>
39 #include <sys/mount.h>
40 #include <sys/mnttab.h>
41 #include <sys/dktp/fdisk.h>
42 
43 #include <libintl.h>
44 #include <locale.h>
45 #include "message.h"
46 #include <errno.h>
47 
48 #ifndef	TEXT_DOMAIN
49 #define	TEXT_DOMAIN	"SUNW_OST_OSCMD"
50 #endif
51 
52 #define	SECTOR_SIZE	0x200
53 #define	STAGE2_MEMADDR	0x8000	/* loading addr of stage2 */
54 
55 #define	STAGE1_BPB_OFFSET	0x3
56 #define	STAGE1_BPB_SIZE		0x3B
57 #define	STAGE1_BOOT_DRIVE	0x40
58 #define	STAGE1_FORCE_LBA	0x41
59 #define	STAGE1_STAGE2_ADDRESS	0x42
60 #define	STAGE1_STAGE2_SECTOR	0x44
61 #define	STAGE1_STAGE2_SEGMENT	0x48
62 
63 #define	STAGE2_BLOCKLIST	(SECTOR_SIZE - 0x8)
64 #define	STAGE2_INSTALLPART	(SECTOR_SIZE + 0x8)
65 #define	STAGE2_FORCE_LBA	(SECTOR_SIZE + 0x11)
66 #define	STAGE2_VER_STRING	(SECTOR_SIZE + 0x12)
67 #define	STAGE2_BLKOFF		50	/* offset from start of fdisk part */
68 
69 static int nowrite = 0;
70 static int write_mboot = 0;
71 static int force_mboot = 0;
72 static int is_floppy = 0;
73 static int is_bootpar = 0;
74 static int stage2_fd;
75 static int partition, slice = 0xff;
76 static int stage2_first_sector, stage2_second_sector;
77 
78 
79 static char bpb_sect[SECTOR_SIZE];
80 static char boot_sect[SECTOR_SIZE];
81 static char stage1_buffer[SECTOR_SIZE];
82 static char stage2_buffer[2 * SECTOR_SIZE];
83 static int blocklist[SECTOR_SIZE / sizeof (int)];
84 
85 static int open_device(char *);
86 static void read_bpb_sect(int);
87 static void read_boot_sect(char *);
88 static void write_boot_sect(char *);
89 static void read_stage1_stage2(char *, char *);
90 static void modify_and_write_stage1(int);
91 static void modify_and_write_stage2(int);
92 static int get_start_sector();
93 static void copy_stage2(int, char *);
94 static char *get_raw_partition(char *);
95 static void usage(char *);
96 
97 extern int read_stage2_blocklist(int, int *);
98 
99 int
100 main(int argc, char *argv[])
101 {
102 	int dev_fd, opt;
103 	char *stage1, *stage2, *device;
104 
105 	(void) setlocale(LC_ALL, "");
106 	(void) textdomain(TEXT_DOMAIN);
107 
108 	while ((opt = getopt(argc, argv, "fmn")) != EOF) {
109 		switch (opt) {
110 		case 'm':
111 			write_mboot = 1;
112 			break;
113 		case 'n':
114 			nowrite = 1;
115 			break;
116 		case 'f':
117 			force_mboot = 1;
118 			break;
119 		default:
120 			/* fall through to process non-optional args */
121 			break;
122 		}
123 	}
124 
125 	/* check arguments */
126 	if (argc != optind + 3) {
127 		usage(argv[0]);
128 	}
129 
130 	if (nowrite) {
131 		(void) fprintf(stdout, DRY_RUN);
132 	}
133 
134 	stage1 = strdup(argv[optind]);
135 	stage2 = strdup(argv[optind + 1]);
136 	device = strdup(argv[optind + 2]);
137 
138 	if (!stage1 || !stage2 || !device) {
139 		usage(argv[0]);
140 	}
141 
142 	/* open and check device type */
143 	dev_fd = open_device(device);
144 
145 	/* read in stage1 and stage2 into buffer */
146 	read_stage1_stage2(stage1, stage2);
147 
148 	/* In the pcfs case, write a fresh stage2 */
149 	if (is_floppy || is_bootpar) {
150 		copy_stage2(dev_fd, device);
151 		read_bpb_sect(dev_fd);
152 	}
153 
154 	/* read in boot sector */
155 	if (!is_floppy)
156 		read_boot_sect(device);
157 
158 	/* modify stage1 based on grub needs */
159 	modify_and_write_stage1(dev_fd);
160 
161 	/* modify stage2 and write to media */
162 	modify_and_write_stage2(dev_fd);
163 
164 	if (!is_floppy && write_mboot)
165 		write_boot_sect(device);
166 	(void) close(dev_fd);
167 
168 	return (0);
169 }
170 
171 static int
172 get_start_sector()
173 {
174 	static int start_sect = 0;
175 
176 	int i;
177 	struct mboot *mboot;
178 	struct ipart *part;
179 
180 	if (start_sect)
181 		return (start_sect);
182 
183 	mboot = (struct mboot *)boot_sect;
184 	for (i = 0; i < FD_NUMPART; i++) {
185 		part = (struct ipart *)mboot->parts + i;
186 		if (is_bootpar) {
187 			if (part->systid == 0xbe)
188 				break;
189 		} else {
190 			if (part->systid == 0x82 || part->systid == 0xbf)
191 				break;
192 		}
193 	}
194 
195 	if (i == FD_NUMPART) {
196 		(void) fprintf(stderr, BOOTPAR);
197 		exit(-1);
198 	}
199 
200 	/* get confirmation for -m */
201 	if (write_mboot && !force_mboot) {
202 		(void) fprintf(stdout, MBOOT_PROMPT);
203 		if (getchar() != 'y') {
204 			write_mboot = 0;
205 			(void) fprintf(stdout, MBOOT_NOT_UPDATED);
206 		}
207 	}
208 
209 	start_sect = part->relsect;
210 	if (part->bootid != 128 && write_mboot == 0) {
211 		(void) fprintf(stdout, BOOTPAR_INACTIVE, i + 1);
212 	}
213 
214 	partition = i;
215 	return (start_sect);
216 }
217 
218 static void
219 usage(char *progname)
220 {
221 	(void) fprintf(stderr, USAGE, basename(progname));
222 	exit(-1);
223 }
224 
225 static int
226 open_device(char *device)
227 {
228 	int dev_fd;
229 	struct stat stat;
230 	char *raw_part;
231 
232 	is_floppy = strncmp(device, "/dev/rdsk", strlen("/dev/rdsk")) &&
233 	    strncmp(device, "/dev/dsk", strlen("/dev/dsk"));
234 
235 	/* handle boot partition specification */
236 	if (!is_floppy && strstr(device, "p0:boot")) {
237 		is_bootpar = 1;
238 	}
239 
240 	raw_part = get_raw_partition(device);
241 
242 	if (nowrite)
243 		dev_fd = open(raw_part, O_RDONLY);
244 	else
245 		dev_fd = open(raw_part, O_RDWR);
246 
247 	if (dev_fd == -1 || fstat(dev_fd, &stat) != 0) {
248 		(void) fprintf(stderr, OPEN_FAIL, raw_part);
249 		exit(-1);
250 	}
251 	if (S_ISCHR(stat.st_mode) == 0) {
252 		(void) fprintf(stderr, NOT_RAW_DEVICE, raw_part);
253 		exit(-1);
254 	}
255 
256 	return (dev_fd);
257 }
258 
259 static void
260 read_stage1_stage2(char *stage1, char *stage2)
261 {
262 	int fd;
263 
264 	/* read the stage1 file from filesystem */
265 	fd = open(stage1, O_RDONLY);
266 	if (fd == -1 || read(fd, stage1_buffer, SECTOR_SIZE) != SECTOR_SIZE) {
267 		(void) fprintf(stderr, READ_FAIL_STAGE1, stage1);
268 		exit(-1);
269 	}
270 	(void) close(fd);
271 
272 	/* read first two blocks of stage 2 from filesystem */
273 	stage2_fd = open(stage2, O_RDONLY);
274 	if (stage2_fd == -1 ||
275 	    read(stage2_fd, stage2_buffer, 2 * SECTOR_SIZE)
276 	    != 2 * SECTOR_SIZE) {
277 		(void) fprintf(stderr, READ_FAIL_STAGE2, stage2);
278 		exit(-1);
279 	}
280 	/* leave the stage2 file open for later */
281 }
282 
283 static void
284 read_bpb_sect(int dev_fd)
285 {
286 	if (pread(dev_fd, bpb_sect, SECTOR_SIZE, 0) != SECTOR_SIZE) {
287 		(void) fprintf(stderr, READ_FAIL_BPB);
288 		exit(-1);
289 	}
290 }
291 
292 static void
293 read_boot_sect(char *device)
294 {
295 	static int read_mbr = 0;
296 	int i, fd;
297 	char save[2];
298 
299 	if (read_mbr)
300 		return;
301 	read_mbr = 1;
302 
303 	/* get the whole disk (p0) */
304 	i = strlen(device);
305 	save[0] = device[i - 2];
306 	save[1] = device[i - 1];
307 	device[i - 2] = 'p';
308 	device[i - 1] = '0';
309 
310 	fd = open(device, O_RDONLY);
311 	if (fd == -1 || read(fd, boot_sect, SECTOR_SIZE) != SECTOR_SIZE) {
312 		(void) fprintf(stderr, READ_FAIL_MBR, device);
313 		if (fd == -1)
314 			perror("open");
315 		else
316 			perror("read");
317 		exit(-1);
318 	}
319 	(void) close(fd);
320 	device[i - 2] = save[0];
321 	device[i - 1] = save[1];
322 }
323 
324 static void
325 write_boot_sect(char *device)
326 {
327 	int fd, len;
328 	char *raw, *end;
329 	struct stat stat;
330 
331 	/* make a copy and chop off ":boot" */
332 	raw = strdup(device);
333 	end = strstr(raw, "p0:boot");
334 	if (end)
335 		end[2] = 0;
336 
337 	/* open p0 (whole disk) */
338 	len = strlen(raw);
339 	raw[len - 2] = 'p';
340 	raw[len - 1] = '0';
341 	fd = open(raw, O_WRONLY);
342 	if (fd == -1 || fstat(fd, &stat) != 0) {
343 		(void) fprintf(stderr, OPEN_FAIL, raw);
344 		exit(-1);
345 	}
346 	if (!nowrite &&
347 	    pwrite(fd, stage1_buffer, SECTOR_SIZE, 0) != SECTOR_SIZE) {
348 		(void) fprintf(stderr, WRITE_FAIL_BOOTSEC);
349 		exit(-1);
350 	}
351 	(void) fprintf(stdout, WRITE_MBOOT);
352 	(void) close(fd);
353 }
354 
355 static void
356 modify_and_write_stage1(int dev_fd)
357 {
358 	if (is_floppy) {
359 		stage2_first_sector = blocklist[0];
360 		/* copy bios parameter block (for fat fs) */
361 		bcopy(bpb_sect + STAGE1_BPB_OFFSET,
362 		    stage1_buffer + STAGE1_BPB_OFFSET, STAGE1_BPB_SIZE);
363 	} else if (is_bootpar) {
364 		stage2_first_sector = get_start_sector() + blocklist[0];
365 		/* copy bios parameter block (for fat fs) and MBR */
366 		bcopy(bpb_sect + STAGE1_BPB_OFFSET,
367 		    stage1_buffer + STAGE1_BPB_OFFSET, STAGE1_BPB_SIZE);
368 		bcopy(boot_sect + BOOTSZ, stage1_buffer + BOOTSZ, 512 - BOOTSZ);
369 		*((unsigned char *)(stage1_buffer + STAGE1_FORCE_LBA)) = 1;
370 	} else {
371 		stage2_first_sector = get_start_sector() + STAGE2_BLKOFF;
372 		/* copy MBR to stage1 in case of overwriting MBR sector */
373 		bcopy(boot_sect + BOOTSZ, stage1_buffer + BOOTSZ, 512 - BOOTSZ);
374 		*((unsigned char *)(stage1_buffer + STAGE1_FORCE_LBA)) = 1;
375 	}
376 
377 	/* modify default stage1 file generated by GRUB */
378 	*((ulong_t *)(stage1_buffer + STAGE1_STAGE2_SECTOR))
379 		= stage2_first_sector;
380 	*((ushort_t *)(stage1_buffer + STAGE1_STAGE2_ADDRESS))
381 		= STAGE2_MEMADDR;
382 	*((ushort_t *)(stage1_buffer + STAGE1_STAGE2_SEGMENT))
383 		= STAGE2_MEMADDR >> 4;
384 
385 	/*
386 	 * XXX the default grub distribution also:
387 	 * - Copy the possible MBR/extended part table
388 	 * - Set the boot drive of stage1
389 	 */
390 
391 	/* write stage1/pboot to 1st sector */
392 	if (!nowrite &&
393 	    pwrite(dev_fd, stage1_buffer, SECTOR_SIZE, 0) != SECTOR_SIZE) {
394 		(void) fprintf(stderr, WRITE_FAIL_PBOOT);
395 		exit(-1);
396 	}
397 
398 	if (is_floppy) {
399 		(void) fprintf(stdout, WRITE_BOOTSEC_FLOPPY);
400 	} else {
401 		(void) fprintf(stdout, WRITE_PBOOT,
402 		    partition, get_start_sector());
403 	}
404 }
405 
406 #define	START_BLOCK(pos)	(*(ulong_t *)(pos))
407 #define	NUM_BLOCK(pos)		(*(ushort_t *)((pos) + 4))
408 #define	START_SEG(pos)		(*(ushort_t *)((pos) + 6))
409 
410 static void
411 modify_and_write_stage2(int dev_fd)
412 {
413 	int nrecord;
414 	off_t offset;
415 
416 	if (is_floppy || is_bootpar) {
417 		int i = 0;
418 		uint_t partition_offset;
419 		uint_t install_addr = 0x8200;
420 		uchar_t *pos = (uchar_t *)stage2_buffer + STAGE2_BLOCKLIST;
421 
422 		stage2_first_sector = blocklist[0];
423 
424 		/* figure out the second sector */
425 		if (blocklist[1] > 1) {
426 			blocklist[0]++;
427 			blocklist[1]--;
428 		} else {
429 			i += 2;
430 		}
431 		stage2_second_sector = blocklist[i];
432 
433 		if (is_floppy)
434 			partition_offset = 0;
435 		else	/* solaris boot partition */
436 			partition_offset = get_start_sector();
437 
438 		/* install the blocklist at the end of stage2_buffer */
439 		while (blocklist[i]) {
440 			if (START_BLOCK(pos - 8) != 0 &&
441 			    START_BLOCK(pos - 8) != blocklist[i + 2]) {
442 				(void) fprintf(stderr, PCFS_FRAGMENTED);
443 				exit(-1);
444 			}
445 			START_BLOCK(pos) = blocklist[i] + partition_offset;
446 			START_SEG(pos) = (ushort_t)(install_addr >> 4);
447 			NUM_BLOCK(pos) = blocklist[i + 1];
448 			install_addr += blocklist[i + 1] * SECTOR_SIZE;
449 			pos -= 8;
450 			i += 2;
451 		}
452 
453 	} else {
454 		/*
455 		 * In a solaris partition, stage2 is written to contiguous
456 		 * blocks. So we update the starting block only.
457 		 */
458 		*((ulong_t *)(stage2_buffer + STAGE2_BLOCKLIST)) =
459 		    stage2_first_sector + 1;
460 	}
461 
462 	if (is_floppy) {
463 		/* modify the config file to add (fd0) */
464 		char *config_file = stage2_buffer + STAGE2_VER_STRING;
465 		while (*config_file++)
466 			;
467 		strcpy(config_file, "(fd0)/boot/grub/menu.lst");
468 	} else {
469 		/* force lba and set disk partition */
470 		*((unsigned char *) (stage2_buffer + STAGE2_FORCE_LBA)) = 1;
471 		*((long *)(stage2_buffer + STAGE2_INSTALLPART))
472 		    = (partition << 16) | (slice << 8) | 0xff;
473 	}
474 
475 	/* modification done, now do the writing */
476 	if (is_floppy || is_bootpar) {
477 		/* we rewrite block 0 and 1 and that's it */
478 		if (!nowrite &&
479 		    (pwrite(dev_fd, stage2_buffer, SECTOR_SIZE,
480 		    stage2_first_sector * SECTOR_SIZE) != SECTOR_SIZE ||
481 		    pwrite(dev_fd, stage2_buffer + SECTOR_SIZE, SECTOR_SIZE,
482 		    stage2_second_sector * SECTOR_SIZE) != SECTOR_SIZE)) {
483 			(void) fprintf(stderr, WRITE_FAIL_STAGE2);
484 			exit(-1);
485 		}
486 		(void) fprintf(stdout, WRITE_STAGE2_PCFS);
487 		return;
488 	}
489 
490 	/* for disk, write stage2 starting at STAGE2_BLKOFF sector */
491 	offset = STAGE2_BLKOFF;
492 
493 	/* write the modified first two sectors */
494 	if (!nowrite && pwrite(dev_fd, stage2_buffer, 2 * SECTOR_SIZE,
495 	    offset * SECTOR_SIZE) != 2 * SECTOR_SIZE) {
496 		(void) fprintf(stderr, WRITE_FAIL_STAGE2);
497 		exit(-1);
498 	}
499 
500 	/* write the remaining sectors */
501 	nrecord = 2;
502 	offset += 2;
503 	for (;;) {
504 		int nread, nwrite;
505 		nread = pread(stage2_fd, stage2_buffer, SECTOR_SIZE,
506 		    nrecord * SECTOR_SIZE);
507 		if (nread > 0 && !nowrite)
508 			nwrite = pwrite(dev_fd, stage2_buffer, SECTOR_SIZE,
509 			    offset * SECTOR_SIZE);
510 		else
511 			nwrite = SECTOR_SIZE;
512 		if (nread < 0 || nwrite != SECTOR_SIZE) {
513 			(void) fprintf(stderr, WRITE_FAIL_STAGE2_BLOCKS,
514 			    nread, nwrite);
515 			break;
516 		}
517 		if (nread > 0) {
518 			nrecord ++;
519 			offset ++;
520 		}
521 		if (nread < SECTOR_SIZE)
522 			break;	/* end of file */
523 	}
524 	(void) fprintf(stdout, WRITE_STAGE2_DISK,
525 	    partition, nrecord, STAGE2_BLKOFF, stage2_first_sector);
526 }
527 
528 static char *
529 get_raw_partition(char *device)
530 {
531 	int len;
532 	struct mboot *mboot;
533 	static char *raw = NULL;
534 
535 	if (raw)
536 		return (raw);
537 	raw = strdup(device);
538 
539 	if (is_floppy)
540 		return (raw);
541 
542 	if (is_bootpar) {
543 		int i;
544 		char *end = strstr(raw, "p0:boot");
545 
546 		end[2] = 0;		/* chop off :boot */
547 		read_boot_sect(raw);
548 		mboot = (struct mboot *)boot_sect;
549 		for (i = 0; i < FD_NUMPART; i++) {
550 			struct ipart *part = (struct ipart *)mboot->parts + i;
551 			if (part->systid == 0xbe)	/* solaris boot part */
552 				break;
553 		}
554 
555 		if (i == FD_NUMPART) {
556 			(void) fprintf(stderr, BOOTPAR_NOTFOUND, device);
557 			exit(-1);
558 		}
559 		end[1] = '1' + i;	/* set partition name */
560 		return (raw);
561 	}
562 
563 	/* For disk, remember slice and return whole fdisk partition  */
564 	len = strlen(raw);
565 	if (raw[len - 2] != 's' || raw[len - 1] == '2') {
566 		(void) fprintf(stderr, NOT_ROOT_SLICE);
567 		exit(-1);
568 	}
569 	slice = atoi(&raw[len - 1]);
570 
571 	raw[len - 2] = 's';
572 	raw[len - 1] = '2';
573 	return (raw);
574 }
575 
576 #define	TMP_MNTPT	"/tmp/installgrub_pcfs"
577 static void
578 copy_stage2(int dev_fd, char *device)
579 {
580 	FILE *mntfp;
581 	int i, pcfs_fp;
582 	char buf[SECTOR_SIZE];
583 	char *cp;
584 	struct mnttab mp = {0}, mpref = {0};
585 
586 	/* convert raw to block device name by removing the first 'r' */
587 	(void) strncpy(buf, device, sizeof (buf));
588 	buf[sizeof (buf) - 1] = 0;
589 	cp = strchr(buf, 'r');
590 	if (cp == NULL) {
591 		(void) fprintf(stderr, CONVERT_FAIL, device);
592 		exit(-1);
593 	}
594 	do {
595 		*cp = *(cp + 1);
596 	} while (*(++cp));
597 
598 	/* get the mount point, if any */
599 	mntfp = fopen("/etc/mnttab", "r");
600 	if (mntfp == NULL) {
601 		(void) fprintf(stderr, OPEN_FAIL_FILE, "/etc/mnttab");
602 		exit(-1);
603 	}
604 
605 	mpref.mnt_special = buf;
606 	if (getmntany(mntfp, &mp, &mpref) != 0) {
607 		char cmd[128];
608 
609 		/* not mounted, try remount */
610 		(void) mkdir(TMP_MNTPT, S_IRWXU);
611 		(void) snprintf(cmd, sizeof (cmd), "mount -F pcfs %s %s",
612 		    buf, TMP_MNTPT);
613 		(void) system(cmd);
614 		rewind(mntfp);
615 		bzero(&mp, sizeof (mp));
616 		if (getmntany(mntfp, &mp, &mpref) != 0) {
617 			(void) fprintf(stderr, MOUNT_FAIL, buf);
618 			exit(-1);
619 		}
620 	}
621 
622 	(void) snprintf(buf, sizeof (buf),
623 	    "%s/boot", mp.mnt_mountp);
624 	(void) mkdir(buf, S_IRWXU);
625 	(void) strcat(buf, "/grub");
626 	(void) mkdir(buf, S_IRWXU);
627 
628 	(void) strcat(buf, "/stage2");
629 	pcfs_fp = open(buf, O_WRONLY | O_CREAT, S_IRWXU);
630 	if (pcfs_fp == -1) {
631 		(void) fprintf(stderr, OPEN_FAIL_FILE, buf);
632 		perror("open:");
633 		(void) umount(TMP_MNTPT);
634 		exit(-1);
635 	}
636 
637 	/* write stage2 to pcfs */
638 	for (i = 0; ; i++) {
639 		int nread, nwrite;
640 		nread = pread(stage2_fd, buf, SECTOR_SIZE, i * SECTOR_SIZE);
641 		if (nowrite)
642 			nwrite = nread;
643 		else
644 			nwrite = pwrite(pcfs_fp, buf, nread, i * SECTOR_SIZE);
645 		if (nread < 0 || nwrite != nread) {
646 			(void) fprintf(stderr, WRITE_FAIL_STAGE2_BLOCKS,
647 			    nread, nwrite);
648 			break;
649 		}
650 		if (nread < SECTOR_SIZE)
651 			break;	/* end of file */
652 	}
653 	(void) close(pcfs_fp);
654 	(void) umount(TMP_MNTPT);
655 
656 	/*
657 	 * Now, get the blocklist from the device.
658 	 */
659 	bzero(blocklist, sizeof (blocklist));
660 	if (read_stage2_blocklist(dev_fd, blocklist) != 0)
661 		exit(-1);
662 }
663