bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: xreadlink: Rewrite as a simple mreadlink wrapper.


From: Jim Meyering
Subject: Re: xreadlink: Rewrite as a simple mreadlink wrapper.
Date: Mon, 03 Sep 2007 01:15:07 +0200

Bruno Haible <address@hidden> wrote:
> Hello Jim,
>
>> as far as I can see, no one even uses xreadlink any more, so it's not urgent.
>
> The 'relocatable' facility uses xreadlink.
>
>> However, your point about the "a" vs. "m" prefix is valid,
>> and I've just renamed mreadlink-with-size.
>
> Thanks. I've got an 'areadlink' module ready now, but I would like to
> get the malloc/realloc situation cleared up before I present it.

Hi Bruno,

Since they may be relevant (you can implement areadlink
via a wrapper around areadlinkat_with_size -- though may not
want to), here are some new modules as part of a patch
for coreutils that's nearly complete:

>From 1679a208de82815519a2b4e66592da68dfd11360 Mon Sep 17 00:00:00 2001
From: Jim Meyering <address@hidden>
Date: Fri, 3 Aug 2007 18:44:54 +0200
Subject: [PATCH] copy.c: handle dangling destination symlinks safely

Signed-off-by: Jim Meyering <address@hidden>
---
 .x-sc_prohibit_strcmp            |    3 +-
 ChangeLog                        |   21 +++
 bootstrap.conf                   |    4 +-
 gl/lib/areadlinkat-with-size.c   |  113 ++++++++++++++++
 gl/lib/areadlinkat.h             |    3 +
 gl/lib/resolve-symlink.c         |  261 ++++++++++++++++++++++++++++++++++++++
 gl/lib/resolve-symlink.h         |    2 +
 gl/modules/areadlinkat           |   27 ++++
 gl/modules/resolve-symlink       |   27 ++++
 gl/modules/resolve-symlink-tests |   13 ++
 gl/tests/test-resolve-symlink.c  |  117 +++++++++++++++++
 gl/tests/test-resolve-symlink.sh |   67 ++++++++++
 src/copy.c                       |   35 ++++--
 13 files changed, 681 insertions(+), 12 deletions(-)
 create mode 100644 gl/lib/areadlinkat-with-size.c
 create mode 100644 gl/lib/areadlinkat.h
 create mode 100644 gl/lib/resolve-symlink.c
 create mode 100644 gl/lib/resolve-symlink.h
 create mode 100644 gl/modules/areadlinkat
 create mode 100644 gl/modules/resolve-symlink
 create mode 100644 gl/modules/resolve-symlink-tests
 create mode 100644 gl/tests/test-resolve-symlink.c
 create mode 100755 gl/tests/test-resolve-symlink.sh

diff --git a/.x-sc_prohibit_strcmp b/.x-sc_prohibit_strcmp
index fdceaf0..e8dfba1 100644
--- a/.x-sc_prohibit_strcmp
+++ b/.x-sc_prohibit_strcmp
@@ -1,2 +1,3 @@
-^src/system\.h
+^src/system\.h$
 ChangeLog
+^gl/tests/test-resolve-symlink\.c$
diff --git a/ChangeLog b/ChangeLog
index 7153fd6..bec3197 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -401,6 +401,27 @@
        * src/system.h (opt_str_storage): New static var.
        (OPT_STR, LONG_OPT_STR, OPT_STR_INIT): New macros.
 
+2007-08-03  Jim Meyering  <address@hidden>
+
+       copy.c: handle dangling destination symlinks safely
+
+       * src/copy.c: Include "resolve-symlink.h", not "canonicalize.h".
+       (copy_reg): Safely handle the unusual case of copying through a
+       dangling destination symlink: i.e., by detecting when it would
+       be risky and failing in that case.
+       Call resolve_symlink to get enough information (fd and ent_name)
+       to open the file safely, then open/create it with openat.
+       * bootstrap.conf (gnulib_modules): Add resolve-symlink.
+       * gl/modules/resolve-symlink: New file.
+       * gl/modules/areadlinkat: New file.
+       * gl/lib/areadlinkat-with-size.c: New file.
+       * gl/lib/areadlinkat.h: New file.
+       * gl/lib/resolve-symlink.c: New file.
+       * gl/lib/resolve-symlink.h: New file.
+       * gl/modules/resolve-symlink-tests: New file.
+       * gl/tests/test-resolve-symlink.c: New file.
+       * gl/tests/test-resolve-symlink.sh: New file.
+
 2007-08-02  Jim Meyering  <address@hidden>
 
        Adjust one more test to accommodate the recent fts change.
