kvm-guest-drivers-windows

Форк
0
2854 строки · 82.3 Кб
1
/*
2
 * Copyright (C) 2019-2020 Red Hat, Inc.
3
 *
4
 * Written By: Gal Hammer <ghammer@redhat.com>
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions
8
 * are met :
9
 * 1. Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 * 2. Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and / or other materials provided with the distribution.
14
 * 3. Neither the names of the copyright holders nor the names of their contributors
15
 *    may be used to endorse or promote products derived from this software
16
 *    without specific prior written permission.
17
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
18
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
 * ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
21
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27
 * SUCH DAMAGE.
28
 */
29

30
#pragma warning(push)
31
// '<anonymous-tag>' : structure was padded due to alignment specifier
32
#pragma warning(disable: 4324)
33
// nonstandard extension used : nameless struct / union
34
#pragma warning(disable: 4201)
35
// potentially uninitialized local variable 'Size' used
36
#pragma warning(disable: 4701)
37
#include "winfsp/winfsp.h"
38
#pragma warning(pop)
39

40
#include <aclapi.h>
41
#include <fcntl.h>
42
#include <initguid.h>
43
#include <lm.h>
44
#include <setupapi.h>
45
#include <stdio.h>
46
#include <sys/stat.h>
47
#include <wtsapi32.h>
48
#include <cfgmgr32.h>
49

50
#include <map>
51
#include <string>
52

53
#include "virtiofs.h"
54
#include "fusereq.h"
55
#include "utils.h"
56

57
#define FS_SERVICE_NAME TEXT("VirtIO-FS")
58
#define FS_SERVICE_REGKEY   TEXT("Software\\") FS_SERVICE_NAME
59
#define ALLOCATION_UNIT 4096
60
#define PAGE_SZ_4K  4096
61
#define FUSE_DEFAULT_MAX_PAGES_PER_REQ  32
62

63
#define INVALID_FILE_HANDLE ((uint64_t)(-1))
64

65
// Some of the constants defined in Windows doesn't match the values that are
66
// used in Linux. Don't try to understand, just redefine them to match.
67
#undef O_DIRECTORY
68
#undef O_EXCL
69
#undef S_IFMT
70
#undef S_IFDIR
71

72
#define O_DIRECTORY 0200000
73
#define O_EXCL      0200
74
#define S_IFMT      0170000
75
#define S_IFDIR     040000
76
#define S_IFLNK     0120000
77

78
#define DEFAULT_OVERFLOWUID 65534
79
#define DEFAULT_OVERFLOWGID 65534
80

81
#define DBG(format, ...) \
82
    FspDebugLog("*** %s: " format "\n", __FUNCTION__, __VA_ARGS__)
83

84
#define SafeHeapFree(p) if (p != NULL) { HeapFree(GetProcessHeap(), 0, p); }
85

86
#define ReadAndExecute(x) ((x) | (((x) & 0444) >> 2))
87
#define GroupAsOwner(x) (((x) & ~0070) | (((x) & 0700) >> 3))
88

89
static uint32_t OverflowUid;
90
static uint32_t OverflowGid;
91

92
typedef struct
93
{
94
    PVOID   DirBuffer;
95
    BOOLEAN IsDirectory;
96

97
    uint64_t NodeId;
98
    uint64_t FileHandle;
99

100
} VIRTFS_FILE_CONTEXT, *PVIRTFS_FILE_CONTEXT;
101

102
struct VIRTFS
103
{
104
    FSP_FILE_SYSTEM *FileSystem{ NULL };
105

106
    HANDLE  Device{ NULL };
107

108
    ULONG   DebugFlags{ 0 };
109

110
    // Used to handle device arrive notification.
111
    DeviceInterfaceNotification DevInterfaceNotification{};
112
    // Used to handle device remove notification.
113
    DeviceHandleNotification DevHandleNotification{};
114

115
    bool CaseInsensitive{ false };
116
    std::wstring FileSystemName{};
117
    std::wstring MountPoint{ L"*" };
118
    std::wstring Tag{};
119

120
    UINT32  MaxPages{ 0 };
121
    // A write request buffer size must not exceed this value.
122
    UINT32  MaxWrite{ 0 };
123

124
    // Uid/Gid used to describe files' owner on the guest side.
125
    // Equals to well-known SID 'Everyone' by default.
126
    UINT32  LocalUid{ 0x10100 };
127
    UINT32  LocalGid{ 0x10100 };
128

129
    // Uid/Gid used to describe files' owner on the host side.
130
    UINT32  OwnerUid{ 0 };
131
    UINT32  OwnerGid{ 0 };
132

133
    // Set owner UID/GID to shared directory owner UID/GID, otherwise use
134
    // commandline/registry parameters.
135
    bool    AutoOwnerIds{ true };
136

137
    // Maps NodeId to its Nlookup counter.
138
    std::map<UINT64, UINT64> LookupMap{};
139

140
    VIRTFS(ULONG DebugFlags, bool CaseInsensitive,
141
        const std::wstring& FileSystemName, const std::wstring& MountPoint,
142
        const std::wstring& Tag, bool AutoOwnerIds,
143
        uint32_t OwnerUid,  uint32_t OwnerGid) :
144
        DebugFlags{ DebugFlags }, CaseInsensitive{ CaseInsensitive },
145
        FileSystemName { FileSystemName },
146
        MountPoint{ MountPoint }, Tag{ Tag }, AutoOwnerIds{ AutoOwnerIds }
147
    {
148
        if (!AutoOwnerIds)
149
        {
150
            this->OwnerUid = OwnerUid;
151
            this->OwnerGid = OwnerGid;
152
        }
153
    }
154

155
    NTSTATUS Start();
156
    VOID Stop();
157

158
    DWORD FindDeviceInterface();
159
    VOID CloseDeviceInterface();
160

161
    DWORD DevInterfaceArrival();
162
    VOID DevQueryRemove();
163

164
    VOID LookupMapNewOrIncNode(UINT64 NodeId);
165
    UINT64 LookupMapPopNode(UINT64 NodeId);
166

167
    NTSTATUS ReadDirAndIgnoreCaseSearch(const VIRTFS_FILE_CONTEXT* ParentContext,
168
        const char *filename, std::string& result);
169
    template<class Request, class... Args>
170
    requires std::invocable<Request, VIRTFS *, uint64_t, const char *, Args...>
171
    NTSTATUS NameAwareRequest(uint64_t parent, const char *name, Request req, Args... args);
172

173
    NTSTATUS RenameWithFallbackRequest(uint64_t oldparent, const char *oldname,
174
        uint64_t newparent, const char *newname, uint32_t flags);
175

176
    NTSTATUS SubmitInitRequest();
177
    NTSTATUS SubmitOpenRequest(UINT32 GrantedAccess,
178
        VIRTFS_FILE_CONTEXT *FileContext);
179
    NTSTATUS SubmitReadDirRequest(const VIRTFS_FILE_CONTEXT *FileContext, uint64_t Offset,
180
        bool Plus, FUSE_READ_OUT *read_out, uint32_t read_out_size);
181
    NTSTATUS SubmitReleaseRequest(const VIRTFS_FILE_CONTEXT *FileContext);
182
    NTSTATUS SubmitLookupRequest(uint64_t parent, const char *filename,
183
        FUSE_LOOKUP_OUT *lookup_out);
184
    NTSTATUS SubmitDeleteRequest(uint64_t parent, const char *filename,
185
        const VIRTFS_FILE_CONTEXT *FileContext);
186
    NTSTATUS SubmitRenameRequest(uint64_t oldparent, uint64_t newparent,
187
        const char *oldname, int oldname_size, const char *newname, int newname_size);
188
    NTSTATUS SubmitRename2Request(uint64_t oldparent, uint64_t newparent,
189
        const char *oldname, int oldname_size, const char *newname, int newname_size,
190
        uint32_t flags);
191
    NTSTATUS SubmitDestroyRequest();
192
};
193

194
static NTSTATUS SetBasicInfo(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
195
    UINT32 FileAttributes, UINT64 CreationTime, UINT64 LastAccessTime,
196
    UINT64 LastWriteTime, UINT64 ChangeTime, FSP_FSCTL_FILE_INFO *FileInfo);
197

198
static NTSTATUS SetFileSize(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
199
    UINT64 NewSize, BOOLEAN SetAllocationSize, FSP_FSCTL_FILE_INFO *FileInfo);
200

201
static VOID FixReparsePointAttributes(VIRTFS *VirtFs, uint64_t nodeid,
202
    UINT32 *PFileAttributes);
203

204
static VOID GetVolumeName(HANDLE Device, PWSTR VolumeName, DWORD VolumeNameSize);
205

206
static NTSTATUS VirtFsLookupFileName(VIRTFS *VirtFs, PWSTR FileName,
207
    FUSE_LOOKUP_OUT *LookupOut);
208

209
static DWORD WINAPI DeviceNotificationCallback(HCMNOTIFICATION Notify,
210
    PVOID Context, CM_NOTIFY_ACTION Action, PCM_NOTIFY_EVENT_DATA EventData,
211
    DWORD EventDataSize);
212

213
static int64_t GetUniqueIdentifier()
214
{
215
    static int64_t uniq = 1;
216

217
    return InterlockedIncrement64(&uniq);
218
}
219

220
static void FUSE_HEADER_INIT(struct fuse_in_header *hdr, uint32_t opcode,
221
    uint64_t nodeid, uint32_t datalen)
222
{
223
    hdr->len = sizeof(*hdr) + datalen;
224
    hdr->opcode = opcode;
225
    hdr->unique = GetUniqueIdentifier();
226
    hdr->nodeid = nodeid;
227
    hdr->uid = 0;
228
    hdr->gid = 0;
229
    hdr->pid = GetCurrentProcessId();
230
}
231

232
VOID VIRTFS::Stop()
233
{
234
    if (FileSystem == NULL)
235
    {
236
        return;
237
    }
238

239
    FspFileSystemStopDispatcher(FileSystem);
240
    FspFileSystemDelete(FileSystem);
241
    FileSystem = NULL;
242

243
    LookupMap.clear();
244

245
    SubmitDestroyRequest();
246
}
247

248
DWORD VIRTFS::FindDeviceInterface()
249
{
250
    if (Tag.empty())
251
    {
252
        return ::FindDeviceInterface(&GUID_DEVINTERFACE_VIRT_FS, &Device, 0);
253
    }
254

255
    auto tag_cmp_fn = [this](HANDLE Device) {
256
        WCHAR VolumeName[MAX_FILE_SYSTEM_NAME + 1];
257
        GetVolumeName(Device, VolumeName, sizeof(VolumeName));
258
        return Tag == VolumeName;
259
    };
260

261
    return ::FindDeviceInterface(&GUID_DEVINTERFACE_VIRT_FS, &Device, tag_cmp_fn);
262
}
263

264
DWORD VIRTFS::DevInterfaceArrival()
265
{
266
    DWORD Error;
267
    NTSTATUS Status;
268

269
    // Wait for unregister work to end, if any.
270
    DevHandleNotification.WaitForUnregWork();
271

272
    Error = FindDeviceInterface();
273
    if (Error != ERROR_SUCCESS)
274
    {
275
        return Error;
276
    }
277

278
    Error = DevHandleNotification.Register(DeviceNotificationCallback, this, Device);
279
    if (Error != ERROR_SUCCESS)
280
    {
281
        goto out_close_handle;
282
    }
283

284
    Status = Start();
285
    if (!NT_SUCCESS(Status))
286
    {
287
        Error = FspWin32FromNtStatus(Status);
288
        goto out_unreg_dh_notify;
289
    }
290

291
    return ERROR_SUCCESS;
292

293
out_unreg_dh_notify:
294
    DevHandleNotification.AsyncUnregister();
295
out_close_handle:
296
    CloseHandle(Device);
297

298
    return Error;
299
}
300

301
VOID VIRTFS::CloseDeviceInterface()
302
{
303
    if (Device != INVALID_HANDLE_VALUE)
304
    {
305
        CloseHandle(Device);
306
        Device = INVALID_HANDLE_VALUE;
307
    }
308
}
309

310
VOID VIRTFS::DevQueryRemove()
311
{
312
    Stop();
313
    DevHandleNotification.AsyncUnregister();
314
    CloseDeviceInterface();
315
}
316

317
DWORD WINAPI DeviceNotificationCallback(HCMNOTIFICATION Notify,
318
    PVOID Context, CM_NOTIFY_ACTION Action, PCM_NOTIFY_EVENT_DATA EventData,
319
    DWORD EventDataSize)
320
{
321
    auto VirtFs = static_cast<VIRTFS *>(Context);
322

323
    UNREFERENCED_PARAMETER(Notify);
324
    UNREFERENCED_PARAMETER(EventData);
325
    UNREFERENCED_PARAMETER(EventDataSize);
326

327
    switch (Action)
328
    {
329
        case CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL:
330
        case CM_NOTIFY_ACTION_DEVICEQUERYREMOVEFAILED:
331
            VirtFs->DevInterfaceArrival();
332
            break;
333
        case CM_NOTIFY_ACTION_DEVICEQUERYREMOVE:
334
        case CM_NOTIFY_ACTION_DEVICEREMOVECOMPLETE:
335
            VirtFs->DevQueryRemove();
336
            break;
337
        default:
338
            break;
339
    }
340

341
    return ERROR_SUCCESS;
342
}
343

344
static UINT32 PosixUnixModeToAttributes(VIRTFS *VirtFs, uint64_t nodeid,
345
    uint32_t mode)
346
{
347
    UINT32 Attributes;
348

349
    switch (mode & S_IFMT)
350
    {
351
        case S_IFDIR:
352
            Attributes = FILE_ATTRIBUTE_DIRECTORY;
353
            break;
354

355
        case S_IFLNK:
356
            Attributes = FILE_ATTRIBUTE_REPARSE_POINT;
357
            break;
358

359
        default:
360
            Attributes = FILE_ATTRIBUTE_ARCHIVE;
361
            break;
362
    }
363

364
    if (Attributes & FILE_ATTRIBUTE_REPARSE_POINT)
365
    {
366
        FixReparsePointAttributes(VirtFs, nodeid, &Attributes);
367
    }
368

369
    if (!!(mode & 0222) == FALSE)
370
    {
371
        Attributes |= FILE_ATTRIBUTE_READONLY;
372
    }
373

374
    return Attributes;
375
}
376

