xref: /illumos-gate/usr/src/lib/libgrubmgmt/common/libgrub_fs.c (revision 4c28a617e3922d92a58e813a5b955eb526b9c386)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 /*
26  * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
27  * Copyright 2015 Toomas Soome <tsoome@me.com>
28  */
29 
30 /*
31  * This file contains all the functions that manipulate the file
32  * system where the GRUB menu resides.
33  */
34 #include <stdio.h>
35 #include <errno.h>
36 #include <stdlib.h>
37 #include <strings.h>
38 #include <unistd.h>
39 #include <fcntl.h>
40 #include <assert.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <sys/mount.h>
44 #include <sys/mntent.h>
45 #include <sys/mnttab.h>
46 #include <sys/efi_partition.h>
47 #include <sys/vtoc.h>
48 #include <sys/fs/ufs_mount.h>
49 #include <sys/dktp/fdisk.h>
50 #include <libfstyp.h>
51 #if defined(i386) || defined(__amd64)
52 #include <libfdisk.h>
53 #endif
54 
55 #include "libgrub_impl.h"
56 
57 static int
58 slice_match(const char *physpath, int slice)
59 {
60 	const char *pos;
61 
62 	/* always match whole disk slice */
63 	if (slice == SLCNUM_WHOLE_DISK)
64 		return (0);
65 
66 	return ((pos = strrchr(physpath, slice)) == NULL ||
67 	    pos[1] != 0 || pos[-1] != ':');
68 }
69 
70 /*
71  * Returns zero if path contains ufs
72  */
73 static int
74 slice_ufs(const char *path)
75 {
76 	int fd, ret;
77 	const char *id;
78 	fstyp_handle_t hdl;
79 
80 	fd = open(path, O_RDONLY);
81 	if ((ret = fstyp_init(fd, 0, NULL, &hdl)) == 0) {
82 		ret = fstyp_ident(hdl, "ufs", &id);
83 		fstyp_fini(hdl);
84 	}
85 	(void) close(fd);
86 	return (ret);
87 }
88 
89 
90 static int
91 get_sol_prtnum(const char *physpath)
92 {
93 	int i, fd;
94 	char *pos;
95 	size_t sz;
96 	struct mboot *mb;
97 	struct ipart *ipart;
98 	char boot_sect[512];
99 	char rdev[MAXNAMELEN];
100 #if defined(i386) || defined(__amd64)
101 	ext_part_t *epp;
102 	int ext_part_found = 0;
103 #endif
104 
105 	(void) snprintf(rdev, sizeof (rdev), "/devices%s,raw", physpath);
106 
107 	if ((pos = strrchr(rdev, ':')) == NULL)
108 		return (PRTNUM_INVALID);
109 
110 	/*
111 	 * first check for EFI partitioning, efi_alloc_and_read()
112 	 * will return partition number.
113 	 */
114 	if ((fd = open(rdev, O_RDONLY|O_NDELAY)) >= 0) {
115 		struct dk_gpt *vtoc;
116 
117 		if ((i = efi_alloc_and_read(fd, &vtoc)) >= 0) {
118 			/* zfs is using V_USR */
119 			if (vtoc->efi_parts[i].p_tag != V_USR)
120 				i = PRTNUM_INVALID; /* error */
121 			efi_free(vtoc);
122 			(void) close(fd);
123 			return (i);
124 		}
125 		(void) close(fd);
126 	} else {
127 		return (PRTNUM_INVALID);
128 	}
129 
130 	pos[1] = SLCNUM_WHOLE_DISK;
131 
132 	fd = open(rdev, O_RDONLY);
133 	sz = read(fd, boot_sect, sizeof (boot_sect));
134 	(void) close(fd);
135 
136 	if (sz != sizeof (boot_sect))
137 		return (PRTNUM_INVALID);
138 
139 	/* parse fdisk table */
140 	mb = (struct mboot *)(uintptr_t)boot_sect;
141 	ipart = (struct ipart *)(uintptr_t)mb->parts;
142 	for (i = 0; i < FD_NUMPART; ++i) {
143 		if (ipart[i].systid == SUNIXOS || ipart[i].systid == SUNIXOS2)
144 			return (i);
145 
146 #if defined(i386) || defined(__amd64)
147 		if (!fdisk_is_dos_extended(ipart[i].systid) ||
148 		    (ext_part_found == 1))
149 			continue;
150 
151 		ext_part_found = 1;
152 
153 		if (libfdisk_init(&epp, rdev, NULL, FDISK_READ_DISK) ==
154 		    FDISK_SUCCESS) {
155 			uint32_t begs, nums;
156 			int pno;
157 			int rval;
158 
159 			rval = fdisk_get_solaris_part(epp, &pno, &begs, &nums);
160 
161 			libfdisk_fini(&epp);
162 
163 			if (rval == FDISK_SUCCESS)
164 				return (pno - 1);
165 		}
166 #endif
167 	}
168 	return (PRTNUM_INVALID);
169 }
170 
171 /*
172  * Get physpath, topfs and bootfs for ZFS root dataset.
173  * Return 0 on success, non-zero (not errno) on failure.
174  */
175 static int
176 get_zfs_root(zfs_handle_t *zfh, grub_fs_t *fs, grub_root_t *root)
177 {
178 	int ret;
179 	zpool_handle_t *zph;
180 	const char *name;
181 
182 	if (zfs_get_type(zfh) != ZFS_TYPE_FILESYSTEM ||
183 	    (name = zfs_get_name(zfh)) == NULL ||
184 	    (zph = zpool_open(fs->gf_lzfh, name)) == NULL)
185 		return (-1);
186 
187 	if ((ret = zpool_get_physpath(zph, root->gr_physpath,
188 	    sizeof (root->gr_physpath))) == 0 &&
189 	    (ret = zpool_get_prop(zph, ZPOOL_PROP_BOOTFS,
190 	    root->gr_fs[GRBM_ZFS_BOOTFS].gfs_dev,
191 	    sizeof (root->gr_fs[GRBM_ZFS_BOOTFS].gfs_dev), NULL,
192 	    B_FALSE)) == 0) {
193 
194 		(void) strlcpy(root->gr_fs[GRBM_ZFS_TOPFS].gfs_dev, name,
195 		    sizeof (root->gr_fs[GRBM_ZFS_TOPFS].gfs_dev));
196 		(void) grub_fsd_get_mountp(root->gr_fs + GRBM_ZFS_BOOTFS,
197 		    MNTTYPE_ZFS);
198 		(void) grub_fsd_get_mountp(root->gr_fs + GRBM_ZFS_TOPFS,
199 		    MNTTYPE_ZFS);
200 	}
201 
202 	zpool_close(zph);
203 	return (ret);
204 }
205 
206 /*
207  * On entry physpath parameter supposed to contain:
208  * <disk_physpath>[<space><disk_physpath>]*.
209  * Retrieves first <disk_physpath> that matches both partition and slice.
210  * If any partition and slice is acceptable, first <disk_physpath> is returned.
211  */
212 static int
213 get_one_physpath(char *physpath, uint_t prtnum, uint_t slcnum)
214 {
215 	int ret;
216 	char *tmp, *tok;
217 
218 	if (!IS_SLCNUM_VALID(slcnum) && !IS_PRTNUM_VALID(prtnum)) {
219 		(void) strtok(physpath, " ");
220 		return (0);
221 	}
222 
223 	if ((tmp = strdup(physpath)) == NULL)
224 		return (errno);
225 
226 	ret = ENODEV;
227 	for (tok = strtok(tmp, " "); tok != NULL; tok = strtok(NULL, " ")) {
228 		if ((ret = (slice_match(tok, slcnum) != 0 ||
229 		    get_sol_prtnum(tok) != prtnum)) == 0) {
230 			(void) strcpy(physpath, tok);
231 			break;
232 		}
233 	}
234 
235 	free(tmp);
236 	if (ret)
237 		ret = ENODEV;
238 	return (ret);
239 }
240 
241 static int
242 zfs_bootsign(zfs_handle_t *zfh, void *data)
243 {
244 	grub_barg_t *barg;
245 	grub_menu_t *menu;
246 	struct stat st;
247 	char path[MAXPATHLEN];
248 
249 	barg = (grub_barg_t *)data;
250 	menu = barg->gb_entry->ge_menu;
251 
252 	do {
253 		if (get_zfs_root(zfh, &menu->gm_fs, &barg->gb_root) != 0 ||
254 		    get_one_physpath(barg->gb_root.gr_physpath, barg->gb_prtnum,
255 		    barg->gb_slcnum) != 0)
256 			break;
257 
258 		/*
259 		 * if top zfs dataset is not mounted, mount it now
260 		 */
261 		if (barg->gb_root.gr_fs[GRBM_ZFS_TOPFS].gfs_mountp[0] == 0) {
262 			if (grub_fsd_mount_tmp(barg->gb_root.gr_fs +
263 			    GRBM_ZFS_TOPFS, MNTTYPE_ZFS) != 0)
264 				break;
265 		}
266 
267 		/* check that bootsign exists and it is a regular file */
268 		(void) snprintf(path, sizeof (path), "%s%s",
269 		    barg->gb_root.gr_fs[GRBM_ZFS_TOPFS].gfs_mountp,
270 		    barg->gb_bootsign);
271 
272 		if (lstat(path, &st) != 0 || S_ISREG(st.st_mode) == 0 ||
273 		    (st.st_mode & S_IRUSR) == 0)
274 			break;
275 
276 		(void) strlcpy(barg->gb_root.gr_fstyp, MNTTYPE_ZFS,
277 		    sizeof (barg->gb_root.gr_fstyp));
278 		barg->gb_walkret = 0;
279 	/* LINTED: E_CONSTANT_CONDITION */
280 	} while (0);
281 
282 	grub_fsd_umount_tmp(barg->gb_root.gr_fs + GRBM_ZFS_TOPFS);
283 	zfs_close(zfh);
284 
285 	/* return non-zero to terminate the walk */
286 	return (barg->gb_walkret == 0);
287 }
288 
289 static int
290 get_devlink(di_devlink_t dl, void *arg)
291 {
292 	const char *path;
293 	grub_barg_t *barg;
294 
295 	barg = (grub_barg_t *)arg;
296 	if ((path = di_devlink_path(dl)) != NULL)
297 		(void) strlcpy(barg->gb_root.gr_fs[GRBM_UFS].gfs_dev, path,
298 		    sizeof (barg->gb_root.gr_fs[GRBM_UFS].gfs_dev));
299 	return (DI_WALK_TERMINATE);
300 }
301 
302 static int
303 ufs_bootsign_check(grub_barg_t *barg)
304 {
305 	int ret;
306 	struct stat st;
307 	grub_menu_t *mp;
308 	char path[MAXPATHLEN];
309 
310 	mp = barg->gb_entry->ge_menu;
311 
312 	/* get /dev/dsk link */
313 	if (di_devlink_walk(mp->gm_fs.gf_dvlh, "^dsk/",
314 	    barg->gb_root.gr_physpath, DI_PRIMARY_LINK, barg, get_devlink) != 0)
315 		return (errno);
316 	/*
317 	 * if disk is not mounted, mount it now
318 	 */
319 	if (grub_fsd_get_mountp(barg->gb_root.gr_fs + GRBM_UFS,
320 	    MNTTYPE_UFS) != 0) {
321 		if ((ret =
322 		    slice_ufs(barg->gb_root.gr_fs[GRBM_UFS].gfs_dev)) != 0 ||
323 		    (ret = grub_fsd_mount_tmp(barg->gb_root.gr_fs + GRBM_UFS,
324 		    MNTTYPE_UFS)) != 0)
325 			return (ret);
326 	}
327 
328 	(void) snprintf(path, sizeof (path), "%s%s",
329 	    barg->gb_root.gr_fs[GRBM_UFS].gfs_mountp, barg->gb_bootsign);
330 
331 	if (lstat(path, &st) == 0 && S_ISREG(st.st_mode) &&
332 	    (st.st_mode & S_IRUSR) != 0) {
333 		barg->gb_walkret = 0;
334 		(void) strlcpy(barg->gb_root.gr_fstyp, MNTTYPE_UFS,
335 		    sizeof (barg->gb_root.gr_fstyp));
336 	}
337 
338 	grub_fsd_umount_tmp(barg->gb_root.gr_fs + GRBM_UFS);
339 	return (barg->gb_walkret);
340 }
341 
342 static int
343 ufs_bootsign(di_node_t node, di_minor_t minor, void *arg)
344 {
345 	uint_t prtnum;
346 	char *name, *path;
347 	grub_barg_t *barg;
348 
349 	barg = (grub_barg_t *)arg;
350 
351 	if (di_minor_spectype(minor) != S_IFBLK)
352 		return (DI_WALK_CONTINUE);
353 
354 	name = di_minor_name(minor);
355 	if (name[0] != barg->gb_slcnum || name[1] != 0)
356 		return (DI_WALK_CONTINUE);
357 
358 	path = di_devfs_path(node);
359 	(void) snprintf(barg->gb_root.gr_physpath,
360 	    sizeof (barg->gb_root.gr_physpath), "%s:%c", path, barg->gb_slcnum);
361 	di_devfs_path_free(path);
362 
363 	prtnum = get_sol_prtnum(barg->gb_root.gr_physpath);
364 	if (!IS_PRTNUM_VALID(prtnum))
365 		return (DI_WALK_CONTINUE);
366 
367 	/*
368 	 * check only specified partition, slice
369 	 */
370 
371 	if (IS_PRTNUM_VALID(barg->gb_prtnum)) {
372 		if (prtnum != barg->gb_prtnum || ufs_bootsign_check(barg) != 0)
373 			return (DI_WALK_CONTINUE);
374 		return (DI_WALK_TERMINATE);
375 	}
376 
377 	/*
378 	 * Walk through all slices in found solaris partition
379 	 */
380 
381 	barg->gb_prtnum = prtnum;
382 	minor = DI_MINOR_NIL;
383 
384 	while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
385 
386 		if (di_minor_spectype(minor) != S_IFBLK)
387 			continue;
388 
389 		name = di_minor_name(minor);
390 		if (!IS_SLCNUM_VALID(name[0]) || name[1] != 0)
391 			continue;
392 
393 		barg->gb_slcnum = name[0];
394 		path = strrchr(barg->gb_root.gr_physpath, ':');
395 		path[1] = barg->gb_slcnum;
396 
397 		if (ufs_bootsign_check(barg) == 0)
398 			return (DI_WALK_TERMINATE);
399 	}
400 
401 	barg->gb_prtnum = (uint_t)PRTNUM_INVALID;
402 	barg->gb_slcnum = (uint_t)SLCNUM_WHOLE_DISK;
403 	return (DI_WALK_CONTINUE);
404 }
405 
406 /*
407  * Differs from what GRUB is doing: GRUB searchs through all disks seen by bios
408  * for bootsign, if bootsign is found on ufs slice GRUB sets it as a root,
409  * if on zfs, then GRUB uses zfs slice as root only if bootsign wasn't found
410  * on other slices.
411  * That function first searches through all top datasets of active zpools,
412  * then if bootsign still not found walks through all disks and tries to
413  * find ufs slice with the bootsign.
414  */
415 int
416 grub_find_bootsign(grub_barg_t *barg)
417 {
418 	grub_menu_t *mp;
419 	mp = barg->gb_entry->ge_menu;
420 
421 	/* try to find bootsign over zfs pools */
422 	barg->gb_walkret = EG_BOOTSIGN;
423 	(void) zfs_iter_root(mp->gm_fs.gf_lzfh, zfs_bootsign, barg);
424 
425 	/* try ufs now */
426 	if (barg->gb_walkret != 0 && di_walk_minor(mp->gm_fs.gf_diroot,
427 	    DDI_NT_BLOCK, 0, barg, ufs_bootsign) != 0)
428 		return (errno);
429 
430 	return (barg->gb_walkret);
431 }
432 
433 /*
434  * Get current root file system.
435  * Return 0 on success, errno code on failure.
436  */
437 int
438 grub_current_root(grub_fs_t *fs, grub_root_t *root)
439 {
440 	int rc = 0;
441 	FILE *fp = NULL;
442 	char *name = NULL;
443 	zfs_handle_t *zfh = NULL;
444 	struct mnttab mp = {0};
445 	struct mnttab mpref = {0};
446 	char buf[MAXNAMELEN] = {0};
447 
448 	mpref.mnt_mountp = "/";
449 
450 	if ((fp = fopen(MNTTAB, "r")) == NULL)
451 		return (errno);
452 
453 	/*
454 	 * getmntany returns non-zero for failure, and sets errno
455 	 */
456 	rc = getmntany(fp, &mp, &mpref);
457 	if (rc != 0)
458 		rc = errno;
459 
460 	(void) fclose(fp);
461 
462 	if (rc != 0)
463 		return (rc);
464 
465 	(void) strlcpy(root->gr_fstyp, mp.mnt_fstype, sizeof (root->gr_fstyp));
466 
467 	if (strcmp(root->gr_fstyp, MNTTYPE_ZFS) == 0) {
468 
469 		(void) strlcpy(buf, mp.mnt_special, sizeof (buf));
470 		if ((name = strtok(buf, "/")) == NULL)
471 			return (EG_CURROOT);
472 
473 		if ((zfh = zfs_open(fs->gf_lzfh, name, ZFS_TYPE_FILESYSTEM)) ==
474 		    NULL)
475 			return (EG_OPENZFS);
476 
477 		/*
478 		 * get_zfs_root returns non-zero on failure, not errno.
479 		 */
480 		if (get_zfs_root(zfh, fs, root))
481 			rc = EG_CURROOT;
482 		else
483 			/*
484 			 * For mirrored root physpath would contain the list of
485 			 * all bootable devices, pick up the first one.
486 			 */
487 			rc = get_one_physpath(root->gr_physpath, SLCNUM_INVALID,
488 			    PRTNUM_INVALID);
489 
490 		zfs_close(zfh);
491 
492 	} else if (strcmp(mp.mnt_fstype, MNTTYPE_UFS) == 0) {
493 		(void) strlcpy(root->gr_fs[GRBM_UFS].gfs_dev, mp.mnt_special,
494 		    sizeof (root->gr_fs[GRBM_UFS].gfs_dev));
495 		(void) strlcpy(root->gr_fs[GRBM_UFS].gfs_mountp, mp.mnt_mountp,
496 		    sizeof (root->gr_fs[GRBM_UFS].gfs_mountp));
497 	} else {
498 		rc = EG_UNKNOWNFS;
499 	}
500 
501 	return (rc);
502 }
503 
504 grub_fsdesc_t *
505 grub_get_rootfsd(const grub_root_t *root)
506 {
507 	grub_fsdesc_t *fsd = NULL;
508 
509 	assert(root);
510 	if (strcmp(MNTTYPE_UFS, root->gr_fstyp) == 0)
511 		fsd = (grub_fsdesc_t *)root->gr_fs + GRBM_UFS;
512 	else if (strcmp(MNTTYPE_ZFS, root->gr_fstyp) == 0)
513 		fsd = (grub_fsdesc_t *)root->gr_fs + GRBM_ZFS_BOOTFS;
514 
515 	return (fsd);
516 }
517 
518 /*
519  * Gets file systems mount point if any.
520  * Return 0 if filesystem is mounted, errno on failure.
521  */
522 int
523 grub_fsd_get_mountp(grub_fsdesc_t *fsd, char *fstyp)
524 {
525 	int rc;
526 	FILE *fp = NULL;
527 	struct mnttab mp = {0};
528 	struct mnttab mpref = {0};
529 
530 	fsd->gfs_mountp[0] = 0;
531 
532 	if ((fp = fopen(MNTTAB, "r")) == NULL)
533 		return (errno);
534 
535 	mpref.mnt_special = fsd->gfs_dev;
536 	mpref.mnt_fstype = fstyp;
537 
538 	if ((rc = getmntany(fp, &mp, &mpref)) == 0)
539 		(void) strlcpy(fsd->gfs_mountp, mp.mnt_mountp,
540 		    sizeof (fsd->gfs_mountp));
541 	else
542 		rc = EG_GETMNTTAB;
543 
544 	(void) fclose(fp);
545 	return (rc);
546 }
547 
548 static const char tmp_mountp[] = "/tmp/.libgrubmgmt.%s.XXXXXX";
549 
550 /*
551  * Mount file system at tmp_mountp.
552  * Return 0 on success, errno on failure.
553  */
554 int
555 grub_fsd_mount_tmp(grub_fsdesc_t *fsd, const char *fstyp)
556 {
557 	const char *pos;
558 	void *data = NULL;
559 	int dtsz = 0;
560 	struct ufs_args ufs_args = {UFSMNT_LARGEFILES};
561 	char mntopts[MNT_LINE_MAX] = "";
562 	int rc = 0;
563 
564 	assert(fsd);
565 	assert(!fsd->gfs_is_tmp_mounted);
566 
567 	fsd->gfs_mountp[0] = 0;
568 
569 	if (strcmp(fstyp, MNTTYPE_UFS) == 0) {
570 		(void) strlcpy(mntopts, MNTOPT_LARGEFILES, sizeof (mntopts));
571 		data = &ufs_args;
572 		dtsz = sizeof (ufs_args);
573 	} else if (strcmp(fstyp, MNTTYPE_ZFS) != 0) {
574 		return (EG_UNKNOWNFS);
575 	}
576 
577 	/* construct name for temporary mount point */
578 	pos = strrchr(fsd->gfs_dev, '/');
579 	pos = (pos == NULL) ? fsd->gfs_dev : pos + 1;
580 
581 	(void) snprintf(fsd->gfs_mountp, sizeof (fsd->gfs_mountp),
582 	    tmp_mountp, pos);
583 	if (mkdtemp(fsd->gfs_mountp) != NULL) {
584 		if ((rc = mount(fsd->gfs_dev, fsd->gfs_mountp,
585 		    MS_DATA | MS_OPTIONSTR | MS_RDONLY,
586 		    fstyp, data, dtsz, mntopts, sizeof (mntopts))) != 0) {
587 			/*
588 			 * mount failed, collect errno and remove temp dir
589 			 */
590 			rc = errno;
591 			(void) rmdir(fsd->gfs_mountp);
592 		}
593 	} else {
594 		rc = errno;
595 	}
596 
597 	if (rc != 0)
598 		fsd->gfs_mountp[0] = 0;
599 
600 	/*
601 	 * Note that valid values for gfs_is_tmp_mounted are 0,1.
602 	 * Any other value indicates that something bad happened.
603 	 * Probably grub_fsd_umount_tmp() wasn't called or didn't
604 	 * work as expected.
605 	 */
606 	fsd->gfs_is_tmp_mounted += (rc == 0);
607 	return (rc);
608 }
609 
610 /*
611  * Unmount file system at tmp_mountp.
612  */
613 void
614 grub_fsd_umount_tmp(grub_fsdesc_t *fsd)
615 {
616 	if (fsd == NULL)
617 		return;
618 
619 	if (fsd->gfs_is_tmp_mounted) {
620 		if (fsd->gfs_mountp[0] != 0) {
621 			(void) umount2(fsd->gfs_mountp, 0);
622 			(void) rmdir(fsd->gfs_mountp);
623 			fsd->gfs_mountp[0] = 0;
624 		}
625 		fsd->gfs_is_tmp_mounted = 0;
626 	}
627 }
628