/*
 * sysfs - extract md related information from sysfs.  Part of:
 * mdadm - manage Linux "md" devices aka RAID arrays.
 *
 * Copyright (C) 2006 Neil Brown <neilb@suse.de>
 *
 *
 *    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.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *    Author: Neil Brown
 *    Email: <neilb@suse.de>
 */

#include	"mdadm.h"
#include	<dirent.h>

int load_sys(char *path, char *buf)
{
	int fd = open(path, O_RDONLY);
	int n;
	if (fd < 0)
		return -1;
	n = read(fd, buf, 1024);
	close(fd);
	if (n <=0 || n >= 1024)
		return -1;
	buf[n] = 0;
	if (buf[n-1] == '\n')
		buf[n-1] = 0;
	return 0;
}

struct sysarray *sysfs_read(int fd, int devnum, unsigned long options)
{
	/* Longest possible name in sysfs, mounted at /sys, is
	 *  /sys/block/md_dXXX/md/dev-XXXXX/block/dev
	 *  /sys/block/md_dXXX/md/metadata_version
	 * which is about 41 characters.  50 should do for now
	 */
	char fname[50];
	char buf[1024];
	char *base;
	char *dbase;
	struct sysarray *sra;
	struct sysdev *dev;
	DIR *dir;
	struct dirent *de;

	sra = malloc(sizeof(*sra));
	if (sra == NULL)
		return sra;

	if (fd >= 0) {
		struct stat stb;
		if (fstat(fd, &stb)) return NULL;
		if (major(stb.st_rdev)==9)
			sprintf(sra->name, "md%d", minor(stb.st_rdev));
		else
			sprintf(sra->name, "md_d%d",
				minor(stb.st_rdev)/16);
	} else {
		if (devnum >= 0)
			sprintf(sra->name, "md%d", devnum);
		else
			sprintf(sra->name, "md_d%d",
				-1-devnum);
	}
	sprintf(fname, "/sys/block/%s/md/", sra->name);
	base = fname + strlen(fname);

	sra->devs = NULL;
	if (options & GET_LEVEL) {
		strcpy(base, "level");
		if (load_sys(fname, buf))
			goto abort;
		sra->level = map_name(pers, buf);
	}
	if (options & GET_LAYOUT) {
		strcpy(base, "layout");
		if (load_sys(fname, buf))
			goto abort;
		sra->layout = strtoul(buf, NULL, 0);
	}
	if (options & GET_COMPONENT) {
		strcpy(base, "component_size");
		if (load_sys(fname, buf))
			goto abort;
		sra->component_size = strtoull(buf, NULL, 0);
		/* sysfs reports "K", but we want sectors */
		sra->component_size *= 2;
	}
	if (options & GET_CHUNK) {
		strcpy(base, "chunk_size");
		if (load_sys(fname, buf))
			goto abort;
		sra->chunk = strtoul(buf, NULL, 0);
	}

	if (! (options & GET_DEVS))
		return sra;

	/* Get all the devices as well */
	*base = 0;
	dir = opendir(fname);
	if (!dir)
		goto abort;
	sra->spares = 0;

	while ((de = readdir(dir)) != NULL) {
		char *ep;
		if (de->d_ino == 0 ||
		    strncmp(de->d_name, "dev-", 4) != 0)
			continue;
		strcpy(base, de->d_name);
		dbase = base + strlen(base);
		*dbase++ = '/';

		dev = malloc(sizeof(*dev));
		if (!dev)
			goto abort;
		dev->next = sra->devs;
		sra->devs = dev;

		/* Always get slot, major, minor */
		strcpy(dbase, "slot");
		if (load_sys(fname, buf))
			goto abort;
		dev->role = strtoul(buf, &ep, 10);
		if (*ep) dev->role = -1;

		strcpy(dbase, "block/dev");
		if (load_sys(fname, buf))
			goto abort;
		sscanf(buf, "%d:%d", &dev->major, &dev->minor);

		if (options & GET_OFFSET) {
			strcpy(dbase, "offset");
			if (load_sys(fname, buf))
				goto abort;
			dev->offset = strtoull(buf, NULL, 0);
		}
		if (options & GET_SIZE) {
			strcpy(dbase, "size");
			if (load_sys(fname, buf))
				goto abort;
			dev->size = strtoull(buf, NULL, 0);
		}
		if (options & GET_STATE) {
			dev->state = 0;
			strcpy(dbase, "state");
			if (load_sys(fname, buf))
				goto abort;
			if (strstr(buf, "in_sync"))
				dev->state |= (1<<MD_DISK_SYNC);
			if (strstr(buf, "faulty"))
				dev->state |= (1<<MD_DISK_FAULTY);
			if (dev->state == 0)
				sra->spares++;
		}
		if (options & GET_ERROR) {
			strcpy(buf, "errors");
			if (load_sys(fname, buf))
				goto abort;
			dev->errors = strtoul(buf, NULL, 0);
		}
	}
	return sra;

 abort:
	while (sra && sra->devs) {
		dev = sra->devs;
		sra->devs = dev->next;
		free(dev);
	}
	if(sra) free(sra);
	return NULL;
}

unsigned long long get_component_size(int fd)
{
	/* Find out the component size of the array.
	 * We cannot trust GET_ARRAY_INFO ioctl as it's
	 * size field is only 32bits.
	 * So look in /sys/block/mdXXX/md/component_size
	 *
	 * WARNING: this returns in units of Kilobytes.
	 */
	struct stat stb;
	char fname[50];
	int n;
	if (fstat(fd, &stb)) return 0;
	if (major(stb.st_rdev) == 9)
		sprintf(fname, "/sys/block/md%d/md/component_size",
			minor(stb.st_rdev));
	else
		sprintf(fname, "/sys/block/md_d%d/md/component_size",
			minor(stb.st_rdev)/16);
	fd = open(fname, O_RDONLY);
	if (fd < 0)
		return 0;
	n = read(fd, fname, sizeof(fname));
	close(fd);
	if (n == sizeof(fname))
		return 0;
	fname[n] = 0;
	return strtoull(fname, NULL, 10);
}

int sysfs_set_str(struct sysarray *sra, struct sysdev *dev,
		  char *name, char *val)
{
	char fname[50];
	int n;
	int fd;
	sprintf(fname, "/sys/block/%s/md/%s/%s",
		sra->name, dev?dev->name:"", name);
	fd = open(fname, O_WRONLY);
	if (fd < 0)
		return -1;
	n = write(fd, val, strlen(val));
	close(fd);
	if (n != strlen(val))
		return -1;
	return 0;
}

int sysfs_set_num(struct sysarray *sra, struct sysdev *dev,
		  char *name, unsigned long long val)
{
	char valstr[50];
	sprintf(valstr, "%llu", val);
	return sysfs_set_str(sra, dev, name, valstr);
}

int sysfs_get_ll(struct sysarray *sra, struct sysdev *dev,
		       char *name, unsigned long long *val)
{
	char fname[50];
	char buf[50];
	int n;
	int fd;
	char *ep;
	sprintf(fname, "/sys/block/%s/md/%s/%s",
		sra->name, dev?dev->name:"", name);
	fd = open(fname, O_RDONLY);
	if (fd < 0)
		return -1;
	n = read(fd, buf, sizeof(buf));
	close(fd);
	if (n <= 0)
		return -1;
	buf[n] = 0;
	*val = strtoull(buf, &ep, 0);
	if (ep == buf || (*ep != 0 && *ep != '\n' && *ep != ' '))
		return -1;
	return 0;
}
