view src/objstore/dir.h @ 862:9a63d933a60e

objstore: expose dirent add/update helpers to the rest of objstore These are useful anytime we want to create a new directory entry - create, link, symlink, etc. Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Sat, 17 Dec 2022 17:02:07 -0500
parents 3fc635b4636f
children
line wrap: on
line source

/*
 * Copyright (c) 2015-2020,2022 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.
 */

#ifndef __OBJSTORE_DIR_H
#define __OBJSTORE_DIR_H

#include "objstore_impl.h"

/*
 * A directory is a file with a special format and a NATTR_DIR mode.  The
 * contents of the "file" are as follows.
 *
 * The contents are divided into blocks of DIR_BLOCK_SIZE bytes.  Therefore,
 * the size of this file is *always* a multiple of this length.
 *
 * Each block is self contained.  It contains a header (struct
 * obj_dir_header) and zero or more directory entries.  Each directory entry
 * is made up of 3 parts: the fixed sized entry (struct ndirent_phys), a
 * name string (a uint8_t array without the trailing nul), and a CBOR
 * sequence of one or more targets.
 *
 * The fixed sized entries are packed into an array that begins immediately
 * after the block header.  This makes it easy to index into.
 *
 * There are no requirements about the placement of names and targets except
 * that they must begin after the fixed sized entries.  (But not necessarily
 * immediately after.)  Therefore, all the names and targets can be grouped,
 * interleaved, with arbitrary padding between them.
 *
 * Visually, each block looks like:
 *
 *   |<---------------- DIR_BLOCK_SIZE --------------->|
 *   |                                                 |
 *   +--------+----------------+-----------------+-----+
 *   | header | ndirent_phys[] | names & targets | pad |
 *   +--------+----------------+-----------------+-----+
 *
 * Each struct ndirent_phys refers to one or more targets.  The targets are
 * packed into a CBOR sequence with each entry in the sequence being an
 * definite length array consisting of:
 *
 *  - dirent type
 *  - dirent type specific target info
 *    - grafts:
 *      - uuid
 *    - other:
 *      - oid host
 *      - oid uniq
 *  - flags (see DIRENT_TGT_FLAG_*)
 *
 * Note: Because grafts and other dirent types have a different number of
 * values stored, the target CBOR array will have a different number of
 * elements depending on the dirent type.  Specifically, a graft will have 3
 * elements while other types will have 4.
 */

#define OBJ_DIR_MAGIC	0x4e4449524f424a30ull	/* "NDIROBJ0" */

#define DIR_BLOCK_SIZE	4096

struct obj_dir_header {
	uint64_t magic;		/* OBJ_DIR_MAGIC */
	uint16_t ndirents;	/* number of dirents in this block */
	uint8_t _pad[22];
} __attribute__((packed,aligned(8)));

#define MAX_NAME_LEN	UINT8_MAX
#define NDIRENT_FLAG_CONFLICTS	0x8000 /* >1 non-deleted target */
#define NDIRENT_FLAG_ALL_DELS	0x4000 /* 0 non-deleted targets */
#define NDIRENT_NTGTS_MASK	0x3fff
struct ndirent_phys {
	uint16_t tgtoff; /* blk offset with target array */
	uint16_t ntgts_and_flags;  /* number of targets; flags in the MSBs */
	uint16_t nameoff;/* blk offset with name */
	uint8_t namelen; /* name length */
} __attribute__((packed,aligned(1)));

STATIC_ASSERT(sizeof(struct obj_dir_header) == 32);
STATIC_ASSERT(sizeof(struct ndirent_phys) == 7);

/*
 * unpacked dirent for easy processing
 *
 * Note: This struct resembles what's stored on disk.  It is not to be
 * confused with struct ndirent, which represents a higher level view - one
 * which combines a dirent with one of its targets.
 */
struct ndirent_mem {
	uint16_t tgtoff; /* blk offset with target array */
	uint16_t ntgts;  /* number of targets in this dirent */
	uint16_t nameoff;/* blk offset with name */
	uint8_t namelen; /* name length */
	bool conflicts; /* more than one non-deleted target */
	bool all_deleted; /* all targets are deleted */
};

/*
 * packed dirent tgt uses a CBOR array
 *
 * Best (impossible) case, each will consist of:
 *  - the array type byte (1 byte)
 *  - the dirent type uint (1 bytes since types <= 23)
 *  - target oid (graft uuid is 1+16)
 *    - host uint (1 bytes)
 *    - uniq uint (1 bytes)
 *  - flags (1 bytes)
 *
 * Worst case, each will consist of:
 *  - the array type byte (1 byte)
 *  - the dirent type uint (1 bytes since types <= 23)
 *  - target oid (graft uuid is only 1+16)
 *    - host uint (1+8 bytes)
 *    - uniq uint (1+8 bytes)
 *  - flags (1+8 bytes)
 */
#define PACKED_DIRENT_TGT_MIN_LEN	(1 + 1 + 1 + 1 + 1)
#define PACKED_DIRENT_TGT_MAX_LEN	(1 + 1 + (1 + 8) + (1 + 8) + (1 + 8))

/* dirent target flags */
#define DIRENT_TGT_FLAG_DELETED		0x0001 /* target removed by user */
#define DIRENT_TGT_ALL_FLAGS \
	(DIRENT_TGT_FLAG_DELETED)

/* unpacked dirent tgt for easy processing */
struct ndirent_tgt {
	uint8_t type;

	bool deleted;

	union {
		struct xuuid graft;
		struct {
			uint64_t host;
			uint64_t uniq;
		};
	};
};

/*
 * an easier to work with representation of a directory block
 *
 * To avoid unnecessary memory allocations, it includes more than enough
 * inline space for the fixed sized entries, their names, as well as their
 * targets.  (Technically, it has enough space for the worst possible case
 * for each - in other words, it is a bit wasteful.)
 */
#define DIRBLOCK_USABLE_SPACE	(DIR_BLOCK_SIZE - sizeof(struct obj_dir_header))

#define DIRBLOCK_MIN_NAME_SIZE	1 /* 1 byte min */
#define DIRBLOCK_MIN_ENTRY_SIZE	\
	(sizeof(struct ndirent_phys) + PACKED_DIRENT_TGT_MIN_LEN + \
	 DIRBLOCK_MIN_NAME_SIZE)

/* max number of fixed sized entries in a block */
#define DIRBLOCK_MAX_DIRENT_COUNT \
		(DIRBLOCK_USABLE_SPACE / DIRBLOCK_MIN_ENTRY_SIZE)
/* max number of bytes used by names in a block */
#define DIRBLOCK_MAX_NAME_SIZE \
		(DIRBLOCK_USABLE_SPACE - sizeof(struct ndirent_phys) - \
		 PACKED_DIRENT_TGT_MIN_LEN)
/* max number of targets in a block */
#define DIRBLOCK_MAX_TGT_COUNT \
		((DIRBLOCK_USABLE_SPACE - sizeof(struct ndirent_phys) - \
		  DIRBLOCK_MIN_NAME_SIZE) / PACKED_DIRENT_TGT_MIN_LEN)

/* make sure we have enough bits to keep track of all targets */
STATIC_ASSERT(NDIRENT_NTGTS_MASK >= DIRBLOCK_MAX_TGT_COUNT);

struct dirblock {
	size_t used_bytes; /* total bytes used in this block */
	size_t name_bytes; /* bytes used in this block for names */
	uint16_t ndirents; /* total number of dirents */
	uint16_t ntgts; /* total number of targets */

	struct {
		struct ndirent_mem dirent;

		/*
		 * instead of using tgtoff and nameoff in the above dirent,
		 * we use real pointers for convenience
		 */
		const char *name;
		struct ndirent_tgt *tgts;
	} entries[DIRBLOCK_MAX_DIRENT_COUNT];

	/* all the names concat'd, no trailing nuls */
	char names[DIRBLOCK_MAX_NAME_SIZE];

	/* all the targets concated */
	struct ndirent_tgt tgts[DIRBLOCK_MAX_TGT_COUNT];
};

extern void dirblock_init(struct dirblock *block);
extern int dirblock_parse(struct dirblock *block, uint8_t *raw, uint16_t ndirents);
extern int dirblock_serialize(struct dirblock *block, struct buffer *buf);
extern int dirblock_add_dirent(struct dirblock *block, const char *name,
			       const struct ndirent_tgt *tgt);
extern int dirblock_add_dirent_target(struct dirblock *block, const char *name,
				      const struct ndirent_tgt *tgt);

extern int dir_add_dirent(struct txn *txn, struct objver *dirver,
			  const char *name, uint16_t mode,
			  const struct noid *child);
extern int dir_update_dirent(struct txn *txn, struct objver *dirver,
			     uint8_t *raw, uint64_t diroff,
			     uint16_t ndirents, const char *name,
			     uint16_t mode, const struct noid *child);
extern int dir_lookup_entry(struct objver *dirver, const char *name,
			    uint8_t *raw, struct ndirent_mem *ent,
			    uint64_t *_off, uint16_t *_ndirents);

extern void dirent_cpu2be(struct ndirent_phys *phys, const struct ndirent_mem *mem);
extern void dirent_be2cpu(struct ndirent_mem *mem, const struct ndirent_phys *phys);

extern int pack_dirent_tgt(struct buffer *buf, const struct ndirent_tgt *tgt);
extern int unpack_dirent_tgt(struct buffer *buf, struct ndirent_tgt *tgt);

#endif