Skip to content

Commit 61dde66

Browse files
committed
Merge branch 'wsl-file-mode-bits'
This patch introduces support to set special NTFS attributes that are interpreted by the Windows Subsystem for Linux as file mode bits, UID and GID. Signed-off-by: Johannes Schindelin <[email protected]>
2 parents a587257 + 7cd2bac commit 61dde66

File tree

8 files changed

+194
-2
lines changed

8 files changed

+194
-2
lines changed

Documentation/config/core.adoc

+6
Original file line numberDiff line numberDiff line change
@@ -770,3 +770,9 @@ core.maxTreeDepth::
770770
to allow Git to abort cleanly, and should not generally need to
771771
be adjusted. When Git is compiled with MSVC, the default is 512.
772772
Otherwise, the default is 2048.
773+
774+
core.WSLCompat::
775+
Tells Git whether to enable wsl compatibility mode.
776+
The default value is false. When set to true, Git will set the mode
777+
bits of the file in the way of wsl, so that the executable flag of
778+
files can be set or read correctly.

compat/mingw.c

+14
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "win32/fscache.h"
2424
#include "../attr.h"
2525
#include "../string-list.h"
26+
#include "win32/wsl.h"
2627
#include "dir.h"
2728
#include "gettext.h"
2829
#define SECURITY_WIN32
@@ -889,6 +890,11 @@ int mingw_open (const char *filename, int oflags, ...)
889890

890891
fd = open_fn(wfilename, oflags, mode);
891892

