/*
 * Common code for checking sessions for mnt use
 *
 * Copyright (C) 2014 - 2015 Chris Leech
 * Copyright (C) 2014 - 2015 Red Hat, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 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
 * General Public License for more details.
 *
 * See the file COPYING included with this distribution for more details.
 */

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <dirent.h>
#include <libmount/libmount.h>

#include "iface.h"
#include "initiator.h"
#include "transport.h"
#include "iscsid.h"
#include "iscsi_ipc.h"
#include "log.h"
#include "iscsi_sysfs.h"
#include "iscsi_settings.h"
#include "host.h"
#include "sysdeps.h"
#include "iscsi_err.h"
#include "iscsi_net_util.h"

static struct libmnt_table *mtab, *swaps;
static struct libmnt_cache *mntcache;

static void libmount_cleanup(void)
{
	mnt_free_table(mtab);
	mnt_free_table(swaps);
	mnt_free_cache(mntcache);
	mtab = NULL;
	swaps = NULL;
	mntcache = NULL;
}

static int libmount_init(void)
{
	mnt_init_debug(0);
	mtab = mnt_new_table();
	swaps = mnt_new_table();
	mntcache = mnt_new_cache();
	if (!mtab || !swaps || !mntcache) {
		libmount_cleanup();
		return -ENOMEM;
	}
	mnt_table_set_cache(mtab, mntcache);
	mnt_table_set_cache(swaps, mntcache);
	mnt_table_parse_mtab(mtab, NULL);
	mnt_table_parse_swaps(swaps, NULL);
	return 0;
}

static int trans_filter(const struct dirent *d)
{
	if (!strcmp(".", d->d_name) || !strcmp("..", d->d_name))
		return 0;
	return 1;
}

static int subdir_filter(const struct dirent *d)
{
	if (!(d->d_type & DT_DIR))
		return 0;
	return trans_filter(d);
}

static int is_partition(const char *path)
{
	char *devtype;
	int rc = 0;

	devtype = sysfs_get_uevent_devtype(path);
	if (!devtype)
		return 0;
	if (strcmp(devtype, "partition") == 0)
		rc = 1;
	free(devtype);
	return rc;
}

static int blockdev_check_mnts(char *syspath)
{
	struct libmnt_fs *fs;
	char *devname = NULL;
	char *_devname = NULL;
	int rc = 0;

	devname = sysfs_get_uevent_devname(syspath);
	if (!devname)
		goto out;

	_devname = calloc(1, PATH_MAX);
	if (!_devname)
		goto out;
	snprintf(_devname, PATH_MAX, "/dev/%s", devname);

	fs = mnt_table_find_source(mtab, _devname, MNT_ITER_FORWARD);
	if (fs) {
		rc = 1;
		goto out;
	}
	fs = mnt_table_find_source(swaps, _devname, MNT_ITER_FORWARD);
	if (fs)
		rc = 1;
out:
	free(devname);
	free(_devname);
	return rc;
}

static int count_device_users(char *syspath);

static int blockdev_get_partitions(char *syspath)
{
	struct dirent **parts = NULL;
	int n, i;
	int count = 0;

	n = scandir(syspath, &parts, subdir_filter, alphasort);
	for (i = 0; i < n; i++) {
		char *newpath;

		newpath = calloc(1, PATH_MAX);
		if (!newpath)
			continue;
		snprintf(newpath, PATH_MAX, "%s/%s", syspath, parts[i]->d_name);
		free(parts[i]);
		if (is_partition(newpath)) {
			count += count_device_users(newpath);
		}
		free(newpath);
	}
	free(parts);
	return count;
}

static int blockdev_get_holders(char *syspath)
{
	char *path = NULL;
	struct dirent **holds = NULL;
	int n, i;
	int count = 0;

	path = calloc(1, PATH_MAX);
	if (!path)
		return 0;
	snprintf(path, PATH_MAX, "%s/holders", syspath);

	n = scandir(path, &holds, trans_filter, alphasort);
	for (i = 0; i < n; i++) {
		char *newpath;
		char *rp;

		newpath = calloc(1, PATH_MAX);
		if (!newpath)
			continue;
		snprintf(newpath, PATH_MAX, "%s/%s", path, holds[i]->d_name);

		free(holds[i]);
		rp = realpath(newpath, NULL);
		if (rp)
			count += count_device_users(rp);
		free(newpath);
		free(rp);
	}
	free(path);
	free(holds);
	return count;
}

static int count_device_users(char *syspath)
{
	int count = 0;
	count += blockdev_check_mnts(syspath);
	count += blockdev_get_partitions(syspath);
	count += blockdev_get_holders(syspath);
	return count;
};

static void device_in_use(void *data, int host_no, int target, int lun)
{
	char *syspath = NULL;
	char *devname = NULL;
	int *count = data;

	devname = iscsi_sysfs_get_blockdev_from_lun(host_no, target, lun);
	if (!devname)
		goto out;
	syspath = calloc(1, PATH_MAX);
	if (!syspath)
		goto out;
	snprintf(syspath, PATH_MAX, "/sys/class/block/%s", devname);
	*count += count_device_users(syspath);
out:
	free(syspath);
	free(devname);
}

int session_in_use(int sid)
{
	int host_no = -1, err = 0;
	int count = 0;

	if (libmount_init()) {
		log_error("Failed to initialize libmount, "
			  "not checking for active mounts on session [%d].", sid);
		return 0;
	}

	host_no = iscsi_sysfs_get_host_no_from_sid(sid, &err);
	if (!err)
		iscsi_sysfs_for_each_device(&count, host_no, sid, device_in_use);

	libmount_cleanup();
	return count;
}
