File Coverage

blib/lib/Unix/Conf/ConfIO.pm
Criterion Covered Total %
statement 24 255 9.4
branch 0 128 0.0
condition 0 28 0.0
subroutine 8 37 21.6
pod 18 19 94.7
total 50 467 10.7


line stmt bran cond sub pod time code
1             # Class to cache configuration file I/O
2             #
3             # Copyright Karthik Krishnamurthy
4             =head1 NAME
5              
6             Unix::Conf::ConfIO - This is an internal module for handling line
7             at a time I/O for configuration files, with caching, locking and
8             security support.
9              
10             =head1 SYNOPSIS
11              
12             Open a configuration file and get a Unix::Conf::ConfIO object.
13              
14             use Unix::Conf;
15              
16             my $conf = Unix::Conf->_open_conf (
17             NAME => 'some_file',
18             SECURE_OPEN => 1,
19             LOCK_STYLE => 'dotlock',
20             );
21             $conf->die ('DEAD') unless ($conf);
22              
23             # use the file in various ways
24             print while (<$conf>);
25             $conf->setline (10, "this is the 11th line\n");
26             $conf->close ();
27              
28             =head1 DESCRIPTION
29              
30             ConfIO is designed to be a class for handling I/O for configuration
31             files with support for locking and security. At the time of creation
32             all the data in the file is read in and stored in an array, where each
33             line is assumed to be one line of the file. It is the responsibility of
34             the user to ensure that while appending or setting lines, the data ends
35             in a newline. While this is not enforced it could cause the lineno
36             counter to get confused.
37              
38             =cut
39              
40             package Unix::Conf::ConfIO;
41              
42 1     1   11 use 5.6.0;
  1         3  
  1         46  
43 1     1   4 use strict;
  1         2  
  1         39  
44 1     1   4 use warnings;
  1         1  
  1         23  
45              
46 1     1   4 use Fcntl qw (:DEFAULT :mode :flock);
  1         2  
  1         658  
47 1     1   24 use Unix::Conf;
  1         1  
  1         42  
48              
49 1         6 use overload '<>' => 'getline',
50             'bool' => '__interpret_as_bool',
51             '""' => '__interpret_as_string',
52 1     1   6 'eq' => '__interpret_as_string';
  1         2  
