Use stat to check that paths are equivalent
[alexxy/gromacs.git] / src / gromacs / utility / path.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2011,2012,2013,2014, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements functions in path.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_utility
41  */
42 #include "gmxpre.h"
43
44 #include "path.h"
45
46 #include "config.h"
47
48 #include <cctype>
49 #include <cerrno>
50 #include <cstdlib>
51 #include <cstring>
52
53 #include <algorithm>
54
55 #include <sys/stat.h>
56
57 #ifdef GMX_NATIVE_WINDOWS
58 #include <Windows.h>
59 #include <direct.h>
60 #else
61 #ifdef HAVE_UNISTD_H
62 #include <unistd.h>
63 #endif
64 #endif
65
66 #include "gromacs/utility/exceptions.h"
67 #include "gromacs/utility/futil.h"
68 #include "gromacs/utility/stringutil.h"
69
70 namespace
71 {
72
73 //! Directory separator to use when joining paths.
74 const char cDirSeparator = '/';
75 //! Directory separators to use when parsing paths.
76 const char cDirSeparators[] = "/\\";
77 /*! \var cPathSeparator
78  * \brief
79  * Separator to use to split the PATH environment variable.
80  *
81  * When reading the PATH environment variable, Unix separates entries
82  * with colon, while windows uses semicolon.
83  */
84 #ifdef GMX_NATIVE_WINDOWS
85 const char cPathSeparator = ';';
86 #else
87 const char cPathSeparator = ':';
88 #endif
89
90 //! Check whether a given character is a directory separator.
91 bool isDirSeparator(char chr)
92 {
93     return std::strchr(cDirSeparators, chr);
94 }
95
96 } // namespace
97
98 namespace gmx
99 {
100
101 /********************************************************************
102  * Path
103  */
104
105 bool Path::containsDirectory(const std::string &path)
106 {
107     return path.find_first_of(cDirSeparators) != std::string::npos;
108 }
109
110 /* Check if the program name begins with "/" on unix/cygwin, or
111  * with "\" or "X:\" on windows. If not, the program name
112  * is relative to the current directory.
113  */
114 bool Path::isAbsolute(const char *path)
115 {
116     if (isDirSeparator(path[0]))
117     {
118         return true;
119     }
120 #ifdef GMX_NATIVE_WINDOWS
121     return path[0] != '\0' && path[1] == ':' && isDirSeparator(path[2]);
122 #else
123     return false;
124 #endif
125 }
126
127 bool Path::isAbsolute(const std::string &path)
128 {
129     return isAbsolute(path.c_str());
130 }
131
132 #ifdef GMX_NATIVE_WINDOWS
133 namespace
134 {
135 struct handle_wrapper
136 {
137     HANDLE handle;
138     handle_wrapper(HANDLE h)
139         : handle(h){}
140     ~handle_wrapper()
141     {
142         if (handle != INVALID_HANDLE_VALUE)
143         {
144             ::CloseHandle(handle);
145         }
146     }
147 };
148 }
149 #endif
150
151 bool Path::isEquivalent(const std::string &path1, const std::string &path2)
152 {
153     //based on boost_1_56_0/libs/filesystem/src/operations.cpp under BSL
154 #ifdef GMX_NATIVE_WINDOWS
155     // Note well: Physical location on external media is part of the
156     // equivalence criteria. If there are no open handles, physical location
157     // can change due to defragmentation or other relocations. Thus handles
158     // must be held open until location information for both paths has
159     // been retrieved.
160
161     // p2 is done first, so any error reported is for p1
162     // FixME: #1635
163     handle_wrapper h2(
164             CreateFile(
165                     path2.c_str(),
166                     0,
167                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
168                     0,
169                     OPEN_EXISTING,
170                     FILE_FLAG_BACKUP_SEMANTICS,
171                     0));
172
173     handle_wrapper h1(
174             CreateFile(
175                     path1.c_str(),
176                     0,
177                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
178                     0,
179                     OPEN_EXISTING,
180                     FILE_FLAG_BACKUP_SEMANTICS,
181                     0));
182
183     if (h1.handle == INVALID_HANDLE_VALUE
184         || h2.handle == INVALID_HANDLE_VALUE)
185     {
186         // if one is invalid and the other isn't, then they aren't equivalent,
187         // but if both are invalid then it is an error
188         if (h1.handle == INVALID_HANDLE_VALUE
189             && h2.handle == INVALID_HANDLE_VALUE)
190         {
191             GMX_THROW(FileIOError("Path::isEquivalent called with two invalid files"));
192         }
193
194         return false;
195     }
196
197     // at this point, both handles are known to be valid
198
199     BY_HANDLE_FILE_INFORMATION info1, info2;
200
201     if (!GetFileInformationByHandle(h1.handle, &info1))
202     {
203         GMX_THROW(FileIOError("Path::isEquivalent: GetFileInformationByHandle failed"));
204     }
205
206     if (!GetFileInformationByHandle(h2.handle, &info2))
207     {
208         GMX_THROW(FileIOError("Path::isEquivalent: GetFileInformationByHandle failed"));
209     }
210
211     // In theory, volume serial numbers are sufficient to distinguish between
212     // devices, but in practice VSN's are sometimes duplicated, so last write
213     // time and file size are also checked.
214     return
215         info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
216         && info1.nFileIndexHigh == info2.nFileIndexHigh
217         && info1.nFileIndexLow == info2.nFileIndexLow
218         && info1.nFileSizeHigh == info2.nFileSizeHigh
219         && info1.nFileSizeLow == info2.nFileSizeLow
220         && info1.ftLastWriteTime.dwLowDateTime
221         == info2.ftLastWriteTime.dwLowDateTime
222         && info1.ftLastWriteTime.dwHighDateTime
223         == info2.ftLastWriteTime.dwHighDateTime;
224 #else
225     struct stat s1, s2;
226     int         e2 = stat(path2.c_str(), &s2);
227     int         e1 = stat(path1.c_str(), &s1);
228
229     if (e1 != 0 || e2 != 0)
230     {
231         // if one is invalid and the other isn't then they aren't equivalent,
232         // but if both are invalid then it is an error.
233         if (e1 != 0 && e2 != 0)
234         {
235             GMX_THROW_WITH_ERRNO(
236                     FileIOError("Path::isEquivalent called with two invalid files"),
237                     "stat", errno);
238         }
239         return false;
240     }
241
242     // both stats now known to be valid
243     return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino
244            // According to the POSIX stat specs, "The st_ino and st_dev fields
245            // taken together uniquely identify the file within the system."
246            // Just to be sure, size and mod time are also checked.
247            && s1.st_size == s2.st_size && s1.st_mtime == s2.st_mtime;
248 #endif
249 }
250
251 std::string Path::join(const std::string &path1,
252                        const std::string &path2)
253 {
254     // TODO: Remove extra separators if they are present in the input paths.
255     return path1 + cDirSeparator + path2;
256 }
257
258
259 std::string Path::join(const std::string &path1,
260                        const std::string &path2,
261                        const std::string &path3)
262 {
263     // TODO: Remove extra separators if they are present in the input paths.
264     return path1 + cDirSeparator + path2 + cDirSeparator + path3;
265 }
266
267 std::string Path::getParentPath(const std::string &path)
268 {
269     /* Expects that the path doesn't contain "." or "..". If used on a path for
270      * which this isn't guaranteed realpath needs to be called first. */
271     size_t pos = path.find_last_of(cDirSeparators);
272     if (pos == std::string::npos)
273     {
274         return std::string();
275     }
276     return path.substr(0, pos);
277 }
278
279 std::string Path::getFilename(const std::string &path)
280 {
281     size_t pos = path.find_last_of(cDirSeparators);
282     if (pos == std::string::npos)
283     {
284         return path;
285     }
286     return path.substr(pos+1);
287 }
288
289 bool Path::hasExtension(const std::string &path)
290 {
291     return getFilename(path).find('.') != std::string::npos;
292 }
293
294 std::string Path::stripExtension(const std::string &path)
295 {
296     size_t dirSeparatorPos = path.find_last_of(cDirSeparators);
297     size_t extPos          = path.find_last_of('.');
298     if (extPos == std::string::npos
299         || (dirSeparatorPos != std::string::npos && extPos < dirSeparatorPos))
300     {
301         return path;
302     }
303     return path.substr(0, extPos);
304 }
305
306 std::string Path::normalize(const std::string &path)
307 {
308     std::string result(path);
309     if (DIR_SEPARATOR != '/')
310     {
311         std::replace(result.begin(), result.end(), '/', DIR_SEPARATOR);
312     }
313     return result;
314 }
315
316 bool Path::exists(const char *path)
317 {
318     return gmx_fexist(path);
319 }
320
321 bool Path::exists(const std::string &path)
322 {
323     return exists(path.c_str());
324 }
325
326 std::string Path::getWorkingDirectory()
327 {
328     // TODO: Use exceptions instead of gmx_fatal().
329     char cwd[GMX_PATH_MAX];
330     gmx_getcwd(cwd, sizeof(cwd));
331     return cwd;
332 }
333
334 void Path::splitPathEnvironment(const std::string        &pathEnv,
335                                 std::vector<std::string> *result)
336 {
337     size_t prevPos = 0;
338     size_t separator;
339     do
340     {
341         separator = pathEnv.find(cPathSeparator, prevPos);
342         result->push_back(pathEnv.substr(prevPos, separator - prevPos));
343         prevPos = separator + 1;
344     }
345     while (separator != std::string::npos);
346 }
347
348 std::vector<std::string> Path::getExecutablePaths()
349 {
350     std::vector<std::string> result;
351 #ifdef GMX_NATIVE_WINDOWS
352     // Add the local dir since it is not in the path on Windows.
353     result.push_back("");
354 #endif
355     const char *path = std::getenv("PATH");
356     if (path != NULL)
357     {
358         splitPathEnvironment(path, &result);
359     }
360     return result;
361 }
362
363 std::string Path::resolveSymlinks(const std::string &path)
364 {
365     /* Does not fully resolve the path like realpath/boost::canonical would.
366      * It doesn't resolve path elements (including "." or ".."), but only
367      * resolves the entire path (it does that recursively). */
368     std::string result(path);
369 #ifndef GMX_NATIVE_WINDOWS
370     char        buf[GMX_PATH_MAX];
371     int         length;
372     while ((length = readlink(result.c_str(), buf, sizeof(buf)-1)) > 0)
373     {
374         buf[length] = '\0';
375         if (isAbsolute(buf))
376         {
377             result = buf;
378         }
379         else
380         {
381             result = join(getParentPath(result), buf);
382         }
383     }
384 #endif
385     return result;
386 }
387
388
389 /********************************************************************
390  * Directory
391  */
392
393 int Directory::create(const char *path)
394 {
395     if (Directory::exists(path))
396     {
397         return 0;
398     }
399 #ifdef GMX_NATIVE_WINDOWS
400     if (_mkdir(path))
401 #else
402     if (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IWOTH) != 0)
403 #endif
404     {
405         // TODO: Proper error handling.
406         return -1;
407     }
408     return 0;
409 }
410
411
412 int Directory::create(const std::string &path)
413 {
414     return create(path.c_str());
415 }
416
417
418 bool Directory::exists(const char *path)
419 {
420     struct stat info;
421     if (stat(path, &info) != 0)
422     {
423         if (errno != ENOENT && errno != ENOTDIR)
424         {
425             // TODO: Proper error handling.
426         }
427         return false;
428     }
429 #ifdef GMX_NATIVE_WINDOWS
430     return ((_S_IFDIR & info.st_mode) != 0);
431 #else
432     return S_ISDIR(info.st_mode);
433 #endif
434 }
435
436
437 bool Directory::exists(const std::string &path)
438 {
439     return exists(path.c_str());
440 }
441
442 } // namespace gmx