path.con commit Documentation: don't assume git-sh-setup and git-parse-remote are in PATH (bd87087)
   1/*
   2 * I'm tired of doing "vsnprintf()" etc just to open a
   3 * file, so here's a "return static buffer with printf"
   4 * interface for paths.
   5 *
   6 * It's obviously not thread-safe. Sue me. But it's quite
   7 * useful for doing things like
   8 *
   9 *   f = open(mkpath("%s/%s.git", base, name), O_RDONLY);
  10 *
  11 * which is what it's designed for.
  12 */
  13#include "cache.h"
  14
  15static char bad_path[] = "/bad-path/";
  16
  17static char *get_pathname(void)
  18{
  19        static char pathname_array[4][PATH_MAX];
  20        static int index;
  21        return pathname_array[3 & ++index];
  22}
  23
  24static char *cleanup_path(char *path)
  25{
  26        /* Clean it up */
  27        if (!memcmp(path, "./", 2)) {
  28                path += 2;
  29                while (*path == '/')
  30                        path++;
  31        }
  32        return path;
  33}
  34
  35char *mkpath(const char *fmt, ...)
  36{
  37        va_list args;
  38        unsigned len;
  39        char *pathname = get_pathname();
  40
  41        va_start(args, fmt);
  42        len = vsnprintf(pathname, PATH_MAX, fmt, args);
  43        va_end(args);
  44        if (len >= PATH_MAX)
  45                return bad_path;
  46        return cleanup_path(pathname);
  47}
  48
  49char *git_path(const char *fmt, ...)
  50{
  51        const char *git_dir = get_git_dir();
  52        char *pathname = get_pathname();
  53        va_list args;
  54        unsigned len;
  55
  56        len = strlen(git_dir);
  57        if (len > PATH_MAX-100)
  58                return bad_path;
  59        memcpy(pathname, git_dir, len);
  60        if (len && git_dir[len-1] != '/')
  61                pathname[len++] = '/';
  62        va_start(args, fmt);
  63        len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
  64        va_end(args);
  65        if (len >= PATH_MAX)
  66                return bad_path;
  67        return cleanup_path(pathname);
  68}
  69
  70
  71/* git_mkstemp() - create tmp file honoring TMPDIR variable */
  72int git_mkstemp(char *path, size_t len, const char *template)
  73{
  74        const char *tmp;
  75        size_t n;
  76
  77        tmp = getenv("TMPDIR");
  78        if (!tmp)
  79                tmp = "/tmp";
  80        n = snprintf(path, len, "%s/%s", tmp, template);
  81        if (len <= n) {
  82                errno = ENAMETOOLONG;
  83                return -1;
  84        }
  85        return mkstemp(path);
  86}
  87
  88
  89int validate_headref(const char *path)
  90{
  91        struct stat st;
  92        char *buf, buffer[256];
  93        unsigned char sha1[20];
  94        int fd;
  95        ssize_t len;
  96
  97        if (lstat(path, &st) < 0)
  98                return -1;
  99
 100        /* Make sure it is a "refs/.." symlink */
 101        if (S_ISLNK(st.st_mode)) {
 102                len = readlink(path, buffer, sizeof(buffer)-1);
 103                if (len >= 5 && !memcmp("refs/", buffer, 5))
 104                        return 0;
 105                return -1;
 106        }
 107
 108        /*
 109         * Anything else, just open it and try to see if it is a symbolic ref.
 110         */
 111        fd = open(path, O_RDONLY);
 112        if (fd < 0)
 113                return -1;
 114        len = read_in_full(fd, buffer, sizeof(buffer)-1);
 115        close(fd);
 116
 117        /*
 118         * Is it a symbolic ref?
 119         */
 120        if (len < 4)
 121                return -1;
 122        if (!memcmp("ref:", buffer, 4)) {
 123                buf = buffer + 4;
 124                len -= 4;
 125                while (len && isspace(*buf))
 126                        buf++, len--;
 127                if (len >= 5 && !memcmp("refs/", buf, 5))
 128                        return 0;
 129        }
 130
 131        /*
 132         * Is this a detached HEAD?
 133         */
 134        if (!get_sha1_hex(buffer, sha1))
 135                return 0;
 136
 137        return -1;
 138}
 139
 140static char *user_path(char *buf, char *path, int sz)
 141{
 142        struct passwd *pw;
 143        char *slash;
 144        int len, baselen;
 145
 146        if (!path || path[0] != '~')
 147                return NULL;
 148        path++;
 149        slash = strchr(path, '/');
 150        if (path[0] == '/' || !path[0]) {
 151                pw = getpwuid(getuid());
 152        }
 153        else {
 154                if (slash) {
 155                        *slash = 0;
 156                        pw = getpwnam(path);
 157                        *slash = '/';
 158                }
 159                else
 160                        pw = getpwnam(path);
 161        }
 162        if (!pw || !pw->pw_dir || sz <= strlen(pw->pw_dir))
 163                return NULL;
 164        baselen = strlen(pw->pw_dir);
 165        memcpy(buf, pw->pw_dir, baselen);
 166        while ((1 < baselen) && (buf[baselen-1] == '/')) {
 167                buf[baselen-1] = 0;
 168                baselen--;
 169        }
 170        if (slash && slash[1]) {
 171                len = strlen(slash);
 172                if (sz <= baselen + len)
 173                        return NULL;
 174                memcpy(buf + baselen, slash, len + 1);
 175        }
 176        return buf;
 177}
 178
 179/*
 180 * First, one directory to try is determined by the following algorithm.
 181 *
 182 * (0) If "strict" is given, the path is used as given and no DWIM is
 183 *     done. Otherwise:
 184 * (1) "~/path" to mean path under the running user's home directory;
 185 * (2) "~user/path" to mean path under named user's home directory;
 186 * (3) "relative/path" to mean cwd relative directory; or
 187 * (4) "/absolute/path" to mean absolute directory.
 188 *
 189 * Unless "strict" is given, we try access() for existence of "%s.git/.git",
 190 * "%s/.git", "%s.git", "%s" in this order.  The first one that exists is
 191 * what we try.
 192 *
 193 * Second, we try chdir() to that.  Upon failure, we return NULL.
 194 *
 195 * Then, we try if the current directory is a valid git repository.
 196 * Upon failure, we return NULL.
 197 *
 198 * If all goes well, we return the directory we used to chdir() (but
 199 * before ~user is expanded), avoiding getcwd() resolving symbolic
 200 * links.  User relative paths are also returned as they are given,
 201 * except DWIM suffixing.
 202 */
 203char *enter_repo(char *path, int strict)
 204{
 205        static char used_path[PATH_MAX];
 206        static char validated_path[PATH_MAX];
 207
 208        if (!path)
 209                return NULL;
 210
 211        if (!strict) {
 212                static const char *suffix[] = {
 213                        ".git/.git", "/.git", ".git", "", NULL,
 214                };
 215                int len = strlen(path);
 216                int i;
 217                while ((1 < len) && (path[len-1] == '/')) {
 218                        path[len-1] = 0;
 219                        len--;
 220                }
 221                if (PATH_MAX <= len)
 222                        return NULL;
 223                if (path[0] == '~') {
 224                        if (!user_path(used_path, path, PATH_MAX))
 225                                return NULL;
 226                        strcpy(validated_path, path);
 227                        path = used_path;
 228                }
 229                else if (PATH_MAX - 10 < len)
 230                        return NULL;
 231                else {
 232                        path = strcpy(used_path, path);
 233                        strcpy(validated_path, path);
 234                }
 235                len = strlen(path);
 236                for (i = 0; suffix[i]; i++) {
 237                        strcpy(path + len, suffix[i]);
 238                        if (!access(path, F_OK)) {
 239                                strcat(validated_path, suffix[i]);
 240                                break;
 241                        }
 242                }
 243                if (!suffix[i] || chdir(path))
 244                        return NULL;
 245                path = validated_path;
 246        }
 247        else if (chdir(path))
 248                return NULL;
 249
 250        if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
 251            validate_headref("HEAD") == 0) {
 252                setenv(GIT_DIR_ENVIRONMENT, ".", 1);
 253                check_repository_format();
 254                return path;
 255        }
 256
 257        return NULL;
 258}
 259
 260int adjust_shared_perm(const char *path)
 261{
 262        struct stat st;
 263        int mode;
 264
 265        if (!shared_repository)
 266                return 0;
 267        if (lstat(path, &st) < 0)
 268                return -1;
 269        mode = st.st_mode;
 270
 271        if (shared_repository) {
 272                int tweak = shared_repository;
 273                if (!(mode & S_IWUSR))
 274                        tweak &= ~0222;
 275                mode = (mode & ~0777) | tweak;
 276        } else {
 277                /* Preserve old PERM_UMASK behaviour */
 278                if (mode & S_IWUSR)
 279                        mode |= S_IWGRP;
 280        }
 281
 282        if (S_ISDIR(mode)) {
 283                mode |= FORCE_DIR_SET_GID;
 284
 285                /* Copy read bits to execute bits */
 286                mode |= (shared_repository & 0444) >> 2;
 287        }
 288
 289        if ((mode & st.st_mode) != mode && chmod(path, mode) < 0)
 290                return -2;
 291        return 0;
 292}
 293
 294static const char *get_pwd_cwd(void)
 295{
 296        static char cwd[PATH_MAX + 1];
 297        char *pwd;
 298        struct stat cwd_stat, pwd_stat;
 299        if (getcwd(cwd, PATH_MAX) == NULL)
 300                return NULL;
 301        pwd = getenv("PWD");
 302        if (pwd && strcmp(pwd, cwd)) {
 303                stat(cwd, &cwd_stat);
 304                if (!stat(pwd, &pwd_stat) &&
 305                    pwd_stat.st_dev == cwd_stat.st_dev &&
 306                    pwd_stat.st_ino == cwd_stat.st_ino) {
 307                        strlcpy(cwd, pwd, PATH_MAX);
 308                }
 309        }
 310        return cwd;
 311}
 312
 313const char *make_nonrelative_path(const char *path)
 314{
 315        static char buf[PATH_MAX + 1];
 316
 317        if (is_absolute_path(path)) {
 318                if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
 319                        die ("Too long path: %.*s", 60, path);
 320        } else {
 321                const char *cwd = get_pwd_cwd();
 322                if (!cwd)
 323                        die("Cannot determine the current working directory");
 324                if (snprintf(buf, PATH_MAX, "%s/%s", cwd, path) >= PATH_MAX)
 325                        die ("Too long path: %.*s", 60, path);
 326        }
 327        return buf;
 328}
 329
 330const char *make_relative_path(const char *abs, const char *base)
 331{
 332        static char buf[PATH_MAX + 1];
 333        int baselen;
 334        if (!base)
 335                return abs;
 336        baselen = strlen(base);
 337        if (prefixcmp(abs, base))
 338                return abs;
 339        if (abs[baselen] == '/')
 340                baselen++;
 341        else if (base[baselen - 1] != '/')
 342                return abs;
 343        strcpy(buf, abs + baselen);
 344        return buf;
 345}