Add crdev utility to platform/dev

BUG=chromium-os:21535
TEST=none

Change-Id: I39f7c73535a10891984c3951156e13a0ba18b431
Reviewed-on: https://gerrit.chromium.org/gerrit/11146
Tested-by: Steven Bennetts <[email protected]>
Reviewed-by: Chris Sosa <[email protected]>
Commit-Ready: Steven Bennetts <[email protected]>
diff --git a/crdev b/crdev
new file mode 100644
index 0000000..1656cce
--- /dev/null
+++ b/crdev
@@ -0,0 +1,462 @@
+#!/usr/bin/env python
+# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""This script provides a suite of utility functions for the chroot.
+"""
+
+import optparse
+import os
+import shutil
+import subprocess
+import sys
+
+_PROG = "crdev"
+_SSH_KEY_FILEPATH = os.path.expanduser("~/.ssh/id_rsa")
+_PUB_KEY_FILENAME = "~/.ssh/id_rsa.pub"
+_PUB_KEY_FILEPATH = os.path.expanduser(_PUB_KEY_FILENAME)
+_AUTH_KEYS_FILEPATH = os.path.expanduser("~/.ssh/authorized_keys")
+_CHROME_MOUNT_PATH = "/tmp/chrome"
+_DEFAULT_CHROME_DIR = "~/chromeos/chromium"
+
+RAN_SUDO = False
+
+###############################################################################
+# Utility commands
+
+def _Confirm(message):
+  """Request confirmation and return True/False."""
+  reply = raw_input(message + " [y/N] ")
+  if reply == "y" or reply == "Y":
+    return True
+  return False
+
+
+def _CheckOverwriteFile(filepath):
+  """Check for a file and request confirmation if it exists."""
+  if os.path.isfile(filepath):
+    if not _Confirm(filepath + " already exists. Overwrite?"):
+      return False
+  return True
+
+
+def _WriteFile(filepath, contents):
+  """Write string |contents| to |filepath|."""
+  try:
+    print "Writing to: ", filepath
+    with open(filepath,"w") as f_1:
+      f_1.write(contents)
+  except EnvironmentError:
+    print >> sys.stderr, "Failed to write to file: ", filepath
+    return False
+
+  return True
+
+
+def _Sudo():
+  """Request sudo access with message."""
+  global RAN_SUDO
+  if not RAN_SUDO:
+    print "Executing: sudo -v"
+  try:
+    subprocess.call(["sudo", "-v"])
+  except EnvironmentError:
+    print >> sys.stderr, "Failed to run sudo"
+    return False
+  RAN_SUDO = True
+  return True
+
+
+def _RunCommand(args):
+  """Pass |args| to subprocess.call() and check the result."""
+  cmd = ''.join([x + " " for x in args])
+  try:
+    if args[0] == "sudo":
+      if _Sudo() == False:
+        return False
+    print "Executing: ", cmd
+    retcode = subprocess.call(args)
+  except EnvironmentError:
+    print >> sys.stderr, "Failed to run command: ", cmd
+    return False
+  else:
+    if retcode != 0:
+      print >> sys.stderr, "Error: ", retcode, ", running command: ", cmd
+      return False
+    return True
+
+
+def _GetCommandOutput(args):
+  """Pass |args| to subprocess.Popen and return the output."""
+  cmd = ''.join([x + " " for x in args])
+  try:
+    proc = subprocess.Popen(args, stdout=subprocess.PIPE)
+    result = proc.communicate()[0]
+  except EnvironmentError:
+    print >> sys.stderr, "Failed to run command: ", cmd
+    return None
+  result = result.rstrip('\n')
+  return result
+
+
+def _MakeDirectory(dirpath):
+  """Create directory |dirpath| if it does not exist."""
+  if not os.path.isdir(dirpath):
+    try:
+      print "Creating directory: ", dirpath
+      os.makedirs(dirpath)
+    except EnvironmentError:
+      print >> sys.stderr, "Failed to create directory: ", dirpath
+      return False
+  return True
+
+
+def _RemoveFile(filepath):
+  """Remove file |filepath| if it exists."""
+  if os.path.isfile(filepath):
+    try:
+      print "Removing file: ", filepath
+      os.remove(filepath)
+    except EnvironmentError:
+      print >> sys.stderr, "Failed to remove file: ", filepath
+      return False
+  return True
+
+
+def _RemoveDirectory(dirpath):
+  """Recursively remove directory |dirpath| if it exists."""
+  if os.path.isdir(dirpath):
+    try:
+      print "Removing directory: ", dirpath
+      shutil.rmtree(dirpath)
+    except EnvironmentError:
+      print >> sys.stderr, "Failed to remove directory: ", dirpath
+      return False
+  return True
+
+
+def _DevUser():
+  """Extract the user name from lsb-release."""
+  awk_expr  = """/CHROMEOS_RELEASE_DESCRIPTION/ {"""
+  awk_expr += """ sub(/.*Developer Build - /,"");"""
+  awk_expr += """ sub(/\).*/,"");"""
+  awk_expr += """ print; }"""
+  return _GetCommandOutput(["awk", awk_expr, "/etc/lsb-release"])
+
+
+def _DevHost():
+  """Extract the host name from lsb-release."""
+  awk_expr  = """/CHROMEOS_DEVSERVER/ {"""
+  awk_expr += """ sub(/.*http:\/\//,"");"""
+  awk_expr += """ sub(/:8080.*/,"");"""
+  awk_expr += """ print; }"""
+  return _GetCommandOutput(["awk", awk_expr, "/etc/lsb-release"])
+
+###############################################################################
+# Other Commands
+
+def TestCommand(args):
+  """Test command."""
+  if len(args) == 0:
+    args = ["sudo", "ls", "/"]
+    return _RunCommand(args)
+  return False
+
+
+def GetUser(unused_args=0):
+  """Gets the user name from /etc/lsb-release."""
+  print _DevUser()
+  return True
+
+
+def GetHost(unused_args=0):
+  """Gets the host name from /etc/lsb-release."""
+  print _DevHost()
+  return True
+
+
+def MountWriteable(unused_args=0):
+  """Remounts / as rw."""
+  return _RunCommand(["sudo", "mount", "-o", "remount,rw", "/"])
+
+
+def ShowIP(unused_args=0):
+  """Shows the IP address of the device."""
+  proc1 = subprocess.Popen(["/sbin/ifconfig", "eth0"], stdout=subprocess.PIPE)
+  awk_cmd  = """/inet addr/ {"""
+  awk_cmd += """ sub(/.*inet addr:/,""); sub(/  Bcast:.*/,"");"""
+  awk_cmd += """ print; }"""
+  proc2 = subprocess.Popen(
+      ["awk", awk_cmd], stdin=proc1.stdout, stdout=subprocess.PIPE)
+  proc1.stdout.close()
+  result = proc2.communicate()[0].rstrip('\n')
+  print "IP: ", result
+  return True
+
+
+def KillChrome(unused_args=0):
+  """Kills all chrome processes and prevents restarting of chrome."""
+  res = True
+  res &=  _RunCommand(["touch", "/tmp/disable_chrome_restart"])
+  res &= _RunCommand(["sudo", "pkill", "-9", "chrome"])
+  return res
+
+
+def ShowOobe(unused_args=0):
+  """Removes .oobe_completed and Local State directory."""
+
+  res = True
+  res &= KillChrome()
+  res &= _RemoveFile("/home/chronos/.oobe_completed")
+  res &= _RemoveDirectory("/home/chronos/Local State")
+
+  if not res:
+    print >> sys.stderr, "Unable to set up OOBE mode."
+    return False
+
+  if _Confirm("Reboot is required to enter OOBE mode. Reboot now?"):
+    _RunCommand(["sudo", "reboot"])
+  return True
+
+###############################################################################
+# Setup Commands
+
+def SetBash(unused_args):
+  """Sets the default shell to bash."""
+  if not _Sudo():
+    return False
+  if not MountWriteable():
+    return False
+  res = True
+  res &= _RunCommand(["chsh", "-s", "/bin/bash"])
+  res &= _RunCommand(["chsh", "-s", "/bin/bash", "chronos"])
+  return res
+
+
+def SetupBashrc(unused_args):
+  """Sets up .bashrc."""
+  filepath = os.path.expanduser("~/.bashrc")
+  if not _CheckOverwriteFile(filepath):
+    return False
+
+  print "Writing to: ", filepath
+  bashrc  = "#!/bin/bash\n"
+  bashrc += "# .bashrc file set by %s\n" % _PROG
+  bashrc += "export DISPLAY=:0.0\n"
+  bashrc += "export PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin\n"
+  bashrc += "/sbin/ifconfig eth0 | grep 'inet addr'\n"
+  if not _WriteFile(filepath, bashrc):
+    return False
+
+  filepath = os.path.expanduser("~/.bash_profile")
+  print "Writing to: ", filepath
+  bashprofile  = "#!/bin/bash\n"
+  bashprofile += ". $HOME/.bashrc\n"
+  if not _WriteFile(filepath, bashprofile):
+    return False
+  return True
+
+
+def SetupDev(unused_args):
+  """Developer friendly setup: skip oobe, don't forget usernames,
+\t\tdisable suspend, enable ssh and remote debugging,
+\t\trun commands from /tmp,/home."""
+  if not _Sudo():
+    return False
+  if not MountWriteable():
+    return False
+  res = True
+  res &= _WriteFile("1", "/home/chronos/.oobe_completed")
+  res &= _RemoveFile("/root/.forget_usernames")
+  res &= _MakeDirectory("/usr/share/power_manager")
+  res &= _WriteFile("1", "/usr/share/power_manager/disable_idle_suspend")
+  # Enable iptables and system-services for remote debugging
+  for filename in ["iptables", "saft"]:
+    res &= _RunCommand(["sudo", "sed", "-i", "-e",
+                       "s/#for_test //", "/etc/init/%s.conf" % filename])
+  # Allow commands to be run from /home and /tmp
+  res &= _RunCommand(["sudo", "sed", "-i", "-e",
+                     "s/#mod_for_test#//g", "/sbin/chromeos_startup"])
+  return res
+
+
+def SetupSsh(unused_args):
+  """Sets up ssh configuration so that the dev host can ssh to the device."""
+  if not MountWriteable():
+    return False
+
+  user = _DevUser()
+  host = _DevHost()
+
+  if not _CheckOverwriteFile(_SSH_KEY_FILEPATH):
+    return False
+
+  res = True
+  res &= _RemoveFile(_SSH_KEY_FILEPATH)
+  # Enable ssh to device
+  res &= _RunCommand(
+      ["sudo", "sed", "-i", "s/#for_test //", "/etc/init/openssh-server.conf"])
+  # Generate an ssh key
+  if _RunCommand(["ssh-keygen", "-f", _SSH_KEY_FILEPATH, "-N", "", "-q"]):
+    host_source_path = "%s@%s:%s" % (user, host, _PUB_KEY_FILENAME)
+    # Copy the ssh key to the host
+    res &= _RunCommand(["scp", host_source_path, _AUTH_KEYS_FILEPATH])
+
+  return res
+
+
+def AuthorizeSsh(unused_args):
+  """Authorizes this netbook to connect to your dev host.
+\t\t*Only use this on a device in a secure location!*"""
+
+  user = _DevUser()
+  host = _DevHost()
+
+  if not _Confirm("This will copy %s to authorized_keys on %s. Are you sure?" %
+                  (_PUB_KEY_FILENAME, host)):
+    return False
+
+  proc1 = subprocess.Popen(["cat", _PUB_KEY_FILEPATH], stdout=subprocess.PIPE)
+  try:
+    ssh_args = ["ssh", user+"@"+host, "cat >> ~/.ssh/authorized_keys"]
+    proc2 = subprocess.Popen(
+        ssh_args, stdin=proc1.stdout, stdout=subprocess.PIPE)
+  except EnvironmentError:
+    print >> sys.stderr, "Error executing: ", ' '.join(ssh_args)
+    return False
+  try:
+    proc1.stdout.close()
+    result, err = proc2.communicate()
+  except EnvironmentError:
+    print >> sys.stderr, \
+        "Error completing ssh command: ", result, "Error: ", err
+    return False
+  return True
+
+
+def SetupSshfsForChrome(unused_args):
+  """<chrome-drir> Sets up sshfs mount to chrome directory on host."""
+
+  user = _DevUser()
+  host = _DevHost()
+
+  prompt = "Chrome directory on %s [ %s ]: " % (host, _DEFAULT_CHROME_DIR)
+  chromedir = raw_input(prompt).rstrip()
+  if not chromedir:
+    chromedir = _DEFAULT_CHROME_DIR
+
+  target = user+"@"+host+":"+chromedir
+  print "Setting up sshfs mount to: ", target
+
+  res = True
+  res &= _RunCommand(["sudo", "modprobe", "fuse"])
+  if os.path.isdir(_CHROME_MOUNT_PATH):
+    res &= _RunCommand(["fusemount", "-q", "-u", _CHROME_MOUNT_PATH])
+    res &= _RemoveDirectory(_CHROME_MOUNT_PATH)
+  res &= _MakeDirectory(_CHROME_MOUNT_PATH)
+  res &= _RunCommand(["sshfs", target, _CHROME_MOUNT_PATH])
+  res &= _RunCommand(["sudo", "/sbin/iptables",
+                     "-A", "INPUT", "-p", "tcp", "--dport", "1234",
+                     "-j", "ACCEPT"])
+  return res
+
+###############################################################################
+# Multi-commands (convenience functions)
+
+def Setup(args):
+  """Performs default developer setup (bash,bashrc,dev,ssh)."""
+  if not SetBash(args):
+    if not _Confirm("Bash setup failed. Continue?"):
+      return False
+  if not SetupBashrc(args):
+    if not _Confirm(".bashrc setup failed. Continue?"):
+      return False
+  if not SetupDev(args):
+    if not _Confirm("Dev setup failed. Continue?"):
+      return False
+  if not SetupSsh(args):
+    return False
+  return True
+
+###############################################################################
+
+_SETUP_COMMANDS = {
+    'setup': Setup,
+    'dev': SetupDev,
+    'bash': SetBash,
+    'bashrc': SetupBashrc,
+    'ssh': SetupSsh,
+    'sshauthorize': AuthorizeSsh,
+    'sshfs': SetupSshfsForChrome,
+}
+
+
+_OTHER_COMMANDS = {
+    'mountrw': MountWriteable,
+    'ip': ShowIP,
+    'test': TestCommand,
+    'user': GetUser,
+    'host': GetHost,
+}
+
+
+def GetUsage(commands):
+  """Get the docstring for each command."""
+  usage = ""
+  for cmd in commands.items():
+    usage += " "
+    usage += cmd[0]
+    usage += ":\t"
+    if len(cmd[0]) < 6:
+      usage += "\t"
+    doc = cmd[1].__doc__
+    if doc:
+      usage += doc
+    usage += "\n"
+  return usage
+
+
+###############################################################################
+
+def main():
+  """Main crdev function"""
+  usage = """usage: crdev command [options]
+
+Note: Beta! Send feature requests / changes to [email protected] (for now)
+
+"""
+
+  usage += "Setup Commands:\n"
+  usage += GetUsage(_SETUP_COMMANDS)
+  usage += "Other Commands:\n"
+  usage += GetUsage(_OTHER_COMMANDS)
+
+  parser = optparse.OptionParser(usage)
+  args = parser.parse_args()[1]
+
+  if not args:
+    print usage
+    return
+
+  if os.getenv('USER') != 'chronos':
+    print >> sys.stderr, "%s must be run as chronos." % _PROG
+    return
+
+  cmd = args[0]
+  if cmd in _SETUP_COMMANDS.keys():
+    res = _SETUP_COMMANDS[cmd](args[1:])
+  elif cmd in _OTHER_COMMANDS.keys():
+    res = _OTHER_COMMANDS[cmd](args[1:])
+  else:
+    parser.error("Unknown command: " + cmd)
+    return
+
+  if not res:
+    print >> sys.stderr, "Error running command: " + ' '.join(args)
+  else:
+    print "Success!"
+
+
+if __name__ == '__main__':
+  main()