~ubuntu-branches/ubuntu/gutsy/evms/gutsy

« back to all changes in this revision

Viewing changes to plugins/gpt/move.c

  • Committer: Bazaar Package Importer
  • Author(s): Steinar H. Gunderson
  • Date: 2006-09-14 19:32:30 UTC
  • mfrom: (2.1.13 edgy)
  • Revision ID: james.westby@ubuntu.com-20060914193230-4b1pmy0coqk81sqa
Tags: 2.5.5-18
* Apply patches from upstream:
  * cli_query_segfault.patch, fixes a segfault in the CLI when doing a
    query.
  * cli_reload_options.patch, reloads the right option descriptors after
    a change.
  * ntfs_unmkfs.patch, fixes a bug in the wiping of NTFS file systems.
  * raid5_remove_spare_fix.patch + raid5_remove_spare_fix_2.patch, lets the
    user remove a spare if resync does not run.
  * raid5_algorithm.patch, makes EVMS heed the parity algorithm the user
    selects when creating a RAID-5 array.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *
 
3
 *   (C) Copyright IBM Corp. 2003
 
4
 *
 
5
 *   This program is free software;  you can redistribute it and/or modify
 
6
 *   it under the terms of the GNU General Public License as published by
 
7
 *   the Free Software Foundation; either version 2 of the License, or
 
8
 *   (at your option) any later version.
 
9
 *
 
10
 *   This program is distributed in the hope that it will be useful,
 
11
 *   but WITHOUT ANY WARRANTY;  without even the implied warranty of
 
12
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 
13
 *   the GNU General Public License for more details.
 
14
 *
 
15
 *   You should have received a copy of the GNU General Public License
 
16
 *   along with this program;  if not, write to the Free Software
 
17
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 
18
 *
 
19
 *   Module: libgpt.so
 
20
 *
 
21
 *   File: move.c
 
22
 */
 
23
#include <stdlib.h>
 
24
#include <stdio.h>
 
25
#include <string.h>
 
26
#include <errno.h>
 
27
 
 
28
#include <plugin.h>
 
29
 
 
30
#include "gpt.h"
 
31
#include "helpers.h"
 
32
#include "move.h"
 
33
#include "dm.h"
 
34
#include "commit.h"
 
35
 
 
36
 
 
37
/*
 
38
 *  Function: safe to move
 
39
 *
 
40
 *  Called to determine if it would be Ok to move
 
41
 *  the sepcified segment object.
 
42
 */
 
43
static inline boolean safe_to_move(DISKSEG *seg)
 
44
{
 
45
        LOGICALDISK        *ld=get_logical_disk(seg);
 
46
        DISK_PRIVATE_DATA  *disk_pdata=NULL;
 
47
 
 
48
        if ( ld ) {
 
49
 
 
50
                disk_pdata = get_gpt_disk_private_data(ld);
 
51
                if (disk_pdata) {
 
52
 
 
53
                        if (  i_can_modify(seg)  ==  TRUE &&
 
54
                              seg->data_type         ==  DATA_TYPE &&
 
55
                              (disk_pdata->flags & DISK_HAS_MOVE_PENDING) == 0 ) {
 
56
 
 
57
                                return TRUE;
 
58
 
 
59
                        }
 
60
                }
 
61
 
 
62
        }
 
63
 
 
64
        return FALSE;
 
65
}
 
66
 
 
67
 
 
68
/*
 
69
 *  Function: free a copy_job_t
 
70
 *
 
71
 *  Called to free a copy_job_t
 
72
 */
 
73
static inline void free_copy_job_t(copy_job_t *job)
 
74
{
 
75
        if (job) {
 
76
                free(job->title);
 
77
                free(job);
 
78
        }
 
79
}
 
80
 
 
81
 
 
82
/*
 
83
 *  Function: alloc copy_job_t
 
84
 *
 
85
 *  Called to allocate a copy_job_t
 
86
 */
 
87
static inline copy_job_t * alloc_copy_job_t(void)
 
88
{
 
89
        copy_job_t *job = calloc( 1, sizeof(copy_job_t) );
 
90
        return job;
 
91
}
 
92
 
 
93
 
 
94
/*
 
95
 *  Function:  copy callback
 
96
 *
 
97
 *  Called by gpt_move_start() to create an engine copy job that
 
98
 *  will be executed during a subsequent commit.
 
99
 */
 
