aboutsummaryrefslogtreecommitdiffstats
path: root/util/system/fs_win.cpp
blob: 65e695bee44803a75bdda8a527501c855133e189 (plain) (blame)
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#include "fs_win.h"
#include "defaults.h"
#include "maxlen.h"

#include <util/folder/dirut.h>
#include <util/charset/wide.h>
#include "file.h"

#include <winioctl.h>

namespace NFsPrivate {
    static LPCWSTR UTF8ToWCHAR(const TStringBuf str, TUtf16String& wstr) {
        wstr.resize(str.size());
        size_t written = 0;
        if (!UTF8ToWide(str.data(), str.size(), wstr.begin(), written)) {
            return nullptr;
        }
        wstr.erase(written);
        static_assert(sizeof(WCHAR) == sizeof(wchar16), "expect sizeof(WCHAR) == sizeof(wchar16)");
        return (const WCHAR*)wstr.data();
    }

    static TString WCHARToUTF8(const LPWSTR wstr, size_t len) {
        static_assert(sizeof(WCHAR) == sizeof(wchar16), "expect sizeof(WCHAR) == sizeof(wchar16)");

        return WideToUTF8((wchar16*)wstr, len);
    }

    HANDLE CreateFileWithUtf8Name(const TStringBuf fName, ui32 accessMode, ui32 shareMode, ui32 createMode, ui32 attributes, bool inheritHandle) {
        TUtf16String wstr;
        LPCWSTR wname = UTF8ToWCHAR(fName, wstr);
        if (!wname) {
            ::SetLastError(ERROR_INVALID_NAME);
            return INVALID_HANDLE_VALUE;
        }
        SECURITY_ATTRIBUTES secAttrs;
        secAttrs.bInheritHandle = inheritHandle ? TRUE : FALSE;
        secAttrs.lpSecurityDescriptor = nullptr;
        secAttrs.nLength = sizeof(secAttrs);
        return ::CreateFileW(wname, accessMode, shareMode, &secAttrs, createMode, attributes, nullptr);
    }

    bool WinRename(const TString& oldPath, const TString& newPath) {
        TUtf16String op, np;
        LPCWSTR opPtr = UTF8ToWCHAR(oldPath, op);
        LPCWSTR npPtr = UTF8ToWCHAR(newPath, np);
        if (!opPtr || !npPtr) {
            ::SetLastError(ERROR_INVALID_NAME);
            return false;
        }

        return MoveFileExW(opPtr, npPtr, MOVEFILE_REPLACE_EXISTING) != 0;
    }

    bool WinRemove(const TString& path) {
        TUtf16String wstr;
        LPCWSTR wname = UTF8ToWCHAR(path, wstr);
        if (!wname) {
            ::SetLastError(ERROR_INVALID_NAME);
            return false;
        }
        WIN32_FILE_ATTRIBUTE_DATA fad;
        if (::GetFileAttributesExW(wname, GetFileExInfoStandard, &fad)) {
            if (fad.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
                fad.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
                ::SetFileAttributesW(wname, fad.dwFileAttributes);
            }
            if (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                return ::RemoveDirectoryW(wname) != 0;
            }
            return ::DeleteFileW(wname) != 0;
        }

        return false;
    }

    bool WinSymLink(const TString& targetName, const TString& linkName) {
        TString tName(targetName);
        {
            size_t pos;
            while ((pos = tName.find('/')) != TString::npos) {
                tName.replace(pos, 1, LOCSLASH_S);
            }
        }
        TUtf16String tstr;
        LPCWSTR wname = UTF8ToWCHAR(tName, tstr);
        TUtf16String lstr;
        LPCWSTR lname = UTF8ToWCHAR(linkName, lstr);

        // we can't create a dangling link to a dir in this way
        ui32 attr = ::GetFileAttributesW(wname);
        if (attr == INVALID_FILE_ATTRIBUTES) {
            TTempBuf result;
            if (GetFullPathNameW(lname, result.Size(), (LPWSTR)result.Data(), 0) != 0) {
                TString fullPath = WideToUTF8(TWtringBuf((const wchar16*)result.Data()));
                TStringBuf linkDir(fullPath);
                linkDir.RNextTok('\\');

                if (linkDir) {
                    TString fullTarget(tName);
                    resolvepath(fullTarget, TString{linkDir});
                    TUtf16String fullTargetW;
                    LPCWSTR ptrFullTarget = UTF8ToWCHAR(fullTarget, fullTargetW);
                    attr = ::GetFileAttributesW(ptrFullTarget);
                }
            }
        }
        return 0 != CreateSymbolicLinkW(lname, wname, attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0);
    }

    bool WinHardLink(const TString& existingPath, const TString& newPath) {
        TUtf16String ep, np;
        LPCWSTR epPtr = UTF8ToWCHAR(existingPath, ep);
        LPCWSTR npPtr = UTF8ToWCHAR(newPath, np);
        if (!epPtr || !npPtr) {
            ::SetLastError(ERROR_INVALID_NAME);
            return false;
        }

        return (CreateHardLinkW(npPtr, epPtr, nullptr) != 0);
    }

    bool WinExists(const TString& path) {
        TUtf16String buf;
        LPCWSTR ptr = UTF8ToWCHAR(path, buf);
        return ::GetFileAttributesW(ptr) != INVALID_FILE_ATTRIBUTES;
    }

