shell.c 4.7 KB
Newer Older
L
Linus Torvalds 已提交
1 2
#include "cache.h"
#include "quote.h"
3
#include "exec_cmd.h"
J
Johannes Schindelin 已提交
4
#include "strbuf.h"
5
#include "run-command.h"
L
Linus Torvalds 已提交
6

7
#define COMMAND_DIR "git-shell-commands"
8
#define HELP_COMMAND COMMAND_DIR "/help"
9

L
Linus Torvalds 已提交
10 11 12 13
static int do_generic_cmd(const char *me, char *arg)
{
	const char *my_argv[4];

14
	setup_path();
15
	if (!arg || !(arg = sq_dequote(arg)))
L
Linus Torvalds 已提交
16
		die("bad argument");
17
	if (prefixcmp(me, "git-"))
18
		die("bad command");
L
Linus Torvalds 已提交
19

20
	my_argv[0] = me + 4;
L
Linus Torvalds 已提交
21 22 23
	my_argv[1] = arg;
	my_argv[2] = NULL;

J
Junio C Hamano 已提交
24
	return execv_git_cmd(my_argv);
L
Linus Torvalds 已提交
25 26
}

J
Johannes Schindelin 已提交
27 28 29 30 31 32 33 34 35
static int do_cvs_cmd(const char *me, char *arg)
{
	const char *cvsserver_argv[3] = {
		"cvsserver", "server", NULL
	};

	if (!arg || strcmp(arg, "server"))
		die("git-cvsserver only handles server: %s", arg);

36
	setup_path();
J
Johannes Schindelin 已提交
37 38 39
	return execv_git_cmd(cvsserver_argv);
}

40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
static int is_valid_cmd_name(const char *cmd)
{
	/* Test command contains no . or / characters */
	return cmd[strcspn(cmd, "./")] == '\0';
}

static char *make_cmd(const char *prog)
{
	char *prefix = xmalloc((strlen(prog) + strlen(COMMAND_DIR) + 2));
	strcpy(prefix, COMMAND_DIR);
	strcat(prefix, "/");
	strcat(prefix, prog);
	return prefix;
}

static void cd_to_homedir(void)
{
	const char *home = getenv("HOME");
	if (!home)
		die("could not determine user's home directory; HOME is unset");
	if (chdir(home) == -1)
		die("could not chdir to user's home directory");
}
J
Johannes Schindelin 已提交
63

64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
static void run_shell(void)
{
	int done = 0;
	static const char *help_argv[] = { HELP_COMMAND, NULL };
	/* Print help if enabled */
	run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE);

	do {
		struct strbuf line = STRBUF_INIT;
		const char *prog;
		char *full_cmd;
		char *rawargs;
		const char **argv;
		int code;

		fprintf(stderr, "git> ");
		if (strbuf_getline(&line, stdin, '\n') == EOF) {
			fprintf(stderr, "\n");
			strbuf_release(&line);
			break;
		}
		strbuf_trim(&line);
		rawargs = strbuf_detach(&line, NULL);
		if (split_cmdline(rawargs, &argv) == -1) {
			free(rawargs);
			continue;
		}

		prog = argv[0];
		if (!strcmp(prog, "")) {
		} else if (!strcmp(prog, "quit") || !strcmp(prog, "logout") ||
			   !strcmp(prog, "exit") || !strcmp(prog, "bye")) {
			done = 1;
		} else if (is_valid_cmd_name(prog)) {
			full_cmd = make_cmd(prog);
			argv[0] = full_cmd;
			code = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE);
			if (code == -1 && errno == ENOENT) {
				fprintf(stderr, "unrecognized command '%s'\n", prog);
			}
			free(full_cmd);
		} else {
			fprintf(stderr, "invalid command format '%s'\n", prog);
		}

		free(argv);
		free(rawargs);
	} while (!done);
}

L
Linus Torvalds 已提交
114 115 116 117 118 119
static struct commands {
	const char *name;
	int (*exec)(const char *me, char *arg);
} cmd_list[] = {
	{ "git-receive-pack", do_generic_cmd },
	{ "git-upload-pack", do_generic_cmd },
120
	{ "git-upload-archive", do_generic_cmd },
J
Johannes Schindelin 已提交
121
	{ "cvs", do_cvs_cmd },
L
Linus Torvalds 已提交
122 123 124
	{ NULL },
};

J
Junio C Hamano 已提交
125
int main(int argc, char **argv)
L
Linus Torvalds 已提交
126 127
{
	char *prog;
128
	const char **user_argv;
L
Linus Torvalds 已提交
129
	struct commands *cmd;
130 131 132 133 134 135 136 137 138 139 140
	int devnull_fd;

	/*
	 * Always open file descriptors 0/1/2 to avoid clobbering files
	 * in die().  It also avoids not messing up when the pipes are
	 * dup'ed onto stdin/stdout/stderr in the child processes we spawn.
	 */
	devnull_fd = open("/dev/null", O_RDWR);
	while (devnull_fd >= 0 && devnull_fd <= 2)
		devnull_fd = dup(devnull_fd);
	if (devnull_fd == -1)
141
		die_errno("opening /dev/null failed");
142
	close (devnull_fd);
L
Linus Torvalds 已提交
143

144 145 146
	/*
	 * Special hack to pretend to be a CVS server
	 */
147
	if (argc == 2 && !strcmp(argv[1], "cvs server")) {
J
Johannes Schindelin 已提交
148
		argv--;
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
	} else if (argc == 1) {
		/* Allow the user to run an interactive shell */
		cd_to_homedir();
		if (access(COMMAND_DIR, R_OK | X_OK) == -1)
			die("Sorry, the interactive git-shell is not enabled");
		run_shell();
		exit(0);
	} else if (argc != 3 || strcmp(argv[1], "-c")) {
		/*
		 * We do not accept any other modes except "-c" followed by
		 * "cmd arg", where "cmd" is a very limited subset of git
		 * commands or a command in the COMMAND_DIR
		 */
		die("Run with no arguments or with -c cmd");
	}
L
Linus Torvalds 已提交
164

165
	prog = xstrdup(argv[2]);
166 167 168 169
	if (!strncmp(prog, "git", 3) && isspace(prog[3]))
		/* Accept "git foo" as if the caller said "git-foo". */
		prog[3] = '-';

L
Linus Torvalds 已提交
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
	for (cmd = cmd_list ; cmd->name ; cmd++) {
		int len = strlen(cmd->name);
		char *arg;
		if (strncmp(cmd->name, prog, len))
			continue;
		arg = NULL;
		switch (prog[len]) {
		case '\0':
			arg = NULL;
			break;
		case ' ':
			arg = prog + len + 1;
			break;
		default:
			continue;
		}
		exit(cmd->exec(cmd->name, arg));
	}
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202

	cd_to_homedir();
	if (split_cmdline(prog, &user_argv) != -1) {
		if (is_valid_cmd_name(user_argv[0])) {
			prog = make_cmd(user_argv[0]);
			user_argv[0] = prog;
			execv(user_argv[0], (char *const *) user_argv);
		}
		free(prog);
		free(user_argv);
		die("unrecognized command '%s'", argv[2]);
	} else {
		free(prog);
		die("invalid command format '%s'", argv[2]);
	}
L
Linus Torvalds 已提交
203
}