The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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  */
24 RCSID("$Id: 3cd094bcda33609a2ebb71ae3f93804ea74b261c $")
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 static ssize_t _fr_mkdir(int *fd_out, char *start, char *path, mode_t mode, fr_mkdir_func_t func, void *uctx)
37 {
38  int ret, fd;
39  char *p;
40 
41  /*
42  * Try to make the path. If it exists, chmod it.
43  * If a path doesn't exist, that's OK. Otherwise
44  * return with an error.
45  *
46  * Directories permissions are initially set so
47  * that only we should have access. This prevents
48  * an attacker removing them and swapping them
49  * out for a link to somewhere else.
50  * We change them to the correct permissions later.
51  */
52  ret = mkdir(path, 0700);
53  if (ret >= 0) {
54  fd = open(path, O_DIRECTORY);
55  if (fd < 0) {
56  fr_strerror_printf("Failed opening directory we created: %s",
57  fr_syserror(errno));
58  mkdir_error:
59  p = strrchr(path, FR_DIR_SEP);
60  if (!p) return start - path;
61 
62  return start - p;
63  }
64 
65  if (fchmod(fd, mode) < 0) {
66  fr_strerror_printf("Failed setting permissions on directory "
67  "we created: %s", fr_syserror(errno));
68  close(fd);
69  goto mkdir_error;
70  }
71  *fd_out = fd;
72  return strlen(start);
73  }
74 
75  /*
76  * EEXIST is only OK when we're calling mkdir on the
77  * whole path, and it exists which should have been
78  * caught by fr_mkdir before calling this function.
79  *
80  * Unless we're running in an environment with multiple
81  * processes, in which case EEXIST means that another
82  * process created this directory in between our check
83  * and our creation.
84  */
85  if (errno == EEXIST) {
86  fd = open(path, O_DIRECTORY);
87  if (fd < 0) {
88  fr_strerror_printf("Failed opening existing directory: %s", fr_syserror(errno));
89  goto mkdir_error;
90  }
91  *fd_out = fd;
92  return strlen(start);
93  }
94 
95  /*
96  * ENOENT means we're trying to create too much path
97  * at once. Recurse to discover the deepest path
98  * component that already exists.
99  */
100  if (errno != ENOENT) {
101  fr_strerror_printf("Failed creating directory path: %s", fr_syserror(errno));
102  goto mkdir_error;
103  }
104 
105  /*
106  * A component in the path doesn't
107  * exist. Look for the LAST path name. Try
108  * to create that. If there's an error, we leave
109  * the path path as the one at which the
110  * error occurred.
111  */
112  p = strrchr(path, FR_DIR_SEP);
113  if (!p || (p == path)) return start - path; /* last path component and we've previously failed */
114 
115  *p = '\0';
116  if (_fr_mkdir(fd_out, start, path, mode, func, uctx) <= 0) return start - p;
117 
118  fr_assert_msg((*fd_out) >= 0, "Logic error - Bad FD %i", *fd_out);
119 
120  /*
121  * At this point *fd_out, should be an FD
122  * for the containing directory.
123  *
124  * Dir may already exist if we're racing
125  * other processes as we do in CI.
126  */
127  if (mkdirat(*fd_out, p + 1, 0700) < 0) {
128  /*
129  * This is usually because of a race with
130  * other processes trying to create the
131  * same directory.
132  */
133  if (errno == EEXIST) {
134  fd = openat(*fd_out, p + 1, O_DIRECTORY);
135  if (fd < 0) {
136  fr_strerror_printf_push("Failed opening existing directory path component: %s",
137  fr_syserror(errno));
138  goto mkdirat_error;
139  }
140  *p = FR_DIR_SEP;
141  goto done;
142  }
143 
144  fr_strerror_printf_push("Failed creating directory path component: %s", fr_syserror(errno));
145 
146  mkdirat_error:
147  close(*fd_out);
148  *fd_out = -1;
149  return start - p;
150  }
151 
152  fd = openat(*fd_out, p + 1, O_DIRECTORY);
153  if (fd < 0) {
154  fr_strerror_printf_push("Failed opening directory we "
155  "created: %s", fr_syserror(errno));
156  goto mkdirat_error;
157  }
158 
159  if (fchmod(fd, mode) < 0) {
160  fr_strerror_printf_push("Failed setting permissions on "
161  "directory we created: %s", fr_syserror(errno));
162  goto mkdirat_error;
163  }
164 
165  *p = FR_DIR_SEP;
166 
167  /*
168  * Call the user function
169  */
170  if (func && (func(fd, path, uctx) < 0)) {
171  fr_strerror_printf_push("Callback failed processing directory \"%s\"", path);
172  goto mkdirat_error;
173  }
174 
175  /*
176  * Swap active *fd_out to point to the dir
177  * we just created.
178  */
179 done:
180  close(*fd_out);
181  *fd_out = fd;
182 
183  return strlen(start);
184 }
185 
186 /** Create directories that are missing in the specified path
187  *
188  * @param[out] fd_out If not NULL, will contain a file descriptor
189  * for the deepest path component created.
190  * @param[in] path to populate with directories.
191  * @param[in] len Length of the path string.
192  * @param[in] mode for new directories.
193  * @param[in] func to call each time a new directory is created.
194  * @param[in] uctx to pass to func.
195  * @return
196  * - >0 on success.
197  * - <= 0 on failure. Negative offset pointing to the
198  * path separator of the path component that caused the error.
199  */
200 ssize_t fr_mkdir(int *fd_out, char const *path, ssize_t len, mode_t mode, fr_mkdir_func_t func, void *uctx)
201 {
202  char *our_path;
203  int fd = -1;
204  ssize_t slen;
205 
206  if (len < 0) len = strlen(path);
207  if (len == 0) return 0;
208 
209  /*
210  * Fast path (har har)
211  *
212  * Avoids duping the input for the
213  * common case.
214  */
215  fd = open(path, O_DIRECTORY);
216  if (fd >= 0) goto done;
217 
218  /*
219  * Dup the amount of input path
220  * we need.
221  */
222  our_path = talloc_bstrndup(NULL, path, (size_t)len);
223  if (!our_path) {
224  fr_strerror_const("Out of memory");
225  return -1;
226  }
227 
228  fr_strerror_clear(); /* We make liberal use of push */
229 
230  /*
231  * Call the recursive function to
232  * create any missing dirs in the
233  * specified path.
234  */
235  slen = _fr_mkdir(&fd, our_path, our_path, mode, func, uctx);
236  talloc_free(our_path);
237  if (slen <= 0) return slen;
238 
239 done:
240  if (fd_out) {
241  *fd_out = fd;
242  } else {
243  close(fd);
244  }
245 
246  return len;
247 }
248 
249 /** Convenience wrapper around realpath
250  *
251  * Wraps realpath, but takes a path with an explicit length, and returns
252  * the result in a talloced buffer.
253  *
254  * On error, errno is set, and the string version of the error is
255  * available with fr_strerror().
256  *
257  * @param[in] ctx in which to allocate the result.
258  * @param[in] path To convert to an absolute path.
259  * @param[in] len How much of 'path' to read. If < 0, then
260  * the entire path will be used.
261  * @return
262  * - NULL on error.
263  * - The absolute version of the input path on success.
264  */
265 char *fr_realpath(TALLOC_CTX *ctx, char const *path, ssize_t len)
266 {
267  char *tmp_path = NULL, *abs_path, *talloc_abs_path;
268 
269  if (len > 0) path = tmp_path = talloc_bstrndup(NULL, path, (size_t)len);
270 
271  abs_path = realpath(path, NULL);
272  if (!abs_path) {
273  fr_strerror_printf("Failed resolving path \"%pV\": %s",
274  fr_box_strvalue_buffer(path), fr_syserror(errno));
275  talloc_free(tmp_path);
276  return NULL;
277  }
278 
279  talloc_free(tmp_path);
280 
281  talloc_abs_path = talloc_strdup(ctx, abs_path);
282  free(abs_path);
283  if (!talloc_abs_path) {
284  fr_strerror_const("Out of Memory");
285  return NULL;
286  }
287 
288  return talloc_abs_path;
289 }
290 
291 /** Create an empty file
292  *
293  * @param[out] fd_out If not NULL, will contain a file descriptor
294  * for the file we just opened.
295  * @param[in] filename path to file.
296  * @param[in] mode Specifies the file mode bits be applied.
297  * @param[in] mkdir Whether we should create directories
298  * for any missing path components.
299  * @param[in] dir_mode Mode of any directories created.
300  * @return
301  * - >0 on success.
302  * - <= 0 on failure. Error available in error stack (use fr_strerror())
303  */
304 ssize_t fr_touch(int *fd_out, char const *filename, mode_t mode, bool mkdir, mode_t dir_mode) {
305  int fd;
306 
307  fd = open(filename, O_WRONLY | O_CREAT, mode);
308  if (fd < 0) {
309  ssize_t slen = 0;
310  char *q;
311 
312  if (mkdir && (errno == ENOENT) && (q = strrchr(filename, FR_DIR_SEP))) {
313  int dir_fd;
314 
315  slen = fr_mkdir(&dir_fd, filename, q - filename, dir_mode, NULL, NULL);
316  if (slen <= 0) return slen;
317 
318  fd = openat(dir_fd, q + 1, O_WRONLY | O_CREAT, mode);
319  if (fd >= 0) {
320  close(dir_fd);
321  close(fd);
322  return strlen(filename);
323  }
324  close(dir_fd);
325  slen = -(q - filename);
326  }
327  fr_strerror_printf("Failed creating file: %s", fr_syserror(errno));
328  return slen;
329  }
330 
331  if (fd_out) {
332  *fd_out = fd;
333  } else {
334  close(fd);
335  }
336 
337  return strlen(filename);
338 }
339 
340 /** Remove a regular file from the filesystem
341  *
342  * @param[in] filename path to file.
343  * @return
344  * - -1 On error.
345  * - 0 if the file was removed.
346  * - 1 if the file didn't exist.
347  */
348 int fr_unlink(char const *filename) {
349  if (unlink(filename) == 0) return 0;
350 
351  if (errno == ENOENT) return 1;
352 
353  fr_strerror_printf("Failed removing regular file \"%s\": %s", filename, fr_syserror(errno));
354 
355  return -1;
356 }
357 
358 /** Intended to be used in logging functions to make output more readable
359  *
360  * This function is not performant and should probably not be used at runtime.
361  *
362  * @param[in] filename to strip working directory from.
363  * @return Position in filename after our working directory.
364  */
365 char const *fr_cwd_strip(char const *filename)
366 {
367  static char our_wd[MAXPATHLEN];
368  char *found;
369 
370  if (!getcwd(our_wd, sizeof(our_wd))) return filename;
371 
372  found = strstr(filename, our_wd);
373  if (found && (found == our_wd)) {
374  filename += strlen(our_wd);
375  while (*filename == '/') filename++;
376  return filename;
377  }
378 
379  return filename;
380 }
381 
382 /** From a pathname, return fd and filename needed for *at() functions
383  *
384  * @param[in] dirfd points to place to store the dirfd
385  * @param[in] filename points to placd to store a pointer into pathname
386  * that points to the filename
387  * @param[in] pathname the full pathname of the file
388  *
389  * @return
390  * - -1 on error
391  * - 0 on success
392  */
393 int fr_dirfd(int *dirfd, char const **filename, char const *pathname)
394 {
395  char const *last_slash = strrchr(pathname, '/');
396 
397  if (last_slash == NULL) {
398  *filename = pathname;
399  *dirfd = AT_FDCWD;
400  return 0;
401  }
402  {
403  char dirpath[(last_slash - pathname) + 1];
404 
405  memcpy(dirpath, pathname, last_slash - pathname);
406  dirpath[last_slash - pathname] = '\0';
407  *filename = last_slash + 1;
408  *dirfd = open(dirpath, O_DIRECTORY);
409  return (*dirfd < 0) ? -1 : 0;
410  }
411 }
#define RCSID(id)
Definition: build.h:444
#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:208
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:200
int fr_unlink(char const *filename)
Remove a regular file from the filesystem.
Definition: file.c:348
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:304
char * fr_realpath(TALLOC_CTX *ctx, char const *path, ssize_t len)
Convenience wrapper around realpath.
Definition: file.c:265
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:393
char const * fr_cwd_strip(char const *filename)
Intended to be used in logging functions to make output more readable.
Definition: file.c:365
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:36
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
Definition: merged_model.c:24
unsigned int mode_t
Definition: merged_model.c:21
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:452
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:282