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": "param"},
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": "param"},
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": "param"},
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 X86emu(SdFusingTarget):
558 {"size": 512, "fstype": "vfat", "name": "EFI", "start": 4,
559 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
561 {"size": 512, "fstype": "ext2", "name": "boot",
562 "binaries": "emulator-boot.img",},
563 {"size": 2048, "fstype": "ext4", "name": "rootfs",
564 "binaries": "emulator-rootfs.img"},
565 {"size": 1344, "fstype": "ext4", "name": "system-data",
566 "binaries": "emulator-sysdata.img"},
567 {"size": 1024, "fstype": "swap", "name": "emulator-swap",
568 "ptype": "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F"},
571 def __init__(self, device, args):
572 super().__init__(device, 'gpt')
573 for p in self.label.part_table:
574 if p.name == "rootfs":
575 p.ptype = args._rootfs_uuid
578 class X86emu32(X86emu):
579 long_name = "QEMU x86 32-bit"
581 def __init__(self, device, args):
582 setattr(args, "_rootfs_uuid", "44479540-F297-41B2-9AF7-D131D5F0458A")
583 super().__init__(device, args)
585 class X86emu64(X86emu):
586 long_name = "QEMU x86 64-bit"
588 def __init__(self, device, args):
589 setattr(args, "_rootfs_uuid", "44479540-F297-41B2-9AF7-D131D5F0458A")
590 super().__init__(device, args)
600 'x86emu32': X86emu32,
601 'x86emu64': X86emu64,
604 def device_size(device):
605 argv = ["sfdisk", "-s", device]
606 logging.debug(" ".join(argv))
607 proc = subprocess.run(argv,
608 stdout=subprocess.PIPE)
609 size = int(proc.stdout.decode('utf-8').strip()) >> 10
610 logging.debug(f"{device} size {size}MiB")
614 proc = subprocess.run(['sfdisk', '-v'],
615 stdout=subprocess.PIPE)
616 version = proc.stdout.decode('utf-8').strip()
617 logging.debug(f"Found {version}")
618 major, minor = [int(x) for x in re.findall('[0-9]+', version)][0:2]
619 support_delete = False
621 if major < 2 or major == 2 and minor < 26:
622 log.error(f"Your sfdisk {major}.{minor}.{patch} is too old.")
624 elif major == 2 and minor >= 28:
625 support_delete = True
627 return True, support_delete
629 def mkpart(args, target):
631 new, support_delete = check_sfdisk()
634 logging.error('sfdisk too old')
637 with open('/proc/self/mounts') as mounts:
638 device_kname = '/dev/' + get_device_kname(Device)
639 device_re = re.compile(device_kname + '[^ ]*')
640 logging.debug(f"Checking for mounted partitions on {device_kname}")
642 match = device_re.match(m)
644 logging.warning('Found mounted device: ' + match[0])
645 argv = ['umount', match[0]]
646 logging.debug(" ".join(argv))
647 proc = subprocess.run(argv)
648 if proc.returncode != 0:
649 logging.error(f"Failed to unmount {match[0]}")
653 logging.info("Removing old partitions")
654 argv = ['sfdisk', '--delete', Device]
655 logging.debug(" ".join(argv))
656 proc = subprocess.run(argv)
657 if proc.returncode != 0:
658 logging.error(f"Failed to remove the old partitions from {Device}")
660 logging.info("Removing old partition table")
661 argv = ['dd', 'if=/dev/zero', 'of=' + Device,
662 'bs=512', 'count=32', 'conv=notrunc']
663 logging.debug(" ".join(argv))
664 proc = subprocess.run(argv)
665 if proc.returncode != 0:
666 logging.error(f"Failed to clear the old partition table on {Device}")
669 logging.debug("New partition table:\n" + str(target.label))
670 argv = ['sfdisk', '--no-reread', '--wipe-partitions', 'always', Device]
671 logging.debug(" ".join(argv))
672 proc = subprocess.run(argv,
675 input=str(target.label).encode())
676 if proc.returncode != 0:
677 logging.error(f"Failed to create partition a new table on {Device}")
678 logging.error(f"New partition table:\n" + str(target.label))
681 logging.debug("Requesting kernel to re-read partition table:\n" + str(target.label))
682 argv = ['blockdev', '--rereadpt', Device]
683 logging.debug(" ".join(argv))
684 proc = subprocess.run(argv,
687 if proc.returncode != 0:
688 logging.error(f"Failed to request kernel to reread partition table on {Device}. (Insufficient permissions?)")
692 logging.debug("Writing bootcode\n")
693 with open(Device, "wb") as f:
694 f.write(target.bootcode)
697 for i, part in enumerate(target.part_table):
698 d = "/dev/" + get_partition_device(target.device, i+1)
699 if not 'fstype' in part:
700 logging.debug(f"Filesystem not defined for {d}, skipping")
702 logging.debug(f"Formatting {d} as {part['fstype']}")
703 if part['fstype'] == 'vfat':
704 argv = ['mkfs.vfat', '-F', '16', '-n', part['name'], d]
705 logging.debug(" ".join(argv))
706 proc = subprocess.run(argv,
707 stdin=subprocess.DEVNULL,
708 stdout=None, stderr=None)
709 if proc.returncode != 0:
710 log.error(f"Failed to create FAT filesystem on {d}")
712 elif part['fstype'] == 'ext4':
713 argv = ['mkfs.ext4', '-q', '-L', part['name'], d]
714 logging.debug(" ".join(argv))
715 proc = subprocess.run(argv,
716 stdin=subprocess.DEVNULL,
717 stdout=None, stderr=None)
718 if proc.returncode != 0:
719 log.error(f"Failed to create ext4 filesystem on {d}")
721 elif part['fstype'] == 'swap':
722 argv = ['mkswap', '-L', part['name'], d]
723 logging.debug(" ".join(argv))
724 proc = subprocess.run(argv,
725 stdin=subprocess.DEVNULL,
726 stdout=None, stderr=None)
727 if proc.returncode != 0:
728 log.error(f"Failed to format swap partition {d}")
730 elif part['fstype'] == 'raw':
732 target.initialize_parameters()
734 def check_args(args):
738 logging.info(f"Device: {args.device}")
740 if args.binaries and len(args.binaries) > 0:
741 logging.info("Fusing binar{}: {}".format("y" if len(args.binaries) == 1 else "ies",
742 ", ".join(args.binaries)))
755 response = input(f"{args.device} will be formatted. Continue? [y/N] ")
756 if response.lower() in ('y', 'yes'):
761 def check_device(args):
767 if os.path.exists(Device):
768 logging.error(f"Failed to create '{Device}', the file alread exists")
771 argv = ["dd", "if=/dev/zero", f"of={Device}",
772 "conv=sparse", "bs=1M", f"count={args.size}"]
773 logging.debug(" ".join(argv))
774 rc = subprocess.run(argv)
775 if rc.returncode != 0:
776 logging.error("Failed to create the backing file")
779 if os.path.isfile(Device):
783 argv = ["losetup", "--show", "--partscan", "--find", f"{File}"]
784 logging.debug(" ".join(argv))
785 proc = subprocess.run(argv,
786 stdout=subprocess.PIPE)
787 Device = proc.stdout.decode('utf-8').strip()
788 if proc.returncode != 0:
789 logging.error(f"Failed to attach {File} to a loopback device")
791 logging.debug(f"Loop device found: {Device}")
792 atexit.register(lambda: subprocess.run(["losetup", "-d", Device]))
796 if not stat.S_ISBLK(s.st_mode):
798 except FileNotFoundError:
799 logging.error(f"No such device: {Device}")
802 logging.error(f"{Device} is not a block device")
805 def check_partition_format(args, target):
810 logging.info(f"Skip formatting of {Device}".format(Device))
812 logging.info(f"Start formatting of {Device}")
814 logging.info(f"{Device} formatted")
816 def check_ddversion():
817 proc = subprocess.run(["dd", "--version"],
818 stdout=subprocess.PIPE)
819 version = proc.stdout.decode('utf-8').split('\n')[0].strip()
820 logging.debug(f"Found {version}")
821 major, minor = (int(x) for x in re.findall('[0-9]+', version))
823 if major < 8 or major == 8 and minor < 24:
828 def get_partition_device(device, idx):
829 argv = ['lsblk', device, '-o', 'TYPE,KNAME']
830 logging.debug(" ".join(argv))
831 proc = subprocess.run(argv,
832 stdout=subprocess.PIPE)
833 if proc.returncode != 0:
834 logging.error("lsblk has failed")
836 part_re = re.compile(f"^part\s+(.*[^0-9]{idx})$")
837 for l in proc.stdout.decode('utf-8').splitlines():
838 match = part_re.match(l)
843 def get_device_kname(device):
844 argv = ['lsblk', device, '-o', 'TYPE,KNAME']
845 logging.debug(" ".join(argv))
846 proc = subprocess.run(argv,
847 stdout=subprocess.PIPE)
848 for l in proc.stdout.decode('utf-8').splitlines():
849 match = re.search(f"^(disk|loop)\s+(.*)", l)
854 def do_fuse_file(f, name, target):
855 idx = target.get_partition_index(name)
857 logging.info(f"No partition defined for {name}, skipping.")
859 pdevice = "/dev/" + get_partition_device(Device, idx)
860 argv = ['dd', 'bs=4M',
866 logging.debug(" ".join(argv))
867 proc_dd = subprocess.Popen(argv,
869 stdin=subprocess.PIPE,
870 stdout=None, stderr=None)
871 logging.notice(f"Writing {name} to {pdevice}")
872 buf = f.read(4 << 20)
874 proc_dd.stdin.write(buf)
875 buf = f.read(4 << 20)
876 proc_dd.communicate()
880 #TODO: functions with the target argument should probably
881 # be part of some class
883 def do_fuse_image_super(tmpd, target):
885 metadata_size = 65536
887 hal_path = os.path.join(tmpd, 'hal.img')
888 rootfs_path = os.path.join(tmpd, 'rootfs.img')
889 super_path = os.path.join(tmpd, 'super.img')
892 hal_size = os.stat(hal_path).st_size
893 rootfs_size = os.stat(rootfs_path).st_size
894 except FileNotFoundError as e:
895 fn = os.path.split(e.filename)[-1]
896 logging.warning(f"{fn} is missing, skipping super partition image")
899 group_size = 2 * hal_size + rootfs_size
900 super_size = metadata_size + 2 * group_size
902 argv = ["lpmake", "-F",
904 f"--device-size={super_size}",
905 f"--metadata-size={metadata_size}",
906 f"--metadata-slots={metadata_slots}",
907 "-g", f"tizen_a:{group_size}",
908 "-p", f"rootfs_a:none:{rootfs_size}:tizen_a",
909 "-p", f"hal_a:none:{hal_size}:tizen_a",
910 "-g", f"tizen_b:{group_size}",
911 "-p", f"rootfs_b:none:{rootfs_size}:tizen_b",
912 "-p", f"hal_b:none:{hal_size}:tizen_b",
913 "-i", f"rootfs_a={rootfs_path}",
914 "-i", f"rootfs_b={rootfs_path}",
915 "-i", f"hal_a={hal_path}",
916 "-i", f"hal_b={hal_path}"]
917 logging.debug(" ".join(argv))
918 proc = subprocess.run(argv,
919 stdin=subprocess.DEVNULL,
920 stdout=None, stderr=None)
922 if proc.returncode != 0:
923 logging.error("Failed to create super.img")
924 do_fuse_image(super_path, target)
926 def do_fuse_image_tarball(tarball, tmpd, target):
927 with tarfile.open(tarball) as tf:
929 if target.with_super:
930 if entry.name in('hal.img', 'rootfs.img'):
931 tf.extract(entry, path=tmpd)
933 f = tf.extractfile(entry)
934 do_fuse_file(f, entry.name, target)
936 def do_fuse_image(img, target):
937 with open(img, 'rb') as f:
938 do_fuse_file(f, os.path.basename(img), target)
940 def fuse_image(args, target):
943 if args.binaries is None or len(args.binaries) == 0:
946 if not Yes and not Format:
947 print(f"The following images will be written to {args.device} and the "
948 "existing data will be lost.\n")
949 for b in args.binaries:
951 response = input("\nContinue? [y/N] ")
952 if not response.lower() in ('y', 'yes'):
955 with tempfile.TemporaryDirectory() as tmpd:
956 for b in args.binaries:
957 if re.search('\.(tar|tar\.gz|tgz)$', b):
958 do_fuse_image_tarball(b, tmpd, target)
960 fn = os.path.split(b)[-1]
961 if target.with_super and fn in ('rootfs.img', 'hal.img'):
962 shutil.copy(b, os.path.join(tmpd, fn))
964 do_fuse_image(b, target)
966 if target.with_super:
967 do_fuse_image_super(tmpd, target)
969 def logger_notice(self, msg, *args, **kws):
970 if self.isEnabledFor(LOGGING_NOTICE):
971 self._log(LOGGING_NOTICE, msg, args, **kws)
972 logging.Logger.notice = logger_notice
974 def logging_notice(msg, *args, **kws):
975 if len(logging.root.handlers) == 0:
977 logging.root.notice(msg, *args, **kws)
978 logging.notice = logging_notice
981 if __name__ == '__main__':
982 parser = argparse.ArgumentParser(description="For {}, version {}".format(
983 ", ".join([v.long_name for k,v in TARGETS.items()]),
986 parser.add_argument("-b", "--binary", action="extend", dest="binaries",
988 help="binary to flash, may be used multiple times")
989 parser.add_argument("--create", action="store_true",
990 help="create the backing file and format the loopback device")
991 parser.add_argument("--debug", action="store_const", const="debug",
992 default="notice", dest="log_level",
993 help="set log level to DEBUG")
994 parser.add_argument("-d", "--device",
995 help="device node or loopback backing file")
996 parser.add_argument("--format", action="store_true",
997 help="create new partition table on the target device")
998 parser.add_argument("--log-level", dest="log_level", default="notice",
999 help="Verbosity, possible values: debug, info, notice, warning, "
1000 "error, critical (default: notice)")
1001 parser.add_argument("--partition-size", type=str, action="extend", dest="partition_sizes",
1003 help="override default partition size (in MiB) (used with --format), "
1004 "may be used multiple times, for example: --partition-size hal_a=256")
1005 parser.add_argument("--size", type=int, default=8192,
1006 help="size of the backing file to create (in MiB)")
1007 parser.add_argument("-t", "--target", required=True,
1008 help="Target device model. Use `--target list`"
1009 " to show supported devices.")
1010 parser.add_argument("--update", choices=['a', 'b'], default=None,
1011 help="Choose partition set to update: a or b.")
1012 parser.add_argument("--version", action="version",
1013 version=f"%(prog)s {__version__}")
1014 parser.add_argument("--YES", action="store_true",
1015 help="agree to destroy data on the DEVICE")
1016 args = parser.parse_args()
1018 if args.target == 'list':
1019 print("\nSupported devices:\n")
1020 for k,v in TARGETS.items():
1021 print(f" {k:6} {v.long_name}")
1024 if args.device is None:
1025 parser.error('-d/--device argument is required for normal operation')
1027 if args.partition_sizes is not None:
1028 partition_sizes = {}
1029 for eqstr in args.partition_sizes:
1030 ptstr = eqstr.split('=')
1033 size = int(ptstr[1])
1034 partition_sizes[name] = size
1036 parser.error('--partition-size must follow the name=size pattern')
1037 args.partition_sizes = partition_sizes
1039 logging.addLevelName(LOGGING_NOTICE, "NOTICE")
1040 conh = ColorStreamHandler(format='%(asctime)s.%(msecs)d %(debuginfo)s%(levelname)-8s %(message)s',
1041 cformat='%(asctime)s.%(msecs)d %(debuginfo)s%(levelcolor)s%(message)s',
1042 datefmt='%Y-%m-%dT%H:%M:%S')
1043 log_handlers = [conh]
1044 logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s',
1045 handlers=log_handlers,
1046 level=args.log_level.upper())
1048 logging.debug(" ".join(sys.argv))
1052 target = TARGETS[args.target](Device, args)
1054 check_partition_format(args, target)
1055 fuse_image(args, target)
1056 subprocess.run(['sync'],
1057 stdin=subprocess.DEVNULL,
1058 stdout=None, stderr=None )