用过 Windows Vista/7/8 的同学可能有这样的经历:用 32 位程序(比如 cygwin)修改 C 盘下的某个文件后,再从 Windows 资源管理器里看,竟然是修改前的版本!难道文件系统还对不同的程序有不同的视图?您说对了,自从 Vista 引入了 UAC 和 VirtualStore,不要相信 32 位程序在 C 盘里做的改动。

Windows Vista 引入更强的安全机制后,一些重要的系统目录就不是谁都能修改了。这些目录包括 C 盘根目录,Program Files,Program Files (x86),Windows,注册表的 HKEY_LOCAL_MACHINE 等。但一些老的应用程序仍然假定这些目录是可写的,如果系统 API 简单返回访问拒绝,则这些程序都不能运行了。

因此,Vista 提供了 VirtualStore,对非管理员权限运行的 32 位程序,只要对这些目录有写操作,就会把被修改或添加的文件复制一份到这个用户的 VirtualStore。以这个用户身份运行的 32 位程序看到的这个路径就是 VirtualStore 里的对应文件,原始路径上的文件再怎么修改,它已经全然不知了。

写这篇博客是因为有同学在 cygwin 里编辑 OpenVPN 配置文件遇到了问题,就拿这个举例子吧。

  1. 在 32 位的 cygwin 里编辑 C:\Program Files\OpenVPN\config\boj.ovpn(如果提示只读,在 vim 里 :w! 就能强制保存)
  2. 到 Windows 资源管理器里,发现上述文件并没有发生变化(看最后修改时间就行)
  3. 在 Windows 资源管理器或者 cygwin 里,都能找到被修改后的虚拟文件C:\Users\boj\AppData\Local\VirtualStore\Program Files\OpenVPN\config\boj.ovpn
  4. 在 Windows 资源管理器里修改 C:\Program Files\OpenVPN\config\boj.ovpn,在 cygwin 里看不到这个修改,说明文件系统的两个 “分支” 已然形成。
  5. 在 Windows 资源管理器或者 cygwin 里把 C:\Users\boj\AppData\Local\VirtualStore\Program Files\OpenVPN\config\boj.ovpn 删除,cygwin 里看到的文件就变成第4步的修改后的版本了。
    如果我是一个 32 位应用程序(例如在 cygwin 里编译出来的程序),想知道对一个文件的修改是否生效了,可以判断 %localappdata%\VirtualStore<path-to-file> 是否存在。(建议使用 %localappdata% 这种环境变量而非绝对路径以提高兼容性)

类似地,对注册表键 HKEY_LOCAL_MACHINE\Software\Example 的修改,会被重定向到HKEY_CURRENT_USER\Software\Classes\VirtualStore\MACHINE\Software\Example。

不过,新编写的应用程序最好还是不要往系统目录里硬编码的路径写东西,建议遵循 API 规范,调用 SHGetKnownFolderPath 传入下列参数获取存储应用数据的路径。

  • FOLDERID_ProgramData – 本地计算机上的所有用户共享;
  • FOLDERID_LocalAppData – 当前用户,且仅在本地计算机上(如浏览器缓存);
  • FOLDERID_RoamingAppData – 当前用户,且随着用户迁移(如偏好设置),比如用户是用域登录的,这些数据就存储在活动目录服务器上。
    希望本文能解开 cygwin 用户修改文件无效的疑惑。

Comments

2013-10-07