+
+ for (i = 0; i < opt_count; i++) {
+ if (!long_options[i].name) {
+ /* Got to the trailing NULL element */
+ break;
+ }
+
+ 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;
+}
+
+LTTNG_HIDDEN
+enum lttng_error_code utils_user_id_from_name(const char *user_name, uid_t *uid)
+{
+ struct passwd p, *pres;
+ int ret;
+ enum lttng_error_code ret_val = LTTNG_OK;
+ char *buf = NULL;
+ ssize_t buflen;
+
+ buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
+ if (buflen < 0) {
+ buflen = FALLBACK_USER_BUFLEN;
+ }
+
+ buf = zmalloc(buflen);
+ if (!buf) {
+ ret_val = LTTNG_ERR_NOMEM;
+ goto end;
+ }
+
+ for (;;) {
+ ret = getpwnam_r(user_name, &p, buf, buflen, &pres);
+ switch (ret) {
+ case EINTR:
+ continue;
+ case ERANGE:
+ buflen *= 2;
+ free(buf);
+ buf = zmalloc(buflen);
+ if (!buf) {
+ ret_val = LTTNG_ERR_NOMEM;
+ goto end;
+ }
+ continue;
+ default:
+ goto end_loop;
+ }
+ }
+end_loop:
+
+ switch (ret) {
+ case 0:
+ if (pres == NULL) {
+ ret_val = LTTNG_ERR_USER_NOT_FOUND;
+ } else {
+ *uid = p.pw_uid;
+ DBG("Lookup of tracker UID/VUID: name '%s' maps to uid %" PRId64,
+ user_name, (int64_t) *uid);
+ ret_val = LTTNG_OK;
+ }
+ break;
+ case ENOENT:
+ case ESRCH:
+ case EBADF:
+ case EPERM:
+ ret_val = LTTNG_ERR_USER_NOT_FOUND;
+ break;
+ default:
+ ret_val = LTTNG_ERR_NOMEM;
+ }
+end:
+ free(buf);
+ return ret_val;
+}
+
+LTTNG_HIDDEN
+enum lttng_error_code utils_group_id_from_name(
+ const char *group_name, gid_t *gid)
+{
+ struct group g, *gres;
+ int ret;
+ enum lttng_error_code ret_val = LTTNG_OK;
+ char *buf = NULL;
+ ssize_t buflen;
+
+ buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
+ if (buflen < 0) {
+ buflen = FALLBACK_GROUP_BUFLEN;
+ }
+
+ buf = zmalloc(buflen);
+ if (!buf) {
+ ret_val = LTTNG_ERR_NOMEM;
+ goto end;
+ }
+
+ for (;;) {
+ ret = getgrnam_r(group_name, &g, buf, buflen, &gres);
+ switch (ret) {
+ case EINTR:
+ continue;
+ case ERANGE:
+ buflen *= 2;
+ free(buf);
+ buf = zmalloc(buflen);
+ if (!buf) {
+ ret_val = LTTNG_ERR_NOMEM;
+ goto end;
+ }
+ continue;
+ default:
+ goto end_loop;
+ }
+ }
+end_loop:
+
+ switch (ret) {
+ case 0:
+ if (gres == NULL) {
+ ret_val = LTTNG_ERR_GROUP_NOT_FOUND;
+ } else {
+ *gid = g.gr_gid;
+ DBG("Lookup of tracker GID/GUID: name '%s' maps to gid %" PRId64,
+ group_name, (int64_t) *gid);
+ ret_val = LTTNG_OK;
+ }
+ break;
+ case ENOENT:
+ case ESRCH:
+ case EBADF:
+ case EPERM:
+ ret_val = LTTNG_ERR_GROUP_NOT_FOUND;
+ break;
+ default:
+ ret_val = LTTNG_ERR_NOMEM;
+ }
+end:
+ free(buf);
+ return ret_val;
+}
+
+LTTNG_HIDDEN
+int utils_parse_unsigned_long_long(const char *str,
+ unsigned long long *value)
+{
+ int ret;
+ char *endptr;
+
+ assert(str);
+ assert(value);
+
+ errno = 0;
+ *value = strtoull(str, &endptr, 10);
+
+ /* Conversion failed. Out of range? */
+ if (errno != 0) {
+ /* Don't print an error; allow the caller to log a better error. */
+ DBG("Failed to parse string as unsigned long long number: string = '%s', errno = %d",
+ str, errno);
+ ret = -1;
+ goto end;
+ }
+
+ /* Not the end of the string or empty string. */
+ if (*endptr || endptr == str) {
+ DBG("Failed to parse string as unsigned long long number: string = '%s'",
+ str);
+ ret = -1;
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ return ret;