#include "SCK.H"

#define FLAGS_NEED_RDEV		1
#define FLAGS_HAVE_RDEV		2
#define FLAGS_DONT_HAVE_RDEV	4

static char *check_specific(struct fnode *fnode, struct fnode_ea *ea, int type, int *flags)
{
	switch (ea->magic & FNODE_EA_MAGIC_MASK) {
		case EA_UNX_MAGIC & FNODE_EA_MAGIC_MASK: {
#define unx ((struct ea_unx *)ea)
			if (__unlikely(ea->magic != EA_UNX_MAGIC)) return "UNX ATTRIBUTE HAS INVALID LENGTH";
			if (__unlikely(!(type & EA_TYPE_FILE))) return "UNX ATTRIBUTE NOT ALLOWED ON HARDLINK";
			if (!LINUX_S_ISLNK(unx->mode) && !LINUX_S_ISREG(unx->mode) && !LINUX_S_ISDIR(unx->mode) && !LINUX_S_ISCHR(unx->mode) && !LINUX_S_ISBLK(unx->mode) && !LINUX_S_ISFIFO(unx->mode) && !LINUX_S_ISSOCK(unx->mode))
				return "UNX ATTRIBUTE ERROR: INVALID MODE";
			if (__unlikely(fnode->flags & FNODE_FLAGS_DIR)) {
				if (__unlikely(!LINUX_S_ISDIR(unx->mode))) return "UNX ATTRIBUTE ERROR: DIRECTORY HAS NON-DIRECTORY MODE";
			} else {
				if (__unlikely(LINUX_S_ISDIR(unx->mode))) return "UNX ATTRIBUTE ERROR: NON-DIRECTORY HAS DIRECTORY MODE";
			}
			if (__likely(LINUX_S_ISREG(unx->mode))) {
				__u64 size = cc_valid(fnode->cc, fnode->txc) ? fnode->size0 : fnode->size1;
				unsigned prealloc = cc_valid(fnode->cc, fnode->txc) ? unx->prealloc0 : unx->prealloc1;
				if (__unlikely(prealloc > size)) return "UNX ATTRIBUTE ERROR: PREALLOC IS LARGER THAN FILE SIZE";
			} else {
				if (__unlikely((unx->prealloc0 | unx->prealloc1) != 0)) return "UNX ATTRIBUTE ERROR: NON-FILE FNODE HAS PREALLOC";
			}
			if (__unlikely(LINUX_S_ISCHR(unx->mode)) || __unlikely(LINUX_S_ISBLK(unx->mode))) {
				if (*flags & FLAGS_DONT_HAVE_RDEV) return "UNX ATTRIBUTE ERROR: DEVICE DOESN'T HAVE RDEV ATTRIBUTE";
				*flags |= FLAGS_NEED_RDEV;
			}
			break;
#undef unx
		}
		case EA_SYMLINK_MAGIC & FNODE_EA_MAGIC_MASK: {
			unsigned len = ea->magic >> FNODE_EA_SIZE_SHIFT;
			unsigned i;
			if (__unlikely(!len)) return "SYMLINK ATTRIBUTE HAS ZERO LENGTH";
			if (__unlikely(!(type & EA_TYPE_FILE))) return "SYMLINK ATTRIBUTE NOT ALLOWED ON HARDLINK";
			for (i = 0; i < len; i++) if (__unlikely(!((char *)(ea + 1))[i])) return "SYMLINK ATTRIBUTE CONTAINS NULL CHARACTER";
			break;
		}
		case EA_RDEV_MAGIC & FNODE_EA_MAGIC_MASK: {
			if (__unlikely(ea->magic != EA_RDEV_MAGIC)) return "RDEV ATTRIBUTE HAS INVALID LENGTH";
			if (__unlikely(!(type & EA_TYPE_FILE))) return "RDEV ATTRIBUTE NOT ALLOWED ON HARDLINK";
			*flags |= FLAGS_HAVE_RDEV;
			break;
		}
	}
	return NULL;
}

static char *check_ea_err(struct fnode *fnode, struct fnode_ea *ea_start, unsigned ea_size, int type, unsigned *err_offset, unsigned *err_size)
{
	unsigned off;
	int flags = 0;
	again:
	off = 0;
	while (off < ea_size) {
		char *c;
		struct fnode_ea *ea = (struct fnode_ea *)((char *)ea_start + off);
		unsigned rec_size = (((ea->magic >> FNODE_EA_SIZE_SHIFT) & FNODE_EA_SIZE_MASK_1) + FNODE_EA_SIZE_ADD) & FNODE_EA_SIZE_MASK_2;
		*err_offset = off;
		if (__unlikely(off + rec_size > ea_size)) {
			*err_size = ea_size - off;
			return "RECORD BEYOND END";
		}
		*err_size = rec_size;
		{
			unsigned off1 = 0;
			while (off1 < off) {
				struct fnode_ea *ea1 = (struct fnode_ea *)((char *)ea_start + off1);
				if (__unlikely((ea1->magic & FNODE_EA_MAGIC_MASK) == (ea->magic & FNODE_EA_MAGIC_MASK))) {
					return "DUPLICATE ATTRIBUTE";
				}
				off1 += (((ea1->magic >> FNODE_EA_SIZE_SHIFT) & FNODE_EA_SIZE_MASK_1) + FNODE_EA_SIZE_ADD) & FNODE_EA_SIZE_MASK_2;
			}
			if (__unlikely(off != off1))
				KERNEL$SUICIDE("check_ea_err: desynced when scanning for duplicate ea (%u != %u)", off, off1);
		}
		c = check_specific(fnode, ea, type, &flags);
		if (__unlikely(c != NULL)) return c;
		off += rec_size;
	}
	if (__unlikely((flags & (FLAGS_NEED_RDEV | FLAGS_HAVE_RDEV)) == FLAGS_NEED_RDEV)) {
		flags |= FLAGS_DONT_HAVE_RDEV;
		goto again;
	}
	return NULL;
}

char *check_ea(struct fnode *fnode, struct fnode_ea *ea_start, unsigned ea_size, int type)
{
	unsigned sink1, sink2;
	return check_ea_err(fnode, ea_start, ea_size, type, &sink1, &sink2);
}

void fix_ea(struct fnode *fnode, struct fnode_ea *ea, unsigned *ea_size, int type)
{
	unsigned err_offset, err_size;
	while (__unlikely(check_ea_err(fnode, ea, *ea_size, type, &err_offset, &err_size) != NULL)) {
		memmove((char *)ea + err_offset, (char *)ea + err_offset + err_size, *ea_size - err_offset - err_size);
		*ea_size -= err_size;
	}
}