100
static int create_copy_job( DISKSEG      *seg,       // data segment to be moved
 
101
                            DISKSEG      *trg,       // move target segment
 
102
                            copy_job_t  **copyjob )  // callers copy_job struct
 
103
{
 
104
        int rc=ENOMEM;
 
105
        DISK_PRIVATE_DATA   *disk_pdata=NULL;
 
106
        LOGICALDISK         *ld=NULL;
 
107
        copy_job_t          *job=NULL;
 
108
        char                *title=NULL;
 
109
 
 
110
 
 
111
        LOG_ENTRY();
 
112
 
 
113
        ld           = get_logical_disk(seg);
 
114
        disk_pdata   = get_gpt_disk_private_data(ld);
 
115
        job          = alloc_copy_job_t();
 
116
        title        = malloc(EVMS_NAME_SIZE*2+2);
 
117
 
 
118
        if (job && title) {
 
119
 
 
120
                sprintf(title, "Moving segment %s\n", seg->name);
 
121
                job->title       = title;
 
122
                job->description = "";
 
123
 
 
124
                job->src.obj     = ld;
 
125
                job->src.start   = seg->start;
 
126
                job->src.len     = seg->size;
 
127
 
 
128
                job->trg.obj     = ld;
 
129
                job->trg.start   = trg->start;
 
130
                job->trg.len     = trg->size;
 
131
 
 
132
                *copyjob         = job;
 
133
 
 
134
                rc               = 0;
 
135
        }
 
136
 
 
137
        if (rc) {
 
138
                if (job) free_copy_job_t(job);
 
139
                if (title) free(title);
 
140
        }
 
141
 
 
142
 
 
143
        LOG_EXIT_INT(rc);
 
144
        return rc;
 
145
}
 
146
 
 
147
 
 
148
/*
 
149
 *  Function:  create move target segment
 
150
 *
 
151
 *  Called by our private function gpt_move_segment()
 
152
 *  to create a target storage_object_t that will
 
153
 *  be used to move the specified disk segment.
 
154
 */
 
155
static int create_move_target( DISKSEG  *seg,        // source data segment
 
156
                               DISKSEG  *freespace,  // target freespace segment
 
157
                               DISKSEG **target,     // where to return move target storage object
 
158
                               boolean   testing )   // testing == TRUE if we just want to test
 
159
{                                                    // if a move object could be created and
 
160
        LOGICALDISK             *ld=NULL;            // dont actually want the object returned
 
161
        DISKSEG                 *trg=NULL;
 
162
        SEG_PRIVATE_DATA        *src_pdata=NULL;
 
163
        SEG_PRIVATE_DATA        *trg_pdata=NULL;
 
164
        DISK_PRIVATE_DATA       *disk_pdata=NULL;
 
165
        lba_t                   start=0;
 
166
        lba_t                   end=0;
 
167
        int                     rc = EINVAL;
 
168
 
 
169
 
 
170
        LOG_ENTRY();
 
171
 
 
172
        if (seg) {
 
173
                ld          = get_logical_disk(seg);
 
174
                disk_pdata  = get_gpt_disk_private_data(ld);
 
175
                src_pdata   = (SEG_PRIVATE_DATA *) seg->private_data;
 
176
        }
 
177
 
 
178
        if (ld && disk_pdata) {
 
179
 
 
180
                rc = ENOMEM;
 
181
 
 
182
                trg = allocate_gpt_disk_segment( ld );
 
183
                if (trg) {
 
184
 
 
185
                        sprintf(trg->name, "%s_move_target", seg->name );
 
186
 
 
187
                        trg->data_type = DATA_TYPE;
 
188
                        trg->flags    &= ~SOFLAG_DIRTY;
 
189
                        trg_pdata      = (SEG_PRIVATE_DATA *) trg->private_data;
 
190
 
 
191
                        rc = 0;
 
192
                }
 
193
 
 
194
        }
 
195
 
 
196
        if (rc==0) {
 
197
 
 
198
                rc = EFBIG;
 
199
 
 
200
                // calculate the starting lba
 
201
                if (starts_on_cylinder_boundary(ld,seg->start)==TRUE) {
 
202
                        if (starts_on_cylinder_boundary(ld,freespace->start)==TRUE) {
 
203
                                start = freespace->start;
 
204
                        }
 
205
                        else {
 
206
                                start = roundup_to_cylinder_boundary(ld,freespace->start)+1;
 
207
                                LOG_DEBUG("freespace doesn't start on cyl bdy ... rounding up\n");
 
208
                        }
 
209
                }
 
210
                else {
 
211
                        sector_count_t sector_offset;
 
212
                        lba_t          cylinder_start;
 
213
 
 
214
                        if (starts_on_cylinder_boundary(ld,freespace->start)==TRUE) {
 
215
                                cylinder_start = rounddown_to_cylinder_boundary(ld,seg->start);
 
216
                                sector_offset  = seg->start - cylinder_start;
 
217
                                start = freespace->start + sector_offset;
 
218
                        }
 
219
                        else {
 
220
                                start = freespace->start;
 
221
                        }
 
222
                }
 
223
 
 
224
                trg->start = start;
 
225
 
 
226
                // now calculate the ending LBA ... always must end on a cyl boundary
 
227
                end = trg->start + seg->size - 1;
 
228
                if (ends_on_cylinder_boundary(ld,end)==FALSE) {
 
229
                        end = roundup_to_cylinder_boundary(ld,end);
 
230
                }
 
231
 
 
232
                if (end <= freespace->start+freespace->size-1 ) {
 
233
                        trg->size = end - trg->start + 1;
 
234
                        *target=trg;
 
235
                        rc = 0;
 
236
                }
 
237
 
 
238
        }
 
239
 
 
240
        if (rc || testing==TRUE) {
 
241
                if (trg) free_gpt_disk_segment(trg);
 
242
        }
 
243
 
 
244
        LOG_EXIT_INT(rc);
 
245
        return rc;
 
246
}
 
247
 
 
248
 
 
249
/*  Function:  validate move target
 
250
 *
 
251
 *  Called to validate that the specified segment
 
252
 *  can be moved to the target freespace segment.
 
253
 */
 
254
int gpt_validate_move_target( DISKSEG *src, DISKSEG *trg )
 
255
{
 
256
        LOGICALDISK             *ld=NULL;
 
257
        DISKSEG                 *tseg;
 
258
        SEG_PRIVATE_DATA        *src_pdata=NULL;
 
259
        SEG_PRIVATE_DATA        *trg_pdata=NULL;
 
260
        DISK_PRIVATE_DATA       *disk_pdata=NULL;
 
261
        int rc=EINVAL;
 
262
 
 
263
        LOG_ENTRY();
 
264
 
 
265
        if (src && trg) {
 
266
 
 
267
                if ( src->data_type == DATA_TYPE &&
 
268
                     trg->data_type == FREE_SPACE_TYPE ) {
 
269
 
 
270
                        ld          = get_logical_disk(src);
 
271
                        disk_pdata  = get_gpt_disk_private_data(ld);
 
272
                        src_pdata   = (SEG_PRIVATE_DATA *) src->private_data;
 
273
                        trg_pdata   = (SEG_PRIVATE_DATA *) trg->private_data;
 
274
 
 
275
                        if (ld && disk_pdata) {
 
276
                                rc = 0;
 
277
                        }
 
278
                }
 
279
 
 
280
                if (rc==0) {
 
281
                        rc = create_move_target( src, trg, &tseg, TRUE);
 
282
                }
 
283
 
 
284
        }
 
285
 
 
286
        LOG_EXIT_INT(rc);
 
287
        return rc;
 
288
}
 
289
 
 
290
 
 
291
/*
 
292
 *  Function:  can move segment
 
293
 *
 
294
 *  Check if the specified segment object can be moved
 
295
 *  elsewhere on the disk. Do so by ... checking each
 
296
 *  freespace segment we find ... stopping at the 1st
 
297
 *  freespace segment that could be used for the move.
 
298
 */
 
299
int gpt_can_move_segment( DISKSEG *seg )
 
300
{
 
301
        DISKSEG       *freespace=NULL;
 
302
        LOGICALDISK   *ld=NULL;
 
303
        int rc=EINVAL;
 
304
        list_element_t iter;
 
305
 
 
306
        LOG_ENTRY();
 
307
 
 
308
        if ( safe_to_move(seg) == TRUE ) {
 
309
 
 
310
                ld = get_logical_disk(seg);
 
311
                if (ld) {
 
312
 
 
313
                        LIST_FOR_EACH( ld->parent_objects, iter, freespace ) {
 
314
 
 
315
                                if ( (freespace->data_type == FREE_SPACE_TYPE) &&
 
316
                                     (freespace->size      >= seg->size) ) {
 
317
 
 
318
                                        rc = gpt_validate_move_target(seg,freespace);
 
319
                                        if (rc == 0) {
 
320
                                                break;
 
321
                                        }
 
322
 
 
323
                                }
 
324
                        }
 
325
 
 
326
                }
 
327
 
 
328
        }
 
329
 
 
330
        LOG_EXIT_INT(rc);
 
331
        return rc;
 
332
}
 
333
 
 
334
 
 
335
/*
 
336
 *  Function:  gpt update segment info
 
337
 *
 
338
 *  Called when finishing a move to update the partition
 
339
 *  table metadata info. This means we need to update
 
340
 *  the source segment info, i.e. where it is now located
 
341
 *  on the disk.
 
342
 */
 
343
static int gpt_update_segment_info( DISKSEG *src, DISKSEG *trg)
 
344
{
 
345
        int rc=EINVAL;
 
346
        LOGICALDISK *ld=NULL;
 
347
 
 
348
        LOG_ENTRY();
 
349
 
 
350
        if (src && trg) {
 
351
 
 
352
                ld = get_logical_disk(src);
 
353
                if (ld) {
 
354
                        // remove segments from the disks segment list cuz ...
 
355
                        // doing so will unregister the names and create freespace
 
356
                        // areas on the disk.
 
357
                        remove_gpt_segment_from_list( ld->parent_objects, src );
 
358
                        remove_gpt_segment_from_list( ld->parent_objects, trg );
 
359
 
 
360
                        // update source segment information
 
361
                        src->start = trg->start;
 
362
                        src->size  = trg->size;
 
363
 
 
364
                        // reinsert the source segment
 
365
                        insert_gpt_segment_into_list( ld->parent_objects, src );
 
366
 
 
367
                        rc = 0;
 
368
                }
 
369
 
 
370
        }
 
371
 
 
372
        LOG_EXIT_INT(rc);
 
373
        return rc;
 
374
}
 
375
 
 
376
 
 
377
/*  Function:  GPT move segment
 
378
 *
 
379
 *  Called as a seg mgr private function to move a data segment
 
380
 *  to a freespace area on the same disk.
 
381
 */
 
382
int gpt_move_segment( DISKSEG *src, DISKSEG *freespace )
 
383
{
 
384
        int rc=EINVAL;
 
385
        LOGICALDISK        *ld=NULL;
 
386
        DISKSEG            *trg=NULL;
 
387
        SEG_PRIVATE_DATA   *trg_pdata=NULL;
 
388
        SEG_PRIVATE_DATA   *src_pdata=NULL;
 
389
        DISK_PRIVATE_DATA  *disk_pdata=NULL;
 
390
        copy_job_t         *job=NULL;
 
391
 
 
392
        LOG_ENTRY();
 
393
 
 
394
        if ( safe_to_move(src) == TRUE ) {
 
395
 
 
396
                ld         = get_logical_disk(src);
 
397
                disk_pdata = get_gpt_disk_private_data(ld);
 
398
                src_pdata  = (SEG_PRIVATE_DATA *) src->private_data;
 
399
 
 
400
                // create a move target segment
 
401
                rc = create_move_target(src, freespace, &trg, FALSE);
 
402
                if (rc==0)  trg_pdata=(SEG_PRIVATE_DATA *)trg->private_data;
 
403
 
 
404
                // create the engine copy job
 
405
                if (rc==0) {
 
406
                        rc = create_copy_job( src, trg, &job );
 
407
                        if (rc) {
 
408
                                free_gpt_disk_segment( trg );
 
409
                                find_freespace_on_gpt_disk(ld);
 
410
                        }
 
411
                }
 
412
 
 
413
                // remove the freespace segment from the disk segment
 
414
                // list and insert the new move-target data segment.
 
415
                if (rc==0) {
 
416
 
 
417
                        remove_gpt_segment_from_list( ld->parent_objects, freespace );
 
418
                        free_gpt_disk_segment( freespace );
 
419
 
 
420
                        insert_gpt_segment_into_ordered_list( ld->parent_objects, trg );
 
421
 
 
422
                        disk_pdata->flags     |= DISK_HAS_MOVE_PENDING;
 
423
                        disk_pdata->copy_job   = job;
 
424
                        src_pdata->move_target = trg;
 
425
                        src->flags |= SOFLAG_DIRTY;
 
426
                        find_freespace_on_gpt_disk(ld);
 
427
                }
 
428
 
 
429
        }
 
430
 
 
431
        LOG_EXIT_INT(rc);
 
432
        return rc;
 
433
}
 
