chiark / gitweb /
489ab2daf5ec09e15cc2ae0e8d567fb09de6d36c
[chiark-utils.git] / cprogs / rcopy-repeatedly.c
1 /*
2  * protocol is:
3  *   server sends banner
4  *    - "chiark-realtime-replicator\n" [27 bytes]
5  *    - length of declaration, as 4 hex digits, zero prefixed,
6  *      and a space [5 bytes].  In this protocol version this
7  *      will be "0002" but client _must_ parse it.
8  *   server sends declaration
9  *    - one of "u " or "d" [1 byte]
10  *    - optionally, some more ascii text, reserved for future use
11  *      must be ignored by client (but not sent by server)
12  *    - a newline [1 byte]
13  *   client sends
14  *    - 0x01   go
15  * then for each update
16  *   sender sends
17  *    - 0x02   update using rle and 8-bit counts
18  *    - zero or more repetitions of
19  *        n    single byte giving length of data same as last time
20  *        d    single byte giving length of data changed
21  *        ...  d bytes of data
22  *        where n!=0 or d!=0 (and for first update, n==0)
23  *    - 0x00 0x00
24  *       indicates file is complete and should be installed
25  *   or server may send
26  *    - 0x03   destination file should be deleted
27  *             but note that contents must be retained by receiver
28  *             as it may be used for rle updates
29  */
30
31 #define REPLMSG_GO   0x01
32 #define REPLMSG_RLE8 0x02
33 #define REPLMSG_RM   0x03
34
35 static void vdie(int ec, const char *fmt, const char *emstr, va_list al) {
36   fputs("realtime-replicator: ",stderr);
37   vfprintf(stderr,fmt,al);
38   if (emstr) fprintf(stderr,": %s",emstr);
39   fputc('\n',stderr);
40   exit(ec);
41 }
42 static void die(int ec, const char *fmt, const char *emstr, ...) {
43   va_arg al;
44   va_start(al,fmt);
45   vdiee(ec,fmt,emstr,al);
46 }
47
48 static void diem(void) { die(16,"malloc failed",strerror(errno)); }
49
50 static void diee(const char *fmt, ...) {
51   const char *em= strerror(errno);
52   va_arg al;
53   va_start(al,fmt);
54   diee(8,fmt,em,al);
55 }
56 static void die_protocol(const char *fmt, ...) {
57   va_arg al;
58   va_start(al,fmt);
59   diee(12,fmt,0,al);
60
61 static void die_badrecv(FILE *f, const char *what) {
62   if (ferror(f)) diee("transmission failed while receiving %s", what);
63   if (feof(f)) die_protocol("receiver got unexpected EOF in %s", what);
64   abort();
65 }
66
67 static void receiver_write(const unsigned char *buf, int n,
68                            FILE *newfile, const char *tmpfilename) {
69   int r;
70
71   r= fwrite(dbuf,1,n,newfile);
72   if (r != n) diee("failed to write temporary receiving file `%s'",
73                    tmpfilename);
74 }
75  
76 static void receiver(const char *filename, FILE *comms) {
77   FILE *lastfile=0, *newfile;
78   char *tmpfilename;
79
80   if (asprintf(&tmpfilename, ".realtime-replicator.#%s#")==-1) diem();
81   
82   r= unlink(tmpfilename);
83   if (r && errno!=ENOENT)
84     diee("could not remove temporary receiving file `%s'", tmpfilename);
85   
86   for (;;) {
87     c= fgetc(comms);  if (c==EOF) break;
88     switch (c) {
89
90     case REPLMSG_RLE8:
91       newfile= fopen(tmpfilename, "w");
92       if (!newfile) diee("could not create temporary receiving file `%s'",
93                          tmpfilename);
94       for (;;) {
95         unsigned char lu[2], dbuf[255];
96         r= fread(lu,1,2,comms);  if (r!=2) die_badrecv(comms,"RLE8 elem hdr");
97         if (!lu[0] && !lu[1]) break;
98         if (lu[0]) {
99           if (!lastfile) die_protocol("first RLE8 requests some previous");
100           r= fread(dbuf,1,lu[0],lastfile);
101           if (r!=lu[0]) {
102             if (ferror(lastfile)) diee("could not read current (old) file"
103                                        " `%s'", filename);
104             assert(feof(lastfile));
105             die_protocol("RLE8 requests more previous than is available");
106           }
107           receiver_write(dbuf,lu[0],newfile,tmpfilename);
108         }
109         if (lu[1]) {
110           r= fread(dbuf,1,lu[1],comms);
111           if (r!=lu[1]) die_badrecv(comms,"RLE8 literal data");
112           receiver_write(dbuf,lu[1],newfile,tmpfilename);
113         }
114       }
115       if (fflush(newfile)) diee("could not flush temporary receiving file"
116                                 " `%s'", tmpfilename);
117       if (rename(tmpfilename, filename))
118         diee("could not install new version of destination file `%s'",
119              filename);
120       if (fclose(lastfile)) diee("failed to close old (now unlinked) file");
121       lastfile= newfile;
122       r= fseek(lastfile,0,SEEK_SET);
123       if (r) diee("failed to seek installed destination file `%s'", filename);
124       continue;
125
126     case REPLMSG_RM:
127       r= unlink(filename);
128       if (r && errno!=ENOENT)
129         diee("could not remove destination file `%s' (as instructed"
130              " by sender)", filename);
131       continue;
132
133     default:
134       die_protocol("unknown transfer message code 0x%02x",c);
135     }
136   }
137   if (feof(comms)) return 0;
138   die_badrecv(comms,"transfer message code");
139 }
140
141  static void sender(const char *filename, FILE *comms,
142                     unsigned long interval_usec) {
143   struct stat stab, laststab;
144   memset(&stab,0,sizeof(stab));
145   FILE *lastfile, *newfile;
146   int told_removed= 0;
147
148   for (;;) {
149     r= stat(filename, &stab);
150     if (r) {
151       newfile= 0;
152     } else {
153       if (stab.st_dev == laststab.st_dev &&
154           stab.st_ino == laststab.st_ino) {
155         usleep(interval_usec);
156         continue;
157       }
158       newfile= fopen(filename, "r");
159     }
160     if (!newfile) {
161       if (errno!=ENOENT) diee("could not access source file `%s'",filename);
162       if (told_removed) {
163         usleep(interval_usec);
164         continue;
165       }
166       sender_sendbyte(REPLMSG_RM);
167       continue;
168     }
169
170     for (;;) {
171       n= 0;
172       while (n<255) {
173         cl= nextchar(lastfile);
174         cn= nextchar(newfile);
175         if (cl!=cn) { 
176
177         if (!feof(lastfile)) {
178           cl= fgetc(lastfile);
179           if (ferror(lastfile))
180             diee("could not read old source file `%s'",filename);
181           assert(cl != EOF || feof(lastfile));
182         }
183         
184       if (errno!=ENOENT) diee("could not open source file `%s'",filename);
185       sender_send
186
187     if (r) 
188 }
189
190 static void sender( 
191
192             assert(ferror(newfile));
193             
194
195           r= fread(dbuf,1,lu[0],comms);
196           if (r!=lu[0]) die_badrecv(comms,
197             
198
199         c= fgetc(comms);
200
201
202 int main(int argc, const char **argv) {
203   for (