| 1 |  |  | /*	$OpenBSD: sensors.c,v 1.30 2015/01/16 00:03:38 deraadt Exp $	*/ | 
    
    | 2 |  |  |  | 
    
    | 3 |  |  | /* | 
    
    | 4 |  |  |  * Copyright (c) 2007 Deanna Phillips <deanna@openbsd.org> | 
    
    | 5 |  |  |  * Copyright (c) 2003 Henning Brauer <henning@openbsd.org> | 
    
    | 6 |  |  |  * Copyright (c) 2006 Constantine A. Murenin <cnst+openbsd@bugmail.mojo.ru> | 
    
    | 7 |  |  |  * | 
    
    | 8 |  |  |  * Permission to use, copy, modify, and distribute this software for any | 
    
    | 9 |  |  |  * purpose with or without fee is hereby granted, provided that the above | 
    
    | 10 |  |  |  * copyright notice and this permission notice appear in all copies. | 
    
    | 11 |  |  |  * | 
    
    | 12 |  |  |  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | 
    
    | 13 |  |  |  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | 
    
    | 14 |  |  |  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | 
    
    | 15 |  |  |  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | 
    
    | 16 |  |  |  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | 
    
    | 17 |  |  |  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | 
    
    | 18 |  |  |  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 
    
    | 19 |  |  |  * | 
    
    | 20 |  |  |  */ | 
    
    | 21 |  |  |  | 
    
    | 22 |  |  | #include <sys/types.h> | 
    
    | 23 |  |  | #include <sys/signal.h> | 
    
    | 24 |  |  | #include <sys/sysctl.h> | 
    
    | 25 |  |  | #include <sys/sensors.h> | 
    
    | 26 |  |  |  | 
    
    | 27 |  |  | #include <err.h> | 
    
    | 28 |  |  | #include <errno.h> | 
    
    | 29 |  |  | #include <stdio.h> | 
    
    | 30 |  |  | #include <stdlib.h> | 
    
    | 31 |  |  | #include <string.h> | 
    
    | 32 |  |  | #include "systat.h" | 
    
    | 33 |  |  |  | 
    
    | 34 |  |  | struct sensor sensor; | 
    
    | 35 |  |  | struct sensordev sensordev; | 
    
    | 36 |  |  |  | 
    
    | 37 |  |  | struct sensinfo { | 
    
    | 38 |  |  | 	int sn_dev; | 
    
    | 39 |  |  | 	struct sensor sn_sensor; | 
    
    | 40 |  |  | }; | 
    
    | 41 |  |  | #define sn_type sn_sensor.type | 
    
    | 42 |  |  | #define sn_numt sn_sensor.numt | 
    
    | 43 |  |  | #define sn_desc sn_sensor.desc | 
    
    | 44 |  |  | #define sn_status sn_sensor.status | 
    
    | 45 |  |  | #define sn_value sn_sensor.value | 
    
    | 46 |  |  |  | 
    
    | 47 |  |  | #define SYSTAT_MAXSENSORDEVICES 1024 | 
    
    | 48 |  |  | char *devnames[SYSTAT_MAXSENSORDEVICES]; | 
    
    | 49 |  |  |  | 
    
    | 50 |  |  | #define ADD_ALLOC 100 | 
    
    | 51 |  |  | static size_t sensor_cnt = 0; | 
    
    | 52 |  |  | static size_t num_alloc = 0; | 
    
    | 53 |  |  | static struct sensinfo *sensors = NULL; | 
    
    | 54 |  |  |  | 
    
    | 55 |  |  | static char *fmttime(double); | 
    
    | 56 |  |  | static void showsensor(struct sensinfo *s); | 
    
    | 57 |  |  |  | 
    
    | 58 |  |  | void print_sn(void); | 
    
    | 59 |  |  | int read_sn(void); | 
    
    | 60 |  |  | int select_sn(void); | 
    
    | 61 |  |  |  | 
    
    | 62 |  |  | const char *drvstat[] = { | 
    
    | 63 |  |  | 	NULL, | 
    
    | 64 |  |  | 	"empty", "ready", "powering up", "online", "idle", "active", | 
    
    | 65 |  |  | 	"rebuilding", "powering down", "failed", "degraded" | 
    
    | 66 |  |  | }; | 
    
    | 67 |  |  |  | 
    
    | 68 |  |  |  | 
    
    | 69 |  |  | field_def fields_sn[] = { | 
    
    | 70 |  |  | 	{"SENSOR", 16, 32, 1, FLD_ALIGN_LEFT, -1, 0, 0, 0}, | 
    
    | 71 |  |  | 	{"VALUE", 16, 20, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0}, | 
    
    | 72 |  |  | 	{"STATUS", 5, 8, 1, FLD_ALIGN_CENTER, -1, 0, 0, 0}, | 
    
    | 73 |  |  | 	{"DESCRIPTION", 20, 45, 1, FLD_ALIGN_LEFT, -1, 0, 0, 0} | 
    
    | 74 |  |  | }; | 
    
    | 75 |  |  |  | 
    
    | 76 |  |  | #define FLD_SN_SENSOR	FIELD_ADDR(fields_sn,0) | 
    
    | 77 |  |  | #define FLD_SN_VALUE	FIELD_ADDR(fields_sn,1) | 
    
    | 78 |  |  | #define FLD_SN_STATUS	FIELD_ADDR(fields_sn,2) | 
    
    | 79 |  |  | #define FLD_SN_DESCR	FIELD_ADDR(fields_sn,3) | 
    
    | 80 |  |  |  | 
    
    | 81 |  |  | /* Define views */ | 
    
    | 82 |  |  | field_def *view_sn_0[] = { | 
    
    | 83 |  |  | 	FLD_SN_SENSOR, FLD_SN_VALUE, FLD_SN_STATUS, FLD_SN_DESCR, NULL | 
    
    | 84 |  |  | }; | 
    
    | 85 |  |  |  | 
    
    | 86 |  |  |  | 
    
    | 87 |  |  | /* Define view managers */ | 
    
    | 88 |  |  | struct view_manager sensors_mgr = { | 
    
    | 89 |  |  | 	"Sensors", select_sn, read_sn, NULL, print_header, | 
    
    | 90 |  |  | 	print_sn, keyboard_callback, NULL, NULL | 
    
    | 91 |  |  | }; | 
    
    | 92 |  |  |  | 
    
    | 93 |  |  | field_view views_sn[] = { | 
    
    | 94 |  |  | 	{view_sn_0, "sensors", '3', &sensors_mgr}, | 
    
    | 95 |  |  | 	{NULL, NULL, 0, NULL} | 
    
    | 96 |  |  | }; | 
    
    | 97 |  |  |  | 
    
    | 98 |  |  | struct sensinfo * | 
    
    | 99 |  |  | next_sn(void) | 
    
    | 100 |  |  | { | 
    
    | 101 |  |  | 	if (num_alloc <= sensor_cnt) { | 
    
    | 102 |  |  | 		struct sensinfo *s; | 
    
    | 103 |  |  | 		size_t a = num_alloc + ADD_ALLOC; | 
    
    | 104 |  |  | 		if (a < num_alloc) | 
    
    | 105 |  |  | 			return NULL; | 
    
    | 106 |  |  | 		s = reallocarray(sensors, a, sizeof(struct sensinfo)); | 
    
    | 107 |  |  | 		if (s == NULL) | 
    
    | 108 |  |  | 			return NULL; | 
    
    | 109 |  |  | 		sensors = s; | 
    
    | 110 |  |  | 		num_alloc = a; | 
    
    | 111 |  |  | 	} | 
    
    | 112 |  |  |  | 
    
    | 113 |  |  | 	return &sensors[sensor_cnt++]; | 
    
    | 114 |  |  | } | 
    
    | 115 |  |  |  | 
    
    | 116 |  |  |  | 
    
    | 117 |  |  | int | 
    
    | 118 |  |  | select_sn(void) | 
    
    | 119 |  |  | { | 
    
    | 120 |  |  | 	num_disp = sensor_cnt; | 
    
    | 121 |  |  | 	return (0); | 
    
    | 122 |  |  | } | 
    
    | 123 |  |  |  | 
    
    | 124 |  |  | int | 
    
    | 125 |  |  | read_sn(void) | 
    
    | 126 |  |  | { | 
    
    | 127 |  |  | 	enum sensor_type type; | 
    
    | 128 |  |  | 	size_t		 slen, sdlen; | 
    
    | 129 |  |  | 	int		 mib[5], dev, numt; | 
    
    | 130 |  |  | 	struct sensinfo	*s; | 
    
    | 131 |  |  |  | 
    
    | 132 |  |  | 	mib[0] = CTL_HW; | 
    
    | 133 |  |  | 	mib[1] = HW_SENSORS; | 
    
    | 134 |  |  |  | 
    
    | 135 |  |  | 	sensor_cnt = 0; | 
    
    | 136 |  |  |  | 
    
    | 137 |  |  | 	for (dev = 0; dev < SYSTAT_MAXSENSORDEVICES; dev++) { | 
    
    | 138 |  |  | 		mib[2] = dev; | 
    
    | 139 |  |  | 		sdlen = sizeof(struct sensordev); | 
    
    | 140 |  |  | 		if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) == -1) { | 
    
    | 141 |  |  | 			if (errno == ENOENT) | 
    
    | 142 |  |  | 				break; | 
    
    | 143 |  |  | 			if (errno == ENXIO) | 
    
    | 144 |  |  | 				continue; | 
    
    | 145 |  |  | 			error("sysctl: %s", strerror(errno)); | 
    
    | 146 |  |  | 		} | 
    
    | 147 |  |  |  | 
    
    | 148 |  |  | 		if (devnames[dev] && strcmp(devnames[dev], sensordev.xname)) { | 
    
    | 149 |  |  | 			free(devnames[dev]); | 
    
    | 150 |  |  | 			devnames[dev] = NULL; | 
    
    | 151 |  |  | 		} | 
    
    | 152 |  |  | 		if (devnames[dev] == NULL) | 
    
    | 153 |  |  | 			devnames[dev] = strdup(sensordev.xname); | 
    
    | 154 |  |  |  | 
    
    | 155 |  |  | 		for (type = 0; type < SENSOR_MAX_TYPES; type++) { | 
    
    | 156 |  |  | 			mib[3] = type; | 
    
    | 157 |  |  | 			for (numt = 0; numt < sensordev.maxnumt[type]; numt++) { | 
    
    | 158 |  |  | 				mib[4] = numt; | 
    
    | 159 |  |  | 				slen = sizeof(struct sensor); | 
    
    | 160 |  |  | 				if (sysctl(mib, 5, &sensor, &slen, NULL, 0) | 
    
    | 161 |  |  | 				    == -1) { | 
    
    | 162 |  |  | 					if (errno != ENOENT) | 
    
    | 163 |  |  | 						error("sysctl: %s", strerror(errno)); | 
    
    | 164 |  |  | 					continue; | 
    
    | 165 |  |  | 				} | 
    
    | 166 |  |  | 				if (sensor.flags & SENSOR_FINVALID) | 
    
    | 167 |  |  | 					continue; | 
    
    | 168 |  |  |  | 
    
    | 169 |  |  | 				s = next_sn(); | 
    
    | 170 |  |  | 				s->sn_sensor = sensor; | 
    
    | 171 |  |  | 				s->sn_dev = dev; | 
    
    | 172 |  |  | 			} | 
    
    | 173 |  |  | 		} | 
    
    | 174 |  |  | 	} | 
    
    | 175 |  |  |  | 
    
    | 176 |  |  | 	num_disp = sensor_cnt; | 
    
    | 177 |  |  | 	return 0; | 
    
    | 178 |  |  | } | 
    
    | 179 |  |  |  | 
    
    | 180 |  |  |  | 
    
    | 181 |  |  | void | 
    
    | 182 |  |  | print_sn(void) | 
    
    | 183 |  |  | { | 
    
    | 184 |  |  | 	int n, count = 0; | 
    
    | 185 |  |  |  | 
    
    | 186 |  |  | 	for (n = dispstart; n < num_disp; n++) { | 
    
    | 187 |  |  | 		showsensor(sensors + n); | 
    
    | 188 |  |  | 		count++; | 
    
    | 189 |  |  | 		if (maxprint > 0 && count >= maxprint) | 
    
    | 190 |  |  | 			break; | 
    
    | 191 |  |  | 	} | 
    
    | 192 |  |  | } | 
    
    | 193 |  |  |  | 
    
    | 194 |  |  | int | 
    
    | 195 |  |  | initsensors(void) | 
    
    | 196 |  |  | { | 
    
    | 197 |  |  | 	field_view *v; | 
    
    | 198 |  |  |  | 
    
    | 199 |  |  | 	memset(devnames, 0, sizeof(devnames)); | 
    
    | 200 |  |  |  | 
    
    | 201 |  |  | 	for (v = views_sn; v->name != NULL; v++) | 
    
    | 202 |  |  | 		add_view(v); | 
    
    | 203 |  |  |  | 
    
    | 204 |  |  | 	return(1); | 
    
    | 205 |  |  | } | 
    
    | 206 |  |  |  | 
    
    | 207 |  |  | static void | 
    
    | 208 |  |  | showsensor(struct sensinfo *s) | 
    
    | 209 |  |  | { | 
    
    | 210 |  |  | 	tb_start(); | 
    
    | 211 |  |  | 	tbprintf("%s.%s%d", devnames[s->sn_dev], | 
    
    | 212 |  |  | 		 sensor_type_s[s->sn_type], s->sn_numt); | 
    
    | 213 |  |  | 	print_fld_tb(FLD_SN_SENSOR); | 
    
    | 214 |  |  |  | 
    
    | 215 |  |  | 	if (s->sn_desc[0] != '\0') | 
    
    | 216 |  |  | 		print_fld_str(FLD_SN_DESCR, s->sn_desc); | 
    
    | 217 |  |  |  | 
    
    | 218 |  |  | 	tb_start(); | 
    
    | 219 |  |  |  | 
    
    | 220 |  |  | 	switch (s->sn_type) { | 
    
    | 221 |  |  | 	case SENSOR_TEMP: | 
    
    | 222 |  |  | 		tbprintf("%10.2f degC", | 
    
    | 223 |  |  | 		    (s->sn_value - 273150000) / 1000000.0); | 
    
    | 224 |  |  | 		break; | 
    
    | 225 |  |  | 	case SENSOR_FANRPM: | 
    
    | 226 |  |  | 		tbprintf("%11lld RPM", s->sn_value); | 
    
    | 227 |  |  | 		break; | 
    
    | 228 |  |  | 	case SENSOR_VOLTS_DC: | 
    
    | 229 |  |  | 		tbprintf("%10.2f V DC", | 
    
    | 230 |  |  | 		    s->sn_value / 1000000.0); | 
    
    | 231 |  |  | 		break; | 
    
    | 232 |  |  | 	case SENSOR_VOLTS_AC: | 
    
    | 233 |  |  | 		tbprintf("%10.2f V AC", | 
    
    | 234 |  |  | 		    s->sn_value / 1000000.0); | 
    
    | 235 |  |  | 		break; | 
    
    | 236 |  |  | 	case SENSOR_OHMS: | 
    
    | 237 |  |  | 		tbprintf("%11lld ohm", s->sn_value); | 
    
    | 238 |  |  | 		break; | 
    
    | 239 |  |  | 	case SENSOR_WATTS: | 
    
    | 240 |  |  | 		tbprintf("%10.2f W", s->sn_value / 1000000.0); | 
    
    | 241 |  |  | 		break; | 
    
    | 242 |  |  | 	case SENSOR_AMPS: | 
    
    | 243 |  |  | 		tbprintf("%10.2f A", s->sn_value / 1000000.0); | 
    
    | 244 |  |  | 		break; | 
    
    | 245 |  |  | 	case SENSOR_WATTHOUR: | 
    
    | 246 |  |  | 		tbprintf("%12.2f Wh", s->sn_value / 1000000.0); | 
    
    | 247 |  |  | 		break; | 
    
    | 248 |  |  | 	case SENSOR_AMPHOUR: | 
    
    | 249 |  |  | 		tbprintf("%10.2f Ah", s->sn_value / 1000000.0); | 
    
    | 250 |  |  | 		break; | 
    
    | 251 |  |  | 	case SENSOR_INDICATOR: | 
    
    | 252 |  |  | 		tbprintf("%15s", s->sn_value ? "On" : "Off"); | 
    
    | 253 |  |  | 		break; | 
    
    | 254 |  |  | 	case SENSOR_INTEGER: | 
    
    | 255 |  |  | 		tbprintf("%11lld raw", s->sn_value); | 
    
    | 256 |  |  | 		break; | 
    
    | 257 |  |  | 	case SENSOR_PERCENT: | 
    
    | 258 |  |  | 		tbprintf("%14.2f%%", s->sn_value / 1000.0); | 
    
    | 259 |  |  | 		break; | 
    
    | 260 |  |  | 	case SENSOR_LUX: | 
    
    | 261 |  |  | 		tbprintf("%15.2f lx", s->sn_value / 1000000.0); | 
    
    | 262 |  |  | 		break; | 
    
    | 263 |  |  | 	case SENSOR_DRIVE: | 
    
    | 264 |  |  | 		if (0 < s->sn_value && | 
    
    | 265 |  |  | 		    s->sn_value < sizeof(drvstat)/sizeof(drvstat[0])) { | 
    
    | 266 |  |  | 			tbprintf("%15s", drvstat[s->sn_value]); | 
    
    | 267 |  |  | 			break; | 
    
    | 268 |  |  | 		} | 
    
    | 269 |  |  | 		break; | 
    
    | 270 |  |  | 	case SENSOR_TIMEDELTA: | 
    
    | 271 |  |  | 		tbprintf("%15s", fmttime(s->sn_value / 1000000000.0)); | 
    
    | 272 |  |  | 		break; | 
    
    | 273 |  |  | 	case SENSOR_HUMIDITY: | 
    
    | 274 |  |  | 		tbprintf("%3.2f%%", s->sn_value / 1000.0); | 
    
    | 275 |  |  | 		break; | 
    
    | 276 |  |  | 	case SENSOR_FREQ: | 
    
    | 277 |  |  | 		tbprintf("%11.2f Hz", s->sn_value / 1000000.0); | 
    
    | 278 |  |  | 		break; | 
    
    | 279 |  |  | 	case SENSOR_ANGLE: | 
    
    | 280 |  |  | 		tbprintf("%3.4f degrees", s->sn_value / 1000000.0); | 
    
    | 281 |  |  | 		break; | 
    
    | 282 |  |  | 	case SENSOR_DISTANCE: | 
    
    | 283 |  |  | 		tbprintf("%.2f mm", s->sn_value / 1000.0); | 
    
    | 284 |  |  | 		break; | 
    
    | 285 |  |  | 	case SENSOR_PRESSURE: | 
    
    | 286 |  |  | 		tbprintf("%.2f Pa", s->sn_value / 1000.0); | 
    
    | 287 |  |  | 		break; | 
    
    | 288 |  |  | 	case SENSOR_ACCEL: | 
    
    | 289 |  |  | 		tbprintf("%2.4f m/s^2", s->sn_value / 1000000.0); | 
    
    | 290 |  |  | 		break; | 
    
    | 291 |  |  | 	default: | 
    
    | 292 |  |  | 		tbprintf("%10lld", s->sn_value); | 
    
    | 293 |  |  | 		break; | 
    
    | 294 |  |  | 	} | 
    
    | 295 |  |  |  | 
    
    | 296 |  |  | 	print_fld_tb(FLD_SN_VALUE); | 
    
    | 297 |  |  |  | 
    
    | 298 |  |  | 	switch (s->sn_status) { | 
    
    | 299 |  |  | 	case SENSOR_S_UNSPEC: | 
    
    | 300 |  |  | 		break; | 
    
    | 301 |  |  | 	case SENSOR_S_UNKNOWN: | 
    
    | 302 |  |  | 		print_fld_str(FLD_SN_STATUS, "unknown"); | 
    
    | 303 |  |  | 		break; | 
    
    | 304 |  |  | 	case SENSOR_S_WARN: | 
    
    | 305 |  |  | 		print_fld_str(FLD_SN_STATUS, "WARNING"); | 
    
    | 306 |  |  | 		break; | 
    
    | 307 |  |  | 	case SENSOR_S_CRIT: | 
    
    | 308 |  |  | 		print_fld_str(FLD_SN_STATUS, "CRITICAL"); | 
    
    | 309 |  |  | 		break; | 
    
    | 310 |  |  | 	case SENSOR_S_OK: | 
    
    | 311 |  |  | 		print_fld_str(FLD_SN_STATUS, "OK"); | 
    
    | 312 |  |  | 		break; | 
    
    | 313 |  |  | 	} | 
    
    | 314 |  |  | 	end_line(); | 
    
    | 315 |  |  | } | 
    
    | 316 |  |  |  | 
    
    | 317 |  |  | #define SECS_PER_DAY 86400 | 
    
    | 318 |  |  | #define SECS_PER_HOUR 3600 | 
    
    | 319 |  |  | #define SECS_PER_MIN 60 | 
    
    | 320 |  |  |  | 
    
    | 321 |  |  | static char * | 
    
    | 322 |  |  | fmttime(double in) | 
    
    | 323 |  |  | { | 
    
    | 324 |  |  | 	int signbit = 1; | 
    
    | 325 |  |  | 	int tiny = 0; | 
    
    | 326 |  |  | 	char *unit; | 
    
    | 327 |  |  | #define LEN 32 | 
    
    | 328 |  |  | 	static char outbuf[LEN]; | 
    
    | 329 |  |  |  | 
    
    | 330 |  |  | 	if (in < 0){ | 
    
    | 331 |  |  | 		signbit = -1; | 
    
    | 332 |  |  | 		in *= -1; | 
    
    | 333 |  |  | 	} | 
    
    | 334 |  |  |  | 
    
    | 335 |  |  | 	if (in >= SECS_PER_DAY ){ | 
    
    | 336 |  |  | 		unit = "days"; | 
    
    | 337 |  |  | 		in /= SECS_PER_DAY; | 
    
    | 338 |  |  | 	} else if (in >= SECS_PER_HOUR ){ | 
    
    | 339 |  |  | 		unit = "hr"; | 
    
    | 340 |  |  | 		in /= SECS_PER_HOUR; | 
    
    | 341 |  |  | 	} else if (in >= SECS_PER_MIN ){ | 
    
    | 342 |  |  | 		unit = "min"; | 
    
    | 343 |  |  | 		in /= SECS_PER_MIN; | 
    
    | 344 |  |  | 	} else if (in >= 1 ){ | 
    
    | 345 |  |  | 		unit = "s"; | 
    
    | 346 |  |  | 		/* in *= 1; */ /* no op */ | 
    
    | 347 |  |  | 	} else if (in == 0 ){ /* direct comparisons to floats are scary */ | 
    
    | 348 |  |  | 		unit = "s"; | 
    
    | 349 |  |  | 	} else if (in >= 1e-3 ){ | 
    
    | 350 |  |  | 		unit = "ms"; | 
    
    | 351 |  |  | 		in *= 1e3; | 
    
    | 352 |  |  | 	} else if (in >= 1e-6 ){ | 
    
    | 353 |  |  | 		unit = "us"; | 
    
    | 354 |  |  | 		in *= 1e6; | 
    
    | 355 |  |  | 	} else if (in >= 1e-9 ){ | 
    
    | 356 |  |  | 		unit = "ns"; | 
    
    | 357 |  |  | 		in *= 1e9; | 
    
    | 358 |  |  | 	} else { | 
    
    | 359 |  |  | 		unit = "ps"; | 
    
    | 360 |  |  | 		if (in < 1e-13) | 
    
    | 361 |  |  | 			tiny = 1; | 
    
    | 362 |  |  | 		in *= 1e12; | 
    
    | 363 |  |  | 	} | 
    
    | 364 |  |  |  | 
    
    | 365 |  |  | 	snprintf(outbuf, LEN, | 
    
    | 366 |  |  | 	    tiny ? "%s%f %s" : "%s%.3f %s", | 
    
    | 367 |  |  | 	    signbit == -1 ? "-" : "", in, unit); | 
    
    | 368 |  |  |  | 
    
    | 369 |  |  | 	return outbuf; | 
    
    | 370 |  |  | } |