/*
 * Copyright (C) 2020  Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "partitions.h"

#include <blkid/blkid.h>

#define UNUSED __attribute__((unused))


/*********************** PARTLIST ***********************/
PyObject *Partlist_new (PyTypeObject *type,  PyObject *args UNUSED, PyObject *kwargs UNUSED) {
    PartlistObject *self = (PartlistObject*) type->tp_alloc (type, 0);

    if (self)
        self->Parttable_object = NULL;

    return (PyObject *) self;
}

int Partlist_init (PartlistObject *self UNUSED, PyObject *args UNUSED, PyObject *kwargs UNUSED) {
    return 0;
}

void Partlist_dealloc (PartlistObject *self) {
    if (self->Parttable_object)
        Py_DECREF (self->Parttable_object);

    Py_TYPE (self)->tp_free ((PyObject *) self);
}

PyObject *_Partlist_get_partlist_object (blkid_probe probe) {
    PartlistObject *result = NULL;
    blkid_partlist partlist = NULL;

    if (!probe) {
        PyErr_SetString (PyExc_RuntimeError, "internal error");
        return NULL;
    }

    partlist = blkid_probe_get_partitions (probe);
    if (!partlist) {
        PyErr_SetString (PyExc_RuntimeError, "Failed to get partitions");
        return NULL;
    }

    result = PyObject_New (PartlistObject, &PartlistType);
    if (!result) {
        PyErr_SetString (PyExc_MemoryError, "Failed to create a new Partlist object");
        return NULL;
    }
    Py_INCREF (result);

    result->partlist = partlist;
    result->Parttable_object = NULL;

    return (PyObject *) result;
}

PyDoc_STRVAR(Partlist_get_partition__doc__,
"get_partition (number)\n\n"
"Get partition by number.\n\n"
"It's possible that the list of partitions is *empty*, but there is a valid partition table on the disk.\n"
"This happen when on-disk details about partitions are unknown or the partition table is empty.");
static PyObject *Partlist_get_partition (PartlistObject *self, PyObject *args, PyObject *kwargs) {
    char *kwlist[] = { "number", NULL };
    int partnum = 0;
    int numof = 0;
    blkid_partition blkid_part = NULL;
    PartitionObject *result = NULL;

    if (!PyArg_ParseTupleAndKeywords (args, kwargs, "i", kwlist, &partnum)) {
        return NULL;
    }

    numof = blkid_partlist_numof_partitions (self->partlist);
    if (numof < 0) {
        PyErr_SetString (PyExc_RuntimeError, "Failed to get number of partitions");
        return NULL;
    }

    if (partnum > numof) {
        PyErr_Format (PyExc_RuntimeError, "Cannot get partition %d, partition table has only %d partitions", partnum, numof);
        return NULL;
    }

    blkid_part = blkid_partlist_get_partition (self->partlist, partnum);
    if (!blkid_part) {
        PyErr_Format (PyExc_RuntimeError, "Failed to get partition %d", partnum);
        return NULL;
    }

    result = PyObject_New (PartitionObject, &PartitionType);
    if (!result) {
        PyErr_SetString (PyExc_MemoryError, "Failed to create a new Partition object");
        return NULL;
    }

    result->number = partnum;
    result->partition = blkid_part;
    result->Parttable_object = NULL;

    return (PyObject *) result;
}

#ifdef HAVE_BLKID_2_25
PyDoc_STRVAR(Partlist_get_partition_by_partno__doc__,
"get_partition_by_partno(number)\n\n"
"Get partition by partition number.\n\n"
"This does not assume any order of partitions and correctly handles \"out of order\" "
"partition tables. partition N is located after partition N+1 on the disk.");
static PyObject *Partlist_get_partition_by_partno (PartlistObject *self, PyObject *args, PyObject *kwargs) {
    char *kwlist[] = { "number", NULL };
    int partno = 0;
    blkid_partition blkid_part = NULL;
    PartitionObject *result = NULL;

    if (!PyArg_ParseTupleAndKeywords (args, kwargs, "i", kwlist, &partno)) {
        return NULL;
    }

    blkid_part = blkid_partlist_get_partition_by_partno (self->partlist, partno);
    if (!blkid_part) {
        PyErr_Format (PyExc_RuntimeError, "Failed to get partition %d", partno);
        return NULL;
    }

    result = PyObject_New (PartitionObject, &PartitionType);
    if (!result) {
        PyErr_NoMemory ();
        return NULL;
    }

    result->number = partno;
    result->partition = blkid_part;
    result->Parttable_object = NULL;

    return (PyObject *) result;
}
#endif

