/*
 * nilfs.c - NILFS library.
 *
 * Copyright (C) 2007-2012 Nippon Telegraph and Telephone Corporation.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * Credits:
 *    Koji Sato
 *    Ryusuke Konishi <konishi.ryusuke@gmail.com>
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif	/* HAVE_CONFIG_H */

#include <stdio.h>

#if HAVE_STDLIB_H
#include <stdlib.h>
#endif	/* HAVE_STDLIB_H */

#if HAVE_UNISTD_H
#include <unistd.h>
#endif	/* HAVE_UNISTD_H */

#include <ctype.h>

#if HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif	/* HAVE_SYS_TYPES_H */

#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif	/* HAVE_SYS_STAT_H */

#if HAVE_STRING_H
#include <string.h>
#endif	/* HAVE_STRING_H */

#if HAVE_FCNTL_H
#include <fcntl.h>
#endif	/* HAVE_FCNTL_H */

#if HAVE_LIMITS_H
#include <limits.h>
#endif	/* HAVE_LIMITS_H */

#if HAVE_SEMAPHORE_H
#include <semaphore.h>
#endif	/* HAVE_SEMAPHORE_H */

#if HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif	/* HAVE_SYS_MMAN_H */

#if HAVE_TIME_H
#include <time.h>
#endif	/* HAVE_TIME_H */

#if HAVE_LINUX_TYPES_H
#include <linux/types.h>
#endif	/* HAVE_LINUX_TYPES_H */

/* compat.h must be included before on-disk definitions for sparse checks */
#include "compat.h"
#include <linux/nilfs2_ondisk.h>

#include <errno.h>
#include <assert.h>
#include <mntent.h>	/* setmntent, getmntent_r, endmntent, etc */

#include "nilfs.h"
#include "util.h"
#include "pathnames.h"
#include "realpath.h"
#include "lookup_device.h"	/* nilfs_lookup_device() */

/**
 * struct nilfs - nilfs object
 * @n_sb: superblock
 * @n_dev: device file
 * @n_ioc: ioctl file
 * @n_devfd: file descriptor of device file
 * @n_iocfd: file descriptor of ioctl file
 * @n_opts: options
 * @n_mincno: the minimum of valid checkpoint numbers
 * @n_sems: array of semaphores
 *     sems[0] protects garbage collection process
 */
struct nilfs {
	struct nilfs_super_block *n_sb;
	char *n_dev;
	/* char *n_mnt; */
	char *n_ioc;
	int n_devfd;
	int n_iocfd;
	int n_opts;
	nilfs_cno_t n_mincno;
	sem_t *n_sems[1];
};

enum {
	NILFS_OPT_MMAP,
	NILFS_OPT_SET_SUINFO,
	__NR_NILFS_OPT,
};


#define MNTOPT_RW	"rw"
#define MNTOPT_RO	"ro"

#ifndef LINE_MAX
#define LINE_MAX	2048
#endif	/* LINE_MAX */

static int nilfs_find_fs(struct nilfs *nilfs, const char *dev, const char *dir,
			 const char *opt)
{
	struct mntent *mntent, mntbuf;
	char buf[LINE_MAX];
	char canonical[PATH_MAX + 2];
	char *cdev = NULL, *cdir = NULL;
	char *mdev, *mdir;
	FILE *fp;
	int ret;

	ret = -1;
	if (dev && myrealpath(dev, canonical, sizeof(canonical))) {
		cdev = strdup(canonical);
		if (unlikely(!cdev))
			goto failed;
		dev = cdev;
	}

	if (dir && myrealpath(dir, canonical, sizeof(canonical))) {
		cdir = strdup(canonical);
		if (unlikely(!cdir))
			goto failed_dev;
		dir = cdir;
	}

	fp = setmntent(_PATH_PROC_MOUNTS, "r");
	if (unlikely(fp == NULL))
		goto failed_dir;

	while ((mntent = getmntent_r(fp, &mntbuf, buf, sizeof(buf))) != NULL) {
		if (strcmp(mntent->mnt_type, NILFS_FSTYPE) != 0)
			continue;

		if (dir != NULL) {
			mdir = mntent->mnt_dir;
			if (myrealpath(mdir, canonical, sizeof(canonical)))
				mdir = canonical;
			if (strcmp(mdir, dir) != 0)
				continue;
		}

		if (dev != NULL) {
			mdev = mntent->mnt_fsname;
			if (myrealpath(mdev, canonical, sizeof(canonical)))
				mdev = canonical;
			if (strcmp(mdev, dev) != 0)
				continue;
		}

		if (hasmntopt(mntent, opt)) {
			free(nilfs->n_dev);
			nilfs->n_dev = strdup(mntent->mnt_fsname);
			if (unlikely(nilfs->n_dev == NULL)) {
				free(nilfs->n_ioc);
				nilfs->n_ioc = NULL;
				ret = -1;
				goto failed_proc_mounts;
			}
			free(nilfs->n_ioc);
			nilfs->n_ioc = strdup(mntent->mnt_dir);
			if (unlikely(nilfs->n_ioc == NULL)) {
				free(nilfs->n_dev);
				nilfs->n_dev = NULL;
				ret = -1;
				goto failed_proc_mounts;
			}
			ret = 0;
		}
	}
	if (ret < 0)
		errno = ENXIO;

 failed_proc_mounts:
	endmntent(fp);

 failed_dir:
	free(cdir);

 failed_dev:
	free(cdev);

 failed:
	return ret;
}

