1 /*-------------------------------------------------------------------------
2  * drawElements C++ Base Library
3  * -----------------------------
4  *
5  * Copyright 2014 The Android Open Source Project
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  *
19  *//*!
20  * \file
21  * \brief Filesystem path class.
22  *//*--------------------------------------------------------------------*/
23 
24 #include "deFilePath.hpp"
25 
26 #include <vector>
27 #include <stdexcept>
28 
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 
32 #if (DE_OS == DE_OS_WIN32)
33 #	define VC_EXTRALEAN
34 #	define WIN32_LEAN_AND_MEAN
35 #	define NOMINMAX
36 #	include <windows.h>
37 #endif
38 
39 using std::string;
40 
41 namespace de
42 {
43 
44 #if (DE_OS == DE_OS_WIN32)
45 	const std::string FilePath::separator = "\\";
46 #else
47 	const std::string FilePath::separator = "/";
48 #endif
49 
FilePath(const std::vector<std::string> & components)50 FilePath::FilePath (const std::vector<std::string>& components)
51 {
52 	for (size_t ndx = 0; ndx < components.size(); ndx++)
53 	{
54 		if (!m_path.empty() && !isSeparator(m_path[m_path.size()-1]))
55 			m_path += separator;
56 		m_path += components[ndx];
57 	}
58 }
59 
split(std::vector<std::string> & components) const60 void FilePath::split (std::vector<std::string>& components) const
61 {
62 	components.clear();
63 
64 	int curCompStart = 0;
65 	int pos;
66 
67 	if (isWinNetPath())
68 		components.push_back(separator + separator);
69 	else if (isRootPath() && !beginsWithDrive())
70 		components.push_back(separator);
71 
72 	for (pos = 0; pos < (int)m_path.length(); pos++)
73 	{
74 		const char c = m_path[pos];
75 
76 		if (isSeparator(c))
77 		{
78 			if (pos - curCompStart > 0)
79 				components.push_back(m_path.substr(curCompStart, pos - curCompStart));
80 
81 			curCompStart = pos+1;
82 		}
83 	}
84 
85 	if (pos - curCompStart > 0)
86 		components.push_back(m_path.substr(curCompStart, pos - curCompStart));
87 }
88 
join(const std::vector<std::string> & components)89 FilePath FilePath::join (const std::vector<std::string>& components)
90 {
91 	return FilePath(components);
92 }
93 
normalize(void)94 FilePath& FilePath::normalize (void)
95 {
96 	std::vector<std::string>	components;
97 	std::vector<std::string>	reverseNormalizedComponents;
98 
99 	split(components);
100 
101 	m_path = "";
102 
103 	int numUp = 0;
104 
105 	// Do in reverse order and eliminate any . or .. components
106 	for (int ndx = (int)components.size()-1; ndx >= 0; ndx--)
107 	{
108 		const std::string& comp = components[ndx];
109 		if (comp == "..")
110 			numUp += 1;
111 		else if (comp == ".")
112 			continue;
113 		else if (numUp > 0)
114 			numUp -= 1; // Skip part
115 		else
116 			reverseNormalizedComponents.push_back(comp);
117 	}
118 
119 	if (isAbsolutePath() && numUp > 0)
120 		throw std::runtime_error("Cannot normalize path: invalid path");
121 
122 	// Prepend necessary ".." components
123 	while (numUp--)
124 		reverseNormalizedComponents.push_back("..");
125 
126 	if (reverseNormalizedComponents.empty() && components.back() == ".")
127 		reverseNormalizedComponents.push_back("."); // Composed of "." components only
128 
129 	*this = join(std::vector<std::string>(reverseNormalizedComponents.rbegin(), reverseNormalizedComponents.rend()));
130 
131 	return *this;
132 }
133 
normalize(const FilePath & path)134 FilePath FilePath::normalize (const FilePath& path)
135 {
136 	return FilePath(path).normalize();
137 }
138 
getBaseName(void) const139 std::string FilePath::getBaseName (void) const
140 {
141 	std::vector<std::string> components;
142 	split(components);
143 	return !components.empty() ? components[components.size()-1] : std::string("");
144 }
145 
getDirName(void) const146 std::string	FilePath::getDirName (void) const
147 {
148 	std::vector<std::string> components;
149 	split(components);
150 	if (components.size() > 1)
151 	{
152 		components.pop_back();
153 		return FilePath(components).getPath();
154 	}
155 	else if (isAbsolutePath())
156 		return separator;
157 	else
158 		return std::string(".");
159 }
160 
getFileExtension(void) const161 std::string FilePath::getFileExtension (void) const
162 {
163 	std::string baseName = getBaseName();
164 	size_t dotPos = baseName.find_last_of('.');
165 	if (dotPos == std::string::npos)
166 		return std::string("");
167 	else
168 		return baseName.substr(dotPos+1);
169 }
170 
exists(void) const171 bool FilePath::exists (void) const
172 {
173 	FilePath	normPath	= FilePath::normalize(*this);
174 	struct		stat		st;
175 	int			result		= stat(normPath.getPath(), &st);
176 	return result == 0;
177 }
178 
getType(void) const179 FilePath::Type FilePath::getType (void) const
180 {
181 	FilePath	normPath	= FilePath::normalize(*this);
182 	struct		stat		st;
183 	int			result		= stat(normPath.getPath(), &st);
184 
185 	if (result != 0)
186 		return TYPE_UNKNOWN;
187 
188 	int type = st.st_mode & S_IFMT;
189 	if (type == S_IFREG)
190 		return TYPE_FILE;
191 	else if (type == S_IFDIR)
192 		return TYPE_DIRECTORY;
193 	else
194 		return TYPE_UNKNOWN;
195 }
196 
beginsWithDrive(void) const197 bool FilePath::beginsWithDrive (void) const
198 {
199 	for (int ndx = 0; ndx < (int)m_path.length(); ndx++)
200 	{
201 		if (m_path[ndx] == ':' && ndx+1 < (int)m_path.length() && isSeparator(m_path[ndx+1]))
202 			return true; // First part is drive letter.
203 		if (isSeparator(m_path[ndx]))
204 			return false;
205 	}
206 	return false;
207 }
208 
isAbsolutePath(void) const209 bool FilePath::isAbsolutePath (void) const
210 {
211 	return isRootPath() || isWinNetPath() || beginsWithDrive();
212 }
213 
FilePath_selfTest(void)214 void FilePath_selfTest (void)
215 {
216 	DE_TEST_ASSERT(!FilePath(".").isAbsolutePath());
217 	DE_TEST_ASSERT(!FilePath("..\\foo").isAbsolutePath());
218 	DE_TEST_ASSERT(!FilePath("foo").isAbsolutePath());
219 	DE_TEST_ASSERT(FilePath("\\foo/bar").isAbsolutePath());
220 	DE_TEST_ASSERT(FilePath("/foo").isAbsolutePath());
221 	DE_TEST_ASSERT(FilePath("\\").isAbsolutePath());
222 	DE_TEST_ASSERT(FilePath("\\\\net\\loc").isAbsolutePath());
223 	DE_TEST_ASSERT(FilePath("C:\\file.txt").isAbsolutePath());
224 	DE_TEST_ASSERT(FilePath("c:/file.txt").isAbsolutePath());
225 
226 	DE_TEST_ASSERT(string(".") == FilePath(".//.").normalize().getPath());
227 	DE_TEST_ASSERT(string(".") == FilePath(".").normalize().getPath());
228 	DE_TEST_ASSERT((string("..") + FilePath::separator + "test") == FilePath("foo/../bar/../../test").normalize().getPath());
229 	DE_TEST_ASSERT((FilePath::separator + "foo" + FilePath::separator + "foo.txt") == FilePath("/foo\\bar/..\\dir\\..\\foo.txt").normalize().getPath());
230 	DE_TEST_ASSERT((string("c:") + FilePath::separator + "foo" + FilePath::separator + "foo.txt") == FilePath("c:/foo\\bar/..\\dir\\..\\foo.txt").normalize().getPath());
231 	DE_TEST_ASSERT((FilePath::separator + FilePath::separator + "foo" + FilePath::separator + "foo.txt") == FilePath("\\\\foo\\bar/..\\dir\\..\\foo.txt").normalize().getPath());
232 
233 	DE_TEST_ASSERT(FilePath("foo/bar"		).getBaseName()	== "bar");
234 	DE_TEST_ASSERT(FilePath("foo/bar/"		).getBaseName()	== "bar");
235 	DE_TEST_ASSERT(FilePath("foo\\bar"		).getBaseName()	== "bar");
236 	DE_TEST_ASSERT(FilePath("foo\\bar\\"	).getBaseName()	== "bar");
237 	DE_TEST_ASSERT(FilePath("foo/bar"		).getDirName()	== "foo");
238 	DE_TEST_ASSERT(FilePath("foo/bar/"		).getDirName()	== "foo");
239 	DE_TEST_ASSERT(FilePath("foo\\bar"		).getDirName()	== "foo");
240 	DE_TEST_ASSERT(FilePath("foo\\bar\\"	).getDirName()	== "foo");
241 	DE_TEST_ASSERT(FilePath("/foo/bar/baz"	).getDirName()	== FilePath::separator + "foo" + FilePath::separator + "bar");
242 }
243 
createDirectoryImpl(const char * path)244 static void createDirectoryImpl (const char* path)
245 {
246 #if (DE_OS == DE_OS_WIN32)
247 	if (!CreateDirectory(path, DE_NULL))
248 		throw std::runtime_error("Failed to create directory");
249 #elif (DE_OS == DE_OS_UNIX) || (DE_OS == DE_OS_OSX) || (DE_OS == DE_OS_IOS) || (DE_OS == DE_OS_ANDROID) || (DE_OS == DE_OS_SYMBIAN)
250 	if (mkdir(path, 0777) != 0)
251 		throw std::runtime_error("Failed to create directory");
252 #else
253 #	error Implement createDirectoryImpl() for your platform.
254 #endif
255 }
256 
createDirectory(const char * path)257 void createDirectory (const char* path)
258 {
259 	FilePath	dirPath		= FilePath::normalize(path);
260 	FilePath	parentPath	(dirPath.getDirName());
261 
262 	if (dirPath.exists())
263 		throw std::runtime_error("Destination exists already");
264 	else if (!parentPath.exists())
265 		throw std::runtime_error("Parent directory doesn't exist");
266 	else if (parentPath.getType() != FilePath::TYPE_DIRECTORY)
267 		throw std::runtime_error("Parent is not directory");
268 
269 	createDirectoryImpl(path);
270 }
271 
createDirectoryAndParents(const char * path)272 void createDirectoryAndParents (const char* path)
273 {
274 	std::vector<std::string>	createPaths;
275 	FilePath					curPath		(path);
276 
277 	if (curPath.exists())
278 		throw std::runtime_error("Destination exists already");
279 
280 	while (!curPath.exists())
281 	{
282 		createPaths.push_back(curPath.getPath());
283 
284 		std::string parent = curPath.getDirName();
285 		DE_CHECK_RUNTIME_ERR(parent != curPath.getPath());
286 		curPath = FilePath(parent);
287 	}
288 
289 	// Create in reverse order.
290 	for (std::vector<std::string>::const_reverse_iterator parentIter = createPaths.rbegin(); parentIter != createPaths.rend(); parentIter++)
291 		createDirectory(parentIter->c_str());
292 }
293 
294 } // de
295