Hi, while trying to cleanup the debianutils/which command I've spotted a minor glitch in bb's find_execable function as it is not able to handle zero lenght prefixes in the PATH variable:
http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html 8.3 Other Environment Variables PATH snip A zero-length prefix is a legacy feature that indicates the current working directory. It appears as two adjacent colons ( "::" ), as an initial colon preceding the rest of the list, or as a trailing colon following the rest of the list. A strictly conforming application shall use an actual pathname (such as .) to represent the current working directory in PATH . The real which command (really a script) in debianutils 4.4 does in fact handle zero-length prefixes in PATH correctly: for ELEMENT in $PATH; do if [ -z "$ELEMENT" ]; then ELEMENT=. fi So PATCH 1 actually fixes this, the size impact is huge and therefore it is dependent on CONFIG_DEKTOP: find_execable 81 182 +101 as parsing the PATH was not trivial as once you've started it is hard to tell if a delimiter was a leading, trailing or double colon. I've tried and retried but no solution was satisfactory as always some corner case was not parsed correctly, so I changed strategy and normalize the PATH variable so that it is easier to parse before starting the parsing. Hints about a better solution with less code are welcome!!! --- libbb/execable.c.orig 2013-06-02 13:56:34.000000000 +0200 +++ libbb/execable.c 2014-05-01 20:30:02.966512357 +0200 @@ -30,9 +30,37 @@ */ char* FAST_FUNC find_execable(const char *filename, char **PATHp) { + /* http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html + * 8.3 Other Environment Variables - PATH + * A zero-length prefix is a legacy feature that indicates the current + * working directory. It appears as two adjacent colons ( "::" ), as an + * initial colon preceding the rest of the list, or as a trailing colon + * following the rest of the list. + */ + + IF_DESKTOP(int len;) char *p, *n; p = *PATHp; +#if ENABLE_DESKTOP + /* Normalize PATH and add . for cwd where needed */ + while ((n = strstr(p, "::")) != NULL) { /* Double :: */ + *n = '\0'; + n += 2; + p = xasprintf("%s:.:%s", p, n); + } + + if (*p == ':' && p[1] != '.') { /* Leading single : */ + p = xasprintf(".%s", p); + } + + len = strlen(p) - 1; + if (p[len] == ':' && p[len - 1] != '.') { /* Trailing single : */ + p = xasprintf("%s.:", p); + } + + *PATHp = p; +#endif while (p) { n = strchr(p, ':'); if (n) The find execable_function and the exists_execable function are used only in which and ifupdown and this change seems not to affect them negatively. PATCH 2 removes the #if ENABLE_DESKTOP part from which relative to the -a option and unifies the code. The size increase is negligible vs the version with ENABLE_DESKTOP disabled: scripts/bloat-o-meter busybox.old.nodesktop busybox_unstripped function old new delta which_main 193 202 +9 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/0 up/down: 9/0) Total: 9 bytes So it makes sense to trade 9 bytes for the improved functionality and reduced number of code lines. In the end our which command looks like this: int which_main(int argc UNUSED_PARAM, char **argv) { int found; char *path; char *p; char *tmp; int status = 0; opt_complementary = "-1"; /* at least one argument */ getopt32(argv, "a"); argv += optind; do { found = 0; /* If file contains a slash don't use PATH */ if (strchr(*argv, '/') && execable_file(*argv)) { /* returns non-negative number on success, or EOF (-1) on error */ found = puts(*argv); } else { p = getenv("PATH"); if (p == NULL) { p = (char *)bb_default_root_path; } path = tmp = xstrdup(p); while ((p = find_execable(*argv, &tmp)) != NULL) { found = puts(p); free(p); if (!option_mask32) /* -a not set*/ break; } free(path); } status |= (!found); } while (*(++argv) != NULL); fflush_stdout_and_exit(status); } Hints, critics and improvements are welcome, Ciao, Tito P.S.: the patches could be applied indipendently.
Find_execable function is not able to handle zero lenght prefixes in the PATH variable as: two adjacent colons ( "::" ), an initial colon preceding the rest of the list, or as a trailing colon following the rest of the list. Signed-off by Tito Ragusa <farmat...@tiscali.it> --- libbb/execable.c.orig 2013-06-02 13:56:34.000000000 +0200 +++ libbb/execable.c 2014-05-01 20:30:02.966512357 +0200 @@ -30,9 +30,37 @@ */ char* FAST_FUNC find_execable(const char *filename, char **PATHp) { + /* http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html + * 8.3 Other Environment Variables - PATH + * A zero-length prefix is a legacy feature that indicates the current + * working directory. It appears as two adjacent colons ( "::" ), as an + * initial colon preceding the rest of the list, or as a trailing colon + * following the rest of the list. + */ + + IF_DESKTOP(int len;) char *p, *n; p = *PATHp; +#if ENABLE_DESKTOP + /* Normalize PATH and add . for cwd where needed */ + while ((n = strstr(p, "::")) != NULL) { /* Double :: */ + *n = '\0'; + n += 2; + p = xasprintf("%s:.:%s", p, n); + } + + if (*p == ':' && p[1] != '.') { /* Leading single : */ + p = xasprintf(".%s", p); + } + + len = strlen(p) - 1; + if (p[len] == ':' && p[len - 1] != '.') { /* Trailing single : */ + p = xasprintf("%s.:", p); + } + + *PATHp = p; +#endif while (p) { n = strchr(p, ':'); if (n)
This patch removes the #if ENABLE_DESKTOP part from which relative to the -a option and unifies the code. Signed-off by Tito Ragusa <farmat...@tiscali.it> --- debianutils/which.c.orig 2013-06-02 13:56:34.000000000 +0200 +++ debianutils/which.c 2014-05-01 20:40:17.289525924 +0200 @@ -24,75 +24,37 @@ int which_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int which_main(int argc UNUSED_PARAM, char **argv) { - IF_DESKTOP(int opt;) - int status = EXIT_SUCCESS; + int found; char *path; char *p; + char *tmp; + int status = 0; - opt_complementary = "-1"; /* at least one argument */ - IF_DESKTOP(opt =) getopt32(argv, "a"); + opt_complementary = "-1"; /* at least one argument */ + getopt32(argv, "a"); argv += optind; - - /* This matches what is seen on e.g. ubuntu. - * "which" there is a shell script. */ - path = getenv("PATH"); - if (!path) { - path = (char*)bb_PATH_root_path; - putenv(path); - path += 5; /* skip "PATH=" */ - } - + do { -#if ENABLE_DESKTOP -/* Much bloat just to support -a */ - if (strchr(*argv, '/')) { - if (execable_file(*argv)) { - puts(*argv); - continue; - } - status = EXIT_FAILURE; + found = 0; + /* If file contains a slash don't use PATH */ + if (strchr(*argv, '/') && execable_file(*argv)) { + /* returns non-negative number on success, or EOF (-1) on error */ + found = puts(*argv); } else { - char *path2 = xstrdup(path); - char *tmp = path2; - - p = find_execable(*argv, &tmp); - if (!p) - status = EXIT_FAILURE; - else { - print: - puts(p); - free(p); - if (opt) { - /* -a: show matches in all PATH components */ - if (tmp) { - p = find_execable(*argv, &tmp); - if (p) - goto print; - } - } - } - free(path2); - } -#else -/* Just ignoring -a */ - if (strchr(*argv, '/')) { - if (execable_file(*argv)) { - puts(*argv); - continue; + p = getenv("PATH"); + if (p == NULL) { + p = (char *)bb_default_root_path; } - } else { - char *path2 = xstrdup(path); - char *tmp = path2; - p = find_execable(*argv, &tmp); - free(path2); - if (p) { - puts(p); + path = tmp = xstrdup(p); + while ((p = find_execable(*argv, &tmp)) != NULL) { + found = puts(p); free(p); - continue; + if (!option_mask32) /* -a not set*/ + break; } + free(path); } - status = EXIT_FAILURE; -#endif + status |= (!found); } while (*(++argv) != NULL); fflush_stdout_and_exit(status);
_______________________________________________ busybox mailing list busybox@busybox.net http://lists.busybox.net/mailman/listinfo/busybox