diff options
-rw-r--r-- | ChangeLog | 13 | ||||
-rw-r--r-- | configure.in | 6 | ||||
-rw-r--r-- | doc/make.texi | 32 | ||||
-rw-r--r-- | function.c | 163 | ||||
-rw-r--r-- | tests/ChangeLog | 8 | ||||
-rw-r--r-- | tests/scripts/functions/abspath | 81 | ||||
-rw-r--r-- | tests/scripts/functions/realpath | 71 |
7 files changed, 368 insertions, 6 deletions
@@ -1,3 +1,16 @@ +2004-11-30 Boris Kolpackov <boris@kolpackov.net> + + Implementation of `realpath' and `abspath' built-in functions. + + * configure.in: Check for realpath. + * function.c (abspath): Return an absolute file name that does + not contain any `.' or `..' components, nor repeated `/'. + * function.c (func_abspath): For each name call abspath. + * function.c (func_realpath): For each name call realpath + from libc or delegate to abspath if realpath is not available. + * doc/make.texi (Functions for File Names): Document new functions. + * doc/make.texi (Quick Reference): Ditto. + 2004-11-28 Paul D. Smith <psmith@gnu.org> * main.c (main) [WINDOWS32]: Remove any trailing slashes from -C diff --git a/configure.in b/configure.in index 15c2b88..39a8b66 100644 --- a/configure.in +++ b/configure.in @@ -134,9 +134,9 @@ if test "$ac_cv_func_gettimeofday" = yes; then fi AC_CHECK_FUNCS( memcpy memmove strchr strdup mkstemp mktemp fdopen \ - bsd_signal dup2 getcwd sigsetmask sigaction getgroups \ - seteuid setegid setlinebuf setreuid setregid setvbuf pipe \ - strerror strsignal) + bsd_signal dup2 getcwd realpath sigsetmask sigaction \ + getgroups seteuid setegid setlinebuf setreuid setregid \ + setvbuf pipe strerror strsignal) AC_FUNC_SETVBUF_REVERSED diff --git a/doc/make.texi b/doc/make.texi index 14d7278..e814ec2 100644 --- a/doc/make.texi +++ b/doc/make.texi @@ -6188,6 +6188,27 @@ wildcard characters (as in shell file name patterns). The result of @code{wildcard} is a space-separated list of the names of existing files that match the pattern. @xref{Wildcards, ,Using Wildcard Characters in File Names}. + +@item $(realpath @var{names}@dots{}) +@findex realpath +@cindex realpath +@cindex file name, realpath of +For each file name in @var{names} return the canonical absolute name. +A canonical name does not contain any @code{.} or @code{..} components, +nor any repeated path separators (@code{/}) or symlinks. In case of a +failure the empty string is returned. Consult the @code{realpath(3)} +documentation for a list of possible failure causes. + +@item $(abspath @var{names}@dots{}) +@findex abspath +@cindex abspath +@cindex file name, abspath of +For each file name in @var{names} return an absolute name that does +not contain any @code{.} or @code{..} components, nor any repeated path +separators (@code{/}). Note that in contrast to @code{realpath} +function, @code{abspath} does not resolve symlinks and does not require +the file names to refer to an existing file or directory. Use the +@code{wildcard} function to test for existence. @end table @node Foreach Function, If Function, File Name Functions, Functions @@ -9756,6 +9777,17 @@ Find file names matching a shell file name pattern (@emph{not} a @samp{%} pattern).@* @xref{Wildcard Function, ,The Function @code{wildcard}}. +@item $(realpath @var{names}@dots{}) +For each file name in @var{names}, expand to an absolute name that +does not contain any @code{.}, @code{..}, nor symlinks.@* +@xref{File Name Functions, ,Functions for File Names}. + +@item $(abspath @var{names}@dots{}) +For each file name in @var{names}, expand to an absolute name that +does not contain any @code{.} or @code{..} components, but preserves +symlinks.@* +@xref{File Name Functions, ,Functions for File Names}. + @item $(error @var{text}@dots{}) When this function is evaluated, @code{make} generates a fatal error @@ -1715,7 +1715,7 @@ func_shell (char *o, char **argv, const char *funcname) equality. Return is string-boolean, ie, the empty string is false. */ static char * -func_eq (char* o, char **argv, char *funcname) +func_eq (char *o, char **argv, char *funcname) { int result = ! strcmp (argv[0], argv[1]); o = variable_buffer_output (o, result ? "1" : "", result); @@ -1727,9 +1727,9 @@ func_eq (char* o, char **argv, char *funcname) string-boolean not operator. */ static char * -func_not (char* o, char **argv, char *funcname) +func_not (char *o, char **argv, char *funcname) { - char * s = argv[0]; + char *s = argv[0]; int result = 0; while (isspace ((unsigned char)*s)) s++; @@ -1740,6 +1740,161 @@ func_not (char* o, char **argv, char *funcname) #endif +/* Return the absolute name of file NAME which does not contain any `.', + `..' components nor any repeated path separators ('/'). */ + +static char * +abspath (const char *name, char *apath) +{ + char *dest; + const char *start, *end, *apath_limit; + + if (name[0] == '\0' || apath == NULL) + return NULL; + + apath_limit = apath + PATH_MAX; + + if (name[0] != '/') + { + /* It is unlikely we would make it until here but just to make sure. */ + if (!starting_directory) + return NULL; + + strcpy (apath, starting_directory); + + dest = strchr (apath, '\0'); + } + else + { + apath[0] = '/'; + dest = apath + 1; + } + + for (start = end = name; *start != '\0'; start = end) + { + unsigned long len; + + /* Skip sequence of multiple path-separators. */ + while (*start == '/') + ++start; + + /* Find end of path component. */ + for (end = start; *end != '\0' && *end != '/'; ++end) + ; + + len = end - start; + + if (len == 0) + break; + else if (len == 1 && start[0] == '.') + /* nothing */; + else if (len == 2 && start[0] == '.' && start[1] == '.') + { + /* Back up to previous component, ignore if at root already. */ + if (dest > apath + 1) + while ((--dest)[-1] != '/'); + } + else + { + if (dest[-1] != '/') + *dest++ = '/'; + + if (dest + len >= apath_limit) + return NULL; + + dest = memcpy (dest, start, len); + dest += len; + *dest = '\0'; + } + } + + /* Unless it is root strip trailing separator. */ + if (dest > apath + 1 && dest[-1] == '/') + --dest; + + *dest = '\0'; + + return apath; +} + + +static char * +func_realpath (char *o, char **argv, const char *funcname UNUSED) +{ + /* Expand the argument. */ + char *p = argv[0]; + char *path = 0; + int doneany = 0; + unsigned int len = 0; + + char in[PATH_MAX]; + char out[PATH_MAX]; + + while ((path = find_next_token (&p, &len)) != 0) + { + if (len < PATH_MAX) + { + strncpy (in, path, len); + in[len] = '\0'; + + if + ( +#ifdef HAVE_REALPATH + realpath (in, out) +#else + abspath (in, out) +#endif + ) + { + o = variable_buffer_output (o, out, strlen (out)); + o = variable_buffer_output (o, " ", 1); + doneany = 1; + } + } + } + + /* Kill last space. */ + if (doneany) + --o; + + return o; +} + +static char * +func_abspath (char *o, char **argv, const char *funcname UNUSED) +{ + /* Expand the argument. */ + char *p = argv[0]; + char *path = 0; + int doneany = 0; + unsigned int len = 0; + + char in[PATH_MAX]; + char out[PATH_MAX]; + + while ((path = find_next_token (&p, &len)) != 0) + { + if (len < PATH_MAX) + { + strncpy (in, path, len); + in[len] = '\0'; + + if (abspath (in, out)) + { + o = variable_buffer_output (o, out, strlen (out)); + o = variable_buffer_output (o, " ", 1); + doneany = 1; + } + } + } + + /* Kill last space. */ + if (doneany) + --o; + + return o; +} + /* Lookup table for builtin functions. This doesn't have to be sorted; we use a straight lookup. We might gain @@ -1758,6 +1913,7 @@ static char *func_call PARAMS ((char *o, char **argv, const char *funcname)); static struct function_table_entry function_table_init[] = { /* Name/size */ /* MIN MAX EXP? Function */ + { STRING_SIZE_TUPLE("abspath"), 0, 1, 1, func_abspath}, { STRING_SIZE_TUPLE("addprefix"), 2, 2, 1, func_addsuffix_addprefix}, { STRING_SIZE_TUPLE("addsuffix"), 2, 2, 1, func_addsuffix_addprefix}, { STRING_SIZE_TUPLE("basename"), 0, 1, 1, func_basename_dir}, @@ -1772,6 +1928,7 @@ static struct function_table_entry function_table_init[] = { STRING_SIZE_TUPLE("join"), 2, 2, 1, func_join}, { STRING_SIZE_TUPLE("lastword"), 0, 1, 1, func_lastword}, { STRING_SIZE_TUPLE("patsubst"), 3, 3, 1, func_patsubst}, + { STRING_SIZE_TUPLE("realpath"), 0, 1, 1, func_realpath}, { STRING_SIZE_TUPLE("shell"), 0, 1, 1, func_shell}, { STRING_SIZE_TUPLE("sort"), 0, 1, 1, func_sort}, { STRING_SIZE_TUPLE("strip"), 0, 1, 1, func_strip}, diff --git a/tests/ChangeLog b/tests/ChangeLog index cef8a33..9f68a05 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,11 @@ +2004-11-30 Boris Kolpackov <boris@kolpackov.net> + + * tests/scripts/functions/abspath: New file: test `abspath' + built-in function. + + * tests/scripts/functions/realpath: New file: test `realpath' + built-in function. + 2004-11-28 Paul D. Smith <psmith@gnu.org> * scripts/options/dash-C [WINDOWS32]: Add a test for bug #10252; diff --git a/tests/scripts/functions/abspath b/tests/scripts/functions/abspath new file mode 100644 index 0000000..d419255 --- /dev/null +++ b/tests/scripts/functions/abspath @@ -0,0 +1,81 @@ +# -*-perl-*- +$description = "Test the abspath functions."; + +$details = ""; + +run_make_test(' +ifneq ($(realpath $(abspath .)),$(CURDIR)) + $(error ) +endif + +ifneq ($(realpath $(abspath ./)),$(CURDIR)) + $(error ) +endif + +ifneq ($(realpath $(abspath .///)),$(CURDIR)) + $(error ) +endif + +ifneq ($(abspath /),/) + $(error ) +endif + +ifneq ($(abspath ///),/) + $(error ) +endif + +ifneq ($(abspath /.),/) + $(error ) +endif + +ifneq ($(abspath ///.),/) + $(error ) +endif + +ifneq ($(abspath /./),/) + $(error ) +endif + +ifneq ($(abspath /.///),/) + $(error ) +endif + +ifneq ($(abspath /..),/) + $(error ) +endif + +ifneq ($(abspath ///..),/) + $(error ) +endif + +ifneq ($(abspath /../),/) + $(error ) +endif + +ifneq ($(abspath /..///),/) + $(error ) +endif + + +ifneq ($(abspath /foo/bar/..),/foo) + $(error ) +endif + +ifneq ($(abspath /foo/bar/../../../baz),/baz) + $(error ) +endif + +ifneq ($(abspath /foo/bar/../ /..),/foo /) + $(error ) +endif + + +.PHONY: all +all: ; @: +', +'', +''); + + +# This tells the test driver that the perl test script executed properly. +1; diff --git a/tests/scripts/functions/realpath b/tests/scripts/functions/realpath new file mode 100644 index 0000000..720af8b --- /dev/null +++ b/tests/scripts/functions/realpath @@ -0,0 +1,71 @@ +# -*-perl-*- +$description = "Test the realpath functions."; + +$details = ""; + +run_make_test(' +ifneq ($(realpath .),$(CURDIR)) + $(error ) +endif + +ifneq ($(realpath ./),$(CURDIR)) + $(error ) +endif + +ifneq ($(realpath .///),$(CURDIR)) + $(error ) +endif + +ifneq ($(realpath /),/) + $(error ) +endif + +ifneq ($(realpath ///),/) + $(error ) +endif + +ifneq ($(realpath /.),/) + $(error ) +endif + +ifneq ($(realpath ///.),/) + $(error ) +endif + +ifneq ($(realpath /./),/) + $(error ) +endif + +ifneq ($(realpath /.///),/) + $(error ) +endif + +ifneq ($(realpath /..),/) + $(error ) +endif + +ifneq ($(realpath ///..),/) + $(error ) +endif + +ifneq ($(realpath /../),/) + $(error ) +endif + +ifneq ($(realpath /..///),/) + $(error ) +endif + +ifneq ($(realpath . /..),$(CURDIR) /) + $(error ) +endif + +.PHONY: all +all: ; @: +', +'', +''); + + +# This tells the test driver that the perl test script executed properly. +1; |