1/*	$NetBSD: ccdconfig.c,v 1.50 2011/01/04 23:31:29 wiz Exp $	*/
2
3/*-
4 * Copyright (c) 1996, 1997 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Jason R. Thorpe.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33#ifndef lint
34__COPYRIGHT("@(#) Copyright (c) 1996, 1997\
35 The NetBSD Foundation, Inc.  All rights reserved.");
36__RCSID("$NetBSD: ccdconfig.c,v 1.50 2011/01/04 23:31:29 wiz Exp $");
37#endif
38
39#include <sys/param.h>
40#include <sys/ioctl.h>
41#include <sys/disklabel.h>
42#include <sys/disk.h>
43#include <sys/stat.h>
44#include <sys/sysctl.h>
45#include <ctype.h>
46#include <err.h>
47#include <errno.h>
48#include <fcntl.h>
49#include <kvm.h>
50#include <limits.h>
51#include <nlist.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <unistd.h>
56#include <util.h>
57
58#include <dev/ccdvar.h>
59
60#include "pathnames.h"
61
62
63static	size_t lineno;
64static	gid_t egid;
65static	int verbose;
66static	const char *ccdconf = _PATH_CCDCONF;
67
68static	char *core;
69static	char *kernel;
70
71static struct	flagval {
72	const char *fv_flag;
73	int	fv_val;
74} flagvaltab[] = {
75	{ "CCDF_UNIFORM",	CCDF_UNIFORM },
76	{ "CCDF_NOLABEL",	CCDF_NOLABEL },
77	{ NULL,			0 },
78};
79
80static	struct nlist nl[] = {
81	{ .n_name = "_ccd_softc" },
82#define SYM_CCDSOFTC		0
83	{ .n_name = "_numccd" },
84#define SYM_NUMCCD		1
85	{ .n_name = "_ccd_softc_elemsize" },
86#define SYM_CCDSOFTCELEMSIZE	2
87	{ .n_name = NULL },
88};
89
90#define CCD_CONFIG		0	/* configure a device */
91#define CCD_CONFIGALL		1	/* configure all devices */
92#define CCD_UNCONFIG		2	/* unconfigure a device */
93#define CCD_UNCONFIGALL		3	/* unconfigure all devices */
94#define CCD_DUMP		4	/* dump a ccd's configuration */
95
96static	int checkdev(char *);
97static	int do_io(char *, u_long, struct ccd_ioctl *);
98static	int do_single(int, char **, int);
99static	int do_all(int);
100static	int dump_ccd(int, char **, int);
101static	int flags_to_val(char *);
102static	int pathtounit(char *, int *);
103static	void print_ccd_info(struct ccd_softc *, kvm_t *);
104static	char *resolve_ccdname(char *);
105__dead static	void usage(void);
106
107int
108main(int argc, char *argv[])
109{
110	int ch, options = 0, action = CCD_CONFIG;
111
112	egid = getegid();
113	setegid(getgid());
114	while ((ch = getopt(argc, argv, "cCf:gM:N:uUv")) != -1) {
115		switch (ch) {
116		case 'c':
117			action = CCD_CONFIG;
118			++options;
119			break;
120
121		case 'C':
122			action = CCD_CONFIGALL;
123			++options;
124			break;
125
126		case 'f':
127			ccdconf = optarg;
128			break;
129
130		case 'g':
131			action = CCD_DUMP;
132			break;
133
134		case 'M':
135			core = optarg;
136			break;
137
138		case 'N':
139			kernel = optarg;
140			break;
141
142		case 'u':
143			action = CCD_UNCONFIG;
144			++options;
145			break;
146
147		case 'U':
148			action = CCD_UNCONFIGALL;
149			++options;
150			break;
151
152		case 'v':
153			verbose = 1;
154			break;
155
156		default:
157			usage();
158		}
159	}
160	argc -= optind;
161	argv += optind;
162
163	if (options > 1)
164		usage();
165
166	/*
167	 * Discard setgid privileges.  If not the running kernel, we toss
168	 * them away totally so that bad guys can't print interesting stuff
169	 * from kernel memory, otherwise switch back to kmem for the
170	 * duration of the kvm_openfiles() call.
171	 *
172	 * We also do this if we aren't just looking...
173	 */
174	if (core != NULL || kernel != NULL || action != CCD_DUMP)
175		setgid(getgid());
176
177	switch (action) {
178		case CCD_CONFIG:
179		case CCD_UNCONFIG:
180			exit(do_single(argc, argv, action));
181			/* NOTREACHED */
182
183		case CCD_CONFIGALL:
184		case CCD_UNCONFIGALL:
185			exit(do_all(action));
186			/* NOTREACHED */
187
188		case CCD_DUMP:
189		default:
190			exit(dump_ccd(argc, argv, action));
191			/* NOTREACHED */
192	}
193	/* NOTREACHED */
194}
195
196static int
197do_single(int argc, char **argv, int action)
198{
199	struct ccd_ioctl ccio;
200	char *ccd, *cp, *cp2, **disks;
201	int noflags = 0, i, ileave, flags, j;
202	unsigned int ui;
203
204	flags = 0;
205	memset(&ccio, 0, sizeof(ccio));
206
207	/*
208	 * If unconfiguring, all arguments are treated as ccds.
209	 */
210	if (action == CCD_UNCONFIG || action == CCD_UNCONFIGALL) {
211		for (i = 0; argc != 0; ) {
212			cp = *argv++; --argc;
213			if ((ccd = resolve_ccdname(cp)) == NULL) {
214				warnx("invalid ccd name: %s", cp);
215				i = 1;
216				continue;
217			}
218			if (do_io(ccd, CCDIOCCLR, &ccio))
219				i = 1;
220			else
221				if (verbose)
222					printf("%s unconfigured\n", cp);
223			free(ccd);
224		}
225		return (i);
226	}
227
228	/* Make sure there are enough arguments. */
229	if (argc < 4)  {
230		if (argc == 3) {
231			/* Assume that no flags are specified. */
232			noflags = 1;
233		} else {
234			if (action == CCD_CONFIGALL) {
235				warnx("%s: bad line: %lu", ccdconf,
236				    (u_long)lineno);
237				return (1);
238			} else
239				usage();
240		}
241	}
242
243	/* First argument is the ccd to configure. */
244	cp = *argv++; --argc;
245	if ((ccd = resolve_ccdname(cp)) == NULL) {
246		warnx("invalid ccd name: %s", cp);
247		return (1);
248	}
249
250	/* Next argument is the interleave factor. */
251	cp = *argv++; --argc;
252	errno = 0;	/* to check for ERANGE */
253	ileave = (int)strtol(cp, &cp2, 10);
254	if ((errno == ERANGE) || (ileave < 0) || (*cp2 != '\0')) {
255		warnx("invalid interleave factor: %s", cp);
256		free(ccd);
257		return (1);
258	}
259
260	if (noflags == 0) {
261		/* Next argument is the ccd configuration flags. */
262		cp = *argv++; --argc;
263		if ((flags = flags_to_val(cp)) < 0) {
264			warnx("invalid flags argument: %s", cp);
265			free(ccd);
266			return (1);
267		}
268	}
269
270	/* Next is the list of disks to make the ccd from. */
271	disks = malloc(argc * sizeof(char *));
272	if (disks == NULL) {
273		warnx("no memory to configure ccd");
274		free(ccd);
275		return (1);
276	}
277	for (ui = 0; argc != 0; ) {
278		cp = *argv++; --argc;
279		if ((j = checkdev(cp)) == 0)
280			disks[ui++] = cp;
281		else {
282			warnx("%s: %s", cp, strerror(j));
283			free(ccd);
284			free(disks);
285			return (1);
286		}
287	}
288
289	/* Fill in the ccio. */
290	ccio.ccio_disks = disks;
291	ccio.ccio_ndisks = ui;
292	ccio.ccio_ileave = ileave;
293	ccio.ccio_flags = flags;
294
295	if (do_io(ccd, CCDIOCSET, &ccio)) {
296		free(ccd);
297		free(disks);
298		return (1);
299	}
300
301	if (verbose) {
302		printf("ccd%d: %d components ", ccio.ccio_unit,
303		    ccio.ccio_ndisks);
304		for (ui = 0; ui < ccio.ccio_ndisks; ++ui) {
305			if ((cp2 = strrchr(disks[ui], '/')) != NULL)
306				++cp2;
307			else
308				cp2 = disks[ui];
309			printf("%c%s%c",
310			    ui == 0 ? '(' : ' ', cp2,
311			    ui == ccio.ccio_ndisks - 1 ? ')' : ',');
312		}
313		printf(", %ld blocks ", (long)ccio.ccio_size);
314		if (ccio.ccio_ileave != 0)
315			printf("interleaved at %d blocks\n", ccio.ccio_ileave);
316		else
317			printf("concatenated\n");
318	}
319
320	free(ccd);
321	free(disks);
322	return (0);
323}
324
325static int
326do_all(int action)
327{
328	FILE *f;
329	char *line, *cp, *vp, **argv, **nargv;
330	int argc, rval;
331	size_t len;
332
333	rval = 0;
334
335	(void)setegid(getgid());
336	if ((f = fopen(ccdconf, "r")) == NULL) {
337		(void)setegid(egid);
338		warn("fopen: %s", ccdconf);
339		return (1);
340	}
341	(void)setegid(egid);
342
343	while ((line = fparseln(f, &len, &lineno, "\\\\#", FPARSELN_UNESCALL))
344	    != NULL) {
345		argc = 0;
346		argv = NULL;
347		if (len == 0)
348			goto end_of_line;
349
350		for (cp = line; cp != NULL; ) {
351			while ((vp = strsep(&cp, "\t ")) != NULL && *vp == '\0')
352				;
353			if (vp == NULL)
354				continue;
355
356			if ((nargv = realloc(argv,
357			    sizeof(char *) * (argc + 1))) == NULL) {
358				warnx("no memory to configure ccds");
359				return (1);
360			}
361			argv = nargv;
362			argc++;
363			argv[argc - 1] = vp;
364
365			/*
366			 * If our action is to unconfigure all, then pass
367			 * just the first token to do_single() and ignore
368			 * the rest.  Since this will be encountered on
369			 * our first pass through the line, the Right
370			 * Thing will happen.
371			 */
372			if (action == CCD_UNCONFIGALL) {
373				if (do_single(argc, argv, action))
374					rval = 1;
375				goto end_of_line;
376			}
377		}
378		if (argc != 0)
379			if (do_single(argc, argv, action))
380				rval = 1;
381
382 end_of_line:
383		if (argv != NULL)
384			free(argv);
385		free(line);
386	}
387
388	(void)fclose(f);
389	return (rval);
390}
391
392static int
393checkdev(char *path)
394{
395	struct stat st;
396
397	if (stat(path, &st) != 0)
398		return (errno);
399
400	if (!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode))
401		return (EINVAL);
402
403	return (0);
404}
405
406static int
407pathtounit(char *path, int *unitp)
408{
409	struct stat st;
410
411	if (stat(path, &st) != 0)
412		return (errno);
413
414	if (!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode))
415		return (EINVAL);
416
417	*unitp = DISKUNIT(st.st_rdev);
418
419	return (0);
420}
421
422static char *
423resolve_ccdname(char *name)
424{
425	char c, *path;
426	size_t len;
427	int rawpart;
428
429	if (name[0] == '/' || name[0] == '.') {
430		/* Assume they gave the correct pathname. */
431		return (strdup(name));
432	}
433
434	len = strlen(name);
435	c = name[len - 1];
436
437	if (isdigit((unsigned char)c)) {
438		if ((rawpart = getrawpartition()) < 0)
439			return (NULL);
440		if (asprintf(&path, "/dev/%s%c", name, 'a' + rawpart) < 0)
441			return (NULL);
442	} else
443		if (asprintf(&path, "/dev/%s", name) < 0)
444			return (NULL);
445
446	return (path);
447}
448
449static int
450do_io(char *path, u_long cmd, struct ccd_ioctl *cciop)
451{
452	int fd;
453	const char *cp;
454
455	if ((fd = open(path, O_RDWR, 0640)) < 0) {
456		warn("open: %s", path);
457		return (1);
458	}
459
460	if (ioctl(fd, cmd, cciop) < 0) {
461		switch (cmd) {
462		case CCDIOCSET:
463			cp = "CCDIOCSET";
464			break;
465
466		case CCDIOCCLR:
467			cp = "CCDIOCCLR";
468			break;
469
470		default:
471			cp = "unknown";
472		}
473		warn("ioctl (%s): %s", cp, path);
474		(void)close(fd);
475		return (1);
476	}
477
478	(void)close(fd);
479	return (0);
480}
481
482#define KVM_ABORT(kd, str) {						\
483	(void)kvm_close((kd));						\
484	warnx("%s", (str));						\
485	warnx("%s", kvm_geterr((kd)));					\
486	return (1);							\
487}
488
489static int
490dump_ccd(int argc, char **argv, int action)
491{
492	char errbuf[_POSIX2_LINE_MAX], *ccd, *cp;
493	struct ccd_softc *cs, *kcs;
494	void *vcs;
495	size_t readsize;
496	int i, error, numccd, ccd_softc_elemsize, numconfiged = 0;
497	kvm_t *kd;
498
499	memset(errbuf, 0, sizeof(errbuf));
500
501	vcs = NULL;
502	(void)setegid(egid);
503	if ((kd = kvm_openfiles(kernel, core, NULL, O_RDONLY,
504	    errbuf)) == NULL) {
505		warnx("can't open kvm: %s", errbuf);
506		return (1);
507	}
508	(void)setgid(getgid());
509
510	if (kvm_nlist(kd, nl))
511		KVM_ABORT(kd, "ccd-related symbols not available");
512
513	/* Check to see how many ccds are currently configured. */
514	if (kvm_read(kd, nl[SYM_NUMCCD].n_value, (void *)&numccd,
515	    sizeof(numccd)) != sizeof(numccd))
516		KVM_ABORT(kd, "can't determine number of configured ccds");
517
518	if (numccd == 0) {
519		printf("ccd driver in kernel, but is uninitialized\n");
520		goto done;
521	}
522
523	if (kvm_read(kd, nl[SYM_CCDSOFTCELEMSIZE].n_value,
524	    (void *)&ccd_softc_elemsize, sizeof(ccd_softc_elemsize))
525	    != sizeof(ccd_softc_elemsize))
526		KVM_ABORT(kd, "can't determine size of ccd_softc");
527
528	/* Allocate space for the kernel's configuration data. */
529	readsize = numccd * ccd_softc_elemsize;
530	if ((vcs = malloc(readsize)) == NULL) {
531		warnx("no memory for configuration data");
532		goto bad;
533	}
534	memset(vcs, 0, readsize);
535
536	/*
537	 * Read the ccd configuration data from the kernel.
538	 * The kernel's ccd_softc is larger than userland's
539	 * (the former contains extra structure members at
540	 * the end of the structure), so read the kernel
541	 * ccd_softcs into a temporary buffer and convert
542	 * into userland ccd_softcs.
543	 */
544	if (kvm_read(kd, nl[SYM_CCDSOFTC].n_value, (void *)&kcs,
545	    sizeof(kcs)) != sizeof(kcs)) {
546		free(vcs);
547		KVM_ABORT(kd, "can't find pointer to configuration data");
548	}
549	if ((size_t)kvm_read(kd, (u_long)kcs, vcs, readsize) != readsize) {
550		free(vcs);
551		KVM_ABORT(kd, "can't read configuration data");
552	}
553
554	if ((cs = calloc(numccd, sizeof(struct ccd_softc))) == NULL) {
555		warnx("no memory for configuration data");
556		free(vcs);
557		goto bad;
558	}
559	for (i = 0; i < numccd; i++) {
560		memcpy(&cs[i], (char *)vcs + i * ccd_softc_elemsize,
561		    sizeof(struct ccd_softc));
562	}
563	free(vcs);
564
565	/* Dump ccd configuration to stdout. */
566	if (argc == 0) {
567		for (i = 0; i < numccd; ++i)
568			if (cs[i].sc_flags & CCDF_INITED) {
569				++numconfiged;
570				print_ccd_info(&cs[i], kd);
571			}
572
573		if (numconfiged == 0)
574			printf("# no concatenated disks configured\n");
575	} else {
576		while (argc) {
577			cp = *argv++; --argc;
578			if ((ccd = resolve_ccdname(cp)) == NULL) {
579				warnx("invalid ccd name: %s", cp);
580				continue;
581			}
582			if ((error = pathtounit(ccd, &i)) != 0) {
583				warn("%s", ccd);
584				free(ccd);
585				continue;
586			}
587			if (i >= numccd) {
588				warnx("ccd%d not configured", i);
589				free(ccd);
590				continue;
591			}
592			if (cs[i].sc_flags & CCDF_INITED)
593				print_ccd_info(&cs[i], kd);
594			else
595				printf("# ccd%d not configured\n", i);
596			free(ccd);
597		}
598	}
599
600	free(cs);
601
602 done:
603	(void)kvm_close(kd);
604	return (0);
605
606 bad:
607	(void)kvm_close(kd);
608	return (1);
609}
610
611static void
612print_ccd_info(struct ccd_softc *cs, kvm_t *kd)
613{
614	static int header_printed = 0;
615	struct ccdcinfo *cip;
616	size_t readsize;
617	char path[MAXPATHLEN];
618	unsigned int i;
619
620	if (header_printed == 0 && verbose) {
621		printf("# ccd\t\tileave\tflags\tcompnent devices\n");
622		header_printed = 1;
623	}
624
625	readsize = cs->sc_nccdisks * sizeof(struct ccdcinfo);
626	if ((cip = malloc(readsize)) == NULL) {
627		warn("%s: can't allocate memory for component info",
628		    cs->sc_xname);
629		return;
630	}
631	memset(cip, 0, readsize);
632
633	/* Dump out softc information. */
634	printf("%s\t\t%d\t%d\t", cs->sc_xname, cs->sc_ileave,
635	    cs->sc_flags & CCDF_USERMASK);
636	fflush(stdout);
637
638	/* Read in the component info. */
639	if ((size_t)kvm_read(kd, (u_long)cs->sc_cinfo, (void *)cip,
640	    readsize) != readsize) {
641		printf("\n");
642		warnx("can't read component info");
643		warnx("%s", kvm_geterr(kd));
644		goto done;
645	}
646
647	/* Read component pathname and display component info. */
648	for (i = 0; i < cs->sc_nccdisks; ++i) {
649		if ((size_t)kvm_read(kd, (u_long)cip[i].ci_path, (void *)path,
650		    cip[i].ci_pathlen) != cip[i].ci_pathlen) {
651			printf("\n");
652			warnx("can't read component pathname");
653			warnx("%s", kvm_geterr(kd));
654			goto done;
655		}
656		fputs(path, stdout);
657		fputc((i + 1 < cs->sc_nccdisks) ? ' ' : '\n', stdout);
658		fflush(stdout);
659	}
660
661 done:
662	free(cip);
663}
664
665static int
666flags_to_val(char *flags)
667{
668	char *cp, *tok;
669	int i, tmp, val = ~CCDF_USERMASK;
670	size_t flagslen;
671
672	/*
673	 * The most common case is that of NIL flags, so check for
674	 * those first.
675	 */
676	if (strcmp("none", flags) == 0 || strcmp("0x0", flags) == 0 ||
677	    strcmp("0", flags) == 0)
678		return (0);
679
680	flagslen = strlen(flags);
681
682	/* Check for values represented by strings. */
683	if ((cp = strdup(flags)) == NULL)
684		err(1, "no memory to parse flags");
685	tmp = 0;
686	for (tok = cp; (tok = strtok(tok, ",")) != NULL; tok = NULL) {
687		for (i = 0; flagvaltab[i].fv_flag != NULL; ++i)
688			if (strcmp(tok, flagvaltab[i].fv_flag) == 0)
689				break;
690		if (flagvaltab[i].fv_flag == NULL) {
691			free(cp);
692			goto bad_string;
693		}
694		tmp |= flagvaltab[i].fv_val;
695	}
696
697	/* If we get here, the string was ok. */
698	free(cp);
699	val = tmp;
700	goto out;
701
702 bad_string:
703
704	/* Check for values represented in hex. */
705	if (flagslen > 2 && flags[0] == '0' && flags[1] == 'x') {
706		errno = 0;	/* to check for ERANGE */
707		val = (int)strtol(&flags[2], &cp, 16);
708		if ((errno == ERANGE) || (*cp != '\0'))
709			return (-1);
710		goto out;
711	}
712
713	/* Check for values represented in decimal. */
714	errno = 0;	/* to check for ERANGE */
715	val = (int)strtol(flags, &cp, 10);
716	if ((errno == ERANGE) || (*cp != '\0'))
717		return (-1);
718
719 out:
720	return (((val & ~CCDF_USERMASK) == 0) ? val : -1);
721}
722
723static void
724usage(void)
725{
726	const char *progname = getprogname();
727
728	fprintf(stderr, "usage: %s [-cv] ccd ileave [flags] dev [...]\n",
729	    progname);
730	fprintf(stderr, "       %s -C [-v] [-f config_file]\n", progname);
731	fprintf(stderr, "       %s -u [-v] ccd [...]\n", progname);
732	fprintf(stderr, "       %s -U [-v] [-f config_file]\n", progname);
733	fprintf(stderr, "       %s -g [-M core] [-N system] [ccd [...]]\n",
734	    progname);
735	exit(1);
736}
737