Skip to content

Commit b731195

Browse files
authored
fe_test: add test for syslog feature (#5985)
The syslog feature allows to run a program and redirect its output/error stream to system log. It's triggered passing `syslog_stdout` argument to `Forkhelpers` functions like `execute_command_get_output`. To test this feature use a small preload library to redirect syslog writing. This allows to test program without changing it. The log is redirected to `/tmp/xyz` instead of `/dev/log`. The name was chosen to allow for future static build redirection. The C program is used only for the test, so the code style take into account this (specifically it does not try to handle all redirect situation and all error paths).
2 parents 4e45833 + a320764 commit b731195

File tree

4 files changed

+207
-4
lines changed

4 files changed

+207
-4
lines changed

ocaml/forkexecd/test/dune

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
(executable
22
(modes exe)
33
(name fe_test)
4-
(libraries fmt forkexec mtime clock mtime.clock.os uuid xapi-stdext-unix fd-send-recv))
4+
(libraries fmt forkexec mtime clock mtime.clock.os uuid xapi-stdext-unix fd-send-recv xapi-log))
5+
6+
; preload library to redirect "/dev/log"
7+
(rule
8+
(targets syslog.so)
9+
(deps syslog.c)
10+
(action
11+
(run %{cc} -O2 -Wall -DPIC -fPIC -s --shared -o %{targets} %{deps} -ldl)))
512

613
(rule
714
(alias runtest)
815
(package xapi-forkexecd)
9-
(deps fe_test.sh fe_test.exe ../src/fe_main.exe)
16+
(deps fe_test.sh fe_test.exe ../src/fe_main.exe syslog.so)
1017
(action
1118
(run ./fe_test.sh)))

ocaml/forkexecd/test/fe_test.ml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,77 @@ let test_internal_failure_error () =
238238
Printexc.print_backtrace stderr ;
239239
fail "Failed with unexpected exception: %s" (Printexc.to_string e)
240240

241+
(* Emulate syslog and output lines to returned channel *)
242+
let syslog_lines sockname =
243+
let clean () = try Unix.unlink sockname with _ -> () in
244+
clean () ;
245+
let sock = Unix.socket ~cloexec:true Unix.PF_UNIX Unix.SOCK_DGRAM 0 in
246+
let rd_pipe, wr_pipe = Unix.pipe ~cloexec:true () in
247+
Unix.bind sock (Unix.ADDR_UNIX sockname) ;
248+
match Unix.fork () with
249+
| 0 ->
250+
(* child, read from socket and output to pipe *)
251+
let term_handler = Sys.Signal_handle (fun _ -> clean () ; exit 0) in
252+
Sys.set_signal Sys.sigint term_handler ;
253+
Sys.set_signal Sys.sigterm term_handler ;
254+
Unix.close rd_pipe ;
255+
Unix.dup2 wr_pipe Unix.stdout ;
256+
Unix.close wr_pipe ;
257+
let buf = Bytes.create 1024 in
258+
let rec fwd () =
259+
let l = Unix.recv sock buf 0 (Bytes.length buf) [] in
260+
if l > 0 then (
261+
print_bytes (Bytes.sub buf 0 l) ;
262+
print_newline () ;
263+
(fwd [@tailcall]) ()
264+
)
265+
in
266+
fwd () ; exit 0
267+
| pid ->
268+
Unix.close sock ;
269+
Unix.close wr_pipe ;
270+
(pid, Unix.in_channel_of_descr rd_pipe)
271+
272+
let test_syslog with_stderr =
273+
let rec syslog_line ic =
274+
let line = input_line ic in
275+
(* ignore log lines from daemon *)
276+
if String.ends_with ~suffix:"\\x0A" line then
277+
syslog_line ic
278+
else
279+
let re = Str.regexp ": " in
280+
match Str.bounded_split re line 3 with
281+
| _ :: _ :: final :: _ ->
282+
final ^ "\n"
283+
| _ ->
284+
raise Not_found
285+
in
286+
let expected_out = "output string" in
287+
let expected_err = "error string" in
288+
let args = ["echo"; expected_out; expected_err] in
289+
let child, ic = syslog_lines "/tmp/xyz" in
290+
let out, err =
291+
Forkhelpers.execute_command_get_output ~syslog_stdout:Syslog_DefaultKey
292+
~redirect_stderr_to_stdout:with_stderr exe args
293+
in
294+
expect "" (out ^ "\n") ;
295+
if with_stderr then
296+
expect "" (err ^ "\n")
297+
else
298+
expect expected_err err ;
299+
Unix.sleepf 0.05 ;
300+
Syslog.log Syslog.Daemon Syslog.Err "exe: XXX\n" ;
301+
Syslog.log Syslog.Daemon Syslog.Err "exe: YYY\n" ;
302+
let out = syslog_line ic in
303+
expect expected_out out ;
304+
let err = syslog_line ic in
305+
let expected = if with_stderr then expected_err else "XXX" in
306+
expect expected err ;
307+
Unix.kill child Sys.sigint ;
308+
Unix.waitpid [] child |> ignore ;
309+
close_in ic ;
310+
print_endline "Completed syslog test"
311+
241312
let master fds =
242313
Printf.printf "\nPerforming timeout tests\n%!" ;
243314
test_delay () ;
@@ -249,6 +320,10 @@ let master fds =
249320
test_input () ;
250321
Printf.printf "\nPerforming internal failure test\n%!" ;
251322
test_internal_failure_error () ;
323+
Printf.printf "\nPerforming syslog tests\n%!" ;
324+
test_syslog true ;
325+
test_syslog false ;
326+
252327
let combinations = shuffle (all_combinations fds) in
253328
Printf.printf "Starting %d tests\n%!" (List.length combinations) ;
254329
let i = ref 0 in

