diff options
-rw-r--r-- | lisplib.c | 2 | ||||
-rw-r--r-- | stdlib/path-test.tl | 13 | ||||
-rw-r--r-- | stream.c | 32 | ||||
-rw-r--r-- | stream.h | 2 | ||||
-rw-r--r-- | tests/018/path-test.tl | 6 | ||||
-rw-r--r-- | txr.1 | 107 |
6 files changed, 93 insertions, 69 deletions
@@ -190,7 +190,7 @@ static val path_test_set_entries(val dlt, val fun) lit("path-newer"), lit("path-older"), lit("path-same-object"), lit("path-private-to-me-p"), lit("path-strictly-private-to-me-p"), - lit("path-dir-empty"), lit("path-search"), + lit("path-dir-empty"), nil }; diff --git a/stdlib/path-test.tl b/stdlib/path-test.tl index 4d700167..04ed2055 100644 --- a/stdlib/path-test.tl +++ b/stdlib/path-test.tl @@ -185,16 +185,3 @@ (casequal ent (("." "..")) (t (return nil)))))))) - -(defun path-search (name : (path (getenv "PATH"))) - (if (empty name) - nil - (let ((pcomp (if (listp path) - path - (spl (if (starts-with "CYGNAL" (uname).sysname) #\; #\:) - path)))) - (each ((pc pcomp)) - (if (nequal pc "") - (let ((st (ignerr (stat (path-cat pc name))))) - (if (and st (path-executable-to-me-p st) (path-file-p st)) - (return st.path)))))))) @@ -103,6 +103,7 @@ val socket_error_s; struct cobj_class *stream_cls, *stdio_stream_cls; const wchli_t *path_sep_chars = wli("/"); +wchar_t path_var_sep_char = ':'; val top_stderr; @@ -4383,6 +4384,31 @@ static void fds_clobber(struct save_fds *fds, int flags) fds_subst_nosave(fds->suberr, STDERR_FILENO); } +val path_search(val name, val path_in) +{ + val self = lit("path-search"); + val ps = static_str(path_sep_chars); + + if (empty(name) || equal(name, lit(".")) || equal(name, lit(".."))) { + return nil; + } else if (break_str(name, ps)) { + return name; + } else { + val path = default_arg_strict(path_in, getenv_wrap(lit("PATH"))); + val spath = if3(listp(path), path, split_str(path, chr(path_var_sep_char))); + for (; spath; spath = cdr(spath)) { + val dir = car(spath); + val full = path_cat(dir, name); + char *full8 = utf8_dup_to(c_str(full, self)); + int res = access(full8, F_OK); + free(full8); + if (res == 0) + return full; + } + return nil; + } +} + #if HAVE_FORK_STUFF static val open_subprocess(val name, val mode_str, val args, val fun) { @@ -4998,9 +5024,10 @@ static void detect_path_separators(void) struct utsname un; if (uname(&un) >= 0) { - if (strncmp(un.sysname, "CYGNAL", 6) == 0) + if (strncmp(un.sysname, "CYGNAL", 6) == 0) { path_sep_chars = wli("\\/"); - return; + path_var_sep_char = ';'; + } } #endif } @@ -5528,6 +5555,7 @@ void stream_init(void) reg_fun(intern(lit("open-file"), user_package), func_n2o(open_file, 1)); reg_fun(intern(lit("open-fileno"), user_package), func_n2o(open_fileno, 1)); reg_fun(intern(lit("open-tail"), user_package), func_n3o(open_tail, 1)); + reg_fun(intern(lit("path-search"), user_package), func_n2o(path_search, 1)); reg_fun(intern(lit("open-command"), user_package), func_n2o(open_command, 1)); reg_fun(intern(lit("open-pipe"), user_package), func_n2(open_command)); reg_fun(intern(lit("open-process"), user_package), func_n3o(open_process, 2)); @@ -152,6 +152,7 @@ extern val socket_error_s; #endif extern const wchli_t *path_sep_chars; +extern wchar_t path_var_sep_char; extern val top_stderr; @@ -245,6 +246,7 @@ val open_file(val path, val mode_str); val open_fileno(val fd, val mode_str); val open_tail(val path, val mode_str, val seek_end_p); val open_command(val path, val mode_str); +val path_search(val name, val path_in); val open_process(val path, val mode_str, val args); val make_catenated_stream(val stream_list); val make_catenated_stream_v(struct args *streams); diff --git a/tests/018/path-test.tl b/tests/018/path-test.tl index 919186ac..d3aa6dce 100644 --- a/tests/018/path-test.tl +++ b/tests/018/path-test.tl @@ -15,4 +15,8 @@ (path-search "sh" "AlMoStCeRtAiNlLyNoNeXisTenT:/bin") "/bin/sh" (path-search "sh" "/bin") "/bin/sh" (path-search "sh" "/bin/") "/bin/sh" - (path-search "sh" ":/bin/") "/bin/sh") + (path-search "sh" ":/bin/") "/bin/sh" + (path-search "" "/bin") nil + (path-search "." "/bin") nil + (path-search ".." "/bin") nil + (path-search "foo/bar" "/bin") "foo/bar") @@ -68630,76 +68630,79 @@ device. .coNP Function @ path-search .synb -.mets (path-search < program-name <> [ search-path ]) +.mets (path-search < name <> [ search-path ]) .syne .desc The .code path-search -function searches for an executable program whose name is given by the -.meta program-name -string argument. If the program is found, then the full path to that -program is returned, otherwise +function searches for the existence of a filesystem object named by +.meta name +in the directories specified +.metn search-path . + +If +.meta name +is the empty string or one of the two strings +.str . +(dot) +or +.str .. +(dotdot), +then .code nil -is returned. +is returned. If +.meta name +contains any path separator characters (any of the set of characters +found in the +.code path-sep-chars +string) then the function returns +.meta name +without performing any search. In all these trivial cases, the +.meta search-path +argument is ignored. -If the +The .meta search-path -argument is omitted, then -.code path-search -uses the search path given in the +argument, if present, may be a string or a list of strings. +If omitted, then it takes on the value of the .code PATH -environment variable. This path is decomposed into components according -to the separator character, which may be +environment variable if that variable exists, or else takes on +the value +.code nil +indicating an empty search path. + +If +.meta search-path +is a string, it is converted to a list of directories by splitting on the +separator character, which may be .code : (colon) or .code ; -(semicolon) depending on the system. Then, for each component of the path, +(semicolon) depending on the system. Then, for each directory in the list, .code path-search affixes the -.meta program-name +.meta name to that component, as if using the .code path-cat -function, and tests whether the resulting path is an existing file object -according to -.code path-file-p -and which is executable according to -.codn path-executable-to-me-p . - -The -.code search-path -argument may be specified as a string, which is taken instead of the value -of the -.code PATH -environment variable, and decomposed into components in the same way. -The argument may also be specified as a list of strings, which are taken -to be components of a search path, and used as-is. - -If -.meta program-name -is the empty string or the search path is empty, the function returns -.codn nil . - -Components of the search path which are empty strings are ignored. - -If -.meta program-name -is a string which includes path-separator characters, or if -.meta program-name -is one of the special names -.str . -or -.strn .. , -the behavior may produce a different result from the host platform's native -path-search strategy. +function, and tests whether the resulting path refers to an existing filesystem +object. +If so, then the search terminates and that resulting path is returned. +If the entire list is traversed without finding a filesystem object, then +.code nil +is returned. +If any error whatsoever occurs while determining whether the resulting path +exists, the situation is treated as nonexistence, and the search continues. -The behavior of +Note: subtle discrepancies may exist between +.code path-search +and the host platform's mechanisms for searching for an executable program. +For instance, since .code path-search -may also deviate from the host platform's native path-search strategy -due to issues of permissions. If an executable file is found in the path, -but is not executable to the caller due to permissions, it is treated -as nonexistent, which means that the search can find a same-named file -later in the search path. +is interested in existence only, it may return a path which exists, but is +not executable. Whereas a path searching implementation which tests for +executability will in that case continue searching, and not return that +path. .SS* Unix Credentials |