--- /dev/null
+/*
+ * protocol is:
+ * server sends banner
+ * - "chiark-realtime-replicator\n" [27 bytes]
+ * - length of declaration, as 4 hex digits, zero prefixed,
+ * and a space [5 bytes]. In this protocol version this
+ * will be "0002" but client _must_ parse it.
+ * server sends declaration
+ * - one of "u " or "d" [1 byte]
+ * - optionally, some more ascii text, reserved for future use
+ * must be ignored by client (but not sent by server)
+ * - a newline [1 byte]
+ * client sends
+ * - 0x01 go
+ * then for each update
+ * sender sends
+ * - 0x02 update using rle and 8-bit counts
+ * - zero or more repetitions of
+ * n single byte giving length of data same as last time
+ * d single byte giving length of data changed
+ * ... d bytes of data
+ * where n!=0 or d!=0 (and for first update, n==0)
+ * - 0x00 0x00
+ * indicates file is complete and should be installed
+ * or server may send
+ * - 0x03 destination file should be deleted
+ * but note that contents must be retained by receiver
+ * as it may be used for rle updates
+ */
+
+#define REPLMSG_GO 0x01
+#define REPLMSG_RLE8 0x02
+#define REPLMSG_RM 0x03
+
+static void vdie(int ec, const char *fmt, const char *emstr, va_list al) {
+ fputs("realtime-replicator: ",stderr);
+ vfprintf(stderr,fmt,al);
+ if (emstr) fprintf(stderr,": %s",emstr);
+ fputc('\n',stderr);
+ exit(ec);
+}
+static void die(int ec, const char *fmt, const char *emstr, ...) {
+ va_arg al;
+ va_start(al,fmt);
+ vdiee(ec,fmt,emstr,al);
+}
+
+static void diem(void) { die(16,"malloc failed",strerror(errno)); }
+
+static void diee(const char *fmt, ...) {
+ const char *em= strerror(errno);
+ va_arg al;
+ va_start(al,fmt);
+ diee(8,fmt,em,al);
+}
+static void die_protocol(const char *fmt, ...) {
+ va_arg al;
+ va_start(al,fmt);
+ diee(12,fmt,0,al);
+
+static void die_badrecv(FILE *f, const char *what) {
+ if (ferror(f)) diee("transmission failed while receiving %s", what);
+ if (feof(f)) die_protocol("receiver got unexpected EOF in %s", what);
+ abort();
+}
+
+static void receiver_write(const unsigned char *buf, int n,
+ FILE *newfile, const char *tmpfilename) {
+ int r;
+
+ r= fwrite(dbuf,1,n,newfile);
+ if (r != n) diee("failed to write temporary receiving file `%s'",
+ tmpfilename);
+}
+
+static void receiver(const char *filename, FILE *comms) {
+ FILE *lastfile=0, *newfile;
+ char *tmpfilename;
+
+ if (asprintf(&tmpfilename, ".realtime-replicator.#%s#")==-1) diem();
+
+ r= unlink(tmpfilename);
+ if (r && errno!=ENOENT)
+ diee("could not remove temporary receiving file `%s'", tmpfilename);
+
+ for (;;) {
+ c= fgetc(comms); if (c==EOF) break;
+ switch (c) {
+
+ case REPLMSG_RLE8:
+ newfile= fopen(tmpfilename, "w");
+ if (!newfile) diee("could not create temporary receiving file `%s'",
+ tmpfilename);
+ for (;;) {
+ unsigned char lu[2], dbuf[255];
+ r= fread(lu,1,2,comms); if (r!=2) die_badrecv(comms,"RLE8 elem hdr");
+ if (!lu[0] && !lu[1]) break;
+ if (lu[0]) {
+ if (!lastfile) die_protocol("first RLE8 requests some previous");
+ r= fread(dbuf,1,lu[0],lastfile);
+ if (r!=lu[0]) {
+ if (ferror(lastfile)) diee("could not read current (old) file"
+ " `%s'", filename);
+ assert(feof(lastfile));
+ die_protocol("RLE8 requests more previous than is available");
+ }
+ receiver_write(dbuf,lu[0],newfile,tmpfilename);
+ }
+ if (lu[1]) {
+ r= fread(dbuf,1,lu[1],comms);
+ if (r!=lu[1]) die_badrecv(comms,"RLE8 literal data");
+ receiver_write(dbuf,lu[1],newfile,tmpfilename);
+ }
+ }
+ if (fflush(newfile)) diee("could not flush temporary receiving file"
+ " `%s'", tmpfilename);
+ if (rename(tmpfilename, filename))
+ diee("could not install new version of destination file `%s'",
+ filename);
+ if (fclose(lastfile)) diee("failed to close old (now unlinked) file");
+ lastfile= newfile;
+ r= fseek(lastfile,0,SEEK_SET);
+ if (r) diee("failed to seek installed destination file `%s'", filename);
+ continue;
+
+ case REPLMSG_RM:
+ r= unlink(filename);
+ if (r && errno!=ENOENT)
+ diee("could not remove destination file `%s' (as instructed"
+ " by sender)", filename);
+ continue;
+
+ default:
+ die_protocol("unknown transfer message code 0x%02x",c);
+ }
+ }
+ if (feof(comms)) return 0;
+ die_badrecv(comms,"transfer message code");
+}
+
+ static void sender(const char *filename, FILE *comms,
+ unsigned long interval_usec) {
+ struct stat stab, laststab;
+ memset(&stab,0,sizeof(stab));
+ FILE *lastfile, *newfile;
+ int told_removed= 0;
+
+ for (;;) {
+ r= stat(filename, &stab);
+ if (r) {
+ newfile= 0;
+ } else {
+ if (stab.st_dev == laststab.st_dev &&
+ stab.st_ino == laststab.st_ino) {
+ usleep(interval_usec);
+ continue;
+ }
+ newfile= fopen(filename, "r");
+ }
+ if (!newfile) {
+ if (errno!=ENOENT) diee("could not access source file `%s'",filename);
+ if (told_removed) {
+ usleep(interval_usec);
+ continue;
+ }
+ sender_sendbyte(REPLMSG_RM);
+ continue;
+ }
+
+ for (;;) {
+ n= 0;
+ while (n<255) {
+ cl= nextchar(lastfile);
+ cn= nextchar(newfile);
+ if (cl!=cn) {
+
+ if (!feof(lastfile)) {
+ cl= fgetc(lastfile);
+ if (ferror(lastfile))
+ diee("could not read old source file `%s'",filename);
+ assert(cl != EOF || feof(lastfile));
+ }
+
+ if (errno!=ENOENT) diee("could not open source file `%s'",filename);
+ sender_send
+
+ if (r)
+}
+
+static void sender(
+
+ assert(ferror(newfile));
+
+
+ r= fread(dbuf,1,lu[0],comms);
+ if (r!=lu[0]) die_badrecv(comms,
+
+
+ c= fgetc(comms);
+
+
+int main(int argc, const char **argv) {
+ for (