<commit_before>/*
 * Copyright (C) 2010-2014 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * POSIX DAC security driver
 */

#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#ifdef  __FreeBSD__
# include <sys/sysctl.h>
# include <sys/user.h>
#endif

#include "security_dac.h"
#include "security_util.h"
#include "virerror.h"
#include "virfile.h"
#include "viralloc.h"
#include "virlog.h"
#include "virmdev.h"
#include "virpci.h"
#include "virusb.h"
#include "virscsi.h"
#include "virscsivhost.h"
#include "virstoragefile.h"
#include "virstring.h"
#include "virutil.h"

#define VIR_FROM_THIS VIR_FROM_SECURITY

VIR_LOG_INIT("security.security_dac");

#define SECURITY_DAC_NAME "dac"
#define DEV_SEV "/dev/sev"

typedef struct _virSecurityDACData virSecurityDACData;
typedef virSecurityDACData *virSecurityDACDataPtr;

struct _virSecurityDACData {
    uid_t user;
    gid_t group;
    gid_t *groups;
    int ngroups;
    bool dynamicOwnership;
    bool mountNamespace;
    char *baselabel;
    virSecurityManagerDACChownCallback chownCallback;
};

typedef struct _virSecurityDACCallbackData virSecurityDACCallbackData;
typedef virSecurityDACCallbackData *virSecurityDACCallbackDataPtr;

struct _virSecurityDACCallbackData {
    virSecurityManagerPtr manager;
    virSecurityLabelDefPtr secdef;
};

typedef struct _virSecurityDACChownItem virSecurityDACChownItem;
typedef virSecurityDACChownItem *virSecurityDACChownItemPtr;
struct _virSecurityDACChownItem {
    char *path;
    const virStorageSource *src;
    uid_t uid;
    gid_t gid;
    bool remember; /* Whether owner remembering should be done for @path/@src */
    bool restore; /* Whether current operation is 'set' or 'restore' */
};

typedef struct _virSecurityDACChownList virSecurityDACChownList;
typedef virSecurityDACChownList *virSecurityDACChownListPtr;
struct _virSecurityDACChownList {
    virSecurityManagerPtr manager;
    virSecurityDACChownItemPtr *items;
    size_t nItems;
    bool lock;
};


virThreadLocal chownList;

static int
virSecurityDACChownListAppend(virSecurityDACChownListPtr list,
                              const char *path,
                              const virStorageSource *src,
                              uid_t uid,
                              gid_t gid,
                              bool remember,
                              bool restore)
{
    int ret = -1;
    char *tmp = NULL;
    virSecurityDACChownItemPtr item = NULL;

    if (VIR_ALLOC(item) < 0)
        return -1;

    if (VIR_STRDUP(tmp, path) < 0)
        goto cleanup;

    item->path = tmp;
    item->src = src;
    item->uid = uid;
    item->gid = gid;
    item->remember = remember;
    item->restore = restore;

    if (VIR_APPEND_ELEMENT(list->items, list->nItems, item) < 0)
        goto cleanup;

    tmp = NULL;

    ret = 0;
 cleanup:
    VIR_FREE(tmp);
    VIR_FREE(item);
    return ret;
}

static void
virSecurityDACChownListFree(void *opaque)
{
    virSecurityDACChownListPtr list = opaque;
    size_t i;

    if (!list)
        return;

    for (i = 0; i < list->nItems; i++) {
        VIR_FREE(list->items[i]->path);
        VIR_FREE(list->items[i]);
    }
    VIR_FREE(list->items);
    virObjectUnref(list->manager);
    VIR_FREE(list);
}


/**
 * virSecurityDACTransactionAppend:
 * @path: Path to chown
 * @src: disk source to chown
 * @uid: user ID
 * @gid: group ID
 * @remember: if the original owner should be recorded/recalled
 * @restore: if current operation is set or restore
 *
 * Appends an entry onto transaction list.
 * The @remember should be true if caller wishes to record/recall
 * the original owner of @path/@src.
 * The @restore should be true if the operation is restoring
 * seclabel and false otherwise.
 *
 * Returns: 1 in case of successful append
 *          0 if there is no transaction enabled
 *         -1 otherwise.
 */
static int
virSecurityDACTransactionAppend(const char *path,
                                const virStorageSource *src,
                                uid_t uid,
                                gid_t gid,
                                bool remember,
                                bool restore)
{
    virSecurityDACChownListPtr list = virThreadLocalGet(&chownList);
    if (!list)
        return 0;

    if (virSecurityDACChownListAppend(list, path, src,
                                      uid, gid, remember, restore) < 0)
        return -1;

    return 1;
}


static int virSecurityDACSetOwnership(virSecurityManagerPtr mgr,
                                      const virStorageSource *src,
                                      const char *path,
                                      uid_t uid,
                                      gid_t gid,
                                      bool remember);

static int virSecurityDACRestoreFileLabelInternal(virSecurityManagerPtr mgr,
                                                  const virStorageSource *src,
                                                  const char *path,
                                                  bool recall);
/**
 * virSecurityDACTransactionRun:
 * @pid: process pid
 * @opaque: opaque data
 *
 * This is the callback that runs in the same namespace as the domain we are
 * relabelling. For given transaction (@opaque) it relabels all the paths on
 * the list. Depending on security manager configuration it might lock paths
 * we will relabel.
 *
 * Returns: 0 on success
 *         -1 otherwise.
 */
