<commit_before>/*
 * Copyright (C) 2009-2012 the libgit2 contributors
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
#include "common.h"
#include "fileops.h"
#include <ctype.h>

int git_futils_mkpath2file(const char *file_path, const mode_t mode)
{
	return git_futils_mkdir(
		file_path, NULL, mode, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST);
}

int git_futils_mktmp(git_buf *path_out, const char *filename)
{
	int fd;

	git_buf_sets(path_out, filename);
	git_buf_puts(path_out, "_git2_XXXXXX");

	if (git_buf_oom(path_out))
		return -1;

	if ((fd = p_mkstemp(path_out->ptr)) < 0) {
		giterr_set(GITERR_OS,
			"Failed to create temporary file '%s'", path_out->ptr);
		return -1;
	}

	return fd;
}

int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode)
{
	int fd;

	if (git_futils_mkpath2file(path, dirmode) < 0)
		return -1;

	fd = p_creat(path, mode);
	if (fd < 0) {
		giterr_set(GITERR_OS, "Failed to create file '%s'", path);
		return -1;
	}

	return fd;
}

int git_futils_creat_locked(const char *path, const mode_t mode)
{
	int fd;

#ifdef GIT_WIN32
	wchar_t buf[GIT_WIN_PATH];

	git__utf8_to_16(buf, GIT_WIN_PATH, path);
	fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
#else
	fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
#endif

	if (fd < 0) {
		giterr_set(GITERR_OS, "Failed to create locked file '%s'", path);
		return -1;
	}

	return fd;
}

int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode)
{
	if (git_futils_mkpath2file(path, dirmode) < 0)
		return -1;

	return git_futils_creat_locked(path, mode);
}

int git_futils_open_ro(const char *path)
{
	int fd = p_open(path, O_RDONLY);
	if (fd < 0) {
		if (errno == ENOENT || errno == ENOTDIR)
			fd = GIT_ENOTFOUND;
		giterr_set(GITERR_OS, "Failed to open '%s'", path);
	}
	return fd;
}

git_off_t git_futils_filesize(git_file fd)
{
	struct stat sb;

	if (p_fstat(fd, &sb)) {
		giterr_set(GITERR_OS, "Failed to stat file descriptor");
		return -1;
	}

	return sb.st_size;
}

mode_t git_futils_canonical_mode(mode_t raw_mode)
{
	if (S_ISREG(raw_mode))
		return S_IFREG | GIT_CANONICAL_PERMS(raw_mode);
	else if (S_ISLNK(raw_mode))
		return S_IFLNK;
	else if (S_ISGITLINK(raw_mode))
		return S_IFGITLINK;
	else if (S_ISDIR(raw_mode))
		return S_IFDIR;
	else
		return 0;
}

int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
{
	ssize_t read_size;

	git_buf_clear(buf);

	if (git_buf_grow(buf, len + 1) < 0)
		return -1;

	/* p_read loops internally to read len bytes */
	read_size = p_read(fd, buf->ptr, len);

	if (read_size != (ssize_t)len) {
		giterr_set(GITERR_OS, "Failed to read descriptor");
		return -1;
	}

	buf->ptr[read_size] = '\0';
	buf->size = read_size;

	return 0;
}

int git_futils_readbuffer_updated(
	git_buf *buf, const char *path, time_t *mtime, int *updated)
{
	git_file fd;
	struct stat st;

	assert(buf && path && *path);

	if (updated != NULL)
		*updated = 0;

	if ((fd = git_futils_open_ro(path)) < 0)
		return fd;

	if (p_fstat(fd, &st) < 0 || S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) {
		p_close(fd);
		giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path);
		return -1;
	}

	/*
	 * If we were given a time, we only want to read the file if it
	 * has been modified.
	 */
	if (mtime != NULL && *mtime >= st.st_mtime) {
		p_close(fd);
		return 0;
	}

	if (mtime != NULL)
		*mtime = st.st_mtime;

	if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) {
		p_close(fd);
		return -1;
	}

	p_close(fd);

	if (updated != NULL)
		*updated = 1;

	return 0;
}

