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