aboutsummaryrefslogtreecommitdiffstats
path: root/util/system/fstat.cpp
diff options
context:
space:
mode:
authorkikht <kikht@yandex-team.ru>2022-02-17 22:22:02 +0300
committerkikht <kikht@yandex-team.ru>2022-02-17 22:22:02 +0300
commita4e5577781d9f1162779286558a90252430f6200 (patch)
tree2c85c7eb414fe138e451463bdb0d5fe89419a916 /util/system/fstat.cpp
parentaf4af5b490ed7283af1288054d4581b1dbc64660 (diff)
downloadydb-a4e5577781d9f1162779286558a90252430f6200.tar.gz
[util] fix fstat for archive directories on windows
Currently TFileStat has a bunch of problems on Windows: 1. Mode computation ORs different types, but file attributes it checks are not mutually exclusive. E.g. it is possible for directory to have both `FILE_ATTRIBUTE_DIRECTORY` and `FILE_ATTRIBUTE_ARCHIVE`, but it will currently lead to invalid `_S_IFDIR | _S_IFREG` mode. 2. Any file with `FILE_ATTRIBUTE_REPARSE_POINT` flag is considered to be symlink. But there are many other types of reparse points even user-defined ones. For more info see https://docs.microsoft.com/en-us/windows/win32/fileio/reparse-point-tags To fix all this we do the following: 1. Add `ReparseTag` field to `TSystemFStat` and fill it for reparse points in `GetStatByHandle`. 2. Refactor `GetWinFileType` out of `GetFileMode` to ensure that we always select only single `S_IFMT` value. 3. Pass reparse tag value into `GetWinFileType` and select `S_IFLNK` only for `IO_REPARSE_TAG_SYMLINK` and `IO_REPARSE_TAG_MOUNT_POINT` tags. The latter one is a bit strange, but this behaviour is aligned with current implementation of `WinReadLink`. 4. Factor `ReadReparsePoint` out of `WinReadLink`. This function uses `DeviceIoControl` to read reparse point data into structures copied from WDK. 5. Add `WinReadReparseTag` that uses `ReadReparsePoint` to extract reparse point tag. We use this approach instead of MSDN-recommended `FindFirstFile` because latter requires file path, but we have file handle at this point. AFAIK golang and python use similar approach for this. 6. Add test for archived directory case. 7. Make symlink nofollow tests run only when symlink creation is possible for current user. Discovered in https://st.yandex-team.ru/ARC-3931 ref:4f6735817b9f85f3351a1021a56dd7eb4606bd65
Diffstat (limited to 'util/system/fstat.cpp')
-rw-r--r--util/system/fstat.cpp57
1 files changed, 44 insertions, 13 deletions
diff --git a/util/system/fstat.cpp b/util/system/fstat.cpp
index 81e98cbc6b..c759f68f11 100644
--- a/util/system/fstat.cpp
+++ b/util/system/fstat.cpp
@@ -15,20 +15,43 @@
#endif
#define _S_IFLNK 0x80000000
-ui32 GetFileMode(DWORD fileAttributes) {
+// See https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
+// for possible flag values
+static ui32 GetWinFileType(DWORD fileAttributes, ULONG reparseTag) {
+ // I'm not really sure, why it is done like this. MSDN tells that
+ // FILE_ATTRIBUTE_DEVICE is reserved for system use. Some more info is
+ // available at https://stackoverflow.com/questions/3419527/setting-file-attribute-device-in-visual-studio
+ // We should probably replace this with GetFileType call and check for
+ // FILE_TYPE_CHAR and FILE_TYPE_PIPE return values.
+ if (fileAttributes & FILE_ATTRIBUTE_DEVICE) {
+ return _S_IFCHR;
+ }
+
+ if (fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+ // We consider IO_REPARSE_TAG_SYMLINK and IO_REPARSE_TAG_MOUNT_POINT
+ // both to be symlinks to align with current WinReadLink behaviour.
+ if (reparseTag == IO_REPARSE_TAG_SYMLINK || reparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
+ return _S_IFLNK;
+ }
+ }
+
+ if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ return _S_IFDIR;
+ }
+
+ return _S_IFREG;
+}
+
+static ui32 GetFileMode(DWORD fileAttributes, ULONG reparseTag) {
ui32 mode = 0;
if (fileAttributes == 0xFFFFFFFF)
return mode;
- if (fileAttributes & FILE_ATTRIBUTE_DEVICE)
- mode |= _S_IFCHR;
- if (fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
- mode |= _S_IFLNK; // todo: was undefined by the moment of writing this code
- if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- mode |= _S_IFDIR;
- if (fileAttributes & (FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_ARCHIVE))
- mode |= _S_IFREG;
- if ((fileAttributes & FILE_ATTRIBUTE_READONLY) == 0)
+
+ mode |= GetWinFileType(fileAttributes, reparseTag);
+
+ if ((fileAttributes & FILE_ATTRIBUTE_READONLY) == 0) {
mode |= _S_IWRITE;
+ }
return mode;
}
@@ -36,7 +59,9 @@ ui32 GetFileMode(DWORD fileAttributes) {
#define S_ISREG(st_mode) (st_mode & _S_IFREG)
#define S_ISLNK(st_mode) (st_mode & _S_IFLNK)
-using TSystemFStat = BY_HANDLE_FILE_INFORMATION;
+struct TSystemFStat: public BY_HANDLE_FILE_INFORMATION {
+ ULONG ReparseTag = 0;
+};
#else
@@ -65,7 +90,7 @@ static void MakeStat(TFileStat& st, const TSystemFStat& fs) {
FileTimeToTimeval(&fs.ftLastWriteTime, &tv);
st.MTime = tv.tv_sec;
st.NLinks = fs.nNumberOfLinks;
- st.Mode = GetFileMode(fs.dwFileAttributes);
+ st.Mode = GetFileMode(fs.dwFileAttributes, fs.ReparseTag);
st.Uid = 0;
st.Gid = 0;
st.Size = ((ui64)fs.nFileSizeHigh << 32) | fs.nFileSizeLow;
@@ -76,7 +101,13 @@ static void MakeStat(TFileStat& st, const TSystemFStat& fs) {
static bool GetStatByHandle(TSystemFStat& fs, FHANDLE f) {
#ifdef _win_
- return GetFileInformationByHandle(f, &fs);
+ if (!GetFileInformationByHandle(f, &fs)) {
+ return false;
+ }
+ if (fs.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+ fs.ReparseTag = NFsPrivate::WinReadReparseTag(f);
+ }
+ return true;
#else
return !fstat(f, &fs);
#endif