github.com/rigado/snapd@v2.42.5-go-mod+incompatible/cmd/libsnap-confine-private/utils.c (about) 1 /* 2 * Copyright (C) 2015 Canonical Ltd 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 3 as 6 * published by the Free Software Foundation. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 * 16 */ 17 #include <errno.h> 18 #include <fcntl.h> 19 #include <stdarg.h> 20 #include <stdio.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <sys/stat.h> 24 #include <unistd.h> 25 26 #include "cleanup-funcs.h" 27 #include "panic.h" 28 #include "utils.h" 29 30 void die(const char *msg, ...) 31 { 32 va_list ap; 33 va_start(ap, msg); 34 sc_panicv(msg, ap); 35 va_end(ap); 36 } 37 38 struct sc_bool_name { 39 const char *text; 40 bool value; 41 }; 42 43 static const struct sc_bool_name sc_bool_names[] = { 44 {"yes", true}, 45 {"no", false}, 46 {"1", true}, 47 {"0", false}, 48 {"", false}, 49 }; 50 51 /** 52 * Convert string to a boolean value, with a default. 53 * 54 * The return value is 0 in case of success or -1 when the string cannot be 55 * converted correctly. In such case errno is set to indicate the problem and 56 * the value is not written back to the caller-supplied pointer. 57 * 58 * If the text cannot be recognized, the default value is used. 59 **/ 60 static int parse_bool(const char *text, bool *value, bool default_value) 61 { 62 if (value == NULL) { 63 errno = EFAULT; 64 return -1; 65 } 66 if (text == NULL) { 67 *value = default_value; 68 return 0; 69 } 70 for (size_t i = 0; i < sizeof sc_bool_names / sizeof *sc_bool_names; 71 ++i) { 72 if (strcmp(text, sc_bool_names[i].text) == 0) { 73 *value = sc_bool_names[i].value; 74 return 0; 75 } 76 } 77 errno = EINVAL; 78 return -1; 79 } 80 81 /** 82 * Get an environment variable and convert it to a boolean. 83 * 84 * Supported values are those of parse_bool(), namely "yes", "no" as well as "1" 85 * and "0". All other values are treated as false and a diagnostic message is 86 * printed to stderr. If the environment variable is unset, set value to the 87 * default_value as if the environment variable was set to default_value. 88 **/ 89 static bool getenv_bool(const char *name, bool default_value) 90 { 91 const char *str_value = getenv(name); 92 bool value = default_value; 93 if (parse_bool(str_value, &value, default_value) < 0) { 94 if (errno == EINVAL) { 95 fprintf(stderr, 96 "WARNING: unrecognized value of environment variable %s (expected yes/no or 1/0)\n", 97 name); 98 return false; 99 } else { 100 die("cannot convert value of environment variable %s to a boolean", name); 101 } 102 } 103 return value; 104 } 105 106 bool sc_is_debug_enabled(void) 107 { 108 return getenv_bool("SNAP_CONFINE_DEBUG", false) 109 || getenv_bool("SNAPD_DEBUG", false); 110 } 111 112 bool sc_is_reexec_enabled(void) 113 { 114 return getenv_bool("SNAP_REEXEC", true); 115 } 116 117 void debug(const char *msg, ...) 118 { 119 if (sc_is_debug_enabled()) { 120 va_list va; 121 va_start(va, msg); 122 fprintf(stderr, "DEBUG: "); 123 vfprintf(stderr, msg, va); 124 fprintf(stderr, "\n"); 125 va_end(va); 126 } 127 } 128 129 void write_string_to_file(const char *filepath, const char *buf) 130 { 131 debug("write_string_to_file %s %s", filepath, buf); 132 FILE *f = fopen(filepath, "w"); 133 if (f == NULL) 134 die("fopen %s failed", filepath); 135 if (fwrite(buf, strlen(buf), 1, f) != 1) 136 die("fwrite failed"); 137 if (fflush(f) != 0) 138 die("fflush failed"); 139 if (fclose(f) != 0) 140 die("fclose failed"); 141 } 142 143 int sc_nonfatal_mkpath(const char *const path, mode_t mode) 144 { 145 // If asked to create an empty path, return immediately. 146 if (strlen(path) == 0) { 147 return 0; 148 } 149 // We're going to use strtok_r, which needs to modify the path, so we'll 150 // make a copy of it. 151 char *path_copy SC_CLEANUP(sc_cleanup_string) = NULL; 152 path_copy = strdup(path); 153 if (path_copy == NULL) { 154 return -1; 155 } 156 // Open flags to use while we walk the user data path: 157 // - Don't follow symlinks 158 // - Don't allow child access to file descriptor 159 // - Only open a directory (fail otherwise) 160 const int open_flags = O_NOFOLLOW | O_CLOEXEC | O_DIRECTORY; 161 162 // We're going to create each path segment via openat/mkdirat calls instead 163 // of mkdir calls, to avoid following symlinks and placing the user data 164 // directory somewhere we never intended for it to go. The first step is to 165 // get an initial file descriptor. 166 int fd SC_CLEANUP(sc_cleanup_close) = AT_FDCWD; 167 if (path_copy[0] == '/') { 168 fd = open("/", open_flags); 169 if (fd < 0) { 170 return -1; 171 } 172 } 173 // strtok_r needs a pointer to keep track of where it is in the string. 174 char *path_walker = NULL; 175 176 // Initialize tokenizer and obtain first path segment. 177 char *path_segment = strtok_r(path_copy, "/", &path_walker); 178 while (path_segment) { 179 // Try to create the directory. It's okay if it already existed, but 180 // return with error on any other error. Reset errno before attempting 181 // this as it may stay stale (errno is not reset if mkdirat(2) returns 182 // successfully). 183 errno = 0; 184 if (mkdirat(fd, path_segment, mode) < 0 && errno != EEXIST) { 185 return -1; 186 } 187 // Open the parent directory we just made (and close the previous one 188 // (but not the special value AT_FDCWD) so we can continue down the 189 // path. 190 int previous_fd = fd; 191 fd = openat(fd, path_segment, open_flags); 192 if (previous_fd != AT_FDCWD && close(previous_fd) != 0) { 193 return -1; 194 } 195 if (fd < 0) { 196 return -1; 197 } 198 // Obtain the next path segment. 199 path_segment = strtok_r(NULL, "/", &path_walker); 200 } 201 return 0; 202 }