VirtualBox

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

Last change on this file since 2948 was 2849, checked in by bird, 8 years ago

split nt_fullpath_cached out into its own file.

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