Merge "Reimplement realpath." am: 8fe1fcd804 am: 46997e68ed am: 5e7cafbfc6

Change-Id: If2125d96c94cc80169249c854768fd9975520fd7
diff --git a/libc/Android.bp b/libc/Android.bp
index c764cb9..f1cca49 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -359,7 +359,6 @@
     defaults: ["libc_defaults"],
     srcs: [
         "upstream-freebsd/lib/libc/gen/glob.c",
-        "upstream-freebsd/lib/libc/stdlib/realpath.c",
     ],
 
     cflags: [
@@ -1179,6 +1178,7 @@
         "bionic/raise.cpp",
         "bionic/rand.cpp",
         "bionic/readlink.cpp",
+        "bionic/realpath.cpp",
         "bionic/reboot.cpp",
         "bionic/recv.cpp",
         "bionic/rename.cpp",
diff --git a/libc/NOTICE b/libc/NOTICE
index 51b00ad..87d39c9 100644
--- a/libc/NOTICE
+++ b/libc/NOTICE
@@ -3809,34 +3809,6 @@
 
 -------------------------------------------------------------------
 
-Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-1. Redistributions of source code must retain the above copyright
-   notice, this list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright
-   notice, this list of conditions and the following disclaimer in the
-   documentation and/or other materials provided with the distribution.
-3. The names of the authors may not be used to endorse or promote
-   products derived from this software without specific prior written
-   permission.
-
-THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGE.
-
--------------------------------------------------------------------
-
 Copyright (c) 2003 Dag-Erling Smørgrav
 All rights reserved.
 
diff --git a/libc/bionic/realpath.cpp b/libc/bionic/realpath.cpp
new file mode 100644
index 0000000..e43d8e2
--- /dev/null
+++ b/libc/bionic/realpath.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "private/FdPath.h"
+#include "private/ScopedFd.h"
+
+// This function needs a 4KiB (PATH_MAX) buffer.
+// The alternative is to heap allocate and then trim, but that's 2x the code.
+// (Remember that readlink(2) won't tell you the needed size, so the multi-pass
+// algorithm isn't even an option unless you want to just guess, in which case
+// you're back needing to trim again.)
+#pragma GCC diagnostic ignored "-Wframe-larger-than="
+
+char* realpath(const char* path, char* result) {
+  // Weird special case.
+  if (!path) {
+    errno = EINVAL;
+    return nullptr;
+  }
+
+  // Get an O_PATH fd, and...
+  ScopedFd fd(open(path, O_PATH | O_CLOEXEC));
+  if (fd.get() == -1) return nullptr;
+
+  // (...remember the device/inode that we're talking about and...)
+  struct stat sb;
+  if (fstat(fd.get(), &sb) == -1) return nullptr;
+  dev_t st_dev = sb.st_dev;
+  ino_t st_ino = sb.st_ino;
+
+  // ...ask the kernel to do the hard work for us.
+  FdPath fd_path(fd.get());
+  char dst[PATH_MAX];
+  ssize_t l = readlink(fd_path.c_str(), dst, sizeof(dst) - 1);
+  if (l == -1) return nullptr;
+  dst[l] = '\0';
+
+  // What if the file was removed in the meantime? readlink(2) will have
+  // returned "/a/b/c (deleted)", and we want to return ENOENT instead.
+  if (stat(dst, &sb) == -1 || st_dev != sb.st_dev || st_ino != sb.st_ino) {
+    errno = ENOENT;
+    return nullptr;
+  }
+
+  return result ? strcpy(result, dst) : strdup(dst);
+}
diff --git a/libc/upstream-freebsd/lib/libc/stdlib/realpath.c b/libc/upstream-freebsd/lib/libc/stdlib/realpath.c
deleted file mode 100644
index c4bd953..0000000
--- a/libc/upstream-freebsd/lib/libc/stdlib/realpath.c
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. The names of the authors may not be used to endorse or promote
- *    products derived from this software without specific prior written
- *    permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#if defined(LIBC_SCCS) && !defined(lint)
-static char sccsid[] = "@(#)realpath.c	8.1 (Berkeley) 2/16/94";
-#endif /* LIBC_SCCS and not lint */
-#include <sys/cdefs.h>
-__FBSDID("$FreeBSD$");
-
-#include "namespace.h"
-#include <sys/param.h>
-#include <sys/stat.h>
-
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include "un-namespace.h"
-
-/*
- * Find the real name of path, by removing all ".", ".." and symlink
- * components.  Returns (resolved) on success, or (NULL) on failure,
- * in which case the path which caused trouble is left in (resolved).
- */
-char *
-realpath(const char * __restrict path, char * __restrict resolved)
-{
-	struct stat sb;
-	char *p, *q, *s;
-	size_t left_len, resolved_len;
-	unsigned symlinks;
-	int m, slen;
-	char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX];
-
-	if (path == NULL) {
-		errno = EINVAL;
-		return (NULL);
-	}
-	if (path[0] == '\0') {
-		errno = ENOENT;
-		return (NULL);
-	}
-	if (resolved == NULL) {
-		resolved = malloc(PATH_MAX);
-		if (resolved == NULL)
-			return (NULL);
-		m = 1;
-	} else
-		m = 0;
-	symlinks = 0;
-	if (path[0] == '/') {
-		resolved[0] = '/';
-		resolved[1] = '\0';
-		if (path[1] == '\0')
-			return (resolved);
-		resolved_len = 1;
-		left_len = strlcpy(left, path + 1, sizeof(left));
-	} else {
-		if (getcwd(resolved, PATH_MAX) == NULL) {
-			if (m)
-				free(resolved);
-			else {
-				resolved[0] = '.';
-				resolved[1] = '\0';
-			}
-			return (NULL);
-		}
-		resolved_len = strlen(resolved);
-		left_len = strlcpy(left, path, sizeof(left));
-	}
-	if (left_len >= sizeof(left) || resolved_len >= PATH_MAX) {
-		if (m)
-			free(resolved);
-		errno = ENAMETOOLONG;
-		return (NULL);
-	}
-
-	/*
-	 * Iterate over path components in `left'.
-	 */
-	while (left_len != 0) {
-		/*
-		 * Extract the next path component and adjust `left'
-		 * and its length.
-		 */
-		p = strchr(left, '/');
-		s = p ? p : left + left_len;
-		if (s - left >= sizeof(next_token)) {
-			if (m)
-				free(resolved);
-			errno = ENAMETOOLONG;
-			return (NULL);
-		}
-		memcpy(next_token, left, s - left);
-		next_token[s - left] = '\0';
-		left_len -= s - left;
-		if (p != NULL)
-			memmove(left, s + 1, left_len + 1);
-		if (resolved[resolved_len - 1] != '/') {
-			if (resolved_len + 1 >= PATH_MAX) {
-				if (m)
-					free(resolved);
-				errno = ENAMETOOLONG;
-				return (NULL);
-			}
-			resolved[resolved_len++] = '/';
-			resolved[resolved_len] = '\0';
-		}
-		if (next_token[0] == '\0') {
-			/* Handle consequential slashes. */
-			continue;
-		}
-		else if (strcmp(next_token, ".") == 0)
-			continue;
-		else if (strcmp(next_token, "..") == 0) {
-			/*
-			 * Strip the last path component except when we have
-			 * single "/"
-			 */
-			if (resolved_len > 1) {
-				resolved[resolved_len - 1] = '\0';
-				q = strrchr(resolved, '/') + 1;
-				*q = '\0';
-				resolved_len = q - resolved;
-			}
-			continue;
-		}
-
-		/*
-		 * Append the next path component and lstat() it.
-		 */
-		resolved_len = strlcat(resolved, next_token, PATH_MAX);
-		if (resolved_len >= PATH_MAX) {
-			if (m)
-				free(resolved);
-			errno = ENAMETOOLONG;
-			return (NULL);
-		}
-		if (lstat(resolved, &sb) != 0) {
-			if (m)
-				free(resolved);
-			return (NULL);
-		}
-		if (S_ISLNK(sb.st_mode)) {
-			if (symlinks++ > MAXSYMLINKS) {
-				if (m)
-					free(resolved);
-				errno = ELOOP;
-				return (NULL);
-			}
-			slen = readlink(resolved, symlink, sizeof(symlink) - 1);
-			if (slen < 0) {
-				if (m)
-					free(resolved);
-				return (NULL);
-			}
-			symlink[slen] = '\0';
-			if (symlink[0] == '/') {
-				resolved[1] = 0;
-				resolved_len = 1;
-			} else if (resolved_len > 1) {
-				/* Strip the last path component. */
-				resolved[resolved_len - 1] = '\0';
-				q = strrchr(resolved, '/') + 1;
-				*q = '\0';
-				resolved_len = q - resolved;
-			}
-
-			/*
-			 * If there are any path components left, then
-			 * append them to symlink. The result is placed
-			 * in `left'.
-			 */
-			if (p != NULL) {
-				if (symlink[slen - 1] != '/') {
-					if (slen + 1 >= sizeof(symlink)) {
-						if (m)
-							free(resolved);
-						errno = ENAMETOOLONG;
-						return (NULL);
-					}
-					symlink[slen] = '/';
-					symlink[slen + 1] = 0;
-				}
-				left_len = strlcat(symlink, left,
-				    sizeof(symlink));
-				if (left_len >= sizeof(left)) {
-					if (m)
-						free(resolved);
-					errno = ENAMETOOLONG;
-					return (NULL);
-				}
-			}
-			left_len = strlcpy(left, symlink, sizeof(left));
-		} else if (!S_ISDIR(sb.st_mode) && p != NULL) {
-			if (m)
-				free(resolved);
-			errno = ENOTDIR;
-			return (NULL);
-		}
-	}
-
-	/*
-	 * Remove trailing slash except when the resolved pathname
-	 * is a single "/".
-	 */
-	if (resolved_len > 1 && resolved[resolved_len - 1] == '/')
-		resolved[resolved_len - 1] = '\0';
-	return (resolved);
-}
diff --git a/tests/stdlib_test.cpp b/tests/stdlib_test.cpp
index c12ae6b..3f1ec86 100644
--- a/tests/stdlib_test.cpp
+++ b/tests/stdlib_test.cpp
@@ -29,6 +29,7 @@
 #include <limits>
 #include <string>
 
