]> git.ipfire.org Git - thirdparty/squid.git/blame - src/disk.cc
remove OPTIMISTIC_IO crap
[thirdparty/squid.git] / src / disk.cc
CommitLineData
b224ea98 1
95e36d02 2
30a4f2a8 3/*
ea6ae5b4 4 * $Id: disk.cc,v 1.143 1999/05/03 20:39:31 wessels Exp $
30a4f2a8 5 *
6 * DEBUG: section 6 Disk I/O Routines
7 * AUTHOR: Harvest Derived
8 *
42c04c16 9 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
e25c139f 10 * ----------------------------------------------------------
30a4f2a8 11 *
12 * Squid is the result of efforts by numerous individuals from the
13 * Internet community. Development is led by Duane Wessels of the
e25c139f 14 * National Laboratory for Applied Network Research and funded by the
15 * National Science Foundation. Squid is Copyrighted (C) 1998 by
16 * Duane Wessels and the University of California San Diego. Please
17 * see the COPYRIGHT file for full details. Squid incorporates
18 * software developed and/or copyrighted by other sources. Please see
19 * the CREDITS file for full details.
30a4f2a8 20 *
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
25 *
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
30 *
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
cbdec147 33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
e25c139f 34 *
30a4f2a8 35 */
ed43818f 36
44a47c6e 37#include "squid.h"
090089c4 38
39#define DISK_LINE_LEN 1024
090089c4 40
0a0bf5db 41typedef struct open_ctrl_t {
f17936ab 42 FOCB *callback;
0a0bf5db 43 void *callback_data;
44 char *path;
45} open_ctrl_t;
46
de866d20 47static AIOCB diskHandleWriteComplete;
48static AIOCB diskHandleReadComplete;
95d15928 49static PF diskHandleRead;
50static PF diskHandleWrite;
ecefc661 51static AIOCB fileOpenComplete;
24382924 52
04cece06 53void
0673c0ba 54disk_init(void)
090089c4 55{
04cece06 56#if USE_ASYNC_IO
57 aioClose(dup(0));
58#endif
090089c4 59}
60
61/* Open a disk file. Return a file descriptor */
684c2720 62int
6cf028ab 63file_open(const char *path, int mode, FOCB * callback, void *callback_data, void *tag)
090089c4 64{
090089c4 65 int fd;
0a0bf5db 66 open_ctrl_t *ctrlp;
67
68 ctrlp = xmalloc(sizeof(open_ctrl_t));
69 ctrlp->path = xstrdup(path);
70 ctrlp->callback = callback;
71 ctrlp->callback_data = callback_data;
090089c4 72
b59c7120 73 if (mode & O_WRONLY)
74 mode |= O_APPEND;
4f92c80c 75 mode |= SQUID_NONBLOCK;
b59c7120 76
090089c4 77 /* Open file */
b6a2f15e 78 Opening_FD++;
0a0bf5db 79#if USE_ASYNC_IO
f17936ab 80 if (callback != NULL) {
ecefc661 81 aioOpen(path, mode, 0644, fileOpenComplete, ctrlp, tag);
9e4ad609 82 return DISK_OK;
0a0bf5db 83 }
f17936ab 84#endif
b870e0b4 85 errno = 0;
0a0bf5db 86 fd = open(path, mode, 0644);
ecefc661 87 fileOpenComplete(-1, ctrlp, fd, errno);
0a0bf5db 88 if (fd < 0)
89 return DISK_ERROR;
90 return fd;
0a0bf5db 91}
92
93
94static void
ecefc661 95fileOpenComplete(int unused, void *data, int fd, int errcode)
0a0bf5db 96{
97 open_ctrl_t *ctrlp = (open_ctrl_t *) data;
701a8572 98 debug(6, 5) ("fileOpenComplete: FD %d, data %p, errcode %d\n",
ecefc661 99 fd, data, errcode);
886f2785 100 Counter.syscalls.disk.opens++;
b6a2f15e 101 Opening_FD--;
0e473d70 102 if (fd == -2 && errcode == -2) { /* Cancelled - clean up */
6cf028ab 103 if (ctrlp->callback)
104 (ctrlp->callback) (ctrlp->callback_data, fd, errcode);
105 xfree(ctrlp->path);
106 xfree(ctrlp);
107 return;
108 }
0a0bf5db 109 if (fd < 0) {
110 errno = errcode;
ecefc661 111 debug(50, 3) ("fileOpenComplete: error opening file %s: %s\n", ctrlp->path,
0a0bf5db 112 xstrerror());
113 if (ctrlp->callback)
6cf028ab 114 (ctrlp->callback) (ctrlp->callback_data, DISK_ERROR, errcode);
0a0bf5db 115 xfree(ctrlp->path);
116 xfree(ctrlp);
117 return;
090089c4 118 }
ecefc661 119 debug(6, 5) ("fileOpenComplete: FD %d\n", fd);
3ca60c86 120 commSetCloseOnExec(fd);
5c5783a2 121 fd_open(fd, FD_FILE, ctrlp->path);
0a0bf5db 122 if (ctrlp->callback)
6cf028ab 123 (ctrlp->callback) (ctrlp->callback_data, fd, errcode);
0a0bf5db 124 xfree(ctrlp->path);
125 xfree(ctrlp);
126}
127
090089c4 128/* close a disk file. */
95d15928 129void
b8d8561b 130file_close(int fd)
090089c4 131{
76f87348 132 fde *F = &fd_table[fd];
65d548bf 133 PF *callback;
25354045 134#if USE_ASYNC_IO
0e473d70 135 if (fd < 0) {
6cf028ab 136 debug(6, 0) ("file_close: FD less than zero: %d\n", fd);
137 return;
138 }
25354045 139#else
140 assert(fd >= 0);
141#endif
60c0b5a2 142 assert(F->flags.open);
65d548bf 143 if ((callback = F->read_handler)) {
144 F->read_handler = NULL;
145 callback(-1, F->read_data);
146 }
0cd30ba5 147 if (F->flags.write_daemon) {
cd377065 148#if defined(_SQUID_MSWIN_) || defined(_SQUID_OS2_)
149 /*
150 * on some operating systems, you can not delete or rename
151 * open files, so we won't allow delayed close.
152 */
153 while (!diskWriteIsComplete(fd))
154 diskHandleWrite(fd, NULL);
155#else
0cd30ba5 156 F->flags.close_request = 1;
66979cec 157 debug(6, 2) ("file_close: FD %d, delaying close\n", fd);
95d15928 158 return;
cd377065 159#endif
fb247d78 160 }
65d548bf 161 /*
162 * Assert there is no write callback. Otherwise we might be
163 * leaking write state data by closing the descriptor
164 */
165 assert(F->write_handler == NULL);
ddb43c58 166 F->flags.closing = 1;
0a0bf5db 167#if USE_ASYNC_IO
95d15928 168 aioClose(fd);
0a0bf5db 169#else
42f99d0d 170#if CALL_FSYNC_BEFORE_CLOSE
171 fsync(fd);
172#endif
95d15928 173 close(fd);
0a0bf5db 174#endif
0cd30ba5 175 debug(6, F->flags.close_request ? 2 : 5)
5ac0b6e7 176 ("file_close: FD %d, really closing\n", fd);
ddb43c58 177#if !USE_ASYNC_IO
6cf028ab 178 fd_close(fd);
ddb43c58 179#endif
886f2785 180 Counter.syscalls.disk.closes++;
090089c4 181}
182
f02b8498 183/*
184 * This function has the purpose of combining multiple writes. This is
185 * to facilitate the ASYNC_IO option since it can only guarantee 1
186 * write to a file per trip around the comm.c select() loop. That's bad
187 * because more than 1 write can be made to the access.log file per
188 * trip, and so this code is purely designed to help batch multiple
189 * sequential writes to the access.log file. Squid will never issue
190 * multiple writes for any other file type during 1 trip around the
191 * select() loop. --SLF
192 */
582b6456 193static void
f02b8498 194diskCombineWrites(struct _fde_disk *fdd)
090089c4 195{
4a86108c 196 int len = 0;
0a0bf5db 197 dwrite_q *q = NULL;
198 dwrite_q *wq = NULL;
f02b8498 199 /*
200 * We need to combine multiple write requests on an FD's write
201 * queue But only if we don't need to seek() in between them, ugh!
202 * XXX This currently ignores any seeks (file_offset)
203 */
204 if (fdd->write_q != NULL && fdd->write_q->next != NULL) {
de866d20 205 len = 0;
f02b8498 206 for (q = fdd->write_q; q != NULL; q = q->next)
d377699f 207 len += q->len - q->buf_offset;
0a0bf5db 208 wq = xcalloc(1, sizeof(dwrite_q));
209 wq->buf = xmalloc(len);
210 wq->len = 0;
d377699f 211 wq->buf_offset = 0;
0a0bf5db 212 wq->next = NULL;
ed7f0b6a 213 wq->free_func = xfree;
0a0bf5db 214 do {
f02b8498 215 q = fdd->write_q;
d377699f 216 len = q->len - q->buf_offset;
217 xmemcpy(wq->buf + wq->len, q->buf + q->buf_offset, len);
0a0bf5db 218 wq->len += len;
f02b8498 219 fdd->write_q = q->next;
ed7f0b6a 220 if (q->free_func)
221 (q->free_func) (q->buf);
0a0bf5db 222 safe_free(q);
f02b8498 223 } while (fdd->write_q != NULL);
de866d20 224 fdd->write_q_tail = wq;
f02b8498 225 fdd->write_q = wq;
0a0bf5db 226 }
f02b8498 227}
228
229/* write handler */
230static void
231diskHandleWrite(int fd, void *notused)
232{
d1cbc844 233#if !USE_ASYNC_IO
f02b8498 234 int len = 0;
d1cbc844 235#endif
f02b8498 236 fde *F = &fd_table[fd];
237 struct _fde_disk *fdd = &F->disk;
238 if (!fdd->write_q)
239 return;
f02b8498 240 debug(6, 3) ("diskHandleWrite: FD %d\n", fd);
8350fe9b 241 assert(fdd->write_q != NULL);
d377699f 242 assert(fdd->write_q->len > fdd->write_q->buf_offset);
0a0bf5db 243#if USE_ASYNC_IO
244 aioWrite(fd,
5999b776 245 -1, /* seek offset, -1 == append */
d377699f 246 fdd->write_q->buf + fdd->write_q->buf_offset,
247 fdd->write_q->len - fdd->write_q->buf_offset,
0a0bf5db 248 diskHandleWriteComplete,
ecefc661 249 fdd->write_q);
0a0bf5db 250#else
ba7b771e 251 debug(6, 3) ("diskHandleWrite: FD %d writing %d bytes\n",
5f6ac48b 252 fd, (int) (fdd->write_q->len - fdd->write_q->buf_offset));
b870e0b4 253 errno = 0;
0a0bf5db 254 len = write(fd,
d377699f 255 fdd->write_q->buf + fdd->write_q->buf_offset,
256 fdd->write_q->len - fdd->write_q->buf_offset);
d1cbc844 257 diskHandleWriteComplete(fd, fdd->write_q, len, errno);
0a0bf5db 258#endif
259}
260
de866d20 261static void
ecefc661 262diskHandleWriteComplete(int fd, void *data, int len, int errcode)
0a0bf5db 263{
76f87348 264 fde *F = &fd_table[fd];
265 struct _fde_disk *fdd = &F->disk;
de866d20 266 dwrite_q *q = fdd->write_q;
267 int status = DISK_OK;
ba7b771e 268 int do_callback;
68c21f71 269 int do_close;
0a0bf5db 270 errno = errcode;
ba7b771e 271 debug(6, 3) ("diskHandleWriteComplete: FD %d len = %d\n", fd, len);
886f2785 272 Counter.syscalls.disk.writes++;
f0027316 273#if USE_ASYNC_IO
274/*
275 * From: "Michael O'Reilly" <michael@metal.iinet.net.au>
276 * Date: 24 Feb 1998 15:12:06 +0800
277 *
278 * A small patch to improve the AIO sanity. the patch below makes sure
279 * the write request really does match the data passed back from the
280 * async IO call. note that I haven't actually rebooted with this
281 * patch yet, so 'provisional' is an understatement.
282 */
ecefc661 283 if (q && q != data) {
284 dwrite_q *p = data;
f0027316 285 debug(50, 0) ("KARMA: q != data (%p, %p)\n", q, p);
286 debug(50, 0) ("KARMA: (%d, %d, %d FD %d)\n",
287 q->buf_offset, q->len, len, fd);
288 debug(50, 0) ("KARMA: desc %s, type %d, open %d, flags 0x%x\n",
98829dd0 289 F->desc, F->type, F->flags.open, F->flags);
f0027316 290 debug(50, 0) ("KARMA: (%d, %d)\n", p->buf_offset, p->len);
291 len = -1;
292 errcode = EFAULT;
293 }
294#endif
de866d20 295 if (q == NULL) /* Someone aborted then write completed */
296 return;
6cf028ab 297
0e473d70 298 if (len == -2 && errcode == -2) { /* Write cancelled - cleanup */
6cf028ab 299 do {
300 fdd->write_q = q->next;
ed7f0b6a 301 if (q->free_func)
302 (q->free_func) (q->buf);
6cf028ab 303 safe_free(q);
304 } while ((q = fdd->write_q));
305 return;
306 }
6cf028ab 307 fd_bytes(fd, len, FD_WRITE);
0a0bf5db 308 if (len < 0) {
b224ea98 309 if (!ignoreErrno(errno)) {
de866d20 310 status = errno == ENOSPC ? DISK_NO_SPACE_LEFT : DISK_ERROR;
a3d5953d 311 debug(50, 1) ("diskHandleWrite: FD %d: disk write error: %s\n",
0c77d853 312 fd, xstrerror());
25354045 313 /*
314 * If there is no write callback, then this file is
315 * most likely something important like a log file, or
316 * an interprocess pipe. Its not a swapfile. We feel
317 * that a write failure on a log file is rather important,
318 * and Squid doesn't otherwise deal with this condition.
319 * So to get the administrators attention, we exit with
320 * a fatal message.
321 */
322 if (fdd->wrt_handle == NULL)
323 fatal("Write failure -- check your disk space and cache.log");
324 /*
325 * If there is a write failure, then we notify the
326 * upper layer via the callback, at the end of this
327 * function. Meanwhile, flush all pending buffers
328 * here. Let the upper layer decide how to handle the
329 * failure. This will prevent experiencing multiple,
330 * repeated write failures for the same FD because of
331 * the queued data.
332 */
333 do {
334 fdd->write_q = q->next;
335 if (q->free_func)
336 (q->free_func) (q->buf);
337 safe_free(q);
338 } while ((q = fdd->write_q));
0c77d853 339 }
de866d20 340 len = 0;
0a0bf5db 341 }
8350fe9b 342 if (q != NULL) {
343 /* q might become NULL from write failure above */
d377699f 344 q->buf_offset += len;
6cf028ab 345 if (q->buf_offset > q->len)
346 debug(50, 1) ("diskHandleWriteComplete: q->buf_offset > q->len (%p,%d, %d, %d FD %d)\n",
5f6ac48b 347 q, (int) q->buf_offset, q->len, len, fd);
d377699f 348 assert(q->buf_offset <= q->len);
349 if (q->buf_offset == q->len) {
56878878 350 /* complete write */
351 fdd->write_q = q->next;
ed7f0b6a 352 if (q->free_func)
353 (q->free_func) (q->buf);
56878878 354 safe_free(q);
355 }
090089c4 356 }
de866d20 357 if (fdd->write_q == NULL) {
358 /* no more data */
359 fdd->write_q_tail = NULL;
0cd30ba5 360 F->flags.write_daemon = 0;
de866d20 361 } else {
0a0bf5db 362 /* another block is queued */
f02b8498 363 diskCombineWrites(fdd);
25354045 364 cbdataLock(fdd->wrt_handle_data);
de866d20 365 commSetSelect(fd, COMM_SELECT_WRITE, diskHandleWrite, NULL, 0);
0cd30ba5 366 F->flags.write_daemon = 1;
4a86108c 367 }
0cd30ba5 368 do_close = F->flags.close_request;
25354045 369 if (fdd->wrt_handle) {
ba7b771e 370 if (fdd->wrt_handle_data == NULL)
371 do_callback = 1;
372 else if (cbdataValid(fdd->wrt_handle_data))
373 do_callback = 1;
374 else
375 do_callback = 0;
25354045 376 if (fdd->wrt_handle_data != NULL)
377 cbdataUnlock(fdd->wrt_handle_data);
26720a7c 378 if (do_callback) {
ba7b771e 379 fdd->wrt_handle(fd, status, len, fdd->wrt_handle_data);
26720a7c 380 /*
381 * NOTE, this callback can close the FD, so we must
382 * not touch 'F', 'fdd', etc. after this.
383 */
384 return;
385 }
25354045 386 }
68c21f71 387 if (do_close)
b59c7120 388 file_close(fd);
090089c4 389}
390
391
090089c4 392/* write block to a file */
393/* write back queue. Only one writer at a time. */
394/* call a handle when writing is complete. */
e3ef2b09 395void
3ebcfaa1 396file_write(int fd,
d377699f 397 off_t file_offset,
5830cdb3 398 void *ptr_to_buf,
684c2720 399 int len,
d89d1fb6 400 DWCB handle,
684c2720 401 void *handle_data,
9e4ad609 402 FREE * free_func)
090089c4 403{
c6ac7aae 404 dwrite_q *wq = NULL;
48cc3fcf 405 fde *F = &fd_table[fd];
406 assert(fd >= 0);
60c0b5a2 407 assert(F->flags.open);
090089c4 408 /* if we got here. Caller is eligible to write. */
30a4f2a8 409 wq = xcalloc(1, sizeof(dwrite_q));
d377699f 410 wq->file_offset = file_offset;
090089c4 411 wq->buf = ptr_to_buf;
090089c4 412 wq->len = len;
d377699f 413 wq->buf_offset = 0;
090089c4 414 wq->next = NULL;
ed7f0b6a 415 wq->free_func = free_func;
76f87348 416 F->disk.wrt_handle = handle;
417 F->disk.wrt_handle_data = handle_data;
090089c4 418 /* add to queue */
48cc3fcf 419 if (F->disk.write_q == NULL) {
090089c4 420 /* empty queue */
76f87348 421 F->disk.write_q = F->disk.write_q_tail = wq;
090089c4 422 } else {
76f87348 423 F->disk.write_q_tail->next = wq;
424 F->disk.write_q_tail = wq;
090089c4 425 }
0cd30ba5 426 if (!F->flags.write_daemon) {
25354045 427 cbdataLock(F->disk.wrt_handle_data);
0a0bf5db 428#if USE_ASYNC_IO
95d15928 429 diskHandleWrite(fd, NULL);
0a0bf5db 430#else
e82d6d21 431 commSetSelect(fd, COMM_SELECT_WRITE, diskHandleWrite, NULL, 0);
9d66d521 432#endif
0cd30ba5 433 F->flags.write_daemon = 1;
429fdbec 434 }
090089c4 435}
436
23b2b404 437/*
438 * a wrapper around file_write to allow for MemBuf to be file_written
439 * in a snap
440 */
137ee196 441void
442file_write_mbuf(int fd, off_t off, MemBuf mb, DWCB * handler, void *handler_data)
443{
444 file_write(fd, off, mb.buf, mb.size, handler, handler_data, memBufFreeFunc(&mb));
445}
090089c4 446
447/* Read from FD */
582b6456 448static void
449diskHandleRead(int fd, void *data)
090089c4 450{
582b6456 451 dread_ctrl *ctrl_dat = data;
606bd3b5 452#if !USE_ASYNC_IO
edd2eb63 453 fde *F = &fd_table[fd];
090089c4 454 int len;
606bd3b5 455#endif
65d548bf 456 /*
457 * FD < 0 indicates premature close; we just have to free
458 * the state data.
459 */
460 if (fd < 0) {
db1cd23c 461 memFree(ctrl_dat, MEM_DREAD_CTRL);
65d548bf 462 return;
463 }
0a0bf5db 464#if USE_ASYNC_IO
465 aioRead(fd,
20f92343 466 ctrl_dat->offset,
711982d8 467 ctrl_dat->buf,
468 ctrl_dat->req_len,
0a0bf5db 469 diskHandleReadComplete,
23b2b404 470 ctrl_dat);
0a0bf5db 471#else
711982d8 472 if (F->disk.offset != ctrl_dat->offset) {
f2b30883 473 debug(6, 3) ("diskHandleRead: FD %d seeking to offset %d\n",
343c257b 474 fd, (int) ctrl_dat->offset);
711982d8 475 lseek(fd, ctrl_dat->offset, SEEK_SET); /* XXX ignore return? */
886f2785 476 Counter.syscalls.disk.seeks++;
556c33d5 477 F->disk.offset = ctrl_dat->offset;
711982d8 478 }
b870e0b4 479 errno = 0;
711982d8 480 len = read(fd, ctrl_dat->buf, ctrl_dat->req_len);
015b507a 481 if (len > 0)
e82d6d21 482 F->disk.offset += len;
ecefc661 483 diskHandleReadComplete(fd, ctrl_dat, len, errno);
090089c4 484#endif
0a0bf5db 485}
486
de866d20 487static void
ecefc661 488diskHandleReadComplete(int fd, void *data, int len, int errcode)
0a0bf5db 489{
23b2b404 490 dread_ctrl *ctrl_dat = data;
901a2a3c 491 int rc = DISK_OK;
886f2785 492 Counter.syscalls.disk.reads++;
0a0bf5db 493 errno = errcode;
0e473d70 494 if (len == -2 && errcode == -2) { /* Read cancelled - cleanup */
6cf028ab 495 cbdataUnlock(ctrl_dat->client_data);
db1cd23c 496 memFree(ctrl_dat, MEM_DREAD_CTRL);
6cf028ab 497 return;
498 }
4f92c80c 499 fd_bytes(fd, len, FD_READ);
0a0bf5db 500 if (len < 0) {
b224ea98 501 if (ignoreErrno(errno)) {
901a2a3c 502 commSetSelect(fd, COMM_SELECT_READ, diskHandleRead, ctrl_dat, 0);
de866d20 503 return;
0a0bf5db 504 }
901a2a3c 505 debug(50, 1) ("diskHandleRead: FD %d: %s\n", fd, xstrerror());
506 len = 0;
507 rc = DISK_ERROR;
090089c4 508 } else if (len == 0) {
901a2a3c 509 rc = DISK_EOF;
090089c4 510 }
901a2a3c 511 if (cbdataValid(ctrl_dat->client_data))
5d86029a 512 ctrl_dat->handler(fd, ctrl_dat->buf, len, rc, ctrl_dat->client_data);
901a2a3c 513 cbdataUnlock(ctrl_dat->client_data);
db1cd23c 514 memFree(ctrl_dat, MEM_DREAD_CTRL);
090089c4 515}
516
517
518/* start read operation */
519/* buffer must be allocated from the caller.
520 * It must have at least req_len space in there.
521 * call handler when a reading is complete. */
b8d8561b 522int
d377699f 523file_read(int fd, char *buf, int req_len, off_t offset, DRCB * handler, void *client_data)
090089c4 524{
525 dread_ctrl *ctrl_dat;
711982d8 526 assert(fd >= 0);
0a5bc521 527 ctrl_dat = memAllocate(MEM_DREAD_CTRL);
090089c4 528 ctrl_dat->fd = fd;
529 ctrl_dat->offset = offset;
530 ctrl_dat->req_len = req_len;
531 ctrl_dat->buf = buf;
090089c4 532 ctrl_dat->end_of_file = 0;
533 ctrl_dat->handler = handler;
534 ctrl_dat->client_data = client_data;
901a2a3c 535 cbdataLock(client_data);
0a0bf5db 536#if USE_ASYNC_IO
537 diskHandleRead(fd, ctrl_dat);
538#else
b177367b 539 commSetSelect(fd,
234967c9 540 COMM_SELECT_READ,
cd1fb0eb 541 diskHandleRead,
542 ctrl_dat,
b177367b 543 0);
0a0bf5db 544#endif
090089c4 545 return DISK_OK;
546}
547
b8d8561b 548int
549diskWriteIsComplete(int fd)
b59c7120 550{
95d15928 551 return fd_table[fd].disk.write_q ? 0 : 1;
0a21bd84 552}