fr_pair_list_init(&head);
fr_pair_list_init(&tmp_list);
- switch (map->lhs->type) {
+ fr_assert(map->lhs->type == TMPL_TYPE_ATTR);
+
/*
* This is a mapping in the form of:
* <list>. += <ldap attr>
* RADIUS control and reply attributes in separate LDAP
* attributes.
*/
- case TMPL_TYPE_LIST:
+ if (tmpl_is_list(map->lhs)) {
for (i = 0; i < self->count; i++) {
map_t *attr = NULL;
- char *attr_str;
+ char *attr_str;
tmpl_rules_t lhs_rules = {
.attr = {
*/
if (map->op != T_OP_ADD_EQ) break;
}
- break;
+ goto finish;
+ }
/*
* Iterate over all the retrieved values,
* don't try and be clever about changing operators
* just use whatever was set in the attribute map.
*/
- case TMPL_TYPE_ATTR:
- for (i = 0; i < self->count; i++) {
- if (!self->values[i]->bv_len) continue;
-
- MEM(vp = fr_pair_afrom_da(ctx, tmpl_attr_tail_da(map->lhs)));
-
- if (fr_pair_value_from_str(vp, self->values[i]->bv_val,
- self->values[i]->bv_len, NULL, true) < 0) {
- RPWDEBUG("Failed parsing value \"%pV\" for attribute %s",
- fr_box_strvalue_len(self->values[i]->bv_val, self->values[i]->bv_len),
- tmpl_attr_tail_da(map->lhs)->name);
+ for (i = 0; i < self->count; i++) {
+ if (!self->values[i]->bv_len) continue;
- talloc_free(vp); /* also frees escaped */
- continue;
- }
+ MEM(vp = fr_pair_afrom_da(ctx, tmpl_attr_tail_da(map->lhs)));
- fr_pair_append(&head, vp);
+ if (fr_pair_value_from_str(vp, self->values[i]->bv_val,
+ self->values[i]->bv_len, NULL, true) < 0) {
+ RPWDEBUG("Failed parsing value \"%pV\" for attribute %s",
+ fr_box_strvalue_len(self->values[i]->bv_val, self->values[i]->bv_len),
+ tmpl_attr_tail_da(map->lhs)->name);
- /*
- * Only process the first value, unless the operator is +=
- */
- if (map->op != T_OP_ADD_EQ) break;
+ talloc_free(vp); /* also frees escaped */
+ continue;
}
- break;
- default:
- fr_assert(0);
+ fr_pair_append(&head, vp);
+
+ /*
+ * Only process the first value, unless the operator is +=
+ */
+ if (map->op != T_OP_ADD_EQ) break;
}
+finish:
fr_pair_list_append(out, &head);
return 0;
* create using LDAP values.
*/
switch (map->lhs->type) {
- case TMPL_TYPE_LIST:
case TMPL_TYPE_ATTR:
break;
break;
case TMPL_TYPE_ATTR:
- case TMPL_TYPE_LIST:
case TMPL_TYPE_DATA:
case TMPL_TYPE_EXEC:
case TMPL_TYPE_EXEC_UNRESOLVED:
switch (vpt->type) {
case TMPL_TYPE_ATTR:
- case TMPL_TYPE_LIST:
/*
* No cast means that it's an existence check.
*/
/*
* These are handled elsewhere.
*/
- case TMPL_TYPE_LIST:
#ifdef HAVE_REGEX
case TMPL_TYPE_REGEX:
-#endif
fr_assert(!async);
return 0;
+#endif
case TMPL_TYPE_ATTR:
/*
/*
* LHS is an attribute or list
*/
- case TMPL_TYPE_LIST:
case TMPL_TYPE_ATTR:
{
fr_pair_t *vp;
case TMPL_TYPE_XLAT_UNRESOLVED:
case TMPL_TYPE_ATTR:
case TMPL_TYPE_ATTR_UNRESOLVED:
- case TMPL_TYPE_LIST:
case TMPL_TYPE_EXEC:
break;
* @param[in,out] ctx to allocate new #fr_pair_t (s) in.
* @param[out] out Where to write the #fr_pair_t (s).
* @param[in] request structure (used only for talloc).
- * @param[in] map the map. The LHS (dst) must be #TMPL_TYPE_ATTR or #TMPL_TYPE_LIST.
+ * @param[in] map the map. The LHS (dst) must be #TMPL_TYPE_ATTR.
* The RHS (src) must be #TMPL_TYPE_EXEC.
* @return
* - 0 on success.
}
switch (map->lhs->type) {
- case TMPL_TYPE_LIST:
- if (fr_pair_list_empty(&output_pairs)) {
- REDEBUG("No valid attributes received from program");
- return -2;
- }
- fr_pair_list_append(out, &output_pairs);
- return 0;
-
case TMPL_TYPE_ATTR:
{
fr_pair_t *vp;
* @param[in,out] ctx to allocate #fr_pair_t (s) in.
* @param[out] out Where to write the #fr_pair_t (s), which may be NULL if not found
* @param[in] request The current request.
- * @param[in] map the map. The LHS (dst) has to be #TMPL_TYPE_ATTR or #TMPL_TYPE_LIST.
+ * @param[in] map the map. The LHS (dst) has to be #TMPL_TYPE_ATTR.
* @param[in] uctx unused.
* @return
* - 0 on success.
/*
* Already in the correct form.
*/
- case TMPL_TYPE_LIST:
case TMPL_TYPE_ATTR:
break;
fr_pair_aprint_value_quoted(request, &rhs, vp, map->rhs->quote);
break;
- /*
- * For the lists, we can't use the original name, and have to
- * rebuild it using tmpl_print, for each attribute we're
- * copying.
- */
- case TMPL_TYPE_LIST:
- {
- tmpl_t *vpt;
- fr_token_t quote;
-
- switch (vp->vp_type) {
- case FR_TYPE_QUOTED:
- quote = T_DOUBLE_QUOTED_STRING;
- break;
- default:
- quote = T_BARE_WORD;
- break;
- }
-
- vpt = tmpl_alloc(request, TMPL_TYPE_ATTR, quote, map->rhs->name, strlen(map->rhs->name));
-
- /*
- * Fudge a temporary tmpl that describes the attribute we're copying
- * this is a combination of the original list tmpl, and values from
- * the fr_pair_t.
- */
- tmpl_attr_copy(vpt, map->rhs);
- tmpl_attr_set_leaf_da(vpt, vp->da);
- tmpl_attr_set_leaf_num(vpt, NUM_UNSPEC);
-
- /*
- * Not appropriate to use map->rhs->quote here, as that's the quoting
- * around the list ref. The attribute value has no quoting, so we choose
- * the quoting based on the data type.
- */
- fr_pair_aprint_value_quoted(request, &value, vp, quote);
- tmpl_print(&FR_SBUFF_OUT(buffer, sizeof(buffer)), vpt, TMPL_ATTR_REF_PREFIX_YES, NULL);
- rhs = talloc_typed_asprintf(request, "%s -> %s", buffer, value);
-
- talloc_free(vpt);
- }
- break;
-
case TMPL_TYPE_ATTR:
{
fr_token_t quote;
}
switch (map->lhs->type) {
- case TMPL_TYPE_LIST:
- /*
- * The MAP may have said "list", but if there's a
- * VP, it has it's own name, which isn't in the
- * map name.
- */
- if (vp) {
- tmpl_print(&FR_SBUFF_OUT(buffer, sizeof(buffer)), map->lhs, TMPL_ATTR_REF_PREFIX_YES, NULL); /* Fixme - bad escaping */
- RDEBUG2("%s%s %s %s", buffer, vp->da->name, fr_table_str_by_value(fr_tokens_table, vp->op, "<INVALID>"), rhs);
- break;
- }
- FALL_THROUGH;
-
case TMPL_TYPE_ATTR:
tmpl_print(&FR_SBUFF_OUT(buffer, sizeof(buffer)), map->lhs, TMPL_ATTR_REF_PREFIX_YES, NULL);
RDEBUG2("%s %s %s", buffer, fr_table_str_by_value(fr_tokens_table, vp ? vp->op : map->op, "<INVALID>"), rhs);
* @param[in,out] ctx to allocate modification maps in.
* @param[out] out Where to write the #fr_pair_t (s), which may be NULL if not found
* @param[in] request The current request.
- * @param[in] original the map. The LHS (dst) has to be #TMPL_TYPE_ATTR or #TMPL_TYPE_LIST.
+ * @param[in] original the map. The LHS (dst) has to be #TMPL_TYPE_ATTR.
* @param[in] lhs_result of previous stack based rhs evaluation.
* Must be provided for rhs types:
* - TMPL_TYPE_XLAT
/*
* Already in the correct form.
*/
- case TMPL_TYPE_LIST:
case TMPL_TYPE_ATTR:
break;
rhs = fr_asprintf(request, "%s%pV%s", quote, vb, quote);
break;
- /*
- * For the lists, we can't use the original name, and have to
- * rebuild it using tmpl_print, for each attribute we're
- * copying.
- */
- case TMPL_TYPE_LIST:
- {
- char buffer[256];
-
- tmpl_print(&FR_SBUFF_OUT(buffer, sizeof(buffer)), map->rhs, TMPL_ATTR_REF_PREFIX_YES, NULL);
- rhs = fr_asprintf(request, "%s -> %s%pV%s", buffer, quote, vb, quote);
- }
- break;
-
case TMPL_TYPE_ATTR:
rhs = fr_asprintf(request, "%s -> %s%pV%s", map->rhs->name, quote, vb, quote);
break;
switch (map->lhs->type) {
case TMPL_TYPE_ATTR:
- case TMPL_TYPE_LIST:
RDEBUG2("%s %s %s", map->lhs->name, fr_table_str_by_value(fr_tokens_table, mod->op, "<INVALID>"), rhs);
break;
* #tmpl_t (VPTs) specify either a data source, or a data sink.
*
* Examples of sources are #TMPL_TYPE_XLAT_UNRESOLVED, #TMPL_TYPE_EXEC and #TMPL_TYPE_ATTR.
- * Examples of sinks are #TMPL_TYPE_ATTR, #TMPL_TYPE_LIST.
+ * Examples of sinks are #TMPL_TYPE_ATTR.
*
* VPTs are used to gather values or attributes for evaluation, or copying, and to specify
* where values or #fr_pair_t should be copied to.
* @see tmpl_afrom_substr
* @see tmpl_afrom_attr_str
*
- * In the case of #TMPL_TYPE_ATTR and #TMPL_TYPE_LIST, there are special cursor overlay
+ * In the case of #TMPL_TYPE_ATTR, there are special cursor overlay
* functions which can be used to iterate over only the #fr_pair_t that match a
* tmpl_t in a given list.
*
*/
TMPL_TYPE_DATA = 0x0002,
- /** Reference to an attribute list
- */
- TMPL_TYPE_LIST = 0x0004 | TMPL_FLAG_ATTR,
-
/** Reference to one or more attributes
*/
TMPL_TYPE_ATTR = 0x0008 | TMPL_FLAG_ATTR,
uint8_t disallow_filters:1; //!< disallow filters.
- uint8_t list_as_attr:1; //!< return #TMPL_TYPE_ATTR for lists, and not #TMPL_TYPE_LIST
+ uint8_t list_as_attr:1; //!< return #TMPL_TYPE_ATTR for lists
};
struct tmpl_xlat_rules_s {
* @section update_maps Use in update map_t
* When used on the LHS it describes an attribute to create and should be one of these types:
* - #TMPL_TYPE_ATTR
- * - #TMPL_TYPE_LIST
*
* When used on the RHS it describes the value to assign to the attribute being created and
* should be one of these types:
* - #TMPL_TYPE_UNRESOLVED
* - #TMPL_TYPE_XLAT_UNRESOLVED
* - #TMPL_TYPE_ATTR
- * - #TMPL_TYPE_LIST
* - #TMPL_TYPE_EXEC
* - #TMPL_TYPE_DATA
* - #TMPL_TYPE_XLAT (pre-parsed xlat)
}
/** @} */
-/** @name Field accessors for #TMPL_TYPE_ATTR, #TMPL_TYPE_ATTR_UNRESOLVED, #TMPL_TYPE_LIST
+/** @name Field accessors for #TMPL_TYPE_ATTR, #TMPL_TYPE_ATTR_UNRESOLVED
*
* @{
*/
void tmpl_verify(char const *file, int line, tmpl_t const *vpt);
#endif
-/** Produces an initialiser for static #TMPL_TYPE_LIST type #tmpl_t
- *
- * Example:
- @code{.c}
- static tmpl_t list = tmpl_init_initialiser_list(CURRENT_REQUEST, PAIR_LIST_REQUEST);
- fr_dcursor_t cursor;
- tmpl_dcursor_ctx_t cc,
- fr_pair_t *vp;
-
- // Iterate over all pairs in the request list
- for (vp = tmpl_dcursor_init(NULL, &cursor, request, &list);
- vp;
- vp = tmpl_cursor_next(&cursor, &list)) {
- // Do something
- }
- tmpl_dcursor_clear(&cc);
- @endcode
- *
- * @param _request to locate the list in.
- * @param _list to set as the target for the template.
- * @see tmpl_dcursor_init
- * @see tmpl_cursor_next
- */
-#define tmpl_init_initialiser_list(_request, _list)\
-{ \
- .name = "static", \
- .len = sizeof("static") - 1, \
- .type = TMPL_TYPE_LIST, \
- .quote = T_SINGLE_QUOTED_STRING, \
- .data = { \
- .attribute = { \
- .request = _request, \
- .list = _list \
- } \
- } \
-}
-
/** Determine the correct context and list head
*
* Used in conjunction with the fr_dcursor functions to determine the correct list
tmpl_dcursor_nested_t *ns = uctx;
fr_pair_t *vp = curr;
- /*
- * Only applies to TMPL_TYPE_LIST - remove when that is gone.
- */
- if ((!ns->ar) || (!ns->ar->ar_da)) return fr_dlist_next(list, curr);
-
while ((vp = fr_dlist_next(list, vp))) {
if (fr_dict_attr_cmp(ns->ar->ar_da, vp->da) == 0) break;
}
return vp;
}
- null_result:
return NULL;
}
- /*
- * Hacks for evaluating lists
- * Hopefully this tmpl type goes away soon...
- */
- case TMPL_TYPE_LIST:
- if (!fr_dlist_tail(&cc->nested)) goto null_result; /* end of list */
-
- vp = _tmpl_cursor_eval(curr, cc);
- if (!vp) goto null_result;
-
- return vp;
-
default:
fr_assert(0);
}
*/
switch (vpt->type) {
case TMPL_TYPE_ATTR:
- case TMPL_TYPE_LIST:
_tmpl_cursor_pair_init(list, cc->list, tmpl_attr_list_head(&vpt->data.attribute.ar), cc);
break;
list_ctx = request->pair_root;
}
- /*
- * If it's a list, just return the list head
- */
- if (vpt->type == TMPL_TYPE_LIST) {
- do_list:
- if (existing) EXTENT_ADD(existing, NULL, list_ctx, list_head);
- return 0;
- }
-
/*
* If it's a leaf skip all the expensive
* initialisation and just return the list
break;
default:
- goto do_list;
+ if (existing) EXTENT_ADD(existing, NULL, list_ctx, list_head);
+ return 0;
}
/*
*/
case TMPL_TYPE_UNINITIALISED:
case TMPL_TYPE_NULL:
- case TMPL_TYPE_LIST:
case TMPL_TYPE_EXEC_UNRESOLVED:
case TMPL_TYPE_ATTR_UNRESOLVED:
case TMPL_TYPE_XLAT_UNRESOLVED:
*/
case TMPL_TYPE_UNINITIALISED:
case TMPL_TYPE_NULL:
- case TMPL_TYPE_LIST:
case TMPL_TYPE_EXEC_UNRESOLVED:
case TMPL_TYPE_REGEX:
case TMPL_TYPE_REGEX_UNCOMPILED:
* @param request The current #request_t.
* @param vpt specifying the #fr_pair_t type or list to copy.
* Must be one of the following types:
- * - #TMPL_TYPE_LIST
* - #TMPL_TYPE_ATTR
* @return
* - -1 if no matching #fr_pair_t could be found.
* @param request The current #request_t.
* @param vpt specifying the #fr_pair_t type or list to copy.
* Must be one of the following types:
- * - #TMPL_TYPE_LIST
* - #TMPL_TYPE_ATTR
* @return
* - -1 if no matching #fr_pair_t could be found.
* @param[in] request The current #request_t.
* @param[in] vpt specifying the #fr_pair_t type to find.
* Must be one of the following types:
- * - #TMPL_TYPE_LIST
* - #TMPL_TYPE_ATTR
* @return
* - 0 on success (found matching #fr_pair_t).
{ L("data"), TMPL_TYPE_DATA },
{ L("attr"), TMPL_TYPE_ATTR },
- { L("list"), TMPL_TYPE_LIST },
{ L("exec"), TMPL_TYPE_EXEC },
{ L("xlat"), TMPL_TYPE_XLAT },
switch (vpt->type) {
case TMPL_TYPE_ATTR:
case TMPL_TYPE_ATTR_UNRESOLVED:
- case TMPL_TYPE_LIST:
break;
default:
{
switch (vpt->type) {
case TMPL_TYPE_ATTR:
- case TMPL_TYPE_LIST:
case TMPL_TYPE_ATTR_UNRESOLVED:
tmpl_attr_debug(vpt);
return;
case TMPL_TYPE_ATTR:
case TMPL_TYPE_ATTR_UNRESOLVED:
- case TMPL_TYPE_LIST:
tmpl_attr_list_talloc_init(tmpl_attr(vpt));
tmpl_request_list_talloc_init(&vpt->data.attribute.rr);
break;
return 0;
}
-/** Parse a string into a TMPL_TYPE_ATTR_* or #TMPL_TYPE_LIST type #tmpl_t
+/** Parse a string into a TMPL_TYPE_ATTR_* type #tmpl_t
*
* @param[in,out] ctx to allocate #tmpl_t in.
* @param[out] err May be NULL. Provides the exact error that the parser hit
* when processing the attribute ref.
* @param[out] out Where to write pointer to new #tmpl_t.
* @param[in] name of attribute including #tmpl_request_ref_t and #pair_list_t qualifiers.
- * If only #tmpl_request_ref_t #pair_list_t qualifiers are found,
- * a #TMPL_TYPE_LIST #tmpl_t will be produced.
* @param[in] p_rules Formatting rules used to check for trailing garbage.
* @param[in] t_rules Rules which control parsing:
* - dict_def The default dictionary to use if attributes
}
}
- /*
- * If there's no attribute references
- * treat this as a list reference.
- *
- * Eventually we'll remove TMPL_TYPE_LIST
- */
- if (!at_rules->list_as_attr && (tmpl_attr_list_num_elements(tmpl_attr(vpt)) == 0)) {
- tmpl_attr_t *ar;
- fr_slen_t slen;
-
- MEM(ar = talloc_zero(vpt, tmpl_attr_t));
- slen = tmpl_attr_parse_filter(err, ar, &our_name, at_rules);
- if (slen == 0) { /* No filter */
- talloc_free(ar);
- } else if (slen > 0) { /* Found a filter */
- tmpl_attr_list_insert_tail(&vpt->data.attribute.ar, ar);
- } else if (slen < 0) { /* Filter error */
- goto error;
- }
- vpt->type = TMPL_TYPE_LIST;
- }
-
tmpl_set_name(vpt, T_BARE_WORD, fr_sbuff_start(&our_name), fr_sbuff_used(&our_name));
vpt->rules = *t_rules; /* Record the rules */
FR_SBUFF_SET_RETURN(name, &our_name);
}
-/** Parse a string into a TMPL_TYPE_ATTR_* or #TMPL_TYPE_LIST type #tmpl_t
+/** Parse a string into a TMPL_TYPE_ATTR_* type #tmpl_t
*
* @param[in,out] ctx to allocate #tmpl_t in.
* @param[out] err May be NULL. Provides the exact error that the parser hit
* when processing the attribute ref.
* @param[out] out Where to write pointer to new #tmpl_t.
* @param[in] name of attribute including #tmpl_request_ref_t and #pair_list_t qualifiers.
- * If only #tmpl_request_ref_t #pair_list_t qualifiers are found,
- * a #TMPL_TYPE_LIST #tmpl_t will be produced.
* @param[in] t_rules Rules which control parsing. See tmpl_afrom_attr_substr() for details.
*
* @note Unlike #tmpl_afrom_attr_substr this function will error out if the entire
* These types contain dynamically allocated
* attribute and request references.
*/
- case TMPL_TYPE_LIST:
case TMPL_TYPE_ATTR:
case TMPL_TYPE_ATTR_UNRESOLVED:
tmpl_attr_list_talloc_free(tmpl_attr(vpt));
* Only print things we can print...
*/
switch (vpt->type) {
- case TMPL_TYPE_LIST:
case TMPL_TYPE_ATTR_UNRESOLVED:
case TMPL_TYPE_ATTR:
break;
TMPL_VERIFY(vpt);
switch (vpt->type) {
- case TMPL_TYPE_LIST:
case TMPL_TYPE_ATTR_UNRESOLVED:
case TMPL_TYPE_ATTR:
FR_SBUFF_RETURN(tmpl_attr_print, &our_out, vpt, ar_prefix);
}
break;
- case TMPL_TYPE_LIST:
- if ((nz = CHECK_ZEROED(vpt, attribute))) {
- PRINT_NON_ZEROED(vpt, attribute, nz);
- fr_fatal_assert_fail("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_LIST"
- "has non-zero bytes after the data.attribute struct in the union",
- file, line);
- }
-
- if ((tmpl_attr_list_num_elements(tmpl_attr(vpt)) > 0) &&
- ((tmpl_attr_t *)tmpl_attr_list_tail(tmpl_attr(vpt)))->da) {
-#ifndef NDEBUG
- tmpl_attr_debug(vpt);
-#endif
- fr_fatal_assert_fail("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_LIST contains %u "
- "references", file, line, tmpl_attr_list_num_elements(tmpl_attr(vpt)));
- }
- break;
-
case TMPL_TYPE_DATA:
if ((nz = CHECK_ZEROED(vpt, literal))) {
PRINT_NON_ZEROED(vpt, literal, nz);
if (!ctx) {
switch (map->lhs->type) {
case TMPL_TYPE_ATTR:
- case TMPL_TYPE_LIST:
tmpl_attr_rewrite_leaf_num(map->lhs, NUM_UNSPEC, NUM_ALL);
break;
*/
switch (map->rhs->type) {
case TMPL_TYPE_ATTR:
- case TMPL_TYPE_LIST:
tmpl_attr_rewrite_leaf_num(map->rhs, NUM_UNSPEC, NUM_ALL);
break;
switch (map->lhs->type) {
case TMPL_TYPE_ATTR:
- case TMPL_TYPE_LIST:
case TMPL_TYPE_XLAT:
break;
case TMPL_TYPE_ATTR:
case TMPL_TYPE_EXEC:
case TMPL_TYPE_DATA:
- case TMPL_TYPE_LIST:
case TMPL_TYPE_REGEX_XLAT_UNRESOLVED:
case TMPL_TYPE_UNRESOLVED:
case TMPL_TYPE_XLAT:
} else {
c_map->lhs = map->lhs; /* lhs shouldn't be touched, so this is ok */
}
- do_rhs:
+
if (vp->vp_type == FR_TYPE_STRING) {
quote = is_printable(vp->vp_strvalue, vp->vp_length) ?
T_SINGLE_QUOTED_STRING : T_DOUBLE_QUOTED_STRING;
TMPL_TYPE_DATA, quote, map->rhs->name, map->rhs->len));
if (fr_value_box_copy(c_map->rhs, tmpl_value(c_map->rhs), &vp->data) < 0) {
REDEBUG("Failed copying attribute value");
- error:
talloc_free(pool);
talloc_free(c);
RETURN_MODULE_FAIL;
}
break;
- /*
- * Lists are weird... We need to fudge a new LHS template,
- * which is a combination of the LHS list and the attribute.
- */
- case TMPL_TYPE_LIST:
- if (tmpl_attr_afrom_list(c_map, &c_map->lhs, map->lhs, vp->da) < 0) {
- RPERROR("Failed attribute -> list copy");
- goto error;
- }
- goto do_rhs;
-
default:
fr_assert(0);
}
type = tmpl_attr_tail_da(map->lhs)->type;
break;
- case TMPL_TYPE_LIST:
- break;
-
case TMPL_TYPE_ATTR_UNRESOLVED:
cf_log_err(map->ci, "Unknown attribute %s", tmpl_attr_tail_unresolved(map->lhs));
return -1;
*/
switch (vpt_p->type) {
case TMPL_TYPE_ATTR:
- case TMPL_TYPE_LIST:
{
#define VECTOR_INCREMENT 20
fr_dcursor_t cursor;