Subversion Repositories DevTools

Rev

Rev 229 | 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:
11
#       Mainatin a local cache of dpkg_Archive
12
#
13
# Notes:
14
#       Stopped using the JATS "cp.exe" utility as there was a wierd problem under
15
#       windows. The package orahops-ssw-install/1.0.1000.ssw could not be installed
16
#       correctly. It appaesr that the 'cp' could not process the subdir pair
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
379
        #       1) Tag the directoryto be deleted on error
380
        #
381
        $remove_on_error = "$cache/$_";
382
        ErrorConfig( 'on_exit' => \&error_recovery );
383
 
384
        #
385
        #   Not a forced refesh. Ensure that the cached copy is
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: $_" );
413
            $dir_found = 0;
414
        }
415
 
416
        #
417
        #   If its not in the cache then copy it in
418
        #
419
        unless ( $dir_found )
420
        {
421
            mkpath( "$cache/$_", $opt_debug, 0777);
422
            TouchFile ( "$cache/$_/built.cache", "Marks the cache copy as incomplete");
423
            Verbose ( "Copy in: $_" );
424
            $copyFind_dst = "$cache/$_";
425
            $copyFind_src = $parchive;
426
            $copyFind_src_len = length( $copyFind_src );
427
            File::Find::find( \&copyFind, $parchive );
428
            rmtree( "$cache/$_/built.cache", $opt_debug );  # Works on files too !!
429
        }
430
        else
431
        {
432
            $opr = "Skip";
433
            print "Cache $opr: $_ -> $cache\n";
434
        }
435
    }
436
}
437
 
438
#-------------------------------------------------------------------------------
439
# Function        : copyFind
440
#
441
# Description     : File:Find:find callback function to transfer files
442
#
443
# Inputs          : None
444
#                   Global: $copyFind_dst       : Target directory
445
#                   Global: $copyFind_src       : Source directory
446
#                   Global: $copyFind_src_len   : Length of Source dir
447
#
448
# Returns         : 
449
#
450
 
451
sub copyFind
452
{
453
    my $item = $File::Find::name;
454
 
455
    #
456
    #   Calculate the target directory name
457
    #
458
    my $target = $copyFind_dst . substr($item, $copyFind_src_len );
459
    if ( -d $item )
460
    {
461
        #
462
        #   Directories are handled differently
463
        #       - Directories are created with nice permissions
464
        #
465
        if ( ! -d $target )
466
        {
467
            mkpath( $target, $opt_debug, 0755);
468
        }
469
    }
470
    else
471
    {
472
        #
473
        #   File copy
474
        #
475
        #
476
        #   Copy file to destination
477
        #   If the file is a link, then duplicate the link contents
478
        #   Use: Unix libraries are created as two files:
479
        #        lib.xxxx.so -> libxxxx.so.vv.vv.vv
480
        #
481
        if ( -l $item )
482
        {
483
            Debug("Clone Link: $target");
484
            my $link = readlink $item;
485
            Debug( "Link: $item, $link");
486
            symlink ($link, $target );
487
            unless ( $link && -l $target )
488
            {
489
                Error("Failed to copy link [$item] to [$target]: $!");
490
            }
491
        }
492
        elsif (File::Copy::copy($item, $target))
493
        {
494
            Debug("Copying File: $target");
495
 
496
            #   Make the file ReadOnly
497
            my $perm = (stat $item)[2] & 07777;
498
            CORE::chmod $perm & 0555, $target;
499
        }
500
        else
501
        {
502
            Error("Failed to copy file [$item] to [$target]: $!");
503
        }
504
    }
505
}
506
 
