#!/bin/sh # # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # # Copyright (c) 2021 Jean-Sébastien Pédron # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # "Written file doesn't match memory buffer" seen on main-n244961-e6bb49f12ca # https://reviews.freebsd.org/D28811 . ../default.cfg kldstat -v | grep -q zfs.ko || { kldload zfs.ko || exit 0; loaded=1; } cat > /tmp/write_vs_sendfile.c < #include #include #include #include #include #include #include #include #include #include #include #include #include #define FILENAME "myfile.bin" #define MAX_SIZE 50 * 1000 * 1000 int tcp_port; int chunk_size; static void * sender_start(void *buffer __unused) { int fd, sock, ret; struct sockaddr_in sa = { 0 }; off_t cursor; printf("Sender: opening connection to TCP port %d\n", tcp_port); sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("Sender: failed to create socket"); return (NULL); } sa.sin_family = AF_INET; sa.sin_port = htons(tcp_port); sa.sin_addr.s_addr = htonl((((((127 << 8) | 0) << 8) | 0) << 8) | 1); ret = connect(sock, (struct sockaddr *)&sa, sizeof(sa)); if (ret < 0) { perror("Sender: failed to connect to localhost"); close(sock); return (NULL); } printf("Sender: opening %s\n", FILENAME); fd = open(FILENAME, O_CREAT|O_RDONLY, 0644); if (fd < 0) { perror("Sender: failed to open file"); close(sock); return (NULL); } printf("Sender: starting sendfile(2) loop\n"); cursor = 0; do { size_t to_send = chunk_size; off_t sbytes = 0; do { #if defined(__FreeBSD__) ret = sendfile(fd, sock, cursor, to_send, NULL, &sbytes, 0); if (ret == 0) { cursor += sbytes; to_send -= sbytes; } #elif defined(__APPLE__) sbytes = to_send; ret = sendfile(fd, sock, cursor, &sbytes, NULL, 0); if (ret < 0 && (errno == EAGAIN || errno == EINTR)) { ret = 0; } if (ret == 0) { ret = 0; cursor += sbytes; to_send -= sbytes; } #else #error Not implemented #endif } while (ret == 0 && to_send > 0); } while (cursor < MAX_SIZE); printf("Sender: closing socket\n"); close(fd); printf("Sender: closing %s\n", FILENAME); close(sock); return (NULL); } static void * writer_start(void *buffer) { int fd, cursor, ret; printf("Writer: opening %s\n", FILENAME); fd = open(FILENAME, O_CREAT|O_RDWR|O_TRUNC|O_DIRECT, 0644); if (fd < 0) { perror("Writer: failed to open file"); return (NULL); } /* We sleep one second to give a head start to the sendfile(2) thread * above. */ sleep(1); printf( "Writer: writing chunks of %u bytes to a max of %u bytes\n", chunk_size, MAX_SIZE); cursor = 0; do { ret = write(fd, buffer, chunk_size); if (ret < 0) { perror("Writer: failed to write file"); break; } assert(ret == chunk_size); cursor += ret; } while (cursor < MAX_SIZE); printf("Writer: closing %s\n", FILENAME); close(fd); return (NULL); } int check_file(void *buffer, int flags) { int fd, ret, cursor; void *read_buffer; printf("Writer: opening %s\n", FILENAME); fd = open(FILENAME, O_RDONLY | flags | O_DIRECT); if (fd < 0) { perror("Checker: failed to open file"); return (1); } read_buffer = malloc(chunk_size); if (buffer == NULL) { perror("Checker: failed to allocate buffer"); close(fd); return (1); } cursor = 0; do { ret = read(fd, read_buffer, chunk_size); if (ret < 0) { perror("Checker: failed to read file"); close(fd); free(read_buffer); return (1); } assert(ret == chunk_size); cursor += ret; ret = memcmp(buffer, read_buffer, chunk_size); } while (ret == 0 && cursor < MAX_SIZE); return (ret); } int main(int argc, char *argv[]) { int ret; void *buffer; pthread_t sender; pthread_t writer; /* The sender thread will connect to the TCP port on the local host. * The user is responsible for starting an instance of netcat like * this: * * nc -k -l 8080 */ tcp_port = argc >= 2 ? atoi(argv[1]) : 8080; chunk_size = argc >= 3 ? atoi(argv[2]) : 32128; /* We initialize a buffer and fill it with 0xff bytes. The buffer is * written many times to a file by the writer thread (see below). */ buffer = malloc(chunk_size); if (buffer == NULL) { perror("Main: failed to allocate buffer"); return (1); } memset(buffer, 255, chunk_size); unlink(FILENAME); /* The sender thread is responsible for sending the file written by the * writer thread to a local TCP port. The goal is always try to send * data which is not written to the file yet. */ ret = pthread_create(&sender, NULL, &sender_start, buffer); if (ret != 0) { free(buffer); return (2); } /* The writer thread is responsible for writing the allocated buffer to * the file until it reaches a size of 50 MB. */ ret = pthread_create(&writer, NULL, &writer_start, buffer); if (ret != 0) { pthread_cancel(sender); pthread_join(sender, NULL); free(buffer); return (2); } pthread_join(writer, NULL); pthread_cancel(sender); pthread_join(sender, NULL); /* Now that both threads terminated, we check the content of the * written file. The bug on ZFS on FreeBSD is that some portions of the * file contains zeros instead of the expected 0xff bytes. */ ret = check_file(buffer, 0); free(buffer); if (ret != 0) { fprintf(stderr, "\033[1;31mWritten file doesn't match memory buffer\033[0m\n"); } return (ret); } EOF mycc -o /tmp/write_vs_sendfile -Wall -Wextra -O2 /tmp/write_vs_sendfile.c -lpthread || exit 1 rm /tmp/write_vs_sendfile.c u1=$mdstart u2=$((u1 + 1)) mdconfig -l | grep -q md$u1 && mdconfig -d -u $u1 mdconfig -l | grep -q md$u2 && mdconfig -d -u $u2 mdconfig -s 2g -u $u1 mdconfig -s 2g -u $u2 zpool list | egrep -q "^stress2_tank" && zpool destroy stress2_tank [ -d /stress2_tank ] && rm -rf /stress2_tank zpool create stress2_tank raidz md$u1 md$u2 zfs create stress2_tank/test here=`pwd` cd /stress2_tank/test nc -k -l 8080 >/dev/null & sleep .5 /tmp/write_vs_sendfile; s=$? kill $! wait cd $here zfs umount stress2_tank/test zfs destroy -r stress2_tank zpool destroy stress2_tank mdconfig -d -u $u1 mdconfig -d -u $u2 [ -n "$loaded" ] && kldunload zfs.ko rm /tmp/write_vs_sendfile exit $s