/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved. */ #include #include static PyObject *MalformedActionError; static PyObject *InvalidActionError; static PyObject *UnknownActionError; static PyObject *aclass_attribute; static PyObject *aclass_depend; static PyObject *aclass_directory; static PyObject *aclass_driver; static PyObject *aclass_file; static PyObject *aclass_group; static PyObject *aclass_hardlink; static PyObject *aclass_legacy; static PyObject *aclass_license; static PyObject *aclass_link; static PyObject *aclass_unknown; static PyObject *aclass_user; static const char *notident = "hash attribute not identical to positional hash"; static const char *nohash = "action type doesn't allow payload"; static inline int add_to_attrs(PyObject *attrs, PyObject *key, PyObject *attr) { int ret; PyObject *list; PyObject *av = PyDict_GetItem(attrs, key); if (av == NULL) return (PyDict_SetItem(attrs, key, attr)); if (PyList_CheckExact(av)) return (PyList_Append(av, attr)); if ((list = PyList_New(2)) == NULL) return (-1); /* PyList_SET_ITEM steals references. */ Py_INCREF(av); PyList_SET_ITEM(list, 0, av); Py_INCREF(attr); PyList_SET_ITEM(list, 1, attr); ret = PyDict_SetItem(attrs, key, list); Py_DECREF(list); return (ret); } static void set_malformederr(const char *str, int pos, const char *msg) { PyObject *val; if ((val = Py_BuildValue("sis", str, pos, msg)) != NULL) { PyErr_SetObject(MalformedActionError, val); Py_DECREF(val); } } static void set_invaliderr(const char *str, const char *msg) { PyObject *val; if ((val = Py_BuildValue("ss", str, msg)) != NULL) { PyErr_SetObject(InvalidActionError, val); Py_DECREF(val); } } /*ARGSUSED*/ static PyObject * fromstr(PyObject *self, PyObject *args, PyObject *kwdict) { char *s = NULL; char *str = NULL; char *hashstr = NULL; char *keystr = NULL; int *slashmap = NULL; int strl, typestrl; int i, ks, vs, keysize; int smlen, smpos; int hash_allowed; char quote; PyObject *act_args = NULL; PyObject *act_class = NULL; PyObject *act_data = NULL; PyObject *action = NULL; PyObject *hash = NULL; PyObject *attrs = NULL; PyObject *key = NULL; PyObject *attr = NULL; enum { KEY, /* key */ UQVAL, /* unquoted value */ QVAL, /* quoted value */ WS /* whitespace */ } state; /* * If malformed() or invalid() are used, CLEANUP_REFS can only be used * after. Likewise, PyMem_Free(str) should not be called before using * malformed() or invalid(). Failure to order this properly will cause * corruption of the exception messages. */ #define malformed(msg) set_malformederr(str, i, (msg)) #define invalid(msg) set_invaliderr(str, (msg)) #define CLEANUP_REFS \ PyMem_Free(str);\ Py_XDECREF(key);\ Py_XDECREF(attr);\ Py_XDECREF(attrs);\ Py_XDECREF(hash);\ free(hashstr); /* * Positional arguments must be included in the keyword argument list in * the order you want them to be assigned. (A subtle point missing from * the Python documentation.) */ static char *kwlist[] = { "string", "data", NULL }; /* Assume data=None by default. */ act_data = Py_None; /* * The action string is currently assumed to be a stream of bytes that * are valid UTF-8. This method works regardless of whether the string * object provided is a Unicode object, string object, or a character * buffer. */ if (PyArg_ParseTupleAndKeywords(args, kwdict, "et#|O:fromstr", kwlist, "utf-8", &str, &strl, &act_data) == 0) { return (NULL); } s = strpbrk(str, " \t"); i = strl; if (s == NULL) { malformed("no attributes"); PyMem_Free(str); return (NULL); } /* * The comparisons here are ordered by frequency in which actions are * most likely to be encountered in usage by the client grouped by * length. Yes, a cheap hack to squeeze a tiny bit of additional * performance out. */ typestrl = s - str; hash_allowed = 0; if (typestrl == 4) { if (strncmp(str, "file", 4) == 0) { act_class = aclass_file; hash_allowed = 1; } else if (strncmp(str, "link", 4) == 0) act_class = aclass_link; else if (strncmp(str, "user", 4) == 0) act_class = aclass_user; } else if (typestrl == 6) { if (strncmp(str, "depend", 6) == 0) act_class = aclass_depend; else if (strncmp(str, "driver", 6) == 0) act_class = aclass_driver; else if (strncmp(str, "legacy", 6) == 0) act_class = aclass_legacy; } else if (typestrl == 3) { if (strncmp(str, "set", 3) == 0) act_class = aclass_attribute; else if (strncmp(str, "dir", 3) == 0) act_class = aclass_directory; } else if (typestrl == 8) { if (strncmp(str, "hardlink", 8) == 0) act_class = aclass_hardlink; } else if (typestrl == 7) { if (strncmp(str, "license", 7) == 0) { act_class = aclass_license; hash_allowed = 1; } else if (strncmp(str, "unknown", 7) == 0) act_class = aclass_unknown; } else if (typestrl == 5) { if (strncmp(str, "group", 5) == 0) act_class = aclass_group; } if (act_class == NULL) { if ((act_args = Py_BuildValue("s#s#", str, strl, str, typestrl)) != NULL) { PyErr_SetObject(UnknownActionError, act_args); Py_DECREF(act_args); PyMem_Free(str); return (NULL); } /* * Unable to build argument list for exception; so raise * general type exception instead. */ PyErr_SetString(PyExc_TypeError, "unknown action type"); PyMem_Free(str); return (NULL); } ks = vs = typestrl; state = WS; if ((attrs = PyDict_New()) == NULL) { PyMem_Free(str); return (NULL); } for (i = s - str; str[i]; i++) { if (state == KEY) { keysize = i - ks; keystr = &str[ks]; if (str[i] == ' ' || str[i] == '\t') { if (PyDict_Size(attrs) > 0 || hash != NULL) { malformed("whitespace in key"); CLEANUP_REFS; return (NULL); } else { if ((hash = PyString_FromStringAndSize( keystr, keysize)) == NULL) { CLEANUP_REFS; return (NULL); } if (!hash_allowed) { invalid(nohash); CLEANUP_REFS; return (NULL); } hashstr = strndup(keystr, keysize); state = WS; } } else if (str[i] == '=') { if ((key = PyString_FromStringAndSize( keystr, keysize)) == NULL) { CLEANUP_REFS; return (NULL); } if (keysize == 4 && strncmp(keystr, "data", keysize) == 0) { invalid("invalid key: 'data'"); CLEANUP_REFS; return (NULL); } if (!hash_allowed && keysize == 4 && strncmp(keystr, "hash", keysize) == 0) { invalid(nohash); CLEANUP_REFS; return (NULL); } /* * Pool attribute key to reduce memory usage and * potentially improve lookup performance. */ PyString_InternInPlace(&key); if (i == ks) { malformed("impossible: missing key"); CLEANUP_REFS; return (NULL); } else if (++i == strl) { malformed("missing value"); CLEANUP_REFS; return (NULL); } if (str[i] == '\'' || str[i] == '\"') { state = QVAL; quote = str[i]; vs = i + 1; } else if (str[i] == ' ' || str[i] == '\t') { malformed("missing value"); CLEANUP_REFS; return (NULL); } else { state = UQVAL; vs = i; } } else if (str[i] == '\'' || str[i] == '\"') { malformed("quote in key"); CLEANUP_REFS; return (NULL); } } else if (state == QVAL) { if (str[i] == '\\') { if (i == strl - 1) break; /* * "slashmap" is a list of the positions of the * backslashes that need to be removed from the * final attribute string. */ if (slashmap == NULL) { smlen = 16; slashmap = calloc(smlen, sizeof (int)); if (slashmap == NULL) { PyMem_Free(str); 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_unknown, "unknown"); cache_class(aclass_user, "user"); Py_DECREF(action_types); }