diff --git a/doc/xpath.txt b/doc/xpath.txt
index cd12ebe..a609003 100644
--- a/doc/xpath.txt
+++ b/doc/xpath.txt
@@ -133,7 +133,10 @@ UnionExpr ::= PathExpr ("|" PathExpr)*
 
 Literal ::= '"' /[^"]* / '"' | "'" /[^']* / "'"
 Number       ::= /[0-9]+/
-Name ::= /([^][/\= \t\n]|\\.)+/
+/* Names can contain whitespace in the interior */
+NameNoWS ::= [^][|/\= \t\n] | \\.
+NameWS   ::= [^][|/\=] | \\.
+Name ::= NameNoWS NameWS* NameNoWS | NameNoWS
 
 VariableReference ::= '$' /[a-zA-Z_][a-zA-Z0-9_]*/
 
diff --git a/src/augeas.c b/src/augeas.c
index 8d6de28..031f077 100644
--- a/src/augeas.c
+++ b/src/augeas.c
@@ -64,7 +64,9 @@ static const char *const errcodes[] = {
     "Syntax error in lens definition",                  /* AUG_ESYNTAX */
     "Lens not found",                                   /* AUG_ENOLENS */
     "Multiple transforms",                              /* AUG_EMXFM */
-    "Node has no span info"                             /* AUG_ENOSPAN */
+    "Node has no span info",                            /* AUG_ENOSPAN */
+    "Cannot move node into its descendant",             /* AUG_EMVDESC */
+    "Invalid argument in function call"                 /* AUG_EBADARG */
 };
 
 static void tree_mark_dirty(struct tree *tree) {
@@ -122,6 +124,44 @@ struct tree *tree_path_cr(struct tree *tree, int n, ...) {
     return tree;
 }
 
+static struct tree *tree_fpath_int(struct augeas *aug, const char *fpath,
+                                   bool create) {
+    int r;
+    char *steps = NULL, *step = NULL;
+    size_t nsteps = 0;
+    struct tree *result = NULL;
+
+    r = argz_create_sep(fpath, '/', &steps, &nsteps);
+    ERR_NOMEM(r < 0, aug);
+    result = aug->origin;
+    while ((step = argz_next(steps, nsteps, step))) {
+        if (create) {
+            result = tree_child_cr(result, step);
+            ERR_THROW(result == NULL, aug, AUG_ENOMEM,
+                      "while searching %s: can not create %s", fpath, step);
+        } else {
+            /* Lookup only */
+            result = tree_child(result, step);
+            if (result == NULL)
+                goto done;
+        }
+    }
+ done:
+    free(steps);
+    return result;
+ error:
+    result = NULL;
+    goto done;
+}
+
+struct tree *tree_fpath(struct augeas *aug, const char *fpath) {
+    return tree_fpath_int(aug, fpath, false);
+}
+
+struct tree *tree_fpath_cr(struct augeas *aug, const char *fpath) {
+    return tree_fpath_int(aug, fpath, true);
+}
+
 struct tree *tree_find(struct augeas *aug, const char *path) {
     struct pathx *p = NULL;
     struct tree *result = NULL;
@@ -283,7 +323,7 @@ static struct tree *tree_from_transform(struct augeas *aug,
     }
     return txfm;
  error:
-    tree_unlink(txfm);
+    tree_unlink(aug, txfm);
     return NULL;
 }
 
@@ -465,6 +505,37 @@ struct augeas *aug_init(const char *root, const char *loadpath,
     return NULL;
 }
 
+/* Free one tree node */
+static void free_tree_node(struct tree *tree) {
+    if (tree == NULL)
+        return;
+
+    if (tree->span != NULL)
+        free_span(tree->span);
+    free(tree->label);
+    free(tree->value);
+    free(tree);
+}
+
+/* Only unlink; assume we know TREE is not in the symtab */
+static int tree_unlink_raw(struct tree *tree) {
+    int result = 0;
+
+    assert (tree->parent != NULL);
+    list_remove(tree, tree->parent->children);
+    tree_mark_dirty(tree->parent);
+    result = free_tree(tree->children) + 1;
+    free_tree_node(tree);
+    return result;
+}
+
+int tree_unlink(struct augeas *aug, struct tree *tree) {
+    if (tree == NULL)
+        return 0;
+    pathx_symtab_remove_descendants(aug->symtab, tree);
+    return tree_unlink_raw(tree);
+}
+
 void tree_unlink_children(struct augeas *aug, struct tree *tree) {
     if (tree == NULL)
         return;
@@ -472,7 +543,7 @@ void tree_unlink_children(struct augeas *aug, struct tree *tree) {
     pathx_symtab_remove_descendants(aug->symtab, tree);
 
     while (tree->children != NULL)
-        tree_unlink(tree->children);
+        tree_unlink_raw(tree->children);
 }
 
 static void tree_mark_files(struct tree *tree) {
@@ -492,8 +563,8 @@ static void tree_rm_dirty_files(struct augeas *aug, struct tree *tree) {
         return;
 
     if ((p = tree_child(tree, "path")) != NULL) {
-        aug_rm(aug, p->value);
-        tree_unlink(tree);
+        tree_unlink(aug, tree_fpath(aug, p->value));
+        tree_unlink(aug, tree);
     } else {
         struct tree *c = tree->children;
         while (c != NULL) {
@@ -517,7 +588,7 @@ static void tree_rm_dirty_leaves(struct augeas *aug, struct tree *tree,
     }
 
     if (tree != protect && tree->children == NULL)
-        tree_unlink(tree);
+        tree_unlink(aug, tree);
 }
 
 int aug_load(struct augeas *aug) {
@@ -639,9 +710,7 @@ static void record_var_meta(struct augeas *aug, const char *name,
     struct tree *tree = tree_path_cr(aug->origin, 2, s_augeas, s_vars);
     ERR_NOMEM(tree == NULL, aug);
     if (expr == NULL) {
-        tree = tree_child(tree, name);
-        if (tree != NULL)
-            tree_unlink(tree);
+        tree_unlink(aug, tree_child(tree, name));
     } else {
         tree = tree_child_cr(tree, name);
         ERR_NOMEM(tree == NULL, aug);
@@ -884,18 +953,6 @@ struct tree *make_tree_origin(struct tree *root) {
     return origin;
 }
 
-/* Free one tree node */
-static void free_tree_node(struct tree *tree) {
-    if (tree == NULL)
-        return;
-
-    if (tree->span != NULL)
-        free_span(tree->span);
-    free(tree->label);
-    free(tree->value);
-    free(tree);
-}
-
 /* Recursively free the whole tree TREE and all its siblings */
 int free_tree(struct tree *tree) {
     int cnt = 0;
@@ -911,17 +968,6 @@ int free_tree(struct tree *tree) {
     return cnt;
 }
 
-int tree_unlink(struct tree *tree) {
-    int result = 0;
-
-    assert (tree->parent != NULL);
-    list_remove(tree, tree->parent->children);
-    tree_mark_dirty(tree->parent);
-    result = free_tree(tree->children) + 1;
-    free_tree_node(tree);
-    return result;
-}
-
 int tree_rm(struct pathx *p) {
     struct tree *tree, **del;
     int cnt = 0, ndel = 0, i;
@@ -948,7 +994,7 @@ int tree_rm(struct pathx *p) {
     }
 
     for (i = 0; i < ndel; i++)
-        cnt += tree_unlink(del[i]);
+        cnt += tree_unlink_raw(del[i]);
     free(del);
 
     return cnt;
@@ -1031,33 +1077,6 @@ int aug_span(struct augeas *aug, const char *path, char **filename,
     return result;
 }
 
-int tree_replace(struct augeas *aug, const char *path, struct tree *sub) {
-    struct tree *parent;
-    struct pathx *p = NULL;
-    int r;
-
-    p = pathx_aug_parse(aug, aug->origin, path, true);
-    ERR_BAIL(aug);
-
-    r = tree_rm(p);
-    if (r == -1)
-        goto error;
-
-    parent = tree_set(p, NULL);
-    if (parent == NULL)
-        goto error;
-
-    list_append(parent->children, sub);
-    list_for_each(s, sub) {
-        s->parent = parent;
-    }
-    free_pathx(p);
-    return 0;
- error:
-    free_pathx(p);
-    return -1;
-}
-
 int aug_mv(struct augeas *aug, const char *src, const char *dst) {
     struct pathx *s = NULL, *d = NULL;
     struct tree *ts, *td, *t;
@@ -1100,7 +1119,7 @@ int aug_mv(struct augeas *aug, const char *src, const char *dst) {
     ts->value = NULL;
     ts->children = NULL;
 
-    tree_unlink(ts);
+    tree_unlink(aug, ts);
     tree_mark_dirty(td);
 
     ret = 0;
@@ -1413,6 +1432,20 @@ int dump_tree(FILE *out, struct tree *tree) {
     return result;
 }
 
+int aug_escape_name(augeas *aug, const char *in, char **out) {
+    int result;
+
+    api_entry(aug);
+    ARG_CHECK(in == NULL, aug, "aug_escape_name: IN must not be NULL");
+    ARG_CHECK(out == NULL, aug, "aug_escape_name: OUT must not be NULL");
+
+    result = pathx_escape_name(in, out);
+    ERR_NOMEM(result < 0, aug);
+ error:
+    api_exit(aug);
+    return result;
+}
+
 int aug_print(const struct augeas *aug, FILE *out, const char *pathin) {
     struct pathx *p;
     int result;
diff --git a/src/augeas.h b/src/augeas.h
index 813b7a6..2981ce1 100644
--- a/src/augeas.h
+++ b/src/augeas.h
@@ -289,6 +289,23 @@ int aug_save(augeas *aug);
  */
 int aug_load(augeas *aug);
 
+/* Function: aug_escape_name
+ *
+ * Escape special characters in a string such that it can be used as part
+ * of a path expressions and only matches a node named exactly
+ * IN. Characters that have special meanings in path expressions, such as
+ * '[' and ']' are prefixed with a '\\'. Note that this function assumes
+ * that it is passed a name, not a path, and will therefore escape '/',
+ * too.
+ *
+ * On return, *OUT is NULL if IN does not need any escaping at all, and
+ * points to an escaped copy of IN otherwise.
+ *
+ * Returns:
+ * 0 on success, or a negative value on failure
+ */
+int aug_escape_name(augeas *aug, const char *in, char **out);
+
 /* Function: aug_print
  *
  * Print each node matching PATH and its descendants to OUT.
@@ -320,7 +337,9 @@ typedef enum {
     AUG_ESYNTAX,        /* Syntax error in lens file */
     AUG_ENOLENS,        /* Lens lookup failed */
     AUG_EMXFM,          /* Multiple transforms */
-    AUG_ENOSPAN         /* No span for this node */
+    AUG_ENOSPAN,        /* No span for this node */
+    AUG_EMVDESC,        /* Cannot move node into its descendant */
+    AUG_EBADARG         /* Invalid argument in funcion call */
 } aug_errcode_t;
 
 /* Return the error code from the last API call */
diff --git a/src/augeas_sym.version b/src/augeas_sym.version
index 3f065f1..03ebee7 100644
--- a/src/augeas_sym.version
+++ b/src/augeas_sym.version
@@ -39,3 +39,8 @@ AUGEAS_0.12.0 {
     global:
       aug_span;
 } AUGEAS_0.11.0;
+
+AUGEAS_0.19.0 {
+    global:
+      aug_escape_name;
+} AUGEAS_0.12.0;
diff --git a/src/errcode.h b/src/errcode.h
index cef33e4..ea2d32b 100644
--- a/src/errcode.h
+++ b/src/errcode.h
@@ -75,6 +75,14 @@ void reset_error(struct error *err);
         }                                               \
     } while(0)
 
+#define ARG_CHECK(cond, obj, fmt ...)                           \
+    do {                                                        \
+        if (cond) {                                             \
+            report_error((obj)->error, AUG_EBADARG, ## fmt);    \
+            goto error;                                         \
+        }                                                       \
+    } while(0)
+
 /* A variant of assert that uses our error reporting infrastructure
  * instead of aborting
  */
diff --git a/src/internal.c b/src/internal.c
index c771816..3af49d1 100644
--- a/src/internal.c
+++ b/src/internal.c
@@ -325,6 +325,8 @@ char *path_expand(struct tree *tree, const char *ppath) {
 
     char *path;
     const char *label;
+    char *escaped = NULL;
+
     int cnt = 0, ind = 0, r;
 
     list_for_each(t, siblings) {
@@ -345,11 +347,21 @@ char *path_expand(struct tree *tree, const char *ppath) {
     else
         label = tree->label;
 
+    r = pathx_escape_name(label, &escaped);
+    if (r < 0)
+        return NULL;
+
+    if (escaped != NULL)
+        label = escaped;
+
     if (cnt > 1) {
         r = asprintf(&path, "%s/%s[%d]", ppath, label, ind);
     } else {
         r = asprintf(&path, "%s/%s", ppath, label);
     }
+
+    free(escaped);
+
     if (r == -1)
         return NULL;
     return path;
diff --git a/src/internal.h b/src/internal.h
index 96f0db0..fcdc237 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -378,12 +378,11 @@ struct tree *make_tree(char *label, char *value,
  */
 struct tree  *make_tree_origin(struct tree *root);
 
-int tree_replace(struct augeas *aug, const char *path, struct tree *sub);
 /* Make a new tree node and append it to parent's children */
 struct tree *tree_append(struct tree *parent, char *label, char *value);
 
 int tree_rm(struct pathx *p);
-int tree_unlink(struct tree *tree);
+int tree_unlink(struct augeas *aug, struct tree *tree);
 struct tree *tree_set(struct pathx *p, const char *value);
 int tree_insert(struct pathx *p, const char *label, int before);
 int free_tree(struct tree *tree);
@@ -408,6 +407,15 @@ void tree_store_value(struct tree *tree, char **value);
 int tree_set_value(struct tree *tree, const char *value);
 /* Cleanly remove all children of TREE, but leave TREE itself unchanged */
 void tree_unlink_children(struct augeas *aug, struct tree *tree);
+/* Find a node in the tree at path FPATH; FPATH is a file path, i.e.
+ * not interpreted as a path expression. If no such node exists, return NULL
+ */
+struct tree *tree_fpath(struct augeas *aug, const char *fpath);
+/* Find a node in the tree at path FPATH; FPATH is a file path, i.e.
+ * not interpreted as a path expression. If no such node exists, create
+ * it and all its missing ancestors.
+ */
+struct tree *tree_fpath_cr(struct augeas *aug, const char *fpath);
 /* Find the node matching PATH.
  * Returns the node or NULL on error
  * Errors: EMMATCH - more than one node matches PATH
@@ -532,6 +540,16 @@ void pathx_symtab_remove_descendants(struct pathx_symtab *symtab,
                                      const struct tree *tree);
 void free_symtab(struct pathx_symtab *symtab);
 
+/* Escape a name so that it is safe to pass to parse_name and have it
+ * interpreted as the literal name of a path component.
+ *
+ * On return, *OUT will be NULL if IN does not need escaping, otherwise it
+ * will contain an escaped copy of IN which the caller must free.
+ *
+ * Returns -1 if it failed to allocate memory for *OUT, 0 on success
+ */
+int pathx_escape_name(const char *in, char **out);
+
 /* Debug helpers, all defined in internal.c. When ENABLE_DEBUG is not
  * set, they compile to nothing.
  */
diff --git a/src/pathx.c b/src/pathx.c
index a4cdf48..5c6776a 100644
--- a/src/pathx.c
+++ b/src/pathx.c
@@ -120,6 +120,15 @@ static const char *const axis_names[] = {
 
 static const char *const axis_sep = "::";
 
+/* The characters that can follow a name in a location expression (aka path)
+ * The parser will assume that name (path component) is finished when it
+ * encounters any of these characters, unless they are escaped by preceding
+ * them with a '\\'.
+ *
+ * See parse_name for the gory details
+ */
+static const char const name_follow[] = "][|/=()!";
+
 /* Doubly linked list of location steps. Besides the information from the
  * path expression, also contains information to iterate over a node set,
  * in particular, the context node CTX for the step, and the current node
@@ -1373,18 +1382,51 @@ static void push_new_binary_op(enum binary_op op, struct state *state) {
     push_expr(expr, state);
 }
 
+int pathx_escape_name(const char *in, char **out) {
+    const char *p;
+    int num_to_escape = 0;
+    char *s;
+
+    *out = NULL;
+
+    for (p = in; *p; p++) {
+        if (strchr(name_follow, *p) || isspace(*p))
+            num_to_escape += 1;
+    }
+
+    if (num_to_escape == 0)
+        return 0;
+
+    if (ALLOC_N(*out, strlen(in) + num_to_escape + 1) < 0)
+        return -1;
+
+    for (p = in, s = *out; *p; p++) {
+        if (strchr(name_follow, *p) || isspace(*p))
+            *s++ = '\\';
+        *s++ = *p;
+    }
+    *s = '\0';
+    return 0;
+}
+
 /*
- * Name ::= /[^][/=) \t\n]+/
+ * NameNoWS ::= [^][|/\= \t\n] | \\.
+ * NameWS   ::= [^][|/\=] | \\.
+ * Name ::= NameNoWS NameWS* NameNoWS | NameNoWS
  */
 static char *parse_name(struct state *state) {
     const char *s = state->pos;
     char *result;
 
-    while (*state->pos != '\0' &&
-           *state->pos != L_BRACK && *state->pos != SEP &&
-           *state->pos != R_BRACK && *state->pos != '=' &&
-           *state->pos != ')' &&
-           !isspace(*state->pos)) {
+    while (*state->pos != '\0' && strchr(name_follow, *state->pos) == NULL) {
+        /* This is a hack: since we allow spaces in names, we need to avoid
+         * gobbling up stuff that is in follow(Name), e.g. 'or' so that
+         * things like [name1 or name2] still work.
+         */
+        if (STREQLEN(state->pos, " or ", strlen(" or ")) ||
+            STREQLEN(state->pos, " and ", strlen(" and ")))
+            break;
+
         if (*state->pos == '\\') {
             state->pos += 1;
             if (*state->pos == '\0') {
@@ -1395,6 +1437,14 @@ static char *parse_name(struct state *state) {
         state->pos += 1;
     }
 
+    /* Strip trailing white space */
+    if (state->pos > s) {
+        state->pos -= 1;
+        while (isspace(*state->pos) && state->pos >= s)
+            state->pos -= 1;
+        state->pos += 1;
+    }
+
     if (state->pos == s) {
         STATE_ERROR(state, PATHX_ENAME);
         return NULL;
diff --git a/src/transform.c b/src/transform.c
index cd0c9d7..5f0ac94 100644
--- a/src/transform.c
+++ b/src/transform.c
@@ -170,7 +170,7 @@ static bool file_current(struct augeas *aug, const char *fname,
     if (path == NULL)
         return false;
 
-    file = tree_find(aug, path->value);
+    file = tree_fpath(aug, path->value);
     return (file != NULL && ! file->dirty);
 }
 
@@ -339,7 +339,7 @@ static int store_error(struct augeas *aug,
     r = pathjoin(&fip, 2, AUGEAS_META_FILES, filename);
     ERR_NOMEM(r < 0, aug);
 
-    finfo = tree_find_cr(aug, fip);
+    finfo = tree_fpath_cr(aug, fip);
     ERR_BAIL(aug);
 
     if (status != NULL) {
@@ -379,11 +379,8 @@ static int store_error(struct augeas *aug,
     } else {
         /* No error, nuke the error node if it exists */
         err_info = tree_child(finfo, s_error);
-        if (err_info != NULL) {
-            tree_unlink_children(aug, err_info);
-            pathx_symtab_remove_descendants(aug->symtab, err_info);
-            tree_unlink(err_info);
-        }
+        if (err_info != NULL)
+            tree_unlink(aug, err_info);
     }
 
     tree_clean(finfo);
@@ -416,7 +413,7 @@ static int add_file_info(struct augeas *aug, const char *node,
     r = pathjoin(&path, 2, AUGEAS_META_TREE, node);
     ERR_NOMEM(r < 0, aug);
 
-    file = tree_find_cr(aug, path);
+    file = tree_fpath_cr(aug, path);
     ERR_BAIL(aug);
 
     /* Set 'path' */
@@ -477,6 +474,21 @@ static char *file_name_path(struct augeas *aug, const char *fname) {
     return path;
 }
 
+/* Replace the subtree for FPATH with SUB */
+static void tree_freplace(struct augeas *aug, const char *fpath,
+                         struct tree *sub) {
+    struct tree *parent;
+
+    parent = tree_fpath_cr(aug, fpath);
+    ERR_RET(aug);
+
+    tree_unlink_children(aug, parent);
+    list_append(parent->children, sub);
+    list_for_each(s, sub) {
+        s->parent = parent;
+    }
+}
+
 static int load_file(struct augeas *aug, struct lens *lens,
                      const char *lens_name, char *filename) {
     char *text = NULL;
@@ -524,7 +536,8 @@ static int load_file(struct augeas *aug, struct lens *lens,
         goto done;
     }
 
-    tree_replace(aug, path, tree);
+    tree_freplace(aug, path, tree);
+    ERR_BAIL(aug);
 
     /* top level node span entire file length */
     if (span != NULL && tree != NULL) {
@@ -575,6 +588,53 @@ static struct lens *lens_from_name(struct augeas *aug, const char *name) {
     return NULL;
 }
 
+int text_store(struct augeas *aug, const char *lens_path,
+               const char *path, const char *text) {
+    struct info *info = NULL;
+    struct lns_error *err = NULL;
+    struct tree *tree = NULL;
+    struct span *span = NULL;
+    int result = -1;
+    const char *err_status = NULL;
+    struct lens *lens = NULL;
+
+    lens = lens_from_name(aug, lens_path);
+    ERR_BAIL(aug);
+
+    make_ref(info);
+    info->first_line = 1;
+    info->last_line = 1;
+    info->first_column = 1;
+    info->last_column = strlen(text);
+
+    tree = lns_get(info, lens, text, &err);
+    if (err != NULL) {
+        err_status = "parse_failed";
+        goto error;
+    }
+
+    unref(info, info);
+
+    tree_freplace(aug, path, tree);
+    ERR_BAIL(aug);
+
+    /* top level node span entire file length */
+    if (span != NULL && tree != NULL) {
+        tree->parent->span = span;
+        tree->parent->span->span_start = 0;
+        tree->parent->span->span_end = strlen(text);
+    }
+
+    tree = NULL;
+
+    result = 0;
+ error:
+    store_error(aug, NULL, path, err_status, errno, err, text);
+    free_tree(tree);
+    free_lns_error(err);
+    return result;
+}
+
 const char *xfm_lens_name(struct tree *xfm) {
     struct tree *l = tree_child(xfm, s_lens);
 
@@ -618,7 +678,7 @@ int transform_validate(struct augeas *aug, struct tree *xfm) {
         if (streqv(t->label, "error")) {
             struct tree *del = t;
             t = del->next;
-            tree_unlink(del);
+            tree_unlink(aug, del);
         } else {
             t = t->next;
         }
@@ -648,7 +708,7 @@ void transform_file_error(struct augeas *aug, const char *status,
     va_list ap;
     int r;
 
-    err = tree_find_cr(aug, ep);
+    err = tree_fpath_cr(aug, ep);
     if (err == NULL)
         return;
 
@@ -676,7 +736,7 @@ static struct tree *file_info(struct augeas *aug, const char *fname) {
     r = pathjoin(&path, 2, AUGEAS_META_FILES, fname);
     ERR_NOMEM(r < 0, aug);
 
-    result = tree_find(aug, path);
+    result = tree_fpath(aug, path);
     ERR_BAIL(aug);
  error:
     free(path);
@@ -1149,21 +1209,34 @@ int transform_save(struct augeas *aug, struct tree *xfm,
 }
 
 int remove_file(struct augeas *aug, struct tree *tree) {
-    char *path = NULL;
-    const char *filename = NULL;
     const char *err_status = NULL;
     char *dyn_err_status = NULL;
     char *augsave = NULL, *augorig = NULL, *augorig_canon = NULL;
+    struct tree *path = NULL;
+    const char *file_path = NULL;
+    char *meta_path = NULL;
     int r;
 
-    path = path_of_tree(tree);
+    path = tree_child(tree, s_path);
     if (path == NULL) {
+        err_status = "no child called 'path' for file entry";
+        goto error;
+    }
+    file_path = path->value + strlen(AUGEAS_FILES_TREE);
+    path = NULL;
+
+    if (file_path == NULL) {
+        err_status = "no path for file";
+        goto error;
+    }
+
+    meta_path = path_of_tree(tree);
+    if (meta_path == NULL) {
         err_status = "path_of_tree";
         goto error;
     }
-    filename = path + strlen(AUGEAS_META_FILES);
 
-    if ((augorig = strappend(aug->root, filename + 1)) == NULL) {
+    if ((augorig = strappend(aug->root, file_path)) == NULL) {
         err_status = "root_file";
         goto error;
     }
@@ -1178,7 +1251,7 @@ int remove_file(struct augeas *aug, struct tree *tree) {
         }
     }
 
-    r = file_saved_event(aug, path + strlen(AUGEAS_META_TREE));
+    r = file_saved_event(aug, meta_path + strlen(AUGEAS_META_TREE));
     if (r < 0) {
         err_status = "saved_event";
         goto error;
@@ -1208,9 +1281,10 @@ int remove_file(struct augeas *aug, struct tree *tree) {
             goto error;
         }
     }
-    tree_unlink(tree);
+    path = NULL;
+    tree_unlink(aug, tree);
  done:
-    free(path);
+    free(meta_path);
     free(augorig);
     free(augorig_canon);
     free(augsave);
@@ -1219,9 +1293,9 @@ int remove_file(struct augeas *aug, struct tree *tree) {
     {
         const char *emsg =
             dyn_err_status == NULL ? err_status : dyn_err_status;
-        store_error(aug, filename, path, emsg, errno, NULL, NULL);
+        store_error(aug, file_path, meta_path, emsg, errno, NULL, NULL);
     }
-    free(path);
+    free(meta_path);
     free(augorig);
     free(augorig_canon);
     free(augsave);
diff --git a/tests/root/etc/sysconfig/network-scripts/ifcfg-weird [!] (used to fail) b/tests/root/etc/sysconfig/network-scripts/ifcfg-weird [!] (used to fail)
new file mode 100644
index 0000000..08cf100
--- /dev/null
+++ b/tests/root/etc/sysconfig/network-scripts/ifcfg-weird [!] (used to fail)	
@@ -0,0 +1,4 @@
+# This file is only here to make sure augeas handles truly bizarre
+# file names gracefully. Looking this file up in the tree will require
+# escaping all the special chars in the file name
+DEVICE=weird
diff --git a/tests/test-api.c b/tests/test-api.c
index 54b4c7d..863c7b9 100644
--- a/tests/test-api.c
+++ b/tests/test-api.c
@@ -334,6 +334,23 @@ static void testNodeInfo(CuTest *tc) {
     aug_close(aug);
 }
 
+static void testAugEscape(CuTest *tc) {
+    static const char *const in  = "a/[]b|=c()!, \td";
+    static const char *const exp = "a\\/\\[\\]b\\|\\=c\\(\\)\\!,\\ \\\td";
+    char *out;
+    struct augeas *aug;
+    int r;
+
+    aug = aug_init(root, loadpath, AUG_NO_STDINC|AUG_NO_LOAD);
+    CuAssertPtrNotNull(tc, aug);
+
+    r = aug_escape_name(aug, in, &out);
+    CuAssertRetSuccess(tc, r);
+
+    CuAssertStrEquals(tc, out, exp);
+    free(out);
+}
+
 int main(void) {
     char *output = NULL;
     CuSuite* suite = CuSuiteNew();
@@ -345,6 +362,7 @@ int main(void) {
     SUITE_ADD_TEST(suite, testDefNodeExistingMeta);
     SUITE_ADD_TEST(suite, testDefNodeCreateMeta);
     SUITE_ADD_TEST(suite, testNodeInfo);
+    SUITE_ADD_TEST(suite, testAugEscape);
 
     abs_top_srcdir = getenv("abs_top_srcdir");
     if (abs_top_srcdir == NULL)
diff --git a/tests/test-save.c b/tests/test-save.c
index 68b7736..9bdaf87 100644
--- a/tests/test-save.c
+++ b/tests/test-save.c
@@ -145,6 +145,58 @@ static void testMtime(CuTest *tc) {
     CuAssertStrNotEqual(tc, "0", mtime2);
 }
 
+/* Test that handling of 'strange' characters in path names works as
+ * expected. In particular, that paths with characters that have special
+ * meaning in path expressions are escaped properly.
+ *
+ * This test isn't all that specific to save, but since these tests set up
+ * a copy of tests/root/ that is modifiable, it was convenient to put this
+ * test here.
+ */
+static void testPathEscaping(CuTest *tc) {
+    /* Path expression with characters escaped */
+    static const char *const weird =
+        "/files/etc/sysconfig/network-scripts/ifcfg-weird\\ \\[\\!\\]\\ \\(used\\ to\\ fail\\)";
+    /* Path without any escaping */
+    static const char *const weird_no_escape =
+        "/files/etc/sysconfig/network-scripts/ifcfg-weird [!] (used to fail)";
+
+    char *fname = NULL, *s = NULL;
+    const char *v;
+    int r;
+
+    /* Construct the file name in the file system and check the file is there */
+    r = asprintf(&fname, "%s%s", root, weird_no_escape + strlen("/files"));
+    CuAssertPositive(tc, r);
+
+    r = access(fname, R_OK);
+    CuAssertIntEquals(tc, 0, r);
+
+    /* Make sure weird is in the tree */
+    r = aug_match(aug, weird, NULL);
+    CuAssertIntEquals(tc, 1, r);
+
+    /* Make sure we can get to the metadata about weird */
+    r = asprintf(&s, "/augeas%s/path", weird);
+    CuAssertPositive(tc, r);
+
+    r = aug_get(aug, s, &v);
+    CuAssertIntEquals(tc, 1, r);
+    CuAssertStrEquals(tc, weird_no_escape, v);
+
+    /* Delete it from the tree and save it; make sure it gets removed
+       from the file system */
+    r = aug_rm(aug, weird);
+    CuAssertPositive(tc, r);
+
+    r = aug_save(aug);
+    CuAssertRetSuccess(tc, r);
+
+    r = access(fname, R_OK);
+    CuAssertIntEquals(tc, -1, r);
+    CuAssertIntEquals(tc, ENOENT, errno);
+}
+
 int main(void) {
     char *output = NULL;
     CuSuite* suite = CuSuiteNew();
@@ -167,6 +219,7 @@ int main(void) {
     SUITE_ADD_TEST(suite, testNonExistentLens);
     SUITE_ADD_TEST(suite, testMultipleXfm);
     SUITE_ADD_TEST(suite, testMtime);
+    SUITE_ADD_TEST(suite, testPathEscaping);
 
     CuSuiteRun(suite);
     CuSuiteSummary(suite, &output);
diff --git a/tests/test-xpath.c b/tests/test-xpath.c
index 3a3d241..c990364 100644
--- a/tests/test-xpath.c
+++ b/tests/test-xpath.c
@@ -78,6 +78,19 @@ static char *token_to_eol(char *s, char **tok) {
     return s;
 }
 
+static char *findpath(char *s, char **p) {
+    char *t = skipws(s);
+
+    while (*s && *s != '=') s++;
+    if (s > t) {
+        s -= 1;
+        while (*s && isspace(*s)) s -= 1;
+        s += 1;
+    }
+    *p = strndup(t, s - t);
+    return s;
+}
+
 static struct test *read_tests(void) {
     char *fname;
     FILE *fp;
@@ -107,7 +120,7 @@ static struct test *read_tests(void) {
             if (ALLOC(e) < 0)
                 die("out of memory");
             list_append(t->entries, e);
-            s = token(s, &(e->path));
+            s = findpath(s, &(e->path));
             s = skipws(s);
             if (*s) {
                 if (*s != '=') {
@@ -318,6 +331,9 @@ static int run_tests(struct test *tests) {
     r = aug_defvar(aug, "localhost", "'127.0.0.1'");
     if (r != 0)
         die("aug_defvar $localhost");
+    r = aug_defvar(aug, "php", "/files/etc/php.ini");
+    if (r != 1)
+        die("aug_defvar $php");
 
     list_for_each(t, tests) {
         if (run_one_test(aug, t) < 0)
diff --git a/tests/xpath.tests b/tests/xpath.tests
index b16792a..973e154 100644
--- a/tests/xpath.tests
+++ b/tests/xpath.tests
@@ -16,7 +16,9 @@
 # results listed in the test
 #
 # The test framework sets up variables:
-#   hosts   /files/etc/hosts/*
+#   hosts      /files/etc/hosts/*
+#   localhost  '127.0.0.1'
+#   php        /files/etc/php.ini
 
 # Very simple to warm up
 test wildcard /files/etc/hosts/*/ipaddr
@@ -235,3 +237,37 @@ test union (/files/etc/yum.conf | /files/etc/yum.repos.d/*)/*/gpgcheck
      /files/etc/yum.repos.d/fedora.repo/fedora-source/gpgcheck = 1
      /files/etc/yum.repos.d/remi.repo/remi/gpgcheck = 1
      /files/etc/yum.repos.d/remi.repo/remi-test/gpgcheck = 1
+
+# Paths with whitespace in them
+test php1 $php/mail function
+     /files/etc/php.ini/mail\ function
+
+test php2 $php[mail function]
+     /files/etc/php.ini
+
+test php3 $php[count(mail function) = 1]
+     /files/etc/php.ini
+
+test php4 $php/mail function/SMTP
+     /files/etc/php.ini/mail\ function/SMTP = localhost
+
+test php5 $php/mail\ function
+     /files/etc/php.ini/mail\ function
+
+test expr-or /files/etc/group/root/*[self::gid or self::user]
+     /files/etc/group/root/gid = 0
+     /files/etc/group/root/user = root
+
+# Test matching with characters that need escaping in the filename
+test escape1 /files/etc/sysconfig/network-scripts/*
+    /files/etc/sysconfig/network-scripts/ifcfg-eth0
+    /files/etc/sysconfig/network-scripts/ifcfg-wlan0
+    /files/etc/sysconfig/network-scripts/ifcfg-weird\ \[\!\]\ \(used\ to\ fail\)
+    /files/etc/sysconfig/network-scripts/ifcfg-lo
+    /files/etc/sysconfig/network-scripts/ifcfg-br0
+
+test escape2 /files/etc/sysconfig/network-scripts/ifcfg-weird\ \[\!\]\ \(used\ to\ fail\)/DEVICE
+     /files/etc/sysconfig/network-scripts/ifcfg-weird\ \[\!\]\ \(used\ to\ fail\)/DEVICE = weird
+
+test escape3 /files/etc/sysconfig/network-scripts/*[DEVICE = 'weird']
+     /files/etc/sysconfig/network-scripts/ifcfg-weird\ \[\!\]\ \(used\ to\ fail\)
