diff options
-rw-r--r-- | scripts/lib/wic/engine.py | 146 | ||||
-rw-r--r-- | scripts/lib/wic/help.py | 40 | ||||
-rwxr-xr-x | scripts/wic | 55 |
3 files changed, 241 insertions, 0 deletions
diff --git a/scripts/lib/wic/engine.py b/scripts/lib/wic/engine.py index 4ffb08d1c8..303f323b83 100644 --- a/scripts/lib/wic/engine.py +++ b/scripts/lib/wic/engine.py | |||
@@ -31,6 +31,8 @@ | |||
31 | import logging | 31 | import logging |
32 | import os | 32 | import os |
33 | import tempfile | 33 | import tempfile |
34 | import json | ||
35 | import subprocess | ||
34 | 36 | ||
35 | from collections import namedtuple, OrderedDict | 37 | from collections import namedtuple, OrderedDict |
36 | from distutils.spawn import find_executable | 38 | from distutils.spawn import find_executable |
@@ -340,6 +342,143 @@ class Disk: | |||
340 | raise err | 342 | raise err |
341 | self._put_part_image(pnum) | 343 | self._put_part_image(pnum) |
342 | 344 | ||
345 | def write(self, target, expand): | ||
346 | """Write disk image to the media or file.""" | ||
347 | def write_sfdisk_script(outf, parts): | ||
348 | for key, val in parts['partitiontable'].items(): | ||
349 | if key in ("partitions", "device", "firstlba", "lastlba"): | ||
350 | continue | ||
351 | if key == "id": | ||
352 | key = "label-id" | ||
353 | outf.write("{}: {}\n".format(key, val)) | ||
354 | outf.write("\n") | ||
355 | for part in parts['partitiontable']['partitions']: | ||
356 | line = '' | ||
357 | for name in ('attrs', 'name', 'size', 'type', 'uuid'): | ||
358 | if name == 'size' and part['type'] == 'f': | ||
359 | # don't write size for extended partition | ||
360 | continue | ||
361 | val = part.get(name) | ||
362 | if val: | ||
363 | line += '{}={}, '.format(name, val) | ||
364 | if line: | ||
365 | line = line[:-2] # strip ', ' | ||
366 | if part.get('bootable'): | ||
367 | line += ' ,bootable' | ||
368 | outf.write("{}\n".format(line)) | ||
369 | outf.flush() | ||
370 | |||
371 | def read_ptable(path): | ||
372 | out = exec_cmd("{} -dJ {}".format(self.sfdisk, path)) | ||
373 | return json.loads(out) | ||
374 | |||
375 | def write_ptable(parts, target): | ||
376 | with tempfile.NamedTemporaryFile(prefix="wic-sfdisk-", mode='w') as outf: | ||
377 | write_sfdisk_script(outf, parts) | ||
378 | cmd = "{} --no-reread {} < {} 2>/dev/null".format(self.sfdisk, target, outf.name) | ||
379 | try: | ||
380 | subprocess.check_output(cmd, shell=True) | ||
381 | except subprocess.CalledProcessError as err: | ||
382 | raise WicError("Can't run '{}' command: {}".format(cmd, err)) | ||
383 | |||
384 | if expand is None: | ||
385 | sparse_copy(self.imagepath, target) | ||
386 | else: | ||
387 | # copy first sectors that may contain bootloader | ||
388 | sparse_copy(self.imagepath, target, length=2048 * self._lsector_size) | ||
389 | |||
390 | # copy source partition table to the target | ||
391 | parts = read_ptable(self.imagepath) | ||
392 | write_ptable(parts, target) | ||
393 | |||
394 | # get size of unpartitioned space | ||
395 | free = None | ||
396 | for line in exec_cmd("{} -F {}".format(self.sfdisk, target)).splitlines(): | ||
397 | if line.startswith("Unpartitioned space ") and line.endswith("sectors"): | ||
398 | free = int(line.split()[-2]) | ||
399 | if free is None: | ||
400 | raise WicError("Can't get size of unpartitioned space") | ||
401 | |||
402 | # calculate expanded partitions sizes | ||
403 | sizes = {} | ||
404 | for num, part in enumerate(parts['partitiontable']['partitions'], 1): | ||
405 | if num in expand: | ||
406 | if expand[num] != 0: # don't resize partition if size is set to 0 | ||
407 | sectors = expand[num] // self._lsector_size | ||
408 | free -= sectors - part['size'] | ||
409 | part['size'] = sectors | ||
410 | sizes[num] = sectors | ||
411 | elif part['type'] != 'f': | ||
412 | sizes[num] = -1 | ||
413 | |||
414 | for num, part in enumerate(parts['partitiontable']['partitions'], 1): | ||
415 | if sizes.get(num) == -1: | ||
416 | part['size'] += free // len(sizes) | ||
417 | |||
418 | # write resized partition table to the target | ||
419 | write_ptable(parts, target) | ||
420 | |||
421 | # read resized partition table | ||
422 | parts = read_ptable(target) | ||
423 | |||
424 | # copy partitions content | ||
425 | for num, part in enumerate(parts['partitiontable']['partitions'], 1): | ||
426 | pnum = str(num) | ||
427 | fstype = self.partitions[pnum].fstype | ||
428 | |||
429 | # copy unchanged partition | ||
430 | if part['size'] == self.partitions[pnum].size // self._lsector_size: | ||
431 | logger.info("copying unchanged partition {}".format(pnum)) | ||
432 | sparse_copy(self._get_part_image(pnum), target, seek=part['start'] * self._lsector_size) | ||
433 | continue | ||
434 | |||
435 | # resize or re-create partitions | ||
436 | if fstype.startswith('ext') or fstype.startswith('fat') or \ | ||
437 | fstype.startswith('linux-swap'): | ||
438 | |||
439 | partfname = None | ||
440 | with tempfile.NamedTemporaryFile(prefix="wic-part{}-".format(pnum)) as partf: | ||
441 | partfname = partf.name | ||
442 | |||
443 | if fstype.startswith('ext'): | ||
444 | logger.info("resizing ext partition {}".format(pnum)) | ||
445 | partimg = self._get_part_image(pnum) | ||
446 | sparse_copy(partimg, partfname) | ||
447 | exec_cmd("{} -pf {}".format(self.e2fsck, partfname)) | ||
448 | exec_cmd("{} {} {}s".format(\ | ||
449 | self.resize2fs, partfname, part['size'])) | ||
450 | elif fstype.startswith('fat'): | ||
451 | logger.info("copying content of the fat partition {}".format(pnum)) | ||
452 | with tempfile.TemporaryDirectory(prefix='wic-fatdir-') as tmpdir: | ||
453 | # copy content to the temporary directory | ||
454 | cmd = "{} -snompi {} :: {}".format(self.mcopy, | ||
455 | self._get_part_image(pnum), | ||
456 | tmpdir) | ||
457 | exec_cmd(cmd) | ||
458 | # create new msdos partition | ||
459 | label = part.get("name") | ||
460 | label_str = "-n {}".format(label) if label else '' | ||
461 | |||
462 | cmd = "{} {} -C {} {}".format(self.mkdosfs, label_str, partfname, | ||
463 | part['size']) | ||
464 | exec_cmd(cmd) | ||
465 | # copy content from the temporary directory to the new partition | ||
466 | cmd = "{} -snompi {} {}/* ::".format(self.mcopy, partfname, tmpdir) | ||
467 | exec_cmd(cmd, as_shell=True) | ||
468 | elif fstype.startswith('linux-swap'): | ||
469 | logger.info("creating swap partition {}".format(pnum)) | ||
470 | label = part.get("name") | ||
471 | label_str = "-L {}".format(label) if label else '' | ||
472 | uuid = part.get("uuid") | ||
473 | uuid_str = "-U {}".format(uuid) if uuid else '' | ||
474 | with open(partfname, 'w') as sparse: | ||
475 | os.ftruncate(sparse.fileno(), part['size'] * self._lsector_size) | ||
476 | exec_cmd("{} {} {} {}".format(self.mkswap, label_str, uuid_str, partfname)) | ||
477 | sparse_copy(partfname, target, seek=part['start'] * self._lsector_size) | ||
478 | os.unlink(partfname) | ||
479 | elif part['type'] != 'f': | ||
480 | logger.warn("skipping partition {}: unsupported fstype {}".format(pnum, fstype)) | ||
481 | |||
343 | def wic_ls(args, native_sysroot): | 482 | def wic_ls(args, native_sysroot): |
344 | """List contents of partitioned image or vfat partition.""" | 483 | """List contents of partitioned image or vfat partition.""" |
345 | disk = Disk(args.path.image, native_sysroot) | 484 | disk = Disk(args.path.image, native_sysroot) |
@@ -370,6 +509,13 @@ def wic_rm(args, native_sysroot): | |||
370 | disk = Disk(args.path.image, native_sysroot) | 509 | disk = Disk(args.path.image, native_sysroot) |
371 | disk.remove(args.path.part, args.path.path) | 510 | disk.remove(args.path.part, args.path.path) |
372 | 511 | ||
512 | def wic_write(args, native_sysroot): | ||
513 | """ | ||
514 | Write image to a target device. | ||
515 | """ | ||
516 | disk = Disk(args.image, native_sysroot, ('fat', 'ext', 'swap')) | ||
517 | disk.write(args.target, args.expand) | ||
518 | |||
373 | def find_canned(scripts_path, file_name): | 519 | def find_canned(scripts_path, file_name): |
374 | """ | 520 | """ |
375 | Find a file either by its path or by name in the canned files dir. | 521 | Find a file either by its path or by name in the canned files dir. |
diff --git a/scripts/lib/wic/help.py b/scripts/lib/wic/help.py index 99912cd400..ccd3382324 100644 --- a/scripts/lib/wic/help.py +++ b/scripts/lib/wic/help.py | |||
@@ -468,6 +468,46 @@ DESCRIPTION | |||
468 | containing the tools(parted and mtools) to use. | 468 | containing the tools(parted and mtools) to use. |
469 | """ | 469 | """ |
470 | 470 | ||
471 | wic_write_usage = """ | ||
472 | |||
473 | Write image to a device | ||
474 | |||
475 | usage: wic write <image> <target device> [--expand [rules]] [--native-sysroot <path>] | ||
476 | |||
477 | This command writes wic image to a target device (USB stick, SD card etc). | ||
478 | |||
479 | See 'wic help write' for more detailed instructions. | ||
480 | |||
481 | """ | ||
482 | |||
483 | wic_write_help = """ | ||
484 | |||
485 | NAME | ||
486 | wic write - write wic image to a device | ||
487 | |||
488 | SYNOPSIS | ||
489 | wic write <image> <target> | ||
490 | wic write <image> <target> --expand auto | ||
491 | wic write <image> <target> --expand 1:100M-2:300M | ||
492 | wic write <image> <target> --native-sysroot <path> | ||
493 | |||
494 | DESCRIPTION | ||
495 | This command writes wic image to a target device (USB stick, SD card etc) | ||
496 | |||
497 | $ wic write ./tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.wic /dev/sdb | ||
498 | |||
499 | The --expand option is used to resize image partitions. | ||
500 | --expand auto expands partitions to occupy all free space available on the target device. | ||
501 | It's also possible to specify expansion rules in a format | ||
502 | <partition>:<size>[-<partition>:<size>...] for one or more partitions. | ||
503 | Specifying size 0 will keep partition unmodified. | ||
504 | Note: Resizing boot partition can result in non-bootable image for non-EFI images. It is | ||
505 | recommended to use size 0 for boot partition to keep image bootable. | ||
506 | |||
507 | The --native-sysroot option is used to specify the path to the native sysroot | ||
508 | containing the tools(parted, resize2fs) to use. | ||
509 | """ | ||
510 | |||
471 | wic_plugins_help = """ | 511 | wic_plugins_help = """ |
472 | 512 | ||
473 | NAME | 513 | NAME |
diff --git a/scripts/wic b/scripts/wic index 02bc82ce42..592a0e4c25 100755 --- a/scripts/wic +++ b/scripts/wic | |||
@@ -257,6 +257,13 @@ def wic_rm_subcommand(args, usage_str): | |||
257 | """ | 257 | """ |
258 | engine.wic_rm(args, args.native_sysroot) | 258 | engine.wic_rm(args, args.native_sysroot) |
259 | 259 | ||
260 | def wic_write_subcommand(args, usage_str): | ||
261 | """ | ||
262 | Command-line handling for writing images. | ||
263 | The real work is done by engine.wic_write() | ||
264 | """ | ||
265 | engine.wic_write(args, args.native_sysroot) | ||
266 | |||
260 | def wic_help_subcommand(args, usage_str): | 267 | def wic_help_subcommand(args, usage_str): |
261 | """ | 268 | """ |
262 | Command-line handling for help subcommand to keep the current | 269 | Command-line handling for help subcommand to keep the current |
@@ -298,6 +305,9 @@ helptopics = { | |||
298 | "rm": [wic_help_topic_subcommand, | 305 | "rm": [wic_help_topic_subcommand, |
299 | wic_help_topic_usage, | 306 | wic_help_topic_usage, |
300 | hlp.wic_rm_help], | 307 | hlp.wic_rm_help], |
308 | "write": [wic_help_topic_subcommand, | ||
309 | wic_help_topic_usage, | ||
310 | hlp.wic_write_help], | ||
301 | "list": [wic_help_topic_subcommand, | 311 | "list": [wic_help_topic_subcommand, |
302 | wic_help_topic_usage, | 312 | wic_help_topic_usage, |
303 | hlp.wic_list_help] | 313 | hlp.wic_list_help] |
@@ -397,6 +407,47 @@ def wic_init_parser_rm(subparser): | |||
397 | subparser.add_argument("-n", "--native-sysroot", | 407 | subparser.add_argument("-n", "--native-sysroot", |
398 | help="path to the native sysroot containing the tools") | 408 | help="path to the native sysroot containing the tools") |
399 | 409 | ||
410 | def expandtype(rules): | ||
411 | """ | ||
412 | Custom type for ArgumentParser | ||
413 | Converts expand rules to the dictionary {<partition>: size} | ||
414 | """ | ||
415 | if rules == 'auto': | ||
416 | return {} | ||
417 | result = {} | ||
418 | for rule in rules.split('-'): | ||
419 | try: | ||
420 | part, size = rule.split(':') | ||
421 | except ValueError: | ||
422 | raise argparse.ArgumentTypeError("Incorrect rule format: %s" % rule) | ||
423 | |||
424 | if not part.isdigit(): | ||
425 | raise argparse.ArgumentTypeError("Rule '%s': partition number must be integer" % rule) | ||
426 | |||
427 | # validate size | ||
428 | multiplier = 1 | ||
429 | for suffix, mult in [('K', 1024), ('M', 1024 * 1024), ('G', 1024 * 1024 * 1024)]: | ||
430 | if size.upper().endswith(suffix): | ||
431 | multiplier = mult | ||
432 | size = size[:-1] | ||
433 | break | ||
434 | if not size.isdigit(): | ||
435 | raise argparse.ArgumentTypeError("Rule '%s': size must be integer" % rule) | ||
436 | |||
437 | result[int(part)] = int(size) * multiplier | ||
438 | |||
439 | return result | ||
440 | |||
441 | def wic_init_parser_write(subparser): | ||
442 | subparser.add_argument("image", | ||
443 | help="path to the wic image") | ||
444 | subparser.add_argument("target", | ||
445 | help="target file or device") | ||
446 | subparser.add_argument("-e", "--expand", type=expandtype, | ||
447 | help="expand rules: auto or <partition>:<size>[,<partition>:<size>]") | ||
448 | subparser.add_argument("-n", "--native-sysroot", | ||
449 | help="path to the native sysroot containing the tools") | ||
450 | |||
400 | def wic_init_parser_help(subparser): | 451 | def wic_init_parser_help(subparser): |
401 | helpparsers = subparser.add_subparsers(dest='help_topic', help=hlp.wic_usage) | 452 | helpparsers = subparser.add_subparsers(dest='help_topic', help=hlp.wic_usage) |
402 | for helptopic in helptopics: | 453 | for helptopic in helptopics: |
@@ -425,6 +476,10 @@ subcommands = { | |||
425 | hlp.wic_rm_usage, | 476 | hlp.wic_rm_usage, |
426 | hlp.wic_rm_help, | 477 | hlp.wic_rm_help, |
427 | wic_init_parser_rm], | 478 | wic_init_parser_rm], |
479 | "write": [wic_write_subcommand, | ||
480 | hlp.wic_write_usage, | ||
481 | hlp.wic_write_help, | ||
482 | wic_init_parser_write], | ||
428 | "help": [wic_help_subcommand, | 483 | "help": [wic_help_subcommand, |
429 | wic_help_topic_usage, | 484 | wic_help_topic_usage, |
430 | hlp.wic_help_help, | 485 | hlp.wic_help_help, |