/*
 * partitions - partition tables parsing
 *
 * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
 *
 * This file may be redistributed under the terms of the
 * GNU Lesser General Public License.
 *
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdarg.h>

#include "partitions.h"
#include "sysfs.h"

/**
 * SECTION: partitions
 * @title: Partitions probing
 * @short_description: partitions tables detection and parsing
 *
 * This chain supports binary and NAME=value interfaces, but complete PT
 * description is provided by binary interface only. The libblkid prober is
 * compatible with kernel partition tables parser. The parser does not return
 * empty (size=0) partitions or special hidden partitions.
 *
 * NAME=value interface, supported tags:
 *
 * @PTTYPE: partition table type (dos, gpt, etc.).
 *
 * @PTUUID: partition table id (uuid for gpt, hex for dos).

 * @PART_ENTRY_SCHEME: partition table type
 *
 * @PART_ENTRY_NAME: partition name (gpt and mac only)
 *
 * @PART_ENTRY_UUID: partition UUID (gpt, or pseudo IDs for MBR)
 *
 * @PART_ENTRY_TYPE: partition type, 0xNN (e.g 0x82) or type UUID (gpt only) or type string (mac)
 *
 * @PART_ENTRY_FLAGS: partition flags (e.g. boot_ind) or  attributes (e.g. gpt attributes)
 *
 * @PART_ENTRY_NUMBER: partition number
 *
 * @PART_ENTRY_OFFSET: the begin of the partition
 *
 * @PART_ENTRY_SIZE: size of the partition
 *
 * @PART_ENTRY_DISK: whole-disk maj:min
 *
 * Example:
 *
 * <informalexample>
 *  <programlisting>
 * blkid_probe pr;
 * const char *ptname;
 *
 * pr = blkid_new_probe_from_filename(devname);
 * if (!pr)
 *	err("%s: faild to open device", devname);
 *
 * blkid_probe_enable_partitions(pr, TRUE);
 * blkid_do_fullprobe(pr);
 *
 * blkid_probe_lookup_value(pr, "PTTYPE", &ptname, NULL);
 * printf("%s partition type detected\n", pttype);
 *
 * blkid_free_probe(pr);
 *
 * // don't forget to check return codes in your code!
 *  </programlisting>
 * </informalexample>
 *
 * Binary interface:
 *
 * <informalexample>
 *  <programlisting>
 * blkid_probe pr;
 * blkid_partlist ls;
 * int nparts, i;
 *
 * pr = blkid_new_probe_from_filename(devname);
 * if (!pr)
 *	err("%s: faild to open device", devname);
 *
 * ls = blkid_probe_get_partitions(pr);
 * nparts = blkid_partlist_numof_partitions(ls);
 *
 * for (i = 0; i < nparts; i++) {
 *      blkid_partition par = blkid_partlist_get_partition(ls, i);
 *      printf("#%d: %llu %llu  0x%x",
 *               blkid_partition_get_partno(par),
 *               blkid_partition_get_start(par),
 *               blkid_partition_get_size(par),
 *               blkid_partition_get_type(par));
 * }
 *
 * blkid_free_probe(pr);
 *
 * // don't forget to check return codes in your code!
 *  </programlisting>
 * </informalexample>
 */

/*
 * Chain driver function
 */
static int partitions_probe(blkid_probe pr, struct blkid_chain *chn);
static void partitions_free_data(blkid_probe pr, void *data);

/*
 * Partitions chain probing functions
 */
static const struct blkid_idinfo *idinfos[] =
{
	&aix_pt_idinfo,
	&sgi_pt_idinfo,
	&sun_pt_idinfo,
	&dos_pt_idinfo,
	&gpt_pt_idinfo,
	&pmbr_pt_idinfo,	/* always after GPT */
	&mac_pt_idinfo,
	&ultrix_pt_idinfo,
	&bsd_pt_idinfo,
	&unixware_pt_idinfo,
	&solaris_x86_pt_idinfo,
	&minix_pt_idinfo
};

/*
 * Driver definition
 */
const struct blkid_chaindrv partitions_drv = {
	.id           = BLKID_CHAIN_PARTS,
	.name         = "partitions",
	.dflt_enabled = FALSE,
	.idinfos      = idinfos,
	.nidinfos     = ARRAY_SIZE(idinfos),
	.has_fltr     = TRUE,
	.probe        = partitions_probe,
	.safeprobe    = partitions_probe,
	.free_data    = partitions_free_data
};


/*
 * For compatibility with the rest of libblkid API (with the old high-level
 * API) we use completely opaque typedefs for all structs. Don't forget that
 * the final blkid_* types are pointers! See blkid.h.
 *
 * [Just for the record, I hate typedef for pointers --kzak]
 */

/* exported as opaque type "blkid_parttable" */
struct blkid_struct_parttable {
	const char	*type;		/* partition table type */
	blkid_loff_t	offset;		/* begin of the partition table (in bytes) */
	int		nparts;		/* number of partitions */
	blkid_partition	parent;		/* parent of nested partition table */
	char		id[37];		/* PT identifier (e.g. UUID for GPT) */

	struct list_head t_tabs;	/* all tables */
};

/* exported as opaque type "blkid_partition" */
struct blkid_struct_partition {
	blkid_loff_t	start;		/* begin of the partition (512-bytes sectors) */
	blkid_loff_t	size;		/* size of the partitions (512-bytes sectors) */

	int		type;		/* partition type */
	char		typestr[37];	/* partition type string (GPT and Mac) */

	unsigned long long flags;	/* partition flags / attributes */