static int
virSecurityDACTransactionRun(pid_t pid ATTRIBUTE_UNUSED,
                             void *opaque)
{
    virSecurityDACChownListPtr list = opaque;
    virSecurityManagerMetadataLockStatePtr state;
    const char **paths = NULL;
    size_t npaths = 0;
    size_t i;
    int rv = 0;
    int ret = -1;

    if (list->lock) {
        if (VIR_ALLOC_N(paths, list->nItems) < 0)
            return -1;

        for (i = 0; i < list->nItems; i++) {
            virSecurityDACChownItemPtr item = list->items[i];
            const char *p = item->path;

            if (item->remember)
                VIR_APPEND_ELEMENT_COPY_INPLACE(paths, npaths, p);
        }

        if (!(state = virSecurityManagerMetadataLock(list->manager, paths, npaths)))
            goto cleanup;
    }

    for (i = 0; i < list->nItems; i++) {
        virSecurityDACChownItemPtr item = list->items[i];
        const bool remember = item->remember && list->lock;

        if (!item->restore) {
            rv = virSecurityDACSetOwnership(list->manager,
                                            item->src,
                                            item->path,
                                            item->uid,
                                            item->gid,
                                            remember);
        } else {
            rv = virSecurityDACRestoreFileLabelInternal(list->manager,
                                                        item->src,
                                                        item->path,
                                                        remember);
        }

        if (rv < 0)
            break;
    }

    for (; rv < 0 && i > 0; i--) {
        virSecurityDACChownItemPtr item = list->items[i - 1];
        const bool remember = item->remember && list->lock;

        if (!item->restore) {
            virSecurityDACRestoreFileLabelInternal(list->manager,
                                                   item->src,
                                                   item->path,
                                                   remember);
        } else {
            VIR_WARN("Ignoring failed restore attempt on %s",
                     NULLSTR(item->src ? item->src->path : item->path));
        }
    }

    if (list->lock)
        virSecurityManagerMetadataUnlock(list->manager, &state);

    if (rv < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    VIR_FREE(paths);
    return ret;
}


/* returns -1 on error, 0 on success */
int
virSecurityDACSetUserAndGroup(virSecurityManagerPtr mgr,
                              uid_t user,
                              gid_t group)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    priv->user = user;
    priv->group = group;

    if (virAsprintf(&priv->baselabel, "+%u:+%u",
                    (unsigned int)user,
                    (unsigned int)group) < 0)
        return -1;

    return 0;
}

void
virSecurityDACSetDynamicOwnership(virSecurityManagerPtr mgr,
                                  bool dynamicOwnership)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    priv->dynamicOwnership = dynamicOwnership;
}

void
virSecurityDACSetMountNamespace(virSecurityManagerPtr mgr,
                                bool mountNamespace)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    priv->mountNamespace = mountNamespace;
}


void
virSecurityDACSetChownCallback(virSecurityManagerPtr mgr,
                               virSecurityManagerDACChownCallback chownCallback)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    priv->chownCallback = chownCallback;
}

/* returns 1 if label isn't found, 0 on success, -1 on error */
static int
ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
virSecurityDACParseIds(virSecurityLabelDefPtr seclabel,
                       uid_t *uidPtr, gid_t *gidPtr)
{
    if (!seclabel || !seclabel->label)
        return 1;

    if (virParseOwnershipIds(seclabel->label, uidPtr, gidPtr) < 0)
        return -1;

    return 0;
}

static int
ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4)
virSecurityDACGetIds(virSecurityLabelDefPtr seclabel,
                     virSecurityDACDataPtr priv,
                     uid_t *uidPtr, gid_t *gidPtr,
                     gid_t **groups, int *ngroups)
{
    int ret;

    if (groups)
        *groups = priv ? priv->groups : NULL;
    if (ngroups)
        *ngroups = priv ? priv->ngroups : 0;

    if ((ret = virSecurityDACParseIds(seclabel, uidPtr, gidPtr)) <= 0)
        return ret;

    if (!priv) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("DAC seclabel couldn't be determined"));
        return -1;
    }

    *uidPtr = priv->user;
    *gidPtr = priv->group;

    return 0;
}


/* returns 1 if label isn't found, 0 on success, -1 on error */
static int
ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
virSecurityDACParseImageIds(virSecurityLabelDefPtr seclabel,
                            uid_t *uidPtr, gid_t *gidPtr)
{
    if (!seclabel || !seclabel->imagelabel)
        return 1;

    if (virParseOwnershipIds(seclabel->imagelabel, uidPtr, gidPtr) < 0)
        return -1;

    return 0;
}

static int
ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4)
virSecurityDACGetImageIds(virSecurityLabelDefPtr seclabel,
                          virSecurityDACDataPtr priv,
                          uid_t *uidPtr, gid_t *gidPtr)
{
    int ret;

    if ((ret = virSecurityDACParseImageIds(seclabel, uidPtr, gidPtr)) <= 0)
        return ret;

    if (!priv) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("DAC imagelabel couldn't be determined"));
        return -1;
    }

    *uidPtr = priv->user;
    *gidPtr = priv->group;

    return 0;
}

/**
 * virSecurityDACRememberLabel:
 * @priv: driver's private data
 * @path: path to the file
 * @uid: user owning the @path
 * @gid: group owning the @path
 *
 * Remember the owner of @path (represented by @uid:@gid).
 *
 * Returns: the @path refcount, or
 *          -1 on failure
 */
static int
virSecurityDACRememberLabel(virSecurityDACDataPtr priv ATTRIBUTE_UNUSED,
                            const char *path,
                            uid_t uid,
                            gid_t gid)
{
    char *label = NULL;
    int ret = -1;

    if (virAsprintf(&label, "+%u:+%u",
                    (unsigned int)uid,
                    (unsigned int)gid) < 0)
        return -1;

    ret = virSecuritySetRememberedLabel(SECURITY_DAC_NAME, path, label);
    VIR_FREE(label);
    return ret;
}

/**
 * virSecurityDACRecallLabel:
 * @priv: driver's private data
 * @path: path to the file
 * @uid: user owning the @path
 * @gid: group owning the @path
 *
 * Recall the previously recorded owner for the @path. However, it may happen
 * that @path is still in use (e.g. by another domain). In that case, 1 is
 * returned and caller should not relabel the @path.
 *
 * Returns: 1 if @path is still in use (@uid and @gid not touched)
 *          0 if @path should be restored (@uid and @gid set)
 *         -1 on failure (@uid and @gid not touched)
 */
