{
   Double commander
   -------------------------------------------------------------------------
   This unit contains Windows specific functions

   Copyright (C) 2015-2019 Alexander Koblov (alexx2000@mail.ru)

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
}

unit DCWindows;

{$mode objfpc}{$H+}

interface

uses
  Windows;

{en
   Converts file name in UTF-8 encoding to file name
   with UTF-16 encoding with extended-length path prefix
}
function UTF16LongName(const FileName: String): UnicodeString;

{en
   Enable a privilege
   @param(hToken Access token handle)
   @param(lpszPrivilege Name of privilege to enable)
   @returns(The function returns @true if successful, @false otherwise)
}
function EnablePrivilege(hToken: HANDLE; lpszPrivilege: LPCTSTR): Boolean;
{en
   Copy permissions specific to the NTFS file system,
   like read and write permissions, and the file owner
}
function CopyNtfsPermissions(const Source, Target: String): Boolean;
{en
   Copy extended attributes
   specific to the NTFS file system, like FILE_ATTRIBUTE_COMPRESSED
}
function mbFileCopyXattr(const Source, Target: String): Boolean;
{en
   Retrieves the final path for the specified file
}
function GetFinalPathNameByHandle(hFile: THandle): UnicodeString;
{en
   Retrieves the file system type name
}
function GetFileSystemType(const Path: String): UnicodeString;

implementation

uses
  SysUtils, JwaAclApi, JwaWinNT, JwaAccCtrl, JwaWinBase, JwaWinType, JwaNative,
  JwaNtStatus, DCConvertEncoding;

var
  GetFinalPathNameByHandleW: function(hFile: HANDLE; lpszFilePath: LPWSTR; cchFilePath: DWORD; dwFlags: DWORD): DWORD; stdcall;
  NtQueryObject: function(ObjectHandle : HANDLE; ObjectInformationClass : OBJECT_INFORMATION_CLASS; ObjectInformation : PVOID; ObjectInformationLength : ULONG; ReturnLength : PULONG): NTSTATUS; stdcall;

function UTF16LongName(const FileName: String): UnicodeString;
var
  Temp: PWideChar;
begin
  if Pos('\\', FileName) = 0 then
    Result := '\\?\' + CeUtf8ToUtf16(FileName)
  else begin
    Result := '\\?\UNC\' + CeUtf8ToUtf16(Copy(FileName, 3, MaxInt));
  end;
  Temp := Pointer(Result) + 4;
  while Temp^ <> #0 do
  begin
    if Temp^ = '/' then Temp^:= '\';
    Inc(Temp);
  end;
  if ((Temp - 1)^ = DriveSeparator) then Result:= Result + '\';
end;

function EnablePrivilege(hToken: HANDLE; lpszPrivilege: LPCTSTR): Boolean;
var
  tp: TTokenPrivileges;
  luid: TLuid = (LowPart: 0; HighPart: 0);
begin
  if (not LookupPrivilegeValue(nil, lpszPrivilege, luid)) then
    Exit(False);

  tp.PrivilegeCount:= 1;
  tp.Privileges[0].Luid:= luid;
  tp.Privileges[0].Attributes:= SE_PRIVILEGE_ENABLED;

  // Enable privilege in the specified access token
  if (not AdjustTokenPrivileges(hToken, False, @tp, SizeOf(TTokenPrivileges), nil, nil)) then
    Exit(False);

  // Not all privileges or groups referenced are assigned to the caller
  Result:= not (GetLastError() = ERROR_NOT_ALL_ASSIGNED);
end;

function CopyNtfsPermissions(const Source, Target: String): Boolean;
const
  DisabledPrivilege: Boolean = True;
var
  Dacl, Sacl: PACL;
  lpdwRevision: DWORD = 0;
  ProcessToken: HANDLE = 0;
  SidOwner, SidGroup: PSID;
  SecDescPtr: PSECURITY_DESCRIPTOR = nil;
  SecDescCtl: SECURITY_DESCRIPTOR_CONTROL = 0;
  SecurityInfo: SECURITY_INFORMATION = DACL_SECURITY_INFORMATION or SACL_SECURITY_INFORMATION or
                                       OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION;
begin
  if DisabledPrivilege then
  begin
    DisabledPrivilege:= False;
    Result:= OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, ProcessToken);
    if not Result then
      Exit(False)
    else begin
      EnablePrivilege(ProcessToken, SE_BACKUP_NAME);
      EnablePrivilege(ProcessToken, SE_RESTORE_NAME);
      EnablePrivilege(ProcessToken, SE_SECURITY_NAME);
      CloseHandle(ProcessToken);
    end;
  end;
  Result:= GetNamedSecurityInfoW(PWideChar(CeUtf8ToUtf16(Source)), SE_FILE_OBJECT, SecurityInfo,
             @SidOwner, @SidGroup, @Dacl, @Sacl, SecDescPtr) = ERROR_SUCCESS;
  if Result then
  begin
    if GetSecurityDescriptorControl(SecDescPtr, SecDescCtl, lpdwRevision) then
    begin
      // Need to copy DACL inheritance
      if (SecDescCtl and SE_DACL_PROTECTED <> 0) then
        SecurityInfo:= SecurityInfo or PROTECTED_DACL_SECURITY_INFORMATION
      else begin
        SecurityInfo:= SecurityInfo or UNPROTECTED_DACL_SECURITY_INFORMATION;
      end;
      // Need to copy SACL inheritance
      if (SecDescCtl and SE_SACL_PROTECTED <> 0) then
        SecurityInfo:= SecurityInfo or PROTECTED_SACL_SECURITY_INFORMATION
      else begin
        SecurityInfo:= SecurityInfo or UNPROTECTED_SACL_SECURITY_INFORMATION;
      end;
      Result:= SetNamedSecurityInfoW(PWideChar(CeUtf8ToUtf16(Target)), SE_FILE_OBJECT,
                 SecurityInfo, SidOwner, SidGroup, Dacl, Sacl) = ERROR_SUCCESS;
    end;
    {$PUSH}{$HINTS OFF}{$WARNINGS OFF}
    LocalFree(HLOCAL(SecDescPtr));
    {$POP}
  end;
