# SPDX-License-Identifier: GPL-2.0+
# Copyright 2022 Google LLC
#
"""Bintool implementation for cbfstool

cfstool provides a number of features useful with Coreboot Filesystem binaries.

Documentation is at https://www.coreboot.org/CBFS

Source code is at https://github.com/coreboot/coreboot/blob/master/util/cbfstool/cbfstool.c

Here is the help:

cbfstool: Management utility for CBFS formatted ROM images

USAGE:
 cbfstool [-h]
 cbfstool FILE COMMAND [-v] [PARAMETERS]...

OPTIONs:
  -H header_offset Do not search for header; use this offset*
  -T               Output top-aligned memory address
  -u               Accept short data; fill upward/from bottom
  -d               Accept short data; fill downward/from top
  -F               Force action
  -g               Generate position and alignment arguments
  -U               Unprocessed; don't decompress or make ELF
  -v               Provide verbose output
  -h               Display this help message

COMMANDs:
 add [-r image,regions] -f FILE -n NAME -t TYPE [-A hash] \
        [-c compression] [-b base-address | -a alignment] \
        [-p padding size] [-y|--xip if TYPE is FSP]       \
        [-j topswap-size] (Intel CPUs only) [--ibb]
        Add a component
        -j valid size: 0x10000 0x20000 0x40000 0x80000 0x100000
 add-payload [-r image,regions] -f FILE -n NAME [-A hash] \
        [-c compression] [-b base-address] \
        (linux specific: [-C cmdline] [-I initrd])
        Add a payload to the ROM
 add-stage [-r image,regions] -f FILE -n NAME [-A hash] \
        [-c compression] [-b base] [-S section-to-ignore] \
        [-a alignment] [-y|--xip] [-P page-size] [--ibb]
        Add a stage to the ROM
 add-flat-binary [-r image,regions] -f FILE -n NAME \
        [-A hash] -l load-address -e entry-point \
        [-c compression] [-b base]
        Add a 32bit flat mode binary
 add-int [-r image,regions] -i INTEGER -n NAME [-b base]
 Add a raw 64-bit integer value
 add-master-header [-r image,regions] \
        [-j topswap-size] (Intel CPUs only)
        Add a legacy CBFS master header
 remove [-r image,regions] -n NAME
 Remove a component
 compact -r image,regions
 Defragment CBFS image.
 copy -r image,regions -R source-region
 Create a copy (duplicate) cbfs instance in fmap
 create -m ARCH -s size [-b bootblock offset] \
        [-o CBFS offset] [-H header offset] [-B bootblock]
        Create a legacy ROM file with CBFS master header*
 create -M flashmap [-r list,of,regions,containing,cbfses]
 Create a new-style partitioned firmware image
 locate [-r image,regions] -f FILE -n NAME [-P page-size] \
        [-a align] [-T]
        Find a place for a file of that size
 layout [-w]
 List mutable (or, with -w, readable) image regions
 print [-r image,regions]
 Show the contents of the ROM
 extract [-r image,regions] [-m ARCH] -n NAME -f FILE [-U]
 Extracts a file from ROM
 write [-F] -r image,regions -f file [-u | -d] [-i int]
 Write file into same-size [or larger] raw region
 read [-r fmap-region] -f file
 Extract raw region contents into binary file
 truncate [-r fmap-region]
 Truncate CBFS and print new size on stdout
 expand [-r fmap-region]
 Expand CBFS to span entire region
OFFSETs:
  Numbers accompanying -b, -H, and -o switches* may be provided
  in two possible formats: if their value is greater than
  0x80000000, they are interpreted as a top-aligned x86 memory
  address; otherwise, they are treated as an offset into flash.
ARCHes:
  arm64, arm, mips, ppc64, power8, riscv, x86, unknown
TYPEs:
 bootblock, cbfs header, stage, simple elf, fit, optionrom, bootsplash, raw,
 vsa, mbi, microcode, fsp, mrc, cmos_default, cmos_layout, spd,
 mrc_cache, mma, efi, struct, deleted, null

* Note that these actions and switches are only valid when
  working with legacy images whose structure is described
  primarily by a CBFS master header. New-style images, in
  contrast, exclusively make use of an FMAP to describe their
  layout: this must minimally contain an 'FMAP' section
  specifying the location of this FMAP itself and a 'COREBOOT'
  section describing the primary CBFS. It should also be noted
  that, when working with such images, the -F and -r switches
  default to 'COREBOOT' for convenience, and both the -b switch to
  CBFS operations and the output of the locate action become
  relative to the selected CBFS region's lowest address.
  The one exception to this rule is the top-aligned address,
  which is always relative to the end of the entire image
  rather than relative to the local region; this is true for
  for both input (sufficiently large) and output (-T) data.


Since binman has a native implementation of CBFS (see cbfs_util.py), we don't
actually need this tool, except for sanity checks in the tests.
"""

