| 417 | more likely than average to be picked, and lines after short lines |
| 418 | are less likely */ |
| 419 | staticfn char * |
| 420 | get_rnd_line( |
| 421 | dlb *fh, /* already opened file */ |
| 422 | char *buf, /* output buffer */ |
| 423 | unsigned bufsiz, /* (unsigned) sizeof buf */ |
| 424 | int (*rng)(int), /* random number routine; rn2(N) or similar, 0..N-1 */ |
| 425 | long startpos, /* location in file of first line of interest */ |
| 426 | long endpos, /* location one byte past last line of interest; |
| 427 | * if 0, end-of-file will be used */ |
| 428 | unsigned padlength) /* expected line length; 0 if no expectations */ |
| 429 | { |
| 430 | char *newl, *xbufp, xbuf[BUFSZ]; |
| 431 | long filechunksize, chunkoffset; |
| 432 | int trylimit; |
| 433 | |
| 434 | *buf = '\0'; |
| 435 | if (!endpos) { |
| 436 | (void) dlb_fseek(fh, 0L, SEEK_END); |
| 437 | endpos = dlb_ftell(fh); |
| 438 | } |
| 439 | filechunksize = endpos - startpos; |
| 440 | |
| 441 | /* might be zero (only if file is empty); should complain in that |
| 442 | case but it could happen over and over, also the suggestion |
| 443 | that save and restore might fix the problem wouldn't be useful */ |
| 444 | if (filechunksize < 1L) |
| 445 | return buf; |
| 446 | /* 'rumors' is about 3/4 of the way to the limit on a 16-bit config |
| 447 | for the whole, roughly 3/8 of the way for either half; all active |
| 448 | configurations these days are at least 32-bits anyway */ |
| 449 | nhassert(filechunksize <= INT_MAX); /* essential for rn2() */ |
| 450 | |
| 451 | /* |
| 452 | * Position randomly which will probably be in the middle of a line. |
| 453 | * (Occasionally by chance it will happen to be at the very start of |
| 454 | * a line, but we'll have no way of knowing that so have to behave |
| 455 | * as if it were positioned in the middle.) |
| 456 | * Read the rest of that line, then use the next one. If there's no |
| 457 | * next line (ie, end of file), go back to beginning and use first. |
| 458 | * |
| 459 | * When short lines have been padded to length N, only accept long |
| 460 | * lines if we land within last N+1 characters (+1 is for newline |
| 461 | * which hasn't been stripped away yet), effectively shortening |
| 462 | * them to normal length. That yields even selection distribution. |
| 463 | */ |
| 464 | for (trylimit = 10; trylimit > 0; --trylimit) { |
| 465 | chunkoffset = (long) (*rng)((int) filechunksize); |
| 466 | (void) dlb_fseek(fh, startpos + chunkoffset, SEEK_SET); |
| 467 | (void) dlb_fgets(buf, bufsiz, fh); |
| 468 | /* if padlength is 0, accept any position; when non-zero, |
| 469 | padlength does not count the newline but strlen(buf) does */ |
| 470 | if (!padlength || (unsigned) strlen(buf) <= padlength + 1) |
| 471 | break; |
| 472 | } |
| 473 | /* use next line; for rumors, caller takes care of whether startpos |
| 474 | and endpos cover just true rumors or just false rumors; reaching |
| 475 | endpos is equivalent to end-of-file in order to avoid using the |
| 476 | first false rumor if fseek for a true one lands within the last one */ |