#include "strutils.h"
#include "fdiskP.h"
static void fdisk_ask_menu_reset_items(struct fdisk_ask *ask);
struct fdisk_ask *fdisk_new_ask(void)
{
struct fdisk_ask *ask = calloc(1, sizeof(struct fdisk_ask));
DBG(ASK, ul_debugobj(ask, "alloc"));
return ask;
}
void fdisk_reset_ask(struct fdisk_ask *ask)
{
assert(ask);
free(ask->query);
DBG(ASK, ul_debugobj(ask, "reset"));
if (fdisk_is_ask(ask, MENU))
fdisk_ask_menu_reset_items(ask);
memset(ask, 0, sizeof(*ask));
}
void fdisk_free_ask(struct fdisk_ask *ask)
{
if (!ask)
return;
fdisk_reset_ask(ask);
DBG(ASK, ul_debugobj(ask, "free"));
free(ask);
}
const char *fdisk_ask_get_query(struct fdisk_ask *ask)
{
assert(ask);
return ask->query;
}
int fdisk_ask_set_query(struct fdisk_ask *ask, const char *str)
{
assert(ask);
return !strdup_to_struct_member(ask, query, str) ? -ENOMEM : 0;
}
int fdisk_ask_get_type(struct fdisk_ask *ask)
{
assert(ask);
return ask->type;
}
int fdisk_ask_set_type(struct fdisk_ask *ask, int type)
{
assert(ask);
ask->type = type;
return 0;
}
unsigned int fdisk_ask_get_flags(struct fdisk_ask *ask)
{
assert(ask);
return ask->flags;
}
int fdisk_ask_set_flags(struct fdisk_ask *ask, unsigned int flags)
{
assert(ask);
ask->flags = flags;
return 0;
}
int fdisk_do_ask(struct fdisk_context *cxt, struct fdisk_ask *ask)
{
int rc;
assert(ask);
assert(cxt);
DBG(ASK, ul_debugobj(ask, "do_ask for '%s'",
ask->query ? ask->query :
ask->type == FDISK_ASKTYPE_INFO ? "info" :
ask->type == FDISK_ASKTYPE_WARNX ? "warnx" :
ask->type == FDISK_ASKTYPE_WARN ? "warn" :
"?nothing?"));
if (!cxt->ask_cb) {
DBG(ASK, ul_debugobj(ask, "no ask callback specified!"));
return -EINVAL;
}
rc = cxt->ask_cb(cxt, ask, cxt->ask_data);
DBG(ASK, ul_debugobj(ask, "do_ask done [rc=%d]", rc));
return rc;
}
#define is_number_ask(a) (fdisk_is_ask(a, NUMBER) || fdisk_is_ask(a, OFFSET))
const char *fdisk_ask_number_get_range(struct fdisk_ask *ask)
{
assert(ask);
assert(is_number_ask(ask));
return ask->data.num.range;
}
int fdisk_ask_number_set_range(struct fdisk_ask *ask, const char *range)
{
assert(ask);
assert(is_number_ask(ask));
ask->data.num.range = range;
return 0;
}
uint64_t fdisk_ask_number_get_default(struct fdisk_ask *ask)
{
assert(ask);
assert(is_number_ask(ask));
return ask->data.num.dfl;
}
int fdisk_ask_number_set_default(struct fdisk_ask *ask, uint64_t dflt)
{
assert(ask);
ask->data.num.dfl = dflt;
return 0;
}
uint64_t fdisk_ask_number_get_low(struct fdisk_ask *ask)
{
assert(ask);
assert(is_number_ask(ask));
return ask->data.num.low;
}
int fdisk_ask_number_set_low(struct fdisk_ask *ask, uint64_t low)
{
assert(ask);
ask->data.num.low = low;
return 0;
}
uint64_t fdisk_ask_number_get_high(struct fdisk_ask *ask)
{
assert(ask);
assert(is_number_ask(ask));
return ask->data.num.hig;
}
int fdisk_ask_number_set_high(struct fdisk_ask *ask, uint64_t high)
{
assert(ask);
ask->data.num.hig = high;
return 0;
}
uint64_t fdisk_ask_number_get_result(struct fdisk_ask *ask)
{
assert(ask);
assert(is_number_ask(ask));
return ask->data.num.result;
}
int fdisk_ask_number_set_result(struct fdisk_ask *ask, uint64_t result)
{
assert(ask);
ask->data.num.result = result;
return 0;
}
uint64_t fdisk_ask_number_get_base(struct fdisk_ask *ask)
{
assert(ask);
assert(is_number_ask(ask));
return ask->data.num.base;
}
int fdisk_ask_number_set_base(struct fdisk_ask *ask, uint64_t base)
{
assert(ask);
ask->data.num.base = base;
return 0;
}
/* if numbers are not in bytes, then specify number of bytes per the unit */
uint64_t fdisk_ask_number_get_unit(struct fdisk_ask *ask)
{
assert(ask);
assert(is_number_ask(ask));
return ask->data.num.unit;
}
int fdisk_ask_number_set_unit(struct fdisk_ask *ask, uint64_t unit)
{
assert(ask);
ask->data.num.unit = unit;
return 0;
}
int fdisk_ask_number_is_relative(struct fdisk_ask *ask)
{
assert(ask);
assert(is_number_ask(ask));
return ask->data.num.relative;
}
int fdisk_ask_number_set_relative(struct fdisk_ask *ask, int relative)
{
assert(ask);
ask->data.num.relative = relative ? 1 : 0;
return 0;
}
int fdisk_ask_number_inchars(struct fdisk_ask *ask)
{
assert(ask);
assert(is_number_ask(ask));
return ask->data.num.inchars;
}
/*
* Generates string with list ranges (e.g. 1,2,5-8) for the 'cur'
*/
#define tochar(num) ((int) ('a' + num - 1))
static char *mk_string_list(char *ptr, size_t *len, size_t *begin,
size_t *run, ssize_t cur, int inchar)
{
int rlen;
if (cur != -1) {
if (!*begin) { /* begin of the list */
*begin = cur + 1;
return ptr;
}
if (*begin + *run == cur) { /* no gap, continue */
(*run)++;
return ptr;
}
} else if (!*begin) {
*ptr = '\0';
return ptr; /* end of empty list */
}
/* add to the list */
if (!*run)
rlen = inchar ? snprintf(ptr, *len, "%c,", tochar(*begin)) :
snprintf(ptr, *len, "%zu,", *begin);
else if (*run == 1)
rlen = inchar ?
snprintf(ptr, *len, "%c,%c,", tochar(*begin), tochar(*begin + 1)) :
snprintf(ptr, *len, "%zu,%zu,", *begin, *begin + 1);
else
rlen = inchar ?
snprintf(ptr, *len, "%c-%c,", tochar(*begin), tochar(*begin + *run)) :
snprintf(ptr, *len, "%zu-%zu,", *begin, *begin + *run);
if (rlen < 0 || (size_t) rlen + 1 > *len)
return NULL;
ptr += rlen;
if (rlen > 0 && *len > (size_t) rlen)
*len -= rlen;
else
*len = 0;
if (cur == -1 && *begin) {
/* end of the list */
*(ptr - 1) = '\0'; /* remove tailing ',' from the list */
return ptr;
}
*begin = cur + 1;
*run = 0;
return ptr;
}
/**
* fdisk_ask_partnum:
* @cxt: context
* @partnum: returns partition number
* @wantnew: 0|1
*
* This function uses libfdisk Ask API to get a partition number.
*
* Returns: 0 on success, < 0 on error, 1 if no free/used partition
*/
int fdisk_ask_partnum(struct fdisk_context *cxt, size_t *partnum, int wantnew)
{
int rc = 0, inchar = 0;
char range[BUFSIZ], *ptr = range;
size_t i, len = sizeof(range), begin = 0, run = 0;
struct fdisk_ask *ask = NULL;
__typeof__(ask->data.num) *num;
assert(cxt);
assert(cxt->label);
assert(partnum);
if (cxt->label && cxt->label->flags & FDISK_LABEL_FL_INCHARS_PARTNO)
inchar = 1;
DBG(ASK, ul_debug("%s: asking for %s partition number "
"(max: %zu, inchar: %s)",
cxt->label->name,
wantnew ? "new" : "used",
cxt->label->nparts_max,
inchar ? "yes" : "not"));
ask = fdisk_new_ask();
if (!ask)
return -ENOMEM;
fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
num = &ask->data.num;
ask->data.num.inchars = inchar ? 1 : 0;
for (i = 0; i < cxt->label->nparts_max; i++) {
int used = fdisk_is_partition_used(cxt, i);
if (wantnew && !used) {
ptr = mk_string_list(ptr, &len, &begin, &run, i, inchar);
if (!ptr) {
rc = -EINVAL;
break;
}
if (!num->low)
num->dfl = num->low = i + 1;
num->hig = i + 1;
} else if (!wantnew && used) {
ptr = mk_string_list(ptr, &len, &begin, &run, i, inchar);
if (!num->low)
num->low = i + 1;
num->dfl = num->hig = i + 1;
}
}
DBG(ASK, ul_debugobj(ask, "ask limits: low: %ju, high: %ju, default: %ju",
num->low, num->hig, num->dfl));
if (!rc && !wantnew && num->low == num->hig) {
if (num->low > 0) {
/* only one existing partiton, don't ask, return the number */
fdisk_ask_number_set_result(ask, num->low);
fdisk_info(cxt, _("Selected partition %ju"), num->low);
} else if (num->low == 0) {
fdisk_warnx(cxt, _("No partition is defined yet!"));
rc = 1;
}
goto dont_ask;
}
if (!rc && wantnew && num->low == num->hig) {
if (num->low > 0) {
/* only one free partition, don't ask, return the number */
fdisk_ask_number_set_result(ask, num->low);
fdisk_info(cxt, _("Selected partition %ju"), num->low);
}
if (num->low == 0) {
fdisk_warnx(cxt, _("No free partition available!"));
rc = 1;
}
goto dont_ask;
}
if (!rc) {
mk_string_list(ptr, &len, &begin, &run, -1, inchar); /* terminate the list */
rc = fdisk_ask_number_set_range(ask, range);
}
if (!rc)
rc = fdisk_ask_set_query(ask, _("Partition number"));
if (!rc)
rc = fdisk_do_ask(cxt, ask);
dont_ask:
if (!rc) {
*partnum = fdisk_ask_number_get_result(ask);
if (*partnum)
*partnum -= 1;
}
DBG(ASK, ul_debugobj(ask, "result: %ju [rc=%d]\n", fdisk_ask_number_get_result(ask), rc));
fdisk_free_ask(ask);
return rc;
}
/* very basic wraper to ask numbers */
int fdisk_ask_number(struct fdisk_context *cxt,
uintmax_t low,
uintmax_t dflt,
uintmax_t high,
const char *query,
uintmax_t *result)
{
struct fdisk_ask *ask;
int rc;
assert(cxt);
ask = fdisk_new_ask();
if (!ask)
return -ENOMEM;
rc = fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
if (!rc)
fdisk_ask_number_set_low(ask, low);
if (!rc)
fdisk_ask_number_set_default(ask, dflt);
if (!rc)
fdisk_ask_number_set_high(ask, high);
if (!rc)
fdisk_ask_set_query(ask, query);
if (!rc)
rc = fdisk_do_ask(cxt, ask);
if (!rc)
*result = fdisk_ask_number_get_result(ask);
DBG(ASK, ul_debugobj(ask, "result: %ju [rc=%d]\n", *result, rc));
fdisk_free_ask(ask);
return rc;
}
char *fdisk_ask_string_get_result(struct fdisk_ask *ask)
{
assert(ask);
assert(fdisk_is_ask(ask, STRING));
return ask->data.str.result;
}
/*
* The @result has to be poiter to the allocated buffer.
*/
int fdisk_ask_string_set_result(struct fdisk_ask *ask, char *result)
{
assert(ask);
ask->data.str.result = result;
return 0;
}
/*
* Don't forget to deallocate @result.
*/
int fdisk_ask_string(struct fdisk_context *cxt,
const char *query,
char **result)
{
struct fdisk_ask *ask;
int rc;
assert(cxt);
ask = fdisk_new_ask();
if (!ask)
return -ENOMEM;
rc = fdisk_ask_set_type(ask, FDISK_ASKTYPE_STRING);
if (!rc)
fdisk_ask_set_query(ask, query);
if (!rc)
rc = fdisk_do_ask(cxt, ask);
if (!rc)
*result = fdisk_ask_string_get_result(ask);
DBG(ASK, ul_debugobj(ask, "result: %s [rc=%d]\n", *result, rc));
fdisk_free_ask(ask);
return rc;
}
int fdisk_ask_yesno(struct fdisk_context *cxt,
const char *query,
int *result)
{
struct fdisk_ask *ask;
int rc;
assert(cxt);
ask = fdisk_new_ask();
if (!ask)
return -ENOMEM;
rc = fdisk_ask_set_type(ask, FDISK_ASKTYPE_YESNO);
if (!rc)
fdisk_ask_set_query(ask, query);
if (!rc)
rc = fdisk_do_ask(cxt, ask);
if (!rc)
*result = fdisk_ask_yesno_get_result(ask);
DBG(ASK, ul_debugobj(ask, "result: %d [rc=%d]\n", *result, rc));
fdisk_free_ask(ask);
return rc;
}
uint64_t fdisk_ask_yesno_get_result(struct fdisk_ask *ask)
{
assert(ask);
assert(fdisk_is_ask(ask, YESNO));
return ask->data.yesno.result;
}
int fdisk_ask_yesno_set_result(struct fdisk_ask *ask, uint64_t result)
{
assert(ask);
ask->data.yesno.result = result;
return 0;
}
/*
* menu
*/
int fdisk_ask_menu_set_default(struct fdisk_ask *ask, int dfl)
{
assert(ask);
assert(fdisk_is_ask(ask, MENU));
ask->data.menu.dfl = dfl;
return 0;
}
int fdisk_ask_menu_get_default(struct fdisk_ask *ask)
{
assert(ask);
assert(fdisk_is_ask(ask, MENU));
return ask->data.menu.dfl;
}
int fdisk_ask_menu_set_result(struct fdisk_ask *ask, int key)
{
assert(ask);
assert(fdisk_is_ask(ask, MENU));
ask->data.menu.result = key;
DBG(ASK, ul_debugobj(ask, "menu result: %c\n", key));
return 0;
}
int fdisk_ask_menu_get_result(struct fdisk_ask *ask, int *key)
{
assert(ask);
assert(fdisk_is_ask(ask, MENU));
if (key)
*key = ask->data.menu.result;
return 0;
}
/* returns: 0 = success, <0 = error, >0 = idx out-of-range */
int fdisk_ask_menu_get_item(struct fdisk_ask *ask, size_t idx, int *key,
const char **name, const char **desc)
{
size_t i;
struct ask_menuitem *mi;
assert(ask);
assert(fdisk_is_ask(ask, MENU));
for (i = 0, mi = ask->data.menu.first; mi; mi = mi->next, i++) {
if (i == idx)
break;
}
if (!mi)
return 1; /* no more items */
if (key)
*key = mi->key;
if (name)
*name = mi->name;
if (desc)
*desc = mi->desc;
return 0;
}
static void fdisk_ask_menu_reset_items(struct fdisk_ask *ask)
{
struct ask_menuitem *mi;
assert(ask);
assert(fdisk_is_ask(ask, MENU));
for (mi = ask->data.menu.first; mi; ) {
struct ask_menuitem *next = mi->next;
free(mi);
mi = next;
}
}
size_t fdisk_ask_menu_get_nitems(struct fdisk_ask *ask)
{
struct ask_menuitem *mi;
size_t n;
assert(ask);
assert(fdisk_is_ask(ask, MENU));
for (n = 0, mi = ask->data.menu.first; mi; mi = mi->next, n++);
return n;
}
int fdisk_ask_menu_add_item(struct fdisk_ask *ask, int key,
const char *name, const char *desc)
{
struct ask_menuitem *mi;
assert(ask);
assert(fdisk_is_ask(ask, MENU));
mi = calloc(1, sizeof(*mi));
if (!mi)
return -ENOMEM;
mi->key = key;
mi->name = name;
mi->desc = desc;
if (!ask->data.menu.first)
ask->data.menu.first = mi;
else {
struct ask_menuitem *last = ask->data.menu.first;
while (last->next)
last = last->next;
last->next = mi;
}
DBG(ASK, ul_debugobj(ask, "new menu item: %c, \"%s\" (%s)\n", mi->key, mi->name, mi->desc));
return 0;
}
/*
* print-like
*/
#define is_print_ask(a) (fdisk_is_ask(a, WARN) || fdisk_is_ask(a, WARNX) || fdisk_is_ask(a, INFO))
int fdisk_ask_print_get_errno(struct fdisk_ask *ask)
{
assert(ask);
assert(is_print_ask(ask));
return ask->data.print.errnum;
}
int fdisk_ask_print_set_errno(struct fdisk_ask *ask, int errnum)
{
assert(ask);
ask->data.print.errnum = errnum;
return 0;
}
const char *fdisk_ask_print_get_mesg(struct fdisk_ask *ask)
{
assert(ask);
assert(is_print_ask(ask));
return ask->data.print.mesg;
}
/* does not reallocate the message! */
int fdisk_ask_print_set_mesg(struct fdisk_ask *ask, const char *mesg)
{
assert(ask);
ask->data.print.mesg = mesg;
return 0;
}
static int do_vprint(struct fdisk_context *cxt, int errnum, int type,
unsigned int flags, const char *fmt, va_list va)
{
struct fdisk_ask *ask;
int rc;
char *mesg;
assert(cxt);
if (vasprintf(&mesg, fmt, va) < 0)
return -ENOMEM;
ask = fdisk_new_ask();
if (!ask) {
free(mesg);
return -ENOMEM;
}
fdisk_ask_set_type(ask, type);
fdisk_ask_set_flags(ask, flags);
fdisk_ask_print_set_mesg(ask, mesg);
if (errnum >= 0)
fdisk_ask_print_set_errno(ask, errnum);
rc = fdisk_do_ask(cxt, ask);
fdisk_free_ask(ask);
free(mesg);
return rc;
}
int fdisk_info(struct fdisk_context *cxt, const char *fmt, ...)
{
int rc;
va_list ap;
assert(cxt);
va_start(ap, fmt);
rc = do_vprint(cxt, -1, FDISK_ASKTYPE_INFO, 0, fmt, ap);
va_end(ap);
return rc;
}
/* "smart" version, allows to set flags for the message */
int fdisk_sinfo(struct fdisk_context *cxt,
unsigned int flags, const char *fmt, ...)
{
int rc;
va_list ap;
assert(cxt);
va_start(ap, fmt);
rc = do_vprint(cxt, -1, FDISK_ASKTYPE_INFO, flags, fmt, ap);
va_end(ap);
return rc;
}
int fdisk_warn(struct fdisk_context *cxt, const char *fmt, ...)
{
int rc;
va_list ap;
assert(cxt);
va_start(ap, fmt);
rc = do_vprint(cxt, errno, FDISK_ASKTYPE_WARN, 0, fmt, ap);
va_end(ap);
return rc;
}
int fdisk_warnx(struct fdisk_context *cxt, const char *fmt, ...)
{
int rc;
va_list ap;
assert(cxt);
va_start(ap, fmt);
rc = do_vprint(cxt, -1, FDISK_ASKTYPE_WARNX, 0, fmt, ap);
va_end(ap);
return rc;
}
int fdisk_info_new_partition(
struct fdisk_context *cxt,
int num, sector_t start, sector_t stop,
struct fdisk_parttype *t)
{
int rc;
char *str = size_to_human_string(SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE,
(uint64_t)(stop - start + 1) * cxt->sector_size);
rc = fdisk_sinfo(cxt, FDISK_INFO_SUCCESS,
_("Created a new partition %d of type '%s' and of size %s."),
num, t ? t->name : _("Unknown"), str);
free(str);
return rc;
}
#ifdef TEST_PROGRAM
int test_ranges(struct fdisk_test *ts, int argc, char *argv[])
{
/* 1 - 3, 6, 8, 9, 11 13 */
size_t nums[] = { 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1 };
size_t numx[] = { 0, 0, 0 };
char range[BUFSIZ], *ptr = range;
size_t i, len = sizeof(range), begin = 0, run = 0;
for (i = 0; i < ARRAY_SIZE(nums); i++) {
if (!nums[i])
continue;
ptr = mk_string_list(ptr, &len, &begin, &run, i, 0);
}
mk_string_list(ptr, &len, &begin, &run, -1, 0);
printf("list: '%s'\n", range);
ptr = range;
len = sizeof(range), begin = 0, run = 0;
for (i = 0; i < ARRAY_SIZE(numx); i++) {
if (!numx[i])
continue;
ptr = mk_string_list(ptr, &len, &begin, &run, i, 0);
}
mk_string_list(ptr, &len, &begin, &run, -1, 0);
printf("empty list: '%s'\n", range);
return 0;
}
int main(int argc, char *argv[])
{
struct fdisk_test tss[] = {
{ "--ranges", test_ranges, "generates ranges" },
{ NULL }
};
return fdisk_run_test(tss, argc, argv);
}
#endif