image_chromeos.py revision 4467f004e7f0854963bec90daff1879fbd9d2fec
1#!/usr/bin/python 2# 3# Copyright 2011 Google Inc. All Rights Reserved. 4 5"""Script to image a ChromeOS device. 6 7This script images a remote ChromeOS device with a specific image." 8""" 9 10__author__ = "asharif@google.com (Ahmad Sharif)" 11 12import filecmp 13import glob 14import optparse 15import os 16import re 17import shutil 18import sys 19import tempfile 20import time 21 22from utils import command_executer 23from utils import logger 24from utils import misc 25from utils.file_utils import FileUtils 26 27checksum_file = "/usr/local/osimage_checksum_file" 28lock_file = "/tmp/image_chromeos_lock/image_chromeos_lock" 29 30def Usage(parser, message): 31 print "ERROR: " + message 32 parser.print_help() 33 sys.exit(0) 34 35 36def DoImage(argv): 37 """Build ChromeOS.""" 38 39 # Common initializations 40 cmd_executer = command_executer.GetCommandExecuter() 41 l = logger.GetLogger() 42 43 parser = optparse.OptionParser() 44 parser.add_option("-c", "--chromeos_root", dest="chromeos_root", 45 help="Target directory for ChromeOS installation.") 46 parser.add_option("-r", "--remote", dest="remote", 47 help="Target device.") 48 parser.add_option("-i", "--image", dest="image", 49 help="Image binary file.") 50 parser.add_option("-b", "--board", dest="board", 51 help="Target board override.") 52 parser.add_option("-f", "--force", dest="force", 53 action="store_true", 54 default=False, 55 help="Force an image even if it is non-test.") 56 parser.add_option("-a", 57 "--image_args", 58 dest="image_args") 59 60 61 options = parser.parse_args(argv[1:])[0] 62 63 if options.chromeos_root is None: 64 Usage(parser, "--chromeos_root must be set") 65 66 if options.remote is None: 67 Usage(parser, "--remote must be set") 68 69 options.chromeos_root = os.path.expanduser(options.chromeos_root) 70 71 if options.board is None: 72 board = cmd_executer.CrosLearnBoard(options.chromeos_root, options.remote) 73 else: 74 board = options.board 75 76 if options.image is None: 77 images_dir = misc.GetImageDir(options.chromeos_root, board) 78 image = os.path.join(images_dir, 79 "latest", 80 "chromiumos_test_image.bin") 81 if not os.path.exists(image): 82 image = os.path.join(images_dir, 83 "latest", 84 "chromiumos_image.bin") 85 else: 86 image = options.image 87 image = os.path.expanduser(image) 88 89 image = os.path.realpath(image) 90 91 if not os.path.exists(image): 92 Usage(parser, "Image file: " + image + " does not exist!") 93 94 image_checksum = FileUtils().Md5File(image) 95 96 command = "cat " + checksum_file 97 retval, device_checksum, err = cmd_executer.CrosRunCommand(command, 98 return_output=True, 99 chromeos_root=options.chromeos_root, 100 machine=options.remote) 101 102 device_checksum = device_checksum.strip() 103 image_checksum = str(image_checksum) 104 105 l.LogOutput("Image checksum: " + image_checksum) 106 l.LogOutput("Device checksum: " + device_checksum) 107 108 if image_checksum != device_checksum: 109 [found, located_image] = LocateOrCopyImage(options.chromeos_root, 110 image, 111 board=board) 112 113 l.LogOutput("Checksums do not match. Re-imaging...") 114 115 is_test_image = IsImageModdedForTest(options.chromeos_root, 116 located_image) 117 118 if not is_test_image and not options.force: 119 logger.GetLogger().LogFatal("Have to pass --force to image a non-test " 120 "image!") 121 122 # If the device has /tmp mounted as noexec, image_to_live.sh can fail. 123 command = "mount -o remount,rw,exec /tmp" 124 cmd_executer.CrosRunCommand(command, 125 chromeos_root=options.chromeos_root, 126 machine=options.remote) 127 128 real_src_dir = os.path.join(os.path.realpath(options.chromeos_root), 129 "src") 130 if located_image.find(real_src_dir) != 0: 131 raise Exception("Located image: %s not in chromeos_root: %s" % 132 (located_image, options.chromeos_root)) 133 chroot_image = os.path.join( 134 "..", 135 located_image[len(real_src_dir):].lstrip("/")) 136 cros_image_to_target_args = ["--remote=%s" % options.remote, 137 "--board=%s" % board, 138 "--from=%s" % os.path.dirname(chroot_image), 139 "--image-name=%s" % 140 os.path.basename(located_image)] 141 142 command = ("./bin/cros_image_to_target.py %s" % 143 " ".join(cros_image_to_target_args)) 144 if options.image_args: 145 command += " %s" % options.image_args 146 147 # Workaround for crosbug.com/35684. 148 os.chmod(misc.GetChromeOSKeyFile(options.chromeos_root), 0600) 149 retval = cmd_executer.ChrootRunCommand(options.chromeos_root, 150 command) 151 if found == False: 152 temp_dir = os.path.dirname(located_image) 153 l.LogOutput("Deleting temp image dir: %s" % temp_dir) 154 shutil.rmtree(temp_dir) 155 156 logger.GetLogger().LogFatalIf(retval, "Image command failed") 157 158 # Unfortunately cros_image_to_target.py sometimes returns early when the 159 # machine isn't fully up yet. 160 retval = EnsureMachineUp(options.chromeos_root, options.remote) 161 162 command = "echo %s > %s && chmod -w %s" % (image_checksum, checksum_file, 163 checksum_file) 164 retval = cmd_executer.CrosRunCommand(command, 165 chromeos_root=options.chromeos_root, 166 machine=options.remote) 167 logger.GetLogger().LogFatalIf(retval, "Writing checksum failed.") 168 169 successfully_imaged = VerifyChromeChecksum(options.chromeos_root, 170 image, 171 options.remote) 172 logger.GetLogger().LogFatalIf(not successfully_imaged, 173 "Image verification failed!") 174 else: 175 l.LogOutput("Checksums match. Skipping reimage") 176 return retval 177 178 179def LocateOrCopyImage(chromeos_root, image, board=None): 180 l = logger.GetLogger() 181 if board is None: 182 board_glob = "*" 183 else: 184 board_glob = board 185 186 chromeos_root_realpath = os.path.realpath(chromeos_root) 187 image = os.path.realpath(image) 188 189 if image.startswith("%s/" % chromeos_root_realpath): 190 return [True, image] 191 192 # First search within the existing build dirs for any matching files. 193 images_glob = ("%s/src/build/images/%s/*/*.bin" % 194 (chromeos_root_realpath, 195 board_glob)) 196 images_list = glob.glob(images_glob) 197 for potential_image in images_list: 198 if filecmp.cmp(potential_image, image): 199 l.LogOutput("Found matching image %s in chromeos_root." % potential_image) 200 return [True, potential_image] 201 # We did not find an image. Copy it in the src dir and return the copied file. 202 if board is None: 203 board = "" 204 base_dir = ("%s/src/build/images/%s" % 205 (chromeos_root_realpath, 206 board)) 207 if not os.path.isdir(base_dir): 208 os.makedirs(base_dir) 209 temp_dir = tempfile.mkdtemp(prefix="%s/tmp" % base_dir) 210 new_image = "%s/%s" % (temp_dir, os.path.basename(image)) 211 l.LogOutput("No matching image found. Copying %s to %s" % 212 (image, new_image)) 213 shutil.copyfile(image, new_image) 214 return [False, new_image] 215 216 217def GetImageMountCommand(chromeos_root, image, rootfs_mp, stateful_mp): 218 image_dir = os.path.dirname(image) 219 image_file = os.path.basename(image) 220 mount_command = ("cd %s/src/scripts &&" 221 "./mount_gpt_image.sh --from=%s --image=%s" 222 " --safe --read_only" 223 " --rootfs_mountpt=%s" 224 " --stateful_mountpt=%s" % 225 (chromeos_root, image_dir, image_file, rootfs_mp, 226 stateful_mp)) 227 return mount_command 228 229 230def MountImage(chromeos_root, image, rootfs_mp, stateful_mp, unmount=False): 231 cmd_executer = command_executer.GetCommandExecuter() 232 command = GetImageMountCommand(chromeos_root, image, rootfs_mp, stateful_mp) 233 if unmount: 234 command = "%s --unmount" % command 235 retval = cmd_executer.RunCommand(command) 236 logger.GetLogger().LogFatalIf(retval, "Mount/unmount command failed!") 237 return retval 238 239 240def IsImageModdedForTest(chromeos_root, image): 241 rootfs_mp = tempfile.mkdtemp() 242 stateful_mp = tempfile.mkdtemp() 243 MountImage(chromeos_root, image, rootfs_mp, stateful_mp) 244 lsb_release_file = os.path.join(rootfs_mp, "etc/lsb-release") 245 lsb_release_contents = open(lsb_release_file).read() 246 is_test_image = re.search("test", lsb_release_contents, re.IGNORECASE) 247 MountImage(chromeos_root, image, rootfs_mp, stateful_mp, unmount=True) 248 return is_test_image 249 250 251def VerifyChromeChecksum(chromeos_root, image, remote): 252 cmd_executer = command_executer.GetCommandExecuter() 253 rootfs_mp = tempfile.mkdtemp() 254 stateful_mp = tempfile.mkdtemp() 255 MountImage(chromeos_root, image, rootfs_mp, stateful_mp) 256 image_chrome_checksum = FileUtils().Md5File("%s/opt/google/chrome/chrome" % 257 rootfs_mp) 258 MountImage(chromeos_root, image, rootfs_mp, stateful_mp, unmount=True) 259 260 command = "md5sum /opt/google/chrome/chrome" 261 [r, o, e] = cmd_executer.CrosRunCommand(command, 262 return_output=True, 263 chromeos_root=chromeos_root, 264 machine=remote) 265 device_chrome_checksum = o.split()[0] 266 if image_chrome_checksum.strip() == device_chrome_checksum.strip(): 267 return True 268 else: 269 return False 270 271 272def EnsureMachineUp(chromeos_root, remote): 273 l = logger.GetLogger() 274 cmd_executer = command_executer.GetCommandExecuter() 275 timeout = 600 276 magic = "abcdefghijklmnopqrstuvwxyz" 277 command = "echo %s" % magic 278 start_time = time.time() 279 while True: 280 current_time = time.time() 281 if current_time - start_time > timeout: 282 l.LogError("Timeout of %ss reached. Machine still not up. Aborting." % 283 timeout) 284 return False 285 retval = cmd_executer.CrosRunCommand(command, 286 chromeos_root=chromeos_root, 287 machine=remote) 288 if not retval: 289 return True 290 291 292def Main(argv): 293 misc.AcquireLock(lock_file) 294 try: 295 return DoImage(argv) 296 finally: 297 misc.ReleaseLock(lock_file) 298 299 300if __name__ == "__main__": 301 retval = Main(sys.argv) 302 sys.exit(retval) 303