/** * @file NCDUdevMonitorParser.c * @author Ambroz Bizjak * * @section LICENSE * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #define PROPERTY_REGEX "^([^=]+)=(.*)$" static uint8_t * find_end (uint8_t *buf, size_t len) { while (len >= 2) { if (buf[0] == '\n' && buf[1] == '\n') { return (buf + 2); } buf++; len--; } return NULL; } static int parse_property (NCDUdevMonitorParser *o, char *data) { ASSERT(o->ready_num_properties >= 0) ASSERT(o->ready_num_properties <= o->max_properties) if (o->ready_num_properties == o->max_properties) { BLog(BLOG_ERROR, "too many properties"); return 0; } struct NCDUdevMonitorParser_property *prop = &o->ready_properties[o->ready_num_properties]; // execute property regex regmatch_t matches[3]; if (regexec(&o->property_regex, data, 3, matches, 0) != 0) { BLog(BLOG_ERROR, "failed to parse property"); return 0; } // extract components prop->name = data + matches[1].rm_so; *(data + matches[1].rm_eo) = '\0'; prop->value = data + matches[2].rm_so; *(data + matches[2].rm_eo) = '\0'; // register property o->ready_num_properties++; return 1; } static int parse_message (NCDUdevMonitorParser *o) { ASSERT(!o->is_ready) ASSERT(o->ready_len >= 2) ASSERT(o->buf[o->ready_len - 2] == '\n') ASSERT(o->buf[o->ready_len - 1] == '\n') // zero terminate message (replacing the second newline) o->buf[o->ready_len - 1] = '\0'; // start parsing char *line = (char *)o->buf; int first_line = 1; // set is not ready event o->ready_is_ready_event = 0; // init properties o->ready_num_properties = 0; do { // find end of line char *line_end = strchr(line, '\n'); ASSERT(line_end) // zero terminate line *line_end = '\0'; if (o->is_info_mode) { // ignore W: entries with missing space if (string_begins_with(line, "W:")) { goto nextline; } // parse prefix if (strlen(line) < 3 || line[1] != ':' || line[2] != ' ') { BLog(BLOG_ERROR, "failed to parse head"); return 0; } char line_type = line[0]; char *line_value = line + 3; if (first_line) { if (line_type != 'P') { BLog(BLOG_ERROR, "wrong first line type"); return 0; } } else { if (line_type == 'E') { if (!parse_property(o, line_value)) { return 0; } } } } else { if (first_line) { // is this the initial informational message? if (string_begins_with(line, "monitor")) { o->ready_is_ready_event = 1; break; } // check first line if (!string_begins_with(line, "UDEV ") && !string_begins_with(line, "KERNEL")) { BLog(BLOG_ERROR, "failed to parse head"); return 0; } } else { if (!parse_property(o, line)) { return 0; } } } nextline: first_line = 0; line = line_end + 1; } while (*line); // set ready o->is_ready = 1; return 1; } static void process_data (NCDUdevMonitorParser *o) { ASSERT(!o->is_ready) while (1) { // look for end of event uint8_t *c = find_end(o->buf, o->buf_used); if (!c) { // check for out of buffer condition if (o->buf_used == o->buf_size) { BLog(BLOG_ERROR, "out of buffer"); o->buf_used = 0; } // receive more data StreamRecvInterface_Receiver_Recv(o->input, o->buf + o->buf_used, o->buf_size - o->buf_used); return; } // remember message length o->ready_len = c - o->buf; // parse message if (parse_message(o)) { break; } // shift buffer memmove(o->buf, o->buf + o->ready_len, o->buf_used - o->ready_len); o->buf_used -= o->ready_len; } // call handler o->handler(o->user); return; } static void input_handler_done (NCDUdevMonitorParser *o, int data_len) { DebugObject_Access(&o->d_obj); ASSERT(!o->is_ready) ASSERT(data_len > 0) ASSERT(data_len <= o->buf_size - o->buf_used) // increment buffer position o->buf_used += data_len; // process data process_data(o); return; } static void done_job_handler (NCDUdevMonitorParser *o) { DebugObject_Access(&o->d_obj); ASSERT(o->is_ready) // shift buffer memmove(o->buf, o->buf + o->ready_len, o->buf_used - o->ready_len); o->buf_used -= o->ready_len; // set not ready o->is_ready = 0; // process data process_data(o); return; } int NCDUdevMonitorParser_Init (NCDUdevMonitorParser *o, StreamRecvInterface *input, int buf_size, int max_properties, int is_info_mode, BPendingGroup *pg, void *user, NCDUdevMonitorParser_handler handler) { ASSERT(buf_size > 0) ASSERT(max_properties >= 0) ASSERT(is_info_mode == 0 || is_info_mode == 1) // init arguments o->input = input; o->buf_size = buf_size; o->max_properties = max_properties; o->is_info_mode = is_info_mode; o->user = user; o->handler = handler; // init input StreamRecvInterface_Receiver_Init(o->input, (StreamRecvInterface_handler_done)input_handler_done, o); // init property regex if (regcomp(&o->property_regex, PROPERTY_REGEX, REG_EXTENDED) != 0) { BLog(BLOG_ERROR, "regcomp failed"); goto fail1; } // init done job BPending_Init(&o->done_job, pg, (BPending_handler)done_job_handler, o); // allocate buffer if (!(o->buf = malloc(o->buf_size))) { BLog(BLOG_ERROR, "malloc failed"); goto fail2; } // set buffer position o->buf_used = 0; // set not ready o->is_ready = 0; // allocate properties if (!(o->ready_properties = BAllocArray(o->max_properties, sizeof(o->ready_properties[0])))) { BLog(BLOG_ERROR, "BAllocArray failed"); goto fail3; } // start receiving StreamRecvInterface_Receiver_Recv(o->input, o->buf, o->buf_size); DebugObject_Init(&o->d_obj); return 1; fail3: free(o->buf); fail2: BPending_Free(&o->done_job); regfree(&o->property_regex); fail1: return 0; } void NCDUdevMonitorParser_Free (NCDUdevMonitorParser *o) { DebugObject_Free(&o->d_obj); // free properties BFree(o->ready_properties); // free buffer free(o->buf); // free done job BPending_Free(&o->done_job); // free property regex regfree(&o->property_regex); } void NCDUdevMonitorParser_AssertReady (NCDUdevMonitorParser *o) { DebugObject_Access(&o->d_obj); ASSERT(o->is_ready) } void NCDUdevMonitorParser_Done (NCDUdevMonitorParser *o) { DebugObject_Access(&o->d_obj); ASSERT(o->is_ready) // schedule done job BPending_Set(&o->done_job); } int NCDUdevMonitorParser_IsReadyEvent (NCDUdevMonitorParser *o) { DebugObject_Access(&o->d_obj); ASSERT(o->is_ready) return o->ready_is_ready_event; } int NCDUdevMonitorParser_GetNumProperties (NCDUdevMonitorParser *o) { DebugObject_Access(&o->d_obj); ASSERT(o->is_ready) return o->ready_num_properties; } void NCDUdevMonitorParser_GetProperty (NCDUdevMonitorParser *o, int index, const char **name, const char **value) { DebugObject_Access(&o->d_obj); ASSERT(o->is_ready) ASSERT(index >= 0) ASSERT(index < o->ready_num_properties) *name = o->ready_properties[index].name; *value = o->ready_properties[index].value; }