summaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
authorEd Bartosh <ed.bartosh@linux.intel.com>2017-08-25 23:12:27 +0300
committerRichard Purdie <richard.purdie@linuxfoundation.org>2017-08-27 22:30:07 +0100
commitcdef76e42414c092667761f8d9476e0d29828e75 (patch)
treed8ce5b0f0ab12fde54b5373c217a54d7ea63bfb3 /scripts
parentcee58f1d411e3321182f18bf2230aa5882178b1f (diff)
downloadpoky-cdef76e42414c092667761f8d9476e0d29828e75.tar.gz
wic: implement 'wic write' command
This command writes image to the media or another file with the possibility to expand partitions to fill free target space. [YOCTO #11278] (From OE-Core rev: ac5fc0d691aad66ac01a5cde34c331c928e9e25a) Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'scripts')
-rw-r--r--scripts/lib/wic/engine.py146
-rw-r--r--scripts/lib/wic/help.py40
-rwxr-xr-xscripts/wic55
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 @@
31import logging 31import logging
32import os 32import os
33import tempfile 33import tempfile
34import json
35import subprocess
34 36
35from collections import namedtuple, OrderedDict 37from collections import namedtuple, OrderedDict
36from distutils.spawn import find_executable 38from 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
343def wic_ls(args, native_sysroot): 482def 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
512def 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
373def find_canned(scripts_path, file_name): 519def 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
471wic_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
483wic_write_help = """
484
485NAME
486 wic write - write wic image to a device
487
488SYNOPSIS
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
494DESCRIPTION
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
471wic_plugins_help = """ 511wic_plugins_help = """
472 512
473NAME 513NAME
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
260def 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
260def wic_help_subcommand(args, usage_str): 267def 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
410def 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
441def 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
400def wic_init_parser_help(subparser): 451def 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,