377
static uint32_t AccessToUnixFlags(UINT32 GrantedAccess)
378
{
379
    uint32_t flags;
380

381
    switch (GrantedAccess & (FILE_READ_DATA | FILE_WRITE_DATA))
382
    {
383
        case FILE_WRITE_DATA:
384
            flags = O_WRONLY;
385
            break;
386
        case FILE_READ_DATA | FILE_WRITE_DATA:
387
            flags = O_RDWR;
388
            break;
389
        case FILE_READ_DATA:
390
            __fallthrough;
391
        default:
392
            flags = O_RDONLY;
393
            break;
394
    }
395

396
    if ((GrantedAccess & FILE_APPEND_DATA) && (flags == 0))
397
    {
398
        flags = O_RDWR;
399
    }
400

401
    return flags;
402
}
403

404
static VOID FileTimeToUnixTime(UINT64 FileTime, uint64_t *time,
405
    uint32_t *nsec)
406
{
407
    __int3264 UnixTime[2];
408
    FspPosixFileTimeToUnixTime(FileTime, UnixTime);
409
    *time = UnixTime[0];
410
    *nsec = (uint32_t)UnixTime[1];
411
}
412

413
static VOID UnixTimeToFileTime(uint64_t time, uint32_t nsec,
414
    PUINT64 PFileTime)
415
{
416
    *PFileTime = time * 10000000 + nsec / 100 +
417
        116444736000000000LL;
418
}
419

420
static VOID SetFileInfo(VIRTFS *VirtFs, struct fuse_entry_out *entry,
421
    FSP_FSCTL_FILE_INFO *FileInfo)
