Discussion:
"System Volume Information" question
(too old to reply)
Bonita Montero
2020-02-04 10:28:04 UTC
Permalink
Can aynone here tell me how to exclude the "System Volume Information"
folder from searching with "FindFirstFile" / "FindNextFile" ? Is there
any special attibute in WIN32_FIND_DATA::dwFileAttributes ?
Thanks in advance.
Bonita Montero
2020-02-04 11:09:31 UTC
Permalink
Post by Bonita Montero
Can aynone here tell me how to exclude the "System Volume Information"
folder from searching with "FindFirstFile" / "FindNextFile" ? Is there
any special attibute in WIN32_FIND_DATA::dwFileAttributes ?
Thanks in advance.
Sorry, stupid question. If FindFirstFile can't find a file because the
permissions of "System Volume Information" don't allow that there aren't
any attributes in WIN32_FIND_DATA.
I asked for this because I wrote a little tool that sets characters that
are continously written uppercase (or lowercase) between points, i.e.
when you have an extension ".EXE" to lowercase. If you chose the force
-option all characters are reverted to lowercase or uppercase.
Here's the sourcecode:

#include <windows.h>
#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include <cstdlib>

using namespace std;

bool recurseToSubdirs = false,
toUpper = false,
force = false,
hidden = false,
quietMode = false;
bool error = false;

wchar_t const *usage =
L"-? help\n"
"-r / -s recurse through subdirectories\n"
"-u uppercase instead of lowercase\n"
"-f force lowercase / uppercase\n"
"-q quiet mode";

void recurseThroughSubdirs( wchar_t const *filePattern, size_t pathLength );

int main()
{
try
{
int argc;
wchar_t **argv = CommandLineToArgvW( GetCommandLineW(),
&argc ),
**argvScn = argv + 1,
**argvScnEnd = argv + argc,
*argScn;
if( !argv )
return EXIT_FAILURE;
vector<wstring> filePatterns;
for( ; argvScn < argvScnEnd; )
if( *(argScn = *argvScn++) == L'-' )
{

if( !argScn[2] )
switch( tolower( argScn[1] ) )
{
case L'r':
case L's':
::recurseToSubdirs = true;
continue;
case L'u':
::toUpper = true;
continue;
case L'f':
::force = true;
continue;
case L'q':
::quietMode = true;
continue;
case L'h':
::hidden = true;
continue;
case L'?':
wcout << ::usage << endl;
return EXIT_SUCCESS;
continue;
}
wcerr << L"unknown command-line option" << argScn << endl;
return EXIT_FAILURE;
}
else
filePatterns.emplace_back( argScn );
for( wstring &fp : filePatterns )
{
size_t pathLength;
for( pathLength = fp.length(); pathLength && fp[pathLength
- 1] != '\\'; --pathLength );
recurseThroughSubdirs( fp.c_str(), pathLength );
}
return !::error ? EXIT_SUCCESS : EXIT_FAILURE;
}
catch( bad_alloc & )
{
wcerr << L"out of memory" << endl;
return EXIT_FAILURE;
}
catch( ... )
{
return EXIT_FAILURE;
}
}

struct WinFind
{
WinFind()
{
hFind = INVALID_HANDLE_VALUE;
}
~WinFind()
{
if( hFind != INVALID_HANDLE_VALUE )
FindClose( hFind );
}
void close()
{
FindClose( hFind );
hFind = INVALID_HANDLE_VALUE;
}
HANDLE hFind;
};