893+
if ((oflags & O_CREAT) && fd >= 0 && are_wsl_compatible_mode_bits_enabled()) {
894+
_mode_t wsl_mode = S_IFREG | (mode&0777);
895+
set_wsl_mode_bits_by_handle((HANDLE)_get_osfhandle(fd), wsl_mode);
896+
}
897+
892898
/*
893899
* Internally, `_wopen()` uses the `CreateFile()` API with CREATE_NEW,
894900
* which may error out with ERROR_ACCESS_DENIED and an NtStatus of
@@ -904,6 +910,7 @@ int mingw_open (const char *filename, int oflags, ...)
904910
if (fd < 0 && create && GetLastError() == ERROR_ACCESS_DENIED &&
905911
INIT_PROC_ADDR(RtlGetLastNtStatus) && RtlGetLastNtStatus() == STATUS_DELETE_PENDING)
906912
errno = EEXIST;
913+
907914
if (fd < 0 && (oflags & O_ACCMODE) != O_RDONLY && errno == EACCES) {
908915
DWORD attrs = GetFileAttributesW(wfilename);
909916
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY))
@@ -1203,6 +1210,11 @@ int mingw_lstat(const char *file_name, struct stat *buf)
12031210
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
12041211
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
12051212
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
1213+
if (S_ISREG(buf->st_mode) &&
1214+
are_wsl_compatible_mode_bits_enabled()) {
1215+
copy_wsl_mode_bits_from_disk(wfilename, -1,
1216+
&buf->st_mode);
1217+
}
12061218
return 0;
12071219
}
12081220

@@ -1254,6 +1266,8 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
12541266
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
12551267
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
12561268
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
1269+
if (are_wsl_compatible_mode_bits_enabled())
1270+
get_wsl_mode_bits_by_handle(hnd, &buf->st_mode);
12571271
return 0;
12581272
}
12591273

compat/win32/fscache.c

+16
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "config.h"
99
#include "../../mem-pool.h"
1010
#include "ntifs.h"
11+
#include "wsl.h"
1112

1213
static volatile long initialized;
1314
static DWORD dwTlsIndex;
@@ -242,6 +243,21 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache,
242243
&(fse->u.s.st_mtim));
243244
filetime_to_timespec((FILETIME *)&(fdata->CreationTime),
244245
&(fse->u.s.st_ctim));
246+
if (fdata->EaSize > 0 &&
247+
sizeof(buf) >= (size_t)(list ? list->len+1 : 0) + fse->len+1 &&
248+
are_wsl_compatible_mode_bits_enabled()) {
249+
size_t off = 0;
250+
wchar_t wpath[MAX_LONG_PATH];
251+
if (list && list->len) {
252+
memcpy(buf, list->dirent.d_name, list->len);
253+
buf[list->len] = '/';
254+
off = list->len + 1;
255+
}
256+
memcpy(buf + off, fse->dirent.d_name, fse->len);
257+
buf[off + fse->len] = '\0';
258+
if (xutftowcs_long_path(wpath, buf) >= 0)
259+
copy_wsl_mode_bits_from_disk(wpath, -1, &fse->st_mode);
260+
}
245261

246262
return fse;
247263
}

compat/win32/wsl.c

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#define USE_THE_REPOSITORY_VARIABLE
2+
#include "../../git-compat-util.h"
3+
#include "../win32.h"
4+
#include "../../repository.h"
5+
#include "config.h"
6+
#include "ntifs.h"
7+
#include "wsl.h"
8+
9+
int are_wsl_compatible_mode_bits_enabled(void)
10+
{
11+
/* default to `false` during initialization */
12+
static const int fallback = 0;
13+
static int enabled = -1;
14+
15+
if (enabled < 0) {
16+
/* avoid infinite recursion */
17+
if (!the_repository)
18+
return fallback;
19+
20+
if (the_repository->config &&
21+
the_repository->config->hash_initialized &&
22+
git_config_get_bool("core.wslcompat", &enabled) < 0)
23+
enabled = 0;
24+
}
25+
26+
return enabled < 0 ? fallback : enabled;
27+
}
28+
29+
int copy_wsl_mode_bits_from_disk(const wchar_t *wpath, ssize_t wpathlen,
30+
_mode_t *mode)
31+
{
32+
int ret = -1;
33+
HANDLE h;
34+
if (wpathlen >= 0) {
35+
/*
36+
* It's caller's duty to make sure wpathlen is reasonable so
37+
* it does not overflow.
38+
*/
39+
wchar_t *fn2 = (wchar_t*)alloca((wpathlen + 1) * sizeof(wchar_t));
40+
memcpy(fn2, wpath, wpathlen * sizeof(wchar_t));
41+
fn2[wpathlen] = 0;
42+
wpath = fn2;
43+
}
44+
h = CreateFileW(wpath, FILE_READ_EA | SYNCHRONIZE,
45+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
46+
NULL, OPEN_EXISTING,
47+
FILE_FLAG_BACKUP_SEMANTICS |
48+
FILE_FLAG_OPEN_REPARSE_POINT,
49+
NULL);
50+
if (h != INVALID_HANDLE_VALUE) {
51+
ret = get_wsl_mode_bits_by_handle(h, mode);
52+
CloseHandle(h);
53+
}
54+
return ret;
55+
}
56+
57+
#ifndef LX_FILE_METADATA_HAS_UID
58+
#define LX_FILE_METADATA_HAS_UID 0x1
59+
#define LX_FILE_METADATA_HAS_GID 0x2
60+
#define LX_FILE_METADATA_HAS_MODE 0x4
61+
#define LX_FILE_METADATA_HAS_DEVICE_ID 0x8
62+
#define LX_FILE_CASE_SENSITIVE_DIR 0x10
63+
typedef struct _FILE_STAT_LX_INFORMATION {
64+
LARGE_INTEGER FileId;
65+
LARGE_INTEGER CreationTime;
66+
LARGE_INTEGER LastAccessTime;
67+
LARGE_INTEGER LastWriteTime;
68+
LARGE_INTEGER ChangeTime;
69+
LARGE_INTEGER AllocationSize;
70+
LARGE_INTEGER EndOfFile;
71+
uint32_t FileAttributes;
72+
uint32_t ReparseTag;
73+
uint32_t NumberOfLinks;
74+
ACCESS_MASK EffectiveAccess;
75+
uint32_t LxFlags;
76+
uint32_t LxUid;
77+
uint32_t LxGid;
78+
uint32_t LxMode;
79+
uint32_t LxDeviceIdMajor;
80+
uint32_t LxDeviceIdMinor;
81+
} FILE_STAT_LX_INFORMATION, *PFILE_STAT_LX_INFORMATION;
82+
#endif
83+
84+
/*
85+
* This struct is extended from the original FILE_FULL_EA_INFORMATION of
86+
* Microsoft Windows.
87+
*/
88+
struct wsl_full_ea_info_t {
89+
uint32_t NextEntryOffset;
90+
uint8_t Flags;
91+
uint8_t EaNameLength;
92+
uint16_t EaValueLength;
93+
char EaName[7];
94+
char EaValue[4];
95+
char Padding[1];
96+
};
97+
98+
enum {
99+
FileStatLxInformation = 70,
100+
};
101+
__declspec(dllimport) NTSTATUS WINAPI
102+
NtQueryInformationFile(HANDLE FileHandle,
103+
PIO_STATUS_BLOCK IoStatusBlock,
104+
PVOID FileInformation, ULONG Length,
105+
uint32_t FileInformationClass);
106+
__declspec(dllimport) NTSTATUS WINAPI
107+
NtSetInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock,
108+
PVOID FileInformation, ULONG Length,
109+
uint32_t FileInformationClass);
110+
__declspec(dllimport) NTSTATUS WINAPI
111+
NtSetEaFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock,
112+
PVOID EaBuffer, ULONG EaBufferSize);
113+
114+
int set_wsl_mode_bits_by_handle(HANDLE h, _mode_t mode)
115+
{
116+
uint32_t value = mode;
117+
struct wsl_full_ea_info_t ea_info;
118+
IO_STATUS_BLOCK iob;
119+
/* mode should be valid to make WSL happy */
120+
assert(S_ISREG(mode) || S_ISDIR(mode));
121+
ea_info.NextEntryOffset = 0;
122+
ea_info.Flags = 0;
123+
ea_info.EaNameLength = 6;
124+
ea_info.EaValueLength = sizeof(value); /* 4 */
125+
strlcpy(ea_info.EaName, "$LXMOD", sizeof(ea_info.EaName));
126+
memcpy(ea_info.EaValue, &value, sizeof(value));
127+
ea_info.Padding[0] = 0;
128+
return NtSetEaFile(h, &iob, &ea_info, sizeof(ea_info));
129+
}
130+
131+
int get_wsl_mode_bits_by_handle(HANDLE h, _mode_t *mode)
132+
{
133+
FILE_STAT_LX_INFORMATION fxi;
134+
IO_STATUS_BLOCK iob;
135+
if (NtQueryInformationFile(h, &iob, &fxi, sizeof(fxi),
136+
FileStatLxInformation) == 0) {
137+
if (fxi.LxFlags & LX_FILE_METADATA_HAS_MODE)
138+
*mode = (_mode_t)fxi.LxMode;
139+
return 0;
140+
}
141+
return -1;
142+
}

compat/win32/wsl.h

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#ifndef COMPAT_WIN32_WSL_H
2+
#define COMPAT_WIN32_WSL_H
3+
4+
int are_wsl_compatible_mode_bits_enabled(void);
5+
6+
int copy_wsl_mode_bits_from_disk(const wchar_t *wpath, ssize_t wpathlen,
7+
_mode_t *mode);
8+
9+
int get_wsl_mode_bits_by_handle(HANDLE h, _mode_t *mode);
10+
int set_wsl_mode_bits_by_handle(HANDLE h, _mode_t mode);
11+
12+
#endif

config.mak.uname

+2-2
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ endif
512512
compat/win32/path-utils.o \
513513
compat/win32/pthread.o compat/win32/syslog.o \
514514
compat/win32/trace2_win32_process_info.o \
515-
compat/win32/dirent.o compat/win32/fscache.o
515+
compat/win32/dirent.o compat/win32/fscache.o compat/win32/wsl.o
516516
COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DDETECT_MSYS_TTY -DENSURE_MSYSTEM_IS_SET -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
517517
BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO
518518
# invalidcontinue.obj allows Git's source code to close the same file
@@ -715,7 +715,7 @@ ifeq ($(uname_S),MINGW)
715715
compat/win32/flush.o \
716716
compat/win32/path-utils.o \
717717
compat/win32/pthread.o compat/win32/syslog.o \
718-
compat/win32/dirent.o compat/win32/fscache.o
718+
compat/win32/dirent.o compat/win32/fscache.o compat/win32/wsl.o
719719
BASIC_CFLAGS += -DWIN32
720720
EXTLIBS += -lws2_32
721721
GITLIBS += git.res

contrib/buildsystems/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
293293
compat/win32/syslog.c
294294
compat/win32/trace2_win32_process_info.c
295295
compat/win32/dirent.c
296+
compat/win32/wsl.c
296297
compat/nedmalloc/nedmalloc.c
297298
compat/strdup.c
298299
compat/win32/fscache.c)

meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,7 @@ elif host_machine.system() == 'windows'
12041204
'compat/win32/path-utils.c',
12051205
'compat/win32/pthread.c',
12061206
'compat/win32/syslog.c',
1207+
'compat/win32/wsl.c',
12071208
'compat/win32mmap.c',
12081209
'compat/nedmalloc/nedmalloc.c',
12091210
]

0 commit comments

Comments
 (0)