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