/* * cfdisk.c - Display or manipulate a disk partition table. * * Copyright (C) 2014-2015 Karel Zak * Copyright (C) 1994 Kevin E. Martin (martin@cs.unc.edu) * * The original cfdisk was inspired by the fdisk program * by A. V. Le Blanc (leblanc@mcc.ac.uk. * * cfdisk is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBBLKID # include /* keep it optional */ #endif #ifdef HAVE_LIBMOUNT # include /* keep it optional for non-linux systems */ #endif #ifdef HAVE_SLANG_H # include #elif defined(HAVE_SLANG_SLANG_H) # include #endif #ifdef HAVE_SLCURSES_H # include #elif defined(HAVE_SLANG_SLCURSES_H) # include #elif defined(HAVE_NCURSESW_NCURSES_H) && defined(HAVE_WIDECHAR) # include #elif defined(HAVE_NCURSES_H) # include #elif defined(HAVE_NCURSES_NCURSES_H) # include #endif #ifdef HAVE_WIDECHAR # include # include #endif #include "c.h" #include "closestream.h" #include "nls.h" #include "strutils.h" #include "xalloc.h" #include "mbsalign.h" #include "colors.h" #include "debug.h" #include "list.h" #ifdef __GNU__ # define DEFAULT_DEVICE "/dev/hd0" # define ALTERNATE_DEVICE "/dev/sd0" #elif defined(__FreeBSD__) # define DEFAULT_DEVICE "/dev/ad0" # define ALTERNATE_DEVICE "/dev/da0" #else # define DEFAULT_DEVICE "/dev/sda" # define ALTERNATE_DEVICE "/dev/hda" #endif #define ARROW_CURSOR_STRING ">> " #define ARROW_CURSOR_DUMMY " " #define ARROW_CURSOR_WIDTH (sizeof(ARROW_CURSOR_STRING) - 1) /* vertical menu */ #define MENU_V_SPADDING 1 /* space around menu item string */ /* horizontal menu */ #define MENU_H_SPADDING 0 /* space around menu item string */ #define MENU_H_BETWEEN 2 /* space between menu items */ #define MENU_H_PRESTR "[" #define MENU_H_POSTSTR "]" #define MENU_TITLE_PADDING 3 #define MENU_H_PRESTR_SZ (sizeof(MENU_H_PRESTR) - 1) #define MENU_H_POSTSTR_SZ (sizeof(MENU_H_POSTSTR) - 1) #define TABLE_START_LINE 4 #define MENU_START_LINE (ui_lines - 4) /* The menu maybe use two lines */ #define INFO_LINE (ui_lines - 2) #define WARN_LINE INFO_LINE #define HINT_LINE (ui_lines - 1) #define CFDISK_ERR_ESC 5000 #ifndef KEY_ESC # define KEY_ESC '\033' #endif #ifndef KEY_DELETE # define KEY_DELETE '\177' #endif /* colors */ enum { CFDISK_CL_NONE = 0, CFDISK_CL_WARNING, CFDISK_CL_FREESPACE, CFDISK_CL_INFO }; static const int color_pairs[][2] = { /* color foreground, background */ [CFDISK_CL_WARNING] = { COLOR_RED, -1 }, [CFDISK_CL_FREESPACE] = { COLOR_GREEN, -1 }, [CFDISK_CL_INFO] = { COLOR_BLUE, -1 } }; struct cfdisk; static struct cfdisk_menuitem *menu_get_menuitem(struct cfdisk *cf, size_t idx); static struct cfdisk_menuitem *menu_get_menuitem_by_key(struct cfdisk *cf, int key, size_t *idx); static struct cfdisk_menu *menu_push(struct cfdisk *cf, struct cfdisk_menuitem *item); static struct cfdisk_menu *menu_pop(struct cfdisk *cf); static void menu_refresh_size(struct cfdisk *cf); static int ui_refresh(struct cfdisk *cf); static void ui_warnx(const char *fmt, ...); static void ui_warn(const char *fmt, ...); static void ui_info(const char *fmt, ...); static void ui_draw_menu(struct cfdisk *cf); static int ui_menu_move(struct cfdisk *cf, int key); static void ui_menu_resize(struct cfdisk *cf); static int ui_get_size(struct cfdisk *cf, const char *prompt, uintmax_t *res, uintmax_t low, uintmax_t up, int *expsize); static int ui_enabled; static int ui_resize; /* ncurses LINES and COLS may be actual variables or *macros*, but we need * something portable and writable */ size_t ui_lines; size_t ui_cols; /* menu item */ struct cfdisk_menuitem { int key; /* keyboard shortcut */ const char *name; /* item name */ const char *desc; /* item description (hint) */ void *userdata; }; /* menu */ struct cfdisk_menu { char *title; /* optional menu title */ struct cfdisk_menuitem *items; /* array with menu items */ char *ignore;/* string with keys to ignore */ size_t width; /* maximal width of the menu item */ size_t nitems; /* number of the active menu items */ size_t page_sz;/* when menu longer than screen */ size_t idx; /* the current menu item */ int prefkey;/* preferred menu item */ struct cfdisk_menu *prev; /* @ignore keys generator */ int (*ignore_cb) (struct cfdisk *, char *, size_t); unsigned int vertical : 1; /* enable vertical mode */ }; /* main menu */ static struct cfdisk_menuitem main_menuitems[] = { { 'b', N_("Bootable"), N_("Toggle bootable flag of the current partition") }, { 'd', N_("Delete"), N_("Delete the current partition") }, { 'n', N_("New"), N_("Create new partition from free space") }, { 'q', N_("Quit"), N_("Quit program without writing partition table") }, { 't', N_("Type"), N_("Change the partition type") }, { 'h', N_("Help"), N_("Print help screen") }, { 's', N_("Sort"), N_("Fix partitions order") }, { 'W', N_("Write"), N_("Write partition table to disk (this might destroy data)") }, { 'u', N_("Dump"), N_("Dump partition table to sfdisk compatible script file") }, { 0, NULL, NULL } }; /* extra partinfo in name:value pairs */ struct cfdisk_extra { char *name; char *data; struct list_head exs; }; /* line and extra partinfo list_head */ struct cfdisk_line { char *data; /* line data */ struct libscols_table *extra; /* extra info ('X') */ WINDOW *w; /* window with extra info */ }; /* top level control struct */ struct cfdisk { struct fdisk_context *cxt; /* libfdisk context */ struct fdisk_table *table; /* partition table */ struct cfdisk_menu *menu; /* the current menu */ int *fields; /* output columns IDs */ size_t nfields; /* number of columns IDs */ char *linesbuf; /* table as string */ size_t linesbufsz; /* size of the tb_buf */ struct cfdisk_line *lines; /* list of lines */ size_t nlines; /* number of lines */ size_t lines_idx; /* current line <0..N>, exclude header */ size_t page_sz; unsigned int nwrites; /* fdisk_write_disklabel() counter */ WINDOW *act_win; /* the window currently on the screen */ #ifdef HAVE_LIBMOUNT struct libmnt_table *mtab; struct libmnt_table *fstab; struct libmnt_cache *mntcache; #endif unsigned int wrong_order :1, /* PT not in right order */ zero_start :1, /* ignore existing partition table */ show_extra :1; /* show extra partinfo */ }; /* * let's use include/debug.h stuff for cfdisk too */ UL_DEBUG_DEFINE_MASK(cfdisk); UL_DEBUG_DEFINE_MASKNAMES(cfdisk) = UL_DEBUG_EMPTY_MASKNAMES; #define CFDISK_DEBUG_INIT (1 << 1) #define CFDISK_DEBUG_UI (1 << 2) #define CFDISK_DEBUG_MENU (1 << 3) #define CFDISK_DEBUG_MISC (1 << 4) #define CFDISK_DEBUG_TABLE (1 << 5) #define CFDISK_DEBUG_ALL 0xFFFF #define DBG(m, x) __UL_DBG(cfdisk, CFDISK_DEBUG_, m, x) static void cfdisk_init_debug(void) { __UL_INIT_DEBUG(cfdisk, CFDISK_DEBUG_, 0, CFDISK_DEBUG); } /* Initialize output columns -- we follow libfdisk fields (usually specific * to the label type. */ static int cols_init(struct cfdisk *cf) { assert(cf); free(cf->fields); cf->fields = NULL; cf->nfields = 0; return fdisk_label_get_fields_ids(NULL, cf->cxt, &cf->fields, &cf->nfields); } static void resize(void) { struct winsize ws; if (ioctl(fileno(stdout), TIOCGWINSZ, &ws) != -1 && ws.ws_row && ws.ws_col) { ui_lines = ws.ws_row; ui_cols = ws.ws_col; #if HAVE_RESIZETERM resizeterm(ws.ws_row, ws.ws_col); #endif clearok(stdscr, TRUE); } touchwin(stdscr); DBG(UI, ul_debug("ui: resize refresh ui_cols=%zu, ui_lines=%zu", ui_cols, ui_lines)); ui_resize = 0; } /* Reads partition in tree-like order from scols */ static int partition_from_scols(struct fdisk_table *tb, struct libscols_line *ln) { struct fdisk_partition *pa = scols_line_get_userdata(ln); fdisk_table_add_partition(tb, pa); fdisk_unref_partition(pa); if (scols_line_has_children(ln)) { struct libscols_line *chln; struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD); if (!itr) return -EINVAL; while (scols_line_next_child(ln, itr, &chln) == 0) partition_from_scols(tb, chln); scols_free_iter(itr); } return 0; } static char *table_to_string(struct cfdisk *cf, struct fdisk_table *tb) { struct fdisk_partition *pa; struct fdisk_label *lb; struct fdisk_iter *itr = NULL; struct libscols_table *table = NULL; struct libscols_iter *s_itr = NULL; char *res = NULL; size_t i; int tree = 0; struct libscols_line *ln, *ln_cont = NULL; DBG(TABLE, ul_debug("convert to string")); assert(cf); assert(cf->cxt); assert(cf->fields); assert(tb); lb = fdisk_get_label(cf->cxt, NULL); assert(lb); itr = fdisk_new_iter(FDISK_ITER_FORWARD); if (!itr) goto done; /* get container (e.g. extended partition) */ while (fdisk_table_next_partition(tb, itr, &pa) == 0) { if (fdisk_partition_is_nested(pa)) { DBG(TABLE, ul_debug("nested detected, using tree")); tree = SCOLS_FL_TREE; break; } } table = scols_new_table(); if (!table) goto done; scols_table_enable_maxout(table, 1); scols_table_enable_nowrap(table, 1); /* headers */ for (i = 0; i < cf->nfields; i++) { int fl = 0; const struct fdisk_field *field = fdisk_label_get_field(lb, cf->fields[i]); if (!field) continue; if (fdisk_field_is_number(field)) fl |= SCOLS_FL_RIGHT; if (fdisk_field_get_id(field) == FDISK_FIELD_TYPE) fl |= SCOLS_FL_TRUNC; if (tree && fdisk_field_get_id(field) == FDISK_FIELD_DEVICE) fl |= SCOLS_FL_TREE; if (!scols_table_new_column(table, _(fdisk_field_get_name(field)), fdisk_field_get_width(field), fl)) goto done; } /* data */ fdisk_reset_iter(itr, FDISK_ITER_FORWARD); while (fdisk_table_next_partition(tb, itr, &pa) == 0) { struct libscols_line *parent = fdisk_partition_is_nested(pa) ? ln_cont : NULL; ln = scols_table_new_line(table, parent); if (!ln) goto done; for (i = 0; i < cf->nfields; i++) { char *cdata = NULL; if (fdisk_partition_to_string(pa, cf->cxt, cf->fields[i], &cdata)) continue; scols_line_refer_data(ln, i, cdata); } if (tree && fdisk_partition_is_container(pa)) ln_cont = ln; scols_line_set_userdata(ln, (void *) pa); fdisk_ref_partition(pa); } if (scols_table_is_empty(table)) goto done; scols_table_reduce_termwidth(table, ARROW_CURSOR_WIDTH); scols_print_table_to_string(table, &res); /* scols_* code might reorder lines, let's reorder @tb according to the * final output (it's no problem because partitions are addressed by * parno stored within struct fdisk_partition) */ /* remove all */ fdisk_reset_iter(itr, FDISK_ITER_FORWARD); while (fdisk_table_next_partition(tb, itr, &pa) == 0) fdisk_table_remove_partition(tb, pa); s_itr = scols_new_iter(SCOLS_ITER_FORWARD); if (!s_itr) goto done; /* add all in the right order (don't forget the output is tree) */ while (scols_table_next_line(table, s_itr, &ln) == 0) { if (scols_line_get_parent(ln)) continue; if (partition_from_scols(tb, ln)) break; } done: scols_unref_table(table); scols_free_iter(s_itr); fdisk_free_iter(itr); return res; } static void cfdisk_free_lines(struct cfdisk *cf) { size_t i = 0; while(i < cf->nlines) { scols_unref_table(cf->lines[i].extra); DBG(UI, ul_debug("delete window: %p", cf->lines[i].w)); if (cf->lines[i].w) delwin(cf->lines[i].w); cf->lines[i].w = NULL; ++i; } cf->act_win = NULL; free(cf->lines); cf->lines = NULL; } /* * Read data about partitions from libfdisk and prepare output lines. */ static int lines_refresh(struct cfdisk *cf) { int rc; char *p; size_t i; assert(cf); DBG(TABLE, ul_debug("refreshing buffer")); free(cf->linesbuf); cfdisk_free_lines(cf); cf->linesbuf = NULL; cf->linesbufsz = 0; cf->lines = NULL; cf->nlines = 0; fdisk_unref_table(cf->table); cf->table = NULL; /* read partitions and free spaces into cf->table */ rc = fdisk_get_partitions(cf->cxt, &cf->table); if (!rc) rc = fdisk_get_freespaces(cf->cxt, &cf->table); if (rc) return rc; cf->linesbuf = table_to_string(cf, cf->table); if (!cf->linesbuf) return -ENOMEM; cf->linesbufsz = strlen(cf->linesbuf); cf->nlines = fdisk_table_get_nents(cf->table) + 1; /* 1 for header line */ cf->page_sz = 0; cf->wrong_order = fdisk_table_wrong_order(cf->table) ? 1 : 0; if (MENU_START_LINE - TABLE_START_LINE < cf->nlines) cf->page_sz = MENU_START_LINE - TABLE_START_LINE - 1; cf->lines = xcalloc(cf->nlines, sizeof(struct cfdisk_line)); for (p = cf->linesbuf, i = 0; p && i < cf->nlines; i++) { cf->lines[i].data = p; p = strchr(p, '\n'); if (p) { *p = '\0'; p++; } cf->lines[i].extra = scols_new_table(); scols_table_enable_noheadings(cf->lines[i].extra, 1); scols_table_new_column(cf->lines[i].extra, NULL, 0, SCOLS_FL_RIGHT); scols_table_new_column(cf->lines[i].extra, NULL, 0, SCOLS_FL_TRUNC); } return 0; } static struct fdisk_partition *get_current_partition(struct cfdisk *cf) { assert(cf); assert(cf->table); return fdisk_table_get_partition(cf->table, cf->lines_idx); } static int is_freespace(struct cfdisk *cf, size_t i) { struct fdisk_partition *pa; assert(cf); assert(cf->table); pa = fdisk_table_get_partition(cf->table, i); return fdisk_partition_is_freespace(pa); } /* converts libfdisk FDISK_ASKTYPE_MENU to cfdisk menu and returns user's * responseback to libfdisk */ static int ask_menu(struct fdisk_ask *ask, struct cfdisk *cf) { struct cfdisk_menuitem *d, *cm; int key; size_t i = 0, nitems; const char *name, *desc; assert(ask); assert(cf); /* create cfdisk menu according to libfdisk ask-menu, note that the * last cm[] item has to be empty -- so nitems + 1 */ nitems = fdisk_ask_menu_get_nitems(ask); cm = xcalloc(nitems + 1, sizeof(struct cfdisk_menuitem)); for (i = 0; i < nitems; i++) { if (fdisk_ask_menu_get_item(ask, i, &key, &name, &desc)) break; cm[i].key = key; cm[i].desc = desc; cm[i].name = name; } /* make the new menu active */ menu_push(cf, cm); ui_draw_menu(cf); refresh(); /* wait for keys */ do { key = getch(); if (ui_resize) ui_menu_resize(cf); if (ui_menu_move(cf, key) == 0) continue; switch (key) { case KEY_ENTER: case '\n': case '\r': d = menu_get_menuitem(cf, cf->menu->idx); if (d) fdisk_ask_menu_set_result(ask, d->key); menu_pop(cf); free(cm); return 0; } } while (1); menu_pop(cf); free(cm); return -1; } /* libfdisk callback */ static int ask_callback(struct fdisk_context *cxt __attribute__((__unused__)), struct fdisk_ask *ask, void *data __attribute__((__unused__))) { int rc = 0; assert(ask); switch(fdisk_ask_get_type(ask)) { case FDISK_ASKTYPE_INFO: ui_info(fdisk_ask_print_get_mesg(ask)); break; case FDISK_ASKTYPE_WARNX: ui_warnx(fdisk_ask_print_get_mesg(ask)); break; case FDISK_ASKTYPE_WARN: ui_warn(fdisk_ask_print_get_mesg(ask)); break; case FDISK_ASKTYPE_MENU: ask_menu(ask, (struct cfdisk *) data); break; default: ui_warnx(_("internal error: unsupported dialog type %d"), fdisk_ask_get_type(ask)); return -EINVAL; } return rc; } static int ui_end(void) { if (!ui_enabled) return -EINVAL; #if defined(HAVE_SLCURSES_H) || defined(HAVE_SLANG_SLCURSES_H) SLsmg_gotorc(ui_lines - 1, 0); SLsmg_refresh(); #else mvcur(0, ui_cols - 1, ui_lines-1, 0); #endif curs_set(1); nl(); endwin(); printf("\n"); ui_enabled = 0; return 0; } static void ui_vprint_center(size_t line, int attrs, const char *fmt, va_list ap) { size_t width; char *buf = NULL; move(line, 0); clrtoeol(); xvasprintf(&buf, fmt, ap); width = mbs_safe_width(buf); if (width > (size_t) ui_cols) { char *p = strrchr(buf + ui_cols, ' '); if (!p) p = buf + ui_cols; *p = '\0'; if (line + 1 >= ui_lines) line--; attron(attrs); mvaddstr(line, 0, buf); mvaddstr(line + 1, 0, p+1); attroff(attrs); } else { attron(attrs); mvaddstr(line, (ui_cols - width) / 2, buf); attroff(attrs); } free(buf); } static void ui_center(size_t line, const char *fmt, ...) { va_list ap; va_start(ap, fmt); ui_vprint_center(line, 0, fmt, ap); va_end(ap); } static void ui_warnx(const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (ui_enabled) ui_vprint_center(WARN_LINE, colors_wanted() ? COLOR_PAIR(CFDISK_CL_WARNING) : 0, fmt, ap); else { vfprintf(stderr, fmt, ap); fputc('\n', stderr); } va_end(ap); } static void ui_warn(const char *fmt, ...) { char *fmt_m; va_list ap; xasprintf(&fmt_m, "%s: %m", fmt); va_start(ap, fmt); if (ui_enabled) ui_vprint_center(WARN_LINE, colors_wanted() ? COLOR_PAIR(CFDISK_CL_WARNING) : 0, fmt_m, ap); else { vfprintf(stderr, fmt_m, ap); fputc('\n', stderr); } va_end(ap); free(fmt_m); } static void ui_clean_warn(void) { move(WARN_LINE, 0); clrtoeol(); } static int __attribute__((__noreturn__)) ui_errx(int rc, const char *fmt, ...) { va_list ap; ui_end(); va_start(ap, fmt); fprintf(stderr, "%s: ", program_invocation_short_name); vfprintf(stderr, fmt, ap); fputc('\n', stderr); va_end(ap); exit(rc); } static void ui_info(const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (ui_enabled) ui_vprint_center(INFO_LINE, colors_wanted() ? COLOR_PAIR(CFDISK_CL_INFO) : 0, fmt, ap); else { vfprintf(stdout, fmt, ap); fputc('\n', stdout); } va_end(ap); } static void ui_clean_info(void) { move(INFO_LINE, 0); clrtoeol(); } static void ui_hint(const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (ui_enabled) ui_vprint_center(HINT_LINE, A_BOLD, fmt, ap); else { vfprintf(stdout, fmt, ap); fputc('\n', stdout); } va_end(ap); } static void ui_clean_hint(void) { move(HINT_LINE, 0); clrtoeol(); } static void die_on_signal(int dummy __attribute__((__unused__))) { DBG(MISC, ul_debug("die on signal.")); ui_end(); exit(EXIT_FAILURE); } static void resize_on_signal(int dummy __attribute__((__unused__))) { DBG(MISC, ul_debug("resize on signal.")); ui_resize = 1; } static void menu_refresh_size(struct cfdisk *cf) { if (cf->menu && cf->menu->nitems) cf->menu->page_sz = (cf->menu->nitems / (ui_lines - 4)) ? ui_lines - 4 : 0; } static void menu_update_ignore(struct cfdisk *cf) { char ignore[128] = { 0 }; int i = 0; struct cfdisk_menu *m; struct cfdisk_menuitem *d, *org = NULL; size_t idx; assert(cf); assert(cf->menu); assert(cf->menu->ignore_cb); m = cf->menu; DBG(MENU, ul_debug("update menu ignored keys")); i = m->ignore_cb(cf, ignore, sizeof(ignore)); ignore[i] = '\0'; /* return if no change */ if ((!m->ignore && !*ignore) || (m->ignore && *ignore && strcmp(m->ignore, ignore) == 0)) { return; } if (!m->prefkey) org = menu_get_menuitem(cf, m->idx); free(m->ignore); m->ignore = xstrdup(ignore); m->nitems = 0; for (d = m->items; d->name; d++) { if (m->ignore && strchr(m->ignore, d->key)) continue; m->nitems++; } DBG(MENU, ul_debug("update menu preferred keys")); /* refresh menu index to be at the same menuitem or go to the first */ if (org && menu_get_menuitem_by_key(cf, org->key, &idx)) m->idx = idx; else if (m->prefkey && menu_get_menuitem_by_key(cf, m->prefkey, &idx)) m->idx = idx; else m->idx = 0; menu_refresh_size(cf); } static struct cfdisk_menu *menu_push( struct cfdisk *cf, struct cfdisk_menuitem *items) { struct cfdisk_menu *m = xcalloc(1, sizeof(*m)); struct cfdisk_menuitem *d; assert(cf); DBG(MENU, ul_debug("new menu")); m->prev = cf->menu; m->items = items; for (d = m->items; d->name; d++) { const char *name = _(d->name); size_t len = mbs_safe_width(name); if (len > m->width) m->width = len; m->nitems++; } cf->menu = m; menu_refresh_size(cf); return m; } static struct cfdisk_menu *menu_pop(struct cfdisk *cf) { struct cfdisk_menu *m = NULL; assert(cf); DBG(MENU, ul_debug("pop menu")); if (cf->menu) { m = cf->menu->prev; free(cf->menu->ignore); free(cf->menu->title); free(cf->menu); } cf->menu = m; return cf->menu; } static void menu_set_title(struct cfdisk_menu *m, const char *title) { char *str = NULL; if (title) { size_t len = mbs_safe_width(title); if (len + MENU_TITLE_PADDING > m->width) m->width = len + MENU_TITLE_PADDING; str = xstrdup(title); } m->title = str; } static int ui_init(struct cfdisk *cf __attribute__((__unused__))) { struct sigaction sa; DBG(UI, ul_debug("init")); /* setup SIGCHLD handler */ sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = die_on_signal; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sa.sa_handler = resize_on_signal; sigaction(SIGWINCH, &sa, NULL); ui_enabled = 1; initscr(); #ifdef HAVE_USE_DEFAULT_COLORS if (colors_wanted() && has_colors()) { size_t i; start_color(); use_default_colors(); for (i = 1; i < ARRAY_SIZE(color_pairs); i++) /* yeah, start from 1! */ init_pair(i, color_pairs[i][0], color_pairs[i][1]); } #else colors_off(); #endif cbreak(); noecho(); nonl(); curs_set(0); keypad(stdscr, TRUE); return 0; } /* "[ string ]" */ #define MENU_H_ITEMWIDTH(m) ( MENU_H_PRESTR_SZ \ + MENU_H_SPADDING \ + (m)->width \ + MENU_H_SPADDING \ + MENU_H_POSTSTR_SZ) #define MENU_V_ITEMWIDTH(m) (MENU_V_SPADDING + (m)->width + MENU_V_SPADDING) static size_t menuitem_get_line(struct cfdisk *cf, size_t idx) { struct cfdisk_menu *m = cf->menu; if (m->vertical) { if (!m->page_sz) /* small menu */ return (ui_lines - (cf->menu->nitems + 1)) / 2 + idx; return (idx % m->page_sz) + 1; } else { size_t len = MENU_H_ITEMWIDTH(m) + MENU_H_BETWEEN; /** item width */ size_t items = ui_cols / len; /* items per line */ if (items == 0) return 0; return MENU_START_LINE + ((idx / items)); } } static int menuitem_get_column(struct cfdisk *cf, size_t idx) { if (cf->menu->vertical) { size_t nc = MENU_V_ITEMWIDTH(cf->menu); if ((size_t) ui_cols <= nc) return 0; return (ui_cols - nc) / 2; } else { size_t len = MENU_H_ITEMWIDTH(cf->menu) + MENU_H_BETWEEN; /* item width */ size_t items = ui_cols / len; /* items per line */ size_t extra = items < cf->menu->nitems ? /* extra space on line */ ui_cols % len : /* - multi-line menu */ ui_cols - (cf->menu->nitems * len); /* - one line menu */ if (items == 0) return 0; /* hmm... no space */ extra += MENU_H_BETWEEN; /* add padding after last item to extra */ if (idx < items) return (idx * len) + (extra / 2); return ((idx % items) * len) + (extra / 2); } } static int menuitem_on_page(struct cfdisk *cf, size_t idx) { struct cfdisk_menu *m = cf->menu; if (m->page_sz == 0 || m->idx / m->page_sz == idx / m->page_sz) return 1; return 0; } static struct cfdisk_menuitem *menu_get_menuitem(struct cfdisk *cf, size_t idx) { struct cfdisk_menuitem *d; size_t i; for (i = 0, d = cf->menu->items; d->name; d++) { if (cf->menu->ignore && strchr(cf->menu->ignore, d->key)) continue; if (i++ == idx) return d; } return NULL; } static struct cfdisk_menuitem *menu_get_menuitem_by_key(struct cfdisk *cf, int key, size_t *idx) { struct cfdisk_menuitem *d; for (*idx = 0, d = cf->menu->items; d->name; d++) { if (cf->menu->ignore && strchr(cf->menu->ignore, d->key)) continue; if (key == d->key) return d; (*idx)++; } return NULL; } static void ui_draw_menuitem(struct cfdisk *cf, struct cfdisk_menuitem *d, size_t idx) { char buf[80 * MB_CUR_MAX], *ptr = buf; const char *name; size_t width; int ln, cl, vert = cf->menu->vertical; if (!menuitem_on_page(cf, idx)) return; /* no visible item */ ln = menuitem_get_line(cf, idx); cl = menuitem_get_column(cf, idx); /* string width */ if (vert) { width = cf->menu->width + MENU_V_SPADDING; memset(ptr, ' ', MENU_V_SPADDING); ptr += MENU_V_SPADDING; } else width = MENU_H_SPADDING + cf->menu->width + MENU_H_SPADDING; name = _(d->name); mbsalign(name, ptr, sizeof(buf), &width, vert ? MBS_ALIGN_LEFT : MBS_ALIGN_CENTER, 0); DBG(MENU, ul_debug("menuitem: cl=%d, ln=%d, item='%s'", cl, ln, buf)); if (vert) { mvaddch(ln, cl - 1, ACS_VLINE); mvaddch(ln, cl + MENU_V_ITEMWIDTH(cf->menu), ACS_VLINE); } if (cf->menu->idx == idx) standout(); if (vert) mvprintw(ln, cl, "%s", buf); else mvprintw(ln, cl, "%s%s%s", MENU_H_PRESTR, buf, MENU_H_POSTSTR); if (cf->menu->idx == idx) { standend(); if (d->desc) ui_hint(_(d->desc)); } } static void ui_clean_menu(struct cfdisk *cf) { size_t i; size_t lastline; struct cfdisk_menu *m = cf->menu; size_t ln = menuitem_get_line(cf, 0); if (m->vertical) lastline = ln + (m->page_sz ? m->page_sz : m->nitems); else lastline = menuitem_get_line(cf, m->nitems); for (i = ln; i <= lastline; i++) { move(i, 0); clrtoeol(); DBG(MENU, ul_debug("clean_menu: line %zu", i)); } if (m->vertical) { move(ln - 1, 0); clrtoeol(); } ui_clean_hint(); } static void ui_draw_menu(struct cfdisk *cf) { struct cfdisk_menuitem *d; struct cfdisk_menu *m; size_t i = 0; size_t ln = menuitem_get_line(cf, 0); size_t nlines; assert(cf); assert(cf->menu); DBG(MENU, ul_debug("draw start")); ui_clean_menu(cf); m = cf->menu; if (m->vertical) nlines = m->page_sz ? m->page_sz : m->nitems; else nlines = menuitem_get_line(cf, m->nitems); if (m->ignore_cb) menu_update_ignore(cf); i = 0; while ((d = menu_get_menuitem(cf, i))) ui_draw_menuitem(cf, d, i++); if (m->vertical) { size_t cl = menuitem_get_column(cf, 0); size_t curpg = m->page_sz ? m->idx / m->page_sz : 0; /* corners and horizontal lines */ mvaddch(ln - 1, cl - 1, ACS_ULCORNER); mvaddch(ln + nlines, cl - 1, ACS_LLCORNER); for (i = 0; i < MENU_V_ITEMWIDTH(m); i++) { mvaddch(ln - 1, cl + i, ACS_HLINE); mvaddch(ln + nlines, cl + i, ACS_HLINE); } mvaddch(ln - 1, cl + i, ACS_URCORNER); mvaddch(ln + nlines, cl + i, ACS_LRCORNER); /* draw also lines around empty lines on last page */ if (m->page_sz && m->nitems / m->page_sz == m->idx / m->page_sz) { for (i = m->nitems % m->page_sz + 1; i <= m->page_sz; i++) { mvaddch(i, cl - 1, ACS_VLINE); mvaddch(i, cl + MENU_V_ITEMWIDTH(m), ACS_VLINE); } } if (m->title) { attron(A_BOLD); mvprintw(ln - 1, cl, " %s ", m->title); attroff(A_BOLD); } if (curpg != 0) mvaddch(ln - 1, cl + MENU_V_ITEMWIDTH(m) - 2, ACS_UARROW); if (m->page_sz && curpg < m->nitems / m->page_sz) mvaddch(ln + nlines, cl + MENU_V_ITEMWIDTH(m) - 2, ACS_DARROW); } DBG(MENU, ul_debug("draw end.")); } inline static int extra_insert_pair(struct cfdisk_line *l, const char *name, const char *data) { struct libscols_line *lsl; int rc; assert(l); assert(l->extra); if (!data || !*data) return 0; lsl = scols_table_new_line(l->extra, NULL); if (!lsl) return -ENOMEM; rc = scols_line_set_data(lsl, 0, name); if (!rc) rc = scols_line_set_data(lsl, 1, data); return rc; } #ifdef HAVE_LIBMOUNT static char *get_mountpoint(struct cfdisk *cf, const char *uuid, const char *label) { struct libmnt_fs *fs = NULL; char *target = NULL; int mounted = 0; assert(uuid || label); DBG(UI, ul_debug("asking for mountpoint [uuid=%s, label=%s]", uuid, label)); if (!cf->mntcache) cf->mntcache = mnt_new_cache(); /* 1st try between mounted filesystems */ if (!cf->mtab) { cf->mtab = mnt_new_table(); if (cf->mtab) { mnt_table_set_cache(cf->mtab, cf->mntcache); mnt_table_parse_mtab(cf->mtab, NULL); } } if (cf->mtab) fs = mnt_table_find_tag(cf->mtab, uuid ? "UUID" : "LABEL", uuid ? : label, MNT_ITER_FORWARD); /* 2nd try fstab */ if (!fs) { if (!cf->fstab) { cf->fstab = mnt_new_table(); if (cf->fstab) { mnt_table_set_cache(cf->fstab, cf->mntcache); mnt_table_parse_fstab(cf->fstab, NULL); } } if (cf->fstab) fs = mnt_table_find_tag(cf->fstab, uuid ? "UUID" : "LABEL", uuid ? : label, MNT_ITER_FORWARD); } else mounted = 1; if (fs) { if (mounted) xasprintf(&target, _("%s (mounted)"), mnt_fs_get_target(fs)); else target = xstrdup(mnt_fs_get_target(fs)); } return target; } #endif /* HAVE_LIBMOUNT */ static void extra_prepare_data(struct cfdisk *cf) { struct fdisk_partition *pa = get_current_partition(cf); struct cfdisk_line *l = &cf->lines[cf->lines_idx]; char *data = NULL; char *devuuid = NULL, *devlabel = NULL; DBG(UI, ul_debug("preparing extra data")); /* string data should not equal an empty string */ if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_NAME, &data) && data) { extra_insert_pair(l, _("Partition name:"), data); free(data); } if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_UUID, &data) && data) { extra_insert_pair(l, _("Partition UUID:"), data); free(data); } if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_TYPE, &data) && data) { char *code = NULL, *type = NULL; fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_TYPEID, &code); xasprintf(&type, "%s (%s)", data, code); extra_insert_pair(l, _("Partition type:"), type); free(data); free(code); free(type); } if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_ATTR, &data) && data) { extra_insert_pair(l, _("Attributes:"), data); free(data); } /* for numeric data, only show non-zero rows */ if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_BSIZE, &data) && data) { if (atoi(data)) extra_insert_pair(l, "BSIZE:", data); free(data); } if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_CPG, &data) && data) { if (atoi(data)) extra_insert_pair(l, "CPG:", data); free(data); } if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_FSIZE, &data) && data) { if (atoi(data)) extra_insert_pair(l, "FSIZE:", data); free(data); } #ifdef HAVE_LIBBLKID if (fdisk_partition_has_start(pa) && fdisk_partition_has_size(pa)) { int fd; uintmax_t start, size; blkid_probe pr = blkid_new_probe(); if (!pr) goto done; DBG(UI, ul_debug("blkid prober: %p", pr)); start = fdisk_partition_get_start(pa) * fdisk_get_sector_size(cf->cxt); size = fdisk_partition_get_size(pa) * fdisk_get_sector_size(cf->cxt); fd = fdisk_get_devfd(cf->cxt); if (blkid_probe_set_device(pr, fd, start, size) == 0 && blkid_do_fullprobe(pr) == 0) { const char *bdata = NULL; if (!blkid_probe_lookup_value(pr, "TYPE", &bdata, NULL)) extra_insert_pair(l, _("Filesystem:"), bdata); if (!blkid_probe_lookup_value(pr, "LABEL", &bdata, NULL)) { extra_insert_pair(l, _("Filesystem label:"), bdata); devlabel = xstrdup(bdata); } if (!blkid_probe_lookup_value(pr, "UUID", &bdata, NULL)) { extra_insert_pair(l, _("Filesystem UUID:"), bdata); devuuid = xstrdup(bdata); } } blkid_free_probe(pr); } #endif /* HAVE_LIBBLKID */ #ifdef HAVE_LIBMOUNT if (devuuid || devlabel) { data = get_mountpoint(cf, devuuid, devlabel); if (data) { extra_insert_pair(l, _("Mountpoint:"), data); free(data); } } #endif /* HAVE_LIBMOUNT */ done: free(devlabel); free(devuuid); } static int ui_draw_extra(struct cfdisk *cf) { WINDOW *win_ex; int wline = 1; struct cfdisk_line *ln = &cf->lines[cf->lines_idx]; char *tbstr = NULL, *end; int win_ex_start_line, win_height, tblen; int ndatalines; if (!cf->show_extra) return 0; DBG(UI, ul_debug("draw extra")); assert(ln->extra); if (cf->act_win) { wclear(cf->act_win); touchwin(stdscr); } if (scols_table_is_empty(ln->extra)) { extra_prepare_data(cf); if (scols_table_is_empty(ln->extra)) return 0; } ndatalines = fdisk_table_get_nents(cf->table) + 1; /* nents + header + one free line */ win_ex_start_line = TABLE_START_LINE + ndatalines; win_height = MENU_START_LINE - win_ex_start_line; tblen = scols_table_get_nlines(ln->extra); /* we can't get a single line of data under the partlist*/ if (win_height < 3) return 1; /* number of data lines + 2 for top/bottom lines */ win_height = win_height < tblen + 2 ? win_height : tblen + 2; if ((size_t) win_ex_start_line + win_height + 1 < MENU_START_LINE) win_ex_start_line = MENU_START_LINE - win_height; win_ex = subwin(stdscr, win_height, ui_cols - 2, win_ex_start_line, 1); scols_table_reduce_termwidth(ln->extra, 4); scols_print_table_to_string(ln->extra, &tbstr); end = tbstr; while ((end = strchr(end, '\n'))) *end++ = '\0'; box(win_ex, 0, 0); end = tbstr; while (--win_height > 1) { mvwaddstr(win_ex, wline++, 1 /* window column*/, tbstr); tbstr += strlen(tbstr) + 1; } free(end); if (ln->w) delwin(ln->w); DBG(UI, ul_debug("draw window: %p", win_ex)); touchwin(stdscr); wrefresh(win_ex); cf->act_win = ln->w = win_ex; return 0; } static void ui_menu_goto(struct cfdisk *cf, int where) { struct cfdisk_menuitem *d; size_t old; /* stop and begin/end for vertical menus */ if (cf->menu->vertical) { if (where < 0) where = 0; else if (where > (int) cf->menu->nitems - 1) where = cf->menu->nitems - 1; } else { /* continue from begin/end */ if (where < 0) where = cf->menu->nitems - 1; else if ((size_t) where > cf->menu->nitems - 1) where = 0; } if ((size_t) where == cf->menu->idx) return; ui_clean_info(); old = cf->menu->idx; cf->menu->idx = where; if (!menuitem_on_page(cf, old)) { ui_draw_menu(cf); return; } d = menu_get_menuitem(cf, old); ui_draw_menuitem(cf, d, old); d = menu_get_menuitem(cf, where); ui_draw_menuitem(cf, d, where); } static int ui_menu_move(struct cfdisk *cf, int key) { struct cfdisk_menu *m; assert(cf); assert(cf->menu); if (key == ERR) return 0; /* ignore errors */ m = cf->menu; DBG(MENU, ul_debug("menu move key >%c<.", key)); if (m->vertical) { switch (key) { case KEY_DOWN: case '\016': /* ^N */ case 'j': /* Vi-like alternative */ ui_menu_goto(cf, m->idx + 1); return 0; case KEY_UP: case '\020': /* ^P */ case 'k': /* Vi-like alternative */ ui_menu_goto(cf, (int) m->idx - 1); return 0; case KEY_PPAGE: if (m->page_sz) { ui_menu_goto(cf, (int) m->idx - m->page_sz); return 0; } /* fallthrough */ case KEY_HOME: ui_menu_goto(cf, 0); return 0; case KEY_NPAGE: if (m->page_sz) { ui_menu_goto(cf, m->idx + m->page_sz); return 0; } /* fallthrough */ case KEY_END: ui_menu_goto(cf, m->nitems); return 0; } } else { switch (key) { case KEY_RIGHT: case '\t': ui_menu_goto(cf, m->idx + 1); return 0; case KEY_LEFT: #ifdef KEY_BTAB case KEY_BTAB: #endif ui_menu_goto(cf, (int) m->idx - 1); return 0; } } return 1; /* key irrelevant for menu move */ } /* but don't call me from ui_run(), this is for pop-up menus only */ static void ui_menu_resize(struct cfdisk *cf) { resize(); ui_clean_menu(cf); menu_refresh_size(cf); ui_draw_menu(cf); refresh(); } static int partition_on_page(struct cfdisk *cf, size_t i) { if (cf->page_sz == 0 || cf->lines_idx / cf->page_sz == i / cf->page_sz) return 1; return 0; } static void ui_draw_partition(struct cfdisk *cf, size_t i) { int ln = TABLE_START_LINE + 1 + i; /* skip table header */ int cl = ARROW_CURSOR_WIDTH; /* we need extra space for cursor */ int cur = cf->lines_idx == i; size_t curpg = 0; if (cf->page_sz) { if (!partition_on_page(cf, i)) return; ln = TABLE_START_LINE + (i % cf->page_sz) + 1; curpg = cf->lines_idx / cf->page_sz; } DBG(UI, ul_debug( "draw partition %zu [page_sz=%zu, " "line=%d, idx=%zu]", i, cf->page_sz, ln, cf->lines_idx)); if (cur) { attron(A_REVERSE); mvaddstr(ln, 0, ARROW_CURSOR_STRING); mvaddstr(ln, cl, cf->lines[i + 1].data); attroff(A_REVERSE); } else { int at = 0; if (colors_wanted() && is_freespace(cf, i)) { attron(COLOR_PAIR(CFDISK_CL_FREESPACE)); at = 1; } mvaddstr(ln, 0, ARROW_CURSOR_DUMMY); mvaddstr(ln, cl, cf->lines[i + 1].data); if (at) attroff(COLOR_PAIR(CFDISK_CL_FREESPACE)); } if ((size_t) ln == MENU_START_LINE - 1 && cf->page_sz && curpg < cf->nlines / cf->page_sz) { if (cur) attron(A_REVERSE); mvaddch(ln, ui_cols - 1, ACS_DARROW); mvaddch(ln, 0, ACS_DARROW); if (cur) attroff(A_REVERSE); } } static int ui_draw_table(struct cfdisk *cf) { int cl = ARROW_CURSOR_WIDTH; size_t i, nparts = fdisk_table_get_nents(cf->table); size_t curpg = cf->page_sz ? cf->lines_idx / cf->page_sz : 0; DBG(UI, ul_debug("draw table")); for (i = TABLE_START_LINE; i <= TABLE_START_LINE + cf->page_sz; i++) { move(i, 0); clrtoeol(); } if (nparts == 0 || (size_t) cf->lines_idx > nparts - 1) cf->lines_idx = nparts ? nparts - 1 : 0; /* print header */ attron(A_BOLD); mvaddstr(TABLE_START_LINE, cl, cf->lines[0].data); attroff(A_BOLD); /* print partitions */ for (i = 0; i < nparts; i++) ui_draw_partition(cf, i); if (curpg != 0) { mvaddch(TABLE_START_LINE, ui_cols - 1, ACS_UARROW); mvaddch(TABLE_START_LINE, 0, ACS_UARROW); } if (cf->page_sz && curpg < cf->nlines / cf->page_sz) { mvaddch(MENU_START_LINE - 1, ui_cols - 1, ACS_DARROW); mvaddch(MENU_START_LINE - 1, 0, ACS_DARROW); } return 0; } static int ui_table_goto(struct cfdisk *cf, int where) { size_t old; size_t nparts = fdisk_table_get_nents(cf->table); DBG(UI, ul_debug("goto table %d", where)); if (where < 0) where = 0; else if ((size_t) where > nparts - 1) where = nparts - 1; if ((size_t) where == cf->lines_idx) return 0; old = cf->lines_idx; cf->lines_idx = where; if (!partition_on_page(cf, old) ||!partition_on_page(cf, where)) ui_draw_table(cf); else { ui_draw_partition(cf, old); /* cleanup old */ ui_draw_partition(cf, where); /* draw new */ } ui_clean_info(); ui_draw_menu(cf); ui_draw_extra(cf); refresh(); return 0; } static int ui_refresh(struct cfdisk *cf) { struct fdisk_label *lb; char *id = NULL; uint64_t bytes = fdisk_get_nsectors(cf->cxt) * fdisk_get_sector_size(cf->cxt); char *strsz; if (!ui_enabled) return -EINVAL; strsz = size_to_human_string(SIZE_SUFFIX_SPACE | SIZE_SUFFIX_3LETTER, bytes); lb = fdisk_get_label(cf->cxt, NULL); assert(lb); clear(); /* header */ attron(A_BOLD); ui_center(0, _("Disk: %s"), fdisk_get_devname(cf->cxt)); attroff(A_BOLD); ui_center(1, _("Size: %s, %ju bytes, %ju sectors"), strsz, bytes, (uintmax_t) fdisk_get_nsectors(cf->cxt)); if (fdisk_get_disklabel_id(cf->cxt, &id) == 0 && id) ui_center(2, _("Label: %s, identifier: %s"), fdisk_label_get_name(lb), id); else ui_center(2, _("Label: %s"), fdisk_label_get_name(lb)); free(strsz); ui_draw_table(cf); ui_draw_menu(cf); refresh(); return 0; } static ssize_t ui_get_string(const char *prompt, const char *hint, char *buf, size_t len) { size_t cells = 0; ssize_t i = 0, rc = -1; int ln = MENU_START_LINE, cl = 1; assert(buf); assert(len); move(ln, 0); clrtoeol(); move(ln + 1, 0); clrtoeol(); if (prompt) { mvaddstr(ln, cl, (char *) prompt); cl += mbs_safe_width(prompt); } /* default value */ if (*buf) { i = strlen(buf); cells = mbs_safe_width(buf); mvaddstr(ln, cl, buf); } if (hint) ui_hint(hint); else ui_clean_hint(); move(ln, cl + cells); curs_set(1); refresh(); while (1) { #if !defined(HAVE_SLCURSES_H) && !defined(HAVE_SLANG_SLCURSES_H) && \ defined(HAVE_LIBNCURSESW) && defined(HAVE_WIDECHAR) wint_t c; if (get_wch(&c) == ERR) { #else int c; if ((c = getch()) == ERR) { #endif if (ui_resize) { resize(); continue; } if (!isatty(STDIN_FILENO)) exit(2); else goto done; } if (c == '\r' || c == '\n' || c == KEY_ENTER) break; switch (c) { case KEY_ESC: rc = -CFDISK_ERR_ESC; goto done; case KEY_LEFT: /* TODO: implement full buffer editor */ case KEY_RIGHT: case KEY_END: case KEY_HOME: case KEY_UP: case KEY_DOWN: beep(); break; case KEY_DELETE: case '\b': case KEY_BACKSPACE: if (i > 0) { cells--; i = mbs_truncate(buf, &cells); if (i < 0) goto done; mvaddch(ln, cl + cells, ' '); move(ln, cl + cells); } else beep(); break; default: #if defined(HAVE_LIBNCURSESW) && defined(HAVE_WIDECHAR) if (i + 1 < (ssize_t) len && iswprint(c)) { wchar_t wc = (wchar_t) c; char s[MB_CUR_MAX + 1]; int sz = wctomb(s, wc); if (sz > 0 && sz + i < (ssize_t) len) { s[sz] = '\0'; mvaddnstr(ln, cl + cells, s, sz); memcpy(buf + i, s, sz); i += sz; buf[i] = '\0'; cells += wcwidth(wc); } else beep(); } #else if (i + 1 < (ssize_t) len && isprint(c)) { mvaddch(ln, cl + cells, c); buf[i++] = c; buf[i] = '\0'; cells++; } #endif else beep(); } refresh(); } rc = i; /* success */ done: move(ln, 0); clrtoeol(); curs_set(0); refresh(); return rc; } /* @res is default value as well as result in bytes */ static int ui_get_size(struct cfdisk *cf, const char *prompt, uintmax_t *res, uintmax_t low, uintmax_t up, int *expsize) { char buf[128]; uintmax_t user = 0; ssize_t rc; char *dflt = size_to_human_string(0, *res); DBG(UI, ul_debug("get_size (default=%ju)", *res)); ui_clean_info(); snprintf(buf, sizeof(buf), "%s", dflt); do { int pwr = 0, insec = 0; rc = ui_get_string(prompt, _("May be followed by M for MiB, G for GiB, " "T for TiB, or S for sectors."), buf, sizeof(buf)); ui_clean_warn(); if (rc == 0) { ui_warnx(_("Please, specify size.")); continue; /* nothing specified */ } else if (rc == -CFDISK_ERR_ESC) break; /* cancel dialog */ if (strcmp(buf, dflt) == 0) user = *res, rc = 0; /* no change, use default */ else { size_t len = strlen(buf); if (buf[len - 1] == 'S' || buf[len - 1] == 's') { insec = 1; buf[len - 1] = '\0'; } rc = parse_size(buf, &user, &pwr); /* parse */ } if (rc == 0) { DBG(UI, ul_debug("get_size user=%ju, power=%d, sectors=%s", user, pwr, insec ? "yes" : "no")); if (insec) user *= fdisk_get_sector_size(cf->cxt); if (user < low) { ui_warnx(_("Minimum size is %ju bytes."), low); rc = -ERANGE; } if (user > up && pwr && user < up + (1ULL << pwr * 10)) /* ignore when the user specified size overflow * with in range specified by suffix (e.g. MiB) */ user = up; if (user > up) { ui_warnx(_("Maximum size is %ju bytes."), up); rc = -ERANGE; } if (rc == 0 && insec && expsize) *expsize = 1; } else ui_warnx(_("Failed to parse size.")); } while (rc != 0); if (rc == 0) *res = user; free(dflt); DBG(UI, ul_debug("get_size (result=%ju, rc=%zd)", *res, rc)); return rc; } static struct fdisk_parttype *ui_get_parttype(struct cfdisk *cf, struct fdisk_parttype *cur) { struct cfdisk_menuitem *d, *cm; size_t i = 0, nitems, idx = 0; struct fdisk_parttype *t = NULL; struct fdisk_label *lb; int codetypes = 0; DBG(UI, ul_debug("asking for parttype.")); lb = fdisk_get_label(cf->cxt, NULL); /* create cfdisk menu according to label types, note that the * last cm[] item has to be empty -- so nitems + 1 */ nitems = fdisk_label_get_nparttypes(lb); if (!nitems) return NULL; cm = xcalloc(nitems + 1, sizeof(struct cfdisk_menuitem)); if (!cm) return NULL; codetypes = fdisk_label_has_code_parttypes(lb); for (i = 0; i < nitems; i++) { const struct fdisk_parttype *x = fdisk_label_get_parttype(lb, i); char *name; cm[i].userdata = (void *) x; if (codetypes) xasprintf(&name, "%2x %s", fdisk_parttype_get_code(x), _(fdisk_parttype_get_name(x))); else { name = (char *) _(fdisk_parttype_get_name(x)); cm[i].desc = fdisk_parttype_get_string(x); } cm[i].name = name; if (x == cur) idx = i; } /* make the new menu active */ menu_push(cf, cm); cf->menu->vertical = 1; cf->menu->idx = idx; menu_set_title(cf->menu, _("Select partition type")); ui_draw_menu(cf); refresh(); do { int key = getch(); if (ui_resize) ui_menu_resize(cf); if (ui_menu_move(cf, key) == 0) continue; switch (key) { case KEY_ENTER: case '\n': case '\r': d = menu_get_menuitem(cf, cf->menu->idx); if (d) t = (struct fdisk_parttype *) d->userdata; goto done; case KEY_ESC: case 'q': case 'Q': goto done; } } while (1); done: menu_pop(cf); if (codetypes) { for (i = 0; i < nitems; i++) free((char *) cm[i].name); } free(cm); DBG(UI, ul_debug("get parrtype done [type=%s] ", t ? fdisk_parttype_get_name(t) : NULL)); return t; } static int ui_script_read(struct cfdisk *cf) { struct fdisk_script *sc = NULL; char buf[PATH_MAX] = { 0 }; int rc; erase(); rc = ui_get_string( _("Enter script file name: "), _("The script file will be applied to in-memory partition table."), buf, sizeof(buf)); if (rc <= 0) return rc; rc = -1; errno = 0; sc = fdisk_new_script_from_file(cf->cxt, buf); if (!sc && errno) ui_warn(_("Cannot open %s"), buf); else if (!sc) ui_warnx(_("Failed to parse script file %s"), buf); else if (fdisk_apply_script(cf->cxt, sc) != 0) ui_warnx(_("Failed to apply script %s"), buf); else rc = 0; ui_clean_hint(); fdisk_unref_script(sc); return rc; } static int ui_script_write(struct cfdisk *cf) { struct fdisk_script *sc = NULL; char buf[PATH_MAX] = { 0 }; FILE *f = NULL; int rc; rc = ui_get_string( _("Enter script file name: "), _("The current in-memory partition table will be dumped to the file."), buf, sizeof(buf)); if (rc <= 0) return rc; rc = 0; sc = fdisk_new_script(cf->cxt); if (!sc) { ui_warn(_("Failed to allocate script handler")); goto done; } rc = fdisk_script_read_context(sc, NULL); if (rc) { ui_warnx(_("Failed to read disk layout into script.")); goto done; } DBG(UI, ul_debug("writing dump into: '%s'", buf)); f = fopen(buf, "w"); if (!f) { ui_warn(_("Cannot open %s"), buf); rc = -errno; goto done; } rc = fdisk_script_write_file(sc, f); if (!rc) ui_info(_("Disk layout successfully dumped.")); done: if (rc) ui_warn(_("Failed to write script %s"), buf); if (f) fclose(f); fdisk_unref_script(sc); return rc; } /* prints menu with libfdisk labels and waits for users response */ static int ui_create_label(struct cfdisk *cf) { struct cfdisk_menuitem *d, *cm; int rc = 1, refresh_menu = 1; size_t i = 0, nitems; struct fdisk_label *lb = NULL; assert(cf); DBG(UI, ul_debug("asking for new disklabe.")); /* create cfdisk menu according to libfdisk labels, note that the * last cm[] item has to be empty -- so nitems + 1 */ nitems = fdisk_get_nlabels(cf->cxt); cm = xcalloc(nitems + 1, sizeof(struct cfdisk_menuitem)); while (fdisk_next_label(cf->cxt, &lb) == 0) { if (fdisk_label_is_disabled(lb) || fdisk_label_get_type(lb) == FDISK_DISKLABEL_BSD) continue; cm[i++].name = fdisk_label_get_name(lb); } erase(); /* make the new menu active */ menu_push(cf, cm); cf->menu->vertical = 1; menu_set_title(cf->menu, _("Select label type")); if (!cf->zero_start) ui_info(_("Device does not contain a recognized partition table.")); do { if (refresh_menu) { ui_draw_menu(cf); ui_hint(_("Select a type to create a new label or press 'L' to load script file.")); refresh(); refresh_menu = 0; } int key = getch(); if (ui_resize) ui_menu_resize(cf); if (ui_menu_move(cf, key) == 0) continue; switch (key) { case KEY_ENTER: case '\n': case '\r': d = menu_get_menuitem(cf, cf->menu->idx); if (d) rc = fdisk_create_disklabel(cf->cxt, d->name); goto done; case KEY_ESC: case 'q': case 'Q': goto done; case 'l': case 'L': rc = ui_script_read(cf); if (rc == 0) goto done; refresh_menu = 1; break; } } while (1); done: menu_pop(cf); free(cm); DBG(UI, ul_debug("create label done [rc=%d] ", rc)); return rc; } static int ui_help(void) { size_t i; static const char *help[] = { N_("This is cfdisk, a curses-based disk partitioning program."), N_("It lets you create, delete, and modify partitions on a block device."), " ", N_("Command Meaning"), N_("------- -------"), N_(" b Toggle bootable flag of the current partition"), N_(" d Delete the current partition"), N_(" h Print this screen"), N_(" n Create new partition from free space"), N_(" q Quit program without writing partition table"), N_(" s Fix partitions order (only when in disarray)"), N_(" t Change the partition type"), N_(" u Dump disk layout to sfdisk compatible script file"), N_(" W Write partition table to disk (you must enter uppercase W);"), N_(" since this might destroy data on the disk, you must either"), N_(" confirm or deny the write by entering 'yes' or 'no'"), N_(" x Display/hide extra information about a partition"), N_("Up Arrow Move cursor to the previous partition"), N_("Down Arrow Move cursor to the next partition"), N_("Left Arrow Move cursor to the previous menu item"), N_("Right Arrow Move cursor to the next menu item"), " ", N_("Note: All of the commands can be entered with either upper or lower"), N_("case letters (except for Write)."), " ", N_("Use lsblk(8) or partx(8) to see more details about the device.") }; erase(); for (i = 0; i < ARRAY_SIZE(help); i++) mvaddstr(i, 1, _(help[i])); ui_info(_("Press a key to continue.")); getch(); return 0; } /* TODO: use @sz, now 128bytes */ static int main_menu_ignore_keys(struct cfdisk *cf, char *ignore, size_t sz __attribute__((__unused__))) { struct fdisk_partition *pa = get_current_partition(cf); size_t i = 0; if (!pa) return 0; if (fdisk_partition_is_freespace(pa)) { ignore[i++] = 'd'; /* delete */ ignore[i++] = 't'; /* set type */ ignore[i++] = 'b'; /* set bootable */ cf->menu->prefkey = 'n'; } else { cf->menu->prefkey = 'q'; ignore[i++] = 'n'; if (!fdisk_is_label(cf->cxt, DOS) && !fdisk_is_label(cf->cxt, SGI)) ignore[i++] = 'b'; } if (!cf->wrong_order) ignore[i++] = 's'; if (fdisk_is_readonly(cf->cxt)) ignore[i++] = 'W'; return i; } /* returns: error: < 0, success: 0, quit: 1 */ static int main_menu_action(struct cfdisk *cf, int key) { size_t n; int ref = 0, rc, org_order = cf->wrong_order; const char *info = NULL, *warn = NULL; struct fdisk_partition *pa; assert(cf); assert(cf->cxt); assert(cf->menu); if (key == 0) { struct cfdisk_menuitem *d = menu_get_menuitem(cf, cf->menu->idx); if (!d) return 0; key = d->key; } else if (key != 'w' && key != 'W') key = tolower(key); /* case insensitive except 'W'rite */ DBG(MENU, ul_debug("main menu action: key=%c", key)); if (cf->menu->ignore && strchr(cf->menu->ignore, key)) { DBG(MENU, ul_debug(" ignore '%c'", key)); return 0; } pa = get_current_partition(cf); if (!pa) return -EINVAL; n = fdisk_partition_get_partno(pa); DBG(MENU, ul_debug("menu action on %p", pa)); ui_clean_hint(); ui_clean_info(); switch (key) { case 'b': /* Bootable flag */ { int fl = fdisk_is_label(cf->cxt, DOS) ? DOS_FLAG_ACTIVE : fdisk_is_label(cf->cxt, SGI) ? SGI_FLAG_BOOT : 0; if (fl && fdisk_toggle_partition_flag(cf->cxt, n, fl)) warn = _("Could not toggle the flag."); else if (fl) ref = 1; break; } #ifdef KEY_DC case KEY_DC: #endif case 'd': /* Delete */ if (fdisk_delete_partition(cf->cxt, n) != 0) warn = _("Could not delete partition %zu."); else info = _("Partition %zu has been deleted."); ref = 1; break; case 'h': /* Help */ case '?': ui_help(); ref = 1; break; case 'n': /* New */ { uint64_t start, size, dflt_size, secs; struct fdisk_partition *npa; /* the new partition */ int expsize = 0; /* size specified explicitly in sectors */ if (!fdisk_partition_is_freespace(pa) || !fdisk_partition_has_start(pa)) return -EINVAL; /* free space range */ start = fdisk_partition_get_start(pa); size = dflt_size = fdisk_partition_get_size(pa) * fdisk_get_sector_size(cf->cxt); if (ui_get_size(cf, _("Partition size: "), &size, fdisk_get_sector_size(cf->cxt), size, &expsize) == -CFDISK_ERR_ESC) break; secs = size / fdisk_get_sector_size(cf->cxt); npa = fdisk_new_partition(); if (!npa) return -ENOMEM; if (dflt_size == size) /* default is to fillin all free space */ fdisk_partition_end_follow_default(npa, 1); else fdisk_partition_set_size(npa, secs); if (expsize) fdisk_partition_size_explicit(pa, 1); fdisk_partition_set_start(npa, start); fdisk_partition_partno_follow_default(npa, 1); /* add to disk label -- libfdisk will ask for missing details */ rc = fdisk_add_partition(cf->cxt, npa, NULL); fdisk_unref_partition(npa); if (rc == 0) ref = 1; break; } case 'q': /* Quit */ return 1; case 't': /* Type */ { struct fdisk_parttype *t; if (fdisk_partition_is_freespace(pa)) return -EINVAL; t = (struct fdisk_parttype *) fdisk_partition_get_type(pa); t = ui_get_parttype(cf, t); ref = 1; if (t && fdisk_set_partition_type(cf->cxt, n, t) == 0) info = _("Changed type of partition %zu."); else info = _("The type of partition %zu is unchanged."); break; } case 's': /* Sort */ if (cf->wrong_order) { fdisk_reorder_partitions(cf->cxt); ref = 1; } break; case 'u': /* dUmp */ ui_script_write(cf); break; case 'W': /* Write */ { char buf[64] = { 0 }; if (fdisk_is_readonly(cf->cxt)) { warn = _("Device is open in read-only mode."); break; } rc = ui_get_string( _("Are you sure you want to write the partition " "table to disk? "), _("Type \"yes\" or \"no\", or press ESC to leave this dialog."), buf, sizeof(buf)); ref = 1; if (rc <= 0 || (strcasecmp(buf, "yes") != 0 && strcasecmp(buf, _("yes")) != 0)) { info = _("Did not write partition table to disk."); break; } rc = fdisk_write_disklabel(cf->cxt); if (rc) warn = _("Failed to write disklabel."); else { fdisk_reread_partition_table(cf->cxt); info = _("The partition table has been altered."); } cf->nwrites++; break; } default: break; } if (ref) { lines_refresh(cf); ui_refresh(cf); ui_draw_extra(cf); } else ui_draw_menu(cf); ui_clean_hint(); if (warn) ui_warnx(warn, n + 1); else if (info) ui_info(info, n + 1); else if (key == 'n' && cf->wrong_order && org_order == 0) ui_info(_("Note that partition table entries are not in disk order now.")); return 0; } static void ui_resize_refresh(struct cfdisk *cf) { resize(); menu_refresh_size(cf); lines_refresh(cf); ui_refresh(cf); ui_draw_extra(cf); } static void toggle_show_extra(struct cfdisk *cf) { if (cf->show_extra && cf->act_win) { wclear(cf->act_win); touchwin(stdscr); } cf->show_extra = cf->show_extra ? 0 : 1; if (cf->show_extra) ui_draw_extra(cf); DBG(MENU, ul_debug("extra: %s", cf->show_extra ? "ENABLED" : "DISABLED" )); } static int ui_run(struct cfdisk *cf) { int rc = 0; ui_lines = LINES; ui_cols = COLS; DBG(UI, ul_debug("start cols=%zu, lines=%zu", ui_cols, ui_lines)); if (fdisk_get_collision(cf->cxt)) { ui_warnx(_("Device already contains a %s signature; it will be removed by a write command."), fdisk_get_collision(cf->cxt)); fdisk_enable_wipe(cf->cxt, 1); ui_hint(_("Press a key to continue.")); getch(); } if (!fdisk_has_label(cf->cxt) || cf->zero_start) { rc = ui_create_label(cf); if (rc < 0) ui_errx(EXIT_FAILURE, _("failed to create a new disklabel")); if (rc) return rc; } cols_init(cf); rc = lines_refresh(cf); if (rc) ui_errx(EXIT_FAILURE, _("failed to read partitions")); menu_push(cf, main_menuitems); cf->menu->ignore_cb = main_menu_ignore_keys; rc = ui_refresh(cf); if (rc) return rc; cf->show_extra = 1; ui_draw_extra(cf); if (fdisk_is_readonly(cf->cxt)) ui_warnx(_("Device is open in read-only mode.")); else if (cf->wrong_order) ui_info(_("Note that partition table entries are not in disk order now.")); do { int key = getch(); rc = 0; if (ui_resize) /* Note that ncurses getch() returns ERR when interrupted * by signal, but SLang does not interrupt at all. */ ui_resize_refresh(cf); if (key == ERR) continue; if (ui_menu_move(cf, key) == 0) continue; DBG(UI, ul_debug("main action key >%c<.", key)); switch (key) { case KEY_DOWN: case '\016': /* ^N */ case 'j': /* Vi-like alternative */ ui_table_goto(cf, cf->lines_idx + 1); break; case KEY_UP: case '\020': /* ^P */ case 'k': /* Vi-like alternative */ ui_table_goto(cf, (int) cf->lines_idx - 1); break; case KEY_PPAGE: if (cf->page_sz) { ui_table_goto(cf, (int) cf->lines_idx - cf->page_sz); break; } case KEY_HOME: ui_table_goto(cf, 0); break; case KEY_NPAGE: if (cf->page_sz) { ui_table_goto(cf, cf->lines_idx + cf->page_sz); break; } case KEY_END: ui_table_goto(cf, (int) cf->nlines - 1); break; case KEY_ENTER: case '\n': case '\r': rc = main_menu_action(cf, 0); break; case 'X': case 'x': /* Extra */ toggle_show_extra(cf); break; default: rc = main_menu_action(cf, key); if (rc < 0) beep(); break; } if (rc == 1) break; /* quit */ } while (1); menu_pop(cf); DBG(UI, ul_debug("end")); return 0; } static void __attribute__ ((__noreturn__)) usage(FILE *out) { fputs(USAGE_HEADER, out); fprintf(out, _(" %1$s [options] \n"), program_invocation_short_name); fputs(USAGE_SEPARATOR, out); fputs(_("Display or manipulate a disk partition table.\n"), out); fputs(USAGE_OPTIONS, out); fputs(_(" -L, --color[=] colorize output (auto, always or never)\n"), out); fprintf(out, " %s\n", USAGE_COLORS_DEFAULT); fputs(_(" -z, --zero start with zeroed partition table\n"), out); fputs(USAGE_SEPARATOR, out); fputs(USAGE_HELP, out); fputs(USAGE_VERSION, out); fprintf(out, USAGE_MAN_TAIL("cfdisk(8)")); exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); } int main(int argc, char *argv[]) { const char *diskpath; int rc, c, colormode = UL_COLORMODE_UNDEF; struct cfdisk _cf = { .lines_idx = 0 }, *cf = &_cf; static const struct option longopts[] = { { "color", optional_argument, NULL, 'L' }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "zero", no_argument, NULL, 'z' }, { NULL, 0, 0, 0 }, }; setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); atexit(close_stdout); while((c = getopt_long(argc, argv, "L::hVz", longopts, NULL)) != -1) { switch(c) { case 'h': usage(stdout); break; case 'L': colormode = UL_COLORMODE_AUTO; if (optarg) colormode = colormode_or_err(optarg, _("unsupported color mode")); break; case 'V': printf(UTIL_LINUX_VERSION); return EXIT_SUCCESS; case 'z': cf->zero_start = 1; break; } } colors_init(colormode, "cfdisk"); fdisk_init_debug(0); scols_init_debug(0); cfdisk_init_debug(); cf->cxt = fdisk_new_context(); if (!cf->cxt) err(EXIT_FAILURE, _("failed to allocate libfdisk context")); fdisk_set_ask(cf->cxt, ask_callback, (void *) cf); if (optind == argc) diskpath = access(DEFAULT_DEVICE, F_OK) == 0 ? DEFAULT_DEVICE : ALTERNATE_DEVICE; else diskpath = argv[optind]; rc = fdisk_assign_device(cf->cxt, diskpath, 0); if (rc == -EACCES) rc = fdisk_assign_device(cf->cxt, diskpath, 1); if (rc != 0) err(EXIT_FAILURE, _("cannot open %s"), optind == argc ? DEFAULT_DEVICE : diskpath); /* Don't use err(), warn() from this point */ ui_init(cf); ui_run(cf); ui_end(); cfdisk_free_lines(cf); free(cf->linesbuf); fdisk_unref_table(cf->table); #ifdef HAVE_LIBMOUNT mnt_unref_table(cf->fstab); mnt_unref_table(cf->mtab); mnt_unref_cache(cf->mntcache); #endif rc = fdisk_deassign_device(cf->cxt, cf->nwrites == 0); fdisk_unref_context(cf->cxt); DBG(MISC, ul_debug("bye! [rc=%d]", rc)); return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; }