Fix: sessiond: occasional badfd error on repeated SIGTERM
[lttng-tools.git] / src / common / utils.c
index 1d07cb31cc01511aedb781124de609277410075d..646ebbbc7d34e6705d82fa654773ccbc387c5c66 100644 (file)
@@ -1,29 +1,18 @@
 /*
- * Copyright (C) 2012 David Goulet <dgoulet@efficios.com>
- * Copyright (C) 2013 Raphaël Beamonte <raphael.beamonte@gmail.com>
- * Copyright (C) 2013 Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ * Copyright (C) 2012 David Goulet <dgoulet@efficios.com>
+ * Copyright (C) 2013 Raphaël Beamonte <raphael.beamonte@gmail.com>
+ * Copyright (C) 2013 Jérémie Galarneau <jeremie.galarneau@efficios.com>
  *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License, version 2 only, as
- * published by the Free Software Foundation.
+ * SPDX-License-Identifier: GPL-2.0-only
  *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program; if not, write to the Free Software Foundation, Inc., 51
- * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#define _GNU_SOURCE
+#define _LGPL_SOURCE
 #include <assert.h>
 #include <ctype.h>
 #include <fcntl.h>
 #include <limits.h>
 #include <stdlib.h>
-#include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <grp.h>
 #include <pwd.h>
 #include <sys/file.h>
+#include <unistd.h>
 
 #include <common/common.h>
+#include <common/readwrite.h>
 #include <common/runas.h>
+#include <common/compat/getenv.h>
+#include <common/compat/string.h>
+#include <common/compat/dirent.h>
+#include <common/compat/directory-handle.h>
+#include <common/dynamic-buffer.h>
+#include <common/string-utils/format.h>
+#include <lttng/constant.h>
 
 #include "utils.h"
 #include "defaults.h"
+#include "time.h"
+
+#define PROC_MEMINFO_PATH               "/proc/meminfo"
+#define PROC_MEMINFO_MEMAVAILABLE_LINE  "MemAvailable:"
+#define PROC_MEMINFO_MEMTOTAL_LINE      "MemTotal:"
+
+/* The length of the longest field of `/proc/meminfo`. */
+#define PROC_MEMINFO_FIELD_MAX_NAME_LEN        20
+
+#if (PROC_MEMINFO_FIELD_MAX_NAME_LEN == 20)
+#define MAX_NAME_LEN_SCANF_IS_A_BROKEN_API "19"
+#else
+#error MAX_NAME_LEN_SCANF_IS_A_BROKEN_API must be updated to match (PROC_MEMINFO_FIELD_MAX_NAME_LEN - 1)
+#endif
 
 /*
  * Return a partial realpath(3) of the path even if the full path does not
@@ -52,7 +64,7 @@
 LTTNG_HIDDEN
 char *utils_partial_realpath(const char *path, char *resolved_path, size_t size)
 {
-       char *cut_path, *try_path = NULL, *try_path_prev = NULL;
+       char *cut_path = NULL, *try_path = NULL, *try_path_prev = NULL;
        const char *next, *prev, *end;
 
        /* Safety net */
