Git fork
at reftables-rust 396 lines 10 kB view raw
1#include "git-compat-util.h" 2#include "abspath.h" 3#include "sigchain.h" 4#include "strbuf.h" 5#include "trace2/tr2_dst.h" 6#include "trace2/tr2_sid.h" 7#include "trace2/tr2_sysenv.h" 8 9/* 10 * How many attempts we will make at creating an automatically-named trace file. 11 */ 12#define MAX_AUTO_ATTEMPTS 10 13 14/* 15 * Sentinel file used to detect when we should discard new traces to avoid 16 * writing too many trace files to a directory. 17 */ 18#define DISCARD_SENTINEL_NAME "git-trace2-discard" 19 20/* 21 * When set to zero, disables directory file count checks. Otherwise, controls 22 * how many files we can write to a directory before entering discard mode. 23 * This can be overridden via the TR2_SYSENV_MAX_FILES setting. 24 */ 25static int tr2env_max_files = 0; 26 27static int tr2_dst_want_warning(void) 28{ 29 static int tr2env_dst_debug = -1; 30 31 if (tr2env_dst_debug == -1) { 32 const char *env_value = tr2_sysenv_get(TR2_SYSENV_DST_DEBUG); 33 if (!env_value || !*env_value) 34 tr2env_dst_debug = 0; 35 else 36 tr2env_dst_debug = atoi(env_value) > 0; 37 } 38 39 return tr2env_dst_debug; 40} 41 42void tr2_dst_trace_disable(struct tr2_dst *dst) 43{ 44 if (dst->need_close) 45 close(dst->fd); 46 dst->fd = 0; 47 dst->initialized = 1; 48 dst->need_close = 0; 49} 50 51/* 52 * Check to make sure we're not overloading the target directory with too many 53 * files. First get the threshold (if present) from the config or envvar. If 54 * it's zero or unset, disable this check. Next check for the presence of a 55 * sentinel file, then check file count. 56 * 57 * Returns 0 if tracing should proceed as normal. Returns 1 if the sentinel file 58 * already exists, which means tracing should be disabled. Returns -1 if there 59 * are too many files but there was no sentinel file, which means we have 60 * created and should write traces to the sentinel file. 61 * 62 * We expect that some trace processing system is gradually collecting files 63 * from the target directory; after it removes the sentinel file we'll start 64 * writing traces again. 65 */ 66static int tr2_dst_too_many_files(struct tr2_dst *dst, const char *tgt_prefix) 67{ 68 int file_count = 0, max_files = 0, ret = 0; 69 const char *max_files_var; 70 DIR *dirp; 71 struct strbuf path = STRBUF_INIT, sentinel_path = STRBUF_INIT; 72 struct stat statbuf; 73 74 /* Get the config or envvar and decide if we should continue this check */ 75 max_files_var = tr2_sysenv_get(TR2_SYSENV_MAX_FILES); 76 if (max_files_var && *max_files_var && ((max_files = atoi(max_files_var)) >= 0)) 77 tr2env_max_files = max_files; 78 79 if (!tr2env_max_files) { 80 ret = 0; 81 goto cleanup; 82 } 83 84 strbuf_addstr(&path, tgt_prefix); 85 if (!is_dir_sep(path.buf[path.len - 1])) { 86 strbuf_addch(&path, '/'); 87 } 88 89 /* check sentinel */ 90 strbuf_addbuf(&sentinel_path, &path); 91 strbuf_addstr(&sentinel_path, DISCARD_SENTINEL_NAME); 92 if (!stat(sentinel_path.buf, &statbuf)) { 93 ret = 1; 94 goto cleanup; 95 } 96 97 /* check file count */ 98 dirp = opendir(path.buf); 99 while (file_count < tr2env_max_files && dirp && readdir(dirp)) 100 file_count++; 101 if (dirp) 102 closedir(dirp); 103 104 if (file_count >= tr2env_max_files) { 105 dst->too_many_files = 1; 106 dst->fd = open(sentinel_path.buf, O_WRONLY | O_CREAT | O_EXCL, 0666); 107 ret = -1; 108 goto cleanup; 109 } 110 111cleanup: 112 strbuf_release(&path); 113 strbuf_release(&sentinel_path); 114 return ret; 115} 116 117static int tr2_dst_try_auto_path(struct tr2_dst *dst, const char *tgt_prefix) 118{ 119 int too_many_files; 120 const char *last_slash, *sid = tr2_sid_get(); 121 struct strbuf path = STRBUF_INIT; 122 size_t base_path_len; 123 unsigned attempt_count; 124 125 last_slash = strrchr(sid, '/'); 126 if (last_slash) 127 sid = last_slash + 1; 128 129 strbuf_addstr(&path, tgt_prefix); 130 if (!is_dir_sep(path.buf[path.len - 1])) 131 strbuf_addch(&path, '/'); 132 strbuf_addstr(&path, sid); 133 base_path_len = path.len; 134 135 too_many_files = tr2_dst_too_many_files(dst, tgt_prefix); 136 if (!too_many_files) { 137 for (attempt_count = 0; attempt_count < MAX_AUTO_ATTEMPTS; attempt_count++) { 138 if (attempt_count > 0) { 139 strbuf_setlen(&path, base_path_len); 140 strbuf_addf(&path, ".%d", attempt_count); 141 } 142 143 dst->fd = open(path.buf, O_WRONLY | O_CREAT | O_EXCL, 0666); 144 if (dst->fd != -1) 145 break; 146 } 147 } else if (too_many_files == 1) { 148 strbuf_release(&path); 149 if (tr2_dst_want_warning()) 150 warning("trace2: not opening %s trace file due to too " 151 "many files in target directory %s", 152 tr2_sysenv_display_name(dst->sysenv_var), 153 tgt_prefix); 154 return 0; 155 } 156 157 if (dst->fd == -1) { 158 if (tr2_dst_want_warning()) 159 warning("trace2: could not open '%.*s' for '%s' tracing: %s", 160 (int) base_path_len, path.buf, 161 tr2_sysenv_display_name(dst->sysenv_var), 162 strerror(errno)); 163 164 tr2_dst_trace_disable(dst); 165 strbuf_release(&path); 166 return 0; 167 } 168 169 strbuf_release(&path); 170 171 dst->need_close = 1; 172 dst->initialized = 1; 173 174 return dst->fd; 175} 176 177static int tr2_dst_try_path(struct tr2_dst *dst, const char *tgt_value) 178{ 179 int fd = open(tgt_value, O_WRONLY | O_APPEND | O_CREAT, 0666); 180 if (fd == -1) { 181 if (tr2_dst_want_warning()) 182 warning("trace2: could not open '%s' for '%s' tracing: %s", 183 tgt_value, 184 tr2_sysenv_display_name(dst->sysenv_var), 185 strerror(errno)); 186 187 tr2_dst_trace_disable(dst); 188 return 0; 189 } 190 191 dst->fd = fd; 192 dst->need_close = 1; 193 dst->initialized = 1; 194 195 return dst->fd; 196} 197 198#ifndef NO_UNIX_SOCKETS 199#define PREFIX_AF_UNIX "af_unix:" 200#define PREFIX_AF_UNIX_STREAM "af_unix:stream:" 201#define PREFIX_AF_UNIX_DGRAM "af_unix:dgram:" 202 203static int tr2_dst_try_uds_connect(const char *path, int sock_type, int *out_fd) 204{ 205 int fd; 206 struct sockaddr_un sa; 207 208 fd = socket(AF_UNIX, sock_type, 0); 209 if (fd == -1) 210 return -1; 211 212 sa.sun_family = AF_UNIX; 213 strlcpy(sa.sun_path, path, sizeof(sa.sun_path)); 214 215 if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { 216 int saved_errno = errno; 217 close(fd); 218 errno = saved_errno; 219 return -1; 220 } 221 222 *out_fd = fd; 223 return 0; 224} 225 226#define TR2_DST_UDS_TRY_STREAM (1 << 0) 227#define TR2_DST_UDS_TRY_DGRAM (1 << 1) 228 229static int tr2_dst_try_unix_domain_socket(struct tr2_dst *dst, 230 const char *tgt_value) 231{ 232 unsigned int uds_try = 0; 233 int fd; 234 const char *path = NULL; 235 236 /* 237 * Allow "af_unix:[<type>:]<absolute_path>" 238 * 239 * Trace2 always writes complete individual messages (without 240 * chunking), so we can talk to either DGRAM or STREAM type sockets. 241 * 242 * Allow the user to explicitly request the socket type. 243 * 244 * If they omit the socket type, try one and then the other. 245 */ 246 247 if (skip_prefix(tgt_value, PREFIX_AF_UNIX_STREAM, &path)) 248 uds_try |= TR2_DST_UDS_TRY_STREAM; 249 250 else if (skip_prefix(tgt_value, PREFIX_AF_UNIX_DGRAM, &path)) 251 uds_try |= TR2_DST_UDS_TRY_DGRAM; 252 253 else if (skip_prefix(tgt_value, PREFIX_AF_UNIX, &path)) 254 uds_try |= TR2_DST_UDS_TRY_STREAM | TR2_DST_UDS_TRY_DGRAM; 255 256 if (!path || !*path) { 257 if (tr2_dst_want_warning()) 258 warning("trace2: invalid AF_UNIX value '%s' for '%s' tracing", 259 tgt_value, 260 tr2_sysenv_display_name(dst->sysenv_var)); 261 262 tr2_dst_trace_disable(dst); 263 return 0; 264 } 265 266 if (!is_absolute_path(path) || 267 strlen(path) >= sizeof(((struct sockaddr_un *)0)->sun_path)) { 268 if (tr2_dst_want_warning()) 269 warning("trace2: invalid AF_UNIX path '%s' for '%s' tracing", 270 path, tr2_sysenv_display_name(dst->sysenv_var)); 271 272 tr2_dst_trace_disable(dst); 273 return 0; 274 } 275 276 if (uds_try & TR2_DST_UDS_TRY_STREAM) { 277 if (!tr2_dst_try_uds_connect(path, SOCK_STREAM, &fd)) 278 goto connected; 279 if (errno != EPROTOTYPE) 280 goto error; 281 } 282 if (uds_try & TR2_DST_UDS_TRY_DGRAM) { 283 if (!tr2_dst_try_uds_connect(path, SOCK_DGRAM, &fd)) 284 goto connected; 285 } 286 287error: 288 if (tr2_dst_want_warning()) 289 warning("trace2: could not connect to socket '%s' for '%s' tracing: %s", 290 path, tr2_sysenv_display_name(dst->sysenv_var), 291 strerror(errno)); 292 293 tr2_dst_trace_disable(dst); 294 return 0; 295 296connected: 297 dst->fd = fd; 298 dst->need_close = 1; 299 dst->initialized = 1; 300 301 return dst->fd; 302} 303#endif 304 305static void tr2_dst_malformed_warning(struct tr2_dst *dst, 306 const char *tgt_value) 307{ 308 warning("trace2: unknown value for '%s': '%s'", 309 tr2_sysenv_display_name(dst->sysenv_var), tgt_value); 310} 311 312int tr2_dst_get_trace_fd(struct tr2_dst *dst) 313{ 314 const char *tgt_value; 315 316 /* don't open twice */ 317 if (dst->initialized) 318 return dst->fd; 319 320 dst->initialized = 1; 321 322 tgt_value = tr2_sysenv_get(dst->sysenv_var); 323 324 if (!tgt_value || !strcmp(tgt_value, "") || !strcmp(tgt_value, "0") || 325 !strcasecmp(tgt_value, "false")) { 326 dst->fd = 0; 327 return dst->fd; 328 } 329 330 if (!strcmp(tgt_value, "1") || !strcasecmp(tgt_value, "true")) { 331 dst->fd = STDERR_FILENO; 332 return dst->fd; 333 } 334 335 if (strlen(tgt_value) == 1 && isdigit(*tgt_value)) { 336 dst->fd = atoi(tgt_value); 337 return dst->fd; 338 } 339 340 if (is_absolute_path(tgt_value)) { 341 if (is_directory(tgt_value)) 342 return tr2_dst_try_auto_path(dst, tgt_value); 343 else 344 return tr2_dst_try_path(dst, tgt_value); 345 } 346 347#ifndef NO_UNIX_SOCKETS 348 if (starts_with(tgt_value, PREFIX_AF_UNIX)) 349 return tr2_dst_try_unix_domain_socket(dst, tgt_value); 350#endif 351 352 /* Always warn about malformed values. */ 353 tr2_dst_malformed_warning(dst, tgt_value); 354 tr2_dst_trace_disable(dst); 355 return 0; 356} 357 358int tr2_dst_trace_want(struct tr2_dst *dst) 359{ 360 return !!tr2_dst_get_trace_fd(dst); 361} 362 363void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line) 364{ 365 int fd = tr2_dst_get_trace_fd(dst); 366 ssize_t bytes; 367 368 strbuf_complete_line(buf_line); /* ensure final NL on buffer */ 369 370 /* 371 * We do not use write_in_full() because we do not want 372 * a short-write to try again. We are using O_APPEND mode 373 * files and the kernel handles the atomic seek+write. If 374 * another thread or git process is concurrently writing to 375 * this fd or file, our remainder-write may not be contiguous 376 * with our initial write of this message. And that will 377 * confuse readers. So just don't bother. 378 * 379 * It is assumed that TRACE2 messages are short enough that 380 * the system can write them in 1 attempt and we won't see 381 * a short-write. 382 * 383 * If we get an IO error, just close the trace dst. 384 */ 385 sigchain_push(SIGPIPE, SIG_IGN); 386 bytes = write(fd, buf_line->buf, buf_line->len); 387 sigchain_pop(SIGPIPE); 388 if (bytes >= 0) 389 return; 390 391 tr2_dst_trace_disable(dst); 392 if (tr2_dst_want_warning()) 393 warning("unable to write trace to '%s': %s", 394 tr2_sysenv_display_name(dst->sysenv_var), 395 strerror(errno)); 396}