summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPavel Březina <pbrezina@redhat.com>2014-04-28 10:54:12 +0200
committerJakub Hrozek <jhrozek@redhat.com>2014-05-29 09:08:38 +0200
commitefa6c1f75c4c18bcc148d6e7efd429c2d56499ad (patch)
treee029f971cc0121605338be6df8cdab46c19017b7 /src
parentf43c6a9ae2aea13b7a83fd932139f9352efbfcad (diff)
downloadsssd-efa6c1f75c4c18bcc148d6e7efd429c2d56499ad.tar.gz
sssd-efa6c1f75c4c18bcc148d6e7efd429c2d56499ad.tar.xz
sssd-efa6c1f75c4c18bcc148d6e7efd429c2d56499ad.zip
sss_sifp: add support for string dictionary
https://fedorahosted.org/sssd/ticket/2254 Reviewed-by: Sumit Bose <sbose@redhat.com>
Diffstat (limited to 'src')
-rw-r--r--src/lib/sifp/sss_sifp.c6
-rw-r--r--src/lib/sifp/sss_sifp.h16
-rw-r--r--src/lib/sifp/sss_sifp_attrs.c24
-rw-r--r--src/lib/sifp/sss_sifp_parser.c197
-rw-r--r--src/lib/sifp/sss_sifp_private.h4
-rw-r--r--src/lib/sifp/sss_simpleifp.exports1
-rw-r--r--src/tests/cmocka/test_sss_sifp.c211
7 files changed, 452 insertions, 7 deletions
diff --git a/src/lib/sifp/sss_sifp.c b/src/lib/sifp/sss_sifp.c
index 0bf29075e..7e8e7c6d6 100644
--- a/src/lib/sifp/sss_sifp.c
+++ b/src/lib/sifp/sss_sifp.c
@@ -370,6 +370,12 @@ sss_sifp_free_attrs(sss_sifp_ctx *ctx,
}
_free(ctx, attrs[i]->data.str);
break;
+ case SSS_SIFP_ATTR_TYPE_STRING_DICT:
+ if (attrs[i]->data.str_dict != NULL) {
+ hash_destroy(attrs[i]->data.str_dict);
+ }
+ attrs[i]->data.str_dict = NULL;
+ break;
}
_free(ctx, attrs[i]->name);
_free(ctx, attrs[i]);
diff --git a/src/lib/sifp/sss_sifp.h b/src/lib/sifp/sss_sifp.h
index 4c6712f5f..6f897135c 100644
--- a/src/lib/sifp/sss_sifp.h
+++ b/src/lib/sifp/sss_sifp.h
@@ -24,6 +24,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
+#include <dhash.h>
/**
* @defgroup sss_simpleifp Simple interface to SSSD InfoPipe responder.
@@ -303,6 +304,21 @@ sss_sifp_find_attr_as_string(sss_sifp_attr **attrs,
const char **_value);
/**
+ * @brief Find attribute in list and return its value.
+ *
+ * The dictionary is stored in dhash table, the values
+ * are pointers to NULL-terminated string array.
+ *
+ * @param[in] attrs Attributes
+ * @param[in] name Name of the attribute to find
+ * @param[out] _value Output value
+ */
+sss_sifp_error
+sss_sifp_find_attr_as_string_dict(sss_sifp_attr **attrs,
+ const char *name,
+ hash_table_t **_value);
+
+/**
* @brief Find attribute in list and return its values.
*
* @param[in] attrs Attributes
diff --git a/src/lib/sifp/sss_sifp_attrs.c b/src/lib/sifp/sss_sifp_attrs.c
index 8e871b2e6..6d10c4611 100644
--- a/src/lib/sifp/sss_sifp_attrs.c
+++ b/src/lib/sifp/sss_sifp_attrs.c
@@ -158,6 +158,30 @@ sss_sifp_find_attr_as_string(sss_sifp_attr **attrs,
return SSS_SIFP_OK;
}
+sss_sifp_error
+sss_sifp_find_attr_as_string_dict(sss_sifp_attr **attrs,
+ const char *name,
+ hash_table_t **_value)
+{
+ sss_sifp_attr *attr = sss_sifp_find_attr(attrs, name);
+
+ if (attr == NULL) {
+ return SSS_SIFP_ATTR_MISSING;
+ }
+
+ if (attr->type != SSS_SIFP_ATTR_TYPE_STRING_DICT) {
+ return SSS_SIFP_INCORRECT_TYPE;
+ }
+
+ if (attr->data.str_dict == NULL) {
+ return SSS_SIFP_ATTR_NULL;
+ }
+
+ *_value = attr->data.str_dict;
+
+ return SSS_SIFP_OK;
+}
+
/**
* @brief Find attribute in list and return its values.
*
diff --git a/src/lib/sifp/sss_sifp_parser.c b/src/lib/sifp/sss_sifp_parser.c
index 1c1ff80c8..eaa57d8d5 100644
--- a/src/lib/sifp/sss_sifp_parser.c
+++ b/src/lib/sifp/sss_sifp_parser.c
@@ -20,6 +20,7 @@
#include <dbus/dbus.h>
#include <string.h>
+#include <dhash.h>
#include "lib/sifp/sss_sifp.h"
#include "lib/sifp/sss_sifp_private.h"
@@ -102,6 +103,149 @@ sss_sifp_get_array_length(DBusMessageIter *iter)
return size;
}
+static void hash_delete_cb(hash_entry_t *item,
+ hash_destroy_enum type,
+ void *pvt)
+{
+ sss_sifp_ctx *ctx = (sss_sifp_ctx*)pvt;
+ char **values = (char**)(item->value.ptr);
+ int i;
+
+ if (values == NULL) {
+ return;
+ }
+
+ for (i = 0; values[i] != NULL; i++) {
+ _free(ctx, values[i]);
+ values[i] = NULL;
+ }
+
+ _free(ctx, values);
+ item->value.ptr = NULL;
+}
+
+static sss_sifp_error
+sss_sifp_parse_dict(sss_sifp_ctx *ctx,
+ DBusMessageIter *iter,
+ hash_table_t *table)
+{
+ DBusMessageIter dict_iter;
+ DBusMessageIter array_iter;
+ sss_sifp_error ret;
+ hash_key_t table_key;
+ hash_value_t table_value;
+ const char *key = NULL;
+ const char *value = NULL;
+ char **values = NULL;
+ unsigned int i;
+ unsigned int num_values;
+ int hret;
+
+ dbus_message_iter_recurse(iter, &dict_iter);
+
+ /* get the key */
+ check_dbus_arg(&dict_iter, DBUS_TYPE_STRING, ret, done);
+ dbus_message_iter_get_basic(&dict_iter, &key);
+
+ table_key.type = HASH_KEY_STRING;
+ table_key.str = sss_sifp_strdup(ctx, key);
+ if (table_key.str == NULL) {
+ ret = SSS_SIFP_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ if (!dbus_message_iter_next(&dict_iter)) {
+ ret = SSS_SIFP_INTERNAL_ERROR;
+ goto done;
+ }
+
+ /* now read the value */
+ switch (dbus_message_iter_get_arg_type(&dict_iter)) {
+ case DBUS_TYPE_STRING:
+ dbus_message_iter_get_basic(&dict_iter, &value);
+ values = _alloc_zero(ctx, char *, 2);
+ if (values == NULL) {
+ ret = SSS_SIFP_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ values[0] = sss_sifp_strdup(ctx, value);
+ if (values[0] == NULL) {
+ ret = SSS_SIFP_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ values[1] = NULL;
+
+ ret = SSS_SIFP_OK;
+ break;
+ case DBUS_TYPE_ARRAY:
+ num_values = sss_sifp_get_array_length(&dict_iter);
+ if (num_values == 0) {
+ values = NULL;
+ ret = SSS_SIFP_OK;
+ goto done;
+ }
+
+ if (dbus_message_iter_get_element_type(&dict_iter)
+ != DBUS_TYPE_STRING) {
+ ret = SSS_SIFP_NOT_SUPPORTED;
+ goto done;
+ }
+
+ dbus_message_iter_recurse(&dict_iter, &array_iter);
+
+ values = _alloc_zero(ctx, char*, num_values + 1);
+ if (values == NULL) {
+ ret = SSS_SIFP_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ for (i = 0; i < num_values; i++) {
+ dbus_message_iter_get_basic(&array_iter, &value);
+ values[i] = sss_sifp_strdup(ctx, value);
+ if (values[i] == NULL) {
+ ret = SSS_SIFP_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ dbus_message_iter_next(&array_iter);
+ }
+
+ ret = SSS_SIFP_OK;
+ break;
+ default:
+ ret = SSS_SIFP_NOT_SUPPORTED;
+ break;
+ }
+
+ table_value.type = HASH_VALUE_PTR;
+ table_value.ptr = values;
+
+ hret = hash_enter(table, &table_key, &table_value);
+ if (hret == HASH_ERROR_NO_MEMORY) {
+ ret = SSS_SIFP_OUT_OF_MEMORY;
+ } else if (hret != HASH_SUCCESS) {
+ ret = SSS_SIFP_INTERNAL_ERROR;
+ }
+
+done:
+ if (table_key.str != NULL) {
+ _free(ctx, table_key.str);
+ }
+
+ if (ret != SSS_SIFP_OK) {
+ if (values != NULL) {
+ for (i = 0; values[i] != NULL; i++) {
+ _free(ctx, values[i]);
+ }
+ _free(ctx, values);
+ }
+ }
+
+ return ret;
+}
+
static sss_sifp_error
sss_sifp_parse_basic(sss_sifp_ctx *ctx,
DBusMessageIter *iter,
@@ -178,6 +322,7 @@ sss_sifp_parse_array(sss_sifp_ctx *ctx,
{
DBusMessageIter array_iter;
sss_sifp_error ret;
+ int hret;
attr->num_values = sss_sifp_get_array_length(iter);
dbus_message_iter_recurse(iter, &array_iter);
@@ -246,19 +391,57 @@ sss_sifp_parse_array(sss_sifp_ctx *ctx,
ret = SSS_SIFP_OK;
break;
+ case DBUS_TYPE_DICT_ENTRY:
+ attr->type = SSS_SIFP_ATTR_TYPE_STRING_DICT;
+ if (attr->num_values == 0) {
+ attr->data.str_dict = NULL;
+ ret = SSS_SIFP_OK;
+ goto done;
+ }
+
+ hret = hash_create_ex(10, &(attr->data.str_dict), 0, 0, 0, 0,
+ ctx->alloc_fn, ctx->free_fn, ctx->alloc_pvt,
+ hash_delete_cb, ctx);
+ if (hret != HASH_SUCCESS) {
+ ret = SSS_SIFP_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ for (i = 0; i < attr->num_values; i++) {
+ ret = sss_sifp_parse_dict(ctx, &array_iter, attr->data.str_dict);
+ if (ret != SSS_SIFP_OK) {
+ _free(ctx, attr->data.str_dict);
+ goto done;
+ }
+
+ if (!dbus_message_iter_next(&array_iter)
+ && i + 1 < attr->num_values) {
+ ret = SSS_SIFP_INTERNAL_ERROR;
+ goto done;
+ }
+ }
+
+ ret = SSS_SIFP_OK;
+ break;
default:
ret = SSS_SIFP_INVALID_ARGUMENT;
break;
}
done:
- if (ret != SSS_SIFP_OK && attr->type == SSS_SIFP_ATTR_TYPE_STRING
- && attr->data.str != NULL) {
- unsigned int i;
- for (i = 0; attr->data.str[i] != NULL && i < attr->num_values; i++) {
- _free(ctx, attr->data.str[i]);
+ if (ret != SSS_SIFP_OK) {
+ if (attr->type == SSS_SIFP_ATTR_TYPE_STRING && attr->data.str != NULL) {
+ for (unsigned int i = 0;
+ attr->data.str[i] != NULL && i < attr->num_values;
+ i++) {
+ _free(ctx, attr->data.str[i]);
+ }
+ _free(ctx, attr->data.str);
+ } else if (attr->type == SSS_SIFP_ATTR_TYPE_STRING_DICT
+ && attr->data.str_dict != NULL) {
+ hash_destroy(attr->data.str_dict);
+ attr->data.str_dict = NULL;
}
- _free(ctx, attr->data.str);
}
return ret;
@@ -283,6 +466,8 @@ sss_sifp_parse_variant(sss_sifp_ctx *ctx,
} else {
/* container types */
switch (type) {
+ /* case DBUS_TYPE_DICT_ENTRY may only be contained within an array
+ * in variant */
case DBUS_TYPE_ARRAY:
ret = sss_sifp_parse_array(ctx, &variant_iter, attr);;
break;
diff --git a/src/lib/sifp/sss_sifp_private.h b/src/lib/sifp/sss_sifp_private.h
index 0f21e4a97..b42d05146 100644
--- a/src/lib/sifp/sss_sifp_private.h
+++ b/src/lib/sifp/sss_sifp_private.h
@@ -53,7 +53,8 @@ enum sss_sifp_attr_type {
SSS_SIFP_ATTR_TYPE_UINT32,
SSS_SIFP_ATTR_TYPE_INT64,
SSS_SIFP_ATTR_TYPE_UINT64,
- SSS_SIFP_ATTR_TYPE_STRING
+ SSS_SIFP_ATTR_TYPE_STRING,
+ SSS_SIFP_ATTR_TYPE_STRING_DICT
};
/**
@@ -72,6 +73,7 @@ struct sss_sifp_attr {
int64_t *int64;
uint64_t *uint64;
char **str;
+ hash_table_t *str_dict;
} data;
};
diff --git a/src/lib/sifp/sss_simpleifp.exports b/src/lib/sifp/sss_simpleifp.exports
index 168d08fdd..921b49d58 100644
--- a/src/lib/sifp/sss_simpleifp.exports
+++ b/src/lib/sifp/sss_simpleifp.exports
@@ -23,6 +23,7 @@ SSS_SIMPLEIFP_0.0 {
sss_sifp_find_attr_as_int64;
sss_sifp_find_attr_as_uint64;
sss_sifp_find_attr_as_string;
+ sss_sifp_find_attr_as_string_dict;
sss_sifp_find_attr_as_bool_array;
sss_sifp_find_attr_as_int16_array;
sss_sifp_find_attr_as_uint16_array;
diff --git a/src/tests/cmocka/test_sss_sifp.c b/src/tests/cmocka/test_sss_sifp.c
index cddc1ab1b..4a5fd7265 100644
--- a/src/tests/cmocka/test_sss_sifp.c
+++ b/src/tests/cmocka/test_sss_sifp.c
@@ -595,6 +595,99 @@ void test_sss_sifp_parse_attr_object_path(void **state)
assert_null(attrs);
}
+void test_sss_sifp_parse_attr_string_dict(void **state)
+{
+ sss_sifp_ctx *ctx = test_ctx.dbus_ctx;
+ DBusMessage *reply = test_ctx.reply;
+ DBusMessageIter iter;
+ DBusMessageIter var_iter;
+ DBusMessageIter array_iter;
+ DBusMessageIter dict_iter;
+ dbus_bool_t bret;
+ sss_sifp_error ret;
+ sss_sifp_attr **attrs = NULL;
+ const char *name = "test-attr";
+ struct {
+ const char *key;
+ const char *value;
+ } data = {"key", "value"};
+ hash_table_t *out;
+ hash_key_t key;
+ hash_value_t value;
+ char **values;
+ int hret;
+
+ /* prepare message */
+ dbus_message_iter_init_append(reply, &iter);
+
+ bret = dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
+ DBUS_TYPE_ARRAY_AS_STRING
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+ &var_iter);
+ assert_true(bret);
+
+ bret = dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+ &array_iter);
+ assert_true(bret);
+
+ bret = dbus_message_iter_open_container(&array_iter,
+ DBUS_TYPE_DICT_ENTRY,
+ NULL, &dict_iter);
+ assert_true(bret);
+
+ bret = dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING,
+ &data.key);
+ assert_true(bret);
+
+ bret = dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING,
+ &data.value);
+ assert_true(bret);
+
+ bret = dbus_message_iter_close_container(&array_iter, &dict_iter);
+ assert_true(bret);
+
+ bret = dbus_message_iter_close_container(&var_iter, &array_iter);
+ assert_true(bret);
+
+ bret = dbus_message_iter_close_container(&iter, &var_iter);
+ assert_true(bret);
+
+ ret = sss_sifp_parse_attr(ctx, name, reply, &attrs);
+ assert_int_equal(ret, SSS_SIFP_OK);
+ assert_non_null(attrs);
+ assert_non_null(attrs[0]);
+ assert_null(attrs[1]);
+
+ assert_int_equal(attrs[0]->num_values, 1);
+ assert_int_equal(attrs[0]->type, SSS_SIFP_ATTR_TYPE_STRING_DICT);
+ assert_string_equal(attrs[0]->name, name);
+
+ ret = sss_sifp_find_attr_as_string_dict(attrs, name, &out);
+ assert_int_equal(ret, SSS_SIFP_OK);
+ assert_int_equal(hash_count(out), 1);
+
+ key.type = HASH_KEY_STRING;
+ key.str = discard_const(data.key);
+ hret = hash_lookup(out, &key, &value);
+ assert_int_equal(hret, HASH_SUCCESS);
+ assert_int_equal(value.type, HASH_VALUE_PTR);
+ assert_non_null(value.ptr);
+ values = value.ptr;
+ assert_non_null(values[0]);
+ assert_string_equal(values[0], data.value);
+ assert_null(values[1]);
+
+ sss_sifp_free_attrs(ctx, &attrs);
+ assert_null(attrs);
+}
+
void test_sss_sifp_parse_attr_bool_array(void **state)
{
sss_sifp_ctx *ctx = test_ctx.dbus_ctx;
@@ -973,6 +1066,120 @@ void test_sss_sifp_parse_attr_object_path_array(void **state)
assert_null(attrs);
}
+void test_sss_sifp_parse_attr_string_dict_array(void **state)
+{
+ return;
+
+ sss_sifp_ctx *ctx = test_ctx.dbus_ctx;
+ DBusMessage *reply = test_ctx.reply;
+ DBusMessageIter iter;
+ DBusMessageIter var_iter;
+ DBusMessageIter array_iter;
+ DBusMessageIter dict_iter;
+ DBusMessageIter val_iter;
+ dbus_bool_t bret;
+ sss_sifp_error ret;
+ sss_sifp_attr **attrs = NULL;
+ const char *name = "test-attr";
+ static struct {
+ const char *key;
+ const char *values[];
+ } data = {"key", {"value1", "value2", "value3"}};
+ unsigned int num_values = 3;
+ hash_table_t *out;
+ hash_key_t key;
+ hash_value_t value;
+ char **values;
+ unsigned int i;
+ int hret;
+
+ /* prepare message */
+ dbus_message_iter_init_append(reply, &iter);
+
+ bret = dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
+ DBUS_TYPE_ARRAY_AS_STRING
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_ARRAY_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+ &var_iter);
+ assert_true(bret);
+
+ bret = dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_ARRAY_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+ &array_iter);
+ assert_true(bret);
+
+ bret = dbus_message_iter_open_container(&array_iter,
+ DBUS_TYPE_DICT_ENTRY,
+ NULL, &dict_iter);
+ assert_true(bret);
+
+ bret = dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING,
+ &data.key);
+ assert_true(bret);
+
+ bret = dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_STRING_AS_STRING,
+ &val_iter);
+ assert_true(bret);
+
+ for (i = 0; i < num_values; i++) {
+ bret = dbus_message_iter_append_basic(&val_iter, DBUS_TYPE_STRING,
+ &data.values[i]);
+ assert_true(bret);
+ }
+
+ bret = dbus_message_iter_close_container(&dict_iter, &val_iter);
+ assert_true(bret);
+
+ bret = dbus_message_iter_close_container(&array_iter, &dict_iter);
+ assert_true(bret);
+
+ bret = dbus_message_iter_close_container(&var_iter, &array_iter);
+ assert_true(bret);
+
+ bret = dbus_message_iter_close_container(&iter, &var_iter);
+ assert_true(bret);
+
+ ret = sss_sifp_parse_attr(ctx, name, reply, &attrs);
+ assert_int_equal(ret, SSS_SIFP_OK);
+ assert_non_null(attrs);
+ assert_non_null(attrs[0]);
+ assert_null(attrs[1]);
+
+ assert_int_equal(attrs[0]->num_values, 1);
+ assert_int_equal(attrs[0]->type, SSS_SIFP_ATTR_TYPE_STRING_DICT);
+ assert_string_equal(attrs[0]->name, name);
+
+ ret = sss_sifp_find_attr_as_string_dict(attrs, name, &out);
+ assert_int_equal(ret, SSS_SIFP_OK);
+ assert_int_equal(hash_count(out), 1);
+
+ key.type = HASH_KEY_STRING;
+ key.str = discard_const(data.key);
+ hret = hash_lookup(out, &key, &value);
+ assert_int_equal(hret, HASH_SUCCESS);
+ assert_int_equal(value.type, HASH_VALUE_PTR);
+ assert_non_null(value.ptr);
+ values = value.ptr;
+
+ for (i = 0; i < num_values; i++) {
+ assert_non_null(values[i]);
+ assert_string_equal(values[i], data.values[i]);
+ }
+ assert_null(values[i]);
+
+
+ sss_sifp_free_attrs(ctx, &attrs);
+ assert_null(attrs);
+}
+
void test_sss_sifp_parse_attr_list(void **state)
{
sss_sifp_ctx *ctx = test_ctx.dbus_ctx;
@@ -1494,6 +1701,8 @@ int main(int argc, const char *argv[])
test_setup, test_teardown_parser),
unit_test_setup_teardown(test_sss_sifp_parse_attr_object_path,
test_setup, test_teardown_parser),
+ unit_test_setup_teardown(test_sss_sifp_parse_attr_string_dict,
+ test_setup, test_teardown_parser),
unit_test_setup_teardown(test_sss_sifp_parse_attr_bool_array,
test_setup, test_teardown_parser),
unit_test_setup_teardown(test_sss_sifp_parse_attr_int32_array,
@@ -1508,6 +1717,8 @@ int main(int argc, const char *argv[])
test_setup, test_teardown_parser),
unit_test_setup_teardown(test_sss_sifp_parse_attr_object_path_array,
test_setup, test_teardown_parser),
+ unit_test_setup_teardown(test_sss_sifp_parse_attr_string_dict_array,
+ test_setup, test_teardown_parser),
unit_test_setup_teardown(test_sss_sifp_parse_attr_list,
test_setup, test_teardown_parser),
unit_test_setup_teardown(test_sss_sifp_parse_attr_list_empty,