Module rsyscall.tests.test_fuse

Expand source code Browse git
from rsyscall.tests.trio_test_case import TrioTestCase
from rsyscall import local_thread
from rsyscall.nix import local_store
import shlex

from rsyscall.sched import CLONE
from rsyscall.fcntl import O
from rsyscall.unistd import SEEK
from rsyscall.stdlib import mkdtemp
from rsyscall.sys.mount import MS
from rsyscall.sys.stat import Stat

from rsyscall.linux.fuse import (
    FuseInitOp, FuseLookupOp, FuseOpenOp, FuseOpendirOp, FuseReadOp, FuseGetattrOp, FuseReaddirplusOp, FuseFlushOp, FuseReleaseOp, FuseReleasedirOp, FuseReadlinkOp, FuseGetxattrOp,
    FuseIn, FuseInList, FuseInitIn, FuseInitOut, FuseAttr, FuseEntryOut, FuseOpenOut, FOPEN,
    FuseAttrOut,
    FuseDirentplus, FuseDirent,
    FUSE_INIT,
)
from rsyscall.linux.dirent import DirentList, DT
from rsyscall.time import Timespec
from rsyscall.sys.stat import TypeMode, S_IF, Mode
from rsyscall import Command, WrittenPointer, FileDescriptor
from rsyscall.memory.ram import RAM
from rsyscall.unistd import AT, ArgList
from rsyscall.tasks.stub import StubServer
from rsyscall.scripts.symsh import FuseFS
import errno
import os
import trio
import typing as t
from dataclasses import dataclass
import unittest

class TestFUSE(TrioTestCase):
    async def asyncSetUp(self) -> None:
        self.tmpdir = await mkdtemp(local_thread)
        self.thr = await local_thread.clone(CLONE.NEWUSER|CLONE.NEWNS)
        self.child = await self.thr.clone()
        self.path = self.tmpdir.path/"path"
        await self.thr.mkdir(self.path)
        self.fuse = await FuseFS.mount(self.thr, self.path)

    async def asyncTearDown(self) -> None:
        await self.fuse.thr.process.kill()
        await self.fuse.cleanup()
        await self.tmpdir.cleanup()

    T_fusein = t.TypeVar('T_fusein', bound=FuseIn)
    async def assertRead(self, cls: t.Type[T_fusein]) -> T_fusein:
        [op] = await self.fuse.read()
        if not isinstance(op, cls):
            raise Exception("expected", cls, "got", op)
        return op

    async def test_basic(self) -> None:
        data_read_from_fuse = b"this is some data read from fuse"
        @self.nursery.start_soon
        async def open() -> None:
            foo = await self.child.task.open(await self.child.ptr(self.path/"foo"), O.RDONLY)
            data, _ = await foo.read(await self.child.malloc(bytes, 4096))
            self.assertEqual(data_read_from_fuse, await data.read())
            data, _ = await foo.read(await self.child.malloc(bytes, 4096))
            self.assertEqual(data.size(), 0)
            await foo.close()

            root = await self.child.task.open(await self.child.ptr(self.path), O.RDONLY)
            valid, rest = await root.getdents(await self.child.ram.malloc(DirentList, 4096))
            await root.close()
        root_getattr = await self.assertRead(FuseGetattrOp)
        if root_getattr.hdr.nodeid != 1:
            raise Exception("expected to get getattr for root node 1,  not", root_getattr.hdr.nodeid)
        await self.fuse.write(root_getattr.respond(FuseAttrOut(
            attr_valid=Timespec(10000, 0),
            attr=FuseAttr(
                ino=1, size=0, blocks=1,
                atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
                mode=TypeMode(S_IF.DIR, Mode(0o777)), nlink=1,
                uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
            ))))
        await self.fuse.write((await self.assertRead(FuseLookupOp)).respond(FuseEntryOut(
            nodeid=2, generation=1,
            entry_valid=Timespec(10000, 0), attr_valid=Timespec(10000, 0),
            # the size needs to be consistent with the data we'll actually send back on read
            # the kernel, I guess, handles delivering an eof
            attr=FuseAttr(
                ino=999, size=len(data_read_from_fuse), blocks=1,
                atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
                mode=TypeMode(S_IF.REG, Mode(0o777)), nlink=1, uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
            ))))
        fh = 42
        await self.fuse.write((await self.assertRead(FuseOpenOp)).respond(FuseOpenOut(fh=fh, open_flags=FOPEN.NONE)))
        await self.fuse.write((await self.assertRead(FuseReadOp)).respond(data_read_from_fuse))
        # close file
        await self.fuse.write((await self.assertRead(FuseFlushOp)).respond())
        await self.fuse.write((await self.assertRead(FuseReleaseOp)).respond())
        # open root and getdents
        root_fh = 137
        await self.fuse.write((await self.assertRead(FuseOpendirOp)).respond(FuseOpenOut(fh=root_fh, open_flags=FOPEN.NONE)))
        foobar_ino = 432
        await self.fuse.write((await self.assertRead(FuseReaddirplusOp)).respond([
            FuseDirentplus(
                FuseEntryOut(
                    nodeid=foobar_ino, generation=1,
                    entry_valid=Timespec(10000, 0), attr_valid=Timespec(10000, 0),
                    # the size needs to be consistent with the data we'll actually send back on read
                    # the kernel, I guess, handles delivering an eof
                    attr=FuseAttr(
                        ino=foobar_ino, size=len(data_read_from_fuse), blocks=1,
                        atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
                        mode=TypeMode(S_IF.REG, Mode(0o777)), nlink=1, uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
                    )),
                FuseDirent(
                    ino=foobar_ino,
                    off=1,
                    type=DT.REG,
                    name="foobar",
                ),
            ),
        ]))
        # close file
        await self.fuse.write((await self.assertRead(FuseReleasedirOp)).respond())

    async def test_symlink(self) -> None:
        @self.nursery.start_soon
        async def open() -> None:
            async with (await self.child.task.open(await self.thr.ptr(self.path/"foo"), O.RDONLY)) as foo:
                data, _ = await foo.read(await self.thr.malloc(bytes, 4096))
        await self.fuse.write((await self.assertRead(FuseGetattrOp)).respond(FuseAttrOut(
            attr_valid=Timespec(10000, 0),
            attr=FuseAttr(
                ino=1, size=0, blocks=1,
                atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
                mode=TypeMode(S_IF.DIR, Mode(0o777)), nlink=1,
                uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
            ))))
        await self.fuse.write((await self.assertRead(FuseLookupOp)).respond(FuseEntryOut(
            nodeid=2, generation=1,
            entry_valid=Timespec(10000, 0), attr_valid=Timespec(10000, 0),
            # the size needs to be consistent with the data we'll actually send back on read
            # the kernel, I guess, handles delivering an eof;
            # we can just claim a larger size then send back less data
            attr=FuseAttr(
                ino=999, size=4096, blocks=1,
                atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
                mode=TypeMode(S_IF.LNK, Mode(0o777)), nlink=1,
                uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
            ))))
        await self.fuse.write((await self.assertRead(FuseReadlinkOp)).respond("/bin/sh"))

