/*****************************************************************************\
 *  $Id: getsize.c 76 2006-02-15 00:49:19Z garlick $
 *****************************************************************************
 *  Copyright (C) 2005 The Regents of the University of California.
 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 *  Written by Jim Garlick <garlick@llnl.gov>.
 *  UCRL-CODE-2003-006.
 *  
 *  This file is part of Scrub, a program for erasing disks.
 *  For details, see <http://www.llnl.gov/linux/scrub/>.
 *  
 *  Scrub 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 2 of the License, or (at your option)
 *  any later version.
 *  
 *  Scrub 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 Scrub; if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
\*****************************************************************************/

#if defined(linux) || defined(sun) || defined(UNIXWARE) || defined(__hpux)
#define _LARGEFILE_SOURCE
#define _FILE_OFFSET_BITS 64
#endif

#if defined(_AIX)
#define _LARGE_FILES
#include <sys/mode.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>

#ifdef STAND
char *prog;
#else
extern char *prog;
#endif


#if defined(linux)
/* scrub-1.7 tested linux 2.6.11-1.1369_FC4 */
/* scrub-1.8 tested Fedora Core 5 */
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <sys/utsname.h>

off_t 
getsize(char *path)
{
    struct utsname ut;
    unsigned long long numbytes;
    int valid_blkgetsize64 = 1;
    int fd;

    fd = open(path, O_RDONLY);
    if (fd < 0) {
        fprintf(stderr, "%s: open ", prog);
        perror(path);
        exit(1);
    }

    /* Ref: e2fsprogs-1.39 - apparently BLKGETSIZE64 doesn't work pre 2.6 */
    if ((uname(&ut) == 0) &&
            ((ut.release[0] == '2') && (ut.release[1] == '.') &&
             (ut.release[2] < '6') && (ut.release[3] == '.')))
                valid_blkgetsize64 = 0;
    if (valid_blkgetsize64) {
        if (ioctl(fd, BLKGETSIZE64, &numbytes) < 0) {
            fprintf(stderr, "%s: ioctl BLKGETSIZE64 ", prog);
            perror(path);
            exit(1);
        }
    } else {
        unsigned long numblocks;

        if (ioctl(fd, BLKGETSIZE, &numblocks) < 0) {
            fprintf(stderr, "%s: ioctl BLKGETSIZE ", prog);
            perror(path);
            exit(1);
        }
        numbytes = (off_t)numblocks*512; /* 2TB limit here */
    }

    (void)close(fd);

    return (off_t)numbytes;
}

#elif defined(__FreeBSD__)
/* scrub-1.7 tested freebsd 5.3-RELEASE-p5 */
#include <sys/ioctl.h>
#include <sys/disk.h>

off_t 
getsize(char *path)
{
    off_t numbytes;
    int fd;

    fd = open(path, O_RDONLY);
    if (fd < 0) {
        fprintf(stderr, "%s: open ", prog);
        perror(path);
        exit(1);
    }
    if (ioctl(fd, DIOCGMEDIASIZE, &numbytes) < 0) {
        fprintf(stderr, "%s: ioctl DIOCGMEDIASIZE ", prog);
        perror(path);
        exit(1);
    }
    (void)close(fd);

    return numbytes;
}

#elif defined(sun)
/* scrub-1.7 tested solaris 5.9 */
#include <sys/ioctl.h>
#include <sys/dkio.h>
#include <sys/vtoc.h>

off_t 
getsize(char *path)
{
    struct dk_minfo dkmp;
    int fd;

    fd = open(path, O_RDONLY);
    if (fd < 0) {
        fprintf(stderr, "%s: open ", prog);
        perror(path);
        exit(1);
    }
    if (ioctl(fd, DKIOCGMEDIAINFO, &dkmp) < 0) {
        fprintf(stderr, "%s: ioctl DKIOCGMEDIAINFO ", prog);
        perror(path);
        exit(1);
    }
    (void)close(fd);

    return (off_t)dkmp.dki_capacity * dkmp.dki_lbsize;
}

#elif defined(__APPLE__)
/* scrub-1.7 tested OS X 7.9.0 */
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/disk.h>

off_t 
getsize(char *path)
{
    uint32_t blocksize;
    uint64_t blockcount;
    int fd;

    fd = open(path, O_RDONLY);
    if (fd < 0) {
        fprintf(stderr, "%s: open ", prog);
        perror(path);
        exit(1);
    }
    if (ioctl(fd, DKIOCGETBLOCKSIZE, &blocksize) < 0) {
        fprintf(stderr, "%s: ioctl DKIOCGETBLOCKSIZE ", prog);
        perror(path);
        exit(1);
    }
    if (ioctl(fd, DKIOCGETBLOCKCOUNT, &blockcount) < 0) {
        fprintf(stderr, "%s: ioctl DKIOGETBLOCKCOUNT ", prog);
        perror(path);
        exit(1);
    }
    (void)close(fd);

    return (off_t)blockcount * blocksize;
}
#elif defined(_AIX)
/* scrub-1.7 tested AIX 5.1 and 5.3 */
/* scrub-1.8 tested AIX 5.2 */
#include <sys/ioctl.h>
#include <sys/devinfo.h>

