blob: cef277cfec77e888b765bf797cf4bf439092a3be [file] [log] [blame]
/*
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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
/** \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;
}
/** @} */