diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df351b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build +dist +doc +minix_fs_src +dokan_src diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1e8950c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required (VERSION 3.1) + +project (MinixFS) + +if (UNIX OR MINGW) + add_compile_options(-Wall -Wno-sign-compare) +elseif (MSVC) + set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS + _CRT_SECURE_NO_WARNINGS) + add_compile_options(/W3) +endif() + +file(GLOB SOURCES *.c) +file(GLOB HEADERS *.h) + +add_executable(MinixFS ${SOURCES} ${HEADERS}) + +target_link_libraries(MinixFS ${CMAKE_SOURCE_DIR}/dokan.lib) + +set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT MinixFS) diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..39f2f1c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2013 Rafał Harabień + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7433d36 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +MINIX Filesystem Driver +======================= + +General +------- +This is read/write Minix Filesystem driver for Windows. +Driver operates in user mode and uses a Dokan library to communicate with the kernel. +Driver was made for educational purposes in few days and gave me a lot of fun. + +Requirements +------------ + +Dokan has to be installed before using this driver. +You can download it from: http://dokan-dev.net/en/download/ +Filesystem has been tested with version 0.6. It may not work with new versions. + +Usage +----- + +Listing subpartitions in disk image: + + minix -f image_path -l + +Mounting partition: + + minix -f image_path p0s0=mount_point_path + +Mount point has to be unused partition letter or path to a non-existing folder. +You can specify more than one subpartion and mount point in command line. +To unmount all partitions terminate program by pressing Ctrl+C. + +Notes +----- +Using this program can make damage to your disk image, so be sure to make a backup. +Blue Screens could happen too. I am not responsible for any loss of data. diff --git a/bitmap.c b/bitmap.c new file mode 100644 index 0000000..3eada8d --- /dev/null +++ b/bitmap.c @@ -0,0 +1,100 @@ +#include +#include +#include "general.h" +#include "debug.h" + +static unsigned MxfsCountWordBits(uint32_t v) +{ + v = v - ((v >> 1) & 0x55555555); // reuse input as temporary + v = (v & 0x33333333) + ((v >> 2) & 0x33333333); // temp + return (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; // count +} + +unsigned MxfsCountBitsSet(MX_BITMAP *Bm) +{ + unsigned i, Result = 0; + for(i = 0; i < Bm->Size; ++i) + Result += MxfsCountWordBits(Bm->Buffer[i]); + return Result; +} + +void MxfsInitBitmap(MX_BITMAP *Bm, uint32_t *Buffer, unsigned BitsCount) +{ + ASSERT(MxfsCountWordBits(0) == 0); + ASSERT(MxfsCountWordBits(0x11111111) == 8); + ASSERT(MxfsCountWordBits(0xFFFFFFFF) == 32); + + Bm->Buffer = Buffer; + Bm->Size = BitsCount / 32; + Bm->Hint = 0; +} + +void MxfsDestroyBitmap(MX_BITMAP *Bm) +{ + MxfsFree(Bm->Buffer); + Bm->Buffer = NULL; +} + +int MxfsFindClearBit(MX_BITMAP *Bm) +{ + unsigned i, j; + + for(i = 0; i < Bm->Size; ++i) + { + if(Bm->Buffer[i] != 0xFFFFFFFF) + break; + } + + if(i == Bm->Size) + { + ERR("Bitmap is full!\n"); + return -ERROR_DISK_FULL; + } + + for(j = 0; j < 32; ++j) + { + unsigned Bit = Bm->Buffer[i] & (1 << j); + if(!Bit) break; + } + + ASSERT(j < 32); + return i * 32 + j; +} + +int MxfsSetBit(MX_BITMAP *Bm, unsigned Bit) +{ + unsigned i = Bit / 32; + Bit = Bit & 31; + + if(i >= Bm->Size) + return -ERROR_INVALID_PARAMETER; + + Bm->Buffer[i] |= (1 << Bit); + return 0; +} + +int MxfsClearBit(MX_BITMAP *Bm, unsigned Bit) +{ + unsigned i = Bit / 32; + Bit = Bit & 31; + + if(i >= Bm->Size) + return -ERROR_INVALID_PARAMETER; + + Bm->Buffer[i] &= ~(1 << Bit); + return 0; +} + +int MxfsGetBit(MX_BITMAP *Bm, unsigned Bit) +{ + unsigned i = Bit / 32; + Bit = Bit & 31; + + if(i >= Bm->Size) + return -ERROR_INVALID_PARAMETER; + + if(Bm->Buffer[i] & (1 << Bit)) + return 1; + else + return 0; +} diff --git a/bitmap.h b/bitmap.h new file mode 100644 index 0000000..e204271 --- /dev/null +++ b/bitmap.h @@ -0,0 +1,21 @@ +#ifndef BITMAP_H_INCLUDED +#define BITMAP_H_INCLUDED + +#include + +typedef struct +{ + uint32_t *Buffer; + unsigned Size; + unsigned Hint; +} MX_BITMAP; + +void MxfsInitBitmap(MX_BITMAP *Bm, uint32_t *Buffer, unsigned BitsCount); +void MxfsDestroyBitmap(MX_BITMAP *Bm); +unsigned MxfsCountBitsSet(MX_BITMAP *Bm); +int MxfsFindClearBit(MX_BITMAP *Bm); +int MxfsSetBit(MX_BITMAP *Bm, unsigned Bit); +int MxfsClearBit(MX_BITMAP *Bm, unsigned Bit); +int MxfsGetBit(MX_BITMAP *Bm, unsigned Bit); + +#endif // BITMAP_H_INCLUDED diff --git a/cache.c b/cache.c new file mode 100644 index 0000000..46ffa3a --- /dev/null +++ b/cache.c @@ -0,0 +1,184 @@ +#include +#include "debug.h" +#include "cache.h" +#include "general.h" + +void MxfsCacheInit(MINIX_FS *FileSys) +{ + InitializeListHead(&FileSys->Cache.MruList); + FileSys->Cache.Count = 0; + InitializeCriticalSection(&FileSys->Cache.Lock); +} + +static int MxfsCacheFlushItem(MINIX_FS *FileSys, MX_CACHE_ITEM *Item) +{ + unsigned Offset = FileSys->uOffset + Item->Index*MINIX_BLOCK_SIZE; + int Result = ImgWrite(FileSys->pImg, Offset, MINIX_BLOCK_SIZE, Item->Data); + Item->Dirty = FALSE; + + if(Result < 0) + ERR("MxfsCacheFlushItem: ImgWrite failed %d\n", Result); + return Result; +} + +void MxfsCacheFlush(MINIX_FS *FileSys) +{ + EnterCriticalSection(&FileSys->Cache.Lock); + + LIST_ENTRY *Entry = FileSys->Cache.MruList.Flink; + while(Entry != &FileSys->Cache.MruList) + { + MX_CACHE_ITEM *Item = CONTAINING_RECORD(Entry, MX_CACHE_ITEM, MruList); + if(Item->Dirty) + MxfsCacheFlushItem(FileSys, Item); + + Entry = Entry->Flink; + } + + LeaveCriticalSection(&FileSys->Cache.Lock); +} + +static void MxfsCacheDestroyLastItem(MINIX_FS *FileSys) +{ + ASSERT(FileSys->Cache.Count > 0); + + LIST_ENTRY *Entry = RemoveTailList(&FileSys->Cache.MruList); + MX_CACHE_ITEM *Item = CONTAINING_RECORD(Entry, MX_CACHE_ITEM, MruList); + --FileSys->Cache.Count; + + if(Item->Dirty) + MxfsCacheFlushItem(FileSys, Item); + + MxfsFree(Item); +} + +void MxfsCacheDestroy(MINIX_FS *FileSys) +{ + while(!IsListEmpty(&FileSys->Cache.MruList)) + MxfsCacheDestroyLastItem(FileSys); +} + +MX_CACHE_ITEM *MxfsCacheFind(MINIX_FS *FileSys, unsigned Block) +{ + LIST_ENTRY *Entry = FileSys->Cache.MruList.Flink; + while(Entry != &FileSys->Cache.MruList) + { + MX_CACHE_ITEM *Item = CONTAINING_RECORD(Entry, MX_CACHE_ITEM, MruList); + if(Item->Index == Block) + return Item; + + Entry = Entry->Flink; + } + return NULL; +} + +MX_CACHE_ITEM *MxfsCacheLoadBlock(MINIX_FS *FileSys, unsigned Block, BOOL Zero) +{ + MX_CACHE_ITEM *Item = MxfsAlloc(sizeof(MX_CACHE_ITEM)); + if(!Item) return NULL; + + unsigned BlockOffset = Block * MINIX_BLOCK_SIZE; + if(BlockOffset + MINIX_BLOCK_SIZE > FileSys->cbLen) + { + ERR("MxfsCacheLoadBlock: Invalid block %u\n", Block); + return NULL; + } + + Item->Index = Block; + + if(!Zero) + { + int Result = ImgRead(FileSys->pImg, FileSys->uOffset + BlockOffset, MINIX_BLOCK_SIZE, Item->Data); + if(Result < 0) + { + MxfsFree(Item); + ERR("MxfsCacheLoadBlock: ImgRead failed %d\n", Result); + return NULL; + } + Item->Dirty = FALSE; + } else { + memset(Item->Data, 0, MINIX_BLOCK_SIZE); + Item->Dirty = TRUE; + } + + if(FileSys->Cache.Count == BLOCK_CACHE_SIZE) + MxfsCacheDestroyLastItem(FileSys); + + InsertHeadList(&FileSys->Cache.MruList, &Item->MruList); + ++FileSys->Cache.Count; + + return Item; +} + +int MxfsCacheRead(MINIX_FS *FileSys, void *Buffer, unsigned Block, unsigned Offset, unsigned Length) +{ + if(!(Offset < MINIX_BLOCK_SIZE && Offset + Length <= MINIX_BLOCK_SIZE)) + { + WARN("MxfsCacheRead: wrong offset (%u) or len (%u)\n", Offset, Length); + return -ERROR_INVALID_PARAMETER; + } + + EnterCriticalSection(&FileSys->Cache.Lock); + + int Result = -ERROR_NOT_FOUND; + MX_CACHE_ITEM *Item = MxfsCacheFind(FileSys, Block); + if(Item) + { + // Push at front + RemoveEntryList(&Item->MruList); + InsertHeadList(&FileSys->Cache.MruList, &Item->MruList); + } else + Item = MxfsCacheLoadBlock(FileSys, Block, FALSE); + + if(Item) + { + memcpy(Buffer, Item->Data + Offset, Length); + Result = 0; + } + + LeaveCriticalSection(&FileSys->Cache.Lock); + return Result; +} + +int MxfsCacheWrite(MINIX_FS *FileSys, const void *Buffer, unsigned Block, unsigned Offset, unsigned Length) +{ + if(!(Offset < MINIX_BLOCK_SIZE && Offset + Length <= MINIX_BLOCK_SIZE)) + { + WARN("MxfsCacheWrite: wrong offset (%u) or len (%u)\n", Offset, Length); + return -ERROR_INVALID_PARAMETER; + } + + EnterCriticalSection(&FileSys->Cache.Lock); + + int Result = -ERROR_WRITE_FAULT; + MX_CACHE_ITEM *Item = MxfsCacheFind(FileSys, Block); + if(!Item) + Item = MxfsCacheLoadBlock(FileSys, Block, FALSE); + + if(Item) + { + memcpy(Item->Data + Offset, Buffer, Length); + Item->Dirty = TRUE; + Result = 0; + } + + LeaveCriticalSection(&FileSys->Cache.Lock); + return Result; +} + +void MxfsCacheZero(MINIX_FS *FileSys, unsigned Block) +{ + EnterCriticalSection(&FileSys->Cache.Lock); + + MX_CACHE_ITEM *Item = MxfsCacheFind(FileSys, Block); + if(Item) + { + memset(Item->Data, 0, MINIX_BLOCK_SIZE); + Item->Dirty = TRUE; + } + else + MxfsCacheLoadBlock(FileSys, Block, TRUE); + + LeaveCriticalSection(&FileSys->Cache.Lock); +} + diff --git a/cache.h b/cache.h new file mode 100644 index 0000000..df8903b --- /dev/null +++ b/cache.h @@ -0,0 +1,35 @@ +#ifndef CACHE_H_INCLUDED +#define CACHE_H_INCLUDED + +#include +#include "internal.h" +#include "list.h" + +#define BLOCK_CACHE_SIZE 128 + +struct _MINIX_FS; + +typedef struct _MX_CACHE_ITEM +{ + LIST_ENTRY MruList; + unsigned Index; + BOOL Dirty; + BYTE Data[MINIX_BLOCK_SIZE]; +} MX_CACHE_ITEM; + +typedef struct +{ + LIST_ENTRY MruList; + unsigned Count; + CRITICAL_SECTION Lock; + struct _MINIX_FS *FileSys; +} MX_BLOCK_CACHE; + +void MxfsCacheInit(struct _MINIX_FS *FileSys); +void MxfsCacheDestroy(struct _MINIX_FS *FileSys); +void MxfsCacheFlush(struct _MINIX_FS *FileSys); +int MxfsCacheRead(struct _MINIX_FS *FileSys, void *Buffer, unsigned Block, unsigned Offset, unsigned Length); +int MxfsCacheWrite(struct _MINIX_FS *FileSys, const void *Buffer, unsigned Block, unsigned Offset, unsigned Length); +void MxfsCacheZero(struct _MINIX_FS *FileSys, unsigned Block); + +#endif // CACHE_H_INCLUDED diff --git a/debug.c b/debug.c new file mode 100644 index 0000000..4691c72 --- /dev/null +++ b/debug.c @@ -0,0 +1,35 @@ +#include "debug.h" +#include + +#ifndef NDEBUG +void MxfsDump(const void *pvBuf, unsigned uSize) +{ + const unsigned COLS = 16; + unsigned i = 0, j; + unsigned char *pcBuf = (unsigned char*)pvBuf; + + while(i < uSize) + { + for(j = 0; j < COLS; ++j) + { + if(i + j < uSize) + printf("%02X ", pcBuf[i + j]); + else + printf(" "); + } + + printf(" "); + + for(j = 0; j < COLS; ++j) + { + if(i + j < uSize) + printf("%c", isprint(pcBuf[i + j]) ? pcBuf[i + j] : '.'); + else + printf(" "); + } + + printf("\n"); + i += COLS; + } +} +#endif // NDEBUG diff --git a/debug.h b/debug.h new file mode 100644 index 0000000..e1614f8 --- /dev/null +++ b/debug.h @@ -0,0 +1,30 @@ +#ifndef DEBUG_H_INCLUDED +#define DEBUG_H_INCLUDED + +#include +#include + +#ifdef NDEBUG + +#define ERR(...) printf(__VA_ARGS__) +#define WARN(...) +#define INFO(...) +#define TRACE(fmt, ...) +#define UNIMPLEMENTED(fmt, ...) +#define ASSERT(...) +#define MxfsDump(...) + +#else // NDEBUG + +#define ERR(...) printf(__VA_ARGS__) +#define WARN(...) printf(__VA_ARGS__) +#define INFO(...) //printf(__VA_ARGS__) +#define TRACE(fmt, ...) //printf("Trace: %s(" fmt ")\n", __func__, ##__VA_ARGS__) +#define UNIMPLEMENTED(fmt, ...) printf("Unimplemented function %s(" fmt ") called\n", __func__, ##__VA_ARGS__) +#define ASSERT(...) assert(__VA_ARGS__) + +void MxfsDump(const void *pvBuf, unsigned uSize); + +#endif // NDEBUG + +#endif // DEBUG_H_INCLUDED diff --git a/dir.c b/dir.c new file mode 100644 index 0000000..f2cea05 --- /dev/null +++ b/dir.c @@ -0,0 +1,141 @@ +#include +#include +#include "dir.h" +#include "general.h" +#include "debug.h" +#include "internal.h" +#include "file.h" +#include "inode.h" +#include "file_info.h" + +int MxfsFindFileInDir(MINIX_FS *FileSys, unsigned Inode, minix_inode *DirInfo, const char *Filename) +{ + unsigned TotalEntries, i, j = 0, Offset; + int Result; + minix_dir_entry Entries[MINIX_BLOCK_SIZE / sizeof(minix_dir_entry)]; + + TotalEntries = DirInfo->d2_size / sizeof(minix_dir_entry); + + for(i = 0; i < TotalEntries; ++i) + { + if(i % COUNTOF(Entries) == 0) + { + Offset = i * sizeof(minix_dir_entry); + Result = MxfsGetBlockFromFileOffset(FileSys, Inode, DirInfo, Offset, FALSE); + if(Result < 0) + return Result; + + Result = MxfsCacheRead(FileSys, Entries, Result, 0, MINIX_BLOCK_SIZE); + if(Result < 0) + return Result; + + j = 0; + } + + if(Entries[j].inode != 0 && strncmp(Filename, Entries[j].name, MAX_NAME_LEN) == 0) + return Entries[j].inode; + + ++j; + } + return -1; +} + +int MxfsParsePath(MINIX_FS *FileSys, const char *Path, BOOL Parent) +{ + char Filename[32]; + unsigned FilenameLen = 0; + int Result = 1; + minix_inode DirInfo; + + while(1) + { + if(*Path != '/' && *Path != '\\' && *Path != '\0') + { + if(FilenameLen + 1 >= sizeof(Filename)) + return -ERROR_NOT_FOUND; + Filename[FilenameLen++] = *Path; + } + else if(FilenameLen != 0) + { + if(Parent && *Path == '\0') + break; + + Filename[FilenameLen] = '\0'; + MxfsReadInode(FileSys, Result, &DirInfo); + + if(!(DirInfo.d2_mode & I_DIRECTORY)) + return -ERROR_NOT_FOUND; + + Result = MxfsFindFileInDir(FileSys, Result, &DirInfo, Filename); + FilenameLen = 0; + } + + if(Result < 0 || *Path == '\0') + break; + + ++Path; + } + + return Result; +} + +int DOKAN_CALLBACK MxfsFindFiles( + LPCWSTR PathName, + PFillFindData Callback, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE("%ls %p", PathName, FileInfo); + ASSERT(FileInfo->IsDirectory); + + int Result; + minix_dir_entry Entries[MINIX_BLOCK_SIZE / sizeof(minix_dir_entry)]; + unsigned TotalEntries, FileIdx, Offset, i, j; + minix_inode DirInfo, Info; + + MINIX_FS *FileSys = (MINIX_FS*)(LONG_PTR)FileInfo->DokanOptions->GlobalContext; + FILE_CTX *FileCtx = (FILE_CTX*)(LONG_PTR)FileInfo->Context; + ASSERT(FileCtx); + + Result = MxfsReadInode(FileSys, FileCtx->Index, &DirInfo); + if(Result < 0) + return Result; + + TotalEntries = DirInfo.d2_size / sizeof(minix_dir_entry); + if(TotalEntries == 0) + return 0; // empty dir + + WIN32_FIND_DATAW wfd; + ZeroMemory(&wfd, sizeof(wfd)); + + for(i = 0, j = 0; i < TotalEntries; ++i, ++j) + { + if(i % COUNTOF(Entries) == 0) + { + Offset = i * sizeof(minix_dir_entry); + Result = MxfsGetBlockFromFileOffset(FileSys, FileCtx->Index, &DirInfo, Offset, FALSE); + if(Result < 0) + return Result; + + Result = MxfsCacheRead(FileSys, Entries, Result, 0, MINIX_BLOCK_SIZE); + if(Result < 0) + return Result; + + j = 0; + } + + FileIdx = Entries[j].inode; + if(!FileIdx) + continue; + + if(MxfsReadInode(FileSys, FileIdx, &Info) < 0) + { + WARN("Invalid node in directory\n"); + continue; + } + + MxfsFillFindData(&wfd, &Entries[j], &Info); + Callback(&wfd, FileInfo); + } + + return 0; +} diff --git a/dir.h b/dir.h new file mode 100644 index 0000000..18b52e4 --- /dev/null +++ b/dir.h @@ -0,0 +1,15 @@ +#ifndef DIR_H_INCLUDED +#define DIR_H_INCLUDED + +#include +#include "dokan.h" +#include "general.h" + +int DOKAN_CALLBACK MxfsFindFiles( + LPCWSTR PathName, + PFillFindData Callback, + PDOKAN_FILE_INFO FileInfo); + +int MxfsParsePath(MINIX_FS *pFileSys, const char *pszPath, BOOL Parent); + +#endif // DIR_H_INCLUDED diff --git a/disk_image.c b/disk_image.c new file mode 100644 index 0000000..b0f6af2 --- /dev/null +++ b/disk_image.c @@ -0,0 +1,68 @@ +#include +#include "disk_image.h" +#include "general.h" + +DISK_IMG *ImgOpen(const char *pszPath) +{ + HANDLE FileHandle = CreateFileA(pszPath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if(FileHandle == INVALID_HANDLE_VALUE) + return NULL; + + DISK_IMG *pImg = MxfsAlloc(sizeof(DISK_IMG)); + if(!pImg) + return NULL; + + pImg->FileHandle = FileHandle; + InitializeCriticalSection(&pImg->Lock); + return pImg; +} + +void ImgClose(DISK_IMG *pImg) +{ + if(!pImg) return; + CloseHandle(pImg->FileHandle); + MxfsFree(pImg); +} + +int ImgRead(DISK_IMG *pImg, unsigned uOffset, unsigned cBytes, PVOID pBuf) +{ + DWORD dwBytesRead; + BOOL bSuccess; + + EnterCriticalSection(&pImg->Lock); + SetFilePointer(pImg->FileHandle, uOffset, NULL, FILE_BEGIN); + bSuccess = ReadFile(pImg->FileHandle, pBuf, cBytes, &dwBytesRead, NULL); + LeaveCriticalSection(&pImg->Lock); + + if(!bSuccess || dwBytesRead != cBytes) + return -ERROR_READ_FAULT; + + return 0; +} + +int ImgWrite(DISK_IMG *pImg, unsigned uOffset, unsigned cBytes, PVOID pBuf) +{ + DWORD dwBytesWritten = 0; + BOOL bSuccess = FALSE; + + EnterCriticalSection(&pImg->Lock); + if(uOffset + cBytes <= GetFileSize(pImg->FileHandle, NULL)) + { + SetFilePointer(pImg->FileHandle, uOffset, NULL, FILE_BEGIN); + bSuccess = WriteFile(pImg->FileHandle, pBuf, cBytes, &dwBytesWritten, NULL); + } + LeaveCriticalSection(&pImg->Lock); + + if(!bSuccess || dwBytesWritten != cBytes) + return -ERROR_WRITE_FAULT; + + return 0; +} + +unsigned ImgGetSize(DISK_IMG *pImg) +{ + EnterCriticalSection(&pImg->Lock); + unsigned uSize = GetFileSize(pImg->FileHandle, NULL); + LeaveCriticalSection(&pImg->Lock); + return uSize; +} diff --git a/disk_image.h b/disk_image.h new file mode 100644 index 0000000..2e5beb3 --- /dev/null +++ b/disk_image.h @@ -0,0 +1,19 @@ +#ifndef DISK_IMAGE_H_INCLUDED +#define DISK_IMAGE_H_INCLUDED + +#include +#include + +typedef struct +{ + CRITICAL_SECTION Lock; + HANDLE FileHandle; +} DISK_IMG; + +DISK_IMG *ImgOpen(const char *pszPath); +void ImgClose(DISK_IMG *pImg); +int ImgRead(DISK_IMG *pImg, unsigned uOffset, unsigned cBytes, PVOID pBuf); +int ImgWrite(DISK_IMG *pImg, unsigned uOffset, unsigned cBytes, PVOID pBuf); +unsigned ImgGetSize(DISK_IMG *pImg); + +#endif // DISK_IMAGE_H_INCLUDED diff --git a/dokan.h b/dokan.h new file mode 100644 index 0000000..987b639 --- /dev/null +++ b/dokan.h @@ -0,0 +1,335 @@ +/* + Dokan : user-mode file system library for Windows + + Copyright (C) 2008 Hiroki Asakawa info@dokan-dev.net + + http://dokan-dev.net/en + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 3 of the License, or (at your option) any +later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program. If not, see . +*/ + + +#ifndef _DOKAN_H_ +#define _DOKAN_H_ + +#define DOKAN_DRIVER_NAME L"dokan.sys" + +#ifndef _M_X64 + #ifdef _EXPORTING + #define DOKANAPI __declspec(dllimport) __stdcall + #else + #define DOKANAPI __declspec(dllexport) __stdcall + #endif +#else + #define DOKANAPI +#endif + +#define DOKAN_CALLBACK __stdcall + +#ifdef __cplusplus +extern "C" { +#endif + +// The current Dokan version (ver 0.6.0). Please set this constant on DokanOptions->Version. +#define DOKAN_VERSION 600 + +#define DOKAN_OPTION_DEBUG 1 // ouput debug message +#define DOKAN_OPTION_STDERR 2 // ouput debug message to stderr +#define DOKAN_OPTION_ALT_STREAM 4 // use alternate stream +#define DOKAN_OPTION_KEEP_ALIVE 8 // use auto unmount +#define DOKAN_OPTION_NETWORK 16 // use network drive, you need to install Dokan network provider. +#define DOKAN_OPTION_REMOVABLE 32 // use removable drive + +typedef struct _DOKAN_OPTIONS { + USHORT Version; // Supported Dokan Version, ex. "530" (Dokan ver 0.5.3) + USHORT ThreadCount; // number of threads to be used + ULONG Options; // combination of DOKAN_OPTIONS_* + ULONG64 GlobalContext; // FileSystem can use this variable + LPCWSTR MountPoint; // mount point "M:\" (drive letter) or "C:\mount\dokan" (path in NTFS) +} DOKAN_OPTIONS, *PDOKAN_OPTIONS; + +typedef struct _DOKAN_FILE_INFO { + ULONG64 Context; // FileSystem can use this variable + ULONG64 DokanContext; // Don't touch this + PDOKAN_OPTIONS DokanOptions; // A pointer to DOKAN_OPTIONS which was passed to DokanMain. + ULONG ProcessId; // process id for the thread that originally requested a given I/O operation + UCHAR IsDirectory; // requesting a directory file + UCHAR DeleteOnClose; // Delete on when "cleanup" is called + UCHAR PagingIo; // Read or write is paging IO. + UCHAR SynchronousIo; // Read or write is synchronous IO. + UCHAR Nocache; + UCHAR WriteToEndOfFile; // If true, write to the current end of file instead of Offset parameter. + +} DOKAN_FILE_INFO, *PDOKAN_FILE_INFO; + + +// FillFileData +// add an entry in FindFiles +// return 1 if buffer is full, otherwise 0 +// (currently never return 1) +typedef int (WINAPI *PFillFindData) (PWIN32_FIND_DATAW, PDOKAN_FILE_INFO); + +typedef struct _DOKAN_OPERATIONS { + + // When an error occurs, return negative value. + // Usually you should return GetLastError() * -1. + + + // CreateFile + // If file is a directory, CreateFile (not OpenDirectory) may be called. + // In this case, CreateFile should return 0 when that directory can be opened. + // You should set TRUE on DokanFileInfo->IsDirectory when file is a directory. + // When CreationDisposition is CREATE_ALWAYS or OPEN_ALWAYS and a file already exists, + // you should return ERROR_ALREADY_EXISTS(183) (not negative value) + int (DOKAN_CALLBACK *CreateFile) ( + LPCWSTR, // FileName + DWORD, // DesiredAccess + DWORD, // ShareMode + DWORD, // CreationDisposition + DWORD, // FlagsAndAttributes + PDOKAN_FILE_INFO); + + int (DOKAN_CALLBACK *OpenDirectory) ( + LPCWSTR, // FileName + PDOKAN_FILE_INFO); + + int (DOKAN_CALLBACK *CreateDirectory) ( + LPCWSTR, // FileName + PDOKAN_FILE_INFO); + + // When FileInfo->DeleteOnClose is true, you must delete the file in Cleanup. + int (DOKAN_CALLBACK *Cleanup) ( + LPCWSTR, // FileName + PDOKAN_FILE_INFO); + + int (DOKAN_CALLBACK *CloseFile) ( + LPCWSTR, // FileName + PDOKAN_FILE_INFO); + + int (DOKAN_CALLBACK *ReadFile) ( + LPCWSTR, // FileName + LPVOID, // Buffer + DWORD, // NumberOfBytesToRead + LPDWORD, // NumberOfBytesRead + LONGLONG, // Offset + PDOKAN_FILE_INFO); + + + int (DOKAN_CALLBACK *WriteFile) ( + LPCWSTR, // FileName + LPCVOID, // Buffer + DWORD, // NumberOfBytesToWrite + LPDWORD, // NumberOfBytesWritten + LONGLONG, // Offset + PDOKAN_FILE_INFO); + + + int (DOKAN_CALLBACK *FlushFileBuffers) ( + LPCWSTR, // FileName + PDOKAN_FILE_INFO); + + + int (DOKAN_CALLBACK *GetFileInformation) ( + LPCWSTR, // FileName + LPBY_HANDLE_FILE_INFORMATION, // Buffer + PDOKAN_FILE_INFO); + + + int (DOKAN_CALLBACK *FindFiles) ( + LPCWSTR, // PathName + PFillFindData, // call this function with PWIN32_FIND_DATAW + PDOKAN_FILE_INFO); // (see PFillFindData definition) + + + // You should implement either FindFiles or FindFilesWithPattern + int (DOKAN_CALLBACK *FindFilesWithPattern) ( + LPCWSTR, // PathName + LPCWSTR, // SearchPattern + PFillFindData, // call this function with PWIN32_FIND_DATAW + PDOKAN_FILE_INFO); + + + int (DOKAN_CALLBACK *SetFileAttributes) ( + LPCWSTR, // FileName + DWORD, // FileAttributes + PDOKAN_FILE_INFO); + + + int (DOKAN_CALLBACK *SetFileTime) ( + LPCWSTR, // FileName + CONST FILETIME*, // CreationTime + CONST FILETIME*, // LastAccessTime + CONST FILETIME*, // LastWriteTime + PDOKAN_FILE_INFO); + + + // You should not delete file on DeleteFile or DeleteDirectory. + // When DeleteFile or DeleteDirectory, you must check whether + // you can delete the file or not, and return 0 (when you can delete it) + // or appropriate error codes such as -ERROR_DIR_NOT_EMPTY, + // -ERROR_SHARING_VIOLATION. + // When you return 0 (ERROR_SUCCESS), you get Cleanup with + // FileInfo->DeleteOnClose set TRUE and you have to delete the + // file in Close. + int (DOKAN_CALLBACK *DeleteFile) ( + LPCWSTR, // FileName + PDOKAN_FILE_INFO); + + int (DOKAN_CALLBACK *DeleteDirectory) ( + LPCWSTR, // FileName + PDOKAN_FILE_INFO); + + + int (DOKAN_CALLBACK *MoveFile) ( + LPCWSTR, // ExistingFileName + LPCWSTR, // NewFileName + BOOL, // ReplaceExisiting + PDOKAN_FILE_INFO); + + + int (DOKAN_CALLBACK *SetEndOfFile) ( + LPCWSTR, // FileName + LONGLONG, // Length + PDOKAN_FILE_INFO); + + + int (DOKAN_CALLBACK *SetAllocationSize) ( + LPCWSTR, // FileName + LONGLONG, // Length + PDOKAN_FILE_INFO); + + + int (DOKAN_CALLBACK *LockFile) ( + LPCWSTR, // FileName + LONGLONG, // ByteOffset + LONGLONG, // Length + PDOKAN_FILE_INFO); + + + int (DOKAN_CALLBACK *UnlockFile) ( + LPCWSTR, // FileName + LONGLONG,// ByteOffset + LONGLONG,// Length + PDOKAN_FILE_INFO); + + + // Neither GetDiskFreeSpace nor GetVolumeInformation + // save the DokanFileContext->Context. + // Before these methods are called, CreateFile may not be called. + // (ditto CloseFile and Cleanup) + + // see Win32 API GetDiskFreeSpaceEx + int (DOKAN_CALLBACK *GetDiskFreeSpace) ( + PULONGLONG, // FreeBytesAvailable + PULONGLONG, // TotalNumberOfBytes + PULONGLONG, // TotalNumberOfFreeBytes + PDOKAN_FILE_INFO); + + + // see Win32 API GetVolumeInformation + int (DOKAN_CALLBACK *GetVolumeInformation) ( + LPWSTR, // VolumeNameBuffer + DWORD, // VolumeNameSize in num of chars + LPDWORD,// VolumeSerialNumber + LPDWORD,// MaximumComponentLength in num of chars + LPDWORD,// FileSystemFlags + LPWSTR, // FileSystemNameBuffer + DWORD, // FileSystemNameSize in num of chars + PDOKAN_FILE_INFO); + + + int (DOKAN_CALLBACK *Unmount) ( + PDOKAN_FILE_INFO); + + + // Suported since 0.6.0. You must specify the version at DOKAN_OPTIONS.Version. + int (DOKAN_CALLBACK *GetFileSecurity) ( + LPCWSTR, // FileName + PSECURITY_INFORMATION, // A pointer to SECURITY_INFORMATION value being requested + PSECURITY_DESCRIPTOR, // A pointer to SECURITY_DESCRIPTOR buffer to be filled + ULONG, // length of Security descriptor buffer + PULONG, // LengthNeeded + PDOKAN_FILE_INFO); + + int (DOKAN_CALLBACK *SetFileSecurity) ( + LPCWSTR, // FileName + PSECURITY_INFORMATION, + PSECURITY_DESCRIPTOR, // SecurityDescriptor + ULONG, // SecurityDescriptor length + PDOKAN_FILE_INFO); + + +} DOKAN_OPERATIONS, *PDOKAN_OPERATIONS; + + + +/* DokanMain returns error codes */ +#define DOKAN_SUCCESS 0 +#define DOKAN_ERROR -1 /* General Error */ +#define DOKAN_DRIVE_LETTER_ERROR -2 /* Bad Drive letter */ +#define DOKAN_DRIVER_INSTALL_ERROR -3 /* Can't install driver */ +#define DOKAN_START_ERROR -4 /* Driver something wrong */ +#define DOKAN_MOUNT_ERROR -5 /* Can't assign a drive letter or mount point */ +#define DOKAN_MOUNT_POINT_ERROR -6 /* Mount point is invalid */ + +int DOKANAPI +DokanMain( + PDOKAN_OPTIONS DokanOptions, + PDOKAN_OPERATIONS DokanOperations); + + +BOOL DOKANAPI +DokanUnmount( + WCHAR DriveLetter); + +BOOL DOKANAPI +DokanRemoveMountPoint( + LPCWSTR MountPoint); + + +// DokanIsNameInExpression +// check whether Name can match Expression +// Expression can contain wildcard characters (? and *) +BOOL DOKANAPI +DokanIsNameInExpression( + LPCWSTR Expression, // matching pattern + LPCWSTR Name, // file name + BOOL IgnoreCase); + + +ULONG DOKANAPI +DokanVersion(); + +ULONG DOKANAPI +DokanDriverVersion(); + +// DokanResetTimeout +// extends the time out of the current IO operation in driver. +BOOL DOKANAPI +DokanResetTimeout( + ULONG Timeout, // timeout in millisecond + PDOKAN_FILE_INFO DokanFileInfo); + +// Get the handle to Access Token +// This method needs be called in CreateFile, OpenDirectory or CreateDirectly callback. +// The caller must call CloseHandle for the returned handle. +HANDLE DOKANAPI +DokanOpenRequestorToken( + PDOKAN_FILE_INFO DokanFileInfo); + +#ifdef __cplusplus +} +#endif + + +#endif // _DOKAN_H_ diff --git a/dokan.lib b/dokan.lib new file mode 100644 index 0000000..c1636e5 Binary files /dev/null and b/dokan.lib differ diff --git a/file.c b/file.c new file mode 100644 index 0000000..40d3a24 --- /dev/null +++ b/file.c @@ -0,0 +1,177 @@ +#include +#include +#include +#include "file.h" +#include "dir.h" +#include "debug.h" +#include "internal.h" +#include "inode.h" + +int DOKAN_CALLBACK MxfsCreateFile( + LPCWSTR FileName, + DWORD DesiredAccess, + DWORD ShareMode, + DWORD CreationDisposition, + DWORD FlagsAndAttributes, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE("FileName %ls DesiredAccess %lx CreationDisposition %ld FileInfo %p\n", FileName, DesiredAccess, CreationDisposition, FileInfo); + + char Path[MAX_PATH]; + MINIX_FS *FileSys = (MINIX_FS*)(ULONG_PTR)FileInfo->DokanOptions->GlobalContext; + + wcstombs(Path, FileName, sizeof(Path)); + int FileIdx = MxfsParsePath(FileSys, Path, FALSE); + if(FileIdx < 0) + { + INFO("Failed to create file %s\n", Path); + return -ERROR_FILE_NOT_FOUND; + } + + minix_inode Info; + int Result = MxfsReadInode(FileSys, FileIdx, &Info); + if(Result < 0) + return Result; + + if(CreationDisposition == CREATE_NEW) + { + INFO("Already exists %s\n", Path); + return -ERROR_ALREADY_EXISTS; + } + + FILE_CTX *FileCtx = MxfsAlloc(sizeof(FILE_CTX)); + if(!FileCtx) + return -ERROR_NOT_ENOUGH_MEMORY; + + FileCtx->Index = FileIdx; + FileCtx->Write = (DesiredAccess & (GENERIC_WRITE|GENERIC_ALL|FILE_WRITE_DATA)) ? TRUE : FALSE; + FileCtx->Changed = FALSE; +#ifndef NDEBUG + wcscpy_s(FileCtx->DbgBuf, sizeof(FileCtx->DbgBuf), FileName); +#endif // NDEBUG + + if(FileInfo->Context) + WARN("MxfsCreateFile: Context %I64u\n", FileInfo->Context); + FileInfo->Context = (LONG64)(ULONG_PTR)FileCtx; + FileInfo->IsDirectory = (Info.d2_mode & I_DIRECTORY) ? TRUE : FALSE; + + FileCtx->Length = Info.d2_size; + if(CreationDisposition == TRUNCATE_EXISTING || CreationDisposition == CREATE_ALWAYS) + FileCtx->Length = 0; + if(FileCtx->Write) + INFO("CreationDisposition %ld\n", CreationDisposition); + + return 0; +} + +int DOKAN_CALLBACK MxfsOpenDirectory( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE("%ls %p", FileName, FileInfo); + + char Path[MAX_PATH]; + MINIX_FS *FileSys = (MINIX_FS*)(ULONG_PTR)FileInfo->DokanOptions->GlobalContext; + + wcstombs(Path, FileName, sizeof(Path)); + int FileIdx = MxfsParsePath(FileSys, Path, FALSE); + if(FileIdx < 0) + { + INFO("Failed to open dir %s\n", Path); + return -ERROR_FILE_NOT_FOUND; + } + + minix_inode Info; + int Result = MxfsReadInode(FileSys, FileIdx, &Info); + if(Result < 0) + return Result; + + FILE_CTX *FileCtx = MxfsAlloc(sizeof(FILE_CTX)); + if(!FileCtx) + return -ERROR_NOT_ENOUGH_MEMORY; + + FileCtx->Index = FileIdx; + FileCtx->Length = Info.d2_size; + FileCtx->Changed = FALSE; +#ifndef NDEBUG + wcscpy_s(FileCtx->DbgBuf, sizeof(FileCtx->DbgBuf), FileName); +#endif // NDEBUG + + if(!(Info.d2_mode & I_DIRECTORY)) + { + MxfsFree(FileCtx); + INFO("Failed to open dir %s\n", Path); + return -ERROR_INVALID_PARAMETER; + } + + if(FileInfo->Context) + WARN("MxfsOpenDirectory: Context %I64u\n", FileInfo->Context); + + FileInfo->IsDirectory = TRUE; + FileInfo->Context = (ULONG64)(ULONG_PTR)FileCtx; + + return 0; +} + +int DOKAN_CALLBACK MxfsCreateDirectory( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE("%ls %p", FileName, FileInfo); + + char Path[MAX_PATH]; + MINIX_FS *FileSys = (MINIX_FS*)(ULONG_PTR)FileInfo->DokanOptions->GlobalContext; + + wcstombs(Path, FileName, sizeof(Path)); + int FileIdx = MxfsParsePath(FileSys, Path, FALSE); + if(FileIdx > 0) + return -ERROR_ALREADY_EXISTS; + + UNIMPLEMENTED("%ls", FileName); + return -ERROR_CALL_NOT_IMPLEMENTED; +} + +// When FileInfo->DeleteOnClose is true, you must delete the file in Cleanup. +int DOKAN_CALLBACK MxfsCleanup( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE("%ls %p", FileName, FileInfo); + + MINIX_FS *FileSys = (MINIX_FS*)(ULONG_PTR)FileInfo->DokanOptions->GlobalContext; + FILE_CTX *FileCtx = (FILE_CTX*)(ULONG_PTR)FileInfo->Context; + ASSERT(FileCtx); + + minix_inode Info; + if(MxfsReadInode(FileSys, FileCtx->Index, &Info) >= 0) + { + Info.d2_atime = time(NULL); + + if(FileCtx->Changed) + { + Info.d2_mtime = time(NULL); + Info.d2_ctime = time(NULL); + } + + MxfsWriteInode(FileSys, FileCtx->Index, &Info); + } + + if(FileCtx->Changed) + MxfsSetEndOfFile(FileName, FileCtx->Length, FileInfo); + + MxfsFree(FileCtx); + FileInfo->Context = 0; + + return 0; +} + +int DOKAN_CALLBACK MxfsCloseFile( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE("%ls %p", FileName, FileInfo); + + ASSERT(FileInfo->Context == 0); + + return 0; +} diff --git a/file.h b/file.h new file mode 100644 index 0000000..9573ea3 --- /dev/null +++ b/file.h @@ -0,0 +1,43 @@ +#ifndef FILE_H_INCLUDED +#define FILE_H_INCLUDED + +#include +#include "dokan.h" +#include "internal.h" + +int DOKAN_CALLBACK MxfsCreateFile( + LPCWSTR FileName, + DWORD DesiredAccess, + DWORD ShareMode, + DWORD CreationDisposition, + DWORD FlagsAndAttributes, + PDOKAN_FILE_INFO FileInfo); + +int DOKAN_CALLBACK MxfsCreateDirectory( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo); + +int DOKAN_CALLBACK MxfsOpenDirectory( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo); + +int DOKAN_CALLBACK MxfsCleanup( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo); + +int DOKAN_CALLBACK MxfsCloseFile( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo); + +typedef struct +{ + int Index; + unsigned Length; + BOOL Write, Changed; + //minix_inode Info; +#ifndef NDEBUG + WCHAR DbgBuf[256]; +#endif +} FILE_CTX; + +#endif // FILE_H_INCLUDED diff --git a/file_info.c b/file_info.c new file mode 100644 index 0000000..275a12c --- /dev/null +++ b/file_info.c @@ -0,0 +1,107 @@ +#include "file_info.h" +#include "file.h" +#include "general.h" +#include "debug.h" +#include "inode.h" + +void MxfsFileTimeFromTimeT(LPFILETIME FileTime, time_t t) +{ + LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000ull; + FileTime->dwLowDateTime = (DWORD)ll; + FileTime->dwHighDateTime = ll >> 32; +} + +time_t MxfsTimeTFromFileTime(CONST FILETIME *FileTime) +{ + ULARGE_INTEGER ull; + ull.LowPart = FileTime->dwLowDateTime; + ull.HighPart = FileTime->dwHighDateTime; + return ull.QuadPart / 10000000ULL - 11644473600ULL; +} + +int DOKAN_CALLBACK MxfsGetFileInformation( + LPCWSTR FileName, + LPBY_HANDLE_FILE_INFORMATION Buffer, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE("%ls %p", FileName, FileInfo); + + MINIX_FS *FileSys = (MINIX_FS*)(LONG_PTR)FileInfo->DokanOptions->GlobalContext; + FILE_CTX *FileCtx = (FILE_CTX*)(LONG_PTR)FileInfo->Context; + ASSERT(FileCtx); + + minix_inode Info; + MxfsReadInode(FileSys, FileCtx->Index, &Info); + + ZeroMemory(Buffer, sizeof(*Buffer)); + Buffer->dwFileAttributes = FileInfo->IsDirectory ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL; + MxfsFileTimeFromTimeT(&Buffer->ftCreationTime, Info.d2_mtime); + MxfsFileTimeFromTimeT(&Buffer->ftLastAccessTime, Info.d2_atime); + MxfsFileTimeFromTimeT(&Buffer->ftLastWriteTime, Info.d2_mtime); + Buffer->dwVolumeSerialNumber = MINIX_VOLUME_ID; + Buffer->nFileSizeLow = Info.d2_size; + Buffer->nNumberOfLinks = 1; + Buffer->nFileIndexLow = FileCtx->Index; + + return 0; +} + +int DOKAN_CALLBACK MxfsSetFileTime( + LPCWSTR FileName, + CONST FILETIME *CreationTime, + CONST FILETIME *LastAccessTime, + CONST FILETIME *LastWriteTime, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE("%ls", FileName); + + MINIX_FS *FileSys = (MINIX_FS*)(ULONG_PTR)FileInfo->DokanOptions->GlobalContext; + FILE_CTX *FileCtx = (FILE_CTX*)(ULONG_PTR)FileInfo->Context; + ASSERT(FileCtx); + + minix_inode Info; + int Result = MxfsReadInode(FileSys, FileCtx->Index, &Info); + if(Result < 0) + return Result; + + if(LastAccessTime && (LastAccessTime->dwHighDateTime || LastAccessTime->dwLowDateTime)) + { + if(LastAccessTime->dwHighDateTime == 0xFFFFFFFF && LastAccessTime->dwLowDateTime == 0xFFFFFFFF) + WARN("TODO: Support -1!\n"); // preserve access time + else + Info.d2_atime = MxfsTimeTFromFileTime(LastAccessTime); + } + + if(LastWriteTime && (LastWriteTime->dwHighDateTime || LastWriteTime->dwLowDateTime)) + { + if(LastWriteTime->dwHighDateTime == 0xFFFFFFFF && LastWriteTime->dwLowDateTime == 0xFFFFFFFF) + WARN("TODO: Support -1!\n"); // preserve write time + else + Info.d2_mtime = MxfsTimeTFromFileTime(LastWriteTime); + } + + return MxfsWriteInode(FileSys, FileCtx->Index, &Info); +} + +void MxfsFillFindData(WIN32_FIND_DATAW *pFindData, const minix_dir_entry *pDirEntry, const minix_inode *pInfo) +{ + /* Attributes */ + if(pInfo->d2_mode & I_DIRECTORY) + pFindData->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; + else + pFindData->dwFileAttributes = FILE_ATTRIBUTE_NORMAL; + + /* Times */ + MxfsFileTimeFromTimeT(&pFindData->ftCreationTime, pInfo->d2_mtime); + MxfsFileTimeFromTimeT(&pFindData->ftLastAccessTime, pInfo->d2_atime); + MxfsFileTimeFromTimeT(&pFindData->ftLastWriteTime, pInfo->d2_mtime); + + /* Size */ + pFindData->nFileSizeLow = pInfo->d2_size; + + /* Name */ + int NameLen = strnlen(pDirEntry->name, MAX_NAME_LEN); + MultiByteToWideChar(CP_ACP, 0, pDirEntry->name, NameLen, pFindData->cFileName, COUNTOF(pFindData->cFileName)); + pFindData->cFileName[min(NameLen, COUNTOF(pFindData->cFileName))] = 0; + wcscpy_s(pFindData->cAlternateFileName, sizeof(pFindData->cAlternateFileName), pFindData->cFileName); +} diff --git a/file_info.h b/file_info.h new file mode 100644 index 0000000..8e3cf2f --- /dev/null +++ b/file_info.h @@ -0,0 +1,24 @@ +#ifndef FILE_INFO_H_INCLUDED +#define FILE_INFO_H_INCLUDED + +#include +#include "dokan.h" +#include "internal.h" + +int DOKAN_CALLBACK MxfsGetFileInformation( + LPCWSTR FileName, + LPBY_HANDLE_FILE_INFORMATION Buffer, + PDOKAN_FILE_INFO FileInfo); + +int DOKAN_CALLBACK MxfsSetFileTime( + LPCWSTR FileName, + CONST FILETIME* CreationTime, + CONST FILETIME* LastAccessTime, + CONST FILETIME* LastWriteTime, + PDOKAN_FILE_INFO FileInfo); + +void MxfsFillFindData(WIN32_FIND_DATAW *pFindData, const minix_dir_entry *pDirEntry, const minix_inode *pInfo); +void MxfsFileTimeFromTimeT(LPFILETIME FileTime, time_t t); +time_t MxfsTimeTFromFileTime(CONST FILETIME *FileTime); + +#endif // FILE_INFO_H_INCLUDED diff --git a/general.c b/general.c new file mode 100644 index 0000000..66e2341 --- /dev/null +++ b/general.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include "general.h" +#include "internal.h" +#include "debug.h" +#include "read_write.h" + +MINIX_FS *MxfsOpen(DISK_IMG *pImg, unsigned uOffset, unsigned cbLen) +{ + MINIX_FS *FileSys; + + TRACE(""); + assert(sizeof(d2_inode) == 64); + + FileSys = MxfsAlloc(sizeof(*FileSys)); + if(!FileSys) + return NULL; + + memset(FileSys, 0, sizeof(*FileSys)); + FileSys->pImg = pImg; + FileSys->uOffset = uOffset; + FileSys->cbLen = cbLen; + InitializeCriticalSection(&FileSys->Lock); + MxfsCacheInit(FileSys); + + if(MxfsCacheRead(FileSys, &FileSys->Super, SUPER_BLOCK, 0, sizeof(FileSys->Super)) != 0) + { + MxfsClose(FileSys); + return NULL; + } + + if(FileSys->Super.s_magic != SUPER_V2_REV) + { + ERR("Invalid magic in super block %x\n", FileSys->Super.s_magic); + MxfsClose(FileSys); + return NULL; + } + + unsigned InodeMapSize = FileSys->Super.s_imap_blocks * MINIX_BLOCK_SIZE; + uint32_t *InodeMapBuf = MxfsAlloc(InodeMapSize); + if(!InodeMapBuf || MxfsReadBlocks(FileSys, InodeMapBuf, SUPER_BLOCK + 1, FileSys->Super.s_imap_blocks) != 0) + { + MxfsFree(InodeMapBuf); + MxfsClose(FileSys); + return NULL; + } + MxfsInitBitmap(&FileSys->InodeMap, InodeMapBuf, InodeMapSize * 8); + + unsigned ZoneMapBlock = SUPER_BLOCK + 1 + FileSys->Super.s_imap_blocks; + unsigned ZoneMapSize = FileSys->Super.s_zmap_blocks * MINIX_BLOCK_SIZE; + uint32_t *ZoneMapBuf = MxfsAlloc(ZoneMapSize); + if(!ZoneMapBuf || MxfsReadBlocks(FileSys, ZoneMapBuf, ZoneMapBlock, FileSys->Super.s_zmap_blocks) != 0) + { + MxfsFree(ZoneMapBuf); + MxfsClose(FileSys); + return NULL; + } + MxfsInitBitmap(&FileSys->ZoneMap, ZoneMapBuf, ZoneMapSize * 8); + + return FileSys; +} + +void MxfsClose(MINIX_FS *FileSys) +{ + TRACE(""); + if(!FileSys) return; + MxfsCacheDestroy(FileSys); // destroy cache first (flush) + MxfsDestroyBitmap(&FileSys->InodeMap); + MxfsDestroyBitmap(&FileSys->ZoneMap); + memset(FileSys, 0, sizeof(*FileSys)); + MxfsFree(FileSys); +} diff --git a/general.h b/general.h new file mode 100644 index 0000000..fe42620 --- /dev/null +++ b/general.h @@ -0,0 +1,42 @@ +#ifndef GENERAL_H_INCLUDED +#define GENERAL_H_INCLUDED + +#include +#include +#include "internal.h" +#include "disk_image.h" +#include "cache.h" +#include "bitmap.h" + +#define ALIGN_UP(addr, n) (((addr) + (n) - 1)/(n)*(n)) +#define ALIGN_DOWN(addr, n) ((addr)/(n)*(n)) +#define COUNTOF(arr) (sizeof(arr)/sizeof((arr)[0])) + +typedef struct _MINIX_FS +{ + DISK_IMG *pImg; + unsigned uOffset, cbLen; + struct super_block Super; + MX_BITMAP InodeMap; + MX_BITMAP ZoneMap; + CRITICAL_SECTION Lock; + MX_BLOCK_CACHE Cache; +} MINIX_FS; + +MINIX_FS *MxfsOpen(DISK_IMG *pImg, unsigned uOffset, unsigned uLen); +void MxfsClose(MINIX_FS *pFileSys); + +FORCEINLINE void *MxfsAlloc(unsigned uSize) +{ + return HeapAlloc(GetProcessHeap(), 0, uSize); +} + +FORCEINLINE void MxfsFree(void *pMem) +{ + if(pMem) + HeapFree(GetProcessHeap(), 0, pMem); +} + +#define MINIX_VOLUME_ID 0x5F7AC349 + +#endif // GENERAL_H_INCLUDED diff --git a/inode.c b/inode.c new file mode 100644 index 0000000..7be3a5d --- /dev/null +++ b/inode.c @@ -0,0 +1,285 @@ +#include "debug.h" +#include "general.h" +#include "file.h" +#include "bitmap.h" +#include "inode.h" + +int MxfsReadInode(MINIX_FS *FileSys, int FileIdx, minix_inode *Result) +{ + if(FileIdx <= 0 || FileIdx > FileSys->Super.s_ninodes) + { + ERR("MxfsReadInode: Invalid inode %d\n", FileIdx); + return -ERROR_INVALID_INDEX; + } + + --FileIdx; // make it zero based + unsigned InodesFirstBlock = SUPER_BLOCK + 1 + FileSys->Super.s_imap_blocks + FileSys->Super.s_zmap_blocks; + unsigned Block = InodesFirstBlock + FileIdx * sizeof(minix_inode) / MINIX_BLOCK_SIZE; + unsigned Offset = (FileIdx * sizeof(minix_inode)) % MINIX_BLOCK_SIZE; + return MxfsCacheRead(FileSys, Result, Block, Offset, sizeof(minix_inode)); +} + +int MxfsWriteInode(MINIX_FS *FileSys, int FileIdx, minix_inode *Info) +{ + if(FileIdx <= 0 || FileIdx > FileSys->Super.s_ninodes) + { + ERR("MxfsWriteInode: Invalid inode %d\n", FileIdx); + return -ERROR_INVALID_INDEX; + } + + // Update inode modified time + Info->d2_ctime = time(NULL); + + --FileIdx; // make it zero based + unsigned InodesFirstBlock = SUPER_BLOCK + 1 + FileSys->Super.s_imap_blocks + FileSys->Super.s_zmap_blocks; + unsigned Block = InodesFirstBlock + FileIdx * sizeof(minix_inode) / MINIX_BLOCK_SIZE; + unsigned Offset = (FileIdx * sizeof(minix_inode)) % MINIX_BLOCK_SIZE; + return MxfsCacheWrite(FileSys, Info, Block, Offset, sizeof(minix_inode)); +} + +int MxfsAllocZone(MINIX_FS *FileSys) +{ + int Bit = MxfsFindClearBit(&FileSys->ZoneMap); + if(Bit < 0) + { + WARN("MxfsAllocZone: disk is full\n"); + return -ERROR_DISK_FULL; + } + + unsigned Zone = MxfsZoneFromBit(FileSys, Bit); + if(Zone > FileSys->Super.s_zones) + { + WARN("MxfsAllocZone: invalid zone %u > %lu\n", Zone, FileSys->Super.s_zones); + return -ERROR_DISK_FULL; + } + + int Result = MxfsSetBit(&FileSys->ZoneMap, Bit); + ASSERT(Result >= 0); + + unsigned BitmapOffset = ALIGN_DOWN(Bit/8, MINIX_BLOCK_SIZE); + unsigned BlockIdx = SUPER_BLOCK + 1 + FileSys->Super.s_imap_blocks + BitmapOffset/MINIX_BLOCK_SIZE; + BYTE *Buffer = ((BYTE*)FileSys->ZoneMap.Buffer) + BitmapOffset; + Result = MxfsCacheWrite(FileSys, Buffer, BlockIdx, 0, MINIX_BLOCK_SIZE); + if(Result < 0) + { + WARN("MxfsAllocZone: MxfsCacheWrite failed\n"); + return Result; + } + + WARN("Alloc zone %u\n", Zone); + return Zone; +} + +int MxfsFreeZone(MINIX_FS *FileSys, unsigned Zone) +{ + WARN("Free zone %u\n", Zone); + + if(Zone > FileSys->Super.s_zones) + { + WARN("MxfsFreeZone: invalid zone %u > %lu\n", Zone, FileSys->Super.s_zones); + return -ERROR_INVALID_PARAMETER; + } + + unsigned Bit = MxfsBitFromZone(FileSys, Zone); + int Result = MxfsClearBit(&FileSys->ZoneMap, Bit); + if(Result < 0) + { + WARN("MxfsFreeZone: MxfsClearBit failed\n"); + return Result; + } + + unsigned BitmapOffset = ALIGN_DOWN(Bit/8, MINIX_BLOCK_SIZE); + unsigned BlockIdx = SUPER_BLOCK + 1 + FileSys->Super.s_imap_blocks + BitmapOffset/MINIX_BLOCK_SIZE; + BYTE *Buffer = ((BYTE*)FileSys->ZoneMap.Buffer) + BitmapOffset; + return MxfsCacheWrite(FileSys, Buffer, BlockIdx, 0, MINIX_BLOCK_SIZE); +} + +int MxfsGetBlockFromFileOffset(MINIX_FS *FileSys, unsigned FileIdx, minix_inode *FileInfo, unsigned Offset, BOOL Alloc) +{ + unsigned Block; + zone_t Zone; + unsigned OffsetInBlocks = Offset / MINIX_BLOCK_SIZE; + unsigned OffsetInZones = OffsetInBlocks >> FileSys->Super.s_log_zone_size; + unsigned FirstZoneBlockOffset = OffsetInZones << FileSys->Super.s_log_zone_size; + + if(OffsetInZones < V2_NR_DZONES) + { + // Use direct zone + Zone = FileInfo->d2_zone[OffsetInZones]; + if(!Zone && Alloc) + { + Zone = MxfsAllocZone(FileSys); + if(Zone) + { + FileInfo->d2_zone[OffsetInZones] = Zone; + MxfsWriteInode(FileSys, FileIdx, FileInfo); + } + } + } + else + { + zone_t IndirectZone; + unsigned IndirectBlock, IndIndex; + int Result; + + IndIndex = OffsetInZones - V2_NR_DZONES; + + if(IndIndex < V2_INDIRECTS) + { + // Use single indirect zone + IndirectZone = FileInfo->d2_zone[V2_NR_DZONES]; + if(!IndirectZone && Alloc) + { + IndirectZone = MxfsAllocZone(FileSys); + if(IndirectZone) + { + MxfsCacheZero(FileSys, IndirectZone << FileSys->Super.s_log_zone_size); + FileInfo->d2_zone[V2_NR_DZONES] = IndirectZone; + MxfsWriteInode(FileSys, FileIdx, FileInfo); + } + } + } + else // use double-indirect + { + zone_t DblIndZone; + unsigned DblIndBlock, DblIndIndex; + + DblIndZone = FileInfo->d2_zone[V2_NR_DZONES + 1]; + if(!DblIndZone && Alloc) + { + DblIndZone = MxfsAllocZone(FileSys); + if(DblIndZone) + { + MxfsCacheZero(FileSys, DblIndZone << FileSys->Super.s_log_zone_size); + FileInfo->d2_zone[V2_NR_DZONES + 1] = DblIndZone; + MxfsWriteInode(FileSys, FileIdx, FileInfo); + } + } + + if(!DblIndZone) + return -ERROR_NOT_FOUND; + + DblIndBlock = DblIndZone << FileSys->Super.s_log_zone_size; + IndIndex -= V2_INDIRECTS; + DblIndIndex = IndIndex / V2_INDIRECTS; + IndIndex = IndIndex % V2_INDIRECTS; + + if(DblIndIndex > V2_INDIRECTS) + { + ERR("Too big offset!\n"); + return -ERROR_INVALID_PARAMETER; + } + + Result = MxfsCacheRead(FileSys, &IndirectZone, DblIndBlock, DblIndIndex*sizeof(zone_t), sizeof(zone_t)); + if(Result < 0) + return Result; + + if(!IndirectZone && Alloc) + { + IndirectZone = MxfsAllocZone(FileSys); + if(IndirectZone) + { + MxfsCacheZero(FileSys, IndirectZone << FileSys->Super.s_log_zone_size); + MxfsCacheWrite(FileSys, &IndirectZone, DblIndBlock, DblIndIndex*sizeof(zone_t), sizeof(zone_t)); + } + } + } + + if(!IndirectZone) + return -ERROR_NOT_FOUND; + + IndirectBlock = IndirectZone << FileSys->Super.s_log_zone_size; + Result = MxfsCacheRead(FileSys, &Zone, IndirectBlock, IndIndex*sizeof(zone_t), sizeof(zone_t)); + if(Result < 0) + return Result; + + if(!Zone && Alloc) + { + Zone = MxfsAllocZone(FileSys); + if(Zone) + { + MxfsCacheZero(FileSys, Zone << FileSys->Super.s_log_zone_size); + MxfsCacheWrite(FileSys, &Zone, IndirectBlock, IndIndex*sizeof(zone_t), sizeof(zone_t)); + } + } + } + + if(!Zone) + return -ERROR_NOT_FOUND; + + unsigned Bit = MxfsBitFromZone(FileSys, Zone); + if(!MxfsGetBit(&FileSys->ZoneMap, Bit)) + { + WARN("NOT GOOD! Bit %u is not set\n", Bit); + } + + Block = Zone << FileSys->Super.s_log_zone_size; + Block += OffsetInBlocks - FirstZoneBlockOffset; // add offset in zone + return Block; +} + +int DOKAN_CALLBACK MxfsSetEndOfFile( + LPCWSTR FileName, + LONGLONG Length, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE("%ls", FileName); + + MINIX_FS *FileSys = (MINIX_FS*)(LONG_PTR)FileInfo->DokanOptions->GlobalContext; + FILE_CTX *FileCtx = (FILE_CTX*)(LONG_PTR)FileInfo->Context; + ASSERT(FileCtx && Length >= 0); + + minix_inode Info; + int Result = MxfsReadInode(FileSys, FileCtx->Index, &Info); + if(Result < 0) + return Result; + + unsigned ZoneSize = MINIX_BLOCK_SIZE << FileSys->Super.s_log_zone_size; + unsigned NewFirstFree = ALIGN_UP(Length, ZoneSize)/ZoneSize; + unsigned FirstFree = ALIGN_UP(Info.d2_size, ZoneSize)/ZoneSize; + + if(NewFirstFree >= FirstFree) + return 0; // done + + unsigned i = NewFirstFree; + while(i < V2_NR_DZONES) + { + if(Info.d2_zone[i]) + MxfsFreeZone(FileSys, Info.d2_zone[i]); + Info.d2_zone[i] = 0; + ++i; + } + + i -= V2_NR_DZONES; + unsigned IndirZone = Info.d2_zone[V2_NR_DZONES]; + if(IndirZone && i < V2_INDIRECTS) + { + unsigned IndirBlock = IndirZone << FileSys->Super.s_log_zone_size; + zone_t IndirTbl[V2_INDIRECTS]; + BOOL FreeIndirZone = (i == 0); + + Result = MxfsCacheRead(FileSys, IndirTbl, IndirBlock, 0, MINIX_BLOCK_SIZE); + if(Result >= 0) + { + while(i < V2_INDIRECTS) + { + if(IndirTbl[i]) + MxfsFreeZone(FileSys, IndirTbl[i]); + IndirTbl[i] = 0; + ++i; + } + if(FreeIndirZone) + MxfsFreeZone(FileSys, IndirZone); + else + MxfsCacheWrite(FileSys, IndirTbl, IndirBlock, 0, MINIX_BLOCK_SIZE); + } else + ERR("Failed to read indirect block\n"); + } + + // TODO: Free dbl indirect block zones! + Info.d2_size = Length; + Info.d2_mtime = time(NULL); + MxfsWriteInode(FileSys, FileCtx->Index, &Info); + + return 0; +} diff --git a/inode.h b/inode.h new file mode 100644 index 0000000..a529f07 --- /dev/null +++ b/inode.h @@ -0,0 +1,28 @@ +#ifndef INODE_H_INCLUDED +#define INODE_H_INCLUDED + +#include +#include "dokan.h" +#include "internal.h" + +int MxfsReadInode(MINIX_FS *FileSys, int FileIdx, minix_inode *Result); +int MxfsWriteInode(MINIX_FS *FileSys, int FileIdx, minix_inode *Result); +int MxfsAllocZone(MINIX_FS *FileSys); +int MxfsGetBlockFromFileOffset(MINIX_FS *FileSys, unsigned FileIdx, minix_inode *FileInfo, unsigned Offset, BOOL Alloc); + +int DOKAN_CALLBACK MxfsSetEndOfFile( + LPCWSTR FileName, + LONGLONG Length, + PDOKAN_FILE_INFO FileInfo); + +FORCEINLINE int MxfsZoneFromBit(MINIX_FS *FileSys, unsigned Bit) +{ + return FileSys->Super.s_firstdatazone + Bit - 1; +} + +FORCEINLINE int MxfsBitFromZone(MINIX_FS *FileSys, unsigned Zone) +{ + return Zone - FileSys->Super.s_firstdatazone + 1; +} + +#endif // INODE_H_INCLUDED diff --git a/internal.h b/internal.h new file mode 100644 index 0000000..54deab4 --- /dev/null +++ b/internal.h @@ -0,0 +1,148 @@ +#ifndef INTERNAL_H_INCLUDED +#define INTERNAL_H_INCLUDED + +#include +#include +#include + +/* Super block table. The root file system and every mounted file system + * has an entry here. The entry holds information about the sizes of the bit + * maps and inodes. The s_ninodes field gives the number of inodes available + * for files and directories, including the root directory. Inode 0 is + * on the disk, but not used. Thus s_ninodes = 4 means that 5 bits will be + * used in the bit map, bit 0, which is always 1 and not used, and bits 1-4 + * for files and directories. The disk layout is: + * + * Item # blocks + * boot block 1 + * super block 1 + * inode map s_imap_blocks + * zone map s_zmap_blocks + * inodes (s_ninodes + 'inodes per block' - 1)/'inodes per block' + * unused whatever is needed to fill out the current zone + * data zones (s_zones - s_firstdatazone) << s_log_zone_size + * + * A super_block slot is free if s_dev == NO_DEV. + */ + +#define MINIX_BLOCK_SIZE 1024 +#define BLOCK_SIZE MINIX_BLOCK_SIZE +#define usizeof sizeof + +#define SUPER_V2_REV 0x2468 /* V2 magic written on PC, read on 68K or vv */ + +/* Tables sizes */ +#define V1_NR_DZONES 7 /* # direct zone numbers in a V1 inode */ +#define V1_NR_TZONES 9 /* total # zone numbers in a V1 inode */ +#define V2_NR_DZONES 7 /* # direct zone numbers in a V2 inode */ +#define V2_NR_TZONES 10 /* total # zone numbers in a V2 inode */ + +/* Types used in disk, inode, etc. data structures. */ +typedef short dev_t; /* holds (major|minor) device pair */ +typedef char gid_t; /* group id */ +typedef unsigned short ino_t; /* i-node number */ +typedef unsigned short mode_t; /* file type and permissions bits */ +typedef char nlink_t; /* number of links to a file */ +typedef unsigned long off_t; /* offset within a file */ +typedef int pid_t; /* process id (must be signed) */ +typedef short uid_t; /* user id */ +typedef unsigned long zone_t; /* zone number */ +typedef unsigned long block_t; /* block number */ +typedef unsigned long bit_t; /* bit number in a bit map */ +typedef unsigned short zone1_t; /* zone number for V1 file systems */ +typedef unsigned short bitchunk_t; /* collection of bits in a bitmap */ + +typedef unsigned char u8_t; /* 8 bit type */ +typedef unsigned short u16_t; /* 16 bit type */ +typedef unsigned long u32_t; /* 32 bit type */ + +typedef char i8_t; /* 8 bit signed type */ +typedef short i16_t; /* 16 bit signed type */ +typedef long i32_t; /* 32 bit signed type */ + +#define ROOT_INODE 1 /* inode number for root directory */ +#define BOOT_BLOCK ((block_t) 0) /* block number of boot block */ +#define SUPER_BLOCK ((block_t) 1) /* block number of super block */ + +#define DIR_ENTRY_SIZE usizeof (struct direct) /* # bytes/dir entry */ +#define NR_DIR_ENTRIES (BLOCK_SIZE/DIR_ENTRY_SIZE) /* # dir entries/blk */ +#define SUPER_SIZE usizeof (struct super_block) /* super_block size */ +#define PIPE_SIZE (V1_NR_DZONES*BLOCK_SIZE) /* pipe size in bytes */ +#define BITMAP_CHUNKS (BLOCK_SIZE/usizeof (bitchunk_t))/* # map chunks/blk */ + +/* Derived sizes pertaining to the V1 file system. */ +#define V1_ZONE_NUM_SIZE usizeof (zone1_t) /* # bytes in V1 zone */ +#define V1_INODE_SIZE usizeof (d1_inode) /* bytes in V1 dsk ino */ +#define V1_INDIRECTS (BLOCK_SIZE/V1_ZONE_NUM_SIZE) /* # zones/indir block */ +#define V1_INODES_PER_BLOCK (BLOCK_SIZE/V1_INODE_SIZE)/* # V1 dsk inodes/blk */ + +/* Derived sizes pertaining to the V2 file system. */ +#define V2_ZONE_NUM_SIZE usizeof (zone_t) /* # bytes in V2 zone */ +#define V2_INODE_SIZE usizeof (d2_inode) /* bytes in V2 dsk ino */ +#define V2_INDIRECTS (BLOCK_SIZE/V2_ZONE_NUM_SIZE) /* # zones/indir block */ +#define V2_INODES_PER_BLOCK (BLOCK_SIZE/V2_INODE_SIZE)/* # V2 dsk inodes/blk */ + +struct super_block +{ + ino_t s_ninodes; /* # usable inodes on the minor device */ + zone1_t s_nzones; /* total device size, including bit maps etc */ + short s_imap_blocks; /* # of blocks used by inode bit map */ + short s_zmap_blocks; /* # of blocks used by zone bit map */ + zone1_t s_firstdatazone; /* number of first data zone */ + short s_log_zone_size; /* log2 of blocks/zone */ + off_t s_max_size; /* maximum file size on this device */ + short s_magic; /* magic number to recognize super-blocks */ + short s_pad; /* try to avoid compiler-dependent padding */ + zone_t s_zones; /* number of zones (replaces s_nzones in V2) */ +}; + +/* Declaration of the V1 inode as it is on the disk (not in core). */ +typedef struct { /* V1.x disk inode */ + mode_t d1_mode; /* file type, protection, etc. */ + uid_t d1_uid; /* user id of the file's owner */ + off_t d1_size; /* current file size in bytes */ + time_t d1_mtime; /* when was file data last changed */ + gid_t d1_gid; /* group number */ + nlink_t d1_nlinks; /* how many links to this file */ + u16_t d1_zone[V1_NR_TZONES]; /* block nums for direct, ind, and dbl ind */ +} d1_inode; + +/* Declaration of the V2 inode as it is on the disk (not in core). */ +typedef struct { /* V2.x disk inode */ + mode_t d2_mode; /* file type, protection, etc. */ + u16_t d2_nlinks; /* how many links to this file. HACK! */ + uid_t d2_uid; /* user id of the file's owner. */ + u16_t d2_gid; /* group number HACK! */ + off_t d2_size; /* current file size in bytes */ + time_t d2_atime; /* when was file data last accessed */ + time_t d2_mtime; /* when was file data last changed */ + time_t d2_ctime; /* when was inode data last changed */ + zone_t d2_zone[V2_NR_TZONES]; /* block nums for direct, ind, and dbl ind */ +} d2_inode; + +/* Flag bits for i_mode in the inode. */ +#define I_TYPE 0170000 /* this field gives inode type */ +#define I_REGULAR 0100000 /* regular file, not dir or special */ +#define I_BLOCK_SPECIAL 0060000 /* block special file */ +#define I_DIRECTORY 0040000 /* file is a directory */ +#define I_CHAR_SPECIAL 0020000 /* character special file */ +#define I_NAMED_PIPE 0010000 /* named pipe (FIFO) */ +#define I_SET_UID_BIT 0004000 /* set effective uid_t on exec */ +#define I_SET_GID_BIT 0002000 /* set effective gid_t on exec */ +#define ALL_MODES 0006777 /* all bits for user, group and others */ +#define RWX_MODES 0000777 /* mode bits for RWX only */ +#define R_BIT 0000004 /* Rwx protection bit */ +#define W_BIT 0000002 /* rWx protection bit */ +#define X_BIT 0000001 /* rwX protection bit */ +#define I_NOT_ALLOC 0000000 /* this inode is free */ + +#define MAX_NAME_LEN 14 + +typedef struct { /* V2.x disk inode */ + u16_t inode; + char name[MAX_NAME_LEN]; +} minix_dir_entry; + +typedef d2_inode minix_inode; + +#endif // INTERNAL_H_INCLUDED diff --git a/list.h b/list.h new file mode 100644 index 0000000..43f7c30 --- /dev/null +++ b/list.h @@ -0,0 +1,120 @@ +#ifndef LIST_H_INCLUDED +#define LIST_H_INCLUDED + +#include + +#ifndef FORCEINLINE +#define FORCEINLINE __inline__ +#endif // FORCEINLINE + +#if !defined(_LIST_ENTRY_DEFINED) && !defined(_MSC_VER) +typedef struct _LIST_ENTRY { + struct _LIST_ENTRY *Flink; + struct _LIST_ENTRY *Blink; +} LIST_ENTRY, *PLIST_ENTRY; +#endif // _LIST_ENTRY_DEFINED + +FORCEINLINE +VOID +InitializeListHead( + OUT PLIST_ENTRY ListHead) +{ + ListHead->Flink = ListHead->Blink = ListHead; +} + +FORCEINLINE +BOOLEAN +IsListEmpty( + IN CONST LIST_ENTRY * ListHead) +{ + return (BOOLEAN)(ListHead->Flink == ListHead); +} + +FORCEINLINE +BOOLEAN +RemoveEntryList( + IN PLIST_ENTRY Entry) +{ + PLIST_ENTRY OldFlink; + PLIST_ENTRY OldBlink; + + OldFlink = Entry->Flink; + OldBlink = Entry->Blink; + OldFlink->Blink = OldBlink; + OldBlink->Flink = OldFlink; + return (BOOLEAN)(OldFlink == OldBlink); +} + +FORCEINLINE +PLIST_ENTRY +RemoveHeadList( + IN OUT PLIST_ENTRY ListHead) +{ + PLIST_ENTRY Flink; + PLIST_ENTRY Entry; + + Entry = ListHead->Flink; + Flink = Entry->Flink; + ListHead->Flink = Flink; + Flink->Blink = ListHead; + return Entry; +} + +FORCEINLINE +PLIST_ENTRY +RemoveTailList( + IN OUT PLIST_ENTRY ListHead) +{ + PLIST_ENTRY Blink; + PLIST_ENTRY Entry; + + Entry = ListHead->Blink; + Blink = Entry->Blink; + ListHead->Blink = Blink; + Blink->Flink = ListHead; + return Entry; +} + +FORCEINLINE +VOID +InsertTailList( + IN OUT PLIST_ENTRY ListHead, + IN OUT PLIST_ENTRY Entry) +{ + PLIST_ENTRY OldBlink; + OldBlink = ListHead->Blink; + Entry->Flink = ListHead; + Entry->Blink = OldBlink; + OldBlink->Flink = Entry; + ListHead->Blink = Entry; +} + +FORCEINLINE +VOID +InsertHeadList( + IN OUT PLIST_ENTRY ListHead, + IN OUT PLIST_ENTRY Entry) +{ + PLIST_ENTRY OldFlink; + OldFlink = ListHead->Flink; + Entry->Flink = OldFlink; + Entry->Blink = ListHead; + OldFlink->Blink = Entry; + ListHead->Flink = Entry; +} + +FORCEINLINE +VOID +AppendTailList( + IN OUT PLIST_ENTRY ListHead, + IN OUT PLIST_ENTRY ListToAppend) +{ + PLIST_ENTRY ListEnd = ListHead->Blink; + + ListHead->Blink->Flink = ListToAppend; + ListHead->Blink = ListToAppend->Blink; + ListToAppend->Blink->Flink = ListHead; + ListToAppend->Blink = ListEnd; +} + +#endif // LIST_H_INCLUDED diff --git a/main.c b/main.c new file mode 100644 index 0000000..a34f49d --- /dev/null +++ b/main.c @@ -0,0 +1,260 @@ +#include +#include +#include +#include +#include +#include "partitions.h" +#include "general.h" +#include "debug.h" +#include "dokan.h" +#include "stubs.h" +#include "file.h" +#include "file_info.h" +#include "dir.h" +#include "read_write.h" +#include "inode.h" +#include "volume_info.h" +#include "disk_image.h" + +#define VERSION "0.4" + +typedef struct +{ + unsigned Part; + unsigned Subpart; + const char *Path; + SUBPARTITION *pSubpart; + DISK_IMG *pImg; + HANDLE hEvent; + volatile BOOL bMounted; +} MOUNT_REQ; + +static DOKAN_OPERATIONS DokanCallbacks = { + MxfsCreateFile, // CreateFile + MxfsOpenDirectory, // OpenDirectory + MxfsCreateDirectory, // CreateDirectory + MxfsCleanup, // Cleanup + MxfsCloseFile, // CloseFile + MxfsReadFile, // ReadFile + MxfsWriteFile, // WriteFile + MxfsFlushFileBuffers, // FlushFileBuffers + MxfsGetFileInformation, // GetFileInformation, + MxfsFindFiles, // FindFiles + NULL, // FindFilesWithPattern + MxfsSetFileAttributes, // SetFileAttributes + MxfsSetFileTime, // SetFileTime + MxfsDeleteFile, // DeleteFile + MxfsDeleteDirectory, // DeleteDirectory + MxfsMoveFile, // MoveFile + MxfsSetEndOfFile, // SetEndOfFile + MxfsSetAllocationSize, // SetAllocationSize + MxfsLockFile, // LockFile + MxfsUnlockFile, // UnlockFile + MxfsGetDiskFreeSpace, // GetDiskFreeSpace + MxfsGetVolumeInformation, // GetVolumeInformation + MxfsUnmount, // Unmount + MxfsGetFileSecurity, // GetFileSecurity + MxfsSetFileSecurity, // SetFileSecurity +}; + +static HANDLE g_Threads[16]; +static unsigned g_cThreads = 0; +static MOUNT_REQ g_MountReq[16]; +static unsigned g_cMountReq = 0; + +static DWORD WINAPI MxfsThread(LPVOID lpParameter) +{ + MOUNT_REQ *pReq = (MOUNT_REQ*)lpParameter; + MINIX_FS *pFileSys; + WCHAR wszMountPath[MAX_PATH]; + + mbstowcs(wszMountPath, pReq->Path, sizeof(wszMountPath)/sizeof(wszMountPath[0])); + + pFileSys = MxfsOpen(pReq->pImg, pReq->pSubpart->Offset, pReq->pSubpart->Size); + if(!pFileSys) + { + ERR("Failed to open Minix filesystem\n"); + SetEvent(pReq->hEvent); + return (DWORD)-1; + } + + DOKAN_OPTIONS Opts = { + DOKAN_VERSION, // Version + 1, // ThreadCount, TODO: support many threads... + DOKAN_OPTION_DEBUG, // Options + (ULONG64)(LONG_PTR)pFileSys, // GlobalContext + wszMountPath, // MountPoint + }; + + SetEvent(pReq->hEvent); + pReq->bMounted = TRUE; + int err = DokanMain(&Opts, &DokanCallbacks); + if(err < 0) + ERR("DokanMain failed: %d\n", err); + pReq->bMounted = FALSE; + MxfsClose(pFileSys); + return 0; +} + +static int MxfsMount(MOUNT_REQ *pReq) +{ + pReq->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + HANDLE hThread = CreateThread(NULL, 0, MxfsThread, pReq, 0, NULL); + if(!hThread) + return -1; + + WaitForSingleObject(pReq->hEvent, INFINITE); + CloseHandle(pReq->hEvent); + Sleep(10); + + g_Threads[g_cThreads++] = hThread; + return 0; +} + +static LONG WINAPI ExceptionHandler(struct _EXCEPTION_POINTERS *ExceptionInfo) +{ + ERR("Exception!!!\n"); + return EXCEPTION_EXECUTE_HANDLER; +} + +static BOOL WINAPI CtrlHandler(DWORD dwCtrlType) +{ + printf("Console Ctrl Handler: unmounting...\n"); + unsigned i; + WCHAR wszMountPath[MAX_PATH]; + + for(i = 0; i < g_cMountReq; ++i) + { + if(!g_MountReq[i].bMounted) + continue; + + mbstowcs(wszMountPath, g_MountReq[i].Path, sizeof(wszMountPath)/sizeof(wszMountPath[0])); + BOOL Result = DokanRemoveMountPoint(wszMountPath); + if(!Result) + ERR("DokanRemoveMountPoint(%ls) failed!\n", wszMountPath); + } + + return TRUE; +} + +static void DisplayHelp(const char *pProcName) +{ + printf("Usage: %s [-l] [-f img_path p0s0=mount_path1 p0s1=mount_path2 ...]\n", pProcName); + printf("-l\t\t\tLists all partitions and subpartitions\n"); + printf("-f img_path\t\tSets disk image path\n"); + printf("p0s0=mount_path\t\tMounts subpartition 0 on partition 0 in given path\n"); +} + +static void Test() +{ +#ifndef NDEBUG + FILETIME ft; + time_t t = time(NULL); + MxfsFileTimeFromTimeT(&ft, t); + assert(t == MxfsTimeTFromFileTime(&ft)); +#endif +} + +int main(int argc, char *argv[]) +{ + const char *pszPath = NULL; + unsigned i; + BOOL bHelp = FALSE, bList = FALSE; + + printf("MINIX Filesystem Driver " VERSION " for Windows (c) 2013 Rafal Harabien\n"); + + Test(); + + for(i = 1; i < (unsigned)argc; ++i) + { + if(!strcmp(argv[i], "-f") && i + 1 < (unsigned)argc) + pszPath = argv[++i]; + else if(!strcmp(argv[i], "-l")) + bList = TRUE; + else if(!strcmp(argv[i], "-h")) + { + bHelp = TRUE; + break; + } + else + { + unsigned uPart, uSubpart; + if(sscanf(argv[i], "p%us%u=", &uPart, &uSubpart) == 2 && g_cMountReq < COUNTOF(g_MountReq)) + { + const char *pszMount = strchr(argv[i], '=') + 1; + g_MountReq[g_cMountReq].Part = uPart; + g_MountReq[g_cMountReq].Subpart = uSubpart; + g_MountReq[g_cMountReq].Path = pszMount; + g_MountReq[g_cMountReq].bMounted = FALSE; + ++g_cMountReq; + } else + printf("Invalid argument: %s\n", argv[i]); + } + } + + if(!pszPath) + bHelp = TRUE; + if(bHelp) + { + DisplayHelp(argv[0]); + return 0; + } + + DISK_IMG *pImg = ImgOpen(pszPath); + if(!pImg) + { + ERR("Failed to open image: %s\n", pszPath); + return -1; + } + + SUBPART_VECTOR SubpartVect; + int err = FindSubpartitions(pImg, &SubpartVect); + if(err < 0) + { + ERR("FindSubpartitions failed!\n"); + ImgClose(pImg); + return err; + } + + if(bList) + { + printf("Subpartitions:\n"); + for(i = 0; i < SubpartVect.Count; ++i) + { + SUBPARTITION *pSubpart = &SubpartVect.Entries[i]; + printf("p%us%u - offset 0x%x size 0x%x\n", pSubpart->PartIdx, pSubpart->SubpartIdx, pSubpart->Offset, pSubpart->Size); + } + } + + SetUnhandledExceptionFilter(ExceptionHandler); + + SetConsoleCtrlHandler(CtrlHandler, TRUE); + + for(i = 0; i < g_cMountReq; ++i) + { + unsigned j; + for(j = 0; j < SubpartVect.Count; ++j) + { + if(g_MountReq[i].Part == SubpartVect.Entries[j].PartIdx && g_MountReq[i].Subpart == SubpartVect.Entries[j].SubpartIdx) + break; + } + + if(j < SubpartVect.Count) + { + printf("Mounting p%us%u in %s...\n", g_MountReq[i].Part, g_MountReq[i].Subpart, g_MountReq[i].Path); + g_MountReq[i].pSubpart = &SubpartVect.Entries[j]; + g_MountReq[i].pImg = pImg; + MxfsMount(&g_MountReq[i]); + } + } + + WaitForMultipleObjects(g_cThreads, g_Threads, TRUE, INFINITE); + for(i = 0; i < g_cThreads; ++i) + CloseHandle(g_Threads[i]); + + MxfsFree(SubpartVect.Entries); + ImgClose(pImg); + + printf("Done!\n"); + return 0; +} diff --git a/mbr.h b/mbr.h new file mode 100644 index 0000000..a317d96 --- /dev/null +++ b/mbr.h @@ -0,0 +1,43 @@ +#ifndef MBR_H_INCLUDED +#define MBR_H_INCLUDED + +#include + +#define MBR_BOOT_SIG 0xAA55 +#define MBR_PART_COUNT 4 + +typedef enum +{ + MBR_PART_FREE = 0x00, + MBR_PART_MINIX14 = 0x81, +} MBR_PART_TYPE; + +#pragma pack(push, 1) + +typedef struct +{ + uint8_t uHead; + uint8_t uCylHiSect; + uint8_t uCylLo; +} MBR_CHS; + +typedef struct +{ + uint8_t uStatus; + MBR_CHS FirstSectChs; + MBR_PART_TYPE Type: 8; + MBR_CHS LastSectChs; + uint32_t uFirstSectLba; + uint32_t cSect; +} MBR_PARTITION; + +typedef struct +{ + char Bootstrap[446]; + MBR_PARTITION Partitions[MBR_PART_COUNT]; + uint16_t uBootSig; +} MBR; + +#pragma pack(pop) + +#endif // MBR_H_INCLUDED diff --git a/partitions.c b/partitions.c new file mode 100644 index 0000000..59769d9 --- /dev/null +++ b/partitions.c @@ -0,0 +1,106 @@ +#include +#include +#include "partitions.h" +#include "debug.h" + +int LoadMBR(DISK_IMG *pImg, MBR *pMbr) +{ + assert(sizeof(*pMbr) == 512); + + int err = ImgRead(pImg, 0, sizeof(*pMbr), pMbr); + if(err < 0) + { + ERR("Failed to load MBR\n"); + return err; + } + + if(pMbr->uBootSig != MBR_BOOT_SIG) + { + WARN("Invalid signature: %u\n", pMbr->uBootSig); + return -1; + } + + return 0; +} + +void AddSubpartToVect(SUBPART_VECTOR *pVect, unsigned Offset, unsigned Size, unsigned PartIdx, unsigned SubpartIdx) +{ + if(pVect->Count % 4 == 0) + pVect->Entries = realloc(pVect->Entries, (pVect->Count + 4) * sizeof(SUBPARTITION)); + + SUBPARTITION *pSubpart = &pVect->Entries[pVect->Count++]; + pSubpart->Offset = Offset; + pSubpart->Size = Size; + pSubpart->PartIdx = PartIdx; + pSubpart->SubpartIdx = SubpartIdx; +} + +int FindSubpartsOnPart(DISK_IMG *pImg, unsigned Offset, unsigned Size, unsigned PartIdx, SUBPART_VECTOR *pVect) +{ + MBR Ebr; + + if(Size > 512) + { + int err = ImgRead(pImg, Offset, sizeof(Ebr), &Ebr); + if(err < 0) + return err; + } else + Ebr.uBootSig = 0; + + if(Ebr.uBootSig != MBR_BOOT_SIG) + { + // No subpartitions + AddSubpartToVect(pVect, Offset, Size, PartIdx, 0); + return 0; + } + + unsigned i; + for(i = 0; i < 4; ++i) + { + MBR_PARTITION *pPart = &Ebr.Partitions[i]; + if(pPart->Type == MBR_PART_FREE) continue; + + unsigned SubpartOffset = pPart->uFirstSectLba * 512; + unsigned SubpartSize = pPart->cSect * 512; + AddSubpartToVect(pVect, SubpartOffset, SubpartSize, PartIdx, i); + + TRACE("EBR[%u]: Type 0x%x uFirstSectLba 0x%x cSect 0x%x off 0x%x\n", i, pPart->Type, pPart->uFirstSectLba, pPart->cSect, pPart->uFirstSectLba*512); + } + + return 0; +} + +int FindSubpartitions(DISK_IMG *pImg, SUBPART_VECTOR *pVect) +{ + pVect->Count = 0; + pVect->Entries = NULL; + + MBR Mbr; + int iRet = LoadMBR(pImg, &Mbr); + if(iRet < 0) + { + // No partitions + AddSubpartToVect(pVect, 0, ImgGetSize(pImg), 0, 0); + return 0; + } + + unsigned i; + for(i = 0; i < MBR_PART_COUNT; ++i) + { + MBR_PARTITION *pPart = &Mbr.Partitions[i]; + if(pPart->Type == MBR_PART_MINIX14) + { + TRACE("Found MINIX partition: %u. MBR entry, lba 0x%x, size 0x%x\n", i, pPart->uFirstSectLba, pPart->cSect); + unsigned Offset = pPart->uFirstSectLba * 512; + unsigned Size = pPart->cSect * 512; + FindSubpartsOnPart(pImg, Offset, Size, i, pVect); + } + else if(pPart->Type != MBR_PART_FREE) + INFO("Unknown partition %u: type 0x%x, lba 0x%x, size 0x%x\n", i, pPart->Type, pPart->uFirstSectLba, pPart->cSect); + } + + if(pVect->Count == 0) + ERR("Failed to find a MINIX partition\n"); + + return 0; +} diff --git a/partitions.h b/partitions.h new file mode 100644 index 0000000..f3d57ad --- /dev/null +++ b/partitions.h @@ -0,0 +1,25 @@ +#ifndef PARTITIONS_H_INCLUDED +#define PARTITIONS_H_INCLUDED + +#include +#include "mbr.h" +#include "disk_image.h" + +typedef struct +{ + unsigned PartIdx; + unsigned SubpartIdx; + unsigned Offset; + unsigned Size; +} SUBPARTITION; + +typedef struct +{ + SUBPARTITION *Entries; + unsigned Count; +} SUBPART_VECTOR; + +int LoadMBR(DISK_IMG *pImg, MBR *pMbr); +int FindSubpartitions(DISK_IMG *pImg, SUBPART_VECTOR *pVect); + +#endif // PARTITIONS_H_INCLUDED diff --git a/read_write.c b/read_write.c new file mode 100644 index 0000000..641a376 --- /dev/null +++ b/read_write.c @@ -0,0 +1,131 @@ +#include +#include "debug.h" +#include "file.h" +#include "read_write.h" +#include "inode.h" + +int MxfsReadBlocks(MINIX_FS *FileSys, void *Buffer, unsigned FirstBlock, unsigned BlocksCount) +{ + while(BlocksCount > 0) + { + int Result = MxfsCacheRead(FileSys, Buffer, FirstBlock, 0, MINIX_BLOCK_SIZE); + if(Result < 0) + return Result; + + Buffer = ((PBYTE)Buffer) + MINIX_BLOCK_SIZE; + ++FirstBlock; + --BlocksCount; + } + return 0; +} + +int DOKAN_CALLBACK MxfsReadFile( + LPCWSTR FileName, + LPVOID Buffer, + DWORD NumberOfBytesToRead, + LPDWORD NumberOfBytesRead, + LONGLONG Offset, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE("%ls %lu %I64u", FileName, NumberOfBytesToRead, Offset); + + MINIX_FS *FileSys = (MINIX_FS*)(LONG_PTR)FileInfo->DokanOptions->GlobalContext; + FILE_CTX *FileCtx = (FILE_CTX*)(LONG_PTR)FileInfo->Context; + if(!FileCtx || Offset < 0) + { + ERR("Invalid params %p %I64d\n", FileCtx, Offset); + return -ERROR_INVALID_PARAMETER; + } + ASSERT(FileCtx && Offset >= 0); + + minix_inode Info; + int Result = MxfsReadInode(FileSys, FileCtx->Index, &Info); + if(Result < 0) + return Result; + + *NumberOfBytesRead = 0; + + if(Offset > FileCtx->Length) + Offset = FileCtx->Length; + if(Offset + NumberOfBytesToRead > FileCtx->Length) + NumberOfBytesToRead = FileCtx->Length - Offset; + + while(NumberOfBytesToRead > 0) + { + int Block = MxfsGetBlockFromFileOffset(FileSys, FileCtx->Index, &Info, Offset, FALSE); + if(Block < 0) + return Block; + + unsigned ChunkOffset = Offset % MINIX_BLOCK_SIZE; + unsigned ChunkLength = min(NumberOfBytesToRead, MINIX_BLOCK_SIZE - ChunkOffset); + + int Result = MxfsCacheRead(FileSys, Buffer, Block, ChunkOffset, ChunkLength); + if(Result < 0) + return Result; + + Buffer = ((PBYTE)Buffer) + ChunkLength; + Offset += ChunkLength; + NumberOfBytesToRead -= ChunkLength; + *NumberOfBytesRead += ChunkLength; + } + + return 0; +} + +int DOKAN_CALLBACK MxfsWriteFile( + LPCWSTR FileName, + LPCVOID Buffer, + DWORD NumberOfBytesToWrite, + LPDWORD NumberOfBytesWritten, + LONGLONG Offset, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE("%ls %lu %I64d\n", FileName, NumberOfBytesToWrite, Offset); + + MINIX_FS *FileSys = (MINIX_FS*)(LONG_PTR)FileInfo->DokanOptions->GlobalContext; + FILE_CTX *FileCtx = (FILE_CTX*)(LONG_PTR)FileInfo->Context; + ASSERT(FileCtx && Offset >= 0); + + minix_inode Info; + int Result = MxfsReadInode(FileSys, FileCtx->Index, &Info); + if(Result < 0) + return Result; + + *NumberOfBytesWritten = 0; + + if(Offset > FileCtx->Length) + Offset = FileCtx->Length; + + while(NumberOfBytesToWrite > 0) + { + int Block = MxfsGetBlockFromFileOffset(FileSys, FileCtx->Index, &Info, Offset, TRUE); + if(Block < 0) + { + ERR("MxfsGetBlockFromFileOffset failed %I64d\n", Offset); + Result = Block; + break; + } + + unsigned ChunkOffset = Offset % MINIX_BLOCK_SIZE; + unsigned ChunkLength = min(NumberOfBytesToWrite, MINIX_BLOCK_SIZE - ChunkOffset); + + Result = MxfsCacheWrite(FileSys, Buffer, Block, ChunkOffset, ChunkLength); + if(Result < 0) + break; + + Buffer = ((PBYTE)Buffer) + ChunkLength; + Offset += ChunkLength; + NumberOfBytesToWrite -= ChunkLength; + *NumberOfBytesWritten += ChunkLength; + } + + if(*NumberOfBytesWritten > 0) + { + if(Offset > FileCtx->Length) + FileCtx->Length = Offset; + + FileCtx->Changed = TRUE; + } + + return Result; +} diff --git a/read_write.h b/read_write.h new file mode 100644 index 0000000..c9c4027 --- /dev/null +++ b/read_write.h @@ -0,0 +1,25 @@ +#ifndef READ_WRITE_H_INCLUDED +#define READ_WRITE_H_INCLUDED + +#include "general.h" +#include "dokan.h" + +int MxfsReadBlocks(MINIX_FS *FileSys, void *pBuffer, unsigned Block, unsigned Count); + +int DOKAN_CALLBACK MxfsReadFile( + LPCWSTR FileName, + LPVOID Buffer, + DWORD NumberOfBytesToRead, + LPDWORD NumberOfBytesRead, + LONGLONG Offset, + PDOKAN_FILE_INFO FileInfo); + +int DOKAN_CALLBACK MxfsWriteFile( + LPCWSTR FileName, + LPCVOID Buffer, + DWORD NumberOfBytesToWrite, + LPDWORD NumberOfBytesWritten, + LONGLONG Offset, + PDOKAN_FILE_INFO FileInfo); + +#endif // READ_WRITE_H_INCLUDED diff --git a/stubs.c b/stubs.c new file mode 100644 index 0000000..19901f2 --- /dev/null +++ b/stubs.c @@ -0,0 +1,118 @@ +#include "stubs.h" +#include "general.h" +#include "debug.h" +#include "internal.h" + +int DOKAN_CALLBACK MxfsMoveFile( + LPCWSTR ExistingFileName, + LPCWSTR NewFileName, + BOOL ReplaceExisiting, + PDOKAN_FILE_INFO FileInfo) +{ + UNIMPLEMENTED("%ls %ls %u", ExistingFileName, NewFileName, ReplaceExisiting ? 1 : 0); + return -ERROR_CALL_NOT_IMPLEMENTED; +} + +int DOKAN_CALLBACK MxfsLockFile( + LPCWSTR FileName, + LONGLONG ByteOffset, + LONGLONG Length, + PDOKAN_FILE_INFO FileInfo) +{ + UNIMPLEMENTED("%ls %I64u %I64u", FileName, ByteOffset, Length); + return -ERROR_CALL_NOT_IMPLEMENTED; +} + +int DOKAN_CALLBACK MxfsUnlockFile( + LPCWSTR FileName, + LONGLONG ByteOffset, + LONGLONG Length, + PDOKAN_FILE_INFO FileInfo) +{ + UNIMPLEMENTED("%ls %I64u %I64u", FileName, ByteOffset, Length); + return -ERROR_CALL_NOT_IMPLEMENTED; +} + +int DOKAN_CALLBACK MxfsUnmount( + PDOKAN_FILE_INFO FileInfo) +{ + UNIMPLEMENTED(""); + return -ERROR_CALL_NOT_IMPLEMENTED; +} + +// Optional +int DOKAN_CALLBACK MxfsFlushFileBuffers( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE("%ls", FileName); + MINIX_FS *FileSys = (MINIX_FS*)(ULONG_PTR)FileInfo->DokanOptions->GlobalContext; + MxfsCacheFlush(FileSys); // flush entire FS... + return 0; +} + +int DOKAN_CALLBACK MxfsSetFileAttributes( + LPCWSTR FileName, + DWORD FileAttributes, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE("%ls", FileName); + return 0; +} + +// You should not delete file on DeleteFile or DeleteDirectory. +// When DeleteFile or DeleteDirectory, you must check whether +// you can delete the file or not, and return 0 (when you can delete it) +// or appropriate error codes such as -ERROR_DIR_NOT_EMPTY, +// -ERROR_SHARING_VIOLATION. +// When you return 0 (ERROR_SUCCESS), you get Cleanup with +// FileInfo->DeleteOnClose set TRUE and you have to delete the +// file in Close. +int DOKAN_CALLBACK MxfsDeleteFile( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo) +{ + UNIMPLEMENTED("%ls", FileName); + return -ERROR_CALL_NOT_IMPLEMENTED; +} + +int DOKAN_CALLBACK MxfsDeleteDirectory( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo) +{ + UNIMPLEMENTED("%ls", FileName); + return -ERROR_CALL_NOT_IMPLEMENTED; +} + +int DOKAN_CALLBACK MxfsSetAllocationSize( + LPCWSTR FileName, + LONGLONG Length, + PDOKAN_FILE_INFO FileInfo) +{ + UNIMPLEMENTED("%ls", FileName); + return -ERROR_CALL_NOT_IMPLEMENTED; +} + +// Suported since 0.6.0. You must specify the version at DOKAN_OPTIONS.Version. +int DOKAN_CALLBACK MxfsGetFileSecurity( + LPCWSTR FileName, + PSECURITY_INFORMATION SecurityInfo, // A pointer to SECURITY_INFORMATION value being requested + PSECURITY_DESCRIPTOR SecurityDescriptor, // A pointer to SECURITY_DESCRIPTOR buffer to be filled + ULONG SecurityDescriptorLength, // length of Security descriptor buffer + PULONG LengthNeeded, + PDOKAN_FILE_INFO FileInfo) +{ + UNIMPLEMENTED("%ls", FileName); + return -ERROR_CALL_NOT_IMPLEMENTED; +} + +int DOKAN_CALLBACK MxfsSetFileSecurity( + LPCWSTR FileName, + PSECURITY_INFORMATION SecurityInfo, + PSECURITY_DESCRIPTOR SecurityDescriptor, + ULONG SecurityDescriptorLength, + PDOKAN_FILE_INFO FileInfo) +{ + UNIMPLEMENTED("%ls", FileName); + return -ERROR_CALL_NOT_IMPLEMENTED; +} diff --git a/stubs.h b/stubs.h new file mode 100644 index 0000000..37ce953 --- /dev/null +++ b/stubs.h @@ -0,0 +1,75 @@ +#ifndef STUBS_H_INCLUDED +#define STUBS_H_INCLUDED + +#include +#include "dokan.h" + +int DOKAN_CALLBACK MxfsMoveFile( + LPCWSTR ExistingFileName, + LPCWSTR NewFileName, + BOOL ReplaceExisiting, + PDOKAN_FILE_INFO FileInfo); + +int DOKAN_CALLBACK MxfsLockFile( + LPCWSTR FileName, + LONGLONG ByteOffset, + LONGLONG Length, + PDOKAN_FILE_INFO FileInfo); + +int DOKAN_CALLBACK MxfsUnlockFile( + LPCWSTR FileName, + LONGLONG ByteOffset, + LONGLONG Length, + PDOKAN_FILE_INFO FileInfo); + +int DOKAN_CALLBACK MxfsUnmount( + PDOKAN_FILE_INFO FileInfo); + +// Optional +int DOKAN_CALLBACK MxfsFlushFileBuffers( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo); + +int DOKAN_CALLBACK MxfsSetFileAttributes( + LPCWSTR FileName, + DWORD FileAttributes, + PDOKAN_FILE_INFO FileInfo); + +// You should not delete file on DeleteFile or DeleteDirectory. +// When DeleteFile or DeleteDirectory, you must check whether +// you can delete the file or not, and return 0 (when you can delete it) +// or appropriate error codes such as -ERROR_DIR_NOT_EMPTY, +// -ERROR_SHARING_VIOLATION. +// When you return 0 (ERROR_SUCCESS), you get Cleanup with +// FileInfo->DeleteOnClose set TRUE and you have to delete the +// file in Close. +int DOKAN_CALLBACK MxfsDeleteFile( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo); + +int DOKAN_CALLBACK MxfsDeleteDirectory( + LPCWSTR FileName, + PDOKAN_FILE_INFO FileInfo); + +int DOKAN_CALLBACK MxfsSetAllocationSize( + LPCWSTR FileName, + LONGLONG Length, + PDOKAN_FILE_INFO FileInfo); + +// Suported since 0.6.0. You must specify the version at DOKAN_OPTIONS.Version. +int DOKAN_CALLBACK MxfsGetFileSecurity( + LPCWSTR FileName, + PSECURITY_INFORMATION SecurityInfo, // A pointer to SECURITY_INFORMATION value being requested + PSECURITY_DESCRIPTOR SecurityDescriptor, // A pointer to SECURITY_DESCRIPTOR buffer to be filled + ULONG SecurityDescriptorLength, // length of Security descriptor buffer + PULONG LengthNeeded, + PDOKAN_FILE_INFO FileInfo); + +int DOKAN_CALLBACK MxfsSetFileSecurity( + LPCWSTR FileName, + PSECURITY_INFORMATION SecurityInfo, + PSECURITY_DESCRIPTOR SecurityDescriptor, + ULONG SecurityDescriptorLength, + PDOKAN_FILE_INFO FileInfo); + +#endif // STUBS_H_INCLUDED diff --git a/volume_info.c b/volume_info.c new file mode 100644 index 0000000..66e924e --- /dev/null +++ b/volume_info.c @@ -0,0 +1,66 @@ +#include "volume_info.h" +#include "debug.h" +#include "general.h" + +unsigned MxfsGetFreeSpace(MINIX_FS *FileSys) +{ + unsigned UsedZones = MxfsCountBitsSet(&FileSys->ZoneMap); + unsigned TotalZones = FileSys->Super.s_zones; + unsigned FreeBlocks = (TotalZones - UsedZones) << FileSys->Super.s_log_zone_size; + return FreeBlocks * MINIX_BLOCK_SIZE; +} + +int DOKAN_CALLBACK MxfsGetDiskFreeSpace( + PULONGLONG FreeBytesAvailable, + PULONGLONG TotalNumberOfBytes, + PULONGLONG TotalNumberOfFreeBytes, + PDOKAN_FILE_INFO FileInfo) +{ + TRACE(""); + + MINIX_FS *FileSys = (MINIX_FS*)(ULONG_PTR)FileInfo->DokanOptions->GlobalContext; + ASSERT(FileSys); + + unsigned FreeBytes = 0; + if(FreeBytesAvailable || TotalNumberOfFreeBytes) + FreeBytes = MxfsGetFreeSpace(FileSys); + + if(FreeBytesAvailable) + *FreeBytesAvailable = FreeBytes; + if(TotalNumberOfBytes) + *TotalNumberOfBytes = FileSys->cbLen; + if(TotalNumberOfFreeBytes) + *TotalNumberOfFreeBytes = FreeBytes; + return 0; +} + +// see Win32 API GetVolumeInformation +int DOKAN_CALLBACK MxfsGetVolumeInformation( + LPWSTR VolumeNameBuffer, + DWORD VolumeNameSize, // in num of chars + LPDWORD VolumeSerialNumber, + LPDWORD MaximumComponentLength, // in num of chars + LPDWORD FileSystemFlags, + LPWSTR FileSystemNameBuffer, + DWORD FileSystemNameSize, // in num of chars (this is not true?) + PDOKAN_FILE_INFO FileInfo) +{ + TRACE(""); + + if(VolumeNameSize > 0 && VolumeNameBuffer) + wcscpy_s(VolumeNameBuffer, VolumeNameSize, L""); + + if(VolumeSerialNumber) + *VolumeSerialNumber = MINIX_VOLUME_ID; + + if(MaximumComponentLength) + *MaximumComponentLength = MAX_NAME_LEN; + + if(FileSystemFlags) + *FileSystemFlags = FILE_CASE_SENSITIVE_SEARCH; + + if(FileSystemNameSize > 0 && FileSystemNameBuffer) + wcscpy_s(FileSystemNameBuffer, FileSystemNameSize, L"Minix"); + + return 0; +} diff --git a/volume_info.h b/volume_info.h new file mode 100644 index 0000000..5fc2c77 --- /dev/null +++ b/volume_info.h @@ -0,0 +1,31 @@ +#ifndef VOLUME_INFO_H_INCLUDED +#define VOLUME_INFO_H_INCLUDED + +#include +#include "dokan.h" + +// Neither GetDiskFreeSpace nor GetVolumeInformation +// save the DokanFileContext->Context. +// Before these methods are called, CreateFile may not be called. +// (ditto CloseFile and Cleanup) + +// see Win32 API GetDiskFreeSpaceEx +int DOKAN_CALLBACK MxfsGetDiskFreeSpace( + PULONGLONG FreeBytesAvailable, + PULONGLONG TotalNumberOfBytes, + PULONGLONG TotalNumberOfFreeBytes, + PDOKAN_FILE_INFO FileInfo); + + +// see Win32 API GetVolumeInformation +int DOKAN_CALLBACK MxfsGetVolumeInformation( + LPWSTR VolumeNameBuffer, + DWORD VolumeNameSize, // in num of chars + LPDWORD VolumeSerialNumber, + LPDWORD MaximumComponentLength, // in num of chars + LPDWORD FileSystemFlags, + LPWSTR FileSystemNameBuffer, + DWORD FileSystemNameSize, // in num of chars + PDOKAN_FILE_INFO FileInfo); + +#endif // VOLUME_INFO_H_INCLUDED