Changeset 3671 for trunk/src


Ignore:
Timestamp:
Mar 4, 2025, 3:26:47 AM (5 months ago)
Author:
bird
Message:

kmk: Workaround for the cygwin pipe problem.

Location:
trunk/src/kmk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/kmk/Makefile.kmk

    r3669 r3671  
    313313endif
    314314
    315 kmk_DEFS.win    += DEBUG_STDOUT_CLOSE_ISSUE
     315#kmk_DEFS.win    += DEBUG_STDOUT_CLOSE_ISSUE
    316316kmk_SOURCES.win += ../lib/msc_buffered_printf.c
    317317
  • trunk/src/kmk/output.c

    r3669 r3671  
    1515this program.  If not, see <http://www.gnu.org/licenses/>.  */
    1616
    17 #if defined (DEBUG_STDOUT_CLOSE_ISSUE) && defined (KBUILD_OS_WINDOWS)
     17#if defined (KBUILD_OS_WINDOWS) && (defined(KMK) || defined(DEBUG_STDOUT_CLOSE_ISSUE))
    1818# include "nt/ntstuff.h"
    1919# include "nt/nthlp.h"
     
    9797  union { BYTE ab[1024]; MY_FILE_NAME_INFORMATION NameInfo; } uBuf = {{0}};
    9898  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);
    104106}
    105107# endif /* KBUILD_OS_WINDOWS */
     
    217219#endif /* DEBUG_STDOUT_CLOSE_ISSUE */
    218220
     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. */
     239struct 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. */
     261static 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. */
     327static 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. */
     355static 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. */
     456int 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. */
     496static 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. */
     533static 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 */
    219557
    220558#if defined(KMK) && !defined(NO_OUTPUT_SYNC)
     
    13021640      exit (MAKE_TROUBLE);
    13031641    }
     1642#if defined(KBUILD_OS_WINDOWS) && defined(KMK)
     1643  win_pipe_hack_terminate ();
     1644#endif
    13041645}
    13051646
     
    13091650output_init (struct output *out)
    13101651{
     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
    13111662#ifdef DEBUG_STDOUT_CLOSE_ISSUE
    13121663  if (STREAM_OK (stdout) && ferror (stdout))
Note: See TracChangeset for help on using the changeset viewer.