Classes

class TestFUSE (methodName='runTest')

A trio-enabled variant of unittest.TestCase

Create an instance of the class that will use the named test method when executed. Raises a ValueError if the instance does not have a method with the specified name.

Expand source code Browse git
class TestFUSE(TrioTestCase):
    async def asyncSetUp(self) -> None:
        self.tmpdir = await mkdtemp(local_thread)
        self.thr = await local_thread.clone(CLONE.NEWUSER|CLONE.NEWNS)
        self.child = await self.thr.clone()
        self.path = self.tmpdir.path/"path"
        await self.thr.mkdir(self.path)
        self.fuse = await FuseFS.mount(self.thr, self.path)

    async def asyncTearDown(self) -> None:
        await self.fuse.thr.process.kill()
        await self.fuse.cleanup()
        await self.tmpdir.cleanup()

    T_fusein = t.TypeVar('T_fusein', bound=FuseIn)
    async def assertRead(self, cls: t.Type[T_fusein]) -> T_fusein:
        [op] = await self.fuse.read()
        if not isinstance(op, cls):
            raise Exception("expected", cls, "got", op)
        return op

    async def test_basic(self) -> None:
        data_read_from_fuse = b"this is some data read from fuse"
        @self.nursery.start_soon
        async def open() -> None:
            foo = await self.child.task.open(await self.child.ptr(self.path/"foo"), O.RDONLY)
            data, _ = await foo.read(await self.child.malloc(bytes, 4096))
            self.assertEqual(data_read_from_fuse, await data.read())
            data, _ = await foo.read(await self.child.malloc(bytes, 4096))
            self.assertEqual(data.size(), 0)
            await foo.close()

            root = await self.child.task.open(await self.child.ptr(self.path), O.RDONLY)
            valid, rest = await root.getdents(await self.child.ram.malloc(DirentList, 4096))
            await root.close()
        root_getattr = await self.assertRead(FuseGetattrOp)
        if root_getattr.hdr.nodeid != 1:
            raise Exception("expected to get getattr for root node 1,  not", root_getattr.hdr.nodeid)
        await self.fuse.write(root_getattr.respond(FuseAttrOut(
            attr_valid=Timespec(10000, 0),
            attr=FuseAttr(
                ino=1, size=0, blocks=1,
                atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
                mode=TypeMode(S_IF.DIR, Mode(0o777)), nlink=1,
                uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
            ))))
        await self.fuse.write((await self.assertRead(FuseLookupOp)).respond(FuseEntryOut(
            nodeid=2, generation=1,
            entry_valid=Timespec(10000, 0), attr_valid=Timespec(10000, 0),
            # the size needs to be consistent with the data we'll actually send back on read
            # the kernel, I guess, handles delivering an eof
            attr=FuseAttr(
                ino=999, size=len(data_read_from_fuse), blocks=1,
                atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
                mode=TypeMode(S_IF.REG, Mode(0o777)), nlink=1, uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
            ))))
        fh = 42
        await self.fuse.write((await self.assertRead(FuseOpenOp)).respond(FuseOpenOut(fh=fh, open_flags=FOPEN.NONE)))
        await self.fuse.write((await self.assertRead(FuseReadOp)).respond(data_read_from_fuse))
        # close file
        await self.fuse.write((await self.assertRead(FuseFlushOp)).respond())
        await self.fuse.write((await self.assertRead(FuseReleaseOp)).respond())
        # open root and getdents
        root_fh = 137
        await self.fuse.write((await self.assertRead(FuseOpendirOp)).respond(FuseOpenOut(fh=root_fh, open_flags=FOPEN.NONE)))
        foobar_ino = 432
        await self.fuse.write((await self.assertRead(FuseReaddirplusOp)).respond([
            FuseDirentplus(
                FuseEntryOut(
                    nodeid=foobar_ino, generation=1,
                    entry_valid=Timespec(10000, 0), attr_valid=Timespec(10000, 0),
                    # the size needs to be consistent with the data we'll actually send back on read
                    # the kernel, I guess, handles delivering an eof
                    attr=FuseAttr(
                        ino=foobar_ino, size=len(data_read_from_fuse), blocks=1,
                        atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
                        mode=TypeMode(S_IF.REG, Mode(0o777)), nlink=1, uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
                    )),
                FuseDirent(
                    ino=foobar_ino,
                    off=1,
                    type=DT.REG,
                    name="foobar",
                ),
            ),
        ]))
        # close file
        await self.fuse.write((await self.assertRead(FuseReleasedirOp)).respond())

    async def test_symlink(self) -> None:
        @self.nursery.start_soon
        async def open() -> None:
            async with (await self.child.task.open(await self.thr.ptr(self.path/"foo"), O.RDONLY)) as foo:
                data, _ = await foo.read(await self.thr.malloc(bytes, 4096))
        await self.fuse.write((await self.assertRead(FuseGetattrOp)).respond(FuseAttrOut(
            attr_valid=Timespec(10000, 0),
            attr=FuseAttr(
                ino=1, size=0, blocks=1,
                atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
                mode=TypeMode(S_IF.DIR, Mode(0o777)), nlink=1,
                uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
            ))))
        await self.fuse.write((await self.assertRead(FuseLookupOp)).respond(FuseEntryOut(
            nodeid=2, generation=1,
            entry_valid=Timespec(10000, 0), attr_valid=Timespec(10000, 0),
            # the size needs to be consistent with the data we'll actually send back on read
            # the kernel, I guess, handles delivering an eof;
            # we can just claim a larger size then send back less data
            attr=FuseAttr(
                ino=999, size=4096, blocks=1,
                atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
                mode=TypeMode(S_IF.LNK, Mode(0o777)), nlink=1,
                uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
            ))))
        await self.fuse.write((await self.assertRead(FuseReadlinkOp)).respond("/bin/sh"))

