static gboolean tcp_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque) { Chardev *chr = CHARDEV(opaque); SocketChardev *s = SOCKET_CHARDEV(opaque); uint8_t buf[CHR_READ_BUF_LEN]; int len, size; if (!s->connected || s->max_size <= 0) { return TRUE; } len = sizeof(buf); if (len > s->max_size) { len = s->max_size; } size = tcp_chr_recv(chr, (void *)buf, len); if (size == 0 || size == -1) { /* connection closed */ tcp_chr_disconnect(chr); } else if (size > 0) { if (s->do_telnetopt) { tcp_chr_process_IAC_bytes(chr, s, buf, &size); } if (size > 0) { qemu_chr_be_write(chr, buf, size); } } return TRUE; }
static gboolean fd_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque) { Chardev *chr = CHARDEV(opaque); FDChardev *s = FD_CHARDEV(opaque); int len; uint8_t buf[CHR_READ_BUF_LEN]; ssize_t ret; len = sizeof(buf); if (len > s->max_size) { len = s->max_size; } if (len == 0) { return TRUE; } ret = qio_channel_read( chan, (gchar *)buf, len, NULL); if (ret == 0) { remove_fd_in_watch(chr); qemu_chr_be_event(chr, CHR_EVENT_CLOSED); return FALSE; } if (ret > 0) { qemu_chr_be_write(chr, buf, ret); } return TRUE; }
static gboolean pty_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque) { Chardev *chr = CHARDEV(opaque); PtyChardev *s = PTY_CHARDEV(opaque); gsize len; uint8_t buf[CHR_READ_BUF_LEN]; ssize_t ret; len = sizeof(buf); if (len > s->read_bytes) { len = s->read_bytes; } if (len == 0) { return TRUE; } ret = qio_channel_read(s->ioc, (char *)buf, len, NULL); if (ret <= 0) { pty_chr_state(chr, 0); return FALSE; } else { pty_chr_state(chr, 1); qemu_chr_be_write(chr, buf, ret); } return TRUE; }
static void udp_chr_flush_buffer(UdpChardev *s) { Chardev *chr = CHARDEV(s); while (s->max_size > 0 && s->bufptr < s->bufcnt) { int n = MIN(s->max_size, s->bufcnt - s->bufptr); qemu_chr_be_write(chr, &s->buf[s->bufptr], n); s->bufptr += n; s->max_size = qemu_chr_be_can_write(chr); } }
/* We want to send a packet */ static void baum_write_packet(BaumDriverState *baum, const uint8_t *buf, int len) { uint8_t io_buf[1 + 2 * len], *cur = io_buf; int room; *cur++ = ESC; while (len--) if ((*cur++ = *buf++) == ESC) *cur++ = ESC; room = qemu_chr_be_can_write(baum->chr); len = cur - io_buf; if (len <= room) { /* Fits */ qemu_chr_be_write(baum->chr, io_buf, len); } else { int first; uint8_t out; /* Can't fit all, send what can be, and store the rest. */ qemu_chr_be_write(baum->chr, io_buf, room); len -= room; cur = io_buf + room; if (len > BUF_SIZE - baum->out_buf_used) { /* Can't even store it, drop the previous data... */ assert(len <= BUF_SIZE); baum->out_buf_used = 0; baum->out_buf_ptr = 0; } out = baum->out_buf_ptr; baum->out_buf_used += len; first = BUF_SIZE - baum->out_buf_ptr; if (len > first) { memcpy(baum->out_buf + out, cur, first); out = 0; len -= first; cur += first; } memcpy(baum->out_buf + out, cur, len); } }
/* The serial port can receive more of our data */ static void baum_accept_input(struct CharDriverState *chr) { BaumDriverState *baum = chr->opaque; int room, first; if (!baum->out_buf_used) return; room = qemu_chr_be_can_write(chr); if (!room) return; if (room > baum->out_buf_used) room = baum->out_buf_used; first = BUF_SIZE - baum->out_buf_ptr; if (room > first) { qemu_chr_be_write(chr, baum->out_buf + baum->out_buf_ptr, first); baum->out_buf_ptr = 0; baum->out_buf_used -= first; room -= first; } qemu_chr_be_write(chr, baum->out_buf + baum->out_buf_ptr, room); baum->out_buf_ptr += room; baum->out_buf_used -= room; }
static void wctablet_chr_accept_input(Chardev *chr) { TabletChardev *tablet = WCTABLET_CHARDEV(chr); int len, canWrite; canWrite = qemu_chr_be_can_write(chr); len = canWrite; if (len > tablet->outlen) { len = tablet->outlen; } if (len) { qemu_chr_be_write(chr, tablet->outbuf, len); tablet->outlen -= len; if (tablet->outlen) { memmove(tablet->outbuf, tablet->outbuf + len, tablet->outlen); } } }
static void msmouse_chr_accept_input(CharDriverState *chr) { MouseState *mouse = chr->opaque; int len; len = qemu_chr_be_can_write(chr); if (len > mouse->outlen) { len = mouse->outlen; } if (!len) { return; } qemu_chr_be_write(chr, mouse->outbuf, len); mouse->outlen -= len; if (mouse->outlen) { memmove(mouse->outbuf, mouse->outbuf + len, mouse->outlen); } }
static int vmc_write(SpiceCharDeviceInstance *sin, const uint8_t *buf, int len) { SpiceCharDriver *scd = container_of(sin, SpiceCharDriver, sin); ssize_t out = 0; ssize_t last_out; uint8_t* p = (uint8_t*)buf; while (len > 0) { last_out = MIN(len, VMC_MAX_HOST_WRITE); if (qemu_chr_be_can_write(scd->chr) < last_out) { break; } qemu_chr_be_write(scd->chr, p, last_out); out += last_out; len -= last_out; p += last_out; } return out; }
static void msmouse_event(void *opaque, int dx, int dy, int dz, int buttons_state) { CharDriverState *chr = (CharDriverState *)opaque; unsigned char bytes[4] = { 0x40, 0x00, 0x00, 0x00 }; /* Movement deltas */ bytes[0] |= (MSMOUSE_HI2(dy) << 2) | MSMOUSE_HI2(dx); bytes[1] |= MSMOUSE_LO6(dx); bytes[2] |= MSMOUSE_LO6(dy); /* Buttons */ bytes[0] |= (buttons_state & 0x01 ? 0x20 : 0x00); bytes[0] |= (buttons_state & 0x02 ? 0x10 : 0x00); bytes[3] |= (buttons_state & 0x04 ? 0x20 : 0x00); /* We always send the packet of, so that we do not have to keep track of previous state of the middle button. This can potentially confuse some very old drivers for two button mice though. */ qemu_chr_be_write(chr, bytes, 4); }
static int gdb_handle_packet(GDBState *s, const char *line_buf) { CPUState *cpu; CPUClass *cc; const char *p; uint32_t thread; int ch, reg_size, type, res; char buf[MAX_PACKET_LENGTH]; uint8_t mem_buf[MAX_PACKET_LENGTH]; uint8_t *registers; target_ulong addr, len; #ifdef DEBUG_GDB printf("command='%s'\n", line_buf); #endif p = line_buf; ch = *p++; switch(ch) { case '?': /* TODO: Make this return the correct value for user-mode. */ snprintf(buf, sizeof(buf), "T%02xthread:%02x;", GDB_SIGNAL_TRAP, cpu_index(s->c_cpu)); put_packet(s, buf); /* Remove all the breakpoints when this query is issued, * because gdb is doing and initial connect and the state * should be cleaned up. */ gdb_breakpoint_remove_all(); break; case 'c': if (*p != '\0') { addr = strtoull(p, (char **)&p, 16); gdb_set_cpu_pc(s, addr); } s->signal = 0; gdb_continue(s); return RS_IDLE; case 'C': s->signal = gdb_signal_to_target (strtoul(p, (char **)&p, 16)); if (s->signal == -1) s->signal = 0; gdb_continue(s); return RS_IDLE; case 'v': if (strncmp(p, "Cont", 4) == 0) { int res_signal, res_thread; p += 4; if (*p == '?') { put_packet(s, "vCont;c;C;s;S"); break; } res = 0; res_signal = 0; res_thread = 0; while (*p) { int action, signal; if (*p++ != ';') { res = 0; break; } action = *p++; signal = 0; if (action == 'C' || action == 'S') { signal = gdb_signal_to_target(strtoul(p, (char **)&p, 16)); if (signal == -1) { signal = 0; } } else if (action != 'c' && action != 's') { res = 0; break; } thread = 0; if (*p == ':') { thread = strtoull(p+1, (char **)&p, 16); } action = tolower(action); if (res == 0 || (res == 'c' && action == 's')) { res = action; res_signal = signal; res_thread = thread; } } if (res) { if (res_thread != -1 && res_thread != 0) { cpu = find_cpu(res_thread); if (cpu == NULL) { put_packet(s, "E22"); break; } s->c_cpu = cpu; } if (res == 's') { cpu_single_step(s->c_cpu, sstep_flags); } s->signal = res_signal; gdb_continue(s); return RS_IDLE; } break; } else { goto unknown_command; } case 'k': /* Kill the target */ fprintf(stderr, "\nQEMU: Terminated via GDBstub\n"); exit(0); case 'D': /* Detach packet */ gdb_breakpoint_remove_all(); gdb_syscall_mode = GDB_SYS_DISABLED; gdb_continue(s); put_packet(s, "OK"); break; case 's': if (*p != '\0') { addr = strtoull(p, (char **)&p, 16); gdb_set_cpu_pc(s, addr); } cpu_single_step(s->c_cpu, sstep_flags); gdb_continue(s); return RS_IDLE; case 'F': { target_ulong ret; target_ulong err; ret = strtoull(p, (char **)&p, 16); if (*p == ',') { p++; err = strtoull(p, (char **)&p, 16); } else { err = 0; } if (*p == ',') p++; type = *p; if (s->current_syscall_cb) { s->current_syscall_cb(s->c_cpu, ret, err); s->current_syscall_cb = NULL; } if (type == 'C') { put_packet(s, "T02"); } else { gdb_continue(s); } } break; case 'g': cpu_synchronize_state(s->g_cpu); len = 0; for (addr = 0; addr < s->g_cpu->gdb_num_g_regs; addr++) { reg_size = gdb_read_register(s->g_cpu, mem_buf + len, addr); len += reg_size; } memtohex(buf, mem_buf, len); put_packet(s, buf); break; case 'G': cpu_synchronize_state(s->g_cpu); registers = mem_buf; len = strlen(p) / 2; hextomem((uint8_t *)registers, p, len); for (addr = 0; addr < s->g_cpu->gdb_num_g_regs && len > 0; addr++) { reg_size = gdb_write_register(s->g_cpu, registers, addr); len -= reg_size; registers += reg_size; } put_packet(s, "OK"); break; case 'm': addr = strtoull(p, (char **)&p, 16); if (*p == ',') p++; len = strtoull(p, NULL, 16); /* memtohex() doubles the required space */ if (len > MAX_PACKET_LENGTH / 2) { put_packet (s, "E22"); break; } if (target_memory_rw_debug(s->g_cpu, addr, mem_buf, len, false) != 0) { put_packet (s, "E14"); } else { memtohex(buf, mem_buf, len); put_packet(s, buf); } break; case 'M': addr = strtoull(p, (char **)&p, 16); if (*p == ',') p++; len = strtoull(p, (char **)&p, 16); if (*p == ':') p++; /* hextomem() reads 2*len bytes */ if (len > strlen(p) / 2) { put_packet (s, "E22"); break; } hextomem(mem_buf, p, len); if (target_memory_rw_debug(s->g_cpu, addr, mem_buf, len, true) != 0) { put_packet(s, "E14"); } else { put_packet(s, "OK"); } break; case 'p': /* Older gdb are really dumb, and don't use 'g' if 'p' is avaialable. This works, but can be very slow. Anything new enough to understand XML also knows how to use this properly. */ if (!gdb_has_xml) goto unknown_command; addr = strtoull(p, (char **)&p, 16); reg_size = gdb_read_register(s->g_cpu, mem_buf, addr); if (reg_size) { memtohex(buf, mem_buf, reg_size); put_packet(s, buf); } else { put_packet(s, "E14"); } break; case 'P': if (!gdb_has_xml) goto unknown_command; addr = strtoull(p, (char **)&p, 16); if (*p == '=') p++; reg_size = strlen(p) / 2; hextomem(mem_buf, p, reg_size); gdb_write_register(s->g_cpu, mem_buf, addr); put_packet(s, "OK"); break; case 'Z': case 'z': type = strtoul(p, (char **)&p, 16); if (*p == ',') p++; addr = strtoull(p, (char **)&p, 16); if (*p == ',') p++; len = strtoull(p, (char **)&p, 16); if (ch == 'Z') res = gdb_breakpoint_insert(addr, len, type); else res = gdb_breakpoint_remove(addr, len, type); if (res >= 0) put_packet(s, "OK"); else if (res == -ENOSYS) put_packet(s, ""); else put_packet(s, "E22"); break; case 'H': type = *p++; thread = strtoull(p, (char **)&p, 16); if (thread == -1 || thread == 0) { put_packet(s, "OK"); break; } cpu = find_cpu(thread); if (cpu == NULL) { put_packet(s, "E22"); break; } switch (type) { case 'c': s->c_cpu = cpu; put_packet(s, "OK"); break; case 'g': s->g_cpu = cpu; put_packet(s, "OK"); break; default: put_packet(s, "E22"); break; } break; case 'T': thread = strtoull(p, (char **)&p, 16); cpu = find_cpu(thread); if (cpu != NULL) { put_packet(s, "OK"); } else { put_packet(s, "E22"); } break; case 'q': case 'Q': /* parse any 'q' packets here */ if (!strcmp(p,"qemu.sstepbits")) { /* Query Breakpoint bit definitions */ snprintf(buf, sizeof(buf), "ENABLE=%x,NOIRQ=%x,NOTIMER=%x", SSTEP_ENABLE, SSTEP_NOIRQ, SSTEP_NOTIMER); put_packet(s, buf); break; } else if (is_query_packet(p, "qemu.sstep", '=')) { /* Display or change the sstep_flags */ p += 10; if (*p != '=') { /* Display current setting */ snprintf(buf, sizeof(buf), "0x%x", sstep_flags); put_packet(s, buf); break; } p++; type = strtoul(p, (char **)&p, 16); sstep_flags = type; put_packet(s, "OK"); break; } else if (strcmp(p,"C") == 0) { /* "Current thread" remains vague in the spec, so always return * the first CPU (gdb returns the first thread). */ put_packet(s, "QC1"); break; } else if (strcmp(p,"fThreadInfo") == 0) { s->query_cpu = first_cpu; goto report_cpuinfo; } else if (strcmp(p,"sThreadInfo") == 0) { report_cpuinfo: if (s->query_cpu) { snprintf(buf, sizeof(buf), "m%x", cpu_index(s->query_cpu)); put_packet(s, buf); s->query_cpu = CPU_NEXT(s->query_cpu); } else put_packet(s, "l"); break; } else if (strncmp(p,"ThreadExtraInfo,", 16) == 0) { thread = strtoull(p+16, (char **)&p, 16); cpu = find_cpu(thread); if (cpu != NULL) { cpu_synchronize_state(cpu); /* memtohex() doubles the required space */ len = snprintf((char *)mem_buf, sizeof(buf) / 2, "CPU#%d [%s]", cpu->cpu_index, cpu->halted ? "halted " : "running"); memtohex(buf, mem_buf, len); put_packet(s, buf); } break; } #ifdef CONFIG_USER_ONLY else if (strcmp(p, "Offsets") == 0) { TaskState *ts = s->c_cpu->opaque; snprintf(buf, sizeof(buf), "Text=" TARGET_ABI_FMT_lx ";Data=" TARGET_ABI_FMT_lx ";Bss=" TARGET_ABI_FMT_lx, ts->info->code_offset, ts->info->data_offset, ts->info->data_offset); put_packet(s, buf); break; } #else /* !CONFIG_USER_ONLY */ else if (strncmp(p, "Rcmd,", 5) == 0) { int len = strlen(p + 5); if ((len % 2) != 0) { put_packet(s, "E01"); break; } len = len / 2; hextomem(mem_buf, p + 5, len); mem_buf[len++] = 0; qemu_chr_be_write(s->mon_chr, mem_buf, len); put_packet(s, "OK"); break; } #endif /* !CONFIG_USER_ONLY */ if (is_query_packet(p, "Supported", ':')) { snprintf(buf, sizeof(buf), "PacketSize=%x", MAX_PACKET_LENGTH); cc = CPU_GET_CLASS(first_cpu); if (cc->gdb_core_xml_file != NULL) { pstrcat(buf, sizeof(buf), ";qXfer:features:read+"); } put_packet(s, buf); break; } if (strncmp(p, "Xfer:features:read:", 19) == 0) { const char *xml; target_ulong total_len; cc = CPU_GET_CLASS(first_cpu); if (cc->gdb_core_xml_file == NULL) { goto unknown_command; } gdb_has_xml = true; p += 19; xml = get_feature_xml(p, &p, cc); if (!xml) { snprintf(buf, sizeof(buf), "E00"); put_packet(s, buf); break; } if (*p == ':') p++; addr = strtoul(p, (char **)&p, 16); if (*p == ',') p++; len = strtoul(p, (char **)&p, 16); total_len = strlen(xml); if (addr > total_len) { snprintf(buf, sizeof(buf), "E00"); put_packet(s, buf); break; } if (len > (MAX_PACKET_LENGTH - 5) / 2) len = (MAX_PACKET_LENGTH - 5) / 2; if (len < total_len - addr) { buf[0] = 'm'; len = memtox(buf + 1, xml + addr, len); } else { buf[0] = 'l'; len = memtox(buf + 1, xml + addr, total_len - addr); } put_packet_binary(s, buf, len + 1); break; } if (is_query_packet(p, "Attached", ':')) { put_packet(s, GDB_ATTACHED); break; } /* Unrecognised 'q' command. */ goto unknown_command; default: unknown_command: /* put empty packet */ buf[0] = '\0'; put_packet(s, buf); break; } return RS_IDLE; }
static void char_mux_test(void) { QemuOpts *opts; Chardev *chr, *base; char *data; FeHandler h1 = { 0, }, h2 = { 0, }; CharBackend chr_be1, chr_be2; opts = qemu_opts_create(qemu_find_opts("chardev"), "mux-label", 1, &error_abort); qemu_opt_set(opts, "backend", "ringbuf", &error_abort); qemu_opt_set(opts, "size", "128", &error_abort); qemu_opt_set(opts, "mux", "on", &error_abort); chr = qemu_chr_new_from_opts(opts, &error_abort); g_assert_nonnull(chr); qemu_opts_del(opts); qemu_chr_fe_init(&chr_be1, chr, &error_abort); qemu_chr_fe_set_handlers(&chr_be1, fe_can_read, fe_read, fe_event, NULL, &h1, NULL, true); qemu_chr_fe_init(&chr_be2, chr, &error_abort); qemu_chr_fe_set_handlers(&chr_be2, fe_can_read, fe_read, fe_event, NULL, &h2, NULL, true); qemu_chr_fe_take_focus(&chr_be2); base = qemu_chr_find("mux-label-base"); g_assert_cmpint(qemu_chr_be_can_write(base), !=, 0); qemu_chr_be_write(base, (void *)"hello", 6); g_assert_cmpint(h1.read_count, ==, 0); g_assert_cmpint(h2.read_count, ==, 6); g_assert_cmpstr(h2.read_buf, ==, "hello"); h2.read_count = 0; /* switch focus */ qemu_chr_be_write(base, (void *)"\1c", 2); qemu_chr_be_write(base, (void *)"hello", 6); g_assert_cmpint(h2.read_count, ==, 0); g_assert_cmpint(h1.read_count, ==, 6); g_assert_cmpstr(h1.read_buf, ==, "hello"); h1.read_count = 0; /* remove first handler */ qemu_chr_fe_set_handlers(&chr_be1, NULL, NULL, NULL, NULL, NULL, NULL, true); qemu_chr_be_write(base, (void *)"hello", 6); g_assert_cmpint(h1.read_count, ==, 0); g_assert_cmpint(h2.read_count, ==, 0); qemu_chr_be_write(base, (void *)"\1c", 2); qemu_chr_be_write(base, (void *)"hello", 6); g_assert_cmpint(h1.read_count, ==, 0); g_assert_cmpint(h2.read_count, ==, 6); g_assert_cmpstr(h2.read_buf, ==, "hello"); h2.read_count = 0; /* print help */ qemu_chr_be_write(base, (void *)"\1?", 2); data = qmp_ringbuf_read("mux-label-base", 128, false, 0, &error_abort); g_assert_cmpint(strlen(data), !=, 0); g_free(data); qemu_chr_fe_deinit(&chr_be1, false); qemu_chr_fe_deinit(&chr_be2, true); }
static void char_mux_test(void) { QemuOpts *opts; Chardev *chr, *base; char *data; FeHandler h1 = { 0, false, 0, false, }, h2 = { 0, false, 0, false, }; CharBackend chr_be1, chr_be2; opts = qemu_opts_create(qemu_find_opts("chardev"), "mux-label", 1, &error_abort); qemu_opt_set(opts, "backend", "ringbuf", &error_abort); qemu_opt_set(opts, "size", "128", &error_abort); qemu_opt_set(opts, "mux", "on", &error_abort); chr = qemu_chr_new_from_opts(opts, NULL, &error_abort); g_assert_nonnull(chr); qemu_opts_del(opts); qemu_chr_fe_init(&chr_be1, chr, &error_abort); qemu_chr_fe_set_handlers(&chr_be1, fe_can_read, fe_read, fe_event, NULL, &h1, NULL, true); qemu_chr_fe_init(&chr_be2, chr, &error_abort); qemu_chr_fe_set_handlers(&chr_be2, fe_can_read, fe_read, fe_event, NULL, &h2, NULL, true); qemu_chr_fe_take_focus(&chr_be2); base = qemu_chr_find("mux-label-base"); g_assert_cmpint(qemu_chr_be_can_write(base), !=, 0); qemu_chr_be_write(base, (void *)"hello", 6); g_assert_cmpint(h1.read_count, ==, 0); g_assert_cmpint(h2.read_count, ==, 6); g_assert_cmpstr(h2.read_buf, ==, "hello"); h2.read_count = 0; g_assert_cmpint(h1.last_event, !=, 42); /* should be MUX_OUT or OPENED */ g_assert_cmpint(h2.last_event, !=, 42); /* should be MUX_IN or OPENED */ /* sending event on the base broadcast to all fe, historical reasons? */ qemu_chr_be_event(base, 42); g_assert_cmpint(h1.last_event, ==, 42); g_assert_cmpint(h2.last_event, ==, 42); qemu_chr_be_event(chr, -1); g_assert_cmpint(h1.last_event, ==, 42); g_assert_cmpint(h2.last_event, ==, -1); /* switch focus */ qemu_chr_be_write(base, (void *)"\1b", 2); g_assert_cmpint(h1.last_event, ==, 42); g_assert_cmpint(h2.last_event, ==, CHR_EVENT_BREAK); qemu_chr_be_write(base, (void *)"\1c", 2); g_assert_cmpint(h1.last_event, ==, CHR_EVENT_MUX_IN); g_assert_cmpint(h2.last_event, ==, CHR_EVENT_MUX_OUT); qemu_chr_be_event(chr, -1); g_assert_cmpint(h1.last_event, ==, -1); g_assert_cmpint(h2.last_event, ==, CHR_EVENT_MUX_OUT); qemu_chr_be_write(base, (void *)"hello", 6); g_assert_cmpint(h2.read_count, ==, 0); g_assert_cmpint(h1.read_count, ==, 6); g_assert_cmpstr(h1.read_buf, ==, "hello"); h1.read_count = 0; qemu_chr_be_write(base, (void *)"\1b", 2); g_assert_cmpint(h1.last_event, ==, CHR_EVENT_BREAK); g_assert_cmpint(h2.last_event, ==, CHR_EVENT_MUX_OUT); /* open/close state and corresponding events */ g_assert_true(qemu_chr_fe_backend_open(&chr_be1)); g_assert_true(qemu_chr_fe_backend_open(&chr_be2)); g_assert_true(h1.is_open); g_assert_false(h1.openclose_mismatch); g_assert_true(h2.is_open); g_assert_false(h2.openclose_mismatch); h1.openclose_count = h2.openclose_count = 0; qemu_chr_fe_set_handlers(&chr_be1, NULL, NULL, NULL, NULL, NULL, NULL, false); qemu_chr_fe_set_handlers(&chr_be2, NULL, NULL, NULL, NULL, NULL, NULL, false); g_assert_cmpint(h1.openclose_count, ==, 0); g_assert_cmpint(h2.openclose_count, ==, 0); h1.is_open = h2.is_open = false; qemu_chr_fe_set_handlers(&chr_be1, NULL, NULL, fe_event, NULL, &h1, NULL, false); qemu_chr_fe_set_handlers(&chr_be2, NULL, NULL, fe_event, NULL, &h2, NULL, false); g_assert_cmpint(h1.openclose_count, ==, 1); g_assert_false(h1.openclose_mismatch); g_assert_cmpint(h2.openclose_count, ==, 1); g_assert_false(h2.openclose_mismatch); qemu_chr_be_event(base, CHR_EVENT_CLOSED); qemu_chr_be_event(base, CHR_EVENT_OPENED); g_assert_cmpint(h1.openclose_count, ==, 3); g_assert_false(h1.openclose_mismatch); g_assert_cmpint(h2.openclose_count, ==, 3); g_assert_false(h2.openclose_mismatch); qemu_chr_fe_set_handlers(&chr_be2, fe_can_read, fe_read, fe_event, NULL, &h2, NULL, false); qemu_chr_fe_set_handlers(&chr_be1, fe_can_read, fe_read, fe_event, NULL, &h1, NULL, false); /* remove first handler */ qemu_chr_fe_set_handlers(&chr_be1, NULL, NULL, NULL, NULL, NULL, NULL, true); qemu_chr_be_write(base, (void *)"hello", 6); g_assert_cmpint(h1.read_count, ==, 0); g_assert_cmpint(h2.read_count, ==, 0); qemu_chr_be_write(base, (void *)"\1c", 2); qemu_chr_be_write(base, (void *)"hello", 6); g_assert_cmpint(h1.read_count, ==, 0); g_assert_cmpint(h2.read_count, ==, 6); g_assert_cmpstr(h2.read_buf, ==, "hello"); h2.read_count = 0; /* print help */ qemu_chr_be_write(base, (void *)"\1?", 2); data = qmp_ringbuf_read("mux-label-base", 128, false, 0, &error_abort); g_assert_cmpint(strlen(data), !=, 0); g_free(data); qemu_chr_fe_deinit(&chr_be1, false); qemu_chr_fe_deinit(&chr_be2, true); }