Blame libglnx/glnx-xattrs.c

rpm-build c487f7
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
rpm-build c487f7
 *
rpm-build c487f7
 * Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>.
rpm-build c487f7
 *
rpm-build c487f7
 * This library is free software; you can redistribute it and/or
rpm-build c487f7
 * modify it under the terms of the GNU Lesser General Public
rpm-build c487f7
 * License as published by the Free Software Foundation; either
rpm-build c487f7
 * version 2 of the License, or (at your option) any later version.
rpm-build c487f7
 *
rpm-build c487f7
 * This library is distributed in the hope that it will be useful,
rpm-build c487f7
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
rpm-build c487f7
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
rpm-build c487f7
 * Lesser General Public License for more details.
rpm-build c487f7
 *
rpm-build c487f7
 * You should have received a copy of the GNU Lesser General Public
rpm-build c487f7
 * License along with this library; if not, write to the
rpm-build c487f7
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
rpm-build c487f7
 * Boston, MA 02111-1307, USA.
rpm-build c487f7
 */
rpm-build c487f7
rpm-build c487f7
#include "config.h"
rpm-build c487f7
rpm-build c487f7
#include <string.h>
rpm-build c487f7
#include <stdio.h>
rpm-build c487f7
rpm-build c487f7
#include <glnx-macros.h>
rpm-build c487f7
#include <glnx-xattrs.h>
rpm-build c487f7
#include <glnx-errors.h>
rpm-build c487f7
#include <glnx-local-alloc.h>
rpm-build c487f7
rpm-build c487f7
static GVariant *
rpm-build c487f7
variant_new_ay_bytes (GBytes *bytes)
rpm-build c487f7
{
rpm-build c487f7
  gsize size;
rpm-build c487f7
  gconstpointer data;
rpm-build c487f7
  data = g_bytes_get_data (bytes, &size);
rpm-build c487f7
  g_bytes_ref (bytes);
rpm-build c487f7
  return g_variant_new_from_data (G_VARIANT_TYPE ("ay"), data, size,
rpm-build c487f7
                                  TRUE, (GDestroyNotify)g_bytes_unref, bytes);
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
static char *
rpm-build c487f7
canonicalize_xattrs (char    *xattr_string,
rpm-build c487f7
                     size_t   len)
rpm-build c487f7
{
rpm-build c487f7
  char *p;
rpm-build c487f7
  GSList *xattrs = NULL;
rpm-build c487f7
  GSList *iter;
rpm-build c487f7
  GString *result;
rpm-build c487f7
rpm-build c487f7
  result = g_string_new (0);
rpm-build c487f7
rpm-build c487f7
  p = xattr_string;
rpm-build c487f7
  while (p < xattr_string+len)
rpm-build c487f7
    {
rpm-build c487f7
      xattrs = g_slist_prepend (xattrs, p);
rpm-build c487f7
      p += strlen (p) + 1;
rpm-build c487f7
    }
rpm-build c487f7
rpm-build c487f7
  xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp);
rpm-build c487f7
  for (iter = xattrs; iter; iter = iter->next) {
rpm-build c487f7
    g_string_append (result, iter->data);
rpm-build c487f7
    g_string_append_c (result, '\0');
rpm-build c487f7
  }
rpm-build c487f7
rpm-build c487f7
  g_slist_free (xattrs);
rpm-build c487f7
  return g_string_free (result, FALSE);
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
static gboolean
rpm-build c487f7
read_xattr_name_array (const char *path,
rpm-build c487f7
                       int         fd,
rpm-build c487f7
                       const char *xattrs,
rpm-build c487f7
                       size_t      len,
rpm-build c487f7
                       GVariantBuilder *builder,
rpm-build c487f7
                       GError  **error)
rpm-build c487f7
{
rpm-build c487f7
  gboolean ret = FALSE;
rpm-build c487f7
  const char *p;
rpm-build c487f7
  int r;
rpm-build c487f7
  const char *funcstr;
rpm-build c487f7
rpm-build c487f7
  g_assert (path != NULL || fd != -1);
rpm-build c487f7
rpm-build c487f7
  funcstr = fd != -1 ? "fgetxattr" : "lgetxattr";
rpm-build c487f7
rpm-build c487f7
  for (p = xattrs; p < xattrs+len; p = p + strlen (p) + 1)
rpm-build c487f7
    {
rpm-build c487f7
      ssize_t bytes_read;
rpm-build c487f7
      g_autofree char *buf = NULL;
rpm-build c487f7
      g_autoptr(GBytes) bytes = NULL;
rpm-build c487f7
rpm-build c487f7
    again:
rpm-build c487f7
      if (fd != -1)
rpm-build c487f7
        bytes_read = fgetxattr (fd, p, NULL, 0);
rpm-build c487f7
      else
rpm-build c487f7
        bytes_read = lgetxattr (path, p, NULL, 0);
rpm-build c487f7
      if (bytes_read < 0)
rpm-build c487f7
        {
rpm-build c487f7
          if (errno == ENODATA)
rpm-build c487f7
            continue;
rpm-build c487f7
rpm-build c487f7
          glnx_set_prefix_error_from_errno (error, "%s", funcstr);
rpm-build c487f7
          goto out;
rpm-build c487f7
        }
rpm-build c487f7
      if (bytes_read == 0)
rpm-build c487f7
        continue;
rpm-build c487f7
rpm-build c487f7
      buf = g_malloc (bytes_read);
rpm-build c487f7
      if (fd != -1)
rpm-build c487f7
        r = fgetxattr (fd, p, buf, bytes_read);
rpm-build c487f7
      else
rpm-build c487f7
        r = lgetxattr (path, p, buf, bytes_read);
rpm-build c487f7
      if (r < 0)
rpm-build c487f7
        {
rpm-build c487f7
          if (errno == ERANGE)
rpm-build c487f7
            {
rpm-build c487f7
              g_free (g_steal_pointer (&buf));
rpm-build c487f7
              goto again;
rpm-build c487f7
            }
rpm-build c487f7
          else if (errno == ENODATA)
rpm-build c487f7
            continue;
rpm-build c487f7
rpm-build c487f7
          glnx_set_prefix_error_from_errno (error, "%s", funcstr);
rpm-build c487f7
          goto out;
rpm-build c487f7
        }
rpm-build c487f7
rpm-build c487f7
      bytes = g_bytes_new_take (g_steal_pointer (&buf), bytes_read);
rpm-build c487f7
      g_variant_builder_add (builder, "(@ay@ay)",
rpm-build c487f7
                             g_variant_new_bytestring (p),
rpm-build c487f7
                             variant_new_ay_bytes (bytes));
rpm-build c487f7
    }
rpm-build c487f7
rpm-build c487f7
  ret = TRUE;
rpm-build c487f7
 out:
rpm-build c487f7
  return ret;
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
static gboolean
rpm-build c487f7
get_xattrs_impl (const char      *path,
rpm-build c487f7
                 int              fd,
rpm-build c487f7
                 GVariant       **out_xattrs,
rpm-build c487f7
                 GCancellable    *cancellable,
rpm-build c487f7
                 GError         **error)
rpm-build c487f7
{
rpm-build c487f7
  gboolean ret = FALSE;
rpm-build c487f7
  ssize_t bytes_read, real_size;
rpm-build c487f7
  g_autofree char *xattr_names = NULL;
rpm-build c487f7
  g_autofree char *xattr_names_canonical = NULL;
rpm-build c487f7
  GVariantBuilder builder;
rpm-build c487f7
  gboolean builder_initialized = FALSE;
rpm-build c487f7
  g_autoptr(GVariant) ret_xattrs = NULL;
rpm-build c487f7
rpm-build c487f7
  g_assert (path != NULL || fd != -1);
rpm-build c487f7
rpm-build c487f7
  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
rpm-build c487f7
  builder_initialized = TRUE;
rpm-build c487f7
rpm-build c487f7
 again:
rpm-build c487f7
  if (path)
rpm-build c487f7
    bytes_read = llistxattr (path, NULL, 0);
rpm-build c487f7
  else
rpm-build c487f7
    bytes_read = flistxattr (fd, NULL, 0);
rpm-build c487f7
rpm-build c487f7
  if (bytes_read < 0)
rpm-build c487f7
    {
rpm-build c487f7
      if (errno != ENOTSUP)
rpm-build c487f7
        {
rpm-build c487f7
          glnx_set_prefix_error_from_errno (error, "%s", "llistxattr");
rpm-build c487f7
          goto out;
rpm-build c487f7
        }
rpm-build c487f7
    }
rpm-build c487f7
  else if (bytes_read > 0)
rpm-build c487f7
    {
rpm-build c487f7
      xattr_names = g_malloc (bytes_read);
rpm-build c487f7
      if (path)
rpm-build c487f7
        real_size = llistxattr (path, xattr_names, bytes_read);
rpm-build c487f7
      else
rpm-build c487f7
        real_size = flistxattr (fd, xattr_names, bytes_read);
rpm-build c487f7
      if (real_size < 0)
rpm-build c487f7
        {
rpm-build c487f7
          if (errno == ERANGE)
rpm-build c487f7
            {
rpm-build c487f7
              g_free (xattr_names);
rpm-build c487f7
              goto again;
rpm-build c487f7
            }
rpm-build c487f7
          glnx_set_prefix_error_from_errno (error, "%s", "llistxattr");
rpm-build c487f7
          goto out;
rpm-build c487f7
        }
rpm-build c487f7
      else if (real_size > 0)
rpm-build c487f7
        {
rpm-build c487f7
          xattr_names_canonical = canonicalize_xattrs (xattr_names, real_size);
rpm-build c487f7
rpm-build c487f7
          if (!read_xattr_name_array (path, fd, xattr_names_canonical, real_size, &builder, error))
rpm-build c487f7
            goto out;
rpm-build c487f7
        }
rpm-build c487f7
    }
rpm-build c487f7
rpm-build c487f7
  ret_xattrs = g_variant_builder_end (&builder);
rpm-build c487f7
  builder_initialized = FALSE;
rpm-build c487f7
  g_variant_ref_sink (ret_xattrs);
rpm-build c487f7
  
rpm-build c487f7
  ret = TRUE;
rpm-build c487f7
  if (out_xattrs)
rpm-build c487f7
    *out_xattrs = g_steal_pointer (&ret_xattrs);
rpm-build c487f7
 out:
rpm-build c487f7
  if (!builder_initialized)
rpm-build c487f7
    g_variant_builder_clear (&builder);
rpm-build c487f7
  return ret;
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
/**
rpm-build c487f7
 * glnx_fd_get_all_xattrs:
rpm-build c487f7
 * @fd: a file descriptor
rpm-build c487f7
 * @out_xattrs: (out): A new #GVariant containing the extended attributes
rpm-build c487f7
 * @cancellable: Cancellable
rpm-build c487f7
 * @error: Error
rpm-build c487f7
 *
rpm-build c487f7
 * Read all extended attributes from @fd in a canonical sorted order, and
rpm-build c487f7
 * set @out_xattrs with the result.
rpm-build c487f7
 *
rpm-build c487f7
 * If the filesystem does not support extended attributes, @out_xattrs
rpm-build c487f7
 * will have 0 elements, and this function will return successfully.
rpm-build c487f7
 */
rpm-build c487f7
gboolean
rpm-build c487f7
glnx_fd_get_all_xattrs (int            fd,
rpm-build c487f7
                        GVariant     **out_xattrs,
rpm-build c487f7
                        GCancellable  *cancellable,
rpm-build c487f7
                        GError       **error)
rpm-build c487f7
{
rpm-build c487f7
  return get_xattrs_impl (NULL, fd, out_xattrs,
rpm-build c487f7
                          cancellable, error);
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
/**
rpm-build c487f7
 * glnx_dfd_name_get_all_xattrs:
rpm-build c487f7
 * @dfd: Parent directory file descriptor
rpm-build c487f7
 * @name: File name
rpm-build c487f7
 * @out_xattrs: (out): Extended attribute set
rpm-build c487f7
 * @cancellable: Cancellable
rpm-build c487f7
 * @error: Error
rpm-build c487f7
 *
rpm-build c487f7
 * Load all extended attributes for the file named @name residing in
rpm-build c487f7
 * directory @dfd.
rpm-build c487f7
 */
rpm-build c487f7
gboolean
rpm-build c487f7
glnx_dfd_name_get_all_xattrs (int            dfd,
rpm-build c487f7
                              const char    *name,
rpm-build c487f7
                              GVariant     **out_xattrs,
rpm-build c487f7
                              GCancellable  *cancellable,
rpm-build c487f7
                              GError       **error)
rpm-build c487f7
{
rpm-build c487f7
  if (G_IN_SET(dfd, AT_FDCWD, -1))
rpm-build c487f7
    {
rpm-build c487f7
      return get_xattrs_impl (name, -1, out_xattrs, cancellable, error);
rpm-build c487f7
    }
rpm-build c487f7
  else
rpm-build c487f7
    {
rpm-build c487f7
      char buf[PATH_MAX];
rpm-build c487f7
      /* A workaround for the lack of lgetxattrat(), thanks to Florian Weimer:
rpm-build c487f7
       * https://mail.gnome.org/archives/ostree-list/2014-February/msg00017.html
rpm-build c487f7
       */
rpm-build c487f7
      snprintf (buf, sizeof (buf), "/proc/self/fd/%d/%s", dfd, name);
rpm-build c487f7
      return get_xattrs_impl (buf, -1, out_xattrs, cancellable, error);
rpm-build c487f7
    }
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
static gboolean
rpm-build c487f7
set_all_xattrs_for_path (const char    *path,
rpm-build c487f7
                         GVariant      *xattrs,
rpm-build c487f7
                         GCancellable  *cancellable,
rpm-build c487f7
                         GError       **error)
rpm-build c487f7
{
rpm-build c487f7
  const guint n = g_variant_n_children (xattrs);
rpm-build c487f7
  for (guint i = 0; i < n; i++)
rpm-build c487f7
    {
rpm-build c487f7
      const guint8* name;
rpm-build c487f7
      g_autoptr(GVariant) value = NULL;
rpm-build c487f7
      g_variant_get_child (xattrs, i, "(^&ay@ay)",
rpm-build c487f7
                           &name, &value);
rpm-build c487f7
rpm-build c487f7
      gsize value_len;
rpm-build c487f7
      const guint8* value_data = g_variant_get_fixed_array (value, &value_len, 1);
rpm-build c487f7
rpm-build c487f7
      if (lsetxattr (path, (char*)name, (char*)value_data, value_len, 0) < 0)
rpm-build c487f7
        return glnx_throw_errno_prefix (error, "lsetxattr");
rpm-build c487f7
    }
rpm-build c487f7
rpm-build c487f7
  return TRUE;
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
/**
rpm-build c487f7
 * glnx_dfd_name_set_all_xattrs:
rpm-build c487f7
 * @dfd: Parent directory file descriptor
rpm-build c487f7
 * @name: File name
rpm-build c487f7
 * @xattrs: Extended attribute set
rpm-build c487f7
 * @cancellable: Cancellable
rpm-build c487f7
 * @error: Error
rpm-build c487f7
 *
rpm-build c487f7
 * Set all extended attributes for the file named @name residing in
rpm-build c487f7
 * directory @dfd.
rpm-build c487f7
 */
rpm-build c487f7
gboolean
rpm-build c487f7
glnx_dfd_name_set_all_xattrs (int            dfd,
rpm-build c487f7
                              const char    *name,
rpm-build c487f7
                              GVariant      *xattrs,
rpm-build c487f7
                              GCancellable  *cancellable,
rpm-build c487f7
                              GError       **error)
rpm-build c487f7
{
rpm-build c487f7
  if (G_IN_SET(dfd, AT_FDCWD, -1))
rpm-build c487f7
    {
rpm-build c487f7
      return set_all_xattrs_for_path (name, xattrs, cancellable, error);
rpm-build c487f7
    }
rpm-build c487f7
  else
rpm-build c487f7
    {
rpm-build c487f7
      char buf[PATH_MAX];
rpm-build c487f7
      /* A workaround for the lack of lsetxattrat(), thanks to Florian Weimer:
rpm-build c487f7
       * https://mail.gnome.org/archives/ostree-list/2014-February/msg00017.html
rpm-build c487f7
       */
rpm-build c487f7
      snprintf (buf, sizeof (buf), "/proc/self/fd/%d/%s", dfd, name);
rpm-build c487f7
      return set_all_xattrs_for_path (buf, xattrs, cancellable, error);
rpm-build c487f7
    }
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
/**
rpm-build c487f7
 * glnx_fd_set_all_xattrs:
rpm-build c487f7
 * @fd: File descriptor
rpm-build c487f7
 * @xattrs: Extended attributes
rpm-build c487f7
 * @cancellable: Cancellable
rpm-build c487f7
 * @error: Error
rpm-build c487f7
 *
rpm-build c487f7
 * For each attribute in @xattrs, set its value on the file or
rpm-build c487f7
 * directory referred to by @fd.  This function does not remove any
rpm-build c487f7
 * attributes not in @xattrs.
rpm-build c487f7
 */
rpm-build c487f7
gboolean
rpm-build c487f7
glnx_fd_set_all_xattrs (int            fd,
rpm-build c487f7
                        GVariant      *xattrs,
rpm-build c487f7
                        GCancellable  *cancellable,
rpm-build c487f7
                        GError       **error)
rpm-build c487f7
{
rpm-build c487f7
  const guint n = g_variant_n_children (xattrs);
rpm-build c487f7
  for (guint i = 0; i < n; i++)
rpm-build c487f7
    {
rpm-build c487f7
      const guint8* name;
rpm-build c487f7
      g_autoptr(GVariant) value = NULL;
rpm-build c487f7
      g_variant_get_child (xattrs, i, "(^&ay@ay)",
rpm-build c487f7
                           &name, &value);
rpm-build c487f7
rpm-build c487f7
      gsize value_len;
rpm-build c487f7
      const guint8* value_data = g_variant_get_fixed_array (value, &value_len, 1);
rpm-build c487f7
rpm-build c487f7
      if (TEMP_FAILURE_RETRY (fsetxattr (fd, (char*)name, (char*)value_data, value_len, 0)) < 0)
rpm-build c487f7
        return glnx_throw_errno_prefix (error, "fsetxattr");
rpm-build c487f7
    }
rpm-build c487f7
rpm-build c487f7
  return TRUE;
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
/**
rpm-build c487f7
 * glnx_lgetxattrat:
rpm-build c487f7
 * @dfd: Directory file descriptor
rpm-build c487f7
 * @subpath: Subpath
rpm-build c487f7
 * @attribute: Extended attribute to retrieve
rpm-build c487f7
 * @error: Error
rpm-build c487f7
 *
rpm-build c487f7
 * Retrieve an extended attribute value, relative to a directory file
rpm-build c487f7
 * descriptor.
rpm-build c487f7
 */
rpm-build c487f7
GBytes *
rpm-build c487f7
glnx_lgetxattrat (int            dfd,
rpm-build c487f7
                  const char    *subpath,
rpm-build c487f7
                  const char    *attribute,
rpm-build c487f7
                  GError       **error)
rpm-build c487f7
{
rpm-build c487f7
  char pathbuf[PATH_MAX];
rpm-build c487f7
  snprintf (pathbuf, sizeof (pathbuf), "/proc/self/fd/%d/%s", dfd, subpath);
rpm-build c487f7
rpm-build c487f7
  ssize_t bytes_read, real_size;
rpm-build c487f7
  if (TEMP_FAILURE_RETRY (bytes_read = lgetxattr (pathbuf, attribute, NULL, 0)) < 0)
rpm-build c487f7
    return glnx_null_throw_errno_prefix (error, "lgetxattr");
rpm-build c487f7
rpm-build c487f7
  g_autofree guint8 *buf = g_malloc (bytes_read);
rpm-build c487f7
  if (TEMP_FAILURE_RETRY (real_size = lgetxattr (pathbuf, attribute, buf, bytes_read)) < 0)
rpm-build c487f7
    return glnx_null_throw_errno_prefix (error, "lgetxattr");
rpm-build c487f7
rpm-build c487f7
  return g_bytes_new_take (g_steal_pointer (&buf), real_size);
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
/**
rpm-build c487f7
 * glnx_fgetxattr_bytes:
rpm-build c487f7
 * @fd: Directory file descriptor
rpm-build c487f7
 * @attribute: Extended attribute to retrieve
rpm-build c487f7
 * @error: Error
rpm-build c487f7
 *
rpm-build c487f7
 * Returns: (transfer full): An extended attribute value, or %NULL on error
rpm-build c487f7
 */
rpm-build c487f7
GBytes *
rpm-build c487f7
glnx_fgetxattr_bytes (int            fd,
rpm-build c487f7
                      const char    *attribute,
rpm-build c487f7
                      GError       **error)
rpm-build c487f7
{
rpm-build c487f7
  ssize_t bytes_read, real_size;
rpm-build c487f7
rpm-build c487f7
  if (TEMP_FAILURE_RETRY (bytes_read = fgetxattr (fd, attribute, NULL, 0)) < 0)
rpm-build c487f7
    return glnx_null_throw_errno_prefix (error, "fgetxattr");
rpm-build c487f7
rpm-build c487f7
  g_autofree guint8 *buf = g_malloc (bytes_read);
rpm-build c487f7
  if (TEMP_FAILURE_RETRY (real_size = fgetxattr (fd, attribute, buf, bytes_read)) < 0)
rpm-build c487f7
    return glnx_null_throw_errno_prefix (error, "fgetxattr");
rpm-build c487f7
rpm-build c487f7
  return g_bytes_new_take (g_steal_pointer (&buf), real_size);
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
/**
rpm-build c487f7
 * glnx_lsetxattrat:
rpm-build c487f7
 * @dfd: Directory file descriptor
rpm-build c487f7
 * @subpath: Path
rpm-build c487f7
 * @attribute: An attribute name
rpm-build c487f7
 * @value: (array length=len) (element-type guint8): Attribute value
rpm-build c487f7
 * @len: Length of @value
rpm-build c487f7
 * @flags: Flags, containing either XATTR_CREATE or XATTR_REPLACE
rpm-build c487f7
 * @error: Error
rpm-build c487f7
 *
rpm-build c487f7
 * Set an extended attribute, relative to a directory file descriptor.
rpm-build c487f7
 */
rpm-build c487f7
gboolean
rpm-build c487f7
glnx_lsetxattrat (int            dfd,
rpm-build c487f7
                  const char    *subpath,
rpm-build c487f7
                  const char    *attribute,
rpm-build c487f7
                  const guint8  *value,
rpm-build c487f7
                  gsize          len,
rpm-build c487f7
                  int            flags,
rpm-build c487f7
                  GError       **error)
rpm-build c487f7
{
rpm-build c487f7
  char pathbuf[PATH_MAX];
rpm-build c487f7
  snprintf (pathbuf, sizeof (pathbuf), "/proc/self/fd/%d/%s", dfd, subpath);
rpm-build c487f7
rpm-build c487f7
  if (TEMP_FAILURE_RETRY (lsetxattr (subpath, attribute, value, len, flags)) < 0)
rpm-build c487f7
    return glnx_throw_errno_prefix (error, "lsetxattr");
rpm-build c487f7
rpm-build c487f7
  return TRUE;
rpm-build c487f7
}
rpm-build c487f7