Coverage Report

Created: 2026-01-09 07:10

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 (!(line = packet_read_line(process->out, NULL)) ||
136
0
      !skip_prefix(line, welcome_prefix, &p) ||
137
0
      strcmp(p, "-server"))
138
0
    return error("Unexpected line '%s', expected %s-server",
139
0
           line ? line : "<flush packet>", welcome_prefix);
140
0
  if (!(line = packet_read_line(process->out, NULL)) ||
141
0
      !skip_prefix(line, "version=", &p) ||
142
0
      strtol_i(p, 10, chosen_version))
143
0
    return error("Unexpected line '%s', expected version",
144
0
           line ? line : "<flush packet>");
145
0
  if ((line = packet_read_line(process->out, NULL)))
146
0
    return error("Unexpected line '%s', expected flush", line);
147
148
  /* Check to make sure that the version received is supported */
149
0
  for (i = 0; versions[i]; i++) {
150
0
    if (versions[i] == *chosen_version)
151
0
      break;
152
0
  }
153
0
  if (!versions[i])
154
0
    return error("Version %d not supported", *chosen_version);
155
156
0
  return 0;
157
0
}
158
159
static int handshake_capabilities(struct child_process *process,
160
          struct subprocess_capability *capabilities,
161
          unsigned int *supported_capabilities)
162
0
{
163
0
  int i;
164
0
  char *line;
165
166
0
  for (i = 0; capabilities[i].name; i++) {
167
0
    if (packet_write_fmt_gently(process->in, "capability=%s\n",
168
0
              capabilities[i].name))
169
0
      return error("Could not write requested capability");
170
0
  }
171
0
  if (packet_flush_gently(process->in))
172
0
    return error("Could not write flush packet");
173
174
0
  while ((line = packet_read_line(process->out, NULL))) {
175
0
    const char *p;
176
0
    if (!skip_prefix(line, "capability=", &p))
177
0
      continue;
178
179
0
    for (i = 0;
180
0
         capabilities[i].name && strcmp(p, capabilities[i].name);
181
0
         i++)
182
0
      ;
183
0
    if (capabilities[i].name) {
184
0
      if (supported_capabilities)
185
0
        *supported_capabilities |= capabilities[i].flag;
186
0
    } else {
187
0
      die("subprocess '%s' requested unsupported capability '%s'",
188
0
          process->args.v[0], p);
189
0
    }
190
0
  }
191
192
0
  return 0;
193
0
}
194
195
int subprocess_handshake(struct subprocess_entry *entry,
196
       const char *welcome_prefix,
197
       int *versions,
198
       int *chosen_version,
199
       struct subprocess_capability *capabilities,
200
       unsigned int *supported_capabilities)
201
0
{
202
0
  int retval;
203
0
  struct child_process *process = &entry->process;
204
205
0
  sigchain_push(SIGPIPE, SIG_IGN);
206
207
0
  retval = handshake_version(process, welcome_prefix, versions,
208
0
           chosen_version) ||
209
0
     handshake_capabilities(process, capabilities,
210
0
          supported_capabilities);
211
212
  sigchain_pop(SIGPIPE);
213
0
  return retval;
214
0
}