blob: 1656cce7d3be4056d501f4dd71a730d027859d5c [file] [log] [blame]
Steven Bennetts62fd7252011-11-03 20:56:091#!/usr/bin/env python
2# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""This script provides a suite of utility functions for the chroot.
7"""
8
9import optparse
10import os
11import shutil
12import subprocess
13import sys
14
15_PROG = "crdev"
16_SSH_KEY_FILEPATH = os.path.expanduser("~/.ssh/id_rsa")
17_PUB_KEY_FILENAME = "~/.ssh/id_rsa.pub"
18_PUB_KEY_FILEPATH = os.path.expanduser(_PUB_KEY_FILENAME)
19_AUTH_KEYS_FILEPATH = os.path.expanduser("~/.ssh/authorized_keys")
20_CHROME_MOUNT_PATH = "/tmp/chrome"
21_DEFAULT_CHROME_DIR = "~/chromeos/chromium"
22
23RAN_SUDO = False
24
25###############################################################################
26# Utility commands
27
28def _Confirm(message):
29 """Request confirmation and return True/False."""
30 reply = raw_input(message + " [y/N] ")
31 if reply == "y" or reply == "Y":
32 return True
33 return False
34
35
36def _CheckOverwriteFile(filepath):
37 """Check for a file and request confirmation if it exists."""
38 if os.path.isfile(filepath):
39 if not _Confirm(filepath + " already exists. Overwrite?"):
40 return False
41 return True
42
43
44def _WriteFile(filepath, contents):
45 """Write string |contents| to |filepath|."""
46 try:
47 print "Writing to: ", filepath
48 with open(filepath,"w") as f_1:
49 f_1.write(contents)
50 except EnvironmentError:
51 print >> sys.stderr, "Failed to write to file: ", filepath
52 return False
53
54 return True
55
56
57def _Sudo():
58 """Request sudo access with message."""
59 global RAN_SUDO
60 if not RAN_SUDO:
61 print "Executing: sudo -v"
62 try:
63 subprocess.call(["sudo", "-v"])
64 except EnvironmentError:
65 print >> sys.stderr, "Failed to run sudo"
66 return False
67 RAN_SUDO = True
68 return True
69
70
71def _RunCommand(args):
72 """Pass |args| to subprocess.call() and check the result."""
73 cmd = ''.join([x + " " for x in args])
74 try:
75 if args[0] == "sudo":
76 if _Sudo() == False:
77 return False
78 print "Executing: ", cmd
79 retcode = subprocess.call(args)
80 except EnvironmentError:
81 print >> sys.stderr, "Failed to run command: ", cmd
82 return False
83 else:
84 if retcode != 0:
85 print >> sys.stderr, "Error: ", retcode, ", running command: ", cmd
86 return False
87 return True
88
89
90def _GetCommandOutput(args):
91 """Pass |args| to subprocess.Popen and return the output."""
92 cmd = ''.join([x + " " for x in args])
93 try:
94 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
95 result = proc.communicate()[0]
96 except EnvironmentError:
97 print >> sys.stderr, "Failed to run command: ", cmd
98 return None
99 result = result.rstrip('\n')
100 return result
101
102
103def _MakeDirectory(dirpath):
104 """Create directory |dirpath| if it does not exist."""
105 if not os.path.isdir(dirpath):
106 try:
107 print "Creating directory: ", dirpath
108 os.makedirs(dirpath)
109 except EnvironmentError:
110 print >> sys.stderr, "Failed to create directory: ", dirpath
111 return False
112 return True
113
114
115def _RemoveFile(filepath):
116 """Remove file |filepath| if it exists."""
117 if os.path.isfile(filepath):
118 try:
119 print "Removing file: ", filepath
120 os.remove(filepath)
121 except EnvironmentError:
122 print >> sys.stderr, "Failed to remove file: ", filepath
123 return False
124 return True
125
126
127def _RemoveDirectory(dirpath):
128 """Recursively remove directory |dirpath| if it exists."""
129 if os.path.isdir(dirpath):
130 try:
131 print "Removing directory: ", dirpath
132 shutil.rmtree(dirpath)
133 except EnvironmentError:
134 print >> sys.stderr, "Failed to remove directory: ", dirpath
135 return False
136 return True
137
138
139def _DevUser():
140 """Extract the user name from lsb-release."""
141 awk_expr = """/CHROMEOS_RELEASE_DESCRIPTION/ {"""
142 awk_expr += """ sub(/.*Developer Build - /,"");"""
143 awk_expr += """ sub(/\).*/,"");"""
144 awk_expr += """ print; }"""
145 return _GetCommandOutput(["awk", awk_expr, "/etc/lsb-release"])
146
147
148def _DevHost():
149 """Extract the host name from lsb-release."""
150 awk_expr = """/CHROMEOS_DEVSERVER/ {"""
151 awk_expr += """ sub(/.*http:\/\//,"");"""
152 awk_expr += """ sub(/:8080.*/,"");"""
153 awk_expr += """ print; }"""
154 return _GetCommandOutput(["awk", awk_expr, "/etc/lsb-release"])
155
156###############################################################################
157# Other Commands
158
159def TestCommand(args):
160 """Test command."""
161 if len(args) == 0:
162 args = ["sudo", "ls", "/"]
163 return _RunCommand(args)
164 return False
165
166
167def GetUser(unused_args=0):
168 """Gets the user name from /etc/lsb-release."""
169 print _DevUser()
170 return True
171
172
173def GetHost(unused_args=0):
174 """Gets the host name from /etc/lsb-release."""
175 print _DevHost()
176 return True
177
178
179def MountWriteable(unused_args=0):
180 """Remounts / as rw."""
181 return _RunCommand(["sudo", "mount", "-o", "remount,rw", "/"])
182
183
184def ShowIP(unused_args=0):
185 """Shows the IP address of the device."""
186 proc1 = subprocess.Popen(["/sbin/ifconfig", "eth0"], stdout=subprocess.PIPE)
187 awk_cmd = """/inet addr/ {"""
188 awk_cmd += """ sub(/.*inet addr:/,""); sub(/ Bcast:.*/,"");"""
189 awk_cmd += """ print; }"""
190 proc2 = subprocess.Popen(
191 ["awk", awk_cmd], stdin=proc1.stdout, stdout=subprocess.PIPE)
192 proc1.stdout.close()
193 result = proc2.communicate()[0].rstrip('\n')
194 print "IP: ", result
195 return True
196
197
198def KillChrome(unused_args=0):
199 """Kills all chrome processes and prevents restarting of chrome."""
200 res = True
201 res &= _RunCommand(["touch", "/tmp/disable_chrome_restart"])
202 res &= _RunCommand(["sudo", "pkill", "-9", "chrome"])
203 return res
204
205
206def ShowOobe(unused_args=0):
207 """Removes .oobe_completed and Local State directory."""
208
209 res = True
210 res &= KillChrome()
211 res &= _RemoveFile("/home/chronos/.oobe_completed")
212 res &= _RemoveDirectory("/home/chronos/Local State")
213
214 if not res:
215 print >> sys.stderr, "Unable to set up OOBE mode."
216 return False
217
218 if _Confirm("Reboot is required to enter OOBE mode. Reboot now?"):
219 _RunCommand(["sudo", "reboot"])
220 return True
221
222###############################################################################
223# Setup Commands
224
225def SetBash(unused_args):
226 """Sets the default shell to bash."""
227 if not _Sudo():
228 return False
229 if not MountWriteable():
230 return False
231 res = True
232 res &= _RunCommand(["chsh", "-s", "/bin/bash"])
233 res &= _RunCommand(["chsh", "-s", "/bin/bash", "chronos"])
234 return res
235
236
237def SetupBashrc(unused_args):
238 """Sets up .bashrc."""
239 filepath = os.path.expanduser("~/.bashrc")
240 if not _CheckOverwriteFile(filepath):
241 return False
242
243 print "Writing to: ", filepath
244 bashrc = "#!/bin/bash\n"
245 bashrc += "# .bashrc file set by %s\n" % _PROG
246 bashrc += "export DISPLAY=:0.0\n"
247 bashrc += "export PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin\n"
248 bashrc += "/sbin/ifconfig eth0 | grep 'inet addr'\n"
249 if not _WriteFile(filepath, bashrc):
250 return False
251
252 filepath = os.path.expanduser("~/.bash_profile")
253 print "Writing to: ", filepath
254 bashprofile = "#!/bin/bash\n"
255 bashprofile += ". $HOME/.bashrc\n"
256 if not _WriteFile(filepath, bashprofile):
257 return False
258 return True
259
260
261def SetupDev(unused_args):
262 """Developer friendly setup: skip oobe, don't forget usernames,
263\t\tdisable suspend, enable ssh and remote debugging,
264\t\trun commands from /tmp,/home."""
265 if not _Sudo():
266 return False
267 if not MountWriteable():
268 return False
269 res = True
270 res &= _WriteFile("1", "/home/chronos/.oobe_completed")
271 res &= _RemoveFile("/root/.forget_usernames")
272 res &= _MakeDirectory("/usr/share/power_manager")
273 res &= _WriteFile("1", "/usr/share/power_manager/disable_idle_suspend")
274 # Enable iptables and system-services for remote debugging
275 for filename in ["iptables", "saft"]:
276 res &= _RunCommand(["sudo", "sed", "-i", "-e",
277 "s/#for_test //", "/etc/init/%s.conf" % filename])
278 # Allow commands to be run from /home and /tmp
279 res &= _RunCommand(["sudo", "sed", "-i", "-e",
280 "s/#mod_for_test#//g", "/sbin/chromeos_startup"])
281 return res
282
283
284def SetupSsh(unused_args):
285 """Sets up ssh configuration so that the dev host can ssh to the device."""
286 if not MountWriteable():
287 return False
288
289 user = _DevUser()
290 host = _DevHost()
291
292 if not _CheckOverwriteFile(_SSH_KEY_FILEPATH):
293 return False
294
295 res = True
296 res &= _RemoveFile(_SSH_KEY_FILEPATH)
297 # Enable ssh to device
298 res &= _RunCommand(
299 ["sudo", "sed", "-i", "s/#for_test //", "/etc/init/openssh-server.conf"])
300 # Generate an ssh key
301 if _RunCommand(["ssh-keygen", "-f", _SSH_KEY_FILEPATH, "-N", "", "-q"]):
302 host_source_path = "%s@%s:%s" % (user, host, _PUB_KEY_FILENAME)
303 # Copy the ssh key to the host
304 res &= _RunCommand(["scp", host_source_path, _AUTH_KEYS_FILEPATH])
305
306 return res
307
308
309def AuthorizeSsh(unused_args):
310 """Authorizes this netbook to connect to your dev host.
311\t\t*Only use this on a device in a secure location!*"""
312
313 user = _DevUser()
314 host = _DevHost()
315
316 if not _Confirm("This will copy %s to authorized_keys on %s. Are you sure?" %
317 (_PUB_KEY_FILENAME, host)):
318 return False
319
320 proc1 = subprocess.Popen(["cat", _PUB_KEY_FILEPATH], stdout=subprocess.PIPE)
321 try:
322 ssh_args = ["ssh", user+"@"+host, "cat >> ~/.ssh/authorized_keys"]
323 proc2 = subprocess.Popen(
324 ssh_args, stdin=proc1.stdout, stdout=subprocess.PIPE)
325 except EnvironmentError:
326 print >> sys.stderr, "Error executing: ", ' '.join(ssh_args)
327 return False
328 try:
329 proc1.stdout.close()
330 result, err = proc2.communicate()
331 except EnvironmentError:
332 print >> sys.stderr, \
333 "Error completing ssh command: ", result, "Error: ", err
334 return False
335 return True
336
337
338def SetupSshfsForChrome(unused_args):
339 """<chrome-drir> Sets up sshfs mount to chrome directory on host."""
340
341 user = _DevUser()
342 host = _DevHost()
343
344 prompt = "Chrome directory on %s [ %s ]: " % (host, _DEFAULT_CHROME_DIR)
345 chromedir = raw_input(prompt).rstrip()
346 if not chromedir:
347 chromedir = _DEFAULT_CHROME_DIR
348
349 target = user+"@"+host+":"+chromedir
350 print "Setting up sshfs mount to: ", target
351
352 res = True
353 res &= _RunCommand(["sudo", "modprobe", "fuse"])
354 if os.path.isdir(_CHROME_MOUNT_PATH):
355 res &= _RunCommand(["fusemount", "-q", "-u", _CHROME_MOUNT_PATH])
356 res &= _RemoveDirectory(_CHROME_MOUNT_PATH)
357 res &= _MakeDirectory(_CHROME_MOUNT_PATH)
358 res &= _RunCommand(["sshfs", target, _CHROME_MOUNT_PATH])
359 res &= _RunCommand(["sudo", "/sbin/iptables",
360 "-A", "INPUT", "-p", "tcp", "--dport", "1234",
361 "-j", "ACCEPT"])
362 return res
363
364###############################################################################
365# Multi-commands (convenience functions)
366
367def Setup(args):
368 """Performs default developer setup (bash,bashrc,dev,ssh)."""
369 if not SetBash(args):
370 if not _Confirm("Bash setup failed. Continue?"):
371 return False
372 if not SetupBashrc(args):
373 if not _Confirm(".bashrc setup failed. Continue?"):
374 return False
375 if not SetupDev(args):
376 if not _Confirm("Dev setup failed. Continue?"):
377 return False
378 if not SetupSsh(args):
379 return False
380 return True
381
382###############################################################################
383
384_SETUP_COMMANDS = {
385 'setup': Setup,
386 'dev': SetupDev,
387 'bash': SetBash,
388 'bashrc': SetupBashrc,
389 'ssh': SetupSsh,
390 'sshauthorize': AuthorizeSsh,
391 'sshfs': SetupSshfsForChrome,
392}
393
394
395_OTHER_COMMANDS = {
396 'mountrw': MountWriteable,
397 'ip': ShowIP,
398 'test': TestCommand,
399 'user': GetUser,
400 'host': GetHost,
401}
402
403
404def GetUsage(commands):
405 """Get the docstring for each command."""
406 usage = ""
407 for cmd in commands.items():
408 usage += " "
409 usage += cmd[0]
410 usage += ":\t"
411 if len(cmd[0]) < 6:
412 usage += "\t"
413 doc = cmd[1].__doc__
414 if doc:
415 usage += doc
416 usage += "\n"
417 return usage
418
419
420###############################################################################
421
422def main():
423 """Main crdev function"""
424 usage = """usage: crdev command [options]
425
426Note: Beta! Send feature requests / changes to [email protected] (for now)
427
428"""
429
430 usage += "Setup Commands:\n"
431 usage += GetUsage(_SETUP_COMMANDS)
432 usage += "Other Commands:\n"
433 usage += GetUsage(_OTHER_COMMANDS)
434
435 parser = optparse.OptionParser(usage)
436 args = parser.parse_args()[1]
437
438 if not args:
439 print usage
440 return
441
442 if os.getenv('USER') != 'chronos':
443 print >> sys.stderr, "%s must be run as chronos." % _PROG
444 return
445
446 cmd = args[0]
447 if cmd in _SETUP_COMMANDS.keys():
448 res = _SETUP_COMMANDS[cmd](args[1:])
449 elif cmd in _OTHER_COMMANDS.keys():
450 res = _OTHER_COMMANDS[cmd](args[1:])
451 else:
452 parser.error("Unknown command: " + cmd)
453 return
454
455 if not res:
456 print >> sys.stderr, "Error running command: " + ' '.join(args)
457 else:
458 print "Success!"
459
460
461if __name__ == '__main__':
462 main()