55#nullable enable
66
77using System ;
8+ using System . Diagnostics ;
9+ using System . Numerics ;
10+ using System . Reflection ;
811using System . Runtime . InteropServices ;
912using System . Runtime . Versioning ;
1013
14+ using Mono . Unix ;
1115using Mono . Unix . Native ;
1216
17+ using Microsoft . Scripting . Runtime ;
18+
1319using IronPython . Runtime ;
20+ using IronPython . Runtime . Operations ;
21+
1422
1523[ assembly: PythonModule ( "fcntl" , typeof ( IronPython . Modules . PythonFcntl ) , PlatformsAttribute . PlatformFamily . Unix ) ]
1624namespace IronPython . Modules ;
@@ -27,6 +35,92 @@ a file object.
2735 """ ;
2836
2937
38+ #region fcntl
39+
40+ [ LightThrowing ]
41+ public static object fcntl ( int fd , int cmd , [ NotNone ] Bytes arg ) {
42+ CheckFileDescriptor ( fd ) ;
43+
44+ const int maxArgSize = 1024 ; // 1 KiB
45+ int argSize = arg . Count ;
46+ if ( argSize > maxArgSize ) {
47+ throw PythonOps . ValueError ( "fcntl bytes arg too long" ) ;
48+ }
49+
50+ if ( ! NativeConvert . TryToFcntlCommand ( cmd , out FcntlCommand fcntlCommand ) ) {
51+ throw PythonOps . OSError ( PythonErrno . EINVAL , "unsupported fcntl command" ) ;
52+ }
53+
54+ var buf = new byte [ maxArgSize ] ;
55+ Array . Copy ( arg . UnsafeByteArray , buf , argSize ) ;
56+
57+ int result ;
58+ Errno errno ;
59+ unsafe {
60+ fixed ( byte * ptr = buf ) {
61+ do {
62+ result = Syscall . fcntl ( fd , fcntlCommand , ( IntPtr ) ptr ) ;
63+ } while ( UnixMarshal . ShouldRetrySyscall ( result , out errno ) ) ;
64+ }
65+ }
66+
67+ if ( result == - 1 ) {
68+ return LightExceptions . Throw ( PythonNT . GetOsError ( NativeConvert . FromErrno ( errno ) ) ) ;
69+ }
70+ byte [ ] response = new byte [ argSize ] ;
71+ Array . Copy ( buf , response , argSize ) ;
72+ return Bytes . Make ( response ) ;
73+ }
74+
75+
76+ [ LightThrowing ]
77+ public static object fcntl ( int fd , int cmd , [ Optional ] object ? arg ) {
78+ CheckFileDescriptor ( fd ) ;
79+
80+ long data = arg switch {
81+ Missing => 0 ,
82+ int i => i ,
83+ uint ui => ui ,
84+ long l => l ,
85+ ulong ul => ( long ) ul ,
86+ BigInteger bi => ( long ) bi ,
87+ Extensible < BigInteger > ebi => ( long ) ebi . Value ,
88+ _ => throw PythonOps . TypeErrorForBadInstance ( "integer argument expected, got {0}" , arg )
89+ } ;
90+
91+ if ( ! NativeConvert . TryToFcntlCommand ( cmd , out FcntlCommand fcntlCommand ) ) {
92+ throw PythonOps . OSError ( PythonErrno . EINVAL , "unsupported fcntl command" ) ;
93+ }
94+
95+ int result ;
96+ Errno errno ;
97+ do {
98+ result = Syscall . fcntl ( fd , fcntlCommand , data ) ;
99+ } while ( UnixMarshal . ShouldRetrySyscall ( result , out errno ) ) ;
100+
101+ if ( result == - 1 ) {
102+ return LightExceptions . Throw ( PythonNT . GetOsError ( NativeConvert . FromErrno ( errno ) ) ) ;
103+ }
104+ return ScriptingRuntimeHelpers . Int32ToObject ( result ) ;
105+ }
106+
107+
108+ [ LightThrowing ]
109+ public static object fcntl ( CodeContext context , object ? fd , int cmd , object ? arg = null ) {
110+ int fileno = GetFileDescriptor ( context , fd ) ;
111+
112+ if ( arg is Bytes bytes ) {
113+ return fcntl ( fileno , cmd , bytes ) ;
114+ }
115+
116+ return fcntl ( fileno , cmd , arg ) ;
117+ }
118+
119+ #endregion
120+
121+
122+ #region ioctl
123+
30124 // supporting fcntl.ioctl(fileno, termios.TIOCGWINSZ, buf)
31125 // where buf = array.array('h', [0, 0, 0, 0])
32126 public static object ioctl ( CodeContext context , int fd , int cmd , [ NotNone ] IBufferProtocol arg , int mutate_flag = 1 ) {
@@ -53,10 +147,109 @@ public static object ioctl(CodeContext context, int fd, int cmd, [NotNone] IBuff
53147 throw new NotImplementedException ( $ "ioctl: unsupported command { cmd } ") ;
54148 }
55149
150+ #endregion
151+
152+
153+ #region flock
154+
155+ [ DllImport ( "libc" , SetLastError = true , EntryPoint = "flock" ) ]
156+ private static extern int _flock ( int fd , int op ) ;
157+
158+ [ LightThrowing ]
159+ public static object ? flock ( int fd , int operation ) {
160+ CheckFileDescriptor ( fd ) ;
161+
162+ int result ;
163+ int errno = 0 ;
164+ do {
165+ result = _flock ( fd , operation ) ;
166+ } while ( result == - 1 && ( errno = Marshal . GetLastWin32Error ( ) ) == PythonErrno . EINTR ) ;
167+
168+ if ( result == - 1 ) {
169+ return LightExceptions . Throw ( PythonNT . GetOsError ( errno ) ) ;
170+ }
171+ return null ;
172+ }
173+
174+
175+ [ LightThrowing ]
176+ public static object ? flock ( CodeContext context , object ? fd , int operation )
177+ => flock ( GetFileDescriptor ( context , fd ) , operation ) ;
178+
179+ #endregion
180+
181+
182+ #region lockf
183+
184+ [ LightThrowing ]
185+ public static object ? lockf ( int fd , int cmd , long len = 0 , long start = 0 , int whence = 0 ) {
186+ CheckFileDescriptor ( fd ) ;
187+
188+ Flock flock = new ( ) {
189+ l_whence = ( SeekFlags ) whence ,
190+ l_start = start ,
191+ l_len = len
192+ } ;
193+ if ( cmd == LOCK_UN ) {
194+ flock . l_type = LockType . F_UNLCK ;
195+ } else if ( ( cmd & LOCK_SH ) != 0 ) {
196+ flock . l_type = LockType . F_RDLCK ;
197+ } else if ( ( cmd & LOCK_EX ) != 0 ) {
198+ flock . l_type = LockType . F_WRLCK ;
199+ } else {
200+ throw PythonOps . ValueError ( "unrecognized lockf argument" ) ;
201+ }
202+
203+ int result ;
204+ Errno errno ;
205+ do {
206+ result = Syscall . fcntl ( fd , ( cmd & LOCK_NB ) != 0 ? FcntlCommand . F_SETLK : FcntlCommand . F_SETLKW , ref flock ) ;
207+ } while ( UnixMarshal . ShouldRetrySyscall ( result , out errno ) ) ;
208+
209+ if ( result == - 1 ) {
210+ return LightExceptions . Throw ( PythonNT . GetOsError ( NativeConvert . FromErrno ( errno ) ) ) ;
211+ }
212+ return null ;
213+ }
214+
215+
216+ [ LightThrowing ]
217+ public static object ? lockf ( CodeContext context , object ? fd , int cmd , long len = 0 , long start = 0 , int whence = 0 )
218+ => lockf ( GetFileDescriptor ( context , fd ) , cmd , len , start , whence ) ;
219+
220+ #endregion
221+
222+
223+ #region Helper Methods
224+
225+ private static int GetFileDescriptor ( CodeContext context , object ? obj ) {
226+ if ( ! PythonOps . TryGetBoundAttr ( context , obj , "fileno" , out object ? filenoMeth ) ) {
227+ throw PythonOps . TypeError ( "argument must be an int, or have a fileno() method." ) ;
228+ }
229+ return PythonCalls . Call ( context , filenoMeth ) switch {
230+ int i => i ,
231+ uint ui => ( int ) ui ,
232+ BigInteger bi => ( int ) bi ,
233+ Extensible < BigInteger > ebi => ( int ) ebi . Value ,
234+ _ => throw PythonOps . TypeError ( "fileno() returned a non-integer" )
235+ } ;
236+ }
237+
238+
239+ private static void CheckFileDescriptor ( int fd ) {
240+ if ( fd < 0 ) {
241+ throw PythonOps . ValueError ( "file descriptor cannot be a negative integer ({0})" , fd ) ;
242+ }
243+ }
244+
245+ #endregion
246+
56247
57248 // FD Flags
58249 public static int FD_CLOEXEC = 1 ;
59- public static int FASYNC => RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ? 0x0040 : 0x2000 ;
250+
251+ // O_* flags under F* name
252+ public static int FASYNC => RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ? 0x0040 : 0x2000 ; // O_ASYNC
60253
61254
62255 #region Generated FD Commands
0 commit comments