Merge "wifiblaster: allow measurements to be triggered."
diff --git a/wifi/utils.py b/wifi/utils.py
index e665654..3d53428 100644
--- a/wifi/utils.py
+++ b/wifi/utils.py
@@ -5,6 +5,7 @@
 from __future__ import print_function
 
 import collections
+import fcntl
 import os
 import re
 import subprocess
@@ -300,3 +301,20 @@
     raise BinWifiException('PSK is not of a valid length: %d', len(psk))
 
   return psk
+
+
+def lock(lockfile, timeout_sec):
+  for i in range(timeout_sec):
+    try:
+      fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
+      return
+    except IOError:
+      time.sleep(1)
+      log('Trying to obtain lock %s for %s seconds', lockfile.name, i + 1)
+
+  raise BinWifiException('Failed to obtain lock %s after %d seconds',
+                         lockfile.name, timeout_sec)
+
+
+def unlock(lockfile):
+  fcntl.lockf(lockfile, fcntl.LOCK_UN)
diff --git a/wifi/utils_test.py b/wifi/utils_test.py
index be91859..deb737b 100755
--- a/wifi/utils_test.py
+++ b/wifi/utils_test.py
@@ -3,7 +3,11 @@
 """Tests for utils.py."""
 
 import collections
+import multiprocessing
 import os
+import shutil
+import tempfile
+import time
 
 import utils
 from wvtest import wvtest
@@ -123,5 +127,67 @@
   wvtest.WVPASSEQ('g' * 63, utils.validate_and_sanitize_psk('g' * 63))
 
 
+@wvtest.wvtest
+def lock_and_unlock_test():
+  """Test utils.lock and utils.unlock."""
+  try:
+    temp_dir = tempfile.mkdtemp()
+    lockfile = open(os.path.join(temp_dir, 'test.lock'), 'w')
+
+    def lock_until_queue_nonempty(q, timeout_sec):
+      try:
+        utils.lock(lockfile, timeout_sec)
+      except utils.BinWifiException:
+        q.put('timed out')
+        return
+      q.get()
+      q.put('releasing')
+      utils.unlock(lockfile)
+
+    q1 = multiprocessing.Queue()
+    p1 = multiprocessing.Process(target=lock_until_queue_nonempty, args=(q1, 1))
+
+    q2 = multiprocessing.Queue()
+    p2 = multiprocessing.Process(target=lock_until_queue_nonempty, args=(q2, 3))
+
+    p1.start()
+    p2.start()
+
+    wvtest.WVPASS(q1.empty())
+    wvtest.WVPASS(q2.empty())
+
+    q1.put('release')
+    # Wait for p1 to take this off the queue and relase the lock.
+    time.sleep(0.5)
+    wvtest.WVFAIL(q1.empty())
+    wvtest.WVPASSEQ(q1.get(), 'releasing')
+    wvtest.WVPASS(q2.empty())
+
+    q2.put('release')
+    # Wait for p2 to take this off the queue and relase the lock.
+    time.sleep(0.5)
+    wvtest.WVPASS(q1.empty())
+    wvtest.WVFAIL(q2.empty())
+    wvtest.WVPASSEQ(q2.get(), 'releasing')
+
+    # Now test that the timeout works.
+    q3 = multiprocessing.Queue()
+    p3 = multiprocessing.Process(target=lock_until_queue_nonempty, args=(q3, 1))
+
+    q4 = multiprocessing.Queue()
+    p4 = multiprocessing.Process(target=lock_until_queue_nonempty, args=(q4, 1))
+
+    p3.start()
+    p4.start()
+    wvtest.WVPASSEQ(q4.get(), 'timed out')
+
+    q3.put('release')
+
+  finally:
+    for p in [p1, p2, p3, p4]:
+      p.join()
+    shutil.rmtree(temp_dir)
+
+
 if __name__ == '__main__':
   wvtest.wvtest_main()
diff --git a/wifi/wifi.py b/wifi/wifi.py
index c4fde30..1071031 100755
--- a/wifi/wifi.py
+++ b/wifi/wifi.py
@@ -49,9 +49,11 @@
 Y,yottasecond-timeouts            Don't rotate any keys: PTK, GTK, or GMK
 P,persist                         For set commands, persist options so we can restore them with 'wifi restore'.  For stop commands, remove persisted options.
 S,interface-suffix=               Interface suffix []
+lock-timeout=                     How long, in seconds, to wait for another /bin/wifi process to finish before giving up. [60]
 """
 
 _FINGERPRINTS_DIRECTORY = '/tmp/wifi/fingerprints'
+_LOCKFILE = '/tmp/wifi/wifi.lock'
 
 
 # pylint: disable=protected-access
@@ -990,31 +992,37 @@
     opt.band = '2.4'
 
   try:
-    function = {
-        'set': set_wifi,
-        'stop': stop_wifi,
-        'off': stop_wifi,
-        'restore': restore_wifi,
-        'show': show_wifi,
-        'setclient': set_client_wifi,
-        'stopclient': stop_client_wifi,
-        'stopap': stop_ap_wifi,
-        'scan': scan_wifi,
-    }[extra[0]]
-  except KeyError:
-    parser.fatal('Unrecognized command %s' % extra[0])
-  success = function(opt)
+    lockfile = open(_LOCKFILE, 'w')
+    try:
+      utils.lock(lockfile, int(opt.lock_timeout))
+      function = {
+          'set': set_wifi,
+          'stop': stop_wifi,
+          'off': stop_wifi,
+          'restore': restore_wifi,
+          'show': show_wifi,
+          'setclient': set_client_wifi,
+          'stopclient': stop_client_wifi,
+          'stopap': stop_ap_wifi,
+          'scan': scan_wifi,
+      }[extra[0]]
+    except KeyError:
+      parser.fatal('Unrecognized command %s' % extra[0])
 
-  if success:
-    if extra[0] in ('set', 'setclient'):
-      program = 'hostapd' if extra[0] == 'set' else 'wpa_supplicant'
-      if opt.persist:
-        phy = iw.find_phy(opt.band, opt.channel)
-        for band in iw.phy_bands(phy):
-          if band != opt.band:
-            persist.delete_options(program, band)
-        persist.save_options(program, opt.band, argv)
-      persist.save_options(program, opt.band, argv, tmp=True)
+    success = function(opt)
+
+    if success:
+      if extra[0] in ('set', 'setclient'):
+        program = 'hostapd' if extra[0] == 'set' else 'wpa_supplicant'
+        if opt.persist:
+          phy = iw.find_phy(opt.band, opt.channel)
+          for band in iw.phy_bands(phy):
+            if band != opt.band:
+              persist.delete_options(program, band)
+          persist.save_options(program, opt.band, argv)
+        persist.save_options(program, opt.band, argv, tmp=True)
+  finally:
+    utils.unlock(lockfile)
 
   return success