aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatti Picus <matti.picus@gmail.com>2021-09-12 09:38:13 +0300
committerMatti Picus <matti.picus@gmail.com>2021-09-12 09:38:13 +0300
commit415fdd5218ab820991ecfbd98ed1b917eefa1f06 (patch)
tree45b6d63f2d40addb7050c955ce77dd8a39aca05c
parentmerge default into release (diff)
parentmention stdlib 3.7.12 in the release note (diff)
downloadpypy-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.rst3
-rw-r--r--pypy/doc/whatsnew-head.rst5
-rw-r--r--pypy/module/posix/interp_posix.py16
-rw-r--r--rpython/rlib/rposix_stat.py350
-rw-r--r--rpython/rlib/rwin32file.py59
-rw-r--r--rpython/rlib/test/test_rposix_stat.py3
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')