hush: fix memory leak. it was actually rather invloved problem.
Now finally glob/variable expansion is done IN THE RIGHT ORDER! It opens up a possibility to cleanly fix remaining known bugs. function old new delta o_save_ptr 115 286 +171 o_save_ptr_helper - 115 +115 done_word 591 690 +99 o_get_last_ptr - 31 +31 expand_on_ifs 125 97 -28 add_string_to_strings 28 - -28 run_list 1895 1862 -33 debug_print_strings 42 - -42 add_strings_to_strings 126 - -126 expand_variables 1550 1394 -156 o_debug_list 168 - -168 expand_strvec_to_strvec 388 10 -378 ------------------------------------------------------------------------------ (add/remove: 2/4 grow/shrink: 2/4 up/down: 416/-959) Total: -543 bytes
This commit is contained in:
parent
ccce59d562
commit
b61e13d247
313
shell/hush.c
313
shell/hush.c
@ -104,6 +104,8 @@
|
||||
#define debug_printf_exec(...) do {} while (0)
|
||||
#define debug_printf_jobs(...) do {} while (0)
|
||||
#define debug_printf_expand(...) do {} while (0)
|
||||
#define debug_printf_glob(...) do {} while (0)
|
||||
#define debug_printf_list(...) do {} while (0)
|
||||
#define debug_printf_clean(...) do {} while (0)
|
||||
|
||||
#ifndef debug_printf
|
||||
@ -120,12 +122,27 @@
|
||||
|
||||
#ifndef debug_printf_jobs
|
||||
#define debug_printf_jobs(...) fprintf(stderr, __VA_ARGS__)
|
||||
#define DEBUG_SHELL_JOBS 1
|
||||
#define DEBUG_JOBS 1
|
||||
#else
|
||||
#define DEBUG_JOBS 0
|
||||
#endif
|
||||
|
||||
#ifndef debug_printf_expand
|
||||
#define debug_printf_expand(...) fprintf(stderr, __VA_ARGS__)
|
||||
#define DEBUG_EXPAND 1
|
||||
#else
|
||||
#define DEBUG_EXPAND 0
|
||||
#endif
|
||||
|
||||
#ifndef debug_printf_glob
|
||||
#define debug_printf_glob(...) fprintf(stderr, __VA_ARGS__)
|
||||
#define DEBUG_GLOB 1
|
||||
#else
|
||||
#define DEBUG_GLOB 0
|
||||
#endif
|
||||
|
||||
#ifndef debug_printf_list
|
||||
#define debug_printf_list(...) fprintf(stderr, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
/* Keep unconditionally on for now */
|
||||
@ -143,6 +160,17 @@ static const char *indenter(int i)
|
||||
#define DEBUG_CLEAN 1
|
||||
#endif
|
||||
|
||||
#if DEBUG_EXPAND
|
||||
static void debug_print_strings(const char *prefix, char **vv)
|
||||
{
|
||||
fprintf(stderr, "%s:\n", prefix);
|
||||
while (*vv)
|
||||
fprintf(stderr, " '%s'\n", *vv++);
|
||||
}
|
||||
#else
|
||||
#define debug_print_strings(prefix, vv) ((void)0)
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Leak hunting. Use hush_leaktool.sh for post-processing.
|
||||
@ -309,6 +337,7 @@ typedef struct {
|
||||
int length;
|
||||
int maxlen;
|
||||
smallint o_quote;
|
||||
smallint o_glob;
|
||||
smallint nonnull;
|
||||
smallint has_empty_slot;
|
||||
} o_string;
|
||||
@ -531,6 +560,18 @@ static int set_local_var(char *str, int flg_export);
|
||||
static void unset_local_var(const char *name);
|
||||
|
||||
|
||||
static int glob_needed(const char *s)
|
||||
{
|
||||
while (*s) {
|
||||
if (*s == '\\')
|
||||
s++;
|
||||
if (*s == '*' || *s == '[' || *s == '?')
|
||||
return 1;
|
||||
s++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char **add_strings_to_strings(int need_xstrdup, char **strings, char **add)
|
||||
{
|
||||
int i;
|
||||
@ -592,17 +633,6 @@ static char **alloc_ptrs(char **argv)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_EXPAND
|
||||
static void debug_print_strings(const char *prefix, char **vv)
|
||||
{
|
||||
fprintf(stderr, "%s:\n", prefix);
|
||||
while (*vv)
|
||||
fprintf(stderr, " '%s'\n", *vv++);
|
||||
}
|
||||
#else
|
||||
#define debug_print_strings(prefix, vv) ((void)0)
|
||||
#endif
|
||||
|
||||
|
||||
/* Function prototypes for builtins */
|
||||
static int builtin_cd(char **argv);
|
||||
@ -1260,7 +1290,33 @@ static void o_addQstr(o_string *o, const char *str, int len)
|
||||
* o_finalize_list() operation post-processes this structure - calculates
|
||||
* and stores actual char* ptrs in list[]. Oh, it NULL terminates it as well.
|
||||
*/
|
||||
static int o_save_ptr(o_string *o, int n)
|
||||
#if DEBUG_EXPAND || DEBUG_GLOB
|
||||
static void debug_print_list(const char *prefix, o_string *o, int n)
|
||||
{
|
||||
char **list = (char**)o->data;
|
||||
int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
|
||||
int i = 0;
|
||||
fprintf(stderr, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d\n",
|
||||
prefix, list, n, string_start, o->length, o->maxlen);
|
||||
while (i < n) {
|
||||
fprintf(stderr, " list[%d]=%d '%s' %p\n", i, (int)list[i],
|
||||
o->data + (int)list[i] + string_start,
|
||||
o->data + (int)list[i] + string_start);
|
||||
i++;
|
||||
}
|
||||
if (n) {
|
||||
const char *p = o->data + (int)list[n - 1] + string_start;
|
||||
fprintf(stderr, " total_sz:%d\n", (p + strlen(p) + 1) - o->data);
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define debug_print_list(prefix, o, n) ((void)0)
|
||||
#endif
|
||||
|
||||
/* n = o_save_ptr_helper(str, n) "starts new string" by storing an index value
|
||||
* in list[n] so that it points past last stored byte so far.
|
||||
* It returns n+1. */
|
||||
static int o_save_ptr_helper(o_string *o, int n)
|
||||
{
|
||||
char **list = (char**)o->data;
|
||||
int string_start;
|
||||
@ -1270,26 +1326,27 @@ static int o_save_ptr(o_string *o, int n)
|
||||
string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
|
||||
string_len = o->length - string_start;
|
||||
if (!(n & 0xf)) { /* 0, 0x10, 0x20...? */
|
||||
//bb_error_msg("list[%d]=%d string_start=%d (growing)", n, string_len, string_start);
|
||||
debug_printf_list("list[%d]=%d string_start=%d (growing)", n, string_len, string_start);
|
||||
/* list[n] points to string_start, make space for 16 more pointers */
|
||||
o->maxlen += 0x10 * sizeof(list[0]);
|
||||
o->data = xrealloc(o->data, o->maxlen + 1);
|
||||
list = (char**)o->data;
|
||||
memmove(list + n + 0x10, list + n, string_len);
|
||||
o->length += 0x10 * sizeof(list[0]);
|
||||
}
|
||||
//else bb_error_msg("list[%d]=%d string_start=%d", n, string_len, string_start);
|
||||
} else
|
||||
debug_printf_list("list[%d]=%d string_start=%d", n, string_len, string_start);
|
||||
} else {
|
||||
/* We have empty slot at list[n], reuse without growth */
|
||||
string_start = ((n+1 + 0xf) & ~0xf) * sizeof(list[0]); /* NB: n+1! */
|
||||
string_len = o->length - string_start;
|
||||
//bb_error_msg("list[%d]=%d string_start=%d (empty slot)", n, string_len, string_start);
|
||||
debug_printf_list("list[%d]=%d string_start=%d (empty slot)", n, string_len, string_start);
|
||||
o->has_empty_slot = 0;
|
||||
}
|
||||
list[n] = (char*)string_len;
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
/* "What was our last o_save_ptr'ed position (byte offset relative o->data)?" */
|
||||
static int o_get_last_ptr(o_string *o, int n)
|
||||
{
|
||||
char **list = (char**)o->data;
|
||||
@ -1298,14 +1355,89 @@ static int o_get_last_ptr(o_string *o, int n)
|
||||
return ((int)list[n-1]) + string_start;
|
||||
}
|
||||
|
||||
/* Convert every \x to x in-place, return ptr past NUL. */
|
||||
static char *unbackslash(char *src)
|
||||
{
|
||||
char *dst = src;
|
||||
while (1) {
|
||||
if (*src == '\\')
|
||||
src++;
|
||||
if ((*dst++ = *src++) == '\0')
|
||||
break;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
static int o_glob(o_string *o, int n)
|
||||
{
|
||||
glob_t globdata;
|
||||
int gr;
|
||||
char *pattern;
|
||||
|
||||
debug_printf_glob("start o_glob: n:%d o->data:%p", n, o->data);
|
||||
if (!o->data)
|
||||
return o_save_ptr_helper(o, n);
|
||||
pattern = o->data + o_get_last_ptr(o, n);
|
||||
debug_printf_glob("glob pattern '%s'", pattern);
|
||||
if (!glob_needed(pattern)) {
|
||||
literal:
|
||||
o->length = unbackslash(pattern) - o->data;
|
||||
debug_printf_glob("glob pattern '%s' is literal", pattern);
|
||||
return o_save_ptr_helper(o, n);
|
||||
}
|
||||
|
||||
memset(&globdata, 0, sizeof(globdata));
|
||||
gr = glob(pattern, 0, NULL, &globdata);
|
||||
debug_printf_glob("glob('%s'):%d\n", pattern, gr);
|
||||
if (gr == GLOB_NOSPACE)
|
||||
bb_error_msg_and_die("out of memory during glob");
|
||||
if (gr == GLOB_NOMATCH) {
|
||||
globfree(&globdata);
|
||||
goto literal;
|
||||
}
|
||||
if (gr != 0) { /* GLOB_ABORTED ? */
|
||||
//TODO: testcase for bad glob pattern behavior
|
||||
bb_error_msg("glob(3) error %d on '%s'", gr, pattern);
|
||||
}
|
||||
if (globdata.gl_pathv && globdata.gl_pathv[0]) {
|
||||
char **argv = globdata.gl_pathv;
|
||||
o->length = pattern - o->data; /* "forget" pattern */
|
||||
while (1) {
|
||||
o_addstr(o, *argv, strlen(*argv) + 1);
|
||||
n = o_save_ptr_helper(o, n);
|
||||
argv++;
|
||||
if (!*argv)
|
||||
break;
|
||||
}
|
||||
}
|
||||
globfree(&globdata);
|
||||
if (DEBUG_GLOB)
|
||||
debug_print_list("o_glob returning", o, n);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* o_save_ptr_helper + but glob the string so far remembered
|
||||
* if o->o_glob == 1 */
|
||||
static int o_save_ptr(o_string *o, int n)
|
||||
{
|
||||
if (o->o_glob)
|
||||
return o_glob(o, n); /* o_save_ptr_helper is inside */
|
||||
return o_save_ptr_helper(o, n);
|
||||
}
|
||||
|
||||
/* "Please convert list[n] to real char* ptrs, and NULL terminate it." */
|
||||
static char **o_finalize_list(o_string *o, int n)
|
||||
{
|
||||
char **list = (char**)o->data;
|
||||
char **list;
|
||||
int string_start;
|
||||
|
||||
o_save_ptr(o, n); /* force growth for list[n] if necessary */
|
||||
string_start = ((n+1 + 0xf) & ~0xf) * sizeof(list[0]);
|
||||
list[n] = NULL;
|
||||
n = o_save_ptr(o, n); /* force growth for list[n] if necessary */
|
||||
if (DEBUG_EXPAND)
|
||||
debug_print_list("finalized", o, n);
|
||||
debug_printf_expand("finalized n:%d", n);
|
||||
list = (char**)o->data;
|
||||
string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
|
||||
list[--n] = NULL;
|
||||
while (n) {
|
||||
n--;
|
||||
list[n] = o->data + (int)list[n] + string_start;
|
||||
@ -1313,28 +1445,6 @@ static char **o_finalize_list(o_string *o, int n)
|
||||
return list;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_EXPAND
|
||||
static void o_debug_list(const char *prefix, o_string *o, int n)
|
||||
{
|
||||
char **list = (char**)o->data;
|
||||
int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
|
||||
int i = 0;
|
||||
fprintf(stderr, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d\n",
|
||||
prefix, list, n, string_start, o->length, o->maxlen);
|
||||
while (i < n) {
|
||||
fprintf(stderr, " list[%d]=%d '%s'\n", i, (int)list[i],
|
||||
o->data + (int)list[i] + string_start);
|
||||
i++;
|
||||
}
|
||||
if (n) {
|
||||
const char *p = o->data + (int)list[n] + string_start;
|
||||
fprintf(stderr, " total_sz:%d\n", (p + strlen(p) + 1) - o->data);
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define o_debug_list(prefix, o, n) ((void)0)
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* in_str support
|
||||
@ -1782,7 +1892,7 @@ static int checkjobs(struct pipe* fg_pipe)
|
||||
while ((childpid = waitpid(-1, &status, attributes)) > 0) {
|
||||
const int dead = WIFEXITED(status) || WIFSIGNALED(status);
|
||||
|
||||
#ifdef DEBUG_SHELL_JOBS
|
||||
#if DEBUG_JOBS
|
||||
if (WIFSTOPPED(status))
|
||||
debug_printf_jobs("pid %d stopped by sig %d (exitcode %d)\n",
|
||||
childpid, WSTOPSIG(status), WEXITSTATUS(status));
|
||||
@ -2502,11 +2612,11 @@ static int expand_on_ifs(o_string *output, int n, const char *str)
|
||||
if (!*str) /* EOL - do not finalize word */
|
||||
break;
|
||||
o_addchr(output, '\0');
|
||||
o_debug_list("expand_on_ifs", output, n);
|
||||
debug_print_list("expand_on_ifs", output, n);
|
||||
n = o_save_ptr(output, n);
|
||||
str += strspn(str, ifs); /* skip ifs chars */
|
||||
}
|
||||
o_debug_list("expand_on_ifs[1]", output, n);
|
||||
debug_print_list("expand_on_ifs[1]", output, n);
|
||||
return n;
|
||||
}
|
||||
|
||||
@ -2530,9 +2640,9 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
|
||||
ored_ch = 0;
|
||||
|
||||
debug_printf_expand("expand_vars_to_list: arg '%s'\n", arg);
|
||||
o_debug_list("expand_vars_to_list", output, n);
|
||||
debug_print_list("expand_vars_to_list", output, n);
|
||||
n = o_save_ptr(output, n);
|
||||
o_debug_list("expand_vars_to_list[0]", output, n);
|
||||
debug_print_list("expand_vars_to_list[0]", output, n);
|
||||
|
||||
while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) {
|
||||
#if ENABLE_HUSH_TICK
|
||||
@ -2540,7 +2650,7 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
|
||||
#endif
|
||||
|
||||
o_addQstr(output, arg, p - arg);
|
||||
o_debug_list("expand_vars_to_list[1]", output, n);
|
||||
debug_print_list("expand_vars_to_list[1]", output, n);
|
||||
arg = ++p;
|
||||
p = strchr(p, SPECIAL_VAR_SYMBOL);
|
||||
|
||||
@ -2575,9 +2685,9 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
|
||||
/* this argv[] is not empty and not last:
|
||||
* put terminating NUL, start new word */
|
||||
o_addchr(output, '\0');
|
||||
o_debug_list("expand_vars_to_list[2]", output, n);
|
||||
debug_print_list("expand_vars_to_list[2]", output, n);
|
||||
n = o_save_ptr(output, n);
|
||||
o_debug_list("expand_vars_to_list[3]", output, n);
|
||||
debug_print_list("expand_vars_to_list[3]", output, n);
|
||||
}
|
||||
}
|
||||
} else
|
||||
@ -2589,7 +2699,7 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
|
||||
if (++i >= global_argc)
|
||||
break;
|
||||
o_addchr(output, '\0');
|
||||
o_debug_list("expand_vars_to_list[4]", output, n);
|
||||
debug_print_list("expand_vars_to_list[4]", output, n);
|
||||
n = o_save_ptr(output, n);
|
||||
}
|
||||
} else { /* quoted $*: add as one word */
|
||||
@ -2647,9 +2757,9 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
|
||||
} /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */
|
||||
|
||||
if (arg[0]) {
|
||||
o_debug_list("expand_vars_to_list[a]", output, n);
|
||||
debug_print_list("expand_vars_to_list[a]", output, n);
|
||||
o_addQstr(output, arg, strlen(arg) + 1);
|
||||
o_debug_list("expand_vars_to_list[b]", output, n);
|
||||
debug_print_list("expand_vars_to_list[b]", output, n);
|
||||
} else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */
|
||||
&& !(ored_ch & 0x80) /* and all vars were not quoted. */
|
||||
) {
|
||||
@ -2662,106 +2772,33 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
|
||||
return n;
|
||||
}
|
||||
|
||||
static char **expand_variables(char **argv, char or_mask)
|
||||
static char **expand_variables(char **argv, int or_mask)
|
||||
{
|
||||
int n;
|
||||
char **list;
|
||||
char **v;
|
||||
o_string output = NULL_O_STRING;
|
||||
|
||||
if (or_mask & 0x100)
|
||||
output.o_glob = 1;
|
||||
|
||||
n = 0;
|
||||
v = argv;
|
||||
while (*v)
|
||||
n = expand_vars_to_list(&output, n, *v++, or_mask);
|
||||
o_debug_list("expand_variables", &output, n);
|
||||
while (*v) {
|
||||
n = expand_vars_to_list(&output, n, *v, (char)or_mask);
|
||||
v++;
|
||||
}
|
||||
debug_print_list("expand_variables", &output, n);
|
||||
|
||||
/* output.data (malloced in one block) gets returned in "list" */
|
||||
list = o_finalize_list(&output, n);
|
||||
debug_print_strings("expand_variables[1]", list);
|
||||
return list;
|
||||
}
|
||||
|
||||
/* Remove non-backslashed backslashes and add to "strings" vector.
|
||||
* XXX broken if the last character is '\\', check that before calling.
|
||||
*/
|
||||
static char **add_unq_string_to_strings(char **strings, const char *src)
|
||||
{
|
||||
int cnt;
|
||||
const char *s;
|
||||
char *v, *dest;
|
||||
|
||||
for (cnt = 1, s = src; s && *s; s++) {
|
||||
if (*s == '\\') s++;
|
||||
cnt++;
|
||||
}
|
||||
v = dest = xmalloc(cnt);
|
||||
for (s = src; s && *s; s++, dest++) {
|
||||
if (*s == '\\') s++;
|
||||
*dest = *s;
|
||||
}
|
||||
*dest = '\0';
|
||||
|
||||
return add_string_to_strings(strings, v);
|
||||
}
|
||||
|
||||
/* XXX broken if the last character is '\\', check that before calling */
|
||||
static int glob_needed(const char *s)
|
||||
{
|
||||
for (; *s; s++) {
|
||||
if (*s == '\\')
|
||||
s++;
|
||||
if (strchr("*[?", *s))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void xglob(char ***pglob, const char *pattern)
|
||||
{
|
||||
if (glob_needed(pattern)) {
|
||||
glob_t globdata;
|
||||
int gr;
|
||||
|
||||
memset(&globdata, 0, sizeof(globdata));
|
||||
gr = glob(pattern, 0, NULL, &globdata);
|
||||
debug_printf("glob returned %d\n", gr);
|
||||
if (gr == GLOB_NOSPACE)
|
||||
bb_error_msg_and_die("out of memory during glob");
|
||||
if (gr == GLOB_NOMATCH) {
|
||||
globfree(&globdata);
|
||||
goto literal;
|
||||
}
|
||||
if (gr != 0) { /* GLOB_ABORTED ? */
|
||||
bb_error_msg("glob(3) error %d on '%s'", gr, pattern);
|
||||
//TODO: testcase for bad glob pattern behavior
|
||||
}
|
||||
if (globdata.gl_pathv && globdata.gl_pathv[0])
|
||||
*pglob = add_strings_to_strings(1, *pglob, globdata.gl_pathv);
|
||||
globfree(&globdata);
|
||||
return;
|
||||
}
|
||||
|
||||
literal:
|
||||
/* quote removal, or more accurately, backslash removal */
|
||||
*pglob = add_unq_string_to_strings(*pglob, pattern);
|
||||
debug_print_strings("after xglob", *pglob);
|
||||
}
|
||||
|
||||
//LEAK is here: callers expect result to be free()able, but we
|
||||
//actually require free_strings(). free() leaks strings.
|
||||
static char **expand_strvec_to_strvec(char **argv)
|
||||
{
|
||||
char **exp;
|
||||
char **res = xzalloc(sizeof(res[0]));
|
||||
|
||||
debug_print_strings("expand_strvec_to_strvec: pre expand", argv);
|
||||
exp = argv = expand_variables(argv, 0);
|
||||
debug_print_strings("expand_strvec_to_strvec: post expand", argv);
|
||||
while (*argv) {
|
||||
xglob(&res, *argv++);
|
||||
}
|
||||
free(exp);
|
||||
debug_print_strings("expand_strvec_to_strvec: res", res);
|
||||
return res;
|
||||
return expand_variables(argv, 0x100);
|
||||
}
|
||||
|
||||
/* used for expansion of right hand of assignments */
|
||||
|
Loading…
Reference in New Issue
Block a user