view src/objstore/dirblock.c @ 800:f679541c8142

switch to new buffer_init_static libjeffpc API The function gained a second size argument removing the need for some truncate calls. Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Fri, 03 Apr 2020 15:54:51 -0400
parents 68939d7b83f8
children
line wrap: on
line source

/*
 * Copyright (c) 2020 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "dir.h"

/*
 * Used as a poison value in dirblock->entries[i].dirent.{tgt,name}off to
 * catch accidental use.
 */
#define DIRBLOCK_DIRENT_OFFSET_POISON	(0xffff)

void dirblock_init(struct dirblock *block)
{
	block->used_bytes = sizeof(struct obj_dir_header);
	block->name_bytes = 0;
	block->ndirents = 0;
	block->ntgts = 0;
}

/* parse a block */
int dirblock_parse(struct dirblock *block, uint8_t *raw, uint16_t ndirents)
{
	size_t i, j;

	dirblock_init(block);

	/* for each dirent */
	for (i = 0; i < ndirents; i++) {
		const int idx = block->ndirents;
		struct ndirent_mem *dirent = &block->entries[idx].dirent;
		size_t non_deleted_targets;
		struct buffer tgtbuf;
		int ret;

		dirent_be2cpu(dirent,
			      (void *) &raw[sizeof(struct obj_dir_header) +
					    sizeof(struct ndirent_phys) * i]);

		/* complete the fixed sized dirent */
		block->entries[idx].name = &block->names[block->name_bytes];
		block->entries[idx].tgts = &block->tgts[block->ntgts];

		/* save the name */
		memcpy(&block->names[block->name_bytes], &raw[dirent->nameoff],
		       dirent->namelen);

		/* save the targets */
		buffer_init_static(&tgtbuf, &raw[dirent->tgtoff],
				   DIR_BLOCK_SIZE - dirent->tgtoff,
				   DIR_BLOCK_SIZE - dirent->tgtoff, false);

		non_deleted_targets = 0;
		for (j = block->ntgts; j < (block->ntgts + dirent->ntgts); j++) {
			ret = unpack_dirent_tgt(&tgtbuf, &block->tgts[j]);
			if (ret)
				return ret;

			if (!block->tgts[j].deleted)
				non_deleted_targets++;
		}

		/* check and correct dirent flags */
		if (dirent->conflicts != (non_deleted_targets > 1))
			cmn_err(CE_WARN, "Parsed dirent conflicts (%s) "
				"mismatches targets (%zu !deleted)...fixed",
				dirent->conflicts ? "true" : "false",
				non_deleted_targets);
		if (dirent->all_deleted != (non_deleted_targets == 0))
			cmn_err(CE_WARN, "Parsed dirent all_deleted (%s) "
				"mismatches targets (%zu !deleted)...fixed",
				dirent->all_deleted ? "true" : "false",
				non_deleted_targets);

		dirent->conflicts = (non_deleted_targets > 1);
		dirent->all_deleted = (non_deleted_targets == 0);

		/* update block state */
		block->used_bytes += sizeof(struct ndirent_phys) +
			dirent->namelen + buffer_offset(&tgtbuf);
		block->name_bytes += dirent->namelen;
		block->ndirents++;
		block->ntgts += dirent->ntgts;

		/* poison the block's memory entries to avoid accidents */
		dirent->tgtoff = DIRBLOCK_DIRENT_OFFSET_POISON;
		dirent->nameoff = DIRBLOCK_DIRENT_OFFSET_POISON;
	}

	return 0;
}

static void __dirblock_serialize_check_dirent(struct dirblock *block,
					      size_t dirent)
{
	size_t non_deleted_targets;
	size_t tgt;

	ASSERT3U(block->entries[dirent].dirent.namelen, <=, MAX_NAME_LEN);

	/*
	 * Check that dirent's 'conflicts' and 'all_deleted' flags matche
	 * the targets
	 */
	non_deleted_targets = 0;
	for (tgt = 0; tgt < block->entries[dirent].dirent.ntgts; tgt++)
		if (!block->entries[dirent].tgts[tgt].deleted)
			non_deleted_targets++;

	if (block->entries[dirent].dirent.conflicts)
		VERIFY3U(non_deleted_targets, >, 1);
	else
		VERIFY3U(non_deleted_targets, <=, 1);

	if (block->entries[dirent].dirent.all_deleted)
		VERIFY3U(non_deleted_targets, ==, 0);
	else
		VERIFY3U(non_deleted_targets, >=, 1);
}

int dirblock_serialize(struct dirblock *block, struct buffer *buf)
{
	const size_t old_size = buffer_size(buf);
	struct obj_dir_header hdr;
	size_t off;
	size_t i, j;
	int ret;

	ASSERT3U(block->used_bytes, <=, DIR_BLOCK_SIZE);

	/* directory header */
	memset(&hdr, 0, sizeof(struct obj_dir_header));
	hdr.magic    = cpu64_to_be(OBJ_DIR_MAGIC);
	hdr.ndirents = cpu16_to_be(block->ndirents);

	ret = buffer_append(buf, &hdr, sizeof(struct obj_dir_header));
	if (ret)
		return ret;

	VERIFY3U(buffer_size(buf), ==, sizeof(struct obj_dir_header));

	off = sizeof(struct obj_dir_header) +
		(block->ndirents * sizeof(struct ndirent_phys));

	/* write out the fixed entries */
	for (i = 0; i < block->ndirents; i++) {
		struct ndirent_phys dirent;
		struct buffer tmp;

		__dirblock_serialize_check_dirent(block, i);

		/* fill in the missing pieces */
		block->entries[i].dirent.tgtoff = off +
			block->entries[i].dirent.namelen;
		block->entries[i].dirent.nameoff = off;

		dirent_cpu2be(&dirent, &block->entries[i].dirent);

		/* poison the block's memory entries to avoid accidents */
		block->entries[i].dirent.tgtoff = DIRBLOCK_DIRENT_OFFSET_POISON;
		block->entries[i].dirent.nameoff = DIRBLOCK_DIRENT_OFFSET_POISON;

		ret = buffer_append(buf, &dirent, sizeof(struct ndirent_phys));
		if (ret)
			return ret;

		/* determine how much space this dirent's targets will use */
		buffer_init_sink(&tmp);
		for (j = 0; j < block->entries[i].dirent.ntgts; j++) {
			ret = pack_dirent_tgt(&tmp, &block->entries[i].tgts[j]);
			if (ret)
				return ret;
		}

		off += block->entries[i].dirent.namelen + buffer_size(&tmp);

		VERIFY3U(off, <=, DIR_BLOCK_SIZE);
	}

	/* write out the names and targets */
	for (i = 0; i < block->ndirents; i++) {
		/* name */
		ret = buffer_append(buf, block->entries[i].name,
				    block->entries[i].dirent.namelen);
		if (ret)
			return ret;

		/* targets */
		for (j = 0; j < block->entries[i].dirent.ntgts; j++) {
			ret = pack_dirent_tgt(buf, &block->entries[i].tgts[j]);
			if (ret)
				return ret;
		}
	}

	ASSERT3U(buffer_size(buf) - old_size, <=, DIR_BLOCK_SIZE);

	return buffer_truncate(buf, old_size + DIR_BLOCK_SIZE);
}

static ssize_t __find_dirent(struct dirblock *block, const char *name)
{
	const size_t namelen = strlen(name);
	size_t i;

	for (i = 0; i < block->ndirents; i++) {
		if (namelen != block->entries[i].dirent.namelen)
			continue;

		if (strncmp(name, block->entries[i].name, namelen))
			continue;

		return i;
	}

	return -ENOENT;
}

static ssize_t __find_dirent_target(struct ndirent_mem *dirent,
				    const struct ndirent_tgt *tgts,
				    const struct ndirent_tgt *match)
{
	size_t i;

	for (i = 0; i < dirent->ntgts; i++) {
		if (match->type != tgts[i].type)
			continue;

		if ((match->type == NDIRENT_TYPE_GRAFT) &&
		    xuuid_compare(&match->graft, &tgts[i].graft))
			continue;

		if ((match->type != NDIRENT_TYPE_GRAFT) &&
		    ((match->host != tgts[i].host) ||
		     (match->uniq != tgts[i].uniq)))
			continue;

		/* not comparing flags on purpose */

		return i;
	}

	return -ENOENT;
}

int dirblock_add_dirent(struct dirblock *block, const char *name,
			const struct ndirent_tgt *tgt)
{
	const size_t namelen = strlen(name);
	struct buffer tgtbuf;
	size_t space_needed;
	ssize_t ret;
	size_t idx;

	if (namelen > MAX_NAME_LEN)
		return -ENAMETOOLONG;

	ret = __find_dirent(block, name);
	if (ret >= 0)
		return -EEXIST;
	if (ret != -ENOENT)
		return ret;

	buffer_init_sink(&tgtbuf);

	ret = pack_dirent_tgt(&tgtbuf, tgt);
	if (ret)
		return ret;

	space_needed = sizeof(struct ndirent_phys);
	space_needed += namelen;
	space_needed += buffer_size(&tgtbuf);

	if ((block->used_bytes + space_needed) > DIR_BLOCK_SIZE)
		return -ENOSPC;

	/* the above space check should cover these */
	VERIFY3U(block->ndirents + 1, <=, ARRAY_LEN(block->entries));
	VERIFY3U(block->name_bytes + namelen, <, sizeof(block->names));
	VERIFY3U(block->ntgts + 1, <=, ARRAY_LEN(block->tgts));

	/* save the fixed sized dirent */
	idx = block->ndirents;

	block->entries[idx].name = &block->names[block->name_bytes];
	block->entries[idx].dirent.tgtoff = DIRBLOCK_DIRENT_OFFSET_POISON;
	block->entries[idx].dirent.ntgts = 1;
	block->entries[idx].dirent.nameoff = DIRBLOCK_DIRENT_OFFSET_POISON;
	block->entries[idx].dirent.namelen = namelen;
	block->entries[idx].dirent.conflicts = false;
	block->entries[idx].dirent.all_deleted = tgt->deleted;
	block->entries[idx].tgts = &block->tgts[block->ntgts];

	/* save the name */
	memcpy(&block->names[block->name_bytes], name, namelen);

	/* save the target */
	block->tgts[block->ntgts] = *tgt;

	/* update block state */
	block->used_bytes += space_needed;
	block->name_bytes += namelen;
	block->ndirents++;
	block->ntgts++;

	return 0;
}

int dirblock_add_dirent_target(struct dirblock *block, const char *name,
			       const struct ndirent_tgt *tgt)
{
	struct ndirent_mem *dirent;
	struct buffer tgtbuf;
	size_t space_needed;
	ssize_t idx;
	ssize_t tmp;
	size_t i;
	int ret;

	idx = __find_dirent(block, name);
	if (idx < 0)
		return idx;

	dirent = &block->entries[idx].dirent;

	tmp = __find_dirent_target(dirent, block->entries[idx].tgts, tgt);
	if (tmp >= 0)
		return -EEXIST;
	if (tmp != -ENOENT)
		return tmp;

	buffer_init_sink(&tgtbuf);

	ret = pack_dirent_tgt(&tgtbuf, tgt);
	if (ret)
		return ret;

	space_needed = buffer_size(&tgtbuf);

	if ((block->used_bytes + space_needed) > DIR_BLOCK_SIZE)
		return -ENOSPC;

	/* for each subsequent dirent */
	for (i = block->ndirents - 1; i > idx; i--) {
		size_t size = sizeof(struct ndirent_tgt) *
			block->entries[idx].dirent.ntgts;

		/* shift the targets down one */
		memmove(&block->entries[idx].tgts[1],
			&block->entries[idx].tgts[0], size);

		/* update the tgts pointer */
		block->entries[idx].tgts = &block->entries[idx].tgts[1];
	}

	/* save the target */
	block->entries[idx].dirent.ntgts++;
	block->entries[idx].dirent.conflicts =
		block->entries[idx].dirent.conflicts ||
		(!block->entries[idx].dirent.all_deleted && !tgt->deleted);
	block->entries[idx].dirent.all_deleted =
		block->entries[idx].dirent.all_deleted && tgt->deleted;
	block->entries[idx].tgts[block->entries[idx].dirent.ntgts - 1] = *tgt;

	/* update block state */
	block->used_bytes += space_needed;
	block->ntgts++;

	return 0;
}