OpenSolaris_b135/lib/libparted/common/libparted/filesys.c

Compare this file to the similar file:
Show the results in this format:

/*
    libparted - a library for manipulating disk partitions
    Copyright (C) 1999, 2000, 2001, 2007 Free Software Foundation, Inc.

    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 3 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, see <http://www.gnu.org/licenses/>.
*/

/** \file filesys.c */

/**
 * \addtogroup PedFileSystem
 *
 * \note File systems exist on a PedGeometry - NOT a PedPartition.
 *
 * @{
 */

#include <config.h>

#include <parted/parted.h>
#include <parted/debug.h>

#if ENABLE_NLS
#  include <libintl.h>
#  define _(String) dgettext (PACKAGE, String)
#else
#  define _(String) (String)
#endif /* ENABLE_NLS */

#define BUFFER_SIZE	4096		/* in sectors */

static PedFileSystemType*	fs_types = NULL;

void
ped_file_system_type_register (PedFileSystemType* fs_type)
{
	PED_ASSERT (fs_type != NULL, return);
	PED_ASSERT (fs_type->ops != NULL, return);
	PED_ASSERT (fs_type->name != NULL, return);
	
	/* pretend that "next" isn't part of the struct :-) */
	((struct _PedFileSystemType*) fs_type)->next = fs_types;
	fs_types = (struct _PedFileSystemType*) fs_type;
}

void
ped_file_system_type_unregister (PedFileSystemType* fs_type)
{
	PedFileSystemType*	walk;
	PedFileSystemType*	last = NULL;

	PED_ASSERT (fs_types != NULL, return);
	PED_ASSERT (fs_type != NULL, return);

	for (walk = fs_types; walk && walk != fs_type;
                last = walk, walk = walk->next);

	PED_ASSERT (walk != NULL, return);
	if (last)
		((struct _PedFileSystemType*) last)->next = fs_type->next;
	else
		fs_types = fs_type->next;	
}

/**
 * Get a PedFileSystemType by its @p name.
 *
 * @return @c NULL if none found.
 */
PedFileSystemType*
ped_file_system_type_get (const char* name)
{
	PedFileSystemType*	walk;

	PED_ASSERT (name != NULL, return NULL);

	for (walk = fs_types; walk != NULL; walk = walk->next) {
		if (!strcasecmp (walk->name, name))
			break;
	}
	return walk;
}

/**
 * Get the next PedFileSystemType after @p fs_type.
 *
 * @return @c NULL if @p fs_type is the last item in the list.
 */
PedFileSystemType*
ped_file_system_type_get_next (const PedFileSystemType* fs_type)
{
	if (fs_type)
		return fs_type->next;
	else
		return fs_types;
}

/**
 * Attempt to find a file system and return the region it occupies.
 *
 * @param fs_type The file system type to probe for.
 * @param geom The region to be searched.
 *
 * @return @p NULL if @p fs_type file system wasn't detected
 */
PedGeometry*
ped_file_system_probe_specific (
		const PedFileSystemType* fs_type, PedGeometry* geom)
{
	PedGeometry*	result;

	PED_ASSERT (fs_type != NULL, return NULL);
	PED_ASSERT (fs_type->ops->probe != NULL, return NULL);
	PED_ASSERT (geom != NULL, return NULL);

	if (!ped_device_open (geom->dev))
		return 0;
	result = fs_type->ops->probe (geom);
	ped_device_close (geom->dev);
	return result;
}

static int
_test_open (PedFileSystemType* fs_type, PedGeometry* geom)
{
	PedFileSystem*		fs;

	ped_exception_fetch_all ();
	fs = fs_type->ops->open (geom);
	if (fs)
		fs_type->ops->close (fs);
	else
		ped_exception_catch ();
	ped_exception_leave_all ();
	return fs != NULL;
}

static PedFileSystemType*
_probe_with_open (PedGeometry* geom, int detected_count,
		  PedFileSystemType* detected[])
{
	int			i;
	PedFileSystemType*	open_detected = NULL;

	ped_device_open (geom->dev);

	/* If one and only one file system that Parted is able to open
	 * can be successfully opened on this geometry, return it.
	 * If more than one can be, return NULL.
	 */
	for (i=0; i<detected_count; i++) {
		if (!detected[i]->ops->open || !_test_open (detected [i], geom))
			continue;

		if (open_detected) {
			ped_device_close (geom->dev);
			return NULL;
		} else {
			open_detected = detected [i];
		}
	}

	/* If no file system has been successfully opened, and
	 * if Parted has detected at most one unopenable file system,
	 * return it.
	 */
	if (!open_detected)
	for (i=0; i<detected_count; i++) {
		if (detected[i]->ops->open)
			continue;
		if (open_detected) {
			ped_device_close (geom->dev);
			return NULL;
		} else {
			open_detected = detected [i];
		}
	}	

	ped_device_close (geom->dev);
	return open_detected;
}

