Files
nt-lite/code/drivers/fs/ntfs/ntfs3g/ntfs3g_driver.c
T
maksw 73abd6f90d isolate POSIX builds from the host UCRT, wire lwext4 and ntfs-3g as ext4.sys and ntfs.sys, split the FAT driver into directory/file/async/driver/mount units, add posixsrv and dwm plus input/console/desktop syscall clients and a pile of new smoke targets, rename serial.sys to cereal.sys, name HDA/USB/lwIP/AHCI workers, harden ATA probing/reset/removal around actual hardware deadlines, absent-channel detection, diagnostics and explicit worker lifetime, redo ACPI S5 shutdown around GAS/MMIO and HW-reduced firmware, scan SSDTs for _S5 and deal with Intel SMI sleep trapping, make serial and SMP timeouts use real monotonic deadlines, add thread names and fix terminal thread teardown, scheduler and APC prepare/commit races, make advisory file locks queryable and preserve split ranges and add mutable file append state, add shared-memory pipes with kernel-assisted wait/wake, introduce capability-scoped input and display managers, split surface map rights into read/write, add explicit unmap and replace present with commit/generation/damage tracking while keeping physical presentation under the display manager, decouple native exception classes from Win32 exception numbers and translate them in kernel32, add opaque process startup data, bump the resource protocol to v3 with namespace directories, symlinks and enumeration and service control to v2 with generic READY handle exports, add the new POSIX errno/status surface and rebuild fds around posixsrv-owned shared open descriptions, descriptor-local flags, exact inheritance snapshots, dup/fcntl/record locks and shared-ring pipes, add waitpid, POSIX credentials/groups/sessions/passwd/group lookups and move sockets onto stable open descriptions, port the shells and apps to the new console/input services and surface commit API, teach init to load services.ini and start/supervise RSSRV, DWM and generic services under restricted capabilities while publishing their endpoints through RSSRV, split RSSRV into namespace, volume, display, session, input, console and resource components and make /SystemRoot a real symlink to the independently published system volume, shift the syscall table again, expand lock/resource/surface/POSIX/service/thread smoke coverage, make docs scream ASK QUESTIONS and DISCUSS at LLMs, replace MIT No Attribution with WTFPL+.
2026-09-16 10:33:20 +02:00

1155 lines
38 KiB
C

