/* libparted - a library for manipulating disk partitions
    Copyright (C) 1999-2014, 2019-2021 Free Software Foundation, Inc.

    This program 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 3 of the License, or
    (at your option) any later version.

    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#define PROC_DEVICES_BUFSIZ 16384

#include <config.h>
#include <arch/linux.h>
#include <linux/blkpg.h>
#include <parted/parted.h>
#include <parted/debug.h>
#if defined __s390__ || defined __s390x__
#include <parted/fdasd.h>
#endif

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdio.h>
#include <syscall.h>
#include <unistd.h>
#include <stdbool.h>
#include <dirent.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>        /* for uname() */
#include <scsi/scsi.h>
#include <assert.h>
#include <sys/sysmacros.h>
#ifdef ENABLE_DEVICE_MAPPER
#include <libdevmapper.h>
#endif

#include "../architecture.h"
#include "dirname.h"
#include "xstrtol.h"
#include "xalloc.h"

#if ENABLE_NLS
#  include <libintl.h>
#  define _(String) dgettext (PACKAGE, String)
#else
#  define _(String) (String)
#endif /* ENABLE_NLS */

/* The __attribute__ feature is available in gcc versions 2.5 and later.
   The __-protected variants of the attributes 'format' and 'printf' are
   accepted by gcc versions 2.6.4 (effectively 2.7) and later.  */
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7)
# define _GL_ATTRIBUTE_FORMAT(spec) __attribute__ ((__format__ spec))
#else
# define _GL_ATTRIBUTE_FORMAT(spec) /* empty */
#endif

#define STRPREFIX(a, b) (strncmp (a, b, strlen (b)) == 0)

#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))

#ifndef __NR__llseek
#define __NR__llseek 140
#endif

#ifndef SCSI_IOCTL_SEND_COMMAND
#define SCSI_IOCTL_SEND_COMMAND 1
#endif

/* from <linux/hdreg.h> */
#define HDIO_GETGEO             0x0301  /* get device geometry */
#define HDIO_GET_IDENTITY       0x030d  /* get IDE identification info */

#define RD_MODE (O_RDONLY)
#define WR_MODE (O_WRONLY)
#define RW_MODE (O_RDWR)

struct hd_geometry {
        unsigned char heads;
        unsigned char sectors;
        unsigned short cylinders;
        unsigned long start;
};

struct ata7_sectinfo {
        int valid1:1;
        int valid2:1;
        int rsv:26;
        int multiplier:4;
};

/* structure returned by HDIO_GET_IDENTITY, as per ANSI ATA2 rev.2f spec */
struct hd_driveid {
        unsigned short  config;         /* lots of obsolete bit flags */
        unsigned short  cyls;           /* "physical" cyls */
        unsigned short  reserved2;      /* reserved (word 2) */
        unsigned short  heads;          /* "physical" heads */
        unsigned short  track_bytes;    /* unformatted bytes per track */
        unsigned short  sector_bytes;   /* unformatted bytes per sector */
        unsigned short  sectors;        /* "physical" sectors per track */
        unsigned short  vendor0;        /* vendor unique */
        unsigned short  vendor1;        /* vendor unique */
        unsigned short  vendor2;        /* vendor unique */
        unsigned char   serial_no[20];  /* 0 = not_specified */
        unsigned short  buf_type;
        unsigned short  buf_size;       /* 512 byte increments;
                                                         0 = not_specified */
        unsigned short  ecc_bytes;      /* for r/w long cmds;
                                                         0 = not_specified */
        unsigned char   fw_rev[8];      /* 0 = not_specified */
        char            model[40];      /* 0 = not_specified */
        unsigned char   max_multsect;   /* 0=not_implemented */
        unsigned char   vendor3;        /* vendor unique */
        unsigned short  dword_io;       /* 0=not_implemented; 1=implemented */
        unsigned char   vendor4;        /* vendor unique */
        unsigned char   capability;     /* bits 0:DMA 1:LBA 2:IORDYsw
                                                3:IORDYsup*/
        unsigned short  reserved50;     /* reserved (word 50) */
        unsigned char   vendor5;        /* vendor unique */
        unsigned char   tPIO;           /* 0=slow, 1=medium, 2=fast */
        unsigned char   vendor6;        /* vendor unique */
        unsigned char   tDMA;           /* 0=slow, 1=medium, 2=fast */
        unsigned short  field_valid;    /* bits 0:cur_ok 1:eide_ok */
        unsigned short  cur_cyls;       /* logical cylinders */
        unsigned short  cur_heads;      /* logical heads */
        unsigned short  cur_sectors;    /* logical sectors per track */
        unsigned short  cur_capacity0;  /* logical total sectors on drive */
        unsigned short  cur_capacity1;  /*  (2 words, misaligned int)     */
        unsigned char   multsect;       /* current multiple sector count */
        unsigned char   multsect_valid; /* when (bit0==1) multsect is ok */
        unsigned int    lba_capacity;   /* total number of sectors */
        unsigned short  dma_1word;      /* single-word dma info */
        unsigned short  dma_mword;      /* multiple-word dma info */
        unsigned short  eide_pio_modes; /* bits 0:mode3 1:mode4 */
        unsigned short  eide_dma_min;   /* min mword dma cycle time (ns) */
        unsigned short  eide_dma_time;  /* recommended mword dma cycle
                                           time (ns) */
        unsigned short  eide_pio;       /* min cycle time (ns), no IORDY  */
        unsigned short  eide_pio_iordy; /* min cycle time (ns), with IORDY */
        unsigned short  words69_70[2];  /* reserved words 69-70 */
        /* HDIO_GET_IDENTITY currently returns only words 0 through 70 */
        unsigned short  words71_74[4];  /* reserved words 71-74 */
        unsigned short  queue_depth;    /*  */
        unsigned short  words76_79[4];  /* reserved words 76-79 */
        unsigned short  major_rev_num;  /*  */
        unsigned short  minor_rev_num;  /*  */
        unsigned short  command_set_1;  /* bits 0:Smart 1:Security 2:Removable
                                                3:PM */
        unsigned short  command_set_2;  /* bits 14:Smart Enabled 13:0 zero */
        unsigned short  cfsse;          /* command set-feature supported
                                           extensions */
        unsigned short  cfs_enable_1;   /* command set-feature enabled */
        unsigned short  cfs_enable_2;   /* command set-feature enabled */
        unsigned short  csf_default;    /* command set-feature default */
        unsigned short  dma_ultra;      /*  */
        unsigned short  word89;         /* reserved (word 89) */
        unsigned short  word90;         /* reserved (word 90) */
        unsigned short  CurAPMvalues;   /* current APM values */
        unsigned short  word92;         /* reserved (word 92) */
        unsigned short  hw_config;      /* hardware config */
        unsigned short  words94_105[12];/* reserved words 94-105 */
        struct ata7_sectinfo ata7_sectinfo; /* ATAPI/ATA7 physical and logical
                                               sector size */
        unsigned short  words107_116[10];/* reserved words 107-116 */
        unsigned int    logical_sectsize;/* ATAPI/ATA7 logical sector size */
        unsigned short  words119_125[7];/* reserved words 119-125 */
        unsigned short  last_lun;       /* reserved (word 126) */
        unsigned short  word127;        /* reserved (word 127) */
        unsigned short  dlf;            /* device lock function
                                         * 15:9 reserved
                                         * 8    security level 1:max 0:high
                                         * 7:6  reserved
                                         * 5    enhanced erase
                                         * 4    expire
                                         * 3    frozen
                                         * 2    locked
                                         * 1    en/disabled
                                         * 0    capability
                                         */
        unsigned short  csfo;           /* current set features options
                                         * 15:4 reserved
                                         * 3    auto reassign
                                         * 2    reverting
                                         * 1    read-look-ahead
                                         * 0    write cache
                                         */
        unsigned short  words130_155[26];/* reserved vendor words 130-155 */
        unsigned short  word156;
        unsigned short  words157_159[3]; /* reserved vendor words 157-159 */
        unsigned short  words160_255[95];/* reserved words 160-255 */
};

/* from <linux/fs.h> */
#define BLKRRPART  _IO(0x12,95) /* re-read partition table */
#define BLKGETSIZE _IO(0x12,96) /* return device size */
#define BLKFLSBUF  _IO(0x12,97) /* flush buffer cache */
#define BLKSSZGET  _IO(0x12,104) /* get block device sector size */
#define BLKGETLASTSECT  _IO(0x12,108) /* get last sector of block device */
#define BLKSETLASTSECT  _IO(0x12,109) /* set last sector of block device */

/* return device size in bytes (u64 *arg) */
#define BLKGETSIZE64 _IOR(0x12,114,size_t)

struct blkdev_ioctl_param {
        unsigned int block;
        size_t content_length;
        char * block_contents;
};

/* from <linux/major.h> */
#define IDE0_MAJOR              3
#define IDE1_MAJOR              22
#define IDE2_MAJOR              33
#define IDE3_MAJOR              34
#define IDE4_MAJOR              56
#define IDE5_MAJOR              57
#define SCSI_CDROM_MAJOR        11
#define SCSI_DISK0_MAJOR        8
#define SCSI_DISK1_MAJOR        65
#define SCSI_DISK2_MAJOR        66
#define SCSI_DISK3_MAJOR        67
#define SCSI_DISK4_MAJOR        68
#define SCSI_DISK5_MAJOR        69
#define SCSI_DISK6_MAJOR        70
#define SCSI_DISK7_MAJOR        71
#define SCSI_DISK8_MAJOR        128
#define SCSI_DISK9_MAJOR        129
#define SCSI_DISK10_MAJOR       130
#define SCSI_DISK11_MAJOR       131
#define SCSI_DISK12_MAJOR       132
#define SCSI_DISK13_MAJOR       133
#define SCSI_DISK14_MAJOR       134
#define SCSI_DISK15_MAJOR       135
#define COMPAQ_SMART2_MAJOR     72
#define COMPAQ_SMART2_MAJOR1    73
#define COMPAQ_SMART2_MAJOR2    74
#define COMPAQ_SMART2_MAJOR3    75
#define COMPAQ_SMART2_MAJOR4    76
#define COMPAQ_SMART2_MAJOR5    77
#define COMPAQ_SMART2_MAJOR6    78
#define COMPAQ_SMART2_MAJOR7    79
#define COMPAQ_SMART_MAJOR      104
#define COMPAQ_SMART_MAJOR1     105
#define COMPAQ_SMART_MAJOR2     106
#define COMPAQ_SMART_MAJOR3     107
#define COMPAQ_SMART_MAJOR4     108
#define COMPAQ_SMART_MAJOR5     109
#define COMPAQ_SMART_MAJOR6     110
#define COMPAQ_SMART_MAJOR7     111
#define DAC960_MAJOR            48
#define ATARAID_MAJOR           114
#define I2O_MAJOR1              80
#define I2O_MAJOR2              81
#define I2O_MAJOR3              82
#define I2O_MAJOR4              83
#define I2O_MAJOR5              84
#define I2O_MAJOR6              85
#define I2O_MAJOR7              86
#define I2O_MAJOR8              87
#define UBD_MAJOR               98
#define DASD_MAJOR              94
#define VIODASD_MAJOR           112
#define AOE_MAJOR               152
#define SX8_MAJOR1              160
#define SX8_MAJOR2              161
#define XVD_MAJOR               202
#define SDMMC_MAJOR             179
#define LOOP_MAJOR              7
#define MD_MAJOR                9
#define BLKEXT_MAJOR            259
#define RAM_MAJOR               1

#define SCSI_BLK_MAJOR(M) (                                             \
                (M) == SCSI_DISK0_MAJOR                                 \
                || (M) == SCSI_CDROM_MAJOR                              \
                || ((M) >= SCSI_DISK1_MAJOR && (M) <= SCSI_DISK7_MAJOR) \
                || ((M) >= SCSI_DISK8_MAJOR && (M) <= SCSI_DISK15_MAJOR))

/* Maximum number of partitions supported by linux. */
#define MAX_NUM_PARTS		64

static char* _device_get_part_path (PedDevice const *dev, int num);
static int _partition_is_mounted_by_path (const char* path);
static unsigned int _device_get_partition_range(PedDevice const* dev);
static int _device_open (PedDevice* dev, int flags);
static int _device_open_ro (PedDevice* dev);
static int _device_close (PedDevice* dev);

static int
_read_fd (int fd, char **buf)
{
        char* p;
        size_t size = PROC_DEVICES_BUFSIZ;
        int s, filesize = 0;

        *buf = malloc (size * sizeof (char));
        if (*buf == 0) {
                return -1;
        }

        do {
                p = &(*buf) [filesize];
                s = read (fd, p, PROC_DEVICES_BUFSIZ);
                /* exit if there is an error or EOF is reached */
                if (s <= 0)
                        break;
                filesize += s;
                size += s;
                char *new_buf = realloc (*buf, size);
                if (new_buf == NULL) {
                        int saved_errno = errno;
                        free (*buf);
                        errno = saved_errno;
                        return -1;
                }
                *buf = new_buf;
        } while (1);

        if (filesize == 0 && s < 0) {
                free (*buf);
                *buf = NULL;
                return -1;
        } else {
                char *new_buf = realloc (*buf, filesize + 1);
                if (new_buf == NULL) {
                        int saved_errno = errno;
                        free (*buf);
                        errno = saved_errno;
                        return -1;
                }
                *buf = new_buf;
                (*buf)[filesize] = '\0';
        }

        return filesize;
}

