]> git.ipfire.org Git - thirdparty/util-linux.git/blob - disk-utils/cfdisk.c
cfdisk: support UI refresh on ^L
[thirdparty/util-linux.git] / disk-utils / cfdisk.c
1 /*
2 * cfdisk.c - Display or manipulate a disk partition table.
3 *
4 * Copyright (C) 2014-2015 Karel Zak <kzak@redhat.com>
5 * Copyright (C) 1994 Kevin E. Martin (martin@cs.unc.edu)
6 *
7 * The original cfdisk was inspired by the fdisk program
8 * by A. V. Le Blanc (leblanc@mcc.ac.uk.
9 *
10 * cfdisk is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 */
15 #include <stdlib.h>
16 #include <stdio.h>
17 #include <errno.h>
18 #include <signal.h>
19 #include <ctype.h>
20 #include <getopt.h>
21 #include <assert.h>
22 #include <libsmartcols.h>
23 #include <sys/ioctl.h>
24 #include <libfdisk.h>
25
26 #ifdef HAVE_LIBMOUNT
27 # include <libmount.h> /* keep it optional for non-linux systems */
28 #endif
29
30 #ifdef HAVE_SLANG_H
31 # include <slang.h>
32 #elif defined(HAVE_SLANG_SLANG_H)
33 # include <slang/slang.h>
34 #endif
35
36 #ifdef HAVE_SLCURSES_H
37 # include <slcurses.h>
38 #elif defined(HAVE_SLANG_SLCURSES_H)
39 # include <slang/slcurses.h>
40 #elif defined(HAVE_NCURSESW_NCURSES_H) && defined(HAVE_WIDECHAR)
41 # include <ncursesw/ncurses.h>
42 #elif defined(HAVE_NCURSES_H)
43 # include <ncurses.h>
44 #elif defined(HAVE_NCURSES_NCURSES_H)
45 # include <ncurses/ncurses.h>
46 #endif
47
48 #ifdef HAVE_WIDECHAR
49 # include <wctype.h>
50 # include <wchar.h>
51 #endif
52
53 #include "c.h"
54 #include "closestream.h"
55 #include "nls.h"
56 #include "strutils.h"
57 #include "xalloc.h"
58 #include "mbsalign.h"
59 #include "colors.h"
60 #include "debug.h"
61 #include "list.h"
62
63 static const char *default_disks[] = {
64 #ifdef __GNU__
65 "/dev/hd0",
66 "/dev/sd0",
67 #elif defined(__FreeBSD__)
68 "/dev/ad0",
69 "/dev/da0",
70 #else
71 "/dev/sda",
72 "/dev/vda",
73 "/dev/hda",
74 #endif
75 };
76
77 #define ARROW_CURSOR_STRING ">> "
78 #define ARROW_CURSOR_DUMMY " "
79 #define ARROW_CURSOR_WIDTH (sizeof(ARROW_CURSOR_STRING) - 1)
80
81 /* vertical menu */
82 #define MENU_V_SPADDING 1 /* space around menu item string */
83
84 /* horizontal menu */
85 #define MENU_H_SPADDING 0 /* space around menu item string */
86 #define MENU_H_BETWEEN 2 /* space between menu items */
87 #define MENU_H_PRESTR "["
88 #define MENU_H_POSTSTR "]"
89
90 #define MENU_TITLE_PADDING 3
91
92 #define MENU_H_PRESTR_SZ (sizeof(MENU_H_PRESTR) - 1)
93 #define MENU_H_POSTSTR_SZ (sizeof(MENU_H_POSTSTR) - 1)
94
95 #define TABLE_START_LINE 4
96 #define MENU_START_LINE (ui_lines - 4) /* The menu maybe use two lines */
97 #define INFO_LINE (ui_lines - 2)
98 #define WARN_LINE INFO_LINE
99 #define HINT_LINE (ui_lines - 1)
100
101 #define CFDISK_ERR_ESC 5000
102
103 #ifndef KEY_ESC
104 # define KEY_ESC '\033'
105 #endif
106 #ifndef KEY_DELETE
107 # define KEY_DELETE '\177'
108 #endif
109
110 /* colors */
111 enum {
112 CFDISK_CL_NONE = 0,
113 CFDISK_CL_WARNING,
114 CFDISK_CL_FREESPACE,
115 CFDISK_CL_INFO
116 };
117 static const int color_pairs[][2] = {
118 /* color foreground, background */
119 [CFDISK_CL_WARNING] = { COLOR_RED, -1 },
120 [CFDISK_CL_FREESPACE] = { COLOR_GREEN, -1 },
121 [CFDISK_CL_INFO] = { COLOR_BLUE, -1 }
122 };
123
124 struct cfdisk;
125
126 static struct cfdisk_menuitem *menu_get_menuitem(struct cfdisk *cf, size_t idx);
127 static struct cfdisk_menuitem *menu_get_menuitem_by_key(struct cfdisk *cf, int key, size_t *idx);
128 static struct cfdisk_menu *menu_push(struct cfdisk *cf, struct cfdisk_menuitem *item);
129 static struct cfdisk_menu *menu_pop(struct cfdisk *cf);
130 static void menu_refresh_size(struct cfdisk *cf);
131
132 static int ui_refresh(struct cfdisk *cf);
133 static void ui_warnx(const char *fmt, ...);
134 static void ui_warn(const char *fmt, ...);
135 static void ui_info(const char *fmt, ...);
136 static void ui_draw_menu(struct cfdisk *cf);
137 static int ui_menu_move(struct cfdisk *cf, int key);
138 static void ui_menu_resize(struct cfdisk *cf);
139
140 static int ui_get_size(struct cfdisk *cf, const char *prompt, uintmax_t *res,
141 uintmax_t low, uintmax_t up, int *expsize);
142
143 static int ui_enabled;
144 static int ui_resize;
145
146 /* ncurses LINES and COLS may be actual variables or *macros*, but we need
147 * something portable and writable */
148 size_t ui_lines;
149 size_t ui_cols;
150
151 /* menu item */
152 struct cfdisk_menuitem {
153 int key; /* keyboard shortcut */
154 const char *name; /* item name */
155 const char *desc; /* item description (hint) */
156 void *userdata;
157 };
158
159 /* menu */
160 struct cfdisk_menu {
161 char *title; /* optional menu title */
162 struct cfdisk_menuitem *items; /* array with menu items */
163 char *ignore;/* string with keys to ignore */
164 size_t width; /* maximal width of the menu item */
165 size_t nitems; /* number of the active menu items */
166 size_t page_sz;/* when menu longer than screen */
167 size_t idx; /* the current menu item */
168 int prefkey;/* preferred menu item */
169 struct cfdisk_menu *prev;
170
171 /* @ignore keys generator */
172 int (*ignore_cb) (struct cfdisk *, char *, size_t);
173
174 unsigned int vertical : 1; /* enable vertical mode */
175 };
176
177 /* main menu */
178 static struct cfdisk_menuitem main_menuitems[] = {
179 { 'b', N_("Bootable"), N_("Toggle bootable flag of the current partition") },
180 { 'd', N_("Delete"), N_("Delete the current partition") },
181 { 'n', N_("New"), N_("Create new partition from free space") },
182 { 'q', N_("Quit"), N_("Quit program without writing changes") },
183 { 't', N_("Type"), N_("Change the partition type") },
184 { 'h', N_("Help"), N_("Print help screen") },
185 { 's', N_("Sort"), N_("Fix partitions order") },
186 { 'W', N_("Write"), N_("Write partition table to disk (this might destroy data)") },
187 { 'u', N_("Dump"), N_("Dump partition table to sfdisk compatible script file") },
188 { 0, NULL, NULL }
189 };
190
191 /* extra partinfo in name:value pairs */
192 struct cfdisk_extra {
193 char *name;
194 char *data;
195
196 struct list_head exs;
197 };
198
199 /* line and extra partinfo list_head */
200 struct cfdisk_line {
201 char *data; /* line data */
202 struct libscols_table *extra; /* extra info ('X') */
203 WINDOW *w; /* window with extra info */
204 };
205
206 /* top level control struct */
207 struct cfdisk {
208 struct fdisk_context *cxt; /* libfdisk context */
209 struct fdisk_table *table; /* partition table */
210
211 struct cfdisk_menu *menu; /* the current menu */
212
213 int *fields; /* output columns IDs */
214 size_t nfields; /* number of columns IDs */
215
216 char *linesbuf; /* table as string */
217 size_t linesbufsz; /* size of the tb_buf */
218
219 struct cfdisk_line *lines; /* list of lines */
220
221 size_t nlines; /* number of lines */
222 size_t lines_idx; /* current line <0..N>, exclude header */
223 size_t page_sz;
224
225 unsigned int nwrites; /* fdisk_write_disklabel() counter */
226
227 WINDOW *act_win; /* the window currently on the screen */
228
229 #ifdef HAVE_LIBMOUNT
230 struct libmnt_table *mtab;
231 struct libmnt_table *fstab;
232 struct libmnt_cache *mntcache;
233 #endif
234 unsigned int wrong_order :1, /* PT not in right order */
235 zero_start :1, /* ignore existing partition table */
236 show_extra :1; /* show extra partinfo */
237 };
238
239
240 /*
241 * let's use include/debug.h stuff for cfdisk too
242 */
243 UL_DEBUG_DEFINE_MASK(cfdisk);
244 UL_DEBUG_DEFINE_MASKNAMES(cfdisk) = UL_DEBUG_EMPTY_MASKNAMES;
245
246 #define CFDISK_DEBUG_INIT (1 << 1)
247 #define CFDISK_DEBUG_UI (1 << 2)
248 #define CFDISK_DEBUG_MENU (1 << 3)
249 #define CFDISK_DEBUG_MISC (1 << 4)
250 #define CFDISK_DEBUG_TABLE (1 << 5)
251 #define CFDISK_DEBUG_ALL 0xFFFF
252
253 #define DBG(m, x) __UL_DBG(cfdisk, CFDISK_DEBUG_, m, x)
254
255 static void cfdisk_init_debug(void)
256 {
257 __UL_INIT_DEBUG(cfdisk, CFDISK_DEBUG_, 0, CFDISK_DEBUG);
258 }
259
260 /* Initialize output columns -- we follow libfdisk fields (usually specific
261 * to the label type.
262 */
263 static int cols_init(struct cfdisk *cf)
264 {
265 assert(cf);
266
267 free(cf->fields);
268 cf->fields = NULL;
269 cf->nfields = 0;
270
271 return fdisk_label_get_fields_ids(NULL, cf->cxt, &cf->fields, &cf->nfields);
272 }
273
274 static void resize(void)
275 {
276 struct winsize ws;
277
278 if (ioctl(fileno(stdout), TIOCGWINSZ, &ws) != -1
279 && ws.ws_row && ws.ws_col) {
280 ui_lines = ws.ws_row;
281 ui_cols = ws.ws_col;
282 #if HAVE_RESIZETERM
283 resizeterm(ws.ws_row, ws.ws_col);
284 #endif
285 clearok(stdscr, TRUE);
286 }
287 touchwin(stdscr);
288
289 DBG(UI, ul_debug("ui: resize refresh ui_cols=%zu, ui_lines=%zu",
290 ui_cols, ui_lines));
291 ui_resize = 0;
292 }
293
294 /* Reads partition in tree-like order from scols
295 */
296 static int partition_from_scols(struct fdisk_table *tb,
297 struct libscols_line *ln)
298 {
299 struct fdisk_partition *pa = scols_line_get_userdata(ln);
300
301 fdisk_table_add_partition(tb, pa);
302 fdisk_unref_partition(pa);
303
304 if (scols_line_has_children(ln)) {
305 struct libscols_line *chln;
306 struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD);
307
308 if (!itr)
309 return -EINVAL;
310 while (scols_line_next_child(ln, itr, &chln) == 0)
311 partition_from_scols(tb, chln);
312 scols_free_iter(itr);
313 }
314 return 0;
315 }
316
317 static char *table_to_string(struct cfdisk *cf, struct fdisk_table *tb)
318 {
319 struct fdisk_partition *pa;
320 struct fdisk_label *lb;
321 struct fdisk_iter *itr = NULL;
322 struct libscols_table *table = NULL;
323 struct libscols_iter *s_itr = NULL;
324 char *res = NULL;
325 size_t i;
326 int tree = 0;
327 struct libscols_line *ln, *ln_cont = NULL;
328
329 DBG(TABLE, ul_debug("convert to string"));
330
331 assert(cf);
332 assert(cf->cxt);
333 assert(cf->fields);
334 assert(tb);
335
336 lb = fdisk_get_label(cf->cxt, NULL);
337 assert(lb);
338
339 itr = fdisk_new_iter(FDISK_ITER_FORWARD);
340 if (!itr)
341 goto done;
342
343 /* get container (e.g. extended partition) */
344 while (fdisk_table_next_partition(tb, itr, &pa) == 0) {
345 if (fdisk_partition_is_nested(pa)) {
346 DBG(TABLE, ul_debug("nested detected, using tree"));
347 tree = SCOLS_FL_TREE;
348 break;
349 }
350 }
351
352 table = scols_new_table();
353 if (!table)
354 goto done;
355 scols_table_enable_maxout(table, 1);
356 scols_table_enable_nowrap(table, 1);
357
358 #if !defined(HAVE_LIBNCURSESW) || !defined(HAVE_WIDECHAR)
359 scols_table_enable_ascii(table, 1);
360 #endif
361
362 /* headers */
363 for (i = 0; i < cf->nfields; i++) {
364 int fl = 0;
365 const struct fdisk_field *field =
366 fdisk_label_get_field(lb, cf->fields[i]);
367 if (!field)
368 continue;
369
370 if (fdisk_field_is_number(field))
371 fl |= SCOLS_FL_RIGHT;
372 if (fdisk_field_get_id(field) == FDISK_FIELD_TYPE)
373 fl |= SCOLS_FL_TRUNC;
374 if (tree && fdisk_field_get_id(field) == FDISK_FIELD_DEVICE)
375 fl |= SCOLS_FL_TREE;
376
377 if (!scols_table_new_column(table,
378 _(fdisk_field_get_name(field)),
379 fdisk_field_get_width(field), fl))
380 goto done;
381 }
382
383 /* data */
384 fdisk_reset_iter(itr, FDISK_ITER_FORWARD);
385
386 while (fdisk_table_next_partition(tb, itr, &pa) == 0) {
387 struct libscols_line *parent = fdisk_partition_is_nested(pa) ? ln_cont : NULL;
388
389 ln = scols_table_new_line(table, parent);
390 if (!ln)
391 goto done;
392 for (i = 0; i < cf->nfields; i++) {
393 char *cdata = NULL;
394
395 if (fdisk_partition_to_string(pa, cf->cxt,
396 cf->fields[i], &cdata))
397 continue;
398 scols_line_refer_data(ln, i, cdata);
399 }
400 if (tree && fdisk_partition_is_container(pa))
401 ln_cont = ln;
402
403 scols_line_set_userdata(ln, (void *) pa);
404 fdisk_ref_partition(pa);
405 }
406
407 if (scols_table_is_empty(table))
408 goto done;
409
410 scols_table_reduce_termwidth(table, ARROW_CURSOR_WIDTH);
411 scols_print_table_to_string(table, &res);
412
413 /* scols_* code might reorder lines, let's reorder @tb according to the
414 * final output (it's no problem because partitions are addressed by
415 * parno stored within struct fdisk_partition) */
416
417 /* remove all */
418 fdisk_reset_iter(itr, FDISK_ITER_FORWARD);
419 while (fdisk_table_next_partition(tb, itr, &pa) == 0)
420 fdisk_table_remove_partition(tb, pa);
421
422 s_itr = scols_new_iter(SCOLS_ITER_FORWARD);
423 if (!s_itr)
424 goto done;
425
426 /* add all in the right order (don't forget the output is tree) */
427 while (scols_table_next_line(table, s_itr, &ln) == 0) {
428 if (scols_line_get_parent(ln))
429 continue;
430 if (partition_from_scols(tb, ln))
431 break;
432 }
433 done:
434 scols_unref_table(table);
435 scols_free_iter(s_itr);
436 fdisk_free_iter(itr);
437
438 return res;
439 }
440
441 static void cfdisk_free_lines(struct cfdisk *cf)
442 {
443 size_t i = 0;
444 while(i < cf->nlines) {
445 scols_unref_table(cf->lines[i].extra);
446
447 DBG(UI, ul_debug("delete window: %p",
448 cf->lines[i].w));
449
450 if (cf->lines[i].w)
451 delwin(cf->lines[i].w);
452 cf->lines[i].w = NULL;
453 ++i;
454 }
455 cf->act_win = NULL;
456 free(cf->lines);
457 cf->lines = NULL;
458 }
459 /*
460 * Read data about partitions from libfdisk and prepare output lines.
461 */
462 static int lines_refresh(struct cfdisk *cf)
463 {
464 int rc;
465 char *p;
466 size_t i;
467
468 assert(cf);
469
470 DBG(TABLE, ul_debug("refreshing buffer"));
471
472 free(cf->linesbuf);
473 cfdisk_free_lines(cf);
474 cf->linesbuf = NULL;
475 cf->linesbufsz = 0;
476 cf->lines = NULL;
477 cf->nlines = 0;
478
479 fdisk_unref_table(cf->table);
480 cf->table = NULL;
481
482 /* read partitions and free spaces into cf->table */
483 rc = fdisk_get_partitions(cf->cxt, &cf->table);
484 if (!rc)
485 rc = fdisk_get_freespaces(cf->cxt, &cf->table);
486 if (rc)
487 return rc;
488
489 cf->linesbuf = table_to_string(cf, cf->table);
490 if (!cf->linesbuf)
491 return -ENOMEM;
492
493 cf->linesbufsz = strlen(cf->linesbuf);
494 cf->nlines = fdisk_table_get_nents(cf->table) + 1; /* 1 for header line */
495 cf->page_sz = 0;
496 cf->wrong_order = fdisk_table_wrong_order(cf->table) ? 1 : 0;
497
498 if (MENU_START_LINE - TABLE_START_LINE < cf->nlines)
499 cf->page_sz = MENU_START_LINE - TABLE_START_LINE - 1;
500
501 cf->lines = xcalloc(cf->nlines, sizeof(struct cfdisk_line));
502
503 for (p = cf->linesbuf, i = 0; p && i < cf->nlines; i++) {
504 cf->lines[i].data = p;
505 p = strchr(p, '\n');
506 if (p) {
507 *p = '\0';
508 p++;
509 }
510 cf->lines[i].extra = scols_new_table();
511 scols_table_enable_noheadings(cf->lines[i].extra, 1);
512 scols_table_new_column(cf->lines[i].extra, NULL, 0, SCOLS_FL_RIGHT);
513 scols_table_new_column(cf->lines[i].extra, NULL, 0, SCOLS_FL_TRUNC);
514 }
515
516 return 0;
517 }
518
519 static struct fdisk_partition *get_current_partition(struct cfdisk *cf)
520 {
521 assert(cf);
522 assert(cf->table);
523
524 return fdisk_table_get_partition(cf->table, cf->lines_idx);
525 }
526
527 static int is_freespace(struct cfdisk *cf, size_t i)
528 {
529 struct fdisk_partition *pa;
530
531 assert(cf);
532 assert(cf->table);
533
534 pa = fdisk_table_get_partition(cf->table, i);
535 return fdisk_partition_is_freespace(pa);
536 }
537
538 /* converts libfdisk FDISK_ASKTYPE_MENU to cfdisk menu and returns user's
539 * response back to libfdisk
540 */
541 static int ask_menu(struct fdisk_ask *ask, struct cfdisk *cf)
542 {
543 struct cfdisk_menuitem *d, *cm;
544 int key;
545 size_t i = 0, nitems;
546 const char *name, *desc;
547
548 assert(ask);
549 assert(cf);
550
551 /* create cfdisk menu according to libfdisk ask-menu, note that the
552 * last cm[] item has to be empty -- so nitems + 1 */
553 nitems = fdisk_ask_menu_get_nitems(ask);
554 cm = xcalloc(nitems + 1, sizeof(struct cfdisk_menuitem));
555
556 for (i = 0; i < nitems; i++) {
557 if (fdisk_ask_menu_get_item(ask, i, &key, &name, &desc))
558 break;
559 cm[i].key = key;
560 cm[i].desc = desc;
561 cm[i].name = name;
562 }
563
564 /* make the new menu active */
565 menu_push(cf, cm);
566 ui_draw_menu(cf);
567 refresh();
568
569 /* wait for keys */
570 do {
571 key = getch();
572
573 if (ui_resize)
574 ui_menu_resize(cf);
575 if (ui_menu_move(cf, key) == 0)
576 continue;
577
578 switch (key) {
579 case KEY_ENTER:
580 case '\n':
581 case '\r':
582 d = menu_get_menuitem(cf, cf->menu->idx);
583 if (d)
584 fdisk_ask_menu_set_result(ask, d->key);
585 menu_pop(cf);
586 free(cm);
587 return 0;
588 }
589 } while (1);
590
591 menu_pop(cf);
592 free(cm);
593 return -1;
594 }
595
596 /* libfdisk callback
597 */
598 static int ask_callback(struct fdisk_context *cxt __attribute__((__unused__)),
599 struct fdisk_ask *ask,
600 void *data __attribute__((__unused__)))
601 {
602 int rc = 0;
603
604 assert(ask);
605
606 switch(fdisk_ask_get_type(ask)) {
607 case FDISK_ASKTYPE_INFO:
608 ui_info(fdisk_ask_print_get_mesg(ask));
609 break;
610 case FDISK_ASKTYPE_WARNX:
611 ui_warnx(fdisk_ask_print_get_mesg(ask));
612 break;
613 case FDISK_ASKTYPE_WARN:
614 ui_warn(fdisk_ask_print_get_mesg(ask));
615 break;
616 case FDISK_ASKTYPE_MENU:
617 ask_menu(ask, (struct cfdisk *) data);
618 break;
619 default:
620 ui_warnx(_("internal error: unsupported dialog type %d"),
621 fdisk_ask_get_type(ask));
622 return -EINVAL;
623 }
624 return rc;
625 }
626
627 static int ui_end(void)
628 {
629 if (!ui_enabled)
630 return -EINVAL;
631
632 #if defined(HAVE_SLCURSES_H) || defined(HAVE_SLANG_SLCURSES_H)
633 SLsmg_gotorc(ui_lines - 1, 0);
634 SLsmg_refresh();
635 #else
636 mvcur(0, ui_cols - 1, ui_lines-1, 0);
637 #endif
638 curs_set(1);
639 nl();
640 endwin();
641 printf("\n");
642 ui_enabled = 0;
643 return 0;
644 }
645
646 static void ui_vprint_center(size_t line, int attrs, const char *fmt, va_list ap)
647 {
648 size_t width;
649 char *buf = NULL;
650
651 move(line, 0);
652 clrtoeol();
653
654 xvasprintf(&buf, fmt, ap);
655
656 width = mbs_safe_width(buf);
657 if (width > (size_t) ui_cols) {
658 char *p = strrchr(buf + ui_cols, ' ');
659 if (!p)
660 p = buf + ui_cols;
661 *p = '\0';
662 if (line + 1 >= ui_lines)
663 line--;
664 attron(attrs);
665 mvaddstr(line, 0, buf);
666 mvaddstr(line + 1, 0, p+1);
667 attroff(attrs);
668 } else {
669 attron(attrs);
670 mvaddstr(line, (ui_cols - width) / 2, buf);
671 attroff(attrs);
672 }
673 free(buf);
674 }
675
676 static void ui_center(size_t line, const char *fmt, ...)
677 {
678 va_list ap;
679 va_start(ap, fmt);
680 ui_vprint_center(line, 0, fmt, ap);
681 va_end(ap);
682 }
683
684 static void ui_warnx(const char *fmt, ...)
685 {
686 va_list ap;
687 va_start(ap, fmt);
688 if (ui_enabled)
689 ui_vprint_center(WARN_LINE,
690 colors_wanted() ? COLOR_PAIR(CFDISK_CL_WARNING) : 0,
691 fmt, ap);
692 else {
693 vfprintf(stderr, fmt, ap);
694 fputc('\n', stderr);
695 }
696 va_end(ap);
697 }
698
699 static void ui_warn(const char *fmt, ...)
700 {
701 char *fmt_m;
702 va_list ap;
703
704 xasprintf(&fmt_m, "%s: %m", fmt);
705
706 va_start(ap, fmt);
707 if (ui_enabled)
708 ui_vprint_center(WARN_LINE,
709 colors_wanted() ? COLOR_PAIR(CFDISK_CL_WARNING) : 0,
710 fmt_m, ap);
711 else {
712 vfprintf(stderr, fmt_m, ap);
713 fputc('\n', stderr);
714 }
715 va_end(ap);
716 free(fmt_m);
717 }
718
719 static void ui_clean_warn(void)
720 {
721 move(WARN_LINE, 0);
722 clrtoeol();
723 }
724
725 static int __attribute__((__noreturn__)) ui_errx(int rc, const char *fmt, ...)
726 {
727 va_list ap;
728 ui_end();
729
730 va_start(ap, fmt);
731 fprintf(stderr, "%s: ", program_invocation_short_name);
732 vfprintf(stderr, fmt, ap);
733 fputc('\n', stderr);
734 va_end(ap);
735
736 exit(rc);
737 }
738
739 static void ui_info(const char *fmt, ...)
740 {
741 va_list ap;
742 va_start(ap, fmt);
743 if (ui_enabled)
744 ui_vprint_center(INFO_LINE,
745 colors_wanted() ? COLOR_PAIR(CFDISK_CL_INFO) : 0,
746 fmt, ap);
747 else {
748 vfprintf(stdout, fmt, ap);
749 fputc('\n', stdout);
750 }
751 va_end(ap);
752 }
753
754 static void ui_clean_info(void)
755 {
756 move(INFO_LINE, 0);
757 clrtoeol();
758 }
759
760 static void ui_hint(const char *fmt, ...)
761 {
762 va_list ap;
763 va_start(ap, fmt);
764 if (ui_enabled)
765 ui_vprint_center(HINT_LINE, A_BOLD, fmt, ap);
766 else {
767 vfprintf(stdout, fmt, ap);
768 fputc('\n', stdout);
769 }
770 va_end(ap);
771 }
772
773 static void ui_clean_hint(void)
774 {
775 move(HINT_LINE, 0);
776 clrtoeol();
777 }
778
779 static void die_on_signal(int dummy __attribute__((__unused__)))
780 {
781 DBG(MISC, ul_debug("die on signal."));
782 ui_end();
783 exit(EXIT_FAILURE);
784 }
785
786 static void resize_on_signal(int dummy __attribute__((__unused__)))
787 {
788 DBG(MISC, ul_debug("resize on signal."));
789 ui_resize = 1;
790 }
791
792 static void menu_refresh_size(struct cfdisk *cf)
793 {
794 if (cf->menu && cf->menu->nitems)
795 cf->menu->page_sz = (cf->menu->nitems / (ui_lines - 4)) ? ui_lines - 4 : 0;
796 }
797
798 static void menu_update_ignore(struct cfdisk *cf)
799 {
800 char ignore[128] = { 0 };
801 int i = 0;
802 struct cfdisk_menu *m;
803 struct cfdisk_menuitem *d, *org = NULL;
804 size_t idx;
805
806 assert(cf);
807 assert(cf->menu);
808 assert(cf->menu->ignore_cb);
809
810 m = cf->menu;
811 DBG(MENU, ul_debug("update menu ignored keys"));
812
813 i = m->ignore_cb(cf, ignore, sizeof(ignore));
814 ignore[i] = '\0';
815
816 /* return if no change */
817 if ((!m->ignore && !*ignore)
818 || (m->ignore && *ignore && strcmp(m->ignore, ignore) == 0)) {
819 return;
820 }
821
822 if (!m->prefkey)
823 org = menu_get_menuitem(cf, m->idx);
824
825 free(m->ignore);
826 m->ignore = xstrdup(ignore);
827 m->nitems = 0;
828
829 for (d = m->items; d->name; d++) {
830 if (m->ignore && strchr(m->ignore, d->key))
831 continue;
832 m->nitems++;
833 }
834
835 DBG(MENU, ul_debug("update menu preferred keys"));
836
837 /* refresh menu index to be at the same menuitem or go to the first */
838 if (org && menu_get_menuitem_by_key(cf, org->key, &idx))
839 m->idx = idx;
840 else if (m->prefkey && menu_get_menuitem_by_key(cf, m->prefkey, &idx))
841 m->idx = idx;
842 else
843 m->idx = 0;
844
845 menu_refresh_size(cf);
846 }
847
848 static struct cfdisk_menu *menu_push(
849 struct cfdisk *cf,
850 struct cfdisk_menuitem *items)
851 {
852 struct cfdisk_menu *m = xcalloc(1, sizeof(*m));
853 struct cfdisk_menuitem *d;
854
855 assert(cf);
856
857 DBG(MENU, ul_debug("new menu"));
858
859 m->prev = cf->menu;
860 m->items = items;
861
862 for (d = m->items; d->name; d++) {
863 const char *name = _(d->name);
864 size_t len = mbs_safe_width(name);
865 if (len > m->width)
866 m->width = len;
867 m->nitems++;
868 }
869
870 cf->menu = m;
871
872 menu_refresh_size(cf);
873 return m;
874 }
875
876 static struct cfdisk_menu *menu_pop(struct cfdisk *cf)
877 {
878 struct cfdisk_menu *m = NULL;
879
880 assert(cf);
881
882 DBG(MENU, ul_debug("pop menu"));
883
884 if (cf->menu) {
885 m = cf->menu->prev;
886 free(cf->menu->ignore);
887 free(cf->menu->title);
888 free(cf->menu);
889 }
890 cf->menu = m;
891 return cf->menu;
892 }
893
894 static void menu_set_title(struct cfdisk_menu *m, const char *title)
895 {
896 char *str = NULL;
897
898 if (title) {
899 size_t len = mbs_safe_width(title);
900 if (len + MENU_TITLE_PADDING > m->width)
901 m->width = len + MENU_TITLE_PADDING;
902 str = xstrdup(title);
903 }
904 m->title = str;
905 }
906
907
908 static int ui_init(struct cfdisk *cf __attribute__((__unused__)))
909 {
910 struct sigaction sa;
911
912 DBG(UI, ul_debug("init"));
913
914 /* setup SIGCHLD handler */
915 sigemptyset(&sa.sa_mask);
916 sa.sa_flags = 0;
917 sa.sa_handler = die_on_signal;
918 sigaction(SIGINT, &sa, NULL);
919 sigaction(SIGTERM, &sa, NULL);
920
921 sa.sa_handler = resize_on_signal;
922 sigaction(SIGWINCH, &sa, NULL);
923
924 ui_enabled = 1;
925 initscr();
926
927 #ifdef HAVE_USE_DEFAULT_COLORS
928 if (colors_wanted() && has_colors()) {
929 size_t i;
930
931 start_color();
932 use_default_colors();
933 for (i = 1; i < ARRAY_SIZE(color_pairs); i++) /* yeah, start from 1! */
934 init_pair(i, color_pairs[i][0], color_pairs[i][1]);
935 }
936 #else
937 colors_off();
938 #endif
939
940 cbreak();
941 noecho();
942 nonl();
943 curs_set(0);
944 keypad(stdscr, TRUE);
945
946 return 0;
947 }
948
949 /* "[ string ]" */
950 #define MENU_H_ITEMWIDTH(m) ( MENU_H_PRESTR_SZ \
951 + MENU_H_SPADDING \
952 + (m)->width \
953 + MENU_H_SPADDING \
954 + MENU_H_POSTSTR_SZ)
955
956 #define MENU_V_ITEMWIDTH(m) (MENU_V_SPADDING + (m)->width + MENU_V_SPADDING)
957
958
959 static size_t menuitem_get_line(struct cfdisk *cf, size_t idx)
960 {
961 struct cfdisk_menu *m = cf->menu;
962
963 if (m->vertical) {
964 if (!m->page_sz) /* small menu */
965 return (ui_lines - (cf->menu->nitems + 1)) / 2 + idx;
966 return (idx % m->page_sz) + 1;
967 } else {
968 size_t len = MENU_H_ITEMWIDTH(m) + MENU_H_BETWEEN; /** item width */
969 size_t items = ui_cols / len; /* items per line */
970
971 if (items == 0)
972 return 0;
973 return MENU_START_LINE + ((idx / items));
974 }
975 }
976
977 static int menuitem_get_column(struct cfdisk *cf, size_t idx)
978 {
979 if (cf->menu->vertical) {
980 size_t nc = MENU_V_ITEMWIDTH(cf->menu);
981 if ((size_t) ui_cols <= nc)
982 return 0;
983 return (ui_cols - nc) / 2;
984 } else {
985 size_t len = MENU_H_ITEMWIDTH(cf->menu) + MENU_H_BETWEEN; /* item width */
986 size_t items = ui_cols / len; /* items per line */
987 size_t extra = items < cf->menu->nitems ? /* extra space on line */
988 ui_cols % len : /* - multi-line menu */
989 ui_cols - (cf->menu->nitems * len); /* - one line menu */
990
991 if (items == 0)
992 return 0; /* hmm... no space */
993
994 extra += MENU_H_BETWEEN; /* add padding after last item to extra */
995
996 if (idx < items)
997 return (idx * len) + (extra / 2);
998 return ((idx % items) * len) + (extra / 2);
999 }
1000 }
1001
1002 static int menuitem_on_page(struct cfdisk *cf, size_t idx)
1003 {
1004 struct cfdisk_menu *m = cf->menu;
1005
1006 if (m->page_sz == 0 ||
1007 m->idx / m->page_sz == idx / m->page_sz)
1008 return 1;
1009 return 0;
1010 }
1011
1012 static struct cfdisk_menuitem *menu_get_menuitem(struct cfdisk *cf, size_t idx)
1013 {
1014 struct cfdisk_menuitem *d;
1015 size_t i;
1016
1017 for (i = 0, d = cf->menu->items; d->name; d++) {
1018 if (cf->menu->ignore && strchr(cf->menu->ignore, d->key))
1019 continue;
1020 if (i++ == idx)
1021 return d;
1022 }
1023
1024 return NULL;
1025 }
1026
1027 static struct cfdisk_menuitem *menu_get_menuitem_by_key(struct cfdisk *cf,
1028 int key, size_t *idx)
1029 {
1030 struct cfdisk_menuitem *d;
1031
1032 for (*idx = 0, d = cf->menu->items; d->name; d++) {
1033 if (cf->menu->ignore && strchr(cf->menu->ignore, d->key))
1034 continue;
1035 if (key == d->key)
1036 return d;
1037 (*idx)++;
1038 }
1039
1040 return NULL;
1041 }
1042
1043 static void ui_draw_menuitem(struct cfdisk *cf,
1044 struct cfdisk_menuitem *d,
1045 size_t idx)
1046 {
1047 char buf[80 * MB_CUR_MAX], *ptr = buf;
1048 const char *name;
1049 size_t width;
1050 int ln, cl, vert = cf->menu->vertical;
1051
1052 if (!menuitem_on_page(cf, idx))
1053 return; /* no visible item */
1054 ln = menuitem_get_line(cf, idx);
1055 cl = menuitem_get_column(cf, idx);
1056
1057 /* string width */
1058 if (vert) {
1059 width = cf->menu->width + MENU_V_SPADDING;
1060 memset(ptr, ' ', MENU_V_SPADDING);
1061 ptr += MENU_V_SPADDING;
1062 } else
1063 width = MENU_H_SPADDING + cf->menu->width + MENU_H_SPADDING;
1064
1065 name = _(d->name);
1066 mbsalign(name, ptr, sizeof(buf), &width,
1067 vert ? MBS_ALIGN_LEFT : MBS_ALIGN_CENTER,
1068 0);
1069
1070 DBG(MENU, ul_debug("menuitem: cl=%d, ln=%d, item='%s'",
1071 cl, ln, buf));
1072
1073 if (vert) {
1074 mvaddch(ln, cl - 1, ACS_VLINE);
1075 mvaddch(ln, cl + MENU_V_ITEMWIDTH(cf->menu), ACS_VLINE);
1076 }
1077
1078 if (cf->menu->idx == idx)
1079 standout();
1080
1081 if (vert)
1082 mvprintw(ln, cl, "%s", buf);
1083 else
1084 mvprintw(ln, cl, "%s%s%s", MENU_H_PRESTR, buf, MENU_H_POSTSTR);
1085
1086 if (cf->menu->idx == idx) {
1087 standend();
1088 if (d->desc)
1089 ui_hint(_(d->desc));
1090 }
1091 }
1092
1093 static void ui_clean_menu(struct cfdisk *cf)
1094 {
1095 size_t i;
1096 size_t lastline;
1097 struct cfdisk_menu *m = cf->menu;
1098 size_t ln = menuitem_get_line(cf, 0);
1099
1100 if (m->vertical)
1101 lastline = ln + (m->page_sz ? m->page_sz : m->nitems);
1102 else
1103 lastline = menuitem_get_line(cf, m->nitems);
1104
1105 for (i = ln; i <= lastline; i++) {
1106 move(i, 0);
1107 clrtoeol();
1108 DBG(MENU, ul_debug("clean_menu: line %zu", i));
1109 }
1110 if (m->vertical) {
1111 move(ln - 1, 0);
1112 clrtoeol();
1113 }
1114 ui_clean_hint();
1115 }
1116
1117 static void ui_draw_menu(struct cfdisk *cf)
1118 {
1119 struct cfdisk_menuitem *d;
1120 struct cfdisk_menu *m;
1121 size_t i = 0;
1122 size_t ln = menuitem_get_line(cf, 0);
1123 size_t nlines;
1124
1125 assert(cf);
1126 assert(cf->menu);
1127
1128 DBG(MENU, ul_debug("draw start"));
1129
1130 ui_clean_menu(cf);
1131 m = cf->menu;
1132
1133 if (m->vertical)
1134 nlines = m->page_sz ? m->page_sz : m->nitems;
1135 else
1136 nlines = menuitem_get_line(cf, m->nitems);
1137
1138 if (m->ignore_cb)
1139 menu_update_ignore(cf);
1140 i = 0;
1141 while ((d = menu_get_menuitem(cf, i)))
1142 ui_draw_menuitem(cf, d, i++);
1143
1144 if (m->vertical) {
1145 size_t cl = menuitem_get_column(cf, 0);
1146 size_t curpg = m->page_sz ? m->idx / m->page_sz : 0;
1147
1148 /* corners and horizontal lines */
1149 mvaddch(ln - 1, cl - 1, ACS_ULCORNER);
1150 mvaddch(ln + nlines, cl - 1, ACS_LLCORNER);
1151
1152 for (i = 0; i < MENU_V_ITEMWIDTH(m); i++) {
1153 mvaddch(ln - 1, cl + i, ACS_HLINE);
1154 mvaddch(ln + nlines, cl + i, ACS_HLINE);
1155 }
1156
1157 mvaddch(ln - 1, cl + i, ACS_URCORNER);
1158 mvaddch(ln + nlines, cl + i, ACS_LRCORNER);
1159
1160 /* draw also lines around empty lines on last page */
1161 if (m->page_sz &&
1162 m->nitems / m->page_sz == m->idx / m->page_sz) {
1163 for (i = m->nitems % m->page_sz + 1; i <= m->page_sz; i++) {
1164 mvaddch(i, cl - 1, ACS_VLINE);
1165 mvaddch(i, cl + MENU_V_ITEMWIDTH(m), ACS_VLINE);
1166 }
1167 }
1168 if (m->title) {
1169 attron(A_BOLD);
1170 mvprintw(ln - 1, cl, " %s ", m->title);
1171 attroff(A_BOLD);
1172 }
1173 if (curpg != 0)
1174 mvaddch(ln - 1, cl + MENU_V_ITEMWIDTH(m) - 2, ACS_UARROW);
1175 if (m->page_sz && curpg < m->nitems / m->page_sz)
1176 mvaddch(ln + nlines, cl + MENU_V_ITEMWIDTH(m) - 2, ACS_DARROW);
1177 }
1178
1179 DBG(MENU, ul_debug("draw end."));
1180 }
1181
1182 inline static int extra_insert_pair(struct cfdisk_line *l, const char *name, const char *data)
1183 {
1184 struct libscols_line *lsl;
1185 int rc;
1186
1187 assert(l);
1188 assert(l->extra);
1189
1190 if (!data || !*data)
1191 return 0;
1192
1193 lsl = scols_table_new_line(l->extra, NULL);
1194 if (!lsl)
1195 return -ENOMEM;
1196
1197 rc = scols_line_set_data(lsl, 0, name);
1198 if (!rc)
1199 rc = scols_line_set_data(lsl, 1, data);
1200
1201 return rc;
1202 }
1203
1204 #ifndef HAVE_LIBMOUNT
1205 static char *get_mountpoint( struct cfdisk *cf __attribute__((unused)),
1206 const char *tagname __attribute__((unused)),
1207 const char *tagdata __attribute__((unused)))
1208 {
1209 return NULL;
1210 }
1211 #else
1212 static char *get_mountpoint(struct cfdisk *cf, const char *tagname, const char *tagdata)
1213 {
1214 struct libmnt_fs *fs = NULL;
1215 char *target = NULL;
1216 int mounted = 0;
1217
1218 assert(tagname);
1219 assert(tagdata);
1220
1221 DBG(UI, ul_debug("asking for mountpoint [%s=%s]", tagname, tagdata));
1222
1223 if (!cf->mntcache)
1224 cf->mntcache = mnt_new_cache();
1225
1226 /* 1st try between mounted filesystems */
1227 if (!cf->mtab) {
1228 cf->mtab = mnt_new_table();
1229 if (cf->mtab) {
1230 mnt_table_set_cache(cf->mtab, cf->mntcache);
1231 mnt_table_parse_mtab(cf->mtab, NULL);
1232 }
1233 }
1234
1235 if (cf->mtab)
1236 fs = mnt_table_find_tag(cf->mtab, tagname, tagdata, MNT_ITER_FORWARD);
1237
1238 /* 2nd try fstab */
1239 if (!fs) {
1240 if (!cf->fstab) {
1241 cf->fstab = mnt_new_table();
1242 if (cf->fstab) {
1243 mnt_table_set_cache(cf->fstab, cf->mntcache);
1244 mnt_table_parse_fstab(cf->fstab, NULL);
1245 }
1246 }
1247 if (cf->fstab)
1248 fs = mnt_table_find_tag(cf->fstab, tagname, tagdata, MNT_ITER_FORWARD);
1249 } else
1250 mounted = 1;
1251
1252 if (fs) {
1253 if (mounted)
1254 xasprintf(&target, _("%s (mounted)"), mnt_fs_get_target(fs));
1255 else
1256 target = xstrdup(mnt_fs_get_target(fs));
1257 }
1258
1259 return target;
1260 }
1261 #endif /* HAVE_LIBMOUNT */
1262
1263 static void extra_prepare_data(struct cfdisk *cf)
1264 {
1265 struct fdisk_partition *pa = get_current_partition(cf);
1266 struct cfdisk_line *l = &cf->lines[cf->lines_idx];
1267 char *data = NULL;
1268 char *mountpoint = NULL;
1269
1270 DBG(UI, ul_debug("preparing extra data"));
1271
1272 /* string data should not equal an empty string */
1273 if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_NAME, &data) && data) {
1274 extra_insert_pair(l, _("Partition name:"), data);
1275 if (!mountpoint)
1276 mountpoint = get_mountpoint(cf, "PARTLABEL", data);
1277 free(data);
1278 }
1279
1280 if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_UUID, &data) && data) {
1281 extra_insert_pair(l, _("Partition UUID:"), data);
1282 if (!mountpoint)
1283 mountpoint = get_mountpoint(cf, "PARTUUID", data);
1284 free(data);
1285 }
1286
1287 if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_TYPE, &data) && data) {
1288 char *code = NULL, *type = NULL;
1289
1290 fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_TYPEID, &code);
1291 xasprintf(&type, "%s (%s)", data, code);
1292
1293 extra_insert_pair(l, _("Partition type:"), type);
1294 free(data);
1295 free(code);
1296 free(type);
1297 }
1298
1299 if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_ATTR, &data) && data) {
1300 extra_insert_pair(l, _("Attributes:"), data);
1301 free(data);
1302 }
1303
1304 /* for numeric data, only show non-zero rows */
1305 if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_BSIZE, &data) && data) {
1306 if (atoi(data))
1307 extra_insert_pair(l, "BSIZE:", data);
1308 free(data);
1309 }
1310
1311 if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_CPG, &data) && data) {
1312 if (atoi(data))
1313 extra_insert_pair(l, "CPG:", data);
1314 free(data);
1315 }
1316
1317 if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_FSIZE, &data) && data) {
1318 if (atoi(data))
1319 extra_insert_pair(l, "FSIZE:", data);
1320 free(data);
1321 }
1322
1323 if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_FSUUID, &data) && data) {
1324 extra_insert_pair(l, _("Filesystem UUID:"), data);
1325 if (!mountpoint)
1326 mountpoint = get_mountpoint(cf, "UUID", data);
1327 free(data);
1328 }
1329
1330 if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_FSLABEL, &data) && data) {
1331 extra_insert_pair(l, _("Filesystem LABEL:"), data);
1332 if (!mountpoint)
1333 mountpoint = get_mountpoint(cf, "LABEL", data);
1334 free(data);
1335 }
1336 if (!fdisk_partition_to_string(pa, cf->cxt, FDISK_FIELD_FSTYPE, &data) && data) {
1337 extra_insert_pair(l, _("Filesystem:"), data);
1338 free(data);
1339 }
1340
1341 if (mountpoint) {
1342 extra_insert_pair(l, _("Mountpoint:"), mountpoint);
1343 free(mountpoint);
1344 }
1345 }
1346
1347 static int ui_draw_extra(struct cfdisk *cf)
1348 {
1349 WINDOW *win_ex;
1350 int wline = 1;
1351 struct cfdisk_line *ln = &cf->lines[cf->lines_idx];
1352 char *tbstr = NULL, *end;
1353 int win_ex_start_line, win_height, tblen;
1354 int ndatalines;
1355
1356 if (!cf->show_extra)
1357 return 0;
1358
1359 DBG(UI, ul_debug("draw extra"));
1360
1361 assert(ln->extra);
1362
1363 if (cf->act_win) {
1364 wclear(cf->act_win);
1365 touchwin(stdscr);
1366 }
1367
1368 if (scols_table_is_empty(ln->extra)) {
1369 extra_prepare_data(cf);
1370 if (scols_table_is_empty(ln->extra))
1371 return 0;
1372 }
1373
1374 ndatalines = fdisk_table_get_nents(cf->table) + 1;
1375
1376 /* nents + header + one free line */
1377 win_ex_start_line = TABLE_START_LINE + ndatalines;
1378 win_height = MENU_START_LINE - win_ex_start_line;
1379 tblen = scols_table_get_nlines(ln->extra);
1380
1381 /* we can't get a single line of data under the partlist*/
1382 if (win_height < 3)
1383 return 1;
1384
1385 /* number of data lines + 2 for top/bottom lines */
1386 win_height = win_height < tblen + 2 ? win_height : tblen + 2;
1387
1388 if ((size_t) win_ex_start_line + win_height + 1 < MENU_START_LINE)
1389 win_ex_start_line = MENU_START_LINE - win_height;
1390
1391 win_ex = subwin(stdscr, win_height, ui_cols - 2, win_ex_start_line, 1);
1392
1393 scols_table_reduce_termwidth(ln->extra, 4);
1394 scols_print_table_to_string(ln->extra, &tbstr);
1395
1396 end = tbstr;
1397 while ((end = strchr(end, '\n')))
1398 *end++ = '\0';
1399
1400 box(win_ex, 0, 0);
1401
1402 end = tbstr;
1403 while (--win_height > 1) {
1404 mvwaddstr(win_ex, wline++, 1 /* window column*/, tbstr);
1405 tbstr += strlen(tbstr) + 1;
1406 }
1407 free(end);
1408
1409 if (ln->w)
1410 delwin(ln->w);
1411
1412 DBG(UI, ul_debug("draw window: %p", win_ex));
1413 touchwin(stdscr);
1414 wrefresh(win_ex);
1415
1416 cf->act_win = ln->w = win_ex;
1417 return 0;
1418 }
1419
1420 static void ui_menu_goto(struct cfdisk *cf, int where)
1421 {
1422 struct cfdisk_menuitem *d;
1423 size_t old;
1424
1425 /* stop and begin/end for vertical menus */
1426 if (cf->menu->vertical) {
1427 if (where < 0)
1428 where = 0;
1429 else if (where > (int) cf->menu->nitems - 1)
1430 where = cf->menu->nitems - 1;
1431 } else {
1432 /* continue from begin/end */
1433 if (where < 0)
1434 where = cf->menu->nitems - 1;
1435 else if ((size_t) where > cf->menu->nitems - 1)
1436 where = 0;
1437 }
1438 if ((size_t) where == cf->menu->idx)
1439 return;
1440
1441 ui_clean_info();
1442
1443 old = cf->menu->idx;
1444 cf->menu->idx = where;
1445
1446 if (!menuitem_on_page(cf, old)) {
1447 ui_draw_menu(cf);
1448 return;
1449 }
1450
1451 d = menu_get_menuitem(cf, old);
1452 ui_draw_menuitem(cf, d, old);
1453
1454 d = menu_get_menuitem(cf, where);
1455 ui_draw_menuitem(cf, d, where);
1456
1457 }
1458
1459 static int ui_menu_move(struct cfdisk *cf, int key)
1460 {
1461 struct cfdisk_menu *m;
1462
1463 assert(cf);
1464 assert(cf->menu);
1465
1466 if (key == ERR)
1467 return 0; /* ignore errors */
1468
1469 m = cf->menu;
1470
1471 DBG(MENU, ul_debug("menu move key >%c<.", key));
1472
1473 if (m->vertical)
1474 {
1475 switch (key) {
1476 case KEY_DOWN:
1477 case '\016': /* ^N */
1478 case 'j': /* Vi-like alternative */
1479 ui_menu_goto(cf, m->idx + 1);
1480 return 0;
1481 case KEY_UP:
1482 case '\020': /* ^P */
1483 case 'k': /* Vi-like alternative */
1484 ui_menu_goto(cf, (int) m->idx - 1);
1485 return 0;
1486 case KEY_PPAGE:
1487 if (m->page_sz) {
1488 ui_menu_goto(cf, (int) m->idx - m->page_sz);
1489 return 0;
1490 }
1491 /* fallthrough */
1492 case KEY_HOME:
1493 ui_menu_goto(cf, 0);
1494 return 0;
1495 case KEY_NPAGE:
1496 if (m->page_sz) {
1497 ui_menu_goto(cf, m->idx + m->page_sz);
1498 return 0;
1499 }
1500 /* fallthrough */
1501 case KEY_END:
1502 ui_menu_goto(cf, m->nitems);
1503 return 0;
1504 }
1505 } else {
1506 switch (key) {
1507 case KEY_RIGHT:
1508 case '\t':
1509 ui_menu_goto(cf, m->idx + 1);
1510 return 0;
1511 case KEY_LEFT:
1512 #ifdef KEY_BTAB
1513 case KEY_BTAB:
1514 #endif
1515 ui_menu_goto(cf, (int) m->idx - 1);
1516 return 0;
1517 }
1518 }
1519
1520 if (key == '\014') { /* ^L refresh */
1521 ui_menu_resize(cf);
1522 return 0;
1523 }
1524
1525 DBG(MENU, ul_debug(" no memu move key"));
1526 return 1;
1527 }
1528
1529 /* but don't call me from ui_run(), this is for pop-up menus only */
1530 static void ui_menu_resize(struct cfdisk *cf)
1531 {
1532 DBG(MENU, ul_debug("memu resize/refresh"));
1533 resize();
1534 ui_clean_menu(cf);
1535 menu_refresh_size(cf);
1536 ui_draw_menu(cf);
1537 refresh();
1538 }
1539
1540 static int partition_on_page(struct cfdisk *cf, size_t i)
1541 {
1542 if (cf->page_sz == 0 ||
1543 cf->lines_idx / cf->page_sz == i / cf->page_sz)
1544 return 1;
1545 return 0;
1546 }
1547
1548 static void ui_draw_partition(struct cfdisk *cf, size_t i)
1549 {
1550 int ln = TABLE_START_LINE + 1 + i; /* skip table header */
1551 int cl = ARROW_CURSOR_WIDTH; /* we need extra space for cursor */
1552 int cur = cf->lines_idx == i;
1553 size_t curpg = 0;
1554
1555 if (cf->page_sz) {
1556 if (!partition_on_page(cf, i))
1557 return;
1558 ln = TABLE_START_LINE + (i % cf->page_sz) + 1;
1559 curpg = cf->lines_idx / cf->page_sz;
1560 }
1561
1562 DBG(UI, ul_debug(
1563 "draw partition %zu [page_sz=%zu, "
1564 "line=%d, idx=%zu]",
1565 i, cf->page_sz, ln, cf->lines_idx));
1566
1567 if (cur) {
1568 attron(A_REVERSE);
1569 mvaddstr(ln, 0, ARROW_CURSOR_STRING);
1570 mvaddstr(ln, cl, cf->lines[i + 1].data);
1571 attroff(A_REVERSE);
1572 } else {
1573 int at = 0;
1574
1575 if (colors_wanted() && is_freespace(cf, i)) {
1576 attron(COLOR_PAIR(CFDISK_CL_FREESPACE));
1577 at = 1;
1578 }
1579 mvaddstr(ln, 0, ARROW_CURSOR_DUMMY);
1580 mvaddstr(ln, cl, cf->lines[i + 1].data);
1581 if (at)
1582 attroff(COLOR_PAIR(CFDISK_CL_FREESPACE));
1583 }
1584
1585 if ((size_t) ln == MENU_START_LINE - 1 &&
1586 cf->page_sz && curpg < cf->nlines / cf->page_sz) {
1587 if (cur)
1588 attron(A_REVERSE);
1589 mvaddch(ln, ui_cols - 1, ACS_DARROW);
1590 mvaddch(ln, 0, ACS_DARROW);
1591 if (cur)
1592 attroff(A_REVERSE);
1593 }
1594
1595 }
1596
1597 static int ui_draw_table(struct cfdisk *cf)
1598 {
1599 int cl = ARROW_CURSOR_WIDTH;
1600 size_t i, nparts = fdisk_table_get_nents(cf->table);
1601 size_t curpg = cf->page_sz ? cf->lines_idx / cf->page_sz : 0;
1602
1603 DBG(UI, ul_debug("draw table"));
1604
1605 for (i = TABLE_START_LINE; i <= TABLE_START_LINE + cf->page_sz; i++) {
1606 move(i, 0);
1607 clrtoeol();
1608 }
1609
1610 if (nparts == 0 || (size_t) cf->lines_idx > nparts - 1)
1611 cf->lines_idx = nparts ? nparts - 1 : 0;
1612
1613 /* print header */
1614 attron(A_BOLD);
1615 mvaddstr(TABLE_START_LINE, cl, cf->lines[0].data);
1616 attroff(A_BOLD);
1617
1618 /* print partitions */
1619 for (i = 0; i < nparts; i++)
1620 ui_draw_partition(cf, i);
1621
1622 if (curpg != 0) {
1623 mvaddch(TABLE_START_LINE, ui_cols - 1, ACS_UARROW);
1624 mvaddch(TABLE_START_LINE, 0, ACS_UARROW);
1625 }
1626 if (cf->page_sz && curpg < cf->nlines / cf->page_sz) {
1627 mvaddch(MENU_START_LINE - 1, ui_cols - 1, ACS_DARROW);
1628 mvaddch(MENU_START_LINE - 1, 0, ACS_DARROW);
1629 }
1630 return 0;
1631 }
1632
1633 static int ui_table_goto(struct cfdisk *cf, int where)
1634 {
1635 size_t old;
1636 size_t nparts = fdisk_table_get_nents(cf->table);
1637
1638 DBG(UI, ul_debug("goto table %d", where));
1639
1640 if (where < 0)
1641 where = 0;
1642 else if ((size_t) where > nparts - 1)
1643 where = nparts - 1;
1644
1645 if ((size_t) where == cf->lines_idx)
1646 return 0;
1647
1648 old = cf->lines_idx;
1649 cf->lines_idx = where;
1650
1651 if (!partition_on_page(cf, old) ||!partition_on_page(cf, where))
1652 ui_draw_table(cf);
1653 else {
1654 ui_draw_partition(cf, old); /* cleanup old */
1655 ui_draw_partition(cf, where); /* draw new */
1656 }
1657 ui_clean_info();
1658 ui_draw_menu(cf);
1659 ui_draw_extra(cf);
1660 refresh();
1661
1662 return 0;
1663 }
1664
1665 static int ui_refresh(struct cfdisk *cf)
1666 {
1667 struct fdisk_label *lb;
1668 char *id = NULL;
1669 uint64_t bytes = fdisk_get_nsectors(cf->cxt) * fdisk_get_sector_size(cf->cxt);
1670 char *strsz;
1671
1672 if (!ui_enabled)
1673 return -EINVAL;
1674
1675 strsz = size_to_human_string(SIZE_SUFFIX_SPACE
1676 | SIZE_SUFFIX_3LETTER, bytes);
1677
1678 lb = fdisk_get_label(cf->cxt, NULL);
1679 assert(lb);
1680
1681 clear();
1682
1683 /* header */
1684 attron(A_BOLD);
1685 ui_center(0, _("Disk: %s"), fdisk_get_devname(cf->cxt));
1686 attroff(A_BOLD);
1687 ui_center(1, _("Size: %s, %ju bytes, %ju sectors"),
1688 strsz, bytes, (uintmax_t) fdisk_get_nsectors(cf->cxt));
1689 if (fdisk_get_disklabel_id(cf->cxt, &id) == 0 && id)
1690 ui_center(2, _("Label: %s, identifier: %s"),
1691 fdisk_label_get_name(lb), id);
1692 else
1693 ui_center(2, _("Label: %s"), fdisk_label_get_name(lb));
1694 free(strsz);
1695
1696 ui_draw_table(cf);
1697 ui_draw_menu(cf);
1698 refresh();
1699 return 0;
1700 }
1701
1702 static ssize_t ui_get_string(const char *prompt,
1703 const char *hint, char *buf, size_t len)
1704 {
1705 size_t cells = 0;
1706 ssize_t i = 0, rc = -1;
1707 int ln = MENU_START_LINE, cl = 1;
1708
1709 assert(buf);
1710 assert(len);
1711
1712 move(ln, 0);
1713 clrtoeol();
1714
1715 move(ln + 1, 0);
1716 clrtoeol();
1717
1718 if (prompt) {
1719 mvaddstr(ln, cl, (char *) prompt);
1720 cl += mbs_safe_width(prompt);
1721 }
1722
1723 /* default value */
1724 if (*buf) {
1725 i = strlen(buf);
1726 cells = mbs_safe_width(buf);
1727 mvaddstr(ln, cl, buf);
1728 }
1729
1730 if (hint)
1731 ui_hint(hint);
1732 else
1733 ui_clean_hint();
1734
1735 move(ln, cl + cells);
1736 curs_set(1);
1737 refresh();
1738
1739 while (1) {
1740 #if !defined(HAVE_SLCURSES_H) && !defined(HAVE_SLANG_SLCURSES_H) && \
1741 defined(HAVE_LIBNCURSESW) && defined(HAVE_WIDECHAR)
1742 wint_t c;
1743 if (get_wch(&c) == ERR) {
1744 #else
1745 int c;
1746 if ((c = getch()) == ERR) {
1747 #endif
1748 if (ui_resize) {
1749 resize();
1750 continue;
1751 }
1752 if (!isatty(STDIN_FILENO))
1753 exit(2);
1754 else
1755 goto done;
1756 }
1757 if (c == '\r' || c == '\n' || c == KEY_ENTER)
1758 break;
1759
1760 switch (c) {
1761 case KEY_ESC:
1762 rc = -CFDISK_ERR_ESC;
1763 goto done;
1764 case KEY_LEFT: /* TODO: implement full buffer editor */
1765 case KEY_RIGHT:
1766 case KEY_END:
1767 case KEY_HOME:
1768 case KEY_UP:
1769 case KEY_DOWN:
1770 beep();
1771 break;
1772 case KEY_DELETE:
1773 case '\b':
1774 case KEY_BACKSPACE:
1775 if (i > 0) {
1776 cells--;
1777 i = mbs_truncate(buf, &cells);
1778 if (i < 0)
1779 goto done;
1780 mvaddch(ln, cl + cells, ' ');
1781 move(ln, cl + cells);
1782 } else
1783 beep();
1784 break;
1785
1786 default:
1787 #if defined(HAVE_LIBNCURSESW) && defined(HAVE_WIDECHAR)
1788 if (i + 1 < (ssize_t) len && iswprint(c)) {
1789 wchar_t wc = (wchar_t) c;
1790 char s[MB_CUR_MAX + 1];
1791 int sz = wctomb(s, wc);
1792
1793 if (sz > 0 && sz + i < (ssize_t) len) {
1794 s[sz] = '\0';
1795 mvaddnstr(ln, cl + cells, s, sz);
1796 memcpy(buf + i, s, sz);
1797 i += sz;
1798 buf[i] = '\0';
1799 cells += wcwidth(wc);
1800 } else
1801 beep();
1802 }
1803 #else
1804 if (i + 1 < (ssize_t) len && isprint(c)) {
1805 mvaddch(ln, cl + cells, c);
1806 buf[i++] = c;
1807 buf[i] = '\0';
1808 cells++;
1809 }
1810 #endif
1811 else
1812 beep();
1813 }
1814 refresh();
1815 }
1816
1817 rc = i; /* success */
1818 done:
1819 move(ln, 0);
1820 clrtoeol();
1821 curs_set(0);
1822 refresh();
1823
1824 return rc;
1825 }
1826
1827 /* @res is default value as well as result in bytes */
1828 static int ui_get_size(struct cfdisk *cf, const char *prompt, uintmax_t *res,
1829 uintmax_t low, uintmax_t up, int *expsize)
1830 {
1831 char buf[128];
1832 uintmax_t user = 0;
1833 ssize_t rc;
1834 char *dflt = size_to_human_string(0, *res);
1835
1836 DBG(UI, ul_debug("get_size (default=%ju)", *res));
1837
1838 ui_clean_info();
1839
1840 snprintf(buf, sizeof(buf), "%s", dflt);
1841
1842 do {
1843 int pwr = 0, insec = 0;
1844
1845 rc = ui_get_string(prompt,
1846 _("May be followed by M for MiB, G for GiB, "
1847 "T for TiB, or S for sectors."),
1848 buf, sizeof(buf));
1849 ui_clean_warn();
1850
1851 if (rc == 0) {
1852 ui_warnx(_("Please, specify size."));
1853 continue; /* nothing specified */
1854 } else if (rc == -CFDISK_ERR_ESC)
1855 break; /* cancel dialog */
1856
1857 if (strcmp(buf, dflt) == 0)
1858 user = *res, rc = 0; /* no change, use default */
1859 else {
1860 size_t len = strlen(buf);
1861 if (buf[len - 1] == 'S' || buf[len - 1] == 's') {
1862 insec = 1;
1863 buf[len - 1] = '\0';
1864 }
1865 rc = parse_size(buf, &user, &pwr); /* parse */
1866 }
1867
1868 if (rc == 0) {
1869 DBG(UI, ul_debug("get_size user=%ju, power=%d, sectors=%s",
1870 user, pwr, insec ? "yes" : "no"));
1871 if (insec)
1872 user *= fdisk_get_sector_size(cf->cxt);
1873 if (user < low) {
1874 ui_warnx(_("Minimum size is %ju bytes."), low);
1875 rc = -ERANGE;
1876 }
1877 if (user > up && pwr && user < up + (1ULL << pwr * 10))
1878 /* ignore when the user specified size overflow
1879 * with in range specified by suffix (e.g. MiB) */
1880 user = up;
1881
1882 if (user > up) {
1883 ui_warnx(_("Maximum size is %ju bytes."), up);
1884 rc = -ERANGE;
1885 }
1886 if (rc == 0 && insec && expsize)
1887 *expsize = 1;
1888
1889 } else
1890 ui_warnx(_("Failed to parse size."));
1891 } while (rc != 0);
1892
1893 if (rc == 0)
1894 *res = user;
1895 free(dflt);
1896
1897 DBG(UI, ul_debug("get_size (result=%ju, rc=%zd)", *res, rc));
1898 return rc;
1899 }
1900
1901 static struct fdisk_parttype *ui_get_parttype(struct cfdisk *cf,
1902 struct fdisk_parttype *cur)
1903 {
1904 struct cfdisk_menuitem *d, *cm;
1905 size_t i = 0, nitems, idx = 0;
1906 struct fdisk_parttype *t = NULL;
1907 struct fdisk_label *lb;
1908 int codetypes = 0;
1909
1910 DBG(UI, ul_debug("asking for parttype."));
1911
1912 lb = fdisk_get_label(cf->cxt, NULL);
1913
1914 /* create cfdisk menu according to label types, note that the
1915 * last cm[] item has to be empty -- so nitems + 1 */
1916 nitems = fdisk_label_get_nparttypes(lb);
1917 if (!nitems)
1918 return NULL;
1919
1920 cm = xcalloc(nitems + 1, sizeof(struct cfdisk_menuitem));
1921 if (!cm)
1922 return NULL;
1923
1924 codetypes = fdisk_label_has_code_parttypes(lb);
1925
1926 for (i = 0; i < nitems; i++) {
1927 const struct fdisk_parttype *x = fdisk_label_get_parttype(lb, i);
1928 char *name;
1929
1930 cm[i].userdata = (void *) x;
1931 if (codetypes)
1932 xasprintf(&name, "%2x %s",
1933 fdisk_parttype_get_code(x),
1934 _(fdisk_parttype_get_name(x)));
1935 else {
1936 name = (char *) _(fdisk_parttype_get_name(x));
1937 cm[i].desc = fdisk_parttype_get_string(x);
1938 }
1939 cm[i].name = name;
1940 if (x == cur)
1941 idx = i;
1942 }
1943
1944 /* make the new menu active */
1945 menu_push(cf, cm);
1946 cf->menu->vertical = 1;
1947 cf->menu->idx = idx;
1948 menu_set_title(cf->menu, _("Select partition type"));
1949 ui_draw_menu(cf);
1950 refresh();
1951
1952 do {
1953 int key = getch();
1954
1955 if (ui_resize)
1956 ui_menu_resize(cf);
1957 if (ui_menu_move(cf, key) == 0)
1958 continue;
1959
1960 switch (key) {
1961 case KEY_ENTER:
1962 case '\n':
1963 case '\r':
1964 d = menu_get_menuitem(cf, cf->menu->idx);
1965 if (d)
1966 t = (struct fdisk_parttype *) d->userdata;
1967 goto done;
1968 case KEY_ESC:
1969 case 'q':
1970 case 'Q':
1971 goto done;
1972 }
1973 } while (1);
1974
1975 done:
1976 menu_pop(cf);
1977 if (codetypes) {
1978 for (i = 0; i < nitems; i++)
1979 free((char *) cm[i].name);
1980 }
1981 free(cm);
1982 DBG(UI, ul_debug("get parrtype done [type=%s] ", t ?
1983 fdisk_parttype_get_name(t) : NULL));
1984 return t;
1985 }
1986
1987 static int ui_script_read(struct cfdisk *cf)
1988 {
1989 struct fdisk_script *sc = NULL;
1990 char buf[PATH_MAX] = { 0 };
1991 int rc;
1992
1993 erase();
1994 rc = ui_get_string( _("Enter script file name: "),
1995 _("The script file will be applied to in-memory partition table."),
1996 buf, sizeof(buf));
1997 if (rc <= 0)
1998 return rc;
1999
2000 rc = -1;
2001 errno = 0;
2002 sc = fdisk_new_script_from_file(cf->cxt, buf);
2003 if (!sc && errno)
2004 ui_warn(_("Cannot open %s"), buf);
2005 else if (!sc)
2006 ui_warnx(_("Failed to parse script file %s"), buf);
2007 else if (fdisk_apply_script(cf->cxt, sc) != 0)
2008 ui_warnx(_("Failed to apply script %s"), buf);
2009 else
2010 rc = 0;
2011
2012 ui_clean_hint();
2013 fdisk_unref_script(sc);
2014 return rc;
2015 }
2016
2017 static int ui_script_write(struct cfdisk *cf)
2018 {
2019 struct fdisk_script *sc = NULL;
2020 char buf[PATH_MAX] = { 0 };
2021 FILE *f = NULL;
2022 int rc;
2023
2024 rc = ui_get_string( _("Enter script file name: "),
2025 _("The current in-memory partition table will be dumped to the file."),
2026 buf, sizeof(buf));
2027 if (rc <= 0)
2028 return rc;
2029
2030 rc = 0;
2031 sc = fdisk_new_script(cf->cxt);
2032 if (!sc) {
2033 ui_warn(_("Failed to allocate script handler"));
2034 goto done;
2035 }
2036
2037 rc = fdisk_script_read_context(sc, NULL);
2038 if (rc) {
2039 ui_warnx(_("Failed to read disk layout into script."));
2040 goto done;
2041 }
2042
2043 DBG(UI, ul_debug("writing dump into: '%s'", buf));
2044 f = fopen(buf, "w");
2045 if (!f) {
2046 ui_warn(_("Cannot open %s"), buf);
2047 rc = -errno;
2048 goto done;
2049 }
2050
2051 rc = fdisk_script_write_file(sc, f);
2052 if (!rc)
2053 ui_info(_("Disk layout successfully dumped."));
2054 done:
2055 if (rc)
2056 ui_warn(_("Failed to write script %s"), buf);
2057 if (f)
2058 fclose(f);
2059 fdisk_unref_script(sc);
2060 return rc;
2061 }
2062
2063 /* prints menu with libfdisk labels and waits for users response */
2064 static int ui_create_label(struct cfdisk *cf)
2065 {
2066 struct cfdisk_menuitem *d, *cm;
2067 int rc = 1, refresh_menu = 1;
2068 size_t i = 0, nitems;
2069 struct fdisk_label *lb = NULL;
2070
2071 assert(cf);
2072
2073 DBG(UI, ul_debug("asking for new disklabe."));
2074
2075 /* create cfdisk menu according to libfdisk labels, note that the
2076 * last cm[] item has to be empty -- so nitems + 1 */
2077 nitems = fdisk_get_nlabels(cf->cxt);
2078 cm = xcalloc(nitems + 1, sizeof(struct cfdisk_menuitem));
2079
2080 while (fdisk_next_label(cf->cxt, &lb) == 0) {
2081 if (fdisk_label_is_disabled(lb) ||
2082 fdisk_label_get_type(lb) == FDISK_DISKLABEL_BSD)
2083 continue;
2084 cm[i++].name = fdisk_label_get_name(lb);
2085 }
2086
2087 erase();
2088
2089 /* make the new menu active */
2090 menu_push(cf, cm);
2091 cf->menu->vertical = 1;
2092 menu_set_title(cf->menu, _("Select label type"));
2093
2094 if (!cf->zero_start)
2095 ui_info(_("Device does not contain a recognized partition table."));
2096
2097
2098 do {
2099 if (refresh_menu) {
2100 ui_draw_menu(cf);
2101 ui_hint(_("Select a type to create a new label or press 'L' to load script file."));
2102 refresh();
2103 refresh_menu = 0;
2104 }
2105
2106 int key = getch();
2107
2108 if (ui_resize)
2109 ui_menu_resize(cf);
2110 if (ui_menu_move(cf, key) == 0)
2111 continue;
2112 switch (key) {
2113 case KEY_ENTER:
2114 case '\n':
2115 case '\r':
2116 d = menu_get_menuitem(cf, cf->menu->idx);
2117 if (d)
2118 rc = fdisk_create_disklabel(cf->cxt, d->name);
2119 goto done;
2120 case KEY_ESC:
2121 case 'q':
2122 case 'Q':
2123 goto done;
2124 case 'l':
2125 case 'L':
2126 rc = ui_script_read(cf);
2127 if (rc == 0)
2128 goto done;
2129 refresh_menu = 1;
2130 break;
2131 }
2132 } while (1);
2133
2134 done:
2135 menu_pop(cf);
2136 free(cm);
2137 DBG(UI, ul_debug("create label done [rc=%d] ", rc));
2138 return rc;
2139 }
2140
2141
2142 static int ui_help(void)
2143 {
2144 size_t i;
2145 static const char *help[] = {
2146 N_("This is cfdisk, a curses-based disk partitioning program."),
2147 N_("It lets you create, delete, and modify partitions on a block device."),
2148 " ",
2149 N_("Command Meaning"),
2150 N_("------- -------"),
2151 N_(" b Toggle bootable flag of the current partition"),
2152 N_(" d Delete the current partition"),
2153 N_(" h Print this screen"),
2154 N_(" n Create new partition from free space"),
2155 N_(" q Quit program without writing partition table"),
2156 N_(" s Fix partitions order (only when in disarray)"),
2157 N_(" t Change the partition type"),
2158 N_(" u Dump disk layout to sfdisk compatible script file"),
2159 N_(" W Write partition table to disk (you must enter uppercase W);"),
2160 N_(" since this might destroy data on the disk, you must either"),
2161 N_(" confirm or deny the write by entering 'yes' or 'no'"),
2162 N_(" x Display/hide extra information about a partition"),
2163 N_("Up Arrow Move cursor to the previous partition"),
2164 N_("Down Arrow Move cursor to the next partition"),
2165 N_("Left Arrow Move cursor to the previous menu item"),
2166 N_("Right Arrow Move cursor to the next menu item"),
2167 " ",
2168 N_("Note: All of the commands can be entered with either upper or lower"),
2169 N_("case letters (except for Write)."),
2170 " ",
2171 N_("Use lsblk(8) or partx(8) to see more details about the device.")
2172 };
2173
2174 erase();
2175 for (i = 0; i < ARRAY_SIZE(help); i++)
2176 mvaddstr(i, 1, _(help[i]));
2177
2178 ui_info(_("Press a key to continue."));
2179
2180 getch();
2181 return 0;
2182 }
2183
2184 /* TODO: use @sz, now 128bytes */
2185 static int main_menu_ignore_keys(struct cfdisk *cf, char *ignore,
2186 size_t sz __attribute__((__unused__)))
2187 {
2188 struct fdisk_partition *pa = get_current_partition(cf);
2189 size_t i = 0;
2190
2191 if (!pa)
2192 return 0;
2193 if (fdisk_partition_is_freespace(pa)) {
2194 ignore[i++] = 'd'; /* delete */
2195 ignore[i++] = 't'; /* set type */
2196 ignore[i++] = 'b'; /* set bootable */
2197 cf->menu->prefkey = 'n';
2198 } else {
2199 cf->menu->prefkey = 'q';
2200 ignore[i++] = 'n';
2201 if (!fdisk_is_label(cf->cxt, DOS) &&
2202 !fdisk_is_label(cf->cxt, SGI))
2203 ignore[i++] = 'b';
2204 }
2205
2206 if (!cf->wrong_order)
2207 ignore[i++] = 's';
2208
2209 if (fdisk_is_readonly(cf->cxt))
2210 ignore[i++] = 'W';
2211
2212 return i;
2213 }
2214
2215
2216 /* returns: error: < 0, success: 0, quit: 1 */
2217 static int main_menu_action(struct cfdisk *cf, int key)
2218 {
2219 size_t n;
2220 int ref = 0, rc, org_order = cf->wrong_order;
2221 const char *info = NULL, *warn = NULL;
2222 struct fdisk_partition *pa;
2223
2224 assert(cf);
2225 assert(cf->cxt);
2226 assert(cf->menu);
2227
2228 if (key == 0) {
2229 struct cfdisk_menuitem *d = menu_get_menuitem(cf, cf->menu->idx);
2230 if (!d)
2231 return 0;
2232 key = d->key;
2233
2234 } else if (key != 'w' && key != 'W')
2235 key = tolower(key); /* case insensitive except 'W'rite */
2236
2237 DBG(MENU, ul_debug("main menu action: key=%c", key));
2238
2239 if (cf->menu->ignore && strchr(cf->menu->ignore, key)) {
2240 DBG(MENU, ul_debug(" ignore '%c'", key));
2241 return 0;
2242 }
2243
2244 pa = get_current_partition(cf);
2245 if (!pa)
2246 return -EINVAL;
2247 n = fdisk_partition_get_partno(pa);
2248
2249 DBG(MENU, ul_debug("menu action on %p", pa));
2250 ui_clean_hint();
2251 ui_clean_info();
2252
2253 switch (key) {
2254 case 'b': /* Bootable flag */
2255 {
2256 int fl = fdisk_is_label(cf->cxt, DOS) ? DOS_FLAG_ACTIVE :
2257 fdisk_is_label(cf->cxt, SGI) ? SGI_FLAG_BOOT : 0;
2258
2259 if (fl && fdisk_toggle_partition_flag(cf->cxt, n, fl))
2260 warn = _("Could not toggle the flag.");
2261 else if (fl)
2262 ref = 1;
2263 break;
2264 }
2265 #ifdef KEY_DC
2266 case KEY_DC:
2267 #endif
2268 case 'd': /* Delete */
2269 if (fdisk_delete_partition(cf->cxt, n) != 0)
2270 warn = _("Could not delete partition %zu.");
2271 else
2272 info = _("Partition %zu has been deleted.");
2273 ref = 1;
2274 break;
2275 case 'h': /* Help */
2276 case '?':
2277 ui_help();
2278 ref = 1;
2279 break;
2280 case 'n': /* New */
2281 {
2282 uint64_t start, size, dflt_size, secs;
2283 struct fdisk_partition *npa; /* the new partition */
2284 int expsize = 0; /* size specified explicitly in sectors */
2285
2286 if (!fdisk_partition_is_freespace(pa) || !fdisk_partition_has_start(pa))
2287 return -EINVAL;
2288
2289 /* free space range */
2290 start = fdisk_partition_get_start(pa);
2291 size = dflt_size = fdisk_partition_get_size(pa) * fdisk_get_sector_size(cf->cxt);
2292
2293 if (ui_get_size(cf, _("Partition size: "), &size,
2294 fdisk_get_sector_size(cf->cxt),
2295 size, &expsize) == -CFDISK_ERR_ESC)
2296 break;
2297
2298 secs = size / fdisk_get_sector_size(cf->cxt);
2299
2300 npa = fdisk_new_partition();
2301 if (!npa)
2302 return -ENOMEM;
2303
2304 if (dflt_size == size) /* default is to fillin all free space */
2305 fdisk_partition_end_follow_default(npa, 1);
2306 else
2307 fdisk_partition_set_size(npa, secs);
2308
2309 if (expsize)
2310 fdisk_partition_size_explicit(pa, 1);
2311
2312 fdisk_partition_set_start(npa, start);
2313 fdisk_partition_partno_follow_default(npa, 1);
2314 /* add to disk label -- libfdisk will ask for missing details */
2315 rc = fdisk_add_partition(cf->cxt, npa, NULL);
2316 fdisk_unref_partition(npa);
2317 if (rc == 0)
2318 ref = 1;
2319 break;
2320 }
2321 case 'q': /* Quit */
2322 return 1;
2323 case 't': /* Type */
2324 {
2325 struct fdisk_parttype *t;
2326
2327 if (fdisk_partition_is_freespace(pa))
2328 return -EINVAL;
2329 t = (struct fdisk_parttype *) fdisk_partition_get_type(pa);
2330 t = ui_get_parttype(cf, t);
2331 ref = 1;
2332
2333 if (t && fdisk_set_partition_type(cf->cxt, n, t) == 0)
2334 info = _("Changed type of partition %zu.");
2335 else
2336 info = _("The type of partition %zu is unchanged.");
2337 break;
2338 }
2339 case 's': /* Sort */
2340 if (cf->wrong_order) {
2341 fdisk_reorder_partitions(cf->cxt);
2342 ref = 1;
2343 }
2344 break;
2345 case 'u': /* dUmp */
2346 ui_script_write(cf);
2347 break;
2348 case 'W': /* Write */
2349 {
2350 char buf[64] = { 0 };
2351
2352 if (fdisk_is_readonly(cf->cxt)) {
2353 warn = _("Device is open in read-only mode.");
2354 break;
2355 }
2356
2357 rc = ui_get_string(
2358 _("Are you sure you want to write the partition "
2359 "table to disk? "),
2360 _("Type \"yes\" or \"no\", or press ESC to leave this dialog."),
2361 buf, sizeof(buf));
2362
2363 ref = 1;
2364 if (rc <= 0 || (strcasecmp(buf, "yes") != 0 &&
2365 strcasecmp(buf, _("yes")) != 0)) {
2366 info = _("Did not write partition table to disk.");
2367 break;
2368 }
2369 rc = fdisk_write_disklabel(cf->cxt);
2370 if (rc)
2371 warn = _("Failed to write disklabel.");
2372 else {
2373 fdisk_reread_partition_table(cf->cxt);
2374 info = _("The partition table has been altered.");
2375 }
2376 cf->nwrites++;
2377 break;
2378 }
2379 default:
2380 break;
2381 }
2382
2383 if (ref) {
2384 lines_refresh(cf);
2385 ui_refresh(cf);
2386 ui_draw_extra(cf);
2387 } else
2388 ui_draw_menu(cf);
2389
2390 ui_clean_hint();
2391
2392 if (warn)
2393 ui_warnx(warn, n + 1);
2394 else if (info)
2395 ui_info(info, n + 1);
2396 else if (key == 'n' && cf->wrong_order && org_order == 0)
2397 ui_info(_("Note that partition table entries are not in disk order now."));
2398
2399 return 0;
2400 }
2401
2402 static void ui_resize_refresh(struct cfdisk *cf)
2403 {
2404 DBG(UI, ul_debug("ui resize/refresh"));
2405 resize();
2406 menu_refresh_size(cf);
2407 lines_refresh(cf);
2408 ui_refresh(cf);
2409 ui_draw_extra(cf);
2410 }
2411
2412 static void toggle_show_extra(struct cfdisk *cf)
2413 {
2414 if (cf->show_extra && cf->act_win) {
2415 wclear(cf->act_win);
2416 touchwin(stdscr);
2417 }
2418 cf->show_extra = cf->show_extra ? 0 : 1;
2419
2420 if (cf->show_extra)
2421 ui_draw_extra(cf);
2422 DBG(MENU, ul_debug("extra: %s", cf->show_extra ? "ENABLED" : "DISABLED" ));
2423 }
2424
2425 static int ui_run(struct cfdisk *cf)
2426 {
2427 int rc = 0;
2428
2429 ui_lines = LINES;
2430 ui_cols = COLS;
2431 DBG(UI, ul_debug("start cols=%zu, lines=%zu", ui_cols, ui_lines));
2432
2433 if (fdisk_get_collision(cf->cxt)) {
2434 ui_warnx(_("Device already contains a %s signature; it will be removed by a write command."),
2435 fdisk_get_collision(cf->cxt));
2436 fdisk_enable_wipe(cf->cxt, 1);
2437 ui_hint(_("Press a key to continue."));
2438 getch();
2439 }
2440
2441 if (!fdisk_has_label(cf->cxt) || cf->zero_start) {
2442 rc = ui_create_label(cf);
2443 if (rc < 0)
2444 ui_errx(EXIT_FAILURE,
2445 _("failed to create a new disklabel"));
2446 if (rc)
2447 return rc;
2448 }
2449
2450 cols_init(cf);
2451 rc = lines_refresh(cf);
2452 if (rc)
2453 ui_errx(EXIT_FAILURE, _("failed to read partitions"));
2454
2455 menu_push(cf, main_menuitems);
2456 cf->menu->ignore_cb = main_menu_ignore_keys;
2457
2458 rc = ui_refresh(cf);
2459 if (rc)
2460 return rc;
2461
2462 cf->show_extra = 1;
2463 ui_draw_extra(cf);
2464
2465 if (fdisk_is_readonly(cf->cxt))
2466 ui_warnx(_("Device is open in read-only mode."));
2467 else if (cf->wrong_order)
2468 ui_info(_("Note that partition table entries are not in disk order now."));
2469
2470 do {
2471 int key = getch();
2472
2473 rc = 0;
2474 if (ui_resize)
2475 /* Note that ncurses getch() returns ERR when interrupted
2476 * by signal, but SLang does not interrupt at all. */
2477 ui_resize_refresh(cf);
2478 if (key == ERR)
2479 continue;
2480 if (key == '\014') { /* ^L refresh */
2481 ui_resize_refresh(cf);
2482 continue;
2483 }
2484 if (ui_menu_move(cf, key) == 0)
2485 continue;
2486
2487 DBG(UI, ul_debug("main action key >%1$c< [\\0%1$o].", key));
2488
2489 switch (key) {
2490 case KEY_DOWN:
2491 case '\016': /* ^N */
2492 case 'j': /* Vi-like alternative */
2493 ui_table_goto(cf, cf->lines_idx + 1);
2494 break;
2495 case KEY_UP:
2496 case '\020': /* ^P */
2497 case 'k': /* Vi-like alternative */
2498 ui_table_goto(cf, (int) cf->lines_idx - 1);
2499 break;
2500 case KEY_PPAGE:
2501 if (cf->page_sz) {
2502 ui_table_goto(cf, (int) cf->lines_idx - cf->page_sz);
2503 break;
2504 }
2505 case KEY_HOME:
2506 ui_table_goto(cf, 0);
2507 break;
2508 case KEY_NPAGE:
2509 if (cf->page_sz) {
2510 ui_table_goto(cf, cf->lines_idx + cf->page_sz);
2511 break;
2512 }
2513 case KEY_END:
2514 ui_table_goto(cf, (int) cf->nlines - 1);
2515 break;
2516 case KEY_ENTER:
2517 case '\n':
2518 case '\r':
2519 rc = main_menu_action(cf, 0);
2520 break;
2521 case 'X':
2522 case 'x': /* Extra */
2523 toggle_show_extra(cf);
2524 break;
2525 default:
2526 rc = main_menu_action(cf, key);
2527 if (rc < 0)
2528 beep();
2529 break;
2530 }
2531
2532 if (rc == 1)
2533 break; /* quit */
2534 } while (1);
2535
2536 menu_pop(cf);
2537
2538 DBG(UI, ul_debug("end"));
2539 return 0;
2540 }
2541
2542 static void __attribute__ ((__noreturn__)) usage(FILE *out)
2543 {
2544 fputs(USAGE_HEADER, out);
2545 fprintf(out,
2546 _(" %1$s [options] <disk>\n"), program_invocation_short_name);
2547
2548 fputs(USAGE_SEPARATOR, out);
2549 fputs(_("Display or manipulate a disk partition table.\n"), out);
2550
2551 fputs(USAGE_OPTIONS, out);
2552 fputs(_(" -L, --color[=<when>] colorize output (auto, always or never)\n"), out);
2553 fprintf(out,
2554 " %s\n", USAGE_COLORS_DEFAULT);
2555 fputs(_(" -z, --zero start with zeroed partition table\n"), out);
2556
2557 fputs(USAGE_SEPARATOR, out);
2558 fputs(USAGE_HELP, out);
2559 fputs(USAGE_VERSION, out);
2560
2561 fprintf(out, USAGE_MAN_TAIL("cfdisk(8)"));
2562 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
2563 }
2564
2565 int main(int argc, char *argv[])
2566 {
2567 const char *diskpath = NULL;
2568 int rc, c, colormode = UL_COLORMODE_UNDEF;
2569 struct cfdisk _cf = { .lines_idx = 0 },
2570 *cf = &_cf;
2571
2572 static const struct option longopts[] = {
2573 { "color", optional_argument, NULL, 'L' },
2574 { "help", no_argument, NULL, 'h' },
2575 { "version", no_argument, NULL, 'V' },
2576 { "zero", no_argument, NULL, 'z' },
2577 { NULL, 0, 0, 0 },
2578 };
2579
2580 setlocale(LC_ALL, "");
2581 bindtextdomain(PACKAGE, LOCALEDIR);
2582 textdomain(PACKAGE);
2583 atexit(close_stdout);
2584
2585 while((c = getopt_long(argc, argv, "L::hVz", longopts, NULL)) != -1) {
2586 switch(c) {
2587 case 'h':
2588 usage(stdout);
2589 break;
2590 case 'L':
2591 colormode = UL_COLORMODE_AUTO;
2592 if (optarg)
2593 colormode = colormode_or_err(optarg,
2594 _("unsupported color mode"));
2595 break;
2596 case 'V':
2597 printf(UTIL_LINUX_VERSION);
2598 return EXIT_SUCCESS;
2599 case 'z':
2600 cf->zero_start = 1;
2601 break;
2602 default:
2603 errtryhelp(EXIT_FAILURE);
2604 }
2605 }
2606
2607 colors_init(colormode, "cfdisk");
2608
2609 fdisk_init_debug(0);
2610 scols_init_debug(0);
2611 cfdisk_init_debug();
2612 cf->cxt = fdisk_new_context();
2613 if (!cf->cxt)
2614 err(EXIT_FAILURE, _("failed to allocate libfdisk context"));
2615
2616 fdisk_set_ask(cf->cxt, ask_callback, (void *) cf);
2617
2618 if (optind == argc) {
2619 size_t i;
2620
2621 for (i = 0; i < ARRAY_SIZE(default_disks); i++) {
2622 if (access(default_disks[i], F_OK) == 0) {
2623 diskpath = default_disks[i];
2624 break;
2625 }
2626 }
2627 if (!diskpath)
2628 diskpath = default_disks[0]; /* default, used for "cannot open" */
2629 } else
2630 diskpath = argv[optind];
2631
2632 rc = fdisk_assign_device(cf->cxt, diskpath, 0);
2633 if (rc == -EACCES)
2634 rc = fdisk_assign_device(cf->cxt, diskpath, 1);
2635 if (rc != 0)
2636 err(EXIT_FAILURE, _("cannot open %s"), diskpath);
2637
2638 /* Don't use err(), warn() from this point */
2639 ui_init(cf);
2640 ui_run(cf);
2641 ui_end();
2642
2643 cfdisk_free_lines(cf);
2644 free(cf->linesbuf);
2645
2646 fdisk_unref_table(cf->table);
2647 #ifdef HAVE_LIBMOUNT
2648 mnt_unref_table(cf->fstab);
2649 mnt_unref_table(cf->mtab);
2650 mnt_unref_cache(cf->mntcache);
2651 #endif
2652 rc = fdisk_deassign_device(cf->cxt, cf->nwrites == 0);
2653 fdisk_unref_context(cf->cxt);
2654 DBG(MISC, ul_debug("bye! [rc=%d]", rc));
2655 return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
2656 }