path.con commit Merge branch 'ap/svn' (834836b)
   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
 294/* We allow "recursive" symbolic links. Only within reason, though. */
 295#define MAXDEPTH 5
 296
 297const char *make_absolute_path(const char *path)
 298{
 299        static char bufs[2][PATH_MAX + 1], *buf = bufs[0], *next_buf = bufs[1];
 300        char cwd[1024] = "";
 301        int buf_index = 1, len;
 302
 303        int depth = MAXDEPTH;
 304        char *last_elem = NULL;
 305        struct stat st;
 306
 307        if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
 308                die ("Too long path: %.*s", 60, path);
 309
 310        while (depth--) {
 311                if (stat(buf, &st) || !S_ISDIR(st.st_mode)) {
 312                        char *last_slash = strrchr(buf, '/');
 313                        if (last_slash) {
 314                                *last_slash = '\0';
 315                                last_elem = xstrdup(last_slash + 1);
 316                        } else {
 317                                last_elem = xstrdup(buf);
 318                                *buf = '\0';
 319                        }
 320                }
 321
 322                if (*buf) {
 323                        if (!*cwd && !getcwd(cwd, sizeof(cwd)))
 324                                die ("Could not get current working directory");
 325
 326                        if (chdir(buf))
 327                                die ("Could not switch to '%s'", buf);
 328                }
 329                if (!getcwd(buf, PATH_MAX))
 330                        die ("Could not get current working directory");
 331
 332                if (last_elem) {
 333                        int len = strlen(buf);
 334                        if (len + strlen(last_elem) + 2 > PATH_MAX)
 335                                die ("Too long path name: '%s/%s'",
 336                                                buf, last_elem);
 337                        buf[len] = '/';
 338                        strcpy(buf + len + 1, last_elem);
 339                        free(last_elem);
 340                        last_elem = NULL;
 341                }
 342
 343                if (!lstat(buf, &st) && S_ISLNK(st.st_mode)) {
 344                        len = readlink(buf, next_buf, PATH_MAX);
 345                        if (len < 0)
 346                                die ("Invalid symlink: %s", buf);
 347                        next_buf[len] = '\0';
 348                        buf = next_buf;
 349                        buf_index = 1 - buf_index;
 350                        next_buf = bufs[buf_index];
 351                } else
 352                        break;
 353        }
 354
 355        if (*cwd && chdir(cwd))
 356                die ("Could not change back to '%s'", cwd);
 357
 358        return buf;
 359}