/**
 * nilfs_get_layout - get layout information of nilfs
 * @nilfs: nilfs object
 * @layout: buffer to nilfs_layout struct
 * @layout_size: size of layout structure (used to ensure compatibility)
 */
ssize_t nilfs_get_layout(const struct nilfs *nilfs,
			 struct nilfs_layout *layout, size_t layout_size)
{
	const struct nilfs_super_block *sb = nilfs->n_sb;

	if (unlikely(layout_size < 0x50)) {
		errno = EINVAL;
		return -1;
	}

	if (unlikely(sb == NULL)) {
		errno = EPERM;
		return -1;
	}

	layout->rev_level = le32_to_cpu(sb->s_rev_level);
	layout->minor_rev_level = le16_to_cpu(sb->s_minor_rev_level);
	layout->flags = le16_to_cpu(sb->s_flags);
	layout->blocksize_bits = le32_to_cpu(sb->s_log_block_size) + 10;
	layout->blocksize = 1UL << layout->blocksize_bits;
	layout->devsize = le64_to_cpu(sb->s_dev_size);
	layout->crc_seed = le32_to_cpu(sb->s_crc_seed);
	layout->pad = 0;
	layout->nsegments = le64_to_cpu(sb->s_nsegments);
	layout->blocks_per_segment = le32_to_cpu(sb->s_blocks_per_segment);
	layout->reserved_segments_ratio =
		le32_to_cpu(sb->s_r_segments_percentage);
	layout->first_segment_blkoff = le64_to_cpu(sb->s_first_data_block);
	layout->feature_compat = le64_to_cpu(sb->s_feature_compat);
	layout->feature_compat_ro = le64_to_cpu(sb->s_feature_compat_ro);
	layout->feature_incompat = le64_to_cpu(sb->s_feature_incompat);

	return sizeof(struct nilfs_layout);
}

/**
 * nilfs_get_block_size - get block size of the file system
 * @nilfs: nilfs object
 */
size_t nilfs_get_block_size(const struct nilfs *nilfs)
{
	assert(nilfs->n_sb != NULL);
	return 1UL << (le32_to_cpu(nilfs->n_sb->s_log_block_size) + 10);
}

/**
 * nilfs_get_nsegments - get number of segments
 * @nilfs: nilfs object
 */
uint64_t nilfs_get_nsegments(const struct nilfs *nilfs)
{
	assert(nilfs->n_sb != NULL);
	return le64_to_cpu(nilfs->n_sb->s_nsegments);
}

/**
 * nilfs_get_blocks_per_segment - get number of blocks per segment
 * @nilfs: nilfs object
 */
uint32_t nilfs_get_blocks_per_segment(const struct nilfs *nilfs)
{
	assert(nilfs->n_sb != NULL);
	return le32_to_cpu(nilfs->n_sb->s_blocks_per_segment);
}

/**
 * nilfs_get_reserved_segments_ratio - get ratio of reserved segments
 * @nilfs: nilfs object
 */
uint32_t nilfs_get_reserved_segments_ratio(const struct nilfs *nilfs)
{
	assert(nilfs->n_sb != NULL);
	return le32_to_cpu(nilfs->n_sb->s_r_segments_percentage);
}

static int __nilfs_opt_set_mmap(struct nilfs *nilfs)
{
	long pagesize;
	size_t segsize;

	pagesize = sysconf(_SC_PAGESIZE);
	if (unlikely(pagesize < 0))
		return -1;

	if (unlikely(nilfs->n_sb == NULL)) {
		errno = EPERM;
		return -1;
	}

	segsize = nilfs_get_blocks_per_segment(nilfs) *
		nilfs_get_block_size(nilfs);
	if (segsize % pagesize != 0)
		return -1;

	return 0;
}

