Subversion Repositories DevTools

Rev

Rev 6423 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
227 dpurdie 1
########################################################################
6177 dpurdie 2
# COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
227 dpurdie 3
#
4
# Module name   : jats.sh
5
# Module type   : Perl Package
6
# Compiler(s)   : n/a
7
# Environment(s): jats
8
#
9
# Description   : This package contains functions to manipulate file paths
10
#                 directories and names.
11
#
6177 dpurdie 12
#                    InitFileUtils              - Call to init globals - after CWD has been setup
227 dpurdie 13
#                    Getcwd                     - Get current directory
14
#                    Realpath                   - Get real path
15
#                    Realfile                   - Get real path ?
16
#                    RelPath                    - Convert to relative path
17
#                    AbsPath                    - Convert to Abs path
18
#                    FullPath                   - Convert to Abs path with driver letter
257 dpurdie 19
#                    TruePath                   - Case Corrected pathname
227 dpurdie 20
#                    CleanPath                  - Clean up a path
21
#                    StripDrive                 - Remove drive letter
22
#                    StripDir                   - Return file + extension
23
#                    StripExt                   - Return dir + file
24
#                    StripFile                  - Returns extension
25
#                    StripFileExt               - Returns directory
26
#                    StripDirExt                - Returns filename ( with optional ext)
27
#                    CleanDirName               - Clean up a path
261 dpurdie 28
#                    TouchFile                  - Touch a file
29
#                    FileIsNewer                - Test if newer
5486 dpurdie 30
#                    DisplayPath                - Generate a Path that can be displayed
261 dpurdie 31
#                    FileCreate                 - Create a simple text file
32
#                    FileAppend                 - Append to a simple text file
6133 dpurdie 33
#                    TagFileMatch               - Simple (oneline) file content matcher
34
#                    TagFileRead                - Return the contents of the tagfile
379 dpurdie 35
#                    RmDirTree                  - Remove a directory tree
6276 dpurdie 36
#                    CatPaths                   - Concatenate Paths            
6415 dpurdie 37
#                    ValidatePath               - Validate directory is sane and within package
4421 dpurdie 38
#           ReExported
39
#                    catdir                     - Concatenate path elements
40
#                    catfile                    - Concatenate path elements and a file
7352 dpurdie 41
#                    mkpath                     - Create path elements
227 dpurdie 42
#
43
#......................................................................#
44
 
255 dpurdie 45
use 5.006_001;
227 dpurdie 46
use strict;
47
use warnings;
48
 
49
################################################################################
50
#   Global variables used by functions in this package
51
#   For historical reasons many of these variabeles are global
52
#
53
 
54
package FileUtils;
283 dpurdie 55
use base qw(Exporter);
361 dpurdie 56
use File::Path;
4309 dpurdie 57
use File::Spec::Functions;
283 dpurdie 58
 
227 dpurdie 59
use JatsError;
60
use Cwd;
283 dpurdie 61
our (@EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION);
227 dpurdie 62
 
63
$VERSION = 1.00;
64
 
65
# Symbols to autoexport (:DEFAULT tag)
66
@EXPORT = qw(   InitFileUtils
67
                Getcwd
68
                Realpath
69
                RelPath
70
                AbsPath
71
                FullPath
72
                CleanPath
73
                StripDrive
74
                StripDir
75
                StripExt
76
                StripFile
77
                StripFileExt
78
                StripDirExt
79
                CleanDirName
80
                TouchFile
81
                FileIsNewer
82
                DisplayPath
257 dpurdie 83
                TruePath
261 dpurdie 84
                FileCreate
85
                FileAppend
6133 dpurdie 86
                TagFileMatch
87
                TagFileRead
361 dpurdie 88
                RmDirTree
6276 dpurdie 89
                CatPaths
4309 dpurdie 90
                catfile
91
                catdir
7352 dpurdie 92
                mkpath
6415 dpurdie 93
                ValidatePath
227 dpurdie 94
 
95
                $ScmPathSep
261 dpurdie 96
                $ScmDirSep
227 dpurdie 97
                $Cwd
6276 dpurdie 98
                $CwdFull
227 dpurdie 99
                $CwdDrive
100
                $ScmHost
101
            );
102
#
103
# exported package globals go here
104
#
105
our $ScmPathSep;                # Windows/Unix path seperator
261 dpurdie 106
our $ScmDirSep;                 # Windows/Unix dir sep
227 dpurdie 107
our $Cwd       ;                # Current directory ( no drive letter )
247 dpurdie 108
our $CwdFull   ;                # Current directory ( with drive letter )
227 dpurdie 109
our $CwdDrive  ;                # Current drive
110
our $ScmHost   ;                # Host Type. Unix, WIN
111
 
112
#
113
#   Internal variables
114
#
4546 dpurdie 115
our  $isCygWin;                 # Running under CygWin
116
our  $isUnix;                   # Is Unix
227 dpurdie 117
 
118
#-------------------------------------------------------------------------------
119
# Function        : BEGIN
120
#
121
# Description     : Determine some values very early
122
#
123
#
124
BEGIN
125
{
126
    $ScmHost = "Unix";                                # UNIX, default
127
 
128
    Debug( "PerlHost:  $^O" );
129
    $ScmHost = "DOS"      if ($^O eq "win95");        # DOS Perl dependent
130
    $ScmHost = "WIN"      if ($^O eq "MSWin32");      # ActivePerl
131
    $ScmHost = "WIN"      if ($^O eq "cygwin");       # Cygwin
132
 
4546 dpurdie 133
    $isUnix = ( $ScmHost eq "Unix"  ) ? 1 : 0;
134
    $isCygWin = ( $ENV{'SHELL'} || $ENV{'CYGWIN'} ) ? 1 : 0;
227 dpurdie 135
 
136
    $ScmPathSep = $isUnix ? ':' : ';';     # Unix / Windows
261 dpurdie 137
    $ScmDirSep = $isUnix ? '/' : '\\';     # Unix / Windows
227 dpurdie 138
}
139
 
140
#-------------------------------------------------------------------------------
141
# Function        : InitFileUtils
142
#
143
# Description     : Initialise this package
144
#                   This function should be called once the user has determined
145
#                   settled on a working directory
146
#
147
#                   The function may be called multiple times
148
#                   to allow various globals to be reset - when the user has
149
#                   changed directory
150
#
151
# Inputs          : Nothing
152
#
153
# Returns         : Nothing
154
#
155
sub InitFileUtils
156
{
157
    #
158
    #   Setup current directory and drive
159
    #
160
 
247 dpurdie 161
    $CwdFull = Getcwd();                    # Current working dir
227 dpurdie 162
 
163
    $CwdDrive = '';
247 dpurdie 164
    $CwdDrive = substr( $CwdFull, 0, 2 )    # Saved Drive letter
227 dpurdie 165
        if ( ! $isUnix );
166
 
247 dpurdie 167
    $Cwd = StripDrive( $CwdFull );          # With drive spec striped
227 dpurdie 168
 
169
    Debug ("InitFileUtils: ScmHost     : $ScmHost");
247 dpurdie 170
    Debug ("InitFileUtils: CwdFull     : $CwdFull");
227 dpurdie 171
    Debug ("InitFileUtils: Cwd         : $Cwd");
172
    Debug ("InitFileUtils: CwdDrive    : $CwdDrive");
173
    Debug ("InitFileUtils: ScmPathSep  : $ScmPathSep");
174
}
175
 
176
 
177
#-------------------------------------------------------------------------------
178
# Function        : Getcwd
179
#
180
# Description     : Retrieve current working directory
181
#
182
# Inputs          : None
183
#
184
# Returns         : The current working directory
185
#
186
# Notes           : Don't use 'pwd' program as it gets symbolic links wrong
187
#
188
sub Getcwd
189
{
190
    my $cwd = getcwd();
191
    return $cwd;
192
}
193
 
194
#-------------------------------------------------------------------------------
261 dpurdie 195
# Function        : TouchFile 
227 dpurdie 196
#
197
# Description     : touch a file
198
#                   Real use is to touch a marker file
199
#
200
# Inputs          : path        - path to the file
201
#
202
# Returns         : TRUE if an error occured in creating the file
203
#
204
sub TouchFile
205
{
206
    my ($path, $text) = @_;
207
    my $result = 0;
283 dpurdie 208
    my $tfh;
209
 
227 dpurdie 210
    Verbose ("Touching file: $path" );
211
    if ( ! -f $path )
212
    {
283 dpurdie 213
        open ($tfh, ">>", $path) || ($result = 1);
214
        close $tfh;
227 dpurdie 215
    }
216
    else
217
    {
218
 
219
        #
220
        #   Modify the file
221
        #
222
        #   Need to physically modify the file
223
        #   Need to change the 'change time' on the file. Simply setting the
224
        #   last-mod and last-access is not enough to get past WIN32
225
        #   OR 'utime()' does not work as expected
226
        #
227
        #   Read in the first character of the file, rewind and write it
228
        #   out again.
229
        #
230
        my $data;
283 dpurdie 231
        open ($tfh , "+<", $path ) || return 1;
232
        if ( read ( $tfh, $data, 1 ) )
233
        {
234
            seek  ( $tfh, 0, 0 );
235
            print $tfh $data;
227 dpurdie 236
        }
237
        else
238
        {
239
            #
240
            #   File must have been of zero length
241
            #   Delete the file and create it
242
            #
283 dpurdie 243
            close ($tfh);
227 dpurdie 244
            unlink ( $path );
283 dpurdie 245
            open ($tfh, ">>", $path) || ($result = 1);
227 dpurdie 246
        }
283 dpurdie 247
        close ($tfh);
227 dpurdie 248
    }
249
    return $result;
250
}
251
 
