[pve-devel] [PATCH cluster v6 1/3] add CFS_IPC_GET_GUEST_CONFIG_PROPERTIES method

Thomas Lamprecht t.lamprecht at proxmox.com
Wed Apr 27 09:17:26 CEST 2022


On 12.04.22 15:34, Dominik Csapak wrote:
> for getting multiple properties from the in memory config of the
> guests. I added a new CSF_IPC_ call to maintain backwards compatibility.

"guests in one go. Keep the existing IPC call as is for backward
compatibility and add this as separate, new one".

> 
> It basically behaves the same as
> CFS_IPC_GET_GUEST_CONFIG_PROPERTY, but takes a list of properties
> instead.

I'd guess that return value is also different?

> 
> The old way of getting a single property is now also done by
> the new function.

maybe we could add a bit more info about changes with reasoning and even a benchmark
in the spirit of commit cf1b19d9adc57571b503737188d30a87e6044792 adding the original
fn, comparing numbers with using just one porperty on this vs. the previous fn to
argue that the change impact is (hopefully) negligible.

> 
> Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
> ---
>  data/src/cfs-ipc-ops.h |   2 +
>  data/src/server.c      |  64 +++++++++++++++
>  data/src/status.c      | 174 ++++++++++++++++++++++++++++-------------
>  data/src/status.h      |   3 +
>  4 files changed, 187 insertions(+), 56 deletions(-)
> 
> diff --git a/data/src/cfs-ipc-ops.h b/data/src/cfs-ipc-ops.h
> index 003e233..249308d 100644
> --- a/data/src/cfs-ipc-ops.h
> +++ b/data/src/cfs-ipc-ops.h
> @@ -43,4 +43,6 @@
>  
>  #define CFS_IPC_VERIFY_TOKEN 12
>  
> +#define CFS_IPC_GET_GUEST_CONFIG_PROPERTIES 13
> +
>  #endif
> diff --git a/data/src/server.c b/data/src/server.c
> index 549788a..c42ff69 100644
> --- a/data/src/server.c
> +++ b/data/src/server.c
> @@ -89,6 +89,13 @@ typedef struct {
>  	char property[];
>  } cfs_guest_config_propery_get_request_header_t;
>  
> +typedef struct {
> +	struct qb_ipc_request_header req_header;
> +	uint32_t vmid;
> +	uint8_t num_props;
> +	char props[]; /* list of \0 terminated properties */
> +} cfs_guest_config_properties_get_request_header_t;
> +
>  typedef struct {
>  	struct qb_ipc_request_header req_header;
>  	char token[];
> @@ -348,6 +355,63 @@ static int32_t s1_msg_process_fn(
>  
>  			result = cfs_create_guest_conf_property_msg(outbuf, memdb, rh->property, rh->vmid);
>  		}
> +	} else if (request_id == CFS_IPC_GET_GUEST_CONFIG_PROPERTIES) {
> +
> +		cfs_guest_config_properties_get_request_header_t *rh =
> +			(cfs_guest_config_properties_get_request_header_t *) data;
> +
> +		size_t remaining = request_size - G_STRUCT_OFFSET(cfs_guest_config_properties_get_request_header_t, props);
> +
> +		result = 0;
> +		if (rh->vmid < 100 && rh->vmid != 0) {
> +			cfs_debug("vmid out of range %u", rh->vmid);
> +			result = -EINVAL;
> +		} else if (rh->vmid >= 100 && !vmlist_vm_exists(rh->vmid)) {
> +			result = -ENOENT;
> +		} else if (rh->num_props == 0) {
> +			cfs_debug("num_props == 0");
> +			result = -EINVAL;
> +		} else if (remaining <= 1) {
> +			cfs_debug("property length <= 1, %ld", remaining);
> +			result = -EINVAL;
> +		} else {
> +			const char **properties = malloc(sizeof(char*) * rh->num_props);
> +			char *current = (rh->props);
> +			for (uint8_t i = 0; i < rh->num_props; i++) {
> +			    size_t proplen = strnlen(current, remaining);
> +			    if (proplen == 0) {
> +				cfs_debug("property length 0");
> +				result = -EINVAL;
> +				break;
> +			    }
> +			    if (proplen == remaining) {
> +				cfs_debug("property not \\0 terminated");
> +				result = -EINVAL;
> +				break;
> +			    }
> +			    if (current[0] < 'a' || current[0] > 'z') {
> +				cfs_debug("property does not start with [a-z]");
> +				result = -EINVAL;
> +				break;
> +			    }
> +			    properties[i] = current;
> +			    current[proplen] = '\0'; // ensure property is 0 terminated
> +			    remaining -= (proplen + 1);
> +			    current += proplen + 1;
> +			}
> +
> +			if (remaining != 0) {
> +			    cfs_debug("leftover data after parsing %lu properties", rh->num_props);
> +			    result = -EINVAL;
> +			}
> +
> +			if (result == 0) {
> +			    cfs_debug("cfs_get_guest_config_properties: basic valid checked, do request");
> +			    result = cfs_create_guest_conf_properties_msg(outbuf, memdb, properties, rh->num_props, rh->vmid);
> +			}
> +
> +			free(properties);
> +		}
>  	} else if (request_id == CFS_IPC_VERIFY_TOKEN) {
>  
>  		cfs_verify_token_request_header_t *rh = (cfs_verify_token_request_header_t *) data;
> diff --git a/data/src/status.c b/data/src/status.c
> index 9bceaeb..03454ec 100644
> --- a/data/src/status.c
> +++ b/data/src/status.c
> @@ -804,25 +804,52 @@ cfs_create_vmlist_msg(GString *str)
>  	return 0;
>  }
>  
> -// checks the conf for a line starting with '$prop:' and returns the value
> -// afterwards, whitout initial whitespace(s), we only deal with the format
> -// restricion imposed by our perl VM config parser, main reference is
> +// 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
> +char *
> +_get_property_value_from_line(char *line, int line_len, const char *prop, int 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];
> +
> +		// drop initial value whitespaces here already
> +		while (*v_start && isspace(*v_start)) v_start++;
> +
> +		if (!*v_start) return NULL;
> +
> +		char *v_end = &line[line_len - 1];
> +		while (v_end > v_start && isspace(*v_end)) v_end--;
> +		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 reges:
> +// 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*
> -// snapshort and *no* pending changes
> -static char *
> -_get_property_value(char *conf, int conf_size, const char *prop, int prop_len)
> +// snapshot and *no* pending changes
> +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;
> +	size_t remaining_size = conf_size;
> +	int count = 0;
>  
>  	char *next_newline = memchr(conf, '\n', conf_size);
>  	if (next_newline == NULL) {
> -		return NULL; // valid property lines end with \n, but none in the config
> +		return; // valid property lines end with \n, but none in the config
>  	}
>  	*next_newline = '\0';
>  
> @@ -830,41 +857,33 @@ _get_property_value(char *conf, int conf_size, const char *prop, int prop_len)
>  		if (!line[0]) goto next;
>  
>  		// snapshot or pending section start, but nothing found yet -> not found
> -		if (line[0] == '[') return NULL;
> -		// properties start with /^[a-z]/, so continue early if not
> -		if (line[0] < 'a' || line[0] > 'z') goto next;
> +		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;
>  
>  		int line_len = strlen(line);
> -		if (line_len <= prop_len + 1) goto next;
> -
> -		if (line[prop_len] == ':' && memcmp(line, prop, prop_len) == 0) { // found
> -			char *v_start = &line[prop_len + 1];
> -
> -			// drop initial value whitespaces here already
> -			while (*v_start && isspace(*v_start)) v_start++;
> -
> -			if (!*v_start) return NULL;
> -
> -			char *v_end = &line[line_len - 1];
> -			while (v_end > v_start && isspace(*v_end)) v_end--;
> -			v_end[1] = '\0';
> -
> -			return v_start;
> +		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) {
> +			// found all

comment belongs after return, avoid code bloat.

> +			return;
>  		}
>  next:
>  		line = next_newline + 1;
>  		remaining_size = conf_end - line;
> -		if (remaining_size <= prop_len) {
> -			return NULL;
> -		}
>  		next_newline = memchr(line, '\n', remaining_size);
>  		if (next_newline == NULL) {
> -			return NULL; // valid property lines end with \n, but none in the config
> +			return;

why do you delete the comment?

>  		}
>  		*next_newline = '\0';
>  	}
>  
> -	return NULL; // not found
> +	return;
>  }
>  
>  static void
> @@ -883,24 +902,73 @@ _g_str_append_kv_jsonescaped(GString *str, const char *k, const char *v)
>  }
>  
>  int
> -cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *prop, uint32_t vmid)
> +_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) {

if if (values[i] != NULL) {
    continue;
}

> +			if (found) {
> +				g_string_append_c(str, ',');
> +			} else {
> +				if (!first) g_string_append_printf(str, ",\n");
> +				else first = 0;
I'm somewaht OK with a standalone, single-line if's being without curly brackets and on one line in C,
but please avoid that for more complex ones, i.e., with more than one branch.



> +				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);
>  
> -	int prop_len = strlen(prop);
> -	int res = 0;
> -	GString *path = NULL;
> -
>  	// 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");
> +	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;
>  	}
> @@ -919,15 +987,8 @@ cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *pro
>  		// use memdb_read_nolock because lock is handled here
>  		int size = memdb_read_nolock(memdb, path->str, &tmp);
>  		if (tmp == NULL) goto err;
> -		if (size <= prop_len) goto ret;
> -
> -		char *val = _get_property_value(tmp, size, prop, prop_len);
> -		if (val == NULL) goto ret;
> -
> -		g_string_append_printf(str, "\"%u\":{", vmid);
> -		_g_str_append_kv_jsonescaped(str, prop, val);
> -		g_string_append_c(str, '}');
>  
> +		_print_found_properties(str, tmp, size, props, num_props, vmid, values, min, max, 1);
>  	} else {
>  		GHashTableIter iter;
>  		g_hash_table_iter_init (&iter, ht);
> @@ -943,21 +1004,16 @@ cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *pro
>  			tmp = NULL;
>  			// use memdb_read_nolock because lock is handled here
>  			int size = memdb_read_nolock(memdb, path->str, &tmp);
> -			if (tmp == NULL || size <= prop_len) continue;
> +			if (tmp == NULL) continue;
>  
> -			char *val = _get_property_value(tmp, size, prop, prop_len);
> -			if (val == NULL) continue;
> -
> -			if (!first) g_string_append_printf(str, ",\n");
> -			else first = 0;
> -
> -			g_string_append_printf(str, "\"%u\":{", vminfo->vmid);
> -			_g_str_append_kv_jsonescaped(str, prop, val);
> -			g_string_append_c(str, '}');
> +			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);
>  	}
> @@ -973,6 +1029,12 @@ 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)
>  {
> diff --git a/data/src/status.h b/data/src/status.h
> index bbf0948..041cb34 100644
> --- a/data/src/status.h
> +++ b/data/src/status.h
> @@ -163,4 +163,7 @@ cfs_create_memberlist_msg(
>  int
>  cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *prop, uint32_t vmid);
>  
> +int
> +cfs_create_guest_conf_properties_msg(GString *str, memdb_t *memdb, const char **props, uint8_t num_props, uint32_t vmid);
> +
>  #endif /* _PVE_STATUS_H_ */






More information about the pve-devel mailing list