xref: /freebsd/usr.bin/ministat/ministat.c (revision 4f52dfbb8d6c4d446500c5b097e3806ec219fbd4)
1 /*-
2  * SPDX-License-Identifier: Beerware
3  *
4  * ----------------------------------------------------------------------------
5  * "THE BEER-WARE LICENSE" (Revision 42):
6  * <phk@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
7  * can do whatever you want with this stuff. If we meet some day, and you think
8  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
9  * ----------------------------------------------------------------------------
10  *
11  */
12 
13 #include <sys/cdefs.h>
14 __FBSDID("$FreeBSD$");
15 
16 #include <sys/capsicum.h>
17 #include <sys/ioctl.h>
18 #include <sys/queue.h>
19 #include <sys/ttycom.h>
20 
21 #include <capsicum_helpers.h>
22 #include <ctype.h>
23 #include <err.h>
24 #include <errno.h>
25 #include <math.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #define NSTUDENT 100
32 #define NCONF 6
33 static double const studentpct[] = { 80, 90, 95, 98, 99, 99.5 };
34 static double student[NSTUDENT + 1][NCONF] = {
35 /* inf */	{	1.282,	1.645,	1.960,	2.326,	2.576,	3.090  },
36 /* 1. */	{	3.078,	6.314,	12.706,	31.821,	63.657,	318.313  },
37 /* 2. */	{	1.886,	2.920,	4.303,	6.965,	9.925,	22.327  },
38 /* 3. */	{	1.638,	2.353,	3.182,	4.541,	5.841,	10.215  },
39 /* 4. */	{	1.533,	2.132,	2.776,	3.747,	4.604,	7.173  },
40 /* 5. */	{	1.476,	2.015,	2.571,	3.365,	4.032,	5.893  },
41 /* 6. */	{	1.440,	1.943,	2.447,	3.143,	3.707,	5.208  },
42 /* 7. */	{	1.415,	1.895,	2.365,	2.998,	3.499,	4.782  },
43 /* 8. */	{	1.397,	1.860,	2.306,	2.896,	3.355,	4.499  },
44 /* 9. */	{	1.383,	1.833,	2.262,	2.821,	3.250,	4.296  },
45 /* 10. */	{	1.372,	1.812,	2.228,	2.764,	3.169,	4.143  },
46 /* 11. */	{	1.363,	1.796,	2.201,	2.718,	3.106,	4.024  },
47 /* 12. */	{	1.356,	1.782,	2.179,	2.681,	3.055,	3.929  },
48 /* 13. */	{	1.350,	1.771,	2.160,	2.650,	3.012,	3.852  },
49 /* 14. */	{	1.345,	1.761,	2.145,	2.624,	2.977,	3.787  },
50 /* 15. */	{	1.341,	1.753,	2.131,	2.602,	2.947,	3.733  },
51 /* 16. */	{	1.337,	1.746,	2.120,	2.583,	2.921,	3.686  },
52 /* 17. */	{	1.333,	1.740,	2.110,	2.567,	2.898,	3.646  },
53 /* 18. */	{	1.330,	1.734,	2.101,	2.552,	2.878,	3.610  },
54 /* 19. */	{	1.328,	1.729,	2.093,	2.539,	2.861,	3.579  },
55 /* 20. */	{	1.325,	1.725,	2.086,	2.528,	2.845,	3.552  },
56 /* 21. */	{	1.323,	1.721,	2.080,	2.518,	2.831,	3.527  },
57 /* 22. */	{	1.321,	1.717,	2.074,	2.508,	2.819,	3.505  },
58 /* 23. */	{	1.319,	1.714,	2.069,	2.500,	2.807,	3.485  },
59 /* 24. */	{	1.318,	1.711,	2.064,	2.492,	2.797,	3.467  },
60 /* 25. */	{	1.316,	1.708,	2.060,	2.485,	2.787,	3.450  },
61 /* 26. */	{	1.315,	1.706,	2.056,	2.479,	2.779,	3.435  },
62 /* 27. */	{	1.314,	1.703,	2.052,	2.473,	2.771,	3.421  },
63 /* 28. */	{	1.313,	1.701,	2.048,	2.467,	2.763,	3.408  },
64 /* 29. */	{	1.311,	1.699,	2.045,	2.462,	2.756,	3.396  },
65 /* 30. */	{	1.310,	1.697,	2.042,	2.457,	2.750,	3.385  },
66 /* 31. */	{	1.309,	1.696,	2.040,	2.453,	2.744,	3.375  },
67 /* 32. */	{	1.309,	1.694,	2.037,	2.449,	2.738,	3.365  },
68 /* 33. */	{	1.308,	1.692,	2.035,	2.445,	2.733,	3.356  },
69 /* 34. */	{	1.307,	1.691,	2.032,	2.441,	2.728,	3.348  },
70 /* 35. */	{	1.306,	1.690,	2.030,	2.438,	2.724,	3.340  },
71 /* 36. */	{	1.306,	1.688,	2.028,	2.434,	2.719,	3.333  },
72 /* 37. */	{	1.305,	1.687,	2.026,	2.431,	2.715,	3.326  },
73 /* 38. */	{	1.304,	1.686,	2.024,	2.429,	2.712,	3.319  },
74 /* 39. */	{	1.304,	1.685,	2.023,	2.426,	2.708,	3.313  },
75 /* 40. */	{	1.303,	1.684,	2.021,	2.423,	2.704,	3.307  },
76 /* 41. */	{	1.303,	1.683,	2.020,	2.421,	2.701,	3.301  },
77 /* 42. */	{	1.302,	1.682,	2.018,	2.418,	2.698,	3.296  },
78 /* 43. */	{	1.302,	1.681,	2.017,	2.416,	2.695,	3.291  },
79 /* 44. */	{	1.301,	1.680,	2.015,	2.414,	2.692,	3.286  },
80 /* 45. */	{	1.301,	1.679,	2.014,	2.412,	2.690,	3.281  },
81 /* 46. */	{	1.300,	1.679,	2.013,	2.410,	2.687,	3.277  },
82 /* 47. */	{	1.300,	1.678,	2.012,	2.408,	2.685,	3.273  },
83 /* 48. */	{	1.299,	1.677,	2.011,	2.407,	2.682,	3.269  },
84 /* 49. */	{	1.299,	1.677,	2.010,	2.405,	2.680,	3.265  },
85 /* 50. */	{	1.299,	1.676,	2.009,	2.403,	2.678,	3.261  },
86 /* 51. */	{	1.298,	1.675,	2.008,	2.402,	2.676,	3.258  },
87 /* 52. */	{	1.298,	1.675,	2.007,	2.400,	2.674,	3.255  },
88 /* 53. */	{	1.298,	1.674,	2.006,	2.399,	2.672,	3.251  },
89 /* 54. */	{	1.297,	1.674,	2.005,	2.397,	2.670,	3.248  },
90 /* 55. */	{	1.297,	1.673,	2.004,	2.396,	2.668,	3.245  },
91 /* 56. */	{	1.297,	1.673,	2.003,	2.395,	2.667,	3.242  },
92 /* 57. */	{	1.297,	1.672,	2.002,	2.394,	2.665,	3.239  },
93 /* 58. */	{	1.296,	1.672,	2.002,	2.392,	2.663,	3.237  },
94 /* 59. */	{	1.296,	1.671,	2.001,	2.391,	2.662,	3.234  },
95 /* 60. */	{	1.296,	1.671,	2.000,	2.390,	2.660,	3.232  },
96 /* 61. */	{	1.296,	1.670,	2.000,	2.389,	2.659,	3.229  },
97 /* 62. */	{	1.295,	1.670,	1.999,	2.388,	2.657,	3.227  },
98 /* 63. */	{	1.295,	1.669,	1.998,	2.387,	2.656,	3.225  },
99 /* 64. */	{	1.295,	1.669,	1.998,	2.386,	2.655,	3.223  },
100 /* 65. */	{	1.295,	1.669,	1.997,	2.385,	2.654,	3.220  },
101 /* 66. */	{	1.295,	1.668,	1.997,	2.384,	2.652,	3.218  },
102 /* 67. */	{	1.294,	1.668,	1.996,	2.383,	2.651,	3.216  },
103 /* 68. */	{	1.294,	1.668,	1.995,	2.382,	2.650,	3.214  },
104 /* 69. */	{	1.294,	1.667,	1.995,	2.382,	2.649,	3.213  },
105 /* 70. */	{	1.294,	1.667,	1.994,	2.381,	2.648,	3.211  },
106 /* 71. */	{	1.294,	1.667,	1.994,	2.380,	2.647,	3.209  },
107 /* 72. */	{	1.293,	1.666,	1.993,	2.379,	2.646,	3.207  },
108 /* 73. */	{	1.293,	1.666,	1.993,	2.379,	2.645,	3.206  },
109 /* 74. */	{	1.293,	1.666,	1.993,	2.378,	2.644,	3.204  },
110 /* 75. */	{	1.293,	1.665,	1.992,	2.377,	2.643,	3.202  },
111 /* 76. */	{	1.293,	1.665,	1.992,	2.376,	2.642,	3.201  },
112 /* 77. */	{	1.293,	1.665,	1.991,	2.376,	2.641,	3.199  },
113 /* 78. */	{	1.292,	1.665,	1.991,	2.375,	2.640,	3.198  },
114 /* 79. */	{	1.292,	1.664,	1.990,	2.374,	2.640,	3.197  },
115 /* 80. */	{	1.292,	1.664,	1.990,	2.374,	2.639,	3.195  },
116 /* 81. */	{	1.292,	1.664,	1.990,	2.373,	2.638,	3.194  },
117 /* 82. */	{	1.292,	1.664,	1.989,	2.373,	2.637,	3.193  },
118 /* 83. */	{	1.292,	1.663,	1.989,	2.372,	2.636,	3.191  },
119 /* 84. */	{	1.292,	1.663,	1.989,	2.372,	2.636,	3.190  },
120 /* 85. */	{	1.292,	1.663,	1.988,	2.371,	2.635,	3.189  },
121 /* 86. */	{	1.291,	1.663,	1.988,	2.370,	2.634,	3.188  },
122 /* 87. */	{	1.291,	1.663,	1.988,	2.370,	2.634,	3.187  },
123 /* 88. */	{	1.291,	1.662,	1.987,	2.369,	2.633,	3.185  },
124 /* 89. */	{	1.291,	1.662,	1.987,	2.369,	2.632,	3.184  },
125 /* 90. */	{	1.291,	1.662,	1.987,	2.368,	2.632,	3.183  },
126 /* 91. */	{	1.291,	1.662,	1.986,	2.368,	2.631,	3.182  },
127 /* 92. */	{	1.291,	1.662,	1.986,	2.368,	2.630,	3.181  },
128 /* 93. */	{	1.291,	1.661,	1.986,	2.367,	2.630,	3.180  },
129 /* 94. */	{	1.291,	1.661,	1.986,	2.367,	2.629,	3.179  },
130 /* 95. */	{	1.291,	1.661,	1.985,	2.366,	2.629,	3.178  },
131 /* 96. */	{	1.290,	1.661,	1.985,	2.366,	2.628,	3.177  },
132 /* 97. */	{	1.290,	1.661,	1.985,	2.365,	2.627,	3.176  },
133 /* 98. */	{	1.290,	1.661,	1.984,	2.365,	2.627,	3.175  },
134 /* 99. */	{	1.290,	1.660,	1.984,	2.365,	2.626,	3.175  },
135 /* 100. */	{	1.290,	1.660,	1.984,	2.364,	2.626,	3.174  }
136 };
137 
138 #define	MAX_DS	8
139 static char symbol[MAX_DS] = { ' ', 'x', '+', '*', '%', '#', '@', 'O' };
140 
141 struct dataset {
142 	char *name;
143 	double	*points;
144 	unsigned lpoints;
145 	double sy, syy;
146 	unsigned n;
147 };
148 
149 static struct dataset *
150 NewSet(void)
151 {
152 	struct dataset *ds;
153 
154 	ds = calloc(1, sizeof *ds);
155 	ds->lpoints = 100000;
156 	ds->points = calloc(sizeof *ds->points, ds->lpoints);
157 	return(ds);
158 }
159 
160 static void
161 AddPoint(struct dataset *ds, double a)
162 {
163 	double *dp;
164 
165 	if (ds->n >= ds->lpoints) {
166 		dp = ds->points;
167 		ds->lpoints *= 4;
168 		ds->points = calloc(sizeof *ds->points, ds->lpoints);
169 		memcpy(ds->points, dp, sizeof *dp * ds->n);
170 		free(dp);
171 	}
172 	ds->points[ds->n++] = a;
173 	ds->sy += a;
174 	ds->syy += a * a;
175 }
176 
177 static double
178 Min(struct dataset *ds)
179 {
180 
181 	return (ds->points[0]);
182 }
183 
184 static double
185 Max(struct dataset *ds)
186 {
187 
188 	return (ds->points[ds->n -1]);
189 }
190 
191 static double
192 Avg(struct dataset *ds)
193 {
194 
195 	return(ds->sy / ds->n);
196 }
197 
198 static double
199 Median(struct dataset *ds)
200 {
201 	if ((ds->n % 2) == 0)
202 		return ((ds->points[ds->n / 2] + (ds->points[(ds->n / 2) - 1])) / 2);
203     	else
204 		return (ds->points[ds->n / 2]);
205 }
206 
207 static double
208 Var(struct dataset *ds)
209 {
210 
211 	/*
212 	 * Due to limited precision it is possible that sy^2/n > syy,
213 	 * but variance cannot actually be negative.
214 	 */
215 	if (ds->syy <= ds->sy * ds->sy / ds->n)
216 		return (0);
217 	return (ds->syy - ds->sy * ds->sy / ds->n) / (ds->n - 1.0);
218 }
219 
220 static double
221 Stddev(struct dataset *ds)
222 {
223 
224 	return sqrt(Var(ds));
225 }
226 
227 static void
228 VitalsHead(void)
229 {
230 
231 	printf("    N           Min           Max        Median           Avg        Stddev\n");
232 }
233 
234 static void
235 Vitals(struct dataset *ds, int flag)
236 {
237 
238 	printf("%c %3d %13.8g %13.8g %13.8g %13.8g %13.8g", symbol[flag],
239 	    ds->n, Min(ds), Max(ds), Median(ds), Avg(ds), Stddev(ds));
240 	printf("\n");
241 }
242 
243 static void
244 Relative(struct dataset *ds, struct dataset *rs, int confidx)
245 {
246 	double spool, s, d, e, t;
247 	double re;
248 	int i;
249 
250 	i = ds->n + rs->n - 2;
251 	if (i > NSTUDENT)
252 		t = student[0][confidx];
253 	else
254 		t = student[i][confidx];
255 	spool = (ds->n - 1) * Var(ds) + (rs->n - 1) * Var(rs);
256 	spool /= ds->n + rs->n - 2;
257 	spool = sqrt(spool);
258 	s = spool * sqrt(1.0 / ds->n + 1.0 / rs->n);
259 	d = Avg(ds) - Avg(rs);
260 	e = t * s;
261 
262 	re = (ds->n - 1) * Var(ds) + (rs->n - 1) * Var(rs) *
263 	    (Avg(ds) * Avg(ds)) / (Avg(rs) * Avg(rs));
264 	re *= (ds->n + rs->n) / (ds->n * rs->n * (ds->n + rs->n - 2.0));
265 	re = t * sqrt(re);
266 
267 	if (fabs(d) > e) {
268 
269 		printf("Difference at %.1f%% confidence\n", studentpct[confidx]);
270 		printf("	%g +/- %g\n", d, e);
271 		printf("	%g%% +/- %g%%\n", d * 100 / Avg(rs), re * 100 / Avg(rs));
272 		printf("	(Student's t, pooled s = %g)\n", spool);
273 	} else {
274 		printf("No difference proven at %.1f%% confidence\n",
275 		    studentpct[confidx]);
276 	}
277 }
278 
279 struct plot {
280 	double		min;
281 	double		max;
282 	double		span;
283 	int		width;
284 
285 	double		x0, dx;
286 	int		height;
287 	char		*data;
288 	char		**bar;
289 	int		separate_bars;
290 	int		num_datasets;
291 };
292 
293 static struct plot plot;
294 
295 static void
296 SetupPlot(int width, int separate, int num_datasets)
297 {
298 	struct plot *pl;
299 
300 	pl = &plot;
301 	pl->width = width;
302 	pl->height = 0;
303 	pl->data = NULL;
304 	pl->bar = NULL;
305 	pl->separate_bars = separate;
306 	pl->num_datasets = num_datasets;
307 	pl->min = 999e99;
308 	pl->max = -999e99;
309 }
310 
311 static void
312 AdjPlot(double a)
313 {
314 	struct plot *pl;
315 
316 	pl = &plot;
317 	if (a < pl->min)
318 		pl->min = a;
319 	if (a > pl->max)
320 		pl->max = a;
321 	pl->span = pl->max - pl->min;
322 	pl->dx = pl->span / (pl->width - 1.0);
323 	pl->x0 = pl->min - .5 * pl->dx;
324 }
325 
326 static void
327 DimPlot(struct dataset *ds)
328 {
329 	AdjPlot(Min(ds));
330 	AdjPlot(Max(ds));
331 	AdjPlot(Avg(ds) - Stddev(ds));
332 	AdjPlot(Avg(ds) + Stddev(ds));
333 }
334 
335 static void
336 PlotSet(struct dataset *ds, int val)
337 {
338 	struct plot *pl;
339 	int i, j, m, x;
340 	unsigned n;
341 	int bar;
342 
343 	pl = &plot;
344 	if (pl->span == 0)
345 		return;
346 
347 	if (pl->separate_bars)
348 		bar = val-1;
349 	else
350 		bar = 0;
351 
352 	if (pl->bar == NULL)
353 		pl->bar = calloc(sizeof(char *), pl->num_datasets);
354 	if (pl->bar[bar] == NULL) {
355 		pl->bar[bar] = malloc(pl->width);
356 		memset(pl->bar[bar], 0, pl->width);
357 	}
358 
359 	m = 1;
360 	i = -1;
361 	j = 0;
362 	for (n = 0; n < ds->n; n++) {
363 		x = (ds->points[n] - pl->x0) / pl->dx;
364 		if (x == i) {
365 			j++;
366 			if (j > m)
367 				m = j;
368 		} else {
369 			j = 1;
370 			i = x;
371 		}
372 	}
373 	m += 1;
374 	if (m > pl->height) {
375 		pl->data = realloc(pl->data, pl->width * m);
376 		memset(pl->data + pl->height * pl->width, 0,
377 		    (m - pl->height) * pl->width);
378 	}
379 	pl->height = m;
380 	i = -1;
381 	for (n = 0; n < ds->n; n++) {
382 		x = (ds->points[n] - pl->x0) / pl->dx;
383 		if (x == i) {
384 			j++;
385 		} else {
386 			j = 1;
387 			i = x;
388 		}
389 		pl->data[j * pl->width + x] |= val;
390 	}
391 	if (!isnan(Stddev(ds))) {
392 		x = ((Avg(ds) - Stddev(ds)) - pl->x0) / pl->dx;
393 		m = ((Avg(ds) + Stddev(ds)) - pl->x0) / pl->dx;
394 		pl->bar[bar][m] = '|';
395 		pl->bar[bar][x] = '|';
396 		for (i = x + 1; i < m; i++)
397 			if (pl->bar[bar][i] == 0)
398 				pl->bar[bar][i] = '_';
399 	}
400 	x = (Median(ds) - pl->x0) / pl->dx;
401 	pl->bar[bar][x] = 'M';
402 	x = (Avg(ds) - pl->x0) / pl->dx;
403 	pl->bar[bar][x] = 'A';
404 }
405 
406 static void
407 DumpPlot(void)
408 {
409 	struct plot *pl;
410 	int i, j, k;
411 
412 	pl = &plot;
413 	if (pl->span == 0) {
414 		printf("[no plot, span is zero width]\n");
415 		return;
416 	}
417 
418 	putchar('+');
419 	for (i = 0; i < pl->width; i++)
420 		putchar('-');
421 	putchar('+');
422 	putchar('\n');
423 	for (i = 1; i < pl->height; i++) {
424 		putchar('|');
425 		for (j = 0; j < pl->width; j++) {
426 			k = pl->data[(pl->height - i) * pl->width + j];
427 			if (k >= 0 && k < MAX_DS)
428 				putchar(symbol[k]);
429 			else
430 				printf("[%02x]", k);
431 		}
432 		putchar('|');
433 		putchar('\n');
434 	}
435 	for (i = 0; i < pl->num_datasets; i++) {
436 		if (pl->bar[i] == NULL)
437 			continue;
438 		putchar('|');
439 		for (j = 0; j < pl->width; j++) {
440 			k = pl->bar[i][j];
441 			if (k == 0)
442 				k = ' ';
443 			putchar(k);
444 		}
445 		putchar('|');
446 		putchar('\n');
447 	}
448 	putchar('+');
449 	for (i = 0; i < pl->width; i++)
450 		putchar('-');
451 	putchar('+');
452 	putchar('\n');
453 }
454 
455 static int
456 dbl_cmp(const void *a, const void *b)
457 {
458 	const double *aa = a;
459 	const double *bb = b;
460 
461 	if (*aa < *bb)
462 		return (-1);
463 	else if (*aa > *bb)
464 		return (1);
465 	else
466 		return (0);
467 }
468 
469 static struct dataset *
470 ReadSet(FILE *f, const char *n, int column, const char *delim)
471 {
472 	char buf[BUFSIZ], *p, *t;
473 	struct dataset *s;
474 	double d;
475 	int line;
476 	int i;
477 
478 	s = NewSet();
479 	s->name = strdup(n);
480 	line = 0;
481 	while (fgets(buf, sizeof buf, f) != NULL) {
482 		line++;
483 
484 		i = strlen(buf);
485 		while (i > 0 && isspace(buf[i - 1]))
486 			buf[--i] = '\0';
487 		for (i = 1, t = strtok(buf, delim);
488 		     t != NULL && *t != '#';
489 		     i++, t = strtok(NULL, delim)) {
490 			if (i == column)
491 				break;
492 		}
493 		if (t == NULL || *t == '#')
494 			continue;
495 
496 		d = strtod(t, &p);
497 		if (p != NULL && *p != '\0')
498 			errx(2, "Invalid data on line %d in %s", line, n);
499 		if (*buf != '\0')
500 			AddPoint(s, d);
501 	}
502 	if (s->n < 3) {
503 		fprintf(stderr,
504 		    "Dataset %s must contain at least 3 data points\n", n);
505 		exit (2);
506 	}
507 	qsort(s->points, s->n, sizeof *s->points, dbl_cmp);
508 	return (s);
509 }
510 
511 static void
512 usage(char const *whine)
513 {
514 	int i;
515 
516 	fprintf(stderr, "%s\n", whine);
517 	fprintf(stderr,
518 	    "Usage: ministat [-C column] [-c confidence] [-d delimiter(s)] [-Ans] [-w width] [file [file ...]]\n");
519 	fprintf(stderr, "\tconfidence = {");
520 	for (i = 0; i < NCONF; i++) {
521 		fprintf(stderr, "%s%g%%",
522 		    i ? ", " : "",
523 		    studentpct[i]);
524 	}
525 	fprintf(stderr, "}\n");
526 	fprintf(stderr, "\t-A : print statistics only. suppress the graph.\n");
527 	fprintf(stderr, "\t-C : column number to extract (starts and defaults to 1)\n");
528 	fprintf(stderr, "\t-d : delimiter(s) string, default to \" \\t\"\n");
529 	fprintf(stderr, "\t-n : print summary statistics only, no graph/test\n");
530 	fprintf(stderr, "\t-s : print avg/median/stddev bars on separate lines\n");
531 	fprintf(stderr, "\t-w : width of graph/test output (default 74 or terminal width)\n");
532 	exit (2);
533 }
534 
535 int
536 main(int argc, char **argv)
537 {
538 	const char *setfilenames[MAX_DS - 1];
539 	struct dataset *ds[MAX_DS - 1];
540 	FILE *setfiles[MAX_DS - 1];
541 	int nds;
542 	double a;
543 	const char *delim = " \t";
544 	char *p;
545 	int c, i, ci;
546 	int column = 1;
547 	int flag_s = 0;
548 	int flag_n = 0;
549 	int termwidth = 74;
550 	int suppress_plot = 0;
551 
552 	if (isatty(STDOUT_FILENO)) {
553 		struct winsize wsz;
554 
555 		if ((p = getenv("COLUMNS")) != NULL && *p != '\0')
556 			termwidth = atoi(p);
557 		else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsz) != -1 &&
558 			 wsz.ws_col > 0)
559 			termwidth = wsz.ws_col - 2;
560 	}
561 
562 	ci = -1;
563 	while ((c = getopt(argc, argv, "AC:c:d:snw:")) != -1)
564 		switch (c) {
565 		case 'A':
566 			suppress_plot = 1;
567 			break;
568 		case 'C':
569 			column = strtol(optarg, &p, 10);
570 			if (p != NULL && *p != '\0')
571 				usage("Invalid column number.");
572 			if (column <= 0)
573 				usage("Column number should be positive.");
574 			break;
575 		case 'c':
576 			a = strtod(optarg, &p);
577 			if (p != NULL && *p != '\0')
578 				usage("Not a floating point number");
579 			for (i = 0; i < NCONF; i++)
580 				if (a == studentpct[i])
581 					ci = i;
582 			if (ci == -1)
583 				usage("No support for confidence level");
584 			break;
585 		case 'd':
586 			if (*optarg == '\0')
587 				usage("Can't use empty delimiter string");
588 			delim = optarg;
589 			break;
590 		case 'n':
591 			flag_n = 1;
592 			break;
593 		case 's':
594 			flag_s = 1;
595 			break;
596 		case 'w':
597 			termwidth = strtol(optarg, &p, 10);
598 			if (p != NULL && *p != '\0')
599 				usage("Invalid width, not a number.");
600 			if (termwidth < 0)
601 				usage("Unable to move beyond left margin.");
602 			break;
603 		default:
604 			usage("Unknown option");
605 			break;
606 		}
607 	if (ci == -1)
608 		ci = 2;
609 	argc -= optind;
610 	argv += optind;
611 
612 	if (argc == 0) {
613 		setfilenames[0] = "<stdin>";
614 		setfiles[0] = stdin;
615 		nds = 1;
616 	} else {
617 		if (argc > (MAX_DS - 1))
618 			usage("Too many datasets.");
619 		nds = argc;
620 		for (i = 0; i < nds; i++) {
621 			setfilenames[i] = argv[i];
622 			setfiles[i] = fopen(argv[i], "r");
623 			if (setfiles[i] == NULL)
624 				err(2, "Cannot open %s", argv[i]);
625 		}
626 	}
627 
628 	if (caph_limit_stdio() < 0)
629 		err(2, "capsicum");
630 
631 	for (i = 0; i < nds; i++)
632 		if (caph_limit_stream(fileno(setfiles[i]), CAPH_READ) < 0)
633 			err(2, "unable to limit rights for %s",
634 			    setfilenames[i]);
635 
636 	/* Enter Capsicum sandbox. */
637 	if (cap_enter() < 0 && errno != ENOSYS)
638 		err(2, "unable to enter capability mode");
639 
640 	for (i = 0; i < nds; i++) {
641 		ds[i] = ReadSet(setfiles[i], setfilenames[i], column, delim);
642 		fclose(setfiles[i]);
643 	}
644 
645 	for (i = 0; i < nds; i++)
646 		printf("%c %s\n", symbol[i+1], ds[i]->name);
647 
648 	if (!flag_n && !suppress_plot) {
649 		SetupPlot(termwidth, flag_s, nds);
650 		for (i = 0; i < nds; i++)
651 			DimPlot(ds[i]);
652 		for (i = 0; i < nds; i++)
653 			PlotSet(ds[i], i + 1);
654 		DumpPlot();
655 	}
656 	VitalsHead();
657 	Vitals(ds[0], 1);
658 	for (i = 1; i < nds; i++) {
659 		Vitals(ds[i], i + 1);
660 		if (!flag_n)
661 			Relative(ds[i], ds[0], ci);
662 	}
663 	exit(0);
664 }
665