1# SPDX-License-Identifier: GPL-2.0+
2# Copyright 2022 Google LLC
3#
4"""Bintool implementation for lzma_alone
5
6lzma_alone allows compression and decompression of files, using an older version
7of lzma.
8
9Documentation is available via::
10
11   man lzma_alone
12
13Here is the help:
14
15LZMA 9.22 beta : Igor Pavlov : Public domain : 2011-04-18
16
17Usage:  LZMA <e|d> inputFile outputFile [<switches>...]
18  e: encode file
19  d: decode file
20  b: Benchmark
21<Switches>
22  -a{N}:  set compression mode - [0, 1], default: 1 (max)
23  -d{N}:  set dictionary size - [12, 30], default: 23 (8MB)
24  -fb{N}: set number of fast bytes - [5, 273], default: 128
25  -mc{N}: set number of cycles for match finder
26  -lc{N}: set number of literal context bits - [0, 8], default: 3
27  -lp{N}: set number of literal pos bits - [0, 4], default: 0
28  -pb{N}: set number of pos bits - [0, 4], default: 2
29  -mf{MF_ID}: set Match Finder: [bt2, bt3, bt4, hc4], default: bt4
30  -mt{N}: set number of CPU threads
31  -eos:   write End Of Stream marker
32  -si:    read data from stdin
33  -so:    write data to stdout
34"""
35
36import re
37import tempfile
38
39from binman import bintool
40from u_boot_pylib import tools
41
42# pylint: disable=C0103
43class Bintoollzma_alone(bintool.Bintool):
44    """Compression/decompression using the LZMA algorithm
45
46    This bintool supports running `lzma_alone` to compress and decompress data,
47    as used by binman.
48
49    It is also possible to fetch the tool, which uses `apt` to install it.
50
51    Documentation is available via::
52
53        man lzma_alone
54    """
55    def __init__(self, name):
56        super().__init__(name, 'lzma_alone compression')
57
58    def compress(self, indata):
59        """Compress data with lzma_alone
60
61        Args:
62            indata (bytes): Data to compress
63
64        Returns:
65            bytes: Compressed data
66        """
67        with tempfile.NamedTemporaryFile(prefix='comp.tmp',
68                                         dir=tools.get_output_dir()) as inf:
69            tools.write_file(inf.name, indata)
70            with tempfile.NamedTemporaryFile(prefix='compo.otmp',
71                                             dir=tools.get_output_dir()) as outf:
72                args = ['e', inf.name, outf.name, '-lc1', '-lp0', '-pb0', '-d8']
73                self.run_cmd(*args, binary=True)
74                return tools.read_file(outf.name)
75
76    def decompress(self, indata):
77        """Decompress data with lzma_alone
78
79        Args:
80            indata (bytes): Data to decompress
81
82        Returns:
83            bytes: Decompressed data
84        """
85        with tempfile.NamedTemporaryFile(prefix='decomp.tmp',
86                                         dir=tools.get_output_dir()) as inf:
87            tools.write_file(inf.name, indata)
88            with tempfile.NamedTemporaryFile(prefix='compo.otmp',
89                                             dir=tools.get_output_dir()) as outf:
90                args = ['d', inf.name, outf.name]
91                self.run_cmd(*args, binary=True)
92                return tools.read_file(outf.name, binary=True)
93
94    def fetch(self, method):
95        """Fetch handler for lzma_alone
96
97        This installs the lzma-alone package using the apt utility.
98
99        Args:
100            method (FETCH_...): Method to use
101
102        Returns:
103            True if the file was fetched and now installed, None if a method
104            other than FETCH_BIN was requested
105
106        Raises:
107            Valuerror: Fetching could not be completed
108        """
109        if method != bintool.FETCH_BIN:
110            return None
111        return self.apt_install('lzma-alone')
112
113    def version(self):
114        """Version handler
115
116        Returns:
117            str: Version number of lzma_alone
118        """
119        out = self.run_cmd_result('', raise_on_error=False).stderr.strip()
120        lines = out.splitlines()
121        if not lines:
122            return super().version()
123        out = lines[0]
124        # e.g. LZMA 9.22 beta : Igor Pavlov : Public domain : 2011-04-18
125        m_version = re.match(r'LZMA ([^:]*).*', out)
126        return m_version.group(1).strip() if m_version else out
127