/*
 *  disksize -- ディスクのジオメトリ情報を取得
 *
 *  revision history
 *	diskinfo:
 *	0.0: Sep.  6, 2002 by Dai ISHIJIMA (とりあえずバージョン)
 *	0.1: Oct. 21, 2002
 *	0.2: Apr. 28, 2004
 *	0.3: May   1, 2004
 *	0.4: May  30, 2004
 *	0.5: Jun. 19, 2004
 *	0.6: Aug.  5, 2006 (ちょっと修正)
 *	0.7: Aug. 12, 2006 (有効桁数の改善)
 *	disksize:
 *	0.9: Sep. 12, 2007 (exit(3)の宣言, パラメータ取得法変更)
 *	1.0: Oct.  2, 2007 (Mバイト単位での表示)
 *
 *  参考文献:
 *	fdisk(8) のソース (/usr/src/sbin/i386/fdisk/fdisk.c)
 *	wpout09以降 (/usr/src/sbin/fdisk/fdisk.c), diskinfo(8)
 */

#include <stdlib.h>
#include <sys/disk.h>
#include <sys/disklabel.h>
#include <fcntl.h>
#include <unistd.h>


#define SHOW_CYLS 1
#define SHOW_HEAD 2
#define SHOW_SECS 4
#define SHOW_BYTES 8
#define SHOW_BYTES_WITH_UNITS 16
#define SHOW_BLOCKS 32
#define SHOW_BYTES_WITH_COMMA 64
#define SHOW_MEGABYTES 128

#define shift --argc; ++argv


#define DFLT_DISK "/dev/ad0"
#define EOS '\0'
#define NEWLINE '\n'

#ifndef BUFSIZ
#define BUFSIZ 512
#endif

#define MEGABYTES (1024 * 1024)


/* 数値を文字列に変換 */
char *num2str(long long d, char *s)
{
    int p;
    int q;
    int ch;

    p = 0;
    do {
	s[p] = '0' + (d % 10);
	++p;
	d /= 10;
    } while (d > 0);
    s[p] = EOS;
    for (q = 0; q < p / 2; q++) {
	ch = s[q];
	s[q] = s[p - q - 1];
	s[p - q - 1] = ch;
    }
    return(s);
}


/* 数値を文字列に変換 */
char *num2commastr(long long d, char *s)
{
    int p;
    int q;
    int n;
    int ch;

    p = 0;
    n = 0;
    do {
	if ((n > 0) && ((n % 3) == 0)) {
	    s[p] = ',';
	    ++p;
	}
	s[p] = '0' + (d % 10);
	++p;
	++n;
	d /= 10;
    } while (d > 0);
    s[p] = EOS;
    for (q = 0; q < p / 2; q++) {
	ch = s[q];
	s[q] = s[p - q - 1];
	s[p - q - 1] = ch;
    }
    return(s);
}


/* 一文字出力 */
void putch(int fd, unsigned char ch)
{
    write(fd, &ch, 1);
}


/* 文字列出力 */
void myputs(int fd, char *s)
{
    while (*s) {
	putch(fd, *s);
	++s;
    }
}


