/* this file has 'high-level' operations which only use the partition # */
/* For example, on a disk the first 4 partitions are the primary partitions */
/* and are numbered 1 thru 4. Then each subsequent logical paritition   */
/* is numbered sequentially starting with 5.                            */
/* The functions in this file use this partition number as the index of */
/* the operation.                                                       */
/* Recoded to use new HardDrive struct with single partition table      */


#include <stdio.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <errno.h>
#include "libfdisk.h"


/* not sure where this guy should go, we'll leave it here for now */
/* make raw partition entry for the given partition               */
/* partition should have the start, size, type, active fields set */
/* rest are taken care of                                         */
int fdiskMakeRawEntry( HardDrive *hd, Partition *pt, RawPartition *p ) {
    unsigned int start, size, end;
    unsigned int s_cyl, s_hd, s_sec;
    unsigned int e_cyl, e_hd, e_sec;
    unsigned int cur, low, hi, act;

    /* read in the current values for the various parameters */
    fdiskGetConstraint( &pt->start,  &p->start,  &low, &hi, &act );
    fdiskGetConstraint( &pt->size,   &p->size,   &low, &hi, &act );
    fdiskGetConstraint( &pt->type,   &cur,       &low, &hi, &act );
    p->type   = cur;
    fdiskGetConstraint( &pt->active, &cur,       &low, &hi, &act );
    p->active = cur;
    
    /* convert from sector to cyl/hd/sector */
    start = p->start;
    size  = p->size;
    end   = start + size - 1;
    
    fdiskSectorToCHS( hd, start, &s_cyl, &s_hd, &s_sec );
    fdiskSectorToCHS( hd, end  , &e_cyl, &e_hd, &e_sec );
    
    /* we have to store CHS in funky way */
    /* handle cylinder limits on PC partitions correctly */
    s_cyl = (s_cyl > 1023) ? 1023 : s_cyl;
    p->start_cyl  = s_cyl & 0xff;
    p->start_head = s_hd;
    p->start_sec  = s_sec | ((s_cyl >> 2) &0xc0);

    e_cyl = (e_cyl > 1023) ? 1023 : e_cyl;
    p->end_cyl    = e_cyl & 0xff;
    p->end_head   = e_hd;
    p->end_sec    = e_sec | ((e_cyl >> 2) &0xc0);
    
    return FDISK_SUCCESS;
}



/* remove partition # n from drive hd. If it doesn't exist then a return */
/* code > 0 is returned, else 0 is returned                              */
/* First primary partition is number 1, not 0!                           */
int fdiskRemovePartition( HardDrive *hd, unsigned int n ) {
    
    if (n < 1)
	return FDISK_ERR_BADNUM;
    
    if (n < 5) {
	return fdiskRemovePrimary( hd, n );
    } else {
	return fdiskRemoveLogical( hd, n );
    }
}

/*                                                                         */
/* not currently used, unclear what numbering scheme to use to index       */
/* partitions we wish to create beyond the first 4.  Logical partition     */
/* numbers are sequential, so only 'free' number to create a new partition */
/* at would be the number of the last logical plus one. Seems kludgey to   */
/* require user to figure out that number just to call the function.       */
/* Instead I will have a fdiskCreatePrimary() and fdiskAppendLogical()     */
/*                                                                         */
#if 0
/* create partition number n, where n can be 1-4 for primary,   */
/* or 5 or greater to append a logical partition (this is       */
/* because of screwy way logicals are numbered )                */ 
/* DO NOT USE THIS FUNCTION YET - UNCLEAR IF IT ACTS LIKE WE WANT!  */
/* DOES NOT MAKE EXTENDED PARTITIONS IF THEY ARE NEEDED FOR LOGICAL */
int fdiskCreatePartition( HardDrive *hd, unsigned int n,
			  unsigned int start, unsigned int size) {

    if (n < 1 )
	return FDISK_ERR_BADNUM;
    
    if (n < 5) {
	return fdiskCreatePrimary(hd, n, start, size);
    } else {
	/* append a logical partition */
	return fdiskAppendLogical( hd, start, size );
    }
}
#endif


