/*
 * dasd - Library for DASD related functions
 *
 * DASD related helper functions for accessing device information via sysfs
 *
 * Copyright IBM Corp. 2016, 2017
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

#include <errno.h>
#include <stdlib.h>

#include "lib/dasd_base.h"
#include "lib/dasd_sys.h"
#include "lib/util_file.h"
#include "lib/util_path.h"
#include "lib/util_sys.h"

/**
 * Get raw-track access mode status
 *
 * The "devnode" parameter can be any valid relative or absolute path to
 * a DASD device node, for example:
 *
 * - /dev/dasda
 * - /dev/disk/by-path/ccw-0.0.bf20
 *
 * @param[in]	devnode		Device node of interest
 *
 * @retval	1		Raw-track access mode is enabled
 * @retval	0		Raw-track access mode is disabled or
 *				cannot be determined
 */
int dasd_sys_raw_track_access(char *devnode)
{
	char busid[DASD_BUS_ID_SIZE];
	char *path;
	FILE *fp;
	int rc;

	if (util_sys_get_dev_addr(devnode, busid) != 0)
		return 0;

	path = util_path_sysfs("bus/ccw/devices/%s/raw_track_access", busid);
	fp = fopen(path, "r");
	if (!fp) {
		free(path);
		return 0;
	}

	rc = fgetc(fp) - '0';
	fclose(fp);
	free(path);

	return (rc == 1) ? 1 : 0;
}

/**
 * Is volume extent space efficient a.k.a. thin-provisioned
 *
 * The "devnode" parameter can be any valid relative or absolute path to
 * a DASD device node, for example:
 *
 * - /dev/dasda
 * - /dev/disk/by-path/ccw-0.0.bf20
 *
 * @param[in]	devnode		Device node of interest
 *
 * @retval	1		Volume is extent space efficient
 * @retval	0		Volume is not extent space efficient or
 *				cannot be determined
 */
int dasd_sys_ese(char *devnode)
{
	char busid[DASD_BUS_ID_SIZE];
	char *path;
	FILE *fp;
	int rc;

	if (util_sys_get_dev_addr(devnode, busid) != 0)
		return 0;

	path = util_path_sysfs("bus/ccw/devices/%s/ese", busid);
	fp = fopen(path, "r");
	if (!fp) {
		free(path);
		return 0;
	}

	rc = fgetc(fp) - '0';
	fclose(fp);
	free(path);

	return (rc == 1) ? 1 : 0;
}

static int dasd_get_pm_from_chpid(char *busid, unsigned int chpid, int *mask)
{
	unsigned int val;
	int count, i;
	char *path;
	FILE *fp;

	path = util_path_sysfs("bus/ccw/devices/%s/../chpids", busid);
	*mask = 0;
	fp = fopen(path, "r");
	if (!fp) {
		free(path);
		return ENODEV;
	}

	for (i = 0; i < 8; i++) {
		count = fscanf(fp, " %x", &val);
		if (count != 1) {
			fclose(fp);
			return EIO;
		}
		if (val == chpid)
			*mask = 0x80 >> i;
	}
	fclose(fp);
	free(path);

	return 0;
}

/**
 * reset chpid
 *
 * The "devnode" parameter can be any valid relative or absolute path to
 * a DASD device node, for example:
 *
 * - /dev/dasda
 * - /dev/disk/by-path/ccw-0.0.bf20
 *
 * @param[in]	devnode		Device node of interest
 * @param[in]	chpid		The chpid to reset
 *                              If NULL all chpids will be reset
 *
 * @return     0 on success, otherwise one of the following error codes:
 *   - EINVAL  No valid chpid specified.
 *   - ENODEV  Could not open device.
 *   - ENOENT  Specified chpid not found.
 *   - EIO     Other I/O error
 *
 */
int dasd_reset_chpid(char *devnode, char *chpid_char)
{
	unsigned int chpid;
	char busid[DASD_BUS_ID_SIZE];
	int  mask, rc;
	char *endptr;
	char *path;
	FILE *fp;

	if (util_sys_get_dev_addr(devnode, busid) != 0)
		return ENODEV;

	if (!chpid_char) {
		path = util_path_sysfs("bus/ccw/devices/%s/path_reset", busid);
		fp = fopen(path, "w");
		if (!fp) {
			free(path);
			return ENODEV;
		}
		fprintf(fp, "%s", "all\n");
		fclose(fp);
		free(path);
		return 0;
	}

	errno = 0;
	chpid = strtoul(chpid_char, &endptr, 16);
	if (errno || (endptr && (*endptr != '\0')))
		return EINVAL;

	rc = dasd_get_pm_from_chpid(busid, chpid, &mask);
	if (rc)
		return rc;
	if (!mask)
		return ENOENT;

	path = util_path_sysfs("bus/ccw/devices/%s/path_reset", busid);
	fp = fopen(path, "w");
	if (!fp) {
		free(path);
		return ENODEV;
	}
	fprintf(fp, "%02x", mask);
	fclose(fp);
	free(path);

	return 0;
}

/**
 * Read amount of host with access to \p device
 *
 * The \p device can be any valid relative or absolute path to a DASD device
 * node, for example:
 *
 * - /dev/dasda
 * - /dev/disk/by-path/ccw-0.0.bf20
 *
 * @param[in]	device	Device node of interest
 *
 * @retval	n	Number of hosts with access to \p device
 * @retval	0	Value could not be determined
 */
int dasd_get_host_access_count(char *device)
{
	char busid[DASD_BUS_ID_SIZE];
	char *path;
	long value;

	if (util_sys_get_dev_addr(device, busid) != 0)
		return 0;

	path = util_path_sysfs("bus/ccw/devices/%s/host_access_count", busid);
	if (util_file_read_l(&value, 10, path))
		value = 0;
	free(path);

	return value;
}
