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];