VirtualSpace icon indicating copy to clipboard operation
VirtualSpace copied to clipboard

Independent Desktop Icons per Virtual Desktop

Open 2061360308 opened this issue 7 months ago • 3 comments

VirtualSpace Feature Request: Independent Desktop Icons per Virtual Desktop

Feature Request Description

I would like to request a new feature for VirtualSpace: the ability to have independent desktop icon sets for each virtual desktop in Windows 10/11. This would allow users to have completely different desktop layouts and icon collections on each virtual desktop, enhancing organization and productivity.

Use Case and Benefits

  • Context-specific workspaces: Different virtual desktops could be organized for specific tasks (e.g., Work, Gaming, Personal, Study)
  • Reduced clutter: Only relevant icons appear on each desktop
  • Improved organization: Better visual separation between different activity contexts
  • Enhanced productivity: Easier to focus on task-specific resources without visual distractions

Technical Implementation Idea

I've created a proof-of-concept Python project called "VirtualIconGuard" that demonstrates this functionality works by:

  1. Creating separate desktop folders for each virtual desktop
  2. Using Windows API (SHSetKnownFolderPath) to dynamically switch the system desktop location when virtual desktops change
  3. Preserving icon layouts using the SelectAndPositionItem method for each desktop
  4. Monitoring desktop switching events via pyvda library

Current Project Status

My Python prototype successfully demonstrates the concept, but I don't have C# experience to contribute directly to VirtualSpace. The prototype shows:

  • The feature is technically feasible
  • The user experience is smooth with only minor visual delays during desktop switching
  • The implementation is compatible with Windows 10/11 native virtual desktop system

Request

I believe this feature would be a valuable addition to VirtualSpace, making it even more powerful for users who rely on virtual desktops for productivity. As I cannot contribute code directly, I'm hoping the VirtualSpace development team might consider implementing this functionality.

I'd be happy to share more details about my Python implementation or assist with testing if this feature is considered for development.

2061360308 avatar Jun 02 '25 12:06 2061360308

en:Excellent work, this request has been marked and will be considered for inclusion in the future when I have time. cn:我已查看过 VirtualIconGuard 项目的效果,很棒。日后闲暇时考虑引入。

btw: 你这洋文写的真好👍,就是得看一阵子,下回直接中文,让外国友人自行翻译就好😁

newlooper avatar Jun 03 '25 09:06 newlooper

en:Excellent work, this request has been marked and will be considered for inclusion in the future when I have time. cn:我已查看过 VirtualIconGuard 项目的效果,很棒。日后闲暇时考虑引入。

btw: 你这洋文写的真好👍,就是得看一阵子,下回直接中文,让外国友人自行翻译就好😁

回复:上面issue是我让Copilot代写的,AI无处不在哈哈。😂


基于WinFSP的虚拟桌面独立图标管理功能建议

问题背景

在经过我多次测试后发现当前VirtualDesktop项目在桌面切换同时有时候会频繁发生图标更新不及时的问题,用户必须手动右键触发刷新(点击右键就行,或者移动某个顶层窗口就会触发刷新)。这严重影响了用户体验和功能可用性。对于这个情况,我昨天多次进行尝试希望改进现有方案(查找刷新Explorer的可靠方法、注册COM组件、进程注入等)后,我提出一个基于虚拟文件系统的全新解决方案,这一方案性能足够好,编程难度也不是很高。

技术方案

提议依赖WinFSP (Github) 实现虚拟桌面图标的独立管理,主要工作流程如下:

挂载虚拟文件夹:使用WinFSP创建一个虚拟文件系统,内部实际指向原桌面文件夹 重定向桌面路径:将系统桌面目录指向此虚拟文件夹 拦截文件操作:通过虚拟文件系统接管所有文件读写操作 智能过滤:根据当前虚拟桌面ID在列出目录等函数请求回调中过滤显示内容 程序关闭时恢复:停止程序时自动恢复原始桌面设置

技术优势

相比当前实现方案,这一方案具有以下优点:

  1. 实时响应:切换桌面后图标立即更新比之前流畅了很多,也无需担心手动刷新的问题
  2. 操作透明:用户不会感知到桌面文件夹的频繁切换过程,只需要启动时重新设置一次桌面路径,不像之前在文件管理器中能够看到桌面路径不断变化
  3. 稳定性高:避免了文件管理器刷新失败的问题,也无需频繁改动系统设置
  4. 兼容性好:对已打开的Explorer窗口无影响,方案无需针对Windows版本进行更新
  5. 性能优异:切换速度更快,WinFsp也无需占用太多系统资源
  6. 灵活配置:通过设计虚拟驱动,可轻松实现不同虚拟桌面使用同一/独立文件夹存储或是多个文件夹存储
  7. 恢复简单:软件关闭后恢复原始状态无需复杂逻辑,只需要重新设置系统桌面路径即可,文件在读写时本来就是被定向到原始桌面的,退出时无需考虑
  8. 避免大文件移动:将大文件在不同桌面上移动时只需要更改过滤规则,不需要在不同物理文件夹间复制移动文件

效果展示

我已在测试环境中验证了该方案的可行性,效果录屏如下:

测试代码通过简单实现了来回切换时屏蔽和展示Edge浏览器以及微信两个项目的功能,完整效果还应该加上恢复图标位置的功能。

桌面切换逻辑代码

num = 0

def on_switch_desktop(old_id, new_id):
    """
    切换虚拟桌面时的回调函数
    """
    global num, operations
    print(f"切换到虚拟桌面: {new_id}, 从: {old_id}")
    if operations:
        if num % 2 == 0:
            operations.set_blacklist([
                'Microsoft Edge.lnk',
                '微信.lnk'])
        else:
            operations.set_blacklist([])
    
    print(f"当前虚拟桌面切换次数: {num}", operations.blacklist)
    num += 1
    refresh_explorer_and_desktop()

https://github.com/user-attachments/assets/e40aa233-6369-4f1d-b2de-f675c3c2a46b

通过视频可以看出:

  1. 切换速度明显更快
  2. 稳定性显著提升
  3. 系统响应更加灵敏

有关代码在我整理之后会进行上传

2061360308 avatar Jun 04 '25 09:06 2061360308

代码惨不忍睹,对比之前改动主要就是多了一个能够映射其他真实路径的虚拟文件夹驱动,我就直接放在下面了。

主要调用过程因为不需要考虑复制、恢复桌面文件夹内容之类反而清爽了不少。

最后我还放了一个C++的源文件,之前本来其实是准备写一个动态链接库封装虚拟驱动的功能的,奈何实力有限失败了,见谅哈哈。

这个代码能够正常挂载,但是挂载后的目录在打开进行访问的时候会报错一个“函数错误无法访问”的错误,解决了半天没成功后面直接用python测试了。

main.py

import os, json, shutil, subprocess
from time import time, sleep
from win32com.shell import shell, shellcon
# from app.main import get_current_path
from module.desktop import Desktop
from module.monitor import VirtualDesktopMonitor
from module.trayIcon import SystemTrayManager
from module.desktopData import saveDesktopData, loadDesktopData
from module.redirectDesktop import redirect_desktop, refresh_explorer_and_desktop
from pyvda import AppView, get_apps_by_z_order, VirtualDesktop, get_virtual_desktops
from module.mount import create_mapped_file_system

# 获取当前文件路径
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))

# 用户数据文件夹
USER_DATA = os.path.join(ROOT_PATH, "data")

if not os.path.exists(USER_DATA):
    os.makedirs(USER_DATA, exist_ok=True)


monitor = VirtualDesktopMonitor()

def get_current_path():
    """
    获取当前用户的桌面路径
    """

    desktop_path = shell.SHGetFolderPath(0, shellcon.CSIDL_DESKTOP, None, 0)
    return os.path.abspath(desktop_path)

# 主桌面备份ID(原始系统桌面文件夹【Desktop】)
MAIN_DESKTOP_BACK_ID = "MAIN_DESKTOP_BACK"
MAIN_DESKTOP = get_current_path()
DESKTOP_PARENT = os.path.dirname(MAIN_DESKTOP)

operations = None

num = 0

def on_switch_desktop(old_id, new_id):
    """
    切换虚拟桌面时的回调函数
    """
    global num, operations
    
    print(f"切换到虚拟桌面: {new_id}, 从: {old_id}")
    if operations:
        if num % 2 == 0:
            operations.set_blacklist([
                'Microsoft Edge.lnk',
                '微信.lnk'])
        else:
            operations.set_blacklist([])
    
    print(f"当前虚拟桌面切换次数: {num}", operations.blacklist)
    num += 1
    refresh_explorer_and_desktop()

# 注册回调函数
monitor.register_callback("desktop_switch", on_switch_desktop)
    
# 启动监听线程
print("正在监听虚拟桌面事件...")
monitor.start_monitoring()

icon_path = os.path.join(ROOT_PATH, "static", "tray_logo.jpg")  # 替换为你的图标文件路径

tray_manager = SystemTrayManager(icon_path=icon_path, title="示例应用")
    
# 定义一些菜单项回调
def show_message(icon, item):
    tray_manager.show_notification("这是一条测试消息", "通知测试")
    
def change_icon(icon, item):
    print(icon, item)
    # 假设有两个图标文件
    print("更改图标")

virtualDesktop = os.path.join(DESKTOP_PARENT, "V")

print(f"虚拟桌面路径: {virtualDesktop}")
print(f"主桌面路径: {MAIN_DESKTOP}")

fs, operations = create_mapped_file_system(virtualDesktop, MAIN_DESKTOP)

fs.start()

redirect_desktop(virtualDesktop)
refresh_explorer_and_desktop()
    
# 添加菜单项
tray_manager.add_menu_item("显示通知", show_message)
tray_manager.add_menu_item("更改图标", change_icon)
    
# 启动托盘图标(非阻塞模式)
tray_manager.start(block=True)

redirect_desktop(MAIN_DESKTOP)
refresh_explorer_and_desktop()

fs.stop()

mount.py

import sys, time
import logging
import argparse
import threading
import os
import shutil
import stat
from datetime import datetime
from functools import wraps
from pathlib import Path, PureWindowsPath

from winfspy import (
    FileSystem,
    BaseFileSystemOperations,
    enable_debug_log,
    FILE_ATTRIBUTE,
    CREATE_FILE_CREATE_OPTIONS,
    NTStatusObjectNameNotFound,
    NTStatusDirectoryNotEmpty,
    NTStatusNotADirectory,
    NTStatusObjectNameCollision,
    NTStatusAccessDenied,
    NTStatusEndOfFile,
    NTStatusMediaWriteProtected,
)
from winfspy.plumbing.win32_filetime import filetime_now, filetime_to_dt, dt_to_filetime
from winfspy.plumbing.security_descriptor import SecurityDescriptor