from binman import bintool

class Bintoolcbfstool(bintool.Bintool):
    """Coreboot filesystem (CBFS) tool

    This bintool supports creating new CBFS images and adding files to an
    existing image, i.e. the features needed by binman.

    It also supports fetching a binary cbfstool, since building it from source
    is fairly slow.

    Documentation about CBFS is at https://www.coreboot.org/CBFS
    """
    def __init__(self, name):
        super().__init__(name, 'Manipulate CBFS files')

    def create_new(self, cbfs_fname, size, arch='x86'):
        """Create a new CBFS

        Args:
            cbfs_fname (str): Filename of CBFS to create
            size (int): Size of CBFS in bytes
            arch (str): Architecture for which this CBFS is intended

        Returns:
            str: Tool output
        """
        args = [cbfs_fname, 'create', '-s', f'{size:#x}', '-m', arch]
        return self.run_cmd(*args)

    # pylint: disable=R0913
    def add_raw(self, cbfs_fname, name, fname, compress=None, base=None):
        """Add a raw file to the CBFS

        Args:
            cbfs_fname (str): Filename of CBFS to create
            name (str): Name to use inside the CBFS
            fname (str): Filename of file to add
            compress (str): Compression to use (cbfs_util.COMPRESS_NAMES) or
                None for None
            base (int): Address to place the file, or None for anywhere

        Returns:
            str: Tool output
        """
        args = [cbfs_fname,
                'add',
                '-n', name,
                '-t', 'raw',
                '-f', fname,
                '-c', compress or 'none']
        if base:
            args += ['-b', f'{base:#x}']
        return self.run_cmd(*args)

    def add_stage(self, cbfs_fname, name, fname):
        """Add a stage file to the CBFS

        Args:
            cbfs_fname (str): Filename of CBFS to create
            name (str): Name to use inside the CBFS
            fname (str): Filename of file to add

        Returns:
            str: Tool output
        """
        args = [cbfs_fname,
                'add-stage',
                '-n', name,
                '-f', fname
            ]
        return self.run_cmd(*args)

    def fail(self):
        """Run cbfstool with invalid arguments to check it reports failure

        This is really just a sanity check

        Returns:
            CommandResult: Result from running the bad command
        """
        args = ['missing-file', 'bad-command']
        return self.run_cmd_result(*args)

    def fetch(self, method):
        """Fetch handler for cbfstool

        This installs cbfstool by downloading from Google Drive.

        Args:
            method (FETCH_...): Method to use

        Returns:
            True if the file was fetched and now installed, None if a method
            other than FETCH_BIN was requested

        Raises:
            Valuerror: Fetching could not be completed
        """
        if method != bintool.FETCH_BIN:
            return None
        fname, tmpdir = self.fetch_from_drive(
            '1IOnE0Qvy97d-0WOCwF64xBGpKSY2sMtJ')
        return fname, tmpdir