view src/objstore/obj_dir.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 f53967574f3e
children a525d07c3fda
line wrap: on
line source

/*
 * Copyright (c) 2015-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"

static int __read_dir_block(struct objver *dirver, uint8_t *raw, size_t rawsize,
			    uint64_t off, uint16_t *ndirents)
{
	struct obj_dir_header hdr;
	ssize_t ret;

	ret = dirver->obj->ops->read(dirver, raw, rawsize, off);
	if (ret < 0)
		return ret;
	if (ret != DIR_BLOCK_SIZE)
		return -EIO; /* we require the whole dir block */

	memcpy(&hdr, raw, sizeof(struct obj_dir_header));
	hdr.magic    = be64_to_cpu(hdr.magic);
	hdr.ndirents = be16_to_cpu(hdr.ndirents);

	if (hdr.magic != OBJ_DIR_MAGIC)
		return -EIO; /* magic mismatch */

	/* ndirents must not be more than the max number of dirents per block */
	if (hdr.ndirents > (rawsize / sizeof(struct ndirent_phys)))
		return -EIO;

	*ndirents = hdr.ndirents;

	return 0;
}

int obj_make_dir_buffer(uint64_t this_host, uint64_t this_uniq,
			uint64_t parent_host, uint64_t parent_uniq,
			struct buffer *buf)
{
	struct ndirent_tgt tgt;
	struct dirblock block;
	int ret;

	dirblock_init(&block);

	ret = buffer_truncate(buf, 0);
	if (ret)
		return ret;

	/* '.' dirent */
	tgt.type = NDIRENT_TYPE_DIR;
	tgt.deleted = false;
	tgt.host = this_host;
	tgt.uniq = this_uniq;

	ret = dirblock_add_dirent(&block, ".", &tgt);
	if (ret)
		return ret;

	/* '..' dirent */
	tgt.type = NDIRENT_TYPE_DIR;
	tgt.deleted = false;
	tgt.host = parent_host;
	tgt.uniq = parent_uniq;

	ret = dirblock_add_dirent(&block, "..", &tgt);
	if (ret)
		return ret;

	/* serialize */
	return dirblock_serialize(&block, buf);
}

/* find an entry - no matter what its state is (conflicts, all_deleted, etc.) */
int dir_lookup_entry(struct objver *dirver, const char *name, uint8_t *raw,
		     struct ndirent_mem *ent, uint64_t *_off,
		     uint16_t *_ndirents)
{
	const size_t namelen = strlen(name);
	uint16_t ndirents;
	uint64_t off;
	uint16_t i;
	int ret;

	off = 0;

	/* for each directory block */
	do {
		uint8_t *cur;

		ret = __read_dir_block(dirver, raw, DIR_BLOCK_SIZE, off,
				       &ndirents);
		if (ret)
			return ret;

		/* for each dirent */
		cur = &raw[sizeof(struct obj_dir_header)];
		for (i = 0; i < ndirents; i++) {
			dirent_be2cpu(ent,
				      (void *) &raw[sizeof(struct obj_dir_header) +
						    sizeof(struct ndirent_phys) * i]);

			/* check the name */
			if (ent->namelen != namelen)
				continue;
			if (memcmp(&raw[ent->nameoff], name, namelen))
				continue;

			if (_off)
				*_off = off;
			if (_ndirents)
				*_ndirents = ndirents;

			return 0;
		}

		off += DIR_BLOCK_SIZE;
	} while (off < dirver->attrs.size);

	return -ENOENT;
}

int dir_lookup_one(struct objver *dirver, const char *name,
		   const struct noid *desired, struct noid *child, uint8_t *type)
{
	uint8_t raw[DIR_BLOCK_SIZE];
	struct ndirent_mem ent;
	struct ndirent_tgt tgt;
	struct buffer tgtbuf;
	size_t i;
	int ret;

	ret = dir_lookup_entry(dirver, name, raw, &ent, NULL, NULL);
	if (ret)
		return ret;

	if (ent.all_deleted)
		return -ENOENT;

	if (ent.conflicts)
		return -ENOTUNIQ;

	buffer_init_static(&tgtbuf, &raw[ent.tgtoff],
			   DIR_BLOCK_SIZE - ent.tgtoff,
			   DIR_BLOCK_SIZE - ent.tgtoff, false);

	/*
	 * There is exactly one non-deleted target, and zero or more deleted
	 * ones.  Find the right one and return it.
	 */

	for (i = 0; i < ent.ntgts; i++) {
		ret = unpack_dirent_tgt(&tgtbuf, &tgt);
		if (ret)
			return ret;

		if (!tgt.deleted)
			break;
	}

	VERIFY3U(i, <, ent.ntgts);

	if (tgt.type == NDIRENT_TYPE_GRAFT)
		noid_set(child, &tgt.graft, 0, 0);
	else
		noid_set(child, &dirver->obj->clone->uuid, tgt.host, tgt.uniq);

	/*
	 * is it the one we desire?
	 */
	if (!noid_is_null(desired))
		return noid_cmp(desired, child) ? -ENOENT : 0;
	else
		return 0;
}

