remote.v

2.1 KB · raw

1//! The remote interface: five operations, two backends.2//!3//! A remote is dumb storage that can do one safe swap. Everything clever —4//! what to upload, what diverged, how to combine — lives in `src/exchange`, above5//! this interface, so the folder and S3 backends stay thin and the sync logic6//! is written once and tested offline.78module remote910import rand1112// get not finding a key is a normal answer, not an error, so `Object.found`13// carries it and errors mean real trouble (a refused connection, bad keys).14pub struct Object {15pub:16	found bool17	body  []u818	tag   string19}2021// Backend is one place to put bytes. `put_if_absent` never overwrites and22// `put_if_match` only overwrites when the version still matches, which is what23// makes two machines offering at once safe.24pub interface Backend {25mut:26	get(key string) !Object27	list(prefix string) ![]string28	put_if_absent(key string, body []u8) !bool29	put_if_match(key string, body []u8, tag string) !bool30	delete(key string) !31}3233pub const probe_key = 'koh-probe'3435// check runs the connection test `koh remote` insists on before saving a36// setting: write a probe object, confirm a second write of it is refused, then37// delete it. A backend that cannot do that cannot keep two machines honest.38pub fn check(mut backend Backend) ! {39	payload := 'koh probe'.bytes()40	// Each check has its own key: with a shared one, two machines setting up41	// at once delete each other's probe and report a broken remote.42	key := '${probe_key}-${rand.hex(16)}'43	if !backend.put_if_absent(key, payload)! {44		return error('the remote refused to store a new object')45	}46	defer {47		// Also on the early returns below; deleting twice is harmless.48		backend.delete(key) or {}49	}50	if backend.put_if_absent(key, payload)! {51		return error('the remote overwrote an existing object — conditional writes are not working')52	}53	probe := backend.get(key)!54	if !probe.found || probe.body != payload {55		return error('the remote did not give back what was written')56	}57	// Strip needs delete, so a remote that cannot delete fails here.58	backend.delete(key)!59}