2 * Copyright (C) 2019 - Jérémie Galarneau <jeremie.galarneau@efficios.com>
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.
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
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.
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>
26 #include <sys/types.h>
32 int lttng_directory_handle_stat(const struct lttng_directory_handle
*handle
,
33 const char *path
, struct stat
*st
);
35 int lttng_directory_handle_mkdir(
36 const struct lttng_directory_handle
*handle
,
37 const char *path
, mode_t mode
);
39 int _run_as_mkdir(const struct lttng_directory_handle
*handle
, const char *path
,
40 mode_t mode
, uid_t uid
, gid_t gid
);
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
);
45 void lttng_directory_handle_invalidate(struct lttng_directory_handle
*handle
);
50 int lttng_directory_handle_init(struct lttng_directory_handle
*new_handle
,
53 const struct lttng_directory_handle cwd_handle
= {
57 /* Open a handle to the CWD if NULL is passed. */
58 return lttng_directory_handle_init_from_handle(new_handle
,
64 int lttng_directory_handle_init_from_handle(
65 struct lttng_directory_handle
*new_handle
, const char *path
,
66 const struct lttng_directory_handle
*handle
)
71 ret
= lttng_directory_handle_copy(handle
, new_handle
);
75 ERR("Failed to initialize directory handle: provided path is an empty string");
79 ret
= openat(handle
->dirfd
, path
, O_RDONLY
| O_DIRECTORY
| O_CLOEXEC
);
81 PERROR("Failed to initialize directory handle to \"%s\"", path
);
84 new_handle
->dirfd
= ret
;
91 int lttng_directory_handle_init_from_dirfd(
92 struct lttng_directory_handle
*handle
, int dirfd
)
94 handle
->dirfd
= dirfd
;
99 void lttng_directory_handle_fini(struct lttng_directory_handle
*handle
)
103 if (handle
->dirfd
== AT_FDCWD
|| handle
->dirfd
== -1) {
106 ret
= close(handle
->dirfd
);
108 PERROR("Failed to close directory file descriptor of directory handle");
111 lttng_directory_handle_invalidate(handle
);
115 int lttng_directory_handle_copy(const struct lttng_directory_handle
*handle
,
116 struct lttng_directory_handle
*new_copy
)
120 if (handle
->dirfd
== AT_FDCWD
) {
121 new_copy
->dirfd
= handle
->dirfd
;
123 new_copy
->dirfd
= dup(handle
->dirfd
);
124 if (new_copy
->dirfd
== -1) {
125 PERROR("Failed to duplicate directory fd of directory handle");
133 void lttng_directory_handle_invalidate(struct lttng_directory_handle
*handle
)
139 int lttng_directory_handle_stat(const struct lttng_directory_handle
*handle
,
140 const char *path
, struct stat
*st
)
142 return fstatat(handle
->dirfd
, path
, st
, 0);
146 int lttng_directory_handle_mkdir(
147 const struct lttng_directory_handle
*handle
,
148 const char *path
, mode_t mode
)
150 return mkdirat(handle
->dirfd
, path
, mode
);
154 int _run_as_mkdir(const struct lttng_directory_handle
*handle
, const char *path
,
155 mode_t mode
, uid_t uid
, gid_t gid
)
157 return run_as_mkdirat(handle
->dirfd
, path
, mode
, uid
, gid
);
161 int _run_as_mkdir_recursive(const struct lttng_directory_handle
*handle
,
162 const char *path
, mode_t mode
, uid_t uid
, gid_t gid
)
164 return run_as_mkdirat_recursive(handle
->dirfd
, path
, mode
, uid
, gid
);
167 #else /* COMPAT_DIRFD */
170 int get_full_path(const struct lttng_directory_handle
*handle
,
171 const char *subdirectory
, char *fullpath
, size_t size
)
175 subdirectory
= subdirectory
? : "";
177 * Don't include the base path if subdirectory is absolute.
178 * This is the same behaviour than mkdirat.
180 ret
= snprintf(fullpath
, size
, "%s%s",
181 *subdirectory
!= '/' ? handle
->base_path
: "",
183 if (ret
== -1 || ret
>= size
) {
184 ERR("Failed to format subdirectory from directory handle");
192 int lttng_directory_handle_init(struct lttng_directory_handle
*handle
,
197 size_t cwd_len
, path_len
;
198 char cwd_buf
[LTTNG_PATH_MAX
] = {};
199 char handle_buf
[LTTNG_PATH_MAX
] = {};
200 bool add_cwd_slash
, add_trailing_slash
;
201 const struct lttng_directory_handle cwd_handle
= {
202 .base_path
= handle_buf
,
205 if (path
&& *path
== '/') {
207 * Creation of an handle to an absolute path; no need to sample
212 path_len
= path
? strlen(path
) : 0;
214 cwd
= getcwd(cwd_buf
, sizeof(cwd_buf
));
216 PERROR("Failed to initialize directory handle, can't get current working directory");
220 cwd_len
= strlen(cwd
);
222 ERR("Failed to initialize directory handle, current working directory path has a length of 0");
227 add_cwd_slash
= cwd
[cwd_len
- 1] != '/';
228 add_trailing_slash
= path
&& path
[path_len
- 1] != '/';
230 ret
= snprintf(handle_buf
, sizeof(handle_buf
), "%s%s%s%s",
232 add_cwd_slash
? "/" : "",
234 add_trailing_slash
? "/" : "");
235 if (ret
== -1 || ret
>= LTTNG_PATH_MAX
) {
236 ERR("Failed to initialize directory handle, failed to format directory path");
240 ret
= lttng_directory_handle_init_from_handle(handle
, path
,
247 int lttng_directory_handle_init_from_handle(
248 struct lttng_directory_handle
*new_handle
, const char *path
,
249 const struct lttng_directory_handle
*handle
)
252 size_t path_len
, handle_path_len
;
253 bool add_trailing_slash
;
254 struct stat stat_buf
;
256 assert(handle
&& handle
->base_path
);
258 ret
= lttng_directory_handle_stat(handle
, path
, &stat_buf
);
260 PERROR("Failed to create directory handle");
262 } else if (!S_ISDIR(stat_buf
.st_mode
)) {
263 char full_path
[LTTNG_PATH_MAX
];
265 /* Best effort for logging purposes. */
266 ret
= get_full_path(handle
, path
, full_path
,
272 ERR("Failed to initialize directory handle to \"%s\": not a directory",
278 ret
= lttng_directory_handle_copy(handle
, new_handle
);
282 path_len
= strlen(path
);
284 ERR("Failed to initialize directory handle: provided path is an empty string");
289 new_handle
->base_path
= strdup(path
);
290 ret
= new_handle
->base_path
? 0 : -1;
294 add_trailing_slash
= path
[path_len
- 1] != '/';
296 handle_path_len
= strlen(handle
->base_path
) + path_len
+
297 !!add_trailing_slash
;
298 if (handle_path_len
>= LTTNG_PATH_MAX
) {
299 ERR("Failed to initialize directory handle as the resulting path's length (%zu bytes) exceeds the maximal allowed length (%d bytes)",
300 handle_path_len
, LTTNG_PATH_MAX
);
304 new_handle
->base_path
= zmalloc(handle_path_len
);
305 if (!new_handle
->base_path
) {
306 PERROR("Failed to initialize directory handle");
311 ret
= sprintf(new_handle
->base_path
, "%s%s%s",
314 add_trailing_slash
? "/" : "");
315 if (ret
== -1 || ret
>= handle_path_len
) {
316 ERR("Failed to initialize directory handle: path formatting failed");
325 int lttng_directory_handle_init_from_dirfd(
326 struct lttng_directory_handle
*handle
, int dirfd
)
328 assert(dirfd
== AT_FDCWD
);
329 return lttng_directory_handle_init(handle
, NULL
);
333 void lttng_directory_handle_fini(struct lttng_directory_handle
*handle
)
335 free(handle
->base_path
);
336 lttng_directory_handle_invalidate(handle
);
340 int lttng_directory_handle_copy(const struct lttng_directory_handle
*handle
,
341 struct lttng_directory_handle
*new_copy
)
343 new_copy
->base_path
= strdup(handle
->base_path
);
344 return new_copy
->base_path
? 0 : -1;
348 void lttng_directory_handle_invalidate(struct lttng_directory_handle
*handle
)
350 handle
->base_path
= NULL
;
354 int lttng_directory_handle_stat(const struct lttng_directory_handle
*handle
,
355 const char *subdirectory
, struct stat
*st
)
358 char fullpath
[LTTNG_PATH_MAX
];
360 ret
= get_full_path(handle
, subdirectory
, fullpath
, sizeof(fullpath
));
366 ret
= stat(fullpath
, st
);
372 int lttng_directory_handle_mkdir(const struct lttng_directory_handle
*handle
,
373 const char *subdirectory
, mode_t mode
)
376 char fullpath
[LTTNG_PATH_MAX
];
378 ret
= get_full_path(handle
, subdirectory
, fullpath
, sizeof(fullpath
));
384 ret
= mkdir(fullpath
, mode
);
390 int _run_as_mkdir(const struct lttng_directory_handle
*handle
, const char *path
,
391 mode_t mode
, uid_t uid
, gid_t gid
)
394 char fullpath
[LTTNG_PATH_MAX
];
396 ret
= get_full_path(handle
, path
, fullpath
, sizeof(fullpath
));
402 ret
= run_as_mkdir(fullpath
, mode
, uid
, gid
);
408 int _run_as_mkdir_recursive(const struct lttng_directory_handle
*handle
,
409 const char *path
, mode_t mode
, uid_t uid
, gid_t gid
)
412 char fullpath
[LTTNG_PATH_MAX
];
414 ret
= get_full_path(handle
, path
, fullpath
, sizeof(fullpath
));
420 ret
= run_as_mkdir_recursive(fullpath
, mode
, uid
, gid
);
425 #endif /* COMPAT_DIRFD */
428 * On some filesystems (e.g. nfs), mkdir will validate access rights before
429 * checking for the existence of the path element. This means that on a setup
430 * where "/home/" is a mounted NFS share, and running as an unpriviledged user,
431 * recursively creating a path of the form "/home/my_user/trace/" will fail with
432 * EACCES on mkdir("/home", ...).
434 * Checking the path for existence allows us to work around this behaviour.
437 int create_directory_check_exists(const struct lttng_directory_handle
*handle
,
438 const char *path
, mode_t mode
)
443 ret
= lttng_directory_handle_stat(handle
, path
, &st
);
445 if (S_ISDIR(st
.st_mode
)) {
446 /* Directory exists, skip. */
449 /* Exists, but is not a directory. */
457 * Let mkdir handle other errors as the caller expects mkdir
460 ret
= lttng_directory_handle_mkdir(handle
, path
, mode
);
465 /* Common implementation. */
467 struct lttng_directory_handle
468 lttng_directory_handle_move(struct lttng_directory_handle
*original
)
470 const struct lttng_directory_handle tmp
= *original
;
472 lttng_directory_handle_invalidate(original
);
477 int create_directory_recursive(const struct lttng_directory_handle
*handle
,
478 const char *path
, mode_t mode
)
480 char *p
, tmp
[LTTNG_PATH_MAX
];
486 ret
= lttng_strncpy(tmp
, path
, sizeof(tmp
));
488 ERR("Failed to create directory: provided path's length (%zu bytes) exceeds the maximal allowed length (%zu bytes)",
489 strlen(path
) + 1, sizeof(tmp
));
494 if (tmp
[len
- 1] == '/') {
498 for (p
= tmp
+ 1; *p
; p
++) {
501 if (tmp
[strlen(tmp
) - 1] == '.' &&
502 tmp
[strlen(tmp
) - 2] == '.' &&
503 tmp
[strlen(tmp
) - 3] == '/') {
504 ERR("Using '/../' is not permitted in the trace path (%s)",
509 ret
= create_directory_check_exists(handle
, tmp
, mode
);
511 if (errno
!= EACCES
) {
512 PERROR("Failed to create directory \"%s\"",
522 ret
= create_directory_check_exists(handle
, tmp
, mode
);
524 PERROR("mkdirat recursive last element");
532 int lttng_directory_handle_create_subdirectory_as_user(
533 const struct lttng_directory_handle
*handle
,
534 const char *subdirectory
,
535 mode_t mode
, const struct lttng_credentials
*creds
)
540 /* Run as current user. */
541 ret
= create_directory_check_exists(handle
,
544 ret
= _run_as_mkdir(handle
, subdirectory
,
545 mode
, creds
->uid
, creds
->gid
);
552 int lttng_directory_handle_create_subdirectory_recursive_as_user(
553 const struct lttng_directory_handle
*handle
,
554 const char *subdirectory_path
,
555 mode_t mode
, const struct lttng_credentials
*creds
)
560 /* Run as current user. */
561 ret
= create_directory_recursive(handle
,
562 subdirectory_path
, mode
);
564 ret
= _run_as_mkdir_recursive(handle
, subdirectory_path
,
565 mode
, creds
->uid
, creds
->gid
);
572 int lttng_directory_handle_create_subdirectory(
573 const struct lttng_directory_handle
*handle
,
574 const char *subdirectory
,
577 return lttng_directory_handle_create_subdirectory_as_user(
578 handle
, subdirectory
, mode
, NULL
);
582 int lttng_directory_handle_create_subdirectory_recursive(
583 const struct lttng_directory_handle
*handle
,
584 const char *subdirectory_path
,
587 return lttng_directory_handle_create_subdirectory_recursive_as_user(
588 handle
, subdirectory_path
, mode
, NULL
);