/* Set    partition #n into the current partition table */
/* DOES NOT create an extended partition if necessary   */
/* does not do any sanity checking on attributes        */
int fdiskSetAttrPartition( HardDrive *hd, unsigned int n, Partition *p ) {
    
    if (n < 1)
	return FDISK_ERR_BADNUM;
    
    if (n < 5)
	return fdiskSetAttrPrimary(hd, n, p);
    else
	return fdiskSetAttrLogical(hd, n, p);
}


int fdiskGetAttrPartition( HardDrive *hd, unsigned int n, Partition **p ) {
    
    if (n < 1)
	return FDISK_ERR_BADNUM;
    
    if (n < 5)
	return fdiskGetAttrPrimary(hd, n, p);
    else
	return fdiskGetAttrLogical(hd, n, p);
}

/* give a hard drive hd, read in the partition data        */
/* COMPLETELY trashes all partition info in the hard drive */
int fdiskReadPartitions( HardDrive *hd ) {
    int i, s;
    unsigned int exttype;
    RawPartitionTable *pt;
    Partition         p, ept, *pti;
    
    s = fdiskReadMBR(hd, &pt );
    if (s)
	return s;

    /* move data from raw partition table into abstract data type */
    /* primary partitions store absolute sector offsets/sizes already */
    for (i=1; i < 5; i++) {
	if ((s=fdiskCreatePrimary( hd, i )) != 0)
	    return s;

	/* get initial attr of the partition */
	fdiskGetAttrPartition( hd, i, &pti );
	memcpy(&p, pti, sizeof(Partition));
	free(pti);

	/* if the partition is defined                                */
	/* have to set this up so that we don't try to mangle this    */
	/* partition later when we are automatically allocating space */
	/* for newly created partitions                               */
	/* remember the rawPT index starts at 0, PT starts at 1       */
	if (pt->entry[i-1].size && pt->entry[i-1].type) {
	    fdiskSetFixedConstraint( &p.size,    pt->entry[i-1].size    );
	    fdiskSetFixedConstraint( &p.start,   pt->entry[i-1].start   );
	    fdiskSetFixedConstraint( &p.type,    pt->entry[i-1].type    );
	    fdiskSetFixedConstraint( &p.active,  pt->entry[i-1].active  );
	    fdiskSetFixedConstraint( &p.offset,  0                      );
	    fdiskDeactivateAllDriveSet( &p.drive );
	    fdiskActivateDriveSet( &p.drive, hd->num );
	    fdiskSetCurrentDriveSet( &p.drive, hd->num );
	    fdiskSetFixedConstraint( &p.num, i );
	    
	    /* now to make sure NOTHING can happen to this partition */
	    p.immutable = 1;
	    p.status    = ALLOCATED;
	} else {
	    p.status = AVAILABLE;
	}

	/* store in the partition table */
	fdiskSetAttrPartition( hd, i, &p );
    }

    /* now pursue extended partition */
    /* we do not handle more than one extended partition per drive */
    /* read in extended partition(s) if they exist */
    hd->pep = 0;
    for (i=1; i<5; i++) {
	exttype = hd->table[i].type.current;
	if (fdiskIsExtended(exttype)) {
	    
	    RawPartitionTable *extended;
	    unsigned int ext_start;
	    unsigned int ext_size;
	    unsigned int cur_start;
	    unsigned int cur_size;
	    unsigned int lp;
	    int sawext, sawlog;
	    int j;

	    /* if we also have seen the PEP, we have trouble */
	    if (hd->pep)
		return FDISK_ERR_TWOEXT;

	    /* mark which primary hold the PEP */
	    hd->pep = i;

	    /* start/size of the PEP */
	    ext_start  = hd->table[i].start.current;
	    ext_size   = hd->table[i].size.current;

	    /* start/size of the EP within the next LP must fall */
	    cur_start = ext_start;
	    cur_size  = ext_size;
	    
	    /* follow linked list of extended partitions              */
	    /* watch out for more than one logical partition          */
	    /* per EPT. This is bad and we will die and they will cry */
	    /* fdisk and cfdisk cant handle this either. You should   */
	    /* be using your OS's fdisk if this is the case, cause    */
	    /* it is insane and only it can understand the madness    */
	    while (1) {
		if ((s=fdiskReadPartitionTable(hd, cur_start, &extended)))
		    return s;

		/* insert this extended partition into the partition table */
		/* we also insert a logical partition as well              */
		/* since there is only 1 LP per EPT/EP, we store the type  */
		/* in the main partition table as the type of the LP.      */
		/* We also store information on the EP in the eptable[].   */
		/* Later when we write the entire                          */
		/* partition table out to disk, we'll remember that there  */
		/* is also an extended partition as well.                  */
		/*                                                         */
		/* The number assigned to the logical partition created is */
		/* passed back in the variable lp                          */
		if ((s=fdiskAppendLogical( hd, &lp )) != 0)
		    return s;

		/* start from scratch */
		fdiskGetAttrPartition( hd, lp, &pti );
		memcpy(&p, pti, sizeof(Partition));
		free(pti);
		fdiskGetAttrExtended( hd, lp, &pti );
		memcpy(&ept, pti, sizeof(Partition));
		free(pti);

		/* store the size/start of the EP                          */
		/* we DO NOT set end because we are interested in          */
		/* restricted the position on disk to the CURRENT position */
		/* endcyl is used if we are trying to restrict the          */
		/* placement of a NEW partition to a region of disk, like  */
		/* the first 1024 cylinfers. Storing the start sector isnt */
		/* useful in that case since the mapping from sector->cyl  */
		/* depends upon the geom of the drive, and we may be       */
		/* considering several different drives of differring geom */
		/* for the placement of the NEW partition.                 */
		fdiskSetFixedConstraint( &ept.size,    cur_size   );
		fdiskSetFixedConstraint( &ept.start,   cur_start  );
		fdiskDeactivateAllDriveSet( &ept.drive );
		fdiskActivateDriveSet( &ept.drive, hd->num );
		fdiskSetCurrentDriveSet( &ept.drive, hd->num );
		fdiskSetFixedConstraint( &ept.num,     lp         );
		fdiskSetFixedConstraint( &ept.type,    exttype    );
	    
		/* now to make sure NOTHING can happen to this partition */
		ept.immutable = 1;
		ept.status    = ALLOCATED;

		/* setup the extended partition which corresponds to the */
		/* logical partition we are going to setup next          */
		fdiskSetAttrExtended( hd, lp, &ept );
		
		/* ok, now figure out what logical partitions are */
		/* in this extended partition */
		sawlog = 0;
		sawext = 0;
		for (j=1; j<5; j++) {
		    RawPartition        *raw;

		    raw = &extended->entry[j-1];

		    /* skip link to next in extended partition chain for now */
		    /* we put it in the logical partitions for future ref */
		    if (fdiskIsExtended(raw->type)) {
			if (sawext)
			    return FDISK_ERR_TWOEXT;
			sawext = j;
			exttype = raw->type;
			continue;
		    }

		    /* make sure the log partition exists */
		    if (!raw->size || !raw->type)
			continue;

		    if (sawlog)
			return FDISK_ERR_TWOLOG;
		    else
			sawlog = 1;
		    
		    /* make sure that numbers make sense */
		    /* we test that:                     */
		    /*  - the current partition isnt bigger than */
		    /*    the extended partition its in          */
		    /*  - start isnt before the start of the "primary" */
		    /*    extended partition                           */
		    /*  - end isnt past end of "primary" extended partition */
		    if (((raw->start+raw->size) > (cur_start+cur_size)) ||
			((cur_start+raw->start) < ext_start) ||
			((cur_start+raw->start+raw->size) >
			 (ext_start+ext_size)))
			return FDISK_ERR_CORRUPT;

		    fdiskSetFixedConstraint( &p.size,   raw->size   );
		    fdiskSetFixedConstraint( &p.start,  raw->start+cur_start );
		    fdiskDeactivateAllDriveSet( &p.drive );
		    fdiskActivateDriveSet( &p.drive, hd->num );
		    fdiskSetCurrentDriveSet( &p.drive, hd->num );
		    fdiskSetFixedConstraint( &p.num,    lp         );
		    fdiskSetFixedConstraint( &p.type,   raw->type    );
		    fdiskSetFixedConstraint( &p.active, raw->active  );

		    /* we currently set ALL offsets to 0       */
		    /* the start parameter stores the absolute */
		    /* starting position of the LP             */
		    /* get the offset by comparing start of LP */
		    /* to that of the EP who EPT it is in      */
		    fdiskSetFixedConstraint( &p.offset, 0 );

		    /* now to make sure NOTHING can happen to this partition */
		    p.immutable = 1;
		    p.status    = ALLOCATED;
		    
		    fdiskSetAttrPartition( hd, lp, &p );
		}

		/* see if we have another extended partition to follow */
		if (!sawext)
		    break;
		else {
		    cur_start  = extended->entry[sawext-1].start + ext_start;
		    cur_size   = extended->entry[sawext-1].size;
		}
	    }
	}
    }
    
    return FDISK_SUCCESS;
}

