summaryrefslogtreecommitdiffstats
path: root/tools/lklfuse.py
blob: 6982be1ce4a08d146e7757f8cfedefaf29e76af0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import logging
import os
import tempfile

from subprocess import Popen, PIPE
from time import sleep
from . import log, rmdir

__all__ = ["mount"]

L = logging.getLogger(__name__)


@log
def mount(path, fs_type, part_nr=None):
    """Mount a RAW image file containing an ext2/ext3/ext4/xfs/btrfs/vfat/ntfs
    filesystem with read-only support using `lklfuse`.

    Make sure you have built `LKL` from the source code:
        $ sudo apt install build-essential flex bison bc libfuse-dev \
            libarchive-dev xfsprogs python git
        $ git clone https://github.com/lkl/linux.git
        $ cd linux
        $ echo "CONFIG_NTFS_FS=y" >> arch/lkl/configs/defconfig
        $ make -C tools/lkl
        $ sudo cp tools/lkl/lklfuse /usr/local/bin

    Args:
        path (str): Path to the RAW image file.
        fs_type (str): Filesystem type.
        part_nr (int|str): Partition number.

    Returns:
        Path to the mount point.
    """
    mp = tempfile.mkdtemp()

    opts = f"ro,type={fs_type}"
    if part_nr is not None:
        opts += f",part={part_nr}"
    if fs_type == "xfs":
        # filesystem will be mounted without running log recovery.
        # otherwise, the mount will fail.
        # see also: https://man7.org/linux/man-pages/man5/xfs.5.html
        opts += ",opts=norecovery"
    elif fs_type == "ext3" or fs_type == "ext4":
        # allow mounting dirty ext3 and ext4 filesystems
        # see also: https://man7.org/linux/man-pages/man5/ext3.5.html
        # example error message that occurs e.g. when mounting Fedora 26:
        # JBD2: recovery failed
        # EXT4-fs (vda): error loading journal
        opts += ",opts=noload"

    cmd = ["lklfuse", path, mp, "-f", "-o", opts]

    try:
        p = Popen(cmd, stdout=PIPE, stderr=PIPE, text=True)
    except Exception as e:
        L.error("failed to execute command %s: %r", cmd, e)
        rmdir(mp)
        return None

    while p.poll() is None:
        if os.path.ismount(mp):
            return mp
        sleep(1)

    ret = p.poll()
    out, err = p.communicate()
    out, err = out.strip(), err.strip()
    L.error("retcode: %d, stdout: %s, stderr: %s", ret, out, err)

    rmdir(mp)

    return None