git/builtin/remote-ext.c
Jeff King 850d2fec53 convert manual allocations to argv_array
There are many manual argv allocations that predate the
argv_array API. Switching to that API brings a few
advantages:

  1. We no longer have to manually compute the correct final
     array size (so it's one less thing we can screw up).

  2. In many cases we had to make a separate pass to count,
     then allocate, then fill in the array. Now we can do it
     in one pass, making the code shorter and easier to
     follow.

  3. argv_array handles memory ownership for us, making it
     more obvious when things should be free()d and and when
     not.

Most of these cases are pretty straightforward. In some, we
switch from "run_command_v" to "run_command" which lets us
directly use the argv_array embedded in "struct
child_process".

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2016-02-22 14:50:32 -08:00

199 lines
4.5 KiB
C

#include "builtin.h"
#include "transport.h"
#include "run-command.h"
#include "pkt-line.h"
/*
* URL syntax:
* 'command [arg1 [arg2 [...]]]' Invoke command with given arguments.
* Special characters:
* '% ': Literal space in argument.
* '%%': Literal percent sign.
* '%S': Name of service (git-upload-pack/git-upload-archive/
* git-receive-pack.
* '%s': Same as \s, but with possible git- prefix stripped.
* '%G': Only allowed as first 'character' of argument. Do not pass this
* Argument to command, instead send this as name of repository
* in in-line git://-style request (also activates sending this
* style of request).
* '%V': Only allowed as first 'character' of argument. Used in
* conjunction with '%G': Do not pass this argument to command,
* instead send this as vhost in git://-style request (note: does
* not activate sending git:// style request).
*/
static char *git_req;
static char *git_req_vhost;
static char *strip_escapes(const char *str, const char *service,
const char **next)
{
size_t rpos = 0;
int escape = 0;
char special = 0;
const char *service_noprefix = service;
struct strbuf ret = STRBUF_INIT;
skip_prefix(service_noprefix, "git-", &service_noprefix);
/* Pass the service to command. */
setenv("GIT_EXT_SERVICE", service, 1);
setenv("GIT_EXT_SERVICE_NOPREFIX", service_noprefix, 1);
/* Scan the length of argument. */
while (str[rpos] && (escape || str[rpos] != ' ')) {
if (escape) {
switch (str[rpos]) {
case ' ':
case '%':
case 's':
case 'S':
break;
case 'G':
case 'V':
special = str[rpos];
if (rpos == 1)
break;
/* Fall-through to error. */
default:
die("Bad remote-ext placeholder '%%%c'.",
str[rpos]);
}
escape = 0;
} else
escape = (str[rpos] == '%');
rpos++;
}
if (escape && !str[rpos])
die("remote-ext command has incomplete placeholder");
*next = str + rpos;
if (**next == ' ')
++*next; /* Skip over space */
/*
* Do the actual placeholder substitution. The string will be short
* enough not to overflow integers.
*/
rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */
escape = 0;
while (str[rpos] && (escape || str[rpos] != ' ')) {
if (escape) {
switch (str[rpos]) {
case ' ':
case '%':
strbuf_addch(&ret, str[rpos]);
break;
case 's':
strbuf_addstr(&ret, service_noprefix);
break;
case 'S':
strbuf_addstr(&ret, service);
break;
}
escape = 0;
} else
switch (str[rpos]) {
case '%':
escape = 1;
break;
default:
strbuf_addch(&ret, str[rpos]);
break;
}
rpos++;
}
switch (special) {
case 'G':
git_req = strbuf_detach(&ret, NULL);
return NULL;
case 'V':
git_req_vhost = strbuf_detach(&ret, NULL);
return NULL;
default:
return strbuf_detach(&ret, NULL);
}
}
static void parse_argv(struct argv_array *out, const char *arg, const char *service)
{
while (*arg) {
char *expanded = strip_escapes(arg, service, &arg);
if (expanded)
argv_array_push(out, expanded);
free(expanded);
}
}
static void send_git_request(int stdin_fd, const char *serv, const char *repo,
const char *vhost)
{
if (!vhost)
packet_write(stdin_fd, "%s %s%c", serv, repo, 0);
else
packet_write(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0,
vhost, 0);
}
static int run_child(const char *arg, const char *service)
{
int r;
struct child_process child = CHILD_PROCESS_INIT;
child.in = -1;
child.out = -1;
child.err = 0;
parse_argv(&child.args, arg, service);
if (start_command(&child) < 0)
die("Can't run specified command");
if (git_req)
send_git_request(child.in, service, git_req, git_req_vhost);
r = bidirectional_transfer_loop(child.out, child.in);
if (!r)
r = finish_command(&child);
else
finish_command(&child);
return r;
}
#define MAXCOMMAND 4096
static int command_loop(const char *child)
{
char buffer[MAXCOMMAND];
while (1) {
size_t i;
if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
if (ferror(stdin))
die("Comammand input error");
exit(0);
}
/* Strip end of line characters. */
i = strlen(buffer);
while (i > 0 && isspace(buffer[i - 1]))
buffer[--i] = 0;
if (!strcmp(buffer, "capabilities")) {
printf("*connect\n\n");
fflush(stdout);
} else if (!strncmp(buffer, "connect ", 8)) {
printf("\n");
fflush(stdout);
return run_child(child, buffer + 8);
} else {
fprintf(stderr, "Bad command");
return 1;
}
}
}
int cmd_remote_ext(int argc, const char **argv, const char *prefix)
{
if (argc != 3)
die("Expected two arguments");
return command_loop(argv[2]);
}