cvsclient.c: switch to binary (non-pipe mode for received files
[cvsps:cvsps.git] / cvsclient.c
1 /*
2  * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc.
3  * See COPYING file for license information 
4  */
5
6 #include <string.h>
7 #include <unistd.h>
8 #include <stdlib.h>
9 #include <limits.h>
10 #include <stdarg.h>
11 #include <stdbool.h>
12 #include <zlib.h>
13 #include <sys/socket.h>
14
15 #include "debug.h"
16 #include "tcpsocket.h"
17 #include "sio.h"
18 #include "cvsclient.h"
19 #include "util.h"
20
21 #define RD_BUFF_SIZE 4096
22
23 struct _CvsServerCtx 
24 {
25     int read_fd;
26     int write_fd;
27     char root[PATH_MAX];
28
29     bool is_pserver;
30
31     /* buffered reads from descriptor */
32     char read_buff[RD_BUFF_SIZE];
33     char * head;
34     char * tail;
35
36     bool compressed;
37     z_stream zout;
38     z_stream zin;
39
40     /* when reading compressed data, the compressed data buffer */
41     unsigned char zread_buff[RD_BUFF_SIZE];
42 };
43
44 static void get_cvspass(char *, const char *, int len);
45 static void send_string(CvsServerCtx *, const char *, ...);
46 static int read_response(CvsServerCtx *, const char *);
47 static void ctx_to_fp(CvsServerCtx * ctx, FILE * fp);
48 static int read_line(CvsServerCtx * ctx, char * p, int len);
49
50 static CvsServerCtx * open_ctx_pserver(CvsServerCtx *, const char *);
51 static CvsServerCtx * open_ctx_forked(CvsServerCtx *, const char *);
52
53 CvsServerCtx * open_cvs_server(char * p_root, int compress)
54 {
55     CvsServerCtx * ctx = (CvsServerCtx*)malloc(sizeof(*ctx));
56     char root[PATH_MAX];
57     char * p = root, *tok;
58
59     if (!ctx)
60         return NULL;
61
62     ctx->head = ctx->tail = ctx->read_buff;
63     ctx->read_fd = ctx->write_fd = -1;
64     ctx->compressed = false;
65     ctx->is_pserver = false;
66
67     if (compress)
68     {
69         memset(&ctx->zout, 0, sizeof(z_stream));
70         memset(&ctx->zin, 0, sizeof(z_stream));
71         
72         /* 
73          * to 'prime' the reads, make it look like there was output
74          * room available (i.e. we have processed all pending compressed 
75          * data
76          */
77         ctx->zin.avail_out = 1;
78         
79         if (deflateInit(&ctx->zout, compress) != Z_OK)
80         {
81             free(ctx);
82             return NULL;
83         }
84         
85         if (inflateInit(&ctx->zin) != Z_OK)
86         {
87             deflateEnd(&ctx->zout);
88             free(ctx);
89             return NULL;
90         }
91     }
92
93     strcpy_a(root, p_root, PATH_MAX);
94
95     tok = strsep(&p, ":");
96
97     /* if root string looks like :pserver:... then the first token will be empty */
98     if (strlen(tok) == 0)
99     {
100         char * method = strsep(&p, ":");
101         if (strcmp(method, "pserver") == 0)
102         {
103             ctx = open_ctx_pserver(ctx, p);
104         }
105         else if (strstr("local:ext:fork:server", method))
106         {
107             /* handle all of these via fork, even local */
108             ctx = open_ctx_forked(ctx, p);
109         }
110         else
111         {
112             debug(DEBUG_APPERROR, "cvsclient: unsupported cvs access method: %s", method);
113             free(ctx);
114             ctx = NULL;
115         }
116     }
117     else
118     {
119         ctx = open_ctx_forked(ctx, p_root);
120     }
121
122     if (ctx)
123     {
124         char buff[BUFSIZ];
125
126         send_string(ctx, "Root %s\n", ctx->root);
127
128         /* this is taken from 1.11.1p1 trace - but with Mbinary removed. we can't handle it (yet!) */
129         send_string(ctx, "Valid-responses ok error Valid-requests Checked-in New-entry Checksum Copy-file Updated Created Update-existing Merged Patched Rcs-diff Mode Mod-time Removed Remove-entry Set-static-directory Clear-static-directory Set-sticky Clear-sticky Template Set-checkin-prog Set-update-prog Notified Module-expansion Wrapper-rcsOption M E F\n", ctx->root);
130
131         send_string(ctx, "valid-requests\n");
132
133         /* check for the commands we will issue */
134         read_line(ctx, buff, BUFSIZ);
135         if (strncmp(buff, "Valid-requests", 14) != 0)
136         {
137             debug(DEBUG_APPERROR, "cvsclient: bad response '%s' to valid-requests command", buff);
138             close_cvs_server(ctx);
139             return NULL;
140         }
141
142         if (!strstr(buff, " version") ||
143             !strstr(buff, " rlog") ||
144             !strstr(buff, " diff") ||
145             !strstr(buff, " co"))
146         {
147             debug(DEBUG_APPERROR, "cvsclient: cvs server too old for cvsclient");
148             close_cvs_server(ctx);
149             return NULL;
150         }
151         
152         read_line(ctx, buff, BUFSIZ);
153         if (strcmp(buff, "ok") != 0)
154         {
155             debug(DEBUG_APPERROR, "cvsclient: bad ok trailer to valid-requests command");
156             close_cvs_server(ctx);
157             return NULL;
158         }
159
160         /* this is myterious but 'mandatory' */
161         send_string(ctx, "UseUnchanged\n");
162
163         if (compress)
164         {
165             send_string(ctx, "Gzip-stream %d\n", compress);
166             ctx->compressed = true;
167         }
168
169         debug(DEBUG_STATUS, "cvsclient: initialized to CVSROOT %s", ctx->root);
170     }
171
172     return ctx;
173 }
174
175 static CvsServerCtx * open_ctx_pserver(CvsServerCtx * ctx, const char * p_root)
176 {
177     char root[PATH_MAX];
178     char full_root[PATH_MAX];
179     char * p = root, *tok, *tok2;
180     char user[BUFSIZ];
181     char server[BUFSIZ];
182     char pass[BUFSIZ];
183     char port[8];
184
185     strcpy_a(root, p_root, PATH_MAX);
186
187     /* parse initial "user@server" portion of p. */
188     tok = strsep(&p, "@");
189     tok2 = p;
190     if (p)
191         p += strcspn(p, ":/"); /* server part ends at first ':' or '/'. */
192     if (!tok || !tok2 || !strlen(tok) || 0 >= (p - tok2))
193     {
194         debug(DEBUG_APPERROR, "parse error on user@server in pserver");
195         goto out_free_err;
196     }
197
198     strcpy(user, tok);
199     memcpy(server, tok2, p - tok2);
200     server[p - tok2] = '\0';
201
202     /* p now points to ':' or '/' following server part. */
203     tok = strchr(p, '/'); /* find start of path */
204     if (!tok)
205     {
206         debug(DEBUG_APPERROR, "parse error: expecting / in root");
207         goto out_free_err;
208     }
209
210     if (*p == ':') /* port number specified. Ends at tok. */
211     {
212         p++;
213         memcpy(port, p, tok - p);
214         port[tok - p] = '\0';
215         if (port[0] == '\0')
216         {
217             debug(DEBUG_APPERROR, "parse error: empty port number, probably from extraneous ':'.");
218             goto out_free_err;
219         }
220     }
221     else
222     {
223         strcpy(port, "2401");
224     }
225
226     /* Make p point to path component, starting with '/'. */
227     p = tok;
228
229     /* the line from .cvspass is fully qualified, so rebuild */
230     snprintf(full_root, PATH_MAX, ":pserver:%s@%s:%s%s", user, server, port, p);
231     get_cvspass(pass, full_root, BUFSIZ);
232
233     debug(DEBUG_TCP, "user:%s server:%s port:%s pass:%s full_root:%s", user, server, port, pass, full_root);
234
235     if ((ctx->read_fd = tcp_create_socket(REUSE_ADDR)) < 0)
236         goto out_free_err;
237
238     ctx->write_fd = dup(ctx->read_fd);
239     if (ctx->write_fd < 0)
240         goto out_close_err;
241
242     if (tcp_connect(ctx->read_fd, server, atoi(port)) < 0)
243         goto out_close_err;
244     
245     send_string(ctx, "BEGIN AUTH REQUEST\n");
246     send_string(ctx, "%s\n", p);
247     send_string(ctx, "%s\n", user);
248     send_string(ctx, "%s\n", pass);
249     send_string(ctx, "END AUTH REQUEST\n");
250
251     if (!read_response(ctx, "I LOVE YOU"))
252         goto out_close_err;
253
254     strcpy_a(ctx->root, p, PATH_MAX);
255     ctx->is_pserver = true;
256
257     return ctx;
258
259  out_close_err:
260     close(ctx->read_fd);
261  out_free_err:
262     free(ctx);
263     return NULL;
264 }
265
266 static CvsServerCtx * open_ctx_forked(CvsServerCtx * ctx, const char * p_root)
267 {
268     char root[PATH_MAX];
269     char * p = root, *tok, *rep;
270     char execcmd[PATH_MAX];
271     int to_cvs[2];
272     int from_cvs[2];
273     pid_t pid;
274     const char * cvs_server = getenv("CVS_SERVER");
275
276     if (!cvs_server)
277         cvs_server = "cvs";
278
279     strcpy_a(root, p_root, PATH_MAX);
280
281     /* if there's a ':', it's remote */
282     tok = strsep(&p, ":");
283
284     if (p)
285     {
286         char * tok2;
287         /* coverity[tainted_data] */
288         const char * cvs_rsh = getenv("CVS_RSH");
289
290         if (!cvs_rsh)
291             cvs_rsh = "rsh";
292
293         tok2 = strsep(&tok, "@");
294
295         if (tok)
296             snprintf(execcmd, PATH_MAX, "%s -l %s %s %s server", cvs_rsh, tok2, tok, cvs_server);
297         else
298             snprintf(execcmd, PATH_MAX, "%s %s %s server", cvs_rsh, tok2, cvs_server);
299
300         rep = p;
301     }
302     else
303     {
304         snprintf(execcmd, PATH_MAX, "%s server", cvs_server);
305         rep = tok;
306     }
307
308     if (pipe(to_cvs) < 0)
309     {
310         debug(DEBUG_SYSERROR, "cvsclient: failed to create pipe to_cvs");
311         goto out_free_err;
312     }
313
314     if (pipe(from_cvs) < 0)
315     {
316         debug(DEBUG_SYSERROR, "cvsclient: failed to create pipe from_cvs");
317         goto out_close_err;
318     }
319
320     debug(DEBUG_TCP, "forked cmdline: %s", execcmd);
321
322     if ((pid = fork()) < 0)
323     {
324         debug(DEBUG_SYSERROR, "cvsclient: can't fork");
325         goto out_close2_err;
326     }
327     else if (pid == 0) /* child */
328     {
329         char * argp[4];
330         argp[0] = "sh";
331         argp[1] = "-c";
332         argp[2] = execcmd;
333         argp[3] = NULL;
334
335         close(to_cvs[1]);
336         close(from_cvs[0]);
337         
338         close(0);
339         if (dup(to_cvs[0]) < 0) {
340             debug(DEBUG_APPERROR, "cvsclient: dup of input failed");
341             exit(1);
342         }
343         close(1);
344         if (dup(from_cvs[1]) < 0) {
345             debug(DEBUG_APPERROR, "cvsclient: dup of output failed");
346             exit(1);
347         }
348
349         execv("/bin/sh",argp);
350
351         debug(DEBUG_APPERROR, "cvsclient: fatal: shouldn't be reached");
352         exit(1);
353     }
354
355     close(to_cvs[0]);
356     close(from_cvs[1]);
357     ctx->read_fd = from_cvs[0];
358     ctx->write_fd = to_cvs[1];
359
360     strcpy_a(ctx->root, rep, PATH_MAX);
361
362     return ctx;
363
364  out_close2_err:
365     close(from_cvs[0]);
366     close(from_cvs[1]);
367  out_close_err:
368     close(to_cvs[0]);
369     close(to_cvs[1]);
370  out_free_err:
371     free(ctx);
372     return NULL;
373 }
374
375 void close_cvs_server(CvsServerCtx * ctx)
376 {
377     /* FIXME: some sort of flushing should be done for non-compressed case */
378
379     if (ctx->compressed)
380     {
381         int ret, len;
382         char buff[BUFSIZ];
383
384         /* 
385          * there shouldn't be anything left, but we do want
386          * to send an 'end of stream' marker, (if such a thing
387          * actually exists..)
388          */
389         do
390         {
391             ctx->zout.next_out = (unsigned char *)buff;
392             ctx->zout.avail_out = BUFSIZ;
393             ret = deflate(&ctx->zout, Z_FINISH);
394
395             if ((ret == Z_OK || ret == Z_STREAM_END) && ctx->zout.avail_out != BUFSIZ)
396             {
397                 len = BUFSIZ - ctx->zout.avail_out;
398                 if (writen(ctx->write_fd, buff, len) != len)
399                     debug(DEBUG_APPERROR, "cvsclient: zout: error writing final state");
400                     
401                 //hexdump(buff, len, "cvsclient: zout: sending unsent data");
402             }
403         } while (ret == Z_OK);
404
405         if ((ret = deflateEnd(&ctx->zout)) != Z_OK)
406             debug(DEBUG_APPERROR, "cvsclient: zout: deflateEnd error: %s: %s", 
407                   (ret == Z_STREAM_ERROR) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx->zout.msg);
408     }
409     
410     /* we're done writing now */
411     debug(DEBUG_TCP, "cvsclient: closing cvs server write connection %d", ctx->write_fd);
412     close(ctx->write_fd);
413
414     /* 
415      * if this is pserver, then read_fd is a bi-directional socket.
416      * we want to shutdown the write side, just to make sure the 
417      * server get's eof
418      */
419     if (ctx->is_pserver)
420     {
421         debug(DEBUG_TCP, "cvsclient: shutdown on read socket");
422         if (shutdown(ctx->read_fd, SHUT_WR) < 0)
423             debug(DEBUG_SYSERROR, "cvsclient: error with shutdown on pserver socket");
424     }
425
426     if (ctx->compressed)
427     {
428         int ret = Z_OK, len, eof = 0;
429         unsigned char buff[BUFSIZ];
430
431         /* read to the 'eof'/'eos' marker.  there are two states we 
432          * track, looking for Z_STREAM_END (application level EOS)
433          * and EOF on socket.  Both should happen at the same time,
434          * but we need to do the read first, the first time through
435          * the loop, but we want to do one read after getting Z_STREAM_END
436          * too.  so this loop has really ugly exit conditions.
437          */
438         for(;;)
439         {
440             /*
441              * if there's nothing in the avail_in, and we
442              * inflated everything last pass (avail_out != 0)
443              * then slurp some more from the descriptor, 
444              * if we get EOF, exit the loop
445              */
446             if (ctx->zin.avail_in == 0 && ctx->zin.avail_out != 0)
447             {
448                 debug(DEBUG_TCP, "cvsclient: doing final slurp");
449                 len = read(ctx->read_fd, ctx->zread_buff, RD_BUFF_SIZE);
450                 debug(DEBUG_TCP, "cvsclient: did final slurp: %d", len);
451
452                 if (len <= 0)
453                 {
454                     eof = 1;
455                     break;
456                 }
457
458                 /* put the data into the inflate input stream */
459                 ctx->zin.next_in = ctx->zread_buff;
460                 ctx->zin.avail_in = len;
461             }
462
463             /* 
464              * if the last time through we got Z_STREAM_END, and we 
465              * get back here, it means we should've gotten EOF but
466              * didn't
467              */
468             if (ret == Z_STREAM_END)
469                 break;
470
471             ctx->zin.next_out = buff;
472             ctx->zin.avail_out = BUFSIZ;
473
474             ret = inflate(&ctx->zin, Z_SYNC_FLUSH);
475             len = BUFSIZ - ctx->zin.avail_out;
476             
477             if (ret == Z_BUF_ERROR)
478                 debug(DEBUG_APPERROR, "Z_BUF_ERROR");
479
480             if (ret == Z_OK && len == 0)
481                 debug(DEBUG_TCP, "cvsclient: no data out of inflate");
482
483             if (ret == Z_STREAM_END)
484                 debug(DEBUG_TCP, "cvsclient: got Z_STREAM_END");
485
486             if ((ret == Z_OK || ret == Z_STREAM_END) && len > 0)
487                 hexdump((char *)buff, BUFSIZ - ctx->zin.avail_out, "cvsclient: zin: unread data at close");
488         }
489
490         if (ret != Z_STREAM_END)
491             debug(DEBUG_APPERROR, "cvsclient: zin: Z_STREAM_END not encountered (premature EOF?)");
492
493         if (eof == 0)
494             debug(DEBUG_APPERROR, "cvsclient: zin: EOF not encountered (premature Z_STREAM_END?)");
495
496         if ((ret = inflateEnd(&ctx->zin)) != Z_OK)
497             debug(DEBUG_APPERROR, "cvsclient: zin: inflateEnd error: %s: %s", 
498                   (ret == Z_STREAM_ERROR) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx->zin.msg ? ctx->zin.msg : "");
499     }
500
501     debug(DEBUG_TCP, "cvsclient: closing cvs server read connection %d", ctx->read_fd);
502     close(ctx->read_fd);
503
504     free(ctx);
505 }
506
507 static void get_cvspass(char * pass, const char * root, int passbuflen)
508 {
509     char cvspass[PATH_MAX];
510     const char * home;
511     FILE * fp;
512
513     pass[0] = 0;
514
515     if (!(home = getenv("HOME")))
516     {
517         debug(DEBUG_APPERROR, "HOME environment variable not set");
518         exit(1);
519     }
520
521     if (snprintf(cvspass, PATH_MAX, "%s/.cvspass", home) >= PATH_MAX)
522     {
523         debug(DEBUG_APPERROR, "prefix buffer overflow");
524         exit(1);
525     }
526     
527     if ((fp = fopen(cvspass, "r")))
528     {
529         char buff[BUFSIZ];
530         int len = strlen(root);
531
532         while (fgets(buff, BUFSIZ, fp))
533         {
534             /* FIXME: what does /1 mean? */
535             if (strncmp(buff, "/1 ", 3) != 0)
536                 continue;
537
538             if (strncmp(buff + 3, root, len) == 0)
539             {
540                 strcpy_a(pass, buff + 3 + len + 1, passbuflen);
541                 chop(pass);
542                 break;
543             }
544                 
545         }
546         fclose(fp);
547     }
548
549     if (!pass[0])
550         pass[0] = 'A';
551 }
552
553 static void send_string(CvsServerCtx * ctx, const char * str, ...)
554 {
555     int len;
556     unsigned char buff[BUFSIZ];
557     va_list ap;
558
559     va_start(ap, str);
560     len = vsnprintf((char *)buff, BUFSIZ, str, ap);
561     va_end(ap);
562
563     if (len >= BUFSIZ)
564     {
565         debug(DEBUG_APPERROR, "cvsclient: command send string overflow");
566         exit(1);
567     }
568
569     if (ctx->compressed)
570     {
571         unsigned char zbuff[BUFSIZ];
572
573         if  (ctx->zout.avail_in != 0)
574         {
575             debug(DEBUG_APPERROR, "cvsclient: zout: last output command not flushed");
576             exit(1);
577         }
578
579         ctx->zout.next_in = buff;
580         ctx->zout.avail_in = len;
581         ctx->zout.avail_out = 0;
582
583         while (ctx->zout.avail_in > 0 || ctx->zout.avail_out == 0)
584         {
585             int ret;
586
587             ctx->zout.next_out = zbuff;
588             ctx->zout.avail_out = BUFSIZ;
589             
590             /* FIXME: for the arguments before a command, flushing is counterproductive */
591             ret = deflate(&ctx->zout, Z_SYNC_FLUSH);
592             
593             if (ret == Z_OK)
594             {
595                 len = BUFSIZ - ctx->zout.avail_out;
596                 
597                 if (writen(ctx->write_fd, zbuff, len) != len)
598                 {
599                     debug(DEBUG_SYSERROR, "cvsclient: zout: can't write");
600                     exit(1);
601                 }
602             }
603             else
604             {
605                 debug(DEBUG_APPERROR, "cvsclient: zout: error %d %s", ret, ctx->zout.msg);
606             }
607         }
608     }
609     else
610     {
611         if (writen(ctx->write_fd, buff, len)  != len)
612         {
613             debug(DEBUG_SYSERROR, "cvsclient: can't send command");
614             exit(1);
615         }
616     }
617
618     debug(DEBUG_TCP, "string: '%s' sent", buff);
619 }
620
621 static int refill_buffer(CvsServerCtx * ctx)
622 {
623     int len;
624
625     if (ctx->head != ctx->tail)
626     {
627         debug(DEBUG_APPERROR, "cvsclient: refill_buffer called on non-empty buffer");
628         exit(1);
629     }
630
631     ctx->head = ctx->read_buff;
632     len = RD_BUFF_SIZE;
633         
634     if (ctx->compressed)
635     {
636         int zlen, ret;
637
638         /* if there was leftover buffer room, it's time to slurp more data */
639         do 
640         {
641             if (ctx->zin.avail_out > 0)
642             {
643                 if (ctx->zin.avail_in != 0)
644                 {
645                     debug(DEBUG_APPERROR, "cvsclient: zin: expect 0 avail_in");
646                     exit(1);
647                 }
648                 zlen = read(ctx->read_fd, ctx->zread_buff, RD_BUFF_SIZE);
649                 ctx->zin.next_in = ctx->zread_buff;
650                 ctx->zin.avail_in = zlen;
651             }
652             
653             ctx->zin.next_out = (unsigned char *)ctx->head;
654             ctx->zin.avail_out = len;
655             
656             /* FIXME: we don't always need Z_SYNC_FLUSH, do we? */
657             ret = inflate(&ctx->zin, Z_SYNC_FLUSH);
658         }
659         while (ctx->zin.avail_out == len);
660
661         if (ret == Z_OK)
662         {
663             ctx->tail = ctx->head + (len - ctx->zin.avail_out);
664         }
665         else
666         {
667             debug(DEBUG_APPERROR, "cvsclient: zin: error %d %s", ret, ctx->zin.msg);
668             exit(1);
669         }
670     }
671     else
672     {
673         len = read(ctx->read_fd, ctx->head, len);
674         ctx->tail = (len <= 0) ? ctx->head : ctx->head + len;
675     }
676
677     return len;
678 }
679
680 static int read_line(CvsServerCtx * ctx, char * p, int maxlen)
681 {
682     int len = 0;
683
684     if (maxlen <= 0)
685         return -1;
686
687     while (1)
688     {
689         if (ctx->head == ctx->tail)
690             if (refill_buffer(ctx) <= 0)
691                 return -1;
692
693         /* break out without advancing head if buffer is exhausted */
694         if (maxlen == 1 || (*p = *ctx->head++) == '\n')
695         {
696             *p = 0;
697             break;
698         }
699
700         p++;
701         len++;
702         maxlen--;
703     }
704
705     return len;
706 }
707
708 static int read_response(CvsServerCtx * ctx, const char * str)
709 {
710     /* FIXME: more than 1 char at a time */
711     char resp[BUFSIZ];
712
713     if (read_line(ctx, resp, BUFSIZ) < 0)
714         return 0;
715
716     debug(DEBUG_TCP, "response '%s' read", resp);
717
718     return (strcmp(resp, str) == 0);
719 }
720
721 static void ctx_to_fp(CvsServerCtx * ctx, FILE * fp)
722 {
723     char line[BUFSIZ];
724     long int conv;
725     char * sz_e;
726
727     while (1)
728     {
729         read_line(ctx, line, BUFSIZ);
730         debug(DEBUG_TCP, "ctx_to_fp: %s", line);
731
732         if (strncmp (line, "error ", 6) == 0)
733         {
734             debug(DEBUG_APPERROR, "ctx_to_fp: error: %s", line);
735             exit(1);
736         }
737
738         /* EOF. likely delete file */
739         if (strcmp (line, "ok") == 0)
740             break;
741
742         /* wait for raw data size */
743         conv = strtol(line, &sz_e, 10);
744         if (conv == LONG_MIN || conv ==  LONG_MAX || *sz_e != '\0')
745             continue;
746
747         debug(DEBUG_TCP, "ctx_to_fp: file size: %ld", conv);
748
749         while (conv)
750         {
751             long bs = ctx->tail - ctx->head;
752             if (bs > conv) bs = conv;
753
754             fwrite (ctx->head, 1, bs, fp);
755             ctx->head += bs;
756             conv -= bs;
757
758             if (conv)
759             {
760                 if (refill_buffer(ctx) <= 0)
761                 {
762                     debug(DEBUG_APPERROR, "ctx_to_fp: refill_buffer error");
763                     exit(1);
764                 }
765             }
766         }
767
768         read_line(ctx, line, BUFSIZ);
769         if (strcmp (line, "ok") != 0)
770         {
771             debug(DEBUG_APPERROR, "ctx_to_fp: error: expected 'ok' at EOF but got: %s", line);
772             exit(1);
773         }
774
775         break;
776     }
777
778     if (fp)
779         fflush(fp);
780 }
781
782 void cvs_rdiff(CvsServerCtx * ctx, 
783                const char * rep, const char * file, 
784                const char * rev1, const char * rev2)
785 {
786     /* NOTE: opts are ignored for rdiff, '-u' is always used */
787
788     send_string(ctx, "Argument -u\n");
789     send_string(ctx, "Argument -r\n");
790     send_string(ctx, "Argument %s\n", rev1);
791     send_string(ctx, "Argument -r\n");
792     send_string(ctx, "Argument %s\n", rev2);
793     send_string(ctx, "Argument %s%s\n", rep, file);
794     send_string(ctx, "rdiff\n");
795
796     ctx_to_fp(ctx, stdout);
797 }
798
799 void cvs_update(CvsServerCtx * ctx, const char * rep, const char * file, const char * rev, bool kk, FILE *fp)
800 {
801     if (kk)
802         send_string(ctx, "Argument -kk\n");
803     send_string(ctx, "Argument -r\n");
804     send_string(ctx, "Argument %s\n", rev);
805     send_string(ctx, "Argument %s/%s\n", rep, file);
806     send_string(ctx, "co\n");
807
808     ctx_to_fp(ctx, fp);
809 }
810
811 static bool parse_patch_arg(char * arg, char ** str)
812 {
813     char *tok, *tok2 = "";
814     tok = strsep(str, " ");
815     if (!tok)
816         return false;
817
818     if (*tok != '-')
819     {
820         debug(DEBUG_APPERROR, "diff_opts parse error: no '-' starting argument: %s", *str);
821         return false;
822     }
823     
824     /* if it's not 'long format' argument, we can process it efficiently */
825     if (tok[1] == '-')
826     {
827         debug(DEBUG_APPERROR, "diff_opts parse_error: long format args not supported");
828         return false;
829     }
830
831     /* see if command wants two args and they're separated by ' ' */
832     if (tok[2] == 0 && strchr("BdDFgiorVxYz", tok[1]))
833     {
834         tok2 = strsep(str, " ");
835         if (!tok2)
836         {
837             debug(DEBUG_APPERROR, "diff_opts parse_error: argument %s requires two arguments", tok);
838             return false;
839         }
840     }
841     
842     snprintf(arg, 32, "%s%s", tok, tok2);
843     return true;
844 }
845
846 void cvs_diff(CvsServerCtx * ctx, 
847                const char * rep, const char * file, 
848                const char * rev1, const char * rev2, const char * opts)
849 {
850     char argstr[BUFSIZ], *p = argstr;
851     char arg[32];
852     char file_buff[PATH_MAX], *basename;
853
854     strzncpy(argstr, opts, BUFSIZ);
855     while (parse_patch_arg(arg, &p))
856         send_string(ctx, "Argument %s\n", arg);
857
858     send_string(ctx, "Argument -r\n");
859     send_string(ctx, "Argument %s\n", rev1);
860     send_string(ctx, "Argument -r\n");
861     send_string(ctx, "Argument %s\n", rev2);
862
863     /* 
864      * we need to separate the 'basename' of file in order to 
865      * generate the Directory directive(s)
866      */
867     strzncpy(file_buff, file, PATH_MAX);
868     if ((basename = strrchr(file_buff, '/')))
869     {
870         *basename = 0;
871         send_string(ctx, "Directory %s/%s\n", rep, file_buff);
872         send_string(ctx, "%s/%s/%s\n", ctx->root, rep, file_buff);
873     }
874     else
875     {
876         send_string(ctx, "Directory %s\n", rep, file_buff);
877         send_string(ctx, "%s/%s\n", ctx->root, rep);
878     }
879
880     send_string(ctx, "Directory .\n");
881     send_string(ctx, "%s\n", ctx->root);
882     send_string(ctx, "Argument %s/%s\n", rep, file);
883     send_string(ctx, "diff\n");
884
885     ctx_to_fp(ctx, stdout);
886 }
887
888 /*
889  * FIXME: the design of this sucks.  It was originally designed to fork a subprocess
890  * which read the cvs response and send it back through a pipe the main process,
891  * which fdopen(3)ed the other end, and just used regular fgets.  This however
892  * didn't work because the reads of compressed data in the child process altered
893  * the compression state, and there was no way to resynchronize that state with
894  * the parent process.  We could use threads...
895  */
896 FILE * cvs_rlog_open(CvsServerCtx * ctx, const char * rep)
897 {
898     send_string(ctx, "Argument %s\n", rep);
899     send_string(ctx, "rlog\n");
900
901     /*
902      * FIXME: is it possible to create a 'fake' FILE * whose 'refill'
903      * function is below?
904      */
905     return (FILE*)ctx;
906 }
907
908 char * cvs_rlog_fgets(char * buff, int buflen, CvsServerCtx * ctx)
909 {
910     char lbuff[BUFSIZ];
911     int len;
912
913   reread:
914     len = read_line(ctx, lbuff, BUFSIZ);
915     debug(DEBUG_TCP, "cvsclient: rlog: read %s", lbuff);
916
917     if (memcmp(lbuff, "M ", 2) == 0)
918     {
919         memcpy(buff, lbuff + 2, len - 2);
920         buff[len - 2 ] = '\n';
921         buff[len - 1 ] = 0;
922     }
923     else if (memcmp(lbuff, "E ", 2) == 0)
924     {
925         debug(DEBUG_TCP, "%s", lbuff + 2);
926         goto reread;
927     }
928     else if (strcmp(lbuff, "ok") == 0 || strncmp(lbuff, "error", 5) == 0)
929     {
930         debug(DEBUG_TCP, "cvsclient: rlog: got command completion");
931         return NULL;
932     }
933
934     return buff;
935 }
936
937 void cvs_rlog_close(CvsServerCtx * ctx)
938 {
939 }
940
941 void cvs_version(CvsServerCtx * ctx, char * client_version, char * server_version, int cvlen, int svlen)
942 {
943     char lbuff[BUFSIZ];
944     strcpy_a(client_version, "Client: Concurrent Versions System (CVS) 99.99.99 (client/server) cvs-direct", cvlen);
945     send_string(ctx, "version\n");
946     read_line(ctx, lbuff, BUFSIZ);
947     if (memcmp(lbuff, "M ", 2) == 0)
948         snprintf(server_version, svlen, "Server: %s", lbuff + 2);
949     else
950         debug(DEBUG_APPERROR, "cvsclient: didn't read version: %s", lbuff);
951     
952     read_line(ctx, lbuff, BUFSIZ);
953     if (strstr(lbuff,"CVSACL")!=NULL) {
954         read_line(ctx, lbuff, BUFSIZ);
955     }
956     if (strcmp(lbuff, "ok") != 0)
957         debug(DEBUG_APPERROR, "cvsclient: protocol error reading version");
958
959     debug(DEBUG_TCP, "cvsclient: client version %s", client_version);
960     debug(DEBUG_TCP, "cvsclient: server version %s", server_version);
961 }