VirtualBox

source: vbox/trunk/src/VBox/Runtime/dir.cpp@ 3672

Last change on this file since 3672 was 3672, checked in by vboxsync, 17 years ago

RT_OS_* and RT_ARCH_* for Runtime/ and Support/

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 20.4 KB
Line 
1/* $Id: dir.cpp 3672 2007-07-17 12:39:30Z vboxsync $ */
2/** @file
3 * innotek Portable Runtime - Directory Manipulation.
4 */
5
6/*
7 * Copyright (C) 2006-2007 innotek GmbH
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License as published by the Free Software Foundation,
13 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
14 * distribution. VirtualBox OSE is distributed in the hope that it will
15 * be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * If you received this file as part of a commercial VirtualBox
18 * distribution, then only the terms of your commercial VirtualBox
19 * license agreement apply instead of the previous paragraph.
20 */
21
22
23/*******************************************************************************
24* Header Files *
25*******************************************************************************/
26#define LOG_GROUP RTLOGGROUP_DIR
27#ifdef RT_OS_WINDOWS
28# include <Windows.h>
29#else
30# include <dirent.h>
31#endif
32
33#include <iprt/dir.h>
34#include <iprt/path.h>
35#include <iprt/alloc.h>
36#include <iprt/log.h>
37#include <iprt/param.h>
38#include <iprt/string.h>
39#include <iprt/err.h>
40#include <iprt/assert.h>
41#include <iprt/uni.h>
42#include "internal/fs.h"
43#include "internal/dir.h"
44
45
46static DECLCALLBACK(bool) rtDirFilterWinNtMatch(PRTDIR pDir, const char *pszName);
47static DECLCALLBACK(bool) rtDirFilterWinNtMatchNoWildcards(PRTDIR pDir, const char *pszName);
48DECLINLINE(bool) rtDirFilterWinNtMatchEon(PCRTUNICP puszFilter);
49static bool rtDirFilterWinNtMatchDosStar(unsigned iDepth, RTUNICP uc, const char *pszNext, PCRTUNICP puszFilter);
50static bool rtDirFilterWinNtMatchStar(unsigned iDepth, RTUNICP uc, const char *pszNext, PCRTUNICP puszFilter);
51static bool rtDirFilterWinNtMatchBase(unsigned iDepth, const char *pszName, PCRTUNICP puszFilter);
52
53
54
55RTDECL(int) RTDirCreateFullPath(const char *pszPath, RTFMODE fMode)
56{
57 /*
58 * Resolve the path.
59 */
60 char szAbsPath[RTPATH_MAX];
61 int rc = RTPathAbs(pszPath, szAbsPath, sizeof(szAbsPath));
62 if (RT_FAILURE(rc))
63 return rc;
64
65 /*
66 * Iterate the path components making sure each of them exists.
67 */
68 /* skip volume name */
69 char *psz = &szAbsPath[rtPathVolumeSpecLen(szAbsPath)];
70
71 /* skip the root slash if any */
72 if ( psz[0] == '/'
73#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
74 || psz[0] == '\\'
75#endif
76 )
77 psz++;
78
79 /* iterate over path components. */
80 do
81 {
82 /* the next component is NULL, stop iterating */
83 if (!*psz)
84 break;
85#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
86 psz = strpbrk(psz, "\\/");
87#else
88 psz = strchr(psz, '/');
89#endif
90 if (psz)
91 *psz = '\0';
92 /*
93 * ASSUME that RTDirCreate will return VERR_ALREADY_EXISTS and not VERR_ACCESS_DENIED in those cases
94 * where the directory exists but we don't have write access to the parent directory.
95 */
96 rc = RTDirCreate(szAbsPath, fMode);
97 if (rc == VERR_ALREADY_EXISTS)
98 rc = VINF_SUCCESS;
99 if (!psz)
100 break;
101 *psz++ = RTPATH_DELIMITER;
102 } while (RT_SUCCESS(rc));
103
104 return rc;
105}
106
107
108/**
109 * Filter a the filename in the against a filter.
110 *
111 * @returns true if the name matches the filter.
112 * @returns false if the name doesn't match filter.
113 * @param pDir The directory handle.
114 * @param pszName The path to match to the filter.
115 */
116static DECLCALLBACK(bool) rtDirFilterWinNtMatchNoWildcards(PRTDIR pDir, const char *pszName)
117{
118 /*
119 * Walk the string and compare.
120 */
121 PCRTUNICP pucFilter = pDir->puszFilter;
122 const char *psz = pszName;
123 RTUNICP uc;
124 do
125 {
126 int rc = RTStrGetCpEx(&psz, &uc);
127 AssertRCReturn(rc, false);
128 RTUNICP ucFilter = *pucFilter++;
129 if ( uc != ucFilter
130 && RTUniCpToUpper(uc) != ucFilter)
131 return false;
132 } while (uc);
133 return true;
134}
135
136
137/**
138 * Matches end of name.
139 */
140DECLINLINE(bool) rtDirFilterWinNtMatchEon(PCRTUNICP puszFilter)
141{
142 RTUNICP ucFilter;
143 while ( (ucFilter = *puszFilter) == '>'
144 || ucFilter == '<'
145 || ucFilter == '*'
146 || ucFilter == '"')
147 puszFilter++;
148 return !ucFilter;
149}
150
151
152/**
153 * Recursive star matching.
154 * Practically the same as normal star, except that the dos star stops
155 * when hitting the last dot.
156 *
157 * @returns true on match.
158 * @returns false on miss.
159 */
160static bool rtDirFilterWinNtMatchDosStar(unsigned iDepth, RTUNICP uc, const char *pszNext, PCRTUNICP puszFilter)
161{
162 AssertReturn(iDepth++ < 256, false);
163
164 /*
165 * If there is no dos star, we should work just like the NT star.
166 * Since that's generally faster algorithms, we jump down to there if we can.
167 */
168 const char *pszDosDot = strrchr(pszNext, '.');
169 if (!pszDosDot && uc == '.')
170 pszDosDot = pszNext - 1;
171 if (!pszDosDot)
172 return rtDirFilterWinNtMatchStar(iDepth, uc, pszNext, puszFilter);
173
174 /*
175 * Inspect the next filter char(s) until we find something to work on.
176 */
177 RTUNICP ucFilter = *puszFilter++;
178 switch (ucFilter)
179 {
180 /*
181 * The star expression is the last in the pattern.
182 * We're fine if the name ends with a dot.
183 */
184 case '\0':
185 return !pszDosDot[1];
186
187 /*
188 * Simplified by brute force.
189 */
190 case '>': /* dos question mark */
191 case '?':
192 case '*':
193 case '<': /* dos star */
194 case '"': /* dos dot */
195 {
196 puszFilter--;
197 const char *pszStart = pszNext;
198 do
199 {
200 if (rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter))
201 return true;
202 int rc = RTStrGetCpEx(&pszNext, &uc); AssertRCReturn(rc, false);
203 } while ((intptr_t)pszDosDot - (intptr_t)pszNext >= -1);
204
205 /* backtrack and do the current char. */
206 pszNext = RTStrPrevCp(NULL, pszStart); AssertReturn(pszNext, false);
207 return rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter);
208 }
209
210 /*
211 * Ok, we've got zero or more characters.
212 * We'll try match starting at each occurence of this character.
213 */
214 default:
215 {
216 if ( RTUniCpToUpper(uc) == ucFilter
217 && rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter))
218 return true;
219 do
220 {
221 int rc = RTStrGetCpEx(&pszNext, &uc); AssertRCReturn(rc, false);
222 if ( RTUniCpToUpper(uc) == ucFilter
223 && rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter))
224 return true;
225 } while ((intptr_t)pszDosDot - (intptr_t)pszNext >= -1);
226 return false;
227 }
228 }
229 /* won't ever get here! */
230}
231
232
233/**
234 * Recursive star matching.
235 *
236 * @returns true on match.
237 * @returns false on miss.
238 */
239static bool rtDirFilterWinNtMatchStar(unsigned iDepth, RTUNICP uc, const char *pszNext, PCRTUNICP puszFilter)
240{
241 AssertReturn(iDepth++ < 256, false);
242
243 /*
244 * Inspect the next filter char(s) until we find something to work on.
245 */
246 for (;;)
247 {
248 RTUNICP ucFilter = *puszFilter++;
249 switch (ucFilter)
250 {
251 /*
252 * The star expression is the last in the pattern.
253 * Cool, that means we're done!
254 */
255 case '\0':
256 return true;
257
258 /*
259 * Just in case (doubt we ever get here), just merge it with the current one.
260 */
261 case '*':
262 break;
263
264 /*
265 * Skip a fixed number of chars.
266 * Figure out how many by walking the filter ignoring '*'s.
267 */
268 case '?':
269 {
270 unsigned cQms = 1;
271 while ((ucFilter = *puszFilter) == '*' || ucFilter == '?')
272 {
273 cQms += ucFilter == '?';
274 puszFilter++;
275 }
276 do
277 {
278 if (!uc)
279 return false;
280 int rc = RTStrGetCpEx(&pszNext, &uc); AssertRCReturn(rc, false);
281 } while (--cQms > 0);
282 /* done? */
283 if (!ucFilter)
284 return true;
285 break;
286 }
287
288 /*
289 * The simple way is to try char by char and match the remaining
290 * expression. If it's trailing we're done.
291 */
292 case '>': /* dos question mark */
293 {
294 if (rtDirFilterWinNtMatchEon(puszFilter))
295 return true;
296 const char *pszStart = pszNext;
297 do
298 {
299 if (rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter))
300 return true;
301 int rc = RTStrGetCpEx(&pszNext, &uc); AssertRCReturn(rc, false);
302 } while (uc);
303
304 /* backtrack and do the current char. */
305 pszNext = RTStrPrevCp(NULL, pszStart); AssertReturn(pszNext, false);
306 return rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter);
307 }
308
309 /*
310 * This bugger is interesting.
311 * Time for brute force. Iterate the name char by char.
312 */
313 case '<':
314 {
315 do
316 {
317 if (rtDirFilterWinNtMatchDosStar(iDepth, uc, pszNext, puszFilter))
318 return true;
319 int rc = RTStrGetCpEx(&pszNext, &uc); AssertRCReturn(rc, false);
320 } while (uc);
321 return false;
322 }
323
324 /*
325 * This guy matches a '.' or the end of the name.
326 * It's very simple if the rest of the filter expression also matches eon.
327 */
328 case '"':
329 if (rtDirFilterWinNtMatchEon(puszFilter))
330 return true;
331 ucFilter = '.';
332 /* fall thru */
333
334 /*
335 * Ok, we've got zero or more characters.
336 * We'll try match starting at each occurence of this character.
337 */
338 default:
339 {
340 do
341 {
342 if ( RTUniCpToUpper(uc) == ucFilter
343 && rtDirFilterWinNtMatchBase(iDepth, pszNext, puszFilter))
344 return true;
345 int rc = RTStrGetCpEx(&pszNext, &uc); AssertRCReturn(rc, false);
346 } while (uc);
347 return false;
348 }
349 }
350 } /* for (;;) */
351
352 /* won't ever get here! */
353}
354
355
356/**
357 * Filter a the filename in the against a filter.
358 *
359 * The rules are as follows:
360 * '?' Matches exactly one char.
361 * '*' Matches zero or more chars.
362 * '<' The dos star, matches zero or more chars except the DOS dot.
363 * '>' The dos question mark, matches one char, but dots and end-of-name eats them.
364 * '"' The dos dot, matches a dot or end-of-name.
365 *
366 * @returns true if the name matches the filter.
367 * @returns false if the name doesn't match filter.
368 * @param iDepth The recursion depth.
369 * @param pszName The path to match to the filter.
370 * @param puszFilter The filter string.
371 */
372static bool rtDirFilterWinNtMatchBase(unsigned iDepth, const char *pszName, PCRTUNICP puszFilter)
373{
374 AssertReturn(iDepth++ < 256, false);
375
376 /*
377 * Walk the string and match it up char by char.
378 */
379 RTUNICP uc;
380 do
381 {
382 RTUNICP ucFilter = *puszFilter++;
383 int rc = RTStrGetCpEx(&pszName, &uc); AssertRCReturn(rc, false);
384 switch (ucFilter)
385 {
386 /* Exactly one char. */
387 case '?':
388 if (!uc)
389 return false;
390 break;
391
392 /* One char, but the dos dot and end-of-name eats '>' and '<'. */
393 case '>': /* dos ? */
394 if (!uc)
395 return rtDirFilterWinNtMatchEon(puszFilter);
396 if (uc == '.')
397 {
398 while ((ucFilter = *puszFilter) == '>' || ucFilter == '<')
399 puszFilter++;
400 if (ucFilter == '"' || ucFilter == '.') /* not 100% sure about the last dot */
401 ++puszFilter;
402 else /* the does question mark doesn't match '.'s, so backtrack. */
403 pszName = RTStrPrevCp(NULL, pszName);
404 }
405 break;
406
407 /* Match a dot or the end-of-name. */
408 case '"': /* dos '.' */
409 if (uc != '.')
410 {
411 if (uc)
412 return false;
413 return rtDirFilterWinNtMatchEon(puszFilter);
414 }
415 break;
416
417 /* zero or more */
418 case '*':
419 return rtDirFilterWinNtMatchStar(iDepth, uc, pszName, puszFilter);
420 case '<': /* dos '*' */
421 return rtDirFilterWinNtMatchDosStar(iDepth, uc, pszName, puszFilter);
422
423
424 /* uppercased match */
425 default:
426 {
427 if (RTUniCpToUpper(uc) != ucFilter)
428 return false;
429 break;
430 }
431 }
432 } while (uc);
433
434 return true;
435}
436
437
438/**
439 * Filter a the filename in the against a filter.
440 *
441 * @returns true if the name matches the filter.
442 * @returns false if the name doesn't match filter.
443 * @param pDir The directory handle.
444 * @param pszName The path to match to the filter.
445 */
446static DECLCALLBACK(bool) rtDirFilterWinNtMatch(PRTDIR pDir, const char *pszName)
447{
448 return rtDirFilterWinNtMatchBase(0, pszName, pDir->puszFilter);
449}
450
451
452/**
453 * Initializes a WinNt like wildcard filter.
454 *
455 * @returns Pointer to the filter function.
456 * @returns NULL if the filter doesn't filter out anything.
457 * @param pDir The directory handle (not yet opened).
458 */
459static PFNRTDIRFILTER rtDirFilterWinNtInit(PRTDIR pDir)
460{
461 /*
462 * Check for the usual * and <"< (*.* in DOS language) patterns.
463 */
464 if ( (pDir->cchFilter == 1 && pDir->pszFilter[0] == '*')
465 || (pDir->cchFilter == 3 && !memcmp(pDir->pszFilter, "<\".>", 3))
466 )
467 return NULL;
468
469 /*
470 * Uppercase the expression, also do a little optimizations when possible.
471 */
472 bool fHaveWildcards = false;
473 unsigned iRead = 0;
474 unsigned iWrite = 0;
475 while (iRead < pDir->cucFilter)
476 {
477 RTUNICP uc = pDir->puszFilter[iRead++];
478 if (uc == '*')
479 {
480 fHaveWildcards = true;
481 /* remove extra stars. */
482 RTUNICP uc2;
483 while ((uc2 = pDir->puszFilter[iRead + 1]) == '*')
484 iRead++;
485 }
486 else if (uc == '?' || uc == '>' || uc == '<' || uc == '"')
487 fHaveWildcards = true;
488 else
489 uc = RTUniCpToUpper(uc);
490 pDir->puszFilter[iWrite++] = uc;
491 }
492 pDir->puszFilter[iWrite] = 0;
493 pDir->cucFilter = iWrite;
494
495 return fHaveWildcards
496 ? rtDirFilterWinNtMatch
497 : rtDirFilterWinNtMatchNoWildcards;
498}
499
500
501/**
502 * Common worker for opening a directory.
503 *
504 * @returns IPRT status code.
505 * @param ppDir Where to store the directory handle.
506 * @param pszPath The specified path.
507 * @param pszFilter Pointer to where the filter start in the path. NULL if no filter.
508 * @param enmFilter The type of filter to apply.
509 */
510static int rtDirOpenCommon(PRTDIR *ppDir, const char *pszPath, const char *pszFilter, RTDIRFILTER enmFilter)
511{
512 /*
513 * Expand the path.
514 *
515 * The purpose of this exercise to have the abs path around
516 * for querying extra information about the objects we list.
517 * As a sideeffect we also validate the path here.
518 */
519 char szRealPath[RTPATH_MAX + 1];
520 int rc;
521 size_t cchFilter; /* includes '\0'. */
522 size_t cucFilter; /* includes U+0. */
523 if (!pszFilter)
524 {
525 cchFilter = cucFilter = 0;
526 rc = RTPathReal(pszPath, szRealPath, sizeof(szRealPath) - 1);
527 }
528 else
529 {
530 cchFilter = strlen(pszFilter) + 1;
531 cucFilter = RTStrUniLen(pszFilter) + 1;
532
533 if (pszFilter != pszPath)
534 {
535 /* yea, I'm lazy. sue me. */
536 char *pszTmp = RTStrDup(pszPath);
537 if (!pszTmp)
538 return VERR_NO_MEMORY;
539 pszTmp[pszFilter - pszPath] = '\0';
540 rc = RTPathReal(pszTmp, szRealPath, sizeof(szRealPath) - 1);
541 RTStrFree(pszTmp);
542 }
543 else
544 rc = RTPathReal(".", szRealPath, sizeof(szRealPath) - 1);
545 }
546 if (RT_FAILURE(rc))
547 return rc;
548
549 /* add trailing '/' if missing. */
550 size_t cchRealPath = strlen(szRealPath);
551 if (!RTPATH_IS_SEP(szRealPath[cchRealPath - 1]))
552 {
553 szRealPath[cchRealPath++] = RTPATH_SLASH;
554 szRealPath[cchRealPath] = '\0';
555 }
556
557 /*
558 * Allocate and initialize the directory handle.
559 */
560 PRTDIR pDir = (PRTDIR)RTMemAlloc(sizeof(RTDIR) + cchRealPath + 1 + 4 + cchFilter + cucFilter * sizeof(RTUNICP));
561 if (!pDir)
562 return VERR_NO_MEMORY;
563
564 /* initialize it */
565 pDir->u32Magic = RTDIR_MAGIC;
566 if (cchFilter)
567 {
568 pDir->puszFilter = (PRTUNICP)(pDir + 1);
569 rc = RTStrToUniEx(pszFilter, RTSTR_MAX, &pDir->puszFilter, cucFilter, &pDir->cucFilter);
570 AssertRC(rc);
571 pDir->pszFilter = (char *)memcpy(pDir->puszFilter + cucFilter, pszFilter, cchFilter);
572 pDir->cchFilter = cchFilter - 1;
573 }
574 else
575 {
576 pDir->puszFilter = NULL;
577 pDir->cucFilter = 0;
578 pDir->pszFilter = NULL;
579 pDir->cchFilter = 0;
580 }
581 pDir->enmFilter = enmFilter;
582 switch (enmFilter)
583 {
584 default:
585 case RTDIRFILTER_NONE:
586 pDir->pfnFilter = NULL;
587 break;
588 case RTDIRFILTER_WINNT:
589 pDir->pfnFilter = rtDirFilterWinNtInit(pDir);
590 break;
591 case RTDIRFILTER_UNIX:
592 pDir->pfnFilter = NULL;
593 break;
594 case RTDIRFILTER_UNIX_UPCASED:
595 pDir->pfnFilter = NULL;
596 break;
597 }
598 pDir->cchPath = cchRealPath;
599 pDir->pszPath = (char *)memcpy((char *)(pDir + 1) + cucFilter * sizeof(RTUNICP) + cchFilter,
600 szRealPath, cchRealPath + 1);
601 pDir->fDataUnread = false;
602#ifndef RT_DONT_CONVERT_FILENAMES
603 pDir->pszName = NULL;
604 pDir->cchName = 0;
605#endif
606
607 /*
608 * Hand it over to the native part.
609 */
610 rc = rtOpenDirNative(pDir, szRealPath);
611 if (RT_SUCCESS(rc))
612 *ppDir = pDir;
613 else
614 RTMemFree(pDir);
615
616 return rc;
617}
618
619
620
621RTDECL(int) RTDirOpen(PRTDIR *ppDir, const char *pszPath)
622{
623 /*
624 * Validate input.
625 */
626 AssertMsgReturn(VALID_PTR(ppDir), ("%p\n", ppDir), VERR_INVALID_POINTER);
627 AssertMsgReturn(VALID_PTR(pszPath), ("%p\n", pszPath), VERR_INVALID_POINTER);
628
629 /*
630 * Take common cause with RTDirOpenFiltered().
631 */
632 int rc = rtDirOpenCommon(ppDir, pszPath, NULL, RTDIRFILTER_NONE);
633 LogFlow(("RTDirOpen(%p:{%p}, %p:{%s}): return %Rrc\n", ppDir, *ppDir, pszPath, pszPath, rc));
634 return rc;
635}
636
637
638RTDECL(int) RTDirOpenFiltered(PRTDIR *ppDir, const char *pszPath, RTDIRFILTER enmFilter)
639{
640 /*
641 * Validate input.
642 */
643 AssertMsgReturn(VALID_PTR(ppDir), ("%p\n", ppDir), VERR_INVALID_POINTER);
644 AssertMsgReturn(VALID_PTR(pszPath), ("%p\n", pszPath), VERR_INVALID_POINTER);
645 switch (enmFilter)
646 {
647 case RTDIRFILTER_UNIX:
648 case RTDIRFILTER_UNIX_UPCASED:
649 AssertMsgFailed(("%d is not implemented!\n", enmFilter));
650 return VERR_NOT_IMPLEMENTED;
651 case RTDIRFILTER_NONE:
652 case RTDIRFILTER_WINNT:
653 break;
654 default:
655 AssertMsgFailedReturn(("%d\n", enmFilter), VERR_INVALID_PARAMETER);
656 }
657
658 /*
659 * Find the last component, i.e. where the filter criteria starts and the dir name ends.
660 */
661 const char *pszFilter = enmFilter != RTDIRFILTER_NONE
662 ? RTPathFilename(pszPath)
663 : NULL;
664
665 /*
666 * Call worker common with RTDirOpen which will verify the path, allocate
667 * and initialize the handle, and finally call the backend.
668 */
669 int rc = rtDirOpenCommon(ppDir, pszPath, pszFilter, enmFilter);
670
671 LogFlow(("RTDirOpenFiltered(%p:{%p}, %p:{%s}, %d): return %Rrc\n",
672 ppDir, *ppDir, pszPath, pszPath, enmFilter, rc));
673 return rc;
674}
675
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