2 * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc.
3 * See COPYING file for license information
8 char * strsep(char **str
, const char *delim
);
17 #define SHUT_WR SD_SEND
18 #define DEV_NULL "nul"
20 #include <sys/socket.h>
21 #define DEV_NULL "/dev/null"
23 #include <cbtcommon/debug.h>
24 #include <cbtcommon/text_util.h>
25 #include <cbtcommon/tcpsocket.h>
26 #include <cbtcommon/sio.h>
28 #include "cvs_direct.h"
31 #define RD_BUFF_SIZE 4096
41 /* buffered reads from descriptor */
42 char read_buff
[RD_BUFF_SIZE
];
50 /* when reading compressed data, the compressed data buffer */
51 char zread_buff
[RD_BUFF_SIZE
];
54 static void get_cvspass(char *, const char *);
55 static void send_string(CvsServerCtx
*, const char *, ...);
56 static int read_response(CvsServerCtx
*, const char *);
57 static void ctx_to_fp(CvsServerCtx
* ctx
, FILE * fp
);
58 static int read_line(CvsServerCtx
* ctx
, char * p
);
60 static CvsServerCtx
* open_ctx_pserver(CvsServerCtx
*, const char *);
61 static CvsServerCtx
* open_ctx_forked(CvsServerCtx
*, const char *);
63 CvsServerCtx
* open_cvs_server(char * p_root
, int compress
)
65 CvsServerCtx
* ctx
= (CvsServerCtx
*)malloc(sizeof(*ctx
));
67 char * p
= root
, *tok
;
72 ctx
->head
= ctx
->tail
= ctx
->read_buff
;
73 ctx
->read_fd
= ctx
->write_fd
= -1;
79 memset(&ctx
->zout
, 0, sizeof(z_stream
));
80 memset(&ctx
->zin
, 0, sizeof(z_stream
));
83 * to 'prime' the reads, make it look like there was output
84 * room available (i.e. we have processed all pending compressed
87 ctx
->zin
.avail_out
= 1;
89 if (deflateInit(&ctx
->zout
, compress
) != Z_OK
)
95 if (inflateInit(&ctx
->zin
) != Z_OK
)
97 deflateEnd(&ctx
->zout
);
103 strcpy(root
, p_root
);
105 tok
= strsep(&p
, ":");
107 /* if root string looks like :pserver:... then the first token will be empty */
108 if (strlen(tok
) == 0)
110 char * method
= strsep(&p
, ":");
111 if (strcmp(method
, "pserver") == 0)
113 ctx
= open_ctx_pserver(ctx
, p
);
115 else if (strstr("local:ext:fork:server", method
))
117 /* handle all of these via fork, even local */
118 ctx
= open_ctx_forked(ctx
, p
);
122 debug(DEBUG_APPERROR
, "cvs_direct: unsupported cvs access method: %s", method
);
129 ctx
= open_ctx_forked(ctx
, p_root
);
136 send_string(ctx
, "Root %s\n", ctx
->root
);
138 /* this is taken from 1.11.1p1 trace - but with Mbinary removed. we can't handle it (yet!) */
139 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
);
141 send_string(ctx
, "valid-requests\n");
143 /* check for the commands we will issue */
144 read_line(ctx
, buff
);
145 if (strncmp(buff
, "Valid-requests", 14) != 0)
147 debug(DEBUG_APPERROR
, "cvs_direct: bad response to valid-requests command");
148 close_cvs_server(ctx
);
152 if (!strstr(buff
, " version") ||
153 !strstr(buff
, " rlog") ||
154 !strstr(buff
, " rdiff") ||
155 !strstr(buff
, " diff") ||
156 !strstr(buff
, " co"))
158 debug(DEBUG_APPERROR
, "cvs_direct: cvs server too old for cvs_direct");
159 close_cvs_server(ctx
);
163 read_line(ctx
, buff
);
164 if (strcmp(buff
, "ok") != 0)
166 debug(DEBUG_APPERROR
, "cvs_direct: bad ok trailer to valid-requests command");
167 close_cvs_server(ctx
);
171 /* this is myterious but 'mandatory' */
172 send_string(ctx
, "UseUnchanged\n");
176 send_string(ctx
, "Gzip-stream %d\n", compress
);
180 debug(DEBUG_APPMSG1
, "cvs_direct initialized to CVSROOT %s", ctx
->root
);
186 static CvsServerCtx
* open_ctx_pserver(CvsServerCtx
* ctx
, const char * p_root
)
189 char full_root
[PATH_MAX
];
190 char * p
= root
, *tok
, *tok2
;
196 strcpy(root
, p_root
);
198 tok
= strsep(&p
, ":");
199 if (strlen(tok
) == 0 || !p
)
201 debug(DEBUG_APPERROR
, "parse error on third token");
205 tok2
= strsep(&tok
, "@");
206 if (!strlen(tok2
) || (!tok
|| !strlen(tok
)))
208 debug(DEBUG_APPERROR
, "parse error on user@server in pserver");
217 tok
= strchr(p
, '/');
220 debug(DEBUG_APPERROR
, "parse error: expecting / in root");
224 memset(port
, 0, sizeof(port
));
225 memcpy(port
, p
, tok
- p
);
231 strcpy(port
, "2401");
235 /* the line from registry does not contain port, so rebuild */
236 snprintf(full_root
, PATH_MAX
, ":pserver:%s@%s:%s", user
, server
, p
);
238 /* the line from .cvspass is fully qualified, so rebuild */
239 snprintf(full_root
, PATH_MAX
, ":pserver:%s@%s:%s%s", user
, server
, port
, p
);
241 get_cvspass(pass
, full_root
);
243 debug(DEBUG_TCP
, "user:%s server:%s port:%s pass:%s full_root:%s", user
, server
, port
, pass
, full_root
);
245 if ((ctx
->read_fd
= tcp_create_socket(REUSE_ADDR
)) < 0)
248 ctx
->write_fd
= dup(ctx
->read_fd
);
250 if (tcp_connect(ctx
->read_fd
, server
, atoi(port
)) < 0)
253 send_string(ctx
, "BEGIN AUTH REQUEST\n");
254 send_string(ctx
, "%s\n", p
);
255 send_string(ctx
, "%s\n", user
);
256 send_string(ctx
, "%s\n", pass
);
257 send_string(ctx
, "END AUTH REQUEST\n");
259 if (!read_response(ctx
, "I LOVE YOU"))
262 strcpy(ctx
->root
, p
);
274 static CvsServerCtx
* open_ctx_forked(CvsServerCtx
* ctx
, const char * p_root
)
277 debug(DEBUG_SYSERROR
, "cvs_direct: fork not supported on MinGW");
280 char * p
= root
, *tok
, *tok2
, *rep
;
281 char execcmd
[PATH_MAX
];
285 const char * cvs_server
= getenv("CVS_SERVER");
290 strcpy(root
, p_root
);
292 /* if there's a ':', it's remote */
293 tok
= strsep(&p
, ":");
297 const char * cvs_rsh
= getenv("CVS_RSH");
302 tok2
= strsep(&tok
, "@");
305 snprintf(execcmd
, PATH_MAX
, "%s -l %s %s %s server", cvs_rsh
, tok2
, tok
, cvs_server
);
307 snprintf(execcmd
, PATH_MAX
, "%s %s %s server", cvs_rsh
, tok2
, cvs_server
);
313 snprintf(execcmd
, PATH_MAX
, "%s server", cvs_server
);
317 if (pipe(to_cvs
) < 0)
319 debug(DEBUG_SYSERROR
, "cvs_direct: failed to create pipe to_cvs");
323 if (pipe(from_cvs
) < 0)
325 debug(DEBUG_SYSERROR
, "cvs_direct: failed to create pipe from_cvs");
329 debug(DEBUG_TCP
, "forked cmdline: %s", execcmd
);
331 if ((pid
= fork()) < 0)
333 debug(DEBUG_SYSERROR
, "cvs_direct: can't fork");
336 else if (pid
== 0) /* child */
352 execv("/bin/sh",argp
);
354 debug(DEBUG_APPERROR
, "cvs_direct: fatal: shouldn't be reached");
360 ctx
->read_fd
= from_cvs
[0];
361 ctx
->write_fd
= to_cvs
[1];
363 strcpy(ctx
->root
, rep
);
379 void close_cvs_server(CvsServerCtx
* ctx
)
381 /* FIXME: some sort of flushing should be done for non-compressed case */
389 * there shouldn't be anything left, but we do want
390 * to send an 'end of stream' marker, (if such a thing
395 ctx
->zout
.next_out
= buff
;
396 ctx
->zout
.avail_out
= BUFSIZ
;
397 ret
= deflate(&ctx
->zout
, Z_FINISH
);
399 if ((ret
== Z_OK
|| ret
== Z_STREAM_END
) && ctx
->zout
.avail_out
!= BUFSIZ
)
401 len
= BUFSIZ
- ctx
->zout
.avail_out
;
402 if (writen(ctx
->write_fd
, buff
, len
) != len
)
403 debug(DEBUG_APPERROR
, "cvs_direct: zout: error writing final state");
405 //hexdump(buff, len, "cvs_direct: zout: sending unsent data");
407 } while (ret
== Z_OK
);
409 if ((ret
= deflateEnd(&ctx
->zout
)) != Z_OK
)
410 debug(DEBUG_APPERROR
, "cvs_direct: zout: deflateEnd error: %s: %s",
411 (ret
== Z_STREAM_ERROR
) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx
->zout
.msg
);
414 /* we're done writing now */
415 debug(DEBUG_TCP
, "cvs_direct: closing cvs server write connection %d", ctx
->write_fd
);
416 close(ctx
->write_fd
);
419 * if this is pserver, then read_fd is a bi-directional socket.
420 * we want to shutdown the write side, just to make sure the
425 debug(DEBUG_TCP
, "cvs_direct: shutdown on read socket");
426 if (shutdown(ctx
->read_fd
, SHUT_WR
) < 0)
427 debug(DEBUG_SYSERROR
, "cvs_direct: error with shutdown on pserver socket");
432 int ret
= Z_OK
, len
, eof
= 0;
435 /* read to the 'eof'/'eos' marker. there are two states we
436 * track, looking for Z_STREAM_END (application level EOS)
437 * and EOF on socket. Both should happen at the same time,
438 * but we need to do the read first, the first time through
439 * the loop, but we want to do one read after getting Z_STREAM_END
440 * too. so this loop has really ugly exit conditions.
445 * if there's nothing in the avail_in, and we
446 * inflated everything last pass (avail_out != 0)
447 * then slurp some more from the descriptor,
448 * if we get EOF, exit the loop
450 if (ctx
->zin
.avail_in
== 0 && ctx
->zin
.avail_out
!= 0)
452 debug(DEBUG_TCP
, "cvs_direct: doing final slurp");
453 len
= read(ctx
->read_fd
, ctx
->zread_buff
, RD_BUFF_SIZE
);
454 debug(DEBUG_TCP
, "cvs_direct: did final slurp: %d", len
);
462 /* put the data into the inflate input stream */
463 ctx
->zin
.next_in
= ctx
->zread_buff
;
464 ctx
->zin
.avail_in
= len
;
468 * if the last time through we got Z_STREAM_END, and we
469 * get back here, it means we should've gotten EOF but
472 if (ret
== Z_STREAM_END
)
475 ctx
->zin
.next_out
= buff
;
476 ctx
->zin
.avail_out
= BUFSIZ
;
478 ret
= inflate(&ctx
->zin
, Z_SYNC_FLUSH
);
479 len
= BUFSIZ
- ctx
->zin
.avail_out
;
481 if (ret
== Z_BUF_ERROR
)
482 debug(DEBUG_APPERROR
, "Z_BUF_ERROR");
484 if (ret
== Z_OK
&& len
== 0)
485 debug(DEBUG_TCP
, "cvs_direct: no data out of inflate");
487 if (ret
== Z_STREAM_END
)
488 debug(DEBUG_TCP
, "cvs_direct: got Z_STREAM_END");
490 if ((ret
== Z_OK
|| ret
== Z_STREAM_END
) && len
> 0)
491 hexdump(buff
, BUFSIZ
- ctx
->zin
.avail_out
, "cvs_direct: zin: unread data at close");
494 if (ret
!= Z_STREAM_END
)
495 debug(DEBUG_APPERROR
, "cvs_direct: zin: Z_STREAM_END not encountered (premature EOF?)");
498 debug(DEBUG_APPERROR
, "cvs_direct: zin: EOF not encountered (premature Z_STREAM_END?)");
500 if ((ret
= inflateEnd(&ctx
->zin
)) != Z_OK
)
501 debug(DEBUG_APPERROR
, "cvs_direct: zin: inflateEnd error: %s: %s",
502 (ret
== Z_STREAM_ERROR
) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx
->zin
.msg
? ctx
->zin
.msg
: "");
505 debug(DEBUG_TCP
, "cvs_direct: closing cvs server read connection %d", ctx
->read_fd
);
511 static void get_cvspass(char * pass
, const char * root
)
514 /* CVSNT stores password in registry HKCU\Software\Cvsnt\cvspass
515 * See http://issues.apache.org/bugzilla/show_bug.cgi?id=21657#c5
516 * See http://www.adp-gmbh.ch/misc/tools/cvs/cvspass.html
519 if (RegOpenKeyExA(HKEY_CURRENT_USER
,"Software\\Cvsnt\\cvspass",0,KEY_READ
,&hKey
))
521 debug(DEBUG_APPERROR
, "Cannot open HKCU\\Software\\Cvsnt\\cvspass registry key");
524 /* "root" is connection string that require a password */
525 static char buf
[4096];
529 ret
= RegQueryValueExA(hKey
,root
,NULL
,&dwType
,(LPBYTE
)buf
,&dwLen
);
531 if (ret
!= ERROR_SUCCESS
)
533 debug(DEBUG_APPERROR
, "HKCU\\Software\\Cvsnt\\cvspass registry key opened, but could not read value '%s'", root
);
534 debug(DEBUG_APPERROR
, "HKCU\\Software\\Cvsnt\\cvspass registry key not readable");
537 strcpy(pass
,buf
); /* If this function knew the size of pass, we could
538 use strncpy and avoid potential buffer-overflow. */
540 char cvspass
[PATH_MAX
];
546 if (!(home
= getenv("HOME")))
548 debug(DEBUG_APPERROR
, "HOME environment variable not set");
552 if (snprintf(cvspass
, PATH_MAX
, "%s/.cvspass", home
) >= PATH_MAX
)
554 debug(DEBUG_APPERROR
, "prefix buffer overflow");
558 if ((fp
= fopen(cvspass
, "r")))
561 int len
= strlen(root
);
563 while (fgets(buff
, BUFSIZ
, fp
))
565 /* FIXME: what does /1 mean? */
566 if (strncmp(buff
, "/1 ", 3) != 0)
569 if (strncmp(buff
+ 3, root
, len
) == 0)
571 strcpy(pass
, buff
+ 3 + len
+ 1);
585 static void send_string(CvsServerCtx
* ctx
, const char * str
, ...)
593 len
= vsnprintf(buff
, BUFSIZ
, str
, ap
);
596 debug(DEBUG_APPERROR
, "cvs_direct: command send string overflow");
604 if (ctx
->zout
.avail_in
!= 0)
606 debug(DEBUG_APPERROR
, "cvs_direct: zout: last output command not flushed");
610 ctx
->zout
.next_in
= buff
;
611 ctx
->zout
.avail_in
= len
;
612 ctx
->zout
.avail_out
= 0;
614 while (ctx
->zout
.avail_in
> 0 || ctx
->zout
.avail_out
== 0)
618 ctx
->zout
.next_out
= zbuff
;
619 ctx
->zout
.avail_out
= BUFSIZ
;
621 /* FIXME: for the arguments before a command, flushing is counterproductive */
622 ret
= deflate(&ctx
->zout
, Z_SYNC_FLUSH
);
626 len
= BUFSIZ
- ctx
->zout
.avail_out
;
628 if (writen(ctx
->write_fd
, zbuff
, len
) != len
)
630 debug(DEBUG_SYSERROR
, "cvs_direct: zout: can't write");
636 debug(DEBUG_APPERROR
, "cvs_direct: zout: error %d %s", ret
, ctx
->zout
.msg
);
642 if (writen(ctx
->write_fd
, buff
, len
) != len
)
644 debug(DEBUG_SYSERROR
, "cvs_direct: can't send command");
649 debug(DEBUG_TCP
, "string: '%s' sent", buff
);
652 static int refill_buffer(CvsServerCtx
* ctx
)
656 if (ctx
->head
!= ctx
->tail
)
658 debug(DEBUG_APPERROR
, "cvs_direct: refill_buffer called on non-empty buffer");
662 ctx
->head
= ctx
->read_buff
;
669 /* if there was leftover buffer room, it's time to slurp more data */
672 if (ctx
->zin
.avail_out
> 0)
674 if (ctx
->zin
.avail_in
!= 0)
676 debug(DEBUG_APPERROR
, "cvs_direct: zin: expect 0 avail_in");
679 zlen
= read(ctx
->read_fd
, ctx
->zread_buff
, RD_BUFF_SIZE
);
680 ctx
->zin
.next_in
= ctx
->zread_buff
;
681 ctx
->zin
.avail_in
= zlen
;
684 ctx
->zin
.next_out
= ctx
->head
;
685 ctx
->zin
.avail_out
= len
;
687 /* FIXME: we don't always need Z_SYNC_FLUSH, do we? */
688 ret
= inflate(&ctx
->zin
, Z_SYNC_FLUSH
);
690 while (ctx
->zin
.avail_out
== len
);
694 ctx
->tail
= ctx
->head
+ (len
- ctx
->zin
.avail_out
);
698 debug(DEBUG_APPERROR
, "cvs_direct: zin: error %d %s", ret
, ctx
->zin
.msg
);
704 len
= read(ctx
->read_fd
, ctx
->head
, len
);
705 ctx
->tail
= (len
<= 0) ? ctx
->head
: ctx
->head
+ len
;
711 static int read_line(CvsServerCtx
* ctx
, char * p
)
716 if (ctx
->head
== ctx
->tail
)
717 if (refill_buffer(ctx
) <= 0)
734 static int read_response(CvsServerCtx
* ctx
, const char * str
)
736 /* FIXME: more than 1 char at a time */
739 if (read_line(ctx
, resp
) < 0)
742 debug(DEBUG_TCP
, "response '%s' read", resp
);
744 return (strcmp(resp
, str
) == 0);
747 static void ctx_to_fp(CvsServerCtx
* ctx
, FILE * fp
)
753 read_line(ctx
, line
);
754 debug(DEBUG_TCP
, "ctx_to_fp: %s", line
);
755 if (memcmp(line
, "M ", 2) == 0)
758 fprintf(fp
, "%s\n", line
+ 2);
760 else if (memcmp(line
, "E ", 2) == 0)
762 debug(DEBUG_APPMSG1
, "%s", line
+ 2);
764 else if (strncmp(line
, "ok", 2) == 0 || strncmp(line
, "error", 5) == 0)
774 void cvs_rdiff(CvsServerCtx
* ctx
,
775 const char * rep
, const char * file
,
776 const char * rev1
, const char * rev2
)
778 /* NOTE: opts are ignored for rdiff, '-u' is always used */
780 send_string(ctx
, "Argument -u\n");
781 send_string(ctx
, "Argument -r\n");
782 send_string(ctx
, "Argument %s\n", rev1
);
783 send_string(ctx
, "Argument -r\n");
784 send_string(ctx
, "Argument %s\n", rev2
);
785 send_string(ctx
, "Argument %s%s\n", rep
, file
);
786 send_string(ctx
, "rdiff\n");
788 ctx_to_fp(ctx
, stdout
);
791 void cvs_rupdate(CvsServerCtx
* ctx
, const char * rep
, const char * file
, const char * rev
, int create
, const char * opts
)
794 char cmdbuff
[BUFSIZ
];
796 snprintf(cmdbuff
, BUFSIZ
, "diff %s %s %s | sed -e '%s s|^\\([+-][+-][+-]\\) -|\\1 %s/%s|g'",
797 opts
, create
?DEV_NULL
:"-", create
?"-":DEV_NULL
, create
?"2":"1", rep
, file
);
799 debug(DEBUG_TCP
, "cmdbuff: %s", cmdbuff
);
801 if (!(fp
= popen(cmdbuff
, "w")))
803 debug(DEBUG_APPERROR
, "cvs_direct: popen for diff failed: %s", cmdbuff
);
807 send_string(ctx
, "Argument -p\n");
808 send_string(ctx
, "Argument -r\n");
809 send_string(ctx
, "Argument %s\n", rev
);
810 send_string(ctx
, "Argument %s/%s\n", rep
, file
);
811 send_string(ctx
, "co\n");
818 static int parse_patch_arg(char * arg
, char ** str
)
820 char *tok
, *tok2
= "";
821 tok
= strsep(str
, " ");
827 debug(DEBUG_APPERROR
, "diff_opts parse error: no '-' starting argument: %s", *str
);
831 /* if it's not 'long format' argument, we can process it efficiently */
834 debug(DEBUG_APPERROR
, "diff_opts parse_error: long format args not supported");
838 /* see if command wants two args and they're separated by ' ' */
839 if (tok
[2] == 0 && strchr("BdDFgiorVxYz", tok
[1]))
841 tok2
= strsep(str
, " ");
844 debug(DEBUG_APPERROR
, "diff_opts parse_error: argument %s requires two arguments", tok
);
849 snprintf(arg
, 32, "%s%s", tok
, tok2
);
853 void cvs_diff(CvsServerCtx
* ctx
,
854 const char * rep
, const char * file
,
855 const char * rev1
, const char * rev2
, const char * opts
)
857 char argstr
[BUFSIZ
], *p
= argstr
;
859 char file_buff
[PATH_MAX
], *basename
;
861 strzncpy(argstr
, opts
, BUFSIZ
);
862 while (parse_patch_arg(arg
, &p
))
863 send_string(ctx
, "Argument %s\n", arg
);
865 send_string(ctx
, "Argument -r\n");
866 send_string(ctx
, "Argument %s\n", rev1
);
867 send_string(ctx
, "Argument -r\n");
868 send_string(ctx
, "Argument %s\n", rev2
);
871 * we need to separate the 'basename' of file in order to
872 * generate the Directory directive(s)
874 strzncpy(file_buff
, file
, PATH_MAX
);
875 if ((basename
= strrchr(file_buff
, '/')))
878 send_string(ctx
, "Directory %s/%s\n", rep
, file_buff
);
879 send_string(ctx
, "%s/%s/%s\n", ctx
->root
, rep
, file_buff
);
883 send_string(ctx
, "Directory %s\n", rep
, file_buff
);
884 send_string(ctx
, "%s/%s\n", ctx
->root
, rep
);
887 send_string(ctx
, "Directory .\n");
888 send_string(ctx
, "%s\n", ctx
->root
);
889 send_string(ctx
, "Argument %s/%s\n", rep
, file
);
890 send_string(ctx
, "diff\n");
892 ctx_to_fp(ctx
, stdout
);
896 * FIXME: the design of this sucks. It was originally designed to fork a subprocess
897 * which read the cvs response and send it back through a pipe the main process,
898 * which fdopen(3)ed the other end, and juts used regular fgets. This however
899 * didn't work because the reads of compressed data in the child process altered
900 * the compression state, and there was no way to resynchronize that state with
901 * the parent process. We could use threads...
903 FILE * cvs_rlog_open(CvsServerCtx
* ctx
, const char * rep
, const char * date_str
)
905 /* note: use of the date_str is handled in a non-standard, cvsps specific way */
906 if (date_str
&& date_str
[0])
908 send_string(ctx
, "Argument -d\n", rep
);
909 send_string(ctx
, "Argument %s<1 Jan 2038 05:00:00 -0000\n", date_str
);
910 send_string(ctx
, "Argument -d\n", rep
);
911 send_string(ctx
, "Argument %s\n", date_str
);
914 send_string(ctx
, "Argument %s\n", rep
);
915 send_string(ctx
, "rlog\n");
918 * FIXME: is it possible to create a 'fake' FILE * whose 'refill'
924 char * cvs_rlog_fgets(char * buff
, int buflen
, CvsServerCtx
* ctx
)
929 len
= read_line(ctx
, lbuff
);
930 debug(DEBUG_TCP
, "cvs_direct: rlog: read %s", lbuff
);
932 if (memcmp(lbuff
, "M ", 2) == 0)
934 memcpy(buff
, lbuff
+ 2, len
- 2);
935 buff
[len
- 2 ] = '\n';
938 else if (memcmp(lbuff
, "E ", 2) == 0)
940 debug(DEBUG_APPMSG1
, "%s", lbuff
+ 2);
942 else if (strcmp(lbuff
, "ok") == 0 ||strcmp(lbuff
, "error") == 0)
944 debug(DEBUG_TCP
, "cvs_direct: rlog: got command completion");
951 void cvs_rlog_close(CvsServerCtx
* ctx
)
955 void cvs_version(CvsServerCtx
* ctx
, char * client_version
, char * server_version
)
958 strcpy(client_version
, "Client: Concurrent Versions System (CVS) 99.99.99 (client/server) cvs-direct");
959 send_string(ctx
, "version\n");
960 read_line(ctx
, lbuff
);
961 if (memcmp(lbuff
, "M ", 2) == 0)
962 sprintf(server_version
, "Server: %s", lbuff
+ 2);
964 debug(DEBUG_APPERROR
, "cvs_direct: didn't read version: %s", lbuff
);
966 read_line(ctx
, lbuff
);
967 if (strcmp(lbuff
, "ok") != 0)
968 debug(DEBUG_APPERROR
, "cvs_direct: protocol error reading version");
970 debug(DEBUG_TCP
, "cvs_direct: client version %s", client_version
);
971 debug(DEBUG_TCP
, "cvs_direct: server version %s", server_version
);