path.con commit Merge branch 'ar/maint-mksnpath' into HEAD (356af64)
   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 *mksnpath(char *buf, size_t n, const char *fmt, ...)
  36{
  37        va_list args;
  38        unsigned len;
  39
  40        va_start(args, fmt);
  41        len = vsnprintf(buf, n, fmt, args);
  42        va_end(args);
  43        if (len >= n) {
  44                snprintf(buf, n, bad_path);
  45                return buf;
  46        }
  47        return cleanup_path(buf);
  48}
  49
  50char *mkpath(const char *fmt, ...)
  51{
  52        va_list args;
  53        unsigned len;
  54        char *pathname = get_pathname();
  55
  56        va_start(args, fmt);
  57        len = vsnprintf(pathname, PATH_MAX, fmt, args);
  58        va_end(args);
  59        if (len >= PATH_MAX)
  60                return bad_path;
  61        return cleanup_path(pathname);
  62}
  63
  64char *git_path(const char *fmt, ...)
  65{
  66        const char *git_dir = get_git_dir();
  67        char *pathname = get_pathname();
  68        va_list args;
  69        unsigned len;
  70
  71        len = strlen(git_dir);
  72        if (len > PATH_MAX-100)
  73                return bad_path;
  74        memcpy(pathname, git_dir, len);
  75        if (len && git_dir[len-1] != '/')
  76                pathname[len++] = '/';
  77        va_start(args, fmt);
  78        len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
  79        va_end(args);
  80        if (len >= PATH_MAX)
  81                return bad_path;
  82        return cleanup_path(pathname);
  83}
  84
  85
  86/* git_mkstemp() - create tmp file honoring TMPDIR variable */
  87int git_mkstemp(char *path, size_t len, const char *template)
  88{
  89        const char *tmp;
  90        size_t n;
  91
  92        tmp = getenv("TMPDIR");
  93        if (!tmp)
  94                tmp = "/tmp";
  95        n = snprintf(path, len, "%s/%s", tmp, template);
  96        if (len <= n) {
  97                errno = ENAMETOOLONG;
  98                return -1;
  99        }
 100        return mkstemp(path);
 101}
 102
 103
 104int validate_headref(const char *path)
 105{
 106        struct stat st;
 107        char *buf, buffer[256];
 108        unsigned char sha1[20];
 109        int fd;
 110        ssize_t len;
 111
 112        if (lstat(path, &st) < 0)
 113                return -1;
 114
 115        /* Make sure it is a "refs/.." symlink */
 116        if (S_ISLNK(st.st_mode)) {
 117                len = readlink(path, buffer, sizeof(buffer)-1);
 118                if (len >= 5 && !memcmp("refs/", buffer, 5))
 119                        return 0;
 120                return -1;
 121        }
 122
 123        /*
 124         * Anything else, just open it and try to see if it is a symbolic ref.
 125         */
 126        fd = open(path, O_RDONLY);
 127        if (fd < 0)
 128                return -1;
 129        len = read_in_full(fd, buffer, sizeof(buffer)-1);
 130        close(fd);
 131
 132        /*
 133         * Is it a symbolic ref?
 134         */
 135        if (len < 4)
 136                return -1;
 137        if (!memcmp("ref:", buffer, 4)) {
 138                buf = buffer + 4;
 139                len -= 4;
 140                while (len && isspace(*buf))
 141                        buf++, len--;
 142                if (len >= 5 && !memcmp("refs/", buf, 5))
 143                        return 0;
 144        }
 145
 146        /*
 147         * Is this a detached HEAD?
 148         */
 149        if (!get_sha1_hex(buffer, sha1))
 150                return 0;
 151
 152        return -1;
 153}
 154
 155static char *user_path(char *buf, char *path, int sz)
 156{
 157        struct passwd *pw;
 158        char *slash;
 159        int len, baselen;
 160
 161        if (!path || path[0] != '~')
 162                return NULL;
 163        path++;
 164        slash = strchr(path, '/');
 165        if (path[0] == '/' || !path[0]) {
 166                pw = getpwuid(getuid());
 167        }
 168        else {
 169                if (slash) {
 170                        *slash = 0;
 171                        pw = getpwnam(path);
 172                        *slash = '/';
 173                }
 174                else
 175                        pw = getpwnam(path);
 176        }
 177        if (!pw || !pw->pw_dir || sz <= strlen(pw->pw_dir))
 178                return NULL;
 179        baselen = strlen(pw->pw_dir);
 180        memcpy(buf, pw->pw_dir, baselen);
 181        while ((1 < baselen) && (buf[baselen-1] == '/')) {
 182                buf[baselen-1] = 0;
 183                baselen--;
 184        }
 185        if (slash && slash[1]) {
 186                len = strlen(slash);
 187                if (sz <= baselen + len)
 188                        return NULL;
 189                memcpy(buf + baselen, slash, len + 1);
 190        }
 191        return buf;
 192}
 193
 194/*
 195 * First, one directory to try is determined by the following algorithm.
 196 *
 197 * (0) If "strict" is given, the path is used as given and no DWIM is
 198 *     done. Otherwise:
 199 * (1) "~/path" to mean path under the running user's home directory;
 200 * (2) "~user/path" to mean path under named user's home directory;
 201 * (3) "relative/path" to mean cwd relative directory; or
 202 * (4) "/absolute/path" to mean absolute directory.
 203 *
 204 * Unless "strict" is given, we try access() for existence of "%s.git/.git",
 205 * "%s/.git", "%s.git", "%s" in this order.  The first one that exists is
 206 * what we try.
 207 *
 208 * Second, we try chdir() to that.  Upon failure, we return NULL.
 209 *
 210 * Then, we try if the current directory is a valid git repository.
 211 * Upon failure, we return NULL.
 212 *
 213 * If all goes well, we return the directory we used to chdir() (but
 214 * before ~user is expanded), avoiding getcwd() resolving symbolic
 215 * links.  User relative paths are also returned as they are given,
 216 * except DWIM suffixing.
 217 */
 218char *enter_repo(char *path, int strict)
 219{
 220        static char used_path[PATH_MAX];
 221        static char validated_path[PATH_MAX];
 222
 223        if (!path)
 224                return NULL;
 225
 226        if (!strict) {
 227                static const char *suffix[] = {
 228                        ".git/.git", "/.git", ".git", "", NULL,
 229                };
 230                int len = strlen(path);
 231                int i;
 232                while ((1 < len) && (path[len-1] == '/')) {
 233                        path[len-1] = 0;
 234                        len--;
 235                }
 236                if (PATH_MAX <= len)
 237                        return NULL;
 238                if (path[0] == '~') {
 239                        if (!user_path(used_path, path, PATH_MAX))
 240                                return NULL;
 241                        strcpy(validated_path, path);
 242                        path = used_path;
 243                }
 244                else if (PATH_MAX - 10 < len)
 245                        return NULL;
 246                else {
 247                        path = strcpy(used_path, path);
 248                        strcpy(validated_path, path);
 249                }
 250                len = strlen(path);
 251                for (i = 0; suffix[i]; i++) {
 252                        strcpy(path + len, suffix[i]);
 253                        if (!access(path, F_OK)) {
 254                                strcat(validated_path, suffix[i]);
 255                                break;
 256                        }
 257                }
 258                if (!suffix[i] || chdir(path))
 259                        return NULL;
 260                path = validated_path;
 261        }
 262        else if (chdir(path))
 263                return NULL;
 264
 265        if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
 266            validate_headref("HEAD") == 0) {
 267                setenv(GIT_DIR_ENVIRONMENT, ".", 1);
 268                check_repository_format();
 269                return path;
 270        }
 271
 272        return NULL;
 273}
 274
 275int adjust_shared_perm(const char *path)
 276{
 277        struct stat st;
 278        int mode;
 279
 280        if (!shared_repository)
 281                return 0;
 282        if (lstat(path, &st) < 0)
 283                return -1;
 284        mode = st.st_mode;
 285
 286        if (shared_repository) {
 287                int tweak = shared_repository;
 288                if (!(mode & S_IWUSR))
 289                        tweak &= ~0222;
 290                mode |= tweak;
 291        } else {
 292                /* Preserve old PERM_UMASK behaviour */
 293                if (mode & S_IWUSR)
 294                        mode |= S_IWGRP;
 295        }
 296
 297        if (S_ISDIR(mode)) {
 298                mode |= FORCE_DIR_SET_GID;
 299
 300                /* Copy read bits to execute bits */
 301                mode |= (shared_repository & 0444) >> 2;
 302        }
 303
 304        if ((mode & st.st_mode) != mode && chmod(path, mode) < 0)
 305                return -2;
 306        return 0;
 307}
 308
 309const char *make_relative_path(const char *abs, const char *base)
 310{
 311        static char buf[PATH_MAX + 1];
 312        int baselen;
 313        if (!base)
 314                return abs;
 315        baselen = strlen(base);
 316        if (prefixcmp(abs, base))
 317                return abs;
 318        if (abs[baselen] == '/')
 319                baselen++;
 320        else if (base[baselen - 1] != '/')
 321                return abs;
 322        strcpy(buf, abs + baselen);
 323        return buf;
 324}
 325
 326/*
 327 * path = absolute path
 328 * buf = buffer of at least max(2, strlen(path)+1) bytes
 329 * It is okay if buf == path, but they should not overlap otherwise.
 330 *
 331 * Performs the following normalizations on path, storing the result in buf:
 332 * - Removes trailing slashes.
 333 * - Removes empty components.
 334 * - Removes "." components.
 335 * - Removes ".." components, and the components the precede them.
 336 * "" and paths that contain only slashes are normalized to "/".
 337 * Returns the length of the output.
 338 *
 339 * Note that this function is purely textual.  It does not follow symlinks,
 340 * verify the existence of the path, or make any system calls.
 341 */
 342int normalize_absolute_path(char *buf, const char *path)
 343{
 344        const char *comp_start = path, *comp_end = path;
 345        char *dst = buf;
 346        int comp_len;
 347        assert(buf);
 348        assert(path);
 349
 350        while (*comp_start) {
 351                assert(*comp_start == '/');
 352                while (*++comp_end && *comp_end != '/')
 353                        ; /* nothing */
 354                comp_len = comp_end - comp_start;
 355
 356                if (!strncmp("/",  comp_start, comp_len) ||
 357                    !strncmp("/.", comp_start, comp_len))
 358                        goto next;
 359
 360                if (!strncmp("/..", comp_start, comp_len)) {
 361                        while (dst > buf && *--dst != '/')
 362                                ; /* nothing */
 363                        goto next;
 364                }
 365
 366                memcpy(dst, comp_start, comp_len);
 367                dst += comp_len;
 368        next:
 369                comp_start = comp_end;
 370        }
 371
 372        if (dst == buf)
 373                *dst++ = '/';
 374
 375        *dst = '\0';
 376        return dst - buf;
 377}
 378
 379/*
 380 * path = Canonical absolute path
 381 * prefix_list = Colon-separated list of absolute paths
 382 *
 383 * Determines, for each path in prefix_list, whether the "prefix" really
 384 * is an ancestor directory of path.  Returns the length of the longest
 385 * ancestor directory, excluding any trailing slashes, or -1 if no prefix
 386 * is an ancestor.  (Note that this means 0 is returned if prefix_list is
 387 * "/".) "/foo" is not considered an ancestor of "/foobar".  Directories
 388 * are not considered to be their own ancestors.  path must be in a
 389 * canonical form: empty components, or "." or ".." components are not
 390 * allowed.  prefix_list may be null, which is like "".
 391 */
 392int longest_ancestor_length(const char *path, const char *prefix_list)
 393{
 394        char buf[PATH_MAX+1];
 395        const char *ceil, *colon;
 396        int len, max_len = -1;
 397
 398        if (prefix_list == NULL || !strcmp(path, "/"))
 399                return -1;
 400
 401        for (colon = ceil = prefix_list; *colon; ceil = colon+1) {
 402                for (colon = ceil; *colon && *colon != ':'; colon++);
 403                len = colon - ceil;
 404                if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil))
 405                        continue;
 406                strlcpy(buf, ceil, len+1);
 407                len = normalize_absolute_path(buf, buf);
 408                /* Strip "trailing slashes" from "/". */
 409                if (len == 1)
 410                        len = 0;
 411
 412                if (!strncmp(path, buf, len) &&
 413                    path[len] == '/' &&
 414                    len > max_len) {
 415                        max_len = len;
 416                }
 417        }
 418
 419        return max_len;
 420}