[NTVDM] Improve DOS file search implementation. Addendum to commit 86ba2faa (r67619).

We get file matches when their attributes match and we can obtain
a valid 8.3 file name. This last condition is of upmost importance.
We cannot reliably use GetShortPathName() on the filename returned by
FindFirstFile()/FindNextFile() because the result is uncertain (one
either needs to build a full path before calling the function just to
get the final short path form, or change the current working directory
to the one where the search is being made, etc.)

The Find*file() functions return anyway the short file. name (in the
cAlternateFileName member) if the file does have such 8.3 filename and
its real name (in cFileName) is longer. Otherwise, cFileName could
already be *THE* short filename because it has the correct format for
it. Check this latter case with RtlIsNameLegalDOS8Dot3() and use it if
so. Otherwise this means the file has a long filename that cannot be
converted to short filename format (because e.g. it is in a volume where
short filenames are unavailable), and we skip such files: they wouldn't
be accessible from DOS anyways.

- Doxygen-document demFileFindFirst(), demFileFindNext() and associated
  flags and structures. Update their annotations.

This fixes TurboC 2.x installation.
This commit is contained in:
Hermès Bélusca-Maïto 2021-12-03 22:20:21 +01:00
parent fcbdab0037
commit 81d5e650de
No known key found for this signature in database
GPG Key ID: 3B2539C65E7B93D0
3 changed files with 218 additions and 63 deletions

View File

