1/*	$NetBSD: interact.c,v 1.34 2010/05/28 07:40:53 dholland Exp $	*/
2
3/*
4 * Copyright (c) 1997 Christos Zoulas.  All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#if HAVE_NBTOOL_CONFIG_H
28#include "nbtool_config.h"
29#endif
30
31#include <sys/cdefs.h>
32#ifndef lint
33__RCSID("$NetBSD: interact.c,v 1.34 2010/05/28 07:40:53 dholland Exp $");
34#endif /* lint */
35
36#include <sys/param.h>
37#define FSTYPENAMES
38#define DKTYPENAMES
39
40#include <err.h>
41#include <stdio.h>
42#include <string.h>
43#include <stdlib.h>
44
45#if HAVE_NBTOOL_CONFIG_H
46#define	getmaxpartitions()	MAXPARTITIONS
47#include <nbinclude/sys/disklabel.h>
48#else
49#include <util.h>
50#include <sys/disklabel.h>
51#endif /* HAVE_NBTOOL_CONFIG_H */
52
53#include "extern.h"
54
55static void	cmd_help(struct disklabel *, char *, int);
56static void	cmd_chain(struct disklabel *, char *, int);
57static void	cmd_print(struct disklabel *, char *, int);
58static void	cmd_printall(struct disklabel *, char *, int);
59static void	cmd_info(struct disklabel *, char *, int);
60static void	cmd_part(struct disklabel *, char *, int);
61static void	cmd_label(struct disklabel *, char *, int);
62static void	cmd_round(struct disklabel *, char *, int);
63static void	cmd_name(struct disklabel *, char *, int);
64static void	cmd_listfstypes(struct disklabel *, char *, int);
65static int	runcmd(struct disklabel *, char *, int);
66static int	getinput(const char *, const char *, const char *, char *);
67static int	alphacmp(const void *, const void *);
68static void	defnum(struct disklabel *, char *, uint32_t);
69static void	dumpnames(const char *, const char * const *, size_t);
70static intmax_t	getnum(struct disklabel *, char *, intmax_t);
71
72static int rounding = 0;	/* sector rounding */
73static int chaining = 0;	/* make partitions contiguous */
74
75static struct cmds {
76	const char *name;
77	void (*func)(struct disklabel *, char *, int);
78	const char *help;
79} cmds[] = {
80	{ "?",	cmd_help,	"print this menu" },
81	{ "C",	cmd_chain,	"make partitions contiguous" },
82	{ "E",	cmd_printall,	"print disk label and current partition table"},
83	{ "I",	cmd_info,	"change label information" },
84	{ "L",	cmd_listfstypes,"list all known file system types" },
85	{ "N",	cmd_name,	"name the label" },
86	{ "P",	cmd_print,	"print current partition table" },
87	{ "Q",	NULL,		"quit" },
88	{ "R",	cmd_round,	"rounding (c)ylinders (s)ectors" },
89	{ "W",	cmd_label,	"write the current partition table" },
90	{ NULL, NULL,		NULL }
91};
92
93
94
95static void
96cmd_help(struct disklabel *lp, char *s, int fd)
97{
98	struct cmds *cmd;
99
100	for (cmd = cmds; cmd->name != NULL; cmd++)
101		printf("%s\t%s\n", cmd->name, cmd->help);
102	printf("[a-%c]\tdefine named partition\n",
103	    'a' + getmaxpartitions() - 1);
104}
105
106
107static void
108cmd_chain(struct disklabel *lp, char *s, int fd)
109{
110	int	i;
111	char	line[BUFSIZ];
112
113	i = getinput(":", "Automatically adjust partitions",
114	    chaining ? "yes" : "no", line);
115	if (i <= 0)
116		return;
117
118	switch (line[0]) {
119	case 'y':
120		chaining = 1;
121		return;
122	case 'n':
123		chaining = 0;
124		return;
125	default:
126		printf("Invalid answer\n");
127		return;
128	}
129}
130
131
132static void
133cmd_printall(struct disklabel *lp, char *s, int fd)
134{
135
136	showinfo(stdout, lp, specname);
137	showpartitions(stdout, lp, Cflag);
138}
139
140
141static void
142cmd_print(struct disklabel *lp, char *s, int fd)
143{
144
145	showpartitions(stdout, lp, Cflag);
146}
147
148
149static void
150cmd_info(struct disklabel *lp, char *s, int fd)
151{
152	char	line[BUFSIZ];
153	char	def[BUFSIZ];
154	int	v, i;
155	u_int32_t u;
156
157	printf("# Current values:\n");
158	showinfo(stdout, lp, specname);
159
160	/* d_type */
161	for (;;) {
162		i = lp->d_type;
163		if (i < 0 || i >= DKMAXTYPES)
164			i = 0;
165		snprintf(def, sizeof(def), "%s", dktypenames[i]);
166		i = getinput(":", "Disk type [?]", def, line);
167		if (i == -1)
168			return;
169		else if (i == 0)
170			break;
171		if (!strcmp(line, "?")) {
172			dumpnames("Supported disk types", dktypenames,
173			    DKMAXTYPES);
174			continue;
175		}
176		for (i = 0; i < DKMAXTYPES; i++) {
177			if (!strcasecmp(dktypenames[i], line)) {
178				lp->d_type = i;
179				goto done_typename;
180			}
181		}
182		v = atoi(line);
183		if ((unsigned)v >= DKMAXTYPES) {
184			warnx("Unknown disk type: %s", line);
185			continue;
186		}
187		lp->d_type = v;
188 done_typename:
189		break;
190	}
191
192	/* d_typename */
193	snprintf(def, sizeof(def), "%.*s",
194	    (int) sizeof(lp->d_typename), lp->d_typename);
195	i = getinput(":", "Disk name", def, line);
196	if (i == -1)
197		return;
198	else if (i == 1)
199		(void) strncpy(lp->d_typename, line, sizeof(lp->d_typename));
200
201	/* d_packname */
202	cmd_name(lp, s, fd);
203
204	/* d_npartitions */
205	for (;;) {
206		snprintf(def, sizeof(def), "%" PRIu16, lp->d_npartitions);
207		i = getinput(":", "Number of partitions", def, line);
208		if (i == -1)
209			return;
210		else if (i == 0)
211			break;
212		if (sscanf(line, "%" SCNu32, &u) != 1) {
213			printf("Invalid number of partitions `%s'\n", line);
214			continue;
215		}
216		lp->d_npartitions = u;
217		break;
218	}
219
220	/* d_secsize */
221	for (;;) {
222		snprintf(def, sizeof(def), "%" PRIu32, lp->d_secsize);
223		i = getinput(":", "Sector size (bytes)", def, line);
224		if (i == -1)
225			return;
226		else if (i == 0)
227			break;
228		if (sscanf(line, "%" SCNu32, &u) != 1) {
229			printf("Invalid sector size `%s'\n", line);
230			continue;
231		}
232		lp->d_secsize = u;
233		break;
234	}
235
236	/* d_nsectors */
237	for (;;) {
238		snprintf(def, sizeof(def), "%" PRIu32, lp->d_nsectors);
239		i = getinput(":", "Number of sectors per track", def, line);
240		if (i == -1)
241			return;
242		else if (i == 0)
243			break;
244		if (sscanf(line, "%" SCNu32, &u) != 1) {
245			printf("Invalid number of sectors `%s'\n", line);
246			continue;
247		}
248		lp->d_nsectors = u;
249		break;
250	}
251
252	/* d_ntracks */
253	for (;;) {
254		snprintf(def, sizeof(def), "%" PRIu32, lp->d_ntracks);
255		i = getinput(":", "Number of tracks per cylinder", def, line);
256		if (i == -1)
257			return;
258		else if (i == 0)
259			break;
260		if (sscanf(line, "%" SCNu32, &u) != 1) {
261			printf("Invalid number of tracks `%s'\n", line);
262			continue;
263		}
264		lp->d_ntracks = u;
265		break;
266	}
267
268	/* d_secpercyl */
269	for (;;) {
270		snprintf(def, sizeof(def), "%" PRIu32, lp->d_secpercyl);
271		i = getinput(":", "Number of sectors/cylinder", def, line);
272		if (i == -1)
273			return;
274		else if (i == 0)
275			break;
276		if (sscanf(line, "%" SCNu32, &u) != 1) {
277			printf("Invalid number of sector/cylinder `%s'\n",
278			    line);
279			continue;
280		}
281		lp->d_secpercyl = u;
282		break;
283	}
284
285	/* d_ncylinders */
286	for (;;) {
287		snprintf(def, sizeof(def), "%" PRIu32, lp->d_ncylinders);
288		i = getinput(":", "Total number of cylinders", def, line);
289		if (i == -1)
290			return;
291		else if (i == 0)
292			break;
293		if (sscanf(line, "%" SCNu32, &u) != 1) {
294			printf("Invalid sector size `%s'\n", line);
295			continue;
296		}
297		lp->d_ncylinders = u;
298		break;
299	}
300
301	/* d_secperunit */
302	for (;;) {
303		snprintf(def, sizeof(def), "%" PRIu32, lp->d_secperunit);
304		i = getinput(":", "Total number of sectors", def, line);
305		if (i == -1)
306			return;
307		else if (i == 0)
308			break;
309		if (sscanf(line, "%" SCNu32, &u) != 1) {
310			printf("Invalid number of sectors `%s'\n", line);
311			continue;
312		}
313		lp->d_secperunit = u;
314		break;
315	}
316
317	/* d_rpm */
318
319	/* d_interleave */
320	for (;;) {
321		snprintf(def, sizeof(def), "%" PRIu16, lp->d_interleave);
322		i = getinput(":", "Hardware sectors interleave", def, line);
323		if (i == -1)
324			return;
325		else if (i == 0)
326			break;
327		if (sscanf(line, "%" SCNu32, &u) != 1) {
328			printf("Invalid sector interleave `%s'\n", line);
329			continue;
330		}
331		lp->d_interleave = u;
332		break;
333	}
334
335	/* d_trackskew */
336	for (;;) {
337		snprintf(def, sizeof(def), "%" PRIu16, lp->d_trackskew);
338		i = getinput(":", "Sector 0 skew, per track", def, line);
339		if (i == -1)
340			return;
341		else if (i == 0)
342			break;
343		if (sscanf(line, "%" SCNu32, &u) != 1) {
344			printf("Invalid track sector skew `%s'\n", line);
345			continue;
346		}
347		lp->d_trackskew = u;
348		break;
349	}
350
351	/* d_cylskew */
352	for (;;) {
353		snprintf(def, sizeof(def), "%" PRIu16, lp->d_cylskew);
354		i = getinput(":", "Sector 0 skew, per cylinder", def, line);
355		if (i == -1)
356			return;
357		else if (i == 0)
358			break;
359		if (sscanf(line, "%" SCNu32, &u) != 1) {
360			printf("Invalid cylinder sector `%s'\n", line);
361			continue;
362		}
363		lp->d_cylskew = u;
364		break;
365	}
366
367	/* d_headswitch */
368	for (;;) {
369		snprintf(def, sizeof(def), "%" PRIu32, lp->d_headswitch);
370		i = getinput(":", "Head switch time (usec)", def, line);
371		if (i == -1)
372			return;
373		else if (i == 0)
374			break;
375		if (sscanf(line, "%" SCNu32, &u) != 1) {
376			printf("Invalid head switch time `%s'\n", line);
377			continue;
378		}
379		lp->d_headswitch = u;
380		break;
381	}
382
383	/* d_trkseek */
384	for (;;) {
385		snprintf(def, sizeof(def), "%" PRIu32, lp->d_trkseek);
386		i = getinput(":", "Track seek time (usec)", def, line);
387		if (i == -1)
388			return;
389		else if (i == 0)
390			break;
391		if (sscanf(line, "%" SCNu32, &u) != 1) {
392			printf("Invalid track seek time `%s'\n", line);
393			continue;
394		}
395		lp->d_trkseek = u;
396		break;
397	}
398}
399
400
401static void
402cmd_name(struct disklabel *lp, char *s, int fd)
403{
404	char	line[BUFSIZ];
405	char	def[BUFSIZ];
406	int	i;
407
408	snprintf(def, sizeof(def), "%.*s",
409	    (int) sizeof(lp->d_packname), lp->d_packname);
410	i = getinput(":", "Label name", def, line);
411	if (i <= 0)
412		return;
413	(void) strncpy(lp->d_packname, line, sizeof(lp->d_packname));
414}
415
416
417static void
418cmd_round(struct disklabel *lp, char *s, int fd)
419{
420	int	i;
421	char	line[BUFSIZ];
422
423	i = getinput(":", "Rounding", rounding ? "cylinders" : "sectors", line);
424	if (i <= 0)
425		return;
426
427	switch (line[0]) {
428	case 'c':
429	case 'C':
430		rounding = 1;
431		return;
432	case 's':
433	case 'S':
434		rounding = 0;
435		return;
436	default:
437		printf("Rounding can be (c)ylinders or (s)ectors\n");
438		return;
439	}
440}
441
442
443static void
444cmd_part(struct disklabel *lp, char *s, int fd)
445{
446	int	i;
447	intmax_t im;
448	char	line[BUFSIZ];
449	char	def[BUFSIZ];
450	int	part;
451	struct partition *p, ps;
452
453	part = s[0] - 'a';
454	p = &lp->d_partitions[part];
455	if (part >= lp->d_npartitions)
456		lp->d_npartitions = part + 1;
457
458	(void)memcpy(&ps, p, sizeof(ps));
459
460	for (;;) {
461		i = p->p_fstype;
462		if (i < 0 || i >= FSMAXTYPES)
463			i = 0;
464		snprintf(def, sizeof(def), "%s", fstypenames[i]);
465		i = getinput(":", "Filesystem type [?]", def, line);
466		if (i == -1)
467			return;
468		else if (i == 0)
469			break;
470		if (!strcmp(line, "?")) {
471			dumpnames("Supported file system types",
472			    fstypenames, FSMAXTYPES);
473			continue;
474		}
475		for (i = 0; i < FSMAXTYPES; i++)
476			if (!strcasecmp(line, fstypenames[i])) {
477				p->p_fstype = i;
478				goto done_typename;
479			}
480		printf("Invalid file system typename `%s'\n", line);
481		continue;
482 done_typename:
483		break;
484	}
485	for (;;) {
486		defnum(lp, def, p->p_offset);
487		i = getinput(":",
488		    "Start offset ('x' to start after partition 'x')",
489		    def, line);
490		if (i == -1)
491			return;
492		else if (i == 0)
493			break;
494		if (line[1] == '\0' &&
495	    		line[0] >= 'a' && line[0] < 'a' + getmaxpartitions()) {
496			struct partition *cp = lp->d_partitions;
497
498			if ((cp[line[0] - 'a'].p_offset +
499			    cp[line[0] - 'a'].p_size) >= lp->d_secperunit) {
500				printf("Bad offset `%s'\n", line);
501				continue;
502			} else {
503				p->p_offset = cp[line[0] - 'a'].p_offset +
504				    cp[line[0] - 'a'].p_size;
505			}
506		} else {
507			if ((im = getnum(lp, line, 0)) == -1 || im < 0) {
508				printf("Bad offset `%s'\n", line);
509				continue;
510			} else if (im > 0xffffffffLL ||
511				   (uint32_t)im > lp->d_secperunit) {
512				printf("Offset `%s' out of range\n", line);
513				continue;
514			}
515			p->p_offset = (uint32_t)im;
516		}
517		break;
518	}
519	for (;;) {
520		defnum(lp, def, p->p_size);
521		i = getinput(":", "Partition size ('$' for all remaining)",
522		    def, line);
523		if (i == -1)
524			return;
525		else if (i == 0)
526			break;
527		if ((im = getnum(lp, line, lp->d_secperunit - p->p_offset))
528		    == -1) {
529			printf("Bad size `%s'\n", line);
530			continue;
531		} else if (im > 0xffffffffLL ||
532			   (im + p->p_offset) > lp->d_secperunit) {
533			printf("Size `%s' out of range\n", line);
534			continue;
535		}
536		p->p_size = im;
537		break;
538	}
539
540	if (memcmp(&ps, p, sizeof(ps)))
541		showpartition(stdout, lp, part, Cflag);
542	if (chaining) {
543		int offs = -1;
544		struct partition *cp = lp->d_partitions;
545		for (i = 0; i < lp->d_npartitions; i++) {
546			if (cp[i].p_fstype != FS_UNUSED) {
547				if (offs != -1 && cp[i].p_offset != (uint32_t)offs) {
548					cp[i].p_offset = offs;
549					showpartition(stdout, lp, i, Cflag);
550					}
551				offs = cp[i].p_offset + cp[i].p_size;
552			}
553		}
554	}
555}
556
557
558static void
559cmd_label(struct disklabel *lp, char *s, int fd)
560{
561	char	line[BUFSIZ];
562	int	i;
563
564	i = getinput("?", "Label disk", "n", line);
565	if (i <= 0 || (*line != 'y' && *line != 'Y') )
566		return;
567
568	if (checklabel(lp) != 0) {
569		printf("Label not written\n");
570		return;
571	}
572
573	if (writelabel(fd, lp) != 0) {
574		printf("Label not written\n");
575		return;
576	}
577	printf("Label written\n");
578}
579
580
581static void
582cmd_listfstypes(struct disklabel *lp, char *s, int fd)
583{
584
585	(void)list_fs_types();
586}
587
588
589static int
590runcmd(struct disklabel *lp, char *line, int fd)
591{
592	struct cmds *cmd;
593
594	for (cmd = cmds; cmd->name != NULL; cmd++)
595		if (strncmp(line, cmd->name, strlen(cmd->name)) == 0) {
596			if (cmd->func == NULL)
597				return -1;
598			(*cmd->func)(lp, line, fd);
599			return 0;
600		}
601
602	if (line[1] == '\0' &&
603	    line[0] >= 'a' && line[0] < 'a' + getmaxpartitions()) {
604		cmd_part(lp, line, fd);
605		return 0;
606	}
607
608	printf("Unknown command %s\n", line);
609	return 1;
610}
611
612
613static int
614getinput(const char *sep, const char *prompt, const char *def, char *line)
615{
616
617	for (;;) {
618		printf("%s", prompt);
619		if (def)
620			printf(" [%s]", def);
621		printf("%s ", sep);
622
623		if (fgets(line, BUFSIZ, stdin) == NULL)
624			return -1;
625		if (line[0] == '\n' || line[0] == '\0') {
626			if (def)
627				return 0;
628		}
629		else {
630			char *p;
631
632			if ((p = strrchr(line, '\n')) != NULL)
633				*p = '\0';
634			return 1;
635		}
636	}
637}
638
639static int
640alphacmp(const void *a, const void *b)
641{
642
643	return (strcasecmp(*(const char * const*)a, *(const char * const*)b));
644}
645
646
647static void
648dumpnames(const char *prompt, const char * const *olist, size_t numentries)
649{
650	int	w;
651	size_t	i, entry, lines;
652	int	columns, width;
653	const char *p;
654	const char **list;
655
656	if ((list = (const char **)malloc(sizeof(char *) * numentries)) == NULL)
657		err(1, "malloc");
658	width = 0;
659	printf("%s:\n", prompt);
660	for (i = 0; i < numentries; i++) {
661		list[i] = olist[i];
662		w = strlen(list[i]);
663		if (w > width)
664			width = w;
665	}
666#if 0
667	for (i = 0; i < numentries; i++)
668		printf("%s%s", i == 0 ? "" : ", ", list[i]);
669	puts("");
670#endif
671	(void)qsort(list, numentries, sizeof(char *), alphacmp);
672	width++;		/* want two spaces between items */
673	width = (width + 8) &~ 7;
674
675#define ttywidth 72
676	columns = ttywidth / width;
677#undef ttywidth
678	if (columns == 0)
679		columns = 1;
680	lines = (numentries + columns - 1) / columns;
681	/* Output sorted by columns */
682	for (i = 0; i < lines; i++) {
683		putc('\t', stdout);
684		entry = i;
685		for (;;) {
686			p = list[entry];
687			fputs(p, stdout);
688			entry += lines;
689			if (entry >= numentries)
690				break;
691			w = strlen(p);
692			while (w < width) {
693				w = (w + 8) & ~7;
694				putc('\t', stdout);
695			}
696		}
697		putc('\n', stdout);
698	}
699	free(list);
700}
701
702
703static void
704defnum(struct disklabel *lp, char *buf, uint32_t size)
705{
706
707	(void) snprintf(buf, BUFSIZ, "%.40gc, %" PRIu32 "s, %.40gM",
708	    size / (float) lp->d_secpercyl,
709	    size, size  * (lp->d_secsize / (float) (1024 * 1024)));
710}
711
712
713static intmax_t
714getnum(struct disklabel *lp, char *buf, intmax_t defaultval)
715{
716	char	*ep;
717	double	 d;
718	intmax_t rv;
719
720	if (defaultval && buf[0] == '$' && buf[1] == 0)
721		return defaultval;
722
723	d = strtod(buf, &ep);
724	if (buf == ep)
725		return -1;
726
727#define ROUND(a)	((((a) / lp->d_secpercyl) + \
728		 	 (((a) % lp->d_secpercyl) ? 1 : 0)) * lp->d_secpercyl)
729
730	switch (*ep) {
731	case '\0':
732	case 's':
733	case 'S':
734		rv = (intmax_t) d;
735		break;
736
737	case 'c':
738	case 'C':
739		rv = (intmax_t) (d * lp->d_secpercyl);
740		break;
741
742	case 'k':
743	case 'K':
744		rv =  (intmax_t) (d * 1024 / lp->d_secsize);
745		break;
746
747	case 'm':
748	case 'M':
749		rv =  (intmax_t) (d * 1024 * 1024 / lp->d_secsize);
750		break;
751
752	case 'g':
753	case 'G':
754		rv =  (intmax_t) (d * 1024 * 1024 * 1024 / lp->d_secsize);
755		break;
756
757	case 't':
758	case 'T':
759		rv =  (intmax_t) (d * 1024 * 1024 * 1024 * 1024 / lp->d_secsize);
760		break;
761
762	default:
763		printf("Unit error %c\n", *ep);
764		printf("Valid units: (S)ectors, (C)ylinders, (K)ilo, (M)ega, "
765		    "(G)iga, (T)era");
766		return -1;
767	}
768
769	if (rounding)
770		return ROUND(rv);
771	else
772		return rv;
773}
774
775
776void
777interact(struct disklabel *lp, int fd)
778{
779	char	line[BUFSIZ];
780
781	puts("Enter '?' for help");
782	for (;;) {
783		if (getinput(">", "partition", NULL, line) == -1)
784			return;
785		if (runcmd(lp, line, fd) == -1)
786			return;
787	}
788}
789