File Coverage

blib/lib/Envy/DB.pm
Criterion Covered Total %
statement 407 522 77.9
branch 221 336 65.7
condition 89 145 61.3
subroutine 45 52 86.5
pod 0 39 0.0
total 762 1094 69.6


line stmt bran cond sub pod time code
1             # ------------------------------------------------------*-perl-*-
2             # GHOSTWHEEL DIMENSION MULTIPLEXER
3             #
4 5     5   13535 use strict;
  5         11  
  5         214  
5             package Envy::DB;
6 5     5   4569 use integer;
  5         50  
  5         25  
7 5     5   123 use Carp;
  5         9  
  5         368  
8 5     5   4546 use Symbol;
  5         5234  
  5         451  
9 5     5   32 use Fcntl;
  5         9  
  5         1694  
10 5         68520 use vars qw(@ISA @EXPORT_OK $VERSION $EVERSION @DefaultPath
11             $MAX_VAR_LENGTH
12 5     5   32 $LOGIN $Context $NestLevel $Loop $Path @FORCEPATH %PASSWD);
  5         13  
13             $VERSION = '2.48';
14             $NestLevel = 0;
15              
16             umask 0; # Figure out how to run setuid 'envy'? XXX
17              
18             $MAX_VAR_LENGTH = 969; # configure time parameter?
19              
20             $EVERSION = 4; # environment variable protocol version
21              
22             sub EVERSION() { 'ENVY_VERSION' }
23             sub PATH() { 'ENVY_PATH' }
24             sub STATE() { 'ENVY_STATE' }
25             sub DIMENSION() { 'ENVY_DIMENSION' }
26             sub CONTEXT() { 'ENVY_CONTEXT' }
27             sub VERBOSE() { 'ENVY_VERBOSE' }
28              
29             if ($ENV{REGRESSION_ENVY_PATH}) {
30             @DefaultPath = split m/\s+/, $ENV{REGRESSION_ENVY_PATH};
31             @FORCEPATH = ();
32             } else {
33             @DefaultPath = qw(/usr/local/etc/envy)
34             ;
35             @FORCEPATH = qw(/bin /usr/bin)
36             ;
37             }
38              
39             sub new { #PUBLIC
40 7     7 0 2887 my ($class, $env) = @_;
41 7         24 my $o = bless {}, $class;
42 7 50       134 my %env = $env? %$env : ();
43 7         65 $o->{orig} = \%env;
44 7   50     85 $o->{where} = $o->{env}{ &CONTEXT } || 'shell';
45 7         17 $o->{desc} = {};
46 7         17 $o->{transaction} = 0;
47 7   50     70 $o->{warnlevel} = $o->{env}{ &VERBOSE } || 1;
48 7         30 $o->begin;
49 7         20 $o;
50             }
51              
52             # help cope with backward compatibility
53             sub version {
54 19     19 0 51 my ($o) = @_;
55 19 100       113 $o->{env}{&EVERSION} || $EVERSION;
56             }
57              
58             # ---------------------------------------------------------------
59             # MESSAGES
60              
61             sub warnlevel {
62 16     16 0 7326 my $o=shift;
63 16 50       48 if (@_) {
64 16         43 $o->{warnlevel} = shift;
65             } else {
66 0         0 $o->{warnlevel}
67             }
68             }
69              
70 1     1 0 5 sub e { my $o=shift; _internal_warn($o, 0, 1, @_) } # abort transaction
  1         5  
71 1     1 0 5 sub w { my $o=shift; _internal_warn($o, 1, 1, @_) } # manditory warning
  1         5  
72 5     5 0 12 sub n { my $o=shift; _internal_warn($o, 2, 1, @_) } # optional warning
  5         19  
73 147     147 0 626 sub t { my $o=shift; _internal_warn($o, 3, 0, @_) } # trace execution
  147         292  
74 16     16 0 25 sub d { my $o=shift; _internal_warn($o, 4, 0, @_) } # debugging info
  16         37  
