1/**
2 * This file has no copyright assigned and is placed in the Public Domain.
3 * This file is part of the mingw-w64 runtime package.
4 * No warranty is given; refer to the file DISCLAIMER.PD within this package.
5 */
6#ifndef WIN32_LEAN_AND_MEAN
7#define WIN32_LEAN_AND_MEAN
8#endif
9#include <stdlib.h>
10#include <locale.h>
11#include <libgen.h>
12#include <windows.h>
13
14/* A 'directory separator' is a byte that equals 0x2F ('solidus' or more
15 * commonly 'forward slash') or 0x5C ('reverse solidus' or more commonly
16 * 'backward slash'). The byte 0x5C may look different from a backward slash
17 * in some locales; for example, it looks the same as a Yen sign in Japanese
18 * locales and a Won sign in Korean locales. Despite its appearance, it still
19 * functions as a directory separator.
20 *
21 * A 'path' comprises an optional DOS drive letter with a colon, and then an
22 * arbitrary number of possibily empty components, separated by non-empty
23 * sequences of directory separators (in other words, consecutive directory
24 * separators are treated as a single one). A path that comprises an empty
25 * component denotes the current working directory.
26 *
27 * An 'absolute path' comprises at least two components, the first of which
28 * is empty.
29 *
30 * A 'relative path' is a path that is not an absolute path. In other words,
31 * it either comprises an empty component, or begins with a non-empty
32 * component.
33 *
34 * POSIX doesn't have a concept about DOS drives. A path that does not have a
35 * drive letter starts from the same drive as the current working directory.
36 *
37 * For example:
38 * (Examples without drive letters match POSIX.)
39 *
40 * Argument dirname() returns basename() returns
41 * -------- ----------------- ------------------
42 * `` or NULL `.` `.`
43 * `usr` `.` `usr`
44 * `usr\` `.` `usr`
45 * `\` `\` `\`
46 * `\usr` `\` `usr`
47 * `\usr\lib` `\usr` `lib`
48 * `\home\\dwc\\test` `\home\\dwc` `test`
49 * `\\host\usr` `\\host\.` `usr`
50 * `\\host\usr\lib` `\\host\usr` `lib`
51 * `\\host\\usr` `\\host\\` `usr`
52 * `\\host\\usr\lib` `\\host\\usr` `lib`
53 * `C:` `C:.` `.`
54 * `C:usr` `C:.` `usr`
55 * `C:usr\` `C:.` `usr`
56 * `C:\` `C:\` `\`
57 * `C:\\` `C:\` `\`
58 * `C:\\\` `C:\` `\`
59 * `C:\usr` `C:\` `usr`
60 * `C:\usr\lib` `C:\usr` `lib`
61 * `C:\\usr\\lib\\` `C:\\usr` `lib`
62 * `C:\home\\dwc\\test` `C:\home\\dwc` `test`
63 */
64
65struct path_info
66 {
67 /* This points to end of the UNC prefix and drive letter, if any. */
68 char* prefix_end;
69
70 /* These point to the directory separator in front of the last non-empty
71 * component. */
72 char* base_sep_begin;
73 char* base_sep_end;
74
75 /* This points to the last directory separator sequence if no other
76 * non-separator characters follow it. */
77 char* term_sep_begin;
78
79 /* This points to the end of the string. */
80 char* path_end;
81 };
82
83#define IS_DIR_SEP(c) ((c) == '/' || (c) == '\\')
84
85static
86void
87do_get_path_info(struct path_info* info, char* path)
88 {
89 char* pos = path;
90 int unc_ncoms = 0;
91 DWORD cp;
92 int dbcs_tb, prev_dir_sep, dir_sep;
93
94 /* Get the code page for paths in the same way as `fopen()`. */
95 cp = __mingw_filename_cp();
96
97 /* Set the structure to 'no data'. */
98 info->prefix_end = NULL;
99 info->base_sep_begin = NULL;
100 info->base_sep_end = NULL;
101 info->term_sep_begin = NULL;
102
103 if(IS_DIR_SEP(pos[0]) && IS_DIR_SEP(pos[1])) {
104 /* The path is UNC. */
105 pos += 2;
106
107 /* Seek to the end of the share/device name. */
108 dbcs_tb = 0;
109 prev_dir_sep = 0;
110
111 while(*pos != 0) {
112 dir_sep = 0;
113
114 if(dbcs_tb)
115 dbcs_tb = 0;
116 else if(__mingw_isleadbyte_cp(*pos, cp))
117 dbcs_tb = 1;
118 else
119 dir_sep = IS_DIR_SEP(*pos);
120
121 /* If a separator has been encountered and the previous character
122 * was not, mark this as the end of the current component. */
123 if(dir_sep && !prev_dir_sep) {
124 unc_ncoms ++;
125
126 /* The first component is the host name, and the second is the
127 * share name. So we stop at the end of the second component. */
128 if(unc_ncoms == 2)
129 break;
130 }
131
132 prev_dir_sep = dir_sep;
133 pos ++;
134 }
135
136 /* The UNC prefix terminates here. The terminating directory separator
137 * is not part of the prefix, and initiates a new absolute path. */
138 info->prefix_end = pos;
139 }
140 else if((pos[0] >= 'A' && pos[0] <= 'Z' && pos[1] == ':')
141 || (pos[0] >= 'a' && pos[0] <= 'z' && pos[1] == ':')) {
142 /* The path contains a DOS drive letter in the beginning. */
143 pos += 2;
144
145 /* The DOS drive prefix terminates here. Unlike UNC paths, the remaing
146 * part can be relative. For example, `C:foo` denotes `foo` in the
147 * working directory of drive `C:`. */
148 info->prefix_end = pos;
149 }
150
151 /* The remaining part of the path is almost the same as POSIX. */
152 dbcs_tb = 0;
153 prev_dir_sep = 0;
154
155 while(*pos != 0) {
156 dir_sep = 0;
157
158 if(dbcs_tb)
159 dbcs_tb = 0;
160 else if(__mingw_isleadbyte_cp(*pos, cp))
161 dbcs_tb = 1;
162 else
163 dir_sep = IS_DIR_SEP(*pos);
164
165 /* If a separator has been encountered and the previous character
166 * was not, mark this as the beginning of the terminating separator
167 * sequence. */
168 if(dir_sep && !prev_dir_sep)
169 info->term_sep_begin = pos;
170
171 /* If a non-separator character has been encountered and a previous
172 * terminating separator sequence exists, start a new component. */
173 if(!dir_sep && prev_dir_sep) {
174 info->base_sep_begin = info->term_sep_begin;
175 info->base_sep_end = pos;
176 info->term_sep_begin = NULL;
177 }
178
179 prev_dir_sep = dir_sep;
180 pos ++;
181 }
182
183 /* Store the end of the path for convenience. */
184 info->path_end = pos;
185 }
186
187char*
188dirname(char* path)
189 {
190 struct path_info info;
191 char* upath;
192 const char* top;
193 static char* static_path_copy;
194
195 if(path == NULL || path[0] == 0)
196 return (char*) ".";
197
198 do_get_path_info(&info, path);
199 upath = info.prefix_end ? info.prefix_end : path;
200 top = (IS_DIR_SEP(path[0]) || IS_DIR_SEP(upath[0])) ? "\\" : ".";
201
202 /* If a non-terminating directory separator exists, it terminates the
203 * dirname. Truncate the path there. */
204 if(info.base_sep_begin) {
205 info.base_sep_begin[0] = 0;
206
207 /* If the unprefixed path has not been truncated to empty, it is now
208 * the dirname, so return it. */
209 if(upath[0])
210 return path;
211 }
212
213 /* The dirname is empty. In principle we return `<prefix>.` if the
214 * path is relative and `<prefix>\` if it is absolute. This can be
215 * optimized if there is no prefix. */
216 if(upath == path)
217 return (char*) top;
218
219 /* When there is a prefix, we must append a character to the prefix.
220 * If there is enough room in the original path, we just reuse its
221 * storage. */
222 if(upath != info.path_end) {
223 upath[0] = *top;
224 upath[1] = 0;
225 return path;
226 }
227
228 /* This is only the last resort. If there is no room, we have to copy
229 * the prefix elsewhere. */
230 upath = realloc(static_path_copy, info.prefix_end - path + 2);
231 if(!upath)
232 return (char*) top;
233
234 static_path_copy = upath;
235 memcpy(upath, path, info.prefix_end - path);
236 upath += info.prefix_end - path;
237 upath[0] = *top;
238 upath[1] = 0;
239 return static_path_copy;
240 }
241
242char*
243basename(char* path)
244 {
245 struct path_info info;
246 char* upath;
247
248 if(path == NULL || path[0] == 0)
249 return (char*) ".";
250
251 do_get_path_info(&info, path);
252 upath = info.prefix_end ? info.prefix_end : path;
253
254 /* If the path is non-UNC and empty, then it's relative. POSIX says '.'
255 * shall be returned. */
256 if(IS_DIR_SEP(path[0]) == 0 && upath[0] == 0)
257 return (char*) ".";
258
259 /* If a terminating separator sequence exists, it is not part of the
260 * name and shall be truncated. */
261 if(info.term_sep_begin)
262 info.term_sep_begin[0] = 0;
263
264 /* If some other separator sequence has been found, the basename
265 * immediately follows it. */
266 if(info.base_sep_end)
267 return info.base_sep_end;
268
269 /* If removal of the terminating separator sequence has caused the
270 * unprefixed path to become empty, it must have comprised only
271 * separators. POSIX says `/` shall be returned, but on Windows, we
272 * return `\` instead. */
273 if(upath[0] == 0)
274 return (char*) "\\";
275
276 /* Return the unprefixed path. */
277 return upath;
278 }