Subversion Repositories DevTools

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
227 dpurdie 1
#! perl
2
########################################################################
3
# Copyright (C) 1998-2004 ERG Limited, All rights reserved
4
#
5
# Module name   : cache_dpkg
6
# Module type   : Makefile system
7
# Compiler(s)   : n/a
8
# Environment(s): jats
9
#
10
# Description:
335 dpurdie 11
#       Maintain a local cache of dpkg_Archive
227 dpurdie 12
#
13
# Notes:
335 dpurdie 14
#       Stopped using the JATS "cp.exe" utility as there was a weird problem under
227 dpurdie 15
#       windows. The package orahops-ssw-install/1.0.1000.ssw could not be installed
335 dpurdie 16
#       correctly. It appears that the 'cp' could not process the subdir pair
227 dpurdie 17
#       "DBMGR/DBMGR". Change the name it was OK.
18
#
19
#       Solution: Avoid system functions
20
#
21
#......................................................................#
22
 
23
use strict;
24
use warnings;
25
use JatsError;
26
use FileUtils;
27
use File::Find;
28
use File::Path;
29
use File::Copy;
30
 
31
use Getopt::Long;
32
use Pod::Usage;                             # required for help support
33
 
34
my $VERSION = "1.4.0";
35
 
36
 
37
#
38
#   Options
39
#
40
my $opt_debug   = $ENV{'GBE_DEBUG'};        # Allow global debug
41
my $opt_verbose = $ENV{'GBE_VERBOSE'};      # Allow global verbose
42
my $opt_help = 0;
43
my $opt_clear;
44
my $opt_flush;
45
my $opt_refresh;
46
my $opt_refresh_all;
47
my $opt_list;
48
my $opt_list_short;
49
my $opt_export;
50
my $opt_quiet;
51
my $opt_update_all;
52
my $opt_age;
53
 
54
#
55
#   Global Variables
56
#
57
my $GBE_DPKG        = $ENV{'GBE_DPKG'};                     # The Master repository
58
my $GBE_DPKG_CACHE  = $ENV{'GBE_DPKG_CACHE'} || "";         # Caches
59
my $GBE_DPKG_LOCAL  = $ENV{'GBE_DPKG_LOCAL'} || "";         # Local scratch
60
my $GBE_DPKG_STORE  = $ENV{'GBE_DPKG_STORE'} || "";         # Global Store
61
my $GBE_BIN         = $ENV{'GBE_BIN'};
62
 
63
my $gstore;
64
my $archive;
65
my $cache;
66
my $parchive;
67
my $local = '';
68
my $local_pkg;
69
my @package_list;
70
my @refresh_list;
71
#
72
#   Globals for recursive copy
73
#
74
my $copyFind_dst;
75
my $copyFind_src;
76
my $copyFind_src_len;
77
 
78
#
79
#   Globals for error recovery
80
#
81
my  $remove_on_error;
82
 
83
#-------------------------------------------------------------------------------
84
# Function        : Mainline Entry Point
85
#
86
# Description     :
87
#
88
# Inputs          :
89
#
90
my $result = GetOptions (
337 dpurdie 91
                "help|h:+"                  => \$opt_help,
92
                "manual:3"                  => \$opt_help,
93
                "verbose:+"                 => \$opt_verbose,           # flag, multiple use allowed
94
                "debug:+"                   => \$opt_debug,             # flag, multiple use allowed
227 dpurdie 95
                "flush"                     => \$opt_flush,             # flag
96
                "clear"                     => \$opt_clear,             # flag
97
                "refresh!"                  => \$opt_refresh,           # flag
98
                "refresh_all|refresh-all"   => \$opt_refresh_all,       # flag
99
                "update_all|update-all"     => \$opt_update_all,        # flag
100
                "list"                      => \$opt_list,              # flag
101
                "dir"                       => \$opt_list_short,        # flag
102
                "export"                    => \$opt_export,            # flag
103
                "quiet"                     => \$opt_quiet,             # flag
104
                "age=i"                     => \$opt_age,               # integer
105
                );
106
 
107
                #
108
                #   UPDATE THE DOCUMENTATION AT THE END OF THIS FILE !!!
109
                #
110
 
