VirtualBox

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

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

Don't use NtQueryInformationFile on remote drives, they typically lie about the file system and we don't correctly strip off the server name.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 18.2 KB
Line 
1/* $Id: nt_fullpath.c 1643 2008-05-19 01:22:27Z 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
272#define MY_FileFsDeviceInformation 4
273typedef struct MY_FILE_FS_DEVICE_INFORMATION
274{
275 ULONG DeviceType;
276 ULONG Characteristics;
277} MY_FILE_FS_DEVICE_INFORMATION, *PMY_FILE_FS_DEVICE_INFORMATION;
278#define MY_FILE_DEVICE_DISK 7
279#define MY_FILE_DEVICE_DISK_FILE_SYSTEM 8
280#define MY_FILE_DEVICE_FILE_SYSTEM 9
281#define MY_FILE_DEVICE_VIRTUAL_DISK 36
282
283
284typedef struct _IO_STATUS_BLOCK
285{
286 union
287 {
288 LONG Status;
289 PVOID Pointer;
290 };
291 ULONG_PTR Information;
292} MY_IO_STATUS_BLOCK, *PMY_IO_STATUS_BLOCK;
293
294static BOOL g_fInitialized = FALSE;
295static int g_afNtfsDrives['Z' - 'A' + 1];
296static MY_FILE_FS_VOLUME_INFORMATION g_aVolumeInfo['Z' - 'A' + 1];
297
298static LONG (NTAPI *g_pfnNtQueryInformationFile)(HANDLE FileHandle,
299 PMY_IO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation,
300 ULONG Length, ULONG FileInformationClass);
301static LONG (NTAPI *g_pfnNtQueryVolumeInformationFile)(HANDLE FileHandle,
302 PMY_IO_STATUS_BLOCK IoStatusBlock, PVOID FsInformation,
303 ULONG Length, ULONG FsInformationClass);
304
305
306int
307nt_get_filename_info(const char *pszPath, char *pszFull, size_t cchFull)
308{
309 static char abBuf[8192];
310 PMY_FILE_NAME_INFORMATION pFileNameInfo = (PMY_FILE_NAME_INFORMATION)abBuf;
311 PMY_FILE_FS_VOLUME_INFORMATION pFsVolInfo = (PMY_FILE_FS_VOLUME_INFORMATION)abBuf;
312 MY_IO_STATUS_BLOCK Ios;
313 LONG rcNt;
314 HANDLE hFile;
315 int cchOut;
316 char *psz;
317 int iDrv;
318 int rc;
319
320 /*
321 * Check for NtQueryInformationFile the first time around.
322 */
323 if (!g_fInitialized)
324 {
325 g_fInitialized = TRUE;
326 if (!getenv("KMK_DONT_USE_NT_QUERY_INFORMATION_FILE"))
327 {
328 *(FARPROC *)&g_pfnNtQueryInformationFile =
329 GetProcAddress(LoadLibrary("ntdll.dll"), "NtQueryInformationFile");
330 *(FARPROC *)&g_pfnNtQueryVolumeInformationFile =
331 GetProcAddress(LoadLibrary("ntdll.dll"), "NtQueryVolumeInformationFile");
332 }
333 if ( g_pfnNtQueryInformationFile
334 && g_pfnNtQueryVolumeInformationFile)
335 {
336 unsigned i;
337 for (i = 0; i < sizeof(g_afNtfsDrives) / sizeof(g_afNtfsDrives[0]); i++ )
338 g_afNtfsDrives[i] = -1;
339 }
340 else
341 {
342 g_pfnNtQueryVolumeInformationFile = NULL;
343 g_pfnNtQueryInformationFile = NULL;
344 }
345 }
346 if (!g_pfnNtQueryInformationFile)
347 return -1;
348
349 /*
350 * The FileNameInformation we get is relative to where the volume is mounted,
351 * so we have to extract the driveletter prefix ourselves.
352 *
353 * FIXME: This will probably not work for volumes mounted in NTFS sub-directories.
354 */
355 psz = pszFull;
356 if (pszPath[0] == '\\' || pszPath[0] == '/')
357 {
358 /* unc or root of volume */
359 if ( (pszPath[1] == '\\' || pszPath[1] == '/')
360 && (pszPath[2] != '\\' || pszPath[2] == '/'))
361 {
362#if 0 /* don't bother with unc yet. */
363 /* unc - we get the server + name back */
364 *psz++ = '\\';
365#endif
366 return -1;
367 }
368 /* root slash */
369 *psz++ = _getdrive() + 'A' - 1;
370 *psz++ = ':';
371 }
372 else if (pszPath[1] == ':' && isalpha(pszPath[0]))
373 {
374 /* drive letter */
375 *psz++ = toupper(pszPath[0]);
376 *psz++ = ':';
377 }
378 else
379 {
380 /* relative */
381 *psz++ = _getdrive() + 'A' - 1;
382 *psz++ = ':';
383 }
384 iDrv = *pszFull - 'A';
385
386 /*
387 * Fat32 doesn't return filenames with the correct case, so restrict it
388 * to NTFS volumes for now.
389 */
390 if (g_afNtfsDrives[iDrv] == -1)
391 {
392 /* FSCTL_GET_REPARSE_POINT? Enumerate mount points? */
393 g_afNtfsDrives[iDrv] = 0;
394 psz[0] = '\\';
395 psz[1] = '\0';
396#if 1
397 hFile = CreateFile(pszFull,
398 GENERIC_READ,
399 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
400 NULL,
401 OPEN_EXISTING,
402 FILE_FLAG_BACKUP_SEMANTICS,
403 NULL);
404 if (hFile != INVALID_HANDLE_VALUE)
405 {
406 PMY_FILE_FS_ATTRIBUTE_INFORMATION pFsAttrInfo = (PMY_FILE_FS_ATTRIBUTE_INFORMATION)abBuf;
407
408 memset(&Ios, 0, sizeof(Ios));
409 rcNt = g_pfnNtQueryVolumeInformationFile(hFile, &Ios, abBuf, sizeof(abBuf),
410 MY_FileFsAttributeInformation);
411 if ( rcNt >= 0
412 //&& pFsAttrInfo->FileSystemNameLength == 4
413 && pFsAttrInfo->FileSystemName[0] == 'N'
414 && pFsAttrInfo->FileSystemName[1] == 'T'
415 && pFsAttrInfo->FileSystemName[2] == 'F'
416 && pFsAttrInfo->FileSystemName[3] == 'S'
417 && pFsAttrInfo->FileSystemName[4] == '\0')
418 {
419 memset(&Ios, 0, sizeof(Ios));
420 rcNt = g_pfnNtQueryVolumeInformationFile(hFile, &Ios, &g_aVolumeInfo[iDrv],
421 sizeof(MY_FILE_FS_VOLUME_INFORMATION),
422 MY_FileFsVolumeInformation);
423 if (rcNt >= 0)
424 {
425 DWORD dwDriveType = GetDriveType(pszFull);
426 if ( dwDriveType == DRIVE_FIXED
427 || dwDriveType == DRIVE_RAMDISK)
428 g_afNtfsDrives[iDrv] = 1;
429 }
430 }
431 CloseHandle(hFile);
432 }
433#else
434 {
435 char szFSName[32];
436 if ( GetVolumeInformation(pszFull,
437 NULL, 0, /* volume name */
438 NULL, /* serial number */
439 NULL, /* max component */
440 NULL, /* volume attribs */
441 szFSName,
442 sizeof(szFSName))
443 && !strcmp(szFSName, "NTFS"))
444 {
445 g_afNtfsDrives[iDrv] = 1;
446 }
447 }
448#endif
449 }
450 if (!g_afNtfsDrives[iDrv])
451 return -1;
452
453 /*
454 * Try open the path and query its file name information.
455 */
456 hFile = CreateFile(pszPath,
457 GENERIC_READ,
458 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
459 NULL,
460 OPEN_EXISTING,
461 FILE_FLAG_BACKUP_SEMANTICS,
462 NULL);
463 if (hFile != INVALID_HANDLE_VALUE)
464 {
465 /* check that the driver letter is correct first (reparse / symlink issues). */
466 memset(&Ios, 0, sizeof(Ios));
467 rcNt = g_pfnNtQueryVolumeInformationFile(hFile, &Ios, pFsVolInfo, sizeof(*pFsVolInfo), MY_FileFsVolumeInformation);
468 if (rcNt >= 0)
469 {
470 /** @todo do a quick search and try correct the drive letter? */
471 if ( pFsVolInfo->VolumeCreationTime.QuadPart == g_aVolumeInfo[iDrv].VolumeCreationTime.QuadPart
472 && pFsVolInfo->VolumeSerialNumber == g_aVolumeInfo[iDrv].VolumeSerialNumber)
473 {
474 memset(&Ios, 0, sizeof(Ios));
475 rcNt = g_pfnNtQueryInformationFile(hFile, &Ios, abBuf, sizeof(abBuf), MY_FileNameInformation);
476 if (rcNt >= 0)
477 {
478 cchOut = WideCharToMultiByte(CP_ACP, 0,
479 pFileNameInfo->FileName, pFileNameInfo->FileNameLength / sizeof(WCHAR),
480 psz, (int)(cchFull - (psz - pszFull) - 2), NULL, NULL);
481 if (cchOut > 0)
482 {
483 const char *pszEnd;
484#if 0
485 /* upper case the server and share */
486 if (fUnc)
487 {
488 for (psz++; *psz != '/' && *psz != '\\'; psz++)
489 *psz = toupper(*psz);
490 for (psz++; *psz != '/' && *psz != '\\'; psz++)
491 *psz = toupper(*psz);
492 }
493#endif
494 /* add trailing slash on directories if input has it. */
495 pszEnd = strchr(pszPath, '\0');
496 if ( (pszEnd[-1] == '/' || pszEnd[-1] == '\\')
497 && psz[cchOut - 1] != '\\'
498 && psz[cchOut - 1] != '//')
499 psz[cchOut++] = '\\';
500
501 /* make sure it's terminated */
502 psz[cchOut] = '\0';
503 rc = 0;
504 }
505 else
506 rc = -3;
507 }
508 else
509 rc = -4;
510 }
511 else
512 rc = -5;
513 }
514 else
515 rc = -6;
516 CloseHandle(hFile);
517 }
518 else
519 rc = -7;
520 return rc;
521}
522
523/**
524 * Somewhat similar to fullpath, except that it will fix
525 * the case of existing path components.
526 */
527void
528nt_fullpath(const char *pszPath, char *pszFull, size_t cchFull)
529{
530#if 0
531 static int s_cHits = 0;
532 static int s_cFallbacks = 0;
533#endif
534
535 /*
536 * The simple case, the file / dir / whatever exists and can be
537 * queried without problems and spaces.
538 */
539 if (nt_get_filename_info(pszPath, pszFull, cchFull) == 0)
540 {
541 /** @todo make nt_get_filename_info return spaceless path. */
542 if (strchr(pszFull, ' '))
543 w32_fixcase(pszFull);
544#if 0
545 fprintf(stdout, "nt #%d - %s\n", ++s_cHits, pszFull);
546 fprintf(stdout, " #%d - %s\n", s_cHits, pszPath);
547#endif
548 return;
549 }
550 if (g_pfnNtQueryInformationFile)
551 {
552 /* do _fullpath and drop off path elements until we get a hit... - later */
553 }
554
555 /*
556 * For now, simply fall back on the old method.
557 */
558 _fullpath(pszFull, pszPath, cchFull);
559 w32_fixcase(pszFull);
560#if 0
561 fprintf(stderr, "fb #%d - %s\n", ++s_cFallbacks, pszFull);
562 fprintf(stderr, " #%d - %s\n", s_cFallbacks, pszPath);
563#endif
564}
565
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