507
#-------------------------------------------------------------------------------
508
# Function        : error_recovery
509
#
510
# Description     : Error recovery routine
511
#                   Delete the cached entry
512
#
513
# Inputs          :  Globals
514
#
515
# Returns         : None
516
#
517
sub error_recovery
518
{
519
    if ( $remove_on_error )
520
    {
521
        Error ("Error cleanup. Delete cache entry: $remove_on_error");
522
        rmtree( $remove_on_error, $opt_debug );
523
        $remove_on_error = undef;
524
    }
525
}
526
 
527
 
528
#-------------------------------------------------------------------------------
529
# Function        : age_the_cache
530
#
531
# Description     : Age cache entries
532
#                   Determine the age by:
533
#                       Use used.cache file if present
534
#                       Use descpkg file
535
#                       Not a proper entry - delete
536
#
537
# Inputs          : opt_age         - Delete packages older than XX days
538
#
539
# Returns         : Nothing
540
#
541
sub age_the_cache
542
{
543
    my $now = time;
544
 
545
    opendir (DIR, $cache) || die "Cannot open $cache\n";
546
    my @dir_list = readdir(DIR);
547
    closedir DIR;
548
 
549
    for my $fn ( @dir_list)
550
    {
551
        my $keep_dir = 0;
552
        next if ( $fn =~ m/^\./ );
553
        next unless ( -d "$cache/$fn" );
554
 
555
        opendir( DIR, "$cache/$fn" ) || die "Cannot open $cache/$fn\n";
556
        while ( my $fn1 = readdir(DIR) )
557
        {
558
            next if ( $fn1 =~ m/^\./ );
559
            my $dir = "$cache/$fn/$fn1";
560
            my $file = "$dir/used.cache" ;
561
            $file = "$dir/descpkg" unless ( -f $file );
562
 
563
            if ( ! -f $file )
564
            {
565
                #
566
                #   No descpkg file
567
                #   This is a badly formed entry - so delete it
568
                #
569
                Message("Purging: $fn/$fn1" );
570
                rmtree( $dir, $opt_debug );
571
 
572
            }
573
            else
574
            {
575
                #
576
                #   used.cache or descpkg file found
577
                #   How old is it
578
                #
579
                my $timestamp = (stat($file))[9] || 0;
580
                my $age = int( ($now - $timestamp) / (60 * 60 * 24));
581
 
582
                if ( $age > $opt_age )
583
                {
584
                    Message("Aging: $fn/$fn1, $age" );
585
                    rmtree( $dir, $opt_debug );
586
                }
587
                else
588
                {
589
                    Verbose("Age of: $fn/$fn1, $age" );
590
                    $keep_dir = 1;
591
                }
592
            }
593
        }
594
        closedir DIR;
595
 
596
        #
597
        #   Delete the entire directory if is is empty
598
        #
599
        unless ( $keep_dir )
600
        {
601
            Message("Remove Empty Dir: $cache/$fn"  );
602
            rmtree( "$cache/$fn", $opt_debug );
603
        }
604
    }
605
}
606
 
607
#-------------------------------------------------------------------------------
608
#   Documentation
609
#
610
 
611
=pod
612
 
613
=head1 NAME
614
 
615
cache_dpkg - Maintain a local cache of packages
616
 
617
=head1 SYNOPSIS
618
 
619
 jats cache_dpkg.pl [options] package/version ...
620
 
621
 Options:
622
    -help              - brief help message
623
    -help -help        - Detailed help message
624
    -man               - Full documentation
625
    -clear             - Delete the entire cache
626
    -flush             - Flush all named packages
627
    -[no]refresh       - Refresh package in cache
628
    -list              - List cache contents with nice format
629
    -dir               - List cache contents
630
    -export            - Generate a list of cached packages
631
    -refresh_all       - Refresh all packages within the cache
632
    -update_all        - Update all packages within the cache as required
633
    -quiet             - Suppress warnings
634
    -age=nn            = Remove all packages older than nn days
635
 
636
=head1 OPTIONS
637
 
638
=over 8
639
 
640
=item B<-help>
641
 
642
Print a brief help message and exits.
643
 
