/*
 * 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;

    item = g_new0(virSecurityDACChownItem, 1);

    tmp = g_strdup(path);

    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 G_GNUC_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) {
        paths = g_new0(const char *, list->nItems);

        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];
            size_t j;

            for (j = 0; j < state->nfds; j++) {
                if (STREQ_NULLABLE(item->path, state->paths[j]))
                    break;
            }

            /* If path wasn't locked, don't try to remember its label. */
            if (j == state->nfds)
                item->remember = false;
        }
    }

    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;

    priv->baselabel = g_strdup_printf("+%u:+%u", (unsigned int)user,
                                      (unsigned int)group);

    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 G_GNUC_UNUSED,
                            const char *path,
                            uid_t uid,
                            gid_t gid)
{
    char *label = NULL;
    int ret = -1;

    label = g_strdup_printf("+%u:+%u", (unsigned int)uid, (unsigned int)gid);

    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 G_GNUC_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 G_GNUC_UNUSED)
{
    return SECURITY_DRIVER_ENABLE;
}

static int
virSecurityDACOpen(virSecurityManagerPtr mgr G_GNUC_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 G_GNUC_UNUSED)
{
    return SECURITY_DAC_NAME;
}

static const char *
virSecurityDACGetDOI(virSecurityManagerPtr mgr G_GNUC_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;
    }

    list = g_new0(virSecurityDACChownList, 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 G_GNUC_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) {
        rc = virProcessRunInMountNamespace(pid,
                                           virSecurityDACTransactionRun,
                                           list);
        if (rc < 0) {
            if (virGetLastErrorCode() == VIR_ERR_SYSTEM_ERROR)
                pid = -1;
            else
                goto cleanup;
        }
    }

    if (pid == -1) {
        if (lock)
            rc = virProcessRunInFork(virSecurityDACTransactionRun, list);
        else
            rc = virSecurityDACTransactionRun(pid, 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 G_GNUC_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;
        }

#ifdef WIN32
        rc = ENOSYS;
#else /* !WIN32 */
        rc = chown(path, uid, gid);
#endif /* !WIN32 */
    }

    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,
                                    bool isChainTop)
{
    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;
    }

    /* This is not very clean. But so far we don't have NVMe
     * storage pool backend so that its chownCallback would be
     * called. And this place looks least offensive. */
    if (src->type == VIR_STORAGE_TYPE_NVME) {
        const virStorageSourceNVMeDef *nvme = src->nvme;
        g_autofree char *vfioGroupDev = NULL;

        if (!(vfioGroupDev = virPCIDeviceAddressGetIOMMUGroupDev(&nvme->pciAddr)))
            return -1;

        return virSecurityDACSetOwnership(mgr, NULL, vfioGroupDev, user, group, false);
    }

    /* 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
     * disabled.
     * For disks, a shared resource is the whole backing chain
     * but the top layer, or read only image, or disk explicitly
     * marked as shared.
     */
    remember = isChainTop && !src->readonly && !src->shared;

    return virSecurityDACSetOwnership(mgr, src, NULL, user, group, remember);
}


static int
virSecurityDACSetImageLabelRelative(virSecurityManagerPtr mgr,
                                    virDomainDefPtr def,
                                    virStorageSourcePtr src,
                                    virStorageSourcePtr parent,
                                    virSecurityDomainImageLabelFlags flags)
{
    virStorageSourcePtr n;

    for (n = src; virStorageSourceIsBacking(n); n = n->backingStore) {
        const bool isChainTop = flags & VIR_SECURITY_DOMAIN_IMAGE_PARENT_CHAIN_TOP;

        if (virSecurityDACSetImageLabelInternal(mgr, def, n, parent, isChainTop) < 0)
            return -1;

        if (!(flags & VIR_SECURITY_DOMAIN_IMAGE_LABEL_BACKING_CHAIN))
            break;

        flags &= ~VIR_SECURITY_DOMAIN_IMAGE_PARENT_CHAIN_TOP;
    }

    return 0;
}

static int
virSecurityDACSetImageLabel(virSecurityManagerPtr mgr,
                            virDomainDefPtr def,
                            virStorageSourcePtr src,
                            virSecurityDomainImageLabelFlags flags)
{
    return virSecurityDACSetImageLabelRelative(mgr, def, src, src, flags);
}

static int
virSecurityDACRestoreImageLabelSingle(virSecurityManagerPtr mgr,
                                      virDomainDefPtr def,
                                      virStorageSourcePtr src,
                                      bool migrated)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virSecurityLabelDefPtr secdef;
    virSecurityDeviceLabelDefPtr disk_seclabel;

    if (!priv->dynamicOwnership)
        return 0;

    /* Don't restore labels on readoly/shared disks, because other VMs may
     * still be accessing these. Alternatively we could iterate over all
     * running domains and try to figure out if it is in use, but this would
     * not work for clustered filesystems, since we can't see running VMs using
     * the file on other nodes. Safest bet is thus to skip the restore step. */
    if (src->readonly || src->shared)
        return 0;

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

    disk_seclabel = virStorageSourceGetSecurityLabelDef(src,
                                                        SECURITY_DAC_NAME);
    if (disk_seclabel && !disk_seclabel->relabel)
        return 0;

    /* If we have a shared FS and are doing migration, we must not change
     * ownership, because that kills access on the destination host which is
     * sub-optimal for the guest VM's I/O attempts :-) */
    if (migrated) {
        int rc = 1;

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

            if ((rc = virFileIsSharedFS(src->path)) < 0)
                return -1;
        }

        if (rc == 1) {
            VIR_DEBUG("Skipping image label restore on %s because FS is shared",
                      src->path);
            return 0;
        }
    }

    /* This is not very clean. But so far we don't have NVMe
     * storage pool backend so that its chownCallback would be
     * called. And this place looks least offensive. */
    if (src->type == VIR_STORAGE_TYPE_NVME) {
        const virStorageSourceNVMeDef *nvme = src->nvme;
        g_autofree char *vfioGroupDev = NULL;

        if (!(vfioGroupDev = virPCIDeviceAddressGetIOMMUGroupDev(&nvme->pciAddr)))
            return -1;

        /* Ideally, we would check if there is not another PCI
         * device within domain def that is in the same IOMMU
         * group. But we're not doing that for hostdevs yet. */

        return virSecurityDACRestoreFileLabelInternal(mgr, NULL, vfioGroupDev, false);
    }

    return virSecurityDACRestoreFileLabelInternal(mgr, src, NULL, true);
}


static int
virSecurityDACRestoreImageLabelInt(virSecurityManagerPtr mgr,
                                   virDomainDefPtr def,
                                   virStorageSourcePtr src,
                                   bool migrated)
{
    if (virSecurityDACRestoreImageLabelSingle(mgr, def, src, migrated) < 0)
        return -1;

    return 0;
}


