swift
atomicfile.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2015 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "misc/atomicfile.h"
5 
6 #include <type_traits>
7 
8 #include <QDir>
9 #include <QFileInfo>
10 #include <QFlags>
11 #include <QStringBuilder>
12 
13 #include "misc/algorithm.h"
14 
15 #if defined(Q_OS_POSIX)
16 # include <errno.h>
17 # include <stdio.h>
18 #elif defined(Q_OS_WIN32)
19 # include <Windows.h>
20 # include <io.h>
21 
22 # include <QNtfsPermissionCheckGuard>
23 #endif
24 
25 namespace swift::misc
26 {
28  bool checkPermissions(CAtomicFile::OpenMode mode, const QFileInfo &fileInfo)
29  {
30  bool ok = true;
31  {
32 #if defined(Q_OS_WIN32)
33  QNtfsPermissionCheckGuard permissionGuard;
34 #endif
35  if ((mode & CAtomicFile::ReadOnly) && !fileInfo.isReadable()) { ok = false; }
36  if ((mode & CAtomicFile::WriteOnly) && !fileInfo.isWritable()) { ok = false; }
37  }
38  return ok;
39  }
40 
42  {
43  m_originalFilename = fileName();
44  QFileInfo fileInfo(fileName());
45  if (exists() && !checkPermissions(mode, fileInfo))
46  {
47  m_permissionError = true;
48  setErrorString("Wrong permissions");
49  return false;
50  }
51  setFileName(QFileInfo(fileInfo.dir(), ".tmp." + fileInfo.fileName() + "." + randomSuffix()).filePath());
52  if (exists()) { remove(); }
53 
54  bool ok = true;
55  if (mode & ReadOnly)
56  {
57  if (exists(m_originalFilename) && !copy(m_originalFilename, fileName())) { ok = false; }
58  }
59 
60  if (ok && !QFile::open(mode)) { ok = false; }
61  if (!ok) { setFileName(m_originalFilename); }
62  return ok;
63  }
64 
66  {
67  if (std::uncaught_exceptions() > 0) { QFile::close(); }
68  }
69 
71  {
72  if (!isOpen()) { return; }
73 
74 #if defined(Q_OS_WIN32)
75  FlushFileBuffers(reinterpret_cast<HANDLE>(_get_osfhandle(handle())));
76 #endif
77 
78  QFile::close();
79 
80  if (error() == NoError) { replaceOriginal(); }
81  setFileName(m_originalFilename);
82  }
83 
85  {
86  close();
87  return error() == NoError;
88  }
89 
91  {
92  if (!isOpen()) { return; }
93 
94  QFile::close();
95  remove();
96  setFileName(m_originalFilename);
97  }
98 
100  {
101  if (m_renameError) { return RenameError; }
102  if (m_permissionError) { return PermissionsError; }
103  return QFile::error();
104  }
105 
107  {
108  m_renameError = false;
109  m_permissionError = false;
111  }
112 
113  QString CAtomicFile::randomSuffix()
114  {
115  constexpr auto max = 2176782335;
116  return QStringLiteral("%1").arg(
117  std::uniform_int_distribution<std::decay_t<decltype(max)>>(0, max)(private_ns::defaultRandomGenerator()), 6,
118  36, QChar('0'));
119  }
120 
121 #if defined(Q_OS_POSIX)
122  void CAtomicFile::replaceOriginal()
123  {
124  auto result = ::rename(qPrintable(fileName()), qPrintable(m_originalFilename));
125  if (result < 0)
126  {
127  m_renameError = true;
128  char s[1024] {};
129  auto x = strerror_r(errno, s, sizeof(s));
131  static_assert(std::is_same_v<decltype(x), int>,
132  "Non-standard signature of POSIX function strerror_r, check documentation.");
133  }
134  }
135 #elif defined(Q_OS_WIN32)
136  void CAtomicFile::replaceOriginal()
137  {
138  auto encode = [](const QString &s) {
139  const auto prefix =
140  "\\\\?\\"; // support long paths:
141  // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx#maxpath
142  return (prefix + QDir::toNativeSeparators(QDir::cleanPath(QFileInfo(s).absoluteFilePath()))).toStdWString();
143  };
144  auto replace = exists(m_originalFilename);
145  auto result = replace ? ReplaceFile(encode(m_originalFilename).c_str(), encode(fileName()).c_str(), nullptr,
146  REPLACEFILE_IGNORE_MERGE_ERRORS, nullptr, nullptr) :
147  MoveFileEx(encode(fileName()).c_str(), encode(m_originalFilename).c_str(),
148  MOVEFILE_WRITE_THROUGH);
149  if (!result)
150  {
151  wchar_t *s = nullptr;
152  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), 0,
153  reinterpret_cast<LPWSTR>(&s), 0, nullptr);
154  const QString windowsError =
155  (replace ? u"ReplaceFile: " : u"MoveFileEx: ") % QString::fromWCharArray(s).simplified();
156  LocalFree(reinterpret_cast<HLOCAL>(s));
157 
158  // fall back to non-atomic remove-and-rename
159  if (exists(m_originalFilename))
160  {
161  QFile old(m_originalFilename);
162  if (!old.remove())
163  {
164  // fall back failed, so report the reasons for the original failure AND the fall back failure
165  m_renameError = true;
166  setErrorString(windowsError % u" QFile::remove: " % old.errorString());
167  return;
168  }
169  }
170  rename(m_originalFilename);
171  }
172  }
173 #else
174  void CAtomicFile::replaceOriginal()
175  {
176  if (exists(m_originalFilename)) { remove(m_originalFilename); }
177  rename(m_originalFilename);
178  }
179 #endif
180 
181 } // namespace swift::misc
virtual bool open(OpenMode mode)
Definition: atomicfile.cpp:41
FileError error() const
Definition: atomicfile.cpp:99
virtual void close()
Definition: atomicfile.cpp:70
bool checkedClose()
Calls close() and returns false if there was an error at any stage.
Definition: atomicfile.cpp:84
void abandon()
Closes the file without renaming it.
Definition: atomicfile.cpp:90
Free functions in swift::misc.
QString cleanPath(const QString &path)
QString toNativeSeparators(const QString &pathName)
bool copy(const QString &fileName, const QString &newName)
bool exists() const const
virtual QString fileName() const const override
bool open(FILE *fh, QIODeviceBase::OpenMode mode, QFileDevice::FileHandleFlags handleFlags)
bool remove()
bool rename(const QString &newName)
void setFileName(const QString &name)
virtual void close() override
QFileDevice::FileError error() const const
int handle() const const
void unsetError()
QDir dir() const const
QString fileName() const const
bool isReadable() const const
bool isWritable() const const
bool isOpen() const const
void setErrorString(const QString &str)
typedef OpenMode
QString arg(Args &&... args) const const
QString fromLocal8Bit(QByteArrayView str)
QString fromWCharArray(const wchar_t *string, qsizetype size)
QString simplified() const const