3

Windows文件系统概述(一)

 1 year ago
source link: https://xuranus.github.io/2023/02/28/Windows%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F1/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

最近接触Windows文件备份相关的业务,从C++ Win32API入手对Windows文件系统的基本概念做一个总结

Win32 API Doc

学习Windows文件系统最佳的手段就是阅读官方文档。文档会详细描述每一个API的入参、出参、返回的字段含义和取值范围,部分还会给出Example程序。

Windows程序开发一般使用MSVC,和GCC有不少区别。为了更有效率的学习Windows文件系统,在介绍文件系统之前,本文先给Linux转Windows的开发者介绍一下Windows的编码和文档阅读方式,磨刀不误砍柴工。

UTF-8/UTF-16

涉及字符串的Win32 API一般提供两类接口:

  • Ansi字符串接口(一般以A结尾)
  • UTF-16宽字符串接口(一般以W结尾)
typedef _Null_terminated_ CONST WCHAR *LPCWSTR, *PCWSTR;
typedef _Null_terminated_ CONST CHAR *LPCSTR, *PCSTR;
...
DWORD GetFileAttributesA(
[in] LPCSTR lpFileName
);

DWORD GetFileAttributesW(
[in] LPCWSTR lpFileName
);

LPCSTR的类型是CHAR*、即char*,表示一个ANSI字符串的指针。而LPCWSTR的类型是WCHAR*、即wchar_t*,表示一个UTF-16字符串指针。此外还提供不显式声明字符串类型的接口GetFileAttributes,他的宏定义如下:

#ifdef UNICODE
#define GetFileAttributes GetFileAttributesW
#else
#define GetFileAttributes GetFileAttributesA
#endif // !UNICODE

可见这类接口会根据是否定义UNICODE宏来决定使用哪种API。对于字符串可能包含Unicode字符的程序,尽量使用宽字符API,否则可能会调用失败!

C++程序一般使用UTF-8编码的std::string来表示字符串,Linux接口一般使用UTF-8编码的字符串,而Windows是一个UTF-16的操作系统。如果程序需要跨平台,一般在公共业务部分用std::string,因为UTF-8表示相同的含有Unicode特殊字符的字符串可能占用更少的空间,在网络IO和数据落盘时的开销也更小,而在涉及Windows接口的部分则最好使用UTF-16编码的std::wstring。此时就不得不考虑std::stringstd::wstring的转码问题。

C++11标准库提供了std::wstringstd::wstring无损互转换工具std::codecvt_utf8_utf16,用法如下:

#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING /* deprecated since C++17, supress warning */
#include <locale>
#include <codecvt>

std::wstring Utf8ToUtf16(const std::string& str)
{
using ConvertTypeX = std::codecvt_utf8_utf16<wchar_t>;
std::wstring_convert<ConvertTypeX> converterX;
std::wstring wstr = converterX.from_bytes(str);
return wstr;
}

std::string Utf16ToUtf8(const std::wstring& wstr)
{
using ConvertTypeX = std::codecvt_utf8_utf16<wchar_t>;
std::wstring_convert<ConvertTypeX> converterX;
return converterX.to_bytes(wstr);
}

对于UTF-8和UTF-16编码的概念和转换方法,本文就不再赘述,读者可以查询相关资料自行拓展。了解了编码相关的知识,就能知道Windows的两种API该如何使用,接下来说说Win32 API文档的阅读方式。

Win32 API Document

官方文档会根据头文件列出其中包含的API,例如FindFirstFileWSyntax中描述如下:

HANDLE FindFirstFileW(
[in] LPCWSTR lpFileName,
[out] LPWIN32_FIND_DATAW lpFindFileData
);

表明了第一个参数lpFileName是个LPCWSTR类型的入参,而lpFindFileData是个LPWIN32_FIND_DATAW类型的出参,返回值是HANDLE类型,表示的含义可以自Return Value中查到。LPWIN32_FIND_DATAW则是该接口获取的信息,其结构体构成也可在页面Parameters中找到详细信息的链接

Remarks区域,列举了该API调用的注意点,例如会提示你用FindClose在调用成功后关闭API返回的句柄。

在Linux中一般用int fd描述一个打开文件的描述符(File Descriptor),当fd < 0往往意味调用失败。Windows中用HANDLE hFile,实际类型是void*来表示一个打开文件的句柄(File Handle),如果hFileINVALID_HANDLE_VALUE则表示调用失败。