static int
_major_type_in_devices (int major, const char* type)
{
        int fd;
        char* buf = NULL;
        char* line;
        char* end;
        int bd = 0;
        char c;

        fd = open ("/proc/devices", O_RDONLY);
        if (fd < 0)
                return 0;

        if (_read_fd(fd, &buf) < 0) {
                close(fd);
                return 0;
        }

        line = buf;
        end = strchr(line, '\n');
        while (end) {
                char *name;
                int maj;

                c = *end;
                *end = '\0';

                if (!bd) {
                        if (!strncmp(line, "Block devices:", 14))
                                bd = 1;
                        goto next;
                }

                name = strrchr(line, ' ');
                if (!name || strcmp(name+1, type))
                        goto next;

                maj = strtol(line, &name, 10);
                if (maj == major) {
                        free(buf);
                        close(fd);
                        return 1;
                }

next:
                *end = c;
                line = end+1;
                end = strchr(line, '\n');
        }
        free(buf);
        close(fd);
        return 0;
}

static int
_is_ide_major (int major)
{
        switch (major) {
                case IDE0_MAJOR:
                case IDE1_MAJOR:
                case IDE2_MAJOR:
                case IDE3_MAJOR:
                case IDE4_MAJOR:
                case IDE5_MAJOR:
                        return 1;

                default:
                        return 0;
        }
}

static int
_is_cpqarray_major (int major)
{
        return ((COMPAQ_SMART2_MAJOR <= major && major <= COMPAQ_SMART2_MAJOR7)
             || (COMPAQ_SMART_MAJOR <= major && major <= COMPAQ_SMART_MAJOR7));
}

static int
_is_i2o_major (int major)
{
        return (I2O_MAJOR1 <= major && major <= I2O_MAJOR8);
}

static int
_is_sx8_major (int major)
{
        return (SX8_MAJOR1 <= major && major <= SX8_MAJOR2);
}

static int
_is_virtblk_major (int major)
{
        return _major_type_in_devices (major, "virtblk");
}

static int
_is_blkext_major (int major)
{
        return _major_type_in_devices (major, "blkext");
}

#ifdef ENABLE_DEVICE_MAPPER
static int
_dm_task_run_wait (struct dm_task *task, uint32_t cookie)
{
        int rc = 0;

        rc = dm_task_run (task);
        dm_udev_wait (cookie);

        return rc;
}

static int
_is_dm_major (int major)
{
        return _major_type_in_devices (major, "device-mapper");
}

static int
_dm_maptype (PedDevice *dev)
{
        LinuxSpecific*  arch_specific = LINUX_SPECIFIC (dev);
        struct dm_task *dmt;
        uint64_t start, length;
        char *target_type = NULL;
        char *params;
        int r = -1;
        const char* dev_dir = getenv ("DM_DEV_DIR");

        if (dev_dir && *dev_dir && !dm_set_dev_dir(dev_dir))
                return r;

        if (!(dmt = dm_task_create(DM_DEVICE_TABLE)))
                return r;

        if (!dm_task_set_major_minor(dmt, arch_specific->major,
                                     arch_specific->minor, 0))
                goto bad;

        dm_task_no_open_count(dmt);

        if (!dm_task_run(dmt))
                goto bad;

        dm_get_next_target(dmt, NULL, &start, &length, &target_type, &params);

        arch_specific->dmtype = strdup(target_type ? target_type : "NO-TARGET");
        if (arch_specific->dmtype == NULL)
                goto bad;
        r = 0;
bad:
        dm_task_destroy(dmt);
        return r;
}

/* Return nonzero if device-mapper device, DEVPATH, is part of a dmraid
   array.  Use the heuristic of checking for the string "DMRAID-" at the
   start of its UUID.  */
static int
_is_dmraid_device (const char *devpath)
{
        int rc = 0;

        char const *dm_name = strrchr (devpath, '/');
        char const *dm_basename = dm_name && *(++dm_name) ? dm_name : devpath;
        struct dm_task *task = dm_task_create (DM_DEVICE_DEPS);
        if (!task)
                return 0;

        dm_task_set_name (task, dm_basename);
        if (!dm_task_run (task))
                goto err;

        const char *dmraid_uuid = dm_task_get_uuid (task);
        if (STRPREFIX (dmraid_uuid, "DMRAID-"))
                rc = 1;

err:
        dm_task_destroy (task);
        return rc;
}

/* We consider a dm device that is a linear mapping with a  *
 * single target that also is a dm device to be a partition */

static int
_dm_is_part (const char *path)
{
        int rc = 0;
        struct dm_task *task = dm_task_create (DM_DEVICE_DEPS);
        if (!task)
                return 0;

        dm_task_set_name(task, path);
        if (!dm_task_run(task))
                goto err;

        struct dm_info *info = alloca (sizeof *info);
        memset(info, '\0', sizeof *info);
        dm_task_get_info (task, info);
        if (!info->exists)
                goto err;

        struct dm_deps *deps = dm_task_get_deps (task);
        if (!deps)
                goto err;

        if (deps->count != 1)
                goto err;
        if (!_is_dm_major (major (deps->device[0])))
                goto err;
        dm_task_destroy (task);
        if (!(task = dm_task_create (DM_DEVICE_TABLE)))
                return 0;
        dm_task_set_name (task, path);
        if (!dm_task_run (task))
                goto err;

        char *target_type = NULL;
        char *params = NULL;
        uint64_t start, length;

        dm_get_next_target (task, NULL, &start, &length, &target_type, &params);
        if (strcmp (target_type, "linear"))
                goto err;
        rc = 1;

err:
        dm_task_destroy(task);
        return rc;
}

static int
_probe_dm_devices ()
{
       DIR*            mapper_dir;
       struct dirent*  dent;
       char            buf [512];      /* readdir(3) claims d_name[256] */
       struct stat     st;

       mapper_dir = opendir ("/dev/mapper");
       if (!mapper_dir)
               return 0;

       /* Search the /dev/mapper directory for devices w/ the same major
        * number that was returned from _probe_lvm_major().
        */
       while ((dent = readdir (mapper_dir))) {
               if (strcmp (dent->d_name, ".")  == 0 ||
                   strcmp (dent->d_name, "..") == 0)
                       continue;

               snprintf (buf, sizeof (buf), "/dev/mapper/%s", dent->d_name);

               if (stat (buf, &st) != 0)
                       continue;

               if (_is_dm_major(major(st.st_rdev)) && _is_dmraid_device (buf)
                   && !_dm_is_part(buf))
                       _ped_device_probe (buf);
       }
       closedir (mapper_dir);

       return 1;
}
#endif

static int
_device_stat (PedDevice* dev, struct stat * dev_stat)
{
        PED_ASSERT (dev != NULL);
        PED_ASSERT (!dev->external_mode);

        while (1) {
                if (!stat (dev->path, dev_stat)) {
                        return 1;
                } else {
                        if (ped_exception_throw (
                                PED_EXCEPTION_ERROR,
                                PED_EXCEPTION_RETRY_CANCEL,
                                _("Could not stat device %s - %s."),
                                dev->path,
                                strerror (errno))
                                        != PED_EXCEPTION_RETRY)
                                return 0;
                }
        }
}

static int
_device_probe_type (PedDevice* dev)
{
        struct stat             dev_stat;
        int                     dev_major;
        int                     dev_minor;
        LinuxSpecific*          arch_specific = LINUX_SPECIFIC (dev);

        if (!_device_stat (dev, &dev_stat))
                return 0;

        if (!S_ISBLK(dev_stat.st_mode)) {
                dev->type = PED_DEVICE_FILE;
                return 1;
        }

        arch_specific->major = dev_major = major (dev_stat.st_rdev);
        arch_specific->minor = dev_minor = minor (dev_stat.st_rdev);

        if (SCSI_BLK_MAJOR (dev_major) && (dev_minor % 0x10 == 0)) {
                dev->type = PED_DEVICE_SCSI;
        } else if (_is_ide_major (dev_major) && (dev_minor % 0x40 == 0)) {
                dev->type = PED_DEVICE_IDE;
        } else if (dev_major == DAC960_MAJOR && (dev_minor % 0x8 == 0)) {
                dev->type = PED_DEVICE_DAC960;
        } else if (dev_major == ATARAID_MAJOR && (dev_minor % 0x10 == 0)) {
                dev->type = PED_DEVICE_ATARAID;
        } else if (dev_major == AOE_MAJOR && (dev_minor % 0x10 == 0)) {
                dev->type = PED_DEVICE_AOE;
        } else if (dev_major == DASD_MAJOR && (dev_minor % 0x4 == 0)) {
                dev->type = PED_DEVICE_DASD;
        } else if (dev_major == VIODASD_MAJOR && (dev_minor % 0x8 == 0)) {
                dev->type = PED_DEVICE_VIODASD;
        } else if (_is_sx8_major(dev_major) && (dev_minor % 0x20 == 0)) {
                dev->type = PED_DEVICE_SX8;
        } else if (_is_i2o_major (dev_major) && (dev_minor % 0x10 == 0)) {
                dev->type = PED_DEVICE_I2O;
        } else if (_is_cpqarray_major (dev_major) && (dev_minor % 0x10 == 0)) {
                dev->type = PED_DEVICE_CPQARRAY;
        } else if (dev_major == UBD_MAJOR && (dev_minor % 0x10 == 0)) {
                dev->type = PED_DEVICE_UBD;
#ifdef ENABLE_DEVICE_MAPPER
        } else if (_is_dm_major(dev_major)) {
                dev->type = PED_DEVICE_DM;
                if (_dm_maptype(dev)) {
                        ped_exception_throw (
                                PED_EXCEPTION_BUG,
                                PED_EXCEPTION_CANCEL,
                                _("Unable to determine the dm type of %s."),
                                dev->path);
                }
#endif
        } else if (dev_major == XVD_MAJOR && (dev_minor % 0x10 == 0)) {
                dev->type = PED_DEVICE_XVD;
        } else if (dev_major == SDMMC_MAJOR && (dev_minor % 0x08 == 0)) {
                dev->type = PED_DEVICE_SDMMC;
        } else if (_is_virtblk_major(dev_major)) {
                dev->type = PED_DEVICE_VIRTBLK;
        } else if (dev_major == LOOP_MAJOR) {
                dev->type = PED_DEVICE_LOOP;
        } else if (dev_major == MD_MAJOR) {
                dev->type = PED_DEVICE_MD;
        } else if (_is_blkext_major(dev_major) && dev->path && strstr(dev->path, "nvme")) {
                dev->type = PED_DEVICE_NVME;
        } else if (dev_major == RAM_MAJOR) {
                dev->type = PED_DEVICE_RAM;
        } else if (_is_blkext_major(dev_major) && dev->path && strstr(dev->path, "pmem")) {
                dev->type = PED_DEVICE_PMEM;
        } else {
                dev->type = PED_DEVICE_UNKNOWN;
        }

        return 1;
}

static int
_get_linux_version ()
{
        static int kver = -1;

        struct utsname uts;
        unsigned int major = 0;
        unsigned int minor = 0;
        unsigned int teeny = 0;

        if (kver != -1)
                return kver;

        if (uname (&uts))
                return kver = 0;
        int n = sscanf (uts.release, "%u.%u.%u", &major, &minor, &teeny);
        assert (n == 2 || n == 3);
        return kver = KERNEL_VERSION (major, minor, teeny);
}

#if USE_BLKID
static void
get_blkid_topology (LinuxSpecific *arch_specific)
{
        arch_specific->probe = blkid_new_probe ();
        if (!arch_specific->probe)
                return;

        if (blkid_probe_set_device(arch_specific->probe,
                                   arch_specific->fd, 0, 0))
                return;

        arch_specific->topology =
                blkid_probe_get_topology(arch_specific->probe);
}
#endif