Ancestors

Class variables

var nursery : trio.Nursery
var T_fusein

Methods

async def assertRead(self, cls: Type[~T_fusein]) ‑> ~T_fusein
Expand source code Browse git
async def assertRead(self, cls: t.Type[T_fusein]) -> T_fusein:
    [op] = await self.fuse.read()
    if not isinstance(op, cls):
        raise Exception("expected", cls, "got", op)
    return op
async def test_basic(self) ‑> NoneType
Expand source code Browse git
async def test_basic(self) -> None:
    data_read_from_fuse = b"this is some data read from fuse"
    @self.nursery.start_soon
    async def open() -> None:
        foo = await self.child.task.open(await self.child.ptr(self.path/"foo"), O.RDONLY)
        data, _ = await foo.read(await self.child.malloc(bytes, 4096))
        self.assertEqual(data_read_from_fuse, await data.read())
        data, _ = await foo.read(await self.child.malloc(bytes, 4096))
        self.assertEqual(data.size(), 0)
        await foo.close()

        root = await self.child.task.open(await self.child.ptr(self.path), O.RDONLY)
        valid, rest = await root.getdents(await self.child.ram.malloc(DirentList, 4096))
        await root.close()
    root_getattr = await self.assertRead(FuseGetattrOp)
    if root_getattr.hdr.nodeid != 1:
        raise Exception("expected to get getattr for root node 1,  not", root_getattr.hdr.nodeid)
    await self.fuse.write(root_getattr.respond(FuseAttrOut(
        attr_valid=Timespec(10000, 0),
        attr=FuseAttr(
            ino=1, size=0, blocks=1,
            atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
            mode=TypeMode(S_IF.DIR, Mode(0o777)), nlink=1,
            uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
        ))))
    await self.fuse.write((await self.assertRead(FuseLookupOp)).respond(FuseEntryOut(
        nodeid=2, generation=1,
        entry_valid=Timespec(10000, 0), attr_valid=Timespec(10000, 0),
        # the size needs to be consistent with the data we'll actually send back on read
        # the kernel, I guess, handles delivering an eof
        attr=FuseAttr(
            ino=999, size=len(data_read_from_fuse), blocks=1,
            atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
            mode=TypeMode(S_IF.REG, Mode(0o777)), nlink=1, uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
        ))))
    fh = 42
    await self.fuse.write((await self.assertRead(FuseOpenOp)).respond(FuseOpenOut(fh=fh, open_flags=FOPEN.NONE)))
    await self.fuse.write((await self.assertRead(FuseReadOp)).respond(data_read_from_fuse))
    # close file
    await self.fuse.write((await self.assertRead(FuseFlushOp)).respond())
    await self.fuse.write((await self.assertRead(FuseReleaseOp)).respond())
    # open root and getdents
    root_fh = 137
    await self.fuse.write((await self.assertRead(FuseOpendirOp)).respond(FuseOpenOut(fh=root_fh, open_flags=FOPEN.NONE)))
    foobar_ino = 432
    await self.fuse.write((await self.assertRead(FuseReaddirplusOp)).respond([
        FuseDirentplus(
            FuseEntryOut(
                nodeid=foobar_ino, generation=1,
                entry_valid=Timespec(10000, 0), attr_valid=Timespec(10000, 0),
                # the size needs to be consistent with the data we'll actually send back on read
                # the kernel, I guess, handles delivering an eof
                attr=FuseAttr(
                    ino=foobar_ino, size=len(data_read_from_fuse), blocks=1,
                    atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
                    mode=TypeMode(S_IF.REG, Mode(0o777)), nlink=1, uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
                )),
            FuseDirent(
                ino=foobar_ino,
                off=1,
                type=DT.REG,
                name="foobar",
            ),
        ),
    ]))
    # close file
    await self.fuse.write((await self.assertRead(FuseReleasedirOp)).respond())
