home *** CD-ROM | disk | FTP | other *** search
- /*
-
- DF.C - A utility to format a disk.
-
- This routine was developed for use on a Palomax hard drive system,
- which did not support bad sector re-mapping. It creates a file
- called 'bad.blocks' which contains a valid file header with links
- to all of the bad blocks. It will work under both filing systems,
- and for hard and floppy drives. If nothing else, it has a better
- syntax than AmigaDos's, and hopefully, provides some examples of
- working with the filing system.
-
- Note: This utility will work with the Maximum drive size
- currently supported by Palomax software, (2048 tracks and
- 16 heads) and then some.
-
- Note also: Some of the error messages are meant mainly for hard
- drives, but should still be informative.
- */
-
-
- #include <exec/io.h>
- #include <exec/devices.h>
- #include <exec/memory.h>
- #include <devices/trackdisk.h>
- #include <devices/bootblock.h>
- #include <libraries/dosextens.h>
- #include <libraries/filehandler.h>
- #include "df.h"
-
-
-
- char Usage[] =
- "<device name (df0: etc)> <options> [<volume name>]\n\
- Where <options> is one or more of the following:\n\
- QUICK FORMAT\t\t -q\n\
- NO VERIFY\t\t -nv\n\
- PRINT BAD BLOCK LIST\t -p\n\
- UPDATE BAD BLOCK FILE\t -u\n\
- USE FAST FILING SYSTEM\t -ffs\n\
- ENTER BAD TRACK LIST\t -t\n";
-
-
- char Greeting[] =
- " Amiga disk foramatter\n\
- Copyright 1989 SLADE software\n\n";
-
-
- char *my_name; /* Invocation name */
- struct MsgPort *diskport; /* Reply port for I/O requests */
- struct IOStdReq *diskreq; /* Request struct for I/O */
- char *dsk_buf; /* buffer to hold info from dsk */
- long doe; /* Device open error flag */
- int dev_open; /* Device open flag */
- int drive_inhibited;/* Flag for inhibit operation */
- int bad_tracks; /* Number of bad tracks */
-
- int dformat; /* Flag to indicate format */
- int update; /* Flag to indicate just update */
- int quick_format; /* Quick format flag... */
- int no_verify; /* Format w/no verify flag.. */
- int report; /* Report on bad blocks only */
- int enter_tracks; /* Enter own bad tracks flag */
-
- struct RootBlock rb; /* Root block structure */
- struct disk_parm dp; /* disk_parm structure */
-
- extern void update_bad_blocks();
- extern void create_bad_block_file();
- extern void print_bad_blocks();
- extern int block_already_bad();
-
- struct bad_block *bad_block_list; /* Used block list pointer */
- int bad_blocks; /* Number of used/bad blocks */
- void add_bad_block(); /* add bad block routine */
-
- long bad_track_list[BADTRKSIZE]; /* Bad track list */
- char *device; /* Device (df0: etc) to use */
- char volname[40]; /* Supplied volume name */
-
- extern struct DosLibrary *DOSBase;
- int def_PreAlloc;
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- BYTE write_blocks( block_start, count )
- ULONG block_start;
- int count;
- {
- /*
- Write count blocks to the disk. block_start contains the
- the actual block number to write. We do not check the
- range here. The write will be immediate with no buffering
- because we do a CMD_UPDATE after the write...
- */
-
- diskreq->io_Length = dp.block_size*count;
- diskreq->io_Data = (APTR) dsk_buf;
- diskreq->io_Command = CMD_WRITE;
- diskreq->io_Offset = block_start*dp.block_size;
- DoIO( diskreq );
- if ( diskreq->io_Error )
- return( (diskreq->io_Error == 0) );
-
- diskreq->io_Command = CMD_UPDATE;
- DoIO( diskreq );
- return( (diskreq->io_Error == 0) );
- }
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- BYTE read_blocks( block_start, count )
- ULONG block_start;
- int count;
- {
- /*
- Read count blocks from the file. block_start contains the
- the actual starting offset of the block to read. We do not
- check the range here!
- */
-
- diskreq->io_Length = dp.block_size*count;
- diskreq->io_Data = (APTR) dsk_buf;
- diskreq->io_Command = CMD_READ;
- diskreq->io_Offset = block_start;
- DoIO( diskreq );
- return( (diskreq->io_Error == 0) );
- }
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- void motor_off()
- {
- /*
- Turn the drive motor off. (only valid for 3.5's)...
- */
- diskreq->io_Length = 0;
- diskreq->io_Command = TD_MOTOR;
- DoIO( diskreq );
- }
-
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- void inhibit_drive( yorn )
- int yorn;
- {
- /*
- Depending on the value of yorn, inhibit or enable the drive.
- */
-
- void df_exit();
- struct MsgPort *dev_proc, *DeviceProc();
-
- /*
- Find the device handling this drive, and complain if it
- does not exist.
- */
- if ( !(dev_proc = DeviceProc( device ) ) )
- {
- df_exit( EBADDEV );
- }
-
- /*
- Tell DOS to inhibit the drive.
- */
- if ( !dos_packet( dev_proc, ACTION_INHIBIT, (long) yorn,
- 0L, 0L, 0L, 0L, 0L, 0L, 0L ) )
- {
- df_exit( ENOINH );
- }
-
- drive_inhibited = yorn;
- }
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- BYTE verify_track( track )
- ULONG track;
- {
- /*
- Do a read-verify of the track we just formatted. If no_verify
- is true, just return true...
- */
- long track_start;
-
- if ( no_verify )
- return( 1 );
-
- track_start = track * dp.track_size;
-
- read_blocks( track_start, dp.blocks_track );
- return( (diskreq->io_Error == 0) );
- }
-
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- BYTE format_track( track )
- ULONG track;
- {
- /*
- Tell the device driver to format the supplied track. A track
- is defined by disk_parm.track_size.
- */
-
- diskreq->io_Length = dp.track_size;
- diskreq->io_Data = (APTR) dsk_buf;
- diskreq->io_Command = TD_FORMAT;
- diskreq->io_Offset = track * dp.track_size;
- DoIO( diskreq );
-
- return( (diskreq->io_Error == 0) );
- }
-
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- void format_disk()
- {
- /*
- Format the disk a track at a time from low_cyl to high_cyl on
- dp.surfaces heads. If we come to a track that is already in the
- bad_track_list, skip the format. If we find a new bad track,
- add it to the list, and make sure the list is not full.
- */
- void df_exit();
- void initialize_disk();
- ULONG i, j, cur_track;
-
-
- inhibit_drive( 1 );
-
- /*
- We have several options here: Normal format and verify,
- format without verify, and quick format (write root and
- boot blocks only). Test for quick first...
- */
- if ( quick_format )
- {
- initialize_disk();
- return;
- }
-
- for( i = dp.low_cyl; i <= dp.high_cyl; i++ )
- for ( j = 0; j < dp.surfaces; j++ )
- {
- cur_track = (i * dp.surfaces) + j;
-
- if ( track_already_bad( cur_track ) )
- {
- printf( "Head %ld, Track %ld already marked as bad - skipping\n",
- j, i );
- continue;
- }
-
- if ( !format_track( cur_track ) || !verify_track( cur_track ) )
- {
- bad_track_list[bad_tracks++] = cur_track;
- if ( bad_tracks > BADTRKSIZE )
- {
- df_exit( EBTOV );
- }
-
- printf( "Unable to format head: %ld, track: %ld ", j, i );
- printf( "Adding to bad track list\n" );
- }
-
- }
-
- /*
- Initialize the disk...
- */
- initialize_disk();
- }
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- void create_bad_block_list()
- {
- /*
- Take the bad track table and convert the tracks to blocks.
- The first block on a track will be (blocks_track * track),
- then we just count up to blocks_track to get the rest.
- */
- int i, j;
- ULONG current_block;
-
- for( i = 0, bad_blocks = 0; i < bad_tracks; i++ )
- {
- current_block = bad_track_list[i] * dp.blocks_track;
-
- for( j = 0; j < dp.blocks_track; j++ )
- {
- add_bad_block( current_block++ );
- bad_blocks++;
- }
- }
- }
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- void write_boot_block()
- {
- /*
- Create the boot block and write it to the disk. We will first
- check to see if it was found to be bad during the format. If
- so, we will inform the user and quit. Also, if we get a write
- error (in case we only did a read verify), we will quit.
- */
- void df_exit();
- int i;
-
- if ( block_already_bad( dp.boot_key ) )
- {
- df_exit( EBADBB );
- }
-
- switch( dp.disk_type )
- {
- case ROMFS : strcpy( dsk_buf, "DOS" );
- break;
-
- case FFS : strcpy( dsk_buf, "DOS\001" );
- }
-
- if ( !write_blocks( dp.boot_key, 1 ) )
- {
- df_exit( EBADBB );
- }
- }
-
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- void initialize_root_block()
- {
- /*
- Initialize the known fields in the root block.
- */
-
- rb.Type = 2L;
- rb.SecondaryType = 1L;
- rb.HTSize = 72L;
-
- /*
- Set the BimapFlag to 0, so the validator will come and
- create the bitmap for us.....
- */
- rb.BitmapFlag = 0L;
-
- /*
- Copy the volume name to the root block.
- */
- rb.Name[0] = strlen( volname );
- strcpy( &rb.Name[1], volname );
-
- /*
- Create and initialize all the pertinent date fields: DiskMade,
- DirAltered, and DiskAltered. I set these to the same value due
- to ignorance. If they should be different, feel free to change
- this.
- */
- DateStamp( &rb.DiskMade[0] );
- movmem( (char *) &rb.DiskMade, (char *) &rb.DirAltered, 12);
- movmem( (char *) &rb.DiskMade, (char *) &rb.DiskAltered, 12);
- }
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- void write_root_block()
- {
- /*
- Calculate the checksum for the root block and attempt to write
- it to the disk.
- */
- int i;
- long temp, *p;
-
- /*
- Calculate the checksum for this block.
- */
- for( i = 0, temp = 0, p = (long *) &rb; i < (sizeof( rb )/4); i++ )
- {
- temp += *p++;
- }
- rb.Checksum = -temp;
-
-
- /*
- Copy it to the disk buffer and write it.
- */
- movmem( (char *) &rb, dsk_buf, sizeof( rb ) );
- if ( !write_blocks( dp.root_key, 1 ) )
- {
- df_exit( EBADRB );
- }
- }
-
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- void initialize_disk()
- {
- /*
- Attempt to write the boot block, root block and bad block file.
- each routine will handle its own errors. We do not mess with the
- bitmap for the disk at all. We set the 'not validated' field of
- the root block, and when we un-inibit() the drive, AmigaDOS will
- go out and create it for us. Not the most efficient thing to do,
- but it beats writing the code to do it by hand...
- */
- create_bad_block_list(); /* translate tracks to blocks */
- write_boot_block(); /* write the boot block */
- initialize_root_block(); /* set some fields in root block*/
-
- /*
- Create and write the bad block file if we have any bad blocks.
- */
- if ( bad_block_list )
- create_bad_block_file();
-
- write_root_block(); /* update and write the root blk*/
- }
-
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- get_drive_type()
- {
- /*
- If we are going to format the drive, its type will be ROMFS by
- default, unless the user specified -ffs. If we are just going
- to do an update, we need to read the boot block to get the type.
- */
- void df_exit();
-
- if ( update )
- {
- if ( !read_blocks( dp.boot_key, 1 ) )
- {
- df_exit( EBADBB );
- }
-
- dp.disk_type = (int) dsk_buf[3];
- }
-
- /*
- Get the pre-allocated block count.
- */
- dp.pre_alloc = (dp.disk_type == ROMFS) ? 0 : def_PreAlloc;
-
- /*
- dp.lower_key is the lowest block on the drive. It is usually 2,
- because AmigaDOS reserves the lowest two for the boot blocks -
- also because of the way the bitmap is structured, we cannot use
- block 0 - a value of 0 in the bitmap indicates a used block.
- */
- dp.lower_key = dp.low_cyl * dp.blocks_track * dp.surfaces +
- dp.reserved;
-
- dp.upper_key = ((dp.high_cyl + 1) * dp.surfaces * dp.blocks_track) -
- ( dp.pre_alloc + 1 );
-
- }
-
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- get_drive_stats( drive )
- char *drive;
- {
- /*
- Get the needed drive data from the environment vector. This is also
- the DevInfo block masquerading under a different name. It is not too
- well documented, and I have TransAmi to thank for this little tidbit.
- */
-
- struct RootNode *rn;
- struct DosInfo *dsi;
- struct DeviceNode *di;
- struct FileSysStartupMsg *fsm;
- struct DosEnvironment *de;
- char dev[32], *p, *q;
-
- for( p = drive, q = dev; (*p != ':') && *p; )
- *q++ = toupper( *p++ );
-
- *q = '\0';
-
- if ( !p )
- {
- return( 0 );
- }
-
- Forbid();
- rn = (struct RootNode *) DOSBase->dl_Root;
- dsi= (struct DosInfo *) BADDR( rn->rn_Info );
- di = (struct DeviceNode *) BADDR( dsi->di_DevInfo );
-
- /*
- Attempt to find the device in the list.
- */
- for( ; di->dn_Next; di = (struct DeviceNode *) BADDR( di->dn_Next ) )
- {
- if ( !strcmp( BADDR( di->dn_Name )+1, dev ) )
- break;
- }
-
- if ( !di ) /* device not found */
- {
- Permit();
- return( 0 );
- }
-
-
- fsm = (struct FileSysStartupMsg *) BADDR( di->dn_Startup );
- de = (struct DosEnvironment *) BADDR( fsm->fssm_Environ );
-
- /*
- Copy the pertinent fields.
- */
- dp.unit = fsm->fssm_Unit; /* Unit number */
- dp.block_size = de->de_SizeBlock * 4; /* Block size */
- dp.surfaces = de->de_Surfaces; /* Sides */
- dp.sec_block = de->de_SecPerBlock; /* Sectors/block */
- dp.blocks_track = de->de_BlocksPerTrack; /* Blocks/track */
- dp.reserved = de->de_Reserved; /* Reserved blocks */
- dp.low_cyl = de->de_LowCyl; /* Low cylinder */
- dp.high_cyl = de->de_HighCyl; /* High cylinder */
-
- /*
- Copy the name of the driver to dp.driver.
- */
- p = (char *) BADDR( fsm->fssm_Device );
- strncpy( dp.driver, p + 1, (int) *p );
-
- dp.track_size = dp.block_size * dp.blocks_track;
-
- dp.root_key = ((dp.high_cyl + 1) * dp.blocks_track * dp.surfaces)/2;
- dp.boot_key = dp.low_cyl * dp.surfaces * dp.blocks_track;
-
- def_PreAlloc = de->de_PreAlloc;
- Permit();
- return( 1 );
- }
-
-
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- void init_bad_track_list()
- {
- /*
- Initialize the bad track list to -1's.
- */
- int i;
-
- for( i = 0; i < BADTRKSIZE; i++ )
- bad_track_list[i] = -1L;
- }
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- void get_bad_tracks()
- {
- /*
- Prompt the user for a list of bad tracks.
- */
- char tmp[10];
- ULONG cur_track, head, track;
-
- printf( "Enter bad tracks - enter q to quit\n" );
-
- for ( ; ; )
- {
- printf( "Head " );
- gets( tmp );
- if ( toupper( *tmp ) == 'Q' )
- break;
-
- head = atol( tmp );
- printf( "Track " );
- gets( tmp );
- track = atol( tmp );
-
- cur_track = (track * dp.surfaces) + head;
-
-
- bad_track_list[bad_tracks++] = cur_track;
- if ( bad_tracks > BADTRKSIZE )
- {
- df_exit( EBTOV );
- }
- }
-
- }
-
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- int track_already_bad( track )
- ULONG track;
- {
- /*
- Cruise through the bad_track_list and see if the given track
- is in it. return the result of the search.
- */
- int i;
-
- for( i = 0; i < BADTRKSIZE; i++ )
- {
- if ( bad_track_list[i] == track )
- return( 1 );
- }
- return( 0 );
- }
-
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- void df_exit( rc, err )
- int rc;
- char *err;
- {
- /*
- This is the function that everyone exits by. We will
- close or return allocated resources and quit gracefully
- on error.
- */
-
- if ( (dev_open) && (dp.disk_type == ROMFS) )
- motor_off();
-
- if ( dev_open )
- CloseDevice( diskreq );
-
- if ( diskport )
- DeletePort( diskport );
-
- if ( diskreq )
- DeleteStdIO( diskreq );
-
- if ( dsk_buf )
- FreeMem( dsk_buf, (long) dp.track_size );
-
- if ( drive_inhibited )
- inhibit_drive( 0 );
-
- if ( !rc )
- exit( rc );
-
-
- if ( rc == ENOARGS )
- {
- printf( "Usage: %s %s\n", my_name, Usage );
- exit( 200 );
- }
-
- /* Tell user where the error came from */
- printf( "%s:", my_name );
-
- switch ( rc )
- {
- case ENOPORT : printf( "Cannot open reply port\n" );
- break;
-
- case ENOIOBLK : printf( "Cannot create I/O block\n" );
- break;
-
- case ENOOPEN : printf( "Cannot open device - error: %x\n", doe );
- break;
-
- case EBADDEV : printf( "Illegal device name\n" );
- break;
-
- case EBTOV : printf( "Bad track table overflow\n" );
- break;
-
- case ENOMEM : printf( "Unable to allocate memory\n" );
- break;
-
- case EBADBB : printf( "The boot block is not writeable. " );
- printf( "If this is a Mounted disk, you\n");
- printf( "must increase the LowCyl value " );
- printf( "in the Mountlist to format this ");
- printf( "disk.\n" );
- break;
-
- case EBADRB : printf( "The root block is not writeable. " );
- printf( "You must change either the low_cyl\n" );
- printf( "or high_cyl value in the Mountlist " );
- printf( "to format this disk\n" );
- break;
-
- case ENOBLKS : printf( "No more room to create bad " );
- printf( "block list\n" );
- break;
-
- case EBMAPWR : printf( "Another bad block found while " );
- printf( "writing the bitmap - please " );
- printf( "re-run\n" );
- break;
-
- case EBADOPT : printf( "Bad option: %s Halting\n", err );
- break;
-
- case ENOINH : printf( "Unable to inhibit drive\n" );
- break;
-
- case ENHDR : printf( "Unable to write file header block " );
- printf( "to the disk - format failed\n" );
- break;
-
- case ENEXTB : printf( "Unable to write file extension " );
- printf( "blocks to the disk - format failed\n");
- break;
-
- case EBADBLK : printf( "Block number out of range\n" );
- break;
- }
-
-
- exit( 200 );
- }
-
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- void parse_args( arg_list )
- char **arg_list;
- {
- /*
- See if we got any flags, and assign some global variables. Assume
- a normal format (dformat = True) if no args are supplied.
- */
- dformat = 1;
- report =
- no_verify =
- update =
- enter_tracks =
- quick_format = 0;
- dp.disk_type = ROMFS; /* default */
-
- /*
- The device name MUST be next...
- */
- device = *arg_list;
-
- /*
- Now all we have left is flags and possibly a volume name.
- */
-
- while( *++arg_list )
- {
- if (!strcmp( *arg_list, "-u" ) )
- {
- update = 1;
- continue;
- }
-
- if (!strcmp( *arg_list, "-p" ) )
- {
- report = 1;
- continue;
- }
-
- if (!strcmp( *arg_list, "-ffs" ) )
- {
- dp.disk_type = FFS;
- continue;
- }
-
- if (!strcmp( *arg_list, "-q" ) )
- {
- quick_format = 1;
- continue;
- }
-
- if (!strcmp( *arg_list, "-nv" ) )
- {
- no_verify = 1;
- continue;
- }
-
- if (!strcmp( *arg_list, "-t" ) )
- {
- enter_tracks = 1;
- continue;
- }
-
- if (!strlen( volname ) )
- {
- strcpy( volname, *arg_list );
- continue;
- }
-
- /*
- If we get here, our illustrious user has entered a
- bad option. We will quit in case this was a typo.
- */
- df_exit( EBADOPT, *arg_list );
- }
-
- /*
- Assign a default volume name of "empty" if none was specified.
- */
- if ( !strlen( volname ) )
- strcpy( volname, "empty" );
- }
-
-
-
-
- /*-----------------------------------------------------------------------------
- ------------------------------------------------------------------------------*/
- main( argc, argv )
- int argc;
- char **argv;
-
- {
- struct MsgPort *CreatePort();
- struct IOStdReq *CreateStdIO();
- char *AllocMem();
- long OpenDevice();
- int i;
-
- /*
- Say hello to joe user, and get our invocation name for later
- use.
- */
- printf( "%s", Greeting );
- my_name = *argv;
-
- /*
- Make sure we got enough arguments..
- */
- if ( argc < 2 )
- {
- df_exit( ENOARGS );
- }
-
- parse_args( ++argv );
-
-
-
- if (! (diskport = CreatePort( 0L, 0L )) )
- {
- df_exit( ENOPORT );
- }
-
- if (! (diskreq = CreateStdIO( diskport )) )
- {
- df_exit( ENOIOBLK );
- }
-
-
- if (! get_drive_stats( device ) )
- {
- df_exit( EBADDEV );
- }
-
- if ( (doe = OpenDevice( dp.driver, (long) dp.unit, diskreq, 0L ) ) )
- {
- df_exit( ENOOPEN );
- }
-
- dev_open = 1;
-
-
- /*
- Create the buffer used to hold information to write to
- the disk during a format, and used in reads.
- */
- if ( !(dsk_buf = AllocMem( (long) dp.track_size,
- MEMF_CHIP | MEMF_PUBLIC | MEMF_CLEAR ) ) )
- df_exit( ENOMEM );
-
-
-
- /*
- We're ready to go. Make sure the disk is inserted if it is a floppy,
- also give the user a chance to bail out.
- */
- printf( "Insert disk in drive %s. Press <enter> to continue.", device );
- getchar();
-
- /*
- Get the type of the drive...
- */
- get_drive_type();
-
-
- /*
- Initialize the bad_track_list.
- */
- init_bad_track_list();
-
- /*
- Ask the user to enter their own bad tracks (perhaps found
- by another test). This part is optional - we do our own
- format and verify, and add any new bad tracks to the list.
- */
- if ( enter_tracks || update )
- get_bad_tracks();
-
-
- printf( " Disk Statistics:\n" );
- printf( "\troot key: \t%ld\n", dp.root_key );
- printf( "\tboot key: \t%ld\n", dp.boot_key );
- printf( "\tlower key: \t%ld\n", dp.lower_key );
- printf( "\tupper key: \t%ld\n", dp.upper_key );
-
- /*
- If the user just wants to update the bad blocks (-u), we will
- skip the rest of this junk and go straigh to update.
- */
- if ( update )
- {
- update_bad_blocks();
- df_exit( 0 );
- }
-
- /*
- If we just want a list of bad blocks, print it and quit...
- */
- if ( report )
- {
- print_bad_blocks();
- df_exit( 0 );
- }
-
-
- /*
- Format the disk...
- */
- if ( dformat || quick_format || no_verify )
- format_disk();
-
- df_exit( 0 );
- }
-
-
-
-