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