static int
virSecurityDACRestoreImageLabel(virSecurityManagerPtr mgr,
                                virDomainDefPtr def,
                                virStorageSourcePtr src,
                                virSecurityDomainImageLabelFlags flags G_GNUC_UNUSED)
{
    return virSecurityDACRestoreImageLabelInt(mgr, def, src, false);
}


struct virSecurityDACMoveImageMetadataData {
    virSecurityManagerPtr mgr;
    const char *src;
    const char *dst;
};


static int
virSecurityDACMoveImageMetadataHelper(pid_t pid G_GNUC_UNUSED,
                                      void *opaque)
{
    struct virSecurityDACMoveImageMetadataData *data = opaque;
    const char *paths[2] = { data->src, data->dst };
    virSecurityManagerMetadataLockStatePtr state;
    int ret;

    if (!(state = virSecurityManagerMetadataLock(data->mgr, paths, G_N_ELEMENTS(paths))))
        return -1;

    ret = virSecurityMoveRememberedLabel(SECURITY_DAC_NAME, data->src, data->dst);
    virSecurityManagerMetadataUnlock(data->mgr, &state);

    if (ret == -2) {
        /* Libvirt built without XATTRS */
        ret = 0;
    }

    return ret;
}


static int
virSecurityDACMoveImageMetadata(virSecurityManagerPtr mgr,
                                pid_t pid,
                                virStorageSourcePtr src,
                                virStorageSourcePtr dst)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    struct virSecurityDACMoveImageMetadataData data = { .mgr = mgr, 0 };
    int rc;

    /* If dynamicOwnership is turned off, or owner remembering is
     * not enabled there's nothing for us to do. */
    if (!priv->dynamicOwnership)
        return 0;

    if (src && virStorageSourceIsLocalStorage(src))
        data.src = src->path;

    if (dst && virStorageSourceIsLocalStorage(dst))
        data.dst = dst->path;

    if (!data.src)
        return 0;

    if (pid == -1) {
        rc = virProcessRunInFork(virSecurityDACMoveImageMetadataHelper, &data);
    } else {
        rc = virProcessRunInMountNamespace(pid,
                                           virSecurityDACMoveImageMetadataHelper,
                                           &data);
    }

    return rc;
}


static int
virSecurityDACSetHostdevLabelHelper(const char *file,
                                    bool remember,
                                    void *opaque)
{
    virSecurityDACCallbackDataPtr cbdata = opaque;
    virSecurityManagerPtr mgr = cbdata->manager;
    virSecurityLabelDefPtr secdef = cbdata->secdef;
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    uid_t user;
    gid_t group;

    if (virSecurityDACGetIds(secdef, priv, &user, &group, NULL, NULL) < 0)
        return -1;

    return virSecurityDACSetOwnership(mgr, NULL, file, user, group, remember);
}


static int
virSecurityDACSetPCILabel(virPCIDevicePtr dev G_GNUC_UNUSED,
                          const char *file,
                          void *opaque)
{
    return virSecurityDACSetHostdevLabelHelper(file, true, opaque);
}


static int
virSecurityDACSetUSBLabel(virUSBDevicePtr dev G_GNUC_UNUSED,
                          const char *file,
                          void *opaque)
{
    return virSecurityDACSetHostdevLabelHelper(file, true, opaque);
}


static int
virSecurityDACSetSCSILabel(virSCSIDevicePtr dev G_GNUC_UNUSED,
                           const char *file,
                           void *opaque)
{
    return virSecurityDACSetHostdevLabelHelper(file, true, opaque);
}


static int
virSecurityDACSetHostLabel(virSCSIVHostDevicePtr dev G_GNUC_UNUSED,
                           const char *file,
                           void *opaque)
{
    return virSecurityDACSetHostdevLabelHelper(file, true, opaque);
}


static int
virSecurityDACSetHostdevLabel(virSecurityManagerPtr mgr,
                              virDomainDefPtr def,
                              virDomainHostdevDefPtr dev,
                              const char *vroot)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virSecurityDACCallbackData cbdata;
    virDomainHostdevSubsysUSBPtr usbsrc = &dev->source.subsys.u.usb;
    virDomainHostdevSubsysPCIPtr pcisrc = &dev->source.subsys.u.pci;
    virDomainHostdevSubsysSCSIPtr scsisrc = &dev->source.subsys.u.scsi;
    virDomainHostdevSubsysSCSIVHostPtr hostsrc = &dev->source.subsys.u.scsi_host;
    virDomainHostdevSubsysMediatedDevPtr mdevsrc = &dev->source.subsys.u.mdev;
    int ret = -1;

    if (!priv->dynamicOwnership)
        return 0;

    if (dev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS)
        return 0;

    /* Like virSecurityDACSetImageLabel() for a networked disk,
     * do nothing for an iSCSI hostdev
     */
    if (dev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI &&
        scsisrc->protocol == VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI)
        return 0;

    cbdata.manager = mgr;
    cbdata.secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_DAC_NAME);

    if (cbdata.secdef && !cbdata.secdef->relabel)
        return 0;

    switch ((virDomainHostdevSubsysType)dev->source.subsys.type) {
    case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: {
        virUSBDevicePtr usb;

        if (dev->missing)
            return 0;

        if (!(usb = virUSBDeviceNew(usbsrc->bus, usbsrc->device, vroot)))
            return -1;

        ret = virUSBDeviceFileIterate(usb,
                                      virSecurityDACSetUSBLabel,
                                      &cbdata);
        virUSBDeviceFree(usb);
        break;
    }

    case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: {
        virPCIDevicePtr pci =
            virPCIDeviceNew(pcisrc->addr.domain, pcisrc->addr.bus,
                            pcisrc->addr.slot, pcisrc->addr.function);

        if (!pci)
            return -1;

        if (pcisrc->backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO) {
            char *vfioGroupDev = virPCIDeviceGetIOMMUGroupDev(pci);

            if (!vfioGroupDev) {
                virPCIDeviceFree(pci);
                return -1;
            }
            ret = virSecurityDACSetHostdevLabelHelper(vfioGroupDev,
                                                      false,
                                                      &cbdata);
            VIR_FREE(vfioGroupDev);
        } else {
            ret = virPCIDeviceFileIterate(pci,
                                          virSecurityDACSetPCILabel,
                                          &cbdata);
        }

        virPCIDeviceFree(pci);
        break;
    }

    case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI: {
        virDomainHostdevSubsysSCSIHostPtr scsihostsrc = &scsisrc->u.host;
        virSCSIDevicePtr scsi =
            virSCSIDeviceNew(NULL,
                             scsihostsrc->adapter, scsihostsrc->bus,
                             scsihostsrc->target, scsihostsrc->unit,
                             dev->readonly, dev->shareable);

        if (!scsi)
            return -1;

        ret = virSCSIDeviceFileIterate(scsi,
                                       virSecurityDACSetSCSILabel,
                                       &cbdata);
        virSCSIDeviceFree(scsi);

        break;
    }

    case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST: {
        virSCSIVHostDevicePtr host = virSCSIVHostDeviceNew(hostsrc->wwpn);

        if (!host)
            return -1;

        ret = virSCSIVHostDeviceFileIterate(host,
                                            virSecurityDACSetHostLabel,
                                            &cbdata);
        virSCSIVHostDeviceFree(host);
        break;
    }

    case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV: {
        char *vfiodev = NULL;

        if (!(vfiodev = virMediatedDeviceGetIOMMUGroupDev(mdevsrc->uuidstr)))
            return -1;

        ret = virSecurityDACSetHostdevLabelHelper(vfiodev, true, &cbdata);

        VIR_FREE(vfiodev);
        break;
    }

    case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST:
        ret = 0;
        break;
    }

    return ret;
}


