aboutsummaryrefslogtreecommitdiff
blob: e812ee629299e070865debd45a64952b8bc7d361 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# distutils: language = c
# cython: language_level = 3

from cpython.bytes cimport PyBytes_AS_STRING
from libc.string cimport strdup
from libc.stdio cimport snprintf
from libc.stdlib cimport atoi, malloc, free
from posix.unistd cimport close, getpid


cdef extern from "ctype.h" nogil:
    int isdigit(int c)


cdef inline void SKIP_SLASHES(char **s):
    """Skip slashes in a given string."""
    while (b'/' == s[0][0]):
        s[0] += 1


cdef bytes _chars(s):
    """Convert input string to bytes."""
    if isinstance(s, unicode):
        # encode to the specific encoding used inside of the module
        return (<unicode>s).encode('utf8')
    elif isinstance(s, bytes):
        return s
    else:
        raise TypeError("arg must be str or bytes, not %s" % type(s).__name__)


def normpath(old_path):
    """Normalize a path entry."""
    cdef char *path = strdup(PyBytes_AS_STRING(_chars(old_path)))
    if not path:
        raise MemoryError()
    cdef char *read = path
    cdef char *new_path = strdup(path)
    if not new_path:
        raise MemoryError()
    cdef char *write = new_path
    cdef int depth = 0
    cdef bint is_absolute = b'/' == path[0]

    if is_absolute:
        depth -= 1

    while b'\0' != read[0]:
        if b'/' == read[0]:
            write[0] = b'/'
            write += 1
            SKIP_SLASHES(&read)
            depth += 1
        elif b'.' == read[0]:
            if b'.' == read[1] and (b'/' == read[2] or b'\0' == read[2]):
                if depth == 1:
                    if is_absolute:
                        write = new_path
                    else:
                        # why -2?  because write is at an empty char.
                        # we need to jump back past it and /
                        write -= 2
                        while b'/' != write[0]:
                            write -= 1
                    write += 1
                    depth = 0
                elif depth:
                    write -= 2
                    while b'/' != write[0]:
                        write -= 1
                    write += 1
                    depth -= 1
                else:
                    if is_absolute:
                        write = new_path + 1
                    else:
                        write[0] = b'.'
                        write[1] = b'.'
                        write[2] = b'/'
                        write += 3
                read += 2
                SKIP_SLASHES(&read)
            elif b'/' == read[1]:
                read += 2
                SKIP_SLASHES(&read)
            elif b'\0' == read[1]:
                read += 1
            else:
                write[0] = b'.'
                read += 1
                write += 1
        else:
            while b'/' != read[0] and b'\0' != read[0]:
                write[0] = read[0]
                write += 1
                read += 1

    if write - 1 > new_path and b'/' == write[-1]:
        write -= 1

    new_path[write - new_path] = 0

    cdef bytes py_path
    try:
        py_path = new_path[:write - new_path]
    finally:
        free(new_path)
        free(path)

    if isinstance(old_path, unicode):
        return py_path.decode('utf-8', 'strict')
    return py_path


def join(*args):
    """Join multiple path items."""
    cdef ssize_t end = len(args)
    cdef ssize_t start = 0, length = 0, i = 0
    cdef bint leading_slash = False
    cdef char **paths = <char **>malloc(end * sizeof(char *))

    if not end:
        raise TypeError("join takes at least one argument (0 given)")

    for i in range(end):
        paths[i] = strdup(PyBytes_AS_STRING(_chars(args[i])))

        # find the right most item with a prefixed '/', else 0
        if b'/' == paths[i][0]:
            leading_slash = True
            start = i

    # know the relevant slice now; figure out the size.
    cdef char *s_start
    cdef char *s_end
    cdef char *s

    for i in range(start, end):
        # this is safe because we're checking types above
        s_start = s = paths[i]
        while b'\0' != s[0]:
            s += 1
        if s_start == s:
            continue
        length += s - s_start
        s_end = s
        if i + 1 != end:
            # cut the length down for trailing duplicate slashes
            while s != s_start and b'/' == s[-1]:
                s -= 1
            # allocate for a leading slash if needed
            if (s_end == s and (s_start != s or
                    (s_end == s_start and i != start))):
                length += 1
            elif s_start != s:
                length -= s_end - s - 1

    # ok... we know the length.  allocate a string, and copy it.
    cdef char *ret = <char *>malloc((length + 1) * sizeof(char))
    if not ret:
        raise MemoryError()

    cdef char *tmp_s
    cdef char *buf = ret

    if leading_slash:
        buf[0] = b'/'
        buf += 1

    for i in range(start, end):
        s_start = s = paths[i]
        if i == start and leading_slash:
            # a slash is inserted anyways, thus we skip one ahead
            # so it doesn't gain an extra.
            s_start += 1
            s = s_start

        if b'\0' == s[0]:
            continue
        while b'\0' != s[0]:
            buf[0] = s[0]
            buf += 1
            if b'/' == s[0]:
                tmp_s = s + 1
                SKIP_SLASHES(&s)
                if b'\0' == s[0]:
                    if i + 1  != end:
                        buf -= 1
                    else:
                        # copy the cracked out trailing slashes on the
                        # last item
                        while tmp_s < s:
                            buf[0] = b'/'
                            buf += 1
                            tmp_s += 1
                    break
                else:
                    # copy the cracked out intermediate slashes.
                    while tmp_s < s:
                        buf[0] = b'/'
                        buf += 1
                        tmp_s += 1
            else:
                s += 1

        if i + 1 != end:
            buf[0] = b'/'
            buf += 1

    buf[0] = b'\0'

    cdef bytes py_path
    try:
        py_path = ret[:length]
    finally:
        free(ret)
        for i in range(end):
            free(paths[i])
        free(paths)

    if isinstance(args[0], unicode):
        return py_path.decode('utf-8', 'strict')
    return py_path


cdef void slow_closerange(int start, int end):
    cdef int i
    for i in range(start, end):
        close(i)


cdef extern from "dirent.h" nogil:
    cdef struct dirent:
        char *d_name
    ctypedef struct DIR
    int dirfd(DIR *dirp)
    DIR *opendir(char *name)
    int closedir(DIR *dirp)
    dirent *readdir(DIR *dirp)
    int readdir_r(DIR *dirp, dirent *entry, dirent **result)


def closerange(int start, int end):
    """Close a range of fds."""
    cdef int i, fd_dir

    if start >= end:
        return

    cdef DIR *dir_handle
    cdef dirent *entry
    # this is sufficient for a 64-bit pid_t
    cdef char path[32]

    # Note that the version I submitted to python upstream has this in a
    # ALLOW_THREADS block; snakeoil doesn't since it's pointless.
    # Realistically the only time this code is ever ran is immediately post
    # fork- where no threads can be running. Thus no gain to releasing the GIL
    # then reacquiring it, thus we skip it.

    snprintf(path, sizeof(path), "/proc/%i/fd", getpid())

    dir_handle = opendir(path)
    if dir_handle == NULL:
        slow_closerange(start, end)
        return

    fd_dir = dirfd(dir_handle)

    if fd_dir < 0:
        closedir(dir_handle)
        slow_closerange(start, end)
        return

    while True:
        entry = readdir(dir_handle)
        if entry == NULL:
            break

        if not isdigit(entry.d_name[0]):
            continue

        i = atoi(entry.d_name)
        if i >= start and i < end and i != fd_dir:
            close(i)

    closedir(dir_handle)