@ -1406,95 +1406,225 @@ demFileDelete(IN LPCSTR FileName)
return GetLastError();
}
/**
* @brief Helper for demFileFindFirst() and demFileFindNext().
* Returns TRUE if a file matches the DOS attributes and has a 8.3 file name.
**/
static BOOLEAN
dempIsFileMatch(
_Inout_ PWIN32_FIND_DATAA FindData,
_In_ WORD AttribMask,
_Out_ PCSTR* ShortName)
{
/* Convert in place the attributes to DOS format */
FindData->dwFileAttributes = NT_TO_DOS_FA(FindData->dwFileAttributes);
/* Check the attributes */
if ((FindData->dwFileAttributes & (FA_HIDDEN | FA_SYSTEM | FA_DIRECTORY))
& ~AttribMask)
{
return FALSE;
}
/* Check whether the file has a 8.3 file name */
if (*FindData->cAlternateFileName)
{
/* Use the available one */
*ShortName = FindData->cAlternateFileName;
return TRUE;
}
else
{
/*
* Verify whether the original long name is actually a valid
* 8.3 file name. Note that we cannot use GetShortPathName()
* since the latter works on full paths, that we do not always have.
*/
BOOLEAN IsNameLegal, SpacesInName;
WCHAR FileNameBufferU[_countof(FindData->cFileName) + 1];
UNICODE_STRING FileNameU;
ANSI_STRING FileNameA;
RtlInitAnsiString(&FileNameA, FindData->cFileName);
RtlInitEmptyUnicodeString(&FileNameU, FileNameBufferU, sizeof(FileNameBufferU));
RtlAnsiStringToUnicodeString(&FileNameU, &FileNameA, FALSE);
IsNameLegal = RtlIsNameLegalDOS8Dot3(&FileNameU,
NULL, // (lpOemName ? &AnsiName : NULL),
&SpacesInName);
if (!IsNameLegal || SpacesInName)
{
/* This is an error situation */
DPRINT1("'%.*s' is %s 8.3 filename %s spaces\n",
_countof(FindData->cFileName), FindData->cFileName,
(IsNameLegal ? "a valid" : "an invalid"), (SpacesInName ? "with" : "without"));
}
if (IsNameLegal && !SpacesInName)
{
/* We can use the original name */
*ShortName = FindData->cFileName;
return TRUE;
}
}
DPRINT1("No short 8.3 filename available for '%.*s'\n",
_countof(FindData->cFileName), FindData->cFileName);
return FALSE;
}
/**
* @name demFileFindFirst
* Implementation of the DOS INT 21h, AH=4Eh "Find First File" function.
*
* Starts enumerating files that match the given file search specification
* and whose attributes are _at most_ those specified by the mask. This means
* in particular that "normal files", i.e. files with no attributes set, are
* always enumerated along those matching the requested attributes.
*
* @param[out] pFindFileData
* Pointer to the DTA (Disk Transfer Area) filled with FindFirst data block.
*
* @param[in] FileName
* File search specification (may include path and wildcards).
*
* @param[in] AttribMask
* Mask of file attributes. Includes files with a given attribute bit set
* if the corresponding bit is set to 1 in the mask. Excludes files with a
* given attribute bit set if the corresponding bit is set to 0 in the mask.
* Supported file attributes:
* FA_NORMAL 0x0000
* FA_READONLY 0x0001 (ignored)
* FA_HIDDEN 0x0002
* FA_SYSTEM 0x0004
* FA_VOLID 0x0008 (not currently supported)
* FA_LABEL
* FA_DIRECTORY 0x0010
* FA_ARCHIVE 0x0020 (ignored)
* FA_DEVICE 0x0040 (ignored)
*
* @return
* ERROR_SUCCESS on success (found match), or a last error (match not found).
*
* @see demFileFindNext()
**/
DWORD
WINAPI
demFileFindFirst(OUT PVOID lpFindFileData,
IN LPCSTR FileName,
IN WORD AttribMask)
demFileFindFirst(
_Out_ PVOID pFindFileData,
_In_ PCSTR FileName,
_In_ WORD AttribMask)
{
BOOLEAN Success = TRUE;
WIN32_FIND_DATAA FindData;
PDOS_FIND_FILE_BLOCK FindFileBlock = (PDOS_FIND_FILE_BLOCK)pFindFileData;
HANDLE SearchHandle;
PDOS_FIND_FILE_BLOCK FindFileBlock = (PDOS_FIND_FILE_BLOCK)lpFindFileData;
WIN32_FIND_DATAA FindData;
PCSTR ShortName = NULL;
/* Reset the private block fields */
RtlZeroMemory(FindFileBlock, RTL_SIZEOF_THROUGH_FIELD(DOS_FIND_FILE_BLOCK, SearchHandle));
// TODO: Handle FA_VOLID for volume label.
if (AttribMask & FA_VOLID)
{
DPRINT1("demFileFindFirst: Volume label attribute is UNIMPLEMENTED!\n");
AttribMask &= ~FA_VOLID; // Remove it for the time being...
}
/* Filter out the ignored attributes */
AttribMask &= ~(FA_DEVICE | FA_ARCHIVE | FA_READONLY);
/* Start a search */
SearchHandle = FindFirstFileA(FileName, &FindData);
if (SearchHandle == INVALID_HANDLE_VALUE) return GetLastError();
do
{
/* Check the attributes and retry as long as we haven't found a matching file */
if (!((FindData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN |
FILE_ATTRIBUTE_SYSTEM |
FILE_ATTRIBUTE_DIRECTORY))
& ~AttribMask))
{
break;
}
}
while ((Success = FindNextFileA(SearchHandle, &FindData)));
/* If we failed at some point, close the search and return an error */
if (!Success)
{
FindClose(SearchHandle);
if (SearchHandle == INVALID_HANDLE_VALUE)
return GetLastError();
/* Check the attributes and retry as long as we haven't found a matching file */
while (!dempIsFileMatch(&FindData, AttribMask, &ShortName))
{
/* Continue searching. If we fail at some point,
* stop the search and return an error. */
if (!FindNextFileA(SearchHandle, &FindData))
{
FindClose(SearchHandle);
return GetLastError();
}
}
/* Fill the block */
FindFileBlock->DriveLetter = DosData->Sda.CurrentDrive + 'A';
strncpy(FindFileBlock->Pattern, FileName, _countof(FindFileBlock->Pattern));
FindFileBlock->AttribMask = AttribMask;
FindFileBlock->SearchHandle = SearchHandle;
FindFileBlock->Attributes = LOBYTE(FindData.dwFileAttributes);
FileTimeToDosDateTime(&FindData.ftLastWriteTime,
&FindFileBlock->FileDate,
&FindFileBlock->FileTime);
FindFileBlock->FileSize = FindData.nFileSizeHigh ? 0xFFFFFFFF
FindFileBlock->FileSize = FindData.nFileSizeHigh ? MAXDWORD
: FindData.nFileSizeLow;
/* Build a short path name */
if (*FindData.cAlternateFileName)
strncpy(FindFileBlock->FileName, FindData.cAlternateFileName, sizeof(FindFileBlock->FileName));
else
GetShortPathNameA(FindData.cFileName, FindFileBlock->FileName, sizeof(FindFileBlock->FileName));
/* Copy the NULL-terminated short file name */
RtlStringCchCopyA(FindFileBlock->FileName,
_countof(FindFileBlock->FileName),
ShortName);
return ERROR_SUCCESS;
}
/**
* @name demFileFindNext
* Implementation of the DOS INT 21h, AH=4Fh "Find Next File" function.
*
* Continues enumerating files, with the same file search specification
* and attributes as those given to the first demFileFindFirst() call.
*
* @param[in,out] pFindFileData
* Pointer to the DTA (Disk Transfer Area) filled with FindFirst data block.
*
* @return
* ERROR_SUCCESS on success (found match), or a last error (match not found).
*
* @see demFileFindFirst()
**/
DWORD
WINAPI
demFileFindNext(OUT PVOID lpFindFileData)
demFileFindNext(
_Inout_ PVOID pFindFileData)
{
PDOS_FIND_FILE_BLOCK FindFileBlock = (PDOS_FIND_FILE_BLOCK)pFindFileData;
HANDLE SearchHandle = FindFileBlock->SearchHandle;
WIN32_FIND_DATAA FindData;
PDOS_FIND_FILE_BLOCK FindFileBlock = (PDOS_FIND_FILE_BLOCK)lpFindFileData;
PCSTR ShortName = NULL;
do
{
/* Continue searching as long as we haven't found a matching file */
/* If we failed at some point, close the search and return an error */
if (!FindNextFileA(FindFileBlock->SearchHandle, &FindData))
/* Continue searching. If we fail at some point,
* stop the search and return an error. */
if (!FindNextFileA(SearchHandle, &FindData))
{
FindClose(FindFileBlock->SearchHandle);
FindClose(SearchHandle);
/* Reset the private block fields */
RtlZeroMemory(FindFileBlock, RTL_SIZEOF_THROUGH_FIELD(DOS_FIND_FILE_BLOCK, SearchHandle));
return GetLastError();
}
}
while ((FindData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN |
FILE_ATTRIBUTE_SYSTEM |
FILE_ATTRIBUTE_DIRECTORY))
& ~FindFileBlock->AttribMask);
/* Check the attributes and retry as long as we haven't found a matching file */
while (!dempIsFileMatch(&FindData, FindFileBlock->AttribMask, &ShortName));
/* Update the block */
FindFileBlock->Attributes = LOBYTE(FindData.dwFileAttributes);
FileTimeToDosDateTime(&FindData.ftLastWriteTime,
&FindFileBlock->FileDate,
&FindFileBlock->FileTime);
FindFileBlock->FileSize = FindData.nFileSizeHigh ? 0xFFFFFFFF
FindFileBlock->FileSize = FindData.nFileSizeHigh ? MAXDWORD
: FindData.nFileSizeLow;
/* Build a short path name */
if (*FindData.cAlternateFileName)
strncpy(FindFileBlock->FileName, FindData.cAlternateFileName, sizeof(FindFileBlock->FileName));
else
GetShortPathNameA(FindData.cFileName, FindFileBlock->FileName, sizeof(FindFileBlock->FileName));
/* Copy the NULL-terminated short file name */
RtlStringCchCopyA(FindFileBlock->FileName,
_countof(FindFileBlock->FileName),
ShortName);
return ERROR_SUCCESS;
}