static void
_device_set_sector_size (PedDevice* dev)
{
        LinuxSpecific*  arch_specific = LINUX_SPECIFIC (dev);
        int sector_size;

        dev->sector_size = PED_SECTOR_SIZE_DEFAULT;
        dev->phys_sector_size = PED_SECTOR_SIZE_DEFAULT;

        PED_ASSERT (dev->open_count);

        if (_get_linux_version() < KERNEL_VERSION (2,3,0)) {
                dev->sector_size = PED_SECTOR_SIZE_DEFAULT;
                return;
        }

        if (ioctl (arch_specific->fd, BLKSSZGET, &sector_size)) {
                ped_exception_throw (
                        PED_EXCEPTION_WARNING,
                        PED_EXCEPTION_OK,
                        _("Could not determine sector size for %s: %s.\n"
                          "Using the default sector size (%lld)."),
                        dev->path, strerror (errno), PED_SECTOR_SIZE_DEFAULT);
        } else {
                dev->sector_size = (long long)sector_size;
                dev->phys_sector_size = dev->sector_size;
        }

#if USE_BLKID
        get_blkid_topology(arch_specific);
        if (!arch_specific->topology) {
                dev->phys_sector_size = 0;
        } else {
                dev->phys_sector_size =
                        blkid_topology_get_physical_sector_size(
                                arch_specific->topology);
        }
        if (dev->phys_sector_size == 0) {
                ped_exception_throw (
                        PED_EXCEPTION_WARNING,
                        PED_EXCEPTION_OK,
                        _("Could not determine physical sector size for %s.\n"
                          "Using the logical sector size (%lld)."),
                        dev->path, dev->sector_size);
                dev->phys_sector_size = dev->sector_size;
        }
#endif

#if defined __s390__ || defined __s390x__
        /* The real_sector_size is currently needed for DASD layouts,
         * so we set it unconditionally. In the long run it should
         * be considered to use the dev->phys_sector_size in label/dasd.c.
         */
        arch_specific->real_sector_size = dev->sector_size;
        /* Return PED_SECTOR_SIZE_DEFAULT for DASDs. */
        if (dev->type == PED_DEVICE_DASD) {
                dev->sector_size = PED_SECTOR_SIZE_DEFAULT;
        }
#endif
}

static int
_kernel_has_blkgetsize64(void)
{
        int version = _get_linux_version();

        if (version >= KERNEL_VERSION (2,5,4)) return 1;
        if (version <  KERNEL_VERSION (2,5,0) &&
            version >= KERNEL_VERSION (2,4,18)) return 1;
        return 0;
}

/* TODO: do a binary search if BLKGETSIZE doesn't work?! */
static PedSector
_device_get_length (PedDevice* dev)
{
        unsigned long           size;
        LinuxSpecific*          arch_specific = LINUX_SPECIFIC (dev);
        uint64_t bytes=0;
        const char*             test_str;
        PedSector               test_size;


        PED_ASSERT (dev->open_count > 0);
        PED_ASSERT (dev->sector_size % PED_SECTOR_SIZE_DEFAULT == 0);

        test_str = getenv ("PARTED_TEST_DEVICE_LENGTH");
        if (test_str
            && xstrtoll (test_str, NULL, 10, &test_size, NULL) == LONGINT_OK)
                return test_size;

        if (_kernel_has_blkgetsize64()) {
                if (ioctl(arch_specific->fd, BLKGETSIZE64, &bytes) == 0) {
                        return bytes / dev->sector_size;
                }
        }

        if (ioctl (arch_specific->fd, BLKGETSIZE, &size)) {
                ped_exception_throw (
                        PED_EXCEPTION_BUG,
                        PED_EXCEPTION_CANCEL,
                        _("Unable to determine the size of %s (%s)."),
                        dev->path,
                        strerror (errno));
                return 0;
        }

        return size;
}

static int
_device_probe_geometry (PedDevice* dev)
{
        LinuxSpecific*          arch_specific = LINUX_SPECIFIC (dev);
        struct stat             dev_stat;
        struct hd_geometry      geometry;
        int                     geometry_is_valid = 0;
        int                     sector_size = 0;

        if (!_device_stat (dev, &dev_stat))
                return 0;
        PED_ASSERT (S_ISBLK (dev_stat.st_mode));

        _device_set_sector_size (dev);

        dev->length = _device_get_length (dev);
        if (!dev->length)
                return 0;

        /* initialize the bios_geom values to something */
        dev->bios_geom.sectors = 0;
        dev->bios_geom.heads = 0;
        dev->bios_geom.cylinders = 0;

        geometry_is_valid = !ioctl (arch_specific->fd, HDIO_GETGEO, &geometry)
                            && geometry.sectors && geometry.heads;

#if defined __s390__ || defined __s390x__
        if (geometry_is_valid) {
#else
        if (!ioctl (arch_specific->fd, BLKSSZGET, &sector_size)) {
                /* get the sector count first */
                dev->bios_geom.sectors = 1 + (sector_size / PED_SECTOR_SIZE_DEFAULT);
                dev->bios_geom.heads = 255;
        } else if (geometry_is_valid) {
                /* if BLKSSZGET failed, use deprecated HDIO_GETGEO result */
#endif
                dev->bios_geom.sectors = geometry.sectors;
                dev->bios_geom.heads = geometry.heads;
        } else {
                ped_exception_throw (
                        PED_EXCEPTION_WARNING,
                        PED_EXCEPTION_OK,
                        _("Could not determine sector size for %s: %s.\n"
                          "Using the default sector size (%lld)."),
                        dev->path, strerror (errno), PED_SECTOR_SIZE_DEFAULT);
                dev->bios_geom.sectors = 2;
                dev->bios_geom.heads = 255;
        }

        dev->bios_geom.cylinders
                = dev->length / (dev->bios_geom.heads
                                 * dev->bios_geom.sectors);
        dev->hw_geom = dev->bios_geom;
        return 1;
}

static char*
strip_name(char* str)
{
        int     i;
        int     end = 0;

        for (i = 0; str[i] != 0; i++) {
                if (!isspace (str[i])
                    || (isspace (str[i]) && !isspace (str[i+1]) && str[i+1])) {
                        str [end] = str[i];
                        end++;
                }
        }
        str[end] = 0;
        return strdup (str);
}

static int
init_ide (PedDevice* dev)
{
        LinuxSpecific*          arch_specific = LINUX_SPECIFIC (dev);
        struct stat             dev_stat;
        struct hd_driveid       hdi;
        PedExceptionOption      ex_status;
        char                    hdi_buf[41];
        int                     sector_multiplier = 0;
        int                     r;

        if (!_device_stat (dev, &dev_stat))
                goto error;

        if (!_device_open_ro (dev))
                goto error;

        r = ioctl (arch_specific->fd, HDIO_GET_IDENTITY, &hdi);
        if (r && errno == EINVAL) {
                /* silently ignore unsupported ioctl */
                dev->model = strdup(_("Generic IDE"));
        } else if (r) {
                ex_status = ped_exception_throw (
                                PED_EXCEPTION_WARNING,
                                PED_EXCEPTION_IGNORE_CANCEL,
                                _("Could not get identity of device %s - %s"),
                                dev->path, strerror (errno));
                switch (ex_status) {
                        case PED_EXCEPTION_CANCEL:
                                goto error_close_dev;

                        case PED_EXCEPTION_UNHANDLED:
                                ped_exception_catch ();
                                /* FALLTHROUGH */
                        case PED_EXCEPTION_IGNORE:
                                dev->model = strdup(_("Generic IDE"));
                                break;
                        default:
                                PED_ASSERT (0);
                                break;
                }
        } else {
                /* hdi.model is not guaranteed to be NULL terminated */
                memcpy (hdi_buf, hdi.model, 40);
                hdi_buf[40] = '\0';
                dev->model = strip_name (hdi_buf);

                if (!hdi.ata7_sectinfo.valid1 && hdi.ata7_sectinfo.valid2)
                        sector_multiplier = hdi.ata7_sectinfo.multiplier;
                else
                        sector_multiplier = 1;

                if (sector_multiplier != 1) {
                        ex_status = ped_exception_throw (
                                PED_EXCEPTION_WARNING,
                                PED_EXCEPTION_IGNORE_CANCEL,
                                _("Device %s has multiple (%d) logical sectors "
                                  "per physical sector.\n"
                                  "GNU Parted supports this EXPERIMENTALLY for "
                                  "some special disk label/file system "
                                  "combinations, e.g. GPT and ext2/3.\n"
                                  "Please consult the web site for up-to-date "
                                  "information."),
                                dev->path, sector_multiplier);

                        switch (ex_status) {
                                case PED_EXCEPTION_CANCEL:
                                        goto error_close_dev;

                                case PED_EXCEPTION_UNHANDLED:
                                        ped_exception_catch ();
                                        /* FALLTHROUGH */
                                case PED_EXCEPTION_IGNORE:
                                        break;
                                default:
                                        PED_ASSERT (0);
                                        break;
                        }
                }

                /* XXX sector_size has not been set yet! */
                /* dev->phys_sector_size = dev->sector_size
                   * sector_multiplier;*/
                dev->phys_sector_size = PED_SECTOR_SIZE_DEFAULT;
        }

        if (!_device_probe_geometry (dev))
                goto error_close_dev;

        _device_close (dev);
        return 1;

error_close_dev:
        _device_close (dev);
error:
        return 0;
}

/* This function reads the /sys entry named "file" for device "dev". */
static char *
read_device_sysfs_file (PedDevice *dev, const char *file)
{
        FILE *f;
        char name_buf[128];
        char buf[256];

        snprintf (name_buf, 127, "/sys/block/%s/device/%s",
                  last_component (dev->path), file);

        if ((f = fopen (name_buf, "r")) == NULL)
                return NULL;

        if (fgets (buf, 255, f) == NULL) {
                fclose (f);
                return NULL;
        }

        fclose (f);
        return strip_name (buf);
}

/* This function sends a query to a SCSI device for vendor and product
 * information.  It uses the deprecated SCSI_IOCTL_SEND_COMMAND to
 * issue this query.
 */
static int
scsi_query_product_info (PedDevice* dev, char **vendor, char **product)
{
        /* The following are defined by the SCSI-2 specification. */
        typedef struct _scsi_inquiry_cmd
        {
                uint8_t op;
                uint8_t lun;          /* bits 5-7 denote the LUN */
                uint8_t page_code;
                uint8_t reserved;
                uint8_t alloc_length;
                uint8_t control;
        } __attribute__((packed)) scsi_inquiry_cmd_t;

        typedef struct _scsi_inquiry_data
        {
                uint8_t peripheral_info;
                uint8_t device_info;
                uint8_t version_info;
                uint8_t _field1;
                uint8_t additional_length;
                uint8_t _reserved1;
                uint8_t _reserved2;
                uint8_t _field2;
                uint8_t vendor_id[8];
                uint8_t product_id[16];
                uint8_t product_revision[4];
                uint8_t vendor_specific[20];
                uint8_t _reserved3[40];
        } __attribute__((packed)) scsi_inquiry_data_t;

        struct scsi_arg
        {
                unsigned int inlen;
                unsigned int outlen;

                union arg_data
                {
                        scsi_inquiry_data_t out;
                        scsi_inquiry_cmd_t  in;
                } data;
        } arg;

        LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
        char    buf[32];

        *vendor = NULL;
        *product = NULL;

        memset (&arg, 0x00, sizeof(struct scsi_arg));
        arg.inlen  = 0;
        arg.outlen = sizeof(scsi_inquiry_data_t);
        arg.data.in.op  = INQUIRY;
        arg.data.in.lun = dev->host << 5;
        arg.data.in.alloc_length = sizeof(scsi_inquiry_data_t);
        arg.data.in.page_code = 0;
        arg.data.in.reserved = 0;
        arg.data.in.control = 0;

        if (ioctl (arch_specific->fd, SCSI_IOCTL_SEND_COMMAND, &arg) < 0)
                return 0;

        memcpy (buf, arg.data.out.vendor_id, 8);
        buf[8] = '\0';
        *vendor = strip_name (buf);

        memcpy (buf, arg.data.out.product_id, 16);
        buf[16] = '\0';
        *product = strip_name (buf);

        return 1;
}

/* This function provides the vendor and product name for a SCSI device.
 * It supports both the modern /sys interface and direct queries
 * via the deprecated ioctl, SCSI_IOCTL_SEND_COMMAND.
 */
static int
scsi_get_product_info (PedDevice* dev, char **vendor, char **product)
{
        *vendor = read_device_sysfs_file (dev, "vendor");
        *product = read_device_sysfs_file (dev, "model");
        if (*vendor && *product)
                return 1;

        return scsi_query_product_info (dev, vendor, product);
}

static int
init_scsi (PedDevice* dev)
{
        struct scsi_idlun
        {
                uint32_t dev_id;
                uint32_t host_unique_id;
        } idlun;

        LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);
        char* vendor;
        char* product;

        if (!_device_open_ro (dev))
                goto error;

        if (ioctl (arch_specific->fd, SCSI_IOCTL_GET_IDLUN, &idlun) < 0) {
                dev->host = 0;
                dev->did = 0;
                if (ped_exception_throw (
                        PED_EXCEPTION_ERROR, PED_EXCEPTION_IGNORE_CANCEL,
                        _("Error initialising SCSI device %s - %s"),
                        dev->path, strerror (errno))
                                != PED_EXCEPTION_IGNORE)
                        goto error_close_dev;
                if (!_device_probe_geometry (dev))
                        goto error_close_dev;
                _device_close (dev);
                return 1;
        }

        dev->host = idlun.host_unique_id;
        dev->did  = idlun.dev_id;

        dev->model = (char*) ped_malloc (8 + 16 + 2);
        if (!dev->model)
                goto error_close_dev;

        if (scsi_get_product_info (dev, &vendor, &product)) {
                sprintf (dev->model, "%.8s %.16s", vendor, product);
                free (vendor);
                free (product);
        } else {
                strcpy (dev->model, "Generic SCSI");
        }

        if (!_device_probe_geometry (dev))
                goto error_close_dev;

        _device_close (dev);
        return 1;

error_close_dev:
        _device_close (dev);
error:
        return 0;
}

