xref: /titanic_52/usr/src/lib/libgrubmgmt/common/libgrub_fs.c (revision d14abf155341d55053c76eeec58b787a456b753b)
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,
165 	    B_FALSE)) == 0) {
166 
167 		(void) strlcpy(root->gr_fs[GRBM_ZFS_TOPFS].gfs_dev, name,
168 		    sizeof (root->gr_fs[GRBM_ZFS_TOPFS].gfs_dev));
169 		(void) grub_fsd_get_mountp(root->gr_fs + GRBM_ZFS_BOOTFS,
170 		    MNTTYPE_ZFS);
171 		(void) grub_fsd_get_mountp(root->gr_fs + GRBM_ZFS_TOPFS,
172 		    MNTTYPE_ZFS);
173 	}
174 
175 	zpool_close(zph);
176 	return (ret);
177 }
178 
179 /*
180  * On entry physpath parameter supposed to contain:
181  * <disk_physpath>[<space><disk_physpath>]*.
182  * Retrieves first <disk_physpath> that matches both partition and slice.
183  * If any partition and slice is acceptable, first <disk_physpath> is returned.
184  */
185 static int
186 get_one_physpath(char *physpath, uint_t prtnum, uint_t slcnum)
187 {
188 	int ret;
189 	char *tmp, *tok;
190 
191 	if (!IS_SLCNUM_VALID(slcnum) && !IS_PRTNUM_VALID(prtnum)) {
192 		(void) strtok(physpath, " ");
193 		return (0);
194 	}
195 
196 	if ((tmp = strdup(physpath)) == NULL)
197 		return (errno);
198 
199 	ret = ENODEV;
200 	for (tok = strtok(tmp, " "); tok != NULL; tok = strtok(NULL, " ")) {
201 		if ((ret = (slice_match(tok, slcnum) != 0 ||
202 		    get_sol_prtnum(tok) != prtnum)) == 0) {
203 			(void) strcpy(physpath, tok);
204 			break;
205 		}
206 	}
207 
208 	free(tmp);
209 	if (ret)
210 		ret = ENODEV;
211 	return (ret);
212 }
213 
214 static int
215 zfs_bootsign(zfs_handle_t *zfh, void *data)
216 {
217 	grub_barg_t *barg;
218 	grub_menu_t *menu;
219 	struct stat st;
220 	char path[MAXPATHLEN];
221 
222 	barg = (grub_barg_t *)data;
223 	menu = barg->gb_entry->ge_menu;
224 
225 	do {
226 		if (get_zfs_root(zfh, &menu->gm_fs, &barg->gb_root) != 0 ||
227 		    get_one_physpath(barg->gb_root.gr_physpath, barg->gb_prtnum,
228 		    barg->gb_slcnum) != 0)
229 			break;
230 
231 		/*
232 		 * if top zfs dataset is not mounted, mount it now
233 		 */
234 		if (barg->gb_root.gr_fs[GRBM_ZFS_TOPFS].gfs_mountp[0] == 0) {
235 			if (grub_fsd_mount_tmp(barg->gb_root.gr_fs +
236 			    GRBM_ZFS_TOPFS, MNTTYPE_ZFS) != 0)
237 				break;
238 		}
239 
240 		/* check that bootsign exists and it is a regular file */
241 		(void) snprintf(path, sizeof (path), "%s%s",
242 		    barg->gb_root.gr_fs[GRBM_ZFS_TOPFS].gfs_mountp,
243 		    barg->gb_bootsign);
244 
245 		if (lstat(path, &st) != 0 || S_ISREG(st.st_mode) == 0 ||
246 		    (st.st_mode & S_IRUSR) == 0)
247 			break;
248 
249 		(void) strlcpy(barg->gb_root.gr_fstyp, MNTTYPE_ZFS,
250 		    sizeof (barg->gb_root.gr_fstyp));
251 		barg->gb_walkret = 0;
252 	/* LINTED: E_CONSTANT_CONDITION */
253 	} while (0);
254 
255 	grub_fsd_umount_tmp(barg->gb_root.gr_fs + GRBM_ZFS_TOPFS);
256 	zfs_close(zfh);
257 
258 	/* return non-zero to terminate the walk */
259 	return (barg->gb_walkret == 0);
260 }
261 
262 static int
263 get_devlink(di_devlink_t dl, void *arg)
264 {
265 	const char *path;
266 	grub_barg_t *barg;
267 
268 	barg = (grub_barg_t *)arg;
269 	if ((path = di_devlink_path(dl)) != NULL)
270 		(void) strlcpy(barg->gb_root.gr_fs[GRBM_UFS].gfs_dev, path,
271 		    sizeof (barg->gb_root.gr_fs[GRBM_UFS].gfs_dev));
272 	return (DI_WALK_TERMINATE);
273 }
274 
275 static int
276 ufs_bootsign_check(grub_barg_t *barg)
277 {
278 	int ret;
279 	struct stat st;
280 	grub_menu_t *mp;
281 	char path[MAXPATHLEN];
282 
283 	mp = barg->gb_entry->ge_menu;
284 
285 	/* get /dev/dsk link */
286 	if (di_devlink_walk(mp->gm_fs.gf_dvlh, "^dsk/",
287 	    barg->gb_root.gr_physpath, DI_PRIMARY_LINK, barg, get_devlink) != 0)
288 		return (errno);
289 	/*
290 	 * if disk is not mounted, mount it now
291 	 */
292 	if (grub_fsd_get_mountp(barg->gb_root.gr_fs + GRBM_UFS,
293 	    MNTTYPE_UFS) != 0) {
294 		if ((ret =
295 		    slice_ufs(barg->gb_root.gr_fs[GRBM_UFS].gfs_dev)) != 0 ||
296 		    (ret = grub_fsd_mount_tmp(barg->gb_root.gr_fs + GRBM_UFS,
297 		    MNTTYPE_UFS)) != 0)
298 			return (ret);
299 	}
300 
301 	(void) snprintf(path, sizeof (path), "%s%s",
302 	    barg->gb_root.gr_fs[GRBM_UFS].gfs_mountp, barg->gb_bootsign);
303 
304 	if (lstat(path, &st) == 0 && S_ISREG(st.st_mode) &&
305 	    (st.st_mode & S_IRUSR) != 0) {
306 		barg->gb_walkret = 0;
307 		(void) strlcpy(barg->gb_root.gr_fstyp, MNTTYPE_UFS,
308 		    sizeof (barg->gb_root.gr_fstyp));
309 	}
310 
311 	grub_fsd_umount_tmp(barg->gb_root.gr_fs + GRBM_UFS);
312 	return (barg->gb_walkret);
313 }
314 
315 static int
316 ufs_bootsign(di_node_t node, di_minor_t minor, void *arg)
317 {
318 	uint_t prtnum;
319 	char *name, *path;
320 	grub_barg_t *barg;
321 
322 	barg = (grub_barg_t *)arg;
323 
324 	if (di_minor_spectype(minor) != S_IFBLK)
325 		return (DI_WALK_CONTINUE);
326 
327 	name = di_minor_name(minor);
328 	if (name[0] != barg->gb_slcnum || name[1] != 0)
329 		return (DI_WALK_CONTINUE);
330 
331 	path = di_devfs_path(node);
332 	(void) snprintf(barg->gb_root.gr_physpath,
333 	    sizeof (barg->gb_root.gr_physpath), "%s:%c", path, barg->gb_slcnum);
334 	di_devfs_path_free(path);
335 
336 	prtnum = get_sol_prtnum(barg->gb_root.gr_physpath);
337 	if (!IS_PRTNUM_VALID(prtnum))
338 		return (DI_WALK_CONTINUE);
339 
340 	/*
341 	 * check only specified partition, slice
342 	 */
343 
344 	if (IS_PRTNUM_VALID(barg->gb_prtnum)) {
345 		if (prtnum != barg->gb_prtnum || ufs_bootsign_check(barg) != 0)
346 			return (DI_WALK_CONTINUE);
347 		return (DI_WALK_TERMINATE);
348 	}
349 
350 	/*
351 	 * Walk through all slices in found solaris partition
352 	 */
353 
354 	barg->gb_prtnum = prtnum;
355 	minor = DI_MINOR_NIL;
356 
357 	while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
358 
359 		if (di_minor_spectype(minor) != S_IFBLK)
360 			continue;
361 
362 		name = di_minor_name(minor);
363 		if (!IS_SLCNUM_VALID(name[0]) || name[1] != 0)
364 			continue;
365 
366 		barg->gb_slcnum = name[0];
367 		path = strrchr(barg->gb_root.gr_physpath, ':');
368 		path[1] = barg->gb_slcnum;
369 
370 		if (ufs_bootsign_check(barg) == 0)
371 			return (DI_WALK_TERMINATE);
372 	}
373 
374 	barg->gb_prtnum = (uint_t)PRTNUM_INVALID;
375 	barg->gb_slcnum = (uint_t)SLCNUM_WHOLE_DISK;
376 	return (DI_WALK_CONTINUE);
377 }
378 
379 /*
380  * Differs from what GRUB is doing: GRUB searchs through all disks seen by bios
381  * for bootsign, if bootsign is found on ufs slice GRUB sets it as a root,
382  * if on zfs, then GRUB uses zfs slice as root only if bootsign wasn't found
383  * on other slices.
384  * That function first searches through all top datasets of active zpools,
385  * then if bootsign still not found walks through all disks and tries to
386  * find ufs slice with the bootsign.
387  */
388 int
389 grub_find_bootsign(grub_barg_t *barg)
390 {
391 	grub_menu_t *mp;
392 	mp = barg->gb_entry->ge_menu;
393 
394 	/* try to find bootsign over zfs pools */
395 	barg->gb_walkret = EG_BOOTSIGN;
396 	(void) zfs_iter_root(mp->gm_fs.gf_lzfh, zfs_bootsign, barg);
397 
398 	/* try ufs now */
399 	if (barg->gb_walkret != 0 && di_walk_minor(mp->gm_fs.gf_diroot,
400 	    DDI_NT_BLOCK, 0, barg, ufs_bootsign) != 0)
401 		return (errno);
402 
403 	return (barg->gb_walkret);
404 }
405 
406 /*
407  * Get current root file system.
408  * Return 0 on success, errno code on failure.
409  */
410 int
411 grub_current_root(grub_fs_t *fs, grub_root_t *root)
412 {
413 	int rc = 0;
414 	FILE *fp = NULL;
415 	char *name = NULL;
416 	zfs_handle_t *zfh = NULL;
417 	struct mnttab mp = {0};
418 	struct mnttab mpref = {0};
419 	char buf[MAXNAMELEN] = {0};
420 
421 	mpref.mnt_mountp = "/";
422 
423 	if ((fp = fopen(MNTTAB, "r")) == NULL)
424 		return (errno);
425 
426 	/*
427 	 * getmntany returns non-zero for failure, and sets errno
428 	 */
429 	rc = getmntany(fp, &mp, &mpref);
430 	if (rc != 0)
431 		rc = errno;
432 
433 	(void) fclose(fp);
434 
435 	if (rc != 0)
436 		return (rc);
437 
438 	(void) strlcpy(root->gr_fstyp, mp.mnt_fstype, sizeof (root->gr_fstyp));
439 
440 	if (strcmp(root->gr_fstyp, MNTTYPE_ZFS) == 0) {
441 
442 		(void) strlcpy(buf, mp.mnt_special, sizeof (buf));
443 		if ((name = strtok(buf, "/")) == NULL)
444 			return (EG_CURROOT);
445 
446 		if ((zfh = zfs_open(fs->gf_lzfh, name, ZFS_TYPE_FILESYSTEM)) ==
447 		    NULL)
448 			return (EG_OPENZFS);
449 
450 		/*
451 		 * get_zfs_root returns non-zero on failure, not errno.
452 		 */
453 		if (get_zfs_root(zfh, fs, root))
454 			rc = EG_CURROOT;
455 		else
456 			/*
457 			 * For mirrored root physpath would contain the list of
458 			 * all bootable devices, pick up the first one.
459 			 */
460 			rc = get_one_physpath(root->gr_physpath, SLCNUM_INVALID,
461 			    PRTNUM_INVALID);
462 
463 		zfs_close(zfh);
464 
465 	} else if (strcmp(mp.mnt_fstype, MNTTYPE_UFS) == 0) {
466 		(void) strlcpy(root->gr_fs[GRBM_UFS].gfs_dev, mp.mnt_special,
467 		    sizeof (root->gr_fs[GRBM_UFS].gfs_dev));
468 		(void) strlcpy(root->gr_fs[GRBM_UFS].gfs_mountp, mp.mnt_mountp,
469 		    sizeof (root->gr_fs[GRBM_UFS].gfs_mountp));
470 	} else {
471 		rc = EG_UNKNOWNFS;
472 	}
473 
474 	return (rc);
475 }
476 
477 grub_fsdesc_t *
478 grub_get_rootfsd(const grub_root_t *root)
479 {
480 	grub_fsdesc_t *fsd = NULL;
481 
482 	assert(root);
483 	if (strcmp(MNTTYPE_UFS, root->gr_fstyp) == 0)
484 		fsd = (grub_fsdesc_t *)root->gr_fs + GRBM_UFS;
485 	else if (strcmp(MNTTYPE_ZFS, root->gr_fstyp) == 0)
486 		fsd = (grub_fsdesc_t *)root->gr_fs + GRBM_ZFS_BOOTFS;
487 
488 	return (fsd);
489 }
490 
491 /*
492  * Gets file systems mount point if any.
493  * Return 0 if filesystem is mounted, errno on failure.
494  */
495 int
496 grub_fsd_get_mountp(grub_fsdesc_t *fsd, char *fstyp)
497 {
498 	int rc;
499 	FILE *fp = NULL;
500 	struct mnttab mp = {0};
501 	struct mnttab mpref = {0};
502 
503 	fsd->gfs_mountp[0] = 0;
504 
505 	if ((fp = fopen(MNTTAB, "r")) == NULL)
506 		return (errno);
507 
508 	mpref.mnt_special = fsd->gfs_dev;
509 	mpref.mnt_fstype = fstyp;
510 
511 	if ((rc = getmntany(fp, &mp, &mpref)) == 0)
512 		(void) strlcpy(fsd->gfs_mountp, mp.mnt_mountp,
513 		    sizeof (fsd->gfs_mountp));
514 	else
515 		rc = EG_GETMNTTAB;
516 
517 	(void) fclose(fp);
518 	return (rc);
519 }
520 
521 static const char tmp_mountp[] = "/tmp/.libgrubmgmt.%s.XXXXXX";
522 
523 /*
524  * Mount file system at tmp_mountp.
525  * Return 0 on success, errno on failure.
526  */
527 int
528 grub_fsd_mount_tmp(grub_fsdesc_t *fsd, const char *fstyp)
529 {
530 	const char *pos;
531 	void *data = NULL;
532 	int dtsz = 0;
533 	struct ufs_args ufs_args = {UFSMNT_LARGEFILES};
534 	char mntopts[MNT_LINE_MAX] = "";
535 	int rc = 0;
536 
537 	assert(fsd);
538 	assert(!fsd->gfs_is_tmp_mounted);
539 
540 	fsd->gfs_mountp[0] = 0;
541 
542 	if (strcmp(fstyp, MNTTYPE_UFS) == 0) {
543 		(void) strlcpy(mntopts, MNTOPT_LARGEFILES, sizeof (mntopts));
544 		data = &ufs_args;
545 		dtsz = sizeof (ufs_args);
546 	} else if (strcmp(fstyp, MNTTYPE_ZFS) != 0) {
547 		return (EG_UNKNOWNFS);
548 	}
549 
550 	/* construct name for temporary mount point */
551 	pos = strrchr(fsd->gfs_dev, '/');
552 	pos = (pos == NULL) ? fsd->gfs_dev : pos + 1;
553 
554 	(void) snprintf(fsd->gfs_mountp, sizeof (fsd->gfs_mountp),
555 	    tmp_mountp, pos);
556 	if (mkdtemp(fsd->gfs_mountp) != NULL) {
557 		if ((rc = mount(fsd->gfs_dev, fsd->gfs_mountp,
558 		    MS_DATA | MS_OPTIONSTR | MS_RDONLY,
559 		    fstyp, data, dtsz, mntopts, sizeof (mntopts))) != 0) {
560 			/*
561 			 * mount failed, collect errno and remove temp dir
562 			 */
563 			rc = errno;
564 			(void) rmdir(fsd->gfs_mountp);
565 		}
566 	} else {
567 		rc = errno;
568 	}
569 
570 	if (rc != 0)
571 		fsd->gfs_mountp[0] = 0;
572 
573 	/*
574 	 * Note that valid values for gfs_is_tmp_mounted are 0,1.
575 	 * Any other value indicates that something bad happened.
576 	 * Probably grub_fsd_umount_tmp() wasn't called or didn't
577 	 * work as expected.
578 	 */
579 	fsd->gfs_is_tmp_mounted += (rc == 0);
580 	return (rc);
581 }
582 
583 /*
584  * Unmount file system at tmp_mountp.
585  */
586 void
587 grub_fsd_umount_tmp(grub_fsdesc_t *fsd)
588 {
589 	if (fsd == NULL)
590 		return;
591 
592 	if (fsd->gfs_is_tmp_mounted) {
593 		if (fsd->gfs_mountp[0] != 0) {
594 			(void) umount2(fsd->gfs_mountp, 0);
595 			(void) rmdir(fsd->gfs_mountp);
596 			fsd->gfs_mountp[0] = 0;
597 		}
598 		fsd->gfs_is_tmp_mounted = 0;
599 	}
600 }
601