Add file creation/unlinking utils to directory handle
[lttng-tools.git] / src / common / compat / directory-handle.c
CommitLineData
18710679
JG
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
2912cead
JG
31/*
32 * This compatibility layer shares a common "base" that is implemented
33 * in terms of an internal API. This file contains two implementations
34 * of the internal API below.
35 */
18710679
JG
36static
37int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
38 const char *path, struct stat *st);
39static
40int lttng_directory_handle_mkdir(
41 const struct lttng_directory_handle *handle,
42 const char *path, mode_t mode);
43static
44int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path,
45 mode_t mode, uid_t uid, gid_t gid);
46static
47int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle,
48 const char *path, mode_t mode, uid_t uid, gid_t gid);
46307ffe 49static
2912cead
JG
50int lttng_directory_handle_open(const struct lttng_directory_handle *handle,
51 const char *filename, int flags, mode_t mode);
52static
53int _run_as_open(const struct lttng_directory_handle *handle,
54 const char *filename,
55 int flags, mode_t mode, uid_t uid, gid_t gid);
56static
57int lttng_directory_handle_unlink(
58 const struct lttng_directory_handle *handle,
59 const char *filename);
60static
61int _run_as_unlink(const struct lttng_directory_handle *handle,
62 const char *filename, uid_t uid, gid_t gid);
63static
46307ffe 64void lttng_directory_handle_invalidate(struct lttng_directory_handle *handle);
18710679
JG
65
66#ifdef COMPAT_DIRFD
67
68LTTNG_HIDDEN
fd774fc6 69int lttng_directory_handle_init(struct lttng_directory_handle *new_handle,
18710679 70 const char *path)
fd774fc6
JG
71{
72 const struct lttng_directory_handle cwd_handle = {
73 .dirfd = AT_FDCWD,
74 };
75
76 /* Open a handle to the CWD if NULL is passed. */
77 return lttng_directory_handle_init_from_handle(new_handle,
78 path,
79 &cwd_handle);
80}
81
82LTTNG_HIDDEN
83int lttng_directory_handle_init_from_handle(
84 struct lttng_directory_handle *new_handle, const char *path,
85 const struct lttng_directory_handle *handle)
18710679
JG
86{
87 int ret;
88
89 if (!path) {
fd774fc6 90 ret = lttng_directory_handle_copy(handle, new_handle);
18710679
JG
91 goto end;
92 }
fd774fc6
JG
93 if (!*path) {
94 ERR("Failed to initialize directory handle: provided path is an empty string");
95 ret = -1;
96 goto end;
97 }
98 ret = openat(handle->dirfd, path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
18710679
JG
99 if (ret == -1) {
100 PERROR("Failed to initialize directory handle to \"%s\"", path);
101 goto end;
102 }
fd774fc6 103 new_handle->dirfd = ret;
18710679
JG
104 ret = 0;
105end:
106 return ret;
107}
108
109LTTNG_HIDDEN
110int lttng_directory_handle_init_from_dirfd(
111 struct lttng_directory_handle *handle, int dirfd)
112{
113 handle->dirfd = dirfd;
114 return 0;
115}
116
117LTTNG_HIDDEN
118void lttng_directory_handle_fini(struct lttng_directory_handle *handle)
119{
120 int ret;
121
46307ffe
JG
122 if (handle->dirfd == AT_FDCWD || handle->dirfd == -1) {
123 goto end;
18710679
JG
124 }
125 ret = close(handle->dirfd);
126 if (ret == -1) {
127 PERROR("Failed to close directory file descriptor of directory handle");
128 }
46307ffe
JG
129end:
130 lttng_directory_handle_invalidate(handle);
18710679
JG
131}
132
578e21bd
JG
133LTTNG_HIDDEN
134int lttng_directory_handle_copy(const struct lttng_directory_handle *handle,
135 struct lttng_directory_handle *new_copy)
136{
137 int ret = 0;
138
139 if (handle->dirfd == AT_FDCWD) {
140 new_copy->dirfd = handle->dirfd;
141 } else {
142 new_copy->dirfd = dup(handle->dirfd);
143 if (new_copy->dirfd == -1) {
144 PERROR("Failed to duplicate directory fd of directory handle");
145 ret = -1;
146 }
147 }
148 return ret;
149}
150
46307ffe
JG
151static
152void lttng_directory_handle_invalidate(struct lttng_directory_handle *handle)
153{
154 handle->dirfd = -1;
155}
156
18710679
JG
157static
158int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
159 const char *path, struct stat *st)
160{
161 return fstatat(handle->dirfd, path, st, 0);
162}
163
164static
165int lttng_directory_handle_mkdir(
166 const struct lttng_directory_handle *handle,
167 const char *path, mode_t mode)
168{
169 return mkdirat(handle->dirfd, path, mode);
170}
171
172static
2912cead
JG
173int lttng_directory_handle_open(const struct lttng_directory_handle *handle,
174 const char *filename, int flags, mode_t mode)
175{
176 return openat(handle->dirfd, filename, flags, mode);
177}
178
179static
180int _run_as_open(const struct lttng_directory_handle *handle,
181 const char *filename,
182 int flags, mode_t mode, uid_t uid, gid_t gid)
183{
184 return run_as_openat(handle->dirfd, filename, flags, mode, uid, gid);
185}
186
187static
188int _run_as_unlink(const struct lttng_directory_handle *handle,
189 const char *filename, uid_t uid, gid_t gid)
190{
191 return run_as_unlinkat(handle->dirfd, filename, uid, gid);
192}
193
194static
195int lttng_directory_handle_unlink(
196 const struct lttng_directory_handle *handle,
197 const char *filename)
198{
199 return unlinkat(handle->dirfd, filename, 0);
200}
201
202static
203int _run_as_mkdir(const struct lttng_directory_handle *handle,
204 const char *path, mode_t mode, uid_t uid, gid_t gid)
18710679
JG
205{
206 return run_as_mkdirat(handle->dirfd, path, mode, uid, gid);
207}
208
209static
210int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle,
211 const char *path, mode_t mode, uid_t uid, gid_t gid)
212{
213 return run_as_mkdirat_recursive(handle->dirfd, path, mode, uid, gid);
214}
215
216#else /* COMPAT_DIRFD */
217
fd774fc6
JG
218static
219int get_full_path(const struct lttng_directory_handle *handle,
220 const char *subdirectory, char *fullpath, size_t size)
221{
222 int ret;
223
224 subdirectory = subdirectory ? : "";
225 /*
226 * Don't include the base path if subdirectory is absolute.
227 * This is the same behaviour than mkdirat.
228 */
229 ret = snprintf(fullpath, size, "%s%s",
230 *subdirectory != '/' ? handle->base_path : "",
231 subdirectory);
232 if (ret == -1 || ret >= size) {
233 ERR("Failed to format subdirectory from directory handle");
234 ret = -1;
235 }
236 ret = 0;
237 return ret;
238}
239
18710679
JG
240LTTNG_HIDDEN
241int lttng_directory_handle_init(struct lttng_directory_handle *handle,
242 const char *path)
243{
244 int ret;
18710679 245 const char *cwd;
fd774fc6
JG
246 size_t cwd_len, path_len;
247 char cwd_buf[LTTNG_PATH_MAX] = {};
248 char handle_buf[LTTNG_PATH_MAX] = {};
249 bool add_cwd_slash, add_trailing_slash;
250 const struct lttng_directory_handle cwd_handle = {
251 .base_path = handle_buf,
252 };
253
254 if (path && *path == '/') {
255 /*
256 * Creation of an handle to an absolute path; no need to sample
257 * the cwd.
258 */
259 goto create;
260 }
261 path_len = path ? strlen(path) : 0;
18710679
JG
262
263 cwd = getcwd(cwd_buf, sizeof(cwd_buf));
264 if (!cwd) {
265 PERROR("Failed to initialize directory handle, can't get current working directory");
266 ret = -1;
267 goto end;
268 }
269 cwd_len = strlen(cwd);
270 if (cwd_len == 0) {
fd774fc6 271 ERR("Failed to initialize directory handle, current working directory path has a length of 0");
18710679
JG
272 ret = -1;
273 goto end;
274 }
fd774fc6
JG
275
276 add_cwd_slash = cwd[cwd_len - 1] != '/';
277 add_trailing_slash = path && path[path_len - 1] != '/';
278
279 ret = snprintf(handle_buf, sizeof(handle_buf), "%s%s%s%s",
280 cwd,
281 add_cwd_slash ? "/" : "",
282 path ? : "",
283 add_trailing_slash ? "/" : "");
284 if (ret == -1 || ret >= LTTNG_PATH_MAX) {
285 ERR("Failed to initialize directory handle, failed to format directory path");
286 goto end;
18710679 287 }
fd774fc6
JG
288create:
289 ret = lttng_directory_handle_init_from_handle(handle, path,
290 &cwd_handle);
291end:
292 return ret;
293}
18710679 294
fd774fc6
JG
295LTTNG_HIDDEN
296int lttng_directory_handle_init_from_handle(
297 struct lttng_directory_handle *new_handle, const char *path,
298 const struct lttng_directory_handle *handle)
299{
300 int ret;
301 size_t path_len, handle_path_len;
302 bool add_trailing_slash;
303 struct stat stat_buf;
18710679 304
fd774fc6
JG
305 assert(handle && handle->base_path);
306
307 ret = lttng_directory_handle_stat(handle, path, &stat_buf);
308 if (ret == -1) {
309 PERROR("Failed to create directory handle");
310 goto end;
311 } else if (!S_ISDIR(stat_buf.st_mode)) {
312 char full_path[LTTNG_PATH_MAX];
313
314 /* Best effort for logging purposes. */
315 ret = get_full_path(handle, path, full_path,
316 sizeof(full_path));
317 if (ret) {
318 full_path[0] = '\0';
18710679 319 }
fd774fc6
JG
320
321 ERR("Failed to initialize directory handle to \"%s\": not a directory",
322 full_path);
323 ret = -1;
324 goto end;
325 }
326 if (!path) {
327 ret = lttng_directory_handle_copy(handle, new_handle);
328 goto end;
329 }
330
331 path_len = strlen(path);
332 if (path_len == 0) {
333 ERR("Failed to initialize directory handle: provided path is an empty string");
334 ret = -1;
335 goto end;
336 }
337 if (*path == '/') {
338 new_handle->base_path = strdup(path);
339 ret = new_handle->base_path ? 0 : -1;
340 goto end;
18710679
JG
341 }
342
fd774fc6
JG
343 add_trailing_slash = path[path_len - 1] != '/';
344
345 handle_path_len = strlen(handle->base_path) + path_len +
346 !!add_trailing_slash;
18710679
JG
347 if (handle_path_len >= LTTNG_PATH_MAX) {
348 ERR("Failed to initialize directory handle as the resulting path's length (%zu bytes) exceeds the maximal allowed length (%d bytes)",
349 handle_path_len, LTTNG_PATH_MAX);
350 ret = -1;
351 goto end;
352 }
fd774fc6
JG
353 new_handle->base_path = zmalloc(handle_path_len);
354 if (!new_handle->base_path) {
18710679
JG
355 PERROR("Failed to initialize directory handle");
356 ret = -1;
357 goto end;
358 }
359
fd774fc6
JG
360 ret = sprintf(new_handle->base_path, "%s%s%s",
361 handle->base_path,
362 path,
363 add_trailing_slash ? "/" : "");
18710679
JG
364 if (ret == -1 || ret >= handle_path_len) {
365 ERR("Failed to initialize directory handle: path formatting failed");
366 ret = -1;
367 goto end;
368 }
369end:
370 return ret;
371}
372
373LTTNG_HIDDEN
374int lttng_directory_handle_init_from_dirfd(
375 struct lttng_directory_handle *handle, int dirfd)
376{
377 assert(dirfd == AT_FDCWD);
378 return lttng_directory_handle_init(handle, NULL);
379}
380
381LTTNG_HIDDEN
382void lttng_directory_handle_fini(struct lttng_directory_handle *handle)
383{
384 free(handle->base_path);
46307ffe 385 lttng_directory_handle_invalidate(handle);
18710679
JG
386}
387
578e21bd
JG
388LTTNG_HIDDEN
389int lttng_directory_handle_copy(const struct lttng_directory_handle *handle,
390 struct lttng_directory_handle *new_copy)
391{
392 new_copy->base_path = strdup(handle->base_path);
393 return new_copy->base_path ? 0 : -1;
394}
395
46307ffe
JG
396static
397void lttng_directory_handle_invalidate(struct lttng_directory_handle *handle)
398{
399 handle->base_path = NULL;
400}
401
18710679
JG
402static
403int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
404 const char *subdirectory, struct stat *st)
405{
406 int ret;
407 char fullpath[LTTNG_PATH_MAX];
408
409 ret = get_full_path(handle, subdirectory, fullpath, sizeof(fullpath));
410 if (ret) {
411 errno = ENOMEM;
412 goto end;
413 }
414
415 ret = stat(fullpath, st);
416end:
417 return ret;
418}
419
420static
421int lttng_directory_handle_mkdir(const struct lttng_directory_handle *handle,
422 const char *subdirectory, mode_t mode)
423{
424 int ret;
425 char fullpath[LTTNG_PATH_MAX];
426
427 ret = get_full_path(handle, subdirectory, fullpath, sizeof(fullpath));
428 if (ret) {
429 errno = ENOMEM;
430 goto end;
431 }
432
433 ret = mkdir(fullpath, mode);
434end:
435 return ret;
436}
437
2912cead
JG
438static
439int lttng_directory_handle_open(const struct lttng_directory_handle *handle,
440 const char *filename, int flags, mode_t mode)
441{
442 int ret;
443 char fullpath[LTTNG_PATH_MAX];
444
445 ret = get_full_path(handle, filename, fullpath, sizeof(fullpath));
446 if (ret) {
447 errno = ENOMEM;
448 goto end;
449 }
450
451 ret = open(fullpath, flags, mode);
452end:
453 return ret;
454}
455
456static
457int lttng_directory_handle_unlink(
458 const struct lttng_directory_handle *handle,
459 const char *filename)
460{
461 int ret;
462 char fullpath[LTTNG_PATH_MAX];
463
464 ret = get_full_path(handle, filename, fullpath, sizeof(fullpath));
465 if (ret) {
466 errno = ENOMEM;
467 goto end;
468 }
469
470 ret = unlink(fullpath);
471end:
472 return ret;
473}
474
18710679
JG
475static
476int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path,
477 mode_t mode, uid_t uid, gid_t gid)
478{
479 int ret;
480 char fullpath[LTTNG_PATH_MAX];
481
482 ret = get_full_path(handle, path, fullpath, sizeof(fullpath));
483 if (ret) {
484 errno = ENOMEM;
485 goto end;
486 }
487
488 ret = run_as_mkdir(fullpath, mode, uid, gid);
489end:
490 return ret;
491}
492
2912cead
JG
493static
494int _run_as_open(const struct lttng_directory_handle *handle,
495 const char *filename,
496 int flags, mode_t mode, uid_t uid, gid_t gid)
497{
498 int ret;
499 char fullpath[LTTNG_PATH_MAX];
500
501 ret = get_full_path(handle, filename, fullpath, sizeof(fullpath));
502 if (ret) {
503 errno = ENOMEM;
504 goto end;
505 }
506
507 ret = run_as_open(fullpath, flags, mode, uid, gid);
508end:
509 return ret;
510}
511
512static
513int _run_as_unlink(const struct lttng_directory_handle *handle,
514 const char *filename, uid_t uid, gid_t gid)
515{
516 int ret;
517 char fullpath[LTTNG_PATH_MAX];
518
519 ret = get_full_path(handle, filename, fullpath, sizeof(fullpath));
520 if (ret) {
521 errno = ENOMEM;
522 goto end;
523 }
524
525 ret = run_as_unlink(fullpath, uid, gid);
526end:
527 return ret;
528}
529
18710679
JG
530static
531int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle,
532 const char *path, mode_t mode, uid_t uid, gid_t gid)
533{
534 int ret;
535 char fullpath[LTTNG_PATH_MAX];
536
537 ret = get_full_path(handle, path, fullpath, sizeof(fullpath));
538 if (ret) {
539 errno = ENOMEM;
540 goto end;
541 }
542
543 ret = run_as_mkdir_recursive(fullpath, mode, uid, gid);
544end:
545 return ret;
546}
547
548#endif /* COMPAT_DIRFD */
549
550/*
551 * On some filesystems (e.g. nfs), mkdir will validate access rights before
552 * checking for the existence of the path element. This means that on a setup
553 * where "/home/" is a mounted NFS share, and running as an unpriviledged user,
554 * recursively creating a path of the form "/home/my_user/trace/" will fail with
555 * EACCES on mkdir("/home", ...).
556 *
557 * Checking the path for existence allows us to work around this behaviour.
558 */
559static
560int create_directory_check_exists(const struct lttng_directory_handle *handle,
561 const char *path, mode_t mode)
562{
563 int ret = 0;
564 struct stat st;
565
566 ret = lttng_directory_handle_stat(handle, path, &st);
567 if (ret == 0) {
568 if (S_ISDIR(st.st_mode)) {
569 /* Directory exists, skip. */
570 goto end;
571 } else {
572 /* Exists, but is not a directory. */
573 errno = ENOTDIR;
574 ret = -1;
575 goto end;
576 }
577 }
578
579 /*
580 * Let mkdir handle other errors as the caller expects mkdir
581 * semantics.
582 */
583 ret = lttng_directory_handle_mkdir(handle, path, mode);
584end:
585 return ret;
586}
587
588/* Common implementation. */
46307ffe
JG
589LTTNG_HIDDEN
590struct lttng_directory_handle
591lttng_directory_handle_move(struct lttng_directory_handle *original)
592{
593 const struct lttng_directory_handle tmp = *original;
594
595 lttng_directory_handle_invalidate(original);
596 return tmp;
597}
598
18710679
JG
599static
600int create_directory_recursive(const struct lttng_directory_handle *handle,
601 const char *path, mode_t mode)
602{
603 char *p, tmp[LTTNG_PATH_MAX];
604 size_t len;
605 int ret;
606
607 assert(path);
608
609 ret = lttng_strncpy(tmp, path, sizeof(tmp));
610 if (ret) {
611 ERR("Failed to create directory: provided path's length (%zu bytes) exceeds the maximal allowed length (%zu bytes)",
612 strlen(path) + 1, sizeof(tmp));
613 goto error;
614 }
615
616 len = strlen(path);
617 if (tmp[len - 1] == '/') {
618 tmp[len - 1] = 0;
619 }
620
621 for (p = tmp + 1; *p; p++) {
622 if (*p == '/') {
623 *p = 0;
624 if (tmp[strlen(tmp) - 1] == '.' &&
625 tmp[strlen(tmp) - 2] == '.' &&
626 tmp[strlen(tmp) - 3] == '/') {
627 ERR("Using '/../' is not permitted in the trace path (%s)",
628 tmp);
629 ret = -1;
630 goto error;
631 }
632 ret = create_directory_check_exists(handle, tmp, mode);
633 if (ret < 0) {
634 if (errno != EACCES) {
635 PERROR("Failed to create directory \"%s\"",
636 path);
637 ret = -errno;
638 goto error;
639 }
640 }
641 *p = '/';
642 }
643 }
644
645 ret = create_directory_check_exists(handle, tmp, mode);
646 if (ret < 0) {
647 PERROR("mkdirat recursive last element");
648 ret = -errno;
649 }
650error:
651 return ret;
652}
653
654LTTNG_HIDDEN
655int lttng_directory_handle_create_subdirectory_as_user(
656 const struct lttng_directory_handle *handle,
657 const char *subdirectory,
69e3a560 658 mode_t mode, const struct lttng_credentials *creds)
18710679
JG
659{
660 int ret;
661
662 if (!creds) {
663 /* Run as current user. */
664 ret = create_directory_check_exists(handle,
665 subdirectory, mode);
666 } else {
667 ret = _run_as_mkdir(handle, subdirectory,
668 mode, creds->uid, creds->gid);
669 }
670
671 return ret;
672}
673
674LTTNG_HIDDEN
675int lttng_directory_handle_create_subdirectory_recursive_as_user(
676 const struct lttng_directory_handle *handle,
677 const char *subdirectory_path,
69e3a560 678 mode_t mode, const struct lttng_credentials *creds)
18710679
JG
679{
680 int ret;
681
682 if (!creds) {
683 /* Run as current user. */
684 ret = create_directory_recursive(handle,
685 subdirectory_path, mode);
686 } else {
687 ret = _run_as_mkdir_recursive(handle, subdirectory_path,
688 mode, creds->uid, creds->gid);
689 }
690
691 return ret;
692}
693
694LTTNG_HIDDEN
695int lttng_directory_handle_create_subdirectory(
696 const struct lttng_directory_handle *handle,
697 const char *subdirectory,
698 mode_t mode)
699{
700 return lttng_directory_handle_create_subdirectory_as_user(
701 handle, subdirectory, mode, NULL);
702}
703
704LTTNG_HIDDEN
705int lttng_directory_handle_create_subdirectory_recursive(
706 const struct lttng_directory_handle *handle,
707 const char *subdirectory_path,
708 mode_t mode)
709{
710 return lttng_directory_handle_create_subdirectory_recursive_as_user(
711 handle, subdirectory_path, mode, NULL);
712}
2912cead
JG
713
714LTTNG_HIDDEN
715int lttng_directory_handle_open_file_as_user(
716 const struct lttng_directory_handle *handle,
717 const char *filename,
718 int flags, mode_t mode,
719 const struct lttng_credentials *creds)
720{
721 int ret;
722
723 if (!creds) {
724 /* Run as current user. */
725 ret = lttng_directory_handle_open(handle, filename, flags,
726 mode);
727 } else {
728 ret = _run_as_open(handle, filename, flags, mode,
729 creds->uid, creds->gid);
730 }
731 return ret;
732}
733
734LTTNG_HIDDEN
735int lttng_directory_handle_open_file(
736 const struct lttng_directory_handle *handle,
737 const char *filename,
738 int flags, mode_t mode)
739{
740 return lttng_directory_handle_open_file_as_user(handle, filename, flags,
741 mode, NULL);
742}
743
744LTTNG_HIDDEN
745int lttng_directory_handle_unlink_file_as_user(
746 const struct lttng_directory_handle *handle,
747 const char *filename,
748 const struct lttng_credentials *creds)
749{
750 int ret;
751
752 if (!creds) {
753 /* Run as current user. */
754 ret = lttng_directory_handle_unlink(handle, filename);
755 } else {
756 ret = _run_as_unlink(handle, filename, creds->uid, creds->gid);
757 }
758 return ret;
759}
760
761LTTNG_HIDDEN
762int lttng_directory_handle_unlink_file(
763 const struct lttng_directory_handle *handle,
764 const char *filename)
765{
766 return lttng_directory_handle_unlink_file_as_user(handle,
767 filename, NULL);
768}
This page took 0.052195 seconds and 4 git commands to generate.