static int
init_file (PedDevice* dev)
{
        struct stat     dev_stat;

        if (!_device_stat (dev, &dev_stat))
                goto error;
        if (!_device_open_ro (dev))
                goto error;

        dev->sector_size = PED_SECTOR_SIZE_DEFAULT;
        char *p = getenv ("PARTED_SECTOR_SIZE");
        if (p) {
                int s = atoi (p);
                if (0 < s && s % 512 == 0)
                        dev->sector_size = s;
        }
        dev->phys_sector_size = dev->sector_size;

        if (S_ISBLK(dev_stat.st_mode))
                dev->length = _device_get_length (dev);
        else
                dev->length = dev_stat.st_size / dev->sector_size;
        if (dev->length <= 0) {
                ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_CANCEL,
                        _("The device %s is so small that it cannot possibly "
                          "store a file system or partition table.  Perhaps "
                          "you selected the wrong device?"),
                        dev->path);
                goto error_close_dev;
        }

        _device_close (dev);

        dev->bios_geom.cylinders = dev->length / 4 / 32;
        dev->bios_geom.heads = 4;
        dev->bios_geom.sectors = 32;
        dev->hw_geom = dev->bios_geom;
        dev->model = strdup ("");

        return 1;

error_close_dev:
        _device_close (dev);
error:
        return 0;
}

#if defined __s390__ || defined __s390x__
static int
init_dasd (PedDevice* dev, const char* model_name)
{
        struct stat             dev_stat;
        struct hd_geometry      geo;
        dasd_information_t dasd_info;

        if (!_device_stat (dev, &dev_stat))
                goto error;

        if (!_device_open_ro (dev))
                goto error;

        LinuxSpecific* arch_specific = LINUX_SPECIFIC (dev);

        PED_ASSERT (S_ISBLK (dev_stat.st_mode));

        _device_set_sector_size (dev);
        if (!dev->sector_size)
                goto error_close_dev;

        dev->length = _device_get_length (dev);
        if (!dev->length)
                goto error_close_dev;

        if (!ioctl (arch_specific->fd, HDIO_GETGEO, &geo)) {
                dev->hw_geom.sectors = geo.sectors;
                dev->hw_geom.heads = geo.heads;
                dev->hw_geom.cylinders = dev->length
                        / (dev->hw_geom.heads * dev->hw_geom.sectors)
                        / (dev->sector_size / PED_SECTOR_SIZE_DEFAULT);
                dev->bios_geom = dev->hw_geom;
        } else {
                dev->bios_geom.sectors = 12;
                dev->bios_geom.heads = 15;
                dev->bios_geom.cylinders = dev->length
                        / (dev->hw_geom.heads * dev->hw_geom.sectors)
                        / (dev->sector_size / PED_SECTOR_SIZE_DEFAULT);
                dev->hw_geom = dev->bios_geom;
        }

        if (!ioctl(arch_specific->fd, BIODASDINFO, &dasd_info)) {
                arch_specific->devno = dasd_info.devno;
        } else {
                arch_specific->devno = arch_specific->major * 256 +
                                       arch_specific->minor;
        }

        dev->model = strdup (model_name);

        _device_close (dev);
        return 1;

error_close_dev:
        _device_close (dev);
error:
        return 0;
}
#endif

static int
init_generic (PedDevice* dev, const char* model_name)
{
        struct stat             dev_stat;
        PedExceptionOption      ex_status;

        if (!_device_stat (dev, &dev_stat))
                goto error;

        if (!_device_open_ro (dev))
                goto error;

        ped_exception_fetch_all ();
        if (_device_probe_geometry (dev)) {
                ped_exception_leave_all ();
        } else {
		if (!_device_get_length (dev)) {
			ped_exception_catch ();
			ped_exception_leave_all ();
			goto error_close_dev;
		}

                /* hack to allow use of files, for testing */
                ped_exception_catch ();
                ped_exception_leave_all ();

                ex_status = ped_exception_throw (
                                PED_EXCEPTION_WARNING,
                                PED_EXCEPTION_IGNORE_CANCEL,
                                _("Unable to determine geometry of "
                                "file/device %s.  You should not use Parted "
                                "unless you REALLY know what you're doing!"),
                                dev->path);
                switch (ex_status) {
                        case PED_EXCEPTION_CANCEL:
                                goto error_close_dev;

                        case PED_EXCEPTION_UNHANDLED:
                                ped_exception_catch ();
                                /* FALLTHROUGH */
                        case PED_EXCEPTION_IGNORE:
                                break;
                        default:
                                PED_ASSERT (0);
                                break;
                }

                /* what should we stick in here? */
                dev->length = dev_stat.st_size / PED_SECTOR_SIZE_DEFAULT;
                dev->bios_geom.cylinders = dev->length / 4 / 32;
                dev->bios_geom.heads = 4;
                dev->bios_geom.sectors = 32;
                dev->sector_size = PED_SECTOR_SIZE_DEFAULT;
                dev->phys_sector_size = PED_SECTOR_SIZE_DEFAULT;
        }

        dev->model = strdup (model_name);

        _device_close (dev);
        return 1;

error_close_dev:
        _device_close (dev);
error:
        return 0;
}

static int
sdmmc_get_product_info (PedDevice* dev, char **type, char **name)
{
        *type = read_device_sysfs_file (dev, "type");
        *name = read_device_sysfs_file (dev, "name");
        if (*type && *name)
                return 1;

        return 0;
}

static int
init_sdmmc (PedDevice* dev)
{
        char id[128];
        char *type, *name;

        if (sdmmc_get_product_info (dev, &type, &name)) {
                snprintf (id, sizeof(id) - 1, "%s %s", type, name);
                free (type);
                free (name);
        } else {
                snprintf (id, sizeof(id) - 1, "%s",
                          _("Generic SD/MMC Storage Card"));
        }
        return init_generic(dev, id);
}

static int
init_nvme (PedDevice* dev)
{
        int ret;
        char *model = read_device_sysfs_file (dev, "model");

        if (!model)
                ret = init_generic (dev, _("NVMe Device"));
        else {
                ret = init_generic (dev, model);
                free (model);
        }

        return ret;
}

static PedDevice*
linux_new (const char* path)
{
        PedDevice*      dev;
        LinuxSpecific*  arch_specific;

        PED_ASSERT (path != NULL);

        dev = (PedDevice*) ped_malloc (sizeof (PedDevice));
        if (!dev)
                goto error;

        dev->path = strdup (path);
        if (!dev->path)
                goto error_free_dev;

        dev->arch_specific
                = (LinuxSpecific*) ped_malloc (sizeof (LinuxSpecific));
        if (!dev->arch_specific)
                goto error_free_path;
        arch_specific = LINUX_SPECIFIC (dev);
        arch_specific->dmtype = NULL;
#if USE_BLKID
        arch_specific->probe = NULL;
        arch_specific->topology = NULL;
#endif

        dev->open_count = 0;
        dev->read_only = 0;
        dev->external_mode = 0;
        dev->dirty = 0;
        dev->boot_dirty = 0;

#ifdef ENABLE_DEVICE_MAPPER
        dm_udev_set_sync_support(1);
#endif

        if (!_device_probe_type (dev))
                goto error_free_arch_specific;

        switch (dev->type) {
        case PED_DEVICE_IDE:
                if (!init_ide (dev))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_SCSI:
                if (!init_scsi (dev))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_DAC960:
                if (!init_generic (dev, _("DAC960 RAID controller")))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_SX8:
                if (!init_generic (dev, _("Promise SX8 SATA Device")))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_AOE:
                if (!init_generic (dev, _("ATA over Ethernet Device")))
                    goto error_free_arch_specific;
                break;

#if defined __s390__ || defined __s390x__
        case PED_DEVICE_DASD:
                if (!init_dasd (dev, _("IBM S390 DASD drive")))
                        goto error_free_arch_specific;
                break;
#endif

        case PED_DEVICE_VIODASD:
                if (!init_generic (dev, _("IBM iSeries Virtual DASD")))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_CPQARRAY:
                if (!init_generic (dev, _("Compaq Smart Array")))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_NVME:
                if (!init_nvme (dev))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_PMEM:
                if (!init_generic (dev, _("NVDIMM Device")))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_ATARAID:
                if (!init_generic (dev, _("ATARAID Controller")))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_I2O:
                if (!init_generic (dev, _("I2O Controller")))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_UBD:
                if (!init_generic (dev, _("User-Mode Linux UBD")))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_FILE:
                if (!init_file (dev))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_LOOP:
                if (!init_generic (dev, _("Loopback device")))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_DM:
                {
                  char* type;
                  if (arch_specific->dmtype == NULL
                      || asprintf(&type, _("Linux device-mapper (%s)"),
                                  arch_specific->dmtype) == -1)
                        goto error_free_arch_specific;
                  bool ok = init_generic (dev, type);
                  free (type);
                  if (!ok)
                    goto error_free_arch_specific;
                  break;
                }

        case PED_DEVICE_XVD:
                if (!init_generic (dev, _("Xen Virtual Block Device")))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_UNKNOWN:
                if (!init_generic (dev, _("Unknown")))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_SDMMC:
                if (!init_sdmmc (dev))
                        goto error_free_arch_specific;
                break;
        case PED_DEVICE_VIRTBLK:
                if (!init_generic(dev, _("Virtio Block Device")))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_MD:
                if (!init_generic(dev, _("Linux Software RAID Array")))
                        goto error_free_arch_specific;
                break;

        case PED_DEVICE_RAM:
                if (!init_generic (dev, _("RAM Drive")))
                        goto error_free_arch_specific;
                break;

        default:
                ped_exception_throw (PED_EXCEPTION_NO_FEATURE,
                                PED_EXCEPTION_CANCEL,
                                _("ped_device_new()  Unsupported device type"));
                goto error_free_arch_specific;
        }
        return dev;

error_free_arch_specific:
        free (dev->arch_specific);
error_free_path:
        free (dev->path);
error_free_dev:
        free (dev);
error:
        return NULL;
}

static void
linux_destroy (PedDevice* dev)
{
        LinuxSpecific *arch_specific = LINUX_SPECIFIC(dev);
        void *p = arch_specific->dmtype;

#if USE_BLKID
        if (arch_specific->probe)
                blkid_free_probe(arch_specific->probe);
#endif
        free (p);
        free (dev->arch_specific);
        free (dev->path);
        free (dev->model);
        free (dev);
}

static int
linux_is_busy (PedDevice* dev)
{
        int     i;
        char*   part_name;

        if (_partition_is_mounted_by_path (dev->path))
                return 1;

        for (i = 0; i < 32; i++) {
                int status;

                part_name = _device_get_part_path (dev, i);
                if (!part_name)
                        return 1;
                status = _partition_is_mounted_by_path (part_name);
                free (part_name);

                if (status)
                        return 1;
        }

        return 0;
}

/* we need to flush the master device, and all the partition devices,
 * because there is no coherency between the caches.
 * We should only flush unmounted partition devices, because:
 *  - there is never a need to flush them (we're not doing IO there)
 *  - flushing a device that is mounted causes unnecessary IO, and can
 * even screw journaling & friends up.  Even cause oopsen!
 */
static void
_flush_cache (PedDevice* dev)
{
        LinuxSpecific*  arch_specific = LINUX_SPECIFIC (dev);
        int             i;
        int             lpn = _device_get_partition_range(dev);

        if (dev->read_only || dev->type == PED_DEVICE_RAM)
                return;
        dev->dirty = 0;

        ioctl (arch_specific->fd, BLKFLSBUF);

        for (i = 1; i < lpn; i++) {
                char*           name;
                int             fd;

                name = _device_get_part_path (dev, i);
                if (!name)
                        break;
                if (!_partition_is_mounted_by_path (name)) {
                        fd = open (name, WR_MODE, 0);
                        if (fd > 0) {
                                ioctl (fd, BLKFLSBUF);
retry:
                                if (fsync (fd) < 0 || close (fd) < 0)
					if (ped_exception_throw (
						PED_EXCEPTION_WARNING,
						PED_EXCEPTION_RETRY +
							PED_EXCEPTION_IGNORE,
						_("Error fsyncing/closing %s: %s"),
						name, strerror (errno))
							== PED_EXCEPTION_RETRY)
						goto retry;
                        }
                }
                free (name);
        }
}

static int
_device_open_ro (PedDevice* dev)
{
    int rc = _device_open (dev, RD_MODE);
    if (rc)
        dev->open_count++;
    return rc;
}

