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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors