forked from DMOJ/judge-server
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of https://github.com/DMOJ/judge-server
- Loading branch information
Showing
19 changed files
with
406 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# Based off https://github.com/django/django/blob/main/django/utils/functional.py, licensed under 3-clause BSD. | ||
from functools import total_ordering | ||
|
||
from dmoj.cptbox._cptbox import BufferProxy | ||
|
||
_SENTINEL = object() | ||
|
||
|
||
@total_ordering | ||
class LazyBytes(BufferProxy): | ||
""" | ||
Encapsulate a function call and act as a proxy for methods that are | ||
called on the result of that function. The function is not evaluated | ||
until one of the methods on the result is called. | ||
""" | ||
|
||
def __init__(self, func): | ||
self.__func = func | ||
self.__value = _SENTINEL | ||
|
||
def __get_value(self): | ||
if self.__value is _SENTINEL: | ||
self.__value = self.__func() | ||
return self.__value | ||
|
||
@classmethod | ||
def _create_promise(cls, method_name): | ||
# Builds a wrapper around some magic method | ||
def wrapper(self, *args, **kw): | ||
# Automatically triggers the evaluation of a lazy value and | ||
# applies the given magic method of the result type. | ||
res = self.__get_value() | ||
return getattr(res, method_name)(*args, **kw) | ||
|
||
return wrapper | ||
|
||
def __cast(self): | ||
return bytes(self.__get_value()) | ||
|
||
def _get_real_buffer(self): | ||
return self.__cast() | ||
|
||
def __bytes__(self): | ||
return self.__cast() | ||
|
||
def __repr__(self): | ||
return repr(self.__cast()) | ||
|
||
def __str__(self): | ||
return str(self.__cast()) | ||
|
||
def __eq__(self, other): | ||
if isinstance(other, LazyBytes): | ||
other = other.__cast() | ||
return self.__cast() == other | ||
|
||
def __lt__(self, other): | ||
if isinstance(other, LazyBytes): | ||
other = other.__cast() | ||
return self.__cast() < other | ||
|
||
def __hash__(self): | ||
return hash(self.__cast()) | ||
|
||
def __mod__(self, rhs): | ||
return self.__cast() % rhs | ||
|
||
def __add__(self, other): | ||
return self.__cast() + other | ||
|
||
def __radd__(self, other): | ||
return other + self.__cast() | ||
|
||
def __deepcopy__(self, memo): | ||
# Instances of this class are effectively immutable. It's just a | ||
# collection of functions. So we don't need to do anything | ||
# complicated for copying. | ||
memo[id(self)] = self | ||
return self | ||
|
||
|
||
for type_ in bytes.mro(): | ||
for method_name in type_.__dict__: | ||
# All __promise__ return the same wrapper method, they | ||
# look up the correct implementation when called. | ||
if hasattr(LazyBytes, method_name): | ||
continue | ||
setattr(LazyBytes, method_name, LazyBytes._create_promise(method_name)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,116 @@ | ||
import io | ||
import mmap | ||
import os | ||
from abc import ABCMeta, abstractmethod | ||
from tempfile import NamedTemporaryFile, TemporaryFile | ||
from typing import Optional | ||
|
||
from dmoj.cptbox._cptbox import memory_fd_create, memory_fd_seal | ||
from dmoj.cptbox._cptbox import memfd_create, memfd_seal | ||
|
||
|
||
class MemoryIO(io.FileIO): | ||
def __init__(self) -> None: | ||
super().__init__(memory_fd_create(), 'r+') | ||
def _make_fd_readonly(fd): | ||
new_fd = os.open(f'/proc/self/fd/{fd}', os.O_RDONLY) | ||
try: | ||
os.dup2(new_fd, fd) | ||
finally: | ||
os.close(new_fd) | ||
|
||
|
||
class MmapableIO(io.FileIO, metaclass=ABCMeta): | ||
def __init__(self, fd, *, prefill: Optional[bytes] = None, seal=False) -> None: | ||
super().__init__(fd, 'r+') | ||
|
||
if prefill: | ||
self.write(prefill) | ||
if seal: | ||
self.seal() | ||
|
||
@classmethod | ||
@abstractmethod | ||
def usable_with_name(cls) -> bool: | ||
... | ||
|
||
@abstractmethod | ||
def seal(self) -> None: | ||
... | ||
|
||
@abstractmethod | ||
def to_path(self) -> str: | ||
... | ||
|
||
def to_bytes(self) -> bytes: | ||
try: | ||
with mmap.mmap(self.fileno(), 0, access=mmap.ACCESS_READ) as f: | ||
return bytes(f) | ||
except ValueError as e: | ||
if e.args[0] == 'cannot mmap an empty file': | ||
return b'' | ||
raise | ||
|
||
|
||
class NamedFileIO(MmapableIO): | ||
_name: str | ||
|
||
def __init__(self, *, prefill: Optional[bytes] = None, seal=False) -> None: | ||
with NamedTemporaryFile(delete=False) as f: | ||
self._name = f.name | ||
super().__init__(os.dup(f.fileno()), prefill=prefill, seal=seal) | ||
|
||
def seal(self) -> None: | ||
self.seek(0, os.SEEK_SET) | ||
|
||
def close(self) -> None: | ||
super().close() | ||
os.unlink(self._name) | ||
|
||
def to_path(self) -> str: | ||
return self._name | ||
|
||
@classmethod | ||
def usable_with_name(cls): | ||
return True | ||
|
||
|
||
class UnnamedFileIO(MmapableIO): | ||
def __init__(self, *, prefill: Optional[bytes] = None, seal=False) -> None: | ||
with TemporaryFile() as f: | ||
super().__init__(os.dup(f.fileno()), prefill=prefill, seal=seal) | ||
|
||
def seal(self) -> None: | ||
self.seek(0, os.SEEK_SET) | ||
_make_fd_readonly(self.fileno()) | ||
|
||
def to_path(self) -> str: | ||
return f'/proc/{os.getpid()}/fd/{self.fileno()}' | ||
|
||
@classmethod | ||
def usable_with_name(cls): | ||
with cls() as f: | ||
return os.path.exists(f.to_path()) | ||
|
||
|
||
class MemfdIO(MmapableIO): | ||
def __init__(self, *, prefill: Optional[bytes] = None, seal=False) -> None: | ||
super().__init__(memfd_create(), prefill=prefill, seal=seal) | ||
|
||
def seal(self) -> None: | ||
memory_fd_seal(self.fileno()) | ||
fd = self.fileno() | ||
memfd_seal(fd) | ||
_make_fd_readonly(fd) | ||
|
||
def to_path(self) -> str: | ||
return f'/proc/{os.getpid()}/fd/{self.fileno()}' | ||
|
||
@classmethod | ||
def usable_with_name(cls): | ||
try: | ||
with cls() as f: | ||
return os.path.exists(f.to_path()) | ||
except OSError: | ||
return False | ||
|
||
|
||
# Try to use memfd if possible, otherwise fallback to unlinked temporary files | ||
# (UnnamedFileIO). On FreeBSD and some other systems, /proc/[pid]/fd doesn't | ||
# exist, so to_path() will not work. We fall back to NamedFileIO in that case. | ||
MemoryIO = next((i for i in (MemfdIO, UnnamedFileIO, NamedFileIO) if i.usable_with_name())) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.