1 /**
2  * \file   path_helpers.c
3  * \brief  Helper functions for path_getrelative.c and path_getabsolute.c
4  */
5 
6 #include "premake.h"
7 #include <string.h>
8 
9 #define USE_DRIVE_LETTERS PLATFORM_WINDOWS
10 
has_drive_letter(const char * path)11 static int has_drive_letter(const char * path)
12 {
13 #if USE_DRIVE_LETTERS
14 	char ch = path[0];
15 	if (((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) && path[1] == ':')
16 		return 1;
17 #else
18 	(void)path;
19 #endif // USE_DRIVE_LETTERS
20 
21 	return 0;
22 }
23 
is_absolute_path(const char * path)24 int is_absolute_path(const char * path)
25 {
26 	char ch = path[0];
27 	if (ch == '\0')
28 	{
29 		return 0;
30 	}
31 
32 	if (ch == '/' || ch == '\\' || ch == '$')
33 	{
34 		return 1;
35 	}
36 
37 	if (has_drive_letter(path))
38 	{
39 		return 1;
40 	}
41 
42 	return 0;
43 }
44 
copy_current_directory(char * buffer,int bufsize)45 static char *copy_current_directory(char * buffer, int bufsize)
46 {
47 #if PLATFORM_WINDOWS
48 	int len = GetCurrentDirectory(bufsize, buffer);
49 	int i;
50 	for (i = 0; i < len; i++)
51 		buffer[i] = (buffer[i] == '\\' ? '/' : buffer[i]);
52 	return buffer + len;
53 #else
54 	char *ptr = getcwd(buffer, bufsize);
55 	int len = ptr ? strlen(ptr) : 0;
56 	return buffer + len;
57 #endif
58 }
59 
60 #if USE_DRIVE_LETTERS
copy_current_drive_letter(char * buffer,int bufsize)61 static char *copy_current_drive_letter(char * buffer, int bufsize)
62 {
63 	int len = GetCurrentDirectory(bufsize, buffer);
64 	if (len >= 2 && buffer[1] == ':') {
65 		buffer[2] = '\0';
66 		return buffer + 2;
67 	}
68 	else {
69 		buffer[0] = '\0';
70 		return buffer;
71 	}
72 }
73 #endif // USE_DRIVE_LETTERS
74 
backtrack(char * path,char * start)75 static char *backtrack(char *path, char *start)
76 {
77 	for (; path > start; --path) {
78 		if (*path == '/') {
79 			*path = '\0';
80 			return path;
81 		}
82 #if USE_DRIVE_LETTERS
83 		else if (*path == ':' && (path == start + 1)) {
84 			path[1] = '/';
85 			path[2] = '\0';
86 			return path + 2;
87 		}
88 #endif
89 	}
90 
91 	start[0] = '/';
92 	start[1] = '\0';
93 	return start + 1;
94 }
95 
concat(char * path,const char * pathTerm,const char * token,const char * tokenTerm)96 static char *concat(char *path, const char *pathTerm, const char *token, const char *tokenTerm)
97 {
98 	while (path < pathTerm && token < tokenTerm)
99 		*path++ = *token++;
100 	return path;
101 }
102 
copy(const char str[],char * buffer,int bufsize)103 static char *copy(const char str[], char *buffer, int bufsize)
104 {
105 	int i = 0;
106 	for (; str[i] && i < bufsize - 1; i++)
107 		buffer[i] = str[i];
108 	buffer[i] = '\0';
109 	return buffer + i;
110 }
111 
get_absolute_path(const char path[],char * buffer,int bufsize)112 char *get_absolute_path(const char path[], char *buffer, int bufsize)
113 {
114 	// There must be room for at least a drive letter, colon and slash.
115 	if (bufsize < 4)
116 		return 0;
117 
118 	char *dst  = buffer;
119 	char *term = buffer + bufsize - 1;
120 
121 	// If the path started as relative, prepend the current working directory
122 	if (!is_absolute_path(path))
123 	{
124 		dst = copy_current_directory(buffer, bufsize);
125 	}
126 #if USE_DRIVE_LETTERS
127 	// Add drive letter to absolute paths without one
128 	else if (!has_drive_letter(path) && path[0] != '$')
129 	{
130 		dst = copy_current_drive_letter(buffer, bufsize);
131 	}
132 #endif
133 
134 	// Process the path one segment at a time.
135 	int segment = 0;
136 	const char *src   = path;
137 	const char *token = src;
138 	for (; dst < term; ++src) {
139 		if (*src == '/' || *src == '\\' || *src == '\0') {
140 			int tokenlen = (int)(src - token);
141 			if (tokenlen == 0) {
142 				if (segment == 0)
143 					*dst++ = '/';
144 			}
145 			else if (tokenlen == 1 && token[0] == '.') {
146 				// do nothing
147 			}
148 			else if (tokenlen == 2 && token[0] == '.' && token[1] == '.') {
149 				dst = backtrack(dst - 1, buffer);
150 			}
151 			else {
152 				if (dst > buffer && dst[-1] != '/')
153 					*dst++ = '/';
154 				dst = concat(dst, term, token, src);
155 			}
156 			token = src + 1;
157 			++segment;
158 			if (*src == '\0')
159 				break;
160 		}
161 	}
162 
163 #if USE_DRIVE_LETTERS
164 	// Make sure drive letter is lowercase
165 	buffer[0] = (char)tolower(buffer[0]);
166 #endif
167 
168 	// Chop trailing slash
169 	if (dst > buffer && dst[-1] == '/')
170 		--dst;
171 
172 	// Add a null terminator.
173 	if (dst < term)
174 		*dst = '\0';
175 	else
176 		*--dst = '\0';
177 
178 	return dst;
179 }
180 
get_relative_path(const char src[],const char dst[],char * buffer,int bufsize)181 char *get_relative_path(const char src[], const char dst[], char *buffer, int bufsize)
182 {
183 	// There must be room for at least a drive letter, colon and slash.
184 	if (bufsize < 4)
185 		return 0;
186 
187 	// If the dst path starts with $, return it as an absolute path.
188 	if (dst[0] == '$')
189 		return copy(dst, buffer, bufsize);
190 
191 	// Get the absolute source and destination paths.
192 	char srcBuf[PATH_BUFSIZE];
193 	char dstBuf[PATH_BUFSIZE];
194 	char *srcEnd = get_absolute_path(src, srcBuf, PATH_BUFSIZE - 1);
195 	char *dstEnd = get_absolute_path(dst, dstBuf, PATH_BUFSIZE - 1);
196 
197 #if USE_DRIVE_LETTERS
198 	// If the paths have different drive letters, return the absolute path.
199 	if (srcBuf[0] != dstBuf[0])
200 		return copy(dstBuf, buffer, bufsize);
201 #endif
202 
203 	// Append '/' to the end of each path.
204 	*srcEnd++ = '/'; *srcEnd = '\0';
205 	*dstEnd++ = '/'; *dstEnd = '\0';
206 
207 	// Find the shared path prefix of the src and dst paths.
208 	int lastSlash = -1;
209 	int i;
210 	for (i = 0; srcBuf[i] == dstBuf[i]; i++) {
211 		if (srcBuf[i] == '/') {
212 			lastSlash = i;
213 		}
214 		else if (srcBuf[i] == '\0') {
215 			// Paths are identical, so return '.'
216 			buffer[0] = '.';
217 			buffer[1] = '\0';
218 			return buffer + 1;
219 		}
220 	}
221 
222 	// For each remaining path segment in src after the last shared slash,
223 	// append '../' to the result.
224 	char *resultPtr  = buffer;
225 	char *resultTerm = buffer + bufsize;
226 	const char *ptr;
227 	for (ptr = srcBuf + lastSlash + 1; *ptr; ++ptr) {
228 		if (*ptr == '/' && resultPtr + 3 < resultTerm) {
229 			resultPtr[0] = '.';
230 			resultPtr[1] = '.';
231 			resultPtr[2] = '/';
232 			resultPtr += 3;
233 		}
234 	}
235 
236 	// Append the portion of the destination path beyond the last shared
237 	// slash.
238 	char *dstPtr;
239 	for (dstPtr = dstBuf + lastSlash + 1; *dstPtr && resultPtr < resultTerm; )
240 		*resultPtr++ = *dstPtr++;
241 
242 	// Remove the trailing slash.
243 	if (resultPtr[-1] == '/')
244 		--resultPtr;
245 
246 	// Add a null terminator.
247 	if (resultPtr < resultTerm)
248 		*resultPtr = '\0';
249 	else
250 		*--resultPtr = '\0';
251 
252 	return resultPtr;
253 }
254