static int
linux_open (PedDevice* dev)
{
    return _device_open (dev, RW_MODE);
}

static int
_device_open (PedDevice* dev, int flags)
{
        LinuxSpecific*  arch_specific = LINUX_SPECIFIC (dev);

retry:
        arch_specific->fd = open (dev->path, flags);

        if (arch_specific->fd == -1) {
                char*   rw_error_msg = strerror (errno);

                arch_specific->fd = open (dev->path, RD_MODE);

                if (arch_specific->fd == -1) {
                        if (ped_exception_throw (
                                PED_EXCEPTION_ERROR,
                                PED_EXCEPTION_RETRY_CANCEL,
                                _("Error opening %s: %s"),
                                dev->path, strerror (errno))
                                        != PED_EXCEPTION_RETRY) {
                                return 0;
                        } else {
                                goto retry;
                        }
                } else {
                        ped_exception_throw (
                                PED_EXCEPTION_WARNING,
                                PED_EXCEPTION_OK,
                                _("Unable to open %s read-write (%s).  %s has "
                                  "been opened read-only."),
                                dev->path, rw_error_msg, dev->path);
                        dev->read_only = 1;
                }
        } else {
                dev->read_only = 0;
        }

        _flush_cache (dev);

        return 1;
}

static int
linux_refresh_open (PedDevice* dev)
{
        return 1;
}

static int
linux_close (PedDevice* dev)
{
        LinuxSpecific*          arch_specific = LINUX_SPECIFIC (dev);

        if (dev->dirty)
                _flush_cache (dev);
retry:
        if (fsync (arch_specific->fd) < 0 || close (arch_specific->fd) < 0)
		if (ped_exception_throw (
			PED_EXCEPTION_WARNING,
			PED_EXCEPTION_RETRY + PED_EXCEPTION_IGNORE,
			_("Error fsyncing/closing %s: %s"),
			dev->path, strerror (errno))
				== PED_EXCEPTION_RETRY)
			goto retry;
        return 1;
}

static int
linux_refresh_close (PedDevice* dev)
{
        if (dev->dirty)
                _flush_cache (dev);
        return 1;
}

static int
_device_close (PedDevice* dev)
{
    int rc = linux_close (dev);
    if (dev->open_count > 0)
        dev->open_count--;
    return rc;
}

#if SIZEOF_OFF_T < 8

static _syscall5(int,_llseek,
                 unsigned int, fd,
                 unsigned long, offset_high,
                 unsigned long, offset_low,
                 loff_t*, result,
                 unsigned int, origin)

loff_t
llseek (unsigned int fd, loff_t offset, unsigned int whence)
{
        loff_t result;
        int retval;

        retval = _llseek(fd,
                         ((unsigned long long)offset) >> 32,
                         ((unsigned long long)offset) & 0xffffffff,
                         &result,
                         whence);
        return (retval==-1 ? (loff_t) retval : result);
}

#endif /* SIZEOF_OFF_T < 8 */

static int
_device_seek (const PedDevice* dev, PedSector sector)
{
        LinuxSpecific*  arch_specific;

        PED_ASSERT (dev->sector_size % PED_SECTOR_SIZE_DEFAULT == 0);
        PED_ASSERT (dev != NULL);
        PED_ASSERT (!dev->external_mode);

        arch_specific = LINUX_SPECIFIC (dev);

#if SIZEOF_OFF_T < 8
        if (sizeof (off_t) < 8) {
                loff_t  pos = (loff_t)(sector * dev->sector_size);
                return llseek (arch_specific->fd, pos, SEEK_SET) == pos;
        } else
#endif
        {
                off_t   pos = sector * dev->sector_size;
                return lseek (arch_specific->fd, pos, SEEK_SET) == pos;
        }
}

static int
_read_lastoddsector (const PedDevice* dev, void* buffer)
{
        LinuxSpecific*                  arch_specific;
        struct blkdev_ioctl_param       ioctl_param;

        PED_ASSERT(dev != NULL);
        PED_ASSERT(buffer != NULL);

        arch_specific = LINUX_SPECIFIC (dev);

retry:
        ioctl_param.block = 0; /* read the last sector */
        ioctl_param.content_length = dev->sector_size;
        ioctl_param.block_contents = buffer;

        if (ioctl(arch_specific->fd, BLKGETLASTSECT, &ioctl_param) == -1) {
                PedExceptionOption      opt;
                opt = ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_RETRY_IGNORE_CANCEL,
                        _("%s during read on %s"),
                        strerror (errno), dev->path);

                if (opt == PED_EXCEPTION_CANCEL)
                        return 0;
                if (opt == PED_EXCEPTION_RETRY)
                        goto retry;
        }

        return 1;
}

static int
linux_read (const PedDevice* dev, void* buffer, PedSector start,
            PedSector count)
{
        LinuxSpecific*          arch_specific = LINUX_SPECIFIC (dev);
        PedExceptionOption      ex_status;
        void*                   diobuf = NULL;

        PED_ASSERT (dev != NULL);
        PED_ASSERT (dev->sector_size % PED_SECTOR_SIZE_DEFAULT == 0);

        if (_get_linux_version() < KERNEL_VERSION (2,6,0)) {
                /* Kludge.  This is necessary to read/write the last
                   block of an odd-sized disk, until Linux 2.5.x kernel fixes.
                */
                if (dev->type != PED_DEVICE_FILE && (dev->length & 1)
                    && start + count - 1 == dev->length - 1)
                        return ped_device_read (dev, buffer, start, count - 1)
                                && _read_lastoddsector (
                                        dev, (char *) buffer + (count-1) * 512);
        }
        while (1) {
                if (_device_seek (dev, start))
                        break;

                ex_status = ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_RETRY_IGNORE_CANCEL,
                        _("%s during seek for read on %s"),
                        strerror (errno), dev->path);

                switch (ex_status) {
                        case PED_EXCEPTION_IGNORE:
                                return 1;

                        case PED_EXCEPTION_RETRY:
                                break;

                        case PED_EXCEPTION_UNHANDLED:
                                ped_exception_catch ();
                                /* FALLTHROUGH */
                        case PED_EXCEPTION_CANCEL:
                                return 0;
                        default:
                                PED_ASSERT (0);
                                break;
                }
        }

        size_t read_length = count * dev->sector_size;
        if (posix_memalign (&diobuf, dev->sector_size, read_length) != 0)
                return 0;

        while (1) {
                ssize_t status = read (arch_specific->fd, diobuf, read_length);
                if (status > 0)
                        memcpy(buffer, diobuf, status);
                if (status == (ssize_t) read_length)
                        break;
                if (status > 0) {
                        read_length -= status;
                        buffer = (char *) buffer + status;
                        continue;
                }

                ex_status = ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_RETRY_IGNORE_CANCEL,
                        (status == 0
                         ? _("%0.0send of file while reading %s")
                         : _("%s during read on %s")),
                        strerror (errno),
                        dev->path);

                switch (ex_status) {
                        case PED_EXCEPTION_IGNORE:
                                free(diobuf);
                                return 1;

                        case PED_EXCEPTION_RETRY:
                                break;

                        case PED_EXCEPTION_UNHANDLED:
                                ped_exception_catch ();
                                /* FALLTHROUGH */
                        case PED_EXCEPTION_CANCEL:
                                free(diobuf);
                                return 0;
                        default:
                                PED_ASSERT (0);
                                break;
                }
        }

        free (diobuf);

        return 1;
}

static int
_write_lastoddsector (PedDevice* dev, const void* buffer)
{
        LinuxSpecific*                  arch_specific;
        struct blkdev_ioctl_param       ioctl_param;

        PED_ASSERT(dev != NULL);
        PED_ASSERT(buffer != NULL);

        arch_specific = LINUX_SPECIFIC (dev);

retry:
        ioctl_param.block = 0; /* write the last sector */
        ioctl_param.content_length = dev->sector_size;
        ioctl_param.block_contents = (void*) buffer;

        if (ioctl(arch_specific->fd, BLKSETLASTSECT, &ioctl_param) == -1) {
                PedExceptionOption      opt;
                opt = ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_RETRY_IGNORE_CANCEL,
                        _("%s during write on %s"),
                        strerror (errno), dev->path);

                if (opt == PED_EXCEPTION_CANCEL)
                        return 0;
                if (opt == PED_EXCEPTION_RETRY)
                        goto retry;
        }

        return 1;
}

static int
linux_write (PedDevice* dev, const void* buffer, PedSector start,
             PedSector count)
{
        LinuxSpecific*          arch_specific = LINUX_SPECIFIC (dev);
        PedExceptionOption      ex_status;
        void*                   diobuf;
        void*                   diobuf_start;

        PED_ASSERT(dev->sector_size % PED_SECTOR_SIZE_DEFAULT == 0);

        if (dev->read_only) {
                if (ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_IGNORE_CANCEL,
                        _("Can't write to %s, because it is opened read-only."),
                        dev->path)
                                != PED_EXCEPTION_IGNORE)
                        return 0;
                else
                        return 1;
        }

        if (_get_linux_version() < KERNEL_VERSION (2,6,0)) {
                /* Kludge.  This is necessary to read/write the last
                   block of an odd-sized disk, until Linux 2.5.x kernel fixes.
                */
                if (dev->type != PED_DEVICE_FILE && (dev->length & 1)
                    && start + count - 1 == dev->length - 1)
                        return ped_device_write (dev, buffer, start, count - 1)
                                && _write_lastoddsector (
                                        dev, ((char*) buffer
                                              + (count-1) * dev->sector_size));
        }
        while (1) {
                if (_device_seek (dev, start))
                        break;

                ex_status = ped_exception_throw (
                        PED_EXCEPTION_ERROR, PED_EXCEPTION_RETRY_IGNORE_CANCEL,
                        _("%s during seek for write on %s"),
                        strerror (errno), dev->path);

                switch (ex_status) {
                        case PED_EXCEPTION_IGNORE:
                                return 1;

                        case PED_EXCEPTION_RETRY:
                                break;

                        case PED_EXCEPTION_UNHANDLED:
                                ped_exception_catch ();
                                /* FALLTHROUGH */
                        case PED_EXCEPTION_CANCEL:
                                return 0;
                        default:
                                PED_ASSERT (0);
                                break;
                }
        }

#ifdef READ_ONLY
        printf ("ped_device_write (\"%s\", %p, %d, %d)\n",
                dev->path, buffer, (int) start, (int) count);
#else
        size_t write_length = count * dev->sector_size;
        dev->dirty = 1;
        if (posix_memalign(&diobuf, dev->sector_size, write_length) != 0)
                return 0;
        memcpy(diobuf, buffer, write_length);
        diobuf_start = diobuf;
        while (1) {
                ssize_t status = write (arch_specific->fd, diobuf, write_length);
                if (status == write_length) break;
                if (status > 0) {
                        write_length -= status;
                        diobuf = (char *) diobuf + status;
                        continue;
                }

                ex_status = ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_RETRY_IGNORE_CANCEL,
                        _("%s during write on %s"),
                        strerror (errno), dev->path);

                switch (ex_status) {
                        case PED_EXCEPTION_IGNORE:
                                free(diobuf_start);
                                return 1;

                        case PED_EXCEPTION_RETRY:
                                break;

                        case PED_EXCEPTION_UNHANDLED:
                                ped_exception_catch ();
                                /* FALLTHROUGH */
                        case PED_EXCEPTION_CANCEL:
                                free(diobuf_start);
                                return 0;
                        default:
                                PED_ASSERT (0);
                                break;
                }
        }
        free(diobuf_start);
#endif /* !READ_ONLY */
        return 1;
}

/* returns the number of sectors that are ok.
 */
static PedSector
linux_check (PedDevice* dev, void* buffer, PedSector start, PedSector count)
{
        LinuxSpecific*  arch_specific = LINUX_SPECIFIC (dev);
        PedSector       done = 0;
        int             status;
        void*           diobuf;

        PED_ASSERT(dev != NULL);

        if (!_device_seek (dev, start))
                return 0;

        if (posix_memalign(&diobuf, PED_SECTOR_SIZE_DEFAULT,
                           count * PED_SECTOR_SIZE_DEFAULT) != 0)
                return 0;

        for (done = 0; done < count; done += status / dev->sector_size) {
                status = read (arch_specific->fd, diobuf,
                               (size_t) ((count-done) * dev->sector_size));
                if (status > 0)
                        memcpy(buffer, diobuf, status);
                if (status < 0)
                        break;
        }
        free(diobuf);

        return done;
}

static int
_do_fsync (PedDevice* dev)
{
        LinuxSpecific*          arch_specific = LINUX_SPECIFIC (dev);
        int                     status;
        PedExceptionOption      ex_status;

        while (1) {
                status = fsync (arch_specific->fd);
                if (status >= 0) break;

                ex_status = ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_RETRY_IGNORE_CANCEL,
                        _("%s during write on %s"),
                        strerror (errno), dev->path);

                switch (ex_status) {
                        case PED_EXCEPTION_IGNORE:
                                return 1;

                        case PED_EXCEPTION_RETRY:
                                break;

                        case PED_EXCEPTION_UNHANDLED:
                                ped_exception_catch ();
                                /* FALLTHROUGH */
                        case PED_EXCEPTION_CANCEL:
                                return 0;
                        default:
                                PED_ASSERT (0);
                                break;
                }
        }
        return 1;
}

