summaryrefslogblamecommitdiffstats
path: root/libfdisk/src/dump.c
blob: e1fb0031d9b9bc390f0e1556a77bcc70998a1124 (plain) (tree)
1
2
3

                   
                     















                                         

                                                                  


































































































                                                                                              
 






















































































                                                                                         
                                                                               










































                                                                             








                                     





























                                                                                              
                                               




























                                                                                     










































































































































































































































































                                                                                       





















                                                              



















                                                               



                                                           
                                                                          






                                               

#include "fdiskP.h"
#include "strutils.h"

/* dump header (e.g. unit: sectors) */
struct fdisk_dumpheader {
	struct list_head	headers;
	char			*name;
	char			*data;
};

/* dump control struct */
struct fdisk_dump {
	struct fdisk_table	*table;
	struct list_head	headers;

	int			refcount;

	/* parser's state */
	size_t			nlines;
	int			fmt;		/* input format */
};


static void fdisk_dump_free_header(struct fdisk_dump *dp, struct fdisk_dumpheader *fi)
{
	if (!fi)
		return;

	DBG(DUMP, ul_debugobj(fi, "free"));
	free(fi->name);
	free(fi->data);
	list_del(&fi->headers);
	free(fi);
}

/**
 * fdisk_new_dump:
 *
 * The dump hold fdisk_table and additional information to read/write
 * dump to the file.
 *
 * Returns: newly allocated dump struct.
 */
struct fdisk_dump *fdisk_new_dump(void)
{
	struct fdisk_dump *dp = NULL;

	dp = calloc(1, sizeof(*dp));
	if (!dp)
		return NULL;

	DBG(DUMP, ul_debugobj(dp, "alloc"));
	dp->refcount = 1;
	INIT_LIST_HEAD(&dp->headers);
	return dp;
}

/**
 * fdisk_ref_dump:
 * @dp: dump pointer
 *
 * Incremparts reference counter.
 */
void fdisk_ref_dump(struct fdisk_dump *dp)
{
	if (dp)
		dp->refcount++;
}

static void fdisk_reset_dump(struct fdisk_dump *dp)
{
	assert(dp);

	fdisk_unref_table(dp->table);
	dp->table = NULL;

	while (!list_empty(&dp->headers)) {
		struct fdisk_dumpheader *fi = list_entry(dp->headers.next,
						  struct fdisk_dumpheader, headers);
		fdisk_dump_free_header(dp, fi);
	}
	INIT_LIST_HEAD(&dp->headers);
}

/**
 * fdisk_unref_dump:
 * @dp: dump pointer
 *
 * De-incremparts reference counter, on zero the @dp is automatically
 * deallocated.
 */
void fdisk_unref_dump(struct fdisk_dump *dp)
{
	if (!dp)
		return;

	dp->refcount--;
	if (dp->refcount <= 0) {
		fdisk_reset_dump(dp);
		DBG(DUMP, ul_debugobj(dp, "free"));
		free(dp);
	}
}

static struct fdisk_dumpheader *fdisk_dump_get_header(struct fdisk_dump *dp,
						     const char *name)
{
	struct list_head *p;

	list_for_each(p, &dp->headers) {
		struct fdisk_dumpheader *fi = list_entry(p, struct fdisk_dumpheader, headers);

		if (strcasecmp(fi->name, name) == 0)
			return fi;
	}

	return NULL;
}


/**
 * fdisk_dump_set_header:
 * @dp: dump instance
 * @name: header name
 * @data: header data (or NULL)
 *
 * The headers are used as global options (in dump) for whole partition table, always one
 * header per line.
 *
 * If no @data specified then the header is removed. If header does not exist
 * and @data speified then a new header added.
 *
 * Note that libfdisk allows to specify arbitrary custom header, the default
 * build-in headers are "unit" and "label", and some label specific headers
 * (for example "uuid" and "name" for GPT).
 *
 * Returns: 0 on success, <0 on error
 */
