scripts: zephyr_flash_debug: teach runners a client/server abstraction

Several debugging scripts run setsid before executing a server
process, then run GDB with SIGINT ignored.

Relying on setsid is not portable. Add a popen_ignore_int() helper
that provides a portable alternative, and provide a generic
run_server_and_client() in ZephyrBinaryRunner which uses it to
abstract the pattern.

Subsequent patches will use this to implement the 'debug' command.

Signed-off-by: Marti Bolivar <marti.bolivar@linaro.org>
diff --git a/scripts/support/zephyr_flash_debug.py b/scripts/support/zephyr_flash_debug.py
index 2228d18..19c1ae7 100755
--- a/scripts/support/zephyr_flash_debug.py
+++ b/scripts/support/zephyr_flash_debug.py
@@ -51,6 +51,25 @@
     return subprocess.check_output(cmd)
 
 
+def popen_ignore_int(cmd, debug):
+    '''Spawn a child command, ensuring it ignores SIGINT.
+
+    The returned subprocess.Popen object must be manually terminated.'''
+    cflags = 0
+    preexec = None
+    system = platform.system()
+
+    if system == 'Windows':
+        cflags |= subprocess.CREATE_NEW_PROCESS_GROUP
+    elif system in {'Linux', 'Darwin'}:
+        preexec = os.setsid
+
+    if debug:
+        print(' '.join(cmd))
+
+    return subprocess.Popen(cmd, creationflags=cflags, preexec_fn=preexec)
+
+
 class ZephyrBinaryRunner(abc.ABC):
     '''Abstract superclass for binary runners (flashers, debuggers).
 
@@ -140,6 +159,25 @@
 
         In case of an unsupported command, raise a ValueError.'''
 
+    def run_server_and_client(self, server, client):
+        '''Run a server that ignores SIGINT, and a client that handles it.
+
+        This routine portably:
+
+        - creates a Popen object for the `server' command which ignores SIGINT
+        - runs `client' in a subprocess while temporarily ignoring SIGINT
+        - cleans up the server after the client exits.
+
+        It's useful to e.g. open a GDB server and client.'''
+        server_proc = popen_ignore_int(server, self.debug)
+        previous = signal.signal(signal.SIGINT, signal.SIG_IGN)
+        try:
+            check_call(client, self.debug)
+        finally:
+            signal.signal(signal.SIGINT, previous)
+            server_proc.terminate()
+            server_proc.wait()
+
 
 DEFAULT_ARC_TCL_PORT = 6333
 DEFAULT_ARC_TELNET_PORT = 4444