1# Copyright (C) 2008-2012 Canonical Ltd 2# 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2 of the License, or 6# (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software 15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16# 17# cython: language_level=3 18 19"""Helper functions for Walkdirs on win32.""" 20 21 22cdef extern from "python-compat.h": 23 struct _HANDLE: 24 pass 25 ctypedef _HANDLE *HANDLE 26 ctypedef unsigned long DWORD 27 ctypedef long long __int64 28 ctypedef unsigned short WCHAR 29 struct _FILETIME: 30 DWORD dwHighDateTime 31 DWORD dwLowDateTime 32 ctypedef _FILETIME FILETIME 33 34 struct _WIN32_FIND_DATAW: 35 DWORD dwFileAttributes 36 FILETIME ftCreationTime 37 FILETIME ftLastAccessTime 38 FILETIME ftLastWriteTime 39 DWORD nFileSizeHigh 40 DWORD nFileSizeLow 41 # Some reserved stuff here 42 WCHAR cFileName[260] # MAX_PATH 43 WCHAR cAlternateFilename[14] 44 45 # We have to use the typedef trick, otherwise pyrex uses: 46 # struct WIN32_FIND_DATAW 47 # which fails due to 'incomplete type' 48 ctypedef _WIN32_FIND_DATAW WIN32_FIND_DATAW 49 50 HANDLE INVALID_HANDLE_VALUE 51 HANDLE FindFirstFileW(WCHAR *path, WIN32_FIND_DATAW *data) 52 int FindNextFileW(HANDLE search, WIN32_FIND_DATAW *data) 53 int FindClose(HANDLE search) 54 55 DWORD FILE_ATTRIBUTE_READONLY 56 DWORD FILE_ATTRIBUTE_DIRECTORY 57 int ERROR_NO_MORE_FILES 58 59 int GetLastError() 60 61 # Wide character functions 62 DWORD wcslen(WCHAR *) 63 64 65cdef extern from "Python.h": 66 WCHAR *PyUnicode_AS_UNICODE(object) 67 Py_ssize_t PyUnicode_GET_SIZE(object) 68 object PyUnicode_FromUnicode(WCHAR *, Py_ssize_t) 69 int PyList_Append(object, object) except -1 70 object PyUnicode_AsUTF8String(object) 71 72 73import operator 74import os 75import stat 76 77from . import _readdir_py 78 79cdef object osutils 80osutils = None 81 82 83cdef class _Win32Stat: 84 """Represent a 'stat' result generated from WIN32_FIND_DATA""" 85 86 cdef readonly int st_mode 87 cdef readonly double st_ctime 88 cdef readonly double st_mtime 89 cdef readonly double st_atime 90 # We can't just declare this as 'readonly' because python2.4 doesn't define 91 # T_LONGLONG as a structure member. So instead we just use a property that 92 # will convert it correctly anyway. 93 cdef __int64 _st_size 94 95 property st_size: 96 def __get__(self): 97 return self._st_size 98 99 # os.stat always returns 0, so we hard code it here 100 property st_dev: 101 def __get__(self): 102 return 0 103 property st_ino: 104 def __get__(self): 105 return 0 106 # st_uid and st_gid required for some external tools like bzr-git & dulwich 107 property st_uid: 108 def __get__(self): 109 return 0 110 property st_gid: 111 def __get__(self): 112 return 0 113 114 def __repr__(self): 115 """Repr is the same as a Stat object. 116 117 (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) 118 """ 119 return repr((self.st_mode, 0, 0, 0, 0, 0, self.st_size, self.st_atime, 120 self.st_mtime, self.st_ctime)) 121 122 123cdef object _get_name(WIN32_FIND_DATAW *data): 124 """Extract the Unicode name for this file/dir.""" 125 return PyUnicode_FromUnicode(data.cFileName, 126 wcslen(data.cFileName)) 127 128 129cdef int _get_mode_bits(WIN32_FIND_DATAW *data): # cannot_raise 130 cdef int mode_bits 131 132 mode_bits = 0100666 # writeable file, the most common 133 if data.dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY: 134 mode_bits = mode_bits ^ 0222 # remove the write bits 135 if data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY: 136 # Remove the FILE bit, set the DIR bit, and set the EXEC bits 137 mode_bits = mode_bits ^ 0140111 138 return mode_bits 139 140 141cdef __int64 _get_size(WIN32_FIND_DATAW *data): # cannot_raise 142 # Pyrex casts a DWORD into a PyLong anyway, so it is safe to do << 32 143 # on a DWORD 144 return ((<__int64>data.nFileSizeHigh) << 32) + data.nFileSizeLow 145 146 147cdef double _ftime_to_timestamp(FILETIME *ft): # cannot_raise 148 """Convert from a FILETIME struct into a floating point timestamp. 149 150 The fields of a FILETIME structure are the hi and lo part 151 of a 64-bit value expressed in 100 nanosecond units. 152 1e7 is one second in such units; 1e-7 the inverse. 153 429.4967296 is 2**32 / 1e7 or 2**32 * 1e-7. 154 It also uses the epoch 1601-01-01 rather than 1970-01-01 155 (taken from posixmodule.c) 156 """ 157 cdef __int64 val 158 # NB: This gives slightly different results versus casting to a 64-bit 159 # integer and doing integer math before casting into a floating 160 # point number. But the difference is in the sub millisecond range, 161 # which doesn't seem critical here. 162 # secs between epochs: 11,644,473,600 163 val = ((<__int64>ft.dwHighDateTime) << 32) + ft.dwLowDateTime 164 return (val * 1.0e-7) - 11644473600.0 165 166 167cdef int _should_skip(WIN32_FIND_DATAW *data): # cannot_raise 168 """Is this '.' or '..' so we should skip it?""" 169 if (data.cFileName[0] != c'.'): 170 return 0 171 if data.cFileName[1] == c'\0': 172 return 1 173 if data.cFileName[1] == c'.' and data.cFileName[2] == c'\0': 174 return 1 175 return 0 176 177 178cdef class Win32ReadDir: 179 """Read directories on win32.""" 180 181 cdef object _directory_kind 182 cdef object _file_kind 183 184 def __init__(self): 185 self._directory_kind = _readdir_py._directory 186 self._file_kind = _readdir_py._file 187 188 def top_prefix_to_starting_dir(self, top, prefix=""): 189 """See DirReader.top_prefix_to_starting_dir.""" 190 global osutils 191 if osutils is None: 192 from . import osutils 193 return (osutils.safe_utf8(prefix), None, None, None, 194 osutils.safe_unicode(top)) 195 196 cdef object _get_kind(self, WIN32_FIND_DATAW *data): 197 if data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY: 198 return self._directory_kind 199 return self._file_kind 200 201 cdef _Win32Stat _get_stat_value(self, WIN32_FIND_DATAW *data): 202 """Get the filename and the stat information.""" 203 cdef _Win32Stat statvalue 204 205 statvalue = _Win32Stat() 206 statvalue.st_mode = _get_mode_bits(data) 207 statvalue.st_ctime = _ftime_to_timestamp(&data.ftCreationTime) 208 statvalue.st_mtime = _ftime_to_timestamp(&data.ftLastWriteTime) 209 statvalue.st_atime = _ftime_to_timestamp(&data.ftLastAccessTime) 210 statvalue._st_size = _get_size(data) 211 return statvalue 212 213 def read_dir(self, prefix, top): 214 """Win32 implementation of DirReader.read_dir. 215 216 :seealso: DirReader.read_dir 217 """ 218 cdef WIN32_FIND_DATAW search_data 219 cdef HANDLE hFindFile 220 cdef int last_err 221 cdef WCHAR *query 222 cdef int result 223 224 if prefix: 225 relprefix = prefix + '/' 226 else: 227 relprefix = '' 228 top_slash = top + '/' 229 230 top_star = top_slash + '*' 231 232 dirblock = [] 233 234 query = PyUnicode_AS_UNICODE(top_star) 235 hFindFile = FindFirstFileW(query, &search_data) 236 if hFindFile == INVALID_HANDLE_VALUE: 237 # Raise an exception? This path doesn't seem to exist 238 raise WindowsError(GetLastError(), top_star) 239 240 try: 241 result = 1 242 while result: 243 # Skip '.' and '..' 244 if _should_skip(&search_data): 245 result = FindNextFileW(hFindFile, &search_data) 246 continue 247 name_unicode = _get_name(&search_data) 248 name_utf8 = PyUnicode_AsUTF8String(name_unicode) 249 PyList_Append(dirblock, 250 (relprefix + name_utf8, name_utf8, 251 self._get_kind(&search_data), 252 self._get_stat_value(&search_data), 253 top_slash + name_unicode)) 254 255 result = FindNextFileW(hFindFile, &search_data) 256 # FindNextFileW sets GetLastError() == ERROR_NO_MORE_FILES when it 257 # actually finishes. If we have anything else, then we have a 258 # genuine problem 259 last_err = GetLastError() 260 if last_err != ERROR_NO_MORE_FILES: 261 raise WindowsError(last_err) 262 finally: 263 result = FindClose(hFindFile) 264 if result == 0: 265 last_err = GetLastError() 266 # TODO: We should probably raise an exception if FindClose 267 # returns an error, however, I don't want to supress an 268 # earlier Exception, so for now, I'm ignoring this 269 dirblock.sort(key=operator.itemgetter(1)) 270 return dirblock 271 272 273def lstat(path): 274 """Equivalent to os.lstat, except match Win32ReadDir._get_stat_value. 275 """ 276 return wrap_stat(os.lstat(path)) 277 278 279def fstat(fd): 280 """Like os.fstat, except match Win32ReadDir._get_stat_value 281 282 :seealso: wrap_stat 283 """ 284 return wrap_stat(os.fstat(fd)) 285 286 287def wrap_stat(st): 288 """Return a _Win32Stat object, based on the given stat result. 289 290 On Windows, os.fstat(open(fname).fileno()) != os.lstat(fname). This is 291 generally because os.lstat and os.fstat differ in what they put into st_ino 292 and st_dev. What gets set where seems to also be dependent on the python 293 version. So we always set it to 0 to avoid worrying about it. 294 """ 295 cdef _Win32Stat statvalue 296 statvalue = _Win32Stat() 297 statvalue.st_mode = st.st_mode 298 statvalue.st_ctime = st.st_ctime 299 statvalue.st_mtime = st.st_mtime 300 statvalue.st_atime = st.st_atime 301 statvalue._st_size = st.st_size 302 return statvalue 303