"Fossies" - the Fresh Open Source Software archive

Member "evms-2.5.5/plugins/fat/fatfsim.c" of archive evms-2.5.5.tar.gz:


/*
 *   (C) Copyright IBM Corp. 2006
 *   (C) Copyright ALT Linux Ltd. 2006
 *
 *   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
 *
 * Module: FAT FSIM
 * File: evms2/engine/plugins/fat/fatfsim.c
 */

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <ctype.h>

#include <plugin.h>

#include "fatfsim.h"
#include "utils.h"

/* FIXME: is this the correct min FAT size? */
#define MIN_FAT_SIZE	(8 * 1024)

static plugin_record_t	fat_plugrec;
static plugin_record_t	fat16_plugrec;
plugin_record_t		*my_plugin_record;
engine_functions_t	*EngFncs;
boolean			have_mkdosfs = FALSE;
boolean			have_dosfsck = FALSE;
boolean			have_fatresize = FALSE;

static int fat_setup(engine_functions_t *engine_function_table)
{
	int rc = 0;

	EngFncs = engine_function_table;
	LOG_ENTRY();

	if (try_run("mkdosfs") == 0) {
		have_mkdosfs = TRUE;
	}

	if (try_run("dosfsck") == 0) {
		have_dosfsck = TRUE;
	}

	if (try_run("fatresize") == 0) {
		have_fatresize = TRUE;
	}

	LOG_EXIT_INT(rc);
	return rc;
}


/*
 * Free all of the private data we have left on volumes.
 */
static void _fat_cleanup(void)
{
	list_anchor_t global_volumes = NULL;
	list_element_t iter;
	logical_volume_t *vol;

	LOG_ENTRY();

	EngFncs->get_volume_list(my_plugin_record, NULL,
				 0, &global_volumes);
	LIST_FOR_EACH(global_volumes, iter, vol) {
		free_private_data(vol);
	}
	EngFncs->destroy_list(global_volumes);

	have_mkdosfs = FALSE;
	have_dosfsck = FALSE;
	have_fatresize = FALSE;

	LOG_EXIT_VOID();
}

static void fat_cleanup(void)
{
	my_plugin_record = &fat_plugrec;
	_fat_cleanup();
}

static void fat16_cleanup(void)
{
	my_plugin_record = &fat16_plugrec;
	_fat_cleanup();
}

/*
 * Does this FSIM manage the file system on this volume?
 * Return 0 for "yes", else a reason code.
 */
static int _fat_probe(int isfat16, logical_volume_t *volume)
{
	fat_boot_sector *boot;
	int rc;

	LOG_ENTRY();

	boot = EngFncs->engine_alloc(sizeof(fat_boot_sector));
	if (!boot) {
		rc = ENOMEM;
		goto out;
	}

	rc = get_fat_boot(isfat16, volume, boot);
	if (rc) {
		goto out;
	}

	volume->private_data = EngFncs->engine_alloc(sizeof(private_data_t));
	if (!volume->private_data) {
		rc = ENOMEM;
		goto out;
	}

	rc = fill_private_data(isfat16, volume, boot);

out:
	EngFncs->engine_free(boot);
	LOG_EXIT_INT(rc);
	return rc;
}

static int fat_probe(logical_volume_t *volume)
{
	my_plugin_record = &fat_plugrec;
	return _fat_probe(0, volume);
}

static int fat16_probe(logical_volume_t *volume)
{
	my_plugin_record = &fat16_plugrec;
	return _fat_probe(1, volume);
}


/*
 * Get the size limits for this volume.
 */
static int fat_get_fs_limits(logical_volume_t *volume,
			     sector_count_t *min_size,
			     sector_count_t *max_volume_size,
			     sector_count_t *max_object_size)
{
	int rc;
	private_data_t *pd = volume->private_data;

	LOG_ENTRY();

	rc = get_fs_limits(volume, min_size, max_volume_size, max_object_size);
	*max_object_size = pd->max_vol_size;

	LOG_EXIT_INT(rc);
	return rc;
}


/*
 * Can mkfs this volume?
 */