View File

@ -15,6 +15,7 @@
/* INCLUDES *******************************************************************/
#include <crt/dos.h> // For _A_NORMAL etc.
#include "dos32krnl/dos.h"
/* DEFINES ********************************************************************/
@ -62,21 +63,36 @@ demFileDelete
IN LPCSTR FileName
);
DWORD
WINAPI
demFileFindFirst
(
OUT PVOID lpFindFileData,
IN LPCSTR FileName,
IN WORD AttribMask
);
/**
* @brief File attributes for demFileFindFirst().
**/
#define FA_NORMAL _A_NORMAL // 0x0000
#define FA_READONLY _A_RDONLY // 0x0001 // FILE_ATTRIBUTE_READONLY
#define FA_HIDDEN _A_HIDDEN // 0x0002 // FILE_ATTRIBUTE_HIDDEN
#define FA_SYSTEM _A_SYSTEM // 0x0004 // FILE_ATTRIBUTE_SYSTEM
#define FA_VOLID _A_VOLID // 0x0008
#define FA_LABEL FA_VOLID
#define FA_DIRECTORY _A_SUBDIR // 0x0010 // FILE_ATTRIBUTE_DIRECTORY
#define FA_ARCHIVE _A_ARCH // 0x0020 // FILE_ATTRIBUTE_ARCHIVE
#define FA_DEVICE 0x0040 // FILE_ATTRIBUTE_DEVICE
#define FA_VALID (FA_ARCHIVE | FA_DIRECTORY | FA_SYSTEM | FA_HIDDEN | FA_READONLY | FA_NORMAL)
/** @brief Convert Win32/NT file attributes to DOS format. */
#define NT_TO_DOS_FA(Attrs) \
( ((Attrs) == FILE_ATTRIBUTE_NORMAL) ? FA_NORMAL : (LOBYTE(Attrs) & FA_VALID) )
DWORD
WINAPI
demFileFindNext
(
OUT PVOID lpFindFileData
);
demFileFindFirst(
_Out_ PVOID pFindFileData,
_In_ PCSTR FileName,
_In_ WORD AttribMask);
DWORD
WINAPI
demFileFindNext(
_Inout_ PVOID pFindFileData);
UCHAR
WINAPI

