Make android::base::Basename() safe. The previous "thread safety" was technically correct, but not super useful in practice --- multiple calls to this function were safe, but you couldn't mix android::base::Basename() and basename(3). This actually hit us in practice when system_server leaked enough fds for fdtrack to start up, which meant that libc calls that created fds would request a backtrace, which meant that libunwind_stack would call android::base::Basename(), which would call basename(3), which would clobber a previous call to basename(3) in the original function that made the otherwise innocuous libc call (realpath(3), in this case): it was as if realpath(3) clobbered basename(3)'s storage! I'm not a huge fan of this particular basename_r() implementation with its gotos, but it's way too late in the T release cycle to be inventing new implementations for a widely-used function. Sadly there's no basename_r() for LP64 -- which, hilariously, is my fault -- so copy & paste it is! I've left mingw with the old implementation. I've no idea how much of that mingw actually needs, so it can probably be cleaned up, but that's a worry for another day. Bug: http://b/231951809 Test: treehugger Change-Id: I58a4c18d7943014ffdac4fd8185977b65b3ba1f7 (cherry picked from commit 91a10d912827b818d0c1931ede3a2afaa93b18cd) Merged-In: I58a4c18d7943014ffdac4fd8185977b65b3ba1f7
diff --git a/file.cpp b/file.cpp index 97cc2b2..a580dcc 100644 --- a/file.cpp +++ b/file.cpp
@@ -468,18 +468,19 @@ return Dirname(GetExecutablePath()); } +#if defined(_WIN32) std::string Basename(const std::string& path) { + // TODO: how much of this is actually necessary for mingw? + // Copy path because basename may modify the string passed in. std::string result(path); -#if !defined(__BIONIC__) // Use lock because basename() may write to a process global and return a // pointer to that. Note that this locking strategy only works if all other // callers to basename in the process also grab this same lock, but its // better than nothing. Bionic's basename returns a thread-local buffer. static std::mutex& basename_lock = *new std::mutex(); std::lock_guard<std::mutex> lock(basename_lock); -#endif // Note that if std::string uses copy-on-write strings, &str[0] will cause // the copy to be made, so there is no chance of us accidentally writing to @@ -492,6 +493,65 @@ return result; } +#else +// Copied from bionic so that Basename() below can be portable and thread-safe. +static int __basename_r(const char* path, char* buffer, size_t buffer_size) { + const char* startp = nullptr; + const char* endp = nullptr; + int len; + int result; + + // Empty or NULL string gets treated as ".". + if (path == nullptr || *path == '\0') { + startp = "."; + len = 1; + goto Exit; + } + + // Strip trailing slashes. + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') { + endp--; + } + + // All slashes becomes "/". + if (endp == path && *endp == '/') { + startp = "/"; + len = 1; + goto Exit; + } + + // Find the start of the base. + startp = endp; + while (startp > path && *(startp - 1) != '/') { + startp--; + } + + len = endp - startp +1; + + Exit: + result = len; + if (buffer == nullptr) { + return result; + } + if (len > static_cast<int>(buffer_size) - 1) { + len = buffer_size - 1; + result = -1; + errno = ERANGE; + } + + if (len >= 0) { + memcpy(buffer, startp, len); + buffer[len] = 0; + } + return result; +} +std::string Basename(const std::string& path) { + char buf[PATH_MAX]; + __basename_r(path.c_str(), buf, sizeof(buf)); + return buf; +} +#endif std::string Dirname(const std::string& path) { // Copy path because dirname may modify the string passed in.
diff --git a/file_test.cpp b/file_test.cpp index 120228d..c739664 100644 --- a/file_test.cpp +++ b/file_test.cpp
@@ -333,6 +333,17 @@ EXPECT_EQ("sh", android::base::Basename("/system/bin/sh")); EXPECT_EQ("sh", android::base::Basename("sh")); EXPECT_EQ("sh", android::base::Basename("/system/bin/sh/")); + + // Since we've copy & pasted bionic's implementation, copy & paste the tests. + EXPECT_EQ(".", android::base::Basename("")); + EXPECT_EQ("lib", android::base::Basename("/usr/lib")); + EXPECT_EQ("usr", android::base::Basename("/usr/")); + EXPECT_EQ("usr", android::base::Basename("usr")); + EXPECT_EQ("/", android::base::Basename("/")); + EXPECT_EQ(".", android::base::Basename(".")); + EXPECT_EQ("..", android::base::Basename("..")); + EXPECT_EQ("/", android::base::Basename("///")); + EXPECT_EQ("lib", android::base::Basename("//usr//lib//")); } TEST(file, Dirname) {