/* * * (C) Copyright IBM Corp. 2003 * * 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: libgpt.so * * File: move.c */ #include #include #include #include #include #include "gpt.h" #include "helpers.h" #include "move.h" #include "dm.h" #include "commit.h" /* * Function: safe to move * * Called to determine if it would be Ok to move * the sepcified segment object. */ static inline boolean safe_to_move(DISKSEG *seg) { LOGICALDISK *ld=get_logical_disk(seg); DISK_PRIVATE_DATA *disk_pdata=NULL; if ( ld ) { disk_pdata = get_gpt_disk_private_data(ld); if (disk_pdata) { if ( i_can_modify(seg) == TRUE && seg->data_type == DATA_TYPE && (disk_pdata->flags & DISK_HAS_MOVE_PENDING) == 0 ) { return TRUE; } } } return FALSE; } /* * Function: free a copy_job_t * * Called to free a copy_job_t */ static inline void free_copy_job_t(copy_job_t *job) { if (job) { free(job->title); free(job); } } /* * Function: alloc copy_job_t * * Called to allocate a copy_job_t */ static inline copy_job_t * alloc_copy_job_t(void) { copy_job_t *job = calloc( 1, sizeof(copy_job_t) ); return job; } /* * Function: copy callback * * Called by gpt_move_start() to create an engine copy job that * will be executed during a subsequent commit. */ static int create_copy_job( DISKSEG *seg, // data segment to be moved DISKSEG *trg, // move target segment copy_job_t **copyjob ) // callers copy_job struct { int rc=ENOMEM; DISK_PRIVATE_DATA *disk_pdata=NULL; LOGICALDISK *ld=NULL; copy_job_t *job=NULL; char *title=NULL; LOG_ENTRY(); ld = get_logical_disk(seg); disk_pdata = get_gpt_disk_private_data(ld); job = alloc_copy_job_t(); title = malloc(EVMS_NAME_SIZE*2+2); if (job && title) { sprintf(title, "Moving segment %s\n", seg->name); job->title = title; job->description = ""; job->src.obj = ld; job->src.start = seg->start; job->src.len = seg->size; job->trg.obj = ld; job->trg.start = trg->start; job->trg.len = trg->size; *copyjob = job; rc = 0; } if (rc) { if (job) free_copy_job_t(job); if (title) free(title); } LOG_EXIT_INT(rc); return rc; } /* * Function: create move target segment * * Called by our private function gpt_move_segment() * to create a target storage_object_t that will * be used to move the specified disk segment. */ static int create_move_target( DISKSEG *seg, // source data segment DISKSEG *freespace, // target freespace segment DISKSEG **target, // where to return move target storage object boolean testing ) // testing == TRUE if we just want to test { // if a move object could be created and LOGICALDISK *ld=NULL; // dont actually want the object returned DISKSEG *trg=NULL; SEG_PRIVATE_DATA *src_pdata=NULL; SEG_PRIVATE_DATA *trg_pdata=NULL; DISK_PRIVATE_DATA *disk_pdata=NULL; lba_t start=0; lba_t end=0; int rc = EINVAL; LOG_ENTRY(); if (seg) { ld = get_logical_disk(seg); disk_pdata = get_gpt_disk_private_data(ld); src_pdata = (SEG_PRIVATE_DATA *) seg->private_data; } if (ld && disk_pdata) { rc = ENOMEM; trg = allocate_gpt_disk_segment( ld ); if (trg) { sprintf(trg->name, "%s_move_target", seg->name ); trg->data_type = DATA_TYPE; trg->flags &= ~SOFLAG_DIRTY; trg_pdata = (SEG_PRIVATE_DATA *) trg->private_data; rc = 0; } } if (rc==0) { rc = EFBIG; // calculate the starting lba if (starts_on_cylinder_boundary(ld,seg->start)==TRUE) { if (starts_on_cylinder_boundary(ld,freespace->start)==TRUE) { start = freespace->start; } else { start = roundup_to_cylinder_boundary(ld,freespace->start)+1; LOG_DEBUG("freespace doesn't start on cyl bdy ... rounding up\n"); } } else { sector_count_t sector_offset; lba_t cylinder_start; if (starts_on_cylinder_boundary(ld,freespace->start)==TRUE) { cylinder_start = rounddown_to_cylinder_boundary(ld,seg->start); sector_offset = seg->start - cylinder_start; start = freespace->start + sector_offset; } else { start = freespace->start; } } trg->start = start; // now calculate the ending LBA ... always must end on a cyl boundary end = trg->start + seg->size - 1; if (ends_on_cylinder_boundary(ld,end)==FALSE) { end = roundup_to_cylinder_boundary(ld,end); } if (end <= freespace->start+freespace->size-1 ) { trg->size = end - trg->start + 1; *target=trg; rc = 0; } } if (rc || testing==TRUE) { if (trg) free_gpt_disk_segment(trg); } LOG_EXIT_INT(rc); return rc; } /* Function: validate move target * * Called to validate that the specified segment * can be moved to the target freespace segment. */ int gpt_validate_move_target( DISKSEG *src, DISKSEG *trg ) { LOGICALDISK *ld=NULL; DISKSEG *tseg; SEG_PRIVATE_DATA *src_pdata=NULL; SEG_PRIVATE_DATA *trg_pdata=NULL; DISK_PRIVATE_DATA *disk_pdata=NULL; int rc=EINVAL; LOG_ENTRY(); if (src && trg) { if ( src->data_type == DATA_TYPE && trg->data_type == FREE_SPACE_TYPE ) { ld = get_logical_disk(src); disk_pdata = get_gpt_disk_private_data(ld); src_pdata = (SEG_PRIVATE_DATA *) src->private_data; trg_pdata = (SEG_PRIVATE_DATA *) trg->private_data; if (ld && disk_pdata) { rc = 0; } } if (rc==0) { rc = create_move_target( src, trg, &tseg, TRUE); } } LOG_EXIT_INT(rc); return rc; } /* * Function: can move segment * * Check if the specified segment object can be moved * elsewhere on the disk. Do so by ... checking each * freespace segment we find ... stopping at the 1st * freespace segment that could be used for the move. */ int gpt_can_move_segment( DISKSEG *seg ) { DISKSEG *freespace=NULL; LOGICALDISK *ld=NULL; int rc=EINVAL; list_element_t iter; LOG_ENTRY(); if ( safe_to_move(seg) == TRUE ) { ld = get_logical_disk(seg); if (ld) { LIST_FOR_EACH( ld->parent_objects, iter, freespace ) { if ( (freespace->data_type == FREE_SPACE_TYPE) && (freespace->size >= seg->size) ) { rc = gpt_validate_move_target(seg,freespace); if (rc == 0) { break; } } } } } LOG_EXIT_INT(rc); return rc; } /* * Function: gpt update segment info * * Called when finishing a move to update the partition * table metadata info. This means we need to update * the source segment info, i.e. where it is now located * on the disk. */ static int gpt_update_segment_info( DISKSEG *src, DISKSEG *trg) { int rc=EINVAL; LOGICALDISK *ld=NULL; LOG_ENTRY(); if (src && trg) { ld = get_logical_disk(src); if (ld) { // remove segments from the disks segment list cuz ... // doing so will unregister the names and create freespace // areas on the disk. remove_gpt_segment_from_list( ld->parent_objects, src ); remove_gpt_segment_from_list( ld->parent_objects, trg ); // update source segment information src->start = trg->start; src->size = trg->size; // reinsert the source segment insert_gpt_segment_into_list( ld->parent_objects, src ); rc = 0; } } LOG_EXIT_INT(rc); return rc; } /* Function: GPT move segment * * Called as a seg mgr private function to move a data segment * to a freespace area on the same disk. */ int gpt_move_segment( DISKSEG *src, DISKSEG *freespace ) { int rc=EINVAL; LOGICALDISK *ld=NULL; DISKSEG *trg=NULL; SEG_PRIVATE_DATA *trg_pdata=NULL; SEG_PRIVATE_DATA *src_pdata=NULL; DISK_PRIVATE_DATA *disk_pdata=NULL; copy_job_t *job=NULL; LOG_ENTRY(); if ( safe_to_move(src) == TRUE ) { ld = get_logical_disk(src); disk_pdata = get_gpt_disk_private_data(ld); src_pdata = (SEG_PRIVATE_DATA *) src->private_data; // create a move target segment rc = create_move_target(src, freespace, &trg, FALSE); if (rc==0) trg_pdata=(SEG_PRIVATE_DATA *)trg->private_data; // create the engine copy job if (rc==0) { rc = create_copy_job( src, trg, &job ); if (rc) { free_gpt_disk_segment( trg ); find_freespace_on_gpt_disk(ld); } } // remove the freespace segment from the disk segment // list and insert the new move-target data segment. if (rc==0) { remove_gpt_segment_from_list( ld->parent_objects, freespace ); free_gpt_disk_segment( freespace ); insert_gpt_segment_into_ordered_list( ld->parent_objects, trg ); disk_pdata->flags |= DISK_HAS_MOVE_PENDING; disk_pdata->copy_job = job; src_pdata->move_target = trg; src->flags |= SOFLAG_DIRTY; find_freespace_on_gpt_disk(ld); } } LOG_EXIT_INT(rc); return rc; } /* Function: do move segment finish * * Called to update partition tables after moving a segment. */ static int do_move_segment_finish( DISKSEG *src, DISKSEG *trg, int copy_rc, boolean offline ) { int rc=EINVAL; LOGICALDISK *ld=NULL; SEG_PRIVATE_DATA *src_pdata=NULL; SEG_PRIVATE_DATA *trg_pdata=NULL; DISK_PRIVATE_DATA *disk_pdata=NULL; DISKSEG saved_seg; LOG_ENTRY(); if (src && trg) { ld = get_logical_disk(src); disk_pdata = get_gpt_disk_private_data(ld); src_pdata = (SEG_PRIVATE_DATA *)src->private_data; trg_pdata = (SEG_PRIVATE_DATA *)trg->private_data; if (copy_rc==0) { memcpy(&saved_seg, src, sizeof(DISKSEG)); rc = gpt_update_segment_info(src,trg); if (rc==0) { rc = commit_guid_partition_tables( ld, trg, 1, FALSE ); if (rc==0) { rc = commit_guid_partition_tables( ld, trg, 2, FALSE ); } } if (rc) { memcpy(src, &saved_seg, sizeof(DISKSEG)); commit_guid_partition_tables( ld, src, 1, FALSE ); commit_guid_partition_tables( ld, src, 2, FALSE ); } } else { rc = copy_rc; } src_pdata->move_target = NULL; free_gpt_disk_segment( trg ); find_freespace_on_gpt_disk(ld); src->flags |= SOFLAG_NEEDS_ACTIVATE; GPT_activate( src ); } LOG_EXIT_INT(rc); return rc; } static int do_online_copy(storage_object_t *src, storage_object_t *tgt, copy_job_t *job) { int rc = 0; dm_target_t *target=NULL; LOG_ENTRY(); rc = EngFncs->copy_setup(job); if (rc != 0) { LOG_SERIOUS("Error code %d when setting up a copy job: %s\n", rc, EngFncs->strerror(rc)); LOG_EXIT_INT(rc); return rc; } /* * Remap the source segment object to point to the mirror rather than the * source object. */ rc = EngFncs->dm_get_targets(src, &target); if (rc == 0) { target->data.linear->major = job->mirror->dev_major; target->data.linear->minor = job->mirror->dev_minor; target->data.linear->start = 0; rc = EngFncs->dm_load_targets(src, target); EngFncs->dm_deallocate_targets(target); if (rc == 0) { EngFncs->dm_set_suspended_flag(TRUE); rc = EngFncs->dm_suspend(src, TRUE); if (rc == 0) { rc = EngFncs->copy_start(job); if (rc) { EngFncs->dm_clear_targets(src); } EngFncs->dm_suspend(src, FALSE); } else { LOG_SERIOUS("Error code %d when resuming object %s: %s\n", rc, src->name, EngFncs->strerror(rc)); } EngFncs->dm_set_suspended_flag(FALSE); } else { LOG_SERIOUS("Error code %d when loading the new mirror target for the object %s: %s\n", rc, src->name, EngFncs->strerror(rc)); } } else { LOG_SERIOUS("Error code %d when getting the target for the object %s: %s\n", rc, src->name, EngFncs->strerror(rc)); } if (rc == 0) { rc = EngFncs->copy_wait(job); } rc = do_move_segment_finish( src, tgt, rc, FALSE); EngFncs->copy_cleanup(job); LOG_EXIT_INT(rc); return rc; } static int do_offline_copy( storage_object_t *src, storage_object_t *tgt, copy_job_t *job ) { int rc = 0; LOG_ENTRY(); rc = EngFncs->offline_copy( job); rc = do_move_segment_finish( src, tgt, rc, TRUE); LOG_EXIT_INT(rc); return rc; } /* * Function: gpt move commit * * Called during MOVE commit phase to commit a segment * move to disk. This is done by calling the engine copy * service and afterwards calling our own move finish * routine that takes care of committing metadata changes * or backing out changes. */ int gpt_move_segment_commit( DISKSEG *src, DISKSEG *tgt, copy_job_t *job ) { int rc=0; char * choices[] = {_("Yes"), _("No"), NULL}; int answer = 0; logical_volume_t *vol=NULL; LOG_ENTRY(); if (EngFncs->can_online_copy()) { rc = do_online_copy(src, tgt, job); } else { if (EngFncs->is_offline(src,&vol)==FALSE) { LOG_DEBUG("Segment %s appears to be part of mounted volume %s\n", src->name, vol->name); QUESTION( &answer, choices, "Segment %s appears to be part of a mounted volume. The volume is %s.\n\n" "Offline move can safely be used only with unmounted volumes. The volume may " "become unuseable if you continue with the move.\n\n" "Question: Would you like to continue and move segment %s?\n", src->name, vol->name, src->name); if (answer != 0) { rc=EPERM; LOG_DEBUG("User is cancelling move\n"); } } if (rc==0) { rc = do_offline_copy(src, tgt, job); } } src->flags &= ~SOFLAG_DIRTY; // always exit with dirty flag cleared LOG_EXIT_INT(rc); return rc; }