static int
virSecurityDACRestorePCILabel(virPCIDevicePtr dev G_GNUC_UNUSED,
                              const char *file,
                              void *opaque)
{
    virSecurityManagerPtr mgr = opaque;
    return virSecurityDACRestoreFileLabel(mgr, file);
}


static int
virSecurityDACRestoreUSBLabel(virUSBDevicePtr dev G_GNUC_UNUSED,
                              const char *file,
                              void *opaque)
{
    virSecurityManagerPtr mgr = opaque;
    return virSecurityDACRestoreFileLabel(mgr, file);
}


static int
virSecurityDACRestoreSCSILabel(virSCSIDevicePtr dev G_GNUC_UNUSED,
                               const char *file,
                               void *opaque)
{
    virSecurityManagerPtr mgr = opaque;
    return virSecurityDACRestoreFileLabel(mgr, file);
}


static int
virSecurityDACRestoreHostLabel(virSCSIVHostDevicePtr dev G_GNUC_UNUSED,
                               const char *file,
                               void *opaque)
{
    virSecurityManagerPtr mgr = opaque;
    return virSecurityDACRestoreFileLabel(mgr, file);
}


static int
virSecurityDACRestoreHostdevLabel(virSecurityManagerPtr mgr,
                                  virDomainDefPtr def,
                                  virDomainHostdevDefPtr dev,
                                  const char *vroot)

{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virSecurityLabelDefPtr secdef;
    virDomainHostdevSubsysUSBPtr usbsrc = &dev->source.subsys.u.usb;
    virDomainHostdevSubsysPCIPtr pcisrc = &dev->source.subsys.u.pci;
    virDomainHostdevSubsysSCSIPtr scsisrc = &dev->source.subsys.u.scsi;
    virDomainHostdevSubsysSCSIVHostPtr hostsrc = &dev->source.subsys.u.scsi_host;
    virDomainHostdevSubsysMediatedDevPtr mdevsrc = &dev->source.subsys.u.mdev;
    int ret = -1;

    secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_DAC_NAME);

    if (!priv->dynamicOwnership || (secdef && !secdef->relabel))
        return 0;

    if (dev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS)
        return 0;

    /* Like virSecurityDACRestoreImageLabelInt() for a networked disk,
     * do nothing for an iSCSI hostdev
     */
    if (dev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI &&
        scsisrc->protocol == VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI)
        return 0;

    switch ((virDomainHostdevSubsysType)dev->source.subsys.type) {
    case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: {
        virUSBDevicePtr usb;

        if (dev->missing)
            return 0;

        if (!(usb = virUSBDeviceNew(usbsrc->bus, usbsrc->device, vroot)))
            return -1;

        ret = virUSBDeviceFileIterate(usb, virSecurityDACRestoreUSBLabel, mgr);
        virUSBDeviceFree(usb);

        break;
    }

    case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: {
        virPCIDevicePtr pci =
            virPCIDeviceNew(pcisrc->addr.domain, pcisrc->addr.bus,
                            pcisrc->addr.slot, pcisrc->addr.function);

        if (!pci)
            return -1;

        if (pcisrc->backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO) {
            char *vfioGroupDev = virPCIDeviceGetIOMMUGroupDev(pci);

            if (!vfioGroupDev) {
                virPCIDeviceFree(pci);
                return -1;
            }
            ret = virSecurityDACRestoreFileLabelInternal(mgr, NULL,
                                                         vfioGroupDev, false);
            VIR_FREE(vfioGroupDev);
        } else {
            ret = virPCIDeviceFileIterate(pci, virSecurityDACRestorePCILabel, mgr);
        }
        virPCIDeviceFree(pci);
        break;
    }

    case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI: {
        virDomainHostdevSubsysSCSIHostPtr scsihostsrc = &scsisrc->u.host;
        virSCSIDevicePtr scsi =
            virSCSIDeviceNew(NULL,
                             scsihostsrc->adapter, scsihostsrc->bus,
                             scsihostsrc->target, scsihostsrc->unit,
                             dev->readonly, dev->shareable);

        if (!scsi)
            return -1;

        ret = virSCSIDeviceFileIterate(scsi, virSecurityDACRestoreSCSILabel, mgr);
        virSCSIDeviceFree(scsi);

        break;
    }

    case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST: {
        virSCSIVHostDevicePtr host = virSCSIVHostDeviceNew(hostsrc->wwpn);

        if (!host)
            return -1;

        ret = virSCSIVHostDeviceFileIterate(host,
                                            virSecurityDACRestoreHostLabel,
                                            mgr);
        virSCSIVHostDeviceFree(host);

        break;
    }

    case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV: {
        char *vfiodev = NULL;

        if (!(vfiodev = virMediatedDeviceGetIOMMUGroupDev(mdevsrc->uuidstr)))
            return -1;

        ret = virSecurityDACRestoreFileLabel(mgr, vfiodev);
        VIR_FREE(vfiodev);
        break;
    }

    case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST:
        ret = 0;
        break;
    }

    return ret;
}


static int
virSecurityDACSetChardevLabelHelper(virSecurityManagerPtr mgr,
                                    virDomainDefPtr def,
                                    virDomainChrSourceDefPtr dev_source,
                                    bool chardevStdioLogd,
                                    bool remember)