int git_futils_readbuffer(git_buf *buf, const char *path)
{
	return git_futils_readbuffer_updated(buf, path, NULL, NULL);
}

int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
{
	if (git_futils_mkpath2file(to, dirmode) < 0)
		return -1;

	if (p_rename(from, to) < 0) {
		giterr_set(GITERR_OS, "Failed to rename '%s' to '%s'", from, to);
		return -1;
	}

	return 0;
}

int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
{
	return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
}

int git_futils_mmap_ro_file(git_map *out, const char *path)
{
	git_file fd = git_futils_open_ro(path);
	git_off_t len;
	int result;

	if (fd < 0)
		return fd;

	len = git_futils_filesize(fd);
	if (!git__is_sizet(len)) {
		giterr_set(GITERR_OS, "File `%s` too large to mmap", path);
		return -1;
	}

	result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
	p_close(fd);
	return result;
}

void git_futils_mmap_free(git_map *out)
{
	p_munmap(out);
}

int git_futils_mkdir(
	const char *path,
	const char *base,
	mode_t mode,
	uint32_t flags)
{
	git_buf make_path = GIT_BUF_INIT;
	ssize_t root = 0;
	char lastch, *tail;

	/* build path and find "root" where we should start calling mkdir */
	if (git_path_join_unrooted(&make_path, path, base, &root) < 0)
		return -1;

	if (make_path.size == 0) {
		giterr_set(GITERR_OS, "Attempt to create empty path");
		goto fail;
	}

	/* remove trailing slashes on path */
	while (make_path.ptr[make_path.size - 1] == '/') {
		make_path.size--;
		make_path.ptr[make_path.size] = '\0';
	}

	/* if we are not supposed to made the last element, truncate it */
	if ((flags & GIT_MKDIR_SKIP_LAST) != 0)
		git_buf_rtruncate_at_char(&make_path, '/');

	/* if we are not supposed to make the whole path, reset root */
	if ((flags & GIT_MKDIR_PATH) == 0)
		root = git_buf_rfind(&make_path, '/');

	/* clip root to make_path length */
	if (root >= (ssize_t)make_path.size)
		root = (ssize_t)make_path.size - 1;
	if (root < 0)
		root = 0;

	tail = & make_path.ptr[root];

	while (*tail) {
		/* advance tail to include next path component */
		while (*tail == '/')
			tail++;
		while (*tail && *tail != '/')
			tail++;

		/* truncate path at next component */
		lastch = *tail;
		*tail = '\0';

		/* make directory */
		if (p_mkdir(make_path.ptr, mode) < 0 &&
			(errno != EEXIST || (flags & GIT_MKDIR_EXCL) != 0))
		{
			giterr_set(GITERR_OS, "Failed to make directory '%s'",
				make_path.ptr);
			goto fail;
		}

		/* chmod if requested */
		if ((flags & GIT_MKDIR_CHMOD_PATH) != 0 ||
			((flags & GIT_MKDIR_CHMOD) != 0 && lastch == '\0'))
		{
			if (p_chmod(make_path.ptr, mode) < 0) {
				giterr_set(GITERR_OS, "Failed to set permissions on '%s'",
					make_path.ptr);
				goto fail;
			}
		}

		*tail = lastch;
	}

	git_buf_free(&make_path);
	return 0;

fail:
	git_buf_free(&make_path);
	return -1;
}

int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
{
	return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH);
}

static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
{
	git_directory_removal_type removal_type = *(git_directory_removal_type *)opaque;

	assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY
		|| removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS
		|| removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS);

	if (git_path_isdir(path->ptr) == true) {
		if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0)
			return -1;

		if (p_rmdir(path->ptr) < 0) {
			if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && (errno == ENOTEMPTY || errno == EEXIST))
				return 0;

			giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr);
			return -1;
		}

		return 0;
	}

	if (removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS) {
		if (p_unlink(path->ptr) < 0) {
			giterr_set(GITERR_OS, "Could not remove directory.  File '%s' cannot be removed", path->ptr);
			return -1;
		}

		return 0;
	}

	if (removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY) {
		giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr);
		return -1;
	}

	return 0;
}

