Raw
1 /*
2 * Generic implementation of background process infrastructure.
3 */
4 #include "git-compat-util.h"
5 #include "sub-process.h"
6 #include "sigchain.h"
7 #include "pkt-line.h"
8
9 int cmd2process_cmp(const void *cmp_data UNUSED,
10 const struct hashmap_entry *eptr,
11 const struct hashmap_entry *entry_or_key,
12 const void *keydata UNUSED)
13 {
14 const struct subprocess_entry *e1, *e2;
15
16 e1 = container_of(eptr, const struct subprocess_entry, ent);
17 e2 = container_of(entry_or_key, const struct subprocess_entry, ent);
18
19 return strcmp(e1->cmd, e2->cmd);
20 }
21
22 struct subprocess_entry *subprocess_find_entry(struct hashmap *hashmap, const char *cmd)
23 {
24 struct subprocess_entry key;
25
26 hashmap_entry_init(&key.ent, strhash(cmd));
27 key.cmd = cmd;
28 return hashmap_get_entry(hashmap, &key, ent, NULL);
29 }
30
31 int subprocess_read_status(int fd, struct strbuf *status)
32 {
33 int len;
34
35 for (;;) {
36 char *line;
37 const char *value;
38
39 len = packet_read_line_gently(fd, NULL, &line);
40 if ((len < 0) || !line)
41 break;
42 if (skip_prefix(line, "status=", &value)) {
43 /* the last "status=<foo>" line wins */
44 strbuf_reset(status);
45 strbuf_addstr(status, value);
46 }
47 }
48
49 return (len < 0) ? len : 0;
50 }
51
52 int subprocess_read_status_gently(int fd, struct strbuf *status)
53 {
54 for (;;) {
55 int pktlen = -1;
56 enum packet_read_status rs;
57 const char *value;
58
59 rs = packet_read_with_status(fd, NULL, NULL, packet_buffer,
60 sizeof(packet_buffer), &pktlen,
61 PACKET_READ_CHOMP_NEWLINE |
62 PACKET_READ_GENTLE_ON_EOF |
63 PACKET_READ_GENTLE_ON_READ_ERROR);
64 if (rs == PACKET_READ_FLUSH)
65 return 0;
66 if (rs != PACKET_READ_NORMAL || !pktlen)
67 return -1;
68 if (skip_prefix(packet_buffer, "status=", &value)) {
69 /* the last "status=<foo>" line wins */
70 strbuf_reset(status);
71 strbuf_addstr(status, value);
72 }
73 }
74 }
75
76 void subprocess_stop_command(struct subprocess_entry *entry)
77 {
78 if (!entry)
79 return;
80
81 entry->process.clean_on_exit = 0;
82 kill(entry->process.pid, SIGTERM);
83 finish_command(&entry->process);
84 }
85
86 void subprocess_stop(struct hashmap *hashmap, struct subprocess_entry *entry)
87 {
88 if (!entry)
89 return;
90
91 subprocess_stop_command(entry);
92 hashmap_remove(hashmap, &entry->ent, NULL);
93 }
94
95 static void subprocess_exit_handler(struct child_process *process)
96 {
97 sigchain_push(SIGPIPE, SIG_IGN);
98 /* Closing the pipe signals the subprocess to initiate a shutdown. */
99 close(process->in);
100 close(process->out);
101 sigchain_pop(SIGPIPE);
102 /* Finish command will wait until the shutdown is complete. */
103 finish_command(process);
104 }
105
106 int subprocess_start_command(struct subprocess_entry *entry, const char *cmd,
107 subprocess_start_fn startfn)
108 {
109 int err;
110 struct child_process *process;
111
112 entry->cmd = cmd;
113 process = &entry->process;
114
115 child_process_init(process);
116 strvec_push(&process->args, cmd);
117 process->use_shell = 1;
118 process->in = -1;
119 process->out = -1;
120 process->clean_on_exit = 1;
121 process->clean_on_exit_handler = subprocess_exit_handler;
122 process->trace2_child_class = "subprocess";
123
124 err = start_command(process);
125 if (err) {
126 error("cannot fork to run subprocess '%s'", cmd);
127 return err;
128 }
129
130 err = startfn(entry);
131 if (err) {
132 error("initialization for subprocess '%s' failed", cmd);
133 subprocess_stop_command(entry);
134 return err;
135 }
136
137 return 0;
138 }
139
140 int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, const char *cmd,
141 subprocess_start_fn startfn)
142 {
143 int err;
144
145 err = subprocess_start_command(entry, cmd, startfn);
146 if (err)
147 return err;
148
149 hashmap_entry_init(&entry->ent, strhash(cmd));
150 hashmap_add(hashmap, &entry->ent);
151 return 0;
152 }
153
154 static int handshake_version(struct child_process *process,
155 const char *welcome_prefix, int *versions,
156 int *chosen_version)
157 {
158 int version_scratch;
159 int i;
160 char *line;
161 const char *p;
162
163 if (!chosen_version)
164 chosen_version = &version_scratch;
165
166 if (packet_write_fmt_gently(process->in, "%s-client\n",
167 welcome_prefix))
168 return error("Could not write client identification");
169 for (i = 0; versions[i]; i++) {
170 if (packet_write_fmt_gently(process->in, "version=%d\n",
171 versions[i]))
172 return error("Could not write requested version");
173 }
174 if (packet_flush_gently(process->in))
175 return error("Could not write flush packet");
176
177 if (packet_read_line_gently(process->out, NULL, &line) < 0)
178 return error("could not read greeting from subprocess '%s'",
179 process->args.v[0]);
180 if (!line || !skip_prefix(line, welcome_prefix, &p) ||
181 strcmp(p, "-server"))
182 return error("Unexpected line '%s', expected %s-server",
183 line ? line : "<flush packet>", welcome_prefix);
184 if (packet_read_line_gently(process->out, NULL, &line) < 0)
185 return error("could not read version from subprocess '%s'",
186 process->args.v[0]);
187 if (!line || !skip_prefix(line, "version=", &p) ||
188 strtol_i(p, 10, chosen_version))
189 return error("Unexpected line '%s', expected version",
190 line ? line : "<flush packet>");
191 if (packet_read_line_gently(process->out, NULL, &line) < 0)
192 return error("could not read version flush from subprocess '%s'",
193 process->args.v[0]);
194 if (line)
195 return error("Unexpected line '%s', expected flush", line);
196
197 /* Check to make sure that the version received is supported */
198 for (i = 0; versions[i]; i++) {
199 if (versions[i] == *chosen_version)
200 break;
201 }
202 if (!versions[i])
203 return error("Version %d not supported", *chosen_version);
204
205 return 0;
206 }
207
208 static int handshake_capabilities(struct child_process *process,
209 struct subprocess_capability *capabilities,
210 unsigned int *supported_capabilities)
211 {
212 int i;
213 char *line;
214
215 for (i = 0; capabilities[i].name; i++) {
216 if (packet_write_fmt_gently(process->in, "capability=%s\n",
217 capabilities[i].name))
218 return error("Could not write requested capability");
219 }
220 if (packet_flush_gently(process->in))
221 return error("Could not write flush packet");
222
223 for (;;) {
224 const char *p;
225 int len = packet_read_line_gently(process->out, NULL, &line);
226
227 if (len < 0)
228 return error("could not read capabilities from subprocess '%s'",
229 process->args.v[0]);
230 if (!line)
231 break;
232 if (!skip_prefix(line, "capability=", &p))
233 continue;
234
235 for (i = 0;
236 capabilities[i].name && strcmp(p, capabilities[i].name);
237 i++)
238 ;
239 if (capabilities[i].name) {
240 if (supported_capabilities)
241 *supported_capabilities |= capabilities[i].flag;
242 } else {
243 die("subprocess '%s' requested unsupported capability '%s'",
244 process->args.v[0], p);
245 }
246 }
247
248 return 0;
249 }
250
251 int subprocess_handshake(struct subprocess_entry *entry,
252 const char *welcome_prefix,
253 int *versions,
254 int *chosen_version,
255 struct subprocess_capability *capabilities,
256 unsigned int *supported_capabilities)
257 {
258 int retval;
259 struct child_process *process = &entry->process;
260
261 sigchain_push(SIGPIPE, SIG_IGN);
262
263 retval = handshake_version(process, welcome_prefix, versions,
264 chosen_version) ||
265 handshake_capabilities(process, capabilities,
266 supported_capabilities);
267
268 sigchain_pop(SIGPIPE);
269 return retval;
270 }