Coverage Report

Created: 2026-06-07 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ostree/libglnx/glnx-shutil.c
Line
Count
Source
1
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2
 *
3
 * Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>.
4
 * SPDX-License-Identifier: LGPL-2.0-or-later
5
 *
6
 * This library is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2 of the License, or (at your option) any later version.
10
 *
11
 * This library is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 * Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with this library; if not, write to the
18
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19
 * Boston, MA 02111-1307, USA.
20
 */
21
22
#include "libglnx-config.h"
23
24
#include <string.h>
25
26
#include <glnx-shutil.h>
27
#include <glnx-errors.h>
28
#include <glnx-fdio.h>
29
#include <glnx-local-alloc.h>
30
31
static gboolean
32
unlinkat_allow_noent (int dfd,
33
                      const char *path,
34
                      int flags,
35
                      GError **error)
36
278
{
37
278
  if (unlinkat (dfd, path, flags) == -1)
38
0
    {
39
0
      if (errno != ENOENT)
40
0
        return glnx_throw_errno_prefix (error, "unlinkat(%s)", path);
41
0
    }
42
278
  return TRUE;
43
278
}
44
45
static gboolean
46
glnx_shutil_rm_rf_children (GLnxDirFdIterator    *dfd_iter,
47
                            GCancellable       *cancellable,
48
                            GError            **error)
49
1.39k
{
50
1.39k
  struct dirent *dent;
51
52
2.78k
  while (TRUE)
53
2.78k
    {
54
2.78k
      if (!glnx_dirfd_iterator_next_dent_ensure_dtype (dfd_iter, &dent, cancellable, error))
55
0
        return FALSE;
56
2.78k
      if (dent == NULL)
57
1.39k
        break;
58
59
1.39k
      if (dent->d_type == DT_DIR)
60
1.25k
        {
61
1.25k
          g_auto(GLnxDirFdIterator) child_dfd_iter = { 0, };
62
63
1.25k
          if (!glnx_dirfd_iterator_init_at (dfd_iter->fd, dent->d_name, FALSE,
64
1.25k
                                            &child_dfd_iter, error))
65
0
            return FALSE;
66
67
1.25k
          if (!glnx_shutil_rm_rf_children (&child_dfd_iter, cancellable, error))
68
0
            return FALSE;
69
70
1.25k
          if (!glnx_unlinkat (dfd_iter->fd, dent->d_name, AT_REMOVEDIR, error))
71
0
            return FALSE;
72
1.25k
        }
73
139
      else
74
139
        {
75
139
          if (!unlinkat_allow_noent (dfd_iter->fd, dent->d_name, 0, error))
76
0
            return FALSE;
77
139
        }
78
1.39k
    }
79
80
1.39k
  return TRUE;
81
1.39k
}
82
83
/**
84
 * glnx_shutil_rm_rf_at:
85
 * @dfd: A directory file descriptor, or `AT_FDCWD` or `-1` for current
86
 * @path: Path
87
 * @cancellable: Cancellable
88
 * @error: Error
89
 *
90
 * Recursively delete the filename referenced by the combination of
91
 * the directory fd @dfd and @path; it may be a file or directory.  No
92
 * error is thrown if @path does not exist.
93
 */
94
gboolean
95
glnx_shutil_rm_rf_at (int                   dfd,
96
                      const char           *path,
97
                      GCancellable         *cancellable,
98
                      GError              **error)
