Fix: directory handle credentials parameter is not const
[lttng-tools.git] / src / common / utils.c
... / ...
CommitLineData
1/*
2 * Copyright (C) 2012 - David Goulet <dgoulet@efficios.com>
3 * Copyright (C) 2013 - Raphaël Beamonte <raphael.beamonte@gmail.com>
4 * Copyright (C) 2013 - Jérémie Galarneau <jeremie.galarneau@efficios.com>
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License, version 2 only, as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 51
17 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#define _LGPL_SOURCE
21#include <assert.h>
22#include <ctype.h>
23#include <fcntl.h>
24#include <limits.h>
25#include <stdlib.h>
26#include <sys/stat.h>
27#include <sys/types.h>
28#include <unistd.h>
29#include <inttypes.h>
30#include <grp.h>
31#include <pwd.h>
32#include <sys/file.h>
33#include <unistd.h>
34
35#include <common/common.h>
36#include <common/readwrite.h>
37#include <common/runas.h>
38#include <common/compat/getenv.h>
39#include <common/compat/string.h>
40#include <common/compat/dirent.h>
41#include <common/compat/directory-handle.h>
42#include <common/dynamic-buffer.h>
43#include <lttng/constant.h>
44
45#include "utils.h"
46#include "defaults.h"
47#include "time.h"
48
49#define PROC_MEMINFO_PATH "/proc/meminfo"
50#define PROC_MEMINFO_MEMAVAILABLE_LINE "MemAvailable:"
51#define PROC_MEMINFO_MEMTOTAL_LINE "MemTotal:"
52
53/* The length of the longest field of `/proc/meminfo`. */
54#define PROC_MEMINFO_FIELD_MAX_NAME_LEN 20
55
56#if (PROC_MEMINFO_FIELD_MAX_NAME_LEN == 20)
57#define MAX_NAME_LEN_SCANF_IS_A_BROKEN_API "19"
58#else
59#error MAX_NAME_LEN_SCANF_IS_A_BROKEN_API must be updated to match (PROC_MEMINFO_FIELD_MAX_NAME_LEN - 1)
60#endif
61
62/*
63 * Return a partial realpath(3) of the path even if the full path does not
64 * exist. For instance, with /tmp/test1/test2/test3, if test2/ does not exist
65 * but the /tmp/test1 does, the real path for /tmp/test1 is concatened with
66 * /test2/test3 then returned. In normal time, realpath(3) fails if the end
67 * point directory does not exist.
68 * In case resolved_path is NULL, the string returned was allocated in the
69 * function and thus need to be freed by the caller. The size argument allows
70 * to specify the size of the resolved_path argument if given, or the size to
71 * allocate.
72 */
73LTTNG_HIDDEN
74char *utils_partial_realpath(const char *path, char *resolved_path, size_t size)
75{
76 char *cut_path = NULL, *try_path = NULL, *try_path_prev = NULL;
77 const char *next, *prev, *end;
78
79 /* Safety net */
80 if (path == NULL) {
81 goto error;
82 }
83
84 /*
85 * Identify the end of the path, we don't want to treat the
86 * last char if it is a '/', we will just keep it on the side
87 * to be added at the end, and return a value coherent with
88 * the path given as argument
89 */
90 end = path + strlen(path);
91 if (*(end-1) == '/') {
92 end--;
93 }
94
95 /* Initiate the values of the pointers before looping */
96 next = path;
97 prev = next;
98 /* Only to ensure try_path is not NULL to enter the while */
99 try_path = (char *)next;
100
101 /* Resolve the canonical path of the first part of the path */
102 while (try_path != NULL && next != end) {
103 char *try_path_buf = NULL;
104
105 /*
106 * If there is not any '/' left, we want to try with
107 * the full path
108 */
109 next = strpbrk(next + 1, "/");
110 if (next == NULL) {
111 next = end;
112 }
113
114 /* Cut the part we will be trying to resolve */
115 cut_path = lttng_strndup(path, next - path);
116 if (cut_path == NULL) {
117 PERROR("lttng_strndup");
118 goto error;
119 }
120
121 try_path_buf = zmalloc(LTTNG_PATH_MAX);
122 if (!try_path_buf) {
123 PERROR("zmalloc");
124 goto error;
125 }
126
127 /* Try to resolve this part */
128 try_path = realpath((char *) cut_path, try_path_buf);
129 if (try_path == NULL) {
130 free(try_path_buf);
131 /*
132 * There was an error, we just want to be assured it
133 * is linked to an unexistent directory, if it's another
134 * reason, we spawn an error
135 */
136 switch (errno) {
137 case ENOENT:
138 /* Ignore the error */
139 break;
140 default:
141 PERROR("realpath (partial_realpath)");
142 goto error;
143 break;
144 }
145 } else {
146 /* Save the place we are before trying the next step */
147 try_path_buf = NULL;
148 free(try_path_prev);
149 try_path_prev = try_path;
150 prev = next;
151 }
152
153 /* Free the allocated memory */
154 free(cut_path);
155 cut_path = NULL;
156 }
157
158 /* Allocate memory for the resolved path if necessary */
159 if (resolved_path == NULL) {
160 resolved_path = zmalloc(size);
161 if (resolved_path == NULL) {
162 PERROR("zmalloc resolved path");
163 goto error;
164 }
165 }
166
167 /*
168 * If we were able to solve at least partially the path, we can concatenate
169 * what worked and what didn't work
170 */
171 if (try_path_prev != NULL) {
172 /* If we risk to concatenate two '/', we remove one of them */
173 if (try_path_prev[strlen(try_path_prev) - 1] == '/' && prev[0] == '/') {
174 try_path_prev[strlen(try_path_prev) - 1] = '\0';
175 }
176
177 /*
178 * Duplicate the memory used by prev in case resolved_path and
179 * path are pointers for the same memory space
180 */
181 cut_path = strdup(prev);
182 if (cut_path == NULL) {
183 PERROR("strdup");
184 goto error;
185 }
186
187 /* Concatenate the strings */
188 snprintf(resolved_path, size, "%s%s", try_path_prev, cut_path);
189
190 /* Free the allocated memory */
191 free(cut_path);
192 free(try_path_prev);
193 cut_path = NULL;
194 try_path_prev = NULL;
195 /*
196 * Else, we just copy the path in our resolved_path to
197 * return it as is
198 */
199 } else {
200 strncpy(resolved_path, path, size);
201 }
202
203 /* Then we return the 'partially' resolved path */
204 return resolved_path;
205
206error:
207 free(resolved_path);
208 free(cut_path);
209 free(try_path);
210 if (try_path_prev != try_path) {
211 free(try_path_prev);
212 }
213 return NULL;
214}
215
216static
217int expand_double_slashes_dot_and_dotdot(char *path)
218{
219 size_t expanded_path_len, path_len;
220 const char *curr_char, *path_last_char, *next_slash, *prev_slash;
221
222 path_len = strlen(path);
223 path_last_char = &path[path_len];
224
225 if (path_len == 0) {
226 goto error;
227 }
228
229 expanded_path_len = 0;
230
231 /* We iterate over the provided path to expand the "//", "../" and "./" */
232 for (curr_char = path; curr_char <= path_last_char; curr_char = next_slash + 1) {
233 /* Find the next forward slash. */
234 size_t curr_token_len;
235
236 if (curr_char == path_last_char) {
237 expanded_path_len++;
238 break;
239 }
240
241 next_slash = memchr(curr_char, '/', path_last_char - curr_char);
242 if (next_slash == NULL) {
243 /* Reached the end of the provided path. */
244 next_slash = path_last_char;
245 }
246
247 /* Compute how long is the previous token. */
248 curr_token_len = next_slash - curr_char;
249 switch(curr_token_len) {
250 case 0:
251 /*
252 * The pointer has not move meaning that curr_char is
253 * pointing to a slash. It that case there is no token
254 * to copy, so continue the iteration to find the next
255 * token
256 */
257 continue;
258 case 1:
259 /*
260 * The pointer moved 1 character. Check if that
261 * character is a dot ('.'), if it is: omit it, else
262 * copy the token to the normalized path.
263 */
264 if (curr_char[0] == '.') {
265 continue;
266 }
267 break;
268 case 2:
269 /*
270 * The pointer moved 2 characters. Check if these
271 * characters are double dots ('..'). If that is the
272 * case, we need to remove the last token of the
273 * normalized path.
274 */
275 if (curr_char[0] == '.' && curr_char[1] == '.') {
276 /*
277 * Find the previous path component by
278 * using the memrchr function to find the
279 * previous forward slash and substract that
280 * len to the resulting path.
281 */
282 prev_slash = lttng_memrchr(path, '/', expanded_path_len);
283 /*
284 * If prev_slash is NULL, we reached the
285 * beginning of the path. We can't go back any
286 * further.
287 */
288 if (prev_slash != NULL) {
289 expanded_path_len = prev_slash - path;
290 }
291 continue;
292 }
293 break;
294 default:
295 break;
296 }
297
298 /*
299 * Copy the current token which is neither a '.' nor a '..'.
300 */
301 path[expanded_path_len++] = '/';
302 memcpy(&path[expanded_path_len], curr_char, curr_token_len);
303 expanded_path_len += curr_token_len;
304 }
305
306 if (expanded_path_len == 0) {
307 path[expanded_path_len++] = '/';
308 }
309
310 path[expanded_path_len] = '\0';
311 return 0;
312error:
313 return -1;
314}
315
316/*
317 * Make a full resolution of the given path even if it doesn't exist.
318 * This function uses the utils_partial_realpath function to resolve
319 * symlinks and relatives paths at the start of the string, and
320 * implements functionnalities to resolve the './' and '../' strings
321 * in the middle of a path. This function is only necessary because
322 * realpath(3) does not accept to resolve unexistent paths.
323 * The returned string was allocated in the function, it is thus of
324 * the responsibility of the caller to free this memory.
325 */
326LTTNG_HIDDEN
327char *_utils_expand_path(const char *path, bool keep_symlink)
328{
329 int ret;
330 char *absolute_path = NULL;
331 char *last_token;
332 bool is_dot, is_dotdot;
333
334 /* Safety net */
335 if (path == NULL) {
336 goto error;
337 }
338
339 /* Allocate memory for the absolute_path */
340 absolute_path = zmalloc(LTTNG_PATH_MAX);
341 if (absolute_path == NULL) {
342 PERROR("zmalloc expand path");
343 goto error;
344 }
345
346 if (path[0] == '/') {
347 ret = lttng_strncpy(absolute_path, path, LTTNG_PATH_MAX);
348 if (ret) {
349 ERR("Path exceeds maximal size of %i bytes", LTTNG_PATH_MAX);
350 goto error;
351 }
352 } else {
353 /*
354 * This is a relative path. We need to get the present working
355 * directory and start the path walk from there.
356 */
357 char current_working_dir[LTTNG_PATH_MAX];
358 char *cwd_ret;
359
360 cwd_ret = getcwd(current_working_dir, sizeof(current_working_dir));
361 if (!cwd_ret) {
362 goto error;
363 }
364 /*
365 * Get the number of character in the CWD and allocate an array
366 * to can hold it and the path provided by the caller.
367 */
368 ret = snprintf(absolute_path, LTTNG_PATH_MAX, "%s/%s",
369 current_working_dir, path);
370 if (ret >= LTTNG_PATH_MAX) {
371 ERR("Concatenating current working directory %s and path %s exceeds maximal size of %i bytes",
372 current_working_dir, path, LTTNG_PATH_MAX);
373 goto error;
374 }
375 }
376
377 if (keep_symlink) {
378 /* Resolve partially our path */
379 absolute_path = utils_partial_realpath(absolute_path,
380 absolute_path, LTTNG_PATH_MAX);
381 }
382
383 ret = expand_double_slashes_dot_and_dotdot(absolute_path);
384 if (ret) {
385 goto error;
386 }
387
388 /* Identify the last token */
389 last_token = strrchr(absolute_path, '/');
390
391 /* Verify that this token is not a relative path */
392 is_dotdot = (strcmp(last_token, "/..") == 0);
393 is_dot = (strcmp(last_token, "/.") == 0);
394
395 /* If it is, take action */
396 if (is_dot || is_dotdot) {
397 /* For both, remove this token */
398 *last_token = '\0';
399
400 /* If it was a reference to parent directory, go back one more time */
401 if (is_dotdot) {
402 last_token = strrchr(absolute_path, '/');
403
404 /* If there was only one level left, we keep the first '/' */
405 if (last_token == absolute_path) {
406 last_token++;
407 }
408
409 *last_token = '\0';
410 }
411 }
412
413 return absolute_path;
414
415error:
416 free(absolute_path);
417 return NULL;
418}
419LTTNG_HIDDEN
420char *utils_expand_path(const char *path)
421{
422 return _utils_expand_path(path, true);
423}
424
425LTTNG_HIDDEN
426char *utils_expand_path_keep_symlink(const char *path)
427{
428 return _utils_expand_path(path, false);
429}
430/*
431 * Create a pipe in dst.
432 */
433LTTNG_HIDDEN
434int utils_create_pipe(int *dst)
435{
436 int ret;
437
438 if (dst == NULL) {
439 return -1;
440 }
441
442 ret = pipe(dst);
443 if (ret < 0) {
444 PERROR("create pipe");
445 }
446
447 return ret;
448}
449
450/*
451 * Create pipe and set CLOEXEC flag to both fd.
452 *
453 * Make sure the pipe opened by this function are closed at some point. Use
454 * utils_close_pipe().
455 */
456LTTNG_HIDDEN
457int utils_create_pipe_cloexec(int *dst)
458{
459 int ret, i;
460
461 if (dst == NULL) {
462 return -1;
463 }
464
465 ret = utils_create_pipe(dst);
466 if (ret < 0) {
467 goto error;
468 }
469
470 for (i = 0; i < 2; i++) {
471 ret = fcntl(dst[i], F_SETFD, FD_CLOEXEC);
472 if (ret < 0) {
473 PERROR("fcntl pipe cloexec");
474 goto error;
475 }
476 }
477
478error:
479 return ret;
480}
481
482/*
483 * Create pipe and set fd flags to FD_CLOEXEC and O_NONBLOCK.
484 *
485 * Make sure the pipe opened by this function are closed at some point. Use
486 * utils_close_pipe(). Using pipe() and fcntl rather than pipe2() to
487 * support OSes other than Linux 2.6.23+.
488 */
489LTTNG_HIDDEN
490int utils_create_pipe_cloexec_nonblock(int *dst)
491{
492 int ret, i;
493
494 if (dst == NULL) {
495 return -1;
496 }
497
498 ret = utils_create_pipe(dst);
499 if (ret < 0) {
500 goto error;
501 }
502
503 for (i = 0; i < 2; i++) {
504 ret = fcntl(dst[i], F_SETFD, FD_CLOEXEC);
505 if (ret < 0) {
506 PERROR("fcntl pipe cloexec");
507 goto error;
508 }
509 /*
510 * Note: we override any flag that could have been
511 * previously set on the fd.
512 */
513 ret = fcntl(dst[i], F_SETFL, O_NONBLOCK);
514 if (ret < 0) {
515 PERROR("fcntl pipe nonblock");
516 goto error;
517 }
518 }
519
520error:
521 return ret;
522}
523
524/*
525 * Close both read and write side of the pipe.
526 */
527LTTNG_HIDDEN
528void utils_close_pipe(int *src)
529{
530 int i, ret;
531
532 if (src == NULL) {
533 return;
534 }
535
536 for (i = 0; i < 2; i++) {
537 /* Safety check */
538 if (src[i] < 0) {
539 continue;
540 }
541
542 ret = close(src[i]);
543 if (ret) {
544 PERROR("close pipe");
545 }
546 }
547}
548
549/*
550 * Create a new string using two strings range.
551 */
552LTTNG_HIDDEN
553char *utils_strdupdelim(const char *begin, const char *end)
554{
555 char *str;
556
557 str = zmalloc(end - begin + 1);
558 if (str == NULL) {
559 PERROR("zmalloc strdupdelim");
560 goto error;
561 }
562
563 memcpy(str, begin, end - begin);
564 str[end - begin] = '\0';
565
566error:
567 return str;
568}
569
570/*
571 * Set CLOEXEC flag to the give file descriptor.
572 */
573LTTNG_HIDDEN
574int utils_set_fd_cloexec(int fd)
575{
576 int ret;
577
578 if (fd < 0) {
579 ret = -EINVAL;
580 goto end;
581 }
582
583 ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
584 if (ret < 0) {
585 PERROR("fcntl cloexec");
586 ret = -errno;
587 }
588
589end:
590 return ret;
591}
592
593/*
594 * Create pid file to the given path and filename.
595 */
596LTTNG_HIDDEN
597int utils_create_pid_file(pid_t pid, const char *filepath)
598{
599 int ret;
600 FILE *fp;
601
602 assert(filepath);
603
604 fp = fopen(filepath, "w");
605 if (fp == NULL) {
606 PERROR("open pid file %s", filepath);
607 ret = -1;
608 goto error;
609 }
610
611 ret = fprintf(fp, "%d\n", (int) pid);
612 if (ret < 0) {
613 PERROR("fprintf pid file");
614 goto error;
615 }
616
617 if (fclose(fp)) {
618 PERROR("fclose");
619 }
620 DBG("Pid %d written in file %s", (int) pid, filepath);
621 ret = 0;
622error:
623 return ret;
624}
625
626/*
627 * Create lock file to the given path and filename.
628 * Returns the associated file descriptor, -1 on error.
629 */
630LTTNG_HIDDEN
631int utils_create_lock_file(const char *filepath)
632{
633 int ret;
634 int fd;
635 struct flock lock;
636
637 assert(filepath);
638
639 memset(&lock, 0, sizeof(lock));
640 fd = open(filepath, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR |
641 S_IRGRP | S_IWGRP);
642 if (fd < 0) {
643 PERROR("open lock file %s", filepath);
644 fd = -1;
645 goto error;
646 }
647
648 /*
649 * Attempt to lock the file. If this fails, there is
650 * already a process using the same lock file running
651 * and we should exit.
652 */
653 lock.l_whence = SEEK_SET;
654 lock.l_type = F_WRLCK;
655
656 ret = fcntl(fd, F_SETLK, &lock);
657 if (ret == -1) {
658 PERROR("fcntl lock file");
659 ERR("Could not get lock file %s, another instance is running.",
660 filepath);
661 if (close(fd)) {
662 PERROR("close lock file");
663 }
664 fd = ret;
665 goto error;
666 }
667
668error:
669 return fd;
670}
671
672/*
673 * Create directory using the given path and mode.
674 *
675 * On success, return 0 else a negative error code.
676 */
677LTTNG_HIDDEN
678int utils_mkdir(const char *path, mode_t mode, int uid, int gid)
679{
680 int ret;
681 struct lttng_directory_handle handle;
682 const struct lttng_credentials creds = {
683 .uid = (uid_t) uid,
684 .gid = (gid_t) gid,
685 };
686
687 (void) lttng_directory_handle_init(&handle, NULL);
688 ret = lttng_directory_handle_create_subdirectory_as_user(
689 &handle, path, mode,
690 (uid >= 0 || gid >= 0) ? &creds : NULL);
691 lttng_directory_handle_fini(&handle);
692 return ret;
693}
694
695/*
696 * Recursively create directory using the given path and mode, under the
697 * provided uid and gid.
698 *
699 * On success, return 0 else a negative error code.
700 */
701LTTNG_HIDDEN
702int utils_mkdir_recursive(const char *path, mode_t mode, int uid, int gid)
703{
704 int ret;
705 struct lttng_directory_handle handle;
706 const struct lttng_credentials creds = {
707 .uid = (uid_t) uid,
708 .gid = (gid_t) gid,
709 };
710
711 (void) lttng_directory_handle_init(&handle, NULL);
712 ret = lttng_directory_handle_create_subdirectory_recursive_as_user(
713 &handle, path, mode,
714 (uid >= 0 || gid >= 0) ? &creds : NULL);
715 lttng_directory_handle_fini(&handle);
716 return ret;
717}
718
719/*
720 * path is the output parameter. It needs to be PATH_MAX len.
721 *
722 * Return 0 on success or else a negative value.
723 */
724static int utils_stream_file_name(char *path,
725 const char *path_name, const char *file_name,
726 uint64_t size, uint64_t count,
727 const char *suffix)
728{
729 int ret;
730 char full_path[PATH_MAX];
731 char *path_name_suffix = NULL;
732 char *extra = NULL;
733
734 ret = snprintf(full_path, sizeof(full_path), "%s/%s",
735 path_name, file_name);
736 if (ret < 0) {
737 PERROR("snprintf create output file");
738 goto error;
739 }
740
741 /* Setup extra string if suffix or/and a count is needed. */
742 if (size > 0 && suffix) {
743 ret = asprintf(&extra, "_%" PRIu64 "%s", count, suffix);
744 } else if (size > 0) {
745 ret = asprintf(&extra, "_%" PRIu64, count);
746 } else if (suffix) {
747 ret = asprintf(&extra, "%s", suffix);
748 }
749 if (ret < 0) {
750 PERROR("Allocating extra string to name");
751 goto error;
752 }
753
754 /*
755 * If we split the trace in multiple files, we have to add the count at
756 * the end of the tracefile name.
757 */
758 if (extra) {
759 ret = asprintf(&path_name_suffix, "%s%s", full_path, extra);
760 if (ret < 0) {
761 PERROR("Allocating path name with extra string");
762 goto error_free_suffix;
763 }
764 strncpy(path, path_name_suffix, PATH_MAX - 1);
765 path[PATH_MAX - 1] = '\0';
766 } else {
767 ret = lttng_strncpy(path, full_path, PATH_MAX);
768 if (ret) {
769 ERR("Failed to copy stream file name");
770 goto error_free_suffix;
771 }
772 }
773 path[PATH_MAX - 1] = '\0';
774 ret = 0;
775
776 free(path_name_suffix);
777error_free_suffix:
778 free(extra);
779error:
780 return ret;
781}
782
783/*
784 * Create the stream file on disk.
785 *
786 * Return 0 on success or else a negative value.
787 */
788LTTNG_HIDDEN
789int utils_create_stream_file(const char *path_name, char *file_name, uint64_t size,
790 uint64_t count, int uid, int gid, char *suffix)
791{
792 int ret, flags, mode;
793 char path[PATH_MAX];
794
795 ret = utils_stream_file_name(path, path_name, file_name,
796 size, count, suffix);
797 if (ret < 0) {
798 goto error;
799 }
800
801 /*
802 * With the session rotation feature on the relay, we might need to seek
803 * and truncate a tracefile, so we need read and write access.
804 */
805 flags = O_RDWR | O_CREAT | O_TRUNC;
806 /* Open with 660 mode */
807 mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
808
809 if (uid < 0 || gid < 0) {
810 ret = open(path, flags, mode);
811 } else {
812 ret = run_as_open(path, flags, mode, uid, gid);
813 }
814 if (ret < 0) {
815 PERROR("open stream path %s", path);
816 }
817error:
818 return ret;
819}
820
821/*
822 * Unlink the stream tracefile from disk.
823 *
824 * Return 0 on success or else a negative value.
825 */
826LTTNG_HIDDEN
827int utils_unlink_stream_file(const char *path_name, char *file_name, uint64_t size,
828 uint64_t count, int uid, int gid, char *suffix)
829{
830 int ret;
831 char path[PATH_MAX];
832
833 ret = utils_stream_file_name(path, path_name, file_name,
834 size, count, suffix);
835 if (ret < 0) {
836 goto error;
837 }
838 if (uid < 0 || gid < 0) {
839 ret = unlink(path);
840 } else {
841 ret = run_as_unlink(path, uid, gid);
842 }
843 if (ret < 0) {
844 goto error;
845 }
846error:
847 DBG("utils_unlink_stream_file %s returns %d", path, ret);
848 return ret;
849}
850
851/*
852 * Change the output tracefile according to the given size and count The
853 * new_count pointer is set during this operation.
854 *
855 * From the consumer, the stream lock MUST be held before calling this function
856 * because we are modifying the stream status.
857 *
858 * Return 0 on success or else a negative value.
859 */
860LTTNG_HIDDEN
861int utils_rotate_stream_file(char *path_name, char *file_name, uint64_t size,
862 uint64_t count, int uid, int gid, int out_fd, uint64_t *new_count,
863 int *stream_fd)
864{
865 int ret;
866
867 assert(stream_fd);
868
869 ret = close(out_fd);
870 if (ret < 0) {
871 PERROR("Closing tracefile");
872 goto error;
873 }
874 *stream_fd = -1;
875
876 if (count > 0) {
877 /*
878 * In tracefile rotation, for the relay daemon we need
879 * to unlink the old file if present, because it may
880 * still be open in reading by the live thread, and we
881 * need to ensure that we do not overwrite the content
882 * between get_index and get_packet. Since we have no
883 * way to verify integrity of the data content compared
884 * to the associated index, we need to ensure the reader
885 * has exclusive access to the file content, and that
886 * the open of the data file is performed in get_index.
887 * Unlinking the old file rather than overwriting it
888 * achieves this.
889 */
890 if (new_count) {
891 *new_count = (*new_count + 1) % count;
892 }
893 ret = utils_unlink_stream_file(path_name, file_name, size,
894 new_count ? *new_count : 0, uid, gid, 0);
895 if (ret < 0 && errno != ENOENT) {
896 goto error;
897 }
898 } else {
899 if (new_count) {
900 (*new_count)++;
901 }
902 }
903
904 ret = utils_create_stream_file(path_name, file_name, size,
905 new_count ? *new_count : 0, uid, gid, 0);
906 if (ret < 0) {
907 goto error;
908 }
909 *stream_fd = ret;
910
911 /* Success. */
912 ret = 0;
913
914error:
915 return ret;
916}
917
918
919/**
920 * Parse a string that represents a size in human readable format. It
921 * supports decimal integers suffixed by 'k', 'K', 'M' or 'G'.
922 *
923 * The suffix multiply the integer by:
924 * 'k': 1024
925 * 'M': 1024^2
926 * 'G': 1024^3
927 *
928 * @param str The string to parse.
929 * @param size Pointer to a uint64_t that will be filled with the
930 * resulting size.
931 *
932 * @return 0 on success, -1 on failure.
933 */
934LTTNG_HIDDEN
935int utils_parse_size_suffix(const char * const str, uint64_t * const size)
936{
937 int ret;
938 uint64_t base_size;
939 long shift = 0;
940 const char *str_end;
941 char *num_end;
942
943 if (!str) {
944 DBG("utils_parse_size_suffix: received a NULL string.");
945 ret = -1;
946 goto end;
947 }
948
949 /* strtoull will accept a negative number, but we don't want to. */
950 if (strchr(str, '-') != NULL) {
951 DBG("utils_parse_size_suffix: invalid size string, should not contain '-'.");
952 ret = -1;
953 goto end;
954 }
955
956 /* str_end will point to the \0 */
957 str_end = str + strlen(str);
958 errno = 0;
959 base_size = strtoull(str, &num_end, 0);
960 if (errno != 0) {
961 PERROR("utils_parse_size_suffix strtoull");
962 ret = -1;
963 goto end;
964 }
965
966 if (num_end == str) {
967 /* strtoull parsed nothing, not good. */
968 DBG("utils_parse_size_suffix: strtoull had nothing good to parse.");
969 ret = -1;
970 goto end;
971 }
972
973 /* Check if a prefix is present. */
974 switch (*num_end) {
975 case 'G':
976 shift = GIBI_LOG2;
977 num_end++;
978 break;
979 case 'M': /* */
980 shift = MEBI_LOG2;
981 num_end++;
982 break;
983 case 'K':
984 case 'k':
985 shift = KIBI_LOG2;
986 num_end++;
987 break;
988 case '\0':
989 break;
990 default:
991 DBG("utils_parse_size_suffix: invalid suffix.");
992 ret = -1;
993 goto end;
994 }
995
996 /* Check for garbage after the valid input. */
997 if (num_end != str_end) {
998 DBG("utils_parse_size_suffix: Garbage after size string.");
999 ret = -1;
1000 goto end;
1001 }
1002
1003 *size = base_size << shift;
1004
1005 /* Check for overflow */
1006 if ((*size >> shift) != base_size) {
1007 DBG("utils_parse_size_suffix: oops, overflow detected.");
1008 ret = -1;
1009 goto end;
1010 }
1011
1012 ret = 0;
1013end:
1014 return ret;
1015}
1016
1017/**
1018 * Parse a string that represents a time in human readable format. It
1019 * supports decimal integers suffixed by:
1020 * "us" for microsecond,
1021 * "ms" for millisecond,
1022 * "s" for second,
1023 * "m" for minute,
1024 * "h" for hour
1025 *
1026 * The suffix multiply the integer by:
1027 * "us" : 1
1028 * "ms" : 1000
1029 * "s" : 1000000
1030 * "m" : 60000000
1031 * "h" : 3600000000
1032 *
1033 * Note that unit-less numbers are assumed to be microseconds.
1034 *
1035 * @param str The string to parse, assumed to be NULL-terminated.
1036 * @param time_us Pointer to a uint64_t that will be filled with the
1037 * resulting time in microseconds.
1038 *
1039 * @return 0 on success, -1 on failure.
1040 */
1041LTTNG_HIDDEN
1042int utils_parse_time_suffix(char const * const str, uint64_t * const time_us)
1043{
1044 int ret;
1045 uint64_t base_time;
1046 uint64_t multiplier = 1;
1047 const char *str_end;
1048 char *num_end;
1049
1050 if (!str) {
1051 DBG("utils_parse_time_suffix: received a NULL string.");
1052 ret = -1;
1053 goto end;
1054 }
1055
1056 /* strtoull will accept a negative number, but we don't want to. */
1057 if (strchr(str, '-') != NULL) {
1058 DBG("utils_parse_time_suffix: invalid time string, should not contain '-'.");
1059 ret = -1;
1060 goto end;
1061 }
1062
1063 /* str_end will point to the \0 */
1064 str_end = str + strlen(str);
1065 errno = 0;
1066 base_time = strtoull(str, &num_end, 10);
1067 if (errno != 0) {
1068 PERROR("utils_parse_time_suffix strtoull on string \"%s\"", str);
1069 ret = -1;
1070 goto end;
1071 }
1072
1073 if (num_end == str) {
1074 /* strtoull parsed nothing, not good. */
1075 DBG("utils_parse_time_suffix: strtoull had nothing good to parse.");
1076 ret = -1;
1077 goto end;
1078 }
1079
1080 /* Check if a prefix is present. */
1081 switch (*num_end) {
1082 case 'u':
1083 /*
1084 * Microsecond (us)
1085 *
1086 * Skip the "us" if the string matches the "us" suffix,
1087 * otherwise let the check for the end of the string handle
1088 * the error reporting.
1089 */
1090 if (*(num_end + 1) == 's') {
1091 num_end += 2;
1092 }
1093 break;
1094 case 'm':
1095 if (*(num_end + 1) == 's') {
1096 /* Millisecond (ms) */
1097 multiplier = USEC_PER_MSEC;
1098 /* Skip the 's' */
1099 num_end++;
1100 } else {
1101 /* Minute (m) */
1102 multiplier = USEC_PER_MINUTE;
1103 }
1104 num_end++;
1105 break;
1106 case 's':
1107 /* Second */
1108 multiplier = USEC_PER_SEC;
1109 num_end++;
1110 break;
1111 case 'h':
1112 /* Hour */
1113 multiplier = USEC_PER_HOURS;
1114 num_end++;
1115 break;
1116 case '\0':
1117 break;
1118 default:
1119 DBG("utils_parse_time_suffix: invalid suffix.");
1120 ret = -1;
1121 goto end;
1122 }
1123
1124 /* Check for garbage after the valid input. */
1125 if (num_end != str_end) {
1126 DBG("utils_parse_time_suffix: Garbage after time string.");
1127 ret = -1;
1128 goto end;
1129 }
1130
1131 *time_us = base_time * multiplier;
1132
1133 /* Check for overflow */
1134 if ((*time_us / multiplier) != base_time) {
1135 DBG("utils_parse_time_suffix: oops, overflow detected.");
1136 ret = -1;
1137 goto end;
1138 }
1139
1140 ret = 0;
1141end:
1142 return ret;
1143}
1144
1145/*
1146 * fls: returns the position of the most significant bit.
1147 * Returns 0 if no bit is set, else returns the position of the most
1148 * significant bit (from 1 to 32 on 32-bit, from 1 to 64 on 64-bit).
1149 */
1150#if defined(__i386) || defined(__x86_64)
1151static inline unsigned int fls_u32(uint32_t x)
1152{
1153 int r;
1154
1155 asm("bsrl %1,%0\n\t"
1156 "jnz 1f\n\t"
1157 "movl $-1,%0\n\t"
1158 "1:\n\t"
1159 : "=r" (r) : "rm" (x));
1160 return r + 1;
1161}
1162#define HAS_FLS_U32
1163#endif
1164
1165#if defined(__x86_64)
1166static inline
1167unsigned int fls_u64(uint64_t x)
1168{
1169 long r;
1170
1171 asm("bsrq %1,%0\n\t"
1172 "jnz 1f\n\t"
1173 "movq $-1,%0\n\t"
1174 "1:\n\t"
1175 : "=r" (r) : "rm" (x));
1176 return r + 1;
1177}
1178#define HAS_FLS_U64
1179#endif
1180
1181#ifndef HAS_FLS_U64
1182static __attribute__((unused))
1183unsigned int fls_u64(uint64_t x)
1184{
1185 unsigned int r = 64;
1186
1187 if (!x)
1188 return 0;
1189
1190 if (!(x & 0xFFFFFFFF00000000ULL)) {
1191 x <<= 32;
1192 r -= 32;
1193 }
1194 if (!(x & 0xFFFF000000000000ULL)) {
1195 x <<= 16;
1196 r -= 16;
1197 }
1198 if (!(x & 0xFF00000000000000ULL)) {
1199 x <<= 8;
1200 r -= 8;
1201 }
1202 if (!(x & 0xF000000000000000ULL)) {
1203 x <<= 4;
1204 r -= 4;
1205 }
1206 if (!(x & 0xC000000000000000ULL)) {
1207 x <<= 2;
1208 r -= 2;
1209 }
1210 if (!(x & 0x8000000000000000ULL)) {
1211 x <<= 1;
1212 r -= 1;
1213 }
1214 return r;
1215}
1216#endif
1217
1218#ifndef HAS_FLS_U32
1219static __attribute__((unused)) unsigned int fls_u32(uint32_t x)
1220{
1221 unsigned int r = 32;
1222
1223 if (!x) {
1224 return 0;
1225 }
1226 if (!(x & 0xFFFF0000U)) {
1227 x <<= 16;
1228 r -= 16;
1229 }
1230 if (!(x & 0xFF000000U)) {
1231 x <<= 8;
1232 r -= 8;
1233 }
1234 if (!(x & 0xF0000000U)) {
1235 x <<= 4;
1236 r -= 4;
1237 }
1238 if (!(x & 0xC0000000U)) {
1239 x <<= 2;
1240 r -= 2;
1241 }
1242 if (!(x & 0x80000000U)) {
1243 x <<= 1;
1244 r -= 1;
1245 }
1246 return r;
1247}
1248#endif
1249
1250/*
1251 * Return the minimum order for which x <= (1UL << order).
1252 * Return -1 if x is 0.
1253 */
1254LTTNG_HIDDEN
1255int utils_get_count_order_u32(uint32_t x)
1256{
1257 if (!x) {
1258 return -1;
1259 }
1260
1261 return fls_u32(x - 1);
1262}
1263
1264/*
1265 * Return the minimum order for which x <= (1UL << order).
1266 * Return -1 if x is 0.
1267 */
1268LTTNG_HIDDEN
1269int utils_get_count_order_u64(uint64_t x)
1270{
1271 if (!x) {
1272 return -1;
1273 }
1274
1275 return fls_u64(x - 1);
1276}
1277
1278/**
1279 * Obtain the value of LTTNG_HOME environment variable, if exists.
1280 * Otherwise returns the value of HOME.
1281 */
1282LTTNG_HIDDEN
1283char *utils_get_home_dir(void)
1284{
1285 char *val = NULL;
1286 struct passwd *pwd;
1287
1288 val = lttng_secure_getenv(DEFAULT_LTTNG_HOME_ENV_VAR);
1289 if (val != NULL) {
1290 goto end;
1291 }
1292 val = lttng_secure_getenv(DEFAULT_LTTNG_FALLBACK_HOME_ENV_VAR);
1293 if (val != NULL) {
1294 goto end;
1295 }
1296
1297 /* Fallback on the password file entry. */
1298 pwd = getpwuid(getuid());
1299 if (!pwd) {
1300 goto end;
1301 }
1302 val = pwd->pw_dir;
1303
1304 DBG3("Home directory is '%s'", val);
1305
1306end:
1307 return val;
1308}
1309
1310/**
1311 * Get user's home directory. Dynamically allocated, must be freed
1312 * by the caller.
1313 */
1314LTTNG_HIDDEN
1315char *utils_get_user_home_dir(uid_t uid)
1316{
1317 struct passwd pwd;
1318 struct passwd *result;
1319 char *home_dir = NULL;
1320 char *buf = NULL;
1321 long buflen;
1322 int ret;
1323
1324 buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
1325 if (buflen == -1) {
1326 goto end;
1327 }
1328retry:
1329 buf = zmalloc(buflen);
1330 if (!buf) {
1331 goto end;
1332 }
1333
1334 ret = getpwuid_r(uid, &pwd, buf, buflen, &result);
1335 if (ret || !result) {
1336 if (ret == ERANGE) {
1337 free(buf);
1338 buflen *= 2;
1339 goto retry;
1340 }
1341 goto end;
1342 }
1343
1344 home_dir = strdup(pwd.pw_dir);
1345end:
1346 free(buf);
1347 return home_dir;
1348}
1349
1350/*
1351 * With the given format, fill dst with the time of len maximum siz.
1352 *
1353 * Return amount of bytes set in the buffer or else 0 on error.
1354 */
1355LTTNG_HIDDEN
1356size_t utils_get_current_time_str(const char *format, char *dst, size_t len)
1357{
1358 size_t ret;
1359 time_t rawtime;
1360 struct tm *timeinfo;
1361
1362 assert(format);
1363 assert(dst);
1364
1365 /* Get date and time for session path */
1366 time(&rawtime);
1367 timeinfo = localtime(&rawtime);
1368 ret = strftime(dst, len, format, timeinfo);
1369 if (ret == 0) {
1370 ERR("Unable to strftime with format %s at dst %p of len %zu", format,
1371 dst, len);
1372 }
1373
1374 return ret;
1375}
1376
1377/*
1378 * Return 0 on success and set *gid to the group_ID matching the passed name.
1379 * Else -1 if it cannot be found or an error occurred.
1380 */
1381LTTNG_HIDDEN
1382int utils_get_group_id(const char *name, bool warn, gid_t *gid)
1383{
1384 static volatile int warn_once;
1385 int ret;
1386 long sys_len;
1387 size_t len;
1388 struct group grp;
1389 struct group *result;
1390 struct lttng_dynamic_buffer buffer;
1391
1392 /* Get the system limit, if it exists. */
1393 sys_len = sysconf(_SC_GETGR_R_SIZE_MAX);
1394 if (sys_len == -1) {
1395 len = 1024;
1396 } else {
1397 len = (size_t) sys_len;
1398 }
1399
1400 lttng_dynamic_buffer_init(&buffer);
1401 ret = lttng_dynamic_buffer_set_size(&buffer, len);
1402 if (ret) {
1403 ERR("Failed to allocate group info buffer");
1404 ret = -1;
1405 goto error;
1406 }
1407
1408 while ((ret = getgrnam_r(name, &grp, buffer.data, buffer.size, &result)) == ERANGE) {
1409 const size_t new_len = 2 * buffer.size;
1410
1411 /* Buffer is not big enough, increase its size. */
1412 if (new_len < buffer.size) {
1413 ERR("Group info buffer size overflow");
1414 ret = -1;
1415 goto error;
1416 }
1417
1418 ret = lttng_dynamic_buffer_set_size(&buffer, new_len);
1419 if (ret) {
1420 ERR("Failed to grow group info buffer to %zu bytes",
1421 new_len);
1422 ret = -1;
1423 goto error;
1424 }
1425 }
1426 if (ret) {
1427 PERROR("Failed to get group file entry for group name \"%s\"",
1428 name);
1429 ret = -1;
1430 goto error;
1431 }
1432
1433 /* Group not found. */
1434 if (!result) {
1435 ret = -1;
1436 goto error;
1437 }
1438
1439 *gid = result->gr_gid;
1440 ret = 0;
1441
1442error:
1443 if (ret && warn && !warn_once) {
1444 WARN("No tracing group detected");
1445 warn_once = 1;
1446 }
1447 lttng_dynamic_buffer_reset(&buffer);
1448 return ret;
1449}
1450
1451/*
1452 * Return a newly allocated option string. This string is to be used as the
1453 * optstring argument of getopt_long(), see GETOPT(3). opt_count is the number
1454 * of elements in the long_options array. Returns NULL if the string's
1455 * allocation fails.
1456 */
1457LTTNG_HIDDEN
1458char *utils_generate_optstring(const struct option *long_options,
1459 size_t opt_count)
1460{
1461 int i;
1462 size_t string_len = opt_count, str_pos = 0;
1463 char *optstring;
1464
1465 /*
1466 * Compute the necessary string length. One letter per option, two when an
1467 * argument is necessary, and a trailing NULL.
1468 */
1469 for (i = 0; i < opt_count; i++) {
1470 string_len += long_options[i].has_arg ? 1 : 0;
1471 }
1472
1473 optstring = zmalloc(string_len);
1474 if (!optstring) {
1475 goto end;
1476 }
1477
1478 for (i = 0; i < opt_count; i++) {
1479 if (!long_options[i].name) {
1480 /* Got to the trailing NULL element */
1481 break;
1482 }
1483
1484 if (long_options[i].val != '\0') {
1485 optstring[str_pos++] = (char) long_options[i].val;
1486 if (long_options[i].has_arg) {
1487 optstring[str_pos++] = ':';
1488 }
1489 }
1490 }
1491
1492end:
1493 return optstring;
1494}
1495
1496/*
1497 * Try to remove a hierarchy of empty directories, recursively. Don't unlink
1498 * any file. Try to rmdir any empty directory within the hierarchy.
1499 */
1500LTTNG_HIDDEN
1501int utils_recursive_rmdir(const char *path)
1502{
1503 DIR *dir;
1504 size_t path_len;
1505 int dir_fd, ret = 0, closeret, is_empty = 1;
1506 struct dirent *entry;
1507
1508 /* Open directory */
1509 dir = opendir(path);
1510 if (!dir) {
1511 PERROR("Cannot open '%s' path", path);
1512 return -1;
1513 }
1514 dir_fd = lttng_dirfd(dir);
1515 if (dir_fd < 0) {
1516 PERROR("lttng_dirfd");
1517 return -1;
1518 }
1519
1520 path_len = strlen(path);
1521 while ((entry = readdir(dir))) {
1522 struct stat st;
1523 size_t name_len;
1524 char filename[PATH_MAX];
1525
1526 if (!strcmp(entry->d_name, ".")
1527 || !strcmp(entry->d_name, "..")) {
1528 continue;
1529 }
1530
1531 name_len = strlen(entry->d_name);
1532 if (path_len + name_len + 2 > sizeof(filename)) {
1533 ERR("Failed to remove file: path name too long (%s/%s)",
1534 path, entry->d_name);
1535 continue;
1536 }
1537 if (snprintf(filename, sizeof(filename), "%s/%s",
1538 path, entry->d_name) < 0) {
1539 ERR("Failed to format path.");
1540 continue;
1541 }
1542
1543 if (stat(filename, &st)) {
1544 PERROR("stat");
1545 continue;
1546 }
1547
1548 if (S_ISDIR(st.st_mode)) {
1549 char subpath[PATH_MAX];
1550
1551 strncpy(subpath, path, PATH_MAX);
1552 subpath[PATH_MAX - 1] = '\0';
1553 strncat(subpath, "/",
1554 PATH_MAX - strlen(subpath) - 1);
1555 strncat(subpath, entry->d_name,
1556 PATH_MAX - strlen(subpath) - 1);
1557 if (utils_recursive_rmdir(subpath)) {
1558 is_empty = 0;
1559 }
1560 } else if (S_ISREG(st.st_mode)) {
1561 is_empty = 0;
1562 } else {
1563 ret = -EINVAL;
1564 goto end;
1565 }
1566 }
1567end:
1568 closeret = closedir(dir);
1569 if (closeret) {
1570 PERROR("closedir");
1571 }
1572 if (is_empty) {
1573 DBG3("Attempting rmdir %s", path);
1574 ret = rmdir(path);
1575 }
1576 return ret;
1577}
1578
1579LTTNG_HIDDEN
1580int utils_truncate_stream_file(int fd, off_t length)
1581{
1582 int ret;
1583 off_t lseek_ret;
1584
1585 ret = ftruncate(fd, length);
1586 if (ret < 0) {
1587 PERROR("ftruncate");
1588 goto end;
1589 }
1590 lseek_ret = lseek(fd, length, SEEK_SET);
1591 if (lseek_ret < 0) {
1592 PERROR("lseek");
1593 ret = -1;
1594 goto end;
1595 }
1596end:
1597 return ret;
1598}
1599
1600static const char *get_man_bin_path(void)
1601{
1602 char *env_man_path = lttng_secure_getenv(DEFAULT_MAN_BIN_PATH_ENV);
1603
1604 if (env_man_path) {
1605 return env_man_path;
1606 }
1607
1608 return DEFAULT_MAN_BIN_PATH;
1609}
1610
1611LTTNG_HIDDEN
1612int utils_show_help(int section, const char *page_name,
1613 const char *help_msg)
1614{
1615 char section_string[8];
1616 const char *man_bin_path = get_man_bin_path();
1617 int ret = 0;
1618
1619 if (help_msg) {
1620 printf("%s", help_msg);
1621 goto end;
1622 }
1623
1624 /* Section integer -> section string */
1625 ret = sprintf(section_string, "%d", section);
1626 assert(ret > 0 && ret < 8);
1627
1628 /*
1629 * Execute man pager.
1630 *
1631 * We provide -M to man here because LTTng-tools can
1632 * be installed outside /usr, in which case its man pages are
1633 * not located in the default /usr/share/man directory.
1634 */
1635 ret = execlp(man_bin_path, "man", "-M", MANPATH,
1636 section_string, page_name, NULL);
1637
1638end:
1639 return ret;
1640}
1641
1642static
1643int read_proc_meminfo_field(const char *field, size_t *value)
1644{
1645 int ret;
1646 FILE *proc_meminfo;
1647 char name[PROC_MEMINFO_FIELD_MAX_NAME_LEN] = {};
1648
1649 proc_meminfo = fopen(PROC_MEMINFO_PATH, "r");
1650 if (!proc_meminfo) {
1651 PERROR("Failed to fopen() " PROC_MEMINFO_PATH);
1652 ret = -1;
1653 goto fopen_error;
1654 }
1655
1656 /*
1657 * Read the contents of /proc/meminfo line by line to find the right
1658 * field.
1659 */
1660 while (!feof(proc_meminfo)) {
1661 unsigned long value_kb;
1662
1663 ret = fscanf(proc_meminfo,
1664 "%" MAX_NAME_LEN_SCANF_IS_A_BROKEN_API "s %lu kB\n",
1665 name, &value_kb);
1666 if (ret == EOF) {
1667 /*
1668 * fscanf() returning EOF can indicate EOF or an error.
1669 */
1670 if (ferror(proc_meminfo)) {
1671 PERROR("Failed to parse " PROC_MEMINFO_PATH);
1672 }
1673 break;
1674 }
1675
1676 if (ret == 2 && strcmp(name, field) == 0) {
1677 /*
1678 * This number is displayed in kilo-bytes. Return the
1679 * number of bytes.
1680 */
1681 *value = ((size_t) value_kb) * 1024;
1682 ret = 0;
1683 goto found;
1684 }
1685 }
1686 /* Reached the end of the file without finding the right field. */
1687 ret = -1;
1688
1689found:
1690 fclose(proc_meminfo);
1691fopen_error:
1692 return ret;
1693}
1694
1695/*
1696 * Returns an estimate of the number of bytes of memory available based on the
1697 * the information in `/proc/meminfo`. The number returned by this function is
1698 * a best guess.
1699 */
1700LTTNG_HIDDEN
1701int utils_get_memory_available(size_t *value)
1702{
1703 return read_proc_meminfo_field(PROC_MEMINFO_MEMAVAILABLE_LINE, value);
1704}
1705
1706/*
1707 * Returns the total size of the memory on the system in bytes based on the
1708 * the information in `/proc/meminfo`.
1709 */
1710LTTNG_HIDDEN
1711int utils_get_memory_total(size_t *value)
1712{
1713 return read_proc_meminfo_field(PROC_MEMINFO_MEMTOTAL_LINE, value);
1714}
This page took 0.027784 seconds and 4 git commands to generate.