3 from functools import reduce
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)
31 return logging.Formatter.format(self, record)
33 class ColorFormatter(DebugFormatter):
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"
42 def format(self, record):
43 record.levelcolor = self._levelToColor[record.levelno]
44 record.msg = record.msg
45 return super().format(record)
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"
54 self.formatter = DebugFormatter(format, datefmt, style)
57 def __init__(self, name, size, start=None, ptype=None, fstype="raw", bootable=False, **kwargs):
62 self.bootable = bootable
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")
70 output.append(f"name={self.name}")
71 output.append(f"type={self.ptype}")
73 output.append("bootable")
74 return ", ".join(output) + "\n"
77 def __init__(self, part_table, ltype):
80 ptype = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
84 for part in part_table:
85 part["ptype"] = part.get("ptype", ptype)
86 self.part_table.append(Partition(**part))
88 output = f"label: {self.ltype}\n"
89 for part in self.part_table:
94 def __init__(self, device, ltype):
95 # TODO: make a copy of a sublcass part_table
96 self.with_super = False
98 total_size = device_size(device)
100 if hasattr(self, 'user_partition'):
101 self.user_size = total_size - self.reserved_space - \
102 reduce(lambda x, y: x + (y["size"] or 0), self.part_table, 0)
103 if self.user_size < 100:
104 logging.error(f"Not enough space for user data ({self.user_size}). Use larger storage.")
105 raise OSError(errno.ENOSPC, os.strerror(errno.ENOSPC), device)
106 # self.user_partition counts from 0
107 self.part_table[self.user_partition]["size"] = self.user_size
109 self.label = Label(self.part_table, ltype)
110 self.binaries = self._get_binaries('binaries')
112 def apply_partition_sizes(self, partition_sizes):
113 if partition_sizes is None or len(partition_sizes) == 0:
116 for name, size in partition_sizes.items():
118 for part in self.part_table:
119 if part['name'] == name:
122 logging.debug(f"overriding partition:{name}, old-size:{psize} MiB new-size:{size} MiB")
123 resized_count = resized_count + 1
124 if resized_count == 0:
125 logging.error(f"partition:{name} not found when attempting to apply_partition_sizes")
126 resized_total = resized_total + resized_count
129 def _get_binaries(self, key):
131 for i, p in enumerate(self.part_table):
135 if isinstance(b, str):
137 elif isinstance(b, list):
142 def get_partition_index(self, binary):
143 return self.binaries.get(binary, None)
146 def initialize_parameters(self):
149 class SdFusingTargetAB(SdFusingTarget):
150 def __init__(self, device, ltype):
151 super().__init__(device, ltype)
152 self.binaries_b = self._get_binaries('binaries_b')
154 def get_partition_index(self, binary):
155 if self.update == 'b':
156 return self.binaries_b.get(binary, None)
157 return self.binaries.get(binary, None)
160 def initialize_parameters(self):
161 logging.debug("Initializing parameterss")
163 for i, p in enumerate(self.part_table):
164 if p['name'] == 'inform':
167 d = "/dev/" + get_partition_device(self.device, n)
169 argv = ['tune2fs', '-O', '^metadata_csum', d]
170 logging.debug(" ".join(argv))
172 stdin=subprocess.DEVNULL,
173 stdout=None, stderr=None)
175 with tempfile.TemporaryDirectory() as mnt:
176 argv = ['mount', '-t', 'ext4', d, mnt]
177 logging.debug(" ".join(argv))
178 proc = subprocess.run(argv,
179 stdin=subprocess.DEVNULL,
180 stdout=None, stderr=None)
181 if proc.returncode != 0:
182 logging.error("Failed to mount {d} in {mnt}")
184 for param, value in self.params:
185 with open(os.path.join(mnt, param), 'w') as f:
186 f.write(value + '\n')
188 logging.debug(" ".join(argv))
190 stdin=subprocess.DEVNULL,
191 stdout=None, stderr=None)
193 class Rpi3(InitParams, SdFusingTarget):
194 long_name = "Raspberry Pi 3"
196 {"size": 64, "fstype": "vfat", "name": "boot", "start": 4, "ptype": "0xe", "bootable": True,
197 "binaries": "boot.img"},
198 {"size": 3072, "fstype": "ext4", "name": "rootfs",
199 "binaries": "rootfs.img"},
200 {"size": 1344, "fstype": "ext4", "name": "system-data",
201 "binaries": "system-data.img"},
202 {"size": None, "ptype": "5", "name": "extended", "start": 4484},
203 {"size": None, "fstype": "ext4", "name": "user",
204 "binaries": "user.img"},
205 {"size": 32, "fstype": "ext4", "name": "modules",
206 "binaries": "modules.img"},
207 {"size": 32, "fstype": "ext4", "name": "ramdisk",
208 "binaries": "ramdisk.img"},
209 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery",
210 "binaries": "ramdisk-recovery.img"},
211 {"size": 8, "fstype": "ext4", "name": "inform"},
212 {"size": 256, "fstype": "ext4", "name": "hal",
213 "binaries": "hal.img"},
214 {"size": 125, "fstype": "ext4", "name": "reserved2"},
216 params = (('reboot-param.bin', ''),)
218 def __init__(self, device, args):
219 self.reserved_space = 12
220 self.user_partition = 4
221 super().__init__(device, "dos")
223 class Rpi4Super(InitParams, SdFusingTargetAB):
224 long_name = "Raspberry Pi 4 w/ super partition"
226 {"size": 64, "fstype": "vfat", "name": "boot_a","start": 4,
227 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
228 "binaries": "boot.img"},
229 {"size": 6656, "fstype": "ext4", "name": "super",
230 "binaries": "super.img"},
231 {"size": 1344, "fstype": "ext4", "name": "system-data",
232 "binaries": "system-data.img"},
233 {"size": 36, "fstype": "raw", "name": "none"},
234 {"size": None, "fstype": "ext4", "name": "user",
235 "binaries": "user.img"},
236 {"size": 32, "fstype": "ext4", "name": "module_a",
237 "binaries": "modules.img"},
238 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
239 "binaries": "ramdisk.img"},
240 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
241 "binaries": "ramdisk-recovery.img"},
242 {"size": 8, "fstype": "ext4", "name": "inform"},
243 {"size": 64, "fstype": "vfat", "name": "boot_b",
244 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
245 "binaries_b": "boot.img"},
246 {"size": 32, "fstype": "ext4", "name": "module_b",
247 "binaries_b": "modules.img"},
248 {"size": 32, "fstype": "ext4", "name": "ramdisk_b",
249 "binaries_b": "ramdisk.img"},
250 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_b",
251 "binaries_b": "ramdisk-recovery.img"},
252 {"size": 4, "fstype": "ext4", "name": "reserved0"},
253 {"size": 64, "fstype": "ext4", "name": "reserved1"},
254 {"size": 125, "fstype": "ext4", "name": "reserved2"}
256 params = (('reboot-param.bin', 'norm'),
257 ('reboot-param.info', 'norm'),
258 ('partition-ab.info', 'a'),
259 ('partition-ab-cloned.info', '1'),
260 ('upgrade-status.info', '0'),
261 ('partition-a-status.info', 'ok'),
262 ('partition-b-status.info', 'ok'))
264 def __init__(self, device, args):
265 self.reserved_space = 8
266 self.user_partition = 4
267 self.update = args.update
268 super().__init__(device, "gpt")
269 self.with_super = True
270 self.super_alignment = 1048576
272 class Rpi4(InitParams, SdFusingTargetAB):
273 long_name = "Raspberry Pi 4"
275 {"size": 64, "fstype": "vfat", "name": "boot_a", "start": 4,
276 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
277 "binaries": "boot.img"},
278 {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
279 "binaries": "rootfs.img"},
280 {"size": 1344, "fstype": "ext4", "name": "system-data",
281 "binaries": "system-data.img"},
282 {"size": 36, "fstype": "raw", "name": "none"},
283 {"size": None, "fstype": "ext4", "name": "user",
284 "binaries": "user.img"},
285 {"size": 32, "fstype": "ext4", "name": "module_a",
286 "binaries": "modules.img"},
287 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
288 "binaries": "ramdisk.img"},
289 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
290 "binaries": "ramdisk-recovery.img"},
291 {"size": 8, "fstype": "ext4", "name": "inform"},
292 {"size": 256, "fstype": "ext4", "name": "hal_a",
293 "binaries": "hal.img"},
294 {"size": 64, "fstype": "vfat", "name": "boot_b",
295 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
296 "binaries_b": "boot.img"},
297 {"size": 3072, "fstype": "ext4", "name": "rootfs_b",
298 "binaries_b": "rootfs.img"},
299 {"size": 32, "fstype": "ext4", "name": "module_b",
300 "binaries_b": "modules.img"},
301 {"size": 32, "fstype": "ext4", "name": "ramdisk_b",
302 "binaries_b": "ramdisk.img"},
303 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_b",
304 "binaries_b": "ramdisk-recovery.img"},
305 {"size": 256, "fstype": "ext4", "name": "hal_b",
306 "binaries_b": "hal.img"},
307 {"size": 4, "fstype": "ext4", "name": "param"},
308 {"size": 64, "fstype": "ext4", "name": "reserved1"},
309 {"size": 125, "fstype": "ext4", "name": "reserved2"},
311 params = (('reboot-param.bin', 'norm'),
312 ('reboot-param.info', 'norm'),
313 ('partition-ab.info', 'a'),
314 ('partition-ab-cloned.info', '1'),
315 ('upgrade-status.info', '0'),
316 ('partition-a-status.info', 'ok'),
317 ('partition-b-status.info', 'ok'))
319 def __init__(self, device, args):
320 self.reserved_space = 5
321 self.user_partition = 4
322 self.update = args.update
323 super().__init__(device, "gpt")
325 class Rpi4AoT(InitParams, SdFusingTargetAB):
326 long_name = "Raspberry Pi 4 for AoT"
328 {"size": 64, "fstype": "vfat", "name": "boot_a", "start": 4,
329 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
330 "binaries": "boot.img"},
331 {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
332 "binaries": "rootfs.img"},
333 {"size": 1344, "fstype": "ext4", "name": "system-data",
334 "binaries": "system-data.img"},
335 {"size": 36, "fstype": "raw", "name": "none"},
336 {"size": None, "fstype": "ext4", "name": "user",
337 "binaries": "user.img"},
338 {"size": 32, "fstype": "ext4", "name": "module_a",
339 "binaries": "modules.img"},
340 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
341 "binaries": "ramdisk.img"},
342 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
343 "binaries": "ramdisk-recovery.img"},
344 {"size": 8, "fstype": "ext4", "name": "inform"},
345 {"size": 256, "fstype": "ext4", "name": "hal_a",
346 "binaries": "hal.img"},
347 {"size": 64, "fstype": "vfat", "name": "boot_b",
348 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
349 "binaries_b": "boot.img"},
350 {"size": 3072, "fstype": "ext4", "name": "rootfs_b",
351 "binaries_b": "rootfs.img"},
352 {"size": 32, "fstype": "ext4", "name": "module_b",
353 "binaries_b": "modules.img"},
354 {"size": 32, "fstype": "ext4", "name": "ramdisk_b",
355 "binaries_b": "ramdisk.img"},
356 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_b",
357 "binaries_b": "ramdisk-recovery.img"},
358 {"size": 256, "fstype": "ext4", "name": "hal_b",
359 "binaries_b": "hal.img"},
360 {"size": 1536, "fstype": "ext4", "name": "aot-system_a",
361 "binaries": "system.img"},
362 {"size": 1536, "fstype": "ext4", "name": "aot-system_b",
363 "binaries_b": "system.img"},
364 {"size": 256, "fstype": "ext4", "name": "aot-vendor_a",
365 "binaries": "vendor.img"},
366 {"size": 256, "fstype": "ext4", "name": "aot-vendor_b",
367 "binaries_b": "vendor.img"},
368 {"size": 4, "fstype": "ext4", "name": "param"},
369 {"size": 64, "fstype": "ext4", "name": "reserved1"},
370 {"size": 125, "fstype": "ext4", "name": "reserved2"},
372 params = (('reboot-param.bin', 'norm'),
373 ('reboot-param.info', 'norm'),
374 ('partition-ab.info', 'a'),
375 ('partition-ab-cloned.info', '1'),
376 ('upgrade-status.info', '0'),
377 ('partition-a-status.info', 'ok'),
378 ('partition-b-status.info', 'ok'))
380 def __init__(self, device, args):
381 self.reserved_space = 5
382 self.user_partition = 4
383 self.update = args.update
384 super().__init__(device, "gpt")
386 class RV64(InitParams, SdFusingTarget):
387 long_name = "QEMU RISC-V 64-bit"
389 {"size": 2, "fstype": "raw", "name": "SPL", "start": 4,
390 "ptype": "2E54B353-1271-4842-806F-E436D6AF6985",
392 {"size": 4, "fstype": "raw", "name": "u-boot",
393 "ptype": "5B193300-FC78-40CD-8002-E86C45580B47",
394 "binaries": ["u-boot.img", "u-boot.itb"],},
395 {"size": 292, "fstype": "vfat", "name": "boot_a",
396 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
397 "binaries": "boot.img"},
398 {"size": 36, "fstype": "raw", "name": "none"},
399 {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
400 "binaries": "rootfs.img"},
401 {"size": 1344, "fstype": "ext4", "name": "system-data",
402 "binaries": "system-data.img"},
403 {"size": None, "fstype": "ext4", "name": "user",
404 "binaries": "user.img"},
405 {"size": 32, "fstype": "ext4", "name": "module_a",
406 "binaries": "modules.img"},
407 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
408 "binaries": "ramdisk.img"},
409 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
410 "binaries": "ramdisk-recovery.img"},
411 {"size": 8, "fstype": "ext4", "name": "inform"},
412 {"size": 256, "fstype": "ext4", "name": "hal_a",
413 "binaries": "hal.img"},
414 {"size": 4, "fstype": "raw", "name": "reserved0"},
415 {"size": 64, "fstype": "raw", "name": "reserved1"},
416 {"size": 125, "fstype": "raw", "name": "reserved2"},
418 params = (('reboot-param.bin', 'norm'),
419 ('reboot-param.info', 'norm'))
421 def __init__(self, device, args):
422 self.user_partition = 6
423 self.reserved_space = 5
424 self.apply_partition_sizes(args.partition_sizes)
425 super().__init__(device, 'gpt')
427 class VF2(InitParams, SdFusingTarget):
428 long_name = "VisionFive2"
430 {"size": 2, "fstype": "raw", "name": "SPL", "start": 4,
431 "ptype": "2E54B353-1271-4842-806F-E436D6AF6985",
432 "binaries": ["u-boot-spl.bin.normal.out"],},
433 {"size": 4, "fstype": "raw", "name": "u-boot",
434 "ptype": "5B193300-FC78-40CD-8002-E86C45580B47",
435 "binaries": ["u-boot.img", "u-boot.itb"],},
436 {"size": 292, "fstype": "vfat", "name": "boot_a",
437 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
438 "binaries": "boot.img"},
439 {"size": 36, "fstype": "raw", "name": "none"},
440 {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
441 "binaries": "rootfs.img"},
442 {"size": 1344, "fstype": "ext4", "name": "system-data",
443 "binaries": "system-data.img"},
444 {"size": None, "fstype": "ext4", "name": "user",
445 "binaries": "user.img"},
446 {"size": 32, "fstype": "ext4", "name": "module_a",
447 "binaries": "modules.img"},
448 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
449 "binaries": "ramdisk.img"},
450 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
451 "binaries": "ramdisk-recovery.img"},
452 {"size": 8, "fstype": "ext4", "name": "inform"},
453 {"size": 256, "fstype": "ext4", "name": "hal_a",
454 "binaries": "hal.img"},
455 {"size": 4, "fstype": "raw", "name": "reserved0"},
456 {"size": 64, "fstype": "raw", "name": "reserved1"},
457 {"size": 125, "fstype": "raw", "name": "reserved2"},
459 params = (('reboot-param.bin', 'norm'),
460 ('reboot-param.info', 'norm'))
462 def __init__(self, device, args):
463 self.user_partition = 6
464 self.reserved_space = 5
465 self.apply_partition_sizes(args.partition_sizes)
466 super().__init__(device, 'gpt')
468 class X86emu(SdFusingTarget):
470 {"size": 512, "fstype": "vfat", "name": "EFI", "start": 4,
471 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
473 {"size": 512, "fstype": "ext2", "name": "boot",
474 "binaries": "emulator-boot.img",},
475 {"size": 2048, "fstype": "ext4", "name": "rootfs",
476 "binaries": "emulator-rootfs.img"},
477 {"size": 1344, "fstype": "ext4", "name": "system-data",
478 "binaries": "emulator-sysdata.img"},
479 {"size": 1024, "fstype": "swap", "name": "emulator-swap",
480 "ptype": "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F"},
483 def __init__(self, device, args):
484 super().__init__(device, 'gpt')
485 for p in self.label.part_table:
486 if p.name == "rootfs":
487 p.ptype = args._rootfs_uuid
490 class X86emu32(X86emu):
491 long_name = "QEMU x86 32-bit"
493 def __init__(self, device, args):
494 setattr(args, "_rootfs_uuid", "44479540-F297-41B2-9AF7-D131D5F0458A")
495 super().__init__(device, args)
497 class X86emu64(X86emu):
498 long_name = "QEMU x86 64-bit"
500 def __init__(self, device, args):
501 setattr(args, "_rootfs_uuid", "44479540-F297-41B2-9AF7-D131D5F0458A")
502 super().__init__(device, args)
511 'x86emu32': X86emu32,
512 'x86emu64': X86emu64,
515 def device_size(device):
516 argv = ["sfdisk", "-s", device]
517 logging.debug(" ".join(argv))
518 proc = subprocess.run(argv,
519 stdout=subprocess.PIPE)
520 size = int(proc.stdout.decode('utf-8').strip()) >> 10
521 logging.debug(f"{device} size {size}MiB")
525 proc = subprocess.run(['sfdisk', '-v'],
526 stdout=subprocess.PIPE)
527 version = proc.stdout.decode('utf-8').strip()
528 logging.debug(f"Found {version}")
529 major, minor = [int(x) for x in re.findall('[0-9]+', version)][0:2]
530 support_delete = False
532 if major < 2 or major == 2 and minor < 26:
533 log.error(f"Your sfdisk {major}.{minor}.{patch} is too old.")
535 elif major == 2 and minor >= 28:
536 support_delete = True
538 return True, support_delete
540 def mkpart(args, target):
542 new, support_delete = check_sfdisk()
545 logging.error('sfdisk too old')
548 with open('/proc/self/mounts') as mounts:
549 device_kname = '/dev/' + get_device_kname(Device)
550 device_re = re.compile(device_kname + '[^ ]*')
551 logging.debug(f"Checking for mounted partitions on {device_kname}")
553 match = device_re.match(m)
555 logging.warning('Found mounted device: ' + match[0])
556 argv = ['umount', match[0]]
557 logging.debug(" ".join(argv))
558 proc = subprocess.run(argv)
559 if proc.returncode != 0:
560 logging.error(f"Failed to unmount {match[0]}")
564 logging.info("Removing old partitions")
565 argv = ['sfdisk', '--delete', Device]
566 logging.debug(" ".join(argv))
567 proc = subprocess.run(argv)
568 if proc.returncode != 0:
569 logging.error(f"Failed to remove the old partitions from {Device}")
571 logging.info("Removing old partition table")
572 argv = ['dd', 'if=/dev/zero', 'of=' + Device,
573 'bs=512', 'count=32', 'conv=notrunc']
574 logging.debug(" ".join(argv))
575 proc = subprocess.run(argv)
576 if proc.returncode != 0:
577 logging.error(f"Failed to clear the old partition table on {Device}")
580 logging.debug("New partition table:\n" + str(target.label))
581 argv = ['sfdisk', '--wipe-partitions', 'always', Device]
582 logging.debug(" ".join(argv))
583 proc = subprocess.run(argv,
586 input=str(target.label).encode())
587 if proc.returncode != 0:
588 logging.error(f"Failed to create partition a new table on {Device}")
589 logging.error(f"New partition table:\n" + str(target.label))
592 for i, part in enumerate(target.part_table):
593 d = "/dev/" + get_partition_device(target.device, i+1)
594 if not 'fstype' in part:
595 logging.debug(f"Filesystem not defined for {d}, skipping")
597 logging.debug(f"Formatting {d} as {part['fstype']}")
598 if part['fstype'] == 'vfat':
599 argv = ['mkfs.vfat', '-F', '16', '-n', part['name'], d]
600 logging.debug(" ".join(argv))
601 proc = subprocess.run(argv,
602 stdin=subprocess.DEVNULL,
603 stdout=None, stderr=None)
604 if proc.returncode != 0:
605 log.error(f"Failed to create FAT filesystem on {d}")
607 elif part['fstype'] == 'ext4':
608 argv = ['mkfs.ext4', '-q', '-L', part['name'], d]
609 logging.debug(" ".join(argv))
610 proc = subprocess.run(argv,
611 stdin=subprocess.DEVNULL,
612 stdout=None, stderr=None)
613 if proc.returncode != 0:
614 log.error(f"Failed to create ext4 filesystem on {d}")
616 elif part['fstype'] == 'swap':
617 argv = ['mkswap', '-L', part['name'], d]
618 logging.debug(" ".join(argv))
619 proc = subprocess.run(argv,
620 stdin=subprocess.DEVNULL,
621 stdout=None, stderr=None)
622 if proc.returncode != 0:
623 log.error(f"Failed to format swap partition {d}")
625 elif part['fstype'] == 'raw':
627 target.initialize_parameters()
629 def check_args(args):
633 logging.info(f"Device: {args.device}")
635 if args.binaries and len(args.binaries) > 0:
636 logging.info("Fusing binar{}: {}".format("y" if len(args.binaries) == 1 else "ies",
637 ", ".join(args.binaries)))
650 response = input(f"{args.device} will be formatted. Continue? [y/N] ")
651 if response.lower() in ('y', 'yes'):
656 def check_device(args):
662 if os.path.exists(Device):
663 logging.error(f"Failed to create '{Device}', the file alread exists")
666 argv = ["dd", "if=/dev/zero", f"of={Device}",
667 "conv=sparse", "bs=1M", f"count={args.size}"]
668 logging.debug(" ".join(argv))
669 rc = subprocess.run(argv)
670 if rc.returncode != 0:
671 logging.error("Failed to create the backing file")
674 if os.path.isfile(Device):
678 argv = ["losetup", "--show", "--partscan", "--find", f"{File}"]
679 logging.debug(" ".join(argv))
680 proc = subprocess.run(argv,
681 stdout=subprocess.PIPE)
682 Device = proc.stdout.decode('utf-8').strip()
683 if proc.returncode != 0:
684 logging.error(f"Failed to attach {File} to a loopback device")
686 logging.debug(f"Loop device found: {Device}")
687 atexit.register(lambda: subprocess.run(["losetup", "-d", Device]))
691 if not stat.S_ISBLK(s.st_mode):
693 except FileNotFoundError:
694 logging.error(f"No such device: {Device}")
697 logging.error(f"{Device} is not a block device")
700 def check_partition_format(args, target):
705 logging.info(f"Skip formatting of {Device}".format(Device))
707 logging.info(f"Start formatting of {Device}")
709 logging.info(f"{Device} formatted")
711 def check_ddversion():
712 proc = subprocess.run(["dd", "--version"],
713 stdout=subprocess.PIPE)
714 version = proc.stdout.decode('utf-8').split('\n')[0].strip()
715 logging.debug(f"Found {version}")
716 major, minor = (int(x) for x in re.findall('[0-9]+', version))
718 if major < 8 or major == 8 and minor < 24:
723 def get_partition_device(device, idx):
724 argv = ['lsblk', device, '-o', 'TYPE,KNAME']
725 logging.debug(" ".join(argv))
726 proc = subprocess.run(argv,
727 stdout=subprocess.PIPE)
728 if proc.returncode != 0:
729 logging.error("lsblk has failed")
731 part_re = re.compile(f"^part\s+(.*[^0-9]{idx})$")
732 for l in proc.stdout.decode('utf-8').splitlines():
733 match = part_re.match(l)
738 def get_device_kname(device):
739 argv = ['lsblk', device, '-o', 'TYPE,KNAME']
740 logging.debug(" ".join(argv))
741 proc = subprocess.run(argv,
742 stdout=subprocess.PIPE)
743 for l in proc.stdout.decode('utf-8').splitlines():
744 match = re.search(f"^(disk|loop)\s+(.*)", l)
749 def do_fuse_file(f, name, target):
750 idx = target.get_partition_index(name)
752 logging.info(f"No partition defined for {name}, skipping.")
754 pdevice = "/dev/" + get_partition_device(Device, idx)
755 argv = ['dd', 'bs=4M',
761 logging.debug(" ".join(argv))
762 proc_dd = subprocess.Popen(argv,
764 stdin=subprocess.PIPE,
765 stdout=None, stderr=None)
766 print('\033[32m'+f"Writing {name} to {pdevice}"+'\033[0m')
767 buf = f.read(4 << 20)
769 proc_dd.stdin.write(buf)
770 buf = f.read(4 << 20)
771 proc_dd.communicate()
775 #TODO: functions with the target argument should probably
776 # be part of some class
778 def get_aligned_size(size, target):
779 return target.super_alignment*int(1+(size-1)/target.super_alignment)
781 def do_fuse_image_super(tmpd, target):
783 metadata_size = 65536
784 metadata_aligned_size = get_aligned_size(metadata_size, target)
786 hal_path = os.path.join(tmpd, 'hal.img')
787 rootfs_path = os.path.join(tmpd, 'rootfs.img')
788 super_path = os.path.join(tmpd, 'super.img')
791 hal_size = os.stat(hal_path).st_size
792 rootfs_size = os.stat(rootfs_path).st_size
793 except FileNotFoundError as e:
794 fn = os.path.split(e.filename)[-1]
795 logging.warning(f"{fn} is missing, skipping super partition image")
798 hal_aligned_size = get_aligned_size(hal_size, target)
799 rootfs_aligned_size = get_aligned_size(rootfs_size, target)
800 group_size = hal_aligned_size + rootfs_aligned_size
801 super_size = metadata_aligned_size + 2 * group_size
803 argv = ["lpmake", "-F",
805 f"--device-size={super_size}",
806 f"--metadata-size={metadata_size}",
807 f"--metadata-slots={metadata_slots}",
808 "-g", f"tizen_a:{group_size}",
809 "-p", f"rootfs_a:none:{rootfs_aligned_size}:tizen_a",
810 "-p", f"hal_a:none:{hal_aligned_size}:tizen_a",
811 "-g", f"tizen_b:{group_size}",
812 "-p", f"rootfs_b:none:{rootfs_aligned_size}:tizen_b",
813 "-p", f"hal_b:none:{hal_aligned_size}:tizen_b",
814 "-i", f"rootfs_a={rootfs_path}",
815 "-i", f"rootfs_b={rootfs_path}",
816 "-i", f"hal_a={hal_path}",
817 "-i", f"hal_b={hal_path}"]
818 logging.debug(" ".join(argv))
819 proc = subprocess.run(argv,
820 stdin=subprocess.DEVNULL,
821 stdout=None, stderr=None)
823 if proc.returncode != 0:
824 logging.error("Failed to create super.img")
825 do_fuse_image(super_path, target)
827 def do_fuse_image_tarball(tarball, tmpd, target):
828 with tarfile.open(tarball) as tf:
830 if target.with_super:
831 if entry.name in('hal.img', 'rootfs.img'):
832 tf.extract(entry, path=tmpd)
834 f = tf.extractfile(entry)
835 do_fuse_file(f, entry.name, target)
837 def do_fuse_image(img, target):
838 with open(img, 'rb') as f:
839 do_fuse_file(f, os.path.basename(img), target)
841 def fuse_image(args, target):
844 if args.binaries is None or len(args.binaries) == 0:
847 if not Yes and not Format:
848 print(f"The following images will be written to {args.device} and the "
849 "existing data will be lost.\n")
850 for b in args.binaries:
852 response = input("\nContinue? [y/N] ")
853 if not response.lower() in ('y', 'yes'):
856 with tempfile.TemporaryDirectory() as tmpd:
857 for b in args.binaries:
858 if re.search('\.(tar|tar\.gz|tgz)$', b):
859 do_fuse_image_tarball(b, tmpd, target)
861 fn = os.path.split(b)[-1]
862 if target.with_super and fn in ('rootfs.img', 'hal.img'):
863 shutil.copy(b, os.path.join(tmpd, fn))
865 do_fuse_image(b, target)
867 if target.with_super:
868 do_fuse_image_super(tmpd, target)
870 if __name__ == '__main__':
871 parser = argparse.ArgumentParser(description="For {}, version {}".format(
872 ", ".join([v.long_name for k,v in TARGETS.items()]),
875 parser.add_argument("-b", "--binary", action="extend", dest="binaries",
877 help="binary to flash, may be used multiple times")
878 parser.add_argument("--create", action="store_true",
879 help="create the backing file and format the loopback device")
880 parser.add_argument("--debug", action="store_const", const="debug",
881 default="warning", dest="log_level",
882 help="set log level to DEBUG")
883 parser.add_argument("-d", "--device",
884 help="device node or loopback backing file")
885 parser.add_argument("--format", action="store_true",
886 help="create new partition table on the target device")
887 parser.add_argument("--log-level", dest="log_level", default="warning",
888 help="Verbosity, possible values: debug, info, warning, "
889 "error, critical (default: warning)")
890 parser.add_argument("--partition-size", type=str, action="extend", dest="partition_sizes",
892 help="override default partition size (in MiB) (used with --format), "
893 "may be used multiple times, for example: --partition-size hal_a=256")
894 parser.add_argument("--size", type=int, default=8192,
895 help="size of the backing file to create (in MiB)")
896 parser.add_argument("-t", "--target", required=True,
897 help="Target device model. Use `--target list`"
898 " to show supported devices.")
899 parser.add_argument("--update", choices=['a', 'b'], default=None,
900 help="Choose partition set to update: a or b.")
901 parser.add_argument("--version", action="version",
902 version=f"%(prog)s {__version__}")
903 parser.add_argument("--YES", action="store_true",
904 help="agree to destroy data on the DEVICE")
905 args = parser.parse_args()
907 if args.target == 'list':
908 print("\nSupported devices:\n")
909 for k,v in TARGETS.items():
910 print(f" {k:6} {v.long_name}")
913 if args.device is None:
914 parser.error('-d/--device argument is required for normal operation')
916 if args.partition_sizes is not None:
918 for eqstr in args.partition_sizes:
919 ptstr = eqstr.split('=')
923 partition_sizes[name] = size
925 parser.error('--partition-size must follow the name=size pattern')
926 args.partition_sizes = partition_sizes
928 conh = ColorStreamHandler(format='%(asctime)s.%(msecs)d %(debuginfo)s%(levelname)-8s %(message)s',
929 cformat='%(asctime)s.%(msecs)d %(debuginfo)s%(levelcolor)s%(message)s',
930 datefmt='%Y-%m-%dT%H:%M:%S')
931 log_handlers = [conh]
932 logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s',
933 handlers=log_handlers,
934 level=args.log_level.upper())
936 logging.debug(" ".join(sys.argv))
940 target = TARGETS[args.target](Device, args)
942 check_partition_format(args, target)
943 fuse_image(args, target)
944 subprocess.run(['sync'],
945 stdin=subprocess.DEVNULL,
946 stdout=None, stderr=None )