Coverage Report

Created: 2026-10-17 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/sub-process.c
Line
Count
Source
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
0
{
14
0
  const struct subprocess_entry *e1, *e2;
15
16
0
  e1 = container_of(eptr, const struct subprocess_entry, ent);
17
0
  e2 = container_of(entry_or_key, const struct subprocess_entry, ent);
18
19
0
  return strcmp(e1->cmd, e2->cmd);
20
0
}
21
22
struct subprocess_entry *subprocess_find_entry(struct hashmap *hashmap, const char *cmd)
23
0
{
24
0
  struct subprocess_entry key;
25
26
0
  hashmap_entry_init(&key.ent, strhash(cmd));
27
0
  key.cmd = cmd;
28
0
  return hashmap_get_entry(hashmap, &key, ent, NULL);
29
0
}
30
31
int subprocess_read_status(int fd, struct strbuf *status)
32
0
{
33
0
  int len;
34
35
0
  for (;;) {
36
0
    char *line;
37
0
    const char *value;
38
39
0
    len = packet_read_line_gently(fd, NULL, &line);
40
0
    if ((len < 0) || !line)
41
0
      break;
42
0
    if (skip_prefix(line, "status=", &value)) {
43
      /* the last "status=<foo>" line wins */
44
0
      strbuf_reset(status);
45
0
      strbuf_addstr(status, value);
46
0
    }
47
0
  }
48
49
0
  return (len < 0) ? len : 0;
50
0
}
51
52
void subprocess_stop(struct hashmap *hashmap, struct subprocess_entry *entry)
53
0
{
54
0
  if (!entry)
55
0
    return;
56
57
0
  entry->process.clean_on_exit = 0;
58
0
  kill(entry->process.pid, SIGTERM);
59
0
  finish_command(&entry->process);
60
61
0
  hashmap_remove(hashmap, &entry->ent, NULL);
62
0
}
63
64
static void subprocess_exit_handler(struct child_process *process)
65
0
{
66
0
  sigchain_push(SIGPIPE, SIG_IGN);
67
  /* Closing the pipe signals the subprocess to initiate a shutdown. */
68
0
  close(process->in);
69
0
  close(process->out);
70
0
  sigchain_pop(SIGPIPE);
71
  /* Finish command will wait until the shutdown is complete. */
72
0
  finish_command(process);
73
0
}
74
75
int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, const char *cmd,
76
  subprocess_start_fn startfn)
77
0
{
78
0
  int err;
79
0
  struct child_process *process;
80
81
0
  entry->cmd = cmd;
82
0
  process = &entry->process;
83
84
0
  child_process_init(process);
85
0
  strvec_push(&process->args, cmd);
86
0
  process->use_shell = 1;
87
0
  process->in = -1;
88
0
  process->out = -1;
89
0
  process->clean_on_exit = 1;
90
0
  process->clean_on_exit_handler = subprocess_exit_handler;
91
0
  process->trace2_child_class = "subprocess";
92
93
0
  err = start_command(process);
94
0
  if (err) {
95
0
    error("cannot fork to run subprocess '%s'", cmd);
96
0
    return err;
97
0
  }
98
99
0
  hashmap_entry_init(&entry->ent, strhash(cmd));
100
101
0
  err = startfn(entry);
102
0
  if (err) {
103
0
    error("initialization for subprocess '%s' failed", cmd);
104
0
    subprocess_stop(hashmap, entry);
105
0
    return err;
106
0
  }
107
108
0
  hashmap_add(hashmap, &entry->ent);
109
0
  return 0;
110
0
}
111
112
static int handshake_version(struct child_process *process,
113
           const char *welcome_prefix, int *versions,
114
           int *chosen_version)
