image_chromeos.py revision fd356fb31c4340851fecc750230d05c9518b125c
1#!/usr/bin/python2.6
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 shutil
17import sys
18import tempfile
19from utils import command_executer
20from utils import logger
21from utils import misc
22from utils.file_utils import FileUtils
23
24checksum_file = "/usr/local/osimage_checksum_file"
25
26
27def Usage(parser, message):
28  print "ERROR: " + message
29  parser.print_help()
30  sys.exit(0)
31
32def Main(argv):
33  """Build ChromeOS."""
34  # Common initializations
35  cmd_executer = command_executer.GetCommandExecuter()
36  l = logger.GetLogger()
37
38  parser = optparse.OptionParser()
39  parser.add_option("-c", "--chromeos_root", dest="chromeos_root",
40                    help="Target directory for ChromeOS installation.")
41  parser.add_option("-r", "--remote", dest="remote",
42                    help="Target device.")
43  parser.add_option("-i", "--image", dest="image",
44                    help="Image binary file.")
45  parser.add_option("-b", "--board", dest="board",
46                    help="Target board override.")
47  parser.add_option("-f", "--force", dest="force",
48                    action="store_true",
49                    default=False,
50                    help="Force an image even if it is non-test.")
51  parser.add_option("-a",
52                    "--image_to_live_args",
53                    dest="image_to_live_args")
54
55
56  options = parser.parse_args(argv[1:])[0]
57
58  if options.chromeos_root is None:
59    Usage(parser, "--chromeos_root must be set")
60
61  if options.remote is None:
62    Usage(parser, "--remote must be set")
63
64  options.chromeos_root = os.path.expanduser(options.chromeos_root)
65
66  if options.board is None:
67    board = cmd_executer.CrosLearnBoard(options.chromeos_root, options.remote)
68  else:
69    board = options.board
70
71  if options.image is None:
72    images_dir = misc.GetImageDir(options.chromeos_root, board)
73    image = os.path.join(images_dir,
74                         "latest",
75                         "chromiumos_test_image.bin")
76    if not os.path.exists(image):
77      image = os.path.join(images_dir,
78                           "latest",
79                           "chromiumos_image.bin")
80  else:
81    image = options.image
82    image = os.path.expanduser(image)
83
84  image = os.path.realpath(image)
85
86  if not os.path.exists(image):
87    Usage(parser, "Image file: " + image + " does not exist!")
88
89  image_checksum = FileUtils().Md5File(image)
90
91  command = "cat " + checksum_file
92  retval, device_checksum, err = cmd_executer.CrosRunCommand(command,
93      return_output=True,
94      chromeos_root=options.chromeos_root,
95      machine=options.remote)
96
97  device_checksum = device_checksum.strip()
98  image_checksum = str(image_checksum)
99
100  l.LogOutput("Image checksum: " + image_checksum)
101  l.LogOutput("Device checksum: " + device_checksum)
102
103  if image_checksum != device_checksum:
104    [found, located_image] = LocateOrCopyImage(options.chromeos_root,
105                                               image,
106                                               board=board)
107
108    l.LogOutput("Checksums do not match. Re-imaging...")
109
110    is_test_image = IsImageModdedForTest(options.chromeos_root,
111                                         located_image)
112
113    if not is_test_image and not options.force:
114      logger.GetLogger().LogFatal("Have to pass --force to image a non-test "
115                                  "image!")
116
117    # If the device has /tmp mounted as noexec, image_to_live.sh can fail.
118    command = "mount -o remount,rw,exec /tmp"
119    cmd_executer.CrosRunCommand(command,
120                                chromeos_root=options.chromeos_root,
121                                machine=options.remote)
122
123    command = (options.chromeos_root +
124               "/src/scripts/image_to_live.sh --remote=" +
125               options.remote +
126               " --image=" + located_image)
127    if options.image_to_live_args:
128      command += " %s" % options.image_to_live_args
129
130    retval = cmd_executer.RunCommand(command)
131
132    if found == False:
133      temp_dir = os.path.dirname(located_image)
134      l.LogOutput("Deleting temp image dir: %s" % temp_dir)
135      shutil.rmtree(temp_dir)
136
137    logger.GetLogger().LogFatalIf(retval, "Image command failed")
138    command = "echo %s > %s && chmod -w %s" % (image_checksum, checksum_file,
139                                               checksum_file)
140    retval = cmd_executer.CrosRunCommand(command,
141                                         chromeos_root=options.chromeos_root,
142                                         machine=options.remote)
143    logger.GetLogger().LogFatalIf(retval, "Writing checksum failed.")
144
145    successfully_imaged = VerifyChromeChecksum(options.chromeos_root,
146                                               image,
147                                               options.remote)
148    logger.GetLogger().LogFatalIf(not successfully_imaged,
149                                  "Image verification failed!")
150  else:
151    l.LogOutput("Checksums match. Skipping reimage")
152
153  return retval
154
155
156def LocateOrCopyImage(chromeos_root, image, board=None):
157  l = logger.GetLogger()
158  if board is None:
159    board_glob = "*"
160  else:
161    board_glob = board
162
163  chromeos_root_realpath = os.path.realpath(chromeos_root)
164  image = os.path.realpath(image)
165
166  if image.startswith("%s/" % chromeos_root_realpath):
167    return [True, image]
168
169  # First search within the existing build dirs for any matching files.
170  images_glob = ("%s/src/build/images/%s/*/*.bin" %
171                 (chromeos_root_realpath,
172                  board_glob))
173  images_list = glob.glob(images_glob)
174  for potential_image in images_list:
175    if filecmp.cmp(potential_image, image):
176      l.LogOutput("Found matching image %s in chromeos_root." % potential_image)
177      return [True, potential_image]
178  # We did not find an image. Copy it in the src dir and return the copied file.
179  if board is None:
180    board = ""
181  base_dir = ("%s/src/build/images/%s" %
182              (chromeos_root_realpath,
183               board))
184  if not os.path.isdir(base_dir):
185    os.makedirs(base_dir)
186  temp_dir = tempfile.mkdtemp(prefix="%s/tmp" % base_dir)
187  new_image = "%s/%s" % (temp_dir, os.path.basename(image))
188  l.LogOutput("No matching image found. Copying %s to %s" %
189              (image, new_image))
190  shutil.copyfile(image, new_image)
191  return [False, new_image]
192
193
194def GetImageMountCommand(chromeos_root, image, rootfs_mp, stateful_mp):
195  image_dir = os.path.dirname(image)
196  image_file = os.path.basename(image)
197  mount_command = ("cd %s/src/scripts &&"
198                   "./mount_gpt_image.sh --from=%s --image=%s"
199                   " --safe --read_only"
200                   " --rootfs_mountpt=%s"
201                   " --stateful_mountpt=%s" %
202                   (chromeos_root, image_dir, image_file, rootfs_mp,
203                    stateful_mp))
204  return mount_command
205
206
207def MountImage(chromeos_root, image, rootfs_mp, stateful_mp, unmount=False):
208  cmd_executer = command_executer.GetCommandExecuter()
209  command = GetImageMountCommand(chromeos_root, image, rootfs_mp, stateful_mp)
210  if unmount:
211    command = "%s --unmount" % command
212  retval = cmd_executer.RunCommand(command)
213  logger.GetLogger().LogFatalIf(retval, "Mount/unmount command failed!")
214  return retval
215
216
217def IsImageModdedForTest(chromeos_root, image):
218  rootfs_mp = tempfile.mkdtemp()
219  stateful_mp = tempfile.mkdtemp()
220  MountImage(chromeos_root, image, rootfs_mp, stateful_mp)
221  lsb_release_file = os.path.join(rootfs_mp, "etc/lsb-release")
222  is_test_image = "Test Build" in open(lsb_release_file).read()
223  MountImage(chromeos_root, image, rootfs_mp, stateful_mp, unmount=True)
224  return is_test_image
225
226
227def VerifyChromeChecksum(chromeos_root, image, remote):
228  cmd_executer = command_executer.GetCommandExecuter()
229  rootfs_mp = tempfile.mkdtemp()
230  stateful_mp = tempfile.mkdtemp()
231  MountImage(chromeos_root, image, rootfs_mp, stateful_mp)
232  image_chrome_checksum = FileUtils().Md5File("%s/opt/google/chrome/chrome" %
233                                              rootfs_mp)
234  MountImage(chromeos_root, image, rootfs_mp, stateful_mp, unmount=True)
235
236  command = "md5sum /opt/google/chrome/chrome"
237  [r, o, e] = cmd_executer.CrosRunCommand(command,
238                                          return_output=True,
239                                          chromeos_root=chromeos_root,
240                                          machine=remote)
241  device_chrome_checksum = o.split()[0]
242  if image_chrome_checksum.strip() == device_chrome_checksum.strip():
243    return True
244  else:
245    return False
246
247
248if __name__ == "__main__":
249  retval = Main(sys.argv)
250  sys.exit(retval)
251