Skip to content

Commit

Permalink
Use FILE_FLAG_DELETE_ON_CLOSE for TempFile on windows.
Browse files Browse the repository at this point in the history
We won't see the temp file no more.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@319137 91177308-0d34-0410-b5e6-96231b3b80d8
  • Loading branch information
espindola committed Nov 28, 2017
1 parent 9f2b724 commit d04afd7
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 8 deletions.
3 changes: 1 addition & 2 deletions include/llvm/Support/FileSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -685,8 +685,7 @@ enum OpenFlags : unsigned {
/// Open the file for read and write.
F_RW = 8,

/// The returned handle can be used for deleting the file. Only makes a
/// difference on windows.
/// Delete the file on close. Only makes a difference on windows.
F_Delete = 16
};

Expand Down
25 changes: 22 additions & 3 deletions lib/Support/Path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1068,12 +1068,15 @@ TempFile::~TempFile() { assert(Done); }

Error TempFile::discard() {
Done = true;
// Always try to close and remove.
std::error_code RemoveEC;
// On windows closing will remove the file.
#ifndef LLVM_ON_WIN32
// Always try to close and remove.
if (!TmpName.empty()) {
RemoveEC = fs::remove(TmpName);
sys::DontRemoveFileOnSignal(TmpName);
}
#endif

if (!RemoveEC)
TmpName = "";
Expand All @@ -1091,8 +1094,15 @@ Error TempFile::keep(const Twine &Name) {
assert(!Done);
Done = true;
// Always try to close and rename.
#ifdef LLVM_ON_WIN32
// If we cant't cancel the delete don't rename.
std::error_code RenameEC = cancelDeleteOnClose(FD);
if (!RenameEC)
RenameEC = rename_fd(FD, Name);
#else
std::error_code RenameEC = fs::rename(TmpName, Name);
sys::DontRemoveFileOnSignal(TmpName);
#endif

if (!RenameEC)
TmpName = "";
Expand All @@ -1110,7 +1120,13 @@ Error TempFile::keep() {
assert(!Done);
Done = true;

#ifdef LLVM_ON_WIN32
if (std::error_code EC = cancelDeleteOnClose(FD))
return errorCodeToError(EC);
#else
sys::DontRemoveFileOnSignal(TmpName);
#endif

TmpName = "";

if (close(FD) == -1) {
Expand All @@ -1125,16 +1141,19 @@ Error TempFile::keep() {
Expected<TempFile> TempFile::create(const Twine &Model, unsigned Mode) {
int FD;
SmallString<128> ResultPath;
if (std::error_code EC = createUniqueFile(Model, FD, ResultPath, Mode))
if (std::error_code EC = createUniqueFile(Model, FD, ResultPath, Mode,
sys::fs::F_RW | sys::fs::F_Delete))
return errorCodeToError(EC);

// Make sure we delete the file when RemoveFileOnSignal fails.
TempFile Ret(ResultPath, FD);
#ifndef LLVM_ON_WIN32
if (sys::RemoveFileOnSignal(ResultPath)) {
// Make sure we delete the file when RemoveFileOnSignal fails.
consumeError(Ret.discard());
std::error_code EC(errc::operation_not_permitted);
return errorCodeToError(EC);
}
#endif
return std::move(Ret);
}
}
Expand Down
61 changes: 58 additions & 3 deletions lib/Support/Windows/Path.inc
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,53 @@ std::error_code is_local(int FD, bool &Result) {
return is_local_internal(FinalPath, Result);
}

/// In order to handle temporary files we want the following properties
///
/// * The temporary file is deleted on crashes
/// * We can use (read, rename, etc) the temporary file.
/// * We can cancel the delete to keep the file.
///
/// Using FILE_DISPOSITION_INFO with DeleteFile=true will create a file that is
/// deleted on close, but it has a few problems:
///
/// * The file cannot be used. An attempt to open or rename the file will fail.
/// This makes the temporary file almost useless, as it cannot be part of
/// any other CreateFileW call in the current or in another process.
/// * It is not atomic. A crash just after CreateFileW or just after canceling
/// the delete will leave the file on disk.
///
/// Using FILE_FLAG_DELETE_ON_CLOSE solves the first issues and the first part
/// of the second one, but there is no way to cancel it in place. What works is
/// to create a second handle to prevent the deletion, close the first one and
/// then clear DeleteFile with SetFileInformationByHandle. This requires
/// changing the handle and file descriptor the caller uses.
static std::error_code cancelDeleteOnClose(int &FD) {
HANDLE Handle = reinterpret_cast<HANDLE>(_get_osfhandle(FD));
SmallVector<wchar_t, MAX_PATH> Name;
if (std::error_code EC = realPathFromHandle(Handle, Name))
return EC;
HANDLE NewHandle =
::CreateFileW(Name.data(), GENERIC_READ | GENERIC_WRITE | DELETE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (NewHandle == INVALID_HANDLE_VALUE)
return mapWindowsError(::GetLastError());
if (close(FD))
return mapWindowsError(::GetLastError());

FILE_DISPOSITION_INFO Disposition;
Disposition.DeleteFile = false;
if (!SetFileInformationByHandle(NewHandle, FileDispositionInfo, &Disposition,
sizeof(Disposition)))
return mapWindowsError(::GetLastError());
FD = ::_open_osfhandle(intptr_t(NewHandle), 0);
if (FD == -1) {
::CloseHandle(NewHandle);
return mapWindowsError(ERROR_INVALID_HANDLE);
}
return std::error_code();
}

static std::error_code rename_internal(HANDLE FromHandle, const Twine &To,
bool ReplaceIfExists) {
SmallVector<wchar_t, 0> ToWide;
Expand Down Expand Up @@ -513,6 +560,11 @@ static std::error_code rename_handle(HANDLE FromHandle, const Twine &To) {
return errc::permission_denied;
}

static std::error_code rename_fd(int FromFD, const Twine &To) {
HANDLE FromHandle = reinterpret_cast<HANDLE>(_get_osfhandle(FromFD));
return rename_handle(FromHandle, To);
}

std::error_code rename(const Twine &From, const Twine &To) {
// Convert to utf-16.
SmallVector<wchar_t, 128> WideFrom;
Expand Down Expand Up @@ -1029,15 +1081,18 @@ std::error_code openFileForWrite(const Twine &Name, int &ResultFD,
CreationDisposition = CREATE_ALWAYS;

DWORD Access = GENERIC_WRITE;
DWORD Attributes = FILE_ATTRIBUTE_NORMAL;
if (Flags & F_RW)
Access |= GENERIC_READ;
if (Flags & F_Delete)
if (Flags & F_Delete) {
Access |= DELETE;
Attributes |= FILE_FLAG_DELETE_ON_CLOSE;
}

HANDLE H =
::CreateFileW(PathUTF16.begin(), Access,
::CreateFileW(PathUTF16.data(), Access,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, CreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
NULL, CreationDisposition, Attributes, NULL);

if (H == INVALID_HANDLE_VALUE) {
DWORD LastError = ::GetLastError();
Expand Down

0 comments on commit d04afd7

Please sign in to comment.