ostree/libglnx/glnx-shutil.c

249 lines
6.3 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>.
*
* 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 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <string.h>
#include <glnx-shutil.h>
#include <glnx-errors.h>
#include <glnx-local-alloc.h>
static gboolean
glnx_shutil_rm_rf_children (GLnxDirFdIterator *dfd_iter,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
struct dirent *dent;
while (TRUE)
{
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (dfd_iter, &dent, cancellable, error))
goto out;
if (dent == NULL)
break;
if (dent->d_type == DT_DIR)
{
g_auto(GLnxDirFdIterator) child_dfd_iter = { 0, };
if (!glnx_dirfd_iterator_init_at (dfd_iter->fd, dent->d_name, FALSE,
&child_dfd_iter, error))
goto out;
if (!glnx_shutil_rm_rf_children (&child_dfd_iter, cancellable, error))
goto out;
if (unlinkat (dfd_iter->fd, dent->d_name, AT_REMOVEDIR) == -1)
{
glnx_set_error_from_errno (error);
goto out;
}
}
else
{
if (unlinkat (dfd_iter->fd, dent->d_name, 0) == -1)
{
if (errno != ENOENT)
{
glnx_set_error_from_errno (error);
goto out;
}
}
}
}
ret = TRUE;
out:
return ret;
}
/**
* glnx_shutil_rm_rf_at:
* @dfd: A directory file descriptor, or `AT_FDCWD` or `-1` for current
* @path: Path
* @cancellable: Cancellable
* @error: Error
*
* Recursively delete the filename referenced by the combination of
* the directory fd @dfd and @path; it may be a file or directory. No
* error is thrown if @path does not exist.
*/
gboolean
glnx_shutil_rm_rf_at (int dfd,
const char *path,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
glnx_fd_close int target_dfd = -1;
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
dfd = glnx_dirfd_canonicalize (dfd);
/* With O_NOFOLLOW first */
target_dfd = openat (dfd, path,
O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
if (target_dfd == -1)
{
int errsv = errno;
if (errsv == ENOENT)
{
;
}
else if (errsv == ENOTDIR || errsv == ELOOP)
{
if (unlinkat (dfd, path, 0) != 0)
{
glnx_set_error_from_errno (error);
goto out;
}
}
else
{
glnx_set_error_from_errno (error);
goto out;
}
}
else
{
if (!glnx_dirfd_iterator_init_take_fd (target_dfd, &dfd_iter, error))
goto out;
target_dfd = -1;
if (!glnx_shutil_rm_rf_children (&dfd_iter, cancellable, error))
goto out;
if (unlinkat (dfd, path, AT_REMOVEDIR) == -1)
{
int errsv = errno;
if (errsv != ENOENT)
{
glnx_set_error_from_errno (error);
goto out;
}
}
}
ret = TRUE;
out:
return ret;
}
static gboolean
mkdir_p_at_internal (int dfd,
char *path,
int mode,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
gboolean did_recurse = FALSE;
if (g_cancellable_set_error_if_cancelled (cancellable, error))
goto out;
again:
if (mkdirat (dfd, path, mode) == -1)
{
if (errno == ENOENT)
{
char *lastslash;
g_assert (!did_recurse);
lastslash = strrchr (path, '/');
g_assert (lastslash != NULL);
/* Note we can mutate the buffer as we dup'd it */
*lastslash = '\0';
if (!glnx_shutil_mkdir_p_at (dfd, path, mode,
cancellable, error))
goto out;
/* Now restore it for another mkdir attempt */
*lastslash = '/';
did_recurse = TRUE;
goto again;
}
else if (errno == EEXIST)
{
/* Fall through; it may not have been a directory,
* but we'll find that out on the next call up.
*/
}
else
{
glnx_set_error_from_errno (error);
goto out;
}
}
ret = TRUE;
out:
return ret;
}
/**
* glnx_shutil_mkdir_p_at:
* @dfd: Directory fd
* @path: Directory path to be created
* @mode: Mode for newly created directories
* @cancellable: Cancellable
* @error: Error
*
* Similar to g_mkdir_with_parents(), except operates relative to the
* directory fd @dfd.
*/
gboolean
glnx_shutil_mkdir_p_at (int dfd,
const char *path,
int mode,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
struct stat stbuf;
char *buf;
/* Fast path stat to see whether it already exists */
if (fstatat (dfd, path, &stbuf, AT_SYMLINK_NOFOLLOW) == 0)
{
if (S_ISDIR (stbuf.st_mode))
{
ret = TRUE;
goto out;
}
}
buf = strdupa (path);
if (!mkdir_p_at_internal (dfd, buf, mode, cancellable, error))
goto out;
ret = TRUE;
out:
return ret;
}