Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/nano-server.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Windows Nano Server tests

on:
workflow_dispatch:

env:
DEVELOPER: 1

jobs:
test-nano-server:
runs-on: windows-2022
env:
WINDBG_DIR: "C:/Program Files (x86)/Windows Kits/10/Debuggers/x64"
IMAGE: mcr.microsoft.com/powershell:nanoserver-ltsc2022

steps:
- uses: actions/checkout@v3
- uses: git-for-windows/setup-git-for-windows-sdk@v1
- name: build Git
shell: bash
run: make -j15
- name: pull nanoserver image
shell: bash
run: docker pull $IMAGE
- name: run nano-server test
shell: bash
run: |
docker run \
--user "ContainerAdministrator" \
-v "$WINDBG_DIR:C:/dbg" \
-v "$(cygpath -aw /mingw64/bin):C:/mingw64-bin" \
-v "$(cygpath -aw .):C:/test" \
$IMAGE pwsh.exe -Command '
# Extend the PATH to include the `.dll` files in /mingw64/bin/
$env:PATH += ";C:\mingw64-bin"

# For each executable to test pick some no-operation set of
# flags/subcommands or something that should quickly result in an
# error with known exit code that is not a negative 32-bit
# number, and set the expected return code appropriately.
#
# Only test executables that could be expected to run in a UI
# less environment.
#
# ( Executable path, arguments, expected return code )
# also note space is required before close parenthesis (a
# powershell quirk when defining nested arrays like this)

$executables_to_test = @(
("C:\test\git.exe", "", 1 ),
("C:\test\scalar.exe", "version", 0 )
)

foreach ($executable in $executables_to_test)
{
Write-Output "Now testing $($executable[0])"
&$executable[0] $executable[1]
if ($LASTEXITCODE -ne $executable[2]) {
# if we failed, run the debugger to find out what function
# or DLL could not be found and then exit the script with
# failure The missing DLL or EXE will be referenced near
# the end of the output

# Set a flag to have the debugger show loader stub
# diagnostics. This requires running as administrator,
# otherwise the flag will be ignored.
C:\dbg\gflags -i $executable[0] +SLS

C:\dbg\cdb.exe -c "g" -c "q" $executable[0] $executable[1]

exit 1
}
}

exit 0
'
6 changes: 6 additions & 0 deletions Documentation/config/core.txt
Original file line number Diff line number Diff line change
Expand Up @@ -749,3 +749,9 @@ core.abbrev::
If set to "no", no abbreviation is made and the object names
are shown in their full length.
The minimum length is 4.

core.WSLCompat::
Tells Git whether to enable wsl compatibility mode.
The default value is false. When set to true, Git will set the mode
bits of the file in the way of wsl, so that the executable flag of
files can be set or read correctly.
13 changes: 13 additions & 0 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "win32/fscache.h"
#include "../attr.h"
#include "../string-list.h"
#include "win32/wsl.h"

#define HCAST(type, handle) ((type)(intptr_t)handle)

Expand Down Expand Up @@ -810,6 +811,11 @@ int mingw_open (const char *filename, int oflags, ...)

fd = open_fn(wfilename, oflags, mode);

if ((oflags & O_CREAT) && fd >= 0 && are_wsl_compatible_mode_bits_enabled()) {
_mode_t wsl_mode = S_IFREG | (mode&0777);
set_wsl_mode_bits_by_handle((HANDLE)_get_osfhandle(fd), wsl_mode);
}

if (fd < 0 && (oflags & O_ACCMODE) != O_RDONLY && errno == EACCES) {
DWORD attrs = GetFileAttributesW(wfilename);
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY))
Expand Down Expand Up @@ -1097,6 +1103,11 @@ int mingw_lstat(const char *file_name, struct stat *buf)
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
if (S_ISREG(buf->st_mode) &&
are_wsl_compatible_mode_bits_enabled()) {
copy_wsl_mode_bits_from_disk(wfilename, -1,
&buf->st_mode);
}
return 0;
}

Expand Down Expand Up @@ -1148,6 +1159,8 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
if (are_wsl_compatible_mode_bits_enabled())
get_wsl_mode_bits_by_handle(hnd, &buf->st_mode);
return 0;
}

Expand Down
5 changes: 5 additions & 0 deletions compat/win32/fscache.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "config.h"
#include "../../mem-pool.h"
#include "ntifs.h"
#include "wsl.h"

static volatile long initialized;
static DWORD dwTlsIndex;
Expand Down Expand Up @@ -237,6 +238,10 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache,
&(fse->u.s.st_mtim));
filetime_to_timespec((FILETIME *)&(fdata->CreationTime),
&(fse->u.s.st_ctim));
if (fdata->EaSize > 0 && are_wsl_compatible_mode_bits_enabled()) {
copy_wsl_mode_bits_from_disk(fdata->FileName,
fdata->FileNameLength / sizeof(wchar_t), &fse->st_mode);
}

return fse;
}
Expand Down
139 changes: 139 additions & 0 deletions compat/win32/wsl.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#include "../../git-compat-util.h"
#include "../win32.h"
#include "../../repository.h"
#include "config.h"
#include "ntifs.h"
#include "wsl.h"

int are_wsl_compatible_mode_bits_enabled(void)
{
/* default to `false` during initialization */
static const int fallback = 0;
static int enabled = -1;

if (enabled < 0) {
/* avoid infinite recursion */
if (!the_repository)
return fallback;

if (the_repository->config &&
the_repository->config->hash_initialized &&
git_config_get_bool("core.wslcompat", &enabled) < 0)
enabled = 0;
}

return enabled < 0 ? fallback : enabled;
}