def operation(fn):
    """Decorator for file system operations.

    Provides both logging and thread-safety
    """
    name = fn.__name__

    @wraps(fn)
    def wrapper(self, *args, **kwargs):
        head = args[0] if args else None
        tail = args[1:] if args else ()
        try:
            with self._thread_lock:
                result = fn(self, *args, **kwargs)
        except Exception as exc:
            # logging.info(f" NOK | {name:20} | {head!r:20} | {tail!r:20} | {exc!r}")
            raise
        else:
            # logging.info(f" OK! | {name:20} | {head!r:20} | {tail!r:20} | {result!r}")
            return result

    return wrapper


class BaseFileObj:
    @property
    def name(self):
        """File name, without the path"""
        return self.path.name

    @property
    def file_name(self):
        """File name, including the path"""
        return str(self.path)

    def __init__(self, path, attributes, security_descriptor):
        self.path = path
        self.attributes = attributes
        self.security_descriptor = security_descriptor
        now = filetime_now()
        self.creation_time = now
        self.last_access_time = now
        self.last_write_time = now
        self.change_time = now
        self.index_number = 0
        self.file_size = 0

    def get_file_info(self):
        return {
            "file_attributes": self.attributes,
            "allocation_size": self.allocation_size,
            "file_size": self.file_size,
            "creation_time": self.creation_time,
            "last_access_time": self.last_access_time,
            "last_write_time": self.last_write_time,
            "change_time": self.change_time,
            "index_number": self.index_number,
        }

    def __repr__(self):
        return f"{type(self).__name__}:{self.file_name}"


class FileObj(BaseFileObj):

    allocation_unit = 4096

    def __init__(self, path, attributes, security_descriptor, allocation_size=0):
        super().__init__(path, attributes, security_descriptor)
        self.data = bytearray(allocation_size)
        self.attributes |= FILE_ATTRIBUTE.FILE_ATTRIBUTE_ARCHIVE
        assert not self.attributes & FILE_ATTRIBUTE.FILE_ATTRIBUTE_DIRECTORY

    @property
    def allocation_size(self):
        return len(self.data)

    def set_allocation_size(self, allocation_size):
        if allocation_size < self.allocation_size:
            self.data = self.data[:allocation_size]
        if allocation_size > self.allocation_size:
            self.data += bytearray(allocation_size - self.allocation_size)
        assert self.allocation_size == allocation_size
        self.file_size = min(self.file_size, allocation_size)

    def adapt_allocation_size(self, file_size):
        units = (file_size + self.allocation_unit - 1) // self.allocation_unit
        self.set_allocation_size(units * self.allocation_unit)

    def set_file_size(self, file_size):
        if file_size < self.file_size:
            zeros = bytearray(self.file_size - file_size)
            self.data[file_size : self.file_size] = zeros
        if file_size > self.allocation_size:
            self.adapt_allocation_size(file_size)
        self.file_size = file_size

    def read(self, offset, length):
        if offset >= self.file_size:
            raise NTStatusEndOfFile()
        end_offset = min(self.file_size, offset + length)
        return self.data[offset:end_offset]

    def write(self, buffer, offset, write_to_end_of_file):
        if write_to_end_of_file:
            offset = self.file_size
        end_offset = offset + len(buffer)
        if end_offset > self.file_size:
            self.set_file_size(end_offset)
        self.data[offset:end_offset] = buffer
        return len(buffer)

    def constrained_write(self, buffer, offset):
        if offset >= self.file_size:
            return 0
        end_offset = min(self.file_size, offset + len(buffer))
        transferred_length = end_offset - offset
        self.data[offset:end_offset] = buffer[:transferred_length]
        return transferred_length


class FolderObj(BaseFileObj):
    def __init__(self, path, attributes, security_descriptor):
        super().__init__(path, attributes, security_descriptor)
        self.allocation_size = 0
        assert self.attributes & FILE_ATTRIBUTE.FILE_ATTRIBUTE_DIRECTORY


class OpenedObj:
    def __init__(self, file_obj, real_path=None):
        self.file_obj = file_obj
        self.real_path = real_path
        self.handle = None

    def __repr__(self):
        return f"{type(self).__name__}:{self.file_obj.file_name}"


