1 /* 2 * Copyright (c) 2007-2012, Novell Inc. 3 * 4 * This program is licensed under the BSD license, read LICENSE.BSD 5 * for further information 6 */ 7 8 /* 9 * repopage.c 10 * 11 * Paging and compression functions for the vertical repository data. 12 * Vertical data is grouped by key, normal data is grouped by solvable. 13 * This makes searching for a string in vertical data fast as there's 14 * no need to skip over data if keys we're not interested in. 15 * 16 * The vertical data is split into pages, each page is compressed with a fast 17 * compression algorithm. These pages are read in on demand, not recently used 18 * pages automatically get dropped. 19 */ 20 21 #define _XOPEN_SOURCE 500 22 23 #include <sys/types.h> 24 #include <stdint.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 #include <assert.h> 30 #include <fcntl.h> 31 #include <time.h> 32 33 #include "repo.h" 34 #include "repopage.h" 35 36 37 38 #define BLOCK_SIZE (65536*1) 39 #if BLOCK_SIZE <= 65536 40 typedef uint16_t Ref; 41 #else 42 typedef uint32_t Ref; 43 #endif 44 45 /* 46 The format is tailored for fast decompression (i.e. only byte based), 47 and skewed to ASCII content (highest bit often not set): 48 49 a 0LLLLLLL 50 - self-describing ASCII character hex L 51 b 100lllll <l+1 bytes> 52 - literal run of length l+1 53 c 101oolll <8o> 54 - back ref of length l+2, at offset -(o+1) (o < 1 << 10) 55 d 110lllll <8o> 56 - back ref of length l+2+8, at offset -(o+1) (o < 1 << 8) 57 e 1110llll <8o> <8o> 58 - back ref of length l+3, at offset -(o+1) (o < 1 << 16) 59 f1 1111llll <8l> <8o> <8o> 60 - back ref, length l+19 (l < 1<<12), offset -(o+1) (o < 1<<16) 61 f2 11110lll <8l> <8o> <8o> 62 - back ref, length l+19 (l < 1<<11), offset -(o+1) (o < 1<<16) 63 g 11111lll <8l> <8o> <8o> <8o> 64 - back ref, length l+5 (l < 1<<11), offset -(o+1) (o < 1<<24) 65 66 Generally for a literal of length L we need L+1 bytes, hence it is 67 better to encode also very short backrefs (2 chars) as backrefs if 68 their offset is small, as that only needs two bytes. Except if we 69 already have a literal run, in that case it's better to append there, 70 instead of breaking it for a backref. So given a potential backref 71 at offset O, length L the strategy is as follows: 72 73 L < 2 : encode as 1-literal 74 L == 2, O > 1024 : encode as 1-literal 75 L == 2, have already literals: encode as 1-literal 76 O = O - 1 77 L >= 2, L <= 9, O < 1024 : encode as c 78 L >= 10, L <= 41, O < 256 : encode as d 79 else we have either O >= 1024, or L >= 42: 80 L < 3 : encode as 1-literal 81 L >= 3, L <= 18, O < 65536 : encode as e 82 L >= 19, L <= 4095+18, O < 65536 : encode as f 83 else we have either L >= 4096+18 or O >= 65536. 84 O >= 65536: encode as 1-literal, too bad 85 (with the current block size this can't happen) 86 L >= 4096+18, so reduce to 4095+18 : encode as f 87 */ 88 89 90 static unsigned int 91 compress_buf(const unsigned char *in, unsigned int in_len, 92 unsigned char *out, unsigned int out_len) 93 { 94 unsigned int oo = 0; /* out-offset */ 95 unsigned int io = 0; /* in-offset */ 96 #define HS (65536) 97 Ref htab[HS]; 98 Ref hnext[BLOCK_SIZE]; 99 unsigned int litofs = 0; 100 memset(htab, -1, sizeof (htab)); 101 memset(hnext, -1, sizeof (hnext)); 102 while (io + 2 < in_len) 103 { 104 /* Search for a match of the string starting at IN, we have at 105 least three characters. */ 106 unsigned int hval = in[io] | in[io + 1] << 8 | in[io + 2] << 16; 107 unsigned int try, mlen, mofs, tries; 108 hval = (hval ^ (hval << 5) ^ (hval >> 5)) - hval * 5; 109 hval = hval & (HS - 1); 110 try = htab[hval]; 111 hnext[io] = htab[hval]; 112 htab[hval] = io; 113 mlen = 0; 114 mofs = 0; 115 116 for (tries = 0; try != -1 && tries < 12; tries++) 117 { 118 if (try < io 119 && in[try] == in[io] && in[try + 1] == in[io + 1]) 120 { 121 mlen = 2; 122 mofs = (io - try) - 1; 123 break; 124 } 125 try = hnext[try]; 126 } 127 for (; try != -1 && tries < 12; tries++) 128 { 129 /* assert(mlen >= 2); */ 130 /* assert(io + mlen < in_len); */ 131 /* Try a match starting from [io] with the strings at [try]. 132 That's only sensible if TRY actually is before IO (can happen 133 with uninit hash table). If we have a previous match already 134 we're only going to take the new one if it's longer, hence 135 check the potentially last character. */ 136 if (try < io && in[try + mlen] == in[io + mlen]) 137 { 138 unsigned int this_len, this_ofs; 139 if (memcmp(in + try, in + io, mlen)) 140 goto no_match; 141 this_len = mlen + 1; 142 /* Now try extending the match by more characters. */ 143 for (; 144 io + this_len < in_len 145 && in[try + this_len] == in[io + this_len]; this_len++) 146 ; 147 #if 0 148 unsigned int testi; 149 for (testi = 0; testi < this_len; testi++) 150 assert(in[try + testi] == in[io + testi]); 151 #endif 152 this_ofs = (io - try) - 1; 153 /*if (this_ofs > 65535) 154 goto no_match; */ 155 #if 0 156 assert(this_len >= 2); 157 assert(this_len >= mlen); 158 assert(this_len > mlen || (this_len == mlen && this_ofs > mofs)); 159 #endif 160 mlen = this_len, mofs = this_ofs; 161 /* If our match extends up to the end of input, no next 162 match can become better. This is not just an 163 optimization, it establishes a loop invariant 164 (io + mlen < in_len). */ 165 if (io + mlen >= in_len) 166 goto match_done; 167 } 168 no_match: 169 try = hnext[try]; 170 /*if (io - try - 1 >= 65536) 171 break;*/ 172 } 173 174 match_done: 175 if (mlen) 176 { 177 /*fprintf(stderr, "%d %d\n", mlen, mofs);*/ 178 if (mlen == 2 && (litofs || mofs >= 1024)) 179 mlen = 0; 180 /*else if (mofs >= 65536) 181 mlen = 0;*/ 182 else if (mofs >= 65536) 183 { 184 if (mlen >= 2048 + 5) 185 mlen = 2047 + 5; 186 else if (mlen < 5) 187 mlen = 0; 188 } 189 else if (mlen < 3) 190 mlen = 0; 191 /*else if (mlen >= 4096 + 19) 192 mlen = 4095 + 19;*/ 193 else if (mlen >= 2048 + 19) 194 mlen = 2047 + 19; 195 /* Skip this match if the next character would deliver a better one, 196 but only do this if we have the chance to really extend the 197 length (i.e. our current length isn't yet the (conservative) 198 maximum). */ 199 if (mlen && mlen < (2048 + 5) && io + 3 < in_len) 200 { 201 unsigned int hval = 202 in[io + 1] | in[io + 2] << 8 | in[io + 3] << 16; 203 unsigned int try; 204 hval = (hval ^ (hval << 5) ^ (hval >> 5)) - hval * 5; 205 hval = hval & (HS - 1); 206 try = htab[hval]; 207 if (try < io + 1 208 && in[try] == in[io + 1] && in[try + 1] == in[io + 2]) 209 { 210 unsigned int this_len; 211 this_len = 2; 212 for (; 213 io + 1 + this_len < in_len 214 && in[try + this_len] == in[io + 1 + this_len]; 215 this_len++) 216 ; 217 if (this_len >= mlen) 218 mlen = 0; 219 } 220 } 221 } 222 if (!mlen) 223 { 224 if (!litofs) 225 litofs = io + 1; 226 io++; 227 } 228 else 229 { 230 if (litofs) 231 { 232 unsigned litlen; 233 litofs--; 234 litlen = io - litofs; 235 /* fprintf(stderr, "lit: %d\n", litlen); */ 236 while (litlen) 237 { 238 unsigned int easy_sz; 239 /* Emit everything we can as self-describers. As soon as 240 we hit a byte we can't emit as such we're going to emit 241 a length descriptor anyway, so we can as well include 242 bytes < 0x80 which might follow afterwards in that run. */ 243 for (easy_sz = 0; 244 easy_sz < litlen && in[litofs + easy_sz] < 0x80; 245 easy_sz++) 246 ; 247 if (easy_sz) 248 { 249 if (oo + easy_sz >= out_len) 250 return 0; 251 memcpy(out + oo, in + litofs, easy_sz); 252 litofs += easy_sz; 253 oo += easy_sz; 254 litlen -= easy_sz; 255 if (!litlen) 256 break; 257 } 258 if (litlen <= 32) 259 { 260 if (oo + 1 + litlen >= out_len) 261 return 0; 262 out[oo++] = 0x80 | (litlen - 1); 263 while (litlen--) 264 out[oo++] = in[litofs++]; 265 break; 266 } 267 else 268 { 269 /* Literal length > 32, so chunk it. */ 270 if (oo + 1 + 32 >= out_len) 271 return 0; 272 out[oo++] = 0x80 | 31; 273 memcpy(out + oo, in + litofs, 32); 274 oo += 32; 275 litofs += 32; 276 litlen -= 32; 277 } 278 } 279 litofs = 0; 280 } 281 282 /* fprintf(stderr, "ref: %d @ %d\n", mlen, mofs); */ 283 284 if (mlen >= 2 && mlen <= 9 && mofs < 1024) 285 { 286 if (oo + 2 >= out_len) 287 return 0; 288 out[oo++] = 0xa0 | ((mofs & 0x300) >> 5) | (mlen - 2); 289 out[oo++] = mofs & 0xff; 290 } 291 else if (mlen >= 10 && mlen <= 41 && mofs < 256) 292 { 293 if (oo + 2 >= out_len) 294 return 0; 295 out[oo++] = 0xc0 | (mlen - 10); 296 out[oo++] = mofs; 297 } 298 else if (mofs >= 65536) 299 { 300 assert(mlen >= 5 && mlen < 2048 + 5); 301 if (oo + 5 >= out_len) 302 return 0; 303 out[oo++] = 0xf8 | ((mlen - 5) >> 8); 304 out[oo++] = (mlen - 5) & 0xff; 305 out[oo++] = mofs & 0xff; 306 out[oo++] = (mofs >> 8) & 0xff; 307 out[oo++] = mofs >> 16; 308 } 309 else if (mlen >= 3 && mlen <= 18) 310 { 311 assert(mofs < 65536); 312 if (oo + 3 >= out_len) 313 return 0; 314 out[oo++] = 0xe0 | (mlen - 3); 315 out[oo++] = mofs & 0xff; 316 out[oo++] = mofs >> 8; 317 } 318 else 319 { 320 assert(mlen >= 19 && mlen <= 4095 + 19 && mofs < 65536); 321 if (oo + 4 >= out_len) 322 return 0; 323 out[oo++] = 0xf0 | ((mlen - 19) >> 8); 324 out[oo++] = (mlen - 19) & 0xff; 325 out[oo++] = mofs & 0xff; 326 out[oo++] = mofs >> 8; 327 } 328 /* Insert the hashes for the compressed run [io..io+mlen-1]. 329 For [io] we have it already done at the start of the loop. 330 So it's from [io+1..io+mlen-1], and we need three chars per 331 hash, so the accessed characters will be [io+1..io+mlen-1+2], 332 ergo io+mlen+1 < in_len. */ 333 mlen--; 334 io++; 335 while (mlen--) 336 { 337 if (io + 2 < in_len) 338 { 339 unsigned int hval = 340 in[io] | in[io + 1] << 8 | in[io + 2] << 16; 341 hval = (hval ^ (hval << 5) ^ (hval >> 5)) - hval * 5; 342 hval = hval & (HS - 1); 343 hnext[io] = htab[hval]; 344 htab[hval] = io; 345 } 346 io++; 347 }; 348 } 349 } 350 /* We might have some characters left. */ 351 if (io < in_len && !litofs) 352 litofs = io + 1; 353 io = in_len; 354 if (litofs) 355 { 356 unsigned litlen; 357 litofs--; 358 litlen = io - litofs; 359 /* fprintf(stderr, "lit: %d\n", litlen); */ 360 while (litlen) 361 { 362 unsigned int easy_sz; 363 /* Emit everything we can as self-describers. As soon as we hit a 364 byte we can't emit as such we're going to emit a length 365 descriptor anyway, so we can as well include bytes < 0x80 which 366 might follow afterwards in that run. */ 367 for (easy_sz = 0; easy_sz < litlen && in[litofs + easy_sz] < 0x80; 368 easy_sz++) 369 ; 370 if (easy_sz) 371 { 372 if (oo + easy_sz >= out_len) 373 return 0; 374 memcpy(out + oo, in + litofs, easy_sz); 375 litofs += easy_sz; 376 oo += easy_sz; 377 litlen -= easy_sz; 378 if (!litlen) 379 break; 380 } 381 if (litlen <= 32) 382 { 383 if (oo + 1 + litlen >= out_len) 384 return 0; 385 out[oo++] = 0x80 | (litlen - 1); 386 while (litlen--) 387 out[oo++] = in[litofs++]; 388 break; 389 } 390 else 391 { 392 /* Literal length > 32, so chunk it. */ 393 if (oo + 1 + 32 >= out_len) 394 return 0; 395 out[oo++] = 0x80 | 31; 396 memcpy(out + oo, in + litofs, 32); 397 oo += 32; 398 litofs += 32; 399 litlen -= 32; 400 } 401 } 402 litofs = 0; 403 } 404 return oo; 405 } 406 407 static unsigned int 408 unchecked_decompress_buf(const unsigned char *in, unsigned int in_len, 409 unsigned char *out, 410 unsigned int out_len __attribute__((unused))) 411 { 412 unsigned char *orig_out = out; 413 const unsigned char *in_end = in + in_len; 414 while (in < in_end) 415 { 416 unsigned int first = *in++; 417 int o; 418 switch (first >> 4) 419 { 420 default: 421 /* This default case can't happen, but GCCs VRP is not strong 422 enough to see this, so make this explicitely not fall to 423 the end of the switch, so that we don't have to initialize 424 o above. */ 425 continue; 426 case 0: case 1: 427 case 2: case 3: 428 case 4: case 5: 429 case 6: case 7: 430 /* a 0LLLLLLL */ 431 /* fprintf (stderr, "lit: 1\n"); */ 432 *out++ = first; 433 continue; 434 case 8: case 9: 435 /* b 100lllll <l+1 bytes> */ 436 { 437 unsigned int l = first & 31; 438 /* fprintf (stderr, "lit: %d\n", l); */ 439 do 440 *out++ = *in++; 441 while (l--); 442 continue; 443 } 444 case 10: case 11: 445 /* c 101oolll <8o> */ 446 { 447 o = first & (3 << 3); 448 o = (o << 5) | *in++; 449 first = (first & 7) + 2; 450 break; 451 } 452 case 12: case 13: 453 /* d 110lllll <8o> */ 454 { 455 o = *in++; 456 first = (first & 31) + 10; 457 break; 458 } 459 case 14: 460 /* e 1110llll <8o> <8o> */ 461 { 462 o = in[0] | (in[1] << 8); 463 in += 2; 464 first = first & 31; 465 first += 3; 466 break; 467 } 468 case 15: 469 /* f1 1111llll <8o> <8o> <8l> */ 470 /* f2 11110lll <8o> <8o> <8l> */ 471 /* g 11111lll <8o> <8o> <8o> <8l> */ 472 { 473 first = first & 15; 474 if (first >= 8) 475 { 476 first = (((first - 8) << 8) | in[0]) + 5; 477 o = in[1] | (in[2] << 8) | (in[3] << 16); 478 in += 4; 479 } 480 else 481 { 482 first = ((first << 8) | in[0]) + 19; 483 o = in[1] | (in[2] << 8); 484 in += 3; 485 } 486 break; 487 } 488 } 489 /* fprintf(stderr, "ref: %d @ %d\n", first, o); */ 490 o++; 491 o = -o; 492 #if 0 493 /* We know that first will not be zero, and this loop structure is 494 better optimizable. */ 495 do 496 { 497 *out = *(out - o); 498 out++; 499 } 500 while (--first); 501 #else 502 switch (first) 503 { 504 case 18: *out = *(out + o); out++; 505 case 17: *out = *(out + o); out++; 506 case 16: *out = *(out + o); out++; 507 case 15: *out = *(out + o); out++; 508 case 14: *out = *(out + o); out++; 509 case 13: *out = *(out + o); out++; 510 case 12: *out = *(out + o); out++; 511 case 11: *out = *(out + o); out++; 512 case 10: *out = *(out + o); out++; 513 case 9: *out = *(out + o); out++; 514 case 8: *out = *(out + o); out++; 515 case 7: *out = *(out + o); out++; 516 case 6: *out = *(out + o); out++; 517 case 5: *out = *(out + o); out++; 518 case 4: *out = *(out + o); out++; 519 case 3: *out = *(out + o); out++; 520 case 2: *out = *(out + o); out++; 521 case 1: *out = *(out + o); out++; 522 case 0: break; 523 default: 524 /* Duff duff :-) */ 525 switch (first & 15) 526 { 527 do 528 { 529 case 0: *out = *(out + o); out++; 530 case 15: *out = *(out + o); out++; 531 case 14: *out = *(out + o); out++; 532 case 13: *out = *(out + o); out++; 533 case 12: *out = *(out + o); out++; 534 case 11: *out = *(out + o); out++; 535 case 10: *out = *(out + o); out++; 536 case 9: *out = *(out + o); out++; 537 case 8: *out = *(out + o); out++; 538 case 7: *out = *(out + o); out++; 539 case 6: *out = *(out + o); out++; 540 case 5: *out = *(out + o); out++; 541 case 4: *out = *(out + o); out++; 542 case 3: *out = *(out + o); out++; 543 case 2: *out = *(out + o); out++; 544 case 1: *out = *(out + o); out++; 545 } 546 while ((int)(first -= 16) > 0); 547 } 548 break; 549 } 550 #endif 551 } 552 return out - orig_out; 553 } 554 555 /**********************************************************************/ 556 557 void repopagestore_init(Repopagestore *store) 558 { 559 memset(store, 0, sizeof(*store)); 560 store->pagefd = -1; 561 } 562 563 void repopagestore_free(Repopagestore *store) 564 { 565 store->blob_store = solv_free(store->blob_store); 566 store->file_pages = solv_free(store->file_pages); 567 store->mapped_at = solv_free(store->mapped_at); 568 store->mapped = solv_free(store->mapped); 569 if (store->pagefd != -1) 570 close(store->pagefd); 571 store->pagefd = -1; 572 } 573 574 575 /**********************************************************************/ 576 577 unsigned char * 578 repopagestore_load_page_range(Repopagestore *store, unsigned int pstart, unsigned int pend) 579 { 580 /* Make sure all pages from PSTART to PEND (inclusive) are loaded, 581 and are consecutive. Return a pointer to the mapping of PSTART. */ 582 unsigned char buf[REPOPAGE_BLOBSIZE]; 583 unsigned int i, best, pnum; 584 585 if (pstart == pend) 586 { 587 /* Quick check in case the requested page is already mapped */ 588 if (store->mapped_at[pstart] != -1) 589 return store->blob_store + store->mapped_at[pstart]; 590 } 591 else 592 { 593 /* Quick check in case all pages are already mapped and consecutive. */ 594 for (pnum = pstart; pnum <= pend; pnum++) 595 if (store->mapped_at[pnum] == -1 596 || (pnum > pstart 597 && store->mapped_at[pnum] 598 != store->mapped_at[pnum-1] + REPOPAGE_BLOBSIZE)) 599 break; 600 if (pnum > pend) 601 return store->blob_store + store->mapped_at[pstart]; 602 } 603 604 if (store->pagefd == -1 || !store->file_pages) 605 return 0; /* no backing file */ 606 607 #ifdef DEBUG_PAGING 608 fprintf(stderr, "PAGE: want %d pages starting at %d\n", pend - pstart + 1, pstart); 609 #endif 610 611 /* Ensure that we can map the numbers of pages we need at all. */ 612 if (pend - pstart + 1 > store->nmapped) 613 { 614 unsigned int oldcan = store->nmapped; 615 store->nmapped = pend - pstart + 1; 616 if (store->nmapped < 4) 617 store->nmapped = 4; 618 store->mapped = solv_realloc2(store->mapped, store->nmapped, sizeof(store->mapped[0])); 619 for (i = oldcan; i < store->nmapped; i++) 620 store->mapped[i] = -1; 621 store->blob_store = solv_realloc2(store->blob_store, store->nmapped, REPOPAGE_BLOBSIZE); 622 #ifdef DEBUG_PAGING 623 fprintf(stderr, "PAGE: can map %d pages\n", store->nmapped); 624 #endif 625 } 626 627 if (store->mapped_at[pstart] != -1) 628 { 629 /* assume forward search */ 630 best = store->mapped_at[pstart] / REPOPAGE_BLOBSIZE; 631 if (best + (pend - pstart) >= store->nmapped) 632 best = 0; 633 } 634 else if (store->mapped_at[pend] != -1) 635 { 636 /* assume backward search */ 637 best = store->mapped_at[pend] / REPOPAGE_BLOBSIZE; 638 if (best < pend - pstart) 639 best = store->nmapped - 1; 640 best -= pend - pstart; 641 } 642 else 643 { 644 /* choose some "random" location to avoid thrashing */ 645 best = (pstart + store->rr_counter++) % (store->nmapped - pend + pstart); 646 } 647 648 /* So we want to map our pages from [best] to [best+pend-pstart]. 649 Use a very simple strategy, which doesn't make the best use of 650 our resources, but works. Throw away all pages in that range 651 (even ours) then copy around ours or read them in. */ 652 for (i = best, pnum = pstart; pnum <= pend; i++, pnum++) 653 { 654 unsigned int pnum_mapped_at; 655 unsigned int oldpnum = store->mapped[i]; 656 if (oldpnum != -1) 657 { 658 if (oldpnum == pnum) 659 continue; /* already have the correct page */ 660 /* Evict this page. */ 661 #ifdef DEBUG_PAGING 662 fprintf(stderr, "PAGE: evict page %d from %d\n", oldpnum, i); 663 #endif 664 store->mapped[i] = -1; 665 store->mapped_at[oldpnum] = -1; 666 } 667 /* check if we can copy the correct content (before it gets evicted) */ 668 pnum_mapped_at = store->mapped_at[pnum]; 669 if (pnum_mapped_at != -1 && pnum_mapped_at != i * REPOPAGE_BLOBSIZE) 670 { 671 void *dest = store->blob_store + i * REPOPAGE_BLOBSIZE; 672 #ifdef DEBUG_PAGING 673 fprintf(stderr, "PAGECOPY: %d from %d to %d\n", pnum, pnum_mapped_at / REPOPAGE_BLOBSIZE, i); 674 #endif 675 memcpy(dest, store->blob_store + pnum_mapped_at, REPOPAGE_BLOBSIZE); 676 store->mapped[pnum_mapped_at / REPOPAGE_BLOBSIZE] = -1; 677 store->mapped[i] = pnum; 678 store->mapped_at[pnum] = i * REPOPAGE_BLOBSIZE; 679 } 680 } 681 682 /* Everything is free now. Read in or copy the pages we want. */ 683 for (i = best, pnum = pstart; pnum <= pend; i++, pnum++) 684 { 685 void *dest = store->blob_store + i * REPOPAGE_BLOBSIZE; 686 if (store->mapped_at[pnum] != -1) 687 { 688 unsigned int pnum_mapped_at = store->mapped_at[pnum]; 689 if (pnum_mapped_at != i * REPOPAGE_BLOBSIZE) 690 { 691 #ifdef DEBUG_PAGING 692 fprintf(stderr, "PAGECOPY: %d from %d to %d\n", pnum, pnum_mapped_at / REPOPAGE_BLOBSIZE, i); 693 #endif 694 /* Still mapped somewhere else, so just copy it from there. */ 695 memcpy(dest, store->blob_store + pnum_mapped_at, REPOPAGE_BLOBSIZE); 696 store->mapped[pnum_mapped_at / REPOPAGE_BLOBSIZE] = -1; 697 } 698 } 699 else 700 { 701 Attrblobpage *p = store->file_pages + pnum; 702 unsigned int in_len = p->page_size; 703 unsigned int compressed = in_len & 1; 704 in_len >>= 1; 705 #ifdef DEBUG_PAGING 706 fprintf(stderr, "PAGEIN: %d to %d", pnum, i); 707 #endif 708 if (pread(store->pagefd, compressed ? buf : dest, in_len, store->file_offset + p->page_offset) != in_len) 709 { 710 perror("mapping pread"); 711 return 0; 712 } 713 if (compressed) 714 { 715 unsigned int out_len; 716 out_len = unchecked_decompress_buf(buf, in_len, dest, REPOPAGE_BLOBSIZE); 717 if (out_len != REPOPAGE_BLOBSIZE && pnum < store->num_pages - 1) 718 { 719 #ifdef DEBUG_PAGING 720 fprintf(stderr, "can't decompress\n"); 721 #endif 722 return 0; 723 } 724 #ifdef DEBUG_PAGING 725 fprintf(stderr, " (expand %d to %d)", in_len, out_len); 726 #endif 727 } 728 #ifdef DEBUG_PAGING 729 fprintf(stderr, "\n"); 730 #endif 731 } 732 store->mapped_at[pnum] = i * REPOPAGE_BLOBSIZE; 733 store->mapped[i] = pnum; 734 } 735 return store->blob_store + best * REPOPAGE_BLOBSIZE; 736 } 737 738 unsigned int 739 repopagestore_compress_page(unsigned char *page, unsigned int len, unsigned char *cpage, unsigned int max) 740 { 741 return compress_buf(page, len, cpage, max); 742 } 743 744 #define SOLV_ERROR_EOF 3 745 #define SOLV_ERROR_CORRUPT 6 746 747 static inline unsigned int 748 read_u32(FILE *fp) 749 { 750 int c, i; 751 unsigned int x = 0; 752 753 for (i = 0; i < 4; i++) 754 { 755 c = getc(fp); 756 if (c == EOF) 757 return 0; 758 x = (x << 8) | c; 759 } 760 return x; 761 } 762 763 /* Try to either setup on-demand paging (using FP as backing 764 file), or in case that doesn't work (FP not seekable) slurps in 765 all pages and deactivates paging. */ 766 int 767 repopagestore_read_or_setup_pages(Repopagestore *store, FILE *fp, unsigned int pagesz, unsigned int blobsz) 768 { 769 unsigned int npages; 770 unsigned int i; 771 unsigned int can_seek; 772 unsigned int cur_page_ofs; 773 unsigned char buf[REPOPAGE_BLOBSIZE]; 774 775 if (pagesz != REPOPAGE_BLOBSIZE) 776 { 777 /* We could handle this by slurping in everything. */ 778 return SOLV_ERROR_CORRUPT; 779 } 780 can_seek = 1; 781 if ((store->file_offset = ftell(fp)) < 0) 782 can_seek = 0; 783 clearerr(fp); 784 if (can_seek) 785 store->pagefd = dup(fileno(fp)); 786 if (store->pagefd == -1) 787 can_seek = 0; 788 else 789 fcntl(store->pagefd, F_SETFD, FD_CLOEXEC); 790 791 #ifdef DEBUG_PAGING 792 fprintf(stderr, "can %sseek\n", can_seek ? "" : "NOT "); 793 #endif 794 npages = (blobsz + REPOPAGE_BLOBSIZE - 1) / REPOPAGE_BLOBSIZE; 795 796 store->num_pages = npages; 797 store->mapped_at = solv_malloc2(npages, sizeof(*store->mapped_at)); 798 799 /* If we can't seek on our input we have to slurp in everything. 800 * Otherwise set up file_pages containing offest/length of the 801 * pages */ 802 if (can_seek) 803 store->file_pages = solv_malloc2(npages, sizeof(*store->file_pages)); 804 else 805 store->blob_store = solv_malloc2(npages, REPOPAGE_BLOBSIZE); 806 cur_page_ofs = 0; 807 for (i = 0; i < npages; i++) 808 { 809 unsigned int in_len = read_u32(fp); 810 unsigned int compressed = in_len & 1; 811 in_len >>= 1; 812 #ifdef DEBUG_PAGING 813 fprintf(stderr, "page %d: len %d (%scompressed)\n", 814 i, in_len, compressed ? "" : "not "); 815 #endif 816 if (can_seek) 817 { 818 Attrblobpage *p = store->file_pages + i; 819 cur_page_ofs += 4; 820 store->mapped_at[i] = -1; /* not mapped yet */ 821 p->page_offset = cur_page_ofs; 822 p->page_size = in_len * 2 + compressed; 823 if (fseek(fp, in_len, SEEK_CUR) < 0) 824 { 825 /* We can't fall back to non-seeking behaviour as we already 826 read over some data pages without storing them away. */ 827 close(store->pagefd); 828 store->pagefd = -1; 829 return SOLV_ERROR_EOF; 830 } 831 cur_page_ofs += in_len; 832 } 833 else 834 { 835 unsigned int out_len; 836 void *dest = store->blob_store + i * REPOPAGE_BLOBSIZE; 837 store->mapped_at[i] = i * REPOPAGE_BLOBSIZE; 838 /* We can't seek, so suck everything in. */ 839 if (fread(compressed ? buf : dest, in_len, 1, fp) != 1) 840 { 841 perror("fread"); 842 return SOLV_ERROR_EOF; 843 } 844 if (compressed) 845 { 846 out_len = unchecked_decompress_buf(buf, in_len, dest, REPOPAGE_BLOBSIZE); 847 if (out_len != REPOPAGE_BLOBSIZE && i < npages - 1) 848 { 849 return SOLV_ERROR_CORRUPT; 850 } 851 } 852 } 853 } 854 return 0; 855 } 856 857 void 858 repopagestore_disable_paging(Repopagestore *store) 859 { 860 if (store->num_pages) 861 repopagestore_load_page_range(store, 0, store->num_pages - 1); 862 } 863 864 #ifdef STANDALONE 865 866 static void 867 transfer_file(FILE * from, FILE * to, int compress) 868 { 869 unsigned char inb[BLOCK_SIZE]; 870 unsigned char outb[BLOCK_SIZE]; 871 while (!feof (from) && !ferror (from)) 872 { 873 unsigned int in_len, out_len; 874 if (compress) 875 { 876 in_len = fread(inb, 1, BLOCK_SIZE, from); 877 if (in_len) 878 { 879 unsigned char *b = outb; 880 out_len = compress_buf(inb, in_len, outb, sizeof (outb)); 881 if (!out_len) 882 b = inb, out_len = in_len; 883 if (fwrite(&out_len, sizeof (out_len), 1, to) != 1) 884 { 885 perror("write size"); 886 exit (1); 887 } 888 if (fwrite(b, out_len, 1, to) != 1) 889 { 890 perror("write data"); 891 exit (1); 892 } 893 } 894 } 895 else 896 { 897 if (fread(&in_len, sizeof(in_len), 1, from) != 1) 898 { 899 if (feof(from)) 900 return; 901 perror("can't read size"); 902 exit(1); 903 } 904 if (fread(inb, in_len, 1, from) != 1) 905 { 906 perror("can't read data"); 907 exit(1); 908 } 909 out_len = 910 unchecked_decompress_buf(inb, in_len, outb, sizeof(outb)); 911 if (fwrite(outb, out_len, 1, to) != 1) 912 { 913 perror("can't write output"); 914 exit(1); 915 } 916 } 917 } 918 } 919 920 /* Just for benchmarking purposes. */ 921 static void 922 dumb_memcpy(void *dest, const void *src, unsigned int len) 923 { 924 char *d = dest; 925 const char *s = src; 926 while (len--) 927 *d++ = *s++; 928 } 929 930 static void 931 benchmark(FILE * from) 932 { 933 unsigned char inb[BLOCK_SIZE]; 934 unsigned char outb[BLOCK_SIZE]; 935 unsigned int in_len = fread(inb, 1, BLOCK_SIZE, from); 936 unsigned int out_len; 937 if (!in_len) 938 { 939 perror("can't read from input"); 940 exit(1); 941 } 942 943 unsigned int calib_loop; 944 unsigned int per_loop; 945 unsigned int i, j; 946 clock_t start, end; 947 float seconds; 948 949 #if 0 950 calib_loop = 1; 951 per_loop = 0; 952 start = clock(); 953 while ((clock() - start) < CLOCKS_PER_SEC / 4) 954 { 955 calib_loop *= 2; 956 for (i = 0; i < calib_loop; i++) 957 dumb_memcpy(outb, inb, in_len); 958 per_loop += calib_loop; 959 } 960 961 fprintf(stderr, "memcpy:\nCalibrated to %d iterations per loop\n", 962 per_loop); 963 964 start = clock(); 965 for (i = 0; i < 10; i++) 966 for (j = 0; j < per_loop; j++) 967 dumb_memcpy(outb, inb, in_len); 968 end = clock(); 969 seconds = (end - start) / (float) CLOCKS_PER_SEC; 970 fprintf(stderr, "%.2f seconds == %.2f MB/s\n", seconds, 971 ((long long) in_len * per_loop * 10) / (1024 * 1024 * seconds)); 972 #endif 973 974 calib_loop = 1; 975 per_loop = 0; 976 start = clock(); 977 while ((clock() - start) < CLOCKS_PER_SEC / 4) 978 { 979 calib_loop *= 2; 980 for (i = 0; i < calib_loop; i++) 981 compress_buf(inb, in_len, outb, sizeof(outb)); 982 per_loop += calib_loop; 983 } 984 985 fprintf(stderr, "compression:\nCalibrated to %d iterations per loop\n", 986 per_loop); 987 988 start = clock(); 989 for (i = 0; i < 10; i++) 990 for (j = 0; j < per_loop; j++) 991 compress_buf(inb, in_len, outb, sizeof(outb)); 992 end = clock(); 993 seconds = (end - start) / (float) CLOCKS_PER_SEC; 994 fprintf(stderr, "%.2f seconds == %.2f MB/s\n", seconds, 995 ((long long) in_len * per_loop * 10) / (1024 * 1024 * seconds)); 996 997 out_len = compress_buf(inb, in_len, outb, sizeof(outb)); 998 999 calib_loop = 1; 1000 per_loop = 0; 1001 start = clock(); 1002 while ((clock() - start) < CLOCKS_PER_SEC / 4) 1003 { 1004 calib_loop *= 2; 1005 for (i = 0; i < calib_loop; i++) 1006 unchecked_decompress_buf(outb, out_len, inb, sizeof(inb)); 1007 per_loop += calib_loop; 1008 } 1009 1010 fprintf(stderr, "decompression:\nCalibrated to %d iterations per loop\n", 1011 per_loop); 1012 1013 start = clock(); 1014 for (i = 0; i < 10; i++) 1015 for (j = 0; j < per_loop; j++) 1016 unchecked_decompress_buf(outb, out_len, inb, sizeof(inb)); 1017 end = clock(); 1018 seconds = (end - start) / (float) CLOCKS_PER_SEC; 1019 fprintf(stderr, "%.2f seconds == %.2f MB/s\n", seconds, 1020 ((long long) in_len * per_loop * 10) / (1024 * 1024 * seconds)); 1021 } 1022 1023 int 1024 main(int argc, char *argv[]) 1025 { 1026 int compress = 1; 1027 if (argc > 1 && !strcmp(argv[1], "-d")) 1028 compress = 0; 1029 if (argc > 1 && !strcmp(argv[1], "-b")) 1030 benchmark(stdin); 1031 else 1032 transfer_file(stdin, stdout, compress); 1033 return 0; 1034 } 1035 1036 #endif 1037 1038