bug-coreutils
[Top][All Lists]
Advanced

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

[PATCH]cp --recursive fails unnecessarily when the names of the latter a


From: Cai Xianchao
Subject: [PATCH]cp --recursive fails unnecessarily when the names of the latter approach become very long
Date: Wed, 05 Dec 2007 14:09:09 +0800
User-agent: Thunderbird 1.5.0.7 (Windows/20060909)

hi,

The file TODO described a bug of cp (package:coreutils-6.9):
"cp --recursive: perform dir traversals in source and dest hierarchy
rather than forming full file names. The latter (current) approach
fails unnecessarily when the names become very long."

We find that if the length of file name is not shorter than PATH_MAX,
it will fail.

Now, we have solved the problem by using the *at functions which were
added to linux in kernel 2.6.16. If the kernel is older than 2.6.16,
cp will run the same as before.

The attachment is the patch.

Signed-off-by: Cai Xianchao <address@hidden>
Signed-off-by: Wen Congyang <address@hidden>
Signed-off-by: Bian Naimeng <address@hidden>
Signed-off-by: Zhou Mingying <address@hidden>

diff --git a/src/copy.c b/src/copy.c
index 4bdb75c..cc99622 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -53,6 +53,10 @@
 #include "utimens.h"
 #include "xreadlink.h"
 #include "yesno.h"
+#include "openat.h"
+#include "acl-internal.h"
+#include "unistd-safer.h"
+#include "dirname.h"
 
 #ifndef HAVE_FCHOWN
 # define HAVE_FCHOWN false
@@ -68,6 +72,11 @@
     ! S_ISLNK (File_mode)                              \
     && euidaccess (File_name, W_OK) != 0)
 