/**
 * nilfs_opt_test - test whether the specified option is set or not
 * @nilfs: nilfs object
 * @index: index of the option to be tested
 */
int nilfs_opt_test(const struct nilfs *nilfs, unsigned int index)
{
	if (unlikely(index >= __NR_NILFS_OPT)) {
		errno = EINVAL;
		return 0;	/* return false for unknown options */
	}
	return !!(nilfs->n_opts & (1 << index));
}

/**
 * nilfs_opt_set - set the specified option
 * @nilfs: nilfs object
 * @index: index of the option to be set
 */
int nilfs_opt_set(struct nilfs *nilfs, unsigned int index)
{
	int ret;

	if (unlikely(index >= __NR_NILFS_OPT)) {
		errno = EINVAL;
		return -1;
	}

	if (index == NILFS_OPT_MMAP) {
		ret = __nilfs_opt_set_mmap(nilfs);
		if (ret < 0)
			return ret;
	}
	nilfs->n_opts |= (1 << index);
	return 0;
}

/**
 * nilfs_opt_clear - clear the specified option
 * @nilfs: nilfs object
 * @index: index of the option to be cleared
 */
int nilfs_opt_clear(struct nilfs *nilfs, unsigned int index)
{
	if (unlikely(index >= __NR_NILFS_OPT)) {
		errno = EINVAL;
		return -1;
	}
	nilfs->n_opts &= ~(1 << index);
	return 0;
}

static int nilfs_open_sem(struct nilfs *nilfs)
{
	char semnambuf[NAME_MAX - 4];
	struct stat stbuf;
	int ret;

	ret = stat(nilfs->n_dev, &stbuf);
	if (unlikely(ret < 0))
		return -1;

	if (S_ISBLK(stbuf.st_mode)) {
		ret = snprintf(semnambuf, sizeof(semnambuf),
			       "/nilfs-cleaner-%llu",
			       (unsigned long long)stbuf.st_rdev);
	} else if (S_ISREG(stbuf.st_mode) || S_ISDIR(stbuf.st_mode)) {
		ret = snprintf(semnambuf, sizeof(semnambuf),
			       "/nilfs-cleaner-%llu-%llu",
			       (unsigned long long)stbuf.st_dev,
			       (unsigned long long)stbuf.st_ino);
	} else {
		errno = EINVAL;
		return -1;
	}

	if (unlikely(ret < 0))
		return -1;

	assert(ret < sizeof(semnambuf));

	nilfs->n_sems[0] = sem_open(semnambuf, O_CREAT, S_IRWXU, 1);
	if (unlikely(nilfs->n_sems[0] == SEM_FAILED)) {
		nilfs->n_sems[0] = NULL;
		return -1;
	}
	return 0;
}

/**
 * nilfs_open - create a NILFS object
 * @dev: device
 * @dir: mount directory
 * @flags: open flags
 */
struct nilfs *nilfs_open(const char *dev, const char *dir, int flags)
{
	struct nilfs *nilfs;
	char *backdev;
	uint64_t features;
	int ret;

	if (unlikely(!(flags & (NILFS_OPEN_RAW | NILFS_OPEN_RDONLY |
				NILFS_OPEN_WRONLY | NILFS_OPEN_RDWR)))) {
		errno = EINVAL;
		return NULL;
	}

	nilfs = malloc(sizeof(*nilfs));
	if (unlikely(nilfs == NULL))
		return NULL;

	nilfs->n_sb = NULL;
	nilfs->n_devfd = -1;
	nilfs->n_iocfd = -1;
	nilfs->n_dev = NULL;
	nilfs->n_ioc = NULL;
	nilfs->n_opts = 0;
	nilfs->n_mincno = NILFS_CNO_MIN;
	memset(nilfs->n_sems, 0, sizeof(nilfs->n_sems));
	backdev = NULL;

	if ((flags & NILFS_OPEN_SRCHDEV) && dev) {
		ret = nilfs_lookup_device(dev, &backdev);
		if (unlikely(ret < 0))
			goto out_fd;
		else if (ret > 0)
			dev = backdev;  /* replace dev in this function */
	}

