patch for syslog
[p5sagit/p5-mst-13.2.git] / ext / Sys / Syslog / Syslog.pm
CommitLineData
a0d0e21e 1package Sys::Syslog;
2require 5.000;
3require Exporter;
8ce86de8 4require DynaLoader;
a0d0e21e 5use Carp;
6
8ce86de8 7@ISA = qw(Exporter DynaLoader);
a0d0e21e 8@EXPORT = qw(openlog closelog setlogmask syslog);
3ffabb8c 9@EXPORT_OK = qw(setlogsock);
b903fcff 10$VERSION = '0.02';
a0d0e21e 11
23642f4b 12# it would be nice to try stream/unix first, since that will be
13# most efficient. However streams are dodgy - see _syslog_send_stream
14#my @connectMethods = ( 'stream', 'unix', 'tcp', 'udp' );
15my @connectMethods = ( 'tcp', 'udp', 'unix', 'stream', 'console' );
16my @defaultMethods = @connectMethods;
17my $syslog_path = undef;
18my $transmit_ok = 0;
19my $current_proto = undef;
20my $failed = undef;
21my $fail_time = undef;
22
37120919 23use Socket;
55497cff 24use Sys::Hostname;
37120919 25
5be1dfc7 26# adapted from syslog.pl
a0d0e21e 27#
5be1dfc7 28# Tom Christiansen <tchrist@convex.com>
a0d0e21e 29# modified to use sockets by Larry Wall <lwall@jpl-devvax.jpl.nasa.gov>
30# NOTE: openlog now takes three arguments, just like openlog(3)
3ffabb8c 31# Modified to add UNIX domain sockets by Sean Robinson <robinson_s@sc.maricopa.edu>
32# with support from Tim Bunce <Tim.Bunce@ig.co.uk> and the perl5-porters mailing list
8ce86de8 33# Modified to use an XS backend instead of syslog.ph by Tom Hughes <tom@compton.nu>
3ffabb8c 34
5be1dfc7 35=head1 NAME
36
37Sys::Syslog, openlog, closelog, setlogmask, syslog - Perl interface to the UNIX syslog(3) calls
38
39=head1 SYNOPSIS
40
3ffabb8c 41 use Sys::Syslog; # all except setlogsock, or:
42 use Sys::Syslog qw(:DEFAULT setlogsock); # default set, plus setlogsock
5be1dfc7 43
3ffabb8c 44 setlogsock $sock_type;
5be1dfc7 45 openlog $ident, $logopt, $facility;
2eae817d 46 syslog $priority, $format, @args;
5be1dfc7 47 $oldmask = setlogmask $mask_priority;
48 closelog;
49
50=head1 DESCRIPTION
51
52Sys::Syslog is an interface to the UNIX C<syslog(3)> program.
53Call C<syslog()> with a string priority and a list of C<printf()> args
54just like C<syslog(3)>.
55
56Syslog provides the functions:
57
bbc7dcd2 58=over 4
5be1dfc7 59
60=item openlog $ident, $logopt, $facility
61
23642f4b 62I<$ident> is prepended to every message. I<$logopt> contains zero or
63more of the words I<pid>, I<ndelay>, I<nowait>. The cons option is
64ignored, since the failover mechanism will drop down to the console
65automatically if all other media fail. I<$facility> specifies the
66part of the system
5be1dfc7 67
2eae817d 68=item syslog $priority, $format, @args
5be1dfc7 69
2eae817d 70If I<$priority> permits, logs I<($format, @args)>
5be1dfc7 71printed as by C<printf(3V)>, with the addition that I<%m>
72is replaced with C<"$!"> (the latest error message).
73
74=item setlogmask $mask_priority
75
76Sets log mask I<$mask_priority> and returns the old mask.
77
23642f4b 78=item setlogsock $sock_type [$stream_location] (added in 5.004_02)
3ffabb8c 79
cb63fe9d 80Sets the socket type to be used for the next call to
3ffabb8c 81C<openlog()> or C<syslog()> and returns TRUE on success,
82undef on failure.
83
23642f4b 84A value of 'unix' will connect to the UNIX domain socket returned by
85the C<_PATH_LOG> macro (if your system defines it) in F<syslog.ph>. A
86value of 'stream' will connect to the stream indicated by the pathname
87provided as the optional second parameter. A value of 'inet' will
88connect to an INET socket (either tcp or udp, tried in that order)
89returned by getservbyname(). 'tcp' and 'udp' can also be given as
90values. The value 'console' will send messages directly to the
91console, as for the 'cons' option in the logopts in openlog().
92
93A reference to an array can also be passed as the first parameter.
94When this calling method is used, the array should contain a list of
95sock_types which are attempted in order.
cb63fe9d 96
23642f4b 97The default is to try tcp, udp, unix, stream, console.
98
99Giving an invalid value for sock_type will croak.
cb63fe9d 100
5be1dfc7 101=item closelog
102
103Closes the log file.
104
105=back
106
107Note that C<openlog> now takes three arguments, just like C<openlog(3)>.
108
109=head1 EXAMPLES
110
111 openlog($program, 'cons,pid', 'user');
112 syslog('info', 'this is another test');
113 syslog('mail|warning', 'this is a better test: %d', time);
114 closelog();
115
116 syslog('debug', 'this is the last test');
cb63fe9d 117
118 setlogsock('unix');
5be1dfc7 119 openlog("$program $$", 'ndelay', 'user');
120 syslog('notice', 'fooprogram: this is really done');
121
cb63fe9d 122 setlogsock('inet');
5be1dfc7 123 $! = 55;
124 syslog('info', 'problem was %m'); # %m == $! in syslog(3)
125
5be1dfc7 126=head1 SEE ALSO
127
128L<syslog(3)>
129
130=head1 AUTHOR
131
150b260b 132Tom Christiansen E<lt>F<tchrist@perl.com>E<gt> and Larry Wall
133E<lt>F<larry@wall.org>E<gt>.
134
135UNIX domain sockets added by Sean Robinson
23642f4b 136E<lt>F<robinson_s@sc.maricopa.edu>E<gt> with support from Tim Bunce
137E<lit>F<Tim.Bunce@ig.co.uk>E<gt> and the perl5-porters mailing list.
150b260b 138
139Dependency on F<syslog.ph> replaced with XS code by Tom Hughes
140E<lt>F<tom@compton.nu>E<gt>.
5be1dfc7 141
23642f4b 142Code for constant()s regenerated by Nicholas Clark E<lt>F<nick@ccl4.org>E<gt>.
143
144Failover to different communication modes by Nick Williams
145E<lt>F<Nick.Williams@morganstanley.com>E<gt>.
b903fcff 146
5be1dfc7 147=cut
a0d0e21e 148
8ce86de8 149sub AUTOLOAD {
150 # This AUTOLOAD is used to 'autoload' constants from the constant()
151 # XS function.
23642f4b 152
8ce86de8 153 my $constname;
154 our $AUTOLOAD;
155 ($constname = $AUTOLOAD) =~ s/.*:://;
b903fcff 156 croak "&Sys::Syslog::constant not defined" if $constname eq 'constant';
157 my ($error, $val) = constant($constname);
158 if ($error) {
159 croak $error;
8ce86de8 160 }
161 *$AUTOLOAD = sub { $val };
162 goto &$AUTOLOAD;
163}
164
165bootstrap Sys::Syslog $VERSION;
a0d0e21e 166
167$maskpri = &LOG_UPTO(&LOG_DEBUG);
168
169sub openlog {
170 ($ident, $logopt, $facility) = @_; # package vars
171 $lo_pid = $logopt =~ /\bpid\b/;
172 $lo_ndelay = $logopt =~ /\bndelay\b/;
a0d0e21e 173 $lo_nowait = $logopt =~ /\bnowait\b/;
a8710ca1 174 return 1 unless $lo_ndelay;
175 &connect;
a0d0e21e 176}
177
178sub closelog {
179 $facility = $ident = '';
180 &disconnect;
181}
182
183sub setlogmask {
184 local($oldmask) = $maskpri;
185 $maskpri = shift;
186 $oldmask;
187}
188
cb63fe9d 189sub setlogsock {
190 local($setsock) = shift;
23642f4b 191 $syslog_path = shift;
3ffabb8c 192 &disconnect if $connected;
23642f4b 193 $transmit_ok = 0;
194 @fallbackMethods = ();
195 @connectMethods = @defaultMethods;
196 if (ref $setsock eq 'ARRAY') {
197 @connectMethods = @$setsock;
198 } elsif (lc($setsock) eq 'stream') {
199 $syslog_path = '/dev/log' unless($syslog_path);
200 if (!-w $syslog_path) {
201 carp "stream passed to setlogsock, but $syslog_path is not writable";
202 return undef;
203 } else {
204 @connectMethods = ( 'stream' );
205 }
206 } elsif (lc($setsock) eq 'unix') {
207 if (length _PATH_LOG() && !defined $syslog_path) {
208 $syslog_path = _PATH_LOG();
209 @connectMethods = ( 'unix' );
3ffabb8c 210 } else {
23642f4b 211 carp 'unix passed to setlogsock, but path not available';
212 return undef;
3ffabb8c 213 }
23642f4b 214 } elsif (lc($setsock) eq 'tcp') {
215 if (getservbyname('syslog', 'tcp') || getservbyname('syslogng', 'tcp')) {
216 @connectMethods = ( 'tcp' );
217 } else {
218 carp "tcp passed to setlogsock, but tcp service unavailable";
219 return undef;
220 }
221 } elsif (lc($setsock) eq 'udp') {
222 if (getservbyname('syslog', 'udp')) {
223 @connectMethods = ( 'udp' );
224 } else {
225 carp "udp passed to setlogsock, but udp service unavailable";
226 return undef;
227 }
cb63fe9d 228 } elsif (lc($setsock) eq 'inet') {
23642f4b 229 @connectMethods = ( 'tcp', 'udp' );
230 } elsif (lc($setsock) eq 'console') {
231 @connectMethods = ( 'console' );
cb63fe9d 232 } else {
23642f4b 233 carp "Invalid argument passed to setlogsock; must be 'stream', 'unix', 'tcp', 'udp' or 'inet'";
cb63fe9d 234 }
f8b75b0c 235 return 1;
cb63fe9d 236}
237
a0d0e21e 238sub syslog {
239 local($priority) = shift;
240 local($mask) = shift;
241 local($message, $whoami);
242 local(@words, $num, $numpri, $numfac, $sum);
243 local($facility) = $facility; # may need to change temporarily.
244
245 croak "syslog: expected both priority and mask" unless $mask && $priority;
246
247 @words = split(/\W+/, $priority, 2);# Allow "level" or "level|facility".
248 undef $numpri;
249 undef $numfac;
250 foreach (@words) {
251 $num = &xlate($_); # Translate word to number.
252 if (/^kern$/ || $num < 0) {
253 croak "syslog: invalid level/facility: $_";
254 }
255 elsif ($num <= &LOG_PRIMASK) {
256 croak "syslog: too many levels given: $_" if defined($numpri);
257 $numpri = $num;
258 return 0 unless &LOG_MASK($numpri) & $maskpri;
259 }
260 else {
261 croak "syslog: too many facilities given: $_" if defined($numfac);
262 $facility = $_;
263 $numfac = $num;
264 }
265 }
266
267 croak "syslog: level must be given" unless defined($numpri);
268
269 if (!defined($numfac)) { # Facility not specified in this call.
270 $facility = 'user' unless $facility;
271 $numfac = &xlate($facility);
272 }
273
274 &connect unless $connected;
275
276 $whoami = $ident;
277
5dad0344 278 if (!$whoami && $mask =~ /^(\S.*?):\s?(.*)/) {
a0d0e21e 279 $whoami = $1;
280 $mask = $2;
281 }
282
283 unless ($whoami) {
284 ($whoami = getlogin) ||
285 ($whoami = getpwuid($<)) ||
286 ($whoami = 'syslog');
287 }
288
289 $whoami .= "[$$]" if $lo_pid;
290
291 $mask =~ s/%m/$!/g;
292 $mask .= "\n" unless $mask =~ /\n$/;
293 $message = sprintf ($mask, @_);
294
295 $sum = $numpri + $numfac;
23642f4b 296 my $buf = "<$sum>$whoami: $message\0";
297
298 # it's possible that we'll get an error from sending
299 # (e.g. if method is UDP and there is no UDP listener,
300 # then we'll get ECONNREFUSED on the send). So what we
301 # want to do at this point is to fallback onto a different
302 # connection method.
303 while (scalar @fallbackMethods || $syslog_send) {
304 if ($failed && (time - $fail_time) > 60) {
305 # it's been a while... maybe things have been fixed
306 @fallbackMethods = ();
307 disconnect();
308 $transmit_ok = 0; # make it look like a fresh attempt
309 &connect;
310 }
311 if ($connected && !connection_ok()) {
312 # Something was OK, but has now broken. Remember coz we'll
313 # want to go back to what used to be OK.
314 $failed = $current_proto unless $failed;
315 $fail_time = time;
316 disconnect();
317 }
318 &connect unless $connected;
319 $failed = undef if ($current_proto eq $failed);
320 if ($syslog_send) {
321 if (&{$syslog_send}($buf)) {
322 $transmit_ok++;
323 return 1;
a0d0e21e 324 }
23642f4b 325 # typically doesn't happen, since errors are rare from write().
326 disconnect();
327 }
328 }
329 # could not send, could not fallback onto a working
330 # connection method. Lose.
331 return 0;
332}
333
334sub _syslog_send_console {
335 my ($buf) = @_;
336 chop($buf); # delete the NUL from the end
337 # The console print is a method which could block
338 # so we do it in a child process and always return success
339 # to the caller.
340 if (my $pid = fork) {
341 if ($lo_nowait) {
342 return 1;
343 } else {
344 if (waitpid($pid, 0) >= 0) {
345 return ($? >> 8);
346 } else {
347 # it's possible that the caller has other
348 # plans for SIGCHLD, so let's not interfere
349 return 1;
a0d0e21e 350 }
351 }
23642f4b 352 } else {
353 if (open(CONS, ">/dev/console")) {
354 my $ret = print CONS $buf . "\r";
355 exit ($ret) if defined $pid;
356 close CONS;
357 }
358 exit if defined $pid;
a0d0e21e 359 }
360}
361
23642f4b 362sub _syslog_send_stream {
363 my ($buf) = @_;
364 # XXX: this only works if the OS stream implementation makes a write
365 # look like a putmsg() with simple header. For instance it works on
366 # Solaris 8 but not Solaris 7.
367 # To be correct, it should use a STREAMS API, but perl doesn't have one.
368 return syswrite(SYSLOG, $buf, length($buf));
369}
370sub _syslog_send_socket {
371 my ($buf) = @_;
372 return syswrite(SYSLOG, $buf, length($buf));
373 #return send(SYSLOG, $buf, 0);
374}
375
a0d0e21e 376sub xlate {
377 local($name) = @_;
55497cff 378 $name = uc $name;
a0d0e21e 379 $name = "LOG_$name" unless $name =~ /^LOG_/;
748a9306 380 $name = "Sys::Syslog::$name";
2c3b42a1 381 # Can't have just eval { &$name } || -1 because some LOG_XXX may be zero.
382 my $value = eval { &$name };
383 defined $value ? $value : -1;
a0d0e21e 384}
385
386sub connect {
23642f4b 387 @fallbackMethods = @connectMethods unless (scalar @fallbackMethods);
388 if ($transmit_ok && $current_proto) {
389 # Retry what we were on, because it's worked in the past.
390 unshift(@fallbackMethods, $current_proto);
391 }
392 $connected = 0;
393 my @errs = ();
394 my $proto = undef;
395 while ($proto = shift(@fallbackMethods)) {
396 my $fn = "connect_$proto";
397 $connected = &$fn(\@errs) unless (!defined &$fn);
398 last if ($connected);
399 }
400
401 $transmit_ok = 0;
402 if ($connected) {
403 $current_proto = $proto;
404 local($old) = select(SYSLOG); $| = 1; select($old);
405 } else {
406 @fallbackMethods = ();
407 foreach my $err (@errs) {
408 carp $err;
409 }
410 croak "no connection to syslog available";
411 }
412}
413
414sub connect_tcp {
415 my ($errs) = @_;
416 unless ($host) {
417 require Sys::Hostname;
418 my($host_uniq) = Sys::Hostname::hostname();
419 ($host) = $host_uniq =~ /([A-Za-z0-9_.-]+)/; # allow FQDN (inc _)
420 }
421 my $tcp = getprotobyname('tcp');
422 if (!defined $tcp) {
423 push(@{$errs}, "getprotobyname failed for tcp");
424 return 0;
425 }
426 my $syslog = getservbyname('syslog','tcp');
427 $syslog = getservbyname('syslogng','tcp') unless (defined $syslog);
428 if (!defined $syslog) {
429 push(@{$errs}, "getservbyname failed for tcp");
430 return 0;
431 }
432
433 my $this = sockaddr_in($syslog, INADDR_ANY);
434 my $that = sockaddr_in($syslog, inet_aton($host));
435 if (!$that) {
436 push(@{$errs}, "can't lookup $host");
437 return 0;
438 }
439 if (!socket(SYSLOG,AF_INET,SOCK_STREAM,$tcp)) {
440 push(@{$errs}, "tcp socket: $!");
441 return 0;
442 }
443 setsockopt(SYSLOG, SOL_SOCKET, SO_KEEPALIVE, 1);
444 setsockopt(SYSLOG, IPPROTO_TCP, TCP_NODELAY, 1);
445 if (!connect(SYSLOG,$that)) {
446 push(@{$errs}, "tcp connect: $!");
447 return 0;
448 }
449 $syslog_send = \&_syslog_send_socket;
450 return 1;
451}
452
453sub connect_udp {
454 my ($errs) = @_;
4fc7577b 455 unless ($host) {
456 require Sys::Hostname;
2eae817d 457 my($host_uniq) = Sys::Hostname::hostname();
3e3baf6d 458 ($host) = $host_uniq =~ /([A-Za-z0-9_.-]+)/; # allow FQDN (inc _)
4fc7577b 459 }
23642f4b 460 my $udp = getprotobyname('udp');
461 if (!defined $udp) {
462 push(@{$errs}, "getprotobyname failed for udp");
463 return 0;
464 }
465 my $syslog = getservbyname('syslog','udp');
466 if (!defined $syslog) {
467 push(@{$errs}, "getservbyname failed for udp");
468 return 0;
469 }
470 my $this = sockaddr_in($syslog, INADDR_ANY);
471 my $that = sockaddr_in($syslog, inet_aton($host));
472 if (!$that) {
473 push(@{$errs}, "can't lookup $host");
474 return 0;
475 }
476 if (!socket(SYSLOG,AF_INET,SOCK_DGRAM,$udp)) {
477 push(@{$errs}, "udp socket: $!");
478 return 0;
479 }
480 if (!connect(SYSLOG,$that)) {
481 push(@{$errs}, "udp connect: $!");
482 return 0;
483 }
484 # We want to check that the UDP connect worked. However the only
485 # way to do that is to send a message and see if an ICMP is returned
486 _syslog_send_socket("");
487 if (!connection_ok()) {
488 push(@{$errs}, "udp connect: nobody listening");
489 return 0;
490 }
491 $syslog_send = \&_syslog_send_socket;
492 return 1;
493}
494
495sub connect_stream {
496 my ($errs) = @_;
497 # might want syslog_path to be variable based on syslog.h (if only
498 # it were in there!)
499 $syslog_path = '/dev/conslog';
500 if (!-w $syslog_path) {
501 push(@{$errs}, "stream $syslog_path is not writable");
502 return 0;
503 }
504 if (!open(SYSLOG, ">" . $syslog_path)) {
505 push(@{$errs}, "stream can't open $syslog_path: $!");
506 return 0;
507 }
508 $syslog_send = \&_syslog_send_stream;
509 return 1;
510}
511
512sub connect_unix {
513 my ($errs) = @_;
514 if (length _PATH_LOG()) {
515 $syslog_path = _PATH_LOG();
cb63fe9d 516 } else {
23642f4b 517 push(@{$errs}, "_PATH_LOG not available in syslog.h");
518 return 0;
519 }
520 my $that = sockaddr_un($syslog_path);
521 if (!$that) {
522 push(@{$errs}, "can't locate $syslog_path");
523 return 0;
524 }
525 if (!socket(SYSLOG,AF_UNIX,SOCK_STREAM,0)) {
526 push(@{$errs}, "unix stream socket: $!");
527 return 0;
528 }
529 if (!connect(SYSLOG,$that)) {
530 if (!socket(SYSLOG,AF_UNIX,SOCK_DGRAM,0)) {
531 push(@{$errs}, "unix dgram socket: $!");
532 return 0;
533 }
3ffabb8c 534 if (!connect(SYSLOG,$that)) {
23642f4b 535 push(@{$errs}, "unix dgram connect: $!");
536 return 0;
537 }
cb63fe9d 538 }
23642f4b 539 $syslog_send = \&_syslog_send_socket;
540 return 1;
541}
542
543sub connect_console {
544 my ($errs) = @_;
545 if (!-w '/dev/console') {
546 push(@{$errs}, "console is not writable");
547 return 0;
548 }
549 $syslog_send = \&_syslog_send_console;
550 return 1;
551}
552
553# to test if the connection is still good, we need to check if any
554# errors are present on the connection. The errors will not be raised
555# by a write. Instead, sockets are made readable and the next read
556# would cause the error to be returned. Unfortunately the syslog
557# 'protocol' never provides anything for us to read. But with
558# judicious use of select(), we can see if it would be readable...
559sub connection_ok {
560 return 1 if ($current_proto eq 'console');
561 my $rin = '';
562 vec($rin, fileno(SYSLOG), 1) = 1;
563 my $ret = select $rin, undef, $rin, 0;
564 return ($ret ? 0 : 1);
a0d0e21e 565}
566
567sub disconnect {
568 close SYSLOG;
569 $connected = 0;
23642f4b 570 $syslog_send = undef;
a0d0e21e 571}
572
5731;