/* give a hard drive hd, write the partition data */
int fdiskWritePartitions( HardDrive *hd ) {
    int i;
    int n;
    int error=0;
    unsigned int ext_start=0, ext_size, cur_start=0, cur_size;
    unsigned int next_start;
    unsigned int low, hi, act;
    unsigned int lpart;
    RawPartitionTable rpt;
    RawPartition *p;
    Partition *pt, *ept;
    
    /* move data to raw partition table from the abstract data type  */
    /* first we handle the primary partitions                        */
    /* we write them NO MATTER what they hold, since they MUST exist */
    /* in the MBR of the hard drive.                                 */
    memset(&rpt, 0, sizeof(RawPartitionTable));
    for (i=1; i < 5; i++) {
	p = &rpt.entry[i-1];
	if (fdiskGetAttrPartition( hd, i, &pt ) == FDISK_SUCCESS) {
	    fdiskMakeRawEntry( hd, pt, p );
	    free(pt);
	} else {
	    memset( p, 0, sizeof(RawPartition) );
	}
    }

    /* write the primary partition */
    fdiskWriteMBR(hd, &rpt);
    
    /* now pursue extended partition                               */
    /* we do not handle more than one extended partition per drive */
    /* we do not handle more than one logical partition per EP     */
    /* we do not handle a tree of EP, just a EP chain              */
    if (hd->pep) {
	fdiskGetAttrPartition( hd, hd->pep, &pt );
	fdiskGetConstraint( &pt->start, &ext_start, &low, &hi, &act );
	fdiskGetConstraint( &pt->size,  &ext_size, &low, &hi, &act );
	free(pt);
	
	cur_start = ext_start;
	cur_size  = ext_size;
    }

    /* now we loop over all logical partitions */
    if (fdiskLastPartition( hd, &lpart ) != FDISK_SUCCESS)
	lpart = 0;
    
    for (n=5; n <= lpart; n++) {
	/* start with a clean table */
	memset(&rpt, 0, sizeof(RawPartitionTable));

	/* move data to raw partition table from the abstract data type */
	/* the raw partition table can hold up to 4 entries, just like  */
	/* the one in the MBR. However, we will have at most 2 entries. */
	/* If there is an EP following the current in the chain, it will*/
	/* have an entry. And there will ALWAYS be an entry for the     */
	/* current LP.                                                  */
	/*                                                              */
	/* stick the extended partition first, then the logical         */
	p = &rpt.entry[0];

	/* see if there is an extended partition following this one in */
	/* the EPT chain.                                              */
	if (fdiskGetAttrExtended( hd, n+1, &ept ) == FDISK_SUCCESS) {
	    fdiskMakeRawEntry( hd, ept, p);
	    free(ept);

	    /* HACK - we have to translate start sector   */
	    /*        to a value relative to the start of */
	    /*        the PEP. We use absolute values     */
	    /*        up until we write to disk (like now)*/
	    /*                                            */
	    /* NOTE - CHS appears to NOT be translated    */
	    /*        Not sure why this is...             */
	    next_start = p->start; /* save for later use */
	    p->start  -= ext_start;

	    /* move pointer to next entry in partition table */
	    p = &rpt.entry[1];
	} else
	    next_start = 0;
	
	/* now do the logical partition */
	fdiskGetAttrPartition( hd, n, &pt );
	fdiskMakeRawEntry( hd, pt, p );
	free(pt);

	/* HACK - we have to translate start sector   */
	/*        relative to the start of the current*/
	/*        extended partition                  */
	/*                                            */
	/* NOTE - CHS appears to NOT be translated    */
	/*        Not sure why this is...             */
	p->start  -= cur_start;
	    
	/* write the partition */
	fdiskWritePartitionTable(hd, cur_start, &rpt);

	/* point to next partition table, if it exists */
	if (next_start)
	    cur_start = next_start;
    }

    /* now sync disk and re-read the partition table */
    sync();
    if ((i = fdiskReReadPartitions( hd ))<0) {
	error = errno;
    } else {
	/* some kernel versions (1.2.x) seem to have trouble
	   rereading the partition table, but if asked to do it
	   twice, the second time works. - biro@yggdrasil.com */

	/* FIXME: is this really necessary -- ewt */
	sync();
	if ((i=fdiskReReadPartitions( hd ))<0)
	    error = errno;
    }
    
    if (i < 0) {
	close(hd->fd);
	
	sync();
	
	printf("Re-read table failed with error %d: %s.\nReboot your "
	       "system to ensure the partition table is updated.\n",
	       error, strerror(error));
    }
    
    return i;
}

