cvs-cvs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Cvs-cvs] Changes to ccvs/src/subr.c [signed-commits]


From: Derek Robert Price
Subject: [Cvs-cvs] Changes to ccvs/src/subr.c [signed-commits]
Date: Tue, 11 Oct 2005 22:46:56 -0400

Index: ccvs/src/subr.c
diff -u /dev/null ccvs/src/subr.c:1.148.2.1
--- /dev/null   Wed Oct 12 02:46:56 2005
+++ ccvs/src/subr.c     Wed Oct 12 02:46:37 2005
@@ -0,0 +1,2011 @@
+/*
+ * 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.
+ * 
+ * Various useful functions for the CVS support code.
+ */
+
+#include "cvs.h"
+
+#include "canonicalize.h"
+#include "canon-host.h"
+#include "getline.h"
+#include "vasprintf.h"
+#include "vasnprintf.h"
+
+/* Get wint_t.  */
+#ifdef HAVE_WINT_T
+# include <wchar.h>
+#endif
+
+
+
+extern char *getlogin (void);
+
+
+
+/* *STRPTR is a pointer returned from malloc (or NULL), pointing to *N
+   characters of space.  Reallocate it so that points to at least
+   NEWSIZE bytes of space.  Gives a fatal error if out of memory;
+   if it returns it was successful.  */
+void
+expand_string (char **strptr, size_t *n, size_t newsize)
+{
+    while (*n < newsize)
+       *strptr = x2realloc (*strptr, n);
+}
+
+
+
+/* char *
+ * Xreadlink (const char *link, size_t size)
+ *
+ * INPUTS
+ *  link       The original path.
+ *  size       A guess as to the size needed for the path. It need
+ *              not be right.
+ * RETURNS
+ *  The resolution of the final symbolic link in the path.
+ *
+ * ERRORS
+ *  This function exits with a fatal error if it fails to read the
+ *  link for any reason.
+ */
+char *
+Xreadlink (const char *link, size_t size)
+{
+    char *file = xreadlink (link, size);
+
+    if (file == NULL)
+       error (1, errno, "cannot readlink %s", link);
+
+    return file;
+}
+
+
+
+/* *STR is a pointer to a malloc'd string or NULL.  *LENP is its allocated
+ * length.  If *STR is NULL then *LENP must be 0 and visa-versa.
+ * Add SRC to the end of *STR, reallocating *STR if necessary.  */
+void
+xrealloc_and_strcat (char **str, size_t *lenp, const char *src)
+{
+    bool newstr = !*lenp;
+    expand_string (str, lenp, (newstr ? 0 : strlen (*str)) + strlen (src) + 1);
+    if (newstr)
+       strcpy (*str, src);
+    else
+       strcat (*str, src);
+}
+
+
+
+/* Remove trailing newlines from STRING, destructively.
+ *
+ * RETURNS
+ *
+ *   True if any newlines were removed, false otherwise.
+ */
+int
+strip_trailing_newlines (char *str)
+{
+    size_t index, origlen;
+    index = origlen = strlen (str);
+
+    while (index > 0 && str[index-1] == '\n')
+       str[--index] = '\0';
+
+    return index != origlen;
+}
+
+
+
+/* Return the number of levels that PATH ascends above where it starts.
+ * For example:
+ *
+ *   "../../foo" -> 2
+ *   "foo/../../bar" -> 1
+ */
+int
+pathname_levels (const char *p)
+{
+    int level;
+    int max_level;
+
+    if (p == NULL) return 0;
+
+    max_level = 0;
+    level = 0;
+    do
+    {
+       /* Now look for pathname level-ups.  */
+       if (p[0] == '.' && p[1] == '.' && (p[2] == '\0' || ISSLASH (p[2])))
+       {
+           --level;
+           if (-level > max_level)
+               max_level = -level;
+       }
+       else if (p[0] == '\0' || ISSLASH (p[0]) ||
+                (p[0] == '.' && (p[1] == '\0' || ISSLASH (p[1]))))
+           ;
+       else
+           ++level;
+
+       /* q = strchr (p, '/'); but sub ISSLASH() for '/': */
+       while (*p != '\0' && !ISSLASH (*p)) p++;
+       if (*p != '\0') p++;
+    } while (*p != '\0');
+    return max_level;
+}
+
+
+
+/* Free a vector, where (*ARGV)[0], (*ARGV)[1], ... (*ARGV)[*PARGC - 1]
+   are malloc'd and so is *ARGV itself.  Such a vector is allocated by
+   line2argv or expand_wild, for example.  */
+void
+free_names (int *pargc, char **argv)
+{
+    register int i;
+
+    for (i = 0; i < *pargc; i++)
+    {                                  /* only do through *pargc */
+       free (argv[i]);
+    }
+    free (argv);
+    *pargc = 0;                                /* and set it to zero when done 
*/
+}
+
+
+
+/* Convert LINE into arguments separated by SEPCHARS.  Set *ARGC
+   to the number of arguments found, and (*ARGV)[0] to the first argument,
+   (*ARGV)[1] to the second, etc.  *ARGV is malloc'd and so are each of
+   (*ARGV)[0], (*ARGV)[1], ...  Use free_names() to return the memory
+   allocated here back to the free pool.  */
+void
+line2argv (int *pargc, char ***argv, char *line, char *sepchars)
+{
+    char *cp;
+    /* Could make a case for size_t or some other unsigned type, but
+       we'll stick with int to avoid signed/unsigned warnings when
+       comparing with *pargc.  */
+    int argv_allocated;
+
+    /* Small for testing.  */
+    argv_allocated = 1;
+    *argv = xnmalloc (argv_allocated, sizeof (**argv));
+
+    *pargc = 0;
+    for (cp = strtok (line, sepchars); cp; cp = strtok (NULL, sepchars))
+    {
+       if (*pargc == argv_allocated)
+       {
+           argv_allocated *= 2;
+           *argv = xnrealloc (*argv, argv_allocated, sizeof (**argv));
+       }
+       (*argv)[*pargc] = xstrdup (cp);
+       (*pargc)++;
+    }
+}
+
+
+
+/*
+ * Returns the number of dots ('.') found in an RCS revision number
+ */
+int
+numdots (const char *s)
+{
+    int dots = 0;
+
+    for (; *s; s++)
+    {
+       if (*s == '.')
+           dots++;
+    }
+    return (dots);
+}
+
+
+
+/* Compare revision numbers REV1 and REV2 by consecutive fields.
+   Return negative, zero, or positive in the manner of strcmp.  The
+   two revision numbers must have the same number of fields, or else
+   compare_revnums will return an inaccurate result. */
+int
+compare_revnums (const char *rev1, const char *rev2)
+{
+    const char *sp, *tp;
+    char *snext, *tnext;
+    int result = 0;
+
+    sp = rev1;
+    tp = rev2;
+    while (result == 0)
+    {
+       result = strtoul (sp, &snext, 10) - strtoul (tp, &tnext, 10);
+       if (*snext == '\0' || *tnext == '\0')
+           break;
+       sp = snext + 1;
+       tp = tnext + 1;
+    }
+
+    return result;
+}
+
+
+
+/* Increment a revision number.  Working on the string is a bit awkward,
+   but it avoid problems with integer overflow should the revision numbers
+   get really big.  */
+char *
+increment_revnum (const char *rev)
+{
+    char *newrev, *p;
+    size_t len = strlen (rev);
+
+    newrev = xmalloc (len + 2);
+    memcpy (newrev, rev, len + 1);
+    for (p = newrev + len; p != newrev; )
+    {
+       --p;
+       if (!isdigit(*p))
+       {
+           ++p;
+           break;
+       }
+       if (*p != '9')
+       {
+           ++*p;
+           return newrev;
+       }
+       *p = '0';
+    }
+    /* The number was all 9s, so change the first character to 1 and add
+       a 0 to the end.  */
+    *p = '1';
+    p = newrev + len;
+    *p++ = '0';
+    *p = '\0';
+    return newrev;
+}
+
+
+
+/* Return the username by which the caller should be identified in
+   CVS, in contexts such as the author field of RCS files, various
+   logs, etc.  */
+char *
+getcaller (void)
+{
+#ifndef SYSTEM_GETCALLER
+    static char *cache;
+    struct passwd *pw;
+    uid_t uid;
+#endif
+
+    /* If there is a CVS username, return it.  */
+#ifdef AUTH_SERVER_SUPPORT
+    if (CVS_Username != NULL)
+       return CVS_Username;
+#endif
+
+#ifdef SYSTEM_GETCALLER
+    return SYSTEM_GETCALLER ();
+#else
+    /* Get the caller's login from his uid.  If the real uid is "root"
+       try LOGNAME USER or getlogin(). If getlogin() and getpwuid()
+       both fail, return the uid as a string.  */
+
+    if (cache != NULL)
+       return cache;
+
+    uid = getuid ();
+    if (uid == (uid_t) 0)
+    {
+       char *name;
+
+       /* super-user; try getlogin() to distinguish */
+       if (((name = getlogin ()) || (name = getenv("LOGNAME")) ||
+            (name = getenv("USER"))) && *name)
+       {
+           cache = xstrdup (name);
+           return cache;
+       }
+    }
+    if ((pw = (struct passwd *) getpwuid (uid)) == NULL)
+    {
+       cache = Xasprintf ("uid%lu", (unsigned long) uid);
+       return cache;
+    }
+    cache = xstrdup (pw->pw_name);
+    return cache;
+#endif
+}
+
+
+
+#ifdef lint
+# ifndef __GNUC__
+/* ARGSUSED */
+bool
+get_date (struct timespec *result, char const *p, struct timespec const *now)
+{
+    result->tv_sec = 0;
+    result->tv_nsec = 0;
+
+    return false;
+}
+# endif
+#endif
+
+
+
+/* Given some revision, REV, return the first prior revision that exists in the
+ * RCS file, RCS.
+ *
+ * ASSUMPTIONS
+ *   REV exists.
+ *
+ * INPUTS
+ *   RCS       The RCS node pointer.
+ *   REV       An existing revision in the RCS file referred to by RCS.
+ *
+ * RETURNS
+ *   The first prior revision that exists in the RCS file, or NULL if no prior
+ *   revision exists.  The caller is responsible for disposing of this string.
+ *
+ * NOTES
+ *   This function currently neglects the case where we are on the trunk with
+ *   rev = X.1, where X != 1.  If rev = X.Y, where X != 1 and Y > 1, then this
+ *   function should work fine, as revision X.1 must exist, due to RCS rules.
+ */
+char *
+previous_rev (RCSNode *rcs, const char *rev)
+{
+    char *p;
+    char *tmp = xstrdup (rev);
+    long r1;
+    char *retval;
+
+    /* Our retval can have no more digits and dots than our input revision.  */
+    retval = xmalloc (strlen (rev) + 1);
+    p = strrchr (tmp, '.');
+    *p = '\0';
+    r1 = strtol (p+1, NULL, 10);
+    do {
+       if (--r1 == 0)
+       {
+               /* If r1 == 0, then we must be on a branch and our parent must
+                * exist, or we must be on the trunk with a REV like X.1.
+                * We are neglecting the X.1 with X != 1 case by assuming that
+                * there is no previous revision when we discover we were on
+                * the trunk.
+                */
+               p = strrchr (tmp, '.');
+               if (p == NULL)
+                   /* We are on the trunk.  */
+                   retval = NULL;
+               else
+               {
+                   *p = '\0';
+                   sprintf (retval, "%s", tmp);
+               }
+               break;
+       }
+       sprintf (retval, "%s.%ld", tmp, r1);
+    } while (!RCS_exist_rev (rcs, retval));
+
+    free (tmp);
+    return retval;
+}
+
+
+
+/* Given two revisions, find their greatest common ancestor.  If the
+   two input revisions exist, then rcs guarantees that the gca will
+   exist.  */
+char *
+gca (const char *rev1, const char *rev2)
+{
+    int dots;
+    char *gca, *g;
+    const char *p1, *p2;
+    int r1, r2;
+    char *retval;
+
+    if (rev1 == NULL || rev2 == NULL)
+    {
+       error (0, 0, "sanity failure in gca");
+       abort();
+    }
+
+    /* The greatest common ancestor will have no more dots, and numbers
+       of digits for each component no greater than the arguments.  Therefore
+       this string will be big enough.  */
+    g = gca = xmalloc (strlen (rev1) + strlen (rev2) + 100);
+
+    /* walk the strings, reading the common parts. */
+    p1 = rev1;
+    p2 = rev2;
+    do
+    {
+       r1 = strtol (p1, (char **) &p1, 10);
+       r2 = strtol (p2, (char **) &p2, 10);
+       
+       /* use the lowest. */
+       (void) sprintf (g, "%d.", r1 < r2 ? r1 : r2);
+       g += strlen (g);
+       if (*p1 == '.') ++p1;
+       else break;
+       if (*p2 == '.') ++p2;
+       else break;
+    } while (r1 == r2);
+
+    /* erase that last dot. */
+    *--g = '\0';
+
+    /* numbers differ, or we ran out of strings.  we're done with the
+       common parts.  */
+
+    dots = numdots (gca);
+    if (dots == 0)
+    {
+       /* revisions differ in trunk major number.  */
+
+       if (r2 < r1) p1 = p2;
+       if (*p1 == '\0')
+       {
+           /* we only got one number.  this is strange.  */
+           error (0, 0, "bad revisions %s or %s", rev1, rev2);
+           abort();
+       }
+       else
+       {
+           /* we have a minor number.  use it.  */
+           *g++ = '.';
+           while (*p1 != '.' && *p1 != '\0')
+               *g++ = *p1++;
+           *g = '\0';
+       }
+    }
+    else if ((dots & 1) == 0)
+    {
+       /* if we have an even number of dots, then we have a branch.
+          remove the last number in order to make it a revision.  */
+       
+       g = strrchr (gca, '.');
+       *g = '\0';
+    }
+
+    retval = xstrdup (gca);
+    free (gca);
+    return retval;
+}
+
+
+
+/* Give fatal error if REV is numeric and ARGC,ARGV imply we are
+   planning to operate on more than one file.  The current directory
+   should be the working directory.  Note that callers assume that we
+   will only be checking the first character of REV; it need not have
+   '\0' at the end of the tag name and other niceties.  Right now this
+   is only called from admin.c, but if people like the concept it probably
+   should also be called from diff -r, update -r, get -r, and log -r.  */
+void
+check_numeric (const char *rev, int argc, char **argv)
+{
+    if (rev == NULL || !isdigit ((unsigned char) *rev))
+       return;
+
+    /* Note that the check for whether we are processing more than one
+       file is (basically) syntactic; that is, we don't behave differently
+       depending on whether a directory happens to contain only a single
+       file or whether it contains more than one.  I strongly suspect this
+       is the least confusing behavior.  */
+    if (argc != 1
+       || (!wrap_name_has (argv[0], WRAP_TOCVS) && isdir (argv[0])))
+    {
+       error (0, 0, "while processing more than one file:");
+       error (1, 0, "attempt to specify a numeric revision");
+    }
+}
+
+
+
+/*
+ *  Sanity checks and any required fix-up on message passed to RCS via '-m'.
+ *  RCS 5.7 requires that a non-total-whitespace, non-null message be provided
+ *  with '-m'.  Returns a newly allocated, non-empty buffer with whitespace
+ *  stripped from end of lines and end of buffer.
+ *
+ *  TODO: We no longer use RCS to manage repository files, so maybe this
+ *  nonsense about non-empty log fields can be dropped.
+ */
+char *
+make_message_rcsvalid (const char *message)
+{
+    char *dst, *dp;
+    const char *mp;
+
+    if (message == NULL) message = "";
+
+    /* Strip whitespace from end of lines and end of string. */
+    dp = dst = (char *) xmalloc (strlen (message) + 1);
+    for (mp = message; *mp != '\0'; ++mp)
+    {
+       if (*mp == '\n')
+       {
+           /* At end-of-line; backtrack to last non-space. */
+           while (dp > dst && (dp[-1] == ' ' || dp[-1] == '\t'))
+               --dp;
+       }
+       *dp++ = *mp;
+    }
+
+    /* Backtrack to last non-space at end of string, and truncate. */
+    while (dp > dst && isspace ((unsigned char) dp[-1]))
+       --dp;
+    *dp = '\0';
+
+    /* After all that, if there was no non-space in the string,
+       substitute a non-empty message. */
+    if (*dst == '\0')
+    {
+       free (dst);
+       dst = xstrdup ("*** empty log message ***");
+    }
+
+    return dst;
+}
+
+
+
+/* Does the file FINFO contain conflict markers?  The whole concept
+   of looking at the contents of the file to figure out whether there are
+   unresolved conflicts is kind of bogus (people do want to manage files
+   which contain those patterns not as conflict markers), but for now it
+   is what we do.  */
+int
+file_has_markers (const struct file_info *finfo)
+{
+    FILE *fp;
+    char *line = NULL;
+    size_t line_allocated = 0;
+    int result;
+
+    result = 0;
+    fp = CVS_FOPEN (finfo->file, "r");
+    if (fp == NULL)
+       error (1, errno, "cannot open %s", finfo->fullname);
+    while (getline (&line, &line_allocated, fp) > 0)
+    {
+       if (strncmp (line, RCS_MERGE_PAT_1, sizeof RCS_MERGE_PAT_1 - 1) == 0 ||
+           strncmp (line, RCS_MERGE_PAT_2, sizeof RCS_MERGE_PAT_2 - 1) == 0 ||
+           strncmp (line, RCS_MERGE_PAT_3, sizeof RCS_MERGE_PAT_3 - 1) == 0)
+       {
+           result = 1;
+           goto out;
+       }
+    }
+    if (ferror (fp))
+       error (0, errno, "cannot read %s", finfo->fullname);
+out:
+    if (fclose (fp) < 0)
+       error (0, errno, "cannot close %s", finfo->fullname);
+    if (line != NULL)
+       free (line);
+    return result;
+}
+
+
+
+/* Read the entire contents of the file NAME into *BUF.
+   If NAME is NULL, read from stdin.  *BUF
+   is a pointer returned from malloc (or NULL), pointing to *BUFSIZE
+   bytes of space.  The actual size is returned in *LEN.  On error,
+   give a fatal error.  The name of the file to use in error messages
+   (typically will include a directory if we have changed directory)
+   is FULLNAME.  MODE is "r" for text or "rb" for binary.  */
+void
+get_file (const char *name, const char *fullname, const char *mode, char **buf,
+         size_t *bufsize, size_t *len)
+{
+    struct stat s;
+    size_t nread;
+    char *tobuf;
+    FILE *e;
+    size_t filesize;
+
+    if (name == NULL)
+    {
+       e = stdin;
+       filesize = 100; /* force allocation of minimum buffer */
+    }
+    else
+    {
+       /* Although it would be cleaner in some ways to just read
+          until end of file, reallocating the buffer, this function
+          does get called on files in the working directory which can
+          be of arbitrary size, so I think we better do all that
+          extra allocation.  */
+
+       if (stat (name, &s) < 0)
+           error (1, errno, "can't stat %s", fullname);
+
+       /* Convert from signed to unsigned.  */
+       filesize = s.st_size;
+
+       e = xfopen (name, mode);
+    }
+
+    if (*buf == NULL || *bufsize <= filesize)
+    {
+       *bufsize = filesize + 1;
+       *buf = xrealloc (*buf, *bufsize);
+    }
+
+    tobuf = *buf;
+    nread = 0;
+    while (1)
+    {
+       size_t got;
+
+       got = fread (tobuf, 1, *bufsize - (tobuf - *buf), e);
+       if (ferror (e))
+           error (1, errno, "can't read %s", fullname);
+       nread += got;
+       tobuf += got;
+
+       if (feof (e))
+           break;
+
+       /* Allocate more space if needed.  */
+       if (tobuf == *buf + *bufsize)
+       {
+           int c;
+           long off;
+
+           c = getc (e);
+           if (c == EOF)
+               break;
+           off = tobuf - *buf;
+           expand_string (buf, bufsize, *bufsize + 100);
+           tobuf = *buf + off;
+           *tobuf++ = c;
+           ++nread;
+       }
+    }
+
+    if (e != stdin && fclose (e) < 0)
+       error (0, errno, "cannot close %s", fullname);
+
+    *len = nread;
+
+    /* Force *BUF to be large enough to hold a null terminator. */
+    if (nread == *bufsize)
+       expand_string (buf, bufsize, *bufsize + 1);
+    (*buf)[nread] = '\0';
+}
+
+
+
+/* Follow a chain of symbolic links to its destination.  FILENAME
+   should be a handle to a malloc'd block of memory which contains the
+   beginning of the chain.  This routine will replace the contents of
+   FILENAME with the destination (a real file).  */
+void
+resolve_symlink (char **filename)
+{
+    ssize_t rsize;
+
+    if (filename == NULL || *filename == NULL)
+       return;
+
+    while ((rsize = islink (*filename)) > 0)
+    {
+#ifdef HAVE_READLINK
+       /* The clean thing to do is probably to have each filesubr.c
+          implement this (with an error if not supported by the
+          platform, in which case islink would presumably return 0).
+          But that would require editing each filesubr.c and so the
+          expedient hack seems to be looking at HAVE_READLINK.  */
+       char *newname = Xreadlink (*filename, rsize);
+       
+       if (ISABSOLUTE (newname))
+       {
+           free (*filename);
+           *filename = newname;
+       }
+       else
+       {
+           const char *oldname = last_component (*filename);
+           int dirlen = oldname - *filename;
+           char *fullnewname = xmalloc (dirlen + strlen (newname) + 1);
+           strncpy (fullnewname, *filename, dirlen);
+           strcpy (fullnewname + dirlen, newname);
+           free (newname);
+           free (*filename);
+           *filename = fullnewname;
+       }
+#else
+       error (1, 0, "internal error: islink doesn't like readlink");
+#endif
+    }
+}
+
+
+
+/*
+ * Rename a file to an appropriate backup name based on BAKPREFIX.
+ * If suffix non-null, then ".<suffix>" is appended to the new name.
+ *
+ * Returns the new name, which caller may free() if desired.
+ */
+char *
+backup_file (const char *filename, const char *suffix)
+{
+    char *backup_name = Xasprintf ("%s%s%s%s", BAKPREFIX, filename,
+                                  suffix ? "." : "", suffix ? suffix : "");
+
+    if (isfile (filename))
+        copy_file (filename, backup_name);
+
+    return backup_name;
+}
+
+
+
+/*
+ * Copy a string into a buffer escaping any shell metacharacters.  The
+ * buffer should be at least twice as long as the string.
+ *
+ * Returns a pointer to the terminating NUL byte in buffer.
+ */
+char *
+shell_escape(char *buf, const char *str)
+{
+    static const char meta[] = "$`\\\"";
+    const char *p;
+
+    for (;;)
+    {
+       p = strpbrk(str, meta);
+       if (!p) p = str + strlen(str);
+       if (p > str)
+       {
+           memcpy(buf, str, p - str);
+           buf += p - str;
+       }
+       if (!*p) break;
+       *buf++ = '\\';
+       *buf++ = *p++;
+       str = p;
+    }
+    *buf = '\0';
+    return buf;
+}
+
+
+
+/*
+ * We can only travel forwards in time, not backwards.  :)
+ */
+void
+sleep_past (time_t desttime)
+{
+    time_t t;
+    long s;
+    long us;
+
+    while (time (&t) <= desttime)
+    {
+#ifdef HAVE_GETTIMEOFDAY
+       struct timeval tv;
+       gettimeofday (&tv, NULL);
+       if (tv.tv_sec > desttime)
+           break;
+       s = desttime - tv.tv_sec;
+       if (tv.tv_usec > 0)
+           us = 1000000 - tv.tv_usec;
+       else
+       {
+           s++;
+           us = 0;
+       }
+#else
+       /* default to 20 ms increments */
+       s = desttime - t;
+       us = 20000;
+#endif
+
+       {
+           struct timespec ts;
+           ts.tv_sec = s;
+           ts.tv_nsec = us * 1000;
+           (void)nanosleep (&ts, NULL);
+       }
+    }
+}
+
+
+
+/* used to store callback data in a list indexed by the user format string
+ */
+typedef int (*CONVPROC_t) (Node *, void *);
+struct cmdline_bindings
+{
+    char conversion;
+    void *data;
+    CONVPROC_t convproc;
+    void *closure;
+};
+/* since we store the above in a list, we need to dispose of the data field.
+ * we don't have to worry about convproc or closure since pointers are stuck
+ * in there directly and format_cmdline's caller is responsible for disposing
+ * of those if necessary.
+ */
+static void
+cmdline_bindings_hash_node_delete (Node *p)
+{
+    struct cmdline_bindings *b = p->data;
+
+    if (b->conversion != ',')
+    {
+       free (b->data);
+    }
+    free (b);
+}
+
+
+
+/*
+ * assume s is a literal argument and put it between quotes,
+ * escaping as appropriate for a shell command line
+ *
+ * the caller is responsible for disposing of the new string
+ */
+char *
+cmdlinequote (char quotes, char *s)
+{
+    char *quoted = cmdlineescape (quotes, s);
+    char *buf = Xasprintf ("%c%s%c", quotes, quoted, quotes);
+
+    free (quoted);
+    return buf;
+}
+
+
+
+/* read quotes as the type of quotes we are between (if any) and then make our
+ * argument so it could make it past a cmdline parser (using sh as a model)
+ * inside the quotes (if any).
+ *
+ * if you were planning on expanding any paths, it should be done before
+ * calling this function, as it escapes shell metacharacters.
+ *
+ * the caller is responsible for disposing of the new string
+ *
+ * FIXME: See about removing/combining this functionality with shell_escape()
+ * in subr.c.
+ */
+char *
+cmdlineescape (char quotes, char *s)
+{
+    char *buf = NULL;
+    size_t length = 0;
+    char *d = NULL;
+    size_t doff;
+    char *lastspace;
+
+    lastspace = s - 1;
+    do
+    {
+       /* FIXME: Single quotes only require other single quotes to be escaped
+        * for Bourne Shell.
+        */
+       if ( isspace( *s ) ) lastspace = s;
+       if( quotes
+           ? ( *s == quotes
+               || ( quotes == '"'
+                    && ( *s == '$' || *s == '`' || *s == '\\' ) ) )
+           : ( strchr( "\\$`'\"*?", *s )
+               || isspace( *s )
+               || ( lastspace == ( s - 1 )
+                    && *s == '~' ) ) )
+       {
+           doff = d - buf;
+           expand_string (&buf, &length, doff + 1);
+           d = buf + doff;
+           *d++ = '\\';
+       }       
+       doff = d - buf;
+       expand_string (&buf, &length, doff + 1);
+       d = buf + doff;
+    } while ((*d++ = *s++) != '\0');
+    return (buf);
+}
+
+
+
+/* expand format strings in a command line.  modeled roughly after printf
+ *
+ * this function's arg list must be NULL terminated
+ *
+ * assume a space delimited list of args is the desired final output,
+ * but args can be quoted (" or ').
+ *
+ * the best usage examples are in tag.c & logmsg.c, but here goes:
+ *
+ * INPUTS
+ *    int oldway       to support old format strings
+ *    char *srepos     you guessed it
+ *    char *format     the format string to parse
+ *    ...              NULL terminated data list in the following format:
+ *                     char *userformat, char *printfformat, <type> data
+ *                         where
+ *                             char *userformat        a list of possible
+ *                                                     format characters the
+ *                                                     end user might pass us
+ *                                                     in the format string
+ *                                                     (e.g. those found in
+ *                                                     taginfo or loginfo)
+ *                                                     multiple characters in
+ *                                                     this strings will be
+ *                                                     aliases for each other
+ *                             char *printfformat      the same list of args
+ *                                                     printf uses to
+ *                                                     determine what kind of
+ *                                                     data the next arg will
+ *                                                     be
+ *                             <type> data             a piece of data to be
+ *                                                     formatted into the user
+ *                                                     string, <type>
+ *                                                     determined by the
+ *                                                     printfformat string.
+ *             or      
+ *                     char *userformat, char *printfformat, List *data,
+ *                             int (*convproc) (Node *, void *), void *closure
+ *                         where
+ *                             char *userformat        same as above, except
+ *                                                     multiple characters in
+ *                                                     this string represent
+ *                                                     different node
+ *                                                     attributes which can be
+ *                                                     retrieved from data by
+ *                                                     convproc
+ *                             char *printfformat      = ","
+ *                             List *data              the list to be walked
+ *                                                     with walklist &
+ *                                                     convproc to retrieve
+ *                                                     data for each of the
+ *                                                     possible format
+ *                                                     characters in
+ *                                                     userformat
+ *                             int (*convproc)()       see data
+ *                             void *closure           arg to be passed into
+ *                                                     walklist as closure
+ *                                                     data for convproc
+ *
+ * EXAMPLE
+ *    (ignoring oldway variable and srepos since those are only around while we
+ *    SUPPORT_OLD_INFO_FMT_STRINGS)
+ *    format_cmdline ("/cvsroot/CVSROOT/mytaginfoproc %t %o %{sVv}",
+ *                    "t", "s", "newtag",
+ *                    "o", "s", "mov",
+ *                    "xG", "ld", longintwhichwontbeusedthispass,
+ *                    "sVv", ",", tlist, pretag_list_to_args_proc,
+ *                    (void *) mydata,
+ *                    (char *) NULL);
+ *
+ *    would generate the following command line, assuming two files in tlist,
+ *    file1 & file2, each with old versions 1.1 and new version 1.1.2.3:
+ *
+ *       /cvsroot/CVSROOT/mytaginfoproc "newtag" "mov" "file1" "1.1" "1.1.2.3" 
"file2" "1.1" "1.1.2.3"
+ *
+ * RETURNS
+ *    pointer to newly allocated string.  the caller is responsible for
+ *    disposing of this string.
+ */
+char *
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+format_cmdline (bool oldway, const char *srepos, const char *format, ...)
+#else /* SUPPORT_OLD_INFO_FMT_STRINGS */
+format_cmdline (const char *format, ...)
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+{
+    va_list args;      /* our input function args */
+    char *buf;         /* where we store our output string */
+    size_t length;     /* the allocated length of our output string in bytes.
+                        * used as a temporary storage for the length of the
+                        * next function argument during function
+                        * initialization
+                        */
+    char *pfmt;                /* initially the list of fmt keys passed in,
+                        * but used as a temporary key buffer later
+                        */
+    char *fmt;         /* buffer for format string which we are processing */
+    size_t flen;       /* length of fmt buffer */
+    char *d, *q, *r;    /* for walking strings */
+    const char *s;
+    size_t doff, qoff;
+    char inquotes;
+
+    List *pflist = getlist();  /* our list of input data indexed by format
+                                * "strings"
+                                */
+    Node *p;
+    struct cmdline_bindings *b;
+    static int warned_of_deprecation = 0;
+    char key[] = "?";          /* Used as temporary storage for a single
+                                * character search string used to locate a
+                                * hash key.
+                                */
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+    /* state varialbes in the while loop which parses the actual
+     * format string in the final parsing pass*/
+    int onearg;
+    int subbedsomething;
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+    if (oldway && !warned_of_deprecation)
+    {
+       /* warn the user that we don't like his kind 'round these parts */
+       warned_of_deprecation = 1;
+       error (0, 0,
+"warning:  Set to use deprecated info format strings.  Establish\n"
+"compatibility with the new info file format strings (add a temporary '1' in\n"
+"all info files after each '%%' which doesn't represent a literal percent)\n"
+"and set UseNewInfoFmtStrings=yes in CVSROOT/config.  After that, convert\n"
+"individual command lines and scripts to handle the new format at your\n"
+"leisure.");
+    }
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+
+    va_start (args, format);
+
+    /* read our possible format strings
+     * expect a certain number of arguments by type and a NULL format
+     * string to terminate the list.
+     */
+    while ((pfmt = va_arg (args, char *)) != NULL)
+    {
+       char *conversion = va_arg (args, char *);
+
+       char conversion_error = 0;
+       char char_conversion = 0;
+       char decimal_conversion = 0;
+       char integer_conversion = 0;
+       char string_conversion = 0;
+
+       /* allocate space to save our data */
+       b = xmalloc(sizeof(struct cmdline_bindings));
+
+       /* where did you think we were going to store all this data??? */
+       b->convproc = NULL;
+       b->closure = NULL;
+
+       /* read a length from the conversion string */
+       s = conversion;
+       length = 0;
+       while (!length && *s)
+       {
+           switch (*s)
+           {
+               case 'h':
+                   integer_conversion = 1;
+                   if (s[1] == 'h')
+                   {
+                       length = sizeof (char);
+                       s += 2;
+                   }
+                   else
+                   {
+                       char_conversion = 1;
+                       length = sizeof (short);
+                       s++;
+                   }
+                   break;
+#ifdef HAVE_INTMAX_T
+               case 'j':
+                   integer_conversion = 1;
+                   length = sizeof (intmax_t);
+                   s++;
+                   break;
+#endif /* HAVE_INTMAX_T */
+               case 'l':
+                   integer_conversion = 1;
+                   if (s[1] == 'l')
+                   {
+#ifdef HAVE_LONG_LONG
+                       length = sizeof (long long);
+#endif
+                       s += 2;
+                   }
+                   else
+                   {
+                       char_conversion = 2;
+                       string_conversion = 2;
+                       length = sizeof (long);
+                       s++;
+                   }
+                   break;
+               case 't':
+                   integer_conversion = 1;
+                   length = sizeof (ptrdiff_t);
+                   s++;
+                   break;
+               case 'z':
+                   integer_conversion = 1;
+                   length = sizeof (size_t);
+                   s++;
+                   break;
+#ifdef HAVE_LONG_DOUBLE
+               case 'L':
+                   decimal_conversion = 1;
+                   length = sizeof (long double);
+                   s++;
+                   break;
+#endif
+               default:
+                   char_conversion = 1;
+                   decimal_conversion = 1;
+                   integer_conversion = 1;
+                   string_conversion = 1;
+                   /* take care of it when we find out what we're looking for 
*/
+                   length = -1;
+                   break;
+           }
+       }
+       /* if we don't have a valid conversion left, that is an error */
+       /* read an argument conversion */
+       buf = xmalloc (strlen(conversion) + 2);
+       *buf = '%';
+       strcpy (buf+1, conversion);
+       switch (*s)
+       {
+           case 'c':
+               /* chars (an integer conversion) */
+               if (!char_conversion)
+               {
+                   conversion_error = 1;
+                   break;
+               }
+               if (char_conversion == 2)
+               {
+#ifdef HAVE_WINT_T
+                   length = sizeof (wint_t);
+#else
+                   conversion_error = 1;
+                   break;
+#endif
+               }
+               else
+                   length = sizeof (char);
+               /* fall through... */
+           case 'd':
+           case 'i':
+           case 'o':
+           case 'u':
+           case 'x':
+           case 'X':
+               /* integer conversions */
+               if (!integer_conversion)
+               {
+                   conversion_error = 1;
+                   break;
+               }
+               if (length == -1)
+               {
+                   length = sizeof (int);
+               }
+               switch (length)
+               {
+                   case sizeof(char):
+                   {
+                       char arg_char = (char) va_arg (args, int);
+                       b->data = Xasprintf (buf, arg_char);
+                       break;
+                   }
+#ifdef UNIQUE_INT_TYPE_WINT_T          /* implies HAVE_WINT_T */
+                   case sizeof(wint_t):
+                   {
+                       wint_t arg_wint_t = va_arg (args, wint_t);
+                       b->data = Xasprintf (buf, arg_wint_t);
+                       break;
+                   }
+#endif /* UNIQUE_INT_TYPE_WINT_T */
+#ifdef UNIQUE_INT_TYPE_SHORT
+                   case sizeof(short):
+                   {
+                       short arg_short = (short) va_arg (args, int);
+                       b->data = Xasprintf (buf, arg_short);
+                       break;
+                   }
+#endif /* UNIQUE_INT_TYPE_SHORT */
+#ifdef UNIQUE_INT_TYPE_INT
+                   case sizeof(int):
+                   {
+                       int arg_int = va_arg (args, int);
+                       b->data = Xasprintf(buf, arg_int);
+                       break;
+                   }
+#endif /* UNIQUE_INT_TYPE_INT */
+#ifdef UNIQUE_INT_TYPE_LONG
+                   case sizeof(long):
+                   {
+                       long arg_long = va_arg (args, long);
+                       b->data = Xasprintf (buf, arg_long);
+                       break;
+                   }
+#endif /* UNIQUE_INT_TYPE_LONG */
+#ifdef UNIQUE_INT_TYPE_LONG_LONG       /* implies HAVE_LONG_LONG */
+                   case sizeof(long long):
+                   {
+                       long long arg_long_long = va_arg (args, long long);
+                       b->data = Xasprintf (buf, arg_long_long);
+                       break;
+                   }
+#endif /* UNIQUE_INT_TYPE_LONG_LONG */
+#ifdef UNIQUE_INT_TYPE_INTMAX_T                /* implies HAVE_INTMAX_T */
+                   case sizeof(intmax_t):
+                   {
+                       intmax_t arg_intmax_t = va_arg (args, intmax_t);
+                       b->data = Xasprintf (buf, arg_intmax_t);
+                       break;
+                   }
+#endif /* UNIQUE_INT_TYPE_INTMAX_T */
+#ifdef UNIQUE_INT_TYPE_SIZE_T
+                   case sizeof(size_t):
+                   {
+                       size_t arg_size_t = va_arg (args, size_t);
+                       b->data = Xasprintf (buf, arg_size_t);
+                       break;
+                   }
+#endif /* UNIQUE_INT_TYPE_SIZE_T */
+#ifdef UNIQUE_INT_TYPE_PTRDIFF_T
+                   case sizeof(ptrdiff_t):
+                   {
+                       ptrdiff_t arg_ptrdiff_t = va_arg (args, ptrdiff_t);
+                       b->data = Xasprintf (buf, arg_ptrdiff_t);
+                       break;
+                   }
+#endif /* UNIQUE_INT_TYPE_PTRDIFF_T */
+                   default:
+                       dellist(&pflist);
+                       free(b);
+                       error (1, 0,
+"internal error:  unknown integer arg size (%d)",
+                               length);
+                       break;
+               }
+               break;
+           case 'a':
+           case 'A':
+           case 'e':
+           case 'E':
+           case 'f':
+           case 'F':
+           case 'g':
+           case 'G':
+               /* decimal conversions */
+               if (!decimal_conversion)
+               {
+                   conversion_error = 1;
+                   break;
+               }
+               if (length == -1)
+               {
+                   length = sizeof (double);
+               }
+               switch (length)
+               {
+                   case sizeof(double):
+                   {
+                       double arg_double = va_arg (args, double);
+                       b->data = Xasprintf (buf, arg_double);
+                       break;
+                   }
+#ifdef UNIQUE_FLOAT_TYPE_LONG_DOUBLE   /* implies HAVE_LONG_DOUBLE */
+                   case sizeof(long double):
+                   {
+                       long double arg_long_double = va_arg (args, long 
double);
+                       b->data = Xasprintf (buf, arg_long_double);
+                       break;
+                   }
+#endif /* UNIQUE_FLOAT_TYPE_LONG_DOUBLE */
+                   default:
+                       dellist(&pflist);
+                       free(b);
+                       error (1, 0,
+"internal error:  unknown floating point arg size (%d)",
+                               length);
+                       break;
+               }
+               break;
+           case 's':
+               switch (string_conversion)
+               {
+                   case 1:
+                       b->data = xstrdup (va_arg (args, char *));
+                       break;
+#ifdef HAVE_WCHAR_T
+                   case 2:
+                   {
+                       wchar_t *arg_wchar_t_string = va_arg (args, wchar_t *);
+                       b->data = Xasprintf (buf, arg_wchar_t_string);
+                       break;
+                   }
+#endif /* HAVE_WCHAR_T */
+                   default:
+                       conversion_error = 1;
+                       break;
+               }
+               break;
+           case ',':
+               if (length != -1)
+               {
+                   conversion_error = 1;
+                   break;
+               }
+               b->data = va_arg (args, List *);
+               b->convproc = va_arg (args, CONVPROC_t);
+               b->closure = va_arg (args, void *);
+               break;
+           default:
+               conversion_error = 1;
+               break;
+       }
+       free (buf);
+       /* fail if we found an error or haven't found the end of the string */
+       if (conversion_error || s[1])
+       {
+           error (1, 0,
+"internal error (format_cmdline): '%s' is not a valid conversion!!!",
+                   conversion);
+       }
+
+
+       /* save our type  - we really only care wheter it's a list type (',')
+        * or not from now on, but what the hell...
+        */
+       b->conversion = *s;
+
+       /* separate the user format string into parts and stuff our data into
+        * the pflist (once for each possible string - diverse keys can have
+        * duplicate data).
+        */
+       q = pfmt;
+       while (*q)
+       {
+           struct cmdline_bindings *tb;
+           if (*q == '{')
+           {
+               s = q + 1;
+               while (*++q && *q != '}');
+               r = q + 1;
+           }
+           else
+           {
+               s = q++;
+               r = q;
+           }
+           if (*r)
+           {
+               /* copy the data since we'll need it again */
+               tb = xmalloc(sizeof(struct cmdline_bindings));
+               if (b->conversion == ',')
+               {
+                   tb->data = b->data;
+               }
+               else
+               {
+                   tb->data = xstrdup(b->data);
+               }
+               tb->conversion = b->conversion;
+               tb->convproc = b->convproc;
+               tb->closure = b->closure;
+           }
+           else
+           {
+               /* we're done after this, so we don't need to copy the data */
+               tb = b;
+           }
+           p = getnode();
+           p->key = xmalloc((q - s) + 1);
+           strncpy (p->key, s, q - s);
+           p->key[q-s] = '\0';
+           p->data = tb;
+           p->delproc = cmdline_bindings_hash_node_delete;
+           addnode(pflist,p);
+       }
+    }
+
+    /* we're done with va_list */
+    va_end(args);
+
+    /* All formatted strings include a format character that resolves to the
+     * empty string by default, so put it in pflist.
+     */
+    /* allocate space to save our data */
+    b = xmalloc(sizeof(struct cmdline_bindings));
+    b->conversion = 's';
+    b->convproc = NULL;
+    b->closure = NULL;
+    b->data = xstrdup( "" );
+    p = getnode();
+    p->key = xstrdup( "n" );
+    p->data = b;
+    p->delproc = cmdline_bindings_hash_node_delete;
+    addnode( pflist,p );
+
+    /* finally, read the user string and copy it into rargv as appropriate */
+    /* user format strings look as follows:
+     *
+     * %% is a literal %
+     * \X, where X is any character = \X, (this is the escape you'd expect, but
+     *        we are leaving the \ for an expected final pass which splits our
+     *        output string into separate arguments
+     *
+     * %X means sub var "X" into location
+     * %{VWXYZ} means sub V,W,X,Y,Z into location as a single arg.  The shell
+     *        || would be to quote the comma separated arguments.  Each list
+     *        that V, W, X, Y, and Z represent attributes of will cause a new
+     *        tuple to be inserted for each list item with a space between
+     *        items.
+     *        e.g."V W1,X1,Z1 W2,X2,Z2 W3,X3,Z3 Y1 Y2" where V is not a list
+     *        variable, W,X,&Z are attributes of a list with 3 items and Y is 
an
+     *        attribute of a second list with 2 items.
+     * %,{VWXYZ} means to separate the args.  The previous example would 
produce
+     *        V W1 X1 Z1 W2 X2 Z2 W3 X3 Z3 Y1 Y2, where each variable is now a
+     *        separate, space delimited, arguments within a single argument.
+     * a%{XY}, where 'a' is a literal, still produces a single arg (a"X Y", in
+     *        shell)
+     * a%1{XY}, where 'a' is a literal, splits the literal as it produces
+     *        multiple args (a X Y).  The rule is that each sub will produce a
+     *        separate arg.  Without a comma, attributes will still be grouped
+     *        together & comma separated in what could be a single argument,
+     *        but internal quotes, commas, and spaces are not excaped.
+     *
+     * clearing the variable oldway, passed into this function, causes the
+     * behavior of '1' and "," in the format string to reverse.
+     */
+
+    /* for convenience, use fmt as a temporary key buffer.
+     * for speed, attempt to realloc it as little as possible
+     */
+    fmt = NULL;
+    flen = 0;
+    
+    /* buf = current argv entry being built
+     * length = current length of buf
+     * s = next char in source buffer to read
+     * d = next char location to write (in buf)
+     * inquotes = current quote char or NUL
+     */
+    s = format;
+    d = buf = NULL;
+    length = 0;
+    doff = d - buf;
+    expand_string (&buf, &length, doff + 1);
+    d = buf + doff;
+
+    inquotes = '\0';
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+    subbedsomething = 0;
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+    while ((*d++ = *s) != '\0')
+    {
+       int list = 0;
+       switch (*s++)
+       {
+           case '\\':
+               /* the character after a \ goes unprocessed but leave the \ in
+                * the string so the function that splits this string into a
+                * command line later can deal with quotes properly
+                *
+                * ignore a NUL
+                */
+               if (*s)
+               {
+                   doff = d - buf;
+                   expand_string (&buf, &length, doff + 1);
+                   d = buf + doff;
+                   *d++ = *s++;
+               }
+               break;
+           case '\'':
+           case '"':
+               /* keep track of quotes so we can escape quote chars we sub in
+                * - the API is that a quoted format string will guarantee that
+                * it gets passed into the command as a single arg
+                */
+               if (!inquotes) inquotes = s[-1];
+               else if (s[-1] == inquotes) inquotes = '\0';
+               break;
+           case '%':
+               if (*s == '%')
+               {
+                   /* "%%" is a literal "%" */
+                   s++;
+                   break;
+               }
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+               if (oldway && subbedsomething)
+               {
+                   /* the old method was to sub only the first format string */
+                   break;
+               }
+               /* initialize onearg each time we get a new format string */
+               onearg = oldway ? 1 : 0;
+               subbedsomething = 1;
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+               d--;    /* we're going to overwrite the '%' regardless
+                        * of other factors... */
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+               /* detect '1' && ',' in the fmt string. */
+               if (*s == '1')
+               {
+                   onearg = 1;
+                   s++;
+                   if (!oldway)
+                   {
+                       /* FIXME - add FILE && LINE */
+                       error (0, 0,
+"Using deprecated info format strings.  Convert your scripts to use\n"
+"the new argument format and remove '1's from your info file format strings.");
+                   }
+               }
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                   
+               /* parse the format string and sub in... */
+               if (*s == '{')
+               {
+                   list = 1;
+                   s++;
+               }
+               /* q = fmt start
+                * r = fmt end + 1
+                */
+               q = fmt;
+               do
+               {
+                   qoff = q - fmt;
+                   expand_string (&fmt, &flen, qoff + 1);
+                   q = fmt + qoff;
+               } while ((*q = *s++) && list && *q++ != '}');
+               /* we will always copy one character, so, whether in list mode
+                * or not, if we just copied a '\0', then we hit the end of the
+                * string before we should have
+                */
+               if (!s[-1])
+               {
+                   /* if we copied a NUL while processing a list, fail
+                    * - we had an empty fmt string or didn't find a list
+                    * terminator ('}')
+                    */
+                   /* FIXME - this wants a file name and line number in a bad
+                    * way.
+                    */
+                   error(1, 0,
+"unterminated format string encountered in command spec.\n"
+"This error is likely to have been caused by an invalid line in a hook 
script\n"
+"spec (see taginfo, loginfo, verifymsginfo, etc. in the Cederqvist).  Most\n"
+"likely the offending line would end with a '%%' character or contain a 
string\n"
+"beginning \"%%{\" and no closing '}' before the end of the line.");
+               }
+               if (list)
+               {
+                   q[-1] = '\0';
+               }
+               else
+               {
+                   /* We're not in a list, so we must have just copied a
+                    * single character.  Terminate the string.
+                    */
+                   q++;
+                   qoff = q - fmt;
+                   expand_string (&fmt, &flen, qoff + 1);
+                   q = fmt + qoff;
+                   *q = '\0';
+               }
+               /* fmt is now a pointer to a list of fmt chars, though the list
+                * could be a single element one
+                */
+               q = fmt;
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+               /* always add quotes in the deprecated onearg case - for
+                * backwards compatibility
+                */
+               if (onearg)
+               {
+                   doff = d - buf;
+                   expand_string (&buf, &length, doff + 1);
+                   d = buf + doff;
+                   *d++ = '"';
+               }
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+               /*
+                * for each character in the fmt string,
+                *
+                * all output will be separate quoted arguments (with
+                * internal quotes escaped) if the argument is in quotes
+                * unless the oldway variable is set, in which case the fmt
+                * statment will correspond to a single argument with
+                * internal space or comma delimited arguments
+                *
+                * see the "user format strings" section above for more info
+                */
+               key[0] = *q;
+               if ((p = findnode (pflist, key)) != NULL)
+               {
+                   b = p->data;
+                   if (b->conversion == ',')
+                   {
+                       /* process the rest of the format string as a list */
+                       struct format_cmdline_walklist_closure c;
+                       c.format = q;
+                       c.buf = &buf;
+                       c.length = &length;
+                       c.d = &d;
+                       c.quotes = inquotes;
+                       c.closure = b->closure;
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+                       c.onearg = onearg;
+                       c.firstpass = 1;
+                       c.srepos = srepos;
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                       walklist(b->data, b->convproc, &c);
+                       d--;    /* back up one space.  we know that ^
+                                  always adds 1 extra */
+                       q += strlen(q);
+                   }
+                   else
+                   {
+                       /* got a flat item */
+                       char *outstr;
+                       if (strlen(q) > 1)
+                       {
+                           error (1, 0,
+"Multiple non-list variables are not allowed in a single format string.");
+                       }
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+                       if (onearg)
+                       {
+                           outstr = b->data;
+                       }
+                       else /* !onearg */
+                       {
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                           /* the *only* case possible without
+                            * SUPPORT_OLD_INFO_FORMAT_STRINGS
+                            * - !onearg
+                            */
+                           /* Avoid adding an empty argument for NULL data.
+                            */
+                           if (!inquotes && b->data)
+                           {
+                               doff = d - buf;
+                               expand_string (&buf, &length, doff + 1);
+                               d = buf + doff;
+                               *d++ = '"';
+                           }
+                           if (b->data)
+                               outstr = cmdlineescape (inquotes ? inquotes
+                                                                : '"',
+                                                       b->data);
+                           else
+                               outstr = xstrdup ("");
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+                       } /* onearg */
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                       doff = d - buf;
+                       expand_string (&buf, &length, doff + strlen(outstr));
+                       d = buf + doff;
+                       strncpy(d, outstr, strlen(outstr));
+                       d += strlen(outstr);
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+                       if (!onearg)
+                       {
+                           free(outstr);
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                           if (!inquotes && b->data)
+                           {
+                               doff = d - buf;
+                               expand_string (&buf, &length, doff + 1);
+                               d = buf + doff;
+                               *d++ = '"';
+                           }
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+                       }
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+                       q++;
+                   }
+               }
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+               else if (onearg)
+               {
+                   /* the old standard was to ignore unknown format
+                    * characters (print the empty string), but also that
+                    * any format character meant print srepos first
+                    */
+                   q++;
+                   doff = d - buf;
+                   expand_string (&buf, &length, doff + strlen(srepos));
+                   d = buf + doff;
+                   strncpy(d, srepos, strlen(srepos));
+                   d += strlen(srepos);
+               }
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+               else /* no key */
+               {
+                   /* print an error message to the user
+                    * FIXME - this should have a file and line number!!! */
+                   error (1, 0,
+"Unknown format character in info file ('%s').\n"
+"Info files are the hook files, verifymsg, taginfo, commitinfo, etc.",
+                           q);
+               }
+#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+               /* always add quotes in the deprecated onearg case - for
+                * backwards compatibility
+                */
+               if (onearg)
+               {
+                   doff = d - buf;
+                   expand_string (&buf, &length, doff + 1);
+                   d = buf + doff;
+                   *d++ = '"';
+               }
+#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+               break;
+       }
+       doff = d - buf;
+       expand_string (&buf, &length, doff + 1);
+       d = buf + doff;
+    } /* while (*d++ = *s) */
+    if (fmt) free (fmt);
+    if (inquotes)
+    {
+       /* FIXME - we shouldn't need this - Parse_Info should be handling
+        * multiple lines...
+        */
+       error (1, 0, "unterminated quote in format string: %s", format);
+    }
+
+    dellist (&pflist);
+    return buf;
+}
+
+
+
+/* Like xstrdup (), but can handle a NULL argument.
+ */
+char *
+Xstrdup (const char *string)
+{
+  if (string == NULL) return NULL;
+  return xmemdup (string, strlen (string) + 1);
+}
+
+
+
+/* Like xasprintf(), but consider all errors fatal (may never return NULL).
+ */
+char *
+Xasprintf (const char *format, ...)
+{
+    va_list args;
+    char *result;
+
+    va_start (args, format);
+    if (vasprintf (&result, format, args) < 0)
+       error (1, errno, "Failed to write to string.");
+    va_end (args);
+
+    return result;
+}
+
+
+
+/* Like xasnprintf(), but consider all errors fatal (may never return NULL).
+ */
+char *
+Xasnprintf (char *resultbuf, size_t *lengthp, const char *format, ...)
+{
+    va_list args;
+    char *result;
+
+    va_start (args, format);
+    result = vasnprintf (resultbuf, lengthp, format, args);
+    if (result == NULL)
+       error (1, errno, "Failed to write to string.");
+    va_end (args);
+
+    return result;
+}
+
+
+
+/* Print a warning and return false if P doesn't look like a string specifying
+ * a boolean value.
+ *
+ * Sets *VAL to the parsed value when it is found to be valid.  *VAL will not
+ * be altered when false is returned.
+ *
+ * INPUTS
+ *   infopath  Where the error is reported to be from on error.  This could
+ *             be, for example, the name of the file the boolean is being read
+ *             from.
+ *   option    An option name being parsed, reported in traces and any error
+ *             message.
+ *   p         The string to actually read the option from.
+ *   val       Pointer to where to store the boolean read from P.
+ *
+ * OUTPUTS
+ *   val       TRUE/FALSE stored, as read, when there are no errors.
+ *
+ * RETURNS
+ *   true      If VAL was read.
+ *   false     On error.
+ */
+bool
+readBool (const char *infopath, const char *option, const char *p, bool *val)
+{
+    TRACE (TRACE_FLOW, "readBool (%s, %s, %s)", infopath, option, p);
+    if (!strcasecmp (p, "no") || !strcasecmp (p, "false")
+        || !strcasecmp (p, "off") || !strcmp (p, "0"))
+    {
+       TRACE (TRACE_DATA, "Read %d for %s", *val, option);
+       *val = false;
+       return true;
+    }
+    else if (!strcasecmp (p, "yes") || !strcasecmp (p, "true")
+            || !strcasecmp (p, "on") || !strcmp (p, "1"))
+    {
+       TRACE (TRACE_DATA, "Read %d for %s", *val, option);
+       *val = true;
+       return true;
+    }
+
+    error (0, 0, "%s: unrecognized value `%s' for `%s'",
+          infopath, p, option);
+    return false;
+}
+
+
+
+/*
+ * Open a file, exiting with a message on error.
+ *
+ * INPUTS
+ *   name      The name of the file to open.
+ *   mode      Mode to open file in, as POSIX fopen().
+ *
+ * NOTES
+ *   If you want to handle errors, just call fopen (NAME, MODE).
+ *
+ * RETURNS
+ *   The new FILE pointer.
+ */
+FILE *
+xfopen (const char *name, const char *mode)
+{
+    FILE *fp;
+
+    if (!(fp = fopen (name, mode)))
+       error (1, errno, "cannot open %s", name);
+    return fp;
+}
+
+
+
+/* char *
+ * xcanonicalize_file_name (const char *path)
+ *
+ * Like canonicalize_file_name(), but exit on error.
+ *
+ * INPUTS
+ *  path       The original path.
+ *
+ * RETURNS
+ *  The path with any symbolic links, `.'s, or `..'s, expanded.
+ *
+ * ERRORS
+ *  This function exits with a fatal error if it fails to read the link for
+ *  any reason.
+ */
+char *
+xcanonicalize_file_name (const char *path)
+{
+    char *hardpath = canonicalize_file_name (path);
+    if (!hardpath)
+       error (1, errno, "Failed to resolve path: `%s'", path);
+    return hardpath;
+}
+
+
+
+/* Declared in main.c.  */
+extern char *server_hostname;
+
+/* Return true if OTHERHOST resolves to this host in the DNS.
+ *
+ * GLOBALS
+ *   server_hostname   The name of this host, as determined by the call to
+ *                     xgethostname() in main().
+ *
+ * RETURNS
+ *   true      If OTHERHOST equals or resolves to HOSTNAME.
+ *   false     Otherwise.
+ */
+bool
+isThisHost (const char *otherhost)
+{
+    char *fqdno;
+    char *fqdns;
+    bool retval;
+
+    /* As an optimization, check the literal strings before looking up
+     * OTHERHOST in the DNS.
+     */
+    if (!strcasecmp (server_hostname, otherhost))
+       return true;
+
+    fqdno = canon_host (otherhost);
+    if (!fqdno)
+       error (1, 0, "Name lookup failed for `%s': %s",
+              otherhost, ch_strerror ());
+    fqdns = canon_host (server_hostname);
+    if (!fqdns)
+       error (1, 0, "Name lookup failed for `%s': %s",
+              server_hostname, ch_strerror ());
+
+    retval = !strcasecmp (fqdns, fqdno);
+
+    free (fqdno);
+    free (fqdns);
+    return retval;
+}
+
+
+
+/* Return true if two paths match, resolving symlinks.
+ */
+bool
+isSamePath (const char *path1_in, const char *path2_in)
+{
+    char *p1, *p2;
+    bool same;
+
+    if (!strcmp (path1_in, path2_in))
+       return true;
+
+    /* Path didn't match, but try to resolve any links that may be
+     * present.
+     */
+    if (!isdir (path1_in) || !isdir (path2_in))
+       /* To be resolvable, paths must exist on this server.  */
+       return false;
+
+    p1 = xcanonicalize_file_name (path1_in);
+    p2 = xcanonicalize_file_name (path2_in);
+    if (strcmp (p1, p2))
+       same = false;
+    else
+       same = true;
+
+    free (p1);
+    free (p2);
+    return same;
+}




reply via email to

[Prev in Thread] Current Thread [Next in Thread]