/* NTFS-3G filesystem provider for NTLite. */
#include "config.h"
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <ntlite/ddi/io_driver.h>
#include <ntlite/ddk/ddk.h>
#include <ntlite/ddk/file.h>
#include <ntlite/ddk/memory.h>
#include <ntlite/ddk/debug.h>
#include <ntlite/ddk/list.h>
#include <ntlite/ddk/mutex.h>
#include <ntlite/ddk/sync.h>
#include <ntfs-3g/device.h>
#include <ntfs-3g/volume.h>
#include <ntfs-3g/inode.h>
#include <ntfs-3g/attrib.h>
#include <ntfs-3g/dir.h>
#include <ntfs-3g/unistr.h>
#include <ntfs-3g/layout.h>
#include <ntfs-3g/ntfstime.h>
#include "ntfs3g_device.h"
#define NTL_NTFS3G_SECTOR_SIZE 512u
typedef struct NTL_NTFS3G_VOLUME {
uint8_t DiskIndex;
uint64_t PartitionLba;
uint64_t PartitionSectors;
NTL_NTFS3G_DEVICE_CONTEXT DeviceContext;
struct ntfs_device* Device;
ntfs_volume* Ntfs;
IO_VOLUME* Volume;
LIST_ENTRY MountLink;
bool Dead;
} NTL_NTFS3G_VOLUME;
typedef struct NTL_NTFS3G_FILE {
NTL_NTFS3G_VOLUME* Volume;
ntfs_inode* Inode;
ntfs_attr* Data;
} NTL_NTFS3G_FILE;
typedef struct NTL_NTFS3G_DIR {
NTL_NTFS3G_VOLUME* Volume;
ntfs_inode* Inode;
s64 Position;
} NTL_NTFS3G_DIR;
static LIST_ENTRY gNtfs3gMounts;
static uint32_t gNtfs3gMountedCount;
static MUTEX gNtfs3gMountMutex;
static MUTEX gNtfs3gFsMutex;
static SPINLOCK gNtfs3gMutexInitLock = SPINLOCK_INIT;
static int gNtfs3gMutexesReady;
static void Ntfs3gListInit(LIST_ENTRY* head) {
head->Fwd = head;
head->Bwd = head;
}
static void Ntfs3gInitStaticMutex(MUTEX* mutex) {
memset(mutex, 0, sizeof(*mutex));
ObInitializeObjectHeader(&mutex->Header, ObjectTypeMutex);
mutex->Lock = (SPINLOCK)SPINLOCK_INIT;
mutex->Owner = (THREAD*)0;
Ntfs3gListInit(&mutex->WaitList);
Ntfs3gListInit(&mutex->OwnerLink);
mutex->DonationClass = KeSchedulingNormal;
mutex->DonationPriority = PriorityLow;
}
static void Ntfs3gEnsureMutexes(void) {
if (gNtfs3gMutexesReady)
return;
uintptr_t flags = KeAcquireSpinlock(&gNtfs3gMutexInitLock);
if (!gNtfs3gMutexesReady) {
Ntfs3gInitStaticMutex(&gNtfs3gMountMutex);
Ntfs3gInitStaticMutex(&gNtfs3gFsMutex);
gNtfs3gMutexesReady = 1;
}
KeReleaseSpinlock(&gNtfs3gMutexInitLock, flags);
}
static KSTATUS Ntfs3gAcquireFs(void) {
Ntfs3gEnsureMutexes();
if (!KeCanWait())
return NTLSTATUS_INVALID_IRQL;
KSTATUS status = KeAcquireMutex(&gNtfs3gFsMutex, KE_WAIT_INFINITE);
return KE_MUTEX_ACQUIRE_SUCCEEDED(status) ? NTLSTATUS_SUCCESS : status;
}
static void Ntfs3gReleaseFs(void) {
KeReleaseMutex(&gNtfs3gFsMutex);
}
static KSTATUS Ntfs3gMapErrno(int error) {
switch (error) {
case 0: return NTLSTATUS_SUCCESS;
case ENOENT:
case ENXIO:
case ENODEV: return NTLSTATUS_NOT_FOUND;
case EEXIST: return NTLSTATUS_ALREADY_EXISTS;
case ENOMEM: return NTLSTATUS_NO_MEMORY;
case ENOSPC:
case EFBIG:
case EMLINK: return NTLSTATUS_LIMIT_REACHED;
case EACCES:
case EPERM:
case EROFS: return NTLSTATUS_ACCESS_DENIED;
case ENOTSUP: return NTLSTATUS_NOT_SUPPORTED;
case EIO: return NTLSTATUS_DEVICE_ERROR;
case ENOTEMPTY:
case EISDIR:
case ENOTDIR:
case EINVAL: return NTLSTATUS_INVALID_PARAMETER;
default: return NTLSTATUS_FAILURE;
}
}
static NTL_NTFS3G_VOLUME* Ntfs3gVolume(IO_VOLUME_PROVIDER* provider) {
return provider ? (NTL_NTFS3G_VOLUME*)provider->Context : (NTL_NTFS3G_VOLUME*)0;
}
static char* Ntfs3gDupPath(SYS_STRING_VIEW path) {
if (path.Length > SIZE_MAX - 1)
return (char*)0;
char* out = (char*)MmAllocatePool(path.Length + 1);
if (!out)
return (char*)0;
if (path.Length)
memcpy(out, path.Data, path.Length);
out[path.Length] = '\0';
return out;
}
static bool Ntfs3gIsDirectory(const ntfs_inode* ni) {
return ni && ni->mrec && (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) != 0;
}
static uint32_t Ntfs3gAttributes(const ntfs_inode* ni) {
if (!ni)
return 0;
uint32_t out = 0;
if (Ntfs3gIsDirectory(ni)) out |= SYS_FILE_ATTR_DIRECTORY;
if (ni->flags & FILE_ATTR_READONLY) out |= SYS_FILE_ATTR_READ_ONLY;
if (ni->flags & FILE_ATTR_HIDDEN) out |= SYS_FILE_ATTR_HIDDEN;
if (ni->flags & FILE_ATTR_SYSTEM) out |= SYS_FILE_ATTR_SYSTEM;
if (ni->flags & FILE_ATTR_ARCHIVE) out |= SYS_FILE_ATTR_ARCHIVE;
return out;
}
static void Ntfs3gSetTime(SYS_SYSTEM_TIME* out, ntfs_time value) {
memset(out, 0, sizeof(*out));
struct timespec ts = ntfs2timespec(value);
if (ts.tv_sec < 0)
return;
uint64_t seconds = (uint64_t)ts.tv_sec;
out->EpochSeconds = seconds;
out->Nanosecond = ts.tv_nsec > 0 ? (uint32_t)ts.tv_nsec : 0;
out->Millisecond = (uint16_t)(out->Nanosecond / 1000000u);
out->Flags = SYS_TIME_FLAG_VALID;
out->TimeZoneMinutes = SYS_TIME_UNSPECIFIED_TZ;
uint64_t days = seconds / 86400ull;
uint32_t rem = (uint32_t)(seconds % 86400ull);
out->Hour = (uint8_t)(rem / 3600u);
out->Minute = (uint8_t)((rem % 3600u) / 60u);
out->Second = (uint8_t)(rem % 60u);
out->DayOfWeek = (uint8_t)((days + 4u) % 7u);
int64_t z = (int64_t)days + 719468ll;
int64_t era = (z >= 0 ? z : z - 146096ll) / 146097ll;
uint32_t doe = (uint32_t)(z - era * 146097ll);
uint32_t yoe = (doe - doe / 1460u + doe / 36524u - doe / 146096u) / 365u;
int64_t year = (int64_t)yoe + era * 400ll;
uint32_t doy = doe - (365u * yoe + yoe / 4u - yoe / 100u);
uint32_t mp = (5u * doy + 2u) / 153u;
uint32_t day = doy - (153u * mp + 2u) / 5u + 1u;
uint32_t month = mp + (mp < 10u ? 3u : (uint32_t)-9);
year += (month <= 2u);
out->Year = (uint16_t)year;
out->Month = (uint8_t)month;
out->Day = (uint8_t)day;
}
static void Ntfs3gFillStat(NTL_NTFS3G_VOLUME* volume, ntfs_inode* ni,
ntfs_attr* data, SYS_FILE_STAT* out) {
memset(out, 0, sizeof(*out));
out->Size = sizeof(*out);
out->Type = Ntfs3gIsDirectory(ni) ? SYS_FILE_TYPE_DIRECTORY : SYS_FILE_TYPE_REGULAR;
out->Attributes = Ntfs3gAttributes(ni);
out->LinkCount = ni && ni->mrec ? (uint32_t)le16_to_cpu(ni->mrec->link_count) : 1u;
if (data && data->data_size > 0)
out->FileSize = (uint64_t)data->data_size;
else if (ni && !Ntfs3gIsDirectory(ni) && ni->data_size > 0)
out->FileSize = (uint64_t)ni->data_size;
if (ni)
out->FileId = ni->mft_no;
if (volume && volume->Volume)
out->VolumeId = IoGetVolumeInstanceId(volume->Volume);
if (ni) {
Ntfs3gSetTime(&out->CreationTime, ni->creation_time);
Ntfs3gSetTime(&out->LastAccessTime, ni->last_access_time);
Ntfs3gSetTime(&out->LastWriteTime, ni->last_data_change_time);
Ntfs3gSetTime(&out->ChangeTime, ni->last_mft_change_time);
}
}
static ntfs_attr* Ntfs3gOpenData(ntfs_inode* ni) {
return ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
}
static int Ntfs3gSplitPath(const char* path, char** parentOut,
const char** leafOut, ntfschar** unicodeOut, int* unicodeLenOut) {
*parentOut = (char*)0;
*leafOut = (const char*)0;
*unicodeOut = (ntfschar*)0;
*unicodeLenOut = 0;
if (!path || !*path) {
errno = EINVAL;
return -1;
}
const char* slash = strrchr(path, '/');
const char* leaf = slash ? slash + 1 : path;
if (!*leaf) {
errno = EINVAL;
return -1;
}
size_t parentLen = slash ? (size_t)(slash - path) : 0;
char* parent = (char*)MmAllocatePool(parentLen + 1);
if (!parent) {
errno = ENOMEM;
return -1;
}
if (parentLen)
memcpy(parent, path, parentLen);
parent[parentLen] = '\0';
ntfschar* unicode = (ntfschar*)0;
int unicodeLen = ntfs_mbstoucs(leaf, &unicode);
if (unicodeLen <= 0 || unicodeLen > 255) {
int saved = errno ? errno : EINVAL;
if (unicode)
ntfs_ucsfree(unicode);
MmFreePool(parent);
errno = saved;
return -1;
}
*parentOut = parent;
*leafOut = leaf;
*unicodeOut = unicode;
*unicodeLenOut = unicodeLen;
return 0;
}
static ntfs_inode* Ntfs3gOpenParent(NTL_NTFS3G_VOLUME* volume, const char* parentPath) {
return ntfs_pathname_to_inode(volume->Ntfs, (ntfs_inode*)0, parentPath ? parentPath : "");
}
static int Ntfs3gDeletePathLocked(NTL_NTFS3G_VOLUME* volume, const char* path, bool directory) {
char* parentPath = (char*)0;
const char* leaf = (const char*)0;
ntfschar* unicode = (ntfschar*)0;
int unicodeLen = 0;
if (Ntfs3gSplitPath(path, &parentPath, &leaf, &unicode, &unicodeLen))
return -1;
(void)leaf;
ntfs_inode* parent = Ntfs3gOpenParent(volume, parentPath);
if (!parent) {
int saved = errno;
ntfs_ucsfree(unicode);
MmFreePool(parentPath);
errno = saved;
return -1;
}
u64 mref = ntfs_inode_lookup_by_name(parent, unicode, unicodeLen);
if (mref == (u64)-1) {
int saved = errno;
ntfs_inode_close(parent);
ntfs_ucsfree(unicode);
MmFreePool(parentPath);
errno = saved;
return -1;
}
ntfs_inode* ni = ntfs_inode_open(volume->Ntfs, mref);
if (!ni) {
int saved = errno;
ntfs_inode_close(parent);
ntfs_ucsfree(unicode);
MmFreePool(parentPath);
errno = saved;
return -1;
}
if (directory != Ntfs3gIsDirectory(ni)) {
ntfs_inode_close(ni);
ntfs_inode_close(parent);
ntfs_ucsfree(unicode);
MmFreePool(parentPath);
errno = directory ? ENOTDIR : EISDIR;
return -1;
}
if (directory && ntfs_check_empty_dir(ni)) {
int saved = errno;
ntfs_inode_close(ni);
ntfs_inode_close(parent);
ntfs_ucsfree(unicode);
MmFreePool(parentPath);
errno = saved;
return -1;
}
/* ntfs_delete owns/closes both ni and parent on every return path. */
int rc = ntfs_delete(volume->Ntfs, path, ni, parent, unicode, (u8)unicodeLen);
int saved = errno;
ntfs_ucsfree(unicode);
MmFreePool(parentPath);
errno = saved;
return rc;
}
static KSTATUS Ntfs3gOpen(IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW path, void** fileCtx) {
if (!fileCtx)
return NTLSTATUS_INVALID_PARAMETER;
*fileCtx = (void*)0;
NTL_NTFS3G_VOLUME* volume = Ntfs3gVolume(provider);
if (!volume || volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
char* p = Ntfs3gDupPath(path);
if (!p)
return NTLSTATUS_NO_MEMORY;
NTL_NTFS3G_FILE* file = (NTL_NTFS3G_FILE*)MmAllocatePool(sizeof(*file));
if (!file) {
MmFreePool(p);
return NTLSTATUS_NO_MEMORY;
}
memset(file, 0, sizeof(*file));
file->Volume = volume;
KSTATUS status = Ntfs3gAcquireFs();
if (KSUCCESS(status)) {
file->Inode = ntfs_pathname_to_inode(volume->Ntfs, (ntfs_inode*)0, p);
if (!file->Inode) {
status = Ntfs3gMapErrno(errno);
} else if (Ntfs3gIsDirectory(file->Inode)) {
ntfs_inode_close(file->Inode);
file->Inode = (ntfs_inode*)0;
status = NTLSTATUS_INVALID_PARAMETER;
} else {
file->Data = Ntfs3gOpenData(file->Inode);
if (!file->Data) {
status = Ntfs3gMapErrno(errno);
ntfs_inode_close(file->Inode);
file->Inode = (ntfs_inode*)0;
}
}
Ntfs3gReleaseFs();
}
MmFreePool(p);
if (!KSUCCESS(status)) {
MmFreePool(file);
return status;
}
*fileCtx = file;
return NTLSTATUS_SUCCESS;
}
static KSTATUS Ntfs3gRead(IO_VOLUME_PROVIDER* provider, void* fileCtx, uint64_t offset,
void* buffer, size_t size, size_t* bytesRead) {
(void)provider;
if (!fileCtx || (!buffer && size) || !bytesRead)
return NTLSTATUS_INVALID_PARAMETER;
*bytesRead = 0;
NTL_NTFS3G_FILE* file = (NTL_NTFS3G_FILE*)fileCtx;
if (file->Volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
if (offset > INT64_MAX)
return NTLSTATUS_LIMIT_REACHED;
if (size > (size_t)INT64_MAX)
size = (size_t)INT64_MAX;
KSTATUS status = Ntfs3gAcquireFs();
if (!KSUCCESS(status))
return status;
s64 got = ntfs_attr_pread(file->Data, (s64)offset, (s64)size, buffer);
if (got < 0) {
status = Ntfs3gMapErrno(errno);
} else {
*bytesRead = (size_t)got;
if (got > 0)
ntfs_inode_update_times(file->Inode, NTFS_UPDATE_ATIME);
status = NTLSTATUS_SUCCESS;
}
Ntfs3gReleaseFs();
return status;
}
static KSTATUS Ntfs3gWrite(IO_VOLUME_PROVIDER* provider, void* fileCtx, uint64_t offset,
const void* buffer, size_t size, size_t* bytesWritten) {
(void)provider;
if (!fileCtx || (!buffer && size) || !bytesWritten)
return NTLSTATUS_INVALID_PARAMETER;
*bytesWritten = 0;
NTL_NTFS3G_FILE* file = (NTL_NTFS3G_FILE*)fileCtx;
if (file->Volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
if (offset > INT64_MAX || size > (size_t)INT64_MAX ||
(uint64_t)size > (uint64_t)INT64_MAX - offset)
return NTLSTATUS_LIMIT_REACHED;
KSTATUS status = Ntfs3gAcquireFs();
if (!KSUCCESS(status))
return status;
s64 done = ntfs_attr_pwrite(file->Data, (s64)offset, (s64)size, buffer);
if (done < 0) {
status = Ntfs3gMapErrno(errno);
} else {
*bytesWritten = (size_t)done;
if (done > 0) {
file->Inode->flags |= FILE_ATTR_ARCHIVE;
ntfs_inode_update_times(file->Inode, NTFS_UPDATE_MCTIME);
}
status = NTLSTATUS_SUCCESS;
}
Ntfs3gReleaseFs();
return status;
}
static KSTATUS Ntfs3gCreate(IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW path, void** fileCtx) {
if (!fileCtx)
return NTLSTATUS_INVALID_PARAMETER;
*fileCtx = (void*)0;
NTL_NTFS3G_VOLUME* volume = Ntfs3gVolume(provider);
if (!volume || volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
char* p = Ntfs3gDupPath(path);
if (!p)
return NTLSTATUS_NO_MEMORY;
NTL_NTFS3G_FILE* file = (NTL_NTFS3G_FILE*)MmAllocatePool(sizeof(*file));
if (!file) {
MmFreePool(p);
return NTLSTATUS_NO_MEMORY;
}
memset(file, 0, sizeof(*file));
file->Volume = volume;
KSTATUS status = Ntfs3gAcquireFs();
if (KSUCCESS(status)) {
char* parentPath = (char*)0;
const char* leaf = (const char*)0;
ntfschar* unicode = (ntfschar*)0;
int unicodeLen = 0;
if (Ntfs3gSplitPath(p, &parentPath, &leaf, &unicode, &unicodeLen)) {
status = Ntfs3gMapErrno(errno);
} else {
(void)leaf;
ntfs_inode* parent = Ntfs3gOpenParent(volume, parentPath);
if (!parent) {
status = Ntfs3gMapErrno(errno);
} else {
file->Inode = ntfs_create(parent, const_cpu_to_le32(0), unicode,
(u8)unicodeLen, S_IFREG);
int saved = errno;
ntfs_inode_close(parent);
errno = saved;
if (!file->Inode) {
status = Ntfs3gMapErrno(errno);
} else {
file->Data = Ntfs3gOpenData(file->Inode);
if (!file->Data) {
status = Ntfs3gMapErrno(errno);
ntfs_inode_close(file->Inode);
file->Inode = (ntfs_inode*)0;
}
}
}
ntfs_ucsfree(unicode);
MmFreePool(parentPath);
}
Ntfs3gReleaseFs();
}
MmFreePool(p);
if (!KSUCCESS(status)) {
MmFreePool(file);
return status;
}
*fileCtx = file;
return NTLSTATUS_SUCCESS;
}
static KSTATUS Ntfs3gDelete(IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW path) {
NTL_NTFS3G_VOLUME* volume = Ntfs3gVolume(provider);
if (!volume || volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
char* p = Ntfs3gDupPath(path);
if (!p)
return NTLSTATUS_NO_MEMORY;
KSTATUS status = Ntfs3gAcquireFs();
if (KSUCCESS(status)) {
int rc = Ntfs3gDeletePathLocked(volume, p, false);
status = rc ? Ntfs3gMapErrno(errno) : NTLSTATUS_SUCCESS;
Ntfs3gReleaseFs();
}
MmFreePool(p);
return status;
}
static KSTATUS Ntfs3gRename(IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW oldPath,
SYS_STRING_VIEW newPath) {
NTL_NTFS3G_VOLUME* volume = Ntfs3gVolume(provider);
if (!volume || volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
char* oldp = Ntfs3gDupPath(oldPath);
char* newp = Ntfs3gDupPath(newPath);
if (!oldp || !newp) {
if (oldp) MmFreePool(oldp);
if (newp) MmFreePool(newp);
return NTLSTATUS_NO_MEMORY;
}
if (strcmp(oldp, newp) == 0) {
MmFreePool(oldp);
MmFreePool(newp);
return NTLSTATUS_SUCCESS;
}
KSTATUS status = Ntfs3gAcquireFs();
if (KSUCCESS(status)) {
ntfs_inode* existing = ntfs_pathname_to_inode(volume->Ntfs, (ntfs_inode*)0, newp);
if (existing) {
ntfs_inode_close(existing);
status = NTLSTATUS_ALREADY_EXISTS;
} else if (errno != ENOENT) {
status = Ntfs3gMapErrno(errno);
} else {
ntfs_inode* source = ntfs_pathname_to_inode(volume->Ntfs, (ntfs_inode*)0, oldp);
if (!source) {
status = Ntfs3gMapErrno(errno);
} else {
bool sourceDir = Ntfs3gIsDirectory(source);
char* parentPath = (char*)0;
const char* leaf = (const char*)0;
ntfschar* unicode = (ntfschar*)0;
int unicodeLen = 0;
if (Ntfs3gSplitPath(newp, &parentPath, &leaf, &unicode, &unicodeLen)) {
status = Ntfs3gMapErrno(errno);
ntfs_inode_close(source);
} else {
(void)leaf;
ntfs_inode* parent = Ntfs3gOpenParent(volume, parentPath);
if (!parent) {
status = Ntfs3gMapErrno(errno);
ntfs_inode_close(source);
} else if (ntfs_link(source, parent, unicode, (u8)unicodeLen)) {
status = Ntfs3gMapErrno(errno);
ntfs_inode_close(parent);
ntfs_inode_close(source);
} else {
ntfs_inode_close(parent);
ntfs_inode_close(source);
if (Ntfs3gDeletePathLocked(volume, oldp, sourceDir)) {
status = Ntfs3gMapErrno(errno);
(void)Ntfs3gDeletePathLocked(volume, newp, sourceDir);
} else {
status = NTLSTATUS_SUCCESS;
}
}
ntfs_ucsfree(unicode);
MmFreePool(parentPath);
}
}
}
Ntfs3gReleaseFs();
}
MmFreePool(oldp);
MmFreePool(newp);
return status;
}
static KSTATUS Ntfs3gCreateDir(IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW path) {
NTL_NTFS3G_VOLUME* volume = Ntfs3gVolume(provider);
if (!volume || volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
char* p = Ntfs3gDupPath(path);
if (!p)
return NTLSTATUS_NO_MEMORY;
KSTATUS status = Ntfs3gAcquireFs();
if (KSUCCESS(status)) {
char* parentPath = (char*)0;
const char* leaf = (const char*)0;
ntfschar* unicode = (ntfschar*)0;
int unicodeLen = 0;
if (Ntfs3gSplitPath(p, &parentPath, &leaf, &unicode, &unicodeLen)) {
status = Ntfs3gMapErrno(errno);
} else {
(void)leaf;
ntfs_inode* parent = Ntfs3gOpenParent(volume, parentPath);
if (!parent) {
status = Ntfs3gMapErrno(errno);
} else {
ntfs_inode* ni = ntfs_create(parent, const_cpu_to_le32(0), unicode,
(u8)unicodeLen, S_IFDIR);
int saved = errno;
ntfs_inode_close(parent);
errno = saved;
if (!ni)
status = Ntfs3gMapErrno(errno);
else {
ntfs_inode_close(ni);
status = NTLSTATUS_SUCCESS;
}
}
ntfs_ucsfree(unicode);
MmFreePool(parentPath);
}
Ntfs3gReleaseFs();
}
MmFreePool(p);
return status;
}
static KSTATUS Ntfs3gRemoveDir(IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW path) {
NTL_NTFS3G_VOLUME* volume = Ntfs3gVolume(provider);
if (!volume || volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
char* p = Ntfs3gDupPath(path);
if (!p)
return NTLSTATUS_NO_MEMORY;
KSTATUS status = Ntfs3gAcquireFs();
if (KSUCCESS(status)) {
int rc = Ntfs3gDeletePathLocked(volume, p, true);
status = rc ? Ntfs3gMapErrno(errno) : NTLSTATUS_SUCCESS;
Ntfs3gReleaseFs();
}
MmFreePool(p);
return status;
}
static KSTATUS Ntfs3gTruncate(IO_VOLUME_PROVIDER* provider, void* fileCtx,
uint64_t newSize, uint32_t fileFlags) {
(void)provider;
(void)fileFlags;
if (!fileCtx)
return NTLSTATUS_INVALID_PARAMETER;
if (newSize > INT64_MAX)
return NTLSTATUS_LIMIT_REACHED;
NTL_NTFS3G_FILE* file = (NTL_NTFS3G_FILE*)fileCtx;
if (file->Volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
KSTATUS status = Ntfs3gAcquireFs();
if (!KSUCCESS(status))
return status;
if (ntfs_attr_truncate(file->Data, (s64)newSize)) {
status = Ntfs3gMapErrno(errno);
} else {
file->Inode->flags |= FILE_ATTR_ARCHIVE;
ntfs_inode_update_times(file->Inode, NTFS_UPDATE_MCTIME);
status = NTLSTATUS_SUCCESS;
}
Ntfs3gReleaseFs();
return status;
}
static uint64_t Ntfs3gGetSize(IO_VOLUME_PROVIDER* provider, void* fileCtx) {
(void)provider;
if (!fileCtx)
return 0;
NTL_NTFS3G_FILE* file = (NTL_NTFS3G_FILE*)fileCtx;
return file->Data && file->Data->data_size > 0 ? (uint64_t)file->Data->data_size : 0;
}
static KSTATUS Ntfs3gQueryInfo(IO_VOLUME_PROVIDER* provider, void* fileCtx, SYS_FILE_STAT* out) {
(void)provider;
if (!fileCtx || !out)
return NTLSTATUS_INVALID_PARAMETER;
NTL_NTFS3G_FILE* file = (NTL_NTFS3G_FILE*)fileCtx;
if (file->Volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
KSTATUS status = Ntfs3gAcquireFs();
if (!KSUCCESS(status))
return status;
Ntfs3gFillStat(file->Volume, file->Inode, file->Data, out);
Ntfs3gReleaseFs();
return NTLSTATUS_SUCCESS;
}
static KSTATUS Ntfs3gStat(IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW path, SYS_FILE_STAT* out) {
if (!out)
return NTLSTATUS_INVALID_PARAMETER;
NTL_NTFS3G_VOLUME* volume = Ntfs3gVolume(provider);
if (!volume || volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
char* p = Ntfs3gDupPath(path);
if (!p)
return NTLSTATUS_NO_MEMORY;
KSTATUS status = Ntfs3gAcquireFs();
if (KSUCCESS(status)) {
ntfs_inode* ni = ntfs_pathname_to_inode(volume->Ntfs, (ntfs_inode*)0, p);
if (!ni) {
status = Ntfs3gMapErrno(errno);
} else {
ntfs_attr* data = (ntfs_attr*)0;
if (!Ntfs3gIsDirectory(ni))
data = Ntfs3gOpenData(ni);
if (!Ntfs3gIsDirectory(ni) && !data)
status = Ntfs3gMapErrno(errno);
else {
Ntfs3gFillStat(volume, ni, data, out);
status = NTLSTATUS_SUCCESS;
}
if (data) ntfs_attr_close(data);
ntfs_inode_close(ni);
}
Ntfs3gReleaseFs();
}
MmFreePool(p);
return status;
}
static KSTATUS Ntfs3gQueryIdentity(IO_VOLUME_PROVIDER* provider, void* fileCtx,
IO_FILE_IDENTITY* out) {
(void)provider;
if (!fileCtx || !out)
return NTLSTATUS_INVALID_PARAMETER;
NTL_NTFS3G_FILE* file = (NTL_NTFS3G_FILE*)fileCtx;
memset(out, 0, sizeof(*out));
if (file->Volume->Volume)
out->VolumeId = IoGetVolumeInstanceId(file->Volume->Volume);
out->FileId = file->Inode->mft_no;
out->Generation = le16_to_cpu(file->Inode->mrec->sequence_number);
out->Size = file->Data->data_size > 0 ? (uint64_t)file->Data->data_size : 0;
return NTLSTATUS_SUCCESS;
}
static KSTATUS Ntfs3gFlush(IO_VOLUME_PROVIDER* provider, void* fileCtx) {
(void)provider;
if (!fileCtx)
return NTLSTATUS_INVALID_PARAMETER;
NTL_NTFS3G_FILE* file = (NTL_NTFS3G_FILE*)fileCtx;
if (file->Volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
KSTATUS status = Ntfs3gAcquireFs();
if (!KSUCCESS(status))
return status;
if (ntfs_inode_sync(file->Inode) || ntfs_device_sync(file->Volume->Device))
status = Ntfs3gMapErrno(errno);
else
status = NTLSTATUS_SUCCESS;
Ntfs3gReleaseFs();
return status;
}
static void Ntfs3gClose(IO_VOLUME_PROVIDER* provider, void* fileCtx) {
(void)provider;
if (!fileCtx)
return;
NTL_NTFS3G_FILE* file = (NTL_NTFS3G_FILE*)fileCtx;
if (KSUCCESS(Ntfs3gAcquireFs())) {
if (file->Data)
ntfs_attr_close(file->Data);
if (file->Inode)
ntfs_inode_close(file->Inode);
Ntfs3gReleaseFs();
}
MmFreePool(file);
}
static void Ntfs3gQueryStats(IO_VOLUME_PROVIDER* provider, IO_FILESYSTEM_STATS* out) {
(void)provider;
if (out)
memset(out, 0, sizeof(*out));
}
static KSTATUS Ntfs3gOpenDir(IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW path, void** dirCtx) {
if (!dirCtx)
return NTLSTATUS_INVALID_PARAMETER;
*dirCtx = (void*)0;
NTL_NTFS3G_VOLUME* volume = Ntfs3gVolume(provider);
if (!volume || volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
char* p = Ntfs3gDupPath(path);
if (!p)
return NTLSTATUS_NO_MEMORY;
NTL_NTFS3G_DIR* dir = (NTL_NTFS3G_DIR*)MmAllocatePool(sizeof(*dir));
if (!dir) {
MmFreePool(p);
return NTLSTATUS_NO_MEMORY;
}
memset(dir, 0, sizeof(*dir));
dir->Volume = volume;
KSTATUS status = Ntfs3gAcquireFs();
if (KSUCCESS(status)) {
dir->Inode = ntfs_pathname_to_inode(volume->Ntfs, (ntfs_inode*)0, p);
if (!dir->Inode)
status = Ntfs3gMapErrno(errno);
else if (!Ntfs3gIsDirectory(dir->Inode)) {
ntfs_inode_close(dir->Inode);
dir->Inode = (ntfs_inode*)0;
status = NTLSTATUS_INVALID_PARAMETER;
}
Ntfs3gReleaseFs();
}
MmFreePool(p);
if (!KSUCCESS(status)) {
MmFreePool(dir);
return status;
}
*dirCtx = dir;
return NTLSTATUS_SUCCESS;
}
typedef struct NTL_NTFS3G_DIRENT_CAPTURE {
char Name[SYS_DIR_NAME_MAX];
MFT_REF Mref;
unsigned Type;
bool HideNtfsMetadata;
bool Got;
} NTL_NTFS3G_DIRENT_CAPTURE;
static int Ntfs3gFillDir(void* context, const ntfschar* name, const int nameLen,
const int nameType, const s64 pos, const MFT_REF mref,
const unsigned type) {
(void)pos;
NTL_NTFS3G_DIRENT_CAPTURE* capture = (NTL_NTFS3G_DIRENT_CAPTURE*)context;
if (nameType == FILE_NAME_DOS)
return 0;
char* utf8 = (char*)0;
int len = ntfs_ucstombs(name, nameLen, &utf8, 0);
if (len < 0 || !utf8)
return 0;
if ((len == 1 && utf8[0] == '.') || (len == 2 && utf8[0] == '.' && utf8[1] == '.')) {
free(utf8);
return 0;
}
/* Match the old native NTFS provider: metadata names are hidden from
normal root listings, but pathname lookup still has access to them. */
if (capture->HideNtfsMetadata && len > 0 && utf8[0] == '$') {
free(utf8);
return 0;
}
if (capture->Got) {
free(utf8);
errno = 0;
return 1;
}
size_t copy = (size_t)len;
if (copy >= sizeof(capture->Name))
copy = sizeof(capture->Name) - 1;
memcpy(capture->Name, utf8, copy);
capture->Name[copy] = '\0';
free(utf8);
capture->Mref = mref;
capture->Type = type;
capture->Got = true;
return 0;
}
static KSTATUS Ntfs3gReadDir(IO_VOLUME_PROVIDER* provider, void* dirCtx, KDIR_ENTRY* out) {
(void)provider;
if (!dirCtx || !out)
return NTLSTATUS_INVALID_PARAMETER;
NTL_NTFS3G_DIR* dir = (NTL_NTFS3G_DIR*)dirCtx;
if (dir->Volume->Dead)
return NTLSTATUS_DEVICE_REMOVED;
memset(out, 0, sizeof(*out));
KSTATUS status = Ntfs3gAcquireFs();
if (!KSUCCESS(status))
return status;
NTL_NTFS3G_DIRENT_CAPTURE capture;
memset(&capture, 0, sizeof(capture));
capture.HideNtfsMetadata = dir->Inode->mft_no == FILE_root;
errno = 0;
int rc = ntfs_readdir(dir->Inode, &dir->Position, &capture, Ntfs3gFillDir);
if (!capture.Got) {
status = rc == 0 ? NTLSTATUS_NO_MORE_FILES : Ntfs3gMapErrno(errno);
} else {
memcpy(out->Name, capture.Name, sizeof(out->Name));
ntfs_inode* ni = ntfs_inode_open(dir->Volume->Ntfs, capture.Mref);
if (ni) {
ntfs_attr* data = (ntfs_attr*)0;
if (!Ntfs3gIsDirectory(ni))
data = Ntfs3gOpenData(ni);
out->Attr = (uint8_t)Ntfs3gAttributes(ni);
if (data && data->data_size > 0)
out->FileSize = data->data_size > UINT32_MAX ? UINT32_MAX : (uint32_t)data->data_size;
SYS_FILE_STAT stat;
Ntfs3gFillStat(dir->Volume, ni, data, &stat);
out->CreateTime = stat.CreationTime;
out->LastAccessTime = stat.LastAccessTime;
out->LastWriteTime = stat.LastWriteTime;
if (data) ntfs_attr_close(data);
ntfs_inode_close(ni);
} else {
out->Attr = capture.Type == NTFS_DT_DIR ? SYS_FILE_ATTR_DIRECTORY : SYS_FILE_ATTR_ARCHIVE;
}
ntfs_inode_update_times(dir->Inode, NTFS_UPDATE_ATIME);
status = NTLSTATUS_SUCCESS;
}
Ntfs3gReleaseFs();
return status;
}
static void Ntfs3gCloseDir(IO_VOLUME_PROVIDER* provider, void* dirCtx) {
(void)provider;
if (!dirCtx)
return;
NTL_NTFS3G_DIR* dir = (NTL_NTFS3G_DIR*)dirCtx;
if (KSUCCESS(Ntfs3gAcquireFs())) {
if (dir->Inode)
ntfs_inode_close(dir->Inode);
Ntfs3gReleaseFs();
}
MmFreePool(dir);
}
static void Ntfs3gVolumeDead(IO_VOLUME_PROVIDER* provider, IO_VOLUME_REMOVAL_REASON reason) {
(void)reason;
NTL_NTFS3G_VOLUME* volume = Ntfs3gVolume(provider);
if (volume)
volume->Dead = true;
}
static void Ntfs3gDestroyProvider(IO_VOLUME_PROVIDER* provider) {
NTL_NTFS3G_VOLUME* volume = Ntfs3gVolume(provider);
if (!volume)
return;
if (KSUCCESS(Ntfs3gAcquireFs())) {
if (volume->Ntfs) {
(void)ntfs_umount(volume->Ntfs, TRUE);
volume->Ntfs = (ntfs_volume*)0;
volume->Device = (struct ntfs_device*)0; /* ntfs_umount frees it. */
} else if (volume->Device) {
(void)ntfs_device_free(volume->Device);
volume->Device = (struct ntfs_device*)0;
}
Ntfs3gReleaseFs();
}
memset(volume, 0, sizeof(*volume));
MmFreePool(volume);
}
static void Ntfs3gInitProvider(IO_VOLUME_PROVIDER* provider, NTL_NTFS3G_VOLUME* volume) {
memset(provider, 0, sizeof(*provider));
provider->Open = Ntfs3gOpen;
provider->Read = Ntfs3gRead;
provider->Write = Ntfs3gWrite;
provider->Create = Ntfs3gCreate;
provider->Delete = Ntfs3gDelete;
provider->Rename = Ntfs3gRename;
provider->CreateDir = Ntfs3gCreateDir;
provider->RemoveDir = Ntfs3gRemoveDir;
provider->Truncate = Ntfs3gTruncate;
provider->GetSize = Ntfs3gGetSize;
provider->QueryInfo = Ntfs3gQueryInfo;
provider->Stat = Ntfs3gStat;
provider->QueryIdentity = Ntfs3gQueryIdentity;
provider->Flush = Ntfs3gFlush;
provider->Close = Ntfs3gClose;
provider->QueryStats = Ntfs3gQueryStats;
provider->OpenDir = Ntfs3gOpenDir;
provider->ReadDir = Ntfs3gReadDir;
provider->CloseDir = Ntfs3gCloseDir;
provider->VolumeDead = Ntfs3gVolumeDead;
provider->Destroy = Ntfs3gDestroyProvider;
provider->Context = volume;
}
static NTL_NTFS3G_VOLUME* Ntfs3gFindMountLocked(const IO_PARTITION_INFO* partition) {
for (LIST_ENTRY* entry = gNtfs3gMounts.Fwd; entry != &gNtfs3gMounts; entry = entry->Fwd) {
NTL_NTFS3G_VOLUME* volume = CONTAINING_RECORD(entry, NTL_NTFS3G_VOLUME, MountLink);
if (volume->DiskIndex == partition->DiskIndex && volume->PartitionLba == partition->StartLba)
return volume;
}
return (NTL_NTFS3G_VOLUME*)0;
}
static bool Ntfs3gProbePartition(const IO_PARTITION_INFO* partition) {
if (!partition || !partition->SectorCount)
return false;
uint8_t sector[NTL_NTFS3G_SECTOR_SIZE];
if (!KSUCCESS(IoReadDiskSectors(partition->DiskIndex, partition->StartLba, 1, sector)))
return false;
return memcmp(sector + 3, "NTFS ", 8) == 0 && sector[510] == 0x55 && sector[511] == 0xAA;
}
static bool Ntfs3gTryMountPartition(const IO_PARTITION_INFO* partition) {
if (!partition || partition->SectorCount > UINT64_MAX / NTL_NTFS3G_SECTOR_SIZE ||
!IoDiskDevicePresent(partition->DiskIndex) || !Ntfs3gProbePartition(partition))
return false;
Ntfs3gEnsureMutexes();
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(&gNtfs3gMountMutex, KE_WAIT_INFINITE)))
return false;
if (Ntfs3gFindMountLocked(partition)) {
KeReleaseMutex(&gNtfs3gMountMutex);
return false;
}
NTL_NTFS3G_VOLUME* volume = (NTL_NTFS3G_VOLUME*)MmAllocatePool(sizeof(*volume));
if (!volume) {
KeReleaseMutex(&gNtfs3gMountMutex);
return false;
}
memset(volume, 0, sizeof(*volume));
Ntfs3gListInit(&volume->MountLink);
volume->DiskIndex = partition->DiskIndex;
volume->PartitionLba = partition->StartLba;
volume->PartitionSectors = partition->SectorCount;
volume->DeviceContext.DiskIndex = partition->DiskIndex;
volume->DeviceContext.PartitionLba = partition->StartLba;
volume->DeviceContext.PartitionSectors = partition->SectorCount;
KSTATUS status = Ntfs3gAcquireFs();
if (!KSUCCESS(status)) {
MmFreePool(volume);
KeReleaseMutex(&gNtfs3gMountMutex);
return false;
}
volume->Device = Ntfs3gCreateDevice(&volume->DeviceContext);
if (!volume->Device) {
status = Ntfs3gMapErrno(errno);
} else {
volume->Ntfs = ntfs_device_mount(volume->Device, NTFS_MNT_RECOVER | NTFS_MNT_EXCLUSIVE);
if (!volume->Ntfs) {
status = Ntfs3gMapErrno(errno);
(void)ntfs_device_free(volume->Device);
volume->Device = (struct ntfs_device*)0;
} else if (NVolReadOnly(volume->Ntfs)) {
(void)ntfs_umount(volume->Ntfs, TRUE);
volume->Ntfs = (ntfs_volume*)0;
volume->Device = (struct ntfs_device*)0;
status = NTLSTATUS_ACCESS_DENIED;
} else {
status = NTLSTATUS_SUCCESS;
}
}
Ntfs3gReleaseFs();
if (!KSUCCESS(status)) {
DbgPrint("NTFS3G: disk=%u lba=%llu mount failed status=0x%x errno=%d\n",
partition->DiskIndex, (unsigned long long)partition->StartLba, status, errno);
MmFreePool(volume);
KeReleaseMutex(&gNtfs3gMountMutex);
return false;
}
IO_VOLUME_PROVIDER provider;
Ntfs3gInitProvider(&provider, volume);
IO_VOLUME_SOURCE source;
memset(&source, 0, sizeof(source));
source.Type = IoVolumeSourcePartition;
source.DiskIndex = volume->DiskIndex;
source.StartLba = volume->PartitionLba;
source.SectorCount = volume->PartitionSectors;
IO_VOLUME* ioVolume = (IO_VOLUME*)0;
status = IoCreateVolume(&provider, &source, &ioVolume);
if (!KSUCCESS(status)) {
if (ioVolume) {
(void)IoRemoveVolume(ioVolume, IoVolumeUnmounted);
IoReleaseVolume(ioVolume);
} else {
Ntfs3gDestroyProvider(&provider);
}
KeReleaseMutex(&gNtfs3gMountMutex);
return false;
}
volume->Volume = ioVolume;
ListInsertTail(&gNtfs3gMounts, &volume->MountLink);
gNtfs3gMountedCount++;
DbgPrint("NTFS3G: disk=%u lba=%llu mounted volume=%u; total=%u\n",
volume->DiskIndex, (unsigned long long)volume->PartitionLba,
IoGetVolumeInstanceId(ioVolume), gNtfs3gMountedCount);
KeReleaseMutex(&gNtfs3gMountMutex);
return true;
}
static void Ntfs3gUnmountPartition(const IO_PARTITION_INFO* partition, IO_PARTITION_CHANGE change) {
if (!partition)
return;
Ntfs3gEnsureMutexes();
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(&gNtfs3gMountMutex, KE_WAIT_INFINITE)))
return;
NTL_NTFS3G_VOLUME* volume = Ntfs3gFindMountLocked(partition);
if (!volume) {
KeReleaseMutex(&gNtfs3gMountMutex);
return;
}
IO_VOLUME* ioVolume = volume->Volume;
uint32_t instance = IoGetVolumeInstanceId(ioVolume);
ListRemove(&volume->MountLink);
volume->Volume = (IO_VOLUME*)0;
volume->Dead = true;
if (gNtfs3gMountedCount)
gNtfs3gMountedCount--;
uint32_t count = gNtfs3gMountedCount;
KeReleaseMutex(&gNtfs3gMountMutex);
(void)IoRemoveVolume(ioVolume, IoVolumeBackingRemoved);
IoReleaseVolume(ioVolume);
DbgPrint("NTFS3G: disk=%u lba=%llu %s; unmounted volume=%u; total=%u\n",
partition->DiskIndex, (unsigned long long)partition->StartLba,
change == IoPartitionSurpriseRemoved ? "surprise removed" : "removed",
instance, count);
}
static void Ntfs3gPartitionChange(const IO_PARTITION_INFO* partition,
IO_PARTITION_CHANGE change, void* context) {
(void)context;
switch (change) {
case IoPartitionArrived:
(void)Ntfs3gTryMountPartition(partition);
break;
case IoPartitionSurpriseRemoved:
case IoPartitionRemoved:
Ntfs3gUnmountPartition(partition, change);
break;
default:
break;
}
}
KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) {
DbgPrint("NTFS3G: Init (libntfs-3g replacement driver)\n");
if (!ddk)
return NTLSTATUS_INVALID_PARAMETER;
if (init)
init->Name = "ntfs";
Ntfs3gListInit(&gNtfs3gMounts);
gNtfs3gMountedCount = 0;
Ntfs3gEnsureMutexes();
KSTATUS status = IoRegisterPartitionChangeCallback(Ntfs3gPartitionChange, (void*)0);
if (!KSUCCESS(status))
return status;
DbgPrint("NTFS3G: Init complete; mounted %u volume(s).\n", gNtfs3gMountedCount);
return NTLSTATUS_SUCCESS;
}