Add a copy method to lttng_directory_handle
[lttng-tools.git] / src / common / compat / directory-handle.c
1 /*
2 * Copyright (C) 2019 - Jérémie Galarneau <jeremie.galarneau@efficios.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License, version 2 only,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 */
17
18 #include <common/compat/directory-handle.h>
19 #include <common/error.h>
20 #include <common/macros.h>
21 #include <common/runas.h>
22 #include <common/credentials.h>
23 #include <lttng/constant.h>
24
25 #include <assert.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30
31 static
32 int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
33 const char *path, struct stat *st);
34 static
35 int lttng_directory_handle_mkdir(
36 const struct lttng_directory_handle *handle,
37 const char *path, mode_t mode);
38 static
39 int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path,
40 mode_t mode, uid_t uid, gid_t gid);
41 static
42 int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle,
43 const char *path, mode_t mode, uid_t uid, gid_t gid);
44
45 #ifdef COMPAT_DIRFD
46
47 LTTNG_HIDDEN
48 int lttng_directory_handle_init(struct lttng_directory_handle *handle,
49 const char *path)
50 {
51 int ret;
52
53 if (!path) {
54 handle->dirfd = AT_FDCWD;
55 ret = 0;
56 goto end;
57 }
58 ret = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
59 if (ret == -1) {
60 PERROR("Failed to initialize directory handle to \"%s\"", path);
61 goto end;
62 }
63 handle->dirfd = ret;
64 ret = 0;
65 end:
66 return ret;
67 }
68
69 LTTNG_HIDDEN
70 int lttng_directory_handle_init_from_dirfd(
71 struct lttng_directory_handle *handle, int dirfd)
72 {
73 handle->dirfd = dirfd;
74 return 0;
75 }
76
77 LTTNG_HIDDEN
78 void lttng_directory_handle_fini(struct lttng_directory_handle *handle)
79 {
80 int ret;
81
82 if (handle->dirfd == AT_FDCWD) {
83 return;
84 }
85 ret = close(handle->dirfd);
86 if (ret == -1) {
87 PERROR("Failed to close directory file descriptor of directory handle");
88 }
89 }
90
91 LTTNG_HIDDEN
92 int lttng_directory_handle_copy(const struct lttng_directory_handle *handle,
93 struct lttng_directory_handle *new_copy)
94 {
95 int ret = 0;
96
97 if (handle->dirfd == AT_FDCWD) {
98 new_copy->dirfd = handle->dirfd;
99 } else {
100 new_copy->dirfd = dup(handle->dirfd);
101 if (new_copy->dirfd == -1) {
102 PERROR("Failed to duplicate directory fd of directory handle");
103 ret = -1;
104 }
105 }
106 return ret;
107 }
108
109 static
110 int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
111 const char *path, struct stat *st)
112 {
113 return fstatat(handle->dirfd, path, st, 0);
114 }
115
116 static
117 int lttng_directory_handle_mkdir(
118 const struct lttng_directory_handle *handle,
119 const char *path, mode_t mode)
120 {
121 return mkdirat(handle->dirfd, path, mode);
122 }
123
124 static
125 int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path,
126 mode_t mode, uid_t uid, gid_t gid)
127 {
128 return run_as_mkdirat(handle->dirfd, path, mode, uid, gid);
129 }
130
131 static
132 int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle,
133 const char *path, mode_t mode, uid_t uid, gid_t gid)
134 {
135 return run_as_mkdirat_recursive(handle->dirfd, path, mode, uid, gid);
136 }
137
138 #else /* COMPAT_DIRFD */
139
140 LTTNG_HIDDEN
141 int lttng_directory_handle_init(struct lttng_directory_handle *handle,
142 const char *path)
143 {
144 int ret;
145 size_t cwd_len, path_len, handle_path_len;
146 char cwd_buf[LTTNG_PATH_MAX];
147 const char *cwd;
148 bool add_slash = false;
149 struct stat stat_buf;
150
151 cwd = getcwd(cwd_buf, sizeof(cwd_buf));
152 if (!cwd) {
153 PERROR("Failed to initialize directory handle, can't get current working directory");
154 ret = -1;
155 goto end;
156 }
157 cwd_len = strlen(cwd);
158 if (cwd_len == 0) {
159 ERR("Failed to initialize directory handle to \"%s\": getcwd() returned an empty string",
160 path);
161 ret = -1;
162 goto end;
163 }
164 if (cwd[cwd_len - 1] != '/') {
165 add_slash = true;
166 }
167
168 if (path) {
169 path_len = strlen(path);
170 if (path_len == 0) {
171 ERR("Failed to initialize directory handle: provided path is an empty string");
172 ret = -1;
173 goto end;
174 }
175
176 /*
177 * Ensure that 'path' is a directory. There is a race
178 * (TOCTOU) since the directory could be removed/replaced/renamed,
179 * but this is inevitable on platforms that don't provide dirfd support.
180 */
181 ret = stat(path, &stat_buf);
182 if (ret == -1) {
183 PERROR("Failed to initialize directory handle to \"%s\", stat() failed",
184 path);
185 goto end;
186 }
187 if (!S_ISDIR(stat_buf.st_mode)) {
188 ERR("Failed to initialize directory handle to \"%s\": not a directory",
189 path);
190 ret = -1;
191 goto end;
192 }
193 if (*path == '/') {
194 handle->base_path = strdup(path);
195 if (!handle->base_path) {
196 ret = -1;
197 }
198 /* Not an error. */
199 goto end;
200 }
201 } else {
202 path = "";
203 path_len = 0;
204 add_slash = false;
205 }
206
207 handle_path_len = cwd_len + path_len + !!add_slash + 2;
208 if (handle_path_len >= LTTNG_PATH_MAX) {
209 ERR("Failed to initialize directory handle as the resulting path's length (%zu bytes) exceeds the maximal allowed length (%d bytes)",
210 handle_path_len, LTTNG_PATH_MAX);
211 ret = -1;
212 goto end;
213 }
214 handle->base_path = zmalloc(handle_path_len);
215 if (!handle->base_path) {
216 PERROR("Failed to initialize directory handle");
217 ret = -1;
218 goto end;
219 }
220
221 ret = sprintf(handle->base_path, "%s%s%s/", cwd,
222 add_slash ? "/" : "", path);
223 if (ret == -1 || ret >= handle_path_len) {
224 ERR("Failed to initialize directory handle: path formatting failed");
225 ret = -1;
226 goto end;
227 }
228 end:
229 return ret;
230 }
231
232 LTTNG_HIDDEN
233 int lttng_directory_handle_init_from_dirfd(
234 struct lttng_directory_handle *handle, int dirfd)
235 {
236 assert(dirfd == AT_FDCWD);
237 return lttng_directory_handle_init(handle, NULL);
238 }
239
240 LTTNG_HIDDEN
241 void lttng_directory_handle_fini(struct lttng_directory_handle *handle)
242 {
243 free(handle->base_path);
244 }
245
246 LTTNG_HIDDEN
247 int lttng_directory_handle_copy(const struct lttng_directory_handle *handle,
248 struct lttng_directory_handle *new_copy)
249 {
250 new_copy->base_path = strdup(handle->base_path);
251 return new_copy->base_path ? 0 : -1;
252 }
253
254 static
255 int get_full_path(const struct lttng_directory_handle *handle,
256 const char *subdirectory, char *fullpath, size_t size)
257 {
258 int ret;
259
260 /*
261 * Don't include the base path if subdirectory is absolute.
262 * This is the same behaviour than mkdirat.
263 */
264 ret = snprintf(fullpath, size, "%s%s",
265 *subdirectory != '/' ? handle->base_path : "",
266 subdirectory);
267 if (ret == -1 || ret >= size) {
268 ERR("Failed to format subdirectory from directory handle");
269 ret = -1;
270 }
271 ret = 0;
272 return ret;
273 }
274
275 static
276 int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
277 const char *subdirectory, struct stat *st)
278 {
279 int ret;
280 char fullpath[LTTNG_PATH_MAX];
281
282 ret = get_full_path(handle, subdirectory, fullpath, sizeof(fullpath));
283 if (ret) {
284 errno = ENOMEM;
285 goto end;
286 }
287
288 ret = stat(fullpath, st);
289 end:
290 return ret;
291 }
292
293 static
294 int lttng_directory_handle_mkdir(const struct lttng_directory_handle *handle,
295 const char *subdirectory, mode_t mode)
296 {
297 int ret;
298 char fullpath[LTTNG_PATH_MAX];
299
300 ret = get_full_path(handle, subdirectory, fullpath, sizeof(fullpath));
301 if (ret) {
302 errno = ENOMEM;
303 goto end;
304 }
305
306 ret = mkdir(fullpath, mode);
307 end:
308 return ret;
309 }
310
311 static
312 int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path,
313 mode_t mode, uid_t uid, gid_t gid)
314 {
315 int ret;
316 char fullpath[LTTNG_PATH_MAX];
317
318 ret = get_full_path(handle, path, fullpath, sizeof(fullpath));
319 if (ret) {
320 errno = ENOMEM;
321 goto end;
322 }
323
324 ret = run_as_mkdir(fullpath, mode, uid, gid);
325 end:
326 return ret;
327 }
328
329 static
330 int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle,
331 const char *path, mode_t mode, uid_t uid, gid_t gid)
332 {
333 int ret;
334 char fullpath[LTTNG_PATH_MAX];
335
336 ret = get_full_path(handle, path, fullpath, sizeof(fullpath));
337 if (ret) {
338 errno = ENOMEM;
339 goto end;
340 }
341
342 ret = run_as_mkdir_recursive(fullpath, mode, uid, gid);
343 end:
344 return ret;
345 }
346
347 #endif /* COMPAT_DIRFD */
348
349 /*
350 * On some filesystems (e.g. nfs), mkdir will validate access rights before
351 * checking for the existence of the path element. This means that on a setup
352 * where "/home/" is a mounted NFS share, and running as an unpriviledged user,
353 * recursively creating a path of the form "/home/my_user/trace/" will fail with
354 * EACCES on mkdir("/home", ...).
355 *
356 * Checking the path for existence allows us to work around this behaviour.
357 */
358 static
359 int create_directory_check_exists(const struct lttng_directory_handle *handle,
360 const char *path, mode_t mode)
361 {
362 int ret = 0;
363 struct stat st;
364
365 ret = lttng_directory_handle_stat(handle, path, &st);
366 if (ret == 0) {
367 if (S_ISDIR(st.st_mode)) {
368 /* Directory exists, skip. */
369 goto end;
370 } else {
371 /* Exists, but is not a directory. */
372 errno = ENOTDIR;
373 ret = -1;
374 goto end;
375 }
376 }
377
378 /*
379 * Let mkdir handle other errors as the caller expects mkdir
380 * semantics.
381 */
382 ret = lttng_directory_handle_mkdir(handle, path, mode);
383 end:
384 return ret;
385 }
386
387 /* Common implementation. */
388 static
389 int create_directory_recursive(const struct lttng_directory_handle *handle,
390 const char *path, mode_t mode)
391 {
392 char *p, tmp[LTTNG_PATH_MAX];
393 size_t len;
394 int ret;
395
396 assert(path);
397
398 ret = lttng_strncpy(tmp, path, sizeof(tmp));
399 if (ret) {
400 ERR("Failed to create directory: provided path's length (%zu bytes) exceeds the maximal allowed length (%zu bytes)",
401 strlen(path) + 1, sizeof(tmp));
402 goto error;
403 }
404
405 len = strlen(path);
406 if (tmp[len - 1] == '/') {
407 tmp[len - 1] = 0;
408 }
409
410 for (p = tmp + 1; *p; p++) {
411 if (*p == '/') {
412 *p = 0;
413 if (tmp[strlen(tmp) - 1] == '.' &&
414 tmp[strlen(tmp) - 2] == '.' &&
415 tmp[strlen(tmp) - 3] == '/') {
416 ERR("Using '/../' is not permitted in the trace path (%s)",
417 tmp);
418 ret = -1;
419 goto error;
420 }
421 ret = create_directory_check_exists(handle, tmp, mode);
422 if (ret < 0) {
423 if (errno != EACCES) {
424 PERROR("Failed to create directory \"%s\"",
425 path);
426 ret = -errno;
427 goto error;
428 }
429 }
430 *p = '/';
431 }
432 }
433
434 ret = create_directory_check_exists(handle, tmp, mode);
435 if (ret < 0) {
436 PERROR("mkdirat recursive last element");
437 ret = -errno;
438 }
439 error:
440 return ret;
441 }
442
443 LTTNG_HIDDEN
444 int lttng_directory_handle_create_subdirectory_as_user(
445 const struct lttng_directory_handle *handle,
446 const char *subdirectory,
447 mode_t mode, const struct lttng_credentials *creds)
448 {
449 int ret;
450
451 if (!creds) {
452 /* Run as current user. */
453 ret = create_directory_check_exists(handle,
454 subdirectory, mode);
455 } else {
456 ret = _run_as_mkdir(handle, subdirectory,
457 mode, creds->uid, creds->gid);
458 }
459
460 return ret;
461 }
462
463 LTTNG_HIDDEN
464 int lttng_directory_handle_create_subdirectory_recursive_as_user(
465 const struct lttng_directory_handle *handle,
466 const char *subdirectory_path,
467 mode_t mode, const struct lttng_credentials *creds)
468 {
469 int ret;
470
471 if (!creds) {
472 /* Run as current user. */
473 ret = create_directory_recursive(handle,
474 subdirectory_path, mode);
475 } else {
476 ret = _run_as_mkdir_recursive(handle, subdirectory_path,
477 mode, creds->uid, creds->gid);
478 }
479
480 return ret;
481 }
482
483 LTTNG_HIDDEN
484 int lttng_directory_handle_create_subdirectory(
485 const struct lttng_directory_handle *handle,
486 const char *subdirectory,
487 mode_t mode)
488 {
489 return lttng_directory_handle_create_subdirectory_as_user(
490 handle, subdirectory, mode, NULL);
491 }
492
493 LTTNG_HIDDEN
494 int lttng_directory_handle_create_subdirectory_recursive(
495 const struct lttng_directory_handle *handle,
496 const char *subdirectory_path,
497 mode_t mode)
498 {
499 return lttng_directory_handle_create_subdirectory_recursive_as_user(
500 handle, subdirectory_path, mode, NULL);
501 }
This page took 0.040806 seconds and 6 git commands to generate.