+#include <android-base/file.h>
 #include <android-base/macros.h>
 #include <gtest/gtest.h>
 
@@ -36,6 +37,8 @@
 #include "math_data_test.h"
 #include "utils.h"
 
+using namespace std::string_literals;
+
 template <typename T = int (*)(char*)>
 class GenericTemporaryFile {
  public:
@@ -322,6 +325,17 @@
   ASSERT_EQ(ENOENT, errno);
 }
 
+TEST(stdlib, realpath__ELOOP) {
+  TemporaryDir td;
+  std::string link = std::string(td.path) + "/loop";
+  ASSERT_EQ(0, symlink(link.c_str(), link.c_str()));
+
+  errno = 0;
+  char* p = realpath(link.c_str(), nullptr);
+  ASSERT_TRUE(p == nullptr);
+  ASSERT_EQ(ELOOP, errno);
+}
+
 TEST(stdlib, realpath__component_after_non_directory) {
   errno = 0;
   char* p = realpath("/dev/null/.", nullptr);
@@ -350,6 +364,47 @@
   free(p);
 }
 
+TEST(stdlib, realpath__dot) {
+  char* p = realpath("/proc/./version", nullptr);
+  ASSERT_STREQ("/proc/version", p);
+  free(p);
+}
+
+TEST(stdlib, realpath__dot_dot) {
+  char* p = realpath("/dev/../proc/version", nullptr);
+  ASSERT_STREQ("/proc/version", p);
+  free(p);
+}
+
+TEST(stdlib, realpath__deleted) {
+  TemporaryDir td;
+
+  // Create a file "A".
+  std::string A_path = td.path + "/A"s;
+  ASSERT_TRUE(android::base::WriteStringToFile("test\n", A_path));
+
+  // Get an O_PATH fd for it.
+  android::base::unique_fd fd(open(A_path.c_str(), O_PATH));
+  ASSERT_NE(fd, -1);
+
+  // Create a file "A (deleted)".
+  android::base::unique_fd fd2(open((td.path + "/A (deleted)"s).c_str(),
+                                    O_CREAT | O_TRUNC | O_WRONLY, 0644));
+  ASSERT_NE(fd2, -1);
+
+  // Delete "A".
+  ASSERT_EQ(0, unlink(A_path.c_str()));
+
+  // Now realpath() on the O_PATH fd, and check we *don't* get "A (deleted)".
+  std::string path = android::base::StringPrintf("/proc/%d/fd/%d", static_cast<int>(getpid()),
+                                                 fd.get());
+  errno = 0;
+  char* result = realpath(path.c_str(), nullptr);
+  ASSERT_EQ(nullptr, result) << result;
+  ASSERT_EQ(ENOENT, errno);
+  free(result);
+}
+
 TEST(stdlib, qsort) {
   struct s {
     char name[16];