VirtualBox

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

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

don't be optimistic in the cache lookup code, the next component might not exist and we might end up screwing the casing of it this way.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 17.7 KB
Line 
1/* $Id: nt_fullpath.c 1368 2007-12-07 19:36:06Z 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 char *pszSrc0 = pszLast;
116 char *pszDst0 = pszCur;
117 for (;;)
118 {
119 const char ch1 = *pszCur;
120 const char ch2 = *pszLast;
121 if ( ch1 != ch2
122 && (ch1 != '\\' || ch2 != '/')
123 && (ch1 != '/' || ch2 != '\\')
124 && tolower(ch1) != tolower(ch2)
125 && toupper(ch1) != toupper(ch2))
126 break;
127 if (ch1 == '/' || ch1 == '\\')
128 {
129 psz = pszCur + 1;
130 *pszLast = ch1; /* preserve the slashes */
131 }
132 else if (ch1 == '\0')
133 {
134 psz = pszCur;
135 break;
136 }
137 pszCur++;
138 pszLast++;
139 }
140 if (psz != pszDst0)
141 memcpy(pszDst0, pszSrc0, psz - pszDst0);
142 }
143
144 /*
145 * Pointing to the first char after the unc or drive specifier,
146 * or in case of a cache hit, the first non-matching char (following a slash of course).
147 */
148 while (*psz)
149 {
150 WIN32_FIND_DATA FindFileData;
151 HANDLE hDir;
152 char chSaved0;
153 char chSaved1;
154 char *pszEnd;
155 int iLongNameDiff;
156 size_t cch;
157
158
159 /* find the end of the component. */
160 pszEnd = psz;
161 while (*pszEnd && *pszEnd != '/' && *pszEnd != '\\')
162 pszEnd++;
163 cch = pszEnd - psz;
164
165 /* replace the end with "?\0" */
166 chSaved0 = pszEnd[0];
167 chSaved1 = pszEnd[1];
168 pszEnd[0] = '?';
169 pszEnd[1] = '\0';
170
171 /* find the right filename. */
172 hDir = FindFirstFile(pszPath, &FindFileData);
173 pszEnd[1] = chSaved1;
174 if (!hDir)
175 {
176 cchLast = psz - pszPath;
177 memcpy(s_szLast, pszPath, cchLast + 1);
178 s_szLast[cchLast + 1] = '\0';
179 pszEnd[0] = chSaved0;
180 return;
181 }
182 pszEnd[0] = '\0';
183 while ( (iLongNameDiff = stricmp(FindFileData.cFileName, psz))
184 && stricmp(FindFileData.cAlternateFileName, psz))
185 {
186 if (!FindNextFile(hDir, &FindFileData))
187 {
188 cchLast = psz - pszPath;
189 memcpy(s_szLast, pszPath, cchLast + 1);
190 s_szLast[cchLast + 1] = '\0';
191 pszEnd[0] = chSaved0;
192 return;
193 }
194 }
195 pszEnd[0] = chSaved0;
196 if ( iLongNameDiff /* matched the short name */
197 || !FindFileData.cAlternateFileName[0] /* no short name */
198 || !memchr(psz, ' ', cch)) /* no spaces in the matching name */
199 memcpy(psz, !iLongNameDiff ? FindFileData.cFileName : FindFileData.cAlternateFileName, cch);
200 else
201 {
202 /* replace spacy name with the short name. */
203 const size_t cchAlt = strlen(FindFileData.cAlternateFileName);
204 const size_t cchDelta = cch - cchAlt;
205 my_assert(cchAlt > 0);
206 if (!cchDelta)
207 memcpy(psz, FindFileData.cAlternateFileName, cch);
208 else
209 {
210 size_t cbLeft = strlen(pszEnd) + 1;
211 if ((psz - pszPath) + cbLeft + cchAlt <= _MAX_PATH)
212 {
213 memmove(psz + cchAlt, pszEnd, cbLeft);
214 pszEnd -= cchDelta;
215 memcpy(psz, FindFileData.cAlternateFileName, cchAlt);
216 }
217 else
218 fprintf(stderr, "kBuild: case & space fixed filename is growing too long (%d bytes)! '%s'\n",
219 (psz - pszPath) + cbLeft + cchAlt, pszPath);
220 }
221 }
222 my_assert(pszEnd[0] == chSaved0);
223 FindClose(hDir);
224
225 /* advance to the next component */
226 if (!chSaved0)
227 {
228 psz = pszEnd;
229 break;
230 }
231 psz = pszEnd + 1;
232 my_assert(*psz != '/' && *psz != '\\');
233 }
234
235 /* *psz == '\0', the end. */
236 cchLast = psz - pszPath;
237 memcpy(s_szLast, pszPath, cchLast + 1);
238#undef my_assert
239}
240
241#define MY_FileNameInformation 9
242typedef struct _MY_FILE_NAME_INFORMATION
243{
244 ULONG FileNameLength;
245 WCHAR FileName[1];
246} MY_FILE_NAME_INFORMATION, *PMY_FILE_NAME_INFORMATION;
247
248#define MY_FileInternalInformation 6
249typedef struct _MY_FILE_INTERNAL_INFORMATION {
250 LARGE_INTEGER IndexNumber;
251} MY_FILE_INTERNAL_INFORMATION, *PMY_FILE_INTERNAL_INFORMATION;
252
253#define MY_FileFsVolumeInformation 1
254typedef struct _MY_FILE_FS_VOLUME_INFORMATION
255{
256 LARGE_INTEGER VolumeCreationTime;
257 ULONG VolumeSerialNumber;
258 ULONG VolumeLabelLength;
259 BOOLEAN SupportsObjects;
260 WCHAR VolumeLabel[/*1*/128];
261} MY_FILE_FS_VOLUME_INFORMATION, *PMY_FILE_FS_VOLUME_INFORMATION;
262
263#define MY_FileFsAttributeInformation 5
264typedef struct _MY_FILE_FS_ATTRIBUTE_INFORMATION
265{
266 ULONG FileSystemAttributes;
267 LONG MaximumComponentNameLength;
268 ULONG FileSystemNameLength;
269 WCHAR FileSystemName[/*1*/64];
270} MY_FILE_FS_ATTRIBUTE_INFORMATION, *PMY_FILE_FS_ATTRIBUTE_INFORMATION;
271
272typedef struct _IO_STATUS_BLOCK
273{
274 union
275 {
276 LONG Status;
277 PVOID Pointer;
278 };
279 ULONG_PTR Information;
280} MY_IO_STATUS_BLOCK, *PMY_IO_STATUS_BLOCK;
281
282static BOOL g_fInitialized = FALSE;
283static int g_afNtfsDrives['Z' - 'A' + 1];
284static MY_FILE_FS_VOLUME_INFORMATION g_aVolumeInfo['Z' - 'A' + 1];
285
286static LONG (NTAPI *g_pfnNtQueryInformationFile)(HANDLE FileHandle,
287 PMY_IO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation,
288 ULONG Length, ULONG FileInformationClass);
289static LONG (NTAPI *g_pfnNtQueryVolumeInformationFile)(HANDLE FileHandle,
290 PMY_IO_STATUS_BLOCK IoStatusBlock, PVOID FsInformation,
291 ULONG Length, ULONG FsInformationClass);
292
293
294int
295nt_get_filename_info(const char *pszPath, char *pszFull, size_t cchFull)
296{
297 static char abBuf[8192];
298 PMY_FILE_NAME_INFORMATION pFileNameInfo = (PMY_FILE_NAME_INFORMATION)abBuf;
299 PMY_FILE_FS_VOLUME_INFORMATION pFsVolInfo = (PMY_FILE_FS_VOLUME_INFORMATION)abBuf;
300 MY_IO_STATUS_BLOCK Ios;
301 LONG rcNt;
302 HANDLE hFile;
303 int cchOut;
304 char *psz;
305 int iDrv;
306 int rc;
307
308 /*
309 * Check for NtQueryInformationFile the first time around.
310 */
311 if (!g_fInitialized)
312 {
313 g_fInitialized = TRUE;
314 if (!getenv("KMK_DONT_USE_NT_QUERY_INFORMATION_FILE"))
315 {
316 *(FARPROC *)&g_pfnNtQueryInformationFile =
317 GetProcAddress(LoadLibrary("ntdll.dll"), "NtQueryInformationFile");
318 *(FARPROC *)&g_pfnNtQueryVolumeInformationFile =
319 GetProcAddress(LoadLibrary("ntdll.dll"), "NtQueryVolumeInformationFile");
320 }
321 if ( g_pfnNtQueryInformationFile
322 && g_pfnNtQueryVolumeInformationFile)
323 {
324 unsigned i;
325 for (i = 0; i < sizeof(g_afNtfsDrives) / sizeof(g_afNtfsDrives[0]); i++ )
326 g_afNtfsDrives[i] = -1;
327 }
328 else
329 {
330 g_pfnNtQueryVolumeInformationFile = NULL;
331 g_pfnNtQueryInformationFile = NULL;
332 }
333 }
334 if (!g_pfnNtQueryInformationFile)
335 return -1;
336
337 /*
338 * The FileNameInformation we get is relative to where the volume is mounted,
339 * so we have to extract the driveletter prefix ourselves.
340 *
341 * FIXME: This will probably not work for volumes mounted in NTFS sub-directories.
342 */
343 psz = pszFull;
344 if (pszPath[0] == '\\' || pszPath[0] == '/')
345 {
346 /* unc or root of volume */
347 if ( (pszPath[1] == '\\' || pszPath[1] == '/')
348 && (pszPath[2] != '\\' || pszPath[2] == '/'))
349 {
350#if 0 /* don't bother with unc yet. */
351 /* unc - we get the server + name back */
352 *psz++ = '\\';
353#endif
354 return -1;
355 }
356 /* root slash */
357 *psz++ = _getdrive() + 'A' - 1;
358 *psz++ = ':';
359 }
360 else if (pszPath[1] == ':' && isalpha(pszPath[0]))
361 {
362 /* drive letter */
363 *psz++ = toupper(pszPath[0]);
364 *psz++ = ':';
365 }
366 else
367 {
368 /* relative */
369 *psz++ = _getdrive() + 'A' - 1;
370 *psz++ = ':';
371 }
372 iDrv = *pszFull - 'A';
373
374 /*
375 * Fat32 doesn't return filenames with the correct case, so restrict it
376 * to NTFS volumes for now.
377 */
378 if (g_afNtfsDrives[iDrv] == -1)
379 {
380 /* FSCTL_GET_REPARSE_POINT? Enumerate mount points? */
381 g_afNtfsDrives[iDrv] = 0;
382 psz[0] = '\\';
383 psz[1] = '\0';
384#if 1
385 hFile = CreateFile(pszFull,
386 GENERIC_READ,
387 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
388 NULL,
389 OPEN_EXISTING,
390 FILE_FLAG_BACKUP_SEMANTICS,
391 NULL);
392 if (hFile != INVALID_HANDLE_VALUE)
393 {
394 PMY_FILE_FS_ATTRIBUTE_INFORMATION pFsAttrInfo = (PMY_FILE_FS_ATTRIBUTE_INFORMATION)abBuf;
395
396 memset(&Ios, 0, sizeof(Ios));
397 rcNt = g_pfnNtQueryVolumeInformationFile(hFile, &Ios, abBuf, sizeof(abBuf),
398 MY_FileFsAttributeInformation);
399 if ( rcNt >= 0
400 //&& pFsAttrInfo->FileSystemNameLength == 4
401 && pFsAttrInfo->FileSystemName[0] == 'N'
402 && pFsAttrInfo->FileSystemName[1] == 'T'
403 && pFsAttrInfo->FileSystemName[2] == 'F'
404 && pFsAttrInfo->FileSystemName[3] == 'S'
405 && pFsAttrInfo->FileSystemName[4] == '\0')
406 {
407 memset(&Ios, 0, sizeof(Ios));
408 rcNt = g_pfnNtQueryVolumeInformationFile(hFile, &Ios, &g_aVolumeInfo[iDrv],
409 sizeof(MY_FILE_FS_VOLUME_INFORMATION),
410 MY_FileFsVolumeInformation);
411 if (rcNt >= 0)
412 {
413 g_afNtfsDrives[iDrv] = 1;
414 }
415 }
416 CloseHandle(hFile);
417 }
418#else
419 {
420 char szFSName[32];
421 if ( GetVolumeInformation(pszFull,
422 NULL, 0, /* volume name */
423 NULL, /* serial number */
424 NULL, /* max component */
425 NULL, /* volume attribs */
426 szFSName,
427 sizeof(szFSName))
428 && !strcmp(szFSName, "NTFS"))
429 {
430 g_afNtfsDrives[iDrv] = 1;
431 }
432 }
433#endif
434 }
435 if (!g_afNtfsDrives[iDrv])
436 return -1;
437
438 /*
439 * Try open the path and query its file name information.
440 */
441 hFile = CreateFile(pszPath,
442 GENERIC_READ,
443 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
444 NULL,
445 OPEN_EXISTING,
446 FILE_FLAG_BACKUP_SEMANTICS,
447 NULL);
448 if (hFile != INVALID_HANDLE_VALUE)
449 {
450 /* check that the driver letter is correct first (reparse / symlink issues). */
451 memset(&Ios, 0, sizeof(Ios));
452 rcNt = g_pfnNtQueryVolumeInformationFile(hFile, &Ios, pFsVolInfo, sizeof(*pFsVolInfo), MY_FileFsVolumeInformation);
453 if (rcNt >= 0)
454 {
455 /** @todo do a quick search and try correct the drive letter? */
456 if ( pFsVolInfo->VolumeCreationTime.QuadPart == g_aVolumeInfo[iDrv].VolumeCreationTime.QuadPart
457 && pFsVolInfo->VolumeSerialNumber == g_aVolumeInfo[iDrv].VolumeSerialNumber)
458 {
459 memset(&Ios, 0, sizeof(Ios));
460 rcNt = g_pfnNtQueryInformationFile(hFile, &Ios, abBuf, sizeof(abBuf), MY_FileNameInformation);
461 if (rcNt >= 0)
462 {
463 cchOut = WideCharToMultiByte(CP_ACP, 0,
464 pFileNameInfo->FileName, pFileNameInfo->FileNameLength / sizeof(WCHAR),
465 psz, (int)(cchFull - (psz - pszFull) - 2), NULL, NULL);
466 if (cchOut > 0)
467 {
468 const char *pszEnd;
469#if 0
470 /* upper case the server and share */
471 if (fUnc)
472 {
473 for (psz++; *psz != '/' && *psz != '\\'; psz++)
474 *psz = toupper(*psz);
475 for (psz++; *psz != '/' && *psz != '\\'; psz++)
476 *psz = toupper(*psz);
477 }
478#endif
479 /* add trailing slash on directories if input has it. */
480 pszEnd = strchr(pszPath, '\0');
481 if ( (pszEnd[-1] == '/' || pszEnd[-1] == '\\')
482 && psz[cchOut - 1] != '\\'
483 && psz[cchOut - 1] != '//')
484 psz[cchOut++] = '\\';
485
486 /* make sure it's terminated */
487 psz[cchOut] = '\0';
488 rc = 0;
489 }
490 else
491 rc = -3;
492 }
493 else
494 rc = -4;
495 }
496 else
497 rc = -5;
498 }
499 else
500 rc = -6;
501 CloseHandle(hFile);
502 }
503 else
504 rc = -7;
505 return rc;
506}
507
508/**
509 * Somewhat similar to fullpath, except that it will fix
510 * the case of existing path components.
511 */
512void
513nt_fullpath(const char *pszPath, char *pszFull, size_t cchFull)
514{
515#if 0
516 static int s_cHits = 0;
517 static int s_cFallbacks = 0;
518#endif
519
520 /*
521 * The simple case, the file / dir / whatever exists and can be
522 * queried without problems and spaces.
523 */
524 if (nt_get_filename_info(pszPath, pszFull, cchFull) == 0)
525 {
526 /** @todo make nt_get_filename_info return spaceless path. */
527 if (strchr(pszFull, ' '))
528 w32_fixcase(pszFull);
529#if 0
530 fprintf(stdout, "nt #%d - %s\n", ++s_cHits, pszFull);
531 fprintf(stdout, " #%d - %s\n", s_cHits, pszPath);
532#endif
533 return;
534 }
535 if (g_pfnNtQueryInformationFile)
536 {
537 /* do _fullpath and drop off path elements until we get a hit... - later */
538 }
539
540 /*
541 * For now, simply fall back on the old method.
542 */
543 _fullpath(pszFull, pszPath, cchFull);
544 w32_fixcase(pszFull);
545#if 0
546 fprintf(stderr, "fb #%d - %s\n", ++s_cFallbacks, pszFull);
547 fprintf(stderr, " #%d - %s\n", s_cFallbacks, pszPath);
548#endif
549}
550
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