int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type)
{
	int error;
	git_buf p = GIT_BUF_INIT;

	error = git_buf_sets(&p, path);
	if (!error)
		error = _rmdir_recurs_foreach(&removal_type, &p);
	git_buf_free(&p);
	return error;
}

#ifdef GIT_WIN32
struct win32_path {
	wchar_t path[MAX_PATH];
	DWORD len;
};

static int win32_expand_path(struct win32_path *s_root, const wchar_t *templ)
{
	s_root->len = ExpandEnvironmentStringsW(templ, s_root->path, MAX_PATH);
	return s_root->len ? 0 : -1;
}

static int win32_find_file(git_buf *path, const struct win32_path *root, const char *filename)
{
	size_t len, alloc_len;
	wchar_t *file_utf16 = NULL;
	char file_utf8[GIT_PATH_MAX];

	if (!root || !filename || (len = strlen(filename)) == 0)
		return GIT_ENOTFOUND;

	/* allocate space for wchar_t path to file */
	alloc_len = root->len + len + 2;
	file_utf16 = git__calloc(alloc_len, sizeof(wchar_t));
	GITERR_CHECK_ALLOC(file_utf16);

	/* append root + '\\' + filename as wchar_t */
	memcpy(file_utf16, root->path, root->len * sizeof(wchar_t));

	if (*filename == '/' || *filename == '\\')
		filename++;

	git__utf8_to_16(file_utf16 + root->len - 1, alloc_len, filename);

	/* check access */
	if (_waccess(file_utf16, F_OK) < 0) {
		git__free(file_utf16);
		return GIT_ENOTFOUND;
	}

	git__utf16_to_8(file_utf8, file_utf16);
	git_path_mkposix(file_utf8);
	git_buf_sets(path, file_utf8);

	git__free(file_utf16);
	return 0;
}

static wchar_t * nextpath(wchar_t * src, wchar_t * dst, size_t maxlen)
{
	wchar_t * orgsrc;

	while (*src == L';')
		src++;

	orgsrc = src;

	if (!--maxlen)
		goto nullterm;

	while (*src && *src != L';')
	{
		if (*src != L'"')
		{
			*dst++ = *src++;
			if (!--maxlen)
			{
				orgsrc = src;
				goto nullterm;
			}
		}
		else
		{
			src++;
			while (*src && *src != L'"')
			{
				*dst++ = *src++;
				if (!--maxlen)
				{
					orgsrc = src;
					goto nullterm;
				}
			}

			if (*src)
				src++;
		}
	}

	while (*src == L';')
		src++;

nullterm:
	*dst = 0;

	return (orgsrc != src) ? (wchar_t *)src : NULL;
}

int find_system_file_using_path(git_buf *path, const char *filename)
{
	wchar_t * env = NULL;
	struct win32_path root;

	env = _wgetenv(L"PATH");
	if (!env)
		return -1;

	// search in all paths defined in PATH
	while ((env = nextpath(env, root.path, MAX_PATH - 1)) != NULL && *root.path)
	{
		wchar_t * pfin = root.path + wcslen(root.path) - 1; // last char of the current path entry

		// ensure trailing slash
		if (*pfin != L'/' && *pfin != L'\\')
			wcscpy(++pfin, L"\\"); // we have enough space left, MAX_PATH - 1 is used in nextpath above

		root.len = (DWORD)wcslen(root.path) + 1;

		if (win32_find_file(path, &root, "git.cmd") == 0 || win32_find_file(path, &root, "git.exe") == 0) {
			// we found the cmd or bin directory of a git installaton
			if (root.len > 5) {
				wcscpy(root.path + wcslen(root.path) - 4, L"etc\\");
				if (win32_find_file(path, &root, filename) == 0)
					return 0;
			}
		}
	}
	
	return GIT_ENOTFOUND;
}

