1155 lines
38 KiB
C
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);
|