static int _Py_Dev_Converter (PyObject *obj, void *p) {
#ifdef HAVE_LONG_LONG
    *((dev_t *)p) = PyLong_AsUnsignedLongLong (obj);
#else
    *((dev_t *)p) = PyLong_AsUnsignedLong (obj);
#endif
    if (PyErr_Occurred ())
        return 0;
    return 1;
}

#ifdef HAVE_LONG_LONG
    #define _PyLong_FromDev PyLong_FromLongLong
#else
    #define _PyLong_FromDev PyLong_FromLong
#endif

PyDoc_STRVAR(Partlist_devno_to_partition__doc__,
"devno_to_partition (devno)\n\n"
"Get partition by devno.\n");
static PyObject *Partlist_devno_to_partition (PartlistObject *self, PyObject *args, PyObject *kwargs) {
    dev_t devno = 0;
    char *kwlist[] = { "devno", NULL };
    blkid_partition blkid_part = NULL;
    PartitionObject *result = NULL;

    if (!PyArg_ParseTupleAndKeywords (args, kwargs, "O&:devno_to_devname", kwlist, _Py_Dev_Converter, &devno))
        return NULL;

    blkid_part = blkid_partlist_devno_to_partition (self->partlist, devno);
    if (!blkid_part) {
        PyErr_Format (PyExc_RuntimeError, "Failed to get partition %zu", devno);
        return NULL;
    }

    result = PyObject_New (PartitionObject, &PartitionType);
    if (!result) {
        PyErr_NoMemory ();
        return NULL;
    }

    result->number = blkid_partition_get_partno (blkid_part);
    result->partition = blkid_part;
    result->Parttable_object = NULL;

    return (PyObject *) result;
}

static PyMethodDef Partlist_methods[] = {
    {"get_partition", (PyCFunction)(void(*)(void)) Partlist_get_partition, METH_VARARGS|METH_KEYWORDS, Partlist_get_partition__doc__},
#ifdef HAVE_BLKID_2_25
    {"get_partition_by_partno", (PyCFunction)(void(*)(void)) Partlist_get_partition_by_partno, METH_VARARGS|METH_KEYWORDS, Partlist_get_partition_by_partno__doc__},
#endif
    {"devno_to_partition", (PyCFunction)(void(*)(void)) Partlist_devno_to_partition, METH_VARARGS|METH_KEYWORDS, Partlist_devno_to_partition__doc__},
    {NULL, NULL, 0, NULL},
};

static PyObject *Partlist_get_table (PartlistObject *self, PyObject *Py_UNUSED (ignored)) {
    if (self->Parttable_object) {
        Py_INCREF (self->Parttable_object);
        return self->Parttable_object;
    }

    self->Parttable_object = _Parttable_get_parttable_object (self->partlist);

    return self->Parttable_object;
}

static PyObject *Partlist_get_numof_partitions (PartlistObject *self, PyObject *Py_UNUSED (ignored)) {
    int ret = 0;

    ret = blkid_partlist_numof_partitions (self->partlist);
    if (ret < 0) {
        PyErr_SetString (PyExc_MemoryError, "Failed to get number of partitions");
        return NULL;
    }

    return PyLong_FromLong (ret);
}


static PyGetSetDef Partlist_getseters[] = {
    {"table", (getter) Partlist_get_table, NULL, "binary interface for partition table on the device", NULL},
    {"numof_partitions", (getter) Partlist_get_numof_partitions, NULL, "number of partitions in the list", NULL},
    {NULL, NULL, NULL, NULL, NULL}
};

