summaryrefslogtreecommitdiff
path: root/zen/file_update_handle.h
blob: aa9edebdc214f1b5c63e8d514ab52395c19bf989 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#ifndef FILE_UPDATE_HANDLE_H_INCLUDED
#define FILE_UPDATE_HANDLE_H_INCLUDED

#include "win.h" //includes "windows.h"
#include "long_path_prefix.h"

namespace
{
//manage file handle to update existing files (temporarily resetting read-only if necessary)
//CreateFileCmd: lambda directly returning non-owned file handle from ::CreateFile()
class FileUpdateHandle
{
public:
    template <class CreateFileCmd>
    FileUpdateHandle(const Zstring& filename, CreateFileCmd cmd) :
        filenameFmt(zen::applyLongPathPrefix(filename)),
        hFile(INVALID_HANDLE_VALUE),
        attr(INVALID_FILE_ATTRIBUTES)
    {
        hFile = cmd();
        if (hFile != INVALID_HANDLE_VALUE)
            return;

        const DWORD lastError = ::GetLastError();
        if (lastError == ERROR_ACCESS_DENIED) //function fails if file is read-only
        {
            zen::ScopeGuard guardErrorCode = zen::makeGuard([&]() { ::SetLastError(lastError); }); //transactional behavior: ensure cleanup (e.g. network drop) -> cref [!]

            //read-only file attribute may cause trouble: temporarily reset it
            const DWORD tmpAttr = ::GetFileAttributes(filenameFmt.c_str());
            if (tmpAttr != INVALID_FILE_ATTRIBUTES && (tmpAttr & FILE_ATTRIBUTE_READONLY))
            {
                if (::SetFileAttributes(filenameFmt.c_str(), FILE_ATTRIBUTE_NORMAL))
                {
                    guardErrorCode.dismiss();
                    attr = tmpAttr; //"create" guard on read-only attribute

                    //now try again
                    hFile = cmd();
                }
            }
        }
    }

    ~FileUpdateHandle()
    {
        if (hFile != INVALID_HANDLE_VALUE)
            ::CloseHandle(hFile);

        if (attr != INVALID_FILE_ATTRIBUTES)
            ::SetFileAttributes(filenameFmt.c_str(), attr);
    }

    //may return INVALID_FILE_ATTRIBUTES, in which case ::GetLastError() may be called directly after FileUpdateHandle()
    HANDLE get() const { return hFile; }

private:
    FileUpdateHandle(const FileUpdateHandle&);
    FileUpdateHandle& operator=(const FileUpdateHandle&);

    Zstring filenameFmt;
    HANDLE hFile;
    DWORD attr;
};
}

#endif // FILE_UPDATE_HANDLE_H_INCLUDED
bgstack15