static ssize_t __tb_property_format_dir()

in property.c [348:475]


static ssize_t __tb_property_format_dir(const struct tb_property_dir *dir,
	u32 *block, unsigned int start_offset, size_t block_len)
{
	unsigned int data_offset, dir_end;
	const struct tb_property *property;
	struct tb_property_entry *entry;
	size_t dir_len, data_len = 0;
	int ret;

	/*
	 * The structure of property block looks like following. Leaf
	 * data/text is included right after the directory and each
	 * directory follows each other (even nested ones).
	 *
	 * +----------+ <-- start_offset
	 * |  header  | <-- root directory header
	 * +----------+ ---
	 * |  entry 0 | -^--------------------.
	 * +----------+  |                    |
	 * |  entry 1 | -|--------------------|--.
	 * +----------+  |                    |  |
	 * |  entry 2 | -|-----------------.  |  |
	 * +----------+  |                 |  |  |
	 * :          :  |  dir_len        |  |  |
	 * .          .  |                 |  |  |
	 * :          :  |                 |  |  |
	 * +----------+  |                 |  |  |
	 * |  entry n |  v                 |  |  |
	 * +----------+ <-- data_offset    |  |  |
	 * |  data 0  | <------------------|--'  |
	 * +----------+                    |     |
	 * |  data 1  | <------------------|-----'
	 * +----------+                    |
	 * | 00000000 | padding            |
	 * +----------+ <-- dir_end <------'
	 * |   UUID   | <-- directory UUID (child directory)
	 * +----------+
	 * |  entry 0 |
	 * +----------+
	 * |  entry 1 |
	 * +----------+
	 * :          :
	 * .          .
	 * :          :
	 * +----------+
	 * |  entry n |
	 * +----------+
	 * |  data 0  |
	 * +----------+
	 *
	 * We use dir_end to hold pointer to the end of the directory. It
	 * will increase as we add directories and each directory should be
	 * added starting from previous dir_end.
	 */
	dir_len = tb_property_dir_length(dir, false, &data_len);
	data_offset = start_offset + dir_len;
	dir_end = start_offset + data_len + dir_len;

	if (data_offset > dir_end)
		return -EINVAL;
	if (dir_end > block_len)
		return -EINVAL;

	/* Write headers first */
	if (dir->uuid) {
		struct tb_property_dir_entry *pe;

		pe = (struct tb_property_dir_entry *)&block[start_offset];
		memcpy(pe->uuid, dir->uuid, sizeof(pe->uuid));
		entry = pe->entries;
	} else {
		struct tb_property_rootdir_entry *re;

		re = (struct tb_property_rootdir_entry *)&block[start_offset];
		re->magic = TB_PROPERTY_ROOTDIR_MAGIC;
		re->length = dir_len - sizeof(*re) / 4;
		entry = re->entries;
	}

	list_for_each_entry(property, &dir->properties, list) {
		const struct tb_property_dir *child;

		format_dwdata(entry, property->key, 2);
		entry->type = property->type;

		switch (property->type) {
		case TB_PROPERTY_TYPE_DIRECTORY:
			child = property->value.dir;
			ret = __tb_property_format_dir(child, block, dir_end,
						       block_len);
			if (ret < 0)
				return ret;
			entry->length = tb_property_dir_length(child, false,
							       NULL);
			entry->value = dir_end;
			dir_end = ret;
			break;

		case TB_PROPERTY_TYPE_DATA:
			format_dwdata(&block[data_offset], property->value.data,
				      property->length);
			entry->length = property->length;
			entry->value = data_offset;
			data_offset += entry->length;
			break;

		case TB_PROPERTY_TYPE_TEXT:
			format_dwdata(&block[data_offset], property->value.text,
				      property->length);
			entry->length = property->length;
			entry->value = data_offset;
			data_offset += entry->length;
			break;

		case TB_PROPERTY_TYPE_VALUE:
			entry->length = property->length;
			entry->value = property->value.immediate;
			break;

		default:
			break;
		}

		entry++;
	}

	return dir_end;
}