]> git.ipfire.org Git - thirdparty/squid.git/blob - src/base/File.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / base / File.cc
1 /*
2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 #include "squid.h"
10 #include "base/File.h"
11 #include "Debug.h"
12 #include "sbuf/Stream.h"
13 #include "tools.h"
14 #include "xusleep.h"
15
16 #include <utility>
17
18 #if HAVE_FCNTL_H
19 #include <fcntl.h>
20 #endif
21 #if HAVE_SYS_STAT_H
22 #include <sys/stat.h>
23 #endif
24 #if HAVE_UNISTD_H
25 #include <unistd.h>
26 #endif
27
28 /* FileOpeningConfig */
29
30 FileOpeningConfig
31 FileOpeningConfig::ReadOnly()
32 {
33 FileOpeningConfig cfg;
34
35 /* I/O */
36 #if _SQUID_WINDOWS_
37 cfg.desiredAccess = GENERIC_READ;
38 cfg.shareMode = FILE_SHARE_READ;
39 #else
40 cfg.openFlags = O_RDONLY;
41 #endif
42
43 /* locking (if enabled later) */
44 #if _SQUID_WINDOWS_
45 cfg.lockFlags = 0; // no named constant for a shared lock
46 #elif _SQUID_SOLARIS_
47 cfg.lockType = F_RDLCK;
48 #else
49 cfg.flockMode = LOCK_SH | LOCK_NB;
50 #endif
51
52 return cfg;
53 }
54
55 FileOpeningConfig
56 FileOpeningConfig::ReadWrite()
57 {
58 FileOpeningConfig cfg;
59
60 /* I/O */
61 #if _SQUID_WINDOWS_
62 cfg.desiredAccess = GENERIC_READ | GENERIC_WRITE;
63 cfg.shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
64 #else
65 cfg.openFlags = O_RDWR;
66 #endif
67
68 /* locking (if enabled later) */
69 #if _SQUID_WINDOWS_
70 cfg.lockFlags = LOCKFILE_EXCLUSIVE_LOCK;
71 #elif _SQUID_SOLARIS_
72 cfg.lockType = F_WRLCK;
73 #else
74 cfg.flockMode = LOCK_EX | LOCK_NB;
75 #endif
76
77 return cfg;
78 }
79
80 FileOpeningConfig &
81 FileOpeningConfig::locked(unsigned int attempts)
82 {
83 lockAttempts = attempts;
84 // for simplicity, correct locking flags are preset in constructing methods
85 return *this;
86 }
87
88 FileOpeningConfig &
89 FileOpeningConfig::createdIfMissing()
90 {
91 #if _SQUID_WINDOWS_
92 Must((desiredAccess & GENERIC_WRITE) == GENERIC_WRITE);
93 creationDisposition = OPEN_ALWAYS;
94 #else
95 Must((openFlags & O_RDWR) == O_RDWR);
96 openFlags |= O_CREAT;
97 creationMask = (S_IXUSR | S_IXGRP|S_IWGRP | S_IXOTH|S_IWOTH); // unwanted bits
98 #endif
99 return *this;
100 }
101
102 /* File */
103
104 #if _SQUID_SOLARIS_
105 // XXX: fcntl() locks are incompatible with complex applications that may lock
106 // multiple open descriptors corresponding to the same underlying file. There is
107 // nothing better on Solaris, but do not be tempted to use this elsewhere. For
108 // more info, see http://bugs.squid-cache.org/show_bug.cgi?id=4212#c14
109 /// fcntl(... struct flock) convenience wrapper
110 int
111 fcntlLock(const int fd, const short lockType)
112 {
113 // the exact composition and order of flock data members is unknown!
114 struct flock fl;
115 memset(&fl, 0, sizeof(fl));
116 fl.l_type = lockType;
117 fl.l_whence = SEEK_SET; // with zero l_len and l_start, means "whole file"
118 return ::fcntl(fd, F_SETLK, &fl);
119 }
120 #endif // _SQUID_SOLARIS_
121
122 File *
123 File::Optional(const SBuf &filename, const FileOpeningConfig &cfg)
124 {
125 try {
126 return new File(filename, cfg);
127 }
128 catch (const std::exception &ex) {
129 debugs(54, 5, "will not lock: " << ex.what());
130 }
131 return nullptr;
132 }
133
134 File::File(const SBuf &aName, const FileOpeningConfig &cfg):
135 name_(aName)
136 {
137 debugs(54, 7, "constructing, this=" << this << ' ' << name_);
138 // close the file during post-open constructor exceptions
139 try {
140 open(cfg);
141 lock(cfg);
142 }
143 catch (...)
144 {
145 close();
146 throw;
147 }
148 }
149
150 File::~File()
151 {
152 debugs(54, 7, "destructing, this=" << this << ' ' << name_);
153 close();
154 }
155
156 File::File(File &&other)
157 {
158 *this = std::move(other);
159 }
160
161 File &
162 File::operator = (File &&other)
163 {
164 std::swap(fd_, other.fd_);
165 return *this;
166 }
167
168 /// opens (or creates) the file
169 void
170 File::open(const FileOpeningConfig &cfg)
171 {
172 #if _SQUID_WINDOWS_
173 fd_ = CreateFile(TEXT(name_.c_str()), cfg.desiredAccess, cfg.shareMode, nullptr, cfg.creationDisposition, FILE_ATTRIBUTE_NORMAL, nullptr);
174 if (fd_ == InvalidHandle) {
175 const auto savedError = GetLastError();
176 throw TexcHere(sysCallFailure("CreateFile", WindowsErrorMessage(savedError)));
177 }
178 #else
179 mode_t oldCreationMask = 0;
180 const auto filename = name_.c_str(); // avoid complex operations inside enter_suid()
181 enter_suid();
182 if (cfg.creationMask)
183 oldCreationMask = umask(cfg.creationMask); // XXX: Why here? Should not this be set for the whole Squid?
184 fd_ = ::open(filename, cfg.openFlags, cfg.openMode);
185 const auto savedErrno = errno;
186 if (cfg.creationMask)
187 umask(oldCreationMask);
188 leave_suid();
189 if (fd_ < 0)
190 throw TexcHere(sysCallError("open", savedErrno));
191 #endif
192 }
193
194 void
195 File::close()
196 {
197 if (!isOpen())
198 return;
199 #if _SQUID_WINDOWS_
200 if (!CloseHandle(fd_)) {
201 const auto savedError = GetLastError();
202 debugs(54, DBG_IMPORTANT, sysCallFailure("CloseHandle", WindowsErrorMessage(savedError)));
203 }
204 #else
205 if (::close(fd_) != 0) {
206 const auto savedErrno = errno;
207 debugs(54, DBG_IMPORTANT, sysCallError("close", savedErrno));
208 }
209 #endif
210 // closing the file handler implicitly removes all associated locks
211 }
212
213 void
214 File::truncate()
215 {
216 #if _SQUID_WINDOWS_
217 if (!SetFilePointer(fd_, 0, nullptr, FILE_BEGIN)) {
218 const auto savedError = GetLastError();
219 throw TexcHere(sysCallFailure("SetFilePointer", WindowsErrorMessage(savedError)));
220 }
221
222 if (!SetEndOfFile(fd_)) {
223 const auto savedError = GetLastError();
224 throw TexcHere(sysCallFailure("SetEndOfFile", WindowsErrorMessage(savedError)));
225 }
226 #else
227 if (::lseek(fd_, SEEK_SET, 0) < 0) {
228 const auto savedErrno = errno;
229 throw TexcHere(sysCallError("lseek", savedErrno));
230 }
231
232 if (::ftruncate(fd_, 0) != 0) {
233 const auto savedErrno = errno;
234 throw TexcHere(sysCallError("ftruncate", savedErrno));
235 }
236 #endif
237 }
238
239 SBuf
240 File::readSmall(const SBuf::size_type minBytes, const SBuf::size_type maxBytes)
241 {
242 SBuf buf;
243 const auto readLimit = maxBytes + 1; // to detect excessively large files that we do not handle
244 char *rawBuf = buf.rawAppendStart(readLimit);
245 #if _SQUID_WINDOWS_
246 DWORD result = 0;
247 if (!ReadFile(fd_, rawBuf, readLimit, &result, nullptr)) {
248 const auto savedError = GetLastError();
249 throw TexcHere(sysCallFailure("ReadFile", WindowsErrorMessage(savedError)));
250 }
251 #else
252 const auto result = ::read(fd_, rawBuf, readLimit);
253 if (result < 0) {
254 const auto savedErrno = errno;
255 throw TexcHere(sysCallError("read", savedErrno));
256 }
257 #endif
258 const auto bytesRead = static_cast<size_t>(result);
259 assert(bytesRead <= readLimit);
260 Must(!buf.length());
261 buf.rawAppendFinish(rawBuf, bytesRead);
262
263 if (buf.length() < minBytes) {
264 static const SBuf errPrematureEof("premature eof");
265 static const SBuf errEmptyFile("empty file");
266 throw TexcHere(sysCallFailure("read", buf.length() ? errPrematureEof : errEmptyFile));
267 }
268
269 if (buf.length() > maxBytes) {
270 static const SBuf failure("unreasonably large file");
271 throw TexcHere(sysCallFailure("read", failure));
272 }
273
274 Must(minBytes <= buf.length() && buf.length() <= maxBytes);
275 return buf;
276 }
277
278 void
279 File::writeAll(const SBuf &data)
280 {
281 #if _SQUID_WINDOWS_
282 DWORD nBytesWritten = 0;
283 if (!WriteFile(fd_, data.rawContent(), data.length(), &nBytesWritten, nullptr)) {
284 const auto savedError = GetLastError();
285 throw TexcHere(sysCallFailure("WriteFile", WindowsErrorMessage(savedError)));
286 }
287 const auto bytesWritten = static_cast<size_t>(nBytesWritten);
288 #else
289 const auto result = ::write(fd_, data.rawContent(), data.length());
290 if (result < 0) {
291 const auto savedErrno = errno;
292 throw TexcHere(sysCallError("write", savedErrno));
293 }
294 const auto bytesWritten = static_cast<size_t>(result);
295 #endif
296 if (bytesWritten != data.length()) {
297 static const SBuf failure("partial write");
298 throw TexcHere(sysCallFailure("write", failure));
299 }
300 }
301
302 void
303 File::synchronize()
304 {
305 #if _SQUID_WINDOWS_
306 if (!FlushFileBuffers(fd_)) {
307 const auto savedError = GetLastError();
308 throw TexcHere(sysCallFailure("FlushFileBuffers", WindowsErrorMessage(savedError)));
309 }
310 #else
311 if (::fsync(fd_) != 0) {
312 const auto savedErrno = errno;
313 throw TexcHere(sysCallError("fsync", savedErrno));
314 }
315 #endif
316 }
317
318 /// calls lockOnce() as many times as necessary (including zero)
319 void
320 File::lock(const FileOpeningConfig &cfg)
321 {
322 unsigned int attemptsLeft = cfg.lockAttempts;
323 while (attemptsLeft) {
324 try {
325 --attemptsLeft;
326 return lockOnce(cfg);
327 } catch (const std::exception &ex) {
328 if (!attemptsLeft)
329 throw;
330 debugs(54, 4, "sleeping and then trying up to " << attemptsLeft <<
331 " more time(s) after a failure: " << ex.what());
332 }
333 Must(attemptsLeft); // the catch statement handles the last attempt
334 xusleep(cfg.RetryGapUsec);
335 }
336 debugs(54, 9, "disabled");
337 }
338
339 /// locks, blocking or returning immediately depending on the lock waiting mode
340 void
341 File::lockOnce(const FileOpeningConfig &cfg)
342 {
343 #if _SQUID_WINDOWS_
344 if (!LockFileEx(fd_, cfg.lockFlags, 0, 0, 1, 0)) {
345 const auto savedError = GetLastError();
346 throw TexcHere(sysCallFailure("LockFileEx", WindowsErrorMessage(savedError)));
347 }
348 #elif _SQUID_SOLARIS_
349 if (fcntlLock(fd_, cfg.lockType) != 0) {
350 const auto savedErrno = errno;
351 throw TexcHere(sysCallError("fcntl(flock)", savedErrno));
352 }
353 #else
354 if (::flock(fd_, cfg.flockMode) != 0) {
355 const auto savedErrno = errno;
356 throw TexcHere(sysCallError("flock", savedErrno));
357 }
358 #endif
359 debugs(54, 3, "succeeded for " << name_);
360 }
361
362 /// \returns a description a system call-related failure
363 SBuf
364 File::sysCallFailure(const char *callName, const SBuf &error) const
365 {
366 return ToSBuf("failed to ", callName, ' ', name_, ": ", error);
367 }
368
369 /// \returns a description of an errno-based system call failure
370 SBuf
371 File::sysCallError(const char *callName, const int savedErrno) const
372 {
373 return sysCallFailure(callName, SBuf(xstrerr(savedErrno)));
374 }
375
376 #if _SQUID_WINDOWS_
377 const HANDLE File::InvalidHandle = INVALID_HANDLE_VALUE;
378 #endif /* _SQUID_WINDOWS_ */
379