1 /* 2 * QEMU I/O channel test helpers 3 * 4 * Copyright (c) 2015 Red Hat, Inc. 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, see <http://www.gnu.org/licenses/>. 18 * 19 */ 20 21 #include "qemu/osdep.h" 22 #include "io-channel-helpers.h" 23 #include "qapi/error.h" 24 25 struct QIOChannelTest { 26 QIOChannel *src; 27 QIOChannel *dst; 28 bool blocking; 29 size_t len; 30 size_t niov; 31 char *input; 32 struct iovec *inputv; 33 char *output; 34 struct iovec *outputv; 35 Error *writeerr; 36 Error *readerr; 37 }; 38 39 40 static void test_skip_iovec(struct iovec **iov, 41 size_t *niov, 42 size_t skip, 43 struct iovec *old) 44 { 45 size_t offset = 0; 46 size_t i; 47 48 for (i = 0; i < *niov; i++) { 49 if (skip < (*iov)[i].iov_len) { 50 old->iov_len = (*iov)[i].iov_len; 51 old->iov_base = (*iov)[i].iov_base; 52 53 (*iov)[i].iov_len -= skip; 54 (*iov)[i].iov_base += skip; 55 break; 56 } else { 57 skip -= (*iov)[i].iov_len; 58 59 if (i == 0 && old->iov_base) { 60 (*iov)[i].iov_len = old->iov_len; 61 (*iov)[i].iov_base = old->iov_base; 62 old->iov_len = 0; 63 old->iov_base = NULL; 64 } 65 66 offset++; 67 } 68 } 69 70 *iov = *iov + offset; 71 *niov -= offset; 72 } 73 74 75 /* This thread sends all data using iovecs */ 76 static gpointer test_io_thread_writer(gpointer opaque) 77 { 78 QIOChannelTest *data = opaque; 79 struct iovec *iov = data->inputv; 80 size_t niov = data->niov; 81 struct iovec old = { 0 }; 82 83 qio_channel_set_blocking(data->src, data->blocking, NULL); 84 85 while (niov) { 86 ssize_t ret; 87 ret = qio_channel_writev(data->src, 88 iov, 89 niov, 90 &data->writeerr); 91 if (ret == QIO_CHANNEL_ERR_BLOCK) { 92 if (data->blocking) { 93 error_setg(&data->writeerr, 94 "Unexpected I/O blocking"); 95 break; 96 } else { 97 qio_channel_wait(data->src, 98 G_IO_OUT); 99 continue; 100 } 101 } else if (ret < 0) { 102 break; 103 } else if (ret == 0) { 104 error_setg(&data->writeerr, 105 "Unexpected zero length write"); 106 break; 107 } 108 109 test_skip_iovec(&iov, &niov, ret, &old); 110 } 111 112 return NULL; 113 } 114 115 116 /* This thread receives all data using iovecs */ 117 static gpointer test_io_thread_reader(gpointer opaque) 118 { 119 QIOChannelTest *data = opaque; 120 struct iovec *iov = data->outputv; 121 size_t niov = data->niov; 122 struct iovec old = { 0 }; 123 124 qio_channel_set_blocking(data->dst, data->blocking, NULL); 125 126 while (niov) { 127 ssize_t ret; 128 129 ret = qio_channel_readv(data->dst, 130 iov, 131 niov, 132 &data->readerr); 133 134 if (ret == QIO_CHANNEL_ERR_BLOCK) { 135 if (data->blocking) { 136 error_setg(&data->readerr, 137 "Unexpected I/O blocking"); 138 break; 139 } else { 140 qio_channel_wait(data->dst, 141 G_IO_IN); 142 continue; 143 } 144 } else if (ret < 0) { 145 break; 146 } else if (ret == 0) { 147 break; 148 } 149 150 test_skip_iovec(&iov, &niov, ret, &old); 151 } 152 153 return NULL; 154 } 155 156 157 QIOChannelTest *qio_channel_test_new(void) 158 { 159 QIOChannelTest *data = g_new0(QIOChannelTest, 1); 160 size_t i; 161 size_t offset; 162 163 164 /* We'll send 1 MB of data */ 165 #define CHUNK_COUNT 250 166 #define CHUNK_LEN 4194 167 168 data->len = CHUNK_COUNT * CHUNK_LEN; 169 data->input = g_new0(char, data->len); 170 data->output = g_new0(gchar, data->len); 171 172 /* Fill input with a pattern */ 173 for (i = 0; i < data->len; i += CHUNK_LEN) { 174 memset(data->input + i, (i / CHUNK_LEN), CHUNK_LEN); 175 } 176 177 /* We'll split the data across a bunch of IO vecs */ 178 data->niov = CHUNK_COUNT; 179 data->inputv = g_new0(struct iovec, data->niov); 180 data->outputv = g_new0(struct iovec, data->niov); 181 182 for (i = 0, offset = 0; i < data->niov; i++, offset += CHUNK_LEN) { 183 data->inputv[i].iov_base = data->input + offset; 184 data->outputv[i].iov_base = data->output + offset; 185 data->inputv[i].iov_len = CHUNK_LEN; 186 data->outputv[i].iov_len = CHUNK_LEN; 187 } 188 189 return data; 190 } 191 192 void qio_channel_test_run_threads(QIOChannelTest *test, 193 bool blocking, 194 QIOChannel *src, 195 QIOChannel *dst) 196 { 197 GThread *reader, *writer; 198 199 test->src = src; 200 test->dst = dst; 201 test->blocking = blocking; 202 203 reader = g_thread_new("reader", 204 test_io_thread_reader, 205 test); 206 writer = g_thread_new("writer", 207 test_io_thread_writer, 208 test); 209 210 g_thread_join(reader); 211 g_thread_join(writer); 212 213 test->dst = test->src = NULL; 214 } 215 216 217 void qio_channel_test_run_writer(QIOChannelTest *test, 218 QIOChannel *src) 219 { 220 test->src = src; 221 test_io_thread_writer(test); 222 test->src = NULL; 223 } 224 225 226 void qio_channel_test_run_reader(QIOChannelTest *test, 227 QIOChannel *dst) 228 { 229 test->dst = dst; 230 test_io_thread_reader(test); 231 test->dst = NULL; 232 } 233 234 235 void qio_channel_test_validate(QIOChannelTest *test) 236 { 237 g_assert(test->readerr == NULL); 238 g_assert(test->writeerr == NULL); 239 g_assert_cmpint(memcmp(test->input, 240 test->output, 241 test->len), ==, 0); 242 243 g_free(test->inputv); 244 g_free(test->outputv); 245 g_free(test->input); 246 g_free(test->output); 247 g_free(test); 248 } 249