int fdisk_dump_set_header(struct fdisk_dump *dp,
			    const char *name,
			    const char *data)
{
	struct fdisk_dumpheader *fi;

	assert(dp);
	assert(name);

	if (!dp || !name)
		return -EINVAL;

	fi = fdisk_dump_get_header(dp, name);
	if (!fi && !data)
		return 0;	/* want to remove header that does not exist, success */

	if (!data) {
		/* no data, remove the header */
		fdisk_dump_free_header(dp, fi);
		return 0;
	}

	if (!fi) {
		/* new header */
		fi = calloc(1, sizeof(*fi));
		if (!fi)
			return -ENOMEM;
		INIT_LIST_HEAD(&fi->headers);
		fi->name = strdup(name);
		fi->data = strdup(data);
		if (!fi->data || !fi->name) {
			fdisk_dump_free_header(dp, fi);
			return -ENOMEM;
		}
		list_add_tail(&fi->headers, &dp->headers);
	} else {
		/* update existing */
		char *x = strdup(data);

		if (!x)
			return -ENOMEM;
		free(fi->data);
		fi->data = x;
	}

	return 0;
}

/**
 * fdisk_dump_get_table:
 * @dp: dump
 *
 * The table (container with partitions) is possible to create by
 * fdisk_dump_read_context() or fdisk_dump_read_file(), otherwise
 * this function returns NULL.
 *
 * Returns: NULL or dump.
 */
struct fdisk_table *fdisk_dump_get_table(struct fdisk_dump *dp)
{
	assert(dp);
	return dp ? dp->table : NULL;
}

/**
 * fdisk_dump_read_context:
 * @dp: dump
 * @cxt: context
 *
 * Reads data from the current context (on disk partition table) into the dump.
 *
 * Return: 0 on success, <0 on error.
 */
int fdisk_dump_read_context(struct fdisk_dump *dp, struct fdisk_context *cxt)
{
	struct fdisk_label *lb;
	int rc;
	char *p = NULL;

	assert(dp);
	assert(cxt);

	if (!dp || !cxt)
		return -EINVAL;

	fdisk_reset_dump(dp);

	lb = fdisk_get_label(cxt, NULL);
	if (!lb)
		return -EINVAL;

	/* allocate and fill new table */
	rc = fdisk_get_partitions(cxt, &dp->table);
	if (rc)
		return rc;

	/* generate headers */
	rc = fdisk_dump_set_header(dp, "label", fdisk_label_get_name(lb));

	if (!rc && fdisk_get_disklabel_id(cxt, &p) == 0 && p) {
		rc = fdisk_dump_set_header(dp, "label-id", p);
		free(p);
	}
	if (!rc && cxt->dev_path)
		rc = fdisk_dump_set_header(dp, "device", cxt->dev_path);
	if (!rc)
		rc = fdisk_dump_set_header(dp, "unit", "sectors");

	/* TODO: label specific headers (e.g. uuid for GPT) */

	return rc;
}

/**
 * fdisk_dump_write_file:
 * @dp: dump
 * @f: output file
 *
 * Writes dump @dp to the ile @f.
 *
 * Returns: 0 on sucess, <0 on error.
 */
int fdisk_dump_write_file(struct fdisk_dump *dp, FILE *f)
{
	struct list_head *h;
	struct fdisk_partition *pa;
	struct fdisk_iter itr;
	const char *devname = NULL;

	assert(dp);
	assert(f);

	/* dump headers */
	list_for_each(h, &dp->headers) {
		struct fdisk_dumpheader *fi = list_entry(h, struct fdisk_dumpheader, headers);
		fprintf(f, "%s: %s\n", fi->name, fi->data);
		if (strcmp(fi->name, "device") == 0)
			devname = fi->data;
	}

	if (!dp->table)
		return 0;

	fputc('\n', f);

	fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
	while (fdisk_table_next_partition(dp->table, &itr, &pa) == 0) {
		char *p = NULL;

		if (devname)
			p = fdisk_partname(devname, pa->partno + 1);
		if (p)
			fprintf(f, "%s : ", p);
		else
			fprintf(f, "%zu :", pa->partno + 1);

		if (pa->start)
			fprintf(f, " start=%12ju", pa->start);
		if (pa->size)
			fprintf(f, ", size=%12ju", pa->size);
		else if (pa->end)
			fprintf(f, ", end=%12ju", pa->end);

		if (pa->type && fdisk_parttype_get_string(pa->type))
			fprintf(f, ", type=%s", fdisk_parttype_get_string(pa->type));
		else if (pa->type)
			fprintf(f, ", type=%x", fdisk_parttype_get_code(pa->type));

		if (pa->uuid)
			fprintf(f, ", uuid=%s", pa->uuid);
		if (pa->name && *pa->name)
			fprintf(f, ", name=\"%s\"", pa->name);
		if (pa->attrs)
			fprintf(f, ", attrs=%s", pa->attrs);
		if (pa->boot)
			fprintf(f, ", bootable");
		fputc('\n', f);
	}

	return 0;
}