static int fat_can_mkfs(logical_volume_t *volume)
{
	LOG_ENTRY();

	if (!have_mkdosfs) {
		LOG_DEBUG("The mkdosfs utility is not installed.\n");
		LOG_EXIT_INT(ENOSYS);
		return ENOSYS;
	}

	if (volume->vol_size < MIN_FAT_SIZE) {
		LOG_DETAILS("Volume %s is too small. FAT volumes must "
			    "be at least %u sectors in size.\n",
			    volume->name, MIN_FAT_SIZE);
		LOG_EXIT_INT(ENOSPC);
		return ENOSPC;
	}

	LOG_EXIT_INT(0);
	return 0;
}


/*
 * Can unmkfs this volume?
 */
static int _fat_can_unmkfs(logical_volume_t *volume)
{
	int rc = 0;

	LOG_ENTRY();

	if (volume->file_system_manager != my_plugin_record) {
		/* It's not my volume. */
		LOG_DEBUG("Volume %s does not have FAT on it.\n", volume->name);
		rc = EINVAL;

	} else if (EngFncs->is_mounted(volume->dev_node, NULL)) {
		/* If mounted, can't unmkfs. */
		LOG_DEBUG("Volume %s is mounted.\n", volume->name);
		rc = EBUSY;
	}

	LOG_EXIT_INT(rc);
	return rc;
}

static int fat_can_unmkfs(logical_volume_t *volume)
{
	my_plugin_record = &fat_plugrec;
	return _fat_can_unmkfs(volume);
}

static int fat16_can_unmkfs(logical_volume_t *volume)
{
	my_plugin_record = &fat16_plugrec;
	return _fat_can_unmkfs(volume);
}


/*
 * Can fsck this volume?
 */
static int fat_can_fsck(logical_volume_t *volume)
{
	LOG_ENTRY();

	/* FIXME: Can we fsck if volume is mounted? */

	if (!have_dosfsck) {
		LOG_DEBUG("The dosfsck utility is not installed.\n");
		LOG_EXIT_INT(ENOSYS);
		return ENOSYS;
	}

	LOG_EXIT_INT(0);
	return 0;
}


/*
 * Can this volume be expanded?
 */
static int fat_can_expand_by(logical_volume_t *volume,
			     sector_count_t *delta_size)
{
	int  rc;

	LOG_ENTRY();

	if (EngFncs->is_mounted(volume->dev_node, NULL)) {
		/* If mounted, can't expand */
		rc = EBUSY;
		goto errout;
	}

	/* reset limits */
	rc = fat_get_fs_limits(volume, &volume->min_fs_size,
			       &volume->max_fs_size, &volume->max_vol_size);

	if (*delta_size > volume->max_fs_size - volume->fs_size) {
		*delta_size = volume->max_fs_size - volume->fs_size;
	}

errout:
	LOG_EXIT_INT(rc);
	return rc;
}


/*
 * Can this volume be shrunk?
 */
static int fat_can_shrink_by(logical_volume_t *volume,
			     sector_count_t *delta_size)
{
	int rc = 0;

	LOG_ENTRY();

	if (EngFncs->is_mounted(volume->dev_node, NULL)) {
		/* If mounted, can't shrink */
		rc = EBUSY;
		goto errout;
	}

	/* reset limits */
	rc = fat_get_fs_limits(volume, &volume->min_fs_size,
			       &volume->max_fs_size, &volume->max_vol_size);

	if (*delta_size > volume->fs_size - volume->min_fs_size) {
		*delta_size = volume->fs_size - volume->min_fs_size;
	}
	if (volume->min_fs_size >= volume->fs_size) {
		rc = ENOSPC;
	}

errout:
	LOG_EXIT_INT(rc);
	return rc;
}


/*
 * Get the current size of this volume
 */
static int fat_get_fs_size(logical_volume_t *volume,
			   sector_count_t *size)
{
	private_data_t *pd = volume->private_data;

	LOG_ENTRY();

	*size =	pd->fs_size;
	LOG_DEBUG("Size of file system on volume %s is %"PRIu64" sectors\n",
		  volume->name, pd->fs_size);

	LOG_EXIT_INT(0);
	return 0;
}


/*
 * mkfs has been scheduled.  Do any setup work such as claiming another
 * volume for an external log.
 */
static int fat_mkfs_setup(logical_volume_t *volume,
			  option_array_t *options)
{
	int rc = 0;

	LOG_ENTRY();

	volume->private_data = EngFncs->engine_alloc(sizeof(private_data_t));
	if (!volume->private_data) {
		LOG_CRITICAL("Unable to get memory for private data.\n");
		rc = ENOMEM;
	}

	LOG_EXIT_INT(rc);
	return rc;
}


/*
 * NAME: set_fat_mkfs_options
 *
 * FUNCTION: Build options array (argv) for mkdosfs
 *
 * PARAMETERS:
 *      options   - options array passed from EVMS engine
 *      argv      - mkfs options array
 *      vol_name  - volume on which program will be executed
 *
 */
static void set_fat_mkfs_options(int isfat16,
				 option_array_t *options,
				 char **argv,
				 logical_volume_t *volume)
{
	int i;
	int bufsize;
	int opt_count = 0;
	char *buf;

	LOG_ENTRY();

	argv[opt_count++] = "mkdosfs";
	argv[opt_count++] = isfat16 ? "-F16" : "-F32";

	for (i = 0; i < options->count; i++) {

		if (!options->option[i].is_number_based) {
			if (!strcmp(options->option[i].name, MKFS_LABEL_NAME)) {
				options->option[i].number = MKFS_LABEL_INDEX;
			} else {
				/* Unknown. Ignore. */
				continue;
			}
		}

		switch (options->option[i].number) {
		case MKFS_LABEL_INDEX:
		       	if (options->option[i].value.s != NULL) {
				argv[opt_count++] = "-n";
				argv[opt_count++] = options->option[i].value.s;
			}
			break;

		default:
			break;
		}
	}

	argv[opt_count++] = volume->dev_node;
	argv[opt_count] = NULL;

	bufsize = 0;
	for (i = 0; argv[i]; i++) {
		bufsize += strlen(argv[i]) + 1;
	}
	buf = EngFncs->engine_alloc(bufsize + 1);
	if (buf) {
		for (i = 0; argv[i]; i++) {
			strcat(buf, argv[i]);
			strcat(buf, " ");
		}

		LOG_DEBUG("mkdosfs command: %s\n", buf);
		EngFncs->engine_free(buf);
	}

	LOG_EXIT_VOID();
	return;
}


/*
 * Put the file system on the volume.
 */
static int _fat_mkfs(int isfat16,
		     logical_volume_t *volume,
		     option_array_t *options)
{
	int rc = 0;
	char *argv[MKFS_OPTIONS_COUNT + 10];
	pid_t pidm;
	int status;
	int fds[2];

	LOG_ENTRY();

	if (!have_mkdosfs) {
		MESSAGE(_("The mkdosfs utility is not installed "
			  "on this machine.\n"));
		LOG_EXIT_INT(ENOSYS);
		return ENOSYS;
	}

	set_fat_mkfs_options(isfat16, options, argv, volume);

	fds[0] = 0;
	fds[1] = open("/dev/null", O_WRONLY);

	pidm = EngFncs->fork_and_execvp(volume, argv, NULL, fds, fds);
	if (pidm != -1) {
		waitpid(pidm, &status, 0);
		if (WIFEXITED(status)) {
			/* get mkdosfs exit code */
			rc = WEXITSTATUS(status);
		} else {
			rc = EINTR;
		}
	} else {
		rc = errno;
	}

	close(fds[1]);

	LOG_EXIT_INT(rc);
	return rc;
}

static int fat_mkfs(logical_volume_t *volume, option_array_t *options)
{
	my_plugin_record = &fat_plugrec;
	return _fat_mkfs(0, volume, options);
}

static int fat16_mkfs(logical_volume_t *volume, option_array_t *options)
{
	my_plugin_record = &fat16_plugrec;
	return _fat_mkfs(1, volume, options);
}


/*
 * Expand the volume to new_size.  If the volume is not expanded exactly to
 * new_size, set new_size to the new_size of the volume.
 */
static int _fat_expand(int isfat16,
		       logical_volume_t *volume,
		       sector_count_t *new_size)
{
	int rc;

	LOG_ENTRY();

	/* Expand must be done offline. */
	if (EngFncs->is_mounted(volume->dev_node, NULL)) {
		LOG_DETAILS("Volume %s is mounted.\n", volume->name);
		LOG_EXIT_INT(EBUSY);
		return EBUSY;
	}

	rc = resize_fat(isfat16, volume, new_size);

	LOG_EXIT_INT(rc);
	return rc;
}