111
#
112
#   Process help and manual options
113
#
114
pod2usage(-verbose => 0, -message => "Version: $VERSION")  if ($opt_help == 1  || ! $result);
115
pod2usage(-verbose => 1)  if ($opt_help == 2 );
337 dpurdie 116
pod2usage(-verbose => 2)  if ($opt_help > 2);
227 dpurdie 117
 
118
#
119
#   Configure the error reporting process now that we have the user options
120
#
121
ErrorConfig( 'name'    =>'CACHE',
122
             'verbose' => $opt_verbose,
123
             'debug'   => $opt_debug );
124
 
125
#
126
#   Validate user options
127
#
128
Error( "GBE_BIN not defined in the environment" ) unless ( defined($GBE_BIN) );
129
Error( "GBE_DPKG not defined in the environment" ) unless ( defined($GBE_DPKG) );
130
 
131
#
132
#   No cache - nothing to do
133
#   Generate a status message and exit
134
#
135
unless ( defined($GBE_DPKG_CACHE) )
136
{
137
    Warning ( "GBE_DPKG_CACHE not defined in the environment" );
138
    return 0;
139
}
140
 
141
 
142
$opt_refresh = 1
143
    if ( $opt_refresh_all );
144
 
145
#
146
#   Locate the archive cache
147
#
148
#   Determine the archive to work on
149
#   Search the cache archive list for one with the keyword "_cache"
150
#   or use the last one.
151
#
152
$GBE_DPKG =~ tr~\\/~/~s;
153
$GBE_DPKG =~ s~/$~~;
154
 
155
my @pkg_list = split ( ';', $GBE_DPKG_CACHE );
156
for (@pkg_list)
157
{
158
    $cache = $_
159
        if ( m/_cache/ );
160
}
161
$cache = pop @pkg_list unless ( $cache );
162
$archive = $GBE_DPKG;
163
$local = $GBE_DPKG_LOCAL;
164
$gstore = $GBE_DPKG_STORE;
165
 
166
Error ("No dpkg_archive_cache found") unless ( $cache );
167
Error ("dpkg_archive_cache is the main archive") if ( $cache eq $archive );
168
Error ("main archive is local archive" )         if ( $local && $local eq $archive );
169
Warning  ("dpkg_archive_cache is local_archive" )   if ( $local && $local eq $cache );
170
 
171
#
172
#   Export the list of cache entries
173
#
174
if ( $opt_export or $opt_refresh_all or $opt_list_short or $opt_update_all )
175
{
176
    opendir (DIR, $cache) || die "Cannot open $cache\n";
177
    my @dir_list = readdir(DIR);
178
    closedir DIR;
179
 
180
    for my $fn ( @dir_list)
181
    {
182
        my $count = 0;
183
        next if ( $fn =~ m/^\./ );
184
        next unless ( -d "$cache/$fn" );
185
 
186
        opendir( DIR, "$cache/$fn" ) || die "Cannot open $cache/$fn\n";
187
        while ( my $fn1 = readdir(DIR) )
188
        {
189
            next if ( $fn1 =~ m/^\./ );
190
            push @package_list, "$fn/$fn1";
191
        }
192
        closedir DIR;
193
    }
194
 
195
    if ( $opt_refresh_all or $opt_update_all )
196
    {
197
        @refresh_list = @package_list
198
    }
199
 
200
    #
201
    #   Simply export a list of packages in the cache
202
    #   Used by other programs
203
    #
204
    if ( $opt_export )
205
    {
206
        print join ' ', @package_list;
207
        exit 0;
208
    }
209
}
210
 
