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