Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 92 additions & 77 deletions src/core/IronPython.Modules/nt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,8 @@ public static bool access(CodeContext context, object? path, int mode, [ParamDic
#if FEATURE_FILESYSTEM

public static void chdir([NotNone] string path) {
if (String.IsNullOrEmpty(path)) {
throw PythonOps.OSError(PythonExceptions._OSError.ERROR_INVALID_NAME, "Path cannot be an empty string", path, PythonExceptions._OSError.ERROR_INVALID_NAME);
if (string.IsNullOrEmpty(path)) {
throw GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_INVALID_NAME, path);
}

try {
Expand Down Expand Up @@ -553,7 +553,7 @@ public static PythonList listdir(CodeContext/*!*/ context, string? path = null)
}

if (path == string.Empty) {
throw PythonOps.OSError(PythonExceptions._OSError.ERROR_PATH_NOT_FOUND, "The system cannot find the path specified", path, PythonExceptions._OSError.ERROR_PATH_NOT_FOUND);
throw GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_PATH_NOT_FOUND, path);
}

#if !NETFRAMEWORK
Expand Down Expand Up @@ -628,7 +628,7 @@ internal DirEntry(CodeContext context, FileSystemInfo info, bool asBytes) {

[LightThrowing]
public object? inode() {
var obj = stat(follow_symlinks: false);
var obj = PythonNT.stat(info.FullName, new Dictionary<string, object>());
if (obj is stat_result res) return res.st_ino;
return obj;
}
Expand All @@ -640,9 +640,15 @@ internal DirEntry(CodeContext context, FileSystemInfo info, bool asBytes) {
public bool is_symlink() => info.Attributes.HasFlag(FileAttributes.ReparsePoint) ? throw new NotImplementedException() : false;

[LightThrowing]
public object? stat(bool follow_symlinks = true) => PythonNT.stat(info.FullName, new Dictionary<string, object>());
public object? stat(bool follow_symlinks = true) {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return statWindowsImpl(info);
return PythonNT.stat(info.FullName, new Dictionary<string, object>());
}

public string __repr__(CodeContext context) => $"<DirEntry {PythonOps.Repr(context, name)}>";

public object __fspath__(CodeContext context) => path;
}

[PythonType, PythonHidden]
Expand Down Expand Up @@ -675,6 +681,8 @@ internal ScandirIterator(CodeContext context, IEnumerable<FileSystemInfo> list,

[PythonHidden]
public void Reset() => enumerator.Reset();

public void close() => Dispose();
}

public static ScandirIterator scandir(CodeContext context, string? path = null)
Expand All @@ -689,7 +697,7 @@ private static IEnumerable<FileSystemInfo> ScandirHelper(CodeContext context, st
}

if (path == string.Empty) {
throw PythonOps.OSError(PythonExceptions._OSError.ERROR_PATH_NOT_FOUND, "The system cannot find the path specified", path, PythonExceptions._OSError.ERROR_PATH_NOT_FOUND);
throw GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_PATH_NOT_FOUND, path);
}

#if !NETFRAMEWORK
Expand Down Expand Up @@ -1396,8 +1404,8 @@ public PythonTuple __reduce__() {
}
}

private static bool HasExecutableExtension(string path) {
string extension = Path.GetExtension(path).ToLower(CultureInfo.InvariantCulture);
private static bool HasExecutableExtension(string extension) {
extension = extension.ToLower(CultureInfo.InvariantCulture);
return (extension == ".exe" || extension == ".dll" || extension == ".com" || extension == ".bat");
}

Expand Down Expand Up @@ -1456,50 +1464,22 @@ public static object stat([NotNone] string path, [ParamDictionary] IDictionary<s
VerifyPath(path, functionName: nameof(stat), argName: nameof(path));

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
if (IsNulFile(path)) {
return new stat_result(0x2000);
}

try {
FileInfo fi = new FileInfo(path);
int mode = 0;
long size;

if (IsNulFile(path)) {
return new stat_result(0x2000);
} else if (Directory.Exists(path)) {
size = 0;
mode = 0x4000 | S_IEXEC;
} else if (File.Exists(path)) {
size = fi.Length;
mode = 0x8000;
if (HasExecutableExtension(path)) {
mode |= S_IEXEC;
}
} else {
return LightExceptions.Throw(PythonOps.OSError(0, "file does not exist", path, PythonExceptions._OSError.ERROR_PATH_NOT_FOUND));
}

mode |= S_IREAD;
if ((fi.Attributes & FileAttributes.ReadOnly) == 0) {
mode |= S_IWRITE;
if (fi.Exists) {
return statWindowsImpl(fi);
}

const long epochDifferenceLong = 62135596800 * TimeSpan.TicksPerSecond;

// 1 tick = 100 nanoseconds
long st_atime_ns = (fi.LastAccessTime.ToUniversalTime().Ticks - epochDifferenceLong) * 100;
long st_mtime_ns = (fi.LastWriteTime.ToUniversalTime().Ticks - epochDifferenceLong) * 100;
long st_ctime_ns = (fi.CreationTime.ToUniversalTime().Ticks - epochDifferenceLong) * 100;

ulong fileIdx = 0;
var handle = CreateFile(path, FILE_READ_ATTRIBUTES, 0, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, IntPtr.Zero);
if (!handle.IsInvalid) {
if (GetFileInformationByHandle(handle, out BY_HANDLE_FILE_INFORMATION fileInfo)) {
fileIdx = (((ulong)fileInfo.FileIndexHigh) << 32) + fileInfo.FileIndexLow;
}
handle.Close();
DirectoryInfo di = new DirectoryInfo(path);
if (di.Exists) {
return statWindowsImpl(di);
}

return new stat_result(mode, fileIdx, size, st_atime_ns, st_mtime_ns, st_ctime_ns);
return LightExceptions.Throw(GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_FILE_NOT_FOUND, path));
} catch (ArgumentException) {
return LightExceptions.Throw(PythonOps.OSError(0, "The path is invalid", path, PythonExceptions._OSError.ERROR_INVALID_NAME));
return LightExceptions.Throw(GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_INVALID_NAME, path));
} catch (Exception e) {
return LightExceptions.Throw(ToPythonException(e, path));
}
Expand All @@ -1510,6 +1490,46 @@ public static object stat([NotNone] string path, [ParamDictionary] IDictionary<s
}
}

[SupportedOSPlatform("windows")]
private static stat_result statWindowsImpl(FileSystemInfo fsi) {
int mode = 0;
long size;
if (fsi is FileInfo fi) {
size = fi.Length;
mode = 0x8000;
if (HasExecutableExtension(fi.Extension)) {
mode |= S_IEXEC;
}
} else {
Debug.Assert(fsi is DirectoryInfo);
size = 0;
mode = 0x4000 | S_IEXEC;
}

mode |= S_IREAD;
if ((fsi.Attributes & FileAttributes.ReadOnly) == 0) {
mode |= S_IWRITE;
}

const long epochDifferenceLong = 62135596800 * TimeSpan.TicksPerSecond;

// 1 tick = 100 nanoseconds
long st_atime_ns = (fsi.LastAccessTime.ToUniversalTime().Ticks - epochDifferenceLong) * 100;
long st_mtime_ns = (fsi.LastWriteTime.ToUniversalTime().Ticks - epochDifferenceLong) * 100;
long st_ctime_ns = (fsi.CreationTime.ToUniversalTime().Ticks - epochDifferenceLong) * 100;

ulong fileIdx = 0;
var handle = CreateFile(fsi.FullName, FILE_READ_ATTRIBUTES, 0, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, IntPtr.Zero);
if (!handle.IsInvalid) {
if (GetFileInformationByHandle(handle, out BY_HANDLE_FILE_INFORMATION fileInfo)) {
fileIdx = (((ulong)fileInfo.FileIndexHigh) << 32) + fileInfo.FileIndexLow;
}
handle.Close();
}

return new stat_result(mode, fileIdx, size, st_atime_ns, st_mtime_ns, st_ctime_ns);
}

[LightThrowing, Documentation("")]
public static object stat(CodeContext context, [NotNone] Bytes path, [ParamDictionary] IDictionary<string, object> dict)
=> stat(path.ToFsString(context), dict);
Expand Down Expand Up @@ -1663,7 +1683,20 @@ public static void remove([NotNone] string path, [ParamDictionary] IDictionary<s
throw PythonOps.TypeError("'{0}' is an invalid keyword argument for this function", key);
}
}
UnlinkWorker(path);

if (path.IndexOfAny(Path.GetInvalidPathChars()) != -1 || Path.GetFileName(path).IndexOfAny(Path.GetInvalidFileNameChars()) != -1) {
throw GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_INVALID_NAME, path);
}

