[Libguestfs] [PATCH v6 24/41] daemon: Reimplement ‘case_sensitive_path’ API in OCaml.

Richard W.M. Jones
Thu, 15 Jun 2017 17:06:14 -0000

---
 daemon/Makefile.am        |   2 +
 daemon/realpath.c         | 187 ----------------------------------------------
 daemon/realpath.ml        |  83 ++++++++++++++++++++
 daemon/realpath.mli       |  19 +++++
 generator/actions_core.ml |   1 +
 5 files changed, 105 insertions(+), 187 deletions(-)

diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 7e06daa02..e49d78dd4 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -268,6 +268,7 @@ SOURCES_MLI = \
 	mount.mli \
 	mountable.mli \
 	parted.mli \
+	realpath.mli \
 	utils.mli

 SOURCES_ML = \
@@ -283,6 +284,7 @@ SOURCES_ML = \
 	link.ml \
 	mount.ml \
 	parted.ml \
+	realpath.ml \
 	callbacks.ml \
 	daemon.ml

diff --git a/daemon/realpath.c b/daemon/realpath.c
index 24ab133e2..f9d22d28d 100644
--- a/daemon/realpath.c
+++ b/daemon/realpath.c
@@ -48,190 +48,3 @@ do_realpath (const char *path)

   return ret;			/* caller frees */
 }
