mirror of
https://github.com/pbatard/rufus.git
synced 2024-08-14 23:57:05 +00:00
[core] added formatting
* also added filter for hotplug detection
This commit is contained in:
parent
7280b0c45b
commit
1c302ee594
3 changed files with 246 additions and 11 deletions
|
@ -30,6 +30,11 @@ const char* additional_copyrights =
|
||||||
"http://www.gnu.org/software/fdisk\r\n"
|
"http://www.gnu.org/software/fdisk\r\n"
|
||||||
"GNU General Public License (GPL) v3 or later\r\n"
|
"GNU General Public License (GPL) v3 or later\r\n"
|
||||||
"\r\n"
|
"\r\n"
|
||||||
|
"fmifs.dll usage based on Formatx by Mark Russinovich:\r\n"
|
||||||
|
"http://doc.sch130.nsc.ru/www.sysinternals.com/ntw2k/source/fmifs.shtml\r\n"
|
||||||
|
"http://svn.reactos.org/svn/reactos/trunk/reactos/include/reactos/libs/fmifs\r\n"
|
||||||
|
"Public Domain\r\n"
|
||||||
|
"\r\n"
|
||||||
"About and License dialogs inspired by WinSCP\r\n"
|
"About and License dialogs inspired by WinSCP\r\n"
|
||||||
"Copyright (c) 2000-2011 Martin Prikryl\r\n"
|
"Copyright (c) 2000-2011 Martin Prikryl\r\n"
|
||||||
"GNU General Public License (GPL) v3 or later";
|
"GNU General Public License (GPL) v3 or later";
|
||||||
|
|
159
rufus.c
159
rufus.c
|
@ -35,12 +35,9 @@
|
||||||
#include <commctrl.h>
|
#include <commctrl.h>
|
||||||
#include <setupapi.h>
|
#include <setupapi.h>
|
||||||
#include <winioctl.h>
|
#include <winioctl.h>
|
||||||
|
#include <dbt.h>
|
||||||
|
|
||||||
// http://doc.sch130.nsc.ru/www.sysinternals.com/ntw2k/source/fmifs.shtml
|
|
||||||
// http://svn.reactos.org/svn/reactos/trunk/reactos/include/reactos/libs/fmifs/
|
|
||||||
//#include <fmifs.h>
|
|
||||||
// http://git.kernel.org/?p=fs/ext2/e2fsprogs.git;a=blob;f=misc/badblocks.c
|
// http://git.kernel.org/?p=fs/ext2/e2fsprogs.git;a=blob;f=misc/badblocks.c
|
||||||
|
|
||||||
// http://ms-sys.sourceforge.net/
|
// http://ms-sys.sourceforge.net/
|
||||||
// http://thestarman.pcministry.com/asm/mbr/MSWIN41.htm
|
// http://thestarman.pcministry.com/asm/mbr/MSWIN41.htm
|
||||||
|
|
||||||
|
@ -77,6 +74,7 @@ struct {
|
||||||
static HWND hDeviceList, hCapacity, hFileSystem, hLabel;
|
static HWND hDeviceList, hCapacity, hFileSystem, hLabel;
|
||||||
static HWND hDeviceTooltip = NULL, hFSTooltip = NULL;
|
static HWND hDeviceTooltip = NULL, hFSTooltip = NULL;
|
||||||
static StrArray DriveID, DriveLabel;
|
static StrArray DriveID, DriveLabel;
|
||||||
|
static DWORD FormatErr;
|
||||||
|
|
||||||
#ifdef RUFUS_DEBUG
|
#ifdef RUFUS_DEBUG
|
||||||
void _uprintf(const char *format, ...)
|
void _uprintf(const char *format, ...)
|
||||||
|
@ -282,12 +280,12 @@ static BOOL GetDriveInfo(void)
|
||||||
if (DriveLayout->PartitionEntry[i].Mbr.PartitionType != PARTITION_ENTRY_UNUSED) {
|
if (DriveLayout->PartitionEntry[i].Mbr.PartitionType != PARTITION_ENTRY_UNUSED) {
|
||||||
uprintf("Partition #%d:\n", ++nb_partitions);
|
uprintf("Partition #%d:\n", ++nb_partitions);
|
||||||
if (hFSTooltip == NULL) {
|
if (hFSTooltip == NULL) {
|
||||||
safe_sprintf(tmp, sizeof(tmp), "Current file system: %s (0x%02X)",
|
safe_sprintf(tmp, sizeof(tmp), "Current file system: %s (0x%02x)",
|
||||||
GetPartitionType(DriveLayout->PartitionEntry[i].Mbr.PartitionType),
|
GetPartitionType(DriveLayout->PartitionEntry[i].Mbr.PartitionType),
|
||||||
DriveLayout->PartitionEntry[i].Mbr.PartitionType);
|
DriveLayout->PartitionEntry[i].Mbr.PartitionType);
|
||||||
hFSTooltip = CreateTooltip(hFileSystem, tmp, -1);
|
hFSTooltip = CreateTooltip(hFileSystem, tmp, -1);
|
||||||
}
|
}
|
||||||
uprintf(" Type: %s (0x%02X)\n Boot: %s\n Recognized: %s\n Hidden Sectors: %d\n",
|
uprintf(" Type: %s (0x%02x)\n Boot: %s\n Recognized: %s\n Hidden Sectors: %d\n",
|
||||||
GetPartitionType(DriveLayout->PartitionEntry[i].Mbr.PartitionType),
|
GetPartitionType(DriveLayout->PartitionEntry[i].Mbr.PartitionType),
|
||||||
DriveLayout->PartitionEntry[i].Mbr.PartitionType,
|
DriveLayout->PartitionEntry[i].Mbr.PartitionType,
|
||||||
DriveLayout->PartitionEntry[i].Mbr.BootIndicator?"Yes":"No",
|
DriveLayout->PartitionEntry[i].Mbr.BootIndicator?"Yes":"No",
|
||||||
|
@ -452,6 +450,7 @@ BOOL CreatePartition(HANDLE hDrive)
|
||||||
BOOL r;
|
BOOL r;
|
||||||
DWORD size;
|
DWORD size;
|
||||||
|
|
||||||
|
StatusPrintf("Partitioning...");
|
||||||
DriveLayoutEx->PartitionStyle = PARTITION_STYLE_MBR;
|
DriveLayoutEx->PartitionStyle = PARTITION_STYLE_MBR;
|
||||||
DriveLayoutEx->PartitionCount = 4; // Must be multiple of 4 for MBR
|
DriveLayoutEx->PartitionCount = 4; // Must be multiple of 4 for MBR
|
||||||
DriveLayoutEx->Mbr.Signature = GetTickCount();
|
DriveLayoutEx->Mbr.Signature = GetTickCount();
|
||||||
|
@ -484,15 +483,126 @@ BOOL CreatePartition(HANDLE hDrive)
|
||||||
safe_closehandle(hDrive);
|
safe_closehandle(hDrive);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
StatusPrintf("Successfully Created Partition");
|
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FormatEx callback. Return FALSE to halt operations
|
||||||
|
*/
|
||||||
|
BOOLEAN __stdcall FormatExCallback(FILE_SYSTEM_CALLBACK_COMMAND Command, DWORD Action, PVOID Data)
|
||||||
|
{
|
||||||
|
DWORD* percent;
|
||||||
|
int task_number = 0;
|
||||||
|
|
||||||
|
switch(Command) {
|
||||||
|
case FCC_PROGRESS:
|
||||||
|
percent = (DWORD*)Data;
|
||||||
|
// PostMessage(hMainDialog, UM_FORMAT_PROGRESS, (WPARAM)*percent, (LPARAM)0);
|
||||||
|
uprintf("%d percent completed.\n", *percent);
|
||||||
|
break;
|
||||||
|
case FCC_STRUCTURE_PROGRESS: // No progress on quick format
|
||||||
|
uprintf("format task %d/n completed.\n", ++task_number);
|
||||||
|
break;
|
||||||
|
case FCC_DONE:
|
||||||
|
if(*(BOOLEAN*)Data == FALSE) {
|
||||||
|
uprintf("Error while formatting.\n");
|
||||||
|
if (FormatErr == 0)
|
||||||
|
FormatErr = FCC_DONE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FCC_INCOMPATIBLE_FILE_SYSTEM:
|
||||||
|
uprintf("Incompatible File System\n");
|
||||||
|
FormatErr = Command;
|
||||||
|
break;
|
||||||
|
case FCC_ACCESS_DENIED:
|
||||||
|
uprintf("Access denied\n");
|
||||||
|
FormatErr = Command;
|
||||||
|
break;
|
||||||
|
case FCC_MEDIA_WRITE_PROTECTED:
|
||||||
|
uprintf("Media is write protected\n");
|
||||||
|
FormatErr = Command;
|
||||||
|
break;
|
||||||
|
case FCC_VOLUME_IN_USE:
|
||||||
|
uprintf("Volume is in use\n");
|
||||||
|
FormatErr = Command;
|
||||||
|
break;
|
||||||
|
case FCC_CANT_QUICK_FORMAT:
|
||||||
|
uprintf("Cannot quick format this volume\n");
|
||||||
|
FormatErr = Command;
|
||||||
|
break;
|
||||||
|
case FCC_BAD_LABEL:
|
||||||
|
uprintf("Bad label\n");
|
||||||
|
break;
|
||||||
|
case FCC_OUTPUT:
|
||||||
|
uprintf("%s\n", ((PTEXTOUTPUT)Data)->Output);
|
||||||
|
break;
|
||||||
|
case FCC_CLUSTER_SIZE_TOO_SMALL:
|
||||||
|
uprintf("Allocation unit size is too small\n");
|
||||||
|
FormatErr = Command;
|
||||||
|
break;
|
||||||
|
case FCC_CLUSTER_SIZE_TOO_BIG:
|
||||||
|
uprintf("Allocation unit size is too big\n");
|
||||||
|
FormatErr = Command;
|
||||||
|
break;
|
||||||
|
case FCC_VOLUME_TOO_SMALL:
|
||||||
|
uprintf("Volume is too small\n");
|
||||||
|
FormatErr = Command;
|
||||||
|
break;
|
||||||
|
case FCC_VOLUME_TOO_BIG:
|
||||||
|
uprintf("Volume is too big\n");
|
||||||
|
FormatErr = Command;
|
||||||
|
break;
|
||||||
|
case FCC_NO_MEDIA_IN_DRIVE:
|
||||||
|
uprintf("No media\n");
|
||||||
|
FormatErr = Command;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
uprintf("FormatExCallback: received unhandled command %X\n", Command);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (FormatErr == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL Format(char DriveLetter)
|
||||||
|
{
|
||||||
|
BOOL r = FALSE;
|
||||||
|
PF_DECL(FormatEx);
|
||||||
|
WCHAR wDriveRoot[] = L"?:\\";
|
||||||
|
WCHAR wFSType[32];
|
||||||
|
WCHAR wLabel[128];
|
||||||
|
|
||||||
|
wDriveRoot[0] = (WCHAR)DriveLetter;
|
||||||
|
StatusPrintf("Formatting...");
|
||||||
|
PF_INIT_OR_OUT(FormatEx, fmifs);
|
||||||
|
|
||||||
|
// TODO: properly set MediaType
|
||||||
|
FormatErr = 0;
|
||||||
|
GetWindowText(hFileSystem, wFSType, ARRAYSIZE(wFSType));
|
||||||
|
GetWindowText(hLabel, wLabel, ARRAYSIZE(wLabel));
|
||||||
|
pfFormatEx(wDriveRoot, RemovableMedia, wFSType, wLabel,
|
||||||
|
(IsDlgButtonChecked(hMainDialog, IDC_QUICKFORMAT) == BST_CHECKED),
|
||||||
|
4096, FormatExCallback);
|
||||||
|
if (FormatErr == 0) {
|
||||||
|
uprintf("Format completed.\n");
|
||||||
|
StatusPrintf("Done.");
|
||||||
|
r = TRUE;
|
||||||
|
} else {
|
||||||
|
uprintf("Format error: %X\n", FormatErr);
|
||||||
|
StatusPrintf("FAILED.");
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: create a thread for this
|
||||||
BOOL FormatDrive(DWORD num)
|
BOOL FormatDrive(DWORD num)
|
||||||
{
|
{
|
||||||
HANDLE hDrive;
|
HANDLE hDrive;
|
||||||
BOOL r;
|
BOOL r = FALSE;
|
||||||
|
char drive_letter;
|
||||||
|
int i;
|
||||||
|
|
||||||
if (!GetDriveHandle(num, &hDrive, NULL, TRUE)) {
|
if (!GetDriveHandle(num, &hDrive, NULL, TRUE)) {
|
||||||
// TODO: report an error for exclusive access
|
// TODO: report an error for exclusive access
|
||||||
|
@ -500,8 +610,24 @@ BOOL FormatDrive(DWORD num)
|
||||||
}
|
}
|
||||||
|
|
||||||
r = CreatePartition(hDrive);
|
r = CreatePartition(hDrive);
|
||||||
|
safe_closehandle(hDrive);
|
||||||
|
if (!r) return FALSE;
|
||||||
|
|
||||||
|
// Make sure we can reopen the drive before trying to format it
|
||||||
|
for (i=0; i<10; i++) {
|
||||||
|
Sleep(500);
|
||||||
|
if (GetDriveHandle(num, &hDrive, &drive_letter, FALSE)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i >= 10) {
|
||||||
|
uprintf("Unable to reopen drive after partitioning\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
safe_closehandle(hDrive);
|
||||||
|
|
||||||
|
r = Format(drive_letter);
|
||||||
|
|
||||||
CloseHandle(hDrive);
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -629,8 +755,12 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA
|
||||||
switch (message) {
|
switch (message) {
|
||||||
|
|
||||||
case WM_DEVICECHANGE:
|
case WM_DEVICECHANGE:
|
||||||
GetUSBDevices();
|
// TODO: also prevent detect during format
|
||||||
return (INT_PTR)TRUE;
|
if ((wParam == DBT_DEVICEARRIVAL) || (wParam == DBT_DEVICEREMOVECOMPLETE)) {
|
||||||
|
GetUSBDevices();
|
||||||
|
return (INT_PTR)TRUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case WM_INITDIALOG:
|
case WM_INITDIALOG:
|
||||||
hMainDialog = hDlg;
|
hMainDialog = hDlg;
|
||||||
|
@ -646,8 +776,11 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA
|
||||||
CreateStatusBar();
|
CreateStatusBar();
|
||||||
// Display the version in the right area of the status bar
|
// Display the version in the right area of the status bar
|
||||||
SendMessageA(GetDlgItem(hDlg, IDC_STATUS), SB_SETTEXTA, SBT_OWNERDRAW | 1, (LPARAM)APP_VERSION);
|
SendMessageA(GetDlgItem(hDlg, IDC_STATUS), SB_SETTEXTA, SBT_OWNERDRAW | 1, (LPARAM)APP_VERSION);
|
||||||
|
// Create the string array
|
||||||
StrArrayCreate(&DriveID, MAX_DRIVES);
|
StrArrayCreate(&DriveID, MAX_DRIVES);
|
||||||
StrArrayCreate(&DriveLabel, MAX_DRIVES);
|
StrArrayCreate(&DriveLabel, MAX_DRIVES);
|
||||||
|
// Set the quick format checkbox
|
||||||
|
CheckDlgButton(hDlg, IDC_QUICKFORMAT, BST_CHECKED);
|
||||||
GetUSBDevices();
|
GetUSBDevices();
|
||||||
return (INT_PTR)TRUE;
|
return (INT_PTR)TRUE;
|
||||||
|
|
||||||
|
@ -706,6 +839,10 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA
|
||||||
PostQuitMessage(0);
|
PostQuitMessage(0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case UM_FORMAT_PROGRESS:
|
||||||
|
uprintf("Got UM_FORMAT_PROGRESS with percent %d\n", (DWORD)wParam);
|
||||||
|
return (INT_PTR)TRUE;
|
||||||
|
|
||||||
}
|
}
|
||||||
return (INT_PTR)FALSE;
|
return (INT_PTR)FALSE;
|
||||||
}
|
}
|
||||||
|
|
93
rufus.h
93
rufus.h
|
@ -15,6 +15,7 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
#include <winioctl.h> // for MEDIA_TYPE
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
@ -98,6 +99,12 @@ extern void _uprintf(const char *format, ...);
|
||||||
#define uprintf(...)
|
#define uprintf(...)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Custom Windows messages */
|
||||||
|
enum user_message_type {
|
||||||
|
UM_FORMAT_PROGRESS = WM_APP,
|
||||||
|
UM_FORMAT_COMPLETED
|
||||||
|
};
|
||||||
|
|
||||||
/* Custom notifications */
|
/* Custom notifications */
|
||||||
enum MessageType {
|
enum MessageType {
|
||||||
MSG_INFO,
|
MSG_INFO,
|
||||||
|
@ -106,6 +113,8 @@ enum MessageType {
|
||||||
};
|
};
|
||||||
|
|
||||||
/* File system indexes in our FS combobox */
|
/* File system indexes in our FS combobox */
|
||||||
|
// TODO: FormatEx should support "NTFS", "FAT", "FAT32", "UDF", and "EXFAT" as per
|
||||||
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa819439.aspx
|
||||||
enum _FSType {
|
enum _FSType {
|
||||||
FS_FAT16 = 0,
|
FS_FAT16 = 0,
|
||||||
FS_FAT32,
|
FS_FAT32,
|
||||||
|
@ -118,3 +127,87 @@ typedef struct {
|
||||||
ULONG DeviceNumber;
|
ULONG DeviceNumber;
|
||||||
ULONG PartitionNumber;
|
ULONG PartitionNumber;
|
||||||
} STORAGE_DEVICE_NUMBER_REDEF;
|
} STORAGE_DEVICE_NUMBER_REDEF;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* typedefs for the function prototypes. Use the something like:
|
||||||
|
* PF_DECL(FormatEx);
|
||||||
|
* which translates to:
|
||||||
|
* FormatEx_t pfFormatEx = NULL;
|
||||||
|
* in your code, to declare the entrypoint and then use:
|
||||||
|
* PF_INIT(FormatEx, fmifs);
|
||||||
|
* which translates to:
|
||||||
|
* pfFormatEx = (FormatEx_t) GetProcAddress(GetDLLHandle("fmifs"), "FormatEx");
|
||||||
|
* to make it accessible.
|
||||||
|
*/
|
||||||
|
static __inline HMODULE GetDLLHandle(char* szDLLName)
|
||||||
|
{
|
||||||
|
HMODULE h = NULL;
|
||||||
|
if ((h = GetModuleHandleA(szDLLName)) == NULL)
|
||||||
|
h = LoadLibraryA(szDLLName);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
#define PF_DECL(proc) proc##_t pf##proc = NULL
|
||||||
|
#define PF_INIT(proc, dllname) pf##proc = (proc##_t) GetProcAddress(GetDLLHandle(#dllname), #proc)
|
||||||
|
#define PF_INIT_OR_OUT(proc, dllname) \
|
||||||
|
PF_INIT(proc, dllname); if (pf##proc == NULL) { \
|
||||||
|
uprintf("unable to access %s DLL: %s", #dllname, \
|
||||||
|
WindowsErrorString()); goto out; }
|
||||||
|
|
||||||
|
/* Callback command types (some errorcode were filled from HPUSBFW V2.2.3 and their
|
||||||
|
designation from msdn.microsoft.com/en-us/library/windows/desktop/aa819439.aspx */
|
||||||
|
typedef enum {
|
||||||
|
FCC_PROGRESS,
|
||||||
|
FCC_DONE_WITH_STRUCTURE,
|
||||||
|
FCC_UNKNOWN2,
|
||||||
|
FCC_INCOMPATIBLE_FILE_SYSTEM,
|
||||||
|
FCC_UNKNOWN4,
|
||||||
|
FCC_UNKNOWN5,
|
||||||
|
FCC_ACCESS_DENIED,
|
||||||
|
FCC_MEDIA_WRITE_PROTECTED,
|
||||||
|
FCC_VOLUME_IN_USE,
|
||||||
|
FCC_CANT_QUICK_FORMAT,
|
||||||
|
FCC_UNKNOWNA,
|
||||||
|
FCC_DONE,
|
||||||
|
FCC_BAD_LABEL,
|
||||||
|
FCC_UNKNOWND,
|
||||||
|
FCC_OUTPUT,
|
||||||
|
FCC_STRUCTURE_PROGRESS,
|
||||||
|
FCC_CLUSTER_SIZE_TOO_SMALL,
|
||||||
|
FCC_CLUSTER_SIZE_TOO_BIG,
|
||||||
|
FCC_VOLUME_TOO_SMALL,
|
||||||
|
FCC_VOLUME_TOO_BIG,
|
||||||
|
FCC_NO_MEDIA_IN_DRIVE,
|
||||||
|
} FILE_SYSTEM_CALLBACK_COMMAND;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DWORD Lines;
|
||||||
|
CHAR* Output;
|
||||||
|
} TEXTOUTPUT, *PTEXTOUTPUT;
|
||||||
|
|
||||||
|
typedef BOOLEAN (__stdcall *FILE_SYSTEM_CALLBACK)(
|
||||||
|
FILE_SYSTEM_CALLBACK_COMMAND Command,
|
||||||
|
ULONG Action,
|
||||||
|
PVOID Data
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Parameter naming aligned to
|
||||||
|
http://msdn.microsoft.com/en-us/library/windows/desktop/aa819439.aspx */
|
||||||
|
typedef VOID (WINAPI *FormatEx_t)(
|
||||||
|
WCHAR* DriveRoot,
|
||||||
|
MEDIA_TYPE MediaType, // See WinIoCtl.h
|
||||||
|
WCHAR* FileSystemTypeName,
|
||||||
|
WCHAR* Label,
|
||||||
|
BOOL QuickFormat,
|
||||||
|
ULONG DesiredUnitAllocationSize,
|
||||||
|
FILE_SYSTEM_CALLBACK Callback
|
||||||
|
);
|
||||||
|
|
||||||
|
/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa383357.aspx */
|
||||||
|
typedef enum {
|
||||||
|
FPF_COMPRESSED = 0x01
|
||||||
|
} FILE_SYSTEM_PROP_FLAG;
|
||||||
|
|
||||||
|
typedef BOOLEAN (WINAPI* EnableVolumeCompression_t)(
|
||||||
|
WCHAR* DriveRoot,
|
||||||
|
ULONG CompressionFlags // FILE_SYSTEM_PROP_FLAG
|
||||||
|
);
|
||||||
|
|
Loading…
Reference in a new issue