Fix the behavior of local shell variables to match that of bash and ash.
-Erik
diff --git a/hush.c b/hush.c
index cfe0b82..5a4966b 100644
--- a/hush.c
+++ b/hush.c
@@ -55,7 +55,6 @@
* to-do:
* port selected bugfixes from post-0.49 busybox lash - done?
* finish implementing reserved words: for, while, until, do, done
- * finish implementing local variable assignment
* change { and } from special chars to reserved words
* builtins: break, continue, eval, return, set, trap, ulimit
* test magic exec
@@ -325,6 +324,7 @@
static int builtin_jobs(struct child_prog *child);
static int builtin_pwd(struct child_prog *child);
static int builtin_read(struct child_prog *child);
+static int builtin_set(struct child_prog *child);
static int builtin_shift(struct child_prog *child);
static int builtin_source(struct child_prog *child);
static int builtin_umask(struct child_prog *child);
@@ -388,6 +388,11 @@
static void insert_bg_job(struct pipe *pi);
static void remove_bg_job(struct pipe *pi);
static void free_pipe(struct pipe *pi);
+/* local variable support */
+static char *get_local_var(const char *var);
+static int set_local_var(const char *s);
+static void unset_local_var(const char *name);
+
/* Table of built-in functions. They can be forked or not, depending on
* context: within pipes, they fork. As simple commands, they do not.
@@ -402,7 +407,8 @@
{"continue", "Continue for, while or until loop", builtin_not_written},
{"env", "Print all environment variables", builtin_env},
{"eval", "Construct and run shell command", builtin_not_written},
- {"exec", "Exec command, replacing this shell with the exec'd process", builtin_exec},
+ {"exec", "Exec command, replacing this shell with the exec'd process",
+ builtin_exec},
{"exit", "Exit from shell()", builtin_exit},
{"export", "Set environment variable", builtin_export},
{"fg", "Bring job into the foreground", builtin_fg_bg},
@@ -410,7 +416,7 @@
{"pwd", "Print current directory", builtin_pwd},
{"read", "Input environment variable", builtin_read},
{"return", "Return from a function", builtin_not_written},
- {"set", "Set/unset shell options", builtin_not_written},
+ {"set", "Set/unset shell local variables", builtin_set},
{"shift", "Shift positional parameters", builtin_shift},
{"trap", "Trap signals", builtin_not_written},
{"ulimit","Controls resource limits", builtin_not_written},
@@ -472,16 +478,41 @@
static int builtin_export(struct child_prog *child)
{
int res;
+ char *value, *name = child->argv[1];
- if (child->argv[1] == NULL) {
+ if (name == NULL) {
return (builtin_env(child));
}
- /* FIXME -- I leak memory. This will be
- * fixed up properly when we add local
- * variable support -- I hope */
- res = putenv(strdup(child->argv[1]));
+
+ value = strchr(name, '=');
+ if (!value) {
+ /* They are exporting something without an =VALUE.
+ * Assume this is a local shell variable they are exporting */
+ name = get_local_var(name);
+ if (! name ) {
+ error_msg("export failed");
+ return (EXIT_FAILURE);
+ }
+ /* FIXME -- I leak memory!!!!! */
+ value = malloc(strlen(child->argv[1]) + strlen(name) + 2);
+ sprintf(value, "%s=%s", child->argv[1], name);
+ } else {
+ /* Bourne shells always put exported variables into the
+ * local shell variable list. Do that first... */
+ set_local_var(name);
+ /* FIXME -- I leak memory!!!!! */
+ value = strdup(name);
+ }
+
+ /* FIXME -- I leak memory!!!!!
+ * It seems most putenv implementations place the very char* pointer
+ * we pass in directly into the environ array, so the memory holding
+ * this string has to be persistant. We can't even use the memory for
+ * the local shell variable list, since where that memory is keeps
+ * changing due to reallocs... */
+ res = putenv(value);
if (res)
- fprintf(stderr, "export: %s\n", strerror(errno));
+ perror_msg("export");
return (res);
}
@@ -616,6 +647,27 @@
return (res);
}
+/* built-in 'set VAR=value' handler */
+static int builtin_set(struct child_prog *child)
+{
+ int res;
+ char *temp = child->argv[1];
+
+ if (child->argv[1] == NULL) {
+ char **e = __shell_local_env;
+ if (e == NULL) return EXIT_FAILURE;
+ for (; *e; e++) {
+ puts(*e);
+ }
+ return EXIT_SUCCESS;
+ }
+ res = set_local_var(temp);
+ if (res)
+ fprintf(stderr, "set: %s\n", strerror(errno));
+ return (res);
+}
+
+
/* Built-in 'shift' handler */
static int builtin_shift(struct child_prog *child)
{
@@ -685,6 +737,7 @@
return EXIT_FAILURE;
}
unsetenv(child->argv[1]);
+ unset_local_var(child->argv[1]);
return EXIT_SUCCESS;
}
@@ -1610,7 +1663,27 @@
return gr;
}
-/* This is used to set local variables (i.e. stuff not going into the environment) */
+/* This is used to get/check local shell variables */
+static char *get_local_var(const char *s)
+{
+ char **p;
+ int len;
+
+ if (!s)
+ return NULL;
+ if (!__shell_local_env)
+ return NULL;
+ len = strlen(s);
+
+ for (p = __shell_local_env; *p; p++) {
+ if (memcmp(s, *p, len) == 0 && (*p)[len] == '=') {
+ return *p + len + 1;
+ }
+ }
+ return NULL;
+}
+
+/* This is used to set local shell variables */
static int set_local_var(const char *s)
{
char **ep;
@@ -1653,7 +1726,8 @@
result = -1;
goto done_already;
}
- memcpy((__ptr_t) new_environ, (__ptr_t) __shell_local_env, size * sizeof(char *));
+ memcpy((__ptr_t) new_environ, (__ptr_t) __shell_local_env,
+ size * sizeof(char *));
new_environ[size] = malloc (namelen + 1 + vallen + 1);
if (new_environ[size] == NULL) {
@@ -1688,26 +1762,35 @@
memcpy (&(*ep)[namelen + 1], value, vallen + 1);
}
+ /* One last little detail... If this variable is already
+ * in the environment we must set it there as well... */
+ tmp = getenv(name);
+ if (tmp) {
+ /* FIXME -- I leak memory!!!!! */
+ putenv(strdup(s));
+ }
+
done_already:
free(name);
return result;
}
-char *get_local_var(const char *var)
+static void unset_local_var(const char *name)
{
- char **p;
- int len;
+ char **ep, **dp;
+ size_t namelen;
- len = strlen(var);
-
- if (!__shell_local_env)
- return 0;
-
- for (p = __shell_local_env; *p; p++) {
- if (memcmp(var, *p, len) == 0 && (*p)[len] == '=')
- return *p + len + 1;
+ if (!name)
+ return;
+ namelen = strlen(name);
+ for (dp = ep = __shell_local_env; ep && *ep != NULL; ++ep) {
+ if (memcmp (*ep, name, namelen)==0 && (*ep)[namelen] == '=') {
+ *dp = *ep;
+ ++dp;
+ *ep = NULL;
+ break;
+ }
}
- return 0;
}
static int is_assignment(const char *s)
@@ -2112,8 +2195,9 @@
{
const char *p=NULL;
if (src->data) {
- p = get_local_var(src->data);
- if (!p) p = getenv(src->data);
+ p = getenv(src->data);
+ if (!p)
+ p = get_local_var(src->data);
}
if (p) parse_string(dest, ctx, p); /* recursion */
b_free(src);
diff --git a/shell/hush.c b/shell/hush.c
index cfe0b82..5a4966b 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -55,7 +55,6 @@
* to-do:
* port selected bugfixes from post-0.49 busybox lash - done?
* finish implementing reserved words: for, while, until, do, done
- * finish implementing local variable assignment
* change { and } from special chars to reserved words
* builtins: break, continue, eval, return, set, trap, ulimit
* test magic exec
@@ -325,6 +324,7 @@
static int builtin_jobs(struct child_prog *child);
static int builtin_pwd(struct child_prog *child);
static int builtin_read(struct child_prog *child);
+static int builtin_set(struct child_prog *child);
static int builtin_shift(struct child_prog *child);
static int builtin_source(struct child_prog *child);
static int builtin_umask(struct child_prog *child);
@@ -388,6 +388,11 @@
static void insert_bg_job(struct pipe *pi);
static void remove_bg_job(struct pipe *pi);
static void free_pipe(struct pipe *pi);
+/* local variable support */
+static char *get_local_var(const char *var);
+static int set_local_var(const char *s);
+static void unset_local_var(const char *name);
+
/* Table of built-in functions. They can be forked or not, depending on
* context: within pipes, they fork. As simple commands, they do not.
@@ -402,7 +407,8 @@
{"continue", "Continue for, while or until loop", builtin_not_written},
{"env", "Print all environment variables", builtin_env},
{"eval", "Construct and run shell command", builtin_not_written},
- {"exec", "Exec command, replacing this shell with the exec'd process", builtin_exec},
+ {"exec", "Exec command, replacing this shell with the exec'd process",
+ builtin_exec},
{"exit", "Exit from shell()", builtin_exit},
{"export", "Set environment variable", builtin_export},
{"fg", "Bring job into the foreground", builtin_fg_bg},
@@ -410,7 +416,7 @@
{"pwd", "Print current directory", builtin_pwd},
{"read", "Input environment variable", builtin_read},
{"return", "Return from a function", builtin_not_written},
- {"set", "Set/unset shell options", builtin_not_written},
+ {"set", "Set/unset shell local variables", builtin_set},
{"shift", "Shift positional parameters", builtin_shift},
{"trap", "Trap signals", builtin_not_written},
{"ulimit","Controls resource limits", builtin_not_written},
@@ -472,16 +478,41 @@
static int builtin_export(struct child_prog *child)
{
int res;
+ char *value, *name = child->argv[1];
- if (child->argv[1] == NULL) {
+ if (name == NULL) {
return (builtin_env(child));
}
- /* FIXME -- I leak memory. This will be
- * fixed up properly when we add local
- * variable support -- I hope */
- res = putenv(strdup(child->argv[1]));
+
+ value = strchr(name, '=');
+ if (!value) {
+ /* They are exporting something without an =VALUE.
+ * Assume this is a local shell variable they are exporting */
+ name = get_local_var(name);
+ if (! name ) {
+ error_msg("export failed");
+ return (EXIT_FAILURE);
+ }
+ /* FIXME -- I leak memory!!!!! */
+ value = malloc(strlen(child->argv[1]) + strlen(name) + 2);
+ sprintf(value, "%s=%s", child->argv[1], name);
+ } else {
+ /* Bourne shells always put exported variables into the
+ * local shell variable list. Do that first... */
+ set_local_var(name);
+ /* FIXME -- I leak memory!!!!! */
+ value = strdup(name);
+ }
+
+ /* FIXME -- I leak memory!!!!!
+ * It seems most putenv implementations place the very char* pointer
+ * we pass in directly into the environ array, so the memory holding
+ * this string has to be persistant. We can't even use the memory for
+ * the local shell variable list, since where that memory is keeps
+ * changing due to reallocs... */
+ res = putenv(value);
if (res)
- fprintf(stderr, "export: %s\n", strerror(errno));
+ perror_msg("export");
return (res);
}
@@ -616,6 +647,27 @@
return (res);
}
+/* built-in 'set VAR=value' handler */
+static int builtin_set(struct child_prog *child)
+{
+ int res;
+ char *temp = child->argv[1];
+
+ if (child->argv[1] == NULL) {
+ char **e = __shell_local_env;
+ if (e == NULL) return EXIT_FAILURE;
+ for (; *e; e++) {
+ puts(*e);
+ }
+ return EXIT_SUCCESS;
+ }
+ res = set_local_var(temp);
+ if (res)
+ fprintf(stderr, "set: %s\n", strerror(errno));
+ return (res);
+}
+
+
/* Built-in 'shift' handler */
static int builtin_shift(struct child_prog *child)
{
@@ -685,6 +737,7 @@
return EXIT_FAILURE;
}
unsetenv(child->argv[1]);
+ unset_local_var(child->argv[1]);
return EXIT_SUCCESS;
}
@@ -1610,7 +1663,27 @@
return gr;
}
-/* This is used to set local variables (i.e. stuff not going into the environment) */
+/* This is used to get/check local shell variables */
+static char *get_local_var(const char *s)
+{
+ char **p;
+ int len;
+
+ if (!s)
+ return NULL;
+ if (!__shell_local_env)
+ return NULL;
+ len = strlen(s);
+
+ for (p = __shell_local_env; *p; p++) {
+ if (memcmp(s, *p, len) == 0 && (*p)[len] == '=') {
+ return *p + len + 1;
+ }
+ }
+ return NULL;
+}
+
+/* This is used to set local shell variables */
static int set_local_var(const char *s)
{
char **ep;
@@ -1653,7 +1726,8 @@
result = -1;
goto done_already;
}
- memcpy((__ptr_t) new_environ, (__ptr_t) __shell_local_env, size * sizeof(char *));
+ memcpy((__ptr_t) new_environ, (__ptr_t) __shell_local_env,
+ size * sizeof(char *));
new_environ[size] = malloc (namelen + 1 + vallen + 1);
if (new_environ[size] == NULL) {
@@ -1688,26 +1762,35 @@
memcpy (&(*ep)[namelen + 1], value, vallen + 1);
}
+ /* One last little detail... If this variable is already
+ * in the environment we must set it there as well... */
+ tmp = getenv(name);
+ if (tmp) {
+ /* FIXME -- I leak memory!!!!! */
+ putenv(strdup(s));
+ }
+
done_already:
free(name);
return result;
}
-char *get_local_var(const char *var)
+static void unset_local_var(const char *name)
{
- char **p;
- int len;
+ char **ep, **dp;
+ size_t namelen;
- len = strlen(var);
-
- if (!__shell_local_env)
- return 0;
-
- for (p = __shell_local_env; *p; p++) {
- if (memcmp(var, *p, len) == 0 && (*p)[len] == '=')
- return *p + len + 1;
+ if (!name)
+ return;
+ namelen = strlen(name);
+ for (dp = ep = __shell_local_env; ep && *ep != NULL; ++ep) {
+ if (memcmp (*ep, name, namelen)==0 && (*ep)[namelen] == '=') {
+ *dp = *ep;
+ ++dp;
+ *ep = NULL;
+ break;
+ }
}
- return 0;
}
static int is_assignment(const char *s)
@@ -2112,8 +2195,9 @@
{
const char *p=NULL;
if (src->data) {
- p = get_local_var(src->data);
- if (!p) p = getenv(src->data);
+ p = getenv(src->data);
+ if (!p)
+ p = get_local_var(src->data);
}
if (p) parse_string(dest, ctx, p); /* recursion */
b_free(src);