-
-static int find_path_element (int fd_cwd, int is_end, const char *name, char
**name_ret);
-
-char *
-do_case_sensitive_path (const char *path)
-{
-  size_t next;
-  int fd_cwd, fd2, err, is_end;
-  char *ret;
-
-  ret = strdup ("/");
-  if (ret == NULL) {
-    reply_with_perror ("strdup");
-    return NULL;
-  }
-  next = 1; /* next position in 'ret' buffer */
-
-  /* 'fd_cwd' here is a surrogate for the current working directory, so
-   * that we don't have to actually call chdir(2).
-   */
-  fd_cwd = open (sysroot, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
-  if (fd_cwd == -1) {
-    reply_with_perror ("%s", sysroot);
-    goto error;
-  }
-
-  /* First character is a '/'.  Take each subsequent path element
-   * and follow it.
-   */
-  while (*path) {
-    char *t;
-    size_t i, len;
-    CLEANUP_FREE char *name_in = NULL, *name_out = NULL;
-
-    i = strcspn (path, "/");
-    if (i == 0) {
-      path++;
-      continue;
-    }
-
-    if ((i == 1 && path[0] == '.') ||
-        (i == 2 && path[0] == '.' && path[1] ==
'.')) {
-      reply_with_error ("path contained . or .. elements");
-      goto error;
-    }
-
-    name_in = strndup (path, i);
-    if (name_in == NULL) {
-      reply_with_perror ("strdup");
-      goto error;
-    }
-
-    /* Skip to next element in path (for the next loop iteration). */
-    path += i;
-    is_end = *path == 0;
-
-    /* Read the current directory looking (case insensitively) for
-     * this element of the path.  This replaces 'name' with the
-     * correct case version.
-     */
-    if (find_path_element (fd_cwd, is_end, name_in, &name_out) == -1)
-      goto error;
-    len = strlen (name_out);
-
-    /* Add the real name of this path element to the return value. */
-    if (next > 1)
-      ret[next++] = '/';
-
-    t = realloc (ret, next+len+1);
-    if (t == NULL) {
-      reply_with_perror ("realloc");
-      goto error;
-    }
-    ret = t;
-
-    strcpy (&ret[next], name_out);
-    next += len;
-
-    /* Is it a directory?  Try going into it. */
-    fd2 = openat (fd_cwd, name_out, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
-    err = errno;
-    close (fd_cwd);
-    fd_cwd = fd2;
-    errno = err;
-    if (fd_cwd == -1) {
-      /* Some errors are OK provided we've reached the end of the path. */
-      if (is_end && (errno == ENOTDIR || errno == ENOENT))
-        break;
-
-      reply_with_perror ("openat: %s", name_out);
-      goto error;
-    }
-  }
-
-  if (fd_cwd >= 0)
-    close (fd_cwd);
-
-  return ret;                   /* caller frees */
-
- error:
-  if (fd_cwd >= 0)
-    close (fd_cwd);
-  free (ret);
-
-  return NULL;
-}
-
-/* 'fd_cwd' is a file descriptor pointing to an open directory.
- * 'name' is the path element to search for.  'is_end' is a
flag
- * indicating if this is the last path element.
- *
- * We search the directory looking for a path element that case
- * insensitively matches 'name', returning the actual name in
'*name_ret'.
- *
- * If this is successful, return 0.  If it fails, reply with an error
- * and return -1.
- */
-static int
-find_path_element (int fd_cwd, int is_end, const char *name, char **name_ret)
-{
-  int fd2;
-  DIR *dir;
-  struct dirent *d;
-
-  fd2 = dup_cloexec (fd_cwd); /* because closedir will close it */
-  if (fd2 == -1) {
-    reply_with_perror ("dup");
-    return -1;
-  }
-  dir = fdopendir (fd2);
-  if (dir == NULL) {
-    reply_with_perror ("opendir");
-    close (fd2);
-    return -1;
-  }
-
-  for (;;) {
-    errno = 0;
-    d = readdir (dir);
-    if (d == NULL)
-      break;
-    if (STRCASEEQ (d->d_name, name))
-      break;
-  }
-
-  if (d == NULL && errno != 0) {
-    reply_with_perror ("readdir");
-    closedir (dir);
-    return -1;
-  }
-
-  if (d == NULL && is_end) {
-    /* Last path element: return it as-is, assuming that the user will
-     * create a new file or directory (RHBZ#840115).
-     */
-    closedir (dir);
-    *name_ret = strdup (name);
-    if (*name_ret == NULL) {
-      reply_with_perror ("strdup");
-      return -1;
-    }
-    return 0;
-  }
-
-  if (d == NULL) {
-    reply_with_error ("%s: no file or directory found with this
name", name);
-    closedir (dir);
-    return -1;
-  }
-
-  *name_ret = strdup (d->d_name);
-  if (*name_ret == NULL) {
-    reply_with_perror ("strdup");
-    closedir (dir);
-    return -1;
-  }
-
-  /* NB: closedir frees the structure associated with 'd', so we must
-   * do this last.
-   */
-  if (closedir (dir) == -1) {
-    reply_with_perror ("closedir");
-    return -1;
-  }
-
-  return 0;
-}
diff --git a/daemon/realpath.ml b/daemon/realpath.ml
new file mode 100644
index 000000000..cffe86322
--- /dev/null
+++ b/daemon/realpath.ml
@@ -0,0 +1,83 @@
+(* guestfs-inspection
+ * Copyright (C) 2009-2017 Red Hat 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 of the License, 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+open Printf
+
+open Std_utils
+
+(* The infamous case_sensitive_path function, which works around
+ * the bug in ntfs-3g that all paths are case sensitive even though
+ * the underlying filesystem is case insensitive.
+ *)
+let rec case_sensitive_path path +  let elems = String.nsplit "/"
path in
+
+  (* The caller ensures that the first element of [path] is [/],
+   * and therefore the first element of the split list must be
+   * empty.
+   *)
+  assert (List.length elems > 0);
+  assert (List.hd elems = "");
+  let elems = List.tl elems in
+
+  let sysroot = Sysroot.sysroot () in
+  let chroot = Chroot.create sysroot
+                             ~name:(sprintf "case_sensitive_path: %s"
path) in
+
+  (* Now we iterate down the tree starting at the sysroot. *)
+  let elems +    Chroot.f chroot (
+      fun () ->
+        let rec loop = function
+          | [] -> []
+          | [ "."|".." ] ->
+             failwithf "path contains \".\" or \"..\"
elements"
+          | "" :: elems ->
+             (* For compatibility with C implementation, we ignore
+              * "//" in the middle of the path.
+              *)
+             loop elems
+          | [ file ] ->
+             (* If it's the final element, it's allowed to be missing.
*)
+             (match find_path_element file with
+              | None -> [ file ] (* return the original *)
+              | Some file -> [ file ]
+             );
+          | elem :: elems ->
+             (match find_path_element elem with
+              | None ->
+                 failwithf "%s: not found" elem
+              | Some elem ->
+                 (* This will fail intentionally if not a directory. *)
+                 Unix.chdir elem;
+                 elem :: loop elems
+             )
+        in
+        loop elems
+    ) () in
+
+  (* Reconstruct the case sensitive path. *)
+  "/" ^ String.concat "/" elems
+
+and find_path_element name +  let dir = Sys.readdir "." in
+  let dir = Array.to_list dir in
+  let lc_name = String.lowercase_ascii name in
+  let cmp n = String.lowercase_ascii n = lc_name in
+  try Some (List.find cmp dir)
+  with Not_found -> None
diff --git a/daemon/realpath.mli b/daemon/realpath.mli
new file mode 100644
index 000000000..371e619fc
--- /dev/null
+++ b/daemon/realpath.mli
@@ -0,0 +1,19 @@
+(* guestfs-inspection
+ * Copyright (C) 2009-2017 Red Hat 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 of the License, 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+val case_sensitive_path : string -> string
diff --git a/generator/actions_core.ml b/generator/actions_core.ml
index 4bf0c7b70..54d0a6ca8 100644
--- a/generator/actions_core.ml
+++ b/generator/actions_core.ml
@@ -4797,6 +4797,7 @@ The result list is not sorted.
   { defaults with
     name = "case_sensitive_path"; added = (1, 0, 75);
     style = RString (RPlainString, "rpath"), [String (Pathname,
"path")], [];
+    impl = OCaml "Realpath.case_sensitive_path";
     tests = [
       InitISOFS, Always, TestResultString (
         [["case_sensitive_path"; "/DIRECTORY"]],
"/directory"), [];
--
2.13.0