diff --git a/bootstrap.conf b/bootstrap.conf
index 93bada9..7651c16 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -64,7 +64,9 @@ gnulib_modules="
        mountlist mpsort obstack pathmax perl physmem posixtm posixver putenv
        quote quotearg raise readlink areadlink-with-size readtokens
        readtokens0 readutmp
-       realloc regex rename-dest-slash rmdir rmdir-errno
+       realloc regex rename-dest-slash
+       resolve-symlink
+       rmdir rmdir-errno
        root-dev-ino
        rpmatch
        safe-read same
diff --git a/gl/lib/areadlinkat-with-size.c b/gl/lib/areadlinkat-with-size.c
new file mode 100644
index 0000000..1a76426
--- /dev/null
+++ b/gl/lib/areadlinkat-with-size.c
@@ -0,0 +1,113 @@
+/* readlinkat wrapper: return symlink name in malloc'd storage
+
+   Copyright (C) 2007 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 Jim Meyering <address@hidden>.  */
+
+#include <config.h>
+
+#include "areadlinkat.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifndef SIZE_MAX
+# define SIZE_MAX ((size_t) -1)
+#endif
+#ifndef SSIZE_MAX
+# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
+#endif
+
+/* SYMLINK_MAX is used only for an initial memory-allocation sanity
+   check, so it's OK to guess too small on hosts where there is no
+   arbitrary limit to symbolic link length.  */
+#ifndef SYMLINK_MAX
+# define SYMLINK_MAX 1024
+#endif
+
+#define MAXSIZE (SIZE_MAX < SSIZE_MAX ? SIZE_MAX : SSIZE_MAX)
+
+/* Use readlinkat to get FILE's link name into malloc'd storage.
+   SIZE is a hint as to how long the string is expected to be;
+   typically it is taken from st_size.  It need not be correct.
+   Return a pointer to that NUL-terminated string in malloc'd storage.
+   If readlinkat or malloc fails, or if the link value is longer than
+   SSIZE_MAX, return NULL (caller may use errno to diagnose).  */
+
+char *
+areadlinkat_with_size (int fd, char const *file, size_t size)
+{
+  /* Some buggy file systems report garbage in st_size.  Defend
+     against them by ignoring outlandish st_size values in the initial
+     memory allocation.  */
+  size_t symlink_max = SYMLINK_MAX;
+  size_t INITIAL_LIMIT_BOUND = 8 * 1024;
+  size_t initial_limit = (symlink_max < INITIAL_LIMIT_BOUND
+                          ? symlink_max + 1
+                          : INITIAL_LIMIT_BOUND);
+
+  /* The initial buffer size for the link value.  */
+  size_t buf_size = size < initial_limit ? size + 1 : initial_limit;
+
+  while (1)
+    {
+      ssize_t r;
+      size_t link_length;
+      char *buffer = malloc (buf_size);
+
+      if (buffer == NULL)
+        return NULL;
+      r = readlinkat (fd, file, buffer, buf_size);
+
+      /* On AIX 5L v5.3 and HP-UX 11i v2 04/09, emulated readlinkat
+         returns -1 with errno == ERANGE if the buffer is too small.  */
+      if (r < 0 && errno != ERANGE)
+        {
+          int saved_errno = errno;
+          free (buffer);
+          errno = saved_errno;
+          return NULL;
+        }
+
+      link_length = r;
+      if (link_length < buf_size)
+        {
+          buffer[link_length] = 0;
+          return buffer;
+        }
+
+      free (buffer);
+      if (buf_size <= MAXSIZE / 2)
+        buf_size *= 2;
+      else if (buf_size < MAXSIZE)
+        buf_size = MAXSIZE;
+      else
+        {
+          errno = ENOMEM;
+          return NULL;
+        }
+    }
+}
+
+/*
+Local Variables:
+indent-tabs-mode: nil
+End:
+*/
diff --git a/gl/lib/areadlinkat.h b/gl/lib/areadlinkat.h
new file mode 100644
index 0000000..3ce29b7
--- /dev/null
+++ b/gl/lib/areadlinkat.h
@@ -0,0 +1,3 @@
+#include <stddef.h>
+extern char *areadlinkat_with_size (int fd, char const *filename,
+                                   size_t est_len);
diff --git a/gl/lib/resolve-symlink.c b/gl/lib/resolve-symlink.c
new file mode 100644
index 0000000..9a4febd
--- /dev/null
+++ b/gl/lib/resolve-symlink.c
@@ -0,0 +1,261 @@
+/* Resolve a symbolic link, safely.
+   Copyright (C) 2007 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 Jim Meyering.  */
+
+#include <config.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "cycle-check.h"
+#include "dirname.h"
+#include "filenamecat.h"
+#include "areadlinkat.h"
+
+/* An estimate of symlink length;  used as a hint to areadlinkat_with_size.  */
+enum { SYMLINK_LENGTH_EST = 63 };
+
+static inline bool
+legit_readlink_errno (int errno_val)
+{
+  /* ENOENT is for when FILE is a dangling symlink,
+     EINVAL is for when FILE is not a symlink.  */
+  return errno_val == ENOENT || errno_val == EINVAL;
+}
+
+/* Return true if DIR_FD is owned by the effective user ID, and not writable
+   by "other".  Ideally, what we want here is to ensure that the directory
+   behind *NEW_FD is writable only by us (wrt existing intermediate
+   symlinks as well as the destination of the final, dangling one),
+   so the sticky bit is not relevant.  This is just an approximation,
+   since it ignores group permissions, but since it does ensure you're
+   the owner, it is your responsibility not to grant group write access
+   if other members are not trustworthy.  */
+static bool
+safely_readable_dir (int dir_fd)
+{
+  struct stat sb;
+
+  /* Stop the traversal if the directory we've just opened is not owned by
+     the effective user ID.  Ideally, what we want here is to ensure that
+     the directory behind *NEW_FD is writable only by us, but it's not
+     feasible to check that.  */
+  return (fstat (dir_fd, &sb) == 0
+          && geteuid () == sb.st_uid
+          && ! (sb.st_mode & S_IWOTH));
+}
+
+/* Given the dirname/basename parts of a file name that specifies a symlink,
+   take one step in the follow-the-symlink-chain process.
+
+   Inputs: file descriptor *FD, open on a directory (or AT_FDCWD),
+   an FD-relative directory name, DIR, ENT the base name of the
+   probable-symlink, and CWD_FULL_NAME, the full name of the directory
+   to which DIR is relative (initially this is cwd),
+   to be used only if openat fails.
+
+   This function always closes an open (i.e., non-AT_FDCWD) *FD.
+
+   Get the symlink value from "DIR/ENT" via openat+readlinkat or via
+   readlink "CWD_FULL_NAME/DIR/ENT".  In either case, update *FD accordingly
+   (to new dir fd in the first case, to AT_FDCWD in the latter).
+   Upon noting a successful return from this function, the caller will
+   probably want to form the next directory name, CWD_FULL_NAME/DIR.
+   Return the symlink value in malloc'd storage, or NULL upon error.  */
+
+static char *
+traverse_symlink (int *fd, char const *dir, char const *ent,
+                  char const *cwd_full_name)
+{
+  int new_fd = openat (*fd, dir, O_RDONLY | O_DIRECTORY);
+
+  if (*fd != AT_FDCWD)
+    close (*fd);
+
+  if (0 <= new_fd)
+    {
+      char *val;
+
+      /* Stop the traversal if the directory we've just opened is not "safe"
+         enough, i.e., if someone else could add or modify the symlink we're
+         about to read.  */
+      if (! safely_readable_dir (new_fd))
+        {
+          close (new_fd);
+          errno = EPERM;
+          return NULL;
+        }
+
+      val = areadlinkat_with_size (new_fd, ent, SYMLINK_LENGTH_EST);
+      if (val || legit_readlink_errno (errno))
+        {
+          *fd = new_fd;
+          return val;
+        }
+      close (new_fd);
+    }
+
+  /* We reach this point only in the relatively unusual event that openat
+     fails or that readlinkat fails with an unusual errno value.  */
+  *fd = AT_FDCWD;
+
+  {
+    /* Form the concatenation: CWD_FULL_NAME/DIR/ENT.  */
+    /* FIXME: is it possible for dir to start with a slash? */
+    char *t = mfile_name_concat (cwd_full_name, dir, NULL);
+    if (t == NULL)
+      return NULL;
+    char *p = mfile_name_concat (t, ent, NULL);
+    free (t);
+    if (p == NULL)
+      return NULL;
+    char *val = areadlinkat_with_size (AT_FDCWD, p, SYMLINK_LENGTH_EST);
+    if (val == NULL)
+      {
+        int saved_errno = errno;
+        free (p);
+        errno = saved_errno;
+        return NULL;
+      }
+
+    free (p);
+    return val;
+  }
+}
+
+static inline bool
+is_dot (char const *filename)
+{
+  return *filename == '.' && filename[1] == '\0';
+}
+
+/* Given a dangling symlink, S, set *DIR_FD and *ENT_NAME so that
+   openat (DIR_FD, ENT_NAME, flags | O_EXCL) creates the same file that
+   open (S, flags) would create.  Using O_EXCL is safer.  Also, set
+   *PARENT_FULL_NAME to the full name (in malloc'd memory) of the
+   directory containing the target of S.  I.e., in shell parlance,
+   set *PARENT_FULL_NAME to $(dirname $(readlink -m S)).
+
+   As for PARENT_FULL_NAME, given that resolving S means following
+   a sequence of more than one symlink, ...
+   if the final symlink value is an absolute name, that is the result.
+   Otherwise, if all links are relative, the result is the concatenation
+   of relative dirname values with the final link value.
+   Otherwise, it is the concatenation of dirname (last_absolute_name) and
+   the following relative dirname values with the final link value.
+*/
+int
+resolve_symlink (char const *slink, int *dir_fd,
+                 char **ent_name, char **parent_full_name)
+{
+  int fd = AT_FDCWD;
+  /* FIXME: is it ok to use "" here? */
+  char *next_parent = strdup ("");
+  if (next_parent == NULL)
+    return -1;
+
+  char *s = strdup (slink);
+  if (s == NULL)
+    {
+      free (next_parent);
+      return -1;
+    }
+
+  size_t n = 0;
+  struct cycle_check_state cycle_check_state;
+  cycle_check_init (&cycle_check_state);
+
+  while (true)
+    {
+      char *dir = dir_name (s);
+      if (dir == NULL)
+        goto fail;
+      char *base = last_component (s);
+      if (*base == '\0')
+        goto fail;
+
+      char *val = traverse_symlink (&fd, dir, base, next_parent);
+      if (val == NULL && ! legit_readlink_errno (errno))
+        {
+        fail:;
+          int saved_errno = errno;
+          free (next_parent);
+          free (s);
+          free (dir);
+          errno = saved_errno;
+          return -1;
+        }
+
+      if (IS_ABSOLUTE_FILE_NAME (s))
+        {
+          next_parent = dir_name (s);
+        }
+      else if (!is_dot (dir))
+        {
+          /* Append DIR to NEXT_PARENT -- as long as it's not ".".  */
+          char *p = mfile_name_concat (next_parent, dir, NULL);
+          int saved_errno = errno;
+          free (next_parent);
+          errno = saved_errno;
+          next_parent = p;
+        }
+
+      if (val == NULL && legit_readlink_errno (errno))
+        {
+          char *b = strdup (base);
+          if (b == NULL)
+            goto fail;
+          *ent_name = b;
+          free (dir);
+          free (s);
+          *dir_fd = fd;
+          /* Convert "" to ".". */
+          if (*next_parent == '\0')
+            {
+              free (next_parent);
+              next_parent = strdup (".");
+            }
+          *parent_full_name = next_parent;
+          return 0;
+        }
+
+      /* Save fstatat overhead until after we've traversed a few symlinks.  */
+      if (5 < ++n)
+        {
+          struct stat sb;
+          if (fstatat (fd, *ent_name, &sb, AT_SYMLINK_NOFOLLOW) == 0
+              && cycle_check (&cycle_check_state, &sb))
+            {
+              errno = ELOOP;
+              return -1;
+            }
+        }
+
+      free (s);
+      free (dir);
+
+      s = val;
+    }
+}
+
+/*
+Local Variables:
+indent-tabs-mode: nil
+End:
+*/
diff --git a/gl/lib/resolve-symlink.h b/gl/lib/resolve-symlink.h
new file mode 100644
index 0000000..c552df7
--- /dev/null
+++ b/gl/lib/resolve-symlink.h
@@ -0,0 +1,2 @@
+extern int resolve_symlink (char const *symlink, int *dir_fd,
+                           char **ent_name, char **full_name);
diff --git a/gl/modules/areadlinkat b/gl/modules/areadlinkat
new file mode 100644
index 0000000..fdaa329
--- /dev/null
+++ b/gl/modules/areadlinkat
@@ -0,0 +1,27 @@
+Description:
+Resolve symbolic links without size limitation.
+
+Files:
+lib/areadlinkat.h
+lib/areadlinkat-with-size.c
+
+Depends-on:
+openat
+readlink
+ssize_t
+unistd
+xalloc
+
+configure.ac:
+
+Makefile.am:
+lib_SOURCES += areadlinkat-with-size.c
+
+Include:
+"areadlink.h"
+
+License:
+LGPL
+
+Maintainer:
+Jim Meyering
diff --git a/gl/modules/resolve-symlink b/gl/modules/resolve-symlink
new file mode 100644
index 0000000..ba4e722
--- /dev/null
+++ b/gl/modules/resolve-symlink
@@ -0,0 +1,27 @@
+Description:
+Resolve a symbolic link, safely.
+
+Files:
+lib/resolve-symlink.h
+lib/resolve-symlink.c
+
+Depends-on:
+cycle-check
+dirname
+filenamecat
+areadlinkat
+strdup
+
+configure.ac:
+
+Makefile.am:
+lib_SOURCES += resolve-symlink.c
+
+Include:
+"resolve-symlink.h"
+
+License:
+GPL
+
+Maintainer:
+Jim Meyering
diff --git a/gl/modules/resolve-symlink-tests b/gl/modules/resolve-symlink-tests
new file mode 100644
index 0000000..001cd53
--- /dev/null
+++ b/gl/modules/resolve-symlink-tests
@@ -0,0 +1,13 @@
+Files:
+tests/test-resolve-symlink.c
+tests/test-resolve-symlink.sh
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-resolve-symlink.sh
+TESTS_ENVIRONMENT += EXEEXT='@EXEEXT@'
+check_PROGRAMS += test-resolve-symlink
+EXTRA_DIST += test-resolve-symlink.sh
diff --git a/gl/tests/test-resolve-symlink.c b/gl/tests/test-resolve-symlink.c
new file mode 100644
index 0000000..c77671c
--- /dev/null
+++ b/gl/tests/test-resolve-symlink.c
@@ -0,0 +1,117 @@
+#include <config.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include "error.h"
+#include "filenamecat.h"
+#include "resolve-symlink.h"
+#include "same-inode.h"
+#include "xgetcwd.h"
+
+#undef _
+#define _(msg) msg
+
+#define STREQ(a, b) (strcmp (a, b) == 0)
+
+static void
+check_one (char const *s)
+{
+  int fd;
+  char *ent_name;
+  char *parent_dir;
+  int err = resolve_symlink (s, &fd, &ent_name, &parent_dir);
+  if (err == 0)
+    {
+      int e;
+      struct stat sb1;
+      struct stat sb2;
+      char *full_name;
+      int flags = O_WRONLY | O_CREAT | O_EXCL;
+      int new_fd;
+      printf ("%s %s\n", ent_name, parent_dir);
+      full_name = file_name_concat (parent_dir, ent_name, NULL);
+      new_fd =
+        (fd == AT_FDCWD
+         ? open (full_name, flags, 0600)
+         : openat (fd, ent_name, flags, 0600));
+      if (new_fd < 0)
+        error (1, errno, _("failed to create %s in %s"), ent_name, parent_dir);
+      assert (fstat (new_fd, &sb1) == 0);
+      close (new_fd);
+      free (parent_dir);
+      e = lstat (full_name, &sb2);
+      if (e)
+        error (0, errno, _("lstat failed for %s"), full_name);
+      if ( ! (e == 0 || errno == ELOOP))
+        error (1, errno, _("unexpected errno after lstat of %s"), full_name);
+      assert (e != 0 || SAME_INODE (sb1, sb2));
+      if (fd != AT_FDCWD)
+        close (fd);
+      free (ent_name);
+      free (full_name);
+    }
+}
+
+static void
+t1 (void)
+{
+  /* Given the symlink chain foo/bar -> baz/bog -> $PWD/no-file, this snippet,
+     int fd = AT_FDCWD; char *val = traverse_symlink (&fd, "foo", "bar", ".");
+     would leave fd open on "foo", and return "baz/bog".  The next call,
+     char *val = traverse_symlink (&fd, "baz", "bog", "foo");
+     would leave fd open on "baz", and return "$PWD/no-file".  The final call
+     char *val = traverse_symlink (&fd, "$PWD", "no-file", ".");
+     would return NULL with say, errno = ENOENT.  */
+  assert (mkdir ("foo", 0700) == 0);
+  assert (mkdir ("foo/baz", 0700) == 0);
+  assert (unlink ("no-file") == 0 || errno == ENOENT);
+  assert (symlink ("baz/bog", "foo/bar") == 0);
+  char *pwd = xgetcwd ();
+  char *no_file = file_name_concat (pwd, "no-file", NULL);
+  assert (symlink (no_file, "foo/baz/bog") == 0);
+
+  /* The following works, but would require exposing the internal
+     traverse_symlink function.  Not worth it.  */
+#if 0
+  char *traverse_symlink (int *fd, char const *dir, char const *ent,
+                          char const *cwd_full_name);
+  {
+    int fd = AT_FDCWD;
+    char *val = traverse_symlink (&fd, "foo", "bar", ".");
+    assert (val && STREQ (val, "baz/bog"));
+    free (val);
+
+    val = traverse_symlink (&fd, "baz", "bog", "foo");
+    assert (val && STREQ (val, no_file));
+    free (val);
+
+    val = traverse_symlink (&fd, pwd, "no-file", ".");
+    assert (val == NULL && errno == ENOENT);
+    free (val);
+    assert (close (fd) == 0);
+  }
+#endif
+
+  check_one ("foo/bar");
+  system ("rm -rf foo");
+}
+
+int
+main (int argc, char **argv)
+{
+  if (argc < 2)
+    t1 ();
+  else
+    check_one (argv[1]);
+  return 0;
+}
+
+/*
+Local Variables:
+indent-tabs-mode: nil
+End:
+*/
diff --git a/gl/tests/test-resolve-symlink.sh b/gl/tests/test-resolve-symlink.sh
new file mode 100755
index 0000000..39548ac
--- /dev/null
+++ b/gl/tests/test-resolve-symlink.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+test "$VERBOSE" = yes && set -x
+
+pwd=`pwd`
+t0=`echo "$0"|sed 's,.*/,,'`.tmp; tmp=$t0/$$
+trap 'status=$?; cd "$pwd" && chmod -R u+rwx $t0 && rm -rf $t0 && exit 
$status' 0
+trap '(exit $?); exit $?' 1 2 13 15
+
+PATH=$pwd:$PATH
+export PATH
+
+framework_failure=0
+mkdir -p $tmp || framework_failure=1
+cd $tmp || framework_failure=1
+
+( for i in `seq 10`; do ln -s b/a a && mkdir b && cd b; done; )
+( for i in `seq 10`; do ln -s d/c c && mkdir d && cd d; done;
+  ln -s no-such c )
+
+if test $framework_failure = 1; then
+  echo "$0: failure in testing framework" 1>&2
+  (exit 1); exit 1
+fi
+
+fail=0
+
+test-resolve-symlink$EXEEXT a > out || fail=1
+cat <<\EOF > exp || fail=1
+a b/b/b/b/b/b/b/b/b/b
+EOF
+diff out exp || fail=1
+
+test-resolve-symlink$EXEEXT c > out || fail=1
+cat <<EOF > exp || fail=1
+no-such d/d/d/d/d/d/d/d/d/d
+EOF
+diff out exp || fail=1
+
+# Run again, now that "no-such" has been created.
+test-resolve-symlink$EXEEXT c > out || fail=1
+diff out exp || fail=1
+
+# Use an absolute name, relative symlink val.
+ln -s nowhere d2
+test-resolve-symlink$EXEEXT `pwd`/d2 > out || fail=1
+
+# Use a relative name, absolute symlink val.
+ln -s `pwd`/nulle-part d3
+test-resolve-symlink$EXEEXT d3 > out || fail=1
+
+# Invoke with no arguments to run its internal test.
+test-resolve-symlink$EXEEXT > out || fail=1
+p=`pwd -P`
+echo "no-file $p" > exp || fail=1
+diff out exp || fail=1
+
+# Exercise the last-resort code (after failed openat)
+# via a symlink through an unreadable directory.
+mkdir unreadable
+chmod u=wx,go= unreadable
+ln -s unreadable/x d4
+test-resolve-symlink$EXEEXT d4 > out || fail=1
+echo "x unreadable" > exp || fail=1
+diff out exp || fail=1
+
+(exit $fail); exit $fail
diff --git a/src/copy.c b/src/copy.c
index 92588bf..138fc01 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -33,7 +33,6 @@
 #include "acl.h"
 #include "backupfile.h"
 #include "buffer-lcm.h"
