The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
file.c
Go to the documentation of this file.
1/*
2 * This library is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2.1 of the License, or (at your option) any later version.
6 *
7 * This library is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with this library; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/** Various miscellaneous functions to manipulate files and paths
18 *
19 * @file src/lib/util/file.c
20 *
21 * @copyright 2019 The FreeRADIUS project
22 * @copyright 2019 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
23 */
24RCSID("$Id: 2ac616577d4367233b5c8a828bad0f6d5731e743 $")
25
26#include <sys/param.h>
27#include <fcntl.h>
28#include <stdio.h>
29#include <sys/stat.h>
30
31#include <freeradius-devel/util/file.h>
32#include <freeradius-devel/util/strerror.h>
33#include <freeradius-devel/util/syserror.h>
34#include <freeradius-devel/util/value.h>
35
36/** Callback for the common case of chown() of the directory.
37 *
38 */
39int fr_mkdir_chown(int fd, char const *path, void *uctx)
40{
41 fr_mkdir_chown_t const *ctx = uctx;
42
43 if ((ctx->uid == (uid_t) -1) && (ctx->gid == (gid_t) -1)) return 0;
44
45 if (fchown(fd, ctx->uid, ctx->gid) < 0) {
46 fr_strerror_printf("Failed changing ownership on directory \"%s\": %s",
47 path, fr_syserror(errno));
48 return -1;
49 }
50
51 return 0;
52}
53
54
55static ssize_t _fr_mkdir(int *fd_out, char *start, char *path, mode_t mode, fr_mkdir_func_t func, void *uctx)
56{
57 int ret, fd;
58 char *p;
59
60 /*
61 * Try to make the path. If it exists, chmod it.
62 * If a path doesn't exist, that's OK. Otherwise
63 * return with an error.
64 *
65 * Directories permissions are initially set so
66 * that only we should have access. This prevents
67 * an attacker removing them and swapping them
68 * out for a link to somewhere else.
69 * We change them to the correct permissions later.
70 */
71 ret = mkdir(path, 0700);
72 if (ret >= 0) {
73 fd = open(path, O_DIRECTORY);
74 if (fd < 0) {
75 fr_strerror_printf("Failed opening directory we created: %s",
76 fr_syserror(errno));
77 mkdir_error:
78 p = strrchr(path, FR_DIR_SEP);
79 if (!p) return start - path;
80
81 return start - p;
82 }
83
84 if (fchmod(fd, mode) < 0) {
85 fr_strerror_printf("Failed setting permissions on directory "
86 "we created: %s", fr_syserror(errno));
87 close(fd);
88 goto mkdir_error;
89 }
90 *fd_out = fd;
91 return strlen(start);
92 }
93
94 /*
95 * EEXIST is only OK when we're calling mkdir on the
96 * whole path, and it exists which should have been
97 * caught by fr_mkdir before calling this function.
98 *
99 * Unless we're running in an environment with multiple
100 * processes, in which case EEXIST means that another
101 * process created this directory in between our check
102 * and our creation.
103 */
104 if (errno == EEXIST) {
105 fd = open(path, O_DIRECTORY);
106 if (fd < 0) {
107 fr_strerror_printf("Failed opening existing directory: %s", fr_syserror(errno));
108 goto mkdir_error;
109 }
110 *fd_out = fd;
111 return strlen(start);
112 }
113
114 /*
115 * ENOENT means we're trying to create too much path
116 * at once. Recurse to discover the deepest path
117 * component that already exists.
118 */
119 if (errno != ENOENT) {
120 fr_strerror_printf("Failed creating directory path: %s", fr_syserror(errno));
121 goto mkdir_error;
122 }
123
124 /*
125 * A component in the path doesn't
126 * exist. Look for the LAST path name. Try
127 * to create that. If there's an error, we leave
128 * the path path as the one at which the
129 * error occurred.
130 */
131 p = strrchr(path, FR_DIR_SEP);
132 if (!p || (p == path)) return start - path; /* last path component and we've previously failed */
133
134 *p = '\0';
135 if (_fr_mkdir(fd_out, start, path, mode, func, uctx) <= 0) return start - p;
136
137 fr_assert_msg((*fd_out) >= 0, "Logic error - Bad FD %i", *fd_out);
138
139 /*
140 * At this point *fd_out, should be an FD
141 * for the containing directory.
142 *
143 * Dir may already exist if we're racing
144 * other processes as we do in CI.
145 */
146 if (mkdirat(*fd_out, p + 1, 0700) < 0) {
147 /*
148 * This is usually because of a race with
149 * other processes trying to create the
150 * same directory.
151 */
152 if (errno == EEXIST) {
153 fd = openat(*fd_out, p + 1, O_DIRECTORY);
154 if (fd < 0) {
155 fr_strerror_printf_push("Failed opening existing directory path component: %s",
156 fr_syserror(errno));
157 goto mkdirat_error;
158 }
159 *p = FR_DIR_SEP;
160 goto done;
161 }
162
163 fr_strerror_printf_push("Failed creating directory path component: %s", fr_syserror(errno));
164
165 mkdirat_error:
166 close(*fd_out);
167 *fd_out = -1;
168 return start - p;
169 }
170
171 fd = openat(*fd_out, p + 1, O_DIRECTORY);
172 if (fd < 0) {
173 fr_strerror_printf_push("Failed opening directory we "
174 "created: %s", fr_syserror(errno));
175 goto mkdirat_error;
176 }
177
178 if (fchmod(fd, mode) < 0) {
179 fr_strerror_printf_push("Failed setting permissions on "
180 "directory we created: %s", fr_syserror(errno));
181 goto mkdirat_error;
182 }
183
184 *p = FR_DIR_SEP;
185
186 /*
187 * Call the user function
188 */
189 if (func && (func(fd, path, uctx) < 0)) {
190 fr_strerror_printf_push("Callback failed processing directory \"%s\"", path);
191 goto mkdirat_error;
192 }
193
194 /*
195 * Swap active *fd_out to point to the dir
196 * we just created.
197 */
198done:
199 close(*fd_out);
200 *fd_out = fd;
201
202 return strlen(start);
203}
204
205/** Create directories that are missing in the specified path
206 *
207 * @param[out] fd_out If not NULL, will contain a file descriptor
208 * for the deepest path component created.
209 * @param[in] path to populate with directories.
210 * @param[in] len Length of the path string.
211 * @param[in] mode for new directories.
212 * @param[in] func to call each time a new directory is created.
213 * @param[in] uctx to pass to func.
214 * @return
215 * - >0 on success.
216 * - <= 0 on failure. Negative offset pointing to the
217 * path separator of the path component that caused the error.
218 */
219ssize_t fr_mkdir(int *fd_out, char const *path, ssize_t len, mode_t mode, fr_mkdir_func_t func, void *uctx)
220{
221 char *our_path;
222 int fd = -1;
223 ssize_t slen;
224
225 if (len < 0) len = strlen(path);
226 if (len == 0) return 0;
227
228 /*
229 * Fast path (har har)
230 *
231 * Avoids duping the input for the
232 * common case.
233 */
234 fd = open(path, O_DIRECTORY);
235 if (fd >= 0) goto done;
236
237 /*
238 * Dup the amount of input path
239 * we need.
240 */
241 our_path = talloc_bstrndup(NULL, path, (size_t)len);
242 if (!our_path) {
243 fr_strerror_const("Out of memory");
244 return -1;
245 }
246
247 fr_strerror_clear(); /* We make liberal use of push */
248
249 /*
250 * Call the recursive function to
251 * create any missing dirs in the
252 * specified path.
253 */
254 slen = _fr_mkdir(&fd, our_path, our_path, mode, func, uctx);
255 talloc_free(our_path);
256 if (slen <= 0) return slen;
257
258done:
259 if (fd_out) {
260 *fd_out = fd;
261 } else {
262 close(fd);
263 }
264
265 return len;
266}
267
268/** Convenience wrapper around realpath
269 *
270 * Wraps realpath, but takes a path with an explicit length, and returns
271 * the result in a talloced buffer.
272 *
273 * On error, errno is set, and the string version of the error is
274 * available with fr_strerror().
275 *
276 * @param[in] ctx in which to allocate the result.
277 * @param[in] path To convert to an absolute path.
278 * @param[in] len How much of 'path' to read. If < 0, then
279 * the entire path will be used.
280 * @return
281 * - NULL on error.
282 * - The absolute version of the input path on success.
283 */
284char *fr_realpath(TALLOC_CTX *ctx, char const *path, ssize_t len)
285{
286 char *tmp_path = NULL, *abs_path, *talloc_abs_path;
287
288 if (len > 0) path = tmp_path = talloc_bstrndup(NULL, path, (size_t)len);
289
290 abs_path = realpath(path, NULL);
291 if (!abs_path) {
292 fr_strerror_printf("Failed resolving path \"%pV\": %s",
293 fr_box_strvalue_buffer(path), fr_syserror(errno));
294 talloc_free(tmp_path);
295 return NULL;
296 }
297
298 talloc_free(tmp_path);
299
300 talloc_abs_path = talloc_strdup(ctx, abs_path);
301 free(abs_path);
302 if (!talloc_abs_path) {
303 fr_strerror_const("Out of Memory");
304 return NULL;
305 }
306
307 return talloc_abs_path;
308}
309
310/** Create an empty file
311 *
312 * @param[out] fd_out If not NULL, will contain a file descriptor
313 * for the file we just opened.
314 * @param[in] filename path to file.
315 * @param[in] mode Specifies the file mode bits be applied.
316 * @param[in] mkdir Whether we should create directories
317 * for any missing path components.
318 * @param[in] dir_mode Mode of any directories created.
319 * @return
320 * - >0 on success.
321 * - <= 0 on failure. Error available in error stack (use fr_strerror())
322 */
323ssize_t fr_touch(int *fd_out, char const *filename, mode_t mode, bool mkdir, mode_t dir_mode) {
324 int fd;
325
326 fd = open(filename, O_WRONLY | O_CREAT, mode);
327 if (fd < 0) {
328 ssize_t slen = 0;
329 char *q;
330
331 if (mkdir && (errno == ENOENT) && (q = strrchr(filename, FR_DIR_SEP))) {
332 int dir_fd = -1;
333
334 slen = fr_mkdir(&dir_fd, filename, q - filename, dir_mode, NULL, NULL);
335 if((slen <= 0) || (dir_fd < 0)) return slen;
336
337 fd = openat(dir_fd, q + 1, O_WRONLY | O_CREAT, mode);
338 if (fd >= 0) {
339 close(dir_fd);
340 close(fd);
341 return strlen(filename);
342 }
343 close(dir_fd);
344 slen = -(q - filename);
345 }
346 fr_strerror_printf("Failed creating file: %s", fr_syserror(errno));
347 return slen;
348 }
349
350 if (fd_out) {
351 *fd_out = fd;
352 } else {
353 close(fd);
354 }
355
356 return strlen(filename);
357}
358
359/** Remove a regular file from the filesystem
360 *
361 * @param[in] filename path to file.
362 * @return
363 * - -1 On error.
364 * - 0 if the file was removed.
365 * - 1 if the file didn't exist.
366 */
367int fr_unlink(char const *filename) {
368 if (unlink(filename) == 0) return 0;
369
370 if (errno == ENOENT) return 1;
371
372 fr_strerror_printf("Failed removing regular file \"%s\": %s", filename, fr_syserror(errno));
373
374 return -1;
375}
376
377/** Intended to be used in logging functions to make output more readable
378 *
379 * This function is not performant and should probably not be used at runtime.
380 *
381 * @param[in] filename to strip working directory from.
382 * @return Position in filename after our working directory.
383 */
384char const *fr_cwd_strip(char const *filename)
385{
386 static char our_wd[MAXPATHLEN];
387 char *found;
388
389 if (!getcwd(our_wd, sizeof(our_wd))) return filename;
390
391 found = strstr(filename, our_wd);
392 if (found && (found == our_wd)) {
393 filename += strlen(our_wd);
394 while (*filename == '/') filename++;
395 return filename;
396 }
397
398 return filename;
399}
400
401/** From a pathname, return fd and filename needed for *at() functions
402 *
403 * @param[in] dirfd points to place to store the dirfd
404 * @param[in] filename points to placd to store a pointer into pathname
405 * that points to the filename
406 * @param[in] pathname the full pathname of the file
407 *
408 * @return
409 * - -1 on error
410 * - 0 on success
411 */
412int fr_dirfd(int *dirfd, char const **filename, char const *pathname)
413{
414 char const *last_slash = strrchr(pathname, '/');
415
416 if (last_slash == NULL) {
417 *filename = pathname;
418 *dirfd = AT_FDCWD;
419 return 0;
420 }
421 {
422 char dirpath[(last_slash - pathname) + 1];
423
424 memcpy(dirpath, pathname, last_slash - pathname);
425 dirpath[last_slash - pathname] = '\0';
426 *filename = last_slash + 1;
427 *dirfd = open(dirpath, O_DIRECTORY);
428 return (*dirfd < 0) ? -1 : 0;
429 }
430}
#define RCSID(id)
Definition build.h:483
#define fr_assert_msg(_x, _msg,...)
Calls panic_action ifndef NDEBUG, else logs error and causes the server to exit immediately with code...
Definition debug.h:210
ssize_t fr_mkdir(int *fd_out, char const *path, ssize_t len, mode_t mode, fr_mkdir_func_t func, void *uctx)
Create directories that are missing in the specified path.
Definition file.c:219
int fr_unlink(char const *filename)
Remove a regular file from the filesystem.
Definition file.c:367
int fr_mkdir_chown(int fd, char const *path, void *uctx)
Callback for the common case of chown() of the directory.
Definition file.c:39
ssize_t fr_touch(int *fd_out, char const *filename, mode_t mode, bool mkdir, mode_t dir_mode)
Create an empty file.
Definition file.c:323
char const * fr_cwd_strip(char const *filename)
Intended to be used in logging functions to make output more readable.
Definition file.c:384
int fr_dirfd(int *dirfd, char const **filename, char const *pathname)
From a pathname, return fd and filename needed for *at() functions.
Definition file.c:412
char * fr_realpath(TALLOC_CTX *ctx, char const *path, ssize_t len)
Convenience wrapper around realpath.
Definition file.c:284
static ssize_t _fr_mkdir(int *fd_out, char *start, char *path, mode_t mode, fr_mkdir_func_t func, void *uctx)
Definition file.c:55
uid_t uid
Definition file.h:48
gid_t gid
Definition file.h:49
int(* fr_mkdir_func_t)(int fd, char const *path, void *uctx)
Callback for allowing additional operations on newly created directories.
Definition file.h:45
free(array)
talloc_free(reap)
long int ssize_t
unsigned int mode_t
static bool done
Definition radclient.c:80
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
char * talloc_bstrndup(TALLOC_CTX *ctx, char const *in, size_t inlen)
Binary safe strndup function.
Definition talloc.c:564
close(uq->fd)
void fr_strerror_clear(void)
Clears all pending messages from the talloc pools.
Definition strerror.c:577
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_printf_push(_fmt,...)
Add a message to an existing stack of messages at the tail.
Definition strerror.h:84
#define fr_strerror_const(_msg)
Definition strerror.h:223
#define fr_box_strvalue_buffer(_val)
Definition value.h:289