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

Posted

技术标签:

【中文标题】如何使用 Delphi 测试目录是不是可写?【英文标题】:How can I use Delphi to test if a Directory is writeable?如何使用 Delphi 测试目录是否可写? 【发布时间】:2011-04-05 16:23:33 【问题描述】:

目前我使用这个函数,基于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 库中可用?

【问题讨论】:

代码不适合你吗?你不喜欢这种方法吗?这确实是一种非常简单(最简单?)的目录写访问测试方法。虽然我从来没有在 Windows 安全方面做过很多工作,但我想另一种方法是使用 GetFileSecurity 函数。 我从来都不喜欢这样的功能。根据目录是否可写,您将做什么?你为什么不试着去做,如果失败了就优雅地处理它。 我的意思是,实际上没有任何需要像这样的通用函数。相反,您应该尝试编写您需要的任何文件,如果出现问题,那么如果此函数返回 false,则执行您将执行的任何操作。例如,如果您需要写入的文件被另一个进程锁定了怎么办?这个函数不会捕获这样的东西。 @Luke:当创建文件失败时,记录有关环境的额外信息会很有用。部分原因可能是目标文件夹是否已找到且可写。另一部分可能是(我们在日志中经常做的事情)列出所有具有我们无法访问的文件的句柄的进程......所以这个函数确实有其自身的优点。此外,我们经常在写入失败后使用它来检测只读文件夹并尝试将其更改为可写文件夹,以便我们可以重试写入。是的,我们可以盲目地尝试使其可写,但我们的日志现在信息量更大。 @Luke:这是一个可能的场景。可以用作“便携式”的应用程序,即从可移动驱动器运行。我想做的第一件事是检查我是否可以编写更改的数据和配置。如果出于某种原因,我从 CD-ROM 或写保护的笔式驱动器运行,那么我会设置一个标志以防止任何写操作尝试,因为在这种情况下,它不是错误或引起关注.否则,用户可能会收到大量“拒绝访问”错误消息,并有理由感到恼火。 【参考方案1】:

安德烈亚斯...

使用安全 API 获取文件/目录的有效权限是 PIA 的一团糟,而且不可靠。 (我为此倾倒了所有代码,而只是检查是否可以在目录中写入文件。)

参考,http://www.ureader.com/msg/16591730.aspx

(我有其他参考资料,但我是新用户,只能发布一个链接。只需按照上面链接中给出的网址进行操作即可。)

【讨论】:

【参考方案2】:

实际上写入目录是确定目录是否可写的最简单方法。有太多的安全选项可供单独检查,即便如此,您也可能会遗漏一些东西。

您还需要在调用DeleteFile() 之前关闭打开的句柄。由于您使用的是FILE_FLAG_DELETE_ON_CLOSE 标志,因此您无论如何都不需要调用它。

顺便说一句,您的代码中有一个小错误。您正在创建一个临时的String 并将其分配给PWideChar,但String 在实际使用PWideChar 之前超出范围,释放了内存。您的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,但这当然只是个人喜好问题。) 临时字符串没有超出范围。临时的范围与函数中的所有其他内容相同。只有在函数退出时,或者需要重新使用临时文件来保存另一个临时字符串时,它才会被销毁。 但是你可能必须使用“随机”文件名,因为如果检查目录中已经存在文件 chk.tmp,函数返回 FALSE。 没有必要使用“随机”文件名。如果您确实想写入目录,请检查文件名是否存在。如果是,请尝试打开文件,如果不是,请使用给定的文件名创建它。如果您只想检查,可以使用任何文件名。 @stackmik CREATE_NEW 检查是否存在。如果文件已经存在,CreateFile() 将失败并显示ERROR_FILE_EXISTS【参考方案3】:

当然,您需要做的就是验证您对目录的访问权限。这有什么问题:

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;

【讨论】:

【参考方案4】:

这是我使用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;

【讨论】:

我喜欢这个。但请注意,能够创建文件和之后能够删除它是两件不同的事情。 GetTempFileName() 成功而 DeleteFile() 失败是有可能的,尽管很少见

以上是关于如何使用 Delphi 测试目录是不是可写?的主要内容,如果未能解决你的问题,请参考以下文章

如何使缓冲区在 spacemacs 中可写?

如何使 Dlookup 文本框可写

如何在 Delphi 中测试泛型类型变量是不是与 Default(T) 相等?

如何在Delphi中使用Pipeline模式

delphi如何删除目录和目录下的所有文件

delphi 如何改变主窗体