/*
  Copyright (C) 2015  ABRT team

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "libabrt.h"
#include "abrt_problems2_entry.h"

#include <dbus/dbus.h>
#include <gio/gunixfdlist.h>

typedef struct
{
    char *p2e_dirname;
    AbrtP2EntryState p2e_state;
} AbrtP2EntryPrivate;

struct _AbrtP2Entry
{
    GObject parent_instance;
    AbrtP2EntryPrivate *pv;
};

G_DEFINE_TYPE_WITH_PRIVATE(AbrtP2Entry, abrt_p2_entry, G_TYPE_OBJECT)

static void abrt_p2_entry_finalize(GObject *gobject)
{
    AbrtP2EntryPrivate *pv = abrt_p2_entry_get_instance_private(ABRT_P2_ENTRY(gobject));
    free(pv->p2e_dirname);
}

static void abrt_p2_entry_class_init(AbrtP2EntryClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);
    object_class->finalize = abrt_p2_entry_finalize;
}

static void abrt_p2_entry_init(AbrtP2Entry *self)
{
    self->pv = abrt_p2_entry_get_instance_private(self);
}

AbrtP2Entry *abrt_p2_entry_new(char *dirname)
{
    return abrt_p2_entry_new_with_state(dirname, ABRT_P2_ENTRY_STATE_COMPLETE);
}

AbrtP2Entry *abrt_p2_entry_new_with_state(char *dirname,
            AbrtP2EntryState state)
{
    AbrtP2Entry *entry = g_object_new(TYPE_ABRT_P2_ENTRY, NULL);
    entry->pv->p2e_dirname = dirname;
    entry->pv->p2e_state = state;

    return entry;
}

AbrtP2EntryState abrt_p2_entry_state(AbrtP2Entry *entry)
{
    return entry->pv->p2e_state;
}

void abrt_p2_entry_set_state(AbrtP2Entry *entry, AbrtP2EntryState state)
{
    entry->pv->p2e_state = state;
}

const char *abrt_p2_entry_problem_id(AbrtP2Entry *entry)
{
    return entry->pv->p2e_dirname;
}

int abrt_p2_entry_accessible_by_uid(AbrtP2Entry *entry,
            uid_t uid,
            struct dump_dir **dd)
{
    struct dump_dir *tmp = dd_opendir(entry->pv->p2e_dirname, DD_OPEN_FD_ONLY
                                                              | DD_FAIL_QUIETLY_ENOENT
                                                              | DD_FAIL_QUIETLY_EACCES);
    if (tmp == NULL)
    {
        VERB2 perror_msg("can't open problem directory '%s'",
                         entry->pv->p2e_dirname);

        return -ENOTDIR;
    }

    const int ret = dd_accessible_by_uid(tmp, uid) ? 0 : -EACCES;

    if (ret == 0 && dd != NULL)
        *dd = tmp;
    else
        dd_close(tmp);

    return ret;
}

int abrt_p2_entry_delete(AbrtP2Entry *entry, uid_t caller_uid, GError **error)
{
    struct dump_dir *dd = NULL;
    int ret = abrt_p2_entry_accessible_by_uid(entry, caller_uid, &dd);
    if (ret != 0)
    {
        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED,
                    "You are not authorized to delete the problem");

        return ret;
    }

    if (entry->pv->p2e_state == ABRT_P2_ENTRY_STATE_DELETED)
    {
        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
                    "Problem entry is already deleted");

        dd_close(dd);
        return -EINVAL;
    }

    dd = dd_fdopendir(dd, DD_DONT_WAIT_FOR_LOCK);
    if (dd == NULL)
    {
        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
                    "Cannot lock the problem. Check system logs.");

        return -EWOULDBLOCK;
    }

    ret = dd_delete(dd);
    if (ret != 0)
    {
        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
                    "Failed to remove problem data. Check system logs.");

        dd_close(dd);
        return ret;
    }

    abrt_p2_entry_set_state(entry, ABRT_P2_ENTRY_STATE_DELETED);

    return ret;
}

GVariant *abrt_p2_entry_problem_data(AbrtP2Entry *node,
            uid_t caller_uid,
            GError **error)
{
    struct dump_dir *dd = abrt_p2_entry_open_dump_dir(node,
                                                      caller_uid,
                                                      DD_OPEN_READONLY,
                                                      error);
    if (dd == NULL)
        return NULL;

    problem_data_t *pd = create_problem_data_from_dump_dir(dd);
    problem_data_add_text_noteditable(pd, CD_DUMPDIR, node->pv->p2e_dirname);

    GVariantBuilder response_builder;
    g_variant_builder_init(&response_builder, G_VARIANT_TYPE_ARRAY);

    GHashTableIter pd_iter;
    char *element_name;
    struct problem_item *element_info;
    g_hash_table_iter_init(&pd_iter, pd);
    while (g_hash_table_iter_next(&pd_iter, (void**)&element_name, (void**)&element_info))
    {
        unsigned long size = 0;
        if (problem_item_get_size(element_info, &size) != 0)
        {
            log_notice("Can't get stat of : '%s'", element_info->content);
            continue;
        }

        g_variant_builder_add(&response_builder, "{s(its)}",
                                                 element_name,
                                                 element_info->flags,
                                                 size,
                                                 element_info->content);
    }

    problem_data_free(pd);
    dd_close(dd);

    return g_variant_new("(a{s(its)})", &response_builder);
}

struct dump_dir *abrt_p2_entry_open_dump_dir(AbrtP2Entry *entry,
             uid_t caller_uid,
             int dd_flags,
             GError **error)
{
    struct dump_dir *dd = NULL;
    if (0 != abrt_p2_entry_accessible_by_uid(entry, caller_uid, &dd))
    {
        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED,
                    "You are not authorized to access the problem");

        return NULL;
    }

    dd = dd_fdopendir(dd, dd_flags);
    if (dd == NULL)
    {
        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_IO_ERROR,
                    "Failed reopen dump directory");

        return NULL;
    }

    return dd;
}

/**
 * Read elements
 */
