VirtualBox

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

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

rebranding: IPRT files again.

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