]> git.ipfire.org Git - thirdparty/util-linux.git/blob - fdisks/cfdisk.c
cfdisk: add UI for linfdisk menus, ask for size
[thirdparty/util-linux.git] / fdisks / cfdisk.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <errno.h>
4 #include <signal.h>
5 #include <ctype.h>
6
7 #ifdef HAVE_SLANG_H
8 #include <slang.h>
9 #elif defined(HAVE_SLANG_SLANG_H)
10 #include <slang/slang.h>
11 #endif
12
13 #ifdef HAVE_SLCURSES_H
14 #include <slcurses.h>
15 #elif defined(HAVE_SLANG_SLCURSES_H)
16 #include <slang/slcurses.h>
17 #elif defined(HAVE_NCURSESW_NCURSES_H) && defined(HAVE_WIDECHAR)
18 #include <ncursesw/ncurses.h>
19 #elif defined(HAVE_NCURSES_H)
20 #include <ncurses.h>
21 #elif defined(HAVE_NCURSES_NCURSES_H)
22 #include <ncurses/ncurses.h>
23 #endif
24
25 #ifdef HAVE_WIDECHAR
26 #include <wctype.h>
27 #endif
28
29 #include "c.h"
30 #include "closestream.h"
31 #include "nls.h"
32 #include "strutils.h"
33 #include "xalloc.h"
34 #include "mbsalign.h"
35
36 #include "fdiskP.h"
37
38 #define ARROW_CURSOR_STRING ">>> "
39 #define ARROW_CURSOR_DUMMY " "
40 #define ARROW_CURSOR_WIDTH (sizeof(ARROW_CURSOR_STRING) - 1)
41
42 #define MENU_PADDING 2
43 #define TABLE_START_LINE 4
44 #define MENU_START_LINE (LINES - 5)
45 #define INFO_LINE (LINES - 2)
46 #define HINT_LINE (LINES - 1)
47
48 #define CFDISK_ERR_ESC 5000
49
50 #ifndef KEY_ESC
51 # define KEY_ESC '\033'
52 #endif
53 #ifndef KEY_DELETE
54 # define KEY_DELETE '\177'
55 #endif
56
57 /* colors */
58 enum {
59 CFDISK_CL_NONE = 0,
60 CFDISK_CL_WARNING
61 };
62 static const int color_pairs[][2] = {
63 /* color foreground, background */
64 [CFDISK_CL_WARNING] = { COLOR_RED, -1 },
65 };
66
67 struct cfdisk;
68 typedef int (menu_callback_t)(struct cfdisk *, int);
69
70 static int menu_cb_main(struct cfdisk *cf, int key);
71 static struct cfdisk_menudesc *menu_get_menuitem(struct cfdisk *cf, size_t idx);
72 static struct cfdisk_menudesc *menu_get_menuitem_by_key(struct cfdisk *cf, int key, size_t *idx);
73 static struct cfdisk_menu *menu_push(struct cfdisk *cf, size_t id, struct cfdisk_menudesc *desc);
74 static struct cfdisk_menu *menu_pop(struct cfdisk *cf);
75
76 static int ui_refresh(struct cfdisk *cf);
77 static void ui_warnx(const char *fmt, ...);
78 static void ui_warn(const char *fmt, ...);
79 static void ui_info(const char *fmt, ...);
80 static void ui_draw_menu(struct cfdisk *cf);
81 static void ui_menu_goto(struct cfdisk *cf, int where);
82 static int ui_get_size(struct cfdisk *cf, const char *prompt, uintmax_t *res,
83 uintmax_t low, uintmax_t up);
84
85 static int ui_enabled;
86
87 struct cfdisk_menudesc {
88 int key; /* keyboard shortcut */
89 const char *name; /* item name */
90 const char *desc; /* item description */
91 };
92
93 struct cfdisk_menu {
94 struct cfdisk_menudesc *desc;
95 char *ignore;
96 size_t id;
97 size_t width;
98 size_t nitems;
99
100 menu_callback_t *callback;
101
102 struct cfdisk_menu *prev;
103 };
104
105 static struct cfdisk_menudesc menu_main[] = {
106 { 'b', N_("Bootable"), N_("Toggle bootable flag of the current partition") },
107 { 'd', N_("Delete"), N_("Delete the current partition") },
108 // { 'g', N_("Geometry"), N_("Change disk geometry (experts only)") },
109 // { 'h', N_("Help"), N_("Print help screen") },
110 // { 'm', N_("Maximize"), N_("Maximize disk usage of the current partition (experts only)") },
111 { 'n', N_("New"), N_("Create new partition from free space") },
112 // { 'p', N_("Print"), N_("Print partition table to the screen or to a file") },
113 { 'q', N_("Quit"), N_("Quit program without writing partition table") },
114 { 't', N_("Type"), N_("Change the partition type") },
115 // { 'u', N_("Units"), N_("Change units of the partition size display (MB, sect, cyl)") },
116 { 'W', N_("Write"), N_("Write partition table to disk (this might destroy data)") },
117 { 0, NULL, NULL }
118 };
119
120 enum {
121 CFDISK_MENU_GENERATED = -1, /* used in libfdisk callback */
122
123 /* built-in menus */
124 CFDISK_MENU_MAIN = 0,
125
126 };
127
128 static struct cfdisk_menudesc *menus[] = {
129 [CFDISK_MENU_MAIN] = menu_main
130 };
131
132 static menu_callback_t *menu_callbacks[] = {
133 [CFDISK_MENU_MAIN] = menu_cb_main
134 };
135
136
137 struct cfdisk {
138 struct fdisk_context *cxt; /* libfdisk context */
139 struct fdisk_table *table; /* partition table */
140
141 struct cfdisk_menu *menu; /* the current menu */
142 size_t menu_idx;
143
144 int *cols; /* output columns */
145 size_t ncols; /* number of columns */
146
147 char *linesbuf; /* table as string */
148 size_t linesbufsz; /* size of the tb_buf */
149
150 char **lines; /* array with lines */
151 size_t nlines; /* number of lines */
152 size_t lines_idx; /* current line <0..N>, exclude header */
153 };
154
155 static int cols_init(struct cfdisk *cf)
156 {
157 assert(cf);
158
159 free(cf->cols);
160 cf->cols = NULL;
161 cf->ncols = 0;
162
163 return fdisk_get_columns(cf->cxt, 0, &cf->cols, &cf->ncols);
164 }
165
166 /* It would be possible to use fdisk_table_to_string(), but we want some
167 * extension to the output format, so let's do it without libfdisk
168 */
169 static char *table_to_string(struct cfdisk *cf, struct fdisk_table *tb)
170 {
171 struct fdisk_partition *pa;
172 const struct fdisk_column *col;
173 struct fdisk_label *lb;
174 struct fdisk_iter *itr = NULL;
175 struct tt *tt = NULL;
176 char *res = NULL;
177 size_t i;
178
179 DBG(FRONTEND, dbgprint("table: convert to string"));
180
181 assert(cf);
182 assert(cf->cxt);
183 assert(cf->cols);
184 assert(tb);
185
186 lb = fdisk_context_get_label(cf->cxt, NULL);
187 assert(lb);
188
189 tt = tt_new_table(TT_FL_FREEDATA | TT_FL_MAX);
190 if (!tt)
191 goto done;
192 itr = fdisk_new_iter(FDISK_ITER_FORWARD);
193 if (!itr)
194 goto done;
195
196 /* headers */
197 for (i = 0; i < cf->ncols; i++) {
198 col = fdisk_label_get_column(lb, cf->cols[i]);
199 if (col)
200 tt_define_column(tt, col->name,
201 col->width,
202 col->tt_flags);
203 }
204
205 /* data */
206 while (fdisk_table_next_partition(tb, itr, &pa) == 0) {
207 struct tt_line *ln = tt_add_line(tt, NULL);
208 if (!ln)
209 goto done;
210 for (i = 0; i < cf->ncols; i++) {
211 char *cdata = NULL;
212
213 col = fdisk_label_get_column(lb, cf->cols[i]);
214 if (!col)
215 continue;
216 if (fdisk_partition_to_string(pa, cf->cxt, col->id, &cdata))
217 continue;
218 tt_line_set_data(ln, i, cdata);
219 }
220 }
221
222 if (!tt_is_empty(tt)) {
223 tt_set_termreduce(tt, ARROW_CURSOR_WIDTH);
224 tt_print_table_to_string(tt, &res);
225 }
226 done:
227 tt_free_table(tt);
228 fdisk_free_iter(itr);
229
230 return res;
231 }
232
233 static int lines_refresh(struct cfdisk *cf)
234 {
235 int rc;
236 char *p;
237 size_t i;
238
239 assert(cf);
240
241 DBG(FRONTEND, dbgprint("refreshing buffer"));
242
243 free(cf->linesbuf);
244 free(cf->lines);
245 cf->linesbuf = NULL;
246 cf->linesbufsz = 0;
247 cf->lines = NULL;
248 cf->nlines = 0;
249
250 fdisk_unref_table(cf->table);
251 cf->table = NULL;
252 fdisk_context_enable_freespace(cf->cxt, 1);
253
254 rc = fdisk_get_table(cf->cxt, &cf->table);
255 if (rc)
256 return rc;
257
258 cf->linesbuf = table_to_string(cf, cf->table);
259 if (!cf->linesbuf)
260 return -ENOMEM;
261
262 cf->linesbufsz = strlen(cf->linesbuf);
263 cf->nlines = fdisk_table_get_nents(cf->table) + 1; /* 1 for header line */
264
265 cf->lines = calloc(cf->nlines, sizeof(char *));
266 if (!cf->lines)
267 return -ENOMEM;
268
269 for (p = cf->linesbuf, i = 0; p && i < cf->nlines; i++) {
270 cf->lines[i] = p;
271 p = strchr(p, '\n');
272 if (p) {
273 *p = '\0';
274 p++;
275 }
276 }
277
278 return 0;
279 }
280
281 static struct fdisk_partition *get_current_partition(struct cfdisk *cf)
282 {
283 assert(cf);
284 assert(cf->table);
285
286 return fdisk_table_get_partition(cf->table, cf->lines_idx);
287 }
288
289 /* converts libfdisk FDISK_ASKTYPE_MENU to cfdisk menu and returns user's
290 * responseback to libfdisk
291 */
292 static int ask_menu(struct fdisk_ask *ask, struct cfdisk *cf)
293 {
294 struct cfdisk_menudesc *d, *cm;
295 int key;
296 size_t i = 0, nitems;
297 const char *name, *desc;
298
299 assert(ask);
300 assert(cf);
301
302 /* create cfdisk menu according to libfdisk ask-menu, note that the
303 * last cm[] item has to be empty -- so nitems + 1 */
304 nitems = fdisk_ask_menu_get_nitems(ask);
305 cm = calloc(nitems + 1, sizeof(struct cfdisk_menudesc));
306 if (!cm)
307 return -ENOMEM;
308
309 for (i = 0; i < nitems; i++) {
310 if (fdisk_ask_menu_get_item(ask, i, &key, &name, &desc))
311 break;
312 cm[i].key = key;
313 cm[i].desc = desc;
314 cm[i].name = name;
315 }
316
317 /* make the new menu active */
318 menu_push(cf, CFDISK_MENU_GENERATED, cm);
319 ui_draw_menu(cf);
320 refresh();
321
322 /* wait for keys */
323 do {
324 switch (getch()) {
325 case KEY_LEFT:
326 #ifdef KEY_BTAB
327 case KEY_BTAB:
328 #endif
329 ui_menu_goto(cf, cf->menu_idx - 1);
330 break;
331 case KEY_RIGHT:
332 case '\t':
333 ui_menu_goto(cf, cf->menu_idx + 1);
334 break;
335 case KEY_ENTER:
336 case '\n':
337 case '\r':
338 d = menu_get_menuitem(cf, cf->menu_idx);
339 if (d)
340 fdisk_ask_menu_set_result(ask, d->key);
341 menu_pop(cf);
342 free(cm);
343 return 0;
344 }
345 } while (1);
346
347 menu_pop(cf);
348 free(cm);
349 return -1;
350 }
351
352
353 static int ask_callback(struct fdisk_context *cxt, struct fdisk_ask *ask,
354 void *data __attribute__((__unused__)))
355 {
356 int rc = 0;
357
358 assert(cxt);
359 assert(ask);
360
361 switch(fdisk_ask_get_type(ask)) {
362 case FDISK_ASKTYPE_INFO:
363 ui_info(fdisk_ask_print_get_mesg(ask));
364 break;
365 case FDISK_ASKTYPE_WARNX:
366 ui_warnx(fdisk_ask_print_get_mesg(ask));
367 break;
368 case FDISK_ASKTYPE_WARN:
369 ui_warn(fdisk_ask_print_get_mesg(ask));
370 break;
371 case FDISK_ASKTYPE_MENU:
372 ask_menu(ask, (struct cfdisk *) data);
373 break;
374 default:
375 ui_warnx(_("internal error: unsupported dialog type %d"),
376 fdisk_ask_get_type(ask));
377 return -EINVAL;
378 }
379 return rc;
380 }
381
382
383 static int ui_end(struct cfdisk *cf)
384 {
385 if (cf && !ui_enabled)
386 return -EINVAL;
387
388 #if defined(HAVE_SLCURSES_H) || defined(HAVE_SLANG_SLCURSES_H)
389 SLsmg_gotorc(LINES - 1, 0);
390 SLsmg_refresh();
391 #else
392 mvcur(0, COLS - 1, LINES-1, 0);
393 #endif
394 nl();
395 endwin();
396 printf("\n");
397 return 0;
398 }
399
400 static void ui_vprint_center(int line, int attrs, const char *fmt, va_list ap)
401 {
402 size_t width;
403 char *buf = NULL;
404
405 move(line, 0);
406 clrtoeol();
407
408 xvasprintf(&buf, fmt, ap);
409
410 width = mbs_safe_width(buf);
411
412 attron(attrs);
413 mvaddstr(line, (COLS - width) / 2, buf);
414 attroff(attrs);
415 free(buf);
416 }
417
418 static void ui_center(int line, const char *fmt, ...)
419 {
420 va_list ap;
421 va_start(ap, fmt);
422 ui_vprint_center(line, 0, fmt, ap);
423 va_end(ap);
424 }
425
426 static void ui_warnx(const char *fmt, ...)
427 {
428 va_list ap;
429 va_start(ap, fmt);
430 if (ui_enabled)
431 ui_vprint_center(INFO_LINE, COLOR_PAIR(CFDISK_CL_WARNING), fmt, ap);
432 else
433 vfprintf(stderr, fmt, ap);
434 va_end(ap);
435 }
436
437 static void ui_warn(const char *fmt, ...)
438 {
439 char *fmt_m;
440 va_list ap;
441
442 xasprintf(&fmt_m, "%s: %m", fmt);
443
444 va_start(ap, fmt);
445 if (ui_enabled)
446 ui_vprint_center(INFO_LINE, COLOR_PAIR(CFDISK_CL_WARNING), fmt_m, ap);
447 else
448 vfprintf(stderr, fmt_m, ap);
449 va_end(ap);
450 free(fmt_m);
451 }
452
453 static void ui_info(const char *fmt, ...)
454 {
455 va_list ap;
456 va_start(ap, fmt);
457 if (ui_enabled)
458 ui_vprint_center(INFO_LINE, A_BOLD, fmt, ap);
459 else
460 vfprintf(stdout, fmt, ap);
461 va_end(ap);
462 }
463
464 static void ui_clean_info(void)
465 {
466 move(INFO_LINE, 0);
467 clrtoeol();
468 }
469
470 static void ui_hint(const char *fmt, ...)
471 {
472 va_list ap;
473 va_start(ap, fmt);
474 if (ui_enabled)
475 ui_vprint_center(HINT_LINE, A_BOLD, fmt, ap);
476 else
477 vfprintf(stdout, fmt, ap);
478 va_end(ap);
479 }
480
481 static void ui_clean_hint(void)
482 {
483 move(HINT_LINE, 0);
484 clrtoeol();
485 }
486
487 static void die_on_signal(int dummy __attribute__((__unused__)))
488 {
489 ui_end(NULL);
490 exit(EXIT_FAILURE);
491 }
492
493 static void menu_update_ignore(struct cfdisk *cf)
494 {
495 char ignore[128] = { 0 };
496 int i = 0;
497 struct fdisk_partition *pa;
498 struct cfdisk_menu *m;
499 struct cfdisk_menudesc *d, *org;
500 size_t idx;
501
502 assert(cf);
503
504 m = cf->menu;
505 org = menu_get_menuitem(cf, cf->menu_idx);
506
507 DBG(FRONTEND, dbgprint("menu: update menu ignored keys"));
508
509 switch (m->id) {
510 case CFDISK_MENU_MAIN:
511 pa = get_current_partition(cf);
512 if (!pa)
513 break;
514 if (fdisk_partition_is_freespace(pa)) {
515 ignore[i++] = 'd'; /* delete */
516 ignore[i++] = 't'; /* set type */
517 ignore[i++] = 'b'; /* set bootable */
518 } else {
519 ignore[i++] = 'n';
520 if (!fdisk_is_disklabel(cf->cxt, DOS) &&
521 !fdisk_is_disklabel(cf->cxt, SGI))
522 ignore[i++] = 'b';
523 }
524
525 break;
526 }
527
528 ignore[i] = '\0';
529
530 /* return if no change */
531 if ( (!m->ignore && !*ignore)
532 || (m->ignore && *ignore && strcmp(m->ignore, ignore) == 0)) {
533 return;
534 }
535
536 free(m->ignore);
537 m->ignore = xstrdup(ignore);
538 m->nitems = 0;
539
540 for (d = m->desc; d->name; d++) {
541 if (m->ignore && strchr(m->ignore, d->key))
542 continue;
543 m->nitems++;
544 }
545
546 /* refresh menu index to be at the same menuitem or go to the first */
547 if (org && menu_get_menuitem_by_key(cf, org->key, &idx))
548 cf->menu_idx = idx;
549 else
550 cf->menu_idx = 0;
551 }
552
553 static struct cfdisk_menu *menu_push(
554 struct cfdisk *cf,
555 size_t id,
556 struct cfdisk_menudesc *desc)
557 {
558 struct cfdisk_menu *m = xcalloc(1, sizeof(*m));
559 struct cfdisk_menudesc *d;
560
561 assert(cf);
562
563 DBG(FRONTEND, dbgprint("menu: new menu"));
564
565 m->prev = cf->menu;
566 m->id = id;
567 m->desc = desc ? desc : menus[id];
568 m->callback = menu_callbacks[id];
569
570 for (d = m->desc; d->name; d++) {
571 const char *name = _(d->name);
572 size_t len = mbs_safe_width(name);
573 if (len > m->width)
574 m->width = len;
575 m->nitems++;
576 }
577
578 cf->menu = m;
579 return m;
580 }
581
582 static struct cfdisk_menu *menu_pop(struct cfdisk *cf)
583 {
584 struct cfdisk_menu *m = NULL;
585
586 assert(cf);
587
588 DBG(FRONTEND, dbgprint("menu: rem menu"));
589
590 if (cf->menu) {
591 m = cf->menu->prev;
592 free(cf->menu->ignore);
593 free(cf->menu);
594 }
595 cf->menu = m;
596 return cf->menu;
597 }
598
599 /* returns: error: < 0, success: 0, quit: 1 */
600 static int menu_cb_main(struct cfdisk *cf, int key)
601 {
602 size_t n;
603 int ref = 0, rc;
604 const char *info = NULL, *warn = NULL;
605 struct fdisk_partition *pa;
606
607 assert(cf);
608 assert(cf->cxt);
609 assert(key);
610
611 n = cf->lines_idx; /* the current partition */
612 pa = get_current_partition(cf);
613
614 switch (key) {
615 case 'b': /* Bootable flag */
616 {
617 int fl = fdisk_is_disklabel(cf->cxt, DOS) ? DOS_FLAG_ACTIVE :
618 fdisk_is_disklabel(cf->cxt, SGI) ? SGI_FLAG_BOOT : 0;
619
620 if (fl && fdisk_partition_toggle_flag(cf->cxt, n, fl))
621 warn = _("Could not toggle the flag.");
622 else if (fl)
623 ref = 1;
624 break;
625 }
626 case 'd': /* Delete */
627 if (fdisk_delete_partition(cf->cxt, n) != 0)
628 warn = _("Could not delete partition %zu.");
629 else
630 info = _("Partition %zu has been deleted.");
631 ref = 1;
632 break;
633 case 'n': /* New */
634 {
635 uint64_t start, size;
636 struct fdisk_partition *npa; /* the new partition */
637
638 if (!pa || !fdisk_partition_is_freespace(pa))
639 return -EINVAL;
640 npa = fdisk_new_partition();
641 if (!npa)
642 return -ENOMEM;
643 /* free space range */
644 start = fdisk_partition_get_start(pa);
645 size = fdisk_partition_get_size(pa) * cf->cxt->sector_size;
646
647 if (ui_get_size(cf, _("Partition size: "), &size, 1, size)
648 == -CFDISK_ERR_ESC)
649 break;
650 size /= cf->cxt->sector_size;
651 /* properties of the new partition */
652 fdisk_partition_set_start(npa, start);
653 fdisk_partition_set_size(npa, size);
654 fdisk_partition_partno_follow_default(npa, 1);
655 /* add to disk label -- libfdisk will ask for missing details */
656 rc = fdisk_add_partition(cf->cxt, npa);
657 fdisk_unref_partition(npa);
658 if (rc == 0)
659 ref = 1;
660 break;
661 }
662 case 'q': /* Quit */
663 return 1;
664 case 't': /* Type */
665 break;
666 case 'W': /* Write */
667 break;
668 }
669
670 if (ref) {
671 lines_refresh(cf);
672 ui_refresh(cf);
673 }
674 if (warn)
675 ui_warnx(warn, n);
676 else if (info)
677 ui_info(info, n);
678
679 return 0;
680 }
681
682 static int ui_init(struct cfdisk *cf __attribute__((__unused__)))
683 {
684 struct sigaction sa;
685
686 DBG(FRONTEND, dbgprint("ui: init"));
687
688 /* setup SIGCHLD handler */
689 sigemptyset(&sa.sa_mask);
690 sa.sa_flags = 0;
691 sa.sa_handler = die_on_signal;
692 sigaction(SIGINT, &sa, NULL);
693 sigaction(SIGTERM, &sa, NULL);
694
695 ui_enabled = 1;
696 initscr();
697
698 if (has_colors()) {
699 size_t i;
700
701 start_color();
702 use_default_colors();
703
704 for (i = 1; i < ARRAY_SIZE(color_pairs); i++) /* yeah, start from 1! */
705 init_pair(i, color_pairs[i][0], color_pairs[i][1]);
706 }
707
708 cbreak();
709 noecho();
710 nonl();
711 curs_set(0);
712 keypad(stdscr, TRUE);
713
714 return 0;
715 }
716
717 static size_t menuitem_get_line(struct cfdisk *cf, size_t idx)
718 {
719 size_t len = cf->menu->width + 4 + MENU_PADDING; /* item width */
720 size_t items = COLS / len; /* items per line */
721
722 return MENU_START_LINE + ((idx / items));
723 }
724
725 static int menuitem_get_column(struct cfdisk *cf, size_t idx)
726 {
727 size_t len = cf->menu->width + 4 + MENU_PADDING; /* item width */
728 size_t items = COLS / len; /* items per line */
729 size_t extra = items < cf->menu->nitems ? /* extra space on line */
730 COLS % len : /* - multi-line menu */
731 COLS - (cf->menu->nitems * len); /* - one line menu */
732
733 extra += MENU_PADDING; /* add padding after last item to extra */
734
735 if (idx < items)
736 return (idx * len) + (extra / 2);
737 return ((idx % items) * len) + (extra / 2);
738 }
739
740 static struct cfdisk_menudesc *menu_get_menuitem(struct cfdisk *cf, size_t idx)
741 {
742 struct cfdisk_menudesc *d;
743 size_t i;
744
745 for (i = 0, d = cf->menu->desc; d->name; d++) {
746 if (cf->menu->ignore && strchr(cf->menu->ignore, d->key))
747 continue;
748 if (i++ == idx)
749 return d;
750 }
751
752 return NULL;
753 }
754
755 static struct cfdisk_menudesc *menu_get_menuitem_by_key(struct cfdisk *cf,
756 int key, size_t *idx)
757 {
758 struct cfdisk_menudesc *d;
759
760 for (*idx = 0, d = cf->menu->desc; d->name; d++) {
761 if (cf->menu->ignore && strchr(cf->menu->ignore, d->key))
762 continue;
763 if (key == d->key)
764 return d;
765 (*idx)++;
766 }
767
768 return NULL;
769 }
770
771 static void ui_draw_menuitem(struct cfdisk *cf,
772 struct cfdisk_menudesc *d,
773 size_t idx)
774 {
775 char buf[80 * MB_CUR_MAX];
776 const char *name;
777 size_t width = cf->menu->width + 2; /* 2 = blank around string */
778 int ln, cl;
779
780 name = _(d->name);
781 mbsalign(name, buf, sizeof(buf), &width, MBS_ALIGN_CENTER, 0);
782
783 ln = menuitem_get_line(cf, idx);
784 cl = menuitem_get_column(cf, idx);
785
786 DBG(FRONTEND, dbgprint("ui: menuitem: cl=%d, ln=%d, item='%s'",
787 cl, ln, buf));
788
789 if (cf->menu_idx == idx) {
790 standout();
791 mvprintw(ln, cl, "[%s]", buf);
792 standend();
793 if (d->desc)
794 ui_hint(d->desc);
795 } else
796 mvprintw(ln, cl, "[%s]", buf);
797 }
798
799 static void ui_draw_menu(struct cfdisk *cf)
800 {
801 struct cfdisk_menudesc *d;
802 size_t i = 0;
803
804 assert(cf);
805 assert(cf->menu);
806
807 DBG(FRONTEND, dbgprint("ui: menu: draw start"));
808
809 for (i = MENU_START_LINE; i < (size_t) LINES - 1; i++) {
810 move(i, 0);
811 clrtoeol();
812 }
813
814 menu_update_ignore(cf);
815
816 i = 0;
817 while ((d = menu_get_menuitem(cf, i)))
818 ui_draw_menuitem(cf, d, i++);
819
820 DBG(FRONTEND, dbgprint("ui: menu: draw end."));
821 }
822
823 static void ui_menu_goto(struct cfdisk *cf, int where)
824 {
825 struct cfdisk_menudesc *d;
826 size_t old;
827
828 if (where < 0)
829 where = cf->menu->nitems - 1;
830 else if ((size_t) where > cf->menu->nitems - 1)
831 where = 0;
832 if ((size_t) where == cf->menu_idx)
833 return;
834
835 ui_clean_info();
836
837 old = cf->menu_idx;
838 cf->menu_idx = where;
839
840 d = menu_get_menuitem(cf, old);
841 ui_draw_menuitem(cf, d, old);
842
843 d = menu_get_menuitem(cf, where);
844 ui_draw_menuitem(cf, d, where);
845 }
846
847 /* returns: error: < 0, success: 0, quit: 1 */
848 static int ui_menu_action(struct cfdisk *cf, int key)
849 {
850 assert(cf);
851 assert(cf->menu);
852 assert(cf->menu->callback);
853
854 if (key == 0) {
855 struct cfdisk_menudesc *d = menu_get_menuitem(cf, cf->menu_idx);
856 if (!d)
857 return 0;
858 key = d->key;
859
860 } else if (key != 'w' && key != 'W')
861 key = tolower(key); /* case insensitive except 'W'rite */
862
863 DBG(FRONTEND, dbgprint("ui: menu action: key=%c", key));
864
865 if (cf->menu->ignore && strchr(cf->menu->ignore, key)) {
866 DBG(FRONTEND, dbgprint(" ignore '%c'", key));
867 return 0;
868 }
869
870 return cf->menu->callback(cf, key);
871 }
872
873 static void ui_draw_partition(struct cfdisk *cf, size_t i)
874 {
875 int ln = TABLE_START_LINE + 1 + i; /* skip table header */
876 int cl = ARROW_CURSOR_WIDTH; /* we need extra space for cursor */
877
878 DBG(FRONTEND, dbgprint("ui: draw partition %zu", i));
879
880 if (cf->lines_idx == i) {
881 standout();
882 mvaddstr(ln, 0, ARROW_CURSOR_STRING);
883 mvaddstr(ln, cl, cf->lines[i + 1]);
884 standend();
885 } else {
886 mvaddstr(ln, 0, ARROW_CURSOR_DUMMY);
887 mvaddstr(ln, cl, cf->lines[i + 1]);
888 }
889
890 }
891
892 static int ui_draw_table(struct cfdisk *cf)
893 {
894 int cl = ARROW_CURSOR_WIDTH;
895 size_t i, nparts = fdisk_table_get_nents(cf->table);
896
897 DBG(FRONTEND, dbgprint("ui: draw table"));
898
899 if (cf->nlines - 2 < cf->lines_idx)
900 cf->lines_idx = cf->nlines - 2; /* don't count header */
901
902 /* print header */
903 attron(A_BOLD);
904 mvaddstr(TABLE_START_LINE, cl, cf->lines[0]);
905 attroff(A_BOLD);
906
907 /* print partitions */
908 for (i = 0; i < nparts; i++)
909 ui_draw_partition(cf, i);
910
911 return 0;
912 }
913
914 static int ui_table_goto(struct cfdisk *cf, int where)
915 {
916 size_t old;
917 size_t nparts = fdisk_table_get_nents(cf->table);
918
919 DBG(FRONTEND, dbgprint("ui: goto table %d", where));
920
921 if (where < 0)
922 where = 0;
923 else if ((size_t) where > nparts - 1)
924 where = nparts - 1;
925
926 if ((size_t) where == cf->lines_idx)
927 return 0;
928
929 old = cf->lines_idx;
930 cf->lines_idx = where;
931
932 ui_draw_partition(cf, old); /* cleanup old */
933 ui_draw_partition(cf, where); /* draw new */
934 ui_clean_info();
935 ui_draw_menu(cf);
936 refresh();
937 return 0;
938 }
939
940 static int ui_refresh(struct cfdisk *cf)
941 {
942 char *id = NULL;
943 uint64_t bytes = cf->cxt->total_sectors * cf->cxt->sector_size;
944 char *strsz = size_to_human_string(SIZE_SUFFIX_SPACE
945 | SIZE_SUFFIX_3LETTER, bytes);
946 erase();
947
948 if (!ui_enabled)
949 return -EINVAL;
950
951 /* header */
952 attron(A_BOLD);
953 ui_center(0, _("Disk: %s"), cf->cxt->dev_path);
954 attroff(A_BOLD);
955 ui_center(1, _("Size: %s, %ju bytes, %ju sectors"),
956 strsz, bytes, (uintmax_t) cf->cxt->total_sectors);
957 if (fdisk_get_disklabel_id(cf->cxt, &id) == 0 && id)
958 ui_center(2, _("Label: %s, identifier: %s"),
959 cf->cxt->label->name, id);
960 else
961 ui_center(2, _("Label: %s"));
962 free(strsz);
963
964 ui_draw_table(cf);
965 ui_draw_menu(cf);
966 refresh();
967 return 0;
968 }
969
970 static ssize_t ui_get_string(struct cfdisk *cf, const char *prompt,
971 const char *hint, char *buf, size_t len)
972 {
973 size_t cells = 0;
974 ssize_t i = 0, rc = -1;
975 wint_t c;
976 int ln = MENU_START_LINE, cl = 1;
977
978 assert(cf);
979 assert(buf);
980 assert(len);
981
982 move(ln, 0);
983 clrtoeol();
984
985 if (prompt) {
986 mvaddstr(ln, cl, prompt);
987 cl += mbs_safe_width(prompt);
988 }
989
990 /* default value */
991 if (*buf) {
992 i = strlen(buf);
993 cells = mbs_safe_width(buf);
994 mvaddstr(ln, cl, buf);
995 }
996
997 if (hint)
998 ui_hint(hint);
999 else
1000 ui_clean_hint();
1001
1002 move(ln, cl + cells);
1003 curs_set(1);
1004 refresh();
1005
1006 while (1) {
1007 #if !defined(HAVE_SLCURSES_H) && !defined(HAVE_SLANG_SLCURSES_H) && \
1008 defined(HAVE_LIBNCURSESW) && defined(HAVE_WIDECHAR)
1009 if (get_wch(&c) == ERR) {
1010 #else
1011 if ((c = getch()) == ERR) {
1012 #endif
1013 if (!isatty(STDIN_FILENO))
1014 exit(2);
1015 else
1016 goto done;
1017 }
1018 if (c == '\r' || c == '\n' || c == KEY_ENTER)
1019 break;
1020
1021 switch (c) {
1022 case KEY_ESC:
1023 rc = -CFDISK_ERR_ESC;
1024 goto done;
1025 case KEY_DELETE:
1026 case '\b':
1027 case KEY_BACKSPACE:
1028 if (i > 0) {
1029 cells--;
1030 i = mbs_truncate(buf, &cells);
1031 if (i < 0)
1032 goto done;
1033 mvaddch(ln, cl + cells, ' ');
1034 move(ln, cl + cells);
1035 } else
1036 beep();
1037 break;
1038 default:
1039 #if defined(HAVE_LIBNCURSESW) && defined(HAVE_WIDECHAR)
1040 if (i + 1 < (ssize_t) len && iswprint(c)) {
1041 wchar_t wc = (wchar_t) c;
1042 char s[MB_CUR_MAX + 1];
1043 int sz = wctomb(s, wc);
1044
1045 if (sz > 0 && sz + i < (ssize_t) len) {
1046 s[sz] = '\0';
1047 mvaddnstr(ln, cl + cells, s, sz);
1048 memcpy(buf + i, s, sz);
1049 i += sz;
1050 buf[i] = '\0';
1051 cells += wcwidth(wc);
1052 } else
1053 beep();
1054 }
1055 #else
1056 if (i + 1 < (ssize_t) len && isprint(c)) {
1057 mvaddch(ln, cl + cells, c);
1058 str[i++] = c;
1059 str[i] = '\0';
1060 cells++;
1061 }
1062 #endif
1063 else
1064 beep();
1065 }
1066 refresh();
1067 }
1068
1069 rc = i; /* success */
1070 done:
1071 move(ln, 0);
1072 clrtoeol();
1073 curs_set(0);
1074 refresh();
1075
1076 return rc;
1077 }
1078
1079 /* @res is default value as well as result in bytes */
1080 static int ui_get_size(struct cfdisk *cf, const char *prompt, uintmax_t *res,
1081 uintmax_t low, uintmax_t up)
1082 {
1083 char buf[128];
1084 uintmax_t user = 0;
1085 ssize_t rc;
1086 char *dflt = size_to_human_string(0, *res);
1087
1088 DBG(FRONTEND, dbgprint("ui: get_size (default=%ju)", *res));
1089
1090 ui_clean_info();
1091
1092 do {
1093 int pwr = 0;
1094
1095 snprintf(buf, sizeof(buf), "%s", dflt);
1096 rc = ui_get_string(cf, prompt,
1097 _("The size may be followed by MiB, GiB or TiB suffix "
1098 "(the \"iB\" is optional)."),
1099 buf, sizeof(buf));
1100 if (rc == 0) {
1101 ui_warnx(_("Please, specify size."));
1102 continue; /* nothing specified */
1103 } else if (rc == -CFDISK_ERR_ESC)
1104 break; /* cancel dialog */
1105
1106 rc = parse_size(buf, &user, &pwr); /* response, parse */
1107 if (rc == 0) {
1108 DBG(FRONTEND, dbgprint("ui: get_size user=%ju, power=%d", user, pwr));
1109 if (user < low) {
1110 ui_warnx(_("Minimal size is %ju"), low);
1111 rc = -ERANGE;
1112 }
1113 if (user > up && pwr && user < up + (1ULL << pwr * 10))
1114 /* ignore when the user specified size overflow
1115 * with in range specified by suffix (e.g. MiB) */
1116 user = up;
1117
1118 if (user > up) {
1119 ui_warnx(_("Maximal size is %ju bytes."), up);
1120 rc = -ERANGE;
1121 }
1122 }
1123 } while (rc != 0);
1124
1125 if (rc == 0)
1126 *res = user;
1127 free(dflt);
1128
1129 DBG(FRONTEND, dbgprint("ui: get_size (result=%ju, rc=%zd)", *res, rc));
1130 return rc;
1131 }
1132
1133
1134 static int ui_run(struct cfdisk *cf)
1135 {
1136 int rc;
1137
1138 DBG(FRONTEND, dbgprint("ui: start COLS=%d, LINES=%d", COLS, LINES));
1139
1140 menu_push(cf, CFDISK_MENU_MAIN, NULL);
1141
1142 rc = ui_refresh(cf);
1143 if (rc)
1144 return rc;
1145
1146 do {
1147 int rc = 0, key = getch();
1148
1149 switch (key) {
1150 case KEY_DOWN:
1151 case '\016': /* ^N */
1152 case 'j': /* Vi-like alternative */
1153 ui_table_goto(cf, cf->lines_idx + 1);
1154 break;
1155 case KEY_UP:
1156 case '\020': /* ^P */
1157 case 'k': /* Vi-like alternative */
1158 ui_table_goto(cf, cf->lines_idx - 1);
1159 break;
1160 case KEY_HOME:
1161 ui_table_goto(cf, 0);
1162 break;
1163 case KEY_END:
1164 ui_table_goto(cf, cf->nlines - 1);
1165 break;
1166 ui_menu_action(cf, 0);
1167 break;
1168 case KEY_LEFT:
1169 #ifdef KEY_BTAB
1170 case KEY_BTAB:
1171 #endif
1172 ui_menu_goto(cf, cf->menu_idx - 1);
1173 break;
1174 case KEY_RIGHT:
1175 case '\t':
1176 ui_menu_goto(cf, cf->menu_idx + 1);
1177 break;
1178 case KEY_ENTER:
1179 case '\n':
1180 case '\r':
1181 rc = ui_menu_action(cf, 0);
1182 break;
1183 default:
1184 rc = ui_menu_action(cf, key);
1185 if (rc < 0)
1186 beep();
1187 break;
1188 }
1189
1190 if (rc == 1)
1191 break; /* quit */
1192 } while (1);
1193
1194 menu_pop(cf);
1195
1196 DBG(FRONTEND, dbgprint("ui: end"));
1197
1198 return 0;
1199 }
1200
1201 int main(int argc, char *argv[])
1202 {
1203 struct cfdisk _cf = { .lines_idx = 0 },
1204 *cf = &_cf;
1205
1206 setlocale(LC_ALL, "");
1207 bindtextdomain(PACKAGE, LOCALEDIR);
1208 textdomain(PACKAGE);
1209 atexit(close_stdout);
1210
1211 fdisk_init_debug(0);
1212 cf->cxt = fdisk_new_context();
1213 if (!cf->cxt)
1214 err(EXIT_FAILURE, _("failed to allocate libfdisk context"));
1215
1216 fdisk_context_set_ask(cf->cxt, ask_callback, (void *) cf);
1217 fdisk_context_enable_freespace(cf->cxt, 1);
1218
1219 if (argc != 2)
1220 err(EXIT_FAILURE, "usage: %s <device>", argv[0]);
1221
1222 if (fdisk_context_assign_device(cf->cxt, argv[optind], 0) != 0)
1223 err(EXIT_FAILURE, _("cannot open %s"), argv[optind]);
1224
1225 cols_init(cf);
1226
1227 if (lines_refresh(cf))
1228 errx(EXIT_FAILURE, _("failed to read partitions"));
1229
1230 /* Don't use err(), warn() from this point */
1231 ui_init(cf);
1232 ui_run(cf);
1233 ui_end(cf);
1234
1235 free(cf->lines);
1236 free(cf->linesbuf);
1237 fdisk_unref_table(cf->table);
1238 fdisk_free_context(cf->cxt);
1239 return EXIT_SUCCESS;
1240 }