Coverage Report

Created: 2024-09-08 06:23

/src/git/builtin/credential-cache.c
Line
Count
Source (jump to first uncovered line)
1
#include "builtin.h"
2
#include "credential.h"
3
#include "gettext.h"
4
#include "parse-options.h"
5
#include "path.h"
6
#include "strbuf.h"
7
#include "write-or-die.h"
8
9
#ifndef NO_UNIX_SOCKETS
10
11
#include "unix-socket.h"
12
#include "run-command.h"
13
14
0
#define FLAG_SPAWN 0x1
15
0
#define FLAG_RELAY 0x2
16
17
#ifdef GIT_WINDOWS_NATIVE
18
19
static int connection_closed(int error)
20
{
21
  return (error == EINVAL);
22
}
23
24
static int connection_fatally_broken(int error)
25
{
26
  return (error != ENOENT) && (error != ENETDOWN);
27
}
28
29
#else
30
31
static int connection_closed(int error)
32
0
{
33
0
  return (error == ECONNRESET);
34
0
}
35
36
static int connection_fatally_broken(int error)
37
0
{
38
0
  return (error != ENOENT) && (error != ECONNREFUSED);
39
0
}
40
41
#endif
42
43
static int send_request(const char *socket, const struct strbuf *out)
44
0
{
45
0
  int got_data = 0;
46
0
  int fd = unix_stream_connect(socket, 0);
47
48
0
  if (fd < 0)
49
0
    return -1;
50
51
0
  if (write_in_full(fd, out->buf, out->len) < 0)
52
0
    die_errno("unable to write to cache daemon");
53
0
  shutdown(fd, SHUT_WR);
54
55
0
  while (1) {
56
0
    char in[1024];
57
0
    int r;
58
59
0
    r = read_in_full(fd, in, sizeof(in));
60
0
    if (r == 0 || (r < 0 && connection_closed(errno)))
61
0
      break;
62
0
    if (r < 0)
63
0
      die_errno("read error from cache daemon");
64
0
    write_or_die(1, in, r);
65
0
    got_data = 1;
66
0
  }
67
0
  close(fd);
68
0
  return got_data;
69
0
}
70
71
static void spawn_daemon(const char *socket)
72
0
{
73
0
  struct child_process daemon = CHILD_PROCESS_INIT;
74
0
  char buf[128];
75
0
  int r;
76
77
0
  strvec_pushl(&daemon.args,
78
0
         "credential-cache--daemon", socket,
79
0
         NULL);
80
0
  daemon.git_cmd = 1;
81
0
  daemon.no_stdin = 1;
82
0
  daemon.out = -1;
83
84
0
  if (start_command(&daemon))
85
0
    die_errno("unable to start cache daemon");
86
0
  r = read_in_full(daemon.out, buf, sizeof(buf));
87
0
  if (r < 0)
88
0
    die_errno("unable to read result code from cache daemon");
89
0
  if (r != 3 || memcmp(buf, "ok\n", 3))
90
0
    die("cache daemon did not start: %.*s", r, buf);
91
92
0
  child_process_clear(&daemon);
93
0
  close(daemon.out);
94
0
}
95
96
static void do_cache(const char *socket, const char *action, int timeout,
97
         int flags)
98
0
{
99
0
  struct strbuf buf = STRBUF_INIT;
100
101
0
  strbuf_addf(&buf, "action=%s\n", action);
102
0
  strbuf_addf(&buf, "timeout=%d\n", timeout);
103
0
  if (flags & FLAG_RELAY) {
104
0
    if (strbuf_read(&buf, 0, 0) < 0)
105
0
      die_errno("unable to relay credential");
106
0
  }
107
108
0
  if (send_request(socket, &buf) < 0) {
109
0
    if (connection_fatally_broken(errno))
110
0
      die_errno("unable to connect to cache daemon");
111
0
    if (flags & FLAG_SPAWN) {
112
0
      spawn_daemon(socket);
113
0
      if (send_request(socket, &buf) < 0)
114
0
        die_errno("unable to connect to cache daemon");
115
0
    }
116
0
  }
117
0
  strbuf_release(&buf);
118
0
}
119
120
static char *get_socket_path(void)
121
0
{
122
0
  struct stat sb;
123
0
  char *old_dir, *socket;
124
0
  old_dir = interpolate_path("~/.git-credential-cache", 0);
125
0
  if (old_dir && !stat(old_dir, &sb) && S_ISDIR(sb.st_mode))
126
0
    socket = xstrfmt("%s/socket", old_dir);
127
0
  else
128
0
    socket = xdg_cache_home("credential/socket");
129
0
  free(old_dir);
130
0
  return socket;
131
0
}
132
133
static void announce_capabilities(void)
134
0
{
135
0
  struct credential c = CREDENTIAL_INIT;
136
0
  c.capa_authtype.request_initial = 1;
137
0
  credential_announce_capabilities(&c, stdout);
138
0
}
139
140
int cmd_credential_cache(int argc, const char **argv, const char *prefix)
141
0
{
142
0
  const char *socket_path_arg = NULL;
143
0
  char *socket_path;
144
0
  int timeout = 900;
145
0
  const char *op;
146
0
  const char * const usage[] = {
147
0
    "git credential-cache [<options>] <action>",
148
0
    NULL
149
0
  };
150
0
  struct option options[] = {
151
0
    OPT_INTEGER(0, "timeout", &timeout,
152
0
          "number of seconds to cache credentials"),
153
0
    OPT_STRING(0, "socket", &socket_path_arg, "path",
154
0
         "path of cache-daemon socket"),
155
0
    OPT_END()
156
0
  };
157
158
0
  argc = parse_options(argc, argv, prefix, options, usage, 0);
159
0
  if (!argc)
160
0
    usage_with_options(usage, options);
161
0
  op = argv[0];
162
163
0
  if (!have_unix_sockets())
164
0
    die(_("credential-cache unavailable; no unix socket support"));
165
166
0
  socket_path = xstrdup_or_null(socket_path_arg);
167
0
  if (!socket_path)
168
0
    socket_path = get_socket_path();
169
0
  if (!socket_path)
170
0
    die("unable to find a suitable socket path; use --socket");
171
172
0
  if (!strcmp(op, "exit"))
173
0
    do_cache(socket_path, op, timeout, 0);
174
0
  else if (!strcmp(op, "get") || !strcmp(op, "erase"))
175
0
    do_cache(socket_path, op, timeout, FLAG_RELAY);
176
0
  else if (!strcmp(op, "store"))
177
0
    do_cache(socket_path, op, timeout, FLAG_RELAY|FLAG_SPAWN);
178
0
  else if (!strcmp(op, "capability"))
179
0
    announce_capabilities();
180
0
  else
181
0
    ; /* ignore unknown operation */
182
183
0
  free(socket_path);
184
0
  return 0;
185
0
}
186
187
#else
188
189
int cmd_credential_cache(int argc, const char **argv, const char *prefix)
190
{
191
  const char * const usage[] = {
192
    "git credential-cache [options] <action>",
193
    "",
194
    "credential-cache is disabled in this build of Git",
195
    NULL
196
  };
197
  struct option options[] = { OPT_END() };
198
199
  argc = parse_options(argc, argv, prefix, options, usage, 0);
200
  die(_("credential-cache unavailable; no unix socket support"));
201
}
202
203
#endif /* NO_UNIX_SOCKETS */