252
#-------------------------------------------------------------------------------
261 dpurdie 253
# Function        : FileCreate
254
#                   FileAppend
255
#                   _FileWrite
256
#
257
# Description     : Simple Text File Creation function
258
#                   Suited to the creation of small, simple text files.
259
#
260
# Inputs          : Name of the file
261
#                   Remainder are:
262
#                       Lines of data to output to the file
263
#                       Or a reference to an array of lines
264
#                       Or a mixture
265
#                   All lines will be terminated with a "\n"
266
#
267
# Returns         : Nothing
268
#
269
sub FileCreate
270
{
271
    _FileWrite ( '>', @_ );
272
}
273
 
274
sub FileAppend
275
{
276
    _FileWrite ( '>>', @_ );
277
}
278
 
279
sub _FileWrite
280
{
281
    my $mode = shift @_;
282
    my $name = shift @_;
283 dpurdie 283
    my $fh;
261 dpurdie 284
 
285
    Error ("FileCreate: No file specified") unless ( $name );
6276 dpurdie 286
    Error ("FileCreate: Path is directory", 'Path :' . $name) if ( -d $name );
261 dpurdie 287
 
283 dpurdie 288
    open  ($fh, $mode, $name ) || Error( "Cannot create file: $name", "Reason: $!" );
261 dpurdie 289
 
290
    foreach my $entry ( @_ ) {
291
        if ( ref ($entry ) eq 'ARRAY'  ) {
283 dpurdie 292
            print $fh $_ . "\n" foreach  ( @$entry );
261 dpurdie 293
        } else {
283 dpurdie 294
            print $fh $entry . "\n"
261 dpurdie 295
        }
296
    }
283 dpurdie 297
    close $fh;
261 dpurdie 298
}
299
 
300
#-------------------------------------------------------------------------------
6133 dpurdie 301
# Function        : TagFileMatch 
302
#
303
# Description     : Test the contents of a simple (one line) file against a string    
304
#
305
# Inputs          : $tfile      - Name of the tag file
306
#                   $tag        - Tag to match 
307
#
308
# Returns         : True - is a match
309
#
310
sub TagFileMatch
311
{
312
    my ($tfile, $tag) = @_;
313
    return 0 unless -f $tfile;
314
 
315
    open( my $file, '<', $tfile) || return 0;
316
    my $text = <$file>;
317
    close $file;
318
 
319
    $text = '' unless defined ($text);
320
    # Remove trailing new line and white space
321
    $text =~ s~\s*$~~;
322
    Debug("TagFileMatch:'$text':'$tag'", $text eq $tag );
323
 
324
    return $text eq $tag;
325
}
326
 
327
#-------------------------------------------------------------------------------
328
# Function        : TagFileRead
329
#
330
# Description     : Read the contents of a simple (one line) file against a string    
331
#
332
# Inputs          : $tfile      - Name of the tag file
333
#
334
# Returns         : One line of the file
335
#
336
sub TagFileRead
337
{
338
    my ($tfile) = @_;
339
    return "" unless -f $tfile;
340
 
341
    open( my $file, '<', $tfile) || Error("Cannot open '$tfile'. $!");
342
    my $text = <$file>;
343
    close $file;
344
 
345
    $text = '' unless defined ($text);
346
    $text =~ s~\s*$~~;
347
    return $text;
348
}
349
 
350
#-------------------------------------------------------------------------------
227 dpurdie 351
# Function        : FileIsNewer
352
#
353
# Description     : Test two files to see if the files are newer
354
#
355
# Inputs          : file1
356
#                   file2
357
#
358
# Returns         : Returns true if file1 is newer than file2 or file2 does not
359
#                   exist.
360
#
361
#                   If file 1 does not exist then it will return false
362
#
363
sub FileIsNewer
364
{
365
    my ($file1, $file2) = @_;
366
 
367
    my $f1_timestamp = (stat($file1))[9] || 0;
368
    my $f2_timestamp = (stat($file2))[9] || 0;
369
    my $result = $f1_timestamp > $f2_timestamp ? 1 : 0;
370
 
371
    Verbose2 ("FileIsNewer: TS: $f1_timestamp, File: $file1");
372
    Verbose2 ("FileIsNewer: TS: $f2_timestamp, File: $file2");
373
    Verbose2 ("FileIsNewer: $result" );
374
 
375
    return $result;
376
}
377
 
