1 /*
2 * Copyright (C) 2009 Jelmer Vernooij <jelmer@jelmer.uk>
3 *
4 * Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
5 * General Public License as public by the Free Software Foundation; version 2.0
6 * or (at your option) any later version. You can redistribute it and/or
7 * modify it under the terms of either of these two licenses.
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 *
15 * You should have received a copy of the licenses; if not, see
16 * <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
17 * and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
18 * License, Version 2.0.
19 */
20
21 #define PY_SSIZE_T_CLEAN
22 #include <Python.h>
23 #include <stdlib.h>
24 #include <sys/stat.h>
25
26 #if PY_MAJOR_VERSION >= 3
27 #define PyInt_Check(obj) 0
28 #define PyInt_CheckExact(obj) 0
29 #define PyInt_AsLong PyLong_AsLong
30 #define PyString_AS_STRING PyBytes_AS_STRING
31 #define PyString_Check PyBytes_Check
32 #define PyString_FromStringAndSize PyBytes_FromStringAndSize
33 #endif
34
35 #if defined(__MINGW32_VERSION) || defined(__APPLE__)
36 size_t rep_strnlen(char *text, size_t maxlen);
rep_strnlen(char * text,size_t maxlen)37 size_t rep_strnlen(char *text, size_t maxlen)
38 {
39 const char *last = memchr(text, '\0', maxlen);
40 return last ? (size_t) (last - text) : maxlen;
41 }
42 #define strnlen rep_strnlen
43 #endif
44
45 #define bytehex(x) (((x)<0xa)?('0'+(x)):('a'-0xa+(x)))
46
47 static PyObject *tree_entry_cls;
48 static PyObject *object_format_exception_cls;
49
sha_to_pyhex(const unsigned char * sha)50 static PyObject *sha_to_pyhex(const unsigned char *sha)
51 {
52 char hexsha[41];
53 int i;
54 for (i = 0; i < 20; i++) {
55 hexsha[i*2] = bytehex((sha[i] & 0xF0) >> 4);
56 hexsha[i*2+1] = bytehex(sha[i] & 0x0F);
57 }
58
59 return PyString_FromStringAndSize(hexsha, 40);
60 }
61
py_parse_tree(PyObject * self,PyObject * args,PyObject * kw)62 static PyObject *py_parse_tree(PyObject *self, PyObject *args, PyObject *kw)
63 {
64 char *text, *start, *end;
65 Py_ssize_t len; int strict;
66 size_t namelen;
67 PyObject *ret, *item, *name, *sha, *py_strict = NULL;
68 static char *kwlist[] = {"text", "strict", NULL};
69
70 #if PY_MAJOR_VERSION >= 3
71 if (!PyArg_ParseTupleAndKeywords(args, kw, "y#|O", kwlist,
72 &text, &len, &py_strict))
73 #else
74 if (!PyArg_ParseTupleAndKeywords(args, kw, "s#|O", kwlist,
75 &text, &len, &py_strict))
76 #endif
77 return NULL;
78 strict = py_strict ? PyObject_IsTrue(py_strict) : 0;
79 /* TODO: currently this returns a list; if memory usage is a concern,
80 * consider rewriting as a custom iterator object */
81 ret = PyList_New(0);
82 if (ret == NULL) {
83 return NULL;
84 }
85 start = text;
86 end = text + len;
87 while (text < end) {
88 long mode;
89 if (strict && text[0] == '0') {
90 PyErr_SetString(object_format_exception_cls,
91 "Illegal leading zero on mode");
92 Py_DECREF(ret);
93 return NULL;
94 }
95 mode = strtol(text, &text, 8);
96 if (*text != ' ') {
97 PyErr_SetString(PyExc_ValueError, "Expected space");
98 Py_DECREF(ret);
99 return NULL;
100 }
101 text++;
102 namelen = strnlen(text, len - (text - start));
103 name = PyString_FromStringAndSize(text, namelen);
104 if (name == NULL) {
105 Py_DECREF(ret);
106 return NULL;
107 }
108 if (text + namelen + 20 >= end) {
109 PyErr_SetString(PyExc_ValueError, "SHA truncated");
110 Py_DECREF(ret);
111 Py_DECREF(name);
112 return NULL;
113 }
114 sha = sha_to_pyhex((unsigned char *)text+namelen+1);
115 if (sha == NULL) {
116 Py_DECREF(ret);
117 Py_DECREF(name);
118 return NULL;
119 }
120 item = Py_BuildValue("(NlN)", name, mode, sha);
121 if (item == NULL) {
122 Py_DECREF(ret);
123 Py_DECREF(sha);
124 Py_DECREF(name);
125 return NULL;
126 }
127 if (PyList_Append(ret, item) == -1) {
128 Py_DECREF(ret);
129 Py_DECREF(item);
130 return NULL;
131 }
132 Py_DECREF(item);
133 text += namelen+21;
134 }
135 return ret;
136 }
137
138 struct tree_item {
139 const char *name;
140 int mode;
141 PyObject *tuple;
142 };
143
cmp_tree_item(const void * _a,const void * _b)144 int cmp_tree_item(const void *_a, const void *_b)
145 {
146 const struct tree_item *a = _a, *b = _b;
147 const char *remain_a, *remain_b;
148 int ret;
149 size_t common;
150 if (strlen(a->name) > strlen(b->name)) {
151 common = strlen(b->name);
152 remain_a = a->name + common;
153 remain_b = (S_ISDIR(b->mode)?"/":"");
154 } else if (strlen(b->name) > strlen(a->name)) {
155 common = strlen(a->name);
156 remain_a = (S_ISDIR(a->mode)?"/":"");
157 remain_b = b->name + common;
158 } else { /* strlen(a->name) == strlen(b->name) */
159 common = 0;
160 remain_a = a->name;
161 remain_b = b->name;
162 }
163 ret = strncmp(a->name, b->name, common);
164 if (ret != 0)
165 return ret;
166 return strcmp(remain_a, remain_b);
167 }
168
cmp_tree_item_name_order(const void * _a,const void * _b)169 int cmp_tree_item_name_order(const void *_a, const void *_b) {
170 const struct tree_item *a = _a, *b = _b;
171 return strcmp(a->name, b->name);
172 }
173
py_sorted_tree_items(PyObject * self,PyObject * args)174 static PyObject *py_sorted_tree_items(PyObject *self, PyObject *args)
175 {
176 struct tree_item *qsort_entries = NULL;
177 int name_order, n = 0, i;
178 PyObject *entries, *py_name_order, *ret, *key, *value, *py_mode, *py_sha;
179 Py_ssize_t pos = 0, num_entries;
180 int (*cmp)(const void *, const void *);
181
182 if (!PyArg_ParseTuple(args, "OO", &entries, &py_name_order))
183 goto error;
184
185 if (!PyDict_Check(entries)) {
186 PyErr_SetString(PyExc_TypeError, "Argument not a dictionary");
187 goto error;
188 }
189
190 name_order = PyObject_IsTrue(py_name_order);
191 if (name_order == -1)
192 goto error;
193 cmp = name_order ? cmp_tree_item_name_order : cmp_tree_item;
194
195 num_entries = PyDict_Size(entries);
196 if (PyErr_Occurred())
197 goto error;
198 qsort_entries = PyMem_New(struct tree_item, num_entries);
199 if (!qsort_entries) {
200 PyErr_NoMemory();
201 goto error;
202 }
203
204 while (PyDict_Next(entries, &pos, &key, &value)) {
205 if (!PyString_Check(key)) {
206 PyErr_SetString(PyExc_TypeError, "Name is not a string");
207 goto error;
208 }
209
210 if (PyTuple_Size(value) != 2) {
211 PyErr_SetString(PyExc_ValueError, "Tuple has invalid size");
212 goto error;
213 }
214
215 py_mode = PyTuple_GET_ITEM(value, 0);
216 if (!PyInt_Check(py_mode) && !PyLong_Check(py_mode)) {
217 PyErr_SetString(PyExc_TypeError, "Mode is not an integral type");
218 goto error;
219 }
220
221 py_sha = PyTuple_GET_ITEM(value, 1);
222 if (!PyString_Check(py_sha)) {
223 PyErr_SetString(PyExc_TypeError, "SHA is not a string");
224 goto error;
225 }
226 qsort_entries[n].name = PyString_AS_STRING(key);
227 qsort_entries[n].mode = PyInt_AsLong(py_mode);
228
229 qsort_entries[n].tuple = PyObject_CallFunctionObjArgs(
230 tree_entry_cls, key, py_mode, py_sha, NULL);
231 if (qsort_entries[n].tuple == NULL)
232 goto error;
233 n++;
234 }
235
236 qsort(qsort_entries, num_entries, sizeof(struct tree_item), cmp);
237
238 ret = PyList_New(num_entries);
239 if (ret == NULL) {
240 PyErr_NoMemory();
241 goto error;
242 }
243
244 for (i = 0; i < num_entries; i++) {
245 PyList_SET_ITEM(ret, i, qsort_entries[i].tuple);
246 }
247 PyMem_Free(qsort_entries);
248 return ret;
249
250 error:
251 for (i = 0; i < n; i++) {
252 Py_XDECREF(qsort_entries[i].tuple);
253 }
254 PyMem_Free(qsort_entries);
255 return NULL;
256 }
257
258 static PyMethodDef py_objects_methods[] = {
259 { "parse_tree", (PyCFunction)py_parse_tree, METH_VARARGS | METH_KEYWORDS,
260 NULL },
261 { "sorted_tree_items", py_sorted_tree_items, METH_VARARGS, NULL },
262 { NULL, NULL, 0, NULL }
263 };
264
265 static PyObject *
moduleinit(void)266 moduleinit(void)
267 {
268 PyObject *m, *objects_mod, *errors_mod;
269
270 #if PY_MAJOR_VERSION >= 3
271 static struct PyModuleDef moduledef = {
272 PyModuleDef_HEAD_INIT,
273 "_objects", /* m_name */
274 NULL, /* m_doc */
275 -1, /* m_size */
276 py_objects_methods, /* m_methods */
277 NULL, /* m_reload */
278 NULL, /* m_traverse */
279 NULL, /* m_clear*/
280 NULL, /* m_free */
281 };
282 m = PyModule_Create(&moduledef);
283 #else
284 m = Py_InitModule3("_objects", py_objects_methods, NULL);
285 #endif
286 if (m == NULL) {
287 return NULL;
288 }
289
290 errors_mod = PyImport_ImportModule("dulwich.errors");
291 if (errors_mod == NULL) {
292 return NULL;
293 }
294
295 object_format_exception_cls = PyObject_GetAttrString(
296 errors_mod, "ObjectFormatException");
297 Py_DECREF(errors_mod);
298 if (object_format_exception_cls == NULL) {
299 return NULL;
300 }
301
302 /* This is a circular import but should be safe since this module is
303 * imported at at the very bottom of objects.py. */
304 objects_mod = PyImport_ImportModule("dulwich.objects");
305 if (objects_mod == NULL) {
306 return NULL;
307 }
308
309 tree_entry_cls = PyObject_GetAttrString(objects_mod, "TreeEntry");
310 Py_DECREF(objects_mod);
311 if (tree_entry_cls == NULL) {
312 return NULL;
313 }
314
315 return m;
316 }
317
318 #if PY_MAJOR_VERSION >= 3
319 PyMODINIT_FUNC
PyInit__objects(void)320 PyInit__objects(void)
321 {
322 return moduleinit();
323 }
324 #else
325 PyMODINIT_FUNC
init_objects(void)326 init_objects(void)
327 {
328 moduleinit();
329 }
330 #endif
331