diff options
Diffstat (limited to 'src/subr.c')
-rw-r--r-- | src/subr.c | 2003 |
1 files changed, 2003 insertions, 0 deletions
diff --git a/src/subr.c b/src/subr.c new file mode 100644 index 0000000..3c3a5e1 --- /dev/null +++ b/src/subr.c @@ -0,0 +1,2003 @@ +/* + * 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 */ + if (!inquotes) + { + doff = d - buf; + expand_string (&buf, &length, doff + 1); + d = buf + doff; + *d++ = '"'; + } + outstr = cmdlineescape (inquotes ? inquotes : '"', b->data); +#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) + { + 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; +} |