1# Copyright (C) 2006, 2008, 2009, 2010 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"""Wrapper for readdir which returns files ordered by inode.""" 20 21 22import os 23import sys 24 25cdef extern from "python-compat.h": 26 pass 27 28 29cdef extern from 'errno.h': 30 int ENOENT 31 int ENOTDIR 32 int EAGAIN 33 int EINTR 34 char *strerror(int errno) 35 # not necessarily a real variable, but this should be close enough 36 int errno 37 38cdef extern from 'unistd.h': 39 int chdir(char *path) 40 int close(int fd) 41 int fchdir(int fd) 42 char *getcwd(char *, int size) 43 44cdef extern from 'stdlib.h': 45 void *malloc(int) 46 void free(void *) 47 48 49cdef extern from 'sys/types.h': 50 ctypedef long ssize_t 51 ctypedef unsigned long size_t 52 ctypedef long time_t 53 ctypedef unsigned long ino_t 54 ctypedef unsigned long long off_t 55 ctypedef int mode_t 56 57 58cdef extern from 'sys/stat.h': 59 cdef struct stat: 60 int st_mode 61 off_t st_size 62 int st_dev 63 ino_t st_ino 64 int st_mtime 65 int st_ctime 66 int lstat(char *path, stat *buf) 67 int S_ISDIR(int mode) 68 int S_ISCHR(int mode) 69 int S_ISBLK(int mode) 70 int S_ISREG(int mode) 71 int S_ISFIFO(int mode) 72 int S_ISLNK(int mode) 73 int S_ISSOCK(int mode) 74 75 76cdef extern from 'fcntl.h': 77 int O_RDONLY 78 int open(char *pathname, int flags, mode_t mode) 79 80 81cdef extern from 'Python.h': 82 int PyErr_CheckSignals() except -1 83 char * PyBytes_AS_STRING(object) 84 ctypedef struct PyObject: 85 pass 86 Py_ssize_t PyBytes_Size(object s) 87 object PyList_GetItem(object lst, Py_ssize_t index) 88 void *PyList_GetItem_object_void "PyList_GET_ITEM" (object lst, int index) 89 int PyList_Append(object lst, object item) except -1 90 void *PyTuple_GetItem_void_void "PyTuple_GET_ITEM" (void* tpl, int index) 91 int PyTuple_SetItem(void *, Py_ssize_t pos, object item) except -1 92 int PyTuple_SetItem_obj "PyTuple_SetItem" (void *, Py_ssize_t pos, PyObject * item) except -1 93 void Py_INCREF(object o) 94 void Py_DECREF(object o) 95 void PyBytes_Concat(PyObject **string, object newpart) 96 97 98cdef extern from 'dirent.h': 99 ctypedef struct dirent: 100 char d_name[256] 101 ino_t d_ino 102 # the opaque C library DIR type. 103 ctypedef struct DIR 104 # should be DIR *, pyrex barfs. 105 DIR * opendir(char * name) 106 int closedir(DIR * dir) 107 dirent *readdir(DIR *dir) 108 109cdef object _directory 110_directory = 'directory' 111cdef object _chardev 112_chardev = 'chardev' 113cdef object _block 114_block = 'block' 115cdef object _file 116_file = 'file' 117cdef object _fifo 118_fifo = 'fifo' 119cdef object _symlink 120_symlink = 'symlink' 121cdef object _socket 122_socket = 'socket' 123cdef object _unknown 124_unknown = 'unknown' 125 126# add a typedef struct dirent dirent to workaround pyrex 127cdef extern from 'readdir.h': 128 pass 129 130 131cdef class _Stat: 132 """Represent a 'stat' result.""" 133 134 cdef stat _st 135 136 property st_dev: 137 def __get__(self): 138 return self._st.st_dev 139 140 property st_ino: 141 def __get__(self): 142 return self._st.st_ino 143 144 property st_mode: 145 def __get__(self): 146 return self._st.st_mode 147 148 property st_ctime: 149 def __get__(self): 150 return self._st.st_ctime 151 152 property st_mtime: 153 def __get__(self): 154 return self._st.st_mtime 155 156 property st_size: 157 def __get__(self): 158 return self._st.st_size 159 160 def __repr__(self): 161 """Repr is the same as a Stat object. 162 163 (mode, ino, dev, nlink, uid, gid, size, None(atime), mtime, ctime) 164 """ 165 return repr((self.st_mode, 0, 0, 0, 0, 0, self.st_size, None, 166 self.st_mtime, self.st_ctime)) 167 168 169from . import osutils 170 171cdef object _safe_utf8 172_safe_utf8 = osutils.safe_utf8 173 174cdef class UTF8DirReader: 175 """A dir reader for utf8 file systems.""" 176 177 def kind_from_mode(self, int mode): 178 """Get the kind of a path from a mode status.""" 179 return self._kind_from_mode(mode) 180 181 cdef _kind_from_mode(self, int mode): 182 # Files and directories are the most common - check them first. 183 if S_ISREG(mode): 184 return _file 185 if S_ISDIR(mode): 186 return _directory 187 if S_ISCHR(mode): 188 return _chardev 189 if S_ISBLK(mode): 190 return _block 191 if S_ISLNK(mode): 192 return _symlink 193 if S_ISFIFO(mode): 194 return _fifo 195 if S_ISSOCK(mode): 196 return _socket 197 return _unknown 198 199 def top_prefix_to_starting_dir(self, top, prefix=""): 200 """See DirReader.top_prefix_to_starting_dir.""" 201 return (_safe_utf8(prefix), None, None, None, _safe_utf8(top)) 202 203 def read_dir(self, prefix, top): 204 """Read a single directory from a utf8 file system. 205 206 All paths in and out are utf8. 207 208 This sub-function is called when we know the filesystem is already in utf8 209 encoding. So we don't need to transcode filenames. 210 211 See DirReader.read_dir for details. 212 """ 213 #cdef char *_prefix = prefix 214 #cdef char *_top = top 215 # Use C accelerated directory listing. 216 cdef object newval 217 cdef int index 218 cdef int length 219 cdef void * atuple 220 cdef object name 221 cdef PyObject * new_val_obj 222 223 if PyBytes_Size(prefix): 224 relprefix = prefix + b'/' 225 else: 226 relprefix = b'' 227 top_slash = top + b'/' 228 229 # read_dir supplies in should-stat order. 230 # for _, name in sorted(_listdir(top)): 231 result = _read_dir(top) 232 length = len(result) 233 # result.sort() 234 for index from 0 <= index < length: 235 atuple = PyList_GetItem_object_void(result, index) 236 name = <object>PyTuple_GetItem_void_void(atuple, 1) 237 # We have a tuple with (inode, name, None, statvalue, None) 238 # Now edit it: 239 # inode -> path_from_top 240 # direct concat - faster than operator +. 241 new_val_obj = <PyObject *>relprefix 242 Py_INCREF(relprefix) 243 PyBytes_Concat(&new_val_obj, name) 244 if NULL == new_val_obj: 245 # PyBytes_Concat will have setup an exception, but how to get 246 # at it? 247 raise Exception("failed to strcat") 248 PyTuple_SetItem_obj(atuple, 0, new_val_obj) 249 # 1st None -> kind 250 newval = self._kind_from_mode( 251 (<_Stat>PyTuple_GetItem_void_void(atuple, 3)).st_mode) 252 Py_INCREF(newval) 253 PyTuple_SetItem(atuple, 2, newval) 254 # 2nd None -> abspath # for all - the caller may need to stat files 255 # etc. 256 # direct concat - faster than operator +. 257 new_val_obj = <PyObject *>top_slash 258 Py_INCREF(top_slash) 259 PyBytes_Concat(&new_val_obj, name) 260 if NULL == new_val_obj: 261 # PyBytes_Concat will have setup an exception, but how to get 262 # at it? 263 raise Exception("failed to strcat") 264 PyTuple_SetItem_obj(atuple, 4, new_val_obj) 265 return result 266 267 268cdef raise_os_error(int errnum, char *msg_prefix, path): 269 if errnum == EINTR: 270 PyErr_CheckSignals() 271 raise OSError(errnum, msg_prefix + strerror(errnum), path) 272 273 274cdef _read_dir(path): 275 """Like os.listdir, this reads the contents of a directory. 276 277 :param path: the directory to list. 278 :return: a list of single-owner (the list) tuples ready for editing into 279 the result tuples walkdirs needs to yield. They contain (inode, name, 280 None, statvalue, None). 281 """ 282 cdef DIR *the_dir 283 # currently this needs a fixup - the C code says 'dirent' but should say 284 # 'struct dirent' 285 cdef dirent * entry 286 cdef dirent sentinel 287 cdef char *name 288 cdef int stat_result 289 cdef _Stat statvalue 290 global errno 291 cdef int orig_dir_fd 292 293 # Avoid chdir('') because it causes problems on Sun OS, and avoid this if 294 # staying in . 295 if path != b"" and path != b'.': 296 # we change into the requested directory before reading, and back at the 297 # end, because that turns out to make the stat calls measurably faster than 298 # passing full paths every time. 299 orig_dir_fd = open(".", O_RDONLY, 0) 300 if orig_dir_fd == -1: 301 raise_os_error(errno, "open: ", ".") 302 if -1 == chdir(path): 303 # Ignore the return value, because we are already raising an 304 # exception 305 close(orig_dir_fd) 306 raise_os_error(errno, "chdir: ", path) 307 else: 308 orig_dir_fd = -1 309 310 try: 311 the_dir = opendir(b".") 312 if NULL == the_dir: 313 raise_os_error(errno, "opendir: ", path) 314 try: 315 result = [] 316 entry = &sentinel 317 while entry != NULL: 318 # Unlike most libc functions, readdir needs errno set to 0 319 # beforehand so that eof can be distinguished from errors. See 320 # <https://bugs.launchpad.net/bzr/+bug/279381> 321 while True: 322 errno = 0 323 entry = readdir(the_dir) 324 if entry == NULL and (errno == EAGAIN or errno == EINTR): 325 if errno == EINTR: 326 PyErr_CheckSignals() 327 # try again 328 continue 329 else: 330 break 331 if entry == NULL: 332 if errno == ENOTDIR or errno == 0: 333 # We see ENOTDIR at the end of a normal directory. 334 # As ENOTDIR for read_dir(file) is triggered on opendir, 335 # we consider ENOTDIR to be 'no error'. 336 continue 337 else: 338 raise_os_error(errno, "readdir: ", path) 339 name = entry.d_name 340 if not (name[0] == c"." and ( 341 (name[1] == 0) or 342 (name[1] == c"." and name[2] == 0)) 343 ): 344 statvalue = _Stat() 345 stat_result = lstat(entry.d_name, &statvalue._st) 346 if stat_result != 0: 347 if errno != ENOENT: 348 raise_os_error(errno, "lstat: ", 349 path + b"/" + entry.d_name) 350 else: 351 # the file seems to have disappeared after being 352 # seen by readdir - perhaps a transient temporary 353 # file. there's no point returning it. 354 continue 355 # We append a 5-tuple that can be modified in-place by the C 356 # api: 357 # inode to sort on (to replace with top_path) 358 # name (to keep) 359 # kind (None, to set) 360 # statvalue (to keep) 361 # abspath (None, to set) 362 PyList_Append(result, (entry.d_ino, entry.d_name, None, 363 statvalue, None)) 364 finally: 365 if -1 == closedir(the_dir): 366 raise_os_error(errno, "closedir: ", path) 367 finally: 368 if -1 != orig_dir_fd: 369 failed = False 370 if -1 == fchdir(orig_dir_fd): 371 # try to close the original directory anyhow 372 failed = True 373 if -1 == close(orig_dir_fd) or failed: 374 raise_os_error(errno, "return to orig_dir: ", "") 375 376 return result 377 378 379# vim: tw=79 ai expandtab sw=4 sts=4 380