void * remove_from_queue(Queue *q) { void *data = NULL; Cell *remove_cell; TSMutexLock(q->mutex); if (q->nb_elem > 0) { remove_cell = q->head; TSAssert(remove_cell->magic == MAGIC_ALIVE); data = remove_cell->ptr_data; q->head = remove_cell->ptr_prev; if (q->head == NULL) { TSAssert(q->nb_elem == 1); q->tail = NULL; } else { TSAssert(q->head->magic == MAGIC_ALIVE); q->head->ptr_next = NULL; } remove_cell->magic = MAGIC_DEAD; TSfree(remove_cell); q->nb_elem--; } TSMutexUnlock(q->mutex); return data; }
void add_to_queue(Queue *q, void *data) { Cell *new_cell; int n; if (data != NULL) { TSMutexLock(q->mutex); /* Init the new cell */ new_cell = TSmalloc(sizeof(Cell)); new_cell->magic = MAGIC_ALIVE; new_cell->ptr_data = data; new_cell->ptr_next = q->tail; new_cell->ptr_prev = NULL; /* Add this new cell to the queue */ if (q->tail == NULL) { TSAssert(q->head == NULL); TSAssert(q->nb_elem == 0); q->tail = new_cell; q->head = new_cell; } else { TSAssert(q->tail->magic == MAGIC_ALIVE); q->tail->ptr_prev = new_cell; q->tail = new_cell; } n = q->nb_elem++; TSMutexUnlock(q->mutex); if (n > MAX_JOBS_ALARM) { TSError("[thread_pool] Warning:Too many jobs in plugin thread pool queue (%d). Maximum allowed is %d", n, MAX_JOBS_ALARM); } } }
/*------------------------------------------------------------------------- wake_up_streams Send an event to the upstream vconnection to either - ask for more data - let it know we're done Reenable the downstream vconnection Input: contp continuation for the current transaction Output : Return Value: 0 if failure 1 if success -------------------------------------------------------------------------*/ static int wake_up_streams(TSCont contp) { TSVIO input_vio; ContData *data; int ntodo; data = TSContDataGet(contp); TSAssert(data->magic == MAGIC_ALIVE); input_vio = TSVConnWriteVIOGet(contp); ntodo = TSVIONTodoGet(input_vio); if (ntodo > 0) { TSVIOReenable(data->output_vio); TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_READY, input_vio); } else { TSDebug(DBG_TAG, "Total bytes produced by transform = %d", data->transform_bytes); TSVIONBytesSet(data->output_vio, data->transform_bytes); TSVIOReenable(data->output_vio); TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, input_vio); } return 1; }
/*------------------------------------------------------------------------- cont_data_destroy Deallocate ContData structure associated to a transaction Input: data structure to deallocate Output: Return Value: none -------------------------------------------------------------------------*/ static void cont_data_destroy(ContData * data) { TSDebug(DBG_TAG, "Destroying continuation data"); if (data) { TSAssert(data->magic == MAGIC_ALIVE); if (data->output_reader) { TSIOBufferReaderFree(data->output_reader); data->output_reader = NULL; } if (data->output_buffer) { TSIOBufferDestroy(data->output_buffer); data->output_buffer = NULL; } if (data->psi_reader) { TSIOBufferReaderFree(data->psi_reader); data->psi_reader = NULL; } if (data->psi_buffer) { TSIOBufferDestroy(data->psi_buffer); data->psi_buffer = NULL; } data->magic = MAGIC_DEAD; TSfree(data); } }
static int handle_output(TSCont contp, JCrusherData * data) { const char *output; int64_t written_bytes; /* Check to see if we need to initiate the output operation. */ TSDebug("jcrusher", "Start of handle_output()"); if (!data->downstream_vio) { TSVConn output_conn; /* Get the json_object as string and write it into buffer */ output = json_object_to_json_string_ext(data->json_obj, JSON_C_TO_STRING_PLAIN); written_bytes = TSIOBufferWrite(data->downstream_buffer, output, (int64_t)(strlen(output))); TSDebug("jcrusher", "handle_output - Just write %" PRId64 " bytes to ouput", written_bytes); /* Get the output connection where we'll write data to. */ output_conn = TSTransformOutputVConnGet(contp); data->downstream_vio = TSVConnWrite(output_conn, contp, data->downstream_reader, TSIOBufferReaderAvail(data->downstream_reader)); TSAssert(data->downstream_vio); } TSDebug("jcrusher", "End of handle_output()"); return 1; }
/* handle_hook * Fires on TS_EVENT_HTTP_READ_REQUEST_HDR events, gets the effectiveUrl * finds the host, gets the generation ID, gen_id, for the host * and runs TSCacheUrlSet to change the cache key for the read */ static int handle_hook(TSCont *contp, TSEvent event, void *edata) { TSHttpTxn txnp = (TSHttpTxn)edata; char *url = NULL, *host = NULL; int url_length; int gen_id; int ok = 1; switch (event) { case TS_EVENT_HTTP_READ_REQUEST_HDR: TSDebug(PLUGIN_NAME, "handling TS_EVENT_HTTP_READ_REQUEST_HDR"); if (ok) { url = TSHttpTxnEffectiveUrlStringGet(txnp, &url_length); if (!url) { TSError("[%s] could not retrieve request url", PLUGIN_NAME); ok = 0; } } if (ok) { get_genid_host(&host, url); if (!host) { TSError("[%s] could not retrieve request host", PLUGIN_NAME); ok = 0; } } if (ok) { TSDebug(PLUGIN_NAME, "From url (%s) discovered host (%s)", url, host); if ((gen_id = get_genid(host)) != 0) { if (TSHttpTxnConfigIntSet(txnp, TS_CONFIG_HTTP_CACHE_GENERATION, gen_id) != TS_SUCCESS) { TSDebug(PLUGIN_NAME, "Error, unable to modify cache url"); TSError("[%s] Unable to set cache generation for %s to %d", PLUGIN_NAME, url, gen_id); ok = 0; } } } /* Clean up */ if (url) { TSfree(url); } if (host) { TSfree(host); } TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); break; default: TSAssert(!"Unexpected event"); ok = 0; break; } return ok; }
static int handle_output(TSCont contp, MyData *data) { /* Check to see if we need to initiate the output operation. */ if (!data->output_vio) { TSVConn output_conn; /* Get the output connection where we'll write data to. */ output_conn = TSTransformOutputVConnGet(contp); data->output_vio = TSVConnWrite(output_conn, contp, data->output_reader, TSIOBufferReaderAvail(data->output_reader)); TSAssert(data->output_vio); } return 1; }
static int handle_hook(TSCont contp, TSEvent event, void *edata) { TSHttpTxn txnp = (TSHttpTxn) edata; pr_list *prl; int ok = 1; prl = (pr_list *)TSContDataGet(contp); switch (event) { case TS_EVENT_HTTP_READ_REQUEST_HDR: ok = rewrite_cacheurl(prl, txnp); TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); break; default: TSAssert(!"Unexpected event"); ok = 0; break; } return ok; }
/*------------------------------------------------------------------------- dump_psi Dump the psi_output to the downstream vconnection. Input: contp continuation for the current transaction Output : Return Value: 0 if failure 1 if success -------------------------------------------------------------------------*/ static int dump_psi(TSCont contp) { ContData *data; int psi_output_len; /* TODO: This is odd, do we need to get the input_vio, but never use it ?? */ #if 0 TSVIO input_vio; input_vio = TSVConnWriteVIOGet(contp); #endif data = TSContDataGet(contp); TSAssert(data->magic == MAGIC_ALIVE); /* If script exec succeded, copy its output to the downstream vconn */ if (data->psi_success == 1) { psi_output_len = TSIOBufferReaderAvail(data->psi_reader); if (psi_output_len > 0) { data->transform_bytes += psi_output_len; TSDebug(DBG_TAG, "Inserting %d bytes from include file", psi_output_len); /* TODO: Should we check the return value of TSIOBufferCopy() ? */ TSIOBufferCopy(TSVIOBufferGet(data->output_vio), data->psi_reader, psi_output_len, 0); /* Consume all the output data */ TSIOBufferReaderConsume(data->psi_reader, psi_output_len); /* Reenable the output connection so it can read the data we've produced. */ TSVIOReenable(data->output_vio); } } /* Change state to finish up reading upstream data */ data->state = STATE_READ_DATA; return 0; }
static int handle_buffering(TSCont contp, MyData *data) { TSVIO write_vio; int towrite; int avail; /* Get the write VIO for the write operation that was performed on ourself. This VIO contains the buffer that we are to read from as well as the continuation we are to call when the buffer is empty. */ write_vio = TSVConnWriteVIOGet(contp); /* Create the output buffer and its associated reader */ if (!data->output_buffer) { data->output_buffer = TSIOBufferCreate(); TSAssert(data->output_buffer); data->output_reader = TSIOBufferReaderAlloc(data->output_buffer); TSAssert(data->output_reader); } /* We also check to see if the write VIO's buffer is non-NULL. A NULL buffer indicates that the write operation has been shutdown and that the continuation does not want us to send any more WRITE_READY or WRITE_COMPLETE events. For this buffered transformation that means we're done buffering data. */ if (!TSVIOBufferGet(write_vio)) { data->state = STATE_OUTPUT_DATA; return 0; } /* Determine how much data we have left to read. For this bnull transform plugin this is also the amount of data we have left to write to the output connection. */ towrite = TSVIONTodoGet(write_vio); if (towrite > 0) { /* The amount of data left to read needs to be truncated by the amount of data actually in the read buffer. */ avail = TSIOBufferReaderAvail(TSVIOReaderGet(write_vio)); if (towrite > avail) { towrite = avail; } if (towrite > 0) { /* Copy the data from the read buffer to the input buffer. */ TSIOBufferCopy(data->output_buffer, TSVIOReaderGet(write_vio), towrite, 0); /* Tell the read buffer that we have read the data and are no longer interested in it. */ TSIOBufferReaderConsume(TSVIOReaderGet(write_vio), towrite); /* Modify the write VIO to reflect how much data we've completed. */ TSVIONDoneSet(write_vio, TSVIONDoneGet(write_vio) + towrite); } } /* Now we check the write VIO to see if there is data left to read. */ if (TSVIONTodoGet(write_vio) > 0) { if (towrite > 0) { /* Call back the write VIO continuation to let it know that we are ready for more data. */ TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_READY, write_vio); } } else { data->state = STATE_OUTPUT_DATA; /* Call back the write VIO continuation to let it know that we have completed the write operation. */ TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_COMPLETE, write_vio); } return 1; }
/*------------------------------------------------------------------------- transform_handler Handler for all events received during the transformation process Input: contp continuation for the current transaction event event received data pointer on optional data Output : Return Value: -------------------------------------------------------------------------*/ static int transform_handler(TSCont contp, TSEvent event, void *edata) { TSVIO input_vio; ContData *data; int state, retval; /* This section will be called by both TS internal and the thread. Protect it with a mutex to avoid concurrent calls. */ /* Handle TryLock result */ if (TSMutexLockTry(TSContMutexGet(contp)) != TS_SUCCESS) { TSCont c = TSContCreate(trylock_handler, NULL); TryLockData *d = TSmalloc(sizeof(TryLockData)); d->contp = contp; d->event = event; TSContDataSet(c, d); TSContSchedule(c, 10, TS_THREAD_POOL_DEFAULT); return 1; } data = TSContDataGet(contp); TSAssert(data->magic == MAGIC_ALIVE); state = data->state; /* Check to see if the transformation has been closed */ retval = TSVConnClosedGet(contp); if (retval) { /* If the thread is still executing its job, we don't want to destroy the continuation right away as the thread will call us back on this continuation. */ if (state == STATE_READ_PSI) { TSContSchedule(contp, 10, TS_THREAD_POOL_DEFAULT); } else { TSMutexUnlock(TSContMutexGet(contp)); cont_data_destroy(TSContDataGet(contp)); TSContDestroy(contp); return 1; } } else { switch (event) { case TS_EVENT_ERROR: input_vio = TSVConnWriteVIOGet(contp); TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio); break; case TS_EVENT_VCONN_WRITE_COMPLETE: TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1); break; case TS_EVENT_VCONN_WRITE_READY: /* downstream vconnection is done reading data we've write into it. let's read some more data from upstream if we're in read state. */ if (state == STATE_READ_DATA) { handle_transform(contp); } break; case TS_EVENT_IMMEDIATE: if (state == STATE_READ_DATA) { /* upstream vconnection signals some more data ready to be read let's try to transform some more data */ handle_transform(contp); } else if (state == STATE_DUMP_PSI) { /* The thread scheduled an event on our continuation to let us know it has completed its job Let's dump the include content to the output vconnection */ dump_psi(contp); wake_up_streams(contp); } break; default: TSAssert(!"Unexpected event"); break; } } TSMutexUnlock(TSContMutexGet(contp)); return 1; }
/*------------------------------------------------------------------------- handle_transform Get data from upstream vconn. Parse it. Include file if include tags found. Copy data to downstream vconn. Wake up upstream to get more data. Input: contp continuation for the current transaction Output : Return Value: 0 if failure 1 if success -------------------------------------------------------------------------*/ static int handle_transform(TSCont contp) { TSVConn output_conn; TSVIO input_vio; ContData *data; TSIOBufferReader input_reader; int toread, avail, psi, toconsume, towrite; /* Get the output (downstream) vconnection where we'll write data to. */ output_conn = TSTransformOutputVConnGet(contp); /* Get upstream vio */ input_vio = TSVConnWriteVIOGet(contp); data = TSContDataGet(contp); TSAssert(data->magic == MAGIC_ALIVE); if (!data->output_buffer) { data->output_buffer = TSIOBufferCreate(); data->output_reader = TSIOBufferReaderAlloc(data->output_buffer); /* INT64_MAX because we don't know yet how much bytes we'll produce */ data->output_vio = TSVConnWrite(output_conn, contp, data->output_reader, INT64_MAX); } /* If the input VIO's buffer is NULL, the transformation is over */ if (!TSVIOBufferGet(input_vio)) { TSDebug(DBG_TAG, "input_vio NULL, terminating transformation"); TSVIONBytesSet(data->output_vio, data->transform_bytes); TSVIOReenable(data->output_vio); return 1; } /* Determine how much data we have left to read. */ toread = TSVIONTodoGet(input_vio); if (toread > 0) { input_reader = TSVIOReaderGet(input_vio); avail = TSIOBufferReaderAvail(input_reader); /* There are some data available for reading. Let's parse it */ if (avail > 0) { /* No need to parse data if there are too few bytes left to contain an include command... */ if (toread > (PSI_START_TAG_LEN + PSI_END_TAG_LEN)) { psi = parse_data(contp, input_reader, avail, &toconsume, &towrite); } else { towrite = avail; toconsume = avail; psi = 0; } if (towrite > 0) { /* Update the total size of the doc so far */ data->transform_bytes += towrite; /* Copy the data from the read buffer to the output buffer. */ /* TODO: Should we check the return value of TSIOBufferCopy() ? */ TSIOBufferCopy(TSVIOBufferGet(data->output_vio), TSVIOReaderGet(input_vio), towrite, 0); /* Reenable the output connection so it can read the data we've produced. */ TSVIOReenable(data->output_vio); } if (toconsume > 0) { /* Consume data we've processed an we are no longer interested in */ TSIOBufferReaderConsume(input_reader, toconsume); /* Modify the input VIO to reflect how much data we've completed. */ TSVIONDoneSet(input_vio, TSVIONDoneGet(input_vio) + toconsume); } /* Did we find a psi filename to execute in the data ? */ if (psi) { Job *new_job; /* Add a request to include a file into the jobs queue.. */ /* We'll be called back once it's done with an EVENT_IMMEDIATE */ TSDebug(DBG_TAG, "Psi filename extracted. Adding an include job to thread queue."); data->state = STATE_READ_PSI; /* Create a new job request and add it to the queue */ new_job = job_create(contp, &psi_include, NULL); add_to_queue(&job_queue, new_job); /* Signal to the threads there is a new job */ thread_signal_job(); return 1; } } } /* Wake up upstream and downstream vconnections */ wake_up_streams(contp); return 1; }
/*------------------------------------------------------------------------- psi_include Read file to include. Copy its content into an iobuffer. This is the function doing blocking calls and called by the plugin's threads Input: data continuation for the current transaction Output : data->psi_buffer contains the file content data->psi_sucess 0 if include failed, 1 if success Return Value: 0 if failure 1 if success -------------------------------------------------------------------------*/ static int psi_include(TSCont contp, void *edata) { #define BUFFER_SIZE 1024 ContData *data; TSFile filep; char buf[BUFFER_SIZE]; char inc_file[PSI_PATH_MAX_SIZE + PSI_FILENAME_MAX_SIZE]; /* We manipulate plugin continuation data from a separate thread. Grab mutex to avoid concurrent access */ TSMutexLock(TSContMutexGet(contp)); data = TSContDataGet(contp); TSAssert(data->magic == MAGIC_ALIVE); if (!data->psi_buffer) { data->psi_buffer = TSIOBufferCreate(); data->psi_reader = TSIOBufferReaderAlloc(data->psi_buffer); } /* For security reason, we do not allow to include files that are not in the directory <plugin_path>/include. Also include file cannot contain any path. */ sprintf(inc_file, "%s/%s", psi_directory, _basename(data->psi_filename)); /* Read the include file and copy content into iobuffer */ if ((filep = TSfopen(inc_file, "r")) != NULL) { TSDebug(DBG_TAG, "Reading include file %s", inc_file); while (TSfgets(filep, buf, BUFFER_SIZE) != NULL) { TSIOBufferBlock block; int64_t len, avail, ndone, ntodo, towrite; char *ptr_block; len = strlen(buf); ndone = 0; ntodo = len; while (ntodo > 0) { /* TSIOBufferStart allocates more blocks if required */ block = TSIOBufferStart(data->psi_buffer); ptr_block = TSIOBufferBlockWriteStart(block, &avail); towrite = MIN(ntodo, avail); memcpy(ptr_block, buf + ndone, towrite); TSIOBufferProduce(data->psi_buffer, towrite); ntodo -= towrite; ndone += towrite; } } TSfclose(filep); data->psi_success = 1; if (log) { TSTextLogObjectWrite(log, "Successfully included file: %s", inc_file); } } else { data->psi_success = 0; if (log) { TSTextLogObjectWrite(log, "Failed to include file: %s", inc_file); } } /* Change state and schedule an event EVENT_IMMEDIATE on the plugin continuation to let it know we're done. */ /* Note: if the blocking call was not in the transformation state (i.e. in TS_HTTP_READ_REQUEST_HDR, TS_HTTP_OS_DNS and so on...) we could use TSHttpTxnReenable to wake up the transaction instead of sending an event. */ TSContSchedule(contp, 0, TS_THREAD_POOL_DEFAULT); data->psi_success = 0; data->state = STATE_READ_DATA; TSMutexUnlock(TSContMutexGet(contp)); return 0; }
/*------------------------------------------------------------------------- parse_data Search for psi filename in the data. Input: contp continuation for the current transaction reader reader on the iobuffer that contains data avail amount of data available in the iobuffer Output: towrite amount of data in the iobuffer that can be written to the downstream vconnection toconsume amount of data in the iobuffer to consume Return Value: 0 if no psi filename found 1 if a psi filename was found -------------------------------------------------------------------------*/ static int parse_data(TSCont contp, TSIOBufferReader input_reader, int avail, int *toconsume, int *towrite) { ContData *data; int nparse = 0; int status; data = TSContDataGet(contp); TSAssert(data->magic == MAGIC_ALIVE); if (data->parse_state == PARSE_SEARCH) { /* Search for the start pattern */ status = strsearch_ioreader(input_reader, PSI_START_TAG, &nparse); switch (status) { case STR_FAIL: /* We didn't found the pattern */ *toconsume = avail; *towrite = avail; data->parse_state = PARSE_SEARCH; return 0; case STR_PARTIAL: /* We need to read some more data */ *toconsume = nparse; *towrite = nparse; data->parse_state = PARSE_SEARCH; return 0; case STR_SUCCESS: /* We found the start_pattern, let's go ahead */ data->psi_filename_len = 0; data->psi_filename[0] = '\0'; data->parse_state = PARSE_EXTRACT; break; default: TSAssert(!"strsearch_ioreader returned unexpected status"); } } /* And now let's extract the filename */ status = strextract_ioreader(input_reader, nparse + PSI_START_TAG_LEN, PSI_END_TAG, data->psi_filename, &data->psi_filename_len); switch (status) { case STR_FAIL: /* We couldn't extract a valid filename */ *toconsume = nparse; *towrite = nparse; data->parse_state = PARSE_SEARCH; return 0; case STR_PARTIAL: /* We need to read some more data */ *toconsume = nparse; *towrite = nparse; data->parse_state = PARSE_EXTRACT; return 0; case STR_SUCCESS: /* We got a valid filename */ *toconsume = nparse + PSI_START_TAG_LEN + data->psi_filename_len + PSI_END_TAG_LEN; *towrite = nparse; data->parse_state = PARSE_SEARCH; return 1; default: TSAssert(!"strextract_ioreader returned bad status"); } return 0; }
static int handle_buffering(TSCont contp, JCrusherData * data) { TSVIO upstream_vio; TSIOBuffer upstream_buffer; int64_t toread; int64_t avail; TSDebug("jcrusher", "Start of handle_buffering()"); /* Get the write VIO for the write operation that was performed on ourself. This VIO contains the buffer that we are to read from as well as the continuation we are to call when the buffer is empty. */ upstream_vio = TSVConnWriteVIOGet(contp); /* Create the output buffer and its associated reader */ if (!data->downstream_buffer) { data->downstream_buffer = TSIOBufferCreate(); TSAssert(data->downstream_buffer); data->downstream_reader = TSIOBufferReaderAlloc(data->downstream_buffer); TSAssert(data->downstream_reader); } /* We also check to see if the write VIO's buffer is non-NULL. A NULL buffer indicates that the write operation has been shutdown and that the continuation does not want us to send any more WRITE_READY or WRITE_COMPLETE events. For this buffered transformation that means we're done buffering data. */ upstream_buffer = TSVIOBufferGet(upstream_vio); if (NULL == upstream_buffer) { data->state = STATE_OUTPUT_DATA; TSDebug("jcrusher", "handle_buffering - upstream_buffer is NULL"); return 0; } /* Determine how much data we have left to read. For this bnull transform plugin this is also the amount of data we have left to write to the output connection. */ toread = TSVIONTodoGet(upstream_vio); TSDebug("jcrusher", "handle_buffering - toread is %" PRId64, toread); if (toread > 0) { /* The amount of data left to read needs to be truncated by the amount of data actually in the read buffer. */ avail = TSIOBufferReaderAvail(TSVIOReaderGet(upstream_vio)); if (toread > avail) { toread = avail; } TSDebug("jcrusher", "handle_buffering - toread is %" PRId64, toread); TSDebug("jcrusher", "handle_buffering - avail is %" PRId64, avail); TSIOBufferReader upstream_reader = TSVIOReaderGet(upstream_vio); TSIOBufferBlock upstream_blk = TSIOBufferReaderStart(upstream_reader); const char *input = TSIOBufferBlockReadStart(upstream_blk, upstream_reader, &toread); TSDebug("jcrusher", "handle_buffering - just read [%d] bytes from buffer", (int)strlen(input)); TSDebug("jcrusher", "handle_buffering - parse json input"); data->json_obj = json_tokener_parse_ex(data->json_tok, input, strlen(input)); if (json_tokener_success == (data->json_err = json_tokener_get_error(data->json_tok))) { TSDebug("jcrusher", "handle_buffering - got json_tokener_success"); data->state = STATE_OUTPUT_DATA; /* Call back the write VIO continuation to let it know that we have completed the write operation. */ TSContCall(TSVIOContGet(upstream_vio), TS_EVENT_VCONN_WRITE_COMPLETE, upstream_vio); return 1; } TSDebug("jcrusher", "handle_buffering - got json_tokener_continue"); /* Tell the read buffer that we have read the data and are no longer interested in it. */ TSIOBufferReaderConsume(TSVIOReaderGet(upstream_vio), toread); /* Modify the upstream VIO to reflect how much data we've completed. */ TSVIONDoneSet(upstream_vio, TSVIONDoneGet(upstream_vio) + toread); /* Now we check the upstream VIO to see if there is data left to read. */ /* Call back the upstream VIO continuation to let it know that we are ready for more data. */ TSContCall(TSVIOContGet(upstream_vio), TS_EVENT_VCONN_WRITE_READY, upstream_vio); } else { TSDebug("jcrusher", "handle_buffering - seems we read all"); data->state = STATE_OUTPUT_DATA; /* Call back the write VIO continuation to let it know that we have completed the write operation. */ TSContCall(TSVIOContGet(upstream_vio), TS_EVENT_VCONN_WRITE_COMPLETE, upstream_vio); } TSDebug("jcrusher", "handle_buffering - End"); return 1; }