VirtualBox

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

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

Biggest check-in ever. New source code headers for all (C) innotek files.

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