int copy_wsl_mode_bits_from_disk(const wchar_t *wpath, ssize_t wpathlen,
_mode_t *mode)
{
int ret = -1;
HANDLE h;
if (wpathlen >= 0) {
/*
* It's caller's duty to make sure wpathlen is reasonable so
* it does not overflow.
*/
wchar_t *fn2 = (wchar_t*)alloca((wpathlen + 1) * sizeof(wchar_t));
memcpy(fn2, wpath, wpathlen * sizeof(wchar_t));
fn2[wpathlen] = 0;
wpath = fn2;
}
h = CreateFileW(wpath, FILE_READ_EA | SYNCHRONIZE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS |
FILE_FLAG_OPEN_REPARSE_POINT,
NULL);
if (h != INVALID_HANDLE_VALUE) {
ret = get_wsl_mode_bits_by_handle(h, mode);
CloseHandle(h);
}
return ret;
}

#define LX_FILE_METADATA_HAS_UID 0x1
#define LX_FILE_METADATA_HAS_GID 0x2
#define LX_FILE_METADATA_HAS_MODE 0x4
#define LX_FILE_METADATA_HAS_DEVICE_ID 0x8
#define LX_FILE_CASE_SENSITIVE_DIR 0x10
typedef struct _FILE_STAT_LX_INFORMATION {
LARGE_INTEGER FileId;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER AllocationSize;
LARGE_INTEGER EndOfFile;
uint32_t FileAttributes;
uint32_t ReparseTag;
uint32_t NumberOfLinks;
ACCESS_MASK EffectiveAccess;
uint32_t LxFlags;
uint32_t LxUid;
uint32_t LxGid;
uint32_t LxMode;
uint32_t LxDeviceIdMajor;
uint32_t LxDeviceIdMinor;
} FILE_STAT_LX_INFORMATION, *PFILE_STAT_LX_INFORMATION;

/*
* This struct is extended from the original FILE_FULL_EA_INFORMATION of
* Microsoft Windows.
*/
struct wsl_full_ea_info_t {
uint32_t NextEntryOffset;
uint8_t Flags;
uint8_t EaNameLength;
uint16_t EaValueLength;
char EaName[7];
char EaValue[4];
char Padding[1];
};

enum {
FileStatLxInformation = 70,
};
__declspec(dllimport) NTSTATUS WINAPI
NtQueryInformationFile(HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation, ULONG Length,
uint32_t FileInformationClass);
__declspec(dllimport) NTSTATUS WINAPI
NtSetInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation, ULONG Length,
uint32_t FileInformationClass);
__declspec(dllimport) NTSTATUS WINAPI
NtSetEaFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock,
PVOID EaBuffer, ULONG EaBufferSize);

int set_wsl_mode_bits_by_handle(HANDLE h, _mode_t mode)
{
uint32_t value = mode;
struct wsl_full_ea_info_t ea_info;
IO_STATUS_BLOCK iob;
/* mode should be valid to make WSL happy */
assert(S_ISREG(mode) || S_ISDIR(mode));
ea_info.NextEntryOffset = 0;
ea_info.Flags = 0;
ea_info.EaNameLength = 6;
ea_info.EaValueLength = sizeof(value); /* 4 */
strlcpy(ea_info.EaName, "$LXMOD", sizeof(ea_info.EaName));
memcpy(ea_info.EaValue, &value, sizeof(value));
ea_info.Padding[0] = 0;
return NtSetEaFile(h, &iob, &ea_info, sizeof(ea_info));
}

int get_wsl_mode_bits_by_handle(HANDLE h, _mode_t *mode)
{
FILE_STAT_LX_INFORMATION fxi;
IO_STATUS_BLOCK iob;
if (NtQueryInformationFile(h, &iob, &fxi, sizeof(fxi),
FileStatLxInformation) == 0) {
if (fxi.LxFlags & LX_FILE_METADATA_HAS_MODE)
*mode = (_mode_t)fxi.LxMode;
return 0;
}
return -1;
}
12 changes: 12 additions & 0 deletions compat/win32/wsl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef COMPAT_WIN32_WSL_H
#define COMPAT_WIN32_WSL_H

int are_wsl_compatible_mode_bits_enabled(void);

int copy_wsl_mode_bits_from_disk(const wchar_t *wpath, ssize_t wpathlen,
_mode_t *mode);

int get_wsl_mode_bits_by_handle(HANDLE h, _mode_t *mode);
int set_wsl_mode_bits_by_handle(HANDLE h, _mode_t mode);

#endif
4 changes: 2 additions & 2 deletions config.mak.uname
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ endif
compat/win32/path-utils.o \
compat/win32/pthread.o compat/win32/syslog.o \
compat/win32/trace2_win32_process_info.o \
compat/win32/dirent.o compat/win32/fscache.o
compat/win32/dirent.o compat/win32/fscache.o compat/win32/wsl.o
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\"
BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO
# invalidcontinue.obj allows Git's source code to close the same file
Expand Down Expand Up @@ -679,7 +679,7 @@ ifeq ($(uname_S),MINGW)
compat/win32/flush.o \
compat/win32/path-utils.o \
compat/win32/pthread.o compat/win32/syslog.o \
compat/win32/dirent.o compat/win32/fscache.o
compat/win32/dirent.o compat/win32/fscache.o compat/win32/wsl.o
BASIC_CFLAGS += -DWIN32
EXTLIBS += -lws2_32
GITLIBS += git.res
Expand Down
1 change: 1 addition & 0 deletions contrib/buildsystems/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
compat/win32/syslog.c
compat/win32/trace2_win32_process_info.c
compat/win32/dirent.c
compat/win32/wsl.c
compat/nedmalloc/nedmalloc.c
compat/strdup.c
compat/win32/fscache.c)
Expand Down