一
前言
CreateFile
函数的dwDesiredAccess
和dwShareMode
参数的笔记。我发现之前的理解不够全面、准确。为了更好的理解这两个参数的作用,我搜索了大量资料,编写了测试程序及测试脚本,参考了xp
源码,终于搞清楚这两个参数的作用。简而言之,需要遵循以下两个规则:规则 1:后续的访问权限与先前的共享模式不能冲突。
规则 2:后续的共享模式与先前的访问权限不能冲突。
1.第一次以读访问权限,写共享模式打开文件,会成功吗?
2.如果第一次打开成功了,第二次以写访问权限,读共享模式打开。会成功吗?
3.如果第二次打开成功了,第三次以读/写/读写访问权限,读写共享模式打开,会成功吗?
4.第一次以读访问权限,写共享模式打开文件,第二次以写访问权限,读写共享模式打开。第三次以写访问权限,读写共享模式打开,会成功吗?
二
参考源码
NTSTATUS IoCheckShareAccess(
IN ACCESS_MASK DesiredAccess,
IN ULONG DesiredShareAccess,
IN OUT PFILE_OBJECT FileObject,
IN OUT PSHARE_ACCESS ShareAccess,
IN BOOLEAN Update
)
{
PAGED_CODE();
// 获取本次调用时,指定的 读/写/删除 访问权限标志
FileObject->ReadAccess = (BOOLEAN) ((DesiredAccess & (FILE_EXECUTE | FILE_READ_DATA)) != 0);
FileObject->WriteAccess = (BOOLEAN) ((DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0);
FileObject->DeleteAccess = (BOOLEAN) ((DesiredAccess & DELETE) != 0);
if (FileObject->ReadAccess || FileObject->WriteAccess || FileObject->DeleteAccess)
{
// 获取本次调用时,指定的 读/写/删除 共享模式标志
FileObject->SharedRead = (BOOLEAN) ((DesiredShareAccess & FILE_SHARE_READ) != 0);
FileObject->SharedWrite = (BOOLEAN) ((DesiredShareAccess & FILE_SHARE_WRITE) != 0);
FileObject->SharedDelete = (BOOLEAN) ((DesiredShareAccess & FILE_SHARE_DELETE) != 0);
if (FileObject->Flags & FO_FILE_OBJECT_HAS_EXTENSION)
{
PIOP_FILE_OBJECT_EXTENSION fileObjectExtension =(PIOP_FILE_OBJECT_EXTENSION)(FileObject + 1);
if (fileObjectExtension->FileObjectExtensionFlags & FO_EXTENSION_IGNORE_SHARE_ACCESS_CHECK)
return STATUS_SUCCESS;
}
ULONG ocount = ShareAccess->OpenCount;
if ( // 本次调用时 DesiredAccess 包含了读/写/删除标志,并且
// 在之前的调用中,DesiredShareAccess 缺少对应的读/写/删除标志(ShareXXX < ocount)
(FileObject->ReadAccess && (ShareAccess->SharedRead < ocount))
|| (FileObject->WriteAccess && (ShareAccess->SharedWrite < ocount))
|| (FileObject->DeleteAccess && (ShareAccess->SharedDelete < ocount))
// 之前的调用中 DesiredAccess 包含了读/写/删除标志,并且
// 本次调用时 DesiredShareAccess 缺少对应读/写/删除标志
|| ((ShareAccess->Readers != 0) && !FileObject->SharedRead)
|| ((ShareAccess->Writers != 0) && !FileObject->SharedWrite)
|| ((ShareAccess->Deleters != 0) && !FileObject->SharedDelete)
)
{
return STATUS_SHARING_VIOLATION;
}
else if (Update)
{
ShareAccess->OpenCount++; // 每次权限检查通过后,打开计数 +1
// 本次调用时 DesiredAccess 包含了读/写/删除标志,对应的计数 +1
ShareAccess->Readers += FileObject->ReadAccess;
ShareAccess->Writers += FileObject->WriteAccess;
ShareAccess->Deleters += FileObject->DeleteAccess;
// 本次调用时 DesiredShareAccess 包含了读/写/删除标志,对应的计数 +1
ShareAccess->SharedRead += FileObject->SharedRead;
ShareAccess->SharedWrite += FileObject->SharedWrite;
ShareAccess->SharedDelete += FileObject->SharedDelete;
}
}
return STATUS_SUCCESS;
}
说明: DesiredAccess
表示 访问权限,DesiredShareAccess
表示 共享模式。
Update
参数,SharedAccess->OpenCount
计数会加一。DesiredAccess
包含读/写/删除标志的时候,SharedAccess->Readers / Writers / Deleters
计数会加一。DesiredShareAccess
包含读/写/删除标志的时候,ShareAccess->SharedRead / SharedWrite / SharedDelete
计数会加一。DesiredAccess
包含了读/写/删除标志(FileObject->ReadAccess / WriteAccess / DeleteAccess
为真)并且在之前的调用中DesiredShareAccess
缺少对应的读/写/删除标志(ShareAccess->SharedRead / SharedWrite / SharedDelete < ocount
),违反规则 1,权限检查会失败。DesiredAccess
包含了读/写/删除标志(ShareAccess->Readers / Writers / Deleters != 0
),并且本次调用时DesiredShareAccess
缺少对应读/写/删除标志(FileObject->SharedRead / SharedWrite / SharedDelete
为假),违反规则 2,权限检查会失败。三
结果表
◆访问权限
代表dwDesiredAccess
参数,共享模式
代表dwShareAccess
参数。1
表示第一次调用,2
表示第二次调用。
◆R
Read
,表示读。W
Write
,表示写。RW
ReadWrite
,表示读写。N
None
, 表示独占。
◆/
表示或者。为了减少组合数量。比如第一行中的访问权限 2可以是读/写/读写中的任意一种。
◆---
表示对应位置是什么都可以,不影响结果。比如,第一行的访问权限 1可以是读/写/读写中的任意一种,不论是哪种都会打开失败。
◆结果列只统计了第二次的结果,因为第一次总是成功的。
win10
系统上亲自验证过,整体验证思路是用不同的参数调用CreateFile
打开同一个文件。关键验证代码如下:四
验证代码
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CreateFile
{
class Program
{
// ref https://learn.microsoft.com/en-us/dotnet/standard/commandline/define-commands
static Command SetupCommandHandler()
{
var filePathOption = new Option<string>(name: "--path", getDefaultValue: () => "test.txt", description: "file path");
filePathOption.AddAlias("-f");
filePathOption.AddAlias("-p");
var fileModeOption = new Option<string>(name: "--mode", getDefaultValue: () => "Open", description: "file mode")
.FromAmong("CreateNew", "Create", "Open", "OpenOrCreate", "Truncate", "Append");
fileModeOption.AddAlias("-m");
var fileShareOption = new Option<string>("--share", "file share") { IsRequired = true }
.FromAmong("None", "Read", "Write", "ReadWrite", "Delete", "Inheritable");
fileShareOption.AddAlias("-s");
var fileAccessOption = new Option<string>("--access", "file access") { IsRequired = true }
.FromAmong("Read", "Write", "ReadWrite");
fileAccessOption.AddAlias("-a");
var autoQuitOption = new Option<bool>(name: "--autoquit", getDefaultValue: () => false, description: "auto quit");
autoQuitOption.AddAlias("-q");
var command = new RootCommand();
command.Add(filePathOption);
command.Add(fileModeOption);
command.Add(fileShareOption);
command.Add(fileAccessOption);
command.Add(autoQuitOption);
command.SetHandler((filePath, fileMode, fileShare, fileAccess, autoQuit) =>
{
OpenFileAndWait(filePath, fileMode, fileShare, fileAccess, autoQuit);
}, filePathOption, fileModeOption, fileShareOption, fileAccessOption, autoQuitOption);
return command;
}
static void Main(string[] args)
{
var command = SetupCommandHandler();
command.Invoke(args);
}
static void OpenFileAndWait(string strFilePath, string strFileMode, string strFileShare, string strFileAccess, bool autoQuit)
{
FileStream stream = null;
try
{
var fileMode = (FileMode)System.Enum.Parse(typeof(FileMode), strFileMode);
var fileShare = (FileShare)System.Enum.Parse(typeof(FileShare), strFileShare);
var fileAccess = (FileAccess)System.Enum.Parse(typeof(FileAccess), strFileAccess);
System.Console.WriteLine(string.Format("[{0}] file:{1}, mode: {2}, share: {3}, access: {4}!"
, System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), strFilePath, strFileMode, strFileShare, strFileAccess));
stream = File.Open(strFilePath, fileMode, fileAccess, fileShare);
}
catch (Exception ex)
{
System.Console.WriteLine(string.Format("opening file [{0}] failed with {1}!", strFilePath, ex));
}
if (!autoQuit)
{
System.Console.WriteLine("press any key to continue...");
System.Console.ReadKey();
}
if (stream != null)
{
stream.Dispose();
}
}
}
}
CreateFile.exe
,该程序可以接收命令行参数,通过-f
指定文件名,通过-a
指定访问权限,通过-s
指定共享模式, 通过-h
显示帮助。五
验证脚本
:: read-readwrite-write-none.bat
CreateFileBatchCaller.bat %~n0%
:: CreateFileBatchCaller.bat
@echo off
cd /d %~dp0
setlocal enabledelayedexpansion
set AccessParams=%1%
set AccessParams=%AccessParams:read=Read%
set AccessParams=%AccessParams:write=Write%
set AccessParams=%AccessParams:none=None%
For /f "tokens=1-4 delims=_/. " %%i In ("%AccessParams%") do (
set Access1=%%i
set SharedAccess1=%%j
set Access2=%%k
set SharedAccess2=%%l
)
start CreateFile -f test.txt -a %Access1% -s %SharedAccess1%
:: will this success?
timeout /T 1
start CreateFile -f test.txt -a %Access2% -s %SharedAccess2%
CreateFileBatchCaller.bat
接收一个参数,内部会根据-
分割参数,前四项有固定意义,分别表示第一次调用CreateFile.exe
的访问权限和共享模式、第二次调用CreateFile.exe
的访问权限和共享模式。read-readwrite-write-none-failed.bat
是众多调用脚本中的一个,内部会把当前脚本的文件名(不包括扩展名)当作参数调用CreateFileBatchCaller.bat
。该脚本可以验证第一次以读访问权限、读写共享模式打开文件,第二次以写访问权限、独占共享模式打开文件的情况。六
亲自动手
七
解惑
答:会成功。
第一次打开时总会成功。
答:会成功。
第一次的共享模式是写,第二次的访问权限是写,第二次的访问权限与第一次的共享模式不冲突。
第二次的共享模式是读,第一次的访问权限是读,第二次的共享模式与第一次的访问权限不冲突。
答:不会成功。
第三次的访问权限是读的话,与第一次的共享模式(写)冲突。
第三次的访问权限是写的话,与第二次的共享模式(读)冲突。
第三次的访问权限是读写的话,既与第一次的共享模式(写)冲突,又与第二次的共享模式(读)冲突。
答:会成功。
第三次的访问权限(写),既不与第一次的共享模式(写)冲突,又不与第二次的共享模式(读写)冲突。
第三次的共享模式(读写),既不与第一次的访问权限(读)冲突,又不与第二次的访问权限(写)冲突。
八
CreateFile 参数
CreateFile
的参数dwDesiredAccess
和dwShareMode
的具体作用不是很清楚,今天重读《windows 核心编程》的时候有了一些新感悟。简要总结如下:◆dwDesiredAccess
表示本次CreateFile
想要获取的权限: 只读(GENERIC_READ
),只写(GENERIC_WRITE
),可读写 (GENERIC_READ | GENERIC_WRITE
)。
◆dwShareMode
表示后续CreateFile
可以取得什么权限。
dwDesiredAccess
各种值及含义抄录如下(摘自 《Windows核心编程》第5
版 第10
章p279
):dwShareMode
的各种值及含义抄录如下(摘自 《Windows核心编程》第5
版 第10
章p279
):
友情提示:上表中的 如果设备已经以 xxx 方式打开
指的是先前调用的dwShareMode
参数。
参考资料
◆https://0cch.com/2011/05/22/e585b3e4ba8ee69687e4bbb6shareaccess/强烈推荐阅读!!!
◆https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
◆https://community.osr.com/discussion/32329/file-object-fscontext9
◆《windows 核心编程》第5
版 第10
章
看雪ID:编程难
https://bbs.kanxue.com/user-home-873494.htm
# 往期推荐
2、Glibc-2.35下对tls_dtor_list的利用详解
球分享
球点赞
球在看
点击阅读原文查看更多
原文始发于微信公众号(看雪学苑):开发常识 | 彻底理清 CreateFile 读写权限与共享模式的关系