99
139
{
100
139
  dfd = glnx_dirfd_canonicalize (dfd);
101
102
  /* With O_NOFOLLOW first */
103
139
  glnx_autofd int target_dfd =
104
139
    openat (dfd, path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
105
106
139
  if (target_dfd == -1)
107
0
    {
108
0
      int errsv = errno;
109
0
      if (errsv == ENOENT)
110
0
        {
111
0
          ;
112
0
        }
113
0
      else if (errsv == ENOTDIR || errsv == ELOOP)
114
0
        {
115
0
          if (!glnx_unlinkat (dfd, path, 0, error))
116
0
            return FALSE;
117
0
        }
118
0
      else
119
0
        return glnx_throw_errno_prefix (error, "open(%s)", path);
120
0
    }
121
139
  else
122
139
    {
123
139
      g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
124
139
      if (!glnx_dirfd_iterator_init_take_fd (&target_dfd, &dfd_iter, error))
125
0
        return FALSE;
126
127
139
      if (!glnx_shutil_rm_rf_children (&dfd_iter, cancellable, error))
128
0
        return glnx_prefix_error (error, "Removing %s", path);
129
130
139
      if (!unlinkat_allow_noent (dfd, path, AT_REMOVEDIR, error))
131
0
        return FALSE;
132
139
    }
133
134
139
  return TRUE;
135
139
}
136
137
static gboolean
138
mkdir_p_at_internal (int              dfd,
139
                     char            *path,
140
                     int              mode,
141
                     GCancellable    *cancellable,
142
                     GError         **error)
143
139
{
144
139
  gboolean did_recurse = FALSE;
145
146
139
  if (g_cancellable_set_error_if_cancelled (cancellable, error))
147
0
    return FALSE;
148
149
139
 again:
150
139
  if (mkdirat (dfd, path, mode) == -1)
151
0
    {
152
0
      if (errno == ENOENT)
153
0
        {
154
0
          char *lastslash;
155
156
0
          g_assert (!did_recurse);
157
158
0
          lastslash = strrchr (path, '/');
159
0
          if (lastslash == NULL)
160
0
            {
161
              /* This can happen if @dfd was deleted between being opened and
162
               * passed to mkdir_p_at_internal(). */
163
0
              return glnx_throw_errno_prefix (error, "mkdir(%s)", path);
164
0
            }
165
166
          /* Note we can mutate the buffer as we dup'd it */
167
0
          *lastslash = '\0';
168
169
0
          if (!glnx_shutil_mkdir_p_at (dfd, path, mode,
170
0
                                       cancellable, error))
171
0
            return FALSE;
172
173
          /* Now restore it for another mkdir attempt */
174
0
          *lastslash = '/';
175
176
0
          did_recurse = TRUE;
177
0
          goto again;
178
0
        }
179
0
      else if (errno == EEXIST)
180
0
        {
181
          /* Fall through; it may not have been a directory,
182
           * but we'll find that out on the next call up.
183
           */
184
0
        }
185
0
      else
186
0
        return glnx_throw_errno_prefix (error, "mkdir(%s)", path);
187
0
    }
188
189
139
  return TRUE;
190
139
}
191
192
/**
193
 * glnx_shutil_mkdir_p_at:
194
 * @dfd: Directory fd
195
 * @path: Directory path to be created
196
 * @mode: Mode for newly created directories
197
 * @cancellable: Cancellable
198
 * @error: Error
199
 *
200
 * Similar to g_mkdir_with_parents(), except operates relative to the
201
 * directory fd @dfd.
202
 *
203
 * See also glnx_ensure_dir() for a non-recursive version.
204
 *
205
 * This will return %G_IO_ERROR_NOT_FOUND if @dfd has been deleted since being
206
 * opened. It may return other errors from mkdirat() in other situations.
207
 */
208
gboolean
209
glnx_shutil_mkdir_p_at (int                   dfd,
210
                        const char           *path,
211
                        int                   mode,
212
                        GCancellable         *cancellable,
213
                        GError              **error)
214
139
{
215
139
  struct stat stbuf;
216
139
  char *buf;
217
218
  /* Fast path stat to see whether it already exists */
219
139
  if (fstatat (dfd, path, &stbuf, AT_SYMLINK_NOFOLLOW) == 0)
220
0
    {
221
      /* Note early return */
222
0
      if (S_ISDIR (stbuf.st_mode))
223
0
        return TRUE;
224
0
    }
225
226
139
  buf = strdupa (path);
227
228
139
  if (!mkdir_p_at_internal (dfd, buf, mode, cancellable, error))
229
0
    return FALSE;
230
231
139
  return TRUE;
232
139
}
233
234
/**
235
 * glnx_shutil_mkdir_p_at_open:
236
 * @dfd: Directory fd
237
 * @path: Directory path to be created
238
 * @mode: Mode for newly created directories
239
 * @out_dfd: (out caller-allocates): Return location for an FD to @dfd/@path,
240
 *    or `-1` on error
241
 * @cancellable: (nullable): Cancellable, or %NULL
242
 * @error: Return location for a #GError, or %NULL
243
 *
244
 * Similar to glnx_shutil_mkdir_p_at(), except it opens the resulting directory
245
 * and returns a directory FD to it. Currently, this is not guaranteed to be
246
 * race-free.
247
 *
248
 * Returns: %TRUE on success, %FALSE otherwise
249
 * Since: UNRELEASED
250
 */
251
gboolean
252
glnx_shutil_mkdir_p_at_open (int            dfd,
253
                             const char    *path,
254
                             int            mode,
255
                             int           *out_dfd,
256
                             GCancellable  *cancellable,
257
                             GError       **error)
258
0
{
259
  /* FIXME: It’s not possible to eliminate the race here until
260
   * openat(O_DIRECTORY | O_CREAT) works (and returns a directory rather than a
261
   * file). It appears to be not supported in current kernels. (Tested with
262
   * 4.10.10-200.fc25.x86_64.) */
263
0
  *out_dfd = -1;
264
265
0
  if (!glnx_shutil_mkdir_p_at (dfd, path, mode, cancellable, error))
266
0
    return FALSE;
267
268
0
  return glnx_opendirat (dfd, path, TRUE, out_dfd, error);
269
0
}