| File: | ui/vnc-jobs.c |
| Location: | line 232, column 21 |
| Description: | Access to field 'vs' results in a dereference of a null pointer (loaded from variable 'job') |
| 1 | /* | |||
| 2 | * QEMU VNC display driver | |||
| 3 | * | |||
| 4 | * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws> | |||
| 5 | * Copyright (C) 2006 Fabrice Bellard | |||
| 6 | * Copyright (C) 2009 Red Hat, Inc | |||
| 7 | * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com> | |||
| 8 | * | |||
| 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| 10 | * of this software and associated documentation files (the "Software"), to deal | |||
| 11 | * in the Software without restriction, including without limitation the rights | |||
| 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
| 13 | * copies of the Software, and to permit persons to whom the Software is | |||
| 14 | * furnished to do so, subject to the following conditions: | |||
| 15 | * | |||
| 16 | * The above copyright notice and this permission notice shall be included in | |||
| 17 | * all copies or substantial portions of the Software. | |||
| 18 | * | |||
| 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
| 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
| 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |||
| 22 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
| 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
| 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
| 25 | * THE SOFTWARE. | |||
| 26 | */ | |||
| 27 | ||||
| 28 | ||||
| 29 | #include "vnc.h" | |||
| 30 | #include "vnc-jobs.h" | |||
| 31 | #include "qemu/sockets.h" | |||
| 32 | ||||
| 33 | /* | |||
| 34 | * Locking: | |||
| 35 | * | |||
| 36 | * There are three levels of locking: | |||
| 37 | * - jobs queue lock: for each operation on the queue (push, pop, isEmpty?) | |||
| 38 | * - VncDisplay global lock: mainly used for framebuffer updates to avoid | |||
| 39 | * screen corruption if the framebuffer is updated | |||
| 40 | * while the worker is doing something. | |||
| 41 | * - VncState::output lock: used to make sure the output buffer is not corrupted | |||
| 42 | * if two threads try to write on it at the same time | |||
| 43 | * | |||
| 44 | * While the VNC worker thread is working, the VncDisplay global lock is held | |||
| 45 | * to avoid screen corruption (this does not block vnc_refresh() because it | |||
| 46 | * uses trylock()) but the output lock is not held because the thread works on | |||
| 47 | * its own output buffer. | |||
| 48 | * When the encoding job is done, the worker thread will hold the output lock | |||
| 49 | * and copy its output buffer in vs->output. | |||
| 50 | */ | |||
| 51 | ||||
| 52 | struct VncJobQueue { | |||
| 53 | QemuCond cond; | |||
| 54 | QemuMutex mutex; | |||
| 55 | QemuThread thread; | |||
| 56 | Buffer buffer; | |||
| 57 | bool_Bool exit; | |||
| 58 | QTAILQ_HEAD(, VncJob)struct { struct VncJob *tqh_first; struct VncJob * *tqh_last; } jobs; | |||
| 59 | }; | |||
| 60 | ||||
| 61 | typedef struct VncJobQueue VncJobQueue; | |||
| 62 | ||||
| 63 | /* | |||
| 64 | * We use a single global queue, but most of the functions are | |||
| 65 | * already reentrant, so we can easily add more than one encoding thread | |||
| 66 | */ | |||
| 67 | static VncJobQueue *queue; | |||
| 68 | ||||
| 69 | static void vnc_lock_queue(VncJobQueue *queue) | |||
| 70 | { | |||
| 71 | qemu_mutex_lock(&queue->mutex); | |||
| 72 | } | |||
| 73 | ||||
| 74 | static void vnc_unlock_queue(VncJobQueue *queue) | |||
| 75 | { | |||
| 76 | qemu_mutex_unlock(&queue->mutex); | |||
| 77 | } | |||
| 78 | ||||
| 79 | VncJob *vnc_job_new(VncState *vs) | |||
| 80 | { | |||
| 81 | VncJob *job = g_malloc0(sizeof(VncJob)); | |||
| 82 | ||||
| 83 | job->vs = vs; | |||
| 84 | vnc_lock_queue(queue); | |||
| 85 | QLIST_INIT(&job->rectangles)do { (&job->rectangles)->lh_first = ((void*)0); } while ( 0); | |||
| 86 | vnc_unlock_queue(queue); | |||
| 87 | return job; | |||
| 88 | } | |||
| 89 | ||||
| 90 | int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h) | |||
| 91 | { | |||
| 92 | VncRectEntry *entry = g_malloc0(sizeof(VncRectEntry)); | |||
| 93 | ||||
| 94 | entry->rect.x = x; | |||
| 95 | entry->rect.y = y; | |||
| 96 | entry->rect.w = w; | |||
| 97 | entry->rect.h = h; | |||
| 98 | ||||
| 99 | vnc_lock_queue(queue); | |||
| 100 | QLIST_INSERT_HEAD(&job->rectangles, entry, next)do { if (((entry)->next.le_next = (&job->rectangles )->lh_first) != ((void*)0)) (&job->rectangles)-> lh_first->next.le_prev = &(entry)->next.le_next; (& job->rectangles)->lh_first = (entry); (entry)->next. le_prev = &(&job->rectangles)->lh_first; } while ( 0); | |||
| 101 | vnc_unlock_queue(queue); | |||
| 102 | return 1; | |||
| 103 | } | |||
| 104 | ||||
| 105 | void vnc_job_push(VncJob *job) | |||
| 106 | { | |||
| 107 | vnc_lock_queue(queue); | |||
| 108 | if (queue->exit || QLIST_EMPTY(&job->rectangles)((&job->rectangles)->lh_first == ((void*)0))) { | |||
| 109 | g_free(job); | |||
| 110 | } else { | |||
| 111 | QTAILQ_INSERT_TAIL(&queue->jobs, job, next)do { (job)->next.tqe_next = ((void*)0); (job)->next.tqe_prev = (&queue->jobs)->tqh_last; *(&queue->jobs) ->tqh_last = (job); (&queue->jobs)->tqh_last = & (job)->next.tqe_next; } while ( 0); | |||
| 112 | qemu_cond_broadcast(&queue->cond); | |||
| 113 | } | |||
| 114 | vnc_unlock_queue(queue); | |||
| 115 | } | |||
| 116 | ||||
| 117 | static bool_Bool vnc_has_job_locked(VncState *vs) | |||
| 118 | { | |||
| 119 | VncJob *job; | |||
| 120 | ||||
| 121 | QTAILQ_FOREACH(job, &queue->jobs, next)for ((job) = ((&queue->jobs)->tqh_first); (job); (job ) = ((job)->next.tqe_next)) { | |||
| 122 | if (job->vs == vs || !vs) { | |||
| 123 | return true1; | |||
| 124 | } | |||
| 125 | } | |||
| 126 | return false0; | |||
| 127 | } | |||
| 128 | ||||
| 129 | bool_Bool vnc_has_job(VncState *vs) | |||
| 130 | { | |||
| 131 | bool_Bool ret; | |||
| 132 | ||||
| 133 | vnc_lock_queue(queue); | |||
| 134 | ret = vnc_has_job_locked(vs); | |||
| 135 | vnc_unlock_queue(queue); | |||
| 136 | return ret; | |||
| 137 | } | |||
| 138 | ||||
| 139 | void vnc_jobs_clear(VncState *vs) | |||
| 140 | { | |||
| 141 | VncJob *job, *tmp; | |||
| 142 | ||||
| 143 | vnc_lock_queue(queue); | |||
| 144 | QTAILQ_FOREACH_SAFE(job, &queue->jobs, next, tmp)for ((job) = ((&queue->jobs)->tqh_first); (job) && ((tmp) = ((job)->next.tqe_next), 1); (job) = (tmp)) { | |||
| 145 | if (job->vs == vs || !vs) { | |||
| 146 | QTAILQ_REMOVE(&queue->jobs, job, next)do { if (((job)->next.tqe_next) != ((void*)0)) (job)->next .tqe_next->next.tqe_prev = (job)->next.tqe_prev; else ( &queue->jobs)->tqh_last = (job)->next.tqe_prev; * (job)->next.tqe_prev = (job)->next.tqe_next; } while ( 0 ); | |||
| 147 | } | |||
| 148 | } | |||
| 149 | vnc_unlock_queue(queue); | |||
| 150 | } | |||
| 151 | ||||
| 152 | void vnc_jobs_join(VncState *vs) | |||
| 153 | { | |||
| 154 | vnc_lock_queue(queue); | |||
| 155 | while (vnc_has_job_locked(vs)) { | |||
| 156 | qemu_cond_wait(&queue->cond, &queue->mutex); | |||
| 157 | } | |||
| 158 | vnc_unlock_queue(queue); | |||
| 159 | vnc_jobs_consume_buffer(vs); | |||
| 160 | } | |||
| 161 | ||||
| 162 | void vnc_jobs_consume_buffer(VncState *vs) | |||
| 163 | { | |||
| 164 | bool_Bool flush; | |||
| 165 | ||||
| 166 | vnc_lock_output(vs); | |||
| 167 | if (vs->jobs_buffer.offset) { | |||
| 168 | vnc_write(vs, vs->jobs_buffer.buffer, vs->jobs_buffer.offset); | |||
| 169 | buffer_reset(&vs->jobs_buffer); | |||
| 170 | } | |||
| 171 | flush = vs->csock != -1 && vs->abort != true1; | |||
| 172 | vnc_unlock_output(vs); | |||
| 173 | ||||
| 174 | if (flush) { | |||
| 175 | vnc_flush(vs); | |||
| 176 | } | |||
| 177 | } | |||
| 178 | ||||
| 179 | /* | |||
| 180 | * Copy data for local use | |||
| 181 | */ | |||
| 182 | static void vnc_async_encoding_start(VncState *orig, VncState *local) | |||
| 183 | { | |||
| 184 | local->vnc_encoding = orig->vnc_encoding; | |||
| 185 | local->features = orig->features; | |||
| 186 | local->vd = orig->vd; | |||
| 187 | local->lossy_rect = orig->lossy_rect; | |||
| 188 | local->write_pixels = orig->write_pixels; | |||
| 189 | local->client_pf = orig->client_pf; | |||
| 190 | local->client_be = orig->client_be; | |||
| 191 | local->tight = orig->tight; | |||
| 192 | local->zlib = orig->zlib; | |||
| 193 | local->hextile = orig->hextile; | |||
| 194 | local->zrle = orig->zrle; | |||
| 195 | local->output = queue->buffer; | |||
| 196 | local->csock = -1; /* Don't do any network work on this thread */ | |||
| 197 | ||||
| 198 | buffer_reset(&local->output); | |||
| 199 | } | |||
| 200 | ||||
| 201 | static void vnc_async_encoding_end(VncState *orig, VncState *local) | |||
| 202 | { | |||
| 203 | orig->tight = local->tight; | |||
| 204 | orig->zlib = local->zlib; | |||
| 205 | orig->hextile = local->hextile; | |||
| 206 | orig->zrle = local->zrle; | |||
| 207 | orig->lossy_rect = local->lossy_rect; | |||
| 208 | ||||
| 209 | queue->buffer = local->output; | |||
| 210 | } | |||
| 211 | ||||
| 212 | static int vnc_worker_thread_loop(VncJobQueue *queue) | |||
| 213 | { | |||
| 214 | VncJob *job; | |||
| 215 | VncRectEntry *entry, *tmp; | |||
| 216 | VncState vs; | |||
| 217 | int n_rectangles; | |||
| 218 | int saved_offset; | |||
| 219 | ||||
| 220 | vnc_lock_queue(queue); | |||
| 221 | while (QTAILQ_EMPTY(&queue->jobs)((&queue->jobs)->tqh_first == ((void*)0)) && !queue->exit) { | |||
| 222 | qemu_cond_wait(&queue->cond, &queue->mutex); | |||
| 223 | } | |||
| 224 | /* Here job can only be NULL if queue->exit is true */ | |||
| 225 | job = QTAILQ_FIRST(&queue->jobs)((&queue->jobs)->tqh_first); | |||
| 226 | vnc_unlock_queue(queue); | |||
| 227 | ||||
| 228 | if (queue->exit) { | |||
| 229 | return -1; | |||
| 230 | } | |||
| 231 | ||||
| 232 | vnc_lock_output(job->vs); | |||
| ||||
| 233 | if (job->vs->csock == -1 || job->vs->abort == true1) { | |||
| 234 | vnc_unlock_output(job->vs); | |||
| 235 | goto disconnected; | |||
| 236 | } | |||
| 237 | vnc_unlock_output(job->vs); | |||
| 238 | ||||
| 239 | /* Make a local copy of vs and switch output buffers */ | |||
| 240 | vnc_async_encoding_start(job->vs, &vs); | |||
| 241 | ||||
| 242 | /* Start sending rectangles */ | |||
| 243 | n_rectangles = 0; | |||
| 244 | vnc_write_u8(&vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE0); | |||
| 245 | vnc_write_u8(&vs, 0); | |||
| 246 | saved_offset = vs.output.offset; | |||
| 247 | vnc_write_u16(&vs, 0); | |||
| 248 | ||||
| 249 | vnc_lock_display(job->vs->vd); | |||
| 250 | QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp)for ((entry) = ((&job->rectangles)->lh_first); (entry ) && ((tmp) = ((entry)->next.le_next), 1); (entry) = (tmp)) { | |||
| 251 | int n; | |||
| 252 | ||||
| 253 | if (job->vs->csock == -1) { | |||
| 254 | vnc_unlock_display(job->vs->vd); | |||
| 255 | goto disconnected; | |||
| 256 | } | |||
| 257 | ||||
| 258 | n = vnc_send_framebuffer_update(&vs, entry->rect.x, entry->rect.y, | |||
| 259 | entry->rect.w, entry->rect.h); | |||
| 260 | ||||
| 261 | if (n >= 0) { | |||
| 262 | n_rectangles += n; | |||
| 263 | } | |||
| 264 | g_free(entry); | |||
| 265 | } | |||
| 266 | vnc_unlock_display(job->vs->vd); | |||
| 267 | ||||
| 268 | /* Put n_rectangles at the beginning of the message */ | |||
| 269 | vs.output.buffer[saved_offset] = (n_rectangles >> 8) & 0xFF; | |||
| 270 | vs.output.buffer[saved_offset + 1] = n_rectangles & 0xFF; | |||
| 271 | ||||
| 272 | vnc_lock_output(job->vs); | |||
| 273 | if (job->vs->csock != -1) { | |||
| 274 | buffer_reserve(&job->vs->jobs_buffer, vs.output.offset); | |||
| 275 | buffer_append(&job->vs->jobs_buffer, vs.output.buffer, | |||
| 276 | vs.output.offset); | |||
| 277 | /* Copy persistent encoding data */ | |||
| 278 | vnc_async_encoding_end(job->vs, &vs); | |||
| 279 | ||||
| 280 | qemu_bh_schedule(job->vs->bh); | |||
| 281 | } | |||
| 282 | vnc_unlock_output(job->vs); | |||
| 283 | ||||
| 284 | disconnected: | |||
| 285 | vnc_lock_queue(queue); | |||
| 286 | QTAILQ_REMOVE(&queue->jobs, job, next)do { if (((job)->next.tqe_next) != ((void*)0)) (job)->next .tqe_next->next.tqe_prev = (job)->next.tqe_prev; else ( &queue->jobs)->tqh_last = (job)->next.tqe_prev; * (job)->next.tqe_prev = (job)->next.tqe_next; } while ( 0 ); | |||
| 287 | vnc_unlock_queue(queue); | |||
| 288 | qemu_cond_broadcast(&queue->cond); | |||
| 289 | g_free(job); | |||
| 290 | return 0; | |||
| 291 | } | |||
| 292 | ||||
| 293 | static VncJobQueue *vnc_queue_init(void) | |||
| 294 | { | |||
| 295 | VncJobQueue *queue = g_malloc0(sizeof(VncJobQueue)); | |||
| 296 | ||||
| 297 | qemu_cond_init(&queue->cond); | |||
| 298 | qemu_mutex_init(&queue->mutex); | |||
| 299 | QTAILQ_INIT(&queue->jobs)do { (&queue->jobs)->tqh_first = ((void*)0); (& queue->jobs)->tqh_last = &(&queue->jobs)-> tqh_first; } while ( 0); | |||
| 300 | return queue; | |||
| 301 | } | |||
| 302 | ||||
| 303 | static void vnc_queue_clear(VncJobQueue *q) | |||
| 304 | { | |||
| 305 | qemu_cond_destroy(&queue->cond); | |||
| 306 | qemu_mutex_destroy(&queue->mutex); | |||
| 307 | buffer_free(&queue->buffer); | |||
| 308 | g_free(q); | |||
| 309 | queue = NULL((void*)0); /* Unset global queue */ | |||
| 310 | } | |||
| 311 | ||||
| 312 | static void *vnc_worker_thread(void *arg) | |||
| 313 | { | |||
| 314 | VncJobQueue *queue = arg; | |||
| 315 | ||||
| 316 | qemu_thread_get_self(&queue->thread); | |||
| 317 | ||||
| 318 | while (!vnc_worker_thread_loop(queue)) ; | |||
| ||||
| 319 | vnc_queue_clear(queue); | |||
| 320 | return NULL((void*)0); | |||
| 321 | } | |||
| 322 | ||||
| 323 | static bool_Bool vnc_worker_thread_running(void) | |||
| 324 | { | |||
| 325 | return queue; /* Check global queue */ | |||
| 326 | } | |||
| 327 | ||||
| 328 | void vnc_start_worker_thread(void) | |||
| 329 | { | |||
| 330 | VncJobQueue *q; | |||
| 331 | ||||
| 332 | if (vnc_worker_thread_running()) | |||
| 333 | return ; | |||
| 334 | ||||
| 335 | q = vnc_queue_init(); | |||
| 336 | qemu_thread_create(&q->thread, vnc_worker_thread, q, QEMU_THREAD_DETACHED1); | |||
| 337 | queue = q; /* Set global queue */ | |||
| 338 | } | |||
| 339 | ||||
| 340 | void vnc_stop_worker_thread(void) | |||
| 341 | { | |||
| 342 | if (!vnc_worker_thread_running()) | |||
| 343 | return ; | |||
| 344 | ||||
| 345 | /* Remove all jobs and wake up the thread */ | |||
| 346 | vnc_lock_queue(queue); | |||
| 347 | queue->exit = true1; | |||
| 348 | vnc_unlock_queue(queue); | |||
| 349 | vnc_jobs_clear(NULL((void*)0)); | |||
| 350 | qemu_cond_broadcast(&queue->cond); | |||
| 351 | } |