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 "io-channel-helpers.h" 22 23 struct QIOChannelTest { 24 QIOChannel *src; 25 QIOChannel *dst; 26 bool blocking; 27 size_t len; 28 size_t niov; 29 char *input; 30 struct iovec *inputv; 31 char *output; 32 struct iovec *outputv; 33 Error *writeerr; 34 Error *readerr; 35 }; 36 37 38 static void test_skip_iovec(struct iovec **iov, 39 size_t *niov, 40 size_t skip, 41 struct iovec *old) 42 { 43 size_t offset = 0; 44 size_t i; 45 46 for (i = 0; i < *niov; i++) { 47 if (skip < (*iov)[i].iov_len) { 48 old->iov_len = (*iov)[i].iov_len; 49 old->iov_base = (*iov)[i].iov_base; 50 51 (*iov)[i].iov_len -= skip; 52 (*iov)[i].iov_base += skip; 53 break; 54 } else { 55 skip -= (*iov)[i].iov_len; 56 57 if (i == 0 && old->iov_base) { 58 (*iov)[i].iov_len = old->iov_len; 59 (*iov)[i].iov_base = old->iov_base; 60 old->iov_len = 0; 61 old->iov_base = NULL; 62 } 63 64 offset++; 65 } 66 } 67 68 *iov = *iov + offset; 69 *niov -= offset; 70 } 71 72 73 /* This thread sends all data using iovecs */ 74 static gpointer test_io_thread_writer(gpointer opaque) 75 { 76 QIOChannelTest *data = opaque; 77 struct iovec *iov = data->inputv; 78 size_t niov = data->niov; 79 struct iovec old = { 0 }; 80 81 qio_channel_set_blocking(data->src, data->blocking, NULL); 82 83 while (niov) { 84 ssize_t ret; 85 ret = qio_channel_writev(data->src, 86 iov, 87 niov, 88 &data->writeerr); 89 if (ret == QIO_CHANNEL_ERR_BLOCK) { 90 if (data->blocking) { 91 error_setg(&data->writeerr, 92 "Unexpected I/O blocking"); 93 break; 94 } else { 95 qio_channel_wait(data->src, 96 G_IO_OUT); 97 continue; 98 } 99 } else if (ret < 0) { 100 break; 101 } else if (ret == 0) { 102 error_setg(&data->writeerr, 103 "Unexpected zero length write"); 104 break; 105 } 106 107 test_skip_iovec(&iov, &niov, ret, &old); 108 } 109 110 return NULL; 111 } 112 113 114 /* This thread receives all data using iovecs */ 115 static gpointer test_io_thread_reader(gpointer opaque) 116 { 117 QIOChannelTest *data = opaque; 118 struct iovec *iov = data->outputv; 119 size_t niov = data->niov; 120 struct iovec old = { 0 }; 121 122 qio_channel_set_blocking(data->dst, data->blocking, NULL); 123 124 while (niov) { 125 ssize_t ret; 126 127 ret = qio_channel_readv(data->dst, 128 iov, 129 niov, 130 &data->readerr); 131 132 if (ret == QIO_CHANNEL_ERR_BLOCK) { 133 if (data->blocking) { 134 error_setg(&data->writeerr, 135 "Unexpected I/O blocking"); 136 break; 137 } else { 138 qio_channel_wait(data->dst, 139 G_IO_IN); 140 continue; 141 } 142 } else if (ret < 0) { 143 break; 144 } else if (ret == 0) { 145 break; 146 } 147 148 test_skip_iovec(&iov, &niov, ret, &old); 149 } 150 151 return NULL; 152 } 153 154 155 QIOChannelTest *qio_channel_test_new(void) 156 { 157 QIOChannelTest *data = g_new0(QIOChannelTest, 1); 158 size_t i; 159 size_t offset; 160 161 162 /* We'll send 1 MB of data */ 163 #define CHUNK_COUNT 250 164 #define CHUNK_LEN 4194 165 166 data->len = CHUNK_COUNT * CHUNK_LEN; 167 data->input = g_new0(char, data->len); 168 data->output = g_new0(gchar, data->len); 169 170 /* Fill input with a pattern */ 171 for (i = 0; i < data->len; i += CHUNK_LEN) { 172 memset(data->input + i, (i / CHUNK_LEN), CHUNK_LEN); 173 } 174 175 /* We'll split the data across a bunch of IO vecs */ 176 data->niov = CHUNK_COUNT; 177 data->inputv = g_new0(struct iovec, data->niov); 178 data->outputv = g_new0(struct iovec, data->niov); 179 180 for (i = 0, offset = 0; i < data->niov; i++, offset += CHUNK_LEN) { 181 data->inputv[i].iov_base = data->input + offset; 182 data->outputv[i].iov_base = data->output + offset; 183 data->inputv[i].iov_len = CHUNK_LEN; 184 data->outputv[i].iov_len = CHUNK_LEN; 185 } 186 187 return data; 188 } 189 190 void qio_channel_test_run_threads(QIOChannelTest *test, 191 bool blocking, 192 QIOChannel *src, 193 QIOChannel *dst) 194 { 195 GThread *reader, *writer; 196 197 test->src = src; 198 test->dst = dst; 199 test->blocking = blocking; 200 201 reader = g_thread_new("reader", 202 test_io_thread_reader, 203 test); 204 writer = g_thread_new("writer", 205 test_io_thread_writer, 206 test); 207 208 g_thread_join(reader); 209 g_thread_join(writer); 210 211 test->dst = test->src = NULL; 212 } 213 214 215 void qio_channel_test_run_writer(QIOChannelTest *test, 216 QIOChannel *src) 217 { 218 test->src = src; 219 test_io_thread_writer(test); 220 test->src = NULL; 221 } 222 223 224 void qio_channel_test_run_reader(QIOChannelTest *test, 225 QIOChannel *dst) 226 { 227 test->dst = dst; 228 test_io_thread_reader(test); 229 test->dst = NULL; 230 } 231 232 233 void qio_channel_test_validate(QIOChannelTest *test) 234 { 235 g_assert_cmpint(memcmp(test->input, 236 test->output, 237 test->len), ==, 0); 238 g_assert(test->readerr == NULL); 239 g_assert(test->writeerr == NULL); 240 241 g_free(test->inputv); 242 g_free(test->outputv); 243 g_free(test->input); 244 g_free(test->output); 245 g_free(test); 246 } 247