- Timestamp:
- Mar 4, 2025, 3:26:47 AM (5 months ago)
- Location:
- trunk/src/kmk
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/kmk/Makefile.kmk
r3669 r3671 313 313 endif 314 314 315 kmk_DEFS.win += DEBUG_STDOUT_CLOSE_ISSUE315 #kmk_DEFS.win += DEBUG_STDOUT_CLOSE_ISSUE 316 316 kmk_SOURCES.win += ../lib/msc_buffered_printf.c 317 317 -
trunk/src/kmk/output.c
r3669 r3671 15 15 this program. If not, see <http://www.gnu.org/licenses/>. */ 16 16 17 #if defined ( DEBUG_STDOUT_CLOSE_ISSUE) && defined (KBUILD_OS_WINDOWS)17 #if defined (KBUILD_OS_WINDOWS) && (defined(KMK) || defined(DEBUG_STDOUT_CLOSE_ISSUE)) 18 18 # include "nt/ntstuff.h" 19 19 # include "nt/nthlp.h" … … 97 97 union { BYTE ab[1024]; MY_FILE_NAME_INFORMATION NameInfo; } uBuf = {{0}}; 98 98 MY_NTSTATUS rcNt3 = g_pfnNtQueryInformationFile(hStdOut, &Ios, &uBuf, sizeof(uBuf) - sizeof(WCHAR), MyFileNameInformation); 99 fprintf(stderr, "kmk[%u/%u]: stdout pipeinfo at %s: mode=%#x complmode=%#x type=%#x cfg=%#x instances=%u/%u inquota=%#x readable=%#x outquota=%#x writable=%#x state=%#x end=%#x hStdOut=%p %S rcNt=%#x/%#x/%#x\n", 100 makelevel, _getpid(), pszWhere, Info1.ReadMode, Info1.CompletionMode, Info2.NamedPipeType, Info2.NamedPipeConfiguration, 101 Info2.CurrentInstances, Info2.MaximumInstances, Info2.InboundQuota, Info2.ReadDataAvailable, Info2.OutboundQuota, 102 Info2.WriteQuotaAvailable, Info2.NamedPipeState, Info2.NamedPipeEnd, 103 hStdOut, uBuf.NameInfo.FileName, rcNt1, rcNt2, rcNt3); 99 DWORD dwMode = 0; 100 MY_NTSTATUS rcNt4 = g_pfnNtQueryInformationFile(hStdOut, &Ios, &dwMode, sizeof(dwMode), MyFileModeInformation); 101 fprintf(stderr, "kmk[%u/%u]: stdout pipeinfo at %s: fmode=%#x pipemode=%#x complmode=%#x type=%#x cfg=%#x instances=%u/%u inquota=%#x readable=%#x outquota=%#x writable=%#x state=%#x end=%#x hStdOut=%p %S rcNt=%#x/%#x/%#x/%#x\n", 102 makelevel, _getpid(), pszWhere, dwMode, Info1.ReadMode, Info1.CompletionMode, Info2.NamedPipeType, 103 Info2.NamedPipeConfiguration, Info2.CurrentInstances, Info2.MaximumInstances, Info2.InboundQuota, 104 Info2.ReadDataAvailable, Info2.OutboundQuota, Info2.WriteQuotaAvailable, Info2.NamedPipeState, Info2.NamedPipeEnd, 105 hStdOut, uBuf.NameInfo.FileName, rcNt1, rcNt2, rcNt3, rcNt4); 104 106 } 105 107 # endif /* KBUILD_OS_WINDOWS */ … … 217 219 #endif /* DEBUG_STDOUT_CLOSE_ISSUE */ 218 220 221 #if defined(KBUILD_OS_WINDOWS) && defined(KMK) 222 /* 223 Windows Asynchronous (Overlapping) Pipe Hack 224 -------------------------------------------- 225 226 If a write pipe is opened with FILE_FLAG_OVERLAPPED or equivalent flags, 227 concurrent WriteFile calls on that pipe may run into a race causing the 228 wrong thread to be worken up on completion. Since the write is still 229 pending, the number of bytes written hasn't been set and is still zero. 230 This leads to UCRT setting errno = ENOSPC and may cause stack corruption 231 when the write is finally completed and the IO_STATUS_BLOCK is written 232 by the kernel. 233 234 To work around this problem, we detect asynchronous pipes attached to 235 stdout and stderr and replaces them with standard pipes and threads 236 pumping output. The thread deals properly with the async writes. */ 237 238 /* Data for the pipe workaround hacks. */ 239 struct win_pipe_hacks 240 { 241 /* 1 (stdout) or 2 (stderr). */ 242 int fd; 243 int volatile fShutdown; 244 /** Event handle for overlapped I/O. */ 245 HANDLE hEvt; 246 /* The original pipe that's in overlapping state (write end). */ 247 HANDLE hDstPipe; 248 /* The replacement pipe (read end). */ 249 HANDLE hSrcPipe; 250 /** The thread pumping bytes between the two pipes. */ 251 HANDLE hThread; 252 /** Putting the overlapped I/O structure here is safer. */ 253 OVERLAPPED Overlapped; 254 } g_pipe_workarounds[2] = 255 { 256 { 1, 0, NULL, NULL, NULL, NULL, { 0, 0, {{ 0, 0}}} }, 257 { 2, 0, NULL, NULL, NULL, NULL, { 0, 0, {{ 0, 0}}} }, 258 }; 259 260 /* Thread function that pumps bytes between our pipe and the parents pipe. */ 261 static unsigned __stdcall win_pipe_pump_thread (void *user) 262 { 263 unsigned const idx = (unsigned)(intptr_t)user; 264 int fQuit = 0; 265 do 266 { 267 /* Read from the source pipe (our). */ 268 char achBuf[4096]; 269 DWORD cbRead = 0; 270 if (ReadFile (g_pipe_workarounds[idx].hSrcPipe, achBuf, sizeof(achBuf), &cbRead, NULL)) 271 { 272 for (unsigned iWrite = 0, off = 0; off < cbRead && !fQuit; iWrite++) 273 { 274 /* Write the data we've read to the origianl pipe, using overlapped 275 I/O. This should work fine even if hDstPipe wasn't opened in 276 overlapped I/O mode. */ 277 g_pipe_workarounds[idx].Overlapped.Internal = 0; 278 g_pipe_workarounds[idx].Overlapped.InternalHigh = 0; 279 g_pipe_workarounds[idx].Overlapped.Offset = 0xffffffff /*FILE_WRITE_TO_END_OF_FILE*/; 280 g_pipe_workarounds[idx].Overlapped.OffsetHigh = (DWORD)-1; 281 g_pipe_workarounds[idx].Overlapped.hEvent = g_pipe_workarounds[idx].hEvt; 282 DWORD cbWritten = 0; 283 if (!WriteFile (g_pipe_workarounds[idx].hDstPipe, &achBuf[off], cbRead - off, 284 &cbWritten, &g_pipe_workarounds[idx].Overlapped)) 285 { 286 if ((fQuit = GetLastError () != ERROR_IO_PENDING)) 287 break; 288 if ((fQuit = !GetOverlappedResult (g_pipe_workarounds[idx].hDstPipe, &g_pipe_workarounds[idx].Overlapped, 289 &cbWritten, TRUE))) 290 break; 291 } 292 off += cbWritten; 293 if (cbWritten == 0 && iWrite > 15) 294 { 295 DWORD fState = 0; 296 if ( GetNamedPipeHandleState(g_pipe_workarounds[idx].hDstPipe, &fState, NULL, NULL, NULL, NULL, 0) 297 && (fState & (PIPE_WAIT | PIPE_NOWAIT)) == PIPE_NOWAIT) 298 { 299 fState &= ~PIPE_NOWAIT; 300 fState |= PIPE_WAIT; 301 if ( SetNamedPipeHandleState(g_pipe_workarounds[idx].hDstPipe, &fState, NULL, NULL) 302 && iWrite == 16) 303 continue; 304 } 305 Sleep(iWrite & 15); 306 } 307 } 308 } 309 else 310 break; 311 } 312 while (!g_pipe_workarounds[idx].fShutdown && !fQuit); 313 314 /* Cleanup. */ 315 CloseHandle (g_pipe_workarounds[idx].hSrcPipe); 316 g_pipe_workarounds[idx].hSrcPipe = NULL; 317 318 CloseHandle (g_pipe_workarounds[idx].hDstPipe); 319 g_pipe_workarounds[idx].hDstPipe = NULL; 320 321 CloseHandle (g_pipe_workarounds[idx].hEvt); 322 g_pipe_workarounds[idx].hEvt = NULL; 323 return 0; 324 } 325 326 /* Shuts down the thread pumping bytes between our pipe and the parents pipe. */ 327 static void win_pipe_hack_terminate (void) 328 { 329 for (unsigned idx = 0; idx < 2; idx++) 330 if (g_pipe_workarounds[idx].hThread != NULL) 331 { 332 g_pipe_workarounds[idx].fShutdown++; 333 if (g_pipe_workarounds[idx].hSrcPipe != NULL) 334 CancelIoEx (g_pipe_workarounds[idx].hSrcPipe, NULL); 335 } 336 337 for (unsigned idx = 0; idx < 2; idx++) 338 if (g_pipe_workarounds[idx].hThread != NULL) 339 for (unsigned msWait = 64; msWait <= 1000; msWait *= 2) /* wait almost 2 seconds. */ 340 { 341 if (g_pipe_workarounds[idx].hSrcPipe != NULL) 342 CancelIoEx (g_pipe_workarounds[idx].hSrcPipe, NULL); 343 DWORD dwWait = WaitForSingleObject (g_pipe_workarounds[idx].hThread, msWait); 344 if (dwWait == WAIT_OBJECT_0) 345 { 346 CloseHandle (g_pipe_workarounds[idx].hThread); 347 g_pipe_workarounds[idx].hThread = NULL; 348 break; 349 } 350 } 351 } 352 353 /* Applies the asynchronous pipe hack to a standard handle. 354 The hPipe argument is the handle, and idx is 0 for stdout and 1 for stderr. */ 355 static void win_pipe_hack_apply (HANDLE hPipe, int idx, int fSameObj) 356 { 357 /* Create a normal pipe and assign it to an CRT file descriptor. The handles 358 will be created as not inheritable, but the _dup2 call below will duplicate 359 the write handle with inhertiance enabled. */ 360 HANDLE hPipeR = NULL; 361 HANDLE hPipeW = NULL; 362 if (CreatePipe (&hPipeR, &hPipeW, NULL, 0x1000)) 363 { 364 int fdTmp = _open_osfhandle ((intptr_t)hPipeW, _O_TEXT); 365 if (fdTmp >= 0) 366 { 367 int const fOldMode = _setmode (idx + 1, _O_TEXT); 368 if (fOldMode != _O_TEXT && fOldMode != -1) 369 { 370 _setmode (idx + 1, fOldMode); 371 _setmode (fdTmp, fOldMode); 372 } 373 374 /* Create the event sempahore. */ 375 HANDLE hEvt = CreateEventW (NULL, FALSE, FALSE, NULL); 376 if (hEvt != NULL && hEvt != INVALID_HANDLE_VALUE) 377 { 378 /* Duplicate the pipe, as the _dup2 call below will (probably) close it. */ 379 HANDLE hDstPipe = NULL; 380 if (DuplicateHandle (GetCurrentProcess (), hPipe, 381 GetCurrentProcess (), &hDstPipe, 382 0, FALSE, DUPLICATE_SAME_ACCESS)) 383 { 384 /* Create a thread for safely pumping bytes between the pipes. */ 385 g_pipe_workarounds[idx].hEvt = hEvt; 386 g_pipe_workarounds[idx].fShutdown = 0; 387 g_pipe_workarounds[idx].hDstPipe = hDstPipe; 388 g_pipe_workarounds[idx].hSrcPipe = hPipeR; 389 HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, win_pipe_pump_thread, (void *)(intptr_t)idx, 0, NULL); 390 if (hThread != NULL && hThread != INVALID_HANDLE_VALUE) 391 { 392 g_pipe_workarounds[idx].hThread = hThread; 393 394 /* Now that the thread is operating, replace the file descriptors(s). 395 This involves DuplicateHandle and will call SetStdHandle. */ 396 if (_dup2 (fdTmp, idx + 1) == 0) 397 { 398 if ( fSameObj 399 && _dup2 (fdTmp, idx + 2) != 0) 400 { 401 fprintf (stderr, "%s: warning: _dup2(%d,%d) failed - fSameObj=1: %s (%d, %u)", 402 program, fdTmp, idx + 2, strerror (errno), errno, GetLastError ()); 403 fSameObj = 0; 404 } 405 406 /* Boost the thread priority. */ 407 int const iPrioOld = GetThreadPriority (hThread); 408 int const iPrioNew = iPrioOld < THREAD_PRIORITY_NORMAL ? THREAD_PRIORITY_NORMAL 409 : iPrioOld < THREAD_PRIORITY_HIGHEST ? THREAD_PRIORITY_HIGHEST 410 : iPrioOld; 411 if (iPrioOld != iPrioNew) 412 SetThreadPriority (hThread, iPrioNew); 413 414 /* Update the standard handle and close the temporary file descriptor. */ 415 close (fdTmp); 416 return; 417 } 418 g_pipe_workarounds[idx].fShutdown = 1; 419 fprintf (stderr, "%s: warning: _dup2(%d,%d) failed: %s (%d, %u)", 420 program, fdTmp, idx + 1, strerror (errno), errno, GetLastError ()); 421 for (unsigned msWait = 64; msWait <= 1000; msWait *= 2) /* wait almost 2 seconds. */ 422 { 423 if (g_pipe_workarounds[idx].hSrcPipe != NULL) 424 CancelIoEx (g_pipe_workarounds[idx].hSrcPipe, NULL); 425 DWORD dwWait = WaitForSingleObject (hThread, msWait); 426 if (dwWait == WAIT_OBJECT_0) 427 break; 428 } 429 CloseHandle (g_pipe_workarounds[idx].hThread); 430 } 431 else 432 fprintf (stderr, "%s: warning: _beginthreadex failed: %s (%d, %u)", 433 program, strerror (errno), errno, GetLastError ()); 434 CloseHandle (hDstPipe); 435 } 436 else 437 fprintf (stderr, "%s: warning: DuplicateHandle failed: %u", program, GetLastError ()); 438 } 439 else 440 fprintf (stderr, "%s: warning: CreateEventW failed: %u", program, GetLastError ()); 441 close (fdTmp); 442 } 443 else 444 { 445 fprintf (stderr, "%s: warning: _open_osfhandle failed: %s (%d, %u)", 446 program, strerror (errno), errno, GetLastError ()); 447 CloseHandle (hPipeW); 448 } 449 CloseHandle (hPipeR); 450 } 451 else 452 fprintf (stderr, "%s: warning: CreatePipe failed: %u", program, GetLastError()); 453 } 454 455 /* Check if the two handles refers to the same pipe. */ 456 int win_pipe_is_same_object (HANDLE hPipe1, HANDLE hPipe2) 457 { 458 if (hPipe1 == NULL || hPipe1 == INVALID_HANDLE_VALUE) 459 return 0; 460 if (hPipe1 == hPipe2) 461 return 1; 462 463 /* Since windows 10 there is an API for this. */ 464 typedef BOOL (WINAPI *PFNCOMPAREOBJECTHANDLES)(HANDLE, HANDLE); 465 static int s_fInitialized = 0; 466 static PFNCOMPAREOBJECTHANDLES s_pfnCompareObjectHandles = NULL; 467 PFNCOMPAREOBJECTHANDLES pfnCompareObjectHandles = s_pfnCompareObjectHandles; 468 if (!pfnCompareObjectHandles && !s_fInitialized) 469 { 470 pfnCompareObjectHandles = (PFNCOMPAREOBJECTHANDLES)GetProcAddress (GetModuleHandleW (L"kernelbase.dll"), 471 "CompareObjectHandles"); 472 s_pfnCompareObjectHandles = pfnCompareObjectHandles; 473 s_fInitialized = 1; 474 } 475 if (pfnCompareObjectHandles) 476 return pfnCompareObjectHandles (hPipe1, hPipe2); 477 478 /* Otherwise we use FileInternalInformation, assuming ofc that the two are 479 local pipes. */ 480 birdResolveImportsWorker(); 481 MY_IO_STATUS_BLOCK Ios = {0}; 482 MY_FILE_INTERNAL_INFORMATION Info1; 483 MY_NTSTATUS rcNt = g_pfnNtQueryInformationFile (hPipe1, &Ios, &Info1, sizeof(Info1), MyFileInternalInformation); 484 if (!NT_SUCCESS (rcNt)) 485 return 0; 486 487 MY_FILE_INTERNAL_INFORMATION Info2; 488 rcNt = g_pfnNtQueryInformationFile (hPipe2, &Ios, &Info2, sizeof(Info2), MyFileInternalInformation); 489 if (!NT_SUCCESS (rcNt)) 490 return 0; 491 492 return Info1.IndexNumber.QuadPart == Info2.IndexNumber.QuadPart; 493 } 494 495 /* Predicate function that checks if the hack is required. */ 496 static int win_pipe_hack_needed (HANDLE hPipe, const char *pszEnvVarOverride) 497 { 498 birdResolveImportsWorker (); 499 500 /* Check the environment variable override first. 501 Setting it to '0' disables the hack, setting to anything else (other than 502 an empty string) forces the hack to be enabled. */ 503 const char * const pszValue = getenv (pszEnvVarOverride); 504 if (pszValue && *pszValue != '\0') 505 return *pszValue != '0'; 506 507 /* Check whether it is a pipe next. */ 508 DWORD const fType = GetFileType (hPipe) & ~FILE_TYPE_REMOTE; 509 if (fType != FILE_TYPE_PIPE) 510 return 0; 511 512 /* Check if the pipe is synchronous or overlapping. If it's overlapping 513 we must apply the workaround. */ 514 MY_IO_STATUS_BLOCK Ios = {0}; 515 DWORD fFlags = 0; 516 MY_NTSTATUS rcNt = g_pfnNtQueryInformationFile (hPipe, &Ios, &fFlags, sizeof(fFlags), MyFileModeInformation); 517 if ( NT_SUCCESS(rcNt) 518 && !(fFlags & (FILE_SYNCHRONOUS_IO_NONALERT | FILE_SYNCHRONOUS_IO_ALERT))) 519 return 1; 520 521 #if 1 522 /* We could also check if the pipe is in NOWAIT mode, but since we've got 523 code elsewhere for switching them to WAIT mode, we probably don't need 524 to do that... */ 525 if ( GetNamedPipeHandleStateW (hPipe, &fFlags, NULL, NULL, NULL, NULL, 0) 526 && (fFlags & PIPE_NOWAIT)) 527 return 1; 528 #endif 529 return 0; 530 } 531 532 /** Initializes the pipe hack. */ 533 static void win_pipe_hack_init (void) 534 { 535 HANDLE const hStdOut = (HANDLE)_get_osfhandle (_fileno (stdout)); 536 int const fStdOutNeeded = win_pipe_hack_needed (hStdOut, "KMK_PIPE_HACK_STDOUT"); 537 HANDLE const hStdErr = (HANDLE)_get_osfhandle (_fileno (stderr)); 538 int const fStdErrNeeded = win_pipe_hack_needed (hStdErr, "KMK_PIPE_HACK_STDERR"); 539 540 /* To avoid getting too mixed up output in a 'kmk |& tee log' situation, we 541 must try figure out if the two handles refer to the same pipe object. */ 542 int const fSameObj = fStdOutNeeded 543 && fStdErrNeeded 544 && win_pipe_is_same_object (hStdOut, hStdOut); 545 546 /* Apply the hack as needed. */ 547 if (fStdOutNeeded) 548 win_pipe_hack_apply (hStdOut, 0, fSameObj); 549 if (fStdErrNeeded && !fSameObj) 550 win_pipe_hack_apply (hStdErr, 1, 0); 551 if (getenv ("KMK_PIPE_HACK_DEBUG")) 552 fprintf (stderr, "fStdOutNeeded=%d fStdErrNeeded=%d fSameObj=%d\n", 553 fStdOutNeeded, fStdErrNeeded, fSameObj); 554 } 555 556 #endif /* KBUILD_OS_WINDOWS && KMK */ 219 557 220 558 #if defined(KMK) && !defined(NO_OUTPUT_SYNC) … … 1302 1640 exit (MAKE_TROUBLE); 1303 1641 } 1642 #if defined(KBUILD_OS_WINDOWS) && defined(KMK) 1643 win_pipe_hack_terminate (); 1644 #endif 1304 1645 } 1305 1646 … … 1309 1650 output_init (struct output *out) 1310 1651 { 1652 #if defined(KBUILD_OS_WINDOWS) && defined(KMK) 1653 /* Apply workaround for asynchronous pipes on windows on first call. */ 1654 static int s_not_first_call = 0; 1655 if (!s_not_first_call) 1656 { 1657 s_not_first_call = 1; 1658 win_pipe_hack_init (); 1659 } 1660 #endif 1661 1311 1662 #ifdef DEBUG_STDOUT_CLOSE_ISSUE 1312 1663 if (STREAM_OK (stdout) && ferror (stdout))
Note:
See TracChangeset
for help on using the changeset viewer.