00001 package MMAgic::Data;
00002
00003 =head1 NAME
00004
00005 MMAgic::Data - Data object stored as file in Perl data structure.
00006
00007 =head1 SYNOPSIS
00008
00009 my $data;
00010 tie $data, 'MMAgic::Data', '/tmp/some.data';
00011
00012 print $data->{field}->[0], "\n";
00013 if ( $somethingHasChanged && tied($var)->lock)
00014 {
00015 my $temp = $data; # cause re-read
00016 # ...statements... # make changes
00017 $data->flush; # push changes out
00018 }
00019
00020 =head1 ABSTRACT
00021
00022 Tie variable to contents of data file expressed as Perl data structure.
00023 Fetch and store entire contents.
00024 Use non-tie methods lock() and flush() to update parts of the data structure.
00025
00026 =head1 DESCRIPTION
00027
00028 =head1 CAVEATS
00029
00030 =head2 C<ARRAY>
00031
00032 An attempt was made to add an C<ARRAY> tie but it doesn't work very well.
00033 Maybe someday...
00034
00035 =head2 Opcodes
00036
00037 The C<Safe> module uses opcodes that have changed over time.
00038 Current code is working for Perl 5.8.8.
00039 It will work as far back as 5.006 but may require different opcodes.
00040 The C<allw> flag in the constructor or C<$ENV{MMAgic_Data_allw}> can
00041 be used to override the opcodes.
00042
00043 When opcode issues arise, it is often difficult to figure out which
00044 opcodes to add.
00045 One way is to look up the C<opcode.h> file in the Perl source and
00046 cross-reference C<PL_op_desc[]> (which is what shows up in error
00047 messages at run-time) to C<PL_op_name[]> (which is what is required
00048 for opcode names in the C<allw> list) by array index.
00049
00050 =head1 METHODS
00051
00052 =over
00053
00054 =cut
00055
00056 require 5.006; # 5.003 WILL NOT work due to Safe and fcntl :seek issues.
00057
00058 use strict;
00059 use warnings;
00060
00061 use Fcntl ':seek', ':flock';
00062 use File::Basename;
00063 use Safe;
00064 use Symbol;
00065 use UNIVERSAL qw(isa);
00066
00067 use MMAgic::File::Status;
00068 use MMAgic::Log;
00069 use MMAgic::Stack;
00070 use MMAgic::Trace;
00071
00072 our $poll = 300;
00073
00074 # Default allowable opcodes for Safe:
00075 our @allow = qw
00076 (
00077 abs
00078 add i_add
00079 anonhash
00080 anonlist
00081 chr ord
00082 const
00083 divide i_divide
00084 hex int oct
00085 leaveeval
00086 length
00087 lineseq
00088 list
00089 modulo i_modulo
00090 multiply i_multiply
00091 padany
00092 pow
00093 pushmark
00094 refgen
00095 scalar
00096 stub
00097 subtract i_subtract
00098 );
00099
00100 ###########################################################################
00101
00102 =item C<_arg($arg, $init, $dflt)>
00103
00104 Look for intialization parameter.
00105
00106 Check in C<$init> value, then environment variable of the form
00107 C<$ENV{"MMAgic_Data_$arg}>, then C<$dflt> value.
00108
00109 Intended for internal use.
00110
00111 =cut
00112
00113 sub _arg
00114 {
00115 my ($arg, $init, $dflt) = @_;
00116 my $val = isa($init, 'HASH') && $init->{$arg};
00117
00118 $val = $ENV{"MMAgic_Data_$arg"} unless defined $val;
00119 $val = $dflt unless defined $val;
00120 $val
00121 }
00122
00123 ###########################################################################
00124 ###########################################################################
00125
00126 =item C<new($class, $path, %init)>
00127
00128 Constructor for MMAgic::Data object.
00129
00130 Flags: (default) (description)
00131 ----------------------------------------------------------------------
00132 * allw (below) allowable operations during file eval
00133 * barf 1 die() if file doesn't exist
00134 * dpth 15 max depth to store when flushing to file
00135 * dflt default value for data if no file
00136 goto label in file to skip past before loading
00137 make create file if necessary to store data (???)
00138 * poll 300 time (sec) to wait before checking to see if
00139 file has been changed and requires reloading
00140 ----------------------------------------------------------------------
00141 * Flag is settable via $ENV{MMAgic_Data_$flag}
00142
00143 =cut
00144
00145 #=| @param $path Pathname of data file.
00146 #=| @param %init Flags used in object creation
00147
00148 sub new
00149 {
00150 my $class = shift;
00151 my ($name, $path, $suff) = fileparse(shift, '\.[^\.]*');
00152 #!# my $auto = Log->method($class, $path, '%', @_)->block;
00153 my %init = @_;
00154 my $self = bless { }, $class;
00155
00156 $suff = '.data' unless $suff;
00157
00158 $self->{allw} = isa($init{allow}, 'ARRAY') ? $init{allow} : \@allow;
00159 $self->{barf} = _arg('barf', \%init, 1);
00160 $self->{dflt} = _arg('dflt', \%init);
00161 $self->{dpth} = _arg('dpth', \%init, 15);
00162 $self->{labl} = $init{'goto'};
00163 $self->{lazy} = $init{'lazy'};
00164 $self->{make} = $init{'make'};
00165 $self->{path} = "$path$name$suff";
00166 $self->{poll} = _arg('poll', \%init, $poll);
00167 $self->{fsts} =
00168 new MMAgic::File::Status($path, poll => $self->{poll});
00169
00170 return $self
00171 if $self->{lazy};
00172
00173 $self->fetch && $self
00174 }
00175
00176 ###########################################################################
00177
00178 =item C<DESTROY($self)>
00179
00180 Destructor for MMAgic::Data object.
00181
00182 Invokes cleanup() routine.
00183
00184 =cut
00185
00186 sub DESTROY
00187 {
00188 #!# my $auto = Log->method($_[1])->block;
00189 #!# MMAgic::Stack::trace(1);
00190 $_[0]->cleanup;
00191 }
00192
00193 ###########################################################################
00194 ###########################################################################
00195
00196 =item C<cleanup($self)>
00197
00198 Cleanup open file and locks.
00199
00200 Intended for internal use.
00201
00202 =cut
00203
00204 sub cleanup
00205 {
00206 #!# my $auto = Log->method($_[1])->block;
00207 my $self = shift;
00208
00209 $self->unlock;
00210
00211 if ($self->{handle})
00212 {
00213 close $self->{handle};
00214 undef $self->{handle};
00215 }
00216
00217 undef
00218 }
00219
00220 ###########################################################################
00221
00222 =item C<handle($self, $writable)>
00223
00224 Open file, return file handle.
00225
00226 Intended for internal use.
00227
00228 =cut
00229
00230 sub handle
00231 {
00232 #!# my $auto = Log->method($_[1])->block;
00233 my $self = shift;
00234
00235 return $self->{handle} if $self->{locked};
00236
00237 my $writable = shift;
00238
00239 unless ($self->{handle} && ! $writable)
00240 {
00241 #!# Log->trace('creating new handle'); #[detail]
00242
00243 if (defined $self->{handle})
00244 {
00245 # Open for reading, must be closed
00246 # and re-opened for writing:
00247 close $self->{handle};
00248 undef $self->{handle};
00249 }
00250
00251 Log->error('No pathname in Data tie'),
00252 #!# MMAgic::Stack::trace(3),
00253 return undef
00254 unless $self->{path};
00255
00256 #!# Log->trace('check for non-existent data file'); #[detail]
00257
00258 unless ($writable || -e $self->{path})
00259 {
00260 Log->crash("Non-existent data file:\n ", $self->{path})
00261 if $self->{barf} && ! $self->{dflt};
00262
00263 return undef;
00264 }
00265
00266 $self->{handle} = gensym;
00267
00268 # my $mode = ! -e $self->{path} ? '>' :
00269 # $writable ? '+<' : '<';
00270
00271 my $mode = ($writable ? '+' : '')
00272 . (-e $self->{path} ? '<' : '>');
00273
00274 #!# Log->trace('opening file ', $self->{path}); #[detail]
00275
00276 unless (open($self->{handle}, "$mode $self->{path}"))
00277 {
00278 Log->crash("Unable to '$mode' open data file:\n ",
00279 $self->{path}, "\n $!")
00280 if $self->{barf} && ! $self->{dflt};
00281
00282 return undef($self->{handle})
00283 }
00284 }
00285
00286 $self->{handle}
00287 }
00288
00289 ###########################################################################
00290
00291 =item C<_load($self, $handle)>
00292
00293 Load and return data from file open on handle.
00294 Default version does an eval on the file contents
00295 protected by a Safe object with only basic opcodes allowed.
00296
00297 Intended for internal use.
00298
00299 =cut
00300
00301 sub _load
00302 {
00303 my $self = shift;
00304 #!# my $auto = Log->method(@_)->block;
00305 my $handle = shift;
00306 my $result = undef;
00307 my @text;
00308
00309 seek $handle, 0, SEEK_SET;
00310
00311 if (@text = <$handle>)
00312 {
00313 if ($self->{labl})
00314 {
00315 while (my $text = shift @text)
00316 {
00317 chomp $text;
00318 #!# Log->trace('Skipping: ', $text),
00319 last if $text eq $self->{labl};
00320 }
00321 }
00322
00323 # Use Safe for evaluation:
00324 my $safe = new Safe;
00325
00326 Log->warning("Unable to create Safe object:\n $!"),
00327 return undef
00328 unless new Safe;
00329
00330 $safe->permit_only(@{$self->{allw}});
00331 $result = $safe->reval(join '', @text);
00332
00333 if ($@)
00334 {
00335 undef $result;
00336 Log->crash("Unable to parse data file:\n ",
00337 $self->{path}, "\n $@")
00338 if $self->{barf}
00339 && ! $self->{dflt};
00340 }
00341
00342 elsif (! defined $result)
00343 {
00344 Log->crash("Unable to execute data file:\n ",
00345 $self->{path}, "\n $!")
00346 if $self->{barf}
00347 && ! $self->{dflt};
00348 }
00349
00350 elsif (! $self->{type})
00351 {
00352 # No pre-specified type to check against.
00353 }
00354
00355 elsif (! isa($result, $self->{type}))
00356 {
00357 undef $result;
00358 Log->crash("Data file didn't return $self->{type}:\n ",
00359 ref($result), "\n");
00360 }
00361 }
00362
00363 elsif ($self->{barf} && ! $self->{dflt})
00364 {
00365 Log->crash("No lines of text in data file:\n ", $self->{path});
00366 #!# MMAgic::Stack::trace(5),
00367 }
00368
00369 $result = $self->{dflt}
00370 unless defined $result;
00371
00372 $self->{loaded} = time
00373 if defined $result;
00374
00375 $result
00376 }
00377
00378 ###########################################################################
00379 ###########################################################################
00380
00381 =item C<fetch($self)>
00382
00383 Fetch data value itself.
00384 Value is a scalar reference.
00385
00386 =cut
00387
00388 sub fetch
00389 {
00390 my $self = shift;
00391 #!# my $auto = Log->method($self)->block;
00392
00393 # Then check to see if the file has newer data than what we have:
00394 unless ($self->upToDate)
00395 {
00396 #!# Log->trace("Reloading data from:\n ", $self->{path}); #[reload]
00397 #!# Log->trace(' no data cached') unless $self->{data}; #[reload]
00398 #!# Log->trace(' data file changed') unless $self->{loaded} >= $self->{mtime}; #[reload]
00399
00400 undef $self->{data} if defined $self->{data};
00401
00402 my $handle = $self->handle;
00403
00404 #!# Log->trace('Handle: ', $handle);
00405
00406 Log->warning("Unable to lock data file for reading:\n ",
00407 $self->{path}, "\n $!"),
00408 return $self->cleanup,
00409 unless $self->{locked} || flock($handle, LOCK_SH);
00410
00411 $self->{data} = $self->_load($handle);
00412
00413 # After fetching from the file, close it:
00414 unless ($self->{locked})
00415 {
00416 # If we're a FETCH() nested w/in a pending lock()/flush(),
00417 # don't close open file handle or lose the lock!!!
00418 #!# Log->trace("closing file handle for\n ", $self->{path}); #[detail]
00419 close $self->{handle};
00420 undef $self->{handle};
00421 }
00422 }
00423
00424 #!# Log->trace('Returning data: ', $self->{data});
00425
00426 $self->{data}
00427 }
00428
00429 ###########################################################################
00430
00431 =item C<flush($self)>
00432
00433 Update current value to backing store.
00434 If it fails, the data remains the same.
00435
00436 =cut
00437
00438 sub flush
00439 {
00440 #!# my $auto = Log->method($_[1])->block;
00441 my $self = shift;
00442
00443 return undef unless $self->{locked};
00444
00445 # File is open and locked, OK to make changes,
00446 # begin by rewinding and truncating (hope this works):
00447 seek $self->{handle}, 0, SEEK_SET;
00448 truncate $self->{handle}, 0;
00449
00450 my $out = new MMAgic::Output(output => $self->{handle});
00451
00452 $self->cleanup, # well,
00453 undef($self->{data}), # we're fucked anyway
00454 return undef
00455 unless isa($out, 'MMAgic::Output');
00456
00457 #!# Log->trace('dumping:')->reference($self->{data}); #[detail]
00458
00459 $out->dumpref($self->{data}, maxDepth => $self->{dpth});
00460 $out->close;
00461 undef $out;
00462
00463 $self->cleanup;
00464 undef $self->{data}; # force re-read next time
00465
00466 1
00467 }
00468
00469 ###########################################################################
00470
00471 =item C<loaded($self)>
00472
00473 Return load time if data currently loaded.
00474
00475 =cut
00476
00477 sub loaded
00478 {
00479 my $self = shift;
00480
00481 $self->{data} && $self->{loaded}
00482 }
00483
00484 ###########################################################################
00485
00486 =item C<lock($self)>
00487
00488 Lock for (reading and) writing preparatory to making changes.
00489 Next read from variable will return new data.
00490 Return true if successful, C<undef> on failure.
00491
00492 Call using:
00493
00494 tied($data)->lock;
00495
00496 Note that file is also opened now!!!
00497
00498 =cut
00499
00500 sub lock
00501 {
00502 #!# my $auto = Log->method($_[1])->block;
00503 my $self = shift;
00504
00505 return 1 if $self->{locked};
00506 return undef unless $self->handle(1);
00507
00508 Log->warning("Unable to lock data file for writing:\n ",
00509 $self->{path}, "\n $!"),
00510 return $self->cleanup,
00511 unless flock($self->{handle}, LOCK_EX);
00512
00513 #!# Log->trace(' - locked'); #[detail]
00514 undef $self->{data}; # force re-read next time
00515 $self->{locked} = 1
00516 }
00517
00518 ###########################################################################
00519
00520 =item C<path($self)>
00521
00522 Return the path of the file containing the underlying data.
00523
00524 =cut
00525
00526 sub path
00527 {
00528 $_[0]->{path}
00529 }
00530
00531 ###########################################################################
00532
00533 =item C<refresh($self)>
00534
00535 Cause underlying data to be refreshed.
00536
00537 =cut
00538
00539 sub refresh
00540 {
00541 #!# my $auto = Log->method($_[1])->block;
00542 my $self = shift;
00543
00544 return if $self->{locked};
00545
00546 undef $self->{data} if defined $self->{data};
00547 }
00548
00549 ###########################################################################
00550
00551 =item C<store($self, $data)>
00552
00553 Store data into backing file.
00554 Data is undefined in case of error.
00555 New data returned.
00556
00557 =cut
00558
00559 sub store
00560 {
00561 my $self = shift;
00562 #!# my $auto = Log->method($self)->block;
00563 my $data = shift;
00564
00565 #!# Log->reference($data); #[detail]
00566 #!# MMAgic::Stack::trace(3);
00567
00568 Log->warning("Data object undefined during STORE"),
00569 #!# MMAgic::Stack::trace,
00570 return undef($self->{data})
00571 unless defined $data;
00572
00573 Log->warning("Data object not a reference"),
00574 # TBD: but could it be a scalar?
00575 return undef($self->{data})
00576 unless ref $data;
00577
00578 return undef($self->{data})
00579 unless $self->lock;
00580
00581 $self->{data} = $data;
00582
00583 return undef($self->{data})
00584 unless $self->flush;
00585
00586 $self->{data}
00587 }
00588
00589 ###########################################################################
00590
00591 =item C<unlock($self)>
00592
00593 Release lock on tied object.
00594 Returns without error if not locked.
00595
00596 =cut
00597
00598 sub unlock
00599 {
00600 my $self = shift;
00601 #!# my $auto = Log->method(@_)->block;
00602
00603 return unless $self->{locked};
00604
00605 flock($self->{handle}, LOCK_UN);
00606 close($self->{handle});
00607 undef $self->{handle};
00608 undef $self->{locked};
00609
00610 # don't force re-read, this is done in flush(),
00611 # which makes sense as there changes have been made
00612 }
00613
00614 ###########################################################################
00615
00616 =item C<upToDate($self)>
00617
00618 Return true if the data is current.
00619
00620 =cut
00621
00622 sub upToDate # $self
00623 {
00624 my $self = shift;
00625 #!# my $auto = Log->method(@_)->block;
00626
00627 #!# Log->trace('Data: ', $self->{data});
00628 #!# Log->trace('Load: ', $self->{loaded});
00629 #!# Log->trace('mTim: ', $self->{fsts}->mtime);
00630
00631 return undef unless $self->{data};
00632 return undef unless $self->{loaded};
00633 return 1 unless $self->{fsts}->mtime; # non-sensical?
00634
00635 $self->{loaded} >= $self->{fsts}->mtime
00636 }
00637
00638 ###########################################################################
00639 ###########################################################################
00640
00641 sub TIESCALAR # $class, $path, %init
00642 {
00643 #!# my $auto = Log->method(@_)->block;
00644
00645 new(@_)
00646 }
00647
00648 ###########################################################################
00649 ###########################################################################
00650
00651 sub TIEARRAY # $class, $path, %init
00652 {
00653 #!# my $auto = Log->method(@_)->block;
00654
00655 new(@_, type => 'ARRAY')
00656 }
00657
00658 ###########################################################################
00659
00660 sub CLEAR # $self, $index
00661 {
00662 Log->warning('No array data for CLEAR'),
00663 return
00664 unless isa($_[0]->fetch, 'ARRAY');
00665
00666 $_[0]->{data} = [ ]
00667 }
00668
00669 ###########################################################################
00670
00671 sub DELETE # $self, $index
00672 {
00673 Log->warning('No array data for DELETE'),
00674 return
00675 unless isa($_[0]->fetch, 'ARRAY');
00676
00677 delete $_[0]->[$_[1]]
00678 }
00679
00680 ###########################################################################
00681
00682 sub EXISTS # $self, $index
00683 {
00684 isa($_[0]->fetch, 'ARRAY') && defined $_[0]->[$_[1]]
00685 }
00686
00687 ###########################################################################
00688
00689 sub EXTEND # $self, $count
00690 {
00691 STORESIZE(@_)
00692 }
00693
00694 ###########################################################################
00695
00696 sub FETCHSIZE # $self
00697 {
00698 scalar @{$_[0]->fetch}
00699 }
00700
00701 ###########################################################################
00702
00703 sub POP # $self
00704 {
00705 Log->warning('No array data for POP'),
00706 return
00707 unless isa($_[0]->fetch, 'ARRAY');
00708
00709 pop @{$_[0]->{data}}
00710 }
00711
00712 ###########################################################################
00713
00714 sub PUSH # $self, $value
00715 {
00716 Log->warning('No array data for PUSH'),
00717 return
00718 unless isa($_[0]->fetch, 'ARRAY');
00719
00720 push @{$_[0]->{data}}, $_[1]
00721 }
00722
00723 ###########################################################################
00724
00725 sub SHIFT # $self
00726 {
00727 Log->warning('No array data for SHIFT'),
00728 return
00729 unless isa($_[0]->fetch, 'ARRAY');
00730
00731 shift @{$_[0]->{data}}
00732 }
00733
00734 ###########################################################################
00735
00736 sub SPLICE # $self, $offset, $length, @list
00737 {
00738 my $self = shift;
00739
00740 Log->warning('No array data for SPLICE'),
00741 return
00742 unless isa($self->fetch, 'ARRAY');
00743
00744 splice @{$self->{data}}, @_
00745 }
00746
00747 ###########################################################################
00748
00749 sub STORESIZE # $self, $count
00750 {
00751 my ($self, $count) = @_;
00752
00753 Log->warning('No array data for STORESIZE'),
00754 return
00755 unless isa($self->fetch, 'ARRAY');
00756
00757 push @{$self->{data}}, undef
00758 while @{$self->{data}} < $count
00759 }
00760
00761 ###########################################################################
00762
00763 sub UNSHIFT # $self, $value
00764 {
00765 Log->warning('No array data for UNSHIFT'),
00766 return
00767 unless isa($_[0]->fetch, 'ARRAY');
00768
00769 unshift @{$_[0]->{data}}, $_[1]
00770 }
00771
00772 ###########################################################################
00773 ###########################################################################
00774
00775 sub FETCH # $self [, $index ]
00776 {
00777 my $self = shift;
00778
00779 return $self->{data}->[$_[0]]
00780 if isa($self->fetch, 'ARRAY')
00781 && defined $_[0];
00782
00783 # default version:
00784 $self->fetch
00785 }
00786
00787 ###########################################################################
00788
00789 sub STORE # $self, [ $index, ] $value
00790 {
00791 my $self = shift;
00792 #!# my $auto = Log->method(@_)->block;
00793
00794 if (isa($self->fetch, 'ARRAY') && $_[1] && $self->{locked})
00795 {
00796 $self->{data}->[$_[0]] = $_[1]
00797 }
00798
00799 else
00800 {
00801 $self->store(@_)
00802 }
00803 }
00804
00805 ###########################################################################
00806
00807 sub UNTIE # $self
00808 {
00809 #!# my $auto = Log->method($_[1])->block;
00810 $_[0]->cleanup;
00811 }
00812
00813 ###########################################################################
00814 ###########################################################################
00815
00816 1
00817
00818 __END__
00819
00820 =back
00821
00822 =head1 AUTHOR
00823
00824 Marc M. Adkins, L<mailto:Perl@Doorways.org>
00825
00826 =head1 COPYRIGHT AND LICENSE
00827
00828 Copyright 2001-2008 by Marc M. Adkins
00829
00830 =cut