PyTypeObject PartlistType = {
    PyVarObject_HEAD_INIT (NULL, 0)
    .tp_name = "blkid.Partlist",
    .tp_basicsize = sizeof (PartlistObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = Partlist_new,
    .tp_dealloc = (destructor) Partlist_dealloc,
    .tp_init = (initproc) Partlist_init,
    .tp_methods = Partlist_methods,
    .tp_getset = Partlist_getseters,
};


/*********************** PARTTABLE ***********************/
PyObject *Parttable_new (PyTypeObject *type,  PyObject *args UNUSED, PyObject *kwargs UNUSED) {
    ParttableObject *self = (ParttableObject*) type->tp_alloc (type, 0);

    return (PyObject *) self;
}

int Parttable_init (ParttableObject *self UNUSED, PyObject *args UNUSED, PyObject *kwargs UNUSED) {
    return 0;
}

void Parttable_dealloc (ParttableObject *self) {
    Py_TYPE (self)->tp_free ((PyObject *) self);
}

PyObject *_Parttable_get_parttable_object (blkid_partlist partlist) {
    ParttableObject *result = NULL;
    blkid_parttable table = NULL;

    if (!partlist) {
        PyErr_SetString(PyExc_RuntimeError, "internal error");
        return NULL;
    }

    table = blkid_partlist_get_table (partlist);
    if (!table) {
        PyErr_SetString (PyExc_RuntimeError, "Failed to get partitions");
        return NULL;
    }

    result = PyObject_New (ParttableObject, &ParttableType);
    if (!result) {
        PyErr_SetString (PyExc_MemoryError, "Failed to create a new Parttable object");
        return NULL;
    }
    Py_INCREF (result);

    result->table = table;

    return (PyObject *) result;
}

PyDoc_STRVAR(Parttable_get_parent__doc__,
"get_parent ()\n\n"
"Parent for nested partition tables.");
static PyObject *Parttable_get_parent (ParttableObject *self, PyObject *Py_UNUSED (ignored)) {
    blkid_partition blkid_part = NULL;
    PartitionObject *result = NULL;

    blkid_part = blkid_parttable_get_parent (self->table);
    if (!blkid_part)
        Py_RETURN_NONE;

    result = PyObject_New (PartitionObject, &PartitionType);
    if (!result) {
        PyErr_SetString (PyExc_MemoryError, "Failed to create a new Partition object");
        return NULL;
    }

    result->number = 0;
    result->partition = blkid_part;

    return (PyObject *) result;
}

static PyMethodDef Parttable_methods[] = {
    {"get_parent", (PyCFunction)(void(*)(void)) Parttable_get_parent, METH_NOARGS, Parttable_get_parent__doc__},
    {NULL, NULL, 0, NULL},
};

static PyObject *Parrtable_get_type (ParttableObject *self, PyObject *Py_UNUSED (ignored)) {
    const char *pttype = blkid_parttable_get_type (self->table);

    return PyUnicode_FromString (pttype);
}

static PyObject *Parrtable_get_id (ParttableObject *self, PyObject *Py_UNUSED (ignored)) {
    const char *ptid = blkid_parttable_get_id (self->table);

    return PyUnicode_FromString (ptid);
}

static PyObject *Parrtable_get_offset (ParttableObject *self, PyObject *Py_UNUSED (ignored)) {
    blkid_loff_t offset = blkid_parttable_get_offset (self->table);

    return PyLong_FromLongLong (offset);
}

static PyGetSetDef Parttable_getseters[] = {
    {"type", (getter) Parrtable_get_type, NULL, "partition table type (type name, e.g. 'dos', 'gpt', ...)", NULL},
    {"id", (getter) Parrtable_get_id, NULL, "GPT disk UUID or DOS disk ID (in hex format)", NULL},
    {"offset", (getter) Parrtable_get_offset, NULL, "position (in bytes) of the partition table", NULL},
    {NULL, NULL, NULL, NULL, NULL}
};

PyTypeObject ParttableType = {
    PyVarObject_HEAD_INIT (NULL, 0)
    .tp_name = "blkid.Parttable",
    .tp_basicsize = sizeof (ParttableObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = Parttable_new,
    .tp_dealloc = (destructor) Parttable_dealloc,
    .tp_init = (initproc) Parttable_init,
    .tp_methods = Parttable_methods,
    .tp_getset = Parttable_getseters,
};

/*********************** PARTITION ***********************/
PyObject *Partition_new (PyTypeObject *type,  PyObject *args UNUSED, PyObject *kwargs UNUSED) {
    PartitionObject *self = (PartitionObject*) type->tp_alloc (type, 0);

    if (self)
        self->Parttable_object = NULL;

    return (PyObject *) self;
}

int Partition_init (PartitionObject *self, PyObject *args, PyObject *kwargs) {
    char *kwlist[] = { "number", NULL };

     if (!PyArg_ParseTupleAndKeywords (args, kwargs, "i", kwlist, &(self->number))) {
        return -1;
    }

    self->partition = NULL;

    return 0;
}

void Partition_dealloc (PartitionObject *self) {
    if (self->Parttable_object)
        Py_DECREF (self->Parttable_object);

    Py_TYPE (self)->tp_free ((PyObject *) self);
}

static PyObject *Partition_get_type (PartitionObject *self, PyObject *Py_UNUSED (ignored)) {
    int type = blkid_partition_get_type (self->partition);

    return PyLong_FromLong (type);
}

static PyObject *Partition_get_type_string (PartitionObject *self, PyObject *Py_UNUSED (ignored)) {
    const char *type = blkid_partition_get_type_string (self->partition);

    return PyUnicode_FromString (type);
}

static PyObject *Partition_get_uuid (PartitionObject *self, PyObject *Py_UNUSED (ignored)) {
    const char *uuid = blkid_partition_get_uuid (self->partition);

    return PyUnicode_FromString (uuid);
}

static PyObject *Partition_get_is_extended (PartitionObject *self, PyObject *Py_UNUSED (ignored)) {
    int extended = blkid_partition_is_extended (self->partition);

    if (extended == 1)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *Partition_get_is_logical (PartitionObject *self, PyObject *Py_UNUSED (ignored)) {
    int logical = blkid_partition_is_logical (self->partition);

    if (logical == 1)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *Partition_get_is_primary (PartitionObject *self, PyObject *Py_UNUSED (ignored)) {
    int primary = blkid_partition_is_primary (self->partition);

    if (primary == 1)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *Partition_get_name (PartitionObject *self, PyObject *Py_UNUSED (ignored)) {
    const char *name = blkid_partition_get_name (self->partition);

    return PyUnicode_FromString (name);
}

static PyObject *Partition_get_flags (PartitionObject *self, PyObject *Py_UNUSED (ignored)) {
    unsigned long long flags = blkid_partition_get_flags (self->partition);

    return PyLong_FromUnsignedLongLong (flags);
}

static PyObject *Partition_get_partno (PartitionObject *self, PyObject *Py_UNUSED (ignored)) {
    int partno = blkid_partition_get_partno (self->partition);

    return PyLong_FromLong (partno);
}

static PyObject *Partition_get_size (PartitionObject *self, PyObject *Py_UNUSED (ignored)) {
    blkid_loff_t size = blkid_partition_get_size (self->partition);

    return PyLong_FromLongLong (size);
}

static PyObject *Partition_get_start (PartitionObject *self, PyObject *Py_UNUSED (ignored)) {
    blkid_loff_t start = blkid_partition_get_start (self->partition);

    return PyLong_FromLongLong (start);
}

PyObject *_Partition_get_parttable_object (blkid_partition partition) {
    ParttableObject *result = NULL;
    blkid_parttable table = NULL;

    if (!partition) {
        PyErr_SetString(PyExc_RuntimeError, "internal error");
        return NULL;
    }

    table = blkid_partition_get_table (partition);
    if (!table) {
        PyErr_SetString (PyExc_RuntimeError, "Failed to get partition table");
        return NULL;
    }

    result = PyObject_New (ParttableObject, &ParttableType);
    if (!result) {
        PyErr_SetString (PyExc_MemoryError, "Failed to create a new Parttable object");
        return NULL;
    }
    Py_INCREF (result);

    result->table = table;

    return (PyObject *) result;
}

static PyObject *Partition_get_table (PartitionObject *self, PyObject *Py_UNUSED (ignored)) {
    if (self->Parttable_object) {
        Py_INCREF (self->Parttable_object);
        return self->Parttable_object;
    }

    self->Parttable_object = _Partition_get_parttable_object (self->partition);

    return self->Parttable_object;
}

static PyGetSetDef Partition_getseters[] = {
    {"type", (getter) Partition_get_type, NULL, "partition type", NULL},
    {"type_string", (getter) Partition_get_type_string, NULL, "partition type string, note the type string is supported by a small subset of partition tables (e.g Mac and EFI GPT)", NULL},
    {"uuid", (getter) Partition_get_uuid, NULL, "partition UUID string if supported by PT (e.g. GPT)", NULL},
    {"is_extended", (getter) Partition_get_is_extended, NULL, "returns whether the partition is extendedor not ", NULL},
    {"is_logical", (getter) Partition_get_is_logical, NULL, "returns whether the partition is logical or not", NULL},
    {"is_primary", (getter) Partition_get_is_primary, NULL, "returns whether the partition is primary or not", NULL},
    {"name", (getter) Partition_get_name, NULL, "partition name string if supported by PT (e.g. Mac)", NULL},
    {"flags", (getter) Partition_get_flags, NULL, "partition flags (or attributes for gpt)", NULL},
    {"partno", (getter) Partition_get_partno, NULL, "proposed partition number (e.g. 'N' from sda'N') or -1 in case of error", NULL},
    {"size", (getter) Partition_get_size, NULL, "size of the partition (in 512-sectors)", NULL},
    {"start", (getter) Partition_get_start, NULL, "start of the partition (in 512-sectors)", NULL},
    {"table", (getter) Partition_get_table, NULL, "partition table object (usually the same for all partitions, except nested partition tables)", NULL},
    {NULL, NULL, NULL, NULL, NULL}
};

PyTypeObject PartitionType = {
    PyVarObject_HEAD_INIT (NULL, 0)
    .tp_name = "blkid.Partition",
    .tp_basicsize = sizeof (PartitionObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = Partition_new,
    .tp_dealloc = (destructor) Partition_dealloc,
    .tp_init = (initproc) Partition_init,
    .tp_getset = Partition_getseters,
};