static int
virSecurityDACRecallLabel(virSecurityDACDataPtr priv ATTRIBUTE_UNUSED,
                          const char *path,
                          uid_t *uid,
                          gid_t *gid)
{
    char *label;
    int ret = -1;
    int rv;

    rv = virSecurityGetRememberedLabel(SECURITY_DAC_NAME, path, &label);
    if (rv < 0)
        return rv;

    if (!label)
        return 1;

    if (virParseOwnershipIds(label, uid, gid) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    VIR_FREE(label);
    return ret;
}

static virSecurityDriverStatus
virSecurityDACProbe(const char *virtDriver ATTRIBUTE_UNUSED)
{
    return SECURITY_DRIVER_ENABLE;
}

static int
virSecurityDACOpen(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
{
    if (virThreadLocalInit(&chownList,
                           virSecurityDACChownListFree) < 0) {
        virReportSystemError(errno, "%s",
                             _("Unable to initialize thread local variable"));
        return -1;
    }

    return 0;
}

static int
virSecurityDACClose(virSecurityManagerPtr mgr)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    VIR_FREE(priv->groups);
    VIR_FREE(priv->baselabel);
    return 0;
}


static const char *
virSecurityDACGetModel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
{
    return SECURITY_DAC_NAME;
}

static const char *
virSecurityDACGetDOI(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
{
    return "0";
}

static int
virSecurityDACPreFork(virSecurityManagerPtr mgr)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    int ngroups;

    VIR_FREE(priv->groups);
    priv->ngroups = 0;
    if ((ngroups = virGetGroupList(priv->user, priv->group,
                                   &priv->groups)) < 0)
        return -1;
    priv->ngroups = ngroups;
    return 0;
}

/**
 * virSecurityDACTransactionStart:
 * @mgr: security manager
 *
 * Starts a new transaction. In transaction nothing is chown()-ed until
 * TransactionCommit() is called. This is implemented as a list that is
 * appended to whenever chown() would be called. Since secdriver APIs
 * can be called from multiple threads (to work over different domains)
 * the pointer to the list is stored in thread local variable.
 *
 * Returns 0 on success,
 *        -1 otherwise.
 */
static int
virSecurityDACTransactionStart(virSecurityManagerPtr mgr)
{
    virSecurityDACChownListPtr list;

    list = virThreadLocalGet(&chownList);
    if (list) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Another relabel transaction is already started"));
        return -1;
    }

    if (VIR_ALLOC(list) < 0)
        return -1;

    list->manager = virObjectRef(mgr);

    if (virThreadLocalSet(&chownList, list) < 0) {
        virReportSystemError(errno, "%s",
                             _("Unable to set thread local variable"));
        virSecurityDACChownListFree(list);
        return -1;
    }

    return 0;
}

/**
 * virSecurityDACTransactionCommit:
 * @mgr: security manager
 * @pid: domain's PID
 * @lock: lock and unlock paths that are relabeled
 *
 * If @pid is not -1 then enter the @pid namespace (usually @pid refers
 * to a domain) and perform all the chown()-s on the list. If @pid is -1
 * then the transaction is performed in the namespace of the caller.
 *
 * If @lock is true then all the paths that transaction would
 * touch are locked before and unlocked after it is done so.
 *
 * Note that the transaction is also freed, therefore new one has to be
 * started after successful return from this function. Also it is
 * considered as error if there's no transaction set and this function
 * is called.
 *
 * Returns: 0 on success,
 *         -1 otherwise.
 */
static int
virSecurityDACTransactionCommit(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
                                pid_t pid,
                                bool lock)
{
    virSecurityDACChownListPtr list;
    int rc;
    int ret = -1;

    list = virThreadLocalGet(&chownList);
    if (!list) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("No transaction is set"));
        goto cleanup;
    }

    if (virThreadLocalSet(&chownList, NULL) < 0) {
        virReportSystemError(errno, "%s",
                             _("Unable to clear thread local variable"));
        goto cleanup;
    }

    list->lock = lock;

    if (pid == -1) {
        if (lock)
            rc = virProcessRunInFork(virSecurityDACTransactionRun, list);
        else
            rc = virSecurityDACTransactionRun(pid, list);
    } else {
        rc = virProcessRunInMountNamespace(pid,
                                           virSecurityDACTransactionRun,
                                           list);
    }

    if (rc < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virSecurityDACChownListFree(list);
    return ret;
}

/**
 * virSecurityDACTransactionAbort:
 * @mgr: security manager
 *
 * Cancels and frees any out standing transaction.
 */