    TString WinCurrentWorkingDirectory() {
        TTempBuf result;
        LPWSTR buf = reinterpret_cast<LPWSTR>(result.Data());
        int r = GetCurrentDirectoryW(result.Size() / sizeof(WCHAR), buf);
        if (r == 0) {
            throw TIoSystemError() << "failed to GetCurrentDirectory";
        }
        return WCHARToUTF8(buf, r);
    }

    bool WinSetCurrentWorkingDirectory(const TString& path) {
        TUtf16String wstr;
        LPCWSTR wname = UTF8ToWCHAR(path, wstr);
        if (!wname) {
            ::SetLastError(ERROR_INVALID_NAME);
            return false;
        }
        return SetCurrentDirectoryW(wname);
    }

    bool WinMakeDirectory(const TString& path) {
        TUtf16String buf;
        LPCWSTR ptr = UTF8ToWCHAR(path, buf);
        return CreateDirectoryW(ptr, (LPSECURITY_ATTRIBUTES) nullptr);
    }
    // edited part of <Ntifs.h> from Windows DDK

#define SYMLINK_FLAG_RELATIVE 1

    struct TReparseBufferHeader {
        USHORT SubstituteNameOffset;
        USHORT SubstituteNameLength;
        USHORT PrintNameOffset;
        USHORT PrintNameLength;
    };

    struct TSymbolicLinkReparseBuffer: public TReparseBufferHeader {
        ULONG Flags; // 0 or SYMLINK_FLAG_RELATIVE
        wchar16 PathBuffer[1];
    };

    struct TMountPointReparseBuffer: public TReparseBufferHeader {
        wchar16 PathBuffer[1];
    };

    struct TGenericReparseBuffer {
        wchar16 DataBuffer[1];
    };

    struct REPARSE_DATA_BUFFER {
        ULONG ReparseTag;
        USHORT ReparseDataLength;
        USHORT Reserved;
        union {
            TSymbolicLinkReparseBuffer SymbolicLinkReparseBuffer;
            TMountPointReparseBuffer MountPointReparseBuffer;
            TGenericReparseBuffer GenericReparseBuffer;
        };
    };

    // the end of edited part of <Ntifs.h>

    // For more info see:
    // * https://docs.microsoft.com/en-us/windows/win32/fileio/reparse-points
    // * https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/fsctl-get-reparse-point
    // * https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer
    REPARSE_DATA_BUFFER* ReadReparsePoint(HANDLE h, TTempBuf& buf) {
        while (true) {
            DWORD bytesReturned = 0;
            BOOL res = DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, nullptr, 0, buf.Data(), buf.Size(), &bytesReturned, nullptr);
            if (res) {
                REPARSE_DATA_BUFFER* rdb = (REPARSE_DATA_BUFFER*)buf.Data();
                return rdb;
            } else {
                if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
                    buf = TTempBuf(buf.Size() * 2);
                } else {
                    return nullptr;
                }
            }
        }
    }

    TString WinReadLink(const TString& name) {
        TFileHandle h = CreateFileWithUtf8Name(name, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING,
                                               FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, true);
        if (h == INVALID_HANDLE_VALUE) {
            ythrow TIoSystemError() << "can't open file " << name;
        }
        TTempBuf buf;
        REPARSE_DATA_BUFFER* rdb = ReadReparsePoint(h, buf);
        if (rdb == nullptr) {
            ythrow TIoSystemError() << "can't read reparse point " << name;
        }
        if (rdb->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
            wchar16* str = (wchar16*)&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(wchar16)];
            size_t len = rdb->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(wchar16);
            return WideToUTF8(str, len);
        } else if (rdb->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
            wchar16* str = (wchar16*)&rdb->MountPointReparseBuffer.PathBuffer[rdb->MountPointReparseBuffer.SubstituteNameOffset / sizeof(wchar16)];
            size_t len = rdb->MountPointReparseBuffer.SubstituteNameLength / sizeof(wchar16);
            return WideToUTF8(str, len);
        }
        // this reparse point is unsupported in arcadia
        return TString();
    }

    ULONG WinReadReparseTag(HANDLE h) {
        TTempBuf buf;
        REPARSE_DATA_BUFFER* rdb = ReadReparsePoint(h, buf);
        return rdb ? rdb->ReparseTag : 0;
    }

    // we can't use this function to get an analog of unix inode due to a lot of NTFS folders do not have this GUID
    // (it will be 'create' case really)
    /*
bool GetObjectId(const char* path, GUID* id) {
    TFileHandle h = CreateFileWithUtf8Name(path, 0, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
                                OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, true);
    if (h.IsOpen()) {
        FILE_OBJECTID_BUFFER fob;
        DWORD resSize = 0;
        if (DeviceIoControl(h, FSCTL_CREATE_OR_GET_OBJECT_ID, nullptr, 0, &fob, sizeof(fob), &resSize, nullptr)) {
            Y_ASSERT(resSize == sizeof(fob));
            memcpy(id, &fob.ObjectId, sizeof(GUID));
            return true;
        }
    }
    memset(id, 0, sizeof(GUID));
    return false;
}
*/

} // namespace NFsPrivate