1 ///////////////////////////////////////////////////////////////////////////////
2 // $Source: x:/prj/tech/libsrc/cpptools/RCS/filepath.cpp $
4 // $Date: 1998/07/09 09:46:29 $
7 // (c) Copyright 1993-1996 Tom Leonard. All Rights Reserved. Unlimited license granted to Looking Glass Technologies Inc.
30 #include <sys/types.h>
34 #define DEBUG_REDUCE 0
36 #pragma message "DEBUG_REDUCE"
42 #pragma message "DEBUG_CASE"
47 #define FA_NORMAL _A_NORMAL
48 #define FA_RDONLY _A_RDONLY
49 #define FA_HIDDEN _A_HIDDEN
50 #define FA_SYSTEM _A_SYSTEM
51 #define FA_LABEL _A_VOLID
52 #define FA_DIREC _A_SUBDIR
53 #define FA_ARCH _A_ARCH
57 static int _chdrive(int iDrive
)
60 _dos_setdrive(iDrive
, &nDummy
);
61 return !(_getdrive() == iDrive
);
65 static char BASED_CODE pcszWildAll
[] = "*.*";
68 // Default constructor
70 cFilePath::cFilePath()
78 cFilePath::cFilePath(const cFilePath
&filePath
)
85 // Construct a file path from a file specification
87 cFilePath::cFilePath(const cFileSpec
&fileSpec
)
89 FromFileSpec(fileSpec
.GetName());
94 // Construct a file path from a text string
96 cFilePath::cFilePath(const char *pPath
)
103 // Set the path from another path
105 cFilePath
&cFilePath::operator=(const cFilePath
& filePath
)
107 m_path
= filePath
.m_path
;
113 // Set the path from a file specification
115 cFilePath
&cFilePath::operator=(const cFileSpec
& fileSpec
)
117 FromFileSpec(fileSpec
.GetName());
123 // Set the path from a file specification string
125 void cFilePath::FromFileSpec(const char *pFileSpec
)
129 cPathSplitter
PathSplitter(pFileSpec
);
132 unsigned nLengthName
= PathSplitter
.GetDriveLen() + PathSplitter
.GetDirectoryLen();
135 char * p
= s
.GetBuffer(nLengthName
);
136 strncpy(p
, PathSplitter
.GetDrive(), PathSplitter
.GetDriveLen());
137 strncpy(p
+ PathSplitter
.GetDriveLen(), PathSplitter
.GetDirectory(), PathSplitter
.GetDirectoryLen());
138 p
[nLengthName
] = '\0';
139 s
.ReleaseBuffer(nLengthName
);
144 // If the path isn't empty...
145 if (!m_path
.IsEmpty())
147 char c
= m_path
[m_path
.GetLength()-1];
149 // If there is no trailing slash...
156 #define S_ISDIR(m) (m & _S_IFDIR)
160 // Check for existence
162 BOOL
cFilePath::PathExists() const
164 struct _stat StatBuf
;
166 if (IsEmpty() || !GetFullPath(StrFullPath
))
169 const unsigned nLenFull
= StrFullPath
.GetLength();
174 char * const pName
= StrFullPath
.Detach();
176 AssertMsg(pName
[nLenFull
-1] == '\\' || pName
[nLenFull
-1] == '/', "Bad file path detected");
177 pName
[nLenFull
- 1] = '\0'; // get rid of '\\'
179 // Previously used access(), but that doesn't distinguish between
180 // a file and a directory:
181 // int result = access(pName, 0);
182 int result
= _stat(pName
, &StatBuf
);
184 // Okay, it exists -- is it actually a directory?
185 if (S_ISDIR(StatBuf
.st_mode
)) {
188 // Nope -- unset result
200 // Check for existence
202 BOOL
cFilePath::CreatePath() const
205 if (IsEmpty() || !GetFullPath(StrFullPath
))
208 const unsigned nLenFull
= StrFullPath
.GetLength();
213 char * const pName
= StrFullPath
.Detach();
215 AssertMsg(pName
[nLenFull
-1] == '\\' || pName
[nLenFull
-1] == '/', "Bad file path detected");
216 pName
[nLenFull
- 1] = '\0'; // get rid of '\\'
218 int result
= mkdir(pName
);
227 // Set the path from a full path string
229 void cFilePath::FromText(const char *pPath
)
234 // If the path isn't empty...
235 if (!m_path
.IsEmpty())
237 char c
= m_path
[m_path
.GetLength()-1];
239 // If there is no trailing slash...
249 // Return the path name as a text string
251 void cFilePath::AsText(cStr
& str
) const
258 // Change directory to this path
260 BOOL
cFilePath::SetCurrentPath() const
262 BEGIN_DEBUG_MSG("cFilePath::SetCurrentPath()")
264 // Get the file and extension
265 cPathSplitter
PathSplitter(m_path
);
269 PathSplitter
.GetSplit(&drive
, &dir
);
271 DebugMsg2("Split into '%s' and '%s'",drive
.BufIn(),dir
.BufIn());
273 // Need to strip trailing '\' from dir
274 if (dir
.GetLength() > 1)
275 dir
.Remove(dir
.GetLength()-1, 1);
277 DebugMsg2("Stripped down to '%s' and '%s'",drive
.BufIn(),dir
.BufIn());
279 if (drive
.GetLength())
281 int driveNum
= toupper(drive
[0]) - 'A'+1;
283 if (_chdrive(driveNum
) != 0)
297 // Add a relative path to the current path
299 BOOL
cFilePath::AddRelativePath(const cFilePath
&relPath
)
301 // Should already be trailed by \...
302 AssertMsg(m_path
[m_path
.GetLength()-1]=='\\', "Invalid file path");
304 if (relPath
.IsEmpty())
307 if (!relPath
.IsRelativePath())
310 m_path
+= relPath
.m_path
;
312 return ReducePathDots();
317 // Make this path a full path
319 BOOL
cFilePath::MakeFullPath()
322 if (!GetFullPath(FullPath
))
331 // Save the path to the stream
333 BOOL
cFilePath::ToStream(cOStore
&OStore
) const
335 return m_path
.ToStream(OStore
);
340 // Load the path from the stream
342 BOOL
cFilePath::FromStream(cIStore
&IStore
)
344 return m_path
.FromStream(IStore
);
349 // See if two paths are logically equal
351 BOOL
cFilePath::operator==(const cFilePath
&FilePath
) const
354 if (!GetFullPath(Path1
))
358 if (!FilePath
.GetFullPath(Path2
))
361 return Path1
== Path2
;
365 // Filespec Iteration
369 #define IsFoundDir(fd) ((fd).dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
372 BOOL
sFindContext::DoFindFirst(tFindState WhatToFind
, const cStr
& StrSpec
,
375 BEGIN_DEBUG_MSG2("sfc DoFindFirst(%d,%s,)",WhatToFind
,StrSpec
.BufIn());
377 DebugMsg("...Win32");
378 if (PlatformFindContext
.hContext
)
379 if (!FindClose(PlatformFindContext
.hContext
))
382 WIN32_FIND_DATA FindData
;
384 if ((PlatformFindContext
.hContext
= FindFirstFile(StrSpec
, &FindData
)) != INVALID_HANDLE_VALUE
)
386 DebugMsg2("FindFirstFile(%s) %c",(char*)FindData
.cFileName
, IsFoundDir(FindData
)?'d':'f');
387 while ((WhatToFind
== kFindingFiles
) ? IsFoundDir(FindData
) : !IsFoundDir(FindData
))
389 if (!FindNextFile(PlatformFindContext
.hContext
, &FindData
))
391 FindClose(PlatformFindContext
.hContext
);
392 PlatformFindContext
.hContext
= NULL
;
393 Finding
= kFindingNone
;
396 DebugMsg2("... skipped, trying %s %c",(char*)FindData
.cFileName
, IsFoundDir(FindData
)?'d':'f');
398 Finding
= WhatToFind
;
399 StrResult
= FindData
.cFileName
;
403 PlatformFindContext
.hContext
= NULL
;
404 Finding
= kFindingNone
;
406 #else /* Windows or Dos */
407 DebugMsg("...Win/Dos");
409 if (WhatToFind
== kFindingFiles
)
411 if(0 == _dos_findfirst((char*)((const char *) StrSpec
),
412 ~FA_DIREC
& ~FA_LABEL
,
413 (find_t
*)&PlatformFindContext
))
415 StrResult
= PlatformFindContext
.name
;
416 Finding
= kFindingFiles
;
420 else if (WhatToFind
== kFindingPaths
)
422 // dos_findfirst(...FA_DIREC...) does not exclude non-directories;
423 // it simply prevents the normal exclusion of directories.
424 // (See MS DOS Ref for 21h#4Eh, 21h#11h.)
425 // Thus, must post-filter normals out of FA_DIREC results etc.
426 if(0 == _dos_findfirst((char*)((const char *) StrSpec
),
428 (find_t
*)&PlatformFindContext
))
430 // Usually the first will be "." which has FA_DIREC attrib,
431 // but Novell Roots don't have "." even if Novell is supposed to show ".."
432 // in which case the first may be a normal file not a directory.
433 // (Even nonroot Novell dirs may not have "." if user doesn't set SHOWDOTS ?)
434 if (!(PlatformFindContext
.attrib
& FA_DIREC
))
438 if (0 != _dos_findnext((find_t
*)&PlatformFindContext
))
440 WhatToFind
= kFindingNone
;
441 return FALSE
; // none/no-more found
443 } while (!(PlatformFindContext
.attrib
& FA_DIREC
));
446 StrResult
= PlatformFindContext
.name
;
447 Finding
= kFindingPaths
;
448 DebugMsg1("... accepting DOS dir '%s'",StrResult
.BufIn());
458 BOOL
sFindContext::DoFindNext(cStr
& StrResult
)
461 if (!PlatformFindContext
.hContext
)
464 WIN32_FIND_DATA FindData
;
466 if (FindNextFile(PlatformFindContext
.hContext
, &FindData
))
468 while ((Finding
== kFindingFiles
) ? IsFoundDir(FindData
) : !IsFoundDir(FindData
))
470 if (!FindNextFile(PlatformFindContext
.hContext
, &FindData
))
472 FindClose(PlatformFindContext
.hContext
);
473 PlatformFindContext
.hContext
= NULL
;
474 Finding
= kFindingNone
;
478 StrResult
= FindData
.cFileName
;
482 FindClose(PlatformFindContext
.hContext
);
483 PlatformFindContext
.hContext
= NULL
;
484 Finding
= kFindingNone
;
487 #else /* Windows or Dos */
489 if (Finding
== kFindingFiles
)
491 if (0 != _dos_findnext((find_t
*)&PlatformFindContext
))
493 Finding
= kFindingNone
;
494 return FALSE
; // none/no-more found
497 else if (Finding
== kFindingPaths
)
501 if (0 != _dos_findnext((find_t
*)&PlatformFindContext
))
503 Finding
= kFindingNone
;
504 return FALSE
; // none/no-more found
506 } while (!(PlatformFindContext
.attrib
& FA_DIREC
));
508 StrResult
= PlatformFindContext
.name
;
515 // File path portion of find
517 BOOL
cFilePath::FindFirst(cFileSpec
&fs
, sFindContext
& FC
) const
519 cStr WcString
= m_path
+ pcszWildAll
;
522 if (FC
.DoFindFirst(sFindContext::kFindingFiles
, WcString
, StrFoundFile
))
525 fs
.SetFileName(StrFoundFile
);
526 fs
.SetFilePath(*this);
530 FC
.Finding
= sFindContext::kFindingNone
;
534 BOOL
cFilePath::FindNext(cFileSpec
&fs
, sFindContext
& FC
) const
536 if (FC
.Finding
!= sFindContext::kFindingFiles
)
538 CriticalMsg("Must FindFirst before FindNext using cFilePath");
539 return FindFirst(fs
,FC
);
544 if (FC
.DoFindNext(StrFoundFile
))
547 fs
.SetFileName(StrFoundFile
);
548 fs
.SetFilePath(*this);
551 FC
.Finding
= sFindContext::kFindingNone
;
557 // Filepath Iteration
559 BOOL
cFilePath::FindFirst(cFilePath
&fp
, sFindContext
& FC
) const
561 cStr WcString
= m_path
+ pcszWildAll
;
564 if (FC
.DoFindFirst(sFindContext::kFindingPaths
, WcString
, StrFoundFile
))
567 if(StrFoundFile
== "." || StrFoundFile
== "..")
569 return FindNext(fp
,FC
);
573 fp
.m_path
+= StrFoundFile
;
575 #ifdef DEBUG_FILEPATH
576 int iPathLen
= m_path
.GetLength();
577 AssertMsg(iPathLen
==0||m_path
[iPathLen
-1]=='\\',
578 "Pathname not ending in \\");
583 FC
.Finding
= sFindContext::kFindingNone
;
587 BOOL
cFilePath::FindNext(cFilePath
&fp
, sFindContext
& FC
) const
589 if (FC
.Finding
!= sFindContext::kFindingPaths
)
591 CriticalMsg("Must FindFirst before FindNext using cFilePath");
592 return FindFirst(fp
,FC
);
600 if (!FC
.DoFindNext(StrFoundFile
))
602 FC
.Finding
= sFindContext::kFindingNone
;
603 return FALSE
; // none/no-more found
606 while( StrFoundFile
== "." || StrFoundFile
== "..");
609 fp
.m_path
+= StrFoundFile
;
612 #ifdef DEBUG_FILEPATH
613 int iPathLen
= m_path
.GetLength();
614 AssertMsg(iPathLen
==0||m_path
[iPathLen
-1]=='\\',
615 "Pathname not ending in \\");
618 return TRUE
; // Found one.
626 // End the file system search
628 void cFilePath::FindDone(sFindContext
& fc
) const
631 if (fc
.PlatformFindContext
.hContext
)
633 FindClose(fc
.PlatformFindContext
.hContext
);
634 fc
.PlatformFindContext
.hContext
= NULL
;
635 fc
.Finding
= sFindContext::kFindingNone
;
642 // What's the name of the root?
644 BOOL
cFilePath::GetRootDir(const char *pPath
, cStr
&Str
)
646 if (!pPath
|| !*pPath
)
652 if (::IsRelativePath(pPath
))
654 if (::GetFullPath(pPath
, Str
.BufOut()))
657 cPathSplitter
PathSplitter(Str
);
658 PathSplitter
.GetDrive(Str
);
663 else if (*(pPath
+1) == ':')
668 else if (*pPath
== '\\' && *(pPath
+1) == '\\')
670 const char * pEndDomain
= strchr(pPath
+2, '\\');
673 nAppendLen
= pEndDomain
- pPath
;
675 nAppendLen
= strlen(pPath
);
677 Str
.Append(nAppendLen
, pPath
);
685 // What's the volume called
687 BOOL
cFilePath::GetVolumeName(const char * /* pPath */, cStr
& /* Str */)
693 // What kind of file system does it use (FAT, HPFS, Mac, NFS, NTFS, etc.)
696 eFileSystemKind
cFilePath::GetFileSystemKind() const
698 // @TBD: This is crude:
699 if (GetCaseConventions() & kNameCaseIgnored
)
700 return kFATFileSystem
;
701 return kDefaultFileSystem
;
704 BOOL
cFilePath::GetFileSystemName(const char * /* pPath */, cStr
& /* Str */)
711 // Get the serial number
713 ulong
cFilePath::GetVolumeSerialNumber(const char * /* pPath */)
720 // Get the volume case relevance
722 int cFilePath::GetCaseConventions(const char *pPath
)
725 BEGIN_DEBUG_MSG_EX1(CASE
, "int cFilePath::GetCaseConventions(%s)", pPath
);
726 DWORD dwFileSysFlags
;
728 GetRootDir(pPath
, StrRootDir
);
729 DebugMsgEx1(CASE
, "Calling ::GetVolumeInformation(%s, ...)", StrRootDir
.operator const char *());
730 if (::GetVolumeInformation(StrRootDir
, NULL
, 0, NULL
, NULL
, &dwFileSysFlags
, NULL
, 0))
732 DebugMsgEx1(CASE
, "::GetVolumeInformation() returned %lx", dwFileSysFlags
);
734 DebugMsgTrueEx(CASE
, (dwFileSysFlags
& FS_CASE_IS_PRESERVED
), "...volume is case retaining");
735 DebugMsgTrueEx(CASE
, (dwFileSysFlags
& FS_CASE_SENSITIVE
), "...volume is case sensitive");
736 DebugMsgTrueEx(CASE
, (dwFileSysFlags
& FS_UNICODE_STORED_ON_DISK
), "...volume stores unicode");
737 DebugMsgTrueEx(CASE
, !(dwFileSysFlags
& (FS_CASE_IS_PRESERVED
| FS_CASE_SENSITIVE
)), "...volume is case ignorant");
742 if (dwFileSysFlags
& FS_CASE_IS_PRESERVED
)
743 iReturn
|= kNameCasePreserved
;
744 if (dwFileSysFlags
& FS_CASE_SENSITIVE
)
745 iReturn
|= kNameCaseSensitive
;
746 if (dwFileSysFlags
& FS_UNICODE_STORED_ON_DISK
)
747 iReturn
|= kUnicodeStored
;
748 if (!(dwFileSysFlags
& (FS_CASE_IS_PRESERVED
| FS_CASE_SENSITIVE
)))
749 iReturn
|= kNameCaseIgnored
;
753 DebugMsgEx(CASE
, "GetVolumeInformation() returned FALSE, ...assuming volume is case ignorant");
754 return kNameCaseIgnored
;
757 return kNameCaseIgnored
;
763 // Get the maximum length of a component name
765 ulong
cFilePath::GetMaxLegalComponentLen(const char * /* pPath */)
776 // Character okay for volume?
778 BOOL
cFilePath::IsValidChar(const char * /* pPath */, char /*c*/)
785 // Component name okay for volume?
787 BOOL
cFilePath::IsValidComponentName(const char * /* pPath */, const char * /* psz */)
794 // Get full path relative to arg
796 BOOL
cFilePath::GetFullPath(cStr
&Str
, const cFilePath
&fp
) const
798 cFilePath
fpFullPath(fp
);
799 if (fpFullPath
.AddRelativePath(*this))
800 return fpFullPath
.GetFullPath(Str
);
801 return GetFullPath(Str
);
806 // Make path full relative to arg
808 BOOL
cFilePath::MakeFullPath(const cFilePath
&fp
)
810 if (IsRelativePath())
812 cFilePath
fpFullPath(fp
);
813 cFilePath
fpRelative(*this);
814 if (fpFullPath
.AddRelativePath(*this))
818 return MakeFullPath();