From 0bbb9b7331aa21072bd51641883fecdcc9f9b7f9 Mon Sep 17 00:00:00 2001 From: Roland Schulz Date: Tue, 26 May 2015 11:21:40 -0700 Subject: [PATCH] Use stat to check that paths are equivalent Replaces the previous approach of comparing path as string. The previous approach didn't work reliable on case insenstive filesystems or with symlinks. Change-Id: Iee11c172db89b98a26b3592be2f285110a85a632 --- .../commandline/cmdlineprogramcontext.cpp | 10 +- src/gromacs/fileio/path.cpp | 134 ++++++++++++++++-- src/gromacs/fileio/path.h | 6 +- src/gromacs/utility/exceptions.h | 3 +- 4 files changed, 137 insertions(+), 16 deletions(-) diff --git a/src/gromacs/commandline/cmdlineprogramcontext.cpp b/src/gromacs/commandline/cmdlineprogramcontext.cpp index 0a24deacbd..36f11815c8 100644 --- a/src/gromacs/commandline/cmdlineprogramcontext.cpp +++ b/src/gromacs/commandline/cmdlineprogramcontext.cpp @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2012,2013,2014, by the GROMACS development team, led by + * Copyright (c) 2012,2013,2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -252,7 +252,13 @@ std::string findDefaultLibraryDataPath(const std::string &binaryPath) // If running directly from the build tree, try to use the source // directory. #if (defined CMAKE_SOURCE_DIR && defined CMAKE_BINARY_DIR) - if (Path::startsWith(searchPath, CMAKE_BINARY_DIR)) + std::string buildBinPath; +#ifdef CMAKE_INTDIR /*In multi-configuration build systems the output subdirectory*/ + buildBinPath = Path::join(CMAKE_BINARY_DIR, "bin", CMAKE_INTDIR); +#else + buildBinPath = Path::join(CMAKE_BINARY_DIR, "bin"); +#endif + if (Path::isEquivalent(searchPath, buildBinPath)) { std::string testPath = Path::join(CMAKE_SOURCE_DIR, "share/top"); if (isAcceptableLibraryPath(testPath)) diff --git a/src/gromacs/fileio/path.cpp b/src/gromacs/fileio/path.cpp index b9db04d7a1..e3e5715456 100644 --- a/src/gromacs/fileio/path.cpp +++ b/src/gromacs/fileio/path.cpp @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2011,2012,2013,2014, by the GROMACS development team, led by + * Copyright (c) 2011,2012,2013,2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -52,6 +52,7 @@ #include #ifdef GMX_NATIVE_WINDOWS +#include #include #else #ifdef HAVE_UNISTD_H @@ -60,6 +61,7 @@ #endif #include "gromacs/fileio/futil.h" +#include "gromacs/utility/exceptions.h" #include "gromacs/utility/stringutil.h" namespace @@ -124,9 +126,123 @@ bool Path::isAbsolute(const std::string &path) return isAbsolute(path.c_str()); } -bool Path::startsWith(const std::string &path, const std::string &prefix) +#ifdef GMX_NATIVE_WINDOWS +namespace +{ +struct handle_wrapper +{ + HANDLE handle; + handle_wrapper(HANDLE h) + : handle(h){} + ~handle_wrapper() + { + if (handle != INVALID_HANDLE_VALUE) + { + ::CloseHandle(handle); + } + } +}; +} +#endif + +bool Path::isEquivalent(const std::string &path1, const std::string &path2) { - return gmx::startsWith(normalize(path), normalize(prefix)); + //based on boost_1_56_0/libs/filesystem/src/operations.cpp under BSL +#ifdef GMX_NATIVE_WINDOWS + // Note well: Physical location on external media is part of the + // equivalence criteria. If there are no open handles, physical location + // can change due to defragmentation or other relocations. Thus handles + // must be held open until location information for both paths has + // been retrieved. + + // p2 is done first, so any error reported is for p1 + // FixME: #1635 + handle_wrapper h2( + CreateFile( + path2.c_str(), + 0, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + 0, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + 0)); + + handle_wrapper h1( + CreateFile( + path1.c_str(), + 0, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + 0, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + 0)); + + if (h1.handle == INVALID_HANDLE_VALUE + || h2.handle == INVALID_HANDLE_VALUE) + { + // if one is invalid and the other isn't, then they aren't equivalent, + // but if both are invalid then it is an error + if (h1.handle == INVALID_HANDLE_VALUE + && h2.handle == INVALID_HANDLE_VALUE) + { + GMX_THROW(FileIOError("Path::isEquivalent called with two invalid files")); + } + + return false; + } + + // at this point, both handles are known to be valid + + BY_HANDLE_FILE_INFORMATION info1, info2; + + if (!GetFileInformationByHandle(h1.handle, &info1)) + { + GMX_THROW(FileIOError("Path::isEquivalent: GetFileInformationByHandle failed")); + } + + if (!GetFileInformationByHandle(h2.handle, &info2)) + { + GMX_THROW(FileIOError("Path::isEquivalent: GetFileInformationByHandle failed")); + } + + // In theory, volume serial numbers are sufficient to distinguish between + // devices, but in practice VSN's are sometimes duplicated, so last write + // time and file size are also checked. + return + info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber + && info1.nFileIndexHigh == info2.nFileIndexHigh + && info1.nFileIndexLow == info2.nFileIndexLow + && info1.nFileSizeHigh == info2.nFileSizeHigh + && info1.nFileSizeLow == info2.nFileSizeLow + && info1.ftLastWriteTime.dwLowDateTime + == info2.ftLastWriteTime.dwLowDateTime + && info1.ftLastWriteTime.dwHighDateTime + == info2.ftLastWriteTime.dwHighDateTime; +#else + struct stat s1, s2; + int e2 = stat(path2.c_str(), &s2); + int e1 = stat(path1.c_str(), &s1); + + if (e1 != 0 || e2 != 0) + { + // if one is invalid and the other isn't then they aren't equivalent, + // but if both are invalid then it is an error. + if (e1 != 0 && e2 != 0) + { + GMX_THROW_WITH_ERRNO( + FileIOError("Path::isEquivalent called with two invalid files"), + "stat", errno); + } + return false; + } + + // both stats now known to be valid + return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino + // According to the POSIX stat specs, "The st_ino and st_dev fields + // taken together uniquely identify the file within the system." + // Just to be sure, size and mod time are also checked. + && s1.st_size == s2.st_size && s1.st_mtime == s2.st_mtime; +#endif } std::string Path::join(const std::string &path1, @@ -147,6 +263,8 @@ std::string Path::join(const std::string &path1, std::string Path::getParentPath(const std::string &path) { + /* Expects that the path doesn't contain "." or "..". If used on a path for + * which this isn't guaranteed realpath needs to be called first. */ size_t pos = path.find_last_of(cDirSeparators); if (pos == std::string::npos) { @@ -168,17 +286,10 @@ std::string Path::getFilename(const std::string &path) std::string Path::normalize(const std::string &path) { std::string result(path); - // TODO: Remove . and .. entries. if (DIR_SEPARATOR != '/') { std::replace(result.begin(), result.end(), '/', DIR_SEPARATOR); } -#ifdef GMX_NATIVE_WINDOWS - if (std::isalpha(result[0]) && result[1] == ':') - { - result[0] = std::toupper(result[0]); - } -#endif return result; } @@ -231,6 +342,9 @@ std::vector Path::getExecutablePaths() std::string Path::resolveSymlinks(const std::string &path) { + /* Does not fully resolve the path like realpath/boost::canonical would. + * It doesn't resolve path elements (including "." or ".."), but only + * resolves the entire path (it does that recursively). */ std::string result(path); #ifndef GMX_NATIVE_WINDOWS char buf[GMX_PATH_MAX]; diff --git a/src/gromacs/fileio/path.h b/src/gromacs/fileio/path.h index b900676d73..4114c72380 100644 --- a/src/gromacs/fileio/path.h +++ b/src/gromacs/fileio/path.h @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2011,2012,2013,2014, by the GROMACS development team, led by + * Copyright (c) 2011,2012,2013,2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -56,8 +56,8 @@ class Path static bool containsDirectory(const std::string &path); static bool isAbsolute(const char *path); static bool isAbsolute(const std::string &path); - static bool startsWith(const std::string &path, - const std::string &prefix); + static bool isEquivalent(const std::string &path1, + const std::string &path2); static std::string join(const std::string &path1, const std::string &path2); diff --git a/src/gromacs/utility/exceptions.h b/src/gromacs/utility/exceptions.h index ace7b66224..e8670abcd1 100644 --- a/src/gromacs/utility/exceptions.h +++ b/src/gromacs/utility/exceptions.h @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2011,2012,2013,2014, by the GROMACS development team, led by + * Copyright (c) 2011,2012,2013,2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -398,6 +398,7 @@ class NotImplementedError : public APIError GMX_THROW((e) << boost::errinfo_errno(stored_errno_) \ << boost::errinfo_api_function(syscall)); \ } while (0) +//TODO: Add an equivalent macro for Windows GetLastError /*! \brief * Formats a standard fatal error message for reporting an exception. -- 2.22.0