int find_system_file_using_registry(git_buf *path, const char *filename)
{
#ifndef _WIN64
#define REG_MSYSGIT_INSTALL L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
#else
#define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
#endif

	struct win32_path root;

	HKEY hKey;
	DWORD dwType = REG_SZ;
	DWORD dwSize = MAX_PATH;

	root.len = 0;
	if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS)
	{
		if (RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType,(LPBYTE)&root.path, &dwSize) == ERROR_SUCCESS)
		{
			// InstallLocation points to the root of the msysgit directory
			if (dwSize + 4 > MAX_PATH) // 4 = wcslen(L"etc\\")
			{
				giterr_set(GITERR_OS, "Cannot locate the system's msysgit directory - path too long");
				return -1;
			}
			wcscat(root.path, L"etc\\");
			root.len = (DWORD)wcslen(root.path) + 1;
		}
	}
	RegCloseKey(hKey);

	if (!root.len) {
		giterr_set(GITERR_OS, "Cannot locate the system's msysgit directory");
		return -1;
	}

	if (win32_find_file(path, &root, filename) < 0) {
		giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename);
		git_buf_clear(path);
		return GIT_ENOTFOUND;
	}

	return 0;
}
#endif

int git_futils_find_system_file(git_buf *path, const char *filename)
{
#ifdef GIT_WIN32
	// try to find git.exe/git.cmd on path
	if (!find_system_file_using_path(path, filename))
		return 0;

	// try to find msysgit installation path using registry
	if (!find_system_file_using_registry(path, filename))
		return 0;
#else
	if (git_buf_joinpath(path, "/etc", filename) < 0)
		return -1;

	if (git_path_exists(path->ptr) == true)
		return 0;
#endif

	git_buf_clear(path);
	giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename);
	return GIT_ENOTFOUND;
}

int git_futils_find_global_file(git_buf *path, const char *filename)
{
	const char *home = getenv("HOME");

#ifdef GIT_WIN32
	struct win32_path root;

	if (home != NULL) {
		if (git_buf_joinpath(path, home, filename) < 0)
			return -1;

		if (git_path_exists(path->ptr)) {
			return 0;
		}
	}

	if (getenv("HOMEPATH") != NULL) {<commit_msg>Added win32_ prefix for Win32-only methods<commit_after>/*
 * Copyright (C) 2009-2012 the libgit2 contributors
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
#include "common.h"
#include "fileops.h"
#include <ctype.h>

int git_futils_mkpath2file(const char *file_path, const mode_t mode)
{
	return git_futils_mkdir(
		file_path, NULL, mode, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST);
}

int git_futils_mktmp(git_buf *path_out, const char *filename)
{
	int fd;

	git_buf_sets(path_out, filename);
	git_buf_puts(path_out, "_git2_XXXXXX");

	if (git_buf_oom(path_out))
		return -1;

	if ((fd = p_mkstemp(path_out->ptr)) < 0) {
		giterr_set(GITERR_OS,
			"Failed to create temporary file '%s'", path_out->ptr);
		return -1;
	}

	return fd;
}

int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode)
{
	int fd;

	if (git_futils_mkpath2file(path, dirmode) < 0)
		return -1;

	fd = p_creat(path, mode);
	if (fd < 0) {
		giterr_set(GITERR_OS, "Failed to create file '%s'", path);
		return -1;
	}

	return fd;
}

int git_futils_creat_locked(const char *path, const mode_t mode)
{
	int fd;

#ifdef GIT_WIN32
	wchar_t buf[GIT_WIN_PATH];

	git__utf8_to_16(buf, GIT_WIN_PATH, path);
	fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
#else
	fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
#endif

	if (fd < 0) {
		giterr_set(GITERR_OS, "Failed to create locked file '%s'", path);
		return -1;
	}

	return fd;
}

int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode)
{
	if (git_futils_mkpath2file(path, dirmode) < 0)
		return -1;

	return git_futils_creat_locked(path, mode);
}

int git_futils_open_ro(const char *path)
{
	int fd = p_open(path, O_RDONLY);
	if (fd < 0) {
		if (errno == ENOENT || errno == ENOTDIR)
			fd = GIT_ENOTFOUND;
		giterr_set(GITERR_OS, "Failed to open '%s'", path);
	}
	return fd;
}

git_off_t git_futils_filesize(git_file fd)
{
	struct stat sb;

	if (p_fstat(fd, &sb)) {
		giterr_set(GITERR_OS, "Failed to stat file descriptor");
		return -1;
	}

	return sb.st_size;
}

mode_t git_futils_canonical_mode(mode_t raw_mode)
{
	if (S_ISREG(raw_mode))
		return S_IFREG | GIT_CANONICAL_PERMS(raw_mode);
	else if (S_ISLNK(raw_mode))
		return S_IFLNK;
	else if (S_ISGITLINK(raw_mode))
		return S_IFGITLINK;
	else if (S_ISDIR(raw_mode))
		return S_IFDIR;
	else
		return 0;
}

int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
{
	ssize_t read_size;

	git_buf_clear(buf);

	if (git_buf_grow(buf, len + 1) < 0)
		return -1;

	/* p_read loops internally to read len bytes */
	read_size = p_read(fd, buf->ptr, len);

	if (read_size != (ssize_t)len) {
		giterr_set(GITERR_OS, "Failed to read descriptor");
		return -1;
	}

	buf->ptr[read_size] = '\0';
	buf->size = read_size;

	return 0;
}

int git_futils_readbuffer_updated(
	git_buf *buf, const char *path, time_t *mtime, int *updated)
{
	git_file fd;
	struct stat st;

	assert(buf && path && *path);

	if (updated != NULL)
		*updated = 0;

	if ((fd = git_futils_open_ro(path)) < 0)
		return fd;

	if (p_fstat(fd, &st) < 0 || S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) {
		p_close(fd);
		giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path);
		return -1;
	}

	/*
	 * If we were given a time, we only want to read the file if it
	 * has been modified.
	 */
	if (mtime != NULL && *mtime >= st.st_mtime) {
		p_close(fd);
		return 0;
	}

	if (mtime != NULL)
		*mtime = st.st_mtime;

	if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) {
		p_close(fd);
		return -1;
	}

	p_close(fd);

	if (updated != NULL)
		*updated = 1;

	return 0;
}

