On 12/12/2011 12:24 AM, Pádraig Brady wrote:
> FYI I'm not forgetting about this.
> This week I'll be travelling with work,
> but I intend to tackle this early the following week.

Sorry for the delay.
Attached is the realpath implementation, which I think
turned into a good cohesive set of functionality.
It has this interface:

Usage: src/realpath [OPTION] FILE...
Print the resolved absolute file name;
all but the last component must exist

  -e, --canonicalize-existing  all components of the path must exist
  -m, --canonicalize-missing   no components of the path need exist
  -L, --logical                resolve `..' components before symlinks
  -P, --physical               resolve symlinks as encountered (default)
  -q, --quiet                  suppress most error messages
      --relative-to=FILE       print the resolved path relative to FILE
      --relative-base=FILE     print absolute paths unless paths below FILE
  -s, --strip                  don't expand symlinks
  -z, --zero                   separate output with NUL rather than newline

      --help     display this help and exit
      --version  output version information and exit

I was going to add a --readlink option to call readlink(3),
but that's a separate function really, and since readlink(1)
is widely available, I thought it better to not duplicate?

I was going to add a --common-prefix mode, to print
the common prefix of all supplied paths.
We can do this in future anyway.

Note this patch required this small update to gnulib:
http://git.sv.gnu.org/gitweb/?p=gnulib.git;a=commit;h=8bcd0399

cheers,
Pádraig
>From b9110a3f4bf697a28c1681e62644639fe5d5f02f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <p...@draigbrady.com>
Date: Tue, 27 Dec 2011 00:30:23 +0000
Subject: [PATCH] readlink: A new program to print the resolved path

This program is compatible with other realpath(1)
implementations, and also incorporates relpath like support,
through the --relative options.  The relpath support
was suggested by Peng Yu, who also provided an initial
implemenation of that functionality.

* AUTHORS: Add my name.
* NEWS: Mention the new command.
* README: Likewise.
* doc/coreutils.texi (realpath invocation): Add realpath info.
* man/Makefile.am (realpath.1): Add dependency.
* man/realpath.x: New template.
* man/.gitignore: Ignore generated man page.
* po/POTFILES.in: Add src/realpath.c.
* src/.gitignore: Exclude realpath.
* src/Makefile.am (EXTRA_PROGRAMS): Add realpath.
* src/realpath.c: New file.
* scripts/git-hooks/commit-msg: Add realpath to the list of prefixes.
* tests/Makefile.am (TESTS): Add misc/realpath.
* tests/misc/realpath: New file.
---
 AUTHORS                      |    1 +
 NEWS                         |    4 +
 README                       |   10 +-
 doc/coreutils.texi           |  108 +++++++++++++-
 man/.gitignore               |    1 +
 man/Makefile.am              |    1 +
 man/realpath.x               |    6 +
 po/POTFILES.in               |    1 +
 scripts/git-hooks/commit-msg |    2 +-
 src/.gitignore               |    1 +
 src/Makefile.am              |    3 +
 src/realpath.c               |  328 ++++++++++++++++++++++++++++++++++++++++++
 tests/Makefile.am            |    1 +
 tests/misc/realpath          |   53 +++++++
 14 files changed, 510 insertions(+), 10 deletions(-)
 create mode 100644 man/realpath.x
 create mode 100644 src/realpath.c
 create mode 100755 tests/misc/realpath

diff --git a/AUTHORS b/AUTHORS
index 0c15472..d1024a5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -62,6 +62,7 @@ printf: David MacKenzie
 ptx: François Pinard
 pwd: Jim Meyering
 readlink: Dmitry V. Levin
+realpath: Pádraig Brady
 rm: Paul Rubin, David MacKenzie, Richard M. Stallman, Jim Meyering
 rmdir: David MacKenzie
 runcon: Russell Coker
diff --git a/NEWS b/NEWS
index 9a2f31c..d825625 100644
--- a/NEWS
+++ b/NEWS
@@ -41,6 +41,10 @@ GNU coreutils NEWS                                    -*- 
outline -*-
   argument, tail -f prints a warning with the FS type magic number and a
   request to report it to the bug-reporting address.
 
+** New programs
+
+  realpath: print the resolved path
+
 
 * Noteworthy changes in release 8.14 (2011-10-12) [stable]
 
diff --git a/README b/README
index eaa5fb0..76e6090 100644
--- a/README
+++ b/README
@@ -11,11 +11,11 @@ The programs that can be built with this package are:
   csplit cut date dd df dir dircolors dirname du echo env expand expr
   factor false fmt fold groups head hostid hostname id install join kill
   link ln logname ls md5sum mkdir mkfifo mknod mktemp mv nice nl nohup
-  nproc od paste pathchk pinky pr printenv printf ptx pwd readlink rm rmdir
-  runcon seq sha1sum sha224sum sha256sum sha384sum sha512sum shred shuf
-  sleep sort split stat stdbuf stty su sum sync tac tail tee test timeout
-  touch tr true truncate tsort tty uname unexpand uniq unlink uptime users
-  vdir wc who whoami yes
+  nproc od paste pathchk pinky pr printenv printf ptx pwd readlink realpath
+  rm rmdir runcon seq sha1sum sha224sum sha256sum sha384sum sha512sum shred
+  shuf sleep sort split stat stdbuf stty su sum sync tac tail tee test
+  timeout touch tr true truncate tsort tty uname unexpand uniq unlink
+  uptime users vdir wc who whoami yes
 
 See the file NEWS for a list of major changes in the current release.
 
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index c229f98..a7aa99f 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -94,6 +94,7 @@
 * ptx: (coreutils)ptx invocation.               Produce permuted indexes.
 * pwd: (coreutils)pwd invocation.               Print working directory.
 * readlink: (coreutils)readlink invocation.     Print referent of a symlink.
+* realpath: (coreutils)readpath invocation.     Print resolved file names.
 * rm: (coreutils)rm invocation.                 Remove files.
 * rmdir: (coreutils)rmdir invocation.           Remove empty directories.
 * runcon: (coreutils)runcon invocation.         Run in specified SELinux CTX.
@@ -194,7 +195,7 @@ Free Documentation License''.
 * Printing text::                echo printf yes
 * Conditions::                   false true test expr
 * Redirection::                  tee
-* File name manipulation::       dirname basename pathchk mktemp
+* File name manipulation::       dirname basename pathchk mktemp realpath
 * Working context::              pwd stty printenv tty
 * User information::             id logname whoami groups users who
 * System context::               date arch nproc uname hostname hostid uptime
@@ -381,6 +382,7 @@ File name manipulation
 * dirname invocation::           Strip last file name component
 * pathchk invocation::           Check file name validity and portability
 * mktemp invocation::            Create temporary file or directory
+* realpath invocation::          Print resolved file names
 
 Working context
 
@@ -9603,7 +9605,6 @@ Set the default SELinux security context to be used for 
created files.
 @cindex displaying value of a symbolic link
 @cindex canonical file name
 @cindex canonicalize a file name
-@pindex realpath
 @findex realpath
 
 @command{readlink} may work in one of two supported modes:
@@ -9686,8 +9687,8 @@ Report error messages.
 
 The @command{readlink} utility first appeared in OpenBSD 2.1.
 
-There is a @command{realpath} command on some systems
-which operates like @command{readlink} in canonicalize mode.
+The @command{realpath} command without options, operates like
+@command{readlink} in canonicalize mode.
 
 @exitstatus
 
@@ -12354,6 +12355,7 @@ This section describes commands that manipulate file 
names.
 * dirname invocation::          Strip last file name component.
 * pathchk invocation::          Check file name validity and portability.
 * mktemp invocation::           Create temporary file or directory.
+* realpath invocation::         Print resolved file names.
 @end menu
 
 
@@ -12707,6 +12709,104 @@ Exit status:
 @end display
 
 
+@node realpath invocation
+@section @command{realpath}: Print the resolved file name.
+
+@pindex realpath
+@cindex file names, canonicalization
+@cindex symlinks, resolution
+@cindex canonical file name
+@cindex canonicalize a file name
+@pindex realpath
+@findex realpath
+
+@command{realpath} expands all symbolic links and resolves references to
+@samp{/./}, @samp{/../} and extra @samp{/} characters.  By default,
+all but the last component of the specified files must exist.  Synopsis:
+
+@example
+realpath [@var{option}]@dots{} @var{file}@dots{}
+@end example
+
+The program accepts the following options.  Also see @ref{Common options}.
+
+@table @samp
+
+@item -e
+@itemx --canonicalize-existing
+@opindex -e
+@opindex --canonicalize-existing
+Ensure that all components of the specified file names exist.
+If any component is missing or unavailable, @command{realpath} will output
+a diagnostic unless the @option{-q} option is specified, and exit with a
+nonzero exit code.  A trailing slash requires that the name resolve to a
+directory.
+
+@item -m
+@itemx --canonicalize-missing
+@opindex -m
+@opindex --canonicalize-missing
+If any component of a specified file name is missing or unavailable,
+treat it as a directory.
+
+@item -L
+@itemx --logical
+@opindex -L
+@opindex --logical
+Symbolic links are resolved in the specified file names,
+but they are resolved after any subsequent @samp{..} components are processed.
+
+@item -P
+@itemx --physical
+@opindex -P
+@opindex --physical
+Symbolic links are resolved in the specified file names,
+and they are resolved before any subsequent @samp{..} components are processed.
+This is the default mode of operation.
+
+@item -q
+@itemx --quiet
+@opindex -q
+@opindex --quiet
+Suppress diagnostic messages for specified file names.
+
+@item -s
+@itemx --strip
+@opindex -s
+@opindex --strip
+Do not resolve symbolic links.  Only resolve references to
+@samp{/./}, @samp{/../} and extra @samp{/} characters.
+
+@item -z
+@itemx --zero
+@opindex -z
+@opindex --zero
+Separate output items with @sc{nul} characters.
+
+@itemx --relative-to=@var{file}
+@opindex --relative-to
+Print the resolved file names relative to the specified file.
+Note this option honors the @option{-m} and @option{-e} options
+pertaining to file existence.
+
+@itemx --relative-base=@var{file}
+@opindex --relative-base
+Ensure both the @option{--relative-to} and processed files are
+subdirectories of @var{file}, or otherwise output the absolute file name.
+Note this option honors the @option{-m} and @option{-e} options
+pertaining to file existence.
+
+@end table
+
+@cindex exit status of @command{realpath}
+Exit status:
+
+@display
+0 if all file names were printed without issue.
+1 otherwise.
+@end display
+
+
 @node Working context
 @chapter Working context
 
diff --git a/man/.gitignore b/man/.gitignore
index 2c2ffcd..9ae1183 100644
--- a/man/.gitignore
+++ b/man/.gitignore
@@ -59,6 +59,7 @@ printf.1
 ptx.1
 pwd.1
 readlink.1
+realpath.1
 rm.1
 rmdir.1
 seq.1
diff --git a/man/Makefile.am b/man/Makefile.am
index 8733a35..e995b08 100644
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -92,6 +92,7 @@ printf.1:     $(common_dep)   $(srcdir)/printf.x      
../src/printf.c
 ptx.1:         $(common_dep)   $(srcdir)/ptx.x         ../src/ptx.c
 pwd.1:         $(common_dep)   $(srcdir)/pwd.x         ../src/pwd.c
 readlink.1:    $(common_dep)   $(srcdir)/readlink.x    ../src/readlink.c
+realpath.1:    $(common_dep)   $(srcdir)/realpath.x    ../src/realpath.c
 rm.1:          $(common_dep)   $(srcdir)/rm.x          ../src/rm.c
 rmdir.1:       $(common_dep)   $(srcdir)/rmdir.x       ../src/rmdir.c
 runcon.1:      $(common_dep)   $(srcdir)/runcon.x      ../src/runcon.c
diff --git a/man/realpath.x b/man/realpath.x
new file mode 100644
index 0000000..a52fa25
--- /dev/null
+++ b/man/realpath.x
@@ -0,0 +1,6 @@
+[NAME]
+realpath \- print the resolved path
+[DESCRIPTION]
+.\" Add any additional description here
+[SEE ALSO]
+readlink(1), readlink(2), realpath(3)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7301532..05d0403 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -99,6 +99,7 @@ src/printf.c
 src/ptx.c
 src/pwd.c
 src/readlink.c
+src/realpath.c
 src/remove.c
 src/rm.c
 src/rmdir.c
diff --git a/scripts/git-hooks/commit-msg b/scripts/git-hooks/commit-msg
index 22f595f..46382ae 100755
--- a/scripts/git-hooks/commit-msg
+++ b/scripts/git-hooks/commit-msg
@@ -17,7 +17,7 @@ my @valid = qw(
     expr factor false fmt fold groups head hostid hostname id install
     join kill link ln logname ls md5sum mkdir mkfifo mknod mktemp
     mv nice nl nohup nproc od paste pathchk pinky pr printenv printf
-    ptx pwd readlink rm rmdir runcon seq sha1sum sha224sum sha256sum
+    ptx pwd readlink realpath rm rmdir runcon seq sha1sum sha224sum sha256sum
     sha384sum sha512sum shred shuf sleep sort split stat stdbuf stty
     su sum sync tac tail tee test timeout touch tr true truncate tsort
     tty uname unexpand uniq unlink uptime users vdir wc who whoami yes
diff --git a/src/.gitignore b/src/.gitignore
index d25a8e5..9c4c9b7 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -68,6 +68,7 @@ printf
 ptx
 pwd
 readlink
+realpath
 rm
 rmdir
 runcon
diff --git a/src/Makefile.am b/src/Makefile.am
index c12b9d9..342afc5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -97,6 +97,7 @@ EXTRA_PROGRAMS = \
   ptx          \
   pwd          \
   readlink     \
+  realpath     \
   rm           \
   rmdir                \
   runcon       \
@@ -248,6 +249,7 @@ printf_LDADD = $(LDADD)
 ptx_LDADD = $(LDADD)
 pwd_LDADD = $(LDADD)
 readlink_LDADD = $(LDADD)
+realpath_LDADD = $(LDADD)
 rm_LDADD = $(LDADD)
 rmdir_LDADD = $(LDADD)
 runcon_LDADD = $(LDADD)
@@ -373,6 +375,7 @@ du_LDADD += $(LIBICONV)
 getlimits_LDADD += $(LIBICONV)
 printf_LDADD += $(LIBICONV)
 ptx_LDADD += $(LIBICONV)
+realpath_LDADD += $(LIBICONV)
 split_LDADD += $(LIBICONV)
 stdbuf_LDADD += $(LIBICONV)
 timeout_LDADD += $(LIBICONV)
diff --git a/src/realpath.c b/src/realpath.c
new file mode 100644
index 0000000..0e596a9
--- /dev/null
+++ b/src/realpath.c
@@ -0,0 +1,328 @@
+/* realpath - print the resolved path
+   Copyright (C) 2011 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Pádraig Brady.  */
+
+#include <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "system.h"
+#include "canonicalize.h"
+#include "error.h"
+#include "quote.h"
+
+/* The official name of this program (e.g., no `g' prefix).  */
+#define PROGRAM_NAME "realpath"
+
+#define AUTHORS proper_name_utf8 ("Padraig Brady", "P\303\241draig Brady")
+
+enum
+{
+  RELATIVE_TO_OPTION = CHAR_MAX + 1,
+  RELATIVE_BASE_OPTION
+};
+
+static bool verbose = true;
+static bool logical;
+static bool use_nuls;
+static const char *can_relative_to;
+static const char *can_relative_base;
+
+static struct option const longopts[] =
+{
+  {"canonicalize-existing", no_argument, NULL, 'e'},
+  {"canonicalize-missing", no_argument, NULL, 'm'},
+  {"relative-to", required_argument, NULL, RELATIVE_TO_OPTION},
+  {"relative-base", required_argument, NULL, RELATIVE_BASE_OPTION},
+  {"quiet", no_argument, NULL, 'q'},
+  {"strip", no_argument, NULL, 's' /* compat with debian util.  */},
+  {"zero", no_argument, NULL, 'z'},
+  {"logical", no_argument, NULL, 'L'},
+  {"physical", no_argument, NULL, 'P'},
+  {GETOPT_HELP_OPTION_DECL},
+  {GETOPT_VERSION_OPTION_DECL},
+  {NULL, 0, NULL, 0}
+};
+
+void
+usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
+             program_name);
+  else
+    {
+      printf (_("Usage: %s [OPTION] FILE...\n"), program_name);
+      fputs (_("\
+Print the resolved absolute file name;\n\
+all but the last component must exist\n\
+\n\
+"), stdout);
+      fputs (_("\
+  -e, --canonicalize-existing  all components of the path must exist\n\
+  -m, --canonicalize-missing   no components of the path need exist\n\
+  -L, --logical                resolve `..' components before symlinks\n\
+  -P, --physical               resolve symlinks as encountered (default)\n\
+  -q, --quiet                  suppress most error messages\n\
+      --relative-to=FILE       print the resolved path relative to FILE\n\
+      --relative-base=FILE     print absolute paths unless paths below FILE\n\
+  -s, --strip                  don't expand symlinks\n\
+  -z, --zero                   separate output with NUL rather than newline\n\
+\n\
+"), stdout);
+      fputs (HELP_OPTION_DESCRIPTION, stdout);
+      fputs (VERSION_OPTION_DESCRIPTION, stdout);
+      emit_ancillary_info ();
+    }
+  exit (status);
+}
+
+/* A wrapper around canonicalize_filename_mode(),
+   to call it twice when in LOGICAL mode.  */
+static char *
+realpath_canon (const char *fname, int can_mode)
+{
+  char *can_fname = canonicalize_filename_mode (fname, can_mode);
+  if (logical && can_fname)  /* canonicalize again to resolve symlinks.  */
+    {
+      can_mode &= ~CAN_NOLINKS;
+      char *can_fname2 = canonicalize_filename_mode (can_fname, can_mode);
+      free (can_fname);
+      return can_fname2;
+    }
+  return can_fname;
+}
+
+/* Test whether prefix is parent or match of path.  */
+static bool _GL_ATTRIBUTE_PURE
+path_prefix (const char *prefix, const char *path)
+{
+  while (*prefix && *path)
+    {
+      if (*prefix != *path)
+          break;
+      prefix++;
+      path++;
+    }
+  return (!*prefix && (*path == '/' || !*path));
+}
+
+/* Return the length of the longest common prefix
+   of PATH1 and PATH2, ensuring only full path components
+   are matched.  Return 0 on no match.  */
+static int _GL_ATTRIBUTE_PURE
+path_common_prefix (const char *path1, const char *path2)
+{
+  int i = 0;
+  int ret = 0;
+
+  while (*path1 && *path2)
+    {
+      if (*path1 != *path2)
+          break;
+      if (*path1 == '/')
+          ret = i;
+      path1++;
+      path2++;
+      i++;
+    }
+
+  if (!*path1 && !*path2)
+      ret = i;
+  if (!*path1 && *path2 == '/')
+      ret = i;
+  if (!*path2 && *path1 == '/')
+      ret = i;
+
+  return ret;
+}
+
+/* Output the relative representation if requested.  */
+static bool
+relpath (const char *can_fname)
+{
+  if (can_relative_to)
+    {
+      /* Enforce --relative-base.  */
+      if (can_relative_base)
+        {
+          if (!path_prefix (can_relative_base, can_fname)
+              || !path_prefix (can_relative_base, can_relative_to))
+            return false;
+        }
+
+      /* Skip the prefix common to --relative-to and path.  */
+      int common_index = path_common_prefix (can_relative_to, can_fname);
+      const char *relto_suffix = can_relative_to + common_index;
+      const char *fname_suffix = can_fname + common_index;
+
+      /* Replace remaining components of --relative-to with '..', to get
+         to a common directory.  Then output the remainder of fname.  */
+      if (*relto_suffix)
+        {
+          ++relto_suffix;
+          printf ("%s", "..");
+          for (; *relto_suffix; ++relto_suffix)
+            {
+              if (*relto_suffix == '/')
+                  printf ("%s", "/..");
+            }
+
+          printf ("%s", fname_suffix);
+        }
+      else
+        {
+          if (*fname_suffix)
+              printf ("%s", ++fname_suffix);
+          else
+              printf ("%c", '.');
+        }
+
+      putchar (use_nuls ? '\0' : '\n');
+
+      return true;
+    }
+
+  return false;
+}
+
+static bool
+isdir (const char *path)
+{
+  struct stat sb;
+  if (stat (path, &sb) != 0)
+    error (EXIT_FAILURE, errno, _("cannot stat %s"), quote (path));
+  return S_ISDIR (sb.st_mode);
+}
+
+static bool
+process_path (const char *fname, int can_mode)
+{
+  char *can_fname = realpath_canon (fname, can_mode);
+  if (!can_fname)
+    {
+      if (verbose)
+        error (0, errno, "%s", quote (fname));
+      return false;
+    }
+
+  if (!relpath (can_fname))
+      printf ("%s%c", can_fname, (use_nuls ? '\0' : '\n'));
+
+  free (can_fname);
+
+  return true;
+}
+
+int
+main (int argc, char **argv)
+{
+  bool ok = true;
+  int can_mode = CAN_ALL_BUT_LAST;
+  const char *relative_to = NULL;
+  const char *relative_base = NULL;
+
+  initialize_main (&argc, &argv);
+  set_program_name (argv[0]);
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
+
+  atexit (close_stdout);
+
+  while (1)
+    {
+      int c = getopt_long (argc, argv, "eLmPqsz", longopts, NULL);
+      if (c == -1)
+        break;
+      switch (c)
+        {
+        case 'e':
+          can_mode &= ~CAN_MODE_MASK;
+          can_mode |= CAN_EXISTING;
+          break;
+        case 'm':
+          can_mode &= ~CAN_MODE_MASK;
+          can_mode |= CAN_MISSING;
+          break;
+        case 'L':
+          can_mode |= CAN_NOLINKS;
+          logical = true;
+          break;
+        case 's':
+          can_mode |= CAN_NOLINKS;
+          logical = false;
+          break;
+        case 'P':
+          can_mode &= ~CAN_NOLINKS;
+          logical = false;
+          break;
+        case 'q':
+          verbose = false;
+          break;
+        case 'z':
+          use_nuls = true;
+          break;
+        case RELATIVE_TO_OPTION:
+          relative_to = optarg;
+          break;
+        case RELATIVE_BASE_OPTION:
+          relative_base = optarg;
+          break;
+        case_GETOPT_HELP_CHAR;
+        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+        default:
+          usage (EXIT_FAILURE);
+        }
+    }
+
+  if (optind >= argc)
+    {
+      error (0, 0, _("missing operand"));
+      usage (EXIT_FAILURE);
+    }
+
+  if (relative_base && !relative_to)
+    {
+      error (0, 0, _("--relative-base requires --relative-to"));
+      usage (EXIT_FAILURE);
+    }
+
+  bool need_dir = (can_mode & CAN_MODE_MASK) == CAN_EXISTING;
+  if (relative_to)
+    {
+      can_relative_to = realpath_canon (relative_to, can_mode);
+      if (!can_relative_to)
+        error (EXIT_FAILURE, errno, "%s", quote (relative_to));
+      if (need_dir && !isdir (can_relative_to))
+        error (EXIT_FAILURE, ENOTDIR, "%s", quote (relative_to));
+    }
+  if (relative_base)
+    {
+      can_relative_base = realpath_canon (relative_base, can_mode);
+      if (!can_relative_base)
+        error (EXIT_FAILURE, errno, "%s", quote (relative_base));
+      if (need_dir && !isdir (can_relative_base))
+        error (EXIT_FAILURE, ENOTDIR, "%s", quote (relative_base));
+    }
+
+  for (; optind < argc; ++optind)
+    ok &= process_path (argv[optind], can_mode);
+
+  exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 23cb70f..894fcf8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -220,6 +220,7 @@ TESTS =                                             \
   misc/printf-surprise                         \
   misc/pwd-long                                        \
   misc/readlink-fp-loop                                \
+  misc/realpath                                        \
   misc/runcon-no-reorder                       \
   misc/sha1sum                                 \
   misc/sha1sum-vec                             \
diff --git a/tests/misc/realpath b/tests/misc/realpath
new file mode 100755
index 0000000..004ba46
--- /dev/null
+++ b/tests/misc/realpath
@@ -0,0 +1,53 @@
+#!/bin/sh
+# Validate realpath operation
+
+# Copyright (C) 2011 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/init.sh"; path_prepend_ ../src
+print_ver_ realpath
+
+# Setup dir, file, symlink structure
+
+mkdir -p dir1/dir2
+ln -s dir1/dir2 ldir2
+touch dir1/f dir1/dir2/f
+
+# Basic operaion
+realpath -Pqz . >/dev/null || fail=1
+# --relative-base and --relative-to require params
+realpath --relative-base --relative-to . && fail=1
+# --relative-base requires --relative-to
+realpath --relative-base=blah . && fail=1
+
+# -e --relative-* require directories
+realpath -e --relative-to=dir1/f --relative-base=. . && fail=1
+realpath -e --relative-to=dir1/  --relative-base=. . || fail=1
+
+# Note NUL params are unconditionally rejected by canonicalize_filename_mode
+realpath -m '' && fail=1
+
+# symlink resolution
+this=$(realpath .)
+test $(realpath $relative ldir2/..) = "$this/dir1" || fail=1
+test $(realpath -L $relative ldir2/..) = "$this" || fail=1
+test $(realpath -s $relative ldir2) = "$this/ldir2" || fail=1
+
+# relative string handling
+test $(realpath -m --relative-to=prefix prefixed/1) = '../prefixed/1' || fail=1
+test $(realpath -m --relative-to=prefixed prefix/1) = '../prefix/1' || fail=1
+test $(realpath -m --relative-to=prefixed prefixed/1) = '1' || fail=1
+
+Exit $fail
-- 
1.7.6.4

Reply via email to