Skip to content

Commit fd23744

Browse files
dschoGit for Windows Build Agent
authored and
Git for Windows Build Agent
committed
Merge branch 'inherit-only-stdhandles'
When spawning child processes, we do want them to inherit the standard handles so that we can talk to them. We do *not* want them to inherit any other handle, as that would hold a lock to the respective files (preventing them from being renamed, modified or deleted), and the child process would not know how to access that handle anyway. Happily, there is an API to make that happen. It is supported in Windows Vista and later, which is exactly what we promise to support in Git for Windows for the time being. This also means that we lift, at long last, the target Windows version from Windows XP to Windows Vista. Signed-off-by: Johannes Schindelin <[email protected]>
2 parents 1a1dfbc + d4610e3 commit fd23744

File tree

5 files changed

+194
-12
lines changed

5 files changed

+194
-12
lines changed

Documentation/config/core.txt

+6
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,12 @@ core.unsetenvvars::
559559
Defaults to `PERL5LIB` to account for the fact that Git for
560560
Windows insists on using its own Perl interpreter.
561561

562+
core.restrictinheritedhandles::
563+
Windows-only: override whether spawned processes inherit only standard
564+
file handles (`stdin`, `stdout` and `stderr`) or all handles. Can be
565+
`auto`, `true` or `false`. Defaults to `auto`, which means `true` on
566+
Windows 7 and later, and `false` on older Windows versions.
567+
562568
core.createObject::
563569
You can set this to 'link', in which case a hardlink followed by
564570
a delete of the source are used to make sure that object creation

compat/mingw.c

+129-11
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ enum hide_dotfiles_type {
225225
HIDE_DOTFILES_DOTGITONLY
226226
};
227227

228+
static int core_restrict_inherited_handles = -1;
228229
static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
229230
static char *unset_environment_variables;
230231

@@ -244,6 +245,15 @@ int mingw_core_config(const char *var, const char *value, void *cb)
244245
return 0;
245246
}
246247

248+
if (!strcmp(var, "core.restrictinheritedhandles")) {
249+
if (value && !strcasecmp(value, "auto"))
250+
core_restrict_inherited_handles = -1;
251+
else
252+
core_restrict_inherited_handles =
253+
git_config_bool(var, value);
254+
return 0;
255+
}
256+
247257
return 0;
248258
}
249259

@@ -1430,8 +1440,13 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
14301440
const char *dir,
14311441
int prepend_cmd, int fhin, int fhout, int fherr)
14321442
{
1433-
STARTUPINFOW si;
1443+
static int restrict_handle_inheritance = -1;
1444+
STARTUPINFOEXW si;
14341445
PROCESS_INFORMATION pi;
1446+
LPPROC_THREAD_ATTRIBUTE_LIST attr_list = NULL;
1447+
HANDLE stdhandles[3];
1448+
DWORD stdhandles_count = 0;
1449+
SIZE_T size;
14351450
struct strbuf args;
14361451
wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs, *wenvblk = NULL;
14371452
unsigned flags = CREATE_UNICODE_ENVIRONMENT;
@@ -1441,6 +1456,16 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
14411456
is_msys2_sh(*argv) ? quote_arg_msys2 : quote_arg_msvc;
14421457
const char *strace_env;
14431458

1459+
if (restrict_handle_inheritance < 0)
1460+
restrict_handle_inheritance = core_restrict_inherited_handles;
1461+
/*
1462+
* The following code to restrict which handles are inherited seems
1463+
* to work properly only on Windows 7 and later, so let's disable it
1464+
* on Windows Vista and 2008.
1465+
*/
1466+
if (restrict_handle_inheritance < 0)
1467+
restrict_handle_inheritance = GetVersion() >> 16 >= 7601;
1468+
14441469
do_unset_environment_variables();
14451470

14461471
/* Determine whether or not we are associated to a console */
@@ -1468,11 +1493,23 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
14681493
CloseHandle(cons);
14691494
}
14701495
memset(&si, 0, sizeof(si));
1471-
si.cb = sizeof(si);
1472-
si.dwFlags = STARTF_USESTDHANDLES;
1473-
si.hStdInput = winansi_get_osfhandle(fhin);
1474-
si.hStdOutput = winansi_get_osfhandle(fhout);
1475-
si.hStdError = winansi_get_osfhandle(fherr);
1496+
si.StartupInfo.cb = sizeof(si);
1497+
si.StartupInfo.hStdInput = winansi_get_osfhandle(fhin);
1498+
si.StartupInfo.hStdOutput = winansi_get_osfhandle(fhout);
1499+
si.StartupInfo.hStdError = winansi_get_osfhandle(fherr);
1500+
1501+
/* The list of handles cannot contain duplicates */
1502+
if (si.StartupInfo.hStdInput != INVALID_HANDLE_VALUE)
1503+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdInput;
1504+
if (si.StartupInfo.hStdOutput != INVALID_HANDLE_VALUE &&
1505+
si.StartupInfo.hStdOutput != si.StartupInfo.hStdInput)
1506+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdOutput;
1507+
if (si.StartupInfo.hStdError != INVALID_HANDLE_VALUE &&
1508+
si.StartupInfo.hStdError != si.StartupInfo.hStdInput &&
1509+
si.StartupInfo.hStdError != si.StartupInfo.hStdOutput)
1510+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdError;
1511+
if (stdhandles_count)
1512+
si.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
14761513

