| /* |
| * Copyright (C) 2013 Linux Test Project |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of version 2 of the GNU General Public |
| * License as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it would be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| * |
| * Further, this software is distributed without any warranty that it |
| * is free of the rightful claim of any third person regarding |
| * infringement or the like. Any license provided herein, whether |
| * implied or otherwise, applies only to this software file. Patent |
| * licenses, if any, provided herein do not apply to combinations of |
| * this program with other software, or any other product whatsoever. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| * 02110-1301, USA. |
| */ |
| /* |
| * Test /dev/kmsg based on kernel doc: Documentation/ABI/testing/dev-kmsg |
| * - read() blocks |
| * - non-blocking read() fails with EAGAIN |
| * - partial read fails (buffer smaller than message) |
| * - can write to /dev/kmsg and message seqno grows |
| * - first read() after open() returns same message |
| * - if messages get overwritten, read() returns -EPIPE |
| * - device supports SEEK_SET, SEEK_END, SEEK_DATA |
| */ |
| #define _GNU_SOURCE |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/syscall.h> |
| #include <sys/wait.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include "config.h" |
| #include "test.h" |
| #include "safe_macros.h" |
| #include "linux_syscall_numbers.h" |
| |
| #define MSG_PREFIX "LTP kmsg01 " |
| #define MAX_MSGSIZE 4096 |
| #define NUM_READ_MSGS 3 |
| #define NUM_READ_RETRY 10 |
| #define NUM_OVERWRITE_MSGS 1024 |
| #define READ_TIMEOUT 5 |
| |
| char *TCID = "kmsg01"; |
| static void setup(void); |
| static void cleanup(void); |
| |
| /* |
| * inject_msg - write message to /dev/kmsg |
| * @msg: null-terminated message to inject, should end with \n |
| * |
| * RETURNS: |
| * 0 on success |
| * -1 on failure, errno reflects write() errno |
| */ |
| static int inject_msg(const char *msg) |
| { |
| int f; |
| f = open("/dev/kmsg", O_WRONLY); |
| if (f < 0) |
| tst_brkm(TBROK | TERRNO, cleanup, "failed to open /dev/kmsg"); |
| TEST(write(f, msg, strlen(msg))); |
| SAFE_CLOSE(cleanup, f); |
| errno = TEST_ERRNO; |
| return TEST_RETURN; |
| } |
| |
| /* |
| * find_msg - find message in kernel ring buffer |
| * @fd: fd to use, if < 0 function opens /dev/kmsg itself |
| * @text_to_find: substring to look for in messages |
| * @buf: buf to store found message |
| * @bufsize: size of 'buf' |
| * @first: 1 - return first matching message |
| * 0 - return last matching message |
| * RETURNS: |
| * 0 on success |
| * -1 on failure |
| */ |
| static int find_msg(int fd, const char *text_to_find, char *buf, int bufsize, |
| int first) |
| { |
| int f, msg_found = 0; |
| char msg[MAX_MSGSIZE + 1]; |
| |
| if (fd < 0) { |
| f = open("/dev/kmsg", O_RDONLY | O_NONBLOCK); |
| if (f < 0) |
| tst_brkm(TBROK, cleanup, "failed to open /dev/kmsg"); |
| } else { |
| f = fd; |
| } |
| |
| while (1) { |
| TEST(read(f, msg, sizeof(msg))); |
| if (TEST_RETURN < 0) { |
| if (TEST_ERRNO == EAGAIN) |
| /* there are no more messages */ |
| break; |
| else if (TEST_ERRNO == EPIPE) |
| /* current message was overwritten */ |
| continue; |
| else |
| tst_brkm(TBROK|TTERRNO, cleanup, |
| "failed to read /dev/kmsg"); |
| } else if (TEST_RETURN < bufsize) { |
| /* lines from kmsg are not NULL terminated */ |
| msg[TEST_RETURN] = '\0'; |
| if (strstr(msg, text_to_find) != NULL) { |
| strncpy(buf, msg, bufsize); |
| msg_found = 1; |
| if (first) |
| break; |
| } |
| } |
| } |
| if (fd < 0) |
| SAFE_CLOSE(cleanup, f); |
| |
| if (msg_found) |
| return 0; |
| else |
| return -1; |
| } |
| |
| static int get_msg_fields(const char *msg, unsigned long *prio, |
| unsigned long *seqno) |
| { |
| unsigned long s, p; |
| if (sscanf(msg, "%lu,%lu,", &p, &s) == 2) { |
| if (prio) |
| *prio = p; |
| if (seqno) |
| *seqno = s; |
| return 0; |
| } else { |
| return 1; |
| } |
| } |
| |
| /* |
| * timed_read - if possible reads from fd or times out |
| * @fd: fd to read from |
| * @timeout_sec: timeout in seconds |
| * |
| * RETURNS: |
| * read bytes on successful read |
| * -1 on read() error, errno reflects read() errno |
| * -2 on timeout |
| */ |
| static int timed_read(int fd, int timeout_sec) |
| { |
| int ret, tmp; |
| struct timeval timeout; |
| fd_set read_fds; |
| |
| FD_ZERO(&read_fds); |
| FD_SET(fd, &read_fds); |
| timeout.tv_sec = timeout_sec; |
| timeout.tv_usec = 0; |
| |
| ret = select(fd + 1, &read_fds, 0, 0, &timeout); |
| switch (ret) { |
| case -1: |
| tst_brkm(TBROK|TERRNO, cleanup, "select failed"); |
| case 0: |
| /* select timed out */ |
| return -2; |
| } |
| |
| return read(fd, &tmp, 1); |
| } |
| |
| /* |
| * timed_read_kmsg - reads file until it reaches end of file, |
| * read fails or times out. This ignores any |
| * EPIPE errors. |
| * @fd: fd to read from |
| * @timeout_sec: timeout in seconds for every read attempt |
| * |
| * RETURNS: |
| * 0 on read reaching eof |
| * -1 on read error, errno reflects read() errno |
| * -2 on timeout |
| */ |
| static int timed_read_kmsg(int fd, int timeout_sec) |
| { |
| int child, status, ret = 0; |
| int pipefd[2]; |
| char msg[MAX_MSGSIZE]; |
| |
| if (pipe(pipefd) != 0) |
| tst_brkm(TBROK|TERRNO, cleanup, "pipe failed"); |
| |
| child = fork(); |
| switch (child) { |
| case -1: |
| tst_brkm(TBROK|TERRNO, cleanup, "failed to fork"); |
| case 0: |
| /* child does all the reading and keeps writing to |
| * pipe to let parent know that it didn't block */ |
| close(pipefd[0]); |
| while (1) { |
| write(pipefd[1], "", 1); |
| TEST(read(fd, msg, MAX_MSGSIZE)); |
| if (TEST_RETURN == 0) |
| break; |
| if (TEST_RETURN == -1 && TEST_ERRNO != EPIPE) { |
| ret = TEST_ERRNO; |
| break; |
| } |
| } |
| |
| close(pipefd[1]); |
| exit(ret); |
| } |
| SAFE_CLOSE(cleanup, pipefd[1]); |
| |
| /* parent reads pipe until it reaches eof or until read times out */ |
| do { |
| TEST(timed_read(pipefd[0], timeout_sec)); |
| } while (TEST_RETURN > 0); |
| SAFE_CLOSE(cleanup, pipefd[0]); |
| |
| /* child is blocked, kill it */ |
| if (TEST_RETURN == -2) |
| kill(child, SIGTERM); |
| if (waitpid(child, &status, 0) == -1) |
| tst_brkm(TBROK | TERRNO, cleanup, "waitpid"); |
| if (WIFEXITED(status)) { |
| if (WEXITSTATUS(status) == 0) { |
| return 0; |
| } else { |
| errno = WEXITSTATUS(status); |
| return -1; |
| } |
| } |
| return -2; |
| } |
| |
| static void test_read_nonblock(void) |
| { |
| int fd; |
| |
| tst_resm(TINFO, "TEST: nonblock read"); |
| fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK); |
| if (fd < 0) |
| tst_brkm(TBROK|TERRNO, cleanup, "failed to open /dev/kmsg"); |
| |
| TEST(timed_read_kmsg(fd, READ_TIMEOUT)); |
| if (TEST_RETURN == -1 && TEST_ERRNO == EAGAIN) |
| tst_resm(TPASS, "non-block read returned EAGAIN"); |
| else |
| tst_resm(TFAIL|TTERRNO, "non-block read returned: %ld", |
| TEST_RETURN); |
| SAFE_CLOSE(cleanup, fd); |
| } |
| |
| static void test_read_block(void) |
| { |
| int fd; |
| |
| tst_resm(TINFO, "TEST: blocking read"); |
| fd = open("/dev/kmsg", O_RDONLY); |
| if (fd < 0) |
| tst_brkm(TBROK|TERRNO, cleanup, "failed to open /dev/kmsg"); |
| |
| TEST(timed_read_kmsg(fd, READ_TIMEOUT)); |
| if (TEST_RETURN == -2) |
| tst_resm(TPASS, "read blocked"); |
| else |
| tst_resm(TFAIL|TTERRNO, "read returned: %ld", TEST_RETURN); |
| SAFE_CLOSE(cleanup, fd); |
| } |
| |
| static void test_partial_read(void) |
| { |
| char msg[MAX_MSGSIZE]; |
| int fd; |
| |
| tst_resm(TINFO, "TEST: partial read"); |
| fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK); |
| if (fd < 0) |
| tst_brkm(TBROK|TERRNO, cleanup, "failed to open /dev/kmsg"); |
| |
| TEST(read(fd, msg, 1)); |
| if (TEST_RETURN < 0) |
| tst_resm(TPASS|TTERRNO, "read failed as expected"); |
| else |
| tst_resm(TFAIL, "read returned: %ld", TEST_RETURN); |
| SAFE_CLOSE(cleanup, fd); |
| } |
| |
| static void test_inject(void) |
| { |
| char imsg[MAX_MSGSIZE], imsg_prefixed[MAX_MSGSIZE]; |
| char tmp[MAX_MSGSIZE]; |
| unsigned long prefix, seqno, seqno_last = 0; |
| int i, facility, prio; |
| |
| tst_resm(TINFO, "TEST: injected messages appear in /dev/kmsg"); |
| |
| /* test all combinations of prio 0-7, facility 0-15 */ |
| for (i = 0; i < 127; i++) { |
| prio = (i & 7); |
| facility = (i >> 3); |
| sprintf(imsg, MSG_PREFIX"TEST MESSAGE %ld prio: %d, " |
| "facility: %d\n", random(), prio, facility); |
| sprintf(imsg_prefixed, "<%d>%s", i, imsg); |
| |
| if (inject_msg(imsg_prefixed) == -1) { |
| tst_resm(TFAIL|TERRNO, "inject failed"); |
| return; |
| } |
| |
| /* check that message appears in log */ |
| if (find_msg(-1, imsg, tmp, sizeof(tmp), 0) == -1) { |
| tst_resm(TFAIL, "failed to find: %s", imsg); |
| return; |
| } |
| |
| /* check that facility is not 0 (LOG_KERN). */ |
| if (get_msg_fields(tmp, &prefix, &seqno) != 0) { |
| tst_resm(TFAIL, "failed to parse seqid: %s", tmp); |
| return; |
| } |
| if (prefix >> 3 == 0) { |
| tst_resm(TFAIL, "facility 0 found: %s", tmp); |
| return; |
| } |
| |
| /* check that seq. number grows */ |
| if (seqno > seqno_last) { |
| seqno_last = seqno; |
| } else { |
| tst_resm(TFAIL, "seqno doesn't grow: %lu, " |
| "last: %lu", seqno, seqno_last); |
| return; |
| } |
| } |
| |
| tst_resm(TPASS, "injected messages found in log"); |
| tst_resm(TPASS, "sequence numbers grow as expected"); |
| } |
| |
| static void test_read_returns_first_message(void) |
| { |
| unsigned long seqno[NUM_READ_MSGS + 1]; |
| char msg[MAX_MSGSIZE]; |
| int msgs_match = 1; |
| int i, fd, j = NUM_READ_RETRY; |
| |
| /* Open extra fd, which we try to read after reading NUM_READ_MSGS. |
| * If this read fails with EPIPE, first message was overwritten and |
| * we should retry the whole test. If it still fails after |
| * NUM_READ_RETRY attempts, report TWARN */ |
| tst_resm(TINFO, "TEST: mult. readers will get same first message"); |
| while (j) { |
| fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK); |
| if (fd < 0) |
| tst_brkm(TBROK|TERRNO, cleanup, |
| "failed to open /dev/kmsg"); |
| |
| for (i = 0; i < NUM_READ_MSGS; i++) { |
| if (find_msg(-1, "", msg, sizeof(msg), 1) != 0) |
| tst_resm(TFAIL, "failed to find any message"); |
| if (get_msg_fields(msg, NULL, &seqno[i]) != 0) |
| tst_resm(TFAIL, "failed to parse seqid: %s", |
| msg); |
| } |
| |
| TEST(read(fd, msg, sizeof(msg))); |
| SAFE_CLOSE(cleanup, fd); |
| if (TEST_RETURN != -1) |
| break; |
| |
| if (TEST_ERRNO == EPIPE) |
| tst_resm(TINFO, "msg overwritten, retrying"); |
| else |
| tst_resm(TFAIL|TTERRNO, "read failed"); |
| |
| /* give a second to whoever overwrote first message to finish */ |
| sleep(1); |
| j--; |
| } |
| |
| if (!j) { |
| tst_resm(TWARN, "exceeded: %d attempts", NUM_READ_RETRY); |
| return; |
| } |
| |
| for (i = 0; i < NUM_READ_MSGS - 1; i++) |
| if (seqno[i] != seqno[i + 1]) |
| msgs_match = 0; |
| if (msgs_match) { |
| tst_resm(TPASS, "all readers got same message on first read"); |
| } else { |
| tst_resm(TFAIL, "readers got different messages"); |
| for (i = 0; i < NUM_READ_MSGS; i++) |
| tst_resm(TINFO, "msg%d: %lu\n", i, seqno[i]); |
| } |
| } |
| |
| static void test_messages_overwritten(void) |
| { |
| int i, fd; |
| char msg[MAX_MSGSIZE]; |
| unsigned long first_seqno, seqno; |
| char filler_str[] = MSG_PREFIX"FILLER MESSAGE TO OVERWRITE OTHERS\n"; |
| |
| /* Keep injecting messages until we overwrite first one. |
| * We know first message is overwritten when its seqno changes */ |
| tst_resm(TINFO, "TEST: read returns EPIPE when messages get " |
| "overwritten"); |
| fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK); |
| if (fd < 0) |
| tst_brkm(TBROK|TERRNO, cleanup, "failed to open /dev/kmsg"); |
| |
| if (find_msg(-1, "", msg, sizeof(msg), 1) == 0 |
| && get_msg_fields(msg, NULL, &first_seqno) == 0) { |
| tst_resm(TINFO, "first seqno: %lu", first_seqno); |
| } else { |
| tst_brkm(TBROK, cleanup, "failed to get first seq. number"); |
| } |
| |
| while (1) { |
| if (find_msg(-1, "", msg, sizeof(msg), 1) != 0 |
| || get_msg_fields(msg, NULL, &seqno) != 0) { |
| tst_resm(TFAIL, "failed to get first seq. number"); |
| break; |
| } |
| if (first_seqno != seqno) { |
| /* first message was overwritten */ |
| tst_resm(TINFO, "first seqno now: %lu", seqno); |
| break; |
| } |
| for (i = 0; i < NUM_OVERWRITE_MSGS; i++) { |
| if (inject_msg(filler_str) == -1) |
| tst_brkm(TBROK|TERRNO, cleanup, |
| "failed write to /dev/kmsg"); |
| } |
| } |
| |
| /* first message is overwritten, so this next read should fail */ |
| TEST(read(fd, msg, sizeof(msg))); |
| if (TEST_RETURN == -1 && TEST_ERRNO == EPIPE) |
| tst_resm(TPASS, "read failed with EPIPE as expected"); |
| else |
| tst_resm(TFAIL|TTERRNO, "read returned: %ld", TEST_RETURN); |
| |
| /* seek position is updated to the next available record */ |
| tst_resm(TINFO, "TEST: Subsequent reads() will return available " |
| "records again"); |
| if (find_msg(fd, "", msg, sizeof(msg), 1) != 0) |
| tst_resm(TFAIL|TTERRNO, "read returned: %ld", TEST_RETURN); |
| else |
| tst_resm(TPASS, "after EPIPE read returned next record"); |
| |
| SAFE_CLOSE(cleanup, fd); |
| } |
| |
| static void test_seek(void) |
| { |
| int k, j = NUM_READ_RETRY, fd; |
| char msg[MAX_MSGSIZE]; |
| unsigned long seqno[2]; |
| |
| /* 1. read() after SEEK_SET 0 returns same (first) message */ |
| tst_resm(TINFO, "TEST: seek SEEK_SET 0"); |
| |
| fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK); |
| if (fd < 0) |
| tst_brkm(TBROK|TERRNO, cleanup, "failed to open /dev/kmsg"); |
| |
| while (j) { |
| for (k = 0; k < 2; k++) { |
| TEST(read(fd, msg, sizeof(msg))); |
| if (TEST_RETURN == -1) { |
| if (errno == EPIPE) |
| break; |
| else |
| tst_brkm(TBROK|TTERRNO, cleanup, |
| "failed to read /dev/kmsg"); |
| } |
| if (get_msg_fields(msg, NULL, &seqno[k]) != 0) |
| tst_resm(TFAIL, "failed to parse seqid: %s", |
| msg); |
| if (k == 0) |
| if (lseek(fd, 0, SEEK_SET) == -1) |
| tst_resm(TFAIL|TERRNO, |
| "SEEK_SET 0 failed"); |
| } |
| |
| if (TEST_RETURN != -1) |
| break; |
| |
| /* give a second to whoever overwrote first message to finish */ |
| sleep(1); |
| j--; |
| |
| /* read above has returned EPIPE, reopen fd and try again */ |
| SAFE_CLOSE(cleanup, fd); |
| fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK); |
| if (fd < 0) |
| tst_brkm(TBROK|TERRNO, cleanup, |
| "failed to open /dev/kmsg"); |
| } |
| |
| if (!j) { |
| tst_resm(TWARN, "exceeded: %d attempts", NUM_READ_RETRY); |
| } else { |
| if (seqno[0] != seqno[1]) |
| tst_resm(TFAIL, "SEEK_SET 0"); |
| else |
| tst_resm(TPASS, "SEEK_SET 0"); |
| } |
| |
| /* 2. messages after SEEK_END 0 shouldn't contain MSG_PREFIX */ |
| tst_resm(TINFO, "TEST: seek SEEK_END 0"); |
| if (lseek(fd, 0, SEEK_END) == -1) |
| tst_resm(TFAIL|TERRNO, "lseek SEEK_END 0 failed"); |
| if (find_msg(fd, MSG_PREFIX, msg, sizeof(msg), 0) != 0) |
| tst_resm(TPASS, "SEEK_END 0"); |
| else |
| tst_resm(TFAIL, "SEEK_END 0 found: %s", msg); |
| |
| #ifdef SEEK_DATA |
| /* 3. messages after SEEK_DATA 0 shouldn't contain MSG_PREFIX */ |
| tst_resm(TINFO, "TEST: seek SEEK_DATA 0"); |
| |
| /* clear ring buffer */ |
| if (ltp_syscall(__NR_syslog, 5, NULL, 0) == -1) |
| tst_brkm(TBROK|TERRNO, cleanup, "syslog clear failed"); |
| if (lseek(fd, 0, SEEK_DATA) == -1) |
| tst_resm(TFAIL|TERRNO, "lseek SEEK_DATA 0 failed"); |
| if (find_msg(fd, MSG_PREFIX, msg, sizeof(msg), 0) != 0) |
| tst_resm(TPASS, "SEEK_DATA 0"); |
| else |
| tst_resm(TFAIL, "SEEK_DATA 0 found: %s", msg); |
| #endif |
| SAFE_CLOSE(cleanup, fd); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int lc; |
| const char *msg; |
| |
| msg = parse_opts(argc, argv, NULL, NULL); |
| if (msg != NULL) |
| tst_brkm(TBROK, tst_exit, "OPTION PARSING ERROR - %s", msg); |
| |
| setup(); |
| for (lc = 0; TEST_LOOPING(lc); lc++) { |
| /* run test_inject first so log isn't empty for other tests */ |
| test_inject(); |
| test_read_nonblock(); |
| test_read_block(); |
| test_partial_read(); |
| test_read_returns_first_message(); |
| test_messages_overwritten(); |
| test_seek(); |
| } |
| cleanup(); |
| tst_exit(); |
| } |
| |
| static void setup(void) |
| { |
| tst_require_root(NULL); |
| if (tst_kvercmp(3, 5, 0) < 0) |
| tst_brkm(TCONF, NULL, "This test requires kernel" |
| " >= 3.5.0"); |
| srand(getpid()); |
| TEST_PAUSE; |
| } |
| |
| static void cleanup(void) |
| { |
| } |