/*
 * tools/lib/lvm_dir_cache.c
 *
 * Copyright (C) 1997 - 2001  Heinz Mauelshagen, Sistina Software
 *
 * May 1998
 * January,April,July,August,September,December 1999
 * January,September 2000
 * February,July,December 2001
 * January 2002
 *
 *
 * This LVM library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This LVM library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this LVM library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 *
 */

/*
 * Changelog
 *
 *    24/01/1999 - corrected to new devfs name space
 *    22/04/1999 - avoided useless allocation in case of cache hit
 *               - DAC960 (/dev/rd/) support by Matthew Taylor
 *                 <M.Taylor@rbgkew.org.uk>
 *    23/07/1999 - added support for Compac Smart Array controller
 *    15/08/1999 - fixed selection of DAC960 devices
 *    29/10/1999 - fixed possible free() bug
 *    24/12/1999 - added /proc/partitions in lvm_dir_cache() support
 *    31/01/2000 - use debug_enter()/debug_leave()
 *    27/09/2000 - deactivated caching in case of /proc/partitions or devfs
 *    07/02/2001 - added COMPAQ_CISS major support
 *    06/07/2001 - tidied, still hate this file [JT]
 *    07/07/2001 - realloc now doubles the size of the cache [JT]
 *    17/12/2001 - make it find loop devices again [HM]
 *    01/22/2002 - fix broken whole device support [HM]
 *
 */


#include <liblvm.h>


static void _scan_partitions();
static void _scan_devs(int);
static int _add(char *directory, char *devname);
static int _check_array_size();
static int _alloc();
static int _is_present(dev_t rdev);
static void _collapse_slashes(char *str);


static dir_cache_t *_dir_cache;
static int _cache_size;
static int _array_size;

/* devices *not* showing up in /proc/partitions must be scanned anyway */
static char *_noprocdir[] = {
	LVM_DIR_PREFIX "loop",
	LVM_DIR_PREFIX "nb",
	NULL
};

char *_devdir[] = {
	LVM_DIR_PREFIX "cciss",
	LVM_DIR_PREFIX "ida",
	LVM_DIR_PREFIX "ide",
	LVM_DIR_PREFIX "ide/hd",
	LVM_DIR_PREFIX "loop",
	LVM_DIR_PREFIX "md",
	LVM_DIR_PREFIX "rd",
	LVM_DIR_PREFIX "nb",
	LVM_DIR_PREFIX "sd",
	LVM_DIR_PREFIX "ubd",
	LVM_DIR_PREFIX "ataraid",
	LVM_DIR_PREFIX "i2o_block",
	NULL
};


int lvm_dir_cache(dir_cache_t **dir_cache_ptr)
{
	int ret = 0;
	debug_enter("lvm_dir_cache -- CALLED\n");

	if (!dir_cache_ptr) {
		ret = -LVM_EPARAM;
		goto out;
	}

	if (!_dir_cache) {
		_scan_partitions();
		if(!_cache_size)
			_scan_devs( TRUE);
		else
			_scan_devs( FALSE);
	}

	*dir_cache_ptr = _dir_cache;
	ret = _cache_size;

 out:
	debug_leave("lvm_dir_cache -- LEAVING with ret: %d\n", ret);
	return ret;
}

/* try to find an entry in the directory cache */
dir_cache_t *lvm_dir_cache_find(char *dev_name)
{
	int d = 0;
	dir_cache_t *dir_cache = NULL, *ret = NULL;

	debug_enter("lvm_dir_cache_find -- CALLED with %s\n", dev_name);

	if (!dev_name || pv_check_name(dev_name))
		goto out;

	d = lvm_dir_cache(&dir_cache);
	for (d--; d >= 0; d--) {
		if (!strcmp(dev_name, dir_cache[d].dev_name)) {
			ret = dir_cache + d;
			break;
		}
	}

 out:
	debug_leave("lvm_dir_cache_find -- LEAVING with entry: %d\n",
		    ret ? d : -1);
	return ret;
}

int lvm_check_devfs()
{
	int ret = 0, len;
	char dir[NAME_LEN], line[512], *dev_prefix = LVM_DIR_PREFIX;
	char type[32];
	FILE *mounts = NULL;

	debug_enter("lvm_check_devfs -- CALLED\n");

	if (!(mounts = fopen("/proc/mounts", "r")))
		goto out;

	/* trim the trailing slash off the dir prefix, yuck */
	len = strlen(dev_prefix) - 1;
	while(len && dev_prefix[len] == '/')
		len--;

	while (!feof(mounts)) {
		fgets(line, sizeof(line) - 1, mounts);
		if (sscanf(line, "%*s %s %s %*s", dir, type) != 2)
			continue;

		if (!strcmp(type, "devfs") && !strncmp(dir, dev_prefix, len)) {
			ret = 1;
			break;
		}
	}
	fclose(mounts);

 out:
	debug_leave("lvm_check_devfs -- LEAVING with ret: %d\n", ret);
	return ret;
}


