1#!/usr/bin/env python
2#
3# Copyright (C) 2014 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Given a target-files zipfile that does not contain images (ie, does
19not have an IMAGES/ top-level subdirectory), produce the images and
20add them to the zipfile.
21
22Usage:  add_img_to_target_files target_files
23"""
24
25import sys
26
27if sys.hexversion < 0x02070000:
28  print >> sys.stderr, "Python 2.7 or newer is required."
29  sys.exit(1)
30
31import errno
32import os
33import re
34import shutil
35import subprocess
36import tempfile
37import zipfile
38
39# missing in Python 2.4 and before
40if not hasattr(os, "SEEK_SET"):
41  os.SEEK_SET = 0
42
43import build_image
44import common
45
46OPTIONS = common.OPTIONS
47
48OPTIONS.add_missing = False
49OPTIONS.rebuild_recovery = False
50
51def AddSystem(output_zip, prefix="IMAGES/", recovery_img=None, boot_img=None):
52  """Turn the contents of SYSTEM into a system image and store it in
53  output_zip."""
54
55  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "system.img")
56  if os.path.exists(prebuilt_path):
57    print "system.img already exists in %s, no need to rebuild..." % (prefix,)
58    return
59
60  def output_sink(fn, data):
61     ofile = open(os.path.join(OPTIONS.input_tmp,"SYSTEM",fn), "w")
62     ofile.write(data)
63     ofile.close()
64
65  if OPTIONS.rebuild_recovery:
66    print("Building new recovery patch")
67    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img, boot_img,
68                             info_dict=OPTIONS.info_dict)
69
70  block_list = common.MakeTempFile(prefix="system-blocklist-", suffix=".map")
71  imgname = BuildSystem(OPTIONS.input_tmp, OPTIONS.info_dict,
72                        block_list=block_list)
73  with open(imgname, "rb") as f:
74    common.ZipWriteStr(output_zip, prefix + "system.img", f.read())
75  with open(block_list, "rb") as f:
76    common.ZipWriteStr(output_zip, prefix + "system.map", f.read())
77
78
79def BuildSystem(input_dir, info_dict, block_list=None):
80  """Build the (sparse) system image and return the name of a temp
81  file containing it."""
82  return CreateImage(input_dir, info_dict, "system", block_list=block_list)
83
84
85def AddVendor(output_zip, prefix="IMAGES/"):
86  """Turn the contents of VENDOR into a vendor image and store in it
87  output_zip."""
88
89  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "vendor.img")
90  if os.path.exists(prebuilt_path):
91    print "vendor.img already exists in %s, no need to rebuild..." % (prefix,)
92    return
93
94  block_list = common.MakeTempFile(prefix="vendor-blocklist-", suffix=".map")
95  imgname = BuildVendor(OPTIONS.input_tmp, OPTIONS.info_dict,
96                     block_list=block_list)
97  with open(imgname, "rb") as f:
98    common.ZipWriteStr(output_zip, prefix + "vendor.img", f.read())
99  with open(block_list, "rb") as f:
100    common.ZipWriteStr(output_zip, prefix + "vendor.map", f.read())
101
102
103def BuildVendor(input_dir, info_dict, block_list=None):
104  """Build the (sparse) vendor image and return the name of a temp
105  file containing it."""
106  return CreateImage(input_dir, info_dict, "vendor", block_list=block_list)
107
108
109def CreateImage(input_dir, info_dict, what, block_list=None):
110  print "creating " + what + ".img..."
111
112  img = common.MakeTempFile(prefix=what + "-", suffix=".img")
113
114  # The name of the directory it is making an image out of matters to
115  # mkyaffs2image.  It wants "system" but we have a directory named
116  # "SYSTEM", so create a symlink.
117  try:
118    os.symlink(os.path.join(input_dir, what.upper()),
119               os.path.join(input_dir, what))
120  except OSError, e:
121      # bogus error on my mac version?
122      #   File "./build/tools/releasetools/img_from_target_files", line 86, in AddSystem
123      #     os.path.join(OPTIONS.input_tmp, "system"))
124      # OSError: [Errno 17] File exists
125    if (e.errno == errno.EEXIST):
126      pass
127
128  image_props = build_image.ImagePropFromGlobalDict(info_dict, what)
129  fstab = info_dict["fstab"]
130  if fstab:
131    image_props["fs_type" ] = fstab["/" + what].fs_type
132
133  if what == "system":
134    fs_config_prefix = ""
135  else:
136    fs_config_prefix = what + "_"
137
138  fs_config = os.path.join(
139      input_dir, "META/" + fs_config_prefix + "filesystem_config.txt")
140  if not os.path.exists(fs_config): fs_config = None
141
142  fc_config = os.path.join(input_dir, "BOOT/RAMDISK/file_contexts")
143  if not os.path.exists(fc_config): fc_config = None
144
145  succ = build_image.BuildImage(os.path.join(input_dir, what),
146                                image_props, img,
147                                fs_config=fs_config,
148                                fc_config=fc_config,
149                                block_list=block_list)
150  assert succ, "build " + what + ".img image failed"
151
152  return img
153
154
155def AddUserdata(output_zip, prefix="IMAGES/"):
156  """Create an empty userdata image and store it in output_zip."""
157
158  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "userdata.img")
159  if os.path.exists(prebuilt_path):
160    print "userdata.img already exists in %s, no need to rebuild..." % (prefix,)
161    return
162
163  image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict,
164                                                    "data")
165  # We only allow yaffs to have a 0/missing partition_size.
166  # Extfs, f2fs must have a size. Skip userdata.img if no size.
167  if (not image_props.get("fs_type", "").startswith("yaffs") and
168      not image_props.get("partition_size")):
169    return
170
171  print "creating userdata.img..."
172
173  # The name of the directory it is making an image out of matters to
174  # mkyaffs2image.  So we create a temp dir, and within it we create an
175  # empty dir named "data", and build the image from that.
176  temp_dir = tempfile.mkdtemp()
177  user_dir = os.path.join(temp_dir, "data")
178  os.mkdir(user_dir)
179  img = tempfile.NamedTemporaryFile()
180
181  fstab = OPTIONS.info_dict["fstab"]
182  if fstab:
183    image_props["fs_type" ] = fstab["/data"].fs_type
184  succ = build_image.BuildImage(user_dir, image_props, img.name)
185  assert succ, "build userdata.img image failed"
186
187  common.CheckSize(img.name, "userdata.img", OPTIONS.info_dict)
188  output_zip.write(img.name, prefix + "userdata.img")
189  img.close()
190  os.rmdir(user_dir)
191  os.rmdir(temp_dir)
192
193
194def AddCache(output_zip, prefix="IMAGES/"):
195  """Create an empty cache image and store it in output_zip."""
196
197  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "cache.img")
198  if os.path.exists(prebuilt_path):
199    print "cache.img already exists in %s, no need to rebuild..." % (prefix,)
200    return
201
202  image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict,
203                                                    "cache")
204  # The build system has to explicitly request for cache.img.
205  if "fs_type" not in image_props:
206    return
207
208  print "creating cache.img..."
209
210  # The name of the directory it is making an image out of matters to
211  # mkyaffs2image.  So we create a temp dir, and within it we create an
212  # empty dir named "cache", and build the image from that.
213  temp_dir = tempfile.mkdtemp()
214  user_dir = os.path.join(temp_dir, "cache")
215  os.mkdir(user_dir)
216  img = tempfile.NamedTemporaryFile()
217
218  fstab = OPTIONS.info_dict["fstab"]
219  if fstab:
220    image_props["fs_type" ] = fstab["/cache"].fs_type
221  succ = build_image.BuildImage(user_dir, image_props, img.name)
222  assert succ, "build cache.img image failed"
223
224  common.CheckSize(img.name, "cache.img", OPTIONS.info_dict)
225  output_zip.write(img.name, prefix + "cache.img")
226  img.close()
227  os.rmdir(user_dir)
228  os.rmdir(temp_dir)
229
230
231def AddImagesToTargetFiles(filename):
232  OPTIONS.input_tmp, input_zip = common.UnzipTemp(filename)
233
234  if not OPTIONS.add_missing:
235    for n in input_zip.namelist():
236      if n.startswith("IMAGES/"):
237        print "target_files appears to already contain images."
238        sys.exit(1)
239
240  try:
241    input_zip.getinfo("VENDOR/")
242    has_vendor = True
243  except KeyError:
244    has_vendor = False
245
246  OPTIONS.info_dict = common.LoadInfoDict(input_zip)
247  if "selinux_fc" in OPTIONS.info_dict:
248    OPTIONS.info_dict["selinux_fc"] = os.path.join(
249        OPTIONS.input_tmp, "BOOT", "RAMDISK", "file_contexts")
250
251  input_zip.close()
252  output_zip = zipfile.ZipFile(filename, "a",
253                               compression=zipfile.ZIP_DEFLATED)
254
255  def banner(s):
256    print "\n\n++++ " + s + " ++++\n\n"
257
258  banner("boot")
259  prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", "boot.img")
260  boot_image = None
261  if os.path.exists(prebuilt_path):
262    print "boot.img already exists in IMAGES/, no need to rebuild..."
263    if OPTIONS.rebuild_recovery:
264      boot_image = common.GetBootableImage(
265          "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
266  else:
267    boot_image = common.GetBootableImage(
268        "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
269    if boot_image:
270      boot_image.AddToZip(output_zip)
271
272  banner("recovery")
273  recovery_image = None
274  prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", "recovery.img")
275  if os.path.exists(prebuilt_path):
276    print "recovery.img already exists in IMAGES/, no need to rebuild..."
277    if OPTIONS.rebuild_recovery:
278      recovery_image = common.GetBootableImage(
279          "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
280  else:
281    recovery_image = common.GetBootableImage(
282        "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
283    if recovery_image:
284      recovery_image.AddToZip(output_zip)
285
286  banner("system")
287  AddSystem(output_zip, recovery_img=recovery_image, boot_img=boot_image)
288  if has_vendor:
289    banner("vendor")
290    AddVendor(output_zip)
291  banner("userdata")
292  AddUserdata(output_zip)
293  banner("cache")
294  AddCache(output_zip)
295
296  output_zip.close()
297
298def main(argv):
299
300  def option_handler(o, a):
301    if o in ("-a", "--add_missing"):
302      OPTIONS.add_missing = True
303    elif o in ("-r", "--rebuild_recovery",):
304      OPTIONS.rebuild_recovery = True
305    else:
306      return False
307    return True
308
309  args = common.ParseOptions(argv, __doc__,
310                             extra_opts="ar",
311                             extra_long_opts=["add_missing",
312                                              "rebuild_recovery",
313                                              ],
314                             extra_option_handler=option_handler)
315
316
317  if len(args) != 1:
318    common.Usage(__doc__)
319    sys.exit(1)
320
321  AddImagesToTargetFiles(args[0])
322  print "done."
323
324if __name__ == '__main__':
325  try:
326    common.CloseInheritedPipes()
327    main(sys.argv[1:])
328  except common.ExternalError, e:
329    print
330    print "   ERROR: %s" % (e,)
331    print
332    sys.exit(1)
333  finally:
334    common.Cleanup()
335