diff options
| author | Craig Jennings <c@cjennings.net> | 2025-05-08 18:49:34 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-05-08 18:51:59 -0500 |
| commit | 000e00871830cd15de032c80e2b62946cf19445c (patch) | |
| tree | 794a7922750472bbe0e024042d6ba84f411fc3e0 /dotfiles/system/.zsh/modules/Src/params.c | |
| parent | fe302606931e4bad91c4ed6df81a4403523ba780 (diff) | |
adding missing dotfiles and folders
- profile.d/
- bashrc
- authinfo.gpg
- .zsh/
Diffstat (limited to 'dotfiles/system/.zsh/modules/Src/params.c')
| -rw-r--r-- | dotfiles/system/.zsh/modules/Src/params.c | 5884 |
1 files changed, 5884 insertions, 0 deletions
diff --git a/dotfiles/system/.zsh/modules/Src/params.c b/dotfiles/system/.zsh/modules/Src/params.c new file mode 100644 index 0000000..a1c299f --- /dev/null +++ b/dotfiles/system/.zsh/modules/Src/params.c @@ -0,0 +1,5884 @@ +/* + * params.c - parameters + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zsh.mdh" +#include "params.pro" + +#include "version.h" +#ifdef CUSTOM_PATCHLEVEL +#define ZSH_PATCHLEVEL CUSTOM_PATCHLEVEL +#else +#include "patchlevel.h" + +#include <math.h> + +/* If removed from the ChangeLog for some reason */ +#ifndef ZSH_PATCHLEVEL +#define ZSH_PATCHLEVEL "unknown" +#endif +#endif + +/* what level of localness we are at */ + +/**/ +mod_export int locallevel; + +/* Variables holding values of special parameters */ + +/**/ +mod_export +char **pparams, /* $argv */ + **cdpath, /* $cdpath */ + **fpath, /* $fpath */ + **mailpath, /* $mailpath */ + **manpath, /* $manpath */ + **psvar, /* $psvar */ + **watch, /* $watch */ + **zsh_eval_context; /* $zsh_eval_context */ +/**/ +mod_export +char **path, /* $path */ + **fignore; /* $fignore */ + +/**/ +mod_export +char *argzero, /* $0 */ + *posixzero, /* $0 */ + *home, /* $HOME */ + *nullcmd, /* $NULLCMD */ + *oldpwd, /* $OLDPWD */ + *zoptarg, /* $OPTARG */ + *prompt, /* $PROMPT */ + *prompt2, /* $PROMPT2 */ + *prompt3, /* $PROMPT3 */ + *prompt4, /* $PROMPT4 */ + *readnullcmd, /* $READNULLCMD */ + *rprompt, /* $RPROMPT */ + *rprompt2, /* $RPROMPT2 */ + *sprompt, /* $SPROMPT */ + *wordchars; /* $WORDCHARS */ +/**/ +mod_export +char *ifs, /* $IFS */ + *postedit, /* $POSTEDIT */ + *term, /* $TERM */ + *zsh_terminfo, /* $TERMINFO */ + *zsh_terminfodirs, /* $TERMINFO_DIRS */ + *ttystrname, /* $TTY */ + *pwd; /* $PWD */ + +/**/ +mod_export +zlong lastval, /* $? */ + mypid, /* $$ */ + lastpid, /* $! */ + zterm_columns, /* $COLUMNS */ + zterm_lines, /* $LINES */ + rprompt_indent, /* $ZLE_RPROMPT_INDENT */ + ppid, /* $PPID */ + zsh_subshell; /* $ZSH_SUBSHELL */ + +/* $FUNCNEST */ +/**/ +mod_export +zlong zsh_funcnest = +#ifdef MAX_FUNCTION_DEPTH + MAX_FUNCTION_DEPTH +#else + /* Disabled by default but can be enabled at run time */ + -1 +#endif + ; + +/**/ +zlong lineno, /* $LINENO */ + zoptind, /* $OPTIND */ + shlvl; /* $SHLVL */ + +/* $histchars */ + +/**/ +mod_export unsigned char bangchar; +/**/ +unsigned char hatchar, hashchar; + +/**/ +unsigned char keyboardhackchar = '\0'; + +/* $SECONDS = now.tv_sec - shtimer.tv_sec + * + (now.tv_usec - shtimer.tv_usec) / 1000000.0 + * (rounded to an integer if the parameter is not set to float) */ + +/**/ +struct timeval shtimer; + +/* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */ + +/**/ +mod_export int termflags; + +/* Forward declaration */ + +static void +rprompt_indent_unsetfn(Param pm, int exp); + +/* Standard methods for get/set/unset pointers in parameters */ + +/**/ +mod_export const struct gsu_scalar stdscalar_gsu = +{ strgetfn, strsetfn, stdunsetfn }; +/**/ +mod_export const struct gsu_scalar varscalar_gsu = +{ strvargetfn, strvarsetfn, stdunsetfn }; +/**/ +mod_export const struct gsu_scalar nullsetscalar_gsu = +{ strgetfn, nullstrsetfn, NULL }; + +/**/ +mod_export const struct gsu_integer stdinteger_gsu = +{ intgetfn, intsetfn, stdunsetfn }; +/**/ +mod_export const struct gsu_integer varinteger_gsu = +{ intvargetfn, intvarsetfn, stdunsetfn }; +/**/ +mod_export const struct gsu_integer nullsetinteger_gsu = +{ intgetfn, NULL, NULL }; + +/**/ +mod_export const struct gsu_float stdfloat_gsu = +{ floatgetfn, floatsetfn, stdunsetfn }; + +/**/ +mod_export const struct gsu_array stdarray_gsu = +{ arrgetfn, arrsetfn, stdunsetfn }; +/**/ +mod_export const struct gsu_array vararray_gsu = +{ arrvargetfn, arrvarsetfn, stdunsetfn }; + +/**/ +mod_export const struct gsu_hash stdhash_gsu = +{ hashgetfn, hashsetfn, stdunsetfn }; +/**/ +mod_export const struct gsu_hash nullsethash_gsu = +{ hashgetfn, nullsethashfn, nullunsetfn }; + + +/* Non standard methods (not exported) */ +static const struct gsu_integer pound_gsu = +{ poundgetfn, nullintsetfn, stdunsetfn }; +static const struct gsu_integer errno_gsu = +{ errnogetfn, errnosetfn, stdunsetfn }; +static const struct gsu_integer gid_gsu = +{ gidgetfn, gidsetfn, stdunsetfn }; +static const struct gsu_integer egid_gsu = +{ egidgetfn, egidsetfn, stdunsetfn }; +static const struct gsu_integer histsize_gsu = +{ histsizegetfn, histsizesetfn, stdunsetfn }; +static const struct gsu_integer random_gsu = +{ randomgetfn, randomsetfn, stdunsetfn }; +static const struct gsu_integer savehist_gsu = +{ savehistsizegetfn, savehistsizesetfn, stdunsetfn }; +static const struct gsu_integer intseconds_gsu = +{ intsecondsgetfn, intsecondssetfn, stdunsetfn }; +static const struct gsu_float floatseconds_gsu = +{ floatsecondsgetfn, floatsecondssetfn, stdunsetfn }; +static const struct gsu_integer uid_gsu = +{ uidgetfn, uidsetfn, stdunsetfn }; +static const struct gsu_integer euid_gsu = +{ euidgetfn, euidsetfn, stdunsetfn }; +static const struct gsu_integer ttyidle_gsu = +{ ttyidlegetfn, nullintsetfn, stdunsetfn }; + +static const struct gsu_scalar argzero_gsu = +{ argzerogetfn, argzerosetfn, nullunsetfn }; +static const struct gsu_scalar username_gsu = +{ usernamegetfn, usernamesetfn, stdunsetfn }; +static const struct gsu_scalar dash_gsu = +{ dashgetfn, nullstrsetfn, stdunsetfn }; +static const struct gsu_scalar histchars_gsu = +{ histcharsgetfn, histcharssetfn, stdunsetfn }; +static const struct gsu_scalar home_gsu = +{ homegetfn, homesetfn, stdunsetfn }; +static const struct gsu_scalar term_gsu = +{ termgetfn, termsetfn, stdunsetfn }; +static const struct gsu_scalar terminfo_gsu = +{ terminfogetfn, terminfosetfn, stdunsetfn }; +static const struct gsu_scalar terminfodirs_gsu = +{ terminfodirsgetfn, terminfodirssetfn, stdunsetfn }; +static const struct gsu_scalar wordchars_gsu = +{ wordcharsgetfn, wordcharssetfn, stdunsetfn }; +static const struct gsu_scalar ifs_gsu = +{ ifsgetfn, ifssetfn, stdunsetfn }; +static const struct gsu_scalar underscore_gsu = +{ underscoregetfn, nullstrsetfn, stdunsetfn }; +static const struct gsu_scalar keyboard_hack_gsu = +{ keyboardhackgetfn, keyboardhacksetfn, stdunsetfn }; +#ifdef USE_LOCALE +static const struct gsu_scalar lc_blah_gsu = +{ strgetfn, lcsetfn, stdunsetfn }; +static const struct gsu_scalar lang_gsu = +{ strgetfn, langsetfn, stdunsetfn }; +static const struct gsu_scalar lc_all_gsu = +{ strgetfn, lc_allsetfn, stdunsetfn }; +#endif + +static const struct gsu_integer varint_readonly_gsu = +{ intvargetfn, nullintsetfn, stdunsetfn }; +static const struct gsu_integer zlevar_gsu = +{ intvargetfn, zlevarsetfn, stdunsetfn }; + +static const struct gsu_scalar colonarr_gsu = +{ colonarrgetfn, colonarrsetfn, stdunsetfn }; + +static const struct gsu_integer argc_gsu = +{ poundgetfn, nullintsetfn, stdunsetfn }; +static const struct gsu_array pipestatus_gsu = +{ pipestatgetfn, pipestatsetfn, stdunsetfn }; + +static const struct gsu_integer rprompt_indent_gsu = +{ intvargetfn, zlevarsetfn, rprompt_indent_unsetfn }; + +/* Nodes for special parameters for parameter hash table */ + +#ifdef HAVE_UNION_INIT +# define BR(X) {X} +typedef struct param initparam; +#else +# define BR(X) X +typedef struct iparam { + struct hashnode *next; + char *nam; /* hash data */ + int flags; /* PM_* flags (defined in zsh.h) */ + void *value; + void *gsu; /* get/set/unset methods */ + int base; /* output base */ + int width; /* output field width */ + char *env; /* location in environment, if exported */ + char *ename; /* name of corresponding environment var */ + Param old; /* old struct for use with local */ + int level; /* if (old != NULL), level of localness */ +} initparam; +#endif + +static initparam special_params[] ={ +#define GSU(X) BR((GsuScalar)(void *)(&(X))) +#define NULL_GSU BR((GsuScalar)(void *)NULL) +#define IPDEF1(A,B,C) {{NULL,A,PM_INTEGER|PM_SPECIAL|C},BR(NULL),GSU(B),10,0,NULL,NULL,NULL,0} +IPDEF1("#", pound_gsu, PM_READONLY), +IPDEF1("ERRNO", errno_gsu, PM_UNSET), +IPDEF1("GID", gid_gsu, PM_DONTIMPORT | PM_RESTRICTED), +IPDEF1("EGID", egid_gsu, PM_DONTIMPORT | PM_RESTRICTED), +IPDEF1("HISTSIZE", histsize_gsu, PM_RESTRICTED), +IPDEF1("RANDOM", random_gsu, 0), +IPDEF1("SAVEHIST", savehist_gsu, PM_RESTRICTED), +IPDEF1("SECONDS", intseconds_gsu, 0), +IPDEF1("UID", uid_gsu, PM_DONTIMPORT | PM_RESTRICTED), +IPDEF1("EUID", euid_gsu, PM_DONTIMPORT | PM_RESTRICTED), +IPDEF1("TTYIDLE", ttyidle_gsu, PM_READONLY), + +#define IPDEF2(A,B,C) {{NULL,A,PM_SCALAR|PM_SPECIAL|C},BR(NULL),GSU(B),0,0,NULL,NULL,NULL,0} +IPDEF2("USERNAME", username_gsu, PM_DONTIMPORT|PM_RESTRICTED), +IPDEF2("-", dash_gsu, PM_READONLY), +IPDEF2("histchars", histchars_gsu, PM_DONTIMPORT), +IPDEF2("HOME", home_gsu, PM_UNSET), +IPDEF2("TERM", term_gsu, PM_UNSET), +IPDEF2("TERMINFO", terminfo_gsu, PM_UNSET), +IPDEF2("TERMINFO_DIRS", terminfodirs_gsu, PM_UNSET), +IPDEF2("WORDCHARS", wordchars_gsu, 0), +IPDEF2("IFS", ifs_gsu, PM_DONTIMPORT | PM_RESTRICTED), +IPDEF2("_", underscore_gsu, PM_DONTIMPORT), +IPDEF2("KEYBOARD_HACK", keyboard_hack_gsu, PM_DONTIMPORT), +IPDEF2("0", argzero_gsu, 0), + +#ifdef USE_LOCALE +# define LCIPDEF(name) IPDEF2(name, lc_blah_gsu, PM_UNSET) +IPDEF2("LANG", lang_gsu, PM_UNSET), +IPDEF2("LC_ALL", lc_all_gsu, PM_UNSET), +# ifdef LC_COLLATE +LCIPDEF("LC_COLLATE"), +# endif +# ifdef LC_CTYPE +LCIPDEF("LC_CTYPE"), +# endif +# ifdef LC_MESSAGES +LCIPDEF("LC_MESSAGES"), +# endif +# ifdef LC_NUMERIC +LCIPDEF("LC_NUMERIC"), +# endif +# ifdef LC_TIME +LCIPDEF("LC_TIME"), +# endif +#endif /* USE_LOCALE */ + +#define IPDEF4(A,B) {{NULL,A,PM_INTEGER|PM_READONLY|PM_SPECIAL},BR((void *)B),GSU(varint_readonly_gsu),10,0,NULL,NULL,NULL,0} +IPDEF4("!", &lastpid), +IPDEF4("$", &mypid), +IPDEF4("?", &lastval), +IPDEF4("HISTCMD", &curhist), +IPDEF4("LINENO", &lineno), +IPDEF4("PPID", &ppid), +IPDEF4("ZSH_SUBSHELL", &zsh_subshell), + +#define IPDEF5(A,B,F) {{NULL,A,PM_INTEGER|PM_SPECIAL},BR((void *)B),GSU(F),10,0,NULL,NULL,NULL,0} +#define IPDEF5U(A,B,F) {{NULL,A,PM_INTEGER|PM_SPECIAL|PM_UNSET},BR((void *)B),GSU(F),10,0,NULL,NULL,NULL,0} +IPDEF5("COLUMNS", &zterm_columns, zlevar_gsu), +IPDEF5("LINES", &zterm_lines, zlevar_gsu), +IPDEF5U("ZLE_RPROMPT_INDENT", &rprompt_indent, rprompt_indent_gsu), +IPDEF5("SHLVL", &shlvl, varinteger_gsu), +IPDEF5("FUNCNEST", &zsh_funcnest, varinteger_gsu), + +/* Don't import internal integer status variables. */ +#define IPDEF6(A,B,F) {{NULL,A,PM_INTEGER|PM_SPECIAL|PM_DONTIMPORT},BR((void *)B),GSU(F),10,0,NULL,NULL,NULL,0} +IPDEF6("OPTIND", &zoptind, varinteger_gsu), +IPDEF6("TRY_BLOCK_ERROR", &try_errflag, varinteger_gsu), +IPDEF6("TRY_BLOCK_INTERRUPT", &try_interrupt, varinteger_gsu), + +#define IPDEF7(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0} +#define IPDEF7R(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL|PM_DONTIMPORT_SUID},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0} +#define IPDEF7U(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL|PM_UNSET},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0} +IPDEF7("OPTARG", &zoptarg), +IPDEF7("NULLCMD", &nullcmd), +IPDEF7U("POSTEDIT", &postedit), +IPDEF7("READNULLCMD", &readnullcmd), +IPDEF7("PS1", &prompt), +IPDEF7U("RPS1", &rprompt), +IPDEF7U("RPROMPT", &rprompt), +IPDEF7("PS2", &prompt2), +IPDEF7U("RPS2", &rprompt2), +IPDEF7U("RPROMPT2", &rprompt2), +IPDEF7("PS3", &prompt3), +IPDEF7R("PS4", &prompt4), +IPDEF7("SPROMPT", &sprompt), + +#define IPDEF9F(A,B,C,D) {{NULL,A,D|PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT},BR((void *)B),GSU(vararray_gsu),0,0,NULL,C,NULL,0} +#define IPDEF9(A,B,C) IPDEF9F(A,B,C,0) +IPDEF9F("*", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY), +IPDEF9F("@", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY), + +/* + * This empty row indicates the end of parameters available in + * all emulations. + */ +{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0}, + +#define IPDEF8(A,B,C,D) {{NULL,A,D|PM_SCALAR|PM_SPECIAL},BR((void *)B),GSU(colonarr_gsu),0,0,NULL,C,NULL,0} +IPDEF8("CDPATH", &cdpath, "cdpath", 0), +IPDEF8("FIGNORE", &fignore, "fignore", 0), +IPDEF8("FPATH", &fpath, "fpath", 0), +IPDEF8("MAILPATH", &mailpath, "mailpath", 0), +IPDEF8("WATCH", &watch, "watch", 0), +IPDEF8("PATH", &path, "path", PM_RESTRICTED), +IPDEF8("PSVAR", &psvar, "psvar", 0), +IPDEF8("ZSH_EVAL_CONTEXT", &zsh_eval_context, "zsh_eval_context", PM_READONLY), + +/* MODULE_PATH is not imported for security reasons */ +IPDEF8("MODULE_PATH", &module_path, "module_path", PM_DONTIMPORT|PM_RESTRICTED), + +#define IPDEF10(A,B) {{NULL,A,PM_ARRAY|PM_SPECIAL},BR(NULL),GSU(B),10,0,NULL,NULL,NULL,0} + +/* + * The following parameters are not available in sh/ksh compatibility * + * mode. + */ + +/* All of these have sh compatible equivalents. */ +IPDEF1("ARGC", argc_gsu, PM_READONLY), +IPDEF2("HISTCHARS", histchars_gsu, PM_DONTIMPORT), +IPDEF4("status", &lastval), +IPDEF7("prompt", &prompt), +IPDEF7("PROMPT", &prompt), +IPDEF7("PROMPT2", &prompt2), +IPDEF7("PROMPT3", &prompt3), +IPDEF7("PROMPT4", &prompt4), +IPDEF8("MANPATH", &manpath, "manpath", 0), +IPDEF9("argv", &pparams, NULL), +IPDEF9("fignore", &fignore, "FIGNORE"), +IPDEF9("cdpath", &cdpath, "CDPATH"), +IPDEF9("fpath", &fpath, "FPATH"), +IPDEF9("mailpath", &mailpath, "MAILPATH"), +IPDEF9("manpath", &manpath, "MANPATH"), +IPDEF9("psvar", &psvar, "PSVAR"), +IPDEF9("watch", &watch, "WATCH"), + +IPDEF9F("zsh_eval_context", &zsh_eval_context, "ZSH_EVAL_CONTEXT", PM_READONLY), + +IPDEF9F("module_path", &module_path, "MODULE_PATH", PM_RESTRICTED), +IPDEF9F("path", &path, "PATH", PM_RESTRICTED), + +/* These are known to zsh alone. */ + +IPDEF10("pipestatus", pipestatus_gsu), + +{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0}, +}; + +/* + * Alternative versions of colon-separated path parameters for + * sh emulation. These don't link to the array versions. + */ +static initparam special_params_sh[] = { +IPDEF8("CDPATH", &cdpath, NULL, 0), +IPDEF8("FIGNORE", &fignore, NULL, 0), +IPDEF8("FPATH", &fpath, NULL, 0), +IPDEF8("MAILPATH", &mailpath, NULL, 0), +IPDEF8("WATCH", &watch, NULL, 0), +IPDEF8("PATH", &path, NULL, PM_RESTRICTED), +IPDEF8("PSVAR", &psvar, NULL, 0), +IPDEF8("ZSH_EVAL_CONTEXT", &zsh_eval_context, NULL, PM_READONLY), + +/* MODULE_PATH is not imported for security reasons */ +IPDEF8("MODULE_PATH", &module_path, NULL, PM_DONTIMPORT|PM_RESTRICTED), + +{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0}, +}; + +/* + * Special way of referring to the positional parameters. Unlike $* + * and $@, this is not readonly. This parameter is not directly + * visible in user space. + */ +static initparam argvparam_pm = IPDEF9F("", &pparams, NULL, \ + PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT); + +#undef BR + +#define IS_UNSET_VALUE(V) \ + ((V) && (!(V)->pm || ((V)->pm->node.flags & PM_UNSET) || \ + !(V)->pm->node.nam || !*(V)->pm->node.nam)) + +static Param argvparam; + +/* hash table containing the parameters */ + +/**/ +mod_export HashTable paramtab, realparamtab; + +/**/ +mod_export HashTable +newparamtable(int size, char const *name) +{ + HashTable ht; + if (!size) + size = 17; + ht = newhashtable(size, name, NULL); + + ht->hash = hasher; + ht->emptytable = emptyhashtable; + ht->filltable = NULL; + ht->cmpnodes = strcmp; + ht->addnode = addhashnode; + ht->getnode = getparamnode; + ht->getnode2 = gethashnode2; + ht->removenode = removehashnode; + ht->disablenode = NULL; + ht->enablenode = NULL; + ht->freenode = freeparamnode; + ht->printnode = printparamnode; + + return ht; +} + +/**/ +static HashNode +getparamnode(HashTable ht, const char *nam) +{ + HashNode hn = gethashnode2(ht, nam); + Param pm = (Param) hn; + + if (pm && pm->u.str && (pm->node.flags & PM_AUTOLOAD)) { + char *mn = dupstring(pm->u.str); + + (void)ensurefeature(mn, "p:", (pm->node.flags & PM_AUTOALL) ? NULL : + nam); + hn = gethashnode2(ht, nam); + if (!hn) { + /* + * This used to be a warning, but surely if we allow + * stuff to go ahead with the autoload stub with + * no error status we're in for all sorts of mayhem? + */ + zerr("autoloading module %s failed to define parameter: %s", mn, + nam); + } + } + return hn; +} + +/* Copy a parameter hash table */ + +static HashTable outtable; + +/**/ +static void +scancopyparams(HashNode hn, UNUSED(int flags)) +{ + /* Going into a real parameter, so always use permanent storage */ + Param pm = (Param)hn; + Param tpm = (Param) zshcalloc(sizeof *tpm); + tpm->node.nam = ztrdup(pm->node.nam); + copyparam(tpm, pm, 0); + addhashnode(outtable, tpm->node.nam, tpm); +} + +/**/ +HashTable +copyparamtable(HashTable ht, char *name) +{ + HashTable nht = 0; + if (ht) { + nht = newparamtable(ht->hsize, name); + outtable = nht; + scanhashtable(ht, 0, 0, 0, scancopyparams, 0); + outtable = NULL; + } + return nht; +} + +/* Flag to freeparamnode to unset the struct */ + +static int delunset; + +/* Function to delete a parameter table. */ + +/**/ +mod_export void +deleteparamtable(HashTable t) +{ + /* The parameters in the hash table need to be unset * + * before being deleted. */ + int odelunset = delunset; + delunset = 1; + deletehashtable(t); + delunset = odelunset; +} + +static unsigned numparamvals; + +/**/ +mod_export void +scancountparams(UNUSED(HashNode hn), int flags) +{ + ++numparamvals; + if ((flags & SCANPM_WANTKEYS) && (flags & SCANPM_WANTVALS)) + ++numparamvals; +} + +static Patprog scanprog; +static char *scanstr; +static char **paramvals; +static Param foundparam; + +/**/ +static void +scanparamvals(HashNode hn, int flags) +{ + struct value v; + Patprog prog; + + if (numparamvals && !(flags & SCANPM_MATCHMANY) && + (flags & (SCANPM_MATCHVAL|SCANPM_MATCHKEY|SCANPM_KEYMATCH))) + return; + v.pm = (Param)hn; + if ((flags & SCANPM_KEYMATCH)) { + char *tmp = dupstring(v.pm->node.nam); + + tokenize(tmp); + remnulargs(tmp); + + if (!(prog = patcompile(tmp, 0, NULL)) || !pattry(prog, scanstr)) + return; + } else if ((flags & SCANPM_MATCHKEY) && !pattry(scanprog, v.pm->node.nam)) { + return; + } + foundparam = v.pm; + if (flags & SCANPM_WANTKEYS) { + paramvals[numparamvals++] = v.pm->node.nam; + if (!(flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL))) + return; + } + v.isarr = (PM_TYPE(v.pm->node.flags) & (PM_ARRAY|PM_HASHED)); + v.flags = 0; + v.start = 0; + v.end = -1; + paramvals[numparamvals] = getstrvalue(&v); + if (flags & SCANPM_MATCHVAL) { + if (pattry(scanprog, paramvals[numparamvals])) { + numparamvals += ((flags & SCANPM_WANTVALS) ? 1 : + !(flags & SCANPM_WANTKEYS)); + } else if (flags & SCANPM_WANTKEYS) + --numparamvals; /* Value didn't match, discard key */ + } else + ++numparamvals; + foundparam = NULL; +} + +/**/ +char ** +paramvalarr(HashTable ht, int flags) +{ + DPUTS((flags & (SCANPM_MATCHKEY|SCANPM_MATCHVAL)) && !scanprog, + "BUG: scanning hash without scanprog set"); + numparamvals = 0; + if (ht) + scanhashtable(ht, 0, 0, PM_UNSET, scancountparams, flags); + paramvals = (char **) zhalloc((numparamvals + 1) * sizeof(char *)); + if (ht) { + numparamvals = 0; + scanhashtable(ht, 0, 0, PM_UNSET, scanparamvals, flags); + } + paramvals[numparamvals] = 0; + return paramvals; +} + +/* Return the full array (no indexing) referred to by a Value. * + * The array value is cached for the lifetime of the Value. */ + +/**/ +static char ** +getvaluearr(Value v) +{ + if (v->arr) + return v->arr; + else if (PM_TYPE(v->pm->node.flags) == PM_ARRAY) + return v->arr = v->pm->gsu.a->getfn(v->pm); + else if (PM_TYPE(v->pm->node.flags) == PM_HASHED) { + v->arr = paramvalarr(v->pm->gsu.h->getfn(v->pm), v->isarr); + /* Can't take numeric slices of associative arrays */ + v->start = 0; + v->end = numparamvals + 1; + return v->arr; + } else + return NULL; +} + +/* Return whether the variable is set * + * checks that array slices are within range * + * used for [[ -v ... ]] condition test */ + +/**/ +int +issetvar(char *name) +{ + struct value vbuf; + Value v; + int slice; + char **arr; + + if (!(v = getvalue(&vbuf, &name, 1)) || *name) + return 0; /* no value or more chars after the variable name */ + if (v->isarr & ~SCANPM_ARRONLY) + return v->end > 1; /* for extracted elements, end gives us a count */ + + slice = v->start != 0 || v->end != -1; + if (PM_TYPE(v->pm->node.flags) != PM_ARRAY || !slice) + return !slice && !(v->pm->node.flags & PM_UNSET); + + if (!v->end) /* empty array slice */ + return 0; + /* get the array and check end is within range */ + if (!(arr = getvaluearr(v))) + return 0; + return arrlen_ge(arr, v->end < 0 ? - v->end : v->end); +} + +/* + * Split environment string into (name, value) pair. + * this is used to avoid in-place editing of environment table + * that results in core dump on some systems + */ + +static int +split_env_string(char *env, char **name, char **value) +{ + char *str, *tenv; + + if (!env || !name || !value) + return 0; + + tenv = strcpy(zhalloc(strlen(env) + 1), env); + for (str = tenv; *str && *str != '='; str++) { + if (STOUC(*str) >= 128) { + /* + * We'll ignore environment variables with names not + * from the portable character set since we don't + * know of a good reason to accept them. + */ + return 0; + } + } + if (str != tenv && *str == '=') { + *str = '\0'; + *name = tenv; + *value = str + 1; + return 1; + } else + return 0; +} + +/** + * Check parameter flags to see if parameter shouldn't be imported + * from environment at start. + * + * return 1: don't import: 0: ok to import. + */ +static int dontimport(int flags) +{ + /* If explicitly marked as don't export */ + if (flags & PM_DONTIMPORT) + return 1; + /* If value already exported */ + if (flags & PM_EXPORTED) + return 1; + /* If security issue when importing and running with some privilege */ + if ((flags & PM_DONTIMPORT_SUID) && isset(PRIVILEGED)) + return 1; + /* OK to import */ + return 0; +} + +/* Set up parameter hash table. This will add predefined * + * parameter entries as well as setting up parameter table * + * entries for environment variables we inherit. */ + +/**/ +void +createparamtable(void) +{ + Param ip, pm; +#if !defined(HAVE_PUTENV) && !defined(USE_SET_UNSET_ENV) + char **new_environ; + int envsize; +#endif +#ifndef USE_SET_UNSET_ENV + char **envp; +#endif + char **envp2, **sigptr, **t; + char buf[50], *str, *iname, *ivalue, *hostnam; + int oae = opts[ALLEXPORT]; +#ifdef HAVE_UNAME + struct utsname unamebuf; + char *machinebuf; +#endif + + paramtab = realparamtab = newparamtable(151, "paramtab"); + + /* Add the special parameters to the hash table */ + for (ip = special_params; ip->node.nam; ip++) + paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip); + if (EMULATION(EMULATE_SH|EMULATE_KSH)) { + for (ip = special_params_sh; ip->node.nam; ip++) + paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip); + } else { + while ((++ip)->node.nam) + paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip); + } + + argvparam = (Param) &argvparam_pm; + + noerrs = 2; + + /* Add the standard non-special parameters which have to * + * be initialized before we copy the environment variables. * + * We don't want to override whatever values the user has * + * given them in the environment. */ + opts[ALLEXPORT] = 0; + setiparam("MAILCHECK", 60); + setiparam("LOGCHECK", 60); + setiparam("KEYTIMEOUT", 40); + setiparam("LISTMAX", 100); + /* + * We used to get the output baud rate here. However, that's + * pretty irrelevant to a terminal on an X display and can lead + * to unnecessary delays if it's wrong (which it probably is). + * Furthermore, even if the output is slow it's very likely + * to be because of WAN delays, not covered by the output + * baud rate. + * So allow the user to set it in the special cases where it's + * useful. + */ + setsparam("TMPPREFIX", ztrdup_metafy(DEFAULT_TMPPREFIX)); + setsparam("TIMEFMT", ztrdup_metafy(DEFAULT_TIMEFMT)); + setsparam("WATCHFMT", ztrdup_metafy(default_watchfmt)); + + hostnam = (char *)zalloc(256); + gethostname(hostnam, 256); + setsparam("HOST", ztrdup_metafy(hostnam)); + zfree(hostnam, 256); + + setsparam("LOGNAME", + ztrdup_metafy((str = getlogin()) && *str ? + str : cached_username)); + +#if !defined(HAVE_PUTENV) && !defined(USE_SET_UNSET_ENV) + /* Copy the environment variables we are inheriting to dynamic * + * memory, so we can do mallocs and frees on it. */ + envsize = sizeof(char *)*(1 + arrlen(environ)); + new_environ = (char **) zalloc(envsize); + memcpy(new_environ, environ, envsize); + environ = new_environ; +#endif + + /* Use heap allocation to avoid many small alloc/free calls */ + pushheap(); + + /* Now incorporate environment variables we are inheriting * + * into the parameter hash table. Copy them into dynamic * + * memory so that we can free them if needed */ + for ( +#ifndef USE_SET_UNSET_ENV + envp = +#endif + envp2 = environ; *envp2; envp2++) { + if (split_env_string(*envp2, &iname, &ivalue)) { + if (!idigit(*iname) && isident(iname) && !strchr(iname, '[')) { + /* + * Parameters that aren't already in the parameter table + * aren't special to the shell, so it's always OK to + * import. Otherwise, check parameter flags. + */ + if ((!(pm = (Param) paramtab->getnode(paramtab, iname)) || + !dontimport(pm->node.flags)) && + (pm = assignsparam(iname, metafy(ivalue, -1, META_DUP), + ASSPM_ENV_IMPORT))) { + pm->node.flags |= PM_EXPORTED; + if (pm->node.flags & PM_SPECIAL) + pm->env = mkenvstr (pm->node.nam, + getsparam(pm->node.nam), pm->node.flags); + else + pm->env = ztrdup(*envp2); +#ifndef USE_SET_UNSET_ENV + *envp++ = pm->env; +#endif + } + } + } + } + popheap(); +#ifndef USE_SET_UNSET_ENV + *envp = NULL; +#endif + opts[ALLEXPORT] = oae; + + /* + * For native emulation we always set the variable home + * (see setupvals()). + */ + pm = (Param) paramtab->getnode(paramtab, "HOME"); + if (EMULATION(EMULATE_ZSH)) + { + pm->node.flags &= ~PM_UNSET; + if (!(pm->node.flags & PM_EXPORTED)) + addenv(pm, home); + } else if (!home) + pm->node.flags |= PM_UNSET; + pm = (Param) paramtab->getnode(paramtab, "LOGNAME"); + if (!(pm->node.flags & PM_EXPORTED)) + addenv(pm, pm->u.str); + pm = (Param) paramtab->getnode(paramtab, "SHLVL"); + sprintf(buf, "%d", (int)++shlvl); + /* shlvl value in environment needs updating unconditionally */ + addenv(pm, buf); + + /* Add the standard non-special parameters */ + set_pwd_env(); +#ifdef HAVE_UNAME + if(uname(&unamebuf)) setsparam("CPUTYPE", ztrdup("unknown")); + else + { + machinebuf = ztrdup_metafy(unamebuf.machine); + setsparam("CPUTYPE", machinebuf); + } + +#else + setsparam("CPUTYPE", ztrdup_metafy("unknown")); +#endif + setsparam("MACHTYPE", ztrdup_metafy(MACHTYPE)); + setsparam("OSTYPE", ztrdup_metafy(OSTYPE)); + setsparam("TTY", ztrdup_metafy(ttystrname)); + setsparam("VENDOR", ztrdup_metafy(VENDOR)); + setsparam("ZSH_ARGZERO", ztrdup(posixzero)); + setsparam("ZSH_VERSION", ztrdup_metafy(ZSH_VERSION)); + setsparam("ZSH_PATCHLEVEL", ztrdup_metafy(ZSH_PATCHLEVEL)); + setaparam("signals", sigptr = zalloc((SIGCOUNT+4) * sizeof(char *))); + for (t = sigs; (*sigptr++ = ztrdup_metafy(*t++)); ); + + noerrs = 0; +} + +/* assign various functions used for non-special parameters */ + +/**/ +mod_export void +assigngetset(Param pm) +{ + switch (PM_TYPE(pm->node.flags)) { + case PM_SCALAR: + pm->gsu.s = &stdscalar_gsu; + break; + case PM_INTEGER: + pm->gsu.i = &stdinteger_gsu; + break; + case PM_EFLOAT: + case PM_FFLOAT: + pm->gsu.f = &stdfloat_gsu; + break; + case PM_ARRAY: + pm->gsu.a = &stdarray_gsu; + break; + case PM_HASHED: + pm->gsu.h = &stdhash_gsu; + break; + default: + DPUTS(1, "BUG: tried to create param node without valid flag"); + break; + } +} + +/* Create a parameter, so that it can be assigned to. Returns NULL if the * + * parameter already exists or can't be created, otherwise returns the * + * parameter node. If a parameter of the same name exists in an outer * + * scope, it is hidden by a newly created parameter. An already existing * + * parameter node at the current level may be `created' and returned * + * provided it is unset and not special. If the parameter can't be * + * created because it already exists, the PM_UNSET flag is cleared. */ + +/**/ +mod_export Param +createparam(char *name, int flags) +{ + Param pm, oldpm; + + if (paramtab != realparamtab) + flags = (flags & ~PM_EXPORTED) | PM_HASHELEM; + + if (name != nulstring) { + oldpm = (Param) (paramtab == realparamtab ? + /* gethashnode2() for direct table read */ + gethashnode2(paramtab, name) : + paramtab->getnode(paramtab, name)); + + DPUTS(oldpm && oldpm->level > locallevel, + "BUG: old local parameter not deleted"); + if (oldpm && (oldpm->level == locallevel || !(flags & PM_LOCAL))) { + if (isset(POSIXBUILTINS) && (oldpm->node.flags & PM_READONLY)) { + zerr("read-only variable: %s", name); + return NULL; + } + if ((oldpm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerr("%s: restricted", name); + return NULL; + } + if (!(oldpm->node.flags & PM_UNSET) || + (oldpm->node.flags & PM_SPECIAL) || + /* POSIXBUILTINS horror: we need to retain 'export' flags */ + (isset(POSIXBUILTINS) && (oldpm->node.flags & PM_EXPORTED))) { + oldpm->node.flags &= ~PM_UNSET; + if ((oldpm->node.flags & PM_SPECIAL) && oldpm->ename) { + Param altpm = + (Param) paramtab->getnode(paramtab, oldpm->ename); + if (altpm) + altpm->node.flags &= ~PM_UNSET; + } + return NULL; + } + + pm = oldpm; + pm->base = pm->width = 0; + oldpm = pm->old; + } else { + pm = (Param) zshcalloc(sizeof *pm); + if ((pm->old = oldpm)) { + /* + * needed to avoid freeing oldpm, but we do take it + * out of the environment when it's hidden. + */ + if (oldpm->env) + delenv(oldpm); + paramtab->removenode(paramtab, name); + } + paramtab->addnode(paramtab, ztrdup(name), pm); + } + + if (isset(ALLEXPORT) && !(flags & PM_HASHELEM)) + flags |= PM_EXPORTED; + } else { + pm = (Param) hcalloc(sizeof *pm); + pm->node.nam = nulstring; + } + pm->node.flags = flags & ~PM_LOCAL; + + if(!(pm->node.flags & PM_SPECIAL)) + assigngetset(pm); + return pm; +} + +/* Empty dummy function for special hash parameters. */ + +/**/ +static void +shempty(void) +{ +} + +/* + * Create a simple special hash parameter. + * + * This is for hashes added internally --- it's not possible to add + * special hashes from shell commands. It's currently used + * - by addparamdef() for special parameters in the zsh/parameter + * module + * - by ztie for special parameters tied to databases. + */ + +/**/ +mod_export Param +createspecialhash(char *name, GetNodeFunc get, ScanTabFunc scan, int flags) +{ + Param pm; + HashTable ht; + + if (!(pm = createparam(name, PM_SPECIAL|PM_HASHED|flags))) + return NULL; + + /* + * If there's an old parameter, we'll put the new one at + * the current locallevel, so that the old parameter is + * exposed again after leaving the function. Otherwise, + * we'll leave it alone. Usually this means the parameter + * will stay in place until explicitly unloaded, however + * if the parameter was previously unset within a function + * we'll inherit the level of that function and follow the + * standard convention that the parameter remains local + * even if unset. + * + * These semantics are similar to those of a normal parameter set + * within a function without a local definition. + */ + if (pm->old) + pm->level = locallevel; + pm->gsu.h = (flags & PM_READONLY) ? &stdhash_gsu : + &nullsethash_gsu; + pm->u.hash = ht = newhashtable(0, name, NULL); + + ht->hash = hasher; + ht->emptytable = (TableFunc) shempty; + ht->filltable = NULL; + ht->addnode = (AddNodeFunc) shempty; + ht->getnode = ht->getnode2 = get; + ht->removenode = (RemoveNodeFunc) shempty; + ht->disablenode = NULL; + ht->enablenode = NULL; + ht->freenode = (FreeNodeFunc) shempty; + ht->printnode = printparamnode; + ht->scantab = scan; + + return pm; +} + + +/* + * Copy a parameter + * + * If fakecopy is set, we are just saving the details of a special + * parameter. Otherwise, the result will be used as a real parameter + * and we need to do more work. + */ + +/**/ +void +copyparam(Param tpm, Param pm, int fakecopy) +{ + /* + * Note that tpm, into which we're copying, may not be in permanent + * storage. However, the values themselves are later used directly + * to set the parameter, so must be permanently allocated (in accordance + * with sets.?fn() usage). + */ + tpm->node.flags = pm->node.flags; + tpm->base = pm->base; + tpm->width = pm->width; + tpm->level = pm->level; + if (!fakecopy) + tpm->node.flags &= ~PM_SPECIAL; + switch (PM_TYPE(pm->node.flags)) { + case PM_SCALAR: + tpm->u.str = ztrdup(pm->gsu.s->getfn(pm)); + break; + case PM_INTEGER: + tpm->u.val = pm->gsu.i->getfn(pm); + break; + case PM_EFLOAT: + case PM_FFLOAT: + tpm->u.dval = pm->gsu.f->getfn(pm); + break; + case PM_ARRAY: + tpm->u.arr = zarrdup(pm->gsu.a->getfn(pm)); + break; + case PM_HASHED: + tpm->u.hash = copyparamtable(pm->gsu.h->getfn(pm), pm->node.nam); + break; + } + /* + * If the value is going to be passed as a real parameter (e.g. this is + * called from inside an associative array), we need the gets and sets + * functions to be useful. + * + * In this case we assume the saved parameter is not itself special, + * so we just use the standard functions. This is also why we switch off + * PM_SPECIAL. + */ + if (!fakecopy) + assigngetset(tpm); +} + +/* Return 1 if the string s is a valid identifier, else return 0. */ + +/**/ +mod_export int +isident(char *s) +{ + char *ss; + + if (!*s) /* empty string is definitely not valid */ + return 0; + + if (idigit(*s)) { + /* If the first character is `s' is a digit, then all must be */ + for (ss = ++s; *ss; ss++) + if (!idigit(*ss)) + break; + } else { + /* Find the first character in `s' not in the iident type table */ + ss = itype_end(s, IIDENT, 0); + } + + /* If the next character is not [, then it is * + * definitely not a valid identifier. */ + if (!*ss) + return 1; + if (s == ss) + return 0; + if (*ss != '[') + return 0; + + /* Require balanced [ ] pairs with something between */ + if (!(ss = parse_subscript(++ss, 1, ']'))) + return 0; + untokenize(s); + return !ss[1]; +} + +/* + * Parse a single argument to a parameter subscript. + * The subscripts starts at *str; *str is updated (input/output) + * + * *inv is set to indicate if the subscript is reversed (output) + * v is the Value for the parameter being accessed (input; note + * v->isarr may be modified, and if v is a hash the parameter will + * be updated to the element of the hash) + * a2 is 1 if this is the second subscript of a range (input) + * *w is only set if we need to find the end of a word (input; should + * be set to 0 by the caller). + * + * The final two arguments are to support multibyte characters. + * If supplied they are set to the length of the character before + * the index position and the one at the index position. If + * multibyte characters are not in use they are set to 1 for + * consistency. Note they aren't fully handled if a2 is non-zero, + * since they aren't needed. + * + * Returns a raw offset into the value from the start or end (i.e. + * after the arithmetic for Meta and possible multibyte characters has + * been taken into account). This actually gives the offset *after* + * the character in question; subtract *prevcharlen if necessary. + */ + +/**/ +static zlong +getarg(char **str, int *inv, Value v, int a2, zlong *w, + int *prevcharlen, int *nextcharlen, int flags) +{ + int hasbeg = 0, word = 0, rev = 0, ind = 0, down = 0, l, i, ishash; + int keymatch = 0, needtok = 0, arglen, len, inpar = 0; + char *s = *str, *sep = NULL, *t, sav, *d, **ta, **p, *tt, c; + zlong num = 1, beg = 0, r = 0, quote_arg = 0; + Patprog pprog = NULL; + + /* + * If in NO_EXEC mode, the parameters won't be set up properly, + * so just pretend everything is a hash for subscript parsing + */ + + ishash = (unset(EXECOPT) || + (v->pm && PM_TYPE(v->pm->node.flags) == PM_HASHED)); + if (prevcharlen) + *prevcharlen = 1; + if (nextcharlen) + *nextcharlen = 1; + + /* first parse any subscription flags */ + if (v->pm && (*s == '(' || *s == Inpar)) { + int escapes = 0; + int waste; + for (s++; *s != ')' && *s != Outpar && s != *str; s++) { + switch (*s) { + case 'r': + rev = 1; + keymatch = down = ind = 0; + break; + case 'R': + rev = down = 1; + keymatch = ind = 0; + break; + case 'k': + keymatch = ishash; + rev = 1; + down = ind = 0; + break; + case 'K': + keymatch = ishash; + rev = down = 1; + ind = 0; + break; + case 'i': + rev = ind = 1; + down = keymatch = 0; + break; + case 'I': + rev = ind = down = 1; + keymatch = 0; + break; + case 'w': + /* If the parameter is a scalar, then make subscription * + * work on a per-word basis instead of characters. */ + word = 1; + break; + case 'f': + word = 1; + sep = "\n"; + break; + case 'e': + quote_arg = 1; + break; + case 'n': + t = get_strarg(++s, &arglen); + if (!*t) + goto flagerr; + sav = *t; + *t = '\0'; + num = mathevalarg(s + arglen, &d); + if (!num) + num = 1; + *t = sav; + s = t + arglen - 1; + break; + case 'b': + hasbeg = 1; + t = get_strarg(++s, &arglen); + if (!*t) + goto flagerr; + sav = *t; + *t = '\0'; + if ((beg = mathevalarg(s + arglen, &d)) > 0) + beg--; + *t = sav; + s = t + arglen - 1; + break; + case 'p': + escapes = 1; + break; + case 's': + /* This gives the string that separates words * + * (for use with the `w' flag). */ + t = get_strarg(++s, &arglen); + if (!*t) + goto flagerr; + sav = *t; + *t = '\0'; + s += arglen; + sep = escapes ? getkeystring(s, &waste, GETKEYS_SEP, NULL) + : dupstring(s); + *t = sav; + s = t + arglen - 1; + break; + default: + flagerr: + num = 1; + word = rev = ind = down = keymatch = 0; + sep = NULL; + s = *str - 1; + } + } + if (s != *str) + s++; + } + if (num < 0) { + down = !down; + num = -num; + } + if (v->isarr & SCANPM_WANTKEYS) + *inv = (ind || !(v->isarr & SCANPM_WANTVALS)); + else if (v->isarr & SCANPM_WANTVALS) + *inv = 0; + else { + if (v->isarr) { + if (ind) { + v->isarr |= SCANPM_WANTKEYS; + v->isarr &= ~SCANPM_WANTVALS; + } else if (rev) + v->isarr |= SCANPM_WANTVALS; + /* + * This catches the case where we are using "k" (rather + * than "K") on a hash. + */ + if (!down && keymatch && ishash) + v->isarr &= ~SCANPM_MATCHMANY; + } + *inv = ind; + } + + for (t = s, i = 0; + (c = *t) && + ((c != Outbrack && (ishash || c != ',')) || i || inpar); + t++) { + /* Untokenize inull() except before brackets and double-quotes */ + if (inull(c)) { + c = t[1]; + if (c == '[' || c == ']' || + c == '(' || c == ')' || + c == '{' || c == '}') { + /* This test handles nested subscripts in hash keys */ + if (ishash && i) + *t = ztokens[*t - Pound]; + needtok = 1; + ++t; + } else if (c != '"') + *t = ztokens[*t - Pound]; + continue; + } + /* Inbrack and Outbrack are probably never found here ... */ + if (c == '[' || c == Inbrack) + i++; + else if (c == ']' || c == Outbrack) + i--; + if (c == '(' || c == Inpar) + inpar++; + else if (c == ')' || c == Outpar) + inpar--; + if (ispecial(c)) + needtok = 1; + } + if (!c) + return 0; + *str = tt = t; + + /* + * If in NO_EXEC mode, the parameters won't be set up properly, + * so there's no additional sanity checking we can do. + * Just return 0 now. + */ + if (unset(EXECOPT)) + return 0; + + s = dupstrpfx(s, t - s); + + /* If we're NOT reverse subscripting, strip the inull()s so brackets * + * are not backslashed after parsestr(). Otherwise leave them alone * + * so that the brackets will be escaped when we patcompile() or when * + * subscript arithmetic is performed (for nested subscripts). */ + if (ishash && (keymatch || !rev)) + remnulargs(s); + if (needtok) { + s = dupstring(s); + if (parsestr(&s)) + return 0; + singsub(&s); + } else if (rev) + remnulargs(s); /* This is probably always a no-op, but ... */ + if (!rev) { + if (ishash) { + HashTable ht = v->pm->gsu.h->getfn(v->pm); + if (!ht) { + if (flags & SCANPM_CHECKING) + return 0; + ht = newparamtable(17, v->pm->node.nam); + v->pm->gsu.h->setfn(v->pm, ht); + } + untokenize(s); + if (!(v->pm = (Param) ht->getnode(ht, s))) { + HashTable tht = paramtab; + paramtab = ht; + v->pm = createparam(s, PM_SCALAR|PM_UNSET); + paramtab = tht; + } + v->isarr = (*inv ? SCANPM_WANTINDEX : 0); + v->start = 0; + *inv = 0; /* We've already obtained the "index" (key) */ + *w = v->end = -1; + r = isset(KSHARRAYS) ? 1 : 0; + } else { + r = mathevalarg(s, &s); + if (isset(KSHARRAYS) && r >= 0) + r++; + } + if (word && !v->isarr) { + s = t = getstrvalue(v); + i = wordcount(s, sep, 0); + if (r < 0) + r += i + 1; + if (r < 1) + r = 1; + if (r > i) + r = i; + if (!s || !*s) + return 0; + while ((d = findword(&s, sep)) && --r); + if (!d) + return 0; + + if (!a2 && *tt != ',') + *w = (zlong)(s - t); + + return (a2 ? s : d + 1) - t; + } else if (!v->isarr && !word) { + int lastcharlen = 1; + s = getstrvalue(v); + /* + * Note for the confused (= pws): the index r we + * have so far is that specified by the user. The value + * passed back is an offset from the start or end of + * the string. Hence it needs correcting at least + * for Meta characters and maybe for multibyte characters. + */ + if (r > 0) { + zlong nchars = r; + + MB_METACHARINIT(); + for (t = s; nchars && *t; nchars--) + t += (lastcharlen = MB_METACHARLEN(t)); + /* for consistency, keep any remainder off the end */ + r = (zlong)(t - s) + nchars; + if (prevcharlen && !nchars /* ignore if off the end */) + *prevcharlen = lastcharlen; + if (nextcharlen && *t) + *nextcharlen = MB_METACHARLEN(t); + } else if (r == 0) { + if (prevcharlen) + *prevcharlen = 0; + if (nextcharlen && *s) { + MB_METACHARINIT(); + *nextcharlen = MB_METACHARLEN(s); + } + } else { + zlong nchars = (zlong)MB_METASTRLEN(s) + r; + + if (nchars < 0) { + /* make sure this isn't valid as a raw pointer */ + r -= (zlong)strlen(s); + } else { + MB_METACHARINIT(); + for (t = s; nchars && *t; nchars--) + t += (lastcharlen = MB_METACHARLEN(t)); + r = - (zlong)strlen(t); /* keep negative */ + if (prevcharlen) + *prevcharlen = lastcharlen; + if (nextcharlen && *t) + *nextcharlen = MB_METACHARLEN(t); + } + } + } + } else { + if (!v->isarr && !word && !quote_arg) { + l = strlen(s); + if (a2) { + if (!l || *s != '*') { + d = (char *) hcalloc(l + 2); + *d = '*'; + strcpy(d + 1, s); + s = d; + } + } else { + if (!l || s[l - 1] != '*' || (l > 1 && s[l - 2] == '\\')) { + d = (char *) hcalloc(l + 2); + strcpy(d, s); + strcat(d, "*"); + s = d; + } + } + } + if (!keymatch) { + if (quote_arg) { + untokenize(s); + /* Scalar (e) needs implicit asterisk tokens */ + if (!v->isarr && !word) { + l = strlen(s); + d = (char *) hcalloc(l + 2); + if (a2) { + *d = Star; + strcpy(d + 1, s); + } else { + strcpy(d, s); + d[l] = Star; + d[l + 1] = '\0'; + } + s = d; + } + } else + tokenize(s); + remnulargs(s); + pprog = patcompile(s, 0, NULL); + } else + pprog = NULL; + + if (v->isarr) { + if (ishash) { + scanprog = pprog; + scanstr = s; + if (keymatch) + v->isarr |= SCANPM_KEYMATCH; + else { + if (!pprog) + return 1; + if (ind) + v->isarr |= SCANPM_MATCHKEY; + else + v->isarr |= SCANPM_MATCHVAL; + } + if (down) + v->isarr |= SCANPM_MATCHMANY; + if ((ta = getvaluearr(v)) && + (*ta || ((v->isarr & SCANPM_MATCHMANY) && + (v->isarr & (SCANPM_MATCHKEY | SCANPM_MATCHVAL | + SCANPM_KEYMATCH))))) { + *inv = (v->flags & VALFLAG_INV) ? 1 : 0; + *w = v->end; + scanprog = NULL; + return 1; + } + scanprog = NULL; + } else + ta = getarrvalue(v); + if (!ta || !*ta) + return !down; + len = arrlen(ta); + if (beg < 0) + beg += len; + if (down) { + if (beg < 0) + return 0; + } else if (beg >= len) + return len + 1; + if (beg >= 0 && beg < len) { + if (down) { + if (!hasbeg) + beg = len - 1; + for (r = 1 + beg, p = ta + beg; p >= ta; r--, p--) { + if (pprog && pattry(pprog, *p) && !--num) + return r; + } + } else + for (r = 1 + beg, p = ta + beg; *p; r++, p++) + if (pprog && pattry(pprog, *p) && !--num) + return r; + } + } else if (word) { + ta = sepsplit(d = s = getstrvalue(v), sep, 1, 1); + len = arrlen(ta); + if (beg < 0) + beg += len; + if (down) { + if (beg < 0) + return 0; + } else if (beg >= len) + return len + 1; + if (beg >= 0 && beg < len) { + if (down) { + if (!hasbeg) + beg = len - 1; + for (r = 1 + beg, p = ta + beg; p >= ta; p--, r--) + if (pprog && pattry(pprog, *p) && !--num) + break; + if (p < ta) + return 0; + } else { + for (r = 1 + beg, p = ta + beg; *p; r++, p++) + if (pprog && pattry(pprog, *p) && !--num) + break; + if (!*p) + return 0; + } + } + if (a2) + r++; + for (i = 0; (t = findword(&d, sep)) && *t; i++) + if (!--r) { + r = (zlong)(t - s + (a2 ? -1 : 1)); + if (!a2 && *tt != ',') + *w = r + strlen(ta[i]) - 1; + return r; + } + return a2 ? -1 : 0; + } else { + /* Searching characters */ + int slen; + d = getstrvalue(v); + if (!d || !*d) + return 0; + /* + * beg and len are character counts, not raw offsets. + * Remember we need to return a raw offset. + */ + len = MB_METASTRLEN(d); + slen = strlen(d); + if (beg < 0) + beg += len; + MB_METACHARINIT(); + if (beg >= 0 && beg < len) { + char *de = d + slen; + + if (a2) { + /* + * Second argument: we don't need to + * handle prevcharlen or nextcharlen, but + * we do need to handle characters appropriately. + */ + if (down) { + int nmatches = 0; + char *lastpos = NULL; + + if (!hasbeg) + beg = len; + + /* + * See below: we have to move forward, + * but need to count from the end. + */ + for (t = d, r = 0; r <= beg; r++) { + sav = *t; + *t = '\0'; + if (pprog && pattry(pprog, d)) { + nmatches++; + lastpos = t; + } + *t = sav; + if (t == de) + break; + t += MB_METACHARLEN(t); + } + + if (nmatches >= num) { + if (num > 1) { + nmatches -= num; + MB_METACHARINIT(); + for (t = d, r = 0; ; r++) { + sav = *t; + *t = '\0'; + if (pprog && pattry(pprog, d) && + nmatches-- == 0) { + lastpos = t; + *t = sav; + break; + } + *t = sav; + t += MB_METACHARLEN(t); + } + } + /* else lastpos is already OK */ + + return lastpos - d; + } + } else { + /* + * This handling of the b flag + * gives odd results, but this is the + * way it's always worked. + */ + for (t = d; beg && t <= de; beg--) + t += MB_METACHARLEN(t); + for (;;) { + sav = *t; + *t = '\0'; + if (pprog && pattry(pprog, d) && !--num) { + *t = sav; + /* + * This time, don't increment + * pointer, since it's already + * after everything we matched. + */ + return t - d; + } + *t = sav; + if (t == de) + break; + t += MB_METACHARLEN(t); + } + } + } else { + /* + * First argument: this is the only case + * where we need prevcharlen and nextcharlen. + */ + int lastcharlen; + + if (down) { + int nmatches = 0; + char *lastpos = NULL; + + if (!hasbeg) + beg = len; + + /* + * We can only move forward through + * multibyte strings, so record the + * matches. + * Unfortunately the count num works + * from the end, so it's easy to get the + * last one but we need to repeat if + * we want another one. + */ + for (t = d, r = 0; r <= beg; r++) { + if (pprog && pattry(pprog, t)) { + nmatches++; + lastpos = t; + } + if (t == de) + break; + t += MB_METACHARLEN(t); + } + + if (nmatches >= num) { + if (num > 1) { + /* + * Need to start again and repeat + * to get the right match. + */ + nmatches -= num; + MB_METACHARINIT(); + for (t = d, r = 0; ; r++) { + if (pprog && pattry(pprog, t) && + nmatches-- == 0) { + lastpos = t; + break; + } + t += MB_METACHARLEN(t); + } + } + /* else lastpos is already OK */ + + /* return pointer after matched char */ + lastpos += + (lastcharlen = MB_METACHARLEN(lastpos)); + if (prevcharlen) + *prevcharlen = lastcharlen; + if (nextcharlen) + *nextcharlen = MB_METACHARLEN(lastpos); + return lastpos - d; + } + + for (r = beg + 1, t = d + beg; t >= d; r--, t--) { + if (pprog && pattry(pprog, t) && + !--num) + return r; + } + } else { + for (t = d; beg && t <= de; beg--) + t += MB_METACHARLEN(t); + for (;;) { + if (pprog && pattry(pprog, t) && !--num) { + /* return pointer after matched char */ + t += (lastcharlen = MB_METACHARLEN(t)); + if (prevcharlen) + *prevcharlen = lastcharlen; + if (nextcharlen) + *nextcharlen = MB_METACHARLEN(t); + return t - d; + } + if (t == de) + break; + t += MB_METACHARLEN(t); + } + } + } + } + return down ? 0 : slen + 1; + } + } + return r; +} + +/* + * Parse a subscript. + * + * pptr: In/Out parameter. On entry, *ptr points to a "[foo]" string. On exit + * it will point one past the closing bracket. + * + * v: In/Out parameter. Its .start and .end members (at least) will be updated + * with the parsed indices. + * + * flags: can be either SCANPM_DQUOTED or zero. Other bits are not used. + */ + +/**/ +int +getindex(char **pptr, Value v, int flags) +{ + int start, end, inv = 0; + char *s = *pptr, *tbrack; + + *s++ = '['; + /* Error handled after untokenizing */ + s = parse_subscript(s, flags & SCANPM_DQUOTED, ']'); + /* Now we untokenize everything except inull() markers so we can check * + * for the '*' and '@' special subscripts. The inull()s are removed * + * in getarg() after we know whether we're doing reverse indexing. */ + for (tbrack = *pptr + 1; *tbrack && tbrack != s; tbrack++) { + if (inull(*tbrack) && !*++tbrack) + break; + if (itok(*tbrack)) /* Need to check for Nularg here? */ + *tbrack = ztokens[*tbrack - Pound]; + } + /* If we reached the end of the string (s == NULL) we have an error */ + if (*tbrack) + *tbrack = Outbrack; + else { + zerr("invalid subscript"); + *pptr = tbrack; + return 1; + } + s = *pptr + 1; + if ((s[0] == '*' || s[0] == '@') && s + 1 == tbrack) { + if ((v->isarr || IS_UNSET_VALUE(v)) && s[0] == '@') + v->isarr |= SCANPM_ISVAR_AT; + v->start = 0; + v->end = -1; + s += 2; + } else { + zlong we = 0, dummy; + int startprevlen, startnextlen; + + start = getarg(&s, &inv, v, 0, &we, &startprevlen, &startnextlen, + flags); + + if (inv) { + if (!v->isarr && start != 0) { + char *t, *p; + t = getstrvalue(v); + /* + * Note for the confused (= pws): this is an inverse + * offset so at this stage we need to convert from + * the immediate offset into the value that we have + * into a logical character position. + */ + if (start > 0) { + int nstart = 0; + char *target = t + start - startprevlen; + + p = t; + MB_METACHARINIT(); + while (*p) { + /* + * move up characters, counting how many we + * found + */ + p += MB_METACHARLEN(p); + if (p < target) + nstart++; + else { + if (p == target) + nstart++; + else + p = target; /* pretend we hit exactly */ + break; + } + } + /* if start was too big, keep the difference */ + start = nstart + (target - p) + 1; + } else { + zlong startoff = start + strlen(t); +#ifdef DEBUG + dputs("BUG: can't have negative inverse offsets???"); +#endif + if (startoff < 0) { + /* invalid: keep index but don't dereference */ + start = startoff; + } else { + /* find start in full characters */ + MB_METACHARINIT(); + for (p = t; p < t + startoff;) + p += MB_METACHARLEN(p); + start = - MB_METASTRLEN(p); + } + } + } + if (start > 0 && (isset(KSHARRAYS) || (v->pm->node.flags & PM_HASHED))) + start--; + if (v->isarr != SCANPM_WANTINDEX) { + v->flags |= VALFLAG_INV; + v->isarr = 0; + v->start = start; + v->end = start + 1; + } + if (*s == ',') { + zerr("invalid subscript"); + *tbrack = ']'; + *pptr = tbrack+1; + return 1; + } + if (s == tbrack) + s++; + } else { + int com; + + if ((com = (*s == ','))) { + s++; + end = getarg(&s, &inv, v, 1, &dummy, NULL, NULL, flags); + } else { + end = we ? we : start; + } + if (start != end) + com = 1; + /* + * Somehow the logic sometimes forces us to use the previous + * or next character to what we would expect, which is + * why we had to calculate them in getarg(). + */ + if (start > 0) + start -= startprevlen; + else if (start == 0 && end == 0) + { + /* + * Strictly, this range is entirely off the + * start of the available index range. + * This can't happen with KSH_ARRAYS; we already + * altered the start index in getarg(). + * Are we being strict? + */ + if (isset(KSHZEROSUBSCRIPT)) { + /* + * We're not. + * Treat this as accessing the first element of the + * array. + */ + end = startnextlen; + } else { + /* + * We are. Flag that this range is invalid + * for setting elements. Set the indexes + * to a range that returns empty for other accesses. + */ + v->flags |= VALFLAG_EMPTY; + start = -1; + com = 1; + } + } + if (s == tbrack) { + s++; + if (v->isarr && !com && + (!(v->isarr & SCANPM_MATCHMANY) || + !(v->isarr & (SCANPM_MATCHKEY | SCANPM_MATCHVAL | + SCANPM_KEYMATCH)))) + v->isarr = 0; + v->start = start; + v->end = end; + } else + s = *pptr; + } + } + *tbrack = ']'; + *pptr = s; + return 0; +} + + +/**/ +mod_export Value +getvalue(Value v, char **pptr, int bracks) +{ + return fetchvalue(v, pptr, bracks, 0); +} + +/**/ +mod_export Value +fetchvalue(Value v, char **pptr, int bracks, int flags) +{ + char *s, *t, *ie; + char sav, c; + int ppar = 0; + + s = t = *pptr; + + if (idigit(c = *s)) { + if (bracks >= 0) + ppar = zstrtol(s, &s, 10); + else + ppar = *s++ - '0'; + } + else if ((ie = itype_end(s, IIDENT, 0)) != s) + s = ie; + else if (c == Quest) + *s++ = '?'; + else if (c == Pound) + *s++ = '#'; + else if (c == String) + *s++ = '$'; + else if (c == Qstring) + *s++ = '$'; + else if (c == Star) + *s++ = '*'; + else if (IS_DASH(c)) + *s++ = '-'; + else if (c == '#' || c == '?' || c == '$' || + c == '!' || c == '@' || c == '*') + s++; + else + return NULL; + + if ((sav = *s)) + *s = '\0'; + if (ppar) { + if (v) + memset(v, 0, sizeof(*v)); + else + v = (Value) hcalloc(sizeof *v); + v->pm = argvparam; + v->flags = 0; + v->start = ppar - 1; + v->end = ppar; + if (sav) + *s = sav; + } else { + Param pm; + int isvarat; + + isvarat = (t[0] == '@' && !t[1]); + pm = (Param) paramtab->getnode(paramtab, *t == '0' ? "0" : t); + if (sav) + *s = sav; + *pptr = s; + if (!pm || (pm->node.flags & PM_UNSET)) + return NULL; + if (v) + memset(v, 0, sizeof(*v)); + else + v = (Value) hcalloc(sizeof *v); + if (PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)) { + /* Overload v->isarr as the flag bits for hashed arrays. */ + v->isarr = flags | (isvarat ? SCANPM_ISVAR_AT : 0); + /* If no flags were passed, we need something to represent * + * `true' yet differ from an explicit WANTVALS. Use a * + * special flag for this case. */ + if (!v->isarr) + v->isarr = SCANPM_ARRONLY; + } + v->pm = pm; + v->flags = 0; + v->start = 0; + v->end = -1; + if (bracks > 0 && (*s == '[' || *s == Inbrack)) { + if (getindex(&s, v, flags)) { + *pptr = s; + return v; + } + } else if (!(flags & SCANPM_ASSIGNING) && v->isarr && + itype_end(t, IIDENT, 1) != t && isset(KSHARRAYS)) + v->end = 1, v->isarr = 0; + } + if (!bracks && *s) + return NULL; + *pptr = s; +#if 0 + /* + * Check for large subscripts that might be erroneous. + * This code is too gross in several ways: + * - the limit is completely arbitrary + * - the test vetoes operations on existing arrays + * - it's not at all clear a general test on large arrays of + * this kind is any use. + * + * Until someone comes up with workable replacement code it's + * therefore commented out. + */ + if (v->start > MAX_ARRLEN) { + zerr("subscript too %s: %d", "big", v->start + !isset(KSHARRAYS)); + return NULL; + } + if (v->start < -MAX_ARRLEN) { + zerr("subscript too %s: %d", "small", v->start); + return NULL; + } + if (v->end > MAX_ARRLEN+1) { + zerr("subscript too %s: %d", "big", v->end - !!isset(KSHARRAYS)); + return NULL; + } + if (v->end < -MAX_ARRLEN) { + zerr("subscript too %s: %d", "small", v->end); + return NULL; + } +#endif + return v; +} + +/**/ +mod_export char * +getstrvalue(Value v) +{ + char *s, **ss; + char buf[BDIGBUFSIZE]; + int len; + + if (!v) + return hcalloc(1); + + if ((v->flags & VALFLAG_INV) && !(v->pm->node.flags & PM_HASHED)) { + sprintf(buf, "%d", v->start); + s = dupstring(buf); + return s; + } + + switch(PM_TYPE(v->pm->node.flags)) { + case PM_HASHED: + /* (!v->isarr) should be impossible unless emulating ksh */ + if (!v->isarr && EMULATION(EMULATE_KSH)) { + s = dupstring("[0]"); + if (getindex(&s, v, 0) == 0) + s = getstrvalue(v); + return s; + } /* else fall through */ + case PM_ARRAY: + ss = getvaluearr(v); + if (v->isarr) + s = sepjoin(ss, NULL, 1); + else { + if (v->start < 0) + v->start += arrlen(ss); + s = (arrlen_le(ss, v->start) || v->start < 0) ? + (char *) hcalloc(1) : ss[v->start]; + } + return s; + case PM_INTEGER: + convbase(buf, v->pm->gsu.i->getfn(v->pm), v->pm->base); + s = dupstring(buf); + break; + case PM_EFLOAT: + case PM_FFLOAT: + s = convfloat(v->pm->gsu.f->getfn(v->pm), + v->pm->base, v->pm->node.flags, NULL); + break; + case PM_SCALAR: + s = v->pm->gsu.s->getfn(v->pm); + break; + default: + s = ""; + DPUTS(1, "BUG: param node without valid type"); + break; + } + + if (v->flags & VALFLAG_SUBST) { + if (v->pm->node.flags & (PM_LEFT|PM_RIGHT_B|PM_RIGHT_Z)) { + unsigned int fwidth = v->pm->width ? v->pm->width : MB_METASTRLEN(s); + switch (v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) { + char *t, *tend; + unsigned int t0; + + case PM_LEFT: + case PM_LEFT | PM_RIGHT_Z: + t = s; + if (v->pm->node.flags & PM_RIGHT_Z) + while (*t == '0') + t++; + else + while (iblank(*t)) + t++; + MB_METACHARINIT(); + for (tend = t, t0 = 0; t0 < fwidth && *tend; t0++) + tend += MB_METACHARLEN(tend); + /* + * t0 is the number of characters from t used, + * hence (fwidth - t0) is the number of padding + * characters. fwidth is a misnomer: we use + * character counts, not character widths. + * + * (tend - t) is the number of bytes we need + * to get fwidth characters or the entire string; + * the characters may be multiple bytes. + */ + fwidth -= t0; /* padding chars remaining */ + t0 = tend - t; /* bytes to copy from string */ + s = (char *) hcalloc(t0 + fwidth + 1); + memcpy(s, t, t0); + if (fwidth) + memset(s + t0, ' ', fwidth); + s[t0 + fwidth] = '\0'; + break; + case PM_RIGHT_B: + case PM_RIGHT_Z: + case PM_RIGHT_Z | PM_RIGHT_B: + { + int zero = 1; + /* Calculate length in possibly multibyte chars */ + unsigned int charlen = MB_METASTRLEN(s); + + if (charlen < fwidth) { + char *valprefend = s; + int preflen; + if (v->pm->node.flags & PM_RIGHT_Z) { + /* + * This is a documented feature: when deciding + * whether to pad with zeroes, ignore + * leading blanks already in the value; + * only look for numbers after that. + * Not sure how useful this really is. + * It's certainly confusing to code around. + */ + for (t = s; iblank(*t); t++) + ; + /* + * Allow padding after initial minus + * for numeric variables. + */ + if ((v->pm->node.flags & + (PM_INTEGER|PM_EFLOAT|PM_FFLOAT)) && + *t == '-') + t++; + /* + * Allow padding after initial 0x or + * base# for integer variables. + */ + if (v->pm->node.flags & PM_INTEGER) { + if (isset(CBASES) && + t[0] == '0' && t[1] == 'x') + t += 2; + else if ((valprefend = strchr(t, '#'))) + t = valprefend + 1; + } + valprefend = t; + if (!*t) + zero = 0; + else if (v->pm->node.flags & + (PM_INTEGER|PM_EFLOAT|PM_FFLOAT)) { + /* zero always OK */ + } else if (!idigit(*t)) + zero = 0; + } + /* number of characters needed for padding */ + fwidth -= charlen; + /* bytes from original string */ + t0 = strlen(s); + t = (char *) hcalloc(fwidth + t0 + 1); + /* prefix guaranteed to be single byte chars */ + preflen = valprefend - s; + memset(t + preflen, + (((v->pm->node.flags & PM_RIGHT_B) + || !zero) ? ' ' : '0'), fwidth); + /* + * Copy - or 0x or base# before any padding + * zeroes. + */ + if (preflen) + memcpy(t, s, preflen); + memcpy(t + preflen + fwidth, + valprefend, t0 - preflen); + t[fwidth + t0] = '\0'; + s = t; + } else { + /* Need to skip (charlen - fwidth) chars */ + for (t0 = charlen - fwidth; t0; t0--) + s += MB_METACHARLEN(s); + } + } + break; + } + } + switch (v->pm->node.flags & (PM_LOWER | PM_UPPER)) { + case PM_LOWER: + s = casemodify(s, CASMOD_LOWER); + break; + case PM_UPPER: + s = casemodify(s, CASMOD_UPPER); + break; + } + } + if (v->start == 0 && v->end == -1) + return s; + + len = strlen(s); + if (v->start < 0) { + v->start += len; + if (v->start < 0) + v->start = 0; + } + if (v->end < 0) { + v->end += len; + if (v->end >= 0) { + char *eptr = s + v->end; + if (*eptr) + v->end += MB_METACHARLEN(eptr); + } + } + + s = (v->start > len) ? dupstring("") : + dupstring_wlen(s + v->start, len - v->start); + + if (v->end <= v->start) + s[0] = '\0'; + else if (v->end - v->start <= len - v->start) + s[v->end - v->start] = '\0'; + + return s; +} + +static char *nular[] = {"", NULL}; + +/**/ +mod_export char ** +getarrvalue(Value v) +{ + char **s; + + if (!v) + return arrdup(nular); + else if (IS_UNSET_VALUE(v)) + return arrdup(&nular[1]); + if (v->flags & VALFLAG_INV) { + char buf[DIGBUFSIZE]; + + s = arrdup(nular); + sprintf(buf, "%d", v->start); + s[0] = dupstring(buf); + return s; + } + s = getvaluearr(v); + if (v->start == 0 && v->end == -1) + return s; + if (v->start < 0) + v->start += arrlen(s); + if (v->end < 0) + v->end += arrlen(s) + 1; + + /* Null if 1) array too short, 2) index still negative */ + if (v->end <= v->start) { + s = arrdup_max(nular, 0); + } + else if (v->start < 0) { + s = arrdup_max(nular, 1); + } + else if (arrlen_le(s, v->start)) { + /* Handle $ary[i,i] consistently for any $i > $#ary + * and $ary[i,j] consistently for any $j > $i > $#ary + */ + s = arrdup_max(nular, v->end - (v->start + 1)); + } + else { + /* Copy to a point before the end of the source array: + * arrdup_max will copy at most v->end - v->start elements, + * starting from v->start element. Original code said: + * s[v->end - v->start] = NULL + * which means that there are exactly the same number of + * elements as the value of the above *0-based* index. + */ + s = arrdup_max(s + v->start, v->end - v->start); + } + + return s; +} + +/**/ +mod_export zlong +getintvalue(Value v) +{ + if (!v) + return 0; + if (v->flags & VALFLAG_INV) + return v->start; + if (v->isarr) { + char **arr = getarrvalue(v); + if (arr) { + char *scal = sepjoin(arr, NULL, 1); + return mathevali(scal); + } else + return 0; + } + if (PM_TYPE(v->pm->node.flags) == PM_INTEGER) + return v->pm->gsu.i->getfn(v->pm); + if (v->pm->node.flags & (PM_EFLOAT|PM_FFLOAT)) + return (zlong)v->pm->gsu.f->getfn(v->pm); + return mathevali(getstrvalue(v)); +} + +/**/ +mnumber +getnumvalue(Value v) +{ + mnumber mn; + mn.type = MN_INTEGER; + + + if (!v) { + mn.u.l = 0; + } else if (v->flags & VALFLAG_INV) { + mn.u.l = v->start; + } else if (v->isarr) { + char **arr = getarrvalue(v); + if (arr) { + char *scal = sepjoin(arr, NULL, 1); + return matheval(scal); + } else + mn.u.l = 0; + } else if (PM_TYPE(v->pm->node.flags) == PM_INTEGER) { + mn.u.l = v->pm->gsu.i->getfn(v->pm); + } else if (v->pm->node.flags & (PM_EFLOAT|PM_FFLOAT)) { + mn.type = MN_FLOAT; + mn.u.d = v->pm->gsu.f->getfn(v->pm); + } else + return matheval(getstrvalue(v)); + return mn; +} + +/**/ +void +export_param(Param pm) +{ + char buf[BDIGBUFSIZE], *val; + + if (PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)) { +#if 0 /* Requires changes elsewhere in params.c and builtin.c */ + if (EMULATION(EMULATE_KSH) /* isset(KSHARRAYS) */) { + struct value v; + v.isarr = 1; + v.flags = 0; + v.start = 0; + v.end = -1; + val = getstrvalue(&v); + } else +#endif + return; + } else if (PM_TYPE(pm->node.flags) == PM_INTEGER) + convbase(val = buf, pm->gsu.i->getfn(pm), pm->base); + else if (pm->node.flags & (PM_EFLOAT|PM_FFLOAT)) + val = convfloat(pm->gsu.f->getfn(pm), pm->base, + pm->node.flags, NULL); + else + val = pm->gsu.s->getfn(pm); + + addenv(pm, val); +} + +/**/ +mod_export void +setstrvalue(Value v, char *val) +{ + assignstrvalue(v, val, 0); +} + +/**/ +mod_export void +assignstrvalue(Value v, char *val, int flags) +{ + if (unset(EXECOPT)) + return; + if (v->pm->node.flags & PM_READONLY) { + zerr("read-only variable: %s", v->pm->node.nam); + zsfree(val); + return; + } + if ((v->pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerr("%s: restricted", v->pm->node.nam); + zsfree(val); + return; + } + if ((v->pm->node.flags & PM_HASHED) && + (v->isarr & (SCANPM_MATCHMANY|SCANPM_ARRONLY))) { + zerr("%s: attempt to set slice of associative array", v->pm->node.nam); + zsfree(val); + return; + } + if (v->flags & VALFLAG_EMPTY) { + zerr("%s: assignment to invalid subscript range", v->pm->node.nam); + zsfree(val); + return; + } + v->pm->node.flags &= ~PM_UNSET; + switch (PM_TYPE(v->pm->node.flags)) { + case PM_SCALAR: + if (v->start == 0 && v->end == -1) { + v->pm->gsu.s->setfn(v->pm, val); + if ((v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) && + !v->pm->width) + v->pm->width = strlen(val); + } else { + char *z, *x; + int zlen, vlen, newsize; + + z = v->pm->gsu.s->getfn(v->pm); + zlen = strlen(z); + + if ((v->flags & VALFLAG_INV) && unset(KSHARRAYS)) + v->start--, v->end--; + if (v->start < 0) { + v->start += zlen; + if (v->start < 0) + v->start = 0; + } + if (v->start > zlen) + v->start = zlen; + if (v->end < 0) { + v->end += zlen; + if (v->end < 0) { + v->end = 0; + } else if (v->end >= zlen) { + v->end = zlen; + } else { +#ifdef MULTIBYTE_SUPPORT + if (isset(MULTIBYTE)) { + v->end += MB_METACHARLEN(z + v->end); + } else { + v->end++; + } +#else + v->end++; +#endif + } + } + else if (v->end > zlen) + v->end = zlen; + + vlen = strlen(val); + /* Characters preceding start index + + characters of what is assigned + + characters following end index */ + newsize = v->start + vlen + (zlen - v->end); + + /* Does new size differ? */ + if (newsize != zlen || v->pm->gsu.s->setfn != strsetfn) { + x = (char *) zalloc(newsize + 1); + strncpy(x, z, v->start); + strcpy(x + v->start, val); + strcat(x + v->start, z + v->end); + v->pm->gsu.s->setfn(v->pm, x); + } else { + Param pm = v->pm; + /* Size doesn't change, can limit actions to only + * overwriting bytes in already allocated string */ + strncpy(z + v->start, val, vlen); + /* Implement remainder of strsetfn */ + if (!(pm->node.flags & PM_HASHELEM) && + ((pm->node.flags & PM_NAMEDDIR) || + isset(AUTONAMEDIRS))) { + pm->node.flags |= PM_NAMEDDIR; + adduserdir(pm->node.nam, z, 0, 0); + } + } + zsfree(val); + } + break; + case PM_INTEGER: + if (val) { + zlong ival; + if (flags & ASSPM_ENV_IMPORT) { + char *ptr; + ival = zstrtol_underscore(val, &ptr, 0, 1); + } else + ival = mathevali(val); + v->pm->gsu.i->setfn(v->pm, ival); + if ((v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) && + !v->pm->width) + v->pm->width = strlen(val); + zsfree(val); + } + if (!v->pm->base && lastbase != -1) + v->pm->base = lastbase; + break; + case PM_EFLOAT: + case PM_FFLOAT: + if (val) { + mnumber mn; + if (flags & ASSPM_ENV_IMPORT) { + char *ptr; + mn.type = MN_FLOAT; + mn.u.d = strtod(val, &ptr); + } else + mn = matheval(val); + v->pm->gsu.f->setfn(v->pm, (mn.type & MN_FLOAT) ? mn.u.d : + (double)mn.u.l); + if ((v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) && + !v->pm->width) + v->pm->width = strlen(val); + zsfree(val); + } + break; + case PM_ARRAY: + { + char **ss = (char **) zalloc(2 * sizeof(char *)); + + ss[0] = val; + ss[1] = NULL; + setarrvalue(v, ss); + } + break; + case PM_HASHED: + { + if (foundparam == NULL) + { + zerr("%s: attempt to set associative array to scalar", + v->pm->node.nam); + zsfree(val); + return; + } + else + foundparam->gsu.s->setfn(foundparam, val); + } + break; + } + if ((!v->pm->env && !(v->pm->node.flags & PM_EXPORTED) && + !(isset(ALLEXPORT) && !(v->pm->node.flags & PM_HASHELEM))) || + (v->pm->node.flags & PM_ARRAY) || v->pm->ename) + return; + export_param(v->pm); +} + +/**/ +void +setnumvalue(Value v, mnumber val) +{ + char buf[BDIGBUFSIZE], *p; + + if (unset(EXECOPT)) + return; + if (v->pm->node.flags & PM_READONLY) { + zerr("read-only variable: %s", v->pm->node.nam); + return; + } + if ((v->pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerr("%s: restricted", v->pm->node.nam); + return; + } + switch (PM_TYPE(v->pm->node.flags)) { + case PM_SCALAR: + case PM_ARRAY: + if ((val.type & MN_INTEGER) || outputradix) { + if (!(val.type & MN_INTEGER)) + val.u.l = (zlong) val.u.d; + p = convbase_underscore(buf, val.u.l, outputradix, + outputunderscore); + } else + p = convfloat_underscore(val.u.d, outputunderscore); + setstrvalue(v, ztrdup(p)); + break; + case PM_INTEGER: + v->pm->gsu.i->setfn(v->pm, (val.type & MN_INTEGER) ? val.u.l : + (zlong) val.u.d); + setstrvalue(v, NULL); + break; + case PM_EFLOAT: + case PM_FFLOAT: + v->pm->gsu.f->setfn(v->pm, (val.type & MN_INTEGER) ? + (double)val.u.l : val.u.d); + setstrvalue(v, NULL); + break; + } +} + +/**/ +mod_export void +setarrvalue(Value v, char **val) +{ + if (unset(EXECOPT)) + return; + if (v->pm->node.flags & PM_READONLY) { + zerr("read-only variable: %s", v->pm->node.nam); + freearray(val); + return; + } + if ((v->pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerr("%s: restricted", v->pm->node.nam); + freearray(val); + return; + } + if (!(PM_TYPE(v->pm->node.flags) & (PM_ARRAY|PM_HASHED))) { + freearray(val); + zerr("%s: attempt to assign array value to non-array", + v->pm->node.nam); + return; + } + if (v->flags & VALFLAG_EMPTY) { + zerr("%s: assignment to invalid subscript range", v->pm->node.nam); + freearray(val); + return; + } + + if (v->start == 0 && v->end == -1) { + if (PM_TYPE(v->pm->node.flags) == PM_HASHED) + arrhashsetfn(v->pm, val, 0); + else + v->pm->gsu.a->setfn(v->pm, val); + } else if (v->start == -1 && v->end == 0 && + PM_TYPE(v->pm->node.flags) == PM_HASHED) { + arrhashsetfn(v->pm, val, ASSPM_AUGMENT); + } else if ((PM_TYPE(v->pm->node.flags) == PM_HASHED)) { + freearray(val); + zerr("%s: attempt to set slice of associative array", + v->pm->node.nam); + return; + } else { + char **const old = v->pm->gsu.a->getfn(v->pm); + char **new; + char **p, **q, **r; /* index variables */ + const int pre_assignment_length = arrlen(old); + int post_assignment_length; + int i; + + q = old; + + if ((v->flags & VALFLAG_INV) && unset(KSHARRAYS)) { + if (v->start > 0) + v->start--; + v->end--; + } + if (v->start < 0) { + v->start += pre_assignment_length; + if (v->start < 0) + v->start = 0; + } + if (v->end < 0) { + v->end += pre_assignment_length + 1; + if (v->end < 0) + v->end = 0; + } + if (v->end < v->start) + v->end = v->start; + + post_assignment_length = v->start + arrlen(val); + if (v->end < pre_assignment_length) { + /* + * Allocate room for array elements between the end of the slice `v' + * and the original array's end. + */ + post_assignment_length += pre_assignment_length - v->end; + } + + if (pre_assignment_length == post_assignment_length + && v->pm->gsu.a->setfn == arrsetfn + /* ... and isn't something that arrsetfn() treats specially */ + && 0 == (v->pm->node.flags & (PM_SPECIAL|PM_UNIQUE)) + && NULL == v->pm->ename) + { + /* v->start is 0-based */ + p = old + v->start; + for (r = val; *r;) { + /* Free previous string */ + zsfree(*p); + /* Give away ownership of the string */ + *p++ = *r++; + } + } else { + /* arr+=( ... ) + * arr[${#arr}+x,...]=( ... ) */ + if (post_assignment_length > pre_assignment_length && + pre_assignment_length <= v->start && + pre_assignment_length > 0 && + v->pm->gsu.a->setfn == arrsetfn) + { + p = new = (char **) zrealloc(old, sizeof(char *) + * (post_assignment_length + 1)); + + p += pre_assignment_length; /* after old elements */ + + /* Consider 1 < 0, case for a=( 1 ); a[1,..] = + * 1 < 1, case for a=( 1 ); a[2,..] = */ + if (pre_assignment_length < v->start) { + for (i = pre_assignment_length; i < v->start; i++) { + *p++ = ztrdup(""); + } + } + + for (r = val; *r;) { + /* Give away ownership of the string */ + *p++ = *r++; + } + + /* v->end doesn't matter: + * a=( 1 2 ); a[4,100]=( a b ); echo "${(q@)a}" + * 1 2 '' a b */ + *p = NULL; + + v->pm->u.arr = NULL; + v->pm->gsu.a->setfn(v->pm, new); + } else { + p = new = (char **) zalloc(sizeof(char *) + * (post_assignment_length + 1)); + for (i = 0; i < v->start; i++) + *p++ = i < pre_assignment_length ? ztrdup(*q++) : ztrdup(""); + for (r = val; *r;) { + /* Give away ownership of the string */ + *p++ = *r++; + } + if (v->end < pre_assignment_length) + for (q = old + v->end; *q;) + *p++ = ztrdup(*q++); + *p = NULL; + + v->pm->gsu.a->setfn(v->pm, new); + } + + DPUTS2(p - new != post_assignment_length, "setarrvalue: wrong allocation: %d 1= %lu", + post_assignment_length, (unsigned long)(p - new)); + } + + /* Ownership of all strings has been + * given away, can plainly free */ + free(val); + } +} + +/* Retrieve an integer parameter */ + +/**/ +mod_export zlong +getiparam(char *s) +{ + struct value vbuf; + Value v; + + if (!(v = getvalue(&vbuf, &s, 1))) + return 0; + return getintvalue(v); +} + +/* Retrieve a numerical parameter, either integer or floating */ + +/**/ +mnumber +getnparam(char *s) +{ + struct value vbuf; + Value v; + + if (!(v = getvalue(&vbuf, &s, 1))) { + mnumber mn; + mn.type = MN_INTEGER; + mn.u.l = 0; + return mn; + } + return getnumvalue(v); +} + +/* Retrieve a scalar (string) parameter */ + +/**/ +mod_export char * +getsparam(char *s) +{ + struct value vbuf; + Value v; + + if (!(v = getvalue(&vbuf, &s, 0))) + return NULL; + return getstrvalue(v); +} + +/**/ +mod_export char * +getsparam_u(char *s) +{ + if ((s = getsparam(s))) + return unmetafy(s, NULL); + return s; +} + +/* Retrieve an array parameter */ + +/**/ +mod_export char ** +getaparam(char *s) +{ + struct value vbuf; + Value v; + + if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) && + PM_TYPE(v->pm->node.flags) == PM_ARRAY) + return v->pm->gsu.a->getfn(v->pm); + return NULL; +} + +/* Retrieve an assoc array parameter as an array */ + +/**/ +mod_export char ** +gethparam(char *s) +{ + struct value vbuf; + Value v; + + if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) && + PM_TYPE(v->pm->node.flags) == PM_HASHED) + return paramvalarr(v->pm->gsu.h->getfn(v->pm), SCANPM_WANTVALS); + return NULL; +} + +/* Retrieve the keys of an assoc array parameter as an array */ + +/**/ +mod_export char ** +gethkparam(char *s) +{ + struct value vbuf; + Value v; + + if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) && + PM_TYPE(v->pm->node.flags) == PM_HASHED) + return paramvalarr(v->pm->gsu.h->getfn(v->pm), SCANPM_WANTKEYS); + return NULL; +} + +/* + * Function behind WARNCREATEGLOBAL and WARNNESTEDVAR option. + * + * For WARNNESTEDVAR: + * Called when the variable is created. + * Apply heuristics to see if this variable was just created + * globally but in a local context. + * + * For WARNNESTEDVAR: + * Called when the variable already exists and is set. + * Apply heuristics to see if this variable is setting + * a variable that was created in a less nested function + * or globally. + */ + +/**/ +static void +check_warn_pm(Param pm, const char *pmtype, int created, + int may_warn_about_nested_vars) +{ + Funcstack i; + + if (!may_warn_about_nested_vars && !created) + return; + + if (created && isset(WARNCREATEGLOBAL)) { + if (locallevel <= forklevel || pm->level != 0) + return; + } else if (!created && isset(WARNNESTEDVAR)) { + if (pm->level >= locallevel) + return; + } else + return; + + if (pm->node.flags & PM_SPECIAL) + return; + + for (i = funcstack; i; i = i->prev) { + if (i->tp == FS_FUNC) { + char *msg; + DPUTS(!i->name, "funcstack entry with no name"); + msg = created ? + "%s parameter %s created globally in function %s" : + "%s parameter %s set in enclosing scope in function %s"; + zwarn(msg, pmtype, pm->node.nam, i->name); + break; + } + } +} + +/**/ +mod_export Param +assignsparam(char *s, char *val, int flags) +{ + struct value vbuf; + Value v; + char *t = s; + char *ss, *copy, *var; + size_t lvar; + mnumber lhs, rhs; + int sstart, created = 0; + + if (!isident(s)) { + zerr("not an identifier: %s", s); + zsfree(val); + errflag |= ERRFLAG_ERROR; + return NULL; + } + queue_signals(); + if ((ss = strchr(s, '['))) { + *ss = '\0'; + if (!(v = getvalue(&vbuf, &s, 1))) { + createparam(t, PM_ARRAY); + created = 1; + } else { + if (v->pm->node.flags & PM_READONLY) { + zerr("read-only variable: %s", v->pm->node.nam); + *ss = '['; + zsfree(val); + unqueue_signals(); + return NULL; + } + /* + * Parameter defined here is a temporary bogus one. + * Don't warn about anything. + */ + flags &= ~ASSPM_WARN; + } + *ss = '['; + v = NULL; + } else { + if (!(v = getvalue(&vbuf, &s, 1))) { + createparam(t, PM_SCALAR); + created = 1; + } else if ((((v->pm->node.flags & PM_ARRAY) && !(flags & ASSPM_AUGMENT)) || + (v->pm->node.flags & PM_HASHED)) && + !(v->pm->node.flags & (PM_SPECIAL|PM_TIED)) && + unset(KSHARRAYS)) { + unsetparam(t); + createparam(t, PM_SCALAR); + /* not regarded as a new creation */ + v = NULL; + } + } + if (!v && !(v = getvalue(&vbuf, &t, 1))) { + unqueue_signals(); + zsfree(val); + /* errflag |= ERRFLAG_ERROR; */ + return NULL; + } + if (flags & ASSPM_WARN) + check_warn_pm(v->pm, "scalar", created, 1); + if (flags & ASSPM_AUGMENT) { + if (v->start == 0 && v->end == -1) { + switch (PM_TYPE(v->pm->node.flags)) { + case PM_SCALAR: + v->start = INT_MAX; /* just append to scalar value */ + break; + case PM_INTEGER: + case PM_EFLOAT: + case PM_FFLOAT: + rhs = matheval(val); + lhs = getnumvalue(v); + if (lhs.type == MN_FLOAT) { + if ((rhs.type) == MN_FLOAT) + lhs.u.d = lhs.u.d + rhs.u.d; + else + lhs.u.d = lhs.u.d + (double)rhs.u.l; + } else { + if ((rhs.type) == MN_INTEGER) + lhs.u.l = lhs.u.l + rhs.u.l; + else + lhs.u.l = lhs.u.l + (zlong)rhs.u.d; + } + setnumvalue(v, lhs); + unqueue_signals(); + zsfree(val); + return v->pm; /* avoid later setstrvalue() call */ + case PM_ARRAY: + if (unset(KSHARRAYS)) { + v->start = arrlen(v->pm->gsu.a->getfn(v->pm)); + v->end = v->start + 1; + } else { + /* ksh appends scalar to first element */ + v->end = 1; + goto kshappend; + } + break; + } + } else { + switch (PM_TYPE(v->pm->node.flags)) { + case PM_SCALAR: + if (v->end > 0) + v->start = v->end; + else + v->start = v->end = strlen(v->pm->gsu.s->getfn(v->pm)) + + v->end + 1; + break; + case PM_INTEGER: + case PM_EFLOAT: + case PM_FFLOAT: + unqueue_signals(); + zerr("attempt to add to slice of a numeric variable"); + zsfree(val); + return NULL; + case PM_ARRAY: + kshappend: + /* treat slice as the end element */ + v->start = sstart = v->end > 0 ? v->end - 1 : v->end; + v->isarr = 0; + var = getstrvalue(v); + v->start = sstart; + copy = val; + lvar = strlen(var); + val = (char *)zalloc(lvar + strlen(val) + 1); + strcpy(val, var); + strcpy(val + lvar, copy); + zsfree(copy); + break; + } + } + } + + assignstrvalue(v, val, flags); + unqueue_signals(); + return v->pm; +} + +/**/ +mod_export Param +setsparam(char *s, char *val) +{ + return assignsparam(s, val, ASSPM_WARN); +} + +/**/ +mod_export Param +assignaparam(char *s, char **val, int flags) +{ + struct value vbuf; + Value v; + char *t = s; + char *ss; + int created = 0; + int may_warn_about_nested_vars = 1; + + if (!isident(s)) { + zerr("not an identifier: %s", s); + freearray(val); + errflag |= ERRFLAG_ERROR; + return NULL; + } + queue_signals(); + if ((ss = strchr(s, '['))) { + *ss = '\0'; + if (!(v = getvalue(&vbuf, &s, 1))) { + createparam(t, PM_ARRAY); + created = 1; + } else { + may_warn_about_nested_vars = 0; + } + *ss = '['; + if (v && PM_TYPE(v->pm->node.flags) == PM_HASHED) { + unqueue_signals(); + zerr("%s: attempt to set slice of associative array", + v->pm->node.nam); + freearray(val); + errflag |= ERRFLAG_ERROR; + return NULL; + } + v = NULL; + } else { + if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) { + createparam(t, PM_ARRAY); + created = 1; + } else if (!(PM_TYPE(v->pm->node.flags) & (PM_ARRAY|PM_HASHED)) && + !(v->pm->node.flags & (PM_SPECIAL|PM_TIED))) { + int uniq = v->pm->node.flags & PM_UNIQUE; + if (flags & ASSPM_AUGMENT) { + /* insert old value at the beginning of the val array */ + char **new; + int lv = arrlen(val); + + new = (char **) zalloc(sizeof(char *) * (lv + 2)); + *new = ztrdup(getstrvalue(v)); + memcpy(new+1, val, sizeof(char *) * (lv + 1)); + free(val); + val = new; + } + unsetparam(t); + createparam(t, PM_ARRAY | uniq); + v = NULL; + } + } + if (!v) + if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING))) { + unqueue_signals(); + freearray(val); + /* errflag |= ERRFLAG_ERROR; */ + return NULL; + } + + if (flags & ASSPM_WARN) + check_warn_pm(v->pm, "array", created, may_warn_about_nested_vars); + + /* + * At this point, we may have array entries consisting of + * - a Marker element --- normally allocated array entry but + * with just Marker char and null + * - an array index element --- as normal for associative array, + * but non-standard for normal array which we handle now. + * - a value for the indexed element. + * This only applies if the flag ASSPM_KEY_VALUE is passed in, + * indicating prefork() detected this syntax. + * + * For associative arrays we just junk the Marker elements. + */ + if (flags & ASSPM_KEY_VALUE) { + char **aptr; + if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) { + /* + * This is an ordinary array with key / value pairs. + */ + int maxlen, origlen, nextind; + char **fullval, **origptr; + zlong *subscripts = (zlong *)zhalloc(arrlen(val) * sizeof(zlong)); + zlong *iptr = subscripts; + if (flags & ASSPM_AUGMENT) { + origptr = v->pm->gsu.a->getfn(v->pm); + maxlen = origlen = arrlen(origptr); + } else { + maxlen = origlen = 0; + origptr = NULL; + } + nextind = 0; + for (aptr = val; *aptr; ) { + if (**aptr == Marker) { + *iptr = mathevali(*++aptr); + if (*iptr < 0 || + (!isset(KSHARRAYS) && *iptr == 0)) { + unqueue_signals(); + zerr("bad subscript for direct array assignment: %s", *aptr); + freearray(val); + return NULL; + } + if (!isset(KSHARRAYS)) + --*iptr; + nextind = *iptr + 1; + ++iptr; + aptr += 2; + } else { + ++nextind; + ++aptr; + } + if (nextind > maxlen) + maxlen = nextind; + } + fullval = zshcalloc((maxlen+1) * sizeof(char *)); + if (!fullval) { + zerr("array too large"); + freearray(val); + return NULL; + } + fullval[maxlen] = NULL; + if (flags & ASSPM_AUGMENT) { + char **srcptr = origptr; + for (aptr = fullval; aptr <= fullval + origlen; aptr++) { + *aptr = ztrdup(*srcptr); + srcptr++; + } + } + iptr = subscripts; + nextind = 0; + for (aptr = val; *aptr; ++aptr) { + char *old; + if (**aptr == Marker) { + int augment = ((*aptr)[1] == '+'); + zsfree(*aptr); + zsfree(*++aptr); /* Index, no longer needed */ + old = fullval[*iptr]; + if (augment && old) { + fullval[*iptr] = bicat(old, *++aptr); + zsfree(*aptr); + } else { + fullval[*iptr] = *++aptr; + } + nextind = *iptr + 1; + ++iptr; + } else { + old = fullval[nextind]; + fullval[nextind] = *aptr; + ++nextind; + } + if (old) + zsfree(old); + /* aptr now on value in both cases */ + } + if (*aptr) { /* Shouldn't be possible */ + DPUTS(1, "Extra element in key / value array"); + zsfree(*aptr); + } + free(val); + for (aptr = fullval; aptr < fullval + maxlen; aptr++) { + /* + * Remember we don't have sparse arrays but and they're null + * terminated --- so any value we don't set has to be an + * empty string. + */ + if (!*aptr) + *aptr = ztrdup(""); + } + setarrvalue(v, fullval); + unqueue_signals(); + return v->pm; + } else if (PM_TYPE(v->pm->node.flags & PM_HASHED)) { + /* + * We strictly enforce [key]=value syntax for associative + * arrays. Marker can only indicate a Marker / key / value + * triad; it cannot be there by accident. + * + * It's too inefficient to strip Markers here, and they + * can't be there in the other form --- so just ignore + * them willy nilly lower down. + */ + for (aptr = val; *aptr; aptr += 3) { + if (**aptr != Marker) { + unqueue_signals(); + freearray(val); + zerr("bad [key]=value syntax for associative array"); + return NULL; + } + } + } else { + unqueue_signals(); + freearray(val); + zerr("invalid use of [key]=value assignment syntax"); + return NULL; + } + } + + if (flags & ASSPM_AUGMENT) { + if (v->start == 0 && v->end == -1) { + if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) { + v->start = arrlen(v->pm->gsu.a->getfn(v->pm)); + v->end = v->start + 1; + } else if (PM_TYPE(v->pm->node.flags) & PM_HASHED) + v->start = -1, v->end = 0; + } else { + if (v->end > 0) + v->start = v->end--; + else if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) { + v->end = arrlen(v->pm->gsu.a->getfn(v->pm)) + v->end; + v->start = v->end + 1; + } + } + } + + setarrvalue(v, val); + unqueue_signals(); + return v->pm; +} + + +/**/ +mod_export Param +setaparam(char *s, char **aval) +{ + return assignaparam(s, aval, ASSPM_WARN); +} + +/**/ +mod_export Param +sethparam(char *s, char **val) +{ + struct value vbuf; + Value v; + char *t = s; + int checkcreate = 0; + + if (!isident(s)) { + zerr("not an identifier: %s", s); + freearray(val); + errflag |= ERRFLAG_ERROR; + return NULL; + } + if (strchr(s, '[')) { + freearray(val); + zerr("nested associative arrays not yet supported"); + errflag |= ERRFLAG_ERROR; + return NULL; + } + if (unset(EXECOPT)) + return NULL; + queue_signals(); + if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) { + createparam(t, PM_HASHED); + checkcreate = 1; + } else if (!(PM_TYPE(v->pm->node.flags) & PM_HASHED)) { + if (!(v->pm->node.flags & PM_SPECIAL)) { + unsetparam(t); + /* no WARNCREATEGLOBAL check here as parameter already existed */ + createparam(t, PM_HASHED); + v = NULL; + } else { + zerr("%s: can't change type of a special parameter", t); + unqueue_signals(); + return NULL; + } + } + if (!v) + if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING))) { + unqueue_signals(); + /* errflag |= ERRFLAG_ERROR; */ + return NULL; + } + check_warn_pm(v->pm, "associative array", checkcreate, 1); + setarrvalue(v, val); + unqueue_signals(); + return v->pm; +} + + +/* + * Set a generic shell number, floating point or integer. + * Option to warn on setting. + */ + +/**/ +mod_export Param +assignnparam(char *s, mnumber val, int flags) +{ + struct value vbuf; + Value v; + char *t = s, *ss; + Param pm; + int was_unset = 0; + + if (!isident(s)) { + zerr("not an identifier: %s", s); + errflag |= ERRFLAG_ERROR; + return NULL; + } + if (unset(EXECOPT)) + return NULL; + queue_signals(); + ss = strchr(s, '['); + v = getvalue(&vbuf, &s, 1); + if (v && (v->pm->node.flags & (PM_ARRAY|PM_HASHED)) && + !(v->pm->node.flags & (PM_SPECIAL|PM_TIED)) && + /* + * not sure what KSHARRAYS has got to do with this... + * copied this from assignsparam(). + */ + unset(KSHARRAYS) && !ss) { + unsetparam_pm(v->pm, 0, 1); + was_unset = 1; + s = t; + v = NULL; + } + if (!v) { + /* s has been updated by getvalue, so check again */ + ss = strchr(s, '['); + if (ss) + *ss = '\0'; + pm = createparam(t, ss ? PM_ARRAY : + isset(POSIXIDENTIFIERS) ? PM_SCALAR : + (val.type & MN_INTEGER) ? PM_INTEGER : PM_FFLOAT); + if (!pm) + pm = (Param) paramtab->getnode(paramtab, t); + DPUTS(!pm, "BUG: parameter not created"); + if (ss) { + *ss = '['; + } else if (val.type & MN_INTEGER) { + pm->base = outputradix; + } + if (!(v = getvalue(&vbuf, &t, 1))) { + DPUTS(!v, "BUG: value not found for new parameter"); + /* errflag |= ERRFLAG_ERROR; */ + unqueue_signals(); + return NULL; + } + if (flags & ASSPM_WARN) + check_warn_pm(v->pm, "numeric", !was_unset, 1); + } else { + if (flags & ASSPM_WARN) + check_warn_pm(v->pm, "numeric", 0, 1); + } + setnumvalue(v, val); + unqueue_signals(); + return v->pm; +} + +/* + * Set a generic shell number, floating point or integer. + * Warn on setting based on option. + */ + +/**/ +mod_export Param +setnparam(char *s, mnumber val) +{ + return assignnparam(s, val, ASSPM_WARN); +} + +/* Simplified interface to assignnparam */ + +/**/ +mod_export Param +assigniparam(char *s, zlong val, int flags) +{ + mnumber mnval; + mnval.type = MN_INTEGER; + mnval.u.l = val; + return assignnparam(s, mnval, flags); +} + +/* Simplified interface to setnparam */ + +/**/ +mod_export Param +setiparam(char *s, zlong val) +{ + mnumber mnval; + mnval.type = MN_INTEGER; + mnval.u.l = val; + return assignnparam(s, mnval, ASSPM_WARN); +} + +/* + * Set an integer parameter without forcing creation of an integer type. + * This is useful if the integer is going to be set to a parmaeter which + * would usually be scalar but may not exist. + */ + +/**/ +mod_export Param +setiparam_no_convert(char *s, zlong val) +{ + /* + * If the target is already an integer, thisgets converted + * back. Low technology rules. + */ + char buf[BDIGBUFSIZE]; + convbase(buf, val, 10); + return assignsparam(s, ztrdup(buf), ASSPM_WARN); +} + +/* Unset a parameter */ + +/**/ +mod_export void +unsetparam(char *s) +{ + Param pm; + + queue_signals(); + if ((pm = (Param) (paramtab == realparamtab ? + /* getnode2() to avoid autoloading */ + paramtab->getnode2(paramtab, s) : + paramtab->getnode(paramtab, s)))) + unsetparam_pm(pm, 0, 1); + unqueue_signals(); +} + +/* Unset a parameter + * + * altflag: if true, don't remove pm->ename from the environment + * exp: See stdunsetfn() + */ + +/**/ +mod_export int +unsetparam_pm(Param pm, int altflag, int exp) +{ + Param oldpm, altpm; + char *altremove; + + if ((pm->node.flags & PM_READONLY) && pm->level <= locallevel) { + zerr("read-only variable: %s", pm->node.nam); + return 1; + } + if ((pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerr("%s: restricted", pm->node.nam); + return 1; + } + + if (pm->ename && !altflag) + altremove = ztrdup(pm->ename); + else + altremove = NULL; + + if (!(pm->node.flags & PM_UNSET)) + pm->gsu.s->unsetfn(pm, exp); + if (pm->env) + delenv(pm); + + /* remove it under its alternate name if necessary */ + if (altremove) { + altpm = (Param) paramtab->getnode(paramtab, altremove); + /* tied parameters are at the same local level as each other */ + oldpm = NULL; + while (altpm && altpm->level > pm->level) { + /* param under alternate name hidden by a local */ + oldpm = altpm; + altpm = altpm->old; + } + if (altpm) { + if (oldpm && !altpm->level) { + oldpm->old = NULL; + /* fudge things so removenode isn't called */ + altpm->level = 1; + } + unsetparam_pm(altpm, 1, exp); + } + + zsfree(altremove); + } + + /* + * If this was a local variable, we need to keep the old + * struct so that it is resurrected at the right level. + * This is partly because when an array/scalar value is set + * and the parameter used to be the other sort, unsetparam() + * is called. Beyond that, there is an ambiguity: should + * foo() { local bar; unset bar; } make the global bar + * available or not? The following makes the answer "no". + * + * Some specials, such as those used in zle, still need removing + * from the parameter table; they have the PM_REMOVABLE flag. + */ + if ((pm->level && locallevel >= pm->level) || + (pm->node.flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL) + return 0; + + /* remove parameter node from table */ + paramtab->removenode(paramtab, pm->node.nam); + + if (pm->old) { + oldpm = pm->old; + paramtab->addnode(paramtab, oldpm->node.nam, oldpm); + if ((PM_TYPE(oldpm->node.flags) == PM_SCALAR) && + !(pm->node.flags & PM_HASHELEM) && + (oldpm->node.flags & PM_NAMEDDIR) && + oldpm->gsu.s == &stdscalar_gsu) + adduserdir(oldpm->node.nam, oldpm->u.str, 0, 0); + if (oldpm->node.flags & PM_EXPORTED) { + /* + * Re-export the old value which we removed in typeset_single(). + * I don't think we need to test for ALL_EXPORT here, since if + * it was used to export the parameter originally the parameter + * should still have the PM_EXPORTED flag. + */ + export_param(oldpm); + } + } + + paramtab->freenode(&pm->node); /* free parameter node */ + + return 0; +} + +/* Standard function to unset a parameter. This is mostly delegated to * + * the specific set function. + * + * This could usefully be made type-specific, but then we need + * to be more careful when calling the unset method directly. + * + * The "exp"licit parameter should be nonzero for assignments and the + * unset command, and zero for implicit unset (e.g., end of scope). + * Currently this is used only by some modules. + */ + +/**/ +mod_export void +stdunsetfn(Param pm, UNUSED(int exp)) +{ + switch (PM_TYPE(pm->node.flags)) { + case PM_SCALAR: + if (pm->gsu.s->setfn) + pm->gsu.s->setfn(pm, NULL); + break; + + case PM_ARRAY: + if (pm->gsu.a->setfn) + pm->gsu.a->setfn(pm, NULL); + break; + + case PM_HASHED: + if (pm->gsu.h->setfn) + pm->gsu.h->setfn(pm, NULL); + break; + + default: + if (!(pm->node.flags & PM_SPECIAL)) + pm->u.str = NULL; + break; + } + if ((pm->node.flags & (PM_SPECIAL|PM_TIED)) == PM_TIED) { + if (pm->ename) { + zsfree(pm->ename); + pm->ename = NULL; + } + pm->node.flags &= ~PM_TIED; + } + pm->node.flags |= PM_UNSET; +} + +/* Function to get value of an integer parameter */ + +/**/ +mod_export zlong +intgetfn(Param pm) +{ + return pm->u.val; +} + +/* Function to set value of an integer parameter */ + +/**/ +static void +intsetfn(Param pm, zlong x) +{ + pm->u.val = x; +} + +/* Function to get value of a floating point parameter */ + +/**/ +static double +floatgetfn(Param pm) +{ + return pm->u.dval; +} + +/* Function to set value of an integer parameter */ + +/**/ +static void +floatsetfn(Param pm, double x) +{ + pm->u.dval = x; +} + +/* Function to get value of a scalar (string) parameter */ + +/**/ +mod_export char * +strgetfn(Param pm) +{ + return pm->u.str ? pm->u.str : (char *) hcalloc(1); +} + +/* Function to set value of a scalar (string) parameter */ + +/**/ +mod_export void +strsetfn(Param pm, char *x) +{ + zsfree(pm->u.str); + pm->u.str = x; + if (!(pm->node.flags & PM_HASHELEM) && + ((pm->node.flags & PM_NAMEDDIR) || isset(AUTONAMEDIRS))) { + pm->node.flags |= PM_NAMEDDIR; + adduserdir(pm->node.nam, x, 0, 0); + } + /* If you update this function, you may need to update the + * `Implement remainder of strsetfn' block in assignstrvalue(). */ +} + +/* Function to get value of an array parameter */ + +static char *nullarray = NULL; + +/**/ +char ** +arrgetfn(Param pm) +{ + return pm->u.arr ? pm->u.arr : &nullarray; +} + +/* Function to set value of an array parameter */ + +/**/ +mod_export void +arrsetfn(Param pm, char **x) +{ + if (pm->u.arr && pm->u.arr != x) + freearray(pm->u.arr); + if (pm->node.flags & PM_UNIQUE) + uniqarray(x); + pm->u.arr = x; + /* Arrays tied to colon-arrays may need to fix the environment */ + if (pm->ename && x) + arrfixenv(pm->ename, x); + /* If you extend this function, update the list of conditions in + * setarrvalue(). */ +} + +/* Function to get value of an association parameter */ + +/**/ +mod_export HashTable +hashgetfn(Param pm) +{ + return pm->u.hash; +} + +/* Function to set value of an association parameter */ + +/**/ +mod_export void +hashsetfn(Param pm, HashTable x) +{ + if (pm->u.hash && pm->u.hash != x) + deleteparamtable(pm->u.hash); + pm->u.hash = x; +} + +/* Function to dispose of setting of an unsettable hash */ + +/**/ +mod_export void +nullsethashfn(UNUSED(Param pm), HashTable x) +{ + deleteparamtable(x); +} + +/* Function to set value of an association parameter using key/value pairs */ + +/**/ +static void +arrhashsetfn(Param pm, char **val, int flags) +{ + /* Best not to shortcut this by using the existing hash table, * + * since that could cause trouble for special hashes. This way, * + * it's up to pm->gsu.h->setfn() what to do. */ + int alen = 0; + HashTable opmtab = paramtab, ht = 0; + char **aptr; + Value v = (Value) hcalloc(sizeof *v); + v->end = -1; + + for (aptr = val; *aptr; ++aptr) { + if (**aptr != Marker) + ++alen; + } + + if (alen % 2) { + freearray(val); + zerr("bad set of key/value pairs for associative array"); + return; + } + if (flags & ASSPM_AUGMENT) { + ht = paramtab = pm->gsu.h->getfn(pm); + } + if (alen && (!(flags & ASSPM_AUGMENT) || !paramtab)) { + ht = paramtab = newparamtable(17, pm->node.nam); + } + for (aptr = val; *aptr; ) { + int eltflags = 0; + if (**aptr == Marker) { + /* Either all elements have Marker or none. Checked in caller. */ + if ((*aptr)[1] == '+') { + /* Actually, assignstrvalue currently doesn't handle this... */ + eltflags = ASSPM_AUGMENT; + /* ...so we'll use the trick from setsparam(). */ + v->start = INT_MAX; + } else { + v->start = 0; + } + v->end = -1; + zsfree(*aptr++); + } + /* The parameter name is ztrdup'd... */ + v->pm = createparam(*aptr, PM_SCALAR|PM_UNSET); + /* + * createparam() doesn't return anything if the parameter + * already existed. + */ + if (!v->pm) + v->pm = (Param) paramtab->getnode(paramtab, *aptr); + zsfree(*aptr++); + /* ...but we can use the value without copying. */ + assignstrvalue(v, *aptr++, eltflags); + } + paramtab = opmtab; + pm->gsu.h->setfn(pm, ht); + free(val); /* not freearray() */ +} + +/* + * These functions are used as the set function for special parameters that + * cannot be set by the user. The set is incomplete as the only such + * parameters are scalar and integer. + */ + +/**/ +mod_export void +nullstrsetfn(UNUSED(Param pm), char *x) +{ + zsfree(x); +} + +/**/ +mod_export void +nullintsetfn(UNUSED(Param pm), UNUSED(zlong x)) +{} + +/**/ +mod_export void +nullunsetfn(UNUSED(Param pm), UNUSED(int exp)) +{} + + +/* Function to get value of generic special integer * + * parameter. data is pointer to global variable * + * containing the integer value. */ + +/**/ +mod_export zlong +intvargetfn(Param pm) +{ + return *pm->u.valptr; +} + +/* Function to set value of generic special integer * + * parameter. data is pointer to global variable * + * where the value is to be stored. */ + +/**/ +mod_export void +intvarsetfn(Param pm, zlong x) +{ + *pm->u.valptr = x; +} + +/* Function to set value of any ZLE-related integer * + * parameter. data is pointer to global variable * + * where the value is to be stored. */ + +/**/ +void +zlevarsetfn(Param pm, zlong x) +{ + zlong *p = pm->u.valptr; + + *p = x; + if (p == &zterm_lines || p == &zterm_columns) + adjustwinsize(2 + (p == &zterm_columns)); +} + + +/* Implements gsu_integer.unsetfn for ZLE_RPROMPT_INDENT; see stdunsetfn() */ + +static void +rprompt_indent_unsetfn(Param pm, int exp) +{ + stdunsetfn(pm, exp); + rprompt_indent = 1; /* Keep this in sync with init_term() */ +} + +/* Function to set value of generic special scalar * + * parameter. data is pointer to a character pointer * + * representing the scalar (string). */ + +/**/ +mod_export void +strvarsetfn(Param pm, char *x) +{ + char **q = ((char **)pm->u.data); + + zsfree(*q); + *q = x; +} + +/* Function to get value of generic special scalar * + * parameter. data is pointer to a character pointer * + * representing the scalar (string). */ + +/**/ +mod_export char * +strvargetfn(Param pm) +{ + char *s = *((char **)pm->u.data); + + if (!s) + return hcalloc(1); + return s; +} + +/* Function to get value of generic special array * + * parameter. data is a pointer to the pointer to * + * a pointer (a pointer to a variable length array * + * of pointers). */ + +/**/ +mod_export char ** +arrvargetfn(Param pm) +{ + char **arrptr = *((char ***)pm->u.data); + + return arrptr ? arrptr : &nullarray; +} + +/* Function to set value of generic special array parameter. * + * data is pointer to a variable length array of pointers which * + * represents this array of scalars (strings). If pm->ename is * + * non NULL, then it is a colon separated environment variable * + * version of this array which will need to be updated. */ + +/**/ +mod_export void +arrvarsetfn(Param pm, char **x) +{ + char ***dptr = (char ***)pm->u.data; + + if (*dptr != x) + freearray(*dptr); + if (pm->node.flags & PM_UNIQUE) + uniqarray(x); + /* + * Special tied arrays point to variables accessible in other + * ways which need to be set to NULL. We can't do this + * with user tied variables since we can leak memory. + */ + if ((pm->node.flags & PM_SPECIAL) && !x) + *dptr = mkarray(NULL); + else + *dptr = x; + if (pm->ename) { + if (x) + arrfixenv(pm->ename, x); + else if (*dptr == path) + pathchecked = path; + } +} + +/**/ +char * +colonarrgetfn(Param pm) +{ + char ***dptr = (char ***)pm->u.data; + return *dptr ? zjoin(*dptr, ':', 1) : ""; +} + +/**/ +void +colonarrsetfn(Param pm, char *x) +{ + char ***dptr = (char ***)pm->u.data; + /* + * We have to make sure this is never NULL, since that + * can cause problems. + */ + if (*dptr) + freearray(*dptr); + if (x) + *dptr = colonsplit(x, pm->node.flags & PM_UNIQUE); + else + *dptr = mkarray(NULL); + arrfixenv(pm->node.nam, *dptr); + zsfree(x); +} + +/**/ +char * +tiedarrgetfn(Param pm) +{ + struct tieddata *dptr = (struct tieddata *)pm->u.data; + return *dptr->arrptr ? zjoin(*dptr->arrptr, STOUC(dptr->joinchar), 1) : ""; +} + +/**/ +void +tiedarrsetfn(Param pm, char *x) +{ + struct tieddata *dptr = (struct tieddata *)pm->u.data; + + if (*dptr->arrptr) + freearray(*dptr->arrptr); + if (x) { + char sepbuf[3]; + if (imeta(dptr->joinchar)) + { + sepbuf[0] = Meta; + sepbuf[1] = dptr->joinchar ^ 32; + sepbuf[2] = '\0'; + } + else + { + sepbuf[0] = dptr->joinchar; + sepbuf[1] = '\0'; + } + *dptr->arrptr = sepsplit(x, sepbuf, 0, 0); + if (pm->node.flags & PM_UNIQUE) + uniqarray(*dptr->arrptr); + zsfree(x); + } else + *dptr->arrptr = NULL; + if (pm->ename) + arrfixenv(pm->node.nam, *dptr->arrptr); +} + +/**/ +void +tiedarrunsetfn(Param pm, UNUSED(int exp)) +{ + /* + * Special unset function because we allocated a struct tieddata + * in typeset_single to hold the special data which we now + * need to delete. + */ + pm->gsu.s->setfn(pm, NULL); + zfree(pm->u.data, sizeof(struct tieddata)); + /* paranoia -- shouldn't need these, but in case we reuse the struct... */ + pm->u.data = NULL; + zsfree(pm->ename); + pm->ename = NULL; + pm->node.flags &= ~PM_TIED; + pm->node.flags |= PM_UNSET; +} + +/**/ +static void +simple_arrayuniq(char **x, int freeok) +{ + char **t, **p = x; + char *hole = ""; + + /* Find duplicates and replace them with holes */ + while (*++p) + for (t = x; t < p; t++) + if (*t != hole && !strcmp(*p, *t)) { + if (freeok) + zsfree(*p); + *p = hole; + break; + } + /* Swap non-holes into holes in optimal jumps */ + for (p = t = x; *t != NULL; t++) { + if (*t == hole) { + while (*p == hole) + ++p; + if ((*t = *p) != NULL) + *p++ = hole; + } else if (p == t) + p++; + } + /* Erase all the remaining holes, just in case */ + while (++t < p) + *t = NULL; +} + +/**/ +static void +arrayuniq_freenode(HashNode hn) +{ + (void)hn; +} + +/**/ +HashTable +newuniqtable(zlong size) +{ + HashTable ht = newhashtable((int)size, "arrayuniq", NULL); + /* ??? error checking */ + + ht->hash = hasher; + ht->emptytable = emptyhashtable; + ht->filltable = NULL; + ht->cmpnodes = strcmp; + ht->addnode = addhashnode; + ht->getnode = gethashnode2; + ht->getnode2 = gethashnode2; + ht->removenode = removehashnode; + ht->disablenode = disablehashnode; + ht->enablenode = enablehashnode; + ht->freenode = arrayuniq_freenode; + ht->printnode = NULL; + + return ht; +} + +/**/ +static void +arrayuniq(char **x, int freeok) +{ + char **it, **write_it; + zlong array_size = arrlen(x); + HashTable ht; + + if (array_size == 0) + return; + if (array_size < 10 || !(ht = newuniqtable(array_size + 1))) { + /* fallback to simpler routine */ + simple_arrayuniq(x, freeok); + return; + } + + for (it = x, write_it = x; *it;) { + if (! gethashnode2(ht, *it)) { + HashNode new_node = zhalloc(sizeof(struct hashnode)); + if (!new_node) { + /* Oops, out of heap memory, no way to recover */ + zerr("out of memory in arrayuniq"); + break; + } + (void) addhashnode2(ht, *it, new_node); + *write_it = *it; + if (it != write_it) + *it = NULL; + ++write_it; + } + else { + if (freeok) + zsfree(*it); + *it = NULL; + } + ++it; + } + + deletehashtable(ht); +} + +/**/ +void +uniqarray(char **x) +{ + if (!x || !*x) + return; + arrayuniq(x, !zheapptr(*x)); +} + +/**/ +void +zhuniqarray(char **x) +{ + if (!x || !*x) + return; + arrayuniq(x, 0); +} + +/* Function to get value of special parameter `#' and `ARGC' */ + +/**/ +zlong +poundgetfn(UNUSED(Param pm)) +{ + return arrlen(pparams); +} + +/* Function to get value for special parameter `RANDOM' */ + +/**/ +zlong +randomgetfn(UNUSED(Param pm)) +{ + return rand() & 0x7fff; +} + +/* Function to set value of special parameter `RANDOM' */ + +/**/ +void +randomsetfn(UNUSED(Param pm), zlong v) +{ + srand((unsigned int)v); +} + +/* Function to get value for special parameter `SECONDS' */ + +/**/ +zlong +intsecondsgetfn(UNUSED(Param pm)) +{ + struct timeval now; + struct timezone dummy_tz; + + gettimeofday(&now, &dummy_tz); + + return (zlong)(now.tv_sec - shtimer.tv_sec - + (now.tv_usec < shtimer.tv_usec ? 1 : 0)); +} + +/* Function to set value of special parameter `SECONDS' */ + +/**/ +void +intsecondssetfn(UNUSED(Param pm), zlong x) +{ + struct timeval now; + struct timezone dummy_tz; + zlong diff; + + gettimeofday(&now, &dummy_tz); + diff = (zlong)now.tv_sec - x; + shtimer.tv_sec = diff; + if ((zlong)shtimer.tv_sec != diff) + zwarn("SECONDS truncated on assignment"); + shtimer.tv_usec = now.tv_usec; +} + +/**/ +double +floatsecondsgetfn(UNUSED(Param pm)) +{ + struct timeval now; + struct timezone dummy_tz; + + gettimeofday(&now, &dummy_tz); + + return (double)(now.tv_sec - shtimer.tv_sec) + + (double)(now.tv_usec - shtimer.tv_usec) / 1000000.0; +} + +/**/ +void +floatsecondssetfn(UNUSED(Param pm), double x) +{ + struct timeval now; + struct timezone dummy_tz; + + gettimeofday(&now, &dummy_tz); + shtimer.tv_sec = now.tv_sec - (zlong)x; + shtimer.tv_usec = now.tv_usec - (zlong)((x - (zlong)x) * 1000000.0); +} + +/**/ +double +getrawseconds(void) +{ + return (double)shtimer.tv_sec + (double)shtimer.tv_usec / 1000000.0; +} + +/**/ +void +setrawseconds(double x) +{ + shtimer.tv_sec = (zlong)x; + shtimer.tv_usec = (zlong)((x - (zlong)x) * 1000000.0); +} + +/**/ +int +setsecondstype(Param pm, int on, int off) +{ + int newflags = (pm->node.flags | on) & ~off; + int tp = PM_TYPE(newflags); + /* Only one of the numeric types is allowed. */ + if (tp == PM_EFLOAT || tp == PM_FFLOAT) + { + pm->gsu.f = &floatseconds_gsu; + } + else if (tp == PM_INTEGER) + { + pm->gsu.i = &intseconds_gsu; + } + else + return 1; + pm->node.flags = newflags; + return 0; +} + +/* Function to get value for special parameter `USERNAME' */ + +/**/ +char * +usernamegetfn(UNUSED(Param pm)) +{ + return get_username(); +} + +/* Function to set value of special parameter `USERNAME' */ + +/**/ +void +usernamesetfn(UNUSED(Param pm), char *x) +{ +#if defined(HAVE_SETUID) && defined(HAVE_GETPWNAM) + struct passwd *pswd; + + if (x && (pswd = getpwnam(x)) && (pswd->pw_uid != cached_uid)) { +# ifdef USE_INITGROUPS + initgroups(x, pswd->pw_gid); +# endif + if (setgid(pswd->pw_gid)) + zwarn("failed to change group ID: %e", errno); + else if (setuid(pswd->pw_uid)) + zwarn("failed to change user ID: %e", errno); + else { + zsfree(cached_username); + cached_username = ztrdup(pswd->pw_name); + cached_uid = pswd->pw_uid; + } + } +#endif /* HAVE_SETUID && HAVE_GETPWNAM */ + zsfree(x); +} + +/* Function to get value for special parameter `UID' */ + +/**/ +zlong +uidgetfn(UNUSED(Param pm)) +{ + return getuid(); +} + +/* Function to set value of special parameter `UID' */ + +/**/ +void +uidsetfn(UNUSED(Param pm), zlong x) +{ +#ifdef HAVE_SETUID + if (setuid((uid_t)x)) + zerr("failed to change user ID: %e", errno); +#endif +} + +/* Function to get value for special parameter `EUID' */ + +/**/ +zlong +euidgetfn(UNUSED(Param pm)) +{ + return geteuid(); +} + +/* Function to set value of special parameter `EUID' */ + +/**/ +void +euidsetfn(UNUSED(Param pm), zlong x) +{ +#ifdef HAVE_SETEUID + if (seteuid((uid_t)x)) + zerr("failed to change effective user ID: %e", errno); +#endif +} + +/* Function to get value for special parameter `GID' */ + +/**/ +zlong +gidgetfn(UNUSED(Param pm)) +{ + return getgid(); +} + +/* Function to set value of special parameter `GID' */ + +/**/ +void +gidsetfn(UNUSED(Param pm), zlong x) +{ +#ifdef HAVE_SETUID + if (setgid((gid_t)x)) + zerr("failed to change group ID: %e", errno); +#endif +} + +/* Function to get value for special parameter `EGID' */ + +/**/ +zlong +egidgetfn(UNUSED(Param pm)) +{ + return getegid(); +} + +/* Function to set value of special parameter `EGID' */ + +/**/ +void +egidsetfn(UNUSED(Param pm), zlong x) +{ +#ifdef HAVE_SETEUID + if (setegid((gid_t)x)) + zerr("failed to change effective group ID: %e", errno); +#endif +} + +/**/ +zlong +ttyidlegetfn(UNUSED(Param pm)) +{ + struct stat ttystat; + + if (SHTTY == -1 || fstat(SHTTY, &ttystat)) + return -1; + return time(NULL) - ttystat.st_atime; +} + +/* Function to get value for special parameter `IFS' */ + +/**/ +char * +ifsgetfn(UNUSED(Param pm)) +{ + return ifs; +} + +/* Function to set value of special parameter `IFS' */ + +/**/ +void +ifssetfn(UNUSED(Param pm), char *x) +{ + zsfree(ifs); + ifs = x; + inittyptab(); +} + +/* Functions to set value of special parameters `LANG' and `LC_*' */ + +#ifdef USE_LOCALE +static struct localename { + char *name; + int category; +} lc_names[] = { +#ifdef LC_COLLATE + {"LC_COLLATE", LC_COLLATE}, +#endif +#ifdef LC_CTYPE + {"LC_CTYPE", LC_CTYPE}, +#endif +#ifdef LC_MESSAGES + {"LC_MESSAGES", LC_MESSAGES}, +#endif +#ifdef LC_NUMERIC + {"LC_NUMERIC", LC_NUMERIC}, +#endif +#ifdef LC_TIME + {"LC_TIME", LC_TIME}, +#endif + {NULL, 0} +}; + +/**/ +static void +setlang(char *x) +{ + struct localename *ln; + char *x2; + + if ((x2 = getsparam_u("LC_ALL")) && *x2) + return; + + /* + * Set the global locale to the value passed, but override + * this with any non-empty definitions for specific + * categories. + * + * We only use non-empty definitions because empty values aren't + * valid as locales; when passed to setlocale() they mean "use the + * environment variable", but if that's what we're setting the value + * from this is meaningless. So just all $LANG to show through in + * that case. + */ + setlocale(LC_ALL, x ? unmeta(x) : ""); + queue_signals(); + for (ln = lc_names; ln->name; ln++) + if ((x = getsparam_u(ln->name)) && *x) + setlocale(ln->category, x); + unqueue_signals(); +} + +/**/ +void +lc_allsetfn(Param pm, char *x) +{ + strsetfn(pm, x); + /* + * Treat an empty LC_ALL the same as an unset one, + * namely by using LANG as the default locale but overriding + * that with any LC_* that are set. + */ + if (!x || !*x) { + x = getsparam_u("LANG"); + if (x && *x) { + queue_signals(); + setlang(x); + unqueue_signals(); + } + } + else + setlocale(LC_ALL, unmeta(x)); +} + +/**/ +void +langsetfn(Param pm, char *x) +{ + strsetfn(pm, x); + setlang(unmeta(x)); +} + +/**/ +void +lcsetfn(Param pm, char *x) +{ + char *x2; + struct localename *ln; + + strsetfn(pm, x); + if ((x2 = getsparam("LC_ALL")) && *x2) + return; + queue_signals(); + /* Treat empty LC_* the same as unset. */ + if (!x || !*x) + x = getsparam("LANG"); + + /* + * If we've got no non-empty string at this + * point (after checking $LANG, too), + * we shouldn't bother setting anything. + */ + if (x && *x) { + for (ln = lc_names; ln->name; ln++) + if (!strcmp(ln->name, pm->node.nam)) + setlocale(ln->category, unmeta(x)); + } + unqueue_signals(); +} +#endif /* USE_LOCALE */ + +/* Function to set value for special parameter `0' */ + +/**/ +static void +argzerosetfn(UNUSED(Param pm), char *x) +{ + if (x) { + if (isset(POSIXARGZERO)) + zerr("read-only variable: 0"); + else { + zsfree(argzero); + argzero = ztrdup(x); + } + zsfree(x); + } +} + +/* Function to get value for special parameter `0' */ + +/**/ +static char * +argzerogetfn(UNUSED(Param pm)) +{ + if (isset(POSIXARGZERO)) + return posixzero; + return argzero; +} + +/* Function to get value for special parameter `HISTSIZE' */ + +/**/ +zlong +histsizegetfn(UNUSED(Param pm)) +{ + return histsiz; +} + +/* Function to set value of special parameter `HISTSIZE' */ + +/**/ +void +histsizesetfn(UNUSED(Param pm), zlong v) +{ + if ((histsiz = v) < 1) + histsiz = 1; + resizehistents(); +} + +/* Function to get value for special parameter `SAVEHIST' */ + +/**/ +zlong +savehistsizegetfn(UNUSED(Param pm)) +{ + return savehistsiz; +} + +/* Function to set value of special parameter `SAVEHIST' */ + +/**/ +void +savehistsizesetfn(UNUSED(Param pm), zlong v) +{ + if ((savehistsiz = v) < 0) + savehistsiz = 0; +} + +/* Function to set value for special parameter `ERRNO' */ + +/**/ +void +errnosetfn(UNUSED(Param pm), zlong x) +{ + errno = (int)x; + if ((zlong)errno != x) + zwarn("errno truncated on assignment"); +} + +/* Function to get value for special parameter `ERRNO' */ + +/**/ +zlong +errnogetfn(UNUSED(Param pm)) +{ + return errno; +} + +/* Function to get value for special parameter `KEYBOARD_HACK' */ + +/**/ +char * +keyboardhackgetfn(UNUSED(Param pm)) +{ + static char buf[2]; + + buf[0] = keyboardhackchar; + buf[1] = '\0'; + return buf; +} + + +/* Function to set value of special parameter `KEYBOARD_HACK' */ + +/**/ +void +keyboardhacksetfn(UNUSED(Param pm), char *x) +{ + if (x) { + int len, i; + + unmetafy(x, &len); + if (len > 1) { + len = 1; + zwarn("Only one KEYBOARD_HACK character can be defined"); /* could be changed if needed */ + } + for (i = 0; i < len; i++) { + if (!isascii(STOUC(x[i]))) { + zwarn("KEYBOARD_HACK can only contain ASCII characters"); + return; + } + } + keyboardhackchar = len ? STOUC(x[0]) : '\0'; + free(x); + } else + keyboardhackchar = '\0'; +} + +/* Function to get value for special parameter `histchar' */ + +/**/ +char * +histcharsgetfn(UNUSED(Param pm)) +{ + static char buf[4]; + + buf[0] = bangchar; + buf[1] = hatchar; + buf[2] = hashchar; + buf[3] = '\0'; + return buf; +} + +/* Function to set value of special parameter `histchar' */ + +/**/ +void +histcharssetfn(UNUSED(Param pm), char *x) +{ + if (x) { + int len, i; + + unmetafy(x, &len); + if (len > 3) + len = 3; + for (i = 0; i < len; i++) { + if (!isascii(STOUC(x[i]))) { + zwarn("HISTCHARS can only contain ASCII characters"); + return; + } + } + bangchar = len ? STOUC(x[0]) : '\0'; + hatchar = len > 1 ? STOUC(x[1]) : '\0'; + hashchar = len > 2 ? STOUC(x[2]) : '\0'; + free(x); + } else { + bangchar = '!'; + hashchar = '#'; + hatchar = '^'; + } + inittyptab(); +} + +/* Function to get value for special parameter `HOME' */ + +/**/ +char * +homegetfn(UNUSED(Param pm)) +{ + return home; +} + +/* Function to set value of special parameter `HOME' */ + +/**/ +void +homesetfn(UNUSED(Param pm), char *x) +{ + zsfree(home); + if (x && isset(CHASELINKS) && (home = xsymlink(x, 0))) + zsfree(x); + else + home = x ? x : ztrdup(""); + finddir(NULL); +} + +/* Function to get value for special parameter `WORDCHARS' */ + +/**/ +char * +wordcharsgetfn(UNUSED(Param pm)) +{ + return wordchars; +} + +/* Function to set value of special parameter `WORDCHARS' */ + +/**/ +void +wordcharssetfn(UNUSED(Param pm), char *x) +{ + zsfree(wordchars); + wordchars = x; + inittyptab(); +} + +/* Function to get value for special parameter `_' */ + +/**/ +char * +underscoregetfn(UNUSED(Param pm)) +{ + char *u = dupstring(zunderscore); + + untokenize(u); + return u; +} + +/* Function used when we need to reinitialise the terminal */ + +static void +term_reinit_from_pm(void) +{ + /* If non-interactive, delay setting up term till we need it. */ + if (unset(INTERACTIVE) || !*term) + termflags |= TERM_UNKNOWN; + else + init_term(); +} + +/* Function to get value for special parameter `TERM' */ + +/**/ +char * +termgetfn(UNUSED(Param pm)) +{ + return term; +} + +/* Function to set value of special parameter `TERM' */ + +/**/ +void +termsetfn(UNUSED(Param pm), char *x) +{ + zsfree(term); + term = x ? x : ztrdup(""); + term_reinit_from_pm(); +} + +/* Function to get value of special parameter `TERMINFO' */ + +/**/ +char * +terminfogetfn(UNUSED(Param pm)) +{ + return zsh_terminfo ? zsh_terminfo : dupstring(""); +} + +/* Function to set value of special parameter `TERMINFO' */ + +/**/ +void +terminfosetfn(Param pm, char *x) +{ + zsfree(zsh_terminfo); + zsh_terminfo = x; + + /* + * terminfo relies on the value being exported before + * we reinitialise the terminal. This is a bit inefficient. + */ + if ((pm->node.flags & PM_EXPORTED) && x) + addenv(pm, x); + + term_reinit_from_pm(); +} + +/* Function to get value of special parameter `TERMINFO_DIRS' */ + +/**/ +char * +terminfodirsgetfn(UNUSED(Param pm)) +{ + return zsh_terminfodirs ? zsh_terminfodirs : dupstring(""); +} + +/* Function to set value of special parameter `TERMINFO_DIRS' */ + +/**/ +void +terminfodirssetfn(Param pm, char *x) +{ + zsfree(zsh_terminfodirs); + zsh_terminfodirs = x; + + /* + * terminfo relies on the value being exported before + * we reinitialise the terminal. This is a bit inefficient. + */ + if ((pm->node.flags & PM_EXPORTED) && x) + addenv(pm, x); + + term_reinit_from_pm(); +} +/* Function to get value for special parameter `pipestatus' */ + +/**/ +static char ** +pipestatgetfn(UNUSED(Param pm)) +{ + char **x = (char **) zhalloc((numpipestats + 1) * sizeof(char *)); + char buf[DIGBUFSIZE], **p; + int *q, i; + + for (p = x, q = pipestats, i = numpipestats; i--; p++, q++) { + sprintf(buf, "%d", *q); + *p = dupstring(buf); + } + *p = NULL; + + return x; +} + +/* Function to get value for special parameter `pipestatus' */ + +/**/ +static void +pipestatsetfn(UNUSED(Param pm), char **x) +{ + if (x) { + int i; + + for (i = 0; *x && i < MAX_PIPESTATS; i++, x++) + pipestats[i] = atoi(*x); + numpipestats = i; + } + else + numpipestats = 0; +} + +/**/ +void +arrfixenv(char *s, char **t) +{ + Param pm; + int joinchar; + + if (t == path) + cmdnamtab->emptytable(cmdnamtab); + + pm = (Param) paramtab->getnode(paramtab, s); + + /* + * Only one level of a parameter can be exported. Unless + * ALLEXPORT is set, this must be global. + */ + + if (pm->node.flags & PM_HASHELEM) + return; + + if (isset(ALLEXPORT)) + pm->node.flags |= PM_EXPORTED; + + /* + * Do not "fix" parameters that were not exported + */ + + if (!(pm->node.flags & PM_EXPORTED)) + return; + + if (pm->node.flags & PM_TIED) + joinchar = STOUC(((struct tieddata *)pm->u.data)->joinchar); + else + joinchar = ':'; + + addenv(pm, t ? zjoin(t, joinchar, 1) : ""); +} + + +/**/ +int +zputenv(char *str) +{ + DPUTS(!str, "Attempt to put null string into environment."); +#ifdef USE_SET_UNSET_ENV + /* + * If we are using unsetenv() to remove values from the + * environment, which is the safe thing to do, we + * need to use setenv() to put them there in the first place. + * Unfortunately this is a slightly different interface + * from what zputenv() assumes. + */ + char *ptr; + int ret; + + for (ptr = str; *ptr && STOUC(*ptr) < 128 && *ptr != '='; ptr++) + ; + if (STOUC(*ptr) >= 128) { + /* + * Environment variables not in the portable character + * set are non-standard and we don't really know of + * a use for them. + * + * We'll disable until someone complains. + */ + return 1; + } else if (*ptr) { + *ptr = '\0'; + ret = setenv(str, ptr+1, 1); + *ptr = '='; + } else { + /* safety first */ + DPUTS(1, "bad environment string"); + ret = setenv(str, ptr, 1); + } + return ret; +#else +#ifdef HAVE_PUTENV + return putenv(str); +#else + char **ep; + int num_env; + + + /* First check if there is already an environment * + * variable matching string `name'. */ + if (findenv(str, &num_env)) { + environ[num_env] = str; + } else { + /* Else we have to make room and add it */ + num_env = arrlen(environ); + environ = (char **) zrealloc(environ, (sizeof(char *)) * (num_env + 2)); + + /* Now add it at the end */ + ep = environ + num_env; + *ep = str; + *(ep + 1) = NULL; + } + return 0; +#endif +#endif +} + +/**/ +#ifndef USE_SET_UNSET_ENV +/**/ +static int +findenv(char *name, int *pos) +{ + char **ep, *eq; + int nlen; + + + eq = strchr(name, '='); + nlen = eq ? eq - name : (int)strlen(name); + for (ep = environ; *ep; ep++) + if (!strncmp (*ep, name, nlen) && *((*ep)+nlen) == '=') { + if (pos) + *pos = ep - environ; + return 1; + } + + return 0; +} +/**/ +#endif + +/* Given *name = "foo", it searches the environment for string * + * "foo=bar", and returns a pointer to the beginning of "bar" */ + +/**/ +mod_export char * +zgetenv(char *name) +{ +#ifdef HAVE_GETENV + return getenv(name); +#else + char **ep, *s, *t; + + for (ep = environ; *ep; ep++) { + for (s = *ep, t = name; *s && *s == *t; s++, t++); + if (*s == '=' && !*t) + return s + 1; + } + return NULL; +#endif +} + +/**/ +static void +copyenvstr(char *s, char *value, int flags) +{ + while (*s++) { + if ((*s = *value++) == Meta) + *s = *value++ ^ 32; + if (flags & PM_LOWER) + *s = tulower(*s); + else if (flags & PM_UPPER) + *s = tuupper(*s); + } +} + +/**/ +void +addenv(Param pm, char *value) +{ + char *newenv = 0; +#ifndef USE_SET_UNSET_ENV + char *oldenv = 0, *env = 0; + int pos; + + /* + * First check if there is already an environment + * variable matching string `name'. + */ + if (findenv(pm->node.nam, &pos)) + oldenv = environ[pos]; +#endif + + newenv = mkenvstr(pm->node.nam, value, pm->node.flags); + if (zputenv(newenv)) { + zsfree(newenv); + pm->env = NULL; + return; + } +#ifdef USE_SET_UNSET_ENV + /* + * If we are using setenv/unsetenv to manage the environment, + * we simply store the string we created in pm->env since + * memory management of the environment is handled entirely + * by the system. + * + * TODO: is this good enough to fix problem cases from + * the other branch? If so, we don't actually need to + * store pm->env at all, just a flag that the value was set. + */ + if (pm->env) + zsfree(pm->env); + pm->env = newenv; + pm->node.flags |= PM_EXPORTED; +#else + /* + * Under Cygwin we must use putenv() to maintain consistency. + * Unfortunately, current version (1.1.2) copies argument and may + * silently reuse existing environment string. This tries to + * check for both cases + */ + if (findenv(pm->node.nam, &pos)) { + env = environ[pos]; + if (env != oldenv) + zsfree(oldenv); + if (env != newenv) + zsfree(newenv); + pm->node.flags |= PM_EXPORTED; + pm->env = env; + return; + } + + DPUTS(1, "addenv should never reach the end"); + pm->env = NULL; +#endif +} + + +/* Given strings *name = "foo", *value = "bar", * + * return a new string *str = "foo=bar". */ + +/**/ +static char * +mkenvstr(char *name, char *value, int flags) +{ + char *str, *s = value; + int len_name, len_value = 0; + + len_name = strlen(name); + if (s) + while (*s && (*s++ != Meta || *s++ != 32)) + len_value++; + s = str = (char *) zalloc(len_name + len_value + 2); + strcpy(s, name); + s += len_name; + *s = '='; + if (value) + copyenvstr(s, value, flags); + else + *++s = '\0'; + return str; +} + +/* Given *name = "foo", *value = "bar", add the * + * string "foo=bar" to the environment. Return a * + * pointer to the location of this new environment * + * string. */ + + +#ifndef USE_SET_UNSET_ENV +/**/ +void +delenvvalue(char *x) +{ + char **ep; + + for (ep = environ; *ep; ep++) { + if (*ep == x) + break; + } + if (*ep) { + for (; (ep[0] = ep[1]); ep++); + } + zsfree(x); +} +#endif + + +/* Delete a pointer from the list of pointers to environment * + * variables by shifting all the other pointers up one slot. */ + +/**/ +void +delenv(Param pm) +{ +#ifdef USE_SET_UNSET_ENV + unsetenv(pm->node.nam); + zsfree(pm->env); +#else + delenvvalue(pm->env); +#endif + pm->env = NULL; + /* + * Note we don't remove PM_EXPORT from the flags. This + * may be asking for trouble but we need to know later + * if we restore this parameter to its old value. + */ +} + +/* + * Guts of convbase: this version can return the number of digits + * sans any base discriminator. + */ + +/**/ +void +convbase_ptr(char *s, zlong v, int base, int *ndigits) +{ + int digs = 0; + zulong x; + + if (v < 0) + *s++ = '-', v = -v; + if (base >= -1 && base <= 1) + base = -10; + + if (base > 0) { + if (isset(CBASES) && base == 16) + sprintf(s, "0x"); + else if (isset(CBASES) && base == 8 && isset(OCTALZEROES)) + sprintf(s, "0"); + else if (base != 10) + sprintf(s, "%d#", base); + else + *s = 0; + s += strlen(s); + } else + base = -base; + for (x = v; x; digs++) + x /= base; + if (!digs) + digs = 1; + if (ndigits) + *ndigits = digs; + s[digs--] = '\0'; + x = v; + while (digs >= 0) { + int dig = x % base; + + s[digs--] = (dig < 10) ? '0' + dig : dig - 10 + 'A'; + x /= base; + } +} + +/* + * Basic conversion of integer to a string given a base. + * If 0 base is 10. + * If negative no base discriminator is output. + */ + +/**/ +mod_export void +convbase(char *s, zlong v, int base) +{ + convbase_ptr(s, v, base, NULL); +} + +/* + * Add underscores to converted integer for readability with given spacing. + * s is as for convbase: at least BDIGBUFSIZE. + * If underscores were added, returned value with underscores comes from + * heap, else the returned value is s. + */ + +/**/ +char * +convbase_underscore(char *s, zlong v, int base, int underscore) +{ + char *retptr, *sptr, *dptr; + int ndigits, nunderscore, mod, len; + + convbase_ptr(s, v, base, &ndigits); + + if (underscore <= 0) + return s; + + nunderscore = (ndigits - 1) / underscore; + if (!nunderscore) + return s; + len = strlen(s); + retptr = zhalloc(len + nunderscore + 1); + mod = 0; + memcpy(retptr, s, len - ndigits); + sptr = s + len; + dptr = retptr + len + nunderscore; + /* copy the null */ + *dptr-- = *sptr--; + for (;;) { + *dptr = *sptr; + if (!--ndigits) + break; + dptr--; + sptr--; + if (++mod == underscore) { + mod = 0; + *dptr-- = '_'; + } + } + + return retptr; +} + +/* + * Convert a floating point value for output. + * Unlike convbase(), this has its own internal storage and returns + * a value from the heap. + */ + +/**/ +char * +convfloat(double dval, int digits, int flags, FILE *fout) +{ + char fmt[] = "%.*e"; + char *prev_locale, *ret; + + /* + * The difficulty with the buffer size is that a %f conversion + * prints all digits before the decimal point: with 64 bit doubles, + * that's around 310. We can't check without doing some quite + * serious floating point operations we'd like to avoid. + * Then we are liable to get all the digits + * we asked for after the decimal point, or we should at least + * bargain for it. So we just allocate 512 + digits. This + * should work until somebody decides on 128-bit doubles. + */ + if (!(flags & (PM_EFLOAT|PM_FFLOAT))) { + /* + * Conversion from a floating point expression without using + * a variable. The best bet in this case just seems to be + * to use the general %g format with something like the maximum + * double precision. + */ + fmt[3] = 'g'; + if (!digits) + digits = 17; + } else { + if (flags & PM_FFLOAT) + fmt[3] = 'f'; + if (digits <= 0) + digits = 10; + if (flags & PM_EFLOAT) { + /* + * Here, we are given the number of significant figures, but + * %e wants the number of decimal places (unlike %g) + */ + digits--; + } + } +#ifdef USE_LOCALE + prev_locale = dupstring(setlocale(LC_NUMERIC, NULL)); + setlocale(LC_NUMERIC, "POSIX"); +#endif + if (fout) { + fprintf(fout, fmt, digits, dval); + ret = NULL; + } else { + VARARR(char, buf, 512 + digits); + if (isinf(dval)) + ret = dupstring((dval < 0.0) ? "-Inf" : "Inf"); + else if (isnan(dval)) + ret = dupstring("NaN"); + else { + sprintf(buf, fmt, digits, dval); + if (!strchr(buf, 'e') && !strchr(buf, '.')) + strcat(buf, "."); + ret = dupstring(buf); + } + } +#ifdef USE_LOCALE + if (prev_locale) setlocale(LC_NUMERIC, prev_locale); +#endif + return ret; +} + +/* + * convert float to string with basic options but inserting underscores + * for readability. + */ + +/**/ +char *convfloat_underscore(double dval, int underscore) +{ + int ndigits_int = 0, ndigits_frac = 0, nunderscore, len; + char *s, *retptr, *sptr, *dptr; + + s = convfloat(dval, 0, 0, NULL); + if (underscore <= 0) + return s; + + /* + * Count the number of digits before and after the decimal point, if any. + */ + sptr = s; + if (*sptr == '-') + sptr++; + while (idigit(*sptr)) { + ndigits_int++; + sptr++; + } + if (*sptr == '.') { + sptr++; + while (idigit(*sptr)) { + ndigits_frac++; + sptr++; + } + } + + /* + * Work out how many underscores to insert --- remember we + * put them in integer and fractional parts separately. + */ + nunderscore = (ndigits_int-1) / underscore + (ndigits_frac-1) / underscore; + if (!nunderscore) + return s; + len = strlen(s); + dptr = retptr = zhalloc(len + nunderscore + 1); + + /* + * Insert underscores in integer part. + * Grouping starts from the point in both directions. + */ + sptr = s; + if (*sptr == '-') + *dptr++ = *sptr++; + while (ndigits_int) { + *dptr++ = *sptr++; + if (--ndigits_int && !(ndigits_int % underscore)) + *dptr++ = '_'; + } + if (ndigits_frac) { + /* + * Insert underscores in the fractional part. + */ + int mod = 0; + /* decimal point, we already checked */ + *dptr++ = *sptr++; + while (ndigits_frac) { + *dptr++ = *sptr++; + mod++; + if (--ndigits_frac && mod == underscore) { + *dptr++ = '_'; + mod = 0; + } + } + } + /* Copy exponent and anything else up to null */ + while ((*dptr++ = *sptr++)) + ; + return retptr; +} + +/* Start a parameter scope */ + +/**/ +mod_export void +startparamscope(void) +{ + locallevel++; +} + +/* End a parameter scope: delete the parameters local to the scope. */ + +/**/ +mod_export void +endparamscope(void) +{ + queue_signals(); + locallevel--; + /* This pops anything from a higher locallevel */ + saveandpophiststack(0, HFILE_USE_OPTIONS); + scanhashtable(paramtab, 0, 0, 0, scanendscope, 0); + unqueue_signals(); +} + +/**/ +static void +scanendscope(HashNode hn, UNUSED(int flags)) +{ + Param pm = (Param)hn; + if (pm->level > locallevel) { + if ((pm->node.flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL) { + /* + * Removable specials are normal in that they can be removed + * to reveal an ordinary parameter beneath. Here we handle + * non-removable specials, which were made local by stealth + * (see newspecial code in typeset_single()). In fact the + * visible pm is always the same struct; the pm->old is + * just a place holder for old data and flags. + */ + Param tpm = pm->old; + + if (!strcmp(pm->node.nam, "SECONDS")) + { + setsecondstype(pm, PM_TYPE(tpm->node.flags), PM_TYPE(pm->node.flags)); + /* + * We restore SECONDS by restoring its raw internal value + * that we cached off into tpm->u.dval. + */ + setrawseconds(tpm->u.dval); + tpm->node.flags |= PM_NORESTORE; + } + DPUTS(!tpm || PM_TYPE(pm->node.flags) != PM_TYPE(tpm->node.flags) || + !(tpm->node.flags & PM_SPECIAL), + "BUG: in restoring scope of special parameter"); + pm->old = tpm->old; + pm->node.flags = (tpm->node.flags & ~PM_NORESTORE); + pm->level = tpm->level; + pm->base = tpm->base; + pm->width = tpm->width; + if (pm->env) + delenv(pm); + + if (!(tpm->node.flags & (PM_NORESTORE|PM_READONLY))) + switch (PM_TYPE(pm->node.flags)) { + case PM_SCALAR: + pm->gsu.s->setfn(pm, tpm->u.str); + break; + case PM_INTEGER: + pm->gsu.i->setfn(pm, tpm->u.val); + break; + case PM_EFLOAT: + case PM_FFLOAT: + pm->gsu.f->setfn(pm, tpm->u.dval); + break; + case PM_ARRAY: + pm->gsu.a->setfn(pm, tpm->u.arr); + break; + case PM_HASHED: + pm->gsu.h->setfn(pm, tpm->u.hash); + break; + } + zfree(tpm, sizeof(*tpm)); + + if (pm->node.flags & PM_EXPORTED) + export_param(pm); + } else + unsetparam_pm(pm, 0, 0); + } +} + + +/**********************************/ +/* Parameter Hash Table Functions */ +/**********************************/ + +/**/ +void +freeparamnode(HashNode hn) +{ + Param pm = (Param) hn; + + /* The second argument of unsetfn() is used by modules to + * differentiate "exp"licit unset from implicit unset, as when + * a parameter is going out of scope. It's not clear which + * of these applies here, but passing 1 has always worked. + */ + if (delunset) + pm->gsu.s->unsetfn(pm, 1); + zsfree(pm->node.nam); + /* If this variable was tied by the user, ename was ztrdup'd */ + if (pm->node.flags & PM_TIED) + zsfree(pm->ename); + zfree(pm, sizeof(struct param)); +} + +/* Print a parameter */ + +enum paramtypes_flags { + PMTF_USE_BASE = (1<<0), + PMTF_USE_WIDTH = (1<<1), + PMTF_TEST_LEVEL = (1<<2) +}; + +struct paramtypes { + int binflag; /* The relevant PM_FLAG(S) */ + const char *string; /* String for verbose output */ + int typeflag; /* Flag for typeset -? */ + int flags; /* The enum above */ +}; + +static const struct paramtypes pmtypes[] = { + { PM_AUTOLOAD, "undefined", 0, 0}, + { PM_INTEGER, "integer", 'i', PMTF_USE_BASE}, + { PM_EFLOAT, "float", 'E', 0}, + { PM_FFLOAT, "float", 'F', 0}, + { PM_ARRAY, "array", 'a', 0}, + { PM_HASHED, "association", 'A', 0}, + { 0, "local", 0, PMTF_TEST_LEVEL}, + { PM_LEFT, "left justified", 'L', PMTF_USE_WIDTH}, + { PM_RIGHT_B, "right justified", 'R', PMTF_USE_WIDTH}, + { PM_RIGHT_Z, "zero filled", 'Z', PMTF_USE_WIDTH}, + { PM_LOWER, "lowercase", 'l', 0}, + { PM_UPPER, "uppercase", 'u', 0}, + { PM_READONLY, "readonly", 'r', 0}, + { PM_TAGGED, "tagged", 't', 0}, + { PM_EXPORTED, "exported", 'x', 0} +}; + +#define PMTYPES_SIZE ((int)(sizeof(pmtypes)/sizeof(struct paramtypes))) + +static void +printparamvalue(Param p, int printflags) +{ + char *t, **u; + + if (!(printflags & PRINT_KV_PAIR)) + putchar('='); + + /* How the value is displayed depends * + * on the type of the parameter */ + switch (PM_TYPE(p->node.flags)) { + case PM_SCALAR: + /* string: simple output */ + if (p->gsu.s->getfn && (t = p->gsu.s->getfn(p))) + quotedzputs(t, stdout); + break; + case PM_INTEGER: + /* integer */ +#ifdef ZSH_64_BIT_TYPE + fputs(output64(p->gsu.i->getfn(p)), stdout); +#else + printf("%ld", p->gsu.i->getfn(p)); +#endif + break; + case PM_EFLOAT: + case PM_FFLOAT: + /* float */ + convfloat(p->gsu.f->getfn(p), p->base, p->node.flags, stdout); + break; + case PM_ARRAY: + /* array */ + if (!(printflags & PRINT_KV_PAIR)) { + putchar('('); + if (!(printflags & PRINT_LINE)) + putchar(' '); + } + u = p->gsu.a->getfn(p); + if(*u) { + if (printflags & PRINT_LINE) { + if (printflags & PRINT_KV_PAIR) + printf(" "); + else + printf("\n "); + } + quotedzputs(*u++, stdout); + while (*u) { + if (printflags & PRINT_LINE) + printf("\n "); + else + putchar(' '); + quotedzputs(*u++, stdout); + } + if ((printflags & (PRINT_LINE|PRINT_KV_PAIR)) == PRINT_LINE) + putchar('\n'); + } + if (!(printflags & PRINT_KV_PAIR)) { + if (!(printflags & PRINT_LINE)) + putchar(' '); + putchar(')'); + } + break; + case PM_HASHED: + /* association */ + { + HashTable ht; + int found = 0; + if (!(printflags & PRINT_KV_PAIR)) { + putchar('('); + if (!(printflags & PRINT_LINE)) + putchar(' '); + } + ht = p->gsu.h->getfn(p); + if (ht) + found = scanhashtable(ht, 1, 0, PM_UNSET, + ht->printnode, PRINT_KV_PAIR | + (printflags & PRINT_LINE)); + if (!(printflags & PRINT_KV_PAIR)) { + if (found && (printflags & PRINT_LINE)) + putchar('\n'); + putchar(')'); + } + } + break; + } + if ((printflags & (PRINT_KV_PAIR|PRINT_LINE)) == PRINT_KV_PAIR) + putchar(' '); + else if (!(printflags & PRINT_KV_PAIR)) + putchar('\n'); +} + +/**/ +mod_export void +printparamnode(HashNode hn, int printflags) +{ + Param p = (Param) hn; + + if (p->node.flags & PM_UNSET) { + if (isset(POSIXBUILTINS) && (p->node.flags & PM_READONLY) && + (printflags & PRINT_TYPESET)) + { + /* + * Special POSIX rules: show the parameter as readonly + * even though it's unset, but with no value. + */ + printflags |= PRINT_NAMEONLY; + } + else if (p->node.flags & PM_EXPORTED) + printflags |= PRINT_NAMEONLY; + else + return; + } + if (p->node.flags & PM_AUTOLOAD) + printflags |= PRINT_NAMEONLY; + + if (printflags & PRINT_TYPESET) { + if ((p->node.flags & (PM_READONLY|PM_SPECIAL)) == + (PM_READONLY|PM_SPECIAL) || + (p->node.flags & PM_AUTOLOAD)) { + /* + * It's not possible to restore the state of + * these, so don't output. + */ + return; + } + if (locallevel && p->level >= locallevel) { + printf("typeset "); /* printf("local "); */ + } else if ((p->node.flags & PM_EXPORTED) && + !(p->node.flags & (PM_ARRAY|PM_HASHED))) { + printf("export "); + } else if (locallevel) { + printf("typeset -g "); + } else + printf("typeset "); + } + + /* Print the attributes of the parameter */ + if (printflags & (PRINT_TYPE|PRINT_TYPESET)) { + int doneminus = 0, i; + const struct paramtypes *pmptr; + + for (pmptr = pmtypes, i = 0; i < PMTYPES_SIZE; i++, pmptr++) { + int doprint = 0; + if (pmptr->flags & PMTF_TEST_LEVEL) { + if (p->level) + doprint = 1; + } else if ((pmptr->binflag != PM_EXPORTED || p->level || + (p->node.flags & (PM_LOCAL|PM_ARRAY|PM_HASHED))) && + (p->node.flags & pmptr->binflag)) + doprint = 1; + + if (doprint) { + if (printflags & PRINT_TYPESET) { + if (pmptr->typeflag) { + if (!doneminus) { + putchar('-'); + doneminus = 1; + } + putchar(pmptr->typeflag); + } + } else + printf("%s ", pmptr->string); + if ((pmptr->flags & PMTF_USE_BASE) && p->base) { + printf("%d ", p->base); + doneminus = 0; + } + if ((pmptr->flags & PMTF_USE_WIDTH) && p->width) { + printf("%d ", p->width); + doneminus = 0; + } + } + } + if (doneminus) + putchar(' '); + } + + if ((printflags & PRINT_NAMEONLY) || + ((p->node.flags & PM_HIDEVAL) && !(printflags & PRINT_INCLUDEVALUE))) { + zputs(p->node.nam, stdout); + putchar('\n'); + } else { + if (printflags & PRINT_KV_PAIR) { + if (printflags & PRINT_LINE) + printf("\n "); + putchar('['); + } + quotedzputs(p->node.nam, stdout); + if (printflags & PRINT_KV_PAIR) + printf("]="); + + printparamvalue(p, printflags); + } +} |
