_actions.c revision 2639
409N/A/*
49N/A * CDDL HEADER START
49N/A *
49N/A * The contents of this file are subject to the terms of the
49N/A * Common Development and Distribution License (the "License").
49N/A * You may not use this file except in compliance with the License.
49N/A *
49N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
49N/A * or http://www.opensolaris.org/os/licensing.
49N/A * See the License for the specific language governing permissions
49N/A * and limitations under the License.
49N/A *
49N/A * When distributing Covered Code, include this CDDL HEADER in each
49N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
49N/A * If applicable, add the following below this CDDL HEADER, with the
49N/A * fields enclosed by brackets "[]" replaced with your own identifying
49N/A * information: Portions Copyright [yyyy] [name of copyright owner]
49N/A *
49N/A * CDDL HEADER END
49N/A */
49N/A
49N/A/*
49N/A * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
873N/A */
49N/A
49N/A#include <Python.h>
49N/A
49N/A#include <string.h>
49N/A
49N/Astatic PyObject *MalformedActionError;
49N/Astatic PyObject *InvalidActionError;
49N/Astatic PyObject *UnknownActionError;
49N/Astatic PyObject *aclass_attribute;
49N/Astatic PyObject *aclass_depend;
49N/Astatic PyObject *aclass_directory;
51N/Astatic PyObject *aclass_driver;
51N/Astatic PyObject *aclass_file;
51N/Astatic PyObject *aclass_group;
51N/Astatic PyObject *aclass_hardlink;
51N/Astatic PyObject *aclass_legacy;
49N/Astatic PyObject *aclass_license;
49N/Astatic PyObject *aclass_link;
49N/Astatic PyObject *aclass_signature;
49N/Astatic PyObject *aclass_unknown;
49N/Astatic PyObject *aclass_user;
49N/A
49N/Astatic const char *notident = "hash attribute not identical to positional hash";
49N/A
49N/Astatic inline int
49N/Aadd_to_attrs(PyObject *attrs, PyObject *key, PyObject *attr)
49N/A{
49N/A int ret;
49N/A PyObject *list;
49N/A PyObject *av = PyDict_GetItem(attrs, key);
49N/A
49N/A if (av == NULL)
49N/A return (PyDict_SetItem(attrs, key, attr));
49N/A
49N/A if (PyList_CheckExact(av))
49N/A return (PyList_Append(av, attr));
49N/A
49N/A if ((list = PyList_New(2)) == NULL)
49N/A return (-1);
49N/A
49N/A /* PyList_SET_ITEM steals references. */
49N/A Py_INCREF(av);
49N/A PyList_SET_ITEM(list, 0, av);
49N/A Py_INCREF(attr);
49N/A PyList_SET_ITEM(list, 1, attr);
49N/A ret = PyDict_SetItem(attrs, key, list);
51N/A Py_DECREF(list);
49N/A return (ret);
49N/A}
49N/A
51N/Astatic void
591N/Aset_malformederr(const char *str, int pos, const char *msg)
873N/A{
873N/A PyObject *val;
873N/A
873N/A if ((val = Py_BuildValue("sis", str, pos, msg)) != NULL) {
591N/A PyErr_SetObject(MalformedActionError, val);
591N/A Py_DECREF(val);
591N/A }
591N/A}
591N/A
591N/Astatic void
591N/Aset_invaliderr(const char *str, const char *msg)
591N/A{
873N/A PyObject *val;
873N/A
873N/A if ((val = Py_BuildValue("ss", str, msg)) != NULL) {
873N/A PyErr_SetObject(InvalidActionError, val);
873N/A Py_DECREF(val);
873N/A }
873N/A}
591N/A
591N/A/*ARGSUSED*/
591N/Astatic PyObject *
591N/Afromstr(PyObject *self, PyObject *args, PyObject *kwdict)
591N/A{
591N/A char *s = NULL;
591N/A char *str = NULL;
591N/A char *hashstr = NULL;
591N/A char *keystr = NULL;
591N/A int *slashmap = NULL;
873N/A int strl, typestrl;
873N/A int i, ks, vs, keysize;
873N/A int smlen, smpos;
873N/A char quote;
873N/A PyObject *act_args = NULL;
873N/A PyObject *act_class = NULL;
873N/A PyObject *act_data = NULL;
873N/A PyObject *action = NULL;
873N/A PyObject *hash = NULL;
873N/A PyObject *attrs = NULL;
873N/A PyObject *key = NULL;
873N/A PyObject *attr = NULL;
873N/A enum {
873N/A KEY, /* key */
873N/A UQVAL, /* unquoted value */
873N/A QVAL, /* quoted value */
873N/A WS /* whitespace */
873N/A } state;
873N/A
873N/A /*
873N/A * If malformed() or invalid() are used, CLEANUP_REFS can only be used
873N/A * after. Likewise, PyMem_Free(str) should not be called before using
873N/A * malformed() or invalid(). Failure to order this properly will cause
873N/A * corruption of the exception messages.
873N/A */
873N/A#define malformed(msg) set_malformederr(str, i, (msg))
873N/A#define invalid(msg) set_invaliderr(str, (msg))
873N/A#define CLEANUP_REFS \
873N/A PyMem_Free(str);\
873N/A Py_XDECREF(key);\
873N/A Py_XDECREF(attr);\
873N/A Py_XDECREF(attrs);\
873N/A Py_XDECREF(hash);\
873N/A free(hashstr);
873N/A
873N/A /*
873N/A * Positional arguments must be included in the keyword argument list in
591N/A * the order you want them to be assigned. (A subtle point missing from
591N/A * the Python documentation.)
591N/A */
1292N/A static char *kwlist[] = { "string", "data", NULL };
1292N/A
1292N/A /* Assume data=None by default. */
1292N/A act_data = Py_None;
1292N/A
1292N/A /*
1292N/A * The action string is currently assumed to be a stream of bytes that
873N/A * are valid UTF-8. This method works regardless of whether the string
873N/A * object provided is a Unicode object, string object, or a character
873N/A * buffer.
279N/A */
591N/A if (PyArg_ParseTupleAndKeywords(args, kwdict, "et#|O:fromstr", kwlist,
591N/A "utf-8", &str, &strl, &act_data) == 0) {
51N/A return (NULL);
51N/A }
873N/A
873N/A s = strpbrk(str, " \t");
873N/A
873N/A i = strl;
873N/A if (s == NULL) {
873N/A malformed("no attributes");
51N/A PyMem_Free(str);
873N/A return (NULL);
873N/A }
873N/A
873N/A /*
873N/A * The comparisons here are ordered by frequency in which actions are
542N/A * most likely to be encountered in usage by the client grouped by
873N/A * length. Yes, a cheap hack to squeeze a tiny bit of additional
873N/A * performance out.
57N/A */
591N/A typestrl = s - str;
57N/A if (typestrl == 4) {
1500N/A if (strncmp(str, "file", 4) == 0)
279N/A act_class = aclass_file;
1500N/A else if (strncmp(str, "link", 4) == 0)
1500N/A act_class = aclass_link;
1500N/A else if (strncmp(str, "user", 4) == 0)
1500N/A act_class = aclass_user;
1500N/A } else if (typestrl == 6) {
1500N/A if (strncmp(str, "depend", 6) == 0)
1500N/A act_class = aclass_depend;
1500N/A else if (strncmp(str, "driver", 6) == 0)
1500N/A act_class = aclass_driver;
1500N/A else if (strncmp(str, "legacy", 6) == 0)
1500N/A act_class = aclass_legacy;
1500N/A } else if (typestrl == 3) {
1500N/A if (strncmp(str, "set", 3) == 0)
279N/A act_class = aclass_attribute;
591N/A else if (strncmp(str, "dir", 3) == 0)
279N/A act_class = aclass_directory;
305N/A } else if (typestrl == 8) {
1500N/A if (strncmp(str, "hardlink", 8) == 0)
1500N/A act_class = aclass_hardlink;
1500N/A } else if (typestrl == 7) {
873N/A if (strncmp(str, "license", 7) == 0)
1500N/A act_class = aclass_license;
1500N/A else if (strncmp(str, "unknown", 7) == 0)
1500N/A act_class = aclass_unknown;
1500N/A } else if (typestrl == 9) {
1500N/A if (strncmp(str, "signature", 9) == 0)
305N/A act_class = aclass_signature;
305N/A } else if (typestrl == 5) {
305N/A if (strncmp(str, "group", 5) == 0)
305N/A act_class = aclass_group;
305N/A }
591N/A
591N/A if (act_class == NULL) {
591N/A if ((act_args = Py_BuildValue("s#s#", str, strl,
591N/A str, typestrl)) != NULL) {
1500N/A PyErr_SetObject(UnknownActionError, act_args);
591N/A Py_DECREF(act_args);
57N/A PyMem_Free(str);
305N/A return (NULL);
305N/A }
305N/A
305N/A /*
305N/A * Unable to build argument list for exception; so raise
305N/A * general type exception instead.
305N/A */
305N/A PyErr_SetString(PyExc_TypeError, "unknown action type");
305N/A PyMem_Free(str);
305N/A return (NULL);
591N/A }
305N/A
591N/A ks = vs = typestrl;
591N/A state = WS;
591N/A if ((attrs = PyDict_New()) == NULL) {
591N/A PyMem_Free(str);
591N/A return (NULL);
591N/A }
1500N/A for (i = s - str; str[i]; i++) {
591N/A if (state == KEY) {
51N/A keysize = i - ks;
1500N/A keystr = &str[ks];
873N/A
873N/A if (str[i] == ' ' || str[i] == '\t') {
873N/A if (PyDict_Size(attrs) > 0 || hash != NULL) {
873N/A malformed("whitespace in key");
1500N/A CLEANUP_REFS;
873N/A return (NULL);
873N/A } else {
873N/A if ((hash = PyString_FromStringAndSize(
1500N/A keystr, keysize)) == NULL) {
1500N/A CLEANUP_REFS;
1500N/A return (NULL);
1500N/A }
1500N/A hashstr = strndup(keystr, keysize);
1500N/A state = WS;
1500N/A }
1500N/A } else if (str[i] == '=') {
1500N/A if ((key = PyString_FromStringAndSize(
1500N/A keystr, keysize)) == NULL) {
1500N/A CLEANUP_REFS;
1500N/A return (NULL);
1500N/A }
1500N/A
1500N/A if (keysize == 4 && strncmp(keystr, "data",
1500N/A keysize) == 0) {
1500N/A invalid("invalid key: 'data'");
1500N/A CLEANUP_REFS;
1500N/A return (NULL);
1500N/A }
1500N/A
1500N/A /*
1500N/A * Pool attribute key to reduce memory usage and
1500N/A * potentially improve lookup performance.
1500N/A */
1500N/A PyString_InternInPlace(&key);
1500N/A
1500N/A if (i == ks) {
1500N/A malformed("impossible: missing key");
1500N/A CLEANUP_REFS;
1500N/A return (NULL);
1500N/A } else if (++i == strl) {
51N/A malformed("missing value");
1500N/A CLEANUP_REFS;
1500N/A return (NULL);
1500N/A }
1500N/A if (str[i] == '\'' || str[i] == '\"') {
1500N/A state = QVAL;
1500N/A quote = str[i];
1500N/A vs = i + 1;
1500N/A } else if (str[i] == ' ' || str[i] == '\t') {
1500N/A malformed("missing value");
1500N/A CLEANUP_REFS;
1500N/A return (NULL);
1500N/A } else {
1500N/A state = UQVAL;
1500N/A vs = i;
1500N/A }
1500N/A } else if (str[i] == '\'' || str[i] == '\"') {
1500N/A malformed("quote in key");
1500N/A CLEANUP_REFS;
1500N/A return (NULL);
1500N/A }
1500N/A } else if (state == QVAL) {
1500N/A if (str[i] == '\\') {
1500N/A if (i == strl - 1)
1500N/A break;
1500N/A /*
1500N/A * "slashmap" is a list of the positions of the
1500N/A * backslashes that need to be removed from the
1500N/A * final attribute string.
1500N/A */
1500N/A if (slashmap == NULL) {
1500N/A smlen = 16;
1500N/A slashmap = calloc(smlen, sizeof (int));
1500N/A if (slashmap == NULL) {
1500N/A PyMem_Free(str);
1500N/A return (PyErr_NoMemory());
}
smpos = 0;
/*
* Terminate slashmap with an invalid
* value so we don't think there's a
* slash right at the beginning.
*/
slashmap[smpos] = -1;
} else if (smpos == smlen - 1) {
smlen *= 2;
slashmap = realloc(slashmap,
smlen * sizeof (int));
if (slashmap == NULL) {
PyMem_Free(str);
return (PyErr_NoMemory());
}
}
i++;
if (str[i] == '\\' || str[i] == quote) {
slashmap[smpos++] = i - 1 - vs;
/*
* Keep slashmap properly terminated so
* that a realloc()ed array doesn't give
* us random slash positions.
*/
slashmap[smpos] = -1;
}
} else if (str[i] == quote) {
state = WS;
if (slashmap != NULL) {
char *sattr;
int j, o, attrlen;
attrlen = i - vs;
sattr = calloc(1, attrlen + 1);
if (sattr == NULL) {
PyMem_Free(str);
free(slashmap);
return (PyErr_NoMemory());
}
/*
* Copy the attribute from str into
* sattr, removing backslashes as
* slashmap indicates we should.
*/
for (j = 0, o = 0; j < attrlen; j++) {
if (slashmap[o] == j) {
o++;
continue;
}
sattr[j - o] = str[vs + j];
}
free(slashmap);
slashmap = NULL;
if ((attr = PyString_FromStringAndSize(
sattr, attrlen - o)) == NULL) {
free(sattr);
CLEANUP_REFS;
return (NULL);
}
free(sattr);
} else {
Py_XDECREF(attr);
if ((attr = PyString_FromStringAndSize(
&str[vs], i - vs)) == NULL) {
CLEANUP_REFS;
return (NULL);
}
}
if (strncmp(keystr, "hash=", 5) == 0) {
char *as = PyString_AsString(attr);
if (hashstr && strcmp(as, hashstr)) {
invalid(notident);
CLEANUP_REFS;
return (NULL);
}
hash = attr;
attr = NULL;
} else {
PyString_InternInPlace(&attr);
if (add_to_attrs(attrs, key,
attr) == -1) {
CLEANUP_REFS;
return (NULL);
}
}
}
} else if (state == UQVAL) {
if (str[i] == ' ' || str[i] == '\t') {
state = WS;
Py_XDECREF(attr);
attr = PyString_FromStringAndSize(&str[vs],
i - vs);
if (strncmp(keystr, "hash=", 5) == 0) {
char *as = PyString_AsString(attr);
if (hashstr && strcmp(as, hashstr)) {
invalid(notident);
CLEANUP_REFS;
return (NULL);
}
hash = attr;
attr = NULL;
} else {
PyString_InternInPlace(&attr);
if (add_to_attrs(attrs, key,
attr) == -1) {
CLEANUP_REFS;
return (NULL);
}
}
}
} else if (state == WS) {
if (str[i] != ' ' && str[i] != '\t') {
state = KEY;
ks = i;
if (str[i] == '=') {
malformed("missing key");
CLEANUP_REFS;
return (NULL);
}
}
}
}
/*
* UQVAL is the most frequently encountered end-state, so check that
* first to avoid unnecessary state comparisons.
*/
if (state == UQVAL) {
Py_XDECREF(attr);
attr = PyString_FromStringAndSize(&str[vs], i - vs);
if (strncmp(keystr, "hash=", 5) == 0) {
char *as = PyString_AsString(attr);
if (hashstr && strcmp(as, hashstr)) {
invalid(notident);
CLEANUP_REFS;
return (NULL);
}
hash = attr;
attr = NULL;
} else {
PyString_InternInPlace(&attr);
if (add_to_attrs(attrs, key, attr) == -1) {
CLEANUP_REFS;
return (NULL);
}
}
} else if (state == QVAL) {
if (slashmap != NULL)
free(slashmap);
malformed("unfinished quoted value");
CLEANUP_REFS;
return (NULL);
} else if (state == KEY) {
malformed("missing value");
CLEANUP_REFS;
return (NULL);
}
PyMem_Free(str);
Py_XDECREF(key);
Py_XDECREF(attr);
/*
* Action parsing is done; now build the list of arguments to construct
* the object for it.
*/
if ((act_args = Py_BuildValue("(O)", act_data)) == NULL) {
if (hash != NULL && hash != Py_None)
Py_DECREF(hash);
Py_DECREF(attrs);
return (NULL);
}
/*
* Using the cached action class assigned earlier based on the type,
* call the action constructor, set the hash attribute, and then return
* the new action object.
*/
action = PyObject_Call(act_class, act_args, attrs);
Py_DECREF(act_args);
Py_DECREF(attrs);
if (action == NULL) {
if (hash != NULL && hash != Py_None)
Py_DECREF(hash);
return (NULL);
}
if (hash != NULL && hash != Py_None) {
if (PyObject_SetAttrString(action, "hash", hash) == -1) {
Py_DECREF(hash);
Py_DECREF(action);
return (NULL);
}
Py_DECREF(hash);
}
return (action);
}
static PyMethodDef methods[] = {
{ "fromstr", (PyCFunction)fromstr, METH_VARARGS | METH_KEYWORDS },
{ NULL, NULL, 0, NULL }
};
PyMODINIT_FUNC
init_actions(void)
{
PyObject *action_types = NULL;
PyObject *pkg_actions = NULL;
PyObject *sys = NULL;
PyObject *sys_modules = NULL;
/*
* Note that module initialization functions are void and may not return
* a value. However, they should set an exception if appropriate.
*/
if (Py_InitModule("_actions", methods) == NULL)
return;
/*
* We need to retrieve the MalformedActionError object from pkg.actions.
* We can't import pkg.actions directly, because that would result in a
* circular dependency. But the "sys" module has a dict called
* "modules" which maps loaded module names to the corresponding module
* objects. We can then grab the exception from those objects.
*/
if ((sys = PyImport_ImportModule("sys")) == NULL)
return;
if ((sys_modules = PyObject_GetAttrString(sys, "modules")) == NULL)
return;
if ((pkg_actions = PyDict_GetItemString(sys_modules, "pkg.actions"))
== NULL) {
/* No exception is set */
PyErr_SetString(PyExc_KeyError, "pkg.actions");
Py_DECREF(sys_modules);
return;
}
Py_DECREF(sys_modules);
/*
* Each reference is DECREF'd after retrieval as Python 2.x doesn't
* provide a module shutdown/cleanup hook. Since these references are
* guaranteed to stay around until the module is unloaded, DECREF'ing
* them now ensures that garbage cleanup will work as expected during
* process exit. This applies to the action type caching below as well.
*/
MalformedActionError = \
PyObject_GetAttrString(pkg_actions, "MalformedActionError");
Py_DECREF(MalformedActionError);
InvalidActionError = \
PyObject_GetAttrString(pkg_actions, "InvalidActionError");
Py_DECREF(InvalidActionError);
UnknownActionError = \
PyObject_GetAttrString(pkg_actions, "UnknownActionError");
Py_DECREF(UnknownActionError);
/*
* Retrieve the list of action types and then store a reference to each
* class for use during action construction. (This allows avoiding the
* overhead of retrieving a new reference for each action constructed.)
*/
if ((action_types = PyObject_GetAttrString(pkg_actions,
"types")) == NULL) {
PyErr_SetString(PyExc_KeyError, "pkg.actions.types missing!");
return;
}
/*
* cache_class borrows the references to the action type objects; this
* is safe as they should remain valid as long as the module is loaded.
* (PyDict_GetItem* doesn't return a new reference.)
*/
#define cache_class(cache_var, name) \
if ((cache_var = PyDict_GetItemString(action_types, name)) == NULL) { \
PyErr_SetString(PyExc_KeyError, \
"Action type class missing: " name); \
Py_DECREF(action_types); \
return; \
}
cache_class(aclass_attribute, "set");
cache_class(aclass_depend, "depend");
cache_class(aclass_directory, "dir");
cache_class(aclass_driver, "driver");
cache_class(aclass_file, "file");
cache_class(aclass_group, "group");
cache_class(aclass_hardlink, "hardlink");
cache_class(aclass_legacy, "legacy");
cache_class(aclass_license, "license");
cache_class(aclass_link, "link");
cache_class(aclass_signature, "signature");
cache_class(aclass_unknown, "unknown");
cache_class(aclass_user, "user");
Py_DECREF(action_types);
}