static int
_geometry_error (const PedGeometry* a, const PedGeometry* b)
{
	PedSector	start_delta = a->start - b->start;
	PedSector	end_delta = a->end - b->end;

	return abs (start_delta) + abs (end_delta);
}

static PedFileSystemType*
_best_match (const PedGeometry* geom, PedFileSystemType* detected [],
	     const int detected_error [], int detected_count)
{
	int		best_match = 0;
	int		i;
	PedSector	min_error;

	min_error = PED_MAX (4096, geom->length / 100);

	for (i = 1; i < detected_count; i++) {
		if (detected_error [i] < detected_error [best_match])
			best_match = i;
	}

	/* make sure the best match is significantly better than all the
	 * other matches
	 */
	for (i = 0; i < detected_count; i++) {
		if (i == best_match)
			continue;

		if (abs (detected_error [best_match] - detected_error [i])
				< min_error)
			return NULL;
	}

	return detected [best_match];
}


/**
 * Attempt to detect a file system in region \p geom. 
 * This function tries to be clever at dealing with ambiguous
 * situations, such as when one file system was not completely erased before a
 * new file system was created on top of it.
 *
 * \return a new PedFileSystem on success, \c NULL on failure
 */
PedFileSystemType*
ped_file_system_probe (PedGeometry* geom)
{
	PedFileSystemType*	detected[32];
	int			detected_error[32];
	int			detected_count = 0;
	PedFileSystemType*	walk = NULL;

	PED_ASSERT (geom != NULL, return NULL);

	if (!ped_device_open (geom->dev))
		return NULL;

	ped_exception_fetch_all ();
	while ( (walk = ped_file_system_type_get_next (walk)) ) {
		PedGeometry*	probed;

		probed = ped_file_system_probe_specific (walk, geom);
		if (probed) {
			detected [detected_count] = walk;
			detected_error [detected_count]
				= _geometry_error (geom, probed);
			detected_count++;
			ped_geometry_destroy (probed);
		} else {
			ped_exception_catch ();
		}
	}
	ped_exception_leave_all ();

	ped_device_close (geom->dev);

	if (!detected_count)
		return NULL;
	walk = _best_match (geom, detected, detected_error, detected_count);
	if (walk)
		return walk;
	return _probe_with_open (geom, detected_count, detected);
}

/**
 * This function erases all file system signatures that indicate that a
 * file system occupies a given region described by \p geom.
 * After this operation ped_file_system_probe() won't detect any file system.
 *
 * \note ped_file_system_create() calls this before creating a new file system.
 * 
 * \return \c 1 on success, \c 0 on failure
 */
int
ped_file_system_clobber (PedGeometry* geom)
{
	PedFileSystemType*	fs_type = NULL;

	PED_ASSERT (geom != NULL, return 0);

	if (!ped_device_open (geom->dev))
		goto error;

	ped_exception_fetch_all ();
	while ((fs_type = ped_file_system_type_get_next (fs_type))) {
		PedGeometry*	probed;

		if (!fs_type->ops->clobber)
			continue;

		probed = ped_file_system_probe_specific (fs_type, geom);
		if (!probed) {
			ped_exception_catch ();
			continue;
		}
		ped_geometry_destroy (probed);

		if (fs_type->ops->clobber && !fs_type->ops->clobber (geom)) {
			ped_exception_leave_all ();
			goto error_close_dev;
		}
	}
	ped_device_close (geom->dev);
	ped_exception_leave_all ();
	return 1;

error_close_dev:
	ped_device_close (geom->dev);
error:
	return 0;
}

/* This function erases all signatures that indicate the presence of
 * a file system in a particular region, without erasing any data
 * contained inside the "exclude" region.
 */
static int
ped_file_system_clobber_exclude (PedGeometry* geom,
				 const PedGeometry* exclude)
{
	PedGeometry*    clobber_geom;
	int             status;

	if (ped_geometry_test_sector_inside (exclude, geom->start))
		return 1;

	clobber_geom = ped_geometry_duplicate (geom);
	if (ped_geometry_test_overlap (clobber_geom, exclude))
		ped_geometry_set_end (clobber_geom, exclude->start - 1);

	status = ped_file_system_clobber (clobber_geom);
	ped_geometry_destroy (clobber_geom);
	return status;
}