GVariant *abrt_p2_entry_read_elements(AbrtP2Entry *entry,
             gint32 flags,
             GVariant *elements,
             GUnixFDList *fd_list,
             uid_t caller_uid,
             long max_size,
             long max_unix_fds,
             GError **error)
{
    if ((flags & ABRT_P2_ENTRY_READ_ALL_FD) && (flags & ABRT_P2_ENTRY_READ_ALL_NO_FD))
    {
        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
                    "Invalid arguments 'ALL FD' ~ 'ALL NO FD'");

        return NULL;
    }

    struct dump_dir *dd = abrt_p2_entry_open_dump_dir(entry,
                                                      caller_uid,
                                                      DD_OPEN_READONLY | DD_DONT_WAIT_FOR_LOCK,
                                                      error);
    if (dd == NULL)
        return NULL;

    GVariantBuilder builder;
    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));

    size_t loaded_size = 0;
    gchar *name = NULL;
    GVariantIter iter;
    g_variant_iter_init(&iter, elements);
    /* No need to free 'name' unless breaking out of the loop */
    while (g_variant_iter_loop(&iter, "s", &name))
    {
        log_debug("Reading element: %s", name);
        /* Do ask me why -> see libreport xmalloc_read() */
        size_t data_size = (INT_MAX - 4095);

        int elem_type = 0;
        char *data = NULL;
        int fd = -1;
        const int r = problem_data_load_dump_dir_element(dd,
                                                         name,
                                                         &data,
                                                         &elem_type,
                                                         &fd);
        if (r < 0)
        {
            if (r == -ENOENT)
                log_debug("Element does not exist: %s", name);
            else if (r == -EINVAL)
                error_msg("Attempt to read prohibited data: '%s'", name);
            else
                error_msg("Failed to open %s: %s", name, strerror(-r));

            continue;
        }

        if (   ((flags & ABRT_P2_ENTRY_READ_ONLY_TEXT)     && !(elem_type & CD_FLAG_TXT))
            || ((flags & ABRT_P2_ENTRY_READ_ONLY_BIG_TEXT) && !(elem_type & CD_FLAG_BIGTXT))
            || ((flags & ABRT_P2_ENTRY_READ_ONLY_BINARY)   && !(elem_type & CD_FLAG_BIN))
           )
        {
            log_debug("Element is not of the requested type: %s", name);

            free(data);
            close(fd);
            continue;
        }

        if ((flags & ABRT_P2_ENTRY_READ_ALL_FD) || !(elem_type & CD_FLAG_TXT))
        {
            log_debug("Rewinding file descriptor %d", fd);

            free(data);
            if (lseek(fd, 0, SEEK_SET))
            {
                perror_msg("Failed to rewind file descriptor of %s", name);

                close(fd);
                continue;
            }
        }

        if (   (flags & ABRT_P2_ENTRY_READ_ALL_FD)
            || (!(flags & ABRT_P2_ENTRY_READ_ALL_NO_FD) && !(elem_type & CD_FLAG_TXT)))
        {
            if (g_unix_fd_list_get_length(fd_list) == max_unix_fds)
            {
                error_msg("Reached limit of UNIX FDs per message: %ld", max_unix_fds);
                close(fd);
                continue;
            }

            GError *error = NULL;
            const gint pos = g_unix_fd_list_append(fd_list, fd, &error);
            close(fd);
            if (error != NULL)
            {
                error_msg("Failed to add file descriptor of %s: %s",
                          name,
                          error->message);

                g_error_free(error);
                continue;
            }

            log_debug("Adding new Unix FD at position: %d",  pos);

            g_variant_builder_add(&builder, "{sv}",
                                            name,
                                            g_variant_new("h",
                                                          pos));
            continue;
        }

        if (!(elem_type & CD_FLAG_TXT))
        {
            data = xmalloc_read(fd, &data_size);

            log_debug("Re-loaded entire element: %zu Bytes", data_size);
        }
        else
            data_size = strlen(data);

        close(fd);

        if (data_size > DBUS_MAXIMUM_ARRAY_LENGTH)
        {
            error_msg("Element '%s' cannot be returned as array due to length limit: %ld",
                      name,
                      (long)DBUS_MAXIMUM_ARRAY_LENGTH);

            free(data);

            continue;
        }

        if (data_size > max_size || loaded_size > max_size - data_size)
        {
            error_msg("With element '%s', reached runtime data size limit: %ld",
                      name,
                      max_size);

            free(data);

            continue;
        }

        if (loaded_size > ULONG_MAX - data_size)
        {
            error_msg("With element '%s', reached static data size limit: %ld",
                      name,
                      max_size);

            free(data);

            continue;
        }

        loaded_size += data_size;

        if (elem_type & CD_FLAG_BIN)
        {
            log_debug("Adding element binary data");
            g_variant_builder_add(&builder, "{sv}",
                                             name,
                                             g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
                                                                       data,
                                                                       data_size,
                                                                       sizeof(char)));
        }
        else
        {
            log_debug("Adding element text data");
            g_variant_builder_add(&builder, "{sv}",
                                            name,
                                            g_variant_new_string(data));
        }

        free(data);
    }

    dd_close(dd);

    GVariant *retval_body[1];
    retval_body[0] = g_variant_builder_end(&builder);
    return  g_variant_new_tuple(retval_body, ARRAY_SIZE(retval_body));
}