/* return value of first partition, starting with primaries and working */
/* down thru logical  partitions                                        */
/* returns FDISK_ERR_BADNUM if NO partitions exists                     */
int fdiskFirstPartition( HardDrive *hd, unsigned int *first ) {
    if (fdiskFirstPrimary( hd, first ) == 0 )
	return FDISK_SUCCESS;

    if (fdiskFirstLogical( hd, first ) == 0 )
	return FDISK_SUCCESS;

    return FDISK_ERR_BADNUM;
}

/* return value of Last partition, starting with logical and working    */
/* down thru primary partitions                                         */
/* returns FDISK_ERR_BADNUM if NO partitions exists                     */
int fdiskLastPartition( HardDrive *hd, unsigned int *last ) {
    if (fdiskLastLogical( hd, last ) == 0 )
	return FDISK_SUCCESS;

    if (fdiskLastPrimary( hd, last ) == 0 )
	return FDISK_SUCCESS;

    *last = 0;
    return FDISK_ERR_BADNUM;
}

/* return pointer to partition #n if it exists            */
/* returns null pointer and FDISK_ERR_BADNUM if it doesnt */
int fdiskFindPartition( HardDrive *hd, unsigned int n, Partition **p ) {
    if (n < 5)
	return fdiskFindPrimary( hd, n, p );
    else
	return fdiskFindLogical( hd, n, p );
}	
    
