From: bluhm Date: Wed, 20 Aug 2014 20:52:14 +0000 (+0000) Subject: Run syslogd regressions tests. As only one syslogd can run per X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=1f53c19fe11027baaa79c8fdd5154bbd2e2d526f;p=openbsd Run syslogd regressions tests. As only one syslogd can run per machine, each test kills any syslogd first. At the end the system's syslogd gets restarted. The test framework runs a client, and a server, and a syslogd. The messages are passed via the log socket or via UDP from the client to syslogd. From there UDP transport is used to reach the server. All processes write log files where the message has to show up. The test arguments are kept in the args-*.pl files. The content of a log file, the data sent to a pipe process and what the server received are checked. The invocation of the sendsyslog(2) syscall is checked with ktrace, the open file descriptors of syslogd are checked with fstat. --- diff --git a/regress/usr.sbin/syslogd/Client.pm b/regress/usr.sbin/syslogd/Client.pm new file mode 100644 index 00000000000..0de6e78ca4a --- /dev/null +++ b/regress/usr.sbin/syslogd/Client.pm @@ -0,0 +1,62 @@ +# $OpenBSD: Client.pm,v 1.1.1.1 2014/08/20 20:52:14 bluhm Exp $ + +# Copyright (c) 2010-2014 Alexander Bluhm +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +use strict; +use warnings; + +package Client; +use parent 'Proc'; +use Carp; +use IO::Socket::INET6; +use Sys::Syslog qw(:standard :extended :macros); + +sub new { + my $class = shift; + my %args = @_; + $args{ktracefile} ||= "client.ktrace"; + $args{logfile} ||= "client.log"; + $args{up} ||= "Openlog"; + my $self = Proc::new($class, %args); + return $self; +} + +sub child { + my $self = shift; + + if ($self->{connectdomain}) { + my $cs = IO::Socket::INET6->new( + Proto => "udp", + Domain => $self->{connectdomain}, + PeerAddr => $self->{connectaddr}, + PeerPort => $self->{connectport}, + ) or die ref($self), " socket connect failed: $!"; + print STDERR "connect sock: ",$cs->sockhost()," ", + $cs->sockport(),"\n"; + print STDERR "connect peer: ",$cs->peerhost()," ", + $cs->peerport(),"\n"; + + *STDIN = *STDOUT = $self->{cs} = $cs; + } + + if ($self->{logsock}) { + setlogsock($self->{logsock}) + or die ref($self), " setlogsock failed: $!"; + } + # we take LOG_UUCP as it is not used nowadays + openlog("syslogd-regress", "ndelay,perror,pid", LOG_UUCP); +} + +1; diff --git a/regress/usr.sbin/syslogd/LICENSE b/regress/usr.sbin/syslogd/LICENSE new file mode 100644 index 00000000000..a6e1dd38fcf --- /dev/null +++ b/regress/usr.sbin/syslogd/LICENSE @@ -0,0 +1,13 @@ +# Copyright (c) 2010-2014 Alexander Bluhm +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/regress/usr.sbin/syslogd/Makefile b/regress/usr.sbin/syslogd/Makefile new file mode 100644 index 00000000000..f3734001a5d --- /dev/null +++ b/regress/usr.sbin/syslogd/Makefile @@ -0,0 +1,86 @@ +# $OpenBSD: Makefile,v 1.1.1.1 2014/08/20 20:52:14 bluhm Exp $ + +# The following ports must be installed for the regression tests: +# p5-IO-Socket-INET6 object interface for AF_INET and AF_INET6 domain sockets +# p5-Socket6 Perl defines relating to AF_INET6 sockets +# p5-IO-Socket-SSL perl interface to SSL sockets +# +# Check wether all required perl packages are installed. If some +# are missing print a warning and skip the tests, but do not fail. + +PERL_REQUIRE != perl -Mstrict -Mwarnings -e ' \ + eval { require IO::Socket::INET6 } or print $@; \ + eval { require Socket6 } or print $@; \ + eval { require IO::Socket::SSL } or print $@; \ +' +.if ! empty (PERL_REQUIRE) +regress: + @echo "${PERL_REQUIRE}" + @echo install these perl packages for additional tests +.endif + +# Automatically generate regress targets from test cases in directory. + +ARGS != cd ${.CURDIR} && ls args-*.pl +TARGETS ?= ${ARGS} +REGRESS_TARGETS = ${TARGETS:S/^/run-regress-/} +CLEANFILES += *.log *.pem *.crt *.key syslogd.conf stamp-* +CLEANFILES += ktrace.out *.ktrace *.fstat + +.MAIN: all + +.if make (regress) || make (all) +.BEGIN: + @echo + [ -z "${SUDO}" ] || ${SUDO} true +.END: + @echo + ${SUDO} /etc/rc.d/syslogd restart +.endif + +# Set variables so that make runs with and without obj directory. +# Only do that if necessary to keep visible output short. + +.if ${.CURDIR} == ${.OBJDIR} +PERLINC = +PERLPATH = +.else +PERLINC = -I${.CURDIR} +PERLPATH = ${.CURDIR}/ +.endif + +# The arg tests take a perl hash with arguments controlling the +# test parameters. Generally they consist of client, syslogd, server. + +.for a in ${ARGS} +run-regress-$a: $a + @echo '\n======== $@ ========' + time SUDO=${SUDO} KTRACE=${KTRACE} SYSLOGD=${SYSLOGD} perl ${PERLINC} ${PERLPATH}syslogd.pl ${PERLPATH}$a +.endfor + +# create the certificates for SSL + +127.0.0.1.crt: + openssl req -batch -new -nodes -newkey rsa -keyout 127.0.0.1.key -subj /CN=127.0.0.1/ -x509 -out $@ + ${SUDO} cp 127.0.0.1.crt /etc/ssl/ + ${SUDO} cp 127.0.0.1.key /etc/ssl/private/ + +server-cert.pem: + openssl req -batch -new -nodes -newkey rsa -keyout server-key.pem -subj /CN=localhost/ -x509 -out $@ + +${REGRESS_TARGETS:M*ssl*} ${REGRESS_TARGETS:M*https*}: server-cert.pem +${REGRESS_TARGETS:M*ssl*} ${REGRESS_TARGETS:M*https*}: 127.0.0.1.crt + +# make perl syntax check for all args files + +.PHONY: syntax + +syntax: stamp-syntax + +stamp-syntax: ${ARGS} +.for a in ${ARGS} + @perl -c ${PERLPATH}$a +.endfor + @date >$@ + +.include diff --git a/regress/usr.sbin/syslogd/Proc.pm b/regress/usr.sbin/syslogd/Proc.pm new file mode 100644 index 00000000000..5e8a14f0368 --- /dev/null +++ b/regress/usr.sbin/syslogd/Proc.pm @@ -0,0 +1,197 @@ +# $OpenBSD: Proc.pm,v 1.1.1.1 2014/08/20 20:52:14 bluhm Exp $ + +# Copyright (c) 2010-2014 Alexander Bluhm +# Copyright (c) 2014 Florian Riehm +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +use strict; +use warnings; + +package Proc; +use Carp; +use Errno; +use IO::File; +use POSIX; +use Time::HiRes qw(time alarm sleep); + +my %CHILDREN; + +sub kill_children { + my @pids = @_ ? @_ : keys %CHILDREN + or return; + my @perms; + foreach my $pid (@pids) { + if (kill(TERM => $pid) != 1 and $!{EPERM}) { + push @perms, $pid; + } + } + if (my $sudo = $ENV{SUDO} and @perms) { + local $?; # do not modify during END block + my @cmd = ($sudo, '/bin/kill', '-TERM', @perms); + system(@cmd); + } + delete @CHILDREN{@pids}; +} + +BEGIN { + $SIG{TERM} = $SIG{INT} = sub { + my $sig = shift; + kill_children(); + $SIG{TERM} = $SIG{INT} = 'DEFAULT'; + POSIX::raise($sig); + }; +} + +END { + kill_children(); + $SIG{TERM} = $SIG{INT} = 'DEFAULT'; +} + +sub new { + my $class = shift; + my $self = { @_ }; + $self->{down} ||= "Shutdown"; + $self->{func} && ref($self->{func}) eq 'CODE' + or croak "$class func not given"; + !$self->{ktrace} || $self->{ktracefile} + or croak "$class ktrace file not given"; + $self->{logfile} + or croak "$class log file not given"; + open(my $fh, '>', $self->{logfile}) + or die "$class log file $self->{logfile} create failed: $!"; + $fh->autoflush; + $self->{log} = $fh; + return bless $self, $class; +} + +sub run { + my $self = shift; + + pipe(my $reader, my $writer) + or die ref($self), " pipe to child failed: $!"; + defined(my $pid = fork()) + or die ref($self), " fork child failed: $!"; + if ($pid) { + $CHILDREN{$pid} = 1; + $self->{pid} = $pid; + close($reader); + $self->{pipe} = $writer; + return $self; + } + %CHILDREN = (); + $SIG{TERM} = $SIG{INT} = 'DEFAULT'; + $SIG{__DIE__} = sub { + die @_ if $^S; + warn @_; + IO::Handle::flush(\*STDERR); + POSIX::_exit(255); + }; + open(STDERR, '>&', $self->{log}) + or die ref($self), " dup STDERR failed: $!"; + open(STDOUT, '>&', $self->{log}) + or die ref($self), " dup STDOUT failed: $!"; + close($writer); + open(STDIN, '<&', $reader) + or die ref($self), " dup STDIN failed: $!"; + close($reader); + + if ($self->{ktrace}) { + my @cmd = ("ktrace", "-f", $self->{ktracefile}, "-p", $$); + system(@cmd) + and die ref($self), " system '@cmd' failed: $?"; + } + $self->child(); + print STDERR $self->{up}, "\n"; + $self->{func}->($self); + print STDERR "Shutdown", "\n"; + + IO::Handle::flush(\*STDOUT); + IO::Handle::flush(\*STDERR); + POSIX::_exit(0); +} + +sub wait { + my $self = shift; + my $flags = shift; + + my $pid = $self->{pid} + or croak ref($self), " no child pid"; + my $kid = waitpid($pid, $flags); + if ($kid > 0) { + my $status = $?; + my $code; + $code = "exit: ". WEXITSTATUS($?) if WIFEXITED($?); + $code = "signal: ". WTERMSIG($?) if WIFSIGNALED($?); + $code = "stop: ". WSTOPSIG($?) if WIFSTOPPED($?); + delete $CHILDREN{$pid} if WIFEXITED($?) || WIFSIGNALED($?); + return wantarray ? ($kid, $status, $code) : $kid; + } + return $kid; +} + +sub loggrep { + my $self = shift; + my($regex, $timeout) = @_; + + my $end = time() + $timeout if $timeout; + + do { + my($kid, $status, $code) = $self->wait(WNOHANG); + if ($kid > 0 && $status != 0) { + # child terminated with failure + die ref($self), " child status: $status $code"; + } + open(my $fh, '<', $self->{logfile}) + or die ref($self), " log file open failed: $!"; + my @match = grep { /$regex/ } <$fh>; + return wantarray ? @match : $match[0] if @match; + close($fh); + # pattern not found + if ($kid == 0) { + # child still running, wait for log data + sleep .1; + } else { + # child terminated, no new log data possible + return; + } + } while ($timeout and time() < $end); + + return; +} + +sub up { + my $self = shift; + my $timeout = shift || 10; + $self->loggrep(qr/$self->{up}/, $timeout) + or croak ref($self), " no '$self->{up}' in $self->{logfile} ". + "after $timeout seconds"; + return $self; +} + +sub down { + my $self = shift; + my $timeout = shift || 30; + $self->loggrep(qr/$self->{down}/, $timeout) + or croak ref($self), " no '$self->{down}' in $self->{logfile} ". + "after $timeout seconds"; + return $self; +} + +sub kill_child { + my $self = shift; + kill_children($self->{pid}); + return $self; +} + +1; diff --git a/regress/usr.sbin/syslogd/README b/regress/usr.sbin/syslogd/README new file mode 100644 index 00000000000..4d8234855a5 --- /dev/null +++ b/regress/usr.sbin/syslogd/README @@ -0,0 +1,24 @@ +Run syslogd regressions tests. As only one syslogd can run per +machine, each test kills any syslogd first. At the end the system's +syslogd gets restarted. +The test framework runs a client, and a server, and a syslogd. The +messages are passed via the log socket or via UDP from the client +to syslogd. From there UDP transport is used to reach the server. +All processes write log files where the message has to show up. +The test arguments are kept in the args-*.pl files. +The content of a log file, the data sent to a pipe process and what +the server received are checked. The invocation of the sendsyslog(2) +syscall is checked with ktrace, the open file descriptors of syslogd +are checked with fstat. + +SUDO=sudo +As syslogd needs root privileges either run the tests as root or +set this variable and run make as a regular user. Only the code +that requires it, is run as root. + +KTRACE=ktrace +Set this variable if you want a ktrace output from syslogd. Note that +ktrace is invoked after sudo as sudo would disable it. + +SYSLOGD=/usr/src/usr.sbin/syslogd/obj/syslogd +Start an alternative syslogd program that is not in the path. diff --git a/regress/usr.sbin/syslogd/Server.pm b/regress/usr.sbin/syslogd/Server.pm new file mode 100644 index 00000000000..2b3087b0c89 --- /dev/null +++ b/regress/usr.sbin/syslogd/Server.pm @@ -0,0 +1,83 @@ +# $OpenBSD: Server.pm,v 1.1.1.1 2014/08/20 20:52:14 bluhm Exp $ + +# Copyright (c) 2010-2014 Alexander Bluhm +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +use strict; +use warnings; + +package Server; +use parent 'Proc'; +use Carp; +use Socket; +use Socket6; +use IO::Socket; +use IO::Socket::INET6; +use IO::Socket::SSL; + +sub new { + my $class = shift; + my %args = @_; + $args{ktracefile} ||= "server.ktrace"; + $args{logfile} ||= "server.log"; + $args{up} ||= "Accepted"; + my $self = Proc::new($class, %args); + $self->{listenprotocol} ||= "udp"; + $self->{listendomain} + or croak "$class listen domain not given"; + $SSL_ERROR = ""; + my $iosocket = $self->{listenprotocol} eq "tls" ? + "IO::Socket::SSL" : "IO::Socket::INET6"; + my $proto = $self->{listenprotocol}; + $proto = "tcp" if $proto eq "tls"; + my $ls = $iosocket->new( + Proto => $proto, + ReuseAddr => 1, + Domain => $self->{listendomain}, + $self->{listenaddr} ? (LocalAddr => $self->{listenaddr}) : (), + $self->{listenport} ? (LocalPort => $self->{listenport}) : (), + SSL_key_file => "server-key.pem", + SSL_cert_file => "server-cert.pem", + SSL_verify_mode => SSL_VERIFY_NONE, + ) or die ref($self), " $iosocket socket listen failed: $!,$SSL_ERROR"; + if ($self->{listenprotocol} eq "tcp") { + listen($ls, 1) + or die ref($self), " socket failed: $!"; + } + my $log = $self->{log}; + print $log "listen sock: ",$ls->sockhost()," ",$ls->sockport(),"\n"; + $self->{listenaddr} = $ls->sockhost() unless $self->{listenaddr}; + $self->{listenport} = $ls->sockport() unless $self->{listenport}; + $self->{ls} = $ls; + return $self; +} + +sub child { + my $self = shift; + + my $iosocket = $self->{ssl} ? "IO::Socket::SSL" : "IO::Socket::INET6"; + my $as = $self->{ls}; + if ($self->{listenprotocol} ne "udp") { + $as = $self->{ls}->accept() + or die ref($self), " $iosocket socket accept failed: $!"; + print STDERR "accept sock: ",$as->sockhost()," ", + $as->sockport(),"\n"; + print STDERR "accept peer: ",$as->peerhost()," ", + $as->peerport(),"\n"; + } + + *STDIN = *STDOUT = $self->{as} = $as; +} + +1; diff --git a/regress/usr.sbin/syslogd/Syslogd.pm b/regress/usr.sbin/syslogd/Syslogd.pm new file mode 100644 index 00000000000..dfa275c4fb0 --- /dev/null +++ b/regress/usr.sbin/syslogd/Syslogd.pm @@ -0,0 +1,128 @@ +# $OpenBSD: Syslogd.pm,v 1.1.1.1 2014/08/20 20:52:14 bluhm Exp $ + +# Copyright (c) 2010-2014 Alexander Bluhm +# Copyright (c) 2014 Florian Riehm +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +use strict; +use warnings; + +package Syslogd; +use parent 'Proc'; +use Carp; +use Cwd; +use File::Basename; + +sub new { + my $class = shift; + my %args = @_; + $args{fstatfile} ||= "syslogd.fstat"; + $args{logfile} ||= "syslogd.log"; + $args{up} ||= "syslogd: started"; + $args{down} ||= "syslogd: exiting"; + $args{func} = sub { Carp::confess "$class func may not be called" }; + $args{conffile} ||= "syslogd.conf"; + $args{outfile} ||= "file.log"; + $args{outpipe} ||= "pipe.log"; + my $self = Proc::new($class, %args); + $self->{connectaddr} + or croak "$class connect addr not given"; + + _make_abspath(\$self->{$_}) foreach (qw(conffile outfile outpipe)); + + # substitute variables in config file + my $connectprotocol = $self->{connectprotocol}; + my $connectdomain = $self->{connectdomain}; + my $connectaddr = $self->{connectaddr}; + my $connectport = $self->{connectport}; + + open(my $fh, '>', $self->{conffile}) + or die ref($self), " create conf file $self->{conffile} failed: $!"; + print $fh "*.*\t$self->{outfile}\n"; + print $fh "*.*\t|dd of=$self->{outpipe} status=none\n"; + my $loghost = $self->{loghost}; + if ($loghost) { + $loghost =~ s/(\$[a-z]+)/$1/eeg; + } else { + $loghost = "\@$connectaddr"; + $loghost .= ":$connectport" if $connectport; + } + print $fh "*.*\t$loghost\n"; + close $fh; + + open($fh, '>', $self->{outfile}) + or die ref($self), " create log file $self->{outfile} failed: $!"; + close $fh; + + open($fh, '>', $self->{outpipe}) + or die ref($self), " create pipe file $self->{outpipe} failed: $!"; + close $fh; + chmod(0666, $self->{outpipe}) + or die ref($self), " chmod pipe file $self->{outpipe} failed: $!"; + + return $self; +} + +sub child { + my $self = shift; + my @sudo = $ENV{SUDO} ? $ENV{SUDO} : (); + + my @pkill = (@sudo, "pkill", "-x", "syslogd"); + my @pgrep = ("pgrep", "-x", "syslogd"); + system(@pkill) && $? != 256 + and die ref($self), " system '@pkill' failed: $?"; + while ($? == 0) { + print STDERR "syslogd still running\n"; + system(@pgrep) && $? != 256 + and die ref($self), " system '@pgrep' failed: $?"; + } + print STDERR "syslogd not running\n"; + + my @ktrace = $ENV{KTRACE} ? ($ENV{KTRACE}, "-i") : (); + my $syslogd = $ENV{SYSLOGD} ? $ENV{SYSLOGD} : "syslogd"; + my @cmd = (@sudo, @ktrace, $syslogd, "-d", "-f", $self->{conffile}); + push @cmd, @{$self->{options}} if $self->{options}; + print STDERR "execute: @cmd\n"; + exec @cmd; + die ref($self), " exec '@cmd' failed: $!"; +} + +sub up { + my $self = Proc::up(shift, @_); + + if ($self->{fstat}) { + open(my $fh, '>', $self->{fstatfile}) or die ref($self), + " open $self->{fstatfile} for writing failed: $!"; + my @cmd = ("fstat"); + open(my $fs, '-|', @cmd) + or die ref($self), " open pipe from '@cmd' failed: $!"; + print $fh grep { /^\w+ *syslogd *\d+/ } <$fs>; + close($fs) or die ref($self), $! ? + " close pipe from '@cmd' failed: $!" : + " command '@cmd' failed: $?"; + close($fh) + or die ref($self), " close $self->{fstatfile} failed: $!"; + } +} + +sub _make_abspath { + my $file = ref($_[0]) ? ${$_[0]} : $_[0]; + if (substr($file, 0, 1) ne "/") { + $file = getcwd(). "/". $file; + ${$_[0]} = $file if ref($_[0]); + } + return $file; +} + +1; diff --git a/regress/usr.sbin/syslogd/args-client-udp-nodns.pl b/regress/usr.sbin/syslogd/args-client-udp-nodns.pl new file mode 100644 index 00000000000..00d88926a5c --- /dev/null +++ b/regress/usr.sbin/syslogd/args-client-udp-nodns.pl @@ -0,0 +1,23 @@ +# The client writes a message to Sys::Syslog UDP method. +# The syslogd writes it into a file and through a pipe without dns. +# The syslogd passes it via UDP to the loghost. +# The server receives the message on its UDP socket. +# Find the message in client, file, pipe, syslogd, server log. +# Check that the file log contains the 127.0.0.1 address. + +use strict; +use warnings; + +our %args = ( + client => { + logsock => { type => "udp", host => "127.0.0.1", port => 514 }, + }, + syslogd => { + options => ["-un"], + }, + file => { + loggrep => qr/ 127.0.0.1 syslogd-regress\[\d+\]: /. get_log(), + }, +); + +1; diff --git a/regress/usr.sbin/syslogd/args-client-udp.pl b/regress/usr.sbin/syslogd/args-client-udp.pl new file mode 100644 index 00000000000..6f62737fa84 --- /dev/null +++ b/regress/usr.sbin/syslogd/args-client-udp.pl @@ -0,0 +1,23 @@ +# The client writes a message to Sys::Syslog UDP method. +# The syslogd writes it into a file and through a pipe. +# The syslogd passes it via UDP to the loghost. +# The server receives the message on its UDP socket. +# Find the message in client, file, pipe, syslogd, server log. +# Check that the file log contains the localhost name. + +use strict; +use warnings; + +our %args = ( + client => { + logsock => { type => "udp", host => "127.0.0.1", port => 514 }, + }, + syslogd => { + options => ["-u"], + }, + file => { + loggrep => qr/ localhost syslogd-regress\[\d+\]: /. get_log(), + }, +); + +1; diff --git a/regress/usr.sbin/syslogd/args-client-udp4-nodns.pl b/regress/usr.sbin/syslogd/args-client-udp4-nodns.pl new file mode 100644 index 00000000000..c1a7bbc344a --- /dev/null +++ b/regress/usr.sbin/syslogd/args-client-udp4-nodns.pl @@ -0,0 +1,23 @@ +# The client writes a message to a localhost IPv4 UDP socket. +# The syslogd writes it into a file and through a pipe without dns. +# The syslogd passes it via UDP to the loghost. +# The server receives the message on its UDP socket. +# Find the message in client, file, pipe, syslogd, server log. +# Check that the file log contains the 127.0.0.1 address. + +use strict; +use warnings; + +our %args = ( + client => { + connect => { domain => AF_INET, addr => "127.0.0.1", port => 514 }, + }, + syslogd => { + options => ["-un"], + }, + file => { + loggrep => qr/ 127.0.0.1 /. get_log(), + }, +); + +1; diff --git a/regress/usr.sbin/syslogd/args-client-udp4.pl b/regress/usr.sbin/syslogd/args-client-udp4.pl new file mode 100644 index 00000000000..8b16fb16458 --- /dev/null +++ b/regress/usr.sbin/syslogd/args-client-udp4.pl @@ -0,0 +1,23 @@ +# The client writes a message to a localhost IPv4 UDP socket. +# The syslogd writes it into a file and through a pipe. +# The syslogd passes it via UDP to the loghost. +# The server receives the message on its UDP socket. +# Find the message in client, file, pipe, syslogd, server log. +# Check that the file log contains the localhost name. + +use strict; +use warnings; + +our %args = ( + client => { + connect => { domain => AF_INET, addr => "127.0.0.1", port => 514 }, + }, + syslogd => { + options => ["-u"], + }, + file => { + loggrep => qr/ localhost /. get_log(), + }, +); + +1; diff --git a/regress/usr.sbin/syslogd/args-default.pl b/regress/usr.sbin/syslogd/args-default.pl new file mode 100644 index 00000000000..c37ce36f1f9 --- /dev/null +++ b/regress/usr.sbin/syslogd/args-default.pl @@ -0,0 +1,14 @@ +# Test with default values, that is: +# The client writes a message to Sys::Syslog native method. +# The syslogd writes it into a file and through a pipe. +# The syslogd passes it via UDP to the loghost. +# The server receives the message on its UDP socket. +# Find the message in client, file, pipe, syslogd, server log. + +use strict; +use warnings; + +our %args = ( +); + +1; diff --git a/regress/usr.sbin/syslogd/args-sendsyslog.pl b/regress/usr.sbin/syslogd/args-sendsyslog.pl new file mode 100644 index 00000000000..b29b25ac91f --- /dev/null +++ b/regress/usr.sbin/syslogd/args-sendsyslog.pl @@ -0,0 +1,23 @@ +# The client writes a message to Sys::Syslog native method. +# The syslogd writes it into a file and through a pipe. +# The syslogd passes it via UDP to the loghost. +# The server receives the message on its UDP socket. +# Find the message in client, file, pipe, syslogd, server log. +# Create a ktrace dump of the client and check that sendsyslog(2) +# has been used. + +use strict; +use warnings; + +our %args = ( + client => { + ktrace => 1, + kdump => { + qr/CALL sendsyslog/ => 2, + qr/GIO fd -1 wrote \d+ bytes/ => 2, + qr/RET sendsyslog 0/ => 2, + }, + }, +); + +1; diff --git a/regress/usr.sbin/syslogd/args-socket.pl b/regress/usr.sbin/syslogd/args-socket.pl new file mode 100644 index 00000000000..9173a42069e --- /dev/null +++ b/regress/usr.sbin/syslogd/args-socket.pl @@ -0,0 +1,27 @@ +# Test with default values, that is: +# The client writes a message to a localhost IPv4 UDP socket. +# The syslogd writes it into a file and through a pipe. +# The syslogd passes it via UDP to the loghost. +# The server receives the message on its UDP socket. +# Find the message in client, file, pipe, syslogd, server log. +# Check that the syslogd has one IPv4 socket in fstat output. + +use strict; +use warnings; + +our %args = ( + client => { + connect => { domain => AF_INET, addr => "127.0.0.1", port => 514 }, + }, + syslogd => { + fstat => 1, + options => ["-nu"], + }, + fstat => { + loggrep => { + qr/ internet dgram udp \*:514$/ => 1, + }, + }, +); + +1; diff --git a/regress/usr.sbin/syslogd/funcs.pl b/regress/usr.sbin/syslogd/funcs.pl new file mode 100644 index 00000000000..593dd5a5012 --- /dev/null +++ b/regress/usr.sbin/syslogd/funcs.pl @@ -0,0 +1,183 @@ +# $OpenBSD: funcs.pl,v 1.1.1.1 2014/08/20 20:52:14 bluhm Exp $ + +# Copyright (c) 2010-2014 Alexander Bluhm +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +use strict; +use warnings; +use Errno; +use List::Util qw(first); +use Socket; +use Socket6; +use Sys::Syslog qw(:standard :extended :macros); +use IO::Socket; +use IO::Socket::INET6; + +my $testlog = "syslogd regress test log message"; +my $downlog = "syslogd regress client shutdown"; + +######################################################################## +# Client funcs +######################################################################## + +sub write_log { + my $self = shift; + + if ($self->{connectdomain}) { + print $testlog; + print STDERR $testlog, "\n"; + } else { + syslog(LOG_INFO, $testlog); + } + write_shutdown($self, @_); +} + +sub write_shutdown { + my $self = shift; + + setlogsock("native") + or die ref($self), " setlogsock native failed: $!"; + syslog(LOG_NOTICE, $downlog); +} + +######################################################################## +# Server funcs +######################################################################## + +sub read_log { + my $self = shift; + + for (;;) { + defined(sysread(STDIN, my $line, 8194)) + or die ref($self), " read log line failed: $!"; + chomp $line; + print STDERR ">>> $line\n"; + last if $line =~ /$downlog/; + } +} + +######################################################################## +# Script funcs +######################################################################## + +sub get_log { + return $testlog; +} + +sub check_logs { + my ($c, $r, $s, %args) = @_; + + return if $args{nocheck}; + + check_log($c, $r, $s, %args); + check_out($r, %args); + check_stat($r, %args); + check_kdump($c, $s, %args); +} + +sub check_pattern { + my ($name, $proc, $pattern, $func) = @_; + + $pattern = [ $pattern ] unless ref($pattern) eq 'ARRAY'; + foreach my $pat (@$pattern) { + if (ref($pat) eq 'HASH') { + while (my($re, $num) = each %$pat) { + my @matches = $func->($proc, $re); + @matches == $num + or die "$name matches '@matches': ", + "'$re' => $num"; + } + } else { + $func->($proc, $pat) + or die "$name log missing pattern: $pat"; + } + } +} + +sub check_log { + my ($c, $r, $s, %args) = @_; + + my %name2proc = (client => $c, syslogd => $r, server => $s); + foreach my $name (qw(client syslogd server)) { + next if $args{$name}{nocheck}; + my $p = $name2proc{$name} or next; + my $pattern = $args{$name}{loggrep} || $testlog; + check_pattern($name, $p, $pattern, \&loggrep); + } +} + +sub loggrep { + my ($proc, $pattern) = @_; + + return $proc->loggrep($pattern); +} + +sub check_out { + my ($r, %args) = @_; + + foreach my $name (qw(file pipe)) { + next if $args{$name}{nocheck}; + my $file = $r->{"out$name"} or next; + my $pattern = $args{$name}{loggrep} || $testlog; + check_pattern($name, $file, $pattern, \&filegrep); + } +} + +sub check_stat { + my ($r, %args) = @_; + + foreach my $name (qw(fstat)) { + next if $args{$name}{nocheck}; + my $file = $r->{$name} && $r->{"${name}file"} or next; + my $pattern = $args{$name}{loggrep} or next; + check_pattern($name, $file, $pattern, \&filegrep); + } +} + +sub filegrep { + my ($file, $pattern) = @_; + + open(my $fh, '<', $file) + or die "Open file $file for reading failed: $!"; + return wantarray ? + grep { /$pattern/ } <$fh> : first { /$pattern/ } <$fh>; +} + +sub check_kdump { + my ($c, $s, %args) = @_; + + my %name2proc = (client => $c, server => $s); + foreach my $name (qw(client server)) { + next unless $args{$name}{ktrace}; + my $p = $name2proc{$name} or next; + my $file = $p->{ktracefile} or next; + my $pattern = $args{$name}{kdump} or next; + check_pattern($name, $file, $pattern, \&kdumpgrep); + } +} + +sub kdumpgrep { + my ($file, $pattern) = @_; + + my @cmd = ("kdump", "-f", $file); + open(my $fh, '-|', @cmd) + or die "Open pipe from '@cmd' failed: $!"; + my @matches = grep { /$pattern/ } <$fh>; + close($fh) or die $! ? + "Close pipe from '@cmd' failed: $!" : + "Command '@cmd' failed: $?"; + return wantarray ? @matches : $matches[0]; +} + +1; diff --git a/regress/usr.sbin/syslogd/syslogd.pl b/regress/usr.sbin/syslogd/syslogd.pl new file mode 100644 index 00000000000..afe434fcae0 --- /dev/null +++ b/regress/usr.sbin/syslogd/syslogd.pl @@ -0,0 +1,79 @@ +#!/usr/bin/perl +# $OpenBSD: syslogd.pl,v 1.1.1.1 2014/08/20 20:52:14 bluhm Exp $ + +# Copyright (c) 2010-2014 Alexander Bluhm +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +use strict; +use warnings; +use Socket; +use Socket6; + +use Client; +use Syslogd; +use Server; +require 'funcs.pl'; + +sub usage { + die "usage: syslogd.pl [test-args.pl]\n"; +} + +my $testfile; +our %args; +if (@ARGV and -f $ARGV[-1]) { + $testfile = pop; + do $testfile + or die "Do test file $testfile failed: ", $@ || $!; +} +@ARGV == 0 or usage(); + +foreach my $name (qw(client syslogd server)) { + foreach my $action (qw(connect listen)) { + my $h = $args{$name}{$action} or next; + foreach my $k (qw(protocol domain addr port)) { + $args{$name}{"$action$k"} = $h->{$k}; + } + } +} +my $s = Server->new( + func => \&read_log, + listendomain => AF_INET, + listenaddr => "127.0.0.1", + %{$args{server}}, + testfile => $testfile, +) unless $args{server}{noserver}; +my $r = Syslogd->new( + connectaddr => "127.0.0.1", + connectport => $s && $s->{listenport}, + %{$args{syslogd}}, + testfile => $testfile, +); +my $c = Client->new( + func => \&write_log, + %{$args{client}}, + testfile => $testfile, +) unless $args{client}{noclient}; + +$s->run unless $args{server}{noserver}; +$r->run; +$r->up; +$c->run->up unless $args{client}{noclient}; +$s->up unless $args{server}{noserver}; + +$c->down unless $args{client}{noclient}; +$s->down unless $args{server}{noserver}; +$r->kill_child; +$r->down; + +check_logs($c, $r, $s, %args);