{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virSecurityLabelDefPtr seclabel;
    virSecurityDeviceLabelDefPtr chr_seclabel = NULL;
    char *in = NULL, *out = NULL;
    int ret = -1;
    uid_t user;
    gid_t group;

    seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_DAC_NAME);

    chr_seclabel = virDomainChrSourceDefGetSecurityLabelDef(dev_source,
                                                            SECURITY_DAC_NAME);

    if (chr_seclabel && !chr_seclabel->relabel)
        return 0;

    if (!chr_seclabel &&
        dev_source->type == VIR_DOMAIN_CHR_TYPE_FILE &&
        chardevStdioLogd)
        return 0;

    if (chr_seclabel && chr_seclabel->label) {
        if (virParseOwnershipIds(chr_seclabel->label, &user, &group) < 0)
            return -1;
    } else {
        if (virSecurityDACGetIds(seclabel, priv, &user, &group, NULL, NULL) < 0)
            return -1;
    }

    switch ((virDomainChrType)dev_source->type) {
    case VIR_DOMAIN_CHR_TYPE_DEV:
    case VIR_DOMAIN_CHR_TYPE_FILE:
        ret = virSecurityDACSetOwnership(mgr, NULL,
                                         dev_source->data.file.path,
                                         user, group, remember);
        break;

    case VIR_DOMAIN_CHR_TYPE_PIPE:
        in = g_strdup_printf("%s.in", dev_source->data.file.path);
        out = g_strdup_printf("%s.out", dev_source->data.file.path);
        if (virFileExists(in) && virFileExists(out)) {
            if (virSecurityDACSetOwnership(mgr, NULL, in, user, group, remember) < 0 ||
                virSecurityDACSetOwnership(mgr, NULL, out, user, group, remember) < 0)
                goto done;
        } else if (virSecurityDACSetOwnership(mgr, NULL,
                                              dev_source->data.file.path,
                                              user, group, remember) < 0) {
            goto done;
        }
        ret = 0;
        break;

    case VIR_DOMAIN_CHR_TYPE_UNIX:
        if (!dev_source->data.nix.listen ||
            (dev_source->data.nix.path &&
             virFileExists(dev_source->data.nix.path))) {
            /* Also label mode='bind' sockets if they exist,
             * e.g. because they were created by libvirt
             * and passed via FD */
            if (virSecurityDACSetOwnership(mgr, NULL,
                                           dev_source->data.nix.path,
                                           user, group, remember) < 0)
                goto done;
        }
        ret = 0;
        break;

    case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
    case VIR_DOMAIN_CHR_TYPE_NULL:
    case VIR_DOMAIN_CHR_TYPE_VC:
    case VIR_DOMAIN_CHR_TYPE_PTY:
    case VIR_DOMAIN_CHR_TYPE_STDIO:
    case VIR_DOMAIN_CHR_TYPE_UDP:
    case VIR_DOMAIN_CHR_TYPE_TCP:
    case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
    case VIR_DOMAIN_CHR_TYPE_NMDM:
    case VIR_DOMAIN_CHR_TYPE_LAST:
        ret = 0;
        break;
    }

 done:
    VIR_FREE(in);
    VIR_FREE(out);
    return ret;
}


static int
virSecurityDACSetChardevLabel(virSecurityManagerPtr mgr,
                              virDomainDefPtr def,
                              virDomainChrSourceDefPtr dev_source,
                              bool chardevStdioLogd)
{
    return virSecurityDACSetChardevLabelHelper(mgr, def, dev_source,
                                               chardevStdioLogd, true);
}


static int
virSecurityDACRestoreChardevLabelHelper(virSecurityManagerPtr mgr,
                                        virDomainDefPtr def G_GNUC_UNUSED,
                                        virDomainChrSourceDefPtr dev_source,
                                        bool chardevStdioLogd,
                                        bool recall)
{
    virSecurityDeviceLabelDefPtr chr_seclabel = NULL;
    char *in = NULL, *out = NULL;
    int ret = -1;

    chr_seclabel = virDomainChrSourceDefGetSecurityLabelDef(dev_source,
                                                            SECURITY_DAC_NAME);

    if (chr_seclabel && !chr_seclabel->relabel)
        return 0;

    if (!chr_seclabel &&
        dev_source->type == VIR_DOMAIN_CHR_TYPE_FILE &&
        chardevStdioLogd)
        return 0;

    switch ((virDomainChrType)dev_source->type) {
    case VIR_DOMAIN_CHR_TYPE_DEV:
    case VIR_DOMAIN_CHR_TYPE_FILE:
        ret = virSecurityDACRestoreFileLabelInternal(mgr, NULL,
                                                     dev_source->data.file.path,
                                                     recall);
        break;

    case VIR_DOMAIN_CHR_TYPE_PIPE:
        out = g_strdup_printf("%s.out", dev_source->data.file.path);
        in = g_strdup_printf("%s.in", dev_source->data.file.path);
        if (virFileExists(in) && virFileExists(out)) {
            if (virSecurityDACRestoreFileLabelInternal(mgr, NULL, out, recall) < 0 ||
                virSecurityDACRestoreFileLabelInternal(mgr, NULL, in, recall) < 0)
                goto done;
        } else if (virSecurityDACRestoreFileLabelInternal(mgr, NULL,
                                                          dev_source->data.file.path,
                                                          recall) < 0) {
            goto done;
        }
        ret = 0;
        break;

    case VIR_DOMAIN_CHR_TYPE_UNIX:
        if (!dev_source->data.nix.listen &&
            virSecurityDACRestoreFileLabelInternal(mgr, NULL,
                                                   dev_source->data.nix.path,
                                                   recall) < 0) {
            goto done;
        }
        ret = 0;
        break;

    case VIR_DOMAIN_CHR_TYPE_NULL:
    case VIR_DOMAIN_CHR_TYPE_VC:
    case VIR_DOMAIN_CHR_TYPE_PTY:
    case VIR_DOMAIN_CHR_TYPE_STDIO:
    case VIR_DOMAIN_CHR_TYPE_UDP:
    case VIR_DOMAIN_CHR_TYPE_TCP:
    case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
    case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
    case VIR_DOMAIN_CHR_TYPE_NMDM:
    case VIR_DOMAIN_CHR_TYPE_LAST:
        ret = 0;
        break;
    }

 done:
    VIR_FREE(in);
    VIR_FREE(out);
    return ret;
}


static int
virSecurityDACRestoreChardevLabel(virSecurityManagerPtr mgr,
                                  virDomainDefPtr def,
                                  virDomainChrSourceDefPtr dev_source,
                                  bool chardevStdioLogd)
{
    return virSecurityDACRestoreChardevLabelHelper(mgr, def, dev_source,
                                                   chardevStdioLogd, true);
}


struct _virSecuritySELinuxChardevCallbackData {
    virSecurityManagerPtr mgr;
    bool chardevStdioLogd;
};


static int
virSecurityDACRestoreChardevCallback(virDomainDefPtr def,
                                     virDomainChrDefPtr dev G_GNUC_UNUSED,
                                     void *opaque)
{
    struct _virSecuritySELinuxChardevCallbackData *data = opaque;

    return virSecurityDACRestoreChardevLabel(data->mgr, def, dev->source,
                                             data->chardevStdioLogd);
}