off_t 
getsize(char *path)
{
    int fd;
    struct devinfo devinfo;
    off_t size;

    fd = open(path, O_RDONLY);
    if (fd < 0) {
        fprintf(stderr, "%s: open ", prog);
        perror(path);
        exit(1);
    }
    if (ioctl(fd, IOCINFO, &devinfo) == -1) {
        fprintf(stderr, "%s: ioctl IOCINFO ", prog);
        perror(path);
        exit(1);
    }
    switch (devinfo.devtype) {
        case DD_DISK:   /* disk */
            size = (off_t)devinfo.un.dk.segment_size * devinfo.un.dk.segment_count;
            break;
        case DD_SCDISK: /* scsi disk */
            size = (off_t)devinfo.un.scdk.blksize * devinfo.un.scdk.numblks;
            break;
        default:        /* unknown */
            size = 0;
            break;
    }

    (void)close(fd);
    return size;
}
#elif defined (__hpux)

#include <stropts.h>
#include <sys/scsi.h>

off_t
getsize(char *path)
{
    int fd;
    struct capacity cap;

    fd = open(path, O_RDONLY);
    if (fd < 0) {
        fprintf(stderr, "%s: open ", prog);
        perror(path);
        exit(1);
    }
    if (ioctl(fd, SIOC_CAPACITY, &cap) == -1) {
        fprintf(stderr, "%s: ioctl SIOC_CAPACITY ", prog);
        perror(path);
        exit(1);
    }

    (void)close(fd);
    return (off_t)cap.lba * cap.blksz;
}

#else
/* Unimplemented!  Scrub will tell user to use -s.
 */
off_t 
getsize(char *path)
{
    return 0;
}
#endif

void
size2str(char *str, int len, off_t size)
{
    off_t eb = size >> 60;
    off_t pb = size >> 50;
    off_t tb = size >> 40;
    off_t gb = size >> 30;
    off_t mb = size >> 20;
    off_t kb = size >> 10;
    off_t num = 0;
    char *unit = NULL;

    if (eb >= 1) {
        num = eb; unit = "EB";
    } else if (pb >= 1) {
        num = pb; unit = "PB";
    } else if (tb >= 1) {
        num = tb; unit = "TB";
    } else if (gb >= 1) {
        num = gb; unit = "GB";
    } else if (mb >= 1) {
        num = mb; unit = "MB";
    } else if (kb >= 1) {
        num = kb; unit = "KB";
    } 
   
    if (unit)
        snprintf(str, len, "%lld bytes (~%lld%s)", (long long int)size, 
                 (long long int)num, unit);
    else
        snprintf(str, len, "%lld bytes", (long long int)size);
}

off_t
str2size(char *str)
{
    char *endptr;
    unsigned long long size;
    int shift = 0;

    if (sscanf(str, "%llu", &size) != 1) /* XXX hpux has no strtoull() */
        goto err;
    for (endptr = str; *endptr; endptr++)
        if (*endptr < '0' || *endptr > '9')
            break;
    if (endptr) {
        switch (*endptr) {
            case 'K':
            case 'k':
                shift = 10;
                break;
            case 'M':
            case 'm':
                shift = 20;
                break;
            case 'G':
            case 'g':
                shift = 30;
                break;
            case 'T':
            case 't':
                shift = 40;
                break;
            case 'P':
            case 'p':
                shift = 50;
                break;
            case 'E':
            case 'e':
                shift = 60;
                break;
            case '\0':
                break;
            default:
                goto err;
        }
        if (shift > 0) {
            if (shift > sizeof(size)*8)
                goto err;
            if ((size >> (sizeof(size)*8 - shift - 1)) > 0)
                goto err;
            size <<= shift;
        }
    }
    return (off_t)size;
err:
    return 0;
}

int
str2int(char *str)
{
    off_t val = str2size(str);

    if (val > 0x7fffffffL || val < 0)
        val = 0;
    return (int)val;
}

#ifdef STAND
int
main(int argc, char *argv[])
{
    off_t sz;
    struct stat sb;
    char buf[80];

    prog = basename(argv[0]);
    if (argc != 2) {
        fprintf(stderr, "Usage: %s [file|string]\n", prog);
        exit(1);
    }
    if (stat(argv[1], &sb) < 0) {
        if (*argv[1] == '/') {
            fprintf(stderr, "%s: could not stat special file\n", prog);
            exit(1);
        }
        sz = str2size(argv[1]);
	    if (sz == 0) {
                fprintf(stderr, "%s: error parsing size string\n", prog);
                exit(1);
	    }
    } else {
        if (!S_ISCHR(sb.st_mode) && !S_ISBLK(sb.st_mode)) {
            fprintf(stderr, "%s: file must be block or char special\n", prog);
            exit(1);
        }
        sz = getsize(argv[1]);
	    if (sz == 0) {
                fprintf(stderr, "%s: could not determine device size\n", prog);
                exit(1);
	    }
    }
    if (sz != 0) {
        size2str(buf, sizeof(buf), sz); 
        printf("%s\n", buf);
    }
    exit(0);
}
#endif

/*
 * vi:tabstop=4 shiftwidth=4 expandtab
 */


syntax highlighted by Code2HTML, v. 0.9.1