Skip to content

Commit 72c4a09

Browse files
authored
Implement fcntl constants (#1873)
* Implement fcntl FD commands * Add fcntl.__doc__ * Define DN Flags * Define LOCK Flags * Remove commands unsupported by Mono.Unix * Remove commands unsupported by Mono.Unix on Linux
1 parent 1a90cb4 commit 72c4a09

File tree

3 files changed

+305
-16
lines changed

3 files changed

+305
-16
lines changed

Src/IronPython.Modules/fcntl.cs

Lines changed: 222 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,225 @@
1-
using IronPython.Runtime;
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System;
8+
using System.Runtime.InteropServices;
9+
using System.Runtime.Versioning;
10+
11+
using Mono.Unix.Native;
12+
13+
using IronPython.Runtime;
214

315
[assembly: PythonModule("fcntl", typeof(IronPython.Modules.PythonFcntl), PlatformsAttribute.PlatformFamily.Unix)]
4-
namespace IronPython.Modules {
5-
public static class PythonFcntl {
6-
}
16+
namespace IronPython.Modules;
17+
18+
[SupportedOSPlatform("linux")]
19+
[SupportedOSPlatform("macos")]
20+
public static class PythonFcntl {
21+
22+
public const string __doc__ = """
23+
This module performs file control and I/O control on file descriptors.
24+
It is an interface to the fcntl() and ioctl() Unix routines.
25+
File descriptors can be obtained with the fileno() method of
26+
a file object.
27+
""";
28+
29+
30+
// FD Flags
31+
public static int FD_CLOEXEC = 1;
32+
public static int FASYNC => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0040 : 0x2000;
33+
34+
35+
#region Generated FD Commands
36+
37+
// *** BEGIN GENERATED CODE ***
38+
// generated by function: generate_FD_commands from: generate_os_codes.py
39+
40+
41+
[PythonHidden(PlatformID.MacOSX)]
42+
[SupportedOSPlatform("linux")]
43+
public static int F_ADD_SEALS => 1033;
44+
45+
public static int F_DUPFD => 0;
46+
47+
[PythonHidden(PlatformID.MacOSX)]
48+
[SupportedOSPlatform("linux")]
49+
public static int F_EXLCK => 4;
50+
51+
public static int F_GETFD => 1;
52+
53+
public static int F_GETFL => 3;
54+
55+
[PythonHidden(PlatformID.MacOSX)]
56+
[SupportedOSPlatform("linux")]
57+
public static int F_GETLEASE => 1025;
58+
59+
public static int F_GETLK => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 7 : 5;
60+
61+
[PythonHidden(PlatformID.MacOSX)]
62+
[SupportedOSPlatform("linux")]
63+
public static int F_GETLK64 => 5;
64+
65+
public static int F_GETOWN => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 5 : 9;
66+
67+
[PythonHidden(PlatformID.MacOSX)]
68+
[SupportedOSPlatform("linux")]
69+
public static int F_GETSIG => 11;
70+
71+
[PythonHidden(PlatformID.MacOSX)]
72+
[SupportedOSPlatform("linux")]
73+
public static int F_GET_SEALS => 1034;
74+
75+
[PythonHidden(PlatformID.Unix)]
76+
[SupportedOSPlatform("macos")]
77+
public static int F_NOCACHE => 48;
78+
79+
[PythonHidden(PlatformID.MacOSX)]
80+
[SupportedOSPlatform("linux")]
81+
public static int F_NOTIFY => 1026;
82+
83+
[PythonHidden(PlatformID.MacOSX)]
84+
[SupportedOSPlatform("linux")]
85+
public static int F_OFD_GETLK => 36;
86+
87+
[PythonHidden(PlatformID.MacOSX)]
88+
[SupportedOSPlatform("linux")]
89+
public static int F_OFD_SETLK => 37;
90+
91+
[PythonHidden(PlatformID.MacOSX)]
92+
[SupportedOSPlatform("linux")]
93+
public static int F_OFD_SETLKW => 38;
94+
95+
public static int F_RDLCK => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 1 : 0;
96+
97+
[PythonHidden(PlatformID.MacOSX)]
98+
[SupportedOSPlatform("linux")]
99+
public static int F_SEAL_GROW => 4;
100+
101+
[PythonHidden(PlatformID.MacOSX)]
102+
[SupportedOSPlatform("linux")]
103+
public static int F_SEAL_SEAL => 1;
104+
105+
[PythonHidden(PlatformID.MacOSX)]
106+
[SupportedOSPlatform("linux")]
107+
public static int F_SEAL_SHRINK => 2;
108+
109+
[PythonHidden(PlatformID.MacOSX)]
110+
[SupportedOSPlatform("linux")]
111+
public static int F_SEAL_WRITE => 8;
112+
113+
public static int F_SETFD => 2;
114+
115+
public static int F_SETFL => 4;
116+
117+
[PythonHidden(PlatformID.MacOSX)]
118+
[SupportedOSPlatform("linux")]
119+
public static int F_SETLEASE => 1024;
120+
121+
public static int F_SETLK => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 8 : 6;
122+
123+
[PythonHidden(PlatformID.MacOSX)]
124+
[SupportedOSPlatform("linux")]
125+
public static int F_SETLK64 => 6;
126+
127+
public static int F_SETLKW => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 9 : 7;
128+
129+
[PythonHidden(PlatformID.MacOSX)]
130+
[SupportedOSPlatform("linux")]
131+
public static int F_SETLKW64 => 7;
132+
133+
public static int F_SETOWN => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 6 : 8;
134+
135+
[PythonHidden(PlatformID.MacOSX)]
136+
[SupportedOSPlatform("linux")]
137+
public static int F_SETSIG => 10;
138+
139+
[PythonHidden(PlatformID.MacOSX)]
140+
[SupportedOSPlatform("linux")]
141+
public static int F_SHLCK => 8;
142+
143+
public static int F_UNLCK => 2;
144+
145+
public static int F_WRLCK => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 3 : 1;
146+
147+
// *** END GENERATED CODE ***
148+
149+
#endregion
150+
151+
152+
#region Generated LOCK Flags
153+
154+
// *** BEGIN GENERATED CODE ***
155+
// generated by function: generate_LOCK_flags from: generate_os_codes.py
156+
157+
158+
public static int LOCK_EX => 0x2;
159+
160+
[PythonHidden(PlatformID.MacOSX)]
161+
[SupportedOSPlatform("linux")]
162+
public static int LOCK_MAND => 0x20;
163+
164+
public static int LOCK_NB => 0x4;
165+
166+
[PythonHidden(PlatformID.MacOSX)]
167+
[SupportedOSPlatform("linux")]
168+
public static int LOCK_READ => 0x40;
169+
170+
[PythonHidden(PlatformID.MacOSX)]
171+
[SupportedOSPlatform("linux")]
172+
public static int LOCK_RW => 0xc0;
173+
174+
public static int LOCK_SH => 0x1;
175+
176+
public static int LOCK_UN => 0x8;
177+
178+
[PythonHidden(PlatformID.MacOSX)]
179+
[SupportedOSPlatform("linux")]
180+
public static int LOCK_WRITE => 0x80;
181+
182+
// *** END GENERATED CODE ***
183+
184+
#endregion
185+
186+
187+
// Linux only
188+
#region Generated Directory Notify Flags
189+
190+
// *** BEGIN GENERATED CODE ***
191+
// generated by function: generate_DN_flags from: generate_os_codes.py
192+
193+
194+
[PythonHidden(PlatformID.MacOSX)]
195+
[SupportedOSPlatform("linux")]
196+
public static int DN_ACCESS => 0x1;
197+
198+
[PythonHidden(PlatformID.MacOSX)]
199+
[SupportedOSPlatform("linux")]
200+
public static int DN_ATTRIB => 0x20;
201+
202+
[PythonHidden(PlatformID.MacOSX)]
203+
[SupportedOSPlatform("linux")]
204+
public static int DN_CREATE => 0x4;
205+
206+
[PythonHidden(PlatformID.MacOSX)]
207+
[SupportedOSPlatform("linux")]
208+
public static int DN_DELETE => 0x8;
209+
210+
[PythonHidden(PlatformID.MacOSX)]
211+
[SupportedOSPlatform("linux")]
212+
public static int DN_MODIFY => 0x2;
213+
214+
[PythonHidden(PlatformID.MacOSX)]
215+
[SupportedOSPlatform("linux")]
216+
public static long DN_MULTISHOT => 0x80000000;
217+
218+
[PythonHidden(PlatformID.MacOSX)]
219+
[SupportedOSPlatform("linux")]
220+
public static int DN_RENAME => 0x10;
221+
222+
// *** END GENERATED CODE ***
223+
224+
#endregion
7225
}

Src/IronPython.Modules/posix.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,7 @@ internal static int dupUnix(int fd, bool closeOnExec) {
9999
int flags = Syscall.fcntl(fd2, FcntlCommand.F_GETFD);
100100
if (flags == -1) throw GetLastUnixError();
101101

102-
const int FD_CLOEXEC = 1; // TODO: Move to module fcntl
103-
flags |= FD_CLOEXEC;
102+
flags |= PythonFcntl.FD_CLOEXEC;
104103
flags = Syscall.fcntl(fd2, FcntlCommand.F_SETFD, flags);
105104
if (flags == -1) throw GetLastUnixError();
106105
} catch {

Src/Scripts/generate_os_codes.py

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# The .NET Foundation licenses this file to you under the Apache 2.0 License.
33
# See the LICENSE file in the project root for more information.
44

5+
import re
56
from generate import generate
67
from collections import OrderedDict
78

@@ -16,11 +17,13 @@
1617
darwin_aliases = {'EWOULDBLOCK': 'EAGAIN'}
1718
aliases = {**linux_aliases, **darwin_aliases}
1819

20+
1921
def set_value(codeval, name, value, index):
2022
if name not in codeval:
2123
codeval[name] = [None] * len(systems)
2224
codeval[name][index] = value
2325

26+
2427
def collect_errornames():
2528
errorval = {}
2629
for code in errorcode_linux:
@@ -102,6 +105,7 @@ def darwin_code_expr(codes, fmt):
102105
def linux_code_expr(codes, fmt):
103106
return fmt(codes[linux_idx])
104107

108+
105109
common_errno_codes = ['ENOENT', 'E2BIG', 'ENOEXEC', 'EBADF', 'ECHILD', 'EAGAIN', 'ENOMEM', 'EACCES', 'EEXIST', 'EXDEV', 'ENOTDIR', 'EMFILE', 'ENOSPC', 'EPIPE', 'ENOTEMPTY', 'EILSEQ', 'EINVAL']
106110

107111
def generate_common_errno_codes(cw):
@@ -114,6 +118,7 @@ def generate_common_errno_codes(cw):
114118
else:
115119
cw.write(f"internal static int {name} => {value};")
116120

121+
117122
def generate_errno_names(cw):
118123
def is_windows_alias(name):
119124
return "WSA" + name in errorvalues and name in errorvalues and errorvalues["WSA" + name][windows_idx] == errorvalues[name][windows_idx]
@@ -178,13 +183,17 @@ def collect_codes():
178183

179184
O_flagvalues = collect_codes()
180185

181-
def generate_O_flags(cw, flagvalues, access):
182-
for name in flagvalues.keys():
183-
codes = flagvalues[name]
186+
187+
def generate_codes(cw, codeval, access, fmt, unix_only=False):
188+
for name in codeval.keys():
189+
codes = codeval[name]
190+
all_systems = set(systems)
191+
if unix_only:
192+
all_systems.discard(systems[windows_idx])
184193
hidden_on = []
185-
supported_on = set(systems)
194+
supported_on = set(all_systems)
186195
cw.writeline()
187-
if codes[windows_idx] is None:
196+
if codes[windows_idx] is None and not unix_only:
188197
hidden_on = ["PlatformsAttribute.PlatformFamily.Windows"]
189198
supported_on.discard(systems[windows_idx])
190199
if codes[linux_idx] is None and codes[darwin_idx] is None:
@@ -201,20 +210,80 @@ def generate_O_flags(cw, flagvalues, access):
201210
supported_on.discard(systems[darwin_idx])
202211
if hidden_on and (access == 'public' or access == 'protected' or access == 'protected internal'):
203212
cw.write(f"[PythonHidden({', '.join(hidden_on)})]")
204-
if len(supported_on) != len(systems):
213+
if len(supported_on) != len(all_systems):
205214
for s in sorted(supported_on):
206215
cw.write(f'[SupportedOSPlatform("{s}")]')
207216

208-
value = windows_code_expr(codes, fmt=hex)
209-
cw.write(f"{access} static int {name} => {value};")
217+
value = windows_code_expr(codes, fmt)
218+
typ = "int"
219+
if (all(c.isdigit() for c in value) or re.match(r'^0x[0-9a-fA-F]+$', value)):
220+
n = eval(value)
221+
if n > 2**31 - 1 or n < -2**31:
222+
typ = "long"
223+
cw.write(f"{access} static {typ} {name} => {value};")
224+
210225

211226
def generate_all_O_flags(cw):
212-
generate_O_flags(cw, O_flagvalues, 'public')
227+
generate_codes(cw, O_flagvalues, 'public', hex)
228+
213229

214230
common_O_flags = ['O_RDONLY', 'O_WRONLY', 'O_RDWR', 'O_APPEND', 'O_CREAT', 'O_TRUNC', 'O_EXCL', 'O_CLOEXEC', 'O_BINARY', 'O_NOINHERIT']
215231

216232
def generate_common_O_flags(cw):
217-
generate_O_flags(cw, OrderedDict((f, O_flagvalues[f]) for f in common_O_flags), 'private')
233+
generate_codes(cw, OrderedDict((f, O_flagvalues[f]) for f in common_O_flags), 'private', hex)
234+
235+
236+
# python3 -c 'import fcntl;print(dict(sorted((s, getattr(fcntl, s)) for s in dir(fcntl) if s.startswith("F_"))))'
237+
# Python 3.6.15 [GCC 12.2.0] on linux 6.10.14
238+
FD_commands_linux = {'F_ADD_SEALS': 1033, 'F_DUPFD': 0, 'F_DUPFD_CLOEXEC': 1030, 'F_EXLCK': 4, 'F_GETFD': 1, 'F_GETFL': 3, 'F_GETLEASE': 1025, 'F_GETLK': 5, 'F_GETLK64': 5, 'F_GETOWN': 9, 'F_GETPIPE_SZ': 1032, 'F_GETSIG': 11, 'F_GET_SEALS': 1034, 'F_NOTIFY': 1026, 'F_OFD_GETLK': 36, 'F_OFD_SETLK': 37, 'F_OFD_SETLKW': 38, 'F_RDLCK': 0, 'F_SEAL_GROW': 4, 'F_SEAL_SEAL': 1, 'F_SEAL_SHRINK': 2, 'F_SEAL_WRITE': 8, 'F_SETFD': 2, 'F_SETFL': 4, 'F_SETLEASE': 1024, 'F_SETLK': 6, 'F_SETLK64': 6, 'F_SETLKW': 7, 'F_SETLKW64': 7, 'F_SETOWN': 8, 'F_SETPIPE_SZ': 1031, 'F_SETSIG': 10, 'F_SHLCK': 8, 'F_UNLCK': 2, 'F_WRLCK': 1}
239+
# Unsupported by Mono.Unix 7.1.0-final.1.21458.1 on linux
240+
FD_commands_linux_unsupported = ['F_DUPFD_CLOEXEC', 'F_GETPIPE_SZ', 'F_SETPIPE_SZ']
241+
# Python 3.7.0 [Clang 4.0.1 ] on darwin 24.2.0
242+
FD_commands_darwin = {'F_DUPFD': 0, 'F_DUPFD_CLOEXEC': 67, 'F_FULLFSYNC': 51, 'F_GETFD': 1, 'F_GETFL': 3, 'F_GETLK': 7, 'F_GETOWN': 5, 'F_NOCACHE': 48, 'F_RDLCK': 1, 'F_SETFD': 2, 'F_SETFL': 4, 'F_SETLK': 8, 'F_SETLKW': 9, 'F_SETOWN': 6, 'F_UNLCK': 2, 'F_WRLCK': 3}
243+
# Unsupported by Mono.Unix 7.1.0-final.1.21458.1 on darwin
244+
FD_commands_darwin_unsupported = ['F_DUPFD_CLOEXEC', 'F_FULLFSYNC']
245+
246+
def generate_FD_commands(cw):
247+
codeval = {}
248+
for name in FD_commands_linux:
249+
if name not in FD_commands_linux_unsupported:
250+
set_value(codeval, name, FD_commands_linux[name], linux_idx)
251+
for name in FD_commands_darwin:
252+
if name not in FD_commands_darwin_unsupported:
253+
set_value(codeval, name, FD_commands_darwin[name], darwin_idx)
254+
codeval = OrderedDict(sorted(codeval.items()))
255+
generate_codes(cw, codeval, 'public', str, unix_only=True)
256+
257+
258+
# python3 -c 'import fcntl;print(dict(sorted((s, getattr(fcntl, s)) for s in dir(fcntl) if s.startswith("DN_"))))'
259+
# Python 3.6.15 [GCC 12.2.0] on linux 6.10.14
260+
# Python 3.12.3 [GCC 13.2.0] on linux 6.8.0
261+
DN_flags_linux = {'DN_ACCESS': 1, 'DN_ATTRIB': 32, 'DN_CREATE': 4, 'DN_DELETE': 8, 'DN_MODIFY': 2, 'DN_MULTISHOT': 2147483648, 'DN_RENAME': 16}
262+
263+
def generate_DN_flags(cw):
264+
codeval = {}
265+
for name in DN_flags_linux:
266+
set_value(codeval, name, DN_flags_linux[name], linux_idx)
267+
codeval = OrderedDict(sorted(codeval.items()))
268+
generate_codes(cw, codeval, 'public', hex, unix_only=True)
269+
270+
271+
# python3 -c 'import fcntl;print(dict(sorted((s, getattr(fcntl, s)) for s in dir(fcntl) if s.startswith("LOCK_"))))'
272+
# Python 3.6.15 [GCC 12.2.0] on linux 6.10.14
273+
# Python 3.12.3 [GCC 13.2.0] on linux 6.8.0
274+
LOCK_flags_linux = {'LOCK_EX': 2, 'LOCK_MAND': 32, 'LOCK_NB': 4, 'LOCK_READ': 64, 'LOCK_RW': 192, 'LOCK_SH': 1, 'LOCK_UN': 8, 'LOCK_WRITE': 128}
275+
# Python 3.7.0 [Clang 4.0.1 ] on darwin 24.2.0
276+
# Python 3.12.0 [Clang 14.0.6 ] on darwin 24.2.0
277+
LOCK_flags_darwin = {'LOCK_EX': 2, 'LOCK_NB': 4, 'LOCK_SH': 1, 'LOCK_UN': 8}
278+
279+
def generate_LOCK_flags(cw):
280+
codeval = {}
281+
for name in LOCK_flags_linux:
282+
set_value(codeval, name, LOCK_flags_linux[name], linux_idx)
283+
for name in LOCK_flags_darwin:
284+
set_value(codeval, name, LOCK_flags_darwin[name], darwin_idx)
285+
codeval = OrderedDict(sorted(codeval.items()))
286+
generate_codes(cw, codeval, 'public', hex, unix_only=True)
218287

219288

220289
def main():
@@ -224,6 +293,9 @@ def main():
224293
("Errno Names", generate_errno_names),
225294
("O_Flags", generate_all_O_flags),
226295
("Common O_Flags", generate_common_O_flags),
296+
("FD Commands", generate_FD_commands),
297+
("Directory Notify Flags", generate_DN_flags),
298+
("LOCK Flags", generate_LOCK_flags),
227299
)
228300

229301
if __name__ == "__main__":

0 commit comments

Comments
 (0)