@@ -79,6 +91,8 @@ char *utils_partial_realpath(const char *path, char *resolved_path, size_t size)
 
        /* Resolve the canonical path of the first part of the path */
        while (try_path != NULL && next != end) {
+               char *try_path_buf = NULL;
+
                /*
                 * If there is not any '/' left, we want to try with
                 * the full path
@@ -89,11 +103,22 @@ char *utils_partial_realpath(const char *path, char *resolved_path, size_t size)
                }
 
                /* Cut the part we will be trying to resolve */
-               cut_path = strndup(path, next - path);
+               cut_path = lttng_strndup(path, next - path);
+               if (cut_path == NULL) {
+                       PERROR("lttng_strndup");
+                       goto error;
+               }
+
+               try_path_buf = zmalloc(LTTNG_PATH_MAX);
+               if (!try_path_buf) {
+                       PERROR("zmalloc");
+                       goto error;
+               }
 
                /* Try to resolve this part */
-               try_path = realpath((char *)cut_path, NULL);
+               try_path = realpath((char *) cut_path, try_path_buf);
                if (try_path == NULL) {
+                       free(try_path_buf);
                        /*
                         * There was an error, we just want to be assured it
                         * is linked to an unexistent directory, if it's another
@@ -110,6 +135,7 @@ char *utils_partial_realpath(const char *path, char *resolved_path, size_t size)
                        }
                } else {
                        /* Save the place we are before trying the next step */
+                       try_path_buf = NULL;
                        free(try_path_prev);
                        try_path_prev = try_path;
                        prev = next;
@@ -117,7 +143,8 @@ char *utils_partial_realpath(const char *path, char *resolved_path, size_t size)
 
                /* Free the allocated memory */
                free(cut_path);
-       };
+               cut_path = NULL;
+       }
 
        /* Allocate memory for the resolved path if necessary */
        if (resolved_path == NULL) {
@@ -143,6 +170,10 @@ char *utils_partial_realpath(const char *path, char *resolved_path, size_t size)
                 * path are pointers for the same memory space
                 */
                cut_path = strdup(prev);
+               if (cut_path == NULL) {
+                       PERROR("strdup");
+                       goto error;
+               }
 
                /* Concatenate the strings */
                snprintf(resolved_path, size, "%s%s", try_path_prev, cut_path);
@@ -150,6 +181,8 @@ char *utils_partial_realpath(const char *path, char *resolved_path, size_t size)
                /* Free the allocated memory */
                free(cut_path);
                free(try_path_prev);
+               cut_path = NULL;
+               try_path_prev = NULL;
        /*
         * Else, we just copy the path in our resolved_path to
         * return it as is
@@ -163,9 +196,114 @@ char *utils_partial_realpath(const char *path, char *resolved_path, size_t size)
 
 error:
        free(resolved_path);
+       free(cut_path);
+       free(try_path);
+       if (try_path_prev != try_path) {
+               free(try_path_prev);
+       }
        return NULL;
 }
 
+static
+int expand_double_slashes_dot_and_dotdot(char *path)
+{
+       size_t expanded_path_len, path_len;
+       const char *curr_char, *path_last_char, *next_slash, *prev_slash;
+
+       path_len = strlen(path);
+       path_last_char = &path[path_len];
+
+       if (path_len == 0) {
+               goto error;
+       }
+
+       expanded_path_len = 0;
+
+       /* We iterate over the provided path to expand the "//", "../" and "./" */
+       for (curr_char = path; curr_char <= path_last_char; curr_char = next_slash + 1) {
+               /* Find the next forward slash. */
+               size_t curr_token_len;
+
+               if (curr_char == path_last_char) {
+                       expanded_path_len++;
+                       break;
+               }
+
+               next_slash = memchr(curr_char, '/', path_last_char - curr_char);
+               if (next_slash == NULL) {
+                       /* Reached the end of the provided path. */
+                       next_slash = path_last_char;
+               }
+
+               /* Compute how long is the previous token. */
+               curr_token_len = next_slash - curr_char;
+               switch(curr_token_len) {
+               case 0:
+                       /*
+                        * The pointer has not move meaning that curr_char is
+                        * pointing to a slash. It that case there is no token
+                        * to copy, so continue the iteration to find the next
+                        * token
+                        */
+                       continue;
+               case 1:
+                       /*
+                        * The pointer moved 1 character. Check if that
+                        * character is a dot ('.'), if it is: omit it, else
+                        * copy the token to the normalized path.
+                        */
+                       if (curr_char[0] == '.') {
+                               continue;
+                       }
+                       break;
+               case 2:
+                       /*
+                        * The pointer moved 2 characters. Check if these
+                        * characters are double dots ('..'). If that is the
+                        * case, we need to remove the last token of the
+                        * normalized path.
+                        */
+                       if (curr_char[0] == '.' && curr_char[1] == '.') {
+                               /*
+                                * Find the previous path component by
+                                * using the memrchr function to find the
+                                * previous forward slash and substract that
+                                * len to the resulting path.
+                                */
+                               prev_slash = lttng_memrchr(path, '/', expanded_path_len);
+                               /*
+                                * If prev_slash is NULL, we reached the
+                                * beginning of the path. We can't go back any
+                                * further.
+                                */
+                               if (prev_slash != NULL) {
+                                       expanded_path_len = prev_slash - path;
+                               }
+                               continue;
+                       }
+                       break;
+               default:
+                       break;
+               }
+
+               /*
+                * Copy the current token which is neither a '.' nor a '..'.
+                */
+               path[expanded_path_len++] = '/';
+               memcpy(&path[expanded_path_len], curr_char, curr_token_len);
+               expanded_path_len += curr_token_len;
+       }
+
+       if (expanded_path_len == 0) {
+               path[expanded_path_len++] = '/';
+       }
+
+       path[expanded_path_len] = '\0';
+       return 0;
+error:
+       return -1;
+}
+
 /*
  * Make a full resolution of the given path even if it doesn't exist.
  * This function uses the utils_partial_realpath function to resolve
@@ -176,12 +314,13 @@ error:
  * The returned string was allocated in the function, it is thus of
  * the responsibility of the caller to free this memory.
  */
