| // SPDX-License-Identifier: LGPL-2.1 | |
| /* | |
| * Copyright (C) 2022 Google Inc, Steven Rostedt <rostedt@goodmis.org> | |
| */ | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <dirent.h> | |
| #include <unistd.h> | |
| #include <fcntl.h> | |
| #include <limits.h> | |
| #include <errno.h> | |
| #include <sys/stat.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/select.h> | |
| #include <kbuffer.h> | |
| #include "tracefs.h" | |
| #include "tracefs-local.h" | |
| enum { | |
| TC_STOP = 1 << 0, /* Stop reading */ | |
| TC_PERM_NONBLOCK = 1 << 1, /* read is always non blocking */ | |
| TC_NONBLOCK = 1 << 2, /* read is non blocking */ | |
| }; | |
| struct tracefs_cpu { | |
| int fd; | |
| int flags; | |
| int nfds; | |
| int ctrl_pipe[2]; | |
| int splice_pipe[2]; | |
| int pipe_size; | |
| int subbuf_size; | |
| int buffered; | |
| int splice_read_flags; | |
| }; | |
| /** | |
| * tracefs_cpu_alloc_fd - create a tracefs_cpu instance for an existing fd | |
| * @fd: The file descriptor to attach the tracefs_cpu to | |
| * @subbuf_size: The expected size to read the subbuffer with | |
| * @nonblock: If true, the file will be opened in O_NONBLOCK mode | |
| * | |
| * Return a descriptor that can read the tracefs trace_pipe_raw file | |
| * that is associated with the given @fd and must be read in @subbuf_size. | |
| * | |
| * Returns NULL on error. | |
| */ | |
| struct tracefs_cpu * | |
| tracefs_cpu_alloc_fd(int fd, int subbuf_size, bool nonblock) | |
| { | |
| struct tracefs_cpu *tcpu; | |
| int mode = O_RDONLY; | |
| int ret; | |
| tcpu = calloc(1, sizeof(*tcpu)); | |
| if (!tcpu) | |
| return NULL; | |
| if (nonblock) { | |
| mode |= O_NONBLOCK; | |
| tcpu->flags |= TC_NONBLOCK | TC_PERM_NONBLOCK; | |
| } | |
| tcpu->splice_pipe[0] = -1; | |
| tcpu->splice_pipe[1] = -1; | |
| tcpu->fd = fd; | |
| tcpu->subbuf_size = subbuf_size; | |
| if (tcpu->flags & TC_PERM_NONBLOCK) { | |
| tcpu->ctrl_pipe[0] = -1; | |
| tcpu->ctrl_pipe[1] = -1; | |
| } else { | |
| /* ctrl_pipe is used to break out of blocked reads */ | |
| ret = pipe(tcpu->ctrl_pipe); | |
| if (ret < 0) | |
| goto fail; | |
| if (tcpu->ctrl_pipe[0] > tcpu->fd) | |
| tcpu->nfds = tcpu->ctrl_pipe[0] + 1; | |
| else | |
| tcpu->nfds = tcpu->fd + 1; | |
| } | |
| return tcpu; | |
| fail: | |
| free(tcpu); | |
| return NULL; | |
| } | |
| /** | |
| * tracefs_cpu_open - open an instance raw trace file | |
| * @instance: the instance (NULL for toplevel) of the cpu raw file to open | |
| * @cpu: The CPU that the raw trace file is associated with | |
| * @nonblock: If true, the file will be opened in O_NONBLOCK mode | |
| * | |
| * Return a descriptor that can read the tracefs trace_pipe_raw file | |
| * for a give @cpu in a given @instance. | |
| * | |
| * Returns NULL on error. | |
| */ | |
| struct tracefs_cpu * | |
| tracefs_cpu_open(struct tracefs_instance *instance, int cpu, bool nonblock) | |
| { | |
| struct tracefs_cpu *tcpu; | |
| struct tep_handle *tep; | |
| char path[128]; | |
| char *buf; | |
| int mode = O_RDONLY; | |
| int subbuf_size; | |
| int len; | |
| int ret; | |
| int fd; | |
| if (nonblock) | |
| mode |= O_NONBLOCK; | |
| sprintf(path, "per_cpu/cpu%d/trace_pipe_raw", cpu); | |
| fd = tracefs_instance_file_open(instance, path, mode); | |
| if (fd < 0) | |
| return NULL; | |
| tep = tep_alloc(); | |
| if (!tep) | |
| goto fail; | |
| /* Get the size of the page */ | |
| buf = tracefs_instance_file_read(NULL, "events/header_page", &len); | |
| if (!buf) | |
| goto fail; | |
| ret = tep_parse_header_page(tep, buf, len, sizeof(long)); | |
| free(buf); | |
| if (ret < 0) | |
| goto fail; | |
| subbuf_size = tep_get_sub_buffer_size(tep); | |
| tep_free(tep); | |
| tep = NULL; | |
| tcpu = tracefs_cpu_alloc_fd(fd, subbuf_size, nonblock); | |
| if (!tcpu) | |
| goto fail; | |
| return tcpu; | |
| fail: | |
| tep_free(tep); | |
| close(fd); | |
| return NULL; | |
| } | |
| static void close_fd(int fd) | |
| { | |
| if (fd < 0) | |
| return; | |
| close(fd); | |
| } | |
| /** | |
| * tracefs_cpu_free_fd - clean up the tracefs_cpu descriptor | |
| * @tcpu: The descriptor created with tracefs_cpu_alloc_fd() | |
| * | |
| * Closes all the internal file descriptors that were opened by | |
| * tracefs_cpu_alloc_fd(), and frees the descriptor. | |
| */ | |
| void tracefs_cpu_free_fd(struct tracefs_cpu *tcpu) | |
| { | |
| close_fd(tcpu->ctrl_pipe[0]); | |
| close_fd(tcpu->ctrl_pipe[1]); | |
| close_fd(tcpu->splice_pipe[0]); | |
| close_fd(tcpu->splice_pipe[1]); | |
| free(tcpu); | |
| } | |
| /** | |
| * tracefs_cpu_close - clean up and close a raw trace descriptor | |
| * @tcpu: The descriptor created with tracefs_cpu_open() | |
| * | |
| * Closes all the file descriptors associated to the trace_pipe_raw | |
| * opened by tracefs_cpu_open(). | |
| */ | |
| void tracefs_cpu_close(struct tracefs_cpu *tcpu) | |
| { | |
| if (!tcpu) | |
| return; | |
| close(tcpu->fd); | |
| tracefs_cpu_free_fd(tcpu); | |
| } | |
| /** | |
| * tracefs_cpu_read_size - Return the size of the sub buffer | |
| * @tcpu: The descriptor that holds the size of the sub buffer | |
| * | |
| * A lot of the functions that read the data from the trace_pipe_raw | |
| * expect the caller to have allocated enough space to store a full | |
| * subbuffer. Calling this function is a requirement to do so. | |
| */ | |
| int tracefs_cpu_read_size(struct tracefs_cpu *tcpu) | |
| { | |
| if (!tcpu) | |
| return -1; | |
| return tcpu->subbuf_size; | |
| } | |
| static void set_nonblock(struct tracefs_cpu *tcpu) | |
| { | |
| long flags; | |
| if (tcpu->flags & TC_NONBLOCK) | |
| return; | |
| flags = fcntl(tcpu->fd, F_GETFL); | |
| fcntl(tcpu->fd, F_SETFL, flags | O_NONBLOCK); | |
| tcpu->flags |= TC_NONBLOCK; | |
| } | |
| static void unset_nonblock(struct tracefs_cpu *tcpu) | |
| { | |
| long flags; | |
| if (!(tcpu->flags & TC_NONBLOCK)) | |
| return; | |
| flags = fcntl(tcpu->fd, F_GETFL); | |
| flags &= ~O_NONBLOCK; | |
| fcntl(tcpu->fd, F_SETFL, flags); | |
| tcpu->flags &= ~TC_NONBLOCK; | |
| } | |
| /* | |
| * If set to blocking mode, block until the watermark has been | |
| * reached, or the control has said to stop. If the contol is | |
| * set, then nonblock will be set to true on the way out. | |
| */ | |
| static int wait_on_input(struct tracefs_cpu *tcpu, bool nonblock) | |
| { | |
| fd_set rfds; | |
| int ret; | |
| if (tcpu->flags & TC_PERM_NONBLOCK) | |
| return 1; | |
| if (nonblock) { | |
| set_nonblock(tcpu); | |
| return 1; | |
| } else { | |
| unset_nonblock(tcpu); | |
| } | |
| FD_ZERO(&rfds); | |
| FD_SET(tcpu->fd, &rfds); | |
| FD_SET(tcpu->ctrl_pipe[0], &rfds); | |
| ret = select(tcpu->nfds, &rfds, NULL, NULL, NULL); | |
| /* Let the application decide what to do with signals and such */ | |
| if (ret < 0) | |
| return ret; | |
| if (FD_ISSET(tcpu->ctrl_pipe[0], &rfds)) { | |
| /* Flush the ctrl pipe */ | |
| read(tcpu->ctrl_pipe[0], &ret, 1); | |
| /* Make nonblock as it is now stopped */ | |
| set_nonblock(tcpu); | |
| /* Permanently set unblock */ | |
| tcpu->flags |= TC_PERM_NONBLOCK; | |
| } | |
| return FD_ISSET(tcpu->fd, &rfds); | |
| } | |
| /** | |
| * tracefs_cpu_read - read from the raw trace file | |
| * @tcpu: The descriptor representing the raw trace file | |
| * @buffer: Where to read into (must be at least the size of the subbuffer) | |
| * @nonblock: Hint to not block on the read if there's no data. | |
| * | |
| * Reads the trace_pipe_raw files associated to @tcpu into @buffer. | |
| * @buffer must be at least the size of the sub buffer of the ring buffer, | |
| * which is returned by tracefs_cpu_read_size(). | |
| * | |
| * If @nonblock is set, and there's no data available, it will return | |
| * immediately. Otherwise depending on how @tcpu was opened, it will | |
| * block. If @tcpu was opened with nonblock set, then this @nonblock | |
| * will make no difference. | |
| * | |
| * Returns the amount read or -1 on error. | |
| */ | |
| int tracefs_cpu_read(struct tracefs_cpu *tcpu, void *buffer, bool nonblock) | |
| { | |
| int ret; | |
| /* | |
| * If nonblock is set, then the wait_on_input() will return | |
| * immediately, if there's nothing in the buffer, with | |
| * ret == 0. | |
| */ | |
| ret = wait_on_input(tcpu, nonblock); | |
| if (ret <= 0) | |
| return ret; | |
| ret = read(tcpu->fd, buffer, tcpu->subbuf_size); | |
| /* It's OK if there's no data to read */ | |
| if (ret < 0 && errno == EAGAIN) { | |
| /* Reset errno */ | |
| errno = 0; | |
| ret = 0; | |
| } | |
| return ret; | |
| } | |
| static int init_splice(struct tracefs_cpu *tcpu) | |
| { | |
| int ret; | |
| if (tcpu->splice_pipe[0] >= 0) | |
| return 0; | |
| ret = pipe(tcpu->splice_pipe); | |
| if (ret < 0) | |
| return ret; | |
| ret = fcntl(tcpu->splice_pipe[0], F_GETPIPE_SZ, &tcpu->pipe_size); | |
| /* | |
| * F_GETPIPE_SZ was introduced in 2.6.35, ftrace was introduced | |
| * in 2.6.31. If we are running on an older kernel, just fall | |
| * back to using subbuf_size for splice(). It could also return | |
| * the size of the pipe and not set pipe_size. | |
| */ | |
| if (ret > 0 && !tcpu->pipe_size) | |
| tcpu->pipe_size = ret; | |
| else if (ret < 0) | |
| tcpu->pipe_size = tcpu->subbuf_size; | |
| tcpu->splice_read_flags = SPLICE_F_MOVE; | |
| if (tcpu->flags & TC_NONBLOCK) | |
| tcpu->splice_read_flags |= SPLICE_F_NONBLOCK; | |
| return 0; | |
| } | |
| /** | |
| * tracefs_cpu_buffered_read - Read the raw trace data buffering through a pipe | |
| * @tcpu: The descriptor representing the raw trace file | |
| * @buffer: Where to read into (must be at least the size of the subbuffer) | |
| * @nonblock: Hint to not block on the read if there's no data. | |
| * | |
| * This is basically the same as tracefs_cpu_read() except that it uses | |
| * a pipe through splice to buffer reads. This will batch reads keeping | |
| * the reading from the ring buffer less intrusive to the system, as | |
| * just reading all the time can cause quite a disturbance. | |
| * | |
| * Note, one difference between this and tracefs_cpu_read() is that it | |
| * will read only in sub buffer pages. If the ring buffer has not filled | |
| * a page, then it will not return anything, even with @nonblock set. | |
| * Calls to tracefs_cpu_flush() should be done to read the rest of | |
| * the file at the end of the trace. | |
| * | |
| * Returns the amount read or -1 on error. | |
| */ | |
| int tracefs_cpu_buffered_read(struct tracefs_cpu *tcpu, void *buffer, bool nonblock) | |
| { | |
| int mode = SPLICE_F_MOVE; | |
| int ret; | |
| if (tcpu->buffered < 0) | |
| tcpu->buffered = 0; | |
| if (tcpu->buffered) | |
| goto do_read; | |
| ret = wait_on_input(tcpu, nonblock); | |
| if (ret <= 0) | |
| return ret; | |
| if (tcpu->flags & TC_NONBLOCK) | |
| mode |= SPLICE_F_NONBLOCK; | |
| ret = init_splice(tcpu); | |
| if (ret < 0) | |
| return ret; | |
| ret = splice(tcpu->fd, NULL, tcpu->splice_pipe[1], NULL, | |
| tcpu->pipe_size, mode); | |
| if (ret <= 0) | |
| return ret; | |
| tcpu->buffered = ret; | |
| do_read: | |
| ret = read(tcpu->splice_pipe[0], buffer, tcpu->subbuf_size); | |
| if (ret > 0) | |
| tcpu->buffered -= ret; | |
| return ret; | |
| } | |
| /** | |
| * tracefs_cpu_stop - Stop a blocked read of the raw tracing file | |
| * @tcpu: The descriptor representing the raw trace file | |
| * | |
| * This will attempt to unblock a task blocked on @tcpu reading it. | |
| * On older kernels, it may not do anything for the pipe reads, as | |
| * older kernels do not wake up tasks waiting on the ring buffer. | |
| * | |
| * Returns 0 if the tasks reading the raw tracing file does not | |
| * need a nudge. | |
| * | |
| * Returns 1 if that tasks may need a nudge (send a signal). | |
| * | |
| * Returns negative on error. | |
| */ | |
| int tracefs_cpu_stop(struct tracefs_cpu *tcpu) | |
| { | |
| int ret = 1; | |
| if (tcpu->flags & TC_PERM_NONBLOCK) | |
| return 0; | |
| ret = write(tcpu->ctrl_pipe[1], &ret, 1); | |
| if (ret < 0) | |
| return ret; | |
| /* Calling ioctl() on recent kernels will wake up the waiters */ | |
| ret = ioctl(tcpu->fd, 0); | |
| if (ret < 0) | |
| ret = 1; | |
| else | |
| ret = 0; | |
| set_nonblock(tcpu); | |
| return ret; | |
| } | |
| /** | |
| * tracefs_cpu_flush - Finish out and read the rest of the raw tracing file | |
| * @tcpu: The descriptor representing the raw trace file | |
| * @buffer: Where to read into (must be at least the size of the subbuffer) | |
| * | |
| * Reads the trace_pipe_raw file associated by the @tcpu and puts it | |
| * into @buffer, which must be the size of the sub buffer which is retrieved. | |
| * by tracefs_cpu_read_size(). This should be called at the end of tracing | |
| * to get the rest of the data. | |
| * | |
| * This will set the file descriptor for reading to non-blocking mode. | |
| * | |
| * Returns the number of bytes read, or negative on error. | |
| */ | |
| int tracefs_cpu_flush(struct tracefs_cpu *tcpu, void *buffer) | |
| { | |
| int ret; | |
| /* Make sure that reading is now non blocking */ | |
| set_nonblock(tcpu); | |
| if (tcpu->buffered < 0) | |
| tcpu->buffered = 0; | |
| if (tcpu->buffered) { | |
| ret = read(tcpu->splice_pipe[0], buffer, tcpu->subbuf_size); | |
| if (ret > 0) | |
| tcpu->buffered -= ret; | |
| return ret; | |
| } | |
| ret = read(tcpu->fd, buffer, tcpu->subbuf_size); | |
| if (ret > 0 && tcpu->buffered) | |
| tcpu->buffered -= ret; | |
| /* It's OK if there's no data to read */ | |
| if (ret < 0 && errno == EAGAIN) { | |
| /* Reset errno */ | |
| errno = 0; | |
| ret = 0; | |
| } | |
| return ret; | |
| } | |
| /** | |
| * tracefs_cpu_flush_write - Finish out and read the rest of the raw tracing file | |
| * @tcpu: The descriptor representing the raw trace file | |
| * @wfd: The write file descriptor to write the data to | |
| * | |
| * Reads the trace_pipe_raw file associated by the @tcpu and writes it to | |
| * @wfd. This should be called at the end of tracing to get the rest of the data. | |
| * | |
| * Returns the number of bytes written, or negative on error. | |
| */ | |
| int tracefs_cpu_flush_write(struct tracefs_cpu *tcpu, int wfd) | |
| { | |
| char buffer[tcpu->subbuf_size]; | |
| int ret; | |
| ret = tracefs_cpu_flush(tcpu, buffer); | |
| if (ret > 0) | |
| ret = write(wfd, buffer, ret); | |
| /* It's OK if there's no data to read */ | |
| if (ret < 0 && errno == EAGAIN) | |
| ret = 0; | |
| return ret; | |
| } | |
| /** | |
| * tracefs_cpu_write - Write the raw trace file into a file descriptor | |
| * @tcpu: The descriptor representing the raw trace file | |
| * @wfd: The write file descriptor to write the data to | |
| * @nonblock: Hint to not block on the read if there's no data. | |
| * | |
| * This will pipe the data from the trace_pipe_raw file associated with @tcpu | |
| * into the @wfd file descriptor. If @nonblock is set, then it will not | |
| * block on if there's nothing to write. Note, it will only write sub buffer | |
| * size data to @wfd. Calls to tracefs_cpu_flush_write() are needed to | |
| * write out the rest. | |
| * | |
| * Returns the number of bytes read or negative on error. | |
| */ | |
| int tracefs_cpu_write(struct tracefs_cpu *tcpu, int wfd, bool nonblock) | |
| { | |
| char buffer[tcpu->subbuf_size]; | |
| int mode = SPLICE_F_MOVE; | |
| int tot_write = 0; | |
| int tot; | |
| int ret; | |
| ret = wait_on_input(tcpu, nonblock); | |
| if (ret <= 0) | |
| return ret; | |
| if (tcpu->flags & TC_NONBLOCK) | |
| mode |= SPLICE_F_NONBLOCK; | |
| ret = init_splice(tcpu); | |
| if (ret < 0) | |
| return ret; | |
| tot = splice(tcpu->fd, NULL, tcpu->splice_pipe[1], NULL, | |
| tcpu->pipe_size, mode); | |
| if (tot < 0) | |
| return tot; | |
| if (tot == 0) | |
| return 0; | |
| ret = splice(tcpu->splice_pipe[0], NULL, wfd, NULL, | |
| tot, SPLICE_F_MOVE | SPLICE_F_NONBLOCK); | |
| if (ret >= 0) | |
| return ret; | |
| /* Some file systems do not allow splicing, try writing instead */ | |
| do { | |
| int r = tcpu->subbuf_size; | |
| if (r > tot) | |
| r = tot; | |
| ret = read(tcpu->splice_pipe[0], buffer, r); | |
| if (ret > 0) { | |
| tot -= ret; | |
| ret = write(wfd, buffer, ret); | |
| } | |
| if (ret > 0) | |
| tot_write += ret; | |
| } while (ret > 0); | |
| if (ret < 0) | |
| return ret; | |
| return tot_write; | |
| } | |
| /** | |
| * tracefs_cpu_pipe - Write the raw trace file into a pipe descriptor | |
| * @tcpu: The descriptor representing the raw trace file | |
| * @wfd: The write file descriptor to write the data to (must be a pipe) | |
| * @nonblock: Hint to not block on the read if there's no data. | |
| * | |
| * This will splice directly the file descriptor of the trace_pipe_raw | |
| * file to the given @wfd, which must be a pipe. This can also be used | |
| * if @tcpu was created with tracefs_cpu_create_fd() where the passed | |
| * in @fd there was a pipe, then @wfd does not need to be a pipe. | |
| * | |
| * Returns the number of bytes read or negative on error. | |
| */ | |
| int tracefs_cpu_pipe(struct tracefs_cpu *tcpu, int wfd, bool nonblock) | |
| { | |
| int mode = SPLICE_F_MOVE; | |
| int ret; | |
| ret = wait_on_input(tcpu, nonblock); | |
| if (ret <= 0) | |
| return ret; | |
| if (tcpu->flags & TC_NONBLOCK) | |
| mode |= SPLICE_F_NONBLOCK; | |
| ret = splice(tcpu->fd, NULL, wfd, NULL, | |
| tcpu->pipe_size, mode); | |
| return ret; | |
| } |