	if (flags & NILFS_OPEN_RAW) {
		if (dev == NULL) {
			if (nilfs_find_fs(nilfs, dev, dir, MNTOPT_RW) < 0)
				goto out_fd;
		} else {
			nilfs->n_dev = strdup(dev);
			if (unlikely(nilfs->n_dev == NULL))
				goto out_fd;
		}
		nilfs->n_devfd = open(nilfs->n_dev, O_RDONLY);
		if (unlikely(nilfs->n_devfd < 0))
			goto out_fd;

		nilfs->n_sb = nilfs_sb_read(nilfs->n_devfd);
		if (unlikely(nilfs->n_sb == NULL))
			goto out_fd;

		features = le64_to_cpu(nilfs->n_sb->s_feature_incompat) &
			~NILFS_FEATURE_INCOMPAT_SUPP;
		if (unlikely(features)) {
			errno = ENOTSUP;
			goto out_fd;
		}
	}

	if (flags &
	    (NILFS_OPEN_RDONLY | NILFS_OPEN_WRONLY | NILFS_OPEN_RDWR)) {
		struct stat iocst, devst;

		if (nilfs_find_fs(nilfs, dev, dir, MNTOPT_RW) < 0) {
			if (!(flags & NILFS_OPEN_RDONLY))
				goto out_fd;
			if (nilfs_find_fs(nilfs, dev, dir, MNTOPT_RO) < 0)
				goto out_fd;
		}
		nilfs->n_iocfd = open(nilfs->n_ioc, O_RDONLY);
		if (unlikely(nilfs->n_iocfd < 0))
			goto out_fd;

		/*
		 * Check if the device of mount point nilfs->n_ioc is the same
		 * as the device of nilfs->n_dev.  If not, return an error with
		 * errno = ENOENT since the mount point is hidden under other
		 * mounts and inaccessible.
		 */
		ret = stat(nilfs->n_dev, &devst);
		if (unlikely(ret < 0))
			goto out_fd;
		if (!S_ISBLK(devst.st_mode)) {
			errno = ENOTBLK;
			goto out_fd;
		}
		ret = fstat(nilfs->n_iocfd, &iocst);
		if (unlikely(ret < 0))
			goto out_fd;
		if (!S_ISDIR(iocst.st_mode) || iocst.st_dev != devst.st_rdev) {
			errno = ENOENT;
			goto out_fd;
		}
	}

	if (flags & NILFS_OPEN_GCLK) {
		/* Initialize cleaner semaphore */
		ret = nilfs_open_sem(nilfs);
		if (unlikely(ret < 0))
			goto out_fd;
	}

	/* success */
	free(backdev);
	return nilfs;

	/* error */
out_fd:
	if (nilfs->n_devfd >= 0)
		close(nilfs->n_devfd);
	if (nilfs->n_iocfd >= 0)
		close(nilfs->n_iocfd);

	free(backdev);
	free(nilfs->n_dev);
	free(nilfs->n_ioc);
	free(nilfs->n_sb);
	free(nilfs);
	return NULL;
}

/**
 * nilfs_close - destroy a NILFS object
 * @nilfs: NILFS object
 */
void nilfs_close(struct nilfs *nilfs)
{
	if (nilfs->n_sems[0] != NULL)
		sem_close(nilfs->n_sems[0]);
	if (nilfs->n_devfd >= 0)
		close(nilfs->n_devfd);
	if (nilfs->n_iocfd >= 0)
		close(nilfs->n_iocfd);

	free(nilfs->n_dev);
	free(nilfs->n_ioc);
	free(nilfs->n_sb);
	free(nilfs);
}

/**
 * nilfs_get_dev - get the name of a device that nilfs object is opening
 * @nilfs: nilfs object
 */
const char *nilfs_get_dev(const struct nilfs *nilfs)
{
	return nilfs->n_dev;
}

/**
 * nilfs_get_root_path - get the path name string of the mount point
 * @nilfs: nilfs object
 *
 * Return: The absolute path name of the mount point directory, or %NULL if
 * unavailable.
 */
const char *nilfs_get_root_path(const struct nilfs *nilfs)
{
	return nilfs->n_ioc;
}

/**
 * nilfs_get_root_fd - get the file descriptor of the mount point directory
 *                     (file system root directory)
 * @nilfs: nilfs object
 *
 * Return: The file descriptor of the mount point directory, or -1 if not
 * available.
 */
int nilfs_get_root_fd(const struct nilfs *nilfs)
{
	return nilfs->n_iocfd;
}

/**
 * nilfs_lock - acquire a lock
 * @nilfs: nilfs object
 * @index: index of the lock to be acquired
 */
int nilfs_lock(struct nilfs *nilfs, unsigned int index)
{
	if (unlikely(index >= ARRAY_SIZE(nilfs->n_sems))) {
		errno = EINVAL;
		return -1;
	}
	return sem_wait(nilfs->n_sems[index]);
}

/**
 * nilfs_trylock - try to acquire a lock
 * @nilfs: nilfs object
 * @index: index of the lock to be tried
 */
int nilfs_trylock(struct nilfs *nilfs, unsigned int index)
{
	if (unlikely(index >= ARRAY_SIZE(nilfs->n_sems))) {
		errno = EINVAL;
		return -1;
	}
	return sem_trywait(nilfs->n_sems[index]);
}

/**
 * nilfs_unlock - release a lock
 * @nilfs: nilfs object
 * @index: index of the lock to be released
 */
int nilfs_unlock(struct nilfs *nilfs, unsigned int index)
{
	if (unlikely(index >= ARRAY_SIZE(nilfs->n_sems))) {
		errno = EINVAL;
		return -1;
	}
	return sem_post(nilfs->n_sems[index]);
}

/**
 * nilfs_change_cpmode - change mode of a checkpoint
 * @nilfs: nilfs object
 * @cno: checkpoint number
 * @mode: new mode of checkpoint
 */
int nilfs_change_cpmode(struct nilfs *nilfs, nilfs_cno_t cno, int mode)
{
	struct nilfs_cpmode cpmode;

	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}
	if (unlikely(cno < NILFS_CNO_MIN)) {
		errno = EINVAL;
		return -1;
	}

	cpmode.cm_cno = cno;
	cpmode.cm_mode = mode;
	cpmode.cm_pad = 0;
	return ioctl(nilfs->n_iocfd, NILFS_IOCTL_CHANGE_CPMODE, &cpmode);
}

/**
 * nilfs_get_cpinfo - get information of checkpoints
 * @nilfs: nilfs object
 * @cno: start checkpoint number
 * @mode: mode of checkpoints that the caller wants to retrieve
 * @cpinfo: array of nilfs_cpinfo structs to store information in
 * @nci: size of @cpinfo array (number of items)
 */
ssize_t nilfs_get_cpinfo(struct nilfs *nilfs, nilfs_cno_t cno, int mode,
			 struct nilfs_cpinfo *cpinfo, size_t nci)
{
	struct nilfs_argv argv;
	int ret;

	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}
	if (mode == NILFS_CHECKPOINT) {
		if (unlikely(cno < NILFS_CNO_MIN)) {
			errno = EINVAL;
			return -1;
		} else if (cno < nilfs->n_mincno)
			cno = nilfs->n_mincno;
	}

	argv.v_base = (unsigned long)cpinfo;
	argv.v_nmembs = nci;
	argv.v_size = sizeof(struct nilfs_cpinfo);
	argv.v_index = cno;
	argv.v_flags = mode;
	ret = ioctl(nilfs->n_iocfd, NILFS_IOCTL_GET_CPINFO, &argv);
	if (unlikely(ret < 0))
		return -1;
	if (mode == NILFS_CHECKPOINT && argv.v_nmembs > 0 &&
	    cno == nilfs->n_mincno) {
		if (cpinfo[0].ci_cno > nilfs->n_mincno)
			nilfs->n_mincno = cpinfo[0].ci_cno;
	}
	return argv.v_nmembs;
}

/**
 * nilfs_delete_checkpoint - delete a checkpoint
 * @nilfs: nilfs object
 * @cno: checkpoint number to be deleted
 */
int nilfs_delete_checkpoint(struct nilfs *nilfs, nilfs_cno_t cno)
{
	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}
	return ioctl(nilfs->n_iocfd, NILFS_IOCTL_DELETE_CHECKPOINT, &cno);
}

/**
 * nilfs_get_cpstat - get checkpoint statistics
 * @nilfs: nilfs object
 * @cpstat: buffer of a nilfs_cpstat struct to store statistics in
 */
int nilfs_get_cpstat(const struct nilfs *nilfs, struct nilfs_cpstat *cpstat)
{
	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}
	return ioctl(nilfs->n_iocfd, NILFS_IOCTL_GET_CPSTAT, cpstat);
}

/**
 * nilfs_get_suinfo - get information of segment usage
 * @nilfs: nilfs object
 * @segnum: start segment number
 * @si: array of nilfs_suinfo structs to store information in
 * @nsi: size of @si array (number of items)
 */
ssize_t nilfs_get_suinfo(const struct nilfs *nilfs, uint64_t segnum,
			 struct nilfs_suinfo *si, size_t nsi)
{
	struct nilfs_argv argv;
	int ret;

	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}

	argv.v_base = (unsigned long)si;
	argv.v_nmembs = nsi;
	argv.v_size = sizeof(struct nilfs_suinfo);
	argv.v_flags = 0;
	argv.v_index = segnum;
	ret = ioctl(nilfs->n_iocfd, NILFS_IOCTL_GET_SUINFO, &argv);
	if (unlikely(ret < 0))
		return -1;
	return argv.v_nmembs;
}

/**
 * nilfs_set_suinfo - sets segment usage info
 * @nilfs: nilfs object
 * @sup: an array of nilfs_suinfo_update structs
 * @nsup: number of elements in sup
 *
 * Description: Takes an array of nilfs_suinfo_update structs and updates
 * segment usage info accordingly. Only the fields indicated by sup_flags
 * are updated.
 *
 * Return Value: On success, 0 is returned. On error, -1 is returned.
 */
int nilfs_set_suinfo(const struct nilfs *nilfs,
		     struct nilfs_suinfo_update *sup, size_t nsup)
{
	struct nilfs_argv argv;

	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}

	argv.v_base = (unsigned long)sup;
	argv.v_nmembs = nsup;
	argv.v_size = sizeof(struct nilfs_suinfo_update);
	argv.v_index = 0;
	argv.v_flags = 0;

	return ioctl(nilfs->n_iocfd, NILFS_IOCTL_SET_SUINFO, &argv);
}

/**
 * nilfs_get_sustat - get segment usage statistics
 * @nilfs: nilfs object
 * @sustat: buffer of a nilfs_sustat struct to store statistics in
 */
int nilfs_get_sustat(const struct nilfs *nilfs, struct nilfs_sustat *sustat)
{
	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}

	return ioctl(nilfs->n_iocfd, NILFS_IOCTL_GET_SUSTAT, sustat);
}

/**
 * nilfs_get_vinfo - get information of virtual block addresses
 * @nilfs: nilfs object
 * @vinfo: array of nilfs_vinfo structs to store information in
 * @nvi: size of @vinfo array (number of items)
 */
ssize_t nilfs_get_vinfo(const struct nilfs *nilfs,
			struct nilfs_vinfo *vinfo, size_t nvi)
{
	struct nilfs_argv argv;
	int ret;

	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}

	argv.v_base = (unsigned long)vinfo;
	argv.v_nmembs = nvi;
	argv.v_size = sizeof(struct nilfs_vinfo);
	argv.v_flags = 0;
	argv.v_index = 0;
	ret = ioctl(nilfs->n_iocfd, NILFS_IOCTL_GET_VINFO, &argv);
	if (unlikely(ret < 0))
		return -1;
	return argv.v_nmembs;
}

/**
 * nilfs_get_bdescs - get information of blocks used in DAT file itself
 * @nilfs: nilfs object
 * @bdescs: array of nilfs_bdesc structs to store information in
 * @nbdescs: size of @bdescs array (number of items)
 */
ssize_t nilfs_get_bdescs(const struct nilfs *nilfs,
			 struct nilfs_bdesc *bdescs, size_t nbdescs)
{
	struct nilfs_argv argv;
	int ret;

	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}

	argv.v_base = (unsigned long)bdescs;
	argv.v_nmembs = nbdescs;
	argv.v_size = sizeof(struct nilfs_bdesc);
	argv.v_flags = 0;
	argv.v_index = 0;
	ret = ioctl(nilfs->n_iocfd, NILFS_IOCTL_GET_BDESCS, &argv);
	if (unlikely(ret < 0))
		return -1;
	return argv.v_nmembs;
}

/**
 * nilfs_clean_segments - do garbage collection operation
 * @nilfs: nilfs object
 * @vdescs: array of nilfs_vdesc structs to specify live blocks
 * @nvdescs: size of @vdescs array (number of items)
 * @periods: array of nilfs_period structs to specify checkpoints to be deleted
 * @nperiods: size of @periods array (number of items)
 * @vblocknrs: array of virtual block addresses to be freed
 * @nvblocknrs: size of @vblocknrs array (number of items)
 * @bdescs: array of nilfs_bdesc structs to specify live DAT blocks
 * @nbdescs: size of @bdescs array (number of items)
 * @segnums: array of segment numbers to specify segments to be freed
 * @nsegs: size of @segnums array (number of items)
 */
int nilfs_clean_segments(struct nilfs *nilfs,
			 struct nilfs_vdesc *vdescs, size_t nvdescs,
			 struct nilfs_period *periods, size_t nperiods,
			 uint64_t *vblocknrs, size_t nvblocknrs,
			 struct nilfs_bdesc *bdescs, size_t nbdescs,
			 uint64_t *segnums, size_t nsegs)
{
	struct nilfs_argv argv[5];

	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}

	memset(argv, 0, sizeof(argv));
	argv[0].v_base = (unsigned long)vdescs;
	argv[0].v_nmembs = nvdescs;
	argv[0].v_size = sizeof(struct nilfs_vdesc);
	argv[1].v_base = (unsigned long)periods;
	argv[1].v_nmembs = nperiods;
	argv[1].v_size = sizeof(struct nilfs_period);
	argv[2].v_base = (unsigned long)vblocknrs;
	argv[2].v_nmembs = nvblocknrs;
	argv[2].v_size = sizeof(uint64_t);
	argv[3].v_base = (unsigned long)bdescs;
	argv[3].v_nmembs = nbdescs;
	argv[3].v_size = sizeof(struct nilfs_bdesc);
	argv[4].v_base = (unsigned long)segnums;
	argv[4].v_nmembs = nsegs;
	argv[4].v_size = sizeof(uint64_t);
	return ioctl(nilfs->n_iocfd, NILFS_IOCTL_CLEAN_SEGMENTS, argv);
}

/**
 * nilfs_sync - sync a NILFS file system
 * @nilfs: nilfs object
 * @cnop: buffer to store the latest checkpoint number in
 */
int nilfs_sync(const struct nilfs *nilfs, nilfs_cno_t *cnop)
{
	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}

	return ioctl(nilfs->n_iocfd, NILFS_IOCTL_SYNC, cnop);
}

/**
 * nilfs_resize - resize the filesystem
 * @nilfs: nilfs object
 * @size: new partition size
 */
int nilfs_resize(struct nilfs *nilfs, off_t size)
{
	uint64_t range = size;

	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}

	return ioctl(nilfs->n_iocfd, NILFS_IOCTL_RESIZE, &range);
}

/**
 * nilfs_set_alloc_range - limit range of segments to be allocated
 * @nilfs: nilfs object
 * @start: lower limit of segments in bytes (inclusive)
 * @end: upper limit of segments in bytes (inclusive)
 */
int nilfs_set_alloc_range(struct nilfs *nilfs, off_t start, off_t end)
{
	uint64_t range[2] = { start, end };

	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}

	return ioctl(nilfs->n_iocfd, NILFS_IOCTL_SET_ALLOC_RANGE, range);
}

/**
 * nilfs_freeze - freeze file system
 * @nilfs: nilfs object
 */
int nilfs_freeze(struct nilfs *nilfs)
{
	int arg = 0;

	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}

	return ioctl(nilfs->n_iocfd, FIFREEZE, &arg);
}

/**
 * nilfs_thaw - thaw file system
 * @nilfs: nilfs object
 */
int nilfs_thaw(struct nilfs *nilfs)
{
	int arg = 0;

	if (unlikely(nilfs->n_iocfd < 0)) {
		errno = EBADF;
		return -1;
	}

	return ioctl(nilfs->n_iocfd, FITHAW, &arg);
}

/**
 * nilfs_get_segment - read or mmap segment to a memory region
 * @nilfs: nilfs object
 * @segnum: segment number
 * @segment: pointer to a segment object (nilfs_segment struct)
 */
int nilfs_get_segment(struct nilfs *nilfs, uint64_t segnum,
		      struct nilfs_segment *segment)
{
	const struct nilfs_super_block *sb = nilfs->n_sb;
	struct nilfs_segment_summary *segsum;
	long pagesize;
	uint32_t blocks_per_segment, blkbits, nblocks;
	uint64_t segblocknr;
	size_t segsize;
	off_t segstart;
	void *addr;
	ssize_t ret;

	if (unlikely(nilfs->n_devfd < 0 || sb == NULL)) {
		errno = EBADF;
		return -1;
	}

	pagesize = sysconf(_SC_PAGESIZE);
	if (unlikely(pagesize <= 0)) {
		errno = EINVAL;
		return -1;
	}

	if (unlikely(segnum >= nilfs_get_nsegments(nilfs))) {
		errno = EINVAL;
		return -1;
	}

	blkbits = le32_to_cpu(sb->s_log_block_size) + 10;
	blocks_per_segment = le32_to_cpu(sb->s_blocks_per_segment);
	if (unlikely(blocks_per_segment < NILFS_SEG_MIN_BLOCKS)) {
		errno = EINVAL;
		return -1;
	}

	if (segnum == 0) {
		segblocknr = le64_to_cpu(sb->s_first_data_block);
		if (unlikely(segblocknr >= blocks_per_segment)) {
			errno = EINVAL;
			return -1;
		}
		nblocks = blocks_per_segment - (uint32_t)segblocknr;
	} else {
		segblocknr = (uint64_t)blocks_per_segment * segnum;
		nblocks = blocks_per_segment;
	}
	segsize = (uint64_t)nblocks << blkbits;
	segstart = segblocknr << blkbits;

#ifdef HAVE_MMAP
	if (nilfs_opt_test_mmap(nilfs)) {
		size_t alloc_size, page_offset;
		int errsv = errno;

		page_offset = segstart % pagesize;
		alloc_size = roundup(segsize + page_offset, pagesize);

		addr = mmap(NULL, alloc_size, PROT_READ, MAP_SHARED,
			    nilfs->n_devfd, segstart - page_offset);
		if (likely(addr != MAP_FAILED)) {
			segment->mmapped = 1;
			segment->adjusted = (page_offset != 0 ||
					     alloc_size != pagesize);
			goto success;
		}

		if (errno != ENODEV)
			return -1;
		/*
		 * The underlying device does not support memory mapping -
		 * fallback to malloc().
		 */
		errno = errsv;
	}
#endif	/* HAVE_MMAP */

	addr = malloc(segsize);
	if (unlikely(addr == NULL))
		return -1;

	ret = pread(nilfs->n_devfd, addr, segsize, segstart);
	if (unlikely(ret < 0)) {
		free(addr);
		return -1;
	}
	segment->mmapped = 0;
	segment->adjusted = 0;

success:
	segment->addr = addr;
	segment->segsize = segsize;
	segment->segnum = segnum;
	segsum = addr;
	segment->seqnum = le64_to_cpu(segsum->ss_seq);
	segment->blocknr = segblocknr;
	segment->nblocks = nblocks;
	segment->blocks_per_segment = blocks_per_segment;
	segment->blkbits = blkbits;
	segment->seed = le32_to_cpu(sb->s_crc_seed);
	return 0;
}

/**
 * nilfs_put_segment - free memory used for raw segment access
 * @segment: pointer to the segment object to be cleaned up
 */
int nilfs_put_segment(struct nilfs_segment *segment)
{
	if (segment->mmapped) {
#ifdef HAVE_MMAP
		size_t page_offset, size;

		if (segment->adjusted) {
			long pagesize = sysconf(_SC_PAGESIZE);

			if (unlikely(pagesize <= 0)) {
				errno = EINVAL;
				return -1;
			}
			page_offset = (unsigned long)segment->addr % pagesize;
			size = roundup(segment->segsize + page_offset,
				       pagesize);
		} else {
			size = segment->segsize;
			page_offset = 0;
		}
		return munmap(segment->addr - page_offset, size);
#else
		errno = EINVAL;
		return -1;
#endif	/* HAVE_MMAP */
	}

	free(segment->addr);
	return 0;
}

/**
 * nilfs_get_segment_seqnum - get sequence number of segment
 * @nilfs: nilfs object
 * @segnum: segment number
 * @seqnum: buffer to store sequence number of the segment given by @segnum
 */
int nilfs_get_segment_seqnum(const struct nilfs *nilfs, uint64_t segnum,
			     uint64_t *seqnum)
{
	const struct nilfs_super_block *sb = nilfs->n_sb;
	uint32_t blocks_per_segment, blkbits;
	__le64 buf;
	off_t segstart, offset;
	ssize_t ret;

	if (unlikely(nilfs->n_devfd < 0 || sb == NULL)) {
		errno = EBADF;
		return -1;
	}

	if (unlikely(segnum >= nilfs_get_nsegments(nilfs))) {
		errno = EINVAL;
		return -1;
	}

	blkbits = le32_to_cpu(sb->s_log_block_size) + 10;
	blocks_per_segment = le32_to_cpu(sb->s_blocks_per_segment);
	segstart = (segnum == 0 ? le64_to_cpu(sb->s_first_data_block) :
		    blocks_per_segment * segnum) << blkbits;

	offset = segstart + offsetof(struct nilfs_segment_summary, ss_seq);
	ret = pread(nilfs->n_devfd, &buf, sizeof(buf), offset);
	if (unlikely(ret < 0))
		return -1;

	*seqnum = le64_to_cpu(buf);
	return 0;
}

nilfs_cno_t nilfs_get_oldest_cno(struct nilfs *nilfs)
{
	struct nilfs_cpinfo cpinfo[1];

	nilfs_get_cpinfo(nilfs, nilfs->n_mincno, NILFS_CHECKPOINT, cpinfo, 1);
	return nilfs->n_mincno;
}