-LTTNG_HIDDEN
-char *utils_expand_path(const char *path)
+static
+char *_utils_expand_path(const char *path, bool keep_symlink)
 {
-       char *next, *previous, *slash, *start_path, *absolute_path = NULL;
+       int ret;
+       char *absolute_path = NULL;
        char *last_token;
-       int is_dot, is_dotdot;
+       bool is_dot, is_dotdot;
 
        /* Safety net */
        if (path == NULL) {
@@ -189,60 +328,55 @@ char *utils_expand_path(const char *path)
        }
 
        /* Allocate memory for the absolute_path */
-       absolute_path = zmalloc(PATH_MAX);
+       absolute_path = zmalloc(LTTNG_PATH_MAX);
        if (absolute_path == NULL) {
                PERROR("zmalloc expand path");
                goto error;
        }
 
-       /*
-        * If the path is not already absolute nor explicitly relative,
-        * consider we're in the current directory
-        */
-       if (*path != '/' && strncmp(path, "./", 2) != 0 &&
-                       strncmp(path, "../", 3) != 0) {
-               snprintf(absolute_path, PATH_MAX, "./%s", path);
-       /* Else, we just copy the path */
+       if (path[0] == '/') {
+               ret = lttng_strncpy(absolute_path, path, LTTNG_PATH_MAX);
+               if (ret) {
+                       ERR("Path exceeds maximal size of %i bytes", LTTNG_PATH_MAX);
+                       goto error;
+               }
        } else {
-               strncpy(absolute_path, path, PATH_MAX);
-       }
-
-       /* Resolve partially our path */
-       absolute_path = utils_partial_realpath(absolute_path,
-                       absolute_path, PATH_MAX);
-
-       /* As long as we find '/./' in the working_path string */
-       while ((next = strstr(absolute_path, "/./"))) {
-
-               /* We prepare the start_path not containing it */
-               start_path = strndup(absolute_path, next - absolute_path);
-
-               /* And we concatenate it with the part after this string */
-               snprintf(absolute_path, PATH_MAX, "%s%s", start_path, next + 2);
+               /*
+                * This is a relative path. We need to get the present working
+                * directory and start the path walk from there.
+                */
+               char current_working_dir[LTTNG_PATH_MAX];
+               char *cwd_ret;
 
-               free(start_path);
+               cwd_ret = getcwd(current_working_dir, sizeof(current_working_dir));
+               if (!cwd_ret) {
+                       goto error;
+               }
+               /*
+                * Get the number of character in the CWD and allocate an array
+                * to can hold it and the path provided by the caller.
+                */
+               ret = snprintf(absolute_path, LTTNG_PATH_MAX, "%s/%s",
+                               current_working_dir, path);
+               if (ret >= LTTNG_PATH_MAX) {
+                       ERR("Concatenating current working directory %s and path %s exceeds maximal size of %i bytes",
+                                       current_working_dir, path, LTTNG_PATH_MAX);
+                       goto error;
+               }
        }
 
-       /* As long as we find '/../' in the working_path string */
-       while ((next = strstr(absolute_path, "/../"))) {
-               /* We find the last level of directory */
-               previous = absolute_path;
-               while ((slash = strpbrk(previous, "/")) && slash != next) {
-                       previous = slash + 1;
+       if (keep_symlink) {
+               /* Resolve partially our path */
+               absolute_path = utils_partial_realpath(absolute_path,
+                               absolute_path, LTTNG_PATH_MAX);
+               if (!absolute_path) {
+                       goto error;
                }
+       }
 
-               /* Then we prepare the start_path not containing it */
-               start_path = strndup(absolute_path, previous - absolute_path);
-
-               /* And we concatenate it with the part after the '/../' */
-               snprintf(absolute_path, PATH_MAX, "%s%s", start_path, next + 4);
-
-               /* We can free the memory used for the start path*/
-               free(start_path);
-
-               /* Then we verify for symlinks using partial_realpath */
-               absolute_path = utils_partial_realpath(absolute_path,
-                               absolute_path, PATH_MAX);
+       ret = expand_double_slashes_dot_and_dotdot(absolute_path);
+       if (ret) {
+               goto error;
        }
 
        /* Identify the last token */
@@ -276,7 +410,17 @@ error:
        free(absolute_path);
        return NULL;
 }
+LTTNG_HIDDEN
+char *utils_expand_path(const char *path)
+{
+       return _utils_expand_path(path, true);
+}
 
+LTTNG_HIDDEN
+char *utils_expand_path_keep_symlink(const char *path)
+{
+       return _utils_expand_path(path, false);
+}
 /*
  * Create a pipe in dst.
  */
@@ -393,6 +537,7 @@ void utils_close_pipe(int *src)
                if (ret) {
                        PERROR("close pipe");
                }
+               src[i] = -1;
        }
 }
 
@@ -458,13 +603,17 @@ int utils_create_pid_file(pid_t pid, const char *filepath)
                goto error;
        }
 
-       ret = fprintf(fp, "%d\n", pid);
+       ret = fprintf(fp, "%d\n", (int) pid);
        if (ret < 0) {
                PERROR("fprintf pid file");
+               goto error;
        }
 
-       fclose(fp);
-       DBG("Pid %d written in file %s", pid, filepath);
+       if (fclose(fp)) {
+               PERROR("fclose");
+       }
+       DBG("Pid %d written in file %s", (int) pid, filepath);
+       ret = 0;
 error:
        return ret;
 }
