| // ttrace: basic functionality test of the #T ttrace device | 
 |  | 
 | #define _LARGEFILE64_SOURCE /* needed to use lseek64 */ | 
 |  | 
 | #include <sys/types.h> | 
 | #include <sys/stat.h> | 
 |  | 
 | #include <assert.h> | 
 | #include <ctype.h> | 
 | #include <fcntl.h> | 
 | #include <errno.h> | 
 | #include <libgen.h> | 
 | #include <stdarg.h> | 
 | #include <stdbool.h> | 
 | #include <stdint.h> | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 | #include <sysexits.h> | 
 | #include <unistd.h> | 
 |  | 
 | #include <ros/ttrace.h> | 
 |  | 
 | #ifndef min | 
 | #define min(a, b) ({ \ | 
 | 	typeof (a) _a = (a); typeof (b) _b = (b); _a < _b ? _a : _b; }) | 
 | #endif | 
 |  | 
 | typedef int Verbimpl(int argc, char *argv[]); | 
 |  | 
 | struct verbtab { | 
 | 	const char *name; | 
 | 	const char *abbrev; | 
 | 	Verbimpl *impl; | 
 | 	uint8_t min_args, max_args; | 
 | }; | 
 |  | 
 | static const char *cmd_name; | 
 | static char mega_buffer[2 * 1024 * 1024]; | 
 | static size_t mega_cur; | 
 | static size_t mega_remaining; | 
 |  | 
 | // Command implementation forward declarations | 
 | static int scaf_tswr(int argc, char *argv[]); | 
 | static int dump_data(int argc, char *argv[]); | 
 | static const struct verbtab verbs[] = { | 
 | 	{ "scaf_tswr", "twr", &scaf_tswr, 2, 2 }, | 
 | 	{ "dump_data", "dd",  &dump_data, 0, 1 }, | 
 | }; | 
 | #define NVERBTAB (sizeof(verbs)/sizeof(verbs[0])) | 
 |  | 
 | static void mega_buffer_clear() | 
 | { | 
 | 	mega_cur = 0; | 
 | 	mega_remaining = sizeof(mega_buffer); | 
 | } | 
 |  | 
 | static void init(char *argv[]) | 
 | { | 
 | 	mega_buffer_clear(); | 
 | 	cmd_name = basename(argv[0]); | 
 | } | 
 |  | 
 | static void outerrf(const char *fmt, ...) | 
 | { | 
 | 	fflush(stdout); | 
 |  | 
 | 	va_list ap; | 
 | 	va_start(ap, fmt); | 
 | 	vfprintf(stderr, fmt, ap); | 
 | 	va_end(ap); | 
 | 	fflush(stderr); | 
 | } | 
 |  | 
 | static void outf(const char *fmt, ...) | 
 | { | 
 | 	fflush(stderr); | 
 |  | 
 | 	va_list ap; | 
 | 	va_start(ap, fmt); | 
 | 	vprintf(fmt, ap); | 
 | 	va_end(ap); | 
 | 	fflush(stdout); | 
 | } | 
 |  | 
 | static void usage(const char *fmt, ...) __attribute__((noreturn)); | 
 | static void usage(const char *fmt, ...) | 
 | { | 
 | 	static const char usage_msg[] = | 
 | "<verb> [args...]\n" | 
 | "where <verb>:\n" | 
 | "    scaf_tswr(twr) <ts> <file>\n" | 
 | "        Scaffold write and read integer timestamp to given file.\n" | 
 | "    dump_data(data) [<ts>]\n" | 
 | "        Dump constant data table, from ts if given 1 otherwise.\n"; | 
 |  | 
 | 	va_list ap; | 
 | 	char error_message[1024]; | 
 |  | 
 | 	/* Try to print in the allocated space */ | 
 | 	va_start(ap, fmt); | 
 | 	vsnprintf(error_message, sizeof(error_message), fmt, ap); | 
 | 	va_end(ap); | 
 |  | 
 | 	outerrf("%s: %s\n%s %s", | 
 | 			cmd_name, error_message, cmd_name, usage_msg); | 
 |  | 
 | 	exit(EX_USAGE); | 
 | } | 
 |  | 
 | static void error(int code, int errnum, const char *fmt, ...) | 
 | 	__attribute__((noreturn)); | 
 | static void error(int code, int errnum, const char *fmt, ...) | 
 | { | 
 | 	va_list ap; | 
 | 	char error_message[1024]; | 
 |  | 
 | 	/* Try to print in the allocated space */ | 
 | 	va_start(ap, fmt); | 
 | 	vsnprintf(error_message, sizeof(error_message), fmt, ap); | 
 | 	va_end(ap); | 
 |  | 
 | 	if (errnum) | 
 | 		outerrf("%s: %s : %s(%d)\n", | 
 | 				cmd_name, error_message, strerror(errnum), errnum); | 
 | 	else | 
 | 		outerrf("%s: %s\n", cmd_name, error_message); | 
 | 	exit(code); | 
 | } | 
 |  | 
 | static int scaf_tswr(int argc, char *argv[]) | 
 | { | 
 | 	assert(2 == argc); | 
 | 	const char * const arg_timestamp = argv[0]; | 
 | 	const char * const arg_fname = argv[1]; | 
 | 	char filename[10]; // big enough for "#T/cpunnn" | 
 |  | 
 | 	size_t len = snprintf(filename, sizeof(filename), "#T/%s", arg_fname); | 
 | 	if (len >= sizeof(filename)) | 
 | 		error(EX_DATAERR, 0, | 
 | 			  "%filename '%s' too long for ttrace device", arg_fname); | 
 |  | 
 | 	int fd = open(filename, O_RDWR); | 
 | 	if (fd < 0) | 
 | 		error(EX_NOINPUT, errno, "Can't open '%s'", filename); | 
 | 	 | 
 | 	len = snprintf(mega_buffer, mega_remaining, "setts %s", arg_timestamp); | 
 | 	if (write(fd, mega_buffer, len) != len) | 
 | 		error(EX_DATAERR, errno, "Can't write command '%s'", mega_buffer); | 
 | 	else | 
 | 		outf("Wrote '%s' to %s\n", mega_buffer, filename); | 
 |  | 
 | 	if (lseek64(fd, 0, SEEK_SET) < 0) | 
 | 		error(EX_OSERR, errno, "Couldn't seek to beginning of '%s'", filename); | 
 |  | 
 | 	len = read(fd, mega_buffer, mega_remaining - 1); | 
 | 	if (len < 0) | 
 | 		error(EX_OSERR, errno, "Can't read data from '%s'", filename); | 
 | 	else { | 
 | 		mega_buffer[len] = '\0'; | 
 | 		char *newline = strrchr(mega_buffer, '\n'); | 
 | 		if (newline) *newline = '\0'; | 
 | 		outf("Read '%s' from %s\n", mega_buffer, filename); | 
 | 	} | 
 | 	if (close(fd) < 0) | 
 | 		error(EX_OSERR, errno, "Can't close '%s'", filename); | 
 |  | 
 | 	return EX_OK; | 
 | } | 
 |  | 
 | static bool gethex4(char hexchar, uint8_t *outp) | 
 | { | 
 | 	bool ok = false; | 
 | 	if (isxdigit(hexchar)) { | 
 | 		if (hexchar - '0' <= 9) | 
 | 			*outp = hexchar - '0'; | 
 | 		else | 
 | 			*outp = tolower(hexchar) - 'a' + 0xa; | 
 | 		assert((unsigned) *outp < 0x10); | 
 | 		ok = true; | 
 | 	} | 
 | 	return ok; | 
 | } | 
 |  | 
 | static bool _gethex8(const char *hexstr, uint8_t *outp) | 
 | { | 
 | 	uint8_t hinib, lonib; | 
 | 	bool ok = gethex4(hexstr[0], &hinib); | 
 | 	if (ok) | 
 | 		ok = gethex4(hexstr[1], &lonib); | 
 | 	if (ok) | 
 | 		*outp = hinib << 4 | lonib; | 
 | 	return ok; | 
 | } | 
 |  | 
 | static bool _gethex16(const char *hexstr, uint16_t *outp) | 
 | { | 
 | 	uint8_t hibyte, lobyte; | 
 | 	bool ok = _gethex8(&hexstr[0], &hibyte) | 
 | 		  &&  _gethex8(&hexstr[2], &lobyte); | 
 | 	if (ok) | 
 | 		*outp = ((uint16_t) hibyte << 8) | lobyte; | 
 | 	return ok; | 
 | } | 
 |  | 
 | static bool _gethex32(const char *hexstr, uint32_t *outp) | 
 | { | 
 | 	uint16_t hishort, loshort; | 
 | 	bool ok = _gethex16(&hexstr[0], &hishort) | 
 | 		  &&  _gethex16(&hexstr[4], &loshort); | 
 | 	if (ok) | 
 | 		*outp = ((uint32_t) hishort << 16) | loshort; | 
 | 	return ok; | 
 | } | 
 |  | 
 | static bool _gethex64(const char *hexstr, uint64_t *outp) | 
 | { | 
 | 	uint32_t hiint, loint; | 
 | 	bool ok = _gethex32(&hexstr[0], &hiint) | 
 | 		  &&  _gethex32(&hexstr[8], &loint); | 
 | 	if (ok) | 
 | 		*outp = ((uint64_t) hiint << 32) | loint; | 
 | 	return ok; | 
 | } | 
 |    | 
 | static inline bool checklen(const char *str, size_t len) | 
 | { | 
 | 	return strnlen(str, len) == len; | 
 | } | 
 |  | 
 | static inline bool gethex8(const char *hexstr, uint8_t *outp) | 
 | { | 
 | 	return checklen(hexstr, 2) &&_gethex8(hexstr, outp); | 
 | } | 
 |  | 
 | static inline bool gethex16(const char *hexstr, uint16_t *outp) | 
 | { | 
 | 	return checklen(hexstr, 4) &&_gethex16(hexstr, outp); | 
 | } | 
 |  | 
 | static inline bool gethex32(const char *hexstr, uint32_t *outp) | 
 | { | 
 | 	return checklen(hexstr, 8) &&_gethex32(hexstr, outp); | 
 | } | 
 |  | 
 | static inline bool gethex64(const char *hexstr, uint64_t *outp) | 
 | { | 
 | 	return checklen(hexstr, 16) && _gethex64(hexstr, outp); | 
 | } | 
 |  | 
 | static bool gethdr(const char *hexstr, | 
 | 				   uint8_t *lenp, uint8_t *tagp, uint64_t *timestampp) | 
 | { | 
 | 	bool ok = checklen(&hexstr[0], 4); | 
 | 	ok = ok && _gethex8(&hexstr[0], lenp); | 
 | 	ok = ok && _gethex8(&hexstr[2], tagp); | 
 | 	assert(!ok || !(*tagp & 0x80)); // Can't deal with continuations yet | 
 | 	ok = ok && gethex64(&hexstr[4], timestampp); | 
 | 	return ok; | 
 | } | 
 |  | 
 | // Decode bsd nn.nn | 
 | static inline double decode_vers(uint16_t vers) | 
 | { | 
 | 	return 10.00 * ((vers >> 12) & 0xf) + 1.00 * ((vers >>  8) & 0xf) | 
 | 		  + 0.10 * ((vers >>  4) & 0xf) + 0.01 * ((vers)       & 0xf); | 
 | } | 
 |  | 
 | static void dd_decode_info(const char *line, int lineno) | 
 | { | 
 | 	const char *info = &line[TTRACEH_LEN]; | 
 | 	uint16_t dv, cv, ncpu; | 
 | 	bool ok = checklen(&line[TTRACEH_LEN], 12); | 
 | 	ok = ok && _gethex16(&info[0], &dv); | 
 | 	ok = ok && _gethex16(&info[4], &cv); | 
 | 	ok = ok && _gethex16(&info[8], &ncpu); | 
 | 	if (ok) | 
 | 		outf("%5d: INFO data v%.02f cpu v%0.2f ncpu %d\n", | 
 | 				lineno, decode_vers(dv), decode_vers(cv), ncpu); | 
 | 	else | 
 | 		outerrf("%5d: bad INFO %s\n", lineno, line); | 
 | } | 
 |  | 
 | static void dd_decode_sysc(const char *line, int lineno) | 
 | { | 
 | 	uint64_t id; | 
 | 	bool ok = gethex64(&line[TTRACEH_LEN], &id); | 
 | 	if (id < (1 << 16)) | 
 | 		outf("%5d: SYSC %3lld %s\n", lineno, id, &line[16 + TTRACEH_LEN]); | 
 | 	else | 
 | 		outerrf("%5d: Bad sysc %016llx %s\n", lineno, id, line); | 
 |  | 
 | } | 
 |  | 
 | static void dd_decode_line(const char *line, int lineno) | 
 | { | 
 | 	uint8_t len; | 
 | 	uint8_t tag; | 
 | 	uint64_t timestamp; | 
 | 	bool ok = gethdr(line, &len, &tag, ×tamp); | 
 | 	if (!ok) { | 
 | 		outerrf("%5d: bad hdr %s\n", lineno, line); | 
 | 		return; | 
 | 	} | 
 | 	if (strlen(line) != len) { | 
 | 		outerrf(" %5d: bad len %s\n", lineno, line); | 
 | 		return; | 
 | 	} | 
 | 	switch (tag) { | 
 | 	case TTRACEH_TAG_INFO: dd_decode_info(line, lineno); break; | 
 | 	case TTRACEH_TAG_SYSC: dd_decode_sysc(line, lineno); break; | 
 | 	default: | 
 | 		outerrf("bad tag %4d: %s\n", lineno, line); | 
 | 	} | 
 | } | 
 |  | 
 | static void outerrline(const char *line, int lineno) | 
 | { | 
 | 	outerrf("%5d: len %d\n", lineno, strlen(line)); | 
 | 	outerrf("     %-2.2s %-2.2s %-16.16s %s\n", | 
 | 			&line[0], &line[2], &line[4], &line[20]); | 
 |  | 
 | } | 
 |  | 
 | static int dump_data(int argc, char *argv[]) | 
 | { | 
 | 	assert(argc <= 1); | 
 | 	const char * const arg_timestamp = argv[0]; | 
 | 	char filename[] = "#T/data"; | 
 |  | 
 | 	int fd = open(filename, O_RDWR); | 
 | 	if (fd < 0) | 
 | 		error(EX_NOINPUT, errno, "Can't open '%s'", filename); | 
 | 	 | 
 | 	ssize_t len; | 
 | 	if (argc == 1) { | 
 | 		len = snprintf(mega_buffer, mega_remaining, "setts %s", arg_timestamp); | 
 | 		if (write(fd, mega_buffer, len) != len) | 
 | 			error(EX_DATAERR, errno, "Can't write command '%s'", mega_buffer); | 
 | 		else | 
 | 			outf("Wrote '%s' to %s\n", mega_buffer, filename); | 
 | 	} | 
 |  | 
 | 	mega_buffer_clear(); | 
 | 	len = read(fd, mega_buffer, mega_remaining - 1); | 
 | 	if (len < 0) | 
 | 		error(EX_OSERR, errno, "Can't read data from '%s'", filename); | 
 | 	else { | 
 | 		mega_buffer[len] = '\0'; | 
 | 		mega_remaining -= len; | 
 | 	} | 
 |  | 
 | 	int nlines = 0; | 
 | 	char *cp = mega_buffer; | 
 | 	while ( (cp = strchr(cp+1, '\n')) ) | 
 | 		nlines++; | 
 | 	outf("Read %d data lines for %d bytes\n", nlines, len); | 
 | 	char *line, *saveptr = NULL; | 
 | 	int lineno = 0; | 
 | 	for (cp = mega_buffer; (line = strtok_r(cp, "\n", &saveptr)); cp = NULL) { | 
 | 		dd_decode_line(line, ++lineno); | 
 | 	} | 
 |  | 
 | 	if (close(fd) < 0) | 
 | 		error(EX_OSERR, errno, "Can't close '%s'", filename); | 
 |  | 
 | 	return EX_OK; | 
 | } | 
 |  | 
 | int main(int argc, char *argv[]) | 
 | { | 
 | 	init(argv); | 
 |  | 
 | 	if (argc < 2) usage("No verb"); | 
 | 	argc -= 2; | 
 |  | 
 |     const char *verb_str = argv[1]; | 
 | 	memmove(&argv[0], &argv[2], argc * sizeof(argv[0])); | 
 | 	for (int i = 0; i < NVERBTAB; i++) { | 
 | 		const struct verbtab * const cmdp = &verbs[i]; | 
 | 		if (strcmp(verb_str, cmdp->name) && strcmp(verb_str, cmdp->abbrev)) | 
 | 			continue; | 
 | 		if (argc < cmdp->min_args) | 
 | 			usage("verb '%s': requires %d arguments, %d given", | 
 | 				  cmdp->name, cmdp->min_args, argc); | 
 | 		else if (cmdp->max_args > 0 && cmdp->max_args < argc) | 
 | 			usage("verb '%s': requires at most %d arguments, %d given", | 
 | 				  cmdp->name, cmdp->min_args, argc); | 
 |  | 
 | 		// Found a valid command, call it and exit | 
 | 		return (*cmdp->impl)(argc, argv); | 
 | 	} | 
 | 	usage("verb '%s': not recognised", verb_str); | 
 | } |