This patch adds a reader tool for IVRing. This tool is used on a host OS and reads data written by a guest. This reader reads data from a ring-buffer via POSIX share memory, so the data will be read without memory copying between a guest and a host. To read data written by a guest, s option assigning same shared memory object of IVShmem is needed.
Some options are available as follows: -f: output log file -h: show usage -m: shared memory size in MB -s: shared memory object path -N: number of log files -S: log file size in MB Example: ./ivring_reader -m 2 -f /tmp/log.txt -S 10 -N 2 -s /ivshmem In this case, two log files are output as /tmp/log.txt.0 and /tmp/log.txt.1 whose sizes are 10MB. Signed-off-by: Yoshihiro YUNOMAE <yoshihiro.yunomae...@hitachi.com> Signed-off-by: Masami Hiramatsu <masami.hiramatsu...@hitachi.com> Signed-off-by: Akihiro Nagai <akihiro.nagai...@hitachi.com> Cc: Borislav Petkov <borislav.pet...@amd.com> Cc: Arnaldo Carvalho de Melo <a...@redhat.com> Cc: linux-ker...@vger.kernel.org Cc: Cam Macdonell <c...@cs.ualberta.ca> Cc: qemu-devel@nongnu.org Cc: system...@sourceware.org --- tools/Makefile | 1 tools/ivshmem/Makefile | 19 ++ tools/ivshmem/ivring_reader.c | 516 +++++++++++++++++++++++++++++++++++++++++ tools/ivshmem/ivring_reader.h | 15 + tools/ivshmem/pr_msg.c | 125 ++++++++++ tools/ivshmem/pr_msg.h | 19 ++ 6 files changed, 695 insertions(+), 0 deletions(-) create mode 100644 tools/ivshmem/Makefile create mode 100644 tools/ivshmem/ivring_reader.c create mode 100644 tools/ivshmem/ivring_reader.h create mode 100644 tools/ivshmem/pr_msg.c create mode 100644 tools/ivshmem/pr_msg.h diff --git a/tools/Makefile b/tools/Makefile index 3ae4394..3edf16a 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -5,6 +5,7 @@ help: @echo '' @echo ' cpupower - a tool for all things x86 CPU power' @echo ' firewire - the userspace part of nosy, an IEEE-1394 traffic sniffer' + @echo ' ivshmem - the userspace tool for ivshmem device' @echo ' lguest - a minimal 32-bit x86 hypervisor' @echo ' perf - Linux performance measurement and analysis tool' @echo ' selftests - various kernel selftests' diff --git a/tools/ivshmem/Makefile b/tools/ivshmem/Makefile new file mode 100644 index 0000000..287508e --- /dev/null +++ b/tools/ivshmem/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -O1 -Wall -Werror -g +LIBS = -lrt + +# makefile to build ivshmem tools + +all: ivring_reader + +.c.o: + $(CC) $(CFLAGS) -c $^ -o $@ + +ivring_reader: ivring_reader.o pr_msg.o + $(CC) $(CFLAGS) -o $@ $^ $(LIBS) + +install: ivring_reader + install ivring_reader /usr/local/bin/ + +clean: + rm -f *.o ivring_reader diff --git a/tools/ivshmem/ivring_reader.c b/tools/ivshmem/ivring_reader.c new file mode 100644 index 0000000..d61e9c9 --- /dev/null +++ b/tools/ivshmem/ivring_reader.c @@ -0,0 +1,516 @@ +/* + * A trace reader for inter-VM shared memory + * + * (C) 2012 Hitachi, Ltd. + * Written by Hitachi Yokohama Research Laboratory. + * + * Created by Masami Hiramatsu <masami.hiramatsu...@hitachi.com> + * Akihiro Nagai <akihiro.nagai...@hitachi.com> + * Yoshihiro Yunomae <yoshihiro.yunomae...@hitachi.com> + * based on IVShmem Server, http://www.gitorious.org/nahanni/guest-code, + * (C) 2009 Cam Macdonell <c...@cs.ualberta.ca> + * + * Licensed under GPL version 2 only. + * + */ + +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/eventfd.h> +#include <sys/mman.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include "../../drivers/ivshmem/ivring.h" +#include "pr_msg.h" +#include "ivring_reader.h" + +/* default pathes */ +#define DEFAULT_SHM_SIZE (1024*1024) +#define BUFFER_SIZE 4096 + +static int global_term; +static int global_outfd; +static char *global_log_basename; +static ssize_t global_log_rotate_size; +static int global_log_rotate_num; +#define log_rotate_mode() (global_log_rotate_size && global_log_rotate_num) + +/* Handle SIGTERM/SIGINT/SIGQUIT to exit */ +void term_handler(int sig) +{ + global_term = sig; + pr_info("Receive an interrupt %d\n", sig); +} + +/* Utilities */ +static void *zalloc(size_t size) +{ + void *ret = malloc(size); + if (ret) + memset(ret, 0, size); + else + pr_perror("malloc"); + return ret; +} + +static u32 __fls32(u32 word) +{ + int num = 31; + if (!(word & (~0ul << 16))) { + num -= 16; + word <<= 16; + } + if (!(word & (~0ul << (32-8)))) { + num -= 8; + word <<= 8; + } + if (!(word & (~0ul << (32-4)))) { + num -= 4; + word <<= 4; + } + if (!(word & (~0ul << (32-2)))) { + num -= 2; + word <<= 2; + } + if (!(word & (~0ul << (32-1)))) + num -= 1; + return num; +} + +/* IVRing Header functions */ +int ivring_hdr_init(struct ivring_hdr *hdr, u32 shmsize) +{ + if (strncmp(hdr->magic, IVRING_MAGIC, 4) == 0) { + pr_debug("Ring header is already initialized\n"); + pr_debug("reader %d, writer %d, pos %llx\n", + (int)hdr->reader, (int)hdr->writer, hdr->pos); + if (hdr->version != IVRING_VERSION) { + pr_debug("Ring version is different! (%d)\n", + (int)hdr->version); + return -EINVAL; + } + return 0; + } + memset(hdr, 0, IVRING_OFFSET); + memcpy(hdr->magic, IVRING_MAGIC, 4); + hdr->version = IVRING_VERSION; + hdr->reader = -1; + hdr->writer = -1; + hdr->total_bits = __fls32(shmsize); + hdr->total_mask = ~(~0 << hdr->total_bits); + hdr->threshold = IVRING_INIT_THRESHOLD; + hdr->pos = IVRING_STARTPOS; + return 1; +} + +void ivring_hdr_free(struct ivring_hdr *hdr, size_t size) +{ + munmap(hdr, size); +} + +struct ivring_hdr *ivring_hdr_new(int shmfd, size_t size) +{ + struct ivring_hdr *hdr; + + hdr = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, shmfd, 0); + if (!hdr) { + pr_perror("mmap"); + return NULL; + } + + if (ivring_hdr_init(hdr, (u32)size) < 0) { + munmap(hdr, size); + return NULL; + } + + return hdr; +} + +static inline u64 fixup_pos64(u64 pos, u32 total_mask) +{ + if (((u32)pos & total_mask) < IVRING_OFFSET) + pos += IVRING_OFFSET; + return pos; +} + +struct ivring_read_ops { + ssize_t (*read)(void *data, void *saddr, ssize_t size); + ssize_t (*cancel)(void *data, ssize_t size); +}; + +/* Ringbuffer Reader */ +ssize_t __ivring_read(struct ivring_user *ivr, struct ivring_read_ops *ops, + void *data, ssize_t max_size) +{ + struct ivring_hdr *hdr = ivr->hdr; + void *saddr, *eaddr, *end, *start; + u64 rpos; + ssize_t read_size; + + if (!hdr) + return -EINVAL; + + if (hdr->pos == ivr->rpos) { /* No data is ready */ + pr_debug("no data\n"); + return 0; + } + + start = ivring_start_addr(hdr); + end = ivring_end_addr(hdr); + + rpos = ivr->rpos; + if ((hdr->pos - rpos) >> hdr->total_bits) { + /* Writer cought up */ + rpos = hdr->pos - (1 << hdr->total_bits) + IVRING_READ_MARGIN; + rpos = fixup_pos64(rpos, hdr->total_mask); + pr_debug("Event drop detected! -- fixup\n"); + ivr->rpos = rpos; + } + saddr = ivring_pos64_addr(hdr, rpos); + + rpos = fixup_pos64(rpos + max_size, hdr->total_mask); + if (rpos > hdr->pos) + rpos = hdr->pos; + eaddr = ivring_pos64_addr(hdr, rpos); + + if (saddr < eaddr) + read_size = ops->read(data, saddr, eaddr - saddr); + else { + ssize_t tmp; + read_size = ops->read(data, saddr, end - saddr); + if (read_size < 0) + return read_size; + tmp = ops->read(data, start, eaddr - start); + if (tmp < 0) + return tmp; + read_size += tmp; + } + + if ((hdr->pos - ivr->rpos) >> hdr->total_bits) { + /* Cought up again */ + pr_debug("Overwritten detected!\n"); + return ops->cancel(data, read_size); + } + + ivr->rpos = rpos; + return read_size; +} + +/* Read from ring to memory */ +static ssize_t read_memcpy(void *data, void *saddr, ssize_t size) +{ + memcpy(data, saddr, size); + return size; +} + +static ssize_t cancel_memcpy(void *data, ssize_t size) +{ + return -EAGAIN; +} + +ssize_t ivring_memcpy(struct ivring_user *ivr, void *buf, ssize_t bufsize) +{ + ssize_t ret; + static struct ivring_read_ops ops = { + .read = read_memcpy, + .cancel = cancel_memcpy}; + + do { + ret = __ivring_read(ivr, &ops, buf, bufsize); + } while (ret == -EAGAIN); + return ret; +} + +/* Read from ring to file */ +static ssize_t read_write_fd(void *data, void *saddr, ssize_t size) +{ + int fd = (int)(long)data; + return write(fd, saddr, size); +} + +static ssize_t cancel_write_fd(void *data, ssize_t size) +{ + int fd = (int)(long)data; + lseek(fd, (off_t)-size, SEEK_CUR); + return -EAGAIN; +} + +ssize_t ivring_read_fd(struct ivring_user *ivr, int fd, ssize_t blocksize) +{ + ssize_t ret; + static struct ivring_read_ops ops = { + .read = read_write_fd, + .cancel = cancel_write_fd}; + + do { + ret = __ivring_read(ivr, &ops, (void *)(long)fd, blocksize); + pr_debug("__ivring_read ret=%d\n", ret); + } while (ret == -EAGAIN); + return ret; +} + +int ivring_init_rpos(struct ivring_user *ivr) +{ + if (ivr->hdr->pos > ivr->shm_size) + ivr->rpos = ivr->hdr->pos - ivr->shm_size + IVRING_READ_MARGIN; + else + ivr->rpos = IVRING_STARTPOS; + + return 0; +} + +int ivring_init_hdr(struct ivring_user *ivr) +{ + struct stat st; + int ret; + + if (fstat(ivr->shm_fd, &st) < 0) { + ret = -errno; + pr_perror("fstat"); + return ret; + } + + if (ivr->shm_size != st.st_size) { + pr_debug("Given shmem size isn't correct\n"); + ivr->shm_size = st.st_size; + } + + ivr->hdr = ivring_hdr_new(ivr->shm_fd, ivr->shm_size); + if (!ivr->hdr) + return -EINVAL; + + return ivring_init_rpos(ivr); +} + +void ivring_init(struct ivring_user *ivr) +{ + ivr->rpos = IVRING_STARTPOS; + ivr->hdr = NULL; + ivr->shm_size = 0; + ivr->shm_fd = -1; + ivr->shm_obj = NULL; +} + +void ivring_cleanup(struct ivring_user *ivr) +{ + /* Unmap Buffer */ + if (ivr->hdr) { + ivring_hdr_free(ivr->hdr, ivr->shm_size); + ivr->hdr = NULL; + } + + if (ivr->shm_fd != -1) { + close(ivr->shm_fd); + ivr->shm_fd = -1; + } +} + +int open_outfd(const char *out_path) +{ + int fd; + + fd = open(out_path, O_CREAT | O_TRUNC | O_RDWR, + S_IRUSR | S_IWUSR); + if (fd < 0) + pr_perror("open(out_fd)"); + + return fd; +} + +static int rotate_log(void) +{ + static int current_log_no; + char *new_outpath; + int length; + + if (global_outfd > 0) + close(global_outfd); + + if (log_rotate_mode()) { + /* prepare filename "log_basename.XXXX" */ + length = strlen(global_log_basename) + 10; + new_outpath = (char *)malloc(sizeof(char) * length); + if (!new_outpath) { + pr_perror("malloc()"); + exit(EXIT_FAILURE); + } + snprintf(new_outpath, length, "%s.%d", global_log_basename, + current_log_no++ % global_log_rotate_num); + } else + new_outpath = strdup(global_log_basename); + + global_outfd = open_outfd(new_outpath); + if (global_outfd < 0) + exit(EXIT_FAILURE); + + free(new_outpath); + + return global_outfd; +} + +static int ivring_read(struct ivring_user *ivr) +{ + char buf[BUFFER_SIZE]; + ssize_t size; + static ssize_t total_size; + + pr_debug("Try to read buffer.\n"); + + do { + if (global_outfd >= 0) + size = ivring_read_fd(ivr, global_outfd, BUFFER_SIZE); + else + size = ivring_memcpy(ivr, buf, BUFFER_SIZE); + if (size < 0) { + pr_err("Ring buffer read Error %d\n", (int)size); + return (int)size; + } else + total_size += size; + + printf("%s", buf); + } while (size > 0); + + if (log_rotate_mode() && total_size > global_log_rotate_size) { + global_outfd = rotate_log(); + total_size = 0; + } + + return 0; +} + +int main(int argc, char **argv) +{ + struct ivring_user *ivr; + + set_pr_mode(PR_MODE_STDIO, 1, "ivtrace_reader"); + + ivr = zalloc(sizeof(struct ivring_user)); + if (!ivr) + return -ENOMEM; + ivring_init(ivr); + + if (parse_args(argc, argv, ivr) < 0) + exit(-1); + + ivr->shm_fd = shm_open(ivr->shm_obj, O_RDWR, S_IRWXU|S_IRWXG|S_IRWXO); + if (ivr->shm_fd < 0) { + pr_err("ivtrace_reader: could not open shared file\n"); + exit(-1); + } + + if (ivr->hdr == NULL && ivr->shm_fd != -1) { + if (ivring_init_hdr(ivr) < 0) { + pr_debug("hdr init error %d\n"); + exit(-1); + } + pr_debug("ivring_init_hdr: %p\n", ivr->hdr); + } + + /* Setup signal handlers */ + signal(SIGTERM, term_handler); + signal(SIGINT, term_handler); + signal(SIGQUIT, term_handler); + + /* Main Loop */ + while (!global_term) { + int ret; + + sleep(1); + + ret = ivring_read(ivr); + + if (ret < 0) { + pr_debug("Exit with an error %d\n", ret); + goto out; + } + } +out: + ivring_cleanup(ivr); + free(ivr); + + if (global_outfd >= 0) + close(global_outfd); + if (global_log_basename) + free(global_log_basename); + + return 0; +} + +size_t parse_size(const char *arg) +{ + uint64_t value; + char *ptr; + + value = strtoul(arg, &ptr, 10); + switch (*ptr) { + case 0: case 'M': case 'm': + value <<= 20; + break; + case 'G': case 'g': + value <<= 30; + break; + default: + pr_err("invalid ram size: %s\n", arg); + exit(1); + } + return (size_t)value; +} + +int parse_args(int argc, char **argv, struct ivring_user *ivr) +{ + int c; + + ivr->shm_size = DEFAULT_SHM_SIZE; + + while ((c = getopt(argc, argv, "h:m:f:S:N:s:")) != -1) { + switch (c) { + /* size of shared memory object */ + case 'm': + ivr->shm_size = parse_size(optarg); + break; + /* output file */ + case 'f': + if (global_log_basename) + free(global_log_basename); + global_log_basename = strdup(optarg); + break; + /* log rotation */ + case 'S': + global_log_rotate_size = atoi(optarg) * 1024 * 1024; + break; + /* number of log files */ + case 'N': + global_log_rotate_num = atoi(optarg); + break; + /* name of shared memory object */ + case 's': + ivr->shm_obj = optarg; + break; + case 'h': + default: + usage(argv[0]); + exit(1); + } + } + + printf("shared object size: %ld (bytes)\n", (long)ivr->shm_size); + + if (ivr->shm_size == 0 || ivr->shm_obj == NULL) + return -1; + + if (global_log_basename) + global_outfd = rotate_log(); + + return 0; +} + +void usage(char const *prg) +{ + fprintf(stderr, "use: %s [-h] [-m <size in MB>] [-f <output>]"\ + "[-S <log size in MB> [-N <log num>] [-s <shm object>]\n", prg); +} diff --git a/tools/ivshmem/ivring_reader.h b/tools/ivshmem/ivring_reader.h new file mode 100644 index 0000000..10fbf10 --- /dev/null +++ b/tools/ivshmem/ivring_reader.h @@ -0,0 +1,15 @@ +#ifndef __IVRING_READER__ +#define __IVRING_READER__ + +struct ivring_user { + size_t shm_size; /* shmem size */ + struct ivring_hdr *hdr; /* Header */ + u64 rpos; /* Read position */ + int shm_fd; /* Shared memory fd */ + char *shm_obj; /* Shared memory object */ +}; + +extern void usage(char const *prg); +extern int parse_args(int argc, char **argv, struct ivring_user *ivr); + +#endif diff --git a/tools/ivshmem/pr_msg.c b/tools/ivshmem/pr_msg.c new file mode 100644 index 0000000..16347e8 --- /dev/null +++ b/tools/ivshmem/pr_msg.c @@ -0,0 +1,125 @@ +#define _GNU_SOURCE +#include <syslog.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> +#include <unistd.h> +#include "pr_msg.h" + +static void pr_stdout(const char *fmt, ...); +static void pr_stderr(const char *fmt, ...); +static void pr_syslog(const char *fmt, ...); +static void pr_syslog_err(const char *fmt, ...); +static void pr_file(const char *fmt, ...); +static void pr_file_err(const char *fmt, ...); +static void pr_void(const char *fmt, ...); + +void (*pr_info)(const char *fmt, ...) = pr_stdout; +void (*pr_err)(const char *fmt, ...) = pr_stderr; +void (*pr_debug)(const char *fmt, ...) = pr_void; + +static int log_fd; +char *program; + +void set_pr_mode(int mode, int debug, const char *prog) +{ + if (program) + free(program); + program = strdup(prog); + + if (mode == PR_MODE_STDIO) { + log_fd = -1; + pr_info = pr_stdout; + pr_err = pr_stderr; + } else if (mode == PR_MODE_SYSLOG) { + log_fd = -1; + openlog(program, 0, 0); + pr_info = pr_syslog; + pr_err = pr_syslog_err; + } else { + log_fd = mode; + pr_info = pr_file; + pr_err = pr_file_err; + } + if (debug) + pr_debug = pr_info; + else + pr_debug = pr_void; +} + +#define format_varg(bufp, fmt) \ + do {va_list ap; va_start(ap, fmt); vasprintf(bufp, fmt, ap); \ + va_end(ap); } while (0) + +static void pr_stdout(const char *fmt, ...) +{ + char *buf; + + format_varg(&buf, fmt); + + fprintf(stdout, "%s", buf); + + free(buf); +} + +static void pr_stderr(const char *fmt, ...) +{ + char *buf; + + format_varg(&buf, fmt); + + fprintf(stderr, "Error: %s", buf); + + free(buf); +} + +static void pr_syslog(const char *fmt, ...) +{ + char *buf; + + format_varg(&buf, fmt); + + syslog(LOG_INFO, "%s", buf); + + free(buf); +} + +static void pr_syslog_err(const char *fmt, ...) +{ + char *buf; + + format_varg(&buf, fmt); + + syslog(LOG_ERR, "Error: %s", buf); + + free(buf); +} + +static void pr_file(const char *fmt, ...) +{ + char *buf; + + format_varg(&buf, fmt); + + write(log_fd, buf, strlen(buf)); + + free(buf); +} + +static void pr_file_err(const char *fmt, ...) +{ + char *buf; + + format_varg(&buf, fmt); + + write(log_fd, "Error: ", 7); + write(log_fd, buf, strlen(buf)); + + free(buf); +} + +static void pr_void(const char *fmt, ...) +{ + /* Do nothing */ +} diff --git a/tools/ivshmem/pr_msg.h b/tools/ivshmem/pr_msg.h new file mode 100644 index 0000000..c9a6acf --- /dev/null +++ b/tools/ivshmem/pr_msg.h @@ -0,0 +1,19 @@ +#ifndef __PR_MSG__ +#define __PR_MSG__ + +#include <errno.h> +#include <string.h> + +#define PR_MODE_STDIO 0 +#define PR_MODE_SYSLOG 1 +#define PR_MODE_FD(fd) (fd) + +extern void set_pr_mode(int mode, int debug, const char *prog); + +extern void (*pr_info)(const char *fmt, ...); +extern void (*pr_err)(const char *fmt, ...); +extern void (*pr_debug)(const char *fmt, ...); + +#define pr_perror(msg) pr_err("%s: %s\n", msg, strerror(errno)) + +#endif