static int
linux_sync (PedDevice* dev)
{
        PED_ASSERT (dev != NULL);
        PED_ASSERT (!dev->external_mode);

        if (dev->read_only)
                return 1;
        if (!_do_fsync (dev))
                return 0;
        _flush_cache (dev);
        return 1;
}

static int
linux_sync_fast (PedDevice* dev)
{
        PED_ASSERT (dev != NULL);
        PED_ASSERT (!dev->external_mode);

        if (dev->read_only)
                return 1;
        if (!_do_fsync (dev))
                return 0;
        /* no cache flush... */
        return 1;
}

static inline int
_compare_digit_state (char ch, int need_digit)
{
        return !!isdigit (ch) == need_digit;
}

/* matches the regexp "[^0-9]+[0-9]+[^0-9]+[0-9]+$".
 * Motivation: accept devices looking like /dev/rd/c0d0, but
 * not looking like /dev/hda1 and /dev/rd/c0d0p1
 */
static int _GL_ATTRIBUTE_PURE
_match_rd_device (const char* name)
{
        const char* pos;
        int state;

        /* exclude directory names from test */
        pos = strrchr(name, '/') ?: name;

        /* states:
         *      0       non-digits
         *      1       digits
         *      2       non-digits
         *      3       digits
         */
        for (state = 0; state < 4; state++) {
                int want_digits = (state % 2 == 1);
                do {
                        if (!*pos)
                                return 0;
                        if (!_compare_digit_state (*pos, want_digits))
                                return 0;
                        pos++;
                } while (_compare_digit_state (*pos, want_digits));
        }

        return *pos == 0;
}

static int
_probe_proc_partitions ()
{
        FILE*           proc_part_file;
        int             major, minor, size;
        char            buf [512];
        char            part_name [256];
        char            dev_name [256];
        int ok = 0;

        proc_part_file = fopen ("/proc/partitions", "r");
        if (!proc_part_file)
                return 0;

        if (fgets (buf, 256, proc_part_file) == NULL)
                goto done;

        if (fgets (buf, 256, proc_part_file) == NULL)
                goto done;

        while (fgets (buf, 512, proc_part_file)
               && sscanf (buf, "%d %d %d %255s", &major, &minor, &size,
                          part_name) == 4) {
                /* Heuristic for telling partitions and devices apart
                 * Probably needs to be improved
                 */
                if (!_match_rd_device (part_name)
                    && isdigit (part_name [strlen (part_name) - 1]))
                        continue;

                strcpy (dev_name, "/dev/");
                strcat (dev_name, part_name);
                _ped_device_probe (dev_name);
        }

        ok = 1;
 done:
        fclose (proc_part_file);
        return ok;
}

struct _entry {
	const char *name;
	size_t len;
};

static int _GL_ATTRIBUTE_PURE
_skip_entry (const char *name)
{
	struct _entry *i;
	static struct _entry entries[] = {
		{ ".",		sizeof (".") - 1	},
		{ "..",		sizeof ("..") - 1	},
		{ "dm-",	sizeof ("dm-") - 1	},
		{ "loop",	sizeof ("loop") - 1	},
		{ "ram",	sizeof ("ram") - 1	},
		{ "fd",		sizeof ("fd") - 1	},
		{ 0, 0 },
	};

	for (i = entries; i->name != 0; i++) {
		if (strncmp (name, i->name, i->len) == 0)
			return 1;
	}

	return 0;
}

static int
_probe_sys_block ()
{
	DIR *blockdir;
	struct dirent *dirent;
	char dev_name [256];
	char *ptr;

	if (!(blockdir = opendir ("/sys/block")))
		return 0;
	while ((dirent = readdir (blockdir))) {
		if (_skip_entry (dirent->d_name))
			continue;

		if (strlen (dirent->d_name) > sizeof (dev_name) - 6)
			continue; /* device name too long! */

		strcpy (dev_name, "/dev/");
		strcat (dev_name, dirent->d_name);
		/* in /sys/block, '/'s are replaced with '!' */
		for (ptr = dev_name; *ptr != '\0'; ptr++) {
			if (*ptr == '!')
				*ptr = '/';
		}
		_ped_device_probe (dev_name);
	}

	closedir (blockdir);
	return 1;
}

static int
_probe_standard_devices ()
{
        _ped_device_probe ("/dev/hda");
        _ped_device_probe ("/dev/hdb");
        _ped_device_probe ("/dev/hdc");
        _ped_device_probe ("/dev/hdd");
        _ped_device_probe ("/dev/hde");
        _ped_device_probe ("/dev/hdf");
        _ped_device_probe ("/dev/hdg");
        _ped_device_probe ("/dev/hdh");

        _ped_device_probe ("/dev/sda");
        _ped_device_probe ("/dev/sdb");
        _ped_device_probe ("/dev/sdc");
        _ped_device_probe ("/dev/sdd");
        _ped_device_probe ("/dev/sde");
        _ped_device_probe ("/dev/sdf");

        return 1;
}

static void
linux_probe_all ()
{
        /* we should probe the standard devs too, even with /proc/partitions,
         * because /proc/partitions might return devfs stuff, and we might not
         * have devfs available
         */
        _probe_standard_devices ();

#ifdef ENABLE_DEVICE_MAPPER
        /* device-mapper devices aren't listed in /proc/partitions; or, if
         * they are, they're listed as dm-X.  So, instead of relying on that,
         * we do our own checks.
         */
        _probe_dm_devices ();
#endif

        /* /sys/block is more reliable and consistent; fall back to using
         * /proc/partitions if the former is unavailable, however.
         */
        if (!_probe_sys_block ())
                _probe_proc_partitions ();
}

static char * _GL_ATTRIBUTE_FORMAT ((__printf__, 1, 2))
zasprintf (const char *format, ...)
{
  va_list args;
  char *resultp;
  va_start (args, format);
  int r = vasprintf (&resultp, format, args);
  va_end (args);
  return r < 0 ? NULL : resultp;
}

#ifdef ENABLE_DEVICE_MAPPER
static char *
dm_canonical_path (PedDevice const *dev)
{
        LinuxSpecific const *arch_specific = LINUX_SPECIFIC (dev);

        /* Get map name from devicemapper */
        struct dm_task *task = dm_task_create (DM_DEVICE_INFO);
        if (!task)
                goto err;
        if (!dm_task_set_major_minor (task, arch_specific->major,
                                      arch_specific->minor, 0))
                goto err;
        if (!dm_task_run(task))
                goto err;
        char *dev_name = zasprintf ("/dev/mapper/%s", dm_task_get_name (task));
        if (dev_name == NULL)
                goto err;
        dm_task_destroy (task);
        return dev_name;
err:
        return NULL;
}
#endif

static char*
_device_get_part_path (PedDevice const *dev, int num)
{
        char *devpath;
        size_t path_len;
        char *result;
#ifdef ENABLE_DEVICE_MAPPER
        devpath = (dev->type == PED_DEVICE_DM
                         ? dm_canonical_path (dev) : dev->path);
#else
        devpath = dev->path;
#endif
        path_len = strlen (devpath);
        /* Check for devfs-style /disc => /partN transformation
           unconditionally; the system might be using udev with devfs rules,
           and if not the test is harmless. */
        if (5 < path_len && !strcmp (devpath + path_len - 5, "/disc")) {
                /* replace /disc with /part%d */
                result = zasprintf ("%.*s/part%d",
                                    (int) (path_len - 5), devpath, num);
        } else {
                char const *p = (dev->type == PED_DEVICE_DAC960
                                 || dev->type == PED_DEVICE_CPQARRAY
                                 || dev->type == PED_DEVICE_ATARAID
                                 || isdigit (devpath[path_len - 1])
                                 ? "p" : "");
                result = zasprintf ("%s%s%d", devpath, p, num);
        }
#ifdef ENABLE_DEVICE_MAPPER
        if (dev->type == PED_DEVICE_DM)
                free (devpath);
#endif
        return result;
}

static char*
linux_partition_get_path (const PedPartition* part)
{
        /* loop label means use the whole disk */
        if (strcmp (part->disk->type->name, "loop") == 0)
                return xstrdup (part->disk->dev->path);
        return _device_get_part_path (part->disk->dev, part->num);
}

static int
_mount_table_search (const char* file_name, dev_t dev)
{
        struct stat part_stat;
        char line[512];
        char part_name[512];
        FILE* file;

        file = fopen (file_name, "r");
        if (!file)
                return 0;
        while (fgets (line, 512, file)) {
                if (sscanf (line, "%s", part_name) == 1
                    && stat (part_name, &part_stat) == 0) {
                        if (part_stat.st_rdev == dev) {
                                fclose (file);
                                return 1;
                        }
                }
        }
        fclose (file);
        return 0;
}

static int
_partition_is_mounted_by_dev (dev_t dev)
{
        return  _mount_table_search( "/proc/mounts", dev)
                || _mount_table_search( "/proc/swaps", dev)
                || _mount_table_search( "/etc/mtab", dev);
}

static int
_partition_is_mounted_by_path (const char *path)
{
        struct stat part_stat;
        if (stat (path, &part_stat) != 0)
                return 0;
        if (!S_ISBLK(part_stat.st_mode))
                return 0;
        return _partition_is_mounted_by_dev (part_stat.st_rdev);
}

/* If partition PART is mounted, or if we encounter an out-of-memory error
   while trying to determine its status, return 1.  Otherwise, return 0.  */
static int
_partition_is_mounted (const PedPartition *part)
{
	if (!ped_partition_is_active (part))
		return 0;
	char *part_name = _device_get_part_path (part->disk->dev, part->num);
	if (!part_name)
		return 1;
	int status = _partition_is_mounted_by_path (part_name);
	free (part_name);
	return !!status;
}

static int
linux_partition_is_busy (const PedPartition* part)
{
        PedPartition*   walk;

        PED_ASSERT (part != NULL);

        if (strcmp (part->disk->type->name, "loop") == 0)
                return linux_is_busy (part->disk->dev);
        if (_partition_is_mounted (part))
                return 1;
        if (part->type == PED_PARTITION_EXTENDED) {
                for (walk = part->part_list; walk; walk = walk->next) {
                        if (linux_partition_is_busy (walk))
                                return 1;
                }
        }
        return 0;
}

static int
_blkpg_part_command (PedDevice* dev, struct blkpg_partition* part, int op)
{
        LinuxSpecific*          arch_specific = LINUX_SPECIFIC (dev);
        struct blkpg_ioctl_arg  ioctl_arg;

        ioctl_arg.op = op;
        ioctl_arg.flags = 0;
        ioctl_arg.datalen = sizeof (struct blkpg_partition);
        ioctl_arg.data = (void*) part;

        return ioctl (arch_specific->fd, BLKPG, &ioctl_arg) == 0;
}

static int
_blkpg_add_partition (PedDisk* disk, const PedPartition *part)
{
        struct blkpg_partition  linux_part;
        const char*             vol_name;
        char*                   dev_name;

        PED_ASSERT(disk != NULL);
        PED_ASSERT(disk->dev->sector_size % PED_SECTOR_SIZE_DEFAULT == 0);

        if (ped_disk_type_check_feature (disk->type,
                                         PED_DISK_TYPE_PARTITION_NAME))
                vol_name = ped_partition_get_name (part);
        else
                vol_name = NULL;

        dev_name = _device_get_part_path (disk->dev, part->num);
        if (!dev_name)
                return 0;

        memset (&linux_part, 0, sizeof (linux_part));
        linux_part.start = part->geom.start * disk->dev->sector_size;
        /* see fs/partitions/msdos.c:msdos_partition(): "leave room for LILO" */
        if (part->type & PED_PARTITION_EXTENDED) {
                linux_part.length = 1;
                if (disk->dev->sector_size == 512) {
                        if (linux_part.length == 1)
                                linux_part.length = 2;
                        PedPartition *walk;
                        /* if the second sector is claimed by a logical partition,
                           then there's just no room for lilo, so don't try to use it */
                        for (walk = part->part_list; walk; walk = walk->next) {
                                if (walk->geom.start == part->geom.start+1)
                                        linux_part.length = 1;
                        }
                }
                linux_part.length *= disk->dev->sector_size;
        }
        else {
                linux_part.length = part->geom.length * disk->dev->sector_size;
        }
        linux_part.pno = part->num;
        strncpy (linux_part.devname, dev_name, BLKPG_DEVNAMELTH-1);
        linux_part.devname[BLKPG_DEVNAMELTH-1] = '\0';
        if (vol_name) {
                strncpy (linux_part.volname, vol_name, BLKPG_VOLNAMELTH-1);
                linux_part.volname[BLKPG_VOLNAMELTH-1] = '\0';
        }

        free (dev_name);

        if (!_blkpg_part_command (disk->dev, &linux_part,
                                  BLKPG_ADD_PARTITION)) {
                return 0;
        }

        return 1;
}