115
0
{
116
0
  int version_scratch;
117
0
  int i;
118
0
  char *line;
119
0
  const char *p;
120
121
0
  if (!chosen_version)
122
0
    chosen_version = &version_scratch;
123
124
0
  if (packet_write_fmt_gently(process->in, "%s-client\n",
125
0
            welcome_prefix))
126
0
    return error("Could not write client identification");
127
0
  for (i = 0; versions[i]; i++) {
128
0
    if (packet_write_fmt_gently(process->in, "version=%d\n",
129
0
              versions[i]))
130
0
      return error("Could not write requested version");
131
0
  }
132
0
  if (packet_flush_gently(process->in))
133
0
    return error("Could not write flush packet");
134
135
0
  if (packet_read_line_gently(process->out, NULL, &line) < 0)
136
0
    return error("could not read greeting from subprocess '%s'",
137
0
           process->args.v[0]);
138
0
  if (!line || !skip_prefix(line, welcome_prefix, &p) ||
139
0
      strcmp(p, "-server"))
140
0
    return error("Unexpected line '%s', expected %s-server",
141
0
           line ? line : "<flush packet>", welcome_prefix);
142
0
  if (packet_read_line_gently(process->out, NULL, &line) < 0)
143
0
    return error("could not read version from subprocess '%s'",
144
0
           process->args.v[0]);
145
0
  if (!line || !skip_prefix(line, "version=", &p) ||
146
0
      strtol_i(p, 10, chosen_version))
147
0
    return error("Unexpected line '%s', expected version",
148
0
           line ? line : "<flush packet>");
149
0
  if (packet_read_line_gently(process->out, NULL, &line) < 0)
150
0
    return error("could not read version flush from subprocess '%s'",
151
0
           process->args.v[0]);
152
0
  if (line)
153
0
    return error("Unexpected line '%s', expected flush", line);
154
155
  /* Check to make sure that the version received is supported */
156
0
  for (i = 0; versions[i]; i++) {
157
0
    if (versions[i] == *chosen_version)
158
0
      break;
159
0
  }
160
0
  if (!versions[i])
161
0
    return error("Version %d not supported", *chosen_version);
162
163
0
  return 0;
164
0
}
165
166
static int handshake_capabilities(struct child_process *process,
167
          struct subprocess_capability *capabilities,
168
          unsigned int *supported_capabilities)
169
0
{
170
0
  int i;
171
0
  char *line;
172
173
0
  for (i = 0; capabilities[i].name; i++) {
174
0
    if (packet_write_fmt_gently(process->in, "capability=%s\n",
175
0
              capabilities[i].name))
176
0
      return error("Could not write requested capability");
177
0
  }
178
0
  if (packet_flush_gently(process->in))
179
0
    return error("Could not write flush packet");
180
181
0
  for (;;) {
182
0
    const char *p;
183
0
    int len = packet_read_line_gently(process->out, NULL, &line);
184
185
0
    if (len < 0)
186
0
      return error("could not read capabilities from subprocess '%s'",
187
0
             process->args.v[0]);
188
0
    if (!line)
189
0
      break;
190
0
    if (!skip_prefix(line, "capability=", &p))
191
0
      continue;
192
193
0
    for (i = 0;
194
0
         capabilities[i].name && strcmp(p, capabilities[i].name);
195
0
         i++)
196
0
      ;
197
0
    if (capabilities[i].name) {
198
0
      if (supported_capabilities)
199
0
        *supported_capabilities |= capabilities[i].flag;
200
0
    } else {
201
0
      die("subprocess '%s' requested unsupported capability '%s'",
202
0
          process->args.v[0], p);
203
0
    }
204
0
  }
205
206
0
  return 0;
207
0
}
208
209
int subprocess_handshake(struct subprocess_entry *entry,
210
       const char *welcome_prefix,
211
       int *versions,
212
       int *chosen_version,
213
       struct subprocess_capability *capabilities,
214
       unsigned int *supported_capabilities)
215
0
{
216
0
  int retval;
217
0
  struct child_process *process = &entry->process;
218
219
0
  sigchain_push(SIGPIPE, SIG_IGN);
220
221
0
  retval = handshake_version(process, welcome_prefix, versions,
222
0
           chosen_version) ||
223
0
     handshake_capabilities(process, capabilities,
224
0
          supported_capabilities);
225
226
  sigchain_pop(SIGPIPE);
227
0
  return retval;
228
0
}