mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
597 lines
17 KiB
597 lines
17 KiB
/*++
|
|
|
|
Copyright (c) 1999-2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
efifmt.cxx
|
|
|
|
Abstract:
|
|
|
|
This is the main program for the efi version of format.
|
|
|
|
--*/
|
|
|
|
#pragma warning(disable: 4091)
|
|
|
|
#include "ulib.hxx"
|
|
#include "wstring.hxx"
|
|
#include "efickmsg.hxx"
|
|
#include "error.hxx"
|
|
#include "ifssys.hxx"
|
|
#include "rtmsg.h"
|
|
#include "ifsentry.hxx"
|
|
#include "fatvol.hxx"
|
|
#include "layout.hxx"
|
|
|
|
#include "efiwintypes.hxx"
|
|
|
|
extern "C" BOOLEAN
|
|
InitializeUfat(
|
|
PVOID DllHandle,
|
|
ULONG Reason,
|
|
PCONTEXT Context
|
|
);
|
|
|
|
extern "C"
|
|
BOOLEAN
|
|
InitializeIfsUtil(
|
|
PVOID DllHandle,
|
|
ULONG Reason,
|
|
PCONTEXT Context
|
|
);
|
|
|
|
int __cdecl
|
|
main(
|
|
int argc,
|
|
WCHAR** argv,
|
|
WCHAR** envp
|
|
);
|
|
|
|
int argc;
|
|
WCHAR ** argv;
|
|
|
|
#if defined(EFI_DEBUG)
|
|
VOID
|
|
PrintLoadedImageInfo (
|
|
IN EFI_LOADED_IMAGE *LoadedImage
|
|
);
|
|
#endif
|
|
|
|
VOID
|
|
InvokeAutoFmtMain (
|
|
IN EFI_HANDLE ImageHandle,
|
|
IN EFI_LOADED_IMAGE *LoadedImage
|
|
);
|
|
|
|
extern "C" {
|
|
|
|
EFI_STATUS
|
|
__declspec(dllexport)
|
|
InitializeEfiFmtApplication(
|
|
IN EFI_HANDLE ImageHandle,
|
|
IN EFI_SYSTEM_TABLE *SystemTable
|
|
)
|
|
{
|
|
EFI_LOADED_IMAGE *LoadedImage;
|
|
|
|
/*
|
|
* Initialize the Library. Set BS, RT, &ST globals
|
|
* BS = Boot Services RT = RunTime Services
|
|
* ST = System Table
|
|
*/
|
|
InitializeLib (ImageHandle, SystemTable);
|
|
InitializeShellApplication( ImageHandle, SystemTable );
|
|
|
|
DEBUG((D_INFO,(CHAR8*)"EFIFMT application started\n"));
|
|
|
|
Print(TEXT("EFI Disk Format Version 0.2\n"));
|
|
Print(TEXT("Based on EFI Core "));
|
|
Print(TEXT("Version %d.%d.%d.%d\n"),
|
|
EFI_SPECIFICATION_MAJOR_REVISION,
|
|
EFI_SPECIFICATION_MINOR_REVISION,
|
|
EFI_FIRMWARE_MAJOR_REVISION,
|
|
EFI_FIRMWARE_MINOR_REVISION
|
|
);
|
|
|
|
BS->HandleProtocol (ImageHandle, &LoadedImageProtocol, (VOID**)&LoadedImage);
|
|
|
|
#if 0
|
|
PrintLoadedImageInfo (LoadedImage);
|
|
#endif
|
|
|
|
// call into autofmt.
|
|
InvokeAutoFmtMain (ImageHandle, LoadedImage);
|
|
|
|
#if 0
|
|
EfiWaitForKey();
|
|
ST->ConOut->OutputString (ST->ConOut, TEXT("\n\n"));
|
|
#endif
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
} // extern "C"
|
|
|
|
UINT16 *MemoryType[] = {
|
|
TEXT("reserved "),
|
|
TEXT("LoaderCode"),
|
|
TEXT("LoaderData"),
|
|
TEXT("BS_code "),
|
|
TEXT("BS_data "),
|
|
TEXT("RT_code "),
|
|
TEXT("RT_data "),
|
|
TEXT("available "),
|
|
TEXT("Unusable "),
|
|
TEXT("ACPI_recl "),
|
|
TEXT("ACPI_NVS "),
|
|
TEXT("MemMapIO "),
|
|
TEXT("MemPortIO "),
|
|
TEXT("PAL_code "),
|
|
TEXT("BUG:BUG: MaxMemoryType")
|
|
};
|
|
|
|
|
|
VOID
|
|
InvokeAutoFmtMain (
|
|
IN EFI_HANDLE ImageHandle,
|
|
IN EFI_LOADED_IMAGE *LoadedImage
|
|
)
|
|
{
|
|
EFI_LOADED_IMAGE *ParentImage;
|
|
|
|
if (!LoadedImage->ParentHandle) {
|
|
/*
|
|
* If you are loaded from the EFI boot manager the ParentHandle
|
|
* is Null. Thus a pure EFI application will not have a parrent.
|
|
*/
|
|
DEBUG((D_INFO,(CHAR8*)"\n%HImage was loaded from EFI Boot Manager%N\n"));
|
|
return;
|
|
}
|
|
|
|
BS->HandleProtocol (LoadedImage->ParentHandle, &LoadedImageProtocol, (VOID**)&ParentImage);
|
|
|
|
#if 0
|
|
Print(TEXT("\n%HImage Parent was %N%s %Hand it passed the following arguments:%N \n %s\n\n"), DevicePathToStr (ParentImage->FilePath),LoadedImage->LoadOptions);
|
|
#endif
|
|
|
|
{
|
|
argc = SI->Argc;
|
|
argv = SI->Argv;
|
|
|
|
DEBUG((D_INFO,(CHAR8*)"Launching main...\n"));
|
|
// call main.
|
|
main(argc,argv,NULL);
|
|
DEBUG((D_INFO,(CHAR8*)"Returned from main...\n"));
|
|
|
|
}
|
|
}
|
|
|
|
#if defined(EFI_DEBUG)
|
|
VOID
|
|
PrintLoadedImageInfo (
|
|
IN EFI_LOADED_IMAGE *LoadedImage
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_DEVICE_PATH *DevicePath;
|
|
|
|
Print(TEXT("\n%HImage was loaded from file %N%s\n"), DevicePathToStr (LoadedImage->FilePath));
|
|
|
|
BS->HandleProtocol (LoadedImage->DeviceHandle, &DevicePathProtocol, (VOID**)&DevicePath);
|
|
if (DevicePath) {
|
|
Print(TEXT("%HImage was loaded from this device %N%s\n"), DevicePathToStr (DevicePath));
|
|
|
|
}
|
|
|
|
Print(TEXT("\n Image Base is %X"), LoadedImage->ImageBase);
|
|
Print(TEXT("\n Image Size is %X"), LoadedImage->ImageSize);
|
|
Print(TEXT("\n Image Code Type %s"), MemoryType[LoadedImage->ImageCodeType]);
|
|
Print(TEXT("\n Image Data Type %s"), MemoryType[LoadedImage->ImageDataType]);
|
|
Print(TEXT("\n %d Bytes of Options passed to this Image\n"), LoadedImage->LoadOptionsSize);
|
|
|
|
if (LoadedImage->ParentHandle) {
|
|
Status = BS->HandleProtocol (LoadedImage->ParentHandle, &DevicePathProtocol, (VOID**)&DevicePath);
|
|
if (Status == EFI_SUCCESS && DevicePath) {
|
|
Print(TEXT("Images parent is %s\n\n"), DevicePathToStr (DevicePath));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int __cdecl
|
|
main(
|
|
int argc,
|
|
WCHAR** argv,
|
|
WCHAR** envp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is the main program for AutoFmt
|
|
|
|
Arguments:
|
|
|
|
argc, argv - Supplies the fully qualified NT path name of the
|
|
the drive to check. The syntax of the autofmt
|
|
command line is:
|
|
|
|
AUTOFMT drive-name /FS:target-file-system [/V:label] [/Q] [/A:size] [/C]
|
|
[/S]
|
|
|
|
Return Value:
|
|
|
|
0 - Success.
|
|
1 - Failure.
|
|
|
|
--*/
|
|
{
|
|
if (!InitializeUlib( NULL, ! DLL_PROCESS_DETACH, NULL ) ||
|
|
!InitializeIfsUtil(NULL, ! DLL_PROCESS_DETACH, NULL) ||
|
|
!InitializeUfat(NULL, ! DLL_PROCESS_DETACH, NULL)
|
|
) {
|
|
return 1;
|
|
}
|
|
|
|
PFAT_VOL fat_volume;
|
|
PDP_DRIVE dp_drive;
|
|
|
|
EFICHECK_MESSAGE *message = NULL;
|
|
DSTRING drive_name;
|
|
DSTRING file_system_name;
|
|
DSTRING label;
|
|
DSTRING* plabel;
|
|
DSTRING fat_name;
|
|
DSTRING fat32_name;
|
|
DSTRING raw_name;
|
|
BOOLEAN quick = FALSE;
|
|
BOOLEAN compress = FALSE;
|
|
FORMAT_ERROR_CODE success;
|
|
BOOLEAN setup_output = FALSE;
|
|
BOOLEAN textmode_output = FALSE;
|
|
BOOLEAN drive_already_specified = FALSE;
|
|
BOOLEAN force_filesystem = FALSE;
|
|
BOOLEAN showhelp = FALSE;
|
|
BOOLEAN labelled = FALSE;
|
|
BIG_INT bigint;
|
|
ULONG cluster_size = 0;
|
|
int i;
|
|
NTSTATUS result;
|
|
|
|
if (!(perrstk = NEW ERRSTACK)) {
|
|
return 1;
|
|
}
|
|
|
|
if (!file_system_name.Initialize()) {
|
|
return 1;
|
|
}
|
|
if (!label.Initialize() || NULL == (dp_drive = NEW DP_DRIVE)) {
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// Parse the arguments.
|
|
//
|
|
|
|
// if ( argc < 2 ) {
|
|
// return 1;
|
|
// }
|
|
|
|
message = NEW EFICHECK_MESSAGE;
|
|
|
|
if (NULL == message || !message->Initialize()) {
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// The rest of the arguments are flags.
|
|
//
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
if (((argv[i][0] == '/' || argv[i][0] == '=') &&
|
|
(argv[i][1] == '?' )) || showhelp
|
|
) {
|
|
showhelp = TRUE;
|
|
}
|
|
else if ((argv[i][0] == '/' || argv[i][0] == '-') &&
|
|
(argv[i][1] == 'f' || argv[i][1] == 'F') &&
|
|
(argv[i][2] == 's' || argv[i][2] == 'S') &&
|
|
(argv[i][3] == ':')) {
|
|
|
|
if (!file_system_name.Initialize(&argv[i][4])) {
|
|
return 1;
|
|
}
|
|
force_filesystem = TRUE;
|
|
DEBUG((D_INFO,(CHAR8*)"fsname: %ws\n", file_system_name.GetWSTR()));
|
|
}
|
|
else if ((argv[i][0] == '/' || argv[i][0] == '-') &&
|
|
(argv[i][1] == 'v' || argv[i][1] == 'V') &&
|
|
(argv[i][2] == ':')) {
|
|
|
|
if (!label.Initialize(&argv[i][3])) {
|
|
return 1;
|
|
}
|
|
labelled = TRUE;
|
|
}
|
|
else if ((argv[i][0] == '/' || argv[i][0] == '-') &&
|
|
(argv[i][1] == 'a' || argv[i][1] == 'A') &&
|
|
(argv[i][2] == ':')) {
|
|
|
|
// Check to see if the size is specified in Kb
|
|
if ((argv[i][StrLen(argv[i])-1] == 'k') || (argv[i][StrLen(argv[i])-1] == 'K')) {
|
|
argv[i][StrLen(argv[i])-1] = '\0'; // remove the K and null terminate the string
|
|
cluster_size = (ULONG)Atoi(&argv[i][3]) * 1024;
|
|
}
|
|
else {
|
|
cluster_size = (ULONG)Atoi(&argv[i][3]);
|
|
}
|
|
}
|
|
else if (0 == StriCmp(argv[i], TEXT("/Q")) || 0 == StriCmp(argv[i], TEXT("-Q"))) {
|
|
quick = TRUE;
|
|
}
|
|
else if (argv[i][0] != '/' && argv[i][0] != '-') {
|
|
// it's not a switch, assume it is the drive
|
|
// we don't allow two drive at the same time
|
|
if( drive_already_specified ) {
|
|
message->Set(MSG_FMT_INVALID_DRIVE_SPEC);
|
|
message->Display();
|
|
return 1;
|
|
}
|
|
drive_already_specified = TRUE;
|
|
if ( !drive_name.Initialize( argv[i] ) ) {
|
|
return 1;
|
|
}
|
|
DEBUG((D_INFO,(CHAR8*)"drive name: %ws\n", drive_name.GetWSTR()));
|
|
}
|
|
else {
|
|
// its not any vaild option we know of
|
|
message->Set(MSG_INVALID_PARAMETER);
|
|
message->Display("%ws",argv[i]);
|
|
message->Set(MSG_BLANK_LINE);
|
|
message->Display();
|
|
showhelp = TRUE;
|
|
}
|
|
}
|
|
|
|
DEBUG((D_INFO,(CHAR8*)"Parsed Args.\n"));
|
|
|
|
if(showhelp || !drive_already_specified) {
|
|
message->Set(MSG_FORMAT_INFO);
|
|
message->Display();
|
|
message->Set(MSG_FORMAT_COMMAND_LINE_1);
|
|
message->Display();
|
|
message->Set(MSG_FORMAT_COMMAND_LINE_2);
|
|
message->Display();
|
|
message->Set(MSG_FORMAT_COMMAND_LINE_4);
|
|
message->Display();
|
|
message->Set(MSG_FORMAT_SLASH_V);
|
|
message->Display();
|
|
message->Set(MSG_FORMAT_SLASH_Q);
|
|
message->Display();
|
|
message->Set(MSG_FORMAT_SLASH_F);
|
|
message->Display();
|
|
message->Set(MSG_FORMAT_SUPPORTED_SIZES);
|
|
message->Display();
|
|
|
|
return 1;
|
|
}
|
|
|
|
textmode_output = TRUE;
|
|
|
|
#if 0 // Shouldn't limit the cluster size as long as it is reasonable.
|
|
if (cluster_size != 0 && cluster_size != 512 && cluster_size != 1024 &&
|
|
cluster_size != 2048 && cluster_size != 4096) {
|
|
|
|
message->Set(MSG_UNSUPPORTED_PARAMETER);
|
|
message->Display();
|
|
|
|
DeRegister( argc, argv );
|
|
|
|
return 1;
|
|
}
|
|
#endif
|
|
DEBUG((D_INFO,(CHAR8*)"Determining file system type.\n"));
|
|
|
|
if (0 == file_system_name.QueryChCount()) {
|
|
|
|
// attempt to get the current filesystem type from disk
|
|
|
|
if (!IFS_SYSTEM::QueryFileSystemName(&drive_name, &file_system_name, &result, NULL)) {
|
|
|
|
message->Set( MSG_FS_NOT_DETERMINED );
|
|
message->Display( "%W", &drive_name );
|
|
|
|
DEBUG((D_ERROR,(CHAR8*)"Unable to determine file system type.\n"));
|
|
|
|
if(result != 0) {
|
|
message->Set(MSG_CANT_DASD);
|
|
message->Display();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
file_system_name.Strupr();
|
|
}
|
|
|
|
if (!fat_name.Initialize("FAT") ||
|
|
!fat32_name.Initialize("FAT32") ||
|
|
!raw_name.Initialize("RAW") ) {
|
|
|
|
return 1;
|
|
}
|
|
|
|
file_system_name.Strupr();
|
|
|
|
//
|
|
// If compression is requested, make sure it's available.
|
|
//
|
|
#if 0
|
|
if (compress) {
|
|
|
|
message->Set(MSG_COMPRESSION_NOT_AVAILABLE);
|
|
message->Display("%W", &file_system_name);
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
DEBUG((D_INFO,(CHAR8*)"Init DP_DRIVE.\n"));
|
|
if (!dp_drive->Initialize(&drive_name, message)) {
|
|
DEBUG((D_ERROR,(CHAR8*)"Failed Init DP_DRIVE.\n"));
|
|
return 1;
|
|
}
|
|
DEBUG((D_INFO,(CHAR8*)"Init DP_DRIVE success.\n"));
|
|
|
|
message->Set(MSG_WARNING_FORMAT);
|
|
message->Display("%W", &drive_name);
|
|
if (!message->IsYesResponse(FALSE)) {
|
|
return 5;
|
|
}
|
|
|
|
if (dp_drive->IsFloppy()) {
|
|
DEBUG((D_INFO,(CHAR8*)"**** Is a floppy.\n"));
|
|
// return 1; BUGBUG should i fail this?
|
|
}
|
|
|
|
//
|
|
// Print the "formatting <size>" message.
|
|
//
|
|
|
|
if (quick) {
|
|
message->Set(MSG_QUICKFORMATTING_MB);
|
|
} else {
|
|
message->Set(MSG_FORMATTING_MB);
|
|
}
|
|
|
|
if( labelled == FALSE ) {
|
|
plabel = NULL;
|
|
} else {
|
|
plabel = &label;
|
|
}
|
|
|
|
DEBUG((D_INFO,(CHAR8*)"QuerySector count.\n"));
|
|
|
|
bigint = dp_drive->QuerySectors() * dp_drive->QuerySectorSize() /
|
|
1048576;
|
|
|
|
DEBUG((D_INFO,(CHAR8*)"Sector count is %ld.\n",dp_drive->QuerySectors()));
|
|
DEBUG((D_INFO,(CHAR8*)"Sector size is %d.\n",dp_drive->QuerySectorSize()));
|
|
|
|
DebugAssert(bigint.GetHighPart() == 0);
|
|
|
|
message->Display("%d", bigint.GetLowPart());
|
|
|
|
#if 0
|
|
PART_DESCRIPTOR partdes;
|
|
UINT32 fatsize;
|
|
|
|
|
|
// if the user hasn't forced a filesystem
|
|
if( force_filesystem == FALSE ) {
|
|
// if the disk is big enough to put at least FAT16 on it
|
|
if (dp_drive->QuerySectors() >= (ULONG)MinSectorsFat16(dp_drive->QuerySectorSize())) {
|
|
DEBUG((D_INFO,(CHAR8*)"Drive is big enough for FAT16 >= %d.\n",MinSectorsFat16(dp_drive->QuerySectorSize())));
|
|
partdes.SectorCount = (dp_drive->QuerySectors()).GetQuadPart();
|
|
partdes.SectorSize = dp_drive->QuerySectorSize();
|
|
partdes.HeaderCount = HEADER_F16;
|
|
partdes.FatEntrySize = 2;
|
|
partdes.MinClusterCount = MIN_CLUSTER_F16;
|
|
partdes.MaxClusterCount = MAX_CLUSTER_F16;
|
|
partdes.FatType = FAT_TYPE_F16;
|
|
file_system_name.Initialize(fat_name.QueryWSTR());
|
|
|
|
// if this disk is big enough for FAT32
|
|
if (dp_drive->QuerySectors() >= (ULONG)MinSectorsFat32(dp_drive->QuerySectorSize())) {
|
|
DEBUG((D_INFO,(CHAR8*)"Drive is big enough for FAT32 >= %d\n",MinSectorsFat32(dp_drive->QuerySectorSize())));
|
|
partdes.SectorCount = (dp_drive->QuerySectors()).GetQuadPart();
|
|
partdes.SectorSize = dp_drive->QuerySectorSize();
|
|
partdes.HeaderCount = HEADER_F32;
|
|
partdes.FatEntrySize = 4;
|
|
partdes.MinClusterCount = MIN_CLUSTER_F32;
|
|
partdes.MaxClusterCount = MAX_CLUSTER_F32;
|
|
partdes.FatType = FAT_TYPE_F32;
|
|
file_system_name.Initialize(fat32_name.QueryWSTR());
|
|
}
|
|
|
|
// if the user hasn't forced a cluster size, select an appropriate one for him
|
|
if( cluster_size==0 ) {
|
|
PickClusterSize(&partdes,(UINT32*)&cluster_size,&fatsize); // note this routine doesn't work for FAT12
|
|
|
|
cluster_size = cluster_size * dp_drive->QuerySectorSize(); // PickClusterSize return cluster size in sectors
|
|
DEBUG((D_INFO,(CHAR8*)"User hasn't forced a cluster size, picking %d\n",cluster_size));
|
|
}
|
|
} else {
|
|
// do the default, which is going to be FAT12 and whatever regular format picks.
|
|
file_system_name.Initialize(fat_name.QueryWSTR());
|
|
}
|
|
} else {
|
|
// if the drive is big enough for FAT16
|
|
if (dp_drive->QuerySectors() >= (ULONG)MinSectorsFat16(dp_drive->QuerySectorSize())) {
|
|
// the user forced a file system, did he force a cluster size?
|
|
if( cluster_size==0 ) {
|
|
// nope, setup and pick a good size
|
|
partdes.SectorCount = (dp_drive->QuerySectors()).GetQuadPart();
|
|
partdes.SectorSize = dp_drive->QuerySectorSize();
|
|
|
|
if(file_system_name == fat_name) {
|
|
partdes.HeaderCount = HEADER_F16;
|
|
partdes.FatEntrySize = 2;
|
|
partdes.MinClusterCount = MIN_CLUSTER_F16;
|
|
partdes.MaxClusterCount = MAX_CLUSTER_F16;
|
|
partdes.FatType = FAT_TYPE_F16;
|
|
} else if(file_system_name == fat32_name){
|
|
partdes.HeaderCount = HEADER_F32;
|
|
partdes.FatEntrySize = 4;
|
|
partdes.MinClusterCount = MIN_CLUSTER_F32;
|
|
partdes.MaxClusterCount = MAX_CLUSTER_F32;
|
|
partdes.FatType = FAT_TYPE_F32;
|
|
}
|
|
PickClusterSize(&partdes,(UINT32*)&cluster_size,&fatsize); // note this routine doesn't work for FAT12
|
|
cluster_size = cluster_size * dp_drive->QuerySectorSize(); // PickClusterSize return cluster size in sectors
|
|
DEBUG((D_INFO,(CHAR8*)"FS type forced, user hasn't forced a cluster size, picking %d\n",cluster_size));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (file_system_name == fat_name || file_system_name == fat32_name) {
|
|
|
|
BOOLEAN old_fat_vol = TRUE;
|
|
|
|
DEBUG((D_INFO,(CHAR8*)"This is an old FAT volume.\n"));
|
|
|
|
if( file_system_name == fat32_name ) {
|
|
old_fat_vol = FALSE;
|
|
}
|
|
|
|
if( !(fat_volume = NEW FAT_VOL) ||
|
|
NoError != fat_volume->Initialize( &drive_name, message, FALSE, !quick,
|
|
Unknown )) {
|
|
DEBUG((D_ERROR,(CHAR8*)"fat_volume init failed.\n"));
|
|
//return 1;
|
|
}
|
|
|
|
DEBUG((D_INFO,(CHAR8*)"fat_volume init succeeded.\n"));
|
|
|
|
success = fat_volume->Format(plabel,
|
|
message,
|
|
old_fat_vol ? FORMAT_BACKWARD_COMPATIBLE : 0,
|
|
cluster_size);
|
|
|
|
DEBUG((D_INFO,(CHAR8*)"Format return code: %d\n", success));
|
|
|
|
DELETE( fat_volume );
|
|
|
|
} else {
|
|
|
|
DEBUG((D_ERROR,(CHAR8*)"fs not supported.\n"));
|
|
|
|
message->Set( MSG_FS_NOT_SUPPORTED );
|
|
message->Display( "%s%W", "EFIFMT", &file_system_name);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return (success != NoError);
|
|
}
|
|
|