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