/* Copyright 1997-2003,2005-2007,2009 Alain Knaff.
* This file is part of mtools.
*
* Mtools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Mtools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Mtools. If not, see .
*
* mformat.c
*/
#define DONT_NEED_WAIT
#include "sysincludes.h"
#include "mtools.h"
#include "fsP.h"
#include "file.h"
#include "plain_io.h"
#include "nameclash.h"
#include "buffer.h"
#include "partition.h"
#include "open_image.h"
#include "lba.h"
#ifdef OS_linux
#include "linux/hdreg.h"
#include "linux/fs.h"
#endif
static void set_offset(hsc *h, unsigned long offset,
uint16_t heads, uint16_t sectors)
{
uint16_t head, sector;
unsigned int cyl;
if(! heads || !sectors)
head = sector = cyl = 0; /* linear mode */
else {
sector = offset % sectors;
offset = offset / sectors;
head = offset % heads;
offset = offset / heads;
if(offset > 1023)
cyl = 1023;
else
cyl = (uint16_t) offset;
}
if(head > UINT8_MAX) {
/* sector or head out of range => linear mode */
head = sector = cyl = 0;
}
h->head = (uint8_t) head;
h->sector = ((sector+1) & 0x3f) | ((cyl & 0x300)>>2);
h->cyl = cyl & 0xff;
}
void setBeginEnd(struct partition *partTable,
uint32_t begin, uint32_t end,
uint16_t iheads, uint16_t isectors,
int activate, uint8_t type, unsigned int fat_bits)
{
uint8_t heads, sectors;
if(iheads > UINT8_MAX) {
fprintf(stderr,
"Too many heads for partition: %d\n",
iheads);
exit(1);
}
heads=(uint8_t) iheads;
if(isectors > UINT8_MAX) {
fprintf(stderr,
"Too many sectors for partition: %d\n",
isectors);
exit(1);
}
sectors=(uint8_t) isectors;
set_offset(&partTable->start, begin, heads, sectors);
set_offset(&partTable->end, end-1, heads, sectors);
set_dword(partTable->start_sect, begin);
set_dword(partTable->nr_sects, end-begin);
if(activate)
partTable->boot_ind = 0x80;
else
partTable->boot_ind = 0;
if(!type) {
if (fat_bits == 0) {
/**
* Fat bits unknown / not specified. We look
* at size to get a rough estimate what FAT
* bits are used. Note: this is only an
* estimate, the precise calculation would
* involve the number of clusters, which is
* not necessarily known here.
*/
/* cc977219 would have a cutoff number of 32680,
* corresponding to a FAT12 partition with 4K
* clusters, however other information hints that
* only partitions with less than 4096 sectors are
* considered */
if(end-begin < 4096)
fat_bits = 12;
else
fat_bits = 16;
}
/* Description of various partition types in
* https://en.wikipedia.org/wiki/Partition_type#List_of_partition_IDs
* and
* https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-2000-server/cc977219(v=technet.10)
*/
if (fat_bits == 32)
/* FAT 32 partition. For now, we disregard the
* possibility of FAT 32 CHS partitions */
type = 0x0C; /* Win95 FAT32, LBA */
else if (end < 65536) {
/* FAT 12 or FAT 16 partitions which fit entirely below
the 32M mark */
/* The 32M restriction doesn't apply to logical
partitions within an extended partition, but for the
moment mpartition only makes primary partitions */
if(fat_bits == 12)
/* FAT 12 partition */
type = 0x01; /* DOS FAT12, CHS */
else if (fat_bits == 16)
/* FAT 16 partition */
type = 0x04; /* DOS FAT16, CHS */
} else if (end < sectors * heads * 1024u)
/* FAT 12 or FAT16 partition above the 32M
* mark but below the 1024 cylinder mark.
* Indeed, there can be no CHS partition
* beyond 1024 cylinders */
type = 0x06; /* DOS BIG FAT16 or FAT12, CHS */
else
type = 0x0E; /* Win95 BIG FAT16, LBA */
}
partTable->sys_ind = type;
}
/* setsize function. Determines scsicam mapping if this cannot be inferred from
* any existing partitions. Shamelessly snarfed from the Linux kernel ;-) */
/*
* Function : static int setsize(unsigned long capacity,unsigned int *cyls,
* unsigned int *hds, unsigned int *secs);
*
* Purpose : to determine a near-optimal int 0x13 mapping for a
* SCSI disk in terms of lost space of size capacity, storing
* the results in *cyls, *hds, and *secs.
*
* Returns : -1 on failure, 0 on success.
*
* Extracted from
*
* WORKING X3T9.2
* DRAFT 792D
*
*
* Revision 6
* 10-MAR-94
* Information technology -
* SCSI-2 Common access method
* transport and SCSI interface module
*
* ANNEX A :
*
* setsize() converts a read capacity value to int 13h
* head-cylinder-sector requirements. It minimizes the value for
* number of heads and maximizes the number of cylinders. This
* will support rather large disks before the number of heads
* will not fit in 4 bits (or 6 bits). This algorithm also
* minimizes the number of sectors that will be unused at the end
* of the disk while allowing for very large disks to be
* accommodated. This algorithm does not use physical geometry.
*/
static int setsize(unsigned long capacity,unsigned int *cyls,
uint16_t *hds, uint16_t *secs) {
int rv = 0;
unsigned long heads, sectors, cylinders, temp;
cylinders = 1024L; /* Set number of cylinders to max */
sectors = 62L; /* Maximize sectors per track */
temp = cylinders * sectors; /* Compute divisor for heads */
heads = capacity / temp; /* Compute value for number of heads */
if (capacity % temp) { /* If no remainder, done! */
heads++; /* Else, increment number of heads */
temp = cylinders * heads; /* Compute divisor for sectors */
sectors = capacity / temp; /* Compute value for sectors per
track */
if (capacity % temp) { /* If no remainder, done! */
sectors++; /* Else, increment number of sectors */
temp = heads * sectors; /* Compute divisor for cylinders */
cylinders = capacity / temp;/* Compute number of cylinders */
}
}
if (cylinders == 0) rv=-1;/* Give error if 0 cylinders */
*cyls = (unsigned int) cylinders; /* Stuff return values */
*secs = (uint16_t) sectors;
*hds = (uint16_t) heads;
return(rv);
}
static void setsize0(uint32_t capacity,unsigned int *cyls,
uint16_t *hds, uint16_t *secs)
{
int r;
/* 1. First try "Megabyte" sizes */
if(capacity < 1024 * 2048 && !(capacity % 1024)) {
*cyls = capacity >> 11;
*hds = 64;
*secs = 32;
return;
}
/* then try scsicam's size */
r = setsize(capacity,cyls,hds,secs);
if(r || *hds > 255 || *secs > 63) {
/* scsicam failed. Do megabytes anyways */
*cyls = capacity >> 11;
*hds = 64;
*secs = 32;
return;
}
}
static void usage(int ret) NORETURN;
static void usage(int ret)
{
fprintf(stderr,
"Mtools version %s, dated %s\n", mversion, mdate);
fprintf(stderr,
"Usage: %s [-pradcv] [-I] [-B bootsect-template] [-s sectors] "
"[-t cylinders] "
"[-h heads] [-T type] [-b begin] [-l length] "
"drive\n", progname);
exit(ret);
}
void mpartition(int argc, char **argv, int dummy UNUSEDP) NORETURN;
void mpartition(int argc, char **argv, int dummy UNUSEDP)
{
Stream_t *Stream;
unsigned int dummy2;
unsigned int i;
uint16_t sec_per_cyl;
int doprint = 0;
int verbose = 0;
int create = 0;
int force = 0;
unsigned int length = 0;
int do_remove = 0;
int initialize = 0;
uint32_t tot_sectors=0;
/* Needs to be long due to BLKGETSIZE ioctl */
uint8_t type = 0;
int begin_set = 0;
int size_set = 0;
int end_set = 0;
int activate = 0;
int has_activated = 0;
int inconsistency=0;
unsigned int begin=0;
unsigned int end=0;
int dirty = 0;
int open2flags = 0;
int c;
struct device used_dev;
unsigned int argtracks;
uint16_t argheads, argsectors;
char drive, name[EXPAND_BUF];
unsigned char buf[512];
struct partition *partTable=(struct partition *)(buf+ 0x1ae);
struct device *dev;
char errmsg[2100];
char *bootSector=0;
struct partition *tpartition;
argtracks = 0;
argheads = 0;
argsectors = 0;
/* get command line options */
if(helpFlag(argc, argv))
usage(0);
while ((c = getopt(argc, argv, "i:adprcIT:t:h:s:fvpb:l:S:B:")) != EOF) {
char *endptr=NULL;
errno=0;
switch (c) {
case 'i':
set_cmd_line_image(optarg);
break;
case 'B':
bootSector = optarg;
break;
case 'a':
/* no privs, as it could be abused to
* make other partitions unbootable, or
* to boot a rogue kernel from this one */
open2flags |= NO_PRIV;
activate = 1;
dirty = 1;
break;
case 'd':
activate = -1;
dirty = 1;
break;
case 'p':
doprint = 1;
break;
case 'r':
do_remove = 1;
dirty = 1;
break;
case 'I':
/* could be abused to nuke all other
* partitions */
open2flags |= NO_PRIV;
initialize = 1;
dirty = 1;
break;
case 'c':
create = 1;
dirty = 1;
break;
case 'T':
/* could be abused to "manually" create
* extended partitions */
open2flags |= NO_PRIV;
type = strtou8(optarg, &endptr, 0);
break;
case 't':
argtracks = atoui(optarg);
break;
case 'h':
argheads = atou16(optarg);
break;
case 's':
argsectors = atou16(optarg);
break;
case 'f':
/* could be abused by creating overlapping
* partitions and other such Snafu */
open2flags |= NO_PRIV;
force = 1;
break;
case 'v':
verbose++;
break;
case 'b':
begin_set = 1;
begin = strtoui(optarg, &endptr, 0);
break;
case 'l':
size_set = 1;
length = parseSize(optarg);
break;
default:
usage(1);
}
check_number_parse_errno((char)c, optarg, endptr);
}
if (argc - optind != 1 ||
!argv[optind][0] || argv[optind][1] != ':')
usage(1);
drive = ch_toupper(argv[optind][0]);
/* check out a drive whose letter and parameters match */
sprintf(errmsg, "Drive '%c:' not supported", drive);
Stream = 0;
for(dev=devices;dev->drive;dev++) {
int mode ;
FREE(&(Stream));
/* drive letter */
if (dev->drive != drive)
continue;
if (dev->partition < 1 || dev->partition > 4) {
sprintf(errmsg,
"Drive '%c:' is not a partition",
drive);
continue;
}
used_dev = *dev;
SET_INT(used_dev.tracks, argtracks);
SET_INT(used_dev.heads, argheads);
SET_INT(used_dev.sectors, argsectors);
expand(dev->name, name);
mode = dirty ? O_RDWR : O_RDONLY;
if(initialize)
mode |= O_CREAT;
#ifdef USING_NEW_VOLD
strcpy(name, getVoldName(dev, name));
#endif
Stream = OpenImage(&used_dev, dev, name, mode, errmsg,
open2flags | SKIP_PARTITION | ALWAYS_GET_GEOMETRY,
mode, NULL, NULL, NULL);
if (!Stream) {
#ifdef HAVE_SNPRINTF
snprintf(errmsg,sizeof(errmsg)-1,
"init: open: %s", strerror(errno));
#else
sprintf(errmsg,"init: open: %s", strerror(errno));
#endif
continue;
}
tot_sectors = used_dev.tot_sectors;
/* read the partition table */
if (PREADS(Stream, (char *) buf, 0, 512) != 512 && !initialize){
#ifdef HAVE_SNPRINTF
snprintf(errmsg, sizeof(errmsg)-1,
"Error reading from '%s', wrong parameters?",
name);
#else
sprintf(errmsg,
"Error reading from '%s', wrong parameters?",
name);
#endif
continue;
}
if(verbose>=2)
print_sector("Read sector", buf, 512);
break;
}
/* print error msg if needed */
if ( dev->drive == 0 ){
FREE(&Stream);
fprintf(stderr,"%s: %s\n", argv[0],errmsg);
exit(1);
}
if((used_dev.sectors || used_dev.heads) &&
(!used_dev.sectors || !used_dev.heads)) {
fprintf(stderr,"You should either indicate both the number of sectors and the number of heads,\n");
fprintf(stderr," or none of them\n");
exit(1);
}
if(initialize) {
if (bootSector) {
int fd;
fd = open(bootSector, O_RDONLY | O_BINARY | O_LARGEFILE);
if (fd < 0) {
perror("open MBR");
exit(1);
}
if(read(fd, (char *) buf, 512) < 512) {
perror("read MBR");
exit(1);
}
}
memset((char *)(partTable+1), 0, 4*sizeof(*partTable));
set_word(((unsigned char*)buf)+510, 0xaa55);
}
/* check for boot signature, and place it if needed */
if((buf[510] != 0x55) || (buf[511] != 0xaa)) {
fprintf(stderr,"Boot signature not set\n");
fprintf(stderr,
"Use the -I flag to initialize the partition table, and set the boot signature\n");
inconsistency = 1;
}
tpartition=&partTable[dev->partition];
if(do_remove){
if(!tpartition->sys_ind)
fprintf(stderr,
"Partition for drive %c: does not exist\n",
drive);
if((tpartition->sys_ind & 0x3f) == 5) {
fprintf(stderr,
"Partition for drive %c: may be an extended partition\n",
drive);
fprintf(stderr,
"Use the -f flag to remove it anyways\n");
inconsistency = 1;
}
memset(tpartition, 0, sizeof(*tpartition));
}
if(create && tpartition->sys_ind) {
fprintf(stderr,
"Partition for drive %c: already exists\n", drive);
fprintf(stderr,
"Use the -r flag to remove it before attempting to recreate it\n");
}
/* if number of heads and sectors not known yet, set "reasonable"
* defaults */
compute_lba_geom_from_tot_sectors(&used_dev);
/* find out whether there is any activated partition. Moreover
* if no offset of a partition to be created have been
* specificed, find out whether it may be placed between the
* preceding and following partition already existing */
has_activated = 0;
for(i=1; i<5; i++){
struct partition *partition=&partTable[i];
if(!partition->sys_ind)
continue;
if(partition->boot_ind)
has_activated++;
if(ipartition && !begin_set)
begin = END(partition);
if(i>dev->partition && !end_set && !size_set) {
end = BEGIN(partition);
end_set = 1;
}
}
if(!used_dev.sectors && !used_dev.heads) {
if(tot_sectors) {
setsize0((uint32_t)tot_sectors,&dummy2,&used_dev.heads,
&used_dev.sectors);
} else {
used_dev.heads = 64;
used_dev.sectors = 32;
}
}
if(verbose)
fprintf(stderr,"sectors: %d heads: %d %u\n",
used_dev.sectors, used_dev.heads, tot_sectors);
sec_per_cyl = used_dev.sectors * used_dev.heads;
if(create) {
unsigned int overlap;
if(!end_set && !size_set && tot_sectors) {
end = tot_sectors - tot_sectors % sec_per_cyl;
end_set = 1;
}
/* if the partition starts right at the beginning of
* the disk, keep one track unused to allow place for
* the master boot record */
if(!begin && !begin_set)
begin = used_dev.sectors ? used_dev.sectors : 2048;
/* Do not try to align partitions (other than first) on track
* boundaries here: apparently this was a thing of the past */
if(size_set) {
end = begin + length;
} else if(!end_set) {
fprintf(stderr,"Unknown size\n");
exit(1);
}
/* Make sure partition boundaries are correctly ordered
* (end > begin) */
if(begin >= end) {
fprintf(stderr, "Begin larger than end\n");
exit(1);
}
/* Check whether new partition doesn't overlap with
* any of those already in place */
if((overlap=findOverlap(partTable, 4, begin, end))) {
fprintf(stderr,
"Partition would overlap with partition %d\n",
overlap);
exit(1);
}
setBeginEnd(tpartition, begin, end,
used_dev.heads, used_dev.sectors,
!has_activated, type,
abs(dev->fat_bits));
}
if(activate) {
if(!tpartition->sys_ind) {
fprintf(stderr,
"Partition for drive %c: does not exist\n",
drive);
} else {
switch(activate) {
case 1:
tpartition->boot_ind=0x80;
break;
case -1:
tpartition->boot_ind=0x00;
break;
}
}
}
inconsistency |= consistencyCheck(partTable, doprint, verbose,
&has_activated, tot_sectors,
&used_dev, dev->partition);
switch(has_activated) {
case 0:
fprintf(stderr,
"Warning: no active (bootable) partition present\n");
break;
case 1:
break;
default:
fprintf(stderr,
"Warning: %d active (bootable) partitions present\n",
has_activated);
fprintf(stderr,
"Usually, a disk should have exactly one active partition\n");
break;
}
if(inconsistency && !force) {
fprintf(stderr,
"inconsistency detected!\n" );
if(dirty) {
fprintf(stderr,
"Retry with the -f switch to go ahead anyways\n");
exit(1);
}
}
if(doprint && tpartition->sys_ind) {
printf("The following command will recreate the partition for drive %c:\n",
drive);
used_dev.tracks =
(DWORD(tpartition->nr_sects) +
(BEGIN(tpartition) % sec_per_cyl)) /
sec_per_cyl;
printf("mpartition -c -b %d -l %d -t %d -h %d -s %d -b %u %c:\n",
BEGIN(tpartition), PART_SIZE(tpartition),
used_dev.tracks, used_dev.heads, used_dev.sectors,
BEGIN(tpartition), drive);
}
if(dirty) {
/* write data back to the disk */
if(verbose>=2)
print_sector("Writing sector", buf, 512);
if (PWRITES(Stream, (char *) buf, 0, 512) != 512) {
fprintf(stderr,"Error writing partition table");
exit(1);
}
if(verbose>=3)
print_sector("Sector written", buf, 512);
}
FREE(&Stream);
exit(0);
}