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