Skip to content

Commit e6620f1

Browse files
authored
Complete O_flags used for file open (#1853)
* Complete O_flags used for file open * Update after review
1 parent e160000 commit e6620f1

File tree

4 files changed

+77
-19
lines changed

4 files changed

+77
-19
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: 12 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,16 @@ 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+
[SupportedOSPlatform("linux")]
59+
[SupportedOSPlatform("macos")]
60+
private static int O_CLOEXEC => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x1000000 : 0x80000;
61+
62+
[SupportedOSPlatform("windows")]
63+
private static int O_BINARY => 0x8000;
64+
65+
[SupportedOSPlatform("windows")]
66+
private static int O_NOINHERIT => 0x80;
67+
5768
// *** END GENERATED CODE ***
5869

5970
#endregion

Src/Scripts/generate_os_codes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ def generate_O_flags(cw, flagvalues, access):
199199
if codes[darwin_idx] is None:
200200
hidden_on += ["PlatformID.MacOSX"]
201201
supported_on.discard(systems[darwin_idx])
202-
if hidden_on:
202+
if hidden_on and (access == 'public' or access == 'protected' or access == 'protected internal'):
203203
cw.write(f"[PythonHidden({', '.join(hidden_on)})]")
204204
if len(supported_on) != len(systems):
205205
for s in sorted(supported_on):
@@ -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: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
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 errno
56
import os
67
import sys
78
import unittest
89
import _thread
910

1011
CP16623_LOCK = _thread.allocate_lock()
1112

12-
from iptest import IronPythonTestCase, is_cli, is_cpython, is_netcoreapp, is_posix, run_test, skipUnlessIronPython
13+
from iptest import IronPythonTestCase, is_cli, is_cpython, is_netcoreapp, is_posix, is_windows, run_test, skipUnlessIronPython
1314

1415
class FileTest(IronPythonTestCase):
1516

@@ -505,12 +506,12 @@ def test_file_manager_leak(self):
505506
# the number of iterations should be larger than Microsoft.Scripting.Utils.HybridMapping.SIZE (currently 4K)
506507
N = 5000
507508
for i in range(N):
508-
fd = os.open(self.temp_file, os.O_WRONLY | os.O_CREAT)
509+
fd = os.open(self.temp_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
509510
f = os.fdopen(fd, 'w', closefd=True)
510511
f.close()
511512

512513
for i in range(N):
513-
fd = os.open(self.temp_file, os.O_WRONLY | os.O_CREAT)
514+
fd = os.open(self.temp_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
514515
f = os.fdopen(fd, 'w', closefd=False)
515516
g = os.fdopen(f.fileno(), 'w', closefd=True)
516517
g.close()
@@ -624,7 +625,7 @@ def test_modes(self):
624625
self.assertRaisesMessage(ValueError, "invalid mode: 'p'", open, 'abc', 'p')
625626

626627
# 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"
628+
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"
628629
self.assertRaisesMessage(ValueError, err_msg, open, 'abc', 'Uw')
629630
self.assertRaisesMessage(ValueError, err_msg, open, 'abc', 'Ua')
630631
self.assertRaisesMessage(ValueError, err_msg, open, 'abc', 'Uw+')
@@ -701,7 +702,7 @@ def test_errors(self):
701702

702703
with self.assertRaises(OSError) as cm:
703704
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)
705+
self.assertEqual(cm.exception.errno, (errno.ENAMETOOLONG if is_posix else errno.EINVAL) if is_netcoreapp and not is_posix or sys.version_info >= (3,6) else errno.ENOENT)
705706

706707
def test_write_bytes(self):
707708
fname = self.temp_file
@@ -756,6 +757,39 @@ def test_open_with_BOM(self):
756757
with open(fileName, "rb") as f:
757758
self.assertEqual(f.read(), b"\xef\xbb\xbf\x42\xc3\x93\x4d\x0d\x0a")
758759

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

0 commit comments

Comments
 (0)