dirs.c
313 lines
| 6.8 KiB
| text/x-c
|
CLexer
/ mercurial / dirs.c
|
r18900 | /* | ||
dirs.c - dynamic directory diddling for dirstates | ||||
Copyright 2013 Facebook | ||||
This software may be used and distributed according to the terms of | ||||
the GNU General Public License, incorporated herein by reference. | ||||
*/ | ||||
#define PY_SSIZE_T_CLEAN | ||||
#include <Python.h> | ||||
#include "util.h" | ||||
|
r30106 | #ifdef IS_PY3K | ||
#define PYLONG_VALUE(o) ((PyLongObject *)o)->ob_digit[1] | ||||
#else | ||||
#define PYLONG_VALUE(o) PyInt_AS_LONG(o) | ||||
#endif | ||||
|
r18900 | /* | ||
* This is a multiset of directory names, built from the files that | ||||
* appear in a dirstate or manifest. | ||||
|
r18901 | * | ||
* A few implementation notes: | ||||
* | ||||
* We modify Python integers for refcounting, but those integers are | ||||
* never visible to Python code. | ||||
|
r18902 | * | ||
* We mutate strings in-place, but leave them immutable once they can | ||||
* be seen by Python code. | ||||
|
r18900 | */ | ||
typedef struct { | ||||
PyObject_HEAD | ||||
PyObject *dict; | ||||
} dirsObject; | ||||
|
r25093 | static inline Py_ssize_t _finddir(const char *path, Py_ssize_t pos) | ||
|
r18900 | { | ||
|
r25015 | while (pos != -1) { | ||
|
r25093 | if (path[pos] == '/') | ||
|
r25015 | break; | ||
pos -= 1; | ||||
} | ||||
return pos; | ||||
|
r18900 | } | ||
static int _addpath(PyObject *dirs, PyObject *path) | ||||
{ | ||||
|
r30105 | const char *cpath = PyBytes_AS_STRING(path); | ||
Py_ssize_t pos = PyBytes_GET_SIZE(path); | ||||
|
r18901 | PyObject *key = NULL; | ||
|
r18900 | int ret = -1; | ||
|
r30107 | /* This loop is super critical for performance. That's why we inline | ||
* access to Python structs instead of going through a supported API. | ||||
* The implementation, therefore, is heavily dependent on CPython | ||||
* implementation details. We also commit violations of the Python | ||||
* "protocol" such as mutating immutable objects. But since we only | ||||
* mutate objects created in this function or in other well-defined | ||||
* locations, the references are known so these violations should go | ||||
|
r30139 | * unnoticed. The code for adjusting the length of a PyBytesObject is | ||
* essentially a minimal version of _PyBytes_Resize. */ | ||||
|
r25093 | while ((pos = _finddir(cpath, pos - 1)) != -1) { | ||
|
r18900 | PyObject *val; | ||
|
r18902 | /* It's likely that every prefix already has an entry | ||
in our dict. Try to avoid allocating and | ||||
deallocating a string for each prefix we check. */ | ||||
if (key != NULL) | ||||
|
r30105 | ((PyBytesObject *)key)->ob_shash = -1; | ||
|
r25015 | else { | ||
/* Force Python to not reuse a small shared string. */ | ||||
|
r30105 | key = PyBytes_FromStringAndSize(cpath, | ||
|
r25015 | pos < 2 ? 2 : pos); | ||
|
r18902 | if (key == NULL) | ||
goto bail; | ||||
} | ||||
|
r30104 | Py_SIZE(key) = pos; | ||
|
r30105 | ((PyBytesObject *)key)->ob_sval[pos] = '\0'; | ||
|
r18900 | |||
val = PyDict_GetItem(dirs, key); | ||||
|
r18901 | if (val != NULL) { | ||
|
r30106 | PYLONG_VALUE(val) += 1; | ||
|
r25016 | break; | ||
|
r18901 | } | ||
|
r18900 | |||
|
r18901 | /* Force Python to not reuse a small shared int. */ | ||
|
r30106 | #ifdef IS_PY3K | ||
val = PyLong_FromLong(0x1eadbeef); | ||||
#else | ||||
|
r18901 | val = PyInt_FromLong(0x1eadbeef); | ||
|
r30106 | #endif | ||
|
r18900 | |||
|
r18901 | if (val == NULL) | ||
|
r18900 | goto bail; | ||
|
r30106 | PYLONG_VALUE(val) = 1; | ||
|
r18901 | ret = PyDict_SetItem(dirs, key, val); | ||
Py_DECREF(val); | ||||
|
r18900 | if (ret == -1) | ||
goto bail; | ||||
|
r24651 | Py_CLEAR(key); | ||
|
r18900 | } | ||
ret = 0; | ||||
bail: | ||||
Py_XDECREF(key); | ||||
return ret; | ||||
} | ||||
static int _delpath(PyObject *dirs, PyObject *path) | ||||
{ | ||||
|
r30105 | char *cpath = PyBytes_AS_STRING(path); | ||
Py_ssize_t pos = PyBytes_GET_SIZE(path); | ||||
|
r18901 | PyObject *key = NULL; | ||
|
r18900 | int ret = -1; | ||
|
r25093 | while ((pos = _finddir(cpath, pos - 1)) != -1) { | ||
|
r18900 | PyObject *val; | ||
|
r30105 | key = PyBytes_FromStringAndSize(cpath, pos); | ||
|
r18900 | |||
if (key == NULL) | ||||
goto bail; | ||||
val = PyDict_GetItem(dirs, key); | ||||
if (val == NULL) { | ||||
PyErr_SetString(PyExc_ValueError, | ||||
"expected a value, found none"); | ||||
goto bail; | ||||
} | ||||
|
r30106 | if (--PYLONG_VALUE(val) <= 0) { | ||
|
r25016 | if (PyDict_DelItem(dirs, key) == -1) | ||
goto bail; | ||||
} else | ||||
break; | ||||
|
r18900 | Py_CLEAR(key); | ||
} | ||||
ret = 0; | ||||
bail: | ||||
Py_XDECREF(key); | ||||
return ret; | ||||
} | ||||
static int dirs_fromdict(PyObject *dirs, PyObject *source, char skipchar) | ||||
{ | ||||
PyObject *key, *value; | ||||
Py_ssize_t pos = 0; | ||||
while (PyDict_Next(source, &pos, &key, &value)) { | ||||
|
r30105 | if (!PyBytes_Check(key)) { | ||
|
r18900 | PyErr_SetString(PyExc_TypeError, "expected string key"); | ||
return -1; | ||||
} | ||||
if (skipchar) { | ||||
|
r21809 | if (!dirstate_tuple_check(value)) { | ||
|
r18900 | PyErr_SetString(PyExc_TypeError, | ||
|
r21809 | "expected a dirstate tuple"); | ||
|
r18900 | return -1; | ||
} | ||||
|
r21809 | if (((dirstateTupleObject *)value)->state == skipchar) | ||
|
r18900 | continue; | ||
} | ||||
if (_addpath(dirs, key) == -1) | ||||
return -1; | ||||
} | ||||
return 0; | ||||
} | ||||
static int dirs_fromiter(PyObject *dirs, PyObject *source) | ||||
{ | ||||
PyObject *iter, *item = NULL; | ||||
int ret; | ||||
iter = PyObject_GetIter(source); | ||||
if (iter == NULL) | ||||
return -1; | ||||
while ((item = PyIter_Next(iter)) != NULL) { | ||||
|
r30105 | if (!PyBytes_Check(item)) { | ||
|
r18900 | PyErr_SetString(PyExc_TypeError, "expected string"); | ||
break; | ||||
} | ||||
if (_addpath(dirs, item) == -1) | ||||
break; | ||||
Py_CLEAR(item); | ||||
} | ||||
ret = PyErr_Occurred() ? -1 : 0; | ||||
|
r23960 | Py_DECREF(iter); | ||
|
r18900 | Py_XDECREF(item); | ||
return ret; | ||||
} | ||||
/* | ||||
* Calculate a refcounted set of directory names for the files in a | ||||
* dirstate. | ||||
*/ | ||||
static int dirs_init(dirsObject *self, PyObject *args) | ||||
{ | ||||
PyObject *dirs = NULL, *source = NULL; | ||||
char skipchar = 0; | ||||
int ret = -1; | ||||
self->dict = NULL; | ||||
if (!PyArg_ParseTuple(args, "|Oc:__init__", &source, &skipchar)) | ||||
return -1; | ||||
dirs = PyDict_New(); | ||||
if (dirs == NULL) | ||||
return -1; | ||||
if (source == NULL) | ||||
ret = 0; | ||||
else if (PyDict_Check(source)) | ||||
ret = dirs_fromdict(dirs, source, skipchar); | ||||
else if (skipchar) | ||||
PyErr_SetString(PyExc_ValueError, | ||||
"skip character is only supported " | ||||
"with a dict source"); | ||||
else | ||||
ret = dirs_fromiter(dirs, source); | ||||
if (ret == -1) | ||||
Py_XDECREF(dirs); | ||||
else | ||||
self->dict = dirs; | ||||
return ret; | ||||
} | ||||
PyObject *dirs_addpath(dirsObject *self, PyObject *args) | ||||
{ | ||||
PyObject *path; | ||||
|
r30105 | if (!PyArg_ParseTuple(args, "O!:addpath", &PyBytes_Type, &path)) | ||
|
r18900 | return NULL; | ||
if (_addpath(self->dict, path) == -1) | ||||
return NULL; | ||||
Py_RETURN_NONE; | ||||
} | ||||
static PyObject *dirs_delpath(dirsObject *self, PyObject *args) | ||||
{ | ||||
PyObject *path; | ||||
|
r30105 | if (!PyArg_ParseTuple(args, "O!:delpath", &PyBytes_Type, &path)) | ||
|
r18900 | return NULL; | ||
if (_delpath(self->dict, path) == -1) | ||||
return NULL; | ||||
Py_RETURN_NONE; | ||||
} | ||||
static int dirs_contains(dirsObject *self, PyObject *value) | ||||
{ | ||||
|
r30105 | return PyBytes_Check(value) ? PyDict_Contains(self->dict, value) : 0; | ||
|
r18900 | } | ||
static void dirs_dealloc(dirsObject *self) | ||||
{ | ||||
Py_XDECREF(self->dict); | ||||
PyObject_Del(self); | ||||
} | ||||
static PyObject *dirs_iter(dirsObject *self) | ||||
{ | ||||
return PyObject_GetIter(self->dict); | ||||
} | ||||
static PySequenceMethods dirs_sequence_methods; | ||||
static PyMethodDef dirs_methods[] = { | ||||
{"addpath", (PyCFunction)dirs_addpath, METH_VARARGS, "add a path"}, | ||||
{"delpath", (PyCFunction)dirs_delpath, METH_VARARGS, "remove a path"}, | ||||
{NULL} /* Sentinel */ | ||||
}; | ||||
static PyTypeObject dirsType = { PyObject_HEAD_INIT(NULL) }; | ||||
void dirs_module_init(PyObject *mod) | ||||
{ | ||||
dirs_sequence_methods.sq_contains = (objobjproc)dirs_contains; | ||||
dirsType.tp_name = "parsers.dirs"; | ||||
dirsType.tp_new = PyType_GenericNew; | ||||
dirsType.tp_basicsize = sizeof(dirsObject); | ||||
dirsType.tp_dealloc = (destructor)dirs_dealloc; | ||||
dirsType.tp_as_sequence = &dirs_sequence_methods; | ||||
dirsType.tp_flags = Py_TPFLAGS_DEFAULT; | ||||
dirsType.tp_doc = "dirs"; | ||||
dirsType.tp_iter = (getiterfunc)dirs_iter; | ||||
dirsType.tp_methods = dirs_methods; | ||||
dirsType.tp_init = (initproc)dirs_init; | ||||
if (PyType_Ready(&dirsType) < 0) | ||||
return; | ||||
Py_INCREF(&dirsType); | ||||
PyModule_AddObject(mod, "dirs", (PyObject *)&dirsType); | ||||
} | ||||