class MappedFileSystemOperations(BaseFileSystemOperations):
    
    blacklist = [
        'Microsoft Edge.lnk',
        '微信.lnk',
    ]
    
    def __init__(self, volume_label, source_dir, read_only=False):
        super().__init__()
        if len(volume_label) > 31:
            raise ValueError("`volume_label` must be 31 characters long max")

        # 检查源目录
        self.source_dir = Path(source_dir).resolve()
        if not self.source_dir.exists():
            raise ValueError(f"目录 '{source_dir}' 不存在")
        if not self.source_dir.is_dir():
            raise ValueError(f"'{source_dir}' 不是目录")

        # 其他属性
        self.read_only = read_only
        self._volume_info = self._get_volume_info(volume_label)
        
        # 根目录初始化
        self._root_path = PureWindowsPath("/")
        self._root_obj = self._create_folder_obj(self._root_path)
        
        self._entries = {self._root_path: self._root_obj}
        self._thread_lock = threading.Lock()
        
        # 加载目录结构
        self._load_directory_structure(self._root_path)
        
    def set_blacklist(self, blacklist):
        """设置黑名单"""
        self.blacklist = blacklist

    def _get_volume_info(self, volume_label):
        """获取卷信息"""
        # 获取磁盘使用情况
        usage = shutil.disk_usage(self.source_dir)
        return {
            "total_size": usage.total,
            "free_size": usage.free,
            "volume_label": volume_label,
        }
    
    def _map_virtual_to_real_path(self, virtual_path):
        """将虚拟路径映射到真实路径"""
        if isinstance(virtual_path, str):
            virtual_path = PureWindowsPath(virtual_path)
        
        # 删除路径中Windows路径分隔符开头的部分
        rel_path = str(virtual_path).lstrip('\\/')
        
        # 返回映射后的路径
        return self.source_dir / rel_path

    def _get_file_attributes(self, path):
        """获取文件属性"""
        attrs = 0
        if path.is_dir():
            attrs |= FILE_ATTRIBUTE.FILE_ATTRIBUTE_DIRECTORY
        if path.name.startswith('.'):
            attrs |= FILE_ATTRIBUTE.FILE_ATTRIBUTE_HIDDEN
        if path.stat().st_mode & (not stat.S_IWUSR):
            attrs |= FILE_ATTRIBUTE.FILE_ATTRIBUTE_READONLY
        if not path.is_dir():
            attrs |= FILE_ATTRIBUTE.FILE_ATTRIBUTE_ARCHIVE
        
        return attrs

    def _create_folder_obj(self, virtual_path):
        """创建文件夹对象"""
        real_path = self._map_virtual_to_real_path(virtual_path)
        try:
            stat_info = real_path.stat()
            attrs = FILE_ATTRIBUTE.FILE_ATTRIBUTE_DIRECTORY
            if not os.access(real_path, os.W_OK):
                attrs |= FILE_ATTRIBUTE.FILE_ATTRIBUTE_READONLY
                
            security_descriptor = SecurityDescriptor.from_string(
                "O:BAG:BAD:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;WD)"
            )
            
            folder = FolderObj(virtual_path, attrs, security_descriptor)
            
            # 设置时间
            folder.creation_time = dt_to_filetime(datetime.fromtimestamp(stat_info.st_ctime))
            folder.last_access_time = dt_to_filetime(datetime.fromtimestamp(stat_info.st_atime))
            folder.last_write_time = dt_to_filetime(datetime.fromtimestamp(stat_info.st_mtime))
            folder.change_time = folder.last_write_time
            
            return folder
            
        except FileNotFoundError:
            raise NTStatusObjectNameNotFound()
    
    def _create_file_obj(self, virtual_path):
        """创建文件对象"""
        real_path = self._map_virtual_to_real_path(virtual_path)
        try:
            stat_info = real_path.stat()
            attrs = 0
            if real_path.name.startswith('.'):
                attrs |= FILE_ATTRIBUTE.FILE_ATTRIBUTE_HIDDEN
            if not os.access(real_path, os.W_OK):
                attrs |= FILE_ATTRIBUTE.FILE_ATTRIBUTE_READONLY
            attrs |= FILE_ATTRIBUTE.FILE_ATTRIBUTE_ARCHIVE
                
            security_descriptor = SecurityDescriptor.from_string(
                "O:BAG:BAD:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;WD)"
            )
            
            file_obj = FileObj(virtual_path, attrs, security_descriptor)
            
            # 设置大小
            file_obj.file_size = stat_info.st_size
            file_obj.adapt_allocation_size(file_obj.file_size)
            
            # 设置时间
            file_obj.creation_time = dt_to_filetime(datetime.fromtimestamp(stat_info.st_ctime))
            file_obj.last_access_time = dt_to_filetime(datetime.fromtimestamp(stat_info.st_atime))
            file_obj.last_write_time = dt_to_filetime(datetime.fromtimestamp(stat_info.st_mtime))
            file_obj.change_time = file_obj.last_write_time
            
            return file_obj
            
        except FileNotFoundError:
            raise NTStatusObjectNameNotFound()

    def _load_directory_structure(self, virtual_path):
        """加载目录结构"""
        real_path = self._map_virtual_to_real_path(virtual_path)
        
        if not real_path.exists() or not real_path.is_dir():
            return
        
        # 遍历目录内容并创建对应的对象
        for item in real_path.iterdir():
            vpath = PureWindowsPath(virtual_path) / item.name
            
            if vpath not in self._entries:
                if item.is_dir():
                    self._entries[vpath] = self._create_folder_obj(vpath)
                else:
                    self._entries[vpath] = self._create_file_obj(vpath)

    # Winfsp operations

    @operation
    def get_volume_info(self):
        # 返回卷信息
        return self._get_volume_info(self._volume_info["volume_label"])

    @operation
    def set_volume_label(self, volume_label):
        if self.read_only:
            raise NTStatusMediaWriteProtected()
        self._volume_info["volume_label"] = volume_label

    @operation
    def get_security_by_name(self, file_name):
        file_name = PureWindowsPath(file_name)
        real_path = self._map_virtual_to_real_path(file_name)

        # 文件不存在
        if not real_path.exists():
            raise NTStatusObjectNameNotFound()

        # 如果对象不在缓存中则创建
        if file_name not in self._entries:
            if real_path.is_dir():
                file_obj = self._create_folder_obj(file_name)
            else:
                file_obj = self._create_file_obj(file_name)
            self._entries[file_name] = file_obj
        else:
            file_obj = self._entries[file_name]

        return (
            file_obj.attributes,
            file_obj.security_descriptor.handle,
            file_obj.security_descriptor.size,
        )

    @operation
    def create(
        self,
        file_name,
        create_options,
        granted_access,
        file_attributes,
        security_descriptor,
        allocation_size,
    ):
        if self.read_only:
            raise NTStatusMediaWriteProtected()

        file_name = PureWindowsPath(file_name)
        real_path = self._map_virtual_to_real_path(file_name)
        
        # 检查父目录
        parent_real_path = real_path.parent
        if not parent_real_path.exists():
            raise NTStatusObjectNameNotFound()
        
        # 父路径必须是目录
        if not parent_real_path.is_dir():
            raise NTStatusNotADirectory()

        # 目标已存在
        if real_path.exists():
            raise NTStatusObjectNameCollision()

        # 创建文件或目录
        is_directory = create_options & CREATE_FILE_CREATE_OPTIONS.FILE_DIRECTORY_FILE
        
        try:
            if is_directory:
                real_path.mkdir()
                file_obj = self._create_folder_obj(file_name)
                self._entries[file_name] = file_obj
                return OpenedObj(file_obj, real_path)
            else:
                # 创建文件
                real_path.touch()
                file_obj = self._create_file_obj(file_name)
                self._entries[file_name] = file_obj
                return OpenedObj(file_obj, real_path)
        except Exception as e:
            logging.error(f"创建失败: {e}")
            raise NTStatusAccessDenied()

    @operation
    def get_security(self, file_context):
        return file_context.file_obj.security_descriptor

    @operation
    def set_security(self, file_context, security_information, modification_descriptor):
        if self.read_only:
            raise NTStatusMediaWriteProtected()

        new_descriptor = file_context.file_obj.security_descriptor.evolve(
            security_information, modification_descriptor
        )
        file_context.file_obj.security_descriptor = new_descriptor
        
        # 这里可以实现实际的权限修改,使用os.chmod等
        # 但Windows权限和POSIX权限有很大不同

    @operation
    def rename(self, file_context, file_name, new_file_name, replace_if_exists):
        if self.read_only:
            raise NTStatusMediaWriteProtected()

        file_name = PureWindowsPath(file_name)
        new_file_name = PureWindowsPath(new_file_name)
        
        real_path = self._map_virtual_to_real_path(file_name)
        new_real_path = self._map_virtual_to_real_path(new_file_name)

        # 源文件必须存在
        if not real_path.exists():
            raise NTStatusObjectNameNotFound()
        
        # 处理目标已存在
        if new_real_path.exists():
            if not replace_if_exists:
                raise NTStatusObjectNameCollision()
            elif real_path.is_dir():
                raise NTStatusAccessDenied()
            else:
                # 删除目标文件
                try:
                    new_real_path.unlink()
                except:
                    raise NTStatusAccessDenied()
        
        # 执行移动
        try:
            shutil.move(str(real_path), str(new_real_path))
            
            # 更新缓存
            if file_name in self._entries:
                file_obj = self._entries.pop(file_name)
                file_obj.path = new_file_name
                self._entries[new_file_name] = file_obj
            
            # 如果是目录,还需要更新子项
            if real_path.is_dir():
                # 更新所有子路径
                for entry_path in list(self._entries):
                    try:
                        if str(entry_path).startswith(str(file_name)):
                            relative = entry_path.relative_to(file_name)
                            new_entry_path = new_file_name / relative
                            entry = self._entries.pop(entry_path)
                            entry.path = new_entry_path
                            self._entries[new_entry_path] = entry
                    except ValueError:
                        continue
                    
        except Exception as e:
            logging.error(f"重命名失败: {e}")
            raise NTStatusAccessDenied()

    @operation
    def open(self, file_name, create_options, granted_access):
        file_name = PureWindowsPath(file_name)
        real_path = self._map_virtual_to_real_path(file_name)

        # 文件必须存在
        if not real_path.exists():
            raise NTStatusObjectNameNotFound()

        # 如果不在缓存中则创建
        if file_name not in self._entries:
            if real_path.is_dir():
                file_obj = self._create_folder_obj(file_name)
            else:
                file_obj = self._create_file_obj(file_name)
            self._entries[file_name] = file_obj
        else:
            file_obj = self._entries[file_name]

        opened_obj = OpenedObj(file_obj, real_path)
        
        # 处理文件句柄打开
        if not real_path.is_dir():
            mode = "rb+"
            if self.read_only:
                mode = "rb"
            try:
                opened_obj.handle = open(real_path, mode)
            except:
                # 尝试以只读方式打开
                try:
                    opened_obj.handle = open(real_path, "rb")
                except:
                    raise NTStatusAccessDenied()
                    
        return opened_obj

    @operation
    def close(self, file_context):
        # 关闭句柄
        if file_context.handle:
            try:
                file_context.handle.close()
            except:
                pass
            file_context.handle = None

    @operation
    def get_file_info(self, file_context):
        # 更新信息
        if file_context.real_path and file_context.real_path.exists():
            real_path = file_context.real_path
            stat_info = real_path.stat()
            
            # 更新时间
            file_context.file_obj.creation_time = dt_to_filetime(datetime.fromtimestamp(stat_info.st_ctime))
            file_context.file_obj.last_access_time = dt_to_filetime(datetime.fromtimestamp(stat_info.st_atime))
            file_context.file_obj.last_write_time = dt_to_filetime(datetime.fromtimestamp(stat_info.st_mtime))
            file_context.file_obj.change_time = file_context.file_obj.last_write_time
            
            # 更新大小
            if not file_context.real_path.is_dir():
                file_context.file_obj.file_size = stat_info.st_size
        
        return file_context.file_obj.get_file_info()

    @operation
    def set_basic_info(
        self,
        file_context,
        file_attributes,
        creation_time,
        last_access_time,
        last_write_time,
        change_time,
        file_info,
    ) -> dict:
        if self.read_only:
            raise NTStatusMediaWriteProtected()

        real_path = file_context.real_path
        file_obj = file_context.file_obj
        
        # 设置属性
        if file_attributes != FILE_ATTRIBUTE.INVALID_FILE_ATTRIBUTES:
            file_obj.attributes = file_attributes
            
            # 设置只读属性
            if file_attributes & FILE_ATTRIBUTE.FILE_ATTRIBUTE_READONLY:
                os.chmod(real_path, stat.S_IREAD)
            else:
                os.chmod(real_path, stat.S_IREAD | stat.S_IWRITE)
        
        # 设置时间
        atime = None
        mtime = None
        
        if last_access_time:
            file_obj.last_access_time = last_access_time
            atime = filetime_to_dt(last_access_time).timestamp()
        
        if last_write_time:
            file_obj.last_write_time = last_write_time
            mtime = filetime_to_dt(last_write_time).timestamp()
        
        if creation_time:
            file_obj.creation_time = creation_time
        
        if change_time:
            file_obj.change_time = change_time
            
        # 更新文件系统时间
        if atime is not None or mtime is not None:
            os.utime(real_path, (
                atime if atime is not None else os.path.getatime(real_path),
                mtime if mtime is not None else os.path.getmtime(real_path)
            ))

        return file_obj.get_file_info()

    @operation
    def set_file_size(self, file_context, new_size, set_allocation_size):
        if self.read_only:
            raise NTStatusMediaWriteProtected()
        
        if not file_context.handle:
            raise NTStatusAccessDenied()
            
        try:
            # 修改文件大小
            os.truncate(file_context.real_path, new_size)
            
            # 更新对象
            if set_allocation_size:
                file_context.file_obj.set_allocation_size(new_size)
            else:
                file_context.file_obj.set_file_size(new_size)
        except:
            raise NTStatusAccessDenied()

    @operation
    def can_delete(self, file_context, file_name: str) -> None:
        file_name = PureWindowsPath(file_name)
        real_path = self._map_virtual_to_real_path(file_name)

        # 文件必须存在
        if not real_path.exists():
            raise NTStatusObjectNameNotFound()
        
        # 检查写入权限
        if not os.access(real_path.parent, os.W_OK):
            raise NTStatusAccessDenied()
        
        # 检查目录
        if real_path.is_dir():
            if any(real_path.iterdir()):
                raise NTStatusDirectoryNotEmpty()

    @operation
    def read_directory(self, file_context, marker):
        print("---** 读取文件夹 Reading directory: ************", time.time())
        real_path = file_context.real_path
        file_obj = file_context.file_obj
        
        # 必须是目录
        if not real_path.is_dir():
            raise NTStatusNotADirectory()
        
        # 加载目录
        virtual_path = file_obj.path
        self._load_directory_structure(virtual_path)
            
        entries = []
        
        # 添加"."和".."
        if file_obj.path != self._root_path:
            parent_path = file_obj.path.parent
            if parent_path not in self._entries:
                parent_obj = self._create_folder_obj(parent_path)
                self._entries[parent_path] = parent_obj
            else:
                parent_obj = self._entries[parent_path]
            
            entries.append({"file_name": ".", **file_obj.get_file_info()})
            entries.append({"file_name": "..", **parent_obj.get_file_info()})
        
        # 添加文件
        for item in real_path.iterdir():
            child_virtual_path = virtual_path / item.name
            
            if child_virtual_path not in self._entries:
                if item.is_dir():
                    child_obj = self._create_folder_obj(child_virtual_path)
                else:
                    child_obj = self._create_file_obj(child_virtual_path)
                self._entries[child_virtual_path] = child_obj
            else:
                child_obj = self._entries[child_virtual_path]
            
            if item.name not in self.blacklist:
                entries.append({"file_name": item.name, **child_obj.get_file_info()})
        
        # 排序
        entries = sorted(entries, key=lambda x: x["file_name"])
        
        print(f"---** 读取文件夹完成 Read directory complete: {len(entries)} entries")
        # print(f"---** entries: {entries}")
        
        # 分页
        if marker is None:
            return entries

        for i, entry in enumerate(entries):
            if entry["file_name"] == marker:
                return entries[i + 1:]

    @operation
    def get_dir_info_by_name(self, file_context, file_name):
        parent_path = file_context.file_obj.path
        child_path = parent_path / file_name
        real_child_path = self._map_virtual_to_real_path(child_path)
        
        # 文件必须存在
        if not real_child_path.exists():
            raise NTStatusObjectNameNotFound()
        
        # 如果不在缓存中则创建
        if child_path not in self._entries:
            if real_child_path.is_dir():
                child_obj = self._create_folder_obj(child_path)
            else:
                child_obj = self._create_file_obj(child_path)
            self._entries[child_path] = child_obj
        else:
            child_obj = self._entries[child_path]
            
        return {"file_name": file_name, **child_obj.get_file_info()}

    @operation
    def read(self, file_context, offset, length):
        if not file_context.handle:
            real_path = file_context.real_path
            if not real_path.exists():
                raise NTStatusObjectNameNotFound()
                
            if not real_path.is_file():
                raise NTStatusNotADirectory()
                
            # 打开文件
            try:
                file_context.handle = open(real_path, "rb")
            except:
                raise NTStatusAccessDenied()
        
        # 读取
        try:
            file_context.handle.seek(offset)
            data = file_context.handle.read(length)
            
            if not data and offset >= file_context.file_obj.file_size:
                raise NTStatusEndOfFile()
                
            return data
        except Exception as e:
            if offset >= file_context.file_obj.file_size:
                raise NTStatusEndOfFile()
            raise NTStatusAccessDenied()

    @operation
    def write(self, file_context, buffer, offset, write_to_end_of_file, constrained_io):
        if self.read_only:
            raise NTStatusMediaWriteProtected()
        
        if not file_context.handle:
            real_path = file_context.real_path
            if not real_path.exists():
                raise NTStatusObjectNameNotFound()
                
            if not real_path.is_file():
                raise NTStatusNotADirectory()
                
            # 打开文件
            try:
                file_context.handle = open(real_path, "rb+")
            except:
                raise NTStatusAccessDenied()
                
        # 写入
        try:
            if write_to_end_of_file:
                file_context.handle.seek(0, os.SEEK_END)
                offset = file_context.handle.tell()
            else:
                file_context.handle.seek(offset)
                
            if constrained_io and offset >= file_context.file_obj.file_size:
                return 0
                
            bytes_written = file_context.handle.write(buffer)
            file_context.handle.flush()
            
            # 更新大小
            file_size = max(offset + bytes_written, file_context.file_obj.file_size)
            file_context.file_obj.file_size = file_size
            
            # 更新时间
            file_context.file_obj.last_write_time = filetime_now()
            file_context.file_obj.change_time = file_context.file_obj.last_write_time
            
            return bytes_written
        except:
            raise NTStatusAccessDenied()

    @operation
    def cleanup(self, file_context, file_name, flags) -> None:
        if self.read_only:
            raise NTStatusMediaWriteProtected()
            
        FspCleanupDelete = 0x01
        FspCleanupSetAllocationSize = 0x02
        FspCleanupSetArchiveBit = 0x10
        FspCleanupSetLastAccessTime = 0x20
        FspCleanupSetLastWriteTime = 0x40
        FspCleanupSetChangeTime = 0x80
        
        # 关闭句柄
        if file_context.handle:
            file_context.handle.close()
            file_context.handle = None

        # 处理删除标志
        if flags & FspCleanupDelete:
            real_path = file_context.real_path
            file_obj = file_context.file_obj
            
            try:
                if real_path.is_dir():
                    # 检查是否为空
                    if any(real_path.iterdir()):
                        return
                    real_path.rmdir()
                else:
                    real_path.unlink()
                    
                # 从缓存中移除
                if file_obj.path in self._entries:
                    del self._entries[file_obj.path]
            except:
                pass

        # 更新时间
        now = filetime_now()
        real_path = file_context.real_path
        file_obj = file_context.file_obj
        
        atime = None
        mtime = None
        
        if flags & FspCleanupSetLastAccessTime:
            file_obj.last_access_time = now
            atime = datetime.now().timestamp()
            
        if flags & FspCleanupSetLastWriteTime:
            file_obj.last_write_time = now
            mtime = datetime.now().timestamp()
            
        if flags & FspCleanupSetChangeTime:
            file_obj.change_time = now
            
        # 更新文件系统时间
        if atime is not None or mtime is not None:
            try:
                os.utime(real_path, (
                    atime if atime is not None else os.path.getatime(real_path),
                    mtime if mtime is not None else os.path.getmtime(real_path)
                ))
            except:
                pass

    @operation
    def overwrite(
        self, file_context, file_attributes, replace_file_attributes: bool, allocation_size: int
    ) -> None:
        if self.read_only:
            raise NTStatusMediaWriteProtected()
            
        real_path = file_context.real_path
        file_obj = file_context.file_obj
        
        # 清空文件
        try:
            with open(real_path, "wb") as f:
                pass
        except:
            raise NTStatusAccessDenied()
            
        # 设置属性
        file_attributes |= FILE_ATTRIBUTE.FILE_ATTRIBUTE_ARCHIVE
        if replace_file_attributes:
            file_obj.attributes = file_attributes
        else:
            file_obj.attributes |= file_attributes
            
        # 更新大小
        file_obj.file_size = 0
        
        # 更新时间
        now = filetime_now()
        file_obj.last_access_time = now
        file_obj.last_write_time = now
        file_obj.change_time = now
        
        # 更新文件系统时间
        try:
            current_time = datetime.now().timestamp()
            os.utime(real_path, (current_time, current_time))
        except:
            pass

    @operation
    def flush(self, file_context) -> None:
        # 刷新文件缓冲区到磁盘
        if file_context and file_context.handle:
            try:
                file_context.handle.flush()
                os.fsync(file_context.handle.fileno())
            except:
                pass