644
=item B<-help -help>
645
 
646
Print a detailed help message with an explanation for each option.
647
 
648
=item B<-man>
649
 
650
Prints the manual page and exits.
651
 
652
=item B<-clear>
653
 
654
Delete the B<entire> contents of the dpkg_archive cache. This will occur before
655
any new packages are copied into the cache.
656
 
657
=item B<-flush>
658
 
659
If set then the utility will delete the named packages from the cache. All named
660
packaged will be deleted. This option affects all named packages. It is not
661
possible to flush and copy packages with the same command.
662
 
663
=item B<-[no]refresh>
664
 
665
If the B<refresh> option has been specified then packages will be deleted, from
666
the cache and then a new copy will be copied in. If not specified then no copy
667
will occur if the package is present in the cache.
668
 
669
=item B<-list>
670
 
671
Display a list of all packages in the cache. A formatted display is generated.
672
 
673
This will be done before any packages are transferred.
674
 
675
=item B<-dir>
676
 
677
Display a list of all packages in the cache. This is a raw directory like
678
listing of the cache.
679
 
680
This will be done before any packages are transferred.
681
 
682
=item B<-export>
683
 
684
Generate a space separated list of cached packages as a single line. This is
685
intended to allow a list be exported for later import.
686
 
687
=item B<-refresh_all>
688
 
689
This option will force the program to refresh all the packages in the cache.
690
This forces a B<-refresh> and may be combined with other packages specified
691
on the command line.
692
 
693
=item B<-update_all>
694
 
695
This option will force the program to examine all packages within the cache and
696
refresh packages that are out of date. This option may be combined with other
697
packages specified on the command line.
698
 
699
A package is deemed to be out-of-date if the modification time of the package's
700
descpkg file in the cache is older than the one in the achive.
701
 
702
=item B<-quiet>
703
 
704
This option will suppress almost all of the progress messages, except for a single
705
copy message. It is intended to be used when the program is called from another
706
script.
707
 
708
=item B<-age=nn>
709
 
710
This option will delete all package versions that are older than the nn days.
711
The age of a package is calculated from the timestamp of the descpkg file.
712
 
713
=back
714
 
715
=head1 DESCRIPTION
716
 
717
This program simplifies the operation of maintaining a local copy of
718
used packages from the maintaining dpkg_archive store. The cache should be
719
stored on your local disk for speed.
720
 
721
The local cache and the master cache are both determined from the environment
722
variable GBE_DPKG.
723
 
724
=head2 Location of the cache
725
 
726
The local cache is located by examining the paths specified in GBE_DPKG. The
727
local cache is taken to be the last directory with the keys string B<_cache> in it.
728
 
729
The local cache cannot be the same as the main cache.
730
 
731
The suggested named for the cache is: F<dpkg_archive_cache>
732
 
733
=head2 Location of the maintaining archive
734
 
735
The main cache is located by examining the paths specified in GBE_DPKG. The
736
main cache is taken to be the last directory with list.
737
 
738
=head2 Interaction with local_dpkg_archive
739
 
740
If a package is located in the users local_dpkg_archive and we are doing a
741
simple cache update then the package will be deleted from the cache. This is
742
done to speed use on slow remote links and ensure cache consistency.
743
 
744
=head1 EXAMPLE
745
 
746
=head2 jats dpkg_cache -list
747
 
748
This will list the current contents of the cache.
749
 
750
=head2 jats dpkg_cache -refresh crc/1.0.4.cr
751
 
752
This will delete any cached copy of the package crc/1.0.4.cr, if one exists,
753
and then copy in a new version.
754
 
755
=head2 jats dpkg_cache crc
756
 
757
This will copy in all versions of the crc package. This may not be desirable.
758
 
759
=head2 jats dpkg_cache -update_all
760
 
761
This will examine all packages in the cache and refresh those packages that are
762
out of date.
763
 
764
=cut
765