Take the /bin/wifi lock in Waveguide scans.

This should reduce how often conman and waveguide race each other at
system boot time.

Also, fix locking:

* Only take the lock once per invocation of /bin/wifi (!!!)
* Try to release the lock when we're done. :)

Change-Id: Ib86e410f3eb23341ab97f92b4619fa099548dee2
diff --git a/waveguide/fake/wifi b/waveguide/fake/wifi
new file mode 100755
index 0000000..9270bc7
--- /dev/null
+++ b/waveguide/fake/wifi
@@ -0,0 +1,28 @@
+#!/bin/sh
+echo "fake/wifi:" "$@" >&2
+cd "$(dirname "$0")"
+
+if [ "$1" = "scan" ]; then
+  # TODO(willangley): pass the dev in an env var for tests, since looking it up
+  #   from the platform cannot be expected to work.
+  iw_scan="./iw dev wlan-22:22 scan"
+  while [ -n "$2" ]; do
+    shift
+    case "$1" in
+      -b)
+        shift
+        ;;
+      --scan-freq)
+        shift
+        iw_scan="$iw_scan freq $1"
+        ;;
+      --scan-*)
+        iw_scan="$iw_scan ${1#--scan-}"
+        ;;
+    esac
+  done
+  exec $iw_scan
+else
+  echo "fake/wifi: first arg ('$1') must be 'scan'" >&2
+  exit 99
+fi
diff --git a/waveguide/waveguide.py b/waveguide/waveguide.py
index b419ced..db2f9ed 100755
--- a/waveguide/waveguide.py
+++ b/waveguide/waveguide.py
@@ -93,6 +93,15 @@
 consensus_start = None
 
 
+def BandForFreq(freq):
+  if freq / 100 == 24:
+    return '2.4'
+  elif freq / 1000 == 5:
+    return '5'
+  else:
+    raise ValueError('Frequency %d is not in any known band', freq)
+
+
 def UpdateConsensus(new_uptime_ms, new_consensus_key):
   """Update the consensus key based on received multicast packets."""
   global consensus_key, consensus_start
@@ -324,9 +333,15 @@
               args=['iw', 'dev', self.vdevname, 'info'])
       # channel scan more than once in case we miss hearing a beacon
       for _ in range(opt.initial_scans):
+        if self.flags & wgdata.ApFlags.Can2G:
+          band = '2.4'
+        elif self.flags & wgdata.ApFlags.Can5G:
+          band = '5'
+
         RunProc(
             callback=self._ScanResults,
-            args=['iw', 'dev', self.vdevname, 'scan', 'ap-force', 'passive'])
+            args=['wifi', 'scan', '-b', band, '--scan-ap-force',
+                  '--scan-passive'])
         self.UpdateStationInfo()
       self.next_scan_time = now
       self.did_initial_scan = True
@@ -338,8 +353,9 @@
       self.Log('scanning %d MHz (%d/%d)', scan_freq, self.scan_idx + 1,
                len(self.allowed_freqs))
       RunProc(callback=self._ScanResults,
-              args=['iw', 'dev', self.vdevname, 'scan', 'freq', str(scan_freq),
-                    'ap-force', 'passive'])
+              args=['wifi', 'scan', '-b', BandForFreq(scan_freq),
+                    '--scan-freq', str(scan_freq), '--scan-ap-force',
+                    '--scan-passive'])
       chan_interval = opt.scan_interval / len(self.allowed_freqs)
       # Randomly fiddle with the timing to avoid permanent alignment with
       # other nodes also doing scans.  If we're perfectly aligned with
@@ -586,9 +602,10 @@
         disabled = (g.group(3) == 'disabled')
         self.Debug('phy freq=%d chan=%d disabled=%d', freq, chan, disabled)
         if not disabled:
-          if freq / 100 == 24:
+          band = BandForFreq(freq)
+          if band == '2.4':
             self.flags |= wgdata.ApFlags.Can2G
