Merge branch 'master' of git://github.com/illumos/illumos-gate
[unleashed.git] / usr / src / lib / libgrubmgmt / common / libgrub_fs.c
blobaa5faa6470baa244483619f0f7757264691374a5
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
26 * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
27 * Copyright 2015 Toomas Soome <tsoome@me.com>
31 * This file contains all the functions that manipulate the file
32 * system where the GRUB menu resides.
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
55 #include "libgrub_impl.h"
57 static int
58 slice_match(const char *physpath, int slice)
60 const char *pos;
62 /* always match whole disk slice */
63 if (slice == SLCNUM_WHOLE_DISK)
64 return (0);
66 return ((pos = strrchr(physpath, slice)) == NULL ||
67 pos[1] != 0 || pos[-1] != ':');
71 * Returns zero if path contains ufs
73 static int
74 slice_ufs(const char *path)
76 int fd, ret;
77 const char *id;
78 fstyp_handle_t hdl;
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);
85 (void) close(fd);
86 return (ret);
90 static int
91 get_sol_prtnum(const char *physpath)
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
105 (void) snprintf(rdev, sizeof (rdev), "/devices%s,raw", physpath);
107 if ((pos = strrchr(rdev, ':')) == NULL)
108 return (PRTNUM_INVALID);
111 * first check for EFI partitioning, efi_alloc_and_read()
112 * will return partition number.
114 if ((fd = open(rdev, O_RDONLY|O_NDELAY)) >= 0) {
115 struct dk_gpt *vtoc;
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);
125 (void) close(fd);
126 } else {
127 return (PRTNUM_INVALID);
130 pos[1] = SLCNUM_WHOLE_DISK;
132 fd = open(rdev, O_RDONLY);
133 sz = read(fd, boot_sect, sizeof (boot_sect));
134 (void) close(fd);
136 if (sz != sizeof (boot_sect))
137 return (PRTNUM_INVALID);
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);
146 #if defined(i386) || defined(__amd64)
147 if (!fdisk_is_dos_extended(ipart[i].systid) ||
148 (ext_part_found == 1))
149 continue;
151 ext_part_found = 1;
153 if (libfdisk_init(&epp, rdev, NULL, FDISK_READ_DISK) ==
154 FDISK_SUCCESS) {
155 uint32_t begs, nums;
156 int pno;
157 int rval;
159 rval = fdisk_get_solaris_part(epp, &pno, &begs, &nums);
161 libfdisk_fini(&epp);
163 if (rval == FDISK_SUCCESS)
164 return (pno - 1);
166 #endif
168 return (PRTNUM_INVALID);
172 * Get physpath, topfs and bootfs for ZFS root dataset.
173 * Return 0 on success, non-zero (not errno) on failure.
175 static int
176 get_zfs_root(zfs_handle_t *zfh, grub_fs_t *fs, grub_root_t *root)
178 int ret;
179 zpool_handle_t *zph;
180 const char *name;
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);
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) {
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);
202 zpool_close(zph);
203 return (ret);
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.
212 static int
213 get_one_physpath(char *physpath, uint_t prtnum, uint_t slcnum)
215 int ret;
216 char *tmp, *tok;
218 if (!IS_SLCNUM_VALID(slcnum) && !IS_PRTNUM_VALID(prtnum)) {
219 (void) strtok(physpath, " ");
220 return (0);
223 if ((tmp = strdup(physpath)) == NULL)
224 return (errno);
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;
235 free(tmp);
236 if (ret)
237 ret = ENODEV;
238 return (ret);
241 static int
242 zfs_bootsign(zfs_handle_t *zfh, void *data)
244 grub_barg_t *barg;
245 grub_menu_t *menu;
246 struct stat st;
247 char path[MAXPATHLEN];
249 barg = (grub_barg_t *)data;
250 menu = barg->gb_entry->ge_menu;
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;
259 * if top zfs dataset is not mounted, mount it now
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;
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);
272 if (lstat(path, &st) != 0 || S_ISREG(st.st_mode) == 0 ||
273 (st.st_mode & S_IRUSR) == 0)
274 break;
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);
282 grub_fsd_umount_tmp(barg->gb_root.gr_fs + GRBM_ZFS_TOPFS);
283 zfs_close(zfh);
285 /* return non-zero to terminate the walk */
286 return (barg->gb_walkret == 0);
289 static int
290 get_devlink(di_devlink_t dl, void *arg)
292 const char *path;
293 grub_barg_t *barg;
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);
302 static int
303 ufs_bootsign_check(grub_barg_t *barg)
305 int ret;
306 struct stat st;
307 grub_menu_t *mp;
308 char path[MAXPATHLEN];
310 mp = barg->gb_entry->ge_menu;
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);
317 * if disk is not mounted, mount it now
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);
328 (void) snprintf(path, sizeof (path), "%s%s",
329 barg->gb_root.gr_fs[GRBM_UFS].gfs_mountp, barg->gb_bootsign);
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));
338 grub_fsd_umount_tmp(barg->gb_root.gr_fs + GRBM_UFS);
339 return (barg->gb_walkret);
342 static int
343 ufs_bootsign(di_node_t node, di_minor_t minor, void *arg)
345 uint_t prtnum;
346 char *name, *path;
347 grub_barg_t *barg;
349 barg = (grub_barg_t *)arg;
351 if (di_minor_spectype(minor) != S_IFBLK)
352 return (DI_WALK_CONTINUE);
354 name = di_minor_name(minor);
355 if (name[0] != barg->gb_slcnum || name[1] != 0)
356 return (DI_WALK_CONTINUE);
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);
363 prtnum = get_sol_prtnum(barg->gb_root.gr_physpath);
364 if (!IS_PRTNUM_VALID(prtnum))
365 return (DI_WALK_CONTINUE);
368 * check only specified partition, slice
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);
378 * Walk through all slices in found solaris partition
381 barg->gb_prtnum = prtnum;
382 minor = DI_MINOR_NIL;
384 while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
386 if (di_minor_spectype(minor) != S_IFBLK)
387 continue;
389 name = di_minor_name(minor);
390 if (!IS_SLCNUM_VALID(name[0]) || name[1] != 0)
391 continue;
393 barg->gb_slcnum = name[0];
394 path = strrchr(barg->gb_root.gr_physpath, ':');
395 path[1] = barg->gb_slcnum;
397 if (ufs_bootsign_check(barg) == 0)
398 return (DI_WALK_TERMINATE);
401 barg->gb_prtnum = (uint_t)PRTNUM_INVALID;
402 barg->gb_slcnum = (uint_t)SLCNUM_WHOLE_DISK;
403 return (DI_WALK_CONTINUE);
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.
416 grub_find_bootsign(grub_barg_t *barg)
418 grub_menu_t *mp;
419 mp = barg->gb_entry->ge_menu;
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);
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);
430 return (barg->gb_walkret);
434 * Get current root file system.
435 * Return 0 on success, errno code on failure.
438 grub_current_root(grub_fs_t *fs, grub_root_t *root)
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};
448 mpref.mnt_mountp = "/";
450 if ((fp = fopen(MNTTAB, "r")) == NULL)
451 return (errno);
454 * getmntany returns non-zero for failure, and sets errno
456 rc = getmntany(fp, &mp, &mpref);
457 if (rc != 0)
458 rc = errno;
460 (void) fclose(fp);
462 if (rc != 0)
463 return (rc);
465 (void) strlcpy(root->gr_fstyp, mp.mnt_fstype, sizeof (root->gr_fstyp));
467 if (strcmp(root->gr_fstyp, MNTTYPE_ZFS) == 0) {
469 (void) strlcpy(buf, mp.mnt_special, sizeof (buf));
470 if ((name = strtok(buf, "/")) == NULL)
471 return (EG_CURROOT);
473 if ((zfh = zfs_open(fs->gf_lzfh, name, ZFS_TYPE_FILESYSTEM)) ==
474 NULL)
475 return (EG_OPENZFS);
478 * get_zfs_root returns non-zero on failure, not errno.
480 if (get_zfs_root(zfh, fs, root))
481 rc = EG_CURROOT;
482 else
484 * For mirrored root physpath would contain the list of
485 * all bootable devices, pick up the first one.
487 rc = get_one_physpath(root->gr_physpath, SLCNUM_INVALID,
488 PRTNUM_INVALID);
490 zfs_close(zfh);
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;
501 return (rc);
504 grub_fsdesc_t *
505 grub_get_rootfsd(const grub_root_t *root)
507 grub_fsdesc_t *fsd = NULL;
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;
515 return (fsd);
519 * Gets file systems mount point if any.
520 * Return 0 if filesystem is mounted, errno on failure.
523 grub_fsd_get_mountp(grub_fsdesc_t *fsd, char *fstyp)
525 int rc;
526 FILE *fp = NULL;
527 struct mnttab mp = {0};
528 struct mnttab mpref = {0};
530 fsd->gfs_mountp[0] = 0;
532 if ((fp = fopen(MNTTAB, "r")) == NULL)
533 return (errno);
535 mpref.mnt_special = fsd->gfs_dev;
536 mpref.mnt_fstype = fstyp;
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;
544 (void) fclose(fp);
545 return (rc);
548 static const char tmp_mountp[] = "/tmp/.libgrubmgmt.%s.XXXXXX";
551 * Mount file system at tmp_mountp.
552 * Return 0 on success, errno on failure.
555 grub_fsd_mount_tmp(grub_fsdesc_t *fsd, const char *fstyp)
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;
564 assert(fsd);
565 assert(!fsd->gfs_is_tmp_mounted);
567 fsd->gfs_mountp[0] = 0;
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);
577 /* construct name for temporary mount point */
578 pos = strrchr(fsd->gfs_dev, '/');
579 pos = (pos == NULL) ? fsd->gfs_dev : pos + 1;
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) {
588 * mount failed, collect errno and remove temp dir
590 rc = errno;
591 (void) rmdir(fsd->gfs_mountp);
593 } else {
594 rc = errno;
597 if (rc != 0)
598 fsd->gfs_mountp[0] = 0;
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.
606 fsd->gfs_is_tmp_mounted += (rc == 0);
607 return (rc);
611 * Unmount file system at tmp_mountp.
613 void
614 grub_fsd_umount_tmp(grub_fsdesc_t *fsd)
616 if (fsd == NULL)
617 return;
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;
625 fsd->gfs_is_tmp_mounted = 0;