如何使用 Delphi 测试目录是否可写?

11

目前我使用这个函数,基于JCL代码,它能够很好地工作:

function IsDirectoryWriteable(const AName: string): Boolean;
var
  FileName: PWideChar;
  H: THandle;
begin
  FileName := PWideChar(IncludeTrailingPathDelimiter(AName) + 'chk.tmp');

  H := CreateFile(FileName, GENERIC_READ or GENERIC_WRITE, 0, nil,
    CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY or FILE_FLAG_DELETE_ON_CLOSE, 0);

  Result := H <> INVALID_HANDLE_VALUE;

  DeleteFile(FileName);
end;

关于这些标志,我能做些什么来改进它们吗?测试是否可以在不实际创建文件的情况下完成?或者这个功能已经在RTL或Jedi库中可用了吗?


4
你的代码不能正常工作吗?你对这种方法有什么不喜欢的地方吗?这确实是一种非常简单(甚至是最简单的?)测试目录写入权限的方法。虽然我没有在 Windows 安全方面做过太多工作,但我猜另一种方法是使用 GetFileSecurity 函数。 - Andreas Rejbrand
2
我从来不喜欢这样的函数。你会根据目录是否可写来做什么?为什么不直接尝试,如果失败了就优雅地处理它。 - Luke
1
我的观点是,实际上没有必要使用这样的通用函数。相反,你应该尝试编写你需要的任何文件,如果出现错误,则执行与此函数返回false时相同的操作。例如,如果你需要写入的文件被另一个进程锁定了怎么办?这个函数无法捕获这样的情况。 - Luke
1
@Luke:当创建文件失败时,记录有关环境的额外信息可能很有用。其中一部分可以是目标文件夹是否可写和是否找到。另一部分可以是(我们在日志中经常做的事情)列出所有具有对我们无法访问的文件的句柄的进程...因此,这个函数本身就有其优点。此外,我们经常在写入失败后使用它来检测只读文件夹并尝试将其更改为可写的文件夹,以便我们可以重试写入。是的,我们可以盲目地尝试使其可写,但现在我们的日志更加详细了。 - Marjan Venema
2
@Luke:这是一个可能的情况。一个可以作为“便携式”应用程序使用的应用,即可以从可移动驱动器上运行。我想首先要做的事情是检查是否能够写入更改的数据和配置。如果由于某种原因我是从CD-ROM或者只读的U盘上运行,那么我会设置一个标志位来阻止任何写操作的尝试,因为在这种情况下,这不是一个错误或值得关注的原因。否则用户可能会遭受大量的"访问被拒绝"错误消息,并且感到合理地恼怒。 - Marek Jedliński
显示剩余7条评论
4个回答

21

实际上,向目录写入文件是确定该目录是否可写的最简单方法。有太多安全选项可供检查,即使您逐个检查也可能会漏掉某些内容。

在调用DeleteFile()之前需要关闭已打开的句柄。但由于您正在使用FILE_FLAG_DELETE_ON_CLOSE标记,因此不需要调用DeleteFile()

顺便说一下,你的代码中存在一个小错误。你正在创建一个临时的String并将其分配给PWideChar,但这个StringPWideChar被实际使用之前就超出了其作用域,从而释放了内存。你的FileName变量应该是一个String而不是一个PWideChar。在调用CreateFile()时进行类型转换,而不是之前。

请尝试以下代码:

function IsDirectoryWriteable(const AName: string): Boolean; 
var 
  FileName: String; 
  H: THandle; 
begin 
  FileName := IncludeTrailingPathDelimiter(AName) + 'chk.tmp'; 
  H := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, 
    CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY or FILE_FLAG_DELETE_ON_CLOSE, 0); 
  Result := H <> INVALID_HANDLE_VALUE; 
  if Result then CloseHandle(H);
end;

+1(个人而言,我会使用HFILE而不是THandle,但当然这只是品味问题。) - Andreas Rejbrand
2
临时字符串不会超出作用域。临时变量的作用域与函数中的其他所有内容相同。只有在函数退出或需要重新使用临时字符串来保存另一个临时字符串时,才会销毁它。 - Rob Kennedy
2
但是你可能需要使用“随机”文件名,因为如果在检查的目录中已经存在chk.tmp文件,该函数将返回FALSE。 - Peter
@stackmik CREATE_NEW 检查文件是否存在。如果文件已经存在,CreateFile() 将失败并返回 ERROR_FILE_EXISTS - Remy Lebeau
@RemyLebeau 对不起,Remy,让你久等了。我的意思是,只需选择任何文件名并检查其是否存在。如果存在,则使用OPEN_EXISTING,否则使用CREATE_NEW。 - stackmik
显示剩余3条评论

5

这是我使用GetTempFileName的版本,它将尝试在目标目录中创建一个唯一的临时文件:

function IsDirecoryWriteable(const AName: string): Boolean;
var
  TempFileName: array[0..MAX_PATH] of Char;
begin
  { attempt to create a temp file in the directory }
  Result := GetTempFileName(PChar(AName), '$', 0, TempFileName) <> 0;
  if Result then
    { clean up }
    Result := DeleteFile(TempFileName);
end;

1
我喜欢这个。但请注意,能够创建文件和能够在之后删除它是两回事。虽然罕见,但 GetTempFileName() 可能会成功,而 DeleteFile() 却失败。 - Remy Lebeau

2

使用安全API获取文件/目录的有效权限是一件非常麻烦且不可靠的事情。我已经放弃了所有相关代码,转而检查是否可以在目录中写入文件。

参见:http://www.ureader.com/msg/16591730.aspx

(我有其他参考资料,但我是一个新用户,只能发布一个链接。请跟随上面链接中给出的URLS。)


1
当然,您所需要做的就是验证您对目录的访问权限。这有什么问题吗:
function IsDirectoryWriteable(aName : String);
var
  FileObject : TJwSecureFileObject;
  DesiredAccess: ACCESS_MASK;
begin
  DesiredAccess := FILE_GENERIC_WRITE;
  FileObject := TJwSecureFileObject.Create(aName);
  try
    result := FileObject.AccessCheck(DesiredAccess);
  finally
    FileObject.Free;
  end;
end;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接