/* * Used to calculate an array index for global()/local(). Sets *arrayp * to true if this is an array, sets *valp to the array index, returns * the basename of the array. May only be called from global()/local() * and must be their first callee. */ static const char * array_index_calc(const char *n, bool *arrayp, uint32_t *valp) { const char *p; size_t len; char *ap = NULL; *arrayp = false; redo_from_ref: p = skip_varname(n, false); if (innermost_refflag == SRF_NOP && (p != n) && ksh_isalphx(n[0])) { struct tbl *vp; char *vn; strndupx(vn, n, p - n, ATEMP); /* check if this is a reference */ varsearch(e->loc, &vp, vn, hash(vn)); afree(vn, ATEMP); if (vp && (vp->flag & (DEFINED | ASSOC | ARRAY)) == (DEFINED | ASSOC)) { char *cp; /* gotcha! */ cp = shf_smprintf("%s%s", str_val(vp), p); afree(ap, ATEMP); n = ap = cp; goto redo_from_ref; } } innermost_refflag = SRF_NOP; if (p != n && *p == '[' && (len = array_ref_len(p))) { char *sub, *tmp; mksh_ari_t rval; /* calculate the value of the subscript */ *arrayp = true; strndupx(tmp, p + 1, len - 2, ATEMP); sub = substitute(tmp, 0); afree(tmp, ATEMP); strndupx(n, n, p - n, ATEMP); evaluate(sub, &rval, KSH_UNWIND_ERROR, true); *valp = (uint32_t)rval; afree(sub, ATEMP); } return (n); }
/* * Search for local variable, if not found create locally. */ struct tbl * local(const char *n, bool copy) { struct tbl *vp; union mksh_cchack vname; struct block *l = e->loc; bool array; uint32_t h, val; /* * check to see if this is an array; * dereference namerefs; must come first */ vn = array_index_calc(n, &array, &val); h = hash(vn); if (!ctype(*vn, C_ALPHX)) { vp = vtemp; vp->flag = DEFINED|RDONLY; vp->type = 0; vp->areap = ATEMP; goto out; } vp = ktenter(&l->vars, vn, h); if (copy && !(vp->flag & DEFINED)) { struct tbl *vq; varsearch(l->next, &vq, vn, h); if (vq != NULL) { vp->flag |= vq->flag & (EXPORT | INTEGER | RDONLY | LJUST | RJUST | ZEROFIL | LCASEV | UCASEV_AL | INT_U | INT_L); if (vq->flag & INTEGER) vp->type = vq->type; vp->u2.field = vq->u2.field; } } if (array) vp = arraysearch(vp, val); vp->flag |= DEFINED; if (special(vn)) vp->flag |= SPECIAL; out: last_lookup_was_array = array; if (vn != n) afree(vname.rw, ATEMP); return (vp); }
/* * Search for local variable, if not found create locally. */ struct tbl * local(const char *n, bool copy) { struct block *l = e->loc; struct tbl *vp; bool array; uint32_t h, val; /* * check to see if this is an array; * dereference namerefs; must come first */ n = array_index_calc(n, &array, &val); h = hash(n); if (!ksh_isalphx(*n)) { vp = &vtemp; vp->flag = DEFINED|RDONLY; vp->type = 0; vp->areap = ATEMP; return (vp); } vp = ktenter(&l->vars, n, h); if (copy && !(vp->flag & DEFINED)) { struct tbl *vq; varsearch(l->next, &vq, n, h); if (vq != NULL) { vp->flag |= vq->flag & (EXPORT | INTEGER | RDONLY | LJUST | RJUST | ZEROFIL | LCASEV | UCASEV_AL | INT_U | INT_L); if (vq->flag & INTEGER) vp->type = vq->type; vp->u2.field = vq->u2.field; } } if (array) vp = arraysearch(vp, val); vp->flag |= DEFINED; if (special(n)) vp->flag |= SPECIAL; return (vp); }
/* * lookup variable (according to (set&LOCAL)), set its attributes * (INTEGER, RDONLY, EXPORT, TRACE, LJUST, RJUST, ZEROFIL, LCASEV, * UCASEV_AL), and optionally set its value if an assignment. */ struct tbl * typeset(const char *var, uint32_t set, uint32_t clr, int field, int base) { struct tbl *vp; struct tbl *vpbase, *t; char *tvar; const char *val; size_t len; bool vappend = false; enum namerefflag new_refflag = SRF_NOP; if ((set & (ARRAY | ASSOC)) == ASSOC) { new_refflag = SRF_ENABLE; set &= ~(ARRAY | ASSOC); } if ((clr & (ARRAY | ASSOC)) == ASSOC) { new_refflag = SRF_DISABLE; clr &= ~(ARRAY | ASSOC); } /* check for valid variable name, search for value */ val = skip_varname(var, false); if (val == var) { /* no variable name given */ return (NULL); } if (*val == '[') { if (new_refflag != SRF_NOP) errorf("%s: %s", var, "reference variable can't be an array"); len = array_ref_len(val); if (len == 0) return (NULL); /* * IMPORT is only used when the shell starts up and is * setting up its environment. Allow only simple array * references at this time since parameter/command * substitution is performed on the [expression] which * would be a major security hole. */ if (set & IMPORT) { size_t i; for (i = 1; i < len - 1; i++) if (!ksh_isdigit(val[i])) return (NULL); } val += len; } if (val[0] == '=') { strndupx(tvar, var, val - var, ATEMP); ++val; } else if (set & IMPORT) { /* environment invalid variable name or no assignment */ return (NULL); } else if (val[0] == '+' && val[1] == '=') { strndupx(tvar, var, val - var, ATEMP); val += 2; vappend = true; } else if (val[0] != '\0') { /* other invalid variable names (not from environment) */ return (NULL); } else { /* just varname with no value part nor equals sign */ strdupx(tvar, var, ATEMP); val = NULL; /* handle foo[*] => foo (whole array) mapping for R39b */ len = strlen(tvar); if (len > 3 && tvar[len - 3] == '[' && tvar[len - 2] == '*' && tvar[len - 1] == ']') tvar[len - 3] = '\0'; } if (new_refflag == SRF_ENABLE) { const char *qval, *ccp; /* bail out on 'nameref foo+=bar' */ if (vappend) errorf("appending not allowed for nameref"); /* find value if variable already exists */ if ((qval = val) == NULL) { varsearch(e->loc, &vp, tvar, hash(tvar)); if (vp == NULL) goto nameref_empty; qval = str_val(vp); } /* check target value for being a valid variable name */ ccp = skip_varname(qval, false); if (ccp == qval) { if (ksh_isdigit(qval[0])) { int c; if (getn(qval, &c)) goto nameref_rhs_checked; } else if (qval[1] == '\0') switch (qval[0]) { case '$': case '!': case '?': case '#': case '-': goto nameref_rhs_checked; } nameref_empty: errorf("%s: %s", var, "empty nameref target"); } len = (*ccp == '[') ? array_ref_len(ccp) : 0; if (ccp[len]) { /* * works for cases "no array", "valid array with * junk after it" and "invalid array"; in the * latter case, len is also 0 and points to '[' */ errorf("%s: %s", qval, "nameref target not a valid parameter name"); } nameref_rhs_checked: /* prevent nameref loops */ while (qval) { if (!strcmp(qval, tvar)) errorf("%s: %s", qval, "expression recurses on parameter"); varsearch(e->loc, &vp, qval, hash(qval)); qval = NULL; if (vp && ((vp->flag & (ARRAY | ASSOC)) == ASSOC)) qval = str_val(vp); } } /* prevent typeset from creating a local PATH/ENV/SHELL */ if (Flag(FRESTRICTED) && (strcmp(tvar, "PATH") == 0 || strcmp(tvar, "ENV") == 0 || strcmp(tvar, "SHELL") == 0)) errorf("%s: %s", tvar, "restricted"); innermost_refflag = new_refflag; vp = (set & LOCAL) ? local(tvar, tobool(set & LOCAL_COPY)) : global(tvar); if (new_refflag == SRF_DISABLE && (vp->flag & (ARRAY|ASSOC)) == ASSOC) vp->flag &= ~ASSOC; else if (new_refflag == SRF_ENABLE) { if (vp->flag & ARRAY) { struct tbl *a, *tmp; /* free up entire array */ for (a = vp->u.array; a; ) { tmp = a; a = a->u.array; if (tmp->flag & ALLOC) afree(tmp->val.s, tmp->areap); afree(tmp, tmp->areap); } vp->u.array = NULL; vp->flag &= ~ARRAY; } vp->flag |= ASSOC; } set &= ~(LOCAL|LOCAL_COPY); vpbase = (vp->flag & ARRAY) ? global(arrayname(tvar)) : vp; /* * only allow export flag to be set; AT&T ksh allows any * attribute to be changed which means it can be truncated or * modified (-L/-R/-Z/-i) */ if ((vpbase->flag & RDONLY) && (val || clr || (set & ~EXPORT))) /* XXX check calls - is error here ok by POSIX? */ errorfx(2, "read-only: %s", tvar); afree(tvar, ATEMP); /* most calls are with set/clr == 0 */ if (set | clr) { bool ok = true; /* * XXX if x[0] isn't set, there will be problems: need * to have one copy of attributes for arrays... */ for (t = vpbase; t; t = t->u.array) { bool fake_assign; char *s = NULL; char *free_me = NULL; fake_assign = (t->flag & ISSET) && (!val || t != vp) && ((set & (UCASEV_AL|LCASEV|LJUST|RJUST|ZEROFIL)) || ((t->flag & INTEGER) && (clr & INTEGER)) || (!(t->flag & INTEGER) && (set & INTEGER))); if (fake_assign) { if (t->flag & INTEGER) { s = str_val(t); free_me = NULL; } else { s = t->val.s + t->type; free_me = (t->flag & ALLOC) ? t->val.s : NULL; } t->flag &= ~ALLOC; } if (!(t->flag & INTEGER) && (set & INTEGER)) { t->type = 0; t->flag &= ~ALLOC; } t->flag = (t->flag | set) & ~clr; /* * Don't change base if assignment is to be * done, in case assignment fails. */ if ((set & INTEGER) && base > 0 && (!val || t != vp)) t->type = base; if (set & (LJUST|RJUST|ZEROFIL)) t->u2.field = field; if (fake_assign) { if (!setstr(t, s, KSH_RETURN_ERROR)) { /* * Somewhat arbitrary action * here: zap contents of * variable, but keep the flag * settings. */ ok = false; if (t->flag & INTEGER) t->flag &= ~ISSET; else { if (t->flag & ALLOC) afree(t->val.s, t->areap); t->flag &= ~(ISSET|ALLOC); t->type = 0; } } if (free_me) afree(free_me, t->areap); } } if (!ok) errorfz(); } if (val != NULL) { char *tval; if (vappend) { tval = shf_smprintf("%s%s", str_val(vp), val); val = tval; } else tval = NULL; if (vp->flag&INTEGER) { /* do not zero base before assignment */ setstr(vp, val, KSH_UNWIND_ERROR | 0x4); /* done after assignment to override default */ if (base > 0) vp->type = base; } else /* setstr can't fail (readonly check already done) */ setstr(vp, val, KSH_RETURN_ERROR | 0x4); if (tval != NULL) afree(tval, ATEMP); } /* only x[0] is ever exported, so use vpbase */ if ((vpbase->flag&EXPORT) && !(vpbase->flag&INTEGER) && vpbase->type == 0) exportprep(vpbase, (vpbase->flag&ISSET) ? vpbase->val.s : null); return (vp); }
/* * Search for variable, if not found create globally. */ struct tbl * global(const char *n) { struct block *l = e->loc; struct tbl *vp; int c; bool array; uint32_t h, val; /* * check to see if this is an array; * dereference namerefs; must come first */ n = array_index_calc(n, &array, &val); h = hash(n); c = (unsigned char)n[0]; if (!ksh_isalphx(c)) { if (array) errorf("bad substitution"); vp = &vtemp; vp->flag = DEFINED; vp->type = 0; vp->areap = ATEMP; *vp->name = c; if (ksh_isdigit(c)) { if (getn(n, &c) && (c <= l->argc)) /* setstr can't fail here */ setstr(vp, l->argv[c], KSH_RETURN_ERROR); vp->flag |= RDONLY; return (vp); } vp->flag |= RDONLY; if (n[1] != '\0') return (vp); vp->flag |= ISSET|INTEGER; switch (c) { case '$': vp->val.i = kshpid; break; case '!': /* if no job, expand to nothing */ if ((vp->val.i = j_async()) == 0) vp->flag &= ~(ISSET|INTEGER); break; case '?': vp->val.i = exstat & 0xFF; break; case '#': vp->val.i = l->argc; break; case '-': vp->flag &= ~INTEGER; vp->val.s = getoptions(); break; default: vp->flag &= ~(ISSET|INTEGER); } return (vp); } l = varsearch(e->loc, &vp, n, h); if (vp != NULL) return (array ? arraysearch(vp, val) : vp); vp = ktenter(&l->vars, n, h); if (array) vp = arraysearch(vp, val); vp->flag |= DEFINED; if (special(n)) vp->flag |= SPECIAL; return (vp); }
/* search for variable; if not found, return NULL or create globally */ struct tbl * isglobal(const char *n, bool docreate) { struct tbl *vp; union mksh_cchack vname; struct block *l = e->loc; int c; bool array; uint32_t h, val; /* * check to see if this is an array; * dereference namerefs; must come first */ vn = array_index_calc(n, &array, &val); h = hash(vn); c = (unsigned char)vn[0]; if (!ctype(c, C_ALPHX)) { if (array) errorf(Tbadsubst); vp = vtemp; vp->flag = DEFINED; vp->type = 0; vp->areap = ATEMP; if (ctype(c, C_DIGIT)) { if (getn(vn, &c)) { /* main.c:main_init() says 12 */ shf_snprintf(vp->name, 12, Tf_d, c); if (c <= l->argc) { /* setstr can't fail here */ setstr(vp, l->argv[c], KSH_RETURN_ERROR); } } else vp->name[0] = '\0'; vp->flag |= RDONLY; goto out; } vp->name[0] = c; vp->name[1] = '\0'; vp->flag |= RDONLY; if (vn[1] != '\0') goto out; vp->flag |= ISSET|INTEGER; switch (c) { case '$': vp->val.i = kshpid; break; case '!': /* if no job, expand to nothing */ if ((vp->val.i = j_async()) == 0) vp->flag &= ~(ISSET|INTEGER); break; case '?': vp->val.i = exstat & 0xFF; break; case '#': vp->val.i = l->argc; break; case '-': vp->flag &= ~INTEGER; vp->val.s = getoptions(); break; default: vp->flag &= ~(ISSET|INTEGER); } goto out; } l = varsearch(e->loc, &vp, vn, h); if (vp == NULL && docreate) vp = ktenter(&l->vars, vn, h); else docreate = false; if (vp != NULL) { if (array) vp = arraysearch(vp, val); if (docreate) { vp->flag |= DEFINED; if (special(vn)) vp->flag |= SPECIAL; } } out: last_lookup_was_array = array; if (vn != n) afree(vname.rw, ATEMP); return (vp); }