root/releases/pkgcore/0.2.4/src/posix.c @ ferringb%2540gmail.com-20070216105940-2k1ibdbm8j5skdhe

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

drop a duplicate stat call from per cache access; the stat data is already available, expose it on the iterable.

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