end;

function mbFileCopyXattr(const Source, Target: String): Boolean;
const
  FSCTL_SET_COMPRESSION = $9C040;
  COMPRESSION_FORMAT_DEFAULT = 1;
var
  dwFlags: DWORD;
  Handle: THandle;
  LastError: DWORD;
  BytesReturned: DWORD;
  dwFileAttributes: DWORD;
  Format: UInt16 = COMPRESSION_FORMAT_DEFAULT;
  lpszVolumePathName: array[0..maxSmallint] of WideChar;
begin
  Result:= True;
  dwFileAttributes:= GetFileAttributesW(PWideChar(UTF16LongName(Source)));
  if (dwFileAttributes and FILE_ATTRIBUTE_COMPRESSED <> 0) then
  begin
    if (dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0) then
      dwFlags:= FILE_FLAG_BACKUP_SEMANTICS
    else begin
      dwFlags:= 0;
    end;
    dwFileAttributes:= GetFileAttributesW(PWideChar(UTF16LongName(Target)));

    if (dwFileAttributes and FILE_ATTRIBUTE_COMPRESSED <> 0) or
       (dwFileAttributes and FILE_ATTRIBUTE_ENCRYPTED <> 0) then
       Exit;

    if GetVolumePathNameW(PWideChar(UTF16LongName(Target)), PWideChar(lpszVolumePathName), maxSmallint) then
    begin
      if GetVolumeInformationW(lpszVolumePathName, nil, 0, nil, LastError, dwFileAttributes, nil, 0) then
      begin
        if (dwFileAttributes and FILE_FILE_COMPRESSION = 0) then Exit;
      end;
    end;

    Handle:= CreateFileW(PWideChar(UTF16LongName(Target)), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, dwFlags, 0);
    if Handle <> INVALID_HANDLE_VALUE then
    begin
      Result:= DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, @Format, SizeOf(Format), nil, 0, @BytesReturned, nil);
      if not Result then LastError:= GetLastError;
      CloseHandle(Handle);
    end;
  end;
  if not Result then SetLastError(LastError);
end;

function GetFinalPathNameByHandle(hFile: THandle): UnicodeString;
const
  VOLUME_NAME_NT = $02;
  MAX_SIZE = SizeOf(TObjectNameInformation) + MAXWORD;
var
  ReturnLength : ULONG;
  ObjectInformation : PObjectNameInformation;
begin
  if (Win32MajorVersion > 5) then
  begin
    SetLength(Result, maxSmallint + 1);
    SetLength(Result, GetFinalPathNameByHandleW(hFile, PWideChar(Result), maxSmallint, VOLUME_NAME_NT));
  end
  else begin
    ObjectInformation:= GetMem(MAX_SIZE);
    if (NtQueryObject(hFile, ObjectNameInformation, ObjectInformation, MAXWORD, @ReturnLength) <> STATUS_SUCCESS) then
      Result:= EmptyWideStr
    else begin
      SetLength(Result, ObjectInformation^.Name.Length div SizeOf(WideChar));
      Move(ObjectInformation^.Name.Buffer^, Result[1], ObjectInformation^.Name.Length);
    end;
    FreeMem(ObjectInformation);
  end;
end;

function GetFileSystemType(const Path: String): UnicodeString;
var
 lpFileSystemFlags: DWORD = 0;
 lpMaximumComponentLength: DWORD = 0;
 lpFileSystemNameBuffer: array [Byte] of WideChar;
begin
  if GetVolumeInformationW(PWideChar(CeUtf8ToUtf16(ExtractFileDrive(Path)) + PathDelim),
                           nil, 0, nil, lpMaximumComponentLength, lpFileSystemFlags,
                           lpFileSystemNameBuffer, SizeOf(lpFileSystemNameBuffer)) then
    Result:= lpFileSystemNameBuffer
  else
    Result:= EmptyWideStr;
end;

procedure Initialize;
begin
  if Win32MajorVersion < 6 then
    Pointer(NtQueryObject):= GetProcAddress(GetModuleHandleW(ntdll), 'NtQueryObject')
  else begin
    Pointer(GetFinalPathNameByHandleW):= GetProcAddress(GetModuleHandleW(kernel32), 'GetFinalPathNameByHandleW');
  end;
end;

initialization
  Initialize;

end.

