Hi Corinna,
On 8/19/2017 5:57 AM, Corinna Vinschen wrote:
Hi Ken,
On Aug 18 18:24, Ken Brown wrote:
Thanks for the improvements! A revised patch is attached. As you'll see, I
still found a few places where I thought I needed to explicitly set the
errno to EEXIST. Let me know if any of these could be avoided.
No, you're right. Just one thing:
@@ -2410,6 +2433,11 @@ rename (const char *oldpath, const char *newpath)
unlink_nt returns with STATUS_DIRECTORY_NOT_EMPTY. */
if (dstpc->isdir ())
{
+ if (noreplace)
+ {
+ set_errno (EEXIST);
+ __leave;
+ }
status = unlink_nt (*dstpc);
if (!NT_SUCCESS (status))
{
@@ -2423,6 +2451,11 @@ rename (const char *oldpath, const char *newpath)
due to a mangled suffix. */
else if (!removepc && dstpc->has_attribute (FILE_ATTRIBUTE_READONLY))
{
+ if (noreplace)
+ {
+ set_errno (EEXIST);
+ __leave;
+ }
status = NtOpenFile (&nfh, FILE_WRITE_ATTRIBUTES,
dstpc->get_object_attr (attr, sec_none_nih),
&io, FILE_SHARE_VALID_FLAGS,
Both of the above cases are just border cases of one and the same
problem, the destination file already exists.
In retrospect your original patch was more concise:
+ /* Should we replace an existing file? */
+ if ((removepc || dstpc->exists ()) && (flags & RENAME_NOREPLACE))
+ {
+ set_errno (EEXIST);
+ __leave;
+ }
The atomicity considerations are not affected by this test anyway, but
it would avoid starting and stopping a transaction on NTFS for no good
reason.
Maybe it's better to revert to this test and drop the other two again?
@@ -2491,11 +2524,15 @@ rename (const char *oldpath, const char *newpath)
__leave;
}
pfri = (PFILE_RENAME_INFORMATION) tp.w_get ();
- pfri->ReplaceIfExists = TRUE;
+ pfri->ReplaceIfExists = !noreplace;
pfri->RootDirectory = NULL;
pfri->FileNameLength = dstpc->get_nt_native_path ()->Length;
memcpy (&pfri->FileName, dstpc->get_nt_native_path ()->Buffer,
pfri->FileNameLength);
+ /* If dstpc points to an existing file and RENAME_NOREPLACE has
+ been specified, then we should get NT error
+ STATUS_OBJECT_NAME_COLLISION ==> Win32 error
+ ERROR_ALREADY_EXISTS ==> Cygwin error EEXIST. */
status = NtSetInformationFile (fh, &io, pfri,
sizeof *pfri + pfri->FileNameLength,
FileRenameInformation);
@@ -2509,6 +2546,11 @@ rename (const char *oldpath, const char *newpath)
if (status == STATUS_ACCESS_DENIED && dstpc->exists ()
&& !dstpc->isdir ())
{
+ if (noremove)
+ {
+ set_errno (EEXIST);
+ __leave;
+ }
Oh, right, that's a good catch.
The patch is ok as is, just let me know what you think of the above
minor tweak (and send the revised patch if you agree).
Yes, I agree. But can't I also drop the third test (where you said
"good catch") for the same reason? I've done that in the attached. If
I'm wrong and I still need that third test, let me know and I'll put it
back.
Thanks.
Ken
From bbff16b727172a459a9a052b0b948641c82d80eb Mon Sep 17 00:00:00 2001
From: Ken Brown <kbr...@cornell.edu>
Date: Thu, 17 Aug 2017 09:12:15 -0400
Subject: [PATCH] cygwin: Implement renameat2
Define the RENAME_NOREPLACE flag in <cygwin/fs.h> as defined on Linux
in <linux/fs.h>. The other RENAME_* flags defined on Linux are not
supported.
---
newlib/libc/include/stdio.h | 3 +++
winsup/cygwin/common.din | 1 +
winsup/cygwin/include/cygwin/fs.h | 6 +++++
winsup/cygwin/include/cygwin/version.h | 3 ++-
winsup/cygwin/syscalls.cc | 48 +++++++++++++++++++++++++++++-----
5 files changed, 54 insertions(+), 7 deletions(-)
diff --git a/newlib/libc/include/stdio.h b/newlib/libc/include/stdio.h
index 5d8cb1092..331a1cf07 100644
--- a/newlib/libc/include/stdio.h
+++ b/newlib/libc/include/stdio.h
@@ -384,6 +384,9 @@ int _EXFUN(vdprintf, (int, const char *__restrict, __VALIST)
#endif
#if __ATFILE_VISIBLE
int _EXFUN(renameat, (int, const char *, int, const char *));
+# ifdef __CYGWIN__
+int _EXFUN(renameat2, (int, const char *, int, const char *, unsigned int));
+# endif
#endif
/*
diff --git a/winsup/cygwin/common.din b/winsup/cygwin/common.din
index 8da432b8a..ca6ff3cf9 100644
--- a/winsup/cygwin/common.din
+++ b/winsup/cygwin/common.din
@@ -1168,6 +1168,7 @@ remquof NOSIGFE
remquol NOSIGFE
rename SIGFE
renameat SIGFE
+renameat2 SIGFE
res_close = __res_close SIGFE
res_init = __res_init SIGFE
res_mkquery = __res_mkquery SIGFE
diff --git a/winsup/cygwin/include/cygwin/fs.h
b/winsup/cygwin/include/cygwin/fs.h
index f606ffc39..48b0cca45 100644
--- a/winsup/cygwin/include/cygwin/fs.h
+++ b/winsup/cygwin/include/cygwin/fs.h
@@ -19,4 +19,10 @@ details. */
#define BLKPBSZGET 0x0000127b
#define BLKGETSIZE64 0x00041268
+/* Flags for renameat2, from /usr/include/linux/fs.h. For now we
+ support only RENAME_NOREPLACE. */
+#define RENAME_NOREPLACE (1 << 0)
+/* #define RENAME_EXCHANGE (1 << 1) */
+/* #define RENAME_WHITEOUT (1 << 2) */
+
#endif
diff --git a/winsup/cygwin/include/cygwin/version.h
b/winsup/cygwin/include/cygwin/version.h
index efd4ac017..7686a6865 100644
--- a/winsup/cygwin/include/cygwin/version.h
+++ b/winsup/cygwin/include/cygwin/version.h
@@ -481,12 +481,13 @@ details. */
314: Export explicit_bzero.
315: Export pthread_mutex_timedlock.
316: Export pthread_rwlock_timedrdlock, pthread_rwlock_timedwrlock.
+ 317: Export renameat2.
Note that we forgot to bump the api for ualarm, strtoll, strtoull,
sigaltstack, sethostname. */
#define CYGWIN_VERSION_API_MAJOR 0
-#define CYGWIN_VERSION_API_MINOR 316
+#define CYGWIN_VERSION_API_MINOR 317
/* There is also a compatibity version number associated with the shared memory
regions. It is incremented when incompatible changes are made to the shared
diff --git a/winsup/cygwin/syscalls.cc b/winsup/cygwin/syscalls.cc
index 885931632..61872fe58 100644
--- a/winsup/cygwin/syscalls.cc
+++ b/winsup/cygwin/syscalls.cc
@@ -60,6 +60,7 @@ details. */
#include "tls_pbuf.h"
#include "sync.h"
#include "child_info.h"
+#include <cygwin/fs.h> /* needed for RENAME_NOREPLACE */
#undef _close
#undef _lseek
@@ -2048,14 +2049,19 @@ nt_path_has_executable_suffix (PUNICODE_STRING upath)
return false;
}
-extern "C" int
-rename (const char *oldpath, const char *newpath)
+/* If newpath names an existing file and the RENAME_NOREPLACE flag is
+ specified, fail with EEXIST. Exception: Don't fail if the purpose
+ of the rename is just to change the case of oldpath on a
+ case-insensitive file system. */
+static int
+rename2 (const char *oldpath, const char *newpath, unsigned int flags)
{
tmp_pathbuf tp;
int res = -1;
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;
+ bool noreplace = flags & RENAME_NOREPLACE;
size_t olen, nlen;
bool equal_path;
NTSTATUS status = STATUS_SUCCESS;
@@ -2068,6 +2074,12 @@ rename (const char *oldpath, const char *newpath)
__try
{
+ if (flags & ~RENAME_NOREPLACE)
+ /* RENAME_NOREPLACE is the only flag currently supported. */
+ {
+ set_errno (EINVAL);
+ __leave;
+ }
if (!*oldpath || !*newpath)
{
/* Reject rename("","x"), rename("x",""). */
@@ -2337,6 +2349,13 @@ rename (const char *oldpath, const char *newpath)
__leave;
}
+ /* Should we replace an existing file? */
+ if ((removepc || dstpc->exists ()) && noreplace)
+ {
+ set_errno (EEXIST);
+ __leave;
+ }
+
/* Opening the file must be part of the transaction. It's not sufficient
to call only NtSetInformationFile under the transaction. Therefore we
have to start the transaction here, if necessary. */
@@ -2491,11 +2510,15 @@ rename (const char *oldpath, const char *newpath)
__leave;
}
pfri = (PFILE_RENAME_INFORMATION) tp.w_get ();
- pfri->ReplaceIfExists = TRUE;
+ pfri->ReplaceIfExists = !noreplace;
pfri->RootDirectory = NULL;
pfri->FileNameLength = dstpc->get_nt_native_path ()->Length;
memcpy (&pfri->FileName, dstpc->get_nt_native_path ()->Buffer,
pfri->FileNameLength);
+ /* If dstpc points to an existing file and RENAME_NOREPLACE has
+ been specified, then we should get NT error
+ STATUS_OBJECT_NAME_COLLISION ==> Win32 error
+ ERROR_ALREADY_EXISTS ==> Cygwin error EEXIST. */
status = NtSetInformationFile (fh, &io, pfri,
sizeof *pfri + pfri->FileNameLength,
FileRenameInformation);
@@ -2578,6 +2601,12 @@ rename (const char *oldpath, const char *newpath)
return res;
}
+extern "C" int
+rename (const char *oldpath, const char *newpath)
+{
+ return rename2 (oldpath, newpath, 0);
+}
+
extern "C" int
system (const char *cmdstring)
{
@@ -4719,8 +4748,8 @@ readlinkat (int dirfd, const char *__restrict pathname,
char *__restrict buf,
}
extern "C" int
-renameat (int olddirfd, const char *oldpathname,
- int newdirfd, const char *newpathname)
+renameat2 (int olddirfd, const char *oldpathname,
+ int newdirfd, const char *newpathname, unsigned int flags)
{
tmp_pathbuf tp;
__try
@@ -4731,13 +4760,20 @@ renameat (int olddirfd, const char *oldpathname,
char *newpath = tp.c_get ();
if (gen_full_path_at (newpath, newdirfd, newpathname))
__leave;
- return rename (oldpath, newpath);
+ return rename2 (oldpath, newpath, flags);
}
__except (EFAULT) {}
__endtry
return -1;
}
+extern "C" int
+renameat (int olddirfd, const char *oldpathname,
+ int newdirfd, const char *newpathname)
+{
+ return renameat2 (olddirfd, oldpathname, newdirfd, newpathname, 0);
+}
+
extern "C" int
scandirat (int dirfd, const char *pathname, struct dirent ***namelist,
int (*select) (const struct dirent *),
--
2.14.1