-#include "canonicalize.h"
 #include "copy.h"
 #include "cp-hash.h"
 #include "euidaccess.h"
@@ -46,6 +45,7 @@
 #include "hash.h"
 #include "hash-triple.h"
 #include "lchmod.h"
+#include "resolve-symlink.h"
 #include "quote.h"
 #include "same.h"
 #include "savedir.h"
@@ -377,16 +377,29 @@ copy_reg (char const *src_name, char const *dst_name,
          if (lstat (dst_name, &dangling_link_sb) == 0
              && S_ISLNK (dangling_link_sb.st_mode))
            {
-             /* FIXME: This is way overkill, since all that's needed
-                is to follow the symlink that is the last file name
-                component.  */
-             name_alloc =
-               canonicalize_filename_mode (dst_name, CAN_MISSING);
-             if (name_alloc)
+             int fd;
+             char *ent_name;
+             char *parent_dir;
+             int err = resolve_symlink (dst_name, &fd, &ent_name, &parent_dir);
+             char *full_name = NULL;
+             if (err == 0
+                 && (full_name = mfile_name_concat (parent_dir,
+                                                    ent_name, NULL)))
+               {
+                 dest_desc = (fd == AT_FDCWD
+                              ? open (full_name, open_flags,
+                                      dst_mode & ~omitted_permissions)
+                              : openat (fd, ent_name, open_flags,
+                                        dst_mode & ~omitted_permissions));
+                 if (dest_desc < 0)
+                   dest_errno = errno;
+                 free (ent_name);
+
+                 /* Free this memory carefully, below.  */
+                 followed_dest_name = full_name;
+               }
+             else
                {
-                 followed_dest_name = name_alloc;
-                 dest_desc = open (followed_dest_name, open_flags,
-                                   dst_mode & ~omitted_permissions);
                  dest_errno = errno;
                }
            }
@@ -654,6 +667,8 @@ close_src_desc:
       return_val = false;
     }
 
+  if (followed_dest_name != dst_name)
+    free ((char *) followed_dest_name);
   free (buf_alloc);
   free (name_alloc);
   return return_val;
-- 
1.5.3-dirty




reply via email to

[Prev in Thread] Current Thread [Next in Thread]