static inline int is_header_line(const char *s)
{
	const char *p = strchr(s, ':');

	if (!p || p == s || !*(p + 1) || strchr(s, '='))
		return 0;

	return 1;
}

/* parses "<name>: value", note modifies @s*/
static int parse_header_line(struct fdisk_dump *dp, char *s)
{
	int rc = -EINVAL;
	char *name, *value;

	DBG(DUMP, ul_debug("   parse header '%s'", s));

	if (!s || !*s)
		return -EINVAL;

	name = s;
	value = strchr(s, ':');
	if (!value)
		goto done;
	*value = '\0';
	value++;

	ltrim_whitespace((unsigned char *) name);
	rtrim_whitespace((unsigned char *) name);
	ltrim_whitespace((unsigned char *) value);
	rtrim_whitespace((unsigned char *) value);

	if (*name && *value)
		rc = fdisk_dump_set_header(dp, name, value);
done:
	if (rc)
		DBG(DUMP, ul_debug("header parse error: [rc=%d]", rc));
	return rc;

}

static int next_number(char **s, uint64_t *num)
{
	char *end = NULL;
	int rc;

	assert(num);
	assert(s);

	*s = (char *) skip_blank(*s);
	if (!**s)
		return -1;

	end = strchr(*s, ',');
	if (end)
		*end = '\0';

	rc = strtosize(*s, (uintmax_t *) num);
	if (end) {
		*end = ',';
		*s = end;
	} else
		while (**s) (*s)++;

	return rc;
}

static int partno_from_devname(char *s)
{
	int pno;
	size_t sz;
	char *end, *p;

	sz = rtrim_whitespace((unsigned char *)s);
	p = s + sz - 1;

	while (p > s && isdigit(*(p - 1)))
		p--;

	errno = 0;
	pno = strtol(p, &end, 10);
	if (errno || !end || p == end)
		return -1;
	return pno - 1;
}

static int parse_dump_line(struct fdisk_dump *dp, char *s)
{
	char *p;
	struct fdisk_partition *pa;
	int rc = 0;
	uint64_t num;
	int pno;

	assert(dp);
	assert(s);

	DBG(DUMP, ul_debug("   parse dump line: '%s'", s));

	pa = fdisk_new_partition();
	if (!pa)
		return -ENOMEM;

	p = strchr(s, ':');
	if (!p)
		return -EINVAL;
	*p = '\0';
	p++;

	pno = partno_from_devname(s);
	if (pno < 0)
		fdisk_partition_partno_follow_default(pa, 1);
	else
		fdisk_partition_set_partno(pa, pno);


	while (rc == 0 && p && *p) {
		while (isblank(*p)) p++;
		if (!*p)
			break;

		if (!strncasecmp(p, "start=", 6)) {
			p += 6;
			rc = next_number(&p, &num);
			if (!rc)
				fdisk_partition_set_start(pa, num);

		} else if (!strncasecmp(p, "size=", 5)) {
			p += 5;
			rc = next_number(&p, &num);
			if (!rc)
				fdisk_partition_set_size(pa, num);

		} else if (!strncasecmp(p, "end=", 4)) {
			p += 4;
			rc = next_number(&p, &num);
			if (!rc)
				fdisk_partition_set_end(pa, num);
		} else {
			DBG(DUMP, ul_debug("dump parse error: unknown field '%s'", p));
			rc = -EINVAL;
			break;
		}

		while (isblank(*p)) p++;
		if (*p == ',')
			p++;
	}

	if (!rc)
		rc = fdisk_table_add_partition(dp->table, pa);
	if (rc)
		DBG(DUMP, ul_debug("dump parse error: [rc=%d]", rc));

	fdisk_unref_partition(pa);
	return 0;
}

