2 * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc.
3 * See COPYING file for license information
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"
21 #define RD_BUFF_SIZE 4096
31 /* buffered reads from descriptor */
32 char read_buff
[RD_BUFF_SIZE
];
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
));
57 char * p
= root
, *tok
;
62 ctx
->head
= ctx
->tail
= ctx
->read_buff
;
63 ctx
->read_fd
= ctx
->write_fd
= -1;
69 memset(&ctx
->zout
, 0, sizeof(z_stream
));
70 memset(&ctx
->zin
, 0, sizeof(z_stream
));
73 * to 'prime' the reads, make it look like there was output
74 * room available (i.e. we have processed all pending compressed
77 ctx
->zin
.avail_out
= 1;
79 if (deflateInit(&ctx
->zout
, compress
) != Z_OK
)
85 if (inflateInit(&ctx
->zin
) != Z_OK
)
87 deflateEnd(&ctx
->zout
);
95 tok
= strsep(&p
, ":");
97 /* if root string looks like :pserver:... then the first token will be empty */
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
);
112 debug(DEBUG_APPERROR
, "cvs_direct: unsupported cvs access method: %s", method
);
119 ctx
= open_ctx_forked(ctx
, p_root
);
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
);
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
);
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
);
161 /* this is myterious but 'mandatory' */
162 send_string(ctx
, "UseUnchanged\n");
166 send_string(ctx
, "Gzip-stream %d\n", compress
);
170 debug(DEBUG_APPMSG1
, "cvs_direct initialized to CVSROOT %s", ctx
->root
);
176 static CvsServerCtx
* open_ctx_pserver(CvsServerCtx
* ctx
, const char * p_root
)
179 char full_root
[PATH_MAX
];
180 char * p
= root
, *tok
, *tok2
;
186 strcpy(root
, p_root
);
188 tok
= strsep(&p
, ":");
189 if (strlen(tok
) == 0 || !p
)
191 debug(DEBUG_APPERROR
, "parse error on third token");
195 tok2
= strsep(&tok
, "@");
196 if (!strlen(tok2
) || (!tok
|| !strlen(tok
)))
198 debug(DEBUG_APPERROR
, "parse error on user@server in pserver");
207 tok
= strchr(p
, '/');
210 debug(DEBUG_APPERROR
, "parse error: expecting / in root");
214 memset(port
, 0, sizeof(port
));
215 memcpy(port
, p
, tok
- p
);
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)
233 ctx
->write_fd
= dup(ctx
->read_fd
);
235 if (tcp_connect(ctx
->read_fd
, server
, atoi(port
)) < 0)
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"))
247 strcpy(ctx
->root
, p
);
259 static CvsServerCtx
* open_ctx_forked(CvsServerCtx
* ctx
, const char * p_root
)
262 char * p
= root
, *tok
, *tok2
, *rep
;
263 char execcmd
[PATH_MAX
];
267 const char * cvs_server
= getenv("CVS_SERVER");
272 strcpy(root
, p_root
);
274 /* if there's a ':', it's remote */
275 tok
= strsep(&p
, ":");
279 const char * cvs_rsh
= getenv("CVS_RSH");
284 tok2
= strsep(&tok
, "@");
287 snprintf(execcmd
, PATH_MAX
, "%s -l %s %s %s server", cvs_rsh
, tok2
, tok
, cvs_server
);
289 snprintf(execcmd
, PATH_MAX
, "%s %s %s server", cvs_rsh
, tok2
, cvs_server
);
295 snprintf(execcmd
, PATH_MAX
, "%s server", cvs_server
);
299 if (pipe(to_cvs
) < 0)
301 debug(DEBUG_SYSERROR
, "cvs_direct: failed to create pipe to_cvs");
305 if (pipe(from_cvs
) < 0)
307 debug(DEBUG_SYSERROR
, "cvs_direct: failed to create pipe from_cvs");
311 debug(DEBUG_TCP
, "forked cmdline: %s", execcmd
);
313 if ((pid
= fork()) < 0)
315 debug(DEBUG_SYSERROR
, "cvs_direct: can't fork");
318 else if (pid
== 0) /* child */
334 execv("/bin/sh",argp
);
336 debug(DEBUG_APPERROR
, "cvs_direct: fatal: shouldn't be reached");
342 ctx
->read_fd
= from_cvs
[0];
343 ctx
->write_fd
= to_cvs
[1];
345 strcpy(ctx
->root
, rep
);
360 void close_cvs_server(CvsServerCtx
* ctx
)
362 /* FIXME: some sort of flushing should be done for non-compressed case */
370 * there shouldn't be anything left, but we do want
371 * to send an 'end of stream' marker, (if such a thing
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
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");
413 int ret
= Z_OK
, len
, eof
= 0;
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.
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
);
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
453 if (ret
== Z_STREAM_END
)
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?)");
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
);
492 static void get_cvspass(char * pass
, const char * root
)
494 char cvspass
[PATH_MAX
];
500 if (!(home
= getenv("HOME")))
502 debug(DEBUG_APPERROR
, "HOME environment variable not set");
506 if (snprintf(cvspass
, PATH_MAX
, "%s/.cvspass", home
) >= PATH_MAX
)
508 debug(DEBUG_APPERROR
, "prefix buffer overflow");
512 if ((fp
= fopen(cvspass
, "r")))
515 int len
= strlen(root
);
517 while (fgets(buff
, BUFSIZ
, fp
))
519 /* FIXME: what does /1 mean? */
520 if (strncmp(buff
, "/1 ", 3) != 0)
523 if (strncmp(buff
+ 3, root
, len
) == 0)
525 strcpy(pass
, buff
+ 3 + len
+ 1);
538 static void send_string(CvsServerCtx
* ctx
, const char * str
, ...)
546 len
= vsnprintf(buff
, BUFSIZ
, str
, ap
);
549 debug(DEBUG_APPERROR
, "cvs_direct: command send string overflow");
557 if (ctx
->zout
.avail_in
!= 0)
559 debug(DEBUG_APPERROR
, "cvs_direct: zout: last output command not flushed");
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)
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
);
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");
589 debug(DEBUG_APPERROR
, "cvs_direct: zout: error %d %s", ret
, ctx
->zout
.msg
);
595 if (writen(ctx
->write_fd
, buff
, len
) != len
)
597 debug(DEBUG_SYSERROR
, "cvs_direct: can't send command");
602 debug(DEBUG_TCP
, "string: '%s' sent", buff
);
605 static int refill_buffer(CvsServerCtx
* ctx
)
609 if (ctx
->head
!= ctx
->tail
)
611 debug(DEBUG_APPERROR
, "cvs_direct: refill_buffer called on non-empty buffer");
615 ctx
->head
= ctx
->read_buff
;
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");
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
);
647 ctx
->tail
= ctx
->head
+ (len
- ctx
->zin
.avail_out
);
651 debug(DEBUG_APPERROR
, "cvs_direct: zin: error %d %s", ret
, ctx
->zin
.msg
);
657 len
= read(ctx
->read_fd
, ctx
->head
, len
);
658 ctx
->tail
= (len
<= 0) ? ctx
->head
: ctx
->head
+ len
;
664 static int read_line(CvsServerCtx
* ctx
, char * p
)
669 if (ctx
->head
== ctx
->tail
)
670 if (refill_buffer(ctx
) <= 0)
687 static int read_response(CvsServerCtx
* ctx
, const char * str
)
689 /* FIXME: more than 1 char at a time */
692 if (read_line(ctx
, resp
) < 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
)
706 read_line(ctx
, line
);
707 debug(DEBUG_TCP
, "ctx_to_fp: %s", line
);
708 if (memcmp(line
, "M ", 2) == 0)
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)
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
)
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
);
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");
771 static int parse_patch_arg(char * arg
, char ** str
)
773 char *tok
, *tok2
= "";
774 tok
= strsep(str
, " ");
780 debug(DEBUG_APPERROR
, "diff_opts parse error: no '-' starting argument: %s", *str
);
784 /* if it's not 'long format' argument, we can process it efficiently */
787 debug(DEBUG_APPERROR
, "diff_opts parse_error: long format args not supported");
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
, " ");
797 debug(DEBUG_APPERROR
, "diff_opts parse_error: argument %s requires two arguments", tok
);
802 snprintf(arg
, 32, "%s%s", tok
, tok2
);
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
;
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
, '/')))
831 send_string(ctx
, "Directory %s/%s\n", rep
, file_buff
);
832 send_string(ctx
, "%s/%s/%s\n", ctx
->root
, rep
, file_buff
);
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'
877 char * cvs_rlog_fgets(char * buff
, int buflen
, CvsServerCtx
* ctx
)
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';
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");
904 void cvs_rlog_close(CvsServerCtx
* ctx
)
908 void cvs_version(CvsServerCtx
* ctx
, char * client_version
, char * server_version
)
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);
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
);