static void
virSecurityDACTransactionAbort(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
{
    virSecurityDACChownListPtr list;

    list = virThreadLocalGet(&chownList);
    if (!list)
        return;

    if (virThreadLocalSet(&chownList, NULL) < 0)
        VIR_DEBUG("Unable to clear thread local variable");
    virSecurityDACChownListFree(list);
}


static int
virSecurityDACSetOwnershipInternal(const virSecurityDACData *priv,
                                   const virStorageSource *src,
                                   const char *path,
                                   uid_t uid,
                                   gid_t gid)
{
    int rc;

    /* Be aware that this function might run in a separate process.
     * Therefore, any driver state changes would be thrown away. */

    if (priv && src && priv->chownCallback) {
        rc = priv->chownCallback(src, uid, gid);
        /* here path is used only for error messages */
        path = NULLSTR(src->path);

        /* on -2 returned an error was already reported */
        if (rc == -2)
            return -1;
    } else {
        struct stat sb;

        if (!path) {
            if (!src || !src->path)
                return 0;

            if (!virStorageSourceIsLocalStorage(src))
                return 0;

            path = src->path;
        }

        if (stat(path, &sb) < 0) {
            virReportSystemError(errno, _("unable to stat: %s"), path);
            return -1;
        }

        if (sb.st_uid == uid && sb.st_gid == gid) {
            /* nothing to chown */
            return 0;
        }

        rc = chown(path, uid, gid);
    }

    if (rc < 0) {
        if (errno == EOPNOTSUPP || errno == EINVAL) {
            VIR_INFO("Setting user and group to '%ld:%ld' on '%s' not "
                     "supported by filesystem",
                     (long)uid, (long)gid, path);
        } else if (errno == EPERM) {
            VIR_INFO("Setting user and group to '%ld:%ld' on '%s' not "
                     "permitted",
                     (long)uid, (long)gid, path);
        } else if (errno == EROFS) {
            VIR_INFO("Setting user and group to '%ld:%ld' on '%s' not "
                     "possible on readonly filesystem",
                     (long)uid, (long)gid, path);
        } else {
            virReportSystemError(errno,
                                 _("unable to set user and group to '%ld:%ld' "
                                   "on '%s'"),
                                 (long)uid, (long)gid, path);
            return -1;
        }
    }
    return 0;
}


static int
virSecurityDACSetOwnership(virSecurityManagerPtr mgr,
                           const virStorageSource *src,
                           const char *path,
                           uid_t uid,
                           gid_t gid,
                           bool remember)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virErrorPtr origerr;
    struct stat sb;
    int refcount;
    int rc;

    if (!path && src && src->path &&
        virStorageSourceIsLocalStorage(src))
        path = src->path;

    /* Be aware that this function might run in a separate process.
     * Therefore, any driver state changes would be thrown away. */

    if ((rc = virSecurityDACTransactionAppend(path, src,
                                              uid, gid, remember, false)) < 0)
        return -1;
    else if (rc > 0)
        return 0;

    if (remember && path) {
        if (stat(path, &sb) < 0) {
            virReportSystemError(errno, _("unable to stat: %s"), path);
            return -1;
        }

        refcount = virSecurityDACRememberLabel(priv, path, sb.st_uid, sb.st_gid);
        if (refcount == -2) {
            /* Not supported. Don't error though. */
        } else if (refcount < 0) {
            return -1;
        } else if (refcount > 1) {
            /* Refcount is greater than 1 which means that there
             * is @refcount domains using the @path. Do not
             * change the label (as it would almost certainly
             * cause the other domains to lose access to the
             * @path). However, the refcounter was incremented in
             * XATTRs so decrease it. */
            if (sb.st_uid != uid || sb.st_gid != gid) {
                virReportError(VIR_ERR_OPERATION_INVALID,
                               _("Setting different DAC user or group on %s "
                                 "which is already in use"), path);
                goto error;
            }
        }
    }

    VIR_INFO("Setting DAC user and group on '%s' to '%ld:%ld'",
             NULLSTR(src ? src->path : path), (long)uid, (long)gid);

    if (virSecurityDACSetOwnershipInternal(priv, src, path, uid, gid) < 0)
        goto error;

    return 0;

 error:
    virErrorPreserveLast(&origerr);
    /* Try to restore the label. This is done so that XATTRs
     * are left in the same state as when the control entered
     * this function. However, if our attempt fails, there's
     * not much we can do. XATTRs refcounting is fubar'ed and
     * the only option we have is warn users. */
    if (virSecurityDACRestoreFileLabelInternal(mgr, src, path, remember) < 0)
        VIR_WARN("Unable to restore label on '%s'. "
                 "XATTRs might have been left in inconsistent state.",
                 NULLSTR(src ? src->path : path));

    virErrorRestore(&origerr);

    return -1;
}


static int
virSecurityDACRestoreFileLabelInternal(virSecurityManagerPtr mgr,
                                       const virStorageSource *src,
                                       const char *path,
                                       bool recall)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    int rv;
    uid_t uid = 0;  /* By default return to root:root */
    gid_t gid = 0;

    if (!path && src && src->path &&
        virStorageSourceIsLocalStorage(src))
        path = src->path;

    /* Be aware that this function might run in a separate process.
     * Therefore, any driver state changes would be thrown away. */

    if ((rv = virSecurityDACTransactionAppend(path, src, uid, gid, recall, true)) < 0)
        return -1;
    else if (rv > 0)
        return 0;

    if (recall && path) {
        rv = virSecurityDACRecallLabel(priv, path, &uid, &gid);
        if (rv == -2) {
            /* Not supported. Don't error though. */
        } else if (rv < 0) {
            return -1;
        } else if (rv > 0) {
            return 0;
        }
    }

    VIR_INFO("Restoring DAC user and group on '%s' to %ld:%ld",
             NULLSTR(src ? src->path : path), (long)uid, (long)gid);

    return virSecurityDACSetOwnershipInternal(priv, src, path, uid, gid);
}


static int
virSecurityDACRestoreFileLabel(virSecurityManagerPtr mgr,
                               const char *path)
{
    return virSecurityDACRestoreFileLabelInternal(mgr, NULL, path, true);
}


static int
virSecurityDACSetImageLabelInternal(virSecurityManagerPtr mgr,
                                    virDomainDefPtr def,
                                    virStorageSourcePtr src,
                                    virStorageSourcePtr parent)
{
    virSecurityLabelDefPtr secdef;
    virSecurityDeviceLabelDefPtr disk_seclabel;
    virSecurityDeviceLabelDefPtr parent_seclabel = NULL;
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    bool remember;
    uid_t user;
    gid_t group;

    if (!priv->dynamicOwnership)
        return 0;

    secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_DAC_NAME);
    if (secdef && !secdef->relabel)
        return 0;

    disk_seclabel = virStorageSourceGetSecurityLabelDef(src, SECURITY_DAC_NAME);
    if (parent)
        parent_seclabel = virStorageSourceGetSecurityLabelDef(parent,
                                                              SECURITY_DAC_NAME);

    if (disk_seclabel && (!disk_seclabel->relabel || disk_seclabel->label)) {
        if (!disk_seclabel->relabel)
            return 0;

        if (virParseOwnershipIds(disk_seclabel->label, &user, &group) < 0)
            return -1;
    } else if (parent_seclabel &&
               (!parent_seclabel->relabel || parent_seclabel->label)) {
        if (!parent_seclabel->relabel)
            return 0;

        if (virParseOwnershipIds(parent_seclabel->label, &user, &group) < 0)
            return -1;
    } else {
        if (virSecurityDACGetImageIds(secdef, priv, &user, &group))
            return -1;
    }

    /* We can't do restore on shared resources safely. Not even
     * with refcounting implemented in XATTRs because if there
     * was a domain running with the feature turned off the
     * refcounter in XATTRs would not reflect the actual number
     * of times the resource is in use and thus the last restore
     * on the resource (which actually restores the original
     * owner) might cut off access to the domain with the feature<commit_msg>security: dac: Drop !parent handling in SetImageLabelInternal<commit_after>/*
 * Copyright (C) 2010-2014 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * POSIX DAC security driver
 */

