overlord: Darwin/XNU compatibility

Go version:
Separate system related utils to sysutils_{GOOS}.go to provide operating
system compatibility for Linux and MacOS X.

Python version:
Use platform.system() to switch between Linux and Darwin
implementations.

BUG=chromium:585733
TEST=`go/src/overlord/test/overlord_e2e_unittest.py`

Change-Id: I0cde3c0996191ac59dc5c5f6a0b4907b53c0dff8
Reviewed-on: https://chromium-review.googlesource.com/327029
Commit-Ready: Wei-Ning Huang <wnhuang@chromium.org>
Tested-by: Wei-Ning Huang <wnhuang@chromium.org>
Reviewed-by: Wei-Han Chen <stimim@chromium.org>
diff --git a/py/tools/ghost.py b/py/tools/ghost.py
index c375d17..0a3e6fb 100755
--- a/py/tools/ghost.py
+++ b/py/tools/ghost.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python -u
+#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 #
 # Copyright 2015 The Chromium OS Authors. All rights reserved.
@@ -7,11 +7,14 @@
 
 import argparse
 import contextlib
+import ctypes
+import ctypes.util
 import fcntl
 import hashlib
 import json
 import logging
 import os
+import platform
 import Queue
 import re
 import select
@@ -195,6 +198,7 @@
     if mode == Ghost.FILE:
       assert file_op is not None
 
+    self._platform = platform.system()
     self._overlord_addrs = overlord_addrs
     self._connected_addr = None
     self._tls_settings = tls_settings
@@ -322,13 +326,14 @@
 
   def CloseSockets(self):
     # Close sockets opened by parent process, since we don't use it anymore.
-    for fd in os.listdir('/proc/self/fd/'):
-      try:
-        real_fd = os.readlink('/proc/self/fd/%s' % fd)
-        if real_fd.startswith('socket'):
-          os.close(int(fd))
-      except Exception:
-        pass
+    if self._platform == 'Linux':
+      for fd in os.listdir('/proc/self/fd/'):
+        try:
+          real_fd = os.readlink('/proc/self/fd/%s' % fd)
+          if real_fd.startswith('socket'):
+            os.close(int(fd))
+        except Exception:
+          pass
 
   def SpawnGhost(self, mode, sid=None, terminal_sid=None, tty_device=None,
                  command=None, file_op=None, port=None):
@@ -357,22 +362,31 @@
     return int(time.time())
 
   def GetGateWayIP(self):
-    with open('/proc/net/route', 'r') as f:
-      lines = f.readlines()
+    if self._platform == 'Darwin':
+      output = subprocess.check_output(['route', '-n', 'get', 'default'])
+      ret = re.search('gateway: (.*)', output)
+      if ret:
+        return [ret.group(1)]
+    elif self._platform == 'Linux':
+      with open('/proc/net/route', 'r') as f:
+        lines = f.readlines()
 
-    ips = []
-    for line in lines:
-      parts = line.split('\t')
-      if parts[2] == '00000000':
-        continue
+      ips = []
+      for line in lines:
+        parts = line.split('\t')
+        if parts[2] == '00000000':
+          continue
 
-      try:
-        h = parts[2].decode('hex')
-        ips.append('%d.%d.%d.%d' % tuple(ord(x) for x in reversed(h)))
-      except TypeError:
-        pass
+        try:
+          h = parts[2].decode('hex')
+          ips.append('%d.%d.%d.%d' % tuple(ord(x) for x in reversed(h)))
+        except TypeError:
+          pass
 
-    return ips
+      return ips
+    else:
+      logging.warning('GetGateWayIP: unsupported platform')
+      return []
 
   def GetShopfloorIP(self):
     try:
@@ -390,18 +404,30 @@
   def GetMachineID(self):
     """Generates machine-dependent ID string for a machine.
     There are many ways to generate a machine ID:
-    1. factory device_id
-    2. factory device-data
-    3. /sys/class/dmi/id/product_uuid (only available on intel machines)
-    4. MAC address
-    We follow the listed order to generate machine ID, and fallback to the next
-    alternative if the previous doesn't work.
+    Linux:
+      1. factory device_id
+      2. factory device-data
+      3. /sys/class/dmi/id/product_uuid (only available on intel machines)
+      4. MAC address
+      We follow the listed order to generate machine ID, and fallback to the
+      next alternative if the previous doesn't work.
+
+    Darwin:
+      All Darwin system should have the IOPlatformSerialNumber attribute.
     """
     if self._mid == Ghost.RANDOM_MID:
       return str(uuid.uuid4())
     elif self._mid:
       return self._mid
 
+    # Darwin
+    if self._platform == 'Darwin':
+      output = subprocess.check_output(['ioreg', '-rd1', '-c',
+                                        'IOPlatformExpertDevice'])
+      ret = re.search('"IOPlatformSerialNumber" = "(.*)"', output)
+      if ret:
+        return ret.group(1)
+
     # Try factory device id
     try:
       import factory_common  # pylint: disable=W0612
@@ -447,6 +473,24 @@
 
     raise RuntimeError('can\'t generate machine ID')
 
+  def GetProcessWorkingDirectory(self, pid):
+    if self._platform == 'Linux':
+      return os.readlink('/proc/%d/cwd' % pid)
+    elif self._platform == 'Darwin':
+      PROC_PIDVNODEPATHINFO = 9
+      proc_vnodepathinfo_size = 2352
+      vid_path_offset = 152
+
+      proc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('libproc'))
+      buf = ctypes.create_string_buffer('\0' * proc_vnodepathinfo_size)
+      proc.proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0,
+                        ctypes.byref(buf), proc_vnodepathinfo_size)
+      buf = buf.raw[vid_path_offset:]
+      n = buf.index('\0')
+      return buf[:n]
+    else:
+      raise RuntimeError('GetProcessWorkingDirectory: unsupported platform')
+
   def Reset(self):
     """Reset state and clear request handlers."""
     if self._sock is not None:
@@ -499,7 +543,7 @@
         pid, fd = os.forkpty()
 
         if pid == 0:
-          ttyname = os.readlink('/proc/%d/fd/0' % os.getpid())
+          ttyname = os.ttyname(sys.stdout.fileno())
           try:
             server = GhostRPCServer()
             server.RegisterTTY(self._session_id, ttyname)
@@ -801,7 +845,10 @@
       if params.has_key('terminal_sid'):
         pid = self._terminal_sid_to_pid.get(params['terminal_sid'], None)
         if pid:
-          target_dir = os.readlink('/proc/%d/cwd' % pid)
+          try:
+            target_dir = self.GetProcessWorkingDirectory(pid)
+          except Exception as e:
+            logging.error(e)
 
       dest_path = os.path.join(target_dir, filename)
 
diff --git a/py/tools/stream_camera.py b/py/tools/stream_camera.py
index 5c8ced3..6b220f3 100755
--- a/py/tools/stream_camera.py
+++ b/py/tools/stream_camera.py
@@ -10,6 +10,7 @@
 import argparse
 import atexit
 import BaseHTTPServer
+import platform
 import struct
 import subprocess
 import sys
@@ -51,12 +52,23 @@
   Returns:
     handler for the subprocess
   """
-  return subprocess.Popen(
-      'sleep 1; '
-      'ffmpeg -an -s %s -f video4linux2 -i %s -f mpeg1video -b %s -r %d '
-      'http://localhost:%s/' % (args.size, args.device, args.bitrate,
-                                args.framerate, _SERVER_PORT),
-      stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+  system = platform.system()
+
+  if system == "Linux":
+    return subprocess.Popen(
+        'sleep 1; '
+        'ffmpeg -an -s %s -f video4linux2 -i %s -f mpeg1video -b:v %s -r %d '
+        'http://localhost:%s/' % (args.size, args.device, args.bitrate,
+                                  args.framerate, _SERVER_PORT),
+        stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+  elif system == "Darwin":
+    return subprocess.Popen(
+        'sleep 1; '
+        'ffmpeg -an -f avfoundation -video_size %s -framerate %d -i %s -b:v %s '
+        '-f mpeg1video -r %d http://localhost:%s/' %
+        (args.size, args.framerate, args.device, args.bitrate, args.framerate,
+         _SERVER_PORT),
+        stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
 
 
 def StopCaptureProcess(handler):
@@ -80,7 +92,7 @@
   atexit.register(StopCaptureProcess, handler)
 
   server = BaseHTTPServer.HTTPServer(
-      ('0.0.0.0', _SERVER_PORT), ForwardToStdoutRequestHandler)
+      ('localhost', _SERVER_PORT), ForwardToStdoutRequestHandler)
   server.size = args.size
   server.serve_forever()