@@ -478,14 +627,16 @@ int utils_create_lock_file(const char *filepath)
 {
        int ret;
        int fd;
+       struct flock lock;
 
        assert(filepath);
 
-       fd = open(filepath, O_CREAT,
-               O_WRONLY | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+       memset(&lock, 0, sizeof(lock));
+       fd = open(filepath, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR |
+               S_IRGRP | S_IWGRP);
        if (fd < 0) {
                PERROR("open lock file %s", filepath);
-               ret = -1;
+               fd = -1;
                goto error;
        }
 
@@ -494,11 +645,17 @@ int utils_create_lock_file(const char *filepath)
         * already a process using the same lock file running
         * and we should exit.
         */
-       ret = flock(fd, LOCK_EX | LOCK_NB);
-       if (ret) {
-               WARN("Could not get lock file %s, another instance is running.",
+       lock.l_whence = SEEK_SET;
+       lock.l_type = F_WRLCK;
+
+       ret = fcntl(fd, F_SETLK, &lock);
+       if (ret == -1) {
+               PERROR("fcntl lock file");
+               ERR("Could not get lock file %s, another instance is running.",
                        filepath);
-               close(fd);
+               if (close(fd)) {
+                       PERROR("close lock file");
+               }
                fd = ret;
                goto error;
        }
@@ -508,187 +665,103 @@ error:
 }
 
 /*
- * Recursively create directory using the given path and mode.
+ * Create directory using the given path and mode.
  *
  * On success, return 0 else a negative error code.
  */
 LTTNG_HIDDEN
-int utils_mkdir_recursive(const char *path, mode_t mode)
+int utils_mkdir(const char *path, mode_t mode, int uid, int gid)
 {
-       char *p, tmp[PATH_MAX];
-       size_t len;
        int ret;
+       struct lttng_directory_handle *handle;
+       const struct lttng_credentials creds = {
+               .uid = (uid_t) uid,
+               .gid = (gid_t) gid,
+       };
 
-       assert(path);
-
-       ret = snprintf(tmp, sizeof(tmp), "%s", path);
-       if (ret < 0) {
-               PERROR("snprintf mkdir");
-               goto error;
-       }
-
-       len = ret;
-       if (tmp[len - 1] == '/') {
-               tmp[len - 1] = 0;
-       }
-
-       for (p = tmp + 1; *p; p++) {
-               if (*p == '/') {
-                       *p = 0;
-                       if (tmp[strlen(tmp) - 1] == '.' &&
-                                       tmp[strlen(tmp) - 2] == '.' &&
-                                       tmp[strlen(tmp) - 3] == '/') {
-                               ERR("Using '/../' is not permitted in the trace path (%s)",
-                                               tmp);
-                               ret = -1;
-                               goto error;
-                       }
-                       ret = mkdir(tmp, mode);
-                       if (ret < 0) {
-                               if (errno != EEXIST) {
-                                       PERROR("mkdir recursive");
-                                       ret = -errno;
-                                       goto error;
-                               }
-                       }
-                       *p = '/';
-               }
-       }
-
-       ret = mkdir(tmp, mode);
-       if (ret < 0) {
-               if (errno != EEXIST) {
-                       PERROR("mkdir recursive last piece");
-                       ret = -errno;
-               } else {
-                       ret = 0;
-               }
+       handle = lttng_directory_handle_create(NULL);
+       if (!handle) {
+               ret = -1;
+               goto end;
        }
-
-error:
+       ret = lttng_directory_handle_create_subdirectory_as_user(
+                       handle, path, mode,
+                       (uid >= 0 || gid >= 0) ? &creds : NULL);
+end:
+       lttng_directory_handle_put(handle);
        return ret;
 }
 
 /*
- * Create the stream tracefile on disk.
+ * Recursively create directory using the given path and mode, under the
+ * provided uid and gid.
  *
- * Return 0 on success or else a negative value.
+ * On success, return 0 else a negative error code.
  */
 LTTNG_HIDDEN
-int utils_create_stream_file(const char *path_name, char *file_name, uint64_t size,
-               uint64_t count, int uid, int gid, char *suffix)
+int utils_mkdir_recursive(const char *path, mode_t mode, int uid, int gid)
 {
-       int ret, out_fd, flags, mode;
-       char full_path[PATH_MAX], *path_name_suffix = NULL, *path;
-       char *extra = NULL;
-
-       assert(path_name);
-       assert(file_name);
-
-       ret = snprintf(full_path, sizeof(full_path), "%s/%s",
-                       path_name, file_name);
-       if (ret < 0) {
-               PERROR("snprintf create output file");
-               goto error;
-       }
-
-       /* Setup extra string if suffix or/and a count is needed. */
-       if (size > 0 && suffix) {
-               ret = asprintf(&extra, "_%" PRIu64 "%s", count, suffix);
-       } else if (size > 0) {
-               ret = asprintf(&extra, "_%" PRIu64, count);
-       } else if (suffix) {
-               ret = asprintf(&extra, "%s", suffix);
-       }
-       if (ret < 0) {
-               PERROR("Allocating extra string to name");
-               goto error;
-       }
-
-       /*
-        * If we split the trace in multiple files, we have to add the count at the
-        * end of the tracefile name
-        */
-       if (extra) {
-               ret = asprintf(&path_name_suffix, "%s%s", full_path, extra);
-               if (ret < 0) {
-                       PERROR("Allocating path name with extra string");
-                       goto error_free_suffix;
-               }
-               path = path_name_suffix;
-       } else {
-               path = full_path;
-       }
-
-       flags = O_WRONLY | O_CREAT | O_TRUNC;
-       /* Open with 660 mode */
-       mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
+       int ret;
+       struct lttng_directory_handle *handle;
+       const struct lttng_credentials creds = {
+               .uid = (uid_t) uid,
+               .gid = (gid_t) gid,
+       };
 
-       if (uid < 0 || gid < 0) {
-               out_fd = open(path, flags, mode);
-       } else {
-               out_fd = run_as_open(path, flags, mode, uid, gid);
-       }
-       if (out_fd < 0) {
-               PERROR("open stream path %s", path);
-               goto error_open;
+       handle = lttng_directory_handle_create(NULL);
+       if (!handle) {
+               ret = -1;
+               goto end;
        }
-       ret = out_fd;
-
-error_open:
-       free(path_name_suffix);
-error_free_suffix:
-       free(extra);
-error:
+       ret = lttng_directory_handle_create_subdirectory_recursive_as_user(
+                       handle, path, mode,
+                       (uid >= 0 || gid >= 0) ? &creds : NULL);
+end:
+       lttng_directory_handle_put(handle);
        return ret;
 }
 
 /*
- * Change the output tracefile according to the given size and count The
- * new_count pointer is set during this operation.
- *
- * From the consumer, the stream lock MUST be held before calling this function
- * because we are modifying the stream status.
+ * out_stream_path is the output parameter.
  *
  * Return 0 on success or else a negative value.
  */
 LTTNG_HIDDEN
-int utils_rotate_stream_file(char *path_name, char *file_name, uint64_t size,
-               uint64_t count, int uid, int gid, int out_fd, uint64_t *new_count,
-               int *stream_fd)
+int utils_stream_file_path(const char *path_name, const char *file_name,
+               uint64_t size, uint64_t count, const char *suffix,
+               char *out_stream_path, size_t stream_path_len)
 {
        int ret;
+        char count_str[MAX_INT_DEC_LEN(count) + 1] = {};
+       const char *path_separator;
 
-       assert(new_count);
-       assert(stream_fd);
-
-       ret = close(out_fd);
-       if (ret < 0) {
-               PERROR("Closing tracefile");
-               goto error;
-       }
-
-       if (count > 0) {
-               *new_count = (*new_count + 1) % count;
+       if (path_name && (path_name[0] == '\0' ||
+                       path_name[strlen(path_name) - 1] == '/')) {
+               path_separator = "";
        } else {
-               (*new_count)++;
+               path_separator = "/";
        }
 
-       ret = utils_create_stream_file(path_name, file_name, size, *new_count,
-                       uid, gid, 0);
-       if (ret < 0) {
-               goto error;
+       path_name = path_name ? : "";
+       suffix = suffix ? : "";
+       if (size > 0) {
+               ret = snprintf(count_str, sizeof(count_str), "_%" PRIu64,
+                               count);
+               assert(ret > 0 && ret < sizeof(count_str));
        }
-       *stream_fd = ret;
 
-       /* Success. */
-       ret = 0;
-
-error:
+        ret = snprintf(out_stream_path, stream_path_len, "%s%s%s%s%s",
+                       path_name, path_separator, file_name, count_str,
+                       suffix);
+       if (ret < 0 || ret >= stream_path_len) {
+               ERR("Truncation occurred while formatting stream path");
+               ret = -1;
+       } else {
+               ret = 0;
+       }
        return ret;
 }
 
-
 /**
  * Parse a string that represents a size in human readable format. It
  * supports decimal integers suffixed by 'k', 'K', 'M' or 'G'.
@@ -787,6 +860,134 @@ end:
        return ret;
 }
 
+/**
+ * Parse a string that represents a time in human readable format. It
+ * supports decimal integers suffixed by:
+ *     "us" for microsecond,
+ *     "ms" for millisecond,
+ *     "s"  for second,
+ *     "m"  for minute,
+ *     "h"  for hour
+ *
+ * The suffix multiply the integer by:
+ *     "us" : 1
+ *     "ms" : 1000
+ *     "s"  : 1000000
+ *     "m"  : 60000000
+ *     "h"  : 3600000000
+ *
+ * Note that unit-less numbers are assumed to be microseconds.
+ *
+ * @param str          The string to parse, assumed to be NULL-terminated.
+ * @param time_us      Pointer to a uint64_t that will be filled with the
+ *                     resulting time in microseconds.
+ *
+ * @return 0 on success, -1 on failure.
+ */
+LTTNG_HIDDEN
+int utils_parse_time_suffix(char const * const str, uint64_t * const time_us)
+{
+       int ret;
+       uint64_t base_time;
+       uint64_t multiplier = 1;
+       const char *str_end;
+       char *num_end;
+
+       if (!str) {
+               DBG("utils_parse_time_suffix: received a NULL string.");
+               ret = -1;
+               goto end;
+       }
+
+       /* strtoull will accept a negative number, but we don't want to. */
+       if (strchr(str, '-') != NULL) {
+               DBG("utils_parse_time_suffix: invalid time string, should not contain '-'.");
+               ret = -1;
+               goto end;
+       }
+
+       /* str_end will point to the \0 */
+       str_end = str + strlen(str);
+       errno = 0;
+       base_time = strtoull(str, &num_end, 10);
+       if (errno != 0) {
+               PERROR("utils_parse_time_suffix strtoull on string \"%s\"", str);
+               ret = -1;
+               goto end;
+       }
+
+       if (num_end == str) {
+               /* strtoull parsed nothing, not good. */
+               DBG("utils_parse_time_suffix: strtoull had nothing good to parse.");
+               ret = -1;
+               goto end;
+       }
+
+       /* Check if a prefix is present. */
+       switch (*num_end) {
+       case 'u':
+               /*
+                * Microsecond (us)
+                *
+                * Skip the "us" if the string matches the "us" suffix,
+                * otherwise let the check for the end of the string handle
+                * the error reporting.
+                */
+               if (*(num_end + 1) == 's') {
+                       num_end += 2;
+               }
+               break;
+       case 'm':
+               if (*(num_end + 1) == 's') {
+                       /* Millisecond (ms) */
+                       multiplier = USEC_PER_MSEC;
+                       /* Skip the 's' */
+                       num_end++;
+               } else {
+                       /* Minute (m) */
+                       multiplier = USEC_PER_MINUTE;
+               }
+               num_end++;
+               break;
+       case 's':
+               /* Second */
+               multiplier = USEC_PER_SEC;
+               num_end++;
+               break;
+       case 'h':
+               /* Hour */
+               multiplier = USEC_PER_HOURS;
+               num_end++;
+               break;
+       case '\0':
+               break;
+       default:
+               DBG("utils_parse_time_suffix: invalid suffix.");
+               ret = -1;
+               goto end;
+       }
+
+       /* Check for garbage after the valid input. */
+       if (num_end != str_end) {
+               DBG("utils_parse_time_suffix: Garbage after time string.");
+               ret = -1;
+               goto end;
+       }
+
+       *time_us = base_time * multiplier;
+
+       /* Check for overflow */
+       if ((*time_us / multiplier) != base_time) {
+               DBG("utils_parse_time_suffix: oops, overflow detected.");
+               ret = -1;
+               goto end;
+       }
+
+       ret = 0;
+end:
+       return ret;
+}
+
 /*
  * fls: returns the position of the most significant bit.
  * Returns 0 if no bit is set, else returns the position of the most
@@ -807,6 +1008,59 @@ static inline unsigned int fls_u32(uint32_t x)
 #define HAS_FLS_U32
 #endif
 
+#if defined(__x86_64) && defined(__LP64__)
+static inline
+unsigned int fls_u64(uint64_t x)
+{
+       long r;
+
+       asm("bsrq %1,%0\n\t"
+           "jnz 1f\n\t"
+           "movq $-1,%0\n\t"
+           "1:\n\t"
+           : "=r" (r) : "rm" (x));
+       return r + 1;
+}
+#define HAS_FLS_U64
+#endif
+
+#ifndef HAS_FLS_U64
+static __attribute__((unused))
+unsigned int fls_u64(uint64_t x)
+{
+       unsigned int r = 64;
+
+       if (!x)
+               return 0;
+
+       if (!(x & 0xFFFFFFFF00000000ULL)) {
+               x <<= 32;
+               r -= 32;
+       }
+       if (!(x & 0xFFFF000000000000ULL)) {
+               x <<= 16;
+               r -= 16;
+       }
+       if (!(x & 0xFF00000000000000ULL)) {
+               x <<= 8;
+               r -= 8;
+       }
+       if (!(x & 0xF000000000000000ULL)) {
+               x <<= 4;
+               r -= 4;
+       }
+       if (!(x & 0xC000000000000000ULL)) {
+               x <<= 2;
+               r -= 2;
+       }
+       if (!(x & 0x8000000000000000ULL)) {
+               x <<= 1;
+               r -= 1;
+       }
+       return r;
+}
+#endif
+
 #ifndef HAS_FLS_U32
 static __attribute__((unused)) unsigned int fls_u32(uint32_t x)
 {
@@ -853,21 +1107,35 @@ int utils_get_count_order_u32(uint32_t x)
        return fls_u32(x - 1);
 }
 
+/*
+ * Return the minimum order for which x <= (1UL << order).
+ * Return -1 if x is 0.
+ */
+LTTNG_HIDDEN
+int utils_get_count_order_u64(uint64_t x)
+{
+       if (!x) {
+               return -1;
+       }
+
+       return fls_u64(x - 1);
+}
+
 /**
  * Obtain the value of LTTNG_HOME environment variable, if exists.
  * Otherwise returns the value of HOME.
  */
 LTTNG_HIDDEN
-char *utils_get_home_dir(void)
+const char *utils_get_home_dir(void)
 {
        char *val = NULL;
        struct passwd *pwd;
 
-       val = getenv(DEFAULT_LTTNG_HOME_ENV_VAR);
+       val = lttng_secure_getenv(DEFAULT_LTTNG_HOME_ENV_VAR);
        if (val != NULL) {
                goto end;
        }
-       val = getenv(DEFAULT_LTTNG_FALLBACK_HOME_ENV_VAR);
+       val = lttng_secure_getenv(DEFAULT_LTTNG_FALLBACK_HOME_ENV_VAR);
        if (val != NULL) {
                goto end;
        }
@@ -925,26 +1193,6 @@ end:
        return home_dir;
 }
 
-/*
- * Obtain the value of LTTNG_KMOD_PROBES environment variable, if exists.
- * Otherwise returns NULL.
- */
-LTTNG_HIDDEN
-char *utils_get_kmod_probes_list(void)
-{
-       return getenv(DEFAULT_LTTNG_KMOD_PROBES);
-}
-
-/*
- * Obtain the value of LTTNG_EXTRA_KMOD_PROBES environment variable, if
- * exists. Otherwise returns NULL.
- */
-LTTNG_HIDDEN
-char *utils_get_extra_kmod_probes_list(void)
-{
-       return getenv(DEFAULT_LTTNG_EXTRA_KMOD_PROBES);
-}
-
 /*
  * With the given format, fill dst with the time of len maximum siz.
  *
@@ -973,24 +1221,77 @@ size_t utils_get_current_time_str(const char *format, char *dst, size_t len)
 }
 
 /*
- * Return the group ID matching name, else 0 if it cannot be found.
+ * Return 0 on success and set *gid to the group_ID matching the passed name.
+ * Else -1 if it cannot be found or an error occurred.
  */
 LTTNG_HIDDEN
-gid_t utils_get_group_id(const char *name)
+int utils_get_group_id(const char *name, bool warn, gid_t *gid)
 {
-       struct group *grp;
+       static volatile int warn_once;
+       int ret;
+       long sys_len;
+       size_t len;
+       struct group grp;
+       struct group *result;
+       struct lttng_dynamic_buffer buffer;
+
+       /* Get the system limit, if it exists. */
+       sys_len = sysconf(_SC_GETGR_R_SIZE_MAX);
+       if (sys_len == -1) {
+               len = 1024;
+       } else {
+               len = (size_t) sys_len;
+       }
 
-       grp = getgrnam(name);
-       if (!grp) {
-               static volatile int warn_once;
+       lttng_dynamic_buffer_init(&buffer);
+       ret = lttng_dynamic_buffer_set_size(&buffer, len);
+       if (ret) {
+               ERR("Failed to allocate group info buffer");
+               ret = -1;
+               goto error;
+       }
 
-               if (!warn_once) {
-                       WARN("No tracing group detected");
-                       warn_once = 1;
+       while ((ret = getgrnam_r(name, &grp, buffer.data, buffer.size, &result)) == ERANGE) {
+               const size_t new_len = 2 * buffer.size;
+
+               /* Buffer is not big enough, increase its size. */
+               if (new_len < buffer.size) {
+                       ERR("Group info buffer size overflow");
+                       ret = -1;
+                       goto error;
                }
-               return 0;
+
+               ret = lttng_dynamic_buffer_set_size(&buffer, new_len);
+               if (ret) {
+                       ERR("Failed to grow group info buffer to %zu bytes",
+                                       new_len);
+                       ret = -1;
+                       goto error;
+               }
+       }
+       if (ret) {
+               PERROR("Failed to get group file entry for group name \"%s\"",
+                               name);
+               ret = -1;
+               goto error;
+       }
+
+       /* Group not found. */
+       if (!result) {
+               ret = -1;
+               goto error;
+       }
+
+       *gid = result->gr_gid;
+       ret = 0;
+
+error:
+       if (ret && warn && !warn_once) {
+               WARN("No tracing group detected");
+               warn_once = 1;
        }
-       return grp->gr_gid;
+       lttng_dynamic_buffer_reset(&buffer);
+       return ret;
 }
 
 /*
@@ -1026,12 +1327,204 @@ char *utils_generate_optstring(const struct option *long_options,
                        break;
                }
 
-               optstring[str_pos++] = (char)long_options[i].val;
-               if (long_options[i].has_arg) {
-                       optstring[str_pos++] = ':';
+               if (long_options[i].val != '\0') {
+                       optstring[str_pos++] = (char) long_options[i].val;
+                       if (long_options[i].has_arg) {
+                               optstring[str_pos++] = ':';
+                       }
                }
        }
 
 end:
        return optstring;
 }
+
+/*
+ * Try to remove a hierarchy of empty directories, recursively. Don't unlink
+ * any file. Try to rmdir any empty directory within the hierarchy.
+ */
+LTTNG_HIDDEN
+int utils_recursive_rmdir(const char *path)
+{
+       int ret;
+       struct lttng_directory_handle *handle;
+
+       handle = lttng_directory_handle_create(NULL);
+       if (!handle) {
+               ret = -1;
+               goto end;
+       }
+       ret = lttng_directory_handle_remove_subdirectory(handle, path);
+end:
+       lttng_directory_handle_put(handle);
+       return ret;
+}
+
+LTTNG_HIDDEN
+int utils_truncate_stream_file(int fd, off_t length)
+{
+       int ret;
+       off_t lseek_ret;
+
+       ret = ftruncate(fd, length);
+       if (ret < 0) {
+               PERROR("ftruncate");
+               goto end;
+       }
+       lseek_ret = lseek(fd, length, SEEK_SET);
+       if (lseek_ret < 0) {
+               PERROR("lseek");
+               ret = -1;
+               goto end;
+       }
+end:
+       return ret;
+}
+
+static const char *get_man_bin_path(void)
+{
+       char *env_man_path = lttng_secure_getenv(DEFAULT_MAN_BIN_PATH_ENV);
+
+       if (env_man_path) {
+               return env_man_path;
+       }
+
+       return DEFAULT_MAN_BIN_PATH;
+}
+
+LTTNG_HIDDEN
+int utils_show_help(int section, const char *page_name,
+               const char *help_msg)
+{
+       char section_string[8];
+       const char *man_bin_path = get_man_bin_path();
+       int ret = 0;
+
+       if (help_msg) {
+               printf("%s", help_msg);
+               goto end;
+       }
+
+       /* Section integer -> section string */
+       ret = sprintf(section_string, "%d", section);
+       assert(ret > 0 && ret < 8);
+
+       /*
+        * Execute man pager.
+        *
+        * We provide -M to man here because LTTng-tools can
+        * be installed outside /usr, in which case its man pages are
+        * not located in the default /usr/share/man directory.
+        */
+       ret = execlp(man_bin_path, "man", "-M", MANPATH,
+               section_string, page_name, NULL);
+
+end:
+       return ret;
+}
+
+static
+int read_proc_meminfo_field(const char *field, size_t *value)
+{
+       int ret;
+       FILE *proc_meminfo;
+       char name[PROC_MEMINFO_FIELD_MAX_NAME_LEN] = {};
+
+       proc_meminfo = fopen(PROC_MEMINFO_PATH, "r");
+       if (!proc_meminfo) {
+               PERROR("Failed to fopen() " PROC_MEMINFO_PATH);
+               ret = -1;
+               goto fopen_error;
+        }
+
+       /*
+        * Read the contents of /proc/meminfo line by line to find the right
+        * field.
+        */
+       while (!feof(proc_meminfo)) {
+               unsigned long value_kb;
+
+               ret = fscanf(proc_meminfo,
+                               "%" MAX_NAME_LEN_SCANF_IS_A_BROKEN_API "s %lu kB\n",
+                               name, &value_kb);
+               if (ret == EOF) {
+                       /*
+                        * fscanf() returning EOF can indicate EOF or an error.
+                        */
+                       if (ferror(proc_meminfo)) {
+                               PERROR("Failed to parse " PROC_MEMINFO_PATH);
+                       }
+                       break;
+               }
+
+               if (ret == 2 && strcmp(name, field) == 0) {
+                       /*
+                        * This number is displayed in kilo-bytes. Return the
+                        * number of bytes.
+                        */
+                       *value = ((size_t) value_kb) * 1024;
+                       ret = 0;
+                       goto found;
+               }
+       }
+       /* Reached the end of the file without finding the right field. */
+       ret = -1;
+
+found:
+       fclose(proc_meminfo);
+fopen_error:
+       return ret;
+}
+
+/*
+ * Returns an estimate of the number of bytes of memory available based on the
+ * the information in `/proc/meminfo`. The number returned by this function is
+ * a best guess.
+ */
+LTTNG_HIDDEN
+int utils_get_memory_available(size_t *value)
+{
+       return read_proc_meminfo_field(PROC_MEMINFO_MEMAVAILABLE_LINE, value);
+}
+
+/*
+ * Returns the total size of the memory on the system in bytes based on the
+ * the information in `/proc/meminfo`.
+ */
+LTTNG_HIDDEN
+int utils_get_memory_total(size_t *value)
+{
+       return read_proc_meminfo_field(PROC_MEMINFO_MEMTOTAL_LINE, value);
+}
+
+LTTNG_HIDDEN
+int utils_change_working_directory(const char *path)
+{
+       int ret;
+
+       assert(path);
+
+       DBG("Changing working directory to \"%s\"", path);
+       ret = chdir(path);
+       if (ret) {
+               PERROR("Failed to change working directory to \"%s\"", path);
+               goto end;
+       }
+
+       /* Check for write access */
+       if (access(path, W_OK)) {
+               if (errno == EACCES) {
+                       /*
+                        * Do not treat this as an error since the permission
+                        * might change in the lifetime of the process
+                        */
+                       DBG("Working directory \"%s\" is not writable", path);
+               } else {
+                       PERROR("Failed to check if working directory \"%s\" is writable",
+                                       path);
+               }
+       }
+
+end:
+       return ret;
+}
This page took 0.036579 seconds and 4 git commands to generate.