-          if freq / 1000 == 5:
+          elif band == '5':
             self.flags |= wgdata.ApFlags.Can5G
           self.allowed_freqs.add(freq)
           freq_to_chan[freq] = chan
diff --git a/wifi/iw.py b/wifi/iw.py
index db6590f..647e56c 100644
--- a/wifi/iw.py
+++ b/wifi/iw.py
@@ -53,8 +53,9 @@
   return subprocess.check_output(('iw', 'dev', interface, 'link'), **kwargs)
 
 
-def _scan(interface, **kwargs):
-  return subprocess.check_output(('iw', 'dev', interface, 'scan'), **kwargs)
+def _scan(interface, scan_args, **kwargs):
+  return subprocess.check_output(['iw', 'dev', interface, 'scan'] + scan_args,
+                                 **kwargs)
 
 
 _WIPHY_RE = re.compile(r'Wiphy (?P<phy>\S+)')
@@ -362,6 +363,6 @@
   return result
 
 
-def scan(interface):
+def scan(interface, scan_args):
   """Return 'iw scan' output for printing."""
-  return _scan(interface)
+  return _scan(interface, scan_args)
diff --git a/wifi/utils.py b/wifi/utils.py
index 7a90065..c3b237e 100644
--- a/wifi/utils.py
+++ b/wifi/utils.py
@@ -355,3 +355,7 @@
   subprocess.call(['lockfile-create', '--use-pid', '--retry', str(retries),
                    lockfile])
   signal.alarm(0)
+
+
+def unlock(lockfile):
+  subprocess.call(['lockfile-remove', lockfile])
diff --git a/wifi/wifi.py b/wifi/wifi.py
index fb38bcd..bd796a1 100755
--- a/wifi/wifi.py
+++ b/wifi/wifi.py
@@ -4,6 +4,7 @@
 
 from __future__ import print_function
 
+import atexit
 import glob
 import os
 import re
@@ -50,10 +51,14 @@
 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]
+scan-ap-force                     (Scan only) scan when in AP mode
+scan-passive                      (Scan only) do not probe, scan passively
+scan-freq=                        (Scan only) limit scan to specific frequencies.
 """
 
 _FINGERPRINTS_DIRECTORY = '/tmp/wifi/fingerprints'
 _LOCKFILE = '/tmp/wifi/wifi'
+lockfile_taken = False
 
 
 # pylint: disable=protected-access
@@ -513,7 +518,15 @@
   if interface is None:
     raise utils.BinWifiException('No client interface for band %s', band)
 
-  print(iw.scan(interface))
+  scan_args = []
+  if opt.scan_ap_force:
+    scan_args += ['ap-force']
+  if opt.scan_passive:
+    scan_args += ['passive']
+  if opt.scan_freq:
+    scan_args += ['freq', opt.scan_freq]
+
+  print(iw.scan(interface, scan_args))
 
   return True
 
@@ -976,6 +989,8 @@
   Raises:
     BinWifiException: On file write failures.
   """
+  global lockfile_taken
+
   optspec = _OPTSPEC_FORMAT.format(
       bin=__file__.split('/')[-1],
       ssid='%s_TestWifi' % subprocess.check_output(('serial')).strip())
@@ -983,8 +998,6 @@
   opt, _, extra = parser.parse(argv)
   stringify_options(opt)
 
-  utils.lock(_LOCKFILE, int(opt.lock_timeout))
-
   if not extra:
     parser.fatal('Must specify a command (see usage for details).')
     return 1
@@ -1008,6 +1021,11 @@
   except KeyError:
     parser.fatal('Unrecognized command %s' % extra[0])
 
+  if not lockfile_taken:
+    utils.lock(_LOCKFILE, int(opt.lock_timeout))
+    atexit.register(utils.unlock, _LOCKFILE)
+    lockfile_taken = True
+
   success = function(opt)
 
   if success: