Implemented `realpath' and `abspath' built-in functions.
authorBoris Kolpackov <boris@kolpackov.net>
Tue, 30 Nov 2004 19:51:24 +0000 (19:51 +0000)
committerBoris Kolpackov <boris@kolpackov.net>
Tue, 30 Nov 2004 19:51:24 +0000 (19:51 +0000)
ChangeLog
configure.in
doc/make.texi
function.c
tests/ChangeLog
tests/scripts/functions/abspath [new file with mode: 0644]
tests/scripts/functions/realpath [new file with mode: 0644]

index e6e733481dfe1239ec1b62a5e38ae423433ba2e8..31b15f4e108cf6862eb80e92996543f9b381cce7 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
index 15c2b88a431fecb70f90d816431cbec9d8117f3f..39a8b660d5d3e37b2ea1355c79207cfc01aefeea 100644 (file)
@@ -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
 
index 14d7278164715e53bedeb82495e14b9566816fbd..e814ec2f4db26a591da78909617a0b2d0db17f18 100644 (file)
@@ -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
index ed5cc44a6288eb968d23d0dab9343087f59e9851..bbb86d2eb3299627ff2e71b988f02e68d1fcb463 100644 (file)
@@ -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 (charo, 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 (charo, 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
 \f
 
+/* 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},
index cef8a331fc84915c77d4983dcd9382581e62b5c3..9f68a05f556de2d2af6e203595446b4b7d0eafe4 100644 (file)
@@ -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 (file)
index 0000000..d419255
--- /dev/null
@@ -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 (file)
index 0000000..720af8b
--- /dev/null
@@ -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;