static int
virSecurityDACSetTPMFileLabel(virSecurityManagerPtr mgr,
                              virDomainDefPtr def,
                              virDomainTPMDefPtr tpm)
{
    int ret = 0;

    switch (tpm->type) {
    case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
        ret = virSecurityDACSetChardevLabelHelper(mgr, def,
                                                  &tpm->data.passthrough.source,
                                                  false, false);
        break;
    case VIR_DOMAIN_TPM_TYPE_EMULATOR:
        ret = virSecurityDACSetChardevLabelHelper(mgr, def,
                                                  &tpm->data.emulator.source,
                                                  false, false);
        break;
    case VIR_DOMAIN_TPM_TYPE_LAST:
        break;
    }

    return ret;
}


static int
virSecurityDACRestoreTPMFileLabel(virSecurityManagerPtr mgr,
                                  virDomainDefPtr def,
                                  virDomainTPMDefPtr tpm)
{
    int ret = 0;

    switch (tpm->type) {
    case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
        ret = virSecurityDACRestoreChardevLabelHelper(mgr, def,
                                                      &tpm->data.passthrough.source,
                                                      false, false);
        break;
    case VIR_DOMAIN_TPM_TYPE_EMULATOR:
        /* swtpm will have removed the Unix socket upon termination */
    case VIR_DOMAIN_TPM_TYPE_LAST:
        break;
    }

    return ret;
}


static int
virSecurityDACSetGraphicsLabel(virSecurityManagerPtr mgr,
                               virDomainDefPtr def,
                               virDomainGraphicsDefPtr gfx)

{
    const char *rendernode = virDomainGraphicsGetRenderNode(gfx);
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virSecurityLabelDefPtr seclabel;
    uid_t user;
    gid_t group;

    /* There's nothing to relabel */
    if (!rendernode)
        return 0;

    /* Skip chowning the shared render file if namespaces are disabled */
    if (!priv->mountNamespace)
        return 0;

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

    if (virSecurityDACGetIds(seclabel, priv, &user, &group, NULL, NULL) < 0)
        return -1;

    if (virSecurityDACSetOwnership(mgr, NULL, rendernode, user, group, true) < 0)
        return -1;

    return 0;
}


static int
virSecurityDACRestoreGraphicsLabel(virSecurityManagerPtr mgr G_GNUC_UNUSED,
                               virDomainDefPtr def G_GNUC_UNUSED,
                               virDomainGraphicsDefPtr gfx G_GNUC_UNUSED)

{
    /* The only graphics labelling we do is dependent on mountNamespaces,
       in which case 'restoring' the label doesn't actually accomplish
       anything, so there's nothing to do here */
    return 0;
}


static int
virSecurityDACSetInputLabel(virSecurityManagerPtr mgr,
                            virDomainDefPtr def,
                            virDomainInputDefPtr input)

{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virSecurityLabelDefPtr seclabel;
    int ret = -1;
    uid_t user;
    gid_t group;

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

    switch ((virDomainInputType)input->type) {
    case VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH:
        if (virSecurityDACGetIds(seclabel, priv, &user, &group, NULL, NULL) < 0)
            return -1;

        ret = virSecurityDACSetOwnership(mgr, NULL,
                                         input->source.evdev,
                                         user, group, true);
        break;

    case VIR_DOMAIN_INPUT_TYPE_MOUSE:
    case VIR_DOMAIN_INPUT_TYPE_TABLET:
    case VIR_DOMAIN_INPUT_TYPE_KBD:
    case VIR_DOMAIN_INPUT_TYPE_LAST:
        ret = 0;
        break;
    }

    return ret;
}

static int
virSecurityDACRestoreInputLabel(virSecurityManagerPtr mgr,
                                virDomainDefPtr def G_GNUC_UNUSED,
                                virDomainInputDefPtr input)
{
    int ret = -1;

    switch ((virDomainInputType)input->type) {
    case VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH:
        ret = virSecurityDACRestoreFileLabel(mgr, input->source.evdev);
        break;

    case VIR_DOMAIN_INPUT_TYPE_MOUSE:
    case VIR_DOMAIN_INPUT_TYPE_TABLET:
    case VIR_DOMAIN_INPUT_TYPE_KBD:
    case VIR_DOMAIN_INPUT_TYPE_LAST:
        ret = 0;
        break;
    }

    return ret;
}


static int
virSecurityDACRestoreMemoryLabel(virSecurityManagerPtr mgr,
                                 virDomainDefPtr def G_GNUC_UNUSED,
                                 virDomainMemoryDefPtr mem)
{
    int ret = -1;

    switch (mem->model) {
    case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
        ret = virSecurityDACRestoreFileLabel(mgr, mem->nvdimmPath);
        break;

    case VIR_DOMAIN_MEMORY_MODEL_DIMM:
    case VIR_DOMAIN_MEMORY_MODEL_LAST:
    case VIR_DOMAIN_MEMORY_MODEL_NONE:
        ret = 0;
        break;
    }

    return ret;
}


static int
virSecurityDACRestoreSEVLabel(virSecurityManagerPtr mgr G_GNUC_UNUSED,
                              virDomainDefPtr def G_GNUC_UNUSED)
{
    /* we only label /dev/sev when running with namespaces, so we don't need to
     * restore anything */
    return 0;
}


static int
virSecurityDACRestoreSysinfoLabel(virSecurityManagerPtr mgr,
                                  virSysinfoDefPtr def)
{
    size_t i;

    for (i = 0; i < def->nfw_cfgs; i++) {
        virSysinfoFWCfgDefPtr f = &def->fw_cfgs[i];

        if (f->file &&
            virSecurityDACRestoreFileLabel(mgr, f->file) < 0)
            return -1;
    }

    return 0;
}