/**
 * Asynchronous version of Read elements
 */
typedef struct
{
    gint32 flags;
    GVariant *elements;
    GUnixFDList *fd_list;
    uid_t caller_uid;
    long  max_size;
    long  max_unix_fds;
} AbrtP2EntryReadElementsData;

#define abrt_p2_entry_read_elements_data_new() \
    xmalloc(sizeof(AbrtP2EntryReadElementsData))

static inline void abrt_p2_entry_read_elements_data_free(AbrtP2EntryReadElementsData *data)
{
    free(data);
}

void abrt_p2_entry_read_elements_async_task(GTask *task,
            gpointer source_object,
            gpointer task_data,
            GCancellable *cancellable)
{
    AbrtP2Entry *entry = source_object;
    AbrtP2EntryReadElementsData *data = task_data;

    GError *error = NULL;
    GVariant *response = abrt_p2_entry_read_elements(entry,
                                                     data->flags,
                                                     data->elements,
                                                     data->fd_list,
                                                     data->caller_uid,
                                                     data->max_size,
                                                     data->max_unix_fds,
                                                     &error);

    if (error == NULL)
        g_task_return_pointer(task, response, (GDestroyNotify)g_variant_unref);
    else
        g_task_return_error(task, error);
}

void abrt_p2_entry_read_elements_async(AbrtP2Entry *entry,
            gint32 flags,
            GVariant *elements,
            GUnixFDList *fd_list,
            uid_t caller_uid,
            long max_size,
            long max_unix_fds,
            GCancellable *cancellable,
            GAsyncReadyCallback callback,
            gpointer user_data)
{
    AbrtP2EntryReadElementsData *data = abrt_p2_entry_read_elements_data_new();
    data->flags = flags;
    data->elements = elements;
    data->fd_list = fd_list;
    data->caller_uid = caller_uid;
    data->max_size = max_size;
    data->max_unix_fds = max_unix_fds;

    GTask *task = g_task_new(entry, cancellable, callback, user_data);
    g_task_set_task_data(task, data, (GDestroyNotify)abrt_p2_entry_read_elements_data_free);
    g_task_run_in_thread(task, abrt_p2_entry_read_elements_async_task);
    g_object_unref(task);
}

