Implemented a revision-map option in cvsps.
[cvsps:cvsps.git] / cvs_direct.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 #include <cbtcommon/debug.h>
15 #include <cbtcommon/text_util.h>
16 #include <cbtcommon/tcpsocket.h>
17 #include <cbtcommon/sio.h>
18
19 #include "cvs_direct.h"
20 #include "util.h"
21
22 #define RD_BUFF_SIZE 4096
23
24 struct _CvsServerCtx 
25 {
26     int read_fd;
27     int write_fd;
28     char root[PATH_MAX];
29
30     bool is_pserver;
31
32     /* buffered reads from descriptor */
33     char read_buff[RD_BUFF_SIZE];
34     char * head;
35     char * tail;
36
37     bool compressed;
38     z_stream zout;
39     z_stream zin;
40
41     /* when reading compressed data, the compressed data buffer */
42     unsigned char zread_buff[RD_BUFF_SIZE];
43 };
44
45 static void get_cvspass(char *, const char *, int len);
46 static void send_string(CvsServerCtx *, const char *, ...);
47 static int read_response(CvsServerCtx *, const char *);
48 static void ctx_to_fp(CvsServerCtx * ctx, FILE * fp);
49 static int read_line(CvsServerCtx * ctx, char * p, int len);
50
51 static CvsServerCtx * open_ctx_pserver(CvsServerCtx *, const char *);
52 static CvsServerCtx * open_ctx_forked(CvsServerCtx *, const char *);
53
54 CvsServerCtx * open_cvs_server(char * p_root, int compress)
55 {
56     CvsServerCtx * ctx = (CvsServerCtx*)malloc(sizeof(*ctx));
57     char root[PATH_MAX];
58     char * p = root, *tok;
59
60     if (!ctx)
61         return NULL;
62
63     ctx->head = ctx->tail = ctx->read_buff;
64     ctx->read_fd = ctx->write_fd = -1;
65     ctx->compressed = false;
66     ctx->is_pserver = false;
67
68     if (compress)
69     {
70         memset(&ctx->zout, 0, sizeof(z_stream));
71         memset(&ctx->zin, 0, sizeof(z_stream));
72         
73         /* 
74          * to 'prime' the reads, make it look like there was output
75          * room available (i.e. we have processed all pending compressed 
76          * data
77          */
78         ctx->zin.avail_out = 1;
79         
80         if (deflateInit(&ctx->zout, compress) != Z_OK)
81         {
82             free(ctx);
83             return NULL;
84         }
85         
86         if (inflateInit(&ctx->zin) != Z_OK)
87         {
88             deflateEnd(&ctx->zout);
89             free(ctx);
90             return NULL;
91         }
92     }
93
94     strcpy_a(root, p_root, PATH_MAX);
95
96     tok = strsep(&p, ":");
97
98     /* if root string looks like :pserver:... then the first token will be empty */
99     if (strlen(tok) == 0)
100     {
101         char * method = strsep(&p, ":");
102         if (strcmp(method, "pserver") == 0)
103         {
104             ctx = open_ctx_pserver(ctx, p);
105         }
106         else if (strstr("local:ext:fork:server", method))
107         {
108             /* handle all of these via fork, even local */
109             ctx = open_ctx_forked(ctx, p);
110         }
111         else
112         {
113             debug(DEBUG_APPERROR, "cvs_direct: unsupported cvs access method: %s", method);
114             free(ctx);
115             ctx = NULL;
116         }
117     }
118     else
119     {
120         ctx = open_ctx_forked(ctx, p_root);
121     }
122
123     if (ctx)
124     {
125         char buff[BUFSIZ];
126
127         send_string(ctx, "Root %s\n", ctx->root);
128
129         /* this is taken from 1.11.1p1 trace - but with Mbinary removed. we can't handle it (yet!) */
130         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);
131
132         send_string(ctx, "valid-requests\n");
133
134         /* check for the commands we will issue */
135         read_line(ctx, buff, BUFSIZ);
136         if (strncmp(buff, "Valid-requests", 14) != 0)
137         {
138             debug(DEBUG_APPERROR, "cvs_direct: bad response to valid-requests command");
139             close_cvs_server(ctx);
140             return NULL;
141         }
142
143         if (!strstr(buff, " version") ||
144             !strstr(buff, " rlog") ||
145             !strstr(buff, " diff") ||
146             !strstr(buff, " co"))
147         {
148             debug(DEBUG_APPERROR, "cvs_direct: cvs server too old for cvs_direct");
149             close_cvs_server(ctx);
150             return NULL;
151         }
152         
153         read_line(ctx, buff, BUFSIZ);
154         if (strcmp(buff, "ok") != 0)
155         {
156             debug(DEBUG_APPERROR, "cvs_direct: bad ok trailer to valid-requests command");
157             close_cvs_server(ctx);
158             return NULL;
159         }
160
161         /* this is myterious but 'mandatory' */
162         send_string(ctx, "UseUnchanged\n");
163
164         if (compress)
165         {
166             send_string(ctx, "Gzip-stream %d\n", compress);
167             ctx->compressed = true;
168         }
169
170         debug(DEBUG_APPMSG2, "cvs_direct: initialized to CVSROOT %s", ctx->root);
171     }
172
173     return ctx;
174 }
175
176 static CvsServerCtx * open_ctx_pserver(CvsServerCtx * ctx, const char * p_root)
177 {
178     char root[PATH_MAX];
179     char full_root[PATH_MAX];
180     char * p = root, *tok, *tok2;
181     char user[BUFSIZ];
182     char server[BUFSIZ];
183     char pass[BUFSIZ];
184     char port[8];
185
186     strcpy_a(root, p_root, PATH_MAX);
187
188     /* parse initial "user@server" portion of p. */
189     tok = strsep(&p, "@");
190     tok2 = 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, "cvs_direct: failed to create pipe to_cvs");
306         goto out_free_err;
307     }
308
309     if (pipe(from_cvs) < 0)
310     {
311         debug(DEBUG_SYSERROR, "cvs_direct: 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, "cvs_direct: 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, "cvs_direct: dup of input failed");
336             exit(1);
337         }
338         close(1);
339         if (dup(from_cvs[1]) < 0) {
340             debug(DEBUG_APPERROR, "cvs_direct: dup of output failed");
341             exit(1);
342         }
343
344         execv("/bin/sh",argp);
345
346         debug(DEBUG_APPERROR, "cvs_direct: 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, "cvs_direct: zout: error writing final state");
395                     
396                 //hexdump(buff, len, "cvs_direct: zout: sending unsent data");
397             }
398         } while (ret == Z_OK);
399
400         if ((ret = deflateEnd(&ctx->zout)) != Z_OK)
401             debug(DEBUG_APPERROR, "cvs_direct: 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, "cvs_direct: 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, "cvs_direct: shutdown on read socket");
417         if (shutdown(ctx->read_fd, SHUT_WR) < 0)
418             debug(DEBUG_SYSERROR, "cvs_direct: 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, "cvs_direct: doing final slurp");
444                 len = read(ctx->read_fd, ctx->zread_buff, RD_BUFF_SIZE);
445                 debug(DEBUG_TCP, "cvs_direct: 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, "cvs_direct: no data out of inflate");
477
478             if (ret == Z_STREAM_END)
479                 debug(DEBUG_TCP, "cvs_direct: 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, "cvs_direct: zin: unread data at close");
483         }
484
485         if (ret != Z_STREAM_END)
486             debug(DEBUG_APPERROR, "cvs_direct: zin: Z_STREAM_END not encountered (premature EOF?)");
487
488         if (eof == 0)
489             debug(DEBUG_APPERROR, "cvs_direct: zin: EOF not encountered (premature Z_STREAM_END?)");
490
491         if ((ret = inflateEnd(&ctx->zin)) != Z_OK)
492             debug(DEBUG_APPERROR, "cvs_direct: 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, "cvs_direct: 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, "cvs_direct: 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, "cvs_direct: 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, "cvs_direct: zout: can't write");
595                     exit(1);
596                 }
597             }
598             else
599             {
600                 debug(DEBUG_APPERROR, "cvs_direct: 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, "cvs_direct: 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, "cvs_direct: 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, "cvs_direct: 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, "cvs_direct: 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_rupdate(CvsServerCtx * ctx, const char * rep, const char * file, const char * rev, FILE *fp)
761 {
762     send_string(ctx, "Argument -kk\n");
763     send_string(ctx, "Argument -p\n");
764     send_string(ctx, "Argument -r\n");
765     send_string(ctx, "Argument %s\n", rev);
766     send_string(ctx, "Argument %s/%s\n", rep, file);
767     send_string(ctx, "co\n");
768
769     ctx_to_fp(ctx, fp);
770 }
771
772 static bool parse_patch_arg(char * arg, char ** str)
773 {
774     char *tok, *tok2 = "";
775     tok = strsep(str, " ");
776     if (!tok)
777         return false;
778
779     if (*tok != '-')
780     {
781         debug(DEBUG_APPERROR, "diff_opts parse error: no '-' starting argument: %s", *str);
782         return false;
783     }
784     
785     /* if it's not 'long format' argument, we can process it efficiently */
786     if (tok[1] == '-')
787     {
788         debug(DEBUG_APPERROR, "diff_opts parse_error: long format args not supported");
789         return false;
790     }
791
792     /* see if command wants two args and they're separated by ' ' */
793     if (tok[2] == 0 && strchr("BdDFgiorVxYz", tok[1]))
794     {
795         tok2 = strsep(str, " ");
796         if (!tok2)
797         {
798             debug(DEBUG_APPERROR, "diff_opts parse_error: argument %s requires two arguments", tok);
799             return false;
800         }
801     }
802     
803     snprintf(arg, 32, "%s%s", tok, tok2);
804     return true;
805 }
806
807 void cvs_diff(CvsServerCtx * ctx, 
808                const char * rep, const char * file, 
809                const char * rev1, const char * rev2, const char * opts)
810 {
811     char argstr[BUFSIZ], *p = argstr;
812     char arg[32];
813     char file_buff[PATH_MAX], *basename;
814
815     strzncpy(argstr, opts, BUFSIZ);
816     while (parse_patch_arg(arg, &p))
817         send_string(ctx, "Argument %s\n", arg);
818
819     send_string(ctx, "Argument -r\n");
820     send_string(ctx, "Argument %s\n", rev1);
821     send_string(ctx, "Argument -r\n");
822     send_string(ctx, "Argument %s\n", rev2);
823
824     /* 
825      * we need to separate the 'basename' of file in order to 
826      * generate the Directory directive(s)
827      */
828     strzncpy(file_buff, file, PATH_MAX);
829     if ((basename = strrchr(file_buff, '/')))
830     {
831         *basename = 0;
832         send_string(ctx, "Directory %s/%s\n", rep, file_buff);
833         send_string(ctx, "%s/%s/%s\n", ctx->root, rep, file_buff);
834     }
835     else
836     {
837         send_string(ctx, "Directory %s\n", rep, file_buff);
838         send_string(ctx, "%s/%s\n", ctx->root, rep);
839     }
840
841     send_string(ctx, "Directory .\n");
842     send_string(ctx, "%s\n", ctx->root);
843     send_string(ctx, "Argument %s/%s\n", rep, file);
844     send_string(ctx, "diff\n");
845
846     ctx_to_fp(ctx, stdout);
847 }
848
849 /*
850  * FIXME: the design of this sucks.  It was originally designed to fork a subprocess
851  * which read the cvs response and send it back through a pipe the main process,
852  * which fdopen(3)ed the other end, and just used regular fgets.  This however
853  * didn't work because the reads of compressed data in the child process altered
854  * the compression state, and there was no way to resynchronize that state with
855  * the parent process.  We could use threads...
856  */
857 FILE * cvs_rlog_open(CvsServerCtx * ctx, const char * rep, const char * date_str)
858 {
859     /* note: use of the date_str is handled in a non-standard, cvsps specific way */
860     if (date_str && date_str[0])
861     {
862         send_string(ctx, "Argument -d\n", rep);
863         send_string(ctx, "Argument %s<1 Jan 2038 05:00:00 -0000\n", date_str);
864         send_string(ctx, "Argument -d\n", rep);
865         send_string(ctx, "Argument %s\n", date_str);
866     }
867
868     send_string(ctx, "Argument %s\n", rep);
869     send_string(ctx, "rlog\n");
870
871     /*
872      * FIXME: is it possible to create a 'fake' FILE * whose 'refill'
873      * function is below?
874      */
875     return (FILE*)ctx;
876 }
877
878 char * cvs_rlog_fgets(char * buff, int buflen, CvsServerCtx * ctx)
879 {
880     char lbuff[BUFSIZ];
881     int len;
882
883     len = read_line(ctx, lbuff, BUFSIZ);
884     debug(DEBUG_TCP, "cvs_direct: rlog: read %s", lbuff);
885
886     if (memcmp(lbuff, "M ", 2) == 0)
887     {
888         memcpy(buff, lbuff + 2, len - 2);
889         buff[len - 2 ] = '\n';
890         buff[len - 1 ] = 0;
891     }
892     else if (memcmp(lbuff, "E ", 2) == 0)
893     {
894         debug(DEBUG_TCP, "%s", lbuff + 2);
895     }
896     else if (strcmp(lbuff, "ok") == 0 || strncmp(lbuff, "error", 5) == 0)
897     {
898         debug(DEBUG_TCP, "cvs_direct: rlog: got command completion");
899         return NULL;
900     }
901
902     return buff;
903 }
904
905 void cvs_rlog_close(CvsServerCtx * ctx)
906 {
907 }
908
909 void cvs_version(CvsServerCtx * ctx, char * client_version, char * server_version, int cvlen, int svlen)
910 {
911     char lbuff[BUFSIZ];
912     strcpy_a(client_version, "Client: Concurrent Versions System (CVS) 99.99.99 (client/server) cvs-direct", cvlen);
913     send_string(ctx, "version\n");
914     read_line(ctx, lbuff, BUFSIZ);
915     if (memcmp(lbuff, "M ", 2) == 0)
916         snprintf(server_version, svlen, "Server: %s", lbuff + 2);
917     else
918         debug(DEBUG_APPERROR, "cvs_direct: didn't read version: %s", lbuff);
919     
920     read_line(ctx, lbuff, BUFSIZ);
921     if (strstr(lbuff,"CVSACL")!=NULL) {
922         read_line(ctx, lbuff, BUFSIZ);
923     }
924     if (strcmp(lbuff, "ok") != 0)
925         debug(DEBUG_APPERROR, "cvs_direct: protocol error reading version");
926
927     debug(DEBUG_TCP, "cvs_direct: client version %s", client_version);
928     debug(DEBUG_TCP, "cvs_direct: server version %s", server_version);
929 }