static int fat_expand(logical_volume_t *volume,
		      sector_count_t *new_size)
{
	return _fat_expand(0, volume, new_size);
}

static int fat16_expand(logical_volume_t *volume,
		        sector_count_t *new_size)
{
	return _fat_expand(1, volume, new_size);
}


/*
 * Shrink the volume to new_size.  If the volume is not shrunk exactly to
 * new_size, set new_size to the new_size of the volume.
 */
static int _fat_shrink(int isfat16,
		       logical_volume_t *volume,
		       sector_count_t requested_size,
		       sector_count_t *new_size)
{
	int rc;

	LOG_ENTRY();

	/* Shrink must be done offline. */
	if (EngFncs->is_mounted(volume->dev_node, NULL)) {
		LOG_DETAILS("Volume %s is mounted.\n", volume->name);
		LOG_EXIT_INT(EBUSY);
		return EBUSY;
	}

	*new_size = requested_size;
	rc = resize_fat(isfat16, volume, new_size);

	LOG_EXIT_INT(rc);
	return rc;
}

static int fat_shrink(logical_volume_t *volume,
		      sector_count_t requested_size,
		      sector_count_t *new_size)
{
	return _fat_shrink(0, volume, requested_size, new_size);
}

static int fat16_shrink(logical_volume_t *volume,
		        sector_count_t requested_size,
		        sector_count_t *new_size)
{
	return _fat_shrink(1, volume, requested_size, new_size);
}


/*
 * Forget about this volume.  Don't remove the file system.  Just clean up any
 * data structures you may have associated with it.
 */
static int fat_discard(logical_volume_t *volume)
{
	LOG_ENTRY();

	free_private_data(volume);

	LOG_EXIT_INT(0);
	return 0;
}


/*
 * Remove the file system from the volume.
 */
static int fat_unmkfs(logical_volume_t *volume)
{
	int rc;

	LOG_ENTRY();

	if (EngFncs->is_mounted(volume->dev_node, NULL)) {
		rc = EBUSY;
	} else {
		rc = clear_fat_boot_sectors(volume);
		if (rc == 0) {
			free_private_data(volume);
		}
	}

	LOG_EXIT_INT(rc);
	return rc;
}


/*
 * Return the total number of supported options for the specified task.
 */
static int fat_get_option_count(task_context_t *context)
{
	int count;

	LOG_ENTRY();

	switch (context->action) {
	case EVMS_Task_mkfs:
		count = MKFS_OPTIONS_COUNT;
		break;
	default:
		count = -1;
		break;
	}

	LOG_EXIT_INT(count);
	return count;
}


/*
 * Initialize mkfs task acceptable objects by enumerating volumes, finding
 * those that have no FSIM claiming them and are of the proper size and
 * adding them to the acceptable objects list.
 */
static int init_mkfs_acceptable_objects(task_context_t *context)
{
	list_anchor_t global_volumes = NULL;
	list_element_t vol_list_iter;
	logical_volume_t *volume;

	LOG_ENTRY();

	EngFncs->get_volume_list(NULL, NULL, 0, &global_volumes);
	LIST_FOR_EACH(global_volumes, vol_list_iter, volume) {
		if ((volume->file_system_manager == NULL) &&
		    (volume->vol_size >= MIN_FAT_SIZE) &&
		    (!EngFncs->is_mounted(volume->name, NULL))) {
			EngFncs->insert_thing(context->acceptable_objects,
					      volume, INSERT_AFTER, NULL);
		}
	}
	EngFncs->destroy_list(global_volumes);

	LOG_EXIT_INT(0);
	return 0;
}


/*
 * Fill in the initial list of acceptable objects.  Fill in the minimum and
 * maximum number of objects that must/can be selected.  Set up all initial
 * values in the option_descriptors in the context record for the given
 * task.  Some fields in the option_descriptor may be dependent on a
 * selected object.  Leave such fields blank for now, and fill in during the
 * set_objects call.
 */
