/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/_readdir_pyx.pyx

  • Committer: John Arbash Meinel
  • Date: 2010-01-12 22:51:31 UTC
  • mto: This revision was merged to the branch mainline in revision 4955.
  • Revision ID: john@arbash-meinel.com-20100112225131-he8h411p6aeeb947
Delay grabbing an output stream until we actually go to show a diff.

This makes the test suite happy, but it also seems to be reasonable.
If we aren't going to write anything, we don't need to hold an
output stream open.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2008, 2009 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
_directory = 'directory'
 
110
_chardev = 'chardev'
 
111
_block = 'block'
 
112
_file = 'file'
 
113
_fifo = 'fifo'
 
114
_symlink = 'symlink'
 
115
_socket = 'socket'
 
116
_unknown = 'unknown'
 
117
_missing = 'missing'
 
118
 
 
119
# add a typedef struct dirent dirent to workaround pyrex
 
120
cdef extern from 'readdir.h':
 
121
    pass
 
122
 
 
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):
 
282
    """Like os.listdir, this reads the contents of a directory.
 
283
 
 
284
    :param path: the directory to list.
 
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).
 
288
    """
 
289
    cdef DIR *the_dir
 
290
    # currently this needs a fixup - the C code says 'dirent' but should say
 
291
    # 'struct dirent'
 
292
    cdef dirent * entry
 
293
    cdef dirent sentinel
 
294
    cdef char *name
 
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
 
 
314
    try:
 
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)
 
371
    finally:
 
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
 
 
380
    return result
 
381
 
 
382
 
 
383
# vim: tw=79 ai expandtab sw=4 sts=4