File Coverage

blib/lib/VMS/FileUtils/Root.pm
Criterion Covered Total %
statement 15 208 7.2
branch 0 90 0.0
condition 0 46 0.0
subroutine 5 16 31.2
pod 1 10 10.0
total 21 370 5.6


line stmt bran cond sub pod time code
1             # Convert directory paths to/from rooted logical paths to evade
2             # VMS's directory depth limitations.
3             #
4             # Version: 0.012
5             # Author: Charles Lane lane@duphy4.physics.drexel.edu
6             # Revised: 4 Mar 2012
7             ##### remove non-vms usage
8             #
9              
10             =head1 NAME
11              
12             VMS::FileUtils::Root - convert directory paths to and from rooted logicals
13             (only works on VMS systems)
14              
15             =head1 SYNOPSIS
16              
17             use VMS::FileUtils::Root;
18              
19             $r = new VMS::FileUtils::Root 'disk:[dir1.dir2]';
20              
21             $path = $r->rooted('disk:[dir1.dir2.dir3]');
22              
23             $path = $r->unrooted('ROOTDIR_1:[dir3]');
24              
25             =head1 DESCRIPTION
26              
27             This module creates and uses rooted logical names in the /job logical
28             name table. This only works on VMS systems.
29              
30             =head2 new
31              
32             The directory path specified can be either in VMS or Unix format; and
33             can be either absolute (disk:[dir] or /disk/dir) or relative to the
34             current directory ([.dir] or dir/dir2). A blank or missing path is
35             taken to be the current directory, and a simple name (without any slashes
36             or brackets) is taken to be a subdirectory name.
37              
38             =head2 to_rooted
39              
40             This routine converts a directory path into a rooted directory path,
41             and returns the result in Unix format (without trailing '/', so
42             it can be used in chdir).
43              
44             The input directory path can be absolute, or relative based on the
45             current directory. Input can be either VMS or Unix format.
46              
47             =head2 from_rooted
48              
49             This routine converts a rooted directory path to an unrooted equivalent
50             path and returns the result in Unix format (without trailing '/').
51              
52             Note that if you input a relative path, it will be taken as relative
53             to the rooted directory. Input can be either VMS or Unix format.
54              
55             The output of this routine may contain more than 8 directory levels
56             and hence not be directly usable on VMS without conversion to a rooted
57             path.
58              
59             =cut
60              
61             package VMS::FileUtils::Root;
62 1     1   586 use File::Spec;
  1         2  
  1         22  
63 1     1   5 use Cwd;
  1         2  
  1         77  
64 1     1   5 use Carp;
  1         4  
  1         77  
65 1     1   6 use strict;
  1         2  
  1         34  
66 1     1   5 use vars qw($VERSION $ROOTNUM $Pnode $Pdev $Pdir $Pname $Ptype $Pvers);
  1         1  
  1         2819  
