/* D-Bus container types: Array, Dict and Struct. * * Copyright (C) 2006-2007 Collabora Ltd. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "dbus_bindings-internal.h" #include #include #include "types-internal.h" /* Array ============================================================ */ PyDoc_STRVAR(Array_tp_doc, "An array of similar items, implemented as a subtype of list.\n" "\n" "As currently implemented, an Array behaves just like a list, but\n" "with the addition of a ``signature`` property set by the constructor;\n" "conversion of its items to D-Bus types is only done when it's sent in\n" "a Message. This might change in future so validation is done earlier.\n" "\n" "Constructor::\n" "\n" " dbus.Array([iterable][, signature][, variant_level])\n" "\n" "``variant_level`` must be non-negative; the default is 0.\n" "\n" "``signature`` is the D-Bus signature string for a single element of the\n" "array, or None. If not None it must represent a single complete type, the\n" "type of a single array item; the signature of the whole Array may be\n" "obtained by prepending ``a`` to the given signature.\n" "\n" "If None (the default), when the Array is sent over\n" "D-Bus, the item signature will be guessed from the first element.\n" "\n" ":IVariables:\n" " `variant_level` : int\n" " Indicates how many nested Variant containers this object\n" " is contained in: if a message's wire format has a variant containing a\n" " variant containing an array, this is represented in Python by an\n" " Array with variant_level==2.\n" ); static struct PyMemberDef Array_tp_members[] = { {"signature", T_OBJECT, offsetof(DBusPyArray, signature), READONLY, "The D-Bus signature of each element of this Array (a Signature " "instance)"}, {"variant_level", T_LONG, offsetof(DBusPyArray, variant_level), READONLY, "The number of nested variants wrapping the real data. " "0 if not in a variant."}, {NULL}, }; static void Array_tp_dealloc (DBusPyArray *self) { Py_CLEAR(self->signature); (PyList_Type.tp_dealloc)((PyObject *)self); } static PyObject * Array_tp_repr(DBusPyArray *self) { PyObject *parent_repr = (PyList_Type.tp_repr)((PyObject *)self); PyObject *sig_repr = PyObject_Repr(self->signature); PyObject *my_repr = NULL; long variant_level = self->variant_level; if (!parent_repr) goto finally; if (!sig_repr) goto finally; if (variant_level > 0) { my_repr = PyUnicode_FromFormat("%s(%V, signature=%V, " "variant_level=%ld)", Py_TYPE(&self->super)->tp_name, REPRV(parent_repr), REPRV(sig_repr), variant_level); } else { my_repr = PyUnicode_FromFormat("%s(%V, signature=%V)", Py_TYPE(&self->super)->tp_name, REPRV(parent_repr), REPRV(sig_repr)); } finally: Py_CLEAR(parent_repr); Py_CLEAR(sig_repr); return my_repr; } static PyObject * Array_tp_new (PyTypeObject *cls, PyObject *args, PyObject *kwargs) { PyObject *variant_level = NULL; DBusPyArray *self = (DBusPyArray *)(PyList_Type.tp_new)(cls, args, kwargs); /* variant_level is immutable, so handle it in __new__ rather than __init__ */ if (!self) return NULL; Py_INCREF(Py_None); self->signature = Py_None; self->variant_level = 0; if (kwargs) { variant_level = PyDict_GetItem(kwargs, dbus_py_variant_level_const); } if (variant_level) { long new_variant_level = PyLong_AsLong(variant_level); if (new_variant_level == -1 && PyErr_Occurred()) { Py_CLEAR(self); return NULL; } self->variant_level = new_variant_level; } return (PyObject *)self; } static int Array_tp_init (DBusPyArray *self, PyObject *args, PyObject *kwargs) { PyObject *obj = dbus_py_empty_tuple; PyObject *signature = NULL; PyObject *tuple; PyObject *variant_level; /* variant_level is accepted but ignored - it's immutable, so * __new__ handles it */ static char *argnames[] = {"iterable", "signature", "variant_level", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOO:__init__", argnames, &obj, &signature, &variant_level)) { return -1; } /* convert signature from a borrowed ref of unknown type to an owned ref of type Signature (or None) */ if (!signature) signature = Py_None; if (signature == Py_None || PyObject_IsInstance(signature, (PyObject *)&DBusPySignature_Type)) { Py_INCREF(signature); } else { signature = PyObject_CallFunction((PyObject *)&DBusPySignature_Type, "(O)", signature); if (!signature) return -1; } if (signature != Py_None) { const char *c_str; PyObject *signature_as_bytes; if ( #ifdef PY3 !PyUnicode_Check(signature) #else !PyBytes_Check(signature) #endif ) { PyErr_SetString(PyExc_TypeError, "str expected"); Py_CLEAR(signature); return -1; } #ifdef PY3 if (!(signature_as_bytes = PyUnicode_AsUTF8String(signature))) { Py_CLEAR(signature); return -1; } #else signature_as_bytes = signature; Py_INCREF(signature_as_bytes); #endif c_str = PyBytes_AS_STRING(signature_as_bytes); if (!dbus_signature_validate_single(c_str, NULL)) { Py_CLEAR(signature); Py_CLEAR(signature_as_bytes); PyErr_SetString(PyExc_ValueError, "There must be exactly one complete type in " "an Array's signature parameter"); return -1; } Py_CLEAR(signature_as_bytes); } tuple = Py_BuildValue("(O)", obj); if (!tuple) { Py_CLEAR(signature); return -1; } if ((PyList_Type.tp_init)((PyObject *)self, tuple, NULL) < 0) { Py_CLEAR(tuple); Py_CLEAR(signature); return -1; } Py_CLEAR(tuple); Py_CLEAR(self->signature); self->signature = signature; return 0; } PyTypeObject DBusPyArray_Type = { PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) "dbus.Array", sizeof(DBusPyArray), 0, (destructor)Array_tp_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)Array_tp_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ Array_tp_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ Array_tp_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Array_tp_init, /* tp_init */ 0, /* tp_alloc */ Array_tp_new, /* tp_new */ }; /* Dict ============================================================= */ PyDoc_STRVAR(Dict_tp_doc, "An mapping whose keys are similar and whose values are similar,\n" "implemented as a subtype of dict.\n" "\n" "As currently implemented, a Dictionary behaves just like a dict, but\n" "with the addition of a ``signature`` property set by the constructor;\n" "conversion of its items to D-Bus types is only done when it's sent in\n" "a Message. This may change in future so validation is done earlier.\n" "\n" "Constructor::\n" "\n" " Dictionary(mapping_or_iterable=(), signature=None, variant_level=0)\n" "\n" "``variant_level`` must be non-negative; the default is 0.\n" "\n" "``signature`` is either a string or None. If a string, it must consist\n" "of exactly two complete type signatures, representing the 'key' type\n" "(which must be a primitive type, i.e. one of \"bdginoqstuxy\")\n" "and the 'value' type. The signature of the whole Dictionary will be\n" "``a{xx}`` where ``xx`` is replaced by the given signature.\n" "\n" "If it is None (the default), when the Dictionary is sent over\n" "D-Bus, the key and value signatures will be guessed from an arbitrary\n" "element of the Dictionary.\n" "\n" ":IVariables:\n" " `variant_level` : int\n" " Indicates how many nested Variant containers this object\n" " is contained in: if a message's wire format has a variant containing a\n" " variant containing an array of DICT_ENTRY, this is represented in\n" " Python by a Dictionary with variant_level==2.\n" ); static struct PyMemberDef Dict_tp_members[] = { {"signature", T_OBJECT, offsetof(DBusPyDict, signature), READONLY, "The D-Bus signature of each key in this Dictionary, followed by " "that of each value in this Dictionary, as a Signature instance."}, {"variant_level", T_LONG, offsetof(DBusPyDict, variant_level), READONLY, "The number of nested variants wrapping the real data. " "0 if not in a variant."}, {NULL}, }; static void Dict_tp_dealloc (DBusPyDict *self) { Py_CLEAR(self->signature); (PyDict_Type.tp_dealloc)((PyObject *)self); } static PyObject * Dict_tp_repr(DBusPyDict *self) { PyObject *parent_repr = (PyDict_Type.tp_repr)((PyObject *)self); PyObject *sig_repr = PyObject_Repr(self->signature); PyObject *my_repr = NULL; long variant_level = self->variant_level; if (!parent_repr) goto finally; if (!sig_repr) goto finally; if (variant_level > 0) { my_repr = PyUnicode_FromFormat("%s(%V, signature=%V, " "variant_level=%ld)", Py_TYPE(&self->super)->tp_name, REPRV(parent_repr), REPRV(sig_repr), variant_level); } else { my_repr = PyUnicode_FromFormat("%s(%V, signature=%V)", Py_TYPE(&self->super)->tp_name, REPRV(parent_repr), REPRV(sig_repr)); } finally: Py_CLEAR(parent_repr); Py_CLEAR(sig_repr); return my_repr; } static PyObject * Dict_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs) { DBusPyDict *self = (DBusPyDict *)(PyDict_Type.tp_new)(cls, args, kwargs); PyObject *variant_level = NULL; /* variant_level is immutable, so handle it in __new__ rather than __init__ */ if (!self) return NULL; Py_INCREF(Py_None); self->signature = Py_None; self->variant_level = 0; if (kwargs) { variant_level = PyDict_GetItem(kwargs, dbus_py_variant_level_const); } if (variant_level) { long new_variant_level = PyLong_AsLong(variant_level); if (new_variant_level == -1 && PyErr_Occurred()) { Py_CLEAR(self); return NULL; } self->variant_level = new_variant_level; } return (PyObject *)self; } static int Dict_tp_init(DBusPyDict *self, PyObject *args, PyObject *kwargs) { PyObject *obj = dbus_py_empty_tuple; PyObject *signature = NULL; PyObject *tuple; PyObject *variant_level; /* ignored here - __new__ uses it */ static char *argnames[] = {"mapping_or_iterable", "signature", "variant_level", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOO:__init__", argnames, &obj, &signature, &variant_level)) { return -1; } /* convert signature from a borrowed ref of unknown type to an owned ref of type Signature (or None) */ if (!signature) signature = Py_None; if (signature == Py_None || PyObject_IsInstance(signature, (PyObject *)&DBusPySignature_Type)) { Py_INCREF(signature); } else { signature = PyObject_CallFunction((PyObject *)&DBusPySignature_Type, "(O)", signature); if (!signature) return -1; } if (signature != Py_None) { const char *c_str; PyObject *signature_as_bytes; if (!NATIVESTR_CHECK(signature)) { PyErr_SetString(PyExc_TypeError, "str expected"); Py_CLEAR(signature); return -1; } #ifdef PY3 if (!(signature_as_bytes = PyUnicode_AsUTF8String(signature))) { Py_CLEAR(signature); return -1; } #else signature_as_bytes = signature; Py_INCREF(signature_as_bytes); #endif c_str = PyBytes_AS_STRING(signature_as_bytes); switch (c_str[0]) { case DBUS_TYPE_BYTE: case DBUS_TYPE_BOOLEAN: case DBUS_TYPE_INT16: case DBUS_TYPE_UINT16: case DBUS_TYPE_INT32: case DBUS_TYPE_UINT32: case DBUS_TYPE_INT64: case DBUS_TYPE_UINT64: case DBUS_TYPE_DOUBLE: #ifdef WITH_DBUS_FLOAT32 case DBUS_TYPE_FLOAT: #endif #ifdef DBUS_TYPE_UNIX_FD case DBUS_TYPE_UNIX_FD: #endif case DBUS_TYPE_STRING: case DBUS_TYPE_OBJECT_PATH: case DBUS_TYPE_SIGNATURE: break; default: Py_CLEAR(signature); Py_CLEAR(signature_as_bytes); PyErr_SetString(PyExc_ValueError, "The key type in a Dictionary's signature " "must be a primitive type"); return -1; } if (!dbus_signature_validate_single(c_str + 1, NULL)) { Py_CLEAR(signature); Py_CLEAR(signature_as_bytes); PyErr_SetString(PyExc_ValueError, "There must be exactly two complete types in " "a Dictionary's signature parameter"); return -1; } Py_CLEAR(signature_as_bytes); } tuple = Py_BuildValue("(O)", obj); if (!tuple) { Py_CLEAR(signature); return -1; } if ((PyDict_Type.tp_init((PyObject *)self, tuple, NULL)) < 0) { Py_CLEAR(tuple); Py_CLEAR(signature); return -1; } Py_CLEAR(tuple); Py_CLEAR(self->signature); self->signature = signature; return 0; } PyTypeObject DBusPyDict_Type = { PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) "dbus.Dictionary", sizeof(DBusPyDict), 0, (destructor)Dict_tp_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)Dict_tp_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ Dict_tp_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ Dict_tp_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Dict_tp_init, /* tp_init */ 0, /* tp_alloc */ Dict_tp_new, /* tp_new */ }; /* Struct =========================================================== */ static PyObject *struct_signatures; PyDoc_STRVAR(Struct_tp_doc, "An structure containing items of possibly distinct types.\n" "\n" "Constructor::\n" "\n" " dbus.Struct(iterable, signature=None, variant_level=0) -> Struct\n" "\n" "D-Bus structs may not be empty, so the iterable argument is required and\n" "may not be an empty iterable.\n" "\n" "``signature`` is either None, or a string representing the contents of the\n" "struct as one or more complete type signatures. The overall signature of\n" "the struct will be the given signature enclosed in parentheses, ``()``.\n" "\n" "If the signature is None (default) it will be guessed\n" "from the types of the items during construction.\n" "\n" "``variant_level`` must be non-negative; the default is 0.\n" "\n" ":IVariables:\n" " `variant_level` : int\n" " Indicates how many nested Variant containers this object\n" " is contained in: if a message's wire format has a variant containing a\n" " variant containing a struct, this is represented in Python by a\n" " Struct with variant_level==2.\n" ); static PyObject * Struct_tp_repr(PyObject *self) { PyObject *parent_repr = (PyTuple_Type.tp_repr)((PyObject *)self); PyObject *sig; PyObject *sig_repr = NULL; PyObject *key; long variant_level; PyObject *my_repr = NULL; if (!parent_repr) goto finally; key = PyLong_FromVoidPtr(self); if (!key) goto finally; sig = PyDict_GetItem(struct_signatures, key); Py_CLEAR(key); if (!sig) sig = Py_None; sig_repr = PyObject_Repr(sig); if (!sig_repr) goto finally; variant_level = dbus_py_variant_level_get(self); if (variant_level < 0) goto finally; if (variant_level > 0) { my_repr = PyUnicode_FromFormat("%s(%V, signature=%V, " "variant_level=%ld)", Py_TYPE(self)->tp_name, REPRV(parent_repr), REPRV(sig_repr), variant_level); } else { my_repr = PyUnicode_FromFormat("%s(%V, signature=%V)", Py_TYPE(self)->tp_name, REPRV(parent_repr), REPRV(sig_repr)); } finally: Py_CLEAR(parent_repr); Py_CLEAR(sig_repr); return my_repr; } static PyObject * Struct_tp_new (PyTypeObject *cls, PyObject *args, PyObject *kwargs) { PyObject *signature = NULL; long variantness = 0; PyObject *self, *key; static char *argnames[] = {"signature", "variant_level", NULL}; if (PyTuple_Size(args) != 1) { PyErr_SetString(PyExc_TypeError, "__new__ takes exactly one positional parameter"); return NULL; } if (!PyArg_ParseTupleAndKeywords(dbus_py_empty_tuple, kwargs, "|Ol:__new__", argnames, &signature, &variantness)) { return NULL; } if (variantness < 0) { PyErr_SetString(PyExc_ValueError, "variant_level must be non-negative"); return NULL; } self = (PyTuple_Type.tp_new)(cls, args, NULL); if (!self) return NULL; if (PyTuple_Size(self) < 1) { PyErr_SetString(PyExc_ValueError, "D-Bus structs may not be empty"); Py_CLEAR(self); return NULL; } if (!dbus_py_variant_level_set(self, variantness)) { Py_CLEAR(self); return NULL; } /* convert signature from a borrowed ref of unknown type to an owned ref of type Signature (or None) */ if (!signature) signature = Py_None; if (signature == Py_None || PyObject_IsInstance(signature, (PyObject *)&DBusPySignature_Type)) { Py_INCREF(signature); } else { signature = PyObject_CallFunction((PyObject *)&DBusPySignature_Type, "(O)", signature); if (!signature) { Py_CLEAR(self); return NULL; } } key = PyLong_FromVoidPtr(self); if (!key) { Py_CLEAR(self); Py_CLEAR(signature); return NULL; } if (PyDict_SetItem(struct_signatures, key, signature) < 0) { Py_CLEAR(key); Py_CLEAR(self); Py_CLEAR(signature); return NULL; } Py_CLEAR(key); Py_CLEAR(signature); return self; } static void Struct_tp_dealloc(PyObject *self) { PyObject *et, *ev, *etb, *key; dbus_py_variant_level_clear(self); PyErr_Fetch(&et, &ev, &etb); key = PyLong_FromVoidPtr(self); if (key) { if (PyDict_GetItem(struct_signatures, key)) { if (PyDict_DelItem(struct_signatures, key) < 0) { /* should never happen */ PyErr_WriteUnraisable(self); } } Py_CLEAR(key); } else { /* not enough memory to free all the memory... leak the signature, * there's not much else we could do here */ PyErr_WriteUnraisable(self); } PyErr_Restore(et, ev, etb); (PyTuple_Type.tp_dealloc)(self); } static PyObject * Struct_tp_getattro(PyObject *obj, PyObject *name) { PyObject *key, *value; #ifdef PY3 if (PyUnicode_CompareWithASCIIString(name, "signature")) return dbus_py_variant_level_getattro(obj, name); #else if (PyBytes_Check(name)) { Py_INCREF(name); } else if (PyUnicode_Check(name)) { name = PyUnicode_AsEncodedString(name, NULL, NULL); if (!name) { return NULL; } } else { PyErr_SetString(PyExc_TypeError, "attribute name must be string"); return NULL; } if (strcmp(PyBytes_AS_STRING(name), "signature")) { value = dbus_py_variant_level_getattro(obj, name); Py_CLEAR(name); return value; } Py_CLEAR(name); #endif /* PY3 */ key = PyLong_FromVoidPtr(obj); if (!key) { return NULL; } value = PyDict_GetItem(struct_signatures, key); Py_CLEAR(key); if (!value) value = Py_None; Py_INCREF(value); return value; } PyTypeObject DBusPyStruct_Type = { PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) "dbus.Struct", 0, 0, Struct_tp_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)Struct_tp_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ Struct_tp_getattro, /* tp_getattro */ dbus_py_immutable_setattro, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ Struct_tp_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ Struct_tp_new, /* tp_new */ }; dbus_bool_t dbus_py_init_container_types(void) { struct_signatures = PyDict_New(); if (!struct_signatures) return 0; DBusPyArray_Type.tp_base = &PyList_Type; if (PyType_Ready(&DBusPyArray_Type) < 0) return 0; DBusPyArray_Type.tp_print = NULL; DBusPyDict_Type.tp_base = &PyDict_Type; if (PyType_Ready(&DBusPyDict_Type) < 0) return 0; DBusPyDict_Type.tp_print = NULL; DBusPyStruct_Type.tp_base = &PyTuple_Type; if (PyType_Ready(&DBusPyStruct_Type) < 0) return 0; DBusPyStruct_Type.tp_print = NULL; return 1; } dbus_bool_t dbus_py_insert_container_types(PyObject *this_module) { /* PyModule_AddObject steals a ref */ Py_INCREF(&DBusPyArray_Type); if (PyModule_AddObject(this_module, "Array", (PyObject *)&DBusPyArray_Type) < 0) return 0; Py_INCREF(&DBusPyDict_Type); if (PyModule_AddObject(this_module, "Dictionary", (PyObject *)&DBusPyDict_Type) < 0) return 0; Py_INCREF(&DBusPyStruct_Type); if (PyModule_AddObject(this_module, "Struct", (PyObject *)&DBusPyStruct_Type) < 0) return 0; return 1; } /* vim:set ft=c cino< sw=4 sts=4 et: */