#include "cvs.h"
#include "edit.h"
#include "hardlink.h"
#ifdef HAVE_MMAP
# include "getpagesize.h"
# include <sys/mman.h>
# ifndef MAP_FILE
# define MAP_FILE 0
# endif
# ifndef MAP_FAILED
# define MAP_FAILED ((void *)-1)
# endif
#endif
static const char *const kflags[] =
{"kv", "kvl", "k", "v", "o", "b", NULL};
enum kflag { KFLAG_KV = 0, KFLAG_KVL, KFLAG_K, KFLAG_V, KFLAG_O, KFLAG_B };
struct rcsbuffer
{
char *ptr;
char *ptrend;
FILE *fp;
const char *filename;
unsigned long pos;
size_t vlen;
int at_string;
int embedded_at;
};
static RCSNode *RCS_parsercsfile_i (FILE * fp, const char *rcsfile);
static char *RCS_getdatebranch (RCSNode * rcs, const char *date,
const char *branch);
static void rcsbuf_open (struct rcsbuffer *, FILE *fp,
const char *filename, unsigned long pos);
static void rcsbuf_close (struct rcsbuffer *);
static int rcsbuf_getkey (struct rcsbuffer *, char **keyp, char **valp);
static int rcsbuf_getrevnum (struct rcsbuffer *, char **revp);
static char *rcsbuf_fill (struct rcsbuffer *, char *ptr, char **keyp,
char **valp);
static int rcsbuf_valcmp (struct rcsbuffer *);
static char *rcsbuf_valcopy (struct rcsbuffer *, char *val, int polish,
size_t *lenp);
static void rcsbuf_valpolish (struct rcsbuffer *, char *val, int polish,
size_t *lenp);
static void rcsbuf_valpolish_internal (struct rcsbuffer *, char *to,
const char *from, size_t *lenp);
static off_t rcsbuf_ftello (struct rcsbuffer *);
static void rcsbuf_get_buffered (struct rcsbuffer *, char **datap,
size_t *lenp);
static void rcsbuf_cache (RCSNode *, struct rcsbuffer *);
static void rcsbuf_cache_close (void);
static void rcsbuf_cache_open (RCSNode *, off_t, FILE **, struct rcsbuffer *);
static int checkmagic_proc (Node *p, void *closure);
static void do_branches (List * list, char *val);
static void do_symbols (List * list, char *val);
static void do_locks (List * list, char *val);
static void free_rcsnode_contents (RCSNode *);
static void free_rcsvers_contents (RCSVers *);
static void rcsvers_delproc (Node * p);
static char *translate_symtag (RCSNode *, const char *);
static char *RCS_addbranch (RCSNode *, const char *);
static char *truncate_revnum_in_place (char *);
static char *truncate_revnum (const char *);
static char *printable_date (const char *);
static char *escape_keyword_value (const char *, int *);
static void expand_keywords (RCSNode *, RCSVers *, const char *,
const char *, size_t, enum kflag, char *,
size_t, char **, size_t *);
static void cmp_file_buffer (void *, const char *, size_t);
static RCSVers *getdelta (struct rcsbuffer *, char *, char **, char **);
static Deltatext *RCS_getdeltatext (RCSNode *, FILE *, struct rcsbuffer *);
static void freedeltatext (Deltatext *);
static void RCS_putadmin (RCSNode *, FILE *);
static void RCS_putdtree (RCSNode *, char *, FILE *);
static void RCS_putdesc (RCSNode *, FILE *);
static void putdelta (RCSVers *, FILE *);
static int putrcsfield_proc (Node *, void *);
static int putsymbol_proc (Node *, void *);
static void RCS_copydeltas (RCSNode *, FILE *, struct rcsbuffer *, FILE *,
Deltatext *, char *);
static int count_delta_actions (Node *, void *);
static void putdeltatext (FILE *, Deltatext *);
static FILE *rcs_internal_lockfile (char *);
static void rcs_internal_unlockfile (FILE *, char *);
static char *rcs_lockfilename (const char *);
#define STREQ(a, b) (*(char *)(a) == *(char *)(b) && strcmp ((a), (b)) == 0)
static char * getfullCVSname (char *, char **);
static const char spacetab[] = {
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
#define whitespace(c) (spacetab[(unsigned char)c] != 0)
static char *rcs_lockfile = NULL;
static int rcs_lockfd = -1;
static char *
locate_rcs (const char *repository, const char *file, int *inattic)
{
char *retval;
retval = xmalloc (strlen (repository)
+ sizeof (CVSATTIC)
+ strlen (file)
+ sizeof (RCSEXT)
+ 3);
sprintf (retval, "%s/%s%s", repository, file, RCSEXT);
if (isreadable (retval))
{
if (inattic)
*inattic = 0;
return retval;
}
sprintf (retval, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
if (isreadable (retval))
{
if (inattic)
*inattic = 1;
return retval;
}
free (retval);
return NULL;
}
RCSNode *
RCS_parse (const char *file, const char *repos)
{
RCSNode *rcs;
FILE *fp;
RCSNode *retval = NULL;
char *rcsfile;
int inattic;
rcsbuf_cache_close ();
if (!(rcsfile = locate_rcs (repos, file, &inattic)))
{
}
else if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)))
{
rcs = RCS_parsercsfile_i (fp, rcsfile);
if (rcs)
{
rcs->flags |= VALID;
if (inattic)
rcs->flags |= INATTIC;
}
free (rcsfile);
retval = rcs;
}
else if (!existence_error (errno))
{
error (0, errno, "cannot open `%s'", rcsfile);
free (rcsfile);
}
return retval;
}
RCSNode *
RCS_parsercsfile (const char *rcsfile)
{
FILE *fp;
RCSNode *rcs;
rcsbuf_cache_close ();
if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) == NULL)
{
error (0, errno, "Couldn't open rcs file `%s'", rcsfile);
return NULL;
}
rcs = RCS_parsercsfile_i (fp, rcsfile);
return rcs;
}
static RCSNode *
RCS_parsercsfile_i (FILE *fp, const char *rcsfile)
{
RCSNode *rdata;
struct rcsbuffer rcsbuf;
char *key, *value;
rdata = xmalloc (sizeof (RCSNode));
memset (rdata, 0, sizeof (RCSNode));
rdata->refcount = 1;
rdata->path = xstrdup (rcsfile);
rdata->print_path = xstrdup (primary_root_inverse_translate (rcsfile));
rcsbuf_open (&rcsbuf, fp, rcsfile, 0);
if (! rcsbuf_getkey (&rcsbuf, &key, &value))
goto l_error;
if (STREQ (key, RCSDESC))
goto l_error;
if (STREQ (RCSHEAD, key) && value != NULL)
rdata->head = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
if (! rcsbuf_getkey (&rcsbuf, &key, &value))
goto l_error;
if (STREQ (key, RCSDESC))
goto l_error;
if (STREQ (RCSBRANCH, key) && value != NULL)
{
char *cp;
rdata->branch = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
if ((numdots (rdata->branch) & 1) != 0)
{
cp = strrchr (rdata->branch, '.');
*cp = '\0';
}
}
while (1)
{
char *cp;
if (STREQ (RCSEXPAND, key))
{
rdata->expand = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
break;
}
for (cp = key;
(isdigit ((unsigned char)*cp) || *cp == '.') && *cp != '\0';
cp++)
;
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;
}
void
RCS_reparsercsfile (RCSNode *rdata, FILE **pfp, struct rcsbuffer *rcsbufp)
{
FILE *fp;
char *rcsfile;
struct rcsbuffer rcsbuf;
Node *q, *kv;
RCSVers *vnode;
int gotkey;
char *cp;
char *key, *value;
assert (rdata != NULL);
rcsfile = rdata->path;
rcsbuf_cache_open (rdata, 0, &fp, &rcsbuf);
rdata->versions = getlist ();
gotkey = 0;
for (;;)
{
if (!gotkey)
{
if (! rcsbuf_getkey (&rcsbuf, &key, &value))
{
error (1, 0, "`%s' does not appear to be a valid rcs file",
rcsfile);
}
}
gotkey = 0;
if (STREQ (key, RCSHEAD)
|| STREQ (key, RCSBRANCH)
|| STREQ (key, RCSEXPAND))
{
continue;
}
if (STREQ (key, "access"))
{
if (value != NULL)
{
if (rdata->access)
{
error (0, 0,
"Duplicate `access' keyword found in RCS file.");
free (rdata->access);
}
rdata->access = rcsbuf_valcopy (&rcsbuf, value, 1, NULL);
}
continue;
}
if (STREQ (key, "locks"))
{
if (value != NULL)
{
if (rdata->locks_data)
{
error (0, 0,
"Duplicate `locks' keyword found in RCS file.");
free (rdata->locks_data);
}
rdata->locks_data = rcsbuf_valcopy (&rcsbuf, value, 0, 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
gotkey = 1;
continue;
}
if (STREQ (RCSSYMBOLS, key))
{
if (value != NULL)
{
if (rdata->symbols_data)
{
error (0, 0,
"Duplicate `%s' keyword found in RCS file.",
RCSSYMBOLS);
free (rdata->symbols_data);
}
rdata->symbols_data = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
}
continue;
}
for (cp = key;
(isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0';
cp++)
;
if (*cp == '\0' && strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) == 0)
break;
if (STREQ (key, RCSDESC))
break;
if (STREQ (key, "comment"))
{
if (rdata->comment)
{
error (0, 0,
"warning: duplicate key `%s' in RCS file `%s'",
key, rcsfile);
free (rdata->comment);
}
rdata->comment = rcsbuf_valcopy (&rcsbuf, value, 0, NULL);
continue;
}
if (rdata->other == NULL)
rdata->other = getlist ();
kv = getnode ();
kv->type = rcsbuf_valcmp (&rcsbuf) ? RCSCMPFLD : RCSFIELD;
kv->key = xstrdup (key);
kv->data = rcsbuf_valcopy (&rcsbuf, value, kv->type == RCSFIELD, NULL);
if (addnode (rdata->other, kv) != 0)
{
error (0, 0, "warning: duplicate key `%s' in RCS file `%s'",
key, rcsfile);
freenode (kv);
}
}
while ((vnode = getdelta (&rcsbuf, rcsfile, &key, &value)) != NULL)
{
q = getnode ();
q->type = RCSVERS;
q->delproc = rcsvers_delproc;
q->data = vnode;
q->key = vnode->version;
if (addnode (rdata->versions, q) != 0)
{
#if 0
purify_printf("WARNING: Adding duplicate version: %s (%s)\n",
q->key, rcsfile);
freenode (q);
#endif
}
}
if (STREQ (key, RCSDESC))
{
if (rdata->desc != NULL)
{
error (0, 0,
"warning: duplicate key `%s' in RCS file `%s'",
key, rcsfile);
free (rdata->desc);
}
rdata->desc = rcsbuf_valcopy (&rcsbuf, value, 1, NULL);
}
rdata->delta_pos = rcsbuf_ftello (&rcsbuf);
if (pfp == NULL)
rcsbuf_cache (rdata, &rcsbuf);
else
{
*pfp = fp;
*rcsbufp = rcsbuf;
}
rdata->flags &= ~PARTIAL;
}
int
RCS_setattic (RCSNode *rcs, int toattic)
{
char *newpath;
const char *p;
char *q;
rcsbuf_cache_close ();
if (toattic)
{
mode_t omask;
if (rcs->flags & INATTIC)
return 0;
newpath = xmalloc (strlen (rcs->path) + sizeof CVSATTIC + 5);
p = last_component (rcs->path);
strncpy (newpath, rcs->path, p - rcs->path);
strcpy (newpath + (p - rcs->path), CVSATTIC);
omask = umask (cvsumask);
if (CVS_MKDIR (newpath, 0777) < 0 && errno != EEXIST)
error (0, errno, "cannot make directory %s", newpath);
(void) umask (omask);
strcat (newpath, "/");
strcat (newpath, p);
if (CVS_RENAME (rcs->path, newpath) < 0)
{
int save_errno = errno;
if (isreadable (rcs->path) || !isreadable (newpath))
{
error (0, save_errno, "cannot rename %s to %s",
rcs->path, newpath);
free (newpath);
return 1;
}
}
}
else
{
if (!(rcs->flags & INATTIC))
return 0;
newpath = xmalloc (strlen (rcs->path));
p = last_component (rcs->path);
strncpy (newpath, rcs->path, p - rcs->path - 1);
newpath[p - rcs->path - 1] = '\0';
q = newpath + (p - rcs->path - 1) - (sizeof CVSATTIC - 1);
assert (strncmp (q, CVSATTIC, sizeof CVSATTIC - 1) == 0);
strcpy (q, p);
if (CVS_RENAME (rcs->path, newpath) < 0)
{
error (0, errno, "failed to move `%s' out of the attic",
rcs->path);
free (newpath);
return 1;
}
}
free (rcs->path);
rcs->path = newpath;
return 0;
}
void
RCS_fully_parse (RCSNode *rcs)
{
FILE *fp;
struct rcsbuffer rcsbuf;
RCS_reparsercsfile (rcs, &fp, &rcsbuf);
while (1)
{
char *key, *value;
Node *vers;
RCSVers *vnode;
if (!rcsbuf_getrevnum (&rcsbuf, &key))
break;
vers = findnode (rcs->versions, key);
if (vers == NULL)
error (1, 0,
"mismatch in rcs file %s between deltas and deltatexts (%s)",
rcs->print_path, key);
vnode = vers->data;
while (rcsbuf_getkey (&rcsbuf, &key, &value))
{
if (!STREQ (key, "text"))
{
Node *kv;
if (vnode->other == NULL)
vnode->other = getlist ();
kv = getnode ();
kv->type = rcsbuf_valcmp (&rcsbuf) ? RCSCMPFLD : RCSFIELD;
kv->key = xstrdup (key);
kv->data = rcsbuf_valcopy (&rcsbuf, value, kv->type == RCSFIELD,
NULL);
if (addnode (vnode->other, kv) != 0)
{
error (0, 0,
"\
warning: duplicate key `%s' in version `%s' of RCS file `%s'",
key, vnode->version, rcs->print_path);
freenode (kv);
}
continue;
}
if (!STREQ (vnode->version, rcs->head))
{
unsigned long add, del;
char buf[50];
Node *kv;
add = 0;
del = 0;
if (value != NULL)
{
size_t vallen;
const char *cp;
rcsbuf_valpolish (&rcsbuf, value, 0, &vallen);
cp = value;
while (cp < value + vallen)
{
char op;
unsigned long count;
op = *cp++;
if (op != 'a' && op != 'd')
error (1, 0, "\
unrecognized operation '\\x%x' in %s",
op, rcs->print_path);
(void) strtoul (cp, (char **) &cp, 10);
if (*cp++ != ' ')
error (1, 0, "space expected in %s revision %s",
rcs->print_path, vnode->version);
count = strtoul (cp, (char **) &cp, 10);
if (*cp++ != '\012')
error (1, 0, "linefeed expected in %s revision %s",
rcs->print_path, vnode->version);
if (op == 'd')
del += count;
else
{
add += count;
while (count != 0)
{
if (*cp == '\012')
--count;
else if (cp == value + vallen)
{
if (count != 1)
error (1, 0, "\
premature end of value in %s revision %s",
rcs->print_path, vnode->version);
else
break;
}
++cp;
}
}
}
}
sprintf (buf, "%lu", add);
kv = getnode ();
kv->type = RCSFIELD;
kv->key = xstrdup (";add");
kv->data = xstrdup (buf);
if (addnode (vnode->other, kv) != 0)
{
error (0, 0,
"\
warning: duplicate key `%s' in version `%s' of RCS file `%s'",
key, vnode->version, rcs->print_path);
freenode (kv);
}
sprintf (buf, "%lu", del);
kv = getnode ();
kv->type = RCSFIELD;
kv->key = xstrdup (";delete");
kv->data = xstrdup (buf);
if (addnode (vnode->other, kv) != 0)
{
error (0, 0,
"\
warning: duplicate key `%s' in version `%s' of RCS file `%s'",
key, vnode->version, rcs->print_path);
freenode (kv);
}
}
break;
}
}
rcsbuf_cache (rcs, &rcsbuf);
}
void
freercsnode (RCSNode **rnodep)
{
if (rnodep == NULL || *rnodep == NULL)
return;
((*rnodep)->refcount)--;
if ((*rnodep)->refcount != 0)
{
*rnodep = NULL;
return;
}
free ((*rnodep)->path);
free ((*rnodep)->print_path);
if ((*rnodep)->head != NULL)
free ((*rnodep)->head);
if ((*rnodep)->branch != NULL)
free ((*rnodep)->branch);
free_rcsnode_contents (*rnodep);
free (*rnodep);
*rnodep = NULL;
}
static void
free_rcsnode_contents (RCSNode *rnode)
{
dellist (&rnode->versions);
if (rnode->symbols != NULL)
dellist (&rnode->symbols);
if (rnode->symbols_data != NULL)
free (rnode->symbols_data);
if (rnode->expand != NULL)
free (rnode->expand);
if (rnode->other != NULL)
dellist (&rnode->other);
if (rnode->access != NULL)
free (rnode->access);
if (rnode->locks_data != NULL)
free (rnode->locks_data);
if (rnode->locks != NULL)
dellist (&rnode->locks);
if (rnode->comment != NULL)
free (rnode->comment);
if (rnode->desc != NULL)
free (rnode->desc);
}
static void
free_rcsvers_contents (RCSVers *rnode)
{
if (rnode->branches != NULL)
dellist (&rnode->branches);
if (rnode->date != NULL)
free (rnode->date);
if (rnode->next != NULL)
free (rnode->next);
if (rnode->author != NULL)
free (rnode->author);
if (rnode->state != NULL)
free (rnode->state);
if (rnode->other != NULL)
dellist (&rnode->other);
if (rnode->other_delta != NULL)
dellist (&rnode->other_delta);
if (rnode->text != NULL)
freedeltatext (rnode->text);
free (rnode);
}
static void
rcsvers_delproc (Node *p)
{
free_rcsvers_contents (p->data);
}
#define RCSBUF_BUFSIZE (8192)
static char *rcsbuf_buffer = NULL;
static size_t rcsbuf_buffer_size = 0;
static int rcsbuf_inuse;
static void
rcsbuf_open (struct rcsbuffer *rcsbuf, FILE *fp, const char *filename,
long unsigned int pos)
{
if (rcsbuf_inuse)
error (1, 0, "rcsbuf_open: internal error");
rcsbuf_inuse = 1;
#ifdef HAVE_MMAP
{
struct stat fs;
size_t mmap_off = 0;
if ( fstat (fileno(fp), &fs) < 0 )
error ( 1, errno, "Could not stat RCS archive %s for mapping", filename );
if (pos)
{
size_t ps = getpagesize ();
mmap_off = ( pos / ps ) * ps;
}
rcsbuf_buffer = mmap ( NULL, fs.st_size - mmap_off,
PROT_READ | PROT_WRITE,
MAP_PRIVATE, fileno(fp), mmap_off );
if ( rcsbuf_buffer == NULL || rcsbuf_buffer == MAP_FAILED )
error ( 1, errno, "Could not map memory to RCS archive %s", filename );
rcsbuf_buffer_size = fs.st_size - mmap_off;
rcsbuf->ptr = rcsbuf_buffer + pos - mmap_off;
rcsbuf->ptrend = rcsbuf_buffer + fs.st_size - mmap_off;
rcsbuf->pos = mmap_off;
}
#else
if (rcsbuf_buffer_size < RCSBUF_BUFSIZE)
expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size, RCSBUF_BUFSIZE);
rcsbuf->ptr = rcsbuf_buffer;
rcsbuf->ptrend = rcsbuf_buffer;
rcsbuf->pos = pos;
#endif
rcsbuf->fp = fp;
rcsbuf->filename = filename;
rcsbuf->vlen = 0;
rcsbuf->at_string = 0;
rcsbuf->embedded_at = 0;
}
static void
rcsbuf_close (struct rcsbuffer *rcsbuf)
{
if (! rcsbuf_inuse)
error (1, 0, "rcsbuf_close: internal error");
#ifdef HAVE_MMAP
munmap ( rcsbuf_buffer, rcsbuf_buffer_size );
#endif
rcsbuf_inuse = 0;
}
static int
rcsbuf_getkey (struct rcsbuffer *rcsbuf, char **keyp, char **valp)
{
register const char * const my_spacetab = spacetab;
register char *ptr, *ptrend;
char c;
#define my_whitespace(c) (my_spacetab[(unsigned char)c] != 0)
rcsbuf->vlen = 0;
rcsbuf->at_string = 0;
rcsbuf->embedded_at = 0;
ptr = rcsbuf->ptr;
ptrend = rcsbuf->ptrend;
assert (ptr >= rcsbuf_buffer && ptr <= rcsbuf_buffer + rcsbuf_buffer_size);
assert (ptrend >= rcsbuf_buffer && ptrend <= rcsbuf_buffer + rcsbuf_buffer_size);
#ifndef HAVE_MMAP
if (ptr - rcsbuf_buffer >= RCSBUF_BUFSIZE)
{
int len;
len = ptrend - ptr;
assert (len <= RCSBUF_BUFSIZE);
rcsbuf->pos += ptr - rcsbuf_buffer;
memcpy (rcsbuf_buffer, ptr, len);
ptr = rcsbuf_buffer;
ptrend = ptr + len;
rcsbuf->ptrend = ptrend;
}
#endif
while (1)
{
if (ptr >= ptrend)
{
ptr = rcsbuf_fill (rcsbuf, ptr, NULL, NULL);
if (ptr == NULL)
return 0;
ptrend = rcsbuf->ptrend;
}
c = *ptr;
if (! my_whitespace (c))
break;
++ptr;
}
*keyp = ptr;
if (c != ';')
{
while (1)
{
++ptr;
if (ptr >= ptrend)
{
ptr = rcsbuf_fill (rcsbuf, ptr, keyp, NULL);
if (ptr == NULL)
error (1, 0, "EOF in key in RCS file %s",
primary_root_inverse_translate (rcsbuf->filename));
ptrend = rcsbuf->ptrend;
}
c = *ptr;
if (c == ';' || my_whitespace (c))
break;
}
}
*ptr = '\0';
++ptr;
if (c == ';')
{
*valp = NULL;
rcsbuf->ptr = ptr;
return 1;
}
while (1)
{
if (ptr >= ptrend)
{
ptr = rcsbuf_fill (rcsbuf, ptr, keyp, NULL);
if (ptr == NULL)
error (1, 0, "EOF while looking for value in RCS file %s",
primary_root_inverse_translate (rcsbuf->filename));
ptrend = rcsbuf->ptrend;
}
c = *ptr;
if (c == ';')
{
*valp = NULL;
rcsbuf->ptr = ptr + 1;
return 1;
}
if (! my_whitespace (c))
break;
++ptr;
}
if (c != '@')
*valp = ptr;
else
{
char *pat;
size_t vlen;
rcsbuf->at_string = 1;
++ptr;
*valp = ptr;
while (1)
{
while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
{
ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
if (ptr == NULL)
error (1, 0,
"EOF while looking for end of string in RCS file %s",
primary_root_inverse_translate (rcsbuf->filename));
ptrend = rcsbuf->ptrend;
}
if (pat + 1 >= ptrend)
{
pat = rcsbuf_fill (rcsbuf, pat, keyp, valp);
if (pat == NULL)
{
pat = rcsbuf->ptrend - 1;
}
ptrend = rcsbuf->ptrend;
}
if (pat + 1 >= ptrend || pat[1] != '@')
break;
++rcsbuf->embedded_at;
ptr = pat + 2;
}
*pat = '\0';
vlen = pat - *valp;
if (vlen == 0)
*valp = NULL;
rcsbuf->vlen = vlen;
ptr = pat + 1;
}
{
char *k;
k = *keyp;
if (STREQ (k, RCSDESC)
|| STREQ (k, "text")
|| STREQ (k, "log"))
{
if (c != '@')
*valp = NULL;
rcsbuf->ptr = ptr;
return 1;
}
}
if (c == '@')
{
while (1)
{
char n;
if (ptr >= ptrend)
{
ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp);
if (ptr == NULL)
error (1, 0, "EOF in value in RCS file %s",
primary_root_inverse_translate (rcsbuf->filename));
ptrend = rcsbuf->ptrend;
}
n = *ptr;
if (n == ';')
{
rcsbuf->ptr = ptr + 1;
return 1;
}
if (! my_whitespace (n))
break;
++ptr;
}
((*valp)--)[rcsbuf->vlen++] = '@';
}
while (1)
{
char *start, *psemi, *pat;
start = ptr;
while ((psemi = memchr (ptr, ';', ptrend - ptr)) == NULL)
{
int slen;
slen = start - *valp;
ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
if (ptr == NULL)
error (1, 0, "EOF in value in RCS file %s",
primary_root_inverse_translate (rcsbuf->filename));
start = *valp + slen;
ptrend = rcsbuf->ptrend;
}
pat = memchr (start, '@', psemi - start);
if (pat == NULL)
{
size_t vlen;
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;
}
rcsbuf->at_string = 1;
rcsbuf->embedded_at = -1;
ptr = pat + 1;
while (1)
{
while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
{
ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
if (ptr == NULL)
error (1, 0,
"EOF while looking for end of string in RCS file %s",
primary_root_inverse_translate (rcsbuf->filename));
ptrend = rcsbuf->ptrend;
}
if (pat + 1 >= ptrend)
{
ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp);
if (ptr == NULL)
error (1, 0, "EOF in value in RCS file %s",
primary_root_inverse_translate (rcsbuf->filename));
ptrend = rcsbuf->ptrend;
}
if (pat[1] != '@')
break;
ptr = pat + 2;
}
ptr = pat + 1;
}
#undef my_whitespace
}
static int
rcsbuf_getrevnum (struct rcsbuffer *rcsbuf, char **revp)
{
char *ptr, *ptrend;
char c;
ptr = rcsbuf->ptr;
ptrend = rcsbuf->ptrend;
*revp = NULL;
while (1)
{
if (ptr >= ptrend)
{
ptr = rcsbuf_fill (rcsbuf, ptr, NULL, NULL);
if (ptr == NULL)
return 0;
ptrend = rcsbuf->ptrend;
}
c = *ptr;
if (! whitespace (c))
break;
++ptr;
}
if (! isdigit ((unsigned char) c) && c != '.')
error (1, 0,
"\
unexpected '\\x%x' reading revision number in RCS file %s",
c, primary_root_inverse_translate (rcsbuf->filename));
*revp = ptr;
do
{
++ptr;
if (ptr >= ptrend)
{
ptr = rcsbuf_fill (rcsbuf, ptr, revp, NULL);
if (ptr == NULL)
error (1, 0,
"unexpected EOF reading revision number in RCS file %s",
primary_root_inverse_translate (rcsbuf->filename));
ptrend = rcsbuf->ptrend;
}
c = *ptr;
}
while (isdigit ((unsigned char) c) || c == '.');
if (! whitespace (c))
error (1, 0, "\
unexpected '\\x%x' reading revision number in RCS file %s",
c, primary_root_inverse_translate (rcsbuf->filename));
*ptr = '\0';
rcsbuf->ptr = ptr + 1;
return 1;
}
static char *
rcsbuf_fill (struct rcsbuffer *rcsbuf, char *ptr, char **keyp, char **valp)
{
#ifdef HAVE_MMAP
return NULL;
#else
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;
koff = keyp == NULL ? 0 : *keyp - rcsbuf_buffer;
voff = valp == NULL ? 0 : *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 = rcsbuf_buffer + koff;
if (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;
#endif
}
static int
rcsbuf_valcmp (struct rcsbuffer *rcsbuf)
{
return rcsbuf->at_string && rcsbuf->embedded_at < 0;
}
static char *
rcsbuf_valcopy (struct rcsbuffer *rcsbuf, char *val, int polish, size_t *lenp)
{
size_t vlen;
int embedded_at;
char *ret;
if (val == NULL)
{
if (lenp != NULL)
*lenp = 0;
return NULL;
}
vlen = rcsbuf->vlen;
embedded_at = rcsbuf->embedded_at < 0 ? 0 : rcsbuf->embedded_at;
ret = xmalloc (vlen - embedded_at + 1);
if (rcsbuf->at_string ? embedded_at == 0 : ! polish)
{
memcpy (ret, val, vlen + 1);
if (lenp != NULL)
*lenp = vlen;
return ret;
}
rcsbuf_valpolish_internal (rcsbuf, ret, val, lenp);
return ret;
}
static void
rcsbuf_valpolish (struct rcsbuffer *rcsbuf, char *val, int polish,
size_t *lenp)
{
if (val == NULL)
{
if (lenp != NULL)
*lenp= 0;
return;
}
if (rcsbuf->at_string ? rcsbuf->embedded_at == 0 : ! polish)
{
if (lenp != NULL)
*lenp = rcsbuf->vlen;
return;
}
rcsbuf_valpolish_internal (rcsbuf, val, val, lenp);
}
static void
rcsbuf_valpolish_internal (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)
{
char c;
c = *from;
if (whitespace (c))
{
while (whitespace (from[1]))
{
++from;
--clen;
}
c = ' ';
}
*to++ = c;
}
*to = '\0';
if (lenp != NULL)
*lenp = to - orig_to;
}
else
{
const char *orig_from;
char *orig_to;
int embedded_at;
size_t clen;
orig_from = from;
orig_to = to;
embedded_at = rcsbuf->embedded_at;
assert (embedded_at > 0);
if (lenp != NULL)
*lenp = len - embedded_at;
for (clen = len; clen > 0; ++from, --clen)
{
char c;
c = *from;
*to++ = c;
if (c == '@')
{
++from;
if (*from != '@' || clen == 0)
abort ();
--clen;
--embedded_at;
if (embedded_at == 0)
{
if (orig_to != orig_from)
memcpy (to, from + 1, clen - 1);
else
memmove (to, from + 1, clen - 1);
from += clen;
to += clen - 1;
break;
}
}
}
assert (from == orig_from + len
&& to == orig_to + (len - rcsbuf->embedded_at));
*to = '\0';
}
}
#ifdef PRESERVE_PERMISSIONS_SUPPORT
static char *
rcsbuf_valword (struct rcsbuffer *rcsbuf, char **valp)
{
register const char * const my_spacetab = spacetab;
register char *ptr, *pat;
char c;
# define my_whitespace(c) (my_spacetab[(unsigned char)c] != 0)
if (*valp == NULL)
return NULL;
for (ptr = *valp; my_whitespace (*ptr); ++ptr) ;
if (*ptr == '\0')
{
assert (ptr - *valp == rcsbuf->vlen);
*valp = NULL;
rcsbuf->vlen = 0;
return NULL;
}
c = *ptr;
if (c == ':')
{
rcsbuf->vlen -= ++ptr - *valp;
*valp = ptr;
return xstrdup (":");
}
if (c == '@')
{
int embedded_at = 0;
size_t vlen;
pat = ++ptr;
while ((pat = strchr (pat, '@')) != NULL)
{
if (pat[1] != '@')
break;
++embedded_at;
pat += 2;
}
*pat++ = '\0';
assert (rcsbuf->at_string);
vlen = rcsbuf->vlen - (pat - *valp);
rcsbuf->vlen = pat - ptr - 1;
rcsbuf->embedded_at = embedded_at;
ptr = rcsbuf_valcopy (rcsbuf, ptr, 0, NULL);
*valp = pat;
rcsbuf->vlen = vlen;
if (strchr (pat, '@') == NULL)
rcsbuf->at_string = 0;
else
rcsbuf->embedded_at = -1;
return ptr;
}
if (c == '$' || c == '.' || c == ',')
error (1, 0, "invalid special character in RCS field in %s",
primary_root_inverse_translate (rcsbuf->filename));
pat = ptr;
while (1)
{
c = *++pat;
if (!isprint ((unsigned char) c) ||
c == ';' || c == '$' || c == ',' || c == '@' || c == ':')
break;
}
if (c != '\0' && !my_whitespace (c))
error (1, 0, "invalid special character in RCS field in %s",
primary_root_inverse_translate (rcsbuf->filename));
*pat = '\0';
rcsbuf->vlen -= pat - *valp;
*valp = pat;
return xstrdup (ptr);
# undef my_whitespace
}
#endif
static off_t
rcsbuf_ftello (struct rcsbuffer *rcsbuf)
{
return rcsbuf->pos + rcsbuf->ptr - rcsbuf_buffer;
}
static void
rcsbuf_get_buffered (struct rcsbuffer *rcsbuf, char **datap, size_t *lenp)
{
*datap = rcsbuf->ptr;
*lenp = rcsbuf->ptrend - rcsbuf->ptr;
}
static RCSNode *cached_rcs;
static struct rcsbuffer cached_rcsbuf;
static void
rcsbuf_cache (RCSNode *rcs, struct rcsbuffer *rcsbuf)
{
if (cached_rcs != NULL)
rcsbuf_cache_close ();
cached_rcs = rcs;
++rcs->refcount;
cached_rcsbuf = *rcsbuf;
}
static void
rcsbuf_cache_close (void)
{
if (cached_rcs != NULL)
{
rcsbuf_close (&cached_rcsbuf);
if (fclose (cached_rcsbuf.fp) != 0)
error (0, errno, "cannot close %s", cached_rcsbuf.filename);
freercsnode (&cached_rcs);
cached_rcs = NULL;
}
}
static void
rcsbuf_cache_open (RCSNode *rcs, off_t pos, FILE **pfp,
struct rcsbuffer *prcsbuf)
{
#ifndef HAVE_MMAP
if (cached_rcs == rcs)
{
if (rcsbuf_ftello (&cached_rcsbuf) != pos)
{
if (fseeko (cached_rcsbuf.fp, pos, SEEK_SET) != 0)
error (1, 0, "cannot fseeko RCS file %s",
cached_rcsbuf.filename);
cached_rcsbuf.ptr = rcsbuf_buffer;
cached_rcsbuf.ptrend = rcsbuf_buffer;
cached_rcsbuf.pos = pos;
}
*pfp = cached_rcsbuf.fp;
cached_rcsbuf.filename = rcs->path;
*prcsbuf = cached_rcsbuf;
cached_rcs = NULL;
--rcs->refcount;
if (rcs->refcount <= 0)
error (1, 0, "rcsbuf_cache_open: internal error");
}
else
{
#endif
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);
#ifndef HAVE_MMAP
if (pos != 0)
{
if (fseeko (*pfp, pos, SEEK_SET) != 0)
error (1, 0, "cannot fseeko RCS file %s", rcs->path);
}
#endif
rcsbuf_open (prcsbuf, *pfp, rcs->path, pos);
#ifndef HAVE_MMAP
}
#endif
}
static void
do_symbols (List *list, char *val)
{
Node *p;
char *cp = val;
char *tag, *rev;
assert (cp);
for (;;)
{
while (whitespace (*cp))
cp++;
if (*cp == '\0')
break;
tag = cp;
cp = strchr (cp, ':');
*cp++ = '\0';
rev = cp;
while (!whitespace (*cp) && *cp != '\0')
cp++;
if (*cp != '\0')
*cp++ = '\0';
p = getnode ();
p->key = xstrdup (tag);
p->data = xstrdup (rev);
(void) addnode (list, p);
}
}
static void
do_locks (List *list, char *val)
{
Node *p;
char *cp = val;
char *user, *rev;
assert (cp);
for (;;)
{
while (whitespace (*cp))
cp++;
if (*cp == '\0')
break;
user = cp;
cp = strchr (cp, ':');
*cp++ = '\0';
rev = cp;
while (!whitespace (*cp) && *cp != '\0')
cp++;
if (*cp != '\0')
*cp++ = '\0';
p = getnode ();
p->key = xstrdup (rev);
p->data = xstrdup (user);
(void) addnode (list, p);
}
}
static void
do_branches (List *list, char *val)
{
Node *p;
char *cp = val;
char *branch;
for (;;)
{
while (whitespace (*cp))
cp++;
if (*cp == '\0')
break;
branch = cp;
while (!whitespace (*cp) && *cp != '\0')
cp++;
if (*cp != '\0')
*cp++ = '\0';
p = getnode ();
p->key = xstrdup (branch);
(void) addnode (list, p);
}
}
char *
RCS_getversion (RCSNode *rcs, const char *tag, const char *date,
int force_tag_match, int *simple_tag)
{
if (simple_tag != NULL)
*simple_tag = 0;
assert (rcs != NULL);
if (tag && date)
{
char *branch, *rev;
if (! RCS_nodeisbranch (rcs, tag))
{
return NULL;
}
if (! isdigit ((unsigned char) tag[0]))
branch = RCS_whatbranch (rcs, tag);
else
branch = xstrdup (tag);
rev = RCS_getdatebranch (rcs, date, branch);
free (branch);
return rev;
}
else if (tag)
return RCS_gettag (rcs, tag, force_tag_match, simple_tag);
else if (date)
return RCS_getdate (rcs, date, force_tag_match);
else
return RCS_head (rcs);
}
char *
RCS_tag2rev (RCSNode *rcs, char *tag)
{
char *rev, *pa, *pb;
int i;
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
if ( RCS_valid_rev (tag) )
{
rev = xstrdup (tag);
if (RCS_exist_rev (rcs, tag))
return rev;
i = numdots (rev);
if ((i & 1) == 1 )
{
pa = strrchr (rev, '.');
if (i == 1 || *(pa-1) != RCS_MAGIC_BRANCH || *(pa-2) != '.')
{
free (rev);
error (1, 0, "revision `%s' does not exist", tag);
}
}
pa = RCS_getbranch (rcs, rev, 1);
if (pa != NULL)
{
free (pa);
return rev;
}
pa = strrchr (rev, '.');
if (!pa)
error (1, 0, "revision `%s' does not exist", tag);
*pa++ = 0;
pb = Xasprintf ("%s.%d.%s", rev, RCS_MAGIC_BRANCH, pa);
free (rev);
rev = pb;
if (RCS_exist_rev (rcs, rev))
return rev;
error (1, 0, "revision `%s' does not exist", tag);
}
RCS_check_tag (tag);
if (tag && STREQ (tag, TAG_HEAD))
return RCS_head (rcs);
rev = translate_symtag (rcs, tag);
if (rev)
return rev;
return NULL;
}
char *
RCS_gettag (RCSNode *rcs, const char *symtag, int force_tag_match,
int *simple_tag)
{
char *tag;
if (simple_tag != NULL)
*simple_tag = 0;
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
if (symtag && STREQ (symtag, TAG_HEAD))
#if 0
if (force_tag_match && (rcs->flags & VALID) && (rcs->flags & INATTIC))
return NULL;
else
#endif
return RCS_head (rcs);
if (!isdigit ((unsigned char) symtag[0]))
{
char *version;
version = translate_symtag (rcs, symtag);
if (version != NULL)
{
int dots;
char *magic, *branch, *cp;
tag = version;
dots = numdots (tag);
if (dots > 2 && (dots & 1) != 0)
{
branch = strrchr (tag, '.');
cp = branch++ - 1;
while (*cp != '.')
cp--;
magic = xmalloc (strlen (tag) + 1);
(void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
if (strncmp (magic, cp, strlen (magic)) == 0)
{
*cp = '\0';
(void) sprintf (magic, "%s.%s", tag, branch);
branch = RCS_getbranch (rcs, magic, 1);
free (magic);
if (branch != NULL)
{
free (tag);
return branch;
}
return tag;
}
free (magic);
}
}
else
{
if (force_tag_match)
return NULL;
else
return RCS_head (rcs);
}
}
else
tag = xstrdup (symtag);
while (tag[strlen (tag) - 1] == '.')
tag[strlen (tag) - 1] = '\0';
if ((numdots (tag) & 1) == 0)
{
char *branch;
branch = RCS_getbranch (rcs, tag, force_tag_match);
free (tag);
return branch;
}
else
{
Node *p;
p = findnode (rcs->versions, tag);
if (p != NULL)
{
if (simple_tag != NULL)
*simple_tag = 1;
return tag;
}
else
{
free (tag);
if (force_tag_match)
return NULL;
else
return RCS_head (rcs);
}
}
}
static char *check_rev;
char *
RCS_magicrev (RCSNode *rcs, char *rev)
{
int rev_num;
char *xrev, *test_branch, *local_branch_num;
xrev = xmalloc (strlen (rev) + 14);
check_rev = xrev;
local_branch_num = getenv("CVS_LOCAL_BRANCH_NUM");
if (local_branch_num)
{
rev_num = atoi(local_branch_num);
if (rev_num < 2)
rev_num = 2;
else
rev_num &= ~1;
}
else
rev_num = 2;
for ( ; ; rev_num += 2)
{
(void) sprintf (xrev, "%s.%d", rev, rev_num);
test_branch = RCS_getbranch (rcs, xrev, 1);
if (test_branch != NULL)
{
free (test_branch);
continue;
}
(void) sprintf (xrev, "%s.%d.%d", rev, RCS_MAGIC_BRANCH, rev_num);
if (walklist (RCS_symbols(rcs), checkmagic_proc, NULL) != 0)
continue;
return xrev;
}
}
static int
checkmagic_proc (Node *p, void *closure)
{
if (STREQ (check_rev, p->data))
return 1;
else
return 0;
}
int
RCS_isbranch (RCSNode *rcs, const char *rev)
{
if (isdigit ((unsigned char) *rev))
return (numdots (rev) & 1) == 0;
if (rcs == NULL)
return 0;
return RCS_nodeisbranch (rcs, rev);
}
int
RCS_nodeisbranch (RCSNode *rcs, const char *rev)
{
int dots;
char *version;
assert (rcs != NULL);
if (isdigit ((unsigned char) *rev))
return (numdots (rev) & 1) == 0;
version = translate_symtag (rcs, rev);
if (version == NULL)
return 0;
dots = numdots (version);
if ((dots & 1) == 0)
{
free (version);
return 1;
}
if (dots > 2)
{
char *magic;
char *branch = strrchr (version, '.');
char *cp = branch - 1;
while (*cp != '.')
cp--;
magic = Xasprintf (".%d.", RCS_MAGIC_BRANCH);
if (strncmp (magic, cp, strlen (magic)) == 0)
{
free (magic);
free (version);
return 1;
}
free (magic);
}
free (version);
return 0;
}
char *
RCS_whatbranch (RCSNode *rcs, const char *rev)
{
char *version;
int dots;
if (rcs == NULL)
return NULL;
version = translate_symtag (rcs, rev);
if (version == NULL)
return NULL;
dots = numdots (version);
if ((dots & 1) == 0)
return version;
if (dots > 2)
{
char *magic;
char *branch = strrchr (version, '.');
char *cp = branch++ - 1;
while (*cp != '.')
cp--;
magic = xmalloc (strlen (version) + 1);
(void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
if (strncmp (magic, cp, strlen (magic)) == 0)
{
*cp = '\0';
(void) sprintf (magic, "%s.%s", version, branch);
free (version);
return magic;
}
free (magic);
}
free (version);
return NULL;
}
char *
RCS_getbranch (RCSNode *rcs, const char *tag, int force_tag_match)
{
Node *p, *head;
RCSVers *vn;
char *xtag;
char *nextvers;
char *cp;
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
cp = strrchr (tag, '.');
if (cp == NULL)
{
xtag = Xasprintf ("%s.", tag);
for (cp = rcs->head; cp != NULL;)
{
if (strncmp (xtag, cp, strlen (xtag)) == 0)
break;
p = findnode (rcs->versions, cp);
if (p == NULL)
{
free (xtag);
if (force_tag_match)
return NULL;
else
return RCS_head (rcs);
}
vn = p->data;
cp = vn->next;
}
free (xtag);
if (cp == NULL)
{
if (force_tag_match)
return NULL;
else
return RCS_head (rcs);
}
return xstrdup (cp);
}
*cp = '\0';
p = findnode (rcs->versions, tag);
*cp = '.';
if (p == NULL)
{
if (force_tag_match)
return NULL;
else
return RCS_head (rcs);
}
vn = p->data;
if (vn->branches == NULL)
return NULL;
xtag = Xasprintf ("%s.", tag);
head = vn->branches->list;
for (p = head->next; p != head; p = p->next)
if (strncmp (p->key, xtag, strlen (xtag)) == 0)
break;
free (xtag);
if (p == head)
{
if (force_tag_match)
return NULL;
else
return RCS_head (rcs);
}
nextvers = p->key;
do
{
p = findnode (rcs->versions, nextvers);
if (p == NULL)
{
if (force_tag_match)
return NULL;
else
return RCS_head (rcs);
}
vn = p->data;
nextvers = vn->next;
} while (nextvers != NULL);
return xstrdup (vn->version);
}
char *
RCS_branch_head (RCSNode *rcs, char *rev)
{
char *num;
char *br;
char *retval;
assert (rcs != NULL);
if (RCS_nodeisbranch (rcs, rev))
return RCS_getbranch (rcs, rev, 1);
if (isdigit ((unsigned char) *rev))
num = xstrdup (rev);
else
{
num = translate_symtag (rcs, rev);
if (num == NULL)
return NULL;
}
br = truncate_revnum (num);
retval = RCS_getbranch (rcs, br, 1);
free (br);
free (num);
return retval;
}
static char *
RCS_getbranchpoint (RCSNode *rcs, char *target)
{
char *branch, *bp;
Node *vp;
RCSVers *rev;
int dots, isrevnum, brlen;
dots = numdots (target);
isrevnum = dots & 1;
if (dots == 1)
return RCS_head (rcs);
branch = xstrdup (target);
bp = strrchr (branch, '.');
if (bp == NULL)
error (1, 0, "%s: confused revision number %s",
rcs->print_path, target);
if (isrevnum)
while (*--bp != '.')
;
*bp = '\0';
vp = findnode (rcs->versions, branch);
if (vp == NULL)
{
error (0, 0, "%s: can't find branch point %s", rcs->print_path, target);
free (branch);
return NULL;
}
rev = vp->data;
*bp++ = '.';
while (*bp && *bp != '.')
++bp;
brlen = bp - branch;
vp = rev->branches->list->next;
while (vp != rev->branches->list)
{
if (strncmp (vp->key, branch, brlen) == 0 && vp->key[brlen] == '.')
break;
vp = vp->next;
}
free (branch);
if (vp == rev->branches->list)
{
error (0, 0, "%s: can't find branch point %s", rcs->print_path, target);
return NULL;
}
else
return xstrdup (vp->key);
}
char *
RCS_head (RCSNode *rcs)
{
assert (rcs);
if (rcs->branch)
return RCS_getbranch (rcs, rcs->branch, 1);
else
return xstrdup (rcs->head);
}
char *
RCS_getdate (RCSNode *rcs, const char *date, int force_tag_match)
{
char *cur_rev = NULL;
char *retval = NULL;
Node *p;
RCSVers *vers = NULL;
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
if (rcs->branch != NULL)
{
retval = RCS_getdatebranch (rcs, date, rcs->branch);
if (retval != NULL)
return retval;
}
if (rcs->head)
{
p = findnode (rcs->versions, rcs->head);
if (p == NULL)
{
error (0, 0, "%s: head revision %s doesn't exist", rcs->print_path,
rcs->head);
}
while (p != NULL)
{
vers = p->data;
if (RCS_datecmp (vers->date, date) <= 0)
{
cur_rev = vers->version;
break;
}
if (vers->next != NULL)
p = findnode (rcs->versions, vers->next);
else
p = NULL;
}
}
else
error (0, 0, "%s: no head revision", rcs->print_path);
if (cur_rev != NULL)
{
if (! STREQ (cur_rev, "1.1"))
return xstrdup (cur_rev);
p = findnode (rcs->versions, "1.1.1.1");
if (p)
{
char *date_1_1 = vers->date;
vers = p->data;
if (RCS_datecmp (vers->date, date_1_1) != 0)
return xstrdup ("1.1");
}
}
retval = RCS_getdatebranch (rcs, date, CVSBRANCH);
if (retval != NULL)
return retval;
if (vers && (!force_tag_match || RCS_datecmp (vers->date, date) <= 0))
return xstrdup (vers->version);
else
return NULL;
}
static char *
RCS_getdatebranch (RCSNode *rcs, const char *date, const char *branch)
{
char *cur_rev = NULL;
char *cp;
char *xbranch, *xrev;
Node *p;
RCSVers *vers;
xrev = xstrdup (branch);
cp = strrchr (xrev, '.');
if (cp == NULL)
{
free (xrev);
return NULL;
}
*cp = '\0';
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
p = findnode (rcs->versions, xrev);
free (xrev);
if (p == NULL)
return NULL;
vers = p->data;
if (RCS_datecmp (vers->date, date) <= 0)
cur_rev = vers->version;
if (vers->branches == NULL)
return xstrdup (cur_rev);
xbranch = Xasprintf ("%s.", branch);
for (p = vers->branches->list->next; p != vers->branches->list; p = p->next)
if (strncmp (p->key, xbranch, strlen (xbranch)) == 0)
break;
free (xbranch);
if (p == vers->branches->list)
{
return xstrdup (cur_rev);
}
p = findnode (rcs->versions, p->key);
while (p != NULL)
{
vers = p->data;
if (RCS_datecmp (vers->date, date) <= 0)
cur_rev = vers->version;
else
break;
if (vers->next != NULL)
p = findnode (rcs->versions, vers->next);
else
p = NULL;
}
return xstrdup (cur_rev);
}
int
RCS_datecmp (const char *date1, const char *date2)
{
int length_diff = strlen (date1) - strlen (date2);
return length_diff ? length_diff : strcmp (date1, date2);
}
time_t
RCS_getrevtime (RCSNode *rcs, const char *rev, char *date, int fudge)
{
char *tdate;
struct tm xtm, *ftm;
struct timespec revdate;
Node *p;
RCSVers *vers;
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
p = findnode (rcs->versions, rev);
if (p == NULL)
return -1;
vers = p->data;
if (sscanf (vers->date, SDATEFORM, &xtm.tm_year, &xtm.tm_mon,
&xtm.tm_mday, &xtm.tm_hour, &xtm.tm_min, &xtm.tm_sec) != 6)
error (1, 0, "%s: invalid date for revision %s (%s)", rcs->print_path,
rev, vers->date);
if (xtm.tm_year >= 100 && xtm.tm_year < 2000)
error (0, 0, "%s: non-standard date format for revision %s (%s)",
rcs->print_path, rev, vers->date);
if (xtm.tm_year >= 1900)
xtm.tm_year -= 1900;
tdate = Xasprintf ("%d-%d-%d %d:%d:%d -0000",
xtm.tm_year + 1900, xtm.tm_mon, xtm.tm_mday,
xtm.tm_hour, xtm.tm_min, xtm.tm_sec);
if (!get_date (&revdate, tdate, NULL))
{
free (tdate);
return (time_t)-1;
}
free (tdate);
revdate.tv_sec -= fudge;
if (date)
{
ftm = gmtime (&revdate.tv_sec);
(void) sprintf (date, DATEFORM,
ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
ftm->tm_min, ftm->tm_sec);
}
return revdate.tv_sec;
}
List *
RCS_getlocks (RCSNode *rcs)
{
assert(rcs != NULL);
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
if (rcs->locks_data) {
rcs->locks = getlist ();
do_locks (rcs->locks, rcs->locks_data);
free(rcs->locks_data);
rcs->locks_data = NULL;
}
return rcs->locks;
}
List *
RCS_symbols(RCSNode *rcs)
{
assert(rcs != NULL);
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
if (rcs->symbols_data) {
rcs->symbols = getlist ();
do_symbols (rcs->symbols, rcs->symbols_data);
free(rcs->symbols_data);
rcs->symbols_data = NULL;
}
return rcs->symbols;
}
static char *
translate_symtag (RCSNode *rcs, const char *tag)
{
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
if (rcs->symbols != NULL)
{
Node *p;
p = findnode (rcs->symbols, tag);
if (p == NULL)
return NULL;
return xstrdup (p->data);
}
if (rcs->symbols_data != NULL)
{
size_t len;
char *cp, *last;
len = strlen (tag);
cp = rcs->symbols_data;
last = NULL;
while ((cp = strchr (cp, tag[0])) != NULL)
{
if (cp == last) break;
if ((cp == rcs->symbols_data || whitespace (cp[-1]))
&& strncmp (cp, tag, len) == 0
&& cp[len] == ':')
{
char *v, *r;
cp += len + 1;
v = cp;
while (! whitespace (*cp) && *cp != '\0')
++cp;
r = xmalloc (cp - v + 1);
strncpy (r, v, cp - v);
r[cp - v] = '\0';
return r;
}
while (! whitespace (*cp) && *cp != '\0')
++cp;
if (*cp == '\0')
break;
last = cp;
}
}
return NULL;
}
char *
RCS_check_kflag (const char *arg)
{
static const char *const keyword_usage[] =
{
"%s %s: invalid RCS keyword expansion mode\n",
"Valid expansion modes include:\n",
" -kkv\tGenerate keywords using the default form.\n",
" -kkvl\tLike -kkv, except locker's name inserted.\n",
" -kk\tGenerate only keyword names in keyword strings.\n",
" -kv\tGenerate only keyword values in keyword strings.\n",
" -ko\tGenerate the old keyword string (no changes from checked in file).\n",
" -kb\tGenerate binary file unmodified (merges not allowed) (RCS 5.7).\n",
"(Specify the --help global option for a list of other help options)\n",
NULL,
};
char const *const *cpp = NULL;
if (arg)
{
for (cpp = kflags; *cpp != NULL; cpp++)
{
if (STREQ (arg, *cpp))
break;
}
}
if (arg == NULL || *cpp == NULL)
{
usage (keyword_usage);
}
return Xasprintf ("-k%s", *cpp);
}
void
RCS_check_tag (const char *tag)
{
char *invalid = "$,.:;@";
const char *cp;
if (isalpha ((unsigned char) *tag))
{
for (cp = tag; *cp; cp++)
{
if (!isgraph ((unsigned char) *cp))
error (1, 0, "tag `%s' has non-visible graphic characters",
tag);
if (strchr (invalid, *cp))
error (1, 0, "tag `%s' must not contain the characters `%s'",
tag, invalid);
}
}
else
error (1, 0, "tag `%s' must start with a letter", tag);
}
int
RCS_valid_rev (const char *rev)
{
char last, c;
last = *rev++;
if (!isdigit ((unsigned char) last))
return 0;
while ((c = *rev++))
{
if (c == '.')
{
if (last == '.')
return 0;
continue;
}
last = c;
if (!isdigit ((unsigned char) c))
return 0;
}
if (!isdigit ((unsigned char) last))
return 0;
return 1;
}
int
RCS_isdead (RCSNode *rcs, const char *tag)
{
Node *p;
RCSVers *version;
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
p = findnode (rcs->versions, tag);
if (p == NULL)
return 0;
version = p->data;
return version->dead;
}
char *
RCS_getexpand (RCSNode *rcs)
{
assert (rcs != NULL);
return rcs->expand;
}
void
RCS_setexpand (RCSNode *rcs, const char *expand)
{
assert (rcs != NULL);
if (rcs->expand != NULL)
free (rcs->expand);
rcs->expand = xstrdup (expand);
}
enum keyword
{
KEYWORD_AUTHOR = 0,
KEYWORD_DATE,
KEYWORD_CVSHEADER,
KEYWORD_HEADER,
KEYWORD_ID,
KEYWORD_LOCKER,
KEYWORD_LOG,
KEYWORD_NAME,
KEYWORD_RCSFILE,
KEYWORD_REVISION,
KEYWORD_SOURCE,
KEYWORD_STATE,
KEYWORD_LOCALID
};
struct rcs_keyword
{
const char *string;
size_t len;
enum keyword expandto;
bool expandit;
};
static inline struct rcs_keyword *
new_keywords (void)
{
struct rcs_keyword *new;
new = xcalloc (KEYWORD_LOCALID + 2, sizeof (struct rcs_keyword));
#define KEYWORD_INIT(k, i, s) \
k[i].string = s; \
k[i].len = sizeof s - 1; \
k[i].expandto = i; \
k[i].expandit = true
KEYWORD_INIT (new, KEYWORD_AUTHOR, "Author");
KEYWORD_INIT (new, KEYWORD_DATE, "Date");
KEYWORD_INIT (new, KEYWORD_CVSHEADER, "CVSHeader");
KEYWORD_INIT (new, KEYWORD_HEADER, "Header");
KEYWORD_INIT (new, KEYWORD_ID, "Id");
KEYWORD_INIT (new, KEYWORD_LOCKER, "Locker");
KEYWORD_INIT (new, KEYWORD_LOG, "Log");
KEYWORD_INIT (new, KEYWORD_NAME, "Name");
KEYWORD_INIT (new, KEYWORD_RCSFILE, "RCSfile");
KEYWORD_INIT (new, KEYWORD_REVISION, "Revision");
KEYWORD_INIT (new, KEYWORD_SOURCE, "Source");
KEYWORD_INIT (new, KEYWORD_STATE, "State");
return new;
}
void
free_keywords (void *keywords)
{
free (keywords);
}
static char *
printable_date (const char *rcs_date)
{
int year, mon, mday, hour, min, sec;
char buf[100];
(void) sscanf (rcs_date, SDATEFORM, &year, &mon, &mday, &hour, &min,
&sec);
if (year < 1900)
year += 1900;
sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d", year, mon, mday,
hour, min, sec);
return xstrdup (buf);
}
static char *
escape_keyword_value (const char *value, int *free_value)
{
char *ret, *t;
const char *s;
for (s = value; *s != '\0'; s++)
{
char c;
c = *s;
if (c == '\t'
|| c == '\n'
|| c == '\\'
|| c == ' '
|| c == '$')
{
break;
}
}
if (*s == '\0')
{
*free_value = 0;
return (char *) value;
}
ret = xmalloc (strlen (value) * 4 + 1);
*free_value = 1;
for (s = value, t = ret; *s != '\0'; s++, t++)
{
switch (*s)
{
default:
*t = *s;
break;
case '\t':
*t++ = '\\';
*t = 't';
break;
case '\n':
*t++ = '\\';
*t = 'n';
break;
case '\\':
*t++ = '\\';
*t = '\\';
break;
case ' ':
*t++ = '\\';
*t++ = '0';
*t++ = '4';
*t = '0';
break;
case '$':
*t++ = '\\';
*t++ = '0';
*t++ = '4';
*t = '4';
break;
}
}
*t = '\0';
return ret;
}
static void
expand_keywords (RCSNode *rcs, RCSVers *ver, const char *name, const char *log,
size_t loglen, enum kflag expand, char *buf, size_t len,
char **retbuf, size_t *retlen)
{
struct expand_buffer
{
struct expand_buffer *next;
char *data;
size_t len;
int free_data;
} *ebufs = NULL;
struct expand_buffer *ebuf_last = NULL;
size_t ebuf_len = 0;
char *locker;
char *srch, *srch_next;
size_t srch_len;
const struct rcs_keyword *keywords;
if (!config
||expand == KFLAG_O || expand == KFLAG_B)
{
*retbuf = buf;
*retlen = len;
return;
}
if (!config->keywords) config->keywords = new_keywords ();
keywords = config->keywords;
locker = NULL;
if (expand == KFLAG_KVL)
{
Node *lock;
lock = findnode (RCS_getlocks(rcs), ver->version);
if (lock != NULL)
locker = xstrdup (lock->data);
}
srch = buf;
srch_len = len;
while ((srch_next = memchr (srch, '$', srch_len)) != NULL)
{
char *s, *send;
size_t slen;
const struct rcs_keyword *keyword;
char *value;
int free_value;
char *sub;
size_t sublen;
srch_len -= (srch_next + 1) - srch;
srch = srch_next + 1;
send = srch + srch_len;
for (s = srch; s < send; s++)
if (! isalpha ((unsigned char) *s))
break;
if (s == send || (*s != '$' && *s != ':'))
continue;
slen = s - srch;
for (keyword = keywords; keyword->string != NULL; keyword++)
{
if (keyword->expandit
&& keyword->len == slen
&& strncmp (keyword->string, srch, slen) == 0)
{
break;
}
}
if (keyword->string == NULL)
continue;
if (*s == ':')
{
for (; s < send; s++)
if (*s == '$' || *s == '\n')
break;
if (s == send || *s != '$')
continue;
}
free_value = 0;
if (expand == KFLAG_K)
value = NULL;
else
{
switch (keyword->expandto)
{
default:
assert (!"unreached");
case KEYWORD_AUTHOR:
value = ver->author;
break;
case KEYWORD_DATE:
value = printable_date (ver->date);
free_value = 1;
break;
case KEYWORD_CVSHEADER:
case KEYWORD_HEADER:
case KEYWORD_ID:
case KEYWORD_LOCALID:
{
const char *path;
int free_path;
char *date;
char *old_path;
old_path = NULL;
if (keyword->expandto == KEYWORD_HEADER)
path = rcs->print_path;
else if (keyword->expandto == KEYWORD_CVSHEADER)
path = getfullCVSname (rcs->print_path, &old_path);
else
path = last_component (rcs->print_path);
path = escape_keyword_value (path, &free_path);
date = printable_date (ver->date);
value = Xasprintf ("%s %s %s %s %s%s%s",
path, ver->version, date, ver->author,
ver->state,
locker != NULL ? " " : "",
locker != NULL ? locker : "");
if (free_path)
free ((char *)path);
if (old_path)
free (old_path);
free (date);
free_value = 1;
}
break;
case KEYWORD_LOCKER:
value = locker;
break;
case KEYWORD_LOG:
case KEYWORD_RCSFILE:
value = escape_keyword_value (last_component (rcs->print_path),
&free_value);
break;
case KEYWORD_NAME:
if (name != NULL && ! isdigit ((unsigned char) *name))
value = (char *) name;
else
value = NULL;
break;
case KEYWORD_REVISION:
value = ver->version;
break;
case KEYWORD_SOURCE:
value = escape_keyword_value (rcs->print_path, &free_value);
break;
case KEYWORD_STATE:
value = ver->state;
break;
}
}
sub = xmalloc (keyword->len
+ (value == NULL ? 0 : strlen (value))
+ 10);
if (expand == KFLAG_V)
{
--srch;
++srch_len;
++s;
sublen = 0;
}
else
{
strcpy (sub, keyword->string);
sublen = strlen (keyword->string);
if (expand != KFLAG_K)
{
sub[sublen] = ':';
sub[sublen + 1] = ' ';
sublen += 2;
}
}
if (value != NULL)
{
strcpy (sub + sublen, value);
sublen += strlen (value);
}
if (expand != KFLAG_V && expand != KFLAG_K)
{
sub[sublen] = ' ';
++sublen;
sub[sublen] = '\0';
}
if (free_value)
free (value);
if (keyword->expandto == KEYWORD_LOG
&& (sizeof "checked in with -k by " <= loglen
|| log == NULL
|| strncmp (log, "checked in with -k by ",
sizeof "checked in with -k by " - 1) != 0))
{
char *start;
char *leader;
size_t leader_len, leader_sp_len;
const char *logend;
const char *snl;
int cnl;
char *date;
const char *sl;
if (expand != KFLAG_V)
++s;
if (log == NULL)
log = "";
start = srch;
leader_len = 0;
while (start > buf && start[-1] != '\n'
&& leader_len <= xsum (config->MaxCommentLeaderLength,
expand != KFLAG_V ? 1 : 0))
{
--start;
++leader_len;
}
if (expand != KFLAG_V)
--leader_len;
if (leader_len > config->MaxCommentLeaderLength)
{
if (config->UseArchiveCommentLeader && rcs->comment)
{
leader = xstrdup (rcs->comment);
leader_len = strlen (rcs->comment);
}
else
{
error (0, 0,
"Skipping `$" "Log$' keyword due to excessive comment leader.");
continue;
}
}
else
{
leader = xmalloc (leader_len);
memcpy (leader, start, leader_len);
}
leader_sp_len = leader_len;
while (leader_sp_len > 0 && isspace (leader[leader_sp_len - 1]))
--leader_sp_len;
cnl = 0;
logend = log + loglen;
for (snl = log; snl < logend; snl++)
if (*snl == '\n')
++cnl;
if (loglen && snl[-1] != '\n')
++cnl;
date = printable_date (ver->date);
sub = xrealloc (sub,
(sublen
+ sizeof "Revision"
+ strlen (ver->version)
+ strlen (date)
+ strlen (ver->author)
+ loglen
+ (cnl + 2) * leader_len
+ 20));
if (expand != KFLAG_V)
{
sub[sublen] = '$';
++sublen;
}
sub[sublen] = '\n';
++sublen;
memcpy (sub + sublen, leader, leader_len);
sublen += leader_len;
sprintf (sub + sublen, "Revision %s %s %s\n",
ver->version, date, ver->author);
sublen += strlen (sub + sublen);
free (date);
sl = log;
while (sl < logend)
{
if (*sl == '\n')
{
memcpy (sub + sublen, leader, leader_sp_len);
sublen += leader_sp_len;
sub[sublen] = '\n';
++sublen;
++sl;
}
else
{
const char *slnl;
memcpy (sub + sublen, leader, leader_len);
sublen += leader_len;
for (slnl = sl; slnl < logend && *slnl != '\n'; ++slnl)
;
if (slnl < logend)
++slnl;
memcpy (sub + sublen, sl, slnl - sl);
sublen += slnl - sl;
if (slnl == logend && slnl[-1] != '\n')
{
sub[sublen] = '\n';
++sublen;
}
sl = slnl;
}
}
memcpy (sub + sublen, leader, leader_sp_len);
sublen += leader_sp_len;
free (leader);
}
if (srch + sublen == s)
{
memcpy (srch, sub, sublen);
free (sub);
}
else
{
struct expand_buffer *ebuf;
if (ebufs == NULL)
{
ebufs = xmalloc (sizeof *ebuf);
ebufs->next = NULL;
ebufs->data = buf;
ebufs->free_data = 0;
ebuf_len = srch - buf;
ebufs->len = ebuf_len;
ebuf_last = ebufs;
}
else
{
assert (srch >= ebuf_last->data);
assert (srch <= ebuf_last->data + ebuf_last->len);
ebuf_len -= ebuf_last->len - (srch - ebuf_last->data);
ebuf_last->len = srch - ebuf_last->data;
}
ebuf = xmalloc (sizeof *ebuf);
ebuf->data = sub;
ebuf->len = sublen;
ebuf->free_data = 1;
ebuf->next = NULL;
ebuf_last->next = ebuf;
ebuf_last = ebuf;
ebuf_len += sublen;
ebuf = xmalloc (sizeof *ebuf);
ebuf->data = s;
ebuf->len = srch_len - (s - srch);
ebuf->free_data = 0;
ebuf->next = NULL;
ebuf_last->next = ebuf;
ebuf_last = ebuf;
ebuf_len += srch_len - (s - srch);
}
srch_len -= (s - srch);
srch = s;
}
if (locker != NULL)
free (locker);
if (ebufs == NULL)
{
*retbuf = buf;
*retlen = len;
}
else
{
char *ret;
ret = xmalloc (ebuf_len);
*retbuf = ret;
*retlen = ebuf_len;
while (ebufs != NULL)
{
struct expand_buffer *next;
memcpy (ret, ebufs->data, ebufs->len);
ret += ebufs->len;
if (ebufs->free_data)
free (ebufs->data);
next = ebufs->next;
free (ebufs);
ebufs = next;
}
}
}
int
RCS_checkout (RCSNode *rcs, const char *workfile, const char *rev,
const char *nametag, const char *options, const char *sout,
RCSCHECKOUTPROC pfn, void *callerdat)
{
int free_rev = 0;
enum kflag expand;
FILE *fp,
*ofp = NULL;
struct stat sb;
struct rcsbuffer rcsbuf;
char *key;
char *value;
size_t len;
int free_value = 0;
char *log = NULL;
size_t loglen = 0;
Node *vp = NULL;
#ifdef PRESERVE_PERMISSIONS_SUPPORT
uid_t rcs_owner = (uid_t) -1;
gid_t rcs_group = (gid_t) -1;
mode_t rcs_mode;
int change_rcs_owner_or_group = 0;
int change_rcs_mode = 0;
int special_file = 0;
unsigned long devnum_long;
dev_t devnum = 0;
#endif
TRACE (TRACE_FUNCTION, "RCS_checkout (%s, %s, %s, %s, %s)",
rcs->path,
rev != NULL ? rev : "",
nametag != NULL ? nametag : "",
options != NULL ? options : "",
(pfn != NULL ? "(function)"
: (workfile != NULL ? workfile
: (sout != RUN_TTY ? sout
: "(stdout)"))));
assert (rev == NULL || isdigit ((unsigned char) *rev));
if (noexec && !server_active && workfile != NULL)
return 0;
assert (sout == RUN_TTY || workfile == NULL);
assert (pfn == NULL || (sout == RUN_TTY && workfile == NULL));
if (rev != NULL && (numdots (rev) & 1) == 0)
{
rev = RCS_getbranch (rcs, rev, 1);
if (rev == NULL)
error (1, 0, "internal error: bad branch tag in checkout");
free_rev = 1;
}
if (rev == NULL || STREQ (rev, rcs->head))
{
int gothead;
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, &fp, &rcsbuf);
else
rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf);
gothead = 0;
if (! rcsbuf_getrevnum (&rcsbuf, &key))
error (1, 0, "unexpected EOF reading %s", rcs->print_path);
while (rcsbuf_getkey (&rcsbuf, &key, &value))
{
if (STREQ (key, "log"))
{
if (log)
{
error (0, 0,
"Duplicate log keyword found for head revision in RCS file.");
free (log);
}
log = rcsbuf_valcopy (&rcsbuf, value, 0, &loglen);
}
else if (STREQ (key, "text"))
{
gothead = 1;
break;
}
}
if (! gothead)
{
error (0, 0, "internal error: cannot find head text");
if (free_rev)
free ((char *)rev);
return 1;
}
rcsbuf_valpolish (&rcsbuf, value, 0, &len);
if (fstat (fileno (fp), &sb) < 0)
error (1, errno, "cannot fstat %s", rcs->path);
rcsbuf_cache (rcs, &rcsbuf);
}
else
{
struct rcsbuffer *rcsbufp;
fp = NULL;
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, &fp, &rcsbuf);
if (fp == NULL)
{
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, rcsbufp, rev, RCS_FETCH, &value, &len,
&log, &loglen);
free_value = 1;
}
if ((options == NULL || options[0] == '\0') && rcs->expand == NULL)
expand = KFLAG_KV;
else
{
const char *ouroptions;
const char * const *cpp;
if (options != NULL && options[0] != '\0')
{
assert (options[0] == '-' && options[1] == 'k');
ouroptions = options + 2;
}
else
ouroptions = rcs->expand;
for (cpp = kflags; *cpp != NULL; cpp++)
if (STREQ (*cpp, ouroptions))
break;
if (*cpp != NULL)
expand = (enum kflag) (cpp - kflags);
else
{
error (0, 0,
"internal error: unsupported substitution string -k%s",
ouroptions);
expand = KFLAG_KV;
}
}
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (preserve_perms)
{
RCSVers *vers;
Node *info;
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 = vp->data;
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;
if (CVS_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, (char *)info->data);
if (free_value)
free (value);
if (free_rev)
free ((char *)rev);
return 0;
}
if (workfile != NULL)
{
List *links = vers->hardlinks;
if (links != NULL)
{
Node *uptodate_link;
uptodate_link = NULL;
(void) walklist (links, find_checkedout_proc, &uptodate_link);
dellist (&links);
if (uptodate_link != NULL)
{
struct hardlink_info *hlinfo = uptodate_link->data;
if (link (uptodate_link->key, workfile) < 0)
error (1, errno, "cannot link %s to %s",
workfile, uptodate_link->key);
hlinfo->checked_out = 1;
if (free_value)
free (value);
if (free_rev)
free ((char *)rev);
return 0;
}
}
}
info = findnode (vers->other_delta, "owner");
if (info != NULL)
{
change_rcs_owner_or_group = 1;
rcs_owner = (uid_t) strtoul (info->data, NULL, 10);
}
info = findnode (vers->other_delta, "group");
if (info != NULL)
{
change_rcs_owner_or_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)
{
char devtype[16];
if (sscanf (info->data, "%15s %lu",
devtype, &devnum_long) < 2)
error (1, 0, "%s:%s has bad `special' newphrase %s",
workfile, vers->version, (char *)info->data);
devnum = devnum_long;
if (STREQ (devtype, "character"))
special_file = S_IFCHR;
else if (STREQ (devtype, "block"))
special_file = S_IFBLK;
else
error (0, 0, "%s is a special file of unsupported type `%s'",
workfile, (char *)info->data);
}
}
#endif
if (expand != KFLAG_O && expand != KFLAG_B)
{
char *newvalue;
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, vp->data, nametag, log, loglen,
expand, value, len, &newvalue, &len);
if (newvalue != value)
{
if (free_value)
free (value);
value = newvalue;
free_value = 1;
}
}
if (free_rev)
free ((char *)rev);
if (log != NULL)
{
free (log);
log = NULL;
}
if (pfn != NULL)
{
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (special_file)
error (1, 0, "special file %s cannot be piped to anything",
rcs->path);
#endif
if (len != 0)
pfn (callerdat, value, len);
}
#ifdef PRESERVE_PERMISSIONS_SUPPORT
else if (special_file)
{
# ifdef HAVE_MKNOD
char *dest;
dest = workfile;
if (dest == NULL)
{
if (sout == RUN_TTY)
error (1, 0, "special file %s cannot be written to stdout",
rcs->path);
dest = sout;
}
if (CVS_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);
# else
error (1, 0,
"cannot create %s: unable to create special files on this system",
workfile);
# endif
}
#endif
else
{
if (workfile == NULL)
{
if (sout == RUN_TTY)
ofp = stdout;
else
{
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);
}
}
else
{
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 && errno == EACCES
&& isfile (workfile) && !iswritable (workfile))
{
xchmod (workfile, 1);
ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w");
}
if (ofp == NULL)
{
error (0, errno, "cannot open %s", workfile);
if (free_value)
free (value);
return 1;
}
}
if (workfile == NULL && sout == RUN_TTY)
{
if (expand == KFLAG_B)
cvs_output_binary (value, len);
else
{
if (len > 0)
cvs_output (value, len);
}
}
else
{
#define LARGEST_FWRITE 8192
size_t nleft = len;
size_t nstep = (len < LARGEST_FWRITE ? len : LARGEST_FWRITE);
char *p = value;
while (nleft > 0)
{
if (fwrite (p, 1, nstep, ofp) != nstep)
{
error (0, errno, "cannot write %s",
(workfile != NULL
? workfile
: (sout != RUN_TTY ? sout : "stdout")));
if (free_value)
free (value);
return 1;
}
p += nstep;
nleft -= nstep;
if (nleft < nstep)
nstep = nleft;
}
}
}
if (free_value)
free (value);
if (workfile != NULL)
{
int ret;
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (!special_file && fclose (ofp) < 0)
{
error (0, errno, "cannot close %s", workfile);
return 1;
}
if (change_rcs_owner_or_group)
{
if (chown (workfile, rcs_owner, rcs_group) < 0)
error (0, errno, "could not change owner or group of %s",
workfile);
}
ret = chmod (workfile,
change_rcs_mode
? rcs_mode
: sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH));
#else
if (fclose (ofp) < 0)
{
error (0, errno, "cannot close %s", workfile);
return 1;
}
ret = chmod (workfile,
sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH));
#endif
if (ret < 0)
{
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 (0, errno, "cannot close %s", sout);
return 1;
}
}
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (preserve_perms && workfile != NULL)
update_hardlink_info (workfile);
#endif
return 0;
}
static RCSVers *
RCS_findlock_or_tip (RCSNode *rcs)
{
char *user = getcaller();
Node *lock, *p;
List *locklist;
locklist = RCS_getlocks (rcs);
lock = NULL;
for (p = locklist->list->next; p != locklist->list; p = p->next)
{
if (STREQ (p->data, user))
{
if (lock != NULL)
{
error (0, 0, "\
%s: multiple revisions locked by %s; please specify one", rcs->print_path, user);
return NULL;
}
lock = p;
}
}
if (lock != NULL)
{
p = findnode (rcs->versions, lock->key);
if (p == NULL)
{
error (0, 0, "%s: can't unlock nonexistent revision %s",
rcs->print_path,
lock->key);
return NULL;
}
return p->data;
}
p = findnode (rcs->versions, RCS_getbranch (rcs, rcs->branch, 0));
if (!p)
{
error (0, 0, "RCS file `%s' does not contain its default revision.",
rcs->path);
return NULL;
}
return p->data;
}
static char *
truncate_revnum (const char *r)
{
size_t len;
char *new_r;
char *dot = strrchr (r, '.');
assert (dot);
len = dot - r;
new_r = xmalloc (len + 1);
memcpy (new_r, r, len);
*(new_r + len) = '\0';
return new_r;
}
static char *
truncate_revnum_in_place (char *r)
{
char *dot = strrchr (r, '.');
assert (dot);
*dot = '\0';
return dot;
}
static int
compare_truncated_revnums (char *r, char *s)
{
char *r_dot = truncate_revnum_in_place (r);
char *s_dot = truncate_revnum_in_place (s);
int cmp;
assert (numdots (r) == numdots (s));
cmp = compare_revnums (r, s);
*r_dot = '.';
*s_dot = '.';
return cmp;
}
static char *
max_rev (const RCSVers *branchnode)
{
Node *head;
Node *bp;
char *max;
if (branchnode->branches == NULL)
{
return NULL;
}
max = NULL;
head = branchnode->branches->list;
for (bp = head->next; bp != head; bp = bp->next)
{
if (max == NULL || compare_truncated_revnums (max, bp->key) < 0)
{
max = bp->key;
}
}
assert (max);
return truncate_revnum (max);
}
static char *
RCS_addbranch (RCSNode *rcs, const char *branch)
{
char *branchpoint, *newrevnum;
Node *nodep, *bp;
Node *marker;
RCSVers *branchnode;
assert (branch);
marker = NULL;
branchpoint = xstrdup (branch);
if ((numdots (branchpoint) & 1) == 0)
{
truncate_revnum_in_place (branchpoint);
}
nodep = findnode (rcs->versions, branchpoint);
if (nodep == NULL)
{
error (0, 0, "%s: can't find branch point %s", rcs->print_path, branchpoint);
free (branchpoint);
return NULL;
}
free (branchpoint);
branchnode = nodep->data;
if ((numdots (branch) & 1) == 1)
{
if (branchnode->branches == NULL)
{
newrevnum = Xasprintf ("%s.2", branch);
}
else
{
char *max = max_rev (branchnode);
assert (max);
newrevnum = increment_revnum (max);
free (max);
}
}
else
{
newrevnum = xstrdup (branch);
if (branchnode->branches != NULL)
{
Node *head;
Node *bp;
head = branchnode->branches->list;
for (bp = head->next; bp != head; bp = bp->next)
{
char *dot;
int found_pos;
assert (bp->next == head
|| compare_truncated_revnums (bp->key,
bp->next->key) < 0);
dot = truncate_revnum_in_place (bp->key);
found_pos = (compare_revnums (branch, bp->key) < 0);
*dot = '.';
if (found_pos)
{
break;
}
}
marker = bp;
}
}
newrevnum = xrealloc (newrevnum, strlen (newrevnum) + 3);
strcat (newrevnum, ".1");
if (branchnode->branches == NULL)
branchnode->branches = getlist();
bp = getnode();
bp->key = xstrdup (newrevnum);
if (marker == NULL)
marker = branchnode->branches->list;
{
int fail;
fail = insert_before (branchnode->branches, marker, bp);
assert (!fail);
}
return newrevnum;
}
int
RCS_checkin (RCSNode *rcs, const char *update_dir, const char *workfile_in,
const char *message, const char *rev, time_t citime, int flags)
{
RCSVers *delta, *commitpt;
Deltatext *dtext;
Node *nodep;
char *tmpfile, *changefile;
int dargc = 0;
size_t darg_allocated = 0;
char **dargv = NULL;
size_t bufsize;
int status, checkin_quiet;
struct tm *ftm;
time_t modtime;
int adding_branch = 0;
char *workfile = xstrdup (workfile_in);
#ifdef PRESERVE_PERMISSIONS_SUPPORT
struct stat sb;
#endif
Node *np;
commitpt = NULL;
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
if (workfile == NULL)
{
char *p;
int extlen = strlen (RCSEXT);
assert (rcs->path);
workfile = xstrdup (last_component (rcs->path));
p = workfile + (strlen (workfile) - extlen);
assert (strncmp (p, RCSEXT, extlen) == 0);
*p = '\0';
}
resolve_symlink (&(rcs->path));
checkin_quiet = flags & RCS_FLAGS_QUIET;
if (!(checkin_quiet || really_quiet))
{
cvs_output (rcs->path, 0);
cvs_output (" <-- ", 7);
if (update_dir && strlen (update_dir))
{
cvs_output (update_dir, 0);
cvs_output ("/", 1);
}
cvs_output (workfile, 0);
cvs_output ("\n", 1);
}
delta = xmalloc (sizeof (RCSVers));
memset (delta, 0, sizeof (RCSVers));
delta->author = xstrdup (getcaller ());
if (flags & RCS_FLAGS_MODTIME)
{
struct stat ws;
if (stat (workfile, &ws) < 0)
{
error (1, errno, "cannot stat %s", workfile);
}
modtime = ws.st_mtime;
}
else if (flags & RCS_FLAGS_USETIME)
modtime = citime;
else
(void) time (&modtime);
ftm = gmtime (&modtime);
delta->date = Xasprintf (DATEFORM,
ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
ftm->tm_min, ftm->tm_sec);
if (flags & RCS_FLAGS_DEAD)
{
delta->state = xstrdup (RCSDEAD);
delta->dead = 1;
}
else
delta->state = xstrdup ("Exp");
delta->other_delta = getlist();
np = getnode();
np->type = RCSFIELD;
np->key = xstrdup ("commitid");
np->data = xstrdup(global_session_id);
addnode (delta->other_delta, np);
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (preserve_perms)
{
Node *np;
char buf[64];
delta->other_delta = getlist();
if (lstat (workfile, &sb) < 0)
error (1, errno, "cannot lstat %s", workfile);
if (S_ISLNK (sb.st_mode))
{
np = getnode();
np->type = RCSFIELD;
np->key = xstrdup ("symlink");
np->data = Xreadlink (workfile, sb.st_size);
addnode (delta->other_delta, np);
}
else
{
(void) sprintf (buf, "%u", sb.st_uid);
np = getnode();
np->type = RCSFIELD;
np->key = xstrdup ("owner");
np->data = xstrdup (buf);
addnode (delta->other_delta, np);
(void) sprintf (buf, "%u", sb.st_gid);
np = getnode();
np->type = RCSFIELD;
np->key = xstrdup ("group");
np->data = xstrdup (buf);
addnode (delta->other_delta, np);
(void) sprintf (buf, "%o", sb.st_mode & 07777);
np = getnode();
np->type = RCSFIELD;
np->key = xstrdup ("permissions");
np->data = xstrdup (buf);
addnode (delta->other_delta, np);
switch (sb.st_mode & S_IFMT)
{
case S_IFREG: break;
case S_IFCHR:
case S_IFBLK:
# ifdef HAVE_STRUCT_STAT_ST_RDEV
np = getnode();
np->type = RCSFIELD;
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);
# else
error (0, 0,
"can't preserve %s: unable to save device files on this system",
workfile);
# endif
break;
default:
error (0, 0, "special file %s has unknown type", workfile);
}
delta->hardlinks = list_linked_files_on_disk (workfile);
}
}
#endif
dtext = xmalloc (sizeof (Deltatext));
memset (dtext, 0, sizeof (Deltatext));
dtext->log = make_message_rcsvalid (message);
if (rcs->head == NULL)
{
char *newrev;
FILE *fout;
if (rev == NULL || *rev == '\0')
newrev = xstrdup ("1.1");
else if (numdots (rev) == 0)
{
newrev = Xasprintf ("%s.1", rev);
}
else
newrev = xstrdup (rev);
rcs->head = newrev;
delta->version = xstrdup (newrev);
nodep = getnode();
nodep->type = RCSVERS;
nodep->delproc = rcsvers_delproc;
nodep->data = delta;
nodep->key = delta->version;
(void) addnode (rcs->versions, nodep);
dtext->version = xstrdup (newrev);
bufsize = 0;
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (preserve_perms && !S_ISREG (sb.st_mode))
bufsize = 0;
else
#endif
get_file (workfile, workfile,
rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
&dtext->text, &bufsize, &dtext->len);
if (!(checkin_quiet || really_quiet))
{
cvs_output ("initial revision: ", 0);
cvs_output (rcs->head, 0);
cvs_output ("\n", 1);
}
rcsbuf_cache_close ();
fout = rcs_internal_lockfile (rcs->path);
RCS_putadmin (rcs, fout);
RCS_putdtree (rcs, rcs->head, fout);
RCS_putdesc (rcs, fout);
rcs->delta_pos = ftello (fout);
if (rcs->delta_pos == -1)
error (1, errno, "cannot ftello for %s", rcs->path);
putdeltatext (fout, dtext);
rcs_internal_unlockfile (fout, rcs->path);
if ((flags & RCS_FLAGS_KEEPFILE) == 0)
{
if (unlink_file (workfile) < 0)
error (0, errno, "cannot remove %s", workfile);
}
status = 0;
goto checkin_done;
}
if (rev == NULL || *rev == '\0')
{
commitpt = RCS_findlock_or_tip (rcs);
if (commitpt == NULL)
{
status = 1;
goto checkin_done;
}
else if (commitpt->next == NULL
|| STREQ (commitpt->version, rcs->head))
delta->version = increment_revnum (commitpt->version);
else
delta->version = RCS_addbranch (rcs, commitpt->version);
}
else
{
char *branch, *tip, *newrev, *p;
int dots, isrevnum;
assert (isdigit ((unsigned char) *rev));
newrev = xstrdup (rev);
dots = numdots (newrev);
isrevnum = dots & 1;
branch = xstrdup (rev);
if (isrevnum)
{
p = strrchr (branch, '.');
*p = '\0';
}
if (dots == 0)
{
tip = xstrdup (rcs->head);
if (atoi (tip) != atoi (branch))
{
newrev = xrealloc (newrev, strlen (newrev) + 3);
strcat (newrev, ".1");
dots = isrevnum = 1;
}
}
else if (dots == 1)
tip = xstrdup (rcs->head);
else
tip = RCS_getbranch (rcs, branch, 1);
if (tip == NULL)
{
if (isrevnum)
{
error (0, 0, "%s: can't find branch point %s",
rcs->print_path, branch);
free (branch);
free (newrev);
status = 1;
goto checkin_done;
}
delta->version = RCS_addbranch (rcs, branch);
if (!delta->version)
{
free (branch);
free (newrev);
status = 1;
goto checkin_done;
}
adding_branch = 1;
p = strrchr (branch, '.');
*p = '\0';
tip = xstrdup (branch);
}
else
{
if (isrevnum)
{
if (compare_revnums (tip, newrev) >= 0)
{
error (0, 0,
"%s: revision %s too low; must be higher than %s",
rcs->print_path,
newrev, tip);
free (branch);
free (newrev);
free (tip);
status = 1;
goto checkin_done;
}
delta->version = xstrdup (newrev);
}
else
delta->version = increment_revnum (tip);
}
nodep = findnode (rcs->versions, tip);
commitpt = nodep->data;
free (branch);
free (newrev);
free (tip);
}
assert (delta->version != NULL);
nodep = findnode (RCS_getlocks (rcs), commitpt->version);
if (nodep != NULL)
{
if (! STREQ (nodep->data, delta->author))
{
if (!adding_branch)
{
error (0, 0, "%s: revision %s locked by %s",
rcs->print_path,
nodep->key, (char *)nodep->data);
status = 1;
goto checkin_done;
}
}
else
delnode (nodep);
}
dtext->version = xstrdup (delta->version);
tmpfile = cvs_temp_name();
status = RCS_checkout (rcs, NULL, commitpt->version, NULL,
((rcs->expand != NULL
&& STREQ (rcs->expand, "b"))
? "-kb"
: "-ko"),
tmpfile,
NULL, NULL);
if (status != 0)
error (1, 0,
"could not check out revision %s of `%s'",
commitpt->version, rcs->print_path);
bufsize = 0;
changefile = cvs_temp_name();
run_add_arg_p (&dargc, &darg_allocated, &dargv, "-a");
run_add_arg_p (&dargc, &darg_allocated, &dargv, "-n");
if (rcs->expand != NULL && STREQ (rcs->expand, "b"))
run_add_arg_p (&dargc, &darg_allocated, &dargv, "--binary");
if (STREQ (commitpt->version, rcs->head) &&
numdots (delta->version) == 1)
{
bufsize = 0;
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (preserve_perms && !S_ISREG (sb.st_mode))
;
else
#endif
get_file (workfile, workfile,
rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
&dtext->text, &bufsize, &dtext->len);
commitpt->text = xmalloc (sizeof (Deltatext));
memset (commitpt->text, 0, sizeof (Deltatext));
bufsize = 0;
switch (diff_exec (workfile, tmpfile, NULL, NULL,
dargc, dargv, changefile))
{
case 0:
case 1:
break;
case -1:
error (1, errno, "error diffing %s", workfile);
break;
default:
error (1, 0, "error diffing %s", workfile);
break;
}
get_file (changefile, changefile,
rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
&commitpt->text->text, &bufsize, &commitpt->text->len);
if (commitpt->text->text == NULL)
{
commitpt->text->text = xstrdup ("");
commitpt->text->len = 0;
}
}
else
{
switch (diff_exec (tmpfile, workfile, NULL, NULL,
dargc, dargv, changefile))
{
case 0:
case 1:
break;
case -1:
error (1, errno, "error diffing %s", workfile);
break;
default:
error (1, 0, "error diffing %s", workfile);
break;
}
get_file (changefile, changefile,
rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
&dtext->text, &bufsize,
&dtext->len);
if (dtext->text == NULL)
{
dtext->text = xstrdup ("");
dtext->len = 0;
}
}
run_arg_free_p (dargc, dargv);
free (dargv);
if (numdots (commitpt->version) == numdots (delta->version))
{
if (STREQ (commitpt->version, rcs->head))
{
delta->next = rcs->head;
rcs->head = xstrdup (delta->version);
}
else
commitpt->next = xstrdup (delta->version);
}
if (rcs->versions == NULL)
rcs->versions = getlist();
nodep = getnode();
nodep->type = RCSVERS;
nodep->delproc = rcsvers_delproc;
nodep->data = delta;
nodep->key = delta->version;
(void) addnode (rcs->versions, nodep);
if (!(checkin_quiet || really_quiet))
{
cvs_output ("new revision: ", 14);
cvs_output (delta->version, 0);
cvs_output ("; previous revision: ", 21);
cvs_output (commitpt->version, 0);
cvs_output ("\n", 1);
}
RCS_rewrite (rcs, dtext, commitpt->version);
if ((flags & RCS_FLAGS_KEEPFILE) == 0)
{
if (unlink_file (workfile) < 0)
error (1, errno, "cannot remove %s", workfile);
}
if (unlink_file (tmpfile) < 0)
error (0, errno, "cannot remove %s", tmpfile);
free (tmpfile);
if (unlink_file (changefile) < 0)
error (0, errno, "cannot remove %s", changefile);
free (changefile);
checkin_done:
free (workfile);
if (commitpt != NULL && commitpt->text != NULL)
{
freedeltatext (commitpt->text);
commitpt->text = NULL;
}
freedeltatext (dtext);
if (status != 0)
{
if (delta->version) free (delta->version);
free_rcsvers_contents (delta);
}
return status;
}
struct cmp_file_data
{
const char *filename;
FILE *fp;
int different;
};
int
RCS_cmp_file (RCSNode *rcs, const char *rev1, char **rev1_cache,
const char *rev2, const char *options, const char *filename)
{
int binary;
TRACE (TRACE_FUNCTION, "RCS_cmp_file( %s, %s, %s, %s, %s )",
rcs->path ? rcs->path : "(null)",
rev1 ? rev1 : "(null)", rev2 ? rev2 : "(null)",
options ? options : "(null)", filename ? filename : "(null)");
if (options != NULL && options[0] != '\0')
binary = STREQ (options, "-kb");
else
{
char *expand;
expand = RCS_getexpand (rcs);
if (expand != NULL && STREQ (expand, "b"))
binary = 1;
else
binary = 0;
}
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (preserve_perms)
{
char *tmp;
int retcode;
tmp = cvs_temp_name();
retcode = RCS_checkout(rcs, NULL, rev, NULL, options, tmp, NULL, NULL);
if (retcode != 0)
return 1;
retcode = xcmp (tmp, filename);
if (CVS_UNLINK (tmp) < 0)
error (0, errno, "cannot remove %s", tmp);
free (tmp);
return retcode;
}
else
#endif
{
FILE *fp;
struct cmp_file_data data;
const char *use_file1;
char *tmpfile = NULL;
if (rev2 != NULL)
{
tmpfile = cvs_temp_name();
if (RCS_checkout (rcs, NULL, rev1, NULL, options, tmpfile,
NULL, NULL))
error (1, errno,
"cannot check out revision %s of %s",
rev1, rcs->print_path);
use_file1 = tmpfile;
if (rev1_cache != NULL)
*rev1_cache = tmpfile;
}
else
use_file1 = filename;
fp = CVS_FOPEN (use_file1, binary ? FOPEN_BINARY_READ : "r");
if (fp == NULL)
error (1, errno, "cannot open file %s for comparing", use_file1);
data.filename = use_file1;
data.fp = fp;
data.different = 0;
if (RCS_checkout (rcs, NULL, rev2 ? rev2 : rev1, NULL, options,
RUN_TTY, cmp_file_buffer, &data ))
error (1, errno,
"cannot check out revision %s of %s",
rev2 ? rev2 : rev1, rcs->print_path);
if (!data.different)
{
if (getc (fp) != EOF)
data.different = 1;
}
fclose (fp);
if (rev1_cache == NULL && tmpfile)
{
if (CVS_UNLINK (tmpfile ) < 0)
error (0, errno, "cannot remove %s", tmpfile);
free (tmpfile);
}
return data.different;
}
}
#define CMP_BUF_SIZE (8 * 1024)
static void
cmp_file_buffer (void *callerdat, const char *buffer, size_t len)
{
struct cmp_file_data *data = callerdat;
char *filebuf;
if (data->different)
return;
filebuf = xmalloc (len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len);
while (len > 0)
{
size_t checklen;
checklen = len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len;
if (fread (filebuf, 1, checklen, data->fp) != checklen)
{
if (ferror (data->fp))
error (1, errno, "cannot read file %s for comparing",
data->filename);
data->different = 1;
free (filebuf);
return;
}
if (memcmp (filebuf, buffer, checklen) != 0)
{
data->different = 1;
free (filebuf);
return;
}
buffer += checklen;
len -= checklen;
}
free (filebuf);
}
int
RCS_settag (RCSNode *rcs, const char *tag, const char *rev)
{
List *symbols;
Node *node;
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
if (STREQ (tag, TAG_BASE)
|| STREQ (tag, TAG_HEAD))
{
error (0, 0, "Attempt to add reserved tag name %s", tag);
return 1;
}
if (rev == NULL)
rev = rcs->branch ? rcs->branch : rcs->head;
symbols = RCS_symbols (rcs);
if (symbols == NULL)
{
symbols = getlist ();
rcs->symbols = symbols;
}
node = findnode (symbols, tag);
if (node != NULL)
{
free (node->data);
node->data = xstrdup (rev);
}
else
{
node = getnode ();
node->key = xstrdup (tag);
node->data = xstrdup (rev);
(void)addnode_at_front (symbols, node);
}
return 0;
}
int
RCS_deltag (RCSNode *rcs, const char *tag)
{
List *symbols;
Node *node;
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
symbols = RCS_symbols (rcs);
if (symbols == NULL)
return 1;
node = findnode (symbols, tag);
if (node == NULL)
return 1;
delnode (node);
return 0;
}
int
RCS_setbranch (RCSNode *rcs, const char *rev)
{
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
if (rev && ! *rev)
rev = NULL;
if (rev == NULL && rcs->branch == NULL)
return 0;
if (rev != NULL && rcs->branch != NULL && STREQ (rev, rcs->branch))
return 0;
if (rcs->branch != NULL)
free (rcs->branch);
rcs->branch = xstrdup (rev);
return 0;
}
int
RCS_lock (RCSNode *rcs, const char *rev, int lock_quiet)
{
List *locks;
Node *p;
char *user;
char *xrev = NULL;
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
locks = RCS_getlocks (rcs);
if (locks == NULL)
locks = rcs->locks = getlist();
user = getcaller();
if (rev == NULL)
xrev = RCS_head (rcs);
else
xrev = RCS_gettag (rcs, rev, 1, NULL);
if (xrev == NULL || findnode (rcs->versions, xrev) == NULL)
{
if (!lock_quiet)
error (0, 0, "%s: revision %s absent", rcs->print_path, rev);
free (xrev);
return 1;
}
p = findnode (locks, xrev);
if (p != NULL)
{
if (STREQ (p->data, user))
{
free (xrev);
return 0;
}
#if 0
if (!lock_quiet)
{
cvs_output (rev, 0);
cvs_output (" unlocked\n", 0);
}
delnode (p);
#else
error (1, 0, "Revision %s is already locked by %s",
xrev, (char *)p->data);
#endif
}
p = getnode();
p->key = xrev;
p->data = xstrdup (getcaller());
(void)addnode_at_front (locks, p);
if (!lock_quiet)
{
cvs_output (xrev, 0);
cvs_output (" locked\n", 0);
}
return 0;
}
int
RCS_unlock (RCSNode *rcs, char *rev, int unlock_quiet)
{
Node *lock;
List *locks;
char *user;
char *xrev = NULL;
user = getcaller();
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
if (rev == NULL)
{
Node *p;
if (rcs->head == NULL)
{
if (!unlock_quiet)
cvs_outerr ("can't unlock an empty tree\n", 0);
return 0;
}
locks = RCS_getlocks (rcs);
if (locks == NULL)
{
if (!unlock_quiet)
cvs_outerr ("No locks are set.\n", 0);
return 0;
}
lock = NULL;
for (p = locks->list->next; p != locks->list; p = p->next)
{
if (STREQ (p->data, user))
{
if (lock != NULL)
{
if (!unlock_quiet)
error (0, 0, "\
%s: multiple revisions locked by %s; please specify one", rcs->print_path, user);
return 1;
}
lock = p;
}
}
if (lock == NULL)
{
if (!unlock_quiet)
error (0, 0, "No locks are set for %s.\n", user);
return 0;
}
xrev = xstrdup (lock->key);
}
else
{
xrev = RCS_gettag (rcs, rev, 1, NULL);
if (xrev == NULL)
{
error (0, 0, "%s: revision %s absent", rcs->print_path, rev);
return 1;
}
}
lock = findnode (RCS_getlocks (rcs), xrev);
if (lock == NULL)
{
free (xrev);
return 0;
}
if (! STREQ (lock->data, user))
{
char *repos, *workfile;
if (!unlock_quiet)
error (0, 0, "\
%s: revision %s locked by %s; breaking lock", rcs->print_path, xrev,
(char *)lock->data);
repos = xstrdup (rcs->path);
workfile = strrchr (repos, '/');
*workfile++ = '\0';
notify_do ('C', workfile, NULL, user, NULL, NULL, repos);
free (repos);
}
delnode (lock);
if (!unlock_quiet)
{
cvs_output (xrev, 0);
cvs_output (" unlocked\n", 0);
}
free (xrev);
return 0;
}
void
RCS_addaccess (RCSNode *rcs, char *user)
{
char *access, *a;
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
if (rcs->access == NULL)
rcs->access = xstrdup (user);
else
{
access = xstrdup (rcs->access);
for (a = strtok (access, " "); a != NULL; a = strtok (NULL, " "))
{
if (STREQ (a, user))
{
free (access);
return;
}
}
free (access);
rcs->access = xrealloc (rcs->access,
strlen (rcs->access) + strlen (user) + 2);
strcat (rcs->access, " ");
strcat (rcs->access, user);
}
}
void
RCS_delaccess (RCSNode *rcs, char *user)
{
char *p, *s;
int ulen;
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
if (rcs->access == NULL)
return;
if (user == NULL)
{
free (rcs->access);
rcs->access = NULL;
return;
}
p = rcs->access;
ulen = strlen (user);
while (p != NULL)
{
if (strncmp (p, user, ulen) == 0 && (p[ulen] == '\0' || p[ulen] == ' '))
break;
p = strchr (p, ' ');
if (p != NULL)
++p;
}
if (p == NULL)
return;
s = p + ulen;
while (*s != '\0')
*p++ = *s++;
*p = '\0';
}
char *
RCS_getaccess (RCSNode *rcs)
{
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
return rcs->access;
}
static int
findtag (Node *node, void *arg)
{
char *rev = arg;
if (STREQ (node->data, rev))
return 1;
else
return 0;
}
int
RCS_delete_revs (RCSNode *rcs, char *tag1, char *tag2, int inclusive)
{
char *next;
Node *nodep;
RCSVers *revp = NULL;
RCSVers *beforep;
int status, found;
int save_noexec;
char *branchpoint = NULL;
char *rev1 = NULL;
char *rev2 = NULL;
int rev1_inclusive = inclusive;
int rev2_inclusive = inclusive;
char *before = NULL;
char *after = NULL;
char *beforefile = NULL;
char *afterfile = NULL;
char *outfile = NULL;
if (tag1 == NULL && tag2 == NULL)
return 0;
status = 1;
if (tag1 != NULL)
{
rev1 = RCS_gettag (rcs, tag1, 1, NULL);
if (rev1 == NULL || (nodep = findnode (rcs->versions, rev1)) == NULL)
{
error (0, 0, "%s: Revision %s doesn't exist.", rcs->print_path, tag1);
goto delrev_done;
}
}
if (tag2 != NULL)
{
rev2 = RCS_gettag (rcs, tag2, 1, NULL);
if (rev2 == NULL || (nodep = findnode (rcs->versions, rev2)) == NULL)
{
error (0, 0, "%s: Revision %s doesn't exist.", rcs->print_path, tag2);
goto delrev_done;
}
}
if (rev2 == NULL && numdots (rev1) == 1)
{
rev2 = xstrdup (rcs->head);
rev2_inclusive = 1;
}
if (rev2 == NULL)
rev2_inclusive = 1;
if (rev1 != NULL && rev2 != NULL)
{
if (RCS_isbranch (rcs, rev1) && STREQ (rev1, rev2))
{
char *tmp = RCS_getbranch (rcs, rev1, 0);
free (rev1);
free (rev2);
rev1 = rev2 = tmp;
}
else
{
char *temp;
int temp_inclusive;
if (numdots (rev1) == 1)
{
if (compare_revnums (rev1, rev2) <= 0)
{
temp = rev2;
rev2 = rev1;
rev1 = temp;
temp_inclusive = rev2_inclusive;
rev2_inclusive = rev1_inclusive;
rev1_inclusive = temp_inclusive;
}
}
else if (compare_revnums (rev1, rev2) > 0)
{
temp = rev2;
rev2 = rev1;
rev1 = temp;
temp_inclusive = rev2_inclusive;
rev2_inclusive = rev1_inclusive;
rev1_inclusive = temp_inclusive;
}
}
}
if (rev1 == NULL)
{
assert (rev2 != NULL);
if (numdots (rev2) == 1)
{
int temp_inclusive;
rev1 = rev2;
rev2 = NULL;
temp_inclusive = rev2_inclusive;
rev2_inclusive = rev1_inclusive;
rev1_inclusive = temp_inclusive;
}
}
before = NULL;
branchpoint = RCS_getbranchpoint (rcs, rev1 != NULL ? rev1 : rev2);
if (rev1 == NULL)
{
rev1 = xstrdup (branchpoint);
if (numdots (branchpoint) > 1)
{
char *bp;
bp = strrchr (branchpoint, '.');
while (*--bp != '.')
;
*bp = '\0';
before = xstrdup (branchpoint);
*bp = '.';
}
}
else if (! STREQ (rev1, branchpoint))
{
nodep = findnode (rcs->versions, branchpoint);
revp = nodep->data;
while (revp->next != NULL && ! STREQ (revp->next, rev1))
{
revp = nodep->data;
nodep = findnode (rcs->versions, revp->next);
}
if (revp->next == NULL)
{
error (0, 0, "%s: Revision %s doesn't exist.", rcs->print_path, rev1);
goto delrev_done;
}
if (rev1_inclusive)
before = xstrdup (revp->version);
else
{
before = rev1;
nodep = findnode (rcs->versions, before);
rev1 = xstrdup (((RCSVers *)nodep->data)->next);
}
}
else if (!rev1_inclusive)
{
before = rev1;
nodep = findnode (rcs->versions, before);
rev1 = xstrdup (((RCSVers *)nodep->data)->next);
}
else if (numdots (branchpoint) > 1)
{
char *bp;
bp = strrchr (branchpoint, '.');
while (*--bp != '.')
;
*bp = '\0';
before = xstrdup (branchpoint);
*bp = '.';
}
after = NULL;
next = rev1;
found = 0;
while (!found && next != NULL)
{
nodep = findnode (rcs->versions, next);
revp = nodep->data;
if (rev2 != NULL)
found = STREQ (revp->version, rev2);
next = revp->next;
if ((!found && next != NULL) || rev2_inclusive || rev2 == NULL)
{
if (findnode (RCS_getlocks (rcs), revp->version))
{
error (0, 0, "%s: can't remove locked revision %s",
rcs->print_path,
revp->version);
goto delrev_done;
}
if (revp->branches != NULL)
{
error (0, 0, "%s: can't remove branch point %s",
rcs->print_path,
revp->version);
goto delrev_done;
}
if (!inclusive
&& walklist (RCS_symbols (rcs), findtag, revp->version))
{
error (0, 0, "cannot remove revision %s because it has tags",
revp->version);
goto delrev_done;
}
cvs_output ("deleting revision ", 0);
cvs_output (revp->version, 0);
cvs_output ("\n", 1);
}
}
if (rev2 == NULL)
;
else if (found)
{
if (rev2_inclusive)
after = xstrdup (next);
else
after = xstrdup (revp->version);
}
else if (!inclusive)
{
status = 0;
goto delrev_done;
}
else
{
assert (tag1 != NULL);
assert (tag2 != NULL);
error (0, 0, "%s: invalid revision range %s:%s", rcs->print_path,
tag1, tag2);
goto delrev_done;
}
if (after == NULL && before == NULL)
{
error (1, 0, "attempt to delete all revisions");
}
if (after != NULL)
{
char *diffbuf;
size_t bufsize, len;
#if defined (WOE32) && !defined (__CYGWIN32__)
{
char *expand = RCS_getexpand (rcs);
if (expand != NULL && STREQ (expand, "b"))
error (1, 0,
"admin -o not implemented yet for binary on this system");
}
#endif
afterfile = cvs_temp_name();
status = RCS_checkout (rcs, NULL, after, NULL, "-ko", afterfile,
NULL, NULL);
if (status > 0)
goto delrev_done;
if (before == NULL)
{
diffbuf = NULL;
bufsize = 0;
get_file (afterfile, afterfile, "r", &diffbuf, &bufsize, &len);
save_noexec = noexec;
noexec = 0;
if (unlink_file (afterfile) < 0)
error (0, errno, "cannot remove %s", afterfile);
noexec = save_noexec;
free (afterfile);
afterfile = NULL;
free (rcs->head);
rcs->head = xstrdup (after);
}
else
{
int dargc = 0;
size_t darg_allocated = 0;
char **dargv = NULL;
beforefile = cvs_temp_name();
status = RCS_checkout (rcs, NULL, before, NULL, "-ko", beforefile,
NULL, NULL);
if (status > 0)
goto delrev_done;
outfile = cvs_temp_name();
run_add_arg_p (&dargc, &darg_allocated, &dargv, "-a");
run_add_arg_p (&dargc, &darg_allocated, &dargv, "-n");
status = diff_exec (beforefile, afterfile, NULL, NULL,
dargc, dargv, outfile);
run_arg_free_p (dargc, dargv);
free (dargv);
if (status == 2)
{
error (0, 0, "%s: could not diff", rcs->print_path);
status = 1;
goto delrev_done;
}
diffbuf = NULL;
bufsize = 0;
get_file (outfile, outfile, "r", &diffbuf, &bufsize, &len);
}
nodep = findnode (rcs->versions, after);
revp = nodep->data;
assert (revp->text == NULL);
revp->text = xmalloc (sizeof (Deltatext));
memset (revp->text, 0, sizeof (Deltatext));
revp->text->version = xstrdup (revp->version);
revp->text->text = diffbuf;
revp->text->len = len;
if (revp->text->text == NULL)
revp->text->text = xstrdup ("");
}
for (next = rev1;
next != NULL && (after == NULL || ! STREQ (next, after));
next = revp->next)
{
nodep = findnode (rcs->versions, next);
revp = nodep->data;
revp->outdated = 1;
}
if (before != NULL)
{
if (rev1 == NULL)
;
else if (STREQ (rev1, branchpoint))
{
nodep = findnode (rcs->versions, before);
revp = nodep->data;
nodep = revp->branches->list->next;
while (nodep != revp->branches->list &&
! STREQ (nodep->key, rev1))
nodep = nodep->next;
assert (nodep != revp->branches->list);
if (after == NULL)
delnode (nodep);
else
{
free (nodep->key);
nodep->key = xstrdup (after);
}
}
else
{
nodep = findnode (rcs->versions, before);
beforep = nodep->data;
free (beforep->next);
beforep->next = xstrdup (after);
}
}
status = 0;
delrev_done:
if (rev1 != NULL)
free (rev1);
if (rev2 && rev2 != rev1)
free (rev2);
if (branchpoint != NULL)
free (branchpoint);
if (before != NULL)
free (before);
if (after != NULL)
free (after);
save_noexec = noexec;
noexec = 0;
if (beforefile != NULL)
{
if (unlink_file (beforefile) < 0)
error (0, errno, "cannot remove %s", beforefile);
free (beforefile);
}
if (afterfile != NULL)
{
if (unlink_file (afterfile) < 0)
error (0, errno, "cannot remove %s", afterfile);
free (afterfile);
}
if (outfile != NULL)
{
if (unlink_file (outfile) < 0)
error (0, errno, "cannot remove %s", outfile);
free (outfile);
}
noexec = save_noexec;
return status;
}
int
RCS_exist_tag (RCSNode *rcs, char *tag)
{
assert (rcs != NULL);
if (findnode (RCS_symbols (rcs), tag))
return 1;
return 0;
}
int
RCS_exist_rev (RCSNode *rcs, char *rev)
{
assert (rcs != NULL);
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, NULL, NULL);
if (findnode(rcs->versions, rev) != 0)
return 1;
if (walklist (RCS_symbols(rcs), findtag, rev) != 0)
return 1;
return 0;
}
struct line
{
char *text;
size_t len;
RCSVers *vers;
int has_newline;
int refcount;
};
struct linevector
{
unsigned int nlines;
unsigned int lines_alloced;
struct line **vector;
};
static void
linevector_init (struct linevector *vec)
{
vec->lines_alloced = 0;
vec->nlines = 0;
vec->vector = NULL;
}
static int
linevector_add (struct linevector *vec, const char *text, size_t len,
RCSVers *vers, unsigned int pos)
{
const char *textend;
unsigned int i;
unsigned int nnew;
const char *p;
const char *nextline_text;
size_t nextline_len;
int nextline_newline;
struct line *q;
if (len == 0)
return 1;
textend = text + len;
nnew = 1;
for (p = text; p < textend; ++p)
if (*p == '\n' && p + 1 < textend)
++nnew;
if (vec->nlines + nnew >= vec->lines_alloced)
{
if (vec->lines_alloced == 0)
vec->lines_alloced = 10;
while (vec->nlines + nnew >= vec->lines_alloced)
vec->lines_alloced *= 2;
vec->vector = xnrealloc (vec->vector,
vec->lines_alloced, sizeof (*vec->vector));
}
for (i = vec->nlines + nnew - 1; i >= pos + nnew; --i)
vec->vector[i] = vec->vector[i - nnew];
if (pos > vec->nlines)
return 0;
i = pos;
nextline_text = text;
nextline_newline = 0;
for (p = text; p < textend; ++p)
if (*p == '\n')
{
nextline_newline = 1;
if (p + 1 == textend)
break;
nextline_len = p - nextline_text;
q = xmalloc (sizeof (struct line) + nextline_len);
q->vers = vers;
q->text = (char *)q + sizeof (struct line);
q->len = nextline_len;
q->has_newline = nextline_newline;
q->refcount = 1;
memcpy (q->text, nextline_text, nextline_len);
vec->vector[i++] = q;
nextline_text = (char *)p + 1;
nextline_newline = 0;
}
nextline_len = p - nextline_text;
q = xmalloc (sizeof (struct line) + nextline_len);
q->vers = vers;
q->text = (char *)q + sizeof (struct line);
q->len = nextline_len;
q->has_newline = nextline_newline;
q->refcount = 1;
memcpy (q->text, nextline_text, nextline_len);
vec->vector[i] = q;
vec->nlines += nnew;
return 1;
}
static void
linevector_delete (struct linevector *vec, unsigned int pos,
unsigned int nlines)
{
unsigned int i;
unsigned int last;
last = vec->nlines - nlines;
for (i = pos; i < pos + nlines; ++i)
{
if (--vec->vector[i]->refcount == 0)
free (vec->vector[i]);
}
for (i = pos; i < last; ++i)
vec->vector[i] = vec->vector[i + nlines];
vec->nlines -= nlines;
}
static void
linevector_copy (struct linevector *to, struct linevector *from)
{
unsigned int ln;
for (ln = 0; ln < to->nlines; ++ln)
{
if (--to->vector[ln]->refcount == 0)
free (to->vector[ln]);
}
if (from->nlines > to->lines_alloced)
{
if (to->lines_alloced == 0)
to->lines_alloced = 10;
while (from->nlines > to->lines_alloced)
to->lines_alloced *= 2;
to->vector = xnrealloc (to->vector,
to->lines_alloced,
sizeof (*to->vector));
}
memcpy (to->vector, from->vector,
xtimes (from->nlines, sizeof (*to->vector)));
to->nlines = from->nlines;
for (ln = 0; ln < to->nlines; ++ln)
++to->vector[ln]->refcount;
}
static void
linevector_free (struct linevector *vec)
{
unsigned int ln;
if (vec->vector != NULL)
{
for (ln = 0; ln < vec->nlines; ++ln)
if (--vec->vector[ln]->refcount == 0)
free (vec->vector[ln]);
free (vec->vector);
}
}
static const char *
month_printname (const char *month)
{
static const char *const months[] =
{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
int mnum;
mnum = atoi (month);
if (mnum < 1 || mnum > 12)
return "???";
return months[mnum - 1];
}
static int
apply_rcs_changes (struct linevector *lines, const char *diffbuf,
size_t difflen, const char *name, RCSVers *addvers,
RCSVers *delvers)
{
const char *p;
const char *q;
int op;
struct deltafrag {
enum {FRAG_ADD, FRAG_DELETE} type;
unsigned long pos;
unsigned long nlines;
const char *new_lines;
size_t len;
struct deltafrag *next;
};
struct deltafrag *dfhead;
struct deltafrag *df;
int err;
dfhead = NULL;
for (p = diffbuf; p != NULL && p < diffbuf + difflen; )
{
op = *p++;
if (op != 'a' && op != 'd')
error (1, 0, "unrecognized operation '\\x%x' in %s",
op, name);
df = xmalloc (sizeof (struct deltafrag));
df->next = dfhead;
dfhead = df;
df->pos = strtoul (p, (char **) &q, 10);
if (p == q)
error (1, 0, "number expected in %s", name);
p = q;
if (*p++ != ' ')
error (1, 0, "space expected in %s", name);
df->nlines = strtoul (p, (char **) &q, 10);
if (p == q)
error (1, 0, "number expected in %s", name);
p = q;
if (*p++ != '\012')
error (1, 0, "linefeed expected in %s", name);
if (op == 'a')
{
unsigned int i;
df->type = FRAG_ADD;
i = df->nlines;
for (q = p; i != 0; ++q)
if (*q == '\n')
--i;
else if (q == diffbuf + difflen)
{
if (i != 1)
error (1, 0, "premature end of change in %s", name);
else
break;
}
df->new_lines = p;
df->len = q - p;
p = q;
}
else
{
--df->pos;
assert (op == 'd');
df->type = FRAG_DELETE;
}
}
err = 0;
for (df = dfhead; df != NULL;)
{
unsigned int ln;
if (!err)
switch (df->type)
{
case FRAG_ADD:
if (! linevector_add (lines, df->new_lines, df->len, addvers,
df->pos))
err = 1;
break;
case FRAG_DELETE:
if (df->pos > lines->nlines
|| df->pos + df->nlines > lines->nlines)
return 0;
if (delvers != NULL)
for (ln = df->pos; ln < df->pos + df->nlines; ++ln)
lines->vector[ln]->vers = delvers;
linevector_delete (lines, df->pos, df->nlines);
break;
}
df = df->next;
free (dfhead);
dfhead = df;
}
return !err;
}
int
rcs_change_text (const char *name, char *textbuf, size_t textlen,
const char *diffbuf, size_t difflen, char **retbuf,
size_t *retlen)
{
struct linevector lines;
int ret;
*retbuf = NULL;
*retlen = 0;
linevector_init (&lines);
if (! linevector_add (&lines, textbuf, textlen, NULL, 0))
error (1, 0, "cannot initialize line vector");
if (! apply_rcs_changes (&lines, diffbuf, difflen, name, NULL, NULL))
{
error (0, 0, "invalid change text in %s", name);
ret = 0;
}
else
{
char *p;
size_t n;
unsigned int ln;
n = 0;
for (ln = 0; ln < lines.nlines; ++ln)
n += lines.vector[ln]->len + 1;
p = xmalloc (n);
*retbuf = p;
for (ln = 0; ln < lines.nlines; ++ln)
{
memcpy (p, lines.vector[ln]->text, lines.vector[ln]->len);
p += lines.vector[ln]->len;
if (lines.vector[ln]->has_newline)
*p++ = '\n';
}
*retlen = p - *retbuf;
assert (*retlen <= n);
ret = 1;
}
linevector_free (&lines);
return ret;
}
void
RCS_deltas (RCSNode *rcs, FILE *fp, struct rcsbuffer *rcsbuf,
const char *version, enum rcs_delta_op op, char **text,
size_t *len, char **log, size_t *loglen)
{
struct rcsbuffer rcsbuf_local;
char *branchversion;
char *cpversion;
char *key;
char *value;
size_t vallen;
RCSVers *vers;
RCSVers *prev_vers;
RCSVers *trunk_vers;
char *next;
int ishead, isnext, isversion, onbranch;
Node *node;
struct linevector headlines;
struct linevector curlines;
struct linevector trunklines;
int foundhead;
assert (version);
if (fp == NULL)
{
rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf_local);
rcsbuf = &rcsbuf_local;
}
if (log) *log = NULL;
ishead = 1;
vers = NULL;
prev_vers = NULL;
trunk_vers = NULL;
next = NULL;
onbranch = 0;
foundhead = 0;
linevector_init (&curlines);
linevector_init (&headlines);
linevector_init (&trunklines);
branchversion = xstrdup (version);
cpversion = strchr (branchversion, '.');
if (cpversion != NULL)
cpversion = strchr (cpversion + 1, '.');
if (cpversion != NULL)
*cpversion = '\0';
do {
if (! rcsbuf_getrevnum (rcsbuf, &key))
error (1, 0, "unexpected EOF reading RCS file %s", rcs->print_path);
if (next != NULL && ! STREQ (next, key))
{
isnext = 0;
isversion = 0;
}
else
{
isnext = 1;
node = findnode (rcs->versions, key);
if (node == NULL)
error (1, 0,
"mismatch in rcs file %s between deltas and deltatexts (%s)",
rcs->print_path, key);
prev_vers = vers;
vers = node->data;
next = vers->next;
if (STREQ (branchversion, key))
isversion = 1;
else
isversion = 0;
}
while (1)
{
if (! rcsbuf_getkey (rcsbuf, &key, &value))
error (1, 0, "%s does not appear to be a valid rcs file",
rcs->print_path);
if (log != NULL
&& isversion
&& STREQ (key, "log")
&& STREQ (branchversion, version))
{
if (*log != NULL)
{
error (0, 0, "Duplicate `log' keyword in RCS file (`%s').",
rcs->print_path);
free (*log);
}
*log = rcsbuf_valcopy (rcsbuf, value, 0, loglen);
}
if (STREQ (key, "text"))
{
rcsbuf_valpolish (rcsbuf, value, 0, &vallen);
if (ishead)
{
if (! linevector_add (&curlines, value, vallen, NULL, 0))
error (1, 0, "invalid rcs file %s", rcs->print_path);
ishead = 0;
}
else if (isnext)
{
if (! apply_rcs_changes (&curlines, value, vallen,
rcs->path,
onbranch ? vers : NULL,
onbranch ? NULL : prev_vers))
error (1, 0, "invalid change text in %s", rcs->print_path);
}
break;
}
}
if (isversion)
{
if (STREQ (branchversion, version))
{
linevector_copy (&headlines, &curlines);
foundhead = 1;
if (onbranch)
{
onbranch = 0;
vers = trunk_vers;
next = vers->next;
linevector_copy (&curlines, &trunklines);
}
}
else
{
Node *p;
onbranch = 1;
if (numdots (branchversion) < 2)
{
unsigned int ln;
trunk_vers = vers;
linevector_copy (&trunklines, &curlines);
for (ln = 0; ln < curlines.nlines; ++ln)
curlines.vector[ln]->vers = NULL;
}
if (vers->branches == NULL)
error (1, 0, "missing expected branches in %s",
rcs->print_path);
if (!cpversion)
error (1, 0, "Invalid revision number in `%s'.",
rcs->print_path);
*cpversion = '.';
++cpversion;
cpversion = strchr (cpversion, '.');
if (cpversion == NULL)
error (1, 0, "version number confusion in %s",
rcs->print_path);
for (p = vers->branches->list->next;
p != vers->branches->list;
p = p->next)
if (strncmp (p->key, branchversion,
cpversion - branchversion) == 0)
break;
if (p == vers->branches->list)
error (1, 0, "missing expected branch in %s",
rcs->print_path);
next = p->key;
cpversion = strchr (cpversion + 1, '.');
if (cpversion != NULL)
*cpversion = '\0';
}
}
if (op == RCS_FETCH && foundhead)
break;
} while (next != NULL);
free (branchversion);
rcsbuf_cache (rcs, rcsbuf);
if (! foundhead)
error (1, 0, "could not find desired version %s in %s",
version, rcs->print_path);
switch (op)
{
case RCS_ANNOTATE:
{
unsigned int ln;
for (ln = 0; ln < headlines.nlines; ++ln)
{
char *buf;
char *ym;
char *md;
RCSVers *prvers;
prvers = headlines.vector[ln]->vers;
if (prvers == NULL)
prvers = vers;
buf = xmalloc (strlen (prvers->version) + 24);
sprintf (buf, "%-12s (%-8.8s ",
prvers->version,
prvers->author);
cvs_output (buf, 0);
free (buf);
ym = strchr (prvers->date, '.');
if (ym == NULL)
{
cvs_output ("??", 0);
cvs_output ("-???", 0);
cvs_output ("-??", 0);
}
else
{
md = strchr (ym + 1, '.');
if (md == NULL)
cvs_output ("??", 0);
else
cvs_output (md + 1, 2);
cvs_output ("-", 1);
cvs_output (month_printname (ym + 1), 0);
cvs_output ("-", 1);
cvs_output (ym - 2, 2);
}
cvs_output ("): ", 0);
if (headlines.vector[ln]->len != 0)
cvs_output (headlines.vector[ln]->text,
headlines.vector[ln]->len);
cvs_output ("\n", 1);
}
}
break;
case RCS_FETCH:
{
char *p;
size_t n;
unsigned int ln;
assert (text != NULL);
assert (len != NULL);
n = 0;
for (ln = 0; ln < headlines.nlines; ++ln)
n += headlines.vector[ln]->len + 1;
p = xmalloc (n);
*text = p;
for (ln = 0; ln < headlines.nlines; ++ln)
{
memcpy (p, headlines.vector[ln]->text,
headlines.vector[ln]->len);
p += headlines.vector[ln]->len;
if (headlines.vector[ln]->has_newline)
*p++ = '\n';
}
*len = p - *text;
assert (*len <= n);
}
break;
}
linevector_free (&curlines);
linevector_free (&headlines);
linevector_free (&trunklines);
return;
}
static RCSVers *
getdelta (struct rcsbuffer *rcsbuf, char *rcsfile, char **keyp, char **valp)
{
RCSVers *vnode;
char *key, *value, *cp;
Node *kv;
if (*keyp != NULL)
{
key = *keyp;
value = *valp;
}
else
{
if (! rcsbuf_getkey (rcsbuf, &key, &value))
error (1, 0, "%s: unexpected EOF", rcsfile);
}
for (cp = key;
(isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0';
cp++)
;
if (*cp != '\0' || strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) != 0)
{
*keyp = key;
*valp = value;
return NULL;
}
vnode = xmalloc (sizeof (RCSVers));
memset (vnode, 0, sizeof (RCSVers));
vnode->version = xstrdup (key);
cp = value + (sizeof RCSDATE) - 1;
while (whitespace (*cp))
cp++;
vnode->date = xstrdup (cp);
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 %s; `author' not in the expected place", rcsfile);
vnode->author = rcsbuf_valcopy (rcsbuf, value, 0, NULL);
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 %s; `state' not in the expected place", rcsfile);
vnode->state = rcsbuf_valcopy (rcsbuf, value, 0, NULL);
if (value != NULL && STREQ (value, RCSDEAD))
{
vnode->dead = 1;
}
if (! rcsbuf_getkey (rcsbuf, &key, &value))
{
error (1, 0, "unexpected end of file reading %s", rcsfile);
}
if (STREQ (key, RCSDESC))
{
*keyp = key;
*valp = value;
error (0, 0, "warning: 'branches' keyword missing from %s", rcsfile);
return vnode;
}
if (value != NULL)
{
vnode->branches = getlist ();
do_branches (vnode->branches, value);
}
if (! rcsbuf_getkey (rcsbuf, &key, &value))
{
error (1, 0, "unexpected end of file reading %s", rcsfile);
}
if (STREQ (key, RCSDESC))
{
*keyp = key;
*valp = value;
error (0, 0, "warning: 'next' keyword missing from %s", rcsfile);
return vnode;
}
if (value != NULL)
vnode->next = rcsbuf_valcopy (rcsbuf, value, 0, NULL);
while (1)
{
if (! rcsbuf_getkey (rcsbuf, &key, &value))
error (1, 0, "unexpected end of file reading %s", rcsfile);
if (strcmp (key, RCSDESC) == 0)
break;
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (strcmp (key, "hardlinks") == 0)
{
char *word;
vnode->hardlinks = getlist();
while ((word = rcsbuf_valword (rcsbuf, &value)) != NULL)
{
Node *n = getnode();
n->key = word;
addnode (vnode->hardlinks, n);
}
continue;
}
#endif
if (STREQ (key, RCSDEAD))
{
vnode->dead = 1;
if (vnode->state != NULL)
free (vnode->state);
vnode->state = xstrdup (RCSDEAD);
continue;
}
for (cp = key;
(isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0';
cp++)
;
if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
break;
if (vnode->other_delta == NULL)
vnode->other_delta = getlist ();
kv = getnode ();
kv->type = rcsbuf_valcmp (rcsbuf) ? RCSCMPFLD : RCSFIELD;
kv->key = xstrdup (key);
kv->data = rcsbuf_valcopy (rcsbuf, value, kv->type == RCSFIELD, NULL);
if (addnode (vnode->other_delta, kv) != 0)
{
error (0, 0, "warning: duplicate key `%s' in RCS file `%s'",
key, rcsfile);
freenode (kv);
}
}
*keyp = key;
*valp = value;
return vnode;
}
static void
freedeltatext (Deltatext *d)
{
if (d->version != NULL)
free (d->version);
if (d->log != NULL)
free (d->log);
if (d->text != NULL)
free (d->text);
if (d->other != NULL)
dellist (&d->other);
free (d);
}
static Deltatext *
RCS_getdeltatext (RCSNode *rcs, FILE *fp, struct rcsbuffer *rcsbuf)
{
char *num;
char *key, *value;
Node *p;
Deltatext *d;
if (! rcsbuf_getrevnum (rcsbuf, &num))
{
if (num == NULL)
return NULL;
else
error (1, 0, "%s: unexpected EOF", rcs->print_path);
}
p = findnode (rcs->versions, num);
if (p == NULL)
error (1, 0, "mismatch in rcs file %s between deltas and deltatexts (%s)",
rcs->print_path, num);
d = xmalloc (sizeof (Deltatext));
d->version = xstrdup (num);
if (! rcsbuf_getkey (rcsbuf, &key, &value))
error (1, 0, "%s, delta %s: unexpected EOF", rcs->print_path, num);
if (! STREQ (key, "log"))
error (1, 0, "%s, delta %s: expected `log', got `%s'",
rcs->print_path, num, key);
d->log = rcsbuf_valcopy (rcsbuf, value, 0, NULL);
d->other = getlist();
while (1)
{
if (! rcsbuf_getkey (rcsbuf, &key, &value))
error (1, 0, "%s, delta %s: unexpected EOF", rcs->print_path, num);
if (STREQ (key, "text"))
break;
p = getnode();
p->type = rcsbuf_valcmp (rcsbuf) ? RCSCMPFLD : RCSFIELD;
p->key = xstrdup (key);
p->data = rcsbuf_valcopy (rcsbuf, value, p->type == RCSFIELD, NULL);
if (addnode (d->other, p) < 0)
{
error (0, 0, "warning: %s, delta %s: duplicate field `%s'",
rcs->print_path, num, key);
}
}
d->text = rcsbuf_valcopy (rcsbuf, value, 0, &d->len);
return d;
}
static int
putsymbol_proc (Node *symnode, void *fparg)
{
FILE *fp = fparg;
putc ('\n', fp);
putc ('\t', fp);
fputs (symnode->key, fp);
putc (':', fp);
fputs (symnode->data, fp);
return 0;
}
static int
putlock_proc (Node *symnode, void *fp)
{
return fprintf (fp, "\n\t%s:%s", (char *)symnode->data, symnode->key);
}
static int
putrcsfield_proc (Node *node, void *vfp)
{
FILE *fp = vfp;
if (node->key[0] == ';')
return 0;
fprintf (fp, "\n%s\t", node->key);
if (node->data != NULL)
{
if (node->type == RCSCMPFLD || strpbrk (node->data, "$,.:;@") == NULL)
fputs (node->data, fp);
else
{
putc ('@', fp);
expand_at_signs (node->data, (off_t) strlen (node->data), fp);
putc ('@', fp);
}
}
if (! STREQ (node->key, "desc") &&
! STREQ (node->key, "log") &&
! STREQ (node->key, "text"))
{
putc (';', fp);
}
return 0;
}
#ifdef PRESERVE_PERMISSIONS_SUPPORT
static int
puthardlink_proc (node, vfp)
Node *node;
void *vfp;
{
FILE *fp = vfp;
char *basename = strrchr (node->key, '/');
if (basename == NULL)
basename = node->key;
else
++basename;
putc ('\t', fp);
putc ('@', fp);
(void) expand_at_signs (basename, strlen (basename), fp);
putc ('@', fp);
return 0;
}
#endif
static void
RCS_putadmin (RCSNode *rcs, FILE *fp)
{
fprintf (fp, "%s\t%s;\n", RCSHEAD, rcs->head ? rcs->head : "");
if (rcs->branch)
fprintf (fp, "%s\t%s;\n", RCSBRANCH, rcs->branch);
fputs ("access", fp);
if (rcs->access)
{
char *p, *s;
s = xstrdup (rcs->access);
for (p = strtok (s, " \n\t"); p != NULL; p = strtok (NULL, " \n\t"))
fprintf (fp, "\n\t%s", p);
free (s);
}
fputs (";\n", fp);
fputs (RCSSYMBOLS, fp);
if (rcs->symbols == NULL && rcs->symbols_data != NULL)
{
fputs ("\n\t", fp);
fputs (rcs->symbols_data, fp);
}
else
walklist (RCS_symbols (rcs), putsymbol_proc, fp);
fputs (";\n", fp);
fputs ("locks", fp);
if (rcs->locks_data)
fprintf (fp, "\t%s", rcs->locks_data);
else if (rcs->locks)
walklist (rcs->locks, putlock_proc, fp);
if (rcs->strict_locks)
fprintf (fp, "; strict");
fputs (";\n", fp);
if (rcs->comment)
{
fprintf (fp, "comment\t@");
expand_at_signs (rcs->comment, (off_t) strlen (rcs->comment), fp);
fputs ("@;\n", fp);
}
if (rcs->expand && ! STREQ (rcs->expand, "kv"))
fprintf (fp, "%s\t@%s@;\n", RCSEXPAND, rcs->expand);
walklist (rcs->other, putrcsfield_proc, fp);
putc ('\n', fp);
}
static void
putdelta (RCSVers *vers, FILE *fp)
{
Node *bp, *start;
if (vers == NULL || vers->outdated)
return;
fprintf (fp, "\n%s\n%s\t%s;\t%s %s;\t%s %s;\nbranches",
vers->version,
RCSDATE, vers->date,
"author", vers->author,
"state", vers->state ? vers->state : "");
if (vers->branches != NULL)
{
start = vers->branches->list;
for (bp = start->next; bp != start; bp = bp->next)
fprintf (fp, "\n\t%s", bp->key);
}
fprintf (fp, ";\nnext\t%s;", vers->next ? vers->next : "");
walklist (vers->other_delta, putrcsfield_proc, fp);
#ifdef PRESERVE_PERMISSIONS_SUPPORT
if (vers->hardlinks)
{
fprintf (fp, "\nhardlinks");
walklist (vers->hardlinks, puthardlink_proc, fp);
putc (';', fp);
}
#endif
putc ('\n', fp);
}
static void
RCS_putdtree (RCSNode *rcs, char *rev, FILE *fp)
{
RCSVers *versp;
Node *p, *branch;
Node *branchlist, *onebranch;
List *branches;
List *onebranchlist;
if (rev == NULL)
return;
branches = getlist();
for (; rev != NULL;)
{
p = findnode (rcs->versions, rev);
if (p == NULL)
{
error (1, 0,
"error parsing repository file %s, file may be corrupt.",
rcs->path);
}
versp = p->data;
putdelta (versp, fp);
if (versp->branches != NULL)
{
branch = getnode();
branch->data = versp->branches;
addnode(branches, branch);
}
rev = versp->next;
}
branchlist = branches->list;
for (branch = branchlist->next;
branch != branchlist;
branch = branch->next)
{
onebranchlist = (List *)(branch->data);
onebranch = onebranchlist->list;
for (p = onebranch->next; p != onebranch; p = p->next)
RCS_putdtree (rcs, p->key, fp);
branch->data = NULL;
}
dellist(&branches);
}
static void
RCS_putdesc (RCSNode *rcs, FILE *fp)
{
fprintf (fp, "\n\n%s\n@", RCSDESC);
if (rcs->desc != NULL)
{
off_t len = (off_t) strlen (rcs->desc);
if (len > 0)
{
expand_at_signs (rcs->desc, len, fp);
if (rcs->desc[len-1] != '\n')
putc ('\n', fp);
}
}
fputs ("@\n", fp);
}
static void
putdeltatext (FILE *fp, Deltatext *d)
{
fprintf (fp, "\n\n%s\nlog\n@", d->version);
if (d->log != NULL)
{
int loglen = strlen (d->log);
expand_at_signs (d->log, (off_t) loglen, fp);
if (d->log[loglen-1] != '\n')
putc ('\n', fp);
}
putc ('@', fp);
walklist (d->other, putrcsfield_proc, fp);
fputs ("\ntext\n@", fp);
if (d->text != NULL)
expand_at_signs (d->text, (off_t) d->len, fp);
fputs ("@\n", fp);
}
static void
RCS_copydeltas (RCSNode *rcs, FILE *fin, struct rcsbuffer *rcsbufin,
FILE *fout, Deltatext *newdtext, char *insertpt)
{
int actions;
RCSVers *dadmin;
Node *np;
int insertbefore, found;
char *bufrest;
int nls;
size_t buflen;
#ifndef HAVE_MMAP
char buf[8192];
int got;
#endif
actions = walklist (rcs->versions, count_delta_actions, NULL);
insertbefore = (newdtext != NULL && numdots (newdtext->version) == 1);
while (actions != 0 || newdtext != NULL)
{
Deltatext *dtext;
dtext = RCS_getdeltatext (rcs, fin, rcsbufin);
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 = np->data;
if (dadmin->outdated)
{
freedeltatext (dtext);
--actions;
continue;
}
if (dadmin->text != NULL)
{
if (dadmin->text->log != NULL || dadmin->text->text != NULL)
--actions;
if (dadmin->text->log != NULL)
{
free (dtext->log);
dtext->log = dadmin->text->log;
dadmin->text->log = NULL;
}
if (dadmin->text->text != NULL)
{
free (dtext->text);
dtext->text = dadmin->text->text;
dtext->len = dadmin->text->len;
dadmin->text->text = NULL;
}
}
putdeltatext (fout, dtext);
freedeltatext (dtext);
if (found && !insertbefore)
{
putdeltatext (fout, newdtext);
newdtext = NULL;
insertpt = NULL;
}
}
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);
}
#ifndef HAVE_MMAP
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;
}
#endif
}
static int
count_delta_actions (Node *np, void *ignore)
{
RCSVers *dadmin = np->data;
if (dadmin->outdated)
return 1;
if (dadmin->text != NULL
&& (dadmin->text->log != NULL || dadmin->text->text != NULL))
{
return 1;
}
return 0;
}
static void
rcs_cleanup (void)
{
TRACE (TRACE_FUNCTION, "rcs_cleanup()");
if (rcs_lockfile != NULL)
{
char *tmp = rcs_lockfile;
rcs_lockfile = NULL;
if (rcs_lockfd >= 0)
{
if (close (rcs_lockfd) != 0)
error (0, errno, "error closing lock file %s", tmp);
rcs_lockfd = -1;
}
if (unlink_file (tmp) < 0
&& !existence_error (errno))
error (0, errno, "cannot remove %s", tmp);
}
}
static FILE *
rcs_internal_lockfile (char *rcsfile)
{
struct stat rstat;
FILE *fp;
static int first_call = 1;
if (first_call)
{
first_call = 0;
cleanup_register (rcs_cleanup);
}
assert (rcs_lockfile == NULL);
assert (rcs_lockfd < 0);
rcs_lockfile = rcs_lockfilename (rcsfile);
if (stat (rcsfile, &rstat) < 0)
{
if (existence_error (errno))
rstat.st_mode = S_IRUSR | S_IRGRP | S_IROTH;
else
error (1, errno, "cannot stat %s", rcsfile);
}
rcs_lockfd = open (rcs_lockfile,
OPEN_BINARY | O_WRONLY | O_CREAT | O_EXCL | O_TRUNC,
S_IRUSR | S_IRGRP | S_IROTH);
if (rcs_lockfd < 0)
{
error (1, errno, "could not open lock file `%s'", rcs_lockfile);
}
#ifdef HAVE_FCHMOD
if (fchmod (rcs_lockfd, rstat.st_mode) < 0)
error (1, errno, "cannot change mode for %s", rcs_lockfile);
#endif
fp = fdopen (rcs_lockfd, FOPEN_BINARY_WRITE);
if (fp == NULL)
error (1, errno, "cannot fdopen %s", rcs_lockfile);
return fp;
}
static void
rcs_internal_unlockfile (FILE *fp, char *rcsfile)
{
assert (rcs_lockfile != NULL);
assert (rcs_lockfd >= 0);
if (ferror (fp))
error (1, errno, "error writing to lock file %s", rcs_lockfile);
if (fflush (fp) != 0)
error (1, errno, "error flushing file `%s' to kernel buffers",
rcs_lockfile);
#ifdef HAVE_FSYNC
if (fsync (rcs_lockfd) < 0)
error (1, errno, "error fsyncing file `%s'", rcs_lockfile);
#endif
if (fclose (fp) == EOF)
error (1, errno, "error closing lock file %s", rcs_lockfile);
rcs_lockfd = -1;
rename_file (rcs_lockfile, rcsfile);
{
char *tmp = rcs_lockfile;
rcs_lockfile = NULL;
free (tmp);
}
}
static char *
rcs_lockfilename (const char *rcsfile)
{
char *lockfile, *lockp;
const char *rcsbase, *rcsp, *rcsend;
int rcslen;
rcslen = strlen (rcsfile);
lockfile = xmalloc (rcslen + 10);
rcsbase = last_component (rcsfile);
rcsend = rcsfile + rcslen - sizeof(RCSEXT);
for (lockp = lockfile, rcsp = rcsfile; rcsp < rcsbase; ++rcsp)
*lockp++ = *rcsp;
*lockp++ = ',';
while (rcsp <= rcsend)
*lockp++ = *rcsp++;
*lockp++ = ',';
*lockp = '\0';
return lockfile;
}
void
RCS_rewrite (RCSNode *rcs, Deltatext *newdtext, char *insertpt)
{
FILE *fin, *fout;
struct rcsbuffer rcsbufin;
if (noexec)
return;
resolve_symlink (&(rcs->path));
fout = rcs_internal_lockfile (rcs->path);
RCS_putadmin (rcs, fout);
RCS_putdtree (rcs, rcs->head, fout);
RCS_putdesc (rcs, fout);
rcsbuf_cache_open (rcs, rcs->delta_pos, &fin, &rcsbufin);
rcs->delta_pos = ftello (fout);
if (rcs->delta_pos == -1)
error (1, errno, "cannot ftello in RCS file %s", rcs->path);
RCS_copydeltas (rcs, fin, &rcsbufin, fout, newdtext, insertpt);
rcsbuf_close (&rcsbufin);
if (ferror (fin))
error (0, 0, "warning: ferror set while rewriting RCS file `%s'", rcs->path);
if (fclose (fin) < 0)
error (0, errno, "warning: closing RCS file `%s'", rcs->path);
rcs_internal_unlockfile (fout, rcs->path);
}
void
RCS_abandon (RCSNode *rcs)
{
free_rcsnode_contents (rcs);
rcs->symbols_data = NULL;
rcs->expand = NULL;
rcs->access = NULL;
rcs->locks_data = NULL;
rcs->comment = NULL;
rcs->desc = NULL;
rcs->flags |= PARTIAL;
}
char *
make_file_label (const char *path, const char *rev, RCSNode *rcs)
{
char datebuf[MAXDATELEN + 1];
char *label;
if (rev)
{
char date[MAXDATELEN + 1];
assert (strcmp(DEVNULL, path));
RCS_getrevtime (rcs, rev, datebuf, 0);
(void) date_to_internet (date, datebuf);
label = Xasprintf ("-L%s\t%s\t%s", path, date, rev);
}
else
{
struct stat sb;
struct tm *wm;
if (strcmp(DEVNULL, path))
{
const char *file = last_component (path);
if (stat (file, &sb) < 0)
error (1, errno, "could not get info for `%s'", path);
wm = gmtime (&sb.st_mtime);
}
else
{
time_t t = 0;
wm = gmtime(&t);
}
(void) tm_to_internet (datebuf, wm);
label = Xasprintf ("-L%s\t%s", path, datebuf);
}
return label;
}
void
RCS_setlocalid (const char *infopath, unsigned int ln,
void **keywords_in, const char *arg)
{
char *copy, *next, *key, *s;
struct rcs_keyword *keywords;
enum keyword save_expandto;
if (!*keywords_in)
*keywords_in = new_keywords ();
keywords = *keywords_in;
copy = xstrdup (arg);
next = copy;
key = strtok (next, "=");
for (s = key; *s != '\0'; s++)
{
if (! isalpha ((unsigned char) *s))
{
if (!parse_error (infopath, ln))
error (0, 0,
"%s [%u]: LocalKeyword ignored: Bad character `%c' in key `%s'",
primary_root_inverse_translate (infopath),
ln, *s, key);
free (copy);
return;
}
}
save_expandto = keywords[KEYWORD_LOCALID].expandto;
while ((key = strtok (NULL, ",")) != NULL) {
if (!strcmp(key, keywords[KEYWORD_ID].string))
keywords[KEYWORD_LOCALID].expandto = KEYWORD_ID;
else if (!strcmp(key, keywords[KEYWORD_HEADER].string))
keywords[KEYWORD_LOCALID].expandto = KEYWORD_HEADER;
else if (!strcmp(key, keywords[KEYWORD_CVSHEADER].string))
keywords[KEYWORD_LOCALID].expandto = KEYWORD_CVSHEADER;
else
{
keywords[KEYWORD_LOCALID].expandto = save_expandto;
if (!parse_error (infopath, ln))
error (0, 0,
"%s [%u]: LocalKeyword ignored: Unknown LocalId mode: `%s'",
primary_root_inverse_translate (infopath),
ln, key);
free (copy);
return;
}
}
keywords[KEYWORD_LOCALID].string = xstrdup (next);
keywords[KEYWORD_LOCALID].len = strlen (next);
keywords[KEYWORD_LOCALID].expandit = 1;
free (copy);
}
void
RCS_setincexc (void **keywords_in, const char *arg)
{
char *key;
char *copy, *next;
bool include = false;
struct rcs_keyword *keyword;
struct rcs_keyword *keywords;
if (!*keywords_in)
*keywords_in = new_keywords ();
keywords = *keywords_in;
copy = xstrdup(arg);
next = copy;
switch (*next++) {
case 'e':
include = false;
break;
case 'i':
include = true;
break;
default:
free(copy);
return;
}
if (include)
for (keyword = keywords; keyword->string != NULL; keyword++)
{
keyword->expandit = false;
}
key = strtok(next, ",");
while (key) {
for (keyword = keywords; keyword->string != NULL; keyword++) {
if (strcmp (keyword->string, key) == 0)
keyword->expandit = include;
}
key = strtok(NULL, ",");
}
free(copy);
return;
}
#define ATTIC "/" CVSATTIC
static char *
getfullCVSname(char *CVSname, char **pathstore)
{
if (current_parsed_root->directory) {
int rootlen;
char *c = NULL;
int alen = sizeof(ATTIC) - 1;
*pathstore = xstrdup(CVSname);
if ((c = strrchr(*pathstore, '/')) != NULL) {
if (c - *pathstore >= alen) {
if (!strncmp(c - alen, ATTIC, alen)) {
while (*c != '\0') {
*(c - alen) = *c;
c++;
}
*(c - alen) = '\0';
}
}
}
rootlen = strlen(current_parsed_root->directory);
if (!strncmp(*pathstore, current_parsed_root->directory, rootlen) &&
(*pathstore)[rootlen] == '/')
CVSname = (*pathstore + rootlen + 1);
else
CVSname = (*pathstore);
}
return CVSname;
}