static json_t *read_bser_pdu(w_jbuffer_t *jr, w_stm_t stm, json_error_t *jerr) { json_int_t needed; json_int_t val; uint32_t ideal; json_int_t need; int r; json_t *obj; jr->rpos += 2; // We don't handle EAGAIN cleanly in here w_stm_set_nonblock(stm, false); if (!w_bser_decode_pdu_len(jr, stm, &val, jerr)) { return NULL; } // val tells us exactly how much storage we need for this PDU need = val - (jr->allocd - jr->wpos); if (need > 0) { ideal = jr->allocd; while (ideal < (uint32_t)need) { ideal *= 2; } if (ideal > jr->allocd) { char *buf = realloc(jr->buf, ideal); if (!buf) { snprintf(jerr->text, sizeof(jerr->text), "out of memory while allocating %" PRIu32 " bytes", ideal); return NULL; } jr->buf = buf; jr->allocd = ideal; } } // We have enough room for the whole thing, let's read it in while ((jr->wpos - jr->rpos) < val) { r = w_stm_read(stm, jr->buf + jr->wpos, jr->allocd - jr->wpos); if (r <= 0) { snprintf(jerr->text, sizeof(jerr->text), "error reading PDU: %s", strerror(errno)); return NULL; } jr->wpos += r; } obj = bunser(jr->buf + jr->rpos, jr->buf + jr->wpos, &needed, jerr); // Ensure that we move the read position to the wpos; we consumed it all jr->rpos = jr->wpos; w_stm_set_nonblock(stm, true); return obj; }
bool w_json_buffer_passthru(w_jbuffer_t *jr, enum w_pdu_type output_pdu, w_jbuffer_t *output_pdu_buf, w_stm_t stm) { json_t *j; json_error_t jerr; bool res; w_stm_set_nonblock(stm, false); if (!read_and_detect_pdu(jr, stm, &jerr)) { w_log(W_LOG_ERR, "failed to identify PDU: %s\n", jerr.text); return false; } if (jr->pdu_type == output_pdu) { // We can stream it through if (!stream_pdu(jr, stm, &jerr)) { w_log(W_LOG_ERR, "stream_pdu: %s\n", jerr.text); return false; } return true; } j = read_pdu_into_json(jr, stm, &jerr); if (!j) { w_log(W_LOG_ERR, "failed to parse response: %s\n", jerr.text); return false; } w_json_buffer_reset(output_pdu_buf); res = w_ser_write_pdu(output_pdu, output_pdu_buf, w_stm_stdout(), j); json_decref(j); return res; }
// The client thread reads and decodes json packets, // then dispatches the commands that it finds static void *client_thread(void *ptr) { struct watchman_client *client = ptr; struct watchman_event_poll pfd[2]; json_t *request; json_error_t jerr; w_stm_set_nonblock(client->stm, true); w_set_thread_name("client:stm=%p", client->stm); w_stm_get_events(client->stm, &pfd[0].evt); pfd[1].evt = client->ping; while (!stopping) { // Wait for input from either the client socket or // via the ping pipe, which signals that some other // thread wants to unilaterally send data to the client ignore_result(w_poll_events(pfd, 2, 2000)); if (stopping) { break; } if (pfd[0].ready) { request = w_json_buffer_next(&client->reader, client->stm, &jerr); if (!request && errno == EAGAIN) { // That's fine } else if (!request) { // Not so cool if (client->reader.wpos == client->reader.rpos) { // If they disconnected in between PDUs, no need to log // any error goto disconected; } send_error_response(client, "invalid json at position %d: %s", jerr.position, jerr.text); w_log(W_LOG_ERR, "invalid data from client: %s\n", jerr.text); goto disconected; } else if (request) { client->pdu_type = client->reader.pdu_type; dispatch_command(client, request, CMD_DAEMON); json_decref(request); } } if (pfd[1].ready) { w_event_test_and_clear(client->ping); } /* now send our response(s) */ while (client->head) { struct watchman_client_response *resp; /* de-queue the first response */ pthread_mutex_lock(&w_client_lock); resp = client->head; if (resp) { client->head = resp->next; if (client->tail == resp) { client->tail = NULL; } } pthread_mutex_unlock(&w_client_lock); if (resp) { bool ok; w_stm_set_nonblock(client->stm, false); /* Return the data in the same format that was used to ask for it */ ok = w_ser_write_pdu(client->pdu_type, &client->writer, client->stm, resp->json); json_decref(resp->json); free(resp); w_stm_set_nonblock(client->stm, true); if (!ok) { break; } } } } disconected: pthread_mutex_lock(&w_client_lock); w_ht_del(clients, w_ht_ptr_val(client)); pthread_mutex_unlock(&w_client_lock); return NULL; }
// The client thread reads and decodes json packets, // then dispatches the commands that it finds static void *client_thread(void *ptr) { struct watchman_client *client = ptr; struct watchman_event_poll pfd[2]; struct watchman_client_response *queued_responses_to_send; json_t *request; json_error_t jerr; bool send_ok = true; w_stm_set_nonblock(client->stm, true); w_set_thread_name("client=%p:stm=%p", client, client->stm); client->client_is_owner = w_stm_peer_is_owner(client->stm); w_stm_get_events(client->stm, &pfd[0].evt); pfd[1].evt = client->ping; while (!stopping) { // Wait for input from either the client socket or // via the ping pipe, which signals that some other // thread wants to unilaterally send data to the client ignore_result(w_poll_events(pfd, 2, 2000)); if (stopping) { break; } if (pfd[0].ready) { request = w_json_buffer_next(&client->reader, client->stm, &jerr); if (!request && errno == EAGAIN) { // That's fine } else if (!request) { // Not so cool if (client->reader.wpos == client->reader.rpos) { // If they disconnected in between PDUs, no need to log // any error goto disconected; } send_error_response(client, "invalid json at position %d: %s", jerr.position, jerr.text); w_log(W_LOG_ERR, "invalid data from client: %s\n", jerr.text); goto disconected; } else if (request) { client->pdu_type = client->reader.pdu_type; dispatch_command(client, request, CMD_DAEMON); json_decref(request); } } if (pfd[1].ready) { w_event_test_and_clear(client->ping); } /* de-queue the pending responses under the lock */ pthread_mutex_lock(&w_client_lock); queued_responses_to_send = client->head; client->head = NULL; client->tail = NULL; pthread_mutex_unlock(&w_client_lock); /* now send our response(s) */ while (queued_responses_to_send) { struct watchman_client_response *response_to_send = queued_responses_to_send; if (send_ok) { w_stm_set_nonblock(client->stm, false); /* Return the data in the same format that was used to ask for it. * Don't bother sending any more messages if the client disconnects, * but still free their memory. */ send_ok = w_ser_write_pdu(client->pdu_type, &client->writer, client->stm, response_to_send->json); w_stm_set_nonblock(client->stm, true); } queued_responses_to_send = response_to_send->next; json_decref(response_to_send->json); free(response_to_send); } } disconected: w_set_thread_name("NOT_CONN:client=%p:stm=%p", client, client->stm); // Remove the client from the map before we tear it down, as this makes // it easier to flush out pending writes on windows without worrying // about w_log_to_clients contending for the write buffers pthread_mutex_lock(&w_client_lock); w_ht_del(clients, w_ht_ptr_val(client)); pthread_mutex_unlock(&w_client_lock); client_delete(client); return NULL; }