static int
_blkpg_remove_partition (PedDisk* disk, int n)
{
        struct blkpg_partition  linux_part;

        memset (&linux_part, 0, sizeof (linux_part));
        linux_part.pno = n;
        return _blkpg_part_command (disk->dev, &linux_part,
                                    BLKPG_DEL_PARTITION);
}

#ifdef BLKPG_RESIZE_PARTITION
static int _blkpg_resize_partition (PedDisk* disk, const PedPartition *part)
{
        struct blkpg_partition  linux_part;
        char*                   dev_name;

        PED_ASSERT(disk != NULL);
        PED_ASSERT(disk->dev->sector_size % PED_SECTOR_SIZE_DEFAULT == 0);

        dev_name = _device_get_part_path (disk->dev, part->num);
        if (!dev_name)
                return 0;
        memset (&linux_part, 0, sizeof (linux_part));
        linux_part.start = part->geom.start * disk->dev->sector_size;
        /* see fs/partitions/msdos.c:msdos_partition(): "leave room for LILO" */
        if (part->type & PED_PARTITION_EXTENDED) {
                if (disk->dev->sector_size == 512) {
                        linux_part.length = 2;
                        PedPartition *walk;
                        /* if the second sector is claimed by a logical partition,
                           then there's just no room for lilo, so don't try to use it */
                        for (walk = part->part_list; walk; walk = walk->next) {
                                if (walk->geom.start == part->geom.start+1)
                                        linux_part.length = 1;
                        }
                } else {
                        linux_part.length = 1;
                }
                linux_part.length *= disk->dev->sector_size;
        }
        else
                linux_part.length = part->geom.length * disk->dev->sector_size;
        linux_part.pno = part->num;
        strncpy (linux_part.devname, dev_name, BLKPG_DEVNAMELTH-1);
        linux_part.devname[BLKPG_DEVNAMELTH-1] = '\0';

        free (dev_name);

        if (!_blkpg_part_command (disk->dev, &linux_part,
                                  BLKPG_RESIZE_PARTITION)) {
                return ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_IGNORE_CANCEL,
                        _("Error informing the kernel about modifications to "
                          "partition %s -- %s.  This means Linux won't know "
                          "about any changes you made to %s until you reboot "
                          "-- so you shouldn't mount it or use it in any way "
                          "before rebooting."),
                        linux_part.devname,
                        strerror (errno),
                        linux_part.devname)
                                == PED_EXCEPTION_IGNORE;
        }

        return 1;
}
#endif

/* Read the integer from /sys/block/DEV_BASE/ENTRY and set *VAL
   to that value, where DEV_BASE is the last component of DEV->path.
   Upon success, return true.  Otherwise, return false. */
static bool
_sysfs_int_entry_from_dev(PedDevice const* dev, const char *entry, int *val)
{
        char        path[128];
        int r = snprintf(path, sizeof(path), "/sys/block/%s/%s",
			 last_component(dev->path), entry);
        if (r < 0 || r >= sizeof(path))
                return false;

        FILE *fp = fopen(path, "r");
        if (!fp)
                return false;

        bool ok = fscanf(fp, "%d", val) == 1;
        fclose(fp);

        return ok;
}

/* Read the unsigned long long from /sys/block/DEV_BASE/PART_BASE/ENTRY
   and set *VAL to that value, where DEV_BASE is the last component of path to
   block device corresponding to PART and PART_BASE is the sysfs name of PART.
   Upon success, return true. Otherwise, return false. */
static bool
_sysfs_ull_entry_from_part(PedPartition const* part, const char *entry,
                           unsigned long long *val)
{
        char path[128];
        char *part_name = _device_get_part_path (part->disk->dev, part->num);
        if (!part_name)
                return false;

        int r = snprintf(path, sizeof(path), "/sys/block/%s/%s/%s",
                last_component(part->disk->dev->path),
                last_component(part_name), entry);
        free(part_name);
        if (r < 0 || r >= sizeof(path))
                return false;

        FILE *fp = fopen(path, "r");
        if (!fp)
                return false;

        bool ok = fscanf(fp, "%llu", val) == 1;
        fclose(fp);

        return ok;
}


/* Get the starting sector and length of a partition PART within a block device
   Use blkpg if available, then check sysfs and then use HDIO_GETGEO and
   BLKGETSIZE64 ioctls as fallback.  Upon success, return true.  Otherwise,
   return false. */
static bool
_kernel_get_partition_start_and_length(PedPartition const *part,
                                       unsigned long long *start,
                                       unsigned long long *length)
{
        PED_ASSERT(part);
        PED_ASSERT(start);
        PED_ASSERT(length);

        char *dev_name = _device_get_part_path (part->disk->dev, part->num);
        if (!dev_name)
                return false;

        int ok = _sysfs_ull_entry_from_part (part, "start", start);
        if (!ok) {
                struct hd_geometry geom;
                int dev_fd = open (dev_name, O_RDONLY);
                if (dev_fd != -1 && ioctl (dev_fd, HDIO_GETGEO, &geom)) {
                        *start = geom.start;
                        close (dev_fd);
                        ok = true;
                } else {
                        if (dev_fd != -1)
                                close(dev_fd);
                        free (dev_name);
                        return false;
                }
        }
        *start = (*start * 512) / part->disk->dev->sector_size;
        ok = _sysfs_ull_entry_from_part (part, "size", length);

        int fd;
        if (!ok) {
                fd = open (dev_name, O_RDONLY);
                if (fd != -1 && ioctl (fd, BLKGETSIZE64, length))
                        ok = true;
        } else {
                fd = -1;
                *length *= 512;
        }
        *length /= part->disk->dev->sector_size;
        if (fd != -1)
                close (fd);

        if (!ok)
                ped_exception_throw (
                        PED_EXCEPTION_BUG,
                        PED_EXCEPTION_CANCEL,
                        _("Unable to determine the start and length of %s."),
                        dev_name);
        free (dev_name);
        return ok;
}


/*
 * The number of partitions that a device can have depends on the kernel.
 * If we don't find this value in /sys/block/DEV/ext_range, we will use our own
 * value.
 */
static unsigned int
_device_get_partition_range(PedDevice const* dev)
{
        int range;
        if (dev->type == PED_DEVICE_DM)
                return MAX_NUM_PARTS;
        bool ok = _sysfs_int_entry_from_dev(dev, "ext_range", &range);

        if (!ok)
                return MAX_NUM_PARTS;
        /* both 0 and 1 mean no partitions */
        return range > 1 ? range : 0;
}

#ifdef ENABLE_DEVICE_MAPPER
static int
_dm_remove_partition(PedDisk* disk, int partno)
{
        int             rc = 0;
        uint32_t        cookie = 0;
        char            *part_name = _device_get_part_path (disk->dev, partno);

        int fd = open (part_name, O_RDONLY | O_EXCL);
        if (fd == -1) {
                if (errno == ENOENT)
                        errno = ENXIO; /* nothing to remove, device already doesn't exist */
                goto err;
        }
        close (fd);
        struct dm_task *task = dm_task_create(DM_DEVICE_REMOVE);
        if (!task)
                goto err;
        dm_task_set_name (task, part_name);
        if (!dm_task_set_cookie (task, &cookie, 0))
                goto err;
        rc = _dm_task_run_wait (task, cookie);
        dm_task_update_nodes();
        dm_task_destroy(task);
err:
        free (part_name);
        return rc;
}

static bool
_dm_get_partition_start_and_length(PedPartition const *part,
                                   unsigned long long *start,
                                   unsigned long long *length)
{
        struct dm_task* task = NULL;
        int             rc = 0;

        if (!(task = dm_task_create(DM_DEVICE_TABLE)))
                return 0;
        char *path = _device_get_part_path (part->disk->dev, part->num);
        PED_ASSERT(path);
        /* libdevmapper likes to complain on stderr instead of quietly
           returning ENOENT or ENXIO, so try to stat first */
        struct stat st;
        if (stat(path, &st))
                goto err;
        dm_task_set_name(task, path);
        if (!dm_task_run(task))
                goto err;

        int major, minor;
        char *params;
        char *target_type;
        dm_get_next_target(task, NULL, (uint64_t *)start, (uint64_t *)length, &target_type, &params);
        if (sscanf (params, "%d:%d %Lu", &major, &minor, start) != 3)
                goto err;
        rc = 1;

        /* device-mapper uses 512b units, make sure we return length and start in terms of the device's
         * sector size.
         */
        *start /= (part->disk->dev->sector_size / PED_SECTOR_SIZE_DEFAULT);
        *length /= (part->disk->dev->sector_size / PED_SECTOR_SIZE_DEFAULT);
err:
        free (path);
        dm_task_destroy(task);
        return rc;
}


static int
_dm_add_partition (PedDisk* disk, const PedPartition* part)
{
        LinuxSpecific*  arch_specific = LINUX_SPECIFIC (disk->dev);
        char*           params = NULL;
        char*           vol_name = NULL;
        const char*     dev_name = NULL;
        char*           vol_uuid = NULL;
        const char*     dev_uuid = NULL;
        uint32_t        cookie = 0;

        /* Get map name from devicemapper */
        struct dm_task *task = dm_task_create (DM_DEVICE_INFO);
        if (!task)
                goto err;

        if (!dm_task_set_major_minor (task, arch_specific->major,
                                      arch_specific->minor, 0))
                goto err;

        if (!dm_task_run(task))
                goto err;

        dev_name = dm_task_get_name (task);
        size_t name_len = strlen (dev_name);
        vol_name = zasprintf ("%s%s%d",
                              dev_name,
                              isdigit (dev_name[name_len - 1]) ? "p" : "",
                              part->num);
        if (vol_name == NULL)
                goto err;

        dev_uuid = dm_task_get_uuid (task);
        if (dev_uuid && (strlen(dev_uuid) > 0)
             && !(vol_uuid = zasprintf ("part%d-%s", part->num, dev_uuid)))
            goto err;

        /* Caution: dm_task_destroy frees dev_name.  */
        dm_task_destroy (task);
        task = NULL;
        /* device-mapper uses 512b units, not the device's sector size */
        if ( ! (params = zasprintf ("%d:%d %lld", arch_specific->major,
                                    arch_specific->minor,
                                    part->geom.start * (disk->dev->sector_size / PED_SECTOR_SIZE_DEFAULT))))
                goto err;

        task = dm_task_create (DM_DEVICE_CREATE);
        if (!task)
                goto err;

        dm_task_set_name (task, vol_name);
        if (vol_uuid)
                dm_task_set_uuid (task, vol_uuid);
        /* device-mapper uses 512b units, not the device's sector size */
        dm_task_add_target (task, 0, part->geom.length * (disk->dev->sector_size / PED_SECTOR_SIZE_DEFAULT),
                "linear", params);
        if (!dm_task_set_cookie (task, &cookie, 0))
                goto err;
        if (_dm_task_run_wait (task, cookie)) {
                dm_task_update_nodes ();
                dm_task_destroy (task);
                free (params);
                free (vol_uuid);
                free (vol_name);
                return 1;
        } else {
                _dm_remove_partition (disk, part->num);
        }
err:
        dm_task_update_nodes();
        if (task)
                dm_task_destroy (task);
        free (params);
        free (vol_uuid);
        free (vol_name);
        return 0;
}

static int
_dm_resize_partition (PedDisk* disk, const PedPartition* part)
{
        LinuxSpecific*  arch_specific = LINUX_SPECIFIC (disk->dev);
        char*           params = NULL;
        char*           vol_name = NULL;
        const char*     dev_name = NULL;
        uint32_t        cookie = 0;
        int             rc = 0;

        /* Get map name from devicemapper */
        struct dm_task *task = dm_task_create (DM_DEVICE_INFO);
        if (!task)
                goto err;

        if (!dm_task_set_major_minor (task, arch_specific->major,
                                      arch_specific->minor, 0))
                goto err;

        if (!dm_task_run(task))
                goto err;

        dev_name = dm_task_get_name (task);
        size_t name_len = strlen (dev_name);
        vol_name = zasprintf ("%s%s%d",
                              dev_name,
                              isdigit (dev_name[name_len - 1]) ? "p" : "",
                              part->num);
        if (vol_name == NULL)
                goto err;

        /* Caution: dm_task_destroy frees dev_name.  */
        dm_task_destroy (task);
        task = NULL;

        /* device-mapper uses 512b units, not the device's sector size */
        if ( ! (params = zasprintf ("%d:%d %lld", arch_specific->major,
                                    arch_specific->minor,
                                    part->geom.start * (disk->dev->sector_size / PED_SECTOR_SIZE_DEFAULT))))
                goto err;

        task = dm_task_create (DM_DEVICE_RELOAD);
        if (!task)
                goto err;

        dm_task_set_name (task, vol_name);
        /* device-mapper uses 512b units, not the device's sector size */
        dm_task_add_target (task, 0, part->geom.length * (disk->dev->sector_size / PED_SECTOR_SIZE_DEFAULT),
                "linear", params);
        /* NOTE: DM_DEVICE_RELOAD doesn't generate udev events, so no cookie is needed (it will freeze).
         *       DM_DEVICE_RESUME does, so get a cookie and synchronize with udev.
         */
        if (dm_task_run (task)) {
                dm_task_destroy (task);
                task = dm_task_create (DM_DEVICE_RESUME);
                if (!task)
                        goto err;
                dm_task_set_name (task, vol_name);
                if (!dm_task_set_cookie (task, &cookie, 0))
                        goto err;
                if (_dm_task_run_wait (task, cookie)) {
                        rc = 1;
                }
        }
err:
        dm_task_update_nodes();
        if (task)
                dm_task_destroy (task);
        free (params);
        free (vol_name);
        return rc;
}