static int fat_init_task(task_context_t *context)
{
	int rc = 0;
	option_descriptor_t *opt;

	LOG_ENTRY();

	switch (context->action) {
		
	case EVMS_Task_mkfs:
		rc = init_mkfs_acceptable_objects(context);
		if (rc) {
			LOG_EXIT_INT(rc);
			return rc;
		}
		context->option_descriptors->count = MKFS_OPTIONS_COUNT;

		/* Volume label option */
		opt = &context->option_descriptors->option[MKFS_LABEL_INDEX];
		opt->name = EngFncs->engine_strdup(MKFS_LABEL_NAME);
		opt->title = EngFncs->engine_strdup(_("Volume label"));
		opt->tip = EngFncs->engine_strdup(_("Set the label for the volume."));
		opt->type = EVMS_Type_String;
		opt->min_len = 1;
		opt->max_len = MAX_LABEL_LEN;
		opt->flags = EVMS_OPTION_FLAGS_NOT_REQUIRED;
		opt->value.s = EngFncs->engine_alloc(MAX_LABEL_LEN + 1);

		context->min_selected_objects = 1;
		context->max_selected_objects = 1;
		break;

	default:
		rc = EINVAL;
		break;
	}

	LOG_EXIT_INT(rc);
	return rc;
}


/*
 * Examine the specified value, and determine if it is valid for the task
 * and option_descriptor index. If it is acceptable, set that value in the
 * appropriate entry in the option_descriptor. The value may be adjusted
 * if necessary/allowed. If so, set the effect return value accordingly.
 */
static int fat_set_option(task_context_t *context,
			  u_int32_t index,
			  value_t *value,
			  task_effect_t *effect)
{
	int rc = 0;

	LOG_ENTRY();

	/* Parameter check */
	if (!context || !value || !effect) {
		LOG_EXIT_INT(EFAULT);
		return EFAULT;
	}

	switch (context->action) {
		
	case EVMS_Task_mkfs:
		switch (index) {
		case MKFS_LABEL_INDEX:
			strncpy(context->option_descriptors->option[index].value.s,
				value->s, MAX_LABEL_LEN);
			if (strlen(value->s) > MAX_LABEL_LEN) {
				MESSAGE(_("Volume label is truncated to \"%s\".\n"),
					context->option_descriptors->option[index].value.s);
			}
			break;

		default:
			break;
		}
		break;

	default:
		LOG_ERROR("I don't know how to set an option for action code "
			  "%d (%#x).\n", context->action, context->action);
		rc = EINVAL;
		break;
	}

	LOG_EXIT_INT(rc);
	return rc;
}


/*
 * Validate the volumes in the selected_objects list in the task context.
 * Remove from the selected objects lists any volumes which are not
 * acceptable.  For unacceptable volumes, create a declined_handle_t
 * structure with the reason why it is not acceptable, and add it to the
 * declined_volumes list.  Modify the acceptable_objects list in the task
 * context as necessary based on the selected objects and the current
 * settings of the options.  Modify any option settings as necessary based
 * on the selected objects.  Return the appropriate task_effect_t settings
 * if the object list(s), minimum or maximum objects selected, or option
 * settings have changed.
 */
static int fat_set_volumes(task_context_t *context,
			   list_anchor_t declined_volumes,	 /* of type declined_handle_t */
			   task_effect_t *effect)
{
	int rc = 0;
	logical_volume_t *vol;

	LOG_ENTRY();

	switch (context->action) {

	case EVMS_Task_mkfs:

		/* Only one selected volume allowed, so grab
		 * the first thing from the list.
		 */
		vol = EngFncs->first_thing(context->selected_objects, NULL);
		if (!vol) {
			rc = EINVAL;
			break;
		}

		if (EngFncs->is_mounted(vol->dev_node, NULL)) {
			/* If mounted, can't mkdosfs. */
			LOG_ERROR("Volume %s is mounted on %s.\n",
				  vol->name, vol->mount_point);
			rc = EBUSY;
			break;
		}

		if (vol->vol_size < MIN_FAT_SIZE) {
			LOG_ERROR("Volume %s is too small. FAT volumes must "
				  "be at least %u sectors in size.\n",
				  vol->name, MIN_FAT_SIZE);
			rc = EINVAL;
			break;
		}

		break;

	default:
		rc = EINVAL;
		break;
	}

	LOG_EXIT_INT(rc);
	return rc;
}


