/************************************************************\
 * Copyright 2001 The Regents of the University of California.
 * Copyright 2007 Lawrence Livermore National Security, LLC.
 * (c.f. DISCLAIMER, COPYING)
 *
 * This file is part of Scrub.
 * For details, see https://github.com/chaos/scrub.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
\************************************************************/

#if HAVE_CONFIG_H
#include "config.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>
#include <string.h>
#if HAVE_STDINT_H
#include <stdint.h>
#endif
#if HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#if HAVE_LINUX_FS_H
#include <linux/fs.h>
#endif
#if HAVE_SYS_DEVINFO_H
#include <sys/devinfo.h>
#endif
#if HAVE_SYS_DISK_H
#include <sys/disk.h>
#endif
#if HAVE_SYS_DKIO_H
#include <sys/dkio.h>
#endif
#if HAVE_SYS_SCSI_H
#include <sys/scsi.h>
#endif

#include "getsize.h"

extern char *prog;

#if defined (BLKGETSIZE) && defined(BLKGETSIZE64)
/* scrub-1.7 tested linux 2.6.11-1.1369_FC4 */
/* scrub-1.8 tested Fedora Core 5 */
#include <sys/utsname.h>
typedef unsigned long long u64; /* for BLKGETSIZE64 (slackware) */

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

    if ((fd = open(path, O_RDONLY)) < 0)
        goto error;

    /* 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)
            goto error;
    } else {
        unsigned long numblocks;

        if (ioctl(fd, BLKGETSIZE, &numblocks) < 0)
            goto error;
        numbytes = (off_t)numblocks*512; /* 2TB limit here */
    }

    if (close(fd) < 0)
        goto error;
    *sizep = (off_t)numbytes;
    return 0;
error:
    if (fd != -1)
        (void)close(fd);
    return -1;
}

#elif defined(DIOCGMEDIASIZE)
/* scrub-1.7 tested freebsd 5.3-RELEASE-p5 */
int
getsize(char *path, off_t *sizep)
{
    off_t numbytes;
    int fd = -1;

    if ((fd = open(path, O_RDONLY)) < 0)
        goto error;
    if (ioctl(fd, DIOCGMEDIASIZE, &numbytes) < 0)
        goto error;
    if (close(fd) < 0)
        goto error;
    *sizep = numbytes;
    return 0;
error:
    if (fd != -1)
        (void)close(fd);
    return -1;
}

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

int
getsize(char *path, off_t *sizep)
{
    struct dk_minfo dkmp;
    int fd = -1;

    if ((fd = open(path, O_RDONLY)) < 0)
        goto error;
    if (ioctl(fd, DKIOCGMEDIAINFO, &dkmp) < 0)
        goto error;
    if (close(fd) < 0)
        goto error;
    *sizep = (off_t)dkmp.dki_capacity * dkmp.dki_lbsize;
    return 0;
error:
    if (fd != -1)
        (void)close(fd);
    return -1;
}

#elif defined(DKIOCGETBLOCKSIZE) && defined(DKIOCGETBLOCKCOUNT)
/* scrub-1.7 tested OS X 7.9.0 */

int
getsize(char *path, off_t *sizep)
{
    uint32_t blocksize;
    uint64_t blockcount;
    int fd = -1;

    if ((fd = open(path, O_RDONLY)) < 0)
        goto error;
    if (ioctl(fd, DKIOCGETBLOCKSIZE, &blocksize) < 0)
        goto error;
    if (ioctl(fd, DKIOCGETBLOCKCOUNT, &blockcount) < 0)
        goto error;
    if (close(fd) < 0)
        goto error;
    *sizep = (off_t)blockcount * blocksize;
    return 0;
error:
    if (fd != -1)
        (void)close(fd);
    return -1;
}

#elif defined(IOCINFO)
/* scrub-1.7 tested AIX 5.1 and 5.3 */
/* scrub-1.8 tested AIX 5.2 */

int
getsize(char *path, off_t *sizep)
{
    int fd = -1;
    struct devinfo devinfo;
    off_t size;

    if ((fd = open(path, O_RDONLY)) < 0)
        goto error;
    if (ioctl(fd, IOCINFO, &devinfo) == -1)
        goto error;
    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 */
#ifdef DF_LGDSK
            /* T.P.Starrin@NASA.gov for AIX 6.1 */
            size = (off_t)((unsigned)(devinfo.un.scdk64.hi_numblks << 32) | ((unsigned)devinfo.un.scdk64.lo_numblks)) * devinfo.un.scdk64.blksize;
#else
            size = (off_t)devinfo.un.scdk.blksize * devinfo.un.scdk.numblks;
#endif
            break;
        default:        /* unknown */
            size = 0;
            break;
    }
    if (close(fd) < 0)
        goto error;
    *sizep = size;
    return 0;
error:
    if (fd != -1)
        (void)close(fd);
    return -1;
}

#elif defined (SIOC_CAPACITY)

#include <stropts.h>

int
getsize(char *path, off_t *sizep)
{
    int fd = -1;
    struct capacity cap;

    if ((fd = open(path, O_RDONLY)) < 0)
        goto error;
    if (ioctl(fd, SIOC_CAPACITY, &cap) == -1)
        goto error;
    if (close(fd) < 0)
        goto error;
    *sizep = (off_t)cap.lba * cap.blksz;
    return 0;
error:
    if (fd != -1)
        (void)close(fd);
    return -1;
}

#elif defined(DIOCGDISKINFO)

int
getsize(char *path, off_t *sizep)
{
    int fd;
    prop_dictionary_t disk_dict, geom_dict;
    uint64_t secperunit;
    uint32_t secsize;

    fd = open(path, O_RDONLY);
    if (fd == -1)
        return -1;

    if (prop_dictionary_recv_ioctl(fd, DIOCGDISKINFO, &disk_dict) != 0)
        return -1;
    if (close(fd) == -1)
        return -1;

    geom_dict = prop_dictionary_get(disk_dict, "geometry");
    if (geom_dict == NULL)
        return -1;

    if (!prop_dictionary_get_uint64(geom_dict, "sectors-per-unit", &secperunit))
        return -1;
    if (!prop_dictionary_get_uint32(geom_dict, "sector-size",      &secsize))
        return -1;
    *sizep = secperunit * secsize;

    return 0;
}

#else
/* Unimplemented!  Scrub will tell user to use -s.
 */
int
getsize(char *path, off_t *sizep)
{
    errno = ENOSYS;
    return -1;
}
#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 >= 10) {
        num = pb; unit = "PB";
    } else if (tb >= 10) {
        num = tb; unit = "TB";
    } else if (gb >= 10) {
        num = gb; unit = "GB";
    } else if (mb >= 10) {
        num = mb; unit = "MB";
    } else if (kb >= 10) {
        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;
}

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