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
19 #include <sys/socket.h>
21 #include <cbtcommon/debug.h>
22 #include <cbtcommon/text_util.h>
23 #include <cbtcommon/tcpsocket.h>
24 #include <cbtcommon/sio.h>
26 #include "cvs_direct.h"
29 #define RD_BUFF_SIZE 4096
39 /* buffered reads from descriptor */
40 char read_buff
[RD_BUFF_SIZE
];
48 /* when reading compressed data, the compressed data buffer */
49 char zread_buff
[RD_BUFF_SIZE
];
52 static void get_cvspass(char *, const char *);
53 static void send_string(CvsServerCtx
*, const char *, ...);
54 static int read_response(CvsServerCtx
*, const char *);
55 static void ctx_to_fp(CvsServerCtx
* ctx
, FILE * fp
);
56 static int read_line(CvsServerCtx
* ctx
, char * p
);
58 static CvsServerCtx
* open_ctx_pserver(CvsServerCtx
*, const char *);
59 static CvsServerCtx
* open_ctx_forked(CvsServerCtx
*, const char *);
61 CvsServerCtx
* open_cvs_server(char * p_root
, int compress
)
63 CvsServerCtx
* ctx
= (CvsServerCtx
*)malloc(sizeof(*ctx
));
65 char * p
= root
, *tok
;
70 ctx
->head
= ctx
->tail
= ctx
->read_buff
;
71 ctx
->read_fd
= ctx
->write_fd
= -1;
77 memset(&ctx
->zout
, 0, sizeof(z_stream
));
78 memset(&ctx
->zin
, 0, sizeof(z_stream
));
81 * to 'prime' the reads, make it look like there was output
82 * room available (i.e. we have processed all pending compressed
85 ctx
->zin
.avail_out
= 1;
87 if (deflateInit(&ctx
->zout
, compress
) != Z_OK
)
93 if (inflateInit(&ctx
->zin
) != Z_OK
)
95 deflateEnd(&ctx
->zout
);
101 strcpy(root
, p_root
);
103 tok
= strsep(&p
, ":");
105 /* if root string looks like :pserver:... then the first token will be empty */
106 if (strlen(tok
) == 0)
108 char * method
= strsep(&p
, ":");
109 if (strcmp(method
, "pserver") == 0)
111 ctx
= open_ctx_pserver(ctx
, p
);
113 else if (strstr("local:ext:fork:server", method
))
115 /* handle all of these via fork, even local */
116 ctx
= open_ctx_forked(ctx
, p
);
120 debug(DEBUG_APPERROR
, "cvs_direct: unsupported cvs access method: %s", method
);
127 ctx
= open_ctx_forked(ctx
, p_root
);
134 send_string(ctx
, "Root %s\n", ctx
->root
);
136 /* this is taken from 1.11.1p1 trace - but with Mbinary removed. we can't handle it (yet!) */
137 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
);
139 send_string(ctx
, "valid-requests\n");
141 /* check for the commands we will issue */
142 read_line(ctx
, buff
);
143 if (strncmp(buff
, "Valid-requests", 14) != 0)
145 debug(DEBUG_APPERROR
, "cvs_direct: bad response to valid-requests command");
146 close_cvs_server(ctx
);
150 if (!strstr(buff
, " version") ||
151 !strstr(buff
, " rlog") ||
152 !strstr(buff
, " rdiff") ||
153 !strstr(buff
, " diff") ||
154 !strstr(buff
, " co"))
156 debug(DEBUG_APPERROR
, "cvs_direct: cvs server too old for cvs_direct");
157 close_cvs_server(ctx
);
161 read_line(ctx
, buff
);
162 if (strcmp(buff
, "ok") != 0)
164 debug(DEBUG_APPERROR
, "cvs_direct: bad ok trailer to valid-requests command");
165 close_cvs_server(ctx
);
169 /* this is myterious but 'mandatory' */
170 send_string(ctx
, "UseUnchanged\n");
174 send_string(ctx
, "Gzip-stream %d\n", compress
);
178 debug(DEBUG_APPMSG1
, "cvs_direct initialized to CVSROOT %s", ctx
->root
);
184 static CvsServerCtx
* open_ctx_pserver(CvsServerCtx
* ctx
, const char * p_root
)
187 char full_root
[PATH_MAX
];
188 char * p
= root
, *tok
, *tok2
;
194 strcpy(root
, p_root
);
196 tok
= strsep(&p
, ":");
197 if (strlen(tok
) == 0 || !p
)
199 debug(DEBUG_APPERROR
, "parse error on third token");
203 tok2
= strsep(&tok
, "@");
204 if (!strlen(tok2
) || (!tok
|| !strlen(tok
)))
206 debug(DEBUG_APPERROR
, "parse error on user@server in pserver");
215 tok
= strchr(p
, '/');
218 debug(DEBUG_APPERROR
, "parse error: expecting / in root");
222 memset(port
, 0, sizeof(port
));
223 memcpy(port
, p
, tok
- p
);
229 strcpy(port
, "2401");
233 /* the line from registry does not contain port, so rebuild */
234 snprintf(full_root
, PATH_MAX
, ":pserver:%s@%s:%s", user
, server
, p
);
236 /* the line from .cvspass is fully qualified, so rebuild */
237 snprintf(full_root
, PATH_MAX
, ":pserver:%s@%s:%s%s", user
, server
, port
, p
);
239 get_cvspass(pass
, full_root
);
241 debug(DEBUG_TCP
, "user:%s server:%s port:%s pass:%s full_root:%s", user
, server
, port
, pass
, full_root
);
243 if ((ctx
->read_fd
= tcp_create_socket(REUSE_ADDR
)) < 0)
246 ctx
->write_fd
= dup(ctx
->read_fd
);
248 if (tcp_connect(ctx
->read_fd
, server
, atoi(port
)) < 0)
251 send_string(ctx
, "BEGIN AUTH REQUEST\n");
252 send_string(ctx
, "%s\n", p
);
253 send_string(ctx
, "%s\n", user
);
254 send_string(ctx
, "%s\n", pass
);
255 send_string(ctx
, "END AUTH REQUEST\n");
257 if (!read_response(ctx
, "I LOVE YOU"))
260 strcpy(ctx
->root
, p
);
272 static CvsServerCtx
* open_ctx_forked(CvsServerCtx
* ctx
, const char * p_root
)
275 debug(DEBUG_SYSERROR
, "cvs_direct: fork not supported on MinGW");
278 char * p
= root
, *tok
, *tok2
, *rep
;
279 char execcmd
[PATH_MAX
];
283 const char * cvs_server
= getenv("CVS_SERVER");
288 strcpy(root
, p_root
);
290 /* if there's a ':', it's remote */
291 tok
= strsep(&p
, ":");
295 const char * cvs_rsh
= getenv("CVS_RSH");
300 tok2
= strsep(&tok
, "@");
303 snprintf(execcmd
, PATH_MAX
, "%s -l %s %s %s server", cvs_rsh
, tok2
, tok
, cvs_server
);
305 snprintf(execcmd
, PATH_MAX
, "%s %s %s server", cvs_rsh
, tok2
, cvs_server
);
311 snprintf(execcmd
, PATH_MAX
, "%s server", cvs_server
);
315 if (pipe(to_cvs
) < 0)
317 debug(DEBUG_SYSERROR
, "cvs_direct: failed to create pipe to_cvs");
321 if (pipe(from_cvs
) < 0)
323 debug(DEBUG_SYSERROR
, "cvs_direct: failed to create pipe from_cvs");
327 debug(DEBUG_TCP
, "forked cmdline: %s", execcmd
);
329 if ((pid
= fork()) < 0)
331 debug(DEBUG_SYSERROR
, "cvs_direct: can't fork");
334 else if (pid
== 0) /* child */
350 execv("/bin/sh",argp
);
352 debug(DEBUG_APPERROR
, "cvs_direct: fatal: shouldn't be reached");
358 ctx
->read_fd
= from_cvs
[0];
359 ctx
->write_fd
= to_cvs
[1];
361 strcpy(ctx
->root
, rep
);
377 void close_cvs_server(CvsServerCtx
* ctx
)
379 /* FIXME: some sort of flushing should be done for non-compressed case */
387 * there shouldn't be anything left, but we do want
388 * to send an 'end of stream' marker, (if such a thing
393 ctx
->zout
.next_out
= buff
;
394 ctx
->zout
.avail_out
= BUFSIZ
;
395 ret
= deflate(&ctx
->zout
, Z_FINISH
);
397 if ((ret
== Z_OK
|| ret
== Z_STREAM_END
) && ctx
->zout
.avail_out
!= BUFSIZ
)
399 len
= BUFSIZ
- ctx
->zout
.avail_out
;
400 if (writen(ctx
->write_fd
, buff
, len
) != len
)
401 debug(DEBUG_APPERROR
, "cvs_direct: zout: error writing final state");
403 //hexdump(buff, len, "cvs_direct: zout: sending unsent data");
405 } while (ret
== Z_OK
);
407 if ((ret
= deflateEnd(&ctx
->zout
)) != Z_OK
)
408 debug(DEBUG_APPERROR
, "cvs_direct: zout: deflateEnd error: %s: %s",
409 (ret
== Z_STREAM_ERROR
) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx
->zout
.msg
);
412 /* we're done writing now */
413 debug(DEBUG_TCP
, "cvs_direct: closing cvs server write connection %d", ctx
->write_fd
);
414 close(ctx
->write_fd
);
417 * if this is pserver, then read_fd is a bi-directional socket.
418 * we want to shutdown the write side, just to make sure the
423 debug(DEBUG_TCP
, "cvs_direct: shutdown on read socket");
424 if (shutdown(ctx
->read_fd
, SHUT_WR
) < 0)
425 debug(DEBUG_SYSERROR
, "cvs_direct: error with shutdown on pserver socket");
430 int ret
= Z_OK
, len
, eof
= 0;
433 /* read to the 'eof'/'eos' marker. there are two states we
434 * track, looking for Z_STREAM_END (application level EOS)
435 * and EOF on socket. Both should happen at the same time,
436 * but we need to do the read first, the first time through
437 * the loop, but we want to do one read after getting Z_STREAM_END
438 * too. so this loop has really ugly exit conditions.
443 * if there's nothing in the avail_in, and we
444 * inflated everything last pass (avail_out != 0)
445 * then slurp some more from the descriptor,
446 * if we get EOF, exit the loop
448 if (ctx
->zin
.avail_in
== 0 && ctx
->zin
.avail_out
!= 0)
450 debug(DEBUG_TCP
, "cvs_direct: doing final slurp");
451 len
= read(ctx
->read_fd
, ctx
->zread_buff
, RD_BUFF_SIZE
);
452 debug(DEBUG_TCP
, "cvs_direct: did final slurp: %d", len
);
460 /* put the data into the inflate input stream */
461 ctx
->zin
.next_in
= ctx
->zread_buff
;
462 ctx
->zin
.avail_in
= len
;
466 * if the last time through we got Z_STREAM_END, and we
467 * get back here, it means we should've gotten EOF but
470 if (ret
== Z_STREAM_END
)
473 ctx
->zin
.next_out
= buff
;
474 ctx
->zin
.avail_out
= BUFSIZ
;
476 ret
= inflate(&ctx
->zin
, Z_SYNC_FLUSH
);
477 len
= BUFSIZ
- ctx
->zin
.avail_out
;
479 if (ret
== Z_BUF_ERROR
)
480 debug(DEBUG_APPERROR
, "Z_BUF_ERROR");
482 if (ret
== Z_OK
&& len
== 0)
483 debug(DEBUG_TCP
, "cvs_direct: no data out of inflate");
485 if (ret
== Z_STREAM_END
)
486 debug(DEBUG_TCP
, "cvs_direct: got Z_STREAM_END");
488 if ((ret
== Z_OK
|| ret
== Z_STREAM_END
) && len
> 0)
489 hexdump(buff
, BUFSIZ
- ctx
->zin
.avail_out
, "cvs_direct: zin: unread data at close");
492 if (ret
!= Z_STREAM_END
)
493 debug(DEBUG_APPERROR
, "cvs_direct: zin: Z_STREAM_END not encountered (premature EOF?)");
496 debug(DEBUG_APPERROR
, "cvs_direct: zin: EOF not encountered (premature Z_STREAM_END?)");
498 if ((ret
= inflateEnd(&ctx
->zin
)) != Z_OK
)
499 debug(DEBUG_APPERROR
, "cvs_direct: zin: inflateEnd error: %s: %s",
500 (ret
== Z_STREAM_ERROR
) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx
->zin
.msg
? ctx
->zin
.msg
: "");
503 debug(DEBUG_TCP
, "cvs_direct: closing cvs server read connection %d", ctx
->read_fd
);
509 static void get_cvspass(char * pass
, const char * root
)
512 /* CVSNT stores password in registry HKCU\Software\Cvsnt\cvspass
513 * See http://issues.apache.org/bugzilla/show_bug.cgi?id=21657#c5
514 * See http://www.adp-gmbh.ch/misc/tools/cvs/cvspass.html
517 if (RegOpenKeyExA(HKEY_CURRENT_USER
,"Software\\Cvsnt\\cvspass",0,KEY_READ
,&hKey
))
519 debug(DEBUG_APPERROR
, "Cannot open HKCU\\Software\\Cvsnt\\cvspass registry key");
522 /* "root" is connection string that require a password */
523 static char buf
[4096];
527 ret
= RegQueryValueExA(hKey
,root
,NULL
,&dwType
,(LPBYTE
)buf
,&dwLen
);
529 if (ret
!= ERROR_SUCCESS
)
531 debug(DEBUG_APPERROR
, "HKCU\\Software\\Cvsnt\\cvspass registry key not readable");
534 strcpy(pass
,buf
); /* If this function knew the size of pass, we could
535 use strncpy and avoid potential buffer-overflow. */
537 char cvspass
[PATH_MAX
];
543 if (!(home
= getenv("HOME")))
545 debug(DEBUG_APPERROR
, "HOME environment variable not set");
549 if (snprintf(cvspass
, PATH_MAX
, "%s/.cvspass", home
) >= PATH_MAX
)
551 debug(DEBUG_APPERROR
, "prefix buffer overflow");
555 if ((fp
= fopen(cvspass
, "r")))
558 int len
= strlen(root
);
560 while (fgets(buff
, BUFSIZ
, fp
))
562 /* FIXME: what does /1 mean? */
563 if (strncmp(buff
, "/1 ", 3) != 0)
566 if (strncmp(buff
+ 3, root
, len
) == 0)
568 strcpy(pass
, buff
+ 3 + len
+ 1);
582 static void send_string(CvsServerCtx
* ctx
, const char * str
, ...)
590 len
= vsnprintf(buff
, BUFSIZ
, str
, ap
);
593 debug(DEBUG_APPERROR
, "cvs_direct: command send string overflow");
601 if (ctx
->zout
.avail_in
!= 0)
603 debug(DEBUG_APPERROR
, "cvs_direct: zout: last output command not flushed");
607 ctx
->zout
.next_in
= buff
;
608 ctx
->zout
.avail_in
= len
;
609 ctx
->zout
.avail_out
= 0;
611 while (ctx
->zout
.avail_in
> 0 || ctx
->zout
.avail_out
== 0)
615 ctx
->zout
.next_out
= zbuff
;
616 ctx
->zout
.avail_out
= BUFSIZ
;
618 /* FIXME: for the arguments before a command, flushing is counterproductive */
619 ret
= deflate(&ctx
->zout
, Z_SYNC_FLUSH
);
623 len
= BUFSIZ
- ctx
->zout
.avail_out
;
625 if (writen(ctx
->write_fd
, zbuff
, len
) != len
)
627 debug(DEBUG_SYSERROR
, "cvs_direct: zout: can't write");
633 debug(DEBUG_APPERROR
, "cvs_direct: zout: error %d %s", ret
, ctx
->zout
.msg
);
639 if (writen(ctx
->write_fd
, buff
, len
) != len
)
641 debug(DEBUG_SYSERROR
, "cvs_direct: can't send command");
646 debug(DEBUG_TCP
, "string: '%s' sent", buff
);
649 static int refill_buffer(CvsServerCtx
* ctx
)
653 if (ctx
->head
!= ctx
->tail
)
655 debug(DEBUG_APPERROR
, "cvs_direct: refill_buffer called on non-empty buffer");
659 ctx
->head
= ctx
->read_buff
;
666 /* if there was leftover buffer room, it's time to slurp more data */
669 if (ctx
->zin
.avail_out
> 0)
671 if (ctx
->zin
.avail_in
!= 0)
673 debug(DEBUG_APPERROR
, "cvs_direct: zin: expect 0 avail_in");
676 zlen
= read(ctx
->read_fd
, ctx
->zread_buff
, RD_BUFF_SIZE
);
677 ctx
->zin
.next_in
= ctx
->zread_buff
;
678 ctx
->zin
.avail_in
= zlen
;
681 ctx
->zin
.next_out
= ctx
->head
;
682 ctx
->zin
.avail_out
= len
;
684 /* FIXME: we don't always need Z_SYNC_FLUSH, do we? */
685 ret
= inflate(&ctx
->zin
, Z_SYNC_FLUSH
);
687 while (ctx
->zin
.avail_out
== len
);
691 ctx
->tail
= ctx
->head
+ (len
- ctx
->zin
.avail_out
);
695 debug(DEBUG_APPERROR
, "cvs_direct: zin: error %d %s", ret
, ctx
->zin
.msg
);
701 len
= read(ctx
->read_fd
, ctx
->head
, len
);
702 ctx
->tail
= (len
<= 0) ? ctx
->head
: ctx
->head
+ len
;
708 static int read_line(CvsServerCtx
* ctx
, char * p
)
713 if (ctx
->head
== ctx
->tail
)
714 if (refill_buffer(ctx
) <= 0)
731 static int read_response(CvsServerCtx
* ctx
, const char * str
)
733 /* FIXME: more than 1 char at a time */
736 if (read_line(ctx
, resp
) < 0)
739 debug(DEBUG_TCP
, "response '%s' read", resp
);
741 return (strcmp(resp
, str
) == 0);
744 static void ctx_to_fp(CvsServerCtx
* ctx
, FILE * fp
)
750 read_line(ctx
, line
);
751 debug(DEBUG_TCP
, "ctx_to_fp: %s", line
);
752 if (memcmp(line
, "M ", 2) == 0)
755 fprintf(fp
, "%s\n", line
+ 2);
757 else if (memcmp(line
, "E ", 2) == 0)
759 debug(DEBUG_APPMSG1
, "%s", line
+ 2);
761 else if (strncmp(line
, "ok", 2) == 0 || strncmp(line
, "error", 5) == 0)
771 void cvs_rdiff(CvsServerCtx
* ctx
,
772 const char * rep
, const char * file
,
773 const char * rev1
, const char * rev2
)
775 /* NOTE: opts are ignored for rdiff, '-u' is always used */
777 send_string(ctx
, "Argument -u\n");
778 send_string(ctx
, "Argument -r\n");
779 send_string(ctx
, "Argument %s\n", rev1
);
780 send_string(ctx
, "Argument -r\n");
781 send_string(ctx
, "Argument %s\n", rev2
);
782 send_string(ctx
, "Argument %s%s\n", rep
, file
);
783 send_string(ctx
, "rdiff\n");
785 ctx_to_fp(ctx
, stdout
);
788 void cvs_rupdate(CvsServerCtx
* ctx
, const char * rep
, const char * file
, const char * rev
, int create
, const char * opts
)
791 char cmdbuff
[BUFSIZ
];
793 snprintf(cmdbuff
, BUFSIZ
, "diff %s %s /dev/null %s | sed -e '%s s|^\\([+-][+-][+-]\\) -|\\1 %s/%s|g'",
794 opts
, create
?"":"-", create
?"-":"", create
?"2":"1", rep
, file
);
796 debug(DEBUG_TCP
, "cmdbuff: %s", cmdbuff
);
798 if (!(fp
= popen(cmdbuff
, "w")))
800 debug(DEBUG_APPERROR
, "cvs_direct: popen for diff failed: %s", cmdbuff
);
804 send_string(ctx
, "Argument -p\n");
805 send_string(ctx
, "Argument -r\n");
806 send_string(ctx
, "Argument %s\n", rev
);
807 send_string(ctx
, "Argument %s/%s\n", rep
, file
);
808 send_string(ctx
, "co\n");
815 static int parse_patch_arg(char * arg
, char ** str
)
817 char *tok
, *tok2
= "";
818 tok
= strsep(str
, " ");
824 debug(DEBUG_APPERROR
, "diff_opts parse error: no '-' starting argument: %s", *str
);
828 /* if it's not 'long format' argument, we can process it efficiently */
831 debug(DEBUG_APPERROR
, "diff_opts parse_error: long format args not supported");
835 /* see if command wants two args and they're separated by ' ' */
836 if (tok
[2] == 0 && strchr("BdDFgiorVxYz", tok
[1]))
838 tok2
= strsep(str
, " ");
841 debug(DEBUG_APPERROR
, "diff_opts parse_error: argument %s requires two arguments", tok
);
846 snprintf(arg
, 32, "%s%s", tok
, tok2
);
850 void cvs_diff(CvsServerCtx
* ctx
,
851 const char * rep
, const char * file
,
852 const char * rev1
, const char * rev2
, const char * opts
)
854 char argstr
[BUFSIZ
], *p
= argstr
;
856 char file_buff
[PATH_MAX
], *basename
;
858 strzncpy(argstr
, opts
, BUFSIZ
);
859 while (parse_patch_arg(arg
, &p
))
860 send_string(ctx
, "Argument %s\n", arg
);
862 send_string(ctx
, "Argument -r\n");
863 send_string(ctx
, "Argument %s\n", rev1
);
864 send_string(ctx
, "Argument -r\n");
865 send_string(ctx
, "Argument %s\n", rev2
);
868 * we need to separate the 'basename' of file in order to
869 * generate the Directory directive(s)
871 strzncpy(file_buff
, file
, PATH_MAX
);
872 if ((basename
= strrchr(file_buff
, '/')))
875 send_string(ctx
, "Directory %s/%s\n", rep
, file_buff
);
876 send_string(ctx
, "%s/%s/%s\n", ctx
->root
, rep
, file_buff
);
880 send_string(ctx
, "Directory %s\n", rep
, file_buff
);
881 send_string(ctx
, "%s/%s\n", ctx
->root
, rep
);
884 send_string(ctx
, "Directory .\n");
885 send_string(ctx
, "%s\n", ctx
->root
);
886 send_string(ctx
, "Argument %s/%s\n", rep
, file
);
887 send_string(ctx
, "diff\n");
889 ctx_to_fp(ctx
, stdout
);
893 * FIXME: the design of this sucks. It was originally designed to fork a subprocess
894 * which read the cvs response and send it back through a pipe the main process,
895 * which fdopen(3)ed the other end, and juts used regular fgets. This however
896 * didn't work because the reads of compressed data in the child process altered
897 * the compression state, and there was no way to resynchronize that state with
898 * the parent process. We could use threads...
900 FILE * cvs_rlog_open(CvsServerCtx
* ctx
, const char * rep
, const char * date_str
)
902 /* note: use of the date_str is handled in a non-standard, cvsps specific way */
903 if (date_str
&& date_str
[0])
905 send_string(ctx
, "Argument -d\n", rep
);
906 send_string(ctx
, "Argument %s<1 Jan 2038 05:00:00 -0000\n", date_str
);
907 send_string(ctx
, "Argument -d\n", rep
);
908 send_string(ctx
, "Argument %s\n", date_str
);
911 send_string(ctx
, "Argument %s\n", rep
);
912 send_string(ctx
, "rlog\n");
915 * FIXME: is it possible to create a 'fake' FILE * whose 'refill'
921 char * cvs_rlog_fgets(char * buff
, int buflen
, CvsServerCtx
* ctx
)
926 len
= read_line(ctx
, lbuff
);
927 debug(DEBUG_TCP
, "cvs_direct: rlog: read %s", lbuff
);
929 if (memcmp(lbuff
, "M ", 2) == 0)
931 memcpy(buff
, lbuff
+ 2, len
- 2);
932 buff
[len
- 2 ] = '\n';
935 else if (memcmp(lbuff
, "E ", 2) == 0)
937 debug(DEBUG_APPMSG1
, "%s", lbuff
+ 2);
939 else if (strcmp(lbuff
, "ok") == 0 ||strcmp(lbuff
, "error") == 0)
941 debug(DEBUG_TCP
, "cvs_direct: rlog: got command completion");
948 void cvs_rlog_close(CvsServerCtx
* ctx
)
952 void cvs_version(CvsServerCtx
* ctx
, char * client_version
, char * server_version
)
955 strcpy(client_version
, "Client: Concurrent Versions System (CVS) 99.99.99 (client/server) cvs-direct");
956 send_string(ctx
, "version\n");
957 read_line(ctx
, lbuff
);
958 if (memcmp(lbuff
, "M ", 2) == 0)
959 sprintf(server_version
, "Server: %s", lbuff
+ 2);
961 debug(DEBUG_APPERROR
, "cvs_direct: didn't read version: %s", lbuff
);
963 read_line(ctx
, lbuff
);
964 if (strcmp(lbuff
, "ok") != 0)
965 debug(DEBUG_APPERROR
, "cvs_direct: protocol error reading version");
967 debug(DEBUG_TCP
, "cvs_direct: client version %s", client_version
);
968 debug(DEBUG_TCP
, "cvs_direct: server version %s", server_version
);