GVariant *abrt_p2_entry_read_elements_finish(AbrtP2Entry *entry,
           GAsyncResult *result,
           GError **error)
{
    g_return_val_if_fail(g_task_is_valid(result, entry), NULL);

    return g_task_propagate_pointer(G_TASK(result), error);
}

/**
 * Save elements
 */
GVariant *abrt_p2_entry_save_elements(AbrtP2Entry *entry,
            gint32 flags,
            GVariant *elements,
            GUnixFDList *fd_list,
            uid_t caller_uid,
            AbrtP2EntrySaveElementsLimits *limits,
            GError **error)
{
    struct dump_dir *dd = abrt_p2_entry_open_dump_dir(entry,
                                                      caller_uid,
                                                      DD_DONT_WAIT_FOR_LOCK,
                                                      error);
    if (dd == NULL)
        return NULL;

    abrt_p2_entry_save_elements_in_dump_dir(dd,
                                            flags,
                                            elements,
                                            fd_list,
                                            caller_uid,
                                            limits,
                                            error);

    dd_close(dd);
    return NULL;
}

/**
 * Save elements in a dump directory
 */
int abrt_p2_entry_save_elements_in_dump_dir(struct dump_dir *dd,
            gint32 flags,
            GVariant *elements,
            GUnixFDList *fd_list,
            uid_t caller_uid,
            AbrtP2EntrySaveElementsLimits *limits,
            GError **error)
{
    int retval = 0;

    gchar *name = NULL;
    GVariant *value = NULL;
    GVariantIter iter;
    g_variant_iter_init(&iter, elements);

    off_t dd_size = dd_compute_size(dd, /*no flags*/0);
    if (dd_size < 0)
    {
        error_msg("Failed to get file system size of dump dir : %s",
                  strerror(-(int)dd_size));

        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_IO_ERROR,
                    "Dump directory file system size");

        return dd_size;
    }

    int dd_items = dd_get_items_count(dd);
    if (dd_items < 0)
    {
        error_msg("Failed to get count of dump dir elements: %s",
                  strerror(-dd_items));

        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_IO_ERROR,
                    "Dump directory elements count");

        return dd_items;
    }

    /* No need to free 'name' and 'container' unless breaking out of the loop */
    while (g_variant_iter_loop(&iter, "{sv}", &name, &value))
    {
        log_debug("Saving element: %s", name);

        struct stat item_stat;
        memset(&item_stat, 0, sizeof(item_stat));

        const int r = dd_item_stat(dd, name, &item_stat);
        if (r == -EINVAL)
        {
            error_msg("Attempt to save prohibited data: '%s'", name);

            g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED,
                        "Not allowed problem element name");

            retval = -EACCES;
            goto exit_loop_on_error;
        }
        else if (r == -ENOENT)
        {
            if (limits->elements_count != 0 && dd_items >= limits->elements_count)
            {
                error_msg("Cannot create new element '%s': reached the limit for elements %u",
                          name,
                          limits->elements_count);

                if (flags & ABRT_P2_ENTRY_ELEMENTS_COUNT_LIMIT_FATAL)
                    goto exit_loop_on_too_many_elements;

                continue;
            }

            ++dd_items;
        }
        else if (r < 0)
        {
            error_msg("Failed to get size of element '%s'", name);

            if (flags & ABRT_P2_ENTRY_IO_ERROR_FATAL)
            {
                g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_IO_ERROR,
                            "Failed to get size of underlying data");

                retval = r;
                goto exit_loop_on_error;
            }

            continue;
        }

        const off_t base_size = dd_size - item_stat.st_size;

        if (   g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)
            || g_variant_is_of_type(value, G_VARIANT_TYPE_BYTESTRING))
        {
            off_t data_size = 0;
            const char *data = NULL;
            if (g_variant_is_of_type(value, G_VARIANT_TYPE_BYTESTRING))
            {
                log_debug("Saving binary element");

                /* Using G_VARIANT_TYPE_BYTESTRING only to check the type. */
                gsize n_elements = 0;
                const gsize element_size = sizeof(guchar);
                data = g_variant_get_fixed_array(value,
                                                 &n_elements,
                                                 element_size);

                data_size = n_elements * element_size;
            }
            else
            {
                log_debug("Saving text element");

                gsize size = 0;
                data = g_variant_get_string(value, &size);
                if (size >= (1ULL << (8 * sizeof(off_t) - 1)))
                {
                    g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_IO_ERROR,
                                "Cannot read huge text data");

                    retval = -EINVAL;
                    goto exit_loop_on_error;
                }

                data_size = (off_t)size;
            }

            if (allowed_new_user_problem_entry(caller_uid, name, data) == false)
            {
                error_msg("Not allowed for user %lu: %s = %s",
                          (long unsigned)caller_uid,
                          name,
                          data);

                g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
                            "You are not allowed to create element '%s' containing '%s'",
                            name, data);

                retval = -EPERM;
                goto exit_loop_on_error;
            }

            /* Do not allow dump dir growing in case it already consumes
             * more than the limit */
            if (   limits->data_size != 0
                && data_size > item_stat.st_size
                && base_size + data_size > limits->data_size)
            {
                error_msg("Cannot save text element: "
                          "problem data size limit %lld, "
                          "data size %lld, "
                          "item size %lld, "
                          "base size %lld",
                          (long long int)limits->data_size,
                          (long long int)data_size,
                          (long long int)item_stat.st_size,
                          (long long int)base_size);

                if (flags & ABRT_P2_ENTRY_DATA_SIZE_LIMIT_FATAL)
                    goto exit_loop_on_too_big_data;

                continue;
            }

            dd_save_binary(dd, name, data, data_size);
            dd_size = base_size + data_size;
        }
        else if (g_variant_is_of_type(value, G_VARIANT_TYPE_HANDLE))
        {
            if (fd_list == NULL)
            {
                error_msg("No UnixFDList to get handle of element '%s'", name);

                if (flags & ABRT_P2_ENTRY_IO_ERROR_FATAL)
                {
                    g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_IO_ERROR,
                                "No UnixFDList to get handle of element '%s'",
                                name);

                    retval = -EIO;
                    goto exit_loop_on_error;
                }

                continue;
            }

            log_debug("Saving data from file descriptor");

            if (problem_entry_is_post_create_condition(name))
            {
                error_msg("post-create element as file descriptor: %s", name);

                g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
                            "Element '%s' must be of '%s' D-Bus type",
                            name,
                            g_variant_type_peek_string(G_VARIANT_TYPE_STRING));

                retval = -EINVAL;
                goto exit_loop_on_error;
            }

            gint32 handle = g_variant_get_handle(value);

            int fd = g_unix_fd_list_get(fd_list, handle, error);
            if (*error != NULL)
            {
                error_msg("Failed to get file descriptor of %s: %s",
                          name,
                          (*error)->message);

                if (flags & ABRT_P2_ENTRY_IO_ERROR_FATAL)
                {
                    g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_IO_ERROR,
                                "Failed to get passed file descriptor");

                    retval = -EIO;
                    goto exit_loop_on_error;
                }

                continue;
            }

            /* Do not allow dump dir growing */
            const off_t max_size = base_size > limits->data_size
                                    ? item_stat.st_size
                                    : limits->data_size - base_size;

            /* Make the file descriptor non-blocking. We will not wait for
             * data. An attacker could use it to stop the service from
             * function. */
            if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
            {
                perror_msg("Failed to set file descriptor of the '%s' item non-blocking:",
                           name);

                close(fd);
                if (flags & ABRT_P2_ENTRY_IO_ERROR_FATAL)
                {
                    g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_IO_ERROR,
                                "Failed to set file file descriptor of the '%s' item non-blocking",
                                name);

                    retval = -EIO;
                    goto exit_loop_on_error;
                }

                continue;
            }

            const off_t r = dd_copy_fd(dd, name, fd, /*copy_flags*/0, max_size);
            close(fd);

            if (r < 0)
            {
                error_msg("Failed to save file descriptor");

                if (flags & ABRT_P2_ENTRY_IO_ERROR_FATAL)
                {
                    g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_IO_ERROR,
                                "Failed to save data of passed file descriptor");

                    retval = r;
                    goto exit_loop_on_error;
                }

                continue;
            }

            if (r >= max_size)
            {
                error_msg("File descriptor was truncated due to size limit");

                if (flags & ABRT_P2_ENTRY_DATA_SIZE_LIMIT_FATAL)
                    goto exit_loop_on_too_big_data;

                /* the file has been created and its size is 'max_size' */
                dd_size = base_size + max_size;
            }
            else
                dd_size = base_size + r ;
        }
        else
        {
            error_msg("Unsupported type: %s", g_variant_get_type_string(value));

            if (flags & ABRT_P2_ENTRY_IO_ERROR_FATAL)
            {
                g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED,
                            "Not supported D-Bus type");

                retval = -ENOTSUP;
                goto exit_loop_on_error;
            }
        }
    }

    return 0;

