~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: 2007-03-28 06:58:22 UTC
  • mfrom: (2379.2.3 hpss-chroot)
  • Revision ID: pqm@pqm.ubuntu.com-20070328065822-999550a858a3ced3
(robertc) Fix chroot urls to not expose the url of the transport they are protecting, allowing regular url operations to work on them. (Robert Collins, Andrew Bennetts)

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