Files
nt-lite/code/drivers/fs/ntfs/ntfs3g/ntfs3g_driver.c
T

1155 lines
38 KiB
C

#include <ntlite/ddk/module.h>
/* 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;
}
}
static KSTATUS DriverInitialize(NTL_MODULE* module) {
(void)module;
DbgPrint("NTFS3G: Init (libntfs-3g replacement driver)\n");
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;
}
NTL_DECLARE_MODULE(DriverInitialize);