/**
 * This function opens the file system stored on \p geom, if it
 * can find one.
 * It is often called in the following manner:
 * \code
 * 	fs = ped_file_system_open (&part.geom)
 * \endcode
 *
 * \throws PED_EXCEPTION_ERROR if file system could not be detected
 * \throws PED_EXCEPTION_ERROR if the file system is bigger than its volume
 * \throws PED_EXCEPTION_NO_FEATURE if opening of a file system stored on 
 * 	\p geom is not implemented
 *
 * \return a PedFileSystem on success, \c NULL on failure.
 */
PedFileSystem*
ped_file_system_open (PedGeometry* geom)
{
	PedFileSystemType*	type;
	PedFileSystem*		fs;
	PedGeometry*		probed_geom;

	PED_ASSERT (geom != NULL, return NULL);

	if (!ped_device_open (geom->dev))
		goto error;

	type = ped_file_system_probe (geom);
	if (!type) {
		ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
				     _("Could not detect file system."));
		goto error_close_dev;
	}

	probed_geom = ped_file_system_probe_specific (type, geom);
	if (!probed_geom)
		goto error_close_dev;
	if (!ped_geometry_test_inside (geom, probed_geom)) {
		if (ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_IGNORE_CANCEL,
			_("The file system is bigger than its volume!"))
				!= PED_EXCEPTION_IGNORE)
			goto error_destroy_probed_geom;
	}

	if (!type->ops->open) {
		ped_exception_throw (PED_EXCEPTION_NO_FEATURE,
				     PED_EXCEPTION_CANCEL,
				     _("Support for opening %s file systems "
				       "is not implemented yet."),
				     type->name);
		goto error_destroy_probed_geom;
	}

	fs = type->ops->open (probed_geom);
	if (!fs)
		goto error_destroy_probed_geom;
	ped_geometry_destroy (probed_geom);
	return fs;

error_destroy_probed_geom:
	ped_geometry_destroy (probed_geom);
error_close_dev:
	ped_device_close (geom->dev);
error:
	return 0;
}

/**
 * This function initializes a new file system of type \p type on 
 * a region described by \p geom, writing out appropriate metadata and 
 * signatures.  If \p timer is non-NULL, it is used as the progress meter.
 *
 * \throws PED_EXCEPTION_NO_FEATURE if creating file system type \p type 
 * 	is not implemented yet
 *
 * \return a PedFileSystem on success, \c NULL on failure
 */
PedFileSystem*
ped_file_system_create (PedGeometry* geom, const PedFileSystemType* type,
			PedTimer* timer)
{
	PedFileSystem*	fs;

	PED_ASSERT (geom != NULL, return NULL);
	PED_ASSERT (type != NULL, return NULL);

	if (!type->ops->create) {
		ped_exception_throw (PED_EXCEPTION_NO_FEATURE,
				     PED_EXCEPTION_CANCEL,
				     _("Support for creating %s file systems "
				       "is not implemented yet."),
				     type->name);
		goto error;
	}

	if (!ped_device_open (geom->dev))
		goto error;

	if (!ped_file_system_clobber (geom))
		goto error_close_dev;
	fs = type->ops->create (geom, timer);
	if (!fs)
		goto error_close_dev;
	return fs;

error_close_dev:
	ped_device_close (geom->dev);
error:
	return 0;
}

/**
 * Close file system \p fs.
 *
 * \return \c 1 on success, \c 0 on failure
 */
int
ped_file_system_close (PedFileSystem* fs)
{
	PedDevice*	dev = fs->geom->dev;

	PED_ASSERT (fs != NULL, goto error_close_dev);

	if (!fs->type->ops->close (fs))
		goto error_close_dev;
	ped_device_close (dev);
	return 1;

error_close_dev:
	ped_device_close (dev);
	return 0;
}

/**
 * Check \p fs file system for errors.
 *
 * \throws PED_EXCEPTION_NO_FEATURE if checking file system \p fs is 
 * 	not implemented yet
 *
 * \return \c 0 on failure (i.e. unfixed errors)
 */
int
ped_file_system_check (PedFileSystem* fs, PedTimer* timer)
{
	PED_ASSERT (fs != NULL, return 0);

	if (!fs->type->ops->check) {
		ped_exception_throw (PED_EXCEPTION_NO_FEATURE,
				     PED_EXCEPTION_CANCEL,
				     _("Support for checking %s file systems "
				       "is not implemented yet."),
				     fs->type->name);
		return 0;
	}
	return fs->type->ops->check (fs, timer);
}

