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