Merge branch 'ab/commit-graph-fixes'
[git] / trace2 / tr2_dst.c
1 #include "cache.h"
2 #include "trace2/tr2_dst.h"
3
4 /*
5  * If a Trace2 target cannot be opened for writing, we should issue a
6  * warning to stderr, but this is very annoying if the target is a pipe
7  * or socket and beyond the user's control -- especially since every
8  * git command (and sub-command) will print the message.  So we silently
9  * eat these warnings and just discard the trace data.
10  *
11  * Enable the following environment variable to see these warnings.
12  */
13 #define TR2_ENVVAR_DST_DEBUG "GIT_TR2_DST_DEBUG"
14
15 static int tr2_dst_want_warning(void)
16 {
17         static int tr2env_dst_debug = -1;
18
19         if (tr2env_dst_debug == -1) {
20                 const char *env_value = getenv(TR2_ENVVAR_DST_DEBUG);
21                 if (!env_value || !*env_value)
22                         tr2env_dst_debug = 0;
23                 else
24                         tr2env_dst_debug = atoi(env_value) > 0;
25         }
26
27         return tr2env_dst_debug;
28 }
29
30 void tr2_dst_trace_disable(struct tr2_dst *dst)
31 {
32         if (dst->need_close)
33                 close(dst->fd);
34         dst->fd = 0;
35         dst->initialized = 1;
36         dst->need_close = 0;
37 }
38
39 static int tr2_dst_try_path(struct tr2_dst *dst, const char *tgt_value)
40 {
41         int fd = open(tgt_value, O_WRONLY | O_APPEND | O_CREAT, 0666);
42         if (fd == -1) {
43                 if (tr2_dst_want_warning())
44                         warning("trace2: could not open '%s' for '%s' tracing: %s",
45                                 tgt_value, dst->env_var_name, strerror(errno));
46
47                 tr2_dst_trace_disable(dst);
48                 return 0;
49         }
50
51         dst->fd = fd;
52         dst->need_close = 1;
53         dst->initialized = 1;
54
55         return dst->fd;
56 }
57
58 #ifndef NO_UNIX_SOCKETS
59 #define PREFIX_AF_UNIX "af_unix:"
60 #define PREFIX_AF_UNIX_STREAM "af_unix:stream:"
61 #define PREFIX_AF_UNIX_DGRAM "af_unix:dgram:"
62
63 static int tr2_dst_try_uds_connect(const char *path, int sock_type, int *out_fd)
64 {
65         int fd;
66         struct sockaddr_un sa;
67
68         fd = socket(AF_UNIX, sock_type, 0);
69         if (fd == -1)
70                 return errno;
71
72         sa.sun_family = AF_UNIX;
73         strlcpy(sa.sun_path, path, sizeof(sa.sun_path));
74
75         if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
76                 int e = errno;
77                 close(fd);
78                 return e;
79         }
80
81         *out_fd = fd;
82         return 0;
83 }
84
85 #define TR2_DST_UDS_TRY_STREAM (1 << 0)
86 #define TR2_DST_UDS_TRY_DGRAM  (1 << 1)
87
88 static int tr2_dst_try_unix_domain_socket(struct tr2_dst *dst,
89                                           const char *tgt_value)
90 {
91         unsigned int uds_try = 0;
92         int fd;
93         int e;
94         const char *path = NULL;
95
96         /*
97          * Allow "af_unix:[<type>:]<absolute_path>"
98          *
99          * Trace2 always writes complete individual messages (without
100          * chunking), so we can talk to either DGRAM or STREAM type sockets.
101          *
102          * Allow the user to explicitly request the socket type.
103          *
104          * If they omit the socket type, try one and then the other.
105          */
106
107         if (skip_prefix(tgt_value, PREFIX_AF_UNIX_STREAM, &path))
108                 uds_try |= TR2_DST_UDS_TRY_STREAM;
109
110         else if (skip_prefix(tgt_value, PREFIX_AF_UNIX_DGRAM, &path))
111                 uds_try |= TR2_DST_UDS_TRY_DGRAM;
112
113         else if (skip_prefix(tgt_value, PREFIX_AF_UNIX, &path))
114                 uds_try |= TR2_DST_UDS_TRY_STREAM | TR2_DST_UDS_TRY_DGRAM;
115
116         if (!path || !*path) {
117                 if (tr2_dst_want_warning())
118                         warning("trace2: invalid AF_UNIX value '%s' for '%s' tracing",
119                                 tgt_value, dst->env_var_name);
120
121                 tr2_dst_trace_disable(dst);
122                 return 0;
123         }
124
125         if (!is_absolute_path(path) ||
126             strlen(path) >= sizeof(((struct sockaddr_un *)0)->sun_path)) {
127                 if (tr2_dst_want_warning())
128                         warning("trace2: invalid AF_UNIX path '%s' for '%s' tracing",
129                                 path, dst->env_var_name);
130
131                 tr2_dst_trace_disable(dst);
132                 return 0;
133         }
134
135         if (uds_try & TR2_DST_UDS_TRY_STREAM) {
136                 e = tr2_dst_try_uds_connect(path, SOCK_STREAM, &fd);
137                 if (!e)
138                         goto connected;
139                 if (e != EPROTOTYPE)
140                         goto error;
141         }
142         if (uds_try & TR2_DST_UDS_TRY_DGRAM) {
143                 e = tr2_dst_try_uds_connect(path, SOCK_DGRAM, &fd);
144                 if (!e)
145                         goto connected;
146         }
147
148 error:
149         if (tr2_dst_want_warning())
150                 warning("trace2: could not connect to socket '%s' for '%s' tracing: %s",
151                         path, dst->env_var_name, strerror(e));
152
153         tr2_dst_trace_disable(dst);
154         return 0;
155
156 connected:
157         dst->fd = fd;
158         dst->need_close = 1;
159         dst->initialized = 1;
160
161         return dst->fd;
162 }
163 #endif
164
165 static void tr2_dst_malformed_warning(struct tr2_dst *dst,
166                                       const char *tgt_value)
167 {
168         struct strbuf buf = STRBUF_INIT;
169
170         strbuf_addf(&buf, "trace2: unknown value for '%s': '%s'",
171                     dst->env_var_name, tgt_value);
172         warning("%s", buf.buf);
173
174         strbuf_release(&buf);
175 }
176
177 int tr2_dst_get_trace_fd(struct tr2_dst *dst)
178 {
179         const char *tgt_value;
180
181         /* don't open twice */
182         if (dst->initialized)
183                 return dst->fd;
184
185         dst->initialized = 1;
186
187         tgt_value = getenv(dst->env_var_name);
188
189         if (!tgt_value || !strcmp(tgt_value, "") || !strcmp(tgt_value, "0") ||
190             !strcasecmp(tgt_value, "false")) {
191                 dst->fd = 0;
192                 return dst->fd;
193         }
194
195         if (!strcmp(tgt_value, "1") || !strcasecmp(tgt_value, "true")) {
196                 dst->fd = STDERR_FILENO;
197                 return dst->fd;
198         }
199
200         if (strlen(tgt_value) == 1 && isdigit(*tgt_value)) {
201                 dst->fd = atoi(tgt_value);
202                 return dst->fd;
203         }
204
205         if (is_absolute_path(tgt_value))
206                 return tr2_dst_try_path(dst, tgt_value);
207
208 #ifndef NO_UNIX_SOCKETS
209         if (starts_with(tgt_value, PREFIX_AF_UNIX))
210                 return tr2_dst_try_unix_domain_socket(dst, tgt_value);
211 #endif
212
213         /* Always warn about malformed values. */
214         tr2_dst_malformed_warning(dst, tgt_value);
215         tr2_dst_trace_disable(dst);
216         return 0;
217 }
218
219 int tr2_dst_trace_want(struct tr2_dst *dst)
220 {
221         return !!tr2_dst_get_trace_fd(dst);
222 }
223
224 void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line)
225 {
226         int fd = tr2_dst_get_trace_fd(dst);
227
228         strbuf_complete_line(buf_line); /* ensure final NL on buffer */
229
230         /*
231          * We do not use write_in_full() because we do not want
232          * a short-write to try again.  We are using O_APPEND mode
233          * files and the kernel handles the atomic seek+write. If
234          * another thread or git process is concurrently writing to
235          * this fd or file, our remainder-write may not be contiguous
236          * with our initial write of this message.  And that will
237          * confuse readers.  So just don't bother.
238          *
239          * It is assumed that TRACE2 messages are short enough that
240          * the system can write them in 1 attempt and we won't see
241          * a short-write.
242          *
243          * If we get an IO error, just close the trace dst.
244          */
245         if (write(fd, buf_line->buf, buf_line->len) >= 0)
246                 return;
247
248         if (tr2_dst_want_warning())
249                 warning("unable to write trace to '%s': %s", dst->env_var_name,
250                         strerror(errno));
251         tr2_dst_trace_disable(dst);
252 }