diff options
author | Matti Picus <matti.picus@gmail.com> | 2021-09-12 09:38:13 +0300 |
---|---|---|
committer | Matti Picus <matti.picus@gmail.com> | 2021-09-12 09:38:13 +0300 |
commit | 415fdd5218ab820991ecfbd98ed1b917eefa1f06 (patch) | |
tree | 45b6d63f2d40addb7050c955ce77dd8a39aca05c | |
parent | merge default into release (diff) | |
parent | mention stdlib 3.7.12 in the release note (diff) | |
download | pypy-415fdd5218ab820991ecfbd98ed1b917eefa1f06.tar.gz pypy-415fdd5218ab820991ecfbd98ed1b917eefa1f06.tar.bz2 pypy-415fdd5218ab820991ecfbd98ed1b917eefa1f06.zip |
merge default into releaserelease-pypy2.7-v7.3.6rc1
-rw-r--r-- | pypy/doc/release-v7.3.6.rst | 3 | ||||
-rw-r--r-- | pypy/doc/whatsnew-head.rst | 5 | ||||
-rw-r--r-- | pypy/module/posix/interp_posix.py | 16 | ||||
-rw-r--r-- | rpython/rlib/rposix_stat.py | 350 | ||||
-rw-r--r-- | rpython/rlib/rwin32file.py | 59 | ||||
-rw-r--r-- | rpython/rlib/test/test_rposix_stat.py | 3 |
6 files changed, 314 insertions, 122 deletions
diff --git a/pypy/doc/release-v7.3.6.rst b/pypy/doc/release-v7.3.6.rst index 8cefb361b6..3e171a7ec8 100644 --- a/pypy/doc/release-v7.3.6.rst +++ b/pypy/doc/release-v7.3.6.rst @@ -19,7 +19,7 @@ three different interpreters: backported security updates) - PyPy3.7, which is an interpreter supporting the syntax and the features of - Python 3.7, including the stdlib for CPython 3.7.10. + Python 3.7, including the stdlib for CPython 3.7.12. - PyPy3.8, which is an interpreter supporting the syntax and the features of Python 3.8, including the stdlib for CPython 3.8.12. Since this is our @@ -208,6 +208,7 @@ Python 3.7+ speedups and enhancements - Check env keys for ``'='`` when calling ``os.execve`` - Add ``_winapi.GetFileType`` and ``FILE_TYPE_*`` values (issue 3531_) - Allow ``ctypes.POINTER()`` to cast `ctypes.array`` (issue 3546_) +- Update the stdlib to v3.7.12 Python 3.7 C-API ~~~~~~~~~~~~~~~~ diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst index 2048f6d3c4..1b3d8abbed 100644 --- a/pypy/doc/whatsnew-head.rst +++ b/pypy/doc/whatsnew-head.rst @@ -5,3 +5,8 @@ What's new in PyPy2.7 7.3.6+ .. this is a revision shortly after release-pypy-7.3.6 .. startrev: 000a308e967f +.. branch: win64-stat + +Add ``st_file_attributes`` and ``st_reparse_tag`` attributes to ``os.stat`` +on windows. Also follow the reparse logic of Python3.8. + diff --git a/pypy/module/posix/interp_posix.py b/pypy/module/posix/interp_posix.py index 6837bf61d5..45aaf60208 100644 --- a/pypy/module/posix/interp_posix.py +++ b/pypy/module/posix/interp_posix.py @@ -835,6 +835,22 @@ def _env2interp(space, w_env): env[space.text0_w(w_key)] = space.text0_w(w_value) return env +def _env2interp(space, w_env): + env = {} + w_keys = space.call_method(w_env, 'keys') + for w_key in space.unpackiterable(w_keys): + w_value = space.getitem(w_env, w_key) + key = space.text0_w(w_key) + val = space.text0_w(w_value) + # Search from index 1 because on Windows starting '=' is allowed for + # defining hidden environment variables + if len(key) == 0 or '=' in key[1:]: + raise oefmt(space.w_ValueError, + "illegal environment variable name") + env[key] = val + return env + + @unwrap_spec(command='fsencode') def execve(space, command, w_args, w_env): """ execve(path, args, env) diff --git a/rpython/rlib/rposix_stat.py b/rpython/rlib/rposix_stat.py index f1e00ffcbc..71f54838e1 100644 --- a/rpython/rlib/rposix_stat.py +++ b/rpython/rlib/rposix_stat.py @@ -21,7 +21,7 @@ from rpython.rlib._os_support import _preferred_traits, string_traits from rpython.rlib.objectmodel import specialize, we_are_translated, not_rpython from rpython.rtyper.lltypesystem import lltype, rffi from rpython.translator.tool.cbuild import ExternalCompilationInfo -from rpython.rlib.rarithmetic import intmask +from rpython.rlib.rarithmetic import widen from rpython.rlib.rposix import ( replace_os_function, handle_posix_error, _as_bytes0) from rpython.rlib import rposix @@ -67,6 +67,10 @@ ALL_STAT_FIELDS = [ ("nsec_mtime", lltype.Signed), # ("nsec_ctime", lltype.Signed), # ] +if sys.platform == 'win32': + ALL_STAT_FIELDS.append(("st_file_attributes", lltype.Signed)) + ALL_STAT_FIELDS.append(("st_reparse_tag", lltype.Signed)) + N_INDEXABLE_FIELDS = 10 # For OO backends, expose only the portable fields (the first 10). @@ -146,27 +150,53 @@ class SomeStatResult(annmodel.SomeObject): TYPE = STAT_FIELD_TYPES[attrname] return lltype_to_annotation(TYPE) - def _get_rmarshall_support_(self): # for rlib.rmarshal - # reduce and recreate stat_result objects from 10-tuples - # (we ignore the extra values here for simplicity and portability) - def stat_result_reduce(st): - return (st[0], st[1], st[2], st[3], st[4], - st[5], st[6], st.st_atime, st.st_mtime, st.st_ctime) - - def stat_result_recreate(tup): - atime, mtime, ctime = tup[7:] - result = tup[:7] - result += (int(atime), int(mtime), int(ctime)) - result += extra_zeroes - result += (int((atime - result[7]) * 1e9), - int((mtime - result[8]) * 1e9), - int((ctime - result[9]) * 1e9)) - return make_stat_result(result) - s_reduced = annmodel.SomeTuple([lltype_to_annotation(TYPE) - for name, TYPE in PORTABLE_STAT_FIELDS[:7]] + if sys.platform == 'win32': + def _get_rmarshall_support_(self): # for rlib.rmarshal + # reduce and recreate stat_result objects from 10-tuples + # (we ignore the extra values here for simplicity and portability) + def stat_result_reduce(st): + return (st[0], st[1], st[2], st[3], st[4], + st[5], st[6], st.st_atime, st.st_mtime, st.st_ctime, + st.st_file_attributes, st.st_reparse_tag) + + def stat_result_recreate(tup): + atime, mtime, ctime = tup[7:10] + result = tup[:7] + result += (int(atime), int(mtime), int(ctime)) + result += extra_zeroes + result += (int((atime - result[7]) * 1e9), + int((mtime - result[8]) * 1e9), + int((ctime - result[9]) * 1e9)) + result += tup[10:] + return make_stat_result(result) + s_reduced = annmodel.SomeTuple([lltype_to_annotation(TYPE) + for name, TYPE in PORTABLE_STAT_FIELDS[:7]] + + 3 * [lltype_to_annotation(lltype.Float)] + + 2 * [lltype_to_annotation(lltype.Int)]) + extra_zeroes = (0,) * (len(STAT_FIELDS) - len(PORTABLE_STAT_FIELDS) - 3) + return s_reduced, stat_result_reduce, stat_result_recreate + else: + def _get_rmarshall_support_(self): # for rlib.rmarshal + # reduce and recreate stat_result objects from 10-tuples + # (we ignore the extra values here for simplicity and portability) + def stat_result_reduce(st): + return (st[0], st[1], st[2], st[3], st[4], + st[5], st[6], st.st_atime, st.st_mtime, st.st_ctime) + + def stat_result_recreate(tup): + atime, mtime, ctime = tup[7:] + result = tup[:7] + result += (int(atime), int(mtime), int(ctime)) + result += extra_zeroes + result += (int((atime - result[7]) * 1e9), + int((mtime - result[8]) * 1e9), + int((ctime - result[9]) * 1e9)) + return make_stat_result(result) + s_reduced = annmodel.SomeTuple([lltype_to_annotation(TYPE) + for name, TYPE in PORTABLE_STAT_FIELDS[:7]] + 3 * [lltype_to_annotation(lltype.Float)]) - extra_zeroes = (0,) * (len(STAT_FIELDS) - len(PORTABLE_STAT_FIELDS) - 3) - return s_reduced, stat_result_reduce, stat_result_recreate + extra_zeroes = (0,) * (len(STAT_FIELDS) - len(PORTABLE_STAT_FIELDS) - 3) + return s_reduced, stat_result_reduce, stat_result_recreate class __extend__(pairtype(SomeStatResult, annmodel.SomeInteger)): @@ -255,9 +285,16 @@ def make_stat_result(tup): value = lltype.cast_primitive(TYPE, tup[i]) positional.append(value) kwds = {} - kwds['st_atime'] = tup[7] + 1e-9 * tup[-3] - kwds['st_mtime'] = tup[8] + 1e-9 * tup[-2] - kwds['st_ctime'] = tup[9] + 1e-9 * tup[-1] + if sys.platform == 'win32': + kwds['st_atime'] = tup[7] + 1e-9 * tup[-5] + kwds['st_mtime'] = tup[8] + 1e-9 * tup[-4] + kwds['st_ctime'] = tup[9] + 1e-9 * tup[-3] + kwds['st_file_attributes'] = tup[-2] + kwds['st_reparse_tag'] = tup[-1] + else: + kwds['st_atime'] = tup[7] + 1e-9 * tup[-3] + kwds['st_mtime'] = tup[8] + 1e-9 * tup[-2] + kwds['st_ctime'] = tup[9] + 1e-9 * tup[-1] for value, (name, TYPE) in zip(tup, STAT_FIELDS)[N_INDEXABLE_FIELDS:]: if name.startswith('nsec_'): continue # ignore the nsec_Xtime here @@ -452,7 +489,10 @@ if sys.platform != 'win32': posix_declaration(ALL_STAT_FIELDS[_i]) del _i -STAT_FIELDS += ALL_STAT_FIELDS[-3:] # nsec_Xtime +if sys.platform == 'win32': + STAT_FIELDS += ALL_STAT_FIELDS[-5:] # nsec_Xtime, st_file_attributes, st_reparse_tag +else: + STAT_FIELDS += ALL_STAT_FIELDS[-3:] # nsec_Xtime # these two global vars only list the fields defined in the underlying platform STAT_FIELD_TYPES = dict(STAT_FIELDS) # {'st_xxx': TYPE} @@ -564,13 +604,13 @@ def fstat(fd): return make_stat_result((win32traits._S_IFCHR, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0)) + 0, 0, 0, 0, 0)) elif filetype == win32traits.FILE_TYPE_PIPE: # socket or named pipe return make_stat_result((win32traits._S_IFIFO, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0)) + 0, 0, 0, 0, 0)) elif filetype == win32traits.FILE_TYPE_UNKNOWN: error = rwin32.GetLastError_saved() if error != 0: @@ -578,16 +618,13 @@ def fstat(fd): # else: unknown but valid file # normal disk file (FILE_TYPE_DISK) - info = lltype.malloc(win32traits.BY_HANDLE_FILE_INFORMATION, - flavor='raw', zero=True) - try: - res = win32traits.GetFileInformationByHandle(handle, info) + with lltype.scoped_alloc(win32traits.BY_HANDLE_FILE_INFORMATION, + zero=True) as fileInfo: + res = win32traits.GetFileInformationByHandle(handle, fileInfo) if res == 0: raise WindowsError(rwin32.GetLastError_saved(), "os_fstat failed") - return win32_by_handle_info_to_stat(win32traits, info) - finally: - lltype.free(info, flavor='raw') + return win32_by_handle_info_to_stat(win32traits, fileInfo, 0) @replace_os_function('stat') @specialize.argtype(0) @@ -600,7 +637,7 @@ def stat(path): else: traits = _preferred_traits(path) path = traits.as_str0(path) - return win32_xstat(traits, path, traverse=True) + return win32_xstat3(traits, path, traverse=True) @replace_os_function('lstat') @specialize.argtype(0) @@ -613,29 +650,29 @@ def lstat(path): else: traits = _preferred_traits(path) path = traits.as_str0(path) - return win32_xstat(traits, path, traverse=False) + return win32_xstat3(traits, path, traverse=False) @specialize.argtype(0) def stat3(path): - # On Windows, the algorithm behind os.stat() changed a lot between - # Python 2 and Python 3. This is the Python 3 version. - if not _WIN32: - return stat(path) - else: + if _WIN32: + # On Windows, the algorithm behind os.stat() changed a lot between + # Python 2 and Python 3. This is the Python 3 version. traits = _preferred_traits(path) path = traits.as_str0(path) return win32_xstat3(traits, path, traverse=True) + else: + return stat(path) @specialize.argtype(0) def lstat3(path): - # On Windows, the algorithm behind os.lstat() changed a lot between - # Python 2 and Python 3. This is the Python 3 version. - if not _WIN32: - return lstat(path) - else: + if _WIN32: + # On Windows, the algorithm behind os.lstat() changed a lot between + # Python 2 and Python 3. This is the Python 3 version. traits = _preferred_traits(path) path = traits.as_str0(path) return win32_xstat3(traits, path, traverse=False) + else: + return lstat(path) if rposix.HAVE_FSTATAT: from rpython.rlib.rposix import AT_FDCWD, AT_SYMLINK_NOFOLLOW @@ -676,67 +713,149 @@ if _WIN32: def make_longlong(high, low): return (rffi.r_longlong(high) << 32) + rffi.r_longlong(low) - @specialize.arg(0) - def win32_xstat(traits, path, traverse=False): - # XXX 'traverse' is ignored - win32traits = make_win32_traits(traits) - with lltype.scoped_alloc( - win32traits.WIN32_FILE_ATTRIBUTE_DATA) as data: - res = win32traits.GetFileAttributesEx( - path, win32traits.GetFileExInfoStandard, data) - if res == 0: - errcode = rwin32.GetLastError_saved() - if errcode == win32traits.ERROR_SHARING_VIOLATION: - res = win32_attributes_from_dir( - win32traits, path, data) - if res == 0: - errcode = rwin32.GetLastError_saved() - raise WindowsError(errcode, "os_stat failed") - return win32_attribute_data_to_stat(win32traits, data) + def IsReparseTagNameSurrogate(_tag): + return widen(_tag) & 0x20000000 @specialize.arg(0) - def win32_xstat3(traits, path, traverse=False): + def win32_xstat3(traits, path0, traverse=False): # This is the Python3 version of os.stat() or lstat(). - # XXX 'traverse' is ignored, and everything related to - # the "reparse points" is missing win32traits = make_win32_traits(traits) + path = traits.as_str0(path0) - hFile = win32traits.CreateFile(traits.as_str0(path), - win32traits.FILE_READ_ATTRIBUTES, - 0, + with lltype.scoped_alloc(win32traits.BY_HANDLE_FILE_INFORMATION, + zero=True) as fileInfo: + with lltype.scoped_alloc(win32traits.FILE_ATTRIBUTE_TAG_INFO, + zero=True) as tagInfo: + return win32_xstat_impl(win32traits, path, traverse, fileInfo, tagInfo) + + @specialize.arg(0) + def win32_xstat_impl(traits, path, traverse, fileInfo, tagInfo): + access = traits.FILE_READ_ATTRIBUTES + flags = traits.FILE_FLAG_BACKUP_SEMANTICS + isUnhandledTag = False + if not traverse: + flags |= traits.FILE_FLAG_OPEN_REPARSE_POINT + hFile = traits.CreateFile(path, access, 0, lltype.nullptr(rwin32.LPSECURITY_ATTRIBUTES.TO), - win32traits.OPEN_EXISTING, - win32traits.FILE_ATTRIBUTE_NORMAL | - win32traits.FILE_FLAG_BACKUP_SEMANTICS | - 0, # win32traits.FILE_FLAG_OPEN_REPARSE_POINT, + traits.OPEN_EXISTING, + flags, rwin32.NULL_HANDLE) if hFile == rwin32.INVALID_HANDLE_VALUE: + # Either the path doesn't exist, or the caller lacks access errcode = rwin32.GetLastError_saved() - if (errcode != win32traits.ERROR_ACCESS_DENIED and - errcode != win32traits.ERROR_SHARING_VIOLATION): - raise WindowsError(errcode, "os_stat failed") - - with lltype.scoped_alloc( - win32traits.WIN32_FILE_ATTRIBUTE_DATA) as data: - if win32_attributes_from_dir(win32traits, path, data) == 0: + if (errcode == traits.ERROR_ACCESS_DENIED or + errcode == traits.ERROR_SHARING_VIOLATION): + # Try reading the parent directory + if win32_attributes_from_dir(traits, path, fileInfo, tagInfo) == 0: raise WindowsError(rwin32.GetLastError_saved(), "win32_attributes_from_dir failed") - return win32_attribute_data_to_stat(win32traits, data) - - with lltype.scoped_alloc( - win32traits.BY_HANDLE_FILE_INFORMATION, zero=True) as data: - res = win32traits.GetFileInformationByHandle(hFile, data) - errcode = rwin32.GetLastError_saved() - rwin32.CloseHandle(hFile) - if res == 0: - raise WindowsError(errcode, "GetFileInformationByHandle failed") - return win32_by_handle_info_to_stat(win32traits, data) + if widen(fileInfo.c_dwFileAttributes) & traits.FILE_ATTRIBUTE_REPARSE_POINT: + if traverse or not IsReparseTagNameSurrogate(tagInfo.c_ReparseTag): + raise WindowsError(rwin32.GetLastError_saved(), + "win32_xstat failed") + elif errcode == traits.ERROR_INVALID_PARAMETER: + # \\.\con requires read or write access. + hFile = traits.CreateFile(path, + access | traits.GENERIC_READ, + traits.FILE_SHARE_READ | traits.FILE_SHARE_WRITE, + lltype.nullptr(rwin32.LPSECURITY_ATTRIBUTES.TO), + traits.OPEN_EXISTING, flags, + rwin32.NULL_HANDLE) + if hFile == rwin32.INVALID_HANDLE_VALUE: + raise WindowsError(rwin32.GetLastError_saved(), + "win32_xstat failed") + elif errcode == traits.ERROR_CANT_ACCESS_FILE: + # bpo37834: opne unhandled reparse points if traverse fails + if traverse: + traverse = False + isUnhandledTag = True + hFile = traits.CreateFile(path, access, 0, + lltype.nullptr(rwin32.LPSECURITY_ATTRIBUTES.TO), + traits.OPEN_EXISTING, + flags | traits.FILE_FLAG_OPEN_REPARSE_POINT, + rwin32.NULL_HANDLE) + if hFile == rwin32.INVALID_HANDLE_VALUE: + raise WindowsError(rwin32.GetLastError_saved(), + "win32_xstat failed") + else: + raise WindowsError(errcode, "os_stat failed") + + if hFile != rwin32.INVALID_HANDLE_VALUE: + # Handle types other than files on disk. + fileType = traits.GetFileType(hFile) + if fileType != traits.FILE_TYPE_DISK: + errcode = rwin32.GetLastError_saved() + if fileType == traits.FILE_TYPE_UNKNOWN and errcode != 0: + rwin32.CloseHandle(hFile) + raise WindowsError(errcode, "os_stat failed") + fileAttributes = widen(traits.GetFileAttributes(path)) + st_mode = 0 + if (fileAttributes != traits.INVALID_FILE_ATTRIBUTES and + fileAttributes & traits.FILE_ATTRIBUTE_DIRECTORY): + # \\.\pipe\ or \\.\mailslot\ + st_mode = traits._S_IFDIR + elif fileType == traits.FILE_TYPE_CHAR: + # \\.\nul + st_mode = traits._S_IFCHR + elif fileType == traits.FILE_TYPE_PIPE: + # \\.\pipe\spam + st_mode = traits._S_IFIFO + rwin32.CloseHandle(hFile) + result = (st_mode, + 0, 0, 0, 0, 0, + 0, + 0, 0, 0, + 0, 0, 0, + 0, 0) + # FILE_TYPE_UNKNOWN, e.g. \\.\mailslot\waitfor.exe\spam + return make_stat_result(result) + # Query the reparse tag, and traverse a non-link. + if not traverse: + if not traits.GetFileInformationByHandleEx(hFile, + traits.FileAttributeTagInfo, tagInfo, + traits.TagInfoSize): + errcode = rwin32.GetLastError_saved() + if errcode in (traits.ERROR_INVALID_PARAMETER, + traits.ERROR_INVALID_FUNCTION, + traits.ERROR_NOT_SUPPORTED): + tagInfo.c_FileAttributes = rffi.cast( + rwin32.DWORD, traits.FILE_ATTRIBUTE_NORMAL) + tagInfo.c_ReparseTag = rffi.cast(rwin32.DWORD, 0) + else: + rwin32.CloseHandle(hFile) + raise WindowsError(errcode, "os_stat failed") + elif widen(tagInfo.c_FileAttributes) & traits.FILE_ATTRIBUTE_REPARSE_POINT: + if IsReparseTagNameSurrogate(tagInfo.c_ReparseTag): + if isUnhandledTag: + # Traversing previously failed for either this + # link or its target. + rwin32.CloseHandle(hFile) + raise WindowsError( + traits.ERROR_CANT_ACCESS_FILE, + "os_stat failed") + # Traverse a non-link, but not if traversing already + # failed for an unhandled tag. + elif not isUnhandledTag: + rwin32.CloseHandle(hFile) + return win32_xstat_impl(traits, path, True, fileInfo, tagInfo) + + res = traits.GetFileInformationByHandle(hFile, fileInfo) + errcode = rwin32.GetLastError_saved() + rwin32.CloseHandle(hFile) + if res == 0: + raise WindowsError(errcode, "GetFileInformationByHandle failed") + result = win32_by_handle_info_to_stat(traits, fileInfo, tagInfo.c_ReparseTag) + + # TBD: adjust the file execute permissions by finding the file extension + # if fileExtension in ('exe', 'bat', 'cmd', 'com'): + # result.st_mode |= 0x0111 + return result @specialize.arg(0) def win32_attributes_to_mode(win32traits, attributes): m = 0 - attributes = intmask(attributes) + attributes = widen(attributes) if attributes & win32traits.FILE_ATTRIBUTE_DIRECTORY: m |= win32traits._S_IFDIR | 0111 # IFEXEC for user,group,other else: @@ -755,16 +874,23 @@ if _WIN32: mtime, extra_mtime = FILE_TIME_to_time_t_nsec(info.c_ftLastWriteTime) atime, extra_atime = FILE_TIME_to_time_t_nsec(info.c_ftLastAccessTime) + st_ino = 0 + st_dev = 0 + st_nlink = 0 + st_file_attributes = info.c_dwFileAttributes + st_reparse_tag = 0 + result = (st_mode, - 0, 0, 0, 0, 0, + st_ino, st_dev, st_nlink, 0, 0, st_size, atime, mtime, ctime, - extra_atime, extra_mtime, extra_ctime) + extra_atime, extra_mtime, extra_ctime, + st_file_attributes, st_reparse_tag) return make_stat_result(result) @specialize.arg(0) - def win32_by_handle_info_to_stat(win32traits, info): + def win32_by_handle_info_to_stat(win32traits, info, reparse_tag): # similar to the one above st_mode = win32_attributes_to_mode(win32traits, info.c_dwFileAttributes) st_size = make_longlong(info.c_nFileSizeHigh, info.c_nFileSizeLow) @@ -776,29 +902,37 @@ if _WIN32: st_ino = make_longlong(info.c_nFileIndexHigh, info.c_nFileIndexLow) st_dev = info.c_dwVolumeSerialNumber st_nlink = info.c_nNumberOfLinks + st_file_attributes = info.c_dwFileAttributes + st_reparse_tag = reparse_tag result = (st_mode, st_ino, st_dev, st_nlink, 0, 0, st_size, atime, mtime, ctime, - extra_atime, extra_mtime, extra_ctime) + extra_atime, extra_mtime, extra_ctime, + st_file_attributes, st_reparse_tag) return make_stat_result(result) @specialize.arg(0) - def win32_attributes_from_dir(win32traits, path, data): - filedata = lltype.malloc(win32traits.WIN32_FIND_DATA, flavor='raw') - try: - hFindFile = win32traits.FindFirstFile(path, filedata) + def win32_attributes_from_dir(traits, path, info, tagInfo): + with lltype.scoped_alloc(traits.WIN32_FIND_DATA) as filedata: + hFindFile = traits.FindFirstFile(path, filedata) if hFindFile == rwin32.INVALID_HANDLE_VALUE: return 0 - win32traits.FindClose(hFindFile) - data.c_dwFileAttributes = filedata.c_dwFileAttributes - rffi.structcopy(data.c_ftCreationTime, filedata.c_ftCreationTime) - rffi.structcopy(data.c_ftLastAccessTime, filedata.c_ftLastAccessTime) - rffi.structcopy(data.c_ftLastWriteTime, filedata.c_ftLastWriteTime) - data.c_nFileSizeHigh = filedata.c_nFileSizeHigh - data.c_nFileSizeLow = filedata.c_nFileSizeLow + traits.FindClose(hFindFile) + tagInfo.c_ReparseTag = win32_find_data_to_file_info(traits, filedata, info) return 1 - finally: - lltype.free(filedata, flavor='raw') + + @specialize.arg(0) + def win32_find_data_to_file_info(traits, filedata, info): + info.c_dwFileAttributes = filedata.c_dwFileAttributes + rffi.structcopy(info.c_ftCreationTime, filedata.c_ftCreationTime) + rffi.structcopy(info.c_ftLastAccessTime, filedata.c_ftLastAccessTime) + rffi.structcopy(info.c_ftLastWriteTime, filedata.c_ftLastWriteTime) + info.c_nFileSizeHigh = filedata.c_nFileSizeHigh + info.c_nFileSizeLow = filedata.c_nFileSizeLow + attr = widen(filedata.c_dwFileAttributes) + if attr & traits.FILE_ATTRIBUTE_REPARSE_POINT: + return filedata.c_dwReserved0 + return 0 diff --git a/rpython/rlib/rwin32file.py b/rpython/rlib/rwin32file.py index 5cffdbce76..aa656a04b9 100644 --- a/rpython/rlib/rwin32file.py +++ b/rpython/rlib/rwin32file.py @@ -31,8 +31,15 @@ def GetCConfigGlobal(): 'INVALID_FILE_ATTRIBUTES') ERROR_SHARING_VIOLATION = platform.ConstantInteger( 'ERROR_SHARING_VIOLATION') - ERROR_ACCESS_DENIED = platform.ConstantInteger( - 'ERROR_ACCESS_DENIED') + ERROR_ACCESS_DENIED = platform.ConstantInteger('ERROR_ACCESS_DENIED') + ERROR_CANT_ACCESS_FILE = platform.ConstantInteger( + 'ERROR_CANT_ACCESS_FILE') + ERROR_INVALID_PARAMETER = platform.ConstantInteger( + 'ERROR_INVALID_PARAMETER') + ERROR_NOT_SUPPORTED = platform.ConstantInteger( + 'ERROR_NOT_SUPPORTED') + ERROR_INVALID_FUNCTION = platform.ConstantInteger( + 'ERROR_INVALID_FUNCTION') MOVEFILE_REPLACE_EXISTING = platform.ConstantInteger( 'MOVEFILE_REPLACE_EXISTING') _S_IFDIR = platform.ConstantInteger('_S_IFDIR') @@ -50,17 +57,23 @@ def GetCConfigGlobal(): FILE_TYPE_UNKNOWN = platform.ConstantInteger('FILE_TYPE_UNKNOWN') FILE_TYPE_CHAR = platform.ConstantInteger('FILE_TYPE_CHAR') FILE_TYPE_PIPE = platform.ConstantInteger('FILE_TYPE_PIPE') - - FILE_READ_ATTRIBUTES = platform.ConstantInteger( - 'FILE_READ_ATTRIBUTES') + FILE_TYPE_DISK = platform.ConstantInteger('FILE_TYPE_DISK') + FILE_READ_ATTRIBUTES = platform.ConstantInteger('FILE_READ_ATTRIBUTES') FILE_WRITE_ATTRIBUTES = platform.ConstantInteger( 'FILE_WRITE_ATTRIBUTES') - OPEN_EXISTING = platform.ConstantInteger( - 'OPEN_EXISTING') + GENERIC_READ = platform.ConstantInteger('GENERIC_READ') + FILE_SHARE_READ = platform.ConstantInteger('FILE_SHARE_READ') + FILE_SHARE_WRITE = platform.ConstantInteger('FILE_SHARE_WRITE') + OPEN_EXISTING = platform.ConstantInteger('OPEN_EXISTING') FILE_ATTRIBUTE_NORMAL = platform.ConstantInteger( 'FILE_ATTRIBUTE_NORMAL') FILE_FLAG_BACKUP_SEMANTICS = platform.ConstantInteger( 'FILE_FLAG_BACKUP_SEMANTICS') + FILE_FLAG_OPEN_REPARSE_POINT = platform.ConstantInteger( + 'FILE_FLAG_OPEN_REPARSE_POINT') + FILE_ATTRIBUTE_REPARSE_POINT = platform.ConstantInteger( + 'FILE_ATTRIBUTE_REPARSE_POINT') + FileAttributeTagInfo = platform.ConstantInteger('FileAttributeTagInfo') VOLUME_NAME_DOS = platform.ConstantInteger('VOLUME_NAME_DOS') VOLUME_NAME_NT = platform.ConstantInteger('VOLUME_NAME_NT') @@ -86,6 +99,11 @@ def GetCConfigGlobal(): ('nFileIndexHigh', rwin32.DWORD), ('nFileIndexLow', rwin32.DWORD)]) + FILE_ATTRIBUTE_TAG_INFO = platform.Struct( + 'FILE_ATTRIBUTE_TAG_INFO', + [('FileAttributes', rwin32.DWORD), + ('ReparseTag', rwin32.DWORD)]) + return CConfigGlobal config_global = None @@ -109,11 +127,13 @@ def make_win32_traits(traits): 'struct _WIN32_FIND_DATA' + suffix, # Only interesting fields [('dwFileAttributes', rwin32.DWORD), - ('nFileSizeHigh', rwin32.DWORD), - ('nFileSizeLow', rwin32.DWORD), ('ftCreationTime', rwin32.FILETIME), ('ftLastAccessTime', rwin32.FILETIME), ('ftLastWriteTime', rwin32.FILETIME), + ('nFileSizeHigh', rwin32.DWORD), + ('nFileSizeLow', rwin32.DWORD), + ('dwReserved0', rwin32.DWORD), + ('dwReserved1', rwin32.DWORD), ('cFileName', lltype.FixedSizeArray(traits.CHAR, 250))]) if config_global is None: @@ -129,18 +149,25 @@ def make_win32_traits(traits): class Win32Traits: apisuffix = suffix - for name in '''WIN32_FIND_DATA WIN32_FILE_ATTRIBUTE_DATA BY_HANDLE_FILE_INFORMATION + for name in '''WIN32_FIND_DATA WIN32_FILE_ATTRIBUTE_DATA + BY_HANDLE_FILE_INFORMATION + FILE_ATTRIBUTE_TAG_INFO GetFileExInfoStandard FILE_ATTRIBUTE_DIRECTORY FILE_ATTRIBUTE_READONLY INVALID_FILE_ATTRIBUTES _S_IFDIR _S_IFREG _S_IFCHR _S_IFIFO FILE_TYPE_UNKNOWN FILE_TYPE_CHAR FILE_TYPE_PIPE + ERROR_INVALID_PARAMETER FILE_TYPE_DISK GENERIC_READ + FILE_SHARE_READ FILE_SHARE_WRITE ERROR_NOT_SUPPORTED + FILE_FLAG_OPEN_REPARSE_POINT FileAttributeTagInfo FILE_READ_ATTRIBUTES FILE_ATTRIBUTE_NORMAL - FILE_WRITE_ATTRIBUTES OPEN_EXISTING FILE_FLAG_BACKUP_SEMANTICS + FILE_WRITE_ATTRIBUTES OPEN_EXISTING VOLUME_NAME_DOS VOLUME_NAME_NT ERROR_FILE_NOT_FOUND ERROR_NO_MORE_FILES ERROR_SHARING_VIOLATION MOVEFILE_REPLACE_EXISTING - ERROR_ACCESS_DENIED + ERROR_ACCESS_DENIED ERROR_CANT_ACCESS_FILE + ERROR_INVALID_FUNCTION FILE_FLAG_BACKUP_SEMANTICS + FILE_ATTRIBUTE_REPARSE_POINT _O_RDONLY _O_WRONLY _O_BINARY '''.split(): locals()[name] = config[name] @@ -178,6 +205,12 @@ def make_win32_traits(traits): rwin32.BOOL, save_err=rffi.RFFI_SAVE_LASTERROR) + GetFileInformationByHandleEx = external( + 'GetFileInformationByHandleEx', + [rwin32.HANDLE, rffi.INT, rffi.VOIDP, rwin32.DWORD], + rwin32.BOOL, + save_err=rffi.RFFI_SAVE_LASTERROR) + GetFileInformationByHandle = external( 'GetFileInformationByHandle', [rwin32.HANDLE, lltype.Ptr(BY_HANDLE_FILE_INFORMATION)], @@ -249,6 +282,8 @@ def make_win32_traits(traits): rwin32.BOOL, save_err=rffi.RFFI_SAVE_LASTERROR) + TagInfoSize = 2 * rffi.sizeof(rwin32.DWORD) + return Win32Traits def make_longlong(high, low): diff --git a/rpython/rlib/test/test_rposix_stat.py b/rpython/rlib/test/test_rposix_stat.py index e201b282c0..154e3c717b 100644 --- a/rpython/rlib/test/test_rposix_stat.py +++ b/rpython/rlib/test/test_rposix_stat.py @@ -77,10 +77,11 @@ class TestPosixStatFunctions: assert st.st_dev == st.st_ino == 0 st = rposix_stat.stat3('C:\\') assert st.st_dev != 0 and st.st_ino != 0 + assert st.st_file_attributes & 0x16 # FILE_ATTRIBUTE_DIRECTORY + assert st.st_reparse_tag == 0 st2 = rposix_stat.lstat3('C:\\') assert (st2.st_dev, st2.st_ino) == (st.st_dev, st.st_ino) - @py.test.mark.skipif("not hasattr(rposix_stat, 'fstatat')") def test_fstatat(tmpdir): tmpdir.join('file').write('text') |