summaryrefslogtreecommitdiff
path: root/compat/cygwin.c
blob: b4a51b958c5651d3b509013aaef0c4e30b68a816 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
134
135
136
137
138
139
140
141
142
143
#define WIN32_LEAN_AND_MEAN
#include "../git-compat-util.h"
#include "win32.h"
#include "../cache.h" /* to read configuration */

static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts)
{
	long long winTime = ((long long)ft->dwHighDateTime << 32) +
			ft->dwLowDateTime;
	winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */
	/* convert 100-nsecond interval to seconds and nanoseconds */
	ts->tv_sec = (time_t)(winTime/10000000);
	ts->tv_nsec = (long)(winTime - ts->tv_sec*10000000LL) * 100;
}

#define size_to_blocks(s) (((s)+511)/512)

/* do_stat is a common implementation for cygwin_lstat and cygwin_stat.
 *
 * To simplify its logic, in the case of cygwin symlinks, this implementation
 * falls back to the cygwin version of stat/lstat, which is provided as the
 * last argument.
 */
static int do_stat(const char *file_name, struct stat *buf, stat_fn_t cygstat)
{
	WIN32_FILE_ATTRIBUTE_DATA fdata;

	if (file_name[0] == '/')
		return cygstat (file_name, buf);

	if (!(errno = get_file_attr(file_name, &fdata))) {
		/*
		 * If the system attribute is set and it is not a directory then
		 * it could be a symbol link created in the nowinsymlinks mode.
		 * Normally, Cygwin works in the winsymlinks mode, so this situation
		 * is very unlikely. For the sake of simplicity of our code, let's
		 * Cygwin to handle it.
		 */
		if ((fdata.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) &&
		    !(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
			return cygstat(file_name, buf);

		/* fill out the stat structure */
		buf->st_dev = buf->st_rdev = 0; /* not used by Git */
		buf->st_ino = 0;
		buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
		buf->st_nlink = 1;
		buf->st_uid = buf->st_gid = 0;
#ifdef __CYGWIN_USE_BIG_TYPES__
		buf->st_size = ((_off64_t)fdata.nFileSizeHigh << 32) +
			fdata.nFileSizeLow;
#else
		buf->st_size = (off_t)fdata.nFileSizeLow;
#endif
		buf->st_blocks = size_to_blocks(buf->st_size);
		filetime_to_timespec(&fdata.ftLastAccessTime, &buf->st_atim);
		filetime_to_timespec(&fdata.ftLastWriteTime, &buf->st_mtim);
		filetime_to_timespec(&fdata.ftCreationTime, &buf->st_ctim);
		return 0;
	} else if (errno == ENOENT) {
		/*
		 * In the winsymlinks mode (which is the default), Cygwin
		 * emulates symbol links using Windows shortcut files. These
		 * files are formed by adding .lnk extension. So, if we have
		 * not found the specified file name, it could be that it is
		 * a symbol link. Let's Cygwin to deal with that.
		 */
		return cygstat(file_name, buf);
	}
	return -1;
}

/* We provide our own lstat/stat functions, since the provided Cygwin versions
 * of these functions are too slow. These stat functions are tailored for Git's
 * usage, and therefore they are not meant to be complete and correct emulation
 * of lstat/stat functionality.
 */
static int cygwin_lstat(const char *path, struct stat *buf)
{
	return do_stat(path, buf, lstat);
}

static int cygwin_stat(const char *path, struct stat *buf)
{
	return do_stat(path, buf, stat);
}


/*
 * At start up, we are trying to determine whether Win32 API or cygwin stat
 * functions should be used. The choice is determined by core.ignorecygwinfstricks.
 * Reading this option is not always possible immediately as git_dir may
 * not be set yet. So until it is set, use cygwin lstat/stat functions.
 * However, if core.filemode is set, we must use the Cygwin posix
 * stat/lstat as the Windows stat functions do not determine posix filemode.
 *
 * Note that git_cygwin_config() does NOT call git_default_config() and this
 * is deliberate.  Many commands read from config to establish initial
 * values in variables and later tweak them from elsewhere (e.g. command line).
 * init_stat() is called lazily on demand, typically much late in the program,
 * and calling git_default_config() from here would break such variables.
 */
static int native_stat = 1;
static int core_filemode;

static int git_cygwin_config(const char *var, const char *value, void *cb)
{
	if (!strcmp(var, "core.ignorecygwinfstricks"))
		native_stat = git_config_bool(var, value);
	else if (!strcmp(var, "core.filemode"))
		core_filemode = git_config_bool(var, value);
	return 0;
}

static int init_stat(void)
{
	if (have_git_dir()) {
		git_config(git_cygwin_config, NULL);
		if (!core_filemode && native_stat) {
			cygwin_stat_fn = cygwin_stat;
			cygwin_lstat_fn = cygwin_lstat;
		} else {
			cygwin_stat_fn = stat;
			cygwin_lstat_fn = lstat;
		}
		return 0;
	}
	return 1;
}

static int cygwin_stat_stub(const char *file_name, struct stat *buf)
{
	return (init_stat() ? stat : *cygwin_stat_fn)(file_name, buf);
}

static int cygwin_lstat_stub(const char *file_name, struct stat *buf)
{
	return (init_stat() ? lstat : *cygwin_lstat_fn)(file_name, buf);
}

stat_fn_t cygwin_stat_fn = cygwin_stat_stub;
stat_fn_t cygwin_lstat_fn = cygwin_lstat_stub;