	int		partno;		/* partition number */
	char		uuid[37];	/* UUID (when supported by PT), e.g GPT */
	unsigned char	name[128];	/* Partition in UTF8 name (when supporte by PT), e.g. Mac */

	blkid_parttable	tab;		/* partition table */
};

/* exported as opaque type "blkid_partlist" */
struct blkid_struct_partlist {
	int		next_partno;	/* next partition number */
	blkid_partition next_parent;	/* next parent if parsing nested PT */

	int		nparts;		/* number of partitions */
	int		nparts_max;	/* max.number of partitions */
	blkid_partition	parts;		/* array of partitions */

	struct list_head l_tabs;	/* list of partition tables */
};

static int blkid_partitions_probe_partition(blkid_probe pr);

/**
 * blkid_probe_enable_partitions:
 * @pr: probe
 * @enable: TRUE/FALSE
 *
 * Enables/disables the partitions probing for non-binary interface.
 *
 * Returns: 0 on success, or -1 in case of error.
 */
int blkid_probe_enable_partitions(blkid_probe pr, int enable)
{
	if (!pr)
		return -1;
	pr->chains[BLKID_CHAIN_PARTS].enabled = enable;
	return 0;
}

/**
 * blkid_probe_set_partitions_flags:
 * @pr: prober
 * @flags: BLKID_PARTS_* flags
 *
 * Sets probing flags to the partitions prober. This function is optional.
 *
 * Returns: 0 on success, or -1 in case of error.
 */
int blkid_probe_set_partitions_flags(blkid_probe pr, int flags)
{
	if (!pr)
		return -1;
	pr->chains[BLKID_CHAIN_PARTS].flags = flags;
	return 0;
}

/**
 * blkid_probe_reset_partitions_filter:
 * @pr: prober
 *
 * Resets partitions probing filter
 *
 * Returns: 0 on success, or -1 in case of error.
 */
int blkid_probe_reset_partitions_filter(blkid_probe pr)
{
	return __blkid_probe_reset_filter(pr, BLKID_CHAIN_PARTS);
}

/**
 * blkid_probe_invert_partitions_filter:
 * @pr: prober
 *
 * Inverts partitions probing filter
 *
 * Returns: 0 on success, or -1 in case of error.
 */
int blkid_probe_invert_partitions_filter(blkid_probe pr)
{
	return __blkid_probe_invert_filter(pr, BLKID_CHAIN_PARTS);
}

/**
 * blkid_probe_filter_partitions_type:
 * @pr: prober
 * @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
 * @names: NULL terminated array of probing function names (e.g. "vfat").
 *
 *  %BLKID_FLTR_NOTIN  - probe for all items which are NOT IN @names
 *
 *  %BLKID_FLTR_ONLYIN - probe for items which are IN @names
 *
 * Returns: 0 on success, or -1 in case of error.
 */
int blkid_probe_filter_partitions_type(blkid_probe pr, int flag, char *names[])
{
	return __blkid_probe_filter_types(pr, BLKID_CHAIN_PARTS, flag, names);
}

/**
 * blkid_probe_get_partitions:
 * @pr: probe
 *
 * This is a binary interface for partitions. See also blkid_partlist_*
 * functions.
 *
 * This function is independent on blkid_do_[safe,full]probe() and
 * blkid_probe_enable_partitions() calls.
 *
 * WARNING: the returned object will be overwritten by the next
 *          blkid_probe_get_partitions() call for the same @pr. If you want to
 *          use more blkid_partlist objects in the same time you have to create
 *          more blkid_probe handlers (see blkid_new_probe()).
 *
 * Returns: list of partitions, or NULL in case of error.
 */
blkid_partlist blkid_probe_get_partitions(blkid_probe pr)
{
	return (blkid_partlist) blkid_probe_get_binary_data(pr,
			&pr->chains[BLKID_CHAIN_PARTS]);
}

/* for internal usage only */
blkid_partlist blkid_probe_get_partlist(blkid_probe pr)
{
	return (blkid_partlist) pr->chains[BLKID_CHAIN_PARTS].data;
}

static void blkid_probe_set_partlist(blkid_probe pr, blkid_partlist ls)
{
	pr->chains[BLKID_CHAIN_PARTS].data = ls;
}

static void ref_parttable(blkid_parttable tab)
{
	tab->nparts++;
}

static void unref_parttable(blkid_parttable tab)
{
	tab->nparts--;

	if (tab->nparts <= 0) {
		list_del(&tab->t_tabs);
		free(tab);
	}
}

/* free all allocated parttables */
static void free_parttables(blkid_partlist ls)
{
	if (!ls || !ls->l_tabs.next)
		return;

	/* remove unassigned partition tables */
	while (!list_empty(&ls->l_tabs)) {
		blkid_parttable tab = list_entry(ls->l_tabs.next,
					struct blkid_struct_parttable, t_tabs);
		unref_parttable(tab);
	}
}

static void reset_partlist(blkid_partlist ls)
{
	if (!ls)
		return;

	free_parttables(ls);

	if (ls->next_partno) {
		/* already initialized - reset */
		int tmp_nparts = ls->nparts_max;
		blkid_partition tmp_parts = ls->parts;

		memset(ls, 0, sizeof(struct blkid_struct_partlist));

		ls->nparts_max = tmp_nparts;
		ls->parts = tmp_parts;
	}

	ls->nparts = 0;
	ls->next_partno = 1;
	INIT_LIST_HEAD(&ls->l_tabs);

	DBG(LOWPROBE, ul_debug("partlist reset"));
}

