Skip to content
WindowsDeep Dive Published Updated 5 min readViews unavailable

Windows FileDispositionInfo: Mark a Temporary File for Deletion by Handle

Use a delete-capable file handle, check the disposition call, and understand why deletion waits for open handles to close.

Some temporary files need a lifetime tied to an open handle rather than to a later pathname cleanup command. Win32 supports that contract through SetFileInformationByHandle with FileDispositionInfo. The caller marks the opened file for deletion; in the documented example, the file is deleted when the handle is closed. This is a narrow handle-lifetime mechanism, not a general-purpose directory cleanup routine and not a promise that another process can immediately reuse the name. Microsoft’s API example shows the required DELETE access and close sequence.

The FILE_DISPOSITION_INFO structure has one Boolean field, DeleteFile. Passing TRUE asks for deletion; passing FALSE clears that request where allowed. Microsoft says the field has no effect if the handle was opened with FILE_FLAG_DELETE_ON_CLOSE. The structure and FileDispositionInfo class are available from Windows Vista and Windows Server 2008 for desktop applications, according to the structure reference. This article deliberately stays with that classic contract. It does not assume the extended POSIX-style disposition class is available or behaves identically on every filesystem and remote server.

Decide ownership before opening the file

The file should be one the application creates and owns, ideally in a private temporary directory. A caller must not accept an untrusted arbitrary path and call a deletion API on it simply because the name contains “temp.” Determine whether the file is new or pre-existing, who else may hold it open, and whether it is needed for recovery after a crash. On a real workflow, the privilege boundary is the handle’s access right, not the spelling of its path. Microsoft’s file-security reference lists DELETE as an access right for file and directory objects.

For a disposable test, create an empty file with CREATE_NEW so an existing path is never overwritten. A fixed filename is unsafe in a shared directory; the fragment below assumes path was generated by the application inside its own test directory and checked for collisions. The two important CreateFileW arguments are DELETE in desired access and share flags chosen for the application’s actual concurrency contract:

#include <windows.h>
#include <stdio.h>

int mark_new_test_file_for_delete(const wchar_t *path)
{
    HANDLE file = CreateFileW(path, DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE |
                              FILE_SHARE_DELETE, NULL, CREATE_NEW,
                              FILE_ATTRIBUTE_TEMPORARY, NULL);
    if (file == INVALID_HANDLE_VALUE) {
        fwprintf(stderr, L"CreateFileW failed: %lu\n", GetLastError());
        return 1;
    }

    FILE_DISPOSITION_INFO disposition = { TRUE };
    BOOL marked = SetFileInformationByHandle(file, FileDispositionInfo,
                                              &disposition, sizeof(disposition));
    DWORD error = marked ? ERROR_SUCCESS : GetLastError();
    if (!CloseHandle(file)) {
        fwprintf(stderr, L"CloseHandle failed: %lu\n", GetLastError());
        return 1;
    }
    if (!marked) {
        fwprintf(stderr, L"mark-for-delete failed: %lu\n", error);
        return 1;
    }
    return 0;
}

This is a helper, not a complete program: it needs a validated unique test path and a caller. It creates and removes only that newly created file. The share flags permit compatible future opens, but they cannot retroactively change an existing handle opened by another process with restrictive sharing. CREATE_NEW fails when the name already exists; that is safer than truncating a user’s file. The Win32 CreateFile documentation defines creation and sharing behavior, while the disposition example establishes that a deletion request needs a handle opened with DELETE access.

Check both operations separately. SetFileInformationByHandle returns nonzero on success and zero on failure, with GetLastError() supplying extended error information. Capturing the error immediately matters because the later CloseHandle call can change the thread’s last-error value. If marking fails, closing the handle still releases the resource, but the helper must not claim that the file was deleted. The application should follow its own cleanup policy for an unmarked test file, not blindly unlink a pathname after an unknown failure.

Treat delete-pending as a state, not instant disappearance

Microsoft’s closing-and-deleting guidance says a file cannot be deleted until all handles to it are closed, and its name may not be reusable while deletion remains pending. That is the key difference from assuming a POSIX unlink operation: an application that marks a file and then immediately creates a new one at the same name may encounter a sharing or name-in-use problem. A second process can also prevent the original open or deletion request if its share mode is incompatible. Diagnose the error rather than retrying in an unbounded loop.

FILE_FLAG_DELETE_ON_CLOSE is a separate way to request close-time deletion when opening a file. If that flag was used, the FILE_DISPOSITION_INFO.DeleteFile field has no effect according to the structure documentation. Choose one lifecycle mechanism and document it; mixing them complicates a cancellation or rollback story. Do not infer that a successful mark irrevocably destroys bytes immediately. Existing handles and the filesystem’s deletion rules still matter.

This handle-bound API also differs from replacing a published file. A robust publish operation stages new contents and uses an appropriate rename or replacement contract; marking the old file delete-pending first can break readers and impede name reuse. Likewise, a service that keeps its temporary handle alive indefinitely will keep the cleanup pending. In review, trace every exit path, including error, cancellation, and process termination, and make sure the owning handle is eventually closed. Microsoft’s CloseHandle guidance notes that the system closes remaining handles at process exit, but depending on termination for ordinary cleanup leaves error reporting and ordering unclear.

Verify only on disposable data

Run a test in an application-owned scratch directory, with a newly generated filename. Log whether creation, marking, and close succeed. Then inspect whether a fresh open by name succeeds or reports the expected absence, without treating one observed error code as a universal cross-filesystem guarantee. Repeat with a second handle deliberately left open only in a controlled test to observe the pending-lifetime boundary; close it before finishing. Avoid tests on system files, shared profiles, or another application’s cache.

The precise contract is modest and useful: acquire a handle with deletion rights, mark that specific opened object, check the result, and close every relevant handle. It solves lifetime-based cleanup for files an application genuinely owns. It does not grant deletion rights, override other processes’ sharing, or make pathname reuse instantaneous.

Related:

Sources:

Comments