image_chromeos.py revision f81680c018729fd4499e1e200d04b48c4b90127c
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 TryRemountPartitionAsRW(options.chromeos_root, options.remote) 175 else: 176 l.LogOutput("Checksums match. Skipping reimage") 177 return retval 178 179 180def LocateOrCopyImage(chromeos_root, image, board=None): 181 l = logger.GetLogger() 182 if board is None: 183 board_glob = "*" 184 else: 185 board_glob = board 186 187 chromeos_root_realpath = os.path.realpath(chromeos_root) 188 image = os.path.realpath(image) 189 190 if image.startswith("%s/" % chromeos_root_realpath): 191 return [True, image] 192 193 # First search within the existing build dirs for any matching files. 194 images_glob = ("%s/src/build/images/%s/*/*.bin" % 195 (chromeos_root_realpath, 196 board_glob)) 197 images_list = glob.glob(images_glob) 198 for potential_image in images_list: 199 if filecmp.cmp(potential_image, image): 200 l.LogOutput("Found matching image %s in chromeos_root." % potential_image) 201 return [True, potential_image] 202 # We did not find an image. Copy it in the src dir and return the copied file. 203 if board is None: 204 board = "" 205 base_dir = ("%s/src/build/images/%s" % 206 (chromeos_root_realpath, 207 board)) 208 if not os.path.isdir(base_dir): 209 os.makedirs(base_dir) 210 temp_dir = tempfile.mkdtemp(prefix="%s/tmp" % base_dir) 211 new_image = "%s/%s" % (temp_dir, os.path.basename(image)) 212 l.LogOutput("No matching image found. Copying %s to %s" % 213 (image, new_image)) 214 shutil.copyfile(image, new_image) 215 return [False, new_image] 216 217 218def GetImageMountCommand(chromeos_root, image, rootfs_mp, stateful_mp): 219 image_dir = os.path.dirname(image) 220 image_file = os.path.basename(image) 221 mount_command = ("cd %s/src/scripts &&" 222 "./mount_gpt_image.sh --from=%s --image=%s" 223 " --safe --read_only" 224 " --rootfs_mountpt=%s" 225 " --stateful_mountpt=%s" % 226 (chromeos_root, image_dir, image_file, rootfs_mp, 227 stateful_mp)) 228 return mount_command 229 230 231def MountImage(chromeos_root, image, rootfs_mp, stateful_mp, unmount=False): 232 cmd_executer = command_executer.GetCommandExecuter() 233 command = GetImageMountCommand(chromeos_root, image, rootfs_mp, stateful_mp) 234 if unmount: 235 command = "%s --unmount" % command 236 retval = cmd_executer.RunCommand(command) 237 logger.GetLogger().LogFatalIf(retval, "Mount/unmount command failed!") 238 return retval 239 240 241def IsImageModdedForTest(chromeos_root, image): 242 rootfs_mp = tempfile.mkdtemp() 243 stateful_mp = tempfile.mkdtemp() 244 MountImage(chromeos_root, image, rootfs_mp, stateful_mp) 245 lsb_release_file = os.path.join(rootfs_mp, "etc/lsb-release") 246 lsb_release_contents = open(lsb_release_file).read() 247 is_test_image = re.search("test", lsb_release_contents, re.IGNORECASE) 248 MountImage(chromeos_root, image, rootfs_mp, stateful_mp, unmount=True) 249 return is_test_image 250 251 252def VerifyChromeChecksum(chromeos_root, image, remote): 253 cmd_executer = command_executer.GetCommandExecuter() 254 rootfs_mp = tempfile.mkdtemp() 255 stateful_mp = tempfile.mkdtemp() 256 MountImage(chromeos_root, image, rootfs_mp, stateful_mp) 257 image_chrome_checksum = FileUtils().Md5File("%s/opt/google/chrome/chrome" % 258 rootfs_mp) 259 MountImage(chromeos_root, image, rootfs_mp, stateful_mp, unmount=True) 260 261 command = "md5sum /opt/google/chrome/chrome" 262 [r, o, e] = cmd_executer.CrosRunCommand(command, 263 return_output=True, 264 chromeos_root=chromeos_root, 265 machine=remote) 266 device_chrome_checksum = o.split()[0] 267 if image_chrome_checksum.strip() == device_chrome_checksum.strip(): 268 return True 269 else: 270 return False 271 272# Remount partition as writable. 273# TODO: auto-detect if an image is built using --noenable_rootfs_verification. 274def TryRemountPartitionAsRW(chromeos_root, remote): 275 l = logger.GetLogger() 276 cmd_executer = command_executer.GetCommandExecuter() 277 command = "sudo mount -o remount,rw /" 278 retval = cmd_executer.CrosRunCommand(\ 279 command, chromeos_root=chromeos_root, machine=remote, terminated_timeout=10) 280 if retval: 281 ## Safely ignore. 282 l.LogWarning("Failed to remount partition as rw, " 283 "probably the image was not built with " 284 "\"--noenable_rootfs_verification\", " 285 "you can safely ignore this.") 286 else: 287 l.LogOutput("Re-mounted partition as writable.") 288 289 290def EnsureMachineUp(chromeos_root, remote): 291 l = logger.GetLogger() 292 cmd_executer = command_executer.GetCommandExecuter() 293 timeout = 600 294 magic = "abcdefghijklmnopqrstuvwxyz" 295 command = "echo %s" % magic 296 start_time = time.time() 297 while True: 298 current_time = time.time() 299 if current_time - start_time > timeout: 300 l.LogError("Timeout of %ss reached. Machine still not up. Aborting." % 301 timeout) 302 return False 303 retval = cmd_executer.CrosRunCommand(command, 304 chromeos_root=chromeos_root, 305 machine=remote) 306 if not retval: 307 return True 308 309 310def Main(argv): 311 misc.AcquireLock(lock_file) 312 try: 313 return DoImage(argv) 314 finally: 315 misc.ReleaseLock(lock_file) 316 317 318if __name__ == "__main__": 319 retval = Main(sys.argv) 320 sys.exit(retval) 321