blob: 198e9edb91b0e19e915911586f2c236b0861b031 [file] [log] [blame]
#include "fdiskP.h"
#include "strutils.h"
#include "carefulputc.h"
/**
* SECTION: script
* @title: Script
* @short_description: text based sfdisk compatible description of partition table
*
* The libfdisk scripts are based on original sfdisk script (dumps). Each
* script has two parts: script headers and partition table entries
* (partitions).
*
* For more details about script format see sfdisk man page.
*/
/* script header (e.g. unit: sectors) */
struct fdisk_scriptheader {
struct list_head headers;
char *name;
char *data;
};
/* script control struct */
struct fdisk_script {
struct fdisk_table *table;
struct list_head headers;
struct fdisk_context *cxt;
int refcount;
char *(*fn_fgets)(struct fdisk_script *, char *, size_t, FILE *);
void *userdata;
/* parser's state */
size_t nlines;
struct fdisk_label *label;
unsigned int json : 1, /* JSON output */
force_label : 1; /* label: <name> specified */
};
static void fdisk_script_free_header(struct fdisk_scriptheader *fi)
{
if (!fi)
return;
DBG(SCRIPT, ul_debugobj(fi, "free header %s", fi->name));
free(fi->name);
free(fi->data);
list_del(&fi->headers);
free(fi);
}
/**
* fdisk_new_script:
* @cxt: context
*
* The script hold fdisk_table and additional information to read/write
* script to the file.
*
* Returns: newly allocated script struct.
*/
struct fdisk_script *fdisk_new_script(struct fdisk_context *cxt)
{
struct fdisk_script *dp = NULL;
dp = calloc(1, sizeof(*dp));
if (!dp)
return NULL;
DBG(SCRIPT, ul_debugobj(dp, "alloc"));
dp->refcount = 1;
dp->cxt = cxt;
fdisk_ref_context(cxt);
dp->table = fdisk_new_table();
if (!dp->table) {
fdisk_unref_script(dp);
return NULL;
}
INIT_LIST_HEAD(&dp->headers);
return dp;
}
/**
* fdisk_new_script_from_file:
* @cxt: context
* @filename: path to the script file
*
* Allocates a new script and reads script from @filename.
*
* Returns: new script instance or NULL in case of error (check errno for more details).
*/
struct fdisk_script *fdisk_new_script_from_file(struct fdisk_context *cxt,
const char *filename)
{
int rc;
FILE *f;
struct fdisk_script *dp, *res = NULL;
assert(cxt);
assert(filename);
DBG(SCRIPT, ul_debug("opening %s", filename));
f = fopen(filename, "r");
if (!f)
return NULL;
dp = fdisk_new_script(cxt);
if (!dp)
goto done;
rc = fdisk_script_read_file(dp, f);
if (rc) {
errno = -rc;
goto done;
}
res = dp;
done:
fclose(f);
if (!res)
fdisk_unref_script(dp);
else
errno = 0;
return res;
}
/**
* fdisk_ref_script:
* @dp: script pointer
*
* Increments reference counter.
*/
void fdisk_ref_script(struct fdisk_script *dp)
{
if (dp)
dp->refcount++;
}
static void fdisk_reset_script(struct fdisk_script *dp)
{
assert(dp);
DBG(SCRIPT, ul_debugobj(dp, "reset"));
fdisk_unref_table(dp->table);
dp->table = NULL;
while (!list_empty(&dp->headers)) {
struct fdisk_scriptheader *fi = list_entry(dp->headers.next,
struct fdisk_scriptheader, headers);
fdisk_script_free_header(fi);
}
INIT_LIST_HEAD(&dp->headers);
}
/**
* fdisk_unref_script:
* @dp: script pointer
*
* Decrements reference counter, on zero the @dp is automatically
* deallocated.
*/
void fdisk_unref_script(struct fdisk_script *dp)
{
if (!dp)
return;
dp->refcount--;
if (dp->refcount <= 0) {
fdisk_reset_script(dp);
fdisk_unref_context(dp->cxt);
DBG(SCRIPT, ul_debugobj(dp, "free script"));
free(dp);
}
}
/**
* fdisk_script_set_userdata
* @dp: script
* @data: your data
*
* Sets data usable for example in callbacks (e.g fdisk_script_set_fgets()).
*
* Returns: 0 on success, <0 on error.
*/
int fdisk_script_set_userdata(struct fdisk_script *dp, void *data)
{
assert(dp);
dp->userdata = data;
return 0;
}
/**
* fdisk_script_get_userdata
* @dp: script
*
* Returns: user data or NULL.
*/
void *fdisk_script_get_userdata(struct fdisk_script *dp)
{
assert(dp);
return dp->userdata;
}
static struct fdisk_scriptheader *script_get_header(struct fdisk_script *dp,
const char *name)
{
struct list_head *p;
list_for_each(p, &dp->headers) {
struct fdisk_scriptheader *fi = list_entry(p, struct fdisk_scriptheader, headers);
if (strcasecmp(fi->name, name) == 0)
return fi;
}
return NULL;
}
/**
* fdisk_script_get_header:
* @dp: script instance
* @name: header name
*
* Returns: pointer to header data or NULL.
*/
const char *fdisk_script_get_header(struct fdisk_script *dp, const char *name)
{
struct fdisk_scriptheader *fi;
assert(dp);
assert(name);
fi = script_get_header(dp, name);
return fi ? fi->data : NULL;
}
/**
* fdisk_script_set_header:
* @dp: script instance
* @name: header name
* @data: header data (or NULL)
*
* The headers are used as global options for whole partition
* table, always one header per line.
*
* If no @data is specified then the header is removed. If header does not exist
* and @data is specified then a new header is 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_script_set_header(struct fdisk_script *dp,
const char *name,
const char *data)
{
struct fdisk_scriptheader *fi;
if (!dp || !name)
return -EINVAL;
fi = script_get_header(dp, name);
if (!fi && !data)
return 0; /* want to remove header that does not exist, success */
if (!data) {
DBG(SCRIPT, ul_debugobj(dp, "freeing header %s", name));
/* no data, remove the header */
fdisk_script_free_header(fi);
return 0;
}
if (!fi) {
DBG(SCRIPT, ul_debugobj(dp, "setting new header %s='%s'", name, data));
/* 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_script_free_header(fi);
return -ENOMEM;
}
list_add_tail(&fi->headers, &dp->headers);
} else {
/* update existing */
char *x = strdup(data);
DBG(SCRIPT, ul_debugobj(dp, "update '%s' header '%s' -> '%s'", name, fi->data, data));
if (!x)
return -ENOMEM;
free(fi->data);
fi->data = x;
}
if (strcmp(name, "label") == 0)
dp->label = NULL;
return 0;
}
/**
* fdisk_script_get_table:
* @dp: script
*
* The table (container with partitions) is possible to create by
* fdisk_script_read_context() or fdisk_script_read_file(), otherwise
* this function returns NULL.
*
* Returns: NULL or script.
*/
struct fdisk_table *fdisk_script_get_table(struct fdisk_script *dp)
{
assert(dp);
return dp ? dp->table : NULL;
}
static struct fdisk_label *script_get_label(struct fdisk_script *dp)
{
assert(dp);
assert(dp->cxt);
if (!dp->label) {
dp->label = fdisk_get_label(dp->cxt,
fdisk_script_get_header(dp, "label"));
DBG(SCRIPT, ul_debugobj(dp, "label '%s'", dp->label ? dp->label->name : ""));
}
return dp->label;
}
/**
* fdisk_script_get_nlines:
* @dp: script
*
* Returns: number of parsed lines or <0 on error.
*/
int fdisk_script_get_nlines(struct fdisk_script *dp)
{
assert(dp);
return dp->nlines;
}
/**
* fdisk_script_has_force_label:
* @dp: script
*
* Label has been explicitly specified in the script.
*
* Since: 2.30
*
* Returns: true if "label: name" has been parsed.
*/
int fdisk_script_has_force_label(struct fdisk_script *dp)
{
assert(dp);
return dp->force_label;
}
/**
* fdisk_script_read_context:
* @dp: script
* @cxt: context
*
* Reads data from the @cxt context (on disk partition table) into the script.
* If the context is no specified than defaults to context used for fdisk_new_script().
*
* Return: 0 on success, <0 on error.
*/
int fdisk_script_read_context(struct fdisk_script *dp, struct fdisk_context *cxt)
{
struct fdisk_label *lb;
int rc;
char *p = NULL;
if (!dp || (!cxt && !dp->cxt))
return -EINVAL;
if (!cxt)
cxt = dp->cxt;
DBG(SCRIPT, ul_debugobj(dp, "reading context into script"));
fdisk_reset_script(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_script_set_header(dp, "label", fdisk_label_get_name(lb));
if (!rc && fdisk_get_disklabel_id(cxt, &p) == 0 && p) {
rc = fdisk_script_set_header(dp, "label-id", p);
free(p);
}
if (!rc && cxt->dev_path)
rc = fdisk_script_set_header(dp, "device", cxt->dev_path);
if (!rc)
rc = fdisk_script_set_header(dp, "unit", "sectors");
if (!rc && fdisk_is_label(cxt, GPT)) {
struct fdisk_labelitem item;
char buf[64];
/* first-lba */
rc = fdisk_get_disklabel_item(cxt, GPT_LABELITEM_FIRSTLBA, &item);
if (!rc) {
snprintf(buf, sizeof(buf), "%"PRIu64, item.data.num64);
rc = fdisk_script_set_header(dp, "first-lba", buf);
}
/* last-lba */
if (!rc)
rc = fdisk_get_disklabel_item(cxt, GPT_LABELITEM_LASTLBA, &item);
if (!rc) {
snprintf(buf, sizeof(buf), "%"PRIu64, item.data.num64);
rc = fdisk_script_set_header(dp, "last-lba", buf);
}
/* table-length */
if (!rc) {
size_t n = fdisk_get_npartitions(cxt);
if (n != FDISK_GPT_NPARTITIONS_DEFAULT) {
snprintf(buf, sizeof(buf), "%zu", n);
rc = fdisk_script_set_header(dp, "table-length", buf);
}
}
}
DBG(SCRIPT, ul_debugobj(dp, "read context done [rc=%d]", rc));
return rc;
}
/**
* fdisk_script_enable_json:
* @dp: script
* @json: 0 or 1
*
* Disable/Enable JSON output format.
*
* Returns: 0 on success, <0 on error.
*/
int fdisk_script_enable_json(struct fdisk_script *dp, int json)
{
assert(dp);
dp->json = json;
return 0;
}
static void fput_indent(int indent, FILE *f)
{
int i;
for (i = 0; i <= indent; i++)
fputs(" ", f);
}
static int write_file_json(struct fdisk_script *dp, FILE *f)
{
struct list_head *h;
struct fdisk_partition *pa;
struct fdisk_iter itr;
const char *devname = NULL;
int ct = 0, indent = 0;
assert(dp);
assert(f);
DBG(SCRIPT, ul_debugobj(dp, "writing json dump to file"));
fputs("{\n", f);
fput_indent(indent, f);
fputs("\"partitiontable\": {\n", f);
indent++;
/* script headers */
list_for_each(h, &dp->headers) {
struct fdisk_scriptheader *fi = list_entry(h, struct fdisk_scriptheader, headers);
const char *name = fi->name;
int num = 0;
if (strcmp(name, "first-lba") == 0) {
name = "firstlba";
num = 1;
} else if (strcmp(name, "last-lba") == 0) {
name = "lastlba";
num = 1;
} else if (strcmp(name, "label-id") == 0)
name = "id";
fput_indent(indent, f);
fputs_quoted_lower(name, f);
fputs(": ", f);
if (!num)
fputs_quoted(fi->data, f);
else
fputs(fi->data, f);
if (!dp->table && fi == list_last_entry(&dp->headers, struct fdisk_scriptheader, headers))
fputc('\n', f);
else
fputs(",\n", f);
if (strcmp(name, "device") == 0)
devname = fi->data;
}
if (!dp->table) {
DBG(SCRIPT, ul_debugobj(dp, "script table empty"));
goto done;
}
DBG(SCRIPT, ul_debugobj(dp, "%zu entries", fdisk_table_get_nents(dp->table)));
fput_indent(indent, f);
fputs("\"partitions\": [\n", f);
indent++;
fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
while (fdisk_table_next_partition(dp->table, &itr, &pa) == 0) {
char *p = NULL;
ct++;
fput_indent(indent, f);
fputc('{', f);
if (devname)
p = fdisk_partname(devname, pa->partno + 1);
if (p) {
DBG(SCRIPT, ul_debugobj(dp, "write %s entry", p));
fputs("\"node\": ", f);
fputs_quoted(p, f);
}
if (fdisk_partition_has_start(pa))
fprintf(f, ", \"start\": %ju", (uintmax_t)pa->start);
if (fdisk_partition_has_size(pa))
fprintf(f, ", \"size\": %ju", (uintmax_t)pa->size);
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) {
fputs(", \"name\": ", f),
fputs_quoted(pa->name, f);
}
/* for MBR attr=80 means bootable */
if (pa->attrs) {
struct fdisk_label *lb = script_get_label(dp);
if (!lb || fdisk_label_get_type(lb) != FDISK_DISKLABEL_DOS)
fprintf(f, ", \"attrs\": \"%s\"", pa->attrs);
}
if (fdisk_partition_is_bootable(pa))
fprintf(f, ", \"bootable\": true");
if ((size_t)ct < fdisk_table_get_nents(dp->table))
fputs("},\n", f);
else
fputs("}\n", f);
}
indent--;
fput_indent(indent, f);
fputs("]\n", f);
done:
indent--;
fput_indent(indent, f);
fputs("}\n}\n", f);
DBG(SCRIPT, ul_debugobj(dp, "write script done"));
return 0;
}
static int write_file_sfdisk(struct fdisk_script *dp, FILE *f)
{
struct list_head *h;
struct fdisk_partition *pa;
struct fdisk_iter itr;
const char *devname = NULL;
assert(dp);
assert(f);
DBG(SCRIPT, ul_debugobj(dp, "writing sfdisk-like script to file"));
/* script headers */
list_for_each(h, &dp->headers) {
struct fdisk_scriptheader *fi = list_entry(h, struct fdisk_scriptheader, headers);
fprintf(f, "%s: %s\n", fi->name, fi->data);
if (strcmp(fi->name, "device") == 0)
devname = fi->data;
}
if (!dp->table) {
DBG(SCRIPT, ul_debugobj(dp, "script table empty"));
return 0;
}
DBG(SCRIPT, ul_debugobj(dp, "%zu entries", fdisk_table_get_nents(dp->table)));
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) {
DBG(SCRIPT, ul_debugobj(dp, "write %s entry", p));
fprintf(f, "%s :", p);
} else
fprintf(f, "%zu :", pa->partno + 1);
if (fdisk_partition_has_start(pa))
fprintf(f, " start=%12ju", (uintmax_t)pa->start);
if (fdisk_partition_has_size(pa))
fprintf(f, ", size=%12ju", (uintmax_t)pa->size);
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);
/* for MBR attr=80 means bootable */
if (pa->attrs) {
struct fdisk_label *lb = script_get_label(dp);
if (!lb || fdisk_label_get_type(lb) != FDISK_DISKLABEL_DOS)
fprintf(f, ", attrs=\"%s\"", pa->attrs);
}
if (fdisk_partition_is_bootable(pa))
fprintf(f, ", bootable");
fputc('\n', f);
}
DBG(SCRIPT, ul_debugobj(dp, "write script done"));
return 0;
}
/**
* fdisk_script_write_file:
* @dp: script
* @f: output file
*
* Writes script @dp to the file @f.
*
* Returns: 0 on success, <0 on error.
*/
int fdisk_script_write_file(struct fdisk_script *dp, FILE *f)
{
assert(dp);
if (dp->json)
return write_file_json(dp, f);
return write_file_sfdisk(dp, f);
}
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_line_header(struct fdisk_script *dp, char *s)
{
int rc = -EINVAL;
char *name, *value;
DBG(SCRIPT, ul_debugobj(dp, " 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 (strcmp(name, "label") == 0) {
if (dp->cxt && !fdisk_get_label(dp->cxt, value))
goto done; /* unknown label name */
dp->force_label = 1;
} else if (strcmp(name, "unit") == 0) {
if (strcmp(value, "sectors") != 0)
goto done; /* only "sectors" supported */
} else if (strcmp(name, "label-id") == 0
|| strcmp(name, "device") == 0
|| strcmp(name, "first-lba") == 0
|| strcmp(name, "last-lba") == 0
|| strcmp(name, "table-length") == 0) {
; /* whatever is possible */
} else
goto done; /* unknown header */
if (*name && *value)
rc = fdisk_script_set_header(dp, name, value);
done:
if (rc)
DBG(SCRIPT, ul_debugobj(dp, "header parse error: "
"[rc=%d, name='%s', value='%s']",
rc, name, value));
return rc;
}
/* returns zero terminated string with next token and @str is updated */
static char *next_token(char **str)
{
char *tk_begin = NULL,
*tk_end = NULL,
*end = NULL,
*p;
int open_quote = 0, terminated = 0;
for (p = *str; p && *p; p++) {
if (!tk_begin) {
if (isblank(*p))
continue;
tk_begin = *p == '"' ? p + 1 : p;
}
if (*p == '"')
open_quote ^= 1;
if (open_quote)
continue;
if (isblank(*p) || *p == ',' || *p == ';' || *p == '"' )
tk_end = p;
else if (*(p + 1) == '\0')
tk_end = p + 1;
if (tk_begin && tk_end)
break;
}
if (!tk_end)
return NULL;
end = tk_end;
/* skip closing quotes */
if (*end == '"')
end++;
/* token is terminated by blank (or blank is before "," or ";") */
if (isblank(*end)) {
end = (char *) skip_blank(end);
terminated++;
}
/* token is terminated by "," or ";" */
if (*end == ',' || *end == ';') {
end++;
terminated++;
/* token is terminated by \0 */
} else if (!*end)
terminated++;
if (!terminated) {
DBG(SCRIPT, ul_debug("unterminated token '%s'", end));
return NULL;
}
/* skip extra space after terminator */
end = (char *) skip_blank(end);
*tk_end = '\0';
*str = end;
return tk_begin;
}
static int next_number(char **s, uint64_t *num, int *power)
{
char *tk;
int rc = -EINVAL;
assert(num);
assert(s);
tk = next_token(s);
if (tk)
rc = parse_size(tk, (uintmax_t *) num, power);
return rc;
}
static int next_string(char **s, char **str)
{
char *tk;
int rc = -EINVAL;
assert(s);
assert(str);
tk = next_token(s);
if (tk) {
*str = strdup(tk);
rc = !*str ? -ENOMEM : 0;
}
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;
}
/* dump format
* <device>: start=<num>, size=<num>, type=<string>, ...
*/
static int parse_line_nameval(struct fdisk_script *dp, char *s)
{
char *p, *x;
struct fdisk_partition *pa;
int rc = 0;
uint64_t num;
int pno;
assert(dp);
assert(s);
DBG(SCRIPT, ul_debugobj(dp, " parse script line: '%s'", s));
pa = fdisk_new_partition();
if (!pa)
return -ENOMEM;
fdisk_partition_start_follow_default(pa, 1);
fdisk_partition_end_follow_default(pa, 1);
fdisk_partition_partno_follow_default(pa, 1);
/* set partno */
p = strchr(s, ':');
x = strchr(s, '=');
if (p && (!x || p < x)) {
*p = '\0';
p++;
pno = partno_from_devname(s);
if (pno >= 0) {
fdisk_partition_partno_follow_default(pa, 0);
fdisk_partition_set_partno(pa, pno);
}
} else
p = s;
while (rc == 0 && p && *p) {
DBG(SCRIPT, ul_debugobj(dp, " parsing '%s'", p));
p = (char *) skip_blank(p);
if (!strncasecmp(p, "start=", 6)) {
int pow = 0;
p += 6;
rc = next_number(&p, &num, &pow);
if (!rc) {
if (pow) /* specified as <num><suffix> */
num /= dp->cxt->sector_size;
fdisk_partition_set_start(pa, num);
fdisk_partition_start_follow_default(pa, 0);
}
} else if (!strncasecmp(p, "size=", 5)) {
int pow = 0;
p += 5;
rc = next_number(&p, &num, &pow);
if (!rc) {
if (pow) /* specified as <num><suffix> */
num /= dp->cxt->sector_size;
else /* specified as number of sectors */
fdisk_partition_size_explicit(pa, 1);
fdisk_partition_set_size(pa, num);
fdisk_partition_end_follow_default(pa, 0);
}
} else if (!strncasecmp(p, "bootable", 8)) {
/* we use next_token() to skip possible extra space */
char *tk = next_token(&p);
if (tk && strcasecmp(tk, "bootable") == 0)
pa->boot = 1;
else
rc = -EINVAL;
} else if (!strncasecmp(p, "attrs=", 6)) {
p += 6;
rc = next_string(&p, &pa->attrs);
} else if (!strncasecmp(p, "uuid=", 5)) {
p += 5;
rc = next_string(&p, &pa->uuid);
} else if (!strncasecmp(p, "name=", 5)) {
p += 5;
rc = next_string(&p, &pa->name);
} else if (!strncasecmp(p, "type=", 5) ||
!strncasecmp(p, "Id=", 3)) { /* backward compatibility */
char *type;
p += ((*p == 'I' || *p == 'i') ? 3 : 5); /* "Id=", "type=" */
rc = next_string(&p, &type);
if (rc)
break;
pa->type = fdisk_label_parse_parttype(
script_get_label(dp), type);
free(type);
if (!pa->type) {
rc = -EINVAL;
fdisk_unref_parttype(pa->type);
pa->type = NULL;
break;
}
} else {
DBG(SCRIPT, ul_debugobj(dp, "script parse error: unknown field '%s'", p));
rc = -EINVAL;
break;
}
}
if (!rc)
rc = fdisk_table_add_partition(dp->table, pa);
if (rc)
DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc));
fdisk_unref_partition(pa);
return rc;
}
/* original sfdisk supports partition types shortcuts like 'L' = Linux native
*/
static struct fdisk_parttype *translate_type_shortcuts(struct fdisk_script *dp, char *str)
{
struct fdisk_label *lb;
const char *type = NULL;
if (strlen(str) != 1)
return NULL;
lb = script_get_label(dp);
if (!lb)
return NULL;
if (lb->id == FDISK_DISKLABEL_DOS) {
switch (*str) {
case 'L': /* Linux */
type = "83";
break;
case 'S': /* Swap */
type = "82";
break;
case 'E': /* Dos extended */
type = "05";
break;
case 'X': /* Linux extended */
type = "85";
break;
case 'U': /* UEFI system */
type = "EF";
break;
}
} else if (lb->id == FDISK_DISKLABEL_GPT) {
switch (*str) {
case 'L': /* Linux */
type = "0FC63DAF-8483-4772-8E79-3D69D8477DE4";
break;
case 'S': /* Swap */
type = "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F";
break;
case 'H': /* Home */
type = "933AC7E1-2EB4-4F13-B844-0E14E2AEF915";
break;
case 'U': /* UEFI system */
type = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B";
break;
}
}
return type ? fdisk_label_parse_parttype(lb, type) : NULL;
}
#define TK_PLUS 1
#define TK_MINUS -1
#define alone_sign(_sign, _p) (_sign && (*_p == '\0' || isblank(*_p)))
/* simple format:
* <start>, <size>, <type>, <bootable>, ...
*/
static int parse_line_valcommas(struct fdisk_script *dp, char *s)
{
int rc = 0;
char *p = s, *str;
struct fdisk_partition *pa;
enum { ITEM_START, ITEM_SIZE, ITEM_TYPE, ITEM_BOOTABLE };
int item = -1;
assert(dp);
assert(s);
pa = fdisk_new_partition();
if (!pa)
return -ENOMEM;
fdisk_partition_start_follow_default(pa, 1);
fdisk_partition_end_follow_default(pa, 1);
fdisk_partition_partno_follow_default(pa, 1);
while (rc == 0 && p && *p) {
uint64_t num;
char *begin;
int sign = 0;
p = (char *) skip_blank(p);
item++;
if (item != ITEM_BOOTABLE) {
sign = *p == '-' ? TK_MINUS : *p == '+' ? TK_PLUS : 0;
if (sign)
p++;
}
DBG(SCRIPT, ul_debugobj(dp, " parsing item %d ('%s')", item, p));
begin = p;
switch (item) {
case ITEM_START:
if (*p == ',' || *p == ';' || alone_sign(sign, p))
fdisk_partition_start_follow_default(pa, 1);
else {
int pow = 0;
rc = next_number(&p, &num, &pow);
if (!rc) {
if (pow) /* specified as <num><suffix> */
num /= dp->cxt->sector_size;
fdisk_partition_set_start(pa, num);
pa->movestart = sign == TK_MINUS ? FDISK_MOVE_DOWN :
sign == TK_PLUS ? FDISK_MOVE_UP :
FDISK_MOVE_NONE;
}
fdisk_partition_start_follow_default(pa, 0);
}
break;
case ITEM_SIZE:
if (*p == ',' || *p == ';' || alone_sign(sign, p)) {
fdisk_partition_end_follow_default(pa, 1);
if (sign == TK_PLUS)
/* '+' alone means use all possible space, '-' alone means nothing */
pa->resize = FDISK_RESIZE_ENLARGE;
} else {
int pow = 0;
rc = next_number(&p, &num, &pow);
if (!rc) {
if (pow) /* specified as <size><suffix> */
num /= dp->cxt->sector_size;
else /* specified as number of sectors */
fdisk_partition_size_explicit(pa, 1);
fdisk_partition_set_size(pa, num);
pa->resize = sign == TK_MINUS ? FDISK_RESIZE_REDUCE :
sign == TK_PLUS ? FDISK_RESIZE_ENLARGE :
FDISK_RESIZE_NONE;
}
fdisk_partition_end_follow_default(pa, 0);
}
break;
case ITEM_TYPE:
if (*p == ',' || *p == ';' || alone_sign(sign, p))
break; /* use default type */
rc = next_string(&p, &str);
if (rc)
break;
pa->type = translate_type_shortcuts(dp, str);
if (!pa->type)
pa->type = fdisk_label_parse_parttype(
script_get_label(dp), str);
free(str);
if (!pa->type) {
rc = -EINVAL;
fdisk_unref_parttype(pa->type);
pa->type = NULL;
break;
}
break;
case ITEM_BOOTABLE:
if (*p == ',' || *p == ';')
break;
else {
char *tk = next_token(&p);
if (tk && *tk == '*' && *(tk + 1) == '\0')
pa->boot = 1;
else if (tk && *tk == '-' && *(tk + 1) == '\0')
pa->boot = 0;
else if (tk && *tk == '+' && *(tk + 1) == '\0')
pa->boot = 1;
else
rc = -EINVAL;
}
break;
default:
break;
}
if (begin == p)
p++;
}
if (!rc)
rc = fdisk_table_add_partition(dp->table, pa);
if (rc)
DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc));
fdisk_unref_partition(pa);
return rc;
}
/* modifies @s ! */
static int fdisk_script_read_buffer(struct fdisk_script *dp, char *s)
{
int rc = 0;
assert(dp);
assert(s);
DBG(SCRIPT, ul_debugobj(dp, " 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_line_header(dp, s);
/* parse script format */
else if (strchr(s, '='))
rc = parse_line_nameval(dp, s);
/* parse simple <value>, ... format */
else
rc = parse_line_valcommas(dp, s);
if (rc)
DBG(SCRIPT, ul_debugobj(dp, "%zu: parse error [rc=%d]",
dp->nlines, rc));
return rc;
}
/**
* fdisk_script_set_fgets:
* @dp: script
* @fn_fgets: callback function
*
* The library uses fgets() function to read the next line from the script.
* This default maybe overridden by another function. Note that the function has
* to return the line terminated by \n (for example readline(3) removes \n).
*
* Return: 0 on success, <0 on error
*/
int fdisk_script_set_fgets(struct fdisk_script *dp,
char *(*fn_fgets)(struct fdisk_script *, char *, size_t, FILE *))
{
assert(dp);
dp->fn_fgets = fn_fgets;
return 0;
}
/**
* fdisk_script_read_line:
* @dp: script
* @f: file
* @buf: buffer to store one line of the file
* @bufsz: buffer size
*
* Reads next line into dump.
*
* Returns: 0 on success, <0 on error, 1 when nothing to read.
*/
int fdisk_script_read_line(struct fdisk_script *dp, FILE *f, char *buf, size_t bufsz)
{
char *s;
assert(dp);
assert(f);
DBG(SCRIPT, ul_debugobj(dp, " parsing line %zu", dp->nlines));
/* read the next non-blank non-comment line */
do {
if (dp->fn_fgets) {
if (dp->fn_fgets(dp, buf, bufsz, f) == NULL)
return 1;
} else if (fgets(buf, bufsz, f) == NULL)
return 1;
dp->nlines++;
s = strchr(buf, '\n');
if (!s) {
/* Missing final newline? Otherwise an extremely */
/* long line - assume file was corrupted */
if (feof(f)) {
DBG(SCRIPT, ul_debugobj(dp, "no final newline"));
s = strchr(buf, '\0');
} else {
DBG(SCRIPT, 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_script_read_buffer(dp, s);
}
/**
* fdisk_script_read_file:
* @dp: script
* @f: input file
*
* Reads file @f into script @dp.
*
* Returns: 0 on success, <0 on error.
*/
int fdisk_script_read_file(struct fdisk_script *dp, FILE *f)
{
char buf[BUFSIZ];
int rc = 1;
assert(dp);
assert(f);
DBG(SCRIPT, ul_debugobj(dp, "parsing file"));
while (!feof(f)) {
rc = fdisk_script_read_line(dp, f, buf, sizeof(buf));
if (rc)
break;
}
if (rc == 1)
rc = 0; /* end of file */
DBG(SCRIPT, ul_debugobj(dp, "parsing file done [rc=%d]", rc));
return rc;
}
/**
* fdisk_set_script:
* @cxt: context
* @dp: script (or NULL to remove previous reference)
*
* Sets reference to the @dp script and remove reference to the previously used
* script.
*
* The script headers might be used by label drivers to overwrite
* built-in defaults (for example disk label Id) and label driver might
* optimize the default semantic to be more usable for scripts (for example to
* not ask for primary/logical/extended partition type).
*
* Note that script also contains reference to the fdisk context (see
* fdisk_new_script()). This context may be completely independent on
* context used for fdisk_set_script().
*
* Returns: <0 on error, 0 on success.
*/
int fdisk_set_script(struct fdisk_context *cxt, struct fdisk_script *dp)
{
assert(cxt);
/* unref old */
if (cxt->script)
fdisk_unref_script(cxt->script);
/* ref new */
cxt->script = dp;
if (cxt->script) {
DBG(CXT, ul_debugobj(cxt, "setting reference to script %p", cxt->script));
fdisk_ref_script(cxt->script);
}
return 0;
}
/**
* fdisk_get_script:
* @cxt: context
*
* Returns: the current script or NULL.
*/
struct fdisk_script *fdisk_get_script(struct fdisk_context *cxt)
{
assert(cxt);
return cxt->script;
}
/**
* fdisk_apply_script_headers:
* @cxt: context
* @dp: script
*
* Associate context @cxt with script @dp and creates a new empty disklabel.
*
* Returns: 0 on success, <0 on error.
*/
int fdisk_apply_script_headers(struct fdisk_context *cxt, struct fdisk_script *dp)
{
const char *name;
const char *str;
int rc;
assert(cxt);
assert(dp);
DBG(SCRIPT, ul_debugobj(dp, "applying script headers"));
fdisk_set_script(cxt, dp);
/* create empty label */
name = fdisk_script_get_header(dp, "label");
if (!name)
return -EINVAL;
rc = fdisk_create_disklabel(cxt, name);
if (rc)
return rc;
str = fdisk_script_get_header(dp, "table-length");
if (str) {
uintmax_t sz;
rc = parse_size(str, &sz, NULL);
if (rc == 0)
rc = fdisk_gpt_set_npartitions(cxt, sz);
}
return rc;
}
/**
* fdisk_apply_script:
* @cxt: context
* @dp: script
*
* This function creates a new disklabel and partition within context @cxt. You
* have to call fdisk_write_disklabel() to apply changes to the device.
*
* Returns: 0 on error, <0 on error.
*/
int fdisk_apply_script(struct fdisk_context *cxt, struct fdisk_script *dp)
{
int rc;
struct fdisk_script *old;
assert(dp);
assert(cxt);
DBG(CXT, ul_debugobj(cxt, "applying script %p", dp));
old = fdisk_get_script(cxt);
fdisk_ref_script(old);
/* create empty disk label */
rc = fdisk_apply_script_headers(cxt, dp);
/* create partitions */
if (!rc && dp->table)
rc = fdisk_apply_table(cxt, dp->table);
fdisk_set_script(cxt, old);
fdisk_unref_script(old);
DBG(CXT, ul_debugobj(cxt, "script done [rc=%d]", rc));
return rc;
}
#ifdef TEST_PROGRAM
static int test_dump(struct fdisk_test *ts, int argc, char *argv[])
{
char *devname = argv[1];
struct fdisk_context *cxt;
struct fdisk_script *dp;
cxt = fdisk_new_context();
fdisk_assign_device(cxt, devname, 1);
dp = fdisk_new_script(cxt);
fdisk_script_read_context(dp, NULL);
fdisk_script_write_file(dp, stdout);
fdisk_unref_script(dp);
fdisk_unref_context(cxt);
return 0;
}
static int test_read(struct fdisk_test *ts, int argc, char *argv[])
{
char *filename = argv[1];
struct fdisk_script *dp;
struct fdisk_context *cxt;
FILE *f;
if (!(f = fopen(filename, "r")))
err(EXIT_FAILURE, "%s: cannot open", filename);
cxt = fdisk_new_context();
dp = fdisk_new_script(cxt);
fdisk_script_read_file(dp, f);
fclose(f);
fdisk_script_write_file(dp, stdout);
fdisk_unref_script(dp);
fdisk_unref_context(cxt);
return 0;
}
static int test_stdin(struct fdisk_test *ts, int argc, char *argv[])
{
char buf[BUFSIZ];
struct fdisk_script *dp;
struct fdisk_context *cxt;
int rc = 0;
cxt = fdisk_new_context();
dp = fdisk_new_script(cxt);
fdisk_script_set_header(dp, "label", "dos");
printf("<start>, <size>, <type>, <bootable: *|->\n");
do {
struct fdisk_partition *pa;
size_t n = fdisk_table_get_nents(dp->table);
printf(" #%zu :\n", n + 1);
rc = fdisk_script_read_line(dp, stdin, buf, sizeof(buf));
if (rc == 0) {
pa = fdisk_table_get_partition(dp->table, n);
printf(" #%zu %12ju %12ju\n", n + 1,
(uintmax_t)fdisk_partition_get_start(pa),
(uintmax_t)fdisk_partition_get_size(pa));
}
} while (rc == 0);
if (!rc)
fdisk_script_write_file(dp, stdout);
fdisk_unref_script(dp);
fdisk_unref_context(cxt);
return rc;
}
static int test_apply(struct fdisk_test *ts, int argc, char *argv[])
{
char *devname = argv[1], *scriptname = argv[2];
struct fdisk_context *cxt;
struct fdisk_script *dp = NULL;
struct fdisk_table *tb = NULL;
struct fdisk_iter *itr = NULL;
struct fdisk_partition *pa = NULL;
int rc;
cxt = fdisk_new_context();
fdisk_assign_device(cxt, devname, 0);
dp = fdisk_new_script_from_file(cxt, scriptname);
if (!dp)
return -errno;
rc = fdisk_apply_script(cxt, dp);
if (rc)
goto done;
fdisk_unref_script(dp);
/* list result */
fdisk_list_disklabel(cxt);
fdisk_get_partitions(cxt, &tb);
itr = fdisk_new_iter(FDISK_ITER_FORWARD);
while (fdisk_table_next_partition(tb, itr, &pa) == 0) {
printf(" #%zu %12ju %12ju\n", fdisk_partition_get_partno(pa),
(uintmax_t)fdisk_partition_get_start(pa),
(uintmax_t)fdisk_partition_get_size(pa));
}
done:
fdisk_free_iter(itr);
fdisk_unref_table(tb);
/*fdisk_write_disklabel(cxt);*/
fdisk_unref_context(cxt);
return 0;
}
static int test_tokens(struct fdisk_test *ts, int argc, char *argv[])
{
char *p, *str = argc == 2 ? strdup(argv[1]) : NULL;
int i;
for (i = 1, p = str; p && *p; i++) {
char *tk = next_token(&p);
if (!tk)
break;
printf("#%d: '%s'\n", i, tk);
}
free(str);
return 0;
}
int main(int argc, char *argv[])
{
struct fdisk_test tss[] = {
{ "--dump", test_dump, "<device> dump PT as script" },
{ "--read", test_read, "<file> read PT script from file" },
{ "--apply", test_apply, "<device> <file> try apply script from file to device" },
{ "--stdin", test_stdin, " read input like sfdisk" },
{ "--tokens", test_tokens, "<string> parse string" },
{ NULL }
};
return fdisk_run_test(tss, argc, argv);
}
#endif