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 self.binaries = self._get_binaries('binaries')
133 def apply_partition_sizes(self, partition_sizes):
134 if partition_sizes is None or len(partition_sizes) == 0:
137 for name, size in partition_sizes.items():
139 for part in self.part_table:
140 if part['name'] == name:
143 logging.debug(f"overriding partition:{name}, old-size:{psize} MiB new-size:{size} MiB")
144 resized_count = resized_count + 1
145 if resized_count == 0:
146 logging.error(f"partition:{name} not found when attempting to apply_partition_sizes")
147 resized_total = resized_total + resized_count
150 def _get_binaries(self, key):
152 for i, p in enumerate(self.part_table):
156 if isinstance(b, str):
158 elif isinstance(b, list):
163 def get_partition_index(self, binary):
164 return self.binaries.get(binary, None)
167 def initialize_parameters(self):
170 class SdFusingTargetAB(SdFusingTarget):
171 def __init__(self, device, ltype):
172 super().__init__(device, ltype)
173 self.binaries_b = self._get_binaries('binaries_b')
175 def get_partition_index(self, binary):
176 if self.update == 'b':
177 return self.binaries_b.get(binary, None)
178 return self.binaries.get(binary, None)
181 def initialize_parameters(self):
182 logging.debug("Initializing parameterss")
184 for i, p in enumerate(self.part_table):
185 if p['name'] == 'inform':
188 d = "/dev/" + get_partition_device(self.device, n)
190 argv = ['tune2fs', '-O', '^metadata_csum', d]
191 logging.debug(" ".join(argv))
193 stdin=subprocess.DEVNULL,
194 stdout=None, stderr=None)
196 with tempfile.TemporaryDirectory() as mnt:
197 argv = ['mount', '-t', 'ext4', d, mnt]
198 logging.debug(" ".join(argv))
199 proc = subprocess.run(argv,
200 stdin=subprocess.DEVNULL,
201 stdout=None, stderr=None)
202 if proc.returncode != 0:
203 logging.error("Failed to mount {d} in {mnt}")
205 for param, value in self.params:
206 with open(os.path.join(mnt, param), 'w') as f:
207 f.write(value + '\n')
209 logging.debug(" ".join(argv))
211 stdin=subprocess.DEVNULL,
212 stdout=None, stderr=None)
214 class Rpi3(InitParams, SdFusingTarget):
215 long_name = "Raspberry Pi 3"
217 {"size": 64, "fstype": "vfat", "name": "boot", "start": 4, "ptype": "0xe", "bootable": True,
218 "binaries": "boot.img"},
219 {"size": 3072, "fstype": "ext4", "name": "rootfs",
220 "binaries": "rootfs.img"},
221 {"size": 1344, "fstype": "ext4", "name": "system-data",
222 "binaries": "system-data.img"},
223 {"size": None, "ptype": "5", "name": "extended", "start": 4484},
224 {"size": None, "fstype": "ext4", "name": "user",
225 "binaries": "user.img"},
226 {"size": 32, "fstype": "ext4", "name": "modules",
227 "binaries": "modules.img"},
228 {"size": 32, "fstype": "ext4", "name": "ramdisk",
229 "binaries": "ramdisk.img"},
230 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery",
231 "binaries": "ramdisk-recovery.img"},
232 {"size": 8, "fstype": "ext4", "name": "inform"},
233 {"size": 256, "fstype": "ext4", "name": "hal",
234 "binaries": "hal.img"},
235 {"size": 125, "fstype": "ext4", "name": "reserved2"},
237 params = (('reboot-param.bin', ''),)
239 def __init__(self, device, args):
240 self.reserved_space = 12
241 self.user_partition = 4
242 super().__init__(device, "dos")
244 class Rpi4Super(InitParams, SdFusingTargetAB):
245 long_name = "Raspberry Pi 4 w/ super partition"
247 {"size": 64, "fstype": "vfat", "name": "boot_a","start": 4,
248 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
249 "binaries": "boot.img"},
250 {"size": 6656, "fstype": "ext4", "name": "super",
251 "binaries": "super.img"},
252 {"size": 1344, "fstype": "ext4", "name": "system-data",
253 "binaries": "system-data.img"},
254 {"size": 36, "fstype": "raw", "name": "none"},
255 {"size": None, "fstype": "ext4", "name": "user",
256 "binaries": "user.img"},
257 {"size": 32, "fstype": "ext4", "name": "module_a",
258 "binaries": "modules.img"},
259 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
260 "binaries": "ramdisk.img"},
261 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
262 "binaries": "ramdisk-recovery.img"},
263 {"size": 8, "fstype": "ext4", "name": "inform"},
264 {"size": 64, "fstype": "vfat", "name": "boot_b",
265 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
266 "binaries_b": "boot.img"},
267 {"size": 32, "fstype": "ext4", "name": "module_b",
268 "binaries_b": "modules.img"},
269 {"size": 32, "fstype": "ext4", "name": "ramdisk_b",
270 "binaries_b": "ramdisk.img"},
271 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_b",
272 "binaries_b": "ramdisk-recovery.img"},
273 {"size": 4, "fstype": "ext4", "name": "reserved0"},
274 {"size": 64, "fstype": "ext4", "name": "reserved1"},
275 {"size": 125, "fstype": "ext4", "name": "reserved2"}
277 params = (('reboot-param.bin', 'norm'),
278 ('reboot-param.info', 'norm'),
279 ('partition-ab.info', 'a'),
280 ('partition-ab-cloned.info', '1'),
281 ('upgrade-status.info', '0'),
282 ('partition-a-status.info', 'ok'),
283 ('partition-b-status.info', 'ok'))
285 def __init__(self, device, args):
286 self.reserved_space = 8
287 self.user_partition = 4
288 self.update = args.update
289 super().__init__(device, "gpt")
290 self.with_super = True
291 self.super_alignment = 1048576
293 class Rpi4(InitParams, SdFusingTargetAB):
294 long_name = "Raspberry Pi 4"
296 {"size": 64, "fstype": "vfat", "name": "boot_a", "start": 4,
297 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
298 "binaries": "boot.img"},
299 {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
300 "binaries": "rootfs.img"},
301 {"size": 1344, "fstype": "ext4", "name": "system-data",
302 "binaries": "system-data.img"},
303 {"size": 36, "fstype": "raw", "name": "none"},
304 {"size": None, "fstype": "ext4", "name": "user",
305 "binaries": "user.img"},
306 {"size": 32, "fstype": "ext4", "name": "module_a",
307 "binaries": "modules.img"},
308 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
309 "binaries": "ramdisk.img"},
310 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
311 "binaries": "ramdisk-recovery.img"},
312 {"size": 8, "fstype": "ext4", "name": "inform"},
313 {"size": 256, "fstype": "ext4", "name": "hal_a",
314 "binaries": "hal.img"},
315 {"size": 64, "fstype": "vfat", "name": "boot_b",
316 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
317 "binaries_b": "boot.img"},
318 {"size": 3072, "fstype": "ext4", "name": "rootfs_b",
319 "binaries_b": "rootfs.img"},
320 {"size": 32, "fstype": "ext4", "name": "module_b",
321 "binaries_b": "modules.img"},
322 {"size": 32, "fstype": "ext4", "name": "ramdisk_b",
323 "binaries_b": "ramdisk.img"},
324 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_b",
325 "binaries_b": "ramdisk-recovery.img"},
326 {"size": 256, "fstype": "ext4", "name": "hal_b",
327 "binaries_b": "hal.img"},
328 {"size": 4, "fstype": "ext4", "name": "param"},
329 {"size": 64, "fstype": "ext4", "name": "reserved1"},
330 {"size": 125, "fstype": "ext4", "name": "reserved2"},
332 params = (('reboot-param.bin', 'norm'),
333 ('reboot-param.info', 'norm'),
334 ('partition-ab.info', 'a'),
335 ('partition-ab-cloned.info', '1'),
336 ('upgrade-status.info', '0'),
337 ('partition-a-status.info', 'ok'),
338 ('partition-b-status.info', 'ok'))
340 def __init__(self, device, args):
341 self.reserved_space = 5
342 self.user_partition = 4
343 self.update = args.update
344 super().__init__(device, "gpt")
346 class Rpi4AoT(InitParams, SdFusingTargetAB):
347 long_name = "Raspberry Pi 4 for AoT"
349 {"size": 64, "fstype": "vfat", "name": "boot_a", "start": 4,
350 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
351 "binaries": "boot.img"},
352 {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
353 "binaries": "rootfs.img"},
354 {"size": 1344, "fstype": "ext4", "name": "system-data",
355 "binaries": "system-data.img"},
356 {"size": 36, "fstype": "raw", "name": "none"},
357 {"size": None, "fstype": "ext4", "name": "user",
358 "binaries": "user.img"},
359 {"size": 32, "fstype": "ext4", "name": "module_a",
360 "binaries": "modules.img"},
361 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
362 "binaries": "ramdisk.img"},
363 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
364 "binaries": "ramdisk-recovery.img"},
365 {"size": 8, "fstype": "ext4", "name": "inform"},
366 {"size": 256, "fstype": "ext4", "name": "hal_a",
367 "binaries": "hal.img"},
368 {"size": 64, "fstype": "vfat", "name": "boot_b",
369 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
370 "binaries_b": "boot.img"},
371 {"size": 3072, "fstype": "ext4", "name": "rootfs_b",
372 "binaries_b": "rootfs.img"},
373 {"size": 32, "fstype": "ext4", "name": "module_b",
374 "binaries_b": "modules.img"},
375 {"size": 32, "fstype": "ext4", "name": "ramdisk_b",
376 "binaries_b": "ramdisk.img"},
377 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_b",
378 "binaries_b": "ramdisk-recovery.img"},
379 {"size": 256, "fstype": "ext4", "name": "hal_b",
380 "binaries_b": "hal.img"},
381 {"size": 1536, "fstype": "ext4", "name": "aot-system_a",
382 "binaries": "system.img"},
383 {"size": 1536, "fstype": "ext4", "name": "aot-system_b",
384 "binaries_b": "system.img"},
385 {"size": 256, "fstype": "ext4", "name": "aot-vendor_a",
386 "binaries": "vendor.img"},
387 {"size": 256, "fstype": "ext4", "name": "aot-vendor_b",
388 "binaries_b": "vendor.img"},
389 {"size": 4, "fstype": "ext4", "name": "param"},
390 {"size": 64, "fstype": "ext4", "name": "reserved1"},
391 {"size": 125, "fstype": "ext4", "name": "reserved2"},
393 params = (('reboot-param.bin', 'norm'),
394 ('reboot-param.info', 'norm'),
395 ('partition-ab.info', 'a'),
396 ('partition-ab-cloned.info', '1'),
397 ('upgrade-status.info', '0'),
398 ('partition-a-status.info', 'ok'),
399 ('partition-b-status.info', 'ok'))
401 def __init__(self, device, args):
402 self.reserved_space = 5
403 self.user_partition = 4
404 self.update = args.update
405 super().__init__(device, "gpt")
407 class RV64(InitParams, SdFusingTarget):
408 long_name = "QEMU RISC-V 64-bit"
410 {"size": 2, "fstype": "raw", "name": "SPL", "start": 4,
411 "ptype": "2E54B353-1271-4842-806F-E436D6AF6985",
413 {"size": 4, "fstype": "raw", "name": "u-boot",
414 "ptype": "5B193300-FC78-40CD-8002-E86C45580B47",
415 "binaries": ["u-boot.img", "u-boot.itb"],},
416 {"size": 292, "fstype": "vfat", "name": "boot_a",
417 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
418 "binaries": "boot.img"},
419 {"size": 36, "fstype": "raw", "name": "none"},
420 {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
421 "binaries": "rootfs.img"},
422 {"size": 1344, "fstype": "ext4", "name": "system-data",
423 "binaries": "system-data.img"},
424 {"size": None, "fstype": "ext4", "name": "user",
425 "binaries": "user.img"},
426 {"size": 32, "fstype": "ext4", "name": "module_a",
427 "binaries": "modules.img"},
428 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
429 "binaries": "ramdisk.img"},
430 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
431 "binaries": "ramdisk-recovery.img"},
432 {"size": 8, "fstype": "ext4", "name": "inform"},
433 {"size": 256, "fstype": "ext4", "name": "hal_a",
434 "binaries": "hal.img"},
435 {"size": 4, "fstype": "raw", "name": "reserved0"},
436 {"size": 64, "fstype": "raw", "name": "reserved1"},
437 {"size": 125, "fstype": "raw", "name": "reserved2"},
439 params = (('reboot-param.bin', 'norm'),
440 ('reboot-param.info', 'norm'))
442 def __init__(self, device, args):
443 self.user_partition = 6
444 self.reserved_space = 5
445 self.apply_partition_sizes(args.partition_sizes)
446 super().__init__(device, 'gpt')
448 class VF2(InitParams, SdFusingTarget):
449 long_name = "VisionFive2"
451 {"size": 2, "fstype": "raw", "name": "SPL", "start": 4,
452 "ptype": "2E54B353-1271-4842-806F-E436D6AF6985",
453 "binaries": ["u-boot-spl.bin.normal.out"],},
454 {"size": 4, "fstype": "raw", "name": "u-boot",
455 "ptype": "5B193300-FC78-40CD-8002-E86C45580B47",
456 "binaries": ["u-boot.img", "u-boot.itb"],},
457 {"size": 292, "fstype": "vfat", "name": "boot_a",
458 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
459 "binaries": "boot.img"},
460 {"size": 36, "fstype": "raw", "name": "none"},
461 {"size": 3072, "fstype": "ext4", "name": "rootfs_a",
462 "binaries": "rootfs.img"},
463 {"size": 1344, "fstype": "ext4", "name": "system-data",
464 "binaries": "system-data.img"},
465 {"size": None, "fstype": "ext4", "name": "user",
466 "binaries": "user.img"},
467 {"size": 32, "fstype": "ext4", "name": "module_a",
468 "binaries": "modules.img"},
469 {"size": 32, "fstype": "ext4", "name": "ramdisk_a",
470 "binaries": "ramdisk.img"},
471 {"size": 32, "fstype": "ext4", "name": "ramdisk-recovery_a",
472 "binaries": "ramdisk-recovery.img"},
473 {"size": 8, "fstype": "ext4", "name": "inform"},
474 {"size": 256, "fstype": "ext4", "name": "hal_a",
475 "binaries": "hal.img"},
476 {"size": 4, "fstype": "raw", "name": "reserved0"},
477 {"size": 64, "fstype": "raw", "name": "reserved1"},
478 {"size": 125, "fstype": "raw", "name": "reserved2"},
480 params = (('reboot-param.bin', 'norm'),
481 ('reboot-param.info', 'norm'))
483 def __init__(self, device, args):
484 self.user_partition = 6
485 self.reserved_space = 5
486 self.apply_partition_sizes(args.partition_sizes)
487 super().__init__(device, 'gpt')
489 class X86emu(SdFusingTarget):
491 {"size": 512, "fstype": "vfat", "name": "EFI", "start": 4,
492 "ptype": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
494 {"size": 512, "fstype": "ext2", "name": "boot",
495 "binaries": "emulator-boot.img",},
496 {"size": 2048, "fstype": "ext4", "name": "rootfs",
497 "binaries": "emulator-rootfs.img"},
498 {"size": 1344, "fstype": "ext4", "name": "system-data",
499 "binaries": "emulator-sysdata.img"},
500 {"size": 1024, "fstype": "swap", "name": "emulator-swap",
501 "ptype": "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F"},
504 def __init__(self, device, args):
505 super().__init__(device, 'gpt')
506 for p in self.label.part_table:
507 if p.name == "rootfs":
508 p.ptype = args._rootfs_uuid
511 class X86emu32(X86emu):
512 long_name = "QEMU x86 32-bit"
514 def __init__(self, device, args):
515 setattr(args, "_rootfs_uuid", "44479540-F297-41B2-9AF7-D131D5F0458A")
516 super().__init__(device, args)
518 class X86emu64(X86emu):
519 long_name = "QEMU x86 64-bit"
521 def __init__(self, device, args):
522 setattr(args, "_rootfs_uuid", "44479540-F297-41B2-9AF7-D131D5F0458A")
523 super().__init__(device, args)
532 'x86emu32': X86emu32,
533 'x86emu64': X86emu64,
536 def device_size(device):
537 argv = ["sfdisk", "-s", device]
538 logging.debug(" ".join(argv))
539 proc = subprocess.run(argv,
540 stdout=subprocess.PIPE)
541 size = int(proc.stdout.decode('utf-8').strip()) >> 10
542 logging.debug(f"{device} size {size}MiB")
546 proc = subprocess.run(['sfdisk', '-v'],
547 stdout=subprocess.PIPE)
548 version = proc.stdout.decode('utf-8').strip()
549 logging.debug(f"Found {version}")
550 major, minor = [int(x) for x in re.findall('[0-9]+', version)][0:2]
551 support_delete = False
553 if major < 2 or major == 2 and minor < 26:
554 log.error(f"Your sfdisk {major}.{minor}.{patch} is too old.")
556 elif major == 2 and minor >= 28:
557 support_delete = True
559 return True, support_delete
561 def mkpart(args, target):
563 new, support_delete = check_sfdisk()
566 logging.error('sfdisk too old')
569 with open('/proc/self/mounts') as mounts:
570 device_kname = '/dev/' + get_device_kname(Device)
571 device_re = re.compile(device_kname + '[^ ]*')
572 logging.debug(f"Checking for mounted partitions on {device_kname}")
574 match = device_re.match(m)
576 logging.warning('Found mounted device: ' + match[0])
577 argv = ['umount', match[0]]
578 logging.debug(" ".join(argv))
579 proc = subprocess.run(argv)
580 if proc.returncode != 0:
581 logging.error(f"Failed to unmount {match[0]}")
585 logging.info("Removing old partitions")
586 argv = ['sfdisk', '--delete', Device]
587 logging.debug(" ".join(argv))
588 proc = subprocess.run(argv)
589 if proc.returncode != 0:
590 logging.error(f"Failed to remove the old partitions from {Device}")
592 logging.info("Removing old partition table")
593 argv = ['dd', 'if=/dev/zero', 'of=' + Device,
594 'bs=512', 'count=32', 'conv=notrunc']
595 logging.debug(" ".join(argv))
596 proc = subprocess.run(argv)
597 if proc.returncode != 0:
598 logging.error(f"Failed to clear the old partition table on {Device}")
601 logging.debug("New partition table:\n" + str(target.label))
602 argv = ['sfdisk', '--wipe-partitions', 'always', Device]
603 logging.debug(" ".join(argv))
604 proc = subprocess.run(argv,
607 input=str(target.label).encode())
608 if proc.returncode != 0:
609 logging.error(f"Failed to create partition a new table on {Device}")
610 logging.error(f"New partition table:\n" + str(target.label))
613 for i, part in enumerate(target.part_table):
614 d = "/dev/" + get_partition_device(target.device, i+1)
615 if not 'fstype' in part:
616 logging.debug(f"Filesystem not defined for {d}, skipping")
618 logging.debug(f"Formatting {d} as {part['fstype']}")
619 if part['fstype'] == 'vfat':
620 argv = ['mkfs.vfat', '-F', '16', '-n', part['name'], d]
621 logging.debug(" ".join(argv))
622 proc = subprocess.run(argv,
623 stdin=subprocess.DEVNULL,
624 stdout=None, stderr=None)
625 if proc.returncode != 0:
626 log.error(f"Failed to create FAT filesystem on {d}")
628 elif part['fstype'] == 'ext4':
629 argv = ['mkfs.ext4', '-q', '-L', part['name'], d]
630 logging.debug(" ".join(argv))
631 proc = subprocess.run(argv,
632 stdin=subprocess.DEVNULL,
633 stdout=None, stderr=None)
634 if proc.returncode != 0:
635 log.error(f"Failed to create ext4 filesystem on {d}")
637 elif part['fstype'] == 'swap':
638 argv = ['mkswap', '-L', part['name'], d]
639 logging.debug(" ".join(argv))
640 proc = subprocess.run(argv,
641 stdin=subprocess.DEVNULL,
642 stdout=None, stderr=None)
643 if proc.returncode != 0:
644 log.error(f"Failed to format swap partition {d}")
646 elif part['fstype'] == 'raw':
648 target.initialize_parameters()
650 def check_args(args):
654 logging.info(f"Device: {args.device}")
656 if args.binaries and len(args.binaries) > 0:
657 logging.info("Fusing binar{}: {}".format("y" if len(args.binaries) == 1 else "ies",
658 ", ".join(args.binaries)))
671 response = input(f"{args.device} will be formatted. Continue? [y/N] ")
672 if response.lower() in ('y', 'yes'):
677 def check_device(args):
683 if os.path.exists(Device):
684 logging.error(f"Failed to create '{Device}', the file alread exists")
687 argv = ["dd", "if=/dev/zero", f"of={Device}",
688 "conv=sparse", "bs=1M", f"count={args.size}"]
689 logging.debug(" ".join(argv))
690 rc = subprocess.run(argv)
691 if rc.returncode != 0:
692 logging.error("Failed to create the backing file")
695 if os.path.isfile(Device):
699 argv = ["losetup", "--show", "--partscan", "--find", f"{File}"]
700 logging.debug(" ".join(argv))
701 proc = subprocess.run(argv,
702 stdout=subprocess.PIPE)
703 Device = proc.stdout.decode('utf-8').strip()
704 if proc.returncode != 0:
705 logging.error(f"Failed to attach {File} to a loopback device")
707 logging.debug(f"Loop device found: {Device}")
708 atexit.register(lambda: subprocess.run(["losetup", "-d", Device]))
712 if not stat.S_ISBLK(s.st_mode):
714 except FileNotFoundError:
715 logging.error(f"No such device: {Device}")
718 logging.error(f"{Device} is not a block device")
721 def check_partition_format(args, target):
726 logging.info(f"Skip formatting of {Device}".format(Device))
728 logging.info(f"Start formatting of {Device}")
730 logging.info(f"{Device} formatted")
732 def check_ddversion():
733 proc = subprocess.run(["dd", "--version"],
734 stdout=subprocess.PIPE)
735 version = proc.stdout.decode('utf-8').split('\n')[0].strip()
736 logging.debug(f"Found {version}")
737 major, minor = (int(x) for x in re.findall('[0-9]+', version))
739 if major < 8 or major == 8 and minor < 24:
744 def get_partition_device(device, idx):
745 argv = ['lsblk', device, '-o', 'TYPE,KNAME']
746 logging.debug(" ".join(argv))
747 proc = subprocess.run(argv,
748 stdout=subprocess.PIPE)
749 if proc.returncode != 0:
750 logging.error("lsblk has failed")
752 part_re = re.compile(f"^part\s+(.*[^0-9]{idx})$")
753 for l in proc.stdout.decode('utf-8').splitlines():
754 match = part_re.match(l)
759 def get_device_kname(device):
760 argv = ['lsblk', device, '-o', 'TYPE,KNAME']
761 logging.debug(" ".join(argv))
762 proc = subprocess.run(argv,
763 stdout=subprocess.PIPE)
764 for l in proc.stdout.decode('utf-8').splitlines():
765 match = re.search(f"^(disk|loop)\s+(.*)", l)
770 def do_fuse_file(f, name, target):
771 idx = target.get_partition_index(name)
773 logging.info(f"No partition defined for {name}, skipping.")
775 pdevice = "/dev/" + get_partition_device(Device, idx)
776 argv = ['dd', 'bs=4M',
782 logging.debug(" ".join(argv))
783 proc_dd = subprocess.Popen(argv,
785 stdin=subprocess.PIPE,
786 stdout=None, stderr=None)
787 logging.notice(f"Writing {name} to {pdevice}")
788 buf = f.read(4 << 20)
790 proc_dd.stdin.write(buf)
791 buf = f.read(4 << 20)
792 proc_dd.communicate()
796 #TODO: functions with the target argument should probably
797 # be part of some class
799 def get_aligned_size(size, target):
800 return target.super_alignment*int(1+(size-1)/target.super_alignment)
802 def do_fuse_image_super(tmpd, target):
804 metadata_size = 65536
805 metadata_aligned_size = get_aligned_size(metadata_size, target)
807 hal_path = os.path.join(tmpd, 'hal.img')
808 rootfs_path = os.path.join(tmpd, 'rootfs.img')
809 super_path = os.path.join(tmpd, 'super.img')
812 hal_size = os.stat(hal_path).st_size
813 rootfs_size = os.stat(rootfs_path).st_size
814 except FileNotFoundError as e:
815 fn = os.path.split(e.filename)[-1]
816 logging.warning(f"{fn} is missing, skipping super partition image")
819 hal_aligned_size = get_aligned_size(hal_size, target)
820 rootfs_aligned_size = get_aligned_size(rootfs_size, target)
821 group_size = hal_aligned_size + rootfs_aligned_size
822 super_size = metadata_aligned_size + 2 * group_size
824 argv = ["lpmake", "-F",
826 f"--device-size={super_size}",
827 f"--metadata-size={metadata_size}",
828 f"--metadata-slots={metadata_slots}",
829 "-g", f"tizen_a:{group_size}",
830 "-p", f"rootfs_a:none:{rootfs_aligned_size}:tizen_a",
831 "-p", f"hal_a:none:{hal_aligned_size}:tizen_a",
832 "-g", f"tizen_b:{group_size}",
833 "-p", f"rootfs_b:none:{rootfs_aligned_size}:tizen_b",
834 "-p", f"hal_b:none:{hal_aligned_size}:tizen_b",
835 "-i", f"rootfs_a={rootfs_path}",
836 "-i", f"rootfs_b={rootfs_path}",
837 "-i", f"hal_a={hal_path}",
838 "-i", f"hal_b={hal_path}"]
839 logging.debug(" ".join(argv))
840 proc = subprocess.run(argv,
841 stdin=subprocess.DEVNULL,
842 stdout=None, stderr=None)
844 if proc.returncode != 0:
845 logging.error("Failed to create super.img")
846 do_fuse_image(super_path, target)
848 def do_fuse_image_tarball(tarball, tmpd, target):
849 with tarfile.open(tarball) as tf:
851 if target.with_super:
852 if entry.name in('hal.img', 'rootfs.img'):
853 tf.extract(entry, path=tmpd)
855 f = tf.extractfile(entry)
856 do_fuse_file(f, entry.name, target)
858 def do_fuse_image(img, target):
859 with open(img, 'rb') as f:
860 do_fuse_file(f, os.path.basename(img), target)
862 def fuse_image(args, target):
865 if args.binaries is None or len(args.binaries) == 0:
868 if not Yes and not Format:
869 print(f"The following images will be written to {args.device} and the "
870 "existing data will be lost.\n")
871 for b in args.binaries:
873 response = input("\nContinue? [y/N] ")
874 if not response.lower() in ('y', 'yes'):
877 with tempfile.TemporaryDirectory() as tmpd:
878 for b in args.binaries:
879 if re.search('\.(tar|tar\.gz|tgz)$', b):
880 do_fuse_image_tarball(b, tmpd, target)
882 fn = os.path.split(b)[-1]
883 if target.with_super and fn in ('rootfs.img', 'hal.img'):
884 shutil.copy(b, os.path.join(tmpd, fn))
886 do_fuse_image(b, target)
888 if target.with_super:
889 do_fuse_image_super(tmpd, target)
891 def logger_notice(self, msg, *args, **kws):
892 if self.isEnabledFor(LOGGING_NOTICE):
893 self._log(LOGGING_NOTICE, msg, args, **kws)
894 logging.Logger.notice = logger_notice
896 def logging_notice(msg, *args, **kws):
897 if len(logging.root.handlers) == 0:
899 logging.root.notice(msg, *args, **kws)
900 logging.notice = logging_notice
903 if __name__ == '__main__':
904 parser = argparse.ArgumentParser(description="For {}, version {}".format(
905 ", ".join([v.long_name for k,v in TARGETS.items()]),
908 parser.add_argument("-b", "--binary", action="extend", dest="binaries",
910 help="binary to flash, may be used multiple times")
911 parser.add_argument("--create", action="store_true",
912 help="create the backing file and format the loopback device")
913 parser.add_argument("--debug", action="store_const", const="debug",
914 default="notice", dest="log_level",
915 help="set log level to DEBUG")
916 parser.add_argument("-d", "--device",
917 help="device node or loopback backing file")
918 parser.add_argument("--format", action="store_true",
919 help="create new partition table on the target device")
920 parser.add_argument("--log-level", dest="log_level", default="notice",
921 help="Verbosity, possible values: debug, info, notice, warning, "
922 "error, critical (default: notice)")
923 parser.add_argument("--partition-size", type=str, action="extend", dest="partition_sizes",
925 help="override default partition size (in MiB) (used with --format), "
926 "may be used multiple times, for example: --partition-size hal_a=256")
927 parser.add_argument("--size", type=int, default=8192,
928 help="size of the backing file to create (in MiB)")
929 parser.add_argument("-t", "--target", required=True,
930 help="Target device model. Use `--target list`"
931 " to show supported devices.")
932 parser.add_argument("--update", choices=['a', 'b'], default=None,
933 help="Choose partition set to update: a or b.")
934 parser.add_argument("--version", action="version",
935 version=f"%(prog)s {__version__}")
936 parser.add_argument("--YES", action="store_true",
937 help="agree to destroy data on the DEVICE")
938 args = parser.parse_args()
940 if args.target == 'list':
941 print("\nSupported devices:\n")
942 for k,v in TARGETS.items():
943 print(f" {k:6} {v.long_name}")
946 if args.device is None:
947 parser.error('-d/--device argument is required for normal operation')
949 if args.partition_sizes is not None:
951 for eqstr in args.partition_sizes:
952 ptstr = eqstr.split('=')
956 partition_sizes[name] = size
958 parser.error('--partition-size must follow the name=size pattern')
959 args.partition_sizes = partition_sizes
961 logging.addLevelName(LOGGING_NOTICE, "NOTICE")
962 conh = ColorStreamHandler(format='%(asctime)s.%(msecs)d %(debuginfo)s%(levelname)-8s %(message)s',
963 cformat='%(asctime)s.%(msecs)d %(debuginfo)s%(levelcolor)s%(message)s',
964 datefmt='%Y-%m-%dT%H:%M:%S')
965 log_handlers = [conh]
966 logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s',
967 handlers=log_handlers,
968 level=args.log_level.upper())
970 logging.debug(" ".join(sys.argv))
974 target = TARGETS[args.target](Device, args)
976 check_partition_format(args, target)
977 fuse_image(args, target)
978 subprocess.run(['sync'],
979 stdin=subprocess.DEVNULL,
980 stdout=None, stderr=None )