findfile.c 6.61 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3 4 5 6 7
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

8 9
#include "findfile.h"

10
#include "path_w32.h"
11
#include "utf-conv.h"
12
#include "fs_path.h"
13

14 15
#define REG_GITFORWINDOWS_KEY       L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
#define REG_GITFORWINDOWS_KEY_WOW64 L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
16

17
static int git_win32__expand_path(git_win32_path dest, const wchar_t *src)
18
{
19
	DWORD len = ExpandEnvironmentStringsW(src, dest, GIT_WIN_PATH_UTF16);
20

21
	if (!len || len > GIT_WIN_PATH_UTF16)
22
		return -1;
23

24
	return 0;
25 26
}

27
static int win32_path_to_8(git_str *dest, const wchar_t *src)
28
{
29
	git_win32_utf8_path utf8_path;
30

31
	if (git_win32_path_to_utf8(utf8_path, src) < 0) {
32
		git_error_set(GIT_ERROR_OS, "unable to convert path to UTF-8");
33
		return -1;
34 35
	}

36
	/* Convert backslashes to forward slashes */
37
	git_fs_path_mkposix(utf8_path);
38

39
	return git_str_sets(dest, utf8_path);
40 41
}

42 43
static git_win32_path mock_registry;
static bool mock_registry_set;
44

45
extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir)
46
{
47 48 49 50 51 52 53 54 55
	if (!mock_sysdir) {
		mock_registry[0] = L'\0';
		mock_registry_set = false;
	} else {
		size_t len = wcslen(mock_sysdir);

		if (len > GIT_WIN_PATH_MAX) {
			git_error_set(GIT_ERROR_INVALID, "mock path too long");
			return -1;
56
		}
57 58 59

		wcscpy(mock_registry, mock_sysdir);
		mock_registry_set = true;
60 61 62 63 64
	}

	return 0;
}

65 66 67 68 69
static int lookup_registry_key(
	git_win32_path out,
	const HKEY hive,
	const wchar_t* key,
	const wchar_t *value)
70
{
71 72 73
	HKEY hkey;
	DWORD type, size;
	int error = GIT_ENOTFOUND;
74

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
	/*
	 * Registry data may not be NUL terminated, provide room to do
	 * it ourselves.
	 */
	size = (DWORD)((sizeof(git_win32_path) - 1) * sizeof(wchar_t));

	if (RegOpenKeyExW(hive, key, 0, KEY_READ, &hkey) != 0)
		return GIT_ENOTFOUND;

	if (RegQueryValueExW(hkey, value, NULL, &type, (LPBYTE)out, &size) == 0 &&
	    type == REG_SZ &&
	    size > 0 &&
	    size < sizeof(git_win32_path)) {
		size_t wsize = size / sizeof(wchar_t);
		size_t len = wsize - 1;

		if (out[wsize - 1] != L'\0') {
			len = wsize;
			out[wsize] = L'\0';
		}
95

96 97
		if (out[len - 1] == L'\\')
			out[len - 1] = L'\0';
98

99 100 101
		if (_waccess(out, F_OK) == 0)
			error = 0;
	}
102

103 104 105
	RegCloseKey(hkey);
	return error;
}
106

107 108 109 110 111
static int find_sysdir_in_registry(git_win32_path out)
{
	if (mock_registry_set) {
		if (mock_registry[0] == L'\0')
			return GIT_ENOTFOUND;
112

113 114
		wcscpy(out, mock_registry);
		return 0;
115
	}
116

117 118 119 120 121 122 123
	if (lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 ||
	    lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0 ||
	    lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 ||
	    lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0)
		return 0;

    return GIT_ENOTFOUND;
124 125
}

126
static int find_sysdir_in_path(git_win32_path out)
127
{
128
	size_t out_len;
129

130 131 132
	if (git_win32_path_find_executable(out, L"git.exe") < 0 &&
	    git_win32_path_find_executable(out, L"git.cmd") < 0)
		return GIT_ENOTFOUND;
133

134
	out_len = wcslen(out);
135

136 137 138
	/* Trim the file name */
	if (out_len <= CONST_STRLEN(L"git.exe"))
		return GIT_ENOTFOUND;
139

140
	out_len -= CONST_STRLEN(L"git.exe");
141

142 143
	if (out_len && out[out_len - 1] == L'\\')
		out_len--;
144

145 146 147 148 149 150 151 152 153 154
	/*
	 * Git for Windows usually places the command in a 'bin' or
	 * 'cmd' directory, trim that.
	 */
	if (out_len >= CONST_STRLEN(L"\\bin") &&
	    wcsncmp(&out[out_len - CONST_STRLEN(L"\\bin")], L"\\bin", CONST_STRLEN(L"\\bin")) == 0)
		out_len -= CONST_STRLEN(L"\\bin");
	else if (out_len >= CONST_STRLEN(L"\\cmd") &&
	         wcsncmp(&out[out_len - CONST_STRLEN(L"\\cmd")], L"\\cmd", CONST_STRLEN(L"\\cmd")) == 0)
		out_len -= CONST_STRLEN(L"\\cmd");
155

156 157 158 159 160
	if (!out_len)
		return GIT_ENOTFOUND;

	out[out_len] = L'\0';
	return 0;
161 162
}