def create_mapped_file_system(
    mountpoint, source_dir, label="mapfs", prefix="", verbose=True, debug=False, testing=False, read_only=False
):
    """创建一个映射到物理目录的虚拟文件系统"""
    if debug:
        enable_debug_log()

    if verbose:
        logging.basicConfig(stream=sys.stdout, level=logging.INFO)

    mountpoint = Path(mountpoint)
    is_drive = mountpoint.parent == mountpoint
    reject_irp_prior_to_transact0 = not is_drive and not testing

    operations = MappedFileSystemOperations(label, source_dir, read_only)
    fs = FileSystem(
        str(mountpoint),
        operations,
        sector_size=512,
        sectors_per_allocation_unit=1,
        volume_creation_time=filetime_now(),
        volume_serial_number=0,
        file_info_timeout=1000,
        case_sensitive_search=1,
        case_preserved_names=1,
        unicode_on_disk=1,
        persistent_acls=1,
        post_cleanup_when_modified_only=1,
        um_file_context_is_user_context2=1,
        file_system_name=str(mountpoint),
        prefix=prefix,
        debug=debug,
        reject_irp_prior_to_transact0=reject_irp_prior_to_transact0,
    )
    return fs, operations


def main(mountpoint, source_dir, label, prefix, verbose, debug, read_only):
    fs = create_mapped_file_system(mountpoint, source_dir, label, prefix, verbose, debug, read_only=read_only)
    try:
        print(f"映射物理目录为虚拟盘: {source_dir}")
        fs.start()
        print(f"虚拟盘已挂载到路径: {mountpoint}")
        print("输入 'q' 退出,'r' 切换为只读,'w' 切换为可写")
        
        while True:
            command = input("> ").lower()
            if command == 'q':
                break
            elif command == 'r':
                fs.operations.read_only = True
                fs.restart(read_only_volume=True)
                print("已切换为只读模式")
            elif command == 'w':
                fs.operations.read_only = False
                fs.restart(read_only_volume=False)
                print("已切换为可写模式")
            else:
                print("无效命令")
                
    finally:
        print("正在卸载虚拟盘...")
        fs.stop()
        print("虚拟盘已卸载")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="将物理文件夹映射为虚拟磁盘")
    parser.add_argument("mountpoint", help="挂载点,如 X: 或空文件夹路径")
    parser.add_argument("source", help="要映射的源目录")
    parser.add_argument("-v", "--verbose", action="store_true", help="显示详细日志")
    parser.add_argument("-d", "--debug", action="store_true", help="启用调试模式")
    parser.add_argument("-l", "--label", type=str, default="MapFS", help="卷标名")
    parser.add_argument("-p", "--prefix", type=str, default="", help="前缀")
    parser.add_argument("-r", "--readonly", action="store_true", help="以只读模式挂载")
    
    args = parser.parse_args()
    main(args.mountpoint, args.source, args.label, args.prefix, args.verbose, args.debug, args.readonly)
/**
 * @file dirfs.cpp
 *
 * 目录映射虚拟文件系统
 * 基于WinFSP实现的将物理目录映射为虚拟驱动器的文件系统
 */

#include <winfsp/winfsp.h>
#include <shlwapi.h>
#include <pathcch.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <limits.h>
#include <map>
#include <string>
#include <iostream>

#pragma comment(lib, "winfsp-x64.lib")
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "pathcch.lib")

#define PROGNAME                        "DirFS"
#define ALLOCATION_UNIT                 4096
#define FULLPATH_SIZE                   (MAX_PATH + MAX_PATH)

#define info(format, ...)               FspServiceLog(EVENTLOG_INFORMATION_TYPE, format, __VA_ARGS__)
#define warn(format, ...)               FspServiceLog(EVENTLOG_WARNING_TYPE, format, __VA_ARGS__)
#define fail(format, ...)               FspServiceLog(EVENTLOG_ERROR_TYPE, format, __VA_ARGS__)

 // 文件系统结构体
typedef struct _DIRFS
{
    FSP_FILE_SYSTEM* FileSystem;
    PWSTR RootDirectory;      // 映射的物理目录路径
    HANDLE RootDirectoryHandle;
    BOOLEAN CaseInsensitive;
    ULONG MaxFileSize;
    ULONG FileInfoTimeout;
} DIRFS;

// 文件上下文
typedef struct _DIRFS_FILE_CONTEXT
{
    HANDLE Handle;            // 物理文件句柄
    PWSTR FileName;           // 文件名
    ULONG FileAttributes;     // 文件属性
    BOOLEAN IsDirectory;      // 是否为目录
    BOOLEAN DeleteOnClose;    // 关闭时是否删除
} DIRFS_FILE_CONTEXT;

static inline
VOID DirFsFileContextDelete(DIRFS_FILE_CONTEXT* FileContext)
{
    if (INVALID_HANDLE_VALUE != FileContext->Handle)
        CloseHandle(FileContext->Handle);

    free(FileContext->FileName);
    free(FileContext);
}