Expand source code Browse git
async def test_symlink(self) -> None:
    @self.nursery.start_soon
    async def open() -> None:
        async with (await self.child.task.open(await self.thr.ptr(self.path/"foo"), O.RDONLY)) as foo:
            data, _ = await foo.read(await self.thr.malloc(bytes, 4096))
    await self.fuse.write((await self.assertRead(FuseGetattrOp)).respond(FuseAttrOut(
        attr_valid=Timespec(10000, 0),
        attr=FuseAttr(
            ino=1, size=0, blocks=1,
            atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
            mode=TypeMode(S_IF.DIR, Mode(0o777)), nlink=1,
            uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
        ))))
    await self.fuse.write((await self.assertRead(FuseLookupOp)).respond(FuseEntryOut(
        nodeid=2, generation=1,
        entry_valid=Timespec(10000, 0), attr_valid=Timespec(10000, 0),
        # the size needs to be consistent with the data we'll actually send back on read
        # the kernel, I guess, handles delivering an eof;
        # we can just claim a larger size then send back less data
        attr=FuseAttr(
            ino=999, size=4096, blocks=1,
            atime=Timespec(0, 0), mtime=Timespec(0, 0), ctime=Timespec(0, 0),
            mode=TypeMode(S_IF.LNK, Mode(0o777)), nlink=1,
            uid=self.fuse.uid, gid=self.fuse.gid, rdev=0, blksize=4096
        ))))
    await self.fuse.write((await self.assertRead(FuseReadlinkOp)).respond("/bin/sh"))

Inherited members