#ifdef NEED_WALK_ROUTINES
/* these routines walk through the list of partitions on a hard drive */
/* Point to 'first' partition on drive                                */
/* returns FDISK_ERR_BADNUM if no partitions exist                    */
/* also sets key to NULL                                              */
int fdiskWalkReset( HardDrive *hd, WalkKey **key ) {
    unsigned int first;
    
    /* find first partition and initialize the key used to walk list */
    if (fdiskFirstPartition( hd, &first ) != 0)
	return FDISK_ERR_BADNUM;

    *key = (WalkKey *) malloc( sizeof(WalkKey) );
    *key->value = first;
}

/* return partition information for where key is currently pointing */
/* and increment key to next position                               */
/* if key is NULL then we're at end of list                         */
int fdiskWalkNext( HardDrive *hd, WalkKey **key, Partition **p ) {

    Partition **q;
    int s, next, last;

    /* find current partition */
    s = fdiskFindPartition( hd, *key->value, p );
    if (s != 0) {
	free(*key);
	*key = NULL;
	return s;
    }

    /* point to next */
    fdiskLastPartition( hd, &last );
    next = *key->value+1;

    for (; next <= last; next++)
	s = fdiskFindPartition( hd, next, q );
	if (!s)
	    break;
	
    /* we've reached the end */
    if (next >= last) {
	free(*key);
	*key = NULL;
    }

    return FDISK_SUCCESS;
}

#endif