#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#ifdef  __FreeBSD__
# include <sys/sysctl.h>
# include <sys/user.h>
#endif

#include "security_dac.h"
#include "security_util.h"
#include "virerror.h"
#include "virfile.h"
#include "viralloc.h"
#include "virlog.h"
#include "virmdev.h"
#include "virpci.h"
#include "virusb.h"
#include "virscsi.h"
#include "virscsivhost.h"
#include "virstoragefile.h"
#include "virstring.h"
#include "virutil.h"

#define VIR_FROM_THIS VIR_FROM_SECURITY

VIR_LOG_INIT("security.security_dac");

#define SECURITY_DAC_NAME "dac"
#define DEV_SEV "/dev/sev"

typedef struct _virSecurityDACData virSecurityDACData;
typedef virSecurityDACData *virSecurityDACDataPtr;

struct _virSecurityDACData {
    uid_t user;
    gid_t group;
    gid_t *groups;
    int ngroups;
    bool dynamicOwnership;
    bool mountNamespace;
    char *baselabel;
    virSecurityManagerDACChownCallback chownCallback;
};

typedef struct _virSecurityDACCallbackData virSecurityDACCallbackData;
typedef virSecurityDACCallbackData *virSecurityDACCallbackDataPtr;

struct _virSecurityDACCallbackData {
    virSecurityManagerPtr manager;
    virSecurityLabelDefPtr secdef;
};

typedef struct _virSecurityDACChownItem virSecurityDACChownItem;
typedef virSecurityDACChownItem *virSecurityDACChownItemPtr;
struct _virSecurityDACChownItem {
    char *path;
    const virStorageSource *src;
    uid_t uid;
    gid_t gid;
    bool remember; /* Whether owner remembering should be done for @path/@src */
    bool restore; /* Whether current operation is 'set' or 'restore' */
};

typedef struct _virSecurityDACChownList virSecurityDACChownList;
typedef virSecurityDACChownList *virSecurityDACChownListPtr;
struct _virSecurityDACChownList {
    virSecurityManagerPtr manager;
    virSecurityDACChownItemPtr *items;
    size_t nItems;
    bool lock;
};


virThreadLocal chownList;

static int
virSecurityDACChownListAppend(virSecurityDACChownListPtr list,
                              const char *path,
                              const virStorageSource *src,
                              uid_t uid,
                              gid_t gid,
                              bool remember,
                              bool restore)
{
    int ret = -1;
    char *tmp = NULL;
    virSecurityDACChownItemPtr item = NULL;

    if (VIR_ALLOC(item) < 0)
        return -1;

    if (VIR_STRDUP(tmp, path) < 0)
        goto cleanup;

    item->path = tmp;
    item->src = src;
    item->uid = uid;
    item->gid = gid;
    item->remember = remember;
    item->restore = restore;

    if (VIR_APPEND_ELEMENT(list->items, list->nItems, item) < 0)
        goto cleanup;

    tmp = NULL;

    ret = 0;
 cleanup:
    VIR_FREE(tmp);
    VIR_FREE(item);
    return ret;
}

static void
virSecurityDACChownListFree(void *opaque)
{
    virSecurityDACChownListPtr list = opaque;
    size_t i;

    if (!list)
        return;

    for (i = 0; i < list->nItems; i++) {
        VIR_FREE(list->items[i]->path);
        VIR_FREE(list->items[i]);
    }
    VIR_FREE(list->items);
    virObjectUnref(list->manager);
    VIR_FREE(list);
}


/**
 * virSecurityDACTransactionAppend:
 * @path: Path to chown
 * @src: disk source to chown
 * @uid: user ID
 * @gid: group ID
 * @remember: if the original owner should be recorded/recalled
 * @restore: if current operation is set or restore
 *
 * Appends an entry onto transaction list.
 * The @remember should be true if caller wishes to record/recall
 * the original owner of @path/@src.
 * The @restore should be true if the operation is restoring
 * seclabel and false otherwise.
 *
 * Returns: 1 in case of successful append
 *          0 if there is no transaction enabled
 *         -1 otherwise.
 */
static int
virSecurityDACTransactionAppend(const char *path,
                                const virStorageSource *src,
                                uid_t uid,
                                gid_t gid,
                                bool remember,
                                bool restore)
{
    virSecurityDACChownListPtr list = virThreadLocalGet(&chownList);
    if (!list)
        return 0;

    if (virSecurityDACChownListAppend(list, path, src,
                                      uid, gid, remember, restore) < 0)
        return -1;

    return 1;
}


static int virSecurityDACSetOwnership(virSecurityManagerPtr mgr,
                                      const virStorageSource *src,
                                      const char *path,
                                      uid_t uid,
                                      gid_t gid,
                                      bool remember);

static int virSecurityDACRestoreFileLabelInternal(virSecurityManagerPtr mgr,
                                                  const virStorageSource *src,
                                                  const char *path,
                                                  bool recall);
/**
 * virSecurityDACTransactionRun:
 * @pid: process pid
 * @opaque: opaque data
 *
 * This is the callback that runs in the same namespace as the domain we are
 * relabelling. For given transaction (@opaque) it relabels all the paths on
 * the list. Depending on security manager configuration it might lock paths
 * we will relabel.
 *
 * Returns: 0 on success
 *         -1 otherwise.
 */
