mirror of
https://github.com/holub/mame
synced 2025-06-07 05:13:46 +03:00
1326 lines
31 KiB
C++
1326 lines
31 KiB
C++
// Windows/FileFind.cpp
|
|
|
|
#include "StdAfx.h"
|
|
|
|
// #include <stdio.h>
|
|
|
|
#ifndef _WIN32
|
|
#include <fcntl.h> /* Definition of AT_* constants */
|
|
#include "TimeUtils.h"
|
|
// for major
|
|
// #include <sys/sysmacros.h>
|
|
#endif
|
|
|
|
#include "FileFind.h"
|
|
#include "FileIO.h"
|
|
#include "FileName.h"
|
|
|
|
#ifndef _UNICODE
|
|
extern bool g_IsNT;
|
|
#endif
|
|
|
|
using namespace NWindows;
|
|
using namespace NFile;
|
|
using namespace NName;
|
|
|
|
#if defined(_WIN32) && !defined(UNDER_CE)
|
|
|
|
EXTERN_C_BEGIN
|
|
|
|
typedef enum
|
|
{
|
|
My_FindStreamInfoStandard,
|
|
My_FindStreamInfoMaxInfoLevel
|
|
} MY_STREAM_INFO_LEVELS;
|
|
|
|
typedef struct
|
|
{
|
|
LARGE_INTEGER StreamSize;
|
|
WCHAR cStreamName[MAX_PATH + 36];
|
|
} MY_WIN32_FIND_STREAM_DATA, *MY_PWIN32_FIND_STREAM_DATA;
|
|
|
|
typedef HANDLE (WINAPI *FindFirstStreamW_Ptr)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel,
|
|
LPVOID findStreamData, DWORD flags);
|
|
|
|
typedef BOOL (APIENTRY *FindNextStreamW_Ptr)(HANDLE findStream, LPVOID findStreamData);
|
|
|
|
EXTERN_C_END
|
|
|
|
#endif // defined(_WIN32) && !defined(UNDER_CE)
|
|
|
|
|
|
namespace NWindows {
|
|
namespace NFile {
|
|
|
|
|
|
#ifdef _WIN32
|
|
#ifdef SUPPORT_DEVICE_FILE
|
|
namespace NSystem
|
|
{
|
|
bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
namespace NFind {
|
|
|
|
/*
|
|
#ifdef _WIN32
|
|
#define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0;
|
|
#else
|
|
#define MY_CLEAR_FILETIME(ft) ft.tv_sec = 0; ft.tv_nsec = 0;
|
|
#endif
|
|
*/
|
|
|
|
void CFileInfoBase::ClearBase() throw()
|
|
{
|
|
Size = 0;
|
|
FiTime_Clear(CTime);
|
|
FiTime_Clear(ATime);
|
|
FiTime_Clear(MTime);
|
|
|
|
#ifdef _WIN32
|
|
Attrib = 0;
|
|
// ReparseTag = 0;
|
|
IsAltStream = false;
|
|
IsDevice = false;
|
|
#else
|
|
dev = 0;
|
|
ino = 0;
|
|
mode = 0;
|
|
nlink = 0;
|
|
uid = 0;
|
|
gid = 0;
|
|
rdev = 0;
|
|
#endif
|
|
}
|
|
|
|
bool CFileInfo::IsDots() const throw()
|
|
{
|
|
if (!IsDir() || Name.IsEmpty())
|
|
return false;
|
|
if (Name[0] != '.')
|
|
return false;
|
|
return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == '.');
|
|
}
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
|
#define WIN_FD_TO_MY_FI(fi, fd) \
|
|
fi.Attrib = fd.dwFileAttributes; \
|
|
fi.CTime = fd.ftCreationTime; \
|
|
fi.ATime = fd.ftLastAccessTime; \
|
|
fi.MTime = fd.ftLastWriteTime; \
|
|
fi.Size = (((UInt64)fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; \
|
|
/* fi.ReparseTag = fd.dwReserved0; */ \
|
|
fi.IsAltStream = false; \
|
|
fi.IsDevice = false;
|
|
|
|
/*
|
|
#ifdef UNDER_CE
|
|
fi.ObjectID = fd.dwOID;
|
|
#else
|
|
fi.ReparseTag = fd.dwReserved0;
|
|
#endif
|
|
*/
|
|
|
|
static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW &fd, CFileInfo &fi)
|
|
{
|
|
WIN_FD_TO_MY_FI(fi, fd);
|
|
fi.Name = us2fs(fd.cFileName);
|
|
#if defined(_WIN32) && !defined(UNDER_CE)
|
|
// fi.ShortName = us2fs(fd.cAlternateFileName);
|
|
#endif
|
|
}
|
|
|
|
#ifndef _UNICODE
|
|
static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi)
|
|
{
|
|
WIN_FD_TO_MY_FI(fi, fd);
|
|
fi.Name = fas2fs(fd.cFileName);
|
|
#if defined(_WIN32) && !defined(UNDER_CE)
|
|
// fi.ShortName = fas2fs(fd.cAlternateFileName);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
////////////////////////////////
|
|
// CFindFile
|
|
|
|
bool CFindFileBase::Close() throw()
|
|
{
|
|
if (_handle == INVALID_HANDLE_VALUE)
|
|
return true;
|
|
if (!::FindClose(_handle))
|
|
return false;
|
|
_handle = INVALID_HANDLE_VALUE;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
WinXP-64 FindFirstFile():
|
|
"" - ERROR_PATH_NOT_FOUND
|
|
folder\ - ERROR_FILE_NOT_FOUND
|
|
\ - ERROR_FILE_NOT_FOUND
|
|
c:\ - ERROR_FILE_NOT_FOUND
|
|
c: - ERROR_FILE_NOT_FOUND, if current dir is ROOT ( c:\ )
|
|
c: - OK, if current dir is NOT ROOT ( c:\folder )
|
|
folder - OK
|
|
|
|
\\ - ERROR_INVALID_NAME
|
|
\\Server - ERROR_INVALID_NAME
|
|
\\Server\ - ERROR_INVALID_NAME
|
|
|
|
\\Server\Share - ERROR_BAD_NETPATH
|
|
\\Server\Share - ERROR_BAD_NET_NAME (Win7).
|
|
!!! There is problem : Win7 makes some requests for "\\Server\Shar" (look in Procmon),
|
|
when we call it for "\\Server\Share"
|
|
|
|
\\Server\Share\ - ERROR_FILE_NOT_FOUND
|
|
|
|
\\?\UNC\Server\Share - ERROR_INVALID_NAME
|
|
\\?\UNC\Server\Share - ERROR_BAD_PATHNAME (Win7)
|
|
\\?\UNC\Server\Share\ - ERROR_FILE_NOT_FOUND
|
|
|
|
\\Server\Share_RootDrive - ERROR_INVALID_NAME
|
|
\\Server\Share_RootDrive\ - ERROR_INVALID_NAME
|
|
|
|
e:\* - ERROR_FILE_NOT_FOUND, if there are no items in that root folder
|
|
w:\* - ERROR_PATH_NOT_FOUND, if there is no such drive w:
|
|
*/
|
|
|
|
bool CFindFile::FindFirst(CFSTR path, CFileInfo &fi)
|
|
{
|
|
if (!Close())
|
|
return false;
|
|
#ifndef _UNICODE
|
|
if (!g_IsNT)
|
|
{
|
|
WIN32_FIND_DATAA fd;
|
|
_handle = ::FindFirstFileA(fs2fas(path), &fd);
|
|
if (_handle == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
WIN32_FIND_DATAW fd;
|
|
|
|
IF_USE_MAIN_PATH
|
|
_handle = ::FindFirstFileW(fs2us(path), &fd);
|
|
#ifdef WIN_LONG_PATH
|
|
if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH)
|
|
{
|
|
UString superPath;
|
|
if (GetSuperPath(path, superPath, USE_MAIN_PATH))
|
|
_handle = ::FindFirstFileW(superPath, &fd);
|
|
}
|
|
#endif
|
|
if (_handle == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CFindFile::FindNext(CFileInfo &fi)
|
|
{
|
|
#ifndef _UNICODE
|
|
if (!g_IsNT)
|
|
{
|
|
WIN32_FIND_DATAA fd;
|
|
if (!::FindNextFileA(_handle, &fd))
|
|
return false;
|
|
Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
WIN32_FIND_DATAW fd;
|
|
if (!::FindNextFileW(_handle, &fd))
|
|
return false;
|
|
Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#if defined(_WIN32) && !defined(UNDER_CE)
|
|
|
|
////////////////////////////////
|
|
// AltStreams
|
|
|
|
static FindFirstStreamW_Ptr g_FindFirstStreamW;
|
|
static FindNextStreamW_Ptr g_FindNextStreamW;
|
|
|
|
static struct CFindStreamLoader
|
|
{
|
|
CFindStreamLoader()
|
|
{
|
|
HMODULE hm = ::GetModuleHandleA("kernel32.dll");
|
|
g_FindFirstStreamW = (FindFirstStreamW_Ptr)(void *)::GetProcAddress(hm, "FindFirstStreamW");
|
|
g_FindNextStreamW = (FindNextStreamW_Ptr)(void *)::GetProcAddress(hm, "FindNextStreamW");
|
|
}
|
|
} g_FindStreamLoader;
|
|
|
|
bool CStreamInfo::IsMainStream() const throw()
|
|
{
|
|
return StringsAreEqualNoCase_Ascii(Name, "::$DATA");
|
|
};
|
|
|
|
UString CStreamInfo::GetReducedName() const
|
|
{
|
|
// remove ":$DATA" postfix, but keep postfix, if Name is "::$DATA"
|
|
UString s (Name);
|
|
if (s.Len() > 6 + 1 && StringsAreEqualNoCase_Ascii(s.RightPtr(6), ":$DATA"))
|
|
s.DeleteFrom(s.Len() - 6);
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
UString CStreamInfo::GetReducedName2() const
|
|
{
|
|
UString s = GetReducedName();
|
|
if (!s.IsEmpty() && s[0] == ':')
|
|
s.Delete(0);
|
|
return s;
|
|
}
|
|
*/
|
|
|
|
static void Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA &sd, CStreamInfo &si)
|
|
{
|
|
si.Size = (UInt64)sd.StreamSize.QuadPart;
|
|
si.Name = sd.cStreamName;
|
|
}
|
|
|
|
/*
|
|
WinXP-64 FindFirstStream():
|
|
"" - ERROR_PATH_NOT_FOUND
|
|
folder\ - OK
|
|
folder - OK
|
|
\ - OK
|
|
c:\ - OK
|
|
c: - OK, if current dir is ROOT ( c:\ )
|
|
c: - OK, if current dir is NOT ROOT ( c:\folder )
|
|
\\Server\Share - OK
|
|
\\Server\Share\ - OK
|
|
|
|
\\ - ERROR_INVALID_NAME
|
|
\\Server - ERROR_INVALID_NAME
|
|
\\Server\ - ERROR_INVALID_NAME
|
|
*/
|
|
|
|
bool CFindStream::FindFirst(CFSTR path, CStreamInfo &si)
|
|
{
|
|
if (!Close())
|
|
return false;
|
|
if (!g_FindFirstStreamW)
|
|
{
|
|
::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
|
return false;
|
|
}
|
|
{
|
|
MY_WIN32_FIND_STREAM_DATA sd;
|
|
SetLastError(0);
|
|
IF_USE_MAIN_PATH
|
|
_handle = g_FindFirstStreamW(fs2us(path), My_FindStreamInfoStandard, &sd, 0);
|
|
if (_handle == INVALID_HANDLE_VALUE)
|
|
{
|
|
if (::GetLastError() == ERROR_HANDLE_EOF)
|
|
return false;
|
|
// long name can be tricky for path like ".\dirName".
|
|
#ifdef WIN_LONG_PATH
|
|
if (USE_SUPER_PATH)
|
|
{
|
|
UString superPath;
|
|
if (GetSuperPath(path, superPath, USE_MAIN_PATH))
|
|
_handle = g_FindFirstStreamW(superPath, My_FindStreamInfoStandard, &sd, 0);
|
|
}
|
|
#endif
|
|
}
|
|
if (_handle == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CFindStream::FindNext(CStreamInfo &si)
|
|
{
|
|
if (!g_FindNextStreamW)
|
|
{
|
|
::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
|
return false;
|
|
}
|
|
{
|
|
MY_WIN32_FIND_STREAM_DATA sd;
|
|
if (!g_FindNextStreamW(_handle, &sd))
|
|
return false;
|
|
Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CStreamEnumerator::Next(CStreamInfo &si, bool &found)
|
|
{
|
|
bool res;
|
|
if (_find.IsHandleAllocated())
|
|
res = _find.FindNext(si);
|
|
else
|
|
res = _find.FindFirst(_filePath, si);
|
|
if (res)
|
|
{
|
|
found = true;
|
|
return true;
|
|
}
|
|
found = false;
|
|
return (::GetLastError() == ERROR_HANDLE_EOF);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
/*
|
|
WinXP-64 GetFileAttributes():
|
|
If the function fails, it returns INVALID_FILE_ATTRIBUTES and use GetLastError() to get error code
|
|
|
|
\ - OK
|
|
C:\ - OK, if there is such drive,
|
|
D:\ - ERROR_PATH_NOT_FOUND, if there is no such drive,
|
|
|
|
C:\folder - OK
|
|
C:\folder\ - OK
|
|
C:\folderBad - ERROR_FILE_NOT_FOUND
|
|
|
|
\\Server\BadShare - ERROR_BAD_NETPATH
|
|
\\Server\Share - WORKS OK, but MSDN says:
|
|
GetFileAttributes for a network share, the function fails, and GetLastError
|
|
returns ERROR_BAD_NETPATH. You must specify a path to a subfolder on that share.
|
|
*/
|
|
|
|
DWORD GetFileAttrib(CFSTR path)
|
|
{
|
|
#ifndef _UNICODE
|
|
if (!g_IsNT)
|
|
return ::GetFileAttributes(fs2fas(path));
|
|
else
|
|
#endif
|
|
{
|
|
IF_USE_MAIN_PATH
|
|
{
|
|
DWORD dw = ::GetFileAttributesW(fs2us(path));
|
|
if (dw != INVALID_FILE_ATTRIBUTES)
|
|
return dw;
|
|
}
|
|
#ifdef WIN_LONG_PATH
|
|
if (USE_SUPER_PATH)
|
|
{
|
|
UString superPath;
|
|
if (GetSuperPath(path, superPath, USE_MAIN_PATH))
|
|
return ::GetFileAttributesW(superPath);
|
|
}
|
|
#endif
|
|
return INVALID_FILE_ATTRIBUTES;
|
|
}
|
|
}
|
|
|
|
/* if path is "c:" or "c::" then CFileInfo::Find() returns name of current folder for that disk
|
|
so instead of absolute path we have relative path in Name. That is not good in some calls */
|
|
|
|
/* In CFileInfo::Find() we want to support same names for alt streams as in CreateFile(). */
|
|
|
|
/* CFileInfo::Find()
|
|
We alow the following paths (as FindFirstFile):
|
|
C:\folder
|
|
c: - if current dir is NOT ROOT ( c:\folder )
|
|
|
|
also we support paths that are not supported by FindFirstFile:
|
|
\
|
|
\\.\c:
|
|
c:\ - Name will be without tail slash ( c: )
|
|
\\?\c:\ - Name will be without tail slash ( c: )
|
|
\\Server\Share
|
|
\\?\UNC\Server\Share
|
|
|
|
c:\folder:stream - Name = folder:stream
|
|
c:\:stream - Name = :stream
|
|
c::stream - Name = c::stream
|
|
*/
|
|
|
|
bool CFileInfo::Find(CFSTR path, bool followLink)
|
|
{
|
|
#ifdef SUPPORT_DEVICE_FILE
|
|
|
|
if (IS_PATH_SEPAR(path[0]) &&
|
|
IS_PATH_SEPAR(path[1]) &&
|
|
path[2] == '.' &&
|
|
path[3] == 0)
|
|
{
|
|
// 22.00 : it's virtual directory for devices
|
|
// IsDevice = true;
|
|
ClearBase();
|
|
Name = path + 2;
|
|
Attrib = FILE_ATTRIBUTE_DIRECTORY;
|
|
return true;
|
|
}
|
|
|
|
if (IsDevicePath(path))
|
|
{
|
|
ClearBase();
|
|
Name = path + 4;
|
|
IsDevice = true;
|
|
|
|
if (NName::IsDrivePath2(path + 4) && path[6] == 0)
|
|
{
|
|
FChar drive[4] = { path[4], ':', '\\', 0 };
|
|
UInt64 clusterSize, totalSize, freeSize;
|
|
if (NSystem::MyGetDiskFreeSpace(drive, clusterSize, totalSize, freeSize))
|
|
{
|
|
Size = totalSize;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
NIO::CInFile inFile;
|
|
// ::OutputDebugStringW(path);
|
|
if (!inFile.Open(path))
|
|
return false;
|
|
// ::OutputDebugStringW(L"---");
|
|
if (inFile.SizeDefined)
|
|
Size = inFile.Size;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
#if defined(_WIN32) && !defined(UNDER_CE)
|
|
|
|
const int colonPos = FindAltStreamColon(path);
|
|
if (colonPos >= 0 && path[(unsigned)colonPos + 1] != 0)
|
|
{
|
|
UString streamName = fs2us(path + (unsigned)colonPos);
|
|
FString filePath (path);
|
|
filePath.DeleteFrom((unsigned)colonPos);
|
|
/* we allow both cases:
|
|
name:stream
|
|
name:stream:$DATA
|
|
*/
|
|
const unsigned kPostfixSize = 6;
|
|
if (streamName.Len() <= kPostfixSize
|
|
|| !StringsAreEqualNoCase_Ascii(streamName.RightPtr(kPostfixSize), ":$DATA"))
|
|
streamName += ":$DATA";
|
|
|
|
bool isOk = true;
|
|
|
|
if (IsDrivePath2(filePath) &&
|
|
(colonPos == 2 || (colonPos == 3 && filePath[2] == '\\')))
|
|
{
|
|
// FindFirstFile doesn't work for "c:\" and for "c:" (if current dir is ROOT)
|
|
ClearBase();
|
|
Name.Empty();
|
|
if (colonPos == 2)
|
|
Name = filePath;
|
|
}
|
|
else
|
|
isOk = Find(filePath, followLink); // check it (followLink)
|
|
|
|
if (isOk)
|
|
{
|
|
Attrib &= ~(DWORD)(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT);
|
|
Size = 0;
|
|
CStreamEnumerator enumerator(filePath);
|
|
for (;;)
|
|
{
|
|
CStreamInfo si;
|
|
bool found;
|
|
if (!enumerator.Next(si, found))
|
|
return false;
|
|
if (!found)
|
|
{
|
|
::SetLastError(ERROR_FILE_NOT_FOUND);
|
|
return false;
|
|
}
|
|
if (si.Name.IsEqualTo_NoCase(streamName))
|
|
{
|
|
// we delete postfix, if alt stream name is not "::$DATA"
|
|
if (si.Name.Len() > kPostfixSize + 1)
|
|
si.Name.DeleteFrom(si.Name.Len() - kPostfixSize);
|
|
Name += us2fs(si.Name);
|
|
Size = si.Size;
|
|
IsAltStream = true;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
CFindFile finder;
|
|
|
|
#if defined(_WIN32) && !defined(UNDER_CE)
|
|
{
|
|
/*
|
|
DWORD lastError = GetLastError();
|
|
if (lastError == ERROR_FILE_NOT_FOUND
|
|
|| lastError == ERROR_BAD_NETPATH // XP64: "\\Server\Share"
|
|
|| lastError == ERROR_BAD_NET_NAME // Win7: "\\Server\Share"
|
|
|| lastError == ERROR_INVALID_NAME // XP64: "\\?\UNC\Server\Share"
|
|
|| lastError == ERROR_BAD_PATHNAME // Win7: "\\?\UNC\Server\Share"
|
|
)
|
|
*/
|
|
|
|
unsigned rootSize = 0;
|
|
if (IsSuperPath(path))
|
|
rootSize = kSuperPathPrefixSize;
|
|
|
|
if (NName::IsDrivePath(path + rootSize) && path[rootSize + 3] == 0)
|
|
{
|
|
DWORD attrib = GetFileAttrib(path);
|
|
if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)
|
|
{
|
|
ClearBase();
|
|
Attrib = attrib;
|
|
Name = path + rootSize;
|
|
Name.DeleteFrom(2);
|
|
if (!Fill_From_ByHandleFileInfo(path))
|
|
{
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
else if (IS_PATH_SEPAR(path[0]))
|
|
{
|
|
if (path[1] == 0)
|
|
{
|
|
DWORD attrib = GetFileAttrib(path);
|
|
if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)
|
|
{
|
|
ClearBase();
|
|
Name.Empty();
|
|
Attrib = attrib;
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const unsigned prefixSize = GetNetworkServerPrefixSize(path);
|
|
if (prefixSize > 0 && path[prefixSize] != 0)
|
|
{
|
|
if (NName::FindSepar(path + prefixSize) < 0)
|
|
{
|
|
if (Fill_From_ByHandleFileInfo(path))
|
|
{
|
|
Name = path + prefixSize;
|
|
return true;
|
|
}
|
|
|
|
FString s (path);
|
|
s.Add_PathSepar();
|
|
s += '*'; // CHAR_ANY_MASK
|
|
bool isOK = false;
|
|
if (finder.FindFirst(s, *this))
|
|
{
|
|
if (Name == FTEXT("."))
|
|
{
|
|
Name = path + prefixSize;
|
|
return true;
|
|
}
|
|
isOK = true;
|
|
/* if "\\server\share" maps to root folder "d:\", there is no "." item.
|
|
But it's possible that there are another items */
|
|
}
|
|
{
|
|
DWORD attrib = GetFileAttrib(path);
|
|
if (isOK || (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0))
|
|
{
|
|
ClearBase();
|
|
if (attrib != INVALID_FILE_ATTRIBUTES)
|
|
Attrib = attrib;
|
|
else
|
|
SetAsDir();
|
|
Name = path + prefixSize;
|
|
return true;
|
|
}
|
|
}
|
|
// ::SetLastError(lastError);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool res = finder.FindFirst(path, *this);
|
|
if (!followLink
|
|
|| !res
|
|
|| !HasReparsePoint())
|
|
return res;
|
|
|
|
// return FollowReparse(path, IsDir());
|
|
return Fill_From_ByHandleFileInfo(path);
|
|
}
|
|
|
|
bool CFileInfoBase::Fill_From_ByHandleFileInfo(CFSTR path)
|
|
{
|
|
BY_HANDLE_FILE_INFORMATION info;
|
|
if (!NIO::CFileBase::GetFileInformation(path, &info))
|
|
return false;
|
|
{
|
|
Size = (((UInt64)info.nFileSizeHigh) << 32) + info.nFileSizeLow;
|
|
CTime = info.ftCreationTime;
|
|
ATime = info.ftLastAccessTime;
|
|
MTime = info.ftLastWriteTime;
|
|
Attrib = info.dwFileAttributes;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
bool CFileInfo::FollowReparse(CFSTR path, bool isDir)
|
|
{
|
|
if (isDir)
|
|
{
|
|
FString prefix = path;
|
|
prefix.Add_PathSepar();
|
|
|
|
// "folder/." refers to folder itself. So we can't use that path
|
|
// we must use enumerator and search "." item
|
|
CEnumerator enumerator;
|
|
enumerator.SetDirPrefix(prefix);
|
|
for (;;)
|
|
{
|
|
CFileInfo fi;
|
|
if (!enumerator.NextAny(fi))
|
|
break;
|
|
if (fi.Name.IsEqualTo_Ascii_NoCase("."))
|
|
{
|
|
// we can copy preperies;
|
|
CTime = fi.CTime;
|
|
ATime = fi.ATime;
|
|
MTime = fi.MTime;
|
|
Attrib = fi.Attrib;
|
|
Size = fi.Size;
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
// LastError(lastError);
|
|
return false;
|
|
}
|
|
|
|
{
|
|
NIO::CInFile inFile;
|
|
if (inFile.Open(path))
|
|
{
|
|
BY_HANDLE_FILE_INFORMATION info;
|
|
if (inFile.GetFileInformation(&info))
|
|
{
|
|
ClearBase();
|
|
Size = (((UInt64)info.nFileSizeHigh) << 32) + info.nFileSizeLow;
|
|
CTime = info.ftCreationTime;
|
|
ATime = info.ftLastAccessTime;
|
|
MTime = info.ftLastWriteTime;
|
|
Attrib = info.dwFileAttributes;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
*/
|
|
|
|
bool DoesFileExist_Raw(CFSTR name)
|
|
{
|
|
CFileInfo fi;
|
|
return fi.Find(name) && !fi.IsDir();
|
|
}
|
|
|
|
bool DoesFileExist_FollowLink(CFSTR name)
|
|
{
|
|
CFileInfo fi;
|
|
return fi.Find_FollowLink(name) && !fi.IsDir();
|
|
}
|
|
|
|
bool DoesDirExist(CFSTR name, bool followLink)
|
|
{
|
|
CFileInfo fi;
|
|
return fi.Find(name, followLink) && fi.IsDir();
|
|
}
|
|
|
|
bool DoesFileOrDirExist(CFSTR name)
|
|
{
|
|
CFileInfo fi;
|
|
return fi.Find(name);
|
|
}
|
|
|
|
|
|
void CEnumerator::SetDirPrefix(const FString &dirPrefix)
|
|
{
|
|
_wildcard = dirPrefix;
|
|
_wildcard += '*';
|
|
}
|
|
|
|
bool CEnumerator::NextAny(CFileInfo &fi)
|
|
{
|
|
if (_findFile.IsHandleAllocated())
|
|
return _findFile.FindNext(fi);
|
|
else
|
|
return _findFile.FindFirst(_wildcard, fi);
|
|
}
|
|
|
|
bool CEnumerator::Next(CFileInfo &fi)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (!NextAny(fi))
|
|
return false;
|
|
if (!fi.IsDots())
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool CEnumerator::Next(CFileInfo &fi, bool &found)
|
|
{
|
|
/*
|
|
for (;;)
|
|
{
|
|
if (!NextAny(fi))
|
|
break;
|
|
if (!fi.IsDots())
|
|
{
|
|
found = true;
|
|
return true;
|
|
}
|
|
}
|
|
*/
|
|
|
|
if (Next(fi))
|
|
{
|
|
found = true;
|
|
return true;
|
|
}
|
|
|
|
found = false;
|
|
DWORD lastError = ::GetLastError();
|
|
if (_findFile.IsHandleAllocated())
|
|
return (lastError == ERROR_NO_MORE_FILES);
|
|
// we support the case for empty root folder: FindFirstFile("c:\\*") returns ERROR_FILE_NOT_FOUND
|
|
if (lastError == ERROR_FILE_NOT_FOUND)
|
|
return true;
|
|
if (lastError == ERROR_ACCESS_DENIED)
|
|
{
|
|
// here we show inaccessible root system folder as empty folder to eliminate redundant user warnings
|
|
const char *s = "System Volume Information" STRING_PATH_SEPARATOR "*";
|
|
const int len = (int)strlen(s);
|
|
const int delta = (int)_wildcard.Len() - len;
|
|
if (delta == 0 || (delta > 0 && IS_PATH_SEPAR(_wildcard[(unsigned)delta - 1])))
|
|
if (StringsAreEqual_Ascii(_wildcard.Ptr((unsigned)delta), s))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
////////////////////////////////
|
|
// CFindChangeNotification
|
|
// FindFirstChangeNotification can return 0. MSDN doesn't tell about it.
|
|
|
|
bool CFindChangeNotification::Close() throw()
|
|
{
|
|
if (!IsHandleAllocated())
|
|
return true;
|
|
if (!::FindCloseChangeNotification(_handle))
|
|
return false;
|
|
_handle = INVALID_HANDLE_VALUE;
|
|
return true;
|
|
}
|
|
|
|
HANDLE CFindChangeNotification::FindFirst(CFSTR path, bool watchSubtree, DWORD notifyFilter)
|
|
{
|
|
#ifndef _UNICODE
|
|
if (!g_IsNT)
|
|
_handle = ::FindFirstChangeNotification(fs2fas(path), BoolToBOOL(watchSubtree), notifyFilter);
|
|
else
|
|
#endif
|
|
{
|
|
IF_USE_MAIN_PATH
|
|
_handle = ::FindFirstChangeNotificationW(fs2us(path), BoolToBOOL(watchSubtree), notifyFilter);
|
|
#ifdef WIN_LONG_PATH
|
|
if (!IsHandleAllocated())
|
|
{
|
|
UString superPath;
|
|
if (GetSuperPath(path, superPath, USE_MAIN_PATH))
|
|
_handle = ::FindFirstChangeNotificationW(superPath, BoolToBOOL(watchSubtree), notifyFilter);
|
|
}
|
|
#endif
|
|
}
|
|
return _handle;
|
|
}
|
|
|
|
#ifndef UNDER_CE
|
|
|
|
bool MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings)
|
|
{
|
|
driveStrings.Clear();
|
|
#ifndef _UNICODE
|
|
if (!g_IsNT)
|
|
{
|
|
driveStrings.Clear();
|
|
UINT32 size = GetLogicalDriveStrings(0, NULL);
|
|
if (size == 0)
|
|
return false;
|
|
CObjArray<char> buf(size);
|
|
UINT32 newSize = GetLogicalDriveStrings(size, buf);
|
|
if (newSize == 0 || newSize > size)
|
|
return false;
|
|
AString s;
|
|
UINT32 prev = 0;
|
|
for (UINT32 i = 0; i < newSize; i++)
|
|
{
|
|
if (buf[i] == 0)
|
|
{
|
|
s = buf + prev;
|
|
prev = i + 1;
|
|
driveStrings.Add(fas2fs(s));
|
|
}
|
|
}
|
|
return prev == newSize;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
UINT32 size = GetLogicalDriveStringsW(0, NULL);
|
|
if (size == 0)
|
|
return false;
|
|
CObjArray<wchar_t> buf(size);
|
|
UINT32 newSize = GetLogicalDriveStringsW(size, buf);
|
|
if (newSize == 0 || newSize > size)
|
|
return false;
|
|
UString s;
|
|
UINT32 prev = 0;
|
|
for (UINT32 i = 0; i < newSize; i++)
|
|
{
|
|
if (buf[i] == 0)
|
|
{
|
|
s = buf + prev;
|
|
prev = i + 1;
|
|
driveStrings.Add(us2fs(s));
|
|
}
|
|
}
|
|
return prev == newSize;
|
|
}
|
|
}
|
|
|
|
#endif // UNDER_CE
|
|
|
|
|
|
|
|
#else // _WIN32
|
|
|
|
// ---------- POSIX ----------
|
|
|
|
static int MY__lstat(CFSTR path, struct stat *st, bool followLink)
|
|
{
|
|
memset(st, 0, sizeof(*st));
|
|
int res;
|
|
// #ifdef ENV_HAVE_LSTAT
|
|
if (/* global_use_lstat && */ !followLink)
|
|
{
|
|
// printf("\nlstat\n");
|
|
res = lstat(path, st);
|
|
}
|
|
else
|
|
// #endif
|
|
{
|
|
// printf("\nstat\n");
|
|
res = stat(path, st);
|
|
}
|
|
/*
|
|
printf("\nres = %d\n", res);
|
|
printf("\n st_dev = %lld \n", (long long)(st->st_dev));
|
|
printf("\n st_ino = %lld \n", (long long)(st->st_ino));
|
|
printf("\n st_mode = %lld \n", (long long)(st->st_mode));
|
|
printf("\n st_nlink = %lld \n", (long long)(st->st_nlink));
|
|
printf("\n st_uid = %lld \n", (long long)(st->st_uid));
|
|
printf("\n st_gid = %lld \n", (long long)(st->st_gid));
|
|
printf("\n st_size = %lld \n", (long long)(st->st_size));
|
|
printf("\n st_blksize = %lld \n", (long long)(st->st_blksize));
|
|
printf("\n st_blocks = %lld \n", (long long)(st->st_blocks));
|
|
*/
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
static const char *Get_Name_from_Path(CFSTR path) throw()
|
|
{
|
|
size_t len = strlen(path);
|
|
if (len == 0)
|
|
return path;
|
|
const char *p = path + len - 1;
|
|
{
|
|
if (p == path)
|
|
return path;
|
|
p--;
|
|
}
|
|
for (;;)
|
|
{
|
|
char c = *p;
|
|
if (IS_PATH_SEPAR(c))
|
|
return p + 1;
|
|
if (p == path)
|
|
return path;
|
|
p--;
|
|
}
|
|
}
|
|
|
|
|
|
UInt32 Get_WinAttribPosix_From_PosixMode(UInt32 mode)
|
|
{
|
|
UInt32 attrib = S_ISDIR(mode) ?
|
|
FILE_ATTRIBUTE_DIRECTORY :
|
|
FILE_ATTRIBUTE_ARCHIVE;
|
|
if ((mode & 0222) == 0) // S_IWUSR in p7zip
|
|
attrib |= FILE_ATTRIBUTE_READONLY;
|
|
return attrib | FILE_ATTRIBUTE_UNIX_EXTENSION | ((mode & 0xFFFF) << 16);
|
|
}
|
|
|
|
/*
|
|
UInt32 Get_WinAttrib_From_stat(const struct stat &st)
|
|
{
|
|
UInt32 attrib = S_ISDIR(st.st_mode) ?
|
|
FILE_ATTRIBUTE_DIRECTORY :
|
|
FILE_ATTRIBUTE_ARCHIVE;
|
|
|
|
if ((st.st_mode & 0222) == 0) // check it !!!
|
|
attrib |= FILE_ATTRIBUTE_READONLY;
|
|
|
|
attrib |= FILE_ATTRIBUTE_UNIX_EXTENSION + ((st.st_mode & 0xFFFF) << 16);
|
|
return attrib;
|
|
}
|
|
*/
|
|
|
|
void CFileInfo::SetFrom_stat(const struct stat &st)
|
|
{
|
|
// IsDevice = false;
|
|
|
|
if (S_ISDIR(st.st_mode))
|
|
{
|
|
Size = 0;
|
|
}
|
|
else
|
|
{
|
|
Size = (UInt64)st.st_size; // for a symbolic link, size = size of filename
|
|
}
|
|
|
|
// Attrib = Get_WinAttribPosix_From_PosixMode(st.st_mode);
|
|
|
|
// NTime::UnixTimeToFileTime(st.st_ctime, CTime);
|
|
// NTime::UnixTimeToFileTime(st.st_mtime, MTime);
|
|
// NTime::UnixTimeToFileTime(st.st_atime, ATime);
|
|
#ifdef __APPLE__
|
|
// #ifdef _DARWIN_FEATURE_64_BIT_INODE
|
|
/*
|
|
here we can use birthtime instead of st_ctimespec.
|
|
but we use st_ctimespec for compatibility with previous versions and p7zip.
|
|
st_birthtimespec in OSX
|
|
st_birthtim : at FreeBSD, NetBSD
|
|
*/
|
|
// timespec_To_FILETIME(st.st_birthtimespec, CTime);
|
|
// #else
|
|
// timespec_To_FILETIME(st.st_ctimespec, CTime);
|
|
// #endif
|
|
// timespec_To_FILETIME(st.st_mtimespec, MTime);
|
|
// timespec_To_FILETIME(st.st_atimespec, ATime);
|
|
CTime = st.st_ctimespec;
|
|
MTime = st.st_mtimespec;
|
|
ATime = st.st_atimespec;
|
|
|
|
#else
|
|
// timespec_To_FILETIME(st.st_ctim, CTime, &CTime_ns100);
|
|
// timespec_To_FILETIME(st.st_mtim, MTime, &MTime_ns100);
|
|
// timespec_To_FILETIME(st.st_atim, ATime, &ATime_ns100);
|
|
CTime = st.st_ctim;
|
|
MTime = st.st_mtim;
|
|
ATime = st.st_atim;
|
|
|
|
#endif
|
|
|
|
dev = st.st_dev;
|
|
ino = st.st_ino;
|
|
mode = st.st_mode;
|
|
nlink = st.st_nlink;
|
|
uid = st.st_uid;
|
|
gid = st.st_gid;
|
|
rdev = st.st_rdev;
|
|
|
|
/*
|
|
printf("\n sizeof timespec = %d", (int)sizeof(timespec));
|
|
printf("\n sizeof st_rdev = %d", (int)sizeof(rdev));
|
|
printf("\n sizeof st_ino = %d", (int)sizeof(ino));
|
|
printf("\n sizeof mode_t = %d", (int)sizeof(mode_t));
|
|
printf("\n sizeof nlink_t = %d", (int)sizeof(nlink_t));
|
|
printf("\n sizeof uid_t = %d", (int)sizeof(uid_t));
|
|
printf("\n");
|
|
*/
|
|
/*
|
|
printf("\n st_rdev = %llx", (long long)rdev);
|
|
printf("\n st_dev = %llx", (long long)dev);
|
|
printf("\n dev : major = %5x minor = %5x", (unsigned)major(dev), (unsigned)minor(dev));
|
|
printf("\n st_ino = %lld", (long long)(ino));
|
|
printf("\n rdev : major = %5x minor = %5x", (unsigned)major(rdev), (unsigned)minor(rdev));
|
|
printf("\n size = %lld \n", (long long)(Size));
|
|
printf("\n");
|
|
*/
|
|
}
|
|
|
|
/*
|
|
int Uid_To_Uname(uid_t uid, AString &name)
|
|
{
|
|
name.Empty();
|
|
struct passwd *passwd;
|
|
|
|
if (uid != 0 && uid == cached_no_such_uid)
|
|
{
|
|
*uname = xstrdup ("");
|
|
return;
|
|
}
|
|
|
|
if (!cached_uname || uid != cached_uid)
|
|
{
|
|
passwd = getpwuid (uid);
|
|
if (passwd)
|
|
{
|
|
cached_uid = uid;
|
|
assign_string (&cached_uname, passwd->pw_name);
|
|
}
|
|
else
|
|
{
|
|
cached_no_such_uid = uid;
|
|
*uname = xstrdup ("");
|
|
return;
|
|
}
|
|
}
|
|
*uname = xstrdup (cached_uname);
|
|
}
|
|
*/
|
|
|
|
bool CFileInfo::Find_DontFill_Name(CFSTR path, bool followLink)
|
|
{
|
|
struct stat st;
|
|
if (MY__lstat(path, &st, followLink) != 0)
|
|
return false;
|
|
// printf("\nFind_DontFill_Name : name=%s\n", path);
|
|
SetFrom_stat(st);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CFileInfo::Find(CFSTR path, bool followLink)
|
|
{
|
|
// printf("\nCEnumerator::Find() name = %s\n", path);
|
|
if (!Find_DontFill_Name(path, followLink))
|
|
return false;
|
|
|
|
// printf("\nOK\n");
|
|
|
|
Name = Get_Name_from_Path(path);
|
|
if (!Name.IsEmpty())
|
|
{
|
|
char c = Name.Back();
|
|
if (IS_PATH_SEPAR(c))
|
|
Name.DeleteBack();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool DoesFileExist_Raw(CFSTR name)
|
|
{
|
|
// FIXME for symbolic links.
|
|
struct stat st;
|
|
if (MY__lstat(name, &st, false) != 0)
|
|
return false;
|
|
return !S_ISDIR(st.st_mode);
|
|
}
|
|
|
|
bool DoesFileExist_FollowLink(CFSTR name)
|
|
{
|
|
// FIXME for symbolic links.
|
|
struct stat st;
|
|
if (MY__lstat(name, &st, true) != 0)
|
|
return false;
|
|
return !S_ISDIR(st.st_mode);
|
|
}
|
|
|
|
bool DoesDirExist(CFSTR name, bool followLink)
|
|
{
|
|
struct stat st;
|
|
if (MY__lstat(name, &st, followLink) != 0)
|
|
return false;
|
|
return S_ISDIR(st.st_mode);
|
|
}
|
|
|
|
bool DoesFileOrDirExist(CFSTR name)
|
|
{
|
|
struct stat st;
|
|
if (MY__lstat(name, &st, false) != 0)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
CEnumerator::~CEnumerator()
|
|
{
|
|
if (_dir)
|
|
closedir(_dir);
|
|
}
|
|
|
|
void CEnumerator::SetDirPrefix(const FString &dirPrefix)
|
|
{
|
|
_wildcard = dirPrefix;
|
|
}
|
|
|
|
bool CDirEntry::IsDots() const throw()
|
|
{
|
|
/* some systems (like CentOS 7.x on XFS) have (Type == DT_UNKNOWN)
|
|
we can call fstatat() for that case, but we use only (Name) check here */
|
|
|
|
#if !defined(_AIX)
|
|
if (Type != DT_DIR && Type != DT_UNKNOWN)
|
|
return false;
|
|
#endif
|
|
|
|
return Name.Len() != 0
|
|
&& Name.Len() <= 2
|
|
&& Name[0] == '.'
|
|
&& (Name.Len() == 1 || Name[1] == '.');
|
|
}
|
|
|
|
|
|
bool CEnumerator::NextAny(CDirEntry &fi, bool &found)
|
|
{
|
|
found = false;
|
|
|
|
if (!_dir)
|
|
{
|
|
const char *w = "./";
|
|
if (!_wildcard.IsEmpty())
|
|
w = _wildcard.Ptr();
|
|
_dir = ::opendir((const char *)w);
|
|
if (_dir == NULL)
|
|
return false;
|
|
}
|
|
|
|
// To distinguish end of stream from an error, we must set errno to zero before readdir()
|
|
errno = 0;
|
|
|
|
struct dirent *de = readdir(_dir);
|
|
if (!de)
|
|
{
|
|
if (errno == 0)
|
|
return true; // it's end of stream, and we report it with (found = false)
|
|
// it's real error
|
|
return false;
|
|
}
|
|
|
|
fi.iNode = de->d_ino;
|
|
|
|
#if !defined(_AIX)
|
|
fi.Type = de->d_type;
|
|
/* some systems (like CentOS 7.x on XFS) have (Type == DT_UNKNOWN)
|
|
we can set (Type) from fstatat() in that case.
|
|
But (Type) is not too important. So we don't set it here with slow fstatat() */
|
|
/*
|
|
// fi.Type = DT_UNKNOWN; // for debug
|
|
if (fi.Type == DT_UNKNOWN)
|
|
{
|
|
struct stat st;
|
|
if (fstatat(dirfd(_dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) == 0)
|
|
if (S_ISDIR(st.st_mode))
|
|
fi.Type = DT_DIR;
|
|
}
|
|
*/
|
|
#endif
|
|
|
|
/*
|
|
if (de->d_type == DT_DIR)
|
|
fi.Attrib = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_UNIX_EXTENSION | ((UInt32)(S_IFDIR) << 16);
|
|
else if (de->d_type < 16)
|
|
fi.Attrib = FILE_ATTRIBUTE_UNIX_EXTENSION | ((UInt32)(de->d_type) << (16 + 12));
|
|
*/
|
|
fi.Name = de->d_name;
|
|
|
|
/*
|
|
printf("\nCEnumerator::NextAny; len = %d %s \n", (int)fi.Name.Len(), fi.Name.Ptr());
|
|
for (unsigned i = 0; i < fi.Name.Len(); i++)
|
|
printf (" %02x", (unsigned)(Byte)de->d_name[i]);
|
|
printf("\n");
|
|
*/
|
|
|
|
found = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CEnumerator::Next(CDirEntry &fi, bool &found)
|
|
{
|
|
// printf("\nCEnumerator::Next()\n");
|
|
// PrintName("Next", "");
|
|
for (;;)
|
|
{
|
|
if (!NextAny(fi, found))
|
|
return false;
|
|
if (!found)
|
|
return true;
|
|
if (!fi.IsDots())
|
|
{
|
|
/*
|
|
if (!NeedFullStat)
|
|
return true;
|
|
// we silently skip error file here - it can be wrong link item
|
|
if (fi.Find_DontFill_Name(path))
|
|
return true;
|
|
*/
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
bool CEnumerator::Next(CDirEntry &fileInfo, bool &found)
|
|
{
|
|
bool found;
|
|
if (!Next(fi, found))
|
|
return false;
|
|
return found;
|
|
}
|
|
*/
|
|
|
|
bool CEnumerator::Fill_FileInfo(const CDirEntry &de, CFileInfo &fileInfo, bool followLink) const
|
|
{
|
|
// printf("\nCEnumerator::Fill_FileInfo()\n");
|
|
struct stat st;
|
|
// probably it's OK to use fstatat() even if it changes file position dirfd(_dir)
|
|
int res = fstatat(dirfd(_dir), de.Name, &st, followLink ? 0 : AT_SYMLINK_NOFOLLOW);
|
|
// if fstatat() is not supported, we can use stat() / lstat()
|
|
|
|
/*
|
|
const FString path = _wildcard + s;
|
|
int res = MY__lstat(path, &st, followLink);
|
|
*/
|
|
|
|
if (res != 0)
|
|
return false;
|
|
// printf("\nname=%s\n", de.Name.Ptr());
|
|
fileInfo.SetFrom_stat(st);
|
|
fileInfo.Name = de.Name;
|
|
return true;
|
|
}
|
|
|
|
#endif // _WIN32
|
|
|
|
}}}
|