/*
- Copyright (C) 2010 Proxmox Server Solutions GmbH
+ Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
#include <rrd.h>
#include <rrd_client.h>
#include <time.h>
+#include <ctype.h>
#include "cfs-utils.h"
#include "status.h"
+#include "memdb.h"
#include "logger.h"
#define KVSTORE_CPG_GROUP_NAME "pve_kvstore_v1"
{ .path = "user.cfg" },
{ .path = "domains.cfg" },
{ .path = "priv/shadow.cfg" },
+ { .path = "priv/acme/plugins.cfg" },
{ .path = "priv/tfa.cfg" },
+ { .path = "priv/token.cfg" },
+ { .path = "priv/ipam.db" },
{ .path = "datacenter.cfg" },
{ .path = "vzdump.cron" },
+ { .path = "vzdump.conf" },
+ { .path = "jobs.cfg" },
{ .path = "ha/crm_commands" },
{ .path = "ha/manager_status" },
{ .path = "ha/resources.cfg" },
{ .path = "status.cfg" },
{ .path = "replication.cfg" },
{ .path = "ceph.conf" },
+ { .path = "sdn/vnets.cfg" },
+ { .path = "sdn/zones.cfg" },
+ { .path = "sdn/controllers.cfg" },
+ { .path = "sdn/subnets.cfg" },
+ { .path = "sdn/ipams.cfg" },
+ { .path = "sdn/dns.cfg" },
+ { .path = "sdn/.running-config" },
+ { .path = "virtual-guest/cpu-models.conf" },
+ { .path = "firewall/cluster.fw" },
};
static GMutex mutex;
g_free(vminfo);
}
+static const char *vminfo_type_to_string(vminfo_t *vminfo)
+{
+ if (vminfo->vmtype == VMTYPE_QEMU) {
+ return "qemu";
+ } else if (vminfo->vmtype == VMTYPE_OPENVZ) {
+ // FIXME: remove openvz stuff for 7.x
+ return "openvz";
+ } else if (vminfo->vmtype == VMTYPE_LXC) {
+ return "lxc";
+ } else {
+ return "unknown";
+ }
+}
+
+static const char *vminfo_type_to_path_type(vminfo_t *vminfo)
+{
+ if (vminfo->vmtype == VMTYPE_QEMU) {
+ return "qemu-server"; // special case..
+ } else {
+ return vminfo_type_to_string(vminfo);
+ }
+}
+
+int vminfo_to_path(vminfo_t *vminfo, GString *path)
+{
+ g_return_val_if_fail(vminfo != NULL, -1);
+ g_return_val_if_fail(path != NULL, -1);
+
+ if (!vminfo->nodename)
+ return 0;
+
+ const char *type = vminfo_type_to_path_type(vminfo);
+ g_string_printf(path, "/nodes/%s/%s/%u.conf", vminfo->nodename, type, vminfo->vmid);
+
+ return 1;
+}
+
void cfs_clnode_destroy(
cfs_clnode_t *clnode)
{
g_return_val_if_fail(vmlist != NULL, FALSE);
g_return_val_if_fail(nodename != NULL, FALSE);
g_return_val_if_fail(vmid != 0, FALSE);
+ // FIXME: remove openvz stuff for 7.x
g_return_val_if_fail(vmtype == VMTYPE_QEMU || vmtype == VMTYPE_OPENVZ ||
vmtype == VMTYPE_LXC, FALSE);
g_return_if_fail(cfs_status.vmlist != NULL);
g_return_if_fail(nodename != NULL);
g_return_if_fail(vmid != 0);
+ // FIXME: remove openvz stuff for 7.x
g_return_if_fail(vmtype == VMTYPE_QEMU || vmtype == VMTYPE_OPENVZ ||
vmtype == VMTYPE_LXC);
int first = 1;
while (g_hash_table_iter_next (&iter, &key, &value)) {
vminfo_t *vminfo = (vminfo_t *)value;
- char *type;
- if (vminfo->vmtype == VMTYPE_QEMU) {
- type = "qemu";
- } else if (vminfo->vmtype == VMTYPE_OPENVZ) {
- type = "openvz";
- } else if (vminfo->vmtype == VMTYPE_LXC) {
- type = "lxc";
- } else {
- type = "unknown";
- }
+ const char *type = vminfo_type_to_string(vminfo);
if (!first)
g_string_append_printf(str, ",\n");
return 0;
}
+// checks if a config line starts with the given prop. if yes, writes a '\0'
+// at the end of the value, and returns the pointer where the value starts
+// note: line[line_end] needs to be guaranteed a null byte
+char *
+_get_property_value_from_line(char *line, size_t line_len, const char *prop, size_t prop_len)
+{
+ if (line_len <= prop_len + 1) return NULL;
+
+ if (line[prop_len] == ':' && memcmp(line, prop, prop_len) == 0) { // found
+ char *v_start = &line[prop_len + 1];
+ char *v_end = &line[line_len - 1];
+
+ // drop initial value whitespaces here already
+ while (v_start < v_end && *v_start && isspace(*v_start)) v_start++;
+
+ if (!*v_start) return NULL;
+
+ while (v_end > v_start && isspace(*v_end)) v_end--;
+ if (v_end < &line[line_len - 1]) {
+ v_end[1] = '\0';
+ }
+
+ return v_start;
+ }
+
+ return NULL;
+}
+
+// checks the conf for lines starting with the given props and
+// writes the pointers into the correct positions into the 'found' array
+// afterwards, without initial whitespace(s), we only deal with the format
+// restriction imposed by our perl VM config parser, main reference is
+// PVE::QemuServer::parse_vm_config this allows to be very fast and still
+// relatively simple
+// main restrictions used for our advantage is the properties match regex:
+// ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) from parse_vm_config
+// currently we only look at the current configuration in place, i.e., *no*
+// snapshot and *no* pending changes
+//
+// min..max is the char range of the first character of the given props,
+// so that we can return early when checking the line
+// note: conf must end with a newline
+void
+_get_property_values(char **found, char *conf, int conf_size, const char **props, uint8_t num_props, char min, char max)
+{
+ const char *const conf_end = conf + conf_size;
+ char *line = conf;
+ size_t remaining_size = conf_size;
+ uint8_t count = 0;
+
+ if (conf_size == 0) {
+ return;
+ }
+
+ char *next_newline = memchr(conf, '\n', conf_size);
+ if (next_newline == NULL) {
+ return; // valid property lines end with \n, but none in the config
+ }
+ *next_newline = '\0';
+
+ while (line != NULL) {
+ if (!line[0]) goto next;
+
+ // snapshot or pending section start, but nothing found yet -> not found
+ if (line[0] == '[') return;
+ // continue early if line does not begin with the min/max char of the properties
+ if (line[0] < min || line[0] > max) goto next;
+
+ size_t line_len = next_newline - line;
+ for (uint8_t i = 0; i < num_props; i++) {
+ char * value = _get_property_value_from_line(line, line_len, props[i], strlen(props[i]));
+ if (value != NULL) {
+ count += (found[i] != NULL) & 0x1; // count newly found lines
+ found[i] = value;
+ }
+ }
+ if (count == num_props) {
+ return; // found all
+ }
+next:
+ line = next_newline + 1;
+ remaining_size = conf_end - line;
+ next_newline = memchr(line, '\n', remaining_size);
+ if (next_newline == NULL) {
+ return; // valid property lines end with \n, but none in the config
+ }
+ *next_newline = '\0';
+ }
+
+ return;
+}
+
+static void
+_g_str_append_kv_jsonescaped(GString *str, const char *k, const char *v)
+{
+ g_string_append_printf(str, "\"%s\": \"", k);
+
+ for (; *v; v++) {
+ if (*v == '\\' || *v == '"') {
+ g_string_append_c(str, '\\');
+ }
+ g_string_append_c(str, *v);
+ }
+
+ g_string_append_c(str, '"');
+}
+
+int
+_print_found_properties(
+ GString *str,
+ gpointer conf,
+ int size,
+ const char **props,
+ uint8_t num_props,
+ uint32_t vmid,
+ char **values,
+ char min,
+ char max,
+ int first)
+{
+ _get_property_values(values, conf, size, props, num_props, min, max);
+
+ uint8_t found = 0;
+ for (uint8_t i = 0; i < num_props; i++) {
+ if (values[i] == NULL) {
+ continue;
+ }
+ if (found) {
+ g_string_append_c(str, ',');
+ } else {
+ if (!first) {
+ g_string_append_printf(str, ",\n");
+ } else {
+ first = 0;
+ }
+ g_string_append_printf(str, "\"%u\":{", vmid);
+ found = 1;
+ }
+ _g_str_append_kv_jsonescaped(str, props[i], values[i]);
+ }
+
+ if (found) {
+ g_string_append_c(str, '}');
+ }
+
+ return first;
+}
+
+int
+cfs_create_guest_conf_properties_msg(GString *str, memdb_t *memdb, const char **props, uint8_t num_props, uint32_t vmid)
+{
+ g_return_val_if_fail(cfs_status.vmlist != NULL, -EINVAL);
+ g_return_val_if_fail(str != NULL, -EINVAL);
+
+ // Prelock &memdb->mutex in order to enable the usage of memdb_read_nolock
+ // to prevent Deadlocks as in #2553
+ g_mutex_lock (&memdb->mutex);
+ g_mutex_lock (&mutex);
+
+ g_string_printf(str, "{\n");
+
+ GHashTable *ht = cfs_status.vmlist;
+
+ int res = 0;
+ GString *path = NULL;
+ gpointer tmp = NULL;
+ char **values = calloc(num_props, sizeof(char*));
+ char min = 'z', max = 'a';
+
+ for (uint8_t i = 0; i < num_props; i++) {
+ if (props[i][0] > max) {
+ max = props[i][0];
+ }
+
+ if (props[i][0] < min) {
+ min = props[i][0];
+ }
+ }
+
+ if (!g_hash_table_size(ht)) {
+ goto ret;
+ }
+
+ if ((path = g_string_sized_new(256)) == NULL) {
+ res = -ENOMEM;
+ goto ret;
+ }
+
+ if (vmid >= 100) {
+ vminfo_t *vminfo = (vminfo_t *) g_hash_table_lookup(cfs_status.vmlist, &vmid);
+ if (vminfo == NULL) goto enoent;
+
+ if (!vminfo_to_path(vminfo, path)) goto err;
+
+ // use memdb_read_nolock because lock is handled here
+ int size = memdb_read_nolock(memdb, path->str, &tmp);
+ if (tmp == NULL) goto err;
+
+ // conf needs to be newline terminated
+ if (((char *)tmp)[size - 1] != '\n') {
+ gpointer new = realloc(tmp, size + 1);
+ if (new == NULL) goto err;
+ tmp = new;
+ ((char *)tmp)[size++] = '\n';
+ }
+ _print_found_properties(str, tmp, size, props, num_props, vmid, values, min, max, 1);
+ } else {
+ GHashTableIter iter;
+ g_hash_table_iter_init (&iter, ht);
+
+ gpointer key, value;
+ int first = 1;
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ vminfo_t *vminfo = (vminfo_t *)value;
+
+ if (!vminfo_to_path(vminfo, path)) goto err;
+
+ g_free(tmp); // no-op if already null
+ tmp = NULL;
+ // use memdb_read_nolock because lock is handled here
+ int size = memdb_read_nolock(memdb, path->str, &tmp);
+ if (tmp == NULL) continue;
+
+ // conf needs to be newline terminated
+ if (((char *)tmp)[size - 1] != '\n') {
+ gpointer new = realloc(tmp, size + 1);
+ if (new == NULL) continue;
+ tmp = new;
+ ((char *)tmp)[size++] = '\n';
+ }
+
+ memset(values, 0, sizeof(char*)*num_props); // reset array
+ first = _print_found_properties(str, tmp, size, props, num_props,
+ vminfo->vmid, values, min, max, first);
+ }
+ }
+ret:
+ g_free(tmp);
+ free(values);
+ if (path != NULL) {
+ g_string_free(path, TRUE);
+ }
+ g_string_append_printf(str,"\n}\n");
+ g_mutex_unlock (&mutex);
+ g_mutex_unlock (&memdb->mutex);
+ return res;
+err:
+ res = -EIO;
+ goto ret;
+enoent:
+ res = -ENOENT;
+ goto ret;
+}
+
+int
+cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *prop, uint32_t vmid)
+{
+ return cfs_create_guest_conf_properties_msg(str, memdb, &prop, 1, vmid);
+}
+
void
record_memdb_change(const char *path)
{
g_return_val_if_fail(data != NULL, FALSE);
kventry_t *entry;
- if ((entry = (kventry_t *)g_hash_table_lookup(kvhash, key))) {
+ if (!len) {
+ g_hash_table_remove(kvhash, key);
+ } else if ((entry = (kventry_t *)g_hash_table_lookup(kvhash, key))) {
g_free(entry->data);
- entry->data = g_memdup(data, len);
+ entry->data = g_memdup2(data, len);
entry->len = len;
entry->version++;
} else {
kventry_t *entry = g_new0(kventry_t, 1);
entry->key = g_strdup(key);
- entry->data = g_memdup(data, len);
+ entry->data = g_memdup2(data, len);
entry->len = len;
g_hash_table_replace(kvhash, entry->key, entry);
rrdentry_t *entry;
if ((entry = (rrdentry_t *)g_hash_table_lookup(rrdhash, key))) {
g_free(entry->data);
- entry->data = g_memdup(data, len);
+ entry->data = g_memdup2(data, len);
entry->len = len;
entry->time = time(NULL);
} else {
rrdentry_t *entry = g_new0(rrdentry_t, 1);
entry->key = g_strdup(key);
- entry->data = g_memdup(data, len);
+ entry->data = g_memdup2(data, len);
entry->len = len;
entry->time = time(NULL);
return NULL;
}
- msg = entry->data;
+ char *msgptr = entry->data;
- if (*((char *)msg + entry->node_len - 1)) {
+ if (*((char *)msgptr + entry->node_len - 1)) {
cfs_critical("unterminated string in log message");
return NULL;
}
- msg += entry->node_len;
+ msgptr += entry->node_len;
- if (*((char *)msg + entry->ident_len - 1)) {
+ if (*((char *)msgptr + entry->ident_len - 1)) {
cfs_critical("unterminated string in log message");
return NULL;
}
- msg += entry->ident_len;
+ msgptr += entry->ident_len;
- if (*((char *)msg + entry->tag_len - 1)) {
+ if (*((char *)msgptr + entry->tag_len - 1)) {
cfs_critical("unterminated string in log message");
return NULL;
}
- msg += entry->tag_len;
+ msgptr += entry->tag_len;
- if (*((char *)msg + entry->msg_len - 1)) {
+ if (*((char *)msgptr + entry->msg_len - 1)) {
cfs_critical("unterminated string in log message");
return NULL;
}
*len = msg_len - 256;
*key = msg;
- *data = msg + 256;
+ *data = (char *) msg + 256;
return TRUE;
}
g_mutex_unlock (&mutex);
}
-