aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar ionite34 <[email protected]>2022-11-24 09:16:30 +0800
committerGravatar ionite34 <[email protected]>2022-11-24 09:16:30 +0800
commit0387793222cb1c0feb5644ba8ba8c26caa84c7b2 (patch)
tree5d693a8a92631e5efe8efc25b31ef542930fba55
parentRemove `None` type hints from home, name (diff)
Refactor subprocess mount to ctypes call
-rw-r--r--snekbox/libmount.py75
-rw-r--r--snekbox/memfs.py36
2 files changed, 82 insertions, 29 deletions
diff --git a/snekbox/libmount.py b/snekbox/libmount.py
new file mode 100644
index 0000000..52ea6b2
--- /dev/null
+++ b/snekbox/libmount.py
@@ -0,0 +1,75 @@
+from __future__ import annotations
+
+import ctypes
+import os
+from ctypes.util import find_library
+from enum import IntEnum
+from pathlib import Path
+
+__all__ = ("mount", "unmount", "Size", "UnmountFlags")
+
+libc = ctypes.CDLL(find_library("c"), use_errno=True)
+libc.mount.argtypes = (
+ ctypes.c_char_p,
+ ctypes.c_char_p,
+ ctypes.c_char_p,
+ ctypes.c_ulong,
+ ctypes.c_char_p,
+)
+libc.umount2.argtypes = (ctypes.c_char_p, ctypes.c_int)
+
+
+class Size(int):
+ """Size in bytes."""
+
+ @classmethod
+ def from_mb(cls, mb: int) -> Size:
+ """Create a Size from mebibytes."""
+ return cls(mb * 1024 * 1024)
+
+
+class UnmountFlags(IntEnum):
+ """Flags for umount2."""
+
+ MNT_FORCE = 1
+ MNT_DETACH = 2
+ MNT_EXPIRE = 4
+ UMOUNT_NOFOLLOW = 8
+
+
+def mount(source: Path | str, target: Path | str, fs: str, **options: str | int) -> None:
+ """
+ Mount a filesystem.
+
+ https://man7.org/linux/man-pages/man8/mount.8.html
+
+ Args:
+ source: Source directory or device.
+ target: Target directory.
+ fs: Filesystem type.
+ **options: Mount options.
+ """
+ kwargs = " ".join(f"{key}={value}" for key, value in options.items())
+
+ result: int = libc.mount(
+ str(source).encode(), str(target).encode(), fs.encode(), 0, kwargs.encode()
+ )
+ if result < 0:
+ errno = ctypes.get_errno()
+ raise OSError(errno, f"Error mounting {target}: {os.strerror(errno)}")
+
+
+def unmount(target: Path | str, flags: UnmountFlags | int = UnmountFlags.MNT_DETACH) -> None:
+ """
+ Unmount a filesystem.
+
+ https://man7.org/linux/man-pages/man2/umount.2.html
+
+ Args:
+ target: Target directory.
+ flags: Unmount flags.
+ """
+ result: int = libc.umount2(str(target).encode(), int(flags))
+ if result < 0:
+ errno = ctypes.get_errno()
+ raise OSError(errno, f"Error unmounting {target}: {os.strerror(errno)}")
diff --git a/snekbox/memfs.py b/snekbox/memfs.py
index 7c9e71b..16f5cfb 100644
--- a/snekbox/memfs.py
+++ b/snekbox/memfs.py
@@ -2,7 +2,6 @@
from __future__ import annotations
import logging
-import subprocess
from collections.abc import Generator
from functools import cached_property
from pathlib import Path
@@ -10,36 +9,12 @@ from types import TracebackType
from typing import Type
from uuid import uuid4
+from snekbox.libmount import mount, unmount
from snekbox.snekio import FileAttachment
log = logging.getLogger(__name__)
-
-def mount_tmpfs(path: str | Path, size: int | str) -> Path:
- """Create and mount a tmpfs directory."""
- path = Path(path)
- path.mkdir()
- # Mount the tmpfs
- subprocess.check_call(
- [
- "mount",
- "-t",
- "tmpfs",
- "-o",
- f"size={size}",
- "tmpfs",
- str(path),
- ]
- )
- return path
-
-
-def unmount_tmpfs(path: str | Path) -> None:
- """Unmount and remove a tmpfs directory."""
- path = Path(path)
- subprocess.check_call(["umount", str(path)])
- # Unmounting will not remove the original folder, so do that here
- path.rmdir()
+__all__ = ("MemFS", "parse_files")
def parse_files(
@@ -95,7 +70,9 @@ class MemFS:
name = str(uuid4())
try:
path = self.root_dir / name
- self._path = mount_tmpfs(path, self.instance_size)
+ path.mkdir()
+ mount("", path, "tmpfs", size=self.instance_size)
+ self._path = path
break
except FileExistsError:
continue
@@ -137,7 +114,8 @@ class MemFS:
"""Unmounts tmpfs."""
if self._path is None:
return
- unmount_tmpfs(self.path)
+ unmount(self.path)
+ self.path.rmdir()
self._path = None
def __repr__(self):