14771514
if (*argv && !strcmp(cmd, *argv))
14781515
wcmd[0] = L'\0';
@@ -1530,16 +1567,97 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
15301567
wenvblk = make_environment_block(deltaenv);
15311568

15321569
memset(&pi, 0, sizeof(pi));
1533-
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL, TRUE,
1534-
flags, wenvblk, dir ? wdir : NULL, &si, &pi);
1570+
if (restrict_handle_inheritance && stdhandles_count &&
1571+
(InitializeProcThreadAttributeList(NULL, 1, 0, &size) ||
1572+
GetLastError() == ERROR_INSUFFICIENT_BUFFER) &&
1573+
(attr_list = (LPPROC_THREAD_ATTRIBUTE_LIST)
1574+
(HeapAlloc(GetProcessHeap(), 0, size))) &&
1575+
InitializeProcThreadAttributeList(attr_list, 1, 0, &size) &&
1576+
UpdateProcThreadAttribute(attr_list, 0,
1577+
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
1578+
stdhandles,
1579+
stdhandles_count * sizeof(HANDLE),
1580+
NULL, NULL)) {
1581+
si.lpAttributeList = attr_list;
1582+
flags |= EXTENDED_STARTUPINFO_PRESENT;
1583+
}
1584+
1585+
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL,
1586+
stdhandles_count ? TRUE : FALSE,
1587+
flags, wenvblk, dir ? wdir : NULL,
1588+
&si.StartupInfo, &pi);
1589+
1590+
/*
1591+
* On Windows 2008 R2, it seems that specifying certain types of handles
1592+
* (such as FILE_TYPE_CHAR or FILE_TYPE_PIPE) will always produce an
1593+
* error. Rather than playing finicky and fragile games, let's just try
1594+
* to detect this situation and simply try again without restricting any
1595+
* handle inheritance. This is still better than failing to create
1596+
* processes.
1597+
*/
1598+
if (!ret && restrict_handle_inheritance && stdhandles_count) {
1599+
DWORD err = GetLastError();
1600+
struct strbuf buf = STRBUF_INIT;
1601+
1602+
if (err != ERROR_NO_SYSTEM_RESOURCES &&
1603+
/*
1604+
* On Windows 7 and earlier, handles on pipes and character
1605+
* devices are inherited automatically, and cannot be
1606+
* specified in the thread handle list. Rather than trying
1607+
* to catch each and every corner case (and running the
1608+
* chance of *still* forgetting a few), let's just fall
1609+
* back to creating the process without trying to limit the
1610+
* handle inheritance.
1611+
*/
1612+
!(err == ERROR_INVALID_PARAMETER &&
1613+
GetVersion() >> 16 < 9200) &&
1614+
!getenv("SUPPRESS_HANDLE_INHERITANCE_WARNING")) {
1615+
DWORD fl = 0;
1616+
int i;
1617+
1618+
setenv("SUPPRESS_HANDLE_INHERITANCE_WARNING", "1", 1);
1619+
1620+
for (i = 0; i < stdhandles_count; i++) {
1621+
HANDLE h = stdhandles[i];
1622+
strbuf_addf(&buf, "handle #%d: %p (type %lx, "
1623+
"handle info (%d) %lx\n", i, h,
1624+
GetFileType(h),
1625+
GetHandleInformation(h, &fl),
1626+
fl);
1627+
}
1628+
strbuf_addstr(&buf, "\nThis is a bug; please report it "
1629+
"at\nhttps://github.com/git-for-windows/"
1630+
"git/issues/new\n\n"
1631+
"To suppress this warning, please set "
1632+
"the environment variable\n\n"
1633+
"\tSUPPRESS_HANDLE_INHERITANCE_WARNING=1"
1634+
"\n");
1635+
}
1636+
restrict_handle_inheritance = 0;
1637+
flags &= ~EXTENDED_STARTUPINFO_PRESENT;
1638+
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL,
1639+
TRUE, flags, wenvblk, dir ? wdir : NULL,
1640+
&si.StartupInfo, &pi);
1641+
if (ret && buf.len) {
1642+
errno = err_win_to_posix(GetLastError());
1643+
warning("failed to restrict file handles (%ld)\n\n%s",
1644+
err, buf.buf);
1645+
}
1646+
strbuf_release(&buf);
1647+
} else if (!ret)
1648+
errno = err_win_to_posix(GetLastError());
1649+
1650+
if (si.lpAttributeList)
1651+
DeleteProcThreadAttributeList(si.lpAttributeList);
1652+
if (attr_list)
1653+
HeapFree(GetProcessHeap(), 0, attr_list);
15351654