static int
virSecurityDACTransactionRun(pid_t pid ATTRIBUTE_UNUSED,
                             void *opaque)
{
    virSecurityDACChownListPtr list = opaque;
    virSecurityManagerMetadataLockStatePtr state;
    const char **paths = NULL;
    size_t npaths = 0;
    size_t i;
    int rv = 0;
    int ret = -1;

    if (list->lock) {
        if (VIR_ALLOC_N(paths, list->nItems) < 0)
            return -1;

        for (i = 0; i < list->nItems; i++) {
            virSecurityDACChownItemPtr item = list->items[i];
            const char *p = item->path;

            if (item->remember)
                VIR_APPEND_ELEMENT_COPY_INPLACE(paths, npaths, p);
        }

        if (!(state = virSecurityManagerMetadataLock(list->manager, paths, npaths)))
            goto cleanup;
    }

    for (i = 0; i < list->nItems; i++) {
        virSecurityDACChownItemPtr item = list->items[i];
        const bool remember = item->remember && list->lock;

        if (!item->restore) {
            rv = virSecurityDACSetOwnership(list->manager,
                                            item->src,
                                            item->path,
                                            item->uid,
                                            item->gid,
                                            remember);
        } else {
            rv = virSecurityDACRestoreFileLabelInternal(list->manager,
                                                        item->src,
                                                        item->path,
                                                        remember);
        }

        if (rv < 0)
            break;
    }

    for (; rv < 0 && i > 0; i--) {
        virSecurityDACChownItemPtr item = list->items[i - 1];
        const bool remember = item->remember && list->lock;

        if (!item->restore) {
            virSecurityDACRestoreFileLabelInternal(list->manager,
                                                   item->src,
                                                   item->path,
                                                   remember);
        } else {
            VIR_WARN("Ignoring failed restore attempt on %s",
                     NULLSTR(item->src ? item->src->path : item->path));
        }
    }

    if (list->lock)
        virSecurityManagerMetadataUnlock(list->manager, &state);

    if (rv < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    VIR_FREE(paths);
    return ret;
}


/* returns -1 on error, 0 on success */
int
virSecurityDACSetUserAndGroup(virSecurityManagerPtr mgr,
                              uid_t user,
                              gid_t group)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    priv->user = user;
    priv->group = group;

    if (virAsprintf(&priv->baselabel, "+%u:+%u",
                    (unsigned int)user,
                    (unsigned int)group) < 0)
        return -1;

    return 0;
}

void
virSecurityDACSetDynamicOwnership(virSecurityManagerPtr mgr,
                                  bool dynamicOwnership)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    priv->dynamicOwnership = dynamicOwnership;
}

void
virSecurityDACSetMountNamespace(virSecurityManagerPtr mgr,
                                bool mountNamespace)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    priv->mountNamespace = mountNamespace;
}


void
virSecurityDACSetChownCallback(virSecurityManagerPtr mgr,
                               virSecurityManagerDACChownCallback chownCallback)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    priv->chownCallback = chownCallback;
}

/* returns 1 if label isn't found, 0 on success, -1 on error */
static int
ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
virSecurityDACParseIds(virSecurityLabelDefPtr seclabel,
                       uid_t *uidPtr, gid_t *gidPtr)
{
    if (!seclabel || !seclabel->label)
        return 1;

    if (virParseOwnershipIds(seclabel->label, uidPtr, gidPtr) < 0)
        return -1;

    return 0;
}

static int
ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4)
virSecurityDACGetIds(virSecurityLabelDefPtr seclabel,
                     virSecurityDACDataPtr priv,
                     uid_t *uidPtr, gid_t *gidPtr,
                     gid_t **groups, int *ngroups)
{
    int ret;

    if (groups)
        *groups = priv ? priv->groups : NULL;
    if (ngroups)
        *ngroups = priv ? priv->ngroups : 0;

    if ((ret = virSecurityDACParseIds(seclabel, uidPtr, gidPtr)) <= 0)
        return ret;

    if (!priv) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("DAC seclabel couldn't be determined"));
        return -1;
    }

    *uidPtr = priv->user;
    *gidPtr = priv->group;

    return 0;
}


/* returns 1 if label isn't found, 0 on success, -1 on error */
static int
ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
virSecurityDACParseImageIds(virSecurityLabelDefPtr seclabel,
                            uid_t *uidPtr, gid_t *gidPtr)
{
    if (!seclabel || !seclabel->imagelabel)
        return 1;

    if (virParseOwnershipIds(seclabel->imagelabel, uidPtr, gidPtr) < 0)
        return -1;

    return 0;
}

static int
ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4)
virSecurityDACGetImageIds(virSecurityLabelDefPtr seclabel,
                          virSecurityDACDataPtr priv,
                          uid_t *uidPtr, gid_t *gidPtr)
{
    int ret;

    if ((ret = virSecurityDACParseImageIds(seclabel, uidPtr, gidPtr)) <= 0)
        return ret;

    if (!priv) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("DAC imagelabel couldn't be determined"));
        return -1;
    }

    *uidPtr = priv->user;
    *gidPtr = priv->group;

    return 0;
}

/**
 * virSecurityDACRememberLabel:
 * @priv: driver's private data
 * @path: path to the file
 * @uid: user owning the @path
 * @gid: group owning the @path
 *
 * Remember the owner of @path (represented by @uid:@gid).
 *
 * Returns: the @path refcount, or
 *          -1 on failure
 */
static int
virSecurityDACRememberLabel(virSecurityDACDataPtr priv ATTRIBUTE_UNUSED,
                            const char *path,
                            uid_t uid,
                            gid_t gid)
{
    char *label = NULL;
    int ret = -1;

    if (virAsprintf(&label, "+%u:+%u",
                    (unsigned int)uid,
                    (unsigned int)gid) < 0)
        return -1;

    ret = virSecuritySetRememberedLabel(SECURITY_DAC_NAME, path, label);
    VIR_FREE(label);
    return ret;
}

/**
 * virSecurityDACRecallLabel:
 * @priv: driver's private data
 * @path: path to the file
 * @uid: user owning the @path
 * @gid: group owning the @path
 *
 * Recall the previously recorded owner for the @path. However, it may happen
 * that @path is still in use (e.g. by another domain). In that case, 1 is
 * returned and caller should not relabel the @path.
 *
 * Returns: 1 if @path is still in use (@uid and @gid not touched)
 *          0 if @path should be restored (@uid and @gid set)
 *         -1 on failure (@uid and @gid not touched)
 */
