1 /*
2  * Copyright (C) the libgit2 contributors. All rights reserved.
3  *
4  * This file is part of libgit2, distributed under the GNU GPL v2 with
5  * a Linking Exception. For full terms see the included COPYING file.
6  */
7 
8 #include "findfile.h"
9 
10 #include "path_w32.h"
11 #include "utf-conv.h"
12 #include "path.h"
13 
14 #define REG_MSYSGIT_INSTALL_LOCAL L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
15 
16 #ifndef _WIN64
17 #define REG_MSYSGIT_INSTALL REG_MSYSGIT_INSTALL_LOCAL
18 #else
19 #define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
20 #endif
21 
22 typedef struct {
23 	git_win32_path path;
24 	DWORD len;
25 } _findfile_path;
26 
git_win32__expand_path(_findfile_path * dest,const wchar_t * src)27 static int git_win32__expand_path(_findfile_path *dest, const wchar_t *src)
28 {
29 	dest->len = ExpandEnvironmentStringsW(src, dest->path, ARRAY_SIZE(dest->path));
30 
31 	if (!dest->len || dest->len > ARRAY_SIZE(dest->path))
32 		return -1;
33 
34 	return 0;
35 }
36 
win32_path_to_8(git_buf * dest,const wchar_t * src)37 static int win32_path_to_8(git_buf *dest, const wchar_t *src)
38 {
39 	git_win32_utf8_path utf8_path;
40 
41 	if (git_win32_path_to_utf8(utf8_path, src) < 0) {
42 		git_error_set(GIT_ERROR_OS, "unable to convert path to UTF-8");
43 		return -1;
44 	}
45 
46 	/* Convert backslashes to forward slashes */
47 	git_path_mkposix(utf8_path);
48 
49 	return git_buf_sets(dest, utf8_path);
50 }
51 
win32_walkpath(wchar_t * path,wchar_t * buf,size_t buflen)52 static wchar_t* win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen)
53 {
54 	wchar_t term, *base = path;
55 
56 	GIT_ASSERT_ARG_WITH_RETVAL(path, NULL);
57 	GIT_ASSERT_ARG_WITH_RETVAL(buf, NULL);
58 	GIT_ASSERT_ARG_WITH_RETVAL(buflen, NULL);
59 
60 	term = (*path == L'"') ? *path++ : L';';
61 
62 	for (buflen--; *path && *path != term && buflen; buflen--)
63 		*buf++ = *path++;
64 
65 	*buf = L'\0'; /* reserved a byte via initial subtract */
66 
67 	while (*path == term || *path == L';')
68 		path++;
69 
70 	return (path != base) ? path : NULL;
71 }
72 
win32_find_git_in_path(git_buf * buf,const wchar_t * gitexe,const wchar_t * subdir)73 static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe, const wchar_t *subdir)
74 {
75 	wchar_t *env = _wgetenv(L"PATH"), lastch;
76 	_findfile_path root;
77 	size_t gitexe_len = wcslen(gitexe);
78 
79 	if (!env)
80 		return -1;
81 
82 	while ((env = win32_walkpath(env, root.path, MAX_PATH-1)) && *root.path) {
83 		root.len = (DWORD)wcslen(root.path);
84 		lastch = root.path[root.len - 1];
85 
86 		/* ensure trailing slash (MAX_PATH-1 to walkpath guarantees space) */
87 		if (lastch != L'/' && lastch != L'\\') {
88 			root.path[root.len++] = L'\\';
89 			root.path[root.len]   = L'\0';
90 		}
91 
92 		if (root.len + gitexe_len >= MAX_PATH)
93 			continue;
94 		wcscpy(&root.path[root.len], gitexe);
95 
96 		if (_waccess(root.path, F_OK) == 0 && root.len > 5) {
97 			/* replace "bin\\" or "cmd\\" with subdir */
98 			wcscpy(&root.path[root.len - 4], subdir);
99 
100 			win32_path_to_8(buf, root.path);
101 			return 0;
102 		}
103 	}
104 
105 	return GIT_ENOTFOUND;
106 }
107 
win32_find_git_in_registry(git_buf * buf,const HKEY hive,const wchar_t * key,const wchar_t * subdir)108 static int win32_find_git_in_registry(
109 	git_buf *buf, const HKEY hive, const wchar_t *key, const wchar_t *subdir)
110 {
111 	HKEY hKey;
112 	int error = GIT_ENOTFOUND;
113 
114 	GIT_ASSERT_ARG(buf);
115 
116 	if (!RegOpenKeyExW(hive, key, 0, KEY_READ, &hKey)) {
117 		DWORD dwType, cbData;
118 		git_win32_path path;
119 
120 		/* Ensure that the buffer is big enough to have the suffix attached
121 		 * after we receive the result. */
122 		cbData = (DWORD)(sizeof(path) - wcslen(subdir) * sizeof(wchar_t));
123 
124 		/* InstallLocation points to the root of the git directory */
125 		if (!RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType, (LPBYTE)path, &cbData) &&
126 			dwType == REG_SZ) {
127 
128 			/* Append the suffix */
129 			wcscat(path, subdir);
130 
131 			/* Convert to UTF-8, with forward slashes, and output the path
132 			 * to the provided buffer */
133 			if (!win32_path_to_8(buf, path))
134 				error = 0;
135 		}
136 
137 		RegCloseKey(hKey);
138 	}
139 
140 	return error;
141 }
142 
win32_find_existing_dirs(git_buf * out,const wchar_t * tmpl[])143 static int win32_find_existing_dirs(
144 	git_buf *out, const wchar_t *tmpl[])
145 {
146 	_findfile_path path16;
147 	git_buf buf = GIT_BUF_INIT;
148 
149 	git_buf_clear(out);
150 
151 	for (; *tmpl != NULL; tmpl++) {
152 		if (!git_win32__expand_path(&path16, *tmpl) &&
153 			path16.path[0] != L'%' &&
154 			!_waccess(path16.path, F_OK))
155 		{
156 			win32_path_to_8(&buf, path16.path);
157 
158 			if (buf.size)
159 				git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
160 		}
161 	}
162 
163 	git_buf_dispose(&buf);
164 
165 	return (git_buf_oom(out) ? -1 : 0);
166 }
167 
git_win32__find_system_dirs(git_buf * out,const wchar_t * subdir)168 int git_win32__find_system_dirs(git_buf *out, const wchar_t *subdir)
169 {
170 	git_buf buf = GIT_BUF_INIT;
171 
172 	/* directories where git.exe & git.cmd are found */
173 	if (!win32_find_git_in_path(&buf, L"git.exe", subdir) && buf.size)
174 		git_buf_set(out, buf.ptr, buf.size);
175 	else
176 		git_buf_clear(out);
177 
178 	if (!win32_find_git_in_path(&buf, L"git.cmd", subdir) && buf.size)
179 		git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
180 
181 	/* directories where git is installed according to registry */
182 	if (!win32_find_git_in_registry(
183 			&buf, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL, subdir) && buf.size)
184 		git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
185 
186 	if (!win32_find_git_in_registry(
187 			&buf, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL, subdir) && buf.size)
188 		git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
189 
190 	git_buf_dispose(&buf);
191 
192 	return (git_buf_oom(out) ? -1 : 0);
193 }
194 
git_win32__find_global_dirs(git_buf * out)195 int git_win32__find_global_dirs(git_buf *out)
196 {
197 	static const wchar_t *global_tmpls[4] = {
198 		L"%HOME%\\",
199 		L"%HOMEDRIVE%%HOMEPATH%\\",
200 		L"%USERPROFILE%\\",
201 		NULL,
202 	};
203 
204 	return win32_find_existing_dirs(out, global_tmpls);
205 }
206 
git_win32__find_xdg_dirs(git_buf * out)207 int git_win32__find_xdg_dirs(git_buf *out)
208 {
209 	static const wchar_t *global_tmpls[7] = {
210 		L"%XDG_CONFIG_HOME%\\git",
211 		L"%APPDATA%\\git",
212 		L"%LOCALAPPDATA%\\git",
213 		L"%HOME%\\.config\\git",
214 		L"%HOMEDRIVE%%HOMEPATH%\\.config\\git",
215 		L"%USERPROFILE%\\.config\\git",
216 		NULL,
217 	};
218 
219 	return win32_find_existing_dirs(out, global_tmpls);
220 }
221 
git_win32__find_programdata_dirs(git_buf * out)222 int git_win32__find_programdata_dirs(git_buf *out)
223 {
224 	static const wchar_t *programdata_tmpls[2] = {
225 		L"%PROGRAMDATA%\\Git",
226 		NULL,
227 	};
228 
229 	return win32_find_existing_dirs(out, programdata_tmpls);
230 }
231