ocaml/forkexecd/test/fe_test.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export FE_TEST=1
88
SOCKET=${XDG_RUNTIME_DIR}/xapi/forker/main
99
rm -f "$SOCKET"
1010

11-
../src/fe_main.exe &
11+
LD_PRELOAD="$PWD/syslog.so" ../src/fe_main.exe &
1212
MAIN=$!
1313
cleanup () {
1414
kill $MAIN
@@ -17,4 +17,4 @@ trap cleanup EXIT INT
1717
for _ in $(seq 1 10); do
1818
test -S ${SOCKET} || sleep 1
1919
done
20-
echo "" | ./fe_test.exe 16
20+
echo "" | LD_PRELOAD="$PWD/syslog.so" ./fe_test.exe 16

ocaml/forkexecd/test/syslog.c

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#define _GNU_SOURCE
2+
#define _DEFAULT_SOURCE
3+
#include <stdarg.h>
4+
#include <stdio.h>
5+
#include <stdlib.h>
6+
#include <stdbool.h>
7+
#include <time.h>
8+
#include <alloca.h>
9+
#include <unistd.h>
10+
#include <dlfcn.h>
11+
#include <syslog.h>
12+
#include <sys/types.h>
13+
#include <sys/socket.h>
14+
#include <sys/un.h>
15+
16+
#define START(name) \
17+
static typeof(name) *old_func = NULL; \
18+
if (!old_func) \
19+
old_func = (typeof(name) *) dlsym(RTLD_NEXT, #name);
20+
21+
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
22+
{
23+
static const char dev_log[] = "/dev/log";
24+
START(connect);
25+
26+
struct sockaddr_un *un = (struct sockaddr_un *) addr;
27+
if (!addr || addr->sa_family != AF_UNIX
28+
|| memcmp(un->sun_path, dev_log, sizeof(dev_log)) != 0)
29+
return old_func(sockfd, addr, addrlen);
30+
31+
struct sockaddr_un new_addr;
32+
new_addr.sun_family = AF_UNIX;
33+
strcpy(new_addr.sun_path, "/tmp/xyz");
34+
return old_func(sockfd, (struct sockaddr *) &new_addr, sizeof(new_addr));
35+
}
36+
37+
static const char *month_name(int month)
38+
{
39+
static const char names[12][4] = {
40+
"Jan",
41+
"Feb",
42+
"Mar",
43+
"Apr",
44+
"May",
45+
"Jun",
46+
"Jul",
47+
"Aug",
48+
"Sep",
49+
"Oct",
50+
"Nov",
51+
"Dec",
52+
};
53+
if (month >= 0 && month < 12)
54+
return names[month];
55+
56+
return "Xxx";
57+
}
58+
59+
static void vsyslog_internal(int priority, const char *format, va_list ap)
60+
{
61+
// format is "<13>Jul 9 07:19:01 hostname: message"
62+
time_t now = time(NULL);
63+
struct tm tm, *p;
64+
p = gmtime_r(&now, &tm);
65+
66+
if (LOG_FAC(priority) == 0)
67+
priority |= LOG_USER;
68+
69+
char buffer[1024];
70+
char *buf = buffer;
71+
const int prefix_len = sprintf(buffer, "<%d> %s % 2d %02d:%02d:%02d %s: ", priority, month_name(p->tm_mon),
72+
p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec, "dummy");
73+
74+
int left = (int) sizeof(buffer) - prefix_len;
75+
int l = vsnprintf(buffer + prefix_len, left, format, ap);
76+
if (l >= left) {
77+
buf = malloc(prefix_len + l + 1);
78+
if (!buf)
79+
return;
80+
memcpy(buf, buffer, prefix_len);
81+
l = vsnprintf(buf + prefix_len, l + 1, format, ap);
82+
}
83+
84+
int sock = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
85+
if (sock >= 0) {
86+
struct sockaddr_un addr;
87+
addr.sun_family = AF_UNIX;
88+
strcpy(addr.sun_path, "/tmp/xyz");
89+
sendto(sock, buf, prefix_len + l, MSG_NOSIGNAL, &addr, sizeof(addr));
90+
91+
close(sock);
92+
}
93+
if (buf != buffer)
94+
free(buf);
95+
}
96+
97+
void syslog(int priority, const char *format, ...)
98+
{
99+
va_list ap;
100+
va_start(ap, format);
101+
vsyslog_internal(priority, format, ap);
102+
va_end(ap);
103+
}
104+
105+
void vsyslog(int priority, const char *format, va_list ap)
106+
{
107+
vsyslog_internal(priority, format, ap);
108+
}
109+
110+
void __syslog_chk(int priority, int flags, const char *format, ...)
111+
{
112+
va_list ap;
113+
va_start(ap, format);
114+
vsyslog_internal(priority, format, ap);
115+
va_end(ap);
116+
}
117+
118+
void __vsyslog_chk(int priority, int flags, const char *format, va_list ap)
119+
{
120+
vsyslog_internal(priority, format, ap);
121+
}

0 commit comments

Comments
 (0)