/*
 * Return any additional information that you wish to provide about the
 * volume.  The Engine provides an external API to get the information
 * stored in the logical_volume_t.  This call is to get any other
 * information about the volume that is not specified in the
 * logical_volume_t.  Any piece of information you wish to provide must be
 * in an extended_info_t structure.  Use the Engine's engine_alloc() to
 * allocate the memory for the extended_info_t.  Also use engine_alloc() to
 * allocate any strings that may go into the extended_info_t.  Then use
 * engine_alloc() to allocate an extended_info_array_t with enough entries
 * for the number of extended_info_t structures you are returning.  Fill
 * in the array and return it in *info.
 * If you have extended_info_t descriptors that themselves may have more
 * extended information, set the EVMS_EINFO_FLAGS_MORE_INFO_AVAILABLE flag
 * in the extended_info_t flags field.  If the caller wants more information
 * about a particular extended_info_t item, this API will be called with a
 * pointer to the storage_object_t and with a pointer to the name of the
 * extended_info_t item.  In that case, return an extended_info_array_t with
 * further information about the item.  Each of those items may have the
 * EVMS_EINFO_FLAGS_MORE_INFO_AVAILABLE flag set if you desire.  It is your
 * responsibility to give the items unique names so that you know which item
 * the caller is asking additional information for.  If info_name is NULL,
 * the caller just wants top level information about the object.
 */
static int fat_get_volume_info(logical_volume_t *volume,
			       char *info_name,
			       extended_info_array_t **info)
{
	private_data_t *pd = volume->private_data;
	extended_info_array_t *Info;
	int i;

	LOG_ENTRY();

	if (info_name != NULL) {
		LOG_ERROR("Volume %s has no extra information named \"%s\".\n",
			  volume->name, info_name);
		LOG_EXIT_INT(EINVAL);
		return EINVAL;
	}

	Info = EngFncs->engine_alloc(sizeof(extended_info_array_t) +
				     3 * sizeof(extended_info_t));
	if (!Info) {
		LOG_CRITICAL("Unable to allocate memory for the "
			     "extended_info_array_t buffer.\n");
		LOG_EXIT_INT(ENOMEM);
		return ENOMEM;
	}

	i = 0;

	if (pd->vol_name != NULL) {
		Info->info[i].name = EngFncs->engine_strdup("FAT Volume Name");
		Info->info[i].title = EngFncs->engine_strdup(_("FAT Volume Name"));
		Info->info[i].desc = EngFncs->engine_strdup(_("The FAT name of the volume"));
		Info->info[i].type = EVMS_Type_String;
		Info->info[i].value.s = EngFncs->engine_strdup(pd->vol_name);
		i++;
	}

	if (pd->vol_id != 0) {
		Info->info[i].name = EngFncs->engine_strdup("FAT Volume ID");
		Info->info[i].title = EngFncs->engine_strdup(_("FAT Volume ID"));
		Info->info[i].desc = EngFncs->engine_strdup(_("The FAT ID of the volume"));
		Info->info[i].type = EVMS_Type_Int32;
		Info->info[i].format = EVMS_Format_Hex;
		Info->info[i].value.i32 = pd->vol_id;
		i++;
	}

	if (pd->cluster_size != 0) {
		Info->info[i].name = EngFncs->engine_strdup("Cluster Size");
		Info->info[i].title = EngFncs->engine_strdup(_("Cluster Size"));
		Info->info[i].desc = EngFncs->engine_strdup(_("Size of a cluster."));
		Info->info[i].type = EVMS_Type_Unsigned_Int32;
		Info->info[i].unit = EVMS_Unit_Bytes;
		Info->info[i].value.ui32 = pd->cluster_size;
		i++;
	}

	Info->count = i;
	*info = Info;

	LOG_EXIT_INT(0);
	return 0;
}


/*
 *  Return Plug-in specific information.
 */
