/*
 * This file is part of DesktopBSD, see the README file.
 *
 * Author: Peter Hofer <ph@desktopbsd.net>
 *			(C) 2004, 2005
 *
 * Copyright: BSD, see the COPYING file included in this distribution
 *
 */

#include <disk.h>
#include <chunk.h>

#include <executor.h>
#include <dbsdtranslator.h>

#include <cstdio>
#include <fcntl.h>
#include <sys/stat.h>



#include <qdir.h>
#include <qstring.h>
#include <qregexp.h>

/* getfsstat(2) */
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>


Disk::Disk(const QString& devName):
		Device(devName)
{
	 readDescription();
	 totalSize.setDefaultUnit(Size::Blocks);
	 dsk = Open_Disk(devName.latin1());
	 update();
}


Disk::Disk(const Disk& d):
		Device()
{
	 dsk = NULL;
	 operator= (d);
}


/** Returns TRUE when the disk is mounted, otherwise FALSE.
  *
  * This method exists to notify the user that the disk
  * can't be changed because it's mounted.
  */
bool  Disk::isMounted()  const {
	int  mounteddevs = getfsstat(NULL, 0, MNT_WAIT);
	if(mounteddevs < 0)
		return  false;

	bool  mounted = false;
	struct statfs  *mntbuf = new struct statfs[mounteddevs];
	mounteddevs = getfsstat(mntbuf, mounteddevs * sizeof(struct statfs), MNT_WAIT);
	if(mounteddevs > 0) {
		QString  disk = "/dev/" + getName();
		for(int i = 0;  i < mounteddevs;  ++i)
			if(strncmp(mntbuf[i].f_mntfromname, disk, disk.length()) == 0) {
				mounted = true;
				break;
			}
	}

	delete  mntbuf;
	return  mounted;
}


Disk::~Disk() {
	 /* Free_Disk() doesn't like NULL pointers... */
	 if(dsk != NULL)
		  Free_Disk(dsk);
}

void Disk::readDescription() {
  QString name = getName();

  /* Separate device name and number, e.g. da0 -> "da", num = "0" */
  bool convOk;
  uint pos = name.find(QRegExp("[0-9]+$"));
  uint num = name.mid(pos).toUInt(&convOk);
  name.truncate(pos);

  if(!convOk)
	 /* desc = "" */;
  else if(name == "ad" || name == "wd")
	 desc = NonUITr::tr("ATA Disk %1 (IDE %2 %3)")
	 			 .arg(num + 1)
				 .arg(num / 2 + 1)
				 .arg((num % 2 == 0) ? NonUITr::tr("Master") : NonUITr::tr("Slave"));
  else if(name == "da")
	 desc = NonUITr::tr("External/SCSI Drive %1").arg(num + 1);
  else if(name == "fla")
	 desc = NonUITr::tr("Flash Drive %1").arg(num + 1);
  else if(QStringList::split('|', "ar|aacd|amrd|idad|ips|mlxd|mlyd|pst|twed").contains(name))
	 desc = NonUITr::tr("ATA RAID %1").arg(num + 1);

  if(desc.isEmpty())
	 desc = getName();
}

QString Disk::getDesc() const {
  return desc;
}


/** Makes one big FreeBSD chunk covering the entire disk. */
void  Disk::allFreeBSD() {
	 if(dsk != NULL) {
		  All_FreeBSD(dsk, 0);
		  update();
	 }
}


/** @param name  The boot sector in /boot, e.g. mbr -> /boot/mbr
  * @param size  If not NULL and no problems occur, the
  *				  boot sector's size is stored in *size.
  *
  * @return Pointer to the boot sector data.
  *
  * @warning The allocated memory
  *			 must be deleted by the caller!
  */
uchar *Disk::bootAlloc(const QString& name, size_t *size) {
	 uchar  *boot = NULL;
	 char	 buf[FILENAME_MAX];
	 struct  stat sb;

	 snprintf(buf, sizeof(buf), "/boot/%s", name.latin1());
	 if (stat(buf, &sb) == 0) {
		  int fd = open(buf, O_RDONLY);
		  if (fd >= 0) {
				boot = new uchar[sb.st_size];

				if (read(fd, boot, sb.st_size) != sb.st_size) {
					 delete boot;
					 boot = NULL;
				} else if(size != NULL)
					 *size = sb.st_size;

				close(fd);
		  }
	 }

	 return boot;
}


/** Sets the boot block. */
bool Disk::setBoot(BootMgr boot) {
	 if(dsk != NULL) {
		  uchar  *blk = NULL;
		  size_t  size;

		  switch (boot) {
		  case BootMenu: {
					blk = bootAlloc("boot0", &size);
					if(blk != NULL)
						Set_Boot_Mgr(dsk, blk, size);
					else
						return false;
				}
				break;

		  case StandardMBR: {
					blk = bootAlloc("mbr", &size);
					if(blk != NULL)
						Set_Boot_Mgr(dsk, blk, size);
					else
						return false;
				}
				break;
		  }

		  /* Free memory allocated by bootAlloc() */
		  delete blk;
		  return true;
	 }

	 return false;
}