static int parse_commas_line(struct fdisk_dump *dp, const char *s)
{
	DBG(DUMP, ul_debug("   commas line parse error"));
	return -EINVAL;
}

/* modifies @s ! */
int fdisk_dump_read_buffer(struct fdisk_dump *dp, char *s)
{
	int rc = 0;

	assert(dp);
	assert(s);

	DBG(DUMP, ul_debug("  parsing buffer"));

	s = (char *) skip_blank(s);
	if (!s || !*s)
		return 0;	/* nothing baby, ignore */

	if (!dp->table) {
		dp->table = fdisk_new_table();
		if (!dp->table)
			return -ENOMEM;
	}

	/* parse header lines only if no partition specified yet */
	if (fdisk_table_is_empty(dp->table) && is_header_line(s))
		rc = parse_header_line(dp, s);

	/* parse dump format */
	else if (strchr(s, '='))
		rc = parse_dump_line(dp, s);

	/* parse simple <value>, ... format */
	else
		rc = parse_commas_line(dp, s);

	if (rc)
		DBG(DUMP, ul_debugobj(dp, "%zu: parse error [rc=%d]",
				dp->nlines, rc));
	return rc;
}

char fdisk_dump_read_line(struct fdisk_dump *dp, FILE *f)
{
	char buf[BUFSIZ];
	char *s;

	assert(dp);
	assert(f);

	DBG(DUMP, ul_debug(" parsing line"));

	/* read the next non-blank non-comment line */
	do {
		if (fgets(buf, sizeof(buf), f) == NULL)
			return -EINVAL;
		dp->nlines++;
		s = strchr(buf, '\n');
		if (!s) {
			/* Missing final newline?  Otherwise an extremely */
			/* long line - assume file was corrupted */
			if (feof(f)) {
				DBG(DUMP, ul_debugobj(dp, "no final newline"));
				s = strchr(buf, '\0');
			} else {
				DBG(DUMP, ul_debugobj(dp,
					"%zu: missing newline at line", dp->nlines));
				return -EINVAL;
			}
		}

		*s = '\0';
		if (--s >= buf && *s == '\r')
			*s = '\0';
		s = (char *) skip_blank(buf);
	} while (*s == '\0' || *s == '#');

	return fdisk_dump_read_buffer(dp, s);
}


/**
 * fdisk_dump_read_file:
 * @dp: dump
 * @f input file
 *
 * Reads file @f into dump @dp.
 *
 * Returns: 0 on success, <0 on error.
 */
int fdisk_dump_read_file(struct fdisk_dump *dp, FILE *f)
{
	int rc = NULL;

	assert(dp);
	assert(f);

	DBG(DUMP, ul_debug("parsing file"));

	while (rc == 0 && !feof(f))
		rc = fdisk_dump_read_line(dp, f);

	return rc;
}


#ifdef TEST_PROGRAM
int test_dump(struct fdisk_test *ts, int argc, char *argv[])
{
	char *devname = argv[1];
	struct fdisk_context *cxt;
	struct fdisk_dump *dp;

	cxt = fdisk_new_context();
	fdisk_assign_device(cxt, devname, 1);

	dp = fdisk_new_dump();
	fdisk_dump_read_context(dp, cxt);
	fdisk_dump_set_header(dp, "custom-header-foo", "bar");

	fdisk_free_context(cxt);

	fdisk_dump_write_file(dp, stdout);
	fdisk_unref_dump(dp);

	return 0;
}

int test_read(struct fdisk_test *ts, int argc, char *argv[])
{
	char *filename = argv[1];
	struct fdisk_dump *dp;
	FILE *f;

	if (!(f = fopen(filename, "r")))
		err(EXIT_FAILURE, "%s: cannot open", filename);

	dp = fdisk_new_dump();

	fdisk_dump_read_file(dp, f);
	fclose(f);

	fdisk_dump_write_file(dp, stdout);
	fdisk_unref_dump(dp);

	return 0;
}

int main(int argc, char *argv[])
{
	struct fdisk_test tss[] = {
	{ "--dump",  test_dump,    "<device>   print PT" },
	{ "--read",  test_read,    "<file>     read PT scrit from file" },
	{ NULL }
	};

	return fdisk_run_test(tss, argc, argv);
}

#endif