xpra icon
Bug tracker and wiki

Ticket #389: namedpipes.patch

File namedpipes.patch, 12.7 KB (added by Antoine Martin, 5 years ago)

work in progress named pipes support

  • xpra/platform/win32/namedpipes/__init__.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2015 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
  • xpra/platform/win32/namedpipes/client.py

     
     1#!/usr/bin/env python
     2# This file is part of Xpra.
     3# Copyright (c) 2009-2013 Antoine Martin <antoine@nagafix.co.uk>
     4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     5# later version. See the file COPYING for details.
     6
     7#@PydevCodeAnalysisIgnore
     8
     9import sys
     10
     11from xpra.log import Logger
     12log = Logger("shadow", "win32")
     13
     14import winerror                                                                                         #@UnresolvedImport
     15from win32pipe import CallNamedPipe, NMPWAIT_NOWAIT                     #@UnresolvedImport
     16from win32api import Sleep, error                                                       #@UnresolvedImport
     17
     18ERROR_PIPE_ENDED = 109
     19ERROR_FILE_NOT_FOUND = 2
     20
     21class LocalNamedPipeClient:
     22        def __init__(self, pipe_name, standalone=True):
     23                self.pipe_name = pipe_name
     24                self.messages = None
     25                self.exit_code = None
     26                self.timedout = False
     27                self.testing = False
     28
     29        def test_socket(self, message="FIXME!"):
     30                try:
     31                        self.testing = True
     32                        code = self.send([message])
     33                finally:
     34                        self.testing = False
     35                log("exit code from sending '%s' to named pipe: %s", message, code)
     36                return code == 0
     37
     38        def stop(self):
     39                self.exit_code = 0
     40
     41        def send(self, msgs):
     42                log("send(%s)", msgs)
     43                self.exit_code = None
     44                self.messages = msgs
     45                while self.exit_code is None:
     46                        self.send_first_message()
     47                return self.exit_code
     48
     49
     50        def CallPipe(self, fn, args):
     51                retryCount = 0
     52                while not self.timedout and retryCount < 8:   # Keep looping until user cancels.
     53                        log("CallPipe(%s, %s) retryCount=%s", fn, args, retryCount)
     54                        retryCount = retryCount + 1
     55                        try:
     56                                return apply(fn, args)
     57                        except error as e:
     58                                log("CallPipe(%s, %s) %s", fn, args, e)
     59                                rc, _, _ = e
     60                                if rc==winerror.ERROR_PIPE_BUSY:
     61                                        log.error("Pipe is busy, waiting")
     62                                        Sleep(5000)
     63                                        continue
     64                                else:
     65                                        raise e
     66                raise RuntimeError("Could not make a connection to the server")
     67
     68        def send_first_message(self):
     69                if len(self.messages)==0:
     70                        log("no more messages!")
     71                        self.stop()
     72                        return
     73                msg = self.messages.pop()
     74                log("sending '%s'" % msg)
     75                try:
     76                        data = self.CallPipe(CallNamedPipe, (self.pipe_name, msg, 256, NMPWAIT_NOWAIT)) #NMPWAIT_WAIT_FOREVER))
     77                        self.lineReceived(data)
     78                except error as e:
     79                        log("pipe error", exc_info=True)
     80                        error_code = e[0]
     81                        if error_code in [ERROR_FILE_NOT_FOUND, ERROR_PIPE_ENDED]:
     82                                if self.testing:
     83                                        log("connection failed/not found: %s ", e)
     84                                else:
     85                                        log.error("connection failed/not found: %s ", e)
     86                                self.exit_code = 1
     87                        else:
     88                                log.error("unknown error: %s", e)
     89                                self.exit_code = 2
     90
     91
     92        def lineReceived(self, line):
     93                log("lineReceived(%s)", line)
     94
     95
     96def main():
     97        import os
     98        from xpra.platform import program_context
     99        from xpra.platform.win32 import console_event_catcher
     100        from xpra.log import enable_color
     101        PIPE_NAME = os.environ.get("XPRA_NAMEDPIPE", "\\\\.\\pipe\\Xpra")
     102        if not PIPE_NAME.find("\\")>=0:
     103                PIPE_NAME = "\\\\.\\pipe\\"+PIPE_NAME
     104        log.info("using named pipe %s", PIPE_NAME)
     105        log.enable_debug()
     106        with program_context("Named Pipe Listener"):
     107                enable_color()
     108                try:
     109                        client = LocalNamedPipeClient(PIPE_NAME)
     110                        def stop(*args):
     111                                client.stop
     112                        with console_event_catcher(stop):
     113                                client.send(list(reversed(sys.argv[1:])))
     114                except Exception:
     115                        log.error("stopped", exc_info=True)
     116
     117if __name__ == "__main__":
     118        main()
  • xpra/platform/win32/namedpipes/common.py

     
     1#!/usr/bin/env python
     2# This file is part of Xpra.
     3# Copyright (c) 2009-2013 Antoine Martin <antoine@nagafix.co.uk>
     4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     5# later version. See the file COPYING for details.
     6
     7#@PydevCodeAnalysisIgnore
     8
     9from threading import Thread
     10
     11from xpra.log import Logger
     12log = Logger("network", "win32")
     13
     14import win32con, winerror                       #@UnresolvedImport
     15from win32file import ReadFile, WriteFile, CloseHandle          #@UnresolvedImport
     16from win32pipe import DisconnectNamedPipe                                       #@UnresolvedImport
     17from win32api import GetCurrentProcess, DuplicateHandle, GetCurrentThread, error                #@UnresolvedImport
     18
     19
     20def ApplyIgnoreError(fn, *args):
     21        try:
     22                return apply(fn, args)
     23        except error: # Ignore win32api errors.
     24                return None
     25
     26class PipeHandler(Thread):
     27        def __init__(self, name, pipeHandle):
     28                Thread.__init__(self, name=name)
     29                self.setDaemon(True)
     30                self.pipeHandle = pipeHandle
     31                self.exit_loop = False
     32
     33        def run(self):
     34                log.info("%s() started for %s", self.getName(), self.pipeHandle)
     35                try:
     36                        procHandle = GetCurrentProcess()
     37                        self.thread_handle = DuplicateHandle(procHandle, GetCurrentThread(), procHandle, 0, 0, win32con.DUPLICATE_SAME_ACCESS)
     38                except error:
     39                        log.error("Error setting up pipe %s", self.pipeHandle)
     40                        return
     41                try:
     42                        return self.do_run()
     43                except error:
     44                        log.error("Error on pipe %s", self.pipeHandle, exc_info=True)
     45                finally:
     46                        ApplyIgnoreError(DisconnectNamedPipe, self.pipeHandle)
     47                        ApplyIgnoreError(CloseHandle, self.pipeHandle)
     48
     49        def stop(self):
     50                self.exit_loop = True
     51
     52        def _send(self, msg):
     53                # A secure service would handle (and ignore!) errors writing to the
     54                # pipe, but for the sake of this demo we dont (if only to see what errors
     55                # we can get when our clients break at strange times :-)
     56                WriteFile(self.pipeHandle, msg)
     57
     58
     59class PipeReader(PipeHandler):
     60        def __init__(self, pipeHandle, packet_handler):
     61                PipeHandler.__init__(self, "PipeReader", pipeHandle)
     62                self.packet_handler = packet_handler
     63
     64        def do_run(self):
     65                log("do_run()")
     66                try:
     67                        # Create a loop, reading large data.  If we knew the data stream
     68                        # was small, a simple ReadFile would do.
     69                        while not self.exit_loop:
     70                                d = ''
     71                                hr = winerror.ERROR_MORE_DATA
     72                                while hr==winerror.ERROR_MORE_DATA:
     73                                        hr, thisd = ReadFile(self.pipeHandle, 256)
     74                                        d = d + thisd
     75                                log("read '%s'", d)
     76                                self.packet_handler(d)
     77                                #self._send("")
     78                        return True
     79                except error:
     80                        # Client disconnection - do nothing
     81                        return False
     82
     83
     84class PipeWriter(PipeHandler):
     85        def __init__(self, pipeHandle):
     86                PipeHandler.__init__(self, "PipeWriter", pipeHandle)
     87                from Queue import Queue
     88                self.packet_queue = Queue()     #maxlen=1
     89
     90        def do_run(self):
     91                while not self.exit_loop:
     92                        msg = self.packet_queue.get()
     93                        self._send(msg)
     94
     95        def send(self, msg):
     96                self.packet_queue.put(msg)
  • xpra/platform/win32/namedpipes/listener.py

     
     1#!/usr/bin/env python
     2# This file is part of Xpra.
     3# Copyright (c) 2009-2013 Antoine Martin <antoine@nagafix.co.uk>
     4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     5# later version. See the file COPYING for details.
     6
     7#@PydevCodeAnalysisIgnore
     8
     9from threading import Thread
     10
     11from xpra.log import Logger
     12log = Logger("network", "win32")
     13
     14from xpra.platform.win32.namedpipes.common import PipeReader, PipeWriter
     15
     16import pywintypes, winerror                     #@UnresolvedImport
     17from win32event import CreateEvent, SetEvent, WaitForMultipleObjects, WAIT_OBJECT_0, INFINITE                                           #@UnresolvedImport
     18from win32file import CloseHandle, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_FLAG_OVERLAPPED, FILE_ALL_ACCESS         #@UnresolvedImport
     19from win32pipe import CreateNamedPipe, ConnectNamedPipe, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE, PIPE_READMODE_BYTE, PIPE_UNLIMITED_INSTANCES    #@UnresolvedImport
     20from win32api import error                      #@UnresolvedImport
     21from ntsecuritycon import SECURITY_CREATOR_SID_AUTHORITY, SECURITY_WORLD_SID_AUTHORITY, SECURITY_WORLD_RID, SECURITY_CREATOR_OWNER_RID  #@UnresolvedImport
     22
     23
     24class NamedPipeListener(Thread):
     25        def __init__(self, pipe_name, new_connection_cb):
     26                Thread.__init__(self, name="NamedPipeListener")
     27                self.pipe_name = pipe_name
     28                self.new_connection_cb = new_connection_cb
     29
     30                self.hWaitStop = CreateEvent(None, 0, 0, None)
     31                self.overlapped = pywintypes.OVERLAPPED()
     32                self.overlapped.hEvent = CreateEvent(None,0,0,None)
     33                self.exit_loop = False
     34                self.terminated = False
     35
     36        def stop(self):
     37                log("%s.stop()", self)
     38                if self.exit_loop:
     39                        return
     40                self.exit_loop = True
     41                self.hWaitStop.close()          #this should do it... (but using the client seems to work better..)
     42                #somehow fails to stop cleanly, try to force it if the socket is still listening:
     43                from xpra.platform.win32.namedpipes.client import LocalNamedPipeClient
     44                try:
     45                        client = LocalNamedPipeClient(self.pipe_name, False)
     46                        log("stopping socket listener using client=%s", client)
     47                        COMMAND_EXIT = "FIXME!"
     48                        if client.test_socket(COMMAND_EXIT):
     49                                log("successfully sent '%s' to existing instance" % COMMAND_EXIT)
     50                except Exception as e:
     51                        log.error("Error using client to stop named pipe: %s", e)
     52
     53        def CreatePipeSecurityObject(self):
     54                # Create a security object giving World read/write access,
     55                # but only "Owner" modify access.
     56                sa = pywintypes.SECURITY_ATTRIBUTES()
     57                sidEveryone = pywintypes.SID()
     58                sidEveryone.Initialize(SECURITY_WORLD_SID_AUTHORITY,1)
     59                sidEveryone.SetSubAuthority(0, SECURITY_WORLD_RID)
     60                sidCreator = pywintypes.SID()
     61                sidCreator.Initialize(SECURITY_CREATOR_SID_AUTHORITY,1)
     62                sidCreator.SetSubAuthority(0, SECURITY_CREATOR_OWNER_RID)
     63                acl = pywintypes.ACL()
     64                acl.AddAccessAllowedAce(FILE_GENERIC_READ|FILE_GENERIC_WRITE, sidEveryone)
     65                acl.AddAccessAllowedAce(FILE_ALL_ACCESS, sidCreator)
     66                sa.SetSecurityDescriptorDacl(1, acl, 0)
     67                return sa
     68
     69        def run(self):
     70                try:
     71                        self.do_run()
     72                except Exception:
     73                        log.error("run()", exc_info=True)
     74                finally:
     75                        self.terminated = True
     76
     77        def do_run(self):
     78                while not self.exit_loop:
     79                        sa = self.CreatePipeSecurityObject()
     80                        pipeHandle = CreateNamedPipe(self.pipe_name,
     81                                        PIPE_ACCESS_DUPLEX| FILE_FLAG_OVERLAPPED,
     82                                        PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE,
     83                                        PIPE_UNLIMITED_INSTANCES,          # max instances
     84                                        0, 0, 6000, sa)
     85                        if self.exit_loop:
     86                                break
     87                        try:
     88                                hr = ConnectNamedPipe(pipeHandle, self.overlapped)
     89                        except error as e:
     90                                log.error("error connecting pipe: %s", e)
     91                                CloseHandle(pipeHandle)
     92                                break
     93                        log("connected to pipe")
     94                        if self.exit_loop:
     95                                break
     96                        if hr==winerror.ERROR_PIPE_CONNECTED:
     97                                # Client is already connected - signal event
     98                                SetEvent(self.overlapped.hEvent)
     99                        rc = WaitForMultipleObjects((self.hWaitStop, self.overlapped.hEvent), 0, INFINITE)
     100                        log("wait ended with rc=%s, exit_loop=%s", rc, self.exit_loop)
     101                        if rc==WAIT_OBJECT_0 or self.exit_loop:
     102                                # Stop event
     103                                break
     104                        else:
     105                                self.new_connection_cb(self, pipeHandle)
     106
     107
     108def main():
     109        import os
     110        from xpra.platform import program_context
     111        from xpra.platform.win32 import console_event_catcher
     112        from xpra.log import enable_color
     113        PIPE_NAME = os.environ.get("XPRA_NAMEDPIPE", "\\\\.\\pipe\\Xpra")
     114        if not PIPE_NAME.find("\\")>=0:
     115                PIPE_NAME = "\\\\.\\pipe\\"+PIPE_NAME
     116        log.info("using named pipe %s", PIPE_NAME)
     117        log.enable_debug()
     118        with program_context("Named Pipe Listener"):
     119                enable_color()
     120                listener = None
     121                try:
     122                        def new_connection(listener, pipeHandle):
     123                                # Pipe event - spawn thread to deal with it.
     124                                listener.writer = PipeWriter(pipeHandle)
     125                                listener.writer.start()
     126                                def packet_handler(data):
     127                                        log.info("packet_handler(%s)", data)
     128                                        listener.writer.send(data)
     129                                listener.reader = PipeReader(pipeHandle, packet_handler)
     130                                listener.reader.start()
     131                        listener = NamedPipeListener(PIPE_NAME, new_connection)
     132                        listener.reader = None
     133                        listener.writer = None
     134                        def stop(*args):
     135                                log.warn("stop%s", args)
     136                                listener.stop()
     137                                for x in (listener.reader, listener.writer):
     138                                        if x:
     139                                                x.stop()
     140                        import signal
     141                        signal.signal(signal.SIGINT, stop)
     142                        signal.signal(signal.SIGTERM, stop)
     143                        with console_event_catcher(stop):
     144                                listener.start()
     145                except Exception:
     146                        log.error("%s stopped", listener, exc_info=True)
     147                        if listener:
     148                                listener.stop()
     149                if listener:
     150                        listener.join()
     151                import sys
     152                sys.exit(0)
     153
     154if __name__ == "__main__":
     155        main()