727a664bee
anway) on tech-userlevel with no adverse response. This allows the magic of vars like HOSTNAME SECONDS, ToD (etc) to be restored should it be lost - perhaps by having a var of the same name imported from the environment (which needs to remove the magic in case a set of scripts are using the env to pass data, and the var name chosen happens to be one of our magic ones). No change to SMALL shells (or smaller) - none of the magic vars (except LINENO, which is exempt from all of this) exist in those, hence such a shell has no need for this command either.
1663 lines
34 KiB
C
1663 lines
34 KiB
C
/* $NetBSD: var.c,v 1.78 2019/02/14 11:15:24 kre Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 1991, 1993
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to Berkeley by
|
|
* Kenneth Almquist.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
#ifndef lint
|
|
#if 0
|
|
static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95";
|
|
#else
|
|
__RCSID("$NetBSD: var.c,v 1.78 2019/02/14 11:15:24 kre Exp $");
|
|
#endif
|
|
#endif /* not lint */
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <paths.h>
|
|
#include <limits.h>
|
|
#include <time.h>
|
|
#include <pwd.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
|
|
/*
|
|
* Shell variables.
|
|
*/
|
|
|
|
#include "shell.h"
|
|
#include "output.h"
|
|
#include "expand.h"
|
|
#include "nodes.h" /* for other headers */
|
|
#include "eval.h" /* defines cmdenviron */
|
|
#include "exec.h"
|
|
#include "syntax.h"
|
|
#include "options.h"
|
|
#include "builtins.h"
|
|
#include "mail.h"
|
|
#include "var.h"
|
|
#include "memalloc.h"
|
|
#include "error.h"
|
|
#include "mystring.h"
|
|
#include "parser.h"
|
|
#include "show.h"
|
|
#include "machdep.h"
|
|
#ifndef SMALL
|
|
#include "myhistedit.h"
|
|
#endif
|
|
|
|
#ifdef SMALL
|
|
#define VTABSIZE 39
|
|
#else
|
|
#define VTABSIZE 517
|
|
#endif
|
|
|
|
|
|
struct varinit {
|
|
struct var *var;
|
|
int flags;
|
|
const char *text;
|
|
union var_func_union v_u;
|
|
};
|
|
#define func v_u.set_func
|
|
#define rfunc v_u.ref_func
|
|
|
|
char *get_lineno(struct var *);
|
|
|
|
#ifndef SMALL
|
|
char *get_tod(struct var *);
|
|
char *get_hostname(struct var *);
|
|
char *get_seconds(struct var *);
|
|
char *get_euser(struct var *);
|
|
char *get_random(struct var *);
|
|
#endif
|
|
|
|
struct localvar *localvars;
|
|
|
|
#ifndef SMALL
|
|
struct var vhistsize;
|
|
struct var vterm;
|
|
struct var editrc;
|
|
struct var ps_lit;
|
|
#endif
|
|
struct var vifs;
|
|
struct var vmail;
|
|
struct var vmpath;
|
|
struct var vpath;
|
|
struct var vps1;
|
|
struct var vps2;
|
|
struct var vps4;
|
|
struct var vvers;
|
|
struct var voptind;
|
|
struct var line_num;
|
|
#ifndef SMALL
|
|
struct var tod;
|
|
struct var host_name;
|
|
struct var seconds;
|
|
struct var euname;
|
|
struct var random_num;
|
|
|
|
intmax_t sh_start_time;
|
|
#endif
|
|
|
|
struct var line_num;
|
|
int line_number;
|
|
int funclinebase = 0;
|
|
int funclineabs = 0;
|
|
|
|
char ifs_default[] = " \t\n";
|
|
|
|
const struct varinit varinit[] = {
|
|
#ifndef SMALL
|
|
{ &vhistsize, VSTRFIXED|VTEXTFIXED|VUNSET, "HISTSIZE=",
|
|
{ .set_func= sethistsize } },
|
|
#endif
|
|
{ &vifs, VSTRFIXED|VTEXTFIXED, "IFS= \t\n",
|
|
{ NULL } },
|
|
{ &vmail, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL=",
|
|
{ NULL } },
|
|
{ &vmpath, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH=",
|
|
{ NULL } },
|
|
{ &vvers, VSTRFIXED|VTEXTFIXED|VNOEXPORT, "NETBSD_SHELL=",
|
|
{ NULL } },
|
|
{ &vpath, VSTRFIXED|VTEXTFIXED, "PATH=" _PATH_DEFPATH,
|
|
{ .set_func= changepath } },
|
|
/*
|
|
* vps1 depends on uid
|
|
*/
|
|
{ &vps2, VSTRFIXED|VTEXTFIXED, "PS2=> ",
|
|
{ NULL } },
|
|
{ &vps4, VSTRFIXED|VTEXTFIXED, "PS4=+ ",
|
|
{ NULL } },
|
|
#ifndef SMALL
|
|
{ &vterm, VSTRFIXED|VTEXTFIXED|VUNSET, "TERM=",
|
|
{ .set_func= setterm } },
|
|
{ &editrc, VSTRFIXED|VTEXTFIXED|VUNSET, "EDITRC=",
|
|
{ .set_func= set_editrc } },
|
|
{ &ps_lit, VSTRFIXED|VTEXTFIXED|VUNSET, "PSlit=",
|
|
{ .set_func= set_prompt_lit } },
|
|
#endif
|
|
{ &voptind, VSTRFIXED|VTEXTFIXED|VNOFUNC, "OPTIND=1",
|
|
{ .set_func= getoptsreset } },
|
|
{ &line_num, VSTRFIXED|VTEXTFIXED|VFUNCREF|VSPECIAL, "LINENO=1",
|
|
{ .ref_func= get_lineno } },
|
|
#ifndef SMALL
|
|
{ &tod, VSTRFIXED|VTEXTFIXED|VFUNCREF, "ToD=",
|
|
{ .ref_func= get_tod } },
|
|
{ &host_name, VSTRFIXED|VTEXTFIXED|VFUNCREF, "HOSTNAME=",
|
|
{ .ref_func= get_hostname } },
|
|
{ &seconds, VSTRFIXED|VTEXTFIXED|VFUNCREF, "SECONDS=",
|
|
{ .ref_func= get_seconds } },
|
|
{ &euname, VSTRFIXED|VTEXTFIXED|VFUNCREF, "EUSER=",
|
|
{ .ref_func= get_euser } },
|
|
{ &random_num, VSTRFIXED|VTEXTFIXED|VFUNCREF|VSPECIAL, "RANDOM=",
|
|
{ .ref_func= get_random } },
|
|
#endif
|
|
{ NULL, 0, NULL,
|
|
{ NULL } }
|
|
};
|
|
|
|
struct var *vartab[VTABSIZE];
|
|
|
|
STATIC int strequal(const char *, const char *);
|
|
STATIC struct var *find_var(const char *, struct var ***, int *);
|
|
STATIC void showvar(struct var *, const char *, const char *, int);
|
|
static void export_usage(const char *) __dead;
|
|
STATIC int makespecial(const char *);
|
|
|
|
/*
|
|
* Initialize the varable symbol tables and import the environment
|
|
*/
|
|
|
|
#ifdef mkinit
|
|
INCLUDE <stdio.h>
|
|
INCLUDE <unistd.h>
|
|
INCLUDE <time.h>
|
|
INCLUDE "var.h"
|
|
INCLUDE "version.h"
|
|
MKINIT char **environ;
|
|
INIT {
|
|
char **envp;
|
|
char buf[64];
|
|
|
|
#ifndef SMALL
|
|
sh_start_time = (intmax_t)time((time_t *)0);
|
|
#endif
|
|
/*
|
|
* Set up our default variables and their values.
|
|
*/
|
|
initvar();
|
|
|
|
/*
|
|
* Import variables from the environment, which will
|
|
* override anything initialised just previously.
|
|
*/
|
|
for (envp = environ ; *envp ; envp++) {
|
|
if (strchr(*envp, '=')) {
|
|
setvareq(*envp, VEXPORT|VTEXTFIXED);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set variables which override anything read from environment.
|
|
*
|
|
* PPID is readonly
|
|
* Always default IFS
|
|
* POSIX: "Whenever the shell is invoked, OPTIND shall
|
|
* be initialized to 1."
|
|
* PSc indicates the root/non-root status of this shell.
|
|
* START_TIME belongs only to this shell.
|
|
* NETBSD_SHELL is a constant (readonly), and is never exported
|
|
* LINENO is simply magic...
|
|
*/
|
|
snprintf(buf, sizeof(buf), "%d", (int)getppid());
|
|
setvar("PPID", buf, VREADONLY);
|
|
setvar("IFS", ifs_default, VTEXTFIXED);
|
|
setvar("OPTIND", "1", VTEXTFIXED);
|
|
setvar("PSc", (geteuid() == 0 ? "#" : "$"), VTEXTFIXED);
|
|
|
|
#ifndef SMALL
|
|
snprintf(buf, sizeof(buf), "%jd", sh_start_time);
|
|
setvar("START_TIME", buf, VTEXTFIXED);
|
|
#endif
|
|
|
|
setvar("NETBSD_SHELL", NETBSD_SHELL
|
|
#ifdef BUILD_DATE
|
|
" BUILD:" BUILD_DATE
|
|
#endif
|
|
#ifdef DEBUG
|
|
" DEBUG"
|
|
#endif
|
|
#if !defined(JOBS) || JOBS == 0
|
|
" -JOBS"
|
|
#endif
|
|
#ifndef DO_SHAREDVFORK
|
|
" -VFORK"
|
|
#endif
|
|
#ifdef SMALL
|
|
" SMALL"
|
|
#endif
|
|
#ifdef TINY
|
|
" TINY"
|
|
#endif
|
|
#ifdef OLD_TTY_DRIVER
|
|
" OLD_TTY"
|
|
#endif
|
|
#ifdef SYSV
|
|
" SYSV"
|
|
#endif
|
|
#ifndef BSD
|
|
" -BSD"
|
|
#endif
|
|
#ifdef BOGUS_NOT_COMMAND
|
|
" BOGUS_NOT"
|
|
#endif
|
|
, VTEXTFIXED|VREADONLY|VNOEXPORT);
|
|
|
|
setvar("LINENO", "1", VTEXTFIXED);
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* This routine initializes the builtin variables. It is called when the
|
|
* shell is initialized and again when a shell procedure is spawned.
|
|
*/
|
|
|
|
void
|
|
initvar(void)
|
|
{
|
|
const struct varinit *ip;
|
|
struct var *vp;
|
|
struct var **vpp;
|
|
|
|
for (ip = varinit ; (vp = ip->var) != NULL ; ip++) {
|
|
if (find_var(ip->text, &vpp, &vp->name_len) != NULL)
|
|
continue;
|
|
vp->next = *vpp;
|
|
*vpp = vp;
|
|
vp->text = strdup(ip->text);
|
|
vp->flags = (ip->flags & ~VTEXTFIXED) | VSTRFIXED;
|
|
vp->v_u = ip->v_u;
|
|
}
|
|
/*
|
|
* PS1 depends on uid
|
|
*/
|
|
if (find_var("PS1", &vpp, &vps1.name_len) == NULL) {
|
|
vps1.next = *vpp;
|
|
*vpp = &vps1;
|
|
vps1.flags = VSTRFIXED;
|
|
vps1.text = NULL;
|
|
choose_ps1();
|
|
}
|
|
}
|
|
|
|
void
|
|
choose_ps1(void)
|
|
{
|
|
uid_t u = geteuid();
|
|
|
|
if ((vps1.flags & (VTEXTFIXED|VSTACK)) == 0)
|
|
free(vps1.text);
|
|
vps1.text = strdup(u != 0 ? "PS1=$ " : "PS1=# ");
|
|
vps1.flags &= ~(VTEXTFIXED|VSTACK);
|
|
|
|
/*
|
|
* Update PSc whenever we feel the need to update PS1
|
|
*/
|
|
setvarsafe("PSc", (u == 0 ? "#" : "$"), 0);
|
|
}
|
|
|
|
/*
|
|
* Validate a string as a valid variable name
|
|
* nb: not parameter - special params and such are "invalid" here.
|
|
* Name terminated by either \0 or the term param (usually '=' or '\0').
|
|
*
|
|
* If not NULL, the length of the (intended) name is returned via len
|
|
*/
|
|
|
|
int
|
|
validname(const char *name, int term, int *len)
|
|
{
|
|
const char *p = name;
|
|
int ok = 1;
|
|
|
|
if (p == NULL || *p == '\0' || *p == term) {
|
|
if (len != NULL)
|
|
*len = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (!is_name(*p))
|
|
ok = 0;
|
|
p++;
|
|
for (;;) {
|
|
if (*p == '\0' || *p == term)
|
|
break;
|
|
if (!is_in_name(*p))
|
|
ok = 0;
|
|
p++;
|
|
}
|
|
if (len != NULL)
|
|
*len = p - name;
|
|
|
|
return ok;
|
|
}
|
|
|
|
/*
|
|
* Safe version of setvar, returns 1 on success 0 on failure.
|
|
*/
|
|
|
|
int
|
|
setvarsafe(const char *name, const char *val, int flags)
|
|
{
|
|
struct jmploc jmploc;
|
|
struct jmploc * const savehandler = handler;
|
|
int volatile err = 0;
|
|
|
|
if (setjmp(jmploc.loc))
|
|
err = 1;
|
|
else {
|
|
handler = &jmploc;
|
|
setvar(name, val, flags);
|
|
}
|
|
handler = savehandler;
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Set the value of a variable. The flags argument is ored with the
|
|
* flags of the variable. If val is NULL, the variable is unset.
|
|
*
|
|
* This always copies name and val when setting a variable, so
|
|
* the source strings can be from anywhere, and are no longer needed
|
|
* after this function returns. The VTEXTFIXED and VSTACK flags should
|
|
* not be used (but just in case they were, clear them.)
|
|
*/
|
|
|
|
void
|
|
setvar(const char *name, const char *val, int flags)
|
|
{
|
|
const char *p;
|
|
const char *q;
|
|
char *d;
|
|
int len;
|
|
int namelen;
|
|
char *nameeq;
|
|
|
|
p = name;
|
|
|
|
if (!validname(p, '=', &namelen))
|
|
error("%.*s: bad variable name", namelen, name);
|
|
len = namelen + 2; /* 2 is space for '=' and '\0' */
|
|
if (val == NULL) {
|
|
flags |= VUNSET;
|
|
} else {
|
|
len += strlen(val);
|
|
}
|
|
d = nameeq = ckmalloc(len);
|
|
q = name;
|
|
while (--namelen >= 0)
|
|
*d++ = *q++;
|
|
*d++ = '=';
|
|
*d = '\0';
|
|
if (val)
|
|
scopy(val, d);
|
|
setvareq(nameeq, flags & ~(VTEXTFIXED | VSTACK));
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Same as setvar except that the variable and value are passed in
|
|
* the first argument as name=value. Since the first argument will
|
|
* be actually stored in the table, it should not be a string that
|
|
* will go away. The flags (VTEXTFIXED or VSTACK) can be used to
|
|
* indicate the source of the string (if neither is set, the string will
|
|
* eventually be free()d when a replacement value is assigned.)
|
|
*/
|
|
|
|
void
|
|
setvareq(char *s, int flags)
|
|
{
|
|
struct var *vp, **vpp;
|
|
int nlen;
|
|
|
|
VTRACE(DBG_VARS, ("setvareq([%s],%#x) aflag=%d ", s, flags, aflag));
|
|
if (aflag && !(flags & VNOEXPORT))
|
|
flags |= VEXPORT;
|
|
vp = find_var(s, &vpp, &nlen);
|
|
if (vp != NULL) {
|
|
VTRACE(DBG_VARS, ("was [%s] fl:%#x\n", vp->text,
|
|
vp->flags));
|
|
if (vp->flags & VREADONLY) {
|
|
if ((flags & (VTEXTFIXED|VSTACK)) == 0)
|
|
ckfree(s);
|
|
if (flags & VNOERROR)
|
|
return;
|
|
error("%.*s: is read only", vp->name_len, vp->text);
|
|
}
|
|
if (flags & VNOSET) {
|
|
if ((flags & (VTEXTFIXED|VSTACK)) == 0)
|
|
ckfree(s);
|
|
return;
|
|
}
|
|
|
|
INTOFF;
|
|
|
|
if (vp->func && !(vp->flags & VFUNCREF) && !(flags & VNOFUNC))
|
|
(*vp->func)(s + vp->name_len + 1);
|
|
|
|
if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
|
|
ckfree(vp->text);
|
|
|
|
/*
|
|
* if we set a magic var, the magic dissipates,
|
|
* unless it is very special indeed.
|
|
*/
|
|
if (vp->rfunc && (vp->flags & (VFUNCREF|VSPECIAL)) == VFUNCREF)
|
|
vp->rfunc = NULL;
|
|
|
|
vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET);
|
|
if (flags & VNOEXPORT)
|
|
vp->flags &= ~VEXPORT;
|
|
if (flags & VDOEXPORT)
|
|
vp->flags &= ~VNOEXPORT;
|
|
if (vp->flags & VNOEXPORT)
|
|
flags &= ~VEXPORT;
|
|
vp->flags |= flags & ~(VNOFUNC | VDOEXPORT);
|
|
vp->text = s;
|
|
|
|
/*
|
|
* We could roll this to a function, to handle it as
|
|
* a regular variable function callback, but why bother?
|
|
*/
|
|
if (vp == &vmpath || (vp == &vmail && ! mpathset()))
|
|
chkmail(1);
|
|
|
|
INTON;
|
|
return;
|
|
}
|
|
/* not found */
|
|
if (flags & VNOSET) {
|
|
VTRACE(DBG_VARS, ("new noset\n"));
|
|
if ((flags & (VTEXTFIXED|VSTACK)) == 0)
|
|
ckfree(s);
|
|
return;
|
|
}
|
|
vp = ckmalloc(sizeof (*vp));
|
|
vp->flags = flags & ~(VNOFUNC|VFUNCREF|VDOEXPORT);
|
|
vp->text = s;
|
|
vp->name_len = nlen;
|
|
vp->func = NULL;
|
|
vp->next = *vpp;
|
|
*vpp = vp;
|
|
|
|
VTRACE(DBG_VARS, ("new [%s] (%d) %#x\n", s, nlen, vp->flags));
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Process a linked list of variable assignments.
|
|
*/
|
|
|
|
void
|
|
listsetvar(struct strlist *list, int flags)
|
|
{
|
|
struct strlist *lp;
|
|
|
|
INTOFF;
|
|
for (lp = list ; lp ; lp = lp->next) {
|
|
setvareq(savestr(lp->text), flags);
|
|
}
|
|
INTON;
|
|
}
|
|
|
|
void
|
|
listmklocal(struct strlist *list, int flags)
|
|
{
|
|
struct strlist *lp;
|
|
|
|
for (lp = list ; lp ; lp = lp->next)
|
|
mklocal(lp->text, flags);
|
|
}
|
|
|
|
|
|
/*
|
|
* Find the value of a variable. Returns NULL if not set.
|
|
*/
|
|
|
|
char *
|
|
lookupvar(const char *name)
|
|
{
|
|
struct var *v;
|
|
char *p;
|
|
|
|
v = find_var(name, NULL, NULL);
|
|
if (v == NULL || v->flags & VUNSET)
|
|
return NULL;
|
|
if (v->rfunc && (v->flags & VFUNCREF) != 0) {
|
|
p = (*v->rfunc)(v);
|
|
if (p == NULL)
|
|
return NULL;
|
|
} else
|
|
p = v->text;
|
|
|
|
return p + v->name_len + 1;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Search the environment of a builtin command. If the second argument
|
|
* is nonzero, return the value of a variable even if it hasn't been
|
|
* exported.
|
|
*/
|
|
|
|
char *
|
|
bltinlookup(const char *name, int doall)
|
|
{
|
|
struct strlist *sp;
|
|
struct var *v;
|
|
char *p;
|
|
|
|
for (sp = cmdenviron ; sp ; sp = sp->next) {
|
|
if (strequal(sp->text, name))
|
|
return strchr(sp->text, '=') + 1;
|
|
}
|
|
|
|
v = find_var(name, NULL, NULL);
|
|
|
|
if (v == NULL || v->flags & VUNSET || (!doall && !(v->flags & VEXPORT)))
|
|
return NULL;
|
|
|
|
if (v->rfunc && (v->flags & VFUNCREF) != 0) {
|
|
p = (*v->rfunc)(v);
|
|
if (p == NULL)
|
|
return NULL;
|
|
} else
|
|
p = v->text;
|
|
|
|
return p + v->name_len + 1;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Generate a list of exported variables. This routine is used to construct
|
|
* the third argument to execve when executing a program.
|
|
*/
|
|
|
|
char **
|
|
environment(void)
|
|
{
|
|
int nenv;
|
|
struct var **vpp;
|
|
struct var *vp;
|
|
char **env;
|
|
char **ep;
|
|
|
|
nenv = 0;
|
|
for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
|
|
for (vp = *vpp ; vp ; vp = vp->next)
|
|
if ((vp->flags & (VEXPORT|VUNSET)) == VEXPORT)
|
|
nenv++;
|
|
}
|
|
CTRACE(DBG_VARS, ("environment: %d vars to export\n", nenv));
|
|
ep = env = stalloc((nenv + 1) * sizeof *env);
|
|
for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
|
|
for (vp = *vpp ; vp ; vp = vp->next)
|
|
if ((vp->flags & (VEXPORT|VUNSET)) == VEXPORT) {
|
|
if (vp->rfunc && (vp->flags & VFUNCREF)) {
|
|
*ep = (*vp->rfunc)(vp);
|
|
if (*ep != NULL)
|
|
ep++;
|
|
} else
|
|
*ep++ = vp->text;
|
|
VTRACE(DBG_VARS, ("environment: %s\n", ep[-1]));
|
|
}
|
|
}
|
|
*ep = NULL;
|
|
return env;
|
|
}
|
|
|
|
|
|
/*
|
|
* Called when a shell procedure is invoked to clear out nonexported
|
|
* variables. It is also necessary to reallocate variables of with
|
|
* VSTACK set since these are currently allocated on the stack.
|
|
*/
|
|
|
|
#ifdef mkinit
|
|
void shprocvar(void);
|
|
|
|
SHELLPROC {
|
|
shprocvar();
|
|
}
|
|
#endif
|
|
|
|
void
|
|
shprocvar(void)
|
|
{
|
|
struct var **vpp;
|
|
struct var *vp, **prev;
|
|
|
|
for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
|
|
for (prev = vpp ; (vp = *prev) != NULL ; ) {
|
|
if ((vp->flags & VEXPORT) == 0) {
|
|
*prev = vp->next;
|
|
if ((vp->flags & VTEXTFIXED) == 0)
|
|
ckfree(vp->text);
|
|
if ((vp->flags & VSTRFIXED) == 0)
|
|
ckfree(vp);
|
|
} else {
|
|
if (vp->flags & VSTACK) {
|
|
vp->text = savestr(vp->text);
|
|
vp->flags &=~ VSTACK;
|
|
}
|
|
prev = &vp->next;
|
|
}
|
|
}
|
|
}
|
|
initvar();
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Command to list all variables which are set. Currently this command
|
|
* is invoked from the set command when the set command is called without
|
|
* any variables.
|
|
*/
|
|
|
|
void
|
|
print_quoted(const char *p)
|
|
{
|
|
const char *q;
|
|
|
|
if (p[0] == '\0') {
|
|
out1fmt("''");
|
|
return;
|
|
}
|
|
if (strcspn(p, "|&;<>()$`\\\"' \t\n*?[]#~=%") == strlen(p)) {
|
|
out1fmt("%s", p);
|
|
return;
|
|
}
|
|
while (*p) {
|
|
if (*p == '\'') {
|
|
out1fmt("\\'");
|
|
p++;
|
|
continue;
|
|
}
|
|
q = strchr(p, '\'');
|
|
if (!q) {
|
|
out1fmt("'%s'", p );
|
|
return;
|
|
}
|
|
out1fmt("'%.*s'", (int)(q - p), p );
|
|
p = q;
|
|
}
|
|
}
|
|
|
|
static int
|
|
sort_var(const void *v_v1, const void *v_v2)
|
|
{
|
|
const struct var * const *v1 = v_v1;
|
|
const struct var * const *v2 = v_v2;
|
|
char *t1 = (*v1)->text, *t2 = (*v2)->text;
|
|
|
|
if (*t1 == *t2) {
|
|
char *p, *s;
|
|
|
|
STARTSTACKSTR(p);
|
|
|
|
/*
|
|
* note: if lengths are equal, strings must be different
|
|
* so we don't care which string we pick for the \0 in
|
|
* that case.
|
|
*/
|
|
if ((strchr(t1, '=') - t1) <= (strchr(t2, '=') - t2)) {
|
|
s = t1;
|
|
t1 = p;
|
|
} else {
|
|
s = t2;
|
|
t2 = p;
|
|
}
|
|
|
|
while (*s && *s != '=') {
|
|
STPUTC(*s, p);
|
|
s++;
|
|
}
|
|
STPUTC('\0', p);
|
|
}
|
|
|
|
return strcoll(t1, t2);
|
|
}
|
|
|
|
/*
|
|
* POSIX requires that 'set' (but not export or readonly) output the
|
|
* variables in lexicographic order - by the locale's collating order (sigh).
|
|
* Maybe we could keep them in an ordered balanced binary tree
|
|
* instead of hashed lists.
|
|
* For now just roll 'em through qsort for printing...
|
|
*/
|
|
|
|
STATIC void
|
|
showvar(struct var *vp, const char *cmd, const char *xtra, int show_value)
|
|
{
|
|
const char *p;
|
|
|
|
p = vp->text;
|
|
if (vp->rfunc && (vp->flags & VFUNCREF) != 0) {
|
|
p = (*vp->rfunc)(vp);
|
|
if (p == NULL) {
|
|
if (!(show_value & 2))
|
|
return;
|
|
p = vp->text;
|
|
show_value = 0;
|
|
}
|
|
}
|
|
if (cmd)
|
|
out1fmt("%s ", cmd);
|
|
if (xtra)
|
|
out1fmt("%s ", xtra);
|
|
for ( ; *p != '=' ; p++)
|
|
out1c(*p);
|
|
if (!(vp->flags & VUNSET) && show_value) {
|
|
out1fmt("=");
|
|
print_quoted(++p);
|
|
}
|
|
out1c('\n');
|
|
}
|
|
|
|
int
|
|
showvars(const char *cmd, int flag, int show_value, const char *xtra)
|
|
{
|
|
struct var **vpp;
|
|
struct var *vp;
|
|
|
|
static struct var **list; /* static in case we are interrupted */
|
|
static int list_len;
|
|
int count = 0;
|
|
|
|
if (!list) {
|
|
list_len = 32;
|
|
list = ckmalloc(list_len * sizeof *list);
|
|
}
|
|
|
|
for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
|
|
for (vp = *vpp ; vp ; vp = vp->next) {
|
|
if (flag && !(vp->flags & flag))
|
|
continue;
|
|
if (vp->flags & VUNSET && !(show_value & 2))
|
|
continue;
|
|
if (count >= list_len) {
|
|
list = ckrealloc(list,
|
|
(list_len << 1) * sizeof *list);
|
|
list_len <<= 1;
|
|
}
|
|
list[count++] = vp;
|
|
}
|
|
}
|
|
|
|
qsort(list, count, sizeof *list, sort_var);
|
|
|
|
for (vpp = list; count--; vpp++)
|
|
showvar(*vpp, cmd, xtra, show_value);
|
|
|
|
/* no free(list), will be used again next time ... */
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* The export and readonly commands.
|
|
*/
|
|
|
|
static void __dead
|
|
export_usage(const char *cmd)
|
|
{
|
|
#ifdef SMALL
|
|
if (*cmd == 'r')
|
|
error("Usage: %s [ -p | var[=val]... ]", cmd);
|
|
else
|
|
error("Usage: %s [ -p | [-n] var[=val]... ]", cmd);
|
|
#else
|
|
if (*cmd == 'r')
|
|
error("Usage: %s [-p [var...] | -q var... | var[=val]... ]", cmd);
|
|
else
|
|
error(
|
|
"Usage: %s [ -px [var...] | -q[x] var... | [-n|x] var[=val]... ]",
|
|
cmd);
|
|
#endif
|
|
}
|
|
|
|
int
|
|
exportcmd(int argc, char **argv)
|
|
{
|
|
struct var *vp;
|
|
char *name;
|
|
const char *p = argv[0];
|
|
int flag = p[0] == 'r'? VREADONLY : VEXPORT;
|
|
int pflg = 0;
|
|
int nflg = 0;
|
|
#ifndef SMALL
|
|
int xflg = 0;
|
|
int qflg = 0;
|
|
#endif
|
|
int res;
|
|
int c;
|
|
int f;
|
|
|
|
#ifdef SMALL
|
|
#define EXPORT_OPTS "np"
|
|
#else
|
|
#define EXPORT_OPTS "npqx"
|
|
#endif
|
|
|
|
while ((c = nextopt(EXPORT_OPTS)) != '\0') {
|
|
|
|
#undef EXPORT_OPTS
|
|
|
|
switch (c) {
|
|
case 'n':
|
|
if (pflg || flag == VREADONLY
|
|
#ifndef SMALL
|
|
|| qflg || xflg
|
|
#endif
|
|
)
|
|
export_usage(p);
|
|
nflg = 1;
|
|
break;
|
|
case 'p':
|
|
if (nflg
|
|
#ifndef SMALL
|
|
|| qflg
|
|
#endif
|
|
)
|
|
export_usage(p);
|
|
pflg = 3;
|
|
break;
|
|
#ifndef SMALL
|
|
case 'q':
|
|
if (nflg || pflg)
|
|
export_usage(p);
|
|
qflg = 1;
|
|
break;
|
|
case 'x':
|
|
if (nflg || flag == VREADONLY)
|
|
export_usage(p);
|
|
flag = VNOEXPORT;
|
|
xflg = 1;
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if ((nflg
|
|
#ifndef SMALL
|
|
|| qflg
|
|
#endif
|
|
) && *argptr == NULL)
|
|
export_usage(p);
|
|
|
|
#ifndef SMALL
|
|
if (pflg && *argptr != NULL) {
|
|
while ((name = *argptr++) != NULL) {
|
|
int len;
|
|
|
|
vp = find_var(name, NULL, &len);
|
|
if (name[len] == '=')
|
|
export_usage(p);
|
|
if (!goodname(name))
|
|
error("%s: bad variable name", name);
|
|
|
|
if (vp && vp->flags & flag)
|
|
showvar(vp, p, xflg ? "-x" : NULL, 1);
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
if (pflg || *argptr == NULL)
|
|
return showvars( pflg ? p : 0, flag, pflg,
|
|
#ifndef SMALL
|
|
pflg && xflg ? "-x" :
|
|
#endif
|
|
NULL );
|
|
|
|
res = 0;
|
|
#ifndef SMALL
|
|
if (qflg) {
|
|
while ((name = *argptr++) != NULL) {
|
|
int len;
|
|
|
|
vp = find_var(name, NULL, &len);
|
|
if (name[len] == '=')
|
|
export_usage(p);
|
|
if (!goodname(name))
|
|
error("%s: bad variable name", name);
|
|
|
|
if (vp == NULL || !(vp->flags & flag))
|
|
res = 1;
|
|
}
|
|
return res;
|
|
}
|
|
#endif
|
|
|
|
while ((name = *argptr++) != NULL) {
|
|
int len;
|
|
|
|
f = flag;
|
|
|
|
vp = find_var(name, NULL, &len);
|
|
p = name + len;
|
|
if (*p++ != '=')
|
|
p = NULL;
|
|
|
|
if (vp != NULL) {
|
|
if (nflg)
|
|
vp->flags &= ~flag;
|
|
else if (flag&VEXPORT && vp->flags&VNOEXPORT) {
|
|
/* note we go ahead and do any assignment */
|
|
sh_warnx("%.*s: not available for export",
|
|
len, name);
|
|
res = 1;
|
|
} else {
|
|
if (flag == VNOEXPORT)
|
|
vp->flags &= ~VEXPORT;
|
|
|
|
/* if not NULL will be done in setvar below */
|
|
if (p == NULL)
|
|
vp->flags |= flag;
|
|
}
|
|
if (p == NULL)
|
|
continue;
|
|
} else if (nflg && p == NULL && !goodname(name))
|
|
error("%s: bad variable name", name);
|
|
|
|
if (!nflg || p != NULL)
|
|
setvar(name, p, f);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
/*
|
|
* The "local" command.
|
|
*/
|
|
|
|
int
|
|
localcmd(int argc, char **argv)
|
|
{
|
|
char *name;
|
|
int c;
|
|
int flags = 0; /*XXX perhaps VUNSET from a -o option value */
|
|
|
|
if (! in_function())
|
|
error("Not in a function");
|
|
|
|
/* upper case options, as bash stole all the good ones ... */
|
|
while ((c = nextopt("INx")) != '\0')
|
|
switch (c) {
|
|
case 'I': flags &= ~VUNSET; break;
|
|
case 'N': flags |= VUNSET; break;
|
|
case 'x': flags |= VEXPORT; break;
|
|
}
|
|
|
|
while ((name = *argptr++) != NULL) {
|
|
mklocal(name, flags);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Make a variable a local variable. When a variable is made local, its
|
|
* value and flags are saved in a localvar structure. The saved values
|
|
* will be restored when the shell function returns. We handle the name
|
|
* "-" as a special case.
|
|
*/
|
|
|
|
void
|
|
mklocal(const char *name, int flags)
|
|
{
|
|
struct localvar *lvp;
|
|
struct var **vpp;
|
|
struct var *vp;
|
|
|
|
INTOFF;
|
|
lvp = ckmalloc(sizeof (struct localvar));
|
|
if (name[0] == '-' && name[1] == '\0') {
|
|
char *p;
|
|
p = ckmalloc(sizeof_optlist);
|
|
lvp->text = memcpy(p, optlist, sizeof_optlist);
|
|
lvp->rfunc = NULL;
|
|
vp = NULL;
|
|
xtrace_clone(0);
|
|
} else {
|
|
vp = find_var(name, &vpp, NULL);
|
|
if (vp == NULL) {
|
|
flags &= ~VNOEXPORT;
|
|
if (strchr(name, '='))
|
|
setvareq(savestr(name),
|
|
VSTRFIXED | (flags & ~VUNSET));
|
|
else
|
|
setvar(name, NULL, VSTRFIXED|flags);
|
|
vp = *vpp; /* the new variable */
|
|
lvp->text = NULL;
|
|
lvp->flags = VUNSET;
|
|
lvp->rfunc = NULL;
|
|
} else {
|
|
lvp->text = vp->text;
|
|
lvp->flags = vp->flags;
|
|
lvp->v_u = vp->v_u;
|
|
vp->flags |= VSTRFIXED|VTEXTFIXED;
|
|
if (flags & (VDOEXPORT | VUNSET))
|
|
vp->flags &= ~VNOEXPORT;
|
|
if (vp->flags & VNOEXPORT &&
|
|
(flags & (VEXPORT|VDOEXPORT|VUNSET)) == VEXPORT)
|
|
flags &= ~VEXPORT;
|
|
if (flags & (VNOEXPORT | VUNSET))
|
|
vp->flags &= ~VEXPORT;
|
|
flags &= ~VNOEXPORT;
|
|
if (name[vp->name_len] == '=')
|
|
setvareq(savestr(name), flags & ~VUNSET);
|
|
else if (flags & VUNSET)
|
|
unsetvar(name, 0);
|
|
else
|
|
vp->flags |= flags & (VUNSET|VEXPORT);
|
|
|
|
if (vp == &line_num) {
|
|
if (name[vp->name_len] == '=')
|
|
funclinebase = funclineabs -1;
|
|
else
|
|
funclinebase = 0;
|
|
}
|
|
}
|
|
}
|
|
lvp->vp = vp;
|
|
lvp->next = localvars;
|
|
localvars = lvp;
|
|
INTON;
|
|
}
|
|
|
|
|
|
/*
|
|
* Called after a function returns.
|
|
*/
|
|
|
|
void
|
|
poplocalvars(void)
|
|
{
|
|
struct localvar *lvp;
|
|
struct var *vp;
|
|
|
|
while ((lvp = localvars) != NULL) {
|
|
localvars = lvp->next;
|
|
vp = lvp->vp;
|
|
VTRACE(DBG_VARS, ("poplocalvar %s\n", vp ? vp->text : "-"));
|
|
if (vp == NULL) { /* $- saved */
|
|
memcpy(optlist, lvp->text, sizeof_optlist);
|
|
ckfree(lvp->text);
|
|
xtrace_pop();
|
|
optschanged();
|
|
} else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
|
|
(void)unsetvar(vp->text, 0);
|
|
} else {
|
|
if (lvp->func && (lvp->flags & (VNOFUNC|VFUNCREF)) == 0)
|
|
(*lvp->func)(lvp->text + vp->name_len + 1);
|
|
if ((vp->flags & VTEXTFIXED) == 0)
|
|
ckfree(vp->text);
|
|
vp->flags = lvp->flags;
|
|
vp->text = lvp->text;
|
|
vp->v_u = lvp->v_u;
|
|
}
|
|
ckfree(lvp);
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
setvarcmd(int argc, char **argv)
|
|
{
|
|
if (argc <= 2)
|
|
return unsetcmd(argc, argv);
|
|
else if (argc == 3)
|
|
setvar(argv[1], argv[2], 0);
|
|
else
|
|
error("List assignment not implemented");
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* The unset builtin command. We unset the function before we unset the
|
|
* variable to allow a function to be unset when there is a readonly variable
|
|
* with the same name.
|
|
*/
|
|
|
|
int
|
|
unsetcmd(int argc, char **argv)
|
|
{
|
|
char **ap;
|
|
int i;
|
|
int flg_func = 0;
|
|
int flg_var = 0;
|
|
int flg_x = 0;
|
|
int ret = 0;
|
|
|
|
while ((i = nextopt("efvx")) != '\0') {
|
|
switch (i) {
|
|
case 'f':
|
|
flg_func = 1;
|
|
break;
|
|
case 'e':
|
|
case 'x':
|
|
flg_x = (2 >> (i == 'e'));
|
|
/* FALLTHROUGH */
|
|
case 'v':
|
|
flg_var = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (flg_func == 0 && flg_var == 0)
|
|
flg_var = 1;
|
|
|
|
for (ap = argptr; *ap ; ap++) {
|
|
if (flg_func)
|
|
ret |= unsetfunc(*ap);
|
|
if (flg_var)
|
|
ret |= unsetvar(*ap, flg_x);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Unset the specified variable.
|
|
*/
|
|
|
|
int
|
|
unsetvar(const char *s, int unexport)
|
|
{
|
|
struct var **vpp;
|
|
struct var *vp;
|
|
|
|
vp = find_var(s, &vpp, NULL);
|
|
if (vp == NULL)
|
|
return 0;
|
|
|
|
if (vp->flags & VREADONLY && !(unexport & 1))
|
|
return 1;
|
|
|
|
INTOFF;
|
|
if (unexport & 1) {
|
|
vp->flags &= ~VEXPORT;
|
|
} else {
|
|
if (vp->text[vp->name_len + 1] != '\0')
|
|
setvar(s, nullstr, 0);
|
|
if (!(unexport & 2))
|
|
vp->flags &= ~VEXPORT;
|
|
vp->flags |= VUNSET;
|
|
if ((vp->flags&(VEXPORT|VSTRFIXED|VREADONLY|VNOEXPORT)) == 0) {
|
|
if ((vp->flags & VTEXTFIXED) == 0)
|
|
ckfree(vp->text);
|
|
*vpp = vp->next;
|
|
ckfree(vp);
|
|
}
|
|
}
|
|
INTON;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns true if the two strings specify the same varable. The first
|
|
* variable name is terminated by '='; the second may be terminated by
|
|
* either '=' or '\0'.
|
|
*/
|
|
|
|
STATIC int
|
|
strequal(const char *p, const char *q)
|
|
{
|
|
while (*p == *q++) {
|
|
if (*p++ == '=')
|
|
return 1;
|
|
}
|
|
if (*p == '=' && *(q - 1) == '\0')
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Search for a variable.
|
|
* 'name' may be terminated by '=' or a NUL.
|
|
* vppp is set to the pointer to vp, or the list head if vp isn't found
|
|
* lenp is set to the number of characters in 'name'
|
|
*/
|
|
|
|
STATIC struct var *
|
|
find_var(const char *name, struct var ***vppp, int *lenp)
|
|
{
|
|
unsigned int hashval;
|
|
int len;
|
|
struct var *vp, **vpp;
|
|
const char *p = name;
|
|
|
|
hashval = 0;
|
|
while (*p && *p != '=')
|
|
hashval = 2 * hashval + (unsigned char)*p++;
|
|
|
|
len = p - name;
|
|
if (lenp)
|
|
*lenp = len;
|
|
|
|
vpp = &vartab[hashval % VTABSIZE];
|
|
if (vppp)
|
|
*vppp = vpp;
|
|
|
|
for (vp = *vpp ; vp ; vpp = &vp->next, vp = *vpp) {
|
|
if (vp->name_len != len)
|
|
continue;
|
|
if (memcmp(vp->text, name, len) != 0)
|
|
continue;
|
|
if (vppp)
|
|
*vppp = vpp;
|
|
return vp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* The following are the functions that create the values for
|
|
* shell variables that are dynamically produced when needed.
|
|
*
|
|
* The output strings cannot be malloc'd as there is nothing to
|
|
* free them - callers assume these are ordinary variables where
|
|
* the value returned is vp->text
|
|
*
|
|
* Each function needs its own storage space, as the results are
|
|
* used to create processes' environment, and (if exported) all
|
|
* the values will (might) be needed simultaneously.
|
|
*
|
|
* It is not a problem if a var is updated while nominally in use
|
|
* somewhere, all these are intended to be dynamic, the value they
|
|
* return is not guaranteed, an updated vaue is just as good.
|
|
*
|
|
* So, malloc a single buffer for the result of each function,
|
|
* grow, and even shrink, it as needed, but once we have one that
|
|
* is a suitable size for the actual usage, simply hold it forever.
|
|
*
|
|
* For a SMALL shell we implement only LINENO, none of the others,
|
|
* and give it just a fixed length static buffer for its result.
|
|
*/
|
|
|
|
#ifndef SMALL
|
|
|
|
struct space_reserved { /* record of space allocated for results */
|
|
char *b;
|
|
int len;
|
|
};
|
|
|
|
/* rough (over-)estimate of the number of bytes needed to hold a number */
|
|
static int
|
|
digits_in(intmax_t number)
|
|
{
|
|
int res = 0;
|
|
|
|
if (number & ~((1LL << 62) - 1))
|
|
res = 64; /* enough for 2^200 and a bit more */
|
|
else if (number & ~((1LL << 32) - 1))
|
|
res = 20; /* enough for 2^64 */
|
|
else if (number & ~((1 << 23) - 1))
|
|
res = 10; /* enough for 2^32 */
|
|
else
|
|
res = 8; /* enough for 2^23 or smaller */
|
|
|
|
return res;
|
|
}
|
|
|
|
static int
|
|
make_space(struct space_reserved *m, int bytes)
|
|
{
|
|
void *p;
|
|
|
|
if (m->len >= bytes && m->len <= (bytes<<2))
|
|
return 1;
|
|
|
|
bytes = SHELL_ALIGN(bytes);
|
|
INTOFF;
|
|
/* not ckrealloc() - we want failure, not error() here */
|
|
p = realloc(m->b, bytes);
|
|
if (p != NULL) {
|
|
m->b = p;
|
|
m->len = bytes;
|
|
m->b[bytes - 1] = '\0';
|
|
}
|
|
INTON;
|
|
|
|
return p != NULL;
|
|
}
|
|
#endif
|
|
|
|
char *
|
|
get_lineno(struct var *vp)
|
|
{
|
|
#ifdef SMALL
|
|
#define length (8 + 10) /* 10 digits is enough for a 32 bit line num */
|
|
static char result[length];
|
|
#else
|
|
static struct space_reserved buf;
|
|
#define result buf.b
|
|
#define length buf.len
|
|
#endif
|
|
int ln = line_number;
|
|
|
|
if (vp->flags & VUNSET)
|
|
return NULL;
|
|
|
|
ln -= funclinebase;
|
|
|
|
#ifndef SMALL
|
|
if (!make_space(&buf, vp->name_len + 2 + digits_in(ln)))
|
|
return vp->text;
|
|
#endif
|
|
|
|
snprintf(result, length, "%.*s=%d", vp->name_len, vp->text, ln);
|
|
return result;
|
|
}
|
|
#undef result
|
|
#undef length
|
|
|
|
#ifndef SMALL
|
|
|
|
char *
|
|
get_hostname(struct var *vp)
|
|
{
|
|
static struct space_reserved buf;
|
|
|
|
if (vp->flags & VUNSET)
|
|
return NULL;
|
|
|
|
if (!make_space(&buf, vp->name_len + 2 + 256))
|
|
return vp->text;
|
|
|
|
memcpy(buf.b, vp->text, vp->name_len + 1); /* include '=' */
|
|
(void)gethostname(buf.b + vp->name_len + 1,
|
|
buf.len - vp->name_len - 3);
|
|
return buf.b;
|
|
}
|
|
|
|
char *
|
|
get_tod(struct var *vp)
|
|
{
|
|
static struct space_reserved buf; /* space for answers */
|
|
static struct space_reserved tzs; /* remember TZ last used */
|
|
static timezone_t last_zone; /* timezone data for tzs zone */
|
|
const char *fmt;
|
|
char *tz;
|
|
time_t now;
|
|
struct tm tm_now, *tmp;
|
|
timezone_t zone = NULL;
|
|
static char t_err[] = "time error";
|
|
int len;
|
|
|
|
if (vp->flags & VUNSET)
|
|
return NULL;
|
|
|
|
fmt = lookupvar("ToD_FORMAT");
|
|
if (fmt == NULL)
|
|
fmt="%T";
|
|
tz = lookupvar("TZ");
|
|
(void)time(&now);
|
|
|
|
if (tz != NULL) {
|
|
if (tzs.b == NULL || strcmp(tzs.b, tz) != 0) {
|
|
INTOFF;
|
|
if (make_space(&tzs, strlen(tz) + 1)) {
|
|
strcpy(tzs.b, tz);
|
|
if (last_zone)
|
|
tzfree(last_zone);
|
|
last_zone = zone = tzalloc(tz);
|
|
INTON;
|
|
} else
|
|
zone = tzalloc(tz);
|
|
} else
|
|
zone = last_zone;
|
|
|
|
tmp = localtime_rz(zone, &now, &tm_now);
|
|
} else
|
|
tmp = localtime_r(&now, &tm_now);
|
|
|
|
len = (strlen(fmt) * 4) + vp->name_len + 2;
|
|
while (make_space(&buf, len)) {
|
|
memcpy(buf.b, vp->text, vp->name_len+1);
|
|
if (tmp == NULL) {
|
|
if (buf.len >= vp->name_len+2+(int)(sizeof t_err - 1)) {
|
|
strcpy(buf.b + vp->name_len + 1, t_err);
|
|
if (zone && zone != last_zone) {
|
|
tzfree(zone);
|
|
INTON;
|
|
}
|
|
return buf.b;
|
|
}
|
|
len = vp->name_len + 4 + sizeof t_err - 1;
|
|
continue;
|
|
}
|
|
if (strftime_z(zone, buf.b + vp->name_len + 1,
|
|
buf.len - vp->name_len - 2, fmt, tmp)) {
|
|
if (zone && zone != last_zone) {
|
|
tzfree(zone);
|
|
INTON;
|
|
}
|
|
return buf.b;
|
|
}
|
|
if (len >= 4096) /* Let's be reasonable */
|
|
break;
|
|
len <<= 1;
|
|
}
|
|
if (zone && zone != last_zone) {
|
|
tzfree(zone);
|
|
INTON;
|
|
}
|
|
return vp->text;
|
|
}
|
|
|
|
char *
|
|
get_seconds(struct var *vp)
|
|
{
|
|
static struct space_reserved buf;
|
|
intmax_t secs;
|
|
|
|
if (vp->flags & VUNSET)
|
|
return NULL;
|
|
|
|
secs = (intmax_t)time((time_t *)0) - sh_start_time;
|
|
if (!make_space(&buf, vp->name_len + 2 + digits_in(secs)))
|
|
return vp->text;
|
|
|
|
snprintf(buf.b, buf.len, "%.*s=%jd", vp->name_len, vp->text, secs);
|
|
return buf.b;
|
|
}
|
|
|
|
char *
|
|
get_euser(struct var *vp)
|
|
{
|
|
static struct space_reserved buf;
|
|
static uid_t lastuid = 0;
|
|
uid_t euid;
|
|
struct passwd *pw;
|
|
|
|
if (vp->flags & VUNSET)
|
|
return NULL;
|
|
|
|
euid = geteuid();
|
|
if (buf.b != NULL && lastuid == euid)
|
|
return buf.b;
|
|
|
|
pw = getpwuid(euid);
|
|
if (pw == NULL)
|
|
return vp->text;
|
|
|
|
if (make_space(&buf, vp->name_len + 2 + strlen(pw->pw_name))) {
|
|
INTOFF;
|
|
lastuid = euid;
|
|
snprintf(buf.b, buf.len, "%.*s=%s", vp->name_len, vp->text,
|
|
pw->pw_name);
|
|
INTON;
|
|
return buf.b;
|
|
}
|
|
|
|
return vp->text;
|
|
}
|
|
|
|
char *
|
|
get_random(struct var *vp)
|
|
{
|
|
static struct space_reserved buf;
|
|
static intmax_t random_val = 0;
|
|
|
|
#ifdef USE_LRAND48
|
|
#define random lrand48
|
|
#define srandom srand48
|
|
#endif
|
|
|
|
if (vp->flags & VUNSET)
|
|
return NULL;
|
|
|
|
if (vp->text != buf.b) {
|
|
/*
|
|
* Either initialisation, or a new seed has been set
|
|
*/
|
|
if (vp->text[vp->name_len + 1] == '\0') {
|
|
int fd;
|
|
|
|
/*
|
|
* initialisation (without pre-seeding),
|
|
* or explictly requesting a truly random seed.
|
|
*/
|
|
INTOFF;
|
|
fd = open("/dev/urandom", 0);
|
|
if (fd == -1) {
|
|
out2str("RANDOM initialisation failed\n");
|
|
random_val = (getpid()<<3) ^ time((time_t *)0);
|
|
} else {
|
|
int n;
|
|
|
|
do {
|
|
n = read(fd,&random_val,sizeof random_val);
|
|
} while (n != sizeof random_val);
|
|
close(fd);
|
|
}
|
|
INTON;
|
|
} else
|
|
/* good enough for today */
|
|
random_val = strtoimax(vp->text+vp->name_len+1,NULL,0);
|
|
|
|
srandom((long)random_val);
|
|
}
|
|
|
|
#if 0
|
|
random_val = (random_val + 1) & 0x7FFF; /* 15 bit "random" numbers */
|
|
#else
|
|
random_val = (random() >> 5) & 0x7FFF;
|
|
#endif
|
|
|
|
if (!make_space(&buf, vp->name_len + 2 + digits_in(random_val)))
|
|
return vp->text;
|
|
|
|
snprintf(buf.b, buf.len, "%.*s=%jd", vp->name_len, vp->text,
|
|
random_val);
|
|
|
|
INTOFF;
|
|
if (buf.b != vp->text && (vp->flags & (VTEXTFIXED|VSTACK)) == 0)
|
|
free(vp->text);
|
|
vp->flags |= VTEXTFIXED;
|
|
vp->text = buf.b;
|
|
INTON;
|
|
|
|
return vp->text;
|
|
#undef random
|
|
#undef srandom
|
|
}
|
|
|
|
STATIC int
|
|
makespecial(const char *name)
|
|
{
|
|
const struct varinit *ip;
|
|
struct var *vp;
|
|
|
|
CTRACE(DBG_VARS, ("makespecial('%s') -> ", name));
|
|
for (ip = varinit ; (vp = ip->var) != NULL ; ip++) {
|
|
if (strequal(ip->text, name)) {
|
|
if (!(ip->flags & VFUNCREF)) {
|
|
CTRACE(DBG_VARS, ("+1\n"));
|
|
return 1;
|
|
}
|
|
INTOFF;
|
|
vp->flags &= ~VUNSET;
|
|
vp->v_u = ip->v_u;
|
|
INTON;
|
|
CTRACE(DBG_VARS, ("0\n"));
|
|
return 0;
|
|
}
|
|
}
|
|
CTRACE(DBG_VARS, ("1\n"));
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
specialvarcmd(int argc, char **argv)
|
|
{
|
|
int res = 0;
|
|
char **ap;
|
|
|
|
(void) nextopt("");
|
|
|
|
if (!*argptr)
|
|
error("Usage: specialvar var...");
|
|
|
|
for (ap = argptr; *ap ; ap++)
|
|
res |= makespecial(*ap);
|
|
|
|
return res;
|
|
}
|
|
|
|
#endif /* SMALL */
|