diff options
Diffstat (limited to 'contrib/cvs/src/rcs.c')
-rw-r--r-- | contrib/cvs/src/rcs.c | 2449 |
1 files changed, 1793 insertions, 656 deletions
diff --git a/contrib/cvs/src/rcs.c b/contrib/cvs/src/rcs.c index 3400027..3eb3eaf 100644 --- a/contrib/cvs/src/rcs.c +++ b/contrib/cvs/src/rcs.c @@ -11,6 +11,9 @@ #include <assert.h> #include "cvs.h" #include "edit.h" +#include "hardlink.h" + +int preserve_perms = 0; /* The RCS -k options, and a set of enums that must match the array. These come first so that we can use enum kflag in function @@ -19,11 +22,57 @@ static const char *const kflags[] = {"kv", "kvl", "k", "v", "o", "b", (char *) NULL}; enum kflag { KFLAG_KV = 0, KFLAG_KVL, KFLAG_K, KFLAG_V, KFLAG_O, KFLAG_B }; +/* A structure we use to buffer the contents of an RCS file. The + various fields are only referenced directly by the rcsbuf_* + functions. We declare the struct here so that we can allocate it + on the stack, rather than in memory. */ + +struct rcsbuffer +{ + /* Points to the current position in the buffer. */ + char *ptr; + /* Points just after the last valid character in the buffer. */ + char *ptrend; + /* The file. */ + FILE *fp; + /* The name of the file, used for error messages. */ + const char *filename; + /* The starting file position of the data in the buffer. */ + unsigned long pos; + /* The length of the value. */ + size_t vlen; + /* Whether the value contains an '@' string. If so, we can not + compress whitespace characters. */ + int at_string; + /* The number of embedded '@' characters in an '@' string. If + this is non-zero, we must search the string for pairs of '@' + and convert them to a single '@'. */ + int embedded_at; +}; + static RCSNode *RCS_parsercsfile_i PROTO((FILE * fp, const char *rcsfile)); static char *RCS_getdatebranch PROTO((RCSNode * rcs, char *date, char *branch)); -static int getrcskey PROTO((FILE * fp, char **keyp, char **valp, - size_t *lenp)); -static void getrcsrev PROTO ((FILE *fp, char **revp)); +static void rcsbuf_open PROTO ((struct rcsbuffer *, FILE *fp, + const char *filename, unsigned long pos)); +static void rcsbuf_close PROTO ((struct rcsbuffer *)); +static int rcsbuf_getkey PROTO ((struct rcsbuffer *, char **keyp, + char **valp)); +static int rcsbuf_getrevnum PROTO ((struct rcsbuffer *, char **revp)); +static char *rcsbuf_fill PROTO ((struct rcsbuffer *, char *ptr, char **keyp, + char **valp)); +static char *rcsbuf_valcopy PROTO ((struct rcsbuffer *, char *val, int polish, + size_t *lenp)); +static void rcsbuf_valpolish PROTO ((struct rcsbuffer *, char *val, int polish, + size_t *lenp)); +static void rcsbuf_valpolish_internal PROTO ((struct rcsbuffer *, char *to, + const char *from, size_t *lenp)); +static unsigned long rcsbuf_ftell PROTO ((struct rcsbuffer *)); +static void rcsbuf_get_buffered PROTO ((struct rcsbuffer *, char **datap, + size_t *lenp)); +static void rcsbuf_cache PROTO ((RCSNode *, struct rcsbuffer *)); +static void rcsbuf_cache_close PROTO ((void)); +static void rcsbuf_cache_open PROTO ((RCSNode *, long, FILE **, + struct rcsbuffer *)); static int checkmagic_proc PROTO((Node *p, void *closure)); static void do_branches PROTO((List * list, char *val)); static void do_symbols PROTO((List * list, char *val)); @@ -40,12 +89,15 @@ static void expand_keywords PROTO((RCSNode *, RCSVers *, const char *, static void cmp_file_buffer PROTO((void *, const char *, size_t)); enum rcs_delta_op {RCS_ANNOTATE, RCS_FETCH}; -static void RCS_deltas PROTO ((RCSNode *, FILE *, char *, enum rcs_delta_op, - char **, size_t *, char **, size_t *)); +static void RCS_deltas PROTO ((RCSNode *, FILE *, struct rcsbuffer *, char *, + enum rcs_delta_op, char **, size_t *, + char **, size_t *)); /* Routines for reading, parsing and writing RCS files. */ -static RCSVers *getdelta PROTO ((FILE *, char *)); -static Deltatext *RCS_getdeltatext PROTO ((RCSNode *, FILE *)); +static RCSVers *getdelta PROTO ((struct rcsbuffer *, char *, char **, + char **)); +static Deltatext *RCS_getdeltatext PROTO ((RCSNode *, FILE *, + struct rcsbuffer *)); static void freedeltatext PROTO ((Deltatext *)); static void RCS_putadmin PROTO ((RCSNode *, FILE *)); @@ -54,13 +106,21 @@ static void RCS_putdesc PROTO ((RCSNode *, FILE *)); static void putdelta PROTO ((RCSVers *, FILE *)); static int putrcsfield_proc PROTO ((Node *, void *)); static int putsymbol_proc PROTO ((Node *, void *)); -static void RCS_copydeltas PROTO ((RCSNode *, FILE *, FILE *, Deltatext *, char *)); +static void RCS_copydeltas PROTO ((RCSNode *, FILE *, struct rcsbuffer *, + FILE *, Deltatext *, char *)); +static int count_delta_actions PROTO ((Node *, void *)); static void putdeltatext PROTO ((FILE *, Deltatext *)); static FILE *rcs_internal_lockfile PROTO ((char *)); static void rcs_internal_unlockfile PROTO ((FILE *, char *)); static char *rcs_lockfilename PROTO ((char *)); +/* The RCS file reading functions are called a lot, and they do some + string comparisons. This macro speeds things up a bit by skipping + the function call when the first characters are different. It + evaluates its arguments multiple times. */ +#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp ((a), (b)) == 0) + static char * getfullCVSname PROTO ((char *, char **)); /* @@ -92,7 +152,6 @@ static const char spacetab[] = { #define whitespace(c) (spacetab[(unsigned char)c] != 0) - /* Parse an rcsfile given a user file name and a repository. If there is an error, we print an error message and return NULL. If the file does not exist, we return NULL without printing anything (I'm not @@ -108,6 +167,10 @@ RCS_parse (file, repos) RCSNode *retval; char *rcsfile; + /* We're creating a new RCSNode, so there is no hope of finding it + in the cache. */ + rcsbuf_cache_close (); + rcsfile = xmalloc (strlen (repos) + strlen (file) + sizeof (RCSEXT) + sizeof (CVSATTIC) + 10); (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT); @@ -117,7 +180,6 @@ RCS_parse (file, repos) if (rcs != NULL) rcs->flags |= VALID; - fclose (fp); retval = rcs; goto out; } @@ -138,7 +200,6 @@ RCS_parse (file, repos) rcs->flags |= VALID; } - fclose (fp); retval = rcs; goto out; } @@ -167,7 +228,6 @@ RCS_parse (file, repos) if (rcs != NULL) rcs->flags |= VALID; - fclose (fp); free (rcs->path); rcs->path = found_path; retval = rcs; @@ -191,7 +251,6 @@ RCS_parse (file, repos) rcs->flags |= VALID; } - fclose (fp); free (rcs->path); rcs->path = found_path; retval = rcs; @@ -223,6 +282,10 @@ RCS_parsercsfile (rcsfile) FILE *fp; RCSNode *rcs; + /* We're creating a new RCSNode, so there is no hope of finding it + in the cache. */ + rcsbuf_cache_close (); + /* open the rcsfile */ if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) == NULL) { @@ -232,7 +295,6 @@ RCS_parsercsfile (rcsfile) rcs = RCS_parsercsfile_i (fp, rcsfile); - fclose (fp); return (rcs); } @@ -245,6 +307,7 @@ RCS_parsercsfile_i (fp, rcsfile) const char *rcsfile; { RCSNode *rdata; + struct rcsbuffer rcsbuf; char *key, *value; /* make a node */ @@ -253,40 +316,32 @@ RCS_parsercsfile_i (fp, rcsfile) rdata->refcount = 1; rdata->path = xstrdup (rcsfile); - /* Process HEAD and BRANCH keywords from the RCS header. + /* Process HEAD, BRANCH, and EXPAND keywords from the RCS header. Most cvs operations on the main branch don't need any more information. Those that do call RCS_reparsercsfile to parse - the rest of the header and the deltas. - - People often wonder whether this is inefficient, to open the - file once here and once in RCS_reparsercsfile. Well, it might - help a little bit if we kept the file open (I haven't tried - timing this myself), but basically the common case, which we - want to optimize, is the one in which we call - RCS_parsercsfile_i and not RCS_reparsercsfile (for example, - "cvs update" on a lot of files most of which are unmodified). - So making the case in which we call RCS_reparsercsfile fast is - not as important. */ - - if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL) + the rest of the header and the deltas. */ + + rcsbuf_open (&rcsbuf, fp, rcsfile, 0); + + if (! rcsbuf_getkey (&rcsbuf, &key, &value)) goto l_error; - if (strcmp (key, RCSDESC) == 0) + if (STREQ (key, RCSDESC)) goto l_error; - if (strcmp (RCSHEAD, key) == 0 && value != NULL) - rdata->head = xstrdup (value); + if (STREQ (RCSHEAD, key) && value != NULL) + rdata->head = rcsbuf_valcopy (&rcsbuf, value, 0, (size_t *) NULL); - if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL) + if (! rcsbuf_getkey (&rcsbuf, &key, &value)) goto l_error; - if (strcmp (key, RCSDESC) == 0) + if (STREQ (key, RCSDESC)) goto l_error; - if (strcmp (RCSBRANCH, key) == 0 && value != NULL) + if (STREQ (RCSBRANCH, key) && value != NULL) { char *cp; - rdata->branch = xstrdup (value); + rdata->branch = rcsbuf_valcopy (&rcsbuf, value, 0, (size_t *) NULL); if ((numdots (rdata->branch) & 1) != 0) { /* turn it into a branch if it's a revision */ @@ -295,23 +350,43 @@ RCS_parsercsfile_i (fp, rcsfile) } } - rdata->flags |= PARTIAL; - return rdata; - -l_error: - if (!really_quiet) + /* Look ahead for expand, stopping when we see desc or a revision + number. */ + while (1) { - if (ferror(fp)) - { - error (1, 0, "error reading `%s'", rcsfile); - } - else + char *cp; + + if (STREQ (RCSEXPAND, key)) { - error (0, 0, "`%s' does not appear to be a valid rcs file", - rcsfile); + rdata->expand = rcsbuf_valcopy (&rcsbuf, value, 0, + (size_t *) NULL); + break; } + + for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) + /* do nothing */ ; + if (*cp == '\0') + break; + + if (STREQ (RCSDESC, key)) + break; + + if (! rcsbuf_getkey (&rcsbuf, &key, &value)) + break; } + + rdata->flags |= PARTIAL; + + rcsbuf_cache (rdata, &rcsbuf); + + return rdata; + +l_error: + error (0, 0, "`%s' does not appear to be a valid rcs file", + rcsfile); + rcsbuf_close (&rcsbuf); freercsnode (&rdata); + fclose (fp); return (NULL); } @@ -323,25 +398,24 @@ l_error: If PFP is NULL, close the file when done. Otherwise, leave it open and store the FILE * in *PFP. */ void -RCS_reparsercsfile (rdata, pfp) +RCS_reparsercsfile (rdata, pfp, rcsbufp) RCSNode *rdata; FILE **pfp; + struct rcsbuffer *rcsbufp; { FILE *fp; char *rcsfile; - + struct rcsbuffer rcsbuf; Node *q, *kv; RCSVers *vnode; - long fpos; + int gotkey; char *cp; char *key, *value; assert (rdata != NULL); rcsfile = rdata->path; - fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ); - if (fp == NULL) - error (1, 0, "unable to reopen `%s'", rcsfile); + rcsbuf_cache_open (rdata, 0, &fp, &rcsbuf); /* make a node */ /* This probably shouldn't be done until later: if a file has an @@ -353,66 +427,68 @@ RCS_reparsercsfile (rdata, pfp) * process all the special header information, break out when we get to * the first revision delta */ + gotkey = 0; for (;;) { - fpos = ftell (fp); - /* get the next key/value pair */ - - /* if key is NULL here, then the file is missing some headers - or we had trouble reading the file. */ - if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL) + if (!gotkey) { - if (ferror(fp)) - { - error (1, 0, "error reading `%s'", rcsfile); - } - else + if (! rcsbuf_getkey (&rcsbuf, &key, &value)) { error (1, 0, "`%s' does not appear to be a valid rcs file", rcsfile); } } - /* Skip head and branch tags; we already have them. */ - if (strcmp (key, RCSHEAD) == 0 || strcmp (key, RCSBRANCH) == 0) + gotkey = 0; + + /* Skip head, branch and expand tags; we already have them. */ + if (STREQ (key, RCSHEAD) + || STREQ (key, RCSBRANCH) + || STREQ (key, RCSEXPAND)) + { continue; + } - if (strcmp (key, "access") == 0) + if (STREQ (key, "access")) { if (value != NULL) - rdata->access = xstrdup (value); + { + /* We pass the POLISH parameter as 1 because + RCS_addaccess expects nothing but spaces. FIXME: + It would be easy and more efficient to change + RCS_addaccess. */ + rdata->access = rcsbuf_valcopy (&rcsbuf, value, 1, + (size_t *) NULL); + } continue; } /* We always save lock information, so that we can handle -kkvl correctly when checking out a file. */ - if (strcmp (key, "locks") == 0) + if (STREQ (key, "locks")) { if (value != NULL) - rdata->locks_data = xstrdup (value); - fpos = ftell (fp); - if (getrcskey (fp, &key, &value, NULL) >= 0 && - strcmp (key, "strict") == 0 && - value == NULL) + rdata->locks_data = rcsbuf_valcopy (&rcsbuf, value, 0, + (size_t *) NULL); + if (! rcsbuf_getkey (&rcsbuf, &key, &value)) + { + error (1, 0, "premature end of file reading %s", rcsfile); + } + if (STREQ (key, "strict") && value == NULL) { rdata->strict_locks = 1; } else - (void) fseek (fp, fpos, SEEK_SET); + gotkey = 1; continue; } - if (strcmp (RCSSYMBOLS, key) == 0) + if (STREQ (RCSSYMBOLS, key)) { if (value != NULL) - rdata->symbols_data = xstrdup(value); - continue; - } - - if (strcmp (RCSEXPAND, key) == 0) - { - rdata->expand = xstrdup (value); + rdata->symbols_data = rcsbuf_valcopy (&rcsbuf, value, 0, + (size_t *) NULL); continue; } @@ -423,15 +499,19 @@ RCS_reparsercsfile (rdata, pfp) */ for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) /* do nothing */ ; - if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0) + /* Note that when comparing with RCSDATE, we are not massaging + VALUE from the string found in the RCS file. This is OK + since we know exactly what to expect. */ + if (*cp == '\0' && strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) == 0) break; - if (strcmp (key, RCSDESC) == 0) + if (STREQ (key, RCSDESC)) break; - if (strcmp (key, "comment") == 0) + if (STREQ (key, "comment")) { - rdata->comment = xstrdup (value); + rdata->comment = rcsbuf_valcopy (&rcsbuf, value, 0, + (size_t *) NULL); continue; } if (rdata->other == NULL) @@ -439,7 +519,7 @@ RCS_reparsercsfile (rdata, pfp) kv = getnode (); kv->type = RCSFIELD; kv->key = xstrdup (key); - kv->data = xstrdup (value); + kv->data = rcsbuf_valcopy (&rcsbuf, value, 1, (size_t *) NULL); if (addnode (rdata->other, kv) != 0) { error (0, 0, "warning: duplicate key `%s' in RCS file `%s'", @@ -450,15 +530,11 @@ RCS_reparsercsfile (rdata, pfp) /* if we haven't grabbed it yet, we didn't want it */ } - /* - * we got out of the loop, so we have the first part of the first - * revision delta in our hand key=the revision and value=the date key and - * its value - */ - /* First, seek back to the start of the delta block. */ - (void) fseek (fp, fpos, SEEK_SET); + /* We got out of the loop, so we have the first part of the first + revision delta in KEY (the revision) and VALUE (the date key + and its value). This is what getdelta expects to receive. */ - while ((vnode = getdelta (fp, rcsfile)) != NULL) + while ((vnode = getdelta (&rcsbuf, rcsfile, &key, &value)) != NULL) { /* get the node */ q = getnode (); @@ -478,8 +554,9 @@ RCS_reparsercsfile (rdata, pfp) } } - (void) getrcskey (fp, &key, &value, NULL); - if (key != NULL && strcmp (key, RCSDESC) == 0) + /* Here KEY and VALUE are whatever caused getdelta to return NULL. */ + + if (STREQ (key, RCSDESC)) { if (rdata->desc != NULL) { @@ -488,19 +565,17 @@ RCS_reparsercsfile (rdata, pfp) key, rcsfile); free (rdata->desc); } - rdata->desc = xstrdup (value); + rdata->desc = rcsbuf_valcopy (&rcsbuf, value, 0, (size_t *) NULL); } - rdata->delta_pos = ftell (fp); + rdata->delta_pos = rcsbuf_ftell (&rcsbuf); if (pfp == NULL) - { - if (fclose (fp) < 0) - error (0, errno, "cannot close %s", rcsfile); - } + rcsbuf_cache (rdata, &rcsbuf); else { *pfp = fp; + *rcsbufp = rcsbuf; } rdata->flags &= ~PARTIAL; } @@ -522,31 +597,21 @@ RCS_fully_parse (rcs) RCSNode *rcs; { FILE *fp; + struct rcsbuffer rcsbuf; - RCS_reparsercsfile (rcs, &fp); + RCS_reparsercsfile (rcs, &fp, &rcsbuf); while (1) { - int c; char *key, *value; - size_t vallen; Node *vers; RCSVers *vnode; /* Rather than try to keep track of how much information we have read, just read to the end of the file. */ - do - { - c = getc (fp); - if (c == EOF) - break; - } while (whitespace (c)); - if (c == EOF) + if (! rcsbuf_getrevnum (&rcsbuf, &key)) break; - if (ungetc (c, fp) == EOF) - error (1, errno, "ungetc failed"); - getrcsrev (fp, &key); vers = findnode (rcs->versions, key); if (vers == NULL) error (1, 0, @@ -555,9 +620,9 @@ RCS_fully_parse (rcs) vnode = (RCSVers *) vers->data; - while (getrcskey (fp, &key, &value, &vallen) >= 0) + while (rcsbuf_getkey (&rcsbuf, &key, &value)) { - if (strcmp (key, "text") != 0) + if (! STREQ (key, "text")) { Node *kv; @@ -566,7 +631,7 @@ RCS_fully_parse (rcs) kv = getnode (); kv->type = RCSFIELD; kv->key = xstrdup (key); - kv->data = xstrdup (value); + kv->data = rcsbuf_valcopy (&rcsbuf, value, 1, (size_t *) NULL); if (addnode (vnode->other, kv) != 0) { error (0, 0, @@ -579,7 +644,7 @@ warning: duplicate key `%s' in version `%s' of RCS file `%s'", continue; } - if (strcmp (vnode->version, rcs->head) != 0) + if (! STREQ (vnode->version, rcs->head)) { unsigned long add, del; char buf[50]; @@ -591,8 +656,10 @@ warning: duplicate key `%s' in version `%s' of RCS file `%s'", del = 0; if (value != NULL) { + size_t vallen; const char *cp; + rcsbuf_valpolish (&rcsbuf, value, 0, &vallen); cp = value; while (cp < value + vallen) { @@ -672,8 +739,7 @@ warning: duplicate key `%s' in version `%s' of RCS file `%s'", } } - if (fclose (fp) < 0) - error (0, errno, "cannot close %s", rcs->path); + rcsbuf_cache (rcs, &rcsbuf); } /* @@ -768,353 +834,877 @@ rcsvers_delproc (p) { free_rcsvers_contents ((RCSVers *) p->data); } + +/* These functions retrieve keys and values from an RCS file using a + buffer. We use this somewhat complex approach because it turns out + that for many common operations, CVS spends most of its time + reading keys, so it's worth doing some fairly hairy optimization. */ -/* - * getrcskey - fill in the key and value from the rcs file the algorithm is - * as follows - * - * o skip whitespace - * o fill in key with everything up to next white - * space or semicolon - * o if key == "desc" then key and data are NULL and return -1 - * o if key wasn't terminated by a semicolon, skip white space and fill - * in value with everything up to a semicolon - * o compress all whitespace down to a single space - * o if a word starts with @, do funky rcs processing - * o strip whitespace off end of value or set value to NULL if it empty - * o return 0 since we found something besides "desc" - * - * Sets *KEYP and *VALUEP to point to storage managed by the getrcskey - * function; the contents are only valid until the next call to - * getrcskey or getrcsrev. If LENP is not NULL, this sets *LENP to - * the length of *VALUEP; this is needed if the string might contain - * binary data. - */ +/* The number of bytes we try to read each time we need more data. */ -static char *key = NULL; -static char *value = NULL; -static size_t keysize = 0; -static size_t valsize = 0; +#define RCSBUF_BUFSIZE (8192) -static int -getrcskey (fp, keyp, valp, lenp) +/* The buffer we use to store data. This grows as needed. */ + +static char *rcsbuf_buffer = NULL; +static size_t rcsbuf_buffer_size = 0; + +/* Whether rcsbuf_buffer is in use. This is used as a sanity check. */ + +static int rcsbuf_inuse; + +/* Set up to start gathering keys and values from an RCS file. This + initializes RCSBUF. */ + +static void +rcsbuf_open (rcsbuf, fp, filename, pos) + struct rcsbuffer *rcsbuf; FILE *fp; + const char *filename; + unsigned long pos; +{ + if (rcsbuf_inuse) + error (1, 0, "rcsbuf_open: internal error"); + rcsbuf_inuse = 1; + + if (rcsbuf_buffer_size < RCSBUF_BUFSIZE) + expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size, RCSBUF_BUFSIZE); + + rcsbuf->ptr = rcsbuf_buffer; + rcsbuf->ptrend = rcsbuf_buffer; + rcsbuf->fp = fp; + rcsbuf->filename = filename; + rcsbuf->pos = pos; + rcsbuf->vlen = 0; + rcsbuf->at_string = 0; + rcsbuf->embedded_at = 0; +} + +/* Stop gathering keys from an RCS file. */ + +static void +rcsbuf_close (rcsbuf) + struct rcsbuffer *rcsbuf; +{ + if (! rcsbuf_inuse) + error (1, 0, "rcsbuf_close: internal error"); + rcsbuf_inuse = 0; +} + +/* Read a key/value pair from an RCS file. This sets *KEYP to point + to the key, and *VALUEP to point to the value. A missing or empty + value is indicated by setting *VALUEP to NULL. + + This function returns 1 on success, or 0 on EOF. If there is an + error reading the file, or an EOF in an unexpected location, it + gives a fatal error. + + This sets *KEYP and *VALUEP to point to storage managed by + rcsbuf_getkey. Moreover, *VALUEP has not been massaged from the + RCS format: it may contain embedded whitespace and embedded '@' + characters. Call rcsbuf_valcopy or rcsbuf_valpolish to do + appropriate massaging. */ + +static int +rcsbuf_getkey (rcsbuf, keyp, valp) + struct rcsbuffer *rcsbuf; char **keyp; char **valp; - size_t *lenp; { - char *cur, *max; - int c; - int just_string; + register const char * const my_spacetab = spacetab; + register char *ptr, *ptrend; + char c; - if (lenp != NULL) - *lenp = 0; +#define my_whitespace(c) (my_spacetab[(unsigned char)c] != 0) - /* skip leading whitespace */ - do + rcsbuf->vlen = 0; + rcsbuf->at_string = 0; + rcsbuf->embedded_at = 0; + + ptr = rcsbuf->ptr; + ptrend = rcsbuf->ptrend; + + /* Sanity check. */ + if (ptr < rcsbuf_buffer || ptr > rcsbuf_buffer + rcsbuf_buffer_size) + abort (); + + /* If the pointer is more than RCSBUF_BUFSIZE bytes into the + buffer, move back to the start of the buffer. This keeps the + buffer from growing indefinitely. */ + if (ptr - rcsbuf_buffer >= RCSBUF_BUFSIZE) { - c = getc (fp); - if (c == EOF) - { - *keyp = (char *) NULL; - *valp = (char *) NULL; - return (-1); - } - } while (whitespace (c)); + int len; + + len = ptrend - ptr; - /* fill in key */ - cur = key; - max = key + keysize; - while (!whitespace (c) && c != ';') + /* Sanity check: we don't read more than RCSBUF_BUFSIZE bytes + at a time, so we can't have more bytes than that past PTR. */ + if (len > RCSBUF_BUFSIZE) + abort (); + + /* Update the POS field, which holds the file offset of the + first byte in the RCSBUF_BUFFER buffer. */ + rcsbuf->pos += ptr - rcsbuf_buffer; + + memcpy (rcsbuf_buffer, ptr, len); + ptr = rcsbuf_buffer; + ptrend = ptr + len; + rcsbuf->ptrend = ptrend; + } + + /* Skip leading whitespace. */ + + while (1) { - if (cur >= max) + if (ptr >= ptrend) { - size_t curoff = cur - key; - expand_string (&key, &keysize, keysize + 1); - cur = key + curoff; - max = key + keysize; + ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, (char **) NULL); + if (ptr == NULL) + return 0; + ptrend = rcsbuf->ptrend; } - *cur++ = c; - c = getc (fp); - if (c == EOF) + c = *ptr; + if (! my_whitespace (c)) + break; + + ++ptr; + } + + /* We've found the start of the key. */ + + *keyp = ptr; + + if (c != ';') + { + while (1) { - *keyp = (char *) NULL; - *valp = (char *) NULL; - return (-1); + ++ptr; + if (ptr >= ptrend) + { + ptr = rcsbuf_fill (rcsbuf, ptr, keyp, (char **) NULL); + if (ptr == NULL) + error (1, 0, "EOF in key in RCS file %s", + rcsbuf->filename); + ptrend = rcsbuf->ptrend; + } + c = *ptr; + if (c == ';' || my_whitespace (c)) + break; } } - if (cur >= max) + + /* Here *KEYP points to the key in the buffer, C is the character + we found at the of the key, and PTR points to the location in + the buffer where we found C. We must set *PTR to \0 in order + to terminate the key. If the key ended with ';', then there is + no value. */ + + *ptr = '\0'; + ++ptr; + + if (c == ';') { - size_t curoff = cur - key; - expand_string (&key, &keysize, keysize + 1); - cur = key + curoff; - max = key + keysize; + *valp = NULL; + rcsbuf->ptr = ptr; + return 1; } - *cur = '\0'; - /* skip whitespace between key and val */ - while (whitespace (c)) + /* C must be whitespace. Skip whitespace between the key and the + value. If we find ';' now, there is no value. */ + + while (1) { - c = getc (fp); - if (c == EOF) + if (ptr >= ptrend) { - *keyp = (char *) NULL; - *valp = (char *) NULL; - return (-1); + ptr = rcsbuf_fill (rcsbuf, ptr, keyp, (char **) NULL); + if (ptr == NULL) + error (1, 0, "EOF while looking for value in RCS file %s", + rcsbuf->filename); + ptrend = rcsbuf->ptrend; } - } + c = *ptr; + if (c == ';') + { + *valp = NULL; + rcsbuf->ptr = ptr + 1; + return 1; + } + if (! my_whitespace (c)) + break; + ++ptr; + } - /* if we ended key with a semicolon, there is no value */ - if (c == ';') + /* Now PTR points to the start of the value, and C is the first + character of the value. */ + + if (c != '@') + *valp = ptr; + else { - *keyp = key; - *valp = (char *) NULL; - return (0); - } + char *pat; + size_t vlen; - /* otherwise, there might be a value, so fill it in */ - cur = value; - max = value + valsize; + /* Optimize the common case of a value composed of a single + '@' string. */ - just_string = (strcmp (key, RCSDESC) == 0 - || strcmp (key, "text") == 0 - || strcmp (key, "log") == 0); + rcsbuf->at_string = 1; - /* process the value */ - for (;;) - { - /* handle RCS "strings" */ - if (c == '@') + ++ptr; + + *valp = ptr; + + while (1) { - for (;;) + while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL) { - c = getc (fp); - if (c == EOF) - { - *keyp = (char *) NULL; - *valp = (char *) NULL; - return (-1); - } + /* Note that we pass PTREND as the PTR value to + rcsbuf_fill, so that we will wind up setting PTR to + the location corresponding to the old PTREND, so + that we don't search the same bytes again. */ + ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp); + if (ptr == NULL) + error (1, 0, + "EOF while looking for end of string in RCS file %s", + rcsbuf->filename); + ptrend = rcsbuf->ptrend; + } - if (c == '@') + /* Handle the special case of an '@' right at the end of + the known bytes. */ + if (pat + 1 >= ptrend) + { + /* Note that we pass PAT, not PTR, here. */ + pat = rcsbuf_fill (rcsbuf, pat, keyp, valp); + if (pat == NULL) { - c = getc (fp); - if (c == EOF) - { - *keyp = (char *) NULL; - *valp = (char *) NULL; - return (-1); - } - - if (c != '@') - break; - } + /* EOF here is OK; it just means that the last + character of the file was an '@' terminating a + value for a key type which does not require a + trailing ';'. */ + pat = rcsbuf->ptrend - 1; - if (cur >= max) - { - size_t curoff = cur - value; - expand_string (&value, &valsize, valsize + 1); - cur = value + curoff; - max = value + valsize; } - *cur++ = c; + ptrend = rcsbuf->ptrend; + + /* Note that the value of PTR is bogus here. This is + OK, because we don't use it. */ } + + if (pat + 1 >= ptrend || pat[1] != '@') + break; + + /* We found an '@' pair in the string. Keep looking. */ + ++rcsbuf->embedded_at; + ptr = pat + 2; } - /* The syntax for some key-value pairs is different; they - don't end with a semicolon. */ - if (just_string) - break; + /* Here PAT points to the final '@' in the string. */ + + *pat = '\0'; + + vlen = pat - *valp; + if (vlen == 0) + *valp = NULL; + rcsbuf->vlen = vlen; + + ptr = pat + 1; + } + + /* Certain keywords only have a '@' string. If there is no '@' + string, then the old getrcskey function assumed that they had + no value, and we do the same. */ - /* compress whitespace down to a single space */ - if (whitespace (c)) + { + char *k; + + k = *keyp; + if (STREQ (k, RCSDESC) + || STREQ (k, "text") + || STREQ (k, "log")) { - do { - c = getc (fp); - if (c == EOF) - { - *keyp = (char *) NULL; - *valp = (char *) NULL; - return (-1); - } - } while (whitespace (c)); + if (c != '@') + *valp = NULL; + rcsbuf->ptr = ptr; + return 1; + } + } - /* Do not include any trailing whitespace in the value. */ - if (c != ';') + /* If we've already gathered a '@' string, try to skip whitespace + and find a ';'. */ + if (c == '@') + { + while (1) + { + char n; + + if (ptr >= ptrend) { - if (cur >= max) - { - size_t curoff = cur - value; - expand_string (&value, &valsize, valsize + 1); - cur = value + curoff; - max = value + valsize; - } - *cur++ = ' '; + ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp); + if (ptr == NULL) + error (1, 0, "EOF in value in RCS file %s", + rcsbuf->filename); + ptrend = rcsbuf->ptrend; + } + n = *ptr; + if (n == ';') + { + /* We're done. We already set everything up for this + case above. */ + rcsbuf->ptr = ptr + 1; + return 1; } + if (! my_whitespace (n)) + break; + ++ptr; } - /* if we got a semi-colon we are done with the entire value */ - if (c == ';') - break; + /* The value extends past the '@' string. We need to undo the + closing of the '@' done in the default case above. This + case never happens in a plain RCS file, but it can happen + if user defined phrases are used. */ + if (rcsbuf->vlen != 0) + (*valp)[rcsbuf->vlen] = ' '; + else + *valp = ptr; + } - if (cur >= max) + /* Here we have a value which is not a simple '@' string. We need + to gather up everything until the next ';', including any '@' + strings. *VALP points to the start of the value. If + RCSBUF->VLEN is not zero, then we have already read an '@' + string, and PTR points to the data following the '@' string. + Otherwise, PTR points to the start of the value. */ + + while (1) + { + char *start, *psemi, *pat; + + /* Find the ';' which must end the value. */ + start = ptr; + while ((psemi = memchr (ptr, ';', ptrend - ptr)) == NULL) { - size_t curoff = cur - value; - expand_string (&value, &valsize, valsize + 1); - cur = value + curoff; - max = value + valsize; + int slen; + + /* Note that we pass PTREND as the PTR value to + rcsbuf_fill, so that we will wind up setting PTR to the + location corresponding to the old PTREND, so that we + don't search the same bytes again. */ + slen = start - *valp; + ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp); + if (ptr == NULL) + error (1, 0, "EOF in value in RCS file %s", rcsbuf->filename); + start = *valp + slen; + ptrend = rcsbuf->ptrend; } - *cur++ = c; - c = getc (fp); - if (c == EOF) + /* See if there are any '@' strings in the value. */ + pat = memchr (start, '@', psemi - start); + + if (pat == NULL) { - *keyp = (char *) NULL; - *valp = (char *) NULL; - return (-1); + size_t vlen; + + /* We're done with the value. Trim any trailing + whitespace. */ + + rcsbuf->ptr = psemi + 1; + + start = *valp; + while (psemi > start && my_whitespace (psemi[-1])) + --psemi; + *psemi = '\0'; + + vlen = psemi - start; + if (vlen == 0) + *valp = NULL; + rcsbuf->vlen = vlen; + + return 1; } + + /* We found an '@' string in the value. We set + RCSBUF->AT_STRING, which means that we won't be able to + compress whitespace correctly for this type of value. + Since this type of value never arises in a normal RCS file, + this should not be a big deal. It means that if anybody + adds a phrase which can have both an '@' string and regular + text, they will have to handle whitespace compression + themselves. */ + + rcsbuf->at_string = 1; + + *pat = ' '; + + ptr = pat + 1; + + while (1) + { + while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL) + { + /* Note that we pass PTREND as the PTR value to + rcsbuff_fill, so that we will wind up setting PTR + to the location corresponding to the old PTREND, so + that we don't search the same bytes again. */ + ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp); + if (ptr == NULL) + error (1, 0, + "EOF while looking for end of string in RCS file %s", + rcsbuf->filename); + ptrend = rcsbuf->ptrend; + } + + /* Handle the special case of an '@' right at the end of + the known bytes. */ + if (pat + 1 >= ptrend) + { + ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp); + if (ptr == NULL) + error (1, 0, "EOF in value in RCS file %s", + rcsbuf->filename); + ptrend = rcsbuf->ptrend; + } + + if (pat[1] != '@') + break; + + /* We found an '@' pair in the string. Keep looking. */ + ++rcsbuf->embedded_at; + ptr = pat + 2; + } + + /* Here PAT points to the final '@' in the string. */ + + *pat = ' '; + + ptr = pat + 1; } - /* terminate the string */ - if (cur >= max) +#undef my_whitespace +} + +/* Read an RCS revision number from an RCS file. This sets *REVP to + point to the revision number; it will point to space that is + managed by the rcsbuf functions, and is only good until the next + call to rcsbuf_getkey or rcsbuf_getrevnum. + + This function returns 1 on success, or 0 on EOF. If there is an + error reading the file, or an EOF in an unexpected location, it + gives a fatal error. */ + +static int +rcsbuf_getrevnum (rcsbuf, revp) + struct rcsbuffer *rcsbuf; + char **revp; +{ + char *ptr, *ptrend; + char c; + + ptr = rcsbuf->ptr; + ptrend = rcsbuf->ptrend; + + *revp = NULL; + + /* Skip leading whitespace. */ + + while (1) { - size_t curoff = cur - value; - expand_string (&value, &valsize, valsize + 1); - cur = value + curoff; - max = value + valsize; + if (ptr >= ptrend) + { + ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, (char **) NULL); + if (ptr == NULL) + return 0; + ptrend = rcsbuf->ptrend; + } + + c = *ptr; + if (! whitespace (c)) + break; + + ++ptr; } - *cur = '\0'; - /* if the string is empty, make it null */ - if (value && cur != value) + if (! isdigit (c) && c != '.') + error (1, 0, + "unexpected `%c' reading revision number in RCS file %s", + c, rcsbuf->filename); + + *revp = ptr; + + do + { + ++ptr; + if (ptr >= ptrend) + { + ptr = rcsbuf_fill (rcsbuf, ptr, revp, (char **) NULL); + if (ptr == NULL) + error (1, 0, + "unexpected EOF reading revision number in RCS file %s", + rcsbuf->filename); + ptrend = rcsbuf->ptrend; + } + + c = *ptr; + } + while (isdigit (c) || c == '.'); + + if (! whitespace (c)) + error (1, 0, "unexpected `%c' reading revision number in RCS file %s", + c, rcsbuf->filename); + + *ptr = '\0'; + + rcsbuf->ptr = ptr + 1; + + return 1; +} + +/* Fill RCSBUF_BUFFER with bytes from the file associated with RCSBUF, + updating PTR and the PTREND field. If KEYP and *KEYP are not NULL, + then *KEYP points into the buffer, and must be adjusted if the + buffer is changed. Likewise for VALP. Returns the new value of + PTR, or NULL on error. */ + +static char * +rcsbuf_fill (rcsbuf, ptr, keyp, valp) + struct rcsbuffer *rcsbuf; + char *ptr; + char **keyp; + char **valp; +{ + int got; + + if (rcsbuf->ptrend - rcsbuf_buffer + RCSBUF_BUFSIZE > rcsbuf_buffer_size) + { + int poff, peoff, koff, voff; + + poff = ptr - rcsbuf_buffer; + peoff = rcsbuf->ptrend - rcsbuf_buffer; + if (keyp != NULL && *keyp != NULL) + koff = *keyp - rcsbuf_buffer; + if (valp != NULL && *valp != NULL) + voff = *valp - rcsbuf_buffer; + + expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size, + rcsbuf_buffer_size + RCSBUF_BUFSIZE); + + ptr = rcsbuf_buffer + poff; + rcsbuf->ptrend = rcsbuf_buffer + peoff; + if (keyp != NULL && *keyp != NULL) + *keyp = rcsbuf_buffer + koff; + if (valp != NULL && *valp != NULL) + *valp = rcsbuf_buffer + voff; + } + + got = fread (rcsbuf->ptrend, 1, RCSBUF_BUFSIZE, rcsbuf->fp); + if (got == 0) + { + if (ferror (rcsbuf->fp)) + error (1, errno, "cannot read %s", rcsbuf->filename); + return NULL; + } + + rcsbuf->ptrend += got; + + return ptr; +} + +/* Copy the value VAL returned by rcsbuf_getkey into a memory buffer, + returning the memory buffer. Polish the value like + rcsbuf_valpolish, q.v. */ + +static char * +rcsbuf_valcopy (rcsbuf, val, polish, lenp) + struct rcsbuffer *rcsbuf; + char *val; + int polish; + size_t *lenp; +{ + size_t vlen; + int embedded_at; + char *ret; + + if (val == NULL) { - *valp = value; if (lenp != NULL) - *lenp = cur - value; + *lenp = 0; + return NULL; } - else - *valp = NULL; - *keyp = key; - return (0); + + vlen = rcsbuf->vlen; + embedded_at = rcsbuf->embedded_at; + + ret = xmalloc (vlen - embedded_at + 1); + + if (rcsbuf->at_string ? embedded_at == 0 : ! polish) + { + /* No special action to take. */ + memcpy (ret, val, vlen + 1); + if (lenp != NULL) + *lenp = vlen; + return ret; + } + + rcsbuf_valpolish_internal (rcsbuf, ret, val, lenp); + return ret; } -/* Read an RCS revision number from FP. Put a pointer to it in *REVP; - it points to space managed by getrcsrev which is only good until - the next call to getrcskey or getrcsrev. */ +/* Polish the value VAL returned by rcsbuf_getkey. The POLISH + parameter is non-zero if multiple embedded whitespace characters + should be compressed into a single whitespace character. Note that + leading and trailing whitespace was already removed by + rcsbuf_getkey. Within an '@' string, pairs of '@' characters are + compressed into a single '@' character regardless of the value of + POLISH. If LENP is not NULL, set *LENP to the length of the value. */ + static void -getrcsrev (fp, revp) - FILE *fp; - char **revp; +rcsbuf_valpolish (rcsbuf, val, polish, lenp) + struct rcsbuffer *rcsbuf; + char *val; + int polish; + size_t *lenp; { - char *cur; - char *max; - int c; + if (val == NULL) + { + if (lenp != NULL) + *lenp= 0; + return; + } - do { - c = getc (fp); - if (c == EOF) + if (rcsbuf->at_string ? rcsbuf->embedded_at == 0 : ! polish) + { + /* No special action to take. */ + if (lenp != NULL) + *lenp = rcsbuf->vlen; + return; + } + + rcsbuf_valpolish_internal (rcsbuf, val, val, lenp); +} + +/* Internal polishing routine, called from rcsbuf_valcopy and + rcsbuf_valpolish. */ + +static void +rcsbuf_valpolish_internal (rcsbuf, to, from, lenp) + struct rcsbuffer *rcsbuf; + char *to; + const char *from; + size_t *lenp; +{ + size_t len; + + len = rcsbuf->vlen; + + if (! rcsbuf->at_string) + { + char *orig_to; + size_t clen; + + orig_to = to; + + for (clen = len; clen > 0; ++from, --clen) { - /* FIXME: should be including filename in error message. */ - if (ferror (fp)) - error (1, errno, "cannot read rcs file"); - else - error (1, 0, "unexpected end of file reading rcs file"); + char c; + + c = *from; + if (whitespace (c)) + { + /* Note that we know that clen can not drop to zero + while we have whitespace, because we know there is + no trailing whitespace. */ + while (whitespace (from[1])) + { + ++from; + --clen; + } + c = ' '; + } + *to++ = c; } - } while (whitespace (c)); - if (!(isdigit (c) || c == '.')) - /* FIXME: should be including filename in error message. */ - error (1, 0, "error reading rcs file; revision number expected"); + *to = '\0'; - cur = key; - max = key + keysize; - while (isdigit (c) || c == '.') + if (lenp != NULL) + *lenp = to - orig_to; + } + else { - if (cur >= max) + const char *orig_from; + char *orig_to; + int embedded_at; + size_t clen; + + orig_from = from; + orig_to = to; + + embedded_at = rcsbuf->embedded_at; + + if (lenp != NULL) + *lenp = len - embedded_at; + + for (clen = len; clen > 0; ++from, --clen) { - size_t curoff = cur - key; - expand_string (&key, &keysize, keysize + 1); - cur = key + curoff; - max = key + keysize; + char c; + + c = *from; + *to++ = c; + if (c == '@') + { + ++from; + + /* Sanity check. */ + if (*from != '@' || clen == 0) + abort (); + + --clen; + + --embedded_at; + if (embedded_at == 0) + { + /* We've found all the embedded '@' characters. + We can just memcpy the rest of the buffer after + this '@' character. */ + if (orig_to != orig_from) + memcpy (to, from + 1, clen - 1); + else + memmove (to, from + 1, clen - 1); + from += clen; + to += clen - 1; + break; + } + } } - *cur++ = c; - c = getc (fp); - if (c == EOF) + /* Sanity check. */ + if (from != orig_from + len + || to != orig_to + (len - rcsbuf->embedded_at)) { - /* FIXME: should be including filename in error message. */ - if (ferror (fp)) - error (1, errno, "cannot read rcs file"); - else - error (1, 0, "unexpected end of file reading rcs file"); + abort (); } - } - if (cur >= max) - { - size_t curoff = cur - key; - expand_string (&key, &keysize, keysize + 1); - cur = key + curoff; - max = key + keysize; + *to = '\0'; } - *cur = '\0'; - *revp = key; } -/* Like getrcsrev, but don't die on error. Return the last character - read (last call to getc, which may be EOF). TODO: implement getrcsrev - in terms of this function. */ -static int -getrevnum (fp, revp) - FILE *fp; - char **revp; +/* Return the current position of an rcsbuf. */ + +static unsigned long +rcsbuf_ftell (rcsbuf) + struct rcsbuffer *rcsbuf; { - char *cur; - char *max; - int c; + return rcsbuf->pos + (rcsbuf->ptr - rcsbuf_buffer); +} - *revp = NULL; - do { - c = getc (fp); - if (c == EOF) - return c; - } while (whitespace (c)); +/* Return a pointer to any data buffered for RCSBUF, along with the + length. */ + +static void +rcsbuf_get_buffered (rcsbuf, datap, lenp) + struct rcsbuffer *rcsbuf; + char **datap; + size_t *lenp; +{ + *datap = rcsbuf->ptr; + *lenp = rcsbuf->ptrend - rcsbuf->ptr; +} - if (!(isdigit (c) || c == '.')) - return c; +/* CVS optimizes by quickly reading some header information from a + file. If it decides it needs to do more with the file, it reopens + it. We speed that up here by maintaining a cache of a single open + file, to save the time it takes to reopen the file in the common + case. */ - cur = key; - max = key + keysize; - while (isdigit (c) || c == '.') +static RCSNode *cached_rcs; +static struct rcsbuffer cached_rcsbuf; + +/* Cache RCS and RCSBUF. This takes responsibility for closing + RCSBUF->FP. */ + +static void +rcsbuf_cache (rcs, rcsbuf) + RCSNode *rcs; + struct rcsbuffer *rcsbuf; +{ + if (cached_rcs != NULL) + rcsbuf_cache_close (); + cached_rcs = rcs; + ++rcs->refcount; + cached_rcsbuf = *rcsbuf; +} + +/* If there is anything in the cache, close it. */ + +static void +rcsbuf_cache_close () +{ + if (cached_rcs != NULL) { - if (cur >= max) + if (fclose (cached_rcsbuf.fp) != 0) + error (0, errno, "cannot close %s", cached_rcsbuf.filename); + rcsbuf_close (&cached_rcsbuf); + freercsnode (&cached_rcs); + cached_rcs = NULL; + } +} + +/* Open an rcsbuffer for RCS, getting it from the cache if possible. + Set *FPP to the file, and *RCSBUFP to the rcsbuf. The file should + be put at position POS. */ + +static void +rcsbuf_cache_open (rcs, pos, pfp, prcsbuf) + RCSNode *rcs; + long pos; + FILE **pfp; + struct rcsbuffer *prcsbuf; +{ + if (cached_rcs == rcs) + { + if (rcsbuf_ftell (&cached_rcsbuf) != pos) { - size_t curoff = cur - key; - expand_string (&key, &keysize, keysize + 1); - cur = key + curoff; - max = key + keysize; + if (fseek (cached_rcsbuf.fp, pos, SEEK_SET) != 0) + error (1, 0, "cannot fseek RCS file %s", + cached_rcsbuf.filename); + cached_rcsbuf.ptr = rcsbuf_buffer; + cached_rcsbuf.ptrend = rcsbuf_buffer; + cached_rcsbuf.pos = pos; } - *cur = c; + *pfp = cached_rcsbuf.fp; - c = getc (fp); - if (c == EOF) - break; - cur++; - } + /* When RCS_parse opens a file using fopen_case, it frees the + filename which we cached in CACHED_RCSBUF and stores a new + file name in RCS->PATH. We avoid problems here by always + copying the filename over. FIXME: This is hackish. */ + cached_rcsbuf.filename = rcs->path; + + *prcsbuf = cached_rcsbuf; + + cached_rcs = NULL; - if (cur >= max) + /* Removing RCS from the cache removes a reference to it. */ + --rcs->refcount; + if (rcs->refcount <= 0) + error (1, 0, "rcsbuf_cache_open: internal error"); + } + else { - size_t curoff = cur - key; - expand_string (&key, &keysize, keysize + 1); - cur = key + curoff; - max = key + keysize; + if (cached_rcs != NULL) + rcsbuf_cache_close (); + + *pfp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ); + if (*pfp == NULL) + error (1, 0, "unable to reopen `%s'", rcs->path); + if (pos != 0) + { + if (fseek (*pfp, pos, SEEK_SET) != 0) + error (1, 0, "cannot fseek RCS file %s", rcs->path); + } + rcsbuf_open (prcsbuf, *pfp, rcs->path, pos); } - *cur = '\0'; - *revp = key; - return c; } + /* * process the symbols list of the rcs file */ @@ -1314,10 +1904,10 @@ RCS_gettag (rcs, symtag, force_tag_match, simple_tag) /* XXX this is probably not necessary, --jtc */ if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); /* If tag is "HEAD", special case to get head RCS revision */ - if (tag && (strcmp (tag, TAG_HEAD) == 0 || *tag == '\0')) + if (tag && (STREQ (tag, TAG_HEAD) || *tag == '\0')) #if 0 /* This #if 0 is only in the Cygnus code. Why? Death support? */ if (force_tag_match && (rcs->flags & VALID) && (rcs->flags & INATTIC)) return ((char *) NULL); /* head request for removed file */ @@ -1515,7 +2105,7 @@ checkmagic_proc (p, closure) Node *p; void *closure; { - if (strcmp (check_rev, p->data) == 0) + if (STREQ (check_rev, p->data)) return (1); else return (0); @@ -1669,7 +2259,7 @@ RCS_getbranch (rcs, tag, force_tag_match) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); /* find out if the tag contains a dot, or is on the trunk */ cp = strrchr (tag, '.'); @@ -1887,7 +2477,7 @@ RCS_getdate (rcs, date, force_tag_match) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); /* if the head is on a branch, try the branch first */ if (rcs->branch != NULL) @@ -1925,7 +2515,7 @@ RCS_getdate (rcs, date, force_tag_match) */ /* if we found what we're looking for, and it's not 1.1 return it */ - if (cur_rev != NULL && strcmp (cur_rev, "1.1") != 0) + if (cur_rev != NULL && ! STREQ (cur_rev, "1.1")) return (xstrdup (cur_rev)); /* look on the vendor branch */ @@ -1974,7 +2564,7 @@ RCS_getdatebranch (rcs, date, branch) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); p = findnode (rcs->versions, xrev); free (xrev); @@ -2071,7 +2661,7 @@ RCS_getrevtime (rcs, rev, date, fudge) assert (rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); /* look up the revision */ p = findnode (rcs->versions, rev); @@ -2123,7 +2713,7 @@ RCS_getlocks (rcs) assert(rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); if (rcs->locks_data) { rcs->locks = getlist (); @@ -2142,7 +2732,7 @@ RCS_symbols(rcs) assert(rcs != NULL); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); if (rcs->symbols_data) { rcs->symbols = getlist (); @@ -2164,7 +2754,7 @@ translate_symtag (rcs, tag) const char *tag; { if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); if (rcs->symbols != NULL) { @@ -2248,7 +2838,7 @@ RCS_check_kflag (arg) { for (cpp = kflags; *cpp != NULL; cpp++) { - if (strcmp (arg, *cpp) == 0) + if (STREQ (arg, *cpp)) break; } } @@ -2306,7 +2896,7 @@ RCS_isdead (rcs, tag) RCSVers *version; if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); p = findnode (rcs->versions, tag); if (p == NULL) @@ -2326,8 +2916,6 @@ RCS_getexpand (rcs) RCSNode *rcs; { assert (rcs != NULL); - if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); return rcs->expand; } @@ -2935,7 +3523,16 @@ expand_keywords (rcs, ver, name, log, loglen, expand, buf, len, retbuf, retlen) OPTIONS is a string such as "-kb" or "-kv" for keyword expansion options. It may be NULL to use the default expansion mode of the - file, typically "-kkv". */ + file, typically "-kkv". + + On an error which prevented checking out the file, either print a + nonfatal error and return 1, or give a fatal error. On success, + return 0. */ + +/* This function mimics the behavior of `rcs co' almost exactly. The + chief difference is in its support for preserving file ownership, + permissions, and special files across checkin and checkout -- see + comments in RCS_checkin for some issues about this. -twp */ int RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) @@ -2950,15 +3547,27 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) { int free_rev = 0; enum kflag expand; - FILE *fp; + FILE *fp, *ofp; struct stat sb; + struct rcsbuffer rcsbuf; char *key; char *value; size_t len; int free_value = 0; char *log = NULL; size_t loglen; - FILE *ofp; + Node *vp = NULL; +#ifdef PRESERVE_PERMISSIONS_SUPPORT + uid_t rcs_owner; + gid_t rcs_group; + mode_t rcs_mode; + int change_rcs_owner = 0; + int change_rcs_group = 0; + int change_rcs_mode = 0; + int special_file = 0; + unsigned long devnum_long; + dev_t devnum = 0; +#endif if (trace) { @@ -2995,34 +3604,25 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) free_rev = 1; } - if (rev == NULL || strcmp (rev, rcs->head) == 0) + if (rev == NULL || STREQ (rev, rcs->head)) { int gothead; /* We want the head revision. Try to read it directly. */ if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, &fp); + RCS_reparsercsfile (rcs, &fp, &rcsbuf); else - { - fp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ); - if (fp == NULL) - error (1, 0, "unable to reopen `%s'", rcs->path); - if (fseek (fp, rcs->delta_pos, SEEK_SET) != 0) - error (1, 0, "cannot fseek RCS file"); - } + rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf); gothead = 0; - getrcsrev (fp, &key); - while (getrcskey (fp, &key, &value, &len) >= 0) + if (! rcsbuf_getrevnum (&rcsbuf, &key)) + error (1, 0, "unexpected EOF reading %s", rcs->path); + while (rcsbuf_getkey (&rcsbuf, &key, &value)) { - if (strcmp (key, "log") == 0) - { - log = xmalloc (len); - memcpy (log, value, len); - loglen = len; - } - if (strcmp (key, "text") == 0) + if (STREQ (key, "log")) + log = rcsbuf_valcopy (&rcsbuf, value, 0, &loglen); + else if (STREQ (key, "text")) { gothead = 1; break; @@ -3037,20 +3637,23 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) return 1; } + rcsbuf_valpolish (&rcsbuf, value, 0, &len); + if (fstat (fileno (fp), &sb) < 0) error (1, errno, "cannot fstat %s", rcs->path); - if (fclose (fp) < 0) - error (0, errno, "cannot close %s", rcs->path); + rcsbuf_cache (rcs, &rcsbuf); } else { + struct rcsbuffer *rcsbufp; + /* It isn't the head revision of the trunk. We'll need to walk through the deltas. */ fp = NULL; if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, &fp); + RCS_reparsercsfile (rcs, &fp, &rcsbuf); if (fp == NULL) { @@ -3058,14 +3661,17 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) here too. Probably should change it thusly.... */ if (stat (rcs->path, &sb) < 0) error (1, errno, "cannot stat %s", rcs->path); + rcsbufp = NULL; } else { if (fstat (fileno (fp), &sb) < 0) error (1, errno, "cannot fstat %s", rcs->path); + rcsbufp = &rcsbuf; } - RCS_deltas (rcs, fp, rev, RCS_FETCH, &value, &len, &log, &loglen); + RCS_deltas (rcs, fp, rcsbufp, rev, RCS_FETCH, &value, &len, + &log, &loglen); free_value = 1; } @@ -3088,7 +3694,7 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) ouroptions = rcs->expand; for (cpp = kflags; *cpp != NULL; cpp++) - if (strcmp (*cpp, ouroptions) == 0) + if (STREQ (*cpp, ouroptions)) break; if (*cpp != NULL) @@ -3102,17 +3708,186 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) } } - if (expand != KFLAG_O && expand != KFLAG_B) +#ifdef PRESERVE_PERMISSIONS_SUPPORT + /* Handle special files and permissions, if that is desired. */ + if (preserve_perms) { - Node *p; - char *newvalue; + RCSVers *vers; + Node *info; + struct hardlink_info *hlinfo; - p = findnode (rcs->versions, rev == NULL ? rcs->head : rev); - if (p == NULL) + vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev); + if (vp == NULL) error (1, 0, "internal error: no revision information for %s", rev == NULL ? rcs->head : rev); + vers = (RCSVers *) vp->data; + + /* First we look for symlinks, which are simplest to handle. */ + info = findnode (vers->other_delta, "symlink"); + if (info != NULL) + { + char *dest; + + if (pfn != NULL || (workfile == NULL && sout == RUN_TTY)) + error (1, 0, "symbolic link %s:%s cannot be piped", + rcs->path, vers->version); + if (workfile == NULL) + dest = sout; + else + dest = workfile; + + /* Remove `dest', just in case. It's okay to get ENOENT here, + since we just want the file not to be there. (TODO: decide + whether it should be considered an error for `dest' to exist + at this point. If so, the unlink call should be removed and + `symlink' should signal the error. -twp) */ + if (unlink (dest) < 0 && existence_error (errno)) + error (1, errno, "cannot remove %s", dest); + if (symlink (info->data, dest) < 0) + error (1, errno, "cannot create symbolic link from %s to %s", + dest, info->data); + if (free_value) + free (value); + if (free_rev) + free (rev); + return 0; + } + + /* Next, we look at this file's hardlinks field, and see whether + it is linked to any other file that has been checked out. + If so, we don't do anything else -- just link it to that file. - expand_keywords (rcs, (RCSVers *) p->data, nametag, log, loglen, + If we are checking out a file to a pipe or temporary storage, + none of this should matter. Hence the `workfile != NULL' + wrapper around the whole thing. -twp */ + + if (workfile != NULL) + { + info = findnode (vers->other_delta, "hardlinks"); + if (info != NULL) + { + char *links = xstrdup (info->data); + char *working_dir = xgetwd(); + char *p, *file = NULL; + Node *n, *uptodate_link; + + /* For each file in the hardlinks field, check to see + if it exists, and if so, if it has been checked out + this iteration. */ + uptodate_link = NULL; + for (p = strtok (links, " "); + p != NULL && uptodate_link == NULL; + p = strtok (NULL, " ")) + { + file = (char *) + xmalloc (sizeof(char) * + (strlen(working_dir) + strlen(p) + 2)); + sprintf (file, "%s/%s", working_dir, p); + n = lookup_file_by_inode (file); + if (n == NULL) + { + if (strcmp (p, workfile) != 0) + { + /* One of the files that WORKFILE should be + linked to is not even in the working directory. + The user should probably be warned. */ + error (0, 0, + "warning: %s should be hardlinked to %s, but is missing", + p, workfile); + } + free (file); + continue; + } + + /* hlinfo may be NULL if, for instance, a file is being + removed. */ + hlinfo = (struct hardlink_info *) n->data; + if (hlinfo && hlinfo->checked_out) + uptodate_link = n; + free (file); + } + free (links); + free (working_dir); + + /* If we've found a file that `workfile' is supposed to be + linked to, and it has been checked out since CVS was + invoked, then simply link workfile to that file. + + If one of these conditions is not met, then we're + checking out workfile to a temp file or stdout, or + workfile is the first one in its hardlink group to be + checked out. Either way we must continue with a full + checkout. */ + + if (uptodate_link != NULL) + { + if (link (uptodate_link->key, workfile) < 0) + error (1, errno, "cannot link %s to %s", + workfile, uptodate_link->key); + hlinfo->checked_out = 1; /* probably unnecessary */ + if (free_value) + free (value); + if (free_rev) + free (rev); + return 0; + } + } + } + + info = findnode (vers->other_delta, "owner"); + if (info != NULL) + { + change_rcs_owner = 1; + rcs_owner = (uid_t) strtoul (info->data, NULL, 10); + } + info = findnode (vers->other_delta, "group"); + if (info != NULL) + { + change_rcs_group = 1; + rcs_group = (gid_t) strtoul (info->data, NULL, 10); + } + info = findnode (vers->other_delta, "permissions"); + if (info != NULL) + { + change_rcs_mode = 1; + rcs_mode = (mode_t) strtoul (info->data, NULL, 8); + } + info = findnode (vers->other_delta, "special"); + if (info != NULL) + { + /* If the size of `devtype' changes, fix the sscanf call also */ + char devtype[16]; + + if (sscanf (info->data, "%16s %lu", + devtype, &devnum_long) < 2) + error (1, 0, "%s:%s has bad `special' newphrase %s", + workfile, vers->version, info->data); + devnum = devnum_long; + if (strcmp (devtype, "character") == 0) + special_file = S_IFCHR; + else if (strcmp (devtype, "block") == 0) + special_file = S_IFBLK; + else + error (0, 0, "%s is a special file of unsupported type `%s'", + workfile, info->data); + } + } +#endif + + if (expand != KFLAG_O && expand != KFLAG_B) + { + char *newvalue; + + /* Don't fetch the delta node again if we already have it. */ + if (vp == NULL) + { + vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev); + if (vp == NULL) + error (1, 0, "internal error: no revision information for %s", + rev == NULL ? rcs->head : rev); + } + + expand_keywords (rcs, (RCSVers *) vp->data, nametag, log, loglen, expand, value, len, &newvalue, &len); if (newvalue != value) @@ -3132,19 +3907,55 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) if (pfn != NULL) { +#ifdef PRESERVE_PERMISSIONS_SUPPORT + if (special_file) + error (1, 0, "special file %s cannot be piped to anything", + rcs->path); +#endif /* The PFN interface is very simple to implement right now, as we always have the entire file in memory. */ if (len != 0) pfn (callerdat, value, len); } +#ifdef PRESERVE_PERMISSIONS_SUPPORT + else if (special_file) + { + char *dest; + + /* Can send either to WORKFILE or to SOUT, as long as SOUT is + not RUN_TTY. */ + dest = workfile; + if (dest == NULL) + { + if (sout == RUN_TTY) + error (1, 0, "special file %s cannot be written to stdout", + rcs->path); + dest = sout; + } + + /* Unlink `dest', just in case. It's okay if this provokes a + ENOENT error. */ + if (unlink (dest) < 0 && existence_error (errno)) + error (1, errno, "cannot remove %s", dest); + if (mknod (dest, special_file, devnum) < 0) + error (1, errno, "could not create special file %s", + dest); + } +#endif else { + /* Not a special file: write to WORKFILE or SOUT. */ if (workfile == NULL) { if (sout == RUN_TTY) ofp = stdout; else { + /* Symbolic links should be removed before replacement, so that + `fopen' doesn't follow the link and open the wrong file. */ + if (islink (sout)) + if (unlink_file (sout) < 0) + error (1, errno, "cannot remove %s", sout); ofp = CVS_FOPEN (sout, expand == KFLAG_B ? "wb" : "w"); if (ofp == NULL) error (1, errno, "cannot open %s", sout); @@ -3152,6 +3963,11 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) } else { + /* Output is supposed to go to WORKFILE, so we should open that + file. Symbolic links should be removed first (see above). */ + if (islink (workfile)) + if (unlink_file (workfile) < 0) + error (1, errno, "cannot remove %s", workfile); ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w"); if (ofp == NULL) error (1, errno, "cannot open %s", workfile); @@ -3196,22 +4012,56 @@ RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat) nstep = nleft; } } + } - if (workfile != NULL) + if (workfile != NULL) + { + int ret; + +#ifdef PRESERVE_PERMISSIONS_SUPPORT + if (!special_file && fclose (ofp) < 0) + error (1, errno, "cannot close %s", workfile); + + if (change_rcs_owner || change_rcs_group) { - if (fclose (ofp) < 0) - error (1, errno, "cannot close %s", workfile); - if (chmod (workfile, - sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH)) < 0) - error (0, errno, "cannot change mode of file %s", + if (chown (workfile, rcs_owner, rcs_group) < 0) + error (0, errno, "could not change file ownership on %s", workfile); } - else if (sout != RUN_TTY) + + ret = chmod (workfile, + change_rcs_mode + ? rcs_mode + : sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH)); +#else + if (fclose (ofp) < 0) + error (1, errno, "cannot close %s", workfile); + + ret = chmod (workfile, + sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH)); +#endif + if (ret < 0) { - if (fclose (ofp) < 0) - error (1, errno, "cannot close %s", sout); + error (0, errno, "cannot change mode of file %s", + workfile); } } + else if (sout != RUN_TTY) + { + if ( +#ifdef PRESERVE_PERMISSIONS_SUPPORT + !special_file && +#endif + fclose (ofp) < 0) + error (1, errno, "cannot close %s", sout); + } + +#ifdef PRESERVE_PERMISSIONS_SUPPORT + /* If we are in the business of preserving hardlinks, then + mark this file as having been checked out. */ + if (preserve_perms && workfile != NULL) + update_hardlink_info (workfile); +#endif if (free_value) free (value); @@ -3260,7 +4110,7 @@ RCS_findlock_or_tip (rcs) lock = NULL; for (p = locklist->list->next; p != locklist->list; p = p->next) { - if (strcmp (p->data, user) == 0) + if (STREQ (p->data, user)) { if (lock != NULL) { @@ -3366,8 +4216,11 @@ compare_truncated_revnums (r, s) FIXME: isn't the max rev always the last one? If so, we don't even need a loop. */ +static char *max_rev PROTO ((const RCSVers *)); + static char * -max_rev (const RCSVers *branchnode) +max_rev (branchnode) + const RCSVers *branchnode; { Node *head; Node *bp; @@ -3502,22 +4355,27 @@ RCS_addbranch (rcs, branch) return newrevnum; } -/* Check in to RCSFILE with revision REV (which must be greater than the - largest revision) and message MESSAGE (which is checked for legality). - If FLAGS & RCS_FLAGS_DEAD, check in a dead revision. If FLAGS & - RCS_FLAGS_QUIET, tell ci to be quiet. If FLAGS & RCS_FLAGS_MODTIME, - use the working file's modification time for the checkin time. - WORKFILE is the working file to check in from, or NULL to use the usual - RCS rules for deriving it from the RCSFILE. +/* Check in to RCSFILE with revision REV (which must be greater than + the largest revision) and message MESSAGE (which is checked for + legality). If FLAGS & RCS_FLAGS_DEAD, check in a dead revision. + If FLAGS & RCS_FLAGS_QUIET, tell ci to be quiet. If FLAGS & + RCS_FLAGS_MODTIME, use the working file's modification time for the + checkin time. WORKFILE is the working file to check in from, or + NULL to use the usual RCS rules for deriving it from the RCSFILE. + If FLAGS & RCS_FLAGS_KEEPFILE, don't unlink the working file; + unlinking the working file is standard RCS behavior, but is rarely + appropriate for CVS. + + This function should almost exactly mimic the behavior of `rcs ci'. The + principal point of difference is the support here for preserving file + ownership and permissions in the delta nodes. This is not a clean + solution -- precisely because it diverges from RCS's behavior -- but + it doesn't seem feasible to do this anywhere else in the code. [-twp] Return value is -1 for error (and errno is set to indicate the error), positive for error (and an error message has been printed), or zero for success. */ -/* TODO: RCS_checkin always unlinks the working file after checkin -- - then RCS_checkout checks it out again. The logic should probably - be reversed here. */ - int RCS_checkin (rcs, workfile, message, rev, flags) RCSNode *rcs; @@ -3540,7 +4398,7 @@ RCS_checkin (rcs, workfile, message, rev, flags) commitpt = NULL; if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); /* Get basename of working file. Is there a library function to do this? I couldn't find one. -twp */ @@ -3594,6 +4452,92 @@ RCS_checkin (rcs, workfile, message, rev, flags) else delta->state = xstrdup ("Exp"); +#ifdef PRESERVE_PERMISSIONS_SUPPORT + /* If permissions should be preserved on this project, then + save the permission info. */ + if (preserve_perms) + { + Node *np; + struct stat sb; + char buf[64]; /* static buffer should be safe: see usage. -twp */ + char *fullpath; + + delta->other_delta = getlist(); + + if (CVS_LSTAT (workfile, &sb) < 0) + error (1, 1, "cannot lstat %s", workfile); + + if (S_ISLNK (sb.st_mode)) + { + np = getnode(); + np->key = xstrdup ("symlink"); + np->data = xreadlink (workfile); + addnode (delta->other_delta, np); + } + else + { + (void) sprintf (buf, "%u", sb.st_uid); + np = getnode(); + np->key = xstrdup ("owner"); + np->data = xstrdup (buf); + addnode (delta->other_delta, np); + + (void) sprintf (buf, "%u", sb.st_gid); + np = getnode(); + np->key = xstrdup ("group"); + np->data = xstrdup (buf); + addnode (delta->other_delta, np); + + (void) sprintf (buf, "%o", sb.st_mode & 07777); + np = getnode(); + np->key = xstrdup ("permissions"); + np->data = xstrdup (buf); + addnode (delta->other_delta, np); + + /* Save device number. */ + switch (sb.st_mode & S_IFMT) + { + case S_IFREG: break; + case S_IFCHR: + case S_IFBLK: + np = getnode(); + np->key = xstrdup ("special"); + sprintf (buf, "%s %lu", + ((sb.st_mode & S_IFMT) == S_IFCHR + ? "character" : "block"), + (unsigned long) sb.st_rdev); + np->data = xstrdup (buf); + addnode (delta->other_delta, np); + break; + + default: + error (0, 0, "special file %s has unknown type", workfile); + } + + /* Save hardlinks. */ + fullpath = xgetwd(); + fullpath = xrealloc (fullpath, + strlen(fullpath) + strlen(workfile) + 2); + sprintf (fullpath + strlen(fullpath), "/%s", workfile); + + np = lookup_file_by_inode (fullpath); + if (np == NULL) + { + error (1, 0, "lost information on %s's linkage", workfile); + } + else + { + struct hardlink_info *hlinfo; + hlinfo = (struct hardlink_info *) np->data; + np = getnode(); + np->key = xstrdup ("hardlinks"); + np->data = xstrdup (hlinfo->links); + (void) addnode (delta->other_delta, np); + } + } + } +#endif + /* Create a new deltatext node. */ dtext = (Deltatext *) xmalloc (sizeof (Deltatext)); memset (dtext, 0, sizeof (Deltatext)); @@ -3632,7 +4576,9 @@ RCS_checkin (rcs, workfile, message, rev, flags) dtext->version = xstrdup (newrev); bufsize = 0; - get_file(workfile, workfile, "r", &dtext->text, &bufsize, &dtext->len); + get_file (workfile, workfile, + rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r", + &dtext->text, &bufsize, &dtext->len); if (!checkin_quiet) { @@ -3641,6 +4587,9 @@ RCS_checkin (rcs, workfile, message, rev, flags) cvs_output ("\n", 1); } + /* We are probably about to invalidate any cached file. */ + rcsbuf_cache_close (); + fout = rcs_internal_lockfile (rcs->path); RCS_putadmin (rcs, fout); RCS_putdtree (rcs, rcs->head, fout); @@ -3652,12 +4601,12 @@ RCS_checkin (rcs, workfile, message, rev, flags) rcs_internal_unlockfile (fout, rcs->path); freedeltatext (dtext); - /* Removing the file here is an RCS user-visible behavior which - we almost surely do not need in the CVS case. In fact, getting - rid of it should clean up link_file and friends in import.c. */ - if (unlink_file (workfile) < 0) - /* FIXME-update-dir: message does not include update_dir. */ - error (0, errno, "cannot remove %s", workfile); + if ((flags & RCS_FLAGS_KEEPFILE) == 0) + { + if (unlink_file (workfile) < 0) + /* FIXME-update-dir: message does not include update_dir. */ + error (0, errno, "cannot remove %s", workfile); + } if (!checkin_quiet) cvs_output ("done\n", 5); @@ -3697,7 +4646,7 @@ RCS_checkin (rcs, workfile, message, rev, flags) goto checkin_done; } else if (commitpt->next == NULL - || strcmp (commitpt->version, rcs->head) == 0) + || STREQ (commitpt->version, rcs->head)) delta->version = increment_revnum (commitpt->version); else delta->version = RCS_addbranch (rcs, commitpt->version); @@ -3801,7 +4750,7 @@ RCS_checkin (rcs, workfile, message, rev, flags) nodep = findnode (RCS_getlocks (rcs), commitpt->version); if (nodep != NULL) { - if (strcmp (nodep->data, delta->author) != 0) + if (! STREQ (nodep->data, delta->author)) { error (0, 0, "%s: revision %s locked by %s", rcs->path, @@ -3823,13 +4772,13 @@ RCS_checkin (rcs, workfile, message, rev, flags) tmpfile = cvs_temp_name(); status = RCS_checkout (rcs, NULL, commitpt->version, NULL, ((rcs->expand != NULL - && strcmp (rcs->expand, "b") == 0) + && STREQ (rcs->expand, "b")) ? "-kb" : "-ko"), tmpfile, (RCSCHECKOUTPROC)0, NULL); if (status != 0) - error (1, status < 0 ? errno : 0, + error (1, 0, "could not check out revision %s of `%s'", commitpt->version, rcs->path); @@ -3840,18 +4789,18 @@ RCS_checkin (rcs, workfile, message, rev, flags) /* Diff options should include --binary if the RCS file has -kb set in its `expand' field. */ - diffopts = (rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 + diffopts = (rcs->expand != NULL && STREQ (rcs->expand, "b") ? "-a -n --binary" : "-a -n"); - if (strcmp (commitpt->version, rcs->head) == 0 && + if (STREQ (commitpt->version, rcs->head) && numdots (delta->version) == 1) { /* If this revision is being inserted on the trunk, the change text for the new delta should be the contents of the working file ... */ bufsize = 0; get_file (workfile, workfile, - rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 ? "rb" : "r", + rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r", &dtext->text, &bufsize, &dtext->len); /* ... and the change text for the old delta should be a diff. */ @@ -3887,7 +4836,7 @@ RCS_checkin (rcs, workfile, message, rev, flags) This should cause no harm, but doesn't strike me as immensely clean. */ get_file (changefile, changefile, - rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 ? "rb" : "r", + rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r", &commitpt->text->text, &bufsize, &commitpt->text->len); /* If COMMITPT->TEXT->TEXT is NULL, it means that CHANGEFILE @@ -3924,7 +4873,7 @@ RCS_checkin (rcs, workfile, message, rev, flags) /* See the comment above, at the other get_file invocation, regarding binary vs. text. */ get_file (changefile, changefile, - rcs->expand != NULL && strcmp (rcs->expand, "b") == 0 ? "rb" : "r", + rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r", &dtext->text, &bufsize, &dtext->len); if (dtext->text == NULL) @@ -3948,7 +4897,7 @@ RCS_checkin (rcs, workfile, message, rev, flags) if (numdots (commitpt->version) == numdots (delta->version)) { - if (strcmp (commitpt->version, rcs->head) == 0) + if (STREQ (commitpt->version, rcs->head)) { delta->next = rcs->head; rcs->head = xstrdup (delta->version); @@ -3978,12 +4927,12 @@ RCS_checkin (rcs, workfile, message, rev, flags) RCS_rewrite (rcs, dtext, commitpt->version); - /* Removing the file here is an RCS user-visible behavior which - we almost surely do not need in the CVS case. In fact, getting - rid of it should clean up link_file and friends in import.c. */ - if (unlink_file (workfile) < 0) - /* FIXME-update-dir: message does not include update_dir. */ - error (1, errno, "cannot remove %s", workfile); + if ((flags & RCS_FLAGS_KEEPFILE) == 0) + { + if (unlink_file (workfile) < 0) + /* FIXME-update-dir: message does not include update_dir. */ + error (1, errno, "cannot remove %s", workfile); + } if (unlink_file (tmpfile) < 0) error (0, errno, "cannot remove %s", tmpfile); if (unlink_file (changefile) < 0) @@ -4036,42 +4985,70 @@ RCS_cmp_file (rcs, rev, options, filename) int retcode; if (options != NULL && options[0] != '\0') - binary = (strcmp (options, "-kb") == 0); + binary = STREQ (options, "-kb"); else { char *expand; expand = RCS_getexpand (rcs); - if (expand != NULL && strcmp (expand, "b") == 0) + if (expand != NULL && STREQ (expand, "b")) binary = 1; else binary = 0; } - fp = CVS_FOPEN (filename, binary ? FOPEN_BINARY_READ : "r"); - - data.filename = filename; - data.fp = fp; - data.different = 0; - - retcode = RCS_checkout (rcs, (char *) NULL, rev, (char *) NULL, - options, RUN_TTY, cmp_file_buffer, - (void *) &data); +#ifdef PRESERVE_PERMISSIONS_SUPPORT + /* If CVS is to deal properly with special files (when + PreservePermissions is on), the best way is to check out the + revision to a temporary file and call `xcmp' on the two disk + files. xcmp needs to handle non-regular files properly anyway, + so calling it simplifies RCS_cmp_file. We *could* just yank + the delta node out of the version tree and look for device + numbers, but writing to disk and calling xcmp is a better + abstraction (therefore probably more robust). -twp */ - /* If we have not yet found a difference, make sure that we are at - the end of the file. */ - if (! data.different) + if (preserve_perms) { - if (getc (fp) != EOF) - data.different = 1; - } + char *tmp; - fclose (fp); + tmp = cvs_temp_name(); + retcode = RCS_checkout(rcs, NULL, rev, NULL, options, tmp, NULL, NULL); + if (retcode != 0) + return 1; - if (retcode != 0) - return 1; + retcode = xcmp (tmp, filename); + if (CVS_UNLINK (tmp) < 0) + error (0, errno, "cannot remove %s", tmp); + return retcode; + } + else +#endif + { + fp = CVS_FOPEN (filename, binary ? FOPEN_BINARY_READ : "r"); + + data.filename = filename; + data.fp = fp; + data.different = 0; + + retcode = RCS_checkout (rcs, (char *) NULL, rev, (char *) NULL, + options, RUN_TTY, cmp_file_buffer, + (void *) &data); + + /* If we have not yet found a difference, make sure that we are at + the end of the file. */ + if (! data.different) + { + if (getc (fp) != EOF) + data.different = 1; + } + + fclose (fp); - return data.different; + if (retcode != 0) + return 1; + + return data.different; + } } /* This is a subroutine of RCS_cmp_file. It is passed to @@ -4139,12 +5116,12 @@ RCS_settag (rcs, tag, rev) Node *node; if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); /* FIXME: This check should be moved to RCS_check_tag. There is no reason for it to be here. */ - if (strcmp (tag, TAG_BASE) == 0 - || strcmp (tag, TAG_HEAD) == 0) + if (STREQ (tag, TAG_BASE) + || STREQ (tag, TAG_HEAD)) { /* Print the name of the tag might be considered redundant with the caller, which also prints it. Perhaps this helps @@ -4198,7 +5175,7 @@ RCS_deltag (rcs, tag) List *symbols; Node *node; if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); symbols = RCS_symbols (rcs); if (symbols == NULL) @@ -4221,11 +5198,14 @@ RCS_setbranch (rcs, rev) const char *rev; { if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); + + if (rev && ! *rev) + rev = NULL; if (rev == NULL && rcs->branch == NULL) return 0; - if (rev != NULL && rcs->branch != NULL && strcmp (rev, rcs->branch) == 0) + if (rev != NULL && rcs->branch != NULL && STREQ (rev, rcs->branch)) return 0; if (rcs->branch != NULL) @@ -4255,7 +5235,7 @@ RCS_lock (rcs, rev, lock_quiet) char *xrev = NULL; if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); locks = RCS_getlocks (rcs); if (locks == NULL) @@ -4299,7 +5279,7 @@ RCS_lock (rcs, rev, lock_quiet) p = findnode (locks, xrev); if (p != NULL) { - if (strcmp (p->data, user) == 0) + if (STREQ (p->data, user)) { /* We already own the lock on this revision, so do nothing. */ free (xrev); @@ -4351,7 +5331,7 @@ RCS_unlock (rcs, rev, unlock_quiet) user = getcaller(); if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); /* If rev is NULL, unlock the latest revision (first in rcs->locks) held by the caller. */ @@ -4378,7 +5358,7 @@ RCS_unlock (rcs, rev, unlock_quiet) lock = NULL; for (p = locks->list->next; p != locks->list; p = p->next) { - if (strcmp (p->data, user) == 0) + if (STREQ (p->data, user)) { if (lock != NULL) { @@ -4417,7 +5397,7 @@ RCS_unlock (rcs, rev, unlock_quiet) return 0; } - if (strcmp (lock->data, user) != 0) + if (! STREQ (lock->data, user)) { /* If the revision is locked by someone else, notify them. Note that this shouldn't ever happen if RCS_unlock @@ -4453,7 +5433,7 @@ RCS_addaccess (rcs, user) char *access, *a; if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); if (rcs->access == NULL) rcs->access = xstrdup (user); @@ -4462,7 +5442,7 @@ RCS_addaccess (rcs, user) access = xstrdup (rcs->access); for (a = strtok (access, " "); a != NULL; a = strtok (NULL, " ")) { - if (strcmp (a, user) == 0) + if (STREQ (a, user)) { free (access); return; @@ -4486,7 +5466,7 @@ RCS_delaccess (rcs, user) int ulen; if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); if (rcs->access == NULL) return; @@ -4517,7 +5497,7 @@ RCS_getaccess (rcs) RCSNode *rcs; { if (rcs->flags & PARTIAL) - RCS_reparsercsfile (rcs, NULL); + RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); return rcs->access; } @@ -4533,7 +5513,7 @@ findtag (node, arg) { char *rev = (char *)arg; - if (strcmp (node->data, rev) == 0) + if (STREQ (node->data, rev)) return 1; else return 0; @@ -4626,7 +5606,7 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive) { /* A range consisting of a branch number means the latest revision on that branch. */ - if (RCS_isbranch (rcs, rev1) && strcmp (rev1, rev2) == 0) + if (RCS_isbranch (rcs, rev1) && STREQ (rev1, rev2)) rev1 = rev2 = RCS_getbranch (rcs, rev1, 0); else { @@ -4717,12 +5697,12 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive) *bp = '.'; } } - else if (strcmp (rev1, branchpoint) != 0) + else if (! STREQ (rev1, branchpoint)) { /* Walk deltas from BRANCHPOINT on, looking for REV1. */ nodep = findnode (rcs->versions, branchpoint); revp = (RCSVers *) nodep->data; - while (revp->next != NULL && strcmp (revp->next, rev1) != 0) + while (revp->next != NULL && ! STREQ (revp->next, rev1)) { revp = (RCSVers *) nodep->data; nodep = findnode (rcs->versions, revp->next); @@ -4771,7 +5751,7 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive) revp = (RCSVers *) nodep->data; if (rev2 != NULL) - found = (strcmp (revp->version, rev2) == 0); + found = STREQ (revp->version, rev2); next = revp->next; if ((!found && next != NULL) || rev2_inclusive || rev2 == NULL) @@ -4868,13 +5848,6 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive) if (status > 0) goto delrev_done; - else if (status < 0) - { - error (0, errno, - "cannot check out revision %s of %s", after, rcs->path); - goto delrev_done; - } - if (before == NULL) { /* We are deleting revisions from the head of the tree, @@ -4902,12 +5875,6 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive) (RCSCHECKOUTPROC)0, NULL); if (status > 0) goto delrev_done; - else if (status < 0) - { - error (0, errno, "cannot check out revision %s of %s", - before, rcs->path); - goto delrev_done; - } outfile = cvs_temp_name(); status = diff_exec (beforefile, afterfile, "-n", outfile); @@ -4952,7 +5919,7 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive) outdated. (FIXME: would it be safe to use the `dead' field for this? Doubtful.) */ for (next = rev1; - next != NULL && (after == NULL || strcmp (next, after) != 0); + next != NULL && (after == NULL || ! STREQ (next, after)); next = revp->next) { nodep = findnode (rcs->versions, next); @@ -4977,13 +5944,13 @@ RCS_delete_revs (rcs, tag1, tag2, inclusive) /* beforep's ->next field already should be equal to after, which I think is always NULL in this case. */ ; - else if (strcmp (rev1, branchpoint) == 0) + else if (STREQ (rev1, branchpoint)) { nodep = findnode (rcs->versions, before); revp = (RCSVers *) nodep->data; nodep = revp->branches->list->next; while (nodep != revp->branches->list && - strcmp (nodep->key, rev1) != 0) + ! STREQ (nodep->key, rev1)) nodep = nodep->next; assert (nodep != revp->branches->list); if (after == NULL) @@ -5503,9 +6470,10 @@ rcs_change_text (name, textbuf, textlen, diffbuf, difflen, retbuf, retlen) On error, give a fatal error. */ static void -RCS_deltas (rcs, fp, version, op, text, len, log, loglen) +RCS_deltas (rcs, fp, rcsbuf, version, op, text, len, log, loglen) RCSNode *rcs; FILE *fp; + struct rcsbuffer *rcsbuf; char *version; enum rcs_delta_op op; char **text; @@ -5513,6 +6481,7 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) char **log; size_t *loglen; { + struct rcsbuffer rcsbuf_local; char *branchversion; char *cpversion; char *key; @@ -5522,7 +6491,6 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) RCSVers *prev_vers; RCSVers *trunk_vers; char *next; - int n; int ishead, isnext, isversion, onbranch; Node *node; struct linevector headlines; @@ -5532,11 +6500,8 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) if (fp == NULL) { - fp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ); - if (fp == NULL) - error (1, 0, "unable to reopen `%s'", rcs->path); - if (fseek (fp, rcs->delta_pos, SEEK_SET) != 0) - error (1, 0, "cannot fseek RCS file"); + rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf_local); + rcsbuf = &rcsbuf_local; } ishead = 1; @@ -5563,9 +6528,10 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) *cpversion = '\0'; do { - getrcsrev (fp, &key); + if (! rcsbuf_getrevnum (rcsbuf, &key)) + error (1, 0, "unexpected EOF reading RCS file %s", rcs->path); - if (next != NULL && strcmp (next, key) != 0) + if (next != NULL && ! STREQ (next, key)) { /* This is not the next version we need. It is a branch version which we want to ignore. */ @@ -5590,27 +6556,30 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) next = vers->next; /* Compare key and trunkversion now, because key points to - storage controlled by getrcskey. */ - if (strcmp (branchversion, key) == 0) + storage controlled by rcsbuf_getkey. */ + if (STREQ (branchversion, key)) isversion = 1; else isversion = 0; } - while ((n = getrcskey (fp, &key, &value, &vallen)) >= 0) + while (1) { + if (! rcsbuf_getkey (rcsbuf, &key, &value)) + error (1, 0, "%s does not appear to be a valid rcs file", + rcs->path); + if (log != NULL && isversion - && strcmp (key, "log") == 0 - && strcmp (branchversion, version) == 0) + && STREQ (key, "log") + && STREQ (branchversion, version)) { - *log = xmalloc (vallen); - memcpy (*log, value, vallen); - *loglen = vallen; + *log = rcsbuf_valcopy (rcsbuf, value, 0, loglen); } - if (strcmp (key, "text") == 0) + if (STREQ (key, "text")) { + rcsbuf_valpolish (rcsbuf, value, 0, &vallen); if (ishead) { if (! linevector_add (&curlines, value, vallen, NULL, 0)) @@ -5629,14 +6598,12 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) break; } } - if (n < 0) - goto l_error; if (isversion) { /* This is either the version we want, or it is the branchpoint to the version we want. */ - if (strcmp (branchversion, version) == 0) + if (STREQ (branchversion, version)) { /* This is the version we want. */ linevector_copy (&headlines, &curlines); @@ -5716,9 +6683,8 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) } while (next != NULL); free (branchversion); - - if (fclose (fp) < 0) - error (0, errno, "cannot close %s", rcs->path); + + rcsbuf_cache (rcs, rcsbuf); if (! foundhead) error (1, 0, "could not find desired version %s in %s", @@ -5822,125 +6788,148 @@ RCS_deltas (rcs, fp, version, op, text, len, log, loglen) linevector_free (&trunklines); return; - - l_error: - if (ferror (fp)) - error (1, errno, "cannot read %s", rcs->path); - else - error (1, 0, "%s does not appear to be a valid rcs file", - rcs->path); } +/* Read the information for a single delta from the RCS buffer RCSBUF, + whose name is RCSFILE. *KEYP and *VALP are either NULL, or the + first key/value pair to read, as set by rcsbuf_getkey. Return NULL + if there are no more deltas. Store the key/value pair which + terminated the read in *KEYP and *VALP. */ + static RCSVers * -getdelta (fp, rcsfile) - FILE *fp; +getdelta (rcsbuf, rcsfile, keyp, valp) + struct rcsbuffer *rcsbuf; char *rcsfile; + char **keyp; + char **valp; { RCSVers *vnode; char *key, *value, *cp; - long fpos; Node *kv; - vnode = (RCSVers *) xmalloc (sizeof (RCSVers)); - memset (vnode, 0, sizeof (RCSVers)); - - /* Get revision number. This uses getrcskey because it doesn't - croak when encountering unexpected input. As a result, we have - to play unholy games with `key' and `value'. */ - fpos = ftell (fp); - getrcskey (fp, &key, &value, NULL); + /* Get revision number if it wasn't passed in. This uses + rcsbuf_getkey because it doesn't croak when encountering + unexpected input. As a result, we have to play unholy games + with `key' and `value'. */ + if (*keyp != NULL) + { + key = *keyp; + value = *valp; + } + else + { + if (! rcsbuf_getkey (rcsbuf, &key, &value)) + error (1, 0, "%s: unexpected EOF", rcsfile); + } /* Make sure that it is a revision number and not a cabbage or something. */ for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) /* do nothing */ ; - if (*cp != '\0' || strncmp (RCSDATE, value, strlen (RCSDATE)) != 0) + /* Note that when comparing with RCSDATE, we are not massaging + VALUE from the string found in the RCS file. This is OK since + we know exactly what to expect. */ + if (*cp != '\0' || strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) != 0) { - (void) fseek (fp, fpos, SEEK_SET); - free (vnode); + *keyp = key; + *valp = value; return NULL; } + + vnode = (RCSVers *) xmalloc (sizeof (RCSVers)); + memset (vnode, 0, sizeof (RCSVers)); + vnode->version = xstrdup (key); - /* grab the value of the date from value */ - cp = value + strlen (RCSDATE);/* skip the "date" keyword */ + /* Grab the value of the date from value. Note that we are not + massaging VALUE from the string found in the RCS file. */ + cp = value + (sizeof RCSDATE) - 1; /* skip the "date" keyword */ while (whitespace (*cp)) /* take space off front of value */ cp++; vnode->date = xstrdup (cp); /* Get author field. */ - (void) getrcskey (fp, &key, &value, NULL); - /* FIXME: should be using errno in case of ferror. */ - if (key == NULL || strcmp (key, "author") != 0) + if (! rcsbuf_getkey (rcsbuf, &key, &value)) + { + error (1, 0, "unexpected end of file reading %s", rcsfile); + } + if (! STREQ (key, "author")) error (1, 0, "\ -unable to parse rcs file; `author' not in the expected place"); - vnode->author = xstrdup (value); +unable to parse %s; `author' not in the expected place", rcsfile); + vnode->author = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL); /* Get state field. */ - (void) getrcskey (fp, &key, &value, NULL); - /* FIXME: should be using errno in case of ferror. */ - if (key == NULL || strcmp (key, "state") != 0) + if (! rcsbuf_getkey (rcsbuf, &key, &value)) + { + error (1, 0, "unexpected end of file reading %s", rcsfile); + } + if (! STREQ (key, "state")) error (1, 0, "\ -unable to parse rcs file; `state' not in the expected place"); - vnode->state = xstrdup (value); - if (strcmp (value, "dead") == 0) +unable to parse %s; `state' not in the expected place", rcsfile); + vnode->state = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL); + if (STREQ (value, "dead")) { vnode->dead = 1; } /* Note that "branches" and "next" are in fact mandatory, according - to doc/RCSFILES. We perhaps should be giving an error if they - are not there. */ + to doc/RCSFILES. */ /* fill in the branch list (if any branches exist) */ - fpos = ftell (fp); - (void) getrcskey (fp, &key, &value, NULL); - /* FIXME: should be handling various error conditions better. */ - if (key != NULL && strcmp (key, RCSDESC) == 0) + if (! rcsbuf_getkey (rcsbuf, &key, &value)) + { + error (1, 0, "unexpected end of file reading %s", rcsfile); + } + if (STREQ (key, RCSDESC)) { - (void) fseek (fp, fpos, SEEK_SET); + *keyp = key; + *valp = value; + /* Probably could/should be a fatal error. */ + error (0, 0, "warning: 'branches' keyword missing from %s", rcsfile); return vnode; } if (value != (char *) NULL) { vnode->branches = getlist (); + /* Note that we are not massaging VALUE from the string found + in the RCS file. */ do_branches (vnode->branches, value); } /* fill in the next field if there is a next revision */ - fpos = ftell (fp); - (void) getrcskey (fp, &key, &value, NULL); - /* FIXME: should be handling various error conditions better. */ - if (key != NULL && strcmp (key, RCSDESC) == 0) + if (! rcsbuf_getkey (rcsbuf, &key, &value)) { - (void) fseek (fp, fpos, SEEK_SET); + error (1, 0, "unexpected end of file reading %s", rcsfile); + } + if (STREQ (key, RCSDESC)) + { + *keyp = key; + *valp = value; + /* Probably could/should be a fatal error. */ + error (0, 0, "warning: 'next' keyword missing from %s", rcsfile); return vnode; } if (value != (char *) NULL) - vnode->next = xstrdup (value); + vnode->next = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL); /* * XXX - this is where we put the symbolic link stuff??? * (into newphrases in the deltas). */ - /* FIXME: Does not correctly handle errors, e.g. from stdio. */ while (1) { - fpos = ftell (fp); - if (getrcskey (fp, &key, &value, NULL) < 0) - break; + if (! rcsbuf_getkey (rcsbuf, &key, &value)) + error (1, 0, "unexpected end of file reading %s", rcsfile); - assert (key != NULL); - - if (strcmp (key, RCSDESC) == 0) + if (STREQ (key, RCSDESC)) break; /* Enable use of repositories created by certain obsolete versions of CVS. This code should remain indefinately; there is no procedure for converting old repositories, and checking for it is harmless. */ - if (strcmp(key, RCSDEAD) == 0) + if (STREQ (key, RCSDEAD)) { vnode->dead = 1; if (vnode->state != NULL) @@ -5951,6 +6940,9 @@ unable to parse rcs file; `state' not in the expected place"); /* if we have a new revision number, we're done with this delta */ for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) /* do nothing */ ; + /* Note that when comparing with RCSDATE, we are not massaging + VALUE from the string found in the RCS file. This is OK + since we know exactly what to expect. */ if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0) break; @@ -5961,7 +6953,7 @@ unable to parse rcs file; `state' not in the expected place"); kv = getnode (); kv->type = RCSFIELD; kv->key = xstrdup (key); - kv->data = xstrdup (value); + kv->data = rcsbuf_valcopy (rcsbuf, value, 1, (size_t *) NULL); if (addnode (vnode->other_delta, kv) != 0) { /* Complaining about duplicate keys in newphrases seems @@ -5973,11 +6965,11 @@ unable to parse rcs file; `state' not in the expected place"); key, rcsfile); freenode (kv); } - } + } - /* We got here because we read beyond the end of a delta. Seek back - to the beginning of the erroneous read. */ - (void) fseek (fp, fpos, SEEK_SET); + /* Return the key which caused us to fail back to the caller. */ + *keyp = key; + *valp = value; return vnode; } @@ -5998,25 +6990,21 @@ freedeltatext (d) } static Deltatext * -RCS_getdeltatext (rcs, fp) +RCS_getdeltatext (rcs, fp, rcsbuf) RCSNode *rcs; FILE *fp; + struct rcsbuffer *rcsbuf; { char *num; char *key, *value; - int n; Node *p; Deltatext *d; - size_t textlen; /* Get the revision number. */ - n = getrevnum (fp, &num); - if (ferror (fp)) - error (1, errno, "%s: cannot read", rcs->path); - if (n == EOF) + if (! rcsbuf_getrevnum (rcsbuf, &num)) { - /* If n == EOF and num == NULL, it means we reached EOF - naturally. That's fine. */ + /* If num == NULL, it means we reached EOF naturally. That's + fine. */ if (num == NULL) return NULL; else @@ -6032,36 +7020,36 @@ RCS_getdeltatext (rcs, fp) d->version = xstrdup (num); /* Get the log message. */ - if (getrcskey (fp, &key, &value, NULL) < 0) + if (! rcsbuf_getkey (rcsbuf, &key, &value)) error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num); - if (strcmp (key, "log") != 0) + if (! STREQ (key, "log")) error (1, 0, "%s, delta %s: expected `log', got `%s'", rcs->path, num, key); - d->log = xstrdup (value); + d->log = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL); /* Get random newphrases. */ d->other = getlist(); - for (n = getrcskey (fp, &key, &value, &textlen); - n >= 0 && strcmp (key, "text") != 0; - n = getrcskey (fp, &key, &value, &textlen)) + while (1) { + if (! rcsbuf_getkey (rcsbuf, &key, &value)) + error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num); + + if (STREQ (key, "text")) + break; + p = getnode(); p->type = RCSFIELD; p->key = xstrdup (key); - p->data = xstrdup (value); + p->data = rcsbuf_valcopy (rcsbuf, value, 1, (size_t *) NULL); if (addnode (d->other, p) < 0) { error (0, 0, "warning: %s, delta %s: duplicate field `%s'", rcs->path, num, key); } } - if (n < 0) - error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num); /* Get the change text. We already know that this key is `text'. */ - d->text = (char *) malloc (textlen + 1); - d->len = textlen; - memcpy (d->text, value, textlen); + d->text = rcsbuf_valcopy (rcsbuf, value, 0, &d->len); return d; } @@ -6078,11 +7066,23 @@ RCS_getdeltatext (rcs, fp) not get corrupted. */ static int -putsymbol_proc (symnode, fp) +putsymbol_proc (symnode, fparg) Node *symnode; - void *fp; + void *fparg; { - return fprintf ((FILE *) fp, "\n\t%s:%s", symnode->key, symnode->data); + FILE *fp = (FILE *) fparg; + + /* A fiddly optimization: this code used to just call fprintf, but + in an old repository with hundreds of tags this can get called + hundreds of thousands of times when doing a cvs tag. Since + tagging is a relatively common operation, and using putc and + fputs is just as comprehensible, the change is worthwhile. */ + putc ('\n', fp); + putc ('\t', fp); + fputs (symnode->key, fp); + putc (':', fp); + fputs (symnode->data, fp); + return 0; } static int putlock_proc PROTO ((Node *, void *)); @@ -6134,9 +7134,9 @@ putrcsfield_proc (node, vfp) /* desc, log and text fields should not be terminated with semicolon; all other fields should be. */ - if (strcmp (node->key, "desc") != 0 && - strcmp (node->key, "log") != 0 && - strcmp (node->key, "text") != 0) + if (! STREQ (node->key, "desc") && + ! STREQ (node->key, "log") && + ! STREQ (node->key, "text")) { putc (';', fp); } @@ -6166,7 +7166,15 @@ RCS_putadmin (rcs, fp) fputs (";\n", fp); fputs (RCSSYMBOLS, fp); - walklist (RCS_symbols(rcs), putsymbol_proc, (void *) fp); + /* If we haven't had to convert the symbols to a list yet, don't + force a conversion now; just write out the string. */ + if (rcs->symbols == NULL && rcs->symbols_data != NULL) + { + fputs ("\n\t", fp); + fputs (rcs->symbols_data, fp); + } + else + walklist (RCS_symbols (rcs), putsymbol_proc, (void *) fp); fputs (";\n", fp); fputs ("locks", fp); @@ -6184,7 +7192,7 @@ RCS_putadmin (rcs, fp) expand_at_signs (rcs->comment, (off_t) strlen (rcs->comment), fp); fputs ("@;\n", fp); } - if (rcs->expand && strcmp (rcs->expand, "kv") != 0) + if (rcs->expand && ! STREQ (rcs->expand, "kv")) fprintf (fp, "%s\t@%s@;\n", RCSEXPAND, rcs->expand); walklist (rcs->other, putrcsfield_proc, (void *) fp); @@ -6306,40 +7314,68 @@ putdeltatext (fp, d) increasing order.) */ static void -RCS_copydeltas (rcs, fin, fout, newdtext, insertpt) +RCS_copydeltas (rcs, fin, rcsbufin, fout, newdtext, insertpt) RCSNode *rcs; FILE *fin; + struct rcsbuffer *rcsbufin; FILE *fout; Deltatext *newdtext; char *insertpt; { - Deltatext *dtext; + int actions; RCSVers *dadmin; Node *np; int insertbefore, found; + char *bufrest; + int nls; + size_t buflen; + char buf[8192]; + int got; + + /* Count the number of versions for which we have to do some + special operation. */ + actions = walklist (rcs->versions, count_delta_actions, (void *) NULL); /* Make a note of whether NEWDTEXT should be inserted before or after its INSERTPT. */ insertbefore = (newdtext != NULL && numdots (newdtext->version) == 1); - found = 0; - while ((dtext = RCS_getdeltatext (rcs, fin)) != NULL) + while (actions != 0 || newdtext != NULL) { - found = (insertpt != NULL && strcmp (dtext->version, insertpt) == 0); + Deltatext *dtext; + + dtext = RCS_getdeltatext (rcs, fin, rcsbufin); + + /* We shouldn't hit EOF here, because that would imply that + some action was not taken, or that we could not insert + NEWDTEXT. */ + if (dtext == NULL) + error (1, 0, "internal error: EOF too early in RCS_copydeltas"); + + found = (insertpt != NULL && STREQ (dtext->version, insertpt)); if (found && insertbefore) + { putdeltatext (fout, newdtext); + newdtext = NULL; + insertpt = NULL; + } np = findnode (rcs->versions, dtext->version); dadmin = (RCSVers *) np->data; /* If this revision has been outdated, just skip it. */ if (dadmin->outdated) + { + --actions; continue; + } /* Update the change text for this delta. New change text data may come from cvs admin -m, cvs admin -o, or cvs ci. */ if (dadmin->text != NULL) { + if (dadmin->text->log != NULL || dadmin->text->text != NULL) + --actions; if (dadmin->text->log != NULL) { free (dtext->log); @@ -6358,9 +7394,92 @@ RCS_copydeltas (rcs, fin, fout, newdtext, insertpt) freedeltatext (dtext); if (found && !insertbefore) + { putdeltatext (fout, newdtext); + newdtext = NULL; + insertpt = NULL; + } + } + + /* Copy the rest of the file directly, without bothering to + interpret it. The caller will handle error checking by calling + ferror. + + We just wrote a newline to the file, either in putdeltatext or + in the caller. However, we may not have read the corresponding + newline from the file, because rcsbuf_getkey returns as soon as + it finds the end of the '@' string for the desc or text key. + Therefore, we may read three newlines when we should really + only write two, and we check for that case here. This is not + an semantically important issue; we only do it to make our RCS + files look traditional. */ + + nls = 3; + + rcsbuf_get_buffered (rcsbufin, &bufrest, &buflen); + if (buflen > 0) + { + if (bufrest[0] != '\n' + || strncmp (bufrest, "\n\n\n", buflen < 3 ? buflen : 3) != 0) + { + nls = 0; + } + else + { + if (buflen < 3) + nls -= buflen; + else + { + ++bufrest; + --buflen; + nls = 0; + } + } + + fwrite (bufrest, 1, buflen, fout); + } + + while ((got = fread (buf, 1, sizeof buf, fin)) != 0) + { + if (nls > 0 + && got >= nls + && buf[0] == '\n' + && strncmp (buf, "\n\n\n", nls) == 0) + { + fwrite (buf + 1, 1, got - 1, fout); + } + else + { + fwrite (buf, 1, got, fout); + } + + nls = 0; + } +} + +/* A helper procedure for RCS_copydeltas. This is called via walklist + to count the number of RCS revisions for which some special action + is required. */ + +int +count_delta_actions (np, ignore) + Node *np; + void *ignore; +{ + RCSVers *dadmin; + + dadmin = (RCSVers *) np->data; + + if (dadmin->outdated) + return 1; + + if (dadmin->text != NULL + && (dadmin->text->log != NULL || dadmin->text->text != NULL)) + { + return 1; } - putc ('\n', fout); + + return 0; } /* RCS_internal_lockfile and RCS_internal_unlockfile perform RCS-style @@ -6468,6 +7587,12 @@ rcs_internal_unlockfile (fp, rcsfile) corrupting the repository. */ if (ferror (fp)) + /* The only case in which using errno here would be meaningful + is if we happen to have left errno unmolested since the call + which produced the error (e.g. fprintf). That is pretty + fragile even if it happens to sometimes be true. The real + solution is to check each call to fprintf rather than waiting + until the end like this. */ error (1, 0, "error writing to lock file %s", lockfile); if (fclose (fp) == EOF) error (1, errno, "error closing lock file %s", lockfile); @@ -6511,6 +7636,7 @@ RCS_rewrite (rcs, newdtext, insertpt) char *insertpt; { FILE *fin, *fout; + struct rcsbuffer rcsbufin; if (noexec) return; @@ -6522,10 +7648,7 @@ RCS_rewrite (rcs, newdtext, insertpt) RCS_putdesc (rcs, fout); /* Open the original RCS file and seek to the first delta text. */ - if ((fin = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ)) == NULL) - error (1, errno, "cannot open RCS file `%s' for reading", rcs->path); - if (fseek (fin, rcs->delta_pos, SEEK_SET) < 0) - error (1, errno, "cannot fseek in RCS file %s", rcs->path); + rcsbuf_cache_open (rcs, rcs->delta_pos, &fin, &rcsbufin); /* Update delta_pos to the current position in the output file. Do NOT move these statements: they must be done after fin has @@ -6535,10 +7658,19 @@ RCS_rewrite (rcs, newdtext, insertpt) if (rcs->delta_pos == -1) error (1, errno, "cannot ftell in RCS file %s", rcs->path); - RCS_copydeltas (rcs, fin, fout, newdtext, insertpt); + RCS_copydeltas (rcs, fin, &rcsbufin, fout, newdtext, insertpt); + /* We don't want to call rcsbuf_cache here, since we're about to + delete the file. */ + rcsbuf_close (&rcsbufin); if (ferror (fin)) - error (0, errno, "warning: when closing RCS file `%s'", rcs->path); + /* The only case in which using errno here would be meaningful + is if we happen to have left errno unmolested since the call + which produced the error (e.g. fread). That is pretty + fragile even if it happens to sometimes be true. The real + solution is to make sure that all the code which reads + from fin checks for errors itself (some does, some doesn't). */ + error (0, 0, "warning: when closing RCS file `%s'", rcs->path); if (fclose (fin) < 0) error (0, errno, "warning: closing RCS file `%s'", rcs->path); @@ -6563,13 +7695,18 @@ annotate_fileproc (callerdat, finfo) struct file_info *finfo; { FILE *fp = NULL; + struct rcsbuffer *rcsbufp = NULL; + struct rcsbuffer rcsbuf; char *version; if (finfo->rcs == NULL) return (1); if (finfo->rcs->flags & PARTIAL) - RCS_reparsercsfile (finfo->rcs, &fp); + { + RCS_reparsercsfile (finfo->rcs, &fp, &rcsbuf); + rcsbufp = &rcsbuf; + } version = RCS_getversion (finfo->rcs, tag, date, force_tag_match, (int *) NULL); @@ -6582,7 +7719,7 @@ annotate_fileproc (callerdat, finfo) cvs_outerr (finfo->fullname, 0); cvs_outerr ("\n***************\n", 0); - RCS_deltas (finfo->rcs, fp, version, RCS_ANNOTATE, (char **) NULL, + RCS_deltas (finfo->rcs, fp, rcsbufp, version, RCS_ANNOTATE, (char **) NULL, (size_t) NULL, (char **) NULL, (size_t *) NULL); free (version); return 0; |