static int _fat_get_plugin_info(char *descriptor_name,
				extended_info_array_t **info)
{
	extended_info_array_t *Info;
	char version_string[64];
	char required_engine_api_version_string[64];
	char required_fsim_api_version_string[64];

	LOG_ENTRY();

	if (!info || descriptor_name) {
		LOG_EXIT_INT(EINVAL);
		return EINVAL;
	}

	/* Initializie to no info returned. */
	*info = NULL;

	Info = EngFncs->engine_alloc(sizeof(extended_info_array_t) +
				     6 * sizeof(extended_info_t));
	if (!Info) {
		LOG_EXIT_INT(ENOMEM);
		return ENOMEM;
	}

	Info->count = 6;

	sprintf(version_string, "%d.%d.%d",
		MAJOR_VERSION, MINOR_VERSION, PATCH_LEVEL);

	sprintf(required_engine_api_version_string, "%d.%d.%d",
		my_plugin_record->required_engine_api_version.major,
		my_plugin_record->required_engine_api_version.minor,
		my_plugin_record->required_engine_api_version.patchlevel);

	sprintf(required_fsim_api_version_string, "%d.%d.%d",
		my_plugin_record->required_plugin_api_version.fsim.major,
		my_plugin_record->required_plugin_api_version.fsim.minor,
		my_plugin_record->required_plugin_api_version.fsim.patchlevel);

	Info->info[0].name = EngFncs->engine_strdup("Short Name");
	Info->info[0].title = EngFncs->engine_strdup(_("Short Name"));
	Info->info[0].desc = EngFncs->engine_strdup(_("A short name given to this plug-in"));
	Info->info[0].type = EVMS_Type_String;
	Info->info[0].value.s = EngFncs->engine_strdup(my_plugin_record->short_name);

	Info->info[1].name = EngFncs->engine_strdup("Long Name");
	Info->info[1].title = EngFncs->engine_strdup(_("Long Name"));
	Info->info[1].desc = EngFncs->engine_strdup(_("A longer, more descriptive name for this plug-in"));
	Info->info[1].type = EVMS_Type_String;
	Info->info[1].value.s = EngFncs->engine_strdup(my_plugin_record->long_name);

	Info->info[2].name = EngFncs->engine_strdup("Type");
	Info->info[2].title = EngFncs->engine_strdup(_("Plug-in Type"));
	Info->info[2].desc = EngFncs->engine_strdup(_("There are various types of plug-ins, each responsible for some kind of storage object or logical volume."));
	Info->info[2].type = EVMS_Type_String;
	Info->info[2].value.s = EngFncs->engine_strdup(_("File System Interface Module"));

	Info->info[3].name = EngFncs->engine_strdup("Version");
	Info->info[3].title = EngFncs->engine_strdup(_("Plug-in Version"));
	Info->info[3].desc = EngFncs->engine_strdup(_("This is the version number of the plug-in."));
	Info->info[3].type = EVMS_Type_String;
	Info->info[3].value.s = EngFncs->engine_strdup(version_string);

	Info->info[4].name = EngFncs->engine_strdup("Required Engine Services Version");
	Info->info[4].title = EngFncs->engine_strdup(_("Required Engine Services Version"));
	Info->info[4].desc = EngFncs->engine_strdup(_("This is the version of the Engine services that this plug-in requires.  "
						      "It will not run on older versions of the Engine services."));
	Info->info[4].type = EVMS_Type_String;
	Info->info[4].value.s = EngFncs->engine_strdup(required_engine_api_version_string);

	Info->info[5].name = EngFncs->engine_strdup("Required Engine FSIM API Version");
	Info->info[5].title = EngFncs->engine_strdup(_("Required Engine FSIM API Version"));
	Info->info[5].desc = EngFncs->engine_strdup(_("This is the version of the Engine FSIM API that this plug-in requires.  "
						      "It will not run on older versions of the Engine FSIM API."));
	Info->info[5].type = EVMS_Type_String;
	Info->info[5].value.s = EngFncs->engine_strdup(required_fsim_api_version_string);

	*info = Info;

	LOG_EXIT_INT(0);
	return 0;
}

static inline int fat_get_plugin_info(char *descriptor_name,
				      extended_info_array_t **info)
{
	my_plugin_record = &fat_plugrec;
	return _fat_get_plugin_info(descriptor_name, info);
}

static inline int fat16_get_plugin_info(char *descriptor_name,
					extended_info_array_t **info)
{
	my_plugin_record = &fat16_plugrec;
	return _fat_get_plugin_info(descriptor_name, info);
}

