aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Ionite <[email protected]>2022-11-15 11:54:07 -0500
committerGravatar Ionite <[email protected]>2022-11-15 11:54:07 -0500
commitf7c45b8d1b0e4b00ae72a58f2a54710936806fd8 (patch)
tree479d042ef5869eb0f45666e37c7e592dc0ed7930
parentUpdate port number to quotes (diff)
Create memfs.py
-rw-r--r--snekbox/memfs.py79
1 files changed, 79 insertions, 0 deletions
diff --git a/snekbox/memfs.py b/snekbox/memfs.py
new file mode 100644
index 0000000..48a26a2
--- /dev/null
+++ b/snekbox/memfs.py
@@ -0,0 +1,79 @@
+"""Memory filesystem for snekbox."""
+
+import logging
+from functools import cache
+from pathlib import Path
+from shutil import rmtree
+from threading import BoundedSemaphore
+from uuid import uuid4, uuid5
+
+from typing_extensions import Self
+
+NAMESPACE = "com.snekbox"
+
+log = logging.getLogger(__name__)
+
+
+@cache
+def shm_tempdir() -> Path:
+ """Return the snekbox namespace temporary directory."""
+ shm = Path("/dev/shm")
+ if not shm.exists() or not shm.is_dir():
+ raise RuntimeError("No /dev/shm found")
+
+ # Create a temporary directory in the snekbox namespace
+ tempdir = Path(shm, NAMESPACE)
+ tempdir.mkdir(exist_ok=True)
+ return tempdir
+
+
+class MemoryTempDir:
+ """A temporary directory using tmpfs."""
+ assignment_lock = BoundedSemaphore(1) # Only one process can assign a tempdir at a time
+ assigned_names: set[str] = set() # Pool of tempdir names in use
+
+ def __init__(self) -> None:
+ self.path: Path | None = None
+ pass
+
+ @property
+ def name(self) -> str | None:
+ """Name of the temp dir."""
+ if self.path is None:
+ return None
+ return self.path.name
+
+ def __enter__(self) -> Self:
+ # Generates a uuid tempdir
+ with self.assignment_lock:
+ for _ in range(10):
+ name = str(uuid4())
+ if name not in self.assigned_names:
+ self.path = Path(shm_tempdir(), name)
+ self.path.mkdir()
+ self.assigned_names.add(name)
+ return self
+ else:
+ raise RuntimeError("Failed to generate a unique tempdir name in 10 attempts")
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.cleanup()
+
+ def cleanup(self) -> None:
+ """Remove files in temp dir, releases name."""
+ if self.path is None:
+ return
+ # Remove the path folder
+ rmtree(self.path, ignore_errors=True)
+
+ if not self.path.exists():
+ with self.assignment_lock:
+ self.assigned_names.remove(self.name)
+ else:
+ # Don't remove name from pool if failed to delete folder
+ logging.warning(f"Failed to remove {self.path} in cleanup")
+
+ self.path = None
+
+ def __repr__(self):
+ return f"<MemoryTempDir {self.name or '(Uninitialized)'}>"