void recurseThroughSubdirs( wchar_t const *filePattern, size_t pathLength )
{
auto isCharLower = []( wchar_t c ) -> bool
{
return IsCharLower( c );
};
auto isCharUpper = []( wchar_t c ) -> bool
{
return IsCharUpper( c );
};
auto toLower = []( wchar_t *str, size_t count )
{
CharLowerBuff( str, count );
};
auto toUpper = []( wchar_t *str, size_t count )
{
CharUpperBuff( str, count );
};
struct
{
bool (*caseChk)( wchar_t );
void (*caseCvt)( wchar_t *str, size_t count );
} cvtTable[] =
{
isCharLower, toLower,
isCharUpper, toUpper
}, *cvt = &cvtTable[::toUpper];
DWORD dwError;
WIN32_FIND_DATAW fd;
WinFind find;
if( (find.hFind = FindFirstFileW( filePattern, &fd )) !=
INVALID_HANDLE_VALUE )
{
do
if( ((fd.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) ||
(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) &&
wcscmp( fd.cFileName, L"." ) != 0 && wcscmp(
fd.cFileName, L".." ) != 0 &&
(!(fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ||
::hidden) )
{
wstring oldFileName,
newFileName;
size_t fnLength;
oldFileName.append( filePattern, pathLength );
fnLength = wcslen( fd.cFileName );
oldFileName.append( fd.cFileName, fnLength );
newFileName = oldFileName;
if( !::force )
for( wchar_t *fnPart = (wchar_t
*)newFileName.c_str() + pathLength; *fnPart; )
if( *fnPart == L'.' )
++fnPart;
else
{
bool convert = true;
wchar_t *partScn;
for( partScn = fnPart; ; )
if( !*partScn || *partScn == '.' )
{
if( convert )
cvt->caseCvt( fnPart, partScn -
fnPart );
partScn += (bool)*partScn;
break;
}
else
if( cvt->caseChk( *partScn++ ) )
convert = false;
fnPart = partScn;
}
else
cvt->caseCvt( &newFileName[0] + pathLength, fnLength );
if( oldFileName != newFileName )
{
bool success = MoveFileW( oldFileName.c_str(),
newFileName.c_str() );
if( !::quietMode )
wcout << L"\"" << oldFileName << L"\" -> \"" <<
newFileName << (success ? L"\"" : L"\" - failue") << endl;
::error |= !success;
if( !success )
error |= error;
}
}
while( FindNextFileW( find.hFind, &fd ) );
if( GetLastError() != ERROR_NO_MORE_FILES )
::error = true,
wcerr << "error finding next file for pattern:" << endl <<
L"\"" << filePattern << L"\"" << endl;
find.close();
}
else
if( (dwError = GetLastError()) != ERROR_FILE_NOT_FOUND &&
dwError != ERROR_ACCESS_DENIED )
::error = true,
wcerr << "error finding first file for pattern:" << endl <<
L"\"" << filePattern << L"\"" << endl;
if( ::recurseToSubdirs )
{
wstring subdirPattern;
subdirPattern.reserve( pathLength + 1 );
subdirPattern.append( filePattern, pathLength );
subdirPattern += L"*";
if( (find.hFind = FindFirstFileW( subdirPattern.c_str(), &fd ))
!= INVALID_HANDLE_VALUE )
{
do
if( (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
wcscmp( fd.cFileName, L"." ) != 0 && wcscmp(
fd.cFileName, L".." ) != 0 )
{
wstring nextFilePattern;
size_t nextPathLength;
nextPathLength = pathLength + wcslen( fd.cFileName
) + 1;
nextFilePattern.reserve( nextPathLength + wcslen(
filePattern + pathLength ) );
nextFilePattern.append( filePattern, pathLength );
nextFilePattern += fd.cFileName;
nextFilePattern += L"\\";
nextFilePattern += filePattern + pathLength;
recurseThroughSubdirs( nextFilePattern.c_str(),
nextPathLength );
}
while( FindNextFileW( find.hFind, &fd ) );
if( GetLastError() != ERROR_NO_MORE_FILES )
::error = true,
wcerr << "error finding next directory for pattern:" <<
endl << L"\"" << subdirPattern << L"\"" << endl;
}
else
if( (dwError = GetLastError()) != ERROR_FILE_NOT_FOUND &&
dwError != ERROR_ACCESS_DENIED )
::error = true,
wcerr << "error finding first directory for pattern:"
<< endl << L"\"" << subdirPattern << L"\"" << endl;
}
}
JJ
2020-02-04 16:47:12 UTC
Permalink
Post by Bonita Montero
Post by Bonita Montero
Can aynone here tell me how to exclude the "System Volume Information"
folder from searching with "FindFirstFile" / "FindNextFile" ? Is there
any special attibute in WIN32_FIND_DATA::dwFileAttributes ?
Thanks in advance.
Sorry, stupid question. If FindFirstFile can't find a file because the
permissions of "System Volume Information" don't allow that there aren't
any attributes in WIN32_FIND_DATA.
No. FindFirstFile/FindNextFile can find the "System Volume Information"
directory itself in the root directory without problem. The problem is
because your code finds files recursively, and when it tries to read the
contents of that directory, the NTFS permission disallows the operation.
Bonita Montero
2020-02-04 17:51:27 UTC
Permalink
Post by JJ
Post by Bonita Montero
Sorry, stupid question. If FindFirstFile can't find a file because the
permissions of "System Volume Information" don't allow that there aren't
any attributes in WIN32_FIND_DATA.
No. FindFirstFile/FindNextFile can find the "System Volume Information"
directory itself in the root directory without problem.
I meant the enumeration of the files *in* "System Volume Information.
JJ
2020-02-05 22:20:27 UTC
Permalink
Post by Bonita Montero
Post by JJ
Post by Bonita Montero
Sorry, stupid question. If FindFirstFile can't find a file because the
permissions of "System Volume Information" don't allow that there aren't
any attributes in WIN32_FIND_DATA.
No. FindFirstFile/FindNextFile can find the "System Volume Information"
directory itself in the root directory without problem.
I meant the enumeration of the files *in* "System Volume Information.
Your code is the one who told FindFirstFile to enumerate files within that
folder. So you'll have to make the code to skip that directory contents from
being enumerated.

Also, you can not tell FindFirstFile to skip enumerating that directory if
the file mask matches that directory name.

And FindFirstFile can not be used to retrieve security attributes of a
files/directory. You'll have to use the Security API functions.
Bonita Montero
2020-02-06 18:18:50 UTC
Permalink
Post by JJ
Your code is the one who told FindFirstFile to enumerate files within that
folder. So you'll have to make the code to skip that directory contents from
being enumerated.
I simply skipped files where I get a GetLastError() of ERROR_ACCESS
_DENIED. And I dont't use CharLowerBuff / CharUpperBuff anymore because
the length of the output-string can be different because of UTF-16.
Here's the new code:

#include <windows.h>
#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <cctype>

using namespace std;

bool recurseThroughSubdirs = false,
toUppercase = false,
force = false,
hidden = false,
quietMode = false;
bool error = false;

void renameFiles( wchar_t const *filePattern, size_t pathLength );
void usage();

int main()
{
try
{
struct Option
{
wchar_t const *str;
void (*fn)();
};
static Option const options[] =
{
L"-r", []() { ::recurseThroughSubdirs = true; },
L"-s", []() { ::recurseThroughSubdirs = true; },
L"-u", []() { ::toUppercase = true; },
L"-f", []() { ::force = true; },
L"-q", []() { ::quietMode = true; },
L"-h", []() { ::hidden = true; },
L"-?", []() { usage(); }
};
int argc;
wchar_t **argv = CommandLineToArgvW( GetCommandLineW(),
&argc ),
**argvScn = argv + 1,
**argvScnEnd = argv + argc;
if( !argv )
return EXIT_FAILURE;
for( ; argvScn < argvScnEnd; argvScn++ )
{
bool match = false;
for( Option const &opt : options )
if( wcscmp( opt.str, *argvScn ) == 0 )
{
match = true;
opt.fn();
break;
}
if( !match )
break;
}
vector<wstring> filePatterns;
for( ; argvScn < argvScnEnd; argvScn++ )
filePatterns.emplace_back( *argvScn );
for( wstring &fp : filePatterns )
{
size_t pathLength;
for( pathLength = fp.length(); pathLength && fp[pathLength
- 1] != '\\'; --pathLength );
renameFiles( fp.c_str(), pathLength );
}
return !::error ? EXIT_SUCCESS : EXIT_FAILURE;
}
catch( bad_alloc & )
{
wcerr << L"out of memory" << endl;
return EXIT_FAILURE;
}
catch( ... )
{
return EXIT_FAILURE;
}
}

bool convertFileName( wchar_t const *oldFileName, size_t ofnLlength,
wstring &newFileName, bool uppercase, bool force );

struct WinFind
{
WinFind()
{
hFind = INVALID_HANDLE_VALUE;
}
~WinFind()
{
if( hFind != INVALID_HANDLE_VALUE )
FindClose( hFind );
}
void close()
{
FindClose( hFind );
hFind = INVALID_HANDLE_VALUE;
}
HANDLE hFind;
};

void renameFiles( wchar_t const *filePattern, size_t pathLength )
{
DWORD dwError;
WIN32_FIND_DATAW fd;
WinFind find;
if( (find.hFind = FindFirstFileW( filePattern, &fd )) !=
INVALID_HANDLE_VALUE )
{
do
if( wcscmp( fd.cFileName, L"." ) != 0 && wcscmp(
fd.cFileName, L".." ) != 0 &&
(!(fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ||
::hidden) )
{
size_t oldFnLength = wcslen( fd.cFileName );
wstring newFileName;
if( !convertFileName( fd.cFileName, oldFnLength,
newFileName, ::toUppercase, ::force ) )
{
wcerr << "error convertig filename \"" <<
fd.cFileName << L"\" to " << (!::toUppercase ? L"lowecase" :
L"uppercase") << endl;
::error = true;
continue;
}
wstring oldFullFileName,
newFullFileName;
oldFullFileName.reserve( pathLength + oldFnLength );
oldFullFileName.append( filePattern, pathLength );
oldFullFileName += fd.cFileName;
newFileName.reserve( pathLength + newFileName.length() );
newFullFileName.append( filePattern, pathLength );
newFullFileName += newFileName;
if( oldFullFileName != newFullFileName )
{
bool success = MoveFileW( oldFullFileName.c_str(),
newFullFileName.c_str() );
if( !::quietMode )
wcout << L"\"" << oldFullFileName << L"\" ->
\"" << newFullFileName << (success ? L"\"" : L"\" - failue") << endl;
::error |= !success;
}
}
while( FindNextFileW( find.hFind, &fd ) );
if( GetLastError() != ERROR_NO_MORE_FILES )
::error = true,
wcerr << "error finding next file for pattern:" << endl <<
L"\"" << filePattern << L"\"" << endl;
find.close();
}
else
if( (dwError = GetLastError()) != ERROR_FILE_NOT_FOUND &&
dwError != ERROR_ACCESS_DENIED )
::error = true,
wcerr << "error finding first file for pattern:" << endl <<
L"\"" << filePattern << L"\"" << endl;
if( ::recurseThroughSubdirs )
{
wstring subdirPattern;
subdirPattern.reserve( pathLength + 1 );
subdirPattern.append( filePattern, pathLength );
subdirPattern += L"*";
if( (find.hFind = FindFirstFileW( subdirPattern.c_str(), &fd ))
!= INVALID_HANDLE_VALUE )
{
do
if( fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY &&
wcscmp( fd.cFileName, L"." ) != 0 && wcscmp(
fd.cFileName, L".." ) != 0 )
{
wstring nextFilePattern;
size_t nextPathLength;
nextPathLength = pathLength + wcslen( fd.cFileName
) + 1;
nextFilePattern.reserve( nextPathLength + wcslen(
filePattern + pathLength ) );
nextFilePattern.append( filePattern, pathLength );
nextFilePattern += fd.cFileName;
nextFilePattern += L"\\";
nextFilePattern += filePattern + pathLength;
renameFiles( nextFilePattern.c_str(), nextPathLength );
}
while( FindNextFileW( find.hFind, &fd ) );
if( GetLastError() != ERROR_NO_MORE_FILES )
::error = true,
wcerr << "error finding next directory for pattern:" <<
endl << L"\"" << subdirPattern << L"\"" << endl;
}
else
if( (dwError = GetLastError()) != ERROR_FILE_NOT_FOUND &&
dwError != ERROR_ACCESS_DENIED )
::error = true,
wcerr << "error finding first directory for pattern:"
<< endl << L"\"" << subdirPattern << L"\"" << endl;
}
}

bool caseCvt( wchar_t const *str, size_t length, bool uppercase, wstring
&newStr );
unsigned utf16Length( wchar_t const *ch, wchar_t const *end );
int isLowercaseOrUppercase( wchar_t const *ch, wchar_t const *end, bool
uppercase );

bool convertFileName( wchar_t const *oldFileName, size_t ofnLlength,
wstring &newFileName, bool uppercase, bool force )
{
newFileName.reserve( 2 * ofnLlength );
newFileName.resize( 0 );
if( force )
return caseCvt( oldFileName, ofnLlength, uppercase, newFileName );
for( wchar_t const *part = oldFileName,
*partsEnd = oldFileName + ofnLlength;
part != partsEnd; )
{
wchar_t const *partScn;
for( partScn = part; ; )
if( partScn != partsEnd && *partScn != L'.' )
{
int caseRet = isLowercaseOrUppercase( partScn,
partsEnd, uppercase );
if( caseRet < 0 )
return false;
partScn += utf16Length( partScn, partsEnd );
if( caseRet )
{
unsigned len;
for( ; partScn != partsEnd && *partScn != L'.'; )
if( (len = utf16Length( partScn, partsEnd )) != 0 )
partScn += len;
else
return false;
for( ; partScn != partsEnd && *partScn == L'.';
++partScn );
newFileName.append( part, partScn - part );
break;
}
}
else
{
wstring convertedPart;
if( !caseCvt( part, partScn - part, uppercase,
convertedPart ) )
return false;
newFileName += convertedPart;
if( partScn != partsEnd )
newFileName += L".",
++partScn;
break;
}
part = partScn;
}
return true;
}

bool caseCvt( wchar_t const *str, size_t length, bool uppercase, wstring
&newStr )
{
if( length == 0 )
{
newStr = L"";
return true;
}
DWORD dwMapFlags = !uppercase ? LCMAP_LOWERCASE : LCMAP_UPPERCASE;
size_t newSize = (size_t)LCMapStringEx( LOCALE_NAME_INVARIANT,
dwMapFlags, str, length, nullptr, 0, nullptr, nullptr, 0 );
if( newSize == 0 )
return false;
newStr.resize( newSize );
return LCMapStringEx( LOCALE_NAME_INVARIANT, dwMapFlags, str,
length, &newStr[0], newSize, nullptr, nullptr, 0 ) != 0;
}

inline
unsigned utf16Length( wchar_t const *ch, wchar_t const *end )
{
assert(ch < end);
if( (*ch & 0b1111'1000'0000'0000) != 0b1101'1000'0000'0000 )
return 1;
unsigned length = 2 - (*ch >> 10 & 1);
assert(length == 1 || (ch + 1) < end);
return (size_t)(end - ch) >= length ? length : 0;
}

int isLowercaseOrUppercase( wchar_t const *ch, wchar_t const *end, bool
uppercase )
{
wchar_t chConverted[2];
unsigned length = utf16Length( ch, end );
if( length == 0 )
return -1;
int newLength = LCMapStringEx( LOCALE_NAME_INVARIANT, !uppercase ?
LCMAP_LOWERCASE : LCMAP_UPPERCASE, ch, length, chConverted, 2, nullptr,
nullptr, 0 );
if( newLength == 0 )
return -2;
return newLength == length && memcmp( ch, chConverted, length *
sizeof(wchar_t) ) == 0 ? 1 : 0;
}

void usage()
{
wcout << L"-? help\n"
"-r / -s recurse through subdirectories\n"
"-u uppercase instead of lowercase\n"
"-f force lowercase / uppercase\n"
"-q quiet mode";

}

R.Wieser
2020-02-06 08:36:23 UTC
Permalink
Bonita,
Post by Bonita Montero
Post by Bonita Montero
Can aynone here tell me how to exclude the "System Volume Information"
folder from searching with "FindFirstFile" / "FindNextFile" ?
You can't. You can provide a filemask so you get only certain entries,
but you cannot even tell it to only return files or folders (if you want
that take a look at "FindFirstFileEx") .

But what you can do is to /compare the result/ of the above functions with
some strings that you do not want to see (just like you (probably) already
do for the "." and ".." folders). I used the "PathMatchSpec" (shlwapi) for
that.

Another way is to check if you actually have permissions to access a certain
folder. Like checking the (error) result of the "FindFirstFile" call and
just silently return if the result shows that you do not have the neccessary
permissions.
Post by Bonita Montero
I meant the enumeration of the files *in* "System Volume Information.
You /do not have the permissions/ to look into that folder. Take a wild
guess to what the solution to that problem is.

Regards,
Rudy Wieser
Loading...