|
|
/*
|
|
|
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 <string.h>
|
|
|
#include "util.h"
|
|
|
|
|
|
/*
|
|
|
* This is a multiset of directory names, built from the files that
|
|
|
* appear in a dirstate or manifest.
|
|
|
*
|
|
|
* A few implementation notes:
|
|
|
*
|
|
|
* We modify Python integers for refcounting, but those integers are
|
|
|
* never visible to Python code.
|
|
|
*
|
|
|
* We mutate strings in-place, but leave them immutable once they can
|
|
|
* be seen by Python code.
|
|
|
*/
|
|
|
typedef struct {
|
|
|
PyObject_HEAD
|
|
|
PyObject *dict;
|
|
|
} dirsObject;
|
|
|
|
|
|
static inline Py_ssize_t _finddir(PyObject *path, Py_ssize_t pos)
|
|
|
{
|
|
|
const char *s = PyString_AS_STRING(path);
|
|
|
|
|
|
const char *ret = strchr(s + pos, '/');
|
|
|
return (ret != NULL) ? (ret - s) : -1;
|
|
|
}
|
|
|
|
|
|
static int _addpath(PyObject *dirs, PyObject *path)
|
|
|
{
|
|
|
char *cpath = PyString_AS_STRING(path);
|
|
|
Py_ssize_t len = PyString_GET_SIZE(path);
|
|
|
Py_ssize_t pos = -1;
|
|
|
PyObject *key = NULL;
|
|
|
int ret = -1;
|
|
|
|
|
|
while ((pos = _finddir(path, pos + 1)) != -1) {
|
|
|
PyObject *val;
|
|
|
|
|
|
/* 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)
|
|
|
((PyStringObject *)key)->ob_shash = -1;
|
|
|
else if (pos != 0) {
|
|
|
/* pos >= 1, which means that len >= 2. This is
|
|
|
guaranteed to produce a non-interned string. */
|
|
|
key = PyString_FromStringAndSize(cpath, len);
|
|
|
if (key == NULL)
|
|
|
goto bail;
|
|
|
} else {
|
|
|
/* pos == 0, which means we need to increment the dir
|
|
|
count for the empty string. We need to make sure we
|
|
|
don't muck around with interned strings, so throw it
|
|
|
away later. */
|
|
|
key = PyString_FromString("");
|
|
|
if (key == NULL)
|
|
|
goto bail;
|
|
|
}
|
|
|
PyString_GET_SIZE(key) = pos;
|
|
|
PyString_AS_STRING(key)[pos] = '\0';
|
|
|
|
|
|
val = PyDict_GetItem(dirs, key);
|
|
|
if (val != NULL) {
|
|
|
PyInt_AS_LONG(val) += 1;
|
|
|
if (pos != 0)
|
|
|
PyString_AS_STRING(key)[pos] = '/';
|
|
|
else
|
|
|
key = NULL;
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
/* Force Python to not reuse a small shared int. */
|
|
|
val = PyInt_FromLong(0x1eadbeef);
|
|
|
|
|
|
if (val == NULL)
|
|
|
goto bail;
|
|
|
|
|
|
PyInt_AS_LONG(val) = 1;
|
|
|
ret = PyDict_SetItem(dirs, key, val);
|
|
|
Py_DECREF(val);
|
|
|
if (ret == -1)
|
|
|
goto bail;
|
|
|
|
|
|
/* Clear the key out since we've already exposed it to Python
|
|
|
and can't mutate it further. key's refcount is currently 2 so
|
|
|
we can't just use Py_CLEAR. */
|
|
|
Py_DECREF(key);
|
|
|
key = NULL;
|
|
|
}
|
|
|
ret = 0;
|
|
|
|
|
|
bail:
|
|
|
Py_XDECREF(key);
|
|
|
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
static int _delpath(PyObject *dirs, PyObject *path)
|
|
|
{
|
|
|
Py_ssize_t pos = -1;
|
|
|
PyObject *key = NULL;
|
|
|
int ret = -1;
|
|
|
|
|
|
while ((pos = _finddir(path, pos + 1)) != -1) {
|
|
|
PyObject *val;
|
|
|
|
|
|
key = PyString_FromStringAndSize(PyString_AS_STRING(path), pos);
|
|
|
|
|
|
if (key == NULL)
|
|
|
goto bail;
|
|
|
|
|
|
val = PyDict_GetItem(dirs, key);
|
|
|
if (val == NULL) {
|
|
|
PyErr_SetString(PyExc_ValueError,
|
|
|
"expected a value, found none");
|
|
|
goto bail;
|
|
|
}
|
|
|
|
|
|
if (--PyInt_AS_LONG(val) <= 0 &&
|
|
|
PyDict_DelItem(dirs, key) == -1)
|
|
|
goto bail;
|
|
|
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)) {
|
|
|
if (!PyString_Check(key)) {
|
|
|
PyErr_SetString(PyExc_TypeError, "expected string key");
|
|
|
return -1;
|
|
|
}
|
|
|
if (skipchar) {
|
|
|
if (!dirstate_tuple_check(value)) {
|
|
|
PyErr_SetString(PyExc_TypeError,
|
|
|
"expected a dirstate tuple");
|
|
|
return -1;
|
|
|
}
|
|
|
if (((dirstateTupleObject *)value)->state == skipchar)
|
|
|
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) {
|
|
|
if (!PyString_Check(item)) {
|
|
|
PyErr_SetString(PyExc_TypeError, "expected string");
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
if (_addpath(dirs, item) == -1)
|
|
|
break;
|
|
|
Py_CLEAR(item);
|
|
|
}
|
|
|
|
|
|
ret = PyErr_Occurred() ? -1 : 0;
|
|
|
Py_DECREF(iter);
|
|
|
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;
|
|
|
|
|
|
if (!PyArg_ParseTuple(args, "O!:addpath", &PyString_Type, &path))
|
|
|
return NULL;
|
|
|
|
|
|
if (_addpath(self->dict, path) == -1)
|
|
|
return NULL;
|
|
|
|
|
|
Py_RETURN_NONE;
|
|
|
}
|
|
|
|
|
|
static PyObject *dirs_delpath(dirsObject *self, PyObject *args)
|
|
|
{
|
|
|
PyObject *path;
|
|
|
|
|
|
if (!PyArg_ParseTuple(args, "O!:delpath", &PyString_Type, &path))
|
|
|
return NULL;
|
|
|
|
|
|
if (_delpath(self->dict, path) == -1)
|
|
|
return NULL;
|
|
|
|
|
|
Py_RETURN_NONE;
|
|
|
}
|
|
|
|
|
|
static int dirs_contains(dirsObject *self, PyObject *value)
|
|
|
{
|
|
|
return PyString_Check(value) ? PyDict_Contains(self->dict, value) : 0;
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
|