diff --git a/Makefile-libglnx.am b/Makefile-libglnx.am index 6340063c32a2903c7cd29e4a4640ca17bc7aaf97..462000133ae64631fc72217fa7a3649dbff8ec67 100644 --- a/Makefile-libglnx.am +++ b/Makefile-libglnx.am @@ -20,6 +20,14 @@ EXTRA_DIST += $(libglnx_srcpath)/README $(libglnx_srcpath)/COPYING libglnx_la_SOURCES = \ $(libglnx_srcpath)/glnx-backport-autocleanups.h \ $(libglnx_srcpath)/glnx-backport-autoptr.h \ + $(libglnx_srcpath)/glnx-local-alloc.h \ + $(libglnx_srcpath)/glnx-local-alloc.c \ + $(libglnx_srcpath)/glnx-errors.h \ + $(libglnx_srcpath)/glnx-errors.c \ + $(libglnx_srcpath)/glnx-dirfd.h \ + $(libglnx_srcpath)/glnx-dirfd.c \ + $(libglnx_srcpath)/glnx-shutil.h \ + $(libglnx_srcpath)/glnx-shutil.c \ $(libglnx_srcpath)/libglnx.h \ $(NULL) diff --git a/glnx-backport-autocleanups.h b/glnx-backport-autocleanups.h index 05ac49ee9875318beaaf9c82d40b69457f296696..8fd9b9c1020e1f8fe95eda973d098da8040fe208 100644 --- a/glnx-backport-autocleanups.h +++ b/glnx-backport-autocleanups.h @@ -21,7 +21,7 @@ #include <glnx-backport-autoptr.h> -#if !GLIB_CHECK_VERSION(2, 44, 0) +#if !GLIB_CHECK_VERSION(2, 43, 3) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GAsyncQueue, g_async_queue_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GBookmarkFile, g_bookmark_file_free) diff --git a/glnx-backport-autoptr.h b/glnx-backport-autoptr.h index de611b7fd7eb976c329bbf6e6661d8c8f4763bf1..fd5f5d0133b2451927de4065c4666c7bb07c7720 100644 --- a/glnx-backport-autoptr.h +++ b/glnx-backport-autoptr.h @@ -27,7 +27,7 @@ G_BEGIN_DECLS -#if !GLIB_CHECK_VERSION(2, 44, 0) +#if !GLIB_CHECK_VERSION(2, 43, 4) #define _GLIB_AUTOPTR_FUNC_NAME(TypeName) glib_autoptr_cleanup_##TypeName #define _GLIB_AUTOPTR_TYPENAME(TypeName) TypeName##_autoptr @@ -127,6 +127,6 @@ static inline gpointer #define g_steal_pointer(pp) \ (0 ? (*(pp)) : (g_steal_pointer) (pp)) -#endif /* !GLIB_CHECK_VERSION(2, 44, 0) */ +#endif /* !GLIB_CHECK_VERSION(2, 43, 3) */ G_END_DECLS diff --git a/glnx-dirfd.c b/glnx-dirfd.c new file mode 100644 index 0000000000000000000000000000000000000000..8bc288b0351fae2ef07510f28dd162f860c6c480 --- /dev/null +++ b/glnx-dirfd.c @@ -0,0 +1,165 @@ +/* -*- 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-dirfd.h> +#include <glnx-errors.h> +#include <glnx-local-alloc.h> + +/** + * glnx_opendirat_with_errno: + * @dfd: File descriptor for origin directory + * @name: Pathname, relative to @dfd + * @follow: Whether or not to follow symbolic links + * + * Use openat() to open a directory, using a standard set of flags. + * This function sets errno. + */ +int +glnx_opendirat_with_errno (int dfd, + const char *path, + gboolean follow) +{ + int flags = O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY; + if (!follow) + flags |= O_NOFOLLOW; + return openat (dfd, path, flags); +} + +/** + * glnx_opendirat: + * @dfd: File descriptor for origin directory + * @path: Pathname, relative to @dfd + * @follow: Whether or not to follow symbolic links + * @error: Error + * + * Use openat() to open a directory, using a standard set of flags. + */ +gboolean +glnx_opendirat (int dfd, + const char *path, + gboolean follow, + int *out_fd, + GError **error) +{ + int ret = glnx_opendirat_with_errno (dfd, path, follow); + if (ret == -1) + { + glnx_set_prefix_error_from_errno (error, "%s", "openat"); + return FALSE; + } + *out_fd = ret; + return TRUE; +} + +struct GLnxRealDirfdIterator +{ + gboolean initialized; + int fd; + DIR *d; +}; +typedef struct GLnxRealDirfdIterator GLnxRealDirfdIterator; + +gboolean +glnx_dirfd_iterator_init_at (int dfd, + const char *path, + gboolean follow, + GLnxDirFdIterator *dfd_iter, + GError **error) +{ + gboolean ret = FALSE; + glnx_fd_close int fd = -1; + + if (!glnx_opendirat (dfd, path, follow, &fd, error)) + goto out; + + if (!glnx_dirfd_iterator_init_take_fd (fd, dfd_iter, error)) + goto out; + fd = -1; /* Transfer ownership */ + + ret = TRUE; + out: + return ret; +} + +gboolean +glnx_dirfd_iterator_init_take_fd (int dfd, + GLnxDirFdIterator *dfd_iter, + GError **error) +{ + gboolean ret = FALSE; + GLnxRealDirfdIterator *real_dfd_iter = (GLnxRealDirfdIterator*) dfd_iter; + DIR *d = NULL; + + d = fdopendir (dfd); + if (!d) + { + glnx_set_prefix_error_from_errno (error, "%s", "fdopendir"); + goto out; + } + + real_dfd_iter->fd = dfd; + real_dfd_iter->d = d; + + ret = TRUE; + out: + return ret; +} + +gboolean +glnx_dirfd_iterator_next_dent (GLnxDirFdIterator *dfd_iter, + struct dirent **out_dent, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GLnxRealDirfdIterator *real_dfd_iter = (GLnxRealDirfdIterator*) dfd_iter; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto out; + + do + { + errno = 0; + *out_dent = readdir (real_dfd_iter->d); + if (*out_dent == NULL && errno != 0) + { + glnx_set_prefix_error_from_errno (error, "%s", "fdopendir"); + goto out; + } + } while (*out_dent && + (strcmp ((*out_dent)->d_name, ".") == 0 || + strcmp ((*out_dent)->d_name, "..") == 0)); + + ret = TRUE; + out: + return ret; +} + +void +glnx_dirfd_iterator_clear (GLnxDirFdIterator *dfd_iter) +{ + GLnxRealDirfdIterator *real_dfd_iter = (GLnxRealDirfdIterator*) dfd_iter; + /* fd is owned by dfd_iter */ + (void) closedir (real_dfd_iter->d); +} diff --git a/glnx-dirfd.h b/glnx-dirfd.h new file mode 100644 index 0000000000000000000000000000000000000000..ccb6aa2f30e2c5d68d840d1b98407edba943a76a --- /dev/null +++ b/glnx-dirfd.h @@ -0,0 +1,60 @@ +/* -*- 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. + */ + +#pragma once + +#include <glnx-backport-autoptr.h> +#include <limits.h> +#include <dirent.h> +#include <sys/stat.h> +#include <fcntl.h> + +G_BEGIN_DECLS + +struct GLnxDirFdIterator { + gboolean initialized; + int fd; + gpointer padding_data[4]; +}; + +typedef struct GLnxDirFdIterator GLnxDirFdIterator; +gboolean glnx_dirfd_iterator_init_at (int dfd, const char *path, + gboolean follow, + GLnxDirFdIterator *dfd_iter, GError **error); +gboolean glnx_dirfd_iterator_init_take_fd (int dfd, GLnxDirFdIterator *dfd_iter, GError **error); +gboolean glnx_dirfd_iterator_next_dent (GLnxDirFdIterator *dfd_iter, + struct dirent **out_dent, + GCancellable *cancellable, + GError **error); +void glnx_dirfd_iterator_clear (GLnxDirFdIterator *dfd_iter); + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GLnxDirFdIterator, glnx_dirfd_iterator_clear) + +int glnx_opendirat_with_errno (int dfd, + const char *path, + gboolean follow); + +gboolean glnx_opendirat (int dfd, + const char *path, + gboolean follow, + int *out_fd, + GError **error); + +G_END_DECLS diff --git a/glnx-errors.c b/glnx-errors.c new file mode 100644 index 0000000000000000000000000000000000000000..3cbda7e41ff20ff4d216f1c42592e8dc31399dd4 --- /dev/null +++ b/glnx-errors.c @@ -0,0 +1,53 @@ +/* -*- 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 <glnx-backport-autoptr.h> +#include <glnx-errors.h> + +void +glnx_real_set_prefix_error_from_errno (GError **error, + gint errsv, + const char *format, + ...) +{ + if (!error) + return; + else + { + GString *buf = g_string_new (""); + va_list args; + + va_start (args, format); + g_string_append_vprintf (buf, format, args); + va_end (args); + + g_string_append (buf, ": "); + g_string_append (buf, g_strerror (errsv)); + + g_set_error_literal (error, + G_IO_ERROR, + g_io_error_from_errno (errsv), + buf->str); + g_string_free (buf, TRUE); + errno = errsv; + } +} diff --git a/glnx-errors.h b/glnx-errors.h new file mode 100644 index 0000000000000000000000000000000000000000..e51cd7e8a25bb528d3c1b684947879eddebefe1d --- /dev/null +++ b/glnx-errors.h @@ -0,0 +1,49 @@ +/* -*- 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. + */ + +#pragma once + +#include <glnx-backport-autoptr.h> +#include <errno.h> + +G_BEGIN_DECLS + +#define glnx_set_error_from_errno(error) \ + do { \ + int errsv = errno; \ + g_set_error_literal (error, G_IO_ERROR, \ + g_io_error_from_errno (errsv), \ + g_strerror (errsv)); \ + errno = errsv; \ + } while (0); + +#define glnx_set_prefix_error_from_errno(error, format, args...) \ + do { \ + int errsv = errno; \ + glnx_real_set_prefix_error_from_errno (error, errsv, format, args); \ + errno = errsv; \ + } while (0); + +void glnx_real_set_prefix_error_from_errno (GError **error, + gint errsv, + const char *format, + ...) G_GNUC_PRINTF (3,4); + +G_END_DECLS diff --git a/glnx-shutil.c b/glnx-shutil.c new file mode 100644 index 0000000000000000000000000000000000000000..9fcb65a0617f2649cdec95492c30b3f93f6137f0 --- /dev/null +++ b/glnx-shutil.c @@ -0,0 +1,188 @@ +/* -*- 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 unsigned char +struct_stat_to_dt (struct stat *stbuf) +{ + if (S_ISDIR (stbuf->st_mode)) + return DT_DIR; + if (S_ISREG (stbuf->st_mode)) + return DT_REG; + if (S_ISCHR (stbuf->st_mode)) + return DT_CHR; + if (S_ISBLK (stbuf->st_mode)) + return DT_BLK; + if (S_ISFIFO (stbuf->st_mode)) + return DT_FIFO; + if (S_ISLNK (stbuf->st_mode)) + return DT_LNK; + if (S_ISSOCK (stbuf->st_mode)) + return DT_SOCK; + return DT_UNKNOWN; +} + +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 (dfd_iter, &dent, cancellable, error)) + goto out; + + if (dent == NULL) + break; + + if (dent->d_type == DT_UNKNOWN) + { + struct stat stbuf; + if (fstatat (dfd_iter->fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1) + { + if (errno == ENOENT) + continue; + else + { + glnx_set_error_from_errno (error); + goto out; + } + } + dent->d_type = struct_stat_to_dt (&stbuf); + /* Assume unknown types are just treated like regular files */ + if (dent->d_type == DT_UNKNOWN) + dent->d_type = DT_REG; + } + + 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 -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, }; + + /* 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; +} diff --git a/glnx-shutil.h b/glnx-shutil.h new file mode 100644 index 0000000000000000000000000000000000000000..0c53fd86de0289b137644206d235134a6e28e272 --- /dev/null +++ b/glnx-shutil.h @@ -0,0 +1,33 @@ +/* -*- 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. + */ + +#pragma once + +#include <glnx-dirfd.h> + +G_BEGIN_DECLS + +gboolean +glnx_shutil_rm_rf_at (int dfd, + const char *path, + GCancellable *cancellable, + GError **error); + +G_END_DECLS diff --git a/libglnx.h b/libglnx.h index 12881eb5f7c4164e620fb5d90d8bf7b27e82e461..294b90447392159f2099d27c8744afae82042ed8 100644 --- a/libglnx.h +++ b/libglnx.h @@ -27,5 +27,8 @@ G_BEGIN_DECLS #include <glnx-local-alloc.h> #include <glnx-backport-autoptr.h> #include <glnx-backport-autocleanups.h> +#include <glnx-errors.h> +#include <glnx-dirfd.h> +#include <glnx-shutil.h> G_END_DECLS