/*
 * File...........: u2s.c
 * Author(s)......: Gerhard Tonn <ton@de.ibm.com>
 *                  Stefan Weinhuber <wein@de.ibm.com>
 * Bugreports.to..: <Linux390@de.ibm.com>
 *
 * (C) Copyright IBM Corp. 2004
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <wait.h>
#include <errno.h>
#include <string.h>
#include <sysfs/libsysfs.h>
#include <sys/stat.h>

#include "u2s.h"

#define DEV_BUFFER_LENGTH 20


/*
 * helper function that compares the string dev against the
 * attribute "dev" in directory dir
 * returns 1 if match is found, else 0
 */
int 
dir_matches_device(struct sysfs_directory *dir, const char *dev) {
	int dev_attr_len, dev_parm_len;
	struct sysfs_attribute *devattr;

	devattr = sysfs_get_directory_attribute(dir, "dev");
	if (devattr) {
		/* exclude white space from comparison */
		dev_attr_len = strspn(devattr->value, "1234567890:");
		dev_parm_len = strlen(dev);
		if (dev_attr_len != dev_parm_len )
			return 0;
		if (!strncmp(dev,devattr->value, dev_parm_len))
			return 1;
	}
	return 0;
}


/*
 * helper function that searches for a specific block device and returns
 * it's busid
 * sysfs: structure that represents the sysfs root directory
 * dev: <major>:<minor> of the device
 * busid: buffer in which the busid string will be returned
 * returns 0 for successfull operation and -1 in case of an error. 
 */

int 
find_busid_in_sysfs(struct sysfs_directory *sysfs, char *dev, char *busid) {

	struct sysfs_directory *block, *dir, *subdir, *blkdevdir;
	struct sysfs_link *device_link;
	struct dlist *blockdirs, *blocksubdirs;

	block = sysfs_get_subdirectory(sysfs, SYSFS_BLOCK_NAME);
        if (!block) 
		return -1;
	blockdirs = sysfs_get_dir_subdirs(block);
        if (!blockdirs) 
		return -1;
	/* go through all directories and subdirectories in /sys/block
	 * e.g. /sys/block/dasda as well as /sys/block/dasda1 etc.
	 * match the attribute 'dev' against the string we build in
	 * the first step.
	 */
	blkdevdir=NULL;
	dlist_for_each_data(blockdirs, dir, struct sysfs_directory) {
		blocksubdirs = sysfs_get_dir_subdirs(dir);
		if (blocksubdirs) {
			dlist_for_each_data(blocksubdirs, subdir,
					    struct sysfs_directory) {
				if (dir_matches_device(subdir, dev)) {
					blkdevdir = dir;
					break;
				}
			}
		}
		if (blkdevdir)
			break;
		if (dir_matches_device(dir, dev)) {
			blkdevdir = dir;
			break;
		}
	}
	if (!blkdevdir) 
		return -1;
	/*
	 * now that we have the right block device, we can follow the
	 * link to the device and extract the busid
	 */
        device_link = sysfs_get_directory_link(blkdevdir, "device");
	if (!device_link) 
		return -1;
        if (sysfs_get_name_from_path(device_link->target,
				     busid, SYSFS_BUS_ID_SIZE)) 
		return -1;
	return 0;
}

/*
 * Return the busid of a given device node.
 * Works only for block devices.
 * devicenode: path to the device node
 * busid: buffer in which the busid string will be returned
 * returns 0 for successfull operation and -1 in case of an error. 
 */

int
u2s_getbusid(char *devicenode, char *busid)
{
	unsigned char mnt_path[SYSFS_PATH_MAX];
        int maj, min, rc;
        struct stat stat_buf;
        char dev_string[DEV_BUFFER_LENGTH];
	struct sysfs_directory *mntdir;

	/*
	 * Get major and minor information of the device special file
	 * and combine them to a <maj>:<min> string, as returned by
         * the dev attributes in sysfs
	 */
        if (stat(devicenode, &stat_buf))
                return -1;
        if (!S_ISBLK(stat_buf.st_mode))
                return -1;
        maj = major(stat_buf.st_rdev);
        min = minor(stat_buf.st_rdev);
        snprintf(dev_string,DEV_BUFFER_LENGTH,"%u:%u", maj, min);

	/* start navigation in sysfs and go to /sys/block */
        if(sysfs_get_mnt_path(mnt_path, SYSFS_PATH_MAX))
                return -1;
	mntdir = sysfs_open_directory(mnt_path);
        if (!mntdir) 
		return -1;
	rc = find_busid_in_sysfs(mntdir, dev_string, busid);
	sysfs_close_directory(mntdir);

	return rc;
}

