1 /* Windows32-based operating system interface for GNU Make.
2 Copyright (C) 2016-2022 Free Software Foundation, Inc.
3 This file is part of GNU Make.
4
5 GNU Make is free software; you can redistribute it and/or modify it under the
6 terms of the GNU General Public License as published by the Free Software
7 Foundation; either version 3 of the License, or (at your option) any later
8 version.
9
10 GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
11 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License along with
15 this program. If not, see <https://www.gnu.org/licenses/>. */
16
17 #include "makeint.h"
18
19 #include <stdio.h>
20 #include <string.h>
21
22 #include <windows.h>
23 #include <process.h>
24 #include <io.h>
25 #if _WIN32_WINNT > 0x0601
26 #include <synchapi.h>
27 #endif
28 #include "pathstuff.h"
29 #include "sub_proc.h"
30 #include "w32err.h"
31 #include "os.h"
32 #include "debug.h"
33
34 unsigned int
35 check_io_state ()
36 {
37 static unsigned int state = IO_UNKNOWN;
38
39 /* We only need to compute this once per process. */
40 if (state != IO_UNKNOWN)
41 return state;
42
43 /* Could have used GetHandleInformation, but that isn't supported
44 on Windows 9X. */
45 HANDLE outfd = (HANDLE)_get_osfhandle (fileno (stdout));
46 HANDLE errfd = (HANDLE)_get_osfhandle (fileno (stderr));
47
48 if ((HANDLE)_get_osfhandle (fileno (stdin)) != INVALID_HANDLE_VALUE)
49 state |= IO_STDIN_OK;
50 if (outfd != INVALID_HANDLE_VALUE)
51 state |= IO_STDOUT_OK;
52 if (errfd != INVALID_HANDLE_VALUE)
53 state |= IO_STDERR_OK;
54
55 if (ALL_SET (state, IO_STDOUT_OK|IO_STDERR_OK))
56 {
57 unsigned int combined = 0;
58
59 if (outfd == errfd)
60 combined = IO_COMBINED_OUTERR;
61 else
62 {
63 DWORD outtype = GetFileType (outfd), errtype = GetFileType (errfd);
64
65 if (outtype == errtype
66 && outtype != FILE_TYPE_UNKNOWN && errtype != FILE_TYPE_UNKNOWN)
67 {
68 if (outtype == FILE_TYPE_CHAR)
69 {
70 /* For character devices, check if they both refer to a
71 console. This loses if both handles refer to the
72 null device (FIXME!), but in that case we don't care
73 in the context of Make. */
74 DWORD outmode, errmode;
75
76 /* Each process on Windows can have at most 1 console,
77 so if both handles are for the console device, they
78 are the same. We also compare the console mode to
79 distinguish between stdin and stdout/stderr. */
80 if (GetConsoleMode (outfd, &outmode)
81 && GetConsoleMode (errfd, &errmode)
82 && outmode == errmode)
83 combined = IO_COMBINED_OUTERR;
84 }
85 else
86 {
87 /* For disk files and pipes, compare their unique
88 attributes. */
89 BY_HANDLE_FILE_INFORMATION outfi, errfi;
90
91 /* Pipes get zero in the volume serial number, but do
92 appear to have meaningful information in file index
93 attributes. We test file attributes as well, for a
94 good measure. */
95 if (GetFileInformationByHandle (outfd, &outfi)
96 && GetFileInformationByHandle (errfd, &errfi)
97 && outfi.dwVolumeSerialNumber == errfi.dwVolumeSerialNumber
98 && outfi.nFileIndexLow == errfi.nFileIndexLow
99 && outfi.nFileIndexHigh == errfi.nFileIndexHigh
100 && outfi.dwFileAttributes == errfi.dwFileAttributes)
101 combined = IO_COMBINED_OUTERR;
102 }
103 }
104 }
105 state |= combined;
106 }
107
108 return state;
109 }
110
111 /* A replacement for tmpfile, since the MSVCRT implementation creates
112 the file in the root directory of the current drive, which might
113 not be writable by our user, and also it returns a FILE* and we want a file
114 descriptor. Mostly borrowed from create_batch_file, see job.c. */
115 int
116 os_anontmp ()
117 {
118 char temp_path[MAX_PATH+1];
119 unsigned path_size = GetTempPath (sizeof (temp_path), temp_path);
120 int using_cwd = 0;
121
122 /* These variables are static so we won't try to reuse a name that was
123 generated a little while ago, because that file might not be on disk yet,
124 since we use FILE_ATTRIBUTE_TEMPORARY below, which tells the OS it
125 doesn't need to flush the cache to disk. If the file is not yet on disk,
126 we might think the name is available, while it really isn't. This
127 happens in parallel builds. */
128 static unsigned uniq = 0;
129 static int second_loop = 0;
130
131 const char base[] = "gmake_tmpf";
132 const unsigned sizemax = sizeof (base) - 1 + 4 + 10 + 10;
133 unsigned pid = GetCurrentProcessId ();
134
135 if (path_size == 0)
136 {
137 path_size = GetCurrentDirectory (sizeof (temp_path), temp_path);
138 using_cwd = 1;
139 }
140
141 ++uniq;
142 if (uniq >= 0x10000 && !second_loop)
143 {
144 /* If we already had 64K batch files in this
145 process, make a second loop through the numbers,
146 looking for free slots, i.e. files that were
147 deleted in the meantime. */
148 second_loop = 1;
149 uniq = 1;
150 }
151
152 while (path_size > 0 && path_size + sizemax < sizeof (temp_path)
153 && (uniq < 0x10000 || !second_loop))
154 {
155 HANDLE h;
156
157 sprintf (temp_path + path_size,
158 "%s%s%u-%x.tmp",
159 temp_path[path_size - 1] == '\\' ? "" : "\\",
160 base, pid, uniq);
161 h = CreateFile (temp_path, /* file name */
162 GENERIC_READ | GENERIC_WRITE | DELETE, /* desired access */
163 FILE_SHARE_READ | FILE_SHARE_WRITE, /* share mode */
164 NULL, /* default security attributes */
165 CREATE_NEW, /* creation disposition */
166 FILE_ATTRIBUTE_NORMAL | /* flags and attributes */
167 FILE_ATTRIBUTE_TEMPORARY |
168 FILE_FLAG_DELETE_ON_CLOSE,
169 NULL); /* no template file */
170
171 if (h != INVALID_HANDLE_VALUE)
172 return _open_osfhandle ((intptr_t)h, 0);
173
174 {
175 const DWORD er = GetLastError ();
176
177 if (er == ERROR_FILE_EXISTS || er == ERROR_ALREADY_EXISTS)
178 {
179 ++uniq;
180 if (uniq == 0x10000 && !second_loop)
181 {
182 second_loop = 1;
183 uniq = 1;
184 }
185 }
186 /* The temporary path is not guaranteed to exist, or might not be
187 writable by user. Use the current directory as fallback. */
188 else if (!using_cwd)
189 {
190 path_size = GetCurrentDirectory (sizeof (temp_path), temp_path);
191 using_cwd = 1;
192 }
193 else
194 {
195 errno = EACCES;
196 return -1;
197 }
198 }
199 }
200
201 if (uniq >= 0x10000)
202 errno = EEXIST;
203 return -1;
204 }
205
206 #if defined(MAKE_JOBSERVER)
207
208 /* This section provides OS-specific functions to support the jobserver. */
209
210 static char jobserver_semaphore_name[MAX_PATH + 1];
211 static HANDLE jobserver_semaphore = NULL;
212
213 unsigned int
214 jobserver_setup (int slots, const char *style)
215 {
216 /* sub_proc.c is limited in the number of objects it can wait for. */
217
218 if (style && strcmp (style, "sem") != 0)
219 OS (fatal, NILF, _("Unknown jobserver auth style '%s'"), style);
220
221 if (slots > process_table_usable_size())
222 {
223 slots = process_table_usable_size();
224 DB (DB_JOBS, (_("Jobserver slots limited to %d\n"), slots));
225 }
226
227 sprintf (jobserver_semaphore_name, "gmake_semaphore_%d", _getpid ());
228
229 jobserver_semaphore = CreateSemaphore (
230 NULL, /* Use default security descriptor */
231 slots, /* Initial count */
232 slots, /* Maximum count */
233 jobserver_semaphore_name); /* Semaphore name */
234
235 if (jobserver_semaphore == NULL)
236 {
237 DWORD err = GetLastError ();
238 const char *estr = map_windows32_error_to_string (err);
239 ONS (fatal, NILF,
240 _("creating jobserver semaphore: (Error %ld: %s)"), err, estr);
241 }
242
243 return 1;
244 }
245
246 unsigned int
247 jobserver_parse_auth (const char *auth)
248 {
249 jobserver_semaphore = OpenSemaphore (
250 SEMAPHORE_ALL_ACCESS, /* Semaphore access setting */
251 FALSE, /* Child processes DON'T inherit */
252 auth); /* Semaphore name */
253
254 if (jobserver_semaphore == NULL)
255 {
256 DWORD err = GetLastError ();
257 const char *estr = map_windows32_error_to_string (err);
258 fatal (NILF, strlen (auth) + INTSTR_LENGTH + strlen (estr),
259 _("internal error: unable to open jobserver semaphore '%s': (Error %ld: %s)"),
260 auth, err, estr);
261 }
262 DB (DB_JOBS, (_("Jobserver client (semaphore %s)\n"), auth));
263
264 return 1;
265 }
266
267 char *
268 jobserver_get_auth ()
269 {
270 return xstrdup (jobserver_semaphore_name);
271 }
272
273 const char *
274 jobserver_get_invalid_auth ()
275 {
276 /* Because we're using a semaphore we don't need to invalidate. */
277 return NULL;
278 }
279
280 unsigned int
281 jobserver_enabled ()
282 {
283 return jobserver_semaphore != NULL;
284 }
285
286 /* Close jobserver semaphore */
287 void
288 jobserver_clear ()
289 {
290 if (jobserver_semaphore != NULL)
291 {
292 CloseHandle (jobserver_semaphore);
293 jobserver_semaphore = NULL;
294 }
295 }
296
297 void
298 jobserver_release (int is_fatal)
299 {
300 if (! ReleaseSemaphore (
301 jobserver_semaphore, /* handle to semaphore */
302 1, /* increase count by one */
303 NULL)) /* not interested in previous count */
304 {
305 if (is_fatal)
306 {
307 DWORD err = GetLastError ();
308 const char *estr = map_windows32_error_to_string (err);
309 ONS (fatal, NILF,
310 _("release jobserver semaphore: (Error %ld: %s)"), err, estr);
311 }
312 perror_with_name ("release_jobserver_semaphore", "");
313 }
314 }
315
316 unsigned int
317 jobserver_acquire_all ()
318 {
319 unsigned int tokens = 0;
320 while (1)
321 {
322 DWORD dwEvent = WaitForSingleObject (
323 jobserver_semaphore, /* Handle to semaphore */
324 0); /* DON'T wait on semaphore */
325
326 if (dwEvent != WAIT_OBJECT_0)
327 return tokens;
328
329 ++tokens;
330 }
331 }
332
333 void
334 jobserver_signal ()
335 {
336 }
337
338 void jobserver_pre_child (int recursive)
339 {
340 }
341
342 void jobserver_post_child (int recursive)
343 {
344 }
345
346 void
347 jobserver_pre_acquire ()
348 {
349 }
350
351 /* Returns 1 if we got a token, or 0 if a child has completed.
352 The Windows implementation doesn't support load detection. */
353 unsigned int
354 jobserver_acquire (int timeout)
355 {
356 HANDLE *handles;
357 DWORD dwHandleCount;
358 DWORD dwEvent;
359
360 handles = xmalloc(process_table_actual_size() * sizeof(HANDLE));
361
362 /* Add jobserver semaphore to first slot. */
363 handles[0] = jobserver_semaphore;
364
365 /* Build array of handles to wait for. */
366 dwHandleCount = 1 + process_set_handles (&handles[1]);
367
368 dwEvent = process_wait_for_multiple_objects (
369 dwHandleCount, /* number of objects in array */
370 handles, /* array of objects */
371 FALSE, /* wait for any object */
372 INFINITE); /* wait until object is signalled */
373
374 free(handles);
375
376 if (dwEvent == WAIT_FAILED)
377 {
378 DWORD err = GetLastError ();
379 const char *estr = map_windows32_error_to_string (err);
380 ONS (fatal, NILF,
381 _("semaphore or child process wait: (Error %ld: %s)"),
382 err, estr);
383 }
384
385 /* WAIT_OBJECT_0 indicates that the semaphore was signalled. */
386 return dwEvent == WAIT_OBJECT_0;
387 }
388
389 #endif /* MAKE_JOBSERVER */
390
391 #if !defined(NO_OUTPUT_SYNC)
392
393 #define MUTEX_PREFIX "fnm:"
394
395 /* Since we're using this with CreateMutex, NULL is invalid. */
396 static HANDLE osync_handle = NULL;
397
398 unsigned int
399 osync_enabled ()
400 {
401 return osync_handle != NULL;
402 }
403
404 void
405 osync_setup ()
406 {
407 SECURITY_ATTRIBUTES secattr;
408
409 /* We are the top-level make, and we want the handle to be inherited
410 by our child processes. */
411 secattr.nLength = sizeof (secattr);
412 secattr.lpSecurityDescriptor = NULL; /* use default security descriptor */
413 secattr.bInheritHandle = TRUE;
414
415 osync_handle = CreateMutex (&secattr, FALSE, NULL);
416 if (!osync_handle)
417 {
418 DWORD err = GetLastError ();
419 fprintf (stderr, "CreateMutex: error %lu\n", err);
420 errno = ENOLCK;
421 }
422 }
423
424 char *
425 osync_get_mutex ()
426 {
427 char *mutex = NULL;
428
429 if (osync_enabled ())
430 {
431 /* Prepare the mutex handle string for our children.
432 2 hex digits per byte + 2 characters for "0x" + null. */
433 mutex = xmalloc ((2 * sizeof (osync_handle)) + 2 + 1);
434 sprintf (mutex, "0x%Ix", (unsigned long long)(DWORD_PTR)osync_handle);
435 }
436
437 return mutex;
438 }
439
440 unsigned int
441 osync_parse_mutex (const char *mutex)
442 {
443 char *endp;
444 unsigned long long i;
445
446 errno = 0;
447 i = strtoull (mutex, &endp, 16);
448 if (errno != 0)
449 OSS (fatal, NILF, _("cannot parse output sync mutex %s: %s"),
450 mutex, strerror (errno));
451 if (endp[0] != '\0')
452 OS (fatal, NILF, _("invalid output sync mutex: %s"), mutex);
453
454 osync_handle = (HANDLE) (DWORD_PTR) i;
455
456 return 1;
457 }
458
459 void
460 osync_clear ()
461 {
462 if (osync_handle)
463 {
464 CloseHandle (osync_handle);
465 osync_handle = NULL;
466 }
467 }
468
469 unsigned int
470 osync_acquire ()
471 {
472 if (osync_enabled())
473 {
474 DWORD result = WaitForSingleObject (osync_handle, INFINITE);
475 if (result == WAIT_FAILED || result == WAIT_TIMEOUT)
476 return 0;
477 }
478
479 return 1;
480 }
481
482 void
483 osync_release ()
484 {
485 if (osync_enabled())
486 /* FIXME: Perhaps we should call ReleaseMutex repatedly until it errors
487 out, to make sure the mutext is released even if we somehow managed to
488 to take ownership multiple times? */
489 ReleaseMutex (osync_handle);
490 }
491
492 #endif /* NO_OUTPUT_SYNC */
493
494 void
495 fd_inherit(int fd)
496 {
497 HANDLE fh = (HANDLE)_get_osfhandle(fd);
498
499 if (fh && fh != INVALID_HANDLE_VALUE)
500 SetHandleInformation(fh, HANDLE_FLAG_INHERIT, 1);
501 }
502
503 void
504 fd_noinherit(int fd)
505 {
506 HANDLE fh = (HANDLE)_get_osfhandle(fd);
507
508 if (fh && fh != INVALID_HANDLE_VALUE)
509 SetHandleInformation(fh, HANDLE_FLAG_INHERIT, 0);
510 }
511
512 void
513 fd_set_append (int fd)
514 {}