aboutsummaryrefslogtreecommitdiff
path: root/toys/pending/sh.c
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2020-01-03 19:39:26 -0600
committerRob Landley <rob@landley.net>2020-01-03 19:39:26 -0600
commit9840fcdb428c712d21b057241e72ac9715ff6b28 (patch)
tree101250bae8b418757a2ccf2b0b3382927999b619 /toys/pending/sh.c
parent440328edfcf65f29b69f9069517b7192e026c187 (diff)
downloadtoybox-9840fcdb428c712d21b057241e72ac9715ff6b28.tar.gz
Finish cd, make help -ahu unconditional, fix expand memory cleanup path,
set $HOME $PWD and $OLDPWD, fix prompt \w, shuffle some functions around to avoid prototypes, implement tilde expansion, add FORCE_COPY.
Diffstat (limited to 'toys/pending/sh.c')
-rw-r--r--toys/pending/sh.c384
1 files changed, 256 insertions, 128 deletions
diff --git a/toys/pending/sh.c b/toys/pending/sh.c
index 27dda822..df8edd4f 100644
--- a/toys/pending/sh.c
+++ b/toys/pending/sh.c
@@ -19,11 +19,9 @@
*
* Things like the bash man page are good to read too.
*
- * TODO: "make sh" doesn't work (nofork builtins need to be included)
* TODO: test that $PS1 color changes work without stupid \[ \] hack
* TODO: make fake pty wrapper for test infrastructure
* TODO: // Handle embedded NUL bytes in the command line.
- * TODO: var=val command
* existing but considered builtins: false kill pwd true time
* buitins: alias bg command fc fg getopts jobs newgrp read umask unalias wait
* "special" builtins: break continue : . eval exec export readonly return set
@@ -37,6 +35,7 @@
* label:
* TODO: test exit from "trap EXIT" doesn't recurse
* TODO: ! history expansion
+ * TODO: getuid() vs geteuid()
*
* bash man page:
* control operators || & && ; ;; ;& ;;& ( ) | |& <newline>
@@ -44,9 +43,7 @@
* ! case coproc do done elif else esac fi for function if in select
* then until while { } time [[ ]]
-
-
-USE_SH(NEWTOY(cd, NULL, TOYFLAG_NOFORK))
+USE_SH(NEWTOY(cd, ">1LP[-LP]", TOYFLAG_NOFORK))
USE_SH(NEWTOY(exit, NULL, TOYFLAG_NOFORK))
USE_SH(NEWTOY(sh, "(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN))
@@ -75,7 +72,7 @@ config CD
default n
depends on SH
help
- usage: cd [-PL] [path]
+ usage: cd [-PLe] [path]
Change current directory. With no arguments, go $HOME.
@@ -100,11 +97,10 @@ GLOBALS(
char *command;
long lineno;
-
char **locals;
-
struct double_list functions;
- unsigned options;
+ unsigned options, jobcnt;
+ int hfd; // next high filehandle (>= 10)
// Running jobs.
struct sh_job {
@@ -120,13 +116,11 @@ GLOBALS(
// null terminated array of running processes in pipeline
struct sh_process {
struct sh_process *next, *prev;
- struct string_list *delete; // expanded strings
+ struct arg_list *delete; // expanded strings
int *urd, envlen, pid, exit; // undo redirects, child PID, exit status
struct sh_arg arg;
} *procs, *proc;
} *jobs, *job;
- unsigned jobcnt;
- int hfd, *hfdclose; // next high filehandle (>= 10)
)
#define BUGBUG 0
@@ -139,20 +133,6 @@ static const char *redirectors[] = {"<<<", "<<-", "<<", "<&", "<>", "<", ">>",
#define SH_NOCLOBBER 1 // set -C
-void cd_main(void)
-{
- char *dest = *toys.optargs ? *toys.optargs : getenv("HOME");
-
-// TODO: -LPE@
-// TODO: cd .. goes up $PWD path we used to get here, not ./..
- xchdir(dest ? dest : "/");
-}
-
-void exit_main(void)
-{
- exit(*toys.optargs ? atoi(*toys.optargs) : 0);
-}
-
// like error_msg() but exit from shell scripts
static void syntax_err(char *msg, ...)
{
@@ -165,59 +145,54 @@ static void syntax_err(char *msg, ...)
if (*toys.optargs) xexit();
}
-// Print prompt to stderr, parsing escapes
-// Truncated to 4k at the moment, waiting for somebody to complain.
-static void do_prompt(char *prompt)
+void array_add(char ***list, unsigned count, char *data)
{
- char *s, c, cc, *pp = toybuf;
- int len;
+ if (!(count&31)) *list = xrealloc(*list, sizeof(char *)*(count+33));
+ (*list)[count] = data;
+ (*list)[count+1] = 0;
+}
- if (!prompt) prompt = "\\$ ";
- while ((len = sizeof(toybuf)-(pp-toybuf))>0 && *prompt) {
- c = *(prompt++);
+// TODO local variables
- if (c=='!') {
- if (*prompt=='!') prompt++;
- else {
- pp += snprintf(pp, len, "%ld", TT.lineno);
- continue;
- }
- } else if (c=='\\') {
- cc = *(prompt++);
- if (!cc) {
- *pp++ = c;
- break;
- }
+// Assign one variable
+// s: key=val
+// type: 0 = whatever it was before, local otherwise
+#define TAKE_MEM 0x80000000
+// declare -aAilnrux
+// ft
+static void setvar(char *s, unsigned type)
+{
+ if (type&TAKE_MEM) type ^= TAKE_MEM;
+ else s = xstrdup(s);
- // \nnn \dD{}hHjlstT@AuvVwW!#$
- // Ignore bash's "nonprintable" hack; query our cursor position instead.
- if (cc=='[' || cc==']') continue;
- else if (cc=='$') *pp++ = getuid() ? '$' : '#';
- else if (cc=='h' || cc=='H') {
- *pp = 0;
- gethostname(pp, len);
- pp[len-1] = 0;
- if (cc=='h' && (s = strchr(pp, '.'))) *s = 0;
- pp += strlen(pp);
- } else if (cc=='s') {
- s = getbasename(*toys.argv);
- while (*s && len--) *pp++ = *s++;
- } else if (!(c = unescape(cc))) {
- *pp++ = '\\';
- if (--len) *pp++ = c;
- } else *pp++ = c;
- } else *pp++ = c;
+ // local, export, readonly, integer...
+ xsetenv(s, 0);
+}
+
+// get variable of length len starting at s.
+static char *getvarlen(char *s, int len)
+{
+ unsigned uu;
+ char **ss = TT.locals;
+
+ // loop through local, then global variables
+ for (uu = 0; ; uu++) {
+ if (!ss || !ss[uu]) {
+ if (ss != TT.locals) return 0;
+ ss = environ;
+ uu = 0;
+ }
+
+ // Use UHF rubik's cube protocol to find match.
+ if (!strncmp(ss[uu], s, len) && ss[uu][len] == '=') return ss[uu]+len+1;
}
- len = pp-toybuf;
- if (len>=sizeof(toybuf)) len = sizeof(toybuf);
- writeall(2, toybuf, len);
+
+ return 0;
}
-void array_add(char ***list, unsigned count, char *data)
+static char *getvar(char *s)
{
- if (!(count&31)) *list = xrealloc(*list, sizeof(char *)*(count+33));
- (*list)[count] = data;
- (*list)[count+1] = 0;
+ return getvarlen(s, strlen(s));
}
// quote removal, brace, tilde, parameter/variable, $(command),
@@ -227,6 +202,7 @@ void array_add(char ***list, unsigned count, char *data)
#define NO_BRACE (1<<2)
#define NO_TILDE (1<<3)
#define NO_QUOTE (1<<4)
+#define FORCE_COPY (1<<31)
// TODO: ${name:?error} causes an error/abort here (syntax_err longjmp?)
// TODO: $1 $@ $* need args marshalled down here: function+structure?
// arg = append to this
@@ -234,15 +210,34 @@ void array_add(char ***list, unsigned count, char *data)
// flags = type of expansions (not) to do
// delete = append new allocations to this so they can be freed later
// TODO: at_args: $1 $2 $3 $* $@
-static void expand_arg(struct sh_arg *arg, char *new, unsigned flags,
- struct string_list **delete)
+static void expand_arg(struct sh_arg *arg, char *old, unsigned flags,
+ struct arg_list **delete)
{
-// char *s = new, quote = 0;
+ char *new = old, *s, *ss, quote = 0;
// TODO ls -l /proc/$$/fd
// ${ $(( $( $[ $' `
+ // Tilde expansion
+ if (!(flags&NO_TILDE) && *new == '~') {
+ struct passwd *pw = 0;
+
+ // first expansion so don't need to free previous new
+ ss = 0;
+ for (s = new; *s && *s!=':' && *s!='/'; s++);
+ if (s-new==1) {
+ if (!(ss = getvar("HOME")) || !*ss) pw = bufgetpwuid(getuid());
+ } else {
+ // TODO bufgetpwnam
+ pw = getpwnam(new = xstrndup(new+1, (s-new)-1));
+ free(new);
+ }
+ if (pw && pw->pw_dir) ss = pw->pw_dir;
+ if (!ss || !*ss) ss = "/";
+ new = xmprintf("%s%s", ss, s);
+ }
+
/*
while (*s) {
if (!quote && !(flags&NO_BRACE) && *s == '{') {
@@ -259,12 +254,6 @@ TODO this recurses
// PS0 PS1='$ ' PS2='> ' PS3
}
}
-*/
- // literal passthrough
- array_add(&arg->v, arg->c++, new);
-
-/*
- char *s = word, *new = 0;
// replacement
while (*s) {
@@ -276,14 +265,23 @@ TODO this recurses
s++;
} else s++;
}
-
- return new;
*/
+
+ // We have a result. Append it.
+ if (old==new && (flags&FORCE_COPY)) new = xstrdup(new);
+ if (old!=new && delete) {
+ struct arg_list *al = xmalloc(sizeof(struct arg_list));
+
+ al->next = *delete;
+ al->arg = new;
+ *delete = al;
+ }
+ array_add(&arg->v, arg->c++, new);
}
// Expand exactly one arg, returning NULL if it split.
// If return != new you need to free it.
-static char *expand_one_arg(char *new, unsigned flags, struct string_list **del)
+static char *expand_one_arg(char *new, unsigned flags, struct arg_list **del)
{
struct sh_arg arg;
char *s = 0;
@@ -300,53 +298,17 @@ static char *expand_one_arg(char *new, unsigned flags, struct string_list **del)
return s;
}
-// Assign one variable
-// s: key=val
-// type: 0 = whatever it was before, local otherwise
-#define TAKE_MEM 0x80000000
-// declare -aAilnrux
-// ft
-static void setvar(char *s, unsigned type)
-{
- if (type&TAKE_MEM) type ^= TAKE_MEM;
- else s = xstrdup(s);
-
- // local, export, readonly, integer...
- xsetenv(s, 0);
-}
-
-// get variable of length len starting at s.
-static char *getvar(char *s, int len)
-{
- unsigned uu;
- char **ss = TT.locals;
-
- // loop through local, then global variables
- for (uu = 0; ; uu++) {
- if (!ss[uu]) {
- if (ss != TT.locals) return 0;
- ss = environ;
- uu = 0;
- }
- // Use UHF rubik's cube protocol to find match.
- if (!strncmp(ss[uu], s, len) && ss[uu][len] == '=') return ss[uu]+len+1;
- }
-}
-
-// return length of match found at this point
+// return length of match found at this point (try is null terminated array)
static int anystart(char *s, char **try)
{
char *ss = s;
- while (*try) {
- if (strstart(&s, *try)) return s-ss;
- try++;
- }
+ while (*try) if (strstart(&s, *try++)) return s-ss;
return 0;
}
-// is this one of the strings in try[] (null terminated array)
+// does this entire string match one of the strings in try[]
static int anystr(char *s, char **try)
{
while (*try) if (!strcmp(s, *try++)) return 1;
@@ -546,7 +508,7 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int envlen, int *urd)
else if (*s == '{') {
// when we close a filehandle, we _read_ from {var}, not write to it
if ((!strcmp(ss, "<&") || !strcmp(ss, ">&")) && !strcmp(sss, "-")) {
- if (!(ss = getvar(s+1, ss-s-2))) break;
+ if (!(ss = getvarlen(s+1, ss-s-2))) break;
to = atoi(ss); // TODO trailing garbage?
if (save_redirect(&pp->urd, -1, to)) break;
close(to);
@@ -562,7 +524,7 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int envlen, int *urd)
// HERE documents?
if (!strcmp(ss, "<<<") || !strcmp(ss, "<<-") || !strcmp(ss, "<<")) {
- char *tmp = getvar("TMPDIR", 6);
+ char *tmp = getvar("TMPDIR");
int i, len, zap = (ss[2] == '-'), x = !ss[strcspn(ss, "\"'")];
// store contents in open-but-deleted /tmp file.
@@ -755,8 +717,7 @@ if (BUGBUG) { int i; dprintf(255, "envlen=%d arg->c=%d run=", envlen, arg->c); f
static void free_process(void *ppp)
{
struct sh_process *pp = ppp;
-
- llist_traverse(pp->delete, free);
+ llist_traverse(pp->delete, llist_free_arg);
free(pp);
}
@@ -999,7 +960,7 @@ static int parse_line(char *line, struct sh_function *sp)
// Did we hit end of line or ) outside a function declaration?
// ) is only saved at start of a statement, ends current statement
if (end == start || (arg->c && *start == ')' && pl->type!='f')) {
- arg->v[arg->c] = 0;
+ if (!arg->v) array_add(&arg->v, arg->c, 0);
if (pl->type == 'f' && arg->c<3) {
s = "function()";
@@ -1306,7 +1267,7 @@ static void run_function(struct sh_function *sp)
struct sh_process *pin; // processes piping into this block
int run, loop, *urd, pout;
struct sh_arg farg; // for/select arg stack
- struct string_list *fdelete; // farg's cleanup list
+ struct arg_list *fdelete; // farg's cleanup list
char *fvar; // for/select's iteration variable name
} *blk = 0, *new;
struct sh_process *pplist = 0; // processes piping into current level
@@ -1524,6 +1485,82 @@ dprintf(2, "TODO skipped running for((;;)), need math parser\n");
return;
}
+// Parse and run a self-contained command line with no prompt/continuation
+static int sh_run(char *new)
+{
+ struct sh_function scratch;
+ int rc;
+
+// TODO: parse with len? (End early?)
+
+ if (!parse_line(new, &scratch)) run_function(&scratch);
+ free_function(&scratch);
+ rc = toys.exitval;
+ toys.exitval = 0;
+
+ return rc;
+}
+
+// Print prompt to stderr, parsing escapes
+// Truncated to 4k at the moment, waiting for somebody to complain.
+static void do_prompt(char *prompt)
+{
+ char *s, *ss, c, cc, *pp = toybuf;
+ int len, ll;
+
+ if (!prompt) prompt = "\\$ ";
+ while ((len = sizeof(toybuf)-(pp-toybuf))>0 && *prompt) {
+ c = *(prompt++);
+
+ if (c=='!') {
+ if (*prompt=='!') prompt++;
+ else {
+ pp += snprintf(pp, len, "%ld", TT.lineno);
+ continue;
+ }
+ } else if (c=='\\') {
+ cc = *(prompt++);
+ if (!cc) {
+ *pp++ = c;
+ break;
+ }
+
+ // \nnn \dD{}hHjlstT@AuvVwW!#$
+ // Ignore bash's "nonprintable" hack; query our cursor position instead.
+ if (cc=='[' || cc==']') continue;
+ else if (cc=='$') *pp++ = getuid() ? '$' : '#';
+ else if (cc=='h' || cc=='H') {
+ *pp = 0;
+ gethostname(pp, len);
+ pp[len-1] = 0;
+ if (cc=='h' && (s = strchr(pp, '.'))) *s = 0;
+ pp += strlen(pp);
+ } else if (cc=='s') {
+ s = getbasename(*toys.argv);
+ while (*s && len--) *pp++ = *s++;
+ } else if (cc=='w') {
+ if ((s = getvar("PWD"))) {
+ if ((ss = getvar("HOME")) && strstart(&s, ss)) {
+ *pp++ = '~';
+ if (--len && *s!='/') *pp++ = '/';
+ len--;
+ }
+ if (len>0) {
+ ll = strlen(s);
+ pp = stpncpy(pp, s, ll>len ? len : ll);
+ }
+ }
+ } else if (!(c = unescape(cc))) {
+ *pp++ = '\\';
+ if (--len) *pp++ = c;
+ } else *pp++ = c;
+ } else *pp++ = c;
+ }
+ len = pp-toybuf;
+ if (len>=sizeof(toybuf)) len = sizeof(toybuf);
+ writeall(2, toybuf, len);
+}
+
void subshell_imports(void)
{
/*
@@ -1587,6 +1624,20 @@ void sh_main(void)
TT.hfd = 10;
signal(SIGPIPE, SIG_IGN);
+ // TODO euid stuff?
+
+ // TODO read profile, read rc
+
+ // if (!FLAG(noprofile)) { }
+
+ // Set local variable $HOME to user's login path
+ if (!(new = getenv("HOME"))) {
+ struct passwd *pw = getpwuid(getuid());
+
+ setvar(xmprintf("HOME=%s", (pw && *pw->pw_dir)?pw->pw_dir:"/"), TAKE_MEM);
+ }
+ sh_run("cd .");
+
if (BUGBUG) { int fd = open("/dev/tty", O_RDWR); dup2(fd, 255); close(fd); }
// Is this an interactive shell?
// if (FLAG(i) || (!FLAG(c)&&(FLAG(S)||!toys.optc) && isatty(0) && isatty(1)))
@@ -1633,3 +1684,80 @@ if (BUGBUG) dump_state(&scratch);
toys.exitval = f && ferror(f);
clearerr(stdout);
}
+
+/********************* shell builtin functions *************************/
+
+#define CLEANUP_sh
+#define FOR_cd
+#include "generated/flags.h"
+void cd_main(void)
+{
+ char *home = getvar("HOME"), *pwd = getvar("PWD"), *dd = 0, *from, *to,
+ *dest = (*toys.optargs && **toys.optargs) ? *toys.optargs : "~";
+ int bad = 0;
+
+ // TODO: CDPATH? Really?
+
+ if (!home) home = "/";
+
+ // expand variables
+ if (dest) dd = expand_one_arg(dest, FORCE_COPY|NO_SPLIT, 0);
+ if (!dd || !*dd) {
+ free(dd);
+ dd = xstrdup("/");
+ }
+
+ // prepend cwd or $PWD to relative path
+ if (*dd != '/') {
+ to = 0;
+ from = pwd ? pwd : (to = getcwd(0, 0));
+ if (!from) xsetenv("PWD", "(nowhere)");
+ else {
+ from = xmprintf("%s/%s", from, dd);
+ free(dd);
+ free(to);
+ dd = from;
+ }
+ }
+
+ if (FLAG(P)) {
+ struct stat st;
+ char *pp;
+
+ // Does this directory exist?
+ if ((pp = xabspath(dd, 1)) && stat(pp, &st) && !S_ISDIR(st.st_mode))
+ bad++, errno = ENOTDIR;
+ else {
+ free(dd);
+ dd = pp;
+ }
+ } else {
+
+ // cancel out . and .. in the string
+ for (from = to = dd; *from;) {
+ while (*from=='/' && from[1]=='/') from++;
+ if (*from!='/' || from[1]!='.') *to++ = *from++;
+ else if (!from[2] || from[2]=='/') from += 2;
+ else if (from[2]=='.' && (!from[3] || from[3]=='/')) {
+ from += 3;
+ while (to>dd && *--to != '/');
+ } else *to++ = *from++;
+ }
+ if (to-dd>1 && to[-1]=='/') to--;
+ *to = 0;
+ }
+
+ if (bad || chdir(dd)) perror_msg("chdir '%s'", dd);
+ else {
+ if (pwd) xsetenv("OLD", pwd);
+ xsetenv("PWD", dd);
+ }
+ free(dd);
+}
+
+void exit_main(void)
+{
+ exit(*toys.optargs ? atoi(*toys.optargs) : 0);
+}
+
+