22import io
33import mmap
44import os
5+ from tempfile import NamedTemporaryFile
56from typing import Optional
67
78from dmoj .cptbox ._cptbox import memory_fd_create , memory_fd_seal
89
910
10- class MemoryIO (io .FileIO ):
11- def __init__ (self , prefill : Optional [bytes ] = None , seal = False ) -> None :
12- super ().__init__ (memory_fd_create (), 'r+' )
11+ def _make_fd_readonly (fd ):
12+ new_fd = os .open (f'/proc/self/fd/{ fd } ' , os .O_RDONLY )
13+ try :
14+ os .dup2 (new_fd , fd )
15+ finally :
16+ os .close (new_fd )
17+
18+
19+ class MmapableIO (io .FileIO ):
20+ def __init__ (self , fd , * , prefill : Optional [bytes ] = None , seal = False ) -> None :
21+ super ().__init__ (fd , 'r+' )
22+
1323 if prefill :
1424 self .write (prefill )
1525 if seal :
1626 self .seal ()
1727
28+ def to_bytes (self ) -> bytes :
29+ try :
30+ with mmap .mmap (self .fileno (), 0 , access = mmap .ACCESS_READ ) as f :
31+ return bytes (f )
32+ except ValueError as e :
33+ if e .args [0 ] == 'cannot mmap an empty file' :
34+ return b''
35+ raise
36+
37+
38+ class NamedFileIO (MmapableIO ):
39+ _name : str
40+
41+ def __init__ (self , * , prefill : Optional [bytes ] = None , seal = False ) -> None :
42+ with NamedTemporaryFile (delete = False ) as f :
43+ self ._name = f .name
44+ super ().__init__ (os .dup (f .fileno ()), prefill = prefill , seal = seal )
45+
46+ def seal (self ) -> None :
47+ self .seek (0 , os .SEEK_SET )
48+ _make_fd_readonly (self .fileno ())
49+
50+ def close (self ) -> None :
51+ super ().close ()
52+ os .unlink (self ._name )
53+
54+ def to_path (self ) -> str :
55+ return self ._name
56+
57+
58+ class MemoryIO (MmapableIO ):
59+ _name : Optional [str ] = None
60+
61+ def __init__ (self , * , prefill : Optional [bytes ] = None , seal = False ) -> None :
62+ super ().__init__ (memory_fd_create (), prefill = prefill , seal = seal )
63+
1864 def seal (self ) -> None :
1965 fd = self .fileno ()
2066 try :
@@ -26,20 +72,14 @@ def seal(self) -> None:
2672 return
2773 raise
2874
29- new_fd = os .open (f'/proc/self/fd/{ fd } ' , os .O_RDONLY )
30- try :
31- os .dup2 (new_fd , fd )
32- finally :
33- os .close (new_fd )
75+ _make_fd_readonly (fd )
3476
3577 def to_path (self ) -> str :
3678 return f'/proc/{ os .getpid ()} /fd/{ self .fileno ()} '
3779
38- def to_bytes (self ) -> bytes :
39- try :
40- with mmap .mmap (self .fileno (), 0 , access = mmap .ACCESS_READ ) as f :
41- return bytes (f )
42- except ValueError as e :
43- if e .args [0 ] == 'cannot mmap an empty file' :
44- return b''
45- raise
80+
81+ # On FreeBSD and some other systems, if /proc/[pid]/fd doesn't exist,
82+ # then MemoryIO.to_path() will not work. We fall back to NamedFileIO
83+ # in that case.
84+ if not os .path .isdir (f'/proc/{ os .getpid ()} /fd' ):
85+ MemoryIO = NamedFileIO
0 commit comments