exit_loop_on_too_big_data:
    g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_LIMITS_EXCEEDED,
                "Problem data is too big");
    retval = -EFBIG;
    goto exit_loop_on_error;

exit_loop_on_too_many_elements:
    g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_LIMITS_EXCEEDED,
                "Too many elements");
    retval = -E2BIG;

exit_loop_on_error:
    g_free(name);
    g_variant_unref(value);
    return retval;
}

/**
 * Asynchronous version of Save elements
 */
typedef struct {
    gint32 flags;
    GVariant *elements;
    GUnixFDList *fd_list;
    uid_t caller_uid;
    AbrtP2EntrySaveElementsLimits limits;
} AbrtP2EntrySaveElementsData;

#define abrt_p2_entry_save_elements_data_new() \
    xmalloc(sizeof(AbrtP2EntrySaveElementsData))

static inline void abrt_p2_entry_save_elements_data_free(AbrtP2EntrySaveElementsData *data)
{
    if (data->fd_list)
        g_object_unref(data->fd_list);

    free(data);
}

static void abrt_p2_entry_save_elements_async_task(GTask *task,
            gpointer source_object, gpointer task_data,
            GCancellable *cancellable)
{
    AbrtP2Entry *entry = source_object;
    AbrtP2EntrySaveElementsData *data = task_data;

    GError *error = NULL;
    GVariant *response = abrt_p2_entry_save_elements(entry,
                                                     data->flags,
                                                     data->elements,
                                                     data->fd_list,
                                                     data->caller_uid,
                                                     &(data->limits),
                                                     &error);

    if (error == NULL)
        g_task_return_pointer(task, response, (GDestroyNotify)g_variant_unref);
    else
        g_task_return_error(task, error);
}

