diff options
Diffstat (limited to 'contrib/cvs/src/entries.c')
-rw-r--r-- | contrib/cvs/src/entries.c | 1263 |
1 files changed, 1263 insertions, 0 deletions
diff --git a/contrib/cvs/src/entries.c b/contrib/cvs/src/entries.c new file mode 100644 index 0000000..9592c3c --- /dev/null +++ b/contrib/cvs/src/entries.c @@ -0,0 +1,1263 @@ +/* + * Copyright (C) 1986-2005 The Free Software Foundation, Inc. + * + * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, + * and others. + * + * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk + * Portions Copyright (C) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS source distribution. + * + * Entries file to Files file + * + * Creates the file Files containing the names that comprise the project, from + * the Entries file. + */ + +/* + * $FreeBSD$ + */ +#include "cvs.h" +#include "getline.h" + +static Node *AddEntryNode PROTO((List * list, Entnode *entnode)); + +static Entnode *fgetentent PROTO((FILE *, char *, int *)); +static int fputentent PROTO((FILE *, Entnode *)); + +static Entnode *subdir_record PROTO((int, const char *, const char *)); + +static FILE *entfile; +static char *entfilename; /* for error messages */ + + + +/* + * Construct an Entnode + */ +static Entnode *Entnode_Create PROTO ((enum ent_type, const char *, + const char *, const char *, + const char *, const char *, + const char *, const char *)); + +static Entnode * +Entnode_Create(type, user, vn, ts, options, tag, date, ts_conflict) + enum ent_type type; + const char *user; + const char *vn; + const char *ts; + const char *options; + const char *tag; + const char *date; + const char *ts_conflict; +{ + Entnode *ent; + + /* Note that timestamp and options must be non-NULL */ + ent = (Entnode *) xmalloc (sizeof (Entnode)); + ent->type = type; + ent->user = xstrdup (user); + ent->version = xstrdup (vn); + ent->timestamp = xstrdup (ts ? ts : ""); + ent->options = xstrdup (options ? options : ""); + ent->tag = xstrdup (tag); + ent->date = xstrdup (date); + ent->conflict = xstrdup (ts_conflict); + + return ent; +} + +/* + * Destruct an Entnode + */ +static void Entnode_Destroy PROTO ((Entnode *)); + +static void +Entnode_Destroy (ent) + Entnode *ent; +{ + free (ent->user); + free (ent->version); + free (ent->timestamp); + free (ent->options); + if (ent->tag) + free (ent->tag); + if (ent->date) + free (ent->date); + if (ent->conflict) + free (ent->conflict); + free (ent); +} + +/* + * Write out the line associated with a node of an entries file + */ +static int write_ent_proc PROTO ((Node *, void *)); +static int +write_ent_proc (node, closure) + Node *node; + void *closure; +{ + Entnode *entnode = node->data; + + if (closure != NULL && entnode->type != ENT_FILE) + *(int *) closure = 1; + + if (fputentent(entfile, entnode)) + error (1, errno, "cannot write %s", entfilename); + + return (0); +} + +/* + * write out the current entries file given a list, making a backup copy + * first of course + */ +static void +write_entries (list) + List *list; +{ + int sawdir; + + sawdir = 0; + + /* open the new one and walk the list writing entries */ + entfilename = CVSADM_ENTBAK; + entfile = CVS_FOPEN (entfilename, "w+"); + if (entfile == NULL) + { + /* Make this a warning, not an error. For example, one user might + have checked out a working directory which, for whatever reason, + contains an Entries.Log file. A second user, without write access + to that working directory, might want to do a "cvs log". The + problem rewriting Entries shouldn't affect the ability of "cvs log" + to work, although the warning is probably a good idea so that + whether Entries gets rewritten is not an inexplicable process. */ + /* FIXME: should be including update_dir in message. */ + error (0, errno, "cannot rewrite %s", entfilename); + + /* Now just return. We leave the Entries.Log file around. As far + as I know, there is never any data lying around in 'list' that + is not in Entries.Log at this time (if there is an error writing + Entries.Log that is a separate problem). */ + return; + } + + (void) walklist (list, write_ent_proc, (void *) &sawdir); + if (! sawdir) + { + struct stickydirtag *sdtp; + + /* We didn't write out any directories. Check the list + private data to see whether subdirectory information is + known. If it is, we need to write out an empty D line. */ + sdtp = list->list->data; + if (sdtp == NULL || sdtp->subdirs) + if (fprintf (entfile, "D\n") < 0) + error (1, errno, "cannot write %s", entfilename); + } + if (fclose (entfile) == EOF) + error (1, errno, "error closing %s", entfilename); + + /* now, atomically (on systems that support it) rename it */ + rename_file (entfilename, CVSADM_ENT); + + /* now, remove the log file */ + if (unlink_file (CVSADM_ENTLOG) < 0 + && !existence_error (errno)) + error (0, errno, "cannot remove %s", CVSADM_ENTLOG); +} + + + +/* + * Removes the argument file from the Entries file if necessary. + */ +void +Scratch_Entry (list, fname) + List *list; + const char *fname; +{ + Node *node; + + if (trace) + (void) fprintf (stderr, "%s-> Scratch_Entry(%s)\n", + CLIENT_SERVER_STR, fname); + + /* hashlookup to see if it is there */ + if ((node = findnode_fn (list, fname)) != NULL) + { + if (!noexec) + { + entfilename = CVSADM_ENTLOG; + entfile = open_file (entfilename, "a"); + + if (fprintf (entfile, "R ") < 0) + error (1, errno, "cannot write %s", entfilename); + + write_ent_proc (node, NULL); + + if (fclose (entfile) == EOF) + error (1, errno, "error closing %s", entfilename); + } + + delnode (node); /* delete the node */ + +#ifdef SERVER_SUPPORT + if (server_active) + server_scratch (fname); +#endif + } +} + + + +/* + * Enters the given file name/version/time-stamp into the Entries file, + * removing the old entry first, if necessary. + */ +void +Register (list, fname, vn, ts, options, tag, date, ts_conflict) + List *list; + const char *fname; + const char *vn; + const char *ts; + const char *options; + const char *tag; + const char *date; + const char *ts_conflict; +{ + Entnode *entnode; + Node *node; + +#ifdef SERVER_SUPPORT + if (server_active) + { + server_register (fname, vn, ts, options, tag, date, ts_conflict); + } +#endif + + if (trace) + { + (void) fprintf (stderr, "%s-> Register(%s, %s, %s%s%s, %s, %s %s)\n", + CLIENT_SERVER_STR, + fname, vn, ts ? ts : "", + ts_conflict ? "+" : "", ts_conflict ? ts_conflict : "", + options, tag ? tag : "", date ? date : ""); + } + + entnode = Entnode_Create (ENT_FILE, fname, vn, ts, options, tag, date, + ts_conflict); + node = AddEntryNode (list, entnode); + + if (!noexec) + { + entfilename = CVSADM_ENTLOG; + entfile = CVS_FOPEN (entfilename, "a"); + + if (entfile == NULL) + { + /* Warning, not error, as in write_entries. */ + /* FIXME-update-dir: should be including update_dir in message. */ + error (0, errno, "cannot open %s", entfilename); + return; + } + + if (fprintf (entfile, "A ") < 0) + error (1, errno, "cannot write %s", entfilename); + + write_ent_proc (node, NULL); + + if (fclose (entfile) == EOF) + error (1, errno, "error closing %s", entfilename); + } +} + +/* + * Node delete procedure for list-private sticky dir tag/date info + */ +static void +freesdt (p) + Node *p; +{ + struct stickydirtag *sdtp = p->data; + + if (sdtp->tag) + free (sdtp->tag); + if (sdtp->date) + free (sdtp->date); + free ((char *) sdtp); +} + +/* Return the next real Entries line. On end of file, returns NULL. + On error, prints an error message and returns NULL. */ + +static Entnode * +fgetentent(fpin, cmd, sawdir) + FILE *fpin; + char *cmd; + int *sawdir; +{ + Entnode *ent; + char *line; + size_t line_chars_allocated; + register char *cp; + enum ent_type type; + char *l, *user, *vn, *ts, *options; + char *tag_or_date, *tag, *date, *ts_conflict; + int line_length; + + line = NULL; + line_chars_allocated = 0; + + ent = NULL; + while ((line_length = getline (&line, &line_chars_allocated, fpin)) > 0) + { + l = line; + + /* If CMD is not NULL, we are reading an Entries.Log file. + Each line in the Entries.Log file starts with a single + character command followed by a space. For backward + compatibility, the absence of a space indicates an add + command. */ + if (cmd != NULL) + { + if (l[1] != ' ') + *cmd = 'A'; + else + { + *cmd = l[0]; + l += 2; + } + } + + type = ENT_FILE; + + if (l[0] == 'D') + { + type = ENT_SUBDIR; + *sawdir = 1; + ++l; + /* An empty D line is permitted; it is a signal that this + Entries file lists all known subdirectories. */ + } + + if (l[0] != '/') + continue; + + user = l + 1; + if ((cp = strchr (user, '/')) == NULL) + continue; + *cp++ = '\0'; + vn = cp; + if ((cp = strchr (vn, '/')) == NULL) + continue; + *cp++ = '\0'; + ts = cp; + if ((cp = strchr (ts, '/')) == NULL) + continue; + *cp++ = '\0'; + options = cp; + if ((cp = strchr (options, '/')) == NULL) + continue; + *cp++ = '\0'; + tag_or_date = cp; + if ((cp = strchr (tag_or_date, '\n')) == NULL) + continue; + *cp = '\0'; + tag = (char *) NULL; + date = (char *) NULL; + if (*tag_or_date == 'T') + tag = tag_or_date + 1; + else if (*tag_or_date == 'D') + date = tag_or_date + 1; + + if ((ts_conflict = strchr (ts, '+'))) + *ts_conflict++ = '\0'; + + /* + * XXX - Convert timestamp from old format to new format. + * + * If the timestamp doesn't match the file's current + * mtime, we'd have to generate a string that doesn't + * match anyways, so cheat and base it on the existing + * string; it doesn't have to match the same mod time. + * + * For an unmodified file, write the correct timestamp. + */ + { + struct stat sb; + if (strlen (ts) > 30 && CVS_STAT (user, &sb) == 0) + { + char *c = ctime (&sb.st_mtime); + /* Fix non-standard format. */ + if (c[8] == '0') c[8] = ' '; + + if (!strncmp (ts + 25, c, 24)) + ts = time_stamp (user); + else + { + ts += 24; + ts[0] = '*'; + } + } + } + + ent = Entnode_Create (type, user, vn, ts, options, tag, date, + ts_conflict); + break; + } + + if (line_length < 0 && !feof (fpin)) + error (0, errno, "cannot read entries file"); + + free (line); + return ent; +} + +static int +fputentent(fp, p) + FILE *fp; + Entnode *p; +{ + switch (p->type) + { + case ENT_FILE: + break; + case ENT_SUBDIR: + if (fprintf (fp, "D") < 0) + return 1; + break; + } + + if (fprintf (fp, "/%s/%s/%s", p->user, p->version, p->timestamp) < 0) + return 1; + if (p->conflict) + { + if (fprintf (fp, "+%s", p->conflict) < 0) + return 1; + } + if (fprintf (fp, "/%s/", p->options) < 0) + return 1; + + if (p->tag) + { + if (fprintf (fp, "T%s\n", p->tag) < 0) + return 1; + } + else if (p->date) + { + if (fprintf (fp, "D%s\n", p->date) < 0) + return 1; + } + else + { + if (fprintf (fp, "\n") < 0) + return 1; + } + + return 0; +} + + +/* Read the entries file into a list, hashing on the file name. + + UPDATE_DIR is the name of the current directory, for use in error + messages, or NULL if not known (that is, noone has gotten around + to updating the caller to pass in the information). */ +List * +Entries_Open (aflag, update_dir) + int aflag; + char *update_dir; +{ + List *entries; + struct stickydirtag *sdtp = NULL; + Entnode *ent; + char *dirtag, *dirdate; + int dirnonbranch; + int do_rewrite = 0; + FILE *fpin; + int sawdir; + + /* get a fresh list... */ + entries = getlist (); + + /* + * Parse the CVS/Tag file, to get any default tag/date settings. Use + * list-private storage to tuck them away for Version_TS(). + */ + ParseTag (&dirtag, &dirdate, &dirnonbranch); + if (aflag || dirtag || dirdate) + { + sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp)); + memset ((char *) sdtp, 0, sizeof (*sdtp)); + sdtp->aflag = aflag; + sdtp->tag = xstrdup (dirtag); + sdtp->date = xstrdup (dirdate); + sdtp->nonbranch = dirnonbranch; + + /* feed it into the list-private area */ + entries->list->data = sdtp; + entries->list->delproc = freesdt; + } + + sawdir = 0; + + fpin = CVS_FOPEN (CVSADM_ENT, "r"); + if (fpin == NULL) + { + if (update_dir != NULL) + error (0, 0, "in directory %s:", update_dir); + error (0, errno, "cannot open %s for reading", CVSADM_ENT); + } + else + { + while ((ent = fgetentent (fpin, (char *) NULL, &sawdir)) != NULL) + { + (void) AddEntryNode (entries, ent); + } + + if (fclose (fpin) < 0) + /* FIXME-update-dir: should include update_dir in message. */ + error (0, errno, "cannot close %s", CVSADM_ENT); + } + + fpin = CVS_FOPEN (CVSADM_ENTLOG, "r"); + if (fpin != NULL) + { + char cmd; + Node *node; + + while ((ent = fgetentent (fpin, &cmd, &sawdir)) != NULL) + { + switch (cmd) + { + case 'A': + (void) AddEntryNode (entries, ent); + break; + case 'R': + node = findnode_fn (entries, ent->user); + if (node != NULL) + delnode (node); + Entnode_Destroy (ent); + break; + default: + /* Ignore unrecognized commands. */ + Entnode_Destroy (ent); + break; + } + } + do_rewrite = 1; + if (fclose (fpin) < 0) + /* FIXME-update-dir: should include update_dir in message. */ + error (0, errno, "cannot close %s", CVSADM_ENTLOG); + } + + /* Update the list private data to indicate whether subdirectory + information is known. Nonexistent list private data is taken + to mean that it is known. */ + if (sdtp != NULL) + sdtp->subdirs = sawdir; + else if (! sawdir) + { + sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp)); + memset ((char *) sdtp, 0, sizeof (*sdtp)); + sdtp->subdirs = 0; + entries->list->data = sdtp; + entries->list->delproc = freesdt; + } + + if (do_rewrite && !noexec) + write_entries (entries); + + /* clean up and return */ + if (dirtag) + free (dirtag); + if (dirdate) + free (dirdate); + return (entries); +} + +void +Entries_Close(list) + List *list; +{ + if (list) + { + if (!noexec) + { + if (isfile (CVSADM_ENTLOG)) + write_entries (list); + } + dellist(&list); + } +} + + +/* + * Free up the memory associated with the data section of an ENTRIES type + * node + */ +static void +Entries_delproc (node) + Node *node; +{ + Entnode *p = node->data; + + Entnode_Destroy(p); +} + +/* + * Get an Entries file list node, initialize it, and add it to the specified + * list + */ +static Node * +AddEntryNode (list, entdata) + List *list; + Entnode *entdata; +{ + Node *p; + + /* was it already there? */ + if ((p = findnode_fn (list, entdata->user)) != NULL) + { + /* take it out */ + delnode (p); + } + + /* get a node and fill in the regular stuff */ + p = getnode (); + p->type = ENTRIES; + p->delproc = Entries_delproc; + + /* this one gets a key of the name for hashing */ + /* FIXME This results in duplicated data --- the hash package shouldn't + assume that the key is dynamically allocated. The user's free proc + should be responsible for freeing the key. */ + p->key = xstrdup (entdata->user); + p->data = entdata; + + /* put the node into the list */ + addnode (list, p); + return (p); +} + +static char *root_template; + +static int +get_root_template(const char *repository, const char *path) +{ + if (root_template) { + if (strcmp(path, root_template) == 0) + return(0); + free(root_template); + } + if ((root_template = strdup(path)) == NULL) + return(-1); + return(0); +} + +/* + * Write out/Clear the CVS/Template file. + */ +void +WriteTemplate (dir, update_dir) + const char *dir; + const char *update_dir; +{ + char *tmp = NULL; + struct stat st1; + struct stat st2; + + if (Parse_Info(CVSROOTADM_RCSINFO, "cvs", get_root_template, 1) < 0) + return; + + if (asprintf(&tmp, "%s/%s", dir, CVSADM_TEMPLATE) < 0) + error (1, errno, "out of memory"); + + if (stat(root_template, &st1) == 0) { + if (stat(tmp, &st2) < 0 || st1.st_mtime != st2.st_mtime) { + FILE *fi; + FILE *fo; + + if ((fi = open_file(root_template, "r")) != NULL) { + if ((fo = open_file(tmp, "w")) != NULL) { + int n; + char buf[256]; + + while ((n = fread(buf, 1, sizeof(buf), fi)) > 0) + fwrite(buf, 1, n, fo); + fflush(fo); + if (ferror(fi) || ferror(fo)) { + fclose(fo); + remove(tmp); + error (1, errno, "error copying Template"); + } else { + struct timeval times[2]; + fclose(fo); + times[0].tv_sec = st1.st_mtime; + times[0].tv_usec = 0; + times[1] = times[0]; + utimes(tmp, times); + } + } + fclose(fi); + } + } + } + free(tmp); +} + +/* + * Write out/Clear the CVS/Tag file. + */ +void +WriteTag (dir, tag, date, nonbranch, update_dir, repository) + const char *dir; + const char *tag; + const char *date; + int nonbranch; + const char *update_dir; + const char *repository; +{ + FILE *fout; + char *tmp; + + if (noexec) + return; + + tmp = xmalloc ((dir ? strlen (dir) : 0) + + sizeof (CVSADM_TAG) + + 10); + if (dir == NULL) + (void) strcpy (tmp, CVSADM_TAG); + else + (void) sprintf (tmp, "%s/%s", dir, CVSADM_TAG); + + if (tag || date) + { + fout = open_file (tmp, "w+"); + if (tag) + { + if (nonbranch) + { + if (fprintf (fout, "N%s\n", tag) < 0) + error (1, errno, "write to %s failed", tmp); + } + else + { + if (fprintf (fout, "T%s\n", tag) < 0) + error (1, errno, "write to %s failed", tmp); + } + } + else + { + if (fprintf (fout, "D%s\n", date) < 0) + error (1, errno, "write to %s failed", tmp); + } + if (fclose (fout) == EOF) + error (1, errno, "cannot close %s", tmp); + } + else + if (unlink_file (tmp) < 0 && ! existence_error (errno)) + error (1, errno, "cannot remove %s", tmp); + free (tmp); +#ifdef SERVER_SUPPORT + if (server_active) + server_set_sticky (update_dir, repository, tag, date, nonbranch); +#endif +} + +/* Parse the CVS/Tag file for the current directory. + + If it contains a date, sets *DATEP to the date in a newly malloc'd + string, *TAGP to NULL, and *NONBRANCHP to an unspecified value. + + If it contains a branch tag, sets *TAGP to the tag in a newly + malloc'd string, *NONBRANCHP to 0, and *DATEP to NULL. + + If it contains a nonbranch tag, sets *TAGP to the tag in a newly + malloc'd string, *NONBRANCHP to 1, and *DATEP to NULL. + + If it does not exist, or contains something unrecognized by this + version of CVS, set *DATEP and *TAGP to NULL and *NONBRANCHP to + an unspecified value. + + If there is an error, print an error message, set *DATEP and *TAGP + to NULL, and return. */ +void +ParseTag (tagp, datep, nonbranchp) + char **tagp; + char **datep; + int *nonbranchp; +{ + FILE *fp; + + if (tagp) + *tagp = (char *) NULL; + if (datep) + *datep = (char *) NULL; + /* Always store a value here, even in the 'D' case where the value + is unspecified. Shuts up tools which check for references to + uninitialized memory. */ + if (nonbranchp != NULL) + *nonbranchp = 0; + fp = CVS_FOPEN (CVSADM_TAG, "r"); + if (fp) + { + char *line; + int line_length; + size_t line_chars_allocated; + + line = NULL; + line_chars_allocated = 0; + + if ((line_length = getline (&line, &line_chars_allocated, fp)) > 0) + { + /* Remove any trailing newline. */ + if (line[line_length - 1] == '\n') + line[--line_length] = '\0'; + switch (*line) + { + case 'T': + if (tagp != NULL) + *tagp = xstrdup (line + 1); + break; + case 'D': + if (datep != NULL) + *datep = xstrdup (line + 1); + break; + case 'N': + if (tagp != NULL) + *tagp = xstrdup (line + 1); + if (nonbranchp != NULL) + *nonbranchp = 1; + break; + default: + /* Silently ignore it; it may have been + written by a future version of CVS which extends the + syntax. */ + break; + } + } + + if (line_length < 0) + { + /* FIXME-update-dir: should include update_dir in messages. */ + if (feof (fp)) + error (0, 0, "cannot read %s: end of file", CVSADM_TAG); + else + error (0, errno, "cannot read %s", CVSADM_TAG); + } + + if (fclose (fp) < 0) + /* FIXME-update-dir: should include update_dir in message. */ + error (0, errno, "cannot close %s", CVSADM_TAG); + + free (line); + } + else if (!existence_error (errno)) + /* FIXME-update-dir: should include update_dir in message. */ + error (0, errno, "cannot open %s", CVSADM_TAG); +} + +/* + * This is called if all subdirectory information is known, but there + * aren't any subdirectories. It records that fact in the list + * private data. + */ + +void +Subdirs_Known (entries) + List *entries; +{ + struct stickydirtag *sdtp = entries->list->data; + + /* If there is no list private data, that means that the + subdirectory information is known. */ + if (sdtp != NULL && ! sdtp->subdirs) + { + FILE *fp; + + sdtp->subdirs = 1; + if (!noexec) + { + /* Create Entries.Log so that Entries_Close will do something. */ + entfilename = CVSADM_ENTLOG; + fp = CVS_FOPEN (entfilename, "a"); + if (fp == NULL) + { + int save_errno = errno; + + /* As in subdir_record, just silently skip the whole thing + if there is no CVSADM directory. */ + if (! isdir (CVSADM)) + return; + error (1, save_errno, "cannot open %s", entfilename); + } + else + { + if (fclose (fp) == EOF) + error (1, errno, "cannot close %s", entfilename); + } + } + } +} + +/* Record subdirectory information. */ + +static Entnode * +subdir_record (cmd, parent, dir) + int cmd; + const char *parent; + const char *dir; +{ + Entnode *entnode; + + /* None of the information associated with a directory is + currently meaningful. */ + entnode = Entnode_Create (ENT_SUBDIR, dir, "", "", "", + (char *) NULL, (char *) NULL, + (char *) NULL); + + if (!noexec) + { + if (parent == NULL) + entfilename = CVSADM_ENTLOG; + else + { + entfilename = xmalloc (strlen (parent) + + sizeof CVSADM_ENTLOG + + 10); + sprintf (entfilename, "%s/%s", parent, CVSADM_ENTLOG); + } + + entfile = CVS_FOPEN (entfilename, "a"); + if (entfile == NULL) + { + int save_errno = errno; + + /* It is not an error if there is no CVS administration + directory. Permitting this case simplifies some + calling code. */ + + if (parent == NULL) + { + if (! isdir (CVSADM)) + return entnode; + } + else + { + sprintf (entfilename, "%s/%s", parent, CVSADM); + if (! isdir (entfilename)) + { + free (entfilename); + entfilename = NULL; + return entnode; + } + } + + error (1, save_errno, "cannot open %s", entfilename); + } + + if (fprintf (entfile, "%c ", cmd) < 0) + error (1, errno, "cannot write %s", entfilename); + + if (fputentent (entfile, entnode) != 0) + error (1, errno, "cannot write %s", entfilename); + + if (fclose (entfile) == EOF) + error (1, errno, "error closing %s", entfilename); + + if (parent != NULL) + { + free (entfilename); + entfilename = NULL; + } + } + + return entnode; +} + +/* + * Record the addition of a new subdirectory DIR in PARENT. PARENT + * may be NULL, which means the current directory. ENTRIES is the + * current entries list; it may be NULL, which means that it need not + * be updated. + */ + +void +Subdir_Register (entries, parent, dir) + List *entries; + const char *parent; + const char *dir; +{ + Entnode *entnode; + + /* Ignore attempts to register ".". These can happen in the + server code. */ + if (dir[0] == '.' && dir[1] == '\0') + return; + + entnode = subdir_record ('A', parent, dir); + + if (entries != NULL && (parent == NULL || strcmp (parent, ".") == 0)) + (void) AddEntryNode (entries, entnode); + else + Entnode_Destroy (entnode); +} + +/* + * Record the removal of a subdirectory. The arguments are the same + * as for Subdir_Register. + */ + +void +Subdir_Deregister (entries, parent, dir) + List *entries; + const char *parent; + const char *dir; +{ + Entnode *entnode; + + entnode = subdir_record ('R', parent, dir); + Entnode_Destroy (entnode); + + if (entries != NULL && (parent == NULL || strcmp (parent, ".") == 0)) + { + Node *p; + + p = findnode_fn (entries, dir); + if (p != NULL) + delnode (p); + } +} + + + +/* OK, the following base_* code tracks the revisions of the files in + CVS/Base. We do this in a file CVS/Baserev. Separate from + CVS/Entries because it needs to go in separate data structures + anyway (the name in Entries must be unique), so this seemed + cleaner. The business of rewriting the whole file in + base_deregister and base_register is the kind of thing we used to + do for Entries and which turned out to be slow, which is why there + is now the Entries.Log machinery. So maybe from that point of + view it is a mistake to do this separately from Entries, I dunno. + + We also need something analogous for: + + 1. CVS/Template (so we can update the Template file automagically + without the user needing to check out a new working directory). + Updating would probably print a message (that part might be + optional, although probably it should be visible because not all + cvs commands would make the update happen and so it is a + user-visible behavior). Constructing version number for template + is a bit hairy (base it on the timestamp on the server? Or see if + the template is in checkoutlist and if yes use its versioning and + if no don't version it?).... + + 2. cvsignore (need to keep a copy in the working directory to do + "cvs release" on the client side; see comment at src/release.c + (release). Would also allow us to stop needing Questionable. */ + +enum base_walk { + /* Set the revision for FILE to *REV. */ + BASE_REGISTER, + /* Get the revision for FILE and put it in a newly malloc'd string + in *REV, or put NULL if not mentioned. */ + BASE_GET, + /* Remove FILE. */ + BASE_DEREGISTER +}; + +static void base_walk PROTO ((enum base_walk, struct file_info *, char **)); + +/* Read through the lines in CVS/Baserev, taking the actions as documented + for CODE. */ + +static void +base_walk (code, finfo, rev) + enum base_walk code; + struct file_info *finfo; + char **rev; +{ + FILE *fp; + char *line; + size_t line_allocated; + FILE *newf; + char *baserev_fullname; + char *baserevtmp_fullname; + + line = NULL; + line_allocated = 0; + newf = NULL; + + /* First compute the fullnames for the error messages. This + computation probably should be broken out into a separate function, + as recurse.c does it too and places like Entries_Open should be + doing it. */ + baserev_fullname = xmalloc (sizeof (CVSADM_BASEREV) + + strlen (finfo->update_dir) + + 2); + baserev_fullname[0] = '\0'; + baserevtmp_fullname = xmalloc (sizeof (CVSADM_BASEREVTMP) + + strlen (finfo->update_dir) + + 2); + baserevtmp_fullname[0] = '\0'; + if (finfo->update_dir[0] != '\0') + { + strcat (baserev_fullname, finfo->update_dir); + strcat (baserev_fullname, "/"); + strcat (baserevtmp_fullname, finfo->update_dir); + strcat (baserevtmp_fullname, "/"); + } + strcat (baserev_fullname, CVSADM_BASEREV); + strcat (baserevtmp_fullname, CVSADM_BASEREVTMP); + + fp = CVS_FOPEN (CVSADM_BASEREV, "r"); + if (fp == NULL) + { + if (!existence_error (errno)) + { + error (0, errno, "cannot open %s for reading", baserev_fullname); + goto out; + } + } + + switch (code) + { + case BASE_REGISTER: + case BASE_DEREGISTER: + newf = CVS_FOPEN (CVSADM_BASEREVTMP, "w"); + if (newf == NULL) + { + error (0, errno, "cannot open %s for writing", + baserevtmp_fullname); + goto out; + } + break; + case BASE_GET: + *rev = NULL; + break; + } + + if (fp != NULL) + { + while (getline (&line, &line_allocated, fp) >= 0) + { + char *linefile; + char *p; + char *linerev; + + if (line[0] != 'B') + /* Ignore, for future expansion. */ + continue; + + linefile = line + 1; + p = strchr (linefile, '/'); + if (p == NULL) + /* Syntax error, ignore. */ + continue; + linerev = p + 1; + p = strchr (linerev, '/'); + if (p == NULL) + continue; + + linerev[-1] = '\0'; + if (fncmp (linefile, finfo->file) == 0) + { + switch (code) + { + case BASE_REGISTER: + case BASE_DEREGISTER: + /* Don't copy over the old entry, we don't want it. */ + break; + case BASE_GET: + *p = '\0'; + *rev = xstrdup (linerev); + *p = '/'; + goto got_it; + } + } + else + { + linerev[-1] = '/'; + switch (code) + { + case BASE_REGISTER: + case BASE_DEREGISTER: + if (fprintf (newf, "%s\n", line) < 0) + error (0, errno, "error writing %s", + baserevtmp_fullname); + break; + case BASE_GET: + break; + } + } + } + if (ferror (fp)) + error (0, errno, "cannot read %s", baserev_fullname); + } + got_it: + + if (code == BASE_REGISTER) + { + if (fprintf (newf, "B%s/%s/\n", finfo->file, *rev) < 0) + error (0, errno, "error writing %s", + baserevtmp_fullname); + } + + out: + + if (line != NULL) + free (line); + + if (fp != NULL) + { + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", baserev_fullname); + } + if (newf != NULL) + { + if (fclose (newf) < 0) + error (0, errno, "cannot close %s", baserevtmp_fullname); + rename_file (CVSADM_BASEREVTMP, CVSADM_BASEREV); + } + + free (baserev_fullname); + free (baserevtmp_fullname); +} + +/* Return, in a newly malloc'd string, the revision for FILE in CVS/Baserev, + or NULL if not listed. */ + +char * +base_get (finfo) + struct file_info *finfo; +{ + char *rev; + base_walk (BASE_GET, finfo, &rev); + return rev; +} + +/* Set the revision for FILE to REV. */ + +void +base_register (finfo, rev) + struct file_info *finfo; + char *rev; +{ + base_walk (BASE_REGISTER, finfo, &rev); +} + +/* Remove FILE. */ + +void +base_deregister (finfo) + struct file_info *finfo; +{ + base_walk (BASE_DEREGISTER, finfo, NULL); +} |