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