]> git.ipfire.org Git - thirdparty/git.git/blob - trace2/tr2_dst.c
Start the 2.46 cycle
[thirdparty/git.git] / trace2 / tr2_dst.c
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 */
25 static int tr2env_max_files = 0;
26
27 static 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
42 void 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 */
66 static 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
111 cleanup:
112 strbuf_release(&path);
113 strbuf_release(&sentinel_path);
114 return ret;
115 }
116
117 static 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
177 static 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
203 static 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
229 static 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
287 error:
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
296 connected:
297 dst->fd = fd;
298 dst->need_close = 1;
299 dst->initialized = 1;
300
301 return dst->fd;
302 }
303 #endif
304
305 static 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
312 int 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
358 int tr2_dst_trace_want(struct tr2_dst *dst)
359 {
360 return !!tr2_dst_get_trace_fd(dst);
361 }
362
363 void 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 }