View File

@ -119,20 +119,29 @@ typedef struct _DOS_INPUT_BUFFER
CHAR Buffer[ANYSIZE_ARRAY];
} DOS_INPUT_BUFFER, *PDOS_INPUT_BUFFER;
/**
* @struct DOS_FIND_FILE_BLOCK
* Data block returned in the DTA (Disk Transfer Area) by the
* INT 21h, AH=4Eh "Find First File" and the INT 21h, AH=4Fh "Find Next File"
* functions.
*
* @see demFileFindFirst(), demFileFindNext()
**/
typedef struct _DOS_FIND_FILE_BLOCK
{
/* The 21 first bytes (0x00 to 0x14 included) are reserved */
CHAR DriveLetter;
CHAR Pattern[11];
UCHAR AttribMask;
DWORD Unused;
HANDLE SearchHandle;
DWORD Unused; // FIXME: We must NOT store a Win32 handle here!
HANDLE SearchHandle; // Instead we should use an ID and helpers to map it to Win32.
/* The following part of the structure is documented */
UCHAR Attributes;
WORD FileTime;
WORD FileDate;
DWORD FileSize;
CHAR FileName[13];
_Null_terminated_ CHAR FileName[13];
} DOS_FIND_FILE_BLOCK, *PDOS_FIND_FILE_BLOCK;
// http://www.ctyme.com/intr/rb-3023.htm