File.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2022 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 #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 
32 {
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 
57 {
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 
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 
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
110 static 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 
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 &
163 {
164  std::swap(fd_, other.fd_);
165  return *this;
166 }
167 
169 void
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
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
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
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 
319 void
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 
340 void
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 
363 SBuf
364 File::sysCallFailure(const char *callName, const SBuf &error) const
365 {
366  return ToSBuf("failed to ", callName, ' ', name_, ": ", error);
367 }
368 
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 
#define TexcHere(msg)
legacy convenience macro; it is not difficult to type Here() now
Definition: TextException.h:59
#define Must(condition)
Definition: TextException.h:71
void error(char *format,...)
#define assert(EX)
Definition: assert.h:19
How should a file be opened/created? Should it be locked?
Definition: File.h:20
static FileOpeningConfig ReadWrite()
Definition: File.cc:56
unsigned int lockAttempts
pause before each lock retry
Definition: File.h:61
mode_t openMode
access mode; 3rd open(2) parameter
Definition: File.h:49
static const unsigned int RetryGapUsec
Definition: File.h:60
int openFlags
opening flags; 2nd open(2) parameter
Definition: File.h:48
static FileOpeningConfig ReadOnly()
Definition: File.cc:31
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:81
FileOpeningConfig & createdIfMissing()
when opening a file for writing, create it if it does not exist
Definition: File.cc:89
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:134
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:320
void writeAll(const SBuf &data)
write(2) with a "wrote everything" check
Definition: File.cc:279
void open(const FileOpeningConfig &cfg)
opens (or creates) the file
Definition: File.cc:170
static File * Optional(const SBuf &aName, const FileOpeningConfig &cfg)
Definition: File.cc:123
void close()
Definition: File.cc:195
void lockOnce(const FileOpeningConfig &cfg)
locks, blocking or returning immediately depending on the lock waiting mode
Definition: File.cc:341
SBuf sysCallError(const char *callName, const int savedErrno) const
Definition: File.cc:371
SBuf sysCallFailure(const char *callName, const SBuf &error) const
Definition: File.cc:364
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:214
~File()
closes
Definition: File.cc:150
void synchronize()
fsync(2)
Definition: File.cc:303
File & operator=(const File &)=delete
SBuf readSmall(SBuf::size_type minBytes, SBuf::size_type maxBytes)
read(2) for small files
Definition: File.cc:240
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:41
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:196
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:556
void enter_suid(void)
Definition: tools.cc:620
unsigned short mode_t
Definition: types.h:150
const char * xstrerr(int error)
Definition: xstrerror.cc:83
SQUIDCEXTERN int xusleep(unsigned int)
Definition: xusleep.c:20

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors