On Sat, Jul 07, 2018 at 10:06:41AM +0200, Andreas Kusalananda Kähäri wrote:
> Hi,
>
> I'm not sure this is a bug or a misunderstanding by me, but it sure
> surprised me:
>
> $ set -- file.txt txt
>
> $ printf 'Two values: %s %s\n' "$1" "$2"
> Two values: file.txt txt
>
> $ printf 'First value with suffix removed: %s\n' "${1%.*}"
> First value with suffix removed: file
>
> $ printf 'First value with second value removed from end: %s\n' "${1%.$2}"
> First value with second value removed from end: txt
>
> I would have expected the last two parameter expansion to generate the
> same result ("file"). Also, removing the dot ("${1%$2}") generates an
> empty result.
>
> Note that using variables, this works as expected:
>
> $ filename=file.txt
> $ suffix=txt
> $ printf 'First value with second value removed from end: %s\n'
> "${filename%.$suffix}"
> First value with second value removed from end: file
>
> This is on a current system using either /bin/sh or /bin/ksh and the
> effect of trying to remove from the start of the string is similar.
That's indeed a bug. Lookup of positional arguments ($1 and $2) is done
using global() which in turn returns a pointer to static storage for
read-only variables (positional arguments are read-only). So the second
call to global() overwrites the previous one. Here's a first stab in
which read-only variables are copied during expansion. I ended up adding
a new function in order to avoid deep indenting. Also, zero st_head in
order to simplify the free loop; otherwise st_head.var would contain
stack garbage.
Comments? OK?
Index: eval.c
===================================================================
RCS file: /cvs/src/bin/ksh/eval.c,v
retrieving revision 1.60
diff -u -p -r1.60 eval.c
--- eval.c 9 Apr 2018 17:53:36 -0000 1.60
+++ eval.c 8 Jul 2018 08:17:05 -0000
@@ -58,6 +58,8 @@ static char *tilde(char *);
static char *homedir(char *);
static void alt_expand(XPtrV *, char *, char *, char *, int);
+static struct tbl *varcpy(struct tbl *);
+
/* compile and expand word */
char *
substitute(const char *cp, int f)
@@ -190,7 +192,8 @@ expand(char *cp, /* input word */
doblank = 0;
make_magic = 0;
word = (f&DOBLANK) ? IFS_WS : IFS_WORD;
- st_head.next = NULL;
+
+ memset(&st_head, 0, sizeof(st_head));
st = &st_head;
while (1) {
@@ -305,7 +308,7 @@ expand(char *cp, /* input word */
st->stype = stype;
st->base = Xsavepos(ds, dp);
st->f = f;
- st->var = x.var;
+ st->var = varcpy(x.var);
st->quote = quote;
/* skip qualifier(s) */
if (stype)
@@ -577,7 +580,7 @@ expand(char *cp, /* input word */
Xinit(ds, dp, 128, ATEMP);
}
if (c == 0)
- return;
+ goto done;
if (word != IFS_NWS)
word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS;
} else {
@@ -682,6 +685,14 @@ expand(char *cp, /* input word */
word = IFS_WORD;
}
}
+
+done:
+ for (st = &st_head; st != NULL; st = st->next) {
+ if (st->var == NULL || (st->var->flag & RDONLY) == 0)
+ continue;
+
+ afree(st->var, ATEMP);
+ }
}
/*
@@ -1285,4 +1296,24 @@ alt_expand(XPtrV *wp, char *start, char
}
}
return;
+}
+
+/*
+ * Copy the given variable if it's flagged as read-only.
+ * Such variables has static storage and only one can therefore be referenced
at
+ * a time.
+ * This is necessary in order to allow variable expansion expressions to refer
+ * to multiple read-only variables.
+ */
+static struct tbl *
+varcpy(struct tbl *vp)
+{
+ struct tbl *cpy;
+
+ if ((vp->flag & RDONLY) == 0)
+ return vp;
+
+ cpy = alloc(sizeof(struct tbl), ATEMP);
+ memcpy(cpy, vp, sizeof(struct tbl));
+ return cpy;
}