fs_io.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2025 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 /* DEBUG: section 06 Disk I/O Routines */
10 
11 #include "squid.h"
12 #include "comm/Loops.h"
13 #include "compat/unistd.h"
14 #include "fd.h"
15 #include "fde.h"
16 #include "fs_io.h"
17 #include "globals.h"
18 #include "MemBuf.h"
19 #include "StatCounters.h"
20 
21 #include <cerrno>
22 
25 
26 #if _SQUID_WINDOWS_ || _SQUID_OS2_
27 static int
28 diskWriteIsComplete(int fd)
29 {
30  return fd_table[fd].disk.write_q ? 0 : 1;
31 }
32 
33 #endif
34 
35 /* hack needed on SunStudio to avoid linkage convention mismatch */
36 static void cxx_xfree(void *ptr)
37 {
38  xfree(ptr);
39 }
40 
41 dwrite_q::dwrite_q(const size_t aSize, char * const aBuffer, FREE * const aFree):
42  buf(aBuffer),
43  capacity(aSize),
44  free_func(aFree)
45 {
46  assert(buf || !free_func);
47  if (!buf) {
48  buf = static_cast<char *>(xmalloc(aSize));
49  free_func = cxx_xfree; // dwrite_q buffer xfree()
50  } else {
51  len = aSize;
52  }
53 }
54 
56 {
57  if (free_func)
58  free_func(buf);
59 }
60 
61 /*
62  * opens a disk file specified by 'path'. This function always
63  * blocks! There is no callback.
64  */
65 int
66 file_open(const char *path, int mode)
67 {
68  if (FILE_MODE(mode) == O_WRONLY)
69  mode |= O_APPEND;
70 
71  errno = 0;
72 
73  auto fd = xopen(path, mode, 0644);
74 
75  ++ statCounter.syscalls.disk.opens;
76 
77  if (fd < 0) {
78  int xerrno = errno;
79  debugs(50, 3, "error opening file " << path << ": " << xstrerr(xerrno));
80  fd = DISK_ERROR;
81  } else {
82  debugs(6, 5, "FD " << fd);
84  fd_open(fd, FD_FILE, path);
85  }
86 
87  return fd;
88 }
89 
90 /* close a disk file. */
91 void
92 file_close(int fd)
93 {
94  fde *F = &fd_table[fd];
95  PF *read_callback;
96  assert(fd >= 0);
97  assert(F->flags.open);
98 
99  if ((read_callback = F->read_handler)) {
100  F->read_handler = nullptr;
101  read_callback(-1, F->read_data);
102  }
103 
104  if (F->flags.write_daemon) {
105 #if _SQUID_WINDOWS_ || _SQUID_OS2_
106  /*
107  * on some operating systems, you can not delete or rename
108  * open files, so we won't allow delayed close.
109  */
110  while (!diskWriteIsComplete(fd))
111  diskHandleWrite(fd, nullptr);
112 #else
113  F->flags.close_request = true;
114  debugs(6, 2, "file_close: FD " << fd << ", delaying close");
115  return;
116 #endif
117 
118  }
119 
120  /*
121  * Assert there is no write callback. Otherwise we might be
122  * leaking write state data by closing the descriptor
123  */
124  assert(F->write_handler == nullptr);
125 
126  xclose(fd);
127 
128  debugs(6, F->flags.close_request ? 2 : 5, "file_close: FD " << fd << " really closing");
129 
130  fd_close(fd);
131 
132  ++ statCounter.syscalls.disk.closes;
133 }
134 
135 /*
136  * This function has the purpose of combining multiple writes. This is
137  * to facilitate the ASYNC_IO option since it can only guarantee 1
138  * write to a file per trip around the comm.c select() loop. That's bad
139  * because more than 1 write can be made to the access.log file per
140  * trip, and so this code is purely designed to help batch multiple
141  * sequential writes to the access.log file. Squid will never issue
142  * multiple writes for any other file type during 1 trip around the
143  * select() loop. --SLF
144  */
145 static void
147 {
148  /*
149  * We need to combine multiple write requests on an FD's write
150  * queue But only if we don't need to seek() in between them, ugh!
151  * XXX This currently ignores any seeks (file_offset)
152  */
153 
154  if (fdd->write_q != nullptr && fdd->write_q->next != nullptr) {
155  size_t wantCapacity = 0;
156 
157  for (dwrite_q *q = fdd->write_q; q != nullptr; q = q->next)
158  wantCapacity += q->len - q->buf_offset; // XXX: might overflow
159 
160  const auto wq = new dwrite_q(wantCapacity);
161  while (const auto q = fdd->write_q) {
162  const auto len = q->len - q->buf_offset;
163  memcpy(wq->buf + wq->len, q->buf + q->buf_offset, len);
164  wq->len += len;
165  fdd->write_q = q->next;
166  delete q;
167  };
168 
169  fdd->write_q_tail = wq;
170 
171  fdd->write_q = wq;
172  }
173 }
174 
175 /* write handler */
176 static void
177 diskHandleWrite(int fd, void *)
178 {
179  int len = 0;
180  fde *F = &fd_table[fd];
181 
182  _fde_disk *fdd = &F->disk;
183  int status = DISK_OK;
184  bool do_close;
185 
186  if (!fdd->write_q)
187  return;
188 
189  debugs(6, 3, "diskHandleWrite: FD " << fd);
190 
191  F->flags.write_daemon = false;
192 
193  assert(fdd->write_q != nullptr);
194 
195  assert(fdd->write_q->len > fdd->write_q->buf_offset);
196 
197  debugs(6, 3, "diskHandleWrite: FD " << fd << " writing " <<
198  (fdd->write_q->len - fdd->write_q->buf_offset) << " bytes at " <<
199  fdd->write_q->file_offset);
200 
201  errno = 0;
202 
203  if (fdd->write_q->file_offset != -1) {
204  errno = 0;
205  if (lseek(fd, fdd->write_q->file_offset, SEEK_SET) == -1) {
206  int xerrno = errno;
207  debugs(50, DBG_IMPORTANT, "ERROR: in seek for FD " << fd << ": " << xstrerr(xerrno));
208  // XXX: handle error?
209  }
210  }
211 
212  len = FD_WRITE_METHOD(fd,
213  fdd->write_q->buf + fdd->write_q->buf_offset,
214  fdd->write_q->len - fdd->write_q->buf_offset);
215  const auto xerrno = errno;
216 
217  debugs(6, 3, "diskHandleWrite: FD " << fd << " len = " << len);
218 
219  ++ statCounter.syscalls.disk.writes;
220 
221  fd_bytes(fd, len, IoDirection::Write);
222 
223  if (len < 0) {
224  if (!ignoreErrno(xerrno)) {
225  status = xerrno == ENOSPC ? DISK_NO_SPACE_LEFT : DISK_ERROR;
226  debugs(50, DBG_IMPORTANT, "ERROR: diskHandleWrite: FD " << fd << ": disk write failure: " << xstrerr(xerrno));
227 
228  /*
229  * If there is no write callback, then this file is
230  * most likely something important like a log file, or
231  * an interprocess pipe. Its not a swapfile. We feel
232  * that a write failure on a log file is rather important,
233  * and Squid doesn't otherwise deal with this condition.
234  * So to get the administrators attention, we exit with
235  * a fatal message.
236  */
237 
238  if (fdd->wrt_handle == nullptr)
239  fatal("Write failure -- check your disk space and cache.log");
240 
241  /*
242  * If there is a write failure, then we notify the
243  * upper layer via the callback, at the end of this
244  * function. Meanwhile, flush all pending buffers
245  * here. Let the upper layer decide how to handle the
246  * failure. This will prevent experiencing multiple,
247  * repeated write failures for the same FD because of
248  * the queued data.
249  */
250  while (const auto q = fdd->write_q) {
251  fdd->write_q = q->next;
252  delete q;
253  }
254  }
255 
256  len = 0;
257  }
258 
259  if (const auto q = fdd->write_q) {
260  /* q might become NULL from write failure above */
261  q->buf_offset += len;
262 
263  if (q->buf_offset > q->len)
264  debugs(50, DBG_IMPORTANT, "diskHandleWriteComplete: q->buf_offset > q->len (" <<
265  q << "," << (int) q->buf_offset << ", " << q->len << ", " <<
266  len << " FD " << fd << ")");
267 
268  assert(q->buf_offset <= q->len);
269 
270  if (q->buf_offset == q->len) {
271  /* complete write */
272  fdd->write_q = q->next;
273  delete q;
274  }
275  }
276 
277  if (fdd->write_q == nullptr) {
278  /* no more data */
279  fdd->write_q_tail = nullptr;
280  } else {
281  /* another block is queued */
282  diskCombineWrites(fdd);
284  F->flags.write_daemon = true;
285  }
286 
287  do_close = F->flags.close_request;
288 
289  if (fdd->wrt_handle) {
290  DWCB *callback = fdd->wrt_handle;
291  void *cbdata;
292  fdd->wrt_handle = nullptr;
293 
295  callback(fd, status, len, cbdata);
296  /*
297  * NOTE, this callback can close the FD, so we must
298  * not touch 'F', 'fdd', etc. after this.
299  */
300  return;
301  /* XXX But what about close_request??? */
302  }
303  }
304 
305  if (do_close)
306  file_close(fd);
307 }
308 
309 /* write block to a file */
310 /* write back queue. Only one writer at a time. */
311 /* call a handle when writing is complete. */
312 void
313 file_write(int fd,
314  off_t file_offset,
315  void const *ptr_to_buf,
316  int len,
317  DWCB * handle,
318  void *handle_data,
319  FREE * free_func)
320 {
321  fde *F = &fd_table[fd];
322  assert(fd >= 0);
323  assert(F->flags.open);
324  /* if we got here. Caller is eligible to write. */
325  const auto wq = new dwrite_q(len, static_cast<char *>(const_cast<void *>(ptr_to_buf)), free_func);
326  wq->file_offset = file_offset;
327 
328  if (!F->disk.wrt_handle_data) {
329  F->disk.wrt_handle = handle;
330  F->disk.wrt_handle_data = cbdataReference(handle_data);
331  } else {
332  /* Detect if there is multiple concurrent users of this fd.. we only support one callback */
333  assert(F->disk.wrt_handle_data == handle_data && F->disk.wrt_handle == handle);
334  }
335 
336  /* add to queue */
337  if (F->disk.write_q == nullptr) {
338  /* empty queue */
339  F->disk.write_q = F->disk.write_q_tail = wq;
340  } else {
341  F->disk.write_q_tail->next = wq;
342  F->disk.write_q_tail = wq;
343  }
344 
345  if (!F->flags.write_daemon) {
346  diskHandleWrite(fd, nullptr);
347  }
348 }
349 
350 /* Read from FD */
351 static void
352 diskHandleRead(int fd, void *data)
353 {
354  dread_ctrl *ctrl_dat = (dread_ctrl *)data;
355  fde *F = &fd_table[fd];
356  int len;
357  int rc = DISK_OK;
358  int xerrno;
359 
360  /*
361  * FD < 0 indicates premature close; we just have to free
362  * the state data.
363  */
364 
365  if (fd < 0) {
366  delete ctrl_dat;
367  return;
368  }
369 
370 #if WRITES_MAINTAIN_DISK_OFFSET
371  if (F->disk.offset != ctrl_dat->offset) {
372 #else
373  {
374 #endif
375  debugs(6, 3, "diskHandleRead: FD " << fd << " seeking to offset " << ctrl_dat->offset);
376  errno = 0;
377  if (lseek(fd, ctrl_dat->offset, SEEK_SET) == -1) {
378  xerrno = errno;
379  // shouldn't happen, let's detect that
380  debugs(50, DBG_IMPORTANT, "ERROR: in seek for FD " << fd << ": " << xstrerr(xerrno));
381  // XXX handle failures?
382  }
383  ++ statCounter.syscalls.disk.seeks;
384  F->disk.offset = ctrl_dat->offset;
385  }
386 
387  errno = 0;
388  len = FD_READ_METHOD(fd, ctrl_dat->buf, ctrl_dat->req_len);
389  xerrno = errno;
390 
391  if (len > 0)
392  F->disk.offset += len;
393 
394  ++ statCounter.syscalls.disk.reads;
395 
396  fd_bytes(fd, len, IoDirection::Read);
397 
398  if (len < 0) {
399  if (ignoreErrno(xerrno)) {
401  return;
402  }
403 
404  debugs(50, DBG_IMPORTANT, "diskHandleRead: FD " << fd << ": " << xstrerr(xerrno));
405  len = 0;
406  rc = DISK_ERROR;
407  } else if (len == 0) {
408  rc = DISK_EOF;
409  }
410 
411  if (cbdataReferenceValid(ctrl_dat->client_data))
412  ctrl_dat->handler(fd, ctrl_dat->buf, len, rc, ctrl_dat->client_data);
413 
414  cbdataReferenceDone(ctrl_dat->client_data);
415 
416  delete ctrl_dat;
417 }
418 
419 /* start read operation */
420 /* buffer must be allocated from the caller.
421  * It must have at least req_len space in there.
422  * call handler when a reading is complete. */
423 void
424 file_read(int fd, char *buf, int req_len, off_t offset, DRCB * handler, void *client_data)
425 {
426  assert(fd >= 0);
427  const auto ctrl_dat = new dread_ctrl(fd, offset, buf, req_len, handler, cbdataReference(client_data));
428  diskHandleRead(fd, ctrl_dat);
429 }
430 
431 void
432 safeunlink(const char *s, int quiet)
433 {
434  ++ statCounter.syscalls.disk.unlinks;
435 
436  if (unlink(s) < 0 && !quiet) {
437  int xerrno = errno;
438  debugs(50, DBG_IMPORTANT, "ERROR: safeunlink: Could not delete " << s << ": " << xstrerr(xerrno));
439  }
440 }
441 
442 bool
443 FileRename(const SBuf &from, const SBuf &to)
444 {
445  debugs(21, 2, "renaming " << from << " to " << to);
446 
447  // non-const copy for c_str()
448  SBuf from2(from);
449  // ensure c_str() lifetimes even if `to` and `from` share memory
450  SBuf to2(to.rawContent(), to.length());
451 
452 #if _SQUID_OS2_ || _SQUID_WINDOWS_
453  remove(to2.c_str());
454 #endif
455 
456  if (rename(from2.c_str(), to2.c_str()) == 0)
457  return true;
458 
459  int xerrno = errno;
460  debugs(21, (xerrno == ENOENT ? 2 : DBG_IMPORTANT), "ERROR: Cannot rename " << from << " to " << to << ": " << xstrerr(xerrno));
461 
462  return false;
463 }
464 
465 int
466 fsBlockSize(const char *path, int *blksize)
467 {
468  struct statvfs sfs;
469 
470  if (xstatvfs(path, &sfs)) {
471  int xerrno = errno;
472  debugs(50, DBG_IMPORTANT, "" << path << ": " << xstrerr(xerrno));
473  *blksize = 2048;
474  return 1;
475  }
476 
477  *blksize = (int) sfs.f_frsize;
478 
479  // Sanity check; make sure we have a meaningful value.
480  if (*blksize < 512)
481  *blksize = 2048;
482 
483  return 0;
484 }
485 
486 #define fsbtoblk(num, fsbs, bs) \
487  (((fsbs) != 0 && (fsbs) < (bs)) ? \
488  (num) / ((bs) / (fsbs)) : (num) * ((fsbs) / (bs)))
489 int
490 fsStats(const char *path, int *totl_kb, int *free_kb, int *totl_in, int *free_in)
491 {
492  struct statvfs sfs;
493 
494  if (xstatvfs(path, &sfs)) {
495  int xerrno = errno;
496  debugs(50, DBG_IMPORTANT, "" << path << ": " << xstrerr(xerrno));
497  return 1;
498  }
499 
500  *totl_kb = (int) fsbtoblk(sfs.f_blocks, sfs.f_frsize, 1024);
501  *free_kb = (int) fsbtoblk(sfs.f_bfree, sfs.f_frsize, 1024);
502  *totl_in = (int) sfs.f_files;
503  *free_in = (int) sfs.f_ffree;
504  return 0;
505 }
506 
void fatal(const char *message)
Definition: fatal.cc:28
const char * xstrerr(int error)
Definition: xstrerror.cc:83
static PF diskHandleWrite
Definition: fs_io.cc:24
fsblkcnt_t f_bfree
Definition: statvfs.h:47
#define xmalloc
void fd_bytes(const int fd, const int len, const IoDirection direction)
Definition: fd.cc:221
static void cxx_xfree(void *ptr)
Definition: fs_io.cc:36
dwrite_q * write_q
Definition: fde.h:46
#define DISK_EOF
Definition: defines.h:29
#define cbdataReferenceValidDone(var, ptr)
Definition: cbdata.h:239
void FREE(void *)
Definition: forward.h:37
void commSetCloseOnExec(int)
Definition: minimal.cc:27
Definition: fde.h:39
int fsBlockSize(const char *path, int *blksize)
Definition: fs_io.cc:466
fsblkcnt_t f_blocks
Definition: statvfs.h:46
int req_len
Definition: fs_io.h:37
Definition: SBuf.h:93
void * client_data
Definition: fs_io.h:41
dwrite_q(const size_t wantCapacity)
Definition: fs_io.h:48
static uint32 F(uint32 X, uint32 Y, uint32 Z)
Definition: md4.c:46
Definition: cbdata.cc:37
void file_read(int fd, char *buf, int req_len, off_t offset, DRCB *handler, void *client_data)
Definition: fs_io.cc:424
int cbdataReferenceValid(const void *p)
Definition: cbdata.cc:270
int xstatvfs(const char *path, struct statvfs *sfs)
Definition: statvfs.cc:22
off_t file_offset
Definition: fs_io.h:53
void file_close(int fd)
Definition: fs_io.cc:92
void fd_open(const int fd, unsigned int, const char *description)
Definition: minimal.cc:15
fsfilcnt_t f_files
Definition: statvfs.h:49
#define cbdataReference(var)
Definition: cbdata.h:348
Definition: fde.h:51
#define DISK_OK
Definition: defines.h:27
static int do_close(diomsg *r, int)
Definition: diskd.cc:89
DRCB * handler
Definition: fs_io.h:40
void DRCB(int, const char *buf, int size, int errflag, void *data)
Definition: typedefs.h:16
char * buf
Definition: fs_io.h:54
DWCB * wrt_handle
Definition: fde.h:44
const char * rawContent() const
Definition: SBuf.cc:509
int fsStats(const char *path, int *totl_kb, int *free_kb, int *totl_in, int *free_in)
Definition: fs_io.cc:490
#define DISK_NO_SPACE_LEFT
Definition: defines.h:30
dwrite_q * write_q_tail
Definition: fde.h:47
size_t len
length of content in buf
Definition: fs_io.h:55
fsfilcnt_t f_ffree
Definition: statvfs.h:50
FREE * free_func
when set, gets called upon object destruction to free buf
Definition: fs_io.h:62
#define assert(EX)
Definition: assert.h:17
#define COMM_SELECT_READ
Definition: defines.h:24
#define cbdataReferenceDone(var)
Definition: cbdata.h:357
int FD_WRITE_METHOD(int fd, const char *buf, int len)
Definition: fde.h:200
const char * c_str()
Definition: SBuf.cc:516
size_type length() const
Returns the number of bytes stored in SBuf.
Definition: SBuf.h:419
off_t offset
Definition: fs_io.h:36
#define xfree
int FD_READ_METHOD(int fd, char *buf, int len)
Definition: fde.h:194
int ignoreErrno(int ierrno)
Definition: comm.cc:1407
#define fd_table
Definition: fde.h:189
~dwrite_q()
Definition: fs_io.cc:55
void SetSelect(int, unsigned int, PF *, void *, time_t)
Mark an FD to be watched for its IO status.
Definition: ModDevPoll.cc:220
void file_write(int fd, off_t file_offset, void const *ptr_to_buf, int len, DWCB *handle, void *handle_data, FREE *free_func)
Definition: fs_io.cc:313
struct StatCounters::@115::@119 disk
#define FILE_MODE(x)
Definition: defines.h:143
unsigned long f_frsize
Definition: statvfs.h:45
#define DBG_IMPORTANT
Definition: Stream.h:38
Definition: fs_io.h:44
int xopen(const char *filename, int oflag, int pmode=0)
POSIX open(2) equivalent.
Definition: unistd.h:55
struct StatCounters::@115 syscalls
void safeunlink(const char *s, int quiet)
Definition: fs_io.cc:432
dwrite_q * next
Definition: fs_io.h:57
void fd_close(const int fd)
Definition: minimal.cc:21
#define DISK_ERROR
Definition: defines.h:28
int xclose(int fd)
POSIX close(2) equivalent.
Definition: unistd.h:43
static PF diskHandleRead
Definition: fs_io.cc:23
bool FileRename(const SBuf &from, const SBuf &to)
Definition: fs_io.cc:443
int file_open(const char *path, int mode)
Definition: fs_io.cc:66
#define fsbtoblk(num, fsbs, bs)
Definition: fs_io.cc:486
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
#define COMM_SELECT_WRITE
Definition: defines.h:25
char * buf
Definition: fs_io.h:38
@ FD_FILE
Definition: enums.h:15
static void diskCombineWrites(_fde_disk *fdd)
Definition: fs_io.cc:146
void * wrt_handle_data
Definition: fde.h:45
void DWCB(int, int, size_t, void *)
Definition: typedefs.h:18
void PF(int, void *)
Definition: forward.h:18
int unsigned int
Definition: stub_fd.cc:19
StatCounters statCounter
Definition: StatCounters.cc:12
size_t buf_offset
Definition: fs_io.h:56

 

Introduction

Documentation

Support

Miscellaneous