/* * CiderPress * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. * See the file LICENSE for distribution terms. */ /* * Allow the user to select a disk volume. */ #include "stdafx.h" #include "OpenVolumeDialog.h" #include "Main.h" #include "../diskimg/Win32Extra.h" // need disk geometry calls #include "../diskimg/ASPI.h" BEGIN_MESSAGE_MAP(OpenVolumeDialog, CDialog) ON_COMMAND(IDHELP, OnHelp) //ON_NOTIFY(NM_CLICK, IDC_VOLUME_LIST, OnListClick) ON_NOTIFY(LVN_ITEMCHANGED, IDC_VOLUME_LIST, OnListChange) ON_NOTIFY(NM_DBLCLK, IDC_VOLUME_LIST, OnListDblClick) ON_CBN_SELCHANGE(IDC_VOLUME_FILTER, OnVolumeFilterSelChange) END_MESSAGE_MAP() BOOL OpenVolumeDialog::OnInitDialog(void) { /* * Sets up the list of drives. */ CDialog::OnInitDialog(); // do any DDX init stuff const Preferences* pPreferences = GET_PREFERENCES(); long defaultFilter; /* highlight/select entire line, not just filename */ CListCtrl* pListView = (CListCtrl*) GetDlgItem(IDC_VOLUME_LIST); ASSERT(pListView != NULL); ListView_SetExtendedListViewStyleEx(pListView->m_hWnd, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT); /* disable the OK button until they click on something */ CButton* pButton = (CButton*) GetDlgItem(IDOK); ASSERT(pButton != NULL); pButton->EnableWindow(FALSE); /* if the read-only state is fixed, don't let them change it */ if (!fAllowROChange) { CButton* pButton; pButton = (CButton*) GetDlgItem(IDC_OPENVOL_READONLY); ASSERT(pButton != NULL); pButton->EnableWindow(FALSE); } /* prep the combo box */ CComboBox* pCombo = (CComboBox*) GetDlgItem(IDC_VOLUME_FILTER); ASSERT(pCombo != NULL); defaultFilter = pPreferences->GetPrefLong(kPrVolumeFilter); if (defaultFilter >= kBoth && defaultFilter <= kPhysical) pCombo->SetCurSel(defaultFilter); else { LOGI("GLITCH: invalid defaultFilter in prefs (%d)", defaultFilter); pCombo->SetCurSel(kLogical); } /* two columns */ CRect rect; pListView->GetClientRect(&rect); int width; width = pListView->GetStringWidth(L"XXVolume or Device NameXXmmmmmm"); pListView->InsertColumn(0, L"Volume or Device Name", LVCFMT_LEFT, width); pListView->InsertColumn(1, L"Remarks", LVCFMT_LEFT, rect.Width() - width - ::GetSystemMetrics(SM_CXVSCROLL)); // Load the drive list. LoadDriveList(); // Kluge the physical drive 0 stuff. DiskImg::SetAllowWritePhys0(GET_PREFERENCES()->GetPrefBool(kPrOpenVolumePhys0)); return TRUE; } void OpenVolumeDialog::DoDataExchange(CDataExchange* pDX) { DDX_Check(pDX, IDC_OPENVOL_READONLY, fReadOnly); LOGI("DoDataExchange: fReadOnly==%d", fReadOnly); } void OpenVolumeDialog::LoadDriveList(void) { CWaitCursor waitc; CComboBox* pCombo; CListCtrl* pListView; int itemIndex = 0; int filterSelection; pListView = (CListCtrl*) GetDlgItem(IDC_VOLUME_LIST); ASSERT(pListView != NULL); pCombo = (CComboBox*) GetDlgItem(IDC_VOLUME_FILTER); ASSERT(pCombo != NULL); pListView->DeleteAllItems(); /* * Load the logical and physical drive sets as needed. Do the "physical" * set first because it's usually what we want. */ filterSelection = pCombo->GetCurSel(); if (filterSelection == kPhysical || filterSelection == kBoth) LoadPhysicalDriveList(pListView, &itemIndex); if (filterSelection == kLogical || filterSelection == kBoth) LoadLogicalDriveList(pListView, &itemIndex); } bool OpenVolumeDialog::LoadLogicalDriveList(CListCtrl* pListView, int* pItemIndex) { DWORD drivesAvailable; bool isWin9x = IsWin9x(); int itemIndex = *pItemIndex; ASSERT(pListView != NULL); drivesAvailable = GetLogicalDrives(); if (drivesAvailable == 0) { LOGI("GetLogicalDrives failed, err=0x%08lx", drivesAvailable); return false; } LOGI("GetLogicalDrives returned 0x%08lx", drivesAvailable); // SetErrorMode(SEM_FAILCRITICALERRORS) /* run through the list, from A-Z */ int i; for (i = 0; i < kMaxLogicalDrives; i++) { fVolumeInfo[i].driveType = DRIVE_UNKNOWN; if ((drivesAvailable >> i) & 0x01) { WCHAR driveName[] = L"_:\\"; driveName[0] = 'A' + i; unsigned int driveType; const WCHAR* driveTypeComment = NULL; BOOL result; driveType = fVolumeInfo[i].driveType = GetDriveType(driveName); switch (driveType) { case DRIVE_UNKNOWN: // The drive type cannot be determined. break; case DRIVE_NO_ROOT_DIR: // The root path is invalid. For example, no volume is mounted at the path. break; case DRIVE_REMOVABLE: // The disk can be removed from the drive. driveTypeComment = L"Removable"; break; case DRIVE_FIXED: // The disk cannot be removed from the drive. driveTypeComment = L"Local Disk"; break; case DRIVE_REMOTE: // The drive is a remote (network) drive. driveTypeComment = L"Network"; break; case DRIVE_CDROM: // The drive is a CD-ROM drive. driveTypeComment = L"CD-ROM"; break; case DRIVE_RAMDISK: // The drive is a RAM disk. break; default: LOGI("UNKNOWN DRIVE TYPE %d", driveType); break; } if (driveType == DRIVE_CDROM && !DiskImgLib::Global::GetHasSPTI()) { /* use "physical" device via ASPI instead */ LOGI("Not including CD-ROM '%ls' in logical drive list", driveName); continue; } WCHAR volNameBuf[256]; WCHAR fsNameBuf[64]; const WCHAR* errorComment = NULL; //DWORD fsFlags; CString entryName, entryRemarks; result = ::GetVolumeInformation(driveName, volNameBuf, NELEM(volNameBuf), NULL, NULL, NULL /*&fsFlags*/, fsNameBuf, NELEM(fsNameBuf)); if (result == FALSE) { DWORD err = GetLastError(); if (err == ERROR_UNRECOGNIZED_VOLUME) { // Win2K: media exists but format not recognized errorComment = L"Non-Windows format"; } else if (err == ERROR_NOT_READY) { // Win2K: device exists but no media loaded if (isWin9x) { LOGI("Not showing drive '%ls': not ready", driveName); continue; // safer not to show it } else errorComment = L"Not ready"; } else if (err == ERROR_PATH_NOT_FOUND /*Win2K*/ || err == ERROR_INVALID_DATA /*Win98*/) { // Win2K/Win98: device letter not in use LOGI("GetVolumeInformation '%ls': nothing there", driveName); continue; } else if (err == ERROR_INVALID_PARAMETER) { // Win2K: device is already open //LOGI("GetVolumeInformation '%ls': currently open??", // driveName); errorComment = L"(currently open?)"; //continue; } else if (err == ERROR_ACCESS_DENIED) { // Win2K: disk is open no-read-sharing elsewhere errorComment = L"(already open read-write)"; } else if (err == ERROR_GEN_FAILURE) { // Win98: floppy format not recognzied // --> we don't want to access ProDOS floppies via A: in // Win98, so we skip it here LOGI("GetVolumeInformation '%ls': general failure", driveName); continue; } else if (err == ERROR_INVALID_FUNCTION) { // Win2K: CD-ROM with HFS if (driveType == DRIVE_CDROM) errorComment = L"Non-Windows format"; else errorComment = L"(invalid disc?)"; } else { LOGI("GetVolumeInformation '%ls' failed: %ld", driveName, GetLastError()); continue; } ASSERT(errorComment != NULL); entryName.Format(L"(%c:)", 'A' + i); if (driveTypeComment != NULL) entryRemarks.Format(L"%ls - %ls", driveTypeComment, errorComment); else entryRemarks.Format(L"%ls", errorComment); } else { entryName.Format(L"%ls (%c:)", volNameBuf, 'A' + i); if (driveTypeComment != NULL) entryRemarks.Format(L"%ls", driveTypeComment); else entryRemarks = ""; } pListView->InsertItem(itemIndex, entryName); pListView->SetItemText(itemIndex, 1, entryRemarks); pListView->SetItemData(itemIndex, (DWORD) i + 'A'); //LOGI("%%%% added logical %d", itemIndex); itemIndex++; } else { LOGI(" (drive %c not available)", i + 'A'); } } *pItemIndex = itemIndex; return true; } bool OpenVolumeDialog::LoadPhysicalDriveList(CListCtrl* pListView, int* pItemIndex) { /* * I don't see a clever way to do this in Win2K except to open the first 8 * or so devices and see what happens. * * Win9x isn't much better, though you can be reasonably confident that there * are at most 4 floppy drives and 4 hard drives. * * TODO: check again for a better way */ bool isWin9x = IsWin9x(); int itemIndex = *pItemIndex; int i; if (isWin9x) { // fairly arbitrary choices const int kMaxFloppies = 4; const int kMaxHardDrives = 8; for (i = 0; i < kMaxFloppies; i++) { CString driveName, remark; bool result; result = HasPhysicalDriveWin9x(i, &remark); if (result) { driveName.Format(L"Floppy disk %d", i); pListView->InsertItem(itemIndex, driveName); pListView->SetItemText(itemIndex, 1, remark); pListView->SetItemData(itemIndex, (DWORD) i); //LOGI("%%%% added floppy %d", itemIndex); itemIndex++; } } for (i = 0; i < kMaxHardDrives; i++) { CString driveName, remark; bool result; result = HasPhysicalDriveWin9x(i + 128, &remark); if (result) { driveName.Format(L"Hard drive %d", i); pListView->InsertItem(itemIndex, driveName); pListView->SetItemText(itemIndex, 1, remark); pListView->SetItemData(itemIndex, (DWORD) i + 128); //LOGI("%%%% added HD %d", itemIndex); itemIndex++; } } } else { for (i = 0; i < kMaxPhysicalDrives; i++) { CString driveName, remark; bool result; result = HasPhysicalDriveWin2K(i + 128, &remark); if (result) { driveName.Format(L"Physical disk %d", i); pListView->InsertItem(itemIndex, driveName); pListView->SetItemText(itemIndex, 1, remark); pListView->SetItemData(itemIndex, (DWORD) i + 128); // HD volume itemIndex++; } } } if (DiskImgLib::Global::GetHasASPI()) { LOGI("IGNORING ASPI"); #if 0 // can we remove this? DIError dierr; DiskImgLib::ASPI* pASPI = DiskImgLib::Global::GetASPI(); ASPIDevice* deviceArray = NULL; int numDevices; dierr = pASPI->GetAccessibleDevices( ASPI::kDevMaskCDROM | ASPI::kDevMaskHardDrive, &deviceArray, &numDevices); if (dierr == kDIErrNone) { LOGI("Adding %d ASPI CD-ROM devices", numDevices); for (i = 0; i < numDevices; i++) { CString driveName, remark; CString addr, vendor, product; DWORD aspiAddr; addr.Format("ASPI %d:%d:%d", deviceArray[i].GetAdapter(), deviceArray[i].GetTarget(), deviceArray[i].GetLun()); vendor = deviceArray[i].GetVendorID(); vendor.TrimRight(); product = deviceArray[i].GetProductID(); product.TrimRight(); driveName.Format("%s %s", vendor, product); if (deviceArray[i].GetDeviceType() == ASPIDevice::kTypeCDROM) remark = "CD-ROM"; else if (deviceArray[i].GetDeviceType() == ASPIDevice::kTypeDASD) remark = "Direct-access device"; if (!deviceArray[i].GetDeviceReady()) remark += " - Not ready"; aspiAddr = (DWORD) 0xaa << 24 | (DWORD) deviceArray[i].GetAdapter() << 16 | (DWORD) deviceArray[i].GetTarget() << 8 | (DWORD) deviceArray[i].GetLun(); //LOGI("ADDR for '%s' is 0x%08lx", // (const char*) driveName, aspiAddr); pListView->InsertItem(itemIndex, driveName); pListView->SetItemText(itemIndex, 1, remark); pListView->SetItemData(itemIndex, aspiAddr); itemIndex++; } } delete[] deviceArray; #endif } *pItemIndex = itemIndex; return true; } bool OpenVolumeDialog::HasPhysicalDriveWin9x(int unit, CString* pRemark) { HANDLE handle = NULL; const int VWIN32_DIOC_DOS_INT13 = 4; const int CARRY_FLAG = 1; BOOL result; typedef struct _DIOC_REGISTERS { DWORD reg_EBX; DWORD reg_EDX; DWORD reg_ECX; DWORD reg_EAX; DWORD reg_EDI; DWORD reg_ESI; DWORD reg_Flags; } DIOC_REGISTERS, *PDIOC_REGISTERS; DIOC_REGISTERS reg = {0}; DWORD lastError, cb; unsigned char buf[512]; if (unit > 4) return false; // floppy drives only handle = CreateFile(L"\\\\.\\vwin32", 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL); if (handle == INVALID_HANDLE_VALUE) { LOGI(" Unable to open vwin32: %ld", ::GetLastError()); return false; } #if 0 // didn't do what I wanted reg.reg_EAX = MAKEWORD(0, 0x00); // func 0x00 == reset controller reg.reg_EDX = MAKEWORD(unit, 0); // specify driver result = DeviceIoControl(handle, VWIN32_DIOC_DOS_INT13, ®, sizeof(reg), ®, sizeof(reg), &cb, 0); LOGI(" DriveReset(drive=0x%02x) result=%d carry=%d", unit, result, reg.reg_Flags & CARRY_FLAG); #endif reg.reg_EAX = MAKEWORD(1, 0x02); // read 1 sector reg.reg_EBX = (DWORD) buf; reg.reg_ECX = MAKEWORD(1, 0); // sector 0 (+1), cylinder 0 reg.reg_EDX = MAKEWORD(unit, 0); // head result = DeviceIoControl(handle, VWIN32_DIOC_DOS_INT13, ®, sizeof(reg) /*bytes*/, ®, sizeof(reg) /*bytes*/, &cb, 0); lastError = GetLastError(); ::CloseHandle(handle); if (result == 0 || (reg.reg_Flags & CARRY_FLAG)) { int ah = HIBYTE(reg.reg_EAX); LOGI(" DevIoCtrl(unit=%02xh) failed: result=%d lastErr=%d Flags=0x%08lx", unit, result, lastError, reg.reg_Flags); LOGI(" AH=%d (EAX=0x%08lx) byte=0x%02x", ah, reg.reg_EAX, buf[0]); if (ah != 1) { // failure code 1 means "invalid parameter", drive doesn't exist // mine returns 128, "timeout", when no disk is in the drive *pRemark = "Not ready"; return true; } else return false; } *pRemark = "Removable"; return true; } bool OpenVolumeDialog::HasPhysicalDriveWin2K(int unit, CString* pRemark) { HANDLE hDevice; // handle to the drive to be examined DISK_GEOMETRY dg; // disk drive geometry structure DISK_GEOMETRY_EX dge; // extended geometry request buffer BOOL result; // results flag DWORD junk; // discard results LONGLONG diskSize; // size of the drive, in bytes CString fileName; DWORD err; /* * See if the drive is there. */ ASSERT(unit >= 128 && unit < 160); // arbitrary max fileName.Format(L"\\\\.\\PhysicalDrive%d", unit - 128); hDevice = ::CreateFile(fileName, // drive to open 0, // no access to the drive FILE_SHARE_READ | FILE_SHARE_WRITE, // share mode NULL, // default security attributes OPEN_EXISTING, // disposition 0, // file attributes NULL); // do not copy file attributes if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive return false; /* * Try to get the drive geometry. First try with the fancy WinXP call, * then fall back to the Win2K call if it doesn't exist. */ result = ::DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, NULL, 0, // input buffer &dge, sizeof(dge), // output buffer + size in bytes &junk, // # bytes returned (LPOVERLAPPED) NULL); // synchronous I/O if (result) { diskSize = dge.DiskSize.QuadPart; LOGI(" EX results for device %02xh", unit); LOGI(" Disk size = %I64d (bytes) = %I64d (MB)", diskSize, diskSize / (1024*1024)); if (diskSize > 1024*1024*1024) pRemark->Format(L"Size is %.2fGB", (double) diskSize / (1024.0 * 1024.0 * 1024.0)); else pRemark->Format(L"Size is %.2fMB", (double) diskSize / (1024.0 * 1024.0)); } else { // Win2K shows ERROR_INVALID_FUNCTION or ERROR_NOT_SUPPORTED LOGI("IOCTL_DISK_GET_DRIVE_GEOMETRY_EX failed, error was %ld", GetLastError()); result = ::DeviceIoControl(hDevice, // device to be queried IOCTL_DISK_GET_DRIVE_GEOMETRY, // operation to perform NULL, 0, // no input buffer &dg, sizeof(dg), // output buffer + size in bytes &junk, // # bytes returned (LPOVERLAPPED) NULL); // synchronous I/O if (result) { LOGI(" Results for device %02xh", unit); LOGI(" Cylinders = %I64d", dg.Cylinders.QuadPart); LOGI(" Tracks per cylinder = %ld", (ULONG) dg.TracksPerCylinder); LOGI(" Sectors per track = %ld", (ULONG) dg.SectorsPerTrack); LOGI(" Bytes per sector = %ld", (ULONG) dg.BytesPerSector); diskSize = dg.Cylinders.QuadPart * (ULONG)dg.TracksPerCylinder * (ULONG)dg.SectorsPerTrack * (ULONG)dg.BytesPerSector; LOGI("Disk size = %I64d (bytes) = %I64d (MB)", diskSize, diskSize / (1024 * 1024)); if (diskSize > 1024*1024*1024) pRemark->Format(L"Size is %.2fGB", (double) diskSize / (1024.0 * 1024.0 * 1024.0)); else pRemark->Format(L"Size is %.2fMB", (double) diskSize / (1024.0 * 1024.0)); } else { err = GetLastError(); } } ::CloseHandle(hDevice); if (!result) { LOGI("DeviceIoControl(IOCTL_DISK_GET_DRIVE_GEOMETRY) failed (err=%ld)", err); *pRemark = "Not ready"; } return true; } void OpenVolumeDialog::OnListChange(NMHDR*, LRESULT* pResult) { CListCtrl* pListView = (CListCtrl*) GetDlgItem(IDC_VOLUME_LIST); CButton* pButton = (CButton*) GetDlgItem(IDOK); pButton->EnableWindow(pListView->GetSelectedCount() != 0); //LOGI("ENABLE %d", pListView->GetSelectedCount() != 0); *pResult = 0; } void OpenVolumeDialog::OnListDblClick(NMHDR* pNotifyStruct, LRESULT* pResult) { CListCtrl* pListView = (CListCtrl*) GetDlgItem(IDC_VOLUME_LIST); CButton* pButton = (CButton*) GetDlgItem(IDOK); if (pListView->GetSelectedCount() != 0) { pButton->EnableWindow(); OnOK(); } *pResult = 0; } void OpenVolumeDialog::OnVolumeFilterSelChange(void) { CComboBox* pCombo = (CComboBox*) GetDlgItem(IDC_VOLUME_FILTER); ASSERT(pCombo != NULL); LOGI("+++ SELECTION IS NOW %d", pCombo->GetCurSel()); LoadDriveList(); } void OpenVolumeDialog::OnOK(void) { /* * Figure out the (zero-based) drive letter. */ CListCtrl* pListView = (CListCtrl*) GetDlgItem(IDC_VOLUME_LIST); ASSERT(pListView != NULL); if (pListView->GetSelectedCount() != 1) { CString msg, failed; CheckedLoadString(&failed, IDS_FAILED); CheckedLoadString(&msg, IDS_VOLUME_SELECT_ONE); MessageBox(msg, failed, MB_OK); return; } POSITION posn; posn = pListView->GetFirstSelectedItemPosition(); if (posn == NULL) { ASSERT(false); return; } int num = pListView->GetNextSelectedItem(posn); DWORD driveID = pListView->GetItemData(num); UINT formatID = 0; if (HIBYTE(HIWORD(driveID)) == 0xaa) { // TODO: remove this? fChosenDrive.Format(L"%hs%d:%d:%d\\", DiskImgLib::kASPIDev, LOBYTE(HIWORD(driveID)), HIBYTE(LOWORD(driveID)), LOBYTE(LOWORD(driveID))); //ForceReadOnly(true); } else if (driveID >= 'A' && driveID <= 'Z') { /* * Do we want to let them do this? We show some logical drives * that we don't want them to actually use. */ switch (fVolumeInfo[driveID-'A'].driveType) { case DRIVE_REMOVABLE: case DRIVE_FIXED: break; // allow case DRIVE_CDROM: //formatID = IDS_VOLUME_NO_CDROM; ForceReadOnly(true); break; case DRIVE_REMOTE: formatID = IDS_VOLUME_NO_REMOTE; break; case DRIVE_RAMDISK: formatID = IDS_VOLUME_NO_RAMDISK; break; case DRIVE_UNKNOWN: case DRIVE_NO_ROOT_DIR: default: formatID = IDS_VOLUME_NO_GENERIC; break; } fChosenDrive.Format(L"%c:\\", driveID); } else if ((driveID >= 0 && driveID < 4) || (driveID >= 0x80 && driveID < 0x88)) { fChosenDrive.Format(L"%02x:\\", driveID); } else { ASSERT(false); return; } if (formatID != 0) { CString msg, notAllowed; CheckedLoadString(¬Allowed, IDS_NOT_ALLOWED); CheckedLoadString(&msg, formatID); MessageBox(msg, notAllowed, MB_OK); } else { Preferences* pPreferences = GET_PREFERENCES_WR(); CComboBox* pCombo = (CComboBox*) GetDlgItem(IDC_VOLUME_FILTER); pPreferences->SetPrefLong(kPrVolumeFilter, pCombo->GetCurSel()); LOGI("SETTING PREF TO %ld", pCombo->GetCurSel()); CDialog::OnOK(); } } void OpenVolumeDialog::ForceReadOnly(bool readOnly) const { CButton* pButton = (CButton*) GetDlgItem(IDC_OPENVOL_READONLY); ASSERT(pButton != NULL); if (readOnly) pButton->SetCheck(BST_CHECKED); else pButton->SetCheck(BST_UNCHECKED); LOGW("FORCED READ ONLY %d", readOnly); }