static blkid_partlist partitions_init_data(struct blkid_chain *chn)
{
	blkid_partlist ls;

	if (chn->data)
		ls = (blkid_partlist) chn->data;
	else {
		/* allocate the new list of partitions */
		ls = calloc(1, sizeof(struct blkid_struct_partlist));
		if (!ls)
			return NULL;
		chn->data = (void *) ls;
	}

	reset_partlist(ls);

	DBG(LOWPROBE, ul_debug("parts: initialized partitions list (%p, size=%d)",
		ls, ls->nparts_max));
	return ls;
}

static void partitions_free_data(blkid_probe pr __attribute__((__unused__)),
				 void *data)
{
	blkid_partlist ls = (blkid_partlist) data;

	if (!ls)
		return;

	free_parttables(ls);

	/* deallocate partitions and partlist */
	free(ls->parts);
	free(ls);
}

blkid_parttable blkid_partlist_new_parttable(blkid_partlist ls,
				const char *type, blkid_loff_t offset)
{
	blkid_parttable tab;

	tab = calloc(1, sizeof(struct blkid_struct_parttable));
	if (!tab)
		return NULL;
	tab->type = type;
	tab->offset = offset;
	tab->parent = ls->next_parent;

	INIT_LIST_HEAD(&tab->t_tabs);
	list_add_tail(&tab->t_tabs, &ls->l_tabs);

	DBG(LOWPROBE, ul_debug("parts: create a new partition table "
		       "(%p, type=%s, offset=%"PRId64")", tab, type, offset));
	return tab;
}

static blkid_partition new_partition(blkid_partlist ls, blkid_parttable tab)
{
	blkid_partition par;

	if (ls->nparts + 1 > ls->nparts_max) {
		/* Linux kernel has DISK_MAX_PARTS=256, but it's too much for
		 * generic Linux machine -- let start with 32 partititions.
		 */
		void *tmp = realloc(ls->parts, (ls->nparts_max + 32) *
					sizeof(struct blkid_struct_partition));
		if (!tmp)
			return NULL;
		ls->parts = tmp;
		ls->nparts_max += 32;
	}

	par = &ls->parts[ls->nparts++];
	memset(par, 0, sizeof(struct blkid_struct_partition));

	ref_parttable(tab);
	par->tab = tab;
	par->partno = blkid_partlist_increment_partno(ls);

	return par;
}

blkid_partition blkid_partlist_add_partition(blkid_partlist ls,
					blkid_parttable tab,
					blkid_loff_t start, blkid_loff_t size)
{
	blkid_partition par = new_partition(ls, tab);

	if (!par)
		return NULL;

	par->start = start;
	par->size = size;

	DBG(LOWPROBE, ul_debug("parts: add partition (%p start=%"
		PRId64 ", size=%" PRId64 ", table=%p)",
		par, par->start, par->size, tab));
	return par;
}

/* allows to modify used partitions numbers (for example for logical partitions) */
int blkid_partlist_set_partno(blkid_partlist ls, int partno)
{
	if (!ls)
		return -1;
	ls->next_partno = partno;
	return 0;
}

int blkid_partlist_increment_partno(blkid_partlist ls)
{
	return ls ? ls->next_partno++ : -1;
}

/* allows to set "parent" for the next nested partition */
int blkid_partlist_set_parent(blkid_partlist ls, blkid_partition par)
{
	if (!ls)
		return -1;
	ls->next_parent = par;
	return 0;
}

blkid_partition blkid_partlist_get_parent(blkid_partlist ls)
{
	if (!ls)
		return NULL;
	return ls->next_parent;
}

int blkid_partitions_need_typeonly(blkid_probe pr)
{
	struct blkid_chain *chn = blkid_probe_get_chain(pr);

	return chn && chn->data && chn->binary ? FALSE : TRUE;
}

/* get private chain flags */
int blkid_partitions_get_flags(blkid_probe pr)
{
	struct blkid_chain *chn = blkid_probe_get_chain(pr);

	return chn ? chn->flags : 0;
}

/* check if @start and @size are within @par partition */
int blkid_is_nested_dimension(blkid_partition par,
			blkid_loff_t start, blkid_loff_t size)
{
	blkid_loff_t pstart;
	blkid_loff_t psize;

	if (!par)
		return 0;

	pstart = blkid_partition_get_start(par);
	psize = blkid_partition_get_size(par);

	if (start < pstart || start + size > pstart + psize)
		return 0;

	return 1;
}

static int idinfo_probe(blkid_probe pr, const struct blkid_idinfo *id,
			struct blkid_chain *chn)
{
	const struct blkid_idmag *mag = NULL;
	blkid_loff_t off;
	int rc = BLKID_PROBE_NONE;		/* default is nothing */

	if (pr->size <= 0 || (id->minsz && id->minsz > pr->size))
		goto nothing;	/* the device is too small */
	if (pr->flags & BLKID_FL_NOSCAN_DEV)
		goto nothing;

	rc = blkid_probe_get_idmag(pr, id, &off, &mag);
	if (rc != BLKID_PROBE_OK)
		goto nothing;

	/* final check by probing function */
	if (id->probefunc) {
		DBG(LOWPROBE, ul_debug(
			"%s: ---> call probefunc()", id->name));
		rc = id->probefunc(pr, mag);
		if (rc < 0) {
			/* reset after error */
			reset_partlist(blkid_probe_get_partlist(pr));
			if (chn && !chn->binary)
				blkid_probe_chain_reset_vals(pr, chn);
			DBG(LOWPROBE, ul_debug("%s probefunc failed, rc %d",
						  id->name, rc));
		}
		if (rc == BLKID_PROBE_OK && mag && chn && !chn->binary)
			rc = blkid_probe_set_magic(pr, off, mag->len,
					(unsigned char *) mag->magic);

		DBG(LOWPROBE, ul_debug("%s: <--- (rc = %d)", id->name, rc));
	}

	return rc;

nothing:
	return BLKID_PROBE_NONE;
}

/*
 * The blkid_do_probe() backend.
 */
static int partitions_probe(blkid_probe pr, struct blkid_chain *chn)
{
	int rc = BLKID_PROBE_NONE;
	size_t i;

	if (!pr || chn->idx < -1)
		return -EINVAL;

	blkid_probe_chain_reset_vals(pr, chn);

	if (pr->flags & BLKID_FL_NOSCAN_DEV)
		return BLKID_PROBE_NONE;

	if (chn->binary)
		partitions_init_data(chn);

	if (!pr->wipe_size && (pr->prob_flags & BLKID_PROBE_FL_IGNORE_PT))
		goto details_only;

	DBG(LOWPROBE, ul_debug("--> starting probing loop [PARTS idx=%d]",
		chn->idx));

	i = chn->idx < 0 ? 0 : chn->idx + 1U;

	for ( ; i < ARRAY_SIZE(idinfos); i++) {
		const char *name;

		chn->idx = i;

		/* apply filter */
		if (chn->fltr && blkid_bmp_get_item(chn->fltr, i))
			continue;

		/* apply checks from idinfo */
		rc = idinfo_probe(pr, idinfos[i], chn);
		if (rc < 0)
			break;
		if (rc != BLKID_PROBE_OK)
			continue;

		name = idinfos[i]->name;

		if (!chn->binary)
			/*
			 * Non-binary interface, set generic variables. Note
			 * that the another variables could be set in prober
			 * functions.
			 */
			blkid_probe_set_value(pr, "PTTYPE",
						(unsigned char *) name,
						strlen(name) + 1);

		DBG(LOWPROBE, ul_debug("<-- leaving probing loop (type=%s) [PARTS idx=%d]",
			name, chn->idx));
		rc = BLKID_PROBE_OK;
		break;
	}

	if (rc != BLKID_PROBE_OK) {
		DBG(LOWPROBE, ul_debug("<-- leaving probing loop (failed=%d) [PARTS idx=%d]",
			rc, chn->idx));
	}

details_only:
	/*
	 * Gather PART_ENTRY_* values if the current device is a partition.
	 */
	if ((rc == BLKID_PROBE_OK || rc == BLKID_PROBE_NONE) && !chn->binary &&
	    (blkid_partitions_get_flags(pr) & BLKID_PARTS_ENTRY_DETAILS)) {

		int xrc = blkid_partitions_probe_partition(pr);

		/* partition entry probing is optional, and "not-found" from
		 * this sub-probing must not to overwrite previous success. */
		if (xrc < 0)
			rc = xrc;			/* always propagate errors */
		else if (rc == BLKID_PROBE_NONE)
			rc = xrc;
	}

	DBG(LOWPROBE, ul_debug("partitions probe done [rc=%d]",	rc));
	return rc;
}

/* Probe for nested partition table within the parental partition */
int blkid_partitions_do_subprobe(blkid_probe pr, blkid_partition parent,
		const struct blkid_idinfo *id)
{
	blkid_probe prc;
	int rc;
	blkid_partlist ls;
	blkid_loff_t sz, off;

	DBG(LOWPROBE, ul_debug(
		"parts: ----> %s subprobe requested (parent=%p)",
		id->name, parent));

	if (!pr || !parent || !parent->size)
		return -EINVAL;
	if (pr->flags & BLKID_FL_NOSCAN_DEV)
		return BLKID_PROBE_NONE;

	/* range defined by parent */
	sz = ((blkid_loff_t) parent->size) << 9;
	off = ((blkid_loff_t) parent->start) << 9;

	if (off < pr->off || pr->off + pr->size < off + sz) {
		DBG(LOWPROBE, ul_debug(
			"ERROR: parts: <---- '%s' subprobe: overflow detected.",
			id->name));
		return -ENOSPC;
	}

	/* create private prober */
	prc = blkid_clone_probe(pr);
	if (!prc)
		return -ENOMEM;

	blkid_probe_set_dimension(prc, off, sz);

	/* clone is always with reset chain, fix it */
	prc->cur_chain = blkid_probe_get_chain(pr);

	/*
	 * Set 'parent' to the current list of the partitions and use the list
	 * in cloned prober (so the cloned prober will extend the current list
	 * of partitions rather than create a new).
	 */
	ls = blkid_probe_get_partlist(pr);
	blkid_partlist_set_parent(ls, parent);

	blkid_probe_set_partlist(prc, ls);

	rc = idinfo_probe(prc, id, blkid_probe_get_chain(pr));

	blkid_probe_set_partlist(prc, NULL);
	blkid_partlist_set_parent(ls, NULL);

	blkid_free_probe(prc);	/* free cloned prober */

	DBG(LOWPROBE, ul_debug(
		"parts: <---- %s subprobe done (parent=%p, rc=%d)",
		id->name, parent, rc));

	return rc;
}

static int blkid_partitions_probe_partition(blkid_probe pr)
{
	blkid_probe disk_pr = NULL;
	blkid_partlist ls;
	blkid_partition par;
	dev_t devno;

	DBG(LOWPROBE, ul_debug("parts: start probing for partition entry"));

	if (pr->flags & BLKID_FL_NOSCAN_DEV)
		goto nothing;

	devno = blkid_probe_get_devno(pr);
	if (!devno)
		goto nothing;

	disk_pr = blkid_probe_get_wholedisk_probe(pr);
	if (!disk_pr)
		goto nothing;

	/* parse PT */
	ls = blkid_probe_get_partitions(disk_pr);
	if (!ls)
		goto nothing;

	par = blkid_partlist_devno_to_partition(ls, devno);
	if (!par)
		goto nothing;
	else {
		const char *v;
		blkid_parttable tab = blkid_partition_get_table(par);
		dev_t disk = blkid_probe_get_devno(disk_pr);

		if (tab) {
			v = blkid_parttable_get_type(tab);
			if (v)
				blkid_probe_set_value(pr, "PART_ENTRY_SCHEME",
					(unsigned char *) v, strlen(v) + 1);
		}

		v = blkid_partition_get_name(par);
		if (v)
			blkid_probe_set_value(pr, "PART_ENTRY_NAME",
				(unsigned char *) v, strlen(v) + 1);

		v = blkid_partition_get_uuid(par);
		if (v)
			blkid_probe_set_value(pr, "PART_ENTRY_UUID",
				(unsigned char *) v, strlen(v) + 1);

		/* type */
		v = blkid_partition_get_type_string(par);
		if (v)
			blkid_probe_set_value(pr, "PART_ENTRY_TYPE",
				(unsigned char *) v, strlen(v) + 1);
		else
			blkid_probe_sprintf_value(pr, "PART_ENTRY_TYPE",
				"0x%x", blkid_partition_get_type(par));

		if (blkid_partition_get_flags(par))
			blkid_probe_sprintf_value(pr, "PART_ENTRY_FLAGS",
				"0x%llx", blkid_partition_get_flags(par));

		blkid_probe_sprintf_value(pr, "PART_ENTRY_NUMBER",
				"%d", blkid_partition_get_partno(par));

		blkid_probe_sprintf_value(pr, "PART_ENTRY_OFFSET", "%jd",
				blkid_partition_get_start(par));
		blkid_probe_sprintf_value(pr, "PART_ENTRY_SIZE", "%jd",
				blkid_partition_get_size(par));

		blkid_probe_sprintf_value(pr, "PART_ENTRY_DISK", "%u:%u",
				major(disk), minor(disk));
	}

	DBG(LOWPROBE, ul_debug("parts: end probing for partition entry [success]"));
	return BLKID_PROBE_OK;

nothing:
	DBG(LOWPROBE, ul_debug("parts: end probing for partition entry [nothing]"));
	return BLKID_PROBE_NONE;


}

/*
 * Returns 1 if the device is whole-disk and the area specified by @offset and
 * @size is covered by any partition.
 */
int blkid_probe_is_covered_by_pt(blkid_probe pr,
				 blkid_loff_t offset, blkid_loff_t size)
{
	blkid_probe prc = NULL;
	blkid_partlist ls = NULL;
	blkid_loff_t start, end;
	int nparts, i, rc = 0;

	DBG(LOWPROBE, ul_debug(
		"=> checking if off=%jd size=%jd covered by PT",
		offset, size));

	if (pr->flags & BLKID_FL_NOSCAN_DEV)
		goto done;

	prc = blkid_clone_probe(pr);
	if (!prc)
		goto done;

	ls = blkid_probe_get_partitions(prc);
	if (!ls)
		goto done;

	nparts = blkid_partlist_numof_partitions(ls);
	if (!nparts)
		goto done;

	end = (offset + size) >> 9;
	start = offset >> 9;

	/* check if the partition table fits into the device */
	for (i = 0; i < nparts; i++) {
		blkid_partition par = &ls->parts[i];

		if (par->start + par->size > (pr->size >> 9)) {
			DBG(LOWPROBE, ul_debug("partition #%d overflows "
				"device (off=%" PRId64 " size=%" PRId64 ")",
				par->partno, par->start, par->size));
			goto done;
		}
	}

	/* check if the requested area is covered by PT */
	for (i = 0; i < nparts; i++) {
		blkid_partition par = &ls->parts[i];

		if (start >= par->start && end <= par->start + par->size) {
			rc = 1;
			break;
		}
	}
done:
	blkid_free_probe(prc);

	DBG(LOWPROBE, ul_debug("<= %s covered by PT", rc ? "IS" : "NOT"));
	return rc;
}

/**
 * blkid_known_pttype:
 * @pttype: partiton name
 *
 * Returns: 1 for known or 0 for unknown partition type.
 */
int blkid_known_pttype(const char *pttype)
{
	size_t i;

	if (!pttype)
		return 0;

	for (i = 0; i < ARRAY_SIZE(idinfos); i++) {
		const struct blkid_idinfo *id = idinfos[i];
		if (strcmp(id->name, pttype) == 0)
			return 1;
	}
	return 0;
}

/**
 * blkid_partlist_numof_partitions:
 * @ls: partitions list
 *
 * Returns: number of partitions in the list or -1 in case of error.
 */
int blkid_partlist_numof_partitions(blkid_partlist ls)
{
	return ls ? ls->nparts : -1;
}

/**
 * blkid_partlist_get_table:
 * @ls: partitions list
 *
 * Returns: top-level partition table or NULL of there is not a partition table
 * on the device.
 */