434
 
 
435
 
 
436
/*  Function:  do move segment finish
 
437
 *
 
438
 *  Called to update partition tables after moving a segment.
 
439
 */
 
440
static int do_move_segment_finish( DISKSEG      *src,
 
441
                                   DISKSEG      *trg,
 
442
                                   int           copy_rc,
 
443
                                   boolean       offline )
 
444
{
 
445
        int rc=EINVAL;
 
446
        LOGICALDISK        *ld=NULL;
 
447
        SEG_PRIVATE_DATA   *src_pdata=NULL;
 
448
        SEG_PRIVATE_DATA   *trg_pdata=NULL;
 
449
        DISK_PRIVATE_DATA  *disk_pdata=NULL;
 
450
        DISKSEG             saved_seg;
 
451
 
 
452
        LOG_ENTRY();
 
453
 
 
454
        if (src && trg) {
 
455
 
 
456
                ld         = get_logical_disk(src);
 
457
                disk_pdata = get_gpt_disk_private_data(ld);
 
458
                src_pdata  = (SEG_PRIVATE_DATA *)src->private_data;
 
459
                trg_pdata  = (SEG_PRIVATE_DATA *)trg->private_data;
 
460
 
 
461
                if (copy_rc==0) {
 
462
                        memcpy(&saved_seg, src, sizeof(DISKSEG));
 
463
                        rc = gpt_update_segment_info(src,trg);
 
464
                        if (rc==0) {
 
465
                                rc = commit_guid_partition_tables( ld, trg, 1, FALSE );
 
466
                                if (rc==0) {
 
467
                                        rc = commit_guid_partition_tables( ld, trg, 2, FALSE );
 
468
                                }
 
469
                        }
 
470
                        if (rc) {
 
471
                                memcpy(src, &saved_seg, sizeof(DISKSEG));
 
472
                                commit_guid_partition_tables( ld, src, 1, FALSE );
 
473
                                commit_guid_partition_tables( ld, src, 2, FALSE );
 
474
                        }
 
475
                }
 
476
                else {
 
477
                        rc = copy_rc;
 
478
                }
 
479
 
 
480
                src_pdata->move_target = NULL;
 
481
                free_gpt_disk_segment( trg );
 
482
                find_freespace_on_gpt_disk(ld);
 
483
                src->flags |= SOFLAG_NEEDS_ACTIVATE;
 
484
                GPT_activate( src );
 
485
        }
 
486
 
 
487
        LOG_EXIT_INT(rc);
 
488
        return rc;
 
489
}
 
490
 
 
491
 
 
492
static int do_online_copy(storage_object_t *src, storage_object_t *tgt, copy_job_t *job)
 
