mingw: ensure temporary file handles are not inherited by child processes
authorBen Wijen <ben@wijen.net>
Mon, 22 Aug 2016 12:47:55 +0000 (14:47 +0200)
committerJunio C Hamano <gitster@pobox.com>
Tue, 23 Aug 2016 16:09:55 +0000 (09:09 -0700)
When the index is locked and child processes inherit the handle to
said lock and the parent process wants to remove the lock before the
child process exits, on Windows there is a problem: it won't work
because files cannot be deleted if a process holds a handle on them.
The symptom:

Rename from 'xxx/.git/index.lock' to 'xxx/.git/index' failed.
Should I try again? (y/n)

Spawning child processes with bInheritHandles==FALSE would not work
because no file handles would be inherited, not even the hStdXxx
handles in STARTUPINFO (stdin/stdout/stderr).

Opening every file with O_NOINHERIT does not work, either, as e.g.
git-upload-pack expects inherited file handles.

This leaves us with the only way out: creating temp files with the
O_NOINHERIT flag. This flag is Windows-specific, however. For our
purposes, it is equivalent to O_CLOEXEC (which does not exist on
Windows), so let's just open temporary files with the O_CLOEXEC flag and
map that flag to O_NOINHERIT on Windows.

As Eric Wong pointed out, we need to be careful to handle the case where
the Linux headers used to compile Git support O_CLOEXEC but the Linux
kernel used to run Git does not: it returns an EINVAL.

This fixes the test that we just introduced to demonstrate the problem.

Signed-off-by: Ben Wijen <ben@wijen.net>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
compat/mingw.h
git-compat-util.h
lockfile.h
t/t6026-merge-attr.sh
tempfile.c
tempfile.h
index ef22cbb05d140a210bd348ea1234fc60cdf09da8..6090e83947f82312463e1fabba6b93a4d6b52f16 100644 (file)
@@ -67,6 +67,10 @@ typedef int pid_t;
 #define F_SETFD 2
 #define FD_CLOEXEC 0x1
 
+#if !defined O_CLOEXEC && defined O_NOINHERIT
+#define O_CLOEXEC      O_NOINHERIT
+#endif
+
 #ifndef EAFNOSUPPORT
 #define EAFNOSUPPORT WSAEAFNOSUPPORT
 #endif
index 49d4029b8dddcb06dc6bea3d5f47c020785e3ddf..f1f98b50d7e3453c66b492e7763f3640198f803f 100644 (file)
@@ -650,6 +650,10 @@ void *gitmemmem(const void *haystack, size_t haystacklen,
 #define getpagesize() sysconf(_SC_PAGESIZE)
 #endif
 
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+
 #ifdef FREAD_READS_DIRECTORIES
 #ifdef fopen
 #undef fopen
index 3d301937b0a7e84ccfb03798be1be4047f0654cd..d26ad27b2b2df207872cb20ce9cc0299ee0aff8e 100644 (file)
  *   * calling `fdopen_lock_file()` to get a `FILE` pointer for the
  *     open file and writing to the file using stdio.
  *
+ *   Note that the file descriptor returned by hold_lock_file_for_update()
+ *   is marked O_CLOEXEC, so the new contents must be written by the
+ *   current process, not a spawned one.
+ *
  * When finished writing, the caller can:
  *
  * * Close the file descriptor and rename the lockfile to its final
index 3d28c786878b385a09c72ed23836a5e6e1fece78..dd8f88d18782f15e35aa3105668d9a713ba9fe3b 100755 (executable)
@@ -181,7 +181,7 @@ test_expect_success 'up-to-date merge without common ancestor' '
        )
 '
 
-test_expect_success !MINGW 'custom merge does not lock index' '
+test_expect_success 'custom merge does not lock index' '
        git reset --hard anchor &&
        write_script sleep-one-second.sh <<-\EOF &&
                sleep 1 &
index 0af7ebf016745c4a114a12d207381342b29ccf4f..2990c92424832d288d5bbab2dfe79b5db361e0b0 100644 (file)
@@ -120,7 +120,12 @@ int create_tempfile(struct tempfile *tempfile, const char *path)
        prepare_tempfile_object(tempfile);
 
        strbuf_add_absolute_path(&tempfile->filename, path);
-       tempfile->fd = open(tempfile->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666);
+       tempfile->fd = open(tempfile->filename.buf,
+                           O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0666);
+       if (O_CLOEXEC && tempfile->fd < 0 && errno == EINVAL)
+               /* Try again w/o O_CLOEXEC: the kernel might not support it */
+               tempfile->fd = open(tempfile->filename.buf,
+                                   O_RDWR | O_CREAT | O_EXCL, 0666);
        if (tempfile->fd < 0) {
                strbuf_reset(&tempfile->filename);
                return -1;
index 4219fe41bd3e2ad16f0b1caf55eedca0b2d9986e..2f0038decd5b6d00b55fa03ec8988a3810d1784f 100644 (file)
  *   * calling `fdopen_tempfile()` to get a `FILE` pointer for the
  *     open file and writing to the file using stdio.
  *
+ *   Note that the file descriptor returned by create_tempfile()
+ *   is marked O_CLOEXEC, so the new contents must be written by
+ *   the current process, not any spawned one.
+ *
  * When finished writing, the caller can:
  *
  * * Close the file descriptor and remove the temporary file by