/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 | } |