+#if defined __USE_ATFILE && defined PATH_MAX
+#define copy_acl copy_acl_long
+#define same_name same_name_long
+#endif
+
 struct dir_list
 {
   struct dir_list *parent;
@@ -94,6 +103,16 @@ static bool copy_internal (char const *src_name, char const 
*dst_name,
                           bool *copy_into_self,
                           bool *rename_succeeded);
 
+static char const* get_dir_fd(const char *filename, int *dir_fd);
+static char const* change_dir(const char *filename);
+# define CLOSE_DIR_FD(fd1, fd2, fd3) \
+ if(fd1 > -1)  \
+   close(fd1); \
+ if(fd2 > -1)  \
+   close(fd2); \
+ if(fd3 > -1)    \
+   close(fd3);
+
 /* Pointers to the file names:  they're used in the diagnostic that is issued
    when we detect the user is trying to copy a directory into itself.  */
 static char const *top_level_src_name;
@@ -102,6 +121,298 @@ static char const *top_level_dst_name;
 /* The invocation name of this program.  */
 extern char *program_name;
 
+/* Same to copy_acl in lib/acl.c except that 
+   this function can deal with the case when the name is longer than PATH_MAX. 
 */
+
+static int
+copy_acl_long (const char *src_name, int source_desc, const char *dst_name,
+          int dest_desc, mode_t mode)
+{
+  int ret;
+  const char *src_relative_path = src_name;
+  const char *dst_relative_path = dst_name;
+  int src_relative_fd = AT_FDCWD;
+  int dst_relative_fd = AT_FDCWD;
+  int current_fd = -1;
+
+#if defined __USE_ATFILE && defined PATH_MAX
+  current_fd = open (".", O_RDONLY | O_BINARY);
+
+  if (strlen (src_name) >= PATH_MAX)
+    {
+      src_relative_path = get_dir_fd (src_name, &src_relative_fd);
+      if (src_relative_path == NULL)
+        {
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
+          return -1;
+        }
+    }
+
+  if (strlen (dst_name) >= PATH_MAX)
+    {
+      dst_relative_path = get_dir_fd (dst_name, &dst_relative_fd);
+      if (dst_relative_path == NULL)
+        {
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
+          return -1;
+        }
+    }
+#endif
+
+#if USE_ACL && HAVE_ACL_GET_FILE && HAVE_ACL_SET_FILE && HAVE_ACL_FREE
+  /* POSIX 1003.1e (draft 17 -- abandoned) specific version.  */
+
+  acl_t acl;
+
+  if (HAVE_ACL_GET_FD && source_desc != -1)
+    acl = acl_get_fd (source_desc);
+  else
+    {
+      if (src_relative_fd > -1)
+        {
+          fchdir (src_relative_fd);
+          acl = acl_get_file (src_relative_path, ACL_TYPE_ACCESS);
+          fchdir (current_fd);
+        }
+      else
+        {
+          acl = acl_get_file (src_name, ACL_TYPE_ACCESS);
+        }
+    }
+  if (acl == NULL)
+    {
+      if (ACL_NOT_WELL_SUPPORTED (errno))
+        {
+          int return_value;
+
+          if (dst_relative_fd > -1)
+            {
+              fchdir (dst_relative_fd);
+              return_value = set_acl (dst_relative_path, dest_desc, mode);
+              fchdir (current_fd);
+            }
+          else
+            return_value = set_acl (dst_name, dest_desc, mode);
+            CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
+          return return_value;
+        }
+      else
+        {
+          error (0, errno, "%s", quote (src_name));
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
+          return -1;
+        }
+    }
+
+  if (HAVE_ACL_SET_FD && dest_desc != -1)
+    ret = acl_set_fd (dest_desc, acl);
+  else
+    {
+      if (dst_relative_fd > -1)
+        {
+          fchdir (dst_relative_fd);
+          ret = acl_set_file (dst_relative_path, ACL_TYPE_ACCESS, acl);
+          fchdir (current_fd);
+        }
+      else
+        {
+          ret = acl_set_file (dst_name, ACL_TYPE_ACCESS, acl);
+        }
+    }
+  if (ret != 0)
+    {
+      int saved_errno = errno;
+
+      if (ACL_NOT_WELL_SUPPORTED (errno))
+        {
+          int n = acl_entries (acl);
+
+          acl_free (acl);
+          if (n == 3)
+            {
+              if (chmod_or_fchmodat (dst_name, dest_desc, mode, 
dst_relative_fd, dst_relative_path) != 0)
+                saved_errno = errno;
+              else
+                {
+                  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
+                  return 0;
+                }
+            }
+          else
+            chmod_or_fchmodat (dst_name, dest_desc, mode, dst_relative_fd, 
dst_relative_path);
+        }
+      else
+        {
+          acl_free (acl);
+          chmod_or_fchmodat (dst_name, dest_desc, mode, dst_relative_fd, 
dst_relative_path);
+        }
+      error (0, saved_errno, _("preserving permissions for %s"),
+             quote (dst_name));
+      CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
+      return -1;
+    }
+  else
+    acl_free (acl);
+
+  if (mode & (S_ISUID | S_ISGID | S_ISVTX))
+    {
+      /* We did not call chmod so far, so the special bits have not yet
+         been set.  */
+
+      if (chmod_or_fchmodat (dst_name, dest_desc, mode, dst_relative_fd, 
dst_relative_path) != 0)
+        {
+          error (0, errno, _("preserving permissions for %s"),
+                 quote (dst_name));
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
+          return -1;
+        }
+    }
+
+  if (S_ISDIR (mode))
+    {
+      if (src_relative_fd > -1)
+        {
+          fchdir (src_relative_fd);
+          acl = acl_get_file (src_relative_path, ACL_TYPE_DEFAULT);
+          fchdir (current_fd);
+        }
+      else
+        acl = acl_get_file (src_name, ACL_TYPE_DEFAULT);
+      if (acl == NULL)
+        {
+          error (0, errno, "%s", quote (src_name));
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
+          return -1;
+        }
+
+      if (dst_relative_fd > -1)
+        fchdir (dst_relative_fd);
+      if (acl_set_file (dst_relative_path, ACL_TYPE_DEFAULT, acl))
+        {
+          error (0, errno, _("preserving permissions for %s"),
+                 quote (dst_name));
+          acl_free (acl);
+          fchdir (current_fd);
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
+          return -1;
+        }
+      else
+        acl_free (acl);
+      fchdir (current_fd);
+    }
+  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
+  return 0;
+#else
+  ret = chmod_or_fchmodat (dst_name, dest_desc, mode, dst_relative_fd, 
dst_relative_path);
+  if (ret != 0)
+    error (0, errno, _("preserving permissions for %s"), quote (dst_name));
+  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
+  return ret;
+#endif
+}
+
+/* Same to same_name in lib/same.c except that 
+   this function can deal with the case when the name is longer than PATH_MAX. 
 */
+
+static bool
+same_name_long (const char *source, const char *dest)
+{
+  /* Compare the basenames.  */
+  char const *source_basename = last_component (source);
+  char const *dest_basename = last_component (dest);
+  size_t source_baselen = base_len (source_basename);
+  size_t dest_baselen = base_len (dest_basename);
+  bool identical_basenames =
+    (source_baselen == dest_baselen
+     && memcmp (source_basename, dest_basename, dest_baselen) == 0);
+  bool compare_dirs = identical_basenames;
+  bool same = false;
+
+#if ! _POSIX_NO_TRUNC && HAVE_PATHCONF && defined _PC_NAME_MAX
+  /* This implementation silently truncates components of file names.  If
+     the base names might be truncated, check whether the truncated
+     base names are the same, while checking the directories.  */
+  size_t slen_max = HAVE_LONG_FILE_NAMES ? 255 : _POSIX_NAME_MAX;
+  size_t min_baselen = MIN (source_baselen, dest_baselen);
+  if (slen_max <= min_baselen
+      && memcmp (source_basename, dest_basename, slen_max) == 0)
+    compare_dirs = true;
+#endif
+
+  if (compare_dirs)
+    {
+      struct stat source_dir_stats;
+      struct stat dest_dir_stats;
+      char *source_dirname, *dest_dirname;
+      const char *src_relative_path;
+      const char *dst_relative_path;
+      int src_relative_fd = AT_FDCWD;
+      int dst_relative_fd = AT_FDCWD;
+
+      /* Compare the parent directories (via the device and inode numbers).  */
+      source_dirname = dir_name (source);
+      dest_dirname = dir_name (dest);
+#if defined __USE_ATFILE && defined PATH_MAX
+      if (strlen (source_dirname) >= PATH_MAX)
+        {
+          src_relative_path = get_dir_fd (source_dirname, &src_relative_fd);
+          if (src_relative_path == NULL)
+            {
+              error (0, 0, _("cannot get the directory descriptor for file 
%s"), quote (source_dirname));
+              return false;
+            }
+        }
+      else
+        src_relative_path = source_dirname;
+
+      if (strlen (dest_dirname) >= PATH_MAX)
+        {
+          dst_relative_path = get_dir_fd (dest_dirname, &dst_relative_fd);
+          if (dst_relative_path == NULL)
+            {
+              error (0, 0, _("cannot get the directory descriptor for file 
%s"), quote (dest_dirname));
+              CLOSE_DIR_FD(src_relative_fd, -1, -1)
+              return false;
+            }
+        }
+      else
+        dst_relative_path = dest_dirname;
+#endif
+      if (stat_or_fstatat (source_dirname, &source_dir_stats, src_relative_fd, 
src_relative_path))
+          /* Shouldn't happen.  */
+          error (1, errno, "%s", source_dirname);
+
+      if (stat_or_fstatat (dest_dirname, &dest_dir_stats, dst_relative_fd, 
dst_relative_path))
+          /* Shouldn't happen.  */
+          error (1, errno, "%s", dest_dirname);
+
+      same = SAME_INODE (source_dir_stats, dest_dir_stats);
+
+#if ! _POSIX_NO_TRUNC && HAVE_PATHCONF && defined _PC_NAME_MAX
+      if (same && ! identical_basenames)
+        {
+          long name_max = (errno = 0, pathconf (dest_dirname, _PC_NAME_MAX));
+          if (name_max < 0)
+            {
+              if (errno)
+                  /* Shouldn't happen.  */
+                  error (1, errno, "%s", dest_dirname);
+              same = false;
+            }
+          else
+            same = (name_max <= min_baselen
+                    && memcmp (source_basename, dest_basename, name_max) == 0);
+        }
+#endif
+
+      free (source_dirname);
+      free (dest_dirname);
+      CLOSE_DIR_FD(src_relative_fd, dst_relative_fd, -1)
+    }
+
+  return same;
+}
+
 /* FIXME: describe */
 /* FIXME: rewrite this to use a hash table so we avoid the quadratic
    performance hit that's probably noticeable only on trees deeper
@@ -137,7 +448,35 @@ copy_dir (char const *src_name_in, char const 
*dst_name_in, bool new_dst,
   struct cp_options non_command_line_options = *x;
   bool ok = true;
 
+#if !defined  __USE_ATFILE || !defined PATH_MAX
   name_space = savedir (src_name_in);
+#else
+  int src_fd; 
+  int relative_fd;
+  const char *relative_path;
+  
+  if (strlen (src_name_in) >= PATH_MAX)
+    {
+      relative_path = get_dir_fd (src_name_in, &relative_fd);
+      if (relative_path == NULL)
+        name_space = NULL;
+      else
+        {
+          src_fd = openat (relative_fd, relative_path, 0);
+          if (src_fd == -1)
+            name_space = NULL;
+          else
+            {
+              src_fd = fd_safer(src_fd);
+              name_space = fdsavedir(src_fd);
+            }
+          CLOSE_DIR_FD(relative_fd, -1, -1)
+        }
+    }
+  else
+    name_space = savedir (src_name_in);
+#endif
+
   if (name_space == NULL)
     {
       /* This diagnostic is a bit vague because savedir can fail in
@@ -235,13 +574,13 @@ set_author (const char *dst_name, int dest_desc, const 
struct stat *src_sb)
    Use DESC if DESC is valid and fchmod is available, NAME otherwise.  */
 
 static int
-fchmod_or_lchmod (int desc, char const *name, mode_t mode)
+fchmod_or_lchmod (int desc, char const *name, mode_t mode, int relative_fd, 
const char *relative_path)
 {
 #if HAVE_FCHMOD
   if (0 <= desc)
     return fchmod (desc, mode);
 #endif
-  return lchmod (name, mode);
+  return lchmod_or_fchmodat (name, mode, relative_fd, relative_path);
 }
 
 /* Copy a regular file from SRC_NAME to DST_NAME.
@@ -270,11 +609,50 @@ copy_reg (char const *src_name, char const *dst_name,
   struct stat sb;
   struct stat src_open_sb;
   bool return_val = true;
+  const char *src_relative_path = src_name;
+  const char *dst_relative_path = dst_name;
+  int src_relative_fd = AT_FDCWD;
+  int dst_relative_fd = AT_FDCWD;
+  int current_fd = -1;
+
+#if defined __USE_ATFILE && defined PATH_MAX
+  current_fd = open (".", O_RDONLY | O_BINARY);
+
+  if (strlen (src_name) >= PATH_MAX)
+    {
+      src_relative_path = get_dir_fd (src_name, &src_relative_fd);
+      if (src_relative_path == NULL)
+        {
+          error (0, 0, _("cannot get the directory descriptor for file %s"), 
quote (src_name));
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
+          return false;
+        }
+    }
+
+  if (strlen (dst_name) >= PATH_MAX)
+    {
+      dst_relative_path = get_dir_fd (dst_name, &dst_relative_fd);
+      if (dst_relative_path == NULL)
+        {
+          error (0, 0, _("cannot get the directory descriptor for file %s"), 
quote (dst_name));
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
+          return false;
+        }
+    }
+#endif
+
+  if (src_relative_fd > -1)
+    {
+      source_desc = openat (src_relative_fd, src_relative_path, O_RDONLY | 
O_BINARY);
+      source_desc = fd_safer (source_desc);
+    }
+  else
+    source_desc = open (src_name, O_RDONLY | O_BINARY);
 
-  source_desc = open (src_name, O_RDONLY | O_BINARY);
   if (source_desc < 0)
     {
       error (0, errno, _("cannot open %s for reading"), quote (src_name));
+      CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
       return false;
     }
 
@@ -300,11 +678,17 @@ copy_reg (char const *src_name, char const *dst_name,
      by the specs for both cp and mv.  */
   if (! *new_dst)
     {
-      dest_desc = open (dst_name, O_WRONLY | O_TRUNC | O_BINARY);
+      if (dst_relative_fd > -1)
+        {
+          dest_desc = openat (dst_relative_fd, dst_relative_path, O_WRONLY | 
O_TRUNC | O_BINARY);
+          dest_desc = fd_safer (dest_desc);
+        }
+      else
+        dest_desc = open (dst_name, O_WRONLY | O_TRUNC | O_BINARY);
 
       if (dest_desc < 0 && x->unlink_dest_after_failed_open)
        {
-         if (unlink (dst_name) != 0)
+          if (unlink_or_unlinkat (dst_name, dst_relative_fd, 
dst_relative_path) != 0)
            {
              error (0, errno, _("cannot remove %s"), quote (dst_name));
              return_val = false;
@@ -319,8 +703,15 @@ copy_reg (char const *src_name, char const *dst_name,
     }
 
   if (*new_dst)
-    dest_desc = open (dst_name, O_WRONLY | O_CREAT | O_EXCL | O_BINARY,
-                     dst_mode & ~omitted_permissions);
+    if (dst_relative_fd > -1)
+      {
+        dest_desc = openat (dst_relative_fd, dst_relative_path, O_WRONLY | 
O_CREAT | O_EXCL | O_BINARY,
+                            dst_mode & ~omitted_permissions);
+        dest_desc = fd_safer (dest_desc);
+      }
+    else
+      dest_desc = open (dst_name, O_WRONLY | O_CREAT | O_EXCL | O_BINARY,
+                        dst_mode & ~omitted_permissions);       
   else
     omitted_permissions = 0;
 
@@ -518,33 +909,49 @@ copy_reg (char const *src_name, char const *dst_name,
       timespec[0] = get_stat_atime (src_sb);
       timespec[1] = get_stat_mtime (src_sb);
 
-      if (futimens (dest_desc, dst_name, timespec) != 0)
+      if (dst_relative_fd > -1)
+        fchdir (dst_relative_fd);
+      if (futimens (dest_desc, dst_relative_path, timespec) != 0)
        {
          error (0, errno, _("preserving times for %s"), quote (dst_name));
          if (x->require_preserve)
            {
              return_val = false;
+              if (current_fd > -1)
+                fchdir (current_fd);
              goto close_src_and_dst_desc;
            }
        }
+      if (current_fd > -1)
+        fchdir (current_fd);
     }
 
   if (x->preserve_ownership && ! SAME_OWNER_AND_GROUP (*src_sb, sb))
     {
-      switch (set_owner (x, dst_name, dest_desc,
-                        src_sb->st_uid, src_sb->st_gid))
+      if (dst_relative_fd > -1)
+        fchdir (dst_relative_fd);
+      switch (set_owner (x, dst_relative_path, dest_desc,
+                         src_sb->st_uid, src_sb->st_gid))
        {
        case -1:
          return_val = false;
+          if (current_fd > -1)
+            fchdir (current_fd);
          goto close_src_and_dst_desc;
 
        case 0:
          src_mode &= ~ (S_ISUID | S_ISGID | S_ISVTX);
          break;
        }
+      if (current_fd > -1)
+        fchdir (current_fd);
     }
 
-  set_author (dst_name, dest_desc, src_sb);
+  if (dst_relative_fd > -1)
+    fchdir (dst_relative_fd);
+  set_author (dst_relative_path, dest_desc, src_sb);
+  if (current_fd > -1)
+    fchdir (current_fd);
 
   if (x->preserve_mode || x->move_mode)
     {
@@ -554,14 +961,19 @@ copy_reg (char const *src_name, char const *dst_name,
     }
   else if (x->set_mode)
     {
-      if (set_acl (dst_name, dest_desc, x->mode) != 0)
+      if (dst_relative_fd > -1)
+        fchdir (dst_relative_fd);
+      if (set_acl (dst_relative_path, dest_desc, x->mode) != 0)
        return_val = false;
+      if (current_fd > -1)
+        fchdir (current_fd);
+
     }
   else if (omitted_permissions)
     {
       omitted_permissions &= ~ cached_umask ();
       if (omitted_permissions
-         && fchmod_or_lchmod (dest_desc, dst_name, dst_mode) != 0)
+          && fchmod_or_lchmod (dest_desc, dst_name, dst_mode, dst_relative_fd, 
dst_relative_path) != 0)
        {
          error (0, errno, _("preserving permissions for %s"),
                 quote (dst_name));
@@ -584,6 +996,7 @@ close_src_desc:
     }
 
   free (buf_alloc);
+  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, current_fd)
   return return_val;
 }
 
@@ -607,7 +1020,9 @@ close_src_desc:
 static bool
 same_file_ok (char const *src_name, struct stat const *src_sb,
              char const *dst_name, struct stat const *dst_sb,
-             const struct cp_options *x, bool *return_now, bool *unlink_src)
+              const struct cp_options *x, bool *return_now, bool *unlink_src,
+              int src_relative_fd, const char *src_relative_path,
+              int dst_relative_fd, const char *dst_relative_path)
 {
   const struct stat *src_sb_link;
   const struct stat *dst_sb_link;
@@ -649,8 +1064,8 @@ same_file_ok (char const *src_name, struct stat const 
*src_sb,
       if (!same)
        return true;
 
-      if (lstat (dst_name, &tmp_dst_sb) != 0
-         || lstat (src_name, &tmp_src_sb) != 0)
+      if (lstat_or_fstatat (dst_name, &tmp_dst_sb, dst_relative_fd, 
dst_relative_path) != 0
+          || lstat_or_fstatat (src_name, &tmp_src_sb, src_relative_fd, 
src_relative_path) != 0)
        return true;
 
       src_sb_link = &tmp_src_sb;
@@ -771,12 +1186,12 @@ same_file_ok (char const *src_name, struct stat const 
*src_sb,
     {
       if ( ! S_ISLNK (src_sb_link->st_mode))
        tmp_src_sb = *src_sb_link;
-      else if (stat (src_name, &tmp_src_sb) != 0)
+      else if (stat_or_fstatat(src_name, &tmp_src_sb, src_relative_fd, 
src_relative_path) != 0)
        return true;
 
       if ( ! S_ISLNK (dst_sb_link->st_mode))
        tmp_dst_sb = *dst_sb_link;
-      else if (stat (dst_name, &tmp_dst_sb) != 0)
+      else if (stat_or_fstatat (dst_name, &tmp_dst_sb, dst_relative_fd, 
dst_relative_path) != 0)
        return true;
 
       if ( ! SAME_INODE (tmp_src_sb, tmp_dst_sb))
@@ -922,7 +1337,8 @@ seen_file (Hash_table const *ht, char const *file,
    If memory allocation fails, exit immediately.  */
 static void
 record_file (Hash_table *ht, char const *file,
-            struct stat const *stats)
+             struct stat const *stats, 
+             int relative_fd, const char *relative_path)
 {
   struct F_triple *ent;
 
@@ -939,7 +1355,7 @@ record_file (Hash_table *ht, char const *file,
   else
     {
       struct stat sb;
-      if (lstat (file, &sb) != 0)
+      if (lstat_or_fstatat (file, &sb, relative_fd, relative_path) != 0)
        return;
       ent->st_ino = sb.st_ino;
       ent->st_dev = sb.st_dev;
@@ -1029,23 +1445,60 @@ copy_internal (char const *src_name, char const 
*dst_name,
   bool delayed_ok;
   bool copied_as_regular = false;
   bool preserve_metadata;
+  int currentpath_fd = -1; 
+  int src_relative_fd = AT_FDCWD;
+  const char *src_relative_path = src_name;
+  int dst_relative_fd = AT_FDCWD;
+  const char *dst_relative_path= dst_name;
+  char *symlink_relative_path = (char *)dst_name;
+  int symlink_relative_fd = AT_FDCWD;
+  
+#if defined  __USE_ATFILE && defined PATH_MAX
+  currentpath_fd = open (".", O_RDONLY);
+  int length_src = strlen (src_name);
+  int length_dst = strlen (dst_name);
+  if (length_src >= PATH_MAX)
+    {
+      src_relative_path = get_dir_fd (src_name, &src_relative_fd);
+      if (src_relative_path == NULL)
+        {
+          error (0, 0, _("cannot get the directory descriptor for file %s"), 
quote (src_name));
+          CLOSE_DIR_FD (currentpath_fd, -1, -1)
+          return false;  
+        }       
+    }
+  if (length_dst >= PATH_MAX)
+    {
+      dst_relative_path = get_dir_fd (dst_name, &dst_relative_fd);
+      if (dst_relative_path == NULL)
+        {
+          error (0, 0, _("cannot get the directory descriptor for file %s"), 
quote (dst_name));
+          CLOSE_DIR_FD (currentpath_fd, src_relative_fd, -1)
+          return false;  
+        }
+      symlink_relative_path = (char *)dst_relative_path;
+      symlink_relative_fd = dst_relative_fd;
+    }
+#endif
 
   if (x->move_mode && rename_succeeded)
     *rename_succeeded = false;
 
   *copy_into_self = false;
-
-  if (XSTAT (x, src_name, &src_sb) != 0)
-    {
-      error (0, errno, _("cannot stat %s"), quote (src_name));
-      return false;
-    }
+  
+    if (XSTAT_or_XSTATAT(x, src_name, &src_sb, src_relative_fd, 
src_relative_path) != 0)
+      {
+        error (0, errno, _("cannot stat %s"), quote (src_name));
+        CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
+        return false;
+      }
 
   src_mode = src_sb.st_mode;
 
   if (S_ISDIR (src_mode) && !x->recursive)
     {
       error (0, 0, _("omitting directory %s"), quote (src_name));
+      CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
       return false;
     }
 
@@ -1061,19 +1514,24 @@ copy_internal (char const *src_name, char const 
*dst_name,
        {
          error (0, 0, _("warning: source file %s specified more than once"),
                 quote (src_name));
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
          return true;
        }
 
-      record_file (x->src_info, src_name, &src_sb);
+      record_file (x->src_info, src_name, &src_sb, src_relative_fd, 
src_relative_path);
     }
 
+  int dst_backup_fd = AT_FDCWD;
+  const char *dst_backup_relative_path = dst_name;
+
   if (!new_dst)
     {
-      if (XSTAT (x, dst_name, &dst_sb) != 0)
+      if (XSTAT_or_XSTATAT (x, dst_name, &dst_sb, dst_relative_fd, 
dst_relative_path) != 0)
        {
          if (errno != ENOENT)
            {
              error (0, errno, _("cannot stat %s"), quote (dst_name));
+              CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
              return false;
            }
          else
@@ -1088,10 +1546,13 @@ copy_internal (char const *src_name, char const 
*dst_name,
          bool unlink_src;
 
          if (! same_file_ok (src_name, &src_sb, dst_name, &dst_sb,
-                             x, &return_now, &unlink_src))
+                              x, &return_now, &unlink_src,
+                              src_relative_fd, src_relative_path,
+                              dst_relative_fd, dst_relative_path))
            {
              error (0, 0, _("%s and %s are the same file"),
                     quote_n (0, src_name), quote_n (1, dst_name));
+              CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
              return false;
            }
 
@@ -1104,8 +1565,16 @@ copy_internal (char const *src_name, char const 
*dst_name,
             cp and mv treat -i and -f differently.  */
          if (x->move_mode)
            {
-             if (abandon_move (x, dst_name, &dst_sb)
-                 || (unlink_src && unlink (src_name) == 0))
+              bool ret_abandon_move;
+              if (dst_relative_fd > -1)
+                {
+                  fchdir (dst_relative_fd);
+                  ret_abandon_move = abandon_move (x, dst_relative_path, 
&dst_sb);
+                  fchdir (currentpath_fd);
+                }
+              else
+                 ret_abandon_move = abandon_move (x, dst_name, &dst_sb);
+              if (ret_abandon_move || (unlink_src && unlink_or_unlinkat 
(src_name, src_relative_fd, src_relative_path) == 0))
                {
                  /* Pretend the rename succeeded, so the caller (mv)
                     doesn't end up removing the source file.  */
@@ -1113,26 +1582,50 @@ copy_internal (char const *src_name, char const 
*dst_name,
                    *rename_succeeded = true;
                  if (unlink_src && x->verbose)
                    printf (_("removed %s\n"), quote (src_name));
+                  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
                  return true;
                }
              if (unlink_src)
                {
                  error (0, errno, _("cannot remove %s"), quote (src_name));
+                  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
                  return false;
                }
            }
          else
            {
-             if (! S_ISDIR (src_mode)
-                 && (x->interactive == I_ALWAYS_NO
-                     || (x->interactive == I_ASK_USER
-                         && (overwrite_prompt (dst_name, &dst_sb), 1)
-                         && ! yesno ())))
-               return true;
+              int ret_overwrite_prompt;
+              if (dst_relative_fd > -1)
+                {
+                  fchdir (dst_relative_fd);
+                  ret_overwrite_prompt = 
+                                ! S_ISDIR (src_mode)
+                        && (x->interactive == I_ALWAYS_NO
+                             || (x->interactive == I_ASK_USER
+                                 && (overwrite_prompt (dst_relative_path, 
&dst_sb), 1)
+                                 && ! yesno ()));
+                  fchdir (currentpath_fd);
+                }
+              else
+                {
+                  ret_overwrite_prompt =  ! S_ISDIR (src_mode)
+                        && (x->interactive == I_ALWAYS_NO
+                            || (x->interactive == I_ASK_USER
+                                && (overwrite_prompt (dst_name, &dst_sb), 1)
+                                && ! yesno ()));
+                }
+              if (ret_overwrite_prompt)
+                {
+                  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
+                  return true;
+                }
            }
 
          if (return_now)
-           return true;
+            {
+              CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
+              return true;
+            }
 
          if (!S_ISDIR (dst_sb.st_mode))
            {
@@ -1148,6 +1641,7 @@ copy_internal (char const *src_name, char const *dst_name,
                      error (0, 0,
                       _("cannot overwrite non-directory %s with directory %s"),
                             quote_n (0, dst_name), quote_n (1, src_name));
+                      CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
                      return false;
                    }
                }
@@ -1166,6 +1660,7 @@ copy_internal (char const *src_name, char const *dst_name,
                  error (0, 0,
                         _("will not overwrite just-created %s with %s"),
                         quote_n (0, dst_name), quote_n (1, src_name));
+                  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
                  return false;
                }
            }
@@ -1184,6 +1679,7 @@ copy_internal (char const *src_name, char const *dst_name,
                      error (0, 0,
                         _("cannot overwrite directory %s with non-directory"),
                             quote (dst_name));
+                      CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
                      return false;
                    }
                }
@@ -1200,7 +1696,16 @@ copy_internal (char const *src_name, char const 
*dst_name,
                                 ? UTIMECMP_TRUNCATE_SOURCE
                                 : 0);
 
-                 if (0 <= utimecmp (dst_name, &dst_sb, &src_sb, options))
+                  int ret_utimecmp;
+                  if (dst_relative_fd > -1)
+                    {
+                      fchdir (dst_relative_fd);
+                      ret_utimecmp = utimecmp (dst_relative_path, &dst_sb, 
&src_sb, options);
+                      fchdir (currentpath_fd);
+                    }
+                  else
+                    ret_utimecmp = utimecmp (dst_name, &dst_sb, &src_sb, 
options);
+                  if (0 <= ret_utimecmp)
                    {
                      /* We're using --update and the destination is not older
                         than the source, so do not copy or move.  Pretend the
@@ -1208,6 +1713,7 @@ copy_internal (char const *src_name, char const *dst_name,
                         end up removing the source file.  */
                      if (rename_succeeded)
                        *rename_succeeded = true;
+                      CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
                      return true;
                    }
                }
@@ -1222,6 +1728,7 @@ copy_internal (char const *src_name, char const *dst_name,
                  error (0, 0,
                       _("cannot move directory onto non-directory: %s -> %s"),
                         quote_n (0, src_name), quote_n (0, dst_name));
+                  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
                  return false;
                }
            }
@@ -1237,7 +1744,27 @@ copy_internal (char const *src_name, char const 
*dst_name,
                 existing hierarchy.  */
              && (x->move_mode || ! S_ISDIR (dst_sb.st_mode)))
            {
-             char *tmp_backup = find_backup_file_name (dst_name,
+              char *tmp_backup;
+              if (dst_relative_fd > -1)
+                {
+                  int dst_length = strlen (dst_name);
+                  int relative_length = strlen (dst_relative_path);
+                  int tmp_length;
+                  char *tmp;
+
+                  fchdir (dst_relative_fd);
+                  tmp = find_backup_file_name (dst_relative_path,
+                                                        x->backup_type);
+                  tmp_length = strlen (tmp);
+                  tmp_backup = xmalloc 
(dst_length-relative_length+tmp_length+1);
+
+                  memcpy (tmp_backup, dst_name, dst_length-relative_length);
+                  memcpy (tmp_backup+dst_length-relative_length, tmp, 
tmp_length);
+                  free (tmp);
+                  fchdir (currentpath_fd);
+                }
+              else
+                tmp_backup = find_backup_file_name (dst_name,
                                                        x->backup_type);
 
              /* Detect (and fail) when creating the backup file would
@@ -1255,6 +1782,7 @@ copy_internal (char const *src_name, char const *dst_name,
                  error (0, 0, fmt,
                         quote_n (0, dst_name),
                         quote_n (1, src_name));
+                  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
                  free (tmp_backup);
                  return false;
                }
@@ -1266,11 +1794,25 @@ copy_internal (char const *src_name, char const 
*dst_name,
                 to use fts, so using alloca here will be less of a problem.  */
              ASSIGN_STRDUPA (dst_backup, tmp_backup);
              free (tmp_backup);
-             if (rename (dst_name, dst_backup) != 0)
+              dst_backup_relative_path = dst_backup;
+#if defined __USE_ATFILE && defined PATH_MAX
+              if (strlen (dst_backup) >= PATH_MAX)
+                {
+                  dst_backup_relative_path = get_dir_fd (dst_backup, 
&dst_backup_fd);
+                  if (dst_backup_relative_path == NULL)
+                    {
+                      error (0, 0, _("cannot get the directory descriptor for 
file %s"), quote (dst_backup));
+                      CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
+                      return false;
+                    }
+                }
+#endif
+              if (rename_or_renameat (dst_name, dst_backup, dst_relative_fd, 
dst_relative_path, dst_backup_fd, dst_backup_relative_path) != 0)
                {
                  if (errno != ENOENT)
                    {
                      error (0, errno, _("cannot backup %s"), quote (dst_name));
+                      CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
                      return false;
                    }
                  else
@@ -1292,16 +1834,17 @@ copy_internal (char const *src_name, char const 
*dst_name,
                           && S_ISLNK (src_sb.st_mode))
                       ))
            {
-             if (unlink (dst_name) != 0 && errno != ENOENT)
+              if (unlink_or_unlinkat (dst_name, dst_relative_fd, 
dst_relative_path) != 0 && errno != ENOENT)
                {
                  error (0, errno, _("cannot remove %s"), quote (dst_name));
+                  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
                  return false;
                }
              new_dst = true;
              if (x->verbose)
                printf (_("removed %s\n"), quote (dst_name));
            }
-       }
+        }
     }
 
   /* If the source is a directory, we don't always create the destination
@@ -1393,36 +1936,57 @@ copy_internal (char const *src_name, char const 
*dst_name,
        }
       else
        {
-         bool link_failed = (link (earlier_file, dst_name) != 0);
+          int earf_fd = AT_FDCWD;
+          const char *earf_relative_path = earlier_file;
+#if defined __USE_ATFILE && defined PATH_MAX
+          if (strlen (earlier_file) >= PATH_MAX)
+            {
+              earf_relative_path = get_dir_fd(earlier_file, &earf_fd);
+              if (earf_relative_path == NULL)
+                {
+                  error (0, 0, _("cannot get the directory descriptor for file 
%s"), quote (earlier_file));
+                  goto un_backup;
+                }
+            }
+#endif
+          bool link_failed = (link_or_linkat (earlier_file, dst_name, 
+                                              earf_fd, earf_relative_path,
+                                              dst_relative_fd, 
dst_relative_path) != 0);
 
          /* If the link failed because of an existing destination,
             remove that file and then call link again.  */
          if (link_failed && errno == EEXIST)
            {
-             if (unlink (dst_name) != 0)
+              if (unlink_or_unlinkat (dst_name, dst_relative_fd, 
dst_relative_path) != 0)
                {
                  error (0, errno, _("cannot remove %s"), quote (dst_name));
+                  CLOSE_DIR_FD (earf_fd, -1, -1)
                  goto un_backup;
                }
              if (x->verbose)
                printf (_("removed %s\n"), quote (dst_name));
-             link_failed = (link (earlier_file, dst_name) != 0);
+              link_failed = (link_or_linkat (earlier_file, dst_name, 
+                                             earf_fd, earf_relative_path,
+                                             dst_relative_fd, 
dst_relative_path) != 0);
            }
 
          if (link_failed)
            {
              error (0, errno, _("cannot create hard link %s to %s"),
                     quote_n (0, dst_name), quote_n (1, earlier_file));
+              CLOSE_DIR_FD (earf_fd, -1, -1)
              goto un_backup;
            }
 
+          CLOSE_DIR_FD (earf_fd, -1, -1)
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
          return true;
        }
     }
 
   if (x->move_mode)
     {
-      if (rename (src_name, dst_name) == 0)
+      if (rename_or_renameat (src_name, dst_name, src_relative_fd, 
src_relative_path, dst_relative_fd, dst_relative_path) == 0)
        {
          if (x->verbose && S_ISDIR (src_mode))
            emit_verbose (src_name, dst_name,
@@ -1440,9 +2004,10 @@ copy_internal (char const *src_name, char const 
*dst_name,
                 changed those, and `mv' always uses lstat.
                 We could limit it further by operating
                 only on non-directories.  */
-             record_file (x->dest_info, dst_name, &src_sb);
+              record_file (x->dest_info, dst_name, &src_sb, dst_relative_fd, 
dst_relative_path);
            }
 
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
          return true;
        }
 
@@ -1468,6 +2033,7 @@ copy_internal (char const *src_name, char const *dst_name,
          /* FIXME-cleanup: Don't return true here; adjust mv.c accordingly.
             The only caller that uses this code (mv.c) ends up setting its
             exit status to nonzero when copy_into_self is nonzero.  */
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd,currentpath_fd);
          return true;
        }
 
@@ -1502,18 +2068,20 @@ copy_internal (char const *src_name, char const 
*dst_name,
                 _("cannot move %s to %s"),
                 quote_n (0, src_name), quote_n (1, dst_name));
          forget_created (src_sb.st_ino, src_sb.st_dev);
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
          return false;
        }
 
       /* The rename attempt has failed.  Remove any existing destination
         file so that a cross-device `mv' acts as if it were really using
         the rename syscall.  */
-      if (unlink (dst_name) != 0 && errno != ENOENT)
+      if (unlink_or_unlinkat (dst_name, dst_relative_fd, dst_relative_path) != 
0 && errno != ENOENT)
        {
          error (0, errno,
             _("inter-device move failed: %s to %s; unable to remove target"),
                 quote_n (0, src_name), quote_n (1, dst_name));
          forget_created (src_sb.st_ino, src_sb.st_dev);
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
          return false;
        }
 
@@ -1568,7 +2136,7 @@ copy_internal (char const *src_name, char const *dst_name,
             (src_mode & ~S_IRWXUGO) != 0.  However, common practice is
             to ask mkdir to copy all the CHMOD_MODE_BITS, letting mkdir
             decide what to do with S_ISUID | S_ISGID | S_ISVTX.  */
-         if (mkdir (dst_name, dst_mode_bits & ~omitted_permissions) != 0)
+          if (mkdir_or_mkdirat (dst_name, dst_mode_bits & 
~omitted_permissions, dst_relative_fd, dst_relative_path) != 0)
            {
              error (0, errno, _("cannot create directory %s"),
                     quote (dst_name));
@@ -1579,7 +2147,7 @@ copy_internal (char const *src_name, char const *dst_name,
             for writing the directory's contents. Check if these
             permissions are there.  */
 
-         if (lstat (dst_name, &dst_sb) != 0)
+          if (lstat_or_fstatat (dst_name, &dst_sb, dst_relative_fd, 
dst_relative_path) != 0)
            {
              error (0, errno, _("cannot stat %s"), quote (dst_name));
              goto un_backup;
@@ -1591,7 +2159,7 @@ copy_internal (char const *src_name, char const *dst_name,
              dst_mode = dst_sb.st_mode;
              restore_dst_mode = true;
 
-             if (lchmod (dst_name, dst_mode | S_IRWXU) != 0)
+              if (lchmod_or_fchmodat (dst_name, dst_mode | S_IRWXU, 
dst_relative_fd, dst_relative_path) != 0)
                {
                  error (0, errno, _("setting permissions for %s"),
                         quote (dst_name));
@@ -1656,12 +2224,41 @@ copy_internal (char const *src_name, char const 
*dst_name,
              goto un_backup;
            }
        }
-      if (symlink (src_name, dst_name) != 0)
+      if (symlink_relative_fd < 0)
+        {
+          char *direc_name = dir_name (dst_name);
+
+          /* only for symlinkat */
+          symlink_relative_fd = open (direc_name, O_RDONLY | O_BINARY);
+          if (symlink_relative_fd < 0)
+            {
+              error (0, errno, _("cannot get %s's file descriptor "), quote 
(direc_name));
+              free (direc_name);
+              CLOSE_DIR_FD (currentpath_fd, src_relative_fd, -1)
+              return false;
+            }
+          symlink_relative_path = base_name (dst_name);
+          free (direc_name);
+        }
+
+      if (symlink_or_symlinkat (src_name, dst_name,
+                                src_relative_fd, src_relative_path,
+                                symlink_relative_fd, symlink_relative_path) != 
0)
        {
          error (0, errno, _("cannot create symbolic link %s to %s"),
                 quote_n (0, dst_name), quote_n (1, src_name));
+          if (dst_relative_fd < 0)
+            {
+              close (symlink_relative_fd);
+              free (symlink_relative_path);
+            }
          goto un_backup;
        }
+        if (dst_relative_fd < 0)
+          {
+            close (symlink_relative_fd);
+            free (symlink_relative_path);
+          }
     }
 
   else if (x->hard_link
@@ -1681,7 +2278,9 @@ copy_internal (char const *src_name, char const *dst_name,
           )
     {
       preserve_metadata = false;
-      if (link (src_name, dst_name))
+      if (link_or_linkat (src_name, dst_name,
+                          src_relative_fd, src_relative_path,
+                          dst_relative_fd, dst_relative_path))
        {
          error (0, errno, _("cannot create link %s"), quote (dst_name));
          goto un_backup;
@@ -1697,7 +2296,7 @@ copy_internal (char const *src_name, char const *dst_name,
         bits were ignored, so it should be the same either way.  */
       if (! copy_reg (src_name, dst_name, x, src_mode & S_IRWXUGO,
                      omitted_permissions, &new_dst, &src_sb))
-       goto un_backup;
+        goto un_backup;
     }
   else if (S_ISFIFO (src_mode))
     {
@@ -1705,9 +2304,9 @@ copy_internal (char const *src_name, char const *dst_name,
         the special mode bits of a fifo on Solaris 10, while mkfifo
         does not.  But fall back on mkfifo, because on some BSD systems,
         mknod always fails when asked to create a FIFO.  */
-      if (mknod (dst_name, src_mode & ~omitted_permissions, 0) != 0)
+      if (mknod_or_mknodat (dst_name, src_mode & ~omitted_permissions, 0, 
dst_relative_fd, dst_relative_path) != 0)
 #if HAVE_MKFIFO
-       if (mkfifo (dst_name, src_mode & ~S_IFIFO & ~omitted_permissions) != 0)
+        if (mkfifo_or_mkfifoat (dst_name, src_mode & ~S_IFIFO & 
~omitted_permissions, dst_relative_fd, dst_relative_path) != 0)
 #endif
          {
            error (0, errno, _("cannot create fifo %s"), quote (dst_name));
@@ -1716,7 +2315,7 @@ copy_internal (char const *src_name, char const *dst_name,
     }
   else if (S_ISBLK (src_mode) || S_ISCHR (src_mode) || S_ISSOCK (src_mode))
     {
-      if (mknod (dst_name, src_mode & ~omitted_permissions, src_sb.st_rdev)
+      if (mknod_or_mknodat (dst_name, src_mode & ~omitted_permissions, 
src_sb.st_rdev, dst_relative_fd, dst_relative_path)
          != 0)
        {
          error (0, errno, _("cannot create special file %s"),
@@ -1726,19 +2325,69 @@ copy_internal (char const *src_name, char const 
*dst_name,
     }
   else if (S_ISLNK (src_mode))
     {
-      char *src_link_val = xreadlink_with_size (src_name, src_sb.st_size);
+      char *src_link_val;
+      int slv_fd = AT_FDCWD;
+      const char *slv_relative_path;
+      if (src_relative_fd > -1)
+        {
+          fchdir (src_relative_fd);
+          src_link_val = xreadlink_with_size (src_relative_path, 
src_sb.st_size);
+          fchdir (currentpath_fd);
+         }
+      else
+         src_link_val = xreadlink_with_size (src_name, src_sb.st_size);
       if (src_link_val == NULL)
        {
          error (0, errno, _("cannot read symbolic link %s"), quote (src_name));
          goto un_backup;
        }
-
-      if (symlink (src_link_val, dst_name) == 0)
-       free (src_link_val);
+#if defined  __USE_ATFILE && defined PATH_MAX
+      if (strlen (src_link_val) >= PATH_MAX)
+        {
+          slv_relative_path = get_dir_fd (src_link_val, &slv_fd);
+          if (slv_relative_path == NULL)
+            {
+              error (0, 0, _("cannot get the directory descriptor for file 
%s"), quote (src_link_val));
+              free (src_link_val);
+              goto un_backup;
+            }
+        }
+      else
+        slv_relative_path = src_link_val;
+#endif
+      if (symlink_relative_fd < 0)
+        {
+          char *direc_name = dir_name (dst_name);
+
+          /* only for symlinkat */
+          symlink_relative_fd = open (direc_name, O_RDONLY | O_BINARY);
+          if (symlink_relative_fd < 0)
+            {
+              error (0, errno, _("cannot get %s's file descriptor "), quote 
(direc_name));
+              free (direc_name);
+              CLOSE_DIR_FD (currentpath_fd, src_relative_fd, -1)
+              return false;
+            }
+          symlink_relative_path = base_name (dst_name);
+          free (direc_name);
+        }
+      if (symlink_or_symlinkat (src_link_val, dst_name,
+                                slv_fd, slv_relative_path,
+                                symlink_relative_fd, symlink_relative_path) == 
0)
+        {
+          CLOSE_DIR_FD (slv_fd, -1, -1)
+          free (src_link_val);
+          if (dst_relative_fd < 0)
+            {
+              close (symlink_relative_fd);
+              free (symlink_relative_path);
+            }
+        }
       else
        {
          int saved_errno = errno;
          bool same_link = false;
+          CLOSE_DIR_FD (slv_fd, -1, -1)
          if (x->update && !new_dst && S_ISLNK (dst_sb.st_mode)
              && dst_sb.st_size == strlen (src_link_val))
            {
@@ -1746,13 +2395,25 @@ copy_internal (char const *src_name, char const 
*dst_name,
                 FIXME: This behavior isn't documented, and seems wrong
                 in some cases, e.g., if the destination symlink has the
                 wrong ownership, permissions, or time stamps.  */
-             char *dest_link_val =
-               xreadlink_with_size (dst_name, dst_sb.st_size);
+              char *dest_link_val;
+              if(dst_relative_fd > -1)
+                {
+                  fchdir (dst_relative_fd);
+                  dest_link_val = xreadlink_with_size (dst_relative_path, 
dst_sb.st_size);
+                  fchdir (currentpath_fd);
+                }
+              else
+                dest_link_val = xreadlink_with_size (dst_name, dst_sb.st_size);
              if (STREQ (dest_link_val, src_link_val))
                same_link = true;
              free (dest_link_val);
            }
          free (src_link_val);
+          if (dst_relative_fd < 0)
+            {
+              close (symlink_relative_fd);
+              free (symlink_relative_path);
+            }
 
          if (! same_link)
            {
@@ -1770,7 +2431,7 @@ copy_internal (char const *src_name, char const *dst_name,
          /* Preserve the owner and group of the just-`copied'
             symbolic link, if possible.  */
 #if HAVE_LCHOWN
-         if (lchown (dst_name, src_sb.st_uid, src_sb.st_gid) != 0
+          if (lchown_or_fchownat (dst_name, src_sb.st_uid, src_sb.st_gid, 
dst_relative_fd, dst_relative_path) != 0
              && ! chown_failure_ok (x))
            {
              error (0, errno, _("failed to preserve ownership for %s"),
@@ -1792,13 +2453,19 @@ copy_internal (char const *src_name, char const 
*dst_name,
     }
 
   if (command_line_arg)
-    record_file (x->dest_info, dst_name, NULL);
+    record_file (x->dest_info, dst_name, NULL, dst_relative_fd, 
dst_relative_path);
 
   if ( ! preserve_metadata)
-    return true;
+    {
+      CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
+      return true;
+    }
 
   if (copied_as_regular)
-    return delayed_ok;
+    {
+      CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
+      return delayed_ok;
+    }
 
   /* POSIX says that `cp -p' must restore the following:
      - permission bits
@@ -1817,22 +2484,43 @@ copy_internal (char const *src_name, char const 
*dst_name,
       struct timespec timespec[2];
       timespec[0] = get_stat_atime (&src_sb);
       timespec[1] = get_stat_mtime (&src_sb);
-
-      if (utimens (dst_name, timespec) != 0)
-       {
-         error (0, errno, _("preserving times for %s"), quote (dst_name));
-         if (x->require_preserve)
-           return false;
-       }
+      int ret_utimens;
+      if (dst_relative_fd > -1)
+        {
+          fchdir (dst_relative_fd);
+          ret_utimens = utimens (dst_relative_path, timespec);
+          fchdir (currentpath_fd);
+        }
+      else
+         ret_utimens = utimens (dst_name, timespec);
+      if (ret_utimens != 0)
+        {
+          error (0, errno, _("preserving times for %s"), quote (dst_name));
+          if (x->require_preserve)
+            {
+              CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
+              return false;
+            }
+        }
     }
 
   /* Avoid calling chown if we know it's not necessary.  */
   if (x->preserve_ownership
       && (new_dst || !SAME_OWNER_AND_GROUP (src_sb, dst_sb)))
     {
-      switch (set_owner (x, dst_name, -1, src_sb.st_uid, src_sb.st_gid))
+      int ret_set_owner;
+      if (dst_relative_fd > -1)
+        {
+          fchdir (dst_relative_fd);
+          ret_set_owner = set_owner (x, dst_relative_path, -1, src_sb.st_uid, 
src_sb.st_gid);
+          fchdir (currentpath_fd);
+        }
+      else
+        ret_set_owner = set_owner (x, dst_name, -1, src_sb.st_uid, 
src_sb.st_gid);
+      switch (ret_set_owner)
        {
        case -1:
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
          return false;
 
        case 0:
@@ -1841,18 +2529,40 @@ copy_internal (char const *src_name, char const 
*dst_name,
        }
     }
 
-  set_author (dst_name, -1, &src_sb);
+  if (dst_relative_fd > -1)
+    {
+      fchdir (dst_relative_fd);
+      set_author (dst_relative_path, -1, &src_sb);
+      fchdir (currentpath_fd);
+    }
+  else
+    set_author (dst_name, -1, &src_sb);
 
   if (x->preserve_mode || x->move_mode)
     {
       if (copy_acl (src_name, -1, dst_name, -1, src_mode) != 0
          && x->require_preserve)
-       return false;
+        {
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
+          return false;
+        }
     }
   else if (x->set_mode)
     {
-      if (set_acl (dst_name, -1, x->mode) != 0)
-       return false;
+      int ret_set_acl;
+      if(dst_relative_fd > -1)
+        {
+          fchdir (dst_relative_fd);
+          ret_set_acl = set_acl (dst_relative_path, -1, x->mode);
+          fchdir (currentpath_fd);
+        }
+      else
+        ret_set_acl = set_acl (dst_name, -1, x->mode);
+      if (ret_set_acl != 0)
+        {
+          CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
+          return false;
+        }
     }
   else
     {
@@ -1868,9 +2578,10 @@ copy_internal (char const *src_name, char const 
*dst_name,
                 the lstat, but deducing the current destination mode
                 is tricky in the presence of implementation-defined
                 rules for special mode bits.  */
-             if (new_dst && lstat (dst_name, &dst_sb) != 0)
+              if (new_dst && lstat_or_fstatat (dst_name, &dst_sb, 
dst_relative_fd, dst_relative_path) != 0)
                {
                  error (0, errno, _("cannot stat %s"), quote (dst_name));
+                  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
                  return false;
                }
              dst_mode = dst_sb.st_mode;
@@ -1881,16 +2592,19 @@ copy_internal (char const *src_name, char const 
*dst_name,
 
       if (restore_dst_mode)
        {
-         if (lchmod (dst_name, dst_mode | omitted_permissions) != 0)
+          if (lchmod_or_fchmodat (dst_name, dst_mode | omitted_permissions, 
dst_relative_fd, dst_relative_path) != 0)
            {
              error (0, errno, _("preserving permissions for %s"),
                     quote (dst_name));
              if (x->require_preserve)
-               return false;
+                {
+                  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, 
currentpath_fd);
+                  return false;
+                }
            }
        }
     }
-
+  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd);
   return delayed_ok;
 
 un_backup:
@@ -1906,7 +2620,7 @@ un_backup:
 
   if (dst_backup)
     {
-      if (rename (dst_backup, dst_name) != 0)
+      if (rename_or_renameat (dst_backup, dst_name, dst_backup_fd, 
dst_relative_path, dst_relative_fd, dst_relative_path) != 0)
        error (0, errno, _("cannot un-backup %s"), quote (dst_name));
       else
        {
@@ -1915,9 +2629,9 @@ un_backup:
                    quote_n (0, dst_backup), quote_n (1, dst_name));
        }
     }
+  CLOSE_DIR_FD (src_relative_fd, dst_relative_fd, currentpath_fd); 
   return false;
 }
-
 static bool
 valid_options (const struct cp_options *co)
 {
@@ -2005,3 +2719,253 @@ cached_umask (void)
     }
   return mask;
 }
+
+/* If the length of filename is longer than PATH_MAX,get a appropriate file 
descriptor
+   and return the path relative to the directory referred to by the file 
descriptor  */
+   
+static char const*
+get_dir_fd (const char *filename, int *dir_fd)
+{
+  size_t len;
+  int i;
+  int current_dir = -1;
+  char dirname[PATH_MAX] = "\0";
+  char const*relative_path = NULL;
+  char const *base = filename;
+  char const *p;
+  char const *q;
+  static char const dot[2] = ".";
+  bool saw_slash;
+
+  len = strlen (base);
+  current_dir = open(".", O_RDONLY);
+
+  relative_path = base;
+  len = strlen (base);
+  while(len >= PATH_MAX)
+    {
+      saw_slash = false;
+      p = base;
+      q = base;
+      for (i = 0; i < PATH_MAX; i++, p++)
+        {
+          if (ISSLASH (*p))
+            saw_slash = true;
+          else if (saw_slash)
+            {
+              q = p;
+              saw_slash = false;
+            }
+        }
+
+      if (ISSLASH (*(p-1)) && q == base)
+        {
+          strncpy (dirname, base, p-base-1);
+          dirname[p-base-1] = '\0';
+
+          while (ISSLASH (*p))
+            p++;
+          if ((*p) == '\0')
+            q = dot;
+          else
+            q = p;
+        }
+      else
+        {
+          strncpy (dirname, base, q-base);
+          dirname[q-base] = '\0';
+        }
+      if (chdir (dirname) == -1)
+        {
+          fchdir (current_dir);
+          CLOSE_DIR_FD (current_dir, -1, -1)
+          return NULL;
+        }
+      relative_path = q;
+      base = q;
+      len = strlen (base);
+    }
+
+  *dir_fd = open(".", O_RDONLY);
+  if (*dir_fd == -1)
+    relative_path = NULL;
+
+  fchdir(current_dir);
+  CLOSE_DIR_FD (current_dir, -1, -1)
+  return relative_path;
+}
+
+int
+XSTAT_or_XSTATAT (const struct cp_options *x, const char *path, struct stat 
*buf, int relative_fd, const char *relative_path)
+{
+  int ret;
+  
+#if !defined  __USE_ATFILE  || !defined PATH_MAX
+  ret = XSTAT (x, path, buf);
+#else  
+  ret = XSTAT_AT (relative_fd, x, relative_path, buf);
+#endif
+  return ret;
+}
+
+int
+unlink_or_unlinkat (const char *pathname, int relative_fd, const char 
*relative_path)
+{
+  int ret;
+  
+#if !defined  __USE_ATFILE  || !defined PATH_MAX
+  ret = unlink(pathname);
+#else  
+  ret = unlinkat (relative_fd, relative_path, 0);
+#endif
+  return ret;
+}
+
+int
+rename_or_renameat (const char *oldname, const char *newname, int old_fd, 
const char *old_relative_path, int new_fd, const char *new_relative_path)
+{
+  int ret;
+
+#if !defined  __USE_ATFILE  || !defined PATH_MAX
+    ret = rename (oldname, newname);  
+#else  
+    ret = renameat (old_fd, old_relative_path, new_fd, new_relative_path);
+#endif
+  return ret;
+}
+
+int
+symlink_or_symlinkat (const char *oldpath, const char *newpath, int old_fd, 
const char *old_relative_path, int new_fd, const char *new_relative_path)
+{
+  int ret;
+  
+#if !defined  __USE_ATFILE || !defined PATH_MAX
+  ret = symlink (oldpath, newpath);
+#else  
+  ret = symlinkat (old_relative_path, new_fd, new_relative_path);
+#endif
+  return ret;
+}
+
+int
+link_or_linkat (const char *oldpath, const char *newpath, int old_fd, const 
char *old_relative_path, int new_fd, const char *new_relative_path)
+{
+  int ret;
+
+#if !defined  __USE_ATFILE || !defined PATH_MAX
+  ret = link (oldpath, newpath); 
+#else
+  ret = linkat (old_fd, old_relative_path, new_fd, new_relative_path, 0);
+#endif
+  return ret;
+}
+
+int
+mkdir_or_mkdirat (const char *pathname, mode_t mode, int relative_fd, const 
char *relative_path)
+{
+  int ret;
+  
+#if !defined  __USE_ATFILE || !defined PATH_MAX
+  ret = mkdir (pathname,mode);
+#else  
+  ret = mkdirat (relative_fd, relative_path, mode);
+#endif
+  return ret;
+}
+
+int
+lstat_or_fstatat (const char *path, struct stat *buf, int relative_fd, const 
char *relative_path)
+{
+  int ret;
+  
+#if !defined  __USE_ATFILE || !defined PATH_MAX
+  ret = lstat (path, buf);
+#else  
+  ret = fstatat (relative_fd, relative_path, buf, AT_SYMLINK_NOFOLLOW);
+#endif
+  return ret;
+}
+
+int
+stat_or_fstatat (const char *path, struct stat *buf, int relative_fd, const 
char *relative_path)
+{
+  int ret;
+  
+#if !defined  __USE_ATFILE || !defined PATH_MAX
+  ret = stat (path,buf);
+#else  
+  ret = fstatat (relative_fd, relative_path, buf, AT_SYMLINK_NOFOLLOW);
+#endif
+  return ret;
+}
+
+int
+lchmod_or_fchmodat (const char *pathname, mode_t mode, int relative_fd, const 
char *relative_path)
+{
+  int ret;
+  
+#if !defined  __USE_ATFILE || !defined PATH_MAX
+  ret = lchmod (pathname, mode);
+#else  
+  ret = fchmodat (relative_fd, relative_path, mode, AT_SYMLINK_NOFOLLOW);
+#endif
+  return ret;
+}
+
+int
+mknod_or_mknodat (const char *pathname, mode_t mode, dev_t dev, int 
relative_fd, const char *relative_path)
+{
+  int ret;
+  
+#if !defined  __USE_ATFILE || !defined PATH_MAX
+  ret = mknod (pathname, mode, dev);
+#else  
+  ret = mknodat (relative_fd, relative_path, mode,dev);
+#endif
+  return ret;
+}
+
+int
+mkfifo_or_mkfifoat (const char *pathname, mode_t mode, int relative_fd, const 
char *relative_path)
+{
+  int ret;
+  
+#if !defined  __USE_ATFILE || !defined PATH_MAX
+  ret = mkfifo (pathname, mode);
+#else  
+  ret = mkfifoat (relative_fd, relative_path, mode);
+#endif
+  return ret;
+}
+
+int
+lchown_or_fchownat (const char *pathname, uid_t owner, gid_t group, int 
relative_fd, const char *relative_path)
+{
+  int ret;
+  
+#if !defined  __USE_ATFILE || !defined PATH_MAX
+  ret = lchown (pathname, owner, group);
+#else  
+  ret = fchownat (relative_fd, relative_path, owner,group, 
AT_SYMLINK_NOFOLLOW);
+#endif
+  return ret;
+}
+
+int
+chmod_or_fchmodat (const char *name, int desc, mode_t mode, int relative_fd, 
const char *relative_path)
+{
+  int ret;
+  
+  if (HAVE_FCHMOD && desc != -1)
+    ret = fchmod (desc, mode);
+  else
+    {
+#if !defined  __USE_ATFILE || !defined PATH_MAX
+      ret = chmod (name, mode);
+#else
+      ret = fchmodat (relative_fd, relative_path, mode,0);
+#endif
+    }
+  return ret;
+}
+
diff --git a/src/copy.h b/src/copy.h
index c815baf..6f04b51 100644
--- a/src/copy.h
+++ b/src/copy.h
@@ -195,6 +195,11 @@ struct cp_options
    ? lstat (Src_name, Src_sb) \
    : stat (Src_name, Src_sb))
 
+# define XSTAT_AT(fd, X, Src_name, Src_sb) \
+  ((X)->dereference == DEREF_NEVER \
+   ? fstatat (fd,Src_name, Src_sb,AT_SYMLINK_NOFOLLOW) \
+   : fstatat (fd,Src_name, Src_sb,0))
+
 /* Arrange to make rename calls go through the wrapper function
    on systems with a rename function that fails for a source file name
    specified with a trailing slash.  */
@@ -214,5 +219,18 @@ void src_info_init (struct cp_options *);
 bool chown_privileges (void);
 bool chown_failure_ok (struct cp_options const *);
 mode_t cached_umask (void);
-
+int XSTAT_or_XSTATAT(const struct cp_options *x,const char *path, struct stat 
*buf,int relative_fd,const char *relative_path);
+int stat_or_fstatat(const char *path, struct stat *buf, int relative_fd, const 
char *relative_path);
+int unlink_or_unlinkat(const char *pathname, int relative_fd,const char 
*relative_path);
+int rename_or_renameat(const char *oldname, const char *newname,int old_fd, 
const char *old_relative_path,int new_fd, const char *new_relative_path);
+int symlink_or_symlinkat(const char *oldpath, const char *newpath, int old_fd, 
const char *old_relative_path,int new_fd, const char *new_relative_path);
+int link_or_linkat(const char *oldpath, const char *newpath,int old_fd, const 
char *old_relative_path,int new_fd, const char *new_relative_path);
+int mkdir_or_mkdirat(const char *pathname,mode_t mode,int relative_fd,const 
char *relative_path);
+int lstat_or_fstatat(const char *path, struct stat *buf, int relative_fd, 
const char *relative_path);
+int lchmod_or_fchmodat(const char *pathname,mode_t mode, int relative_fd, 
const char *relative_path);
+int chmod_or_fchmodat (const char *name, int desc, mode_t mode,int 
relative_fd, const char *relative_path);
+int mknod_or_mknodat(const char *pathname, mode_t mode, dev_t dev, int 
relative_fd, const char *relative_path);
+int mkfifo_or_mkfifoat(const char *pathname, mode_t mode,int relative_fd, 
const char *relative_path);
+int lchown_or_fchownat(const char *pathname,uid_t owner,gid_t group,int 
relative_fd, const char *relative_path);
+int open_or_openat(const char *pathname, int flags, mode_t mode,int 
dirfd,const char *relative_path);
 #endif

reply via email to

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