void abrt_p2_entry_save_elements_async(AbrtP2Entry *entry,
            gint32 flags,
            GVariant *elements,
            GUnixFDList *fd_list,
            uid_t caller_uid,
            AbrtP2EntrySaveElementsLimits *limits,
            GCancellable *cancellable,
            GAsyncReadyCallback callback,
            gpointer user_data)
{
    AbrtP2EntrySaveElementsData *data = abrt_p2_entry_save_elements_data_new();
    data->flags = flags;
    data->elements = elements;
    data->fd_list = fd_list != NULL ? g_object_ref(fd_list) : NULL;
    data->caller_uid = caller_uid;
    data->limits = *limits;

    GTask *task = g_task_new(entry, cancellable, callback, user_data);
    g_task_set_task_data(task, data, (GDestroyNotify)abrt_p2_entry_save_elements_data_free);
    g_task_run_in_thread(task,  abrt_p2_entry_save_elements_async_task);
    g_object_unref(task);
    return;
}

GVariant *abrt_p2_entry_save_elements_finish(AbrtP2Entry *entry,
            GAsyncResult *result,
            GError **error)
{
    g_return_val_if_fail(g_task_is_valid(result, entry), NULL);

    return g_task_propagate_pointer(G_TASK(result), error);
}


/**
 * Delete elements
 */
