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