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