int git_futils_readbuffer(git_buf *buf, const char *path)
{
	return git_futils_readbuffer_updated(buf, path, NULL, NULL);
}

int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
{
	if (git_futils_mkpath2file(to, dirmode) < 0)
		return -1;

	if (p_rename(from, to) < 0) {
		giterr_set(GITERR_OS, "Failed to rename '%s' to '%s'", from, to);
		return -1;
	}

	return 0;
}

int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
{
	return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
}

int git_futils_mmap_ro_file(git_map *out, const char *path)
{
	git_file fd = git_futils_open_ro(path);
	git_off_t len;
	int result;

	if (fd < 0)
		return fd;

	len = git_futils_filesize(fd);
	if (!git__is_sizet(len)) {
		giterr_set(GITERR_OS, "File `%s` too large to mmap", path);
		return -1;
	}

	result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
	p_close(fd);
	return result;
}

void git_futils_mmap_free(git_map *out)
{
	p_munmap(out);
}

int git_futils_mkdir(
	const char *path,
	const char *base,
	mode_t mode,
	uint32_t flags)
{
	git_buf make_path = GIT_BUF_INIT;
	ssize_t root = 0;
	char lastch, *tail;

	/* build path and find "root" where we should start calling mkdir */
	if (git_path_join_unrooted(&make_path, path, base, &root) < 0)
		return -1;

	if (make_path.size == 0) {
		giterr_set(GITERR_OS, "Attempt to create empty path");
		goto fail;
	}

	/* remove trailing slashes on path */
	while (make_path.ptr[make_path.size - 1] == '/') {
		make_path.size--;
		make_path.ptr[make_path.size] = '\0';
	}

	/* if we are not supposed to made the last element, truncate it */
	if ((flags & GIT_MKDIR_SKIP_LAST) != 0)
		git_buf_rtruncate_at_char(&make_path, '/');

	/* if we are not supposed to make the whole path, reset root */
	if ((flags & GIT_MKDIR_PATH) == 0)
		root = git_buf_rfind(&make_path, '/');

	/* clip root to make_path length */
	if (root >= (ssize_t)make_path.size)
		root = (ssize_t)make_path.size - 1;
	if (root < 0)
		root = 0;

	tail = & make_path.ptr[root];

	while (*tail) {
		/* advance tail to include next path component */
		while (*tail == '/')
			tail++;
		while (*tail && *tail != '/')
			tail++;

		/* truncate path at next component */
		lastch = *tail;
		*tail = '\0';

		/* make directory */
		if (p_mkdir(make_path.ptr, mode) < 0 &&
			(errno != EEXIST || (flags & GIT_MKDIR_EXCL) != 0))
		{
			giterr_set(GITERR_OS, "Failed to make directory '%s'",
				make_path.ptr);
			goto fail;
		}

		/* chmod if requested */
		if ((flags & GIT_MKDIR_CHMOD_PATH) != 0 ||
			((flags & GIT_MKDIR_CHMOD) != 0 && lastch == '\0'))
		{
			if (p_chmod(make_path.ptr, mode) < 0) {
				giterr_set(GITERR_OS, "Failed to set permissions on '%s'",
					make_path.ptr);
				goto fail;
			}
		}

		*tail = lastch;
	}

	git_buf_free(&make_path);
	return 0;

fail:
	git_buf_free(&make_path);
	return -1;
}

