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