int main(int argc, char *argv[])
{
    char *disk;
    char s[BUFSIZ];
    int show;
    int fd;
    off_t mediasize, blocks;
    unsigned long secsiz, nsecs, nhead, ncyls;
    unsigned char units[] = { 'k', 'M', 'G', 'T', EOS };
    int p;
    off_t m;
    int fraction;

    show = 0;
    shift;
    while ((argc > 0) && (argv[0][0] == '-')) {
	if (argv[0][1] == 'c') {
	    show |= SHOW_CYLS;
	}
	else if (argv[0][1] == 'h') {
	    show |= SHOW_HEAD;
	}
	else if (argv[0][1] == 's') {
	    show |= SHOW_SECS;
	}
	else if (argv[0][1] == 'b') {
	    show |= SHOW_BYTES;
	}
	else if (argv[0][1] == 'm') {	/* Mバイト単位で */
	    show |= SHOW_MEGABYTES;
	}
	else if (argv[0][1] == 'B') {	/* バイト数をコンマ区切りで */
	    show |= SHOW_BYTES_WITH_COMMA;
	}
	else if (argv[0][1] == 'p') {	/* 適当に単位付け。k, M, G, etc. */
	    show |= SHOW_BYTES_WITH_UNITS;
	}
	else if (argv[0][1] == 'l') {
	    show |= SHOW_BLOCKS;
	}
	else {
	    myputs(STDERR_FILENO, "Usage: diskinfo [-chsbBpl] [device]\n");
	    exit(1);
	}
	shift;
    }
    if (show == 0) {
	show = SHOW_CYLS | SHOW_HEAD | SHOW_SECS;
    }
    if (argc > 0) {
	disk = *argv;
    }
    else {
	disk = DFLT_DISK;
    }
    if ((fd = open(disk, O_RDONLY)) < 0) {
	myputs(STDERR_FILENO, "diskinfo: unable to open disk ");
	myputs(STDERR_FILENO, disk);
	putch(STDERR_FILENO, NEWLINE);
	exit(1);
    }
    /* FreeBSD 5.1以降ぐらいで「ioctl(fd, DIOCGDINFO, &disklabel)」*/
    /* を使わなくなった? */
    if (ioctl(fd, DIOCGSECTORSIZE, &secsiz) != 0) {
	myputs(STDERR_FILENO, "diskinfo: can't get sectorsize\n");
	exit(1);
    }
    if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) != 0) {
	myputs(STDERR_FILENO, "diskinfo: can't get mediasize\n");
	exit(1);
    }
    if (ioctl(fd, DIOCGFWSECTORS, &nsecs) != 0) {
	myputs(STDERR_FILENO, "diskinfo: can't get number of sectors\n");
	exit(1);
    }
    if (ioctl(fd, DIOCGFWHEADS, &nhead) != 0) {
	myputs(STDERR_FILENO, "diskinfo: can't get number of heads\n");
	exit(1);
    }
    blocks = mediasize / secsiz;
    ncyls = mediasize / (secsiz * nhead * nsecs);
    if (show & SHOW_BLOCKS) {
	num2str(blocks, s);
	myputs(STDOUT_FILENO, s);
	putch(STDOUT_FILENO, NEWLINE);
    }
    if (show & SHOW_BYTES) {
	num2str(mediasize, s);
	myputs(STDOUT_FILENO, s);
	putch(STDOUT_FILENO, NEWLINE);
    }
    if (show & SHOW_MEGABYTES) {
	num2str((mediasize + MEGABYTES - 1) / MEGABYTES, s);
	myputs(STDOUT_FILENO, s);
	putch(STDOUT_FILENO, NEWLINE);
    }
    if (show & SHOW_BYTES_WITH_COMMA) {
	num2commastr(mediasize, s);
	myputs(STDOUT_FILENO, s);
	putch(STDOUT_FILENO, NEWLINE);
    }
    if (show & SHOW_BYTES_WITH_UNITS) {
	m = mediasize / 1024;
	p = 0;
	fraction = 0;
	while ((m > 1000) && (units[p] != EOS)) {
	    fraction = ((m * 10) / 1024) % 10;
	    m /= 1024;
	    ++p;
	}
	num2str(m, s);
	myputs(STDOUT_FILENO, s);
	if (m < 10) {
	    putch(STDOUT_FILENO, '.');
	    num2str(fraction, s);
	    myputs(STDOUT_FILENO, s);
	}
	putch(STDOUT_FILENO, units[p]);
	putch(STDOUT_FILENO, NEWLINE);
    }
    if (show & SHOW_CYLS) {
	num2str(ncyls, s);
	myputs(STDOUT_FILENO, s);
	putch(STDOUT_FILENO, NEWLINE);
    }
    if (show & SHOW_HEAD) {
	num2str(nhead, s);
	myputs(STDOUT_FILENO, s);
	putch(STDOUT_FILENO, NEWLINE);
    }
    if (show & SHOW_SECS) {
	num2str(nsecs, s);
	myputs(STDOUT_FILENO, s);
	putch(STDOUT_FILENO, NEWLINE);
    }
    exit(0);
}

/* Local Variables: */
/* compile-command:"cc -Wall -O -s -o disksize disksize.c" */
/* End: */
