OSDN Git Service

* syscalls.cc (rename): Check oldpath and newpath for trailing dir
authorcorinna <corinna>
Fri, 10 Aug 2007 11:16:26 +0000 (11:16 +0000)
committercorinna <corinna>
Fri, 10 Aug 2007 11:16:26 +0000 (11:16 +0000)
separators, require them to be existing directories if so.  Check
for a request to change only the case of the filename.  Check paths
for case insensitve equality only once.  Handle renaming a directory
to another, existing directory by unlinking the destination directory
first.  If newpath points to an existing file with R/O attribute set,
try to unset R/O attribute first.  Augment hardlink test by not
checking directories.  If renaming fails with STATUS_ACCESS_DENIED,
try to unlink existing destination filename and try renaming again.
Drop useless test for non-empty directory.  Always close fh at the
end of the function.

winsup/cygwin/ChangeLog
winsup/cygwin/syscalls.cc

index 0e08931..7b158b1 100644 (file)
@@ -1,3 +1,17 @@
+2007-08-10  Corinna Vinschen  <corinna@vinschen.de>
+
+       * syscalls.cc (rename): Check oldpath and newpath for trailing dir
+       separators, require them to be existing directories if so.  Check
+       for a request to change only the case of the filename.  Check paths
+       for case insensitve equality only once.  Handle renaming a directory
+       to another, existing directory by unlinking the destination directory
+       first.  If newpath points to an existing file with R/O attribute set,
+       try to unset R/O attribute first.  Augment hardlink test by not
+       checking directories.  If renaming fails with STATUS_ACCESS_DENIED,
+       try to unlink existing destination filename and try renaming again.
+       Drop useless test for non-empty directory.  Always close fh at the
+       end of the function.
+
 2007-08-09  Ernie Coskrey  <Ernie.Coskrey@steeleye.com>
 
        * gendef (sigbe): Reset "incyg" while the stack lock is active to avoid
index 3db5dae..89cff28 100644 (file)
@@ -1350,11 +1350,14 @@ extern "C" int
 rename (const char *oldpath, const char *newpath)
 {
   int res = -1;
+  char *oldbuf, *newbuf;
   path_conv oldpc, newpc, new2pc, *dstpc, *removepc = NULL;
+  bool old_dir_requested = false, new_dir_requested = false;
   bool old_explicit_suffix = false, new_explicit_suffix = false;
   size_t olen, nlen;
+  bool equal_path;
   NTSTATUS status;
-  HANDLE fh, nfh;
+  HANDLE fh = NULL, nfh;
   OBJECT_ATTRIBUTES attr;
   IO_STATUS_BLOCK io;
   ULONG size;
@@ -1372,6 +1375,18 @@ rename (const char *oldpath, const char *newpath)
       goto out;
     }
 
+  /* A trailing slash requires that the pathname points to an existing
+     directory.  If it's not, it's a ENOTDIR condition.  The same goes
+     for newpath a bit further down this function. */
+  olen = strlen (oldpath);
+  if (isdirsep (oldpath[olen - 1]))
+    {
+      stpcpy (oldbuf = (char *) alloca (olen + 1), oldpath);
+      while (olen > 0 && isdirsep (oldbuf[olen - 1]))
+        oldbuf[--olen] = '\0';
+      oldpath = oldbuf;
+      old_dir_requested = true;
+    }
   oldpc.check (oldpath, PC_SYM_NOFOLLOW, stat_suffixes);
   if (oldpc.error)
     {
@@ -1388,12 +1403,25 @@ rename (const char *oldpath, const char *newpath)
       set_errno (EROFS);
       goto out;
     }
-  olen = strlen (oldpath);
+  if (old_dir_requested && !oldpc.isdir ())
+    {
+      set_errno (ENOTDIR);
+      goto out;
+    }
   if (oldpc.known_suffix
       && (strcasematch (oldpath + olen - 4, ".lnk")
          || strcasematch (oldpath + olen - 4, ".exe")))
     old_explicit_suffix = true;
 
