git.con commit Prevent "git-commit -a path1 path2..." (f678dd1)
   1#include <stdio.h>
   2#include <sys/types.h>
   3#include <sys/stat.h>
   4#include <dirent.h>
   5#include <unistd.h>
   6#include <stdlib.h>
   7#include <string.h>
   8#include <errno.h>
   9#include <limits.h>
  10#include <stdarg.h>
  11
  12#ifndef PATH_MAX
  13# define PATH_MAX 4096
  14#endif
  15
  16static const char git_usage[] =
  17        "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]";
  18
  19/* most gui terms set COLUMNS (although some don't export it) */
  20static int term_columns(void)
  21{
  22        char *col_string = getenv("COLUMNS");
  23        int n_cols = 0;
  24
  25        if (col_string && (n_cols = atoi(col_string)) > 0)
  26                return n_cols;
  27
  28        return 80;
  29}
  30
  31static void oom(void)
  32{
  33        fprintf(stderr, "git: out of memory\n");
  34        exit(1);
  35}
  36
  37static inline void mput_char(char c, unsigned int num)
  38{
  39        while(num--)
  40                putchar(c);
  41}
  42
  43static struct cmdname {
  44        size_t len;
  45        char name[1];
  46} **cmdname;
  47static int cmdname_alloc, cmdname_cnt;
  48
  49static void add_cmdname(const char *name, int len)
  50{
  51        struct cmdname *ent;
  52        if (cmdname_alloc <= cmdname_cnt) {
  53                cmdname_alloc = cmdname_alloc + 200;
  54                cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname));
  55                if (!cmdname)
  56                        oom();
  57        }
  58        ent = malloc(sizeof(*ent) + len);
  59        if (!ent)
  60                oom();
  61        ent->len = len;
  62        memcpy(ent->name, name, len);
  63        ent->name[len] = 0;
  64        cmdname[cmdname_cnt++] = ent;
  65}
  66
  67static int cmdname_compare(const void *a_, const void *b_)
  68{
  69        struct cmdname *a = *(struct cmdname **)a_;
  70        struct cmdname *b = *(struct cmdname **)b_;
  71        return strcmp(a->name, b->name);
  72}
  73
  74static void pretty_print_string_list(struct cmdname **cmdname, int longest)
  75{
  76        int cols = 1;
  77        int space = longest + 1; /* min 1 SP between words */
  78        int max_cols = term_columns() - 1; /* don't print *on* the edge */
  79        int i;
  80
  81        if (space < max_cols)
  82                cols = max_cols / space;
  83
  84        qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare);
  85
  86        for (i = 0; i < cmdname_cnt; ) {
  87                int c;
  88                printf("  ");
  89
  90                for (c = cols; c && i < cmdname_cnt; i++) {
  91                        printf("%s", cmdname[i]->name);
  92
  93                        if (--c)
  94                                mput_char(' ', space - cmdname[i]->len);
  95                }
  96                putchar('\n');
  97        }
  98}
  99
 100static void list_commands(const char *exec_path, const char *pattern)
 101{
 102        unsigned int longest = 0;
 103        char path[PATH_MAX];
 104        int dirlen;
 105        DIR *dir = opendir(exec_path);
 106        struct dirent *de;
 107
 108        if (!dir) {
 109                fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno));
 110                exit(1);
 111        }
 112
 113        dirlen = strlen(exec_path);
 114        if (PATH_MAX - 20 < dirlen) {
 115                fprintf(stderr, "git: insanely long exec-path '%s'\n",
 116                        exec_path);
 117                exit(1);
 118        }
 119
 120        memcpy(path, exec_path, dirlen);
 121        path[dirlen++] = '/';
 122
 123        while ((de = readdir(dir)) != NULL) {
 124                struct stat st;
 125                int entlen;
 126
 127                if (strncmp(de->d_name, "git-", 4))
 128                        continue;
 129                strcpy(path+dirlen, de->d_name);
 130                if (stat(path, &st) || /* stat, not lstat */
 131                    !S_ISREG(st.st_mode) ||
 132                    !(st.st_mode & S_IXUSR))
 133                        continue;
 134
 135                entlen = strlen(de->d_name);
 136                if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe"))
 137                        entlen -= 4;
 138
 139                if (longest < entlen)
 140                        longest = entlen;
 141
 142                add_cmdname(de->d_name + 4, entlen-4);
 143        }
 144        closedir(dir);
 145
 146        printf("git commands available in '%s'\n", exec_path);
 147        printf("----------------------------");
 148        mput_char('-', strlen(exec_path));
 149        putchar('\n');
 150        pretty_print_string_list(cmdname, longest - 4);
 151        putchar('\n');
 152}
 153
 154#ifdef __GNUC__
 155static void usage(const char *exec_path, const char *fmt, ...)
 156        __attribute__((__format__(__printf__, 2, 3), __noreturn__));
 157#endif
 158static void usage(const char *exec_path, const char *fmt, ...)
 159{
 160        if (fmt) {
 161                va_list ap;
 162
 163                va_start(ap, fmt);
 164                printf("git: ");
 165                vprintf(fmt, ap);
 166                va_end(ap);
 167                putchar('\n');
 168        }
 169        else
 170                puts(git_usage);
 171
 172        putchar('\n');
 173
 174        if(exec_path)
 175                list_commands(exec_path, "git-*");
 176
 177        exit(1);
 178}
 179
 180static void prepend_to_path(const char *dir, int len)
 181{
 182        char *path, *old_path = getenv("PATH");
 183        int path_len = len;
 184
 185        if (!old_path)
 186                old_path = "/usr/local/bin:/usr/bin:/bin";
 187
 188        path_len = len + strlen(old_path) + 1;
 189
 190        path = malloc(path_len + 1);
 191        path[path_len + 1] = '\0';
 192
 193        memcpy(path, dir, len);
 194        path[len] = ':';
 195        memcpy(path + len + 1, old_path, path_len - len);
 196
 197        setenv("PATH", path, 1);
 198}
 199
 200static void show_man_page(char *git_cmd)
 201{
 202        char *page;
 203
 204        if (!strncmp(git_cmd, "git", 3))
 205                page = git_cmd;
 206        else {
 207                int page_len = strlen(git_cmd) + 4;
 208
 209                page = malloc(page_len + 1);
 210                strcpy(page, "git-");
 211                strcpy(page + 4, git_cmd);
 212                page[page_len] = 0;
 213        }
 214
 215        execlp("man", "man", page, NULL);
 216}
 217
 218int main(int argc, char **argv, char **envp)
 219{
 220        char git_command[PATH_MAX + 1];
 221        char wd[PATH_MAX + 1];
 222        int i, len, show_help = 0;
 223        char *exec_path = getenv("GIT_EXEC_PATH");
 224
 225        getcwd(wd, PATH_MAX);
 226
 227        if (!exec_path)
 228                exec_path = GIT_EXEC_PATH;
 229
 230        for (i = 1; i < argc; i++) {
 231                char *arg = argv[i];
 232
 233                if (strncmp(arg, "--", 2))
 234                        break;
 235
 236                arg += 2;
 237
 238                if (!strncmp(arg, "exec-path", 9)) {
 239                        arg += 9;
 240                        if (*arg == '=')
 241                                exec_path = arg + 1;
 242                        else {
 243                                puts(exec_path);
 244                                exit(0);
 245                        }
 246                }
 247                else if (!strcmp(arg, "version")) {
 248                        printf("git version %s\n", GIT_VERSION);
 249                        exit(0);
 250                }
 251                else if (!strcmp(arg, "help"))
 252                        show_help = 1;
 253                else if (!show_help)
 254                        usage(NULL, NULL);
 255        }
 256
 257        if (i >= argc || show_help) {
 258                if (i >= argc)
 259                        usage(exec_path, NULL);
 260
 261                show_man_page(argv[i]);
 262        }
 263
 264        if (*exec_path != '/') {
 265                if (!getcwd(git_command, sizeof(git_command))) {
 266                        fprintf(stderr,
 267                                "git: cannot determine current directory");
 268                        exit(1);
 269                }
 270                len = strlen(git_command);
 271
 272                /* Trivial cleanup */
 273                while (!strncmp(exec_path, "./", 2)) {
 274                        exec_path += 2;
 275                        while (*exec_path == '/')
 276                                exec_path++;
 277                }
 278                snprintf(git_command + len, sizeof(git_command) - len,
 279                         "/%s", exec_path);
 280        }
 281        else
 282                strcpy(git_command, exec_path);
 283        len = strlen(git_command);
 284        prepend_to_path(git_command, len);
 285
 286        strncat(&git_command[len], "/git-", sizeof(git_command) - len);
 287        len += 5;
 288        strncat(&git_command[len], argv[i], sizeof(git_command) - len);
 289
 290        if (access(git_command, X_OK))
 291                usage(exec_path, "'%s' is not a git-command", argv[i]);
 292
 293        /* execve() can only ever return if it fails */
 294        execve(git_command, &argv[i], envp);
 295        printf("Failed to run command '%s': %s\n", git_command, strerror(errno));
 296
 297        return 1;
 298}