blkid_parttable blkid_partlist_get_table(blkid_partlist ls)
{
	if (!ls || list_empty(&ls->l_tabs))
		return NULL;

	return list_entry(ls->l_tabs.next,
			struct blkid_struct_parttable, t_tabs);
}


/**
 * blkid_partlist_get_partition:
 * @ls: partitions list
 * @n: partition number in range 0..N, where 'N' is blkid_partlist_numof_partitions().
 *
 * It's possible that the list of partitions is *empty*, but there is a valid
 * partition table on the disk. This happen when on-disk details about
 * partitions are unknown or the partition table is empty.
 *
 * See also blkid_partlist_get_table().
 *
 * Returns: partition object or NULL in case or error.
 */
blkid_partition blkid_partlist_get_partition(blkid_partlist ls, int n)
{
	if (!ls || n < 0 || n >= ls->nparts)
		return NULL;

	return &ls->parts[n];
}

/**
 * blkid_partlist_get_partition_by_partno
 * @ls: partitions list
 * @n: the partition number (e.g. 'N' from sda'N')
 *
 * This does not assume any order of the input blkid_partlist.  And correctly
 * handles "out of order" partition tables.  partition N is located after
 * partition N+1 on the disk.
 *
 * Returns: partition object or NULL in case or error.
 */
blkid_partition blkid_partlist_get_partition_by_partno(blkid_partlist ls, int n)
{
	int i, nparts;
	blkid_partition par;

	if (!ls)
		return NULL;

	nparts = blkid_partlist_numof_partitions(ls);
	for (i = 0; i < nparts; i++) {
		par = blkid_partlist_get_partition(ls, i);
		if (n == blkid_partition_get_partno(par))
			return par;
	}
	return NULL;
}


/**
 * blkid_partlist_devno_to_partition:
 * @ls: partitions list
 * @devno: requested partition
 *
 * This function tries to get start and size for @devno from sysfs and
 * returns a partition from @ls which matches with the values from sysfs.
 *
 * This function is necessary when you want to make a relation between an entry
 * in the partition table (@ls) and block devices in your system.
 *
 * Returns: partition object or NULL in case or error.
 */
blkid_partition blkid_partlist_devno_to_partition(blkid_partlist ls, dev_t devno)
{
	struct sysfs_cxt sysfs;
	uint64_t start, size;
	int i, rc, partno = 0;

	if (!ls)
		return NULL;

	DBG(LOWPROBE, ul_debug("triyng to convert devno 0x%llx to partition",
			(long long) devno));

	if (sysfs_init(&sysfs, devno, NULL)) {
		DBG(LOWPROBE, ul_debug("failed t init sysfs context"));
		return NULL;
	}
	rc = sysfs_read_u64(&sysfs, "size", &size);
	if (!rc) {
		rc = sysfs_read_u64(&sysfs, "start", &start);
		if (rc) {
			/* try to get partition number from DM uuid.
			 */
			char *uuid = sysfs_strdup(&sysfs, "dm/uuid");
			char *tmp = uuid;
			char *prefix = uuid ? strsep(&tmp, "-") : NULL;

			if (prefix && strncasecmp(prefix, "part", 4) == 0) {
				char *end = NULL;

				partno = strtol(prefix + 4, &end, 10);
				if (prefix == end || (end && *end))
					partno = 0;
				else
					rc = 0;		/* success */
			}
			free(uuid);
		}
	}

	sysfs_deinit(&sysfs);

	if (rc)
		return NULL;

	if (partno) {
		DBG(LOWPROBE, ul_debug("mapped by DM, using partno %d", partno));

		/*
		 * Partition mapped by kpartx does not provide "start" offset
		 * in /sys, but if we know partno and size of the partition
		 * that we can probably make the releation bettween the device
		 * and an entry in partition table.
		 */
		 for (i = 0; i < ls->nparts; i++) {
			 blkid_partition par = &ls->parts[i];

			 if (partno != blkid_partition_get_partno(par))
				 continue;

			 if ((blkid_loff_t) size == blkid_partition_get_size(par) ||
			     (blkid_partition_is_extended(par) && size <= 1024))
				 return par;

		 }
		 return NULL;
	}

	DBG(LOWPROBE, ul_debug("searching by offset/size"));

	for (i = 0; i < ls->nparts; i++) {
		blkid_partition par = &ls->parts[i];

		if (blkid_partition_get_start(par) == (blkid_loff_t) start &&
		    blkid_partition_get_size(par) == (blkid_loff_t) size)
			return par;

		/* exception for extended dos partitions */
		if (blkid_partition_get_start(par) == (blkid_loff_t) start &&
		    blkid_partition_is_extended(par) && size <= 1024)
			return par;

	}

	DBG(LOWPROBE, ul_debug("not found partition for device"));
	return NULL;
}


int blkid_parttable_set_uuid(blkid_parttable tab, const unsigned char *id)
{
	if (!tab)
		return -1;

	blkid_unparse_uuid(id, tab->id, sizeof(tab->id));
	return 0;
}

int blkid_parttable_set_id(blkid_parttable tab, const unsigned char *id)
{
	if (!tab)
		return -1;

	strncpy(tab->id, (const char *) id, sizeof(tab->id));
	return 0;
}

/* set PTUUID variable for non-binary API */
int blkid_partitions_set_ptuuid(blkid_probe pr, unsigned char *uuid)
{
	struct blkid_chain *chn = blkid_probe_get_chain(pr);
	struct blkid_prval *v;

	if (chn->binary || blkid_uuid_is_empty(uuid, 16))
		return 0;

	v = blkid_probe_assign_value(pr, "PTUUID");

	blkid_unparse_uuid(uuid, (char *) v->data, sizeof(v->data));
	v->len = 37;

	return 0;
}

