386BSD 0.1 development
authorWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Fri, 10 Apr 1992 18:59:43 +0000 (10:59 -0800)
committerWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Fri, 10 Apr 1992 18:59:43 +0000 (10:59 -0800)
Work on file usr/othersrc/public/cvs-1.3/src/find_names.c
Work on file usr/othersrc/public/cvs-1.3/src/lock.c
Work on file usr/othersrc/public/cvs-1.3/src/parseinfo.c

Co-Authored-By: Lynne Greer Jolitz <ljolitz@cardio.ucsf.edu>
Synthesized-from: 386BSD-0.1

usr/othersrc/public/cvs-1.3/src/find_names.c [new file with mode: 0644]
usr/othersrc/public/cvs-1.3/src/lock.c [new file with mode: 0644]
usr/othersrc/public/cvs-1.3/src/parseinfo.c [new file with mode: 0644]

diff --git a/usr/othersrc/public/cvs-1.3/src/find_names.c b/usr/othersrc/public/cvs-1.3/src/find_names.c
new file mode 100644 (file)
index 0000000..187bd23
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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 1.3 kit.
+ * 
+ * Find Names
+ * 
+ * Finds all the pertinent file names, both from the administration and from the
+ * repository
+ * 
+ * Find Dirs
+ * 
+ * Finds all pertinent sub-directories of the checked out instantiation and the
+ * repository (and optionally the attic)
+ */
+
+#include "cvs.h"
+
+#ifndef lint
+static char rcsid[] = "@(#)find_names.c 1.38 92/04/10";
+#endif
+
+#if __STDC__
+static int find_dirs (char *dir, List * list, int checkadm);
+static int find_rcs (char *dir, List * list);
+#else
+static int find_rcs ();
+static int find_dirs ();
+#endif                         /* __STDC__ */
+
+static List *filelist;
+
+/*
+ * add the key from entry on entries list to the files list
+ */
+static int
+add_entries_proc (node)
+    Node *node;
+{
+    Node *fnode;
+
+    fnode = getnode ();
+    fnode->type = FILES;
+    fnode->key = xstrdup (node->key);
+    if (addnode (filelist, fnode) != 0)
+       freenode (fnode);
+    return (0);
+}
+
+/*
+ * compare two files list node (for sort)
+ */
+static int
+fsortcmp (p, q)
+    Node *p, *q;
+{
+    return (strcmp (p->key, q->key));
+}
+
+List *
+Find_Names (repository, which, aflag, optentries)
+    char *repository;
+    int which;
+    int aflag;
+    List **optentries;
+{
+    List *entries;
+    List *files;
+    char dir[PATH_MAX];
+
+    /* make a list for the files */
+    files = filelist = getlist ();
+
+    /* look at entries (if necessary) */
+    if (which & W_LOCAL)
+    {
+       /* parse the entries file (if it exists) */
+       entries = ParseEntries (aflag);
+
+       if (entries != NULL)
+       {
+           /* walk the entries file adding elements to the files list */
+           (void) walklist (entries, add_entries_proc);
+
+           /* if our caller wanted the entries list, return it; else free it */
+           if (optentries != NULL)
+               *optentries = entries;
+           else
+               dellist (&entries);
+       }
+    }
+
+    if ((which & W_REPOS) && repository && !isreadable (CVSADM_ENTSTAT))
+    {
+       /* search the repository */
+       if (find_rcs (repository, files) != 0)
+           error (1, errno, "cannot open directory %s", repository);
+
+       /* search the attic too */
+       if (which & W_ATTIC)
+       {
+           (void) sprintf (dir, "%s/%s", repository, CVSATTIC);
+           (void) find_rcs (dir, files);
+       }
+    }
+
+    /* sort the list into alphabetical order and return it */
+    sortlist (files, fsortcmp);
+    return (files);
+}
+
+/*
+ * create a list of directories to traverse from the current directory
+ */
+List *
+Find_Dirs (repository, which)
+    char *repository;
+    int which;
+{
+    List *dirlist;
+
+    /* make a list for the directories */
+    dirlist = getlist ();
+
+    /* find the local ones */
+    if (which & W_LOCAL)
+    {
+       /* look only for CVS controlled sub-directories */
+       if (find_dirs (".", dirlist, 1) != 0)
+           error (1, errno, "cannot open current directory");
+    }
+
+    /* look for sub-dirs in the repository */
+    if ((which & W_REPOS) && repository)
+    {
+       /* search the repository */
+       if (find_dirs (repository, dirlist, 0) != 0)
+           error (1, errno, "cannot open directory %s", repository);
+
+#ifdef ATTIC_DIR_SUPPORT               /* XXX - FIXME */
+       /* search the attic too */
+       if (which & W_ATTIC)
+       {
+           char dir[PATH_MAX];
+
+           (void) sprintf (dir, "%s/%s", repository, CVSATTIC);
+           (void) find_dirs (dir, dirlist, 0);
+       }
+#endif
+    }
+
+    /* sort the list into alphabetical order and return it */
+    sortlist (dirlist, fsortcmp);
+    return (dirlist);
+}
+
+/*
+ * Finds all the ,v files in the argument directory, and adds them to the
+ * files list.  Returns 0 for success and non-zero if the argument directory
+ * cannot be opened.
+ */
+static int
+find_rcs (dir, list)
+    char *dir;
+    List *list;
+{
+    Node *p;
+    CONST char *regex_err;
+    char line[50];
+    struct direct *dp;
+    DIR *dirp;
+
+    /* set up to read the dir */
+    if ((dirp = opendir (dir)) == NULL)
+       return (1);
+
+    /* set up a regular expression to find the ,v files */
+    (void) sprintf (line, ".*%s$", RCSEXT);
+    if ((regex_err = re_comp (line)) != NULL)
+       error (1, 0, "%s", regex_err);
+
+    /* read the dir, grabbing the ,v files */
+    while ((dp = readdir (dirp)) != NULL)
+    {
+       if (re_exec (dp->d_name))
+       {
+           char *comma;
+
+           comma = rindex (dp->d_name, ',');   /* strip the ,v */
+           *comma = '\0';
+           p = getnode ();
+           p->type = FILES;
+           p->key = xstrdup (dp->d_name);
+           if (addnode (list, p) != 0)
+               freenode (p);
+       }
+    }
+    (void) closedir (dirp);
+    return (0);
+}
+
+/*
+ * Finds all the subdirectories of the argument dir and adds them to the
+ * specified list.  Sub-directories without a CVS administration directory
+ * are optionally ignored  Returns 0 for success or 1 on error.
+ */
+static int
+find_dirs (dir, list, checkadm)
+    char *dir;
+    List *list;
+    int checkadm;
+{
+    Node *p;
+    CONST char *regex_err;
+    char tmp[PATH_MAX];
+    char admdir[PATH_MAX];
+    struct direct *dp;
+    DIR *dirp;
+
+    /* build a regex to blow off ,v files */
+    (void) sprintf (tmp, ".*%s$", RCSEXT);
+    if ((regex_err = re_comp (tmp)) != NULL)
+       error (1, 0, "%s", regex_err);
+
+    /* set up to read the dir */
+    if ((dirp = opendir (dir)) == NULL)
+       return (1);
+
+    /* read the dir, grabbing sub-dirs */
+    while ((dp = readdir (dirp)) != NULL)
+    {
+       if (strcmp (dp->d_name, ".") == 0 ||
+           strcmp (dp->d_name, "..") == 0 ||
+           strcmp (dp->d_name, CVSATTIC) == 0 ||
+           strcmp (dp->d_name, CVSLCK) == 0 ||
+           re_exec (dp->d_name))       /* don't bother stating ,v files */
+           continue;
+
+       (void) sprintf (tmp, "%s/%s", dir, dp->d_name);
+       if (isdir (tmp))
+       {
+           /* check for administration directories (if needed) */
+           if (checkadm)
+           {
+               /* blow off symbolic links to dirs in local dir */
+               if (islink (tmp))
+                   continue;
+
+               /* check for new style */
+               (void) sprintf (admdir, "%s/%s", tmp, CVSADM);
+               if (!isdir (admdir))
+               {
+                   /* and old style */
+                   (void) sprintf (admdir, "%s/%s", tmp, OCVSADM);
+                   if (!isdir (admdir))
+                       continue;
+               }
+           }
+
+           /* put it in the list */
+           p = getnode ();
+           p->type = DIRS;
+           p->key = xstrdup (dp->d_name);
+           if (addnode (list, p) != 0)
+               freenode (p);
+       }
+    }
+    (void) closedir (dirp);
+    return (0);
+}
diff --git a/usr/othersrc/public/cvs-1.3/src/lock.c b/usr/othersrc/public/cvs-1.3/src/lock.c
new file mode 100644 (file)
index 0000000..5da23b3
--- /dev/null
@@ -0,0 +1,522 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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 1.3 kit.
+ * 
+ * Set Lock
+ * 
+ * Lock file support for CVS.
+ */
+
+#include "cvs.h"
+
+#ifndef lint
+static char rcsid[] = "@(#)lock.c 1.42 92/04/10";
+#endif
+
+extern char *ctime ();
+
+#if __STDC__
+static int readers_exist (char *repository);
+static int set_lock (char *lockdir, int will_wait, char *repository);
+static void set_lockers_name (struct stat *statp);
+static int set_writelock_proc (Node * p);
+static int unlock_proc (Node * p);
+static int write_lock (char *repository);
+static void unlock (char *repository);
+static void lock_wait ();
+#else
+static int unlock_proc ();
+static void unlock ();
+static int set_writelock_proc ();
+static int write_lock ();
+static int readers_exist ();
+static int set_lock ();
+static void set_lockers_name ();
+static void lock_wait ();
+#endif                         /* __STDC__ */
+
+static char lockers_name[20];
+static char *repository;
+static char readlock[PATH_MAX], writelock[PATH_MAX];
+static int cleanup_lckdir;
+static List *locklist;
+
+#define L_OK           0               /* success */
+#define L_ERROR                1               /* error condition */
+#define L_LOCK_OWNED   2               /* lock already owned by us */
+#define L_LOCKED       3               /* lock owned by someone else */
+
+/*
+ * Clean up all outstanding locks
+ */
+void
+Lock_Cleanup ()
+{
+    /* clean up simple locks (if any) */
+    if (repository != NULL)
+    {
+       unlock (repository);
+       repository = (char *) NULL;
+    }
+
+    /* clean up multiple locks (if any) */
+    if (locklist != (List *) NULL)
+    {
+       (void) walklist (locklist, unlock_proc);
+       locklist = (List *) NULL;
+    }
+}
+
+/*
+ * walklist proc for removing a list of locks
+ */
+static int
+unlock_proc (p)
+    Node *p;
+{
+    unlock (p->key);
+    return (0);
+}
+
+/*
+ * Remove the lock files (without complaining if they are not there),
+ */
+static void
+unlock (repository)
+    char *repository;
+{
+    char tmp[PATH_MAX];
+    struct stat sb;
+
+    if (readlock[0] != '\0')
+    {
+       (void) sprintf (tmp, "%s/%s", repository, readlock);
+       (void) unlink (tmp);
+    }
+
+    if (writelock[0] != '\0')
+    {
+       (void) sprintf (tmp, "%s/%s", repository, writelock);
+       (void) unlink (tmp);
+    }
+
+    /*
+     * Only remove the lock directory if it is ours, note that this does
+     * lead to the limitation that one user ID should not be committing
+     * files into the same Repository directory at the same time. Oh well.
+     */
+    (void) sprintf (tmp, "%s/%s", repository, CVSLCK);
+    if (stat (tmp, &sb) != -1 && sb.st_uid == geteuid () &&
+       (writelock[0] != '\0' || (readlock[0] != '\0' && cleanup_lckdir)))
+    {
+       (void) rmdir (tmp);
+    }
+    cleanup_lckdir = 0;
+}
+
+/*
+ * Create a lock file for readers
+ */
+int
+Reader_Lock (xrepository)
+    char *xrepository;
+{
+    int err = 0;
+    FILE *fp;
+    char tmp[PATH_MAX];
+
+    if (noexec)
+       return (0);
+
+    /* we only do one directory at a time for read locks! */
+    if (repository != NULL)
+    {
+       error (0, 0, "Reader_Lock called while read locks set - Help!");
+       return (1);
+    }
+
+    if (readlock[0] == '\0')
+       (void) sprintf (readlock, "%s.%d", CVSRFL, getpid ());
+
+    /* remember what we're locking (for lock_cleanup) */
+    repository = xrepository;
+
+    /* make sure we clean up on error */
+    (void) SIG_register (SIGHUP, Lock_Cleanup);
+    (void) SIG_register (SIGINT, Lock_Cleanup);
+    (void) SIG_register (SIGQUIT, Lock_Cleanup);
+    (void) SIG_register (SIGPIPE, Lock_Cleanup);
+    (void) SIG_register (SIGTERM, Lock_Cleanup);
+
+    /* make sure we can write the repository */
+    (void) sprintf (tmp, "%s/%s.%d", xrepository, CVSTFL, getpid ());
+    if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
+    {
+       error (0, errno, "cannot create read lock in repository `%s'",
+              xrepository);
+       readlock[0] = '\0';
+       (void) unlink (tmp);
+       return (1);
+    }
+    (void) unlink (tmp);
+
+    /* get the lock dir for our own */
+    (void) sprintf (tmp, "%s/%s", xrepository, CVSLCK);
+    if (set_lock (tmp, 1, xrepository) != L_OK)
+    {
+       error (0, 0, "failed to obtain dir lock in repository `%s'",
+              xrepository);
+       readlock[0] = '\0';
+       return (1);
+    }
+
+    /* write a read-lock */
+    (void) sprintf (tmp, "%s/%s", xrepository, readlock);
+    if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
+    {
+       error (0, errno, "cannot create read lock in repository `%s'",
+              xrepository);
+       readlock[0] = '\0';
+       err = 1;
+    }
+
+    /* free the lock dir */
+    (void) sprintf (tmp, "%s/%s", xrepository, CVSLCK);
+    if (rmdir (tmp) < 0)
+       error (0, errno, "failed to remove lock dir `%s'", tmp);
+
+    return (err);
+}
+
+/*
+ * Lock a list of directories for writing
+ */
+static char *lock_error_repos;
+static int lock_error;
+int
+Writer_Lock (list)
+    List *list;
+{
+    if (noexec)
+       return (0);
+
+    /* We only know how to do one list at a time */
+    if (locklist != (List *) NULL)
+    {
+       error (0, 0, "Writer_Lock called while write locks set - Help!");
+       return (1);
+    }
+
+    for (;;)
+    {
+       /* try to lock everything on the list */
+       lock_error = L_OK;              /* init for set_writelock_proc */
+       lock_error_repos = (char *) NULL; /* init for set_writelock_proc */
+       locklist = list;                /* init for Lock_Cleanup */
+       (void) strcpy (lockers_name, "unknown");
+
+       (void) walklist (list, set_writelock_proc);
+
+       switch (lock_error)
+       {
+           case L_ERROR:               /* Real Error */
+               Lock_Cleanup ();        /* clean up any locks we set */
+               error (0, 0, "lock failed - giving up");
+               return (1);
+
+           case L_LOCKED:              /* Someone already had a lock */
+               Lock_Cleanup ();        /* clean up any locks we set */
+               lock_wait (lock_error_repos); /* sleep a while and try again */
+               continue;
+
+           case L_OK:                  /* we got the locks set */
+               return (0);
+
+           default:
+               error (0, 0, "unknown lock status %d in Writer_Lock",
+                      lock_error);
+               return (1);
+       }
+    }
+}
+
+/*
+ * walklist proc for setting write locks
+ */
+static int
+set_writelock_proc (p)
+    Node *p;
+{
+    /* if some lock was not OK, just skip this one */
+    if (lock_error != L_OK)
+       return (0);
+
+    /* apply the write lock */
+    lock_error_repos = p->key;
+    lock_error = write_lock (p->key);
+    return (0);
+}
+
+/*
+ * Create a lock file for writers returns L_OK if lock set ok, L_LOCKED if
+ * lock held by someone else or L_ERROR if an error occurred
+ */
+static int
+write_lock (repository)
+    char *repository;
+{
+    int status;
+    FILE *fp;
+    char tmp[PATH_MAX];
+
+    if (writelock[0] == '\0')
+       (void) sprintf (writelock, "%s.%d", CVSWFL, getpid ());
+
+    /* make sure we clean up on error */
+    (void) SIG_register (SIGHUP, Lock_Cleanup);
+    (void) SIG_register (SIGINT, Lock_Cleanup);
+    (void) SIG_register (SIGQUIT, Lock_Cleanup);
+    (void) SIG_register (SIGPIPE, Lock_Cleanup);
+    (void) SIG_register (SIGTERM, Lock_Cleanup);
+
+    /* make sure we can write the repository */
+    (void) sprintf (tmp, "%s/%s.%d", repository, CVSTFL, getpid ());
+    if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
+    {
+       error (0, errno, "cannot create write lock in repository `%s'",
+              repository);
+       (void) unlink (tmp);
+       return (L_ERROR);
+    }
+    (void) unlink (tmp);
+
+    /* make sure the lock dir is ours (not necessarily unique to us!) */
+    (void) sprintf (tmp, "%s/%s", repository, CVSLCK);
+    status = set_lock (tmp, 0, repository);
+    if (status == L_OK || status == L_LOCK_OWNED)
+    {
+       /* we now own a writer - make sure there are no readers */
+       if (readers_exist (repository))
+       {
+           /* clean up the lock dir if we created it */
+           if (status == L_OK)
+           {
+               if (rmdir (tmp) < 0)
+                   error (0, errno, "failed to remove lock dir `%s'", tmp);
+           }
+
+           /* indicate we failed due to read locks instead of error */
+           return (L_LOCKED);
+       }
+
+       /* write the write-lock file */
+       (void) sprintf (tmp, "%s/%s", repository, writelock);
+       if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
+       {
+           int xerrno = errno;
+
+           (void) unlink (tmp);
+           /* free the lock dir if we created it */
+           if (status == L_OK)
+           {
+               (void) sprintf (tmp, "%s/%s", repository, CVSLCK);
+               if (rmdir (tmp) < 0)
+                   error (0, errno, "failed to remove lock dir `%s'", tmp);
+           }
+
+           /* return the error */
+           error (0, xerrno, "cannot create write lock in repository `%s'",
+                  repository);
+           return (L_ERROR);
+       }
+       return (L_OK);
+    }
+    else
+       return (status);
+}
+
+/*
+ * readers_exist() returns 0 if there are no reader lock files remaining in
+ * the repository; else 1 is returned, to indicate that the caller should
+ * sleep a while and try again.
+ */
+static int
+readers_exist (repository)
+    char *repository;
+{
+    char line[MAXLINELEN];
+    DIR *dirp;
+    struct direct *dp;
+    struct stat sb;
+    CONST char *regex_err;
+    int ret = 0;
+
+#ifdef CVS_FUDGELOCKS
+again:
+#endif
+
+    if ((dirp = opendir (repository)) == NULL)
+       error (1, 0, "cannot open directory %s", repository);
+
+    (void) sprintf (line, "^%s.*", CVSRFL);
+    if ((regex_err = re_comp (line)) != NULL)
+       error (1, 0, "%s", regex_err);
+
+    while ((dp = readdir (dirp)) != NULL)
+    {
+       (void) sprintf (line, "%s/%s", repository, dp->d_name);
+       if (re_exec (dp->d_name))
+       {
+#ifdef CVS_FUDGELOCKS
+           time_t now;
+
+           (void) time (&now);
+
+           /*
+            * If the create time of the file is more than CVSLCKAGE seconds
+            * ago, try to clean-up the lock file, and if successful, re-open
+            * the directory and try again.
+            */
+           if (stat (line, &sb) != -1)
+           {
+               if (now >= (sb.st_ctime + CVSLCKAGE) && unlink (line) != -1)
+               {
+                   (void) closedir (dirp);
+                   goto again;
+               }
+               set_lockers_name (&sb);
+           }
+#else
+           if (stat (line, &sb) != -1)
+               set_lockers_name (&sb);
+#endif
+           ret = 1;
+           break;
+       }
+    }
+    (void) closedir (dirp);
+    return (ret);
+}
+
+/*
+ * Set the static variable lockers_name appropriately, based on the stat
+ * structure passed in.
+ */
+static void
+set_lockers_name (statp)
+    struct stat *statp;
+{
+    struct passwd *pw;
+
+    if ((pw = (struct passwd *) getpwuid (statp->st_uid)) !=
+       (struct passwd *) NULL)
+    {
+       (void) strcpy (lockers_name, pw->pw_name);
+    }
+    else
+       (void) sprintf (lockers_name, "uid%d", statp->st_uid);
+}
+
+/*
+ * Persistently tries to make the directory "lckdir",, which serves as a
+ * lock. If the create time on the directory is greater than CVSLCKAGE
+ * seconds old, just try to remove the directory.
+ */
+static int
+set_lock (lockdir, will_wait, repository)
+    char *lockdir;
+    int will_wait;
+    char *repository;
+{
+    struct stat sb;
+#ifdef CVS_FUDGELOCKS
+    time_t now;
+#endif
+
+    /*
+     * Note that it is up to the callers of set_lock() to arrange for signal
+     * handlers that do the appropriate things, like remove the lock
+     * directory before they exit.
+     */
+    cleanup_lckdir = 0;
+    for (;;)
+    {
+       SIG_beginCrSect ();
+       if (mkdir (lockdir, 0777) == 0)
+       {
+           cleanup_lckdir = 1;
+           SIG_endCrSect ();
+           return (L_OK);
+       }
+       SIG_endCrSect ();
+
+       if (errno != EEXIST)
+       {
+           error (0, errno,
+                  "failed to create lock directory in repository `%s'",
+                  repository);
+           return (L_ERROR);
+       }
+
+       /*
+        * stat the dir - if it is non-existent, re-try the loop since
+        * someone probably just removed it (thus releasing the lock)
+        */
+       if (stat (lockdir, &sb) < 0)
+       {
+           if (errno == ENOENT)
+               continue;
+
+           error (0, errno, "couldn't stat lock directory `%s'", lockdir);
+           return (L_ERROR);
+       }
+
+       /*
+        * if we already own the lock, go ahead and return 1 which means it
+        * existed but we owned it
+        */
+       if (sb.st_uid == geteuid () && !will_wait)
+           return (L_LOCK_OWNED);
+
+#ifdef CVS_FUDGELOCKS
+
+       /*
+        * If the create time of the directory is more than CVSLCKAGE seconds
+        * ago, try to clean-up the lock directory, and if successful, just
+        * quietly retry to make it.
+        */
+       (void) time (&now);
+       if (now >= (sb.st_ctime + CVSLCKAGE))
+       {
+           if (rmdir (lockdir) >= 0)
+               continue;
+       }
+#endif
+
+       /* set the lockers name */
+       set_lockers_name (&sb);
+
+       /* if he wasn't willing to wait, return an error */
+       if (!will_wait)
+           return (L_LOCKED);
+       lock_wait (repository);
+    }
+}
+
+/*
+ * Print out a message that the lock is still held, then sleep a while.
+ */
+static void
+lock_wait (repos)
+    char *repos;
+{
+    time_t now;
+
+    (void) time (&now);
+    error (0, 0, "[%8.8s] waiting for %s's lock in %s", ctime (&now) + 11,
+          lockers_name, repos);
+    (void) sleep (CVSLCKSLEEP);
+}
diff --git a/usr/othersrc/public/cvs-1.3/src/parseinfo.c b/usr/othersrc/public/cvs-1.3/src/parseinfo.c
new file mode 100644 (file)
index 0000000..65343f5
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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 1.3 kit.
+ */
+
+#include "cvs.h"
+
+#ifndef lint
+static char rcsid[] = "@(#)parseinfo.c 1.16 92/04/10";
+#endif
+
+/*
+ * Parse the INFOFILE file for the specified REPOSITORY.  Invoke CALLPROC for
+ * each line in the file that matches the REPOSITORY.  
+ * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure.
+ */
+int
+Parse_Info (infofile, repository, callproc, all)
+    char *infofile;
+    char *repository;
+    int (*callproc) ();
+    int all;
+{
+    int err = 0;
+    FILE *fp_info;
+    char infopath[PATH_MAX];
+    char line[MAXLINELEN];
+    char *default_value = NULL;
+    int callback_done, line_number;
+    char *cp, *exp, *value, *srepos;
+    CONST char *regex_err;
+
+    if (CVSroot == NULL)
+    {
+       /* XXX - should be error maybe? */
+       error (0, 0, "CVSROOT variable not set");
+       return (1);
+    }
+
+    /* find the info file and open it */
+    (void) sprintf (infopath, "%s/%s/%s", CVSroot,
+                   CVSROOTADM, infofile);
+    if ((fp_info = fopen (infopath, "r")) == NULL)
+       return (0);                     /* no file -> nothing special done */
+
+    /* strip off the CVSROOT if repository was absolute */
+    srepos = Short_Repository (repository);
+
+    /* search the info file for lines that match */
+    callback_done = line_number = 0;
+    while (fgets (line, sizeof (line), fp_info) != NULL)
+    {
+       line_number++;
+
+       /* skip lines starting with # */
+       if (line[0] == '#')
+           continue;
+
+       /* skip whitespace at beginning of line */
+       for (cp = line; *cp && isspace (*cp); cp++)
+           ;
+
+       /* if *cp is null, the whole line was blank */
+       if (*cp == '\0')
+           continue;
+
+       /* the regular expression is everything up to the first space */
+       for (exp = cp; *cp && !isspace (*cp); cp++)
+           ;
+       if (*cp != '\0')
+           *cp++ = '\0';
+
+       /* skip whitespace up to the start of the matching value */
+       while (*cp && isspace (*cp))
+           cp++;
+
+       /* no value to match with the regular expression is an error */
+       if (*cp == '\0')
+       {
+           error (0, 0, "syntax error at line %d file %s; ignored",
+                  line_number, infofile);
+           continue;
+       }
+       value = cp;
+
+       /* strip the newline off the end of the value */
+       if ((cp = rindex (value, '\n')) != NULL)
+           *cp = '\0';
+
+       /*
+        * At this point, exp points to the regular expression, and value
+        * points to the value to call the callback routine with.  Evaluate
+        * the regular expression against srepos and callback with the value
+        * if it matches.
+        */
+
+       /* save the default value so we have it later if we need it */
+       if (strcmp (exp, "DEFAULT") == 0)
+       {
+           default_value = xstrdup (value);
+           continue;
+       }
+
+       /*
+        * For a regular expression of "ALL", do the callback always We may
+        * execute lots of ALL callbacks in addition to one regular matching
+        * callback or default
+        */
+       if (strcmp (exp, "ALL") == 0)
+       {
+           if (all)
+               err += callproc (repository, value);
+           else
+               error(0, 0, "Keyword `ALL' is ignored at line %d in %s file",
+                     line_number, infofile);
+           continue;
+       }
+
+       /* see if the repository matched this regular expression */
+       if ((regex_err = re_comp (exp)) != NULL)
+       {
+           error (0, 0, "bad regular expression at line %d file %s: %s",
+                  line_number, infofile, regex_err);
+           continue;
+       }
+       if (re_exec (srepos) == 0)
+           continue;                   /* no match */
+
+       /* it did, so do the callback and note that we did one */
+       err += callproc (repository, value);
+       callback_done = 1;
+    }
+    (void) fclose (fp_info);
+
+    /* if we fell through and didn't callback at all, do the default */
+    if (callback_done == 0 && default_value != NULL)
+       err += callproc (repository, default_value);
+
+    /* free up space if necessary */
+    if (default_value != NULL)
+       free (default_value);
+
+    return (err);
+}