/**
 * Tables of standard FSIM APIs for the FAT plugins.
 **/
static fsim_functions_t fsim_ops = {
	.setup_evms_plugin	= fat_setup,
	.cleanup_evms_plugin	= fat_cleanup,
	.probe			= fat_probe,
	.get_fs_size		= fat_get_fs_size,
	.get_fs_limits		= fat_get_fs_limits,
	.mkfs_setup		= fat_mkfs_setup,
	.mkfs			= fat_mkfs,
	.can_mkfs		= fat_can_mkfs,
	.can_unmkfs		= fat_can_unmkfs,
	.can_fsck		= fat_can_fsck,
	.can_expand_by		= fat_can_expand_by,
	.can_shrink_by		= fat_can_shrink_by,
	.discard		= fat_discard,
	.unmkfs			= fat_unmkfs,
	.expand			= fat_expand,
	.shrink			= fat_shrink,
	.get_option_count	= fat_get_option_count,
	.init_task		= fat_init_task,
	.set_option		= fat_set_option,
	.set_volumes		= fat_set_volumes,
	.get_volume_info	= fat_get_volume_info,
	.get_plugin_info	= fat_get_plugin_info,
};

static fsim_functions_t fsim16_ops = {
	.setup_evms_plugin	= fat_setup,
	.cleanup_evms_plugin	= fat16_cleanup,
	.probe			= fat16_probe,
	.get_fs_size		= fat_get_fs_size,
	.get_fs_limits		= fat_get_fs_limits,
	.mkfs_setup		= fat_mkfs_setup,
	.mkfs			= fat16_mkfs,
	.can_mkfs		= fat_can_mkfs,
	.can_unmkfs		= fat16_can_unmkfs,
	.can_fsck		= fat_can_fsck,
	.can_expand_by		= fat_can_expand_by,
	.can_shrink_by		= fat_can_shrink_by,
	.discard		= fat_discard,
	.unmkfs			= fat_unmkfs,
	.expand			= fat16_expand,
	.shrink			= fat16_shrink,
	.get_option_count	= fat_get_option_count,
	.init_task		= fat_init_task,
	.set_option		= fat_set_option,
	.set_volumes		= fat_set_volumes,
	.get_volume_info	= fat_get_volume_info,
	.get_plugin_info	= fat16_get_plugin_info,
};


/**
 * Plugin records for the FAT plugins.
 **/
static plugin_record_t fat_plugrec = {
	.id = EVMS_FAT_PLUGIN_ID,
	.version = {
		.major		= MAJOR_VERSION,
		.minor		= MINOR_VERSION,
		.patchlevel	= PATCH_LEVEL
	},
	.required_engine_api_version = {
		.major		= 15,
		.minor		= 0,
		.patchlevel	= 0
	},
	.required_plugin_api_version = {
		.fsim = {
			.major		= 11,
			.minor		= 0,
			.patchlevel	= 0
		}
	},
	.short_name = EVMS_FAT_PLUGIN_SHORT_NAME,
	.long_name = EVMS_FAT_PLUGIN_LONG_NAME,
	.oem_name = EVMS_ALT_LINUX_OEM_NAME,
	.functions = {
		.fsim = &fsim_ops
	},
	.container_functions = NULL
};

static plugin_record_t fat16_plugrec = {
	.id = EVMS_FAT16_PLUGIN_ID,
	.version = {
		.major		= MAJOR_VERSION,
		.minor		= MINOR_VERSION,
		.patchlevel	= PATCH_LEVEL
	},
	.required_engine_api_version = {
		.major		= 15,
		.minor		= 0,
		.patchlevel	= 0
	},
	.required_plugin_api_version = {
		.fsim = {
			.major		= 11,
			.minor		= 0,
			.patchlevel	= 0
		}
	},
	.short_name = EVMS_FAT16_PLUGIN_SHORT_NAME,
	.long_name = EVMS_FAT16_PLUGIN_LONG_NAME,
	.oem_name = EVMS_ALT_LINUX_OEM_NAME,
	.functions = {
		.fsim = &fsim16_ops
	},
	.container_functions = NULL
};

plugin_record_t *evms_plugin_records[] = {
	&fat_plugrec,
	&fat16_plugrec,
	NULL
};