/* set PTUUID variable for non-binary API for tables where
 * the ID is just a string */
int blkid_partitions_strcpy_ptuuid(blkid_probe pr, char *str)
{
	struct blkid_chain *chn = blkid_probe_get_chain(pr);
	struct blkid_prval *v;
	size_t len;

	if (chn->binary || !str || !*str)
		return 0;

	len = strlen((char *) str);
	if (len > BLKID_PROBVAL_BUFSIZ)
		len = BLKID_PROBVAL_BUFSIZ;

	v = blkid_probe_assign_value(pr, "PTUUID");
	if (v) {
		if (len == BLKID_PROBVAL_BUFSIZ)
			len--;		/* make a space for \0 */

		memcpy((char *) v->data, str, len);
		v->data[len] = '\0';
		v->len = len + 1;
		return 0;
	}
	return -1;
}

/**
 * blkid_parttable_get_id:
 * @tab: partition table
 *
 * The ID is GPT disk UUID or DOS disk ID (in hex format).
 *
 * Returns: partition table ID (for example GPT disk UUID) or NULL
 */
const char *blkid_parttable_get_id(blkid_parttable tab)
{
	return tab && *tab->id ? tab->id : NULL;
}


int blkid_partition_set_type(blkid_partition par, int type)
{
	if (!par)
		return -1;
	par->type = type;
	return 0;
}

/**
 * blkid_parttable_get_type:
 * @tab: partition table
 *
 * Returns: partition table type (type name, e.g. "dos", "gpt", ...)
 */
const char *blkid_parttable_get_type(blkid_parttable tab)
{
	return tab ? tab->type : NULL;
}

/**
 * blkid_parttable_get_parent:
 * @tab: partition table
 *
 * Returns: parent for nexted partitition tables or NULL.
 */
blkid_partition blkid_parttable_get_parent(blkid_parttable tab)
{
	return tab ? tab->parent : NULL;
}

/**
 * blkid_parttable_get_offset:
 * @tab: partition table
 *
 * Note the position is relative to begin of the device as defined by
 * blkid_probe_set_device() for primary partition table, and relative
 * to parental partition for nested patition tables.
 *
 * <informalexample>
 *   <programlisting>
 * off_t offset;
 * blkid_partition parent = blkid_parttable_get_parent(tab);
 *
 * offset = blkid_parttable_get_offset(tab);
 *
 * if (parent)
 *      / * 'tab' is nested partition table * /
 *	offset += blkid_partition_get_start(parent);
 *   </programlisting>
 * </informalexample>

 * Returns: position (in bytes) of the partition table or -1 in case of error.
 *
 */
blkid_loff_t blkid_parttable_get_offset(blkid_parttable tab)
{
	return tab ? tab->offset : -1;
}

/**
 * blkid_partition_get_table:
 * @par: partition
 *
 * The "parttable" describes partition table. The table is usually the same for
 * all partitions -- except nested partition tables.
 *
 * For example bsd, solaris, etc. use a nested partition table within
 * standard primary dos partition:
 *
 * <informalexample>
 *   <programlisting>
 *
 *  -- dos partition table
 *  0: sda1     dos primary partition
 *  1: sda2     dos primary partition
 *     -- bsd partition table (with in sda2)
 *  2:    sda5  bds partition
 *  3:    sda6  bds partition
 *
 *   </programlisting>
 * </informalexample>
 *
 * The library does not to use a separate partition table object for dos logical
 * partitions (partitions within extended partition). It's possible to
 * differentiate between logical, extended and primary partitions by
 *
 *	blkid_partition_is_{extended,primary,logical}().
 *
 * Returns: partition table object or NULL in case of error.
 */
blkid_parttable blkid_partition_get_table(blkid_partition par)
{
	return par ? par->tab : NULL;
}

static int partition_get_logical_type(blkid_partition par)
{
	blkid_parttable tab;

	if (!par)
		return -1;

	tab = blkid_partition_get_table(par);
	if (!tab || !tab->type)
		return -1;

	if (tab->parent)
		return 'L';  /* report nested partitions as logical */

	if (!strcmp(tab->type, "dos")) {
		if (par->partno > 4)
			return 'L';	/* logical */

	        if(par->type == MBR_DOS_EXTENDED_PARTITION ||
                   par->type == MBR_W95_EXTENDED_PARTITION ||
		   par->type == MBR_LINUX_EXTENDED_PARTITION)
			return 'E';
	}
	return 'P';
}

/**
 * blkid_partition_is_primary:
 * @par: partition
 *
 * Note, this function returns FALSE for DOS extended partitions and
 * all partitions in nested partition tables.
 *
 * Returns: 1 if the partitions is primary partition or 0 if not.
 */
int blkid_partition_is_primary(blkid_partition par)
{
	return partition_get_logical_type(par) == 'P' ? TRUE : FALSE;
}

/**
 * blkid_partition_is_extended:
 * @par: partition
 *
 * Returns: 1 if the partitions is extended (dos, windows or linux)
 * partition or 0 if not.
 */
int blkid_partition_is_extended(blkid_partition par)
{
	return partition_get_logical_type(par) == 'E' ? TRUE : FALSE;
}

/**
 * blkid_partition_is_logical:
 * @par: partition
 *
 * Note that this function returns TRUE for all partitions in all
 * nested partition tables (e.g. BSD labels).
 *
 * Returns: 1 if the partitions is logical partition or 0 if not.
 */