+  nlen = strlen (newpath);
+  if (isdirsep (newpath[nlen - 1]))
+    {
+      stpcpy (newbuf = (char *) alloca (nlen + 1), newpath);
+      while (nlen > 0 && isdirsep (newbuf[nlen - 1]))
+        newbuf[--nlen] = '\0';
+      newpath = newbuf;
+      new_dir_requested = true;
+    }
   newpc.check (newpath, PC_SYM_NOFOLLOW, stat_suffixes);
   if (newpc.error)
     {
@@ -1405,13 +1433,32 @@ rename (const char *oldpath, const char *newpath)
       set_errno (EROFS);
       goto out;
     }
-  nlen = strlen (newpath);
+  if (new_dir_requested && !newpc.isdir ())
+    {
+      set_errno (ENOTDIR);
+      goto out;
+    }
   if (newpc.known_suffix
       && (strcasematch (newpath + nlen - 4, ".lnk")
          || strcasematch (newpath + nlen - 4, ".exe")))
     new_explicit_suffix = true;
 
-  if (oldpc.isdir ())
+  /* This test is necessary in almost every case, so just do it once here. */
+  equal_path = RtlEqualUnicodeString (oldpc.get_nt_native_path (),
+                                     newpc.get_nt_native_path (),
+                                     TRUE);
+
+  /* First check if oldpath and newpath only differ by case.  If so, it's
+     just a request to change the case of the filename.  By simply setting
+     the file attributes to INVALID_FILE_ATTRIBUTES (which translates to
+     "file doesn't exist"), all later tests are skipped. */
+  if (newpc.exists ()
+      && equal_path
+      && !RtlEqualUnicodeString (oldpc.get_nt_native_path (),
+                                newpc.get_nt_native_path (),
+                                FALSE))
+    newpc.file_attributes (INVALID_FILE_ATTRIBUTES);
+  else if (oldpc.isdir ())
     {
       if (newpc.exists () && !newpc.isdir ())
        {
@@ -1433,10 +1480,7 @@ rename (const char *oldpath, const char *newpath)
     }
   else if (!newpc.exists ())
     {
-      if (RtlEqualUnicodeString (oldpc.get_nt_native_path (),
-                                newpc.get_nt_native_path (),
-                                TRUE)
-         && old_explicit_suffix != new_explicit_suffix)
+      if (equal_path && old_explicit_suffix != new_explicit_suffix)
        {
          newpc.check (newpath, PC_SYM_NOFOLLOW);
          if (RtlEqualUnicodeString (oldpc.get_nt_native_path (),
@@ -1464,10 +1508,7 @@ rename (const char *oldpath, const char *newpath)
     }
   else
     {
-      if (RtlEqualUnicodeString (oldpc.get_nt_native_path (),
-                                newpc.get_nt_native_path (),
-                                TRUE)
-         && old_explicit_suffix != new_explicit_suffix)
+      if (equal_path && old_explicit_suffix != new_explicit_suffix)
        {
          newpc.check (newpath, PC_SYM_NOFOLLOW);
          if (RtlEqualUnicodeString (oldpc.get_nt_native_path (),
@@ -1524,6 +1565,52 @@ rename (const char *oldpath, const char *newpath)
       __seterrno_from_nt_status (status);
       goto out;
     }
+
+  /* Renaming a dir to another, existing dir fails always, even if
+     ReplaceIfExists is set to TRUE and the existing dir is empty.  So
+     we have to remove the destination dir first.  This also covers the
+     case that the destination directory is not empty.  In that case,
+     unlink_nt returns with STATUS_DIRECTORY_NOT_EMPTY. */
+  if (dstpc->isdir ())
+    {
+      status = unlink_nt (*dstpc);
+      if (!NT_SUCCESS (status))
+       {
+         __seterrno_from_nt_status (status);
+         goto out;
+       }
+    }
+  /* You can't copy a file if the destination exists and has the R/O
+     attribute set.  Remove the R/O attribute first. */
+  else if (dstpc->has_attribute (FILE_ATTRIBUTE_READONLY))
+    {
+      status = NtOpenFile (&nfh, FILE_WRITE_ATTRIBUTES,
+                          dstpc->get_object_attr (attr, sec_none_nih),
+                          &io, FILE_SHARE_VALID_FLAGS,
+                          FILE_OPEN_FOR_BACKUP_INTENT
+                          | (dstpc->is_rep_symlink ()
+                             ? FILE_OPEN_REPARSE_POINT : 0));
+      if (!NT_SUCCESS (status))
+        {
+         __seterrno_from_nt_status (status);
+         goto out;
+       }
+      FILE_BASIC_INFORMATION fbi;
+      fbi.CreationTime.QuadPart = fbi.LastAccessTime.QuadPart =
+      fbi.LastWriteTime.QuadPart = fbi.ChangeTime.QuadPart = 0LL;
+      fbi.FileAttributes = (dstpc->file_attributes ()
+                           & ~FILE_ATTRIBUTE_READONLY)
+                          ?: FILE_ATTRIBUTE_NORMAL;
+      status = NtSetInformationFile (nfh, &io,  &fbi, sizeof fbi,
+                                    FileBasicInformation);
+      NtClose (nfh);
+      if (!NT_SUCCESS (status))
+        {
+         __seterrno_from_nt_status (status);
+         goto out;
+       }
+    }
+
   /* SUSv3: If the old argument and the new argument resolve to the same
      existing file, rename() shall return successfully and perform no
      other action.
@@ -1532,6 +1619,7 @@ rename (const char *oldpath, const char *newpath)
      file ids.  If so, it tests for identical volume serial numbers,  If so,
      oldpath and newpath refer to the same file. */
   if ((removepc || dstpc->exists ())
+      && !oldpc.isdir ()
       && NT_SUCCESS (NtQueryInformationFile (fh, &io, &ofsi, sizeof ofsi,
                                             FileStandardInformation))
       && ofsi.NumberOfLinks > 1
@@ -1562,7 +1650,6 @@ rename (const char *oldpath, const char *newpath)
        {
          debug_printf ("%s and %s are the same file", oldpath, newpath);
          NtClose (nfh);
-         NtClose (fh);
          res = 0;
          goto out;
        }
@@ -1577,7 +1664,16 @@ rename (const char *oldpath, const char *newpath)
   memcpy (&pfri->FileName,  dstpc->get_nt_native_path ()->Buffer,
          pfri->FileNameLength);
   status = NtSetInformationFile (fh, &io, pfri, size, FileRenameInformation);
-  NtClose (fh);
+  /* This happens if the access rights don't allow deleting the destination.
+     Even if the handle to the original file is opened with BACKUP
+     and/or RECOVERY, these flags don't apply to the destination of the
+     rename operation.  So, a privileged user can't rename a file to an
+     existing file, if the permissions of the existing file aren't right.
+     Like directories, we have to handle this separately by removing the
+     destination before renaming. */
+  if (status == STATUS_ACCESS_DENIED && dstpc->exists () && !dstpc->isdir ()
+      && NT_SUCCESS (status = unlink_nt (*dstpc)))
+    status = NtSetInformationFile (fh, &io, pfri, size, FileRenameInformation);
   if (NT_SUCCESS (status))
     {
       if (removepc)
@@ -1585,25 +1681,11 @@ rename (const char *oldpath, const char *newpath)
       res = 0;
     }
   else
-    {
-      /* Check in case of STATUS_ACCESS_DENIED and pc.isdir(),
-         whether we tried to rename to an existing non-empty dir.
-        In this case we have to set errno to EEXIST. */
-      if (status == STATUS_ACCESS_DENIED && dstpc->isdir ()
-         && NT_SUCCESS (NtOpenFile (&nfh, FILE_LIST_DIRECTORY | SYNCHRONIZE,
-                                   dstpc->get_object_attr (attr, sec_none_nih),
-                                   &io, FILE_SHARE_VALID_FLAGS,
-                                   FILE_OPEN_FOR_BACKUP_INTENT
-                                   | FILE_SYNCHRONOUS_IO_NONALERT)))
-       {
-         if (check_dir_not_empty (nfh) == STATUS_DIRECTORY_NOT_EMPTY)
-           status = STATUS_DIRECTORY_NOT_EMPTY;
-         NtClose (nfh);
-       }
-      __seterrno_from_nt_status (status);
-    }
+    __seterrno_from_nt_status (status);
 
 out:
+  if (fh)
+    NtClose (fh);
   syscall_printf ("%d = rename (%s, %s)", res, oldpath, newpath);
   return res;
 }