static int
_raw_copy (const PedGeometry* src, PedGeometry* dest, PedTimer* timer)
{
	char*		buf;
	PedSector	pos;

	PED_ASSERT (src != NULL, goto error);
	PED_ASSERT (dest != NULL, goto error);
	PED_ASSERT (src->length <= dest->length, goto error);

	buf = ped_malloc (BUFFER_SIZE * 512);		/* FIXME */
	if (!buf)
		goto error;

	if (!ped_device_open (src->dev))
		goto error_free_buf;
	if (!ped_device_open (dest->dev))
		goto error_close_src;

	for (pos = 0; pos + BUFFER_SIZE < src->length; pos += BUFFER_SIZE) {
		ped_timer_update (timer, 1.0 * pos / src->length);
		if (!ped_geometry_read (src, buf, pos, BUFFER_SIZE))
			goto error_close_dest;
		if (!ped_geometry_write (dest, buf, pos, BUFFER_SIZE))
			goto error_close_dest;
	}
	if (pos < src->length) {
		ped_timer_update (timer, 1.0 * pos / src->length);
		if (!ped_geometry_read (src, buf, pos, src->length - pos))
			goto error_close_dest;
		if (!ped_geometry_write (dest, buf, pos, src->length - pos))
			goto error_close_dest;
	}
	ped_timer_update (timer, 1.0);

	ped_device_close (src->dev);
	ped_device_close (dest->dev);
	ped_free (buf);
	return 1;

error_close_dest:
	ped_device_close (dest->dev);
error_close_src:
	ped_device_close (src->dev);
error_free_buf:
	ped_free (buf);
error:
	return 0;
}

static PedFileSystem*
_raw_copy_and_resize (const PedFileSystem* fs, PedGeometry* geom,
		      PedTimer* timer)
{
	PedFileSystem*	new_fs;
	PedTimer*	sub_timer = NULL;

	ped_timer_reset (timer);
	ped_timer_set_state_name (timer, _("raw block copying"));

	sub_timer = ped_timer_new_nested (timer, 0.95);
	if (!_raw_copy (fs->geom, geom, sub_timer))
		goto error;
	ped_timer_destroy_nested (sub_timer);

	new_fs = ped_file_system_open (geom);
	if (!new_fs)
		goto error;

	ped_timer_set_state_name (timer, _("growing file system"));

	sub_timer = ped_timer_new_nested (timer, 0.05);
	if (!ped_file_system_resize (new_fs, geom, sub_timer))
		goto error_close_new_fs;
	ped_timer_destroy_nested (sub_timer);
	return new_fs;

error_close_new_fs:
	ped_file_system_close (new_fs);
error:
	ped_timer_destroy_nested (sub_timer);
	return NULL;
}

/**
 * Create a new file system (of the same type) on \p geom, and
 * copy the contents of \p fs into the new filesystem.  
 * If \p timer is non-NULL, it is used as the progress meter.
 *
 * \throws PED_EXCEPTION_ERROR when trying to copy onto an overlapping partition
 * \throws PED_EXCEPTION_NO_FEATURE if copying of file system \p fs 
 * 	is not implemented yet
 *
 * \return a new PedFileSystem on success, \c NULL on failure
 */
PedFileSystem*
ped_file_system_copy (PedFileSystem* fs, PedGeometry* geom, PedTimer* timer)
{
	PedFileSystem* new_fs;

	PED_ASSERT (fs != NULL, return 0);
	PED_ASSERT (geom != NULL, return 0);

	if (!ped_device_open (geom->dev))
		goto error;

	if (ped_geometry_test_overlap (fs->geom, geom)) {
		ped_exception_throw (
			PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
			_("Can't copy onto an overlapping partition."));
		goto error_close_dev;
	}

	if (!fs->checked && fs->type->ops->check) {
		if (!ped_file_system_check (fs, timer))
			goto error_close_dev;
	}

	if (!ped_file_system_clobber_exclude (geom, fs->geom))
		goto error_close_dev;

	if (!fs->type->ops->copy) {
		if (fs->type->ops->resize) {
			if (fs->geom->length <= geom->length)
				return _raw_copy_and_resize (
						fs, (PedGeometry*) geom,
						timer);
				
			ped_exception_throw (
				PED_EXCEPTION_NO_FEATURE,
				PED_EXCEPTION_CANCEL,
				_("Direct support for copying file systems is "
				  "not yet implemented for %s.  However, "
				  "support for resizing is implemented.  "
				  "Therefore, the file system can be copied if "
				  "the new partition is at least as big as the "
				  "old one.  So, either shrink the partition "
				  "you are trying to copy, or copy to a bigger "
				  "partition."),
				fs->type->name);
			goto error_close_dev;
		} else {
			ped_exception_throw (
				PED_EXCEPTION_NO_FEATURE,
				PED_EXCEPTION_CANCEL,
				_("Support for copying %s file systems is not "
				  "implemented yet."),
				fs->type->name);
			goto error_close_dev;
		}
	}
	new_fs = fs->type->ops->copy (fs, geom, timer);
	if (!new_fs)
		goto error_close_dev;
	return new_fs;

error_close_dev:
	ped_device_close (geom->dev);
error:
	return NULL;;
}