#endif

/*
 * Sync the partition table in two step process:
 * 1. Remove all of the partitions from the kernel's tables, but do not attempt
 *    removal of any partition for which the corresponding ioctl call fails.
 * 2. Add all the partitions that we hold in disk, throwing a warning
 *    if we cannot because step 1 failed to remove it and it is not being
 *    added back with the same start and length.
 *
 * To achieve this two step process we must calculate the minimum number of
 * maximum possible partitions between what linux supports and what the label
 * type supports. EX:
 *
 * number=MIN(max_parts_supported_in_linux,max_parts_supported_in_msdos_tables)
 */
static int
_disk_sync_part_table (PedDisk* disk)
{
        PED_ASSERT(disk != NULL);
        PED_ASSERT(disk->dev != NULL);
        int lpn, lpn2;
        unsigned int part_range = _device_get_partition_range(disk->dev);
        int (*add_partition)(PedDisk* disk, const PedPartition *part);
        int (*resize_partition)(PedDisk* disk, const PedPartition *part);
        int (*remove_partition)(PedDisk* disk, int partno);
        bool (*get_partition_start_and_length)(PedPartition const *part,
                                               unsigned long long *start,
                                               unsigned long long *length);


#ifdef ENABLE_DEVICE_MAPPER
        if (disk->dev->type == PED_DEVICE_DM) {
                add_partition = _dm_add_partition;
                remove_partition = _dm_remove_partition;
                resize_partition = _dm_resize_partition;
                get_partition_start_and_length = _dm_get_partition_start_and_length;
        } else
#endif
        {
                add_partition = _blkpg_add_partition;
                remove_partition = _blkpg_remove_partition;
#ifdef BLKPG_RESIZE_PARTITION
                resize_partition = _blkpg_resize_partition;
#else
                resize_partition = NULL;
#endif
                get_partition_start_and_length = _kernel_get_partition_start_and_length;
        }

        /* lpn = largest partition number.
         * for remove pass, use greater of device or label limit */
        if (ped_disk_get_max_supported_partition_count(disk, &lpn))
                lpn = PED_MAX(lpn, part_range);
        else
                lpn = part_range;
        /* for add pass, use lesser of device or label limit */
        if (ped_disk_get_max_supported_partition_count(disk, &lpn2))
                lpn2 = PED_MIN(lpn2, part_range);
        else
                lpn2 = part_range;
        /* Its not possible to support largest_partnum < 0.
         * largest_partnum == 0 would mean does not support partitions.
         * */
        if (lpn < 1)
                return 0;
        int ret = 0;
        int *ok = calloc (lpn, sizeof *ok);
        if (!ok)
                return 0;
        int *errnums = ped_malloc(sizeof(int) * lpn);
        if (!errnums)
                goto cleanup;

        int i;
        /* remove old partitions first */
        for (i = 1; i <= lpn; i++) {
                PedPartition *part = ped_disk_get_partition (disk, i);
                if (part) {
                        unsigned long long length;
                        unsigned long long start;
                        /* get start and length of existing partition */
                        if (get_partition_start_and_length(part,
                                                           &start, &length)
                            && start == part->geom.start
                            && (length == part->geom.length
                                || (resize_partition && part->num < lpn2)))
                        {
                                /* partition is unchanged, or will be resized so nothing to do */
                                ok[i - 1] = 1;
                                continue;
                        }
                }
                /* Attempt to remove the partition, retrying for
                   up to max_sleep_seconds upon any failure due to EBUSY. */
                unsigned int sleep_microseconds = 10000;
                unsigned int max_sleep_seconds = 1;
                unsigned int n_sleep = (max_sleep_seconds
                                        * 1000000 / sleep_microseconds);
                do {
                        ok[i - 1] = remove_partition (disk, i);
                        errnums[i - 1] = errno;
                        if (ok[i - 1] || errnums[i - 1] != EBUSY)
                                break;
                        usleep (sleep_microseconds);
                } while (n_sleep--);
                if (!ok[i - 1] && errnums[i - 1] == ENXIO)
                        ok[i - 1] = 1; /* it already doesn't exist */
        }
        lpn = lpn2;
        /* don't actually add partitions for loop */
        if (strcmp (disk->type->name, "loop") == 0)
                lpn = 0;
        for (i = 1; i <= lpn; i++) {
                PedPartition *part = ped_disk_get_partition (disk, i);
                if (!part)
                        continue;
                unsigned long long length;
                unsigned long long start;
                /* get start and length of existing partition */
                if (get_partition_start_and_length(part,
                                                   &start, &length)
                    && start == part->geom.start)
                {
                        if (length == part->geom.length) {
                                ok[i - 1] = 1;
                                /* partition is unchanged, so nothing to do */
                                continue;
                        }
                        if (resize_partition
                            && start == part->geom.start)
                        {
                                /* try to resize */
                                if (resize_partition (disk, part)) {
                                        ok[i - 1] = 1;
                                        continue;
                                }
                        }
                }
                /* add the (possibly modified or new) partition */
                if (!add_partition (disk, part)) {
                        ok[i - 1] = 0;
                        errnums[i - 1] = errno;
                }
        }

        char *bad_part_list = NULL;
        /* now warn about any errors */
        for (i = 1; i <= lpn; i++) {
                if (ok[i - 1] || errnums[i - 1] == ENXIO)
                        continue;
                if (bad_part_list == NULL) {
                        bad_part_list = malloc (lpn * 5);
                        if (!bad_part_list)
                                goto cleanup;
                        bad_part_list[0] = 0;
                }
                sprintf (bad_part_list + strlen (bad_part_list), "%d, ", i);
        }
        if (bad_part_list == NULL)
                ret = 1;
        else {
                bad_part_list[strlen (bad_part_list) - 2] = 0;
                if (ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_IGNORE_CANCEL,
                        _("Partition(s) %s on %s have been written, but we have "
                          "been unable to inform the kernel of the change, "
                          "probably because it/they are in use.  As a result, "
                          "the old partition(s) will remain in use.  You "
                          "should reboot now before making further changes."),
                        bad_part_list, disk->dev->path) == PED_EXCEPTION_IGNORE)
                        ret = 1;
                free (bad_part_list);
        }
 cleanup:
        free (errnums);
        free (ok);
        return ret;
}

static int
_have_blkpg ()
{
        static int have_blkpg = -1;
        int kver;

        if (have_blkpg != -1)
                return have_blkpg;

        kver = _get_linux_version();
        return have_blkpg = kver >= KERNEL_VERSION (2,4,0) ? 1 : 0;
}

/* Return nonzero upon success, 0 if something fails.  */
static int
linux_disk_commit (PedDisk* disk)
{
        if (disk->dev->type != PED_DEVICE_FILE) {

                /* We now require BLKPG support.  If this assertion fails,
                   please write to the mailing list describing your system.
                   Assuming it's never triggered, ...
                   FIXME: remove this assertion in 2012.  */
                assert (_have_blkpg ());

                if (!_disk_sync_part_table (disk))
                        return 0;
        }

        return 1;
}

#if USE_BLKID
static PedAlignment*
linux_get_minimum_alignment(const PedDevice *dev)
{
        blkid_topology tp = LINUX_SPECIFIC(dev)->topology;
        if (!tp)
                return NULL;

        if (blkid_topology_get_minimum_io_size(tp) == 0)
                return ped_alignment_new(
                        blkid_topology_get_alignment_offset(tp) /
                                dev->sector_size,
                        dev->phys_sector_size / dev->sector_size);

        return ped_alignment_new(
                blkid_topology_get_alignment_offset(tp) / dev->sector_size,
                blkid_topology_get_minimum_io_size(tp) / dev->sector_size);
}

static PedAlignment*
linux_get_optimum_alignment(const PedDevice *dev)
{
        blkid_topology tp = LINUX_SPECIFIC(dev)->topology;
        if (!tp)
                return NULL;

        /* When PED_DEFAULT_ALIGNMENT is divisible by the *_io_size or
	   there are no *_io_size values, use the PED_DEFAULT_ALIGNMENT
           If one or the other will not divide evenly, fall through to
           previous logic. */
        unsigned long optimal_io = blkid_topology_get_optimal_io_size(tp);
        unsigned long minimum_io = blkid_topology_get_minimum_io_size(tp);
        if (
            (!optimal_io && !minimum_io)
	    || (optimal_io && PED_DEFAULT_ALIGNMENT % optimal_io == 0
		&& minimum_io && PED_DEFAULT_ALIGNMENT % minimum_io == 0)
	    || (!minimum_io && optimal_io
		&& PED_DEFAULT_ALIGNMENT % optimal_io == 0)
	    || (!optimal_io && minimum_io
		&& PED_DEFAULT_ALIGNMENT % minimum_io == 0)
           )
            return ped_alignment_new(
                    blkid_topology_get_alignment_offset(tp) / dev->sector_size,
                    PED_DEFAULT_ALIGNMENT / dev->sector_size);

        /* If optimal_io_size is 0 and we don't meet the other criteria
           for using the device.c default, return the minimum alignment. */
        if (blkid_topology_get_optimal_io_size(tp) == 0)
                return linux_get_minimum_alignment(dev);

        return ped_alignment_new(
                blkid_topology_get_alignment_offset(tp) / dev->sector_size,
                blkid_topology_get_optimal_io_size(tp) / dev->sector_size);
}
#endif

#if defined __s390__ || defined __s390x__
/**
 * Check whether this device could be a DASD
 *
 * The device probing yields PED_DEVICE_DASD for native DASD transport
 * If the block device uses a different transport (e.g. virtio)
 * a simplified heuristic (assuming a model 3390 with 4K sectors)
 * is applied (only) on s390x systems for this check.
 *
 * \return 1 if the geometry indicates this could be a DASD
 *         and 0 otherwise
 */
static int
_ped_device_like_dasd(const PedDevice *dev)
{
        return (dev->type == PED_DEVICE_DASD)
          || (dev->hw_geom.heads == 15
              && dev->hw_geom.sectors == 12
              && (dev->hw_geom.cylinders
                  * dev->hw_geom.heads
                  * dev->hw_geom.sectors
                  * dev->phys_sector_size
                  == dev->length * dev->sector_size));
}



static PedAlignment*
s390_get_minimum_alignment(const PedDevice *dev)
{
#if USE_BLKID
        return linux_get_minimum_alignment(dev);
#else
        return ped_alignment_new(0,
                                 dev->phys_sector_size
                                 / dev->sector_size);
#endif
}

static PedAlignment*
s390_get_optimum_alignment(const PedDevice *dev)
{
        /* DASD needs to use minimum alignment */
        if (_ped_device_like_dasd(dev))
                return s390_get_minimum_alignment(dev);
#if USE_BLKID
        return linux_get_optimum_alignment(dev);
#else
        return NULL;
#endif
}
#endif

static PedDeviceArchOps linux_dev_ops = {
        _new:           linux_new,
        destroy:        linux_destroy,
        is_busy:        linux_is_busy,
        open:           linux_open,
        refresh_open:   linux_refresh_open,
        close:          linux_close,
        refresh_close:  linux_refresh_close,
        read:           linux_read,
        write:          linux_write,
        check:          linux_check,
        sync:           linux_sync,
        sync_fast:      linux_sync_fast,
        probe_all:      linux_probe_all,
#if defined __s390__ || defined __s390x__
        get_minimum_alignment:	s390_get_minimum_alignment,
        get_optimum_alignment:	s390_get_optimum_alignment,
#elif USE_BLKID
        get_minimum_alignment:	linux_get_minimum_alignment,
        get_optimum_alignment:	linux_get_optimum_alignment,
#endif
};

PedDiskArchOps linux_disk_ops =  {
        partition_get_path:     linux_partition_get_path,
        partition_is_busy:      linux_partition_is_busy,
        disk_commit:            linux_disk_commit
};

PedArchitecture ped_linux_arch = {
        dev_ops:        &linux_dev_ops,
        disk_ops:       &linux_disk_ops
};
