diff options
Diffstat (limited to 'libparted/device.c')
-rw-r--r-- | libparted/device.c | 562 |
1 files changed, 562 insertions, 0 deletions
diff --git a/libparted/device.c b/libparted/device.c new file mode 100644 index 0000000..36fecd2 --- /dev/null +++ b/libparted/device.c @@ -0,0 +1,562 @@ +/* + libparted - a library for manipulating disk partitions + Copyright (C) 1999 - 2001, 2005, 2007-2010 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 device.c */ + +/** + * \addtogroup PedDevice + * + * \brief Device access. + * + * When ped_device_probe_all() is called, libparted attempts to detect all + * devices. It constructs a list which can be accessed with + * ped_device_get_next(). + * + * If you want to use a device that isn't on the list, use + * ped_device_get(). Also, there may be OS-specific constructors, for creating + * devices from file descriptors, stores, etc. For example, + * ped_device_new_from_store(). + * + * @{ + */ + +#include <config.h> + +#include <parted/parted.h> +#include <parted/debug.h> + +#include <limits.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> + +#include "architecture.h" + +static PedDevice* devices; /* legal advice says: initialized to NULL, + under section 6.7.8 part 10 + of ISO/EIC 9899:1999 */ + +static void +_device_register (PedDevice* dev) +{ + PedDevice* walk; + for (walk = devices; walk && walk->next; walk = walk->next); + if (walk) + walk->next = dev; + else + devices = dev; + dev->next = NULL; +} + +static void +_device_unregister (PedDevice* dev) +{ + PedDevice* walk; + PedDevice* last = NULL; + + for (walk = devices; walk != NULL; last = walk, walk = walk->next) { + if (walk == dev) break; + } + + /* This function may be called twice for the same device if a + libparted user explictly removes the device from the cache using + ped_device_cache_remove(), we get called and it then becomes the + user's responsibility to free the PedDevice by calling + ped_device_destroy(). + ped_device_destroy() will then call us a second time, so if the + device is not found in the list do nothing. */ + if (walk == NULL) + return; + + if (last) + last->next = dev->next; + else + devices = dev->next; +} + +/** + * Returns the next device that was detected by ped_device_probe_all(), or + * calls to ped_device_get_next(). + * If dev is NULL, returns the first device. + * + * \return NULL if dev is the last device. + */ +PedDevice* +ped_device_get_next (const PedDevice* dev) +{ + if (dev) + return dev->next; + else + return devices; +} + +void +_ped_device_probe (const char* path) +{ + PedDevice* dev; + + PED_ASSERT (path != NULL); + + ped_exception_fetch_all (); + dev = ped_device_get (path); + if (!dev) + ped_exception_catch (); + ped_exception_leave_all (); +} + +/** + * Attempts to detect all devices. + */ +void +ped_device_probe_all () +{ + ped_architecture->dev_ops->probe_all (); +} + +/** + * Close/free all devices. + * Called by ped_done(), so you do not need to worry about it. + */ +void +ped_device_free_all () +{ + while (devices) + ped_device_destroy (devices); +} + +/** + * Gets the device "name", where name is usually the block device, e.g. + * /dev/sdb. If the device wasn't detected with ped_device_probe_all(), + * an attempt will be made to detect it again. If it is found, it will + * be added to the list. + */ +PedDevice* +ped_device_get (const char* path) +{ + PedDevice* walk; + char* normal_path = NULL; + + PED_ASSERT (path != NULL); + /* Don't canonicalize /dev/mapper or /dev/md/ paths, see + tests/symlink.c + */ + if (strncmp (path, "/dev/mapper/", 12) && + strncmp (path, "/dev/md/", 8)) + normal_path = canonicalize_file_name (path); + if (!normal_path) + /* Well, maybe it is just that the file does not exist. + * Try it anyway. */ + normal_path = strdup (path); + if (!normal_path) + return NULL; + + for (walk = devices; walk != NULL; walk = walk->next) { + if (!strcmp (walk->path, normal_path)) { + free (normal_path); + return walk; + } + } + + walk = ped_architecture->dev_ops->_new (normal_path); + free (normal_path); + if (!walk) + return NULL; + _device_register (walk); + return walk; +} + +/** + * Destroys a device and removes it from the device list, and frees + * all resources associated with the device (all resources allocated + * when the device was created). + */ +void +ped_device_destroy (PedDevice* dev) +{ + _device_unregister (dev); + + while (dev->open_count) { + if (!ped_device_close (dev)) + break; + } + + ped_architecture->dev_ops->destroy (dev); +} + +void +ped_device_cache_remove(PedDevice *dev) +{ + _device_unregister (dev); +} + +int +ped_device_is_busy (PedDevice* dev) +{ + return ped_architecture->dev_ops->is_busy (dev); +} + +/** + * Attempt to open a device to allow use of read, write and sync functions. + * + * The meaning of "open" is architecture-dependent. Apart from requesting + * access to the device from the operating system, it does things like flushing + * caches. + * \note May allocate resources. Any resources allocated here will + * be freed by a final ped_device_close(). (ped_device_open() may be + * called multiple times -- it's a ref-count-like mechanism) + * + * \return zero on failure + */ +int +ped_device_open (PedDevice* dev) +{ + int status; + + PED_ASSERT (dev != NULL); + PED_ASSERT (!dev->external_mode); + + if (dev->open_count) + status = ped_architecture->dev_ops->refresh_open (dev); + else + status = ped_architecture->dev_ops->open (dev); + if (status) + dev->open_count++; + return status; +} + +/** + * Close dev. + * If this is the final close, then resources allocated by + * ped_device_open() are freed. + * + * \return zero on failure + */ +int +ped_device_close (PedDevice* dev) +{ + PED_ASSERT (dev != NULL); + PED_ASSERT (!dev->external_mode); + PED_ASSERT (dev->open_count > 0); + + if (--dev->open_count) + return ped_architecture->dev_ops->refresh_close (dev); + else + return ped_architecture->dev_ops->close (dev); +} + +/** + * Begins external access mode. External access mode allows you to + * safely do IO on the device. If a PedDevice is open, then you should + * not do any IO on that device, e.g. by calling an external program + * like e2fsck, unless you put it in external access mode. You should + * not use any libparted commands that do IO to a device, e.g. + * ped_file_system_{open|resize|copy}, ped_disk_{read|write}), while + * a device is in external access mode. + * Also, you should not ped_device_close() a device, while it is + * in external access mode. + * Note: ped_device_begin_external_access_mode() does things like + * tell the kernel to flush its caches. + * + * Close a device while pretending it is still open. + * This is useful for temporarily suspending libparted access to the device + * in order for an external program to access it. + * (Running external programs while the device is open can cause cache + * coherency problems.) + * + * In particular, this function keeps track of dev->open_count, so that + * reference counting isn't screwed up. + * + * \return zero on failure. + */ +int +ped_device_begin_external_access (PedDevice* dev) +{ + PED_ASSERT (dev != NULL); + PED_ASSERT (!dev->external_mode); + + dev->external_mode = 1; + if (dev->open_count) + return ped_architecture->dev_ops->close (dev); + else + return 1; +} + +/** + * \brief Complementary function to ped_device_begin_external_access. + * + * \note does things like tell the kernel to flush the device's cache. + * + * \return zero on failure. + */ +int +ped_device_end_external_access (PedDevice* dev) +{ + PED_ASSERT (dev != NULL); + PED_ASSERT (dev->external_mode); + + dev->external_mode = 0; + if (dev->open_count) + return ped_architecture->dev_ops->open (dev); + else + return 1; +} + +/** + * \internal Read count sectors from dev into buffer, beginning with sector + * start. + * + * \return zero on failure. + */ +int +ped_device_read (const PedDevice* dev, void* buffer, PedSector start, + PedSector count) +{ + PED_ASSERT (dev != NULL); + PED_ASSERT (buffer != NULL); + PED_ASSERT (!dev->external_mode); + PED_ASSERT (dev->open_count > 0); + + return (ped_architecture->dev_ops->read) (dev, buffer, start, count); +} + +/** + * \internal Write count sectors from buffer to dev, starting at sector + * start. + * + * \return zero on failure. + * + * \sa PedDevice::sector_size + * \sa PedDevice::phys_sector_size + */ +int +ped_device_write (PedDevice* dev, const void* buffer, PedSector start, + PedSector count) +{ + PED_ASSERT (dev != NULL); + PED_ASSERT (buffer != NULL); + PED_ASSERT (!dev->external_mode); + PED_ASSERT (dev->open_count > 0); + + return (ped_architecture->dev_ops->write) (dev, buffer, start, count); +} + +PedSector +ped_device_check (PedDevice* dev, void* buffer, PedSector start, + PedSector count) +{ + PED_ASSERT (dev != NULL); + PED_ASSERT (!dev->external_mode); + PED_ASSERT (dev->open_count > 0); + + return (ped_architecture->dev_ops->check) (dev, buffer, start, count); +} + +/** + * \internal Flushes all write-behind caches that might be holding up + * writes. + * It is slow because it guarantees cache coherency among all relevant caches. + * + * \return zero on failure + */ +int +ped_device_sync (PedDevice* dev) +{ + PED_ASSERT (dev != NULL); + PED_ASSERT (!dev->external_mode); + PED_ASSERT (dev->open_count > 0); + + return ped_architecture->dev_ops->sync (dev); +} + +/** + * \internal Flushes all write-behind caches that might be holding writes. + * \warning Does NOT ensure cache coherency with other caches. + * If you need cache coherency, use ped_device_sync() instead. + * + * \return zero on failure + */ +int +ped_device_sync_fast (PedDevice* dev) +{ + PED_ASSERT (dev != NULL); + PED_ASSERT (!dev->external_mode); + PED_ASSERT (dev->open_count > 0); + + return ped_architecture->dev_ops->sync_fast (dev); +} + +/** + * Get a constraint that represents hardware requirements on geometry. + * This function will return a constraint representing the limits imposed + * by the size of the disk, it will *not* provide any alignment constraints. + * + * Alignment constraints may be desirable when using media that have a physical + * sector size that is a multiple of the logical sector size, as in this case + * proper partition alignment can benefit disk performance signigicantly. + * When you want a constraint with alignment info, use + * ped_device_get_minimal_aligned_constraint() or + * ped_device_get_optimal_aligned_constraint(). + * + * \return NULL on error, otherwise a pointer to a dynamically allocated + * constraint. + */ +PedConstraint* +ped_device_get_constraint (const PedDevice* dev) +{ + PedGeometry *s, *e; + PedConstraint* c = ped_constraint_new ( + ped_alignment_any, ped_alignment_any, + s = ped_geometry_new (dev, 0, dev->length), + e = ped_geometry_new (dev, 0, dev->length), + 1, dev->length); + + free (s); + free (e); + return c; +} + +static PedConstraint* +_ped_device_get_aligned_constraint(const PedDevice *dev, + PedAlignment* start_align) +{ + PedAlignment *end_align = NULL; + PedGeometry *whole_dev_geom = NULL; + PedConstraint *c = NULL; + + if (start_align) { + end_align = ped_alignment_new(start_align->offset - 1, + start_align->grain_size); + if (!end_align) + goto free_start_align; + } + + whole_dev_geom = ped_geometry_new (dev, 0, dev->length); + + if (start_align) + c = ped_constraint_new (start_align, end_align, + whole_dev_geom, whole_dev_geom, + 1, dev->length); + else + c = ped_constraint_new (ped_alignment_any, ped_alignment_any, + whole_dev_geom, whole_dev_geom, + 1, dev->length); + + free (whole_dev_geom); + free (end_align); +free_start_align: + free (start_align); + return c; +} + +/** + * Get a constraint that represents hardware requirements on geometry and + * alignment. + * + * This function will return a constraint representing the limits imposed + * by the size of the disk and the minimal alignment requirements for proper + * performance of the disk. + * + * \return NULL on error, otherwise a pointer to a dynamically allocated + * constraint. + */ +PedConstraint* +ped_device_get_minimal_aligned_constraint(const PedDevice *dev) +{ + return _ped_device_get_aligned_constraint(dev, + ped_device_get_minimum_alignment(dev)); +} + +/** + * Get a constraint that represents hardware requirements on geometry and + * alignment. + * + * This function will return a constraint representing the limits imposed + * by the size of the disk and the alignment requirements for optimal + * performance of the disk. + * + * \return NULL on error, otherwise a pointer to a dynamically allocated + * constraint. + */ +PedConstraint* +ped_device_get_optimal_aligned_constraint(const PedDevice *dev) +{ + return _ped_device_get_aligned_constraint(dev, + ped_device_get_optimum_alignment(dev)); +} + +/** + * Get an alignment that represents minimum hardware requirements on alignment. + * When for example using media that has a physical sector size that is a + * multiple of the logical sector size, it is desirable to have disk accesses + * (and thus partitions) properly aligned. Having partitions not aligned to + * the minimum hardware requirements may lead to a performance penalty. + * + * The returned alignment describes the alignment for the start sector of the + * partition, the end sector should be aligned too, to get the end sector + * alignment decrease the returned alignment's offset by 1. + * + * \return the minimum alignment of partition start sectors, or NULL if this + * information is not available. + */ +PedAlignment* +ped_device_get_minimum_alignment(const PedDevice *dev) +{ + PedAlignment *align = NULL; + + if (ped_architecture->dev_ops->get_minimum_alignment) + align = ped_architecture->dev_ops->get_minimum_alignment(dev); + + if (align == NULL) + align = ped_alignment_new(0, + dev->phys_sector_size / dev->sector_size); + + return align; +} + +/** + * Get an alignment that represents the hardware requirements for optimal + * performance. + * + * The returned alignment describes the alignment for the start sector of the + * partition, the end sector should be aligned too, to get the end sector + * alignment decrease the returned alignment's offset by 1. + * + * \return the optimal alignment of partition start sectors, or NULL if this + * information is not available. + */ +PedAlignment* +ped_device_get_optimum_alignment(const PedDevice *dev) +{ + PedAlignment *align = NULL; + + if (ped_architecture->dev_ops->get_optimum_alignment) + align = ped_architecture->dev_ops->get_optimum_alignment(dev); + + /* If the arch specific code could not give as an alignment + return a default value based on the type of device. */ + if (align == NULL) { + /* Align to a grain of 1MiB (like vista / win7) */ + align = ped_alignment_new(0, + (PED_DEFAULT_ALIGNMENT + / dev->sector_size)); + } + + return align; +} + +/** @} */ |