53              
54             # Cache of file keyed on filenames. Value is a hash reference.
55             # {
56             # NAME,
57             # FH,
58             # MODE,
59             # PERMS,
60             # DATA,
61             # TIMESTAMP,
62             # LINENO,
63             # LOCK_STYLE,
64             # DIRTY,
65             # PERSIST,
66             # IN_USE,
67             # }
68             # FH is true if the file is currently in use (the filehandle is stored there).
69             # TIMESTAMP is used to see if the file contents have been changed.
70             # PERSIST is used for those files that need to be held and not
71             # released even when their destructors have been called. This is needed for
72             # cases where the file obj goes out of scope, but the file is not closed
73             # here thus not releasing the lock on that file. This eases the task of
74             # maintaining the filehandle by the user of this module.
75             my %File_Cache;
76              
77             # This hash contains an entry for every module that calls us. If persistent
78             # open is called, such files are stored as values for the key, which is the
79             # calling module name. Thus, when release_all is called, we know exactly which
80             # persistent files are to be released.
81             my %Calling_Modules;
82              
83             =over 4
84              
85             =item open ()
86              
87             Arguments
88             NAME => 'PATHNAME',
89             FH => filehandle # filehandle, reference to a filehandle
90             MODE => FILE_OPEN_MODE, # default is O_RDWR | O_CREAT
91             PERMS => FILE_CREATION_PERMS, # default is 0600
92             LOCK_STYLE => 'flock'/'dotlock', # default is 'flock'
93             SECURE_OPEN => 0/1, # default is 0 (disabled)
94             PERSIST => 0/1, # default is 0 (disabled)
95              
96             Class constructor.
97             Creates a ConfIO object which is associated with the file.
98             Releasing the object automatically syncs with the disk version of
99             the file. Passing an open filehandle with FH creates a
100             Unix::Conf::ConfIO object representing the open file. Take care
101             to open FH in both read & write mode, because Unix::Conf::ConfIO
102             reads in the whole file into an in core array as of now.
103             MODE and PERMS are the same as for sysopen. LOCK_STYLE
104             is for choosing between different locking methods. 'dotlock' is
105             used for locking '/etc/passwd', '/etc/shadow', '/etc/group',
106             '/etc/gshadow'. 'flock' is the default locking style. If the
107             value of SECURE_OPEN is 1, it enables a check to see if PATHNAME
108             is secure. PERSIST is used to keep files open till release ()
109             or release_all () is called even though the object may go out
110             of scope in the calling code. It reduces the overhead of
111             managing ancillary files. Otherwise the file locks associated
112             with the file would be released for these anciallary files.
113             TODO: Need to implement ability to accept open filehandles,
114             IO::Handle, IO::File objects too.
115             NOTE: This method should not be used directly. Instead use
116             Unix::Conf::_open_conf () which has the same syntax.
117              
118             Example
119             use Unix::Conf;
120             my $conf;
121             $conf = Unix::Conf->_open_conf (
122             NAME => '/etc/passwd',
123             SECURE_OPEN => 1,
124             LOCK_STYLE => 'dotlock',
125             ) or $conf->die ("Could not open '/etc/passwd'");
126              
127             # or attach a filehandle to a Unix::Conf object.
128             $conf = Unix::Conf->_open_conf (
129             FH => FH, # or any object that represents an open filehandle
130             ) or $conf->die ("Could not attach FH");
131              
132             =cut
133            
134             sub open
135             {
136 0     0 1   my $class = shift ();
137 0           my $args = {
138             LOCK_STYLE => 'flock',
139             MODE => O_RDWR | O_CREAT,
140             PERMS => 0600,
141             SECURE_OPEN => 0,
142             PERSIST => 0,
143             @_,
144             };
145              
146 0           my ($fh, $name, $timestamp, $retval);
147              
148             # do sanity check on the passed argument
149 0 0 0       return (Unix::Conf->_err ('open', "neither filename nor filehandle passed"))
150             unless (defined ($args->{FH}) || defined ($args->{NAME}));
151              
152 0 0         if ($args->{FH}) {
    0          
153 0 0         return (Unix::Conf->_err ("open", "`$args->{LOCK_STYLE}' illegal with FH"))
154             if ($args->{LOCK_STYLE} ne 'flock');
155 0 0         return (Unix::Conf->_err ("open", "`SECURE_OPEN' illegal with FH"))
156             if ($args->{SECURE_OPEN});
157             # store the filehandle name as the name of the file.
158             # this is needed for persistent opens where the handle
159             # is cached in $Calling_Modules.
160 0           $args->{NAME} = "$args->{FH}";
161 0           $args->{FILEHANDLE_PASSED} = 1;
162 0 0         $fh = $File_Cache{$args->{NAME}} = $args
163             unless (($fh = $File_Cache{$args->{NAME}}));
164             # fh now contains the old ConfIO object if one with the same
165             # name exists
166              
167             # if file is locked in our cache return Err
168 0 0         return (Unix::Conf->_err ('open', "`$fh->{NAME}' already in use, locked"))
169             if ($fh->{IN_USE});
170 0 0         $retval = __lock ($fh) or return ($retval);
171             }
172             elsif ($args->{NAME}) {
173 0 0         $fh = $File_Cache{$args->{NAME}} = $args
174             unless (($fh = $File_Cache{$args->{NAME}}));
175             # fh now contains the old ConfIO object if one with the same
176             # name exists
177              
178             # if file is locked in our cache return Err
179 0 0         return (Unix::Conf->_err ('open', "`$fh->{NAME}' already in use, locked"))
180             if ($fh->{IN_USE});
181            
182             # if FH exists, file must be a persistent one. we call __checkpath
183             # if SECURE_OPEN was specified, and not before. However any change in
184             # modes we barf
185 0 0         if ($fh->{FH}) {
186 0           my $ret;
187 0 0 0       $ret = __checkpath ($fh->{NAME}) or return ($ret)
188             if ($args->{SECURE_OPEN} > $fh->{SECURE_OPEN});
189 0           my ($oldmode, $newmode);
190 0           $oldmode = $fh->{MODE} & (O_RDONLY | O_WRONLY | O_RDWR);
191 0           $newmode = $args->{MODE} & (O_RDONLY | O_WRONLY | O_RDWR);
192 0 0         return (Unix::Conf->_err ('open', "mode is not the same as in the original open"))
193             if ($oldmode != $newmode);
194             }
195             else {
196             # file is not in cache, or is but had been previously closed.
197             # we need to open file even if our cache has good data, and lock the file
198 0 0         $fh->{FH} = __open (
199             $fh->{NAME},
200             $fh->{MODE},
201             $fh->{PERMS},
202             $fh->{SECURE_OPEN}
203             ) or return ($fh->{FH});
204              
205 0 0         unless ($retval = __lock ($fh)) {
206 0           close ($fh->{FH});
207 0           return ($retval);
208             }
209             }
210             }
211             # check timestamp even if file was held in persistent store and locked
212 0           $timestamp = (stat ($fh->{FH}))[9];
213             # if we had previously cached the file and it has not changed since
214             # bless and return.
215 0 0 0       if (!defined ($fh->{TIMESTAMP}) || $fh->{TIMESTAMP} != $timestamp) {
216             # if we reach here, either we don't have the file in our cache,
217             # the cache is stale.
218 0 0         return (Unix::Conf->_err ("open")) unless (seek ($fh->{FH}, 0, 0));
219 0           my @lines = readline ($fh->{FH});
220 0           @$fh{'DATA', 'TIMESTAMP'} = (\@lines, $timestamp);
221             }
222              
223 0           @$fh{'LINENO', 'DIRTY'} = (-1, 0);
224 0           $fh->{IN_USE} = 1;
225             # store files that the calling module wants persisted. subsequently
226             # when release_all is called by the same module, these will be actually
227             # released (locks)
228 0 0         $Calling_Modules{__caller ()}{$fh->{NAME}} = 1
229             if ($fh->{PERSIST});
230 0           my $instance = $fh->{NAME};
231 0           my $obj = bless (\$instance, $class);
232 0           return ($obj);
233             }
234              
235             =item close ()
236              
237             Object method.
238             Syncs the cache to disk and releases the lock on the file unless
239             the file was opened with PERSIST enabled. However the cache of data
240             is maintained in the hope that it will still be useful and obviate
241             the necessity for a read of all the data.
242             Returns true or an Err object in case of error.
243              
244             =cut
245              
246             # method instance. can also be called as a subroutine with the filename as the
247             # first arg.
248             sub close
249             {
250 0     0 1   my $self = shift ();
251 0 0         my $file = ref ($self) ? $File_Cache{$$self} : $File_Cache{$self};
252              
253 0 0         return (Unix::Conf->_err ('close', "object already closed"))
254             unless ($file->{FH});
255              
256             # sync file if dirty
257 0 0         if ($file->{DIRTY}) {
258 0 0         truncate ($file->{FH}, 0) or return (Unix::Conf->_err ("truncate"));
259 0 0         sysseek ($file->{FH}, 0, 0) or return (Unix::Conf->_err ("sysseek"));
260              
261             # suppress warnings so that we don't get a warning about empty
262             # array elements that we delete'ed.
263 1     1   955 no warnings;
  1         15  
  1         738  
264 0           syswrite ($file->{FH}, $_ ) for (@{$$file{DATA}});
  0            
265             }
266              
267 0           $file->{TIMESTAMP} = (stat ($file->{FH}))[8]; # store new timestamp.
268 0           $file->{IN_USE} = 0;
269              
270             # if persistent file, do not close
271 0 0         return (1) if ($file->{PERSIST});
272              
273 0 0         close ($file->{FH}) || return (Unix::Conf->_err ('close'));
274 0           __unlock ($file);
275 0 0         delete ($File_Cache{$file->{NAME}}) if ($file->{FILEHANDLE_PASSED});
276 0           undef ($file->{FH});
277 0           return (1);
278             }
279              
280             sub DESTROY
281             {
282 0     0     my $self = shift ();
283 0           my $obj = $File_Cache{$$self};
284 0           my $retval;
285              
286             # if FH is still set, call close
287 0 0         if ($obj->{FH}) {
288 0 0         $retval = $self->close () or $retval->die ('Unix::Conf::ConfIO DESTRUCTOR failed');
289             }
290             }
291              
292             sub secure_open
293             {
294 0     0 0   my $self = shift ();
295 0           my $obj = $File_Cache{$$self};
296 0           return ($obj->{SECURE_OPEN});
297             }
298              
299             =item release ()
300              
301             Object method.
302             Closes the file and releases the lock if opened with PERSIST.
303             Returns true or an Err object in case of error.
304              
305             =cut
306              
307             sub release
308             {
309 0     0 1   my $self = shift ();
310 0           my $obj = $File_Cache{$$self};
311              
312             # if FH is still set, call close
313 0 0         if ($obj->{FH}) {
314             # clear PERSIST so that close below can actually close
315 0           $obj->{PERSIST} = 0;
316 0           my $caller = __caller ();
317             #
318             # check to see the sanity check works properly.
319             #
320 0 0         return (Unix::Conf->_err ('release', "This file was not opened with PERSIST by $caller"))
321             unless (exists ($Calling_Modules{$caller}{$$self}));
322 0           delete ($Calling_Modules{$caller}{$$self});
323 0           return ($self->close ());
324             }
325 0           return (1);
326             }
327              
328             =item release_all ()
329              
330             Class method.
331             Closes all files opened with PERSIST by a specific class. This can
332             be called from the destructor for that class, allowing hassle free
333             operation for ancillary files.
334             Returns true or an Err object in case of error.
335              
336             =cut
337              
338             sub release_all
339             {
340 0     0 1   my $caller = __caller ();
341 0           my ($obj, $ret);
342              
343 0 0         return (Unix::Conf->_err ('release_all', "No files were opened with PERSIST by $caller"))
344             unless (exists ($Calling_Modules{$caller}));
345 0           for (keys(%{$Calling_Modules{$caller}})) {
  0            
346 0           $obj = $File_Cache{$_};
347 0           $obj->{PERSIST} = 0;
348             # call method close with the filename as arg
349 0 0         $ret = &close ($_) or return ($ret);
350             }
351 0           delete ($Calling_Modules{$caller});
352 0           return (1);
353             }
354              
355             =item dirty ()
356              
357             Object method.
358             Mark the file cache as dirty explicitly.
359              
360             =cut
361              
362             sub dirty
363             {
364 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
365 0           $self->{DIRTY} = 1;
366 0           return (1);
367             }
368              
369             =item name ()
370              
371             Object method.
372             Returns the name of the associated file.
373              
374             =cut
375            
376             sub name
377             {
378 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
379 0           return ($self->{FILENAME});
380             }
381              
382             =item rewind ()
383              
384             Object method.
385             Rewind the file to the beginning of the data.
386              
387             =cut
388              
389             sub rewind
390             {
391 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
392 0           $self->{LINENO} = -1;
393 0           return (1);
394             }
395              
396             =item next_lineno ()
397              
398             Object method.
399             Returns max lineno + 1.
400              
401             =cut
402              
403             sub next_lineno
404             {
405 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
406 0           return (scalar (@{$self->{DATA}}));
  0            
407             }
408              
409             =item set_scalar ()
410              
411             Arguments
412             SCALAR_REF,
413              
414             Object method.
415             Pass reference to a scalar. The file data will be set to the value
416             of the scalar.
417             Returns true.
418              
419             =cut
420              
421             sub set_scalar
422             {
423 1     1   6 no warnings;
  1         1  
  1         2057  
424 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
425             # release the old data
426 0           undef ($self->{DATA});
427 0           $self->{DATA} = [ split (/^/, ${$_[0]}) ];
  0            
428 0           $self->{DIRTY} = 1;
429 0           return (1);
430             }
431              
432             =item getlines ()
433              
434             Object method.
435             Returns reference to the cache array.
436             NOTE: If the caller then changes the array in anyway it is his/her
437             responsibility to mark the cache as dirty.
438              
439             =cut
440              
441             sub getlines
442             {
443 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
444 0           return ($self->{DATA});
445             }
446              
447             =item setlines ()
448              
449             Arguments
450             ARRAY_REF,
451              
452             Object method.
453             Store the array referenced by ARRAY_REF as the file cache. It is
454             assumed that each element of the file will be a line of data with a
455             trailing newline, though it is not a necessity.
456             Returns true or an Err object in case of error.
457              
458             =cut
459              
460             sub setlines
461             {
462 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
463 0           my $openmode = $self->{MODE} & O_ACCMODE;
464 0 0 0       return (Unix::Conf->_err ('setlines', "file $self->{FH} not opened for writing"))
465             if ($openmode != O_WRONLY && $openmode != O_RDWR);
466 0           $self->{DATA} = shift;
467 0           $self->{DIRTY} = 1;
468 0           return (1);
469             }
470              
471             =item delete ()
472              
473             Object method.
474             Delete all lines in the file.
475             Returns true or an Err object in case of error.
476              
477             =cut
478              
479             sub delete
480             {
481 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
482 0           my $openmode = $self->{MODE} & O_ACCMODE;
483 0 0 0       return (Unix::Conf->_err ('delete', "file $self->{FH} not opened for writing"))
484             if ($openmode != O_WRONLY && $openmode != O_RDWR);
485 0           $self->{LINENO} = -1;
486 0           undef (@{$self->{DATA}});
  0            
487 0           $self->{DIRTY} = 1;
488 0           return (1);
489             }
490              
491             =item lineno ()
492              
493             Object method.
494             Get/set the current lineno of the ConfIO object.
495              
496             =cut
497              
498             sub lineno
499             {
500 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
501 0           my $lineno = shift ();
502 0 0         if (defined ($lineno)) {
503 0 0         return (Unix::Conf->_err ('lineno', "argument passed not numeric"))
504             if ($lineno !~ /^-?\d+$/);
505 0 0         return (Unix::Conf->_err ('lineno', "`$lineno' value illegal"))
506             if ($lineno < -1);
507 0           my $max = $#{$self->{DATA}};
  0            
508 0 0         return (Unix::Conf->_err ('lineno', "argument passed out of bounds, max possible `$max'"))
509             if ($lineno > $max);
510 0           $self->{LINENO} = $lineno;
511 0           return (1);
512             }
513 0           return ($self->{LINENO});
514             }
515              
516             =item getline ()
517              
518             Object method.
519             Returns the next line.
520              
521             =cut
522              
523             sub getline
524             {
525 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
526 0           return ( $self->{DATA}[++($self->{LINENO})] );
527             }
528              
529             =item setline ()
530              
531             Arguments
532             LINENO,
533             SCALAR,
534              
535             Object method.
536             Set LINENO to value of SCALAR.
537             Returns true or an Err object in case of error.
538              
539             =cut
540              
541             sub setline
542             {
543 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
544              
545 0           my $openmode = $self->{MODE} & O_ACCMODE;
546 0 0 0       return (Unix::Conf->_err ('setline', "file $self->{FH} not opened for writing"))
547             if ($openmode != O_WRONLY && $openmode != O_RDWR);
548 0           $self->{DATA}[$_[0]] = $_[1];
549 0           $self->{DIRTY} = 1;
550 0           return (1);
551             }
552              
553             =item append ()
554              
555             Arguments
556             LIST,
557              
558             Object method.
559             Append LIST to the end of the file.
560             Returns true or an Err object in case of error.
561              
562             =cut
563              
564             sub append
565             {
566 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
567 0           my $openmode = $self->{MODE} & O_ACCMODE;
568 0 0 0       return (Unix::Conf->_err ('append', "file $self->{FH} not opened for writing"))
569             if ($openmode != O_WRONLY && $openmode != O_RDWR);
570 0           push (@{$self->{DATA}}, @_);
  0            
571 0           $self->{DIRTY} = 1;
572 0           return (1);
573             }
574              
575             =item delete_lines ()
576              
577             Arguments
578             START_LINENO,
579             END_LINENO,
580              
581             Object method.
582             Delete from START_LINENO to END_LINENO including.
583             Returns true or an Err object in case of error.
584              
585             =cut
586              
587             sub delete_lines
588             {
589 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
590 0           my $openmode = $self->{MODE} & O_ACCMODE;
591 0 0 0       return (Unix::Conf->_err ('delete_lines', "file $self->{FH} not opened for writing"))
592             if ($openmode != O_WRONLY && $openmode != O_RDWR);
593 0 0         return (Unix::Conf->_err ('delete_lines', "offset not specified"))
594             unless (defined ($_[0]));
595 0           my $start = $_[0];
596 0 0         my $end = $_[1] ? $_[1] : $start;
597 0           delete (@{$self->{DATA}}[$start..$end]);
  0            
598 0           $self->{DIRTY} = 1;
599 0           return (1);
600             }
601              
602             =item ungetline ()
603              
604             Object method.
605             Rewind the current lineno pointer.
606             Returns true.
607              
608             =cut
609            
610             sub ungetline
611             {
612 0     0 1   my $self = $File_Cache{${shift ()}};
  0            
613 0           ($self->{LINENO})--;
614 0           return (1);
615             }
616              
617 0     0     sub __caller { return ((caller (1))[0]); }
618              
619             # If a ConfIO object has a defined filehandle it is true, else false
620             sub __interpret_as_bool
621             {
622 0     0     my $self = $File_Cache{${shift ()}};
  0            
623 0           return ($self->{IN_USE});
624             }
625              
626             sub __interpret_as_string
627             {
628 0     0     my $self = shift;
629 0           return "$$self";
630             }
631              
632             sub __lock ($)
633             {
634 0     0     my ($fh) = @_;
635              
636 0 0         ($fh->{LOCK_STYLE} eq 'flock') && do {
637 0 0         flock ($fh->{FH}, LOCK_EX | LOCK_NB) || return (Unix::Conf->_err ('flock'));
638 0           return (1);
639             };
640 0 0         ($fh->{LOCK_STYLE} eq 'dotlock') && do {
641 0           return (__dotlock ($fh->{NAME}));
642             };
643             }
644              
645             sub __unlock ($)
646             {
647 0     0     my ($fh) = @_;
648              
649 0 0         ($fh->{LOCK_STYLE} eq 'flock') && do {
650             # no unlocking necessary. when the appropriate fh is released or
651             # close called on it the lock will be automatically released.
652 0           return (1);
653             };
654 0 0         ($fh->{LOCK_STYLE} eq 'dotlock') && do {
655 0 0         unlink ("$fh->{NAME}.lock") or return (Unix::Conf->_err ('unlink'));
656 0           return (1);
657             };
658             }
659              
660             # ARGUMENTS: filename to be locked
661             # RETURN: BOOL indicating failure or success.
662             # Locks files.
663             # Create a unique file from the filename (filename.pid). Write our PID into
664             # pidfile. link to filename.lock. If filename.lock nlink is 2 then we have
665             # succeded, unlink pidfile. If link fails then read the PID from the (already)
666             # existing lockfile. Post 0 to that PID. If no such process exists, lock file
667             # is stale and hence unlink it. loop again. All of this is because, opening
668             # the actual lock file and writing out the PID into it is not atomic. So we
669             # create an tmp unique file, write out our PID into it and then try linking it
670             # which is atomic, since it translates into a single system call.
671             sub __dotlock ($)
672             {
673 0     0     my $file = shift;
674 0           my ($pidfile, $lockfile) = ("$file.$$", "$file.lock");
675 0           my $retval;
676              
677 0 0         sysopen (FH, $pidfile, O_WRONLY | O_CREAT || O_EXCL, 0600)
678             or return (Unix::Conf->_err ('sysopen'));
679 0 0         print FH "$$\x00" or return (Unix::Conf->_err ('new'));
680 0 0         CORE::close (FH) or return (Unix::Conf->_err ('close'));
681              
682             # keep looping until we lock or return inside loop
683 0           until (link ($pidfile, $lockfile)) {
684 0           my $pid;
685 0 0         unless (sysopen (FH, $lockfile, O_RDONLY)) {
686 0           $retval = Unix::Conf->_err ('sysopen');
687 0           goto ERR_RET;
688             }
689 0   0       $pid = || goto ERR_RET;
690 0           $pid = substr ($pid, 0, -1);
691 0 0         CORE::close (FH) || goto ERR_RET;
692             # if process is alive unlink opened files and return undef
693 0 0         if (kill (0, $pid)) {
694 0           $retval = Unix::Conf->_err ('kill');
695 0           goto ERR_RET;
696             }
697 0           unlink ($lockfile);
698             }
699              
700 0           $retval = __check_link_count ($pidfile);
701 0           unlink ($pidfile);
702 0           return $retval;
703              
704 0           ERR_RET:
705             unlink ($pidfile);
706 0           return ($retval);
707             }
708              
709             # check link count of argument and return true if link count == 2
710             sub __check_link_count ($)
711             {
712 0     0     my $file = shift;
713 0           my $nlink;
714 0 0         (undef, undef, undef, $nlink) = stat ($file)
715             or return (Unix::Conf->_err ('stat'));
716              
717 0 0         return (1) if ($nlink == 2);
718             # failure. set _err and return failure
719 0           return (Unix::Conf->_err ('__check_link_count', 'link count of $file is $nlink'));
720             }
721              
722             # ARGUMENTS: file_path, mode, perms, secure
723             # if secure is true then security checks are done on the pathname to see
724             # if any component is writeable by anyone other than root. if so return
725             # error.
726             sub __open ($$$$)
727             {
728 0     0     my ($file_path, $mode, $perms, $secure) = @_;
729            
730 0           my ($fh, $ret);
731 0 0         sysopen ($fh, $file_path, $mode, $perms) ||
732             return (Unix::Conf->_err ("sysopen ($file_path)"));
733              
734 0 0         if ($secure) {
735 0 0         $ret = __checkpath ($file_path) or return ($ret);
736             }
737 0           return ($fh);
738             }
739              
740             sub __checkpath ($)
741             {
742 0     0     my $file_path = $_[0];
743              
744 0           my @chopped = split (/\//, $file_path);
745             # if $chopped[0] is "" then the path was absolute.
746 0 0         if ($chopped[0]) {
747             # is using `pwd` safe ?
748 0           my $cwd = `pwd`;
749 0           chomp ($cwd);
750 0           unshift (@chopped, split (/\//, $cwd));
751             }
752 0           my ($uid, $gid, $mode);
753 0           my $path = "";
754 0           foreach (@chopped) {
755             # on the second iteration $path will be just '/'.
756 0 0         $path .= ($path =~ /^\/$/) ? "$_" : "/$_";
757 0           ($mode, $uid, $gid) = (stat ($path))[2,4,5];
758             # check ownership
759 0 0 0       return (Unix::Conf->_err ('__open', "$file_path resides in an insecure path ($path)"))
760             if ($uid != 0 || $gid != 0);
761             # check to see if others have write perms
762 0 0         return (Unix::Conf->_err ('__open', "$file_path resides in an insecure path ($path)"))
763             if ($mode & S_IWOTH);
764             }
765 0           return (1);
766             }
767              
768             1;
769             __END__