int output(lua_State* lua) { void* luserdata = lua_touserdata(lua, lua_upvalueindex(1)); if (NULL == luserdata) { lua_pushstring(lua, "output() invalid lightuserdata"); lua_error(lua); } lua_sandbox* lsb = (lua_sandbox*)luserdata; int n = lua_gettop(lua); if (n == 0) { lua_pushstring(lua, "output() must have at least one argument"); lua_error(lua); } int result = 0; void* ud = NULL; for (int i = 1; result == 0 && i <= n; ++i) { switch (lua_type(lua, i)) { case LUA_TNUMBER: if (dynamic_snprintf(&lsb->m_output, "%0.9g", lua_tonumber(lua, i))) { result = 1; } break; case LUA_TSTRING: if (dynamic_snprintf(&lsb->m_output, "%s", lua_tostring(lua, i))) { result = 1; } break; case LUA_TNIL: if (dynamic_snprintf(&lsb->m_output, "nil")) { result = 1; } break; case LUA_TUSERDATA: ud = lua_touserdata(lua, i); if (heka_circular_buffer == userdata_type(lua, ud, i)) { if (output_circular_buffer((circular_buffer*)ud, &lsb->m_output)) { result = 1; } } break; } } lsb->m_usage[USAGE_TYPE_OUTPUT][USAGE_STAT_CURRENT] = lsb->m_output.m_pos; if (lsb->m_usage[USAGE_TYPE_OUTPUT][USAGE_STAT_CURRENT] > lsb->m_usage[USAGE_TYPE_OUTPUT][USAGE_STAT_MAXIMUM]) { lsb->m_usage[USAGE_TYPE_OUTPUT][USAGE_STAT_MAXIMUM] = lsb->m_usage[USAGE_TYPE_OUTPUT][USAGE_STAT_CURRENT]; } if (result != 0 || lsb->m_usage[USAGE_TYPE_OUTPUT][USAGE_STAT_CURRENT] > lsb->m_usage[USAGE_TYPE_OUTPUT][USAGE_STAT_LIMIT]) { lua_pushstring(lua, "output_limit exceeded"); lua_error(lua); } return 0; }
int serialize_data(lua_sandbox* lsb, int index, output_data* output) { output->m_pos = 0; switch (lua_type(lsb->m_lua, index)) { case LUA_TNUMBER: if (serialize_double(output, lua_tonumber(lsb->m_lua, index))) { return 1; } break; case LUA_TSTRING: // The stack is cleaned up on failure by preserve_global_data // but for clarity it is incrementally cleaned up anyway. lua_checkstack(lsb->m_lua, 4); lua_getglobal(lsb->m_lua, "string"); if (!lua_istable(lsb->m_lua, -1)) { snprintf(lsb->m_error_message, ERROR_SIZE, "serialize_data cannot access the string table"); lua_pop(lsb->m_lua, 1); // Remove bogus string table. return 1; } lua_getfield(lsb->m_lua, -1, "format"); if (!lua_isfunction(lsb->m_lua, -1)) { snprintf(lsb->m_error_message, ERROR_SIZE, "serialize_data cannot access the string format function"); lua_pop(lsb->m_lua, 2); // Remove the bogus format function and // string table. return 1; } lua_pushstring(lsb->m_lua, "%q"); lua_pushvalue(lsb->m_lua, index - 3); if (lua_pcall(lsb->m_lua, 2, 1, 0) == 0) { if (dynamic_snprintf(output, "%s", lua_tostring(lsb->m_lua, -1))) { lua_pop(lsb->m_lua, 1); // Remove the string table. return 1; } } else { snprintf(lsb->m_error_message, ERROR_SIZE, "serialize_data '%s'", lua_tostring(lsb->m_lua, -1)); lua_pop(lsb->m_lua, 2); // Remove the error message and the string // table. return 1; } lua_pop(lsb->m_lua, 2); // Remove the pcall result and the string table. break; case LUA_TBOOLEAN: if (dynamic_snprintf(output, "%s", lua_toboolean(lsb->m_lua, index) ? "true" : "false")) { return 1; } break; default: snprintf(lsb->m_error_message, ERROR_SIZE, "serialize_data cannot preserve type '%s'", lua_typename(lsb->m_lua, lua_type(lsb->m_lua, index))); return 1; } return 0; }
int serialize_kvp_as_json(lua_sandbox* lsb, serialization_data* data, int isHash) { static const char* array_start = "[", *array_end = "]"; static const char* hash_start = "{", *hash_end = "}"; int kindex = -2, vindex = -1, result = 0; if (ignore_value_type_json(lsb, vindex)) return 0; if (ignore_key(lsb, kindex)) return 0; if (isHash) { if (serialize_data_as_json(lsb, kindex, &lsb->m_output)) return 1; if (dynamic_snprintf(&lsb->m_output, ":")) return 1; } if (lua_type(lsb->m_lua, vindex) == LUA_TTABLE) { const void* ptr = lua_topointer(lsb->m_lua, vindex); table_ref* seen = find_table_ref(&data->m_tables, ptr); if (seen == NULL) { seen = add_table_ref(&data->m_tables, ptr, 0); if (seen != NULL) { const char* start, *end; lua_rawgeti(lsb->m_lua, vindex, 1); int hash = lua_isnil(lsb->m_lua, -1); lua_pop(lsb->m_lua, 1); // remove the test value if (hash) { start = hash_start; end = hash_end; } else { start = array_start; end = array_end; } if (dynamic_snprintf(&lsb->m_output, start)) return 1; if (serialize_table_as_json(lsb, data, hash)) return 1; if (dynamic_snprintf(&lsb->m_output, end)) return 1; } else { snprintf(lsb->m_error_message, ERROR_SIZE, "serialize table out of memory"); return 1; } } else { snprintf(lsb->m_error_message, ERROR_SIZE, "table contains an internal or circular reference"); return 1; } } else { result = serialize_data_as_json(lsb, vindex, &lsb->m_output); } return result; }
int serialize_table_as_json(lua_sandbox* lsb, serialization_data* data, int isHash) { int result = 0; lua_checkstack(lsb->m_lua, 2); lua_pushnil(lsb->m_lua); int had_output = 0; size_t start = 0; while (result == 0 && lua_next(lsb->m_lua, -2) != 0) { if (had_output) { if (dynamic_snprintf(&lsb->m_output, ",")) return 1; } start = lsb->m_output.m_pos; result = serialize_kvp_as_json(lsb, data, isHash); lua_pop(lsb->m_lua, 1); // Remove the value leaving the key on top for // the next interation. if (start != lsb->m_output.m_pos) { had_output = 1; } else { had_output = 0; } } if (start != 0 && had_output == 0) { // remove the trailing comma size_t reset_pos = start - 1; if (lsb->m_output.m_data[reset_pos] == ',') { lsb->m_output.m_data[reset_pos] = 0; lsb->m_output.m_pos = reset_pos; } } return result; }
int serialize_circular_buffer(const char* key, circular_buffer* cb, output_data* output) { output->m_pos = 0; if (dynamic_snprintf(output, "if %s == nil then %s = circular_buffer.new(%d, %d, %d) end\n", key, key, cb->m_rows, cb->m_columns, cb->m_seconds_per_row)) { return 1; } unsigned column_idx; for (column_idx = 0; column_idx < cb->m_columns; ++column_idx) { if (dynamic_snprintf(output, "%s:set_header(%d, \"%s\", \"%s\")\n", key, column_idx+1, cb->m_headers[column_idx].m_name, column_type_names[cb->m_headers[column_idx].m_type])) { return 1; } } if (dynamic_snprintf(output, "%s:fromstring(\"%lld %d", key, cb->m_current_time, cb->m_current_row)) { return 1; } for (unsigned row_idx = 0; row_idx < cb->m_rows; ++row_idx) { for (column_idx = 0; column_idx < cb->m_columns; ++column_idx) { if (dynamic_snprintf(output, " %0.9g", cb->m_values[(row_idx * cb->m_columns) + column_idx])) { return 1; } } } if (dynamic_snprintf(output, "\")\n")) { return 1; } return 0; }
int preserve_global_data(lua_sandbox* lsb, const char* data_file) { static const char* G = "_G"; lua_getglobal(lsb->m_lua, G); if (!lua_istable(lsb->m_lua, -1)) { snprintf(lsb->m_error_message, ERROR_SIZE, "preserve_global_data cannot access the global table"); return 1; } FILE* fh = fopen(data_file, "wb"); if (fh == NULL) { snprintf(lsb->m_error_message, ERROR_SIZE, "preserve_global_data could not open: %s", data_file); return 1; } int result = 0; serialization_data data; data.m_fh = fh; data.m_keys.m_size = OUTPUT_SIZE; data.m_keys.m_pos = 0; data.m_keys.m_data = malloc(data.m_keys.m_size); data.m_tables.m_size = 64; data.m_tables.m_pos = 0; data.m_tables.m_array = malloc(data.m_tables.m_size * sizeof(table_ref)); if (data.m_tables.m_array == NULL || data.m_keys.m_data == NULL) { snprintf(lsb->m_error_message, ERROR_SIZE, "preserve_global_data out of memory"); result = 1; } else { dynamic_snprintf(&data.m_keys, "%s", G); data.m_keys.m_pos += 1; data.m_globals = lua_topointer(lsb->m_lua, -1); lua_checkstack(lsb->m_lua, 2); lua_pushnil(lsb->m_lua); while (result == 0 && lua_next(lsb->m_lua, -2) != 0) { result = serialize_kvp(lsb, &data, 0); lua_pop(lsb->m_lua, 1); } lua_pop(lsb->m_lua, lua_gettop(lsb->m_lua)); // Wipe the entire Lua stack. Since incremental cleanup on failure // was added the stack should only contain table _G. } free(data.m_tables.m_array); free(data.m_keys.m_data); fclose(fh); if (result != 0) { remove(data_file); } return result; }
int serialize_kvp(lua_sandbox* lsb, serialization_data* data, size_t parent) { int kindex = -2, vindex = -1; if (ignore_value_type(lsb, data, vindex)) return 0; int result = serialize_data(lsb, kindex, &lsb->m_output); if (result != 0) return result; size_t pos = data->m_keys.m_pos; if (dynamic_snprintf(&data->m_keys, "%s[%s]", data->m_keys.m_data + parent, lsb->m_output.m_data)) { return 1; } fprintf(data->m_fh, "%s = ", data->m_keys.m_data + pos); if (lua_type(lsb->m_lua, vindex) == LUA_TTABLE) { const void* ptr = lua_topointer(lsb->m_lua, vindex); table_ref* seen = find_table_ref(&data->m_tables, ptr); if (seen == NULL) { seen = add_table_ref(&data->m_tables, ptr, pos); if (seen != NULL) { data->m_keys.m_pos += 1; fprintf(data->m_fh, "{}\n"); result = serialize_table(lsb, data, pos); } else { snprintf(lsb->m_error_message, ERROR_SIZE, "preserve table out of memory"); return 1; } } else { data->m_keys.m_pos = pos; fprintf(data->m_fh, "%s\n", data->m_keys.m_data + seen->m_name_pos); } } else { data->m_keys.m_pos = pos; result = serialize_data(lsb, vindex, &lsb->m_output); if (result == 0) { fprintf(data->m_fh, "%s\n", lsb->m_output.m_data); } } return result; }
int output_circular_buffer(circular_buffer* cb, output_data* output) { // output header if (dynamic_snprintf(output, "{\"time\":%d,\"rows\":%d,\"columns\":%d,\"seconds_per_row\":%d,\"column_info\":[", cb->m_current_time - (cb->m_seconds_per_row * (cb->m_rows - 1)), cb->m_rows, cb->m_columns, cb->m_seconds_per_row)) { return 1; } unsigned column_idx; for (column_idx = 0; column_idx < cb->m_columns; ++column_idx) { if (column_idx != 0) { if (dynamic_snprintf(output, ",")) return 1; } if (dynamic_snprintf(output, "{\"name\":\"%s\",\"type\":\"%s\"}", cb->m_headers[column_idx].m_name, column_type_names[cb->m_headers[column_idx].m_type])) { return 1; } } if (dynamic_snprintf(output, "]}\n")) return 1; // output buffer data unsigned row_idx = cb->m_current_row + 1; for (unsigned i = 0; i < cb->m_rows; ++i, ++row_idx) { if (row_idx >= cb->m_rows) row_idx = 0; for (column_idx = 0; column_idx < cb->m_columns; ++column_idx) { if (column_idx != 0) { if (dynamic_snprintf(output, "\t")) return 1; } if (dynamic_snprintf(output, "%0.9g", cb->m_values[(row_idx * cb->m_columns) + column_idx])) { return 1; } } if (dynamic_snprintf(output, "\n")) return 1; } return 0; }
int output(lua_State* lua) { void* luserdata = lua_touserdata(lua, lua_upvalueindex(1)); if (NULL == luserdata) { luaL_error(lua, "output() invalid lightuserdata"); } lua_sandbox* lsb = (lua_sandbox*)luserdata; int n = lua_gettop(lua); if (n == 0) { luaL_error(lua, "output() must have at least one argument"); } int result = 0; void* ud = NULL; for (int i = 1; result == 0 && i <= n; ++i) { switch (lua_type(lua, i)) { case LUA_TNUMBER: if (serialize_double(&lsb->m_output, lua_tonumber(lua, i))) { result = 1; } break; case LUA_TSTRING: if (dynamic_snprintf(&lsb->m_output, "%s", lua_tostring(lua, i))) { result = 1; } break; case LUA_TNIL: if (dynamic_snprintf(&lsb->m_output, "nil")) { result = 1; } break; case LUA_TBOOLEAN: if (dynamic_snprintf(&lsb->m_output, "%s", lua_toboolean(lsb->m_lua, i) ? "true" : "false")) { result = 1; } break; case LUA_TTABLE: if (!dynamic_snprintf(&lsb->m_output, "{")) { serialization_data data; data.m_globals = NULL; data.m_tables.m_size = 64; data.m_tables.m_pos = 0; data.m_tables.m_array = malloc(data.m_tables.m_size * sizeof(table_ref)); if (data.m_tables.m_array == NULL) { snprintf(lsb->m_error_message, ERROR_SIZE, "json table serialization out of memory"); result = 1; } else { lua_checkstack(lsb->m_lua, 2); lua_getfield(lsb->m_lua, i, "_name"); if (lua_type(lsb->m_lua, -1) != LUA_TSTRING) { lua_pop(lsb->m_lua, 1); // remove the failed _name result lua_pushstring(lsb->m_lua, "table"); // add default name } lua_pushvalue(lsb->m_lua, i); result = serialize_kvp_as_json(lsb, &data, 1); if (result == 0) { result = dynamic_snprintf(&lsb->m_output, "}\n"); } lua_pop(lsb->m_lua, 2); // remove the name and copy of the table free(data.m_tables.m_array); } } else { result = 1; } break; case LUA_TUSERDATA: ud = lua_touserdata(lua, i); if (heka_circular_buffer == userdata_type(lua, ud, i)) { if (output_circular_buffer(lua, (circular_buffer*)ud, &lsb->m_output)) { result = 1; } } break; } } update_output_stats(lsb); if (result != 0 || lsb->m_usage[USAGE_TYPE_OUTPUT][USAGE_STAT_CURRENT] > lsb->m_usage[USAGE_TYPE_OUTPUT][USAGE_STAT_LIMIT]) { if (lsb->m_error_message[0] == 0) { luaL_error(lua, "output_limit exceeded"); } luaL_error(lua, lsb->m_error_message); } return 0; }
int serialize_data_as_json(lua_sandbox* lsb, int index, output_data* output) { const char* s; size_t len = 0; size_t start_pos = output->m_pos; size_t escaped_len = 0; switch (lua_type(lsb->m_lua, index)) { case LUA_TNUMBER: if (serialize_double(output, lua_tonumber(lsb->m_lua, index))) { return 1; } break; case LUA_TSTRING: s = lua_tolstring(lsb->m_lua, index, &len); escaped_len = len + 3; // account for the quotes and terminator for (size_t i = 0; i < len; ++i) { // buffer needs at least enough room for quotes, terminator, and an // escaped character if (output->m_pos + 5 > output->m_size) { size_t needed = escaped_len - (output->m_pos - start_pos); if (realloc_output(output, needed)) return 1; } if (i == 0) { output->m_data[output->m_pos++] = '"'; } switch (s[i]) { case '"': output->m_data[output->m_pos++] = '\\'; output->m_data[output->m_pos++] = '"'; ++escaped_len; break; case '\\': output->m_data[output->m_pos++] = '\\'; output->m_data[output->m_pos++] = '\\'; ++escaped_len; break; case '/': output->m_data[output->m_pos++] = '\\'; output->m_data[output->m_pos++] = '/'; ++escaped_len; break; case '\b': output->m_data[output->m_pos++] = '\\'; output->m_data[output->m_pos++] = 'b'; ++escaped_len; break; case '\f': output->m_data[output->m_pos++] = '\\'; output->m_data[output->m_pos++] = 'f'; ++escaped_len; break; case '\n': output->m_data[output->m_pos++] = '\\'; output->m_data[output->m_pos++] = 'n'; ++escaped_len; break; case '\r': output->m_data[output->m_pos++] = '\\'; output->m_data[output->m_pos++] = 'r'; ++escaped_len; break; case '\t': output->m_data[output->m_pos++] = '\\'; output->m_data[output->m_pos++] = 't'; ++escaped_len; break; default: output->m_data[output->m_pos++] = s[i]; } } output->m_data[output->m_pos++] = '"'; output->m_data[output->m_pos] = 0; break; case LUA_TBOOLEAN: if (dynamic_snprintf(output, "%s", lua_toboolean(lsb->m_lua, index) ? "true" : "false")) { return 1; } break; default: snprintf(lsb->m_error_message, ERROR_SIZE, "serialize_data_as_json cannot preserve type '%s'", lua_typename(lsb->m_lua, lua_type(lsb->m_lua, index))); return 1; } return 0; }
int serialize_double(output_data* output, double d) { if (d > INT_MAX) { return dynamic_snprintf(output, "%0.9g", d); } const int precision = 8; const unsigned magnitude = 100000000; char buffer[20]; char* p = buffer; int negative = 0; if (d < 0) { negative = 1; d = -d; } int number = (int)d; double tmp = (d - number) * magnitude; unsigned fraction = (unsigned)tmp; double diff = tmp - fraction; if (diff > 0.5) { ++fraction; if (fraction >= magnitude) { fraction = 0; ++number; } } else if (diff == 0.5 && ((fraction == 0) || (fraction & 1))) { // bankers rounding ++fraction; } // output decimal fraction if (fraction != 0) { int nodigits = 1; char c = 0; for (int x = 0; x < precision; ++x) { c = fraction % 10; if (!(c == 0 && nodigits)) { *p++ = c + '0'; nodigits = 0; } fraction /= 10; } *p++ = '.'; } // output number do { *p++ = (number % 10) + '0'; number /= 10; } while (number > 0); size_t remaining = output->m_size - output->m_pos; size_t len = (p - buffer) + negative; if (len >= remaining) { size_t newsize = output->m_size * 2; while (len >= newsize - output->m_pos) { newsize *= 2; } void* ptr = realloc(output->m_data, newsize); if (ptr == NULL) return 1; output->m_data = ptr; output->m_size = newsize; } if (negative) { output->m_data[output->m_pos++] = '-'; } do { --p; output->m_data[output->m_pos++] = *p; } while (p != buffer); output->m_data[output->m_pos] = 0; return 0; }