static int
virSecurityDACRestoreAllLabel(virSecurityManagerPtr mgr,
                              virDomainDefPtr def,
                              bool migrated,
                              bool chardevStdioLogd)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virSecurityLabelDefPtr secdef;
    size_t i;
    int rc = 0;

    struct _virSecuritySELinuxChardevCallbackData chardevData = {
        .mgr = mgr,
        .chardevStdioLogd = chardevStdioLogd,
    };

    secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_DAC_NAME);

    if (!priv->dynamicOwnership || (secdef && !secdef->relabel))
        return 0;

    VIR_DEBUG("Restoring security label on %s migrated=%d",
              def->name, migrated);

    for (i = 0; i < def->ndisks; i++) {
        if (virSecurityDACRestoreImageLabelInt(mgr,
                                               def,
                                               def->disks[i]->src,
                                               migrated) < 0)
            rc = -1;
    }

    for (i = 0; i < def->ngraphics; i++) {
        if (virSecurityDACRestoreGraphicsLabel(mgr, def, def->graphics[i]) < 0)
            return -1;
    }

    for (i = 0; i < def->ninputs; i++) {
        if (virSecurityDACRestoreInputLabel(mgr, def, def->inputs[i]) < 0)
            rc = -1;
    }

    for (i = 0; i < def->nhostdevs; i++) {
        if (virSecurityDACRestoreHostdevLabel(mgr,
                                              def,
                                              def->hostdevs[i],
                                              NULL) < 0)
            rc = -1;
    }

    for (i = 0; i < def->nmems; i++) {
        if (virSecurityDACRestoreMemoryLabel(mgr,
                                             def,
                                             def->mems[i]) < 0)
            rc = -1;
    }

    if (virDomainChrDefForeach(def,
                               false,
                               virSecurityDACRestoreChardevCallback,
                               &chardevData) < 0)
        rc = -1;

    for (i = 0; i < def->ntpms; i++) {
        if (virSecurityDACRestoreTPMFileLabel(mgr,
                                              def,
                                              def->tpms[i]) < 0)
            rc = -1;
    }

    if (def->sev) {
        if (virSecurityDACRestoreSEVLabel(mgr, def) < 0)
            rc = -1;
    }

    for (i = 0; i < def->nsysinfo; i++) {
        if (virSecurityDACRestoreSysinfoLabel(mgr,
                                              def->sysinfo[i]) < 0)
            rc = -1;
    }

    if (def->os.loader && def->os.loader->nvram &&
        virSecurityDACRestoreFileLabel(mgr, def->os.loader->nvram) < 0)
        rc = -1;

    if (def->os.kernel &&
        virSecurityDACRestoreFileLabel(mgr, def->os.kernel) < 0)
        rc = -1;

    if (def->os.initrd &&
        virSecurityDACRestoreFileLabel(mgr, def->os.initrd) < 0)
        rc = -1;

    if (def->os.dtb &&
        virSecurityDACRestoreFileLabel(mgr, def->os.dtb) < 0)
        rc = -1;

    if (def->os.slic_table &&
        virSecurityDACRestoreFileLabel(mgr, def->os.slic_table) < 0)
        rc = -1;

    return rc;
}


static int
virSecurityDACSetChardevCallback(virDomainDefPtr def,
                                 virDomainChrDefPtr dev G_GNUC_UNUSED,
                                 void *opaque)
{
    struct _virSecuritySELinuxChardevCallbackData *data = opaque;

    return virSecurityDACSetChardevLabel(data->mgr, def, dev->source,
                                         data->chardevStdioLogd);
}


static int
virSecurityDACSetMemoryLabel(virSecurityManagerPtr mgr,
                             virDomainDefPtr def,
                             virDomainMemoryDefPtr mem)

{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virSecurityLabelDefPtr seclabel;
    int ret = -1;
    uid_t user;
    gid_t group;

    switch (mem->model) {
    case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
        seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_DAC_NAME);
        if (seclabel && !seclabel->relabel)
            return 0;

        if (virSecurityDACGetIds(seclabel, priv, &user, &group, NULL, NULL) < 0)
            return -1;

        ret = virSecurityDACSetOwnership(mgr, NULL,
                                         mem->nvdimmPath,
                                         user, group, true);
        break;

    case VIR_DOMAIN_MEMORY_MODEL_DIMM:
    case VIR_DOMAIN_MEMORY_MODEL_LAST:
    case VIR_DOMAIN_MEMORY_MODEL_NONE:
        ret = 0;
        break;
    }

    return ret;
}


static int
virSecurityDACSetSEVLabel(virSecurityManagerPtr mgr,
                          virDomainDefPtr def)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virSecurityLabelDefPtr seclabel;
    uid_t user;
    gid_t group;

    /* Skip chowning /dev/sev if namespaces are disabled as we'd significantly
     * increase the chance of a DOS attack on SEV
     */
    if (!priv->mountNamespace)
        return 0;

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

    if (virSecurityDACGetIds(seclabel, priv, &user, &group, NULL, NULL) < 0)
        return -1;

    if (virSecurityDACSetOwnership(mgr, NULL, DEV_SEV,
                                   user, group, true) < 0)
        return -1;

    return 0;
}


static int
virSecurityDACSetSysinfoLabel(virSecurityManagerPtr mgr,
                              uid_t user,
                              gid_t group,
                              virSysinfoDefPtr def)
{
    size_t i;

    for (i = 0; i < def->nfw_cfgs; i++) {
        virSysinfoFWCfgDefPtr f = &def->fw_cfgs[i];

        if (f->file &&
            virSecurityDACSetOwnership(mgr, NULL, f->file,
                                       user, group, true) < 0)
            return -1;
    }

    return 0;
}


static int
virSecurityDACSetAllLabel(virSecurityManagerPtr mgr,
                          virDomainDefPtr def,
                          const char *incomingPath G_GNUC_UNUSED,
                          bool chardevStdioLogd,
                          bool migrated G_GNUC_UNUSED)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virSecurityLabelDefPtr secdef;
    size_t i;
    uid_t user;
    gid_t group;

    struct _virSecuritySELinuxChardevCallbackData chardevData = {
        .mgr = mgr,
        .chardevStdioLogd = chardevStdioLogd,
    };

    secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_DAC_NAME);

    if (!priv->dynamicOwnership || (secdef && !secdef->relabel))
        return 0;

    for (i = 0; i < def->ndisks; i++) {
        /* XXX fixme - we need to recursively label the entire tree :-( */
        if (virDomainDiskGetType(def->disks[i]) == VIR_STORAGE_TYPE_DIR)
            continue;
        if (virSecurityDACSetImageLabel(mgr, def, def->disks[i]->src,
                                        VIR_SECURITY_DOMAIN_IMAGE_LABEL_BACKING_CHAIN |
                                        VIR_SECURITY_DOMAIN_IMAGE_PARENT_CHAIN_TOP) < 0)
            return -1;
    }

    for (i = 0; i < def->ngraphics; i++) {
        if (virSecurityDACSetGraphicsLabel(mgr, def, def->graphics[i]) < 0)
            return -1;
    }

    for (i = 0; i < def->ninputs; i++) {
        if (virSecurityDACSetInputLabel(mgr, def, def->inputs[i]) < 0)
            return -1;
    }

    for (i = 0; i < def->nhostdevs; i++) {
        if (virSecurityDACSetHostdevLabel(mgr,
                                          def,
                                          def->hostdevs[i],
                                          NULL) < 0)
            return -1;
    }

    for (i = 0; i < def->nmems; i++) {
        if (virSecurityDACSetMemoryLabel(mgr,
                                         def,
                                         def->mems[i]) < 0)
            return -1;
    }

    if (virDomainChrDefForeach(def,
                               true,
                               virSecurityDACSetChardevCallback,
                               &chardevData) < 0)
        return -1;

    for (i = 0; i < def->ntpms; i++) {
        if (virSecurityDACSetTPMFileLabel(mgr,
                                          def,
                                          def->tpms[i]) < 0)
            return -1;
    }

    if (def->sev) {
        if (virSecurityDACSetSEVLabel(mgr, def) < 0)
            return -1;
    }

    if (virSecurityDACGetImageIds(secdef, priv, &user, &group))
        return -1;

    for (i = 0; i < def->nsysinfo; i++) {
        if (virSecurityDACSetSysinfoLabel(mgr, user, group, def->sysinfo[i]) < 0)
            return -1;
    }

    if (def->os.loader && def->os.loader->nvram &&
        virSecurityDACSetOwnership(mgr, NULL,
                                   def->os.loader->nvram,
                                   user, group, true) < 0)
        return -1;

    if (def->os.kernel &&
        virSecurityDACSetOwnership(mgr, NULL,
                                   def->os.kernel,
                                   user, group, true) < 0)
        return -1;

    if (def->os.initrd &&
        virSecurityDACSetOwnership(mgr, NULL,
                                   def->os.initrd,
                                   user, group, true) < 0)
        return -1;

    if (def->os.dtb &&
        virSecurityDACSetOwnership(mgr, NULL,
                                   def->os.dtb,
                                   user, group, true) < 0)
        return -1;

    if (def->os.slic_table &&
        virSecurityDACSetOwnership(mgr, NULL,
                                   def->os.slic_table,
                                   user, group, true) < 0)
        return -1;

    return 0;
}