163
static int win32_find_existing_dirs(
164 165
    git_str* out,
    const wchar_t* tmpl[])
166
{
167
	git_win32_path path16;
168
	git_str buf = GIT_STR_INIT;
169

170
	git_str_clear(out);
171 172

	for (; *tmpl != NULL; tmpl++) {
173
		if (!git_win32__expand_path(path16, *tmpl) &&
174 175
		    path16[0] != L'%' &&
		    !_waccess(path16, F_OK)) {
176
			win32_path_to_8(&buf, path16);
177 178

			if (buf.size)
179
				git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
180 181 182
		}
	}

183
	git_str_dispose(&buf);
184

185
	return (git_str_oom(out) ? -1 : 0);
186
}
187

188
static int append_subdir(git_str *out, git_str *path, const char *subdir)
189
{
190 191 192 193 194 195 196 197
	static const char* architecture_roots[] = {
		"",
		"mingw64",
		"mingw32",
		NULL
	};
	const char **root;
	size_t orig_path_len = path->size;
198

199 200 201 202
	for (root = architecture_roots; *root; root++) {
		if ((*root[0] && git_str_joinpath(path, path->ptr, *root) < 0) ||
		    git_str_joinpath(path, path->ptr, subdir) < 0)
			return -1;
203

204 205 206
		if (git_fs_path_exists(path->ptr) &&
		    git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, path->ptr) < 0)
			return -1;
207

208 209
		git_str_truncate(path, orig_path_len);
	}
210

211
	return 0;
212 213
}

214
int git_win32__find_system_dirs(git_str *out, const char *subdir)
215
{
216 217 218
	git_win32_path pathdir, regdir;
	git_str path8 = GIT_STR_INIT;
	bool has_pathdir, has_regdir;
219 220
	int error;

221 222
	has_pathdir = (find_sysdir_in_path(pathdir) == 0);
	has_regdir = (find_sysdir_in_registry(regdir) == 0);
223

224
	if (!has_pathdir && !has_regdir)
225
		return 0;
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248

	/*
	 * Usually the git in the path is the same git in the registry,
	 * in this case there's no need to duplicate the paths.
	 */
	if (has_pathdir && has_regdir && wcscmp(pathdir, regdir) == 0)
		has_regdir = false;

	if (has_pathdir) {
		if ((error = win32_path_to_8(&path8, pathdir)) < 0 ||
		    (error = append_subdir(out, &path8, subdir)) < 0)
			goto done;
	}

	if (has_regdir) {
		if ((error = win32_path_to_8(&path8, regdir)) < 0 ||
		    (error = append_subdir(out, &path8, subdir)) < 0)
			goto done;
	}

done:
    git_str_dispose(&path8);
    return error;
249 250
}

251
int git_win32__find_global_dirs(git_str *out)
252 253 254 255 256 257 258 259
{
	static const wchar_t *global_tmpls[4] = {
		L"%HOME%\\",
		L"%HOMEDRIVE%%HOMEPATH%\\",
		L"%USERPROFILE%\\",
		NULL,
	};

nulltoken committed
260
	return win32_find_existing_dirs(out, global_tmpls);
261 262
}

263
int git_win32__find_xdg_dirs(git_str *out)
264 265 266 267 268 269 270 271 272 273 274
{
	static const wchar_t *global_tmpls[7] = {
		L"%XDG_CONFIG_HOME%\\git",
		L"%APPDATA%\\git",
		L"%LOCALAPPDATA%\\git",
		L"%HOME%\\.config\\git",
		L"%HOMEDRIVE%%HOMEPATH%\\.config\\git",
		L"%USERPROFILE%\\.config\\git",
		NULL,
	};

nulltoken committed
275
	return win32_find_existing_dirs(out, global_tmpls);
276
}
277

278
int git_win32__find_programdata_dirs(git_str *out)
279 280 281 282 283 284 285 286
{
	static const wchar_t *programdata_tmpls[2] = {
		L"%PROGRAMDATA%\\Git",
		NULL,
	};

	return win32_find_existing_dirs(out, programdata_tmpls);
}