VirtualBox

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

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

Only use FileNameInformation on NTFS volumes because at least FAT32 will screw up the case.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 11.7 KB
Line 
1/* $Id: nt_fullpath.c 1175 2007-10-04 18:33:47Z 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
154
155 /* find the end of the component. */
156 pszEnd = psz;
157 while (*pszEnd && *pszEnd != '/' && *pszEnd != '\\')
158 pszEnd++;
159
160 /* replace the end with "?\0" */
161 chSaved0 = pszEnd[0];
162 chSaved1 = pszEnd[1];
163 pszEnd[0] = '?';
164 pszEnd[1] = '\0';
165
166 /* find the right filename. */
167 hDir = FindFirstFile(pszPath, &FindFileData);
168 pszEnd[1] = chSaved1;
169 if (!hDir)
170 {
171 cchLast = psz - pszPath;
172 memcpy(s_szLast, pszPath, cchLast + 1);
173 pszEnd[0] = chSaved0;
174 return;
175 }
176 pszEnd[0] = '\0';
177 while ( (iLongNameDiff = stricmp(FindFileData.cFileName, psz))
178 && stricmp(FindFileData.cAlternateFileName, psz))
179 {
180 if (!FindNextFile(hDir, &FindFileData))
181 {
182 cchLast = psz - pszPath;
183 memcpy(s_szLast, pszPath, cchLast + 1);
184 pszEnd[0] = chSaved0;
185 return;
186 }
187 }
188 strcpy(psz, !iLongNameDiff ? FindFileData.cFileName : FindFileData.cAlternateFileName);
189 pszEnd[0] = chSaved0;
190 FindClose(hDir);
191
192 /* advance to the next component */
193 if (!chSaved0)
194 {
195 psz = pszEnd;
196 break;
197 }
198 psz = pszEnd + 1;
199 my_assert(*psz != '/' && *psz != '\\');
200 }
201
202 /* *psz == '\0', the end. */
203 cchLast = psz - pszPath;
204 memcpy(s_szLast, pszPath, cchLast + 1);
205#undef my_assert
206}
207
208#define MY_FileNameInformation 9
209
210typedef struct _MY_FILE_NAME_INFORMATION
211{
212 ULONG FileNameLength;
213 WCHAR FileName[1];
214} MY_FILE_NAME_INFORMATION, *PMY_FILE_NAME_INFORMATION;
215
216typedef struct _IO_STATUS_BLOCK
217{
218 union
219 {
220 LONG Status;
221 PVOID Pointer;
222 };
223 ULONG_PTR Information;
224} MY_IO_STATUS_BLOCK, *PMY_IO_STATUS_BLOCK;
225
226static BOOL g_fInitialized = FALSE;
227static int g_afNtfsDrives['Z' - 'A' + 1];
228static LONG (NTAPI *g_pfnNtQueryInformationFile)(HANDLE FileHandle,
229 PMY_IO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation,
230 ULONG Length, ULONG FileInformationClass);
231
232
233int
234nt_get_filename_info(const char *pszPath, char *pszFull, size_t cchFull)
235{
236 static char abBuf[8192];
237 PMY_FILE_NAME_INFORMATION pFileNameInfo = (PMY_FILE_NAME_INFORMATION)abBuf;
238 MY_IO_STATUS_BLOCK Ios;
239 LONG rcNt;
240 HANDLE hFile;
241 int cchOut;
242 char *psz;
243
244 /*
245 * Check for NtQueryInformationFile the first time around.
246 */
247 if (!g_fInitialized)
248 {
249 g_fInitialized = TRUE;
250 if (!getenv("KMK_DONT_USE_NT_QUERY_INFORMATION_FILE"))
251 *(FARPROC *)&g_pfnNtQueryInformationFile =
252 GetProcAddress(LoadLibrary("ntdll.dll"), "NtQueryInformationFile");
253 if (g_pfnNtQueryInformationFile)
254 {
255 unsigned i;
256 for (i = 0; i < sizeof(g_afNtfsDrives) / sizeof(g_afNtfsDrives[0]); i++ )
257 g_afNtfsDrives[i] = -1;
258 }
259 }
260 if (!g_pfnNtQueryInformationFile)
261 return -1;
262
263 /*
264 * The FileNameInformation we get is relative to where the volume is mounted,
265 * so we have to extract the driveletter prefix ourselves.
266 *
267 * FIXME: This will probably not work for volumes mounted in NTFS sub-directories.
268 */
269 psz = pszFull;
270 if (pszPath[0] == '\\' || pszPath[0] == '/')
271 {
272 /* unc or root of volume */
273 if ( (pszPath[1] == '\\' || pszPath[1] == '/')
274 && (pszPath[2] != '\\' || pszPath[2] == '/'))
275 {
276#if 0 /* don't bother with unc yet. */
277 /* unc - we get the server + name back */
278 *psz++ = '\\';
279#endif
280 return -1;
281 }
282 /* root slash */
283 *psz++ = _getdrive() + 'A' - 1;
284 *psz++ = ':';
285 }
286 else if (pszPath[1] == ':' && isalpha(pszPath[0]))
287 {
288 /* drive letter */
289 *psz++ = toupper(pszPath[0]);
290 *psz++ = ':';
291 }
292 else
293 {
294 /* relative */
295 *psz++ = _getdrive() + 'A' - 1;
296 *psz++ = ':';
297 }
298
299 /*
300 * Fat32 doesn't return filenames with the correct case, so restrict it
301 * to NTFS volumes for now.
302 */
303 if (g_afNtfsDrives[*pszFull - 'A'] == -1)
304 {
305 char szFSName[32];
306
307 psz[0] = '\\';
308 psz[1] = '\0';
309 if ( GetVolumeInformation(pszFull,
310 NULL, 0, /* volume name */
311 NULL, /* serial number */
312 NULL, /* max component */
313 NULL, /* volume attribs */
314 szFSName,
315 sizeof(szFSName))
316 && !strcmp(szFSName, "NTFS"))
317 g_afNtfsDrives[*pszFull - 'A'] = 1;
318 else
319 g_afNtfsDrives[*pszFull - 'A'] = 0;
320 }
321 if (!g_afNtfsDrives[*pszFull - 'A'])
322 return -1;
323
324 /*
325 * Try open the path and query its file name information.
326 */
327 hFile = CreateFile(pszPath,
328 GENERIC_READ,
329 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
330 NULL,
331 OPEN_EXISTING,
332 FILE_FLAG_BACKUP_SEMANTICS,
333 NULL);
334 if (hFile)
335 {
336 memset(&Ios, 0, sizeof(Ios));
337 rcNt = g_pfnNtQueryInformationFile(hFile, &Ios, abBuf, sizeof(abBuf), MY_FileNameInformation);
338 CloseHandle(hFile);
339 if (rcNt >= 0)
340 {
341 cchOut = WideCharToMultiByte(CP_ACP, 0,
342 pFileNameInfo->FileName, pFileNameInfo->FileNameLength / sizeof(WCHAR),
343 psz, (int)(cchFull - (psz - pszFull) - 2), NULL, NULL);
344 if (cchOut > 0)
345 {
346 const char *pszEnd;
347#if 0
348 /* upper case the server and share */
349 if (fUnc)
350 {
351 for (psz++; *psz != '/' && *psz != '\\'; psz++)
352 *psz = toupper(*psz);
353 for (psz++; *psz != '/' && *psz != '\\'; psz++)
354 *psz = toupper(*psz);
355 }
356#endif
357 /* add trailing slash on directories if input has it. */
358 pszEnd = strchr(pszPath, '\0');
359 if ( (pszEnd[-1] == '/' || pszEnd[-1] == '\\')
360 && psz[cchOut - 1] != '\\'
361 && psz[cchOut - 1] != '//')
362 psz[cchOut++] = '\\';
363
364 /* make sure it's terminated */
365 psz[cchOut] = '\0';
366 return 0;
367 }
368 return -3;
369 }
370 }
371 return -2;
372}
373
374/**
375 * Somewhat similar to fullpath, except that it will fix
376 * the case of existing path components.
377 */
378void
379nt_fullpath(const char *pszPath, char *pszFull, size_t cchFull)
380{
381#if 0
382 static int s_cHits = 0;
383 static int s_cFallbacks = 0;
384#endif
385
386 /*
387 * The simple case, the file / dir / whatever exists and can be
388 * queried without problems.
389 */
390 if (nt_get_filename_info(pszPath, pszFull, cchFull) == 0)
391 {
392#if 0
393 fprintf(stderr, "nt #%d - %s\n", ++s_cHits, pszFull);
394#endif
395 return;
396 }
397 if (g_pfnNtQueryInformationFile)
398 {
399 /* do _fullpath and drop off path elements until we get a hit... - later */
400 }
401
402 /*
403 * For now, simply fall back on the old method.
404 */
405 _fullpath(pszFull, pszPath, cchFull);
406 w32_fixcase(pszFull);
407#if 0
408 fprintf(stderr, "fb #%d - %s\n", ++s_cFallbacks, pszFull);
409#endif
410}
411
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