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