static void _scan_partitions()
{
	FILE *proc;
	char major[20], minor[20], blocks[20], line[128], devname[128];

	if (!(proc = fopen("/proc/partitions", "r")))
		return;

	while (!feof(proc)) {
		fgets(line, sizeof(line) - 1, proc);
		if (sscanf(line, " %s %s %s %s\n",
			   major, minor, blocks, devname) != 4)
			continue;

		if ((atoi(major) > 0) && (atoi(major) != LVM_BLK_MAJOR))
			_add(LVM_DIR_PREFIX, devname);
	}

	fclose(proc);
}

static void _scan_devs( int all)
{
	int d, dirent_count, len, n;
	char *dirname;
	struct dirent **dirent;
        char **dirs;
        char *filter;

        if ( all == TRUE) dirs = _devdir;
	else		  dirs = _noprocdir;

	for (d = 0; dirs[d]; d++) {
		dirname = dirs[d];

		dirent_count = scandir(dirname, &dirent, NULL, alphasort);
		if (dirent_count > 0) {
			for (n = 0; n < dirent_count; n++) {
				_add(dirname, dirent[n]->d_name);
				free(dirent[n]);
			}

			free(dirent);
		} else {
		/* there's no subdir with that name, try to filter files out
                   of directory LVM_DIR_PREFIX */
			filter = strrchr (dirname, '/');
			if (!filter) continue;
			filter++;
			len = strlen (filter);
			if (len) {
				dirname = LVM_DIR_PREFIX;
				dirent_count = scandir(dirname, &dirent, NULL, alphasort);
				if (dirent_count > 0) {
					for (n = 0; n < dirent_count; n++) {
						if (!strncmp(dirent[n]->d_name, filter, len)) {
						   _add(dirname, dirent[n]->d_name);
						}
						free(dirent[n]);
					}
					free(dirent);
				}
			}
		}
	}
}

static int _add(char *directory, char *devname)
{
	char devpath[NAME_LEN];
	struct stat stat_b;
	int in, ret = 0;

	if (!directory || !devname)
		return 0;

	snprintf(devpath, sizeof(devpath), "%s/%s", directory, devname);
	_collapse_slashes(devpath);

	debug_enter("lvm_add_dir_cache -- CALLED with %s\n", devpath);

	if (stat(devpath, &stat_b) == -1)
		goto out;

	if (!S_ISBLK(stat_b.st_mode))
		goto out;

	if (!lvm_check_dev(&stat_b, TRUE))
		goto out;

	if (_is_present(stat_b.st_rdev))
		goto out;

	if ((in = open (devpath,O_RDONLY)) == -1)
		goto out;
	close (in);

	if (!_check_array_size())
		goto out;

	_dir_cache[_cache_size].dev_name = malloc(strlen(devpath) + 1);
	if (!_dir_cache[_cache_size].dev_name) {
		fprintf(stderr, "malloc error in %s [line %d]\n",
			__FILE__, __LINE__);
		goto out;
	}

	strcpy(_dir_cache[_cache_size].dev_name, devpath);
	_dir_cache[_cache_size].st_rdev = stat_b.st_rdev;
	_dir_cache[_cache_size].st_mode = stat_b.st_mode;
	_cache_size++;
	ret = 1;

 out:
	debug_leave("lvm_add_dir_cache -- LEAVING with ret: %s\n",
		    ret ? "ADDED" : "NOT ADDED");
	return ret;
}

static int _check_array_size()
{
	if (!_array_size)
		return _alloc(128);

	if (_cache_size >= _array_size)
		return _alloc(_array_size * 2);

	return 1;
}

static int _alloc(int new_size)
{
	dir_cache_t *new = realloc(_dir_cache, new_size * sizeof(*_dir_cache));
	if (!new) {
		fprintf(stderr, "realloc error in %s [line %d]\n",
			__FILE__, __LINE__);
		return 0;
	}

	_array_size = new_size;
	_dir_cache = new;

	return 1;
}

static int _is_present(dev_t rdev)
{
	int d;

	for (d = 0; d < _cache_size; d++)
		if (_dir_cache[d].st_rdev == rdev)
			return 1;

	return 0;
}

static void _collapse_slashes(char *str)
{
	char *ptr;
	int was_slash = 0;

	for (ptr = str; *ptr; ptr++) {
		if (*ptr == '/') {
			if (was_slash)
				continue;

			was_slash = 1;
		} else
			was_slash = 0;
		*str++ = *ptr;
	}

	*str = *ptr;
}
