summaryrefslogtreecommitdiff
path: root/src/expand_path.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/expand_path.c')
-rw-r--r--src/expand_path.c382
1 files changed, 382 insertions, 0 deletions
diff --git a/src/expand_path.c b/src/expand_path.c
new file mode 100644
index 0000000..a25e84f
--- /dev/null
+++ b/src/expand_path.c
@@ -0,0 +1,382 @@
+/* expand_path.c -- expand environmental variables in passed in string
+ *
+ * Copyright (C) 1995-2005 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * The main routine is expand_path(), it is the routine that handles
+ * the '~' character in four forms:
+ * ~name
+ * ~name/
+ * ~/
+ * ~
+ * and handles environment variables contained within the pathname
+ * which are defined by:
+ * ${var_name} (var_name is the name of the environ variable)
+ * $var_name (var_name ends w/ non-alphanumeric char other than '_')
+ */
+
+#include "cvs.h"
+#include <sys/types.h>
+
+/* User variables. */
+
+List *variable_list;
+
+static void variable_delproc (Node *);
+
+static void
+variable_delproc (Node *node)
+{
+ free (node->data);
+}
+
+/* Currently used by -s option; we might want a way to set user
+ variables in a file in the $CVSROOT/CVSROOT directory too. */
+
+void
+variable_set (char *nameval)
+{
+ char *p;
+ char *name;
+ Node *node;
+
+ p = nameval;
+ while (isalnum ((unsigned char) *p) || *p == '_')
+ ++p;
+ if (*p != '=')
+ error (1, 0, "invalid character in user variable name in %s", nameval);
+ if (p == nameval)
+ error (1, 0, "empty user variable name in %s", nameval);
+ name = xmalloc (p - nameval + 1);
+ strncpy (name, nameval, p - nameval);
+ name[p - nameval] = '\0';
+ /* Make p point to the value. */
+ ++p;
+ if (strchr (p, '\012'))
+ error (1, 0, "linefeed in user variable value in %s", nameval);
+
+ if (!variable_list)
+ variable_list = getlist ();
+
+ node = findnode (variable_list, name);
+ if (!node)
+ {
+ node = getnode ();
+ node->type = VARIABLE;
+ node->delproc = variable_delproc;
+ node->key = name;
+ node->data = xstrdup (p);
+ (void) addnode (variable_list, node);
+ }
+ else
+ {
+ /* Replace the old value. For example, this means that -s
+ options on the command line override ones from .cvsrc. */
+ free (node->data);
+ node->data = xstrdup (p);
+ free (name);
+ }
+}
+
+
+
+/* Expand variable NAME into its contents, per the rules above.
+ *
+ * CVSROOT is used to expanding $CVSROOT.
+ *
+ * RETURNS
+ * A pointer to the requested variable contents or NULL when the requested
+ * variable is not found.
+ *
+ * ERRORS
+ * None, though this function may generate warning messages when NAME is not
+ * found.
+ */
+static const char *
+expand_variable (const char *name, const char *cvsroot,
+ const char *file, int line)
+{
+ if (!strcmp (name, CVSROOT_ENV))
+ return cvsroot;
+ else if (!strcmp (name, "RCSBIN"))
+ {
+ error (0, 0, "RCSBIN internal variable is no longer supported");
+ return NULL;
+ }
+ else if (!strcmp (name, EDITOR1_ENV))
+ return Editor;
+ else if (!strcmp (name, EDITOR2_ENV))
+ return Editor;
+ else if (!strcmp (name, EDITOR3_ENV))
+ return Editor;
+ else if (!strcmp (name, "USER"))
+ return getcaller ();
+ else if (!strcmp (name, "SESSIONID")
+ || !strcmp (name, "COMMITID"))
+ return global_session_id;
+ else if (isalpha (name[0]))
+ {
+ /* These names are reserved for future versions of CVS,
+ so that is why it is an error. */
+ if (line)
+ error (0, 0, "%s:%d: no such internal variable $%s",
+ file, line, name);
+ else
+ error (0, 0, "%s: no such internal variable $%s",
+ file, name);
+ return NULL;
+ }
+ else if (name[0] == '=')
+ {
+ Node *node;
+ /* Crazy syntax for a user variable. But we want
+ *something* that lets the user name a user variable
+ anything he wants, without interference from
+ (existing or future) internal variables. */
+ node = findnode (variable_list, name + 1);
+ if (!node)
+ {
+ if (line)
+ error (0, 0, "%s:%d: no such user variable ${%s}",
+ file, line, name);
+ else
+ error (0, 0, "%s: no such user variable ${%s}",
+ file, name);
+ return NULL;
+ }
+ return node->data;
+ }
+ else
+ {
+ /* It is an unrecognized character. We return an error to
+ reserve these for future versions of CVS; it is plausible
+ that various crazy syntaxes might be invented for inserting
+ information about revisions, branches, etc. */
+ if (line)
+ error (0, 0, "%s:%d: unrecognized variable syntax %s",
+ file, line, name);
+ else
+ error (0, 0, "%s: unrecognized variable syntax %s",
+ file, name);
+ return NULL;
+ }
+}
+
+
+
+/* This routine will expand the pathname to account for ~ and $
+ * characters as described above. Returns a pointer to a newly
+ * malloc'd string. If an error occurs, an error message is printed
+ * via error() and NULL is returned. FILE and LINE are the filename
+ * and linenumber to include in the error message. FILE must point
+ * to something; LINE can be zero to indicate the line number is not
+ * known.
+ *
+ * When FORMATSAFE is set, percent signs (`%') in variable contents are doubled
+ * to prevent later expansion by format_cmdline.
+ *
+ * CVSROOT is used to expanding $CVSROOT.
+ */
+char *
+expand_path (const char *name, const char *cvsroot, bool formatsafe,
+ const char *file, int line)
+{
+ size_t s, d, p;
+ const char *e;
+
+ char *mybuf = NULL;
+ size_t mybuf_size = 0;
+ char *buf = NULL;
+ size_t buf_size = 0;
+
+ char inquotes = '\0';
+
+ char *result;
+
+ /* Sorry this routine is so ugly; it is a head-on collision
+ between the `traditional' unix *d++ style and the need to
+ dynamically allocate. It would be much cleaner (and probably
+ faster, not that this is a bottleneck for CVS) with more use of
+ strcpy & friends, but I haven't taken the effort to rewrite it
+ thusly. */
+
+ /* First copy from NAME to MYBUF, expanding $<foo> as we go. */
+ s = d = 0;
+ expand_string (&mybuf, &mybuf_size, d + 1);
+ while ((mybuf[d++] = name[s]) != '\0')
+ {
+ if (name[s] == '\\')
+ {
+ /* The next character is a literal. Leave the \ in the string
+ * since it will be needed again when the string is split into
+ * arguments.
+ */
+ /* if we have a \ as the last character of the string, just leave
+ * it there - this is where we would set the escape flag to tell
+ * our parent we want another line if we cared.
+ */
+ if (name[++s])
+ {
+ expand_string (&mybuf, &mybuf_size, d + 1);
+ mybuf[d++] = name[s++];
+ }
+ }
+ /* skip $ variable processing for text inside single quotes */
+ else if (inquotes == '\'')
+ {
+ if (name[s++] == '\'')
+ {
+ inquotes = '\0';
+ }
+ }
+ else if (name[s] == '\'')
+ {
+ s++;
+ inquotes = '\'';
+ }
+ else if (name[s] == '"')
+ {
+ s++;
+ if (inquotes) inquotes = '\0';
+ else inquotes = '"';
+ }
+ else if (name[s++] == '$')
+ {
+ int flag = (name[s] == '{');
+ p = d;
+
+ expand_string (&mybuf, &mybuf_size, d + 1);
+ for (; (mybuf[d++] = name[s]); s++)
+ {
+ if (flag
+ ? name[s] =='}'
+ : !isalnum (name[s]) && name[s] != '_')
+ break;
+ expand_string (&mybuf, &mybuf_size, d + 1);
+ }
+ mybuf[--d] = '\0';
+ e = expand_variable (&mybuf[p+flag], cvsroot, file, line);
+
+ if (e)
+ {
+ expand_string (&mybuf, &mybuf_size, d + 1);
+ for (d = p - 1; (mybuf[d++] = *e++); )
+ {
+ expand_string (&mybuf, &mybuf_size, d + 1);
+ if (mybuf[d-1] == '"')
+ {
+ /* escape the double quotes if we're between a matched
+ * pair of double quotes so that this sub will be
+ * passed inside as or as part of a single argument
+ * during the argument split later.
+ */
+ if (inquotes)
+ {
+ mybuf[d-1] = '\\';
+ expand_string (&mybuf, &mybuf_size, d + 1);
+ mybuf[d++] = '"';
+ }
+ }
+ else if (formatsafe && mybuf[d-1] == '%')
+ {
+ /* escape '%' to get past printf style format strings
+ * later (in make_cmdline).
+ */
+ expand_string (&mybuf, &mybuf_size, d + 1);
+ mybuf[d] = '%';
+ d++;
+ }
+ }
+ --d;
+ if (flag && name[s])
+ s++;
+ }
+ else
+ /* expand_variable has already printed an error message. */
+ goto error_exit;
+ }
+ expand_string (&mybuf, &mybuf_size, d + 1);
+ }
+ expand_string (&mybuf, &mybuf_size, d + 1);
+ mybuf[d] = '\0';
+
+ /* Then copy from MYBUF to BUF, expanding ~. */
+ s = d = 0;
+ /* If you don't want ~username ~/ to be expanded simply remove
+ * This entire if statement including the else portion
+ */
+ if (mybuf[s] == '~')
+ {
+ p = d;
+ while (mybuf[++s] != '/' && mybuf[s] != '\0')
+ {
+ expand_string (&buf, &buf_size, p + 1);
+ buf[p++] = name[s];
+ }
+ expand_string (&buf, &buf_size, p + 1);
+ buf[p] = '\0';
+
+ if (p == d)
+ e = get_homedir ();
+ else
+ {
+#ifdef GETPWNAM_MISSING
+ if (line)
+ error (0, 0,
+ "%s:%d:tilde expansion not supported on this system",
+ file, line);
+ else
+ error (0, 0, "%s:tilde expansion not supported on this system",
+ file);
+ goto error_exit;
+#else
+ struct passwd *ps;
+ ps = getpwnam (buf + d);
+ if (ps == NULL)
+ {
+ if (line)
+ error (0, 0, "%s:%d: no such user %s",
+ file, line, buf + d);
+ else
+ error (0, 0, "%s: no such user %s", file, buf + d);
+ goto error_exit;
+ }
+ e = ps->pw_dir;
+#endif
+ }
+ if (!e)
+ error (1, 0, "cannot find home directory");
+
+ p = strlen (e);
+ expand_string (&buf, &buf_size, d + p);
+ memcpy (buf + d, e, p);
+ d += p;
+ }
+ /* Kill up to here */
+ p = strlen (mybuf + s) + 1;
+ expand_string (&buf, &buf_size, d + p);
+ memcpy (buf + d, mybuf + s, p);
+
+ /* OK, buf contains the value we want to return. Clean up and return
+ it. */
+ free (mybuf);
+ /* Save a little memory with xstrdup; buf will tend to allocate
+ more than it needs to. */
+ result = xstrdup (buf);
+ free (buf);
+ return result;
+
+ error_exit:
+ if (mybuf) free (mybuf);
+ if (buf) free (buf);
+ return NULL;
+}