Annotation of libaitsync/src/aitsync.c, revision 1.6
1.1 misho 1: /*************************************************************************
2: * (C) 2010 AITNET ltd - Sofia/Bulgaria - <misho@aitbg.com>
3: * by Michael Pounov <misho@openbsd-bg.org>
4: *
5: * $Author: misho $
1.6 ! misho 6: * $Id: aitsync.c,v 1.5.2.1 2014/02/04 16:39:00 misho Exp $
1.1 misho 7: *
1.2 misho 8: **************************************************************************
9: The ELWIX and AITNET software is distributed under the following
10: terms:
11:
12: All of the documentation and software included in the ELWIX and AITNET
13: Releases is copyrighted by ELWIX - Sofia/Bulgaria <info@elwix.org>
14:
1.6 ! misho 15: Copyright 2004 - 2014
1.2 misho 16: by Michael Pounov <misho@elwix.org>. All rights reserved.
17:
18: Redistribution and use in source and binary forms, with or without
19: modification, are permitted provided that the following conditions
20: are met:
21: 1. Redistributions of source code must retain the above copyright
22: notice, this list of conditions and the following disclaimer.
23: 2. Redistributions in binary form must reproduce the above copyright
24: notice, this list of conditions and the following disclaimer in the
25: documentation and/or other materials provided with the distribution.
26: 3. All advertising materials mentioning features or use of this software
27: must display the following acknowledgement:
28: This product includes software developed by Michael Pounov <misho@elwix.org>
29: ELWIX - Embedded LightWeight unIX and its contributors.
30: 4. Neither the name of AITNET nor the names of its contributors
31: may be used to endorse or promote products derived from this software
32: without specific prior written permission.
33:
34: THIS SOFTWARE IS PROVIDED BY AITNET AND CONTRIBUTORS ``AS IS'' AND
35: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
36: IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
37: ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
38: FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
39: DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
40: OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
41: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
42: LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
43: OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44: SUCH DAMAGE.
45: */
1.1 misho 46: #include "global.h"
47: #include "tool.h"
48: #include "zc.h"
49: #include "patch.h"
50: #include "file.h"
51:
52:
53: static int sync_Errno;
54: static char sync_Error[STRSIZ];
55:
56:
1.3 misho 57: static inline int
58: func_comp(sync_tag_t const *t1, sync_tag_t const *t2)
1.1 misho 59: {
60: return t1->st_tag - t2->st_tag;
61: }
62:
1.4 misho 63: /*
64: * Error maintenance functions ...
65: */
1.1 misho 66:
67: // sync_GetErrno() Get error code of last operation
1.5 misho 68: int
1.3 misho 69: sync_GetErrno()
1.1 misho 70: {
71: return sync_Errno;
72: }
73:
74: // sync_GetError() Get error text of last operation
1.5 misho 75: const char *
1.3 misho 76: sync_GetError()
1.1 misho 77: {
78: return sync_Error;
79: }
80:
81: // sync_SetErr() Set error to variables for internal use!!!
1.5 misho 82: void
1.4 misho 83: sync_SetErr(int eno, char *estr, ...)
1.1 misho 84: {
85: va_list lst;
86:
87: sync_Errno = eno;
1.4 misho 88: memset(sync_Error, 0, sizeof sync_Error);
1.1 misho 89: va_start(lst, estr);
1.4 misho 90: vsnprintf(sync_Error, sizeof sync_Error, estr, lst);
1.1 misho 91: va_end(lst);
92: }
93:
1.4 misho 94: /* ---------------------------------------------------------- */
1.1 misho 95:
96: /*
1.4 misho 97: * syncSignature() - Calculate and create signature for diff
98: *
99: * @csInput = Input target file name for calculating check sums
1.1 misho 100: * @csSig = Output Signature file name
1.2 misho 101: * @compress = 2 compress signatures output, 0 not compressed
1.1 misho 102: * return: -1 error, 0 ok
103: */
1.3 misho 104: int
105: syncSignature(const char *csInput, const char *csSig, int compress)
1.1 misho 106: {
1.2 misho 107: int inf, outf, f, ret;
1.1 misho 108: u_char buf[CHUNK_MAX];
109: register int i = 0;
110: off_t off = 0ll;
111: sync_chunk_t sc;
1.2 misho 112: char szTemp[MAXPATHLEN];
1.1 misho 113:
1.4 misho 114: /* open work files */
115: inf = sync_Open(csInput, O_RDONLY, 0);
1.1 misho 116: if (inf == -1)
117: return inf;
1.2 misho 118: if (compress & 2)
1.4 misho 119: f = sync_Temp(szTemp, sizeof szTemp);
1.2 misho 120: else
1.4 misho 121: f = sync_Open(csSig, O_WRONLY, 0);
1.2 misho 122: if (f == -1) {
1.4 misho 123: sync_Close(inf);
1.2 misho 124: return f;
1.1 misho 125: }
126:
127: for (i = 0, off = 0ll, ret = -1; ret; i++, off += ret) {
128: memset(buf, 0, CHUNK_MAX);
129: ret = read(inf, buf, CHUNK_MAX);
130: if (ret == -1) {
1.4 misho 131: LOGERR;
1.1 misho 132: break;
133: }
134:
1.4 misho 135: /* fill chunk */
1.1 misho 136: sync_mksig(i, off, buf, ret, &sc);
137:
1.2 misho 138: if (write(f, &sc, sizeof sc) == -1) {
1.4 misho 139: LOGERR;
1.1 misho 140: break;
141: }
142: }
143:
1.4 misho 144: /* Signatures are READY */
1.2 misho 145:
146: if (compress & 2) {
1.4 misho 147: /* build compressed delta file */
148: outf = sync_Open(csSig, O_WRONLY, 0);
1.2 misho 149: if (outf == -1) {
150: ret = outf;
151: goto end;
152: }
153: if (sync_Deflate(f, outf, Z_DEFAULT_COMPRESSION) == -1) {
1.4 misho 154: sync_Close(outf);
1.2 misho 155: unlink(csSig);
156: ret = -1;
157: goto end;
158: }
1.4 misho 159: sync_Close(outf);
1.2 misho 160: }
161: end:
1.4 misho 162: sync_Close(f);
1.2 misho 163: if (compress & 2)
164: unlink(szTemp);
1.4 misho 165: sync_Close(inf);
1.1 misho 166: return ret;
167: }
168:
169: /*
1.4 misho 170: * syncDelta() - Create Delta patch file
171: *
1.1 misho 172: * @csInput = Input original source file name for make delta patch file
1.4 misho 173: * @csSig = Input target Signature file name
1.1 misho 174: * @csDelta = Output Delta patch file name
1.2 misho 175: * @compress = 3 everything compress, 2 compressed signatures, 1 compress delta output, 0 not compressed
1.1 misho 176: * return: -1 error, 0 ok
177: */
1.3 misho 178: int
179: syncDelta(const char *csInput, const char *csSig, const char *csDelta, int compress)
1.1 misho 180: {
181: int inf, outf, f, sigf, ret, cnt;
182: size_t blk;
183: register int i, j, c, cx;
184: struct stat sb, sb_f;
185: u_long tags[TABLESIZ];
186: sync_tag_t *tag_table;
187: sync_chunk_t *chunks, *find, sc;
188: u_char buf[CHUNK_MAX];
189: off_t off;
190: char szTemp[MAXPATHLEN];
191:
192: /* load signatures */
193:
1.2 misho 194: if (compress & 2) {
1.4 misho 195: f = sync_Open(csSig, O_RDONLY, 0);
1.2 misho 196: if (-1 == f)
197: return f;
1.4 misho 198: sigf = sync_Temp(szTemp, sizeof szTemp);
1.2 misho 199: if (-1 == sigf) {
1.4 misho 200: sync_Close(f);
1.2 misho 201: return sigf;
202: }
203:
204: if (sync_Inflate(f, sigf) == -1) {
1.4 misho 205: sync_Close(sigf);
206: sync_Close(f);
1.2 misho 207: unlink(szTemp);
208: return -1;
209: } else
1.4 misho 210: sync_Close(f);
1.2 misho 211: } else {
1.4 misho 212: sigf = sync_Open(csSig, O_RDONLY, 0);
1.2 misho 213: if (-1 == sigf)
214: return sigf;
1.1 misho 215: }
1.2 misho 216:
1.1 misho 217: if (fstat(sigf, &sb) == -1) {
1.4 misho 218: LOGERR;
219: sync_Close(sigf);
1.2 misho 220: if (compress & 2)
221: unlink(szTemp);
1.1 misho 222: return -1;
223: } else {
224: if (!sb.st_size) {
1.4 misho 225: sync_Close(sigf);
1.2 misho 226: if (compress & 2)
227: unlink(szTemp);
1.1 misho 228: return 1;
229: }
230:
231: cnt = sb.st_size / sizeof(sync_chunk_t);
232: if (sb.st_size % sizeof(sync_chunk_t)) {
1.4 misho 233: sync_SetErr(ENOEXEC, "Error:: signature file is broken!\n");
234: sync_Close(sigf);
1.2 misho 235: if (compress & 2)
236: unlink(szTemp);
1.1 misho 237: return -1;
238: }
239: }
240: chunks = (sync_chunk_t*) mmap(0, sb.st_size, PROT_READ, MAP_PRIVATE, sigf, 0);
241: if (MAP_FAILED == chunks) {
1.4 misho 242: LOGERR;
243: sync_Close(sigf);
1.2 misho 244: if (compress & 2)
245: unlink(szTemp);
1.1 misho 246: return -1;
1.2 misho 247: } else {
1.4 misho 248: sync_Close(sigf);
1.2 misho 249: if (compress & 2)
250: unlink(szTemp);
1.1 misho 251: }
252:
253: /* build from signatures sorted index and hashes */
254:
1.4 misho 255: /* init first stage tags array index */
1.1 misho 256: for (i = 0; i < TABLESIZ; i++)
257: tags[i] = NULL_TAG;
258:
1.4 misho 259: /* build second index from signature blocks */
1.6 ! misho 260: tag_table = (sync_tag_t*) e_calloc(cnt, sizeof(sync_tag_t));
1.1 misho 261: if (!tag_table) {
1.4 misho 262: LOGERR;
1.1 misho 263: munmap(chunks, sb.st_size);
264: return -1;
265: } else {
266: for (i = 0; i < cnt; i++) {
267: tag_table[i].st_id = i;
268: tag_table[i].st_tag = GETTAG(chunks[i].sc_roll);
269: }
270:
1.4 misho 271: qsort(tag_table, cnt, sizeof(sync_tag_t),
272: (int (*)(const void *, const void *)) func_comp);
1.1 misho 273: }
1.4 misho 274: /* assign less id position in tag_table to tags.
275: * It made relation between 1st & 2nd indexes */
1.1 misho 276: for (i = cnt - 1; i > -1; i--)
277: tags[tag_table[i].st_tag] = i;
278:
1.4 misho 279:
1.1 misho 280: /* build delta patch */
281:
1.4 misho 282: inf = sync_Open(csInput, O_RDONLY, 0);
1.1 misho 283: if (inf == -1) {
1.6 ! misho 284: e_free(tag_table);
1.1 misho 285: munmap(chunks, sb.st_size);
286: return inf;
287: }
1.2 misho 288: if (compress & 1)
1.4 misho 289: f = sync_Temp(szTemp, sizeof szTemp);
1.1 misho 290: else
1.4 misho 291: f = sync_Open(csDelta, O_WRONLY, 0);
1.1 misho 292: if (f == -1) {
1.4 misho 293: sync_Close(inf);
1.6 ! misho 294: e_free(tag_table);
1.1 misho 295: munmap(chunks, sb.st_size);
296: return f;
297: }
298:
1.4 misho 299: for (i = 0, off = 0ll, ret = -1, blk = 0;
300: (ret = read(inf, buf, CHUNK_MAX)); i++, off += ret) {
1.1 misho 301: if (ret == -1) {
1.4 misho 302: LOGERR;
1.1 misho 303: break;
304: }
305: find = NULL;
306:
1.4 misho 307: #if 0
308: printf("+ find=%p off=%llu i=%d blk=%d\n", find, off, i, blk);
309: #endif
1.1 misho 310:
1.4 misho 311: /* check chunk for differences with signature */
1.1 misho 312: sync_mksig(i, off, buf, ret, &sc);
313: cx = GETTAG(sc.sc_roll);
1.4 misho 314: /* find in hash -> hash_sorted_table */
1.1 misho 315: if (NULL_TAG != tags[cx] && tag_table[tags[cx]].st_tag == cx) {
1.4 misho 316: /* find in hash_sorted_table crc == -> real chunks id */
317: for (j = 0, c = tag_table[tags[cx]].st_id;
318: tag_table[tags[cx] + j].st_tag == cx;
1.1 misho 319: j++, c = tag_table[tags[cx] + j].st_id) {
1.4 misho 320: if (chunks[c].sc_magic == sc.sc_magic &&
321: chunks[c].sc_len == sc.sc_len &&
1.1 misho 322: chunks[c].sc_roll == sc.sc_roll &&
323: !memcmp(chunks[c].sc_cksum, sc.sc_cksum, MD5_DIGEST_LENGTH)) {
324: find = &chunks[c];
325: break;
326: }
327: }
328: }
329:
1.4 misho 330: #if 0
331: printf("+ find=%p off=%llu i=%d blk=%d\n", find, off, i, blk);
332: #endif
1.1 misho 333:
1.4 misho 334: /* if match chunk, check for previous match */
1.1 misho 335: if (!blk && find)
336: continue;
1.4 misho 337: /* if not find chunk in signature skip write to delta patch */
1.1 misho 338: if (!find) {
1.4 misho 339: /* different piece, write it!
340: * Write signature of current chunk */
1.1 misho 341: ret = write(f, &sc, sizeof sc);
342: if (-1 == ret) {
1.4 misho 343: LOGERR;
1.1 misho 344: break;
345: }
1.4 misho 346: /* if write chunk len is differnt from requested len */
1.1 misho 347: if (ret != sizeof sc) {
1.4 misho 348: sync_SetErr(ENOEXEC, "Error:: delta file signature is broken!\n");
1.1 misho 349: ret = -1;
350: break;
351: }
1.4 misho 352: /* write current chunk data ... */
1.1 misho 353: ret = write(f, buf, sc.sc_len);
354: if (-1 == ret) {
1.4 misho 355: LOGERR;
1.1 misho 356: break;
357: }
1.4 misho 358: /* if write chunk len is differnt from requested len */
1.1 misho 359: if (ret != sc.sc_len) {
1.4 misho 360: sync_SetErr(ENOEXEC, "Error:: delta file data is broken!\n");
1.1 misho 361: ret = -1;
362: break;
363: }
364: blk += sc.sc_len;
365:
366: continue;
367: }
1.4 misho 368: /* match 1st block after difference and copy signature from B */
1.1 misho 369: memcpy(&sc, find, sizeof sc);
370: sc.sc_magic = SIGSYNC_MAGIC;
371: sc.sc_len = blk;
372:
1.4 misho 373: /* write signature from chunk B */
1.1 misho 374: blk = write(f, &sc, sizeof sc);
375: if (-1 == blk) {
1.4 misho 376: LOGERR;
1.1 misho 377: break;
378: }
1.4 misho 379: /* if write chunk len is differnt from requested len */
1.1 misho 380: if (blk != sizeof sc) {
1.4 misho 381: sync_SetErr(ENOEXEC, "Error:: delta file end signature is broken!\n");
1.1 misho 382: ret = -1;
383: break;
384: }
385:
386: blk ^= blk;
387: }
388:
1.4 misho 389: /* check for error or empty delta file */
1.1 misho 390: if (ret == -1)
391: goto end;
392: fsync(f);
393: if (fstat(f, &sb_f) == -1) {
1.4 misho 394: LOGERR;
1.1 misho 395: ret = -1;
396: goto end;
397: }
398:
1.4 misho 399: /* No deferences, not needed delta.patch !!! */
1.1 misho 400: if (!sb_f.st_size) {
401: ret = 1;
402: goto end;
403: }
404:
405: /* Delta patch is READY */
406:
1.4 misho 407: /* build compressed delta file */
1.2 misho 408: if (compress & 1) {
1.4 misho 409: outf = sync_Open(csDelta, O_WRONLY, 0);
1.1 misho 410: if (outf == -1) {
411: ret = outf;
412: goto end;
413: }
414: if (sync_Deflate(f, outf, Z_DEFAULT_COMPRESSION) == -1) {
1.4 misho 415: sync_Close(outf);
1.1 misho 416: unlink(csDelta);
417: ret = -1;
418: goto end;
419: }
1.4 misho 420: sync_Close(outf);
1.1 misho 421: }
422:
423: end:
1.4 misho 424: sync_Close(f);
1.2 misho 425: if (compress & 1)
426: unlink(szTemp);
1.4 misho 427: sync_Close(inf);
1.6 ! misho 428: e_free(tag_table);
1.1 misho 429: munmap(chunks, sb.st_size);
430: return ret;
431: }
432:
433: /*
1.4 misho 434: * syncPatch() - Apply delta patch file to target
435: *
1.1 misho 436: * @csInput = Input target file name for patch
437: * @csDelta = Input Delta patch file name
438: * @csPatch = After applied patch create new alternate target file, if != NULL
1.2 misho 439: * @compress = 1 compress delta input, 0 not compressed
1.1 misho 440: * return: -1 error, 0 ok, create delta patch, 1 ok, no differences and not create patch
441: */
1.3 misho 442: int
443: syncPatch(const char *csInput, const char *csDelta, const char *csPatch, int compress)
1.1 misho 444: {
445: int inf, outf, f, d, ret, readlen;
446: char szTemp[MAXPATHLEN];
447: u_char *buffer, buf[CHUNK_MAX];
448: struct stat sb;
449: void *delta;
450: struct tagPiece *piece, *pieces = NULL;
451: register int i;
452: off_t off;
453: sync_chunk_t sc, *suffix;
454:
1.2 misho 455: if (compress & 1) {
1.4 misho 456: f = sync_Open(csDelta, O_RDONLY, 0);
1.1 misho 457: if (f == -1)
458: return f;
1.4 misho 459: d = sync_Temp(szTemp, sizeof szTemp);
1.1 misho 460: if (d == -1) {
1.4 misho 461: sync_Close(f);
1.1 misho 462: return d;
463: }
464:
465: if (sync_Inflate(f, d) == -1) {
1.4 misho 466: sync_Close(d);
467: sync_Close(f);
1.1 misho 468: unlink(szTemp);
469: return -1;
470: } else
1.4 misho 471: sync_Close(f);
1.1 misho 472: } else {
1.4 misho 473: d = sync_Open(csDelta, O_RDONLY, 0);
1.1 misho 474: if (d == -1)
475: return d;
476: }
477:
478: if (fstat(d, &sb) == -1) {
1.4 misho 479: LOGERR;
480: sync_Close(d);
1.2 misho 481: if (compress & 1)
1.1 misho 482: unlink(szTemp);
483: return -1;
484: }
485: delta = mmap(0, sb.st_size, PROT_READ, MAP_PRIVATE, d, 0);
486: if (MAP_FAILED == delta) {
1.4 misho 487: LOGERR;
488: sync_Close(d);
1.2 misho 489: if (compress & 1)
1.1 misho 490: unlink(szTemp);
491: return -1;
492: } else {
1.4 misho 493: sync_Close(d);
1.2 misho 494: if (compress & 1)
1.1 misho 495: unlink(szTemp);
496: }
497:
498: if (sync_buildPatch(delta, sb.st_size, &pieces) == -1 || !pieces) {
1.4 misho 499: sync_SetErr(ENOEXEC, "Error:: patch file is broken!\n");
1.1 misho 500: munmap(delta, sb.st_size);
501: return -1;
502: }
503:
1.4 misho 504: inf = sync_Open(csInput, O_RDONLY, 0);
1.1 misho 505: if (inf == -1) {
1.6 ! misho 506: e_free(pieces);
1.1 misho 507: munmap(delta, sb.st_size);
508: return inf;
509: }
1.4 misho 510: outf = sync_Open(csPatch, O_WRONLY, 0);
1.1 misho 511: if (outf == -1) {
1.4 misho 512: sync_Close(inf);
1.6 ! misho 513: e_free(pieces);
1.1 misho 514: munmap(delta, sb.st_size);
515: return outf;
516: }
517:
518: if (fstat(inf, &sb) == -1) {
1.4 misho 519: LOGERR;
1.1 misho 520: ret = -1;
521: goto end;
522: } else {
523: if (!sb.st_size) {
524: ret = -1;
525: goto end;
526: }
527: }
528:
529: ret = readlen = 0;
530: buffer = NULL;
1.4 misho 531: for (i = 0, off = 0ll, suffix = NULL, piece = pieces; piece->pfx;
532: i++, off += readlen) {
533: #if 0
534: printf("i=%d off=%llu sfx=%p piece=%p\n", i, off, suffix, piece);
535: #endif
1.1 misho 536:
1.4 misho 537: /* if input offset is less then input file size */
1.1 misho 538: if (off < sb.st_size) {
539: readlen = read(inf, buf, CHUNK_MAX);
540: if (readlen == -1) {
1.4 misho 541: LOGERR;
1.1 misho 542: ret = -1;
543: break;
544: }
1.4 misho 545: /* if suffix find, check for correct patch */
1.1 misho 546: if (suffix) {
1.4 misho 547: if (suffix->sc_len != readlen ||
548: suffix->sc_off != off) {
549: sync_SetErr(ENOEXEC, "Error:: patch file is broken! "
550: "(wrong suffix pos)\n");
1.1 misho 551: ret = -1;
552: break;
553: }
554: sync_mksig(i, off, buf, readlen, &sc);
555: if (sc.sc_roll != suffix->sc_roll ||
1.4 misho 556: memcmp(sc.sc_cksum, suffix->sc_cksum,
557: MD5_DIGEST_LENGTH)) {
558: sync_SetErr(ENOEXEC, "Error:: patch file is broken! "
559: "(wrong suffix crc)\n");
1.1 misho 560: ret = -1;
561: break;
562: }
563:
564: suffix = NULL;
565: }
566:
567: buffer = buf;
568: }
569:
1.4 misho 570: #if 0
571: printf("i=%d off=%llu sfx=%p piece=%p pfx=%p pfx_off=%llu\n", i, off,
572: suffix, piece, piece ? piece->pfx : 0l,
573: piece->pfx ? piece->pfx->sc_off : 0l);
574: #endif
1.1 misho 575:
1.4 misho 576: /* if delta chunk match! */
1.1 misho 577: if (piece->pfx && piece->pfx->sc_off == off) {
578: if (!piece->buf) {
1.4 misho 579: sync_SetErr(ENOEXEC, "Error:: patch file is broken! "
580: "(missing data)\n");
1.1 misho 581: ret = -1;
582: break;
583: }
584:
585: buffer = piece->buf;
586: readlen = piece->pfx->sc_len;
587: suffix = piece->sfx ? piece->sfx : NULL;
588:
589: piece++;
590:
591: if (suffix && off >= sb.st_size) {
1.4 misho 592: sync_SetErr(ENOEXEC, "Error:: patch file is broken! "
593: "(after eof find suffix)\n");
1.1 misho 594: ret = -1;
595: break;
596: }
1.4 misho 597: } else if (off >= sb.st_size) {
598: if (piece->pfx) {
599: sync_SetErr(ENOEXEC, "Error:: patch file is broken! "
600: "(after eof find prefix)\n");
601: ret = -1;
602: }
1.1 misho 603:
1.4 misho 604: break;
605: }
1.1 misho 606:
607: ret = write(outf, buffer, readlen);
608: if (ret == -1 || ret != readlen) {
1.4 misho 609: LOGERR;
1.1 misho 610: break;
611: }
612: }
613:
614: end:
1.4 misho 615: sync_Close(inf);
616: sync_Close(outf);
1.6 ! misho 617: e_free(pieces);
1.1 misho 618: munmap(delta, sb.st_size);
619: return ret;
620: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>