File Coverage

blib/lib/Unix/Lsof/Result.pm
Criterion Covered Total %
statement 18 182 9.8
branch 0 72 0.0
condition 0 24 0.0
subroutine 6 25 24.0
pod 9 9 100.0
total 33 312 10.5


line stmt bran cond sub pod time code
1             package Unix::Lsof::Result;
2              
3 3     3   75 use 5.008;
  3         9  
  3         121  
4 3     3   16 use version; our $VERSION = qv('0.1.0');
  3         5  
  3         24  
5              
6 3     3   239 use warnings;
  3         6  
  3         122  
7 3     3   19 use strict;
  3         5  
  3         126  
8 3     3   17 use Unix::Lsof;
  3         12  
  3         296  
9              
10             use overload bool => sub {
11 0     0     my ($self) = @_;
12 0 0 0       if ( $self->{error} && (!keys %{$self->{output}}) ) {
  0            
13 0           return;
14             } else {
15 0           return 1;
16             }
17 3     3   18 };
  3         6  
  3         40  
18              
19             sub _new {
20 0     0     my ( $class, $parsed, $err, $raw,$opt ) = @_;
21              
22 0           my $self = {
23             output => $parsed,
24             error => $err,
25             _raw_output => $raw,
26             options => $opt,
27             };
28 0           bless $self, $class;
29              
30 0           $self->_get_field_ids();
31 0           return $self;
32             }
33              
34             sub has_errors {
35 0     0 1   my $self = shift;
36 0 0         return $self->{error} ? 1 : 0;
37             }
38              
39             sub errors {
40 0     0 1   my $self = shift;
41 0           return $self->{error};
42             }
43              
44             sub get_pids {
45 0     0 1   my $self = shift;
46 0           my @params = ( "process id");
47 0 0         unshift @params,$_[0] if ref( $_[0] );
48 0           my @ret =$self->get_values( @params );
49 0 0         return wantarray ? @ret : \@ret;
50             }
51              
52             sub get_filenames {
53 0     0 1   my $self = shift;
54 0           my @params = ( "file name" );
55 0 0         unshift @params,$_[0] if ref( $_[0] );
56 0           my @ret = $self->get_values( @params );
57 0 0         return wantarray ? @ret : \@ret;
58             }
59              
60             sub get_values {
61 0     0 1   my ( $self, @args ) = @_;
62 0           my @col = $self->get_arrayof_columns(@args);
63 0 0         return if (!defined $col[0]);
64 0 0         return wantarray ? @{$col[0]} : $col[0];
  0            
65             }
66              
67             sub get_arrayof_columns {
68 0     0 1   my ( $self, @args ) = @_;
69 0           my @rows = $self->get_arrayof_rows(@args);
70              
71 0           my @cols;
72 0           my $i = 0;
73 0           for my $r (@rows) {
74 0           my $j = 0;
75 0           for my $c (@$r) {
76 0           $cols[ $j++ ][$i] = $c;
77             }
78 0           $i++;
79             }
80 0 0         return wantarray ? @cols : \@cols;
81             }
82              
83             sub get_hashof_columns {
84 0     0 1   my ( $self, @args ) = @_;
85 0           my @cols = $self->get_arrayof_columns(@args);
86              
87 0           my %return;
88 0           for my $i ( 0 .. $#cols ) {
89 0           $return{ $args[$i] } = $cols[$i];
90             }
91              
92 0 0         return wantarray ? %return : \%return;
93             }
94              
95             sub get_hashof_rows {
96 0     0 1   my ( $self, @args ) = @_;
97              
98 0           my ( $key, %ret );
99 0 0         if ( ref( $args[0] ) eq ref( {} ) ) {
100 0           $self->{_query}{filter} = shift @args;
101             }
102              
103 0           ( $key, @{ $self->{_query}{inp_fields} } ) = @args;
  0            
104              
105 0   0       my $full_key = $Unix::Lsof::op_field{$key} || $key;
106              
107 0           $full_key =~ s/_/ /g;
108              
109 0           $self->_setup_fields();
110 0           $self->_setup_filter();
111              
112 0           my %outp = %{ $self->{output} };
  0            
113              
114 0           my %uniqify;
115 0           for my $pid ( keys %outp ) {
116 0           LINELOOP:
117 0           for my $file ( @{ $outp{$pid}{files} } ) {
118 0   0       my $hkey = $self->_get_value( $full_key, $pid, $file ) || next LINELOOP;
119 0   0       my $line = $self->_get_line( $pid, $file ) || next LINELOOP;
120              
121 0           my $i = 0;
122 0           my %rline;
123              
124 0           for my $l (@$line) {
125 0 0         if (defined $l) {
126 0           $rline{ $self->{_query}{inp_fields}[ $i ] } = $l;
127             }
128 0           $i++;
129             }
130 0           my $ukey = join $;, sort values %rline;
131 0 0         next LINELOOP if !defined $ukey;
132              
133 0 0 0       if ( !exists $uniqify{$hkey} || !exists $uniqify{$hkey}{$ukey} ) {
134 0           push @{ $ret{$hkey} }, \%rline;
  0            
135 0           $uniqify{$hkey}{$ukey}=1;
136             }
137             }
138             }
139 0           delete $self->{_query};
140 0 0         return wantarray ? %ret : \%ret;
141             }
142              
143             sub get_arrayof_rows {
144 0     0 1   my ( $self, @args ) = @_;
145              
146 0           my @ret;
147              
148 0 0         if ( ref( $args[0] ) eq ref( {} ) ) {
149 0           $self->{_query}{filter} = shift @args;
150             }
151              
152 0           $self->{_query}{inp_fields} = \@args;
153              
154 0           $self->_setup_fields();
155 0           $self->_setup_filter();
156              
157 0           my %outp = %{ $self->{output} };
  0            
158              
159 0           my %uniqify;
160 0           for my $pid ( keys %outp ) {
161 0           ROWLOOP:
162 0           for my $file ( @{ $outp{$pid}{files} } ) {
163 0   0       my $line = $self->_get_line( $pid, $file ) || next ROWLOOP;
164              
165 0 0         push @ret, $line if ( !$uniqify{ join $;, map { defined $_ ? $_ : "" } @$line }++ );
  0 0          
166             }
167             }
168 0           delete $self->{_query};
169 0 0         return wantarray ? @ret : \@ret;
170             }
171              
172             sub _setup_filter {
173 0     0     my $self = shift;
174 0 0         return if ( !exists $self->{_query}{filter} );
175              
176 0           %{ $self->{_query}{filter} } =
  0            
177 0           map { my $t = $_; $t =~ s/_/ /g;
  0            
178 0   0       $Unix::Lsof::op_field{$t} || $t => $self->{_query}{filter}{$_} }
179 0           keys %{ $self->{_query}{filter} };
180              
181 0           my %check_filter = map { $_ => 1 } @{ $self->{_query}{ret_fields} };
  0            
  0            
182 0           @{ $self->{_query}{force_validate} } = grep { !exists $check_filter{$_} }
  0            
  0            
183 0           keys %{ $self->{_query}{filter} };
184             }
185              
186             sub _setup_fields {
187 0     0     my $self = shift;
188 0           my ( @temp_fields, @ret_fields );
189              
190             # Use either field designators or names
191 0           @ret_fields =
192 0 0         map { $_ =~ s/_/ /g; $Unix::Lsof::op_field{$_} || $_ }
  0            
193 0           @{ $self->{_query}{inp_fields} };
194              
195 0           for my $f (@ret_fields) {
196 0 0 0       if ( exists $self->{_program_field_ids}{$f} ||
197             exists $self->{_file_field_ids}{$f}
198             ) {
199 0           push @temp_fields, $f;
200             } else {
201 0           $self->_iwarn("$f is not in the list of fields returned by lsof");
202             }
203             }
204              
205 0           $self->{_query}{ret_fields} = \@temp_fields;
206             }
207              
208             sub _validate {
209 0     0     my ( $self, $filter_key, $value ) = @_;
210              
211 0           my $filter = $self->{_query}{filter}{$filter_key};
212              
213 0 0         return if !defined $value;
214              
215 0 0 0 0     if ( ref($filter) eq ref( sub { } ) ) {
  0 0          
    0          
    0          
216 0           return $filter->($value);
217             } elsif ( ref($filter) eq ref(qr//) ) {
218 0 0         return $value =~ $filter ? 1 : 0;
219             } elsif ( $filter =~ m/\A\d+\z/ && $value =~ m/\A\d+\z/) {
220 0 0         return $filter == $value ? 1 : 0;
221             } elsif ( !ref($filter) ) {
222 0 0         return $filter eq $value ? 1 : 0;
223             } else {
224 0           $self->_iwarn(qq(Invalid filter specified for "$filter_key"));
225             }
226             }
227              
228              
229             sub _get_line {
230 0     0     my ( $self, $pid, $file ) = @_;
231 0           my @line;
232              
233 0           for my $force ( @{ $self->{_query}{force_validate} } ) {
  0            
234 0           my $val = $self->_get_value( $force, $pid, $file );
235 0 0         $self->_validate( $force, $val ) || return;
236             }
237              
238 0           for my $field ( @{ $self->{_query}{ret_fields} } ) {
  0            
239              
240 0           my $val = $self->_get_value( $field, $pid, $file );
241              
242 0 0         if ( exists( $self->{_query}{filter}{$field} ) ) {
243 0 0         $self->_validate( $field, $val ) || return;
244             }
245              
246 0           push @line, $val;
247             }
248 0           return \@line;
249             }
250              
251             sub _get_value {
252 0     0     my ( $self, $name, $pid, $file ) = @_;
253 0 0         my $ret =
254             exists $self->{_program_field_ids}{$name}
255             ? $self->{output}{$pid}{$name}
256             : $file->{$name};
257 0           return $ret;
258             }
259              
260             sub _get_field_ids {
261 0     0     my $self = shift;
262              
263 0           for my $pid ( keys %{ $self->{output} } ) {
  0            
264 0           for my $pfield ( keys %{ $self->{output}{$pid} } ) {
  0            
265 0 0         if ( $pfield eq "files" ) {
266 0           for my $file ( @{ ${ $self->{output} }{$pid}{files} } ) {
  0            
  0            
267 0           for my $id ( keys %$file ) {
268 0           $self->{_file_field_ids}{$id}++;
269             }
270             }
271             } else {
272 0           $self->{_program_field_ids}{$pfield}++;
273             }
274             }
275             }
276             }
277              
278              
279             sub _iwarn {
280 0     0     my $self = shift;
281 0           my $message = $_[0];
282 0 0         if ( $self->{options}{suppress_errors} ) {
283 0           $self->{error} .= $message;
284             } else {
285 0           warn @_;
286             }
287             }
288              
289              
290             =head1 NAME
291              
292             Unix::Lsof::Result - Perlish interface to lsof output
293              
294              
295             =head1 VERSION
296              
297             This document describes Unix::Lsof::Result version 0.1.0
298              
299              
300             =head1 SYNOPSIS
301              
302             use Unix::Lsof;
303              
304             my $lr = lsof("-p",$$);
305              
306             if ($lr->has_errors()) {
307             print qq(Errors encountered: $lr->errors());
308             }
309              
310             my @pids = $lr->get_pids();
311             my @file_types = $lr->get_values( "file type" );
312             my $access_modes = $lr->get_values( "access mode" );
313              
314             # Print out file name and type
315             my @filenames = $lr->get_arrayof_rows( "file type", "file name" );
316             for my $p (@filenames) {
317             print "File type: $p->[0] - File name: $p->[1]\n";
318             }
319              
320             # Print a list of open IPv4 connections
321             my %filetype = $lr->get_hashof_rows( "file type", "n", "protocol name" );
322             for my $conn ( @{ $filetype{"IPv4"} } ) {
323             print qq(IPv4 connection to: $conn->{"n"}, protocol: $conn->{"protocol name"}\n);
324             }
325              
326             # Print out a list of open files larger than 1kb
327             my @filesize = $lr->get_arrayof_columns( "file name", "file size" );
328             for my $i ( 0..scalar( @{ $filesize[1] } ) ) {
329             if ( $filesize[1][$i] >= 1024 ) {
330             print "File $filesize[0][$1] is over 1k\n";
331             }
332             }
333              
334             # Print out the size of text files found
335             my $fs = $lr->get_hashof_columns( "file name", "file size" );
336             for my $i ( 0..scalar( @{ $fs->{"file name"} } ) ) {
337             if ( $fs->{"file name"}[$i] =~ m/\.txt\z/ ) {
338             print qq(Found $fs->{"file size"}[$i] bytes large text file\n);
339             }
340             }
341              
342             # The same as previous, using filters
343             my @file_list = $lr->get_values( { "file name" => qr/\.txt\z/ },
344             "file size" );
345             for my $f (@file_list) {
346             print qq(Found $f bytes large text file);
347             }
348              
349             # Looking for text files between 1 and 4k
350             my @file_list = $lr->get_filenames( { "file name" => qr/\.txt\z/,
351             "file size" => sub { $_[0] > 1024 &&
352             $_[0] <= 4096 }
353             }, "file name");
354              
355              
356             =head1 DESCRIPTION
357              
358             This module offers multiple ways of organising and retrieving the data returned
359             by lsof. It attempts to make it as easy as possible for the user to obtain the
360             information he wants in the way he wants it.
361              
362             The C object is returned when calling C<lsof()>>
363             in scalar context. When evaluated in boolean context the object will evaluate to
364             true unless no STDOUT output is obtained from running the C binary and
365             STDERR output was obtained from the same run. This allows for the following
366             logic :
367              
368             if ($lf = $lsof(@params)) {
369             # we got output or no errors, some success was had
370             warn "Errors: ".$lf->errors()
371             if $lf->has_errors();
372             # normal processing continues
373             # ...
374             } else {
375             # no output and we have an error message, something is badly wrong
376             die "Errors: ".$lf->errors();
377             }
378              
379             Note that you will only find out whether B errors were encountered by
380             examining the C return value, examining truth of the object itself
381             only makes sense if you just care about some valid output being returned (e.g.
382             if you're passing in a list of files, some of which may not exist).
383              
384             All of the output accessor methods (i.e. the methods starting with C, for
385             example C, C) have the following properties:
386              
387             =over 4
388              
389             =item *
390              
391             Only return unique values
392              
393             All output accessor methods will only return unique result sets, e.g. if
394             a single file is opened by multiple programs
395              
396             $lf->get_arrayof_rows( "file name", "process id");
397              
398             will return as many rows as there are processes opening the file, whereas
399              
400             $lf->get_arrayof_rows( "file name", "file type");
401              
402             will only return a single row, since file name and file type are the same for
403             all return sets.
404              
405             =item *
406              
407             Returns list or reference depending on calling context
408              
409             The accessor methods are sensitive to their calling context and will return
410             either a list or a reference to an array/hash.
411              
412             # This will return a list
413             @e = $lf->get_pids();
414              
415             # This will return an array reference
416             $e = $lf->get_pids()
417              
418             =item *
419              
420             Fields can be specified either with their full name or single character
421              
422             When specifying a list of fields which you want returned from the accessor, you
423             can either use the single character associated with that field (see the lsof man
424             page section "OUTPUT FOR OTHER PROGRAMS" for a list of these) or the full field
425             name as given in ther C perldoc, or the full field name with spaces
426             replaced by underscores (e.g. file_name instead of "file name").
427              
428             =item *
429              
430             Filters
431              
432             The method can optionally be provided with a "filter" which limits the
433             data returned. For this, pass a hash reference as the first argument, of the
434             format
435              
436             {
437             => ,
438             => ,
439             ...
440             }
441              
442             where can be either a scalar value, a reference to a regular expression
443             or a reference to a subroutine. Only record sets that match on all fields will
444             be returned. A subroutine must return true to "match", the field value is passed
445             in normally as the first element of the @_ array.
446              
447             Example:
448              
449             {
450             "process id" => 4242,
451             "n" => qr/\.txt\z/i,
452             "command_name" => sub { $_[0] =~ m/kde/ || $_[0] eq "cupsd" }
453             }
454              
455             Limitations: is is very well possible to specify a filter that completely excludes
456             any files, e.g.
457              
458             { "process id" => "-1" }
459              
460             . Also, to specify more than one condition on a given field name (e.g. greater
461             than 100 but not 105) you need to use a sub e.g.
462              
463             { "process id" => sub { $_[0] > 100 && $_[0] != 105 } }
464              
465             The same goes if you need to C a set of constraints. Note that there is
466             currently no way to C constraints over several fields (e.g. process id equals
467             1000 or user id equals 42).
468              
469              
470             =back
471              
472             =head1 INTERFACE
473              
474             =head2 Methods
475              
476             =over 4
477              
478             =item C
479              
480             $lf->has_errors();
481              
482             Returns true if the call to the lsof binary returned any STDERR output.
483              
484             =item C
485              
486             $lf->errors();
487              
488             Returns the STDERR output of the lsof binary in a single string. WARNING: it is
489             possible that this B change in some future version to allow for more
490             sophisticated error handling (though using the result of this subroutine as a
491             simple string will almost certainly continue to be supported).
492              
493             =item C
494              
495             $lf->get_values( $field_name );
496             $lf->get_values( $filter, $field_name );
497              
498             Returns a list with the values for a single field.
499              
500             =item C
501              
502             $lf->get_values();
503             $lf->get_values( $filter );
504              
505             Specialised version of get_values which returns a list of process ids.
506              
507             =item C
508              
509             $lf->get_filenames();
510             $lf->get_filenames( $filter );
511              
512             Specialised version of get_values which returns a list of file names.
513              
514             =item C
515              
516             $lf->get_arrayof_rows( @column_names );
517             $lf->get_arrayof_rows( $filter, @column_names );
518              
519             Returns a list of array references, each of which corresponds to a row of lsof
520             output. The order of the values in the referenced arrays corresponds to the
521             order of the parameters passed in; e.g. a call to
522              
523             $lf->get_arrayof_rows( "file name", "file type");
524              
525             would produce a data structure like this
526              
527             [
528             [ filename1, filetype1 ],
529             [ filename2, filetype2 ],
530             ...
531             ]
532              
533             =item C
534              
535             $lf->get_arrayof_columns( @column_names );
536             $lf->get_arrayof_columns( $filter, @column_names );
537              
538             Returns a list of array references, each of which correspond to a field name
539             column. The order of array references correspond to the order of parameters
540             passed in, e.g. a call to
541              
542             $lf->get_arrayof_columns( "file name", "file type");
543              
544             would produce a data structure like this
545              
546             [
547             [ filename1, filename2, ... ],
548             [ filetype1, filetype2, ... ]
549             ]
550              
551             =item C
552              
553             $lf->get_hashof_columns( @column_names );
554             $lf->get_arrayof_columns( $filter, @column_names );
555              
556             Returns a hash references (or list which can be assigned to a hash). The hash
557             keys are the column names specified in the parameters, the hash values are array
558             references with the column values. E.g. a call to
559              
560             $lf->get_hashof_columns( "file name", "file type");
561              
562             would produce a data structure like this
563              
564             {
565             "file name" => [ filename1, filename2 ],
566             "file type" => [ filetype1, filetype2 ]
567             }
568              
569             The hash keys returned are exactly of the same format as passed in via the
570             parameters, so passing in a single character will B create a full field
571             name key. E.g.
572              
573             $lf->get_hashof_columns( "file_name", "t");
574              
575             will produce this
576              
577             {
578             "file_name" => [ filename1, filename2 ],
579             "t" => [ filetype1, filetype2 ]
580             }
581              
582             =item C
583              
584             $lf->get_hashof_rows( $key, @column_names );
585             $lf->get_arrayof_rows( $filter, $key, @column_names );
586              
587             Returns a hash reference (or a list which can be assigned to a hash). The hash
588             keys are the value of the field which is given as the first parameter. The hash
589             values are references to arrays, each of which contain a row of the requested
590             fields in a hash; e.g. a call to
591              
592             $lf->get_hashof_rows( "process id", "file name", "t" );
593              
594             would produce a data structure like this
595              
596             {
597             pid1 => [
598             {
599             "file name" => filename1,
600             "t" => filetype1
601             },
602             {
603             "file name" => filename2,
604             "t" => filetype2
605             },
606             ],
607             pid2 => [
608             {
609             "file name" => filename3,
610             "t" => filetype3
611             }
612             ]
613             }
614              
615              
616              
617             =back
618              
619             =head1 DIAGNOSTICS
620              
621             =over
622              
623             =item C<< %s is not in the list of fields returned by lsof >>
624              
625             You requested a field which was not in the list of fields returned by the lsof
626             binary. Check that you spelt the field name correctly and that it is in the list
627             of field names specified in the C docs. Also check that the field
628             name is supported on the platform you are running lsof on.
629              
630             =item C<< Invalid filter specified for "%s" >>
631              
632             You specified an invalid filter. Valid filters are strings, numbers or
633             references to regular expressions or subroutines. See the documentation on
634             "Filters" above.
635              
636             =back
637              
638              
639             =head1 CONFIGURATION AND ENVIRONMENT
640              
641             Unix::Lsof::Result requires no configuration files or environment variables.
642              
643              
644             =head1 DEPENDENCIES
645              
646             Unix::Lsof::Result requires the following modules:
647              
648             =over
649              
650             =item *
651              
652             version
653              
654             =back
655              
656              
657             =head1 INCOMPATIBILITIES
658              
659             None reported.
660              
661              
662             =head1 BUGS AND LIMITATIONS
663              
664             Please report any bugs or feature requests to
665             C, or through the web interface at
666             L.
667              
668             No bugs have been reported so far. As with C, there are a number of
669             improvements that could be made to this module, particularly with regards to
670             filtering. Further development is almost certainly strictly "develop by
671             bugreport", so if you want a feature added, please open a wishlist item in the
672             RT web interface and let me know. I'm more than happy to do more work on this
673             module, but want to see what concrete improvements people would like to have.
674             As always, patches are more than welcome.
675              
676              
677             =head1 ACKNOWLEDGEMENTS
678              
679             A very heartfelt thanks to Vic Abell for writing C, it has been invaluable
680             to me over the years. Many thanks as always to http://www.perlmonks.org, the
681             monks continue to amaze and enlighten me. A very special thanks to Damian
682             Conway, who (amongst other things) recommends writing module documentation
683             at the same time as code (in his excellent book "Perl Best Practices"). I didn't
684             follow that advice and as a result writing these docs was more painful and
685             error-prone than it should have been. Please Mr. Conway, for the next edition
686             could you put more emphasis on that recommendation so that dolts like me get
687             it the first time?
688              
689             =head1 AUTHOR
690              
691             Marc Beyer C<< >>
692              
693             =head1 LICENCE AND COPYRIGHT
694              
695             Copyright (c) 2008-2013,2009, Marc Beyer C<< >>. All rights reserved.
696              
697             This module is free software; you can redistribute it and/or
698             modify it under the same terms as Perl itself. See L.
699              
700              
701             =head1 DISCLAIMER OF WARRANTY
702              
703             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
704             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
705             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
706             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
707             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
708             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
709             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
710             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
711             NECESSARY SERVICING, REPAIR, OR CORRECTION.
712              
713             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
714             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
715             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
716             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
717             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
718             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
719             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
720             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
721             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
722             SUCH DAMAGES.