15361655
free(wenvblk);
15371656
free(wargs);
15381657

1539-
if (!ret) {
1540-
errno = ENOENT;
1658+
if (!ret)
15411659
return -1;
1542-
}
1660+
15431661
CloseHandle(pi.hThread);
15441662

15451663
/*

compat/winansi.c

+11-1
Original file line numberDiff line numberDiff line change
@@ -662,10 +662,20 @@ void winansi_init(void)
662662
*/
663663
HANDLE winansi_get_osfhandle(int fd)
664664
{
665+
HANDLE ret;
666+
665667
if (fd == 1 && (fd_is_interactive[1] & FD_SWAPPED))
666668
return hconsole1;
667669
if (fd == 2 && (fd_is_interactive[2] & FD_SWAPPED))
668670
return hconsole2;
669671

670-
return (HANDLE)_get_osfhandle(fd);
672+
ret = (HANDLE)_get_osfhandle(fd);
673+
674+
/*
675+
* There are obviously circumstances under which _get_osfhandle()
676+
* returns (HANDLE)-2. This is not documented anywhere, but that is so
677+
* clearly an invalid handle value that we can just work around this
678+
* and return the correct value for invalid handles.
679+
*/
680+
return ret == (HANDLE)-2 ? INVALID_HANDLE_VALUE : ret;
671681
}

t/helper/test-run-command.c

+44
Original file line numberDiff line numberDiff line change
@@ -200,13 +200,57 @@ static int testsuite(int argc, const char **argv)
200200
return !!ret;
201201
}
202202

203+
static int inherit_handle(const char *argv0)
204+
{
205+
struct child_process cp = CHILD_PROCESS_INIT;
206+
char path[PATH_MAX];
207+
int tmp;
208+
209+
/* First, open an inheritable handle */
210+
xsnprintf(path, sizeof(path), "out-XXXXXX");
211+
tmp = xmkstemp(path);
212+
213+
argv_array_pushl(&cp.args,
214+
"test-tool", argv0, "inherited-handle-child", NULL);
215+
cp.in = -1;
216+
cp.no_stdout = cp.no_stderr = 1;
217+
if (start_command(&cp) < 0)
218+
die("Could not start child process");
219+
220+
/* Then close it, and try to delete it. */
221+
close(tmp);
222+
if (unlink(path))
223+
die("Could not delete '%s'", path);
224+
225+
if (close(cp.in) < 0 || finish_command(&cp) < 0)
226+
die("Child did not finish");
227+
228+
return 0;
229+
}
230+
231+
static int inherit_handle_child(void)
232+
{
233+
struct strbuf buf = STRBUF_INIT;
234+
235+
if (strbuf_read(&buf, 0, 0) < 0)
236+
die("Could not read stdin");
237+
printf("Received %s\n", buf.buf);
238+
strbuf_release(&buf);
239+
240+
return 0;
241+
}
242+
203243
int cmd__run_command(int argc, const char **argv)
204244
{
205245
struct child_process proc = CHILD_PROCESS_INIT;
206246
int jobs;
207247

208248
if (argc > 1 && !strcmp(argv[1], "testsuite"))
209249
exit(testsuite(argc - 1, argv + 1));
250+
if (!strcmp(argv[1], "inherited-handle"))
251+
exit(inherit_handle(argv[0]));
252+
if (!strcmp(argv[1], "inherited-handle-child"))
253+
exit(inherit_handle_child());
210254

211255
if (argc < 3)
212256
return 1;

t/t0061-run-command.sh

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ cat >hello-script <<-EOF
1212
cat hello-script
1313
EOF
1414

15+
test_expect_success MINGW 'subprocess inherits only std handles' '
16+
test-tool run-command inherited-handle
17+
'
18+
1519
test_expect_success 'start_command reports ENOENT (slash)' '
1620
test-tool run-command start-command-ENOENT ./does-not-exist 2>err &&
1721
test_i18ngrep "\./does-not-exist" err

0 commit comments

Comments
 (0)