Skip to content

Commit fbc37f4

Browse files
authored
Improve handling of NUL file on Windows (#1712)
* Improve handling of NUL file on Windows * Fix test using os.devnull
1 parent 8a2911a commit fbc37f4

3 files changed

Lines changed: 42 additions & 13 deletions

File tree

Src/IronPython.Modules/nt.cs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -437,15 +437,25 @@ public static object fspath(CodeContext context, [AllowNull] object path)
437437
[LightThrowing]
438438
public static object fstat(CodeContext/*!*/ context, int fd) {
439439
PythonContext pythonContext = context.LanguageContext;
440-
if (pythonContext.FileManager.TryGetFileFromId(pythonContext, fd, out PythonIOModule.FileIO file)) {
440+
pythonContext.FileManager.TryGetObjectFromId(pythonContext, fd, out object obj);
441+
if (obj is PythonIOModule.FileIO file) {
441442
if (file.IsConsole) return new stat_result(8192);
442-
if (file._readStream is PipeStream) return new stat_result(4096);
443-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
444-
if (IsUnixStream(file._readStream)) return new stat_result(4096);
445-
}
443+
if (StatStream(file._readStream) is not null and var res) return res;
446444
if (file.name is string strName) return lstat(strName, new Dictionary<string, object>(1));
445+
} else if (obj is Stream stream && StatStream(stream) is not null and var res) {
446+
return res;
447+
}
448+
return LightExceptions.Throw(PythonOps.OSError(9, "Bad file descriptor"));
449+
450+
static stat_result? StatStream(Stream stream) {
451+
if (stream is PipeStream) return new stat_result(4096);
452+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
453+
if (ReferenceEquals(stream, Stream.Null)) return new stat_result(8192);
454+
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
455+
if (IsUnixStream(stream)) return new stat_result(4096);
456+
}
457+
return null;
447458
}
448-
throw PythonOps.OSError(9, "Bad file descriptor");
449459

450460
static bool IsUnixStream(Stream stream) {
451461
return stream is Mono.Unix.UnixStream;
@@ -843,8 +853,7 @@ public static object open(CodeContext/*!*/ context, [NotNone] string path, int f
843853
FileAccess access = FileAccessFromFlags(flags);
844854
FileOptions options = FileOptionsFromFlags(flags);
845855
Stream fs;
846-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && string.Equals(path, "nul", StringComparison.OrdinalIgnoreCase)
847-
|| (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) && path == "/dev/null") {
856+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && IsNulFile(path)) {
848857
fs = Stream.Null;
849858
} else if (access == FileAccess.Read && (fileMode == FileMode.CreateNew || fileMode == FileMode.Create || fileMode == FileMode.Append)) {
850859
// .NET doesn't allow Create/CreateNew w/ access == Read, so create the file, then close it, then
@@ -1427,7 +1436,9 @@ public static object stat([NotNone] string path, [ParamDictionary, NotNone] IDic
14271436
int mode = 0;
14281437
long size;
14291438

1430-
if (Directory.Exists(path)) {
1439+
if (IsNulFile(path)) {
1440+
return new stat_result(0x2000);
1441+
} else if (Directory.Exists(path)) {
14311442
size = 0;
14321443
mode = 0x4000 | S_IEXEC;
14331444
} else if (File.Exists(path)) {
@@ -2337,6 +2348,13 @@ private static void VerifyPath(string path, string functionName, string argName)
23372348
if (path.IndexOf((char)0) != -1) throw PythonOps.ValueError($"{functionName}: embedded null character in {argName}");
23382349
}
23392350

2351+
[SupportedOSPlatform("windows")]
2352+
private static bool IsNulFile(string path)
2353+
=> path.StartsWith("nul", StringComparison.OrdinalIgnoreCase)
2354+
&& (path.Length == 3
2355+
|| path.Length == 4 && path[3] == ':'
2356+
|| path.Length == 5 && path[3] == ':' && path[4] == ':');
2357+
23402358
#endregion
23412359
}
23422360
}

Tests/modules/io_related/test_fd.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,7 @@ def test_dup2(self):
7171
self.assertTrue(is_open(fd + 2))
7272

7373
# Verify that dup2 closes the previous occupant of a fd.
74-
if is_cli:
75-
self.assertEqual(os.open(os.devnull, os.O_WRONLY, 0o600), fd + 1)
76-
else:
77-
self.assertEqual(os.open(os.devnull, os.O_RDWR, 0o600), fd + 1)
74+
self.assertEqual(os.open(os.devnull, os.O_RDWR, 0o600), fd + 1)
7875
self.assertEqual(os.dup2(fd + 1, fd), fd if is_cli or sys.version_info >= (3,7) else None)
7976
# null can not be stated on windows - but writes are ok
8077
self.assertTrue(is_open_nul(fd))

Tests/modules/system_related/test_nt.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ def test_stat(self):
6464
else:
6565
self.assertRaisesNumber(WindowsError, 22, nt.stat, 'bad?path.txt')
6666

67+
def test_stat_nul(self):
68+
st_arg = [0] * nt.stat_result.n_sequence_fields
69+
st_arg[0] = 0x2000
70+
st_res = nt.stat_result(st_arg)
71+
for name in ["nul", "nul:", "nul::", "NUL", "NUL:", "NUL::"]:
72+
with self.subTest(name=name):
73+
self.assertEqual(nt.stat(name), st_res)
74+
self.assertEqual(nt.lstat(name), st_res)
75+
fd = nt.open(name, os.O_RDWR)
76+
self.assertEqual(nt.fstat(fd), st_res)
77+
nt.close(fd)
78+
self.assertRaises(WindowsError, nt.stat, "nul:::")
79+
80+
6781
# stat should accept bytes as argument
6882
def test_stat_cp34910(self):
6983
self.assertEqual(nt.stat('/'), nt.stat(b'/'))

0 commit comments

Comments
 (0)