Guard against a core dump.
[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     }
216     else
217     {
218         strcpy(port, "2401");
219     }
220
221     /* Make p point to path component, starting with '/'. */
222     p = tok;
223
224     /* the line from .cvspass is fully qualified, so rebuild */
225     snprintf(full_root, PATH_MAX, ":pserver:%s@%s:%s%s", user, server, port, p);
226     get_cvspass(pass, full_root, BUFSIZ);
227
228     debug(DEBUG_TCP, "user:%s server:%s port:%s pass:%s full_root:%s", user, server, port, pass, full_root);
229
230     if ((ctx->read_fd = tcp_create_socket(REUSE_ADDR)) < 0)
231         goto out_free_err;
232
233     ctx->write_fd = dup(ctx->read_fd);
234     if (ctx->write_fd < 0)
235         goto out_close_err;
236
237     if (tcp_connect(ctx->read_fd, server, atoi(port)) < 0)
238         goto out_close_err;
239     
240     send_string(ctx, "BEGIN AUTH REQUEST\n");
241     send_string(ctx, "%s\n", p);
242     send_string(ctx, "%s\n", user);
243     send_string(ctx, "%s\n", pass);
244     send_string(ctx, "END AUTH REQUEST\n");
245
246     if (!read_response(ctx, "I LOVE YOU"))
247         goto out_close_err;
248
249     strcpy_a(ctx->root, p, PATH_MAX);
250     ctx->is_pserver = true;
251
252     return ctx;
253
254  out_close_err:
255     close(ctx->read_fd);
256  out_free_err:
257     free(ctx);
258     return NULL;
259 }
260
261 static CvsServerCtx * open_ctx_forked(CvsServerCtx * ctx, const char * p_root)
262 {
263     char root[PATH_MAX];
264     char * p = root, *tok, *rep;
265     char execcmd[PATH_MAX];
266     int to_cvs[2];
267     int from_cvs[2];
268     pid_t pid;
269     const char * cvs_server = getenv("CVS_SERVER");
270
271     if (!cvs_server)
272         cvs_server = "cvs";
273
274     strcpy_a(root, p_root, PATH_MAX);
275
276     /* if there's a ':', it's remote */
277     tok = strsep(&p, ":");
278
279     if (p)
280     {
281         char * tok2;
282         /* coverity[tainted_data] */
283         const char * cvs_rsh = getenv("CVS_RSH");
284
285         if (!cvs_rsh)
286             cvs_rsh = "rsh";
287
288         tok2 = strsep(&tok, "@");
289
290         if (tok)
291             snprintf(execcmd, PATH_MAX, "%s -l %s %s %s server", cvs_rsh, tok2, tok, cvs_server);
292         else
293             snprintf(execcmd, PATH_MAX, "%s %s %s server", cvs_rsh, tok2, cvs_server);
294
295         rep = p;
296     }
297     else
298     {
299         snprintf(execcmd, PATH_MAX, "%s server", cvs_server);
300         rep = tok;
301     }
302
303     if (pipe(to_cvs) < 0)
304     {
305         debug(DEBUG_SYSERROR, "cvsclient: failed to create pipe to_cvs");
306         goto out_free_err;
307     }
308
309     if (pipe(from_cvs) < 0)
310     {
311         debug(DEBUG_SYSERROR, "cvsclient: failed to create pipe from_cvs");
312         goto out_close_err;
313     }
314
315     debug(DEBUG_TCP, "forked cmdline: %s", execcmd);
316
317     if ((pid = fork()) < 0)
318     {
319         debug(DEBUG_SYSERROR, "cvsclient: can't fork");
320         goto out_close2_err;
321     }
322     else if (pid == 0) /* child */
323     {
324         char * argp[4];
325         argp[0] = "sh";
326         argp[1] = "-c";
327         argp[2] = execcmd;
328         argp[3] = NULL;
329
330         close(to_cvs[1]);
331         close(from_cvs[0]);
332         
333         close(0);
334         if (dup(to_cvs[0]) < 0) {
335             debug(DEBUG_APPERROR, "cvsclient: dup of input failed");
336             exit(1);
337         }
338         close(1);
339         if (dup(from_cvs[1]) < 0) {
340             debug(DEBUG_APPERROR, "cvsclient: dup of output failed");
341             exit(1);
342         }
343
344         execv("/bin/sh",argp);
345
346         debug(DEBUG_APPERROR, "cvsclient: fatal: shouldn't be reached");
347         exit(1);
348     }
349
350     close(to_cvs[0]);
351     close(from_cvs[1]);
352     ctx->read_fd = from_cvs[0];
353     ctx->write_fd = to_cvs[1];
354
355     strcpy_a(ctx->root, rep, PATH_MAX);
356
357     return ctx;
358
359  out_close2_err:
360     close(from_cvs[0]);
361     close(from_cvs[1]);
362  out_close_err:
363     close(to_cvs[0]);
364     close(to_cvs[1]);
365  out_free_err:
366     free(ctx);
367     return NULL;
368 }
369
370 void close_cvs_server(CvsServerCtx * ctx)
371 {
372     /* FIXME: some sort of flushing should be done for non-compressed case */
373
374     if (ctx->compressed)
375     {
376         int ret, len;
377         char buff[BUFSIZ];
378
379         /* 
380          * there shouldn't be anything left, but we do want
381          * to send an 'end of stream' marker, (if such a thing
382          * actually exists..)
383          */
384         do
385         {
386             ctx->zout.next_out = (unsigned char *)buff;
387             ctx->zout.avail_out = BUFSIZ;
388             ret = deflate(&ctx->zout, Z_FINISH);
389
390             if ((ret == Z_OK || ret == Z_STREAM_END) && ctx->zout.avail_out != BUFSIZ)
391             {
392                 len = BUFSIZ - ctx->zout.avail_out;
393                 if (writen(ctx->write_fd, buff, len) != len)
394                     debug(DEBUG_APPERROR, "cvsclient: zout: error writing final state");
395                     
396                 //hexdump(buff, len, "cvsclient: zout: sending unsent data");
397             }
398         } while (ret == Z_OK);
399
400         if ((ret = deflateEnd(&ctx->zout)) != Z_OK)
401             debug(DEBUG_APPERROR, "cvsclient: zout: deflateEnd error: %s: %s", 
402                   (ret == Z_STREAM_ERROR) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx->zout.msg);
403     }
404     
405     /* we're done writing now */
406     debug(DEBUG_TCP, "cvsclient: closing cvs server write connection %d", ctx->write_fd);
407     close(ctx->write_fd);
408
409     /* 
410      * if this is pserver, then read_fd is a bi-directional socket.
411      * we want to shutdown the write side, just to make sure the 
412      * server get's eof
413      */
414     if (ctx->is_pserver)
415     {
416         debug(DEBUG_TCP, "cvsclient: shutdown on read socket");
417         if (shutdown(ctx->read_fd, SHUT_WR) < 0)
418             debug(DEBUG_SYSERROR, "cvsclient: error with shutdown on pserver socket");
419     }
420
421     if (ctx->compressed)
422     {
423         int ret = Z_OK, len, eof = 0;
424         unsigned char buff[BUFSIZ];
425
426         /* read to the 'eof'/'eos' marker.  there are two states we 
427          * track, looking for Z_STREAM_END (application level EOS)
428          * and EOF on socket.  Both should happen at the same time,
429          * but we need to do the read first, the first time through
430          * the loop, but we want to do one read after getting Z_STREAM_END
431          * too.  so this loop has really ugly exit conditions.
432          */
433         for(;;)
434         {
435             /*
436              * if there's nothing in the avail_in, and we
437              * inflated everything last pass (avail_out != 0)
438              * then slurp some more from the descriptor, 
439              * if we get EOF, exit the loop
440              */
441             if (ctx->zin.avail_in == 0 && ctx->zin.avail_out != 0)
442             {
443                 debug(DEBUG_TCP, "cvsclient: doing final slurp");
444                 len = read(ctx->read_fd, ctx->zread_buff, RD_BUFF_SIZE);
445                 debug(DEBUG_TCP, "cvsclient: did final slurp: %d", len);
446
447                 if (len <= 0)
448                 {
449                     eof = 1;
450                     break;
451                 }
452
453                 /* put the data into the inflate input stream */
454                 ctx->zin.next_in = ctx->zread_buff;
455                 ctx->zin.avail_in = len;
456             }
457
458             /* 
459              * if the last time through we got Z_STREAM_END, and we 
460              * get back here, it means we should've gotten EOF but
461              * didn't
462              */
463             if (ret == Z_STREAM_END)
464                 break;
465
466             ctx->zin.next_out = buff;
467             ctx->zin.avail_out = BUFSIZ;
468
469             ret = inflate(&ctx->zin, Z_SYNC_FLUSH);
470             len = BUFSIZ - ctx->zin.avail_out;
471             
472             if (ret == Z_BUF_ERROR)
473                 debug(DEBUG_APPERROR, "Z_BUF_ERROR");
474
475             if (ret == Z_OK && len == 0)
476                 debug(DEBUG_TCP, "cvsclient: no data out of inflate");
477
478             if (ret == Z_STREAM_END)
479                 debug(DEBUG_TCP, "cvsclient: got Z_STREAM_END");
480
481             if ((ret == Z_OK || ret == Z_STREAM_END) && len > 0)
482                 hexdump((char *)buff, BUFSIZ - ctx->zin.avail_out, "cvsclient: zin: unread data at close");
483         }
484
485         if (ret != Z_STREAM_END)
486             debug(DEBUG_APPERROR, "cvsclient: zin: Z_STREAM_END not encountered (premature EOF?)");
487
488         if (eof == 0)
489             debug(DEBUG_APPERROR, "cvsclient: zin: EOF not encountered (premature Z_STREAM_END?)");
490
491         if ((ret = inflateEnd(&ctx->zin)) != Z_OK)
492             debug(DEBUG_APPERROR, "cvsclient: zin: inflateEnd error: %s: %s", 
493                   (ret == Z_STREAM_ERROR) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx->zin.msg ? ctx->zin.msg : "");
494     }
495
496     debug(DEBUG_TCP, "cvsclient: closing cvs server read connection %d", ctx->read_fd);
497     close(ctx->read_fd);
498
499     free(ctx);
500 }
501
502 static void get_cvspass(char * pass, const char * root, int passbuflen)
503 {
504     char cvspass[PATH_MAX];
505     const char * home;
506     FILE * fp;
507
508     pass[0] = 0;
509
510     if (!(home = getenv("HOME")))
511     {
512         debug(DEBUG_APPERROR, "HOME environment variable not set");
513         exit(1);
514     }
515
516     if (snprintf(cvspass, PATH_MAX, "%s/.cvspass", home) >= PATH_MAX)
517     {
518         debug(DEBUG_APPERROR, "prefix buffer overflow");
519         exit(1);
520     }
521     
522     if ((fp = fopen(cvspass, "r")))
523     {
524         char buff[BUFSIZ];
525         int len = strlen(root);
526
527         while (fgets(buff, BUFSIZ, fp))
528         {
529             /* FIXME: what does /1 mean? */
530             if (strncmp(buff, "/1 ", 3) != 0)
531                 continue;
532
533             if (strncmp(buff + 3, root, len) == 0)
534             {
535                 strcpy_a(pass, buff + 3 + len + 1, passbuflen);
536                 chop(pass);
537                 break;
538             }
539                 
540         }
541         fclose(fp);
542     }
543
544     if (!pass[0])
545         pass[0] = 'A';
546 }
547
548 static void send_string(CvsServerCtx * ctx, const char * str, ...)
549 {
550     int len;
551     unsigned char buff[BUFSIZ];
552     va_list ap;
553
554     va_start(ap, str);
555     len = vsnprintf((char *)buff, BUFSIZ, str, ap);
556     va_end(ap);
557
558     if (len >= BUFSIZ)
559     {
560         debug(DEBUG_APPERROR, "cvsclient: command send string overflow");
561         exit(1);
562     }
563
564     if (ctx->compressed)
565     {
566         unsigned char zbuff[BUFSIZ];
567
568         if  (ctx->zout.avail_in != 0)
569         {
570             debug(DEBUG_APPERROR, "cvsclient: zout: last output command not flushed");
571             exit(1);
572         }
573
574         ctx->zout.next_in = buff;
575         ctx->zout.avail_in = len;
576         ctx->zout.avail_out = 0;
577
578         while (ctx->zout.avail_in > 0 || ctx->zout.avail_out == 0)
579         {
580             int ret;
581
582             ctx->zout.next_out = zbuff;
583             ctx->zout.avail_out = BUFSIZ;
584             
585             /* FIXME: for the arguments before a command, flushing is counterproductive */
586             ret = deflate(&ctx->zout, Z_SYNC_FLUSH);
587             
588             if (ret == Z_OK)
589             {
590                 len = BUFSIZ - ctx->zout.avail_out;
591                 
592                 if (writen(ctx->write_fd, zbuff, len) != len)
593                 {
594                     debug(DEBUG_SYSERROR, "cvsclient: zout: can't write");
595                     exit(1);
596                 }
597             }
598             else
599             {
600                 debug(DEBUG_APPERROR, "cvsclient: zout: error %d %s", ret, ctx->zout.msg);
601             }
602         }
603     }
604     else
605     {
606         if (writen(ctx->write_fd, buff, len)  != len)
607         {
608             debug(DEBUG_SYSERROR, "cvsclient: can't send command");
609             exit(1);
610         }
611     }
612
613     debug(DEBUG_TCP, "string: '%s' sent", buff);
614 }
615
616 static int refill_buffer(CvsServerCtx * ctx)
617 {
618     int len;
619
620     if (ctx->head != ctx->tail)
621     {
622         debug(DEBUG_APPERROR, "cvsclient: refill_buffer called on non-empty buffer");
623         exit(1);
624     }
625
626     ctx->head = ctx->read_buff;
627     len = RD_BUFF_SIZE;
628         
629     if (ctx->compressed)
630     {
631         int zlen, ret;
632
633         /* if there was leftover buffer room, it's time to slurp more data */
634         do 
635         {
636             if (ctx->zin.avail_out > 0)
637             {
638                 if (ctx->zin.avail_in != 0)
639                 {
640                     debug(DEBUG_APPERROR, "cvsclient: zin: expect 0 avail_in");
641                     exit(1);
642                 }
643                 zlen = read(ctx->read_fd, ctx->zread_buff, RD_BUFF_SIZE);
644                 ctx->zin.next_in = ctx->zread_buff;
645                 ctx->zin.avail_in = zlen;
646             }
647             
648             ctx->zin.next_out = (unsigned char *)ctx->head;
649             ctx->zin.avail_out = len;
650             
651             /* FIXME: we don't always need Z_SYNC_FLUSH, do we? */
652             ret = inflate(&ctx->zin, Z_SYNC_FLUSH);
653         }
654         while (ctx->zin.avail_out == len);
655
656         if (ret == Z_OK)
657         {
658             ctx->tail = ctx->head + (len - ctx->zin.avail_out);
659         }
660         else
661         {
662             debug(DEBUG_APPERROR, "cvsclient: zin: error %d %s", ret, ctx->zin.msg);
663             exit(1);
664         }
665     }
666     else
667     {
668         len = read(ctx->read_fd, ctx->head, len);
669         ctx->tail = (len <= 0) ? ctx->head : ctx->head + len;
670     }
671
672     return len;
673 }
674
675 static int read_line(CvsServerCtx * ctx, char * p, int maxlen)
676 {
677     int len = 0;
678
679     if (maxlen <= 0)
680         return -1;
681
682     while (1)
683     {
684         if (ctx->head == ctx->tail)
685             if (refill_buffer(ctx) <= 0)
686                 return -1;
687
688         /* break out without advancing head if buffer is exhausted */
689         if (maxlen == 1 || (*p = *ctx->head++) == '\n')
690         {
691             *p = 0;
692             break;
693         }
694
695         p++;
696         len++;
697         maxlen--;
698     }
699
700     return len;
701 }
702
703 static int read_response(CvsServerCtx * ctx, const char * str)
704 {
705     /* FIXME: more than 1 char at a time */
706     char resp[BUFSIZ];
707
708     if (read_line(ctx, resp, BUFSIZ) < 0)
709         return 0;
710
711     debug(DEBUG_TCP, "response '%s' read", resp);
712
713     return (strcmp(resp, str) == 0);
714 }
715
716 static void ctx_to_fp(CvsServerCtx * ctx, FILE * fp)
717 {
718     char line[BUFSIZ];
719
720     while (1)
721     {
722         read_line(ctx, line, BUFSIZ);
723         debug(DEBUG_TCP, "ctx_to_fp: %s", line);
724         if (memcmp(line, "M ", 2) == 0)
725         {
726             if (fp)
727                 fprintf(fp, "%s\n", line + 2);
728         }
729         else if (memcmp(line, "E ", 2) == 0)
730         {
731             debug(DEBUG_TCP, "%s", line + 2);
732         }
733         else if (strncmp(line, "ok", 2) == 0 || strncmp(line, "error", 5) == 0)
734         {
735             break;
736         }
737     }
738
739     if (fp)
740         fflush(fp);
741 }
742
743 void cvs_rdiff(CvsServerCtx * ctx, 
744                const char * rep, const char * file, 
745                const char * rev1, const char * rev2)
746 {
747     /* NOTE: opts are ignored for rdiff, '-u' is always used */
748
749     send_string(ctx, "Argument -u\n");
750     send_string(ctx, "Argument -r\n");
751     send_string(ctx, "Argument %s\n", rev1);
752     send_string(ctx, "Argument -r\n");
753     send_string(ctx, "Argument %s\n", rev2);
754     send_string(ctx, "Argument %s%s\n", rep, file);
755     send_string(ctx, "rdiff\n");
756
757     ctx_to_fp(ctx, stdout);
758 }
759
760 void cvs_update(CvsServerCtx * ctx, const char * rep, const char * file, const char * rev, bool kk, FILE *fp)
761 {
762     if (kk)
763         send_string(ctx, "Argument -kk\n");
764     send_string(ctx, "Argument -p\n");
765     send_string(ctx, "Argument -r\n");
766     send_string(ctx, "Argument %s\n", rev);
767     send_string(ctx, "Argument %s/%s\n", rep, file);
768     send_string(ctx, "co\n");
769
770     ctx_to_fp(ctx, fp);
771 }
772
773 static bool parse_patch_arg(char * arg, char ** str)
774 {
775     char *tok, *tok2 = "";
776     tok = strsep(str, " ");
777     if (!tok)
778         return false;
779
780     if (*tok != '-')
781     {
782         debug(DEBUG_APPERROR, "diff_opts parse error: no '-' starting argument: %s", *str);
783         return false;
784     }
785     
786     /* if it's not 'long format' argument, we can process it efficiently */
787     if (tok[1] == '-')
788     {
789         debug(DEBUG_APPERROR, "diff_opts parse_error: long format args not supported");
790         return false;
791     }
792
793     /* see if command wants two args and they're separated by ' ' */
794     if (tok[2] == 0 && strchr("BdDFgiorVxYz", tok[1]))
795     {
796         tok2 = strsep(str, " ");
797         if (!tok2)
798         {
799             debug(DEBUG_APPERROR, "diff_opts parse_error: argument %s requires two arguments", tok);
800             return false;
801         }
802     }
803     
804     snprintf(arg, 32, "%s%s", tok, tok2);
805     return true;
806 }
807
808 void cvs_diff(CvsServerCtx * ctx, 
809                const char * rep, const char * file, 
810                const char * rev1, const char * rev2, const char * opts)
811 {
812     char argstr[BUFSIZ], *p = argstr;
813     char arg[32];
814     char file_buff[PATH_MAX], *basename;
815
816     strzncpy(argstr, opts, BUFSIZ);
817     while (parse_patch_arg(arg, &p))
818         send_string(ctx, "Argument %s\n", arg);
819
820     send_string(ctx, "Argument -r\n");
821     send_string(ctx, "Argument %s\n", rev1);
822     send_string(ctx, "Argument -r\n");
823     send_string(ctx, "Argument %s\n", rev2);
824
825     /* 
826      * we need to separate the 'basename' of file in order to 
827      * generate the Directory directive(s)
828      */
829     strzncpy(file_buff, file, PATH_MAX);
830     if ((basename = strrchr(file_buff, '/')))
831     {
832         *basename = 0;
833         send_string(ctx, "Directory %s/%s\n", rep, file_buff);
834         send_string(ctx, "%s/%s/%s\n", ctx->root, rep, file_buff);
835     }
836     else
837     {
838         send_string(ctx, "Directory %s\n", rep, file_buff);
839         send_string(ctx, "%s/%s\n", ctx->root, rep);
840     }
841
842     send_string(ctx, "Directory .\n");
843     send_string(ctx, "%s\n", ctx->root);
844     send_string(ctx, "Argument %s/%s\n", rep, file);
845     send_string(ctx, "diff\n");
846
847     ctx_to_fp(ctx, stdout);
848 }
849
850 /*
851  * FIXME: the design of this sucks.  It was originally designed to fork a subprocess
852  * which read the cvs response and send it back through a pipe the main process,
853  * which fdopen(3)ed the other end, and just used regular fgets.  This however
854  * didn't work because the reads of compressed data in the child process altered
855  * the compression state, and there was no way to resynchronize that state with
856  * the parent process.  We could use threads...
857  */
858 FILE * cvs_rlog_open(CvsServerCtx * ctx, const char * rep)
859 {
860     send_string(ctx, "Argument %s\n", rep);
861     send_string(ctx, "rlog\n");
862
863     /*
864      * FIXME: is it possible to create a 'fake' FILE * whose 'refill'
865      * function is below?
866      */
867     return (FILE*)ctx;
868 }
869
870 char * cvs_rlog_fgets(char * buff, int buflen, CvsServerCtx * ctx)
871 {
872     char lbuff[BUFSIZ];
873     int len;
874
875     len = read_line(ctx, lbuff, BUFSIZ);
876     debug(DEBUG_TCP, "cvsclient: rlog: read %s", lbuff);
877
878     if (memcmp(lbuff, "M ", 2) == 0)
879     {
880         memcpy(buff, lbuff + 2, len - 2);
881         buff[len - 2 ] = '\n';
882         buff[len - 1 ] = 0;
883     }
884     else if (memcmp(lbuff, "E ", 2) == 0)
885     {
886         debug(DEBUG_TCP, "%s", lbuff + 2);
887     }
888     else if (strcmp(lbuff, "ok") == 0 || strncmp(lbuff, "error", 5) == 0)
889     {
890         debug(DEBUG_TCP, "cvsclient: rlog: got command completion");
891         return NULL;
892     }
893
894     return buff;
895 }
896
897 void cvs_rlog_close(CvsServerCtx * ctx)
898 {
899 }
900
901 void cvs_version(CvsServerCtx * ctx, char * client_version, char * server_version, int cvlen, int svlen)
902 {
903     char lbuff[BUFSIZ];
904     strcpy_a(client_version, "Client: Concurrent Versions System (CVS) 99.99.99 (client/server) cvs-direct", cvlen);
905     send_string(ctx, "version\n");
906     read_line(ctx, lbuff, BUFSIZ);
907     if (memcmp(lbuff, "M ", 2) == 0)
908         snprintf(server_version, svlen, "Server: %s", lbuff + 2);
909     else
910         debug(DEBUG_APPERROR, "cvsclient: didn't read version: %s", lbuff);
911     
912     read_line(ctx, lbuff, BUFSIZ);
913     if (strstr(lbuff,"CVSACL")!=NULL) {
914         read_line(ctx, lbuff, BUFSIZ);
915     }
916     if (strcmp(lbuff, "ok") != 0)
917         debug(DEBUG_APPERROR, "cvsclient: protocol error reading version");
918
919     debug(DEBUG_TCP, "cvsclient: client version %s", client_version);
920     debug(DEBUG_TCP, "cvsclient: server version %s", server_version);
921 }