67              
68             $VERSION = '0.012';
69              
70             sub new {
71 0     0 1   my $pkg = shift;
72 0   0       $pkg = ref($pkg) || $pkg;
73 0           my $b = shift;
74 0           my $self = {};
75 0           bless $self, $pkg;
76              
77              
78 0           $self->{O_LOWER} = 1; # all lowercase
79 0           $self->{O_LOCAL} = 1; # strip any node info
80 0           $self->{O_LOCALONLY} = 1; # warn if remote node
81 0           $self->{O_DIRONLY} = 1;
82 0           $b = $self->expander($b);
83 0           $self->{O_DIRONLY} = 0;
84              
85 0           my(@d) = split('/',$b);
86 0 0         pop @d if ($d[$#d] =~ /[\.\;]/);
87              
88 0           while ($#d > 8) { pop @d }
  0            
89 0           $b = join('/',@d);
90              
91 0           $self->{base} = $b;
92 0           $b = $self->tran_unix($b, -d $b);
93 0           $b =~ s#\].*#.\]#;
94              
95 0           $ROOTNUM++;
96 0 0         if ($^O =~ /vms/i) {
97 0           $self->{rootlogical} = sprintf('ROOT_%X_%d',$$,$ROOTNUM);
98 0           my $rslt = `define/nolog/job/trans=conceal $self->{rootlogical} $b`;
99 0 0         carp("Error defining logical, '$rslt' status:$?\n") if $?;
100             }
101 0           return $self;
102             }
103              
104             sub DESTROY {
105 0     0     my $self = shift;
106              
107 0 0         return if !defined($self->{rootlogical});
108 0           my $rslt = `deassign/job $self->{rootlogical}`;
109 0 0         carp("Error deassigning logical, '$rslt' status:$?\n") if $?;
110             }
111              
112              
113             #
114             # have to do this stuff manually, std call chokes on too many subdirs
115             #
116              
117             sub rooted ($;$$) {
118 0     0 0   my $self = shift;
119 0           my $d = shift;
120 0   0       $d ||= '';
121 0           my $opt = shift;
122 0   0       $opt ||= '';
123 0           my $WantVMS = $opt =~ /O_VMS/;
124              
125 0           $d = $self->expander($d);
126              
127 0 0         if (lc(substr($d,0,length($self->{base}))) ne lc($self->{base})) {
128 0           carp "$d isn't a subdirectory of $self->{base}\n";
129 0           return undef;
130             }
131 0           $d = substr($d,length($self->{base}));
132 0           my (@p) = split('/',$d);
133 0           shift @p;
134 0           my $f = pop(@p);
135 0 0 0       if (defined($f) && $f !~/\./) {
136 0           push(@p,$f);
137 0           $f = '';
138             }
139 0   0       while($#p >= 0 && $p[0] eq '000000') {shift @p};
  0            
140 0 0         push(@p,'000000') if ($#p < 0);
141 0           unshift(@p,'');
142 0   0       $f ||= '';
143 0           $d = '/'.lc($self->{rootlogical}).join('/',@p,$f);
144 0           $d =~ s#/$##;
145 0 0         $d = $self->tran_unix($d) if $WantVMS;
146 0           return $d;
147             }
148              
149             sub unrooted ($;$$) {
150 0     0 0   my $self = shift;
151 0           my $d = shift;
152 0           my $opt = shift;
153 0   0       $opt ||= '';
154 0           my $WantVMS = $opt =~ /O_VMS/;
155              
156 0           $d = $self->expander($d,'/'.$self->{rootlogical});
157 0 0         $d = $self->tran_unix($d) if $WantVMS;
158 0           return $d;
159             }
160              
161              
162             sub vms2unix {
163 0     0 0   my $self = shift;
164 0           my (@in) = $self->vmssplit(shift);
165 0           my (@def) = $self->vmssplit(shift);
166 0           my (@cur) = $self->vmssplit(cwd);
167 0           ($cur[3],$cur[4]) = $self->tran_rooted($cur[3],$cur[4]);
168 0           my $j;
169              
170 0           for ($j = 0; $j < 8; $j++) {
171 0 0         $def[$j] = $cur[$j] if !defined($def[$j]);
172 0 0         $in[$j] = $def[$j] if !defined($in[$j]);
173             }
174              
175 0           $in[4] = $self->make_abs($cur[4],$in[4]);
176 0           ($in[3],$in[4]) = $self->tran_rooted($in[3],$in[4]);
177              
178 0 0         $in[5] = $in[6] = $in[7] = undef if ($self->{O_DIRONLY});
179              
180 0           return $self->combine_for_unix(@in);
181              
182             }
183              
184             #
185             # translate "device" + "directory"
186             # from a conceal logical with rooted directory to
187             # an "absolute" form.
188             # and ignore that 8 directory depth stuff, too.
189             #
190              
191             sub tran_rooted {
192 0     0 0   my $self = shift;
193 0           my $dev = shift;
194 0           my $dir = shift;
195              
196 0           my $d = $dev;
197 0           $d =~ s#\:\Z##;
198 0           $d = $ENV{$d};
199 0 0         if ($d) {
200 0           my (@r) = $self->vmssplit($d);
201 0 0 0       if ($r[3] && $r[4] && $r[4] =~ /\.\]\Z/) {
      0        
202 0           $dev = $r[3];
203 0           $dir = $r[4].$dir;
204 0           $dir =~ s#\.\]\[#.#;
205             }
206             }
207              
208 0           $d = $dir;
209 0           $d =~ s#[\[\]]##g;
210 0           my (@d) = split('\.',$d);
211 0           my (@p) = ();
212 0           foreach (@d) {
213 0 0         push @p,$_ unless $_ eq '000000';
214             }
215 0 0         push @p,'000000' if $#p < 0;
216 0           $dir = '['.join('.',@p).']';
217 0           return ($dev,$dir);
218             }
219              
220              
221             #
222             # make a relative directory path absolute
223             # VMS syntax
224             #
225              
226             sub make_abs {
227 0     0 0   my $self = shift;
228 0           my $base = shift;
229 0           my $rel = shift;
230              
231 0 0         return $rel unless $rel =~ /\A\[(\.|\-|\])/;
232              
233 0           $base =~ s#[\[\]]##g;
234 0           $rel =~ s#[\[\]]##g; # strip brackets
235 0           my (@b) = split('\.',$base);
236 0           my (@r) = split('\.',$rel);
237              
238 0           foreach (@r) {
239 0 0         if (/\A\-+\Z/) {
    0          
240 0           my $j;
241 0 0         if (length($_) > $#b+1) {
242 0           warn 'relative directory going above root directory';
243             }
244 0           for ($j = 0; $j < length($_); $j++) {
245 0           pop @b;
246             }
247             } elsif ($_ ne '') {
248 0           push @b,$_;
249             }
250             }
251 0           return '['.join('.',@b).']';
252             }
253              
254             #
255             # split a VMS filename into components, we don't check for validity
256             # of the components, as long as it parses okay.
257             #
258             # directory names in < ... > get translated to [ ... ]
259             # versions with '.' separator ( file.txt.1) get translated to ';'
260             #
261             $Pnode = '([a-z0-9][a-z0-9\-\.]*)(\"([a-z0-9]+)(\s+(\w*))?\")?\:\:';
262             $Pdev = '[\w\$]+\:';
263             $Pdir = '\[([\w\.\-]*)\]|\<([\w\.\-]*)\>';
264             $Pname = '[\w\$\-]+';
265             $Ptype = '\.[\w\$\-]*';
266             $Pvers = '(\;|\.)((\+\d|\-\d)?\d*)';
267              
268              
269              
270              
271              
272             sub vmssplit {
273 0     0 0   my $self = shift;
274 0           my $f = shift;
275 0 0         $f = '' unless defined($f);
276              
277 0           my ($node,$user,$pwd,$dev,$dir,$name,$type,$vers,$adir,$bdir,$idir,$rdir);
278              
279 0 0         if ($f =~ /\A($Pnode)?($Pdev)?($Pdir)?($Pname)?($Ptype)?($Pvers)?\Z/i) {
280 0           $node = $2;
281 0           $user = $4;
282 0           $pwd = $6;
283 0           $dev = $7;
284 0           $dir = $8;
285 0           $bdir = $9;
286 0           $adir = $10;
287 0 0         $rdir = $bdir ? $bdir : $adir ;
288 0 0 0       $rdir = '['.$rdir.']' if defined($rdir) && $rdir ne '';
289 0           $name = $11;
290 0           $type = $12;
291 0 0         $vers = $15 ? ';'.$15 : undef;
292             } else {
293 0           warn "$f is not a valid VMS filename";
294             }
295              
296 0 0 0       if ($node && $self->{O_LOCALONLY}) {
297 0           warn 'node specified for what should be a local path';
298             }
299 0 0         if ($self->{O_LOCAL}) {
300 0           $node = $user = $pwd = undef;
301             }
302              
303 0           return ($node,$user,$pwd,$dev,$rdir,$name,$type,$vers);
304             }
305              
306             #
307             # convert deconstructed VMS filespec to Unix
308             # note that if we have node/user/password info (a la decnet)
309             # it gets converted to a "URL" style prefix: //user:password@node
310             #
311              
312             sub combine_for_unix {
313 0     0 0   my $self = shift;
314 0 0         my ($node,$user,$pwd,$dev,$dir,$name,$type,$vers) = map($self->{O_LOWER}?lc($_):$_,@_);
315              
316 0           my $s = '';
317              
318 0 0         if ($node) {
319 0           $s .= '//';
320 0 0         if ($user) {
321 0           $s .= $user;
322 0 0         if ($pwd) {
323 0           $s .= ':'.$pwd;
324             }
325 0           $s .= '@';
326             }
327 0           $s .= $node;
328             }
329 0 0         if ($dev) {
330 0           $dev =~ s#\:\Z##;
331 0           $s .= '/'.$dev;
332             }
333 0 0         if ($dir) {
334 0           $dir =~ s#[\[\]]##g;
335 0           $dir =~ s#\.#/#g;
336 0           $s .= '/'.$dir;
337             }
338 0 0 0       if ($name||$type||$vers) {
      0        
339 0           $s .= '/'.$name.$type.$vers;
340             }
341 0           return $s;
342             }
343              
344              
345              
346             sub tran_unix {
347 0     0 0   my $self = shift;
348 0           my (@p) = split('/',shift);
349 0           my (@p2);
350             my ($d);
351              
352 0           $d = shift(@p);
353 0 0 0       if (defined($d) && $d eq '') {
354 0           $d = shift(@p).':';
355             } else {
356 0           unshift(@p,$d);
357 0           $d = '';
358 0           $p2[0] = '';
359             }
360              
361 0           foreach (@p) {
362 0 0         next unless defined($_);
363 0 0         next if $_ eq '.';
364              
365 0 0         if ($_ eq '..') {
366 0 0         if ($#p2 == 0) {
367 0 0         if ($p2[0] =~ /\A\-*\Z/) {
368 0           $p2[0] .= '-';
369 0           next;
370             }
371             }
372 0           pop @p2;
373             } else {
374 0           push @p2, $_;
375             }
376             }
377 0           my $f = pop @p2;
378             #
379             # our only hint that the trailing bit is a filename: it has a . or ; in it
380             #
381 0 0 0       if (defined($f) && $f !~ /[\.\;]/) {
382 0           push @p2, $f;
383 0           $f = '';
384             }
385 0   0       $f ||= '';
386 0           return $d.'['.join('.',@p2).']'.$f;
387             }
388              
389             sub expander {
390 0     0 0   my $self = shift;
391 0           my $dir = shift;
392 0           my $def = shift;
393 0           my $dironly = $self->{O_DIRONLY};
394              
395 0 0         $dir = '' if !defined($dir);
396 0 0         if ($dir !~ /[\[\<\;\:]/) {
397             #
398             # how can we tell if this is /dev/dir/file or /dev/dir/sdir ?
399             #
400 0           $dir = $self->tran_unix($dir);
401             }
402              
403 0 0 0       if (defined($def) && $def !~ /[\[\<\;\:]/) {
404 0           $def = $self->tran_unix($def);
405             }
406              
407 0           return $self->vms2unix($dir,$def);
408             }
409              
410             1;