#include "am.h"
static struct am_opts fs_static;
static char *opt_host = hostname;
static char *opt_hostd = hostd;
static char nullstr[] = "";
static char *opt_key = nullstr;
static char *opt_map = nullstr;
static char *opt_path = nullstr;
static char *vars[8];
#define NLEN 16
#define S(x) (x) , (sizeof(x)-1)
static struct opt {
char *name;
int nlen;
char **optp;
char **sel_p;
} opt_fields[] = {
{ S("opts"), &fs_static.opt_opts, 0 },
{ S("host"), 0, &opt_host },
{ S("hostd"), 0, &opt_hostd },
{ S("type"), &fs_static.opt_type, 0 },
{ S("rhost"), &fs_static.opt_rhost, 0 },
{ S("rfs"), &fs_static.opt_rfs, 0 },
{ S("fs"), &fs_static.opt_fs, 0 },
{ S("key"), 0, &opt_key },
{ S("map"), 0, &opt_map },
{ S("sublink"), &fs_static.opt_sublink, 0 },
{ S("arch"), 0, &arch },
{ S("dev"), &fs_static.opt_dev, 0 },
{ S("pref"), &fs_static.opt_pref, 0 },
{ S("path"), 0, &opt_path },
{ S("autodir"), 0, &auto_dir },
{ S("delay"), &fs_static.opt_delay, 0 },
{ S("domain"), 0, &hostdomain },
{ S("karch"), 0, &karch },
{ S("cluster"), 0, &cluster },
{ S("wire"), 0, &wire },
{ S("byte"), 0, &endian },
{ S("os"), 0, &op_sys },
{ S("remopts"), &fs_static.opt_remopts, 0 },
{ S("mount"), &fs_static.opt_mount, 0 },
{ S("unmount"), &fs_static.opt_unmount, 0 },
{ S("cache"), &fs_static.opt_cache, 0 },
{ S("user"), &fs_static.opt_user, 0 },
{ S("group"), &fs_static.opt_group, 0 },
{ S("var0"), &vars[0], 0 },
{ S("var1"), &vars[1], 0 },
{ S("var2"), &vars[2], 0 },
{ S("var3"), &vars[3], 0 },
{ S("var4"), &vars[4], 0 },
{ S("var5"), &vars[5], 0 },
{ S("var6"), &vars[6], 0 },
{ S("var7"), &vars[7], 0 },
{ 0, 0, 0, 0 },
};
typedef struct opt_apply opt_apply;
struct opt_apply {
char **opt;
char *val;
};
static opt_apply rhost_expansion[] = {
{ &fs_static.opt_rhost, "${host}" },
{ 0, 0 },
};
static opt_apply expansions[] = {
{ &fs_static.opt_sublink, 0 },
{ &fs_static.opt_rfs, "${path}" },
{ &fs_static.opt_fs, "${autodir}/${rhost}${rfs}" },
{ &fs_static.opt_opts, "rw" },
{ &fs_static.opt_remopts, "${opts}" },
{ &fs_static.opt_mount, 0 },
{ &fs_static.opt_unmount, 0 },
{ 0, 0 },
};
static opt_apply to_free[] = {
{ &fs_static.fs_glob, 0 },
{ &fs_static.fs_local, 0 },
{ &fs_static.fs_mtab, 0 },
{ &fs_static.opt_sublink, 0 },
{ &fs_static.opt_rfs, 0 },
{ &fs_static.opt_fs, 0 },
{ &fs_static.opt_rhost, 0 },
{ &fs_static.opt_opts, 0 },
{ &fs_static.opt_remopts, 0 },
{ &fs_static.opt_mount, 0 },
{ &fs_static.opt_unmount, 0 },
{ &vars[0], 0 },
{ &vars[1], 0 },
{ &vars[2], 0 },
{ &vars[3], 0 },
{ &vars[4], 0 },
{ &vars[5], 0 },
{ &vars[6], 0 },
{ &vars[7], 0 },
{ 0, 0 },
};
static char *
opt(char **p)
{
char *cp = *p;
char *dp = cp;
char *s = cp;
top:
while (*cp && *cp != ';') {
if (*cp == '\"') {
cp++;
while (*cp && *cp != '\"')
*dp++ = *cp++;
if (*cp)
cp++;
} else {
*dp++ = *cp++;
}
}
while (*cp == ';')
cp++;
if (*cp && dp == s)
goto top;
*dp = '\0';
*p = cp;
return s;
}
static int
eval_opts(char *opts, char *mapkey)
{
char *o = opts;
char *f;
while (*(f = opt(&o))) {
struct opt *op;
enum vs_opt { OldSyn, SelEQ, SelNE, VarAss } vs_opt;
char *eq = strchr(f, '=');
char *opt;
if (!eq || eq[1] == '\0' || eq == f) {
plog(XLOG_USER, "key %s: No value component in \"%s\"", mapkey, f);
continue;
}
if (eq[-1] == '!') {
vs_opt = SelNE;
eq[-1] = '\0';
opt = eq + 1;
} else if (eq[-1] == ':') {
vs_opt = VarAss;
eq[-1] = '\0';
opt = eq + 1;
} else if (eq[1] == '=') {
vs_opt = SelEQ;
eq[0] = '\0';
opt = eq + 2;
} else if (eq[1] == '!') {
vs_opt = SelNE;
eq[0] = '\0';
opt = eq + 2;
} else {
vs_opt = OldSyn;
eq[0] = '\0';
opt = eq + 1;
}
for (op = opt_fields; op->name; op++) {
if (FSTREQ(op->name, f)) {
switch (vs_opt) {
#if 1
case OldSyn:
plog(XLOG_WARNING, "key %s: Old syntax selector found: %s=%s", mapkey, f, opt);
if (!op->sel_p) {
*op->optp = opt;
break;
}
#endif
case SelEQ:
case SelNE:
if (op->sel_p && (STREQ(*op->sel_p, opt) == (vs_opt == SelNE))) {
plog(XLOG_MAP, "key %s: map selector %s (=%s) did not %smatch %s",
mapkey,
op->name,
*op->sel_p,
vs_opt == SelNE ? "not " : "",
opt);
return 0;
}
break;
case VarAss:
if (op->sel_p) {
plog(XLOG_USER, "key %s: Can't assign to a selector (%s)", mapkey, op->name);
return 0;
}
*op->optp = opt;
break;
}
break;
}
}
if (!op->name)
plog(XLOG_USER, "key %s: Unrecognised key/option \"%s\"", mapkey, f);
}
return 1;
}
static void
free_op(opt_apply *p, int b)
{
if (*p->opt) {
free(*p->opt);
*p->opt = 0;
}
}
void
normalize_slash(char *p)
{
char *f = strchr(p, '/');
char *f0 = f;
if (f) {
char *t = f;
do {
if (f == f0 && f[0] == '/' && f[1] == '/') {
*t++ = *f++;
*t++ = *f++;
} else {
*t++ = *f++;
}
while (*f == '/')
f++;
while (*f && *f != '/') {
*t++ = *f++;
}
} while (*f);
*t = 0;
}
}
static void
expand_op(opt_apply *p, int sel_p)
{
#define BUFSPACE(ep, len) (((ep) + (len)) < expbuf+PATH_MAX)
static char expand_error[] = "No space to expand \"%s\"";
char expbuf[PATH_MAX+1];
char nbuf[NLEN+1];
char *ep = expbuf;
char *cp = *p->opt;
char *dp;
#ifdef DEBUG
char *cp_orig = *p->opt;
#endif
struct opt *op;
while ((dp = strchr(cp, '$'))) {
char ch;
{ int len = dp - cp;
if (BUFSPACE(ep, len)) {
strncpy(ep, cp, len);
ep += len;
} else {
plog(XLOG_ERROR, expand_error, *p->opt);
goto out;
}
}
cp = dp + 1;
ch = *cp++;
if (ch == '$') {
if (BUFSPACE(ep, 1)) {
*ep++ = '$';
} else {
plog(XLOG_ERROR, expand_error, *p->opt);
goto out;
}
} else if (ch == '{') {
enum { E_All, E_Dir, E_File, E_Domain, E_Host } todo;
char *br_p = strchr(cp, '}');
int len;
if (!br_p) {
plog(XLOG_USER, "No closing '}' in \"%s\"", *p->opt);
goto out;
}
len = br_p - cp;
if (*cp == '/') {
todo = E_File;
cp++;
--len;
} else if (br_p[-1] == '/') {
todo = E_Dir;
--len;
} else if (*cp == '.') {
todo = E_Domain;
cp++;
--len;
} else if (br_p[-1] == '.') {
todo = E_Host;
--len;
} else {
todo = E_All;
}
if (len > NLEN)
len = NLEN;
strncpy(nbuf, cp, len);
nbuf[len] = '\0';
cp = br_p + 1;
for (op = opt_fields; op->name; op++) {
if (len == op->nlen && STREQ(op->name, nbuf)) {
char xbuf[NLEN+3];
char *val;
if (!(!op->sel_p == !sel_p)) {
snprintf(xbuf, sizeof(xbuf), "${%s%s%s}",
todo == E_File ? "/" :
todo == E_Domain ? "." : "",
nbuf,
todo == E_Dir ? "/" :
todo == E_Host ? "." : "");
val = xbuf;
todo = E_All;
} else if (op->sel_p) {
val = *op->sel_p;
} else {
val = *op->optp;
}
if (val) {
int vlen = strlen(val);
char *vptr = val;
switch (todo) {
case E_Dir:
vptr = strrchr(val, '/');
if (vptr)
vlen = vptr - val;
vptr = val;
break;
case E_File:
vptr = strrchr(val, '/');
if (vptr) {
vptr++;
vlen = strlen(vptr);
} else
vptr = val;
break;
case E_Domain:
vptr = strchr(val, '.');
if (vptr) {
vptr++;
vlen = strlen(vptr);
} else {
vptr = "";
vlen = 0;
}
break;
case E_Host:
vptr = strchr(val, '.');
if (vptr)
vlen = vptr - val;
vptr = val;
break;
case E_All:
break;
}
#ifdef DEBUG
#endif
if (BUFSPACE(ep, vlen)) {
strlcpy(ep, vptr, expbuf + sizeof expbuf - ep);
ep += strlen(ep);
} else {
plog(XLOG_ERROR, expand_error, *p->opt);
goto out;
}
}
break;
}
}
if (!op->name) {
char *env = getenv(nbuf);
if (env) {
int vlen = strlen(env);
if (BUFSPACE(ep, vlen)) {
strlcpy(ep, env, expbuf + sizeof expbuf - ep);
ep += strlen(ep);
} else {
plog(XLOG_ERROR, expand_error, *p->opt);
goto out;
}
#ifdef DEBUG
Debug(D_STR)
plog(XLOG_DEBUG, "Environment gave \"%s\" -> \"%s\"", nbuf, env);
#endif
} else {
plog(XLOG_USER, "Unknown sequence \"${%s}\"", nbuf);
}
}
} else {
plog(XLOG_USER, "Unknown $ sequence in \"%s\"", *p->opt);
}
}
out:
if (cp == *p->opt) {
*p->opt = strdup(cp);
} else {
if (BUFSPACE(ep, strlen(cp))) {
strlcpy(ep, cp, expbuf + sizeof expbuf - ep);
} else {
plog(XLOG_ERROR, expand_error, *p->opt);
}
*p->opt = strdup(expbuf);
}
normalize_slash(*p->opt);
#ifdef DEBUG
Debug(D_STR) {
plog(XLOG_DEBUG, "Expansion of \"%s\"...", cp_orig);
plog(XLOG_DEBUG, "... is \"%s\"", *p->opt);
}
#endif
}
static void
expand_opts(opt_apply *p, int sel_p)
{
if (*p->opt) {
expand_op(p, sel_p);
} else if (p->val) {
char *s = *p->opt = expand_key(p->val);
expand_op(p, sel_p);
free(s);
}
}
static void
apply_opts(void (*op)(), opt_apply ppp[], int b)
{
opt_apply *pp;
for (pp = ppp; pp->opt; pp++)
(*op)(pp, b);
}
void
free_opts(am_opts *fo)
{
fs_static = *fo;
apply_opts(free_op, to_free, FALSE);
}
char *
expand_key(char *key)
{
opt_apply oa;
oa.opt = &key; oa.val = 0;
expand_opts(&oa, TRUE);
return key;
}
void
deslashify(char *s)
{
if (s && *s) {
char *sl = s + strlen(s);
while (*--sl == '/' && sl > s)
*sl = '\0';
}
}
int
eval_fs_opts(am_opts *fo, char *opts, char *g_opts, char *path,
char *key, char *map)
{
int ok = TRUE;
free_opts(fo);
bzero(&fs_static, sizeof(fs_static));
bzero(vars, sizeof(vars));
bzero(fo, sizeof(*fo));
opt_key = key;
opt_map = map;
opt_path = path;
fs_static.fs_glob = expand_key(g_opts);
fs_static.fs_local = expand_key(opts);
if (!eval_opts(fs_static.fs_glob, key))
ok = FALSE;
if (ok && !eval_opts(fs_static.fs_local, key))
ok = FALSE;
apply_opts(expand_opts, rhost_expansion, FALSE);
if (ok && fs_static.opt_rhost && *fs_static.opt_rhost)
host_normalize(&fs_static.opt_rhost);
apply_opts(expand_opts, expansions, FALSE);
deslashify(fs_static.opt_fs);
*fo = fs_static;
opt_key = opt_map = opt_path = nullstr;
return ok;
}