[iso] cancellation improvements

* remove threaded cancellation, detect blocking and warn instead
* also add hidden shortcut to disable ISO size check
This commit is contained in:
Pete Batard 2012-02-07 16:17:14 +00:00
parent d1f41309a9
commit 5d58b83ed8
5 changed files with 103 additions and 60 deletions

View File

@ -560,10 +560,10 @@ DWORD WINAPI FormatThread(LPVOID param)
}
}
// TODO: the only way to properly recover from a cancel will be through a device reset
// We issue a complete remount of the filesystem at the end on account of:
// - Ensuring the file explorer properly detects that the volume was updated
// - Ensuring that an NTFS system will be reparsed so that it becomes bootable
// TODO: on cancellation, this can leave the drive unmounted!
if (GetVolumeNameForVolumeMountPointA(drive_name, drive_guid, sizeof(drive_guid))) {
if (DeleteVolumeMountPointA(drive_name)) {
Sleep(200);

View File

@ -53,7 +53,6 @@
// How often should we update the progress bar (in 2K blocks) as updating
// the progress bar for every block will bring extraction to a crawl
#define PROGRESS_THRESHOLD 1024
#define THREADED_CLOSE_THRESHOLD (20 * 1024 * 1024) // 20 MB
#define FOUR_GIGABYTES 4294967296LL
// Needed for UDF ISO access
@ -61,7 +60,10 @@ CdIo_t* cdio_open (const char *psz_source, driver_id_t driver_id) {return NULL;}
void cdio_destroy (CdIo_t *p_cdio) {}
RUFUS_ISO_REPORT iso_report;
int64_t iso_blocking_status = -1;
#define ISO_BLOCKING(x) do {x; iso_blocking_status++; } while(0)
static const char *psz_extract_dir;
static const char *isolinux_name = "isolinux", *bootmgr_name = "bootmgr";
static uint64_t total_blocks, nb_blocks;
static BOOL scan_only = FALSE;
@ -89,26 +91,12 @@ DWORD WINAPI ISOCloseHandleThread(LPVOID param)
ExitThread(0);
}
#define SAFE_CLOSEHANDLE_THREADED(handle) \
if (!threaded_close) { \
safe_closehandle(handle); \
} else { \
thid = CreateThread(NULL, 0, ISOCloseHandleThread, (LPVOID)handle, 0, NULL); \
while (WaitForSingleObject(thid, 1000) == WAIT_TIMEOUT) { \
if (!FormatStatus) continue; \
safe_closehandle(thid); \
break; \
} \
handle = NULL; \
threaded_close = FALSE; \
}
// Returns 0 on success, nonzero on error
static int udf_extract_files(udf_t *p_udf, udf_dirent_t *p_udf_dirent, const char *psz_path)
{
HANDLE thid, file_handle = NULL;
HANDLE file_handle = NULL;
DWORD buf_size, wr_size;
BOOL threaded_close = FALSE;
BOOL r;
int i_length;
size_t i, nul_pos;
char* psz_fullpath;
@ -138,7 +126,7 @@ static int udf_extract_files(udf_t *p_udf, udf_dirent_t *p_udf_dirent, const cha
_mkdir(psz_fullpath);
} else {
// Check for an "isolinux\" dir in root (psz_path = "")
if ((*psz_path == 0) && (safe_strcmp(psz_basename, "isolinux") == 0))
if ((*psz_path == 0) && (safe_strcmp(psz_basename, isolinux_name) == 0))
iso_report.has_isolinux = TRUE;
}
p_udf_dirent2 = udf_opendir(p_udf_dirent);
@ -150,7 +138,7 @@ static int udf_extract_files(udf_t *p_udf, udf_dirent_t *p_udf_dirent, const cha
i_file_length = udf_get_file_length(p_udf_dirent);
if (scan_only) {
// Check for a "bootmgr" file in root (psz_path = "")
if ((*psz_path == 0) && (safe_strcmp(psz_basename, "bootmgr") == 0))
if ((*psz_path == 0) && (safe_strcmp(psz_basename, bootmgr_name) == 0))
iso_report.has_bootmgr = TRUE;
if (i_file_length >= FOUR_GIGABYTES)
iso_report.has_4GB_file = TRUE;
@ -177,8 +165,6 @@ static int udf_extract_files(udf_t *p_udf, udf_dirent_t *p_udf_dirent, const cha
uprintf(" Unable to create file: %s\n", WindowsErrorString());
goto out;
}
threaded_close = (i_file_length > THREADED_CLOSE_THRESHOLD);
if (threaded_close) uprintf("will use threaded close\n");
while (i_file_length > 0) {
if (FormatStatus) goto out;
memset(buf, 0, UDF_BLOCKSIZE);
@ -188,7 +174,8 @@ static int udf_extract_files(udf_t *p_udf, udf_dirent_t *p_udf_dirent, const cha
goto out;
}
buf_size = (DWORD)MIN(i_file_length, i_read);
if (!WriteFile(file_handle, buf, buf_size, &wr_size, NULL) || (buf_size != wr_size)) {
ISO_BLOCKING(r = WriteFile(file_handle, buf, buf_size, &wr_size, NULL));
if ((!r) || (buf_size != wr_size)) {
uprintf(" Error writing file: %s\n", WindowsErrorString());
goto out;
}
@ -201,9 +188,8 @@ static int udf_extract_files(udf_t *p_udf, udf_dirent_t *p_udf_dirent, const cha
// excellent job at compensating for our small blocks read/writes to max out the
// device's bandwidth.
// The drawback however is with cancellation. With a large file, CloseHandle()
// may take forever to complete on a large file and is not an interruptible
// operation. To compensate for this, we create a thread when needed.
SAFE_CLOSEHANDLE_THREADED(file_handle);
// may take forever to complete and is not interruptible. We try to detect this.
ISO_BLOCKING(safe_closehandle(file_handle));
}
safe_free(psz_fullpath);
}
@ -212,7 +198,7 @@ static int udf_extract_files(udf_t *p_udf, udf_dirent_t *p_udf_dirent, const cha
out:
if (p_udf_dirent != NULL)
udf_dirent_free(p_udf_dirent);
SAFE_CLOSEHANDLE_THREADED(file_handle);
ISO_BLOCKING(safe_closehandle(file_handle));
safe_free(psz_fullpath);
return 1;
}
@ -220,9 +206,9 @@ out:
// Returns 0 on success, nonzero on error
static int iso_extract_files(iso9660_t* p_iso, const char *psz_path)
{
HANDLE thid, file_handle = NULL;
HANDLE file_handle = NULL;
DWORD buf_size, wr_size;
BOOL threaded_close = FALSE;
BOOL s;
int i_length, r = 1;
char psz_fullpath[1024], *psz_basename;
const char *psz_iso_name = &psz_fullpath[strlen(psz_extract_dir)];
@ -259,7 +245,7 @@ static int iso_extract_files(iso9660_t* p_iso, const char *psz_path)
_mkdir(psz_fullpath);
} else {
// Check for an "isolinux\" dir in root (psz_path = "")
if ((*psz_path == 0) && (safe_strcmp(psz_basename, "isolinux") == 0))
if ((*psz_path == 0) && (safe_strcmp(psz_basename, isolinux_name) == 0))
iso_report.has_isolinux = TRUE;
}
if (iso_extract_files(p_iso, psz_iso_name))
@ -268,7 +254,7 @@ static int iso_extract_files(iso9660_t* p_iso, const char *psz_path)
i_file_length = p_statbuf->size;
if (scan_only) {
// Check for a "bootmgr" file in root (psz_path = "")
if ((*psz_path == 0) && (safe_strcmp(psz_basename, "bootmgr") == 0))
if ((*psz_path == 0) && (safe_strcmp(psz_basename, bootmgr_name) == 0))
iso_report.has_bootmgr = TRUE;
if (i_file_length >= FOUR_GIGABYTES)
iso_report.has_4GB_file = TRUE;
@ -293,7 +279,6 @@ static int iso_extract_files(iso9660_t* p_iso, const char *psz_path)
uprintf(" Unable to create file: %s\n", WindowsErrorString());
goto out;
}
threaded_close = (i_file_length > THREADED_CLOSE_THRESHOLD);
for (i = 0; i_file_length > 0; i++) {
if (FormatStatus) goto out;
memset(buf, 0, ISO_BLOCKSIZE);
@ -304,7 +289,8 @@ static int iso_extract_files(iso9660_t* p_iso, const char *psz_path)
goto out;
}
buf_size = (DWORD)MIN(i_file_length, ISO_BLOCKSIZE);
if (!WriteFile(file_handle, buf, buf_size, &wr_size, NULL) || (buf_size != wr_size)) {
ISO_BLOCKING(s = WriteFile(file_handle, buf, buf_size, &wr_size, NULL));
if ((!s) || (buf_size != wr_size)) {
uprintf(" Error writing file: %s\n", WindowsErrorString());
goto out;
}
@ -313,13 +299,13 @@ static int iso_extract_files(iso9660_t* p_iso, const char *psz_path)
SendMessage(hISOProgressBar, PBM_SETPOS, (WPARAM)((MAX_PROGRESS*nb_blocks)/total_blocks), 0);
}
}
SAFE_CLOSEHANDLE_THREADED(file_handle);
ISO_BLOCKING(safe_closehandle(file_handle));
}
}
r = 0;
out:
SAFE_CLOSEHANDLE_THREADED(file_handle);
ISO_BLOCKING(safe_closehandle(file_handle));
_cdio_list_free(p_entlist, true);
return r;
}
@ -360,6 +346,7 @@ BOOL ExtractISO(const char* src_iso, const char* dest_dir, bool scan)
goto out;
}
nb_blocks = 0;
iso_blocking_status = 0;
SetWindowLong(hISOProgressBar, GWL_STYLE, progress_style & (~PBS_MARQUEE));
SendMessage(hISOProgressBar, PBM_SETPOS, 0, 0);
}
@ -390,6 +377,7 @@ try_iso:
r = iso_extract_files(p_iso, "");
out:
iso_blocking_status = -1;
if (scan_only) {
// We use the fact that UDF_BLOCKSIZE and ISO_BLOCKSIZE are the same here
iso_report.projected_size = total_blocks * ISO_BLOCKSIZE;

View File

@ -44,8 +44,8 @@ static const char* FileSystemLabel[FS_MAX] = { "FAT", "FAT32", "NTFS", "exFAT" }
static const char* ClusterSizeLabel[] = { "512 bytes", "1024 bytes","2048 bytes","4096 bytes","8192 bytes",
"16 kilobytes", "32 kilobytes", "64 kilobytes", "128 kilobytes", "256 kilobytes", "512 kilobytes",
"1024 kilobytes","2048 kilobytes","4096 kilobytes","8192 kilobytes","16 megabytes","32 megabytes" };
// For LGP set/restore
static BOOL existing_key = FALSE;
static BOOL existing_key = FALSE; // For LGP set/restore
static BOOL iso_size_check = TRUE;
/*
* Globals
@ -67,6 +67,7 @@ static HICON hIconDisc;
static StrArray DriveID, DriveLabel;
static char szTimer[12] = "00:00:00";
static unsigned int timer;
static int64_t last_iso_blocking_status;
/*
* The following is used to allocate slots within the progress bar
@ -472,7 +473,6 @@ BOOL CreatePartition(HANDLE hDrive)
break;
case FS_NTFS:
case FS_EXFAT:
// TODO: but how do we set this thing up afterwards?
DriveLayoutEx->PartitionEntry[0].Mbr.PartitionType = 0x07; // NTFS
break;
default:
@ -626,7 +626,6 @@ static void InitProgress(void)
}
if (IsChecked(IDC_DOS)) {
// 1 extra slot for PBR writing
// TODO: ISO
switch (ComboBox_GetItemData(hDOSType, ComboBox_GetCurSel(hDOSType))) {
case DT_WINME:
nb_slots[OP_DOS] = 3+1;
@ -887,6 +886,35 @@ static void CALLBACK ClockTimer(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dw
SendMessageA(GetDlgItem(hWnd, IDC_STATUS), SB_SETTEXTA, SBT_OWNERDRAW | 1, (LPARAM)szTimer);
}
/*
* Detect and notify about a blocking operation during ISO extraction cancellation
*/
static void CALLBACK BlockingTimer(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
static BOOL user_notified = FALSE;
if (iso_blocking_status < 0) {
KillTimer(hMainDialog, TID_BLOCKING_TIMER);
user_notified = FALSE;
uprintf("Killed blocking I/O timer\n");
} else if(!user_notified) {
if (last_iso_blocking_status == iso_blocking_status) {
// A write or close operation hasn't made any progress since our last check
user_notified = TRUE;
uprintf("Blocking I/O operation detected\n");
MessageBoxU(hMainDialog,
"Rufus detected that Windows is still flushing its internal buffers\n"
"onto the USB device.\n\n"
"Depending on the speed of your USB device, this operation may\n"
"take a long time to complete, especially for large files.\n\n"
"We recommend that you let Windows finish, to avoid corruption.\n"
"But if you grow tired of waiting, you can just unplug the device...",
RUFUS_BLOCKING_IO_TITLE, MB_OK|MB_ICONINFORMATION);
} else {
last_iso_blocking_status = iso_blocking_status;
}
}
}
/* Callback for the modeless ISO extraction progress */
BOOL CALLBACK ISOProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
@ -906,10 +934,15 @@ BOOL CALLBACK ISOProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
switch (LOWORD(wParam)) {
case IDC_ISO_ABORT:
FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_CANCELLED;
// if (format_thid != NULL)
// CancelSynchronousIo(format_thid);
PrintStatus(0, FALSE, "Cancelling - This might take a while...");
uprintf("Cancelling (ISO)\n");
PrintStatus(0, FALSE, "Cancelling - Please wait...");
uprintf("Cancelling (from ISO proc.)\n");
EnableWindow(GetDlgItem(hISOProgressDlg, IDC_ISO_ABORT), FALSE);
EnableWindow(GetDlgItem(hMainDialog, IDCANCEL), FALSE);
// Start a timer to detect blocking operations during ISO file extraction
if (iso_blocking_status >= 0) {
last_iso_blocking_status = iso_blocking_status;
SetTimer(hMainDialog, TID_BLOCKING_TIMER, 5000, BlockingTimer);
}
return TRUE;
}
case WM_CLOSE: // prevent closure using Alt-F4
@ -921,7 +954,10 @@ BOOL CALLBACK ISOProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
// The scanning process can be blocking for message processing => use a thread
DWORD WINAPI ISOScanThread(LPVOID param)
{
size_t i;
int i;
if (iso_path == NULL)
goto out;
PrintStatus(0, TRUE, "Scanning ISO image...\n");
if (!ExtractISO(iso_path, "", TRUE)) {
PrintStatus(0, TRUE, "Failed to scan ISO image.");
@ -934,7 +970,7 @@ DWORD WINAPI ISOScanThread(LPVOID param)
"ISO images that rely on 'bootmgr' - sorry.", "Unsupported ISO", MB_OK|MB_ICONINFORMATION);
safe_free(iso_path);
} else {
for (i=safe_strlen(iso_path); (i>=0)&&(iso_path[i] != '\\'); i--);
for (i=(int)safe_strlen(iso_path); (i>0)&&(iso_path[i]!='\\'); i--);
PrintStatus(0, TRUE, "Using ISO: '%s'\n", &iso_path[i+1]);
}
@ -1095,17 +1131,26 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA
switch(LOWORD(wParam)) {
case IDOK: // close application
case IDCANCEL:
EnableWindow(GetDlgItem(hISOProgressDlg, IDC_ISO_ABORT), FALSE);
EnableWindow(GetDlgItem(hDlg, IDCANCEL), FALSE);
if (format_thid != NULL) {
if (MessageBoxA(hMainDialog, "Cancelling may leave the device in an UNUSABLE state.\r\n"
"If you are sure you want to cancel, click YES. Otherwise, click NO.",
RUFUS_CANCELBOX_TITLE, MB_YESNO|MB_ICONWARNING) == IDYES) {
// Operation may have completed in the meantime
if (format_thid != NULL) {
// CancelSynchronousIo(format_thid);
FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_CANCELLED;
PrintStatus(0, FALSE, "Cancelling - Please wait...");
uprintf("Cancelling (General)\n");
uprintf("Cancelling (from main app)\n");
// Start a timer to detect blocking operations during ISO file extraction
if (iso_blocking_status >= 0) {
last_iso_blocking_status = iso_blocking_status;
SetTimer(hMainDialog, TID_BLOCKING_TIMER, 3000, BlockingTimer);
}
}
} else {
EnableWindow(GetDlgItem(hISOProgressDlg, IDC_ISO_ABORT), TRUE);
EnableWindow(GetDlgItem(hDlg, IDCANCEL), TRUE);
}
return (INT_PTR)TRUE;
}
@ -1215,7 +1260,7 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA
"No ISO image selected...", MB_OK|MB_ICONERROR);
break;
}
if (iso_report.projected_size > (uint64_t)SelectedDrive.DiskSize) {
if ((iso_size_check) && (iso_report.projected_size > (uint64_t)SelectedDrive.DiskSize)) {
MessageBoxA(hMainDialog, "This ISO image is too big "
"for the selected target.", "ISO image too big...", MB_OK|MB_ICONERROR);
break;
@ -1266,16 +1311,18 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA
format_thid = NULL;
// Stop the timer
KillTimer(hMainDialog, TID_APP_TIMER);
// Close the cancel MessageBox if active
// Close the cancel MessageBox and Blocking notification if active
SendMessage(FindWindowA(MAKEINTRESOURCEA(32770), RUFUS_CANCELBOX_TITLE), WM_COMMAND, IDNO, 0);
SendMessage(FindWindowA(MAKEINTRESOURCEA(32770), RUFUS_BLOCKING_IO_TITLE), WM_COMMAND, IDYES, 0);
EnableWindow(GetDlgItem(hISOProgressDlg, IDC_ISO_ABORT), TRUE);
EnableWindow(GetDlgItem(hMainDialog, IDCANCEL), TRUE);
EnableControls(TRUE);
GetUSBDevices();
if (!IS_ERROR(FormatStatus)) {
PrintStatus(0, FALSE, "DONE");
} else if (SCODE_CODE(FormatStatus) == ERROR_CANCELLED) {
PrintStatus(0, FALSE, "Cancelled");
Notification(MSG_INFO, "Cancelled", "Operation cancelled by the user.\n"
"If you aborted during file extraction, you should replug the drive...");
Notification(MSG_INFO, "Cancelled", "Operation cancelled by the user.");
} else {
PrintStatus(0, FALSE, "FAILED");
Notification(MSG_ERROR, "Error", "Error: %s", StrError(FormatStatus));
@ -1347,6 +1394,12 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
while(GetMessage(&msg, NULL, 0, 0)) {
// The following ensures the processing of the ISO progress window messages
if (!IsWindow(hISOProgressDlg) || !IsDialogMessage(hISOProgressDlg, &msg)) {
// Alt-S => Disable size limit
if ((msg.message == WM_SYSKEYDOWN) && (msg.wParam == 'S')) {
iso_size_check = !iso_size_check;
PrintStatus(0, FALSE, "ISO size check %s", iso_size_check?"enabled":"disabled");
continue;
}
#ifdef DISABLE_AUTORUN
// Alt-D => Delete the NoDriveTypeAutorun key on exit (useful if the app crashed)
if ((msg.message == WM_SYSKEYDOWN) && (msg.wParam == 'D')) {

View File

@ -22,14 +22,14 @@
#pragma once
/* Program options */
#define RUFUS_DEBUG // print debug info to Debug facility (use debugview to consult)
#define RUFUS_DEBUG // print debug info to Debug facility
#define DISABLE_AUTORUN // disable new USB drive notification from explorer when application is running
/* Features not ready for prime time and that may *DESTROY* your data - USE AT YOUR OWN RISKS! */
//#define RUFUS_TEST
#define STR_NO_LABEL "NO_LABEL"
#define RUFUS_CANCELBOX_TITLE "Rufus - Cancellation"
#define RUFUS_BLOCKING_IO_TITLE "Rufus - Flushing buffers"
#define DRIVE_INDEX_MIN 0x80
#define DRIVE_INDEX_MAX 0xC0
#define MAX_DRIVES 16
@ -93,7 +93,8 @@ enum notification_type {
enum timer_type {
TID_MESSAGE = 0x1000,
TID_BADBLOCKS_UPDATE,
TID_APP_TIMER
TID_APP_TIMER,
TID_BLOCKING_TIMER
};
/* Action type, for progress bar breakdown */
@ -165,6 +166,7 @@ extern RUFUS_DRIVE_INFO SelectedDrive;
extern const int nb_steps[FS_MAX];
extern BOOL bWithFreeDOS;
extern RUFUS_ISO_REPORT iso_report;
extern int64_t iso_blocking_status;
/*
* Shared prototypes

View File

@ -33,7 +33,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL
IDD_DIALOG DIALOGEX 12, 12, 206, 278
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "Rufus v1.0.8.128"
CAPTION "Rufus v1.0.8.129"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "Start",IDC_START,94,236,50,14
@ -71,7 +71,7 @@ BEGIN
DEFPUSHBUTTON "OK",IDOK,231,175,50,14,WS_GROUP
CONTROL "<a href=""http://rufus.akeo.ie"">http://rufus.akeo.ie</a>",IDC_ABOUT_RUFUS_URL,
"SysLink",WS_TABSTOP,46,47,114,9
LTEXT "Version 1.0.8 (Build 128)",IDC_STATIC,46,19,78,8
LTEXT "Version 1.0.8 (Build 129)",IDC_STATIC,46,19,78,8
PUSHBUTTON "License...",IDC_ABOUT_LICENSE,46,175,50,14,WS_GROUP
EDITTEXT IDC_ABOUT_COPYRIGHTS,46,107,235,63,ES_MULTILINE | ES_READONLY | WS_VSCROLL
LTEXT "Report bugs or request enhancements at:",IDC_STATIC,46,66,187,8
@ -223,8 +223,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,8,128
PRODUCTVERSION 1,0,8,128
FILEVERSION 1,0,8,129
PRODUCTVERSION 1,0,8,129
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -241,13 +241,13 @@ BEGIN
BEGIN
VALUE "CompanyName", "akeo.ie"
VALUE "FileDescription", "Rufus"
VALUE "FileVersion", "1.0.8.128"
VALUE "FileVersion", "1.0.8.129"
VALUE "InternalName", "Rufus"
VALUE "LegalCopyright", "© 2011 Pete Batard (GPL v3)"
VALUE "LegalTrademarks", "http://www.gnu.org/copyleft/gpl.html"
VALUE "OriginalFilename", "rufus.exe"
VALUE "ProductName", "Rufus"
VALUE "ProductVersion", "1.0.8.128"
VALUE "ProductVersion", "1.0.8.129"
END
END
BLOCK "VarFileInfo"