--- /dev/null
+/*
+ * Copyright (C) 2018 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/>.
+ */
+
+#include <config.h>
+
+#include "viralloc.h"
+#include "virfile.h"
+#include "virstring.h"
+#include "virerror.h"
+
+#include "security_util.h"
+
+#define VIR_FROM_THIS VIR_FROM_SECURITY
+
+/* There are four namespaces available on Linux (xattr(7)):
+ *
+ * user - can be modified by anybody,
+ * system - used by ACLs
+ * security - used by SELinux
+ * trusted - accessibly by CAP_SYS_ADMIN processes only
+ *
+ * Looks like the last one is way to go.
+ * Unfortunately, FreeBSD only supports:
+ *
+ * user - can be modified by anybody,
+ * system - accessible by CAP_SYS_ADMIN processes only
+ *
+ * Note that 'system' on FreeBSD corresponds to 'trusted' on
+ * Linux. So far the only point where FreeBSD and Linux can meet
+ * is NFS which still doesn't support XATTRs. Therefore we can
+ * use different namespace on each system. If NFS gains support
+ * for XATTRs then we have to find a way to deal with the
+ * different namespaces. But that is a problem for future me.
+ */
+#if defined(__linux__)
+# define XATTR_NAMESPACE "trusted"
+#elif defined(__FreeBSD__)
+# define XATTR_NAMESPACE "system"
+#endif
+
+static char *
+virSecurityGetAttrName(const char *name ATTRIBUTE_UNUSED)
+{
+ char *ret = NULL;
+#ifdef XATTR_NAMESPACE
+ ignore_value(virAsprintf(&ret, XATTR_NAMESPACE".libvirt.security.%s", name));
+#else
+ errno = ENOSYS;
+ virReportSystemError(errno, "%s",
+ _("Extended attributes are not supported on this system"));
+#endif
+ return ret;
+}
+
+
+static char *
+virSecurityGetRefCountAttrName(const char *name ATTRIBUTE_UNUSED)
+{
+ char *ret = NULL;
+#ifdef XATTR_NAMESPACE
+ ignore_value(virAsprintf(&ret, XATTR_NAMESPACE".libvirt.security.ref_%s", name));
+#else
+ errno = ENOSYS;
+ virReportSystemError(errno, "%s",
+ _("Extended attributes are not supported on this system"));
+#endif
+ return ret;
+}
+
+
+/**
+ * virSecurityGetRememberedLabel:
+ * @name: security driver name
+ * @path: file name
+ * @label: label
+ *
+ * For given @path and security driver (@name) fetch remembered
+ * @label. The caller must not restore label if an error is
+ * indicated or if @label is NULL upon return.
+ *
+ * The idea is that the first time
+ * virSecuritySetRememberedLabel() is called over @path the
+ * @label is recorded and refcounter is set to 1. Each subsequent
+ * call to virSecuritySetRememberedLabel() increases the counter.
+ * Counterpart to this is virSecurityGetRememberedLabel() which
+ * decreases the counter and reads the @label only if the counter
+ * reached value of zero. For any other call (i.e. when the
+ * counter is not zero), virSecurityGetRememberedLabel() sets
+ * @label to NULL (to notify the caller that the refcount is not
+ * zero) and returns zero.
+ *
+ * Returns: 0 on success,
+ * -1 otherwise (with error reported)
+ */
+int
+virSecurityGetRememberedLabel(const char *name,
+ const char *path,
+ char **label)
+{
+ char *ref_name = NULL;
+ char *attr_name = NULL;
+ char *value = NULL;
+ unsigned int refcount = 0;
+ int ret = -1;
+
+ *label = NULL;
+
+ if (!(ref_name = virSecurityGetRefCountAttrName(name)))
+ goto cleanup;
+
+ if (virFileGetXAttr(path, ref_name, &value) < 0) {
+ if (errno == ENOSYS || errno == ENODATA || errno == ENOTSUP) {
+ ret = 0;
+ } else {
+ virReportSystemError(errno,
+ _("Unable to get XATTR %s on %s"),
+ ref_name,
+ path);
+ }
+ goto cleanup;
+ }
+
+ if (virStrToLong_ui(value, NULL, 10, &refcount) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("malformed refcount %s on %s"),
+ value, path);
+ goto cleanup;
+ }
+
+ VIR_FREE(value);
+
+ refcount--;
+
+ if (refcount > 0) {
+ if (virAsprintf(&value, "%u", refcount) < 0)
+ goto cleanup;
+
+ if (virFileSetXAttr(path, ref_name, value) < 0)
+ goto cleanup;
+ } else {
+ if (virFileRemoveXAttr(path, ref_name) < 0)
+ goto cleanup;
+
+ if (!(attr_name = virSecurityGetAttrName(name)))
+ goto cleanup;
+
+ if (virFileGetXAttr(path, attr_name, label) < 0)
+ goto cleanup;
+
+ if (virFileRemoveXAttr(path, attr_name) < 0)
+ goto cleanup;
+ }
+
+ ret = 0;
+ cleanup:
+ VIR_FREE(value);
+ VIR_FREE(attr_name);
+ VIR_FREE(ref_name);
+ return ret;
+}
+
+
+/**
+ * virSecuritySetRememberedLabel:
+ * @name: security driver name
+ * @path: file name
+ * @label: label
+ *
+ * For given @path and security driver (@name), if called the
+ * first time over @path, set the @label to remember (i.e. the
+ * original owner of the @path). Any subsequent call over @path
+ * will increment refcounter. It is strongly recommended that the
+ * caller checks for the return value and if it is greater than 1
+ * (meaning that some domain is already using @path) the current
+ * label is required instead of setting a new one.
+ *
+ * See also virSecurityGetRememberedLabel.
+ *
+ * Returns: the new refcount value on success,
+ * -1 otherwise (with error reported)
+ */
+int
+virSecuritySetRememberedLabel(const char *name,
+ const char *path,
+ const char *label)
+{
+ char *ref_name = NULL;
+ char *attr_name = NULL;
+ char *value = NULL;
+ unsigned int refcount = 0;
+ int ret = -1;
+
+ if (!(ref_name = virSecurityGetRefCountAttrName(name)))
+ goto cleanup;
+
+ if (virFileGetXAttr(path, ref_name, &value) < 0) {
+ if (errno == ENOSYS || errno == ENOTSUP) {
+ ret = 0;
+ goto cleanup;
+ } else if (errno != ENODATA) {
+ virReportSystemError(errno,
+ _("Unable to get XATTR %s on %s"),
+ ref_name,
+ path);
+ goto cleanup;
+ }
+ }
+
+ if (value &&
+ virStrToLong_ui(value, NULL, 10, &refcount) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("malformed refcount %s on %s"),
+ value, path);
+ goto cleanup;
+ }
+
+ VIR_FREE(value);
+
+ refcount++;
+
+ if (refcount == 1) {
+ if (!(attr_name = virSecurityGetAttrName(name)))
+ goto cleanup;
+
+ if (virFileSetXAttr(path, attr_name, label) < 0)
+ goto cleanup;
+ }
+
+ if (virAsprintf(&value, "%u", refcount) < 0)
+ goto cleanup;
+
+ if (virFileSetXAttr(path, ref_name, value) < 0)
+ goto cleanup;
+
+ ret = refcount;
+ cleanup:
+ VIR_FREE(value);
+ VIR_FREE(attr_name);
+ VIR_FREE(ref_name);
+ return ret;
+}