~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/_readdir_pyx.pyx

Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.

This is used to replace various ad hoc implementations of the same logic,
notably the version used in registry's _LazyObjectGetter which had a bug when
getting a module without also getting a member.  And of course, this new
function has unit tests, unlike the replaced code.

This also adds a KnownHooksRegistry subclass to provide a more natural home for
some other logic.

I'm not thrilled about the name of the new module or the new functions, but it's
hard to think of good names for such generic functionality.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
"""Wrapper for readdir which returns files ordered by inode."""
 
18
 
 
19
 
 
20
import os
 
21
import sys
 
22
 
 
23
#python2.4 support
 
24
cdef extern from "python-compat.h":
 
25
    pass
 
26
 
 
27
 
 
28
cdef extern from 'errno.h':
 
29
    int ENOENT
 
30
    int ENOTDIR
 
31
    int EAGAIN
 
32
    int EINTR
 
33
    char *strerror(int errno)
 
34
    # not necessarily a real variable, but this should be close enough
 
35
    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
 
 
47
 
 
48
cdef extern from 'sys/types.h':
 
49
    ctypedef long ssize_t
 
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
 
 
97
 
 
98
cdef 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
 
 
109
cdef object _directory
 
110
_directory = 'directory'
 
111
cdef object _chardev
 
112
_chardev = 'chardev'
 
113
cdef object _block
 
114
_block = 'block'
 
115
cdef object _file
 
116
_file = 'file'
 
117
cdef object _fifo
 
118
_fifo = 'fifo'
 
119
cdef object _symlink
 
120
_symlink = 'symlink'
 
121
cdef object _socket
 
122
_socket = 'socket'
 
123
cdef object _unknown
 
124
_unknown = 'unknown'
 
125
 
 
126
# add a typedef struct dirent dirent to workaround pyrex
 
127
cdef extern from 'readdir.h':
 
128
    pass
 
129
 
 
130
 
 
131
cdef 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
 
 
169
from bzrlib import osutils
 
170
 
 
171
cdef object _safe_utf8
 
172
_safe_utf8 = osutils.safe_utf8
 
173
 
 
174
cdef 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 PyString_Size(prefix):
 
224
            relprefix = prefix + '/'
 
225
        else:
 
226
            relprefix = ''
 
227
        top_slash = top + '/'
 
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
            PyString_Concat(&new_val_obj, name)
 
244
            if NULL == new_val_obj:
 
245
                # PyString_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
            PyString_Concat(&new_val_obj, name)
 
260
            if NULL == new_val_obj:
 
261
                # PyString_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
 
 
268
cdef 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
 
 
274
cdef _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 != "" and path != '.':
 
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(".")
 
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 + "/" + 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