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_ || _SQUID_MINGW_
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_ || _SQUID_MINGW_
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_ || _SQUID_MINGW_
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_ || _SQUID_MINGW_
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 
82 FileOpeningConfig::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_ || _SQUID_MINGW_
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
111 static int
112 fcntlLock(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 
123 File *
124 File::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 
135 File::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 
157 File::File(File &&other)
158 {
159  *this = std::move(other);
160 }
161 
162 File &
164 {
165  std::swap(fd_, other.fd_);
166  return *this;
167 }
168 
170 void
172 {
173 #if _SQUID_WINDOWS_ || _SQUID_MINGW_
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 
195 void
197 {
198  if (!isOpen())
199  return;
200 #if _SQUID_WINDOWS_ || _SQUID_MINGW_
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 
214 void
216 {
217 #if _SQUID_WINDOWS_ || _SQUID_MINGW_
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 
240 SBuf
241 File::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_ || _SQUID_MINGW_
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 
279 void
280 File::writeAll(const SBuf &data)
281 {
282 #if _SQUID_WINDOWS_ || _SQUID_MINGW_
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 
303 void
305 {
306 #if _SQUID_WINDOWS_ || _SQUID_MINGW_
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 
320 void
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 
341 void
343 {
344 #if _SQUID_WINDOWS_ || _SQUID_MINGW_
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 
364 SBuf
365 File::sysCallFailure(const char *callName, const SBuf &error) const
366 {
367  return ToSBuf("failed to ", callName, ' ', name_, ": ", error);
368 }
369 
371 SBuf
372 File::sysCallError(const char *callName, const int savedErrno) const
373 {
374  return sysCallFailure(callName, SBuf(xstrerr(savedErrno)));
375 }
376 
377 #if _SQUID_WINDOWS_ || _SQUID_MINGW_
378 const HANDLE File::InvalidHandle = INVALID_HANDLE_VALUE;
379 #endif /* _SQUID_WINDOWS_ || _SQUID_MINGW_*/
380 
const char * xstrerr(int error)
Definition: xstrerror.cc:83
static FileOpeningConfig ReadOnly()
Definition: File.cc:32
unsigned int lockAttempts
how many times to try locking
Definition: File.h:61
void open(const FileOpeningConfig &cfg)
opens (or creates) the file
Definition: File.cc:171
~File()
closes
Definition: File.cc:151
bool isOpen() const
Definition: File.h:94
void error(char *format,...)
mode_t openMode
access mode; 3rd open(2) parameter
Definition: File.h:49
Definition: SBuf.h:93
void rawAppendFinish(const char *start, size_type actualSize)
Definition: SBuf.cc:144
static File * Optional(const SBuf &aName, const FileOpeningConfig &cfg)
Definition: File.cc:124
const unsigned int retryGapUsec
pause before each lock retry
Definition: File.h:60
FileOpeningConfig & locked(unsigned int attempts=5)
protect concurrent accesses by attempting to obtain an appropriate lock
Definition: File.cc:82
#define TexcHere(msg)
legacy convenience macro; it is not difficult to type Here() now
Definition: TextException.h:63
a portable locking-aware exception-friendly file (with RAII API)
Definition: File.h:66
void synchronize()
fsync(2)
Definition: File.cc:304
char * rawAppendStart(size_type anticipatedSize)
Definition: SBuf.cc:136
void close()
Definition: File.cc:196
static const Handle InvalidHandle
Definition: File.h:121
void leave_suid(void)
Definition: tools.cc:559
const char * rawContent() const
Definition: SBuf.cc:509
MemBlob::size_type size_type
Definition: SBuf.h:96
File(const SBuf &aFilename, const FileOpeningConfig &cfg)
opens
Definition: File.cc:135
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
#define assert(EX)
Definition: assert.h:17
int openFlags
opening flags; 2nd open(2) parameter
Definition: File.h:48
const char * c_str()
Definition: SBuf.cc:516
void lockOnce(const FileOpeningConfig &cfg)
locks, blocking or returning immediately depending on the lock waiting mode
Definition: File.cc:342
size_type length() const
Returns the number of bytes stored in SBuf.
Definition: SBuf.h:419
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
File & operator=(const File &)=delete
How should a file be opened/created? Should it be locked?
Definition: File.h:19
void truncate()
makes the file size (and the current I/O offset) zero
Definition: File.cc:215
mode_t creationMask
umask() parameter; the default is S_IWGRP|S_IWOTH
Definition: File.h:47
SBuf ToSBuf(Args &&... args)
slowly stream-prints all arguments into a freshly allocated SBuf
Definition: Stream.h:63
#define Must(condition)
Definition: TextException.h:75
SBuf sysCallError(const char *callName, const int savedErrno) const
Definition: File.cc:372
unsigned short mode_t
Definition: types.h:129
void enter_suid(void)
Definition: tools.cc:623
#define DBG_IMPORTANT
Definition: Stream.h:38
FileOpeningConfig & createdIfMissing()
when opening a file for writing, create it if it does not exist
Definition: File.cc:90
int flockMode
2nd flock(2) parameter
Definition: File.h:58
SBuf sysCallFailure(const char *callName, const SBuf &error) const
Definition: File.cc:365
Handle fd_
OS-specific file handle.
Definition: File.h:123
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
static FileOpeningConfig ReadWrite()
Definition: File.cc:57

 

Introduction

Documentation

Support

Miscellaneous