| /* |
| * Routines and data structures common to all BASIL versions |
| * |
| * Copyright (c) 2009-2011 Centro Svizzero di Calcolo Scientifico (CSCS) |
| * Licensed under the GPLv2. |
| */ |
| #include "parser_internal.h" |
| #include "../parser_common.h" |
| |
| const char *bv_names[BV_MAX]; |
| const char *bv_names_long[BV_MAX]; |
| const char *bm_names[BM_MAX]; |
| const char *be_names[BE_MAX]; |
| |
| const char *nam_arch[BNA_MAX]; |
| const char *nam_memtype[BMT_MAX]; |
| const char *nam_labeltype[BLT_MAX]; |
| const char *nam_ldisp[BLD_MAX]; |
| |
| const char *nam_noderole[BNR_MAX]; |
| const char *nam_nodestate[BNS_MAX]; |
| const char *nam_proc[BPT_MAX]; |
| const char *nam_rsvn_mode[BRM_MAX]; |
| const char *nam_gpc_mode[BGM_MAX]; |
| |
| const char *nam_gpc_mode[BGM_MAX]; |
| const char *nam_rsvn_mode[BRM_MAX]; |
| |
| const char *nam_acceltype[BA_MAX]; |
| const char *nam_accelstate[BAS_MAX]; |
| |
| /* |
| * General-purpose routines |
| */ |
| /** Decode (negative) error code following a Basil response. */ |
| const char *basil_strerror(int rc) |
| { |
| return be_names_long[decode_basil_error(rc)]; |
| } |
| |
| /* |
| * Overwrite @reqc attribute keys supplied in @reqv with corresponding |
| * attribute value from @attr_list. |
| */ |
| void extract_attributes(const XML_Char **attr_list, char **reqv, int reqc) |
| { |
| const XML_Char **attr, *val; |
| |
| while (--reqc >= 0) { |
| for (attr = attr_list, val = NULL; *attr; attr += 2) |
| if (strcmp(reqv[reqc], *attr) == 0) { |
| if (val != NULL) |
| fatal("multiple '%s' occurrences", |
| *attr); |
| val = attr[1]; |
| } |
| if (val == NULL) |
| fatal("unspecified '%s' attribute", reqv[reqc]); |
| reqv[reqc] = (XML_Char *)val; |
| } |
| } |
| |
| /* |
| * XML Handlers |
| */ |
| |
| /** Generic 'Message' element */ |
| void eh_message(struct ud *ud, const XML_Char **attrs) |
| { |
| char *attribs[] = { "severity" }; |
| |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| |
| /* Message appears within ResponseData, which may set ud->error */ |
| if (ud->error == BE_NONE) |
| snprintf(ud->bp->msg, sizeof(ud->bp->msg), "%s: ", attribs[0]); |
| } |
| |
| /** Generic 'BasilResponse' element */ |
| void eh_response(struct ud *ud, const XML_Char **attrs) |
| { |
| char *attribs[] = { "protocol" }; |
| |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| /* |
| * When the method call failed (ResponseData with status="FAILURE"), |
| * it can happen that ALPS sets the 'protocol' to the empty string (""). |
| */ |
| if (*attribs[0] && strcmp(attribs[0], bv_names[ud->bp->version]) != 0) |
| fatal("Version mismatch: expected %s, but got %s", |
| bv_names[ud->bp->version], attribs[0]); |
| } |
| |
| /** Generic 'ResponseData' element */ |
| void eh_resp_data(struct ud *ud, const XML_Char **attrs) |
| { |
| char *attr_std[] = { "method", "status" }; |
| |
| extract_attributes(attrs, attr_std, ARRAY_SIZE(attr_std)); |
| |
| if (strcmp(attr_std[1], "SUCCESS") == 0) { |
| ud->error = BE_NONE; |
| /* |
| * When the method call failed, ALPS in some cases sets the |
| * 'method' to "UNDEFINED", hence verify this on success only. |
| */ |
| if (strcmp(attr_std[0], bm_names[ud->bp->method]) != 0) |
| fatal("method mismatch in=%s, out=%s", |
| bm_names[ud->bp->method], attr_std[0]); |
| } else { |
| char *attr_err[] = { "error_source", "error_class" }; |
| |
| extract_attributes(attrs, attr_err, ARRAY_SIZE(attr_err)); |
| |
| for (ud->error = BE_INTERNAL; |
| ud->error < BE_UNKNOWN; ud->error++) |
| if (strcmp(attr_err[0], be_names[ud->error]) == 0) |
| break; |
| |
| snprintf(ud->bp->msg, sizeof(ud->bp->msg), "%s ALPS %s error: ", |
| attr_err[1], be_names[ud->error]); |
| |
| if (strcmp(attr_err[1], "TRANSIENT") == 0) |
| ud->error |= BE_TRANSIENT; |
| } |
| } |
| |
| /** Basil 1.0/1.1/3.1 'Reserved' element */ |
| void eh_reserved(struct ud *ud, const XML_Char **attrs) |
| { |
| char *attribs[] = { "reservation_id" }; |
| /* |
| * The Catamount 'admin_cookie' and 'alloc_cookie' attributes |
| * have been deprecated starting from Basil 1.1. |
| */ |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| |
| if (atou32(attribs[0], &ud->bp->mdata.res->rsvn_id) < 0) |
| fatal("illegal reservation_id = %s", attribs[0]); |
| |
| ud->counter[BT_RESVDNODEARRAY] = 0; /* Basil 3.1 */ |
| } |
| |
| /** Basil 1.0/1.1 'Engine' element */ |
| void eh_engine(struct ud *ud, const XML_Char **attrs) |
| { |
| char *attribs[] = { "name", "version" }; |
| /* |
| * Basil 3.1 has an additional attribute 'basil_support' which |
| * contains a comma-separated list of supported Basil versions. |
| */ |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| |
| if (strcmp(attribs[0], "ALPS") != 0) |
| fatal("unknown engine name '%s'", attribs[0]); |
| strncpy(ud->bp->msg, attribs[1], sizeof(ud->bp->msg)); |
| } |
| |
| /** Basil 1.0/1.1 'Node' element */ |
| void eh_node(struct ud *ud, const XML_Char **attrs) |
| { |
| struct basil_node node = {0}; |
| char *attribs[] = { "node_id", "name", "architecture", |
| "role", "state" }; |
| /* |
| * Basil 3.1 in addition has a 'router_id' attribute. |
| */ |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| |
| if (atou32(attribs[0], &node.node_id) < 0) |
| fatal("illegal node_id = %s", attribs[0]); |
| |
| strncpy(node.name, attribs[1], sizeof(node.name)); |
| |
| for (node.arch = BNA_X2; node.arch < BNA_MAX; node.arch++) |
| if (strcmp(attribs[2], nam_arch[node.arch]) == 0) |
| break; |
| |
| for (node.role = BNR_INTER; node.role < BNR_MAX; node.role++) |
| if (strcmp(attribs[3], nam_noderole[node.role]) == 0) |
| break; |
| |
| for (node.state = BNS_UP; node.state < BNS_MAX; node.state++) |
| if (strcmp(attribs[4], nam_nodestate[node.state]) == 0) |
| break; |
| |
| ud->current_node.available = node.arch == BNA_XT && |
| node.role == BNR_BATCH && |
| node.state == BNS_UP; |
| ud->current_node.reserved = false; |
| |
| if (ud->ud_inventory) { |
| struct basil_node *new = xmalloc(sizeof(*new)); |
| |
| *new = node; |
| if (ud->ud_inventory->node_head) |
| new->next = ud->ud_inventory->node_head; |
| ud->ud_inventory->node_head = new; |
| } |
| |
| ud->counter[BT_SEGMARRAY] = 0; |
| ud->counter[BT_ACCELARRAY] = 0; |
| |
| /* Cover up Basil version differences by faking a segment. */ |
| if (ud->bp->version < BV_1_1) |
| eh_segment(ud, NULL); |
| } |
| |
| /** Basil 1.1/3.1 'Segment' element */ |
| void eh_segment(struct ud *ud, const XML_Char **attrs) |
| { |
| uint32_t ordinal = 0; |
| char *attribs[] = { "ordinal" }; |
| |
| if (attrs) { |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| if (atou32(attribs[0], &ordinal) < 0) |
| fatal("illegal segment ordinal = %s", attribs[0]); |
| } |
| |
| if (ud->ud_inventory) { |
| struct basil_segment *new = xmalloc(sizeof(*new)); |
| |
| new->ordinal = ordinal; |
| xassert(ud->ud_inventory->node_head); |
| |
| if (ud->ud_inventory->node_head->seg_head) |
| new->next = ud->ud_inventory->node_head->seg_head; |
| ud->ud_inventory->node_head->seg_head = new; |
| } |
| |
| ud->counter[BT_PROCARRAY] = 0; |
| ud->counter[BT_MEMARRAY] = 0; |
| ud->counter[BT_LABELARRAY] = 0; |
| } |
| |
| /** Generic 'Processor' element */ |
| void eh_proc(struct ud *ud, const XML_Char **attrs) |
| { |
| struct basil_node_processor proc = {0}; |
| char *attribs[] = { "ordinal", "architecture", "clock_mhz" }; |
| |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| |
| if (atou32(attribs[0], &proc.ordinal) < 0) |
| fatal("illegal ordinal = %s", attribs[0]); |
| |
| for (proc.arch = BPT_X86_64; proc.arch < BPT_MAX; proc.arch++) |
| if (strcmp(attribs[1], nam_proc[proc.arch]) == 0) |
| break; |
| |
| if (atou32(attribs[2], &proc.clock_mhz) < 0) |
| fatal("illegal clock_mhz = %s", attribs[2]); |
| |
| if (ud->ud_inventory) { |
| struct basil_node_processor *new = xmalloc(sizeof(*new)); |
| |
| *new = proc; |
| xassert(ud->ud_inventory->node_head); |
| xassert(ud->ud_inventory->node_head->seg_head); |
| |
| if (ud->ud_inventory->node_head->seg_head->proc_head) |
| new->next = ud->ud_inventory->node_head-> |
| seg_head->proc_head; |
| ud->ud_inventory->node_head->seg_head->proc_head = new; |
| } |
| } |
| |
| /** Generic 'ProcessorAllocation' element */ |
| void eh_proc_alloc(struct ud *ud, const XML_Char **attrs) |
| { |
| uint32_t rsvn_id; |
| char *attribs[] = { "reservation_id" }; |
| |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| |
| if (atou32(attribs[0], &rsvn_id) < 0) |
| fatal("illegal reservation_id = %s", attribs[0]); |
| |
| /* A node is "reserved" if at has at least one allocation */ |
| ud->current_node.reserved = true; |
| |
| if (ud->ud_inventory) { |
| xassert(ud->ud_inventory->node_head); |
| xassert(ud->ud_inventory->node_head->seg_head); |
| xassert(ud->ud_inventory->node_head->seg_head->proc_head); |
| |
| ud->ud_inventory->node_head->seg_head->proc_head->rsvn_id = |
| rsvn_id; |
| } |
| } |
| |
| /** Generic 'Memory' element */ |
| void eh_mem(struct ud *ud, const XML_Char **attrs) |
| { |
| struct basil_node_memory memory = {0}; |
| char *attribs[] = { "type", "page_size_kb", "page_count" }; |
| |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| |
| for (memory.type = BMT_OS; memory.type < BMT_MAX; memory.type++) |
| if (strcmp(attribs[0], nam_memtype[memory.type]) == 0) |
| break; |
| |
| if (atou32(attribs[1], &memory.page_size_kb) < 0 || |
| memory.page_size_kb < 1) |
| fatal("illegal page_size_kb = %s", attribs[1]); |
| |
| if (atou32(attribs[2], &memory.page_count) < 0 || |
| memory.page_count < 1) |
| fatal("illegal page_count = %s", attribs[2]); |
| |
| if (ud->ud_inventory) { |
| struct basil_node_memory *new = xmalloc(sizeof(*new)); |
| |
| *new = memory; |
| xassert(ud->ud_inventory->node_head); |
| xassert(ud->ud_inventory->node_head->seg_head); |
| |
| if (ud->ud_inventory->node_head->seg_head->mem_head) |
| new->next = ud->ud_inventory->node_head-> |
| seg_head->mem_head; |
| ud->ud_inventory->node_head->seg_head->mem_head = new; |
| } |
| } |
| |
| /** Generic 'MemoryAllocation' element */ |
| void eh_mem_alloc(struct ud *ud, const XML_Char **attrs) |
| { |
| struct basil_mem_alloc memalloc = {0}; |
| char *attribs[] = { "reservation_id", "page_count" }; |
| |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| |
| if (atou32(attribs[0], &memalloc.rsvn_id) < 0) |
| fatal("illegal reservation_id = %s", attribs[0]); |
| |
| if (atou32(attribs[1], &memalloc.page_count) < 0) |
| fatal("illegal page_count = %s", attribs[1]); |
| |
| ud->current_node.reserved = true; |
| |
| if (ud->ud_inventory) { |
| struct basil_mem_alloc *new = xmalloc(sizeof(*new)); |
| |
| *new = memalloc; |
| xassert(ud->ud_inventory->node_head); |
| xassert(ud->ud_inventory->node_head->seg_head); |
| xassert(ud->ud_inventory->node_head->seg_head->mem_head); |
| |
| if (ud->ud_inventory->node_head->seg_head->mem_head->a_head) |
| new->next = ud->ud_inventory->node_head-> |
| seg_head->mem_head->a_head; |
| ud->ud_inventory->node_head->seg_head->mem_head->a_head = new; |
| } |
| } |
| |
| /** Generic 'Label' element */ |
| void eh_label(struct ud *ud, const XML_Char **attrs) |
| { |
| struct basil_label label = {0}; |
| char *attribs[] = { "name", "type", "disposition" }; |
| |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| |
| strncpy(label.name, attribs[0], sizeof(label.name)); |
| |
| for (label.type = BLT_HARD; label.type < BLT_MAX; label.type++) |
| if (strcmp(attribs[1], nam_labeltype[label.type]) == 0) |
| break; |
| |
| for (label.disp = BLD_ATTRACT; label.disp < BLD_MAX; label.disp++) |
| if (strcmp(attribs[2], nam_ldisp[label.disp]) == 0) |
| break; |
| |
| if (ud->ud_inventory) { |
| struct basil_label *new = xmalloc(sizeof(*new)); |
| |
| *new = label; |
| xassert(ud->ud_inventory->node_head); |
| xassert(ud->ud_inventory->node_head->seg_head); |
| |
| if (ud->ud_inventory->node_head->seg_head->lbl_head) |
| new->next = ud->ud_inventory->node_head-> |
| seg_head->lbl_head; |
| ud->ud_inventory->node_head->seg_head->lbl_head = new; |
| } |
| } |
| |
| /** Basil 1.0 'Reservation' element (1.1 and 3.1 have additional attributes). */ |
| void eh_resv(struct ud *ud, const XML_Char **attrs) |
| { |
| uint32_t rsvn_id; |
| char *attribs[] = { "reservation_id", "user_name", "account_name" }; |
| |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| |
| if (atou32(attribs[0], &rsvn_id) < 0) |
| fatal("illegal reservation_id '%s'", attribs[0]); |
| |
| if (ud->ud_inventory) { |
| struct basil_rsvn *new = xmalloc(sizeof(*new)); |
| |
| new->rsvn_id = rsvn_id; |
| strncpy(new->user_name, attribs[1], sizeof(new->user_name)); |
| strncpy(new->account_name, attribs[2], |
| sizeof(new->account_name)); |
| |
| if (ud->ud_inventory->rsvn_head) |
| new->next = ud->ud_inventory->rsvn_head; |
| ud->ud_inventory->rsvn_head = new; |
| } |
| |
| ud->counter[BT_APPARRAY] = 0; /* Basil 3.1 */ |
| } |
| |
| /** Basil 1.1/3.1 'Application' element */ |
| void eh_application(struct ud *ud, const XML_Char **attrs) |
| { |
| char *attribs[] = { "application_id", "user_id", "group_id", |
| "time_stamp" }; |
| |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| |
| if (ud->ud_inventory) { |
| struct basil_rsvn_app *new = xmalloc(sizeof(*new)); |
| |
| if (atou64(attribs[0], &new->apid) < 0) |
| fatal("invalid application_id '%s'", attribs[0]); |
| else if (atou32(attribs[1], &new->user_id) < 0) |
| fatal("invalid user_id '%s'", attribs[1]); |
| else if (atou32(attribs[2], &new->group_id) < 0) |
| fatal("invalid group_id '%s'", attribs[2]); |
| else if (atotime_t(attribs[3], &new->timestamp) < 0) |
| fatal("invalid time_stamp '%s'", attribs[3]); |
| |
| xassert(ud->ud_inventory->rsvn_head); |
| if (ud->ud_inventory->rsvn_head->app_head) |
| new->next = ud->ud_inventory->rsvn_head->app_head; |
| ud->ud_inventory->rsvn_head->app_head = new; |
| } |
| |
| ud->counter[BT_CMDARRAY] = 0; |
| } |
| |
| /** Basil 1.1/3.1 'Command' element */ |
| void eh_command(struct ud *ud, const XML_Char **attrs) |
| { |
| char *attribs[] = { "width", "depth", "nppn", "memory", |
| "architecture", "cmd" }; |
| |
| extract_attributes(attrs, attribs, ARRAY_SIZE(attribs)); |
| |
| if (ud->ud_inventory) { |
| struct basil_rsvn_app_cmd *new = xmalloc(sizeof(*new)); |
| |
| xassert(ud->ud_inventory->rsvn_head); |
| xassert(ud->ud_inventory->rsvn_head->app_head); |
| |
| if (atou32(attribs[0], &new->width) < 0) |
| fatal("invalid width '%s'", attribs[0]); |
| else if (atou32(attribs[1], &new->depth) < 0) |
| fatal("invalid depth '%s'", attribs[1]); |
| else if (atou32(attribs[2], &new->nppn) < 0) |
| fatal("invalid nppn '%s'", attribs[2]); |
| else if (atou32(attribs[3], &new->memory) < 0) |
| fatal("invalid memory '%s'", attribs[3]); |
| for (new->arch = BNA_X2; new->arch < BNA_MAX; new->arch += 1) |
| if (strcmp(attribs[4], nam_arch[new->arch]) == 0) |
| break; |
| strncpy(new->cmd, attribs[5], sizeof(new->cmd)); |
| |
| if (ud->ud_inventory->rsvn_head->app_head->cmd_head) |
| new->next = ud->ud_inventory->rsvn_head-> |
| app_head->cmd_head; |
| ud->ud_inventory->rsvn_head->app_head->cmd_head = new; |
| } |
| } |
| |
| /* |
| * Top-Level Handlers |
| */ |
| static const struct element_handler *basil_tables[BV_MAX] = { |
| [BV_1_0] = basil_1_0_elements, |
| [BV_1_1] = basil_1_1_elements, |
| [BV_1_2] = basil_1_1_elements, /* Basil 1.2 behaves like 1.1 */ |
| [BV_3_1] = basil_3_1_elements, |
| [BV_4_0] = basil_4_0_elements, |
| [BV_4_1] = basil_4_0_elements, |
| [BV_5_0] = basil_4_0_elements |
| }; |
| |
| /** |
| * tag_to_method - Look up Basil method by tag. |
| * NOTE: This must be kept in synch with the order in %basil_element! |
| */ |
| static enum basil_method _tag_to_method(const enum basil_element tag) |
| { |
| switch (tag) { |
| case BT_MESSAGE ... BT_RESPDATA: /* generic, no method */ |
| return BM_none; |
| case BT_RESVDNODEARRAY ... BT_RESVDNODE:/* RESERVE, Basil >= 3.1 */ |
| case BT_RESERVED: /* RESERVE, Basil >= 1.0 */ |
| return BM_reserve; |
| case BT_CONFIRMED: |
| return BM_confirm; |
| case BT_RELEASED: |
| return BM_release; |
| case BT_ENGINE: |
| return BM_engine; |
| case BT_ACCELARRAY ... BT_ACCELALLOC: /* INVENTORY, Basil >= 4.0 */ |
| case BT_SEGMARRAY ... BT_COMMAND: /* INVENTORY, Basil >= 1.1 */ |
| case BT_INVENTORY ... BT_RESVN: /* INVENTORY, Basil >= 1.0 */ |
| return BM_inventory; |
| case BT_SWITCH ... BT_SWITCHAPPARRAY: |
| return BM_switch; |
| default: |
| return BM_UNKNOWN; |
| } |
| } |
| |
| static void _start_handler(void *user_data, |
| const XML_Char *el, const XML_Char **attrs) |
| { |
| struct ud *ud = user_data; |
| const struct element_handler *table = basil_tables[ud->bp->version]; |
| enum basil_method method; |
| enum basil_element tag; |
| |
| for (tag = BT_MESSAGE; table[tag].tag; tag++) { |
| if (strcmp(table[tag].tag, el) == 0) { |
| /* since BM_inventory is returned for Arrays |
| if the method is switch we need to "switch" |
| it up here. |
| */ |
| if (ud->bp->method == BM_switch) { |
| if(!strcmp(table[tag].tag, "ReservationArray")) |
| tag = BT_SWITCHRESARRAY; |
| else if(!strcmp(table[tag].tag, "Reservation")) |
| tag = BT_SWITCHRES; |
| else if(!strcmp(table[tag].tag, |
| "ApplicationArray")) |
| tag = BT_SWITCHAPPARRAY; |
| else if(!strcmp(table[tag].tag, "Application")) |
| tag = BT_SWITCHAPP; |
| } |
| break; |
| } |
| } |
| if (table[tag].tag == NULL) |
| fatal("Unrecognized XML start tag '%s'", el); |
| |
| method = _tag_to_method(tag); |
| if (method == BM_UNKNOWN) |
| fatal("Unsupported XML start tag '%s'", el); |
| |
| if (method != BM_none && method != ud->bp->method) |
| fatal("Unexpected '%s' start tag within %u response, " |
| "expected %u", |
| el, method, ud->bp->method); |
| |
| if (tag != BT_MESSAGE) { |
| if (ud->depth != table[tag].depth) |
| fatal("Tag '%s' appeared at depth %d instead of %d", |
| el, ud->depth, table[tag].depth); |
| if (ud->counter[tag] && table[tag].uniq) |
| fatal("Multiple occurrences of %s in document", el); |
| } |
| |
| if (ud->depth == TAG_DEPTH_MAX) |
| fatal("BUG: maximum tag depth reached"); |
| ud->stack[ud->depth] = tag; |
| ud->counter[tag]++; |
| |
| if (table[tag].hnd == NULL && *attrs != NULL) |
| fatal("Unexpected attribute '%s' in %s", *attrs, el); |
| else if (table[tag].hnd != NULL && *attrs == NULL) |
| fatal("Tag %s without expected attributes", el); |
| else if (table[tag].hnd != NULL) |
| table[tag].hnd(ud, attrs); |
| ud->depth++; |
| } |
| |
| static void _end_handler(void *user_data, const XML_Char *el) |
| { |
| struct ud *ud = user_data; |
| const struct element_handler *table = basil_tables[ud->bp->version]; |
| enum basil_element end_tag; |
| |
| --ud->depth; |
| for (end_tag = BT_MESSAGE; table[end_tag].tag; end_tag++) |
| if (strcmp(table[end_tag].tag, el) == 0) { |
| /* since BM_inventory is returned for Arrays |
| if the method is switch we need to "switch" |
| it up here. |
| */ |
| if (ud->bp->method == BM_switch) { |
| if(!strcmp(table[end_tag].tag, |
| "ReservationArray")) |
| end_tag = BT_SWITCHRESARRAY; |
| else if(!strcmp(table[end_tag].tag, |
| "Reservation")) |
| end_tag = BT_SWITCHRES; |
| else if(!strcmp(table[end_tag].tag, |
| "ApplicationArray")) |
| end_tag = BT_SWITCHAPPARRAY; |
| else if(!strcmp(table[end_tag].tag, |
| "Application")) |
| end_tag = BT_SWITCHAPP; |
| } |
| break; |
| } |
| if (table[end_tag].tag == NULL) { |
| fatal("Unknown end tag '%s'", el); |
| } else if (end_tag != ud->stack[ud->depth]) { |
| fatal("Non-matching end element '%s'", el); |
| } else if (end_tag == BT_NODE) { |
| if (ud->current_node.reserved) { |
| ud->bp->mdata.inv->batch_total++; |
| } else if (ud->current_node.available) { |
| ud->bp->mdata.inv->batch_avail++; |
| ud->bp->mdata.inv->batch_total++; |
| } |
| ud->bp->mdata.inv->nodes_total++; |
| } else if (end_tag == BT_RESPDATA && ud->error) { |
| /* |
| * Re-classify errors. The error message has been added by the |
| * cdata handler nested inside the ResponseData tags. |
| * |
| * Match substrings that are common to all Basil versions: |
| * - the ' No entry for resId ' string is returned when calling |
| * the RELEASE method multiple times; |
| * - the ' cannot find resId ' string is returned when trying to |
| * confirm a reservation which does not or no longer exist. |
| */ |
| if (strstr(ud->bp->msg, " No entry for resId ") || |
| strstr(ud->bp->msg, " cannot find resId ")) |
| ud->error = BE_NO_RESID; |
| } |
| } |
| |
| static void _cdata_handler(void *user_data, const XML_Char *s, int len) |
| { |
| struct ud *ud = user_data; |
| size_t mrest; |
| |
| if (!ud->depth || ud->stack[ud->depth - 1] != BT_MESSAGE) |
| return; |
| |
| while (isspace(*s)) |
| ++s, --len; |
| |
| mrest = sizeof(ud->bp->msg) - (strlen(ud->bp->msg) + 1); |
| if (mrest > 0) |
| strncat(ud->bp->msg, s, len > mrest ? mrest : len); |
| } |
| |
| /* |
| * parse_basil - parse the response to a Basil query (version-independent) |
| * |
| * @bp: information passed in to guide the parsing process |
| * @fd: file descriptor connected to the output of apbasil |
| * Returns 0 if ok, negative %basil_error otherwise. |
| */ |
| int parse_basil(struct basil_parse_data *bp, int fd) |
| { |
| char xmlbuf[65536]; |
| struct ud ud = {0}; |
| XML_Parser parser; |
| int len; |
| |
| /* Almost all methods require method-specific data in mdata */ |
| xassert(bp->method == BM_engine || bp->mdata.raw != NULL); |
| ud.bp = bp; |
| |
| parser = XML_ParserCreate("US-ASCII"); |
| if (parser == NULL) |
| fatal("can not allocate memory for parser"); |
| |
| XML_SetUserData(parser, &ud); |
| XML_SetElementHandler(parser, _start_handler, _end_handler); |
| XML_SetCharacterDataHandler(parser, _cdata_handler); |
| do { |
| len = read(fd, xmlbuf, sizeof(xmlbuf)); |
| if (len == -1) |
| fatal("read error on stream: len=%d", len); |
| switch (XML_Parse(parser, xmlbuf, len, len == 0)) { |
| case XML_STATUS_ERROR: |
| xmlbuf[len] = '\0'; |
| snprintf(ud.bp->msg, sizeof(ud.bp->msg), |
| "Basil %s %s response parse error: %s " |
| "at line %lu: '%s'", |
| bv_names_long[bp->version], |
| bm_names[bp->method], |
| XML_ErrorString(XML_GetErrorCode(parser)), |
| XML_GetCurrentLineNumber(parser), xmlbuf); |
| /* fall through */ |
| case XML_STATUS_SUSPENDED: |
| ud.error = BE_PARSER; |
| /* fall through */ |
| case XML_STATUS_OK: |
| break; |
| } |
| } while (len && ud.error == BE_NONE); |
| |
| close(fd); |
| XML_ParserFree(parser); |
| |
| switch (ud.error) { |
| case BE_NO_RESID: /* resId no longer exists */ |
| case BE_NONE: /* no error: bp->msg is empty */ |
| break; |
| default: |
| error("%s", bp->msg); |
| } |
| return -ud.error; |
| } |