VirtualBox

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

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

fixed a symlink problem.

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