75              
76             sub _internal_warn {
77 170     170   334 my ($o, $level, $show_context) = splice @_, 0, 3;
78 170         998 my $w = join('', @_);
79 170 100       456 if ($show_context) {
80 7 100 100     45 $w .= $Context
81             if $Context && $w !~ m/\n$/s;
82             } else {
83 163         474 $w = 'D ' . (' 'x$NestLevel).$w;
84             }
85 170 100       523 $w .= "\n" if $w !~ m/\n$/s;
86 170 100       434 if ($level <= $o->{strictness}) {
87 1         3 $w = 'ERROR: '.$w;
88 1         3 ++$o->{errors};
89             }
90 170 100       412 push @{$o->{'warn'}}, $w
  8         20  
91             if $level <= $o->{warnlevel};
92 170         563 0
93             }
94              
95             # ---------------------------------------------------------------
96             # GENERIC UTILITIES
97              
98             sub diff_hash { #PUBLIC
99 18     18 0 28 my ($orig,$env) = @_;
100 18         21 my %delta;
101 18         93 for my $k (keys(%$orig), keys(%$env)) {
102 297 100       867 if (!exists $$orig{$k}) {
    100          
    100          
103 54         106 $delta{$k} = $env->{$k};
104             } elsif (!exists $$env{$k}) {
105 15         29 $delta{$k} = undef;
106             } elsif ($$orig{$k} ne $$env{$k}) {
107 40         78 $delta{$k} = $$env{$k};
108             }
109             }
110 18         69 \%delta;
111             }
112              
113             # ---------------------------------------------------------------
114             # ENVY_PATH MANAGEMENT
115              
116             sub search_envy_path { #PRIVATE
117 14     14 0 24 my ($o) = @_;
118 14         31 $o->{fullpath} = {};
119 14         68 $o->{shadowed} = {};
120              
121 14         23 my %PATH;
122             my $add_path = sub {
123 15     15   28 my ($o, $p) = @_;
124            
125 15 50       45 return if exists $PATH{$p};
126 15         65 $PATH{$p}=1;
127 15         64 $o->d("Reading $p...\n");
128            
129 15         52 my $dh = gensym;
130 15 100       919 opendir($dh, $p) or return $o->n("Directory '$p' not readable.\n");
131 14         265 for my $m (readdir($dh)) {
132 210 100       750 next if $m !~ m/\.(mo|env)$/;
133 182 50       3474 next if -d "$p/$m";
134            
135 182 50       557 if ($m =~ /[,\s]/) {
136 0         0 $o->w("Envy found containing commas '$p/$m' (ignored).\n");
137 0         0 next;
138             }
139 182 50       622 $o->w("Envy '$p/$m' should have .env suffix (ignored).\n")
140             if $m !~ m/\.env$/;
141            
142 182         218 my $file = $m;
143 182         641 $m =~ s/\.(mo|env)$//;
144 182 50       1074 if ($m =~ m/^\d$/) {
145 0         0 $o->w("Envy: '$p/$m' single digits are reserved\n");
146 0         0 next;
147             }
148 182 50       502 if (!exists $o->{fullpath}{$m}) {
149             #$o->d("\t$m=$p/$file\n"); emergency debugging
150 182         760 $o->{fullpath}{$m} = "$p/$file";
151             } else {
152 0         0 push @{ $o->{shadowed}{$m} }, "$p/$file";
  0         0  
153             }
154             }
155 14         256 closedir($dh);
156 14         86 };
157            
158 14   100     102 my @add = split /:+/, $o->{env}{&PATH}||'';
159 14 100       43 @add = @DefaultPath if !@add;
160 14 50 33     52 if (exists $o->{env}{'HOME'} and -d $o->{env}{'HOME'}."/.envy") {
161 0         0 unshift @add, $o->{env}{'HOME'}."/.envy";
162             }
163 14         28 for my $p (@add) {
164 15         34 $add_path->($o, $p);
165 15 50       694 if (-d "$p/.priv") {
    50          
166 0         0 $add_path->($o, "$p/.priv")
167             } elsif (-d "$p/.private") { # remove deprecated XXX
168 0         0 $o->w("$p/.private should be renamed to $p/.priv\n");
169 0         0 $add_path->($o, "$p/.private");
170             }
171             }
172             }
173              
174             sub get_fullpath {
175 36     36 0 80 my ($o, $mo) = @_;
176 36 100       196 $o->search_envy_path() if !exists $o->{fullpath};
177 36 50       168 if (!defined $mo) {
    50          
    0          
178 0         0 $o->{fullpath}
179             } elsif (exists $o->{fullpath}{$mo}) {
180 36         103 $o->{fullpath}{$mo}
181             } elsif (-f $mo) {
182             # I think this is a hack for cache_shell_script? XXX
183             #$o->n("Using '$mo' from the current working directory");
184 0         0 $mo
185             } else {
186             ()
187 0         0 }
188             }
189              
190             # ---------------------------------------------------------------
191             # TRANSACTIONS
192              
193             sub begin { #PUBLIC
194 22     22 0 74 my ($o) = @_;
195 22 100       86 return if $o->{transaction};
196              
197 19         30 my @env = %{ $o->{orig} };
  19         135  
198 19         126 $o->{env} = { @env };
199 19         180 $o->{delta} = undef;
200 19         64 $o->{'warn'} = [];
201 19         100 $o->{'log'} = [];
202 19         56 $o->{errors} = 0;
203 19         30 $o->{strictness} = 0
204             ;
205 19         52 my $v = $o->version;
206 19 50       58 $o->w("Envy protocol $v is not supported in this version of Envy (v$VERSION).\nPlease consider upgrading to the latest version!\n")
207             if $v > $EVERSION;
208              
209 19         77 for my $k (&STATE, &DIMENSION) {
210 38         95 $o->join_variable($k);
211             }
212            
213 19         71 my %loaded;
214 19   100     285 for my $m (split /:+/, $o->{env}{&STATE} || '') {
215 40         131 my ($k,$v) = split /,/, $m;
216 40 50       83 $v = '0' if $v eq 'STARTUP'; # v1.18?
217 40         96 $loaded{$k}=$v;
218             }
219 19         50 $o->{loaded} = \%loaded;
220              
221 19   100     236 my %dimen = map { split /,/ } split /:+/, $o->{env}{&DIMENSION} || '';
  24         93  
222 19         52 $o->{dimen} = \%dimen;
223 19         59 $o->{first} = $o->{dimen}{First};
224 19         80 $o->{transaction} = 1;
225             }
226              
227             sub commit { #PUBLIC
228 19     19 0 91 my ($o) = @_;
229 19 50       58 confess "not in transaction" if $o->{transaction}!=1;
230 19         31 $o->{transaction} = 0;
231 19 100       53 return 1 if $o->{errors};
232              
233             # ENVY_PATH is adjusted just like other variables (almost).
234             # It does not concern us here.
235              
236 18         64 my $dim = $o->{dimen};
237 18         170 $o->{env}{&EVERSION} = $EVERSION;
238 49         186 $o->{env}{&STATE} = join(':', map {"$_,$o->{loaded}{$_}"} sort
  18         100  
239 18         31 keys %{$o->{loaded}});
240 32         137 $o->{env}{&DIMENSION} = join(':', map {"$_,$o->{dimen}{$_}"} sort
  32         89  
241 18         72 grep { defined $dim->{$_} } keys %$dim);
242 18         641 for my $k (&STATE, &DIMENSION) {
243 36         97 $o->split_variable($k)
244             }
245              
246 18         60 $o->{delta} = diff_hash($o->{orig}, $o->{env});
247 18         36 $o->{orig} = $o->{env};
248 18         66 $o->{env} = undef;
249 18         38 0;
250             }
251              
252             # ---------------------------------------------------------------
253             # ENVY_STATE & ENVY_DIMENSION API
254              
255             sub is_first {
256 93 100   93 0 607 if (@_ == 1) {
    50          
257 32         44 my ($o) = @_;
258 32         171 !$o->{dimen}{First} # true if never seen 'First'
259             } elsif (@_ == 2) {
260 61         112 my ($o, $mo) = @_;
261 61         151 my $f = $o->{dimen}{First};
262 61 50       812 $f && $f eq $mo
263             }
264             }
265              
266             # Explaination of reference counting scheme:
267             #
268             # 0 - the first envy loaded (First dimension)
269             # 1 - top level (refcnt=1)
270             # - explicit dependency (refcnt=1)
271             # 2+ - required by multiple envys
272              
273             sub get_seniority { #PRIVATE
274 28     28 0 36 my ($o, $e) = @_;
275 28         140 my $rc = $o->{loaded}{$e};
276 28 100       59 return 0 if !$rc;
277 19 100       47 return 1 if $rc eq '1';
278 14 50       134 return 2 if $rc !~ m/^\d+$/;
279 0         0 return 2+$rc;
280             }
281             sub get_refcnt { #PRIVATE
282 40     40 0 53 my ($o, $e) = @_;
283 40 100       225 return 0 if !exists $o->{loaded}{$e};
284 29         60 my $rc = $o->{loaded}{$e};
285 29 100 100     256 $rc = 1 if $rc eq '0' || $rc !~ m/^\d+$/;
286 29         252 $rc;
287             # dualvar? XXX
288             }
289             sub set_refcnt { #PRIVATE
290 5     5 0 13 my ($o,$e,$rc) = @_;
291 5 50       18 if ($rc <= 0) {
292 5         18 delete $o->{loaded}{$e};
293             } else {
294 0         0 $o->{loaded}{$e} = $rc;
295             }
296             }
297              
298             sub refcnt_inc { #PRIVATE
299 21     21 0 39 my ($o, $e, $by) = @_;
300 21         31 my $l = $o->{loaded};
301 21 100       45 if ($o->is_first($e)) {
    50          
302 5         18 $l->{$e} = '0'; # cannot increase, emit warning? XXX
303             } elsif (!exists $l->{$e}) {
304 16         62 $l->{$e} = $by;
305             } else {
306 0         0 $l->{$e} = $o->get_refcnt($e)+1;
307             }
308             }
309              
310             sub refcnt_dec { #PRIVATE
311 7     7 0 12 my ($o, $e, $by) = @_;
312 7         17 my $rc = $o->get_refcnt($e);
313 7 50       17 if ($rc <= 1) {
314 7         17 my $old = $o->{loaded}{$e};
315 7 50 66     54 $o->n("Envy '$e' unloaded by '$by' instead of '$old'")
      66        
316             if ($old and $old !~ m/^\d+$/ and $old ne $by);
317 7         21 delete $o->{loaded}{$e}
318             } else {
319 0         0 $o->{loaded}{$e} = $rc - 1;
320             }
321             }
322              
323             sub nav_dimension { #PRIVATE
324 16     16 0 37 my ($o, $how, $di, $by) = @_;
325 16         19 my $swap;
326 16 100       55 my $old = $o->{dimen}{$di} if exists $o->{dimen}{$di};
327 16 100       36 if ($how>0) {
328 11 100       31 if ($o->is_first) {
329 4 50       36 if ($di ne 'First') {
330 0         0 $o->w("Envy '$by' should claim dimension 'First' instead of '$di'");
331 0         0 $di = 'First';
332             }
333             }
334             else {
335 7 100       20 if ($di eq 'First') {
336 1 50       5 if ($by eq $o->{first}) {
337 1 50       5 $o->n("First dimension '$by' reloaded") #is harmless?
338             if !$o->{unload_all};
339             } else {
340 0         0 $o->unload_all()
341             }
342             }
343 7 100 100     29 if ($old and $old ne $by) {
344 2         11 $o->n("Swapping $di from '$old'");
345 2         6 $swap = $o->get_refcnt($old);
346 2         6 $o->process_envy(-100, $di, $di);
347             }
348             }
349 11         71 $o->{dimen}{$di} = $by;
350 11 100       34 $o->{first} = $by
351             if $di eq 'First';
352             } else {
353 5         7 if (0) {
354             # only positive transitions
355             $o->w("Envy '$by' releasing $di (was '$old')")
356             if $old && $old ne $by; # unlikely to occur
357             $o->{dimen}{$di} = undef; # don't delete!
358             }
359             }
360 16         49 $swap;
361             }
362              
363             # ---------------------------------------------------------------
364              
365             sub catalogue {
366 0     0 0 0 my($o) = shift;
367 0         0 $o->{desc} = {}; #reset
368 0 0       0 $o->search_envy_path() if !exists $o->{fullpath};
369 0         0 my $fh = gensym;
370 0         0 foreach my $mo (keys %{$o->{fullpath}}){
  0         0  
371 0         0 my @file;
372             # if we are going to touch the file-system, we might as
373             # well load the dimension/dependency information too? XXX
374 0 0       0 if(open($fh,$o->{fullpath}{$mo})){
375 0         0 @file = <$fh>;
376 0         0 close $fh;
377             }
378 0         0 chomp(@file);
379 0         0 my(@desc) = grep(s/^desc(ription)?\s+//,@file);
380 0 0 0     0 if((defined $o->{desc}{$mo}) && ($o->{desc}{$mo} ne "")){
381 0         0 $o->{desc}{$mo} = join("\n",$o->{desc}{$mo},@desc);
382             } else {
383 0         0 $o->{desc}{$mo} = join("\n",@desc);
384             }
385             }
386             }
387              
388             # ---------------------------------------------------------------
389             # VARIABLE SUBSTITUTION, ETC
390              
391             sub tree_top {
392 25     25 0 37 my ($base) = @_;
393 25         219 $base =~ s,(/etc/envy|/mo|)/[^/]+\.(mo|env)$,,;
394 25         622 $base;
395             }
396              
397             # Split up a var if it is too large for the shell to handle
398             sub split_variable { # PRIVATE
399 36     36 0 50 my($o,$k) = @_;
400 36 100       160 return if length $o->{env}{$k} < $MAX_VAR_LENGTH;
401 6         99 my @var = split /:+/, $o->{env}{$k};
402 6 100       24 return if @var <= 1; # gasp!
403 3         9 delete $o->{env}{$k};
404 3         2 my @chunk;
405 3         5 my $chunk=1;
406 3         4 my $csz=0;
407             my $save_chunk = sub {
408 11     11   17 my $ck = "$k$chunk";
409 11 50       25 if (exists $o->{env}{$ck}) {
410 0         0 $o->n("Stomping '$ck' while storing very long '$k'")
411             }
412 11         26 $o->{env}{$ck} = join ':', @chunk;
413 11         12 @chunk=();
414 11         11 $csz=0;
415 11         30 ++$chunk;
416 3         17 };
417 3         7 while (@var) {
418 11         17 my $c = shift @var;
419 11 100 66     43 &$save_chunk if @chunk && length($c) + $csz > $MAX_VAR_LENGTH;
420 11         12 push @chunk, $c;
421 11         21 $csz += length $c;
422             }
423 3 50       9 &$save_chunk if @chunk;
424             }
425              
426             # put a var back together if it was split due
427             # to being too large for the shell to handle
428             sub join_variable { # PRIVATE
429 38     38 0 57 my ($o,$k) = @_;
430 38 100       146 return if exists $o->{env}{$k};
431 12         14 my @c;
432 12         56 for (my $c=1; exists $o->{env}{"$k$c"}; $c++){
433 8         14 push @c, $o->{env}{"$k$c"};
434 8         23 delete $o->{env}{"$k$c"};
435             }
436 12   100     87 $o->{env}{$k} = join(':', @c) || '';
437             }
438              
439             sub interpolate {
440 86     86 0 132 my ($o, $qx, $str) = @_;
441             my $subst = sub {
442 37     37   82 my $var = shift;
443 37         60 $var =~ tr [{}] []d;
444             # removed deprecated XXX
445 37 50 33     386 if ($var eq 'MODULE_BASE' or $var eq 'modulebase' or
    100 33        
    50 66        
446             $var eq 'ENVY_LINKBASE') {
447 0 0       0 $o->w("$var is deprecated")
448             if $var =~ /module/i;
449 0         0 return tree_top($Path);
450             } elsif ($var eq 'MODULE_REALBASE' or $var eq 'ENVY_BASE') {
451 25 50       68 $o->w("$var is deprecated")
452             if $var =~ /module/i;
453 25         39 my $rbase = $Path;
454 25         593 while (-l $rbase) {
455 0 0       0 my $link = readlink($rbase) or die "readlink $rbase";
456 0 0       0 if ($link =~ m,^/,) {
457 0         0 $rbase = $link # absolute path
458             } else {
459             # collapse ../
460 0         0 $rbase =~ s,/([^/]+)$,/,;
461 0         0 my $envy = $1;
462 0         0 $link =~ s,/.+?$,/,;
463 0         0 while ($link =~ s,\.\./$,,) {
464 0         0 $rbase =~ s,/[^/]+/$,/,;
465             }
466 0         0 $rbase .= $link . $envy;
467             }
468             }
469 25         124 return tree_top($rbase);
470             } elsif ($var =~ m/^ENVY_(R|E)UID([_\w]*)$/) {
471 12 100       66 my $id = $1 eq 'R'? $< : $>;
472 12         23 my $field = $2;
473 12   100     191 my $pw = $PASSWD{$id} ||= [getpwuid($id)];
474 12         14 my $got = do {
475 12 100       50 if ($field eq '') {
    100          
    100          
    100          
    100          
    50          
476 2         4 $id
477             } elsif ($field eq '_NAME') {
478 2         5 $pw->[0]
479             } elsif ($field eq '_GID') {
480 2         4 $pw->[3]
481             } elsif ($field eq '_GCOS') {
482 2         6 $pw->[6]
483             } elsif ($field eq '_DIR') {
484 2         5 $pw->[7]
485             } elsif ($field eq '_SHELL') {
486 2         5 $pw->[8]
487             } else {
488 0         0 $o->w("Builtin '$var' unrecognized");
489 0         0 ''
490             }
491             };
492 12         81 return $got;
493             }
494 0 0       0 return $o->e("Variable '$var' unset for interpolation"), '' if
495             !defined $o->{env}{$var};
496 0         0 $o->{env}{$var};
497 86         614 };
498             # need to do real lexical analysis XXX
499 86 50       309 if($str =~ /^\'(.*)\'$/){
500 0         0 return $1;
501             }
502 86 50       173 if($str =~ /\`(.*)\`$/){
503 0         0 my(@asBackTic) = `$1`;
504 0         0 chomp(@asBackTic);
505 0         0 my($sBackTic) = join(" ",@asBackTic);
506 0         0 $str =~ s/\`(.*)\`/$sBackTic/;
507             }
508 86         371 while ($str =~ s/
509             \$ (
510             (:? \{[\w-]+\} ) |
511             (:? [\w-]+ )
512             )
513 37         77 /&$subst($1)/exg) {};
514 86         1438 $str;
515             }
516              
517             sub edit_key {
518 77     77 0 208 my ($o, $k) = @_;
519 77 50       160 if ($k eq 'MODULE_PATH') {
520 0         0 $o->w("'$k' is deprecated; please use ".&PATH);
521 0         0 $k = &PATH;
522             }
523 77 50       286 return $o->w("Variable '$k' is not alpha-numeric") if
524             $k !~ /^[\w-]+$/;
525 77 50 33     781 return $o->w("Naughty: '$k' is private") if
      33        
      33        
526             ($k eq &STATE or $k eq &DIMENSION or
527             $k eq 'ENVY_BASE' or $k eq 'ENVY_LINKBASE');
528 77         309 $k;
529             }
530              
531             sub assign {
532 31     31 0 69 my ($o, $reverse, $k, $force, $v) = @_;
533 31         66 $k = $o->edit_key($k);
534 31 50       69 return if !$k;
535 31 50 33     132 return $o->e("Variable '$k' must be edited with +=")
536             if ($k eq 'PATH' or $k eq 'MANPATH');
537 31 100       124 if (!$reverse) {
538 26 50 33     127 $o->n("Variable '$k' redefined") if
539             (!$force and exists $o->{env}{$k});
540 26         64 my $vinterp = $o->interpolate(1,$v);
541 26         88 $o->{env}{$k} = $vinterp;
542 26         135 $o->t("$k=$vinterp");
543             }
544             else {
545 5         15 delete $o->{env}{$k};
546 5         20 $o->t("unset $k");
547             }
548             }
549              
550             sub rejoin {
551 46     46 0 102 my ($o, $reverse, $k, $prepend, $sep, $v) = @_;
552 46         108 $k = $o->edit_key($k);
553 46 50       178 return if !$k;
554              
555             # SPECIAL CASES:
556             #
557             # PATH - '.' is always kept first if it is seen
558             # @FORCEPATH is always next
559             # don't remove ourselves from the path?
560             # if switching to a new envy.pl abort subsequent loads?
561             #
562             # ENVY_PATH - rescan all directories for .env files
563              
564             # fetch old list
565 46 100       349 my @old = split /$sep+/, $o->{env}{$k} if defined $o->{env}{$k};
566            
567             # fetch delta
568 46         213 my @delta = split /$sep+/, $o->interpolate(0,$v);
569 46         68 my %delta; for (@delta) { $delta{$_}=1 }
  46         88  
  52         252  
570 46         61 my @now;
571              
572 46 100       87 my $sign = $reverse?'-':'+';
573 46 100       238 $o->t("$k".($prepend?"$sign=":"=$sign").join($sep, @delta));
574              
575             # filter @old with @delta -> @now
576 46 100       96 if ($k eq 'PATH') {
577 20         23 my %old; for (@old) { $old{$_}=1; }
  20         36  
  30         74  
578 20         52 my $has_dot = exists $old{'.'};
579 20         23 my %ign; for ('.', @FORCEPATH) { $ign{$_}=1 }
  20         37  
  20         51  
580              
581 20 100 100     96 @now = @delta if $prepend && !$reverse;
582            
583 20         35 for my $p (@old) {
584 30 50 66     109 if (!$reverse and exists $delta{$p} and !exists $ign{$p}) {
      33        
585 0         0 $o->n("Component '$p' added to '$k' again");
586             next
587 0         0 }
588 30 100 66     126 next if delete $delta{$p} || exists $ign{$p};
589 21         45 push @now, $p;
590             }
591              
592 20 100 66     55 push @now, @delta if !$prepend && !$reverse;
593 20         29 unshift @now, @FORCEPATH;
594 20 50       69 unshift @now, '.' if $has_dot; # once sloppy always sloppy
595             } else {
596 26 100 100     117 push @now, @delta if $prepend && !$reverse;
597 26         44 for my $p (@old) {
598 25 50 66     80 if (!$reverse and exists $delta{$p}) {
599 0         0 $o->n("Component '$p' added to '$k' again");
600             next
601 0         0 }
602 25 100       78 next if delete $delta{$p};
603 12         30 push @now, $p;
604             }
605 26 100 66     60 push @now, @delta if !$prepend && !$reverse;
606             }
607 46 100       87 if (@now) {
608 37         208 $o->{env}{$k} = join $sep, @now;
609             } else {
610 9         40 delete $o->{env}{$k};
611             }
612             }
613              
614             # ---------------------------------------------------------------
615             # PROCESS A SINGLE FILE
616              
617             sub _inherit_how {
618 14     14   24 my $how = shift;
619 14 100       97 $how==0? 0 : ($how > 0? 1:-1)
    50          
620             }
621              
622             sub process_envy { #PRIVATE
623 36     36 0 70 my ($o, $how, $e, $by) = @_;
624 36 50       79 confess $o if @_ != 4;
625 36 50       241 confess "no transaction" if !$o->{transaction};
626 36 50       85 return if $o->{errors} > 5;
627 36         52 local $Loop = $Loop+1;
628 36 50       78 return $o->e("Recursive envy processing detected")
629             if $Loop>100;
630              
631 36 50 66     147 $how=1 if $how == 0 && !$o->{loaded}{$e};
632              
633             # unload by dimension
634 36 100 100     146 $e = $o->{dimen}{$e} if $how<0 && !$o->{loaded}{$e} && $o->{dimen}{$e};
      100        
635              
636 36         95 local $Path = $o->get_fullpath($e);
637 36 50       95 return $o->w("Can't find envy '$e' (skipping)")
638             if !$Path;
639 36 100 100     594 if ($how<0 and $o->get_refcnt($e)==0) {
640 1 50       9 $o->n("Envy '$e' re-unloaded")
641             if !$o->{unload_all};
642 1         3 return;
643             }
644              
645 35         199 $o->t("[$how] $e ($Path)");
646 35         144 my $swap;
647 35   100     128 my $mod = !(abs($how)<=1 and ($how>=0 xor $o->get_refcnt($e)==0));
648             # warn "$e $how $by mod=$mod\n";
649 35         41 my @L;
650             {
651 35         38 my $fh = gensym;
  35         109  
652 35 50       2650 open($fh, $Path) or
653             return $o->w("Envy '$e' (in $Path) is not readable (skipping)");
654 35         932 @L = <$fh>; #cache XXX
655 35         467 close $fh;
656             }
657 35         188 my $seen_stuff=0;
658 35         38 my $is_first;
659             my $prechange = sub {
660 96 100   96   207 return if $seen_stuff;
661 35         38 $seen_stuff = 1;
662 35 50 66     144 if ($how>0 and $o->is_first and !$o->is_first($e)) {
      33        
663 0         0 $o->w("Envy '$e' must claim dimension 'First' (don't be shy :-)");
664 0         0 $swap = $o->nav_dimension($how, 'First', $e);
665             }
666 35         79 $is_first = $o->is_first($e);
667 35         301 };
668             my $doline = sub {
669 152     152   171 my $line = shift;
670 152         566 my $l = $L[$line-1];
671              
672             # need a real lexer; '#' cannot be hidden by quoting! XXX
673             # get rid of comment lines & trailing whitespace
674 152         1713 $l =~ s/ \s* (\#.*)? $//sx;
675 152 100       426 return if !length $l;
676              
677 112 100       877 local $Context = (" at ".($how>=0?'':'un').
678             "envy '$e' line $line\n\t".$Context);
679 112         153 local $NestLevel = $NestLevel + 1;
680              
681 112 50       1373 if ($l =~ /^(alpha|beta|deprecated)$/) {
    50          
    50          
    50          
    100          
    100          
    50          
    100          
    50          
682 0 0 0     0 $o->w("Envy '$e' is ".uc($1).", use at your own risk")
683             if $mod && $how>=0;
684             } elsif ($l =~ s/^(echo|error)\s?//) {
685 0 0 0     0 if ($mod and $how>=0) {
686 0         0 my $str = $o->interpolate(1,$l)."\n";
687 0 0       0 if ($1 eq 'echo') {
688 0         0 print $str; # ok? XXX
689             } else {
690 0         0 $o->e($str);
691             }
692             }
693             } elsif ($l =~ s/^desc(ription)?\s+//) {
694             # need to reset before reading file XXX
695             # will be backwards for unload XXX
696             # just ignore here? XXX
697 0         0 my $d = $o->{desc};
698 0 0       0 if (!$d->{$e}) {
699 0         0 $d->{$e} = $l;
700             } else {
701 0         0 $d->{$e} .= "\n$l";
702             }
703             } elsif ($l =~ m/^require\s+Envy\s+([\d\.]+)$/) {
704 0         0 my $v = $1;
705 0 0 0     0 return $o->w("Envy '$e' requires envy $v -- this is only $VERSION")
706             if $mod && $v > $VERSION;
707              
708             } elsif ($l =~ s/^dimension\s+//) {
709             # variable substitution? XXX
710 16 50 66     53 return $o->w("Dimensions are declared at the beginning (ignored)")
711             if $seen_stuff && $how>=0;
712 16 50       66 return $o->w("Bad dimension name '$l' (ignored)")
713             if $l !~ m/^[\w-]+$/;
714 16 50 33     86 $o->n("Dimension '$l' should not mention '$1'")
715             if $l =~ m/(release)/ or $l =~ m/(version)/;
716 16         56 $swap = $o->nav_dimension($how, $l, $e);
717              
718             } elsif ($l =~ s/^require\s+//) {
719 14         30 $prechange->();
720 14         38 my $str = $o->interpolate(0,$l);
721 14 50       71 return $o->w("Bad characters in require '$str' (skipping)")
722             if $str !~ /^[-\w.\/]+$/;
723 14         31 $o->process_envy(_inherit_how($how), $str, $e);
724              
725             } elsif ($l =~ s/^(c)?sh_load\s+//) {
726 0         0 $prechange->();
727 0         0 require Envy::Import; # try to avoid in most cases
728 0         0 my $envy_file = $o->cache_shell_script($l,$e);
729 0 0       0 if ($envy_file) {
730             # reread $HOME/.envy/... XXX
731 0         0 $o->process_envy(_inherit_how($how), $envy_file, $e);
732             }
733             }
734             # careful to match PATH=+ before PATH=
735             elsif ($l =~ /^([\w-]+) (\+\=|\=\+) (.*)$/x) {
736 50         239 my @got = ($1, $2 eq '+=', ':', $3);
737 50         131 $prechange->();
738 50 100       234 if ($got[0] eq &PATH) {
739 2 50       4 if ($is_first) {
740 0         0 $o->w("In First, '".&PATH."' must be assigned with =")
741             }
742 2 50       5 if ($got[1]) {
743 0         0 $o->w("Variable '".&PATH."' cannot be prepended")
744             }
745 2         2 $got[1] = 0;
746             }
747 50 100       184 $o->rejoin($how<0, @got)
748             if $mod;
749 50 100       418 $o->search_envy_path if $got[0] eq &PATH;
750              
751             } elsif ($l =~ /^([\w-]+) (:)?= (.*)$/x) {
752 32         138 my @got = ($1,$2,$3);
753 32         59 $prechange->();
754 32 50 66     147 if (!$is_first and $got[0] eq &PATH) {
755 0         0 $o->w("Variable '".&PATH."' must be edited with +=")
756             }
757 32 100       119 $o->assign($how<0, @got)
758             if $mod;
759 32 100       160 $o->search_envy_path if $got[0] eq &PATH;
760              
761             } else {
762             # Newer version of envy knows about new tokens...?
763 0         0 $o->n("Inexplicable '$l' (ignored)");
764             }
765 35         221 };
766              
767 35 100       79 if ($how>=0) {
768 23         65 for (my $line_no = 1; $line_no <= @L; $line_no++) {
769 106         192 $doline->($line_no);
770 106 50       452 last if $o->{errors} > 5;
771             }
772 23 100       126 if ($how) {
773 21 50 66     81 if ($is_first and !$o->{env}{&PATH}) {
774 0         0 $o->w("You must set '".&PATH."'")
775             }
776 21 50 66     67 if ($swap and $swap != 1) {
777 0         0 $o->set_refcnt($e, $swap)
778             } else {
779 21         60 $o->refcnt_inc($e, $by);
780             }
781             }
782             } else {
783 12         46 for (my $line_no = @L; $line_no >= 1; $line_no--) {
784 46         153 $doline->($line_no);
785 46 50       170 last if $o->{errors} > 5;
786             }
787 12 100       25 if ($how < -1) {
788 5         21 $o->set_refcnt($e, 0);
789             } else {
790 7         20 $o->refcnt_dec($e, $by);
791             }
792             }
793              
794 35         138 $o->t("[$how] $e DONE");
795 35   50     5089 $LOGIN ||= getlogin || getpwuid($<) || "?";
      66        
796 35 100       52 push @{$o->{'log'}}, { when => time(), who => $LOGIN,
  35         1484  
797             action => ($how<0?'un':'').'load',
798             what => $e };
799             }
800              
801             # ---------------------------------------------------------------
802             # DURING TRANSACTION
803              
804             sub envy { #PUBLIC
805 18     18 0 74 my ($o, $reverse, $e) = @_;
806 18 100 100     68 if ($reverse and $o->is_first($e)) {
807 1         31 $o->unload_all();
808             } else {
809 17 100       40 my $how = $reverse?-100:100;
810 17 100 100     92 $how=0 if !$reverse && $o->{loaded}{$e};
811 17         22 $Loop = 0;
812 17 100       67 local $Context = " while ".($reverse?"un":"")."loading envy '$e'.\n";
813 17         46 $o->process_envy($how, $e, '1');
814             }
815             }
816              
817             sub unload_all { #PUBLIC
818 1     1 0 3 my ($o) = @_;
819 1         2 $Loop = 0;
820 1         2 local $Context = " while unloading everything.\n";
821              
822 1         1 my @un = keys %{$o->{loaded}};
  1         6  
823 1         138 while (@un) {
824 5         18 @un = sort { $o->get_seniority($a) <=> $o->get_seniority($b) } @un;
  14         31  
825 5         8 my $e = shift @un;
826 5 100       179 next if !$o->{loaded}{$e};
827 1 50       3 $o->process_envy(-100, $e, '1') if !$o->is_first($e);
828             }
829             # make sure we didn't shoot ourself in the foot
830 1         5 $o->{unload_all}=1;
831 1         5 $o->process_envy(-1, $o->{first}, '1');
832 1         6 $o->process_envy(1, $o->{first}, '1');
833 1         7 $o->{unload_all}=0;
834             }
835              
836             # ---------------------------------------------------------------
837             # AFTER TRANSACTION COMMIT
838              
839             sub write_log { #PUBLIC
840 0     0 0 0 my ($o) = @_;
841 0 0       0 confess "transaction in progress" if $o->{transaction};
842 0         0 require FindBin;
843 0         0 my $file = "$FindBin::Bin/../var/envy.log";
844 0         0 my @stat = stat($file);
845 0 0 0     0 if (@stat and $stat[7] > 1024 * 256) {
846 0 0       0 $o->w("envy: rename $file $file.old: $!\n")
847             if !rename $file, "$file.old";
848             }
849 0         0 my $LOG = gensym;
850 0 0       0 sysopen($LOG, $file, &O_WRONLY| &O_APPEND| &O_CREAT, 0666) or
851             return $o->n("envy: open $file: $!");
852 0         0 for my $e (@{$o->{'log'}}) {
  0         0  
853 0         0 print $LOG (join("\t", scalar localtime($e->{when}),
854             $e->{who}, $o->{where}, $e->{action}, $e->{what})."\n");
855             }
856             }
857              
858             sub to_sync { #PUBLIC
859 18     18 0 68 my ($o) = @_;
860 18 50       49 confess "transaction in progress" if $o->{transaction};
861 18 50       41 return if $o->{errors};
862 18         25 my $delta = $o->{delta};
863 18         54 sort {$a->[0] cmp $b->[0]} map { [$_,$$delta{$_}] } keys %$delta;
  164         313  
  89         291  
864             }
865              
866             # ---------------------------------------------------------------
867              
868             sub warnings { #PUBLIC
869 17     17 0 251 my ($o) = @_;
870 17 50       51 Carp::cluck "use warnlevel instead of warnings(level)"
871             if @_ == 2;
872 17         27 my $all = $o->{'warn'};
873 17         29 $o->{'warn'} = [];
874 17         94 @$all;
875             }
876              
877             sub description { #PUBLIC
878 0     0 0   my($o,$mo) = @_;
879 0 0         $o->catalogue() if !keys %{$o->{desc}};
  0            
880 0           $o->{desc}{$mo};
881             }
882              
883             sub status { #PUBLIC
884 0     0 0   carp 'status is deprecated';
885 0           my ($o) = @_;
886 0           my @loaded = keys %{$o->{loaded}};
  0            
887 0           ($o->get_fullpath(),\@loaded);
888             }
889              
890             sub status2 { #PUBLIC
891 0     0 0   my ($o) = @_;
892 0           ($o->get_fullpath(),$o->{loaded});
893             }
894              
895             sub quick_status { #PUBLIC
896 0     0 0   my ($o) = @_;
897 0           ($o->{loaded}, $o->{dimen});
898             }
899              
900             sub check_fuzzy { # move to another file XXX
901 0     0 0   my ($o, $mo) = @_;
902 0 0         $o->w("Envy: non-interactive fuzzy match used to load '$mo'.\n Please use 'envy load $mo' instead of just 'envy $mo'!\n")
903             if $o->{where} ne 'shell';
904             }
905              
906             1;
907              
908             # set_defaults.PL DB.IN -> DB.pm