path.con commit write_in_full: really write in full or return error on disk full. (f6aa66c)
   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        char *env, *pch = path;
  75
  76        if ((env = getenv("TMPDIR")) == NULL) {
  77                strcpy(pch, "/tmp/");
  78                len -= 5;
  79                pch += 5;
  80        } else {
  81                size_t n = snprintf(pch, len, "%s/", env);
  82
  83                len -= n;
  84                pch += n;
  85        }
  86
  87        strlcpy(pch, template, len);
  88
  89        return mkstemp(path);
  90}
  91
  92
  93int validate_symref(const char *path)
  94{
  95        struct stat st;
  96        char *buf, buffer[256];
  97        int len, fd;
  98
  99        if (lstat(path, &st) < 0)
 100                return -1;
 101
 102        /* Make sure it is a "refs/.." symlink */
 103        if (S_ISLNK(st.st_mode)) {
 104                len = readlink(path, buffer, sizeof(buffer)-1);
 105                if (len >= 5 && !memcmp("refs/", buffer, 5))
 106                        return 0;
 107                return -1;
 108        }
 109
 110        /*
 111         * Anything else, just open it and try to see if it is a symbolic ref.
 112         */
 113        fd = open(path, O_RDONLY);
 114        if (fd < 0)
 115                return -1;
 116        len = read_in_full(fd, buffer, sizeof(buffer)-1);
 117        close(fd);
 118
 119        /*
 120         * Is it a symbolic ref?
 121         */
 122        if (len < 4 || memcmp("ref:", buffer, 4))
 123                return -1;
 124        buf = buffer + 4;
 125        len -= 4;
 126        while (len && isspace(*buf))
 127                buf++, len--;
 128        if (len >= 5 && !memcmp("refs/", buf, 5))
 129                return 0;
 130        return -1;
 131}
 132
 133static char *user_path(char *buf, char *path, int sz)
 134{
 135        struct passwd *pw;
 136        char *slash;
 137        int len, baselen;
 138
 139        if (!path || path[0] != '~')
 140                return NULL;
 141        path++;
 142        slash = strchr(path, '/');
 143        if (path[0] == '/' || !path[0]) {
 144                pw = getpwuid(getuid());
 145        }
 146        else {
 147                if (slash) {
 148                        *slash = 0;
 149                        pw = getpwnam(path);
 150                        *slash = '/';
 151                }
 152                else
 153                        pw = getpwnam(path);
 154        }
 155        if (!pw || !pw->pw_dir || sz <= strlen(pw->pw_dir))
 156                return NULL;
 157        baselen = strlen(pw->pw_dir);
 158        memcpy(buf, pw->pw_dir, baselen);
 159        while ((1 < baselen) && (buf[baselen-1] == '/')) {
 160                buf[baselen-1] = 0;
 161                baselen--;
 162        }
 163        if (slash && slash[1]) {
 164                len = strlen(slash);
 165                if (sz <= baselen + len)
 166                        return NULL;
 167                memcpy(buf + baselen, slash, len + 1);
 168        }
 169        return buf;
 170}
 171
 172/*
 173 * First, one directory to try is determined by the following algorithm.
 174 *
 175 * (0) If "strict" is given, the path is used as given and no DWIM is
 176 *     done. Otherwise:
 177 * (1) "~/path" to mean path under the running user's home directory;
 178 * (2) "~user/path" to mean path under named user's home directory;
 179 * (3) "relative/path" to mean cwd relative directory; or
 180 * (4) "/absolute/path" to mean absolute directory.
 181 *
 182 * Unless "strict" is given, we try access() for existence of "%s.git/.git",
 183 * "%s/.git", "%s.git", "%s" in this order.  The first one that exists is
 184 * what we try.
 185 *
 186 * Second, we try chdir() to that.  Upon failure, we return NULL.
 187 *
 188 * Then, we try if the current directory is a valid git repository.
 189 * Upon failure, we return NULL.
 190 *
 191 * If all goes well, we return the directory we used to chdir() (but
 192 * before ~user is expanded), avoiding getcwd() resolving symbolic
 193 * links.  User relative paths are also returned as they are given,
 194 * except DWIM suffixing.
 195 */
 196char *enter_repo(char *path, int strict)
 197{
 198        static char used_path[PATH_MAX];
 199        static char validated_path[PATH_MAX];
 200
 201        if (!path)
 202                return NULL;
 203
 204        if (!strict) {
 205                static const char *suffix[] = {
 206                        ".git/.git", "/.git", ".git", "", NULL,
 207                };
 208                int len = strlen(path);
 209                int i;
 210                while ((1 < len) && (path[len-1] == '/')) {
 211                        path[len-1] = 0;
 212                        len--;
 213                }
 214                if (PATH_MAX <= len)
 215                        return NULL;
 216                if (path[0] == '~') {
 217                        if (!user_path(used_path, path, PATH_MAX))
 218                                return NULL;
 219                        strcpy(validated_path, path);
 220                        path = used_path;
 221                }
 222                else if (PATH_MAX - 10 < len)
 223                        return NULL;
 224                else {
 225                        path = strcpy(used_path, path);
 226                        strcpy(validated_path, path);
 227                }
 228                len = strlen(path);
 229                for (i = 0; suffix[i]; i++) {
 230                        strcpy(path + len, suffix[i]);
 231                        if (!access(path, F_OK)) {
 232                                strcat(validated_path, suffix[i]);
 233                                break;
 234                        }
 235                }
 236                if (!suffix[i] || chdir(path))
 237                        return NULL;
 238                path = validated_path;
 239        }
 240        else if (chdir(path))
 241                return NULL;
 242
 243        if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
 244            validate_symref("HEAD") == 0) {
 245                putenv("GIT_DIR=.");
 246                check_repository_format();
 247                return path;
 248        }
 249
 250        return NULL;
 251}
 252
 253int adjust_shared_perm(const char *path)
 254{
 255        struct stat st;
 256        int mode;
 257
 258        if (!shared_repository)
 259                return 0;
 260        if (lstat(path, &st) < 0)
 261                return -1;
 262        mode = st.st_mode;
 263        if (mode & S_IRUSR)
 264                mode |= (shared_repository == PERM_GROUP
 265                         ? S_IRGRP
 266                         : (shared_repository == PERM_EVERYBODY
 267                            ? (S_IRGRP|S_IROTH)
 268                            : 0));
 269
 270        if (mode & S_IWUSR)
 271                mode |= S_IWGRP;
 272
 273        if (mode & S_IXUSR)
 274                mode |= (shared_repository == PERM_GROUP
 275                         ? S_IXGRP
 276                         : (shared_repository == PERM_EVERYBODY
 277                            ? (S_IXGRP|S_IXOTH)
 278                            : 0));
 279        if (S_ISDIR(mode))
 280                mode |= S_ISGID;
 281        if ((mode & st.st_mode) != mode && chmod(path, mode) < 0)
 282                return -2;
 283        return 0;
 284}