File Coverage

blib/lib/Proc/Application.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Proc::Application;
2              
3             =head1 NAME
4              
5             Proc::Application - base class for all applications
6              
7             =head1 SYNOPSIS
8              
9             package Program;
10             @Program::ISA = qw(Proc::Application);
11             sub main { print "Done\n"; }
12             package main;
13             Program->new->run();
14              
15             =head1 DESCRIPTION
16              
17             This is a application code base
18              
19             =cut
20              
21 1     1   713 use strict;
  1         2  
  1         34  
22 1     1   1319 use Getopt::ArgvFile;
  0            
  0            
23             use Getopt::Long;
24             use Proc::PID_File;
25              
26             use constant MAX_CLOSED_DESCRIPTOR => 1;
27              
28             =head2 new
29              
30             =cut
31              
32             sub new
33             {
34             my $class = shift;
35             my $this = bless {}, $class;
36             $this;
37             }
38              
39             =head2 main
40              
41             =cut
42              
43             sub main
44             {
45             my $this = shift;
46             }
47              
48             =head2 run
49              
50             =cut
51              
52             sub run
53             {
54             my $this = shift;
55             $this->processCommandOptions;
56             $SIG{INT} = $SIG{TERM} = sub { $this || return; $this->DESTROY; exit ( 0 ) };
57             $this->log->log ( level => 'notice', message => "start\n" );
58             eval { $this->main(); };
59             if ( $@ )
60             {
61             $this->log->error ( $@ );
62             warn $@;
63             }
64             $this->log->log ( level => 'notice', message => "stop\n" );
65             }
66              
67             =head2 DESTROY
68              
69             =cut
70              
71             sub DESTROY
72             {
73             my $this = shift;
74             foreach my $lock ( values %{ $this->{locks} } )
75             {
76             next unless $lock;
77             $lock->DESTROY();
78             }
79             }
80              
81             =head2 processCommandOptions
82              
83             Process options from command line by Getopt::Long && Getopt::ArgvFile
84              
85             =cut
86              
87             sub processCommandOptions
88             {
89             my $this = shift;
90             Getopt::ArgvFile::argvFile ( default => 1, home => 1 );
91             my $optionsDescription = $this->options;
92             my $options = ( $this->{options} ||= {} );
93             my %getoptOptions = ();
94             while ( my ( $optionName, $optionDescription) = each %$optionsDescription )
95             {
96             my $multiplicity = $optionDescription->{multiplicity};
97             $options->{ $optionName } = $optionDescription->{default} || ( $multiplicity ? [] : '' );
98             $getoptOptions{ $optionDescription->{template} } = $multiplicity ? $options->{ $optionName } : \ $options->{ $optionName };
99             #$optionDescription->{action} ? $optionDescription->{action} : \ $options->{ $optionName };
100             }
101             Getopt::Long::GetOptions ( %getoptOptions );
102             foreach my $optionName ( map { $_->{name} } sort { $b->{priority} <=> $a->{priority} }
103             map { my $result = { priority => $optionsDescription->{ $_ }->{priority} || 0,
104             name => $_ }; $result } keys %$optionsDescription )
105             {
106             my $optionDescription = $optionsDescription->{ $optionName };
107             my $action = $optionDescription->{action} || next;
108             my $optionValue = $options->{ $optionName };
109             next unless ( ref ( $optionValue ) ? @$optionValue : $optionValue );
110             eval { &$action ( $optionName => $optionValue ); };
111             if ( $@ )
112             {
113             $this->log->error ( $@ );
114             die $@;
115             }
116             }
117             }
118              
119             =head2 log
120              
121             Create and return log object ( the Log::Dispatch )
122              
123             =cut
124              
125             sub log
126             {
127             my $this = shift;
128             use Log::Dispatch;
129             $this->{log} ||= new Log::Dispatch;
130             $this->{logCounter} ||= 0;
131             $this->{log};
132             }
133              
134             =head2 options
135              
136             =cut
137              
138             sub options
139             {
140             my $this = shift;
141             return
142             { 'filelog' => { template => 'filelog=s',
143             description => 'setup file name for logging, paramaters format --logfile "filename,minlevel,maxlevel"',
144             multiplicity => 1,
145             priority => 10,
146             action => sub { $this->_processFileLog ( @_ ) } },
147             'syslog' => { template => 'syslog=s',
148             description => 'setup syslog logging, parameters format "facility,ident,logoptions,minlevel,maxlevel"',
149             multiplicity => 1,
150             priority => 10,
151             action => sub { $this->_processSysLog ( @_ ) } },
152             'screenlog' => { template => 'screenlog=s',
153             description => 'setup screen logging"',
154             multiplicity => 1,
155             priority => 1,
156             action => sub { $this->_processScreenLog ( @_ ) } },
157             'filelock' => { template => 'filelock=s',
158             description => 'setup syslog logging, parameters format "facility,ident,logoptions,minlevel,maxlevel"',
159             multiplicity => 1,
160             priority => 1,
161             action => sub { $this->_processFileLock ( @_ ) } },
162             'help' => { template => 'help',
163             description => 'this screen',
164             priority => 100,
165             action => sub { $this->usage } },
166             'detach' => { template => 'detach!',
167             description => 'detach from terminal',
168             priority => 9,
169             action => sub { $this->detach ( @_ ) } },
170             'chroot' => { template => 'chroot=s',
171             description => 'chroot to specified path',
172             priority => 9,
173             action => sub { $this->chroot ( @_ ) } },
174             'user' => { template => 'user=s',
175             description => 'change uid (euid) to specified user',
176             priority => 7,
177             action => sub { $this->changeUser ( @_ ) } },
178             'group' => { template => 'group=s',
179             description => 'change gid (egid) to specified group',
180             priority => 8,
181             action => sub { $this->changeGroup ( @_ ) } },
182             'pidfile' => { template => 'pidfile=s',
183             description => 'write pid of process to specified file',
184             priority => 1,
185             action => sub { $this->pidfile ( @_ ) } },
186             'debug' => { template => 'debug!',
187             description => 'inc. debug messages of process',
188             priority => 1 },
189             }
190             }
191              
192             =head2 description
193              
194             =cut
195              
196             sub description
197             {
198             my $this = shift;
199             "$0 - description";
200             }
201              
202             =head2 usage
203              
204             =cut
205              
206             sub usage
207             {
208             my $this = shift;
209             my $options = $this->options;
210             print STDERR $this->description . "\n\n";
211             while ( my ( $key, $value ) = each ( %$options ) )
212             {
213             print STDERR "$key - " . $value->{description} . "\n";
214             }
215             exit ( 1 );
216             }
217              
218             sub _decodeOption
219             {
220             my ( $this, $option ) = @_;
221             my @params = map { my @result = split /=/, $_;
222             $result[0] = '' unless defined $result[0];
223             $result[1] = '' if ( ( m/=/ ) && ( ! defined $result[1] ) );
224             @result; } split /:/, $option;
225             @params;
226             }
227              
228             =head2 _processFileLog
229              
230             =cut
231              
232             sub _processFileLog
233             {
234             my ( $this, $option, $params ) = @_;
235             use Log::Dispatch::File;
236             $params = [ $params ] unless ref $params;
237             foreach my $param ( @$params )
238             {
239             my %params = $this->_decodeOption ( $param || '' );
240             my $ident = delete $params{ident};
241             $this->log->add
242             ( new Log::Dispatch::File
243             ( name => 'log' . $this->{logCounter}++,
244             callbacks => sub { stFormatLogLine ( $ident, @_ ) },
245             %params ) );
246             }
247             }
248              
249             =head2 _processSysLog
250              
251             =cut
252              
253             sub _processSysLog
254             {
255             my ( $this, $option, $params ) = @_;
256             use Log::Dispatch::Syslog;
257             $params = [ $params ] unless ref $params;
258             foreach my $param ( @$params )
259             {
260             $this->log->add
261             ( new Log::Dispatch::Syslog
262             ( name => 'log' . $this->{logCounter}++,
263             $this->_decodeOption ( $param || '' ) ) );
264             }
265             }
266              
267             =head2 _processScreenLog
268              
269             =cut
270              
271             sub _processScreenLog
272             {
273             my ( $this, $option, $params ) = @_;
274             use Log::Dispatch::Screen;
275             $params = [ $params ] unless ref $params;
276             foreach my $param ( @$params )
277             {
278             $this->log->add
279             ( new Log::Dispatch::Screen
280             ( name => 'log' . $this->{logCounter}++,
281             $this->_decodeOption ( $param || '' ) ) );
282             }
283             }
284              
285             =head2 _processFileLock
286              
287             =cut
288              
289             sub _processFileLock
290             {
291             my ( $this, $option, $params ) = @_;
292             use Proc::Lock::File;
293             $params = [ $params ] unless ref $params;
294             foreach my $param ( @$params )
295             {
296             $this->{logCount} ||= 0;
297             $this->{locks} ||= {};
298             $this->{locks}->{ ++$this->{logCount} } = new Proc::Lock::File ( $this->_decodeOption ( $param || '' ),
299             log => $this->log );
300             $this->{locks}->{ $this->{logCount} }->set() || die "Can't set lock!\n";
301             }
302             }
303              
304             =head2 stFormatLogLine
305              
306             =cut
307              
308             sub stFormatLogLine
309             {
310             my ( $ident, %params ) = @_;
311             $ident = $ident ? " $ident:" : '';
312             my $line = $params{message} || '';
313             my ( $s, $m, $h, $d, $mon, $y) = localtime(); $mon++; $y += 1900;
314             my $time = sprintf ( "%.2d/%.2d/%.4d %.2d:%.2d:%.2d", $d, $mon, $y, $h, $m, $s );
315             "$time$ident $line\n";
316             }
317              
318             =head2 detach
319              
320             =cut
321              
322             sub detach
323             {
324             my $this = shift;
325             use IO::Handle;
326             use POSIX;
327             my $pid = fork;
328             defined $pid || die "Can't for for detach: $!\n";
329             exit ( 0 ) if $pid;
330             for ( 0 .. MAX_CLOSED_DESCRIPTOR )
331             {
332             $this->log->log ( level => 'debug', message => "*** close fd $_" );
333             my IO::Handle $fh = new IO::Handle;
334             $fh->fdopen ( $_, 'r' );
335             $fh->close;
336             }
337             chdir '/';
338             POSIX::setsid ();
339             $this->log->log ( level => 'notice', message => 'detach from terminal' );
340             }
341              
342             =head2 chroot
343              
344             =cut
345              
346             sub chroot
347             {
348             my ( $this, $option, $params ) = @_;
349             $this->log ( level => 'notice', message => "chroot to $params" );
350             chroot $params || die "Can't chroot";
351             chdir '/';
352             }
353              
354             =head2 changeUser
355              
356             =cut
357              
358             sub changeUser
359             {
360             my ( $this, $option, $user ) = @_;
361             $this->log->log ( level => 'notice', message => "change uid (euid) to $user" );
362             ( $user = getpwnam ( $user ) || die "Can't get uid for user $user: $!" )
363             unless ( $user =~ /^\d+$/ );
364             $< = ( $> = $user );
365             }
366              
367             =head2 changeGroup
368              
369             =cut
370              
371             sub changeGroup
372             {
373             my ( $this, $option, $group ) = @_;
374             $this->log->log ( level => 'notice', message => "change gid (egid) to $group" );
375             ( $group = getgrnam ( $group ) || die "Can't get gid for group $group: $!" )
376             unless ( $group =~ /^\d+$/ );
377             $( = ( $) = $group );
378             }
379              
380             =head2 pidfile
381              
382             =cut
383              
384             sub pidfile
385             {
386             my ( $this, $option, $pidFileName ) = @_;
387             use Proc::PID_File;
388             my Proc::PID_File $pidFile = new Proc::PID_File ( path => $pidFileName ) || die "Can't create pidfile $pidFileName: $!";
389             $pidFile->init || die "Can't open/create pid file $pidFileName: $!";
390             $pidFile->active();
391             $this->{options}->{pidfile} = $pidFile;
392             }
393              
394             1;