static int
virSecurityDACSetProcessLabel(virSecurityManagerPtr mgr,
                              virDomainDefPtr def)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virSecurityLabelDefPtr secdef;
    uid_t user;
    gid_t group;
    gid_t *groups;
    int ngroups;

    secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_DAC_NAME);

    if (virSecurityDACGetIds(secdef, priv, &user, &group, &groups, &ngroups) < 0)
        return -1;

    VIR_DEBUG("Dropping privileges to %u:%u, %d supplemental groups",
              (unsigned int)user, (unsigned int)group, ngroups);

    if (virSetUIDGID(user, group, groups, ngroups) < 0)
        return -1;

    return 0;
}


static int
virSecurityDACSetChildProcessLabel(virSecurityManagerPtr mgr,
                                   virDomainDefPtr def,
                                   virCommandPtr cmd)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virSecurityLabelDefPtr secdef;
    uid_t user;
    gid_t group;

    secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_DAC_NAME);

    if (virSecurityDACGetIds(secdef, priv, &user, &group, NULL, NULL) < 0)
        return -1;

    VIR_DEBUG("Setting child to drop privileges to %u:%u",
              (unsigned int)user, (unsigned int)group);

    virCommandSetUID(cmd, user);
    virCommandSetGID(cmd, group);
    return 0;
}


static int
virSecurityDACVerify(virSecurityManagerPtr mgr G_GNUC_UNUSED,
                     virDomainDefPtr def G_GNUC_UNUSED)
{
    return 0;
}

static int
virSecurityDACGenLabel(virSecurityManagerPtr mgr,
                       virDomainDefPtr def)
{
    int rc = -1;
    virSecurityLabelDefPtr seclabel;
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);

    seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_DAC_NAME);
    if (seclabel == NULL)
        return rc;

    if (seclabel->imagelabel) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("security image label already "
                         "defined for VM"));
        return rc;
    }

    if (seclabel->model
        && STRNEQ(seclabel->model, SECURITY_DAC_NAME)) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("security label model %s is not supported "
                         "with selinux"),
                       seclabel->model);
            return rc;
    }

    switch ((virDomainSeclabelType)seclabel->type) {
    case VIR_DOMAIN_SECLABEL_STATIC:
        if (seclabel->label == NULL) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("missing label for static security "
                             "driver in domain %s"), def->name);
            return rc;
        }
        break;
    case VIR_DOMAIN_SECLABEL_DYNAMIC:
        seclabel->label = g_strdup_printf("+%u:+%u", (unsigned int)priv->user,
                                          (unsigned int)priv->group);
        if (seclabel->label == NULL) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("cannot generate dac user and group id "
                             "for domain %s"), def->name);
            return rc;
        }
        break;
    case VIR_DOMAIN_SECLABEL_NONE:
        /* no op */
        return 0;
    case VIR_DOMAIN_SECLABEL_DEFAULT:
    case VIR_DOMAIN_SECLABEL_LAST:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("unexpected security label type '%s'"),
                       virDomainSeclabelTypeToString(seclabel->type));
        return rc;
    }

    if (seclabel->relabel && !seclabel->imagelabel)
        seclabel->imagelabel = g_strdup(seclabel->label);

    return 0;
}

static int
virSecurityDACReleaseLabel(virSecurityManagerPtr mgr G_GNUC_UNUSED,
                           virDomainDefPtr def G_GNUC_UNUSED)
{
    return 0;
}

static int
virSecurityDACReserveLabel(virSecurityManagerPtr mgr G_GNUC_UNUSED,
                           virDomainDefPtr def G_GNUC_UNUSED,
                           pid_t pid G_GNUC_UNUSED)
{
    return 0;
}

#ifdef __linux__
static int
virSecurityDACGetProcessLabelInternal(pid_t pid,
                                      virSecurityLabelPtr seclabel)
{
    struct stat sb;
    char *path = NULL;
    int ret = -1;

    VIR_DEBUG("Getting DAC user and group on process '%d'", pid);

    path = g_strdup_printf("/proc/%d", (int)pid);

    if (g_lstat(path, &sb) < 0) {
        virReportSystemError(errno,
                             _("unable to get uid and gid for PID %d via procfs"),
                             pid);
        goto cleanup;
    }

    g_snprintf(seclabel->label, VIR_SECURITY_LABEL_BUFLEN,
               "+%u:+%u", (unsigned int)sb.st_uid, (unsigned int)sb.st_gid);
    ret = 0;

 cleanup:
    VIR_FREE(path);
    return ret;
}
#elif defined(__FreeBSD__)
static int
virSecurityDACGetProcessLabelInternal(pid_t pid,
                                      virSecurityLabelPtr seclabel)
{
    struct kinfo_proc p;
    int mib[4];
    size_t len = 4;

    sysctlnametomib("kern.proc.pid", mib, &len);

    len = sizeof(struct kinfo_proc);
    mib[3] = pid;

    if (sysctl(mib, 4, &p, &len, NULL, 0) < 0) {
        virReportSystemError(errno,
                             _("unable to get PID %d uid and gid via sysctl"),
                             pid);
        return -1;
    }

    g_snprintf(seclabel->label, VIR_SECURITY_LABEL_BUFLEN,
               "+%u:+%u", (unsigned int)p.ki_uid, (unsigned int)p.ki_groups[0]);

    return 0;
}
#else
static int
virSecurityDACGetProcessLabelInternal(pid_t pid G_GNUC_UNUSED,
                                      virSecurityLabelPtr seclabel G_GNUC_UNUSED)
{
    virReportSystemError(ENOSYS, "%s",
                         _("Cannot get process uid and gid on this platform"));
    return -1;
}
#endif

