3877 fast reboot does not work with extended partition
[unleashed.git] / usr / src / lib / libgrubmgmt / common / libgrub_fs.c
blob28a47deabb82e153abad1140f946c7a1f0b22142
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.
30 * This file contains all the functions that manipulate the file
31 * system where the GRUB menu resides.
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
52 #include "libgrub_impl.h"
54 static int
55 slice_match(const char *physpath, int slice)
57 const char *pos;
59 return ((pos = strrchr(physpath, slice)) == NULL ||
60 pos[1] != 0 || pos[-1] != ':');
64 * Returns zero if path contains ufs
66 static int
67 slice_ufs(const char *path)
69 int fd, ret;
70 const char *id;
71 fstyp_handle_t hdl;
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);
78 (void) close(fd);
79 return (ret);
83 static int
84 get_sol_prtnum(const char *physpath)
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
98 (void) snprintf(rdev, sizeof (rdev), "/devices%s,raw", physpath);
100 if ((pos = strrchr(rdev, ':')) == NULL)
101 return (PRTNUM_INVALID);
103 pos[1] = SLCNUM_WHOLE_DISK;
105 fd = open(rdev, O_RDONLY);
106 sz = read(fd, boot_sect, sizeof (boot_sect));
107 (void) close(fd);
109 if (sz != sizeof (boot_sect))
110 return (PRTNUM_INVALID);
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);
119 #if defined(i386) || defined(__amd64)
120 if (!fdisk_is_dos_extended(ipart[i].systid) ||
121 (ext_part_found == 1))
122 continue;
124 ext_part_found = 1;
126 if (libfdisk_init(&epp, rdev, NULL, FDISK_READ_DISK) ==
127 FDISK_SUCCESS) {
128 uint32_t begs, nums;
129 int pno;
130 int rval;
132 rval = fdisk_get_solaris_part(epp, &pno, &begs, &nums);
134 libfdisk_fini(&epp);
136 if (rval == FDISK_SUCCESS)
137 return (pno - 1);
139 #endif
141 return (PRTNUM_INVALID);
145 * Get physpath, topfs and bootfs for ZFS root dataset.
146 * Return 0 on success, non-zero (not errno) on failure.
148 static int
149 get_zfs_root(zfs_handle_t *zfh, grub_fs_t *fs, grub_root_t *root)
151 int ret;
152 zpool_handle_t *zph;
153 const char *name;
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);
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) {
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);
174 zpool_close(zph);
175 return (ret);
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.
184 static int
185 get_one_physpath(char *physpath, uint_t prtnum, uint_t slcnum)
187 int ret;
188 char *tmp, *tok;
190 if (!IS_SLCNUM_VALID(slcnum) && !IS_PRTNUM_VALID(prtnum)) {
191 (void) strtok(physpath, " ");
192 return (0);
195 if ((tmp = strdup(physpath)) == NULL)
196 return (errno);
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;
207 free(tmp);
208 if (ret)
209 ret = ENODEV;
210 return (ret);
213 static int
214 zfs_bootsign(zfs_handle_t *zfh, void *data)
216 grub_barg_t *barg;
217 grub_menu_t *menu;
218 struct stat st;
219 char path[MAXPATHLEN];
221 barg = (grub_barg_t *)data;
222 menu = barg->gb_entry->ge_menu;
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;
231 * if top zfs dataset is not mounted, mount it now
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;
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);
244 if (lstat(path, &st) != 0 || S_ISREG(st.st_mode) == 0 ||
245 (st.st_mode & S_IRUSR) == 0)
246 break;
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);
254 grub_fsd_umount_tmp(barg->gb_root.gr_fs + GRBM_ZFS_TOPFS);
255 zfs_close(zfh);
257 /* return non-zero to terminate the walk */
258 return (barg->gb_walkret == 0);
261 static int
262 get_devlink(di_devlink_t dl, void *arg)
264 const char *path;
265 grub_barg_t *barg;
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);
274 static int
275 ufs_bootsign_check(grub_barg_t *barg)
277 int ret;
278 struct stat st;
279 grub_menu_t *mp;
280 char path[MAXPATHLEN];
282 mp = barg->gb_entry->ge_menu;
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);
289 * if disk is not mounted, mount it now
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);
300 (void) snprintf(path, sizeof (path), "%s%s",
301 barg->gb_root.gr_fs[GRBM_UFS].gfs_mountp, barg->gb_bootsign);
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));
310 grub_fsd_umount_tmp(barg->gb_root.gr_fs + GRBM_UFS);
311 return (barg->gb_walkret);
314 static int
315 ufs_bootsign(di_node_t node, di_minor_t minor, void *arg)
317 uint_t prtnum;
318 char *name, *path;
319 grub_barg_t *barg;
321 barg = (grub_barg_t *)arg;
323 if (di_minor_spectype(minor) != S_IFBLK)
324 return (DI_WALK_CONTINUE);
326 name = di_minor_name(minor);
327 if (name[0] != barg->gb_slcnum || name[1] != 0)
328 return (DI_WALK_CONTINUE);
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);
335 prtnum = get_sol_prtnum(barg->gb_root.gr_physpath);
336 if (!IS_PRTNUM_VALID(prtnum))
337 return (DI_WALK_CONTINUE);
340 * check only specified partition, slice
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);
350 * Walk through all slices in found solaris partition
353 barg->gb_prtnum = prtnum;
354 minor = DI_MINOR_NIL;
356 while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
358 if (di_minor_spectype(minor) != S_IFBLK)
359 continue;
361 name = di_minor_name(minor);
362 if (!IS_SLCNUM_VALID(name[0]) || name[1] != 0)
363 continue;
365 barg->gb_slcnum = name[0];
366 path = strrchr(barg->gb_root.gr_physpath, ':');
367 path[1] = barg->gb_slcnum;
369 if (ufs_bootsign_check(barg) == 0)
370 return (DI_WALK_TERMINATE);
373 barg->gb_prtnum = (uint_t)PRTNUM_INVALID;
374 barg->gb_slcnum = (uint_t)SLCNUM_WHOLE_DISK;
375 return (DI_WALK_CONTINUE);
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.
388 grub_find_bootsign(grub_barg_t *barg)
390 grub_menu_t *mp;
391 mp = barg->gb_entry->ge_menu;
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);
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);
402 return (barg->gb_walkret);
406 * Get current root file system.
407 * Return 0 on success, errno code on failure.
410 grub_current_root(grub_fs_t *fs, grub_root_t *root)
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};
420 mpref.mnt_mountp = "/";
422 if ((fp = fopen(MNTTAB, "r")) == NULL)
423 return (errno);
426 * getmntany returns non-zero for failure, and sets errno
428 rc = getmntany(fp, &mp, &mpref);
429 if (rc != 0)
430 rc = errno;
432 (void) fclose(fp);
434 if (rc != 0)
435 return (rc);
437 (void) strlcpy(root->gr_fstyp, mp.mnt_fstype, sizeof (root->gr_fstyp));
439 if (strcmp(root->gr_fstyp, MNTTYPE_ZFS) == 0) {
441 (void) strlcpy(buf, mp.mnt_special, sizeof (buf));
442 if ((name = strtok(buf, "/")) == NULL)
443 return (EG_CURROOT);
445 if ((zfh = zfs_open(fs->gf_lzfh, name, ZFS_TYPE_FILESYSTEM)) ==
446 NULL)
447 return (EG_OPENZFS);
450 * get_zfs_root returns non-zero on failure, not errno.
452 if (get_zfs_root(zfh, fs, root))
453 rc = EG_CURROOT;
454 else
456 * For mirrored root physpath would contain the list of
457 * all bootable devices, pick up the first one.
459 rc = get_one_physpath(root->gr_physpath, SLCNUM_INVALID,
460 PRTNUM_INVALID);
462 zfs_close(zfh);
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;
473 return (rc);
476 grub_fsdesc_t *
477 grub_get_rootfsd(const grub_root_t *root)
479 grub_fsdesc_t *fsd = NULL;
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;
487 return (fsd);
491 * Gets file systems mount point if any.
492 * Return 0 if filesystem is mounted, errno on failure.
495 grub_fsd_get_mountp(grub_fsdesc_t *fsd, char *fstyp)
497 int rc;
498 FILE *fp = NULL;
499 struct mnttab mp = {0};
500 struct mnttab mpref = {0};
502 fsd->gfs_mountp[0] = 0;
504 if ((fp = fopen(MNTTAB, "r")) == NULL)
505 return (errno);
507 mpref.mnt_special = fsd->gfs_dev;
508 mpref.mnt_fstype = fstyp;
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;
516 (void) fclose(fp);
517 return (rc);
520 static const char tmp_mountp[] = "/tmp/.libgrubmgmt.%s.XXXXXX";
523 * Mount file system at tmp_mountp.
524 * Return 0 on success, errno on failure.
527 grub_fsd_mount_tmp(grub_fsdesc_t *fsd, const char *fstyp)
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;
536 assert(fsd);
537 assert(!fsd->gfs_is_tmp_mounted);
539 fsd->gfs_mountp[0] = 0;
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);
549 /* construct name for temporary mount point */
550 pos = strrchr(fsd->gfs_dev, '/');
551 pos = (pos == NULL) ? fsd->gfs_dev : pos + 1;
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) {
560 * mount failed, collect errno and remove temp dir
562 rc = errno;
563 (void) rmdir(fsd->gfs_mountp);
565 } else {
566 rc = errno;
569 if (rc != 0)
570 fsd->gfs_mountp[0] = 0;
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.
578 fsd->gfs_is_tmp_mounted += (rc == 0);
579 return (rc);
583 * Unmount file system at tmp_mountp.
585 void
586 grub_fsd_umount_tmp(grub_fsdesc_t *fsd)
588 if (fsd == NULL)
589 return;
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;
597 fsd->gfs_is_tmp_mounted = 0;