Add mkdirat utils and runas wrappers
[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 static
92 int 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
98 static
99 int 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
106 static
107 int _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
113 static
114 int _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
122 LTTNG_HIDDEN
123 int 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 }
210 end:
211 return ret;
212 }
213
214 LTTNG_HIDDEN
215 int 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
222 LTTNG_HIDDEN
223 void lttng_directory_handle_fini(struct lttng_directory_handle *handle)
224 {
225 free(handle->base_path);
226 }
227
228 static
229 int 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
249 static
250 int 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);
263 end:
264 return ret;
265 }
266
267 static
268 int 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);
281 end:
282 return ret;
283 }
284
285 static
286 int _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);
299 end:
300 return ret;
301 }
302
303 static
304 int _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);
317 end:
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 */
332 static
333 int 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);
357 end:
358 return ret;
359 }
360
361 /* Common implementation. */
362 static
363 int 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 }
413 error:
414 return ret;
415 }
416
417 LTTNG_HIDDEN
418 int 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
437 LTTNG_HIDDEN
438 int 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
457 LTTNG_HIDDEN
458 int 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
467 LTTNG_HIDDEN
468 int 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.039945 seconds and 5 git commands to generate.