static int
virSecurityDACGetProcessLabel(virSecurityManagerPtr mgr G_GNUC_UNUSED,
                              virDomainDefPtr def,
                              pid_t pid,
                              virSecurityLabelPtr seclabel)
{
    virSecurityLabelDefPtr secdef =
        virDomainDefGetSecurityLabelDef(def, SECURITY_DAC_NAME);

    if (secdef == NULL) {
        VIR_DEBUG("missing label for DAC security "
                  "driver in domain %s", def->name);

        if (virSecurityDACGetProcessLabelInternal(pid, seclabel) < 0)
            return -1;
        return 0;
    }

    if (secdef->label)
        ignore_value(virStrcpy(seclabel->label, secdef->label,
                               VIR_SECURITY_LABEL_BUFLEN));

    return 0;
}

static int
virSecurityDACSetDaemonSocketLabel(virSecurityManagerPtr mgr G_GNUC_UNUSED,
                                   virDomainDefPtr vm G_GNUC_UNUSED)
{
    return 0;
}


static int
virSecurityDACSetSocketLabel(virSecurityManagerPtr mgr G_GNUC_UNUSED,
                             virDomainDefPtr def G_GNUC_UNUSED)
{
    return 0;
}


static int
virSecurityDACClearSocketLabel(virSecurityManagerPtr mgr G_GNUC_UNUSED,
                               virDomainDefPtr def G_GNUC_UNUSED)
{
    return 0;
}

static int
virSecurityDACSetImageFDLabel(virSecurityManagerPtr mgr G_GNUC_UNUSED,
                              virDomainDefPtr def G_GNUC_UNUSED,
                              int fd G_GNUC_UNUSED)
{
    return 0;
}

static int
virSecurityDACSetTapFDLabel(virSecurityManagerPtr mgr G_GNUC_UNUSED,
                            virDomainDefPtr def G_GNUC_UNUSED,
                            int fd G_GNUC_UNUSED)
{
    return 0;
}

static char *
virSecurityDACGetMountOptions(virSecurityManagerPtr mgr G_GNUC_UNUSED,
                              virDomainDefPtr vm G_GNUC_UNUSED)
{
    return NULL;
}

static const char *
virSecurityDACGetBaseLabel(virSecurityManagerPtr mgr,
                           int virt G_GNUC_UNUSED)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    return priv->baselabel;
}

static int
virSecurityDACDomainSetPathLabel(virSecurityManagerPtr mgr,
                                 virDomainDefPtr def,
                                 const char *path,
                                 bool allowSubtree G_GNUC_UNUSED)
{
    virSecurityDACDataPtr priv = virSecurityManagerGetPrivateData(mgr);
    virSecurityLabelDefPtr seclabel;
    uid_t user;
    gid_t group;

    seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_DAC_NAME);

    if (virSecurityDACGetIds(seclabel, priv, &user, &group, NULL, NULL) < 0)
        return -1;

    return virSecurityDACSetOwnership(mgr, NULL, path, user, group, true);
}

static int
virSecurityDACDomainRestorePathLabel(virSecurityManagerPtr mgr,
                                     virDomainDefPtr def G_GNUC_UNUSED,
                                     const char *path)
{
    return virSecurityDACRestoreFileLabel(mgr, path);
}


virSecurityDriver virSecurityDriverDAC = {
    .privateDataLen                     = sizeof(virSecurityDACData),
    .name                               = SECURITY_DAC_NAME,
    .probe                              = virSecurityDACProbe,
    .open                               = virSecurityDACOpen,
    .close                              = virSecurityDACClose,

    .getModel                           = virSecurityDACGetModel,
    .getDOI                             = virSecurityDACGetDOI,

    .preFork                            = virSecurityDACPreFork,

    .transactionStart                   = virSecurityDACTransactionStart,
    .transactionCommit                  = virSecurityDACTransactionCommit,
    .transactionAbort                   = virSecurityDACTransactionAbort,

    .domainSecurityVerify               = virSecurityDACVerify,

    .domainSetSecurityImageLabel        = virSecurityDACSetImageLabel,
    .domainRestoreSecurityImageLabel    = virSecurityDACRestoreImageLabel,
    .domainMoveImageMetadata            = virSecurityDACMoveImageMetadata,

    .domainSetSecurityMemoryLabel       = virSecurityDACSetMemoryLabel,
    .domainRestoreSecurityMemoryLabel   = virSecurityDACRestoreMemoryLabel,

    .domainSetSecurityInputLabel        = virSecurityDACSetInputLabel,
    .domainRestoreSecurityInputLabel    = virSecurityDACRestoreInputLabel,

    .domainSetSecurityDaemonSocketLabel = virSecurityDACSetDaemonSocketLabel,
    .domainSetSecuritySocketLabel       = virSecurityDACSetSocketLabel,
    .domainClearSecuritySocketLabel     = virSecurityDACClearSocketLabel,

    .domainGenSecurityLabel             = virSecurityDACGenLabel,
    .domainReserveSecurityLabel         = virSecurityDACReserveLabel,
    .domainReleaseSecurityLabel         = virSecurityDACReleaseLabel,

    .domainGetSecurityProcessLabel      = virSecurityDACGetProcessLabel,
    .domainSetSecurityProcessLabel      = virSecurityDACSetProcessLabel,
    .domainSetSecurityChildProcessLabel = virSecurityDACSetChildProcessLabel,

    .domainSetSecurityAllLabel          = virSecurityDACSetAllLabel,
    .domainRestoreSecurityAllLabel      = virSecurityDACRestoreAllLabel,

    .domainSetSecurityHostdevLabel      = virSecurityDACSetHostdevLabel,
    .domainRestoreSecurityHostdevLabel  = virSecurityDACRestoreHostdevLabel,

    .domainSetSecurityImageFDLabel      = virSecurityDACSetImageFDLabel,
    .domainSetSecurityTapFDLabel        = virSecurityDACSetTapFDLabel,

    .domainGetSecurityMountOptions      = virSecurityDACGetMountOptions,

    .getBaseLabel                       = virSecurityDACGetBaseLabel,

    .domainSetPathLabel                 = virSecurityDACDomainSetPathLabel,
    .domainRestorePathLabel             = virSecurityDACDomainRestorePathLabel,

    .domainSetSecurityChardevLabel      = virSecurityDACSetChardevLabel,
    .domainRestoreSecurityChardevLabel  = virSecurityDACRestoreChardevLabel,
};