Window文件的基本属性有:

  • iNode
  • 用户ID (User ID)
  • 组ID (Group ID)
  • 访问时间(Access Time)
  • 修改时间(Modification Time)
  • 创建时间(Creation Time)
  • 大小(Size)
  • 设备号(Device ID)
  • 硬连接数(Links Number)

Window的文件系统没有iNode这个概念,只有一个类似iNode的概念index。无论是目录还是文件都有各自的index,硬链接文件共享相同的index。需要注意的是:iNode在Linux文件系统是一个全局的唯一编号,index在Windows中只保证在同一个卷上是唯一的

Windows文件系统中除了iNode,用户ID,组ID,其余基本属性含义类似。

Window可以用GetFileInformationByHandle()获取文件的BY_HANDLE_FILE_INFORMATION结构信息,其中包含index、访问时间、修改时间、创建实现、大小、设备号、硬链接数。还包含DWORD类型的dwFileAttributes结构,该机构类似于Linux的mode_t类型,记录文件的属性flag,包含是否是目录,压缩文件、隐藏文件、只读文件、等。

具体代码如下:

std::wstring wPath = L"C:\\Users\\XUranus\\Desktop\\file1.txt"; // filepath
BY_HANDLE_FILE_INFORMATION handleFileInformation{};
HANDLE hFile = ::CreateFileW( /* Open file and obtain the handle */
wPath.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
0
);
if (hFile == INVALID_HANDLE_VALUE) {
/* failed */
}
if (::GetFileInformationByHandle(hFile, &handleFileInformation) == 0) {
::CloseHandle(hFile);
}

CreateFileW()会先获得句柄,如果文件是链接文件,则会获得最终指向的目标文件的句柄,基于此句柄调用GetFileInformationByHandle()获取的handleFileInformation中的dwFileAttributes属性字段反应的是最终指向文件的属性信息,会丢掉原本链接文件的FILE_ATTRIBUTE_REPARSE_POINT符号位。如果要识别原链接文件的属性,需要使用GetFileAttributesW()

DWORD attribute = GetFileAttributesW(wPath.c_str()); /* to detect origin attribute */
if (attribute != INVALID_FILE_ATTRIBUTES) {
handleFileInformation.dwFileAttributes = attribute;
}

attributeDWORD类型,使用flag位来标记多个属性,详见官方文档File Attribute Constants

BY_HANDLE_FILE_INFORMATION handleFileInformation结构定义如下:

typedef struct _BY_HANDLE_FILE_INFORMATION {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD dwVolumeSerialNumber;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD nNumberOfLinks;
DWORD nFileIndexHigh;
DWORD nFileIndexLow;
} BY_HANDLE_FILE_INFORMATION, *PBY_HANDLE_FILE_INFORMATION, *LPBY_HANDLE_FILE_INFORMATION;

其中sizeindex被拆分成了高低字节,time字段也是拆分为高低字节的结构体

typedef struct _FILETIME {
DWORD dwLowDateTime;
DWORD dwHighDateTime;
} FILETIME, *PFILETIME, *LPFILETIME;

这类数据构造LARGE_INTEGER结构体,并通过QuadPart()方法来获取整合两个字段的最终值

inline uint64_t CombineDWORD(DWORD low, DWORD high) {
LARGE_INTEGER li;
li.LowPart = low;
li.HighPart = high;
return li.QuadPart;
}

UNIX的时间戳从1970年1月1日开始,Windows的时间戳转UNIX时间戳需要减去0x019DB1DED53E8000换算

inline uint64_t ConvertWin32Time(DWORD low, DWORD high)
{
const uint64_t UNIX_TIME_START = 0x019DB1DED53E8000; /* January 1, 1970 (start of Unix epoch) in "ticks" */
const uint64_t TICKS_PER_SECOND = 10000000; /* a tick is 100ns */
LARGE_INTEGER li;
li.LowPart = low;
li.HighPart = high;
#ifdef KEEP_WIN32_NATIVE_TIMESTAMP_VALUE
return li.QuadPart;
#else
/* Convert ticks since 1/1/1970 into seconds */
return (li.QuadPart - UNIX_TIME_START) / TICKS_PER_SECOND;
#endif
}

完整代码见:https://github.com/XUranus/FileSystemUtil
Windows Error Codes


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK