int
v7fs_directory_remove_entry(struct v7fs_self *fs, struct v7fs_inode *parent_dir,
    const char *name)
{
	struct v7fs_inode inode;
	int error;
	struct v7fs_dirent lastdirent;
	v7fs_daddr_t lastblk;
	size_t sz, lastsz;
	v7fs_off_t pos;
	void *buf;

	/* Setup replaced entry. */
	sz = parent_dir->filesize;
	lastblk = v7fs_datablock_last(fs, parent_dir,
	    v7fs_inode_filesize(parent_dir));
	lastsz = V7FS_RESIDUE_BSIZE(sz);
	pos = lastsz - sizeof(lastdirent);

	if (!(buf = scratch_read(fs, lastblk)))
		return EIO;
	lastdirent = *((struct v7fs_dirent *)((uint8_t *)buf + pos));
	scratch_free(fs, buf);
	DPRINTF("last dirent=%d %s pos=%d\n",
	    V7FS_VAL16(fs, lastdirent.inode_number), lastdirent.name, pos);

	struct v7fs_lookup_arg lookup_arg =
	    { .name = name, .replace = &lastdirent/*disk endian */ };
	/* Search entry that removed. replace it to last dirent. */
	if ((error = v7fs_datablock_foreach(fs, parent_dir, remove_subr,
	    &lookup_arg)) != V7FS_ITERATOR_BREAK)
		return ENOENT;

	/* Contract dirent entries. */
	v7fs_datablock_contract(fs, parent_dir, sizeof(lastdirent));
	DPRINTF("done. (dirent size=%dbyte)\n", parent_dir->filesize);

	/* Target inode */
	if ((error = v7fs_inode_load(fs, &inode, lookup_arg.inode_number)))
		return error;

	if (v7fs_inode_isdir(&inode)) {
		parent_dir->nlink--;
		v7fs_inode_writeback(fs, parent_dir);
	}

	return 0;
}
int
v7fs_file_lookup_by_name(struct v7fs_self *fs, struct v7fs_inode *parent_dir,
    const char *name, v7fs_ino_t *ino)
{
	char filename[V7FS_NAME_MAX + 1];
	char *q;
	int error;
	size_t len;

	if ((q = strchr(name, '/'))) {
		/* Zap following path. */
		len = MIN(V7FS_NAME_MAX, q - name);
		memcpy(filename, name, len);
		filename[len] = '\0';	/* '/' -> '\0' */
	} else {
		v7fs_dirent_filename(filename, name);
	}
	DPRINTF("%s(%s) dir=%d\n", filename, name, parent_dir->inode_number);

	struct v7fs_lookup_arg lookup_arg = { .name = filename,
					      .inode_number = 0 };
	if ((error = v7fs_datablock_foreach(fs, parent_dir, lookup_subr,
		    &lookup_arg)) != V7FS_ITERATOR_BREAK) {
		DPRINTF("not found.\n");
		return ENOENT;
	}

	*ino = lookup_arg.inode_number;
	DPRINTF("done. ino=%d\n", *ino);

	return 0;
}

static int
lookup_subr(struct v7fs_self *fs, void *ctx, v7fs_daddr_t blk, size_t sz)
{
	struct v7fs_lookup_arg *p = (struct v7fs_lookup_arg *)ctx;
	struct v7fs_dirent *dir;
	const char *name = p->name;
	void *buf;
	size_t i, n;
	int ret = 0;

	if (!(buf = scratch_read(fs, blk)))
		return EIO;

	dir = (struct v7fs_dirent *)buf;
	n = sz / sizeof(*dir);
	v7fs_dirent_endian_convert(fs, dir, n);

	for (i = 0; i < n; i++, dir++) {
		if (dir->inode_number < 1) {
			DPRINTF("*** bad inode #%d ***\n", dir->inode_number);
			continue;
		}

		if (strncmp((const char *)dir->name, name, V7FS_NAME_MAX) == 0)
		{
			p->inode_number = dir->inode_number;
			ret =  V7FS_ITERATOR_BREAK; /* found */
			break;
		}
	}
	scratch_free(fs, buf);

	return ret;
}

int
v7fs_file_allocate(struct v7fs_self *fs, struct v7fs_inode *parent_dir,
    const char *srcname, struct v7fs_fileattr *attr, v7fs_ino_t *ino)
{
	struct v7fs_inode inode;
	char filename[V7FS_NAME_MAX + 1];
	struct v7fs_dirent *dir;
	int error;

	/* Truncate filename. */
	v7fs_dirent_filename(filename, srcname);
	DPRINTF("%s(%s)\n", filename, srcname);

	/* Check filename. */
	if (v7fs_file_lookup_by_name(fs, parent_dir, filename, ino) == 0) {
		DPRINTF("%s exists\n", filename);
		return EEXIST;
	}

	/* Get new inode. */
	if ((error = v7fs_inode_allocate(fs, ino)))
		return error;

	/* Set initial attribute. */
	memset(&inode, 0, sizeof(inode));
	inode.inode_number = *ino;
	inode.mode = attr->mode;
	inode.uid = attr->uid;
	inode.gid = attr->gid;
	if (attr->ctime)
		inode.ctime = attr->ctime;
	if (attr->mtime)
		inode.mtime = attr->mtime;
	if (attr->atime)
		inode.atime = attr->atime;

	switch (inode.mode & V7FS_IFMT)	{
	default:
		DPRINTF("Can't allocate %o type.\n", inode.mode);
		v7fs_inode_deallocate(fs, *ino);
		return EINVAL;
	case V7FS_IFCHR:
		/* FALLTHROUGH */
	case V7FS_IFBLK:
		inode.nlink = 1;
		inode.device = attr->device;
		inode.addr[0] = inode.device;
		break;
	case V7FSBSD_IFFIFO:
		/* FALLTHROUGH */
	case V7FSBSD_IFSOCK:
		/* FALLTHROUGH */
	case V7FSBSD_IFLNK:
		/* FALLTHROUGH */
	case V7FS_IFREG:
		inode.nlink = 1;
		break;
	case V7FS_IFDIR:
		inode.nlink = 2;	/* . + .. */
		if ((error = v7fs_datablock_expand(fs, &inode, sizeof(*dir) * 2
		    ))) {
			v7fs_inode_deallocate(fs, *ino);
			return error;
		}
		v7fs_daddr_t blk = inode.addr[0];
		void *buf;
		if (!(buf = scratch_read(fs, blk))) {
			v7fs_inode_deallocate(fs, *ino);
			return EIO;
		}
		dir = (struct v7fs_dirent *)buf;
		strcpy(dir[0].name, ".");
		dir[0].inode_number = V7FS_VAL16(fs, *ino);
		strcpy(dir[1].name, "..");
		dir[1].inode_number = V7FS_VAL16(fs, parent_dir->inode_number);
		if (!fs->io.write(fs->io.cookie, buf, blk)) {
			scratch_free(fs, buf);
			return EIO;
		}
		scratch_free(fs, buf);
		break;
	}

	v7fs_inode_writeback(fs, &inode);

	/* Link this inode to parent directory. */
	if ((error = v7fs_directory_add_entry(fs, parent_dir, *ino, filename)))
	{
		DPRINTF("can't add dirent.\n");
		return error;
	}

	return 0;
}

int
v7fs_file_deallocate(struct v7fs_self *fs, struct v7fs_inode *parent_dir,
    const char *name)
{
	v7fs_ino_t ino;
	struct v7fs_inode inode;
	int error;

	DPRINTF("%s\n", name);
	if ((error = v7fs_file_lookup_by_name(fs, parent_dir, name, &ino))) {
		DPRINTF("no such a file: %s\n", name);
		return error;
	}
	DPRINTF("%s->#%d\n", name, ino);
	if ((error = v7fs_inode_load(fs, &inode, ino)))
		return error;

	if (v7fs_inode_isdir(&inode)) {
		char filename[V7FS_NAME_MAX + 1];
		v7fs_dirent_filename(filename, name);
		/* Check parent */
		if (strncmp(filename, "..", V7FS_NAME_MAX) == 0) {
			DPRINTF("can not remove '..'\n");
			return EINVAL; /* t_vnops rename_dotdot */
		}
		/* Check empty */
		if (v7fs_inode_filesize(&inode) !=
		    sizeof(struct v7fs_dirent) * 2 /*"." + ".."*/) {
			DPRINTF("directory not empty.\n");
			return ENOTEMPTY;/* t_vnops dir_noempty, rename_dir(6)*/
		}
		inode.nlink = 0;	/* remove this. */
	} else {
		/* Decrement reference count. */
		--inode.nlink;	/* regular file. */
		DPRINTF("%s nlink=%d\n", name, inode.nlink);
	}


	if ((error = v7fs_directory_remove_entry(fs, parent_dir, name)))
		return error;
	DPRINTF("remove dirent\n");

	if (inode.nlink == 0) {
		v7fs_datablock_contract(fs, &inode, inode.filesize);
		DPRINTF("remove datablock\n");
		v7fs_inode_deallocate(fs, ino);
		DPRINTF("remove inode\n");
	} else {
		v7fs_inode_writeback(fs, &inode);
	}

	return 0;
}