static int __dir_lookup_all_realloc(size_t ntgts, struct noid **oids,
				    uint8_t **types, bool first)
{
	void *tmp;

	if (first) {
		*oids = NULL;
		*types = NULL;
	}

	tmp = mem_reallocarray(*oids, ntgts, sizeof(struct noid));
	if (!tmp)
		return -ENOMEM;

	*oids = tmp;

	tmp = mem_reallocarray(*types, ntgts, sizeof(uint8_t));
	if (!tmp)
		return -ENOMEM;

	*types = tmp;

	return 0;
}

ssize_t dir_lookup_all(struct objver *dirver, const char *name,
		       struct noid **child, uint8_t **type)
{
	uint8_t raw[DIR_BLOCK_SIZE];
	struct ndirent_mem ent;
	struct buffer tgtbuf;
	struct noid *oids;
	uint8_t *types;
	size_t ntgts;
	size_t i, j;
	int ret;

	ret = dir_lookup_entry(dirver, name, raw, &ent, NULL, NULL);
	if (ret)
		return ret;

	if (ent.all_deleted)
		return -ENOENT;

	/*
	 * There is one or more non-deleted target and zero or more deleted
	 * ones.  Find all the non-deleted ones and return them.
	 */

	/* start with 2 if there are conflicts; 1 is enough otherwise */
	ntgts = ent.conflicts ? 2 : 1;

	ret = __dir_lookup_all_realloc(ntgts, &oids, &types, true);
	if (ret)
		goto err;

	buffer_init_static(&tgtbuf, &raw[ent.tgtoff],
			   DIR_BLOCK_SIZE - ent.tgtoff,
			   DIR_BLOCK_SIZE - ent.tgtoff, false);

	for (i = 0, j = 0; i < ent.ntgts; i++) {
		struct ndirent_tgt tgt;

		ret = unpack_dirent_tgt(&tgtbuf, &tgt);
		if (ret)
			goto err;

		if (tgt.deleted)
			continue; /* deleted - skip */

		/* grow the arrays if needed */
		if (j == ntgts) {
			ntgts++;

			ret = __dir_lookup_all_realloc(ntgts, &oids, &types,
						       false);
			if (ret)
				goto err;
		}

		if (tgt.type == NDIRENT_TYPE_GRAFT)
			noid_set(&oids[j], &tgt.graft, 0, 0);
		else
			noid_set(&oids[j], &dirver->obj->clone->uuid, tgt.host,
				 tgt.uniq);

		types[j] = tgt.type;
		j++;
	}

	ASSERT3U(j, ==, ntgts);

	*child = oids;
	*type = types;

	return ntgts;

err:
	free(oids);
	free(types);

	return ret;
}

int dir_getdent(struct objver *dirver, const uint64_t offset,
		struct ndirent *child)
{
	uint8_t raw[DIR_BLOCK_SIZE];
	struct ndirent_mem dirent;
	uint16_t ndirents;
	uint64_t blkno, blkidx;
	char *name;
	int ret;

	blkno = offset / DIR_BLOCK_SIZE;
	blkidx = offset % DIR_BLOCK_SIZE;

restart:
	/* find the next entry >= supplied index */
	while ((blkno * DIR_BLOCK_SIZE) < dirver->attrs.size) {
		ret = __read_dir_block(dirver, raw, DIR_BLOCK_SIZE,
				       blkno * DIR_BLOCK_SIZE, &ndirents);
		if (ret)
			return ret;

		if (blkidx < ndirents)
			break;

		blkno++;
		blkidx = 0;
	}

	/* found EOF */
	if ((blkno * DIR_BLOCK_SIZE) >= dirver->attrs.size)
		return -ENOENT;

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

	/* all targets deleted - go to next dirent */
	if (dirent.all_deleted) {
		blkidx++;

		if (blkidx >= ndirents) {
			blkno++;
			blkidx = 0;
		}

		goto restart;
	}

	/* fill out `child` */
	name = malloc(dirent.namelen + 1);
	if (name) {
		child->dir_offset = (blkno * DIR_BLOCK_SIZE) + blkidx;
		child->name = name;
		child->namelen = dirent.namelen;

		if (!dirent.conflicts) {
			struct ndirent_tgt tgt;
			struct buffer tgtbuf;
			size_t i;

			buffer_init_static(&tgtbuf, &raw[dirent.tgtoff],
					   DIR_BLOCK_SIZE - dirent.tgtoff,
					   DIR_BLOCK_SIZE - dirent.tgtoff,
					   false);

			/*
			 * There is exactly one non-deleted target, and zero
			 * or more deleted ones.  Find the right one and
			 * return it.
			 */

			for (i = 0; i < dirent.ntgts; i++) {
				ret = unpack_dirent_tgt(&tgtbuf, &tgt);
				if (ret)
					goto err;

				if (!tgt.deleted)
					break;
			}

			VERIFY3U(i, <, dirent.ntgts);

			child->type = tgt.type;

			if (tgt.type == NDIRENT_TYPE_GRAFT)
				noid_set(&child->oid, &tgt.graft, 0, 0);
			else
				noid_set(&child->oid, &dirver->obj->clone->uuid,
					 be64_to_cpu(tgt.host),
					 be64_to_cpu(tgt.uniq));
		} else {
			child->type = NDIRENT_TYPE_MULTI;
		}

		memcpy(name, &raw[dirent.nameoff], dirent.namelen);
		name[dirent.namelen] = '\0';

		ret = 0;
	} else {
		ret = -ENOMEM;
	}

	return ret;

err:
	free(name);

	return ret;
}