int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
{
	return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH);
}

static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
{
	git_directory_removal_type removal_type = *(git_directory_removal_type *)opaque;

	assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY
		|| removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS
		|| removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS);

	if (git_path_isdir(path->ptr) == true) {
		if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0)
			return -1;

		if (p_rmdir(path->ptr) < 0) {
			if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && (errno == ENOTEMPTY || errno == EEXIST))
				return 0;

			giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr);
			return -1;
		}

		return 0;
	}

	if (removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS) {
		if (p_unlink(path->ptr) < 0) {
			giterr_set(GITERR_OS, "Could not remove directory.  File '%s' cannot be removed", path->ptr);
			return -1;
		}

		return 0;
	}

	if (removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY) {
		giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr);
		return -1;
	}

	return 0;
}

int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type)
{
	int error;
	git_buf p = GIT_BUF_INIT;

	error = git_buf_sets(&p, path);
	if (!error)
		error = _rmdir_recurs_foreach(&removal_type, &p);
	git_buf_free(&p);
	return error;
}

#ifdef GIT_WIN32
struct win32_path {
	wchar_t path[MAX_PATH];
	DWORD len;
};

static int win32_expand_path(struct win32_path *s_root, const wchar_t *templ)
{
	s_root->len = ExpandEnvironmentStringsW(templ, s_root->path, MAX_PATH);
	return s_root->len ? 0 : -1;
}

static int win32_find_file(git_buf *path, const struct win32_path *root, const char *filename)
{
	size_t len, alloc_len;
	wchar_t *file_utf16 = NULL;
	char file_utf8[GIT_PATH_MAX];

	if (!root || !filename || (len = strlen(filename)) == 0)
		return GIT_ENOTFOUND;

	/* allocate space for wchar_t path to file */
	alloc_len = root->len + len + 2;
	file_utf16 = git__calloc(alloc_len, sizeof(wchar_t));
	GITERR_CHECK_ALLOC(file_utf16);

	/* append root + '\\' + filename as wchar_t */
	memcpy(file_utf16, root->path, root->len * sizeof(wchar_t));

	if (*filename == '/' || *filename == '\\')
		filename++;

	git__utf8_to_16(file_utf16 + root->len - 1, alloc_len, filename);

	/* check access */
	if (_waccess(file_utf16, F_OK) < 0) {
		git__free(file_utf16);
		return GIT_ENOTFOUND;
	}

	git__utf16_to_8(file_utf8, file_utf16);
	git_path_mkposix(file_utf8);
	git_buf_sets(path, file_utf8);

	git__free(file_utf16);
	return 0;
}

