Squid Web Cache master
Loading...
Searching...
No Matches
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_
27static int
28diskWriteIsComplete(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 */
36static void cxx_xfree(void *ptr)
37{
38 xfree(ptr);
39}
40
41dwrite_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
60
61/*
62 * opens a disk file specified by 'path'. This function always
63 * blocks! There is no callback.
64 */
65int
66file_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
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. */
91void
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
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 */
145static 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 */
176static void
177diskHandleWrite(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
220
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 */
284 F->flags.write_daemon = true;
285 }
286
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. */
312void
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 */
351static void
352diskHandleRead(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 }
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
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
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. */
423void
424file_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
431void
432safeunlink(const char *s, int quiet)
433{
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
442bool
443FileRename(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
465int
466fsBlockSize(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)))
489int
490fsStats(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
StatCounters statCounter
#define assert(EX)
Definition assert.h:17
int cbdataReferenceValid(const void *p)
Definition cbdata.cc:270
#define cbdataReferenceDone(var)
Definition cbdata.h:357
#define cbdataReference(var)
Definition cbdata.h:348
#define cbdataReferenceValidDone(var, ptr)
Definition cbdata.h:239
Definition SBuf.h:94
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:419
struct StatCounters::@112 syscalls
struct StatCounters::@112::@116 disk
DWCB * wrt_handle
Definition fde.h:44
dwrite_q * write_q
Definition fde.h:46
off_t offset
Definition fde.h:48
void * wrt_handle_data
Definition fde.h:45
dwrite_q * write_q_tail
Definition fde.h:47
int req_len
Definition fs_io.h:37
void * client_data
Definition fs_io.h:41
DRCB * handler
Definition fs_io.h:40
off_t offset
Definition fs_io.h:36
char * buf
Definition fs_io.h:38
~dwrite_q()
Definition fs_io.cc:55
dwrite_q(const size_t wantCapacity)
Definition fs_io.h:48
char * buf
Definition fs_io.h:54
size_t buf_offset
Definition fs_io.h:56
size_t len
length of content in buf
Definition fs_io.h:55
dwrite_q * next
Definition fs_io.h:57
off_t file_offset
Definition fs_io.h:53
FREE * free_func
when set, gets called upon object destruction to free buf
Definition fs_io.h:62
Definition fde.h:52
_fde_disk disk
Definition fde.h:148
PF * read_handler
Definition fde.h:149
struct fde::_fde_flags flags
void * read_data
Definition fde.h:150
PF * write_handler
Definition fde.h:151
void PF(int, void *)
Definition forward.h:18
void fd_open(const int fd, unsigned int, const char *description)
Definition minimal.cc:15
void fd_close(const int fd)
Definition minimal.cc:21
void commSetCloseOnExec(int fd)
Definition comm.cc:1105
int ignoreErrno(int ierrno)
Definition comm.cc:1407
#define DBG_IMPORTANT
Definition Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define COMM_SELECT_READ
Definition defines.h:24
#define COMM_SELECT_WRITE
Definition defines.h:25
#define FILE_MODE(x)
Definition defines.h:143
#define DISK_EOF
Definition defines.h:29
#define DISK_NO_SPACE_LEFT
Definition defines.h:30
#define DISK_ERROR
Definition defines.h:28
#define DISK_OK
Definition defines.h:27
static int do_close(diomsg *r, int)
Definition diskd.cc:89
@ FD_FILE
Definition enums.h:15
void fatal(const char *message)
Definition fatal.cc:28
void fd_bytes(const int fd, const int len, const IoDirection direction)
Definition fd.cc:221
#define fd_table
Definition fde.h:189
int FD_READ_METHOD(int fd, char *buf, int len)
Definition fde.h:194
int FD_WRITE_METHOD(int fd, const char *buf, int len)
Definition fde.h:200
int file_open(const char *path, int mode)
Definition fs_io.cc:66
#define fsbtoblk(num, fsbs, bs)
Definition fs_io.cc:486
static void diskCombineWrites(_fde_disk *fdd)
Definition fs_io.cc:146
bool FileRename(const SBuf &from, const SBuf &to)
Definition fs_io.cc:443
void safeunlink(const char *s, int quiet)
Definition fs_io.cc:432
void file_read(int fd, char *buf, int req_len, off_t offset, DRCB *handler, void *client_data)
Definition fs_io.cc:424
static PF diskHandleRead
Definition fs_io.cc:23
int fsBlockSize(const char *path, int *blksize)
Definition fs_io.cc:466
static PF diskHandleWrite
Definition fs_io.cc:24
static void cxx_xfree(void *ptr)
Definition fs_io.cc:36
void file_close(int fd)
Definition fs_io.cc:92
int fsStats(const char *path, int *totl_kb, int *free_kb, int *totl_in, int *free_in)
Definition fs_io.cc:490
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
void FREE(void *)
Definition forward.h:37
void SetSelect(int, unsigned int, PF *, void *, time_t)
Mark an FD to be watched for its IO status.
#define xfree
#define xmalloc
int xstatvfs(const char *path, struct statvfs *sfs)
Definition statvfs.cc:22
bool open
Definition fde.h:118
bool close_request
true if file_ or comm_close has been called
Definition fde.h:119
bool write_daemon
Definition fde.h:120
fsfilcnt_t f_files
Definition statvfs.h:49
fsfilcnt_t f_ffree
Definition statvfs.h:50
fsblkcnt_t f_blocks
Definition statvfs.h:46
fsblkcnt_t f_bfree
Definition statvfs.h:47
unsigned long f_frsize
Definition statvfs.h:45
int unsigned int
Definition stub_fd.cc:19
void DRCB(int, const char *buf, int size, int errflag, void *data)
Definition typedefs.h:16
void DWCB(int, int, size_t, void *)
Definition typedefs.h:18
int xclose(int fd)
POSIX close(2) equivalent.
Definition unistd.h:43
int xopen(const char *filename, int oflag, int pmode=0)
POSIX open(2) equivalent.
Definition unistd.h:55
const char * xstrerr(int error)
Definition xstrerror.cc:83