VirtualBox

source: kBuild/trunk/src/lib/nt_fullpath.c@ 1248

Last change on this file since 1248 was 1248, checked in by bird, 17 years ago

Substitute spacy names with the alternate one.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 13.2 KB
Line 
1/* $Id: nt_fullpath.c 1248 2007-10-23 12:46:50Z bird $ */
2/** @file
3 * fixcase - fixes the case of paths, windows specific.
4 */
5
6/*
7 * Copyright (c) 2004-2007 knut st. osmundsen <[email protected]>
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 *
25 */
26
27#include <Windows.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <ctype.h>
32#include <direct.h>
33
34/*
35 * Corrects the case of a path.
36 * Expects a fullpath!
37 * Added by bird for the $(abspath ) function and w32ify
38 */
39static void w32_fixcase(char *pszPath)
40{
41 static char s_szLast[260];
42 size_t cchLast;
43
44#ifndef NDEBUG
45# define my_assert(expr) \
46 do { \
47 if (!(expr)) { \
48 printf("my_assert: %s, file %s, line %d\npszPath=%s\npsz=%s\n", \
49 #expr, __FILE__, __LINE__, pszPath, psz); \
50 __debugbreak(); \
51 exit(1); \
52 } \
53 } while (0)
54#else
55# define my_assert(expr) do {} while (0)
56#endif
57
58 char *psz = pszPath;
59 if (*psz == '/' || *psz == '\\')
60 {
61 if (psz[1] == '/' || psz[1] == '\\')
62 {
63 /* UNC */
64 my_assert(psz[1] == '/' || psz[1] == '\\');
65 my_assert(psz[2] != '/' && psz[2] != '\\');
66
67 /* skip server name */
68 psz += 2;
69 while (*psz != '\\' && *psz != '/')
70 {
71 if (!*psz)
72 return;
73 *psz++ = toupper(*psz);
74 }
75
76 /* skip the share name */
77 psz++;
78 my_assert(*psz != '/' && *psz != '\\');
79 while (*psz != '\\' && *psz != '/')
80 {
81 if (!*psz)
82 return;
83 *psz++ = toupper(*psz);
84 }
85 my_assert(*psz == '/' || *psz == '\\');
86 psz++;
87 }
88 else
89 {
90 /* Unix spec */
91 psz++;
92 }
93 }
94 else
95 {
96 /* Drive letter */
97 my_assert(psz[1] == ':');
98 *psz = toupper(*psz);
99 my_assert(psz[0] >= 'A' && psz[0] <= 'Z');
100 my_assert(psz[2] == '/' || psz[2] == '\\');
101 psz += 3;
102 }
103
104 /*
105 * Try make use of the result from the previous call.
106 * This is ignorant to slashes and similar, but may help even so.
107 */
108 if ( s_szLast[0] == pszPath[0]
109 && (psz - pszPath == 1 || s_szLast[1] == pszPath[1])
110 && (psz - pszPath <= 2 || s_szLast[2] == pszPath[2])
111 )
112 {
113 char *pszLast = &s_szLast[psz - pszPath];
114 char *pszCur = psz;
115 for (;;)
116 {
117 const char ch1 = *pszCur;
118 const char ch2 = *pszLast;
119 if ( ch1 != ch2
120 && (ch1 != '\\' || ch2 != '/')
121 && (ch1 != '/' || ch2 != '\\'))
122 {
123 if ( tolower(ch1) != tolower(ch2)
124 && toupper(ch1) != toupper(ch2))
125 break;
126 /* optimistic, component mismatch will be corrected in the next loop. */
127 *pszCur = ch2;
128 }
129 if (ch1 == '/' || ch1 == '\\')
130 psz = pszCur + 1;
131 else if (ch1 == '\0')
132 {
133 psz = pszCur;
134 break;
135 }
136 pszCur++;
137 pszLast++;
138 }
139 }
140
141 /*
142 * Pointing to the first char after the unc or drive specifier,
143 * or in case of a cache hit, the first non-matching char (following a slash of course).
144 */
145 while (*psz)
146 {
147 WIN32_FIND_DATA FindFileData;
148 HANDLE hDir;
149 char chSaved0;
150 char chSaved1;
151 char *pszEnd;
152 int iLongNameDiff;
153 size_t cch;
154
155
156 /* find the end of the component. */
157 pszEnd = psz;
158 while (*pszEnd && *pszEnd != '/' && *pszEnd != '\\')
159 pszEnd++;
160 cch = pszEnd - psz;
161
162 /* replace the end with "?\0" */
163 chSaved0 = pszEnd[0];
164 chSaved1 = pszEnd[1];
165 pszEnd[0] = '?';
166 pszEnd[1] = '\0';
167
168 /* find the right filename. */
169 hDir = FindFirstFile(pszPath, &FindFileData);
170 pszEnd[1] = chSaved1;
171 if (!hDir)
172 {
173 cchLast = psz - pszPath;
174 memcpy(s_szLast, pszPath, cchLast + 1);
175 s_szLast[cchLast + 1] = '\0';
176 pszEnd[0] = chSaved0;
177 return;
178 }
179 pszEnd[0] = '\0';
180 while ( (iLongNameDiff = stricmp(FindFileData.cFileName, psz))
181 && stricmp(FindFileData.cAlternateFileName, psz))
182 {
183 if (!FindNextFile(hDir, &FindFileData))
184 {
185 cchLast = psz - pszPath;
186 memcpy(s_szLast, pszPath, cchLast + 1);
187 s_szLast[cchLast + 1] = '\0';
188 pszEnd[0] = chSaved0;
189 return;
190 }
191 }
192 pszEnd[0] = chSaved0;
193 if ( iLongNameDiff /* matched the short name */
194 || !FindFileData.cAlternateFileName[0] /* no short name */
195 || !memchr(psz, ' ', cch)) /* no spaces in the matching name */
196 memcpy(psz, !iLongNameDiff ? FindFileData.cFileName : FindFileData.cAlternateFileName, cch);
197 else
198 {
199 /* replace spacy name with the short name. */
200 const size_t cchAlt = strlen(FindFileData.cAlternateFileName);
201 const size_t cchDelta = cch - cchAlt;
202 my_assert(cchAlt > 0);
203 if (!cchDelta)
204 memcpy(psz, FindFileData.cAlternateFileName, cch);
205 else
206 {
207 size_t cbLeft = strlen(pszEnd) + 1;
208 if ((psz - pszPath) + cbLeft + cchAlt <= _MAX_PATH)
209 {
210 memmove(psz + cchAlt, pszEnd, cbLeft);
211 pszEnd -= cchDelta;
212 memcpy(psz, FindFileData.cAlternateFileName, cchAlt);
213 }
214 else
215 fprintf(stderr, "kBuild: case & space fixed filename is growing too long (%d bytes)! '%s'\n",
216 (psz - pszPath) + cbLeft + cchAlt, pszPath);
217 }
218 }
219 my_assert(pszEnd[0] == chSaved0);
220 FindClose(hDir);
221
222 /* advance to the next component */
223 if (!chSaved0)
224 {
225 psz = pszEnd;
226 break;
227 }
228 psz = pszEnd + 1;
229 my_assert(*psz != '/' && *psz != '\\');
230 }
231
232 /* *psz == '\0', the end. */
233 cchLast = psz - pszPath;
234 memcpy(s_szLast, pszPath, cchLast + 1);
235#undef my_assert
236}
237
238#define MY_FileNameInformation 9
239
240typedef struct _MY_FILE_NAME_INFORMATION
241{
242 ULONG FileNameLength;
243 WCHAR FileName[1];
244} MY_FILE_NAME_INFORMATION, *PMY_FILE_NAME_INFORMATION;
245
246typedef struct _IO_STATUS_BLOCK
247{
248 union
249 {
250 LONG Status;
251 PVOID Pointer;
252 };
253 ULONG_PTR Information;
254} MY_IO_STATUS_BLOCK, *PMY_IO_STATUS_BLOCK;
255
256static BOOL g_fInitialized = FALSE;
257static int g_afNtfsDrives['Z' - 'A' + 1];
258static LONG (NTAPI *g_pfnNtQueryInformationFile)(HANDLE FileHandle,
259 PMY_IO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation,
260 ULONG Length, ULONG FileInformationClass);
261
262
263int
264nt_get_filename_info(const char *pszPath, char *pszFull, size_t cchFull)
265{
266 static char abBuf[8192];
267 PMY_FILE_NAME_INFORMATION pFileNameInfo = (PMY_FILE_NAME_INFORMATION)abBuf;
268 MY_IO_STATUS_BLOCK Ios;
269 LONG rcNt;
270 HANDLE hFile;
271 int cchOut;
272 char *psz;
273
274 /*
275 * Check for NtQueryInformationFile the first time around.
276 */
277 if (!g_fInitialized)
278 {
279 g_fInitialized = TRUE;
280 if (!getenv("KMK_DONT_USE_NT_QUERY_INFORMATION_FILE"))
281 *(FARPROC *)&g_pfnNtQueryInformationFile =
282 GetProcAddress(LoadLibrary("ntdll.dll"), "NtQueryInformationFile");
283 if (g_pfnNtQueryInformationFile)
284 {
285 unsigned i;
286 for (i = 0; i < sizeof(g_afNtfsDrives) / sizeof(g_afNtfsDrives[0]); i++ )
287 g_afNtfsDrives[i] = -1;
288 }
289 }
290 if (!g_pfnNtQueryInformationFile)
291 return -1;
292
293 /*
294 * The FileNameInformation we get is relative to where the volume is mounted,
295 * so we have to extract the driveletter prefix ourselves.
296 *
297 * FIXME: This will probably not work for volumes mounted in NTFS sub-directories.
298 */
299 psz = pszFull;
300 if (pszPath[0] == '\\' || pszPath[0] == '/')
301 {
302 /* unc or root of volume */
303 if ( (pszPath[1] == '\\' || pszPath[1] == '/')
304 && (pszPath[2] != '\\' || pszPath[2] == '/'))
305 {
306#if 0 /* don't bother with unc yet. */
307 /* unc - we get the server + name back */
308 *psz++ = '\\';
309#endif
310 return -1;
311 }
312 /* root slash */
313 *psz++ = _getdrive() + 'A' - 1;
314 *psz++ = ':';
315 }
316 else if (pszPath[1] == ':' && isalpha(pszPath[0]))
317 {
318 /* drive letter */
319 *psz++ = toupper(pszPath[0]);
320 *psz++ = ':';
321 }
322 else
323 {
324 /* relative */
325 *psz++ = _getdrive() + 'A' - 1;
326 *psz++ = ':';
327 }
328
329 /*
330 * Fat32 doesn't return filenames with the correct case, so restrict it
331 * to NTFS volumes for now.
332 */
333 if (g_afNtfsDrives[*pszFull - 'A'] == -1)
334 {
335 char szFSName[32];
336
337 psz[0] = '\\';
338 psz[1] = '\0';
339 if ( GetVolumeInformation(pszFull,
340 NULL, 0, /* volume name */
341 NULL, /* serial number */
342 NULL, /* max component */
343 NULL, /* volume attribs */
344 szFSName,
345 sizeof(szFSName))
346 && !strcmp(szFSName, "NTFS"))
347 g_afNtfsDrives[*pszFull - 'A'] = 1;
348 else
349 g_afNtfsDrives[*pszFull - 'A'] = 0;
350 }
351 if (!g_afNtfsDrives[*pszFull - 'A'])
352 return -1;
353
354 /*
355 * Try open the path and query its file name information.
356 */
357 hFile = CreateFile(pszPath,
358 GENERIC_READ,
359 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
360 NULL,
361 OPEN_EXISTING,
362 FILE_FLAG_BACKUP_SEMANTICS,
363 NULL);
364 if (hFile)
365 {
366 memset(&Ios, 0, sizeof(Ios));
367 rcNt = g_pfnNtQueryInformationFile(hFile, &Ios, abBuf, sizeof(abBuf), MY_FileNameInformation);
368 CloseHandle(hFile);
369 if (rcNt >= 0)
370 {
371 cchOut = WideCharToMultiByte(CP_ACP, 0,
372 pFileNameInfo->FileName, pFileNameInfo->FileNameLength / sizeof(WCHAR),
373 psz, (int)(cchFull - (psz - pszFull) - 2), NULL, NULL);
374 if (cchOut > 0)
375 {
376 const char *pszEnd;
377#if 0
378 /* upper case the server and share */
379 if (fUnc)
380 {
381 for (psz++; *psz != '/' && *psz != '\\'; psz++)
382 *psz = toupper(*psz);
383 for (psz++; *psz != '/' && *psz != '\\'; psz++)
384 *psz = toupper(*psz);
385 }
386#endif
387 /* add trailing slash on directories if input has it. */
388 pszEnd = strchr(pszPath, '\0');
389 if ( (pszEnd[-1] == '/' || pszEnd[-1] == '\\')
390 && psz[cchOut - 1] != '\\'
391 && psz[cchOut - 1] != '//')
392 psz[cchOut++] = '\\';
393
394 /* make sure it's terminated */
395 psz[cchOut] = '\0';
396 return 0;
397 }
398 return -3;
399 }
400 }
401 return -2;
402}
403
404/**
405 * Somewhat similar to fullpath, except that it will fix
406 * the case of existing path components.
407 */
408void
409nt_fullpath(const char *pszPath, char *pszFull, size_t cchFull)
410{
411#if 0
412 static int s_cHits = 0;
413 static int s_cFallbacks = 0;
414#endif
415
416 /*
417 * The simple case, the file / dir / whatever exists and can be
418 * queried without problems and spaces.
419 */
420 if (nt_get_filename_info(pszPath, pszFull, cchFull) == 0)
421 {
422 /** @todo make nt_get_filename_info return spaceless path. */
423 if (strchr(pszPath, ' '))
424 w32_fixcase(pszPath);
425#if 0
426 fprintf(stderr, "nt #%d - %s\n", ++s_cHits, pszFull);
427#endif
428 return;
429 }
430 if (g_pfnNtQueryInformationFile)
431 {
432 /* do _fullpath and drop off path elements until we get a hit... - later */
433 }
434
435 /*
436 * For now, simply fall back on the old method.
437 */
438 _fullpath(pszFull, pszPath, cchFull);
439 w32_fixcase(pszFull);
440#if 0
441 fprintf(stderr, "fb #%d - %s\n", ++s_cFallbacks, pszFull);
442#endif
443}
444
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette