【发布时间】:2009-10-17 19:41:47
【问题描述】:
在描述我的问题之前,这里是我正在编写的程序(IHExplorer.exe)的描述:
这是一个 C++ 应用程序。
IHExplorer 应用程序看起来尽可能像 Windows 资源管理器窗口。除了一个例外,从这个资源管理器窗口中启动文件将首先将它们解密到用户的临时文件夹,然后启动与文件扩展名关联的应用程序并在关闭时删除文件。
我遇到的问题是文件关闭时自动删除。这是一个场景:
- 用户在 IHExplorer 中双击加密的 .txt 文件。
IHExplorer 解密内存中的 .txt 文件,然后使用 ::CreateFile 将其写入 %TEMP%,该文件返回文件的 HANDLE(IHExplorer 必须保持此句柄至少打开,直到 .txt 文件被 shell 执行)。
IHExplorer Shell 从其临时位置执行 .txt 文件(通过调用 ::ShellExecute)。
- 现在 IHExplorer 和记事本都有打开文件的句柄。
- 当 IHExplorer 和记事本都关闭了文件的句柄时,必须自动删除文件,即使 IHExplorer 先关闭也是如此。
好的。这是一个基本的用户案例,描述了我想要发生的事情。我遇到的问题是当我 ::ShellExecute() 时,记事本说“该进程无法访问该文件,因为它正在被另一个进程使用。” (这将是 IHExplorer)。我需要解决这个问题并让记事本打开它,即使我仍然在 IHExplorer 中打开了句柄。
我对 ::CreateFile 的调用如下所示:
DWORD dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE;
HANDLE hFile = ::CreateFile(strTempFile.c_str(), GENERIC_WRITE, dwShareMode, &sa, CREATE_NEW, dwFlagsAndAttributes, NULL);
请注意,我使用了 FILE_SHARE_DELETE 以便其他进程(例如记事本)可以使用删除权限打开文件。
请注意,我使用了 FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE 属性表示文件是临时文件,应在关闭时删除。
还要注意 &sa 参数。这是我正在使用的 SECURITY_ATTRIBUTES 结构,我觉得(希望)这就是我的问题所在。又是代码,这次我把整个函数贴出来让大家看看我是如何填写SECURITY_ATTRIBUTES结构的:
int CIHExplorerDoc::OpenFile(std::string strFileName, bool bIsFullPath) {
std::string strFullFilePath;
if(bIsFullPath) {
strFullFilePath = strFileName;
strFileName = IHawk::RemovePath(strFileName);
}else {
strFullFilePath = m_strDirectory + strFileName;
}
if(!HasEncryptionFileExtension(strFullFilePath)) {
LaunchFile(strFullFilePath);
}else {
//it's an encrypted file, so open it and copy unencrypted file to temp.
IHawk::EncryptedFileHandle hEncryptedFile(strFullFilePath.c_str(), true, theApp.GetKeyServer());
if(hEncryptedFile.IsValid()) {
std::string strTempFile = g_strTempFolder + IHawk::ChangeFileExtension(strFileName, "");
//TODO: Determine what the LPSECURITY_ATTRIBUTES should be.
SECURITY_ATTRIBUTES sa;
SECURITY_DESCRIPTOR sd;
if(!InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION)) {
DWORD dwLastError = ::GetLastError();
LOG4CPLUS_ERROR(m_Logger, "Cannot launch file '" << strFullFilePath << "'. Failed to initialize security descriptor. GetLastError=" << dwLastError);
return dwLastError;
}
if(!SetSecurityDescriptorDacl(
&sd, // A pointer to the SECURITY_DESCRIPTOR structure to which the function adds the DACL
TRUE, // presence of a DACL in the security descriptor
NULL, // allows all access to the object
FALSE // DACL has been explicitly specified by a user
))
{
DWORD dwLastError = ::GetLastError();
LOG4CPLUS_ERROR(m_Logger, "Cannot launch file '" << strFullFilePath << "'. Failed to set security descriptor DACL. GetLastError=" << dwLastError);
return dwLastError;
}
if(!SetSecurityDescriptorGroup(
&sd, // A pointer to the SECURITY_DESCRIPTOR structure whose primary group is set by this function
NULL, // no primary group
FALSE // Indicates whether the primary group information was derived from a default mechanism
))
{
DWORD dwLastError = ::GetLastError();
LOG4CPLUS_ERROR(m_Logger, "Cannot launch file '" << strFullFilePath << "'. Failed to set security descriptor primary group. GetLastError=" << dwLastError);
return dwLastError;
}
if(!SetSecurityDescriptorOwner(
&sd, // A pointer to the SECURITY_DESCRIPTOR structure whose owner is set by this function.
NULL, // If this parameter is NULL, the function clears the security descriptor's owner information. This marks the security descriptor as having no owner.
FALSE // Indicates whether the owner information is derived from a default mechanism.
))
{
DWORD dwLastError = ::GetLastError();
LOG4CPLUS_ERROR(m_Logger, "Cannot launch file '" << strFullFilePath << "'. Failed to set security descriptor owner information. GetLastError=" << dwLastError);
return dwLastError;
}
if(!SetSecurityDescriptorSacl(
&sd, // A pointer to the SECURITY_DESCRIPTOR structure to which the function adds the SACL
FALSE, // the security descriptor does not contain a SACL
NULL, // security descriptor has a NULL SACL
FALSE // A pointer to a flag that is set to the value of the SE_SACL_DEFAULTED flag in the SECURITY_DESCRIPTOR_CONTROL structure if a SACL exists for the security descriptor
))
{
DWORD dwLastError = ::GetLastError();
LOG4CPLUS_ERROR(m_Logger, "Cannot launch file '" << strFullFilePath << "'. Failed to set security descriptor SACL. GetLastError=" << dwLastError);
return dwLastError;
}
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = &sd;
sa.bInheritHandle = TRUE;
DWORD dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
// DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE;
HANDLE hFile = ::CreateFile(strTempFile.c_str(), GENERIC_WRITE, dwShareMode, &sa, CREATE_NEW, dwFlagsAndAttributes, NULL);
//verify we created the file.
if(hFile == INVALID_HANDLE_VALUE) {
DWORD dwLastError = ::GetLastError();
return dwLastError;
}
//copy to temp
char buffer[64*1024];
size_t nBytesRead = hEncryptedFile.Read(buffer, sizeof(buffer));
while(nBytesRead) {
DWORD numBytesWritten;
if(!::WriteFile(hFile, buffer, nBytesRead, &numBytesWritten, (LPOVERLAPPED) NULL)) {
DWORD dwLastError = ::GetLastError();
LOG4CPLUS_ERROR(m_Logger, "Failed to write file to %TEMP% folder. GetLastError=" << dwLastError);
return dwLastError;
}
nBytesRead = hEncryptedFile.Read(buffer, sizeof(buffer));
}
hEncryptedFile.Close();
//execute the file from temp.
LaunchFile(strTempFile);
}
}
return 0;
}
我认为,如果我确定要传递给 ::CreateFile 的正确 SECURITY_DESCRIPTOR 可能会像我想要的那样工作。请帮忙。
顺便说一句,LaunchFile 函数最终会调用 ::ShellExecute 来启动文件。
【问题讨论】:
-
在重新阅读 msdn 文档后,恐怕我已经回答了自己的问题。 FILE_FLAG_DELETE_ON_CLOSE 该文件将在其所有句柄关闭后立即删除,其中包括指定的句柄和任何其他打开或重复的句柄。如果文件存在现有的打开句柄,则调用将失败,除非它们都以 FILE_SHARE_DELETE 共享模式打开。除非指定了 FILE_SHARE_DELETE 共享模式,否则对文件的后续打开请求将失败。就我而言,我怀疑记事本是否请求 FILE_SHARE_DELETE 权限,因此无法打开文件。
-
是的...您可能想要获取 FileMon 的副本或来自technet.microsoft.com/en-us/sysinternals/bb896642.aspx 的任何最新替代品。这将显示记事本在打开文件时使用的标志。
-
我编写了一个示例程序,尝试使用 FILE_SHARE_DELETE 打开临时文件并确认确实是问题所在。除非其他进程请求 FILE_SHARE_DELETE 权限,否则它将获得访问冲突。所以现在我需要考虑其他方法来实现这一点。有任何想法吗?大声笑。
-
啊是的...我也可以看看filemon,只是为了进一步确认。
-
在创建文件时省略 FILE_FLAG_DELETE_ON_CLOSE,然后使用 ShellExecuteEx() 而不是 ShellExecute(),这样您就可以获得生成进程的 HANDLE。然后,您的应用可以将该句柄传递给任何 WaitFor...() 函数以等待记事本(或其他)退出,然后再调用 DeleteFile()。
标签: c++ filesystems temporary-files createfile