Blame libglnx/glnx-shutil.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
rpm-build c487f7
#include <glnx-shutil.h>
rpm-build c487f7
#include <glnx-errors.h>
rpm-build c487f7
#include <glnx-local-alloc.h>
rpm-build c487f7
rpm-build c487f7
static gboolean
rpm-build c487f7
glnx_shutil_rm_rf_children (GLnxDirFdIterator    *dfd_iter,
rpm-build c487f7
                            GCancellable       *cancellable,
rpm-build c487f7
                            GError            **error)
rpm-build c487f7
{
rpm-build c487f7
  struct dirent *dent;
rpm-build c487f7
rpm-build c487f7
  while (TRUE)
rpm-build c487f7
    {
rpm-build c487f7
      if (!glnx_dirfd_iterator_next_dent_ensure_dtype (dfd_iter, &dent, cancellable, error))
rpm-build c487f7
        return FALSE;
rpm-build c487f7
      if (dent == NULL)
rpm-build c487f7
        break;
rpm-build c487f7
rpm-build c487f7
      if (dent->d_type == DT_DIR)
rpm-build c487f7
        {
rpm-build c487f7
          g_auto(GLnxDirFdIterator) child_dfd_iter = { 0, };
rpm-build c487f7
rpm-build c487f7
          if (!glnx_dirfd_iterator_init_at (dfd_iter->fd, dent->d_name, FALSE,
rpm-build c487f7
                                            &child_dfd_iter, error))
rpm-build c487f7
            return FALSE;
rpm-build c487f7
rpm-build c487f7
          if (!glnx_shutil_rm_rf_children (&child_dfd_iter, cancellable, error))
rpm-build c487f7
            return FALSE;
rpm-build c487f7
rpm-build c487f7
          if (unlinkat (dfd_iter->fd, dent->d_name, AT_REMOVEDIR) == -1)
rpm-build c487f7
            return glnx_throw_errno_prefix (error, "unlinkat");
rpm-build c487f7
        }
rpm-build c487f7
      else
rpm-build c487f7
        {
rpm-build c487f7
          if (unlinkat (dfd_iter->fd, dent->d_name, 0) == -1)
rpm-build c487f7
            {
rpm-build c487f7
              if (errno != ENOENT)
rpm-build c487f7
                return glnx_throw_errno_prefix (error, "unlinkat");
rpm-build c487f7
            }
rpm-build c487f7
        }
rpm-build c487f7
    }
rpm-build c487f7
rpm-build c487f7
  return TRUE;
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
/**
rpm-build c487f7
 * glnx_shutil_rm_rf_at:
rpm-build c487f7
 * @dfd: A directory file descriptor, or `AT_FDCWD` or `-1` for current
rpm-build c487f7
 * @path: Path
rpm-build c487f7
 * @cancellable: Cancellable
rpm-build c487f7
 * @error: Error
rpm-build c487f7
 *
rpm-build c487f7
 * Recursively delete the filename referenced by the combination of
rpm-build c487f7
 * the directory fd @dfd and @path; it may be a file or directory.  No
rpm-build c487f7
 * error is thrown if @path does not exist.
rpm-build c487f7
 */
rpm-build c487f7
gboolean
rpm-build c487f7
glnx_shutil_rm_rf_at (int                   dfd,
rpm-build c487f7
                      const char           *path,
rpm-build c487f7
                      GCancellable         *cancellable,
rpm-build c487f7
                      GError              **error)
rpm-build c487f7
{
rpm-build c487f7
  dfd = glnx_dirfd_canonicalize (dfd);
rpm-build c487f7
rpm-build c487f7
rpm-build c487f7
  /* With O_NOFOLLOW first */
rpm-build c487f7
  glnx_autofd int target_dfd =
rpm-build c487f7
    openat (dfd, path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
rpm-build c487f7
rpm-build c487f7
  if (target_dfd == -1)
rpm-build c487f7
    {
rpm-build c487f7
      int errsv = errno;
rpm-build c487f7
      if (errsv == ENOENT)
rpm-build c487f7
        {
rpm-build c487f7
          ;
rpm-build c487f7
        }
rpm-build c487f7
      else if (errsv == ENOTDIR || errsv == ELOOP)
rpm-build c487f7
        {
rpm-build c487f7
          if (unlinkat (dfd, path, 0) != 0)
rpm-build c487f7
            return glnx_throw_errno_prefix (error, "unlinkat");
rpm-build c487f7
        }
rpm-build c487f7
      else
rpm-build c487f7
        return glnx_throw_errno_prefix (error, "open(%s)", path);
rpm-build c487f7
    }
rpm-build c487f7
  else
rpm-build c487f7
    {
rpm-build c487f7
      g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
rpm-build c487f7
      if (!glnx_dirfd_iterator_init_take_fd (&target_dfd, &dfd_iter, error))
rpm-build c487f7
        return FALSE;
rpm-build c487f7
rpm-build c487f7
      if (!glnx_shutil_rm_rf_children (&dfd_iter, cancellable, error))
rpm-build c487f7
        return FALSE;
rpm-build c487f7
rpm-build c487f7
      if (unlinkat (dfd, path, AT_REMOVEDIR) == -1)
rpm-build c487f7
        {
rpm-build c487f7
          if (errno != ENOENT)
rpm-build c487f7
            return glnx_throw_errno_prefix (error, "unlinkat");
rpm-build c487f7
        }
rpm-build c487f7
    }
rpm-build c487f7
rpm-build c487f7
  return TRUE;
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
static gboolean
rpm-build c487f7
mkdir_p_at_internal (int              dfd,
rpm-build c487f7
                     char            *path,
rpm-build c487f7
                     int              mode,
rpm-build c487f7
                     GCancellable    *cancellable,
rpm-build c487f7
                     GError         **error)
rpm-build c487f7
{
rpm-build c487f7
  gboolean did_recurse = FALSE;
rpm-build c487f7
rpm-build c487f7
  if (g_cancellable_set_error_if_cancelled (cancellable, error))
rpm-build c487f7
    return FALSE;
rpm-build c487f7
rpm-build c487f7
 again:
rpm-build c487f7
  if (mkdirat (dfd, path, mode) == -1)
rpm-build c487f7
    {
rpm-build c487f7
      if (errno == ENOENT)
rpm-build c487f7
        {
rpm-build c487f7
          char *lastslash;
rpm-build c487f7
rpm-build c487f7
          g_assert (!did_recurse);
rpm-build c487f7
rpm-build c487f7
          lastslash = strrchr (path, '/');
rpm-build c487f7
          if (lastslash == NULL)
rpm-build c487f7
            {
rpm-build c487f7
              /* This can happen if @dfd was deleted between being opened and
rpm-build c487f7
               * passed to mkdir_p_at_internal(). */
rpm-build c487f7
              return glnx_throw_errno_prefix (error, "mkdir(%s)", path);
rpm-build c487f7
            }
rpm-build c487f7
rpm-build c487f7
          /* Note we can mutate the buffer as we dup'd it */
rpm-build c487f7
          *lastslash = '\0';
rpm-build c487f7
rpm-build c487f7
          if (!glnx_shutil_mkdir_p_at (dfd, path, mode,
rpm-build c487f7
                                       cancellable, error))
rpm-build c487f7
            return FALSE;
rpm-build c487f7
rpm-build c487f7
          /* Now restore it for another mkdir attempt */
rpm-build c487f7
          *lastslash = '/';
rpm-build c487f7
rpm-build c487f7
          did_recurse = TRUE;
rpm-build c487f7
          goto again;
rpm-build c487f7
        }
rpm-build c487f7
      else if (errno == EEXIST)
rpm-build c487f7
        {
rpm-build c487f7
          /* Fall through; it may not have been a directory,
rpm-build c487f7
           * but we'll find that out on the next call up.
rpm-build c487f7
           */
rpm-build c487f7
        }
rpm-build c487f7
      else
rpm-build c487f7
        return glnx_throw_errno_prefix (error, "mkdir(%s)", path);
rpm-build c487f7
    }
rpm-build c487f7
rpm-build c487f7
  return TRUE;
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
/**
rpm-build c487f7
 * glnx_shutil_mkdir_p_at:
rpm-build c487f7
 * @dfd: Directory fd
rpm-build c487f7
 * @path: Directory path to be created
rpm-build c487f7
 * @mode: Mode for newly created directories
rpm-build c487f7
 * @cancellable: Cancellable
rpm-build c487f7
 * @error: Error
rpm-build c487f7
 *
rpm-build c487f7
 * Similar to g_mkdir_with_parents(), except operates relative to the
rpm-build c487f7
 * directory fd @dfd.
rpm-build c487f7
 *
rpm-build c487f7
 * See also glnx_ensure_dir() for a non-recursive version.
rpm-build c487f7
 *
rpm-build c487f7
 * This will return %G_IO_ERROR_NOT_FOUND if @dfd has been deleted since being
rpm-build c487f7
 * opened. It may return other errors from mkdirat() in other situations.
rpm-build c487f7
 */
rpm-build c487f7
gboolean
rpm-build c487f7
glnx_shutil_mkdir_p_at (int                   dfd,
rpm-build c487f7
                        const char           *path,
rpm-build c487f7
                        int                   mode,
rpm-build c487f7
                        GCancellable         *cancellable,
rpm-build c487f7
                        GError              **error)
rpm-build c487f7
{
rpm-build c487f7
  struct stat stbuf;
rpm-build c487f7
  char *buf;
rpm-build c487f7
rpm-build c487f7
  /* Fast path stat to see whether it already exists */
rpm-build c487f7
  if (fstatat (dfd, path, &stbuf, AT_SYMLINK_NOFOLLOW) == 0)
rpm-build c487f7
    {
rpm-build c487f7
      /* Note early return */
rpm-build c487f7
      if (S_ISDIR (stbuf.st_mode))
rpm-build c487f7
        return TRUE;
rpm-build c487f7
    }
rpm-build c487f7
rpm-build c487f7
  buf = strdupa (path);
rpm-build c487f7
rpm-build c487f7
  if (!mkdir_p_at_internal (dfd, buf, mode, cancellable, error))
rpm-build c487f7
    return FALSE;
rpm-build c487f7
rpm-build c487f7
  return TRUE;
rpm-build c487f7
}
rpm-build c487f7
rpm-build c487f7
/**
rpm-build c487f7
 * glnx_shutil_mkdir_p_at_open:
rpm-build c487f7
 * @dfd: Directory fd
rpm-build c487f7
 * @path: Directory path to be created
rpm-build c487f7
 * @mode: Mode for newly created directories
rpm-build c487f7
 * @out_dfd: (out caller-allocates): Return location for an FD to @dfd/@path,
rpm-build c487f7
 *    or `-1` on error
rpm-build c487f7
 * @cancellable: (nullable): Cancellable, or %NULL
rpm-build c487f7
 * @error: Return location for a #GError, or %NULL
rpm-build c487f7
 *
rpm-build c487f7
 * Similar to glnx_shutil_mkdir_p_at(), except it opens the resulting directory
rpm-build c487f7
 * and returns a directory FD to it. Currently, this is not guaranteed to be
rpm-build c487f7
 * race-free.
rpm-build c487f7
 *
rpm-build c487f7
 * Returns: %TRUE on success, %FALSE otherwise
rpm-build c487f7
 * Since: UNRELEASED
rpm-build c487f7
 */
rpm-build c487f7
gboolean
rpm-build c487f7
glnx_shutil_mkdir_p_at_open (int            dfd,
rpm-build c487f7
                             const char    *path,
rpm-build c487f7
                             int            mode,
rpm-build c487f7
                             int           *out_dfd,
rpm-build c487f7
                             GCancellable  *cancellable,
rpm-build c487f7
                             GError       **error)
rpm-build c487f7
{
rpm-build c487f7
  /* FIXME: It’s not possible to eliminate the race here until
rpm-build c487f7
   * openat(O_DIRECTORY | O_CREAT) works (and returns a directory rather than a
rpm-build c487f7
   * file). It appears to be not supported in current kernels. (Tested with
rpm-build c487f7
   * 4.10.10-200.fc25.x86_64.) */
rpm-build c487f7
  *out_dfd = -1;
rpm-build c487f7
rpm-build c487f7
  if (!glnx_shutil_mkdir_p_at (dfd, path, mode, cancellable, error))
rpm-build c487f7
    return FALSE;
rpm-build c487f7
rpm-build c487f7
  return glnx_opendirat (dfd, path, TRUE, out_dfd, error);
rpm-build c487f7
}