{*******************************************************} { } { Tocsg.Disk } { } { Copyright (C) 2022 kkuzil } { } {*******************************************************} unit Tocsg.Disk; interface uses System.SysUtils, Winapi.Windows, System.Generics.Collections; const DISKPART_FMT_SELECT = 'select disk %d'; DISKPART_SET_ATTRIBUTES_READONLY = 'attributes disk set readonly'; DISKPART_DEL_ATTRIBUTES_READONLY = 'attributes disk clear readonly'; DISKPART_OFFLINE_DISK = 'offline disk'; DISKPART_ONLINE_DISK = 'online disk'; type PDriveExtent = ^TDriveExtent; TDriveExtent = record dwDiskNumber : DWORD; liStartingOffset : LARGE_INTEGER; liExtentLength : LARGE_INTEGER; end; PDiskExtents = ^TDiskExtents; TDiskExtents = record dwNumberOfDriveExtents: DWORD; Extents: array [0..0] of TDriveExtent; end; PDriveExtentEntry = ^TDriveExtentEntry; TDriveExtentEntry = record wDiskNum: WORD; sDrive: String; ullStartSector, ullTotalSectors: ULONGLONG; end; TDriveEntentList = TList; TTgDriveExtent = class(TObject) private lstEntry_: TDriveEntentList; procedure OnEntryNotify(Sender: TObject; const Item: PDriveExtentEntry; Action: TCollectionNotification); function GetCount: Integer; function GetItem(nIndex: Integer): PDriveExtentEntry; public Constructor Create; Destructor Destroy; override; procedure RefreshDriveExtent; property Count: Integer read GetCount; property Items[nIndex: Integer]: PDriveExtentEntry read GetItem; end; PDevPathLetter = ^TDevPathLetter; TDevPathLetter = record cLetter: Char; sDevicePath: String; end; TDevPathLetterList = class(TList) protected procedure Notify(const Item: PDevPathLetter; Action: TCollectionNotification); override; end; function GetDriveFromMask(dwMask: DWORD): String; function GetDrivesFromMask(dwMask: DWORD; bLetterOnly: Boolean = false; bFixedOnly: Boolean = false): String; function GetDrivesDevPathLetterList(aList: TDevPathLetterList): Integer; function GetDrivesVolNumLetterList(aList: TDevPathLetterList): Integer; function GetDriveExtent(const sDrive: String): TDriveExtent; function GetDriveSize(const sDrive: String): ULONGLONG; function GetDriveFree(const sDrive: String): ULONGLONG; function GetVolumeSerial(const sDrive: String): DWORD; function GetVolumeName(const sDrive: String): String; function GetVolumeFilesystem(const sDrive: String): String; function GetDriveTypeToStr(nType: Integer): String; function IsReadOnlyByWriteProbe(const sDrive: String): Integer; procedure SetDriveReadOnly(sDrive: String; nDiskNum: Integer; bVal: Boolean); implementation uses Tocsg.Strings, Tocsg.Exception, System.Classes, Tocsg.Path, Tocsg.Safe, Tocsg.Process, Tocsg.Files; function GetDriveFromMask(dwMask: DWORD): String; var wLetter: Word; begin wLetter := Ord('A'); while (dwMask and 1) = 0 do begin dwMask := dwMask shr 1; Inc(wLetter); end; Result := Format('%s:\', [Char(wLetter)]); end; function GetDrivesFromMask(dwMask: DWORD; bLetterOnly: Boolean = false; bFixedOnly: Boolean = false): String; var ucDrive: Byte; sDrive: String; begin Result := ''; for ucDrive := 0 to 31 do if (dwMask and (1 shl ucDrive)) > 0 then if bLetterOnly then begin if bFixedOnly then begin sDrive := Format('%s:\', [Char(ucDrive + 65)]); if (GetDriveType(PChar(sDrive)) <> DRIVE_FIXED) then continue; end; SumString(Result, Char(ucDrive + 65), ','); end else begin sDrive := Format('%s:\', [Char(ucDrive + 65)]); if bFixedOnly and (GetDriveType(PChar(sDrive)) <> DRIVE_FIXED) then continue; SumString(Result, sDrive, ','); end; end; function GetDrivesDevPathLetterList(aList: TDevPathLetterList): Integer; var dwMask: DWORD; ucDrive: Byte; cDrive: Char; pEnt: PDevPathLetter; arrPath: array [0..MAX_PATH] of Char; begin try Result := 0; dwMask := GetLogicalDrives; for ucDrive := 0 to 31 do if (dwMask and (1 shl ucDrive)) > 0 then begin cDrive := Char(ucDrive + 65); if GetDriveExtent(cDrive).liExtentLength.QuadPart <> 0 then begin ZeroMemory(@arrPath, SizeOf(arrPath)); QueryDosDevice(PChar(cDrive + ':'), @arrPath, MAX_PATH); if arrPath <> '' then begin New(pEnt); pEnt.cLetter := cDrive; pEnt.sDevicePath := arrPath; aList.Add(pEnt); Inc(Result); end; end; end; except on E: Exception do ETgException.TraceException(E, 'Fail .. GetDrivesDevPathLetterList()'); end; end; function GetDrivesVolNumLetterList(aList: TDevPathLetterList): Integer; var dwMask: DWORD; ucDrive: Byte; cDrive: Char; pEnt: PDevPathLetter; sVolNum: String; begin Result := 0; dwMask := GetLogicalDrives; for ucDrive := 0 to 31 do if (dwMask and (1 shl ucDrive)) > 0 then begin cDrive := Char(ucDrive + 65); if GetDriveExtent(cDrive).liExtentLength.QuadPart <> 0 then begin sVolNum := IntToHex(GetVolumeSerial(cDrive + ':\'), 3); if sVolNum <> '' then begin New(pEnt); pEnt.cLetter := cDrive; pEnt.sDevicePath := sVolNum; aList.Add(pEnt); Inc(Result); end; end; end; end; function GetDriveExtent(const sDrive: String): TDriveExtent; var hVolume: THandle; DiskExtents: PDiskExtents; dwOutBytes: DWORD; sDrivePath: String; begin ZeroMemory(@Result, SizeOf(Result)); Result.dwDiskNumber := 999; sDrivePath := Format('\\.\%s:', [sDrive[1]]); hVolume := CreateFile(PChar(sDrivePath), 0,//GENERIC_READ or GENERIC_WRITE, // GENERIC_READ 이걸 넣어주면 딜레이가 많이 생긴다.. 15_0202 16:26:50 sunk FILE_SHARE_READ,// or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0); // hVolume := CreateFile(PChar(sDrivePath), // GENERIC_READ, // or GENERIC_WRITE, // FILE_SHARE_READ,// or FILE_SHARE_WRITE, // nil, // OPEN_EXISTING, // FILE_FLAG_NO_BUFFERING or FILE_FLAG_SEQUENTIAL_SCAN, // 0); if hVolume < 1 then exit; DiskExtents := AllocMem(MAX_PATH); try if DeviceIoControl(hVolume, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, nil, 0, DiskExtents, MAX_PATH, dwOutBytes, nil) then begin if DiskExtents.dwNumberOfDriveExtents > 0 then Result := DiskExtents.Extents[0]; end; // else // imdisk로 마운트한 드라이브 정보를 가져오기 위해서 아래처럼 시도 하지만.. 정보를 제대로 가져오지 못하는 상황이다 15_0202 17:45:02 sunk // if DeviceIoControl(hVolume, // IOCTL_DISK_GET_DRIVE_GEOMETRY, // nil, // 0, // @DriveExtent, // SizeOf(DriveExtent), // dwOutBytes, // nil) then // begin // Result := DriveExtent; // end; // todo : IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS 이걸로 안되는거 IOCTL_DISK_GET_DRIVE_GEOMETRY_EX 이걸로 한번더 확인해 보기 finally Finalize(DiskExtents); FreeMem(DiskExtents, Max_Path); CloseHandle(hVolume); end; end; function GetDriveSize(const sDrive: String): ULONGLONG; var bResult: Boolean; llAvail, llTotal, llFree: TLargeInteger; begin Result := 0; // 윈도우 xp에서 빈디스크 에러 메시지 걸러내기 2012-01-25 sunk // 보안, 가상 드라이브의 경우 IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS가 항상 실패 25_0423 08:45:20 kku // if GetDriveExtent(sDrive[1]).liExtentLength.QuadPart = 0 then // exit; bResult := GetDiskFreeSpaceEx(PChar(sDrive), llAvail, llTotal, @llFree); if bResult = true then Result := llTotal; end; function GetDriveFree(const sDrive: String): ULONGLONG; var FSectorsPerCluster, FBytesPerSector, FNumberOfFreeClusters, FTotalNumberOfClusters: DWORD; begin Result := 0; if sDrive = '' then exit; if GetDriveExtent(sDrive[1]).liExtentLength.QuadPart = 0 then exit; FSectorsPerCluster := 0; FBytesPerSector := 0; FNumberOfFreeClusters := 0; FTotalNumberOfClusters := 0; // GetDiskFreeSpace() API 호출 GetDiskFreeSpace( PChar(sDrive), FSectorsPerCluster, FBytesPerSector, FNumberOfFreeClusters, FTotalNumberOfClusters ); Result := ULONGLONG(FBytesPerSector) * FSectorsPerCluster * FNumberOfFreeClusters; end; function GetVolumeSerial(const sDrive: String): DWORD; var sVolumeName: array [0..255] of Char; dwMaxComponentLen: DWORD; sFileSystemName: array[0..255] of Char; dwFileSystemFlag: DWORD; bResult: Boolean; // ErrCode: DWORD; begin Result := 0; bResult := GetVolumeInformation(PWideChar(sDrive), sVolumeName, 256, @Result, dwMaxComponentLen, dwFileSystemFlag, sFileSystemName, 256); if not bResult then begin // ErrCode := GetLastError; // OutputDebugString(PChar('GetVolumeInformation Fail!! Error Code : ' + IntToStr(ErrCode))); end; end; function GetVolumeName(const sDrive: String): String; var sVolumeName: array [0..255] of Char; dwVolumeSerial: DWORD; dwMaxComponentLen: DWORD; sFileSystemName: array[0..255] of Char; dwFileSystemFlag: DWORD; begin Result := ''; if GetVolumeInformation(PChar(sDrive), sVolumeName, 256, @dwVolumeSerial, dwMaxComponentLen, dwFileSystemFlag, sFileSystemName, 256) then Result := sVolumeName; end; function GetVolumeFilesystem(const sDrive: String): String; var sVolumeName: array [0..255] of Char; dwVolumeSerial: DWORD; dwMaxComponentLen: DWORD; sFileSystemName: array[0..255] of Char; dwFileSystemFlag: DWORD; begin Result := ''; if GetVolumeInformation(PChar(sDrive), sVolumeName, 256, @dwVolumeSerial, dwMaxComponentLen, dwFileSystemFlag, sFileSystemName, 256) then Result := sFileSystemName; end; function GetDriveTypeToStr(nType: Integer): String; begin case nType of DRIVE_FIXED : Result := 'FIXED'; DRIVE_CDROM : Result := 'CDROM'; DRIVE_REMOVABLE : Result := 'REMOVABLE'; else Result := 'Unknown'; end; end; // 0 : 쓰기 가능, 1 : 읽기 전용, 2 : 권한 없음, 3 : 공간 부족 function IsReadOnlyByWriteProbe(const sDrive: String): Integer; var sTmpPath: string; h: THandle; begin Result := 0; // 루트에 직접 파일 쓰기 시도 (권한 문제를 피하려면 하위 폴더를 탐색해서 시도해도 됨) sTmpPath := IncludeTrailingPathDelimiter(sDrive) + '_wr_probe_.tmp'; if FileExists(sTmpPath) then DeleteFile(PChar(sTmpPath)); h := CreateFile(PChar(sTmpPath), GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY or FILE_FLAG_DELETE_ON_CLOSE, 0); if h = INVALID_HANDLE_VALUE then begin case GetLastError of ERROR_WRITE_PROTECT : Result := 1; ERROR_ACCESS_DENIED : Result := 2; ERROR_DISK_FULL : Result := 3; end end else CloseHandle(h); end; // ThdUsbMon.pas 에서 SetReadOnly() 을 가져옴 25_1015 16:21:30 kku procedure SetDriveReadOnly(sDrive: String; nDiskNum: Integer; bVal: Boolean); var StrList: TStringList; sTemp, sDrvPath, sScptPath: String; nTO, nRst: Integer; fs: TFileStream; begin if sDrive = '' then exit; if nDiskNum <= 0 then exit; sScptPath := GetRunExePathDir + Format('$d-scrpt=%d.txt', [GetTickCount]); Guard(StrList, TStringList.Create); StrList.Add(Format(DISKPART_FMT_SELECT, [nDiskNum])); if bVal then StrList.Add(DISKPART_SET_ATTRIBUTES_READONLY) else StrList.Add(DISKPART_DEL_ATTRIBUTES_READONLY); StrList.SaveToFile(sScptPath, TEncoding.ANSI); ExecuteAppWaitUntilTerminate('diskpart.exe', Format('/s "%s"', [sScptPath]), SW_HIDE, 10000); DeleteFile_wait(PChar(sScptPath), 2); end; { TTgDriveExtent } Constructor TTgDriveExtent.Create; begin Inherited Create; lstEntry_ := TDriveEntentList.Create; lstEntry_.OnNotify := OnEntryNotify; RefreshDriveExtent; end; Destructor TTgDriveExtent.Destroy; begin FreeAndNil(lstEntry_); Inherited; end; procedure TTgDriveExtent.OnEntryNotify(Sender: TObject; const Item: PDriveExtentEntry; Action: TCollectionNotification); begin case Action of cnAdded: ; cnRemoved: Dispose(Item); cnExtracted: ; end; end; function TTgDriveExtent.GetCount: Integer; begin Result := lstEntry_.Count; end; function TTgDriveExtent.GetItem(nIndex: Integer): PDriveExtentEntry; begin Result := nil; if (nIndex > -1) and (nIndex < lstEntry_.Count) then Result := lstEntry_[nIndex]; end; procedure TTgDriveExtent.RefreshDriveExtent; var dwDriveMask: DWORD; ucDrive: Byte; sDrive: String; Extent: TDriveExtent; pEntry: PDriveExtentEntry; begin lstEntry_.Clear; dwDriveMask := GetLogicalDrives; for ucDrive := 0 to 31 do if (dwDriveMask and (1 shl ucDrive)) > 0 then begin sDrive := Format('%s:\', [Char(ucDrive + 65)]); Extent := GetDriveExtent(sDrive); if Extent.liExtentLength.QuadPart > 0 then begin case GetDriveType(PChar(sDrive)) of DRIVE_UNKNOWN, DRIVE_REMOTE, DRIVE_CDROM: continue; end; New(pEntry); ZeroMemory(pEntry, SizeOf(TDriveExtentEntry)); pEntry.sDrive := sDrive; with Extent do begin pEntry.wDiskNum := dwDiskNumber; pEntry.ullStartSector := liStartingOffset.QuadPart div 512; pEntry.ullTotalSectors := liExtentLength.QuadPart div 512; end; lstEntry_.Add(pEntry); end; end; end; { TDevPathLetterList } procedure TDevPathLetterList.Notify(const Item: PDevPathLetter; Action: TCollectionNotification); begin case Action of cnAdded: ; cnRemoved: Dispose(Item); cnExtracted: ; end; end; end.