diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c862af8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +cvsps diff --git a/cvs_direct.c b/cvs_direct.c index 920487d..c21a55c 100644 --- a/cvs_direct.c +++ b/cvs_direct.c @@ -3,6 +3,7 @@ * See COPYING file for license information */ +#include #include #include #include @@ -126,7 +127,7 @@ CvsServerCtx * open_cvs_server(char * p_root, int compress) 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-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 LOGM\n", ctx->root); send_string(ctx, "valid-requests\n"); @@ -853,8 +854,19 @@ void cvs_diff(CvsServerCtx * ctx, * 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) +CvsRlog * cvs_rlog_open(CvsServerCtx * ctx, const char * rep, const char * date_str) { + CvsRlog * cvsrlog; + + cvsrlog = calloc(1, sizeof(*cvsrlog)); + if (!cvsrlog) + { + debug(DEBUG_SYSERROR, "cvs_rlog_open: calloc failed"); + exit(1); + } + cvsrlog->csctx = ctx; + cvsrlog->flags = CRLOGF_CVSDIRECT; + /* note: use of the date_str is handled in a non-standard, cvsps specific way */ if (date_str && date_str[0]) { @@ -871,22 +883,39 @@ FILE * cvs_rlog_open(CvsServerCtx * ctx, const char * rep, const char * date_str * FIXME: is it possible to create a 'fake' FILE * whose 'refill' * function is below? */ - return (FILE*)ctx; + return cvsrlog; } -char * cvs_rlog_fgets(char * buff, int buflen, CvsServerCtx * ctx) +char * cvs_rlog_fgets(char * buff, int buflen, CvsRlog * cvsrlog) { char lbuff[BUFSIZ]; - int len; + int n, len; + + assert(cvsrlog->flags & CRLOGF_CVSDIRECT); - len = read_line(ctx, lbuff); + len = read_line(cvsrlog->csctx, lbuff); debug(DEBUG_TCP, "cvs_direct: rlog: read %s", lbuff); - if (memcmp(lbuff, "M ", 2) == 0) + if (memcmp(lbuff, "M ", 2) == 0 || memcmp(lbuff, "LOGM ", 5) == 0) { - memcpy(buff, lbuff + 2, len - 2); - buff[len - 2 ] = '\n'; - buff[len - 1 ] = 0; + if ('L' == lbuff[0]) + { + n = 5; + CRLOG_SET_LOGM(cvsrlog); + } + else + { + n = 2; + CRLOG_CLR_LOGM(cvsrlog); + } + if (buflen < len - n) { + fprintf(stderr, "****WARNING**** rlog buffer len(=%d) > " + "buflen(=%d)\n", len - n, buflen); + len = buflen; + } + memcpy(buff, lbuff + n, len - n); + buff[len - n ] = '\n'; + buff[len - n + 1 ] = 0; } else if (memcmp(lbuff, "E ", 2) == 0) { @@ -901,8 +930,10 @@ char * cvs_rlog_fgets(char * buff, int buflen, CvsServerCtx * ctx) return buff; } -void cvs_rlog_close(CvsServerCtx * ctx) +void cvs_rlog_close(CvsRlog * cvsrlog) { + assert(cvsrlog->flags & CRLOGF_CVSDIRECT); + free(cvsrlog); } void cvs_version(CvsServerCtx * ctx, char * client_version, char * server_version) diff --git a/cvs_direct.h b/cvs_direct.h index 52a81a3..0f678e9 100644 --- a/cvs_direct.h +++ b/cvs_direct.h @@ -11,14 +11,37 @@ typedef struct _CvsServerCtx CvsServerCtx; #endif +struct _CvsRlog +{ + unsigned int flags; + #define CRLOGF_CAP_LOGM 0x01U /* set on first LOGM read */ + #define CRLOGF_READ_LOGM 0x02U /* flags current read as LOGM */ + #define CRLOGF_CVSDIRECT 0x04U + + /* if CVSDIRECT */ + CvsServerCtx * csctx; + /* else application FILE used and application managed */ + FILE * fp; +}; +/* private */ +#define CRLOG_SET_LOGM(x) ((x)->flags |= CRLOGF_CAP_LOGM|CRLOGF_READ_LOGM) +#define CRLOG_CLR_LOGM(x) ((x)->flags &= ~CRLOGF_READ_LOGM) +/* public */ +#define CRLOG_HAS_LOGM(x) ((x)->flags & CRLOGF_CAP_LOGM) +#define CRLOG_IS_LOGM(x) ((x)->flags & CRLOGF_READ_LOGM) + +typedef struct _CvsRlog CvsRlog; + CvsServerCtx * open_cvs_server(char * root, int); void close_cvs_server(CvsServerCtx*); void cvs_rdiff(CvsServerCtx *, const char *, const char *, const char *, const char *); void cvs_rupdate(CvsServerCtx *, const char *, const char *, const char *, int, const char *); void cvs_diff(CvsServerCtx *, const char *, const char *, const char *, const char *, const char *); -FILE * cvs_rlog_open(CvsServerCtx *, const char *, const char *); -char * cvs_rlog_fgets(char *, int, CvsServerCtx *); -void cvs_rlog_close(CvsServerCtx *); +/* Must call cvs_rlog_close to free CvsRlog object */ +CvsRlog * cvs_rlog_open(CvsServerCtx *, const char *, const char *); +char * cvs_rlog_fgets(char *, int, CvsRlog *); +/* frees CvsRlog object */ +void cvs_rlog_close(CvsRlog *); void cvs_version(CvsServerCtx *, char *, char *); #endif /* CVS_DIRECT_H */ diff --git a/cvsps.c b/cvsps.c index 981cd78..712c6b5 100644 --- a/cvsps.c +++ b/cvsps.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -46,7 +47,8 @@ enum NEED_START_LOG, NEED_REVISION, NEED_DATE_AUTHOR_STATE, - NEED_EOM + NEED_EOM, + NEED_LOGM }; /* true globals */ @@ -148,6 +150,9 @@ static CvsFileRevision * rev_follow_branch(CvsFileRevision *, const char *); static int before_tag(CvsFileRevision * rev, const char * tag); static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps); static void handle_collisions(); +static int count_loglines(PatchSet *); +static size_t append_logbuff(char **_logbuff, size_t *_logbuffsz, + size_t _loglen, char * _buff); int main(int argc, char *argv[]) { @@ -259,29 +264,13 @@ int main(int argc, char *argv[]) exit(0); } -static void load_from_cvs() +static CvsRlog * cvsrlog_open(void) { - FILE * cvsfp; - char buff[BUFSIZ]; - int state = NEED_RCS_FILE; - CvsFile * file = NULL; - PatchSetMember * psm = NULL; - char datebuff[20]; - char authbuff[AUTH_STR_MAX]; - int logbufflen = LOG_STR_MAX + 1; - char * logbuff = malloc(logbufflen); - int loglen = 0; - int have_log = 0; - char cmd[BUFSIZ]; + char * ltype; char date_str[64]; + char cmd[BUFSIZ]; char use_rep_buff[PATH_MAX]; - char * ltype; - - if (logbuff == NULL) - { - debug(DEBUG_SYSERROR, "could not malloc %d bytes for logbuff in load_from_cvs", logbufflen); - exit(1); - } + CvsRlog * cvsrlog; if (!no_rlog && !test_log_file && cvs_check_cap(CAP_HAVE_RLOG)) { @@ -308,43 +297,102 @@ static void load_from_cvs() * which is necessary to fill in the pre_rev stuff for a * PatchSetMember */ - snprintf(cmd, BUFSIZ, "cvs %s %s -q %s -d '%s<;%s' %s", compress_arg, norc, ltype, date_str, date_str, use_rep_buff); + snprintf(cmd, BUFSIZ, "cvs %s %s %s -d '%s<;%s' %s", compress_arg, norc, ltype, date_str, date_str, use_rep_buff); } else { date_str[0] = 0; - snprintf(cmd, BUFSIZ, "cvs %s %s -q %s %s", compress_arg, norc, ltype, use_rep_buff); + snprintf(cmd, BUFSIZ, "cvs %s %s %s %s", compress_arg, norc, ltype, use_rep_buff); } - + debug(DEBUG_STATUS, "******* USING CMD %s", cmd); cache_date = time(NULL); + if (cvs_direct_ctx) + return cvs_rlog_open(cvs_direct_ctx, repository_path, date_str); + + cvsrlog = calloc(1, sizeof(*cvsrlog)); + if (!cvsrlog) + { + debug(DEBUG_SYSERROR, "calloc failed"); + exit(1); + } + /* FIXME: this is ugly, need to virtualize the accesses away from here */ if (test_log_file) - cvsfp = fopen(test_log_file, "r"); - else if (cvs_direct_ctx) - cvsfp = cvs_rlog_open(cvs_direct_ctx, repository_path, date_str); + { + cvsrlog->fp = fopen(test_log_file, "r"); + if (!cvsrlog->fp) + debug(DEBUG_SYSERROR, "can't open log-file: %s", test_log_file); + exit(1); + } else - cvsfp = popen(cmd, "r"); - - if (!cvsfp) { - debug(DEBUG_SYSERROR, "can't open cvs pipe using command %s", cmd); + cvsrlog->fp = popen(cmd, "r"); + if (!cvsrlog->fp) + debug(DEBUG_SYSERROR, "can't open cvs pipe using command %s", cmd); exit(1); } - for (;;) + return cvsrlog; +} + +static void cvsrlog_close(CvsRlog *cvsrlog) +{ + if (cvsrlog->flags & CRLOGF_CVSDIRECT) + return cvs_rlog_close(cvsrlog); + + if (test_log_file) + fclose(cvsrlog->fp); + else { - char * tst; - if (cvs_direct_ctx) - tst = cvs_rlog_fgets(buff, BUFSIZ, cvs_direct_ctx); - else - tst = fgets(buff, BUFSIZ, cvsfp); + if (pclose(cvsrlog->fp) < 0) + { + debug(DEBUG_APPERROR, "cvs rlog command exited with error. aborting"); + exit(1); + } + } + free(cvsrlog); +} - if (!tst) - break; +static char * cvsrlog_fgets(CvsRlog *, char * _buff, int _buflen); +static char * cvsrlog_fgets(CvsRlog * cvsrlog, char * buff, int buflen) +{ + if (cvsrlog->flags & CRLOGF_CVSDIRECT) + return cvs_rlog_fgets(buff, buflen, cvsrlog); + else + return fgets(buff, buflen, cvsrlog->fp); +} + +static void load_from_cvs() +{ + char buff[BUFSIZ]; + int state = NEED_RCS_FILE; + CvsFile * file = NULL; + PatchSetMember * psm = NULL; + char datebuff[20]; + char authbuff[AUTH_STR_MAX]; + size_t logbufflen = LOG_STR_MAX + 1; + char * logbuff; + size_t loglen = 0; + int have_log = 0; + int haslogm = 0, logmstarted = 0; + CvsRlog *cvsrlog; + logbuff = malloc(logbufflen); + if (logbuff == NULL) + { + debug(DEBUG_SYSERROR, "could not malloc %lu bytes for logbuff in load_from_cvs", (unsigned long)logbufflen); + exit(1); + } + logbuff[0] = 0; + buff[0] = 0; + + cvsrlog = cvsrlog_open(); + + while (cvsrlog_fgets(cvsrlog, buff, sizeof(buff))) + { debug(DEBUG_STATUS, "state: %d read line:%s", state, buff); switch(state) @@ -429,7 +477,7 @@ static void load_from_cvs() * of the info (revs and logs) until we hit the next file */ psm = NULL; - state = NEED_EOM; + state = haslogm ? NEED_LOGM : NEED_EOM; } } break; @@ -466,10 +514,18 @@ static void load_from_cvs() psm->post_rev->dead = 1; } - state = NEED_EOM; + state = haslogm ? NEED_LOGM : NEED_EOM; } break; case NEED_EOM: + if (!haslogm && CRLOG_HAS_LOGM(cvsrlog)) + { + /* First encounter of LOGM, switch processing. */ + haslogm = 1; + state = NEED_LOGM; + goto dologm; + } + dobounds: if (strcmp(buff, CVS_LOG_BOUNDARY) == 0) { if (psm) @@ -503,40 +559,44 @@ static void load_from_cvs() { /* other "blahblah: information;" messages can * follow the stuff we pay attention to + * + * If server supports LOGM, do not append to logbuff here. */ - if (have_log || !is_revision_metadata(buff)) + if (!haslogm && (have_log || !is_revision_metadata(buff))) { - /* If the log buffer is full, try to reallocate more. */ - if (loglen < logbufflen) - { - int len = strlen(buff); - - if (len >= logbufflen - loglen) - { - debug(DEBUG_STATUS, "reallocating logbufflen to %d bytes for file %s", logbufflen, file->filename); - logbufflen += (len >= LOG_STR_MAX ? (len+1) : LOG_STR_MAX); - char * newlogbuff = realloc(logbuff, logbufflen); - if (newlogbuff == NULL) - { - debug(DEBUG_SYSERROR, "could not realloc %d bytes for logbuff in load_from_cvs", logbufflen); - exit(1); - } - logbuff = newlogbuff; - } - - debug(DEBUG_STATUS, "appending %s to log", buff); - memcpy(logbuff + loglen, buff, len); - loglen += len; - logbuff[loglen] = 0; - have_log = 1; - } + loglen = append_logbuff(&logbuff, &logbufflen, loglen, buff); + have_log = 1; } else { debug(DEBUG_STATUS, "ignoring unhandled info %s", buff); } } + break; + case NEED_LOGM: + dologm: + if (!logmstarted) + { + if (!CRLOG_IS_LOGM(cvsrlog)) + break; + logmstarted = 1; + logbuff[0] = 0; + loglen = 0; + } + else if (!CRLOG_IS_LOGM(cvsrlog)) + { + /* + * Just passed the last LOGM line. continue at EOM for + * revision/file boundary parsing. + */ + logmstarted = 0; + state = NEED_EOM; + goto dobounds; + break; + } + + loglen = append_logbuff(&logbuff, &logbufflen, loglen, buff); break; } } @@ -553,23 +613,73 @@ static void load_from_cvs() debug(DEBUG_APPERROR, "Error: Log file parsing error. (%d) Use -v to debug", state); exit(1); } - - if (test_log_file) - { - fclose(cvsfp); - } - else if (cvs_direct_ctx) + + cvsrlog_close(cvsrlog); + free(logbuff); +} + +static size_t append_logbuff(char **logbuff, size_t *logbuffsz, size_t loglen, + char * buff) +{ + size_t len, newsz; + + /* + * Also, read lines (fgets) always have \n in them + * which we count on. So if truncation happens, + * be careful to put a \n on. + * + * Buffer has LOG_STR_MAX + 1 for room for \0 if + * necessary + */ + len = strlen(buff); + if (buff[len - 1] == '\n') + len--; + + /* + * Ensure some reasonable size; e.g., 50 lines worth of 80 col of text + * + * Calls to realloc(3) here are "bad form", however, since the code + * exists on failure, it is OK. + */ + if (*logbuffsz < (80 * 50)) { - cvs_rlog_close(cvs_direct_ctx); + *logbuffsz = 80 * 50; + *logbuff = realloc(*logbuff, *logbuffsz); + if (*logbuff == NULL) + { + debug(DEBUG_SYSERROR, "could not realloc %lu bytes for logbuff", (unsigned long)*logbuffsz); + exit(1); + } } - else + + if (*logbuffsz - 2 - loglen < len) { - if (pclose(cvsfp) < 0) + if (SIZE_MAX - 2 - loglen < len) { - debug(DEBUG_APPERROR, "cvs rlog command exited with error. aborting"); + debug(DEBUG_APPMSG1, "WARNING: maximum log buffer size exceeded, truncating log"); + len = SIZE_MAX - 2 - *logbuffsz - loglen; + + /* however unlikely */ + if (len == 0) + return loglen; + } + + *logbuffsz = loglen + len + 2; + *logbuff = realloc(*logbuff, *logbuffsz); + if (*logbuff == NULL) + { + debug(DEBUG_SYSERROR, "could not realloc %lu bytes for logbuff", (unsigned long)*logbuffsz); exit(1); } } + + debug(DEBUG_STATUS, "appending %s to log", buff); + memcpy(*logbuff + loglen, buff, len); + loglen += len; + (*logbuff)[loglen] = '\n'; + (*logbuff)[loglen + 1] = 0; + + return loglen + 1; } static int usage(const char * str1, const char * str2) @@ -1478,6 +1588,24 @@ static void check_print_patch_set(PatchSet * ps) fflush(stdout); } +static int count_loglines(PatchSet * ps) +{ + char *p; + int loglines; + + if (!ps->descr) + return 0; + + loglines = 0; + p = ps->descr; + while (*p) + { + if (*p++ == '\n') + loglines++; + } + return loglines; +} + static void print_patch_set(PatchSet * ps) { struct tm *tm; @@ -1500,7 +1628,7 @@ static void print_patch_set(PatchSet * ps) if (ps->ancestor_branch) printf("Ancestor branch: %s\n", ps->ancestor_branch); printf("Tag: %s %s\n", ps->tag ? ps->tag : "(none)", tag_flag_descr[ps->tag_flags]); - printf("Log:\n%s\n", ps->descr); + printf("Log: %d\n%s\n", count_loglines(ps), ps->descr); printf("Members: \n"); while (next != &ps->members)