#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <alloca.h>
#include <errno.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/fs/ufs_mount.h>
#include <sys/dktp/fdisk.h>
#if defined(__i386)
#include <sys/x86_archext.h>
#endif
#include "libgrub_impl.h"
#define RESET_MODULE(barg) ((barg)->gb_module[0] = 0)
#define BPROP_ZFSBOOTFS "zfs-bootfs"
#define BPROP_BOOTPATH "bootpath"
#if defined(__i386)
static const char cpuid_dev[] = "/dev/cpu/self/cpuid";
static int
cpuid_64bit_capable(void)
{
int fd, ret = -1;
struct {
uint32_t cp_eax, cp_ebx, cp_ecx, cp_edx;
} cpuid_regs;
if ((fd = open(cpuid_dev, O_RDONLY)) == -1)
return (ret);
if (pread(fd, &cpuid_regs, sizeof (cpuid_regs), 0x80000001) ==
sizeof (cpuid_regs))
ret = ((CPUID_AMD_EDX_LM & cpuid_regs.cp_edx) != 0);
(void) close(fd);
return (ret);
}
#endif
#if !defined(__i386)
#endif
static size_t
barg_isadir_var(char *var, int sz)
{
#if defined(__i386)
if (cpuid_64bit_capable() == 1)
return (strlcpy(var, "amd64", sz));
#endif
var[0] = 0;
return (0);
}
static size_t
barg_bootfs_var(const grub_barg_t *barg, char *var, int sz)
{
int n;
assert(barg);
if (strcmp(barg->gb_root.gr_fstyp, MNTTYPE_ZFS) == 0) {
n = snprintf(var, sz,
BPROP_ZFSBOOTFS "=%s," BPROP_BOOTPATH "=\"%s\"",
barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev,
barg->gb_root.gr_physpath);
} else {
var[0] = 0;
n = 0;
}
return (n);
}
static int
expand_var(char *arg, size_t argsz, const char *var, size_t varsz,
char *val, size_t valsz)
{
char *sp = arg;
size_t sz = argsz, len;
char *buf, *dst, *src;
int ret = 0;
buf = alloca(argsz);
dst = buf;
while ((src = strstr(sp, var)) != NULL) {
len = src - sp;
if (len + valsz > sz) {
ret = E2BIG;
break;
}
(void) bcopy(sp, dst, len);
(void) bcopy(val, dst + len, valsz);
dst += len + valsz;
sz -= len + valsz;
sp = src + varsz;
}
if (strlcpy(dst, sp, sz) >= sz)
ret = E2BIG;
if (ret == 0)
bcopy(buf, arg, argsz);
return (ret);
}
static const char *
find_bootprop(const char *str, const char *bprop)
{
const char *s;
size_t bplen, len;
assert(str);
assert(bprop);
bplen = strlen(bprop);
s = str;
while ((str = strstr(s, " -B")) != NULL ||
(str = strstr(s, "\t-B")) != NULL) {
s = str + 3;
len = strspn(s, " \t");
if (len != 0 && s[len] == '-')
continue;
s += len;
do {
len = strcspn(s, "= \t");
if (s[len] != '=')
break;
if (len == bplen && strncmp(s, bprop, bplen) == 0)
return (s);
s += len;
while ((s = strpbrk(s + 1, "\"\', \t")) != NULL) {
if (s[0] == '\"' || s[0] == '\'') {
if ((s = strchr(s + 1, s[0])) == NULL) {
return (s);
}
}
else
break;
}
if (s == NULL)
return (s);
if (s[0] != ',')
break;
s += strspn(s, ",");
} while (s[0] != ' ' && s[0] != '\t');
}
return (NULL);
}
static int
update_bootpath(char *str, size_t strsz, const char *bootpath)
{
size_t n;
char *buf;
const char *bfs;
if ((bfs = find_bootprop(str, BPROP_ZFSBOOTFS)) == NULL ||
find_bootprop(str, BPROP_BOOTPATH) != NULL)
return (0);
n = bfs - str;
buf = alloca(strsz);
bcopy(str, buf, n);
if (snprintf(buf + n, strsz - n, BPROP_BOOTPATH "=\"%s\",%s",
bootpath, bfs) >= strsz - n)
return (E2BIG);
bcopy(buf, str, strsz);
return (0);
}
static int
match_bootfs(zfs_handle_t *zfh, void *data)
{
int ret;
const char *zfn;
grub_barg_t *barg = (grub_barg_t *)data;
ret = (zfs_get_type(zfh) == ZFS_TYPE_FILESYSTEM &&
(zfn = zfs_get_name(zfh)) != NULL &&
strcmp(barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev, zfn) == 0);
if (ret != 0)
barg->gb_walkret = 0;
else
(void) zfs_iter_filesystems(zfh, match_bootfs, barg);
zfs_close(zfh);
return (barg->gb_walkret == 0);
}
static void
reset_root(grub_barg_t *barg)
{
(void) memset(&barg->gb_root, 0, sizeof (barg->gb_root));
barg->gb_bootsign[0] = 0;
barg->gb_kernel[0] = 0;
RESET_MODULE(barg);
}
int
skip_line(const grub_line_t *lp, grub_barg_t *barg)
{
return (0);
}
int
error_line(const grub_line_t *lp, grub_barg_t *barg)
{
if (lp->gl_cmdtp == GRBM_ROOT_CMD)
return (EG_ROOTNOTSUPP);
return (EG_INVALIDLINE);
}
int
kernel(const grub_line_t *lp, grub_barg_t *barg)
{
RESET_MODULE(barg);
if (strlcpy(barg->gb_kernel, lp->gl_arg, sizeof (barg->gb_kernel)) >=
sizeof (barg->gb_kernel))
return (E2BIG);
return (0);
}
int
module(const grub_line_t *lp, grub_barg_t *barg)
{
if (strlcpy(barg->gb_module, lp->gl_arg, sizeof (barg->gb_module)) >=
sizeof (barg->gb_module))
return (E2BIG);
return (0);
}
int
dollar_kernel(const grub_line_t *lp, grub_barg_t *barg)
{
int ret;
size_t bfslen, isalen;
char isadir[32];
char bootfs[BOOTARGS_MAX];
RESET_MODULE(barg);
if (strlcpy(barg->gb_kernel, lp->gl_arg, sizeof (barg->gb_kernel)) >=
sizeof (barg->gb_kernel))
return (E2BIG);
bfslen = barg_bootfs_var(barg, bootfs, sizeof (bootfs));
isalen = barg_isadir_var(isadir, sizeof (isadir));
if (bfslen >= sizeof (bootfs) || isalen >= sizeof (isadir))
return (EINVAL);
if ((ret = expand_var(barg->gb_kernel, sizeof (barg->gb_kernel),
ZFS_BOOT_VAR, strlen(ZFS_BOOT_VAR), bootfs, bfslen)) != 0)
return (ret);
if ((ret = expand_var(barg->gb_kernel, sizeof (barg->gb_kernel),
ISADIR_VAR, strlen(ISADIR_VAR), isadir, isalen)) != 0)
return (ret);
if (strcmp(barg->gb_root.gr_fstyp, MNTTYPE_ZFS) == 0)
ret = update_bootpath(barg->gb_kernel, sizeof (barg->gb_kernel),
barg->gb_root.gr_physpath);
return (ret);
}
int
dollar_module(const grub_line_t *lp, grub_barg_t *barg)
{
int ret;
size_t isalen;
char isadir[32];
if (strlcpy(barg->gb_module, lp->gl_arg, sizeof (barg->gb_module)) >=
sizeof (barg->gb_module))
return (E2BIG);
if ((isalen = barg_isadir_var(isadir, sizeof (isadir))) >= sizeof
(isadir))
return (EINVAL);
ret = expand_var(barg->gb_module, sizeof (barg->gb_module),
ISADIR_VAR, strlen(ISADIR_VAR), isadir, isalen);
return (ret);
}
int
findroot(const grub_line_t *lp, grub_barg_t *barg)
{
size_t sz, bsz;
const char *sign;
reset_root(barg);
sign = lp->gl_arg;
barg->gb_prtnum = (uint_t)PRTNUM_INVALID;
barg->gb_slcnum = (uint_t)SLCNUM_WHOLE_DISK;
if (sign[0] == '(') {
const char *pos;
++sign;
if ((pos = strchr(sign, ',')) == NULL || (sz = pos - sign) == 0)
return (EG_FINDROOTFMT);
++pos;
if (!IS_PRTNUM_VALID(barg->gb_prtnum = pos[0] - '0'))
return (EG_FINDROOTFMT);
++pos;
if (pos[0] != ')') {
if (pos[0] != ',' ||
!IS_SLCNUM_VALID(barg->gb_slcnum = pos[1]) ||
pos[2] != ')')
return (EG_FINDROOTFMT);
}
} else {
sz = strlen(sign);
}
bsz = strlen(BOOTSIGN_DIR "/");
if (bsz + sz + 1 > sizeof (barg->gb_bootsign))
return (E2BIG);
bcopy(BOOTSIGN_DIR "/", barg->gb_bootsign, bsz);
bcopy(sign, barg->gb_bootsign + bsz, sz);
barg->gb_bootsign [bsz + sz] = 0;
return (grub_find_bootsign(barg));
}
int
bootfs(const grub_line_t *lp, grub_barg_t *barg)
{
zfs_handle_t *zfh;
grub_menu_t *mp = barg->gb_entry->ge_menu;
char *gfs_devp;
size_t gfs_dev_len;
if (strcmp(barg->gb_root.gr_fstyp, MNTTYPE_ZFS) != 0)
return (EG_NOTZFS);
gfs_devp = barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev;
gfs_dev_len = sizeof (barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev);
if (strcmp(lp->gl_arg, gfs_devp) == 0)
return (0);
if (strlcpy(gfs_devp, lp->gl_arg, gfs_dev_len) >= gfs_dev_len)
return (E2BIG);
if ((zfh = zfs_open(mp->gm_fs.gf_lzfh,
barg->gb_root.gr_fs[GRBM_ZFS_TOPFS].gfs_dev,
ZFS_TYPE_FILESYSTEM)) == NULL)
return (EG_OPENZFS);
barg->gb_walkret = EG_UNKBOOTFS;
(void) zfs_iter_filesystems(zfh, match_bootfs, barg);
zfs_close(zfh);
if (barg->gb_walkret == 0)
(void) grub_fsd_get_mountp(barg->gb_root.gr_fs +
GRBM_ZFS_BOOTFS, MNTTYPE_ZFS);
return (barg->gb_walkret);
}