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