602
+ − 1
#!perl
+ − 2
# Copyright (c) 2001-2009 Nokia Corporation and/or its subsidiary(-ies).
+ − 3
# All rights reserved.
+ − 4
# This component and the accompanying materials are made available
+ − 5
# under the terms of the License "Eclipse Public License v1.0"
+ − 6
# which accompanies this distribution, and is available
+ − 7
# at the URL "http://www.eclipse.org/legal/epl-v10.html".
+ − 8
#
+ − 9
# Initial Contributors:
+ − 10
# Nokia Corporation - initial contribution.
+ − 11
#
+ − 12
# Contributors:
+ − 13
#
+ − 14
# Description:
+ − 15
#
+ − 16
#
+ − 17
+ − 18
use strict;
+ − 19
use FindBin;
+ − 20
use lib "$FindBin::Bin";
+ − 21
use Getopt::Long;
+ − 22
use IniData;
+ − 23
use RelData;
+ − 24
use File::Copy;
+ − 25
use File::Path;
+ − 26
use File::Spec;
+ − 27
use File::Basename;
+ − 28
use Cleaner;
+ − 29
use Utils;
+ − 30
use Cwd;
+ − 31
+ − 32
#
+ − 33
# Globals.
+ − 34
#
+ − 35
+ − 36
my $verbose = 0;
+ − 37
my $overwrite = 0;
+ − 38
my $dummyRun = 0;
+ − 39
my $descriptionFile;
+ − 40
my $iniData = IniData->New();
+ − 41
my $cleaner; # object that does most of it
+ − 42
my $cleanTo;
+ − 43
my $expunge = 0; # don't leave reldatas lying around
+ − 44
my $reallyClean;
+ − 45
+ − 46
#
+ − 47
# Main.
+ − 48
#
+ − 49
+ − 50
ProcessCommandLine();
+ − 51
$cleaner = Cleaner->New($iniData, 0, $verbose, $reallyClean); # 0 = local not remote
+ − 52
ParseDescriptionFile($descriptionFile);
+ − 53
$cleaner->SetCleaningSubroutine(\&CleaningSubroutine);
+ − 54
if (!$expunge) {
+ − 55
$cleaner->SetRevertingSubroutine(\&RevertingSubroutine);
+ − 56
}
+ − 57
$cleaner->Clean();
+ − 58
+ − 59
+ − 60
#
+ − 61
# Subs.
+ − 62
#
+ − 63
+ − 64
sub ProcessCommandLine {
+ − 65
Getopt::Long::Configure ("bundling");
+ − 66
my $help;
+ − 67
GetOptions('h' => \$help, 'd' => \$dummyRun, 'v+' => \$verbose, 'o' => \$overwrite, 'r' => \$reallyClean);
+ − 68
+ − 69
if ($help) {
+ − 70
Usage(0);
+ − 71
}
+ − 72
+ − 73
$descriptionFile = shift @ARGV;
+ − 74
+ − 75
unless ($descriptionFile) {
+ − 76
print "Error: Archive cleaning description file not specified\n";
+ − 77
Usage(1);
+ − 78
}
+ − 79
+ − 80
unless ($#ARGV == -1) {
+ − 81
print "Error: Invalid number of arguments\n";
+ − 82
Usage(1);
+ − 83
}
+ − 84
+ − 85
if ($dummyRun and not $verbose) {
+ − 86
$verbose = 1;
+ − 87
}
+ − 88
}
+ − 89
+ − 90
sub Usage {
+ − 91
my $exitCode = shift;
+ − 92
+ − 93
Utils::PrintDeathMessage($exitCode, "\nUsage: cleanlocalarch [options] description-file
+ − 94
+ − 95
options:
+ − 96
+ − 97
-h help
+ − 98
-d dummy run (don't do anything) - assumes -v
+ − 99
-r really clean (removes corrupt and partially released components)
+ − 100
-v verbose output (-vv very verbose)
+ − 101
-o overwrite destination (delete destination then normal copy)
+ − 102
+ − 103
Please note, if you are in the process of publishing components to the archive
+ − 104
and specify the -r option you may lose partially released components.\n");
+ − 105
+ − 106
}
+ − 107
+ − 108
sub ParseDescriptionFile {
+ − 109
if ($dummyRun) { print "Running in dummy mode...\n"; }
+ − 110
if ($verbose) { print "Parsing \"$descriptionFile\"...\n"; }
+ − 111
open (DES, $descriptionFile) or die "Unable to open \"$descriptionFile\" for reading: $!\n";
+ − 112
+ − 113
while (my $line = <DES>) {
+ − 114
# Remove line feed, white space and comments.
+ − 115
chomp($line);
+ − 116
$line =~ s/^\s*$//;
+ − 117
$line =~ s/#.*//;
+ − 118
if ($line eq '') {
+ − 119
# Nothing left.
+ − 120
next;
+ − 121
}
+ − 122
+ − 123
my $keyWord;
+ − 124
my @operand;
+ − 125
if ($line =~ /^(\w+)\s+(.*)/) {
+ − 126
$keyWord = $1;
+ − 127
@operand = ();
+ − 128
if ($2) {
+ − 129
@operand = split /\s+/, $2;
+ − 130
}
+ − 131
} else {
+ − 132
$keyWord = $line;
+ − 133
}
+ − 134
+ − 135
unless (defined $keyWord) {
+ − 136
die "Error: Invalid line in \"$descriptionFile\":\n$line\n";
+ − 137
next;
+ − 138
}
+ − 139
+ − 140
if ($cleaner->ProcessDescriptionLine($descriptionFile, $keyWord, @operand)) {
+ − 141
# We're happy because Cleaner.pm knows what to do with this line
+ − 142
} elsif ($keyWord =~ /^clean_to$/) {
+ − 143
unless ($#operand == 0) {
+ − 144
die "Error: Incorrect number of arguments to \'$keyWord\' keyword in \"$descriptionFile\"\nSyntax: clean_to <path>\n";
+ − 145
}
+ − 146
if ($cleanTo) {
+ − 147
die "Error: \'$keyWord\' keyword specifed more than once in \"$descriptionFile\"\n";
+ − 148
}
+ − 149
$cleanTo = $operand[0];
+ − 150
} elsif ($keyWord =~ /^delete$/) {
+ − 151
if (@operand) {
+ − 152
die "Error: Incorrect number of arguments to \'$keyWord\' keyword in \"$descriptionFile\"\nSyntax: delete\n";
+ − 153
}
+ − 154
} elsif ($keyWord =~ /^expunge$/) {
+ − 155
$expunge = 1;
+ − 156
$cleaner->{expunge_already_cleaned} = 1;
+ − 157
} elsif ($keyWord =~ /^no_prompt$/) {
+ − 158
print "Warning: currently, CleanLocalArch does not prompt. 'no_prompt' keyword is redundant.\n";
+ − 159
} else {
+ − 160
die "Error: Unknown keyword \'$keyWord\' in \"$descriptionFile\"\n";
+ − 161
}
+ − 162
}
+ − 163
+ − 164
close (DES);
+ − 165
+ − 166
unless ($cleanTo || $expunge) {
+ − 167
die "Error: \"Clean to\" path not specified in \"$descriptionFile\"\n";
+ − 168
}
+ − 169
if ($cleanTo && $expunge) {
+ − 170
die "Error: can't specify both \"clean_to\" and \"expunge\" in \"$descriptionFile\"\n";
+ − 171
}
+ − 172
+ − 173
if ($verbose > 1) {
+ − 174
$cleaner->PrintEnvsToKeep();
+ − 175
}
+ − 176
}
+ − 177
+ − 178
sub CleaningSubroutine {
+ − 179
# This actually gets run by Cleaner.pm (it's a callback)
+ − 180
my $thisComp = shift;
+ − 181
my $thisVer = shift;
+ − 182
my $relDir = shift;
+ − 183
if ($expunge) {
+ − 184
print "Expunging $thisComp $thisVer from $relDir...\n" if ($verbose);
+ − 185
return DeleteComp($relDir);
+ − 186
}
+ − 187
print "Archiving $thisComp $thisVer from $relDir to $cleanTo...\n" if ($verbose);
+ − 188
my $cleanDir = "$cleanTo\\$thisComp\\$thisVer";
+ − 189
+ − 190
if (CopyComp($relDir, $cleanDir)) {
+ − 191
print "Wiping $thisComp $thisVer from $relDir...\n" if ($verbose);
+ − 192
if (DeleteComp("$relDir")) {
+ − 193
# Check if the remaining dir is empty
+ − 194
my ($parent, $file, $ext) = Utils::SplitFileName($relDir);
+ − 195
return DeleteCompIfEmpty($parent);
+ − 196
}
+ − 197
else {
+ − 198
# Call the reverting subroutine here because cleaner.pm will only revert clean components
+ − 199
RevertingSubroutine($thisComp, $thisVer, $relDir);
+ − 200
}
+ − 201
}
+ − 202
+ − 203
return 0;
+ − 204
}
+ − 205
+ − 206
sub RevertingSubroutine {
+ − 207
# Again, this gets run by Cleaner.pm
+ − 208
my $thisComp = shift;
+ − 209
my $thisVer = shift;
+ − 210
my $relDir = shift;
+ − 211
+ − 212
print "Restoring $thisComp $thisVer to $relDir...\n" if ($verbose);
+ − 213
+ − 214
# create the reldir if required
+ − 215
if(!-d $relDir) {
+ − 216
Utils::MakeDir($relDir);
+ − 217
}
+ − 218
+ − 219
my $fullCleanToPath = File::Spec->catdir($cleanTo, $thisComp, $thisVer);
+ − 220
+ − 221
my $dirContents = Utils::ReadDir($fullCleanToPath);
+ − 222
foreach my $thisFile (@$dirContents) {
+ − 223
copy(File::Spec->catdir($fullCleanToPath, $thisFile), $relDir);
+ − 224
}
+ − 225
+ − 226
print "Removing copy of $thisComp $thisVer from $cleanTo...\n" if ($verbose);
+ − 227
if (DeleteComp("$cleanTo\\$thisComp\\$thisVer")) {
+ − 228
# Check if the remaining dir is empty
+ − 229
return DeleteCompIfEmpty("$cleanTo\\$thisComp");
+ − 230
}
+ − 231
else {
+ − 232
# Failed to even delete component
+ − 233
return 0;
+ − 234
}
+ − 235
}
+ − 236
+ − 237
sub CopyComp {
+ − 238
my $dir = shift;
+ − 239
my $destDir = shift;
+ − 240
+ − 241
if (-e $destDir) {
+ − 242
if ($overwrite) {
+ − 243
if ($verbose > 0) { print "Overwriting by deleting \"$destDir\"\n"; }
+ − 244
DeleteComp("$destDir");
+ − 245
}
+ − 246
else {
+ − 247
print "Error: Can't copy \"$dir\" to \"$destDir\" because directory \"$destDir\" already exists\n";
+ − 248
return 0;
+ − 249
}
+ − 250
}
+ − 251
+ − 252
my $failed = 0;
+ − 253
my @copied;
+ − 254
eval {
+ − 255
Utils::MakeDir($destDir) unless $dummyRun;
+ − 256
};
+ − 257
if ($@) {
+ − 258
print "$@";
+ − 259
$failed = 1;
+ − 260
}
+ − 261
+ − 262
if($failed==0) {
+ − 263
my $dirContents = Utils::ReadDir($dir);
+ − 264
foreach my $thisFile (@$dirContents) {
+ − 265
if ($verbose > 1) { print "\tCopying \"$dir\\$thisFile\" to \"$destDir\"...\n"; }
+ − 266
if ($dummyRun) {
+ − 267
return 1;
+ − 268
}
+ − 269
else {
+ − 270
if (copy($dir."\\".$thisFile, $destDir)) {
+ − 271
push @copied, $thisFile;
+ − 272
}
+ − 273
else {
+ − 274
print "Error: Couldn't copy \"$dir\\$thisFile\" to \"$destDir\": $!\n";
+ − 275
$failed = 1;
+ − 276
if (-f $destDir."\\".$thisFile) {
+ − 277
# Must've part-copied this file
+ − 278
push @copied, $thisFile;
+ − 279
}
+ − 280
last;
+ − 281
}
+ − 282
}
+ − 283
}
+ − 284
}
+ − 285
+ − 286
if ($failed) {
+ − 287
# Revert copied files
+ − 288
foreach my $thisFile (@copied) {
+ − 289
unlink $destDir."\\".$thisFile or print "Error: Couldn't delete $destDir\\$thisFile when cleaning up\n";
+ − 290
}
+ − 291
DeleteCompIfEmpty($destDir) or print "Error: Couldn't clean up empty directory $destDir\n";
+ − 292
}
+ − 293
+ − 294
return ($failed == 0);
+ − 295
}
+ − 296
+ − 297
sub DeleteComp {
+ − 298
my $dir = shift;
+ − 299
+ − 300
if (!$dummyRun) {
+ − 301
local $SIG{__WARN__} = sub {my $line = shift;
+ − 302
$line =~ s/ at .*$//;
+ − 303
print "Error: $line\n";};
+ − 304
+ − 305
my $reldataFile = File::Spec->catdir($dir, 'reldata');
+ − 306
+ − 307
my $origDir = cwd();
+ − 308
chdir(dirname($dir));
+ − 309
+ − 310
if (-e $reldataFile) {
+ − 311
# Delete the reldata file first, if something goes wrong other tools will identify the archived component
+ − 312
# as corrupt by the absence of reldata
+ − 313
if (!unlink $reldataFile) {
+ − 314
print "Error: Couldn't delete \"$reldataFile\"\n";
+ − 315
return 0;
+ − 316
}
+ − 317
}
+ − 318
+ − 319
if (!rmtree($dir, 0, 0) or -d $dir) {
+ − 320
print "Error: Couldn't delete \"$dir\"\n";
+ − 321
return 0;
+ − 322
}
+ − 323
else {
+ − 324
chdir($origDir);
+ − 325
return 1;
+ − 326
}
+ − 327
}
+ − 328
else {
+ − 329
return 1;
+ − 330
}
+ − 331
}
+ − 332
+ − 333
sub DeleteCompIfEmpty {
+ − 334
my $dir = shift;
+ − 335
+ − 336
if (!$dummyRun) {
+ − 337
if (opendir(DIR, $dir)) {
+ − 338
my @files = grep( !/\.\.?$/, readdir DIR);
+ − 339
if (!closedir(DIR)) {
+ − 340
die "Error: Couldn't close '$dir' after reading. Aborting\n";
+ − 341
}
+ − 342
if (scalar(@files) == 0) {
+ − 343
print "Tidying $dir...\n" if ($verbose);
+ − 344
return DeleteComp("$dir");
+ − 345
+ − 346
}
+ − 347
else {
+ − 348
return 1; # Nothing to do
+ − 349
}
+ − 350
}
+ − 351
else {
+ − 352
print "Warning: Couldn't open '$dir' directory for reading. An empty directory may have been left behind.\n";
+ − 353
return 1; # Warning only
+ − 354
}
+ − 355
}
+ − 356
else {
+ − 357
return 1; # Dummy run
+ − 358
}
+ − 359
}
+ − 360
+ − 361
__END__
+ − 362
+ − 363
=head1 NAME
+ − 364
+ − 365
CleanLocalArch - Cleans unwanted releases from the local release archive.
+ − 366
+ − 367
=head1 SYNOPSIS
+ − 368
+ − 369
cleanlocalarch [options] <description_file>
+ − 370
+ − 371
options:
+ − 372
+ − 373
-h help
+ − 374
-d dummy run (don't do anything) - assumes -v
+ − 375
-r really clean (removes corrupt and partially released components)
+ − 376
-v verbose output (-vv very verbose)
+ − 377
-o overwrite destination (delete destination then normal copy)
+ − 378
+ − 379
Please note, if you are in the process of publishing components to the archive and specify the -r option you may lose partially released components.
+ − 380
+ − 381
=head1 DESCRIPTION
+ − 382
+ − 383
C<CleanLocalArch> allows releases to be cleaned out of a local archive. This may be useful if a local archive is consuming a large amount of disk space and there are old releases present that are no longer required. Note that releases to be cleaned are normally backed up to a user defined directory before being deleted. This allows the cleaned releases to be permanently archived (to say a writable CDROM) before they are deleted.
+ − 384
+ − 385
If C<CleanLocalArch> encounters an error while backing up releases to be cleaned, it will attempt to back out of the change by deleting the backups of any releases already done. If C<CleanLocalArch> encounters errors while backing out of a clean, it has the potential to leave releases in the backup directory. Similarly, if after backing up all releases to delete, it encounters errors while actually deleting them, it may leave releases in the local archive. However the clean can be repeated to a fresh backup directory once the problem has been isolated to get rid of these releases.
+ − 386
+ − 387
Before using C<CleanLocalArchive> you must write a plain text file that describes which releases you want to keep etc. The following keywords are supported:
+ − 388
+ − 389
=over 4
+ − 390
+ − 391
=item keep_env <component> <version>
+ − 392
+ − 393
Instructs C<CleanLocalArchive> to keep all the component versions in the environment from which the specified component was released. This keyword may be used multiple times.
+ − 394
+ − 395
=item keep_rel <component> <version>
+ − 396
+ − 397
Instructs C<CleanLocalArchive> to keep a specific component release. This keyword may be used multiple times.
+ − 398
+ − 399
=item keep_recent_env <component> <num_days>
+ − 400
+ − 401
Instructs C<CleanLocalArchive> to keep all named component releases, including their environments, where the component release has been made within the specified number of days (since the current time). This keyword may be used multiple times provided it is used for different components each time.
+ − 402
+ − 403
=item keep_recent_rel [component] <num_days>
+ − 404
+ − 405
Instructs C<CleanLocalArchive> to keep any component releases made within the specified number of days (since the current time). If a component name is specified, C<CleanLocalArchive> will only keep component releases which match that name (and are sufficiently recent). This keyword may be used multiple times if the command is used for different components.
+ − 406
+ − 407
=item keep_recent <num_days>
+ − 408
+ − 409
B<Depricated:> Equivalent to keep_recent_rel without a component name entered.
+ − 410
+ − 411
=item clean_to
+ − 412
+ − 413
Specifies where to move release to be cleaned. Use of this keyword is mandatory and may only be used once. There is an alternative, 'expunge', which will actually delete the releases - but this is only intended for test scripts and use on real, important archives is strongly discouraged.
+ − 414
+ − 415
=item force
+ − 416
+ − 417
This keyword, which takes no operands, specifies that cleanlocalarch should be non-interactive.
+ − 418
+ − 419
=back
+ − 420
+ − 421
For example:
+ − 422
+ − 423
keep_env pixie alpha
+ − 424
keep_env pixie beta
+ − 425
keep_rel comp1 rel1
+ − 426
keep_recent 10
+ − 427
clean_to \\backup\pixie_cleaned_releases
+ − 428
+ − 429
C<CleanLocalArch> will work out which component releases need to be kept in order to satisfy the specified keep criteria. All other component releases found in the archive will be moved to the C<clean_to> directory. B<It is therefore extremely important that the list of environments to keep is complete>. It is recommended that this file be controlled using a configuration management tool. It is also recommended that each project has only one description file, and that all users of C<CleanLocalArch> know where to find it.
+ − 430
+ − 431
Recommended procedure for using C<CleanLocalArch>:
+ − 432
+ − 433
=over 4
+ − 434
+ − 435
=item 1
+ − 436
+ − 437
Inform all users of the archive that a clean is about to be performed, and that the archive will be unavailable whilst this is happening.
+ − 438
+ − 439
=item 2
+ − 440
+ − 441
Take the archive off-line or alter directory permissions such that you are the only person that can access it.
+ − 442
+ − 443
=item 3
+ − 444
+ − 445
Backup the archive.
+ − 446
+ − 447
=item 4
+ − 448
+ − 449
Run C<CleanLocalArchive> and carefully check the list of components that are about to be cleaned. If you are happy, type 'yes' to continue, otherwise type 'no', modify your description file and re-run C<CleanLocalArchive>.
+ − 450
+ − 451
=item 5
+ − 452
+ − 453
Backup the C<clean_to> directory.
+ − 454
+ − 455
=item 6
+ − 456
+ − 457
Bring the archive back on-line.
+ − 458
+ − 459
=item 7
+ − 460
+ − 461
Inform all users of the archive that it is available for use once more.
+ − 462
+ − 463
=back
+ − 464
+ − 465
=head1 STATUS
+ − 466
+ − 467
Supported. If you find a problem, please report it to us.
+ − 468
+ − 469
=head1 COPYRIGHT
+ − 470
+ − 471
Copyright (c) 2001-2009 Nokia Corporation and/or its subsidiary(-ies).
+ − 472
All rights reserved.
+ − 473
This component and the accompanying materials are made available
+ − 474
under the terms of the License "Eclipse Public License v1.0"
+ − 475
which accompanies this distribution, and is available
+ − 476
at the URL "http://www.eclipse.org/legal/epl-v10.html".
+ − 477
+ − 478
Initial Contributors:
+ − 479
Nokia Corporation - initial contribution.
+ − 480
+ − 481
Contributors:
+ − 482
+ − 483
Description:
+ − 484
+ − 485
+ − 486
=cut