path.c 3.82 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
/*
 * I'm tired of doing "vsnprintf()" etc just to open a
 * file, so here's a "return static buffer with printf"
 * interface for paths.
 *
 * It's obviously not thread-safe. Sue me. But it's quite
 * useful for doing things like
 *
 *   f = open(mkpath("%s/%s.git", base, name), O_RDONLY);
 *
 * which is what it's designed for.
 */
#include "cache.h"
14
#include <pwd.h>
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

static char pathname[PATH_MAX];
static char bad_path[] = "/bad-path/";

static char *cleanup_path(char *path)
{
	/* Clean it up */
	if (!memcmp(path, "./", 2)) {
		path += 2;
		while (*path == '/')
			path++;
	}
	return path;
}

char *mkpath(const char *fmt, ...)
{
	va_list args;
	unsigned len;

	va_start(args, fmt);
	len = vsnprintf(pathname, PATH_MAX, fmt, args);
	va_end(args);
	if (len >= PATH_MAX)
		return bad_path;
	return cleanup_path(pathname);
}

char *git_path(const char *fmt, ...)
{
45
	const char *git_dir = get_git_dir();
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
	va_list args;
	unsigned len;

	len = strlen(git_dir);
	if (len > PATH_MAX-100)
		return bad_path;
	memcpy(pathname, git_dir, len);
	if (len && git_dir[len-1] != '/')
		pathname[len++] = '/';
	va_start(args, fmt);
	len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
	va_end(args);
	if (len >= PATH_MAX)
		return bad_path;
	return cleanup_path(pathname);
}
62 63 64 65 66 67 68 69 70 71


/* git_mkstemp() - create tmp file honoring TMPDIR variable */
int git_mkstemp(char *path, size_t len, const char *template)
{
	char *env, *pch = path;

	if ((env = getenv("TMPDIR")) == NULL) {
		strcpy(pch, "/tmp/");
		len -= 5;
72 73 74 75 76 77 78
		pch += 5;
	} else {
		size_t n = snprintf(pch, len, "%s/", env);

		len -= n;
		pch += n;
	}
79 80 81 82 83 84 85 86 87 88 89 90 91 92

	safe_strncpy(pch, template, len);

	return mkstemp(path);
}


char *safe_strncpy(char *dest, const char *src, size_t n)
{
	strncpy(dest, src, n);
	dest[n - 1] = '\0';

	return dest;
}
93

94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
int validate_symref(const char *path)
{
	struct stat st;
	char *buf, buffer[256];
	int len, fd;

	if (lstat(path, &st) < 0)
		return -1;

	/* Make sure it is a "refs/.." symlink */
	if (S_ISLNK(st.st_mode)) {
		len = readlink(path, buffer, sizeof(buffer)-1);
		if (len >= 5 && !memcmp("refs/", buffer, 5))
			return 0;
		return -1;
	}

	/*
	 * Anything else, just open it and try to see if it is a symbolic ref.
	 */
	fd = open(path, O_RDONLY);
	if (fd < 0)
		return -1;
	len = read(fd, buffer, sizeof(buffer)-1);
	close(fd);

	/*
	 * Is it a symbolic ref?
	 */
	if (len < 4 || memcmp("ref:", buffer, 4))
		return -1;
	buf = buffer + 4;
	len -= 4;
	while (len && isspace(*buf))
		buf++, len--;
	if (len >= 5 && !memcmp("refs/", buf, 5))
		return 0;
	return -1;
}

Timo Hirvonen's avatar
Timo Hirvonen committed
134
static char *current_dir(void)
135 136 137 138
{
	return getcwd(pathname, sizeof(pathname));
}

139
static int user_chdir(char *path)
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
{
	char *dir = path;

	if(*dir == '~') {		/* user-relative path */
		struct passwd *pw;
		char *slash = strchr(dir, '/');

		dir++;
		/* '~/' and '~' (no slash) means users own home-dir */
		if(!*dir || *dir == '/')
			pw = getpwuid(getuid());
		else {
			if (slash) {
				*slash = '\0';
				pw = getpwnam(dir);
				*slash = '/';
			}
			else
				pw = getpwnam(dir);
		}

		/* make sure we got something back that we can chdir() to */
		if(!pw || chdir(pw->pw_dir) < 0)
163
			return -1;
164 165

		if(!slash || !slash[1]) /* no path following username */
166
			return 0;
167 168 169 170 171 172

		dir = slash + 1;
	}

	/* ~foo/path/to/repo is now path/to/repo and we're in foo's homedir */
	if(chdir(dir) < 0)
173
		return -1;
174

175
	return 0;
176 177 178 179 180 181 182
}

char *enter_repo(char *path, int strict)
{
	if(!path)
		return NULL;

183
	if (strict) {
184
		if (chdir(path) < 0)
185 186
			return NULL;
	}
187 188 189 190 191 192 193 194 195 196 197
	else {
		if (!*path)
			; /* happy -- no chdir */
		else if (!user_chdir(path))
			; /* happy -- as given */
		else if (!user_chdir(mkpath("%s.git", path)))
			; /* happy -- uemacs --> uemacs.git */
		else
			return NULL;
		(void)chdir(".git");
	}
198

199 200
	if(access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
	   validate_symref("HEAD") == 0) {
201 202 203 204 205 206
		putenv("GIT_DIR=.");
		return current_dir();
	}

	return NULL;
}