/* parser.c * * Copyright (C) 2010 - 2013 UNISYS CORPORATION * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or * NON INFRINGEMENT. See the GNU General Public License for more * details. */ #include "parser.h" #include "memregion.h" #include "controlvmchannel.h" #include #include #include #define MYDRVNAME "visorchipset_parser" #define CURRENT_FILE_PC VISOR_CHIPSET_PC_parser_c /* We will refuse to allocate more than this many bytes to copy data from * incoming payloads. This serves as a throttling mechanism. */ #define MAX_CONTROLVM_PAYLOAD_BYTES (1024*128) static ulong controlvm_payload_bytes_buffered; struct parser_context { ulong allocbytes; ulong param_bytes; u8 *curr; ulong bytes_remaining; BOOL byte_stream; char data[0]; }; static struct parser_context * parser_init_guts(u64 addr, u32 bytes, BOOL local, BOOL standard_payload_header, BOOL *retry) { int allocbytes = sizeof(struct parser_context) + bytes; struct parser_context *rc = NULL; struct parser_context *ctx = NULL; struct memregion *rgn = NULL; struct spar_controlvm_parameters_header *phdr = NULL; if (retry) *retry = FALSE; if (!standard_payload_header) /* alloc and 0 extra byte to ensure payload is * '\0'-terminated */ allocbytes++; if ((controlvm_payload_bytes_buffered + bytes) > MAX_CONTROLVM_PAYLOAD_BYTES) { if (retry) *retry = TRUE; rc = NULL; goto cleanup; } ctx = kzalloc(allocbytes, GFP_KERNEL|__GFP_NORETRY); if (!ctx) { if (retry) *retry = TRUE; rc = NULL; goto cleanup; } ctx->allocbytes = allocbytes; ctx->param_bytes = bytes; ctx->curr = NULL; ctx->bytes_remaining = 0; ctx->byte_stream = FALSE; if (local) { void *p; if (addr > virt_to_phys(high_memory - 1)) { rc = NULL; goto cleanup; } p = __va((ulong) (addr)); memcpy(ctx->data, p, bytes); } else { rgn = visor_memregion_create(addr, bytes); if (!rgn) { rc = NULL; goto cleanup; } if (visor_memregion_read(rgn, 0, ctx->data, bytes) < 0) { rc = NULL; goto cleanup; } } if (!standard_payload_header) { ctx->byte_stream = TRUE; rc = ctx; goto cleanup; } phdr = (struct spar_controlvm_parameters_header *)(ctx->data); if (phdr->total_length != bytes) { rc = NULL; goto cleanup; } if (phdr->total_length < phdr->header_length) { rc = NULL; goto cleanup; } if (phdr->header_length < sizeof(struct spar_controlvm_parameters_header)) { rc = NULL; goto cleanup; } rc = ctx; cleanup: if (rgn) { visor_memregion_destroy(rgn); rgn = NULL; } if (rc) { controlvm_payload_bytes_buffered += ctx->param_bytes; } else { if (ctx) { parser_done(ctx); ctx = NULL; } } return rc; } struct parser_context * parser_init(u64 addr, u32 bytes, BOOL local, BOOL *retry) { return parser_init_guts(addr, bytes, local, TRUE, retry); } /* Call this instead of parser_init() if the payload area consists of just * a sequence of bytes, rather than a struct spar_controlvm_parameters_header * structures. Afterwards, you can call parser_simpleString_get() or * parser_byteStream_get() to obtain the data. */ struct parser_context * parser_init_byte_stream(u64 addr, u32 bytes, BOOL local, BOOL *retry) { return parser_init_guts(addr, bytes, local, FALSE, retry); } /* Obtain '\0'-terminated copy of string in payload area. */ char * parser_simpleString_get(struct parser_context *ctx) { if (!ctx->byte_stream) return NULL; return ctx->data; /* note this IS '\0'-terminated, because of * the num of bytes we alloc+clear in * parser_init_byteStream() */ } /* Obtain a copy of the buffer in the payload area. */ void *parser_byte_stream_get(struct parser_context *ctx, ulong *nbytes) { if (!ctx->byte_stream) return NULL; if (nbytes) *nbytes = ctx->param_bytes; return (void *)ctx->data; } uuid_le parser_id_get(struct parser_context *ctx) { struct spar_controlvm_parameters_header *phdr = NULL; if (ctx == NULL) return NULL_UUID_LE; phdr = (struct spar_controlvm_parameters_header *)(ctx->data); return phdr->id; } void parser_param_start(struct parser_context *ctx, PARSER_WHICH_STRING which_string) { struct spar_controlvm_parameters_header *phdr = NULL; if (ctx == NULL) goto Away; phdr = (struct spar_controlvm_parameters_header *)(ctx->data); switch (which_string) { case PARSERSTRING_INITIATOR: ctx->curr = ctx->data + phdr->initiator_offset; ctx->bytes_remaining = phdr->initiator_length; break; case PARSERSTRING_TARGET: ctx->curr = ctx->data + phdr->target_offset; ctx->bytes_remaining = phdr->target_length; break; case PARSERSTRING_CONNECTION: ctx->curr = ctx->data + phdr->connection_offset; ctx->bytes_remaining = phdr->connection_length; break; case PARSERSTRING_NAME: ctx->curr = ctx->data + phdr->name_offset; ctx->bytes_remaining = phdr->name_length; break; default: break; } Away: return; } void parser_done(struct parser_context *ctx) { if (!ctx) return; controlvm_payload_bytes_buffered -= ctx->param_bytes; kfree(ctx); } /** Return length of string not counting trailing spaces. */ static int string_length_no_trail(char *s, int len) { int i = len - 1; while (i >= 0) { if (!isspace(s[i])) return i + 1; i--; } return 0; } /** Grab the next name and value out of the parameter buffer. * The entire parameter buffer looks like this: * =\0 * =\0 * ... * \0 * If successful, the next value is returned within the supplied * buffer (the value is always upper-cased), and the corresponding * is returned within a kmalloc()ed buffer, whose pointer is * provided as the return value of this function. * (The total number of bytes allocated is strlen()+1.) * * NULL is returned to indicate failure, which can occur for several reasons: * - all = pairs have already been processed * - bad parameter * - parameter buffer ends prematurely (couldn't find an '=' or '\0' within * the confines of the parameter buffer) * - the buffer is not large enough to hold the of the next * parameter */ void * parser_param_get(struct parser_context *ctx, char *nam, int namesize) { u8 *pscan, *pnam = nam; ulong nscan; int value_length = -1, orig_value_length = -1; void *value = NULL; int i; int closing_quote = 0; if (!ctx) return NULL; pscan = ctx->curr; nscan = ctx->bytes_remaining; if (nscan == 0) return NULL; if (*pscan == '\0') /* This is the normal return point after you have processed * all of the = pairs in a syntactically-valid * parameter buffer. */ return NULL; /* skip whitespace */ while (isspace(*pscan)) { pscan++; nscan--; if (nscan == 0) return NULL; } while (*pscan != ':') { if (namesize <= 0) return NULL; *pnam = toupper(*pscan); pnam++; namesize--; pscan++; nscan--; if (nscan == 0) return NULL; } if (namesize <= 0) return NULL; *pnam = '\0'; nam[string_length_no_trail(nam, strlen(nam))] = '\0'; /* point to char immediately after ":" in ":" */ pscan++; nscan--; /* skip whitespace */ while (isspace(*pscan)) { pscan++; nscan--; if (nscan == 0) return NULL; } if (nscan == 0) return NULL; if (*pscan == '\'' || *pscan == '"') { closing_quote = *pscan; pscan++; nscan--; if (nscan == 0) return NULL; } /* look for a separator character, terminator character, or * end of data */ for (i = 0, value_length = -1; i < nscan; i++) { if (closing_quote) { if (pscan[i] == '\0') return NULL; if (pscan[i] == closing_quote) { value_length = i; break; } } else if (pscan[i] == ',' || pscan[i] == ';' || pscan[i] == '\0') { value_length = i; break; } } if (value_length < 0) { if (closing_quote) return NULL; value_length = nscan; } orig_value_length = value_length; if (closing_quote == 0) value_length = string_length_no_trail(pscan, orig_value_length); value = kmalloc(value_length + 1, GFP_KERNEL|__GFP_NORETRY); if (value == NULL) return NULL; memcpy(value, pscan, value_length); ((u8 *) (value))[value_length] = '\0'; pscan += orig_value_length; nscan -= orig_value_length; /* skip past separator or closing quote */ if (nscan > 0) { if (*pscan != '\0') { pscan++; nscan--; } } if (closing_quote && (nscan > 0)) { /* we still need to skip around the real separator if present */ /* first, skip whitespace */ while (isspace(*pscan)) { pscan++; nscan--; if (nscan == 0) break; } if (nscan > 0) { if (*pscan == ',' || *pscan == ';') { pscan++; nscan--; } else if (*pscan != '\0') { kfree(value); value = NULL; return NULL; } } } ctx->curr = pscan; ctx->bytes_remaining = nscan; return value; } void * parser_string_get(struct parser_context *ctx) { u8 *pscan; ulong nscan; int value_length = -1; void *value = NULL; int i; if (!ctx) return NULL; pscan = ctx->curr; nscan = ctx->bytes_remaining; if (nscan == 0) return NULL; if (!pscan) return NULL; for (i = 0, value_length = -1; i < nscan; i++) if (pscan[i] == '\0') { value_length = i; break; } if (value_length < 0) /* '\0' was not included in the length */ value_length = nscan; value = kmalloc(value_length + 1, GFP_KERNEL|__GFP_NORETRY); if (value == NULL) return NULL; if (value_length > 0) memcpy(value, pscan, value_length); ((u8 *) (value))[value_length] = '\0'; return value; }