git.con commit archimport: safer log file parsing (6df896b)
   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#include "git-compat-util.h"
  12
  13#ifndef PATH_MAX
  14# define PATH_MAX 4096
  15#endif
  16
  17static const char git_usage[] =
  18        "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]";
  19
  20/* most gui terms set COLUMNS (although some don't export it) */
  21static int term_columns(void)
  22{
  23        char *col_string = getenv("COLUMNS");
  24        int n_cols = 0;
  25
  26        if (col_string && (n_cols = atoi(col_string)) > 0)
  27                return n_cols;
  28
  29        return 80;
  30}
  31
  32static void oom(void)
  33{
  34        fprintf(stderr, "git: out of memory\n");
  35        exit(1);
  36}
  37
  38static inline void mput_char(char c, unsigned int num)
  39{
  40        while(num--)
  41                putchar(c);
  42}
  43
  44static struct cmdname {
  45        size_t len;
  46        char name[1];
  47} **cmdname;
  48static int cmdname_alloc, cmdname_cnt;
  49
  50static void add_cmdname(const char *name, int len)
  51{
  52        struct cmdname *ent;
  53        if (cmdname_alloc <= cmdname_cnt) {
  54                cmdname_alloc = cmdname_alloc + 200;
  55                cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname));
  56                if (!cmdname)
  57                        oom();
  58        }
  59        ent = malloc(sizeof(*ent) + len);
  60        if (!ent)
  61                oom();
  62        ent->len = len;
  63        memcpy(ent->name, name, len);
  64        ent->name[len] = 0;
  65        cmdname[cmdname_cnt++] = ent;
  66}
  67
  68static int cmdname_compare(const void *a_, const void *b_)
  69{
  70        struct cmdname *a = *(struct cmdname **)a_;
  71        struct cmdname *b = *(struct cmdname **)b_;
  72        return strcmp(a->name, b->name);
  73}
  74
  75static void pretty_print_string_list(struct cmdname **cmdname, int longest)
  76{
  77        int cols = 1;
  78        int space = longest + 1; /* min 1 SP between words */
  79        int max_cols = term_columns() - 1; /* don't print *on* the edge */
  80        int i;
  81
  82        if (space < max_cols)
  83                cols = max_cols / space;
  84
  85        qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare);
  86
  87        for (i = 0; i < cmdname_cnt; ) {
  88                int c;
  89                printf("  ");
  90
  91                for (c = cols; c && i < cmdname_cnt; i++) {
  92                        printf("%s", cmdname[i]->name);
  93
  94                        if (--c)
  95                                mput_char(' ', space - cmdname[i]->len);
  96                }
  97                putchar('\n');
  98        }
  99}
 100
 101static void list_commands(const char *exec_path, const char *pattern)
 102{
 103        unsigned int longest = 0;
 104        char path[PATH_MAX];
 105        int dirlen;
 106        DIR *dir = opendir(exec_path);
 107        struct dirent *de;
 108
 109        if (!dir) {
 110                fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno));
 111                exit(1);
 112        }
 113
 114        dirlen = strlen(exec_path);
 115        if (PATH_MAX - 20 < dirlen) {
 116                fprintf(stderr, "git: insanely long exec-path '%s'\n",
 117                        exec_path);
 118                exit(1);
 119        }
 120
 121        memcpy(path, exec_path, dirlen);
 122        path[dirlen++] = '/';
 123
 124        while ((de = readdir(dir)) != NULL) {
 125                struct stat st;
 126                int entlen;
 127
 128                if (strncmp(de->d_name, "git-", 4))
 129                        continue;
 130                strcpy(path+dirlen, de->d_name);
 131                if (stat(path, &st) || /* stat, not lstat */
 132                    !S_ISREG(st.st_mode) ||
 133                    !(st.st_mode & S_IXUSR))
 134                        continue;
 135
 136                entlen = strlen(de->d_name);
 137                if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe"))
 138                        entlen -= 4;
 139
 140                if (longest < entlen)
 141                        longest = entlen;
 142
 143                add_cmdname(de->d_name + 4, entlen-4);
 144        }
 145        closedir(dir);
 146
 147        printf("git commands available in '%s'\n", exec_path);
 148        printf("----------------------------");
 149        mput_char('-', strlen(exec_path));
 150        putchar('\n');
 151        pretty_print_string_list(cmdname, longest - 4);
 152        putchar('\n');
 153}
 154
 155#ifdef __GNUC__
 156static void cmd_usage(const char *exec_path, const char *fmt, ...)
 157        __attribute__((__format__(__printf__, 2, 3), __noreturn__));
 158#endif
 159static void cmd_usage(const char *exec_path, const char *fmt, ...)
 160{
 161        if (fmt) {
 162                va_list ap;
 163
 164                va_start(ap, fmt);
 165                printf("git: ");
 166                vprintf(fmt, ap);
 167                va_end(ap);
 168                putchar('\n');
 169        }
 170        else
 171                puts(git_usage);
 172
 173        putchar('\n');
 174
 175        if(exec_path)
 176                list_commands(exec_path, "git-*");
 177
 178        exit(1);
 179}
 180
 181static void prepend_to_path(const char *dir, int len)
 182{
 183        char *path, *old_path = getenv("PATH");
 184        int path_len = len;
 185
 186        if (!old_path)
 187                old_path = "/usr/local/bin:/usr/bin:/bin";
 188
 189        path_len = len + strlen(old_path) + 1;
 190
 191        path = malloc(path_len + 1);
 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                        cmd_usage(NULL, NULL);
 255        }
 256
 257        if (i >= argc || show_help) {
 258                if (i >= argc)
 259                        cmd_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        len += snprintf(git_command + len, sizeof(git_command) - len,
 287                        "/git-%s", argv[i]);
 288        if (sizeof(git_command) <= len) {
 289                fprintf(stderr, "git: command name given is too long.\n");
 290                exit(1);
 291        }
 292
 293        /* execve() can only ever return if it fails */
 294        execve(git_command, &argv[i], envp);
 295
 296        if (errno == ENOENT)
 297                cmd_usage(exec_path, "'%s' is not a git-command", argv[i]);
 298
 299        fprintf(stderr, "Failed to run command '%s': %s\n",
 300                git_command, strerror(errno));
 301
 302        return 1;
 303}