// 构建完整的物理路径
static NTSTATUS DirFsBuildPath(DIRFS* DirFs, PWSTR FileName,
    PWSTR PathBuf, SIZE_T PathBufSize)
{
    SIZE_T Length;

    // 确保不是根目录访问
    if (L'\0' == FileName[0])
        FileName = const_cast<PWSTR>(L"\\");

    Length = (wcslen(DirFs->RootDirectory) + wcslen(FileName) + 1) * sizeof(WCHAR);
    if (Length > PathBufSize)
        return STATUS_OBJECT_NAME_INVALID;

    wcscpy_s(PathBuf, PathBufSize / sizeof(WCHAR), DirFs->RootDirectory);
    if (L'\\' != FileName[0])
        wcscat_s(PathBuf, PathBufSize / sizeof(WCHAR), L"\\");
    wcscat_s(PathBuf, PathBufSize / sizeof(WCHAR), FileName);

    // 替换相对路径符号
    PWSTR Part, P;
    for (P = PathBuf; *P; P++)
        if (L'/' == *P)
            *P = L'\\';

    // 规格化路径
    HRESULT hr = PathCchCanonicalizeEx(PathBuf, PathBufSize / sizeof(WCHAR),
        PathBuf, PATHCCH_ALLOW_LONG_PATHS);
    if (FAILED(hr))
        return STATUS_OBJECT_NAME_INVALID;

    // 确保路径仍在映射目录内
    if (0 != wcsncmp(DirFs->RootDirectory, PathBuf, wcslen(DirFs->RootDirectory)) ||
        (PathBuf[wcslen(DirFs->RootDirectory)] != L'\0' && PathBuf[wcslen(DirFs->RootDirectory)] != L'\\'))
        return STATUS_ACCESS_DENIED;

    return STATUS_SUCCESS;
}

// 获取卷信息
static NTSTATUS GetVolumeInfo(FSP_FILE_SYSTEM* FileSystem,
    FSP_FSCTL_VOLUME_INFO* VolumeInfo)
{
    DIRFS* DirFs = (DIRFS*)FileSystem->UserContext;
    WCHAR VolumeName[32] = L"DIRFS";

    VolumeInfo->TotalSize = UINT64_MAX;
    VolumeInfo->FreeSize = UINT64_MAX / 2;  // 返回虚拟的卷大小
    VolumeInfo->VolumeLabelLength = (UINT16)(wcslen(VolumeName) * sizeof(WCHAR));
    memcpy(VolumeInfo->VolumeLabel, VolumeName, VolumeInfo->VolumeLabelLength);

    return STATUS_SUCCESS;
}