378
#-------------------------------------------------------------------------------
379
# Function        : Realpath
380
#
381
# Description     : Returns the 'real path'
382
#
383
# Inputs          : $path       - Path to process
384
#
385
# Returns         : The real path
386
#
387
sub Realpath
388
{
389
    my( $path ) = @_;
390
    my( $real, $cwd );
391
 
392
    $cwd = Getcwd();
393
    if (!chdir( $path )) {
394
        $real = "";
395
    } else {
396
        $real = Getcwd();
397
        Error ("FATAL: Realpath($path) could not restore directory ($cwd)." )
398
            unless (chdir( $cwd ));
399
    }
400
    Debug( "Realpath:   = $real ($path)" );
401
    return $real;
402
}
403
 
404
#-------------------------------------------------------------------------------
405
# Function        : Realfile
406
#
407
# Description     : Returns the 'real path'
408
#
409
# Inputs          : $path       - Path to process
410
#
411
# Returns         : The real path
412
#
413
#sub Realfile
414
#{
415
#    my( $path ) = @_;
416
#    my( $real, $cwd );
417
#
418
#    $cwd = Getcwd();
419
#    if (!chdir( $path )) {
420
#        $real = "";
421
#    } else {
422
#        $real = Getcwd();
423
#        Error ("FATAL: Realpath($path) could not restore directory ($cwd)." )
424
#            unless (chdir( $cwd ));
425
#    }
426
#    Debug( "Realpath:   = $real ($path)" );
427
#    return $real;
428
#}
429
 
