ambuild2/ipc/winapi.py
author David Anderson <danderson@mozilla.com>
Wed Nov 13 16:23:04 2013 -0800 (2013-11-13)
changeset 184 b8e9518bd03b
parent 171 08b968f0e072
child 223 0cf7fb24b5fa
permissions -rw-r--r--
Add Cygwin support.
     1 # vim: set ts=8 sts=2 sw=2 tw=99 et:
     2 #
     3 # This file is part of AMBuild.
     4 # 
     5 # AMBuild is free software: you can redistribute it and/or modify
     6 # it under the terms of the GNU General Public License as published by
     7 # the Free Software Foundation, either version 3 of the License, or
     8 # (at your option) any later version.
     9 # 
    10 # AMBuild is distributed in the hope that it will be useful,
    11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
    12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13 # GNU General Public License for more details.
    14 # 
    15 # You should have received a copy of the GNU General Public License
    16 # along with AMBuild. If not, see <http://www.gnu.org/licenses/>.
    17 import ctypes
    18 import os, sys
    19 import tempfile
    20 from ambuild2 import util
    21 
    22 handle_t = ctypes.c_void_p
    23 handlep_t = ctypes.POINTER(handle_t)
    24 if ctypes.sizeof(handle_t) == 4:
    25   INVALID_HANDLE_VALUE = 0xffffffff
    26 elif ctypes.sizeof(handle_t) == 8:
    27   INVALID_HANDLE_VALUE = 0xffffffffffffffff
    28 INVALID_HANDLE = handle_t(INVALID_HANDLE_VALUE)
    29 
    30 LANG_NEUTRAL = 0x00
    31 SUBLANG_DEFAULT = 0x01
    32 
    33 def MAKELANGID(primary, sub):
    34   return (sub << 10) | primary
    35 
    36 if sys.platform != 'cygwin':
    37   from ctypes import WinError
    38   WINDLL = ctypes.windll
    39 
    40   def sys_executable():
    41     return sys.executable
    42 else:
    43   import subprocess
    44 
    45   WINDLL = ctypes.cdll
    46 
    47   sys_executable_ = None
    48   def sys_executable():
    49     global sys_executable_
    50     if not sys_executable_:
    51       output = subprocess.check_output(['cygpath', '-w', sys.executable])
    52       sys_executable_ = output.strip() + '.exe'
    53     return sys_executable_
    54 
    55   class WindowsError(Exception):
    56     def __init__(self, errno, message):
    57       self.winerror = errno
    58       Exception.__init__(self, message)
    59 
    60   def WinError():
    61     error = GetLastError()
    62     buffer = ctypes.create_string_buffer(4096)
    63 
    64     rval = fnFormatMessage(
    65       FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
    66       None,
    67       error,
    68       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    69       ctypes.cast(buffer, ctypes.c_void_p),
    70       ctypes.sizeof(buffer),
    71       None
    72     )
    73     if rval == 0:
    74       raise Exception('system error {0} while reporting system error {1}'.format(GetLastError(), error))
    75 
    76     raise WindowsError(error, buffer.value)
    77 
    78 fnCreateNamedPipeA = WINDLL.kernel32.CreateNamedPipeA
    79 fnCreateNamedPipeA.argtypes = [
    80   ctypes.c_char_p,          # LPCTSTR lpName
    81   ctypes.c_int,             # DWORD dwOpenMode
    82   ctypes.c_int,             # DWORD dwPipeMode
    83   ctypes.c_int,             # DWORD nMaxInstances
    84   ctypes.c_int,             # DWORD nOutBufferSize
    85   ctypes.c_int,             # DWORD nInBufferSize,
    86   ctypes.c_int,             # DWORD nDefaultTimeOut
    87   ctypes.c_void_p           # LPSECURITY_ATTRIBUTES lpSecurityAttributes
    88 ]
    89 fnCreateNamedPipeA.restype = handle_t
    90 
    91 fnCreateFileA = WINDLL.kernel32.CreateFileA
    92 fnCreateFileA.argtypes = [
    93   ctypes.c_char_p,          # LPCTSTR lpFileName
    94   ctypes.c_int,             # DWORD dwDesiredAccess
    95   ctypes.c_int,             # DWORD dwShareMode
    96   ctypes.c_void_p,          # LPSECURITY_ATTRIBUTES lpSecurityAttributes
    97   ctypes.c_int,             # DWORD dwCreationDisposition
    98   ctypes.c_int,             # DWORD dwFlagsAndAttributes
    99   handle_t                  # HANDLE hTempateFile
   100 ]
   101 fnCreateFileA.restype = handle_t
   102 
   103 fnCloseHandle = WINDLL.kernel32.CloseHandle
   104 fnCloseHandle.argtypes = [ handle_t ]
   105 fnCloseHandle.restype = ctypes.c_int
   106 
   107 class Overlapped(ctypes.Structure):
   108   _fields_ = [
   109     ('Internal',      ctypes.c_void_p),
   110     ('InternalHigh',  ctypes.c_void_p),
   111 
   112     # This is technically a union of 8 bytes and a pointer, so it's always
   113     # 8 bytes on every platform.
   114     ('Offset',        ctypes.c_int),
   115     ('OffsetHigh',    ctypes.c_int),
   116 
   117     ('hEvent',        handle_t)
   118   ]
   119 
   120   def __init__(self, event=handle_t()):
   121     super(Overlapped, self).__init__()
   122     self.hEvent = event
   123 
   124   def quiet(self):
   125     # This prevents notifying IO completion ports.
   126     self.hEvent = ctypes.cast(ctypes.c_void_p(self.hEvent | 1), handle_t)
   127 
   128   def event(self):
   129     return ctypes.cast(ctypes.c_void_p((self.hEvent.value >> 1) << 1), handle_t)
   130 
   131 # Given the occasional shitfest that is ctypes, it doesn't work to byref() a
   132 # struct. We have to keep the POINTER type around and use from_address.
   133 LPOVERLAPPED = ctypes.POINTER(Overlapped)
   134 
   135 fnCreateIoCompletionPort = WINDLL.kernel32.CreateIoCompletionPort
   136 fnCreateIoCompletionPort.argtypes = [
   137   handle_t,                 # HANDLE FileHandle
   138   handle_t,                 # HANDLE ExistingCompletionPort
   139   ctypes.c_size_t,          # ULONG_PTR CompletionKey
   140   ctypes.c_int              # DWORD NumberOfConcurrentThreads
   141 ]
   142 fnCreateIoCompletionPort.restype = handle_t
   143 
   144 fnGetQueuedCompletionStatus = WINDLL.kernel32.GetQueuedCompletionStatus
   145 fnGetQueuedCompletionStatus.argtypes = [
   146   handle_t,                         # HANDLE CompletionPort
   147   ctypes.POINTER(ctypes.c_int),     # LPDWORD
   148   ctypes.POINTER(ctypes.c_size_t),  # PULONG_PTR lpCompletionKey
   149   ctypes.POINTER(ctypes.c_void_p),  # LPOVERLAPPED *lpOverlapped
   150   ctypes.c_int                      # DWORD
   151 ]
   152 fnGetQueuedCompletionStatus.restype = ctypes.c_int
   153 
   154 fnCreateProcessA = WINDLL.kernel32.CreateProcessA
   155 fnCreateProcessA.argtypes = [
   156   ctypes.c_char_p,          # LPCTSTR lpApplicationName
   157   ctypes.c_char_p,          # LPTSTR lpCommandLine
   158   ctypes.c_void_p,          # LPSECURITY_ATTRIBUTES lpProcessAttributes
   159   ctypes.c_void_p,          # LPSECURITY_ATTRIBUTES lpThreadAttributes
   160   ctypes.c_int,             # BOOL bInheritHandles
   161   ctypes.c_int,             # DWORD dwCreationFlags
   162   ctypes.c_void_p,          # lpEnvironment
   163   ctypes.c_char_p,          # lpCurrentDirectory
   164   ctypes.c_void_p,          # lpStartupInfo
   165   ctypes.c_void_p           # LPPROCESS_INFORMATION lpProcessInformation
   166 ]
   167 fnCreateProcessA.restype = ctypes.c_int
   168 
   169 fnDuplicateHandle = WINDLL.kernel32.DuplicateHandle
   170 fnDuplicateHandle.argtypes = [
   171   handle_t,                 # hSourceProcessHandle
   172   handle_t,                 # hSourceHandle,
   173   handle_t,                 # hTargetProcessHandle
   174   handlep_t,                # lpTargetHandle,
   175   ctypes.c_int,             # dwDesiredAccess
   176   ctypes.c_int,             # bInheritHandle
   177   ctypes.c_int              # dwOptions
   178 ]
   179 fnDuplicateHandle.restype = ctypes.c_int
   180 
   181 fnGetCurrentProcess = WINDLL.kernel32.GetCurrentProcess
   182 fnGetCurrentProcess.restype = handle_t
   183 
   184 fnGetStdHandle = WINDLL.kernel32.GetStdHandle
   185 fnGetStdHandle.argtypes = [ ctypes.c_int ]
   186 fnGetStdHandle.restype = handle_t
   187 
   188 fnWriteFile = WINDLL.kernel32.WriteFile
   189 fnWriteFile.argtypes = [
   190   handle_t,                         # HANDLE hFile,
   191   ctypes.c_void_p,                  # LPCVOID lpBuffer
   192   ctypes.c_int,                     # DWORD nNumberOfBytesToWrite
   193   ctypes.POINTER(ctypes.c_int),     # LPDWORD lpNumberOfBytesWritten
   194   ctypes.c_void_p                   # LPOVERLAPPED lpOverlapped
   195 ]
   196 fnWriteFile.restype = ctypes.c_int
   197 
   198 fnReadFile = WINDLL.kernel32.ReadFile
   199 fnReadFile.argtypes = [
   200   handle_t,                         # HANDLE hFile,
   201   ctypes.c_void_p,                  # LPVOID lpBuffer,
   202   ctypes.c_int,                     # DWORD lpNumberOfBytesRead,
   203   ctypes.POINTER(ctypes.c_int),     # LPDWORD lpNumberOfBytesWritten,
   204   ctypes.c_void_p                   # LPOVERLAPPED lpOverlapped
   205 ]
   206 fnReadFile.restype = ctypes.c_int
   207 
   208 fnCreateEvent = WINDLL.kernel32.CreateEventA
   209 fnCreateEvent.argtypes = [
   210   ctypes.c_void_p,          # LPSECURITY_ATTRIBUTES lpEventAttributes
   211   ctypes.c_int,             # BOOL bManualReset
   212   ctypes.c_int,             # BOOL bInitialState
   213   ctypes.c_char_p           # LPCTSTR lpName
   214 ]
   215 fnCreateEvent.restype = handle_t
   216 
   217 fnResetEvent = WINDLL.kernel32.ResetEvent
   218 fnResetEvent.argtypes = [ handle_t ]
   219 fnResetEvent.restype = ctypes.c_int
   220 
   221 fnGetLastError = WINDLL.kernel32.GetLastError
   222 fnGetLastError.restype = ctypes.c_int
   223 
   224 fnWaitForSingleObject = WINDLL.kernel32.WaitForSingleObject
   225 fnWaitForSingleObject.argtypes = [ handle_t, ctypes.c_int ]
   226 fnWaitForSingleObject.restype = ctypes.c_int
   227 
   228 fnWaitForMultipleObjects = WINDLL.kernel32.WaitForMultipleObjects
   229 fnWaitForMultipleObjects.argptypes = [
   230   ctypes.c_int,                     # DWORD nCount
   231   ctypes.POINTER(handle_t),         # const HANDLE *lpHandles
   232   ctypes.c_int,                     # BOOL bWaitAll
   233   ctypes.c_int                      # DWORD dwMilliseconds
   234 ]
   235 fnWaitForMultipleObjects.restype = ctypes.c_int
   236 
   237 fnConnectNamedPipe = WINDLL.kernel32.ConnectNamedPipe
   238 fnConnectNamedPipe.argtypes = [ handle_t, ctypes.POINTER(Overlapped) ]
   239 fnConnectNamedPipe.restype = ctypes.c_int
   240 
   241 fnGetExitCodeProcess = WINDLL.kernel32.GetExitCodeProcess
   242 fnGetExitCodeProcess.argtypes = [ handle_t, ctypes.POINTER(ctypes.c_int) ]
   243 fnGetExitCodeProcess.restype = ctypes.c_int
   244 
   245 fnFormatMessage = WINDLL.kernel32.FormatMessageA
   246 fnFormatMessage.argtypes = [
   247   ctypes.c_int,                     # DWORD dwFlags
   248   ctypes.c_void_p,                  # LPCVOID lpSource
   249   ctypes.c_int,                     # DWORD dwMessageId
   250   ctypes.c_int,                     # DWRD dwLanguageId
   251   ctypes.c_void_p,                  # LPTSTR lpBuffer
   252   ctypes.c_int,                     # DWORD nSize
   253   ctypes.c_void_p                   # va_list *Arguments
   254 ]
   255 fnFormatMessage.restype = ctypes.c_int
   256 
   257 PIPE_ACCESS_DUPLEX =              0x00000003
   258 PIPE_TYPE_BYTE =                  0x00000000
   259 PIPE_TYPE_READMODE_BYTE =         0x00000000
   260 FILE_FLAG_FIRST_PIPE_INSTANCE =   0x00080000
   261 FILE_FLAG_OVERLAPPED =            0x40000000
   262 GENERIC_READ =                    0x80000000
   263 GENERIC_WRITE =                   0x40000000
   264 OPEN_EXISTING =                   3
   265 DUPLICATE_SAME_ACCESS =           0x00000002
   266 STARTF_USESTDHANDLES =            0x00000100
   267 STD_INPUT_HANDLE =                -10
   268 STD_OUTPUT_HANDLE =               -11
   269 STD_ERROR_HANDLE =                -12
   270 INFINITE =                        -1
   271 WAIT_ABANDONED =                  0x00000080
   272 WAIT_OBJECT_0 =                   0x00000000
   273 WAIT_TIMEOUT =                    0x00000102
   274 WAIT_FAILED =                     -1
   275 STILL_ACTIVE =                    259
   276 FORMAT_MESSAGE_ALLOCATE_BUFFER =  0x00000100
   277 FORMAT_MESSAGE_IGNORE_INSERTS =   0x00000200
   278 FORMAT_MESSAGE_FROM_SYSTEM =      0x00001000
   279 ERROR_BROKEN_PIPE =               109
   280 ERROR_PIPE_CONNECTED =            535
   281 ERROR_IO_PENDING =                997
   282 
   283 pipe_counter_ = 0
   284 
   285 class StartupInfo(ctypes.Structure):
   286   def __init__(self):
   287     super(StartupInfo, self).__init__()
   288     self.cb = ctypes.sizeof(StartupInfo)
   289 
   290   _fields_ = [
   291     ('cb',              ctypes.c_int),
   292     ('lpReserved',      ctypes.c_char_p),
   293     ('lpDesktop',       ctypes.c_char_p),
   294     ('lpTitle',         ctypes.c_char_p),
   295     ('dwX',             ctypes.c_int),
   296     ('dwY',             ctypes.c_int),
   297     ('dwXSize',         ctypes.c_int),
   298     ('dwYSize',         ctypes.c_int),
   299     ('dwXCountChars',   ctypes.c_int),
   300     ('dwYCountChars',   ctypes.c_int),
   301     ('dwFillAttribute', ctypes.c_int),
   302     ('dwFlags',         ctypes.c_int),
   303     ('wShowWindow',     ctypes.c_short),
   304     ('cbReserved2',     ctypes.c_short),
   305     ('lpReserved2',     ctypes.c_void_p),
   306     ('hStdInput',       handle_t),
   307     ('hStdOutput',      handle_t),
   308     ('hStdError',       handle_t)
   309   ]
   310 
   311 class ProcessInformation(ctypes.Structure):
   312   def __init__(self):
   313     super(ProcessInformation, self).__init__()
   314 
   315   _fields_ = [
   316     ('hProcess',        handle_t),
   317     ('hThread',         handle_t),
   318     ('dwProcessId',     ctypes.c_int),
   319     ('dwThreadId',      ctypes.c_int)
   320   ]
   321 
   322 def CreateEvent(manual, initial):
   323   rval = fnCreateEvent(None, int(manual), int(initial), None)
   324   if not rval:
   325     raise WinError()
   326 
   327   return handle_t(rval)
   328 
   329 def ResetEvent(handle):
   330   if not fnResetEvent(handle):
   331     raise WinError()
   332 
   333 def CreateNamedPipe():
   334   global pipe_counter_
   335 
   336   pipe_name = r'\\.\pipe\ambuild-{0}-{1}'.format(os.getpid(), pipe_counter_)
   337   pipe_counter_ += 1
   338 
   339   flags = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE
   340 
   341   rval = fnCreateNamedPipeA(
   342     util.str2b(pipe_name),
   343     flags,
   344     PIPE_TYPE_BYTE | PIPE_TYPE_READMODE_BYTE,
   345     1,
   346     4096,
   347     4096,
   348     5000,
   349     None
   350   )
   351   if rval == INVALID_HANDLE_VALUE:
   352     raise WinError()
   353   return handle_t(rval), pipe_name
   354 
   355 def OpenPipe(path):
   356   rval = fnCreateFileA(
   357     util.str2b(path),
   358     GENERIC_READ | GENERIC_WRITE,
   359     0,
   360     None,
   361     OPEN_EXISTING,
   362     FILE_FLAG_OVERLAPPED,
   363     None
   364   )
   365   if rval == INVALID_HANDLE_VALUE:
   366     raise WinError()
   367   return handle_t(rval)
   368 
   369 def CloseHandle(handle):
   370   fnCloseHandle(handle)
   371 
   372 def WaitForSingleObject(handle, wait):
   373   result = fnWaitForSingleObject(handle, wait)
   374   if result == WAIT_FAILED:
   375     raise WinError()
   376   return result == WAIT_OBJECT_0
   377 
   378 def WaitForMultipleObjects(handles, wait_all, wait):
   379   nhandles = len(handles)
   380   array = (handle_t * nhandles)()
   381   for index, handle in enumerate(handles):
   382     array[index] = handle
   383   
   384   result = fnWaitForMultipleObjects(
   385     nhandles,
   386     array,
   387     int(wait_all),
   388     wait
   389   )
   390 
   391   if result == WAIT_FAILED:
   392     raise WinError()
   393 
   394   return result
   395 
   396 def CreateIoCompletionPort():
   397   rval = fnCreateIoCompletionPort(
   398     INVALID_HANDLE,
   399     None,
   400     0,
   401     1
   402   )
   403   if not rval:
   404     raise WinError()
   405   return handle_t(rval)
   406 
   407 def RegisterIoCompletion(port, handle, key):
   408   rval = fnCreateIoCompletionPort(
   409     handle,
   410     port,
   411     key,
   412     1
   413   )
   414   if not rval:
   415     raise WinError()
   416   assert rval == port.value
   417 
   418 def GetQueuedCompletionStatus(port, wait):
   419   nbytes = ctypes.c_int()
   420   key = ctypes.c_size_t()
   421   poverlapped = ctypes.c_void_p()
   422 
   423   result = fnGetQueuedCompletionStatus(
   424     port,
   425     ctypes.byref(nbytes),
   426     ctypes.byref(key),
   427     ctypes.byref(poverlapped),
   428     wait
   429   )
   430 
   431   if not poverlapped.value:
   432     overlapped = None
   433   else:
   434     overlapped = ctypes.cast(poverlapped.value, LPOVERLAPPED)
   435     overlapped = overlapped.contents
   436 
   437   if not result:
   438     if not poverlapped:
   439       return False, 0, -1, overlapped
   440     return False, 0, key, overlapped
   441 
   442   return True, nbytes, key, overlapped
   443 
   444 
   445 def GetCurrentProcess():
   446   return handle_t(fnGetCurrentProcess())
   447 
   448 def GetStdHandle(n):
   449   rval = fnGetStdHandle(n)
   450   if rval == INVALID_HANDLE_VALUE:
   451     raise WinError()
   452   return handle_t(rval)
   453 
   454 def GetLastError():
   455   return fnGetLastError()
   456 
   457 def DuplicateHandle(handle):
   458   current = GetCurrentProcess()
   459   target = handle_t()
   460   rval = fnDuplicateHandle(
   461     current,
   462     handle,
   463     current,
   464     ctypes.pointer(target),
   465     0, # dwDesiredAccess (ignored)
   466     1, # bInheritHandle
   467     DUPLICATE_SAME_ACCESS
   468   )
   469   if not rval:
   470     raise WinError()
   471   return target
   472 
   473 class Process(object):
   474   def __init__(self, handle, pid):
   475     self.handle = handle
   476     self.pid = pid
   477     self.returncode = None
   478 
   479   def is_alive(self):
   480     if self.returncode != None:
   481       return False
   482 
   483     status = ctypes.c_int()
   484     rval = fnGetExitCodeProcess(self.handle, ctypes.byref(status))
   485     if not rval:
   486       raise WinError()
   487 
   488     if status.value == 259:
   489       return True
   490 
   491     self.returncode = status.value
   492     return False
   493 
   494   @classmethod
   495   def spawn(cls, channel):
   496     eval = 'from ambuild2.ipc.windows import child_main; child_main()'
   497     argv = [sys.executable, '-c', eval]
   498     argv += ['--name', '{0}'.format(channel.name)]
   499 
   500     # This is pretty hacky. Maybe we should just pass in the pipe name instead.
   501     argv += ['--pipe', '{0}'.format(channel.path)]
   502 
   503     cmdline = ' '.join(['"{0}"'.format(arg) for arg in argv])
   504     cmdline = util.str2b(cmdline)
   505     cmdline_buffer = ctypes.create_string_buffer(cmdline)
   506 
   507     startup_info = StartupInfo()
   508     startup_info.dwFlags |= STARTF_USESTDHANDLES
   509     startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE)
   510     startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE)
   511     startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE)
   512 
   513     proc_info = ProcessInformation()
   514 
   515     executable = sys_executable()
   516 
   517     rval = fnCreateProcessA(
   518       util.str2b(executable),
   519       cmdline_buffer,
   520       None,
   521       None,
   522       1,    # bInheritHandles
   523       0,    # dwCreationFlags
   524       None, # lpEnvironment
   525       None, # lpCurrentDirectory
   526       ctypes.pointer(startup_info),
   527       ctypes.pointer(proc_info)
   528     )
   529     if not rval:
   530       raise WinError()
   531 
   532     CloseHandle(proc_info.hThread)
   533 
   534     return cls(proc_info.hProcess, proc_info.dwProcessId)