]> git.ipfire.org Git - thirdparty/squid.git/blame - src/disk.cc
update
[thirdparty/squid.git] / src / disk.cc
CommitLineData
b224ea98 1
30a4f2a8 2/*
5999b776 3 * $Id: disk.cc,v 1.116 1998/05/22 23:44:03 wessels Exp $
30a4f2a8 4 *
5 * DEBUG: section 6 Disk I/O Routines
6 * AUTHOR: Harvest Derived
7 *
42c04c16 8 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
30a4f2a8 9 * --------------------------------------------------------
10 *
11 * Squid is the result of efforts by numerous individuals from the
12 * Internet community. Development is led by Duane Wessels of the
13 * National Laboratory for Applied Network Research and funded by
14 * the National Science Foundation.
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 *
30 */
31
32/*
33 * Copyright (c) 1994, 1995. All rights reserved.
34 *
35 * The Harvest software was developed by the Internet Research Task
36 * Force Research Group on Resource Discovery (IRTF-RD):
37 *
38 * Mic Bowman of Transarc Corporation.
39 * Peter Danzig of the University of Southern California.
40 * Darren R. Hardy of the University of Colorado at Boulder.
41 * Udi Manber of the University of Arizona.
42 * Michael F. Schwartz of the University of Colorado at Boulder.
43 * Duane Wessels of the University of Colorado at Boulder.
44 *
45 * This copyright notice applies to software in the Harvest
46 * ``src/'' directory only. Users should consult the individual
47 * copyright notices in the ``components/'' subdirectories for
48 * copyright information about other software bundled with the
49 * Harvest source code distribution.
50 *
51 * TERMS OF USE
52 *
53 * The Harvest software may be used and re-distributed without
54 * charge, provided that the software origin and research team are
55 * cited in any use of the system. Most commonly this is
56 * accomplished by including a link to the Harvest Home Page
57 * (http://harvest.cs.colorado.edu/) from the query page of any
58 * Broker you deploy, as well as in the query result pages. These
59 * links are generated automatically by the standard Broker
60 * software distribution.
61 *
62 * The Harvest software is provided ``as is'', without express or
63 * implied warranty, and with no support nor obligation to assist
64 * in its use, correction, modification or enhancement. We assume
65 * no liability with respect to the infringement of copyrights,
66 * trade secrets, or any patents, and are not responsible for
67 * consequential damages. Proper use of the Harvest software is
68 * entirely the responsibility of the user.
69 *
70 * DERIVATIVE WORKS
71 *
72 * Users may make derivative works from the Harvest software, subject
73 * to the following constraints:
74 *
75 * - You must include the above copyright notice and these
76 * accompanying paragraphs in all forms of derivative works,
77 * and any documentation and other materials related to such
78 * distribution and use acknowledge that the software was
79 * developed at the above institutions.
80 *
81 * - You must notify IRTF-RD regarding your distribution of
82 * the derivative work.
83 *
84 * - You must clearly notify users that your are distributing
85 * a modified version and not the original Harvest software.
86 *
87 * - Any derivative product is also subject to these copyright
88 * and use restrictions.
89 *
90 * Note that the Harvest software is NOT in the public domain. We
91 * retain copyright, as specified above.
92 *
93 * HISTORY OF FREE SOFTWARE STATUS
94 *
95 * Originally we required sites to license the software in cases
96 * where they were going to build commercial products/services
97 * around Harvest. In June 1995 we changed this policy. We now
98 * allow people to use the core Harvest software (the code found in
99 * the Harvest ``src/'' directory) for free. We made this change
100 * in the interest of encouraging the widest possible deployment of
101 * the technology. The Harvest software is really a reference
102 * implementation of a set of protocols and formats, some of which
103 * we intend to standardize. We encourage commercial
104 * re-implementations of code complying to this set of standards.
105 */
ed43818f 106
44a47c6e 107#include "squid.h"
090089c4 108
109#define DISK_LINE_LEN 1024
090089c4 110
0a0bf5db 111typedef struct disk_ctrl_t {
112 int fd;
113 void *data;
114} disk_ctrl_t;
115
116
117typedef struct open_ctrl_t {
f17936ab 118 FOCB *callback;
0a0bf5db 119 void *callback_data;
120 char *path;
121} open_ctrl_t;
122
de866d20 123static AIOCB diskHandleWriteComplete;
124static AIOCB diskHandleReadComplete;
95d15928 125static PF diskHandleRead;
126static PF diskHandleWrite;
f5b8bbc4 127static void file_open_complete(void *, int, int);
24382924 128
04cece06 129void
0673c0ba 130disk_init(void)
090089c4 131{
04cece06 132#if USE_ASYNC_IO
133 aioClose(dup(0));
134#endif
090089c4 135}
136
137/* Open a disk file. Return a file descriptor */
684c2720 138int
6cf028ab 139file_open(const char *path, int mode, FOCB * callback, void *callback_data, void *tag)
090089c4 140{
090089c4 141 int fd;
0a0bf5db 142 open_ctrl_t *ctrlp;
143
144 ctrlp = xmalloc(sizeof(open_ctrl_t));
145 ctrlp->path = xstrdup(path);
146 ctrlp->callback = callback;
147 ctrlp->callback_data = callback_data;
090089c4 148
b59c7120 149 if (mode & O_WRONLY)
150 mode |= O_APPEND;
4f92c80c 151 mode |= SQUID_NONBLOCK;
b59c7120 152
090089c4 153 /* Open file */
0a0bf5db 154#if USE_ASYNC_IO
f17936ab 155 if (callback != NULL) {
6cf028ab 156 aioOpen(path, mode, 0644, file_open_complete, ctrlp, tag);
9e4ad609 157 return DISK_OK;
0a0bf5db 158 }
f17936ab 159#endif
0a0bf5db 160 fd = open(path, mode, 0644);
161 file_open_complete(ctrlp, fd, errno);
162 if (fd < 0)
163 return DISK_ERROR;
164 return fd;
0a0bf5db 165}
166
167
168static void
95d15928 169file_open_complete(void *data, int fd, int errcode)
0a0bf5db 170{
171 open_ctrl_t *ctrlp = (open_ctrl_t *) data;
6cf028ab 172
0e473d70 173 if (fd == -2 && errcode == -2) { /* Cancelled - clean up */
6cf028ab 174 if (ctrlp->callback)
175 (ctrlp->callback) (ctrlp->callback_data, fd, errcode);
176 xfree(ctrlp->path);
177 xfree(ctrlp);
178 return;
179 }
0a0bf5db 180 if (fd < 0) {
181 errno = errcode;
af95908a 182 debug(50, 3) ("file_open: error opening file %s: %s\n", ctrlp->path,
0a0bf5db 183 xstrerror());
184 if (ctrlp->callback)
6cf028ab 185 (ctrlp->callback) (ctrlp->callback_data, DISK_ERROR, errcode);
0a0bf5db 186 xfree(ctrlp->path);
187 xfree(ctrlp);
188 return;
090089c4 189 }
365e5b34 190 debug(6, 5) ("file_open: FD %d\n", fd);
3ca60c86 191 commSetCloseOnExec(fd);
5c5783a2 192 fd_open(fd, FD_FILE, ctrlp->path);
0a0bf5db 193 if (ctrlp->callback)
6cf028ab 194 (ctrlp->callback) (ctrlp->callback_data, fd, errcode);
0a0bf5db 195 xfree(ctrlp->path);
196 xfree(ctrlp);
197}
198
090089c4 199/* close a disk file. */
95d15928 200void
b8d8561b 201file_close(int fd)
090089c4 202{
76f87348 203 fde *F = &fd_table[fd];
25354045 204#if USE_ASYNC_IO
0e473d70 205 if (fd < 0) {
6cf028ab 206 debug(6, 0) ("file_close: FD less than zero: %d\n", fd);
207 return;
208 }
25354045 209#else
210 assert(fd >= 0);
211#endif
76f87348 212 assert(F->open);
0cd30ba5 213 if (F->flags.write_daemon) {
214 F->flags.close_request = 1;
66979cec 215 debug(6, 2) ("file_close: FD %d, delaying close\n", fd);
95d15928 216 return;
fb247d78 217 }
0a0bf5db 218#if USE_ASYNC_IO
95d15928 219 aioClose(fd);
0a0bf5db 220#else
95d15928 221 close(fd);
0a0bf5db 222#endif
0cd30ba5 223 debug(6, F->flags.close_request ? 2 : 5)
5ac0b6e7 224 ("file_close: FD %d, really closing\n", fd);
6cf028ab 225 fd_close(fd);
090089c4 226}
227
090089c4 228
229/* write handler */
582b6456 230static void
79d39a72 231diskHandleWrite(int fd, void *notused)
090089c4 232{
4a86108c 233 int len = 0;
0a0bf5db 234 disk_ctrl_t *ctrlp;
235 dwrite_q *q = NULL;
236 dwrite_q *wq = NULL;
76f87348 237 fde *F = &fd_table[fd];
238 struct _fde_disk *fdd = &F->disk;
de866d20 239 if (!fdd->write_q)
582b6456 240 return;
ba7b771e 241 debug(6, 3) ("diskHandleWrite: FD %d\n", fd);
0a0bf5db 242 /* We need to combine subsequent write requests after the first */
d377699f 243 /* But only if we don't need to seek() in betwen them, ugh! */
20f92343 244 /* XXX This currently ignores any seeks (file_offset) */
de866d20 245 if (fdd->write_q->next != NULL && fdd->write_q->next->next != NULL) {
246 len = 0;
247 for (q = fdd->write_q->next; q != NULL; q = q->next)
d377699f 248 len += q->len - q->buf_offset;
0a0bf5db 249 wq = xcalloc(1, sizeof(dwrite_q));
250 wq->buf = xmalloc(len);
251 wq->len = 0;
d377699f 252 wq->buf_offset = 0;
0a0bf5db 253 wq->next = NULL;
ed7f0b6a 254 wq->free_func = xfree;
0a0bf5db 255 do {
de866d20 256 q = fdd->write_q->next;
d377699f 257 len = q->len - q->buf_offset;
258 xmemcpy(wq->buf + wq->len, q->buf + q->buf_offset, len);
0a0bf5db 259 wq->len += len;
de866d20 260 fdd->write_q->next = q->next;
ed7f0b6a 261 if (q->free_func)
262 (q->free_func) (q->buf);
0a0bf5db 263 safe_free(q);
de866d20 264 } while (fdd->write_q->next != NULL);
265 fdd->write_q_tail = wq;
266 fdd->write_q->next = wq;
0a0bf5db 267 }
268 ctrlp = xcalloc(1, sizeof(disk_ctrl_t));
269 ctrlp->fd = fd;
f0027316 270#if USE_ASYNC_IO
271 ctrlp->data = fdd->write_q;
272#endif
8350fe9b 273 assert(fdd->write_q != NULL);
d377699f 274 assert(fdd->write_q->len > fdd->write_q->buf_offset);
0a0bf5db 275#if USE_ASYNC_IO
276 aioWrite(fd,
5999b776 277 -1, /* seek offset, -1 == append */
d377699f 278 fdd->write_q->buf + fdd->write_q->buf_offset,
279 fdd->write_q->len - fdd->write_q->buf_offset,
0a0bf5db 280 diskHandleWriteComplete,
cd1fb0eb 281 ctrlp);
0a0bf5db 282#else
ba7b771e 283 debug(6, 3) ("diskHandleWrite: FD %d writing %d bytes\n",
5f6ac48b 284 fd, (int) (fdd->write_q->len - fdd->write_q->buf_offset));
0a0bf5db 285 len = write(fd,
d377699f 286 fdd->write_q->buf + fdd->write_q->buf_offset,
287 fdd->write_q->len - fdd->write_q->buf_offset);
582b6456 288 diskHandleWriteComplete(ctrlp, len, errno);
0a0bf5db 289#endif
290}
291
de866d20 292static void
b69f7771 293diskHandleWriteComplete(void *data, int len, int errcode)
0a0bf5db 294{
295 disk_ctrl_t *ctrlp = data;
0a0bf5db 296 int fd = ctrlp->fd;
76f87348 297 fde *F = &fd_table[fd];
298 struct _fde_disk *fdd = &F->disk;
de866d20 299 dwrite_q *q = fdd->write_q;
300 int status = DISK_OK;
ba7b771e 301 int do_callback;
68c21f71 302 int do_close;
0a0bf5db 303 errno = errcode;
ba7b771e 304 debug(6, 3) ("diskHandleWriteComplete: FD %d len = %d\n", fd, len);
f0027316 305#if USE_ASYNC_IO
306/*
307 * From: "Michael O'Reilly" <michael@metal.iinet.net.au>
308 * Date: 24 Feb 1998 15:12:06 +0800
309 *
310 * A small patch to improve the AIO sanity. the patch below makes sure
311 * the write request really does match the data passed back from the
312 * async IO call. note that I haven't actually rebooted with this
313 * patch yet, so 'provisional' is an understatement.
314 */
315 if (q && q != ctrlp->data) {
316 dwrite_q *p = ctrlp->data;
317 debug(50, 0) ("KARMA: q != data (%p, %p)\n", q, p);
318 debug(50, 0) ("KARMA: (%d, %d, %d FD %d)\n",
319 q->buf_offset, q->len, len, fd);
320 debug(50, 0) ("KARMA: desc %s, type %d, open %d, flags 0x%x\n",
321 F->desc, F->type, F->open, F->flags);
322 debug(50, 0) ("KARMA: (%d, %d)\n", p->buf_offset, p->len);
323 len = -1;
324 errcode = EFAULT;
325 }
326#endif
0a0bf5db 327 safe_free(data);
de866d20 328 if (q == NULL) /* Someone aborted then write completed */
329 return;
6cf028ab 330
0e473d70 331 if (len == -2 && errcode == -2) { /* Write cancelled - cleanup */
6cf028ab 332 do {
333 fdd->write_q = q->next;
ed7f0b6a 334 if (q->free_func)
335 (q->free_func) (q->buf);
6cf028ab 336 safe_free(q);
337 } while ((q = fdd->write_q));
338 return;
339 }
6cf028ab 340 fd_bytes(fd, len, FD_WRITE);
0a0bf5db 341 if (len < 0) {
b224ea98 342 if (!ignoreErrno(errno)) {
de866d20 343 status = errno == ENOSPC ? DISK_NO_SPACE_LEFT : DISK_ERROR;
a3d5953d 344 debug(50, 1) ("diskHandleWrite: FD %d: disk write error: %s\n",
0c77d853 345 fd, xstrerror());
25354045 346 /*
347 * If there is no write callback, then this file is
348 * most likely something important like a log file, or
349 * an interprocess pipe. Its not a swapfile. We feel
350 * that a write failure on a log file is rather important,
351 * and Squid doesn't otherwise deal with this condition.
352 * So to get the administrators attention, we exit with
353 * a fatal message.
354 */
355 if (fdd->wrt_handle == NULL)
356 fatal("Write failure -- check your disk space and cache.log");
357 /*
358 * If there is a write failure, then we notify the
359 * upper layer via the callback, at the end of this
360 * function. Meanwhile, flush all pending buffers
361 * here. Let the upper layer decide how to handle the
362 * failure. This will prevent experiencing multiple,
363 * repeated write failures for the same FD because of
364 * the queued data.
365 */
366 do {
367 fdd->write_q = q->next;
368 if (q->free_func)
369 (q->free_func) (q->buf);
370 safe_free(q);
371 } while ((q = fdd->write_q));
0c77d853 372 }
de866d20 373 len = 0;
0a0bf5db 374 }
8350fe9b 375 if (q != NULL) {
376 /* q might become NULL from write failure above */
d377699f 377 q->buf_offset += len;
6cf028ab 378 if (q->buf_offset > q->len)
379 debug(50, 1) ("diskHandleWriteComplete: q->buf_offset > q->len (%p,%d, %d, %d FD %d)\n",
5f6ac48b 380 q, (int) q->buf_offset, q->len, len, fd);
d377699f 381 assert(q->buf_offset <= q->len);
382 if (q->buf_offset == q->len) {
56878878 383 /* complete write */
384 fdd->write_q = q->next;
ed7f0b6a 385 if (q->free_func)
386 (q->free_func) (q->buf);
56878878 387 safe_free(q);
388 }
090089c4 389 }
de866d20 390 if (fdd->write_q == NULL) {
391 /* no more data */
392 fdd->write_q_tail = NULL;
0cd30ba5 393 F->flags.write_daemon = 0;
de866d20 394 } else {
0a0bf5db 395 /* another block is queued */
25354045 396 cbdataLock(fdd->wrt_handle_data);
de866d20 397 commSetSelect(fd, COMM_SELECT_WRITE, diskHandleWrite, NULL, 0);
0cd30ba5 398 F->flags.write_daemon = 1;
4a86108c 399 }
0cd30ba5 400 do_close = F->flags.close_request;
25354045 401 if (fdd->wrt_handle) {
ba7b771e 402 if (fdd->wrt_handle_data == NULL)
403 do_callback = 1;
404 else if (cbdataValid(fdd->wrt_handle_data))
405 do_callback = 1;
406 else
407 do_callback = 0;
25354045 408 if (fdd->wrt_handle_data != NULL)
409 cbdataUnlock(fdd->wrt_handle_data);
26720a7c 410 if (do_callback) {
ba7b771e 411 fdd->wrt_handle(fd, status, len, fdd->wrt_handle_data);
26720a7c 412 /*
413 * NOTE, this callback can close the FD, so we must
414 * not touch 'F', 'fdd', etc. after this.
415 */
416 return;
417 }
25354045 418 }
68c21f71 419 if (do_close)
b59c7120 420 file_close(fd);
090089c4 421}
422
423
090089c4 424/* write block to a file */
425/* write back queue. Only one writer at a time. */
426/* call a handle when writing is complete. */
e3ef2b09 427void
3ebcfaa1 428file_write(int fd,
d377699f 429 off_t file_offset,
5830cdb3 430 void *ptr_to_buf,
684c2720 431 int len,
d89d1fb6 432 DWCB handle,
684c2720 433 void *handle_data,
9e4ad609 434 FREE * free_func)
090089c4 435{
c6ac7aae 436 dwrite_q *wq = NULL;
48cc3fcf 437 fde *F = &fd_table[fd];
438 assert(fd >= 0);
439 assert(F->open);
090089c4 440 /* if we got here. Caller is eligible to write. */
30a4f2a8 441 wq = xcalloc(1, sizeof(dwrite_q));
d377699f 442 wq->file_offset = file_offset;
090089c4 443 wq->buf = ptr_to_buf;
090089c4 444 wq->len = len;
d377699f 445 wq->buf_offset = 0;
090089c4 446 wq->next = NULL;
ed7f0b6a 447 wq->free_func = free_func;
76f87348 448 F->disk.wrt_handle = handle;
449 F->disk.wrt_handle_data = handle_data;
090089c4 450 /* add to queue */
48cc3fcf 451 if (F->disk.write_q == NULL) {
090089c4 452 /* empty queue */
76f87348 453 F->disk.write_q = F->disk.write_q_tail = wq;
090089c4 454 } else {
76f87348 455 F->disk.write_q_tail->next = wq;
456 F->disk.write_q_tail = wq;
090089c4 457 }
0cd30ba5 458 if (!F->flags.write_daemon) {
25354045 459 cbdataLock(F->disk.wrt_handle_data);
0a0bf5db 460#if USE_ASYNC_IO
95d15928 461 diskHandleWrite(fd, NULL);
0a0bf5db 462#else
95d15928 463 commSetSelect(fd, COMM_SELECT_WRITE, diskHandleWrite, NULL, 0);
0a0bf5db 464#endif
0cd30ba5 465 F->flags.write_daemon = 1;
429fdbec 466 }
090089c4 467}
468
469
470
471/* Read from FD */
582b6456 472static void
473diskHandleRead(int fd, void *data)
090089c4 474{
582b6456 475 dread_ctrl *ctrl_dat = data;
606bd3b5 476#if !USE_ASYNC_IO
edd2eb63 477 fde *F = &fd_table[fd];
090089c4 478 int len;
606bd3b5 479#endif
480 disk_ctrl_t *ctrlp = xcalloc(1, sizeof(disk_ctrl_t));
0a0bf5db 481 ctrlp->fd = fd;
cd1fb0eb 482 ctrlp->data = ctrl_dat;
0a0bf5db 483#if USE_ASYNC_IO
484 aioRead(fd,
20f92343 485 ctrl_dat->offset,
711982d8 486 ctrl_dat->buf,
487 ctrl_dat->req_len,
0a0bf5db 488 diskHandleReadComplete,
cd1fb0eb 489 ctrlp);
0a0bf5db 490#else
711982d8 491 if (F->disk.offset != ctrl_dat->offset) {
f2b30883 492 debug(6, 3) ("diskHandleRead: FD %d seeking to offset %d\n",
343c257b 493 fd, (int) ctrl_dat->offset);
711982d8 494 lseek(fd, ctrl_dat->offset, SEEK_SET); /* XXX ignore return? */
556c33d5 495 F->disk.offset = ctrl_dat->offset;
711982d8 496 }
497 len = read(fd, ctrl_dat->buf, ctrl_dat->req_len);
498 F->disk.offset += len;
582b6456 499 diskHandleReadComplete(ctrlp, len, errno);
090089c4 500#endif
0a0bf5db 501}
502
de866d20 503static void
4f92c80c 504diskHandleReadComplete(void *data, int len, int errcode)
0a0bf5db 505{
506 disk_ctrl_t *ctrlp = data;
507 dread_ctrl *ctrl_dat = ctrlp->data;
508 int fd = ctrlp->fd;
901a2a3c 509 int rc = DISK_OK;
0a0bf5db 510 errno = errcode;
6cf028ab 511
0a0bf5db 512 xfree(data);
6cf028ab 513
0e473d70 514 if (len == -2 && errcode == -2) { /* Read cancelled - cleanup */
6cf028ab 515 cbdataUnlock(ctrl_dat->client_data);
516 safe_free(ctrl_dat);
517 return;
518 }
4f92c80c 519 fd_bytes(fd, len, FD_READ);
0a0bf5db 520 if (len < 0) {
b224ea98 521 if (ignoreErrno(errno)) {
901a2a3c 522 commSetSelect(fd, COMM_SELECT_READ, diskHandleRead, ctrl_dat, 0);
de866d20 523 return;
0a0bf5db 524 }
901a2a3c 525 debug(50, 1) ("diskHandleRead: FD %d: %s\n", fd, xstrerror());
526 len = 0;
527 rc = DISK_ERROR;
090089c4 528 } else if (len == 0) {
901a2a3c 529 rc = DISK_EOF;
090089c4 530 }
901a2a3c 531 if (cbdataValid(ctrl_dat->client_data))
5d86029a 532 ctrl_dat->handler(fd, ctrl_dat->buf, len, rc, ctrl_dat->client_data);
901a2a3c 533 cbdataUnlock(ctrl_dat->client_data);
6a54c60e 534 safe_free(ctrl_dat);
090089c4 535}
536
537
538/* start read operation */
539/* buffer must be allocated from the caller.
540 * It must have at least req_len space in there.
541 * call handler when a reading is complete. */
b8d8561b 542int
d377699f 543file_read(int fd, char *buf, int req_len, off_t offset, DRCB * handler, void *client_data)
090089c4 544{
545 dread_ctrl *ctrl_dat;
711982d8 546 assert(fd >= 0);
30a4f2a8 547 ctrl_dat = xcalloc(1, sizeof(dread_ctrl));
090089c4 548 ctrl_dat->fd = fd;
549 ctrl_dat->offset = offset;
550 ctrl_dat->req_len = req_len;
551 ctrl_dat->buf = buf;
090089c4 552 ctrl_dat->end_of_file = 0;
553 ctrl_dat->handler = handler;
554 ctrl_dat->client_data = client_data;
901a2a3c 555 cbdataLock(client_data);
0a0bf5db 556#if USE_ASYNC_IO
557 diskHandleRead(fd, ctrl_dat);
558#else
b177367b 559 commSetSelect(fd,
234967c9 560 COMM_SELECT_READ,
cd1fb0eb 561 diskHandleRead,
562 ctrl_dat,
b177367b 563 0);
0a0bf5db 564#endif
090089c4 565 return DISK_OK;
566}
567
b8d8561b 568int
569diskWriteIsComplete(int fd)
b59c7120 570{
95d15928 571 return fd_table[fd].disk.write_q ? 0 : 1;
0a21bd84 572}