Skip to content

Commit c360095

Browse files
committed
Complete O_flags used for file open
1 parent 8aaa27b commit c360095

File tree

4 files changed

+78
-18
lines changed

4 files changed

+78
-18
lines changed

Src/IronPython/Modules/_fileio.cs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ public FileIO(CodeContext/*!*/ context, string name, string mode = "r", bool clo
152152
}
153153

154154
if (!_context.FileManager.TryGetStreams(fd, out _streams)) {
155+
// TODO: This is not necessarily an error on Posix.
156+
// The descriptor could have been opened by a different means than os.open.
157+
// In such case:
158+
// _streams = new(new UnixStream(fd, ownsHandle: true))
159+
// _context.FileManager.Add(fd, _streams);
155160
throw PythonOps.OSError(PythonFileManager.EBADF, "Bad file descriptor");
156161
}
157162
} else {
@@ -163,34 +168,40 @@ public FileIO(CodeContext/*!*/ context, string name, string mode = "r", bool clo
163168
}
164169

165170
private static string NormalizeMode(string mode, out int flags) {
171+
flags = 0;
172+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
173+
flags |= O_NOINHERIT | O_BINARY;
174+
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
175+
flags |= O_CLOEXEC;
176+
}
166177
switch (StandardizeMode(mode)) {
167178
case "r":
168-
flags = O_RDONLY;
179+
flags |= O_RDONLY;
169180
return "rb";
170181
case "w":
171-
flags = O_CREAT | O_TRUNC | O_WRONLY;
182+
flags |= O_CREAT | O_TRUNC | O_WRONLY;
172183
return "wb";
173184
case "a":
174-
flags = O_APPEND | O_CREAT;
185+
flags |= O_APPEND | O_CREAT | O_WRONLY;
175186
return "ab";
176187
case "x":
177-
flags = O_CREAT | O_EXCL;
188+
flags |= O_CREAT | O_EXCL | O_WRONLY;
178189
return "xb";
179190
case "r+":
180191
case "+r":
181-
flags = O_RDWR;
192+
flags |= O_RDWR;
182193
return "rb+";
183194
case "w+":
184195
case "+w":
185-
flags = O_CREAT | O_TRUNC | O_RDWR;
196+
flags |= O_CREAT | O_TRUNC | O_RDWR;
186197
return "wb+";
187198
case "a+":
188199
case "+a":
189-
flags = O_APPEND | O_CREAT | O_RDWR;
200+
flags |= O_APPEND | O_CREAT | O_RDWR;
190201
return "ab+";
191202
case "x+":
192203
case "+x":
193-
flags = O_CREAT | O_RDWR | O_EXCL;
204+
flags |= O_CREAT | O_EXCL | O_RDWR;
194205
return "xb+";
195206
default:
196207
throw BadMode(mode);
@@ -225,14 +236,14 @@ static Exception BadMode(string mode) {
225236
case 'a':
226237
case 'x':
227238
if (foundMode) {
228-
return PythonOps.ValueError("Must have exactly one of create/read/write/append mode and at most one plus");
239+
return BadModeException();
229240
} else {
230241
foundMode = true;
231242
continue;
232243
}
233244
case '+':
234245
if (foundPlus) {
235-
return PythonOps.ValueError("Must have exactly one of create/read/write/append mode and at most one plus");
246+
return BadModeException();
236247
} else {
237248
foundPlus = true;
238249
continue;
@@ -245,7 +256,9 @@ static Exception BadMode(string mode) {
245256
}
246257
}
247258

248-
return PythonOps.ValueError("Must have exactly one of create/read/write/append mode and at most one plus");
259+
return BadModeException();
260+
261+
static Exception BadModeException() => PythonOps.ValueError("Must have exactly one of create/read/write/append mode and at most one plus");
249262
}
250263
}
251264

Src/IronPython/Modules/_io.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
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

@@ -12,6 +12,7 @@
1212
using System.Reflection;
1313
using System.Runtime.CompilerServices;
1414
using System.Runtime.InteropServices;
15+
using System.Runtime.Versioning;
1516
using System.Text;
1617

1718
using Microsoft.Scripting.Runtime;
@@ -54,6 +55,19 @@ public static partial class PythonIOModule {
5455

5556
private static int O_EXCL => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 0x400 : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x800 : 0x80;
5657

58+
[PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
59+
[SupportedOSPlatform("linux")]
60+
[SupportedOSPlatform("macos")]
61+
private static int O_CLOEXEC => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x1000000 : 0x80000;
62+
63+
[PythonHidden(PlatformsAttribute.PlatformFamily.Unix)]
64+
[SupportedOSPlatform("windows")]
65+
private static int O_BINARY => 0x8000;
66+
67+
[PythonHidden(PlatformsAttribute.PlatformFamily.Unix)]
68+
[SupportedOSPlatform("windows")]
69+
private static int O_NOINHERIT => 0x80;
70+
5771
// *** END GENERATED CODE ***
5872

5973
#endregion

Src/Scripts/generate_os_codes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ def generate_O_flags(cw, flagvalues, access):
211211
def generate_all_O_flags(cw):
212212
generate_O_flags(cw, O_flagvalues, 'public')
213213

214-
common_O_flags = ['O_RDONLY', 'O_WRONLY', 'O_RDWR', 'O_APPEND', 'O_CREAT', 'O_TRUNC', 'O_EXCL']
214+
common_O_flags = ['O_RDONLY', 'O_WRONLY', 'O_RDWR', 'O_APPEND', 'O_CREAT', 'O_TRUNC', 'O_EXCL', 'O_CLOEXEC', 'O_BINARY', 'O_NOINHERIT']
215215

216216
def generate_common_O_flags(cw):
217217
generate_O_flags(cw, OrderedDict((f, O_flagvalues[f]) for f in common_O_flags), 'private')

Tests/test_file.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
CP16623_LOCK = _thread.allocate_lock()
1111

12-
from iptest import IronPythonTestCase, is_cli, is_cpython, is_netcoreapp, is_posix, run_test, skipUnlessIronPython
12+
from iptest import IronPythonTestCase, is_cli, is_cpython, is_netcoreapp, is_posix, is_linux, is_osx, is_windows, run_test, skipUnlessIronPython
1313

1414
class FileTest(IronPythonTestCase):
1515

@@ -505,12 +505,12 @@ def test_file_manager_leak(self):
505505
# the number of iterations should be larger than Microsoft.Scripting.Utils.HybridMapping.SIZE (currently 4K)
506506
N = 5000
507507
for i in range(N):
508-
fd = os.open(self.temp_file, os.O_WRONLY | os.O_CREAT)
508+
fd = os.open(self.temp_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
509509
f = os.fdopen(fd, 'w', closefd=True)
510510
f.close()
511511

512512
for i in range(N):
513-
fd = os.open(self.temp_file, os.O_WRONLY | os.O_CREAT)
513+
fd = os.open(self.temp_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
514514
f = os.fdopen(fd, 'w', closefd=False)
515515
g = os.fdopen(f.fileno(), 'w', closefd=True)
516516
g.close()
@@ -624,7 +624,7 @@ def test_modes(self):
624624
self.assertRaisesMessage(ValueError, "invalid mode: 'p'", open, 'abc', 'p')
625625

626626
# allow anything w/ U but r and w
627-
err_msg = "mode U cannot be combined with 'x', 'w', 'a', or '+'" if is_cli or sys.version_info >= (3,7) else "mode U cannot be combined with x', 'w', 'a', or '+'" if sys.version_info >= (3,6) else "can't use U and writing mode at once"
627+
err_msg = "mode U cannot be combined with 'x', 'w', 'a', or '+'" if is_cli or sys.version_info >= (3,7,4) else "mode U cannot be combined with x', 'w', 'a', or '+'" if sys.version_info >= (3,6) else "can't use U and writing mode at once"
628628
self.assertRaisesMessage(ValueError, err_msg, open, 'abc', 'Uw')
629629
self.assertRaisesMessage(ValueError, err_msg, open, 'abc', 'Ua')
630630
self.assertRaisesMessage(ValueError, err_msg, open, 'abc', 'Uw+')
@@ -701,7 +701,7 @@ def test_errors(self):
701701

702702
with self.assertRaises(OSError) as cm:
703703
open('path_too_long' * 100)
704-
self.assertEqual(cm.exception.errno, (36 if is_posix else 22) if is_netcoreapp and not is_posix or sys.version_info >= (3,6) else 2)
704+
self.assertEqual(cm.exception.errno, (63 if is_osx else 36 if is_linux else 22) if is_netcoreapp and not is_posix or sys.version_info >= (3,6) else 2)
705705

706706
def test_write_bytes(self):
707707
fname = self.temp_file
@@ -756,6 +756,39 @@ def test_open_with_BOM(self):
756756
with open(fileName, "rb") as f:
757757
self.assertEqual(f.read(), b"\xef\xbb\xbf\x42\xc3\x93\x4d\x0d\x0a")
758758

759+
760+
def test_open_flags(self):
761+
test_data = {
762+
'rb': os.O_RDONLY,
763+
'wb': os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
764+
'ab': os.O_WRONLY | os.O_CREAT | os.O_APPEND,
765+
'xb': os.O_WRONLY | os.O_CREAT | os.O_EXCL,
766+
'rb+': os.O_RDWR,
767+
'wb+': os.O_RDWR | os.O_CREAT | os.O_TRUNC,
768+
'ab+': os.O_RDWR | os.O_CREAT | os.O_APPEND,
769+
'xb+': os.O_RDWR | os.O_CREAT | os.O_EXCL,
770+
}
771+
extra_flags = 0
772+
if is_posix:
773+
extra_flags |= os.O_CLOEXEC
774+
elif is_windows:
775+
extra_flags |= os.O_NOINHERIT | os.O_BINARY
776+
test_data = {k: v | extra_flags for k, v in test_data.items()}
777+
778+
flags_received = None
779+
def test_open(name, flags):
780+
nonlocal flags_received
781+
flags_received = flags
782+
if mode[0] == 'x':
783+
os.unlink(name)
784+
return os.open(name, flags)
785+
786+
for mode in sorted(test_data):
787+
with self.subTest(mode=mode):
788+
with open(self.temp_file, mode, opener=test_open): pass
789+
self.assertEqual(flags_received, test_data[mode])
790+
791+
759792
def test_opener(self):
760793
data = "test message\n"
761794
with open(self.temp_file, "w", opener=os.open) as f:

0 commit comments

Comments
 (0)