Raw
1 /*
2 * Test process implementing the diff process protocol (diff.<driver>.process).
3 *
4 * Speaks the long-running process protocol over stdin/stdout and
5 * answers command=hunks-by-oid requests from the blob object names
6 * alone; no content is exchanged. The --mode= switch selects the
7 * response shape:
8 *
9 * oid-fixed packet: git< hunk 5 2 5 2
10 * oid-equal packet: git< status=success (zero hunks: equivalent)
11 * oid-need-content packet: git< status=need-content
12 * oid-empty packet: git< hunk 0 0 1 2 (empty old side)
13 *
14 * and the adversarial shapes the protocol error paths are tested
15 * with:
16 *
17 * oid-trailing a hunk line with a trailing token to ignore
18 * oid-malformed a hunk line that does not parse
19 * oid-huge coordinates far past the end of any test blob
20 * oid-erange a count too large for any long
21 * oid-overlap two hunks out of order
22 * oid-misaligned two hunks whose unchanged runs differ in length
23 * oid-badstart a start of 0 paired with a nonzero count
24 * oid-unknown-status status=frobnicate
25 * oid-abort status=abort
26 * oid-bare-status a status packet without the hunk-section flush
27 * oid-empty-packet an empty packet (0004) inside the hunk section
28 * oid-crash one hunk line, then exit with no flush or status
29 * oid-garbage raw non-pkt-line bytes, then exit
30 * cap-none handshake announcing no capability at all
31 *
32 * Success responses end with:
33 *
34 * packet: git< 0000
35 * packet: git< status=success
36 * packet: git< 0000
37 *
38 * Each request is logged to --log as:
39 *
40 * command=<cmd> pathname=<path> old-oid=<hex> new-oid=<hex>
41 */
42
43 #include "test-tool.h"
44 #include "pkt-line.h"
45 #include "parse-options.h"
46 #include "strbuf.h"
47
48 static FILE *logfile;
49
50 enum mode {
51 MODE_OID_FIXED,
52 MODE_OID_EQUAL,
53 MODE_OID_NEED_CONTENT,
54 MODE_OID_EMPTY,
55 MODE_OID_TRAILING,
56 MODE_OID_MALFORMED,
57 MODE_OID_HUGE,
58 MODE_OID_ERANGE,
59 MODE_OID_OVERLAP,
60 MODE_OID_MISALIGNED,
61 MODE_OID_BADSTART,
62 MODE_OID_UNKNOWN_STATUS,
63 MODE_OID_ABORT,
64 MODE_OID_BARE_STATUS,
65 MODE_OID_EMPTY_PACKET,
66 MODE_OID_CRASH,
67 MODE_OID_GARBAGE,
68 MODE_CAP_NONE,
69 };
70
71 static enum mode parse_mode(const char *s)
72 {
73 if (!strcmp(s, "oid-fixed"))
74 return MODE_OID_FIXED;
75 if (!strcmp(s, "oid-equal"))
76 return MODE_OID_EQUAL;
77 if (!strcmp(s, "oid-need-content"))
78 return MODE_OID_NEED_CONTENT;
79 if (!strcmp(s, "oid-empty"))
80 return MODE_OID_EMPTY;
81 if (!strcmp(s, "oid-trailing"))
82 return MODE_OID_TRAILING;
83 if (!strcmp(s, "oid-malformed"))
84 return MODE_OID_MALFORMED;
85 if (!strcmp(s, "oid-huge"))
86 return MODE_OID_HUGE;
87 if (!strcmp(s, "oid-erange"))
88 return MODE_OID_ERANGE;
89 if (!strcmp(s, "oid-overlap"))
90 return MODE_OID_OVERLAP;
91 if (!strcmp(s, "oid-misaligned"))
92 return MODE_OID_MISALIGNED;
93 if (!strcmp(s, "oid-badstart"))
94 return MODE_OID_BADSTART;
95 if (!strcmp(s, "oid-unknown-status"))
96 return MODE_OID_UNKNOWN_STATUS;
97 if (!strcmp(s, "oid-abort"))
98 return MODE_OID_ABORT;
99 if (!strcmp(s, "oid-bare-status"))
100 return MODE_OID_BARE_STATUS;
101 if (!strcmp(s, "oid-empty-packet"))
102 return MODE_OID_EMPTY_PACKET;
103 if (!strcmp(s, "oid-crash"))
104 return MODE_OID_CRASH;
105 if (!strcmp(s, "oid-garbage"))
106 return MODE_OID_GARBAGE;
107 if (!strcmp(s, "cap-none"))
108 return MODE_CAP_NONE;
109 die("unknown --mode=%s", s);
110 }
111
112 /*
113 * Read "key=value" packets up to a flush, capturing "command" and
114 * "pathname". Returns 1 if a request was read, 0 on EOF.
115 *
116 * The first packet uses the gentle variant so that a clean shutdown
117 * by Git (EOF) does not produce a spurious "the remote end hung up
118 * unexpectedly" on stderr. Subsequent packets use the non-gentle
119 * variant: once inside a request, truncation is a protocol violation
120 * and dying loudly is the correct response.
121 */
122 static int read_request_header(char **command, char **pathname,
123 char **old_oid, char **new_oid)
124 {
125 int first = 1;
126 char *line;
127
128 *command = *pathname = *old_oid = *new_oid = NULL;
129 for (;;) {
130 const char *value;
131
132 if (first) {
133 if (packet_read_line_gently(0, NULL, &line) < 0)
134 return 0;
135 first = 0;
136 } else {
137 line = packet_read_line(0, NULL);
138 }
139 if (!line)
140 break;
141 if (skip_prefix(line, "command=", &value))
142 *command = xstrdup(value);
143 else if (skip_prefix(line, "pathname=", &value))
144 *pathname = xstrdup(value);
145 else if (skip_prefix(line, "old-oid=", &value))
146 *old_oid = xstrdup(value);
147 else if (skip_prefix(line, "new-oid=", &value))
148 *new_oid = xstrdup(value);
149 }
150 return 1;
151 }
152
153 static void send_status(const char *status)
154 {
155 packet_flush(1);
156 packet_write_fmt(1, "%s\n", status);
157 packet_flush(1);
158 }
159
160 static void command_loop(enum mode mode)
161 {
162 for (;;) {
163 char *command = NULL, *pathname = NULL;
164 char *old_oid = NULL, *new_oid = NULL;
165
166 if (!read_request_header(&command, &pathname,
167 &old_oid, &new_oid))
168 break; /* EOF: Git closed its end */
169
170 if (!command || strcmp(command, "hunks-by-oid"))
171 die("unexpected command: '%s'",
172 command ? command : "(none)");
173
174 if (logfile) {
175 fprintf(logfile,
176 "command=%s pathname=%s old-oid=%s new-oid=%s\n",
177 command,
178 pathname ? pathname : "(none)",
179 old_oid ? old_oid : "(none)",
180 new_oid ? new_oid : "(none)");
181 fflush(logfile);
182 }
183
184 switch (mode) {
185 case MODE_OID_FIXED:
186 packet_write_fmt(1, "hunk 5 2 5 2\n");
187 send_status("status=success");
188 break;
189 case MODE_OID_EQUAL:
190 send_status("status=success");
191 break;
192 case MODE_OID_EMPTY:
193 /*
194 * An empty old side: the "git diff" convention
195 * addresses it with a start of 0 and a count of 0.
196 * Claims two lines added, fewer than the builtin
197 * would show, so the answer is observable.
198 */
199 packet_write_fmt(1, "hunk 0 0 1 2\n");
200 send_status("status=success");
201 break;
202 case MODE_OID_TRAILING:
203 /*
204 * Git must ignore trailing space-separated tokens
205 * on a hunk line (the appendability rule), so this
206 * must behave exactly like oid-fixed.
207 */
208 packet_write_fmt(1, "hunk 5 2 5 2 moved=yes\n");
209 send_status("status=success");
210 break;
211 case MODE_OID_MALFORMED:
212 packet_write_fmt(1, "hunk five two 5 2\n");
213 send_status("status=success");
214 break;
215 case MODE_OID_HUGE:
216 /*
217 * In-range for int32 (and for a 32-bit long), so
218 * only the blob-size bound can reject it.
219 */
220 packet_write_fmt(1, "hunk 1 1000000000 1 1000000000\n");
221 send_status("status=success");
222 break;
223 case MODE_OID_ERANGE:
224 /* Overflows strtol() even where long is 64-bit. */
225 packet_write_fmt(1, "hunk 1 99999999999999999999 1 1\n");
226 send_status("status=success");
227 break;
228 case MODE_OID_OVERLAP:
229 packet_write_fmt(1, "hunk 3 2 3 2\n");
230 packet_write_fmt(1, "hunk 2 2 2 2\n");
231 send_status("status=success");
232 break;
233 case MODE_OID_MISALIGNED:
234 packet_write_fmt(1, "hunk 2 1 2 1\n");
235 packet_write_fmt(1, "hunk 5 1 6 1\n");
236 send_status("status=success");
237 break;
238 case MODE_OID_BADSTART:
239 /*
240 * A start of 0 names an empty side, so a nonzero
241 * count beside it names no line; the coordinate is
242 * rejected per pair while the process stays alive.
243 */
244 packet_write_fmt(1, "hunk 0 2 1 2\n");
245 send_status("status=success");
246 break;
247 case MODE_OID_UNKNOWN_STATUS:
248 send_status("status=frobnicate");
249 break;
250 case MODE_OID_ABORT:
251 send_status("status=abort");
252 break;
253 case MODE_OID_BARE_STATUS:
254 /* No hunk-section flush: a protocol violation. */
255 packet_write_fmt(1, "status=success\n");
256 packet_flush(1);
257 break;
258 case MODE_OID_EMPTY_PACKET:
259 /*
260 * An empty packet is not a flush; inside the hunk
261 * section it is a protocol violation.
262 */
263 if (write(1, "0004", 4) < 0)
264 die_errno("write empty packet");
265 send_status("status=success");
266 break;
267 case MODE_OID_CRASH:
268 packet_write_fmt(1, "hunk 5 2 5 2\n");
269 exit(0);
270 case MODE_OID_GARBAGE:
271 if (write(1, "@@@@ not a pkt-line @@@@", 24) < 0)
272 die_errno("write garbage");
273 exit(0);
274 default:
275 send_status("status=need-content");
276 break;
277 }
278
279 free(command);
280 free(pathname);
281 free(old_oid);
282 free(new_oid);
283 }
284 }
285
286 static void handshake(enum mode mode)
287 {
288 char *line;
289
290 line = packet_read_line(0, NULL);
291 if (!line || strcmp(line, "git-diff-client"))
292 die("bad welcome: '%s'", line ? line : "(eof)");
293 line = packet_read_line(0, NULL);
294 if (!line || strcmp(line, "version=1"))
295 die("bad version: '%s'", line ? line : "(eof)");
296 if (packet_read_line(0, NULL))
297 die("expected flush after version");
298
299 packet_write_fmt(1, "git-diff-server\n");
300 packet_write_fmt(1, "version=1\n");
301 packet_flush(1);
302
303 /* Drain capabilities advertised by Git */
304 while ((line = packet_read_line(0, NULL)))
305 ; /* drain */
306
307 if (mode != MODE_CAP_NONE)
308 packet_write_fmt(1, "capability=hunks-by-oid\n");
309 packet_flush(1);
310 }
311
312 static const char *const usage_str[] = {
313 "test-tool diff-process-backend --mode=<mode> [--log=<path>]",
314 NULL
315 };
316
317 int cmd__diff_process_backend(int argc, const char **argv)
318 {
319 const char *mode_str = NULL, *log_path = NULL;
320 enum mode mode = MODE_OID_FIXED;
321 struct option options[] = {
322 OPT_STRING(0, "mode", &mode_str, "mode",
323 "response shape (default oid-fixed);"
324 " see the file header for the full list of modes"),
325 OPT_STRING(0, "log", &log_path, "path",
326 "append per-request summary to this file"),
327 OPT_END()
328 };
329
330 argc = parse_options(argc, argv, NULL, options, usage_str, 0);
331 if (argc)
332 usage_with_options(usage_str, options);
333
334 if (mode_str)
335 mode = parse_mode(mode_str);
336
337 if (log_path) {
338 logfile = fopen(log_path, "a");
339 if (!logfile)
340 die_errno("failed to open log '%s'", log_path);
341 }
342
343 handshake(mode);
344 command_loop(mode);
345
346 if (logfile && fclose(logfile))
347 die_errno("error closing log");
348 return 0;
349 }