/**
 * Resize \p fs to new geometry \p geom.
 *
 * \p geom should satisfy the ped_file_system_get_resize_constraint().
 * (This isn't asserted, so it's not a bug not to... just it's likely
 * to fail ;)  If \p timer is non-NULL, it is used as the progress meter.
 *
 * \throws PED_EXCEPTION_NO_FEATURE if resizing of file system \p fs 
 * 	is not implemented yet
 * 
 * \return \c 0 on failure 
 */
int
ped_file_system_resize (PedFileSystem* fs, PedGeometry* geom, PedTimer* timer)
{
	PED_ASSERT (fs != NULL, return 0);
	PED_ASSERT (geom != NULL, return 0);

	if (!fs->type->ops->resize) {
		ped_exception_throw (PED_EXCEPTION_NO_FEATURE,
				     PED_EXCEPTION_CANCEL,
				     _("Support for resizing %s file systems "
				       "is not implemented yet."),
				     fs->type->name);
		return 0;
	}
	if (!fs->checked && fs->type->ops->check) {
		if (!ped_file_system_check (fs, timer))
			return 0;
	}
	if (!ped_file_system_clobber_exclude (geom, fs->geom))
		return 0;

	return fs->type->ops->resize (fs, geom, timer);
}

/**
 * This function returns a constraint on the region that all file systems
 * of a particular type \p fs_type created on device \p dev with 
 * ped_file_system_create() must satisfy. For example, FAT16 file systems must
 * be at least 32 megabytes.
 *
 * \return \c NULL on failure
 */
PedConstraint*
ped_file_system_get_create_constraint (const PedFileSystemType* fs_type,
				       const PedDevice* dev)
{
	PED_ASSERT (fs_type != NULL, return NULL);
	PED_ASSERT (dev != NULL, return NULL);

	if (!fs_type->ops->get_create_constraint)
		return NULL;
	return fs_type->ops->get_create_constraint (dev);
}
/**
 * Return a constraint, that represents all of the possible ways the
 * file system \p fs can be resized with ped_file_system_resize().  
 * This takes into account the amount of used space on
 * the filesystem \p fs and the capabilities of the resize algorithm.
 * Hints:
 * -# if constraint->start_align->grain_size == 0, or
 *    constraint->start_geom->length == 1, then the start can not be moved
 * -# constraint->min_size is the minimum size you can resize the partition
 *    to.  You might want to tell the user this ;-).
 *    
 * \return a PedConstraint on success, \c NULL on failure
 */
PedConstraint*
ped_file_system_get_resize_constraint (const PedFileSystem* fs)
{
	PED_ASSERT (fs != NULL, return 0);

	if (!fs->type->ops->get_resize_constraint)
		return NULL;
	return fs->type->ops->get_resize_constraint (fs);
}

/**
 * Get the constraint on copying \p fs with ped_file_system_copy()
 * to somewhere on \p dev.
 *
 * \return a PedConstraint on success, \c NULL on failure
 */ 
PedConstraint*
ped_file_system_get_copy_constraint (const PedFileSystem* fs,
				     const PedDevice* dev)
{
	PedGeometry	full_dev;

	PED_ASSERT (fs != NULL, return NULL);
	PED_ASSERT (dev != NULL, return NULL);

	if (fs->type->ops->get_copy_constraint)
		return fs->type->ops->get_copy_constraint (fs, dev);

	if (fs->type->ops->resize) {
		if (!ped_geometry_init (&full_dev, dev, 0, dev->length - 1))
			return NULL;
		return ped_constraint_new (
				ped_alignment_any, ped_alignment_any,
				&full_dev, &full_dev,
				fs->geom->length, dev->length);
	}

	return NULL;
}

/** @} */