211
#
212
#   Display cache information
213
#   This is done AFTER the export - since the export command does not
214
#   need this header.
215
if ( $opt_verbose || $#ARGV < 0 )
216
{
217
    print  "dpkg_store        : $gstore\n" if ($gstore);
218
    print  "dpkg_archive      : $archive\n";
219
    print  "dpkg_archive cache: $cache\n";
220
    print  "dpkg_archive local: $local\n";
221
    Verbose ("args:               @ARGV");
222
}
223
 
224
#
225
#   List the contents of the cache
226
#
227
if ( $opt_list_short )
228
{
229
    print join "\n", @package_list;
230
}
231
 
232
if ( $opt_list )
233
{
234
    #
235
    #   Process user commands
236
    #   List: A nice pretty display of the cache
237
    #
238
    opendir (DIR, $cache) || die "Cannot open $cache\n";
239
    my @dir_list = readdir(DIR);
240
    closedir DIR;
241
 
242
    #
243
    #   Determine max length of a name so that the display is nicely aligned
244
    #
245
    my $nl = 10;
246
    for my $fn ( @dir_list)
247
    {
248
        my $ns = length($fn);
249
        $nl = $ns if ( $ns > $nl )
250
    }
251
 
252
    #
253
    #   Display information by package
254
    #   A nicely formatted list with line wrapping
255
    #
256
    for my $fn ( @dir_list)
257
    {
258
        my $count = 0;
259
        next if ( $fn =~ m/^\./ );
260
        next unless ( -d "$cache/$fn" );
261
 
262
        opendir( DIR, "$cache/$fn" ) || die "Cannot open $cache/$fn\n";
263
        printf "%-*s :", $nl, $fn;
264
        while ( my $fn1 = readdir(DIR) )
265
        {
266
            next if ( $fn1 =~ m/^\./ );
267
            if ( $count++ >= 4 )
268
            {
269
                $count = 1;
270
                printf "\n%*s  ", $nl, "";
271
            }
272
            printf " %14.14s", $fn1;
273
        }
274
        closedir DIR;
275
        print "\n";
276
    }
277
}
278
 
279
#
280
#   Clear the cache
281
#
282
if ( $opt_clear )
283
{
284
    Warning   ( "Deleting the ENTIRE cache");
285
    rmtree( $cache, $opt_debug );
286
    mkpath( $cache, $opt_debug, 0777);
287
}
288
 
289
#
290
#   Perform cache aging
291
#
292
if ( $opt_age )
293
{
294
    Error ("Age parameter cannot be negative") if ( $opt_age < 0 );
295
    age_the_cache();
296
}
297
 
298
#
299
#   Process the command line arguments
300
#   These MUST be of the form
301
#       packagename
302
#       packagename/version
303
#
304
 
305
for (@ARGV, @refresh_list)
306
{
307
    $remove_on_error = undef;
308
    if ( $opt_flush )
309
    {
310
        unless ( -d "$cache/$_" )
311
        {
312
            Warning ("Package not in cache: $_")
313
                unless ( $opt_quiet );
314
        }
315
        else
316
        {
317
            rmtree( "$cache/$_", $opt_debug );
318
            print "Package removed: $_\n"
319
                unless ( $opt_quiet );
320
        }
321
    }
322
    else
323
    {
324
        #
325
        #   Speed up - for remote access
326
        #   If the package is found in the local archive and we aren't doing
327
        #   anything fancy then don't access the remote archive
328
        #
329
        my $local_pkg = ( -d "$local/$_" || -f "$local/$_.lnk" );
330
        if ( $local_pkg )
331
        {
332
            if ( !$opt_refresh )
333
            {
334
                print "Cache SkipLocal: $_ Local copy found\n";
335
 
336
                #
337
                # Delete the cache copy
338
                # The user is playing with local copy
339
                #
340
                if ( -d "$cache/$_" )
341
                {
342
                    print  "Cache SkipLocalDelete: $_ -> $cache\n";
343
                    Verbose ( "Remove: $_" );
344
                    rmtree( "$cache/$_", $opt_debug );
345
                    Verbose ( "Remove Complete: $_" );
346
                }
347
                next;
348
            }
349
        }
350
 
351
        #
352
        #   Locate package.
353
        #   It may be in GBE_DPKG or GBE_DPKG_STORE
354
        #
355
        $parchive = "$archive/$_";
356
        unless ( -d $parchive )
357
        {
358
            $parchive = "$gstore/$_";
359
            unless ( $gstore && -d $parchive )
360
            {
361
                Warning ("Package not in archive: $_")
362
                    unless ( $opt_quiet );
363
                next;
364
            }
365
        }
366
 
367
        ########################################################################
368
        #   We have a package to process
369
        #
370
 
371
        my $dir_found = ( -d "$cache/$_" ) ;
372
        my $force_update = 0;
373
        my $opr = "Update";
374
 
375
 
376
        #
377
        #   Setup error recovery
335 dpurdie 378
        #       1) Tag the directory to be deleted on error
227 dpurdie 379
        #
380
        $remove_on_error = "$cache/$_";
381
        ErrorConfig( 'on_exit' => \&error_recovery );
382
 
383
        #
335 dpurdie 384
        #   Not a forced refresh. Ensure that the cached copy is
227 dpurdie 385
        #   up to date. Examine descpkg
386
        #
387
        if ( $dir_found )
388
        {
389
            if ( -f "$cache/$_/built.cache" )
390
            {
391
                $force_update = 1;
392
                $opr = "Incomplete";
393
                Verbose ("Cache Copy Incomplete: $_");
394
            }
395
            elsif ( FileIsNewer( "$parchive/descpkg", "$cache/$_/descpkg" ) )
396
            {
397
                $force_update = 1;
398
                $opr = "OutOfDate";
399
                Verbose ("Cache out-of-date: $_");
400
            }
401
        }
402
 
403
        #
404
        #   If we need to refresh the cache copy - delete it first
405
        #
406
        if ( ($opt_refresh || $force_update) && $dir_found )
407
        {
408
            print  "Cache $opr: $_ -> $cache\n";
409
            Verbose ( "Remove: $_" );
410
            rmtree( "$cache/$_", $opt_debug );
411
            Verbose ( "Remove Complete: $_" );
229 dpurdie 412
 
413
            #
414
            #   Force transfer, but without the status message
415
            #
227 dpurdie 416
            $dir_found = 0;
229 dpurdie 417
            $opr = '';
227 dpurdie 418
        }
419
 
420
        #
421
        #   If its not in the cache then copy it in
422
        #
423
        unless ( $dir_found )
424
        {
229 dpurdie 425
            print "Cache $opr: $_ -> $cache\n" if $opr;
227 dpurdie 426
            mkpath( "$cache/$_", $opt_debug, 0777);
427
            TouchFile ( "$cache/$_/built.cache", "Marks the cache copy as incomplete");
428
            Verbose ( "Copy in: $_" );
429
            $copyFind_dst = "$cache/$_";
430
            $copyFind_src = $parchive;
431
            $copyFind_src_len = length( $copyFind_src );
432
            File::Find::find( \&copyFind, $parchive );
433
            rmtree( "$cache/$_/built.cache", $opt_debug );  # Works on files too !!
434
        }
435
        else
436
        {
437
            $opr = "Skip";
438
            print "Cache $opr: $_ -> $cache\n";
439
        }
440
    }
441
}
442
 
443
#-------------------------------------------------------------------------------
444
# Function        : copyFind
445
#
446
# Description     : File:Find:find callback function to transfer files
447
#
448
# Inputs          : None
449
#                   Global: $copyFind_dst       : Target directory
450
#                   Global: $copyFind_src       : Source directory
451
#                   Global: $copyFind_src_len   : Length of Source dir
452
#
453
# Returns         : 
454
#
455
 
456
sub copyFind
457
{
458
    my $item = $File::Find::name;
459
 
460
    #
461
    #   Calculate the target directory name
462
    #
463
    my $target = $copyFind_dst . substr($item, $copyFind_src_len );
464
    if ( -d $item )
465
    {
466
        #
467
        #   Directories are handled differently
468
        #       - Directories are created with nice permissions
469
        #
470
        if ( ! -d $target )
471
        {
472
            mkpath( $target, $opt_debug, 0755);
473
        }
474
    }
475
    else
476
    {
477
        #
478
        #   File copy
479
        #
480
        #
481
        #   Copy file to destination
482
        #   If the file is a link, then duplicate the link contents
483
        #   Use: Unix libraries are created as two files:
484
        #        lib.xxxx.so -> libxxxx.so.vv.vv.vv
485
        #
486
        if ( -l $item )
487
        {
488
            Debug("Clone Link: $target");
489
            my $link = readlink $item;
490
            Debug( "Link: $item, $link");
491
            symlink ($link, $target );
492
            unless ( $link && -l $target )
493
            {
494
                Error("Failed to copy link [$item] to [$target]: $!");
495
            }
496
        }
497
        elsif (File::Copy::copy($item, $target))
498
        {
499
            Debug("Copying File: $target");
500
 
501
            #   Make the file ReadOnly
502
            my $perm = (stat $item)[2] & 07777;
503
            CORE::chmod $perm & 0555, $target;
504
        }
505
        else
506
        {
507
            Error("Failed to copy file [$item] to [$target]: $!");
508
        }
509
    }
510
}
511
 
512
#-------------------------------------------------------------------------------
513
# Function        : error_recovery
514
#
515
# Description     : Error recovery routine
516
#                   Delete the cached entry
517
#
518
# Inputs          :  Globals
519
#
520
# Returns         : None
521
#
522
sub error_recovery
523
{
524
    if ( $remove_on_error )
525
    {
345 dpurdie 526
        ReportError ("Error cleanup. Delete cache entry: $remove_on_error");
227 dpurdie 527
        rmtree( $remove_on_error, $opt_debug );
528
        $remove_on_error = undef;
529
    }
530
}
531
 
532
 
533
#-------------------------------------------------------------------------------
534
# Function        : age_the_cache
535
#
536
# Description     : Age cache entries
537
#                   Determine the age by:
538
#                       Use used.cache file if present
539
#                       Use descpkg file
540
#                       Not a proper entry - delete
541
#
542
# Inputs          : opt_age         - Delete packages older than XX days
543
#
544
# Returns         : Nothing
545
#
546
sub age_the_cache
547
{
548
    my $now = time;
549
 
550
    opendir (DIR, $cache) || die "Cannot open $cache\n";
551
    my @dir_list = readdir(DIR);
552
    closedir DIR;
553
 
554
    for my $fn ( @dir_list)
555
    {
556
        my $keep_dir = 0;
557
        next if ( $fn =~ m/^\./ );
558
        next unless ( -d "$cache/$fn" );
559
 
560
        opendir( DIR, "$cache/$fn" ) || die "Cannot open $cache/$fn\n";
561
        while ( my $fn1 = readdir(DIR) )
562
        {
563
            next if ( $fn1 =~ m/^\./ );
564
            my $dir = "$cache/$fn/$fn1";
565
            my $file = "$dir/used.cache" ;
566
            $file = "$dir/descpkg" unless ( -f $file );
567
 
568
            if ( ! -f $file )
569
            {
570
                #
571
                #   No descpkg file
572
                #   This is a badly formed entry - so delete it
573
                #
574
                Message("Purging: $fn/$fn1" );
575
                rmtree( $dir, $opt_debug );
576
 
577
            }
578
            else
579
            {
580
                #
581
                #   used.cache or descpkg file found
582
                #   How old is it
583
                #
584
                my $timestamp = (stat($file))[9] || 0;
585
                my $age = int( ($now - $timestamp) / (60 * 60 * 24));
586
 
587
                if ( $age > $opt_age )
588
                {
589
                    Message("Aging: $fn/$fn1, $age" );
590
                    rmtree( $dir, $opt_debug );
591
                }
592
                else
593
                {
594
                    Verbose("Age of: $fn/$fn1, $age" );
595
                    $keep_dir = 1;
596
                }
597
            }
598
        }
599
        closedir DIR;
600
 
601
        #
602
        #   Delete the entire directory if is is empty
603
        #
604
        unless ( $keep_dir )
605
        {
606
            Message("Remove Empty Dir: $cache/$fn"  );
607
            rmtree( "$cache/$fn", $opt_debug );
608
        }
609
    }
610
}
611
 
612
#-------------------------------------------------------------------------------
613
#   Documentation
614
#
615
 
616
=pod
617
 
618
=head1 NAME
619
 
620
cache_dpkg - Maintain a local cache of packages
621
 
622
=head1 SYNOPSIS
623
 
624
 jats cache_dpkg.pl [options] package/version ...
625
 
626
 Options:
627
    -help              - brief help message
628
    -help -help        - Detailed help message
629
    -man               - Full documentation
630
    -clear             - Delete the entire cache
631
    -flush             - Flush all named packages
632
    -[no]refresh       - Refresh package in cache
633
    -list              - List cache contents with nice format
634
    -dir               - List cache contents
635
    -export            - Generate a list of cached packages
636
    -refresh_all       - Refresh all packages within the cache
637
    -update_all        - Update all packages within the cache as required
638
    -quiet             - Suppress warnings
639
    -age=nn            = Remove all packages older than nn days
640
 
641
=head1 OPTIONS
642
 
643
=over 8
644
 
645
=item B<-help>
646
 
647
Print a brief help message and exits.
648
 
649
=item B<-help -help>
650
 
651
Print a detailed help message with an explanation for each option.
652
 
653
=item B<-man>
654
 
655
Prints the manual page and exits.
656
 
657
=item B<-clear>
658
 
659
Delete the B<entire> contents of the dpkg_archive cache. This will occur before
660
any new packages are copied into the cache.
661
 
662
=item B<-flush>
663
 
664
If set then the utility will delete the named packages from the cache. All named
665
packaged will be deleted. This option affects all named packages. It is not
666
possible to flush and copy packages with the same command.
667
 
668
=item B<-[no]refresh>
669
 
670
If the B<refresh> option has been specified then packages will be deleted, from
671
the cache and then a new copy will be copied in. If not specified then no copy
672
will occur if the package is present in the cache.
673
 
674
=item B<-list>
675
 
676
Display a list of all packages in the cache. A formatted display is generated.
677
 
678
This will be done before any packages are transferred.
679
 
680
=item B<-dir>
681
 
682
Display a list of all packages in the cache. This is a raw directory like
683
listing of the cache.
684
 
685
This will be done before any packages are transferred.
686
 
687
=item B<-export>
688
 
689
Generate a space separated list of cached packages as a single line. This is
690
intended to allow a list be exported for later import.
691
 
692
=item B<-refresh_all>
693
 
694
This option will force the program to refresh all the packages in the cache.
695
This forces a B<-refresh> and may be combined with other packages specified
696
on the command line.
697
 
698
=item B<-update_all>
699
 
700
This option will force the program to examine all packages within the cache and
701
refresh packages that are out of date. This option may be combined with other
702
packages specified on the command line.
703
 
704
A package is deemed to be out-of-date if the modification time of the package's
335 dpurdie 705
descpkg file in the cache is older than the one in the archive.
227 dpurdie 706
 
707
=item B<-quiet>
708
 
709
This option will suppress almost all of the progress messages, except for a single
710
copy message. It is intended to be used when the program is called from another
711
script.
712
 
713
=item B<-age=nn>
714
 
715
This option will delete all package versions that are older than the nn days.
716
The age of a package is calculated from the timestamp of the descpkg file.
717
 
718
=back
719
 
720
=head1 DESCRIPTION
721
 
722
This program simplifies the operation of maintaining a local copy of
723
used packages from the maintaining dpkg_archive store. The cache should be
724
stored on your local disk for speed.
725
 
726
The local cache and the master cache are both determined from the environment
727
variable GBE_DPKG.
728
 
729
=head2 Location of the cache
730
 
731
The local cache is located by examining the paths specified in GBE_DPKG. The
732
local cache is taken to be the last directory with the keys string B<_cache> in it.
733
 
734
The local cache cannot be the same as the main cache.
735
 
736
The suggested named for the cache is: F<dpkg_archive_cache>
737
 
738
=head2 Location of the maintaining archive
739
 
740
The main cache is located by examining the paths specified in GBE_DPKG. The
741
main cache is taken to be the last directory with list.
742
 
743
=head2 Interaction with local_dpkg_archive
744
 
745
If a package is located in the users local_dpkg_archive and we are doing a
746
simple cache update then the package will be deleted from the cache. This is
747
done to speed use on slow remote links and ensure cache consistency.
748
 
749
=head1 EXAMPLE
750
 
751
=head2 jats dpkg_cache -list
752
 
753
This will list the current contents of the cache.
754
 
755
=head2 jats dpkg_cache -refresh crc/1.0.4.cr
756
 
757
This will delete any cached copy of the package crc/1.0.4.cr, if one exists,
758
and then copy in a new version.
759
 
760
=head2 jats dpkg_cache crc
761
 
762
This will copy in all versions of the crc package. This may not be desirable.
763
 
764
=head2 jats dpkg_cache -update_all
765
 
766
This will examine all packages in the cache and refresh those packages that are
767
out of date.
768
 
769
=cut
770