Add test about issue with no commit on a branch
[cvsps-hv.git] / cvs_direct.c
blob920487d9f6ab47a2002893225694b2b3eb531e1d
1 /*
2 * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc.
3 * See COPYING file for license information
4 */
6 #include <string.h>
7 #include <unistd.h>
8 #include <stdlib.h>
9 #include <limits.h>
10 #include <stdarg.h>
11 #include <zlib.h>
12 #include <sys/socket.h>
13 #include <cbtcommon/debug.h>
14 #include <cbtcommon/text_util.h>
15 #include <cbtcommon/tcpsocket.h>
16 #include <cbtcommon/sio.h>
18 #include "cvs_direct.h"
19 #include "util.h"
21 #define RD_BUFF_SIZE 4096
23 struct _CvsServerCtx
25 int read_fd;
26 int write_fd;
27 char root[PATH_MAX];
29 int is_pserver;
31 /* buffered reads from descriptor */
32 char read_buff[RD_BUFF_SIZE];
33 char * head;
34 char * tail;
36 int compressed;
37 z_stream zout;
38 z_stream zin;
40 /* when reading compressed data, the compressed data buffer */
41 char zread_buff[RD_BUFF_SIZE];
44 static void get_cvspass(char *, const char *);
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);
50 static CvsServerCtx * open_ctx_pserver(CvsServerCtx *, const char *);
51 static CvsServerCtx * open_ctx_forked(CvsServerCtx *, const char *);
53 CvsServerCtx * open_cvs_server(char * p_root, int compress)
55 CvsServerCtx * ctx = (CvsServerCtx*)malloc(sizeof(*ctx));
56 char root[PATH_MAX];
57 char * p = root, *tok;
59 if (!ctx)
60 return NULL;
62 ctx->head = ctx->tail = ctx->read_buff;
63 ctx->read_fd = ctx->write_fd = -1;
64 ctx->compressed = 0;
65 ctx->is_pserver = 0;
67 if (compress)
69 memset(&ctx->zout, 0, sizeof(z_stream));
70 memset(&ctx->zin, 0, sizeof(z_stream));
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
77 ctx->zin.avail_out = 1;
79 if (deflateInit(&ctx->zout, compress) != Z_OK)
81 free(ctx);
82 return NULL;
85 if (inflateInit(&ctx->zin) != Z_OK)
87 deflateEnd(&ctx->zout);
88 free(ctx);
89 return NULL;
93 strcpy(root, p_root);
95 tok = strsep(&p, ":");
97 /* if root string looks like :pserver:... then the first token will be empty */
98 if (strlen(tok) == 0)
100 char * method = strsep(&p, ":");
101 if (strcmp(method, "pserver") == 0)
103 ctx = open_ctx_pserver(ctx, p);
105 else if (strstr("local:ext:fork:server", method))
107 /* handle all of these via fork, even local */
108 ctx = open_ctx_forked(ctx, p);
110 else
112 debug(DEBUG_APPERROR, "cvs_direct: unsupported cvs access method: %s", method);
113 free(ctx);
114 ctx = NULL;
117 else
119 ctx = open_ctx_forked(ctx, p_root);
122 if (ctx)
124 char buff[BUFSIZ];
126 send_string(ctx, "Root %s\n", ctx->root);
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);
131 send_string(ctx, "valid-requests\n");
133 /* check for the commands we will issue */
134 read_line(ctx, buff);
135 if (strncmp(buff, "Valid-requests", 14) != 0)
137 debug(DEBUG_APPERROR, "cvs_direct: bad response to valid-requests command");
138 close_cvs_server(ctx);
139 return NULL;
142 if (!strstr(buff, " version") ||
143 !strstr(buff, " rlog") ||
144 !strstr(buff, " rdiff") ||
145 !strstr(buff, " diff") ||
146 !strstr(buff, " co"))
148 debug(DEBUG_APPERROR, "cvs_direct: cvs server too old for cvs_direct");
149 close_cvs_server(ctx);
150 return NULL;
153 read_line(ctx, buff);
154 if (strcmp(buff, "ok") != 0)
156 debug(DEBUG_APPERROR, "cvs_direct: bad ok trailer to valid-requests command");
157 close_cvs_server(ctx);
158 return NULL;
161 /* this is myterious but 'mandatory' */
162 send_string(ctx, "UseUnchanged\n");
164 if (compress)
166 send_string(ctx, "Gzip-stream %d\n", compress);
167 ctx->compressed = 1;
170 debug(DEBUG_APPMSG1, "cvs_direct initialized to CVSROOT %s", ctx->root);
173 return ctx;
176 static CvsServerCtx * open_ctx_pserver(CvsServerCtx * ctx, const char * p_root)
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];
186 strcpy(root, p_root);
188 tok = strsep(&p, ":");
189 if (strlen(tok) == 0 || !p)
191 debug(DEBUG_APPERROR, "parse error on third token");
192 goto out_free_err;
195 tok2 = strsep(&tok, "@");
196 if (!strlen(tok2) || (!tok || !strlen(tok)))
198 debug(DEBUG_APPERROR, "parse error on user@server in pserver");
199 goto out_free_err;
202 strcpy(user, tok2);
203 strcpy(server, tok);
205 if (*p != '/')
207 tok = strchr(p, '/');
208 if (!tok)
210 debug(DEBUG_APPERROR, "parse error: expecting / in root");
211 goto out_free_err;
214 memset(port, 0, sizeof(port));
215 memcpy(port, p, tok - p);
217 p = tok;
219 else
221 strcpy(port, "2401");
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);
228 debug(DEBUG_TCP, "user:%s server:%s port:%s pass:%s full_root:%s", user, server, port, pass, full_root);
230 if ((ctx->read_fd = tcp_create_socket(REUSE_ADDR)) < 0)
231 goto out_free_err;
233 ctx->write_fd = dup(ctx->read_fd);
235 if (tcp_connect(ctx->read_fd, server, atoi(port)) < 0)
236 goto out_close_err;
238 send_string(ctx, "BEGIN AUTH REQUEST\n");
239 send_string(ctx, "%s\n", p);
240 send_string(ctx, "%s\n", user);
241 send_string(ctx, "%s\n", pass);
242 send_string(ctx, "END AUTH REQUEST\n");
244 if (!read_response(ctx, "I LOVE YOU"))
245 goto out_close_err;
247 strcpy(ctx->root, p);
248 ctx->is_pserver = 1;
250 return ctx;
252 out_close_err:
253 close(ctx->read_fd);
254 out_free_err:
255 free(ctx);
256 return NULL;
259 static CvsServerCtx * open_ctx_forked(CvsServerCtx * ctx, const char * p_root)
261 char root[PATH_MAX];
262 char * p = root, *tok, *tok2, *rep;
263 char execcmd[PATH_MAX];
264 int to_cvs[2];
265 int from_cvs[2];
266 pid_t pid;
267 const char * cvs_server = getenv("CVS_SERVER");
269 if (!cvs_server)
270 cvs_server = "cvs";
272 strcpy(root, p_root);
274 /* if there's a ':', it's remote */
275 tok = strsep(&p, ":");
277 if (p)
279 const char * cvs_rsh = getenv("CVS_RSH");
281 if (!cvs_rsh)
282 cvs_rsh = "rsh";
284 tok2 = strsep(&tok, "@");
286 if (tok)
287 snprintf(execcmd, PATH_MAX, "%s -l %s %s %s server", cvs_rsh, tok2, tok, cvs_server);
288 else
289 snprintf(execcmd, PATH_MAX, "%s %s %s server", cvs_rsh, tok2, cvs_server);
291 rep = p;
293 else
295 snprintf(execcmd, PATH_MAX, "%s server", cvs_server);
296 rep = tok;
299 if (pipe(to_cvs) < 0)
301 debug(DEBUG_SYSERROR, "cvs_direct: failed to create pipe to_cvs");
302 goto out_free_err;
305 if (pipe(from_cvs) < 0)
307 debug(DEBUG_SYSERROR, "cvs_direct: failed to create pipe from_cvs");
308 goto out_close_err;
311 debug(DEBUG_TCP, "forked cmdline: %s", execcmd);
313 if ((pid = fork()) < 0)
315 debug(DEBUG_SYSERROR, "cvs_direct: can't fork");
316 goto out_close2_err;
318 else if (pid == 0) /* child */
320 char * argp[4];
321 argp[0] = "sh";
322 argp[1] = "-c";
323 argp[2] = execcmd;
324 argp[3] = NULL;
326 close(to_cvs[1]);
327 close(from_cvs[0]);
329 close(0);
330 dup(to_cvs[0]);
331 close(1);
332 dup(from_cvs[1]);
334 execv("/bin/sh",argp);
336 debug(DEBUG_APPERROR, "cvs_direct: fatal: shouldn't be reached");
337 exit(1);
340 close(to_cvs[0]);
341 close(from_cvs[1]);
342 ctx->read_fd = from_cvs[0];
343 ctx->write_fd = to_cvs[1];
345 strcpy(ctx->root, rep);
347 return ctx;
349 out_close2_err:
350 close(from_cvs[0]);
351 close(from_cvs[1]);
352 out_close_err:
353 close(to_cvs[0]);
354 close(to_cvs[1]);
355 out_free_err:
356 free(ctx);
357 return NULL;
360 void close_cvs_server(CvsServerCtx * ctx)
362 /* FIXME: some sort of flushing should be done for non-compressed case */
364 if (ctx->compressed)
366 int ret, len;
367 char buff[BUFSIZ];
370 * there shouldn't be anything left, but we do want
371 * to send an 'end of stream' marker, (if such a thing
372 * actually exists..)
376 ctx->zout.next_out = buff;
377 ctx->zout.avail_out = BUFSIZ;
378 ret = deflate(&ctx->zout, Z_FINISH);
380 if ((ret == Z_OK || ret == Z_STREAM_END) && ctx->zout.avail_out != BUFSIZ)
382 len = BUFSIZ - ctx->zout.avail_out;
383 if (writen(ctx->write_fd, buff, len) != len)
384 debug(DEBUG_APPERROR, "cvs_direct: zout: error writing final state");
386 //hexdump(buff, len, "cvs_direct: zout: sending unsent data");
388 } while (ret == Z_OK);
390 if ((ret = deflateEnd(&ctx->zout)) != Z_OK)
391 debug(DEBUG_APPERROR, "cvs_direct: zout: deflateEnd error: %s: %s",
392 (ret == Z_STREAM_ERROR) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx->zout.msg);
395 /* we're done writing now */
396 debug(DEBUG_TCP, "cvs_direct: closing cvs server write connection %d", ctx->write_fd);
397 close(ctx->write_fd);
400 * if this is pserver, then read_fd is a bi-directional socket.
401 * we want to shutdown the write side, just to make sure the
402 * server get's eof
404 if (ctx->is_pserver)
406 debug(DEBUG_TCP, "cvs_direct: shutdown on read socket");
407 if (shutdown(ctx->read_fd, SHUT_WR) < 0)
408 debug(DEBUG_SYSERROR, "cvs_direct: error with shutdown on pserver socket");
411 if (ctx->compressed)
413 int ret = Z_OK, len, eof = 0;
414 char buff[BUFSIZ];
416 /* read to the 'eof'/'eos' marker. there are two states we
417 * track, looking for Z_STREAM_END (application level EOS)
418 * and EOF on socket. Both should happen at the same time,
419 * but we need to do the read first, the first time through
420 * the loop, but we want to do one read after getting Z_STREAM_END
421 * too. so this loop has really ugly exit conditions.
423 for(;;)
426 * if there's nothing in the avail_in, and we
427 * inflated everything last pass (avail_out != 0)
428 * then slurp some more from the descriptor,
429 * if we get EOF, exit the loop
431 if (ctx->zin.avail_in == 0 && ctx->zin.avail_out != 0)
433 debug(DEBUG_TCP, "cvs_direct: doing final slurp");
434 len = read(ctx->read_fd, ctx->zread_buff, RD_BUFF_SIZE);
435 debug(DEBUG_TCP, "cvs_direct: did final slurp: %d", len);
437 if (len <= 0)
439 eof = 1;
440 break;
443 /* put the data into the inflate input stream */
444 ctx->zin.next_in = ctx->zread_buff;
445 ctx->zin.avail_in = len;
449 * if the last time through we got Z_STREAM_END, and we
450 * get back here, it means we should've gotten EOF but
451 * didn't
453 if (ret == Z_STREAM_END)
454 break;
456 ctx->zin.next_out = buff;
457 ctx->zin.avail_out = BUFSIZ;
459 ret = inflate(&ctx->zin, Z_SYNC_FLUSH);
460 len = BUFSIZ - ctx->zin.avail_out;
462 if (ret == Z_BUF_ERROR)
463 debug(DEBUG_APPERROR, "Z_BUF_ERROR");
465 if (ret == Z_OK && len == 0)
466 debug(DEBUG_TCP, "cvs_direct: no data out of inflate");
468 if (ret == Z_STREAM_END)
469 debug(DEBUG_TCP, "cvs_direct: got Z_STREAM_END");
471 if ((ret == Z_OK || ret == Z_STREAM_END) && len > 0)
472 hexdump(buff, BUFSIZ - ctx->zin.avail_out, "cvs_direct: zin: unread data at close");
475 if (ret != Z_STREAM_END)
476 debug(DEBUG_APPERROR, "cvs_direct: zin: Z_STREAM_END not encountered (premature EOF?)");
478 if (eof == 0)
479 debug(DEBUG_APPERROR, "cvs_direct: zin: EOF not encountered (premature Z_STREAM_END?)");
481 if ((ret = inflateEnd(&ctx->zin)) != Z_OK)
482 debug(DEBUG_APPERROR, "cvs_direct: zin: inflateEnd error: %s: %s",
483 (ret == Z_STREAM_ERROR) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx->zin.msg ? ctx->zin.msg : "");
486 debug(DEBUG_TCP, "cvs_direct: closing cvs server read connection %d", ctx->read_fd);
487 close(ctx->read_fd);
489 free(ctx);
492 static void get_cvspass(char * pass, const char * root)
494 char cvspass[PATH_MAX];
495 const char * home;
496 FILE * fp;
498 pass[0] = 0;
500 if (!(home = getenv("HOME")))
502 debug(DEBUG_APPERROR, "HOME environment variable not set");
503 exit(1);
506 if (snprintf(cvspass, PATH_MAX, "%s/.cvspass", home) >= PATH_MAX)
508 debug(DEBUG_APPERROR, "prefix buffer overflow");
509 exit(1);
512 if ((fp = fopen(cvspass, "r")))
514 char buff[BUFSIZ];
515 int len = strlen(root);
517 while (fgets(buff, BUFSIZ, fp))
519 /* FIXME: what does /1 mean? */
520 if (strncmp(buff, "/1 ", 3) != 0)
521 continue;
523 if (strncmp(buff + 3, root, len) == 0)
525 strcpy(pass, buff + 3 + len + 1);
526 chop(pass);
527 break;
531 fclose(fp);
534 if (!pass[0])
535 pass[0] = 'A';
538 static void send_string(CvsServerCtx * ctx, const char * str, ...)
540 int len;
541 char buff[BUFSIZ];
542 va_list ap;
544 va_start(ap, str);
546 len = vsnprintf(buff, BUFSIZ, str, ap);
547 if (len >= BUFSIZ)
549 debug(DEBUG_APPERROR, "cvs_direct: command send string overflow");
550 exit(1);
553 if (ctx->compressed)
555 char zbuff[BUFSIZ];
557 if (ctx->zout.avail_in != 0)
559 debug(DEBUG_APPERROR, "cvs_direct: zout: last output command not flushed");
560 exit(1);
563 ctx->zout.next_in = buff;
564 ctx->zout.avail_in = len;
565 ctx->zout.avail_out = 0;
567 while (ctx->zout.avail_in > 0 || ctx->zout.avail_out == 0)
569 int ret;
571 ctx->zout.next_out = zbuff;
572 ctx->zout.avail_out = BUFSIZ;
574 /* FIXME: for the arguments before a command, flushing is counterproductive */
575 ret = deflate(&ctx->zout, Z_SYNC_FLUSH);
577 if (ret == Z_OK)
579 len = BUFSIZ - ctx->zout.avail_out;
581 if (writen(ctx->write_fd, zbuff, len) != len)
583 debug(DEBUG_SYSERROR, "cvs_direct: zout: can't write");
584 exit(1);
587 else
589 debug(DEBUG_APPERROR, "cvs_direct: zout: error %d %s", ret, ctx->zout.msg);
593 else
595 if (writen(ctx->write_fd, buff, len) != len)
597 debug(DEBUG_SYSERROR, "cvs_direct: can't send command");
598 exit(1);
602 debug(DEBUG_TCP, "string: '%s' sent", buff);
605 static int refill_buffer(CvsServerCtx * ctx)
607 int len;
609 if (ctx->head != ctx->tail)
611 debug(DEBUG_APPERROR, "cvs_direct: refill_buffer called on non-empty buffer");
612 exit(1);
615 ctx->head = ctx->read_buff;
616 len = RD_BUFF_SIZE;
618 if (ctx->compressed)
620 int zlen, ret;
622 /* if there was leftover buffer room, it's time to slurp more data */
625 if (ctx->zin.avail_out > 0)
627 if (ctx->zin.avail_in != 0)
629 debug(DEBUG_APPERROR, "cvs_direct: zin: expect 0 avail_in");
630 exit(1);
632 zlen = read(ctx->read_fd, ctx->zread_buff, RD_BUFF_SIZE);
633 ctx->zin.next_in = ctx->zread_buff;
634 ctx->zin.avail_in = zlen;
637 ctx->zin.next_out = ctx->head;
638 ctx->zin.avail_out = len;
640 /* FIXME: we don't always need Z_SYNC_FLUSH, do we? */
641 ret = inflate(&ctx->zin, Z_SYNC_FLUSH);
643 while (ctx->zin.avail_out == len);
645 if (ret == Z_OK)
647 ctx->tail = ctx->head + (len - ctx->zin.avail_out);
649 else
651 debug(DEBUG_APPERROR, "cvs_direct: zin: error %d %s", ret, ctx->zin.msg);
652 exit(1);
655 else
657 len = read(ctx->read_fd, ctx->head, len);
658 ctx->tail = (len <= 0) ? ctx->head : ctx->head + len;
661 return len;
664 static int read_line(CvsServerCtx * ctx, char * p)
666 int len = 0;
667 while (1)
669 if (ctx->head == ctx->tail)
670 if (refill_buffer(ctx) <= 0)
671 return -1;
673 *p = *ctx->head++;
675 if (*p == '\n')
677 *p = 0;
678 break;
680 p++;
681 len++;
684 return len;
687 static int read_response(CvsServerCtx * ctx, const char * str)
689 /* FIXME: more than 1 char at a time */
690 char resp[BUFSIZ];
692 if (read_line(ctx, resp) < 0)
693 return 0;
695 debug(DEBUG_TCP, "response '%s' read", resp);
697 return (strcmp(resp, str) == 0);
700 static void ctx_to_fp(CvsServerCtx * ctx, FILE * fp)
702 char line[BUFSIZ];
704 while (1)
706 read_line(ctx, line);
707 debug(DEBUG_TCP, "ctx_to_fp: %s", line);
708 if (memcmp(line, "M ", 2) == 0)
710 if (fp)
711 fprintf(fp, "%s\n", line + 2);
713 else if (memcmp(line, "E ", 2) == 0)
715 debug(DEBUG_APPMSG1, "%s", line + 2);
717 else if (strncmp(line, "ok", 2) == 0 || strncmp(line, "error", 5) == 0)
719 break;
723 if (fp)
724 fflush(fp);
727 void cvs_rdiff(CvsServerCtx * ctx,
728 const char * rep, const char * file,
729 const char * rev1, const char * rev2)
731 /* NOTE: opts are ignored for rdiff, '-u' is always used */
733 send_string(ctx, "Argument -u\n");
734 send_string(ctx, "Argument -r\n");
735 send_string(ctx, "Argument %s\n", rev1);
736 send_string(ctx, "Argument -r\n");
737 send_string(ctx, "Argument %s\n", rev2);
738 send_string(ctx, "Argument %s%s\n", rep, file);
739 send_string(ctx, "rdiff\n");
741 ctx_to_fp(ctx, stdout);
744 void cvs_rupdate(CvsServerCtx * ctx, const char * rep, const char * file, const char * rev, int create, const char * opts)
746 FILE * fp;
747 char cmdbuff[BUFSIZ];
749 snprintf(cmdbuff, BUFSIZ, "diff %s %s /dev/null %s | sed -e '%s s|^\\([+-][+-][+-]\\) -|\\1 %s/%s|g'",
750 opts, create?"":"-", create?"-":"", create?"2":"1", rep, file);
752 debug(DEBUG_TCP, "cmdbuff: %s", cmdbuff);
754 if (!(fp = popen(cmdbuff, "w")))
756 debug(DEBUG_APPERROR, "cvs_direct: popen for diff failed: %s", cmdbuff);
757 exit(1);
760 send_string(ctx, "Argument -p\n");
761 send_string(ctx, "Argument -r\n");
762 send_string(ctx, "Argument %s\n", rev);
763 send_string(ctx, "Argument %s/%s\n", rep, file);
764 send_string(ctx, "co\n");
766 ctx_to_fp(ctx, fp);
768 pclose(fp);
771 static int parse_patch_arg(char * arg, char ** str)
773 char *tok, *tok2 = "";
774 tok = strsep(str, " ");
775 if (!tok)
776 return 0;
778 if (!*tok == '-')
780 debug(DEBUG_APPERROR, "diff_opts parse error: no '-' starting argument: %s", *str);
781 return 0;
784 /* if it's not 'long format' argument, we can process it efficiently */
785 if (tok[1] == '-')
787 debug(DEBUG_APPERROR, "diff_opts parse_error: long format args not supported");
788 return 0;
791 /* see if command wants two args and they're separated by ' ' */
792 if (tok[2] == 0 && strchr("BdDFgiorVxYz", tok[1]))
794 tok2 = strsep(str, " ");
795 if (!tok2)
797 debug(DEBUG_APPERROR, "diff_opts parse_error: argument %s requires two arguments", tok);
798 return 0;
802 snprintf(arg, 32, "%s%s", tok, tok2);
803 return 1;
806 void cvs_diff(CvsServerCtx * ctx,
807 const char * rep, const char * file,
808 const char * rev1, const char * rev2, const char * opts)
810 char argstr[BUFSIZ], *p = argstr;
811 char arg[32];
812 char file_buff[PATH_MAX], *basename;
814 strzncpy(argstr, opts, BUFSIZ);
815 while (parse_patch_arg(arg, &p))
816 send_string(ctx, "Argument %s\n", arg);
818 send_string(ctx, "Argument -r\n");
819 send_string(ctx, "Argument %s\n", rev1);
820 send_string(ctx, "Argument -r\n");
821 send_string(ctx, "Argument %s\n", rev2);
824 * we need to separate the 'basename' of file in order to
825 * generate the Directory directive(s)
827 strzncpy(file_buff, file, PATH_MAX);
828 if ((basename = strrchr(file_buff, '/')))
830 *basename = 0;
831 send_string(ctx, "Directory %s/%s\n", rep, file_buff);
832 send_string(ctx, "%s/%s/%s\n", ctx->root, rep, file_buff);
834 else
836 send_string(ctx, "Directory %s\n", rep, file_buff);
837 send_string(ctx, "%s/%s\n", ctx->root, rep);
840 send_string(ctx, "Directory .\n");
841 send_string(ctx, "%s\n", ctx->root);
842 send_string(ctx, "Argument %s/%s\n", rep, file);
843 send_string(ctx, "diff\n");
845 ctx_to_fp(ctx, stdout);
849 * FIXME: the design of this sucks. It was originally designed to fork a subprocess
850 * which read the cvs response and send it back through a pipe the main process,
851 * which fdopen(3)ed the other end, and juts used regular fgets. This however
852 * didn't work because the reads of compressed data in the child process altered
853 * the compression state, and there was no way to resynchronize that state with
854 * the parent process. We could use threads...
856 FILE * cvs_rlog_open(CvsServerCtx * ctx, const char * rep, const char * date_str)
858 /* note: use of the date_str is handled in a non-standard, cvsps specific way */
859 if (date_str && date_str[0])
861 send_string(ctx, "Argument -d\n", rep);
862 send_string(ctx, "Argument %s<1 Jan 2038 05:00:00 -0000\n", date_str);
863 send_string(ctx, "Argument -d\n", rep);
864 send_string(ctx, "Argument %s\n", date_str);
867 send_string(ctx, "Argument %s\n", rep);
868 send_string(ctx, "rlog\n");
871 * FIXME: is it possible to create a 'fake' FILE * whose 'refill'
872 * function is below?
874 return (FILE*)ctx;
877 char * cvs_rlog_fgets(char * buff, int buflen, CvsServerCtx * ctx)
879 char lbuff[BUFSIZ];
880 int len;
882 len = read_line(ctx, lbuff);
883 debug(DEBUG_TCP, "cvs_direct: rlog: read %s", lbuff);
885 if (memcmp(lbuff, "M ", 2) == 0)
887 memcpy(buff, lbuff + 2, len - 2);
888 buff[len - 2 ] = '\n';
889 buff[len - 1 ] = 0;
891 else if (memcmp(lbuff, "E ", 2) == 0)
893 debug(DEBUG_APPMSG1, "%s", lbuff + 2);
895 else if (strcmp(lbuff, "ok") == 0 ||strcmp(lbuff, "error") == 0)
897 debug(DEBUG_TCP, "cvs_direct: rlog: got command completion");
898 return NULL;
901 return buff;
904 void cvs_rlog_close(CvsServerCtx * ctx)
908 void cvs_version(CvsServerCtx * ctx, char * client_version, char * server_version)
910 char lbuff[BUFSIZ];
911 strcpy(client_version, "Client: Concurrent Versions System (CVS) 99.99.99 (client/server) cvs-direct");
912 send_string(ctx, "version\n");
913 read_line(ctx, lbuff);
914 if (memcmp(lbuff, "M ", 2) == 0)
915 sprintf(server_version, "Server: %s", lbuff + 2);
916 else
917 debug(DEBUG_APPERROR, "cvs_direct: didn't read version: %s", lbuff);
919 read_line(ctx, lbuff);
920 if (strcmp(lbuff, "ok") != 0)
921 debug(DEBUG_APPERROR, "cvs_direct: protocol error reading version");
923 debug(DEBUG_TCP, "cvs_direct: client version %s", client_version);
924 debug(DEBUG_TCP, "cvs_direct: server version %s", server_version);