422
{
423
    struct fuse_attr *attr = &entry->attr;
424

425
    FileInfo->FileAttributes = PosixUnixModeToAttributes(VirtFs,
426
        entry->nodeid, attr->mode);
427

428
    if (FileInfo->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
429
    {
430
        FileInfo->ReparseTag = IO_REPARSE_TAG_SYMLINK;
431
        FileInfo->FileSize = 0;
432
        FileInfo->AllocationSize = 0;
433
    }
434
    else
435
    {
436
        FileInfo->ReparseTag = 0;
437
        FileInfo->FileSize = attr->size;
438
        FileInfo->AllocationSize = attr->blocks * 512;
439
    }
440
    UnixTimeToFileTime(attr->ctime, attr->ctimensec, &FileInfo->CreationTime);
441
    UnixTimeToFileTime(attr->atime, attr->atimensec,
442
        &FileInfo->LastAccessTime);
443
    UnixTimeToFileTime(attr->mtime, attr->mtimensec,
444
        &FileInfo->LastWriteTime);
445
    FileInfo->ChangeTime = FileInfo->LastWriteTime;
446
    FileInfo->IndexNumber = 0;
447
    FileInfo->HardLinks = 0;
448
    FileInfo->EaSize = 0;
449

450
    DBG("ino=%I64u size=%I64u blocks=%I64u atime=%I64u mtime=%I64u ctime=%I64u "
451
        "atimensec=%u mtimensec=%u ctimensec=%u mode=%x nlink=%u uid=%u "
452
        "gid=%u rdev=%u blksize=%u", attr->ino, attr->size, attr->blocks,
453
        attr->atime, attr->mtime, attr->ctime, attr->atimensec,
454
        attr->mtimensec, attr->ctimensec, attr->mode, attr->nlink,
455
        attr->uid, attr->gid, attr->rdev, attr->blksize);
456
}
457

458
static NTSTATUS VirtFsFuseRequest(HANDLE Device, LPVOID InBuffer,
459
    DWORD InBufferSize, LPVOID OutBuffer, DWORD OutBufferSize)
460
{
461
    NTSTATUS Status = STATUS_SUCCESS;
462
    DWORD BytesReturned = 0;
463
    BOOL Result;
464
    struct fuse_in_header *in_hdr = (struct fuse_in_header *)InBuffer;
465
    struct fuse_out_header *out_hdr = (struct fuse_out_header *)OutBuffer;
466

467
    DBG(">>req: %d unique: %I64u len: %u", in_hdr->opcode, in_hdr->unique,
468
        in_hdr->len);
469

470
    Result = DeviceIoControl(Device, IOCTL_VIRTFS_FUSE_REQUEST,
471
        InBuffer, InBufferSize, OutBuffer, OutBufferSize,
472
        &BytesReturned, NULL);
473

474
    if (Result == FALSE)
475
    {
476
        return FspNtStatusFromWin32(GetLastError());
477
    }
478

479
    DBG("<<len: %u error: %d unique: %I64u", out_hdr->len, out_hdr->error,
480
        out_hdr->unique);
481

482
    if (BytesReturned != out_hdr->len)
483
    {
484
        DBG("BytesReturned != hdr->len");
485
    }
486

487
    if ((BytesReturned != sizeof(struct fuse_out_header)) &&
488
        (BytesReturned < OutBufferSize))
489
    {
490
        DBG("Bytes Returned: %d Expected: %d", BytesReturned, OutBufferSize);
491
        // XXX return STATUS_UNSUCCESSFUL;
492
    }
493

494
    if (out_hdr->error < 0)
495
    {
496
        switch (out_hdr->error)
497
        {
498
            case -EPERM:
499
            case -EACCES:
500
                Status = STATUS_ACCESS_DENIED;
501
                break;
502
            case -ENOENT:
503
                Status = STATUS_OBJECT_NAME_NOT_FOUND;
504
                break;
505
            case -EIO:
506
                Status = STATUS_IO_DEVICE_ERROR;
507
                break;
508
            case -EBADF:
509
                Status = STATUS_OBJECT_NAME_INVALID;
510
                break;
511
            case -ENOMEM:
512
                Status = STATUS_INSUFFICIENT_RESOURCES;
513
                break;
514
            case -EEXIST:
515
                Status = STATUS_OBJECT_NAME_COLLISION;
516
                break;
517
            case -EINVAL:
518
                Status = STATUS_INVALID_PARAMETER;
519
                break;
520
            case -ENAMETOOLONG:
521
                Status = STATUS_NAME_TOO_LONG;
522
                break;
523
            case -ENOSYS:
524
                Status = STATUS_NOT_IMPLEMENTED;
525
                break;
526
            case -EOPNOTSUPP:
527
                Status = STATUS_NOT_SUPPORTED;
528
                break;
529
            default:
530
                Status = STATUS_UNSUCCESSFUL;
531
                break;
532
        }
533
    }
534

535
    return Status;
536
}
537

538
static NTSTATUS VirtFsCreateFile(VIRTFS *VirtFs,
539
    VIRTFS_FILE_CONTEXT *FileContext, UINT32 GrantedAccess, CHAR *FileName,
540
    UINT64 Parent, UINT32 Mode, UINT64 AllocationSize,
541
    FSP_FSCTL_FILE_INFO *FileInfo)
542
{
543
    NTSTATUS Status;
544
    FUSE_CREATE_IN create_in;
545
    FUSE_CREATE_OUT create_out;
546

547
    FUSE_HEADER_INIT(&create_in.hdr, FUSE_CREATE, Parent,
548
        sizeof(struct fuse_create_in) + lstrlenA(FileName) + 1);
549

550
    create_in.hdr.uid = VirtFs->OwnerUid;
551
    create_in.hdr.gid = VirtFs->OwnerGid;
552

553
    lstrcpyA(create_in.name, FileName);
554
    create_in.create.mode = Mode;
555
    create_in.create.umask = 0;
556
    create_in.create.flags = AccessToUnixFlags(GrantedAccess) | O_EXCL;
557

558
    DBG("create_in.create.flags: 0x%08x", create_in.create.flags);
559
    DBG("create_in.create.mode: 0x%08x", create_in.create.mode);
560

561
    Status = VirtFsFuseRequest(VirtFs->Device, &create_in, create_in.hdr.len,
562
        &create_out, sizeof(create_out));
563

564
    if (NT_SUCCESS(Status))
565
    {
566
        FileContext->NodeId = create_out.entry.nodeid;
567
        FileContext->FileHandle = create_out.open.fh;
568

569
        // Newly created file has nlookup = 1
570
        if (!VirtFs->LookupMap.emplace(FileContext->NodeId, 1).second)
571
        {
572
            return STATUS_UNSUCCESSFUL;
573
        }
574

575
        if (AllocationSize > 0)
576
        {
577
            SetFileSize(VirtFs->FileSystem, FileContext,
578
                AllocationSize, TRUE, FileInfo);
579
        }
580
        else
581
        {
582
            SetFileInfo(VirtFs, &create_out.entry, FileInfo);
583
        }
584
    }
585

586
    return Status;
587
}
588

589
static NTSTATUS VirtFsCreateDir(VIRTFS *VirtFs,
590
    VIRTFS_FILE_CONTEXT *FileContext, CHAR *FileName, UINT64 Parent,
591
    UINT32 Mode, FSP_FSCTL_FILE_INFO *FileInfo)
592
{
593
    NTSTATUS Status;
594
    FUSE_MKDIR_IN mkdir_in;
595
    FUSE_MKDIR_OUT mkdir_out;
596

597
    FUSE_HEADER_INIT(&mkdir_in.hdr, FUSE_MKDIR, Parent,
598
        sizeof(struct fuse_mkdir_in) + lstrlenA(FileName) + 1);
599

600
    mkdir_in.hdr.uid = VirtFs->OwnerUid;
601
    mkdir_in.hdr.gid = VirtFs->OwnerGid;
602

603
    lstrcpyA(mkdir_in.name, FileName);
604
    mkdir_in.mkdir.mode = Mode | 0111; /* ---x--x--x */
605
    mkdir_in.mkdir.umask = 0;
606

607
    Status = VirtFsFuseRequest(VirtFs->Device, &mkdir_in, mkdir_in.hdr.len,
608
        &mkdir_out, sizeof(mkdir_out));
609

610
    if (NT_SUCCESS(Status))
611
    {
612
        FileContext->NodeId = mkdir_out.entry.nodeid;
613

614
        // Newly created directory has nlookup = 1
615
        if (!VirtFs->LookupMap.emplace(FileContext->NodeId, 1).second)
616
        {
617
            return STATUS_UNSUCCESSFUL;
618
        }
619

620
        SetFileInfo(VirtFs, &mkdir_out.entry, FileInfo);
621
    }
622

623
    return Status;
624
}
625

626
VOID VIRTFS::LookupMapNewOrIncNode(UINT64 NodeId)
627
{
628
    auto EmplaceResult = LookupMap.emplace(NodeId, 1);
629

630
    if (!EmplaceResult.second)
631
    {
632
        EmplaceResult.first->second += 1;
633
    }
634
}
635

636
UINT64 VIRTFS::LookupMapPopNode(UINT64 NodeId)
637
{
638
    auto Item = LookupMap.extract(NodeId);
639

640
    return Item.empty() ? 0 : Item.mapped();
641
}
642

643
static VOID SubmitForgetRequest(HANDLE Device, UINT64 NodeId, UINT64 Nlookup)
644
{
645
    FUSE_FORGET_IN forget_in;
646
    FUSE_FORGET_OUT forget_out;
647

648
    DBG("NodeId: %lu Nlookup: %lu", NodeId, Nlookup);
649

650
    FUSE_HEADER_INIT(&forget_in.hdr, FUSE_FORGET, NodeId,
651
        sizeof(forget_in.forget));
652

653
    forget_in.forget.nlookup = Nlookup;
654

655
    VirtFsFuseRequest(Device, &forget_in, forget_in.hdr.len,
656
        &forget_out, sizeof(forget_out));
657
}
658

659
NTSTATUS VIRTFS::SubmitDeleteRequest(uint64_t parent, const char *filename,
660
    const VIRTFS_FILE_CONTEXT *FileContext)
661
{
662
    FUSE_UNLINK_IN unlink_in;
663
    FUSE_UNLINK_OUT unlink_out;
664

665
    FUSE_HEADER_INIT(&unlink_in.hdr,
666
        FileContext->IsDirectory ? FUSE_RMDIR : FUSE_UNLINK, parent,
667
        lstrlenA(filename) + 1);
668

669
    lstrcpyA(unlink_in.name, filename);
670

671
    NTSTATUS Status = VirtFsFuseRequest(Device, &unlink_in, unlink_in.hdr.len,
672
        &unlink_out, sizeof(unlink_out));
673

674
    if (NT_SUCCESS(Status))
675
    {
676
        UINT64 Nlookup = LookupMapPopNode(FileContext->NodeId);
677

678
        SubmitForgetRequest(Device, FileContext->NodeId, Nlookup);
679
    }
680

681
    return Status;
682
}
683

684
NTSTATUS VIRTFS::SubmitLookupRequest(uint64_t parent, const char *filename,
685
    FUSE_LOOKUP_OUT *lookup_out)
686
{
687
    DBG("filename = '%s' parent = %I64u", filename, parent);
688

689
    NTSTATUS Status;
690
    FUSE_LOOKUP_IN lookup_in;
691

692
    FUSE_HEADER_INIT(&lookup_in.hdr, FUSE_LOOKUP, parent,
693
        lstrlenA(filename) + 1);
694

695
    lstrcpyA(lookup_in.name, filename);
696

697
    Status = VirtFsFuseRequest(Device, &lookup_in, lookup_in.hdr.len,
698
        lookup_out, sizeof(*lookup_out));
699

700
    if (NT_SUCCESS(Status))
701
    {
702
        struct fuse_attr *attr = &lookup_out->entry.attr;
703

704
        LookupMapNewOrIncNode(lookup_out->entry.nodeid);
705

706
        DBG("nodeid=%I64u ino=%I64u size=%I64u blocks=%I64u atime=%I64u mtime=%I64u "
707
            "ctime=%I64u atimensec=%u mtimensec=%u ctimensec=%u mode=%x "
708
            "nlink=%u uid=%u gid=%u rdev=%u blksize=%u",
709
            lookup_out->entry.nodeid, attr->ino, attr->size, attr->blocks,
710
            attr->atime, attr->mtime, attr->ctime, attr->atimensec,
711
            attr->mtimensec, attr->ctimensec, attr->mode, attr->nlink,
712
            attr->uid, attr->gid, attr->rdev, attr->blksize);
713
    }
714

715
    return Status;
716
}
717

718
static NTSTATUS SubmitReadLinkRequest(HANDLE Device, UINT64 NodeId,
719
    PWSTR SubstituteName, PUSHORT SubstituteNameLength)
720
{
721
    FUSE_READLINK_IN readlink_in;
722
    FUSE_READLINK_OUT readlink_out;
723
    NTSTATUS Status;
724

725
    FUSE_HEADER_INIT(&readlink_in.hdr, FUSE_READLINK, NodeId,
726
        sizeof(readlink_out.name));
727

728
    Status = VirtFsFuseRequest(Device, &readlink_in, readlink_in.hdr.len,
729
        &readlink_out, sizeof(readlink_out));
730

731
    if (NT_SUCCESS(Status))
732
    {
733
        int namelen = readlink_out.hdr.len - sizeof(readlink_out.hdr);
734

735
        *SubstituteNameLength = (USHORT)MultiByteToWideChar(CP_UTF8, 0,
736
            readlink_out.name, namelen, SubstituteName, MAX_PATH - 1);
737

738
        SubstituteName[*SubstituteNameLength] = L'\0';
739

740
        if (*SubstituteNameLength == 0)
741
        {
742
            Status = FspNtStatusFromWin32(GetLastError());
743
        }
744
    }
745

746
    return Status;
747
}
748

749
NTSTATUS VIRTFS::SubmitRenameRequest(uint64_t oldparent,
750
    uint64_t newparent, const char *oldname, int oldname_size, const char *newname,
751
    int newname_size)
752
{
753
    FUSE_RENAME_IN *rename_in;
754
    FUSE_RENAME_OUT rename_out;
755
    NTSTATUS Status;
756

757
    rename_in = (FUSE_RENAME_IN *)HeapAlloc(GetProcessHeap(), 0,
758
        sizeof(*rename_in) + oldname_size + newname_size);
759

760
    if (rename_in == NULL)
761
    {
762
        return STATUS_INSUFFICIENT_RESOURCES;
763
    }
764

765
    FUSE_HEADER_INIT(&rename_in->hdr, FUSE_RENAME, oldparent,
766
        sizeof(rename_in->rename) + oldname_size + newname_size);
767

768
    rename_in->rename.newdir = newparent;
769
    CopyMemory(rename_in->names, oldname, oldname_size);
770
    CopyMemory(rename_in->names + oldname_size, newname, newname_size);
771

772
    Status = VirtFsFuseRequest(Device, rename_in, rename_in->hdr.len,
773
        &rename_out, sizeof(rename_out));
774

775
    SafeHeapFree(rename_in);
776

777
    return Status;
778
}
779

780
NTSTATUS VIRTFS::SubmitRename2Request(uint64_t oldparent,
781
    uint64_t newparent, const char *oldname, int oldname_size, const char *newname,
782
    int newname_size, uint32_t flags)
783
{
784
    FUSE_RENAME2_IN *rename2_in;
785
    FUSE_RENAME_OUT rename_out;
786
    NTSTATUS Status;
787

788
    rename2_in = (FUSE_RENAME2_IN *)HeapAlloc(GetProcessHeap(), 0,
789
        sizeof(*rename2_in) + oldname_size + newname_size);
790

791
    if (rename2_in == NULL)
792
    {
793
        return STATUS_INSUFFICIENT_RESOURCES;
794
    }
795

796
    FUSE_HEADER_INIT(&rename2_in->hdr, FUSE_RENAME2, oldparent,
797
        sizeof(rename2_in->rename) + oldname_size + newname_size);
798

799
    rename2_in->rename.newdir = newparent;
800
    rename2_in->rename.flags = flags;
801
    CopyMemory(rename2_in->names, oldname, oldname_size);
802
    CopyMemory(rename2_in->names + oldname_size, newname, newname_size);
803

804
    Status = VirtFsFuseRequest(Device, rename2_in, rename2_in->hdr.len,
805
        &rename_out, sizeof(rename_out));
806

807
    SafeHeapFree(rename2_in);
808

809
    return Status;
810
}
811

812
NTSTATUS VIRTFS::RenameWithFallbackRequest(uint64_t oldparent, const char *oldname,
813
    uint64_t newparent, const char *newname, uint32_t flags)
814
{
815
    int oldname_size = lstrlenA(oldname) + 1;
816
    int newname_size = lstrlenA(newname) + 1;
817

818
    DBG("old: %s (%d) new: %s (%d) flags: %d",
819
        oldname, oldname_size, newname, newname_size, flags);
820

821
    NTSTATUS Status = SubmitRename2Request(oldparent, newparent,
822
        oldname, oldname_size, newname, newname_size, flags);
823

824
    // Rename2 fails on NFS shared folder with EINVAL error. So retry to
825
    // rename the file without the flags.
826
    if (Status == STATUS_INVALID_PARAMETER)
827
    {
828
        Status = SubmitRenameRequest(oldparent, newparent,
829
            oldname, oldname_size, newname, newname_size);
830
    }
831

832
    return Status;
833
}
834

835
template<class Request, class... Args>
836
requires std::invocable<Request, VIRTFS *, uint64_t, const char *, Args...>
837
NTSTATUS VIRTFS::NameAwareRequest(uint64_t parent, const char *name, Request req, Args... args)
838
{
839
    // First attempt
840
    NTSTATUS Status = std::invoke(req, this, parent, name, args...);
841
    if (NT_SUCCESS(Status) || !CaseInsensitive)
842
    {
843
        return Status;
844
    }
845

846
    VIRTFS_FILE_CONTEXT ParentContext = { .IsDirectory = TRUE, .NodeId = parent, };
847

848
    Status = SubmitOpenRequest(0, &ParentContext);
849
    if (!NT_SUCCESS(Status))
850
    {
851
        return Status;
852
    }
853
    SCOPE_EXIT(ParentContext, { SubmitReleaseRequest(&ParentContext); }, this);
854

855
    std::string result_name{};
856
    Status = ReadDirAndIgnoreCaseSearch(&ParentContext, name, result_name);
857
    if (!NT_SUCCESS(Status))
858
    {
859
        return Status;
860
    }
861

862
    // Second attempt
863
    Status = std::invoke(req, this, parent, result_name.c_str(), args...);
864

865
    return Status;
866
}
867

868
static NTSTATUS PathWalkthough(VIRTFS *VirtFs, CHAR *FullPath,
869
    CHAR **FileName, UINT64 *Parent)
870
{
871
    WCHAR SubstituteName[MAX_PATH];
872
    USHORT SubstituteNameLength = 0;
873
    NTSTATUS Status = STATUS_SUCCESS;
874
    FUSE_LOOKUP_OUT LookupOut;
875
    CHAR *Separator;
876

877
    *Parent = FUSE_ROOT_ID;
878
    *FileName = FullPath;
879

880
    while ((Separator = strchr(*FileName, '/')) != NULL)
881
    {
882
        *Separator = '\0';
883

884
        Status = VirtFs->NameAwareRequest(*Parent, *FileName,
885
            &VIRTFS::SubmitLookupRequest, &LookupOut);
886
        if (!NT_SUCCESS(Status))
887
        {
888
            break;
889
        }
890

891
        if ((LookupOut.entry.attr.mode & S_IFLNK) == S_IFLNK)
892
        {
893
            Status = SubmitReadLinkRequest(VirtFs->Device,
894
                LookupOut.entry.nodeid, SubstituteName, &SubstituteNameLength);
895

896
            if (!NT_SUCCESS(Status))
897
            {
898
                break;
899
            }
900

901
            Status = VirtFsLookupFileName(VirtFs, SubstituteName, &LookupOut);
902
            if (!NT_SUCCESS(Status))
903
            {
904
                break;
905
            }
906
        }
907

908
        *Parent = LookupOut.entry.nodeid;
909
        *FileName = Separator + 1;
910
    }
911

912
    return Status;
913
}
914

915
static NTSTATUS VirtFsLookupFileName(VIRTFS *VirtFs, PWSTR FileName,
916
    FUSE_LOOKUP_OUT *LookupOut)
917
{
918
    NTSTATUS Status;
919
    char *filename, *fullpath;
920
    uint64_t parent;
921

922
    if (lstrcmp(FileName, TEXT("\\")) == 0)
923
    {
924
        FileName = (PWSTR)L".";
925
    }
926
    else if (FileName[0] == TEXT('\\'))
927
    {
928
        // Skip backslash if exist.
929
        FileName += 1;
930
    }
931

932
    Status = FspPosixMapWindowsToPosixPath(FileName, &fullpath);
933
    if (!NT_SUCCESS(Status))
934
    {
935
        return Status;
936
    }
937
    SCOPE_EXIT(fullpath, { FspPosixDeletePath(fullpath); });
938

939
    Status = PathWalkthough(VirtFs, fullpath, &filename, &parent);
940
    if (NT_SUCCESS(Status))
941
    {
942
        Status = VirtFs->NameAwareRequest(parent, filename,
943
            &VIRTFS::SubmitLookupRequest, LookupOut);
944
    }
945

946
    return Status;
947
}
948

949
static VOID FixReparsePointAttributes(VIRTFS *VirtFs, uint64_t nodeid,
950
    UINT32 *PFileAttributes)
951
{
952
    WCHAR SubstituteName[MAX_PATH];
953
    USHORT SubstituteNameLength = 0;
954
    UINT32 FileAttributes;
955
    NTSTATUS Status;
956
    FUSE_LOOKUP_OUT lookup_out;
957

958
    Status = SubmitReadLinkRequest(VirtFs->Device, nodeid, SubstituteName,
959
        &SubstituteNameLength);
960

961
    if (NT_SUCCESS(Status))
962
    {
963
        Status = VirtFsLookupFileName(VirtFs, SubstituteName, &lookup_out);
964

965
        if (NT_SUCCESS(Status))
966
        {
967
            struct fuse_attr *attr = &lookup_out.entry.attr;
968

969
            FileAttributes = PosixUnixModeToAttributes(VirtFs,
970
                lookup_out.entry.nodeid, attr->mode);
971

972
            if (FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
973
            {
974
                *PFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
975
            }
976
        }
977
    }
978
}
979

980
static NTSTATUS GetFileInfoInternal(VIRTFS *VirtFs,
981
    PVIRTFS_FILE_CONTEXT FileContext, FSP_FSCTL_FILE_INFO *FileInfo,
982
    PSECURITY_DESCRIPTOR *SecurityDescriptor)
983
{
984
    NTSTATUS Status;
985
    FUSE_GETATTR_IN getattr_in;
986
    FUSE_GETATTR_OUT getattr_out;
987

988
    if ((FileInfo != NULL) && (SecurityDescriptor != NULL))
989
    {
990
        return STATUS_INVALID_PARAMETER;
991
    }
992

993
    FUSE_HEADER_INIT(&getattr_in.hdr, FUSE_GETATTR, FileContext->NodeId,
994
        sizeof(getattr_in.getattr));
995

996
    if (FileContext->FileHandle != INVALID_FILE_HANDLE)
997
    {
998
        getattr_in.getattr.fh = FileContext->FileHandle;
999
        getattr_in.getattr.getattr_flags |= FUSE_GETATTR_FH;
1000
    }
1001

1002
    getattr_in.getattr.getattr_flags = 0;
1003

1004
    Status = VirtFsFuseRequest(VirtFs->Device, &getattr_in,
1005
        sizeof(getattr_in), &getattr_out, sizeof(getattr_out));
1006

1007
    if (NT_SUCCESS(Status))
1008
    {
1009
        struct fuse_attr *attr = &getattr_out.attr.attr;
1010

1011
        if (FileInfo != NULL)
1012
        {
1013
            struct fuse_entry_out entry;
1014

1015
            ZeroMemory(&entry, sizeof(entry));
1016
            entry.nodeid = FileContext->NodeId;
1017
            entry.attr = *attr;
1018

1019
            SetFileInfo(VirtFs, &entry, FileInfo);
1020
        }
1021

1022
        if (SecurityDescriptor != NULL)
1023
        {
1024
            Status = FspPosixMapPermissionsToSecurityDescriptor(
1025
                VirtFs->LocalUid, VirtFs->LocalGid,
1026
                GroupAsOwner(ReadAndExecute(attr->mode)), SecurityDescriptor);
1027
        }
1028
    }
1029

1030
    return Status;
1031
}
1032

1033
static NTSTATUS IsEmptyDirectory(FSP_FILE_SYSTEM *FileSystem,
1034
    PVOID FileContext0)
1035
{
1036
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1037
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
1038
    BYTE ReadOutBuf[0x1000];
1039
    struct fuse_dirent *DirEntry;
1040
    NTSTATUS Status = STATUS_SUCCESS;
1041
    UINT32 Entries;
1042
    UINT32 Remains;
1043
    FUSE_READ_IN read_in;
1044
    FUSE_READ_OUT *read_out = (FUSE_READ_OUT *)ReadOutBuf;
1045

1046
    FUSE_HEADER_INIT(&read_in.hdr, FUSE_READDIR, FileContext->NodeId,
1047
        sizeof(read_in.read));
1048

1049
    read_in.read.fh = FileContext->FileHandle;
1050
    read_in.read.offset = 0;
1051
    read_in.read.size = sizeof(ReadOutBuf) - sizeof(struct fuse_out_header);
1052
    read_in.read.read_flags = 0;
1053
    read_in.read.lock_owner = 0;
1054
    read_in.read.flags = 0;
1055

1056
    Status = VirtFsFuseRequest(VirtFs->Device, &read_in, sizeof(read_in),
1057
        read_out, sizeof(struct fuse_out_header) + read_in.read.size);
1058

1059
    if (NT_SUCCESS(Status))
1060
    {
1061
        Entries = 0;
1062
        Remains = read_out->hdr.len - sizeof(struct fuse_out_header);
1063
        DirEntry = (struct fuse_dirent *)read_out->buf;
1064

1065
        while (Remains > sizeof(struct fuse_dirent))
1066
        {
1067
            if (++Entries > 2)
1068
            {
1069
                Status = STATUS_DIRECTORY_NOT_EMPTY;
1070
                break;
1071
            }
1072

1073
            Remains -= FUSE_DIRENT_SIZE(DirEntry);
1074
            DirEntry = (struct fuse_dirent *)((PBYTE)DirEntry +
1075
                FUSE_DIRENT_SIZE(DirEntry));
1076
        }
1077
    }
1078

1079
    return Status;
1080
}
1081

1082
static VOID GetVolumeName(HANDLE Device, PWSTR VolumeName,
1083
    DWORD VolumeNameSize)
1084
{
1085
    DWORD BytesReturned;
1086
    BOOL Result;
1087

1088
    Result = DeviceIoControl(Device, IOCTL_VIRTFS_GET_VOLUME_NAME, NULL, 0,
1089
        VolumeName, VolumeNameSize, &BytesReturned, NULL);
1090

1091
    if (Result == FALSE)
1092
    {
1093
        lstrcpy(VolumeName, L"Default");
1094
    }
1095
}
1096

1097
static NTSTATUS GetReparsePointByName(FSP_FILE_SYSTEM *FileSystem,
1098
    PVOID Context, PWSTR FileName, BOOLEAN IsDirectory, PVOID Buffer,
1099
    PSIZE_T PSize)
1100
{
1101
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1102
    PREPARSE_DATA_BUFFER ReparseData = (PREPARSE_DATA_BUFFER)Buffer;
1103
    FUSE_LOOKUP_OUT lookup_out;
1104
    WCHAR SubstituteName[MAX_PATH];
1105
    USHORT SubstituteNameLength = 0;
1106
    NTSTATUS Status;
1107

1108
    UNREFERENCED_PARAMETER(Context);
1109
    UNREFERENCED_PARAMETER(IsDirectory);
1110
    UNREFERENCED_PARAMETER(PSize);
1111

1112
    Status = VirtFsLookupFileName(VirtFs, FileName, &lookup_out);
1113
    if (!NT_SUCCESS(Status))
1114
    {
1115
        return Status;
1116
    }
1117

1118
    if ((lookup_out.entry.attr.mode & S_IFLNK) != S_IFLNK)
1119
    {
1120
        return STATUS_NOT_A_REPARSE_POINT;
1121
    }
1122

1123
    Status = SubmitReadLinkRequest(VirtFs->Device, lookup_out.entry.nodeid,
1124
        SubstituteName, &SubstituteNameLength);
1125

1126
    if (NT_SUCCESS(Status))
1127
    {
1128
        ReparseData->ReparseTag = IO_REPARSE_TAG_SYMLINK;
1129
        ReparseData->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE;
1130
        ReparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;
1131
        ReparseData->SymbolicLinkReparseBuffer.SubstituteNameLength =
1132
            SubstituteNameLength * sizeof(WCHAR);
1133
        CopyMemory(ReparseData->SymbolicLinkReparseBuffer.PathBuffer,
1134
            SubstituteName, SubstituteNameLength * sizeof(WCHAR));
1135
    }
1136

1137
    return Status;
1138
}
1139

1140
static NTSTATUS GetVolumeInfo(FSP_FILE_SYSTEM *FileSystem,
1141
    FSP_FSCTL_VOLUME_INFO *VolumeInfo)
1142
{
1143
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1144
    NTSTATUS Status;
1145
    FUSE_STATFS_IN statfs_in;
1146
    FUSE_STATFS_OUT statfs_out;
1147

1148
    FUSE_HEADER_INIT(&statfs_in.hdr, FUSE_STATFS, FUSE_ROOT_ID, 0);
1149

1150
    Status = VirtFsFuseRequest(VirtFs->Device, &statfs_in, sizeof(statfs_in),
1151
        &statfs_out, sizeof(statfs_out));
1152

1153
    if (NT_SUCCESS(Status))
1154
    {
1155
        struct fuse_kstatfs *kstatfs = &statfs_out.statfs.st;
1156

1157
        VolumeInfo->TotalSize = kstatfs->bsize * kstatfs->blocks;
1158
        VolumeInfo->FreeSize = kstatfs->bsize * kstatfs->bavail;
1159

1160
        GetVolumeName(VirtFs->Device, VolumeInfo->VolumeLabel,
1161
            sizeof(VolumeInfo->VolumeLabel));
1162

1163
        VolumeInfo->VolumeLabelLength =
1164
            (UINT16)(wcslen(VolumeInfo->VolumeLabel) * sizeof(WCHAR));
1165
    }
1166

1167
    DBG("VolumeLabel: %S", VolumeInfo->VolumeLabel);
1168

1169
    return Status;
1170
}
1171

1172
static NTSTATUS GetSecurityByName(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName,
1173
    PUINT32 PFileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptor,
1174
    SIZE_T *PSecurityDescriptorSize)
1175
{
1176
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1177
    PSECURITY_DESCRIPTOR Security = NULL;
1178
    DWORD SecuritySize;
1179
    NTSTATUS Status;
1180
    FUSE_LOOKUP_OUT lookup_out;
1181

1182
    DBG("\"%S\"", FileName);
1183

1184
    Status = VirtFsLookupFileName(VirtFs, FileName, &lookup_out);
1185
    if (NT_SUCCESS(Status))
1186
    {
1187
        struct fuse_attr *attr = &lookup_out.entry.attr;
1188

1189
        if (VirtFs->AutoOwnerIds && (lstrcmp(FileName, TEXT("\\")) == 0))
1190
        {
1191
            // If the shared directory UID or GID turns out to be 'nobody', it
1192
            // means the host daemon is inside the user namespace. So, the
1193
            // previous identity is 0 or another valid value. So, let's try to
1194
            // preserve it.
1195
            VirtFs->OwnerUid = (attr->uid != OverflowUid) ?
1196
                                attr->uid : VirtFs->OwnerUid;
1197
            VirtFs->OwnerGid = (attr->gid != OverflowGid) ?
1198
                                attr->gid : VirtFs->OwnerGid;
1199
        }
1200

1201
        if (PFileAttributes != NULL)
1202
        {
1203
            *PFileAttributes = PosixUnixModeToAttributes(VirtFs,
1204
                lookup_out.entry.nodeid, attr->mode);
1205
        }
1206

1207
        Status = FspPosixMapPermissionsToSecurityDescriptor(VirtFs->LocalUid,
1208
            VirtFs->LocalGid, GroupAsOwner(ReadAndExecute(attr->mode)), &Security);
1209

1210
        if (NT_SUCCESS(Status))
1211
        {
1212
            SecuritySize = GetSecurityDescriptorLength(Security);
1213

1214
            if ((PSecurityDescriptorSize != NULL) &&
1215
                (*PSecurityDescriptorSize < SecuritySize))
1216
            {
1217
                Status = STATUS_BUFFER_OVERFLOW;
1218
            }
1219
            else
1220
            {
1221
                if (SecurityDescriptor != NULL)
1222
                {
1223
                    memcpy(SecurityDescriptor, Security, SecuritySize);
1224
                }
1225
            }
1226
            SafeHeapFree(Security);
1227
        }
1228
        else
1229
        {
1230
            SecuritySize = 0;
1231
        }
1232

1233
        if (PSecurityDescriptorSize != NULL)
1234
        {
1235
            *PSecurityDescriptorSize = SecuritySize;
1236
        }
1237
    }
1238

1239
    return Status;
1240
}
1241

1242
NTSTATUS VIRTFS::SubmitOpenRequest(UINT32 GrantedAccess,
1243
    VIRTFS_FILE_CONTEXT *FileContext)
1244
{
1245
    NTSTATUS Status;
1246
    FUSE_OPEN_IN open_in;
1247
    FUSE_OPEN_OUT open_out;
1248

1249
    FUSE_HEADER_INIT(&open_in.hdr,
1250
        FileContext->IsDirectory ? FUSE_OPENDIR : FUSE_OPEN,
1251
        FileContext->NodeId, sizeof(open_in.open));
1252

1253
    open_in.open.flags = FileContext->IsDirectory ?
1254
        (O_RDONLY | O_DIRECTORY) : AccessToUnixFlags(GrantedAccess);
1255

1256
    Status = VirtFsFuseRequest(Device, &open_in, sizeof(open_in),
1257
        &open_out, sizeof(open_out));
1258

1259
    if (!NT_SUCCESS(Status))
1260
    {
1261
        return Status;
1262
    }
1263

1264
    FileContext->FileHandle = open_out.open.fh;
1265

1266
    return STATUS_SUCCESS;
1267
}
1268

1269
static NTSTATUS Create(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName,
1270
    UINT32 CreateOptions, UINT32 GrantedAccess, UINT32 FileAttributes,
1271
    PSECURITY_DESCRIPTOR SecurityDescriptor, UINT64 AllocationSize,
1272
    PVOID *PFileContext, FSP_FSCTL_FILE_INFO *FileInfo)
1273
{
1274
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1275
    VIRTFS_FILE_CONTEXT *FileContext;
1276
    NTSTATUS Status;
1277
    UINT32 Mode = 0664 /* -rw-rw-r-- */;
1278
    char *filename, *fullpath;
1279
    uint64_t parent;
1280

1281
    UNREFERENCED_PARAMETER(SecurityDescriptor);
1282

1283
    DBG("\"%S\" CreateOptions: 0x%08x GrantedAccess: 0x%08x "
1284
        "FileAttributes: 0x%08x AllocationSize: %I64u", FileName,
1285
        CreateOptions, GrantedAccess, FileAttributes, AllocationSize);
1286

1287
    Status = FspPosixMapWindowsToPosixPath(FileName + 1, &fullpath);
1288
    if (!NT_SUCCESS(Status))
1289
    {
1290
        return Status;
1291
    }
1292
    SCOPE_EXIT(fullpath, { FspPosixDeletePath(fullpath); });
1293

1294
    Status = PathWalkthough(VirtFs, fullpath, &filename, &parent);
1295
    if (!NT_SUCCESS(Status) && (Status != STATUS_OBJECT_NAME_NOT_FOUND))
1296
    {
1297
        return Status;
1298
    }
1299

1300
    FileContext = (VIRTFS_FILE_CONTEXT *)HeapAlloc(GetProcessHeap(),
1301
            HEAP_ZERO_MEMORY, sizeof(*FileContext));
1302

1303
    if (FileContext == NULL)
1304
    {
1305
        return STATUS_INSUFFICIENT_RESOURCES;
1306
    }
1307

1308
    FileContext->FileHandle = INVALID_FILE_HANDLE;
1309

1310
    if (!!(FileAttributes & FILE_ATTRIBUTE_READONLY) == TRUE)
1311
    {
1312
        Mode &= ~0222;
1313
    }
1314

1315
    if (CreateOptions & FILE_DIRECTORY_FILE)
1316
    {
1317
        FileContext->IsDirectory = TRUE;
1318

1319
        Status = VirtFsCreateDir(VirtFs, FileContext, filename, parent, Mode,
1320
            FileInfo);
1321

1322
        if (NT_SUCCESS(Status))
1323
        {
1324
            Status = VirtFs->SubmitOpenRequest(GrantedAccess, FileContext);
1325
        }
1326
    }
1327
    else
1328
    {
1329
        Status = VirtFsCreateFile(VirtFs, FileContext, GrantedAccess,
1330
            filename, parent, Mode, AllocationSize, FileInfo);
1331
    }
1332

1333
    if (!NT_SUCCESS(Status))
1334
    {
1335
        SafeHeapFree(FileContext);
1336
        return Status;
1337
    }
1338

1339
    *PFileContext = FileContext;
1340

1341
    return Status;
1342
}
1343

1344
static NTSTATUS Open(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName,
1345
    UINT32 CreateOptions, UINT32 GrantedAccess, PVOID *PFileContext,
1346
    FSP_FSCTL_FILE_INFO *FileInfo)
1347
{
1348
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1349
    VIRTFS_FILE_CONTEXT *FileContext;
1350
    NTSTATUS Status;
1351
    FUSE_LOOKUP_OUT lookup_out;
1352

1353
    DBG("\"%S\" CreateOptions: 0x%08x GrantedAccess: 0x%08x", FileName,
1354
        CreateOptions, GrantedAccess);
1355

1356
    FileContext = (VIRTFS_FILE_CONTEXT *)HeapAlloc(GetProcessHeap(),
1357
        HEAP_ZERO_MEMORY, sizeof(*FileContext));
1358

1359
    if (FileContext == NULL)
1360
    {
1361
        return STATUS_INSUFFICIENT_RESOURCES;
1362
    }
1363

1364
    Status = VirtFsLookupFileName(VirtFs, FileName, &lookup_out);
1365
    if (!NT_SUCCESS(Status))
1366
    {
1367
        SafeHeapFree(FileContext);
1368
        return Status;
1369
    }
1370

1371
    FileContext->NodeId = lookup_out.entry.nodeid;
1372

1373
    if ((lookup_out.entry.attr.mode & S_IFLNK) != S_IFLNK)
1374
    {
1375
        FileContext->IsDirectory = !!(lookup_out.entry.attr.mode & S_IFDIR);
1376
        Status = VirtFs->SubmitOpenRequest(GrantedAccess, FileContext);
1377
        if (!NT_SUCCESS(Status))
1378
        {
1379
            SafeHeapFree(FileContext);
1380
            return Status;
1381
        }
1382
    }
1383

1384
    SetFileInfo(VirtFs, &lookup_out.entry, FileInfo);
1385
    *PFileContext = FileContext;
1386

1387
    return Status;
1388
}
1389

1390
static NTSTATUS Overwrite(FSP_FILE_SYSTEM *FileSystem,
1391
    PVOID FileContext0, UINT32 FileAttributes, BOOLEAN ReplaceFileAttributes,
1392
    UINT64 AllocationSize, FSP_FSCTL_FILE_INFO *FileInfo)
1393
{
1394
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1395
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
1396
    NTSTATUS Status;
1397

1398
    DBG("FileAttributes: 0x%08x ReplaceFileAttributes: %d "
1399
        "AllocationSize: %I64u", FileAttributes, ReplaceFileAttributes,
1400
        AllocationSize);
1401

1402
    if ((FileAttributes != 0) && (FileAttributes != INVALID_FILE_ATTRIBUTES))
1403
    {
1404
        if (ReplaceFileAttributes == FALSE)
1405
        {
1406
            Status = GetFileInfoInternal(VirtFs, FileContext, FileInfo, NULL);
1407
            if (!NT_SUCCESS(Status))
1408
            {
1409
                return Status;
1410
            }
1411

1412
            FileAttributes |= FileInfo->FileAttributes;
1413
        }
1414

1415
        if ((FileAttributes != FileInfo->FileAttributes))
1416
        {
1417
            Status = SetBasicInfo(FileSystem, FileContext0, FileAttributes,
1418
                0LL, 0LL, 0LL, 0LL, FileInfo);
1419

1420
            if (!NT_SUCCESS(Status))
1421
            {
1422
                return Status;
1423
            }
1424
        }
1425
    }
1426

1427
    return SetFileSize(FileSystem, FileContext0, AllocationSize, FALSE,
1428
        FileInfo);
1429
}
1430

1431
NTSTATUS VIRTFS::SubmitReleaseRequest(const VIRTFS_FILE_CONTEXT *FileContext)
1432
{
1433
    FUSE_RELEASE_IN release_in;
1434
    FUSE_RELEASE_OUT release_out;
1435

1436
    FUSE_HEADER_INIT(&release_in.hdr,
1437
        FileContext->IsDirectory ? FUSE_RELEASEDIR : FUSE_RELEASE,
1438
        FileContext->NodeId, sizeof(release_in.release));
1439

1440
    release_in.release.fh = FileContext->FileHandle;
1441
    release_in.release.flags = 0;
1442
    release_in.release.lock_owner = 0;
1443
    release_in.release.release_flags = 0;
1444

1445
    return VirtFsFuseRequest(Device, &release_in, sizeof(release_in),
1446
        &release_out, sizeof(release_out));
1447
}
1448

1449
static VOID Close(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0)
1450
{
1451
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1452
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
1453

1454
    DBG("fh: %I64u nodeid: %I64u", FileContext->FileHandle, FileContext->NodeId);
1455

1456
    (VOID)VirtFs->SubmitReleaseRequest(FileContext);
1457

1458
    FspFileSystemDeleteDirectoryBuffer(&FileContext->DirBuffer);
1459

1460
    SafeHeapFree(FileContext);
1461
}
1462

1463
static NTSTATUS Read(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
1464
    PVOID Buffer, UINT64 Offset, ULONG Length, PULONG PBytesTransferred)
1465
{
1466
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1467
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
1468
    FUSE_READ_OUT *read_out;
1469
    NTSTATUS Status = STATUS_SUCCESS;
1470
    // Host page size is unknown, but it can't be less than 4KiB
1471
    UINT32 BufSize = min(VirtFs->MaxPages * PAGE_SZ_4K, Length);
1472
    PUCHAR Buf = (PUCHAR)Buffer;
1473

1474
    DBG("Offset: %I64u Length: %u", Offset, Length);
1475
    DBG("fh: %I64u nodeid: %I64u", FileContext->FileHandle, FileContext->NodeId);
1476

1477
    *PBytesTransferred = 0;
1478

1479
    if (Buffer == NULL)
1480
    {
1481
        return STATUS_INVALID_PARAMETER;
1482
    }
1483

1484
    read_out = (FUSE_READ_OUT *)HeapAlloc(GetProcessHeap(), 0,
1485
            sizeof(*read_out) + BufSize);
1486
    if (read_out == NULL)
1487
    {
1488
        return STATUS_INSUFFICIENT_RESOURCES;
1489
    }
1490

1491
    while (Length)
1492
    {
1493
        UINT32 Size = min(Length, BufSize);
1494
        FUSE_READ_IN read_in;
1495
        UINT32 OutSize;
1496

1497
        read_in.read.fh = FileContext->FileHandle;
1498
        read_in.read.offset = Offset;
1499
        read_in.read.size = Size;
1500
        read_in.read.read_flags = 0;
1501
        read_in.read.lock_owner = 0;
1502
        read_in.read.flags = 0;
1503

1504
        FUSE_HEADER_INIT(&read_in.hdr, FUSE_READ, FileContext->NodeId,
1505
            sizeof(read_in.read));
1506

1507
        Status = VirtFsFuseRequest(VirtFs->Device, &read_in, sizeof(read_in),
1508
            read_out, sizeof(*read_out) + Size);
1509
        if (!NT_SUCCESS(Status))
1510
        {
1511
            SafeHeapFree(read_out);
1512
            return Status;
1513
        }
1514

1515
        OutSize = read_out->hdr.len - sizeof(struct fuse_out_header);
1516
        CopyMemory(Buf, read_out->buf, OutSize);
1517
        *PBytesTransferred += OutSize;
1518

1519
        // A successful read with no bytes read means file offset is at or past
1520
        // the end of file.
1521
        if (OutSize == 0)
1522
        {
1523
            Status = STATUS_END_OF_FILE;
1524
        }
1525

1526
        if (OutSize < Size)
1527
        {
1528
            break;
1529
        }
1530

1531
        Buf    += OutSize;
1532
        Offset += OutSize;
1533
        Length -= OutSize;
1534
    }
1535

1536
    DBG("BytesTransferred: %d", *PBytesTransferred);
1537

1538
    SafeHeapFree(read_out);
1539

1540
    return Status;
1541
}
1542

1543
static NTSTATUS Write(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
1544
    PVOID Buffer, UINT64 Offset, ULONG Length, BOOLEAN WriteToEndOfFile,
1545
    BOOLEAN ConstrainedIo, PULONG PBytesTransferred,
1546
    FSP_FSCTL_FILE_INFO *FileInfo)
1547
{
1548
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1549
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
1550
    ULONG WriteSize;
1551
    NTSTATUS Status;
1552
    FUSE_WRITE_IN *write_in;
1553
    FUSE_WRITE_OUT write_out;
1554

1555
    DBG("Buffer: %p Offset: %I64u Length: %u WriteToEndOfFile: %d "
1556
        "ConstrainedIo: %d", Buffer, Offset, Length, WriteToEndOfFile,
1557
        ConstrainedIo);
1558
    DBG("fh: %I64u nodeid: %I64u", FileContext->FileHandle, FileContext->NodeId);
1559

1560
    // Both these cases requires knowing the actual file size.
1561
    if ((WriteToEndOfFile == TRUE) || (ConstrainedIo == TRUE))
1562
    {
1563
        Status = GetFileInfoInternal(VirtFs, FileContext, FileInfo, NULL);
1564
        if (!NT_SUCCESS(Status))
1565
        {
1566
            return Status;
1567
        }
1568
    }
1569

1570
    if (WriteToEndOfFile == TRUE)
1571
    {
1572
        Offset = FileInfo->FileSize;
1573
    }
1574

1575
    if (ConstrainedIo == TRUE)
1576
    {
1577
        if (Offset >= FileInfo->FileSize)
1578
        {
1579
            return STATUS_SUCCESS;
1580
        }
1581

1582
        if ((Offset + Length) > FileInfo->FileSize)
1583
        {
1584
            Length = (ULONG)(FileInfo->FileSize - Offset);
1585
        }
1586
    }
1587

1588
    WriteSize = min(Length, VirtFs->MaxWrite);
1589

1590
    write_in = (FUSE_WRITE_IN *)HeapAlloc(GetProcessHeap(), 0,
1591
        sizeof(*write_in) + WriteSize);
1592
    if (write_in == NULL)
1593
    {
1594
        return STATUS_INSUFFICIENT_RESOURCES;
1595
    }
1596

1597
    do
1598
    {
1599
        FUSE_HEADER_INIT(&write_in->hdr, FUSE_WRITE, FileContext->NodeId,
1600
            sizeof(struct fuse_write_in) + WriteSize);
1601

1602
        write_in->write.fh = FileContext->FileHandle;
1603
        write_in->write.offset = Offset + *PBytesTransferred;
1604
        write_in->write.size = WriteSize;
1605
        write_in->write.write_flags = 0;
1606
        write_in->write.lock_owner = 0;
1607
        write_in->write.flags = 0;
1608

1609
        CopyMemory(write_in->buf, (BYTE*)Buffer + *PBytesTransferred,
1610
            WriteSize);
1611

1612
        Status = VirtFsFuseRequest(VirtFs->Device, write_in,
1613
            write_in->hdr.len, &write_out, sizeof(write_out));
1614

1615
        if (!NT_SUCCESS(Status))
1616
        {
1617
            break;
1618
        }
1619

1620
        *PBytesTransferred += write_out.write.size;
1621
        Length -= write_out.write.size;
1622
        WriteSize = min(Length, VirtFs->MaxWrite);
1623
    }
1624
    while (Length > 0);
1625

1626
    SafeHeapFree(write_in);
1627

1628
    if (!NT_SUCCESS(Status))
1629
    {
1630
        return Status;
1631
    }
1632

1633
    return GetFileInfoInternal(VirtFs, FileContext, FileInfo, NULL);
1634
}
1635

1636
static NTSTATUS Flush(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
1637
    FSP_FSCTL_FILE_INFO *FileInfo)
1638
{
1639
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1640
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
1641
    NTSTATUS Status;
1642
    FUSE_FLUSH_IN flush_in;
1643
    FUSE_FLUSH_OUT flush_out;
1644

1645
    DBG("fh: %I64u nodeid: %I64u", FileContext->FileHandle, FileContext->NodeId);
1646

1647
    FUSE_HEADER_INIT(&flush_in.hdr, FUSE_FLUSH, FileContext->NodeId,
1648
        sizeof(flush_in.flush));
1649

1650
    flush_in.flush.fh = FileContext->FileHandle;
1651
    flush_in.flush.unused = 0;
1652
    flush_in.flush.padding = 0;
1653
    flush_in.flush.lock_owner = 0;
1654

1655
    Status = VirtFsFuseRequest(VirtFs->Device, &flush_in, sizeof(flush_in),
1656
        &flush_out, sizeof(flush_out));
1657

1658
    if (!NT_SUCCESS(Status))
1659
    {
1660
        return Status;
1661
    }
1662

1663
    return GetFileInfoInternal(VirtFs, FileContext, FileInfo, NULL);
1664
}
1665

1666
static NTSTATUS GetFileInfo(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
1667
    FSP_FSCTL_FILE_INFO *FileInfo)
1668
{
1669
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1670
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
1671

1672
    DBG("fh: %I64u nodeid: %I64u", FileContext->FileHandle, FileContext->NodeId);
1673

1674
    return GetFileInfoInternal(VirtFs, FileContext, FileInfo, NULL);
1675
}
1676

1677
static NTSTATUS SetBasicInfo(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
1678
    UINT32 FileAttributes, UINT64 CreationTime, UINT64 LastAccessTime,
1679
    UINT64 LastWriteTime, UINT64 ChangeTime, FSP_FSCTL_FILE_INFO *FileInfo)
1680
{
1681
    VIRTFS* VirtFs = (VIRTFS*)FileSystem->UserContext;
1682
    VIRTFS_FILE_CONTEXT* FileContext = (VIRTFS_FILE_CONTEXT*)FileContext0;
1683
    NTSTATUS Status;
1684
    FUSE_SETATTR_IN setattr_in;
1685
    FUSE_SETATTR_OUT setattr_out;
1686

1687
    DBG("fh: %I64u nodeid: %I64u", FileContext->FileHandle, FileContext->NodeId);
1688

1689
    FUSE_HEADER_INIT(&setattr_in.hdr, FUSE_SETATTR, FileContext->NodeId,
1690
        sizeof(setattr_in.setattr));
1691

1692
    ZeroMemory(&setattr_in.setattr, sizeof(setattr_in.setattr));
1693

1694
    if ((FileContext->IsDirectory == FALSE) &&
1695
        (FileContext->FileHandle != INVALID_FILE_HANDLE))
1696
    {
1697
        setattr_in.setattr.valid |= FATTR_FH;
1698
        setattr_in.setattr.fh = FileContext->FileHandle;
1699
    }
1700

1701
    if (FileAttributes != INVALID_FILE_ATTRIBUTES)
1702
    {
1703
        setattr_in.setattr.valid |= FATTR_MODE;
1704
        setattr_in.setattr.mode = 0664 /* -rw-rw-r-- */;
1705

1706
        if (!!(FileAttributes & FILE_ATTRIBUTE_READONLY) == TRUE)
1707
        {
1708
            setattr_in.setattr.mode &= ~0222;
1709
        }
1710

1711
        if (!!(FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == TRUE)
1712
        {
1713
            setattr_in.setattr.mode |= 040111;
1714
        }
1715
    }
1716

1717
    if (LastAccessTime != 0)
1718
    {
1719
        setattr_in.setattr.valid |= FATTR_ATIME;
1720
        FileTimeToUnixTime(LastAccessTime, &setattr_in.setattr.atime,
1721
            &setattr_in.setattr.atimensec);
1722
    }
1723
    if ((LastWriteTime != 0) || (ChangeTime != 0))
1724
    {
1725
        if (LastWriteTime == 0)
1726
        {
1727
            LastWriteTime = ChangeTime;
1728
        }
1729
        setattr_in.setattr.valid |= FATTR_MTIME;
1730
        FileTimeToUnixTime(LastWriteTime, &setattr_in.setattr.mtime,
1731
            &setattr_in.setattr.mtimensec);
1732
    }
1733
    if (CreationTime != 0)
1734
    {
1735
        setattr_in.setattr.valid |= FATTR_CTIME;
1736
        FileTimeToUnixTime(CreationTime, &setattr_in.setattr.ctime,
1737
            &setattr_in.setattr.ctimensec);
1738
    }
1739

1740
    Status = VirtFsFuseRequest(VirtFs->Device, &setattr_in,
1741
        sizeof(setattr_in), &setattr_out, sizeof(setattr_out));
1742

1743
    if (!NT_SUCCESS(Status))
1744
    {
1745
        return Status;
1746
    }
1747

1748
    return GetFileInfoInternal(VirtFs, FileContext, FileInfo, NULL);
1749
}
1750

1751
static VOID Cleanup(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
1752
    PWSTR FileName, ULONG Flags)
1753
{
1754
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1755
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
1756
    UINT64 LastAccessTime, LastWriteTime;
1757
    FILETIME CurrentTime;
1758
    NTSTATUS Status;
1759
    char *filename, *fullpath;
1760
    uint64_t parent;
1761

1762
    DBG("\"%S\" Flags: 0x%02x", FileName, Flags);
1763

1764
    if (FileName == NULL)
1765
    {
1766
        return;
1767
    }
1768

1769
    Status = FspPosixMapWindowsToPosixPath(FileName + 1, &fullpath);
1770
    if (!NT_SUCCESS(Status))
1771
    {
1772
        return;
1773
    }
1774
    SCOPE_EXIT(fullpath, { FspPosixDeletePath(fullpath); });
1775

1776
    Status = PathWalkthough(VirtFs, fullpath, &filename, &parent);
1777
    if (!NT_SUCCESS(Status))
1778
    {
1779
        return;
1780
    }
1781

1782
    if (Flags & FspCleanupDelete)
1783
    {
1784
        VirtFs->NameAwareRequest(parent, filename,
1785
            &VIRTFS::SubmitDeleteRequest, FileContext);
1786
    }
1787
    else
1788
    {
1789
        GetSystemTimeAsFileTime(&CurrentTime);
1790
        LastAccessTime = LastWriteTime = 0;
1791

1792
        if (Flags & FspCleanupSetAllocationSize)
1793
        {
1794

1795
        }
1796
        if (Flags & FspCleanupSetArchiveBit)
1797
        {
1798

1799
        }
1800
        if (Flags & FspCleanupSetLastAccessTime)
1801
        {
1802
            LastAccessTime = ((PLARGE_INTEGER)&CurrentTime)->QuadPart;
1803
        }
1804
        if ((Flags & FspCleanupSetLastWriteTime) ||
1805
            (Flags & FspCleanupSetChangeTime))
1806
        {
1807
            LastWriteTime = ((PLARGE_INTEGER)&CurrentTime)->QuadPart;
1808
        }
1809

1810
        (VOID)SetBasicInfo(FileSystem, FileContext0, 0, 0, LastAccessTime,
1811
            LastWriteTime, 0, NULL);
1812
    }
1813
}
1814

1815
static NTSTATUS SetFileSize(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
1816
    UINT64 NewSize, BOOLEAN SetAllocationSize, FSP_FSCTL_FILE_INFO *FileInfo)
1817
{
1818
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1819
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
1820
    NTSTATUS Status;
1821

1822
    DBG("NewSize: %I64u SetAllocationSize: %d", NewSize, SetAllocationSize);
1823
    DBG("fh: %I64u nodeid: %I64u", FileContext->FileHandle,
1824
        FileContext->NodeId);
1825

1826
    if (SetAllocationSize == TRUE)
1827
    {
1828
        if (NewSize > 0)
1829
        {
1830
            FUSE_FALLOCATE_IN falloc_in;
1831
            FUSE_FALLOCATE_OUT falloc_out;
1832

1833
            FUSE_HEADER_INIT(&falloc_in.hdr, FUSE_FALLOCATE,
1834
                FileContext->NodeId, sizeof(struct fuse_fallocate_in));
1835

1836
            falloc_in.hdr.uid = VirtFs->OwnerUid;
1837
            falloc_in.hdr.gid = VirtFs->OwnerGid;
1838

1839
            falloc_in.falloc.fh = FileContext->FileHandle;
1840
            falloc_in.falloc.offset = 0;
1841
            falloc_in.falloc.length = NewSize;
1842
            falloc_in.falloc.mode = 0x01; /* FALLOC_FL_KEEP_SIZE */
1843
            falloc_in.falloc.padding = 0;
1844

1845
            Status = VirtFsFuseRequest(VirtFs->Device, &falloc_in,
1846
                falloc_in.hdr.len, &falloc_out, sizeof(falloc_out));
1847
        }
1848
        else
1849
        {
1850
            // fallocate on host fails when len is less than or equal to 0.
1851
            // So ignore the request and report success. This fix a failure
1852
            // to create a new file through Windows Explorer.
1853
            Status = STATUS_SUCCESS;
1854
        }
1855
    }
1856
    else
1857
    {
1858
        FUSE_SETATTR_IN setattr_in;
1859
        FUSE_SETATTR_OUT setattr_out;
1860

1861
        FUSE_HEADER_INIT(&setattr_in.hdr, FUSE_SETATTR, FileContext->NodeId,
1862
            sizeof(setattr_in.setattr));
1863

1864
        ZeroMemory(&setattr_in.setattr, sizeof(setattr_in.setattr));
1865
        setattr_in.setattr.valid = FATTR_SIZE;
1866
        setattr_in.setattr.size = NewSize;
1867

1868
        Status = VirtFsFuseRequest(VirtFs->Device, &setattr_in,
1869
            sizeof(setattr_in), &setattr_out, sizeof(setattr_out));
1870
    }
1871

1872
    if (!NT_SUCCESS(Status))
1873
    {
1874
        return Status;
1875
    }
1876

1877
    return GetFileInfoInternal(VirtFs, FileContext, FileInfo, NULL);
1878
}
1879

1880
static NTSTATUS CanDelete(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
1881
    PWSTR FileName)
1882
{
1883
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
1884
    NTSTATUS Status = STATUS_SUCCESS;
1885

1886
    DBG("\"%S\"", FileName);
1887

1888
    if (FileContext->IsDirectory == TRUE)
1889
    {
1890
        Status = IsEmptyDirectory(FileSystem, FileContext0);
1891
    }
1892

1893
    return Status;
1894
}
1895

1896
static NTSTATUS Rename(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
1897
    PWSTR FileName, PWSTR NewFileName, BOOLEAN ReplaceIfExists)
1898
{
1899
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1900
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
1901
    NTSTATUS Status;
1902
    char *oldname, *newname, *oldfullpath, *newfullpath;
1903
    uint64_t oldparent, newparent;
1904
    uint32_t flags;
1905

1906
    DBG("\"%S\" -> \"%S\" ReplaceIfExist: %d", FileName, NewFileName,
1907
        ReplaceIfExists);
1908
    DBG("fh: %I64u nodeid: %I64u", FileContext->FileHandle, FileContext->NodeId);
1909

1910
    Status = FspPosixMapWindowsToPosixPath(FileName + 1, &oldfullpath);
1911
    if (!NT_SUCCESS(Status))
1912
    {
1913
        return Status;
1914
    }
1915
    SCOPE_EXIT(oldfullpath, { FspPosixDeletePath(oldfullpath); });
1916

1917
    Status = FspPosixMapWindowsToPosixPath(NewFileName + 1, &newfullpath);
1918
    if (!NT_SUCCESS(Status))
1919
    {
1920
        return Status;
1921
    }
1922
    SCOPE_EXIT(newfullpath, { FspPosixDeletePath(newfullpath); });
1923

1924
    Status = PathWalkthough(VirtFs, oldfullpath, &oldname, &oldparent);
1925
    if (!NT_SUCCESS(Status))
1926
    {
1927
        return Status;
1928
    }
1929

1930
    Status = PathWalkthough(VirtFs, newfullpath, &newname, &newparent);
1931
    if (!NT_SUCCESS(Status))
1932
    {
1933
        return Status;
1934
    }
1935

1936
    if (VirtFs->CaseInsensitive && oldparent == newparent &&
1937
        FileNameIgnoreCaseCompare(oldname, newname))
1938
    {
1939
        return STATUS_SUCCESS;
1940
    }
1941

1942
    // It is not allowed to rename to an existing directory even when
1943
    // ReplaceIfExists is set.
1944
    flags = ((FileContext->IsDirectory == FALSE) &&
1945
        (ReplaceIfExists == TRUE)) ? 0 : (1 << 0) /* RENAME_NOREPLACE */;
1946

1947
    Status = VirtFs->NameAwareRequest(oldparent, oldname,
1948
        &VIRTFS::RenameWithFallbackRequest, newparent, newname, flags);
1949

1950
    // Fix to expected error when renaming a directory to existing directory.
1951
    if ((FileContext->IsDirectory == TRUE) && (ReplaceIfExists == TRUE) &&
1952
        (Status == STATUS_OBJECT_NAME_COLLISION))
1953
    {
1954
        Status = STATUS_ACCESS_DENIED;
1955
    }
1956

1957
    return Status;
1958
}
1959

1960
static NTSTATUS GetSecurity(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
1961
    PSECURITY_DESCRIPTOR SecurityDescriptor, SIZE_T *PSecurityDescriptorSize)
1962
{
1963
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
1964
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
1965
    PSECURITY_DESCRIPTOR Security;
1966
    DWORD SecurityLength;
1967
    NTSTATUS Status;
1968

1969
    DBG("fh: %I64u nodeid: %I64u", FileContext->FileHandle, FileContext->NodeId);
1970

1971
    Status = GetFileInfoInternal(VirtFs, FileContext, NULL, &Security);
1972
    if (!NT_SUCCESS(Status))
1973
    {
1974
        *PSecurityDescriptorSize = 0;
1975
        return Status;
1976
    }
1977

1978
    SecurityLength = GetSecurityDescriptorLength(Security);
1979
    if (*PSecurityDescriptorSize < SecurityLength)
1980
    {
1981
        *PSecurityDescriptorSize = SecurityLength;
1982
        SafeHeapFree(Security);
1983
        return STATUS_BUFFER_TOO_SMALL;
1984
    }
1985

1986
    *PSecurityDescriptorSize = SecurityLength;
1987
    if (SecurityDescriptor != NULL)
1988
    {
1989
        CopyMemory(SecurityDescriptor, Security, SecurityLength);
1990
    }
1991

1992
    SafeHeapFree(Security);
1993

1994
    return STATUS_SUCCESS;
1995
}
1996

1997
static NTSTATUS SetSecurity(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
1998
    SECURITY_INFORMATION SecurityInformation,
1999
    PSECURITY_DESCRIPTOR ModificationDescriptor)
2000
{
2001
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
2002
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
2003
    PSECURITY_DESCRIPTOR FileSecurity, NewSecurityDescriptor;
2004
    UINT32 Uid, Gid, Mode, NewMode;
2005
    NTSTATUS Status;
2006

2007
    DBG("fh: %I64u nodeid: %I64u", FileContext->FileHandle, FileContext->NodeId);
2008

2009
    Status = GetFileInfoInternal(VirtFs, FileContext, NULL, &FileSecurity);
2010
    if (!NT_SUCCESS(Status))
2011
    {
2012
        SafeHeapFree(FileSecurity);
2013
        return Status;
2014
    }
2015

2016
    Status = FspPosixMapSecurityDescriptorToPermissions(FileSecurity, &Uid,
2017
        &Gid, &Mode);
2018

2019
    if (!NT_SUCCESS(Status))
2020
    {
2021
        SafeHeapFree(FileSecurity);
2022
        return Status;
2023
    }
2024

2025
    Status = FspSetSecurityDescriptor(FileSecurity, SecurityInformation,
2026
        ModificationDescriptor, &NewSecurityDescriptor);
2027

2028
    if (!NT_SUCCESS(Status))
2029
    {
2030
        SafeHeapFree(FileSecurity);
2031
        return Status;
2032
    }
2033

2034
    SafeHeapFree(FileSecurity);
2035

2036
    Status = FspPosixMapSecurityDescriptorToPermissions(NewSecurityDescriptor,
2037
        &Uid, &Gid, &NewMode);
2038

2039
    if (!NT_SUCCESS(Status))
2040
    {
2041
        FspDeleteSecurityDescriptor(NewSecurityDescriptor,
2042
            (NTSTATUS(*)())FspSetSecurityDescriptor);
2043
        return Status;
2044
    }
2045

2046
    FspDeleteSecurityDescriptor(NewSecurityDescriptor,
2047
        (NTSTATUS(*)())FspSetSecurityDescriptor);
2048

2049
    if (Mode != NewMode)
2050
    {
2051
        FUSE_SETATTR_IN setattr_in;
2052
        FUSE_SETATTR_OUT setattr_out;
2053

2054
        FUSE_HEADER_INIT(&setattr_in.hdr, FUSE_SETATTR, FileContext->NodeId,
2055
            sizeof(setattr_in.setattr));
2056

2057
        ZeroMemory(&setattr_in.setattr, sizeof(setattr_in.setattr));
2058
        setattr_in.setattr.valid = FATTR_MODE;
2059
        setattr_in.setattr.mode = NewMode;
2060

2061
        Status = VirtFsFuseRequest(VirtFs->Device, &setattr_in,
2062
            sizeof(setattr_in), &setattr_out, sizeof(setattr_out));
2063
    }
2064

2065
    return Status;
2066
}
2067

2068
NTSTATUS VIRTFS::SubmitReadDirRequest(const VIRTFS_FILE_CONTEXT *FileContext,
2069
    uint64_t Offset, bool Plus, FUSE_READ_OUT *read_out, uint32_t read_out_size)
2070
{
2071
    FUSE_READ_IN read_in;
2072

2073
    FUSE_HEADER_INIT(&read_in.hdr, Plus ? FUSE_READDIRPLUS : FUSE_READDIR,
2074
        FileContext->NodeId, sizeof(read_in.read));
2075

2076
    read_in.read.fh = FileContext->FileHandle;
2077
    read_in.read.offset = Offset;
2078
    read_in.read.size = read_out_size - sizeof(struct fuse_out_header);
2079
    read_in.read.read_flags = 0;
2080
    read_in.read.lock_owner = 0;
2081
    read_in.read.flags = 0;
2082

2083
    return VirtFsFuseRequest(Device, &read_in, sizeof(read_in),
2084
        read_out, read_out_size);
2085
}
2086

2087
NTSTATUS VIRTFS::ReadDirAndIgnoreCaseSearch(const VIRTFS_FILE_CONTEXT *ParentContext,
2088
    const char *filename, std::string &result)
2089
{
2090
    NTSTATUS Status;
2091
    struct fuse_dirent *dirent;
2092
    UINT64 Offset = 0;
2093
    UINT32 Remains;
2094
    uint32_t buf_size = PAGE_SZ_4K - sizeof(struct fuse_out_header);
2095
    WCHAR FileName[MAX_PATH];
2096

2097
    DBG("filename = '%s'", filename);
2098

2099
    FUSE_READ_OUT *read_out = (FUSE_READ_OUT *)HeapAlloc(GetProcessHeap(), 0,
2100
        sizeof(struct fuse_out_header) + buf_size);
2101
    if (read_out == NULL)
2102
    {
2103
        return STATUS_INSUFFICIENT_RESOURCES;
2104
    }
2105
    SCOPE_EXIT(read_out, { SafeHeapFree(read_out); });
2106

2107
    if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, FileName, MAX_PATH) == 0)
2108
    {
2109
        return FspNtStatusFromWin32(GetLastError());
2110
    }
2111

2112
    for (;;)
2113
    {
2114
        Status = SubmitReadDirRequest(ParentContext, Offset, FALSE,
2115
            read_out, buf_size + sizeof(struct fuse_out_header));
2116
        if (!NT_SUCCESS(Status))
2117
        {
2118
            return Status;
2119
        }
2120

2121
        Remains = read_out->hdr.len - sizeof(struct fuse_out_header);
2122
        if (Remains == 0)
2123
        {
2124
            break;
2125
        }
2126

2127
        dirent = (struct fuse_dirent *)read_out->buf;
2128

2129
        while (Remains > sizeof(struct fuse_dirent))
2130
        {
2131
            if (FileNameIgnoreCaseCompare(FileName, dirent->name, dirent->namelen))
2132
            {
2133
                result.assign(dirent->name, dirent->namelen);
2134
                DBG("match: name = '%s' (%u) type = %u",
2135
                    result.c_str(), dirent->namelen, dirent->type);
2136

2137
                return STATUS_SUCCESS;
2138
            }
2139

2140
            Offset = dirent->off;
2141
            Remains -= FUSE_DIRENT_SIZE(dirent);
2142
            dirent = (struct fuse_dirent *)((PBYTE)dirent + FUSE_DIRENT_SIZE(dirent));
2143
        }
2144
    }
2145

2146
    return STATUS_OBJECT_NAME_NOT_FOUND;
2147
}
2148

2149
static NTSTATUS ReadDirectory(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext0,
2150
    PWSTR Pattern, PWSTR Marker, PVOID Buffer, ULONG BufferLength,
2151
    PULONG PBytesTransferred)
2152
{
2153
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
2154
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
2155
    BYTE DirInfoBuf[sizeof(FSP_FSCTL_DIR_INFO) + MAX_PATH * sizeof(WCHAR)];
2156
    FSP_FSCTL_DIR_INFO *DirInfo = (FSP_FSCTL_DIR_INFO *)DirInfoBuf;
2157
    struct fuse_direntplus *DirEntryPlus;
2158
    NTSTATUS Status = STATUS_SUCCESS;
2159
    UINT64 Offset = 0;
2160
    UINT32 Remains;
2161
    BOOLEAN Result;
2162
    int FileNameLength;
2163
    FUSE_READ_OUT *read_out;
2164

2165
    DBG("Pattern: %S Marker: %S BufferLength: %u",
2166
        Pattern ? Pattern : TEXT("(null)"), Marker ? Marker : TEXT("(null)"),
2167
        BufferLength);
2168

2169
    Result = FspFileSystemAcquireDirectoryBuffer(&FileContext->DirBuffer,
2170
        Marker == NULL, &Status);
2171

2172
    if (Result == TRUE)
2173
    {
2174
        read_out = (FUSE_READ_OUT *)HeapAlloc(GetProcessHeap(), 0,
2175
            sizeof(struct fuse_out_header) + (ULONG64)BufferLength * 2);
2176

2177
        if (read_out != NULL)
2178
        {
2179
            for (;;)
2180
            {
2181
                VirtFs->SubmitReadDirRequest(FileContext, Offset, TRUE, read_out,
2182
                    sizeof(struct fuse_out_header) + (ULONG64)BufferLength * 2);
2183

2184
                if (!NT_SUCCESS(Status))
2185
                {
2186
                    break;
2187
                }
2188

2189
                Remains = read_out->hdr.len - sizeof(struct fuse_out_header);
2190
                if (Remains == 0)
2191
                {
2192
                    // A successful request with no data means no more
2193
                    // entries.
2194
                    break;
2195
                }
2196

2197
                DirEntryPlus = (struct fuse_direntplus *)read_out->buf;
2198

2199
                while (Remains > sizeof(struct fuse_direntplus))
2200
                {
2201
                    DBG("ino=%I64u off=%I64u namelen=%u type=%u name=%s",
2202
                        DirEntryPlus->dirent.ino, DirEntryPlus->dirent.off,
2203
                        DirEntryPlus->dirent.namelen,
2204
                        DirEntryPlus->dirent.type, DirEntryPlus->dirent.name);
2205

2206
                    ZeroMemory(DirInfoBuf, sizeof(DirInfoBuf));
2207

2208
                    // Not using FspPosixMapPosixToWindowsPath so we can do
2209
                    // the conversion in-place.
2210
                    FileNameLength = MultiByteToWideChar(CP_UTF8, 0,
2211
                        DirEntryPlus->dirent.name,
2212
                        DirEntryPlus->dirent.namelen, DirInfo->FileNameBuf,
2213
                        MAX_PATH);
2214

2215
                    DBG("\"%S\" (%d)", DirInfo->FileNameBuf, FileNameLength);
2216

2217
                    if (FileNameLength > 0)
2218
                    {
2219
                        DirInfo->Size = (UINT16)(sizeof(FSP_FSCTL_DIR_INFO) +
2220
                            FileNameLength * sizeof(WCHAR));
2221

2222
                        SetFileInfo(VirtFs, &DirEntryPlus->entry_out,
2223
                            &DirInfo->FileInfo);
2224

2225
                        Result = FspFileSystemFillDirectoryBuffer(
2226
                            &FileContext->DirBuffer, DirInfo, &Status);
2227

2228
                        if (Result == FALSE)
2229
                        {
2230
                            break;
2231
                        }
2232
                    }
2233

2234
                    if (wcscmp(DirInfo->FileNameBuf, L".") &&
2235
                        wcscmp(DirInfo->FileNameBuf, L".."))
2236
                    {
2237
                        VirtFs->LookupMapNewOrIncNode(DirEntryPlus->entry_out.nodeid);
2238
                    }
2239

2240
                    Offset = DirEntryPlus->dirent.off;
2241
                    Remains -= FUSE_DIRENTPLUS_SIZE(DirEntryPlus);
2242
                    DirEntryPlus = (struct fuse_direntplus *)(
2243
                        (PBYTE)DirEntryPlus +
2244
                        FUSE_DIRENTPLUS_SIZE(DirEntryPlus));
2245
                }
2246
            }
2247

2248
            SafeHeapFree(read_out);
2249
        }
2250
        else
2251
        {
2252
            Status = STATUS_INSUFFICIENT_RESOURCES;
2253
        }
2254

2255
        FspFileSystemReleaseDirectoryBuffer(&FileContext->DirBuffer);
2256
    }
2257

2258
    if (NT_SUCCESS(Status))
2259
    {
2260
        FspFileSystemReadDirectoryBuffer(&FileContext->DirBuffer, Marker,
2261
            Buffer, BufferLength, PBytesTransferred);
2262
    }
2263

2264
    return Status;
2265
}
2266

2267
static NTSTATUS ResolveReparsePoints(FSP_FILE_SYSTEM *FileSystem,
2268
    PWSTR FileName, UINT32 ReparsePointIndex,
2269
    BOOLEAN ResolveLastPathComponent, PIO_STATUS_BLOCK PIoStatus,
2270
    PVOID Buffer, PSIZE_T PSize)
2271
{
2272
    return FspFileSystemResolveReparsePoints(FileSystem,
2273
        GetReparsePointByName, NULL, FileName, ReparsePointIndex,
2274
        ResolveLastPathComponent, PIoStatus, Buffer, PSize);
2275
}
2276

2277
static NTSTATUS SetReparsePoint(FSP_FILE_SYSTEM *FileSystem,
2278
    PVOID FileContext, PWSTR FileName, PVOID Buffer, SIZE_T Size)
2279
{
2280
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
2281
    PREPARSE_DATA_BUFFER ReparseData = (PREPARSE_DATA_BUFFER)Buffer;
2282
    FUSE_SYMLINK_IN *symlink_in;
2283
    FUSE_SYMLINK_OUT symlink_out;
2284
    WCHAR TargetName[MAX_PATH];
2285
    USHORT TargetLength;
2286
    NTSTATUS Status;
2287
    char *filename, *linkname, *targetname;
2288
    int linkname_len, targetname_len;
2289
    uint64_t parent;
2290

2291
    UNREFERENCED_PARAMETER(Size);
2292

2293
    DBG("\"%S\"", FileName);
2294

2295
    if (!(ReparseData->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE))
2296
    {
2297
        return STATUS_INVALID_PARAMETER;
2298
    }
2299

2300
    Cleanup(FileSystem, FileContext, FileName, FspCleanupDelete);
2301

2302
    CopyMemory(TargetName,
2303
        ReparseData->SymbolicLinkReparseBuffer.PathBuffer +
2304
            (ReparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset /
2305
                sizeof(WCHAR)),
2306
        ReparseData->SymbolicLinkReparseBuffer.SubstituteNameLength);
2307

2308
    TargetLength =
2309
        ReparseData->SymbolicLinkReparseBuffer.SubstituteNameLength /
2310
        sizeof(WCHAR);
2311

2312
    TargetName[TargetLength] = TEXT('\0');
2313

2314
    Status = FspPosixMapWindowsToPosixPath(TargetName, &targetname);
2315
    if (!NT_SUCCESS(Status))
2316
    {
2317
        return Status;
2318
    }
2319
    SCOPE_EXIT(targetname, { FspPosixDeletePath(targetname); });
2320

2321
    Status = FspPosixMapWindowsToPosixPath(FileName + 1, &linkname);
2322
    if (!NT_SUCCESS(Status))
2323
    {
2324
        return Status;
2325
    }
2326
    SCOPE_EXIT(linkname, { FspPosixDeletePath(linkname); });
2327

2328
    Status = PathWalkthough(VirtFs, linkname, &filename, &parent);
2329
    if (!NT_SUCCESS(Status))
2330
    {
2331
        return Status;
2332
    }
2333

2334
    linkname_len = lstrlenA(filename) + 1;
2335
    targetname_len = lstrlenA(targetname) + 1;
2336

2337
    symlink_in = (FUSE_SYMLINK_IN *)HeapAlloc(GetProcessHeap(),
2338
        0, sizeof(*symlink_in) + linkname_len + targetname_len);
2339

2340
    if (symlink_in == NULL)
2341
    {
2342
        return STATUS_INSUFFICIENT_RESOURCES;
2343
    }
2344

2345
    FUSE_HEADER_INIT(&symlink_in->hdr, FUSE_SYMLINK, parent,
2346
        linkname_len + targetname_len);
2347

2348
    CopyMemory(symlink_in->names, filename, linkname_len);
2349
    CopyMemory(symlink_in->names + linkname_len, targetname, targetname_len);
2350

2351
    Status = VirtFsFuseRequest(VirtFs->Device, symlink_in,
2352
        symlink_in->hdr.len, &symlink_out, sizeof(symlink_out));
2353

2354
    SafeHeapFree(symlink_in);
2355

2356
    return Status;
2357
}
2358

2359
static NTSTATUS GetDirInfoByName(FSP_FILE_SYSTEM *FileSystem,
2360
    PVOID FileContext0, PWSTR FileName, FSP_FSCTL_DIR_INFO *DirInfo)
2361
{
2362
    VIRTFS *VirtFs = (VIRTFS *)FileSystem->UserContext;
2363
    VIRTFS_FILE_CONTEXT *FileContext = (VIRTFS_FILE_CONTEXT *)FileContext0;
2364
    FUSE_LOOKUP_OUT lookup_out;
2365
    NTSTATUS Status;
2366
    char *filename;
2367

2368
    DBG("\"%S\"", FileName);
2369

2370
    Status = FspPosixMapWindowsToPosixPath(FileName, &filename);
2371
    if (!NT_SUCCESS(Status))
2372
    {
2373
        return Status;
2374
    }
2375
    SCOPE_EXIT(filename, { FspPosixDeletePath(filename); });
2376

2377
    Status = VirtFs->NameAwareRequest(FileContext->NodeId, filename,
2378
        &VIRTFS::SubmitLookupRequest, &lookup_out);
2379

2380
    if (NT_SUCCESS(Status))
2381
    {
2382
        DirInfo->Size = (UINT16)(sizeof(FSP_FSCTL_DIR_INFO) +
2383
            wcslen(FileName) * sizeof(WCHAR));
2384

2385
        SetFileInfo(VirtFs, &lookup_out.entry, &DirInfo->FileInfo);
2386

2387
        CopyMemory(DirInfo->FileNameBuf, FileName,
2388
            DirInfo->Size - sizeof(FSP_FSCTL_DIR_INFO));
2389
    }
2390

2391
    return Status;
2392
}
2393

2394
static FSP_FILE_SYSTEM_INTERFACE VirtFsInterface =
2395
{
2396
    .GetVolumeInfo = GetVolumeInfo,
2397
    .GetSecurityByName = GetSecurityByName,
2398
    .Create = Create,
2399
    .Open = Open,
2400
    .Overwrite = Overwrite,
2401
    .Cleanup = Cleanup,
2402
    .Close = Close,
2403
    .Read = Read,
2404
    .Write = Write,
2405
    .Flush = Flush,
2406
    .GetFileInfo = GetFileInfo,
2407
    .SetBasicInfo = SetBasicInfo,
2408
    .SetFileSize = SetFileSize,
2409
    .CanDelete = CanDelete,
2410
    .Rename = Rename,
2411
    .GetSecurity = GetSecurity,
2412
    .SetSecurity = SetSecurity,
2413
    .ReadDirectory = ReadDirectory,
2414
    .ResolveReparsePoints = ResolveReparsePoints,
2415
    .SetReparsePoint = SetReparsePoint,
2416
    .GetDirInfoByName = GetDirInfoByName
2417
};
2418

2419
static ULONG wcstol_deflt(wchar_t *w, ULONG deflt)
2420
{
2421
    wchar_t *endp;
2422
    ULONG ul = wcstol(w, &endp, 0);
2423
    return L'\0' != w[0] && L'\0' == *endp ? ul : deflt;
2424
}
2425

2426
NTSTATUS VIRTFS::SubmitInitRequest()
2427
{
2428
    NTSTATUS Status;
2429
    FUSE_INIT_IN init_in;
2430
    FUSE_INIT_OUT init_out;
2431

2432
    FUSE_HEADER_INIT(&init_in.hdr, FUSE_INIT, FUSE_ROOT_ID,
2433
        sizeof(init_in.init));
2434

2435
    init_in.init.major = FUSE_KERNEL_VERSION;
2436
    init_in.init.minor = FUSE_KERNEL_MINOR_VERSION;
2437
    init_in.init.max_readahead = 0;
2438
    init_in.init.flags = FUSE_DO_READDIRPLUS | FUSE_MAX_PAGES;
2439

2440
    Status = VirtFsFuseRequest(Device, &init_in, sizeof(init_in),
2441
        &init_out, sizeof(init_out));
2442
    if (!NT_SUCCESS(Status))
2443
    {
2444
        return Status;
2445
    }
2446

2447
    MaxWrite = init_out.init.max_write;
2448
    MaxPages = init_out.init.max_pages ?
2449
        init_out.init.max_pages : FUSE_DEFAULT_MAX_PAGES_PER_REQ;
2450

2451
    DBG("Init: MaxWrite %u bytes, MaxPages %u", MaxWrite, MaxPages);
2452
    return STATUS_SUCCESS;
2453
}
2454

2455
NTSTATUS VIRTFS::SubmitDestroyRequest()
2456
{
2457
    NTSTATUS Status;
2458
    FUSE_DESTROY_IN destroy_in;
2459
    FUSE_DESTROY_OUT destroy_out;
2460

2461
    FUSE_HEADER_INIT(&destroy_in.hdr, FUSE_DESTROY, FUSE_ROOT_ID, 0);
2462

2463
    Status = VirtFsFuseRequest(Device, &destroy_in, sizeof(destroy_in),
2464
        &destroy_out, sizeof(destroy_out));
2465
    if (!NT_SUCCESS(Status))
2466
    {
2467
        return Status;
2468
    }
2469

2470
    return STATUS_SUCCESS;
2471
}
2472

2473
NTSTATUS VIRTFS::Start()
2474
{
2475
    NTSTATUS Status;
2476
    FILETIME FileTime;
2477
    FSP_FSCTL_VOLUME_PARAMS VolumeParams;
2478

2479
    Status = SubmitInitRequest();
2480
    if (!NT_SUCCESS(Status))
2481
    {
2482
        return Status;
2483
    }
2484

2485
    GetSystemTimeAsFileTime(&FileTime);
2486

2487
    ZeroMemory(&VolumeParams, sizeof(VolumeParams));
2488
    VolumeParams.Version = sizeof(FSP_FSCTL_VOLUME_PARAMS);
2489
    VolumeParams.SectorSize = ALLOCATION_UNIT;
2490
    VolumeParams.SectorsPerAllocationUnit = 1;
2491
    VolumeParams.VolumeCreationTime = ((PLARGE_INTEGER)&FileTime)->QuadPart;
2492
//    VolumeParams.VolumeSerialNumber = 0;
2493
    VolumeParams.FileInfoTimeout = 1000;
2494
    VolumeParams.CaseSensitiveSearch = !CaseInsensitive;
2495
    VolumeParams.CasePreservedNames = 1;
2496
    VolumeParams.UnicodeOnDisk = 1;
2497
    VolumeParams.PersistentAcls = 1;
2498
    VolumeParams.ReparsePoints = 1;
2499
    VolumeParams.ReparsePointsAccessCheck = 0;
2500
    VolumeParams.PostCleanupWhenModifiedOnly = 1;
2501
//    VolumeParams.PassQueryDirectoryPattern = 1;
2502
    VolumeParams.PassQueryDirectoryFileName = 1;
2503
    VolumeParams.FlushAndPurgeOnCleanup = 1;
2504
    VolumeParams.UmFileContextIsUserContext2 = 1;
2505
//    VolumeParams.DirectoryMarkerAsNextOffset = 1;
2506
    wcscpy_s(VolumeParams.FileSystemName,
2507
        sizeof(VolumeParams.FileSystemName) / sizeof(WCHAR),
2508
        FileSystemName.empty() ?  FS_SERVICE_NAME : FileSystemName.c_str());
2509

2510
    Status = FspFileSystemCreate((PWSTR)TEXT(FSP_FSCTL_DISK_DEVICE_NAME),
2511
        &VolumeParams, &VirtFsInterface, &FileSystem);
2512
    if (!NT_SUCCESS(Status))
2513
    {
2514
        return Status;
2515
    }
2516
    FileSystem->UserContext = this;
2517

2518
    FspFileSystemSetDebugLog(FileSystem, DebugFlags);
2519

2520
    Status = FspFileSystemSetMountPoint(FileSystem,
2521
        (MountPoint == L"*") ? NULL : (PWSTR)MountPoint.c_str());
2522
    if (!NT_SUCCESS(Status))
2523
    {
2524
        goto out_del_fs;
2525
    }
2526

2527
    Status = FspFileSystemStartDispatcher(FileSystem, 0);
2528
    if (!NT_SUCCESS(Status))
2529
    {
2530
        FspServiceLog(EVENTLOG_ERROR_TYPE,
2531
            (PWSTR)L"Failed to mount virtio-fs file system.");
2532
        goto out_del_fs;
2533
    }
2534

2535
    return STATUS_SUCCESS;
2536

2537
out_del_fs:
2538
    FspFileSystemDelete(FileSystem);
2539

2540
    return Status;
2541
}
2542

2543
static NTSTATUS ParseArgs(ULONG argc, PWSTR *argv,
2544
    ULONG& DebugFlags, std::wstring& DebugLogFile, bool& CaseInsensitive,
2545
    std::wstring& FileSystemName, std::wstring& MountPoint, std::wstring& Tag, std::wstring& Owner)
2546
{
2547
#define argtos(v) if (arge > ++argp && *argp) v.assign(*argp); else goto usage
2548
#define argtol(v) if (arge > ++argp) v = wcstol_deflt(*argp, v); \
2549
    else goto usage
2550

2551
    wchar_t **argp, **arge;
2552

2553
    for (argp = argv + 1, arge = argv + argc; arge > argp; argp++)
2554
    {
2555
        if (L'-' != argp[0][0])
2556
        {
2557
            break;
2558
        }
2559

2560
        switch (argp[0][1])
2561
        {
2562
            case L'?':
2563
                goto usage;
2564
            case L'd':
2565
                argtol(DebugFlags);
2566
                break;
2567
            case L'D':
2568
                argtos(DebugLogFile);
2569
                break;
2570
            case L'i':
2571
                CaseInsensitive = true;
2572
                break;
2573
            case L'F':
2574
                argtos(FileSystemName);
2575
                break;
2576
            case L'm':
2577
                argtos(MountPoint);
2578
                break;
2579
            case L't':
2580
                argtos(Tag);
2581
                break;
2582
            case L'o':
2583
                argtos(Owner);
2584
                if (!CheckIds(Owner))
2585
                {
2586
                    goto usage;
2587
                }
2588
                break;
2589
            default:
2590
                goto usage;
2591
        }
2592
    }
2593

2594
    if (arge > argp)
2595
    {
2596
        goto usage;
2597
    }
2598

2599
    return STATUS_SUCCESS;
2600

2601
#undef argtos
2602
#undef argtol
2603

2604
usage:
2605
    static wchar_t usage[] = L""
2606
        "Usage: %s OPTIONS\n"
2607
        "\n"
2608
        "options:\n"
2609
        "    -d DebugFlags       [-1: enable all debug logs]\n"
2610
        "    -D DebugLogFile     [file path; use - for stderr]\n"
2611
        "    -i                  [case insensitive file system]\n"
2612
        "    -F FileSystemName   [file system name for OS]\n"
2613
        "    -m MountPoint       [X:|* (required if no UNC prefix)]\n"
2614
        "    -t Tag              [mount tag; max 36 symbols]\n"
2615
        "    -o UID:GID          [host owner UID:GID]\n";
2616

2617
    FspServiceLog(EVENTLOG_ERROR_TYPE, usage, FS_SERVICE_NAME);
2618

2619
    return STATUS_UNSUCCESSFUL;
2620
}
2621

2622
static VOID ParseRegistry(ULONG& DebugFlags, std::wstring& DebugLogFile,
2623
    bool &CaseInsensitive, std::wstring& FileSystemName, std::wstring& MountPoint, std::wstring& Owner)
2624
{
2625
    RegistryGetVal(FS_SERVICE_REGKEY, L"DebugFlags", DebugFlags);
2626
    RegistryGetVal(FS_SERVICE_REGKEY, L"DebugLogFile", DebugLogFile);
2627
    RegistryGetVal(FS_SERVICE_REGKEY, L"CaseInsensitive", CaseInsensitive);
2628
    RegistryGetVal(FS_SERVICE_REGKEY, L"FileSystemName", FileSystemName);
2629
    RegistryGetVal(FS_SERVICE_REGKEY, L"MountPoint", MountPoint);
2630
    RegistryGetVal(FS_SERVICE_REGKEY, L"Owner", Owner);
2631
}
2632

2633
static VOID ParseRegistryCommon()
2634
{
2635
    OverflowUid = DEFAULT_OVERFLOWUID;
2636
    OverflowGid = DEFAULT_OVERFLOWGID;
2637

2638
    RegistryGetVal(FS_SERVICE_REGKEY, L"OverflowUid", OverflowUid);
2639
    RegistryGetVal(FS_SERVICE_REGKEY, L"OverflowGid", OverflowGid);
2640
}
2641

2642
static NTSTATUS DebugLogSet(const std::wstring& DebugLogFile)
2643
{
2644
    HANDLE DebugLogHandle = INVALID_HANDLE_VALUE;
2645

2646
    if (DebugLogFile == L"-")
2647
    {
2648
        DebugLogHandle = GetStdHandle(STD_ERROR_HANDLE);
2649
    }
2650
    else
2651
    {
2652
        DebugLogHandle = CreateFileW(DebugLogFile.c_str(), FILE_APPEND_DATA,
2653
            FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_ALWAYS,
2654
            FILE_ATTRIBUTE_NORMAL, 0);
2655
    }
2656

2657
    if (DebugLogHandle == INVALID_HANDLE_VALUE)
2658
    {
2659
        FspServiceLog(EVENTLOG_ERROR_TYPE,
2660
            (PWSTR)L"Can not open debug log file.");
2661
        return STATUS_UNSUCCESSFUL;
2662
    }
2663

2664
    FspDebugLogSetHandle(DebugLogHandle);
2665

2666
    return STATUS_SUCCESS;
2667
}
2668

2669
static NTSTATUS SvcStart(FSP_SERVICE* Service, ULONG argc, PWSTR* argv)
2670
{
2671
    std::wstring DebugLogFile{};
2672
    ULONG DebugFlags{ 0 };
2673
    bool CaseInsensitive{ false };
2674
    std::wstring MountPoint{ L"*" };
2675
    std::wstring FileSystemName{};
2676
    std::wstring Tag{};
2677
    std::wstring Owner{};
2678
    uint32_t OwnerUid, OwnerGid;
2679
    bool AutoOwnerIds;
2680
    VIRTFS *VirtFs;
2681
    NTSTATUS Status{ STATUS_SUCCESS };
2682
    DWORD Error;
2683

2684
    if (argc > 1)
2685
    {
2686
        Status = ParseArgs(argc, argv, DebugFlags, DebugLogFile,
2687
            CaseInsensitive, FileSystemName, MountPoint, Tag, Owner);
2688
    }
2689
    else
2690
    {
2691
        ParseRegistry(DebugFlags, DebugLogFile, CaseInsensitive, FileSystemName, MountPoint,
2692
            Owner);
2693
    }
2694

2695
    ParseRegistryCommon();
2696

2697
    AutoOwnerIds = !ParseIds(Owner, OwnerUid, OwnerGid);
2698

2699
    if (!NT_SUCCESS(Status))
2700
    {
2701
        return Status;
2702
    }
2703

2704
    if (!DebugLogFile.empty())
2705
    {
2706
        Status = DebugLogSet(DebugLogFile);
2707
        if (!NT_SUCCESS(Status))
2708
        {
2709
            return Status;
2710
        }
2711
    }
2712

2713
    try
2714
    {
2715
        VirtFs = new VIRTFS(DebugFlags, CaseInsensitive, FileSystemName, MountPoint, Tag,
2716
            AutoOwnerIds, OwnerUid, OwnerGid);
2717
    }
2718
    catch (std::bad_alloc)
2719
    {
2720
        return STATUS_INSUFFICIENT_RESOURCES;
2721
    }
2722

2723
    Service->UserContext = VirtFs;
2724

2725
    if (!VirtFs->DevHandleNotification.CreateUnregWork())
2726
    {
2727
        Status = STATUS_UNSUCCESSFUL;
2728
        goto out_free_virtfs;
2729
    }
2730

2731
    Error = VirtFs->DevInterfaceNotification.Register(DeviceNotificationCallback,
2732
        VirtFs, GUID_DEVINTERFACE_VIRT_FS);
2733
    if (Error != ERROR_SUCCESS)
2734
    {
2735
        Status = FspNtStatusFromWin32(Error);
2736
        goto out_unreg_di_notify;
2737
    }
2738

2739
    Error = VirtFs->FindDeviceInterface();
2740
    if (Error != ERROR_SUCCESS)
2741
    {
2742
        // Wait for device to be found by arrival notification callback.
2743
        FspServiceLog(EVENTLOG_INFORMATION_TYPE,
2744
            (PWSTR)L"The %s service will start and wait for the device.",
2745
	    FS_SERVICE_NAME);
2746
        return STATUS_SUCCESS;
2747
    }
2748

2749
    Error = VirtFs->DevHandleNotification.Register(DeviceNotificationCallback,
2750
        VirtFs, VirtFs->Device);
2751
    if (Error != ERROR_SUCCESS)
2752
    {
2753
        Status = FspNtStatusFromWin32(Error);
2754
        goto out_close_handle;
2755
    }
2756

2757
    Status = VirtFs->Start();
2758
    if (!NT_SUCCESS(Status))
2759
    {
2760
        goto out_unreg_dh_notify;
2761
    }
2762

2763
    return STATUS_SUCCESS;
2764

2765
out_unreg_dh_notify:
2766
    VirtFs->DevHandleNotification.Unregister();
2767
out_close_handle:
2768
    CloseHandle(VirtFs->Device);
2769
out_unreg_di_notify:
2770
    VirtFs->DevInterfaceNotification.Unregister();
2771
out_free_virtfs:
2772
    delete VirtFs;
2773

2774
    return Status;
2775
}
2776

2777
static NTSTATUS SvcStop(FSP_SERVICE *Service)
2778
{
2779
    VIRTFS *VirtFs = (VIRTFS *)Service->UserContext;
2780

2781
    VirtFs->Stop();
2782
    VirtFs->DevHandleNotification.Unregister();
2783
    VirtFs->CloseDeviceInterface();
2784
    VirtFs->DevInterfaceNotification.Unregister();
2785
    delete VirtFs;
2786

2787
    return STATUS_SUCCESS;
2788
}
2789

2790
static NTSTATUS SvcControl(FSP_SERVICE *Service, ULONG Control,
2791
    ULONG EventType, PVOID EventData)
2792
{
2793
    UNREFERENCED_PARAMETER(Service);
2794
    UNREFERENCED_PARAMETER(EventType);
2795
    UNREFERENCED_PARAMETER(EventData);
2796

2797
    switch (Control)
2798
    {
2799
        case SERVICE_CONTROL_DEVICEEVENT:
2800
            break;
2801

2802
        default:
2803
            break;
2804
    }
2805

2806
    return STATUS_SUCCESS;
2807
}
2808

2809
int wmain(int argc, wchar_t **argv)
2810
{
2811
    FSP_SERVICE *Service;
2812
    NTSTATUS Result;
2813
    ULONG ExitCode;
2814

2815
    UNREFERENCED_PARAMETER(argc);
2816
    UNREFERENCED_PARAMETER(argv);
2817

2818
    Result = FspLoad(0);
2819

2820
    if (!NT_SUCCESS(Result))
2821
    {
2822
        fwprintf(stderr,
2823
            L"The service %s failed to load WinFsp DLL (Status=%lx).",
2824
            FS_SERVICE_NAME, Result);
2825

2826
        return ERROR_DELAY_LOAD_FAILED;
2827
    }
2828

2829
    Result = FspServiceCreate((PWSTR)FS_SERVICE_NAME, SvcStart, SvcStop,
2830
            SvcControl, &Service);
2831

2832
    if (!NT_SUCCESS(Result))
2833
    {
2834
        FspServiceLog(EVENTLOG_ERROR_TYPE,
2835
            (PWSTR)L"The service %s cannot be created (Status=%lx).",
2836
            FS_SERVICE_NAME, Result);
2837
        return FspWin32FromNtStatus(Result);
2838
    }
2839
    FspServiceAllowConsoleMode(Service);
2840
    FspServiceAcceptControl(Service, SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN);
2841
    Result = FspServiceLoop(Service);
2842
    ExitCode = FspServiceGetExitCode(Service);
2843
    FspServiceDelete(Service);
2844

2845
    if (!NT_SUCCESS(Result))
2846
    {
2847
        FspServiceLog(EVENTLOG_ERROR_TYPE,
2848
            (PWSTR)L"The service %s has failed to run (Status=%lx).",
2849
            FS_SERVICE_NAME, Result);
2850
        return FspWin32FromNtStatus(Result);
2851
    }
2852

2853
    return ExitCode;
2854
}
2855

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.