static wchar_t * win32_nextpath(wchar_t * src, wchar_t * dst, size_t maxlen)
{
	wchar_t * orgsrc;

	while (*src == L';')
		src++;

	orgsrc = src;

	if (!--maxlen)
		goto nullterm;

	while (*src && *src != L';')
	{
		if (*src != L'"')
		{
			*dst++ = *src++;
			if (!--maxlen)
			{
				orgsrc = src;
				goto nullterm;
			}
		}
		else
		{
			src++;
			while (*src && *src != L'"')
			{
				*dst++ = *src++;
				if (!--maxlen)
				{
					orgsrc = src;
					goto nullterm;
				}
			}

			if (*src)
				src++;
		}
	}

	while (*src == L';')
		src++;

nullterm:
	*dst = 0;

	return (orgsrc != src) ? (wchar_t *)src : NULL;
}

int win32_find_system_file_using_path(git_buf *path, const char *filename)
{
	wchar_t * env = NULL;
	struct win32_path root;

	env = _wgetenv(L"PATH");
	if (!env)
		return -1;

	// search in all paths defined in PATH
	while ((env = win32_nextpath(env, root.path, MAX_PATH - 1)) != NULL && *root.path)
	{
		wchar_t * pfin = root.path + wcslen(root.path) - 1; // last char of the current path entry

		// ensure trailing slash
		if (*pfin != L'/' && *pfin != L'\\')
			wcscpy(++pfin, L"\\"); // we have enough space left, MAX_PATH - 1 is used in nextpath above

		root.len = (DWORD)wcslen(root.path) + 1;

		if (win32_find_file(path, &root, "git.cmd") == 0 || win32_find_file(path, &root, "git.exe") == 0) {
			// we found the cmd or bin directory of a git installaton
			if (root.len > 5) {
				wcscpy(root.path + wcslen(root.path) - 4, L"etc\\");
				if (win32_find_file(path, &root, filename) == 0)
					return 0;
			}
		}
	}
	
	return GIT_ENOTFOUND;
}

int win32_find_system_file_using_registry(git_buf *path, const char *filename)
{
#ifndef _WIN64
#define REG_MSYSGIT_INSTALL L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
#else
#define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
#endif

	struct win32_path root;

	HKEY hKey;
	DWORD dwType = REG_SZ;
	DWORD dwSize = MAX_PATH;

	root.len = 0;
	if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS)
	{
		if (RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType,(LPBYTE)&root.path, &dwSize) == ERROR_SUCCESS)
		{
			// InstallLocation points to the root of the msysgit directory
			if (dwSize + 4 > MAX_PATH) // 4 = wcslen(L"etc\\")
			{
				giterr_set(GITERR_OS, "Cannot locate the system's msysgit directory - path too long");
				return -1;
			}
			wcscat(root.path, L"etc\\");
			root.len = (DWORD)wcslen(root.path) + 1;
		}
	}
	RegCloseKey(hKey);

	if (!root.len) {
		giterr_set(GITERR_OS, "Cannot locate the system's msysgit directory");
		return -1;
	}

	if (win32_find_file(path, &root, filename) < 0) {
		giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename);
		git_buf_clear(path);
		return GIT_ENOTFOUND;
	}

	return 0;
}
#endif

int git_futils_find_system_file(git_buf *path, const char *filename)
{
#ifdef GIT_WIN32
	// try to find git.exe/git.cmd on path
	if (!win32_find_system_file_using_path(path, filename))
		return 0;

	// try to find msysgit installation path using registry
	if (!win32_find_system_file_using_registry(path, filename))
		return 0;
#else
	if (git_buf_joinpath(path, "/etc", filename) < 0)
		return -1;

	if (git_path_exists(path->ptr) == true)
		return 0;
#endif

	git_buf_clear(path);
	giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename);
	return GIT_ENOTFOUND;
}

int git_futils_find_global_file(git_buf *path, const char *filename)
{
	const char *home = getenv("HOME");

#ifdef GIT_WIN32
	struct win32_path root;

	if (home != NULL) {
		if (git_buf_joinpath(path, home, filename) < 0)
			return -1;

		if (git_path_exists(path->ptr)) {
			return 0;
		}
	}

	if (getenv("HOMEPATH") != NULL) {
{'fraction_non_alphanumeric': 0.14143311735613234, 'fraction_numerical': 0.015489015834068167, 'mean_word_length': 3.9883342895391087, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6762497', 'n_tokens_mistral': 11743, 'n_tokens_neox': 10776, 'n_words': 2581}