int blkid_partition_is_logical(blkid_partition par)
{
	return partition_get_logical_type(par) == 'L' ? TRUE : FALSE;
}

static void set_string(unsigned char *item, size_t max,
				const unsigned char *data, size_t len)
{
	if (len >= max)
		len = max - 1;

	memcpy(item, data, len);
	item[len] = '\0';

	blkid_rtrim_whitespace(item);
}

int blkid_partition_set_name(blkid_partition par,
		const unsigned char *name, size_t len)
{
	if (!par)
		return -1;

	set_string(par->name, sizeof(par->name), name, len);
	return 0;
}

int blkid_partition_set_utf8name(blkid_partition par, const unsigned char *name,
		size_t len, int enc)
{
	if (!par)
		return -1;

	blkid_encode_to_utf8(enc, par->name, sizeof(par->name), name, len);
	blkid_rtrim_whitespace(par->name);
	return 0;
}

int blkid_partition_set_uuid(blkid_partition par, const unsigned char *uuid)
{
	if (!par)
		return -1;

	blkid_unparse_uuid(uuid, par->uuid, sizeof(par->uuid));
	return 0;
}

int blkid_partition_gen_uuid(blkid_partition par)
{
	if (!par || !par->tab || !*par->tab->id)
		return -1;

	snprintf(par->uuid, sizeof(par->uuid), "%s-%02x",
			par->tab->id, par->partno);
	return 0;
}

/**
 * blkid_partition_get_name:
 * @par: partition
 *
 * Returns: partition name string if supported by PT (e.g. Mac) or NULL.
 */
const char *blkid_partition_get_name(blkid_partition par)
{
	return par && *par->name ? (char *) par->name : NULL;
}

/**
 * blkid_partition_get_uuid:
 * @par: partition
 *
 * Returns: partition UUID string if supported by PT (e.g. GPT) or NULL.
 */
const char *blkid_partition_get_uuid(blkid_partition par)
{
	return par && *par->uuid ? par->uuid : NULL;
}

/**
 * blkid_partition_get_partno:
 * @par: partition
 *
 * Returns: proposed partitin number (e.g. 'N' from sda'N') or -1 in case of
 * error. Note that the number is generate by library independenly on your OS.
 */
int blkid_partition_get_partno(blkid_partition par)
{
	return par ? par->partno : -1;
}

/**
 * blkid_partition_get_start:
 * @par: partition
 *
 * Be careful if you _not_ probe whole disk:
 *
 * 1) the offset is usully relative to begin of the disk -- but if you probe a
 *    fragment of the disk only -- then the offset could be still relative to
 *    the begin of the disk rather that relative to the fragment.
 *
 * 2) the offset for nested partitions could be releative to parent (e.g. Solaris)
 *    _or_ relative to the begin of the whole disk (e.g. bsd).
 *
 * You don't have to care about such details if you proble whole disk. In such
 * a case libblkid always returns the offset relative to the begin of the disk.
 *
 * Returns: start of the partition (in 512-sectors).
 */
blkid_loff_t blkid_partition_get_start(blkid_partition par)
{
	return par ? par->start : -1;
}

/**
 * blkid_partition_get_size:
 * @par: partition
 *
 * WARNING: be very careful when you work with MS-DOS extended partitions. The
 *          library always returns full size of the partition. If you want add
 *          the partition to the Linux system (BLKPG_ADD_PARTITION ioctl) you
 *          need to reduce the size of the partition to 1 or 2 blocks. The
 *          rest of the partition has to be unaccessible for mkfs or mkswap
 *          programs, we need a small space for boot loaders only.
 *
 *          For some unknown reason this (safe) practice is not to used for
 *          nested BSD, Solaris, ..., partition tables in Linux kernel.
 *
 * Returns: size of the partition (in 512-sectors).
 */
blkid_loff_t blkid_partition_get_size(blkid_partition par)
{
	return par ? par->size : -1;
}

/**
 * blkid_partition_get_type:
 * @par: partition
 *
 * Returns: partition type.
 */
int blkid_partition_get_type(blkid_partition par)
{
	return par->type;
}

/* Sets partition 'type' for PT where the type is defined by string rather
 * than by number
 */
int blkid_partition_set_type_string(blkid_partition par,
		const unsigned char *type, size_t len)
{
	if (!par)
		return -1;

	set_string((unsigned char *) par->typestr,
			sizeof(par->typestr), type, len);
	return 0;
}

/* Sets partition 'type' for PT where the type is defined by UUIDrather
 * than by number
 */
int blkid_partition_set_type_uuid(blkid_partition par, const unsigned char *uuid)
{
	if (!par)
		return -1;

	blkid_unparse_uuid(uuid, par->typestr, sizeof(par->typestr));
	return 0;
}

/**
 * blkid_partition_get_type_string:
 * @par: partition
 *
 * The type string is supported by a small subset of partition tables (e.g Mac
 * and EFI GPT).  Note that GPT uses type UUID and this function returns this
 * UUID as string.
 *
 * Returns: partition type string or NULL.
 */
const char *blkid_partition_get_type_string(blkid_partition par)
{
	return par && *par->typestr ? par->typestr : NULL;
}


int blkid_partition_set_flags(blkid_partition par, unsigned long long flags)
{
	if (!par)
		return -1;
	par->flags = flags;
	return 0;
}

/**
 * blkid_partition_get_flags
 * @par: partition
 *
 * Returns: partition flags (or attributes for gpt).
 */
unsigned long long blkid_partition_get_flags(blkid_partition par)
{
	return par->flags;
}

