]>
Commit | Line | Data |
---|---|---|
cd748f27 | 1 | /* |
bde978a6 | 2 | * Copyright (C) 1996-2015 The Squid Software Foundation and contributors |
cd748f27 | 3 | * |
bbc27441 AJ |
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. | |
cd748f27 | 7 | */ |
8 | ||
bbc27441 AJ |
9 | /* DEBUG: section 79 Storage Manager UFS Interface */ |
10 | ||
582c2af2 | 11 | #include "squid.h" |
b9ae18aa | 12 | #include "DiskIO/DiskFile.h" |
13 | #include "DiskIO/DiskIOStrategy.h" | |
14 | #include "DiskIO/ReadRequest.h" | |
15 | #include "DiskIO/WriteRequest.h" | |
602d9612 | 16 | #include "Generic.h" |
41c97755 | 17 | #include "SquidList.h" |
602d9612 | 18 | #include "Store.h" |
2745fea5 | 19 | #include "store/Disk.h" |
58373ff8 | 20 | #include "UFSStoreState.h" |
602d9612 | 21 | #include "UFSStrategy.h" |
cd748f27 | 22 | |
58373ff8 | 23 | CBDATA_NAMESPACED_CLASS_INIT(Fs::Ufs,UFSStoreState); |
72711e31 | 24 | |
cd748f27 | 25 | void |
58373ff8 | 26 | Fs::Ufs::UFSStoreState::ioCompletedNotification() |
cd748f27 | 27 | { |
d3b3ab85 | 28 | if (opening) { |
62e76326 | 29 | opening = false; |
bf8fe701 | 30 | debugs(79, 3, "UFSStoreState::ioCompletedNotification: dirno " << |
31 | swap_dirn << ", fileno "<< std::setfill('0') << std::hex << | |
32 | std::setw(8) << swap_filen << " status "<< std::setfill(' ') << | |
33 | std::dec << theFile->error()); | |
34 | ||
59b2d47f | 35 | assert (FILE_MODE(mode) == O_RDONLY); |
36 | openDone(); | |
37 | ||
62e76326 | 38 | return; |
d3b3ab85 | 39 | } |
62e76326 | 40 | |
d3b3ab85 | 41 | if (creating) { |
62e76326 | 42 | creating = false; |
bf8fe701 | 43 | debugs(79, 3, "UFSStoreState::ioCompletedNotification: dirno " << |
44 | swap_dirn << ", fileno "<< std::setfill('0') << std::hex << | |
45 | std::setw(8) << swap_filen << " status "<< std::setfill(' ') << | |
46 | std::dec << theFile->error()); | |
47 | ||
59b2d47f | 48 | openDone(); |
49 | ||
62e76326 | 50 | return; |
d3b3ab85 | 51 | } |
62e76326 | 52 | |
59b2d47f | 53 | assert (!(closing ||opening)); |
bf8fe701 | 54 | debugs(79, 3, "diskd::ioCompleted: dirno " << swap_dirn << ", fileno "<< |
55 | std::setfill('0') << std::hex << std::setw(8) << swap_filen << | |
56 | " status "<< std::setfill(' ') << std::dec << theFile->error()); | |
57 | ||
59b2d47f | 58 | /* Ok, notification past open means an error has occured */ |
59 | assert (theFile->error()); | |
1e0d7905 | 60 | tryClosing(); |
d3b3ab85 | 61 | } |
cd748f27 | 62 | |
d3b3ab85 | 63 | void |
58373ff8 | 64 | Fs::Ufs::UFSStoreState::openDone() |
d3b3ab85 | 65 | { |
1e0d7905 | 66 | if (closing) |
fa84c01d | 67 | debugs(0, DBG_CRITICAL, HERE << "already closing in openDone()!?"); |
1e0d7905 | 68 | |
59b2d47f | 69 | if (theFile->error()) { |
1e0d7905 | 70 | tryClosing(); |
59b2d47f | 71 | return; |
72 | } | |
73 | ||
74 | if (FILE_MODE(mode) == O_WRONLY) { | |
1e0d7905 | 75 | drainWriteQueue(); |
76 | ||
59b2d47f | 77 | } else if ((FILE_MODE(mode) == O_RDONLY) && !closing) { |
78 | if (kickReadQueue()) | |
79 | return; | |
80 | } | |
81 | ||
1e0d7905 | 82 | if (flags.try_closing) |
83 | tryClosing(); | |
59b2d47f | 84 | |
bf8fe701 | 85 | debugs(79, 3, "UFSStoreState::openDone: exiting"); |
d3b3ab85 | 86 | } |
87 | ||
88 | void | |
58373ff8 | 89 | Fs::Ufs::UFSStoreState::closeCompleted() |
d3b3ab85 | 90 | { |
59b2d47f | 91 | assert (closing); |
bf8fe701 | 92 | debugs(79, 3, "UFSStoreState::closeCompleted: dirno " << swap_dirn << |
93 | ", fileno "<< std::setfill('0') << std::hex << std::setw(8) << | |
94 | swap_filen << " status "<< std::setfill(' ') << std::dec << | |
95 | theFile->error()); | |
62e76326 | 96 | |
1e0d7905 | 97 | if (theFile->error()) { |
98 | debugs(79,3,HERE<< "theFile->error() ret " << theFile->error()); | |
3e2cded3 | 99 | doCloseCallback(DISK_ERROR); |
1e0d7905 | 100 | } else { |
3e2cded3 | 101 | doCloseCallback(DISK_OK); |
1e0d7905 | 102 | } |
59b2d47f | 103 | |
104 | closing = false; | |
105 | } | |
106 | ||
1e0d7905 | 107 | /* |
108 | * DPW 2006-05-24 | |
109 | * This close function is called by the higher layer when it has finished | |
110 | * reading/writing everything, or otherwise wants to close the swap | |
111 | * file. In the case of writing and using aufs storage, close() might | |
112 | * be called before any/all data is written, and even before the open | |
113 | * callback occurs. Thus, we use our tryClosing() method, which knows | |
114 | * when it is safe to actually signal the lower layer for closing. | |
115 | */ | |
59b2d47f | 116 | void |
58373ff8 | 117 | Fs::Ufs::UFSStoreState::close(int) |
59b2d47f | 118 | { |
bf8fe701 | 119 | debugs(79, 3, "UFSStoreState::close: dirno " << swap_dirn << ", fileno "<< |
120 | std::setfill('0') << std::hex << std::uppercase << std::setw(8) << swap_filen); | |
aa1a691e | 121 | tryClosing(); // UFS does not distinguish different closure types |
d3b3ab85 | 122 | } |
123 | ||
124 | void | |
58373ff8 | 125 | Fs::Ufs::UFSStoreState::read_(char *buf, size_t size, off_t aOffset, STRCB * aCallback, void *aCallbackData) |
d3b3ab85 | 126 | { |
127 | assert(read.callback == NULL); | |
128 | assert(read.callback_data == NULL); | |
129 | assert(!reading); | |
130 | assert(!closing); | |
e4ae841b | 131 | assert (aCallback); |
62e76326 | 132 | |
d3b3ab85 | 133 | if (!theFile->canRead()) { |
bf8fe701 | 134 | debugs(79, 3, "UFSStoreState::read_: queueing read because theFile can't read"); |
18ec8500 | 135 | queueRead (buf, size, aOffset, aCallback, aCallbackData); |
62e76326 | 136 | return; |
cd748f27 | 137 | } |
62e76326 | 138 | |
e4ae841b FC |
139 | read.callback = aCallback; |
140 | read.callback_data = cbdataReference(aCallbackData); | |
bf8fe701 | 141 | debugs(79, 3, "UFSStoreState::read_: dirno " << swap_dirn << ", fileno "<< |
142 | std::setfill('0') << std::hex << std::uppercase << std::setw(8) << swap_filen); | |
18ec8500 | 143 | offset_ = aOffset; |
d3b3ab85 | 144 | read_buf = buf; |
145 | reading = true; | |
18ec8500 | 146 | theFile->read(new ReadRequest(buf,aOffset,size)); |
d3b3ab85 | 147 | } |
62e76326 | 148 | |
1e0d7905 | 149 | /* |
150 | * DPW 2006-05-24 | |
151 | * This, the public write interface, places the write request at the end | |
152 | * of the pending_writes queue to ensure correct ordering of writes. | |
153 | * We could optimize things a little if there are no other pending | |
154 | * writes and just do the write directly. But for now we'll keep the | |
155 | * code simpler and always go through the pending_writes queue. | |
156 | */ | |
90e8b325 | 157 | bool |
58373ff8 | 158 | Fs::Ufs::UFSStoreState::write(char const *buf, size_t size, off_t aOffset, FREE * free_func) |
d3b3ab85 | 159 | { |
bf8fe701 | 160 | debugs(79, 3, "UFSStoreState::write: dirn " << swap_dirn << ", fileno "<< |
161 | std::setfill('0') << std::hex << std::uppercase << std::setw(8) << swap_filen); | |
62e76326 | 162 | |
1e0d7905 | 163 | if (theFile->error()) { |
e0236918 FC |
164 | debugs(79, DBG_IMPORTANT,HERE << "avoid write on theFile with error"); |
165 | debugs(79, DBG_IMPORTANT,HERE << "calling free_func for " << (void*) buf); | |
1e0d7905 | 166 | free_func((void*)buf); |
90e8b325 | 167 | return false; |
1e0d7905 | 168 | } |
169 | ||
18ec8500 | 170 | queueWrite(buf, size, aOffset, free_func); |
1e0d7905 | 171 | drainWriteQueue(); |
90e8b325 | 172 | return true; |
1e0d7905 | 173 | } |
174 | ||
1e0d7905 | 175 | /* |
176 | * DPW 2006-05-24 | |
177 | * This, the private write method, calls the lower level write for the | |
178 | * first write request in the pending_writes queue. doWrite() is only | |
179 | * called by drainWriteQueue(). | |
180 | */ | |
181 | void | |
58373ff8 | 182 | Fs::Ufs::UFSStoreState::doWrite() |
1e0d7905 | 183 | { |
184 | debugs(79, 3, HERE << this << " UFSStoreState::doWrite"); | |
185 | ||
186 | assert(theFile->canWrite()); | |
187 | ||
188 | _queued_write *q = (_queued_write *)linklistShift(&pending_writes); | |
189 | ||
190 | if (q == NULL) { | |
191 | debugs(79, 3, HERE << this << " UFSStoreState::doWrite queue is empty"); | |
192 | return; | |
193 | } | |
194 | ||
195 | if (theFile->error()) { | |
e0236918 | 196 | debugs(79, DBG_IMPORTANT,HERE << "avoid write on theFile with error"); |
1e0d7905 | 197 | debugs(79,3,HERE << "calling free_func for " << (void*) q->buf); |
198 | /* | |
199 | * DPW 2006-05-24 | |
200 | * Note "free_func" is memNodeWriteComplete(), which doesn't | |
201 | * really free the memory. Instead it clears the node's | |
202 | * write_pending flag. | |
203 | */ | |
204 | q->free_func((void*)q->buf); | |
9db2e213 | 205 | delete q; |
62e76326 | 206 | return; |
d3b3ab85 | 207 | } |
62e76326 | 208 | |
1e0d7905 | 209 | /* |
210 | * DPW 2006-05-24 | |
211 | * UFSStoreState has a 'writing' flag that we used to set here, | |
212 | * but it wasn't really used anywhere. In fact, some lower | |
213 | * layers such as DISKD allow multiple outstanding writes, which | |
214 | * makes the boolean writing flag meaningless. We would need | |
215 | * a counter to keep track of writes going out and write callbacks | |
216 | * coming in. For now let's just not use the writing flag at | |
217 | * all. | |
218 | */ | |
219 | debugs(79, 3, HERE << this << " calling theFile->write(" << q->size << ")"); | |
220 | ||
221 | theFile->write(new WriteRequest(q->buf, q->offset, q->size, q->free_func)); | |
9db2e213 | 222 | delete q; |
d3b3ab85 | 223 | } |
224 | ||
225 | void | |
ced8def3 | 226 | Fs::Ufs::UFSStoreState::readCompleted(const char *buf, int len, int, RefCount<ReadRequest> result) |
d3b3ab85 | 227 | { |
b9ae18aa | 228 | assert (result.getRaw()); |
d3b3ab85 | 229 | reading = false; |
bf8fe701 | 230 | debugs(79, 3, "UFSStoreState::readCompleted: dirno " << swap_dirn << |
231 | ", fileno "<< std::setfill('0') << std::hex << std::setw(8) << | |
232 | swap_filen << " len "<< std::setfill(' ') << std::dec << len); | |
62e76326 | 233 | |
d3b3ab85 | 234 | if (len > 0) |
62e76326 | 235 | offset_ += len; |
236 | ||
e4ae841b | 237 | STRCB *callback_ = read.callback; |
62e76326 | 238 | |
e4ae841b | 239 | assert(callback_); |
62e76326 | 240 | |
d3b3ab85 | 241 | read.callback = NULL; |
62e76326 | 242 | |
d3b3ab85 | 243 | void *cbdata; |
62e76326 | 244 | |
59b2d47f | 245 | /* A note: |
246 | * diskd IO queues closes via the diskd queue. So close callbacks | |
247 | * occur strictly after reads and writes. | |
248 | * ufs doesn't queue, it simply completes, so close callbacks occur | |
249 | * strictly after reads and writes. | |
250 | * aufs performs closes syncronously, so close events must be managed | |
251 | * to force strict ordering. | |
252 | * The below does this: | |
1e0d7905 | 253 | * closing is set when theFile->close() has been called, and close only triggers |
59b2d47f | 254 | * when no io's are pending. |
255 | * writeCompleted likewise. | |
256 | */ | |
d3b3ab85 | 257 | if (!closing && cbdataReferenceValidDone(read.callback_data, &cbdata)) { |
62e76326 | 258 | if (len > 0 && read_buf != buf) |
259 | memcpy(read_buf, buf, len); | |
260 | ||
e4ae841b | 261 | callback_(cbdata, read_buf, len, this); |
1e0d7905 | 262 | } |
263 | ||
8bc0c94b | 264 | if (flags.try_closing || (theFile != NULL && theFile->error()) ) |
1e0d7905 | 265 | tryClosing(); |
d3b3ab85 | 266 | } |
267 | ||
268 | void | |
ced8def3 | 269 | Fs::Ufs::UFSStoreState::writeCompleted(int, size_t len, RefCount<WriteRequest>) |
d3b3ab85 | 270 | { |
983983ce | 271 | debugs(79, 3, HERE << "dirno " << swap_dirn << ", fileno " << |
26ac0430 | 272 | std::setfill('0') << std::hex << std::uppercase << std::setw(8) << swap_filen << |
983983ce | 273 | ", len " << len); |
1e0d7905 | 274 | /* |
275 | * DPW 2006-05-24 | |
276 | * See doWrites() for why we don't update UFSStoreState::writing | |
277 | * here anymore. | |
278 | */ | |
62e76326 | 279 | |
59b2d47f | 280 | offset_ += len; |
281 | ||
282 | if (theFile->error()) { | |
983983ce | 283 | debugs(79,2,HERE << " detected an error, will try to close"); |
1e0d7905 | 284 | tryClosing(); |
59b2d47f | 285 | } |
bdf5b250 | 286 | |
287 | /* | |
8c212619 HN |
288 | * HNO 2009-07-24 |
289 | * Kick any pending write/close operations alive | |
bdf5b250 | 290 | */ |
8c212619 | 291 | drainWriteQueue(); |
d3b3ab85 | 292 | } |
293 | ||
294 | void | |
58373ff8 | 295 | Fs::Ufs::UFSStoreState::doCloseCallback(int errflag) |
cd748f27 | 296 | { |
bf8fe701 | 297 | debugs(79, 3, "storeUfsIOCallback: errflag=" << errflag); |
1e0d7905 | 298 | /* |
299 | * DPW 2006-05-24 | |
300 | * When we signal the higher layer with this callback, it might unlock | |
301 | * the StoreEntry and its associated data. We must "free" any queued | |
302 | * I/Os (especially writes) now, otherwise the StoreEntry's mem_node's | |
303 | * will have their write_pending flag set, and we'll get an assertion. | |
304 | */ | |
305 | freePending(); | |
59b2d47f | 306 | STIOCB *theCallback = callback; |
d3b3ab85 | 307 | callback = NULL; |
d3b3ab85 | 308 | |
59b2d47f | 309 | void *cbdata; |
d3b3ab85 | 310 | |
59b2d47f | 311 | if (cbdataReferenceValidDone(callback_data, &cbdata) && theCallback) |
e5de8b13 | 312 | theCallback(cbdata, errflag, this); |
7fbb6b8f | 313 | |
1e0d7905 | 314 | /* |
315 | * We are finished with theFile since the lower layer signalled | |
316 | * us that the file has been closed. This must be the last line, | |
317 | * as theFile may be the only object holding us in memory. | |
7fbb6b8f | 318 | */ |
f53969cc | 319 | theFile = NULL; // refcounted |
59b2d47f | 320 | } |
d3b3ab85 | 321 | |
322 | /* ============= THE REAL UFS CODE ================ */ | |
323 | ||
cc8c4af2 AJ |
324 | Fs::Ufs::UFSStoreState::UFSStoreState(SwapDir * SD, StoreEntry * anEntry, STIOCB * cbIo, void *data) : |
325 | StoreIOState(NULL, cbIo, data), | |
326 | opening(false), | |
327 | creating(false), | |
328 | closing(false), | |
329 | reading(false), | |
330 | writing(false), | |
331 | pending_reads(NULL), | |
332 | pending_writes(NULL), | |
333 | read_buf(NULL) | |
59b2d47f | 334 | { |
cc8c4af2 | 335 | // StoreIOState inherited members |
59b2d47f | 336 | swap_filen = anEntry->swap_filen; |
337 | swap_dirn = SD->index; | |
59b2d47f | 338 | e = anEntry; |
cc8c4af2 AJ |
339 | |
340 | // our flags | |
1e0d7905 | 341 | flags.write_draining = false; |
342 | flags.try_closing = false; | |
59b2d47f | 343 | } |
62e76326 | 344 | |
58373ff8 | 345 | Fs::Ufs::UFSStoreState::~UFSStoreState() |
1e0d7905 | 346 | { |
347 | assert(pending_reads == NULL); | |
348 | assert(pending_writes == NULL); | |
349 | } | |
350 | ||
351 | void | |
58373ff8 | 352 | Fs::Ufs::UFSStoreState::freePending() |
d3b3ab85 | 353 | { |
354 | _queued_read *qr; | |
62e76326 | 355 | |
d3b3ab85 | 356 | while ((qr = (_queued_read *)linklistShift(&pending_reads))) { |
62e76326 | 357 | cbdataReferenceDone(qr->callback_data); |
358 | delete qr; | |
d3b3ab85 | 359 | } |
360 | ||
1e0d7905 | 361 | debugs(79,3,HERE << "UFSStoreState::freePending: freed pending reads"); |
362 | ||
5aeabf95 | 363 | _queued_write *qw; |
62e76326 | 364 | |
5aeabf95 | 365 | while ((qw = (_queued_write *)linklistShift(&pending_writes))) { |
62e76326 | 366 | if (qw->free_func) |
367 | qw->free_func(const_cast<char *>(qw->buf)); | |
368 | delete qw; | |
d3b3ab85 | 369 | } |
1e0d7905 | 370 | |
371 | debugs(79,3,HERE << "UFSStoreState::freePending: freed pending writes"); | |
d3b3ab85 | 372 | } |
373 | ||
374 | bool | |
58373ff8 | 375 | Fs::Ufs::UFSStoreState::kickReadQueue() |
d3b3ab85 | 376 | { |
377 | _queued_read *q = (_queued_read *)linklistShift(&pending_reads); | |
62e76326 | 378 | |
d3b3ab85 | 379 | if (NULL == q) |
62e76326 | 380 | return false; |
381 | ||
4a7a3d56 | 382 | debugs(79, 3, "UFSStoreState::kickReadQueue: reading queued request of " << q->size << " bytes"); |
62e76326 | 383 | |
d3b3ab85 | 384 | void *cbdata; |
62e76326 | 385 | |
dc5348e9 | 386 | if (cbdataReferenceValidDone(q->callback_data, &cbdata)) { |
62e76326 | 387 | read_(q->buf, q->size, q->offset, q->callback, cbdata); |
39d70ea8 | 388 | } else { |
dc5348e9 AJ |
389 | debugs(79, 2, "UFSStoreState::kickReadQueue: this: " << this << " cbdataReferenceValidDone returned false." << " closing: " << closing << " flags.try_closing: " << flags.try_closing); |
390 | delete q; | |
391 | return false; | |
392 | } | |
62e76326 | 393 | |
d3b3ab85 | 394 | delete q; |
62e76326 | 395 | |
d3b3ab85 | 396 | return true; |
397 | } | |
398 | ||
d3b3ab85 | 399 | void |
58373ff8 | 400 | Fs::Ufs::UFSStoreState::queueRead(char *buf, size_t size, off_t aOffset, STRCB *callback_, void *callback_data_) |
d3b3ab85 | 401 | { |
bf8fe701 | 402 | debugs(79, 3, "UFSStoreState::queueRead: queueing read"); |
d3b3ab85 | 403 | assert(opening); |
404 | assert (pending_reads == NULL); | |
405 | _queued_read *q = new _queued_read; | |
406 | q->buf = buf; | |
407 | q->size = size; | |
18ec8500 | 408 | q->offset = aOffset; |
e4ae841b FC |
409 | q->callback = callback_; |
410 | q->callback_data = cbdataReference(callback_data_); | |
d3b3ab85 | 411 | linklistPush(&pending_reads, q); |
412 | } | |
413 | ||
1e0d7905 | 414 | /* |
415 | * DPW 2006-05-24 | |
416 | * drainWriteQueue() is a loop around doWrite(). | |
417 | */ | |
418 | void | |
58373ff8 | 419 | Fs::Ufs::UFSStoreState::drainWriteQueue() |
cd748f27 | 420 | { |
2eaddcc2 | 421 | /* |
422 | * DPW 2007-04-12 | |
423 | * We might find that flags.write_draining is already set | |
424 | * because schemes like diskd can process I/O acks | |
425 | * before sending another I/O request. e.g. the following | |
426 | * sequence of events: open request -> write request -> | |
427 | * drainWriteQueue() -> queue full -> callbacks -> openDone() -> | |
428 | * drainWriteQueue(). | |
429 | */ | |
430 | if (flags.write_draining) | |
26ac0430 | 431 | return; |
62e76326 | 432 | |
1e0d7905 | 433 | if (!theFile->canWrite()) |
434 | return; | |
62e76326 | 435 | |
1e0d7905 | 436 | flags.write_draining = true; |
62e76326 | 437 | |
1e0d7905 | 438 | while (pending_writes != NULL) { |
439 | doWrite(); | |
440 | } | |
441 | ||
442 | flags.write_draining = false; | |
443 | ||
444 | if (flags.try_closing) | |
445 | tryClosing(); | |
446 | } | |
447 | ||
448 | /* | |
449 | * DPW 2006-05-24 | |
f53969cc | 450 | * This blows. DiskThreadsDiskFile::close() won't actually do the close |
1e0d7905 | 451 | * if ioInProgress() is true. So we have to check it here. Maybe someday |
452 | * DiskThreadsDiskFile::close() will be modified to have a return value, | |
453 | * or will remember to do the close for us. | |
454 | */ | |
455 | void | |
58373ff8 | 456 | Fs::Ufs::UFSStoreState::tryClosing() |
1e0d7905 | 457 | { |
458 | debugs(79,3,HERE << this << " tryClosing()" << | |
459 | " closing = " << closing << | |
460 | " flags.try_closing = " << flags.try_closing << | |
461 | " ioInProgress = " << theFile->ioInProgress()); | |
462 | ||
463 | if (theFile->ioInProgress()) { | |
26ac0430 AJ |
464 | debugs(79, 3, HERE << this << |
465 | " won't close since ioInProgress is true, bailing"); | |
1e0d7905 | 466 | flags.try_closing = true; |
467 | return; | |
468 | } | |
469 | ||
470 | closing = true; | |
471 | flags.try_closing = false; | |
472 | theFile->close(); | |
d3b3ab85 | 473 | } |
474 | ||
475 | void | |
58373ff8 | 476 | Fs::Ufs::UFSStoreState::queueWrite(char const *buf, size_t size, off_t aOffset, FREE * free_func) |
d3b3ab85 | 477 | { |
1e0d7905 | 478 | debugs(79, 3, HERE << this << " UFSStoreState::queueWrite: queueing write of size " << size); |
62e76326 | 479 | |
5aeabf95 | 480 | _queued_write *q; |
d3b3ab85 | 481 | q = new _queued_write; |
482 | q->buf = buf; | |
483 | q->size = size; | |
18ec8500 | 484 | q->offset = aOffset; |
d3b3ab85 | 485 | q->free_func = free_func; |
486 | linklistPush(&pending_writes, q); | |
487 | } | |
488 |