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 tempfile
34import zipfile
35
36import build_image
37import common
38
39OPTIONS = common.OPTIONS
40
41OPTIONS.add_missing = False
42OPTIONS.rebuild_recovery = False
43
44def AddSystem(output_zip, prefix="IMAGES/", recovery_img=None, boot_img=None):
45  """Turn the contents of SYSTEM into a system image and store it in
46  output_zip."""
47
48  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "system.img")
49  if os.path.exists(prebuilt_path):
50    print "system.img already exists in %s, no need to rebuild..." % (prefix,)
51    return
52
53  def output_sink(fn, data):
54    ofile = open(os.path.join(OPTIONS.input_tmp, "SYSTEM", fn), "w")
55    ofile.write(data)
56    ofile.close()
57
58  if OPTIONS.rebuild_recovery:
59    print "Building new recovery patch"
60    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img,
61                             boot_img, info_dict=OPTIONS.info_dict)
62
63  block_list = common.MakeTempFile(prefix="system-blocklist-", suffix=".map")
64  imgname = BuildSystem(OPTIONS.input_tmp, OPTIONS.info_dict,
65                        block_list=block_list)
66  common.ZipWrite(output_zip, imgname, prefix + "system.img")
67  common.ZipWrite(output_zip, block_list, prefix + "system.map")
68
69
70def BuildSystem(input_dir, info_dict, block_list=None):
71  """Build the (sparse) system image and return the name of a temp
72  file containing it."""
73  return CreateImage(input_dir, info_dict, "system", block_list=block_list)
74
75
76def AddVendor(output_zip, prefix="IMAGES/"):
77  """Turn the contents of VENDOR into a vendor image and store in it
78  output_zip."""
79
80  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "vendor.img")
81  if os.path.exists(prebuilt_path):
82    print "vendor.img already exists in %s, no need to rebuild..." % (prefix,)
83    return
84
85  block_list = common.MakeTempFile(prefix="vendor-blocklist-", suffix=".map")
86  imgname = BuildVendor(OPTIONS.input_tmp, OPTIONS.info_dict,
87                        block_list=block_list)
88  common.ZipWrite(output_zip, imgname, prefix + "vendor.img")
89  common.ZipWrite(output_zip, block_list, prefix + "vendor.map")
90
91
92def BuildVendor(input_dir, info_dict, block_list=None):
93  """Build the (sparse) vendor image and return the name of a temp
94  file containing it."""
95  return CreateImage(input_dir, info_dict, "vendor", block_list=block_list)
96
97
98def CreateImage(input_dir, info_dict, what, block_list=None):
99  print "creating " + what + ".img..."
100
101  img = common.MakeTempFile(prefix=what + "-", suffix=".img")
102
103  # The name of the directory it is making an image out of matters to
104  # mkyaffs2image.  It wants "system" but we have a directory named
105  # "SYSTEM", so create a symlink.
106  try:
107    os.symlink(os.path.join(input_dir, what.upper()),
108               os.path.join(input_dir, what))
109  except OSError as e:
110    # bogus error on my mac version?
111    #   File "./build/tools/releasetools/img_from_target_files"
112    #     os.path.join(OPTIONS.input_tmp, "system"))
113    # OSError: [Errno 17] File exists
114    if e.errno == errno.EEXIST:
115      pass
116
117  image_props = build_image.ImagePropFromGlobalDict(info_dict, what)
118  fstab = info_dict["fstab"]
119  if fstab:
120    image_props["fs_type"] = fstab["/" + what].fs_type
121
122  if what == "system":
123    fs_config_prefix = ""
124  else:
125    fs_config_prefix = what + "_"
126
127  fs_config = os.path.join(
128      input_dir, "META/" + fs_config_prefix + "filesystem_config.txt")
129  if not os.path.exists(fs_config):
130    fs_config = None
131
132  fc_config = os.path.join(input_dir, "BOOT/RAMDISK/file_contexts")
133  if not os.path.exists(fc_config):
134    fc_config = None
135
136  # Override values loaded from info_dict.
137  if fs_config:
138    image_props["fs_config"] = fs_config
139  if fc_config:
140    image_props["selinux_fc"] = fc_config
141  if block_list:
142    image_props["block_list"] = block_list
143  if image_props.get("system_root_image") == "true":
144    image_props["ramdisk_dir"] = os.path.join(input_dir, "BOOT/RAMDISK")
145    image_props["ramdisk_fs_config"] = os.path.join(
146        input_dir, "META/boot_filesystem_config.txt")
147
148  succ = build_image.BuildImage(os.path.join(input_dir, what),
149                                image_props, img)
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  common.ZipWrite(output_zip, 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  common.ZipWrite(output_zip, 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  common.ZipClose(input_zip)
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  common.ZipClose(output_zip)
297
298def main(argv):
299  def option_handler(o, _):
300    if o in ("-a", "--add_missing"):
301      OPTIONS.add_missing = True
302    elif o in ("-r", "--rebuild_recovery",):
303      OPTIONS.rebuild_recovery = True
304    else:
305      return False
306    return True
307
308  args = common.ParseOptions(
309      argv, __doc__, extra_opts="ar",
310      extra_long_opts=["add_missing", "rebuild_recovery"],
311      extra_option_handler=option_handler)
312
313
314  if len(args) != 1:
315    common.Usage(__doc__)
316    sys.exit(1)
317
318  AddImagesToTargetFiles(args[0])
319  print "done."
320
321if __name__ == '__main__':
322  try:
323    common.CloseInheritedPipes()
324    main(sys.argv[1:])
325  except common.ExternalError as e:
326    print
327    print "   ERROR: %s" % (e,)
328    print
329    sys.exit(1)
330  finally:
331    common.Cleanup()
332