root/releases/pkgcore/0.2.2/src/posix.c @ ferringb%2540gmail.com-20070114211508-lugk7pqrmkk6hdb4

Revision ferringb%2540gmail.com-20070114211508-lugk7pqrmkk6hdb4, 17.1 KB (checked in by Brian Harring <ferringb@…>, 2 years ago)

make our replacement join match os.path.join for "", "bar"

Line 
1/*
2 * Copyright: 2006 Brian Harring <ferringb@gmail.com>
3 * License: GPL2
4 *
5 * C version of some of pkgcore (for extra speed).
6 */
7
8/* This does not really do anything since we do not use the "#"
9 * specifier in a PyArg_Parse or similar call, but hey, not using it
10 * means we are Py_ssize_t-clean too!
11 */
12
13#define PY_SSIZE_T_CLEAN
14
15#include <Python.h>
16#include "py24-compatibility.h"
17#include <sys/mman.h>
18#include <sys/types.h>
19#include <sys/stat.h>
20#include <fcntl.h>
21
22// only 2.5.46 kernels and up have this.
23#ifndef MAP_POPULATE
24#define MAP_POPULATE 0
25#endif
26
27#define SKIP_SLASHES(ptr) while('/' == *(ptr)) (ptr)++;
28
29static PyObject *
30pkgcore_normpath(PyObject *self, PyObject *old_path)
31{
32    if(!PyString_CheckExact(old_path)) {
33        PyErr_SetString(PyExc_TypeError,
34            "old_path must be a str");
35        return (PyObject *)NULL;
36    }
37    Py_ssize_t len = PyString_Size(old_path);
38    if(!len)
39        return PyString_FromString(".");
40   
41    char *oldstart, *oldp, *newstart, *newp, *real_newstart;
42    oldstart = oldp = PyString_AsString(old_path);
43   
44    PyObject *new_path = PyString_FromStringAndSize(NULL, len);
45    if(!new_path)
46        return new_path;
47    real_newstart = newstart = newp = PyString_AsString(new_path);
48   
49
50    int leading_slash;
51    Py_ssize_t slash_count = 0;
52    // /../ == / , ../foo == ../foo , ../foo/../../ == ../../../
53    if('/' == *oldp) {
54        *newp = '/';
55        newp++;
56        leading_slash = 1;
57        slash_count++;
58        SKIP_SLASHES(oldp);
59        newstart = newp;
60    } else {
61        leading_slash = 0;
62    }
63
64    while('\0' != *oldp) {
65        if('/' == *oldp) {
66            *newp = '/';
67            newp++;
68            slash_count++;
69            SKIP_SLASHES(oldp);
70        }
71        if('.' == *oldp) {
72            oldp++;
73            if('\0' == *oldp)
74                break;
75            if('/' == *oldp) {
76                oldp++;
77                SKIP_SLASHES(oldp);
78                continue;
79            }
80            if(*oldp == '.' && ('/' == oldp[1] || '\0' ==  oldp[1])) {
81                // for newp, ../ == ../ , /../ == /
82                if(leading_slash == slash_count) {
83                    if(!leading_slash) {
84                        // ../ case.
85                        newp[0] = '.';
86                        newp[1] = '.';
87                        newp[2] = '/';
88                        newp += 3;
89                    }
90                } else if (slash_count != 1 || '/' != *newstart) {
91                    // if its /, then the stripping would be ignored.
92                    newp--;
93                    while(newp > newstart && '/' != newp[-1])
94                        newp--;
95                }
96                oldp++;
97                SKIP_SLASHES(oldp);
98                continue;
99            }
100            // funky file name.
101            oldp--;
102        }
103        while('/' != *oldp && '\0' != *oldp) {
104            *newp = *oldp;
105            ++newp;
106            ++oldp;
107        }
108    }
109
110    *newp = '\0';
111    // protect leading slash, but strip trailing.
112    --newp;
113    while(newp > real_newstart && '/' == *newp)
114        newp--;
115
116    // resize it now.
117    _PyString_Resize(&new_path, newp - real_newstart + 1);
118    return new_path;
119}
120
121static PyObject *
122pkgcore_join(PyObject *self, PyObject *args)
123{
124    if(!args) {
125        PyErr_SetString(PyExc_TypeError, "requires at least one path");
126        return (PyObject *)NULL;
127    }
128    PyObject *fast = PySequence_Fast(args, "arg must be a sequence");
129    if(!fast)
130        return (PyObject *)NULL;
131    Py_ssize_t end = PySequence_Fast_GET_SIZE(fast);
132    if(!end) {
133        PyErr_SetString(PyExc_TypeError,
134            "join takes at least one arguement (0 given)");
135        return (PyObject *)NULL;
136    }
137   
138    PyObject **items = PySequence_Fast_ITEMS(fast);
139    Py_ssize_t start = 0, len, i = 0;
140    char *s;
141    int leading_slash = 0;
142    // find the right most item with a prefixed '/', else 0.
143    for(; i < end; i++) {
144        if(!PyString_CheckExact(items[i])) {
145            PyErr_SetString(PyExc_TypeError, "all args must be strings");
146            Py_DECREF(fast);
147            return (PyObject *)NULL;
148        }
149        s = PyString_AsString(items[i]);
150        if('/' == *s) {
151            leading_slash = 1;
152            start = i;
153        }
154    }
155    // know the relevant slice now; figure out the size.
156    len = 0;
157    char *s_start;
158    for(i = start; i < end; i++) {
159        // this is safe because we're using CheckExact above.
160        s_start = s = PyString_AS_STRING(items[i]);
161        while('\0' != *s)
162            s++;
163        if(s_start == s)
164            continue;
165        len += s - s_start;
166        char *s_end = s;
167        if(i + 1 != end) {
168            // cut the length down for trailing duplicate slashes
169            while(s != s_start && '/' == s[-1])
170                s--;
171            // allocate for a leading slash if needed
172            if(s_end == s && (s_start != s ||
173                (s_end == s_start && i != start))) {
174                len++;
175            } else if(s_start != s) {
176                len -= s_end - s -1;
177            }
178        }
179    }
180
181    // ok... we know the length.  allocate a string, and copy it.
182    PyObject *ret = PyString_FromStringAndSize(NULL, len);
183    if(!ret)
184        return (PyObject*)NULL;
185    char *buf = PyString_AS_STRING(ret);
186    if(leading_slash) {
187        *buf = '/';
188        buf++;
189    }
190    for(i = start; i < end; i++) {
191        s_start = s = PyString_AS_STRING(items[i]);
192        if(i == start && leading_slash) {
193            SKIP_SLASHES(s);
194            s_start = s;
195        }
196        if('\0' == *s)
197            continue;
198        while('\0' != *s) {
199            *buf = *s;
200            buf++;
201            if('/' == *s) {
202                char *tmp_s = s + 1;
203                SKIP_SLASHES(s);
204                if('\0' == *s) {
205                    if(i + 1  != end) {
206                        buf--;
207                    } else {
208                        // copy the cracked out trailing slashes on the
209                        // last item
210                        while(tmp_s < s) {
211                            *buf = '/';
212                            buf++;
213                            tmp_s++;
214                        }
215                    }
216                    break;
217                } else {
218                    // copy the cracked out intermediate slashes.
219                    while(tmp_s < s) {
220                        *buf = '/';
221                        buf++;
222                        tmp_s++;
223                    }
224                }
225            } else
226                s++;
227        }
228        if(i + 1 != end) {
229            *buf = '/';
230            buf++;
231        }
232    }
233    *buf = '\0';
234    Py_DECREF(fast);
235    return ret;
236}
237
238// returns 0 on success opening, 1 on ENOENT but ignore, and -1 on failure
239// if failure condition, appropriate exception is set.
240
241static inline int
242pkgcore_open_and_stat(PyObject *path,
243    int *fd, Py_ssize_t *size)
244{
245    struct stat st;
246    errno = 0;
247    if((*fd = open(PyString_AsString(path), O_LARGEFILE)) >= 0) {
248        int ret = fstat(*fd, &st);
249        if(!ret) {
250            *size = st.st_size;
251            return 0;
252        }
253    }
254    return 1;
255}
256
257static inline int
258handle_failed_open_stat(int fd, Py_ssize_t size, PyObject *path,
259PyObject *swallow_missing)
260{
261    if(fd < 0) {
262        if(errno == ENOENT) {
263            if(swallow_missing) {
264                if(PyObject_IsTrue(swallow_missing)) {
265                    errno = 0;
266                    return 0;
267                }
268                if(PyErr_Occurred())
269                    return 1;
270            }
271        }
272        PyErr_SetFromErrnoWithFilenameObject(PyExc_IOError, path);
273        return 1;
274    }
275    PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path);
276    if(close(fd))
277        PyErr_SetFromErrnoWithFilenameObject(PyExc_IOError, path);
278    return 1;
279}
280
281static PyObject *
282pkgcore_readfile(PyObject *self, PyObject *args)
283{
284    PyObject *path, *swallow_missing = NULL;
285    if(!args || !PyArg_ParseTuple(args, "S|O:readfile", &path,
286        &swallow_missing)) {
287        return (PyObject *)NULL;
288    }
289    Py_ssize_t size;
290    int fd;
291    Py_BEGIN_ALLOW_THREADS
292    if(pkgcore_open_and_stat(path, &fd, &size)) {
293        Py_BLOCK_THREADS
294        if(handle_failed_open_stat(fd, size, path, swallow_missing))
295            return NULL;
296        Py_RETURN_NONE;
297    }
298    Py_END_ALLOW_THREADS
299
300    int ret = 0;
301    PyObject *data = PyString_FromStringAndSize(NULL, size);
302
303    Py_BEGIN_ALLOW_THREADS
304    errno = 0;
305    if(data) {
306        ret = size != read(fd, PyString_AS_STRING(data), size) ? 1 : 0;
307    }
308    ret += close(fd);
309    Py_END_ALLOW_THREADS
310
311    if(ret) {
312        Py_CLEAR(data);
313        data = PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path);
314    }
315    return data;
316}
317
318typedef struct {
319    PyObject_HEAD
320    char *start;
321    char *end;
322    char *map;
323    int fd;
324    int strip_newlines;
325    PyObject *fallback;
326} pkgcore_readlines;
327
328
329static PyObject *
330pkgcore_readlines_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
331{
332    PyObject *path, *swallow_missing = NULL, *strip_newlines = NULL;
333    PyObject *none_on_missing = NULL;
334    pkgcore_readlines *self = NULL;
335    if(kwargs && PyDict_Size(kwargs)) {
336        PyErr_SetString(PyExc_TypeError,
337            "readlines.__new__ doesn't accept keywords");
338        return (PyObject *)NULL;
339    } else if (!PyArg_ParseTuple(args, "S|OOO:readlines.__new__",
340        &path, &strip_newlines, &swallow_missing, &none_on_missing)) {
341        return (PyObject *)NULL;
342    }
343   
344    int fd;
345    Py_ssize_t size;
346    void *ptr;
347    PyObject *fallback = NULL;
348    Py_BEGIN_ALLOW_THREADS
349    errno = 0;
350    if(pkgcore_open_and_stat(path, &fd, &size)) {
351        Py_BLOCK_THREADS
352
353        if(handle_failed_open_stat(fd, size, path, swallow_missing))
354            return NULL;
355
356        // return an empty tuple, and let them iter over that.
357        if(none_on_missing && PyObject_IsTrue(none_on_missing)) {
358            Py_RETURN_NONE;
359        }
360       
361        PyObject *data = PyTuple_New(0);
362        if(!data)
363            return (PyObject *)NULL;
364        PyObject *tmp = PySeqIter_New(data);
365        Py_DECREF(data);
366        return tmp;
367    }
368    if(size >= 0x4000) {
369        ptr = (char *)mmap(NULL, size, PROT_READ,
370            MAP_SHARED|MAP_NORESERVE|MAP_POPULATE, fd, 0);
371        if(ptr == MAP_FAILED)
372            ptr = NULL;
373    } else {
374        Py_BLOCK_THREADS
375        fallback = PyString_FromStringAndSize(NULL, size);
376        Py_UNBLOCK_THREADS
377        if(fallback) {
378            errno = 0;
379            ptr = (size != read(fd, PyString_AS_STRING(fallback), size)) ?
380                MAP_FAILED : NULL;
381        }
382        int ret = close(fd);
383        if(ret) {
384            Py_CLEAR(fallback);
385            PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path);
386            Py_BLOCK_THREADS
387            return NULL;
388        } else if(!fallback) {
389            Py_BLOCK_THREADS
390            return NULL;
391        }
392    }
393    Py_END_ALLOW_THREADS
394
395    if(ptr == MAP_FAILED) {
396        PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path);
397        if(close(fd))
398            PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path);
399        Py_CLEAR(fallback);
400        return NULL;
401    }
402
403    self = (pkgcore_readlines *)type->tp_alloc(type, 0);
404    if(!self) {
405        // you've got to be kidding me...
406        if(ptr) {
407            munmap(ptr, size);
408            close(fd);
409            errno = 0;
410        } else {
411            Py_DECREF(fallback);
412        }
413        return NULL;
414    }
415    self->fallback = fallback;
416    self->map = ptr;
417    if (ptr) {
418        self->start = ptr;
419        self->fd = fd;
420    } else {
421        self->start = PyString_AS_STRING(fallback);
422        self->fd = -1;
423    }
424    self->end = self->start + size;
425
426    if(strip_newlines) {
427        // die...
428        if(PyObject_IsTrue(strip_newlines)) {
429            self->strip_newlines = 1;
430        } else if(PyErr_Occurred()) {
431            Py_DECREF(self);
432            return NULL;
433        } else {
434            self->strip_newlines = 0;
435        }
436    } else
437        self->strip_newlines = 0;
438    return (PyObject *)self;
439}
440
441static void
442pkgcore_readlines_dealloc(pkgcore_readlines *self)
443{
444    if(self->fallback) {
445        Py_DECREF(self->fallback);
446    } else if(self->map) {
447        if(munmap(self->map, self->end - self->map))
448            // swallow it, no way to signal an error
449            errno = 0;
450        if(close(self->fd))
451            // swallow it, no way to signal an error
452            errno = 0;
453    }
454    self->ob_type->tp_free((PyObject *)self);
455}
456
457static PyObject *
458pkgcore_readlines_iternext(pkgcore_readlines *self)
459{
460    if(self->start == self->end) {
461        // at the end, thus return
462        return (PyObject *)NULL;
463    }
464    char *p = self->start;
465    assert(self->end);
466    assert(self->start);
467    assert(self->map || self->fallback);
468    assert(self->end > self->start);
469
470    while(p != self->end && '\n' != *p)
471        p++;
472
473    PyObject *ret;
474    if(self->strip_newlines) {
475        ret = PyString_FromStringAndSize(self->start, p - self->start);
476    } else {
477        if(p == self->end)
478            ret = PyString_FromStringAndSize(self->start, p - self->start);
479        else
480            ret = PyString_FromStringAndSize(self->start, p - self->start + 1);
481    }
482    if(p != self->end) {
483        p++;
484    }
485    self->start = p;
486    return ret;
487}
488
489PyDoc_STRVAR(
490    pkgcore_readlines_documentation,
491    "readline(path [, strip_newlines [, swallow_missing [, none_on_missing]]])"
492    " -> iterable yielding"
493    " each line of a file\n\n"
494    "if strip_newlines is True, the trailing newline is stripped\n"
495    "if swallow_missing is True, for missing files it returns an empty "
496    "iterable\n"
497    "if none_on_missing and the file is missing, return None instead"
498    );
499
500static PyTypeObject pkgcore_readlines_type = {
501    PyObject_HEAD_INIT(NULL)
502    0,                                               /* ob_size*/
503    "pkgcore.util.osutils._posix.readlines",         /* tp_name*/
504    sizeof(pkgcore_readlines),                       /* tp_basicsize*/
505    0,                                               /* tp_itemsize*/
506    (destructor)pkgcore_readlines_dealloc,           /* tp_dealloc*/
507    0,                                               /* tp_print*/
508    0,                                               /* tp_getattr*/
509    0,                                               /* tp_setattr*/
510    0,                                               /* tp_compare*/
511    0,                                               /* tp_repr*/
512    0,                                               /* tp_as_number*/
513    0,                                               /* tp_as_sequence*/
514    0,                                               /* tp_as_mapping*/
515    0,                                               /* tp_hash */
516    (ternaryfunc)0,                                  /* tp_call*/
517    (reprfunc)0,                                     /* tp_str*/
518    0,                                               /* tp_getattro*/
519    0,                                               /* tp_setattro*/
520    0,                                               /* tp_as_buffer*/
521    Py_TPFLAGS_DEFAULT,                              /* tp_flags*/
522    pkgcore_readlines_documentation,                 /* tp_doc */
523    (traverseproc)0,                                 /* tp_traverse */
524    (inquiry)0,                                      /* tp_clear */
525    (richcmpfunc)0,                                  /* tp_richcompare */
526    0,                                               /* tp_weaklistoffset */
527    (getiterfunc)PyObject_SelfIter,                  /* tp_iter */
528    (iternextfunc)pkgcore_readlines_iternext,        /* tp_iternext */
529    0,                                               /* tp_methods */
530    0,                                               /* tp_members */
531    0,                                               /* tp_getset */
532    0,                                               /* tp_base */
533    0,                                               /* tp_dict */
534    0,                                               /* tp_descr_get */
535    0,                                               /* tp_descr_set */
536    0,                                               /* tp_dictoffset */
537    (initproc)0,                                     /* tp_init */
538    0,                                               /* tp_alloc */
539    pkgcore_readlines_new,                           /* tp_new */
540};
541
542static PyMethodDef pkgcore_posix_methods[] = {
543    {"normpath", (PyCFunction)pkgcore_normpath, METH_O,
544        "normalize a path entry"},
545    {"join", pkgcore_join, METH_VARARGS,
546        "join multiple path items"},
547    {"readfile", pkgcore_readfile, METH_VARARGS,
548        "fast read of a file: requires a string path, and an optional bool "
549        "indicating whether to swallow ENOENT; defaults to false"},
550    {NULL}
551};
552
553PyDoc_STRVAR(
554    pkgcore_posix_documentation,
555    "cpython posix path functionality");
556
557PyMODINIT_FUNC
558init_posix()
559{
560    PyObject *m = Py_InitModule3("_posix", pkgcore_posix_methods,
561                                 pkgcore_posix_documentation);
562    if (!m)
563        return;
564
565    if (PyType_Ready(&pkgcore_readlines_type) < 0)
566        return;
567
568    Py_INCREF(&pkgcore_readlines_type);
569    if (PyModule_AddObject(
570            m, "readlines", (PyObject *)&pkgcore_readlines_type) == -1)
571        return;
572
573    /* Success! */
574}
Note: See TracBrowser for help on using the browser.