scripts: generate binaries and binaries_b dictionaries
[platform/kernel/u-boot.git] / scripts / tizen / sd_fusing.py
1 #!/usr/bin/env python3
2
3 from functools import reduce
4
5 import argparse
6 import atexit
7 import errno
8 import logging
9 import os
10 import re
11 import shutil
12 import stat
13 import subprocess
14 import sys
15 import tarfile
16 import tempfile
17
18 __version__ = "1.0.0"
19
20 Format = False
21 Device = ""
22 File = ""
23 Yes = False
24
25 class DebugFormatter(logging.Formatter):
26     def format(self, record):
27         if record.levelno == logging.DEBUG:
28             record.debuginfo = "[{}:{}] ".format(os.path.basename(record.pathname), record.lineno)
29         else:
30             record.debuginfo = ''
31         return logging.Formatter.format(self, record)
32
33 class ColorFormatter(DebugFormatter):
34     _levelToColor = {
35         logging.CRITICAL: "\x1b[35;1m",
36         logging.ERROR: "\x1b[33;1m",
37         logging.WARNING: "\x1b[33;1m",
38         logging.INFO: "\x1b[0m",
39         logging.DEBUG: "\x1b[30;1m",
40         logging.NOTSET: "\x1b[30;1m"
41     }
42     def format(self, record):
43         record.levelcolor = self._levelToColor[record.levelno]
44         record.msg = record.msg
45         return super().format(record)
46
47 class ColorStreamHandler(logging.StreamHandler):
48     def __init__(self, stream=None, format=None, datefmt=None, style='%', cformat=None):
49         logging.StreamHandler.__init__(self, stream)
50         if os.isatty(self.stream.fileno()):
51             self.formatter = ColorFormatter(cformat, datefmt, style)
52             self.terminator = "\x1b[0m\n"
53         else:
54             self.formatter = DebugFormatter(format, datefmt, style)
55
56 class Partition:
57     def __init__(self, name, size, start=None, ptype=None, fstype="raw", bootable=False, **kwargs):
58         self.name = name
59         self.size = size
60         self.start = start
61         self.ptype = ptype
62         self.bootable = bootable
63     def __str__(self):
64         output = []
65         if self.start:
66             output.append(f"start={self.start}MiB")
67         if type(self.size) == int and self.size >= 0:
68             output.append(f"size={self.size}MiB")
69         if self.name:
70             output.append(f"name={self.name}")
71         output.append(f"type={self.ptype}")
72         if self.bootable:
73                        output.append("bootable")
74         return ", ".join(output) + "\n"
75
76 class Label:
77     def __init__(self, part_table, ltype):
78         self.ltype = ltype
79         if ltype == 'gpt':
80             ptype = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
81         elif ltype == 'dos':
82             ptype = '83'
83         self.part_table = []
84         for part in part_table:
85             part["ptype"] = part.get("ptype", ptype)
86             self.part_table.append(Partition(**part))
87     def __str__(self):
88         output = f"label: {self.ltype}\n"
89         for part in self.part_table:
90             output += str(part)
91         return output
92
93 class SdFusingTarget:
94     def __init__(self, device, ltype):
95         # TODO: make a copy of a sublcass part_table
96         self.with_super = False
97         self.device = device
98         total_size = device_size(device)
99         self.user_size = total_size - self.reserved_space - \
100             reduce(lambda x, y: x + (y["size"] or 0), self.part_table, 0)
101         if self.user_size < 100:
102             logging.error(f"Not enough space for user data ({self.user_size}). Use larger storage.")
103             raise OSError(errno.ENOSPC, os.strerror(errno.ENOSPC), device)
104         # self.user_partition counts from 0
105         self.part_table[self.user_partition]["size"] = self.user_size
106         self.label = Label(self.part_table, ltype)
107         self.binaries = self._get_binaries('binaries')
108
109     def _get_binaries(self, key):
110         binaries = {}
111         for i, p in enumerate(self.part_table):
112             b = p.get(key, None)
113             if b is None:
114                 continue
115             if isinstance(b, str):
116                 binaries[b] = i + 1
117             elif isinstance(b, list):
118                 for f in b:
119                     binaries[f] = i + 1
120         return binaries
121
122     def get_partition_index(self, binary):
123         return self.binaries.get(binary, None)
124
125     params = ()
126     def initialize_parameters(self):
127         pass
128
129 class SdFusingTargetAB(SdFusingTarget):
130     def __init__(self, device, ltype):
131         super().__init__(device, ltype)
132         self.binaries_b = self._get_binaries('binaries_b')
133
134     def get_partition_index(self, binary):
135         if self.update == 'b':
136             return self.binaries_b.get(binary, None)
137         return self.binaries.get(binary, None)
138
139 class RpiInitParams:
140     def initialize_parameters(self):
141         logging.debug("Initializing parameterss")
142         n = None
143         for i, p in enumerate(self.part_table):
144             if p['name'] == 'inform':
145                 n = i + 1;
146                 break
147         d = "/dev/" + get_partition_device(self.device, n)
148
149         argv = ['tune2fs', '-O', '^metadata_csum', d]
150         logging.debug(" ".join(argv))
151         subprocess.run(argv,
152                        stdin=subprocess.DEVNULL,
153                        stdout=None, stderr=None)
154
155         with tempfile.TemporaryDirectory() as mnt:
156             argv = ['mount', '-t', 'ext4', d, mnt]
157             logging.debug(" ".join(argv))
158             proc = subprocess.run(argv,
159                                   stdin=subprocess.DEVNULL,
160                                   stdout=None, stderr=None)
161             if proc.returncode != 0:
162                 logging.error("Failed to mount {d} in {mnt}")
163                 return
164             for param, value in self.params:
165                 with open(os.path.join(mnt, param), 'w') as f:
166                     f.write(value + '\n')
167             argv = ['umount', d]
168             logging.debug(" ".join(argv))
169             subprocess.run(argv,
170                            stdin=subprocess.DEVNULL,
171                            stdout=None, stderr=None)
172
173 class Rpi3(RpiInitParams, SdFusingTarget):
174     long_name = "Raspberry Pi 3"
175     part_table = [
176         {"size": 64,   "fstype": "vfat", "name": "boot", "start": 4, "ptype": "0xe", "bootable": True,
177          "binaries": "boot.img"},
178         {"size": 3072, "fstype": "ext4", "name": "rootfs",
179          "binaries": "rootfs.img"},
180         {"size": 1344, "fstype": "ext4", "name": "system-data",
181          "binaries": "system-data.img"},
182         {"size": None, "ptype":  "5",    "name": "extended", "start": 4484},
183         {"size": None, "fstype": "ext4", "name": "user",
184          "binaries": "user.img"},
185         {"size": 32,   "fstype": "ext4", "name": "modules",
186          "binaries": "modules.img"},
187         {"size": 32,   "fstype": "ext4", "name": "ramdisk",
188          "binaries": "ramdisk.img"},
189         {"size": 32,   "fstype": "ext4", "name": "ramdisk-recovery",
190          "binaries": "ramdisk-recovery.img"},
191         {"size": 8,    "fstype": "ext4", "name": "inform"},
192         {"size": 256,  "fstype": "ext4", "name": "hal",
193          "binaries": "hal.img"},
194         {"size": 125,  "fstype": "ext4", "name": "reserved2"},
195     ]
196     params = (('reboot-param.bin', ''),)
197
198     def __init__(self, device, args):
199         self.reserved_space = 12
200         self.user_partition = 4
201         super().__init__(device, "dos")
202
203 class Rpi4Super(RpiInitParams, SdFusingTargetAB):
204     long_name = "Raspberry Pi 4 w/ super partition"
205     part_table = [
206         {"size": 64,   "fstype": "vfat", "name": "boot_a","start": 4,
207          "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
208          "binaries": "boot.img"},
209         {"size": 6656, "fstype": "ext4", "name": "super",
210          "binaries": "super.img"},
211         {"size": 1344, "fstype": "ext4", "name": "system-data",
212          "binaries": "system-data.img"},
213         {"size": 36,   "fstype": "raw",  "name": "none"},
214         {"size": None, "fstype": "ext4", "name": "user",
215          "binaries": "user.img"},
216         {"size": 32,   "fstype": "ext4", "name": "module_a",
217          "binaries": "modules.img"},
218         {"size": 32,   "fstype": "ext4", "name": "ramdisk_a",
219          "binaries": "ramdisk.img"},
220         {"size": 32,   "fstype": "ext4", "name": "ramdisk-recovery_a",
221          "binaries": "ramdisk-recovery.img"},
222         {"size": 8,    "fstype": "ext4", "name": "inform"},
223         {"size": 64,   "fstype": "vfat", "name": "boot_b",
224          "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
225          "binaries_b": "boot.img"},
226         {"size": 32,   "fstype": "ext4", "name": "module_b",
227          "binaries_b": "modules.img"},
228         {"size": 32,   "fstype": "ext4", "name": "ramdisk_b",
229          "binaries_b": "ramdisk.img"},
230         {"size": 32,   "fstype": "ext4", "name": "ramdisk-recovery_b",
231          "binaries_b": "ramdisk-recovery.img"},
232         {"size": 4,    "fstype": "ext4", "name": "reserved0"},
233         {"size": 64,   "fstype": "ext4", "name": "reserved1"},
234         {"size": 125,  "fstype": "ext4", "name": "reserved2"}
235     ]
236     params = (('reboot-param.bin', 'norm'),
237               ('reboot-param.info', 'norm'),
238               ('partition-ab.info', 'a'),
239               ('partition-ab-cloned.info', '1'),
240               ('upgrade-status.info', '0'),
241               ('partition-a-status.info', 'ok'),
242               ('partition-b-status.info', 'ok'))
243
244     def __init__(self, device, args):
245         self.reserved_space = 8
246         self.user_partition = 4
247         self.update = args.update
248         super().__init__(device, "gpt")
249         self.with_super = True
250         self.super_alignment = 1048576
251
252 class Rpi4(RpiInitParams, SdFusingTargetAB):
253     long_name = "Raspberry Pi 4"
254     part_table = [
255         {"size": 64,   "fstype": "vfat", "name": "boot_a", "start": 4,
256          "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
257          "binaries": "boot.img"},
258         {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
259          "binaries": "rootfs.img"},
260         {"size": 1344, "fstype": "ext4", "name": "system-data",
261          "binaries": "system-data.img"},
262         {"size": 36,   "fstype": "raw",  "name": "none"},
263         {"size": None, "fstype": "ext4", "name": "user",
264          "binaries": "user.img"},
265         {"size": 32,   "fstype": "ext4", "name": "module_a",
266          "binaries": "modules.img"},
267         {"size": 32,   "fstype": "ext4", "name": "ramdisk_a",
268          "binaries": "ramdisk.img"},
269         {"size": 32,   "fstype": "ext4", "name": "ramdisk-recovery_a",
270          "binaries": "ramdisk-recovery.img"},
271         {"size": 8,    "fstype": "ext4", "name": "inform"},
272         {"size": 256,  "fstype": "ext4", "name": "hal_a",
273          "binaries": "hal.img"},
274         {"size": 64,   "fstype": "vfat", "name": "boot_b",
275          "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
276          "binaries_b": "boot.img"},
277         {"size": 3072, "fstype": "ext4", "name": "rootfs_b",
278          "binaries_b": "rootfs.img"},
279         {"size": 32,   "fstype": "ext4", "name": "module_b",
280          "binaries_b": "modules.img"},
281         {"size": 32,   "fstype": "ext4", "name": "ramdisk_b",
282          "binaries_b": "ramdisk.img"},
283         {"size": 32,   "fstype": "ext4", "name": "ramdisk-recovery_b",
284          "binaries_b": "ramdisk-recovery.img"},
285         {"size": 256,  "fstype": "ext4", "name": "hal_b",
286          "binaries_b": "hal.img"},
287         {"size": 4,    "fstype": "ext4", "name": "param"},
288         {"size": 64,  "fstype": "ext4", "name": "reserved1"},
289         {"size": 125,  "fstype": "ext4", "name": "reserved2"},
290     ]
291     params = (('reboot-param.bin', 'norm'),
292               ('reboot-param.info', 'norm'),
293               ('partition-ab.info', 'a'),
294               ('partition-ab-cloned.info', '1'),
295               ('upgrade-status.info', '0'),
296               ('partition-a-status.info', 'ok'),
297               ('partition-b-status.info', 'ok'))
298
299     def __init__(self, device, args):
300         self.reserved_space = 5
301         self.user_partition = 4
302         self.update = args.update
303         super().__init__(device, "gpt")
304
305 class RV64(SdFusingTarget):
306     long_name = "QEMU RISC-V 64-bit"
307     part_table = [
308         {"size": 2,    "fstype": "raw",  "name": "SPL", "start": 4,
309          "ptype": "2E54B353-1271-4842-806F-E436D6AF6985",
310          "binaries": ""},
311         {"size": 4,    "fstype": "raw",  "name": "u-boot",
312          "ptype": "5B193300-FC78-40CD-8002-E86C45580B47",
313          "binaries": ["u-boot.img", "u-boot.itb"],},
314         {"size": 292,  "fstype": "vfat", "name": "boot_a",
315          "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
316          "binaries": "boot.img"},
317         {"size": 36,   "fstype": "raw",  "name": "none"},
318         {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
319          "binaries": "rootfs.img"},
320         {"size": 1344, "fstype": "ext4", "name": "system-data",
321          "binaries": "system-data.img"},
322         {"size": None, "fstype": "ext4", "name": "user",
323          "binaries": "user.img"},
324         {"size": 32,   "fstype": "ext4", "name": "module_a",
325          "binaries": "modules.img"},
326         {"size": 32,   "fstype": "ext4", "name": "ramdisk_a",
327          "binaries": "ramdisk.img"},
328         {"size": 32,   "fstype": "ext4", "name": "ramdisk-recovery_a",
329          "binaries": "ramdisk-recovery.img"},
330         {"size": 8,    "fstype": "raw",  "name": "inform"},
331         {"size": 256,  "fstype": "ext4", "name": "hal_a",
332          "binaries": "hal.img"},
333         {"size": 4,    "fstype": "raw",  "name": "reserved0"},
334         {"size": 64,   "fstype": "raw",  "name": "reserved1"},
335         {"size": 125,  "fstype": "raw",  "name": "reserved2"},
336     ]
337
338     def __init__(self, device, args):
339         self.user_partition = 6
340         self.reserved_space = 5
341         super().__init__(device, 'gpt')
342
343 class VF2(RV64):
344     long_name = "VisionFive2"
345
346 TARGETS = {
347     'rpi3': Rpi3,
348     'rpi4': Rpi4,
349     'rpi4s': Rpi4Super,
350     'vf2': VF2,
351     'rv64': RV64
352 }
353
354 def device_size(device):
355     argv = ["sfdisk", "-s", device]
356     logging.debug(" ".join(argv))
357     proc = subprocess.run(argv,
358                           stdout=subprocess.PIPE)
359     size = int(proc.stdout.decode('utf-8').strip()) >> 10
360     logging.debug(f"{device} size {size}MiB")
361     return size
362
363 def check_sfdisk():
364     proc = subprocess.run(['sfdisk', '-v'],
365                           stdout=subprocess.PIPE)
366     version = proc.stdout.decode('utf-8').strip()
367     logging.debug(f"Found {version}")
368     major, minor = [int(x) for x in re.findall('[0-9]+', version)][0:2]
369     support_delete = False
370
371     if major < 2 or major == 2 and minor < 26:
372         log.error(f"Your sfdisk {major}.{minor}.{patch} is too old.")
373         return False,False
374     elif major == 2 and minor >= 28:
375         support_delete = True
376
377     return True, support_delete
378
379 def mkpart(args, target):
380     global Device
381     new, support_delete = check_sfdisk()
382
383     if not new:
384         logging.error('sfdisk too old')
385         sys.exit(1)
386
387     with open('/proc/self/mounts') as mounts:
388         device_kname = '/dev/' + get_device_kname(Device)
389         device_re = re.compile(device_kname + '[^ ]*')
390         logging.debug(f"Checking for mounted partitions on {device_kname}")
391         for m in mounts:
392             match = device_re.match(m)
393             if match:
394                 logging.warning('Found mounted device: ' + match[0])
395                 argv = ['umount', match[0]]
396                 logging.debug(" ".join(argv))
397                 proc = subprocess.run(argv)
398                 if proc.returncode != 0:
399                     logging.error(f"Failed to unmount {match[0]}")
400                     sys.exit(1)
401
402     if support_delete:
403         logging.info("Removing old partitions")
404         argv = ['sfdisk', '--delete', Device]
405         logging.debug(" ".join(argv))
406         proc = subprocess.run(argv)
407         if proc.returncode != 0:
408             logging.error(f"Failed to remove the old partitions from {Device}")
409     else:
410         logging.info("Removing old partition table")
411         argv = ['dd', 'if=/dev/zero', 'of=' + Device,
412                 'bs=512', 'count=32', 'conv=notrunc']
413         logging.debug(" ".join(argv))
414         proc = subprocess.run(argv)
415         if proc.returncode != 0:
416             logging.error(f"Failed to clear the old partition table on {Device}")
417             sys.exit(1)
418
419     logging.debug("New partition table:\n" + str(target.label))
420     argv = ['sfdisk', '--wipe-partitions', 'always', Device]
421     logging.debug(" ".join(argv))
422     proc = subprocess.run(argv,
423                           stdout=None,
424                           stderr=None,
425                           input=str(target.label).encode())
426     if proc.returncode != 0:
427         logging.error(f"Failed to create partition a new table on {Device}")
428         logging.error(f"New partition table:\n" + str(target.label))
429         sys.exit(1)
430
431     for i, part in enumerate(target.part_table):
432         d = "/dev/" + get_partition_device(target.device, i+1)
433         if not 'fstype' in part:
434             logging.debug(f"Filesystem not defined for {d}, skipping")
435             continue
436         logging.debug(f"Formatting {d} as {part['fstype']}")
437         if part['fstype'] == 'vfat':
438             argv = ['mkfs.vfat', '-F', '16', '-n', part['name'], d]
439             logging.debug(" ".join(argv))
440             proc = subprocess.run(argv,
441                                   stdin=subprocess.DEVNULL,
442                                   stdout=None, stderr=None)
443             if proc.returncode != 0:
444                 log.error(f"Failed to create FAT filesystem on {d}")
445                 sys.exit(1)
446         elif part['fstype'] == 'ext4':
447             argv = ['mkfs.ext4', '-q', '-L', part['name'], d]
448             logging.debug(" ".join(argv))
449             proc = subprocess.run(argv,
450                                   stdin=subprocess.DEVNULL,
451                                   stdout=None, stderr=None)
452             if proc.returncode != 0:
453                 log.error(f"Failed to create ext4 filesystem on {d}")
454                 sys.exit(1)
455         elif part['fstype'] == 'raw':
456             pass
457     target.initialize_parameters()
458
459 def check_args(args):
460     global Format
461     global Yes
462
463     logging.info(f"Device: {args.device}")
464
465     if args.binaries and len(args.binaries) > 0:
466         logging.info("Fusing binar{}: {}".format("y" if len(args.binaries) == 1 else "ies",
467                      ", ".join(args.binaries)))
468
469     if args.YES:
470         Yes = True
471
472     if args.create:
473         Format = True
474         Yes = True
475
476     if args.format:
477         if Yes:
478             Format = True
479         else:
480             response = input(f"{args.device} will be formatted. Continue? [y/N] ")
481             if response.lower() in ('y', 'yes'):
482                 Format = True
483             else:
484                 Format = False
485
486 def check_device(args):
487     global Format
488     global Device
489     Device = args.device
490
491     if args.create:
492         if os.path.exists(Device):
493             logging.error(f"Failed to create '{Device}', the file alread exists")
494             sys.exit(1)
495         else:
496             argv = ["dd", "if=/dev/zero", f"of={Device}",
497                     "conv=sparse", "bs=1M", f"count={args.size}"]
498             logging.debug(" ".join(argv))
499             rc = subprocess.run(argv)
500             if rc.returncode != 0:
501                 logging.error("Failed to create the backing file")
502                 sys.exit(1)
503
504     if os.path.isfile(Device):
505         global File
506         File = Device
507
508         argv = ["losetup", "--show", "--partscan", "--find", f"{File}"]
509         logging.debug(" ".join(argv))
510         proc = subprocess.run(argv,
511                               stdout=subprocess.PIPE)
512         Device = proc.stdout.decode('utf-8').strip()
513         if proc.returncode != 0:
514             logging.error(f"Failed to attach {File} to a loopback device")
515             sys.exit(1)
516         logging.debug(f"Loop device found: {Device}")
517         atexit.register(lambda: subprocess.run(["losetup", "-d", Device]))
518
519     try:
520         s = os.stat(Device)
521         if not stat.S_ISBLK(s.st_mode):
522             raise TypeError
523     except FileNotFoundError:
524         logging.error(f"No such device: {Device}")
525         sys.exit(1)
526     except TypeError:
527         logging.error(f"{Device} is not a block device")
528         sys.exit(1)
529
530 def check_partition_format(args, target):
531     global Format
532     global Device
533
534     if not Format:
535         logging.info(f"Skip formatting of {Device}".format(Device))
536         return
537     logging.info(f"Start formatting of {Device}")
538     mkpart(args, target)
539     logging.info(f"{Device} formatted")
540
541 def check_ddversion():
542     proc = subprocess.run(["dd", "--version"],
543                             stdout=subprocess.PIPE)
544     version = proc.stdout.decode('utf-8').split('\n')[0].strip()
545     logging.debug(f"Found {version}")
546     major, minor = (int(x) for x in re.findall('[0-9]+', version))
547
548     if major < 8 or major == 8 and minor < 24:
549         return False
550
551     return True
552
553 def get_partition_device(device, idx):
554     argv = ['lsblk', device, '-o', 'TYPE,KNAME']
555     logging.debug(" ".join(argv))
556     proc = subprocess.run(argv,
557                           stdout=subprocess.PIPE)
558     if proc.returncode != 0:
559         logging.error("lsblk has failed")
560         return None
561     part_re = re.compile(f"^part\s+(.*[^0-9]{idx})$")
562     for l in proc.stdout.decode('utf-8').splitlines():
563         match = part_re.match(l)
564         if match:
565             return match[1]
566     return None
567
568 def get_device_kname(device):
569     argv = ['lsblk', device, '-o', 'TYPE,KNAME']
570     logging.debug(" ".join(argv))
571     proc = subprocess.run(argv,
572                           stdout=subprocess.PIPE)
573     for l in proc.stdout.decode('utf-8').splitlines():
574         match = re.search(f"^(disk|loop)\s+(.*)", l)
575         if match:
576             return match[2]
577     return None
578
579 def do_fuse_file(f, name, target):
580     idx = target.get_partition_index(name)
581     if idx is None:
582         logging.info(f"No partition defined for {name}, skipping.")
583         return
584     pdevice = "/dev/" + get_partition_device(Device, idx)
585     argv = ['dd', 'bs=4M',
586             'oflag=direct',
587             'iflag=fullblock',
588             'conv=nocreat',
589             f"of={pdevice}"]
590     logging.debug(" ".join(argv))
591     proc_dd = subprocess.Popen(argv,
592                                bufsize=(4 << 20),
593                                stdin=subprocess.PIPE,
594                                stdout=None, stderr=None)
595     logging.info(f"Writing {name} to {pdevice}")
596     buf = f.read(4 << 20)
597     while len(buf) > 0:
598         #TODO: progress
599         proc_dd.stdin.write(buf)
600         buf = f.read(4 << 20)
601     proc_dd.communicate()
602     logging.info("Done")
603     #TODO: verification
604
605 #TODO: functions with the target argument should probably
606 #      be part of some class
607
608 def get_aligned_size(size, target):
609     return target.super_alignment*int(1+(size-1)/target.super_alignment)
610
611 def do_fuse_image_super(tmpd, target):
612     metadata_slots = 2
613     metadata_size = 65536
614     metadata_aligned_size = get_aligned_size(metadata_size, target)
615
616     hal_path = os.path.join(tmpd, 'hal.img')
617     rootfs_path = os.path.join(tmpd, 'rootfs.img')
618     super_path = os.path.join(tmpd, 'super.img')
619
620     try:
621         hal_size = os.stat(hal_path).st_size
622         rootfs_size = os.stat(rootfs_path).st_size
623     except FileNotFoundError as e:
624         fn = os.path.split(e.filename)[-1]
625         logging.warning(f"{fn} is missing, skipping super partition image")
626         return
627
628     hal_aligned_size = get_aligned_size(hal_size, target)
629     rootfs_aligned_size = get_aligned_size(rootfs_size, target)
630     group_size = hal_aligned_size + rootfs_aligned_size
631     super_size = metadata_aligned_size + 2 * group_size
632
633     argv = ["lpmake", "-F",
634             f"-o={super_path}",
635             f"--device-size={super_size}",
636             f"--metadata-size={metadata_size}",
637             f"--metadata-slots={metadata_slots}",
638             "-g", f"tizen_a:{group_size}",
639             "-p", f"rootfs_a:none:{rootfs_aligned_size}:tizen_a",
640             "-p", f"hal_a:none:{hal_aligned_size}:tizen_a",
641             "-g", f"tizen_b:{group_size}",
642             "-p", f"rootfs_b:none:{rootfs_aligned_size}:tizen_b",
643             "-p", f"hal_b:none:{hal_aligned_size}:tizen_b",
644             "-i", f"rootfs_a={rootfs_path}",
645             "-i", f"rootfs_b={rootfs_path}",
646             "-i", f"hal_a={hal_path}",
647             "-i", f"hal_b={hal_path}"]
648     logging.debug(" ".join(argv))
649     proc = subprocess.run(argv,
650                           stdin=subprocess.DEVNULL,
651                           stdout=None, stderr=None)
652
653     if proc.returncode != 0:
654         logging.error("Failed to create super.img")
655     do_fuse_image(super_path, target)
656
657 def do_fuse_image_tarball(tarball, tmpd, target):
658     with tarfile.open(tarball) as tf:
659         for entry in tf:
660             if target.with_super:
661                 if entry.name in('hal.img', 'rootfs.img'):
662                     tf.extract(entry, path=tmpd)
663                     continue
664             f = tf.extractfile(entry)
665             do_fuse_file(f, entry.name, target)
666
667 def do_fuse_image(img, target):
668     with open(img, 'rb') as f:
669         do_fuse_file(f, os.path.basename(img), target)
670
671 def fuse_image(args, target):
672     global Yes
673
674     if args.binaries is None or len(args.binaries) == 0:
675         return
676
677     if not Yes and not Format:
678         print(f"The following images will be written to {args.device} and the "
679               "existing data will be lost.\n")
680         for b in args.binaries:
681             print("  " + b)
682         response = input("\nContinue? [y/N] ")
683         if not response.lower() in ('y', 'yes'):
684             return
685
686     with tempfile.TemporaryDirectory() as tmpd:
687         for b in args.binaries:
688             if re.search('\.(tar|tar\.gz|tgz)$', b):
689                 do_fuse_image_tarball(b, tmpd, target)
690             else:
691                 fn = os.path.split(b)[-1]
692                 if target.with_super and fn in ('rootfs.img', 'hal.img'):
693                     shutil.copy(b, os.path.join(tmpd, fn))
694                 else:
695                     do_fuse_image(b, target)
696
697         if target.with_super:
698             do_fuse_image_super(tmpd, target)
699
700 if __name__ == '__main__':
701     parser = argparse.ArgumentParser(description="For {}, version {}".format(
702         ", ".join([v.long_name for k,v in TARGETS.items()]),
703         __version__
704     ))
705     parser.add_argument("-b", "--binary", action="extend", dest="binaries",
706                         nargs='+',
707                         help="binary to flash, may be used multiple times")
708     parser.add_argument("--create", action="store_true",
709                         help="create the backing file and format the loopback device")
710     parser.add_argument("--debug", action="store_const", const="debug",
711                         default="warning", dest="log_level",
712                         help="set log level to DEBUG")
713     parser.add_argument("-d", "--device",
714                         help="device node or loopback backing file")
715     parser.add_argument("--format", action="store_true",
716                         help="create new partition table on the target device")
717     parser.add_argument("--log-level", dest="log_level", default="warning",
718                         help="Verbosity, possible values: debug, info, warning, "
719                         "error, critical (default: warning)")
720     parser.add_argument("--size", type=int, default=8192,
721                         help="size of the backing file to create (in MiB)")
722     parser.add_argument("-t", "--target", required=True,
723                         help="Target device model. Use `--target list`"
724                         " to show supported devices.")
725     parser.add_argument("--update", choices=['a', 'b'], default=None,
726                         help="Choose partition set to update: a or b.")
727     parser.add_argument("--version", action="version",
728                         version=f"%(prog)s {__version__}")
729     parser.add_argument("--YES", action="store_true",
730                         help="agree to destroy data on the DEVICE")
731     args = parser.parse_args()
732
733     if args.target == 'list':
734         print("\nSupported devices:\n")
735         for k,v in TARGETS.items():
736             print(f"  {k:6}  {v.long_name}")
737         sys.exit(0)
738
739     if args.device is None:
740         parser.error('-d/--device argument is required for normal operation')
741
742     conh = ColorStreamHandler(format='%(asctime)s.%(msecs)d %(debuginfo)s%(levelname)-8s %(message)s',
743                               cformat='%(asctime)s.%(msecs)d %(debuginfo)s%(levelcolor)s%(message)s',
744                               datefmt='%Y-%m-%dT%H:%M:%S')
745     log_handlers = [conh]
746     logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s',
747                         handlers=log_handlers,
748                         level=args.log_level.upper())
749
750     logging.debug(" ".join(sys.argv))
751     check_args(args)
752     check_device(args)
753
754     target = TARGETS[args.target](Device, args)
755
756     check_partition_format(args, target)
757     fuse_image(args, target)
758     subprocess.run(['sync'],
759                    stdin=subprocess.DEVNULL,
760                    stdout=None, stderr=None )