| /* Windows32-based operating system interface for GNU Make. |
| Copyright (C) 2016-2025 Free Software Foundation, Inc. |
| This file is part of GNU Make. |
| |
| GNU Make is free software; you can redistribute it and/or modify it under the |
| terms of the GNU General Public License as published by the Free Software |
| Foundation; either version 3 of the License, or (at your option) any later |
| version. |
| |
| GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY |
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License along with |
| this program. If not, see <https://www.gnu.org/licenses/>. */ |
| |
| #include "makeint.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <windows.h> |
| #include <process.h> |
| #include <io.h> |
| #if _WIN32_WINNT > 0x0601 |
| #include <synchapi.h> |
| #endif |
| #include "pathstuff.h" |
| #include "sub_proc.h" |
| #include "w32err.h" |
| #include "os.h" |
| #include "debug.h" |
| |
| unsigned int |
| check_io_state () |
| { |
| static unsigned int state = IO_UNKNOWN; |
| HANDLE outfd, errfd; |
| |
| /* We only need to compute this once per process. */ |
| if (state != IO_UNKNOWN) |
| return state; |
| |
| /* Could have used GetHandleInformation, but that isn't supported |
| on Windows 9X. */ |
| outfd = get_handle_for_fd (fileno (stdout)); |
| errfd = get_handle_for_fd (fileno (stderr)); |
| |
| if (get_handle_for_fd (fileno (stdin)) != INVALID_HANDLE_VALUE) |
| state |= IO_STDIN_OK; |
| if (outfd != INVALID_HANDLE_VALUE) |
| state |= IO_STDOUT_OK; |
| if (errfd != INVALID_HANDLE_VALUE) |
| state |= IO_STDERR_OK; |
| |
| if (ALL_SET (state, IO_STDOUT_OK|IO_STDERR_OK)) |
| { |
| unsigned int combined = 0; |
| |
| if (outfd == errfd) |
| combined = IO_COMBINED_OUTERR; |
| else |
| { |
| DWORD outtype = GetFileType (outfd), errtype = GetFileType (errfd); |
| |
| if (outtype == errtype |
| && outtype != FILE_TYPE_UNKNOWN && errtype != FILE_TYPE_UNKNOWN) |
| { |
| if (outtype == FILE_TYPE_CHAR) |
| { |
| /* For character devices, check if they both refer to a |
| console. This loses if both handles refer to the |
| null device (FIXME!), but in that case we don't care |
| in the context of Make. */ |
| DWORD outmode, errmode; |
| |
| /* Each process on Windows can have at most 1 console, |
| so if both handles are for the console device, they |
| are the same. We also compare the console mode to |
| distinguish between stdin and stdout/stderr. */ |
| if (GetConsoleMode (outfd, &outmode) |
| && GetConsoleMode (errfd, &errmode) |
| && outmode == errmode) |
| combined = IO_COMBINED_OUTERR; |
| } |
| else |
| { |
| /* For disk files and pipes, compare their unique |
| attributes. */ |
| BY_HANDLE_FILE_INFORMATION outfi, errfi; |
| |
| /* Pipes get zero in the volume serial number, but do |
| appear to have meaningful information in file index |
| attributes. We test file attributes as well, for a |
| good measure. */ |
| if (GetFileInformationByHandle (outfd, &outfi) |
| && GetFileInformationByHandle (errfd, &errfi) |
| && outfi.dwVolumeSerialNumber == errfi.dwVolumeSerialNumber |
| && outfi.nFileIndexLow == errfi.nFileIndexLow |
| && outfi.nFileIndexHigh == errfi.nFileIndexHigh |
| && outfi.dwFileAttributes == errfi.dwFileAttributes) |
| combined = IO_COMBINED_OUTERR; |
| } |
| } |
| } |
| state |= combined; |
| } |
| |
| return state; |
| } |
| |
| /* A replacement for tmpfile, since the MSVCRT implementation creates |
| the file in the root directory of the current drive, which might |
| not be writable by our user, and also it returns a FILE* and we want a file |
| descriptor. Mostly borrowed from create_batch_file, see job.c. */ |
| int |
| os_anontmp () |
| { |
| char temp_path[MAX_PATH+1]; |
| unsigned path_size = GetTempPath (sizeof (temp_path), temp_path); |
| int using_cwd = 0; |
| |
| /* These variables are static so we won't try to reuse a name that was |
| generated a little while ago, because that file might not be on disk yet, |
| since we use FILE_ATTRIBUTE_TEMPORARY below, which tells the OS it |
| doesn't need to flush the cache to disk. If the file is not yet on disk, |
| we might think the name is available, while it really isn't. This |
| happens in parallel builds. */ |
| static unsigned uniq = 0; |
| static int second_loop = 0; |
| |
| const char base[] = "GmTMPF"; |
| const unsigned sizemax = sizeof (base) - 1 + 4 + 10 + 10; |
| unsigned pid = GetCurrentProcessId (); |
| |
| if (path_size == 0) |
| { |
| path_size = GetCurrentDirectory (sizeof (temp_path), temp_path); |
| using_cwd = 1; |
| } |
| |
| ++uniq; |
| if (uniq >= 0x10000 && !second_loop) |
| { |
| /* If we already had 64K batch files in this |
| process, make a second loop through the numbers, |
| looking for free slots, i.e. files that were |
| deleted in the meantime. */ |
| second_loop = 1; |
| uniq = 1; |
| } |
| |
| while (path_size > 0 && path_size + sizemax < sizeof (temp_path) |
| && (uniq < 0x10000 || !second_loop)) |
| { |
| HANDLE h; |
| |
| sprintf (temp_path + path_size, |
| "%s%s%u-%x.tmp", |
| temp_path[path_size - 1] == '\\' ? "" : "\\", |
| base, pid, uniq); |
| h = CreateFile (temp_path, /* file name */ |
| GENERIC_READ | GENERIC_WRITE | DELETE, /* desired access */ |
| FILE_SHARE_READ | FILE_SHARE_WRITE, /* share mode */ |
| NULL, /* default security attributes */ |
| CREATE_NEW, /* creation disposition */ |
| FILE_ATTRIBUTE_NORMAL | /* flags and attributes */ |
| FILE_ATTRIBUTE_TEMPORARY | |
| FILE_FLAG_DELETE_ON_CLOSE, |
| NULL); /* no template file */ |
| |
| if (h != INVALID_HANDLE_VALUE) |
| return _open_osfhandle ((intptr_t)h, 0); |
| |
| { |
| const DWORD er = GetLastError (); |
| |
| if (er == ERROR_FILE_EXISTS || er == ERROR_ALREADY_EXISTS) |
| { |
| ++uniq; |
| if (uniq == 0x10000 && !second_loop) |
| { |
| second_loop = 1; |
| uniq = 1; |
| } |
| } |
| /* The temporary path is not guaranteed to exist, or might not be |
| writable by user. Use the current directory as fallback. */ |
| else if (!using_cwd) |
| { |
| path_size = GetCurrentDirectory (sizeof (temp_path), temp_path); |
| using_cwd = 1; |
| } |
| else |
| { |
| errno = EACCES; |
| return -1; |
| } |
| } |
| } |
| |
| if (uniq >= 0x10000) |
| errno = EEXIST; |
| return -1; |
| } |
| |
| #if defined(MAKE_JOBSERVER) |
| |
| /* This section provides OS-specific functions to support the jobserver. */ |
| |
| static char jobserver_semaphore_name[MAX_PATH + 1]; |
| static HANDLE jobserver_semaphore = NULL; |
| |
| unsigned int |
| jobserver_setup (int slots, const char *style) |
| { |
| /* sub_proc.c is limited in the number of objects it can wait for. */ |
| |
| if (style && strcmp (style, "sem") != 0) |
| OS (fatal, NILF, _("unknown jobserver auth style '%s'"), style); |
| |
| if (slots > process_table_usable_size()) |
| { |
| slots = process_table_usable_size(); |
| DB (DB_JOBS, (_("jobserver slots limited to %d\n"), slots)); |
| } |
| |
| sprintf (jobserver_semaphore_name, "gmake_semaphore_%d", _getpid ()); |
| |
| jobserver_semaphore = CreateSemaphore ( |
| NULL, /* Use default security descriptor */ |
| slots, /* Initial count */ |
| slots, /* Maximum count */ |
| jobserver_semaphore_name); /* Semaphore name */ |
| |
| if (jobserver_semaphore == NULL) |
| { |
| DWORD err = GetLastError (); |
| const char *estr = map_windows32_error_to_string (err); |
| ONS (fatal, NILF, |
| _("creating jobserver semaphore: (Error %lu: %s)"), err, estr); |
| } |
| |
| return 1; |
| } |
| |
| unsigned int |
| jobserver_parse_auth (const char *auth) |
| { |
| jobserver_semaphore = OpenSemaphore ( |
| SEMAPHORE_ALL_ACCESS, /* Semaphore access setting */ |
| FALSE, /* Child processes DON'T inherit */ |
| auth); /* Semaphore name */ |
| |
| if (jobserver_semaphore == NULL) |
| { |
| DWORD err = GetLastError (); |
| const char *estr = map_windows32_error_to_string (err); |
| error (NILF, strlen (auth) + INTSTR_LENGTH + strlen (estr), |
| _("unable to open jobserver semaphore '%s': (Error %lu: %s)"), |
| auth, err, estr); |
| return 0; |
| } |
| |
| DB (DB_JOBS, (_("Jobserver client (semaphore %s)\n"), auth)); |
| |
| return 1; |
| } |
| |
| char * |
| jobserver_get_auth () |
| { |
| return xstrdup (jobserver_semaphore_name); |
| } |
| |
| const char * |
| jobserver_get_invalid_auth () |
| { |
| /* Because we're using a semaphore we don't need to invalidate. */ |
| return NULL; |
| } |
| |
| unsigned int |
| jobserver_enabled () |
| { |
| return jobserver_semaphore != NULL; |
| } |
| |
| /* Close jobserver semaphore */ |
| void |
| jobserver_clear () |
| { |
| if (jobserver_semaphore != NULL) |
| { |
| CloseHandle (jobserver_semaphore); |
| jobserver_semaphore = NULL; |
| } |
| } |
| |
| void |
| jobserver_release (int is_fatal) |
| { |
| if (! ReleaseSemaphore ( |
| jobserver_semaphore, /* handle to semaphore */ |
| 1, /* increase count by one */ |
| NULL)) /* not interested in previous count */ |
| { |
| if (is_fatal) |
| { |
| DWORD err = GetLastError (); |
| const char *estr = map_windows32_error_to_string (err); |
| ONS (fatal, NILF, |
| _("release jobserver semaphore: (Error %lu: %s)"), err, estr); |
| } |
| perror_with_name ("release_jobserver_semaphore", ""); |
| } |
| } |
| |
| unsigned int |
| jobserver_acquire_all () |
| { |
| unsigned int tokens = 0; |
| while (1) |
| { |
| DWORD dwEvent = WaitForSingleObject ( |
| jobserver_semaphore, /* Handle to semaphore */ |
| 0); /* DON'T wait on semaphore */ |
| |
| if (dwEvent != WAIT_OBJECT_0) |
| return tokens; |
| |
| ++tokens; |
| } |
| } |
| |
| void |
| jobserver_signal () |
| { |
| } |
| |
| void jobserver_pre_child (int recursive UNUSED) |
| { |
| } |
| |
| void jobserver_post_child (int recursive UNUSED) |
| { |
| } |
| |
| void |
| jobserver_pre_acquire () |
| { |
| } |
| |
| /* Returns 1 if we got a token, or 0 if a child has completed. |
| The Windows implementation doesn't support load detection. */ |
| unsigned int |
| jobserver_acquire (int timeout UNUSED) |
| { |
| HANDLE *handles; |
| DWORD dwHandleCount; |
| DWORD dwEvent; |
| |
| handles = xmalloc(process_table_actual_size() * sizeof(HANDLE)); |
| |
| /* Add jobserver semaphore to first slot. */ |
| handles[0] = jobserver_semaphore; |
| |
| /* Build array of handles to wait for. */ |
| dwHandleCount = 1 + process_set_handles (&handles[1]); |
| |
| dwEvent = process_wait_for_multiple_objects ( |
| dwHandleCount, /* number of objects in array */ |
| handles, /* array of objects */ |
| FALSE, /* wait for any object */ |
| INFINITE); /* wait until object is signalled */ |
| |
| free(handles); |
| |
| if (dwEvent == WAIT_FAILED) |
| { |
| DWORD err = GetLastError (); |
| const char *estr = map_windows32_error_to_string (err); |
| ONS (fatal, NILF, |
| _("semaphore or child process wait: (Error %lu: %s)"), |
| err, estr); |
| } |
| |
| /* WAIT_OBJECT_0 indicates that the semaphore was signalled. */ |
| return dwEvent == WAIT_OBJECT_0; |
| } |
| |
| #endif /* MAKE_JOBSERVER */ |
| |
| #if !defined(NO_OUTPUT_SYNC) |
| |
| #define MUTEX_PREFIX "fnm:" |
| |
| /* Since we're using this with CreateMutex, NULL is invalid. */ |
| static HANDLE osync_handle = NULL; |
| |
| unsigned int |
| osync_enabled () |
| { |
| return osync_handle != NULL; |
| } |
| |
| void |
| osync_setup () |
| { |
| SECURITY_ATTRIBUTES secattr; |
| |
| /* We are the top-level make, and we want the handle to be inherited |
| by our child processes. */ |
| secattr.nLength = sizeof (secattr); |
| secattr.lpSecurityDescriptor = NULL; /* use default security descriptor */ |
| secattr.bInheritHandle = TRUE; |
| |
| osync_handle = CreateMutex (&secattr, FALSE, NULL); |
| if (!osync_handle) |
| { |
| DWORD err = GetLastError (); |
| fprintf (stderr, "CreateMutex: error %lu\n", err); |
| errno = ENOLCK; |
| } |
| } |
| |
| char * |
| osync_get_mutex () |
| { |
| char *mutex = NULL; |
| |
| if (osync_enabled ()) |
| { |
| /* Prepare the mutex handle string for our children. |
| 2 hex digits per byte + 2 characters for "0x" + null. */ |
| mutex = xmalloc ((2 * sizeof (osync_handle)) + 2 + 1); |
| sprintf (mutex, "0x%Ix", (unsigned long long)(DWORD_PTR)osync_handle); |
| } |
| |
| return mutex; |
| } |
| |
| unsigned int |
| osync_parse_mutex (const char *mutex) |
| { |
| char *endp; |
| unsigned long long i; |
| |
| errno = 0; |
| i = strtoull (mutex, &endp, 16); |
| if (errno != 0) |
| OSS (fatal, NILF, _("cannot parse output sync mutex %s: %s"), |
| mutex, strerror (errno)); |
| if (endp[0] != '\0') |
| OS (fatal, NILF, _("invalid output sync mutex: %s"), mutex); |
| |
| osync_handle = (HANDLE) (DWORD_PTR) i; |
| |
| return 1; |
| } |
| |
| void |
| osync_clear () |
| { |
| if (osync_handle) |
| { |
| CloseHandle (osync_handle); |
| osync_handle = NULL; |
| } |
| } |
| |
| unsigned int |
| osync_acquire () |
| { |
| if (osync_enabled()) |
| { |
| DWORD result = WaitForSingleObject (osync_handle, INFINITE); |
| if (result == WAIT_FAILED || result == WAIT_TIMEOUT) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| void |
| osync_release () |
| { |
| if (osync_enabled()) |
| /* FIXME: Perhaps we should call ReleaseMutex repatedly until it errors |
| out, to make sure the mutext is released even if we somehow managed to |
| to take ownership multiple times? */ |
| ReleaseMutex (osync_handle); |
| } |
| |
| #endif /* NO_OUTPUT_SYNC */ |
| |
| void |
| fd_inherit(int fd) |
| { |
| HANDLE fh = get_handle_for_fd (fd); |
| |
| if (fh && fh != INVALID_HANDLE_VALUE) |
| SetHandleInformation (fh, HANDLE_FLAG_INHERIT, 1); |
| } |
| |
| void |
| fd_noinherit(int fd) |
| { |
| HANDLE fh = get_handle_for_fd (fd); |
| |
| if (fh && fh != INVALID_HANDLE_VALUE) |
| SetHandleInformation (fh, HANDLE_FLAG_INHERIT, 0); |
| } |
| |
| int |
| fd_set_append (int fd UNUSED) |
| { |
| return -1; |
| } |
| |
| void |
| fd_reset_append (int fd UNUSED, int flags UNUSED) |
| {} |
| |
| HANDLE |
| get_handle_for_fd (int fd) |
| { |
| /* This funcion call is needed to get around the "bad-function-cast"-warning |
| emitted by GCC when casting and assigning in the same statement. */ |
| intptr_t fh = _get_osfhandle (fd); |
| return (HANDLE) fh; |
| } |