Add a copy method to lttng_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
31static
32int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
33 const char *path, struct stat *st);
34static
35int lttng_directory_handle_mkdir(
36 const struct lttng_directory_handle *handle,
37 const char *path, mode_t mode);
38static
39int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path,
40 mode_t mode, uid_t uid, gid_t gid);
41static
42int _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
47LTTNG_HIDDEN
48int 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;
65end:
66 return ret;
67}
68
69LTTNG_HIDDEN
70int lttng_directory_handle_init_from_dirfd(
71 struct lttng_directory_handle *handle, int dirfd)
72{
73 handle->dirfd = dirfd;
74 return 0;
75}
76
77LTTNG_HIDDEN
78void 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
578e21bd
JG
91LTTNG_HIDDEN
92int 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
18710679
JG
109static
110int 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
116static
117int 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
124static
125int _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
131static
132int _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
140LTTNG_HIDDEN
141int 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 }
228end:
229 return ret;
230}
231
232LTTNG_HIDDEN
233int 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
240LTTNG_HIDDEN
241void lttng_directory_handle_fini(struct lttng_directory_handle *handle)
242{
243 free(handle->base_path);
244}
245
578e21bd
JG
246LTTNG_HIDDEN
247int 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
18710679
JG
254static
255int 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
275static
276int 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);
289end:
290 return ret;
291}
292
293static
294int 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);
307end:
308 return ret;
309}
310
311static
312int _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);
325end:
326 return ret;
327}
328
329static
330int _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);
343end:
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 */
358static
359int 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);
383end:
384 return ret;
385}
386
387/* Common implementation. */
388static
389int 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 }
439error:
440 return ret;
441}
442
443LTTNG_HIDDEN
444int lttng_directory_handle_create_subdirectory_as_user(
445 const struct lttng_directory_handle *handle,
446 const char *subdirectory,
69e3a560 447 mode_t mode, const struct lttng_credentials *creds)
18710679
JG
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
463LTTNG_HIDDEN
464int lttng_directory_handle_create_subdirectory_recursive_as_user(
465 const struct lttng_directory_handle *handle,
466 const char *subdirectory_path,
69e3a560 467 mode_t mode, const struct lttng_credentials *creds)
18710679
JG
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
483LTTNG_HIDDEN
484int 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
493LTTNG_HIDDEN
494int 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.042734 seconds and 5 git commands to generate.