Naraeon SSD Tools internals – 2. Objects pointing path

Necessity

Naraeon SSD Tools has various objects those need path. From TPhysicalDrive as the name shows, a lot of Getter objects need the destination drive path. Other than that, some objects require path of a partition. In those cases, it is reasonable to memorize the path and use many times.

Someone would think this is unnecessary, but in this project this pattern is widely used. So it is not a option to understand this subject.

Because of the length, I divided this into two parts. PhysicalDrive part would be next one.

Basic form: TOSFile

The basic form is TOSFile. (Without verifying) memorizes a path, and changing OS error code into exception object. It is easy to get a bug by forgetting to write GetLastError checking code. But this exception-based approach just needs writing ‘IfOSErrorRaiseException’ after each API call. Next code block shows the implementation of it.

function TOSFile.IsLastSystemCallSucceed: Boolean;
begin
  result :=
    GetLastError = ERROR_SUCCESS;
end;

function TOSFile.GetOSErrorString(const OSErrorCode: Integer): String;
begin
  result :=
    'OS Error: ' +
      SysErrorMessage(OSErrorCode) + ' (' + IntToStr(OSErrorCode) + ')';
end;

procedure TOSFile.IfOSErrorRaiseException;
var
  OSErrorException: EOSError;
begin
  if not IsLastSystemCallSucceed then
  begin
    OSErrorException := EOSError.Create(GetOSErrorString(GetLastError));
    OSErrorException.ErrorCode := GetLastError;
    raise OSErrorException;
  end;
end;

Many functions need the path without prefix. For example, ‘7’ from ‘\\.\PhysicalDrive7’, and ‘C:’ from ‘\\.\C:’. The GetPathOfFileAccessingWithoutPrefix function does it.

function TOSFile.DeletePrefix(const PrefixToDelete: String): String;
var
  PathToDeletePrefix: String;
begin
  PathToDeletePrefix := GetPathOfFileAccessing;
  result :=
    Copy(PathToDeletePrefix, Length(PrefixToDelete) + 1,
      Length(PathToDeletePrefix) - Length(PrefixToDelete));
end;

function TOSFile.IsPathOfFileAccessingHavePrefix(
  const PrefixToCheck: String): Boolean;
begin
  result :=
    Copy(GetPathOfFileAccessing, 0, Length(PrefixToCheck)) = PrefixToCheck;
end;

function TOSFile.GetPathOfFileAccessingWithoutPrefix: String;
begin
  if IsPathOfFileAccessingHavePrefix(
    ThisComputerPrefix + PhysicalDrivePrefix) then
      exit(DeletePrefix(ThisComputerPrefix + PhysicalDrivePrefix))
  else if IsPathOfFileAccessingHavePrefix(ThisComputerPrefix) then
    exit(DeletePrefix(ThisComputerPrefix))
  else
    exit(GetPathOfFileAccessing);
end;

Windows path is not case sensitive. So internally Naraeon SSD Tools converts all the path into upper case path. Path comparing can be done with IsPathEqual function.

function TOSFile.IsPathEqual(const PathToCompare: String): Boolean;
begin
  result := GetPathOfFileAccessing = UpperCase(PathToCompare);
end;

constructor TOSFile.Create(const FileToGetAccess: String);
begin
  inherited Create;
  PathOfFileAccessing := UpperCase(FileToGetAccess);
end;

When handle is needed: TOSFileWithHandle

Sometimes APIs require handles, not path. Then we can use TOSFileWithHandle. You can override GetMinimumPrivilege function and the handle would have the right permission. And also, Security descriptor would be set for each handles. This would improve security of the handles.

procedure TOSFileWithHandle.CreateHandle(const FileToGetAccess: String;
  const DesiredAccess: TCreateFileDesiredAccess);
begin
  if FileHandle <> nil then
    raise EInvalidOp.Create('Invalid Operation: Don''t create handle twice');
  inherited Create(FileToGetAccess);
  FileHandle := TFileHandle.Create(FileToGetAccess, DesiredAccess);
end;

Most of the things are done in TFileHandle object. And there is a function named ‘Unlock’. This is used when there are some storage that is both removable storage and SAT(SCSI) storage, like Z80. With the function, the handles would be closed during the unlock object is referred from anywhere. After the unlock object is destroyed, the handles would be created again and set.

constructor TOSFileUnlock.Create(const Handle: PTHandle; const Path: String;
  const AccessPrevilege: TCreateFileDesiredAccess);