bool existing = File.Exists(path); // will return false also on access denied
try {
File.Delete(path); // will throw an exception on access denied, no exception on file not existing
} catch (Exception e) {
throw ToPythonException(e, path);
}
if (!existing) { // file was not existing in the first place
throw GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_FILE_NOT_FOUND, path);
}
}

[Documentation("")]
Expand All @@ -1685,24 +1718,6 @@ public static void unlink(CodeContext context, [NotNone] Bytes path, [ParamDicti
[Documentation("")]
public static void unlink(CodeContext context, object? path, [ParamDictionary] IDictionary<string, object> kwargs)
=> unlink(ConvertToFsString(context, path, nameof(path)), kwargs);

private static void UnlinkWorker(string path) {
if (path == null) {
throw new ArgumentNullException(nameof(path));
} else if (path.IndexOfAny(Path.GetInvalidPathChars()) != -1 || Path.GetFileName(path).IndexOfAny(Path.GetInvalidFileNameChars()) != -1) {
throw PythonOps.OSError(PythonExceptions._OSError.ERROR_INVALID_NAME, "The filename, directory name, or volume label syntax is incorrect", path, PythonExceptions._OSError.ERROR_INVALID_NAME);
}

bool existing = File.Exists(path); // will return false also on access denied
try {
File.Delete(path); // will throw an exception on access denied, no exception on file not existing
} catch (Exception e) {
throw ToPythonException(e, path);
}
if (!existing) { // file was not existing in the first place
throw PythonOps.OSError(PythonExceptions._OSError.ERROR_FILE_NOT_FOUND, "The system cannot find the file specified", path, PythonExceptions._OSError.ERROR_FILE_NOT_FOUND);
}
}
#endif

#if FEATURE_PROCESS
Expand Down Expand Up @@ -2127,15 +2142,13 @@ private static Exception ToPythonException(Exception e, string? filename = null)
message = e.Message;
isWindowsError = true;
} else if (e is UnauthorizedAccessException unauth) {
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
GetWin32Error(PythonExceptions._OSError.ERROR_ACCESS_DENIED, filename) :
GetOsError(PythonErrno.EACCES, filename);
return GetOsOrWinError(PythonErrno.EACCES, PythonExceptions._OSError.ERROR_ACCESS_DENIED, filename);
} else {
var ioe = e as IOException;
Exception? pe = IOExceptionToPythonException(ioe, error, filename);
if (pe != null) return pe;

errorCode = System.Runtime.InteropServices.Marshal.GetHRForException(e);
errorCode = Marshal.GetHRForException(e);

if ((errorCode & ~0xfff) == (unchecked((int)0x80070000))) {
// Win32 HR, translate HR to Python error code if possible, otherwise
Expand Down Expand Up @@ -2310,19 +2323,21 @@ private static bool TryGetShellCommand(string command, [NotNullWhen(true)] out s

#endif

private static Exception DirectoryExistsError(string? filename) {
private static Exception DirectoryExistsError(string? filename)
=> GetOsOrWinError(PythonErrno.EEXIST, PythonExceptions._OSError.ERROR_ALREADY_EXISTS, filename);

internal static Exception GetOsError(int errno, string? filename = null, string? filename2 = null)
=> PythonOps.OSError(errno, strerror(errno), filename, null, filename2);

internal static Exception GetOsOrWinError(int errno, int winerror, string? filename = null, string? filename2 = null) {
#if FEATURE_NATIVE
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
return GetWin32Error(PythonExceptions._OSError.ERROR_ALREADY_EXISTS, filename);
return GetWin32Error(winerror, filename, filename2);
}
#endif
return GetOsError(PythonErrno.EEXIST, filename);
return PythonOps.OSError(errno, strerror(errno), filename, null, filename2);
}


internal static Exception GetOsError(int errno, string? filename = null, string? filename2 = null)
=> PythonOps.OSError(errno, strerror(errno), filename, null, filename2);

#if FEATURE_NATIVE || FEATURE_CTYPES

[SupportedOSPlatform("windows")]
Expand Down
2 changes: 1 addition & 1 deletion src/core/IronPython.StdLib
3 changes: 0 additions & 3 deletions tests/IronPython.Tests/Cases/CPythonCasesManifest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -640,9 +640,6 @@ RunCondition=$(IS_POSIX)
Ignore=true
Reason=unittest.case.SkipTest: os.openpty() not available.

[CPython.test_os]
Ignore=true

[CPython.test_ossaudiodev] # Module has been removed in 3.13 - https://github.com/IronLanguages/ironpython3/issues/1352
Ignore=true
Reason=unittest.case.SkipTest: No module named 'ossaudiodev'
Expand Down
Loading
Loading