1*4882a593Smuzhiyun#!/usr/bin/env python3 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# Copyright (c) 2016 Intel, Inc. 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun# DESCRIPTION 8*4882a593Smuzhiyun# This module provides parser for kickstart format 9*4882a593Smuzhiyun# 10*4882a593Smuzhiyun# AUTHORS 11*4882a593Smuzhiyun# Ed Bartosh <ed.bartosh> (at] linux.intel.com> 12*4882a593Smuzhiyun 13*4882a593Smuzhiyun"""Kickstart parser module.""" 14*4882a593Smuzhiyun 15*4882a593Smuzhiyunimport os 16*4882a593Smuzhiyunimport shlex 17*4882a593Smuzhiyunimport logging 18*4882a593Smuzhiyunimport re 19*4882a593Smuzhiyun 20*4882a593Smuzhiyunfrom argparse import ArgumentParser, ArgumentError, ArgumentTypeError 21*4882a593Smuzhiyun 22*4882a593Smuzhiyunfrom wic.engine import find_canned 23*4882a593Smuzhiyunfrom wic.partition import Partition 24*4882a593Smuzhiyunfrom wic.misc import get_bitbake_var 25*4882a593Smuzhiyun 26*4882a593Smuzhiyunlogger = logging.getLogger('wic') 27*4882a593Smuzhiyun 28*4882a593Smuzhiyun__expand_var_regexp__ = re.compile(r"\${[^{}@\n\t :]+}") 29*4882a593Smuzhiyun 30*4882a593Smuzhiyundef expand_line(line): 31*4882a593Smuzhiyun while True: 32*4882a593Smuzhiyun m = __expand_var_regexp__.search(line) 33*4882a593Smuzhiyun if not m: 34*4882a593Smuzhiyun return line 35*4882a593Smuzhiyun key = m.group()[2:-1] 36*4882a593Smuzhiyun val = get_bitbake_var(key) 37*4882a593Smuzhiyun if val is None: 38*4882a593Smuzhiyun logger.warning("cannot expand variable %s" % key) 39*4882a593Smuzhiyun return line 40*4882a593Smuzhiyun line = line[:m.start()] + val + line[m.end():] 41*4882a593Smuzhiyun 42*4882a593Smuzhiyunclass KickStartError(Exception): 43*4882a593Smuzhiyun """Custom exception.""" 44*4882a593Smuzhiyun pass 45*4882a593Smuzhiyun 46*4882a593Smuzhiyunclass KickStartParser(ArgumentParser): 47*4882a593Smuzhiyun """ 48*4882a593Smuzhiyun This class overwrites error method to throw exception 49*4882a593Smuzhiyun instead of producing usage message(default argparse behavior). 50*4882a593Smuzhiyun """ 51*4882a593Smuzhiyun def error(self, message): 52*4882a593Smuzhiyun raise ArgumentError(None, message) 53*4882a593Smuzhiyun 54*4882a593Smuzhiyundef sizetype(default, size_in_bytes=False): 55*4882a593Smuzhiyun def f(arg): 56*4882a593Smuzhiyun """ 57*4882a593Smuzhiyun Custom type for ArgumentParser 58*4882a593Smuzhiyun Converts size string in <num>[S|s|K|k|M|G] format into the integer value 59*4882a593Smuzhiyun """ 60*4882a593Smuzhiyun try: 61*4882a593Smuzhiyun suffix = default 62*4882a593Smuzhiyun size = int(arg) 63*4882a593Smuzhiyun except ValueError: 64*4882a593Smuzhiyun try: 65*4882a593Smuzhiyun suffix = arg[-1:] 66*4882a593Smuzhiyun size = int(arg[:-1]) 67*4882a593Smuzhiyun except ValueError: 68*4882a593Smuzhiyun raise ArgumentTypeError("Invalid size: %r" % arg) 69*4882a593Smuzhiyun 70*4882a593Smuzhiyun 71*4882a593Smuzhiyun if size_in_bytes: 72*4882a593Smuzhiyun if suffix == 's' or suffix == 'S': 73*4882a593Smuzhiyun return size * 512 74*4882a593Smuzhiyun mult = 1024 75*4882a593Smuzhiyun else: 76*4882a593Smuzhiyun mult = 1 77*4882a593Smuzhiyun 78*4882a593Smuzhiyun if suffix == "k" or suffix == "K": 79*4882a593Smuzhiyun return size * mult 80*4882a593Smuzhiyun if suffix == "M": 81*4882a593Smuzhiyun return size * mult * 1024 82*4882a593Smuzhiyun if suffix == "G": 83*4882a593Smuzhiyun return size * mult * 1024 * 1024 84*4882a593Smuzhiyun 85*4882a593Smuzhiyun raise ArgumentTypeError("Invalid size: %r" % arg) 86*4882a593Smuzhiyun return f 87*4882a593Smuzhiyun 88*4882a593Smuzhiyundef overheadtype(arg): 89*4882a593Smuzhiyun """ 90*4882a593Smuzhiyun Custom type for ArgumentParser 91*4882a593Smuzhiyun Converts overhead string to float and checks if it's bigger than 1.0 92*4882a593Smuzhiyun """ 93*4882a593Smuzhiyun try: 94*4882a593Smuzhiyun result = float(arg) 95*4882a593Smuzhiyun except ValueError: 96*4882a593Smuzhiyun raise ArgumentTypeError("Invalid value: %r" % arg) 97*4882a593Smuzhiyun 98*4882a593Smuzhiyun if result < 1.0: 99*4882a593Smuzhiyun raise ArgumentTypeError("Overhead factor should be > 1.0" % arg) 100*4882a593Smuzhiyun 101*4882a593Smuzhiyun return result 102*4882a593Smuzhiyun 103*4882a593Smuzhiyundef cannedpathtype(arg): 104*4882a593Smuzhiyun """ 105*4882a593Smuzhiyun Custom type for ArgumentParser 106*4882a593Smuzhiyun Tries to find file in the list of canned wks paths 107*4882a593Smuzhiyun """ 108*4882a593Smuzhiyun scripts_path = os.path.abspath(os.path.dirname(__file__) + '../../..') 109*4882a593Smuzhiyun result = find_canned(scripts_path, arg) 110*4882a593Smuzhiyun if not result: 111*4882a593Smuzhiyun raise ArgumentTypeError("file not found: %s" % arg) 112*4882a593Smuzhiyun return result 113*4882a593Smuzhiyun 114*4882a593Smuzhiyundef systemidtype(arg): 115*4882a593Smuzhiyun """ 116*4882a593Smuzhiyun Custom type for ArgumentParser 117*4882a593Smuzhiyun Checks if the argument sutisfies system id requirements, 118*4882a593Smuzhiyun i.e. if it's one byte long integer > 0 119*4882a593Smuzhiyun """ 120*4882a593Smuzhiyun error = "Invalid system type: %s. must be hex "\ 121*4882a593Smuzhiyun "between 0x1 and 0xFF" % arg 122*4882a593Smuzhiyun try: 123*4882a593Smuzhiyun result = int(arg, 16) 124*4882a593Smuzhiyun except ValueError: 125*4882a593Smuzhiyun raise ArgumentTypeError(error) 126*4882a593Smuzhiyun 127*4882a593Smuzhiyun if result <= 0 or result > 0xff: 128*4882a593Smuzhiyun raise ArgumentTypeError(error) 129*4882a593Smuzhiyun 130*4882a593Smuzhiyun return arg 131*4882a593Smuzhiyun 132*4882a593Smuzhiyunclass KickStart(): 133*4882a593Smuzhiyun """Kickstart parser implementation.""" 134*4882a593Smuzhiyun 135*4882a593Smuzhiyun DEFAULT_EXTRA_SPACE = 10*1024 136*4882a593Smuzhiyun DEFAULT_OVERHEAD_FACTOR = 1.3 137*4882a593Smuzhiyun 138*4882a593Smuzhiyun def __init__(self, confpath): 139*4882a593Smuzhiyun 140*4882a593Smuzhiyun self.partitions = [] 141*4882a593Smuzhiyun self.bootloader = None 142*4882a593Smuzhiyun self.lineno = 0 143*4882a593Smuzhiyun self.partnum = 0 144*4882a593Smuzhiyun 145*4882a593Smuzhiyun parser = KickStartParser() 146*4882a593Smuzhiyun subparsers = parser.add_subparsers() 147*4882a593Smuzhiyun 148*4882a593Smuzhiyun part = subparsers.add_parser('part') 149*4882a593Smuzhiyun part.add_argument('mountpoint', nargs='?') 150*4882a593Smuzhiyun part.add_argument('--active', action='store_true') 151*4882a593Smuzhiyun part.add_argument('--align', type=int) 152*4882a593Smuzhiyun part.add_argument('--offset', type=sizetype("K", True)) 153*4882a593Smuzhiyun part.add_argument('--exclude-path', nargs='+') 154*4882a593Smuzhiyun part.add_argument('--include-path', nargs='+', action='append') 155*4882a593Smuzhiyun part.add_argument('--change-directory') 156*4882a593Smuzhiyun part.add_argument("--extra-space", type=sizetype("M")) 157*4882a593Smuzhiyun part.add_argument('--fsoptions', dest='fsopts') 158*4882a593Smuzhiyun part.add_argument('--fstype', default='vfat', 159*4882a593Smuzhiyun choices=('ext2', 'ext3', 'ext4', 'btrfs', 160*4882a593Smuzhiyun 'squashfs', 'vfat', 'msdos', 'erofs', 161*4882a593Smuzhiyun 'swap')) 162*4882a593Smuzhiyun part.add_argument('--mkfs-extraopts', default='') 163*4882a593Smuzhiyun part.add_argument('--label') 164*4882a593Smuzhiyun part.add_argument('--use-label', action='store_true') 165*4882a593Smuzhiyun part.add_argument('--no-table', action='store_true') 166*4882a593Smuzhiyun part.add_argument('--ondisk', '--ondrive', dest='disk', default='sda') 167*4882a593Smuzhiyun part.add_argument("--overhead-factor", type=overheadtype) 168*4882a593Smuzhiyun part.add_argument('--part-name') 169*4882a593Smuzhiyun part.add_argument('--part-type') 170*4882a593Smuzhiyun part.add_argument('--rootfs-dir') 171*4882a593Smuzhiyun part.add_argument('--type', default='primary', 172*4882a593Smuzhiyun choices = ('primary', 'logical')) 173*4882a593Smuzhiyun 174*4882a593Smuzhiyun # --size and --fixed-size cannot be specified together; options 175*4882a593Smuzhiyun # ----extra-space and --overhead-factor should also raise a parser 176*4882a593Smuzhiyun # --error, but since nesting mutually exclusive groups does not work, 177*4882a593Smuzhiyun # ----extra-space/--overhead-factor are handled later 178*4882a593Smuzhiyun sizeexcl = part.add_mutually_exclusive_group() 179*4882a593Smuzhiyun sizeexcl.add_argument('--size', type=sizetype("M"), default=0) 180*4882a593Smuzhiyun sizeexcl.add_argument('--fixed-size', type=sizetype("M"), default=0) 181*4882a593Smuzhiyun 182*4882a593Smuzhiyun part.add_argument('--source') 183*4882a593Smuzhiyun part.add_argument('--sourceparams') 184*4882a593Smuzhiyun part.add_argument('--system-id', type=systemidtype) 185*4882a593Smuzhiyun part.add_argument('--use-uuid', action='store_true') 186*4882a593Smuzhiyun part.add_argument('--uuid') 187*4882a593Smuzhiyun part.add_argument('--fsuuid') 188*4882a593Smuzhiyun part.add_argument('--no-fstab-update', action='store_true') 189*4882a593Smuzhiyun 190*4882a593Smuzhiyun bootloader = subparsers.add_parser('bootloader') 191*4882a593Smuzhiyun bootloader.add_argument('--append') 192*4882a593Smuzhiyun bootloader.add_argument('--configfile') 193*4882a593Smuzhiyun bootloader.add_argument('--ptable', choices=('msdos', 'gpt'), 194*4882a593Smuzhiyun default='msdos') 195*4882a593Smuzhiyun bootloader.add_argument('--timeout', type=int) 196*4882a593Smuzhiyun bootloader.add_argument('--source') 197*4882a593Smuzhiyun 198*4882a593Smuzhiyun include = subparsers.add_parser('include') 199*4882a593Smuzhiyun include.add_argument('path', type=cannedpathtype) 200*4882a593Smuzhiyun 201*4882a593Smuzhiyun self._parse(parser, confpath) 202*4882a593Smuzhiyun if not self.bootloader: 203*4882a593Smuzhiyun logger.warning('bootloader config not specified, using defaults\n') 204*4882a593Smuzhiyun self.bootloader = bootloader.parse_args([]) 205*4882a593Smuzhiyun 206*4882a593Smuzhiyun def _parse(self, parser, confpath): 207*4882a593Smuzhiyun """ 208*4882a593Smuzhiyun Parse file in .wks format using provided parser. 209*4882a593Smuzhiyun """ 210*4882a593Smuzhiyun with open(confpath) as conf: 211*4882a593Smuzhiyun lineno = 0 212*4882a593Smuzhiyun for line in conf: 213*4882a593Smuzhiyun line = line.strip() 214*4882a593Smuzhiyun lineno += 1 215*4882a593Smuzhiyun if line and line[0] != '#': 216*4882a593Smuzhiyun line = expand_line(line) 217*4882a593Smuzhiyun try: 218*4882a593Smuzhiyun line_args = shlex.split(line) 219*4882a593Smuzhiyun parsed = parser.parse_args(line_args) 220*4882a593Smuzhiyun except ArgumentError as err: 221*4882a593Smuzhiyun raise KickStartError('%s:%d: %s' % \ 222*4882a593Smuzhiyun (confpath, lineno, err)) 223*4882a593Smuzhiyun if line.startswith('part'): 224*4882a593Smuzhiyun # SquashFS does not support filesystem UUID 225*4882a593Smuzhiyun if parsed.fstype == 'squashfs': 226*4882a593Smuzhiyun if parsed.fsuuid: 227*4882a593Smuzhiyun err = "%s:%d: SquashFS does not support UUID" \ 228*4882a593Smuzhiyun % (confpath, lineno) 229*4882a593Smuzhiyun raise KickStartError(err) 230*4882a593Smuzhiyun if parsed.label: 231*4882a593Smuzhiyun err = "%s:%d: SquashFS does not support LABEL" \ 232*4882a593Smuzhiyun % (confpath, lineno) 233*4882a593Smuzhiyun raise KickStartError(err) 234*4882a593Smuzhiyun # erofs does not support filesystem labels 235*4882a593Smuzhiyun if parsed.fstype == 'erofs' and parsed.label: 236*4882a593Smuzhiyun err = "%s:%d: erofs does not support LABEL" % (confpath, lineno) 237*4882a593Smuzhiyun raise KickStartError(err) 238*4882a593Smuzhiyun if parsed.fstype == 'msdos' or parsed.fstype == 'vfat': 239*4882a593Smuzhiyun if parsed.fsuuid: 240*4882a593Smuzhiyun if parsed.fsuuid.upper().startswith('0X'): 241*4882a593Smuzhiyun if len(parsed.fsuuid) > 10: 242*4882a593Smuzhiyun err = "%s:%d: fsuuid %s given in wks kickstart file " \ 243*4882a593Smuzhiyun "exceeds the length limit for %s filesystem. " \ 244*4882a593Smuzhiyun "It should be in the form of a 32 bit hexadecimal" \ 245*4882a593Smuzhiyun "number (for example, 0xABCD1234)." \ 246*4882a593Smuzhiyun % (confpath, lineno, parsed.fsuuid, parsed.fstype) 247*4882a593Smuzhiyun raise KickStartError(err) 248*4882a593Smuzhiyun elif len(parsed.fsuuid) > 8: 249*4882a593Smuzhiyun err = "%s:%d: fsuuid %s given in wks kickstart file " \ 250*4882a593Smuzhiyun "exceeds the length limit for %s filesystem. " \ 251*4882a593Smuzhiyun "It should be in the form of a 32 bit hexadecimal" \ 252*4882a593Smuzhiyun "number (for example, 0xABCD1234)." \ 253*4882a593Smuzhiyun % (confpath, lineno, parsed.fsuuid, parsed.fstype) 254*4882a593Smuzhiyun raise KickStartError(err) 255*4882a593Smuzhiyun if parsed.use_label and not parsed.label: 256*4882a593Smuzhiyun err = "%s:%d: Must set the label with --label" \ 257*4882a593Smuzhiyun % (confpath, lineno) 258*4882a593Smuzhiyun raise KickStartError(err) 259*4882a593Smuzhiyun # using ArgumentParser one cannot easily tell if option 260*4882a593Smuzhiyun # was passed as argument, if said option has a default 261*4882a593Smuzhiyun # value; --overhead-factor/--extra-space cannot be used 262*4882a593Smuzhiyun # with --fixed-size, so at least detect when these were 263*4882a593Smuzhiyun # passed with non-0 values ... 264*4882a593Smuzhiyun if parsed.fixed_size: 265*4882a593Smuzhiyun if parsed.overhead_factor or parsed.extra_space: 266*4882a593Smuzhiyun err = "%s:%d: arguments --overhead-factor and --extra-space not "\ 267*4882a593Smuzhiyun "allowed with argument --fixed-size" \ 268*4882a593Smuzhiyun % (confpath, lineno) 269*4882a593Smuzhiyun raise KickStartError(err) 270*4882a593Smuzhiyun else: 271*4882a593Smuzhiyun # ... and provide defaults if not using 272*4882a593Smuzhiyun # --fixed-size iff given option was not used 273*4882a593Smuzhiyun # (again, one cannot tell if option was passed but 274*4882a593Smuzhiyun # with value equal to 0) 275*4882a593Smuzhiyun if '--overhead-factor' not in line_args: 276*4882a593Smuzhiyun parsed.overhead_factor = self.DEFAULT_OVERHEAD_FACTOR 277*4882a593Smuzhiyun if '--extra-space' not in line_args: 278*4882a593Smuzhiyun parsed.extra_space = self.DEFAULT_EXTRA_SPACE 279*4882a593Smuzhiyun 280*4882a593Smuzhiyun self.partnum += 1 281*4882a593Smuzhiyun self.partitions.append(Partition(parsed, self.partnum)) 282*4882a593Smuzhiyun elif line.startswith('include'): 283*4882a593Smuzhiyun self._parse(parser, parsed.path) 284*4882a593Smuzhiyun elif line.startswith('bootloader'): 285*4882a593Smuzhiyun if not self.bootloader: 286*4882a593Smuzhiyun self.bootloader = parsed 287*4882a593Smuzhiyun # Concatenate the strings set in APPEND 288*4882a593Smuzhiyun append_var = get_bitbake_var("APPEND") 289*4882a593Smuzhiyun if append_var: 290*4882a593Smuzhiyun self.bootloader.append = ' '.join(filter(None, \ 291*4882a593Smuzhiyun (self.bootloader.append, append_var))) 292*4882a593Smuzhiyun else: 293*4882a593Smuzhiyun err = "%s:%d: more than one bootloader specified" \ 294*4882a593Smuzhiyun % (confpath, lineno) 295*4882a593Smuzhiyun raise KickStartError(err) 296