#include /* NTFS-3G filesystem provider for NTLite. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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);