/** Writes all changes. */
bool Disk::writeChanges() {
	 if(dsk != NULL) {
		  uchar *b1, *b2;

		  b1 = bootAlloc("boot1");
		  b2 = bootAlloc("boot2");
		  if(b1 == NULL || b2 == NULL)
				return false;

		  bool success = !Set_Boot_Blocks(dsk, b1, b2) && !Write_Disk(dsk);
		  delete b1;
		  delete b2;
		  return success;
	 }
	 return false;
}


/** Reverts all changes. */
void  Disk::undoChanges() {
	 if(dsk != NULL) {
		  Free_Disk(dsk);
		  dsk = Open_Disk(getName().latin1());
		  update();
	 }
}


/** Returns the number of chunks on this disk */
uint  Disk::chunkCount() const {
	 return chunks;
}


/**
  * Returns the number of used chunks on
  * this disk.
  *
  */
uint  Disk::usedChunkCount() const {
	uint n, ucnt = 0, cnt = chunkCount();
	for(n = 0; n < cnt; ++n)
		if(findChnk(n)->type != unused)
			++ucnt;
			
	return ucnt;
}


/** Provides access to single chunks.
  * @param pos The chunk's position on the disk.
  * @return A Disk::Chunk object.
  * @throw std::out_of_range If the chunk at pos doesn't exist.
  */
Disk::Chunk  Disk::operator[](uint pos) throw(std::out_of_range) {
	 struct chunk *cnk = findChnk(pos);
	 if(cnk == NULL)
		  throw std::out_of_range("pos");

	 return Chunk(cnk, this);
}


/** operator= () copies two disks, but
  * does not take over unwritten changes.
  */
Disk&  Disk::operator= (const Disk& d) {
	 if(this != &d) {
		  Device::operator= (d);
		  delete dsk;
		  if(d.dsk != NULL)
				dsk = Open_Disk(d.dsk->name);
		  else
				dsk = NULL;
		  update();
	 }

	 return *this;
}


/** @return The disk's size, converted to the used size unit. */
const Size&  Disk::size()  const {
	 return totalSize;
}


/** Search a chunk.
  * @return A pointer to a chunk structure
  *			or NULL when out of range.
  */
chunk *Disk::findChnk(uint pos) const {
	 chunk *c = NULL;
	 if(dsk != NULL && dsk->chunks != NULL) {
		  c = dsk->chunks->part;
		  for(uint i = 0; c != NULL && i < pos; ++i)
				c = c->next;
	 }
	 return c;
}


/** Updates "chunks" and "totalSize". */
void  Disk::update() {
	 totalSize = 0;
	 chunks = 0;

	 if(dsk != NULL && dsk->chunks != NULL) {
		  chunk *c = dsk->chunks->part;
		  while(c != NULL) {
				totalSize += c->size;
				chunks++;
				c = c->next;
		  }
	 }
}


/** Returns FALSE when the disk's BIOS
  * geometry is incorrect, otherwise TRUE.
  */
bool  Disk::saneBIOSGeom()  const {
	 return (dsk->bios_cyl <= 65536
	 			&& dsk->bios_hd <= 256
	 			&& dsk->bios_sect < 64);
}


/** Sets the BIOS geometry to something sane. */
void  Disk::sanitizeBIOSGeom() {
	 Sanitize_Bios_Geom(dsk);
}


/** Sets the BIOS geometry to the given values.
  * @param cyl Cylinders
  * @param heads Heads
  * @param sect Sectors
  */
void Disk::setBIOSGeometry(ulong cyl, ulong heads, ulong sect) {
	 Set_Bios_Geom(dsk, cyl, heads, sect);
}


ulong Disk::getBIOSCyl() {
  return dsk->bios_cyl;
}

ulong Disk::getBIOSHeads() {
  return dsk->bios_hd;
}

ulong Disk::getBIOSSectors() {
  return dsk->bios_sect;
}


/** Returns the same devices as getDiskNames(), but
  * as a MntDevice object. Be aware that this consumes
  * quite some cpu time because all sub-devices are created.
  */
QPtrList<Disk>	Disk::getAllDevices() {
	QPtrList<Disk>	list;

	QStringList	names = getAllDeviceNames();
	QStringList::ConstIterator	it = names.begin();
	while(it != names.end())
		list.prepend(new  Disk(*it++));

	return list;
}



/** This returns all present disks, RAIDs and flash memory devices */
QStringList Disk::getAllDeviceNames() {
	QStringList  sl = QDir("/dev").entryList(QDir::System);
	return sl.grep(QRegExp("^(aacd|ad|ar|amrd|da|fla|idad|ips|mlxd|mlyd|pst|twed|wd)[0-9]+$"));
}


