1 /*
2 * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. The names of the authors may not be used to endorse or promote
13 * products derived from this software without specific prior written
14 * permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include "readlink.h"
30
31 #include <string>
32
33 #include <errno.h>
34 #include <sys/param.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37
38 /**
39 * This differs from realpath(3) mainly in its behavior when a path element does not exist or can
40 * not be searched. realpath(3) treats that as an error and gives up, but we have Java-compatible
41 * behavior where we just assume the path element was not a symbolic link. This leads to a textual
42 * treatment of ".." from that point in the path, which may actually lead us back to a path we
43 * can resolve (as in "/tmp/does-not-exist/../blah.txt" which would be an error for realpath(3)
44 * but "/tmp/blah.txt" under the traditional Java interpretation).
45 *
46 * This implementation also removes all the fixed-length buffers of the C original.
47 */
realpath(const char * path,std::string & resolved)48 bool realpath(const char* path, std::string& resolved) {
49 // 'path' must be an absolute path.
50 if (path[0] != '/') {
51 errno = EINVAL;
52 return false;
53 }
54
55 resolved = "/";
56 if (path[1] == '\0') {
57 return true;
58 }
59
60 // Iterate over path components in 'left'.
61 int symlinkCount = 0;
62 std::string left(path + 1);
63 while (!left.empty()) {
64 // Extract the next path component.
65 size_t nextSlash = left.find('/');
66 std::string nextPathComponent = left.substr(0, nextSlash);
67 if (nextSlash != std::string::npos) {
68 left.erase(0, nextSlash + 1);
69 } else {
70 left.clear();
71 }
72 if (nextPathComponent.empty()) {
73 continue;
74 } else if (nextPathComponent == ".") {
75 continue;
76 } else if (nextPathComponent == "..") {
77 // Strip the last path component except when we have single "/".
78 if (resolved.size() > 1) {
79 resolved.erase(resolved.rfind('/'));
80 }
81 continue;
82 }
83
84 // Append the next path component.
85 if (resolved[resolved.size() - 1] != '/') {
86 resolved += '/';
87 }
88 resolved += nextPathComponent;
89
90 // See if we've got a symbolic link, and resolve it if so.
91 struct stat sb;
92 if (lstat(resolved.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode)) {
93 if (symlinkCount++ > MAXSYMLINKS) {
94 errno = ELOOP;
95 return false;
96 }
97
98 std::string symlink;
99 if (!readlink(resolved.c_str(), symlink)) {
100 return false;
101 }
102 if (symlink[0] == '/') {
103 // The symbolic link is absolute, so we need to start from scratch.
104 resolved = "/";
105 } else if (resolved.size() > 1) {
106 // The symbolic link is relative, so we just lose the last path component (which
107 // was the link).
108 resolved.erase(resolved.rfind('/'));
109 }
110
111 if (!left.empty()) {
112 const char* maybeSlash = (symlink[symlink.size() - 1] != '/') ? "/" : "";
113 left = symlink + maybeSlash + left;
114 } else {
115 left = symlink;
116 }
117 }
118 }
119
120 // Remove trailing slash except when the resolved pathname is a single "/".
121 if (resolved.size() > 1 && resolved[resolved.size() - 1] == '/') {
122 resolved.erase(resolved.size() - 1, 1);
123 }
124 return true;
125 }
126