3 from functools import reduce
25 LOGGING_NOTICE = int((logging.INFO + logging.WARNING) / 2)
27 class DebugFormatter(logging.Formatter):
28 def format(self, record):
29 if record.levelno == logging.DEBUG:
30 record.debuginfo = "[{}:{}] ".format(os.path.basename(record.pathname), record.lineno)
33 return logging.Formatter.format(self, record)
35 class ColorFormatter(DebugFormatter):
37 logging.CRITICAL: "\x1b[35;1m",
38 logging.ERROR: "\x1b[33;1m",
39 logging.WARNING: "\x1b[33;1m",
40 LOGGING_NOTICE: "\x1b[0m",
41 logging.INFO: "\x1b[0m",
42 logging.DEBUG: "\x1b[30;1m",
43 logging.NOTSET: "\x1b[30;1m"
45 def format(self, record):
46 record.levelcolor = self._levelToColor[record.levelno]
47 record.msg = record.msg
48 return super().format(record)
50 class ColorStreamHandler(logging.StreamHandler):
51 def __init__(self, stream=None, format=None, datefmt=None, style='%', cformat=None):
52 logging.StreamHandler.__init__(self, stream)
53 if os.isatty(self.stream.fileno()):
54 self.formatter = ColorFormatter(cformat, datefmt, style)
55 self.terminator = "\x1b[0m\n"
57 self.formatter = DebugFormatter(format, datefmt, style)
60 def __init__(self, name, size, start=None, ptype=None, fstype="raw", bootable=False, **kwargs):
63 self.size_sectors = kwargs.get("size_sectors", None)
65 self.start_sector = kwargs.get("start_sector", None)
67 self.bootable = bootable
68 if type(self.size_sectors) == int and self.size_sectors >= 0:
69 if type(self.size) == int and self.size >= 0:
70 logging.warning(f"partition:{name} overriding size to the value obtained from size_sectors")
71 # size is used to calculate free space, so adjust it here
72 self.size = (self.size_sectors * 512 - 1) / (1024*1024) + 1
73 if type(self.start_sector) == int and self.start_sector >= 0:
74 if type(self.start) == int and self.start >= 0:
75 logging.warning(f"partition:{name} overriding start to the value obtained from start_sector")
81 output.append(f"start={self.start_sector}")
83 output.append(f"start={self.start}MiB")
84 if type(self.size_sectors) == int and self.size_sectors >= 0:
85 output.append(f"size={self.size_sectors}")
86 elif type(self.size) == int and self.size >= 0:
87 output.append(f"size={self.size}MiB")
89 output.append(f"name={self.name}")
90 output.append(f"type={self.ptype}")
92 output.append("bootable")
93 return ", ".join(output) + "\n"
96 def __init__(self, part_table, ltype):
99 ptype = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
103 for part in part_table:
104 part["ptype"] = part.get("ptype", ptype)
105 self.part_table.append(Partition(**part))
107 output = f"label: {self.ltype}\n"
108 if self.ltype == 'gpt':
109 output += f"first-lba: 34\n"
110 for part in self.part_table:
114 class SdFusingTarget:
115 def __init__(self, device, ltype):
116 # TODO: make a copy of a sublcass part_table
117 self.with_super = False
119 total_size = device_size(device)
121 if hasattr(self, 'user_partition'):
122 self.user_size = total_size - self.reserved_space - \
123 reduce(lambda x, y: x + (y["size"] or 0), self.part_table, 0)
124 if self.user_size < 100:
125 logging.error(f"Not enough space for user data ({self.user_size}). Use larger storage.")
126 raise OSError(errno.ENOSPC, os.strerror(errno.ENOSPC), device)
127 # self.user_partition counts from 0
128 self.part_table[self.user_partition]["size"] = self.user_size
130 self.label = Label(self.part_table, ltype)
131 if not hasattr(self, 'bootcode'):
133 self.binaries = self._get_binaries('binaries')
135 def apply_partition_sizes(self, partition_sizes):
136 if partition_sizes is None or len(partition_sizes) == 0:
139 for name, size in partition_sizes.items():
141 for part in self.part_table:
142 if part['name'] == name:
145 logging.debug(f"overriding partition:{name}, old-size:{psize} MiB new-size:{size} MiB")
146 resized_count = resized_count + 1
147 if resized_count == 0:
148 logging.error(f"partition:{name} not found when attempting to apply_partition_sizes")
149 resized_total = resized_total + resized_count
152 def _get_binaries(self, key):
154 for i, p in enumerate(self.part_table):
158 if isinstance(b, str):
160 elif isinstance(b, list):
165 def get_partition_index(self, binary):
166 if hasattr(self, 'update'):
167 logging.error("You have requested to update the {} partition set. "
168 "This target does not support A/B partition sets."
169 .format(self.update.upper()))
171 return self.binaries.get(binary, None)
174 def initialize_parameters(self):
177 class SdFusingTargetAB(SdFusingTarget):
178 def __init__(self, device, ltype):
179 super().__init__(device, ltype)
180 self.binaries_b = self._get_binaries('binaries_b')
182 def get_partition_index(self, binary):
183 if self.update == 'b':
184 return self.binaries_b.get(binary, None)
185 return self.binaries.get(binary, None)
188 def initialize_parameters(self):
189 logging.debug("Initializing parameterss")
191 for i, p in enumerate(self.part_table):
192 if p['name'] == 'inform':
195 d = "/dev/" + get_partition_device(self.device, n)
197 argv = ['tune2fs', '-O', '^metadata_csum', d]
198 logging.debug(" ".join(argv))
200 stdin=subprocess.DEVNULL,
201 stdout=None, stderr=None)
203 with tempfile.TemporaryDirectory() as mnt:
204 argv = ['mount', '-t', 'ext4', d, mnt]
205 logging.debug(" ".join(argv))
206 proc = subprocess.run(argv,
207 stdin=subprocess.DEVNULL,
208 stdout=None, stderr=None)
209 if proc.returncode != 0:
210 logging.error("Failed to mount {d} in {mnt}")
212 for param, value in self.params:
213 with open(os.path.join(mnt, param), 'w') as f:
214 f.write(value + '\n')
216 logging.debug(" ".join(argv))
218 stdin=subprocess.DEVNULL,
219 stdout=None, stderr=None)
221 class Rpi3(InitParams, SdFusingTarget):
222 long_name = "Raspberry Pi 3"
224 {"size": 64, "fstype": "vfat", "name": "boot", "start": 4, "ptype": "0xe", "bootable": True,
225 "binaries": "boot.img"},
226 {"size": 3072, "fstype": "ext4", "name": "rootfs",
227 "binaries": "rootfs.img"},
228 {"size": 1344, "fstype": "ext4", "name": "system-data",
229 "binaries": "system-data.img"},
230 {"size": None, "ptype": "5", "name": "extended", "start": 4484},
231 {"size": None, "fstype": "ext4", "name": "user",
232 "binaries": "user.img"},
233 {"size": 32, "fstype": "ext4", "name": "modules",
234 "binaries": "modules.img"},
235 {"size": 32, "fstype": "ext4", "name": "ramdisk",
236 "binaries": "ramdisk.img"},
237 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery",
238 "binaries": "ramdisk-recovery.img"},
239 {"size": 8, "fstype": "ext4", "name": "inform"},
240 {"size": 256, "fstype": "ext4", "name": "hal",
241 "binaries": "hal.img"},
242 {"size": 125, "fstype": "ext4", "name": "reserved2"},
244 params = (('reboot-param.bin', ''),)
246 def __init__(self, device, args):
247 self.reserved_space = 12
248 self.user_partition = 4
249 super().__init__(device, "dos")
251 class Rpi4Super(InitParams, SdFusingTargetAB):
252 long_name = "Raspberry Pi 4 w/ super partition"
254 {"size": 64, "fstype": "vfat", "name": "boot_a","start": 4,
255 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
256 "binaries": "boot.img"},
257 {"size": 6657, "fstype": "ext4", "name": "super",
258 "binaries": "super.img"},
259 {"size": 1344, "fstype": "ext4", "name": "system-data",
260 "binaries": "system-data.img"},
261 {"size": 36, "fstype": "raw", "name": "none"},
262 {"size": None, "fstype": "ext4", "name": "user",
263 "binaries": "user.img"},
264 {"size": 32, "fstype": "ext4", "name": "module_a",
265 "binaries": "modules.img"},
266 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
267 "binaries": "ramdisk.img"},
268 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
269 "binaries": "ramdisk-recovery.img"},
270 {"size": 8, "fstype": "ext4", "name": "inform"},
271 {"size": 64, "fstype": "vfat", "name": "boot_b",
272 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
273 "binaries_b": "boot.img"},
274 {"size": 32, "fstype": "ext4", "name": "module_b",
275 "binaries_b": "modules.img"},
276 {"size": 32, "fstype": "ext4", "name": "ramdisk_b",
277 "binaries_b": "ramdisk.img"},
278 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_b",
279 "binaries_b": "ramdisk-recovery.img"},
280 {"size": 4, "fstype": "ext4", "name": "reserved0"},
281 {"size": 64, "fstype": "ext4", "name": "reserved1"},
282 {"size": 125, "fstype": "ext4", "name": "reserved2"}
284 params = (('reboot-param.bin', 'norm'),
285 ('reboot-param.info', 'norm'),
286 ('partition-ab.info', 'a'),
287 ('partition-ab-cloned.info', '1'),
288 ('upgrade-status.info', '0'),
289 ('partition-a-status.info', 'ok'),
290 ('partition-b-status.info', 'ok'))
292 def __init__(self, device, args):
293 self.reserved_space = 8
294 self.user_partition = 4
295 self.update = args.update
296 super().__init__(device, "gpt")
297 self.with_super = True
298 self.super_alignment = 1048576
300 class Rpi4(InitParams, SdFusingTargetAB):
301 long_name = "Raspberry Pi 4"
303 {"size": 64, "fstype": "vfat", "name": "boot_a", "start": 4,
304 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
305 "binaries": "boot.img"},
306 {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
307 "binaries": "rootfs.img"},
308 {"size": 1344, "fstype": "ext4", "name": "system-data",
309 "binaries": "system-data.img"},
310 {"size": 36, "fstype": "raw", "name": "none"},
311 {"size": None, "fstype": "ext4", "name": "user",
312 "binaries": "user.img"},
313 {"size": 32, "fstype": "ext4", "name": "module_a",
314 "binaries": "modules.img"},
315 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
316 "binaries": "ramdisk.img"},
317 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
318 "binaries": "ramdisk-recovery.img"},
319 {"size": 8, "fstype": "ext4", "name": "inform"},
320 {"size": 256, "fstype": "ext4", "name": "hal_a",
321 "binaries": "hal.img"},
322 {"size": 64, "fstype": "vfat", "name": "boot_b",
323 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
324 "binaries_b": "boot.img"},
325 {"size": 3072, "fstype": "ext4", "name": "rootfs_b",
326 "binaries_b": "rootfs.img"},
327 {"size": 32, "fstype": "ext4", "name": "module_b",
328 "binaries_b": "modules.img"},
329 {"size": 32, "fstype": "ext4", "name": "ramdisk_b",
330 "binaries_b": "ramdisk.img"},
331 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_b",
332 "binaries_b": "ramdisk-recovery.img"},
333 {"size": 256, "fstype": "ext4", "name": "hal_b",
334 "binaries_b": "hal.img"},
335 {"size": 4, "fstype": "ext4", "name": "reserved0"},
336 {"size": 64, "fstype": "ext4", "name": "reserved1"},
337 {"size": 125, "fstype": "ext4", "name": "reserved2"},
339 params = (('reboot-param.bin', 'norm'),
340 ('reboot-param.info', 'norm'),
341 ('partition-ab.info', 'a'),
342 ('partition-ab-cloned.info', '1'),
343 ('upgrade-status.info', '0'),
344 ('partition-a-status.info', 'ok'),
345 ('partition-b-status.info', 'ok'))
347 def __init__(self, device, args):
348 self.reserved_space = 5
349 self.user_partition = 4
350 self.update = args.update
351 super().__init__(device, "gpt")
353 class Rpi4AoT(InitParams, SdFusingTargetAB):
354 long_name = "Raspberry Pi 4 for AoT"
356 {"size": 64, "fstype": "vfat", "name": "boot_a", "start": 4,
357 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
358 "binaries": "boot.img"},
359 {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
360 "binaries": "rootfs.img"},
361 {"size": 1344, "fstype": "ext4", "name": "system-data",
362 "binaries": "system-data.img"},
363 {"size": 36, "fstype": "raw", "name": "none"},
364 {"size": None, "fstype": "ext4", "name": "user",
365 "binaries": "user.img"},
366 {"size": 32, "fstype": "ext4", "name": "module_a",
367 "binaries": "modules.img"},
368 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
369 "binaries": "ramdisk.img"},
370 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
371 "binaries": "ramdisk-recovery.img"},
372 {"size": 8, "fstype": "ext4", "name": "inform"},
373 {"size": 256, "fstype": "ext4", "name": "hal_a",
374 "binaries": "hal.img"},
375 {"size": 64, "fstype": "vfat", "name": "boot_b",
376 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
377 "binaries_b": "boot.img"},
378 {"size": 3072, "fstype": "ext4", "name": "rootfs_b",
379 "binaries_b": "rootfs.img"},
380 {"size": 32, "fstype": "ext4", "name": "module_b",
381 "binaries_b": "modules.img"},
382 {"size": 32, "fstype": "ext4", "name": "ramdisk_b",
383 "binaries_b": "ramdisk.img"},
384 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_b",
385 "binaries_b": "ramdisk-recovery.img"},
386 {"size": 256, "fstype": "ext4", "name": "hal_b",
387 "binaries_b": "hal.img"},
388 {"size": 1536, "fstype": "ext4", "name": "aot-system_a",
389 "binaries": "system.img"},
390 {"size": 1536, "fstype": "ext4", "name": "aot-system_b",
391 "binaries_b": "system.img"},
392 {"size": 256, "fstype": "ext4", "name": "aot-vendor_a",
393 "binaries": "vendor.img"},
394 {"size": 256, "fstype": "ext4", "name": "aot-vendor_b",
395 "binaries_b": "vendor.img"},
396 {"size": 4, "fstype": "ext4", "name": "reserved0"},
397 {"size": 64, "fstype": "ext4", "name": "reserved1"},
398 {"size": 125, "fstype": "ext4", "name": "reserved2"},
400 params = (('reboot-param.bin', 'norm'),
401 ('reboot-param.info', 'norm'),
402 ('partition-ab.info', 'a'),
403 ('partition-ab-cloned.info', '1'),
404 ('upgrade-status.info', '0'),
405 ('partition-a-status.info', 'ok'),
406 ('partition-b-status.info', 'ok'))
408 def __init__(self, device, args):
409 self.reserved_space = 5
410 self.user_partition = 4
411 self.update = args.update
412 super().__init__(device, "gpt")
414 class RV64(InitParams, SdFusingTarget):
415 long_name = "QEMU RISC-V 64-bit"
417 {"size": 2, "fstype": "raw", "name": "SPL", "start": 4,
418 "ptype": "2E54B353-1271-4842-806F-E436D6AF6985",
420 {"size": 4, "fstype": "raw", "name": "u-boot",
421 "ptype": "5B193300-FC78-40CD-8002-E86C45580B47",
422 "binaries": ["u-boot.img", "u-boot.itb"],},
423 {"size": 292, "fstype": "vfat", "name": "boot_a",
424 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
425 "binaries": "boot.img"},
426 {"size": 36, "fstype": "raw", "name": "none"},
427 {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
428 "binaries": "rootfs.img"},
429 {"size": 1344, "fstype": "ext4", "name": "system-data",
430 "binaries": "system-data.img"},
431 {"size": None, "fstype": "ext4", "name": "user",
432 "binaries": "user.img"},
433 {"size": 32, "fstype": "ext4", "name": "module_a",
434 "binaries": "modules.img"},
435 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
436 "binaries": "ramdisk.img"},
437 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
438 "binaries": "ramdisk-recovery.img"},
439 {"size": 8, "fstype": "ext4", "name": "inform"},
440 {"size": 256, "fstype": "ext4", "name": "hal_a",
441 "binaries": "hal.img"},
442 {"size": 4, "fstype": "raw", "name": "reserved0"},
443 {"size": 64, "fstype": "raw", "name": "reserved1"},
444 {"size": 125, "fstype": "raw", "name": "reserved2"},
446 params = (('reboot-param.bin', 'norm'),
447 ('reboot-param.info', 'norm'))
449 def __init__(self, device, args):
450 self.user_partition = 6
451 self.reserved_space = 5
452 self.apply_partition_sizes(args.partition_sizes)
453 super().__init__(device, 'gpt')
455 class VF2(InitParams, SdFusingTarget):
456 long_name = "VisionFive2"
458 {"size": 2, "fstype": "raw", "name": "SPL", "start": 4,
459 "ptype": "2E54B353-1271-4842-806F-E436D6AF6985",
460 "binaries": ["u-boot-spl.bin.normal.out"],},
461 {"size": 4, "fstype": "raw", "name": "u-boot",
462 "ptype": "5B193300-FC78-40CD-8002-E86C45580B47",
463 "binaries": ["u-boot.img", "u-boot.itb"],},
464 {"size": 292, "fstype": "vfat", "name": "boot_a",
465 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
466 "binaries": "boot.img"},
467 {"size": 36, "fstype": "raw", "name": "none"},
468 {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
469 "binaries": "rootfs.img"},
470 {"size": 1344, "fstype": "ext4", "name": "system-data",
471 "binaries": "system-data.img"},
472 {"size": None, "fstype": "ext4", "name": "user",
473 "binaries": "user.img"},
474 {"size": 32, "fstype": "ext4", "name": "module_a",
475 "binaries": "modules.img"},
476 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
477 "binaries": "ramdisk.img"},
478 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
479 "binaries": "ramdisk-recovery.img"},
480 {"size": 8, "fstype": "ext4", "name": "inform"},
481 {"size": 256, "fstype": "ext4", "name": "hal_a",
482 "binaries": "hal.img"},
483 {"size": 4, "fstype": "raw", "name": "reserved0"},
484 {"size": 64, "fstype": "raw", "name": "reserved1"},
485 {"size": 125, "fstype": "raw", "name": "reserved2"},
487 params = (('reboot-param.bin', 'norm'),
488 ('reboot-param.info', 'norm'))
490 def __init__(self, device, args):
491 self.user_partition = 6
492 self.reserved_space = 5
493 self.apply_partition_sizes(args.partition_sizes)
494 super().__init__(device, 'gpt')
496 class LicheePi4A(InitParams, SdFusingTargetAB):
497 long_name = "LicheePi4A"
499 {"size": None, "fstype": "raw", "name": "spl+uboot",
500 "start_sector": 34, "size_sectors": 4062,
501 "ptype": "8DA63339-0007-60C0-C436-083AC8230908",
502 "binaries": ["u-boot-with-spl.bin"],},
503 {"size": 128, "fstype": "vfat", "name": "boot_a",
504 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
505 "binaries": "boot.img"},
506 {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
507 "binaries": "rootfs.img"},
508 {"size": 1344, "fstype": "ext4", "name": "system-data",
509 "binaries": "system-data.img"},
510 {"size": None, "fstype": "ext4", "name": "user",
511 "binaries": "user.img"},
512 {"size": 32, "fstype": "ext4", "name": "module_a",
513 "binaries": "modules.img"},
514 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
515 "binaries": "ramdisk.img"},
516 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
517 "binaries": "ramdisk-recovery.img"},
518 {"size": 8, "fstype": "ext4", "name": "inform"},
519 {"size": 256, "fstype": "ext4", "name": "hal_a",
520 "binaries": "hal.img"},
521 {"size": 128, "fstype": "vfat", "name": "boot_b",
522 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
523 "binaries_b": "boot.img"},
524 {"size": 3072, "fstype": "ext4", "name": "rootfs_b",
525 "binaries_b": "rootfs.img"},
526 {"size": 32, "fstype": "ext4", "name": "module_b",
527 "binaries_b": "modules.img"},
528 {"size": 32, "fstype": "ext4", "name": "ramdisk_b",
529 "binaries_b": "ramdisk.img"},
530 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_b",
531 "binaries_b": "ramdisk-recovery.img"},
532 {"size": 256, "fstype": "ext4", "name": "hal_b",
533 "binaries_b": "hal.img"},
534 {"size": 4, "fstype": "raw", "name": "reserved0"},
535 {"size": 64, "fstype": "raw", "name": "reserved1"},
536 {"size": 125, "fstype": "raw", "name": "reserved2"},
538 params = (('reboot-param.bin', 'norm'),
539 ('reboot-param.info', 'norm'),
540 ('partition-ab.info', 'a'),
541 ('partition-ab-cloned.info', '1'),
542 ('upgrade-status.info', '0'),
543 ('partition-a-status.info', 'ok'),
544 ('partition-b-status.info', 'ok'))
546 # bootcode written to the protective MBR, aka RV64 'J 0x4400' (sector 34)
547 bootcode = b'\x6f\x40\x00\x40'
549 def __init__(self, device, args):
550 self.user_partition = 4
551 self.reserved_space = 5
552 self.update = args.update
553 self.apply_partition_sizes(args.partition_sizes)
554 super().__init__(device, 'gpt')
556 class LicheePi4ASuper(InitParams, SdFusingTargetAB):
557 long_name = "LicheePi4A w/ super partition"
559 {"size": None, "fstype": "raw", "name": "spl+uboot",
560 "start_sector": 34, "size_sectors": 4062,
561 "ptype": "8DA63339-0007-60C0-C436-083AC8230908",
562 "binaries": ["u-boot-with-spl.bin"],},
563 {"size": 128, "fstype": "vfat", "name": "boot_a",
564 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
565 "binaries": "boot.img"},
566 {"size": 6656, "fstype": "ext4", "name": "super",
567 "binaries": "super.img"},
568 {"size": 1344, "fstype": "ext4", "name": "system-data",
569 "binaries": "system-data.img"},
570 {"size": None, "fstype": "ext4", "name": "user",
571 "binaries": "user.img"},
572 {"size": 32, "fstype": "ext4", "name": "module_a",
573 "binaries": "modules.img"},
574 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
575 "binaries": "ramdisk.img"},
576 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
577 "binaries": "ramdisk-recovery.img"},
578 {"size": 8, "fstype": "ext4", "name": "inform"},
579 {"size": 128, "fstype": "vfat", "name": "boot_b",
580 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
581 "binaries_b": "boot.img"},
582 {"size": 32, "fstype": "ext4", "name": "module_b",
583 "binaries_b": "modules.img"},
584 {"size": 32, "fstype": "ext4", "name": "ramdisk_b",
585 "binaries_b": "ramdisk.img"},
586 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_b",
587 "binaries_b": "ramdisk-recovery.img"},
588 {"size": 4, "fstype": "ext4", "name": "reserved0"},
589 {"size": 64, "fstype": "ext4", "name": "reserved1"},
590 {"size": 125, "fstype": "ext4", "name": "reserved2"}
592 params = (('reboot-param.bin', 'norm'),
593 ('reboot-param.info', 'norm'),
594 ('partition-ab.info', 'a'),
595 ('partition-ab-cloned.info', '1'),
596 ('upgrade-status.info', '0'),
597 ('partition-a-status.info', 'ok'),
598 ('partition-b-status.info', 'ok'))
600 # bootcode written to the protective MBR, aka RV64 'J 0x4400' (sector 34)
601 bootcode = b'\x6f\x40\x00\x40'
603 def __init__(self, device, args):
604 self.reserved_space = 8
605 self.user_partition = 4
606 self.update = args.update
607 super().__init__(device, 'gpt')
608 self.with_super = True
609 self.super_alignment = 1048576
612 class X86emu(SdFusingTarget):
614 {"size": 512, "fstype": "vfat", "name": "EFI", "start": 4,
615 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
617 {"size": 512, "fstype": "ext2", "name": "boot",
618 "binaries": "emulator-boot.img",},
619 {"size": 2048, "fstype": "ext4", "name": "rootfs",
620 "binaries": "emulator-rootfs.img"},
621 {"size": 1344, "fstype": "ext4", "name": "system-data",
622 "binaries": "emulator-sysdata.img"},
623 {"size": 1024, "fstype": "swap", "name": "emulator-swap",
624 "ptype": "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F"},
627 def __init__(self, device, args):
628 super().__init__(device, 'gpt')
629 for p in self.label.part_table:
630 if p.name == "rootfs":
631 p.ptype = args._rootfs_uuid
634 class X86emu32(X86emu):
635 long_name = "QEMU x86 32-bit"
637 def __init__(self, device, args):
638 setattr(args, "_rootfs_uuid", "44479540-F297-41B2-9AF7-D131D5F0458A")
639 super().__init__(device, args)
641 class X86emu64(X86emu):
642 long_name = "QEMU x86 64-bit"
644 def __init__(self, device, args):
645 setattr(args, "_rootfs_uuid", "44479540-F297-41B2-9AF7-D131D5F0458A")
646 super().__init__(device, args)
656 'lpi4as': LicheePi4ASuper,
657 'x86emu32': X86emu32,
658 'x86emu64': X86emu64,
661 def device_size(device):
662 argv = ["sfdisk", "-s", device]
663 logging.debug(" ".join(argv))
664 proc = subprocess.run(argv,
665 stdout=subprocess.PIPE)
666 size = int(proc.stdout.decode('utf-8').strip()) >> 10
667 logging.debug(f"{device} size {size}MiB")
671 proc = subprocess.run(['sfdisk', '-v'],
672 stdout=subprocess.PIPE)
673 version = proc.stdout.decode('utf-8').strip()
674 logging.debug(f"Found {version}")
675 major, minor = [int(x) for x in re.findall('[0-9]+', version)][0:2]
676 support_delete = False
678 if major < 2 or major == 2 and minor < 26:
679 log.error(f"Your sfdisk {major}.{minor}.{patch} is too old.")
681 elif major == 2 and minor >= 28:
682 support_delete = True
684 return True, support_delete
686 def mkpart(args, target):
688 new, support_delete = check_sfdisk()
691 logging.error('sfdisk too old')
694 with open('/proc/self/mounts') as mounts:
695 device_kname = '/dev/' + get_device_kname(Device)
696 device_re = re.compile(device_kname + '[^ ]*')
697 logging.debug(f"Checking for mounted partitions on {device_kname}")
699 match = device_re.match(m)
701 logging.warning('Found mounted device: ' + match[0])
702 argv = ['umount', match[0]]
703 logging.debug(" ".join(argv))
704 proc = subprocess.run(argv)
705 if proc.returncode != 0:
706 logging.error(f"Failed to unmount {match[0]}")
710 logging.info("Removing old partitions")
711 argv = ['sfdisk', '--delete', Device]
712 logging.debug(" ".join(argv))
713 proc = subprocess.run(argv)
714 if proc.returncode != 0:
715 logging.error(f"Failed to remove the old partitions from {Device}")
717 logging.info("Removing old partition table")
718 argv = ['dd', 'if=/dev/zero', 'of=' + Device,
719 'bs=512', 'count=32', 'conv=notrunc']
720 logging.debug(" ".join(argv))
721 proc = subprocess.run(argv)
722 if proc.returncode != 0:
723 logging.error(f"Failed to clear the old partition table on {Device}")
726 logging.debug("New partition table:\n" + str(target.label))
727 argv = ['sfdisk', '--no-reread', '--wipe-partitions', 'always', Device]
728 logging.debug(" ".join(argv))
729 proc = subprocess.run(argv,
732 input=str(target.label).encode())
733 if proc.returncode != 0:
734 logging.error(f"Failed to create partition a new table on {Device}")
735 logging.error(f"New partition table:\n" + str(target.label))
738 logging.debug("Requesting kernel to re-read partition table:\n" + str(target.label))
739 argv = ['blockdev', '--rereadpt', Device]
740 logging.debug(" ".join(argv))
741 proc = subprocess.run(argv,
744 if proc.returncode != 0:
745 logging.error(f"Failed to request kernel to reread partition table on {Device}. (Insufficient permissions?)")
749 logging.debug("Writing bootcode\n")
750 with open(Device, "wb") as f:
751 f.write(target.bootcode)
754 for i, part in enumerate(target.part_table):
755 d = "/dev/" + get_partition_device(target.device, i+1)
756 if not 'fstype' in part:
757 logging.debug(f"Filesystem not defined for {d}, skipping")
759 logging.debug(f"Formatting {d} as {part['fstype']}")
760 if part['fstype'] == 'vfat':
761 argv = ['mkfs.vfat', '-F', '16', '-n', part['name'], d]
762 logging.debug(" ".join(argv))
763 proc = subprocess.run(argv,
764 stdin=subprocess.DEVNULL,
765 stdout=None, stderr=None)
766 if proc.returncode != 0:
767 log.error(f"Failed to create FAT filesystem on {d}")
769 elif part['fstype'] == 'ext4':
770 argv = ['mkfs.ext4', '-q', '-L', part['name'], d]
771 logging.debug(" ".join(argv))
772 proc = subprocess.run(argv,
773 stdin=subprocess.DEVNULL,
774 stdout=None, stderr=None)
775 if proc.returncode != 0:
776 log.error(f"Failed to create ext4 filesystem on {d}")
778 elif part['fstype'] == 'swap':
779 argv = ['mkswap', '-L', part['name'], d]
780 logging.debug(" ".join(argv))
781 proc = subprocess.run(argv,
782 stdin=subprocess.DEVNULL,
783 stdout=None, stderr=None)
784 if proc.returncode != 0:
785 log.error(f"Failed to format swap partition {d}")
787 elif part['fstype'] == 'raw':
789 target.initialize_parameters()
791 def check_args(args):
795 logging.info(f"Device: {args.device}")
797 if args.binaries and len(args.binaries) > 0:
798 logging.info("Fusing binar{}: {}".format("y" if len(args.binaries) == 1 else "ies",
799 ", ".join(args.binaries)))
812 response = input(f"{args.device} will be formatted. Continue? [y/N] ")
813 if response.lower() in ('y', 'yes'):
818 def check_device(args):
824 if os.path.exists(Device):
825 logging.error(f"Failed to create '{Device}', the file alread exists")
828 argv = ["dd", "if=/dev/zero", f"of={Device}",
829 "conv=sparse", "bs=1M", f"count={args.size}"]
830 logging.debug(" ".join(argv))
831 rc = subprocess.run(argv)
832 if rc.returncode != 0:
833 logging.error("Failed to create the backing file")
836 if os.path.isfile(Device):
840 argv = ["losetup", "--show", "--partscan", "--find", f"{File}"]
841 logging.debug(" ".join(argv))
842 proc = subprocess.run(argv,
843 stdout=subprocess.PIPE)
844 Device = proc.stdout.decode('utf-8').strip()
845 if proc.returncode != 0:
846 logging.error(f"Failed to attach {File} to a loopback device")
848 logging.debug(f"Loop device found: {Device}")
849 atexit.register(lambda: subprocess.run(["losetup", "-d", Device]))
853 if not stat.S_ISBLK(s.st_mode):
855 except FileNotFoundError:
856 logging.error(f"No such device: {Device}")
859 logging.error(f"{Device} is not a block device")
862 def check_partition_format(args, target):
867 logging.info(f"Skip formatting of {Device}".format(Device))
869 logging.info(f"Start formatting of {Device}")
871 logging.info(f"{Device} formatted")
873 def check_ddversion():
874 proc = subprocess.run(["dd", "--version"],
875 stdout=subprocess.PIPE)
876 version = proc.stdout.decode('utf-8').split('\n')[0].strip()
877 logging.debug(f"Found {version}")
878 major, minor = (int(x) for x in re.findall('[0-9]+', version))
880 if major < 8 or major == 8 and minor < 24:
885 def get_partition_device(device, idx):
886 argv = ['lsblk', device, '-o', 'TYPE,KNAME']
887 logging.debug(" ".join(argv))
888 proc = subprocess.run(argv,
889 stdout=subprocess.PIPE)
890 if proc.returncode != 0:
891 logging.error("lsblk has failed")
893 part_re = re.compile(f"^part\s+(.*[^0-9]{idx})$")
894 for l in proc.stdout.decode('utf-8').splitlines():
895 match = part_re.match(l)
900 def get_device_kname(device):
901 argv = ['lsblk', device, '-o', 'TYPE,KNAME']
902 logging.debug(" ".join(argv))
903 proc = subprocess.run(argv,
904 stdout=subprocess.PIPE)
905 for l in proc.stdout.decode('utf-8').splitlines():
906 match = re.search(f"^(disk|loop)\s+(.*)", l)
911 def do_fuse_file(f, name, target):
912 idx = target.get_partition_index(name)
914 logging.info(f"No partition defined for {name}, skipping.")
916 pdevice = "/dev/" + get_partition_device(Device, idx)
917 argv = ['dd', 'bs=4M',
923 logging.debug(" ".join(argv))
924 proc_dd = subprocess.Popen(argv,
926 stdin=subprocess.PIPE,
927 stdout=None, stderr=None)
928 logging.notice(f"Writing {name} to {pdevice}")
929 buf = f.read(4 << 20)
931 proc_dd.stdin.write(buf)
932 buf = f.read(4 << 20)
933 proc_dd.communicate()
937 #TODO: functions with the target argument should probably
938 # be part of some class
940 def do_fuse_image_super(tmpd, target):
942 metadata_size = 65536
944 hal_path = os.path.join(tmpd, 'hal.img')
945 rootfs_path = os.path.join(tmpd, 'rootfs.img')
946 super_path = os.path.join(tmpd, 'super.img')
949 hal_size = os.stat(hal_path).st_size
950 rootfs_size = os.stat(rootfs_path).st_size
951 except FileNotFoundError as e:
952 fn = os.path.split(e.filename)[-1]
953 logging.warning(f"{fn} is missing, skipping super partition image")
956 group_size = 2 * hal_size + rootfs_size
957 super_size = metadata_size + 2 * group_size
959 argv = ["lpmake", "-F",
961 f"--device-size={super_size}",
962 f"--metadata-size={metadata_size}",
963 f"--metadata-slots={metadata_slots}",
964 "-g", f"tizen_a:{group_size}",
965 "-p", f"rootfs_a:none:{rootfs_size}:tizen_a",
966 "-p", f"hal_a:none:{hal_size}:tizen_a",
967 "-g", f"tizen_b:{group_size}",
968 "-p", f"rootfs_b:none:{rootfs_size}:tizen_b",
969 "-p", f"hal_b:none:{hal_size}:tizen_b",
970 "-i", f"rootfs_a={rootfs_path}",
971 "-i", f"rootfs_b={rootfs_path}",
972 "-i", f"hal_a={hal_path}",
973 "-i", f"hal_b={hal_path}"]
974 logging.debug(" ".join(argv))
975 proc = subprocess.run(argv,
976 stdin=subprocess.DEVNULL,
977 stdout=None, stderr=None)
979 if proc.returncode != 0:
980 logging.error("Failed to create super.img")
981 do_fuse_image(super_path, target)
983 def do_fuse_image_tarball(tarball, tmpd, target):
984 with tarfile.open(tarball) as tf:
986 if target.with_super:
987 if entry.name in('hal.img', 'rootfs.img'):
988 tf.extract(entry, path=tmpd)
990 f = tf.extractfile(entry)
991 do_fuse_file(f, entry.name, target)
993 def do_fuse_image(img, target):
994 with open(img, 'rb') as f:
995 do_fuse_file(f, os.path.basename(img), target)
997 def fuse_image(args, target):
1000 if args.binaries is None or len(args.binaries) == 0:
1003 if not Yes and not Format:
1004 print(f"The following images will be written to {args.device} and the "
1005 "existing data will be lost.\n")
1006 for b in args.binaries:
1008 response = input("\nContinue? [y/N] ")
1009 if not response.lower() in ('y', 'yes'):
1012 with tempfile.TemporaryDirectory() as tmpd:
1013 for b in args.binaries:
1014 if re.search('\.(tar|tar\.gz|tgz)$', b):
1015 do_fuse_image_tarball(b, tmpd, target)
1017 fn = os.path.split(b)[-1]
1018 if target.with_super and fn in ('rootfs.img', 'hal.img'):
1019 shutil.copy(b, os.path.join(tmpd, fn))
1021 do_fuse_image(b, target)
1023 if target.with_super:
1024 do_fuse_image_super(tmpd, target)
1026 def logger_notice(self, msg, *args, **kws):
1027 if self.isEnabledFor(LOGGING_NOTICE):
1028 self._log(LOGGING_NOTICE, msg, args, **kws)
1029 logging.Logger.notice = logger_notice
1031 def logging_notice(msg, *args, **kws):
1032 if len(logging.root.handlers) == 0:
1034 logging.root.notice(msg, *args, **kws)
1035 logging.notice = logging_notice
1038 if __name__ == '__main__':
1039 parser = argparse.ArgumentParser(description="For {}, version {}".format(
1040 ", ".join([v.long_name for k,v in TARGETS.items()]),
1043 parser.add_argument("-b", "--binary", action="extend", dest="binaries",
1045 help="binary to flash, may be used multiple times")
1046 parser.add_argument("--create", action="store_true",
1047 help="create the backing file and format the loopback device")
1048 parser.add_argument("--debug", action="store_const", const="debug",
1049 default="notice", dest="log_level",
1050 help="set log level to DEBUG")
1051 parser.add_argument("-d", "--device",
1052 help="device node or loopback backing file")
1053 parser.add_argument("--format", action="store_true",
1054 help="create new partition table on the target device")
1055 parser.add_argument("--log-level", dest="log_level", default="notice",
1056 help="Verbosity, possible values: debug, info, notice, warning, "
1057 "error, critical (default: notice)")
1058 parser.add_argument("--partition-size", type=str, action="extend", dest="partition_sizes",
1060 help="override default partition size (in MiB) (used with --format), "
1061 "may be used multiple times, for example: --partition-size hal_a=256")
1062 parser.add_argument("--size", type=int, default=8192,
1063 help="size of the backing file to create (in MiB)")
1064 parser.add_argument("-t", "--target", required=True,
1065 help="Target device model. Use `--target list`"
1066 " to show supported devices.")
1067 parser.add_argument("--update", choices=['a', 'b'], default=None,
1068 help="Choose partition set to update: a or b.")
1069 parser.add_argument("--version", action="version",
1070 version=f"%(prog)s {__version__}")
1071 parser.add_argument("--YES", action="store_true",
1072 help="agree to destroy data on the DEVICE")
1073 args = parser.parse_args()
1075 if args.target == 'list':
1076 print("\nSupported devices:\n")
1077 for k,v in TARGETS.items():
1078 print(f" {k:6} {v.long_name}")
1081 if args.device is None:
1082 parser.error('-d/--device argument is required for normal operation')
1084 if args.partition_sizes is not None:
1085 partition_sizes = {}
1086 for eqstr in args.partition_sizes:
1087 ptstr = eqstr.split('=')
1090 size = int(ptstr[1])
1091 partition_sizes[name] = size
1093 parser.error('--partition-size must follow the name=size pattern')
1094 args.partition_sizes = partition_sizes
1096 logging.addLevelName(LOGGING_NOTICE, "NOTICE")
1097 conh = ColorStreamHandler(format='%(asctime)s.%(msecs)d %(debuginfo)s%(levelname)-8s %(message)s',
1098 cformat='%(asctime)s.%(msecs)d %(debuginfo)s%(levelcolor)s%(message)s',
1099 datefmt='%Y-%m-%dT%H:%M:%S')
1100 log_handlers = [conh]
1101 logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s',
1102 handlers=log_handlers,
1103 level=args.log_level.upper())
1105 logging.debug(" ".join(sys.argv))
1109 target = TARGETS[args.target](Device, args)
1111 check_partition_format(args, target)
1112 fuse_image(args, target)
1113 subprocess.run(['sync'],
1114 stdin=subprocess.DEVNULL,
1115 stdout=None, stderr=None )