Files
ps5upload/payload/include/direct_open.h
T
phantomptrandClaude Opus 5.5 0b6901fa53 fix(payload): a resumed single-file upload writes where the acknowledged bytes end
A fresh single-file upload pre-sizes its .ps5up2-tmp to the full transfer
(posix_fallocate); a resume reopened it with O_APPEND, so the resumed
shards were written past the pre-sized end. A 1 GiB upload that dropped
and resumed grew to 1 GiB + the unsent remainder and COMMIT refused it
with size_mismatch (seen on the Android emulator).

Resume now opens without appending and seeks to entry->bytes_received
(the journal's acknowledged byte count). direct_open.h is header-only so
the new host self-test drives the same code on a real file.

Hardware (Phat 5.10): 1 GiB upload, engine killed at ~300 MB, resumed
with the same tx id — before: size_mismatch at 1,879,048,192 bytes;
after: committed at 1,073,741,824, SHA-256 identical to the source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:11:20 -07:00

60 lines
2.0 KiB
C

/* How a single-file upload opens its `.ps5up2-tmp`.
*
* A fresh upload truncates the tmp file (the caller then pre-sizes it to the
* full transfer with posix_fallocate). A resumed upload must write where the
* acknowledged bytes end — NOT at end-of-file: the pre-sized file's end is
* the full transfer size, so O_APPEND wrote past it (a 1 GiB upload that
* resumed after 384 MiB grew to 1 GiB + 640 MiB and COMMIT refused it with
* size_mismatch). Positional writes are also idempotent: a shard re-sent
* after a drop overwrites the same bytes instead of duplicating them.
*
* Header-only so the host self-test drives exactly this code. */
#ifndef PS5UPLOAD_DIRECT_OPEN_H
#define PS5UPLOAD_DIRECT_OPEN_H
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct {
int flags;
/* Fresh file: the caller may pre-size it after opening. */
int truncate;
/* Where the next shard's bytes go. */
uint64_t offset;
} direct_open_plan_t;
/* `bytes_acked` is the tx's acknowledged byte count (entry->bytes_received,
* persisted in the journal), i.e. the byte offset of the next shard. */
static inline direct_open_plan_t direct_open_plan(int is_first_shard, uint64_t bytes_acked) {
direct_open_plan_t p;
if (is_first_shard) {
p.flags = O_WRONLY | O_CREAT | O_TRUNC;
p.truncate = 1;
p.offset = 0;
} else {
p.flags = O_WRONLY | O_CREAT;
p.truncate = 0;
p.offset = bytes_acked;
}
return p;
}
/* Open per `p` and position the fd at `p.offset`. Returns the fd, or -1 with
* errno set (the fd is closed if the seek fails). */
static inline int direct_open_for_shard(const char *path, direct_open_plan_t p) {
int fd = open(path, p.flags, 0777);
if (fd < 0) return -1;
if (p.offset > 0 && lseek(fd, (off_t)p.offset, SEEK_SET) < 0) {
int e = errno;
close(fd);
errno = e;
return -1;
}
return fd;
}
#endif