493
{
 
494
        int rc = 0;
 
495
        dm_target_t *target=NULL;
 
496
 
 
497
        LOG_ENTRY();
 
498
 
 
499
        rc = EngFncs->copy_setup(job);
 
500
        if (rc != 0) {
 
501
                LOG_SERIOUS("Error code %d when setting up a copy job: %s\n", rc, EngFncs->strerror(rc));
 
502
                LOG_EXIT_INT(rc);
 
503
                return rc;
 
504
        }
 
505
 
 
506
        /*
 
507
         * Remap the source segment object to point to the mirror rather than the
 
508
         * source object.
 
509
         */
 
510
        rc = EngFncs->dm_get_targets(src, &target);
 
511
 
 
512
        if (rc == 0) {
 
513
                target->data.linear->major = job->mirror->dev_major;
 
514
                target->data.linear->minor = job->mirror->dev_minor;
 
515
                target->data.linear->start = 0;
 
516
 
 
517
                rc = EngFncs->dm_load_targets(src, target);
 
518
 
 
519
                EngFncs->dm_deallocate_targets(target);
 
520
 
 
521
                if (rc == 0) {
 
522
                        EngFncs->dm_set_suspended_flag(TRUE);
 
523
 
 
524
                        rc = EngFncs->dm_suspend(src, TRUE);
 
525
                        if (rc == 0) {
 
526
                                rc = EngFncs->copy_start(job);
 
527
                                if (rc) {
 
528
                                        EngFncs->dm_clear_targets(src);
 
529
                                }
 
530
                                EngFncs->dm_suspend(src, FALSE);
 
531
                        }
 
532
                        else {
 
533
                                LOG_SERIOUS("Error code %d when resuming object %s: %s\n",
 
534
                                            rc, src->name, EngFncs->strerror(rc));
 
535
                        }
 
536
 
 
537
                        EngFncs->dm_set_suspended_flag(FALSE);
 
538
                }
 
539
                else {
 
540
                        LOG_SERIOUS("Error code %d when loading the new mirror target for the object %s: %s\n",
 
541
                                    rc, src->name, EngFncs->strerror(rc));
 
542
                }
 
543
 
 
544
        }
 
545
        else {
 
546
                LOG_SERIOUS("Error code %d when getting the target for the object %s: %s\n",
 
547
                            rc, src->name, EngFncs->strerror(rc));
 
548
        }
 
549
 
 
550
        if (rc == 0) {
 
551
                rc = EngFncs->copy_wait(job);
 
552
        }
 
553
 
 
554
        rc = do_move_segment_finish( src, tgt, rc, FALSE);
 
555
 
 
556
        EngFncs->copy_cleanup(job);
 
557
 
 
558
        LOG_EXIT_INT(rc);
 
559
        return rc;
 
560
}
 
561
 
 
562
static int do_offline_copy( storage_object_t *src, storage_object_t *tgt, copy_job_t *job )
 
563
{
 
564
        int rc = 0;
 
565
 
 
566
        LOG_ENTRY();
 
567
 
 
568
        rc = EngFncs->offline_copy( job);
 
569
 
 
570
        rc = do_move_segment_finish( src, tgt, rc, TRUE);
 
571
 
 
572
        LOG_EXIT_INT(rc);
 
573
        return rc;
 
574
}
 
575
 
 
576
 
 
577
/*
 
578
 *  Function: gpt move commit
 
579
 *
 
580
 *  Called during MOVE commit phase to commit a segment
 
581
 *  move to disk. This is done by calling the engine copy
 
582
 *  service and afterwards calling our own move finish
 
583
 *  routine that takes care of committing metadata changes
 
584
 *  or backing out changes.
 
585
 */
 
586
int gpt_move_segment_commit( DISKSEG *src, DISKSEG *tgt, copy_job_t *job )
 
587
{
 
588
        int rc=0;
 
589
        char * choices[] = {_("Yes"), _("No"), NULL};
 
590
        int answer = 0;
 
591
        logical_volume_t *vol=NULL;
 
592
 
 
593
        LOG_ENTRY();
 
594
 
 
595
        if (EngFncs->can_online_copy()) {
 
596
                rc = do_online_copy(src, tgt, job);
 
597
        }
 
598
        else {
 
599
                if (EngFncs->is_offline(src,&vol)==FALSE) {
 
600
                        LOG_DEBUG("Segment %s appears to be part of mounted volume %s\n", src->name, vol->name);
 
601
                        QUESTION( &answer, choices,
 
602
                                  "Segment %s appears to be part of a mounted volume.  The volume is %s.\n\n"
 
603
                                  "Offline move can safely be used only with unmounted volumes.  The volume may "
 
604
                                  "become unuseable if you continue with the move.\n\n"
 
605
                                  "Question: Would you like to continue and move segment %s?\n",
 
606
                                  src->name, vol->name, src->name);
 
607
 
 
608
                        if (answer != 0) {
 
609
                                rc=EPERM;
 
610
                                LOG_DEBUG("User is cancelling move\n");
 
611
                        }
 
612
                }
 
613
                if (rc==0) {
 
614
                        rc = do_offline_copy(src, tgt, job);
 
615
                }
 
616
        }
 
617
 
 
618
        src->flags &= ~SOFLAG_DIRTY;  // always exit with dirty flag cleared
 
619
 
 
620
        LOG_EXIT_INT(rc);
 
621
        return rc;
 
622
}