Index: grub/util/grub.d/10_linux.in =================================================================== --- grub.orig/util/grub.d/10_linux.in 2012-01-24 23:44:10.530591000 -0600 +++ grub/util/grub.d/10_linux.in 2012-01-24 23:44:10.706928000 -0600 @@ -56,8 +56,10 @@ LINUX_ROOT_DEVICE=UUID=${GRUB_DEVICE_UUID} fi -if [ "x`${grub_probe} --device ${GRUB_DEVICE} --target=fs 2>/dev/null || true`" = xbtrfs ] \ - || [ "x`stat -f --printf=%T /`" = xbtrfs ]; then +LINUX_ROOT_FS=`${grub_probe} --device ${GRUB_DEVICE} --target=fs 2>/dev/null || true` +LINUX_ROOT_STAT=`stat -f --printf=%T / || true` + +if [ "x${LINUX_ROOT_FS}" = xbtrfs -o "x${LINUX_ROOT_STAT}" = xbtrfs ]; then rootsubvol="`make_system_path_relative_to_its_root /`" rootsubvol="${rootsubvol#/}" if [ "x${rootsubvol}" != x ]; then @@ -76,6 +78,10 @@ GRUB_CMDLINE_EXTRA="$GRUB_CMDLINE_EXTRA crashkernel=384M-2G:64M,2G-:128M" fi +if [ "x${LINUX_ROOT_FS}" = xzfs ]; then + GRUB_CMDLINE_LINUX="boot=zfs \$bootfs ${GRUB_CMDLINE_LINUX}" +fi + linux_entry () { os="$1" @@ -114,6 +120,12 @@ fi printf '%s\n' "${prepare_boot_cache}" fi + if [ "x${LINUX_ROOT_FS}" = xzfs ]; then + cat << EOF + insmod zfsinfo + zfs-bootfs (\$root) bootfs +EOF + fi if [ "x$5" != "xquiet" ]; then message="$(gettext_printf "Loading Linux %s ..." ${version})" cat << EOF Index: grub/util/getroot.c =================================================================== --- grub.orig/util/getroot.c 2012-01-24 23:44:04.105772000 -0600 +++ grub/util/getroot.c 2012-01-27 20:48:50.875006000 -0600 @@ -52,6 +52,8 @@ #endif #ifdef __linux__ +# include +# include # include # include #endif @@ -115,6 +117,8 @@ return path; } +static char *find_device_from_pool (const char *poolname); + #ifdef __linux__ #define ESCAPED_PATH_MAX (4 * PATH_MAX) @@ -263,7 +267,32 @@ if (!*entries[i].device) continue; - ret = strdup (entries[i].device); + if (strcmp (entries[i].fstype, "zfs") == 0) + { + char *poolname = entries[i].device; + char *poolname_i = poolname; + char *poolname_j = poolname; + /* Replace \040 with a space. Cut at the first slash. */ + while (*poolname_j) + { + if (*poolname_j == '/') + break; + if (strncmp (poolname_j, "\\040", 4) == 0) + { + *poolname_i = ' '; + poolname_i++; + poolname_j += 4; + continue; + } + *poolname_i = *poolname_j; + poolname_i++; + poolname_j++; + } + *poolname_i = '\0'; + ret = find_device_from_pool (poolname); + } + else + ret = strdup (entries[i].device); if (relroot) *relroot = strdup (entries[i].enc_root); break; @@ -280,13 +309,25 @@ static char * find_root_device_from_libzfs (const char *dir) { - char *device = NULL; + char *device; char *poolname; char *poolfs; grub_find_zpool_from_dir (dir, &poolname, &poolfs); if (! poolname) return NULL; + if (poolfs) + free (poolfs); + + device = find_device_from_pool(poolname); + free(poolname); + return device; +} + +static char * +find_device_from_pool (const char *poolname) +{ + char *device = NULL; #if defined(HAVE_LIBZFS) && defined(HAVE_LIBNVPAIR) { @@ -357,7 +398,7 @@ char cksum[257], notes[257]; unsigned int dummy; - cmd = xasprintf ("zpool status %s", poolname); + cmd = xasprintf ("zpool status \"%s\"", poolname); fp = popen (cmd, "r"); free (cmd); @@ -382,7 +423,10 @@ st++; break; case 1: - if (!strcmp (name, poolname)) + /* Use strncmp() because poolname can technically have trailing + spaces, which the sscanf() above will not catch. Since we've + asked about this pool specifically, this should be safe. */ + if (!strncmp (name, poolname, strlen(name))) st++; break; case 2: @@ -395,17 +439,71 @@ free (line); } + +#ifdef __linux__ + /* The name returned by zpool isn't necessarily directly under /dev. */ + { + const char *disk_naming_schemes[] = { + "/dev/disk/by-id/%s", + "/dev/disk/by-path/%s", + "/dev/disk/by-uuid/%s", + "/dev/disk/by-partuuid/%s", + "/dev/disk/by-label/%s", + "/dev/disk/by-partlabel/%s", + "/dev/%s", + NULL + }; + const char **disk_naming_scheme = disk_naming_schemes; + + for (; *disk_naming_scheme ; disk_naming_scheme++) + { + struct stat sb; + device = xasprintf (*disk_naming_scheme, name); + if (stat (device, &sb) == 0) + { + char *real_device; + const char *c; + char *first_partition; + + /* Resolve the symlink to something like /dev/sda. */ + real_device = realpath (device, NULL); + free (device); + + /* It ends in a number; assume it's a partition and stop. */ + for (c = real_device ; *(c+1) ; c++); + if (*c >= '0' && *c <= '9') + { + device = real_device; + break; + } + + /* Otherwise, it might be a partitioned wholedisk vdev. */ + first_partition = xasprintf ("%s1", real_device); + if (stat (first_partition, &sb) == 0) + { + free (real_device); + device = first_partition; + break; + } + + /* The device is not partitioned. */ + free (device); + device = real_device; + break; + } + free (device); + device = NULL; + } + } +#else device = xasprintf ("/dev/%s", name); +#endif /* !__linux__ */ fail: pclose (fp); } #endif - free (poolname); - if (poolfs) - free (poolfs); - return device; } @@ -708,10 +806,10 @@ #ifdef __linux__ if (!os_dev) os_dev = grub_find_root_device_from_mountinfo (dir, NULL); -#endif /* __linux__ */ - +#else if (!os_dev) os_dev = find_root_device_from_libzfs (dir); +#endif /* !__linux__ */ if (os_dev) { @@ -1484,6 +1582,37 @@ break; } } + + fclose (mnttab); + } +#elif defined(__linux__) + { + struct stat st; + struct mntent *mnt; + FILE *mnttab; + + if (stat (dir, &st) != 0) + return; + + mnttab = fopen ("/proc/mounts", "r"); + if (! mnttab) + mnttab = fopen ("/etc/mtab", "r"); + if (! mnttab) + return; + + while ((mnt = getmntent (mnttab)) != NULL) + { + struct stat mnt_st; + if (strcmp (mnt->mnt_type, "zfs") != 0) + continue; + if (stat (mnt->mnt_dir, &mnt_st) != 0) + continue; + if (mnt_st.st_dev == st.st_dev) + { + *poolname = xstrdup (mnt->mnt_fsname); + break; + } + } fclose (mnttab); }