Fixed bug: MapDir on non-existent directories reported TRUE. Improved generated mappings report

This commit is contained in:
Berserker 2019-05-26 13:51:04 +03:00
parent 7cb8c94406
commit e9ef40a404
4 changed files with 209 additions and 8 deletions

View File

@ -126,6 +126,10 @@ function MapDir (const VirtPath, RealPath: WideString; OverwriteExisting: boolea
(* Calls specified function with a single argument and returns its result. VFS is disabled for current thread during function exection *) (* Calls specified function with a single argument and returns its result. VFS is disabled for current thread during function exection *)
function CallWithoutVfs (Func: TSingleArgExternalFunc; Arg: pointer = nil): integer; stdcall; function CallWithoutVfs (Func: TSingleArgExternalFunc; Arg: pointer = nil): integer; stdcall;
(* Returns text with all applied mappings, separated via #13#10. If ShortenPaths is true, common part
of real and virtual paths is stripped *)
function GetMappingsReport: WideString;
(***) implementation (***) (***) implementation (***)
@ -133,12 +137,13 @@ function CallWithoutVfs (Func: TSingleArgExternalFunc; Arg: pointer = nil): inte
type type
(* Applied and remembered mapping. Used to refresh or report VFS *) (* Applied and remembered mapping. Used to refresh or report VFS *)
TMapping = class TMapping = class
Applied: LONGBOOL;
AbsVirtPath: WideString; AbsVirtPath: WideString;
AbsRealPath: WideString; AbsRealPath: WideString;
OverwriteExisting: boolean; OverwriteExisting: LONGBOOL;
Flags: integer; Flags: integer;
class function Make (const AbsVirtPath, AbsRealPath: WideString; OverwriteExisting: boolean; Flags: integer): TMapping; class function Make (Applied: boolean; const AbsVirtPath, AbsRealPath: WideString; OverwriteExisting: boolean; Flags: integer): TMapping;
end; end;
var var
@ -332,9 +337,10 @@ begin
end; end;
end; // .procedure BuildVfsItemsTree end; // .procedure BuildVfsItemsTree
class function TMapping.Make (const AbsVirtPath, AbsRealPath: WideString; OverwriteExisting: boolean; Flags: integer): {O} TMapping; class function TMapping.Make (Applied: boolean; const AbsVirtPath, AbsRealPath: WideString; OverwriteExisting: boolean; Flags: integer): {O} TMapping;
begin begin
result := TMapping.Create; result := TMapping.Create;
result.Applied := Applied;
result.AbsVirtPath := AbsVirtPath; result.AbsVirtPath := AbsVirtPath;
result.AbsRealPath := AbsRealPath; result.AbsRealPath := AbsRealPath;
result.OverwriteExisting := OverwriteExisting; result.OverwriteExisting := OverwriteExisting;
@ -468,7 +474,8 @@ begin
Dest.EaSize := Src.EaSize; Dest.EaSize := Src.EaSize;
end; end;
(* Redirects single file/directory path (not including directory contents). Target must exist for success *) (* Redirects single file/directory path (not including directory contents). Returns redirected VFS item
for given real path if VFS item was successfully created/overwritten or it already existed and OverwriteExisting = false. *)
function RedirectFile (const AbsVirtPath, AbsRealPath: WideString; {n} FileInfoPtr: WinNative.PFILE_ID_BOTH_DIR_INFORMATION; OverwriteExisting: boolean; Priority: integer): {Un} TVfsItem; function RedirectFile (const AbsVirtPath, AbsRealPath: WideString; {n} FileInfoPtr: WinNative.PFILE_ID_BOTH_DIR_INFORMATION; OverwriteExisting: boolean; Priority: integer): {Un} TVfsItem;
const const
WIDE_NULL_CHAR_LEN = Length(#0); WIDE_NULL_CHAR_LEN = Length(#0);
@ -550,7 +557,7 @@ begin
end; end;
DirVfsItem := RedirectFile(AbsVirtPath, AbsRealPath, FileInfoPtr, OverwriteExisting, Priority); DirVfsItem := RedirectFile(AbsVirtPath, AbsRealPath, FileInfoPtr, OverwriteExisting, Priority);
Success := DirVfsItem <> nil; Success := (DirVfsItem <> nil) and ((DirVfsItem.RealPath = AbsRealPath) or VfsUtils.IsDir(AbsRealPath));
if Success then begin if Success then begin
VirtPathPrefix := AddBackslash(AbsVirtPath); VirtPathPrefix := AddBackslash(AbsVirtPath);
@ -599,8 +606,8 @@ begin
result := (AbsVirtPath <> '') and (AbsRealPath <> ''); result := (AbsVirtPath <> '') and (AbsRealPath <> '');
if result then begin if result then begin
Mappings.Add(TMapping.Make(AbsVirtPath, AbsRealPath, OverwriteExisting, Flags));
result := _MapDir(AbsVirtPath, AbsRealPath, nil, OverwriteExisting, Flags, AUTO_PRIORITY) <> nil; result := _MapDir(AbsVirtPath, AbsRealPath, nil, OverwriteExisting, Flags, AUTO_PRIORITY) <> nil;
Mappings.Add(TMapping.Make(result, AbsVirtPath, AbsRealPath, OverwriteExisting, Flags));
end; end;
LeaveVfsConfig; LeaveVfsConfig;
@ -638,7 +645,7 @@ begin
for i := 0 to Mappings.Count - 1 do begin for i := 0 to Mappings.Count - 1 do begin
with TMapping(Mappings[i]) do begin with TMapping(Mappings[i]) do begin
MapDir(AbsVirtPath, AbsRealPath, OverwriteExisting, Flags); TMapping(Mappings[i]).Applied := MapDir(AbsVirtPath, AbsRealPath, OverwriteExisting, Flags);
end; end;
end; end;
@ -686,6 +693,126 @@ begin
end; // .with end; // .with
end; // .function RefreshMappedFile end; // .function RefreshMappedFile
function GetMappingsReport: WideString;
const
COL_PATHS = 0;
COL_META = 1;
var
{O} Buf: StrLib.TStrBuilder;
{O} Line: StrLib.TStrBuilder;
Cols: array [0..1] of array of WideString;
MaxPathColWidth: integer;
i: integer;
procedure WriteMapping (Mapping: TMapping; LineN: integer);
var
StartPathPos: integer;
MaxCommonPathLen: integer;
ShortestPath: WideString;
LongestPath: WideString;
i: integer;
begin
{!} Assert(Mapping <> nil);
StartPathPos := 1;
if Length(Mapping.AbsRealPath) > Length(Mapping.AbsVirtPath) then begin
LongestPath := Mapping.AbsRealPath;
ShortestPath := Mapping.AbsVirtPath;
end else begin
LongestPath := Mapping.AbsVirtPath;
ShortestPath := Mapping.AbsRealPath;
end;
i := 1;
MaxCommonPathLen := Length(ShortestPath);
while (i <= MaxCommonPathLen) and (ShortestPath[i] = LongestPath[i]) do begin
Inc(i);
end;
// Handle case: [xxx\yyy] zzz and [xxx\yyy]. Common part is [xxx]
if (Length(LongestPath) > MaxCommonPathLen) and (LongestPath[i] <> '\') then begin
while (i >= 2) and (LongestPath[i] <> '\') do begin
Dec(i);
end;
end
// Handle case: D:\App <= D:\Mods. Common part is D:
else if ShortestPath[i] = '\' then begin
Dec(i);
end;
StartPathPos := i;
Line.Clear;
if StartPathPos > 1 then begin
Line.AppendWide('$');
end;
Line.AppendWide(Copy(Mapping.AbsVirtPath, StartPathPos));
Line.AppendWide(' <= ');
if StartPathPos > 1 then begin
Line.AppendWide('$');
end;
Line.AppendWide(Copy(Mapping.AbsRealPath, StartPathPos));
if not Mapping.Applied then begin
Line.AppendWide(' *MISS*');
end;
Cols[COL_PATHS][LineN] := Line.BuildWideStr;
MaxPathColWidth := Max(MaxPathColWidth, Length(Cols[COL_PATHS][LineN]));
Line.Clear;
Line.AppendWide('[Overwrite = ' + IntToStr(ord(Mapping.OverwriteExisting)) + ', Flags = ' + IntToStr(Mapping.Flags));
if StartPathPos > 1 then begin
Line.AppendWide(', $ = "' + Copy(ShortestPath, 1, StartPathPos - 1) + '"]');
end else begin
Line.AppendWide(']');
end;
Cols[COL_META][LineN] := Line.BuildWideStr;
end; // .procedure WriteMapping
function FormatResultTable: WideString;
var
i: integer;
begin
for i := 0 to Mappings.Count - 1 do begin
Buf.AppendWide(Cols[COL_PATHS][i] + StringOfChar(WideChar(' '), MaxPathColWidth - Length(Cols[COL_PATHS][i]) + 1));
Buf.AppendWide(Cols[COL_META][i]);
if i < Mappings.Count - 1 then begin
Buf.AppendWide(#13#10);
end;
end;
result := Buf.BuildWideStr;
end;
begin
Buf := StrLib.TStrBuilder.Create;
Line := StrLib.TStrBuilder.Create;
// * * * * * //
SetLength(Cols[COL_PATHS], Mappings.Count);
SetLength(Cols[COL_META], Mappings.Count);
MaxPathColWidth := 0;
for i := 0 to Mappings.Count - 1 do begin
WriteMapping(TMapping(Mappings[i]), i);
end;
result := FormatResultTable;
// * * * * * //
SysUtils.FreeAndNil(Buf);
SysUtils.FreeAndNil(Line);
end; // .function GetMappingsReport
begin begin
VfsCritSection.Init; VfsCritSection.Init;
VfsItems := DataLib.NewDict(Utils.OWNS_ITEMS, DataLib.CASE_SENSITIVE); VfsItems := DataLib.NewDict(Utils.OWNS_ITEMS, DataLib.CASE_SENSITIVE);

View File

@ -8,6 +8,7 @@ unit VfsExport;
uses uses
Windows, Windows,
Utils,
VfsDebug, VfsBase, VfsControl, VfsWatching; VfsDebug, VfsBase, VfsControl, VfsWatching;
exports exports
@ -48,6 +49,36 @@ begin
result := VfsWatching.RunWatcher(WatchDir, DebounceInterval); result := VfsWatching.RunWatcher(WatchDir, DebounceInterval);
end; end;
(* Frees buffer, that was transfered to client earlier using other VFS API *)
procedure MemFree ({O} Buf: pointer); stdcall;
begin
FreeMem(Buf);
end;
(* Returns text with all applied mappings, separated via #13#10. If ShortenPaths is true, common part
of real and virtual paths is stripped. Call MemFree to release result buffer *)
function GetMappingsReport: {O} PWideChar; stdcall;
var
Res: WideString;
begin
result := nil;
Res := VfsBase.GetMappingsReport;
GetMem(result, (Length(Res) + 1) * sizeof(WideChar));
Utils.CopyMem((Length(Res) + 1) * sizeof(WideChar), PWideChar(Res), result);
end;
function GetMappingsReportA: {O} pchar; stdcall;
var
Res: string;
begin
result := nil;
Res := VfsBase.GetMappingsReport;
GetMem(result, Length(Res) + 1);
Utils.CopyMem(Length(Res) + 1, pchar(Res), result);
end;
procedure ConsoleLoggingProc (Operation, Message: pchar); stdcall; procedure ConsoleLoggingProc (Operation, Message: pchar); stdcall;
begin begin
WriteLn('>> ', string(Operation), ': ', string(Message), #13#10); WriteLn('>> ', string(Operation), ': ', string(Message), #13#10);
@ -88,6 +119,9 @@ exports
MapModsFromList, MapModsFromList,
MapModsFromListA, MapModsFromListA,
RunWatcher, RunWatcher,
GetMappingsReport,
GetMappingsReportA,
MemFree,
InstallConsoleLogger; InstallConsoleLogger;
end. end.

View File

@ -33,9 +33,18 @@ function RunVfs (DirListingOrder: TDirListingSortType): LONGBOOL; stdcall; exter
time in msec to wait after last change before running full VFS rescanning routine *) time in msec to wait after last change before running full VFS rescanning routine *)
function RunWatcher (const WatchDir: PWideChar; DebounceInterval: integer): LONGBOOL; stdcall; external 'vfs.dll'; function RunWatcher (const WatchDir: PWideChar; DebounceInterval: integer): LONGBOOL; stdcall; external 'vfs.dll';
(* Frees buffer, that was transfered to client earlier using other VFS API *)
procedure MemFree ({O} Buf: pointer); stdcall; external 'vfs.dll';
(* Returns text with all applied mappings, separated via #13#10. If ShortenPaths is true, common part
of real and virtual paths is stripped. Call MemFree to release result buffer *)
function GetMappingsReport: {O} PWideChar; stdcall; external 'vfs.dll';
function GetMappingsReportA: {O} pchar; stdcall; external 'vfs.dll';
(* Allocates console and install logger, writing messages to console *) (* Allocates console and install logger, writing messages to console *)
procedure InstallConsoleLogger; stdcall; external 'vfs.dll'; procedure InstallConsoleLogger; stdcall; external 'vfs.dll';
(***) implementation (***) (***) implementation (***)

View File

@ -140,6 +140,12 @@ function StripNtAbsPathPrefix (const Path: WideString): WideString;
(* Saves API result in external variable and returns result as is *) (* Saves API result in external variable and returns result as is *)
function SaveAndRet (Res: integer; out ResCopy): integer; function SaveAndRet (Res: integer; out ResCopy): integer;
(* Returns attributes for file at given path *)
function GetFileAttrs (const Path: WideString; {out} var Attrs: integer): boolean;
(* Returns true if directory with given path exists *)
function IsDir (const Path: WideString): boolean;
(* Opens file/directory using absolute NT path and returns success flag *) (* Opens file/directory using absolute NT path and returns success flag *)
function SysOpenFile (const NtAbsPath: WideString; {OUT} var Res: Windows.THandle; const OpenMode: TSysOpenFileMode = OPEN_AS_ANY; const AccessMode: ACCESS_MASK = FILE_GENERIC_READ): boolean; function SysOpenFile (const NtAbsPath: WideString; {OUT} var Res: Windows.THandle; const OpenMode: TSysOpenFileMode = OPEN_AS_ANY; const AccessMode: ACCESS_MASK = FILE_GENERIC_READ): boolean;
@ -155,6 +161,7 @@ function SysScanDir (const DirPath, Mask: WideString): ISysDirScanner; overload;
tests were run on network drive *) tests were run on network drive *)
procedure GetDirectoryListing (const SearchPath, FileMask: WideString; {Un} Exclude: TDict {OF CaselessKey => not NIL}; DirListing: TDirListing); procedure GetDirectoryListing (const SearchPath, FileMask: WideString; {Un} Exclude: TDict {OF CaselessKey => not NIL}; DirListing: TDirListing);
(***) implementation (***) (***) implementation (***)
@ -478,6 +485,30 @@ begin
result := StrLib.Join(FileNames, #13#10); result := StrLib.Join(FileNames, #13#10);
end; end;
function GetFileAttrs (const Path: WideString; {out} var Attrs: integer): boolean;
const
INVALID_FILE_ATTRIBUTES = -1;
var
Res: integer;
begin
Res := integer(Windows.GetFileAttributesW(PWideChar(Path)));
result := Res <> INVALID_FILE_ATTRIBUTES;
if result then begin
Attrs := Res;
end;
end;
function IsDir (const Path: WideString): boolean;
var
FileAttrs: integer;
begin
result := GetFileAttrs(Path, FileAttrs) and Utils.HasFlag(Windows.FILE_ATTRIBUTE_DIRECTORY, FileAttrs);
end;
function SysOpenFile (const NtAbsPath: WideString; {OUT} var Res: Windows.THandle; const OpenMode: TSysOpenFileMode = OPEN_AS_ANY; const AccessMode: ACCESS_MASK = FILE_GENERIC_READ): boolean; function SysOpenFile (const NtAbsPath: WideString; {OUT} var Res: Windows.THandle; const OpenMode: TSysOpenFileMode = OPEN_AS_ANY; const AccessMode: ACCESS_MASK = FILE_GENERIC_READ): boolean;
var var
FilePathU: WinNative.UNICODE_STRING; FilePathU: WinNative.UNICODE_STRING;
@ -521,7 +552,7 @@ begin
if IsNtRootDriveAbsPath(NtAbsPath) then begin if IsNtRootDriveAbsPath(NtAbsPath) then begin
// Return fake info for root drive // Return fake info for root drive
result := SaveAndRet(Windows.GetFileAttributesW(PWideChar(StripNtAbsPathPrefix(NtAbsPath))), FileAllInfo.BasicInformation.FileAttributes) <> integer(Windows.INVALID_HANDLE_VALUE); result := GetFileAttrs(StripNtAbsPathPrefix(NtAbsPath), integer(FileAllInfo.BasicInformation.FileAttributes));
if result then begin if result then begin
FillChar(Res.Base, sizeof(Res.Base), 0); FillChar(Res.Base, sizeof(Res.Base), 0);