430
#-------------------------------------------------------------------------------
431
# Function        : RelPath
432
#
433
# Description     : Return the relative path to the current working directory
434
#                   as provided in $Cwd
435
#
285 dpurdie 436
# Inputs          : $base       - Base directory to convert
4421 dpurdie 437
#                                 Expected to be well formed absolute path
285 dpurdie 438
#                   $here       - Optional current directory
4421 dpurdie 439
#                                 Expected to be well formed absolute path
285 dpurdie 440
#                                 $Cwd will be used if non provided
227 dpurdie 441
#
442
# Returns         : Relative path from the current directory to the base directory
443
#
444
sub RelPath
445
{
285 dpurdie 446
    my ($base, $here) = @_;
6276 dpurdie 447
    unless (defined $base)
448
    {
449
        DebugTraceBack ('RelPath');
450
        Error ("Internal: 'RelPath(). base not defined'");
451
    }
227 dpurdie 452
 
285 dpurdie 453
    $here = $Cwd unless ( defined $here );
6276 dpurdie 454
    $here = $CwdDrive . $here if ( $base =~ m~^\w+:/~ && $here !~ m~^\w+:/~);
227 dpurdie 455
    my @base = split ('/', $base );
285 dpurdie 456
    my @here = split ('/', $here );
227 dpurdie 457
    my $result;
458
 
4265 dpurdie 459
    Debug("RelPath: Here  : $here");
227 dpurdie 460
    Debug("RelPath: Source: $base");
461
 
4421 dpurdie 462
    # Not absolute - just return it
6133 dpurdie 463
    return $base unless ( $base =~ m~^/~ || $base =~ m~^\w+:/~ );
464
 
227 dpurdie 465
    #
466
    #   Remove common bits from the head of both lists
467
    #
468
    while ( $#base >= 0 && $#here >= 0 && $base[0] eq $here[0] )
469
    {
470
        shift @base;
471
        shift @here;
472
    }
473
 
474
    #
475
    #   Need to go up some directories from here and then down into base
476
    #
477
    $result = '../' x ($#here + 1);
478
    $result .= join ( '/', @base);
479
    $result = '.' unless ( $result );
480
    $result =~ s~/$~~;
481
 
482
    Debug("RelPath: Result: $result");
483
    return $result;
484
}
485
 
486
#-------------------------------------------------------------------------------
487
# Function        : AbsPath
488
#
489
# Description     : Return the absolute path to the file
490
#                   Add the current directory if the path is absolute
491
#                   Clean up xxx/.. constructs
492
#
493
#                   If an absolute path is provided then it will simply be
494
#                   cleaned up.
495
#
496
# Assumption      : Absolute paths start with a "/" and do not have a drive letter
497
#
2450 dpurdie 498
# Inputs          : $dpath      - Source file path
325 dpurdie 499
#                   $here       - Optional current directory
500
#                                 $Cwd will be used if non provided
2450 dpurdie 501
#                   $mode       - Defined: No error
502
#                                 Used during error reporting
227 dpurdie 503
#
504
# Returns         : Cleaned abs path
505
#
506
sub AbsPath
507
{
2450 dpurdie 508
    my ($dpath, $here, $mode) = @_;
227 dpurdie 509
    my @result;
6276 dpurdie 510
    unless (defined $dpath)
511
    {
512
        DebugTraceBack ();
513
        Error ("Internal: 'AbsPath(). dpath not defined'");
514
    }
227 dpurdie 515
 
516
    #
517
    #   If we have a relative path then prepend the current directory
518
    #   An absolute path is:
519
    #           /aaa/aa/aa
520
    #       or  c:/aaa/aa/aa
521
    #
325 dpurdie 522
    $here = $Cwd unless ( defined $here );
3859 dpurdie 523
    $here =~ s~^\w:~~;
325 dpurdie 524
    $dpath = $here . '/' . $dpath
3832 dpurdie 525
        unless ( $dpath =~ m~^/|\w:[/\\]~  );
227 dpurdie 526
    $dpath =~ s~//~/~g;
527
 
528
    #
529
    #   Walk the bits and remove ".." directories
530
    #       Done by pushing non-.. elements and poping last entry for .. elements.
531
    #   Have a leading "/" which is good.
532
    #
533
    foreach ( split ( '/', $dpath ) )
534
    {
535
        next if ( $_ eq '.' );
536
        unless ( $_ eq '..' )
537
        {
538
            push @result, $_;
539
        }
540
        else
541
        {
2450 dpurdie 542
            if ( $#result <= 0 )
543
            {
544
                Error ("Bad Pathname: $dpath") unless ( $mode );
545
                return $dpath;
546
            }
547
            else
548
            {
549
                pop @result;
550
            }
227 dpurdie 551
        }
552
    }
553
 
554
    #
555
    #   Create a nice directory name again.
556
    #
557
    return join ( '/', @result );
558
}
559
 
560
#-------------------------------------------------------------------------------
561
# Function        : FullPath
562
#
563
# Description     : Return the absolute path to the file - with driver letter
564
#                   Add the current directory if the path is absolute
565
#                   Clean up xxx/.. constructs
566
#
567
#                   If an absolute path is provided then it will simply be
568
#                   cleaned up.
569
#
570
# Inputs          : Source file path
325 dpurdie 571
#                   $here       - Optional current directory
572
#                                 $Cwd will be used if non provided
227 dpurdie 573
#
574
# Returns         : Cleaned abs path
575
#
576
sub FullPath
577
{
578
    my $path = AbsPath (@_ );
229 dpurdie 579
    $path = $CwdDrive . $path unless ( $path =~ m~^\w:~  );
227 dpurdie 580
    return $path;
581
}
582
 
583
#-------------------------------------------------------------------------------
257 dpurdie 584
# Function        : TruePath
585
#
586
# Description     : Returns a case correct pathname
587
#                   Really only applicable to windows, under unix it returns
588
#                   its input path.
589
#
590
#                   Maintains a cache to speed up processing
591
#
592
# Inputs          : Confused path (Absolute with a driver letter)
593
#
594
# Returns         : Case Correct Path : Windows
595
#                   Input Path : Non Windows
596
#
597
my %TruePathCache;
598
my %DirRead;
599
sub TruePath
600
{
601
    my ($path) = @_;
6276 dpurdie 602
    Debug("TruePath: $path");
257 dpurdie 603
    $path =~ tr~\\/~/~s;
604
 
605
    #
606
    #   On Unix systems the path is case sensitive to start with
607
    #   Can't get it wrong - can't do anything.
608
    #
609
    return $path if ( $isUnix );
610
 
611
    #
612
    #   If the path does not exist at all then return the user input
613
    #   Assume that the user will handle this later
614
    #
615
    unless ( -e $path )
616
    {
617
        Warning ("TruePath given invalid path: $path");
618
        return $path;
619
    }
620
 
621
    #
622
    #   Look in the cache - have we seen this before
623
    #
624
    if ( exists $TruePathCache{lc($path)} )
625
    {
5969 dpurdie 626
        Verbose2( "TruePath Cache Hit: $path");
257 dpurdie 627
        return $TruePathCache{lc($path)};
628
    }
629
 
630
    #
631
    #   Split the directory into components
632
    #
633
    my $TrueComponent = '';
634
    my @components = split ('/', $path );
635
    foreach my $elem ( @components )
636
    {
637
        Debug ("Process: $elem in $TrueComponent");
638
        my $tag;
639
        #
640
        #   Handle driver letter
641
        #
642
        if ( $elem =~ m~^[a-zA-Z]:$~ )
643
        {
644
            $elem = uc($elem);
645
            $TrueComponent = $elem;
646
 
647
            $tag = lc($TrueComponent);
648
            $TruePathCache{$tag} = $elem;
649
            Debug ("     Add: $elem");
650
            next;
651
        }
652
 
653
        #
654
        #   Ensure that we have read in containing directory
655
        #   Note: Append / to ensure we read root directories correctly
656
        #
657
        $TrueComponent .= '/';
658
        unless ( $DirRead{ $TrueComponent }  )
659
        {
660
            Debug ("Reading: $TrueComponent");
285 dpurdie 661
            opendir (my $tp, $TrueComponent ) or Error ("Cannot open $TrueComponent");
662
            my @dirlist = readdir $tp;
663
            closedir $tp;
257 dpurdie 664
            $DirRead {$TrueComponent } = 1;
665
 
666
            #
667
            #   Add cache entries for each path in the directory
668
            #
669
            foreach my $dir ( @dirlist )
670
            {
671
                next if ( $dir eq '.' );
672
                next if ( $dir eq '..' );
673
                my $fullpath = $TrueComponent . $dir;
674
                Debug ("     Add: $fullpath");
675
                $TruePathCache{lc($fullpath)} = $fullpath;
676
            }
677
        }
678
 
679
        #
680
        #   Now that we have populated the cache with data from the directory
681
        #   we can expect to find our desired entry in the cache.
682
        #
683
        $tag = lc($TrueComponent . $elem );
684
        if ( exists $TruePathCache{ $tag } )
685
        {
686
            $TrueComponent = $TruePathCache{ $tag };
687
        }
688
        else
689
        {
690
            DebugDumpData ("Cache", \%TruePathCache);
691
            Error ("TruePath Internal error. File may have been deleted: $tag");
692
        }
693
        Debug ("Have: $TrueComponent");
694
    }
695
 
5969 dpurdie 696
    Verbose2 ("TruePath: $TrueComponent");
257 dpurdie 697
    return $TrueComponent;
698
}
699
 
700
#-------------------------------------------------------------------------------
227 dpurdie 701
# Function        : CleanPath
702
#
703
# Description     : Cleanup a path
704
#                   Remove xxx/.. constructs
6133 dpurdie 705
#                   Replace /./ constructs with /
227 dpurdie 706
#
707
# Note            : Will not perform error detection on badly formed
708
#                   absolute paths.
709
#
710
# Inputs          : Source file path
711
#
712
# Returns         : Clean absolute or relative path
713
#
714
#
715
sub CleanPath
716
{
717
    my ($dpath) = @_;
718
    my @result;
719
    Debug("CleanPath: Source: $dpath");
720
 
721
    #
722
    #   Cleanup the the user input. Remove double delimiters and ensure there
6276 dpurdie 723
    #   is no trailing delimiter
227 dpurdie 724
    #
6276 dpurdie 725
    $dpath =~ s~\\~/~g;
726
    $dpath =~ s~^\./~~g;
727
    $dpath =~ s~/\./~/~g;
245 dpurdie 728
    $dpath =~ s~/+~/~g;
227 dpurdie 729
    $dpath =~ s~/$~~g;
730
 
731
    #
732
    #   Walk the bits and remove "xxx/.." directories
733
    #
734
    foreach ( split ( '/', $dpath ) )
735
    {
736
        if ( $_ ne '..' || $#result < 0 )
737
        {
738
            push @result, $_;
739
        }
740
        else
741
        {
742
            if ( $#result >= 0 )
743
            {
283 dpurdie 744
                my $last_dir = pop @result;
745
                push (@result, $last_dir, $_)
746
                    if ($last_dir eq '..' || $last_dir eq '');
227 dpurdie 747
            }
748
        }
749
    }
750
 
751
    my $result = join ( '/', @result );
6276 dpurdie 752
    $result = '.' unless $result;
227 dpurdie 753
    Debug("CleanPath: Result: $result");
754
    return $result;
755
}
756
 
757
#-------------------------------------------------------------------------------
6276 dpurdie 758
# Function        : CatPaths 
759
#
760
# Description     : Join path elemanets together with a '/'
761
#                   Clean up the result
762
#
763
# Inputs          : Patth elemenst to join    
764
#
765
# Returns         : Cleaned up path elements
766
#
767
sub CatPaths
768
{
6423 dpurdie 769
    my @paths = grep defined , @_; 
770
    Debug("CatPaths: ", @paths );
771
    return CleanPath join ('/', @paths );
6276 dpurdie 772
}
773
 
774
#-------------------------------------------------------------------------------
227 dpurdie 775
# Function        : StripDrive
776
#
777
# Description     : Strip any leading drive speification
778
#
779
# Inputs          : $fname          - Path to process
780
#
781
# Returns         : Path, with drive letter stripped
782
#                   Will do nothing on Unix systems
783
#
784
sub StripDrive
785
{
786
    my( $fname ) = @_;                          # Full name
787
 
788
    $fname =~ s/^[A-Za-z]://g                  # leading drive spec
789
        if ( ! $isUnix );
790
    return $fname;
791
}
792
 
793
#-------------------------------------------------------------------------------
794
# Function        : StripDir
795
#
796
# Description     : Strip directory (returns file, including extension)
797
#
798
# Inputs          : $fname          - Path to process
799
#
800
# Returns         : filename + extension
801
#
802
sub StripDir
803
{
804
    my( $fname ) = @_;                          # Full name
805
    my( $idx );
806
 
807
    if (($idx = rindex($fname, "/")) == -1) {
808
        if (($idx = rindex($fname, "\\")) == -1) {
809
            return $fname;                      # No path ...
810
        }
811
    }
812
    return substr($fname, $idx+1, 512);
813
}
814
 
815
#-------------------------------------------------------------------------------
816
# Function        : StripExt
817
#
818
# Description     : Strip extension (return basename, plus any dir)
819
#
820
# Inputs          : $fname          - Path to process
821
#
822
# Returns         : basename, plus any dir
823
#                   Simply removes one extension
824
#
825
sub StripExt
826
{
827
    my( $fname ) = @_;
828
 
829
    $fname =~ s/(\S+)(\.\S+)/$1/;               # strip out trailing '.<ext>'
830
    return ($fname);
831
}
832
 
833
#-------------------------------------------------------------------------------
834
# Function        : StripFile
835
#
836
# Description     : Strip filename (returns extension)
837
#
838
# Inputs          : $fname          - Path to process
839
#
840
# Returns         : extension
841
#                   Will return an empty string if the input does not have an
842
#                   extension.
843
#
844
sub StripFile
845
{
846
    my( $fname ) = @_;
847
 
848
    $fname =~ s/(\S+)(\.\S+)/$2/;               # Strip out items before '.<ext>'
849
    return ("")                                 # No extension
850
        if ("$fname" eq "@_");
851
    return ($fname);
852
}
853
 
854
#-------------------------------------------------------------------------------
855
# Function        : StripFileExt
856
#
857
# Description     : Strip filename and ext (returns dir)
858
#
859
# Inputs          : $fname          - Path to process
860
#
861
# Returns         : Directory of a file path
862
#
863
 
864
 
865
#   StripFileExt( path ) ---
866
#       Strip filename and ext (returns dir)
867
#..
868
 
869
sub StripFileExt
870
{
871
    my( $fname ) = @_;                          # Full name
872
    my( $idx );
873
    my $dir;
874
 
875
    if (($idx = rindex($fname, "/")) == -1) {
876
        if (($idx = rindex($fname, "\\")) == -1) {
877
            return "";                          # No path ...
878
        }
879
    }
880
 
881
    return substr($fname, 0, $idx);
882
}
883
 
884
#-------------------------------------------------------------------------------
885
# Function        : StripDirExt
886
#
887
# Description     : Strip the directory and extension from a file
888
#                   Returning the base file. Optionally replace the extension
889
#                   with a user value
890
#
891
# Inputs          : Full path name
892
#                   Optional extension to be replaced
893
#
894
# Returns         :
895
#
896
sub StripDirExt
897
{
898
    my ($fname, $ext ) = (@_, '');
899
    $fname =~ s~.*[/\\]~~;                      # Strip directory
900
    $fname =~ s/\.[^.]+$/$ext/;
901
    return $fname;
902
}
903
 
904
 
905
#-------------------------------------------------------------------------------
906
# Function        : CleanDirName
907
#
908
# Description     : Clean up a directory path string
909
#                       1) Remove multiple //
910
#                       2) Remove multiple /./
911
#                       2) Remove leading ./
912
#                       3) Remove trailing /
913
#                       4) Remove /xxxx/../
914
#
915
# Inputs          : A dirty directory path
916
#
917
# Returns         : A clean directory path
918
#
919
sub CleanDirName
920
{
921
    my ( $dir ) = @_;
922
    $dir =~ s~//~/~g;                   # Kill multiple //
923
    $dir =~ s~/\./~/~g;                 # Kill multiple /./
924
    $dir =~ s~^\./~~;                   # Kill leading ./
925
    $dir = '.' unless ( $dir );         # Ensure we have a path
926
 
927
    #
928
    #   Remove /xxxxx/../ bits
929
    #
930
    unless ( $dir =~ m~^\.\./~  )
931
    {
932
        while ( $dir =~ s~
933
                        (^|/)               # Allow for stings that may not start with a /
934
                        [^/]+/\.\.          # xxxxx/.., where xxxx is anything other than a /
935
                        (/|$)               # Allow for strings ending with /..
936
                        ~$1~x               # Replace with the start character
937
              )
938
        {
939
            last if ( $dir =~ m~^\.\./~ );  # Too far. Stop now !
940
        }
941
    }
942
 
943
    $dir =~ s~/$~~;                     # No trailing /
944
    $dir =~ s~/\.$~~;                   # No trailing /.
945
    return $dir;
946
}
947
 
948
#-------------------------------------------------------------------------------
949
# Function        : DisplayPath
950
#
951
# Description     : Cleanup a path for display purposes
952
#                   Useful under windows to provide paths with \ that can be
953
#                   cut and pasted.
954
#
955
#                   If cygwin is located in the environment, then this function
956
#                   will not convert / to \.
957
#
958
# Inputs          : A path to modify
959
#
960
# Returns         : Modified path
961
#
962
sub DisplayPath
963
{
964
    my ($path) = @_;
965
    if ( ! $isUnix && ! $isCygWin )
966
    {
967
        $path =~ s~/~\\~g;
968
    }
4546 dpurdie 969
    else
970
    {
971
        $path =~ s~\\~/~g;
972
    }
227 dpurdie 973
    return $path;
974
}
975
 
361 dpurdie 976
#-------------------------------------------------------------------------------
977
# Function        : RmDirTree
978
#
979
# Description     : Delete a directory tree
980
#                   Really delete it. Allow for users to remove directory
981
#                   without search permissions under unix.
982
#
983
#                   Can also delete a file
984
#
2439 dpurdie 985
#                   This function has a bit of history
986
#                   I've tried the Perl rmtree(), but there were situations
987
#                   where the OS(WIN32) says the directory exists after its been
988
#                   deleted. Also the Jats-Win32 version of chmod would issue
989
#                   messages if it couldn't find the dir/file.
990
#
991
#                   The solution is to use JATS' own JatsFileUtil utility
992
#                   This appears to do the right thing
993
#
361 dpurdie 994
# Inputs          : $path                   - Path to directory
4344 dpurdie 995
#                                             May be empty, in which case nothing is done
361 dpurdie 996
#
997
# Returns         : 1                       - Still there
998
#
999
sub RmDirTree
1000
{
1001
    my ($path) = @_;
4344 dpurdie 1002
    return 0 unless $path;
361 dpurdie 1003
    if ( -e $path )
1004
    {
2439 dpurdie 1005
        #  Need to know if its a file or a directory
1006
        #
1007
        my $mode = ( -d $path ) ? 'T' : 'r';
1008
 
1009
        #
1010
        #   Use JATS's own utility to do the hardwork
1011
        #   Used as it address a number of issues
1012
        #
1013
        #   Merge in verbosity
1014
        #
1015
        system ("$ENV{GBE_BIN}/JatsFileUtil", $mode . $::ScmVerbose, '', $path );
1016
 
1017
        #
1018
        #   Shouldn't happen but ...
1019
        #   If the path still exists try another (one this has known problems)
1020
        #
1021
        if ( -e $path )
1022
        {
1023
            Verbose3 ("RmDirTree: Directory still exists. Change permissions: $path");
1024
            system ("$ENV{GBE_BIN}/chmod", '-R', 'u+wrx', $path);
5568 dpurdie 1025
            eval { rmtree( $path ); };
2439 dpurdie 1026
        }
361 dpurdie 1027
    }
1028
    return ( -e $path );
1029
}
1030
 
6415 dpurdie 1031
#-------------------------------------------------------------------------------
1032
# Function        : ValidatePath  
1033
#
1034
# Description     : Ensure that the user provided path does not escape the current
1035
#                   package and is sane
1036
#
1037
# Inputs          : $path       - One path to validate 
1038
#                   $mode       - 0 : No sanity test (only escape test)
1039
#                                 1 : Abs path not allowed
1040
#                                 2 : Parent directory not allowed
1041
#                                 4 : Path must exist
1042
#                                 Mode options are bit mask and may be combined
1043
#
1044
# Returns         : Array:
1045
#                       - Clean pathname (unless error)
1046
#                       - Error message
1047
#
1048
sub ValidatePath
1049
{
1050
    my ($path, $mode) = @_;
1051
    Error("Internal: ValidatePath. ProjectBase not known" ) unless defined $::ProjectBase;
6423 dpurdie 1052
    Debug("ValidatePath. $::ProjectBase, $path, $mode");
6415 dpurdie 1053
 
1054
    my $errPath = $path;
1055
 
6423 dpurdie 1056
    $path = CleanPath($path);
6415 dpurdie 1057
    if ($mode & 1 && $path =~ m~^/~ ) {
1058
        return $errPath, 'Absolute path not allowed';
1059
    }
1060
    $path =~ s~^/~~;
1061
 
1062
    if ($mode & 2 && $path =~ m~^[./]+$~ ) {
1063
        return $errPath, 'Parent directory not allowed';
1064
    }
1065
 
1066
    if ($mode & 4 && ! -d $path ) {
1067
        return $errPath, 'Directory does not exist';
1068
    }
1069
 
1070
    my $dirFromBase = RelPath(AbsPath($path), AbsPath($::ProjectBase));
1071
    if ( $dirFromBase =~ m~\.\.~ ) {
6423 dpurdie 1072
        Debug("ProjectBase:", AbsPath($::ProjectBase));
1073
        Debug("User Path  :", AbsPath($path));
6415 dpurdie 1074
        return $errPath, 'Path outside the current package';
1075
    }
1076
    return $path;
1077
}
1078
 
227 dpurdie 1079
1;
1080
 
1081