From fe9a0097970d12ff261b7417f9e57db95957ab24 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Wed, 17 Jun 2015 13:39:43 +0200
Subject: [PATCH 12/13] IFP: Add wildcard requests
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Resolves:
https://fedorahosted.org/sssd/ticket/2553
Can be used as:
dbus-send --print-reply --system --dest=org.freedesktop.sssd.infopipe \
/org/freedesktop/sssd/infopipe/Users \
org.freedesktop.sssd.infopipe.Users.ListByName \
string:r\* uint32:10
dbus-send --print-reply --system --dest=org.freedesktop.sssd.infopipe \
/org/freedesktop/sssd/infopipe/Groups \
org.freedesktop.sssd.infopipe.Groups.ListByName \
string:r\* uint32:10
dbus-send --print-reply --system --dest=org.freedesktop.sssd.infopipe \
/org/freedesktop/sssd/infopipe/Users \
org.freedesktop.sssd.infopipe.Users.ListByDomainAndName \
string:ipaldap string:r\* uint32:10
dbus-send --print-reply --system --dest=org.freedesktop.sssd.infopipe \
/org/freedesktop/sssd/infopipe/Groups \
org.freedesktop.sssd.infopipe.Groups.ListByDomainAndName \
string:ipaldap string:r\* uint32:10
By default the wildcard_limit is unset, that is, the request will return
all cached entries that match.
Reviewed-by: Pavel Březina <pbrezina@redhat.com>
---
src/confdb/confdb.h | 1 +
src/man/sssd-ifp.5.xml | 15 ++++
src/responder/ifp/ifp_groups.c | 175 ++++++++++++++++++++++++++++++++++++++
src/responder/ifp/ifp_private.h | 22 +++++
src/responder/ifp/ifp_users.c | 184 ++++++++++++++++++++++++++++++++++++++++
src/responder/ifp/ifpsrv.c | 23 +++++
src/responder/ifp/ifpsrv_util.c | 52 ++++++++++++
7 files changed, 472 insertions(+)
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index b2ec2e0b98a9be2d50009df524a1072e9b1c15c7..36df6aea268cc5c82696f20b1a65963350d5e100 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -140,6 +140,7 @@
/* InfoPipe */
#define CONFDB_IFP_CONF_ENTRY "config/ifp"
#define CONFDB_IFP_USER_ATTR_LIST "user_attributes"
+#define CONFDB_IFP_WILDCARD_LIMIT "wildcard_limit"
/* Domains */
#define CONFDB_DOMAIN_PATH_TMPL "config/domain/%s"
diff --git a/src/man/sssd-ifp.5.xml b/src/man/sssd-ifp.5.xml
index 867c117edccc3c000f7d9e8456298b72ebcdf693..da247f89dd2d9d08e0b1591d4c89f52197b278df 100644
--- a/src/man/sssd-ifp.5.xml
+++ b/src/man/sssd-ifp.5.xml
@@ -131,6 +131,21 @@ user_attributes = +telephoneNumber, -loginShell
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>wildcart_limit (integer)</term>
+ <listitem>
+ <para>
+ Specifies an upper limit on the number of entries
+ that are downloaded during a wildcard lookup that
+ overrides caller-supplied limit.
+ </para>
+ <para>
+ Default: 0 (let the caller set an upper limit)
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/responder/ifp/ifp_groups.c b/src/responder/ifp/ifp_groups.c
index 1b581b568f14362a47b4a80eb55d2de8eb936ae3..3060035924026641cc245f2a1970db9e2646e11c 100644
--- a/src/responder/ifp/ifp_groups.c
+++ b/src/responder/ifp/ifp_groups.c
@@ -81,6 +81,27 @@ done:
return ret;
}
+static int ifp_groups_list_copy(struct ifp_list_ctx *list_ctx,
+ struct ldb_result *result)
+{
+ size_t copy_count, i;
+
+ copy_count = ifp_list_ctx_remaining_capacity(list_ctx, result->count);
+
+ for (i = 0; i < copy_count; i++) {
+ list_ctx->paths[list_ctx->path_count + i] = \
+ ifp_groups_build_path_from_msg(list_ctx->paths,
+ list_ctx->dom,
+ result->msgs[i]);
+ if (list_ctx->paths[list_ctx->path_count + i] == NULL) {
+ return ENOMEM;
+ }
+ }
+
+ list_ctx->path_count += copy_count;
+ return EOK;
+}
+
static void ifp_groups_find_by_name_done(struct tevent_req *req);
int ifp_groups_find_by_name(struct sbus_request *sbus_req,
@@ -221,23 +242,177 @@ done:
return;
}
+static int ifp_groups_list_by_name_step(struct ifp_list_ctx *list_ctx);
+static void ifp_groups_list_by_name_done(struct tevent_req *req);
+static void ifp_groups_list_by_name_reply(struct ifp_list_ctx *list_ctx);
+
int ifp_groups_list_by_name(struct sbus_request *sbus_req,
void *data,
const char *filter,
uint32_t limit)
{
+ struct ifp_ctx *ctx;
+ struct ifp_list_ctx *list_ctx;
+
+ ctx = talloc_get_type(data, struct ifp_ctx);
+ if (ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n");
+ return ERR_INTERNAL;
+ }
+
+ list_ctx = ifp_list_ctx_new(sbus_req, ctx, filter, limit);
+ if (list_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ return ifp_groups_list_by_name_step(list_ctx);
+}
+
+static int ifp_groups_list_by_name_step(struct ifp_list_ctx *list_ctx)
+{
+ struct tevent_req *req;
+
+ req = cache_req_group_by_filter_send(list_ctx,
+ list_ctx->ctx->rctx->ev,
+ list_ctx->ctx->rctx,
+ list_ctx->dom->name,
+ list_ctx->filter);
+ if (req == NULL) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req,
+ ifp_groups_list_by_name_done, list_ctx);
+
return EOK;
}
+static void ifp_groups_list_by_name_done(struct tevent_req *req)
+{
+ DBusError *error;
+ struct ifp_list_ctx *list_ctx;
+ struct sbus_request *sbus_req;
+ struct ldb_result *result;
+ struct sss_domain_info *domain;
+ errno_t ret;
+
+ list_ctx = tevent_req_callback_data(req, struct ifp_list_ctx);
+ sbus_req = list_ctx->sbus_req;
+
+ ret = cache_req_group_by_name_recv(sbus_req, req, &result, &domain, NULL);
+ talloc_zfree(req);
+ if (ret != EOK && ret != ENOENT) {
+ error = sbus_error_new(sbus_req, DBUS_ERROR_FAILED, "Failed to fetch "
+ "groups by filter [%d]: %s\n", ret, sss_strerror(ret));
+ sbus_request_fail_and_finish(sbus_req, error);
+ return;
+ }
+
+ ret = ifp_groups_list_copy(list_ctx, result);
+ if (ret != EOK) {
+ error = sbus_error_new(sbus_req, SBUS_ERROR_INTERNAL,
+ "Failed to copy domain result");
+ sbus_request_fail_and_finish(sbus_req, error);
+ return;
+ }
+
+ list_ctx->dom = get_next_domain(list_ctx->dom, true);
+ if (list_ctx->dom == NULL) {
+ return ifp_groups_list_by_name_reply(list_ctx);
+ }
+
+ ret = ifp_groups_list_by_name_step(list_ctx);
+ if (ret != EOK) {
+ error = sbus_error_new(sbus_req, SBUS_ERROR_INTERNAL,
+ "Failed to start next-domain search");
+ sbus_request_fail_and_finish(sbus_req, error);
+ return;
+ }
+}
+
+static void ifp_groups_list_by_name_reply(struct ifp_list_ctx *list_ctx)
+{
+ iface_ifp_groups_ListByDomainAndName_finish(list_ctx->sbus_req,
+ list_ctx->paths,
+ list_ctx->path_count);
+}
+
+static void ifp_groups_list_by_domain_and_name_done(struct tevent_req *req);
+
int ifp_groups_list_by_domain_and_name(struct sbus_request *sbus_req,
void *data,
const char *domain,
const char *filter,
uint32_t limit)
{
+ struct tevent_req *req;
+ struct ifp_ctx *ctx;
+ struct ifp_list_ctx *list_ctx;
+
+ ctx = talloc_get_type(data, struct ifp_ctx);
+ if (ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n");
+ return ERR_INTERNAL;
+ }
+
+ list_ctx = ifp_list_ctx_new(sbus_req, ctx, filter, limit);
+ if (list_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ req = cache_req_group_by_filter_send(list_ctx, ctx->rctx->ev, ctx->rctx,
+ domain, filter);
+ if (req == NULL) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req,
+ ifp_groups_list_by_domain_and_name_done, list_ctx);
+
return EOK;
}
+static void ifp_groups_list_by_domain_and_name_done(struct tevent_req *req)
+{
+ DBusError *error;
+ struct ifp_list_ctx *list_ctx;
+ struct sbus_request *sbus_req;
+ struct ldb_result *result;
+ struct sss_domain_info *domain;
+ errno_t ret;
+
+ list_ctx = tevent_req_callback_data(req, struct ifp_list_ctx);
+ sbus_req = list_ctx->sbus_req;
+
+ ret = cache_req_user_by_name_recv(sbus_req, req, &result, &domain, NULL);
+ talloc_zfree(req);
+ if (ret == ENOENT) {
+ error = sbus_error_new(sbus_req, SBUS_ERROR_NOT_FOUND,
+ "User not found by filter");
+ goto done;
+ } else if (ret != EOK) {
+ error = sbus_error_new(sbus_req, DBUS_ERROR_FAILED, "Failed to fetch "
+ "groups by filter [%d]: %s\n", ret, sss_strerror(ret));
+ goto done;
+ }
+
+ ret = ifp_groups_list_copy(list_ctx, result);
+ if (ret != EOK) {
+ error = sbus_error_new(sbus_req, SBUS_ERROR_INTERNAL,
+ "Failed to copy domain result");
+ goto done;
+ }
+
+done:
+ if (ret != EOK) {
+ sbus_request_fail_and_finish(sbus_req, error);
+ return;
+ }
+
+ iface_ifp_groups_ListByDomainAndName_finish(sbus_req,
+ list_ctx->paths,
+ list_ctx->path_count);
+ return;
+}
+
static errno_t
ifp_groups_group_get(struct sbus_request *sbus_req,
void *data,
diff --git a/src/responder/ifp/ifp_private.h b/src/responder/ifp/ifp_private.h
index 304e4dc535aac4215cf318a0bea845c161c5f079..43519de6fef3033f1e47cecb787d6b02dc9c6e56 100644
--- a/src/responder/ifp/ifp_private.h
+++ b/src/responder/ifp/ifp_private.h
@@ -44,6 +44,7 @@ struct ifp_ctx {
struct sysbus_ctx *sysbus;
const char **user_whitelist;
+ uint32_t wildcard_limit;
};
errno_t ifp_register_sbus_interface(struct sbus_connection *conn,
@@ -84,4 +85,25 @@ ifp_get_user_extra_attributes(TALLOC_CTX *mem_ctx, struct ifp_ctx *ifp_ctx);
bool ifp_attr_allowed(const char *whitelist[], const char *attr);
bool ifp_is_user_attr_allowed(struct ifp_ctx *ifp_ctx, const char *attr);
+/* Used for list calls */
+struct ifp_list_ctx {
+ struct sbus_request *sbus_req;
+ const char *filter;
+ uint32_t limit;
+
+ struct sss_domain_info *dom;
+ struct ifp_ctx *ctx;
+
+ const char **paths;
+ size_t path_count;
+};
+
+struct ifp_list_ctx *ifp_list_ctx_new(struct sbus_request *sbus_req,
+ struct ifp_ctx *ctx,
+ const char *filter,
+ uint32_t limit);
+
+size_t ifp_list_ctx_remaining_capacity(struct ifp_list_ctx *list_ctx,
+ size_t entries);
+
#endif /* _IFPSRV_PRIVATE_H_ */
diff --git a/src/responder/ifp/ifp_users.c b/src/responder/ifp/ifp_users.c
index 2ec74c30b348ac5f2b84cdc8e2dd406fd44a7da3..effefdc0435d794206dbe7358c61d2ea47760361 100644
--- a/src/responder/ifp/ifp_users.c
+++ b/src/responder/ifp/ifp_users.c
@@ -309,23 +309,207 @@ done:
return;
}
+static int ifp_users_list_copy(struct ifp_list_ctx *list_ctx,
+ struct ldb_result *result)
+{
+ size_t copy_count, i;
+
+ copy_count = ifp_list_ctx_remaining_capacity(list_ctx, result->count);
+
+ for (i = 0; i < copy_count; i++) {
+ list_ctx->paths[list_ctx->path_count + i] = \
+ ifp_users_build_path_from_msg(list_ctx->paths,
+ list_ctx->dom,
+ result->msgs[i]);
+ if (list_ctx->paths[list_ctx->path_count + i] == NULL) {
+ return ENOMEM;
+ }
+ }
+
+ list_ctx->path_count += copy_count;
+ return EOK;
+}
+
+static int ifp_users_list_by_name_step(struct ifp_list_ctx *list_ctx);
+static void ifp_users_list_by_name_done(struct tevent_req *req);
+static void ifp_users_list_by_name_reply(struct ifp_list_ctx *list_ctx);
+
int ifp_users_list_by_name(struct sbus_request *sbus_req,
void *data,
const char *filter,
uint32_t limit)
{
+ struct ifp_ctx *ctx;
+ struct ifp_list_ctx *list_ctx;
+
+ ctx = talloc_get_type(data, struct ifp_ctx);
+ if (ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n");
+ return ERR_INTERNAL;
+ }
+
+ list_ctx = ifp_list_ctx_new(sbus_req, ctx, filter, limit);
+ if (list_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ return ifp_users_list_by_name_step(list_ctx);
+}
+
+static int ifp_users_list_by_name_step(struct ifp_list_ctx *list_ctx)
+{
+ struct tevent_req *req;
+
+ req = cache_req_user_by_filter_send(list_ctx,
+ list_ctx->ctx->rctx->ev,
+ list_ctx->ctx->rctx,
+ list_ctx->dom->name,
+ list_ctx->filter);
+ if (req == NULL) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req,
+ ifp_users_list_by_name_done, list_ctx);
+
return EOK;
}
+static void ifp_users_list_by_name_done(struct tevent_req *req)
+{
+ DBusError *error;
+ struct ifp_list_ctx *list_ctx;
+ struct sbus_request *sbus_req;
+ struct ldb_result *result;
+ struct sss_domain_info *domain;
+ errno_t ret;
+
+ list_ctx = tevent_req_callback_data(req, struct ifp_list_ctx);
+ sbus_req = list_ctx->sbus_req;
+
+ ret = cache_req_user_by_name_recv(sbus_req, req, &result, &domain, NULL);
+ talloc_zfree(req);
+ if (ret != EOK && ret != ENOENT) {
+ error = sbus_error_new(sbus_req, DBUS_ERROR_FAILED, "Failed to fetch "
+ "users by filter [%d]: %s\n", ret, sss_strerror(ret));
+ sbus_request_fail_and_finish(sbus_req, error);
+ return;
+ }
+
+ ret = ifp_users_list_copy(list_ctx, result);
+ if (ret != EOK) {
+ error = sbus_error_new(sbus_req, SBUS_ERROR_INTERNAL,
+ "Failed to copy domain result");
+ sbus_request_fail_and_finish(sbus_req, error);
+ return;
+ }
+
+ list_ctx->dom = get_next_domain(list_ctx->dom, true);
+ if (list_ctx->dom == NULL) {
+ return ifp_users_list_by_name_reply(list_ctx);
+ }
+
+ ret = ifp_users_list_by_name_step(list_ctx);
+ if (ret != EOK) {
+ error = sbus_error_new(sbus_req, SBUS_ERROR_INTERNAL,
+ "Failed to start next-domain search");
+ sbus_request_fail_and_finish(sbus_req, error);
+ return;
+ }
+}
+
+static void ifp_users_list_by_name_reply(struct ifp_list_ctx *list_ctx)
+{
+ iface_ifp_users_ListByName_finish(list_ctx->sbus_req,
+ list_ctx->paths,
+ list_ctx->path_count);
+}
+
+static void ifp_users_list_by_domain_and_name_done(struct tevent_req *req);
+
int ifp_users_list_by_domain_and_name(struct sbus_request *sbus_req,
void *data,
const char *domain,
const char *filter,
uint32_t limit)
{
+ struct tevent_req *req;
+ struct ifp_ctx *ctx;
+ struct ifp_list_ctx *list_ctx;
+
+ ctx = talloc_get_type(data, struct ifp_ctx);
+ if (ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n");
+ return ERR_INTERNAL;
+ }
+
+ list_ctx = ifp_list_ctx_new(sbus_req, ctx, filter, limit);
+ if (list_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ req = cache_req_user_by_filter_send(list_ctx, ctx->rctx->ev, ctx->rctx,
+ domain, filter);
+ if (req == NULL) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req,
+ ifp_users_list_by_domain_and_name_done, list_ctx);
+
return EOK;
}
+static void ifp_users_list_by_domain_and_name_done(struct tevent_req *req)
+{
+ DBusError *error;
+ struct ifp_list_ctx *list_ctx;
+ struct sbus_request *sbus_req;
+ struct ldb_result *result;
+ struct sss_domain_info *domain;
+ errno_t ret;
+ size_t copy_count, i;
+
+ list_ctx = tevent_req_callback_data(req, struct ifp_list_ctx);
+ sbus_req = list_ctx->sbus_req;
+
+ ret = cache_req_user_by_name_recv(sbus_req, req, &result, &domain, NULL);
+ talloc_zfree(req);
+ if (ret == ENOENT) {
+ error = sbus_error_new(sbus_req, SBUS_ERROR_NOT_FOUND,
+ "User not found by filter");
+ goto done;
+ } else if (ret != EOK) {
+ error = sbus_error_new(sbus_req, DBUS_ERROR_FAILED, "Failed to fetch "
+ "users by filter [%d]: %s\n", ret, sss_strerror(ret));
+ goto done;
+ }
+
+ copy_count = ifp_list_ctx_remaining_capacity(list_ctx, result->count);
+
+ for (i = 0; i < copy_count; i++) {
+ list_ctx->paths[i] = ifp_users_build_path_from_msg(list_ctx->paths,
+ list_ctx->dom,
+ result->msgs[i]);
+ if (list_ctx->paths[i] == NULL) {
+ error = sbus_error_new(sbus_req, SBUS_ERROR_INTERNAL,
+ "Failed to compose object path");
+ goto done;
+ }
+ }
+
+ list_ctx->path_count += copy_count;
+
+done:
+ if (ret != EOK) {
+ sbus_request_fail_and_finish(sbus_req, error);
+ return;
+ }
+
+ iface_ifp_users_ListByDomainAndName_finish(sbus_req,
+ list_ctx->paths,
+ list_ctx->path_count);
+ return;
+}
+
static errno_t
ifp_users_user_get(struct sbus_request *sbus_req,
struct ifp_ctx *ifp_ctx,
diff --git a/src/responder/ifp/ifpsrv.c b/src/responder/ifp/ifpsrv.c
index 631bcd266d7e06154dbf1f37f9f439119b2b8944..cdc411faa330dc2c063e52abe63cd68dbe16a5d9 100644
--- a/src/responder/ifp/ifpsrv.c
+++ b/src/responder/ifp/ifpsrv.c
@@ -34,6 +34,7 @@
#include <dbus/dbus.h>
#include "util/util.h"
+#include "util/strtonum.h"
#include "sbus/sssd_dbus.h"
#include "monitor/monitor_interfaces.h"
#include "confdb/confdb.h"
@@ -228,6 +229,7 @@ int ifp_process_init(TALLOC_CTX *mem_ctx,
int max_retries;
char *uid_str;
char *attr_list_str;
+ char *wildcard_limit_str;
ifp_cmds = get_ifp_cmds();
ret = sss_process_init(mem_ctx, ev, cdb,
@@ -321,6 +323,27 @@ int ifp_process_init(TALLOC_CTX *mem_ctx,
goto fail;
}
+ /* A bit convoluted way until we have a confdb_get_uint32 */
+ ret = confdb_get_string(ifp_ctx->rctx->cdb,
+ ifp_ctx->rctx,
+ CONFDB_IFP_CONF_ENTRY,
+ CONFDB_IFP_WILDCARD_LIMIT,
+ NULL, /* no limit by default */
+ &wildcard_limit_str);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to retrieve limit for a wildcard search\n");
+ goto fail;
+ }
+
+ if (wildcard_limit_str) {
+ ifp_ctx->wildcard_limit = strtouint32(wildcard_limit_str, NULL, 10);
+ if (errno != 0) {
+ ret = errno;
+ goto fail;
+ }
+ }
+
for (iter = ifp_ctx->rctx->be_conns; iter; iter = iter->next) {
sbus_reconnect_init(iter->conn, max_retries,
ifp_dp_reconnect_init, iter);
diff --git a/src/responder/ifp/ifpsrv_util.c b/src/responder/ifp/ifpsrv_util.c
index 674165ee4901115c9e17458a75fdb3536b6468c2..3b02fd06f5227e4ffc3d40ffb20fed981c5028a7 100644
--- a/src/responder/ifp/ifpsrv_util.c
+++ b/src/responder/ifp/ifpsrv_util.c
@@ -21,6 +21,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <sys/param.h>
+
#include "db/sysdb.h"
#include "responder/ifp/ifp_private.h"
@@ -269,3 +271,53 @@ ifp_is_user_attr_allowed(struct ifp_ctx *ifp_ctx, const char *attr)
{
return ifp_attr_allowed(ifp_ctx->user_whitelist, attr);
}
+
+static uint32_t ifp_list_limit(struct ifp_ctx *ctx, uint32_t limit)
+{
+ if (ctx->wildcard_limit) {
+ return MIN(ctx->wildcard_limit, limit);
+ } else {
+ return limit;
+ }
+}
+
+struct ifp_list_ctx *ifp_list_ctx_new(struct sbus_request *sbus_req,
+ struct ifp_ctx *ctx,
+ const char *filter,
+ uint32_t limit)
+{
+ struct ifp_list_ctx *list_ctx;
+
+ list_ctx = talloc_zero(sbus_req, struct ifp_list_ctx);
+ if (list_ctx == NULL) {
+ return NULL;
+ }
+
+ list_ctx->sbus_req = sbus_req;
+ list_ctx->limit = ifp_list_limit(ctx, limit);
+ list_ctx->ctx = ctx;
+ list_ctx->dom = ctx->rctx->domains;
+ list_ctx->filter = filter;
+ list_ctx->paths = talloc_zero_array(list_ctx, const char *, limit);
+ if (list_ctx->paths == NULL) {
+ talloc_free(list_ctx);
+ return NULL;
+ }
+
+ return list_ctx;
+}
+
+size_t ifp_list_ctx_remaining_capacity(struct ifp_list_ctx *list_ctx,
+ size_t entries)
+{
+ size_t capacity = list_ctx->limit - list_ctx->path_count;
+
+ if (capacity < entries) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "IFP list request has limit of %"PRIu32" entries but back end "
+ "returned %zu entries\n", list_ctx->limit, entries);
+ return capacity;
+ } else {
+ return entries;
+ }
+}
--
2.4.3