=== added directory '.pc/06-discard-extra-version-lines.patch' === added file '.pc/06-discard-extra-version-lines.patch/cvs_direct.c' --- .pc/06-discard-extra-version-lines.patch/cvs_direct.c 1970-01-01 00:00:00 +0000 +++ .pc/06-discard-extra-version-lines.patch/cvs_direct.c 2015-02-15 22:42:42 +0000 @@ -0,0 +1,925 @@ +/* + * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc. + * See COPYING file for license information + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cvs_direct.h" +#include "util.h" + +#define RD_BUFF_SIZE 4096 + +struct _CvsServerCtx +{ + int read_fd; + int write_fd; + char root[PATH_MAX]; + + int is_pserver; + + /* buffered reads from descriptor */ + char read_buff[RD_BUFF_SIZE]; + char * head; + char * tail; + + int compressed; + z_stream zout; + z_stream zin; + + /* when reading compressed data, the compressed data buffer */ + char zread_buff[RD_BUFF_SIZE]; +}; + +static void get_cvspass(char *, const char *); +static void send_string(CvsServerCtx *, const char *, ...); +static int read_response(CvsServerCtx *, const char *); +static void ctx_to_fp(CvsServerCtx * ctx, FILE * fp); +static int read_line(CvsServerCtx * ctx, char * p); + +static CvsServerCtx * open_ctx_pserver(CvsServerCtx *, const char *); +static CvsServerCtx * open_ctx_forked(CvsServerCtx *, const char *); + +CvsServerCtx * open_cvs_server(char * p_root, int compress) +{ + CvsServerCtx * ctx = (CvsServerCtx*)malloc(sizeof(*ctx)); + char root[PATH_MAX]; + char * p = root, *tok; + + if (!ctx) + return NULL; + + ctx->head = ctx->tail = ctx->read_buff; + ctx->read_fd = ctx->write_fd = -1; + ctx->compressed = 0; + ctx->is_pserver = 0; + + if (compress) + { + memset(&ctx->zout, 0, sizeof(z_stream)); + memset(&ctx->zin, 0, sizeof(z_stream)); + + /* + * to 'prime' the reads, make it look like there was output + * room available (i.e. we have processed all pending compressed + * data + */ + ctx->zin.avail_out = 1; + + if (deflateInit(&ctx->zout, compress) != Z_OK) + { + free(ctx); + return NULL; + } + + if (inflateInit(&ctx->zin) != Z_OK) + { + deflateEnd(&ctx->zout); + free(ctx); + return NULL; + } + } + + strcpy(root, p_root); + + tok = strsep(&p, ":"); + + /* if root string looks like :pserver:... then the first token will be empty */ + if (strlen(tok) == 0) + { + char * method = strsep(&p, ":"); + if (strcmp(method, "pserver") == 0) + { + ctx = open_ctx_pserver(ctx, p); + } + else if (strstr("local:ext:fork:server", method)) + { + /* handle all of these via fork, even local */ + ctx = open_ctx_forked(ctx, p); + } + else + { + debug(DEBUG_APPERROR, "cvs_direct: unsupported cvs access method: %s", method); + free(ctx); + ctx = NULL; + } + } + else + { + ctx = open_ctx_forked(ctx, p_root); + } + + if (ctx) + { + char buff[BUFSIZ]; + + send_string(ctx, "Root %s\n", ctx->root); + + /* this is taken from 1.11.1p1 trace - but with Mbinary removed. we can't handle it (yet!) */ + 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); + + send_string(ctx, "valid-requests\n"); + + /* check for the commands we will issue */ + read_line(ctx, buff); + if (strncmp(buff, "Valid-requests", 14) != 0) + { + debug(DEBUG_APPERROR, "cvs_direct: bad response to valid-requests command"); + close_cvs_server(ctx); + return NULL; + } + + if (!strstr(buff, " version") || + !strstr(buff, " rlog") || + !strstr(buff, " rdiff") || + !strstr(buff, " diff") || + !strstr(buff, " co")) + { + debug(DEBUG_APPERROR, "cvs_direct: cvs server too old for cvs_direct"); + close_cvs_server(ctx); + return NULL; + } + + read_line(ctx, buff); + if (strcmp(buff, "ok") != 0) + { + debug(DEBUG_APPERROR, "cvs_direct: bad ok trailer to valid-requests command"); + close_cvs_server(ctx); + return NULL; + } + + /* this is myterious but 'mandatory' */ + send_string(ctx, "UseUnchanged\n"); + + if (compress) + { + send_string(ctx, "Gzip-stream %d\n", compress); + ctx->compressed = 1; + } + + debug(DEBUG_APPMSG1, "cvs_direct initialized to CVSROOT %s", ctx->root); + } + + return ctx; +} + +static CvsServerCtx * open_ctx_pserver(CvsServerCtx * ctx, const char * p_root) +{ + char root[PATH_MAX]; + char full_root[PATH_MAX]; + char * p = root, *tok, *tok2; + char user[BUFSIZ]; + char server[BUFSIZ]; + char pass[BUFSIZ]; + char port[8]; + + strcpy(root, p_root); + + tok = strsep(&p, ":"); + if (strlen(tok) == 0 || !p) + { + debug(DEBUG_APPERROR, "parse error on third token"); + goto out_free_err; + } + + tok2 = strsep(&tok, "@"); + if (!strlen(tok2) || (!tok || !strlen(tok))) + { + debug(DEBUG_APPERROR, "parse error on user@server in pserver"); + goto out_free_err; + } + + strcpy(user, tok2); + strcpy(server, tok); + + if (*p != '/') + { + tok = strchr(p, '/'); + if (!tok) + { + debug(DEBUG_APPERROR, "parse error: expecting / in root"); + goto out_free_err; + } + + memset(port, 0, sizeof(port)); + memcpy(port, p, tok - p); + + p = tok; + } + else + { + strcpy(port, "2401"); + } + + /* the line from .cvspass is fully qualified, so rebuild */ + snprintf(full_root, PATH_MAX, ":pserver:%s@%s:%s%s", user, server, port, p); + get_cvspass(pass, full_root); + + debug(DEBUG_TCP, "user:%s server:%s port:%s pass:%s full_root:%s", user, server, port, pass, full_root); + + if ((ctx->read_fd = tcp_create_socket(REUSE_ADDR)) < 0) + goto out_free_err; + + ctx->write_fd = dup(ctx->read_fd); + + if (tcp_connect(ctx->read_fd, server, atoi(port)) < 0) + goto out_close_err; + + send_string(ctx, "BEGIN AUTH REQUEST\n"); + send_string(ctx, "%s\n", p); + send_string(ctx, "%s\n", user); + send_string(ctx, "%s\n", pass); + send_string(ctx, "END AUTH REQUEST\n"); + + if (!read_response(ctx, "I LOVE YOU")) + goto out_close_err; + + strcpy(ctx->root, p); + ctx->is_pserver = 1; + + return ctx; + + out_close_err: + close(ctx->read_fd); + out_free_err: + free(ctx); + return NULL; +} + +static CvsServerCtx * open_ctx_forked(CvsServerCtx * ctx, const char * p_root) +{ + char root[PATH_MAX]; + char * p = root, *tok, *tok2, *rep; + char execcmd[PATH_MAX]; + int to_cvs[2]; + int from_cvs[2]; + pid_t pid; + const char * cvs_server = getenv("CVS_SERVER"); + + if (!cvs_server) + cvs_server = "cvs"; + + strcpy(root, p_root); + + /* if there's a ':', it's remote */ + tok = strsep(&p, ":"); + + if (p) + { + const char * cvs_rsh = getenv("CVS_RSH"); + + if (!cvs_rsh) + cvs_rsh = "rsh"; + + tok2 = strsep(&tok, "@"); + + if (tok) + snprintf(execcmd, PATH_MAX, "%s -l %s %s %s server", cvs_rsh, tok2, tok, cvs_server); + else + snprintf(execcmd, PATH_MAX, "%s %s %s server", cvs_rsh, tok2, cvs_server); + + rep = p; + } + else + { + snprintf(execcmd, PATH_MAX, "%s server", cvs_server); + rep = tok; + } + + if (pipe(to_cvs) < 0) + { + debug(DEBUG_SYSERROR, "cvs_direct: failed to create pipe to_cvs"); + goto out_free_err; + } + + if (pipe(from_cvs) < 0) + { + debug(DEBUG_SYSERROR, "cvs_direct: failed to create pipe from_cvs"); + goto out_close_err; + } + + debug(DEBUG_TCP, "forked cmdline: %s", execcmd); + + if ((pid = fork()) < 0) + { + debug(DEBUG_SYSERROR, "cvs_direct: can't fork"); + goto out_close2_err; + } + else if (pid == 0) /* child */ + { + char * argp[4]; + argp[0] = "sh"; + argp[1] = "-c"; + argp[2] = execcmd; + argp[3] = NULL; + + close(to_cvs[1]); + close(from_cvs[0]); + + close(0); + dup(to_cvs[0]); + close(1); + dup(from_cvs[1]); + + execv("/bin/sh",argp); + + debug(DEBUG_APPERROR, "cvs_direct: fatal: shouldn't be reached"); + exit(1); + } + + close(to_cvs[0]); + close(from_cvs[1]); + ctx->read_fd = from_cvs[0]; + ctx->write_fd = to_cvs[1]; + + strcpy(ctx->root, rep); + + return ctx; + + out_close2_err: + close(from_cvs[0]); + close(from_cvs[1]); + out_close_err: + close(to_cvs[0]); + close(to_cvs[1]); + out_free_err: + free(ctx); + return NULL; +} + +void close_cvs_server(CvsServerCtx * ctx) +{ + /* FIXME: some sort of flushing should be done for non-compressed case */ + + if (ctx->compressed) + { + int ret, len; + char buff[BUFSIZ]; + + /* + * there shouldn't be anything left, but we do want + * to send an 'end of stream' marker, (if such a thing + * actually exists..) + */ + do + { + ctx->zout.next_out = buff; + ctx->zout.avail_out = BUFSIZ; + ret = deflate(&ctx->zout, Z_FINISH); + + if ((ret == Z_OK || ret == Z_STREAM_END) && ctx->zout.avail_out != BUFSIZ) + { + len = BUFSIZ - ctx->zout.avail_out; + if (writen(ctx->write_fd, buff, len) != len) + debug(DEBUG_APPERROR, "cvs_direct: zout: error writing final state"); + + //hexdump(buff, len, "cvs_direct: zout: sending unsent data"); + } + } while (ret == Z_OK); + + if ((ret = deflateEnd(&ctx->zout)) != Z_OK) + debug(DEBUG_APPERROR, "cvs_direct: zout: deflateEnd error: %s: %s", + (ret == Z_STREAM_ERROR) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx->zout.msg); + } + + /* we're done writing now */ + debug(DEBUG_TCP, "cvs_direct: closing cvs server write connection %d", ctx->write_fd); + close(ctx->write_fd); + + /* + * if this is pserver, then read_fd is a bi-directional socket. + * we want to shutdown the write side, just to make sure the + * server get's eof + */ + if (ctx->is_pserver) + { + debug(DEBUG_TCP, "cvs_direct: shutdown on read socket"); + if (shutdown(ctx->read_fd, SHUT_WR) < 0) + debug(DEBUG_SYSERROR, "cvs_direct: error with shutdown on pserver socket"); + } + + if (ctx->compressed) + { + int ret = Z_OK, len, eof = 0; + char buff[BUFSIZ]; + + /* read to the 'eof'/'eos' marker. there are two states we + * track, looking for Z_STREAM_END (application level EOS) + * and EOF on socket. Both should happen at the same time, + * but we need to do the read first, the first time through + * the loop, but we want to do one read after getting Z_STREAM_END + * too. so this loop has really ugly exit conditions. + */ + for(;;) + { + /* + * if there's nothing in the avail_in, and we + * inflated everything last pass (avail_out != 0) + * then slurp some more from the descriptor, + * if we get EOF, exit the loop + */ + if (ctx->zin.avail_in == 0 && ctx->zin.avail_out != 0) + { + debug(DEBUG_TCP, "cvs_direct: doing final slurp"); + len = read(ctx->read_fd, ctx->zread_buff, RD_BUFF_SIZE); + debug(DEBUG_TCP, "cvs_direct: did final slurp: %d", len); + + if (len <= 0) + { + eof = 1; + break; + } + + /* put the data into the inflate input stream */ + ctx->zin.next_in = ctx->zread_buff; + ctx->zin.avail_in = len; + } + + /* + * if the last time through we got Z_STREAM_END, and we + * get back here, it means we should've gotten EOF but + * didn't + */ + if (ret == Z_STREAM_END) + break; + + ctx->zin.next_out = buff; + ctx->zin.avail_out = BUFSIZ; + + ret = inflate(&ctx->zin, Z_SYNC_FLUSH); + len = BUFSIZ - ctx->zin.avail_out; + + if (ret == Z_BUF_ERROR) + debug(DEBUG_APPERROR, "Z_BUF_ERROR"); + + if (ret == Z_OK && len == 0) + debug(DEBUG_TCP, "cvs_direct: no data out of inflate"); + + if (ret == Z_STREAM_END) + debug(DEBUG_TCP, "cvs_direct: got Z_STREAM_END"); + + if ((ret == Z_OK || ret == Z_STREAM_END) && len > 0) + hexdump(buff, BUFSIZ - ctx->zin.avail_out, "cvs_direct: zin: unread data at close"); + } + + if (ret != Z_STREAM_END) + debug(DEBUG_APPERROR, "cvs_direct: zin: Z_STREAM_END not encountered (premature EOF?)"); + + if (eof == 0) + debug(DEBUG_APPERROR, "cvs_direct: zin: EOF not encountered (premature Z_STREAM_END?)"); + + if ((ret = inflateEnd(&ctx->zin)) != Z_OK) + debug(DEBUG_APPERROR, "cvs_direct: zin: inflateEnd error: %s: %s", + (ret == Z_STREAM_ERROR) ? "Z_STREAM_ERROR":"Z_DATA_ERROR", ctx->zin.msg ? ctx->zin.msg : ""); + } + + debug(DEBUG_TCP, "cvs_direct: closing cvs server read connection %d", ctx->read_fd); + close(ctx->read_fd); + + free(ctx); +} + +static void get_cvspass(char * pass, const char * root) +{ + char cvspass[PATH_MAX]; + const char * home; + FILE * fp; + + pass[0] = 0; + + if (!(home = getenv("HOME"))) + { + debug(DEBUG_APPERROR, "HOME environment variable not set"); + exit(1); + } + + if (snprintf(cvspass, PATH_MAX, "%s/.cvspass", home) >= PATH_MAX) + { + debug(DEBUG_APPERROR, "prefix buffer overflow"); + exit(1); + } + + if ((fp = fopen(cvspass, "r"))) + { + char buff[BUFSIZ]; + int len = strlen(root); + + while (fgets(buff, BUFSIZ, fp)) + { + /* FIXME: what does /1 mean? */ + if (strncmp(buff, "/1 ", 3) != 0) + continue; + + if (strncmp(buff + 3, root, len) == 0) + { + strcpy(pass, buff + 3 + len + 1); + chop(pass); + break; + } + + } + fclose(fp); + } + + if (!pass[0]) + pass[0] = 'A'; +} + +static void send_string(CvsServerCtx * ctx, const char * str, ...) +{ + int len; + char buff[BUFSIZ]; + va_list ap; + + va_start(ap, str); + + len = vsnprintf(buff, BUFSIZ, str, ap); + if (len >= BUFSIZ) + { + debug(DEBUG_APPERROR, "cvs_direct: command send string overflow"); + exit(1); + } + + if (ctx->compressed) + { + char zbuff[BUFSIZ]; + + if (ctx->zout.avail_in != 0) + { + debug(DEBUG_APPERROR, "cvs_direct: zout: last output command not flushed"); + exit(1); + } + + ctx->zout.next_in = buff; + ctx->zout.avail_in = len; + ctx->zout.avail_out = 0; + + while (ctx->zout.avail_in > 0 || ctx->zout.avail_out == 0) + { + int ret; + + ctx->zout.next_out = zbuff; + ctx->zout.avail_out = BUFSIZ; + + /* FIXME: for the arguments before a command, flushing is counterproductive */ + ret = deflate(&ctx->zout, Z_SYNC_FLUSH); + + if (ret == Z_OK) + { + len = BUFSIZ - ctx->zout.avail_out; + + if (writen(ctx->write_fd, zbuff, len) != len) + { + debug(DEBUG_SYSERROR, "cvs_direct: zout: can't write"); + exit(1); + } + } + else + { + debug(DEBUG_APPERROR, "cvs_direct: zout: error %d %s", ret, ctx->zout.msg); + } + } + } + else + { + if (writen(ctx->write_fd, buff, len) != len) + { + debug(DEBUG_SYSERROR, "cvs_direct: can't send command"); + exit(1); + } + } + + debug(DEBUG_TCP, "string: '%s' sent", buff); +} + +static int refill_buffer(CvsServerCtx * ctx) +{ + int len; + + if (ctx->head != ctx->tail) + { + debug(DEBUG_APPERROR, "cvs_direct: refill_buffer called on non-empty buffer"); + exit(1); + } + + ctx->head = ctx->read_buff; + len = RD_BUFF_SIZE; + + if (ctx->compressed) + { + int zlen, ret; + + /* if there was leftover buffer room, it's time to slurp more data */ + do + { + if (ctx->zin.avail_out > 0) + { + if (ctx->zin.avail_in != 0) + { + debug(DEBUG_APPERROR, "cvs_direct: zin: expect 0 avail_in"); + exit(1); + } + zlen = read(ctx->read_fd, ctx->zread_buff, RD_BUFF_SIZE); + ctx->zin.next_in = ctx->zread_buff; + ctx->zin.avail_in = zlen; + } + + ctx->zin.next_out = ctx->head; + ctx->zin.avail_out = len; + + /* FIXME: we don't always need Z_SYNC_FLUSH, do we? */ + ret = inflate(&ctx->zin, Z_SYNC_FLUSH); + } + while (ctx->zin.avail_out == len); + + if (ret == Z_OK) + { + ctx->tail = ctx->head + (len - ctx->zin.avail_out); + } + else + { + debug(DEBUG_APPERROR, "cvs_direct: zin: error %d %s", ret, ctx->zin.msg); + exit(1); + } + } + else + { + len = read(ctx->read_fd, ctx->head, len); + ctx->tail = (len <= 0) ? ctx->head : ctx->head + len; + } + + return len; +} + +static int read_line(CvsServerCtx * ctx, char * p) +{ + int len = 0; + while (1) + { + if (ctx->head == ctx->tail) + if (refill_buffer(ctx) <= 0) + return -1; + + *p = *ctx->head++; + + if (*p == '\n') + { + *p = 0; + break; + } + p++; + len++; + } + + return len; +} + +static int read_response(CvsServerCtx * ctx, const char * str) +{ + /* FIXME: more than 1 char at a time */ + char resp[BUFSIZ]; + + if (read_line(ctx, resp) < 0) + return 0; + + debug(DEBUG_TCP, "response '%s' read", resp); + + return (strcmp(resp, str) == 0); +} + +static void ctx_to_fp(CvsServerCtx * ctx, FILE * fp) +{ + char line[BUFSIZ]; + + while (1) + { + read_line(ctx, line); + debug(DEBUG_TCP, "ctx_to_fp: %s", line); + if (memcmp(line, "M ", 2) == 0) + { + if (fp) + fprintf(fp, "%s\n", line + 2); + } + else if (memcmp(line, "E ", 2) == 0) + { + debug(DEBUG_APPMSG1, "%s", line + 2); + } + else if (strncmp(line, "ok", 2) == 0 || strncmp(line, "error", 5) == 0) + { + break; + } + } + + if (fp) + fflush(fp); +} + +void cvs_rdiff(CvsServerCtx * ctx, + const char * rep, const char * file, + const char * rev1, const char * rev2) +{ + /* NOTE: opts are ignored for rdiff, '-u' is always used */ + + send_string(ctx, "Argument -u\n"); + send_string(ctx, "Argument -r\n"); + send_string(ctx, "Argument %s\n", rev1); + send_string(ctx, "Argument -r\n"); + send_string(ctx, "Argument %s\n", rev2); + send_string(ctx, "Argument %s%s\n", rep, file); + send_string(ctx, "rdiff\n"); + + ctx_to_fp(ctx, stdout); +} + +void cvs_rupdate(CvsServerCtx * ctx, const char * rep, const char * file, const char * rev, int create, const char * opts) +{ + FILE * fp; + char cmdbuff[BUFSIZ]; + + snprintf(cmdbuff, BUFSIZ, "diff %s %s /dev/null %s | sed -e '%s s|^\\([+-][+-][+-]\\) -|\\1 %s/%s|g'", + opts, create?"":"-", create?"-":"", create?"2":"1", rep, file); + + debug(DEBUG_TCP, "cmdbuff: %s", cmdbuff); + + if (!(fp = popen(cmdbuff, "w"))) + { + debug(DEBUG_APPERROR, "cvs_direct: popen for diff failed: %s", cmdbuff); + exit(1); + } + + send_string(ctx, "Argument -p\n"); + send_string(ctx, "Argument -r\n"); + send_string(ctx, "Argument %s\n", rev); + send_string(ctx, "Argument %s/%s\n", rep, file); + send_string(ctx, "co\n"); + + ctx_to_fp(ctx, fp); + + pclose(fp); +} + +static int parse_patch_arg(char * arg, char ** str) +{ + char *tok, *tok2 = ""; + tok = strsep(str, " "); + if (!tok) + return 0; + + if (!*tok == '-') + { + debug(DEBUG_APPERROR, "diff_opts parse error: no '-' starting argument: %s", *str); + return 0; + } + + /* if it's not 'long format' argument, we can process it efficiently */ + if (tok[1] == '-') + { + debug(DEBUG_APPERROR, "diff_opts parse_error: long format args not supported"); + return 0; + } + + /* see if command wants two args and they're separated by ' ' */ + if (tok[2] == 0 && strchr("BdDFgiorVxYz", tok[1])) + { + tok2 = strsep(str, " "); + if (!tok2) + { + debug(DEBUG_APPERROR, "diff_opts parse_error: argument %s requires two arguments", tok); + return 0; + } + } + + snprintf(arg, 32, "%s%s", tok, tok2); + return 1; +} + +void cvs_diff(CvsServerCtx * ctx, + const char * rep, const char * file, + const char * rev1, const char * rev2, const char * opts) +{ + char argstr[BUFSIZ], *p = argstr; + char arg[32]; + char file_buff[PATH_MAX], *basename; + + strzncpy(argstr, opts, BUFSIZ); + while (parse_patch_arg(arg, &p)) + send_string(ctx, "Argument %s\n", arg); + + send_string(ctx, "Argument -r\n"); + send_string(ctx, "Argument %s\n", rev1); + send_string(ctx, "Argument -r\n"); + send_string(ctx, "Argument %s\n", rev2); + + /* + * we need to separate the 'basename' of file in order to + * generate the Directory directive(s) + */ + strzncpy(file_buff, file, PATH_MAX); + if ((basename = strrchr(file_buff, '/'))) + { + *basename = 0; + send_string(ctx, "Directory %s/%s\n", rep, file_buff); + send_string(ctx, "%s/%s/%s\n", ctx->root, rep, file_buff); + } + else + { + send_string(ctx, "Directory %s\n", rep, file_buff); + send_string(ctx, "%s/%s\n", ctx->root, rep); + } + + send_string(ctx, "Directory .\n"); + send_string(ctx, "%s\n", ctx->root); + send_string(ctx, "Argument %s/%s\n", rep, file); + send_string(ctx, "diff\n"); + + ctx_to_fp(ctx, stdout); +} + +/* + * FIXME: the design of this sucks. It was originally designed to fork a subprocess + * which read the cvs response and send it back through a pipe the main process, + * which fdopen(3)ed the other end, and juts used regular fgets. This however + * didn't work because the reads of compressed data in the child process altered + * the compression state, and there was no way to resynchronize that state with + * the parent process. We could use threads... + */ +FILE * cvs_rlog_open(CvsServerCtx * ctx, const char * rep, const char * date_str) +{ + /* note: use of the date_str is handled in a non-standard, cvsps specific way */ + if (date_str && date_str[0]) + { + send_string(ctx, "Argument -d\n", rep); + send_string(ctx, "Argument %s<1 Jan 2038 05:00:00 -0000\n", date_str); + send_string(ctx, "Argument -d\n", rep); + send_string(ctx, "Argument %s\n", date_str); + } + + send_string(ctx, "Argument %s\n", rep); + send_string(ctx, "rlog\n"); + + /* + * FIXME: is it possible to create a 'fake' FILE * whose 'refill' + * function is below? + */ + return (FILE*)ctx; +} + +char * cvs_rlog_fgets(char * buff, int buflen, CvsServerCtx * ctx) +{ + char lbuff[BUFSIZ]; + int len; + + len = read_line(ctx, lbuff); + debug(DEBUG_TCP, "cvs_direct: rlog: read %s", lbuff); + + if (memcmp(lbuff, "M ", 2) == 0) + { + memcpy(buff, lbuff + 2, len - 2); + buff[len - 2 ] = '\n'; + buff[len - 1 ] = 0; + } + else if (memcmp(lbuff, "E ", 2) == 0) + { + debug(DEBUG_APPMSG1, "%s", lbuff + 2); + } + else if (strcmp(lbuff, "ok") == 0 ||strcmp(lbuff, "error") == 0) + { + debug(DEBUG_TCP, "cvs_direct: rlog: got command completion"); + return NULL; + } + + return buff; +} + +void cvs_rlog_close(CvsServerCtx * ctx) +{ +} + +void cvs_version(CvsServerCtx * ctx, char * client_version, char * server_version) +{ + char lbuff[BUFSIZ]; + strcpy(client_version, "Client: Concurrent Versions System (CVS) 99.99.99 (client/server) cvs-direct"); + send_string(ctx, "version\n"); + read_line(ctx, lbuff); + if (memcmp(lbuff, "M ", 2) == 0) + sprintf(server_version, "Server: %s", lbuff + 2); + else + debug(DEBUG_APPERROR, "cvs_direct: didn't read version: %s", lbuff); + + read_line(ctx, lbuff); + if (strcmp(lbuff, "ok") != 0) + debug(DEBUG_APPERROR, "cvs_direct: protocol error reading version"); + + debug(DEBUG_TCP, "cvs_direct: client version %s", client_version); + debug(DEBUG_TCP, "cvs_direct: server version %s", server_version); +} === modified file '.pc/applied-patches' --- .pc/applied-patches 2011-04-07 23:18:19 +0000 +++ .pc/applied-patches 2015-02-15 22:42:42 +0000 @@ -2,3 +2,4 @@ 02_dynamicbufferalloc.patch 03_diffoptstypo.patch 05-inet_addr_fix.patch +06-discard-extra-version-lines.patch === modified file 'cvs_direct.c' --- cvs_direct.c 2005-07-28 13:32:58 +0000 +++ cvs_direct.c 2015-02-15 22:42:42 +0000 @@ -916,7 +916,8 @@ else debug(DEBUG_APPERROR, "cvs_direct: didn't read version: %s", lbuff); - read_line(ctx, lbuff); + while (strncmp(lbuff, "M ", 2) == 0) + read_line(ctx, lbuff); if (strcmp(lbuff, "ok") != 0) debug(DEBUG_APPERROR, "cvs_direct: protocol error reading version"); === modified file 'debian/changelog' --- debian/changelog 2012-10-01 20:57:26 +0000 +++ debian/changelog 2015-02-15 22:43:49 +0000 @@ -1,3 +1,10 @@ +cvsps (2.1-6ubuntu0.14.04.1) trusty; urgency=medium + + * Discard extra "M" response lines when reading the server's + version. (LP: #1413084) + + -- Richard Hansen Wed, 11 Feb 2015 12:13:52 -0800 + cvsps (2.1-6build1) quantal; urgency=low * Rebuild for new armel compiler default of ARMv5t. === modified file 'debian/control' --- debian/control 2011-04-07 23:18:19 +0000 +++ debian/control 2015-02-15 22:42:42 +0000 @@ -1,7 +1,8 @@ Source: cvsps Section: devel Priority: optional -Maintainer: Debian QA Group +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Debian QA Group Build-Depends: debhelper (>= 7.0.50~), zlib1g-dev Standards-Version: 3.8.0 === added file 'debian/patches/06-discard-extra-version-lines.patch' --- debian/patches/06-discard-extra-version-lines.patch 1970-01-01 00:00:00 +0000 +++ debian/patches/06-discard-extra-version-lines.patch 2015-02-15 22:42:42 +0000 @@ -0,0 +1,27 @@ +Subject: Discard extra "M" lines in response to "version" +From: Richard Hansen +Bug-Ubuntu: https://bugs.launchpad.net/bugs/1413084 +Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=775883 + +Some CVS servers print more than one "M" line in response to a +"version" command. For example: + +Client: version +Server: M Concurrent Versions System (CVS) 1.12.13 (client/server) +Server: M with CVSACL Patch 1.2.5 (cvsacl.sourceforge.net) +Server: ok + +This patch causes cvsps to consume all such lines rather than fail. + +--- a/cvs_direct.c ++++ b/cvs_direct.c +@@ -916,7 +916,8 @@ + else + debug(DEBUG_APPERROR, "cvs_direct: didn't read version: %s", lbuff); + +- read_line(ctx, lbuff); ++ while (strncmp(lbuff, "M ", 2) == 0) ++ read_line(ctx, lbuff); + if (strcmp(lbuff, "ok") != 0) + debug(DEBUG_APPERROR, "cvs_direct: protocol error reading version"); + === modified file 'debian/patches/series' --- debian/patches/series 2011-04-07 23:18:19 +0000 +++ debian/patches/series 2015-02-15 22:42:42 +0000 @@ -2,3 +2,4 @@ 02_dynamicbufferalloc.patch 03_diffoptstypo.patch 05-inet_addr_fix.patch +06-discard-extra-version-lines.patch