]>
Commit | Line | Data |
---|---|---|
4d6d905e | 1 | /* |
0aa2c37c | 2 | * $Id: ufscommon.cc,v 1.6 2003/01/09 11:49:35 hno Exp $ |
4d6d905e | 3 | * |
4 | * DEBUG: section 47 Store Directory Routines | |
5 | * AUTHOR: Duane Wessels | |
6 | * | |
7 | * SQUID Web Proxy Cache http://www.squid-cache.org/ | |
8 | * ---------------------------------------------------------- | |
9 | * | |
10 | * Squid is the result of efforts by numerous individuals from | |
11 | * the Internet community; see the CONTRIBUTORS file for full | |
12 | * details. Many organizations have provided support for Squid's | |
13 | * development; see the SPONSORS file for full details. Squid is | |
14 | * Copyrighted (C) 2001 by the Regents of the University of | |
15 | * California; see the COPYRIGHT file for full details. Squid | |
16 | * incorporates software developed and/or copyrighted by other | |
17 | * sources; see the CREDITS file for full details. | |
18 | * | |
19 | * This program is free software; you can redistribute it and/or modify | |
20 | * it under the terms of the GNU General Public License as published by | |
21 | * the Free Software Foundation; either version 2 of the License, or | |
22 | * (at your option) any later version. | |
23 | * | |
24 | * This program is distributed in the hope that it will be useful, | |
25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
27 | * GNU General Public License for more details. | |
28 | * | |
29 | * You should have received a copy of the GNU General Public License | |
30 | * along with this program; if not, write to the Free Software | |
31 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. | |
32 | * | |
33 | */ | |
34 | ||
35 | #include "ufscommon.h" | |
e6ccf245 | 36 | #include "Store.h" |
d3b3ab85 | 37 | #include "RefCount.h" |
4d6d905e | 38 | |
d3b3ab85 | 39 | CBDATA_CLASS_INIT(RebuildState); |
4d6d905e | 40 | |
d3b3ab85 | 41 | void * |
42 | RebuildState::operator new (size_t size) | |
4d6d905e | 43 | { |
d3b3ab85 | 44 | assert (size == sizeof(RebuildState)); |
45 | CBDATA_INIT_TYPE(RebuildState); | |
46 | RebuildState *result = cbdataAlloc(RebuildState); | |
47 | /* Mark result as being owned - we want the refcounter to do the delete | |
48 | * call */ | |
49 | cbdataReference(result); | |
50 | return result; | |
4d6d905e | 51 | } |
d3b3ab85 | 52 | |
4d6d905e | 53 | void |
d3b3ab85 | 54 | RebuildState::operator delete (void *address) |
4d6d905e | 55 | { |
d3b3ab85 | 56 | RebuildState *t = static_cast<RebuildState *>(address); |
57 | cbdataFree(address); | |
58 | /* And allow the memory to be freed */ | |
59 | cbdataReferenceDone (t); | |
4d6d905e | 60 | } |
61 | ||
62 | void | |
d3b3ab85 | 63 | RebuildState::deleteSelf() const |
4d6d905e | 64 | { |
d3b3ab85 | 65 | delete this; |
4d6d905e | 66 | } |
67 | ||
d3b3ab85 | 68 | RebuildState::~RebuildState() |
4d6d905e | 69 | { |
d3b3ab85 | 70 | store_dirs_rebuilding--; |
71 | sd->closeTmpSwapLog(); | |
72 | storeRebuildComplete(&counts); | |
4d6d905e | 73 | } |
74 | ||
75 | void | |
d3b3ab85 | 76 | RebuildState::RebuildFromDirectory(void *data) |
4d6d905e | 77 | { |
d3b3ab85 | 78 | RebuildState *rb = (RebuildState *)data; |
79 | rb->rebuildFromDirectory(); | |
4d6d905e | 80 | } |
81 | ||
82 | void | |
d3b3ab85 | 83 | RebuildState::rebuildFromDirectory() |
4d6d905e | 84 | { |
4d6d905e | 85 | LOCAL_ARRAY(char, hdr_buf, SM_PAGE_SIZE); |
86 | StoreEntry *e = NULL; | |
87 | StoreEntry tmpe; | |
88 | cache_key key[MD5_DIGEST_CHARS]; | |
4d6d905e | 89 | struct stat sb; |
90 | int swap_hdr_len; | |
91 | int fd = -1; | |
92 | tlv *tlv_list; | |
93 | tlv *t; | |
d3b3ab85 | 94 | assert(this != NULL); |
95 | debug(47, 3) ("commonUfsDirRebuildFromDirectory: DIR #%d\n", sd->index); | |
96 | for (int count = 0; count < speed; count++) { | |
4d6d905e | 97 | assert(fd == -1); |
d3b3ab85 | 98 | sfileno filn = 0; |
99 | int size; | |
100 | fd = getNextFile(&filn, &size); | |
4d6d905e | 101 | if (fd == -2) { |
102 | debug(47, 1) ("Done scanning %s swaplog (%d entries)\n", | |
d3b3ab85 | 103 | sd->path, n_read); |
104 | deleteSelf(); | |
4d6d905e | 105 | return; |
106 | } else if (fd < 0) { | |
107 | continue; | |
108 | } | |
109 | assert(fd > -1); | |
110 | /* lets get file stats here */ | |
111 | if (fstat(fd, &sb) < 0) { | |
112 | debug(47, 1) ("commonUfsDirRebuildFromDirectory: fstat(FD %d): %s\n", | |
113 | fd, xstrerror()); | |
114 | file_close(fd); | |
115 | store_open_disk_fd--; | |
116 | fd = -1; | |
117 | continue; | |
118 | } | |
d3b3ab85 | 119 | if ((++counts.scancount & 0xFFFF) == 0) |
4d6d905e | 120 | debug(47, 3) (" %s %7d files opened so far.\n", |
d3b3ab85 | 121 | sd->path, counts.scancount); |
4d6d905e | 122 | debug(47, 9) ("file_in: fd=%d %08X\n", fd, filn); |
123 | statCounter.syscalls.disk.reads++; | |
124 | if (FD_READ_METHOD(fd, hdr_buf, SM_PAGE_SIZE) < 0) { | |
125 | debug(47, 1) ("commonUfsDirRebuildFromDirectory: read(FD %d): %s\n", | |
126 | fd, xstrerror()); | |
127 | file_close(fd); | |
128 | store_open_disk_fd--; | |
129 | fd = -1; | |
130 | continue; | |
131 | } | |
132 | file_close(fd); | |
133 | store_open_disk_fd--; | |
134 | fd = -1; | |
135 | swap_hdr_len = 0; | |
136 | #if USE_TRUNCATE | |
137 | if (sb.st_size == 0) | |
138 | continue; | |
139 | #endif | |
140 | tlv_list = storeSwapMetaUnpack(hdr_buf, &swap_hdr_len); | |
141 | if (tlv_list == NULL) { | |
142 | debug(47, 1) ("commonUfsDirRebuildFromDirectory: failed to get meta data\n"); | |
143 | /* XXX shouldn't this be a call to commonUfsUnlink ? */ | |
d3b3ab85 | 144 | sd->unlinkFile (filn); |
4d6d905e | 145 | continue; |
146 | } | |
147 | debug(47, 3) ("commonUfsDirRebuildFromDirectory: successful swap meta unpacking\n"); | |
148 | memset(key, '\0', MD5_DIGEST_CHARS); | |
149 | memset(&tmpe, '\0', sizeof(StoreEntry)); | |
150 | for (t = tlv_list; t; t = t->next) { | |
151 | switch (t->type) { | |
152 | case STORE_META_KEY: | |
153 | assert(t->length == MD5_DIGEST_CHARS); | |
154 | xmemcpy(key, t->value, MD5_DIGEST_CHARS); | |
155 | break; | |
156 | case STORE_META_STD: | |
157 | assert(t->length == STORE_HDR_METASIZE); | |
158 | xmemcpy(&tmpe.timestamp, t->value, STORE_HDR_METASIZE); | |
159 | break; | |
160 | default: | |
161 | break; | |
162 | } | |
163 | } | |
164 | storeSwapTLVFree(tlv_list); | |
165 | tlv_list = NULL; | |
166 | if (storeKeyNull(key)) { | |
167 | debug(47, 1) ("commonUfsDirRebuildFromDirectory: NULL key\n"); | |
d3b3ab85 | 168 | sd->unlinkFile(filn); |
4d6d905e | 169 | continue; |
170 | } | |
332dafa2 | 171 | tmpe.key = key; |
4d6d905e | 172 | /* check sizes */ |
173 | if (tmpe.swap_file_sz == 0) { | |
933c4a31 | 174 | tmpe.swap_file_sz = (size_t) sb.st_size; |
e6ccf245 | 175 | } else if (tmpe.swap_file_sz == (size_t)(sb.st_size - swap_hdr_len)) { |
933c4a31 | 176 | tmpe.swap_file_sz = (size_t) sb.st_size; |
e6ccf245 | 177 | } else if (tmpe.swap_file_sz != (size_t)sb.st_size) { |
4d6d905e | 178 | debug(47, 1) ("commonUfsDirRebuildFromDirectory: SIZE MISMATCH %ld!=%ld\n", |
179 | (long int) tmpe.swap_file_sz, (long int) sb.st_size); | |
d3b3ab85 | 180 | sd->unlinkFile(filn); |
4d6d905e | 181 | continue; |
182 | } | |
183 | if (EBIT_TEST(tmpe.flags, KEY_PRIVATE)) { | |
d3b3ab85 | 184 | sd->unlinkFile(filn); |
185 | counts.badflags++; | |
4d6d905e | 186 | continue; |
187 | } | |
188 | e = storeGet(key); | |
189 | if (e && e->lastref >= tmpe.lastref) { | |
190 | /* key already exists, current entry is newer */ | |
191 | /* keep old, ignore new */ | |
d3b3ab85 | 192 | counts.dupcount++; |
4d6d905e | 193 | continue; |
194 | } else if (NULL != e) { | |
195 | /* URL already exists, this swapfile not being used */ | |
196 | /* junk old, load new */ | |
197 | storeRelease(e); /* release old entry */ | |
d3b3ab85 | 198 | counts.dupcount++; |
4d6d905e | 199 | } |
d3b3ab85 | 200 | counts.objcount++; |
4d6d905e | 201 | storeEntryDump(&tmpe, 5); |
d3b3ab85 | 202 | e = sd->addDiskRestore(key, |
4d6d905e | 203 | filn, |
204 | tmpe.swap_file_sz, | |
205 | tmpe.expires, | |
206 | tmpe.timestamp, | |
207 | tmpe.lastref, | |
208 | tmpe.lastmod, | |
209 | tmpe.refcount, /* refcount */ | |
210 | tmpe.flags, /* flags */ | |
d3b3ab85 | 211 | (int) flags.clean); |
4d6d905e | 212 | storeDirSwapLog(e, SWAP_LOG_ADD); |
213 | } | |
d3b3ab85 | 214 | eventAdd("storeRebuild", RebuildFromDirectory, this, 0.0, 1); |
4d6d905e | 215 | } |
216 | ||
217 | void | |
d3b3ab85 | 218 | RebuildState::RebuildFromSwapLog(void *data) |
4d6d905e | 219 | { |
e6ccf245 | 220 | RebuildState *rb = (RebuildState *)data; |
d3b3ab85 | 221 | rb->rebuildFromSwapLog(); |
222 | } | |
223 | ||
224 | void | |
225 | RebuildState::rebuildFromSwapLog() | |
226 | { | |
4d6d905e | 227 | StoreEntry *e = NULL; |
4d6d905e | 228 | double x; |
4d6d905e | 229 | /* load a number of objects per invocation */ |
d3b3ab85 | 230 | for (int count = 0; count < speed; count++) { |
231 | storeSwapLogData s; | |
232 | size_t ss = sizeof(storeSwapLogData); | |
233 | if (fread(&s, ss, 1, log) != 1) { | |
4d6d905e | 234 | debug(47, 1) ("Done reading %s swaplog (%d entries)\n", |
d3b3ab85 | 235 | sd->path, n_read); |
236 | fclose(log); | |
237 | log = NULL; | |
238 | delete this; | |
4d6d905e | 239 | return; |
240 | } | |
d3b3ab85 | 241 | n_read++; |
4d6d905e | 242 | if (s.op <= SWAP_LOG_NOP) |
243 | continue; | |
244 | if (s.op >= SWAP_LOG_MAX) | |
245 | continue; | |
246 | /* | |
247 | * BC: during 2.4 development, we changed the way swap file | |
248 | * numbers are assigned and stored. The high 16 bits used | |
249 | * to encode the SD index number. There used to be a call | |
250 | * to storeDirProperFileno here that re-assigned the index | |
251 | * bits. Now, for backwards compatibility, we just need | |
252 | * to mask it off. | |
253 | */ | |
254 | s.swap_filen &= 0x00FFFFFF; | |
255 | debug(47, 3) ("commonUfsDirRebuildFromSwapLog: %s %s %08X\n", | |
256 | swap_log_op_str[(int) s.op], | |
257 | storeKeyText(s.key), | |
258 | s.swap_filen); | |
259 | if (s.op == SWAP_LOG_ADD) { | |
260 | (void) 0; | |
261 | } else if (s.op == SWAP_LOG_DEL) { | |
0aa2c37c | 262 | /* Delete unless we already have a newer copy */ |
263 | if ((e = storeGet(s.key)) != NULL && s.lastref > e->lastref) { | |
4d6d905e | 264 | /* |
265 | * Make sure we don't unlink the file, it might be | |
266 | * in use by a subsequent entry. Also note that | |
267 | * we don't have to subtract from store_swap_size | |
268 | * because adding to store_swap_size happens in | |
269 | * the cleanup procedure. | |
270 | */ | |
271 | storeExpireNow(e); | |
272 | storeReleaseRequest(e); | |
273 | if (e->swap_filen > -1) { | |
d3b3ab85 | 274 | sd->replacementRemove(e); |
275 | sd->mapBitReset(e->swap_filen); | |
4d6d905e | 276 | e->swap_filen = -1; |
277 | e->swap_dirn = -1; | |
278 | } | |
279 | storeRelease(e); | |
d3b3ab85 | 280 | counts.objcount--; |
281 | counts.cancelcount++; | |
4d6d905e | 282 | } |
283 | continue; | |
284 | } else { | |
d3b3ab85 | 285 | x = ::log(++counts.bad_log_op) / ::log(10.0); |
4d6d905e | 286 | if (0.0 == x - (double) (int) x) |
287 | debug(47, 1) ("WARNING: %d invalid swap log entries found\n", | |
d3b3ab85 | 288 | counts.bad_log_op); |
289 | counts.invalid++; | |
4d6d905e | 290 | continue; |
291 | } | |
d3b3ab85 | 292 | if ((++counts.scancount & 0xFFF) == 0) { |
4d6d905e | 293 | struct stat sb; |
d3b3ab85 | 294 | if (0 == fstat(fileno(log), &sb)) |
295 | storeRebuildProgress(sd->index, | |
296 | (int) sb.st_size / ss, n_read); | |
4d6d905e | 297 | } |
d3b3ab85 | 298 | if (!sd->validFileno(s.swap_filen, 0)) { |
299 | counts.invalid++; | |
4d6d905e | 300 | continue; |
301 | } | |
302 | if (EBIT_TEST(s.flags, KEY_PRIVATE)) { | |
d3b3ab85 | 303 | counts.badflags++; |
4d6d905e | 304 | continue; |
305 | } | |
306 | e = storeGet(s.key); | |
d3b3ab85 | 307 | int used; /* is swapfile already in use? */ |
308 | used = sd->mapBitTest(s.swap_filen); | |
4d6d905e | 309 | /* If this URL already exists in the cache, does the swap log |
310 | * appear to have a newer entry? Compare 'lastref' from the | |
311 | * swap log to e->lastref. */ | |
d3b3ab85 | 312 | /* is the log entry newer than current entry? */ |
313 | int disk_entry_newer = e ? (s.lastref > e->lastref ? 1 : 0) : 0; | |
4d6d905e | 314 | if (used && !disk_entry_newer) { |
315 | /* log entry is old, ignore it */ | |
d3b3ab85 | 316 | counts.clashcount++; |
4d6d905e | 317 | continue; |
d3b3ab85 | 318 | } else if (used && e && e->swap_filen == s.swap_filen && e->swap_dirn == sd->index) { |
4d6d905e | 319 | /* swapfile taken, same URL, newer, update meta */ |
320 | if (e->store_status == STORE_OK) { | |
321 | e->lastref = s.timestamp; | |
322 | e->timestamp = s.timestamp; | |
323 | e->expires = s.expires; | |
324 | e->lastmod = s.lastmod; | |
325 | e->flags = s.flags; | |
326 | e->refcount += s.refcount; | |
d3b3ab85 | 327 | sd->dereference(*e); |
4d6d905e | 328 | } else { |
329 | debug_trap("commonUfsDirRebuildFromSwapLog: bad condition"); | |
330 | debug(47, 1) ("\tSee %s:%d\n", __FILE__, __LINE__); | |
331 | } | |
332 | continue; | |
333 | } else if (used) { | |
334 | /* swapfile in use, not by this URL, log entry is newer */ | |
335 | /* This is sorta bad: the log entry should NOT be newer at this | |
336 | * point. If the log is dirty, the filesize check should have | |
337 | * caught this. If the log is clean, there should never be a | |
338 | * newer entry. */ | |
339 | debug(47, 1) ("WARNING: newer swaplog entry for dirno %d, fileno %08X\n", | |
d3b3ab85 | 340 | sd->index, s.swap_filen); |
4d6d905e | 341 | /* I'm tempted to remove the swapfile here just to be safe, |
342 | * but there is a bad race condition in the NOVM version if | |
343 | * the swapfile has recently been opened for writing, but | |
344 | * not yet opened for reading. Because we can't map | |
345 | * swapfiles back to StoreEntrys, we don't know the state | |
346 | * of the entry using that file. */ | |
347 | /* We'll assume the existing entry is valid, probably because | |
348 | * were in a slow rebuild and the the swap file number got taken | |
349 | * and the validation procedure hasn't run. */ | |
d3b3ab85 | 350 | assert(flags.need_to_validate); |
351 | counts.clashcount++; | |
4d6d905e | 352 | continue; |
353 | } else if (e && !disk_entry_newer) { | |
354 | /* key already exists, current entry is newer */ | |
355 | /* keep old, ignore new */ | |
d3b3ab85 | 356 | counts.dupcount++; |
4d6d905e | 357 | continue; |
358 | } else if (e) { | |
359 | /* key already exists, this swapfile not being used */ | |
360 | /* junk old, load new */ | |
361 | storeExpireNow(e); | |
362 | storeReleaseRequest(e); | |
363 | if (e->swap_filen > -1) { | |
d3b3ab85 | 364 | sd->replacementRemove(e); |
4d6d905e | 365 | /* Make sure we don't actually unlink the file */ |
d3b3ab85 | 366 | sd->mapBitReset(e->swap_filen); |
4d6d905e | 367 | e->swap_filen = -1; |
368 | e->swap_dirn = -1; | |
369 | } | |
370 | storeRelease(e); | |
d3b3ab85 | 371 | counts.dupcount++; |
4d6d905e | 372 | } else { |
373 | /* URL doesnt exist, swapfile not in use */ | |
374 | /* load new */ | |
375 | (void) 0; | |
376 | } | |
377 | /* update store_swap_size */ | |
d3b3ab85 | 378 | counts.objcount++; |
379 | e = sd->addDiskRestore(s.key, | |
4d6d905e | 380 | s.swap_filen, |
381 | s.swap_file_sz, | |
382 | s.expires, | |
383 | s.timestamp, | |
384 | s.lastref, | |
385 | s.lastmod, | |
386 | s.refcount, | |
387 | s.flags, | |
d3b3ab85 | 388 | (int) flags.clean); |
4d6d905e | 389 | storeDirSwapLog(e, SWAP_LOG_ADD); |
390 | } | |
d3b3ab85 | 391 | eventAdd("storeRebuild", RebuildFromSwapLog, this, 0.0, 1); |
4d6d905e | 392 | } |
393 | ||
394 | int | |
d3b3ab85 | 395 | RebuildState::getNextFile(sfileno * filn_p, int *size) |
4d6d905e | 396 | { |
4d6d905e | 397 | int fd = -1; |
4d6d905e | 398 | int dirs_opened = 0; |
399 | debug(47, 3) ("commonUfsDirGetNextFile: flag=%d, %d: /%02X/%02X\n", | |
d3b3ab85 | 400 | flags.init, |
401 | sd->index, | |
402 | curlvl1, | |
403 | curlvl2); | |
404 | if (done) | |
4d6d905e | 405 | return -2; |
d3b3ab85 | 406 | while (fd < 0 && done == 0) { |
4d6d905e | 407 | fd = -1; |
d3b3ab85 | 408 | if (0 == flags.init) { /* initialize, open first file */ |
409 | done = 0; | |
410 | curlvl1 = 0; | |
411 | curlvl2 = 0; | |
412 | in_dir = 0; | |
413 | flags.init = 1; | |
4d6d905e | 414 | assert(Config.cacheSwap.n_configured > 0); |
415 | } | |
d3b3ab85 | 416 | if (0 == in_dir) { /* we need to read in a new directory */ |
417 | snprintf(fullpath, SQUID_MAXPATHLEN, "%s/%02X/%02X", | |
418 | sd->path, | |
419 | curlvl1, curlvl2); | |
4d6d905e | 420 | if (dirs_opened) |
421 | return -1; | |
d3b3ab85 | 422 | td = opendir(fullpath); |
4d6d905e | 423 | dirs_opened++; |
d3b3ab85 | 424 | if (td == NULL) { |
4d6d905e | 425 | debug(47, 1) ("commonUfsDirGetNextFile: opendir: %s: %s\n", |
d3b3ab85 | 426 | fullpath, xstrerror()); |
4d6d905e | 427 | } else { |
d3b3ab85 | 428 | entry = readdir(td); /* skip . and .. */ |
429 | entry = readdir(td); | |
430 | if (entry == NULL && errno == ENOENT) | |
4d6d905e | 431 | debug(47, 1) ("commonUfsDirGetNextFile: directory does not exist!.\n"); |
d3b3ab85 | 432 | debug(47, 3) ("commonUfsDirGetNextFile: Directory %s\n", fullpath); |
4d6d905e | 433 | } |
434 | } | |
d3b3ab85 | 435 | if (td != NULL && (entry = readdir(td)) != NULL) { |
436 | in_dir++; | |
437 | if (sscanf(entry->d_name, "%x", &fn) != 1) { | |
4d6d905e | 438 | debug(47, 3) ("commonUfsDirGetNextFile: invalid %s\n", |
d3b3ab85 | 439 | entry->d_name); |
4d6d905e | 440 | continue; |
441 | } | |
d3b3ab85 | 442 | if (!UFSSwapDir::FilenoBelongsHere(fn, sd->index, curlvl1, curlvl2)) { |
4d6d905e | 443 | debug(47, 3) ("commonUfsDirGetNextFile: %08X does not belong in %d/%d/%d\n", |
d3b3ab85 | 444 | fn, sd->index, curlvl1, curlvl2); |
4d6d905e | 445 | continue; |
446 | } | |
d3b3ab85 | 447 | if (sd->mapBitTest(fn)) { |
4d6d905e | 448 | debug(47, 3) ("commonUfsDirGetNextFile: Locked, continuing with next.\n"); |
449 | continue; | |
450 | } | |
d3b3ab85 | 451 | snprintf(fullfilename, SQUID_MAXPATHLEN, "%s/%s", |
452 | fullpath, entry->d_name); | |
453 | debug(47, 3) ("commonUfsDirGetNextFile: Opening %s\n", fullfilename); | |
454 | fd = file_open(fullfilename, O_RDONLY | O_BINARY); | |
4d6d905e | 455 | if (fd < 0) |
d3b3ab85 | 456 | debug(47, 1) ("commonUfsDirGetNextFile: %s: %s\n", fullfilename, xstrerror()); |
4d6d905e | 457 | else |
458 | store_open_disk_fd++; | |
459 | continue; | |
460 | } | |
d3b3ab85 | 461 | if (td != NULL) |
462 | closedir(td); | |
463 | td = NULL; | |
464 | in_dir = 0; | |
465 | if (sd->validL2(++curlvl2)) | |
4d6d905e | 466 | continue; |
d3b3ab85 | 467 | curlvl2 = 0; |
468 | if (sd->validL1(++curlvl1)) | |
4d6d905e | 469 | continue; |
d3b3ab85 | 470 | curlvl1 = 0; |
471 | done = 1; | |
4d6d905e | 472 | } |
d3b3ab85 | 473 | *filn_p = fn; |
4d6d905e | 474 | return fd; |
475 | } |