/* Raw disk i/o handling the partition tables on a hard drive       */
/*                                                                  */
/* Michael Fulbright <msf@redhat.com>  June 1997                    */
/* Recoded to use new HardDrive structure                           */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <malloc.h>
#include <linux/fs.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include "libfdisk.h"


long long llseek(int fd, long long offset,  int whence);


int fdiskSectorToCHS( HardDrive *hd, unsigned int start,
		      unsigned int *c, unsigned int *h, unsigned int *s ) {

    *s = start % hd->geom.sectors + 1;
    start /= hd->geom.sectors;
    *h = start % hd->geom.heads;
    start /= hd->geom.heads;
    *c = start;
    return FDISK_SUCCESS;
}

/* move absolute sector position to the next highest cylinder boundary */
/* HOWEVER, if we are within 2 heads of the previous cylinder we dont  */
/* round up. This is how partitions with partition tables at the front */
/* work (like LP).                                                     */
int fdiskRoundStartToCylinder( HardDrive *hd, unsigned int *start ) {
    unsigned int m;

    m = *start % (hd->geom.heads*hd->geom.sectors);
    if (m > 2*hd->geom.sectors)
	*start += (hd->geom.heads*hd->geom.sectors) - m;
    
    return FDISK_SUCCESS;
}

/* move absolute sector position to the next lowest  cylinder boundary */
int fdiskRoundEndToCylinder( HardDrive *hd, unsigned int *end ) {
    unsigned int m;

    if (*end > hd->geom.heads*hd->geom.sectors) {
	m = (*end % (hd->geom.heads*hd->geom.sectors));
	if (m != hd->geom.heads*hd->geom.sectors-1)
	    *end -= m+1;
    }
    return FDISK_SUCCESS;
}


    
/* these routes return < 0 on system error, errno has error */
/*                       0 on success                       */
/*                     > 0 on other errors                  */
/* open device dev, returns allocated HardDrive structure */
/* num is the booting numbers, ie, IDE drives come first, then SCSI, etc */
/* up to caller to set values                                            */
int fdiskOpenDevice(char *dev, unsigned int num, HardDrive **hddev) {
    int fd;
    int i;
    HDGeometry g;
    HardDrive *hd;
    
    *hddev = NULL;
    
    /* try to determine everything, if it works then we return */
    /* filled in hd_device struct                              */
    fd = open(dev, O_RDWR);
    if (fd < 0)
	return -1;

    if (ioctl(fd, HDIO_GETGEO, &g))
	return -1;
    
    hd = (HardDrive *) malloc(sizeof(HardDrive));
    hd->fd = fd;
    strncpy(hd->name, dev, MAX_HD_NAMELEN);
    memcpy(&hd->geom, &g, sizeof(HDGeometry));
    hd->totalsectors = hd->geom.sectors*hd->geom.heads*hd->geom.cylinders;
    
    *hddev = hd;

    /* zero out the partition table */
    memset(&hd->table, 0, (MAX_PARTITIONS+1)*sizeof(Partition));
    memset(&hd->eptable, 0, (MAX_PARTITIONS+1)*sizeof(Partition));

    /* set all primaries as available and all extended as unavailable */
    for (i=1; i <= MAX_PARTITIONS; i++) {
	if (i < 5)
	    hd->table[i].status = AVAILABLE;
	else
	    hd->table[i].status = UNAVAILABLE;

	/* set all eptable entries as UNAVAILABLE */
	hd->eptable[i].status = UNAVAILABLE;
    }

    /* no PEP yet! */
    hd->pep = 0;

    hd->num = num;
    
    return FDISK_SUCCESS;
}

/* Note - no error checking (yet) */
int fdiskCloseDevice(HardDrive *hddev) {
    close(hddev->fd);
    free(hddev);
    return FDISK_SUCCESS;
}

/* cause kernel to re-read partition table */
int fdiskReReadPartitions( HardDrive *hd ) {
    return ioctl(hd->fd, BLKRRPART);
}

/* these two routines read/write a Partition Table at the location */
/* specified by the caller                                         */
/* returns a value of FDISK_ERR_BADNUM if no table found at loc    */
int fdiskReadPartitionTable(HardDrive *hddev, unsigned int loc,
			     RawPartitionTable **pt ) {
    int i;
    long long offset;
    unsigned int magic;
    unsigned char b[SECTORSIZE];
    RawPartitionTable *t;

    offset = (long long) loc * (long long) SECTORSIZE;
    if (llseek(hddev->fd, offset, SEEK_SET) < 0)
	return -1;

    if (read(hddev->fd, b, SECTORSIZE) != SECTORSIZE)
	return -1;

    /* see if we really got something */
    magic = b[PARTMAGOFF]*256 + b[PARTMAGOFF+1];
    if (magic != PARTMAGIC)
	return FDISK_ERR_BADMAGIC;

    t = (RawPartitionTable *) malloc(sizeof(RawPartitionTable));
    
    for (i=0; i<4; i++)
	memcpy(  &t->entry[i],
		 b+PARTTBLOFF+i*sizeof(RawPartition),
		 sizeof(RawPartition));
    *pt = t;
    return FDISK_SUCCESS;
}

/* we read the current sector and modify it, in case there is some */
/* boot code there we need to preserve                             */
/* not sure this is what we want to ALWAYS do                      */
/* accepts location as the SECTOR OFFSET!!!                        */
int fdiskWritePartitionTable(HardDrive *hddev, unsigned int loc,
			     RawPartitionTable *pt ) {
    int i;
    long long offset;
    unsigned char b[SECTORSIZE];

#ifdef REALLY_DEBUG
    printf("seeking to %d and writing partition table\n",loc);
    print_raw_partition_table(0, pt);
#endif
    offset = (long long) loc * (long long) SECTORSIZE;
    if (llseek(hddev->fd, offset, SEEK_SET) < 0)
	return -1;

    if (read(hddev->fd, b, SECTORSIZE) != SECTORSIZE)
	return -1;

    /* move partition data into the sector */
    for (i=0; i<4; i++)
	memcpy(  b+PARTTBLOFF+i*sizeof(RawPartition),
		 &pt->entry[i],
		 sizeof(RawPartition));

    /* stick the magic on there */
    b[PARTMAGOFF   ] = PARTMAGIC >> 8;
    b[PARTMAGOFF+1 ] = PARTMAGIC & 0xff;
    
    if (llseek(hddev->fd, offset, SEEK_SET) < 0)
	return -1;

    if (write(hddev->fd, b, SECTORSIZE) != SECTORSIZE)
	return -1;

    return FDISK_SUCCESS;
}


int fdiskReadMBR( HardDrive *hddev, RawPartitionTable **pt ) {
    return fdiskReadPartitionTable(hddev, 0L, pt );
}

int fdiskWriteMBR( HardDrive *hddev, RawPartitionTable *pt ) {
    return fdiskWritePartitionTable( hddev, 0L, pt );
}

int fdiskZeroMBR( HardDrive *hddev ) {
    RawPartitionTable pt;

    memset(&pt, 0, sizeof(RawPartitionTable));

    return fdiskWriteMBR( hddev, &pt );
}
	    

