##// END OF EJS Templates
osutil: fix the bug on OS X when we return more in listdir...
Maciej Fijalkowski -
r29821:8656dcac default
parent child Browse files
Show More
@@ -1,361 +1,362
1 1 # osutil.py - pure Python version of osutil.c
2 2 #
3 3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import ctypes
11 11 import ctypes.util
12 12 import os
13 13 import socket
14 14 import stat as statmod
15 15 import sys
16 16
17 17 from . import policy
18 18 modulepolicy = policy.policy
19 19 policynocffi = policy.policynocffi
20 20
21 21 def _mode_to_kind(mode):
22 22 if statmod.S_ISREG(mode):
23 23 return statmod.S_IFREG
24 24 if statmod.S_ISDIR(mode):
25 25 return statmod.S_IFDIR
26 26 if statmod.S_ISLNK(mode):
27 27 return statmod.S_IFLNK
28 28 if statmod.S_ISBLK(mode):
29 29 return statmod.S_IFBLK
30 30 if statmod.S_ISCHR(mode):
31 31 return statmod.S_IFCHR
32 32 if statmod.S_ISFIFO(mode):
33 33 return statmod.S_IFIFO
34 34 if statmod.S_ISSOCK(mode):
35 35 return statmod.S_IFSOCK
36 36 return mode
37 37
38 38 def listdirpure(path, stat=False, skip=None):
39 39 '''listdir(path, stat=False) -> list_of_tuples
40 40
41 41 Return a sorted list containing information about the entries
42 42 in the directory.
43 43
44 44 If stat is True, each element is a 3-tuple:
45 45
46 46 (name, type, stat object)
47 47
48 48 Otherwise, each element is a 2-tuple:
49 49
50 50 (name, type)
51 51 '''
52 52 result = []
53 53 prefix = path
54 54 if not prefix.endswith(os.sep):
55 55 prefix += os.sep
56 56 names = os.listdir(path)
57 57 names.sort()
58 58 for fn in names:
59 59 st = os.lstat(prefix + fn)
60 60 if fn == skip and statmod.S_ISDIR(st.st_mode):
61 61 return []
62 62 if stat:
63 63 result.append((fn, _mode_to_kind(st.st_mode), st))
64 64 else:
65 65 result.append((fn, _mode_to_kind(st.st_mode)))
66 66 return result
67 67
68 68 ffi = None
69 69 if modulepolicy not in policynocffi and sys.platform == 'darwin':
70 70 try:
71 71 from _osutil_cffi import ffi, lib
72 72 except ImportError:
73 73 if modulepolicy == 'cffi': # strict cffi import
74 74 raise
75 75
76 76 if sys.platform == 'darwin' and ffi is not None:
77 77 listdir_batch_size = 4096
78 78 # tweakable number, only affects performance, which chunks
79 79 # of bytes do we get back from getattrlistbulk
80 80
81 81 attrkinds = [None] * 20 # we need the max no for enum VXXX, 20 is plenty
82 82
83 83 attrkinds[lib.VREG] = statmod.S_IFREG
84 84 attrkinds[lib.VDIR] = statmod.S_IFDIR
85 85 attrkinds[lib.VLNK] = statmod.S_IFLNK
86 86 attrkinds[lib.VBLK] = statmod.S_IFBLK
87 87 attrkinds[lib.VCHR] = statmod.S_IFCHR
88 88 attrkinds[lib.VFIFO] = statmod.S_IFIFO
89 89 attrkinds[lib.VSOCK] = statmod.S_IFSOCK
90 90
91 91 class stat_res(object):
92 92 def __init__(self, st_mode, st_mtime, st_size):
93 93 self.st_mode = st_mode
94 94 self.st_mtime = st_mtime
95 95 self.st_size = st_size
96 96
97 97 tv_sec_ofs = ffi.offsetof("struct timespec", "tv_sec")
98 98 buf = ffi.new("char[]", listdir_batch_size)
99 99
100 100 def listdirinternal(dfd, req, stat, skip):
101 101 ret = []
102 102 while True:
103 103 r = lib.getattrlistbulk(dfd, req, buf, listdir_batch_size, 0)
104 104 if r == 0:
105 105 break
106 106 if r == -1:
107 107 raise OSError(ffi.errno, os.strerror(ffi.errno))
108 108 cur = ffi.cast("val_attrs_t*", buf)
109 109 for i in range(r):
110 110 lgt = cur.length
111 111 assert lgt == ffi.cast('uint32_t*', cur)[0]
112 112 ofs = cur.name_info.attr_dataoffset
113 113 str_lgt = cur.name_info.attr_length
114 114 base_ofs = ffi.offsetof('val_attrs_t', 'name_info')
115 115 name = str(ffi.buffer(ffi.cast("char*", cur) + base_ofs + ofs,
116 116 str_lgt - 1))
117 117 tp = attrkinds[cur.obj_type]
118 118 if name == "." or name == "..":
119 119 continue
120 120 if skip == name and tp == statmod.S_ISDIR:
121 121 return []
122 122 if stat:
123 mtime = cur.time.tv_sec
123 mtime = cur.mtime.tv_sec
124 124 mode = (cur.accessmask & ~lib.S_IFMT)| tp
125 125 ret.append((name, tp, stat_res(st_mode=mode, st_mtime=mtime,
126 126 st_size=cur.datalength)))
127 127 else:
128 128 ret.append((name, tp))
129 cur += lgt
129 cur = ffi.cast("val_attrs_t*", int(ffi.cast("intptr_t", cur))
130 + lgt)
130 131 return ret
131 132
132 133 def listdir(path, stat=False, skip=None):
133 134 req = ffi.new("struct attrlist*")
134 135 req.bitmapcount = lib.ATTR_BIT_MAP_COUNT
135 136 req.commonattr = (lib.ATTR_CMN_RETURNED_ATTRS |
136 137 lib.ATTR_CMN_NAME |
137 138 lib.ATTR_CMN_OBJTYPE |
138 139 lib.ATTR_CMN_ACCESSMASK |
139 140 lib.ATTR_CMN_MODTIME)
140 141 req.fileattr = lib.ATTR_FILE_DATALENGTH
141 142 dfd = lib.open(path, lib.O_RDONLY, 0)
142 143 if dfd == -1:
143 144 raise OSError(ffi.errno, os.strerror(ffi.errno))
144 145
145 146 try:
146 147 ret = listdirinternal(dfd, req, stat, skip)
147 148 finally:
148 149 try:
149 150 lib.close(dfd)
150 151 except BaseException:
151 152 pass # we ignore all the errors from closing, not
152 153 # much we can do about that
153 154 return ret
154 155 else:
155 156 listdir = listdirpure
156 157
157 158 if os.name != 'nt':
158 159 posixfile = open
159 160
160 161 _SCM_RIGHTS = 0x01
161 162 _socklen_t = ctypes.c_uint
162 163
163 164 if sys.platform == 'linux2':
164 165 # socket.h says "the type should be socklen_t but the definition of
165 166 # the kernel is incompatible with this."
166 167 _cmsg_len_t = ctypes.c_size_t
167 168 _msg_controllen_t = ctypes.c_size_t
168 169 _msg_iovlen_t = ctypes.c_size_t
169 170 else:
170 171 _cmsg_len_t = _socklen_t
171 172 _msg_controllen_t = _socklen_t
172 173 _msg_iovlen_t = ctypes.c_int
173 174
174 175 class _iovec(ctypes.Structure):
175 176 _fields_ = [
176 177 (u'iov_base', ctypes.c_void_p),
177 178 (u'iov_len', ctypes.c_size_t),
178 179 ]
179 180
180 181 class _msghdr(ctypes.Structure):
181 182 _fields_ = [
182 183 (u'msg_name', ctypes.c_void_p),
183 184 (u'msg_namelen', _socklen_t),
184 185 (u'msg_iov', ctypes.POINTER(_iovec)),
185 186 (u'msg_iovlen', _msg_iovlen_t),
186 187 (u'msg_control', ctypes.c_void_p),
187 188 (u'msg_controllen', _msg_controllen_t),
188 189 (u'msg_flags', ctypes.c_int),
189 190 ]
190 191
191 192 class _cmsghdr(ctypes.Structure):
192 193 _fields_ = [
193 194 (u'cmsg_len', _cmsg_len_t),
194 195 (u'cmsg_level', ctypes.c_int),
195 196 (u'cmsg_type', ctypes.c_int),
196 197 (u'cmsg_data', ctypes.c_ubyte * 0),
197 198 ]
198 199
199 200 _libc = ctypes.CDLL(ctypes.util.find_library(u'c'), use_errno=True)
200 201 _recvmsg = getattr(_libc, 'recvmsg', None)
201 202 if _recvmsg:
202 203 _recvmsg.restype = getattr(ctypes, 'c_ssize_t', ctypes.c_long)
203 204 _recvmsg.argtypes = (ctypes.c_int, ctypes.POINTER(_msghdr),
204 205 ctypes.c_int)
205 206 else:
206 207 # recvmsg isn't always provided by libc; such systems are unsupported
207 208 def _recvmsg(sockfd, msg, flags):
208 209 raise NotImplementedError('unsupported platform')
209 210
210 211 def _CMSG_FIRSTHDR(msgh):
211 212 if msgh.msg_controllen < ctypes.sizeof(_cmsghdr):
212 213 return
213 214 cmsgptr = ctypes.cast(msgh.msg_control, ctypes.POINTER(_cmsghdr))
214 215 return cmsgptr.contents
215 216
216 217 # The pure version is less portable than the native version because the
217 218 # handling of socket ancillary data heavily depends on C preprocessor.
218 219 # Also, some length fields are wrongly typed in Linux kernel.
219 220 def recvfds(sockfd):
220 221 """receive list of file descriptors via socket"""
221 222 dummy = (ctypes.c_ubyte * 1)()
222 223 iov = _iovec(ctypes.cast(dummy, ctypes.c_void_p), ctypes.sizeof(dummy))
223 224 cbuf = ctypes.create_string_buffer(256)
224 225 msgh = _msghdr(None, 0,
225 226 ctypes.pointer(iov), 1,
226 227 ctypes.cast(cbuf, ctypes.c_void_p), ctypes.sizeof(cbuf),
227 228 0)
228 229 r = _recvmsg(sockfd, ctypes.byref(msgh), 0)
229 230 if r < 0:
230 231 e = ctypes.get_errno()
231 232 raise OSError(e, os.strerror(e))
232 233 # assumes that the first cmsg has fds because it isn't easy to write
233 234 # portable CMSG_NXTHDR() with ctypes.
234 235 cmsg = _CMSG_FIRSTHDR(msgh)
235 236 if not cmsg:
236 237 return []
237 238 if (cmsg.cmsg_level != socket.SOL_SOCKET or
238 239 cmsg.cmsg_type != _SCM_RIGHTS):
239 240 return []
240 241 rfds = ctypes.cast(cmsg.cmsg_data, ctypes.POINTER(ctypes.c_int))
241 242 rfdscount = ((cmsg.cmsg_len - _cmsghdr.cmsg_data.offset) /
242 243 ctypes.sizeof(ctypes.c_int))
243 244 return [rfds[i] for i in xrange(rfdscount)]
244 245
245 246 else:
246 247 import msvcrt
247 248
248 249 _kernel32 = ctypes.windll.kernel32
249 250
250 251 _DWORD = ctypes.c_ulong
251 252 _LPCSTR = _LPSTR = ctypes.c_char_p
252 253 _HANDLE = ctypes.c_void_p
253 254
254 255 _INVALID_HANDLE_VALUE = _HANDLE(-1).value
255 256
256 257 # CreateFile
257 258 _FILE_SHARE_READ = 0x00000001
258 259 _FILE_SHARE_WRITE = 0x00000002
259 260 _FILE_SHARE_DELETE = 0x00000004
260 261
261 262 _CREATE_ALWAYS = 2
262 263 _OPEN_EXISTING = 3
263 264 _OPEN_ALWAYS = 4
264 265
265 266 _GENERIC_READ = 0x80000000
266 267 _GENERIC_WRITE = 0x40000000
267 268
268 269 _FILE_ATTRIBUTE_NORMAL = 0x80
269 270
270 271 # open_osfhandle flags
271 272 _O_RDONLY = 0x0000
272 273 _O_RDWR = 0x0002
273 274 _O_APPEND = 0x0008
274 275
275 276 _O_TEXT = 0x4000
276 277 _O_BINARY = 0x8000
277 278
278 279 # types of parameters of C functions used (required by pypy)
279 280
280 281 _kernel32.CreateFileA.argtypes = [_LPCSTR, _DWORD, _DWORD, ctypes.c_void_p,
281 282 _DWORD, _DWORD, _HANDLE]
282 283 _kernel32.CreateFileA.restype = _HANDLE
283 284
284 285 def _raiseioerror(name):
285 286 err = ctypes.WinError()
286 287 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
287 288
288 289 class posixfile(object):
289 290 '''a file object aiming for POSIX-like semantics
290 291
291 292 CPython's open() returns a file that was opened *without* setting the
292 293 _FILE_SHARE_DELETE flag, which causes rename and unlink to abort.
293 294 This even happens if any hardlinked copy of the file is in open state.
294 295 We set _FILE_SHARE_DELETE here, so files opened with posixfile can be
295 296 renamed and deleted while they are held open.
296 297 Note that if a file opened with posixfile is unlinked, the file
297 298 remains but cannot be opened again or be recreated under the same name,
298 299 until all reading processes have closed the file.'''
299 300
300 301 def __init__(self, name, mode='r', bufsize=-1):
301 302 if 'b' in mode:
302 303 flags = _O_BINARY
303 304 else:
304 305 flags = _O_TEXT
305 306
306 307 m0 = mode[0]
307 308 if m0 == 'r' and '+' not in mode:
308 309 flags |= _O_RDONLY
309 310 access = _GENERIC_READ
310 311 else:
311 312 # work around http://support.microsoft.com/kb/899149 and
312 313 # set _O_RDWR for 'w' and 'a', even if mode has no '+'
313 314 flags |= _O_RDWR
314 315 access = _GENERIC_READ | _GENERIC_WRITE
315 316
316 317 if m0 == 'r':
317 318 creation = _OPEN_EXISTING
318 319 elif m0 == 'w':
319 320 creation = _CREATE_ALWAYS
320 321 elif m0 == 'a':
321 322 creation = _OPEN_ALWAYS
322 323 flags |= _O_APPEND
323 324 else:
324 325 raise ValueError("invalid mode: %s" % mode)
325 326
326 327 fh = _kernel32.CreateFileA(name, access,
327 328 _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
328 329 None, creation, _FILE_ATTRIBUTE_NORMAL, None)
329 330 if fh == _INVALID_HANDLE_VALUE:
330 331 _raiseioerror(name)
331 332
332 333 fd = msvcrt.open_osfhandle(fh, flags)
333 334 if fd == -1:
334 335 _kernel32.CloseHandle(fh)
335 336 _raiseioerror(name)
336 337
337 338 f = os.fdopen(fd, mode, bufsize)
338 339 # unfortunately, f.name is '<fdopen>' at this point -- so we store
339 340 # the name on this wrapper. We cannot just assign to f.name,
340 341 # because that attribute is read-only.
341 342 object.__setattr__(self, 'name', name)
342 343 object.__setattr__(self, '_file', f)
343 344
344 345 def __iter__(self):
345 346 return self._file
346 347
347 348 def __getattr__(self, name):
348 349 return getattr(self._file, name)
349 350
350 351 def __setattr__(self, name, value):
351 352 '''mimics the read-only attributes of Python file objects
352 353 by raising 'TypeError: readonly attribute' if someone tries:
353 354 f = posixfile('foo.txt')
354 355 f.name = 'bla' '''
355 356 return self._file.__setattr__(name, value)
356 357
357 358 def __enter__(self):
358 359 return self._file.__enter__()
359 360
360 361 def __exit__(self, exc_type, exc_value, exc_tb):
361 362 return self._file.__exit__(exc_type, exc_value, exc_tb)
General Comments 0
You need to be logged in to leave comments. Login now