static int
virSecurityDACRecallLabel(virSecurityDACDataPtr priv ATTRIBUTE_UNUSED,
                          const char *path,
                          uid_t *uid,
                          gid_t *gid)
{
    char *label;
    int ret = -1;
    int rv;

    rv = virSecurityGetRememberedLabel(SECURITY_DAC_NAME, path, &label);
    if (rv < 0)
        return rv;

    if (!label)
        return 1;

    if (virParseOwnershipIds(label, uid, gid) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    VIR_FREE(label);
    return ret;
}

static virSecurityDriverStatus
virSecurityDACProbe(const char *virtDriver ATTRIBUTE_UNUSED)
{
    return SECURITY_DRIVER_ENABLE;
}

static int
virSecurityDACOpen(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
{
    if (virThreadLocalInit(&chownList,
                           virSecurityDACChownListFree) < 0) {
        virReportSystemError(errno, "%s",
                             _("Unable to initialize thread local variable"));
        return -1;
    }

    return 0;
}

static int
virSecurityDACClose(virSecurityManagerPtr mgr)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    VIR_FREE(priv->groups);
    VIR_FREE(priv->baselabel);
    return 0;
}


static const char *
virSecurityDACGetModel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
{
    return SECURITY_DAC_NAME;
}

static const char *
virSecurityDACGetDOI(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
{
    return "0";
}

static int
virSecurityDACPreFork(virSecurityManagerPtr mgr)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    int ngroups;

    VIR_FREE(priv->groups);
    priv->ngroups = 0;
    if ((ngroups = virGetGroupList(priv->user, priv->group,
                                   &priv->groups)) < 0)
        return -1;
    priv->ngroups = ngroups;
    return 0;
}

/**
 * virSecurityDACTransactionStart:
 * @mgr: security manager
 *
 * Starts a new transaction. In transaction nothing is chown()-ed until
 * TransactionCommit() is called. This is implemented as a list that is
 * appended to whenever chown() would be called. Since secdriver APIs
 * can be called from multiple threads (to work over different domains)
 * the pointer to the list is stored in thread local variable.
 *
 * Returns 0 on success,
 *        -1 otherwise.
 */
static int
virSecurityDACTransactionStart(virSecurityManagerPtr mgr)
{
    virSecurityDACChownListPtr list;

    list = virThreadLocalGet(&chownList);
    if (list) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Another relabel transaction is already started"));
        return -1;
    }

    if (VIR_ALLOC(list) < 0)
        return -1;

    list->manager = virObjectRef(mgr);

    if (virThreadLocalSet(&chownList, list) < 0) {
        virReportSystemError(errno, "%s",
                             _("Unable to set thread local variable"));
        virSecurityDACChownListFree(list);
        return -1;
    }

    return 0;
}

/**
 * virSecurityDACTransactionCommit:
 * @mgr: security manager
 * @pid: domain's PID
 * @lock: lock and unlock paths that are relabeled
 *
 * If @pid is not -1 then enter the @pid namespace (usually @pid refers
 * to a domain) and perform all the chown()-s on the list. If @pid is -1
 * then the transaction is performed in the namespace of the caller.
 *
 * If @lock is true then all the paths that transaction would
 * touch are locked before and unlocked after it is done so.
 *
 * Note that the transaction is also freed, therefore new one has to be
 * started after successful return from this function. Also it is
 * considered as error if there's no transaction set and this function
 * is called.
 *
 * Returns: 0 on success,
 *         -1 otherwise.
 */
static int
virSecurityDACTransactionCommit(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
                                pid_t pid,
                                bool lock)
{
    virSecurityDACChownListPtr list;
    int rc;
    int ret = -1;

    list = virThreadLocalGet(&chownList);
    if (!list) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("No transaction is set"));
        goto cleanup;
    }

    if (virThreadLocalSet(&chownList, NULL) < 0) {
        virReportSystemError(errno, "%s",
                             _("Unable to clear thread local variable"));
        goto cleanup;
    }

    list->lock = lock;

    if (pid == -1) {
        if (lock)
            rc = virProcessRunInFork(virSecurityDACTransactionRun, list);
        else
            rc = virSecurityDACTransactionRun(pid, list);
    } else {
        rc = virProcessRunInMountNamespace(pid,
                                           virSecurityDACTransactionRun,
                                           list);
    }

    if (rc < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virSecurityDACChownListFree(list);
    return ret;
}

/**
 * virSecurityDACTransactionAbort:
 * @mgr: security manager
 *
 * Cancels and frees any out standing transaction.
 */