begin
  inherited Create();
  FileHandle := Handle;
  if IsHandleValid(FileHandle^) then
    CloseHandle(FileHandle^);
  self.Path := Path;
  self.AccessPrivilege := AccessPrevilege;
end;

destructor TOSFileUnlock.Destroy;
begin
  FileHandle^ := CreateHandle(Path, AccessPrivilege);
  inherited;
end;

When ioctl is needed: TIoControlFile

ioctl is used frequently in this app. So it is managed separately. And by using metaclass, I made the code to double-check buffer types. Because I made several mistakes with the types and size of them.

function TIoControlFile.BuildOSBufferBy<InputType, OutputType>(
  var InputBuffer: InputType; var OutputBuffer: OutputType): TIoControlIOBuffer;
begin
  result.InputBuffer.Size := SizeOf(InputBuffer);
  result.InputBuffer.Buffer := @InputBuffer;
  result.OutputBuffer.Size := SizeOf(OutputBuffer);
  result.OutputBuffer.Buffer := @OutputBuffer;
end;

function TIoControlFile.TDeviceIoControlCodeToOSControlCode(
  ControlCode: TIoControlCode): Integer;
const
  IOCTL_SCSI_BASE = FILE_DEVICE_CONTROLLER;
  IOCTL_STORAGE_BASE = $2D;
  IOCTL_ATA_PASS_THROUGH =
    (IOCTL_SCSI_BASE shl 16) or
    ((FILE_READ_ACCESS or FILE_WRITE_ACCESS) shl 14) or ($040B shl 2) or
    (METHOD_BUFFERED);
  IOCTL_ATA_PASS_THROUGH_DIRECT = $4D030;
  IOCTL_SCSI_PASS_THROUGH =
    (IOCTL_SCSI_BASE shl 16) or
    ((FILE_READ_ACCESS or FILE_WRITE_ACCESS) shl 14) or ($0401 shl 2) or
    (METHOD_BUFFERED);
  IOCTL_STORAGE_PROTOCOL_COMMAND =
    (IOCTL_SCSI_BASE shl 16) or
    ((FILE_READ_ACCESS or FILE_WRITE_ACCESS) shl 14) or ($04F0 shl 2) or
    (METHOD_BUFFERED);
  IOCTL_SCSI_MINIPORT =
    (IOCTL_SCSI_BASE shl 16) or
    ((FILE_READ_ACCESS or FILE_WRITE_ACCESS) shl 14) or ($0402 shl 2) or
    (METHOD_BUFFERED);
  IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES =
    (IOCTL_STORAGE_BASE shl 16) or
    (FILE_WRITE_ACCESS shl 14) or ($0501 shl 2) or
    (METHOD_BUFFERED);
  IOCTL_SCSI_GET_ADDRESS =
    (IOCTL_SCSI_BASE shl 16) or
    (FILE_ANY_ACCESS shl 14) or ($0406 shl 2) or
    (METHOD_BUFFERED);

  OSControlCodeOfIoControlCode: Array[TIoControlCode] of Integer =
    (IOCTL_ATA_PASS_THROUGH,
     IOCTL_ATA_PASS_THROUGH_DIRECT,
     IOCTL_SCSI_PASS_THROUGH,
     IOCTL_STORAGE_PROTOCOL_COMMAND,
     IOCTL_STORAGE_QUERY_PROPERTY,
     IOCTL_STORAGE_CHECK_VERIFY,
     FSCTL_GET_VOLUME_BITMAP,
     IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
     FSCTL_GET_NTFS_VOLUME_DATA,
     IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
     IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES,
     IOCTL_SCSI_MINIPORT,
     IOCTL_SCSI_GET_ADDRESS,
     FSCTL_LOCK_VOLUME,
     FSCTL_UNLOCK_VOLUME,
     IOCTL_STORAGE_LOAD_MEDIA,
     0);
begin
  if (ControlCode = TIoControlCode.Unknown) or (IsOutOfRange(ControlCode)) then
    raise EInvalidIoControlCode.Create
      ('InvalidIoControlCode: There''s no such IoControlCode - ' +
       IntToStr(Cardinal(ControlCode)));
  exit(OSControlCodeOfIoControlCode[ControlCode]);
end;

TL;DR

Naraeon SSD Tools’ objects need path. And repeated portions are defined as following classes.

  1. TOSFile: Path
  2. TOSFileWithHandle: Path + Handle
  3. TIoControlFile: Path + Handle + Ioctl

Leave a Reply

Your email address will not be published. Required fields are marked *