]>
Commit | Line | Data |
---|---|---|
23c9e63c DD |
1 | /* |
2 | * Copyright (c) 2000, Red Hat, Inc. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * A copy of the GNU General Public License can be found at | |
10 | * http://www.gnu.org/ | |
11 | * | |
12 | * Written by DJ Delorie <dj@cygnus.com> | |
13 | * | |
14 | */ | |
15 | ||
16 | /* The purpose of this file is to hide all the details about accessing | |
17 | Cygwin's mount table. If the format or location of the mount table | |
18 | changes, this is the file to change to match it. */ | |
19 | ||
b24c88b3 | 20 | #if 0 |
3c054baf | 21 | static const char *cvsid = "\n%%% $Id$\n"; |
b24c88b3 | 22 | #endif |
8507f105 | 23 | |
23c9e63c DD |
24 | #include "win32.h" |
25 | ||
26 | #include <stdio.h> | |
a351e48c | 27 | #include <stdlib.h> |
db04fc41 RC |
28 | |
29 | // These headers aren't available outside the winsup tree | |
30 | // #include "../cygwin/include/cygwin/version.h" | |
31 | // KEEP SYNCHRONISED WITH /src/winsup/cygwin/include/cygwin/version.h | |
32 | ||
33 | #define CYGWIN_INFO_CYGNUS_REGISTRY_NAME "Cygnus Solutions" | |
34 | #define CYGWIN_INFO_CYGWIN_REGISTRY_NAME "Cygwin" | |
35 | #define CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME "mounts v2" | |
36 | #define CYGWIN_INFO_CYGDRIVE_FLAGS "cygdrive flags" | |
37 | #define CYGWIN_INFO_CYGDRIVE_PREFIX "cygdrive prefix" | |
38 | #define CYGWIN_INFO_CYGDRIVE_DEFAULT_PREFIX "/cygdrive" | |
39 | ||
40 | ||
41 | // #include "../cygwin/include/sys/mount.h" | |
42 | ||
43 | // KEEP SYNCHRONISED WITH /src/winsup/cygwin/include/sys/mount.h | |
44 | #ifdef __cplusplus | |
45 | extern "C" { | |
46 | #endif | |
47 | ||
48 | enum | |
49 | { | |
50 | MOUNT_SYMLINK = 0x001, /* "mount point" is a symlink */ | |
51 | MOUNT_BINARY = 0x002, /* "binary" format read/writes */ | |
52 | MOUNT_SYSTEM = 0x008, /* mount point came from system table */ | |
53 | MOUNT_EXEC = 0x010, /* Any file in the mounted directory gets 'x' bit */ | |
54 | MOUNT_AUTO = 0x020, /* mount point refers to auto device mount */ | |
55 | MOUNT_CYGWIN_EXEC = 0x040, /* file or directory is or contains a cygwin executable */ | |
56 | MOUNT_MIXED = 0x080, /* reads are text, writes are binary */ | |
57 | }; | |
58 | ||
59 | // int mount (const char *, const char *, unsigned __flags); | |
60 | // int umount (const char *); | |
61 | // int cygwin_umount (const char *__path, unsigned __flags); | |
62 | ||
63 | #ifdef __cplusplus | |
64 | }; | |
65 | #endif | |
66 | ||
67 | ||
23c9e63c DD |
68 | |
69 | #include "mount.h" | |
70 | #include "msg.h" | |
71 | #include "resource.h" | |
72 | #include "dialog.h" | |
a351e48c | 73 | #include "state.h" |
23c9e63c | 74 | |
3c054baf RC |
75 | #include "String++.h" |
76 | ||
1ac649ed RC |
77 | /* Used when treating / and \ as equivalent. */ |
78 | #define SLASH_P(ch) \ | |
79 | ({ \ | |
80 | char __c = (ch); \ | |
81 | ((__c) == '/' || (__c) == '\\'); \ | |
82 | }) | |
83 | ||
a351e48c | 84 | static struct mnt |
b24c88b3 | 85 | { |
3c054baf RC |
86 | String native; |
87 | String posix; | |
b24c88b3 RC |
88 | int istext; |
89 | } | |
90 | mount_table[255]; | |
23c9e63c | 91 | |
85b43844 CF |
92 | struct mnt *root_here = NULL; |
93 | ||
3c054baf RC |
94 | static String |
95 | find2 (HKEY rkey, int *istext, String const &what) | |
23c9e63c | 96 | { |
a351e48c | 97 | HKEY key; |
23c9e63c | 98 | |
3c054baf RC |
99 | if (RegOpenKeyEx (rkey, what.cstr_oneuse (), 0, KEY_READ, &key) != |
100 | ERROR_SUCCESS) | |
23c9e63c DD |
101 | return 0; |
102 | ||
3c054baf RC |
103 | DWORD retvallen = 0; |
104 | DWORD type; | |
105 | ||
106 | String Sretval; | |
23c9e63c DD |
107 | if (RegQueryValueEx (key, "native", 0, &type, 0, &retvallen) |
108 | == ERROR_SUCCESS) | |
109 | { | |
3c054baf | 110 | char retval[retvallen]; |
b24c88b3 RC |
111 | if (RegQueryValueEx |
112 | (key, "native", 0, &type, (BYTE *) retval, | |
3c054baf RC |
113 | &retvallen) == ERROR_SUCCESS) |
114 | Sretval = String (retval); | |
23c9e63c DD |
115 | } |
116 | ||
3c054baf | 117 | DWORD flags = 0; |
23c9e63c | 118 | retvallen = sizeof (flags); |
b24c88b3 | 119 | RegQueryValueEx (key, "flags", 0, &type, (BYTE *) & flags, &retvallen); |
23c9e63c DD |
120 | |
121 | RegCloseKey (key); | |
122 | ||
3c054baf | 123 | if (Sretval.size ()) |
23c9e63c | 124 | *istext = (flags & MOUNT_BINARY) ? 0 : 1; |
23c9e63c | 125 | |
3c054baf | 126 | return Sretval; |
23c9e63c DD |
127 | } |
128 | ||
129 | void | |
3c054baf RC |
130 | create_mount (String const posix, String const win32, int istext, |
131 | int issystem) | |
23c9e63c DD |
132 | { |
133 | char buf[1000]; | |
23c9e63c | 134 | HKEY key; |
b24c88b3 | 135 | DWORD disposition; |
23c9e63c DD |
136 | DWORD flags; |
137 | ||
138 | remove_mount (posix); | |
139 | ||
140 | sprintf (buf, "Software\\%s\\%s\\%s\\%s", | |
141 | CYGWIN_INFO_CYGNUS_REGISTRY_NAME, | |
142 | CYGWIN_INFO_CYGWIN_REGISTRY_NAME, | |
3c054baf | 143 | CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME, posix.cstr_oneuse ()); |
23c9e63c | 144 | |
24e259bb | 145 | HKEY kr = issystem ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; |
c90bc3df | 146 | if (RegCreateKeyEx (kr, buf, 0, (char *)"Cygwin", 0, KEY_ALL_ACCESS, |
23c9e63c DD |
147 | 0, &key, &disposition) != ERROR_SUCCESS) |
148 | fatal ("mount"); | |
149 | ||
3c054baf RC |
150 | RegSetValueEx (key, "native", 0, REG_SZ, (BYTE *) win32.cstr_oneuse (), |
151 | win32.size () + 1); | |
24e259bb DD |
152 | flags = 0; |
153 | if (!istext) | |
154 | flags |= MOUNT_BINARY; | |
155 | if (issystem) | |
156 | flags |= MOUNT_SYSTEM; | |
b24c88b3 RC |
157 | RegSetValueEx (key, "flags", 0, REG_DWORD, (BYTE *) & flags, |
158 | sizeof (flags)); | |
159 | ||
160 | RegCloseKey (key); | |
0af2d779 | 161 | read_mounts (); |
23c9e63c DD |
162 | } |
163 | ||
164 | static void | |
3c054baf | 165 | remove1 (HKEY rkey, String const posix) |
23c9e63c DD |
166 | { |
167 | char buf[1000]; | |
168 | ||
169 | sprintf (buf, "Software\\%s\\%s\\%s\\%s", | |
170 | CYGWIN_INFO_CYGNUS_REGISTRY_NAME, | |
171 | CYGWIN_INFO_CYGWIN_REGISTRY_NAME, | |
3c054baf | 172 | CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME, posix.cstr_oneuse ()); |
23c9e63c DD |
173 | |
174 | RegDeleteKey (rkey, buf); | |
175 | } | |
176 | ||
177 | void | |
3c054baf | 178 | remove_mount (String const posix) |
23c9e63c DD |
179 | { |
180 | remove1 (HKEY_LOCAL_MACHINE, posix); | |
181 | remove1 (HKEY_CURRENT_USER, posix); | |
182 | } | |
f8a6415f DD |
183 | |
184 | static void | |
185 | set_cygdrive_flags (HKEY key, int istext, DWORD cygdrive_flags) | |
186 | { | |
187 | int cur_istext = (cygdrive_flags & MOUNT_BINARY) ? 0 : 1; | |
188 | if (cur_istext != istext) | |
189 | { | |
190 | if (!istext) | |
191 | cygdrive_flags |= MOUNT_BINARY; | |
192 | else | |
193 | cygdrive_flags &= ~MOUNT_BINARY; | |
194 | RegSetValueEx (key, CYGWIN_INFO_CYGDRIVE_FLAGS, 0, REG_DWORD, | |
b24c88b3 | 195 | (BYTE *) & cygdrive_flags, sizeof (cygdrive_flags)); |
f8a6415f DD |
196 | } |
197 | } | |
198 | ||
199 | static LONG | |
b24c88b3 | 200 | get_cygdrive_flags (HKEY key, DWORD * cygdrive_flags) |
f8a6415f | 201 | { |
b24c88b3 | 202 | DWORD retvallen = sizeof (*cygdrive_flags); |
f8a6415f | 203 | LONG status = RegQueryValueEx (key, CYGWIN_INFO_CYGDRIVE_FLAGS, 0, 0, |
b24c88b3 | 204 | (BYTE *) cygdrive_flags, &retvallen); |
f8a6415f DD |
205 | return status; |
206 | } | |
207 | ||
208 | static DWORD | |
b24c88b3 | 209 | default_cygdrive (HKEY key) |
f8a6415f DD |
210 | { |
211 | RegSetValueEx (key, CYGWIN_INFO_CYGDRIVE_PREFIX, 0, REG_SZ, | |
b24c88b3 | 212 | (BYTE *) CYGWIN_INFO_CYGDRIVE_DEFAULT_PREFIX, |
f8a6415f DD |
213 | strlen (CYGWIN_INFO_CYGDRIVE_DEFAULT_PREFIX) + 1); |
214 | DWORD cygdrive_flags = MOUNT_AUTO; | |
215 | RegSetValueEx (key, CYGWIN_INFO_CYGDRIVE_FLAGS, 0, REG_DWORD, | |
b24c88b3 | 216 | (BYTE *) & cygdrive_flags, sizeof (cygdrive_flags)); |
f8a6415f DD |
217 | return cygdrive_flags; |
218 | } | |
219 | ||
220 | void | |
221 | set_cygdrive_flags (int istext, int issystem) | |
222 | { | |
223 | int found_system = 0; | |
224 | ||
225 | char buf[1000]; | |
226 | sprintf (buf, "Software\\%s\\%s\\%s", | |
227 | CYGWIN_INFO_CYGNUS_REGISTRY_NAME, | |
228 | CYGWIN_INFO_CYGWIN_REGISTRY_NAME, | |
229 | CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME); | |
230 | ||
231 | if (issystem) | |
232 | { | |
233 | HKEY key; | |
234 | DWORD disposition; | |
235 | LONG status = RegCreateKeyEx (HKEY_LOCAL_MACHINE, buf, 0, 0, 0, | |
b24c88b3 | 236 | KEY_ALL_ACCESS, 0, &key, &disposition); |
f8a6415f DD |
237 | if (status == ERROR_SUCCESS) |
238 | { | |
239 | DWORD cygdrive_flags = 0; | |
240 | status = get_cygdrive_flags (key, &cygdrive_flags); | |
45e01f23 RC |
241 | if (status == ERROR_FILE_NOT_FOUND) |
242 | { | |
243 | status = ERROR_SUCCESS; | |
244 | cygdrive_flags = default_cygdrive (key); | |
245 | } | |
f8a6415f DD |
246 | if (status == ERROR_SUCCESS) |
247 | { | |
248 | set_cygdrive_flags (key, istext, cygdrive_flags); | |
249 | found_system = 1; | |
250 | } | |
b24c88b3 | 251 | RegCloseKey (key); |
f8a6415f DD |
252 | } |
253 | } | |
254 | ||
255 | HKEY key; | |
256 | DWORD disposition; | |
b24c88b3 RC |
257 | LONG status = |
258 | RegCreateKeyEx (HKEY_CURRENT_USER, buf, 0, 0, 0, KEY_ALL_ACCESS, | |
259 | 0, &key, &disposition); | |
f8a6415f DD |
260 | if (status != ERROR_SUCCESS) |
261 | fatal ("set_cygdrive_flags"); | |
262 | ||
263 | DWORD cygdrive_flags = 0; | |
264 | status = get_cygdrive_flags (key, &cygdrive_flags); | |
265 | if (status == ERROR_FILE_NOT_FOUND && !found_system) | |
266 | { | |
b24c88b3 | 267 | cygdrive_flags = default_cygdrive (key); |
f8a6415f DD |
268 | status = ERROR_SUCCESS; |
269 | } | |
270 | ||
271 | if (status == ERROR_SUCCESS) | |
272 | set_cygdrive_flags (key, istext, cygdrive_flags); | |
273 | ||
b24c88b3 | 274 | RegCloseKey (key); |
f8a6415f | 275 | } |
a351e48c | 276 | |
85b43844 | 277 | static int |
a351e48c CF |
278 | in_table (struct mnt *m) |
279 | { | |
b24c88b3 | 280 | for (struct mnt * m1 = mount_table; m1 < m; m1++) |
3c054baf | 281 | if (m1->posix.casecompare (m->posix) == 0) |
a351e48c CF |
282 | return 1; |
283 | return 0; | |
284 | } | |
285 | ||
286 | /* | |
287 | * is_admin () determines whether or not the current user is a member of the | |
288 | * Administrators group. On Windows 9X, the current user is considered an | |
289 | * Administrator by definition. | |
290 | */ | |
291 | ||
292 | static int | |
293 | is_admin () | |
294 | { | |
295 | // Windows 9X users are considered Administrators by definition | |
296 | OSVERSIONINFO verinfo; | |
297 | verinfo.dwOSVersionInfoSize = sizeof (verinfo); | |
298 | GetVersionEx (&verinfo); | |
299 | if (verinfo.dwPlatformId != VER_PLATFORM_WIN32_NT) | |
300 | return 1; | |
301 | ||
302 | // Get the process token for the current process | |
303 | HANDLE token; | |
b24c88b3 | 304 | BOOL status = OpenProcessToken (GetCurrentProcess (), TOKEN_QUERY, &token); |
a351e48c CF |
305 | if (!status) |
306 | return 0; | |
307 | ||
308 | // Get the group token information | |
309 | UCHAR token_info[1024]; | |
310 | PTOKEN_GROUPS groups = (PTOKEN_GROUPS) token_info; | |
311 | DWORD token_info_len = sizeof (token_info); | |
b24c88b3 RC |
312 | status = |
313 | GetTokenInformation (token, TokenGroups, token_info, token_info_len, | |
314 | &token_info_len); | |
315 | CloseHandle (token); | |
a351e48c CF |
316 | if (!status) |
317 | return 0; | |
318 | ||
319 | // Create the Administrators group SID | |
320 | PSID admin_sid; | |
b24c88b3 RC |
321 | SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY }; |
322 | status = | |
323 | AllocateAndInitializeSid (&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, | |
324 | DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, | |
325 | &admin_sid); | |
a351e48c CF |
326 | if (!status) |
327 | return 0; | |
328 | ||
329 | // Check to see if the user is a member of the Administrators group | |
330 | status = 0; | |
b24c88b3 RC |
331 | for (UINT i = 0; i < groups->GroupCount; i++) |
332 | { | |
333 | if (EqualSid (groups->Groups[i].Sid, admin_sid)) | |
334 | { | |
335 | status = 1; | |
336 | break; | |
337 | } | |
a351e48c | 338 | } |
a351e48c CF |
339 | |
340 | // Destroy the Administrators group SID | |
341 | FreeSid (admin_sid); | |
342 | ||
343 | // Return whether or not the user is a member of the Administrators group | |
344 | return status; | |
345 | } | |
346 | ||
347 | void | |
348 | read_mounts () | |
349 | { | |
350 | DWORD posix_path_size; | |
351 | int res; | |
352 | struct mnt *m = mount_table; | |
353 | DWORD disposition; | |
354 | char buf[10000]; | |
355 | ||
0af2d779 | 356 | root_here = NULL; |
3c054baf | 357 | for (mnt * m1 = mount_table; m1->posix.size (); m1++) |
0af2d779 | 358 | { |
3c054baf RC |
359 | m1->posix = String (); //empty string; |
360 | m1->native = String (); | |
0af2d779 CF |
361 | } |
362 | ||
a351e48c CF |
363 | /* Loop through subkeys */ |
364 | /* FIXME: we would like to not check MAX_MOUNTS but the heap in the | |
365 | shared area is currently statically allocated so we can't have an | |
366 | arbitrarily large number of mounts. */ | |
367 | for (int issystem = 0; issystem <= 1; issystem++) | |
368 | { | |
369 | sprintf (buf, "Software\\%s\\%s\\%s", | |
370 | CYGWIN_INFO_CYGNUS_REGISTRY_NAME, | |
371 | CYGWIN_INFO_CYGWIN_REGISTRY_NAME, | |
372 | CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME); | |
373 | ||
374 | HKEY key = issystem ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; | |
c90bc3df | 375 | if (RegCreateKeyEx (key, buf, 0, (char *)"Cygwin", 0, KEY_ALL_ACCESS, |
a351e48c CF |
376 | 0, &key, &disposition) != ERROR_SUCCESS) |
377 | break; | |
b24c88b3 | 378 | for (int i = 0;; i++, m++) |
a351e48c | 379 | { |
3c054baf | 380 | char aBuffer[MAX_PATH + 1]; |
a351e48c CF |
381 | posix_path_size = MAX_PATH; |
382 | /* FIXME: if maximum posix_path_size is 256, we're going to | |
383 | run into problems if we ever try to store a mount point that's | |
384 | over 256 but is under MAX_PATH. */ | |
3c054baf | 385 | res = RegEnumKeyEx (key, i, aBuffer, &posix_path_size, NULL, |
a351e48c CF |
386 | NULL, NULL, NULL); |
387 | ||
388 | if (res == ERROR_NO_MORE_ITEMS) | |
0af2d779 | 389 | { |
3c054baf | 390 | m->posix = String (); |
0af2d779 CF |
391 | break; |
392 | } | |
3c054baf | 393 | m->posix = String (aBuffer); |
a351e48c | 394 | |
3c054baf | 395 | if (!m->posix.size () || in_table (m)) |
0af2d779 | 396 | goto no_go; |
a351e48c CF |
397 | else if (res != ERROR_SUCCESS) |
398 | break; | |
399 | else | |
400 | { | |
401 | m->native = find2 (key, &m->istext, m->posix); | |
3c054baf | 402 | if (!m->native.size ()) |
0af2d779 | 403 | goto no_go; |
b24c88b3 | 404 | |
3c054baf | 405 | if (m->posix == "/") |
a351e48c | 406 | { |
85b43844 | 407 | root_here = m; |
a351e48c CF |
408 | if (m->istext) |
409 | root_text = IDC_ROOT_TEXT; | |
410 | else | |
411 | root_text = IDC_ROOT_BINARY; | |
412 | if (issystem) | |
413 | root_scope = IDC_ROOT_SYSTEM; | |
414 | else | |
415 | root_scope = IDC_ROOT_USER; | |
416 | } | |
417 | } | |
0af2d779 CF |
418 | continue; |
419 | no_go: | |
3c054baf RC |
420 | m->posix = String (); |
421 | --m; | |
a351e48c CF |
422 | } |
423 | RegCloseKey (key); | |
424 | } | |
425 | ||
85b43844 | 426 | if (!root_here) |
a351e48c | 427 | { |
85b43844 | 428 | root_here = m; |
3c054baf | 429 | m->posix = String ("/"); |
a351e48c | 430 | char windir[_MAX_PATH]; |
a351e48c | 431 | root_text = IDC_ROOT_BINARY; |
b24c88b3 | 432 | root_scope = (is_admin ())? IDC_ROOT_SYSTEM : IDC_ROOT_USER; |
85b43844 CF |
433 | GetWindowsDirectory (windir, sizeof (windir)); |
434 | windir[2] = 0; | |
3c054baf | 435 | set_root_dir (String (windir) + "\\cygwin"); |
0af2d779 | 436 | m++; |
a351e48c | 437 | } |
a351e48c CF |
438 | } |
439 | ||
85b43844 | 440 | void |
3c054baf | 441 | set_root_dir (String const val) |
85b43844 CF |
442 | { |
443 | root_here->native = val; | |
444 | } | |
445 | ||
3c054baf | 446 | String const |
85b43844 CF |
447 | get_root_dir () |
448 | { | |
3c054baf | 449 | return root_here ? root_here->native : String (); |
85b43844 CF |
450 | } |
451 | ||
a351e48c CF |
452 | /* Return non-zero if PATH1 is a prefix of PATH2. |
453 | Both are assumed to be of the same path style and / vs \ usage. | |
454 | Neither may be "". | |
a351e48c CF |
455 | |
456 | Examples: | |
457 | /foo/ is a prefix of /foo <-- may seem odd, but desired | |
458 | /foo is a prefix of /foo/ | |
459 | / is a prefix of /foo/bar | |
460 | / is not a prefix of foo/bar | |
461 | foo/ is a prefix foo/bar | |
462 | /foo is not a prefix of /foobar | |
463 | */ | |
464 | ||
465 | static int | |
3c054baf | 466 | path_prefix_p (String const path1, String const path2) |
a351e48c | 467 | { |
3c054baf | 468 | size_t len1 = path1.size (); |
a351e48c | 469 | /* Handle case where PATH1 has trailing '/' and when it doesn't. */ |
3c054baf RC |
470 | if (len1 > 0 && SLASH_P (path1.cstr_oneuse ()[len1 - 1])) |
471 | --len1; | |
a351e48c CF |
472 | |
473 | if (len1 == 0) | |
3c054baf RC |
474 | return SLASH_P (path2.cstr_oneuse ()[0]) |
475 | && !SLASH_P (path2.cstr_oneuse ()[1]); | |
a351e48c | 476 | |
3c054baf | 477 | if (path1.casecompare (path2, len1) != 0) |
a351e48c CF |
478 | return 0; |
479 | ||
3c054baf RC |
480 | return SLASH_P (path2.cstr_oneuse ()[len1]) || path2.size () == len1 |
481 | || path1.cstr_oneuse ()[len1 - 1] == ':'; | |
a351e48c CF |
482 | } |
483 | ||
1ac649ed | 484 | String |
3c054baf | 485 | cygpath (String const &thePath) |
a351e48c | 486 | { |
3c054baf | 487 | size_t max_len = 0; |
b24c88b3 | 488 | struct mnt *m, *match = NULL; |
3c054baf | 489 | for (m = mount_table; m->posix.size (); m++) |
a351e48c | 490 | { |
3c054baf RC |
491 | size_t n = m->posix.size (); |
492 | if (n <= max_len || !path_prefix_p (m->posix, thePath)) | |
a351e48c CF |
493 | continue; |
494 | max_len = n; | |
495 | match = m; | |
496 | } | |
497 | ||
b24c88b3 | 498 | if (!match) |
076654e7 | 499 | return String(); |
b24c88b3 | 500 | |
3c054baf RC |
501 | String native; |
502 | if (max_len == thePath.size ()) | |
503 | { | |
504 | native = match->native; | |
505 | } | |
a351e48c | 506 | else |
1ac649ed | 507 | native = match->native + "/" + String (thePath.cstr_oneuse() + max_len); |
a351e48c CF |
508 | return native; |
509 | } |