(
source: pathlib.Path,
destination: pathlib.Path,
*,
expected_sha256: str,
expected_size: int,
)
| 444 | |
| 445 | |
| 446 | def copy_selected( |
| 447 | source: pathlib.Path, |
| 448 | destination: pathlib.Path, |
| 449 | *, |
| 450 | expected_sha256: str, |
| 451 | expected_size: int, |
| 452 | ) -> None: |
| 453 | if sha256_file(source, expected_size=expected_size) != expected_sha256: |
| 454 | raise ContractError(f"candidate object changed before selection copy: {source}") |
| 455 | destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True) |
| 456 | before = regular_status(source, ceiling=MAX_CANDIDATE_BYTES, label="candidate object") |
| 457 | flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) |
| 458 | descriptor = os.open(source, flags) |
| 459 | try: |
| 460 | opened = os.fstat(descriptor) |
| 461 | if not stat.S_ISREG(opened.st_mode) or (opened.st_dev, opened.st_ino) != ( |
| 462 | before.st_dev, |
| 463 | before.st_ino, |
| 464 | ): |
| 465 | raise ContractError(f"candidate object changed while being opened: {source}") |
| 466 | digest = hashlib.sha256() |
| 467 | total = 0 |
| 468 | with os.fdopen(descriptor, "rb", closefd=False) as input_handle, destination.open( |
| 469 | "xb" |
| 470 | ) as output_handle: |
| 471 | while True: |
| 472 | chunk = input_handle.read(1024 * 1024) |
| 473 | if not chunk: |
| 474 | break |
| 475 | total += len(chunk) |
| 476 | if total > expected_size: |
| 477 | raise ContractError(f"candidate object grew during selection: {source}") |
| 478 | digest.update(chunk) |
| 479 | output_handle.write(chunk) |
| 480 | output_handle.flush() |
| 481 | os.fsync(output_handle.fileno()) |
| 482 | finally: |
| 483 | os.close(descriptor) |
| 484 | if total != expected_size or digest.hexdigest() != expected_sha256: |
| 485 | raise ContractError(f"candidate object changed during selection copy: {source}") |
| 486 | destination.chmod(0o555) |
| 487 | if sha256_file(destination, expected_size=expected_size) != expected_sha256: |
| 488 | raise ContractError(f"selected copy is not content-bound: {destination}") |
| 489 | |
| 490 | |
| 491 | def main(argv: Sequence[str]) -> None: |
no test coverage detected