~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/_readdir_pyx.pyx

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-03-18 09:13:28 UTC
  • mfrom: (5096.1.1 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100318091328-8fo347hq4at1usky
(vila) Get better feedback about why
        TestGetFileMTime.test_get_file_mtime is failing

Show diffs side-by-side

added added

removed removed

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