GVariant *abrt_p2_entry_delete_elements(AbrtP2Entry *entry,
            uid_t caller_uid,
            GVariant *elements,
            GError **error)
{
    struct dump_dir *dd = abrt_p2_entry_open_dump_dir(entry,
                                                      caller_uid,
                                                      DD_DONT_WAIT_FOR_LOCK,
                                                      error);
    if (dd == NULL)
        return NULL;

    gchar *name = NULL;
    GVariantIter iter;
    g_variant_iter_init(&iter, elements);

    /* No need to free 'name' unless breaking out of the loop */
    while (g_variant_iter_loop(&iter, "s", &name))
    {
        log_debug("Deleting element: %s", name);
        const int r = dd_delete_item(dd, name);

        if (r == -EINVAL)
            error_msg("Attempt to remove prohibited data: '%s'", name);
    }

    dd_close(dd);

    return NULL;
}

/*
 * Properties
 */
uid_t abrt_p2_entry_get_owner(AbrtP2Entry *entry,
            GError **error)
{
    struct dump_dir *dd = dd_opendir(entry->pv->p2e_dirname, DD_OPEN_FD_ONLY);
    if (dd == NULL)
    {
        g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_IO_ERROR,
                    "Failed open dump directory");
        return -1;
    }

    const uid_t uid = dd_get_owner(dd);
    dd_close(dd);

    return uid;
}
