Add mkdirat utils and runas wrappers
[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
91static
92int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
93 const char *path, struct stat *st)
94{
95 return fstatat(handle->dirfd, path, st, 0);
96}
97
98static
99int lttng_directory_handle_mkdir(
100 const struct lttng_directory_handle *handle,
101 const char *path, mode_t mode)
102{
103 return mkdirat(handle->dirfd, path, mode);
104}
105
106static
107int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path,
108 mode_t mode, uid_t uid, gid_t gid)
109{
110 return run_as_mkdirat(handle->dirfd, path, mode, uid, gid);
111}
112
113static
114int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle,
115 const char *path, mode_t mode, uid_t uid, gid_t gid)
116{
117 return run_as_mkdirat_recursive(handle->dirfd, path, mode, uid, gid);
118}
119
120#else /* COMPAT_DIRFD */
121
122LTTNG_HIDDEN
123int lttng_directory_handle_init(struct lttng_directory_handle *handle,
124 const char *path)
125{
126 int ret;
127 size_t cwd_len, path_len, handle_path_len;
128 char cwd_buf[LTTNG_PATH_MAX];
129 const char *cwd;
130 bool add_slash = false;
131 struct stat stat_buf;
132
133 cwd = getcwd(cwd_buf, sizeof(cwd_buf));
134 if (!cwd) {
135 PERROR("Failed to initialize directory handle, can't get current working directory");
136 ret = -1;
137 goto end;
138 }
139 cwd_len = strlen(cwd);
140 if (cwd_len == 0) {
141 ERR("Failed to initialize directory handle to \"%s\": getcwd() returned an empty string",
142 path);
143 ret = -1;
144 goto end;
145 }
146 if (cwd[cwd_len - 1] != '/') {
147 add_slash = true;
148 }
149
150 if (path) {
151 path_len = strlen(path);
152 if (path_len == 0) {
153 ERR("Failed to initialize directory handle: provided path is an empty string");
154 ret = -1;
155 goto end;
156 }
157
158 /*
159 * Ensure that 'path' is a directory. There is a race
160 * (TOCTOU) since the directory could be removed/replaced/renamed,
161 * but this is inevitable on platforms that don't provide dirfd support.
162 */
163 ret = stat(path, &stat_buf);
164 if (ret == -1) {
165 PERROR("Failed to initialize directory handle to \"%s\", stat() failed",
166 path);
167 goto end;
168 }
169 if (!S_ISDIR(stat_buf.st_mode)) {
170 ERR("Failed to initialize directory handle to \"%s\": not a directory",
171 path);
172 ret = -1;
173 goto end;
174 }
175 if (*path == '/') {
176 handle->base_path = strdup(path);
177 if (!handle->base_path) {
178 ret = -1;
179 }
180 /* Not an error. */
181 goto end;
182 }
183 } else {
184 path = "";
185 path_len = 0;
186 add_slash = false;
187 }
188
189 handle_path_len = cwd_len + path_len + !!add_slash + 2;
190 if (handle_path_len >= LTTNG_PATH_MAX) {
191 ERR("Failed to initialize directory handle as the resulting path's length (%zu bytes) exceeds the maximal allowed length (%d bytes)",
192 handle_path_len, LTTNG_PATH_MAX);
193 ret = -1;
194 goto end;
195 }
196 handle->base_path = zmalloc(handle_path_len);
197 if (!handle->base_path) {
198 PERROR("Failed to initialize directory handle");
199 ret = -1;
200 goto end;
201 }
202
203 ret = sprintf(handle->base_path, "%s%s%s/", cwd,
204 add_slash ? "/" : "", path);
205 if (ret == -1 || ret >= handle_path_len) {
206 ERR("Failed to initialize directory handle: path formatting failed");
207 ret = -1;
208 goto end;
209 }
210end:
211 return ret;
212}
213
214LTTNG_HIDDEN
215int lttng_directory_handle_init_from_dirfd(
216 struct lttng_directory_handle *handle, int dirfd)
217{
218 assert(dirfd == AT_FDCWD);
219 return lttng_directory_handle_init(handle, NULL);
220}
221
222LTTNG_HIDDEN
223void lttng_directory_handle_fini(struct lttng_directory_handle *handle)
224{
225 free(handle->base_path);
226}
227
228static
229int get_full_path(const struct lttng_directory_handle *handle,
230 const char *subdirectory, char *fullpath, size_t size)
231{
232 int ret;
233
234 /*
235 * Don't include the base path if subdirectory is absolute.
236 * This is the same behaviour than mkdirat.
237 */
238 ret = snprintf(fullpath, size, "%s%s",
239 *subdirectory != '/' ? handle->base_path : "",
240 subdirectory);
241 if (ret == -1 || ret >= size) {
242 ERR("Failed to format subdirectory from directory handle");
243 ret = -1;
244 }
245 ret = 0;
246 return ret;
247}
248
249static
250int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
251 const char *subdirectory, struct stat *st)
252{
253 int ret;
254 char fullpath[LTTNG_PATH_MAX];
255
256 ret = get_full_path(handle, subdirectory, fullpath, sizeof(fullpath));
257 if (ret) {
258 errno = ENOMEM;
259 goto end;
260 }
261
262 ret = stat(fullpath, st);
263end:
264 return ret;
265}
266
267static
268int lttng_directory_handle_mkdir(const struct lttng_directory_handle *handle,
269 const char *subdirectory, mode_t mode)
270{
271 int ret;
272 char fullpath[LTTNG_PATH_MAX];
273
274 ret = get_full_path(handle, subdirectory, fullpath, sizeof(fullpath));
275 if (ret) {
276 errno = ENOMEM;
277 goto end;
278 }
279
280 ret = mkdir(fullpath, mode);
281end:
282 return ret;
283}
284
285static
286int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path,
287 mode_t mode, uid_t uid, gid_t gid)
288{
289 int ret;
290 char fullpath[LTTNG_PATH_MAX];
291
292 ret = get_full_path(handle, path, fullpath, sizeof(fullpath));
293 if (ret) {
294 errno = ENOMEM;
295 goto end;
296 }
297
298 ret = run_as_mkdir(fullpath, mode, uid, gid);
299end:
300 return ret;
301}
302
303static
304int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle,
305 const char *path, mode_t mode, uid_t uid, gid_t gid)
306{
307 int ret;
308 char fullpath[LTTNG_PATH_MAX];
309
310 ret = get_full_path(handle, path, fullpath, sizeof(fullpath));
311 if (ret) {
312 errno = ENOMEM;
313 goto end;
314 }
315
316 ret = run_as_mkdir_recursive(fullpath, mode, uid, gid);
317end:
318 return ret;
319}
320
321#endif /* COMPAT_DIRFD */
322
323/*
324 * On some filesystems (e.g. nfs), mkdir will validate access rights before
325 * checking for the existence of the path element. This means that on a setup
326 * where "/home/" is a mounted NFS share, and running as an unpriviledged user,
327 * recursively creating a path of the form "/home/my_user/trace/" will fail with
328 * EACCES on mkdir("/home", ...).
329 *
330 * Checking the path for existence allows us to work around this behaviour.
331 */
332static
333int create_directory_check_exists(const struct lttng_directory_handle *handle,
334 const char *path, mode_t mode)
335{
336 int ret = 0;
337 struct stat st;
338
339 ret = lttng_directory_handle_stat(handle, path, &st);
340 if (ret == 0) {
341 if (S_ISDIR(st.st_mode)) {
342 /* Directory exists, skip. */
343 goto end;
344 } else {
345 /* Exists, but is not a directory. */
346 errno = ENOTDIR;
347 ret = -1;
348 goto end;
349 }
350 }
351
352 /*
353 * Let mkdir handle other errors as the caller expects mkdir
354 * semantics.
355 */
356 ret = lttng_directory_handle_mkdir(handle, path, mode);
357end:
358 return ret;
359}
360
361/* Common implementation. */
362static
363int create_directory_recursive(const struct lttng_directory_handle *handle,
364 const char *path, mode_t mode)
365{
366 char *p, tmp[LTTNG_PATH_MAX];
367 size_t len;
368 int ret;
369
370 assert(path);
371
372 ret = lttng_strncpy(tmp, path, sizeof(tmp));
373 if (ret) {
374 ERR("Failed to create directory: provided path's length (%zu bytes) exceeds the maximal allowed length (%zu bytes)",
375 strlen(path) + 1, sizeof(tmp));
376 goto error;
377 }
378
379 len = strlen(path);
380 if (tmp[len - 1] == '/') {
381 tmp[len - 1] = 0;
382 }
383
384 for (p = tmp + 1; *p; p++) {
385 if (*p == '/') {
386 *p = 0;
387 if (tmp[strlen(tmp) - 1] == '.' &&
388 tmp[strlen(tmp) - 2] == '.' &&
389 tmp[strlen(tmp) - 3] == '/') {
390 ERR("Using '/../' is not permitted in the trace path (%s)",
391 tmp);
392 ret = -1;
393 goto error;
394 }
395 ret = create_directory_check_exists(handle, tmp, mode);
396 if (ret < 0) {
397 if (errno != EACCES) {
398 PERROR("Failed to create directory \"%s\"",
399 path);
400 ret = -errno;
401 goto error;
402 }
403 }
404 *p = '/';
405 }
406 }
407
408 ret = create_directory_check_exists(handle, tmp, mode);
409 if (ret < 0) {
410 PERROR("mkdirat recursive last element");
411 ret = -errno;
412 }
413error:
414 return ret;
415}
416
417LTTNG_HIDDEN
418int lttng_directory_handle_create_subdirectory_as_user(
419 const struct lttng_directory_handle *handle,
420 const char *subdirectory,
421 mode_t mode, struct lttng_credentials *creds)
422{
423 int ret;
424
425 if (!creds) {
426 /* Run as current user. */
427 ret = create_directory_check_exists(handle,
428 subdirectory, mode);
429 } else {
430 ret = _run_as_mkdir(handle, subdirectory,
431 mode, creds->uid, creds->gid);
432 }
433
434 return ret;
435}
436
437LTTNG_HIDDEN
438int lttng_directory_handle_create_subdirectory_recursive_as_user(
439 const struct lttng_directory_handle *handle,
440 const char *subdirectory_path,
441 mode_t mode, struct lttng_credentials *creds)
442{
443 int ret;
444
445 if (!creds) {
446 /* Run as current user. */
447 ret = create_directory_recursive(handle,
448 subdirectory_path, mode);
449 } else {
450 ret = _run_as_mkdir_recursive(handle, subdirectory_path,
451 mode, creds->uid, creds->gid);
452 }
453
454 return ret;
455}
456
457LTTNG_HIDDEN
458int lttng_directory_handle_create_subdirectory(
459 const struct lttng_directory_handle *handle,
460 const char *subdirectory,
461 mode_t mode)
462{
463 return lttng_directory_handle_create_subdirectory_as_user(
464 handle, subdirectory, mode, NULL);
465}
466
467LTTNG_HIDDEN
468int lttng_directory_handle_create_subdirectory_recursive(
469 const struct lttng_directory_handle *handle,
470 const char *subdirectory_path,
471 mode_t mode)
472{
473 return lttng_directory_handle_create_subdirectory_recursive_as_user(
474 handle, subdirectory_path, mode, NULL);
475}
This page took 0.061943 seconds and 4 git commands to generate.