0

So basically, I'm listing all of the files in my temp directory, and then deleting them. Obviously, some of the files are in use and the program itself is using the files that it's deleting. I tried fiddling around with SHFileOperation with no success. (tbh I got no idea how to use it). How do I make it check if the file is in use by another program before deleting it? Thanks!

This is giving me the error: fs::remove_all(entry.path());

Code:

#include <iostream>
#include <Windows.h>
#include <filesystem>
#include <lmcons.h>
#include <fileapi.h>
#include "cColors.h"
using namespace std;

namespace fs = filesystem;
char type;

int main()
{
    SetConsoleTextAttribute(h, 15);

    while (true)
    {
        cout << "[~] Are you sure you want to run Windows Cleaner? [Y/N]";
        cin >> type;

        if (type == 'n')
        {
            break;
            exit(0);
        }
        else if (type == 'y')
        {
            cout << "[#] Cleaning temp directory\n";

            for (const auto& entry : fs::directory_iterator(fs::temp_directory_path()))
            {
                cout << "[#] Deleting " << entry.path();
                fs::remove_all(entry.path()); //This is giving me the error
            }       
        }
        else
        {
            break;
            exit(0);
        }
    }
}
Ken White
  • 123,280
  • 14
  • 225
  • 444
shook_one
  • 315
  • 1
  • 4
  • 11
  • Possible duplicate of [How to check if a file has been opened by another application in C++?](https://stackoverflow.com/questions/1048592/how-to-check-if-a-file-has-been-opened-by-another-application-in-c) –  Nov 27 '19 at 22:00
  • 1
    If files are already open you usually can't delete them anyway. – Jonathan Potter Nov 27 '19 at 22:22
  • 1
    Maybe you could use [CreateFile](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea) with _desired access_ `0` and _share mode_ `OPEN_EXISTING`. Then you have to check the failing reason. If it doesn't fail; close and delete. – urbanSoft Nov 27 '19 at 22:23
  • [How do I find out which process has a file open?](https://devblogs.microsoft.com/oldnewthing/20120217-00/?p=8283) But really, that just introduces another TOCTTOU race. Why not just attempt to delete it? The system already does what you're trying to replicate. – IInspectable Nov 27 '19 at 22:25
  • @JonathanPotter you can delete a file that is in use (rights permitting), it just won't actually be deleted until all open handles to it have been closed. – Remy Lebeau Nov 28 '19 at 02:03
  • @urbanSoft that is the way to go. You could also delete the file before closing your exclusive handle to it, or use `NtSetInformationFile(FileDispositionInformation)`, to avoid a race condition where somebody else could open the file after it is closed and before it is deleted. – Remy Lebeau Nov 28 '19 at 02:07
  • Honestly, you can just write a batch file that does something like `rd %temp%\* /s`. Files and directories that are in use won't be deleted. Mission accomplished with a one-line script. – Carey Gregory Nov 28 '19 at 06:03

2 Answers2

1

Catch the exception Just ignore the error and continue. Or use the second form and pass an error_code argument. Than you get no exception and you can check the reason why it failed.

If the file is in use you get an error. So you can't delete it. If you have no rights you can't delete it too.

Checking the usage first is a race condition. After the check, the file might get closed and you may be able to delete it safely. There is no difference in checking first and than delete it or trying to delete it and it fails.

xMRi
  • 14,982
  • 3
  • 26
  • 59
  • 1
    A file being in use doesn't mean that you cannot call `DeleteFile` on it. If all open handles to the file have passed the `FILE_SHARE_DELETE` share mode, an attempt to delete the file will not result in a `ERROR_SHARING_VIOLATION` error. – IInspectable Nov 28 '19 at 10:15
  • @IInspectable if they have, they presumably don't care if someone deletes it. – Caleth Dec 04 '19 at 13:06
  • @cal: I was commenting solely on the inaccuracy of the statement. – IInspectable Dec 04 '19 at 15:16
1

Here's what I came up with. A recursive delete function which uses CreateFile. My original comment

Maybe you could use CreateFile with desired access 0 and share mode OPEN_EXISTING. Then you have to check the failing reason. If it doesn't fail; close and delete.

wasn't 100% correct. dwDesiredAccess should be 0, dwShareMode should be FILE_SHARE_DELETE, dwCreationDisposition should be OPEN_EXISTING and dwFlagsAndAttributes should be FILE_FLAG_DELETE_ON_CLOSE. If then a valid handle is received the last CloseHandle on the file will lead to deletion (see here).

Here's an example:

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <tchar.h>
#include <iostream>
#include <filesystem>

#ifdef _UNICODE
auto& cout = std::wcout;
#else
using std::cout;
#endif // _UNICODE
using std::endl;
namespace fs=std::filesystem;

void deleteRecursive(const fs::path& path);
void tryDeleteFile(const fs::path& path);

int main(int argc, char* argv[])
{
    TCHAR tempDir[255];
    GetEnvironmentVariable(_T("TEMP"), tempDir, 255);
    deleteRecursive(fs::path(tempDir));
    return 0;
}

void deleteRecursive(const fs::path& path)
{
    fs::directory_iterator dirs(path);
    for (const auto& entry : dirs)
    {
        const auto& path = entry.path();
        if (entry.is_directory())
        {
            deleteRecursive(path);
            if (fs::is_empty(path))
            {
                if (!RemoveDirectory(path.c_str()))
                {
                    cout << _T("Can't delete dir: ") << path << endl;
                }
            }
        }
        else
        {
            tryDeleteFile(path);
        }
    }
}

void tryDeleteFile(const fs::path& path)
{
    const auto file = path.c_str();
    HANDLE fileHandle = CreateFile(
        file,                      // lpFileName,
        0,                         // dwDesiredAccess,
        FILE_SHARE_DELETE,         // dwShareMode,
        NULL,                      // lpSecurityAttributes,
        OPEN_EXISTING,             // dwCreationDisposition,
        FILE_FLAG_DELETE_ON_CLOSE, // dwFlagsAndAttributes,
        NULL                       // hTemplateFile
    );
    if (fileHandle == INVALID_HANDLE_VALUE)
    {
        DWORD lastErr = GetLastError();
        if (lastErr != ERROR_FILE_NOT_FOUND) // gone in the mean time
        {
            cout << _T("Can't delete file: ") << file << endl;
        }
    }
    else
    {
        CloseHandle(fileHandle);
    }
}

tryDeleteFile contains the crucial part.

urbanSoft
  • 686
  • 6
  • 14