static void
virSecurityDACTransactionAbort(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
{
    virSecurityDACChownListPtr list;

    list = virThreadLocalGet(&chownList);
    if (!list)
        return;

    if (virThreadLocalSet(&chownList, NULL) < 0)
        VIR_DEBUG("Unable to clear thread local variable");
    virSecurityDACChownListFree(list);
}


static int
virSecurityDACSetOwnershipInternal(const virSecurityDACData *priv,
                                   const virStorageSource *src,
                                   const char *path,
                                   uid_t uid,
                                   gid_t gid)
{
    int rc;

    /* Be aware that this function might run in a separate process.
     * Therefore, any driver state changes would be thrown away. */

    if (priv && src && priv->chownCallback) {
        rc = priv->chownCallback(src, uid, gid);
        /* here path is used only for error messages */
        path = NULLSTR(src->path);

        /* on -2 returned an error was already reported */
        if (rc == -2)
            return -1;
    } else {
        struct stat sb;

        if (!path) {
            if (!src || !src->path)
                return 0;

            if (!virStorageSourceIsLocalStorage(src))
                return 0;

            path = src->path;
        }

        if (stat(path, &sb) < 0) {
            virReportSystemError(errno, _("unable to stat: %s"), path);
            return -1;
        }

        if (sb.st_uid == uid && sb.st_gid == gid) {
            /* nothing to chown */
            return 0;
        }

        rc = chown(path, uid, gid);
    }

    if (rc < 0) {
        if (errno == EOPNOTSUPP || errno == EINVAL) {
            VIR_INFO("Setting user and group to '%ld:%ld' on '%s' not "
                     "supported by filesystem",
                     (long)uid, (long)gid, path);
        } else if (errno == EPERM) {
            VIR_INFO("Setting user and group to '%ld:%ld' on '%s' not "
                     "permitted",
                     (long)uid, (long)gid, path);
        } else if (errno == EROFS) {
            VIR_INFO("Setting user and group to '%ld:%ld' on '%s' not "
                     "possible on readonly filesystem",
                     (long)uid, (long)gid, path);
        } else {
            virReportSystemError(errno,
                                 _("unable to set user and group to '%ld:%ld' "
                                   "on '%s'"),
                                 (long)uid, (long)gid, path);
            return -1;
        }
    }
    return 0;
}


static int
virSecurityDACSetOwnership(virSecurityManagerPtr mgr,
                           const virStorageSource *src,
                           const char *path,
                           uid_t uid,
                           gid_t gid,
                           bool remember)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virErrorPtr origerr;
    struct stat sb;
    int refcount;
    int rc;

    if (!path && src && src->path &&
        virStorageSourceIsLocalStorage(src))
        path = src->path;

    /* Be aware that this function might run in a separate process.
     * Therefore, any driver state changes would be thrown away. */

    if ((rc = virSecurityDACTransactionAppend(path, src,
                                              uid, gid, remember, false)) < 0)
        return -1;
    else if (rc > 0)
        return 0;

    if (remember && path) {
        if (stat(path, &sb) < 0) {
            virReportSystemError(errno, _("unable to stat: %s"), path);
            return -1;
        }

        refcount = virSecurityDACRememberLabel(priv, path, sb.st_uid, sb.st_gid);
        if (refcount == -2) {
            /* Not supported. Don't error though. */
        } else if (refcount < 0) {
            return -1;
        } else if (refcount > 1) {
            /* Refcount is greater than 1 which means that there
             * is @refcount domains using the @path. Do not
             * change the label (as it would almost certainly
             * cause the other domains to lose access to the
             * @path). However, the refcounter was incremented in
             * XATTRs so decrease it. */
            if (sb.st_uid != uid || sb.st_gid != gid) {
                virReportError(VIR_ERR_OPERATION_INVALID,
                               _("Setting different DAC user or group on %s "
                                 "which is already in use"), path);
                goto error;
            }
        }
    }

    VIR_INFO("Setting DAC user and group on '%s' to '%ld:%ld'",
             NULLSTR(src ? src->path : path), (long)uid, (long)gid);

    if (virSecurityDACSetOwnershipInternal(priv, src, path, uid, gid) < 0)
        goto error;

    return 0;

 error:
    virErrorPreserveLast(&origerr);
    /* Try to restore the label. This is done so that XATTRs
     * are left in the same state as when the control entered
     * this function. However, if our attempt fails, there's
     * not much we can do. XATTRs refcounting is fubar'ed and
     * the only option we have is warn users. */
    if (virSecurityDACRestoreFileLabelInternal(mgr, src, path, remember) < 0)
        VIR_WARN("Unable to restore label on '%s'. "
                 "XATTRs might have been left in inconsistent state.",
                 NULLSTR(src ? src->path : path));

    virErrorRestore(&origerr);

    return -1;
}


static int
virSecurityDACRestoreFileLabelInternal(virSecurityManagerPtr mgr,
                                       const virStorageSource *src,
                                       const char *path,
                                       bool recall)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    int rv;
    uid_t uid = 0;  /* By default return to root:root */
    gid_t gid = 0;

    if (!path && src && src->path &&
        virStorageSourceIsLocalStorage(src))
        path = src->path;

    /* Be aware that this function might run in a separate process.
     * Therefore, any driver state changes would be thrown away. */

    if ((rv = virSecurityDACTransactionAppend(path, src, uid, gid, recall, true)) < 0)
        return -1;
    else if (rv > 0)
        return 0;

    if (recall && path) {
        rv = virSecurityDACRecallLabel(priv, path, &uid, &gid);
        if (rv == -2) {
            /* Not supported. Don't error though. */
        } else if (rv < 0) {
            return -1;
        } else if (rv > 0) {
            return 0;
        }
    }

    VIR_INFO("Restoring DAC user and group on '%s' to %ld:%ld",
             NULLSTR(src ? src->path : path), (long)uid, (long)gid);

    return virSecurityDACSetOwnershipInternal(priv, src, path, uid, gid);
}


static int
virSecurityDACRestoreFileLabel(virSecurityManagerPtr mgr,
                               const char *path)
{
    return virSecurityDACRestoreFileLabelInternal(mgr, NULL, path, true);
}


static int
virSecurityDACSetImageLabelInternal(virSecurityManagerPtr mgr,
                                    virDomainDefPtr def,
                                    virStorageSourcePtr src,
                                    virStorageSourcePtr parent)
{
    virSecurityLabelDefPtr secdef;
    virSecurityDeviceLabelDefPtr disk_seclabel;
    virSecurityDeviceLabelDefPtr parent_seclabel = NULL;
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    bool remember;
    uid_t user;
    gid_t group;

    if (!priv->dynamicOwnership)
        return 0;

    secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_DAC_NAME);
    if (secdef && !secdef->relabel)
        return 0;

    disk_seclabel = virStorageSourceGetSecurityLabelDef(src, SECURITY_DAC_NAME);
    parent_seclabel = virStorageSourceGetSecurityLabelDef(parent,
                                                          SECURITY_DAC_NAME);

    if (disk_seclabel && (!disk_seclabel->relabel || disk_seclabel->label)) {
        if (!disk_seclabel->relabel)
            return 0;

        if (virParseOwnershipIds(disk_seclabel->label, &user, &group) < 0)
            return -1;
    } else if (parent_seclabel &&
               (!parent_seclabel->relabel || parent_seclabel->label)) {
        if (!parent_seclabel->relabel)
            return 0;

        if (virParseOwnershipIds(parent_seclabel->label, &user, &group) < 0)
            return -1;
    } else {
        if (virSecurityDACGetImageIds(secdef, priv, &user, &group))
            return -1;
    }

    /* We can't do restore on shared resources safely. Not even
     * with refcounting implemented in XATTRs because if there
     * was a domain running with the feature turned off the
     * refcounter in XATTRs would not reflect the actual number
     * of times the resource is in use and thus the last restore
     * on the resource (which actually restores the original
     * owner) might cut off access to the domain with the feature
{'fraction_non_alphanumeric': 0.08947523043590026, 'fraction_numerical': 0.0058389335683595904, 'mean_word_length': 1.986401272139058, 'pattern_counts': {'":': 0, '<': 91, '<?xml version=': 0, '>': 217, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 122, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1048082', 'n_tokens_mistral': 16186, 'n_tokens_neox': 15323, 'n_words': 4569}