shell.con commit general improvements (43abf13)
   1#include "cache.h"
   2#include "quote.h"
   3#include "exec-cmd.h"
   4#include "strbuf.h"
   5#include "run-command.h"
   6#include "alias.h"
   7
   8#define COMMAND_DIR "git-shell-commands"
   9#define HELP_COMMAND COMMAND_DIR "/help"
  10#define NOLOGIN_COMMAND COMMAND_DIR "/no-interactive-login"
  11
  12static int do_generic_cmd(const char *me, char *arg)
  13{
  14        const char *my_argv[4];
  15
  16        setup_path();
  17        if (!arg || !(arg = sq_dequote(arg)) || *arg == '-')
  18                die("bad argument");
  19        if (!starts_with(me, "git-"))
  20                die("bad command");
  21
  22        my_argv[0] = me + 4;
  23        my_argv[1] = arg;
  24        my_argv[2] = NULL;
  25
  26        return execv_git_cmd(my_argv);
  27}
  28
  29static int is_valid_cmd_name(const char *cmd)
  30{
  31        /* Test command contains no . or / characters */
  32        return cmd[strcspn(cmd, "./")] == '\0';
  33}
  34
  35static char *make_cmd(const char *prog)
  36{
  37        return xstrfmt("%s/%s", COMMAND_DIR, prog);
  38}
  39
  40static void cd_to_homedir(void)
  41{
  42        const char *home = getenv("HOME");
  43        if (!home)
  44                die("could not determine user's home directory; HOME is unset");
  45        if (chdir(home) == -1)
  46                die("could not chdir to user's home directory");
  47}
  48
  49static void run_shell(void)
  50{
  51        int done = 0;
  52        static const char *help_argv[] = { HELP_COMMAND, NULL };
  53
  54        if (!access(NOLOGIN_COMMAND, F_OK)) {
  55                /* Interactive login disabled. */
  56                const char *argv[] = { NOLOGIN_COMMAND, NULL };
  57                int status;
  58
  59                status = run_command_v_opt(argv, 0);
  60                if (status < 0)
  61                        exit(127);
  62                exit(status);
  63        }
  64
  65        /* Print help if enabled */
  66        run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE);
  67
  68        do {
  69                struct strbuf line = STRBUF_INIT;
  70                const char *prog;
  71                char *full_cmd;
  72                char *rawargs;
  73                char *split_args;
  74                const char **argv;
  75                int code;
  76                int count;
  77
  78                fprintf(stderr, "git> ");
  79                if (strbuf_getline_lf(&line, stdin) == EOF) {
  80                        fprintf(stderr, "\n");
  81                        strbuf_release(&line);
  82                        break;
  83                }
  84                strbuf_trim(&line);
  85                rawargs = strbuf_detach(&line, NULL);
  86                split_args = xstrdup(rawargs);
  87                count = split_cmdline(split_args, &argv);
  88                if (count < 0) {
  89                        fprintf(stderr, "invalid command format '%s': %s\n", rawargs,
  90                                split_cmdline_strerror(count));
  91                        free(split_args);
  92                        free(rawargs);
  93                        continue;
  94                }
  95
  96                prog = argv[0];
  97                if (!strcmp(prog, "")) {
  98                } else if (!strcmp(prog, "quit") || !strcmp(prog, "logout") ||
  99                           !strcmp(prog, "exit") || !strcmp(prog, "bye")) {
 100                        done = 1;
 101                } else if (is_valid_cmd_name(prog)) {
 102                        full_cmd = make_cmd(prog);
 103                        argv[0] = full_cmd;
 104                        code = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE);
 105                        if (code == -1 && errno == ENOENT) {
 106                                fprintf(stderr, "unrecognized command '%s'\n", prog);
 107                        }
 108                        free(full_cmd);
 109                } else {
 110                        fprintf(stderr, "invalid command format '%s'\n", prog);
 111                }
 112
 113                free(argv);
 114                free(rawargs);
 115        } while (!done);
 116}
 117
 118static struct commands {
 119        const char *name;
 120        int (*exec)(const char *me, char *arg);
 121} cmd_list[] = {
 122        { "git-receive-pack", do_generic_cmd },
 123        { "git-upload-pack", do_generic_cmd },
 124        { "git-upload-archive", do_generic_cmd },
 125        { NULL },
 126};
 127
 128int cmd_main(int argc, const char **argv)
 129{
 130        char *prog;
 131        const char **user_argv;
 132        struct commands *cmd;
 133        int count;
 134
 135        /*
 136         * Special hack to pretend to be a CVS server
 137         */
 138        if (argc == 2 && !strcmp(argv[1], "cvs server")) {
 139                argv--;
 140        } else if (argc == 1) {
 141                /* Allow the user to run an interactive shell */
 142                cd_to_homedir();
 143                if (access(COMMAND_DIR, R_OK | X_OK) == -1) {
 144                        die("Interactive git shell is not enabled.\n"
 145                            "hint: ~/" COMMAND_DIR " should exist "
 146                            "and have read and execute access.");
 147                }
 148                run_shell();
 149                exit(0);
 150        } else if (argc != 3 || strcmp(argv[1], "-c")) {
 151                /*
 152                 * We do not accept any other modes except "-c" followed by
 153                 * "cmd arg", where "cmd" is a very limited subset of git
 154                 * commands or a command in the COMMAND_DIR
 155                 */
 156                die("Run with no arguments or with -c cmd");
 157        }
 158
 159        prog = xstrdup(argv[2]);
 160        if (!strncmp(prog, "git", 3) && isspace(prog[3]))
 161                /* Accept "git foo" as if the caller said "git-foo". */
 162                prog[3] = '-';
 163
 164        for (cmd = cmd_list ; cmd->name ; cmd++) {
 165                int len = strlen(cmd->name);
 166                char *arg;
 167                if (strncmp(cmd->name, prog, len))
 168                        continue;
 169                arg = NULL;
 170                switch (prog[len]) {
 171                case '\0':
 172                        arg = NULL;
 173                        break;
 174                case ' ':
 175                        arg = prog + len + 1;
 176                        break;
 177                default:
 178                        continue;
 179                }
 180                exit(cmd->exec(cmd->name, arg));
 181        }
 182
 183        cd_to_homedir();
 184        count = split_cmdline(prog, &user_argv);
 185        if (count >= 0) {
 186                if (is_valid_cmd_name(user_argv[0])) {
 187                        prog = make_cmd(user_argv[0]);
 188                        user_argv[0] = prog;
 189                        execv(user_argv[0], (char *const *) user_argv);
 190                }
 191                free(prog);
 192                free(user_argv);
 193                die("unrecognized command '%s'", argv[2]);
 194        } else {
 195                free(prog);
 196                die("invalid command format '%s': %s", argv[2],
 197                    split_cmdline_strerror(count));
 198        }
 199}