// 通过名称获取文件信息和安全描述符
static NTSTATUS GetSecurityByName(FSP_FILE_SYSTEM* FileSystem,
    PWSTR FileName, PUINT32 PFileAttributes,
    PSECURITY_DESCRIPTOR SecurityDescriptor, SIZE_T* PSecurityDescriptorSize)
{
    DIRFS* DirFs = (DIRFS*)FileSystem->UserContext;
    WCHAR FullPath[FULLPATH_SIZE];
    NTSTATUS Result;

    // 构建物理路径
    Result = DirFsBuildPath(DirFs, FileName, FullPath, sizeof(FullPath));
    if (!NT_SUCCESS(Result))
        return Result;

    // 检查文件是否存在
    WIN32_FILE_ATTRIBUTE_DATA AttributeData;
    if (!GetFileAttributesExW(FullPath, GetFileExInfoStandard, &AttributeData))
    {
        // 如果文件不存在,尝试检查父目录
        PWSTR LastSlash = wcsrchr(FullPath, L'\\');
        if (NULL == LastSlash)
            return STATUS_OBJECT_NAME_NOT_FOUND;

        *LastSlash = L'\0';
        if (!GetFileAttributesExW(FullPath, GetFileExInfoStandard, &AttributeData))
            return STATUS_OBJECT_PATH_NOT_FOUND;

        // 父目录必须是目录
        if (0 == (AttributeData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
            return STATUS_NOT_A_DIRECTORY;

        return STATUS_OBJECT_NAME_NOT_FOUND;
    }

    if (PFileAttributes)
        *PFileAttributes = AttributeData.dwFileAttributes;

    if (PSecurityDescriptorSize)
    {
        // 获取文件安全描述符
        DWORD SecurityDescriptorSizeNeeded = 0;
        if (!GetFileSecurityW(FullPath,
            OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
            NULL, 0, &SecurityDescriptorSizeNeeded) &&
            ERROR_INSUFFICIENT_BUFFER != GetLastError())
            return FspNtStatusFromWin32(GetLastError());

        if (*PSecurityDescriptorSize < SecurityDescriptorSizeNeeded)
        {
            *PSecurityDescriptorSize = SecurityDescriptorSizeNeeded;
            return STATUS_BUFFER_OVERFLOW;
        }

        if (!GetFileSecurityW(FullPath,
            OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
            SecurityDescriptor, (DWORD)*PSecurityDescriptorSize, &SecurityDescriptorSizeNeeded))
            return FspNtStatusFromWin32(GetLastError());

        *PSecurityDescriptorSize = SecurityDescriptorSizeNeeded;
    }

    return STATUS_SUCCESS;
}

// 文件打开/创建
static NTSTATUS Create(FSP_FILE_SYSTEM* FileSystem,
    PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess,
    UINT32 FileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptor, UINT64 AllocationSize,
    PVOID* PFileContext, FSP_FSCTL_FILE_INFO* FileInfo)
{
    DIRFS* DirFs = (DIRFS*)FileSystem->UserContext;
    WCHAR FullPath[FULLPATH_SIZE];
    NTSTATUS Result;
    DIRFS_FILE_CONTEXT* FileContext;

    // 构建物理路径
    Result = DirFsBuildPath(DirFs, FileName, FullPath, sizeof(FullPath));
    if (!NT_SUCCESS(Result))
        return Result;

    // 准备创建文件参数
    DWORD CreateDisposition;
    if (CreateOptions & FILE_DELETE_ON_CLOSE)
        CreateDisposition = CREATE_ALWAYS;
    else if (CreateOptions & FILE_DIRECTORY_FILE)
        CreateDisposition = CREATE_NEW;
    else
        CreateDisposition = OPEN_ALWAYS;

    // 准备访问权限
    DWORD DesiredAccess = 0;
    if (GrantedAccess & (FILE_READ_DATA | FILE_EXECUTE))
        DesiredAccess |= GENERIC_READ;
    if (GrantedAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA))
        DesiredAccess |= GENERIC_WRITE;

    // 确保有创建权限
    if (CreateDisposition == CREATE_ALWAYS || CreateDisposition == CREATE_NEW)
        DesiredAccess |= GENERIC_WRITE;

    // 打开或创建文件
    HANDLE Handle;
    if (CreateOptions & FILE_DIRECTORY_FILE)
    {
        if (!CreateDirectoryW(FullPath, NULL))
        {
            DWORD Error = GetLastError();
            if (Error != ERROR_ALREADY_EXISTS || CreateDisposition == CREATE_NEW)
                return FspNtStatusFromWin32(Error);
        }

        Handle = CreateFileW(FullPath,
            DesiredAccess,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
            NULL);
    }
    else
    {
        Handle = CreateFileW(FullPath,
            DesiredAccess,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL,
            CreateDisposition,
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
            NULL);
    }

    if (INVALID_HANDLE_VALUE == Handle)
        return FspNtStatusFromWin32(GetLastError());

    // 设置文件属性
    if (FileAttributes != INVALID_FILE_ATTRIBUTES &&
        !SetFileAttributesW(FullPath, FileAttributes))
    {
        CloseHandle(Handle);
        return FspNtStatusFromWin32(GetLastError());
    }

    // 设置安全描述符
    if (SecurityDescriptor && !SetFileSecurityW(FullPath,
        OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
        SecurityDescriptor))
    {
        CloseHandle(Handle);
        return FspNtStatusFromWin32(GetLastError());
    }

    // 创建文件上下文
    FileContext = (DIRFS_FILE_CONTEXT*)calloc(1, sizeof(*FileContext));
    if (!FileContext)
    {
        CloseHandle(Handle);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    FileContext->Handle = Handle;
    FileContext->FileName = _wcsdup(FileName);
    FileContext->DeleteOnClose = (CreateOptions & FILE_DELETE_ON_CLOSE) != 0;

    // 获取文件属性
    BY_HANDLE_FILE_INFORMATION FileInfoData;
    if (!GetFileInformationByHandle(Handle, &FileInfoData))
    {
        CloseHandle(Handle);
        free(FileContext);
        return FspNtStatusFromWin32(GetLastError());
    }

    FileContext->FileAttributes = FileInfoData.dwFileAttributes;
    FileContext->IsDirectory = (FileInfoData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;

    // 填充文件信息
    FileInfo->FileAttributes = FileInfoData.dwFileAttributes;
    FileInfo->ReparseTag = 0;
    FileInfo->AllocationSize = ((UINT64)FileInfoData.nFileSizeHigh << 32) | FileInfoData.nFileSizeLow;
    FileInfo->FileSize = FileInfo->AllocationSize;
    FileInfo->CreationTime = ((UINT64)FileInfoData.ftCreationTime.dwHighDateTime << 32) |
        FileInfoData.ftCreationTime.dwLowDateTime;
    FileInfo->LastAccessTime = ((UINT64)FileInfoData.ftLastAccessTime.dwHighDateTime << 32) |
        FileInfoData.ftLastAccessTime.dwLowDateTime;
    FileInfo->LastWriteTime = ((UINT64)FileInfoData.ftLastWriteTime.dwHighDateTime << 32) |
        FileInfoData.ftLastWriteTime.dwLowDateTime;
    FileInfo->ChangeTime = FileInfo->LastWriteTime;
    FileInfo->IndexNumber = ((UINT64)FileInfoData.nFileIndexHigh << 32) | FileInfoData.nFileIndexLow;
    FileInfo->HardLinks = FileInfoData.nNumberOfLinks;

    *PFileContext = FileContext;

    return STATUS_SUCCESS;
}

// 打开已存在的文件
static NTSTATUS Open(FSP_FILE_SYSTEM* FileSystem,
    PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess,
    PVOID* PFileContext, FSP_FSCTL_FILE_INFO* FileInfo)
{
    return Create(FileSystem, FileName, CreateOptions, GrantedAccess,
        INVALID_FILE_ATTRIBUTES, NULL, 0, PFileContext, FileInfo);
}

// 关闭前清理
static VOID Cleanup(FSP_FILE_SYSTEM* FileSystem,
    PVOID FileContext0, PWSTR FileName, ULONG Flags)
{
    DIRFS_FILE_CONTEXT* FileContext = (DIRFS_FILE_CONTEXT*)FileContext0;

    if (FileContext->DeleteOnClose || (Flags & FspCleanupDelete))
    {
        DIRFS* DirFs = (DIRFS*)FileSystem->UserContext;
        WCHAR FullPath[FULLPATH_SIZE];

        if (NT_SUCCESS(DirFsBuildPath(DirFs, FileContext->FileName, FullPath, sizeof(FullPath))))
        {
            if (FileContext->IsDirectory)
                RemoveDirectoryW(FullPath);
            else
                DeleteFileW(FullPath);
        }
    }
}

// 关闭文件
static VOID Close(FSP_FILE_SYSTEM* FileSystem,
    PVOID FileContext0)
{
    DIRFS_FILE_CONTEXT* FileContext = (DIRFS_FILE_CONTEXT*)FileContext0;
    DirFsFileContextDelete(FileContext);
}

// 读取文件数据
static NTSTATUS Read(FSP_FILE_SYSTEM* FileSystem,
    PVOID FileContext0, PVOID Buffer, UINT64 Offset, ULONG Length,
    PULONG PBytesTransferred)
{
    DIRFS_FILE_CONTEXT* FileContext = (DIRFS_FILE_CONTEXT*)FileContext0;

    // 不能读取目录数据
    if (FileContext->IsDirectory)
        return STATUS_INVALID_DEVICE_REQUEST;

    // 设置文件指针位置
    LARGE_INTEGER LargeOffset;
    LargeOffset.QuadPart = Offset;

    OVERLAPPED Overlapped = { 0 };
    Overlapped.Offset = LargeOffset.LowPart;
    Overlapped.OffsetHigh = LargeOffset.HighPart;

    if (!ReadFile(FileContext->Handle, Buffer, Length, PBytesTransferred, &Overlapped))
    {
        DWORD Error = GetLastError();
        if (ERROR_HANDLE_EOF == Error)
            return STATUS_END_OF_FILE;
        else
            return FspNtStatusFromWin32(Error);
    }

    return STATUS_SUCCESS;
}

// 写入文件数据
static NTSTATUS Write(FSP_FILE_SYSTEM* FileSystem,
    PVOID FileContext0, PVOID Buffer, UINT64 Offset, ULONG Length,
    BOOLEAN WriteToEndOfFile, BOOLEAN ConstrainedIo,
    PULONG PBytesTransferred, FSP_FSCTL_FILE_INFO* FileInfo)
{
    DIRFS_FILE_CONTEXT* FileContext = (DIRFS_FILE_CONTEXT*)FileContext0;

    // 不能写入目录
    if (FileContext->IsDirectory)
        return STATUS_INVALID_DEVICE_REQUEST;

    LARGE_INTEGER LargeOffset;
    LargeOffset.QuadPart = Offset;

    OVERLAPPED Overlapped = { 0 };
    Overlapped.Offset = LargeOffset.LowPart;
    Overlapped.OffsetHigh = LargeOffset.HighPart;

    if (!WriteFile(FileContext->Handle, Buffer, Length, PBytesTransferred, &Overlapped))
        return FspNtStatusFromWin32(GetLastError());

    // 更新文件信息
    BY_HANDLE_FILE_INFORMATION FileInfoData;
    if (!GetFileInformationByHandle(FileContext->Handle, &FileInfoData))
        return FspNtStatusFromWin32(GetLastError());

    // 填充文件信息
    FileInfo->FileAttributes = FileInfoData.dwFileAttributes;
    FileInfo->ReparseTag = 0;
    FileInfo->AllocationSize = ((UINT64)FileInfoData.nFileSizeHigh << 32) | FileInfoData.nFileSizeLow;
    FileInfo->FileSize = FileInfo->AllocationSize;
    FileInfo->CreationTime = ((UINT64)FileInfoData.ftCreationTime.dwHighDateTime << 32) |
        FileInfoData.ftCreationTime.dwLowDateTime;
    FileInfo->LastAccessTime = ((UINT64)FileInfoData.ftLastAccessTime.dwHighDateTime << 32) |
        FileInfoData.ftLastAccessTime.dwLowDateTime;
    FileInfo->LastWriteTime = ((UINT64)FileInfoData.ftLastWriteTime.dwHighDateTime << 32) |
        FileInfoData.ftLastWriteTime.dwLowDateTime;
    FileInfo->ChangeTime = FileInfo->LastWriteTime;
    FileInfo->IndexNumber = ((UINT64)FileInfoData.nFileIndexHigh << 32) | FileInfoData.nFileIndexLow;
    FileInfo->HardLinks = FileInfoData.nNumberOfLinks;

    return STATUS_SUCCESS;
}

// 刷新文件缓冲区
static NTSTATUS Flush(FSP_FILE_SYSTEM* FileSystem,
    PVOID FileContext0, FSP_FSCTL_FILE_INFO* FileInfo)
{
    if (0 == FileContext0)
        return STATUS_SUCCESS;

    DIRFS_FILE_CONTEXT* FileContext = (DIRFS_FILE_CONTEXT*)FileContext0;

    if (!FlushFileBuffers(FileContext->Handle))
        return FspNtStatusFromWin32(GetLastError());

    // 更新文件信息
    BY_HANDLE_FILE_INFORMATION FileInfoData;
    if (!GetFileInformationByHandle(FileContext->Handle, &FileInfoData))
        return FspNtStatusFromWin32(GetLastError());

    // 填充文件信息
    FileInfo->FileAttributes = FileInfoData.dwFileAttributes;
    FileInfo->ReparseTag = 0;
    FileInfo->AllocationSize = ((UINT64)FileInfoData.nFileSizeHigh << 32) | FileInfoData.nFileSizeLow;
    FileInfo->FileSize = FileInfo->AllocationSize;
    FileInfo->CreationTime = ((UINT64)FileInfoData.ftCreationTime.dwHighDateTime << 32) |
        FileInfoData.ftCreationTime.dwLowDateTime;
    FileInfo->LastAccessTime = ((UINT64)FileInfoData.ftLastAccessTime.dwHighDateTime << 32) |
        FileInfoData.ftLastAccessTime.dwLowDateTime;
    FileInfo->LastWriteTime = ((UINT64)FileInfoData.ftLastWriteTime.dwHighDateTime << 32) |
        FileInfoData.ftLastWriteTime.dwLowDateTime;
    FileInfo->ChangeTime = FileInfo->LastWriteTime;
    FileInfo->IndexNumber = ((UINT64)FileInfoData.nFileIndexHigh << 32) | FileInfoData.nFileIndexLow;
    FileInfo->HardLinks = FileInfoData.nNumberOfLinks;

    return STATUS_SUCCESS;
}

// 获取文件信息
static NTSTATUS GetFileInfo(FSP_FILE_SYSTEM* FileSystem,
    PVOID FileContext0, FSP_FSCTL_FILE_INFO* FileInfo)
{
    DIRFS_FILE_CONTEXT* FileContext = (DIRFS_FILE_CONTEXT*)FileContext0;

    BY_HANDLE_FILE_INFORMATION FileInfoData;
    if (!GetFileInformationByHandle(FileContext->Handle, &FileInfoData))
        return FspNtStatusFromWin32(GetLastError());

    // 填充文件信息
    FileInfo->FileAttributes = FileInfoData.dwFileAttributes;
    FileInfo->ReparseTag = 0;
    FileInfo->AllocationSize = ((UINT64)FileInfoData.nFileSizeHigh << 32) | FileInfoData.nFileSizeLow;
    FileInfo->FileSize = FileInfo->AllocationSize;
    FileInfo->CreationTime = ((UINT64)FileInfoData.ftCreationTime.dwHighDateTime << 32) |
        FileInfoData.ftCreationTime.dwLowDateTime;
    FileInfo->LastAccessTime = ((UINT64)FileInfoData.ftLastAccessTime.dwHighDateTime << 32) |
        FileInfoData.ftLastAccessTime.dwLowDateTime;
    FileInfo->LastWriteTime = ((UINT64)FileInfoData.ftLastWriteTime.dwHighDateTime << 32) |
        FileInfoData.ftLastWriteTime.dwLowDateTime;
    FileInfo->ChangeTime = FileInfo->LastWriteTime;
    FileInfo->IndexNumber = ((UINT64)FileInfoData.nFileIndexHigh << 32) | FileInfoData.nFileIndexLow;
    FileInfo->HardLinks = FileInfoData.nNumberOfLinks;

    return STATUS_SUCCESS;
}

// 设置文件基本信息(时间、属性等)
static NTSTATUS SetBasicInfo(FSP_FILE_SYSTEM* FileSystem,
    PVOID FileContext0, UINT32 FileAttributes,
    UINT64 CreationTime, UINT64 LastAccessTime, UINT64 LastWriteTime, UINT64 ChangeTime,
    FSP_FSCTL_FILE_INFO* FileInfo)
{
    DIRFS_FILE_CONTEXT* FileContext = (DIRFS_FILE_CONTEXT*)FileContext0;
    FILETIME CreationFileTime, LastAccessFileTime, LastWriteFileTime;

    // 检查是否有属性需要修改
    if (INVALID_FILE_ATTRIBUTES != FileAttributes)
    {
        DIRFS* DirFs = (DIRFS*)FileSystem->UserContext;
        WCHAR FullPath[FULLPATH_SIZE];

        if (NT_SUCCESS(DirFsBuildPath(DirFs, FileContext->FileName, FullPath, sizeof(FullPath))))
        {
            if (!SetFileAttributesW(FullPath, FileAttributes))
                return FspNtStatusFromWin32(GetLastError());
        }
    }

    // 检查是否有时间需要修改
    if (0 != CreationTime || 0 != LastAccessTime || 0 != LastWriteTime)
    {
        if (0 != CreationTime)
        {
            CreationFileTime.dwLowDateTime = (DWORD)CreationTime;
            CreationFileTime.dwHighDateTime = (DWORD)(CreationTime >> 32);
        }

        if (0 != LastAccessTime)
        {
            LastAccessFileTime.dwLowDateTime = (DWORD)LastAccessTime;
            LastAccessFileTime.dwHighDateTime = (DWORD)(LastAccessTime >> 32);
        }

        if (0 != LastWriteTime)
        {
            LastWriteFileTime.dwLowDateTime = (DWORD)LastWriteTime;
            LastWriteFileTime.dwHighDateTime = (DWORD)(LastWriteTime >> 32);
        }

        if (!SetFileTime(FileContext->Handle,
            0 != CreationTime ? &CreationFileTime : NULL,
            0 != LastAccessTime ? &LastAccessFileTime : NULL,
            0 != LastWriteTime ? &LastWriteFileTime : NULL))
            return FspNtStatusFromWin32(GetLastError());
    }

    // 更新文件信息
    return GetFileInfo(FileSystem, FileContext0, FileInfo);
}

// 设置文件大小
static NTSTATUS SetFileSize(FSP_FILE_SYSTEM* FileSystem,
    PVOID FileContext0, UINT64 NewSize, BOOLEAN SetAllocationSize,
    FSP_FSCTL_FILE_INFO* FileInfo)
{
    DIRFS_FILE_CONTEXT* FileContext = (DIRFS_FILE_CONTEXT*)FileContext0;

    // 不能更改目录大小
    if (FileContext->IsDirectory)
        return STATUS_INVALID_DEVICE_REQUEST;

    LARGE_INTEGER LargeSize;
    LargeSize.QuadPart = NewSize;

    // 设置文件指针位置到文件尾
    if (!SetFilePointerEx(FileContext->Handle, LargeSize, NULL, FILE_BEGIN))
        return FspNtStatusFromWin32(GetLastError());

    // 设置文件尾
    if (!SetEndOfFile(FileContext->Handle))
        return FspNtStatusFromWin32(GetLastError());

    // 更新文件信息
    return GetFileInfo(FileSystem, FileContext0, FileInfo);
}

// 检查文件是否可以删除
static NTSTATUS CanDelete(FSP_FILE_SYSTEM* FileSystem,
    PVOID FileContext0, PWSTR FileName)
{
    DIRFS* DirFs = (DIRFS*)FileSystem->UserContext;
    WCHAR FullPath[FULLPATH_SIZE];
    NTSTATUS Result;

    // 构建物理路径
    Result = DirFsBuildPath(DirFs, FileName, FullPath, sizeof(FullPath));
    if (!NT_SUCCESS(Result))
        return Result;

    // 检查是否为目录且是否为空
    WIN32_FILE_ATTRIBUTE_DATA AttributeData;
    if (!GetFileAttributesExW(FullPath, GetFileExInfoStandard, &AttributeData))
        return FspNtStatusFromWin32(GetLastError());

    if (AttributeData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
    {
        // 检查目录是否为空
        WCHAR Pattern[FULLPATH_SIZE];
        wcscpy_s(Pattern, FULLPATH_SIZE, FullPath);
        wcscat_s(Pattern, FULLPATH_SIZE, L"\\*");

        WIN32_FIND_DATAW FindData;
        HANDLE FindHandle = FindFirstFileW(Pattern, &FindData);
        if (INVALID_HANDLE_VALUE == FindHandle)
        {
            DWORD Error = GetLastError();
            if (ERROR_NO_MORE_FILES == Error)
                return STATUS_SUCCESS;
            return FspNtStatusFromWin32(Error);
        }

        // 检查文件或者目录是否不是.或..
        BOOL HasFiles = FALSE;
        do
        {
            if (wcscmp(FindData.cFileName, L".") != 0 && wcscmp(FindData.cFileName, L"..") != 0)
            {
                HasFiles = TRUE;
                break;
            }
        } while (FindNextFileW(FindHandle, &FindData));

        FindClose(FindHandle);

        if (HasFiles)
            return STATUS_DIRECTORY_NOT_EMPTY;
    }

    return STATUS_SUCCESS;
}

// 重命名文件
static NTSTATUS Rename(FSP_FILE_SYSTEM* FileSystem,
    PVOID FileContext0,
    PWSTR FileName, PWSTR NewFileName, BOOLEAN ReplaceIfExists)
{
    DIRFS* DirFs = (DIRFS*)FileSystem->UserContext;
    WCHAR FullPath[FULLPATH_SIZE], NewFullPath[FULLPATH_SIZE];
    NTSTATUS Result;

    // 构建物理路径
    Result = DirFsBuildPath(DirFs, FileName, FullPath, sizeof(FullPath));
    if (!NT_SUCCESS(Result))
        return Result;

    Result = DirFsBuildPath(DirFs, NewFileName, NewFullPath, sizeof(NewFullPath));
    if (!NT_SUCCESS(Result))
        return Result;

    // 执行重命名
    DWORD Flags = MOVEFILE_COPY_ALLOWED;
    if (ReplaceIfExists)
        Flags |= MOVEFILE_REPLACE_EXISTING;

    if (!MoveFileExW(FullPath, NewFullPath, Flags))
        return FspNtStatusFromWin32(GetLastError());

    DIRFS_FILE_CONTEXT* FileContext = (DIRFS_FILE_CONTEXT*)FileContext0;
    free(FileContext->FileName);
    FileContext->FileName = _wcsdup(NewFileName);

    return STATUS_SUCCESS;
}

// 获取文件安全描述符
static NTSTATUS GetSecurity(FSP_FILE_SYSTEM* FileSystem,
    PVOID FileContext0,
    PSECURITY_DESCRIPTOR SecurityDescriptor, SIZE_T* PSecurityDescriptorSize)
{
    DIRFS_FILE_CONTEXT* FileContext = (DIRFS_FILE_CONTEXT*)FileContext0;

    // 获取安全描述符
    DWORD SecurityDescriptorSizeNeeded = 0;
    if (!GetKernelObjectSecurity(FileContext->Handle,
        OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
        NULL, 0, &SecurityDescriptorSizeNeeded) &&
        ERROR_INSUFFICIENT_BUFFER != GetLastError())
        return FspNtStatusFromWin32(GetLastError());

    if (*PSecurityDescriptorSize < SecurityDescriptorSizeNeeded)
    {
        *PSecurityDescriptorSize = SecurityDescriptorSizeNeeded;
        return STATUS_BUFFER_OVERFLOW;
    }

    if (!GetKernelObjectSecurity(FileContext->Handle,
        OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
        SecurityDescriptor, (DWORD)*PSecurityDescriptorSize, &SecurityDescriptorSizeNeeded))
        return FspNtStatusFromWin32(GetLastError());

    *PSecurityDescriptorSize = SecurityDescriptorSizeNeeded;

    return STATUS_SUCCESS;
}

// 设置文件安全描述符
static NTSTATUS SetSecurity(FSP_FILE_SYSTEM* FileSystem,
    PVOID FileContext0,
    SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR SecurityDescriptor)
{
    DIRFS_FILE_CONTEXT* FileContext = (DIRFS_FILE_CONTEXT*)FileContext0;

    if (!SetKernelObjectSecurity(FileContext->Handle, SecurityInformation, SecurityDescriptor))
        return FspNtStatusFromWin32(GetLastError());

    return STATUS_SUCCESS;
}

// 读取目录内容
static NTSTATUS ReadDirectory(FSP_FILE_SYSTEM* FileSystem,
    PVOID FileContext0, PWSTR Pattern, PWSTR Marker,
    PVOID Buffer, ULONG Length, PULONG PBytesTransferred)
{
    DIRFS_FILE_CONTEXT* FileContext = (DIRFS_FILE_CONTEXT*)FileContext0;
    DIRFS* DirFs = (DIRFS*)FileSystem->UserContext;

    // 确保是目录
    if (!FileContext->IsDirectory)
        return STATUS_NOT_A_DIRECTORY;

    // 构建搜索路径
    WCHAR FullPath[FULLPATH_SIZE];
    NTSTATUS Result;

    Result = DirFsBuildPath(DirFs, FileContext->FileName, FullPath, sizeof(FullPath));
    if (!NT_SUCCESS(Result))
        return Result;

    // 添加通配符
    WCHAR PatternPath[FULLPATH_SIZE];
    wcscpy_s(PatternPath, FULLPATH_SIZE / sizeof(WCHAR), FullPath);
    if (PatternPath[wcslen(PatternPath) - 1] != L'\\')
        wcscat_s(PatternPath, FULLPATH_SIZE / sizeof(WCHAR), L"\\");

    if (NULL == Pattern || L'\0' == Pattern[0])
        wcscat_s(PatternPath, FULLPATH_SIZE / sizeof(WCHAR), L"*");
    else
        wcscat_s(PatternPath, FULLPATH_SIZE / sizeof(WCHAR), Pattern);

    // 首先添加 . 和 .. 目录
    if (NULL == Marker)
    {
        FSP_FSCTL_DIR_INFO DirInfo;
        memset(&DirInfo, 0, sizeof(DirInfo));

        DirInfo.Size = (UINT16)(sizeof(FSP_FSCTL_DIR_INFO) + sizeof(WCHAR));
        DirInfo.FileInfo.FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
        DirInfo.FileInfo.IndexNumber = 0;
        DirInfo.FileNameBuf[0] = L'.';

        if (!FspFileSystemAddDirInfo(&DirInfo, Buffer, Length, PBytesTransferred))
            return STATUS_SUCCESS;

        memset(&DirInfo, 0, sizeof(DirInfo));
        DirInfo.Size = (UINT16)(sizeof(FSP_FSCTL_DIR_INFO) + 2 * sizeof(WCHAR));
        DirInfo.FileInfo.FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
        DirInfo.FileInfo.IndexNumber = 0;
        DirInfo.FileNameBuf[0] = L'.';
        DirInfo.FileNameBuf[1] = L'.';

        if (!FspFileSystemAddDirInfo(&DirInfo, Buffer, Length, PBytesTransferred))
            return STATUS_SUCCESS;
    }

    // 开始查找文件
    WIN32_FIND_DATAW FindData;
    HANDLE FindHandle = FindFirstFileW(PatternPath, &FindData);
    if (INVALID_HANDLE_VALUE == FindHandle)
    {
        DWORD Error = GetLastError();
        if (ERROR_NO_MORE_FILES == Error)
            return STATUS_SUCCESS;
        return FspNtStatusFromWin32(Error);
    }

    // 处理找到的文件
    try
    {
        // 跳过.和..目录,如果有Marker,跳过直到找到Marker
        if (Marker != NULL)
        {
            do
            {
                if (wcscmp(FindData.cFileName, Marker) == 0)
                    break;
            } while (FindNextFileW(FindHandle, &FindData));

            // 如果找到Marker,再读取下一个
            if (!FindNextFileW(FindHandle, &FindData))
            {
                FindClose(FindHandle);
                FspFileSystemAddDirInfo(0, Buffer, Length, PBytesTransferred);
                return STATUS_SUCCESS;
            }
        }

        // 添加找到的所有文件到Buffer
        do
        {
            if (wcscmp(FindData.cFileName, L".") == 0 || wcscmp(FindData.cFileName, L"..") == 0)
                continue;

            FSP_FSCTL_DIR_INFO DirInfo;
            memset(&DirInfo, 0, sizeof(DirInfo));

            // 计算文件名长度
            size_t FileNameLen = wcslen(FindData.cFileName);
            DirInfo.Size = (UINT16)(sizeof(FSP_FSCTL_DIR_INFO) + FileNameLen * sizeof(WCHAR));

            // 设置文件信息
            LARGE_INTEGER FileSize, CreationTime, LastAccessTime, LastWriteTime;
            FileSize.HighPart = FindData.nFileSizeHigh;
            FileSize.LowPart = FindData.nFileSizeLow;
            CreationTime.HighPart = FindData.ftCreationTime.dwHighDateTime;
            CreationTime.LowPart = FindData.ftCreationTime.dwLowDateTime;
            LastAccessTime.HighPart = FindData.ftLastAccessTime.dwHighDateTime;
            LastAccessTime.LowPart = FindData.ftLastAccessTime.dwLowDateTime;
            LastWriteTime.HighPart = FindData.ftLastWriteTime.dwHighDateTime;
            LastWriteTime.LowPart = FindData.ftLastWriteTime.dwLowDateTime;

            DirInfo.FileInfo.FileAttributes = FindData.dwFileAttributes;
            DirInfo.FileInfo.ReparseTag = 0;
            DirInfo.FileInfo.FileSize = FileSize.QuadPart;
            DirInfo.FileInfo.AllocationSize = (FileSize.QuadPart + ALLOCATION_UNIT - 1) & ~(ALLOCATION_UNIT - 1);
            DirInfo.FileInfo.CreationTime = CreationTime.QuadPart;
            DirInfo.FileInfo.LastAccessTime = LastAccessTime.QuadPart;
            DirInfo.FileInfo.LastWriteTime = LastWriteTime.QuadPart;
            DirInfo.FileInfo.ChangeTime = LastWriteTime.QuadPart;
            DirInfo.FileInfo.IndexNumber = 0; // 使用0而不是实际的索引号
            DirInfo.FileInfo.HardLinks = 0;   // 不关心硬链接数量

            // 复制文件名
            memcpy(DirInfo.FileNameBuf, FindData.cFileName, FileNameLen * sizeof(WCHAR));

            if (!FspFileSystemAddDirInfo(&DirInfo, Buffer, Length, PBytesTransferred))
                break;

        } while (FindNextFileW(FindHandle, &FindData));
    }
    catch (...)
    {
        FindClose(FindHandle);
        return STATUS_INTERNAL_ERROR;
    }

    FindClose(FindHandle);

    // 添加结束标记
    FspFileSystemAddDirInfo(0, Buffer, Length, PBytesTransferred);

    return STATUS_SUCCESS;
}

static FSP_FILE_SYSTEM_INTERFACE DirFsInterface =
{
    GetVolumeInfo,
    0,                          // SetVolumeLabel
    GetSecurityByName,
    Create,
    Open,
    0,                          // Overwrite
    Cleanup,
    Close,
    Read,
    Write,
    Flush,
    GetFileInfo,
    SetBasicInfo,
    SetFileSize,
    CanDelete,
    Rename,
    GetSecurity,
    SetSecurity,
    ReadDirectory,
    0,                          // ResolveReparsePoints
    0,                          // GetReparsePoint
    0,                          // SetReparsePoint
    0,                          // DeleteReparsePoint
    0,                          // GetStreamInfo
    0,                          // GetDirInfoByName
    0,                          // Control
    0,                          // SetDelete
};

static NTSTATUS SvcStart(FSP_SERVICE* Service, ULONG argc, PWSTR* argv)
{
    WCHAR Root[MAX_PATH];
    PWSTR MountPoint = 0;
    PWSTR RootDirectory = 0;
    ULONG FileInfoTimeout = 1000; // 默认1秒
    BOOLEAN CaseInsensitive = FALSE;
    NTSTATUS Result;
    DIRFS* DirFs = 0;
    FSP_FSCTL_VOLUME_PARAMS VolumeParams;

    if (argc < 2 || argc > 3)
    {
        // 修复常量字符串转换为PWSTR
        static WCHAR usage[] = L""
            L"usage: %s RootDirectory [MountPoint]\n"
            L"\n"
            L"    RootDirectory   Directory to mirror\n"
            L"    MountPoint      Mount point (drive letter or directory path)\n";

        fail(usage, PROGNAME);
        return STATUS_INVALID_PARAMETER;
    }

    RootDirectory = argv[1];
    if (argc > 2)
        MountPoint = argv[2];

    // 验证根目录是否存在
    if (!GetFullPathNameW(RootDirectory, MAX_PATH, Root, NULL) ||
        GetFileAttributesW(Root) == INVALID_FILE_ATTRIBUTES)
    {
        // 修复: 使用 const_cast 解决常量字符串转换问题
        WCHAR errorFormat[] = L"Invalid root directory %s";
        fail(errorFormat, RootDirectory);
        return STATUS_OBJECT_PATH_NOT_FOUND;
    }

    // 确保路径以反斜杠结尾
    size_t RootLen = wcslen(Root);
    if (Root[RootLen - 1] != L'\\')
    {
        Root[RootLen] = L'\\';
        Root[RootLen + 1] = L'\0';
    }

    DirFs = (DIRFS*)malloc(sizeof(*DirFs));
    if (NULL == DirFs)
        return STATUS_INSUFFICIENT_RESOURCES;

    memset(DirFs, 0, sizeof(*DirFs));

    DirFs->RootDirectory = _wcsdup(Root);
    if (NULL == DirFs->RootDirectory)
    {
        free(DirFs);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    DirFs->CaseInsensitive = CaseInsensitive;
    DirFs->FileInfoTimeout = FileInfoTimeout;

    memset(&VolumeParams, 0, sizeof(VolumeParams));
    VolumeParams.Version = sizeof(FSP_FSCTL_VOLUME_PARAMS);
    VolumeParams.SectorSize = 4096; // 使用标准扇区大小
    VolumeParams.SectorsPerAllocationUnit = 1;
    VolumeParams.VolumeCreationTime = 0; // 使用当前时间
    VolumeParams.VolumeSerialNumber = 0; // 自动生成序列号
    VolumeParams.FileInfoTimeout = DirFs->FileInfoTimeout;
    VolumeParams.CaseSensitiveSearch = !DirFs->CaseInsensitive;
    VolumeParams.CasePreservedNames = 1;
    VolumeParams.UnicodeOnDisk = 1;
    VolumeParams.PersistentAcls = 1;
    VolumeParams.PostCleanupWhenModifiedOnly = 1;
    VolumeParams.PassQueryDirectoryFileName = 0;

    wcscpy_s(VolumeParams.FileSystemName, sizeof(VolumeParams.FileSystemName) / sizeof(WCHAR), L"DIRFS");

    Result = FspFileSystemCreate(const_cast<PWSTR>(L"" FSP_FSCTL_DISK_DEVICE_NAME), 
                               &VolumeParams, &DirFsInterface, &DirFs->FileSystem);
    if (!NT_SUCCESS(Result))
    {
        free(DirFs->RootDirectory);
        free(DirFs);
        return Result;
    }

    DirFs->FileSystem->UserContext = DirFs;

    Result = FspFileSystemSetMountPoint(DirFs->FileSystem, MountPoint);
    if (!NT_SUCCESS(Result))
    {
        FspFileSystemDelete(DirFs->FileSystem);
        free(DirFs->RootDirectory);
        free(DirFs);
        return Result;
    }

    Result = FspFileSystemStartDispatcher(DirFs->FileSystem, 0);
    if (!NT_SUCCESS(Result))
    {
        FspFileSystemDelete(DirFs->FileSystem);
        free(DirFs->RootDirectory);
        free(DirFs);
        return Result;
    }

    Service->UserContext = DirFs;

    MountPoint = FspFileSystemMountPoint(DirFs->FileSystem);
    // 修复: 使用本地变量存储格式字符串
    WCHAR formatStr[] = L"%s -m \"%s\" \"%s\"";
    info(formatStr, PROGNAME, MountPoint ? MountPoint : L"(null)", DirFs->RootDirectory);

    return STATUS_SUCCESS;
}

static NTSTATUS SvcStop(FSP_SERVICE* Service)
{
    DIRFS* DirFs = (DIRFS*)Service->UserContext;

    FspFileSystemStopDispatcher(DirFs->FileSystem);
    FspFileSystemDelete(DirFs->FileSystem);
    free(DirFs->RootDirectory);
    free(DirFs);

    return STATUS_SUCCESS;
}

int wmain(int argc, wchar_t** argv)
{
    // 修复: 使用正确的宽字符字符串
    WCHAR progNameBuf[32];
    // 修复: 使用宏定义的宽字符版本
    wcscpy_s(progNameBuf, 32, L"DirFS");  // 直接使用 L"DirFS" 而不是 PROGNAME
    return FspServiceRun(progNameBuf, SvcStart, SvcStop, 0);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{

    // 在 WinMain 函数开始处添加
    BOOL result = SetDllDirectoryW(L"C:\\Program Files (x86)\\WinFsp\\bin");
    if (!result) {
        MessageBoxW(NULL, L"无法设置 DLL 目录,程序可能无法正常运行", L"错误", MB_OK | MB_ICONERROR);
        // 可以继续尝试运行,但可能会失败
    }

    // 创建控制台窗口
    if (AllocConsole())
    {
        // 重定向标准输入/输出到控制台窗口
        FILE* pCout;
        freopen_s(&pCout, "CONOUT$", "w", stdout);
        FILE* pCin;
        freopen_s(&pCin, "CONIN$", "r", stdin);
        FILE* pCerr;
        freopen_s(&pCerr, "CONOUT$", "w", stderr);

        // 设置控制台标题
        SetConsoleTitle(L"DirFS 控制台");

        // 允许使用 C++ 标准输入输出
        std::ios::sync_with_stdio(true);
    }

    // 获取命令行参数
    int argc;
    wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc);

    if (NULL == argv)
        return GetLastError();

    int Result = wmain(argc, argv);

    LocalFree(argv);

    return Result;
}

2061360308 avatar Jun 04 '25 10:06 2061360308