tests: verify failed startup publishes no endpoints
This commit is contained in:
635
tests/daemon_failure_test.c
Normal file
635
tests/daemon_failure_test.c
Normal file
@@ -0,0 +1,635 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
enum
|
||||
{
|
||||
Failtimeout = 8000,
|
||||
};
|
||||
|
||||
typedef struct Test Test;
|
||||
|
||||
struct Test
|
||||
{
|
||||
pid_t child;
|
||||
int errfd;
|
||||
int notifyfd;
|
||||
int runtimewd;
|
||||
int buswd;
|
||||
int endpointclean;
|
||||
char root[256];
|
||||
char runtime[320];
|
||||
char config[320];
|
||||
char ibus[384];
|
||||
char bus[448];
|
||||
char home[320];
|
||||
char socket[384];
|
||||
char badmap[320];
|
||||
char missingfont[384];
|
||||
char expected[768];
|
||||
char err[4096];
|
||||
size_t nerr;
|
||||
};
|
||||
|
||||
static uint32_t endpointmask = IN_CREATE|IN_MOVED_TO|IN_MOVED_FROM|IN_ATTRIB|
|
||||
IN_DELETE|IN_MODIFY|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF;
|
||||
|
||||
static int
|
||||
fail(char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
fprintf(stderr, "daemon_failure_test: ");
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int64_t
|
||||
nowms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
||||
return -1;
|
||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
static int
|
||||
leftms(int64_t deadline)
|
||||
{
|
||||
int64_t n;
|
||||
|
||||
n = nowms();
|
||||
if(n < 0)
|
||||
return -1;
|
||||
n = deadline - n;
|
||||
if(n <= 0)
|
||||
return 0;
|
||||
if(n > 0x7fffffff)
|
||||
return 0x7fffffff;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int
|
||||
makedir(char *path)
|
||||
{
|
||||
if(mkdir(path, 0700) == 0)
|
||||
return 1;
|
||||
return fail("mkdir %s: %s", path, strerror(errno));
|
||||
}
|
||||
|
||||
static void
|
||||
readerrors(Test *t)
|
||||
{
|
||||
ssize_t n;
|
||||
|
||||
if(t->errfd < 0)
|
||||
return;
|
||||
while(t->nerr + 1 < sizeof t->err){
|
||||
n = read(t->errfd, t->err + t->nerr,
|
||||
sizeof t->err - t->nerr - 1);
|
||||
if(n > 0){
|
||||
t->nerr += n;
|
||||
continue;
|
||||
}
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
t->err[t->nerr] = '\0';
|
||||
}
|
||||
|
||||
static void
|
||||
showerrors(Test *t)
|
||||
{
|
||||
readerrors(t);
|
||||
if(t->nerr != 0)
|
||||
fprintf(stderr, "daemon_failure_test: daemon stderr:\n%s", t->err);
|
||||
}
|
||||
|
||||
static int
|
||||
setup(Test *t)
|
||||
{
|
||||
memset(t, 0, sizeof *t);
|
||||
t->child = -1;
|
||||
t->errfd = -1;
|
||||
t->notifyfd = -1;
|
||||
t->runtimewd = -1;
|
||||
t->buswd = -1;
|
||||
t->endpointclean = 1;
|
||||
if(nowms() < 0)
|
||||
return fail("read monotonic clock: %s", strerror(errno));
|
||||
snprintf(t->root, sizeof t->root, "/tmp/strans-failure.XXXXXX");
|
||||
if(mkdtemp(t->root) == NULL){
|
||||
t->root[0] = '\0';
|
||||
return fail("mkdtemp: %s", strerror(errno));
|
||||
}
|
||||
if(snprintf(t->runtime, sizeof t->runtime, "%s/runtime", t->root)
|
||||
>= (int)sizeof t->runtime ||
|
||||
snprintf(t->config, sizeof t->config, "%s/config", t->root)
|
||||
>= (int)sizeof t->config ||
|
||||
snprintf(t->ibus, sizeof t->ibus, "%s/ibus", t->config)
|
||||
>= (int)sizeof t->ibus ||
|
||||
snprintf(t->bus, sizeof t->bus, "%s/bus", t->ibus)
|
||||
>= (int)sizeof t->bus ||
|
||||
snprintf(t->home, sizeof t->home, "%s/home", t->root)
|
||||
>= (int)sizeof t->home ||
|
||||
snprintf(t->socket, sizeof t->socket, "%s/strans.sock", t->runtime)
|
||||
>= (int)sizeof t->socket ||
|
||||
snprintf(t->badmap, sizeof t->badmap, "%s/missing-map", t->root)
|
||||
>= (int)sizeof t->badmap ||
|
||||
snprintf(t->missingfont, sizeof t->missingfont,
|
||||
"%s/missing-font.ttf", t->root) >= (int)sizeof t->missingfont ||
|
||||
snprintf(t->expected, sizeof t->expected,
|
||||
"strans: can't open: %s/hira.map\n", t->badmap)
|
||||
>= (int)sizeof t->expected)
|
||||
return fail("temporary path is too long");
|
||||
if(!makedir(t->runtime) || !makedir(t->config) || !makedir(t->ibus) ||
|
||||
!makedir(t->bus) || !makedir(t->home))
|
||||
return 0;
|
||||
t->notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
|
||||
if(t->notifyfd < 0)
|
||||
return fail("inotify_init1: %s", strerror(errno));
|
||||
t->runtimewd = inotify_add_watch(t->notifyfd, t->runtime, endpointmask);
|
||||
if(t->runtimewd < 0)
|
||||
return fail("watch runtime directory: %s", strerror(errno));
|
||||
t->buswd = inotify_add_watch(t->notifyfd, t->bus, endpointmask);
|
||||
if(t->buswd < 0)
|
||||
return fail("watch IBus directory: %s", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
startchild(Test *t, char *program)
|
||||
{
|
||||
int errpipe[2], fd;
|
||||
long maxfd;
|
||||
pid_t pid;
|
||||
|
||||
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0)
|
||||
return fail("pipe2: %s", strerror(errno));
|
||||
pid = fork();
|
||||
if(pid < 0){
|
||||
close(errpipe[0]);
|
||||
close(errpipe[1]);
|
||||
return fail("fork: %s", strerror(errno));
|
||||
}
|
||||
if(pid == 0){
|
||||
close(errpipe[0]);
|
||||
if(dup2(errpipe[1], STDERR_FILENO) < 0)
|
||||
_exit(126);
|
||||
close(errpipe[1]);
|
||||
if(close_range(3, UINT_MAX, 0) < 0){
|
||||
maxfd = sysconf(_SC_OPEN_MAX);
|
||||
if(maxfd < 0)
|
||||
maxfd = 1024;
|
||||
for(fd = 3; fd < maxfd; fd++)
|
||||
close(fd);
|
||||
}
|
||||
if(setenv("XDG_RUNTIME_DIR", t->runtime, 1) < 0 ||
|
||||
setenv("XDG_CONFIG_HOME", t->config, 1) < 0 ||
|
||||
setenv("HOME", t->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
||||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
||||
unsetenv("IBUS_ADDRESS") < 0){
|
||||
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
|
||||
strerror(errno));
|
||||
_exit(126);
|
||||
}
|
||||
execl(program, program, t->badmap, t->missingfont, (char*)0);
|
||||
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
||||
_exit(127);
|
||||
}
|
||||
close(errpipe[1]);
|
||||
t->child = pid;
|
||||
t->errfd = errpipe[0];
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
drainnotify(Test *t)
|
||||
{
|
||||
char buf[4096];
|
||||
char *where;
|
||||
struct inotify_event *ev;
|
||||
ssize_t n;
|
||||
size_t off;
|
||||
int ok;
|
||||
|
||||
ok = 1;
|
||||
for(;;){
|
||||
n = read(t->notifyfd, buf, sizeof buf);
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(n < 0 && errno == EAGAIN)
|
||||
return ok;
|
||||
if(n < 0)
|
||||
return fail("read endpoint watches: %s", strerror(errno));
|
||||
if(n == 0)
|
||||
return ok;
|
||||
for(off = 0; off + sizeof *ev <= (size_t)n;
|
||||
off += sizeof *ev + ev->len){
|
||||
ev = (struct inotify_event*)(buf + off);
|
||||
if(off + sizeof *ev + ev->len > (size_t)n)
|
||||
return fail("truncated inotify event");
|
||||
if(ev->mask & IN_Q_OVERFLOW){
|
||||
t->endpointclean = 0;
|
||||
fail("endpoint watch queue overflowed");
|
||||
ok = 0;
|
||||
continue;
|
||||
}
|
||||
if(ev->mask & (IN_IGNORED|IN_UNMOUNT)){
|
||||
t->endpointclean = 0;
|
||||
fail("endpoint watch became invalid: %#x", ev->mask);
|
||||
ok = 0;
|
||||
continue;
|
||||
}
|
||||
if(ev->wd != t->runtimewd && ev->wd != t->buswd)
|
||||
continue;
|
||||
if(!(ev->mask & endpointmask))
|
||||
continue;
|
||||
where = ev->wd == t->runtimewd ? "runtime" : "IBus";
|
||||
t->endpointclean = 0;
|
||||
fail("unexpected %s endpoint event %#x for %s", where,
|
||||
ev->mask, ev->len != 0 ? ev->name : "directory");
|
||||
ok = 0;
|
||||
}
|
||||
if(off != (size_t)n)
|
||||
return fail("truncated inotify event buffer");
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
killowned(Test *t, int *status)
|
||||
{
|
||||
pid_t pid;
|
||||
int n, ok;
|
||||
|
||||
if(t->child <= 0)
|
||||
return 1;
|
||||
pid = t->child;
|
||||
ok = 1;
|
||||
if(kill(pid, SIGKILL) < 0 && errno != ESRCH){
|
||||
fail("kill -9 %ld: %s", (long)pid, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
do
|
||||
n = waitpid(pid, status, 0);
|
||||
while(n < 0 && errno == EINTR);
|
||||
if(n != pid){
|
||||
fail("reap %ld after SIGKILL: %s", (long)pid,
|
||||
n < 0 ? strerror(errno) : "wrong child");
|
||||
ok = 0;
|
||||
}else
|
||||
t->child = -1;
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
waitfailed(Test *t)
|
||||
{
|
||||
struct pollfd pfd[2];
|
||||
pid_t pid;
|
||||
int n, ok, status, timeout;
|
||||
int64_t deadline;
|
||||
|
||||
pid = t->child;
|
||||
ok = 1;
|
||||
deadline = nowms();
|
||||
if(deadline < 0){
|
||||
fail("read monotonic clock before waiting for daemon: %s",
|
||||
strerror(errno));
|
||||
killowned(t, &status);
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
return 0;
|
||||
}
|
||||
deadline += Failtimeout;
|
||||
for(;;){
|
||||
do
|
||||
n = waitpid(pid, &status, WNOHANG);
|
||||
while(n < 0 && errno == EINTR);
|
||||
if(n == pid){
|
||||
t->child = -1;
|
||||
readerrors(t);
|
||||
if(!drainnotify(t))
|
||||
ok = 0;
|
||||
break;
|
||||
}
|
||||
if(n < 0){
|
||||
fail("waitpid failed daemon %ld: %s", (long)pid,
|
||||
strerror(errno));
|
||||
if(errno == ECHILD){
|
||||
t->child = -1;
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
}else{
|
||||
killowned(t, &status);
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
timeout = leftms(deadline);
|
||||
if(timeout < 0){
|
||||
fail("read monotonic clock while waiting for daemon: %s",
|
||||
strerror(errno));
|
||||
killowned(t, &status);
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
return 0;
|
||||
}
|
||||
if(timeout == 0){
|
||||
fail("daemon %ld did not exit after map initialization failure",
|
||||
(long)pid);
|
||||
killowned(t, &status);
|
||||
return 0;
|
||||
}
|
||||
pfd[0].fd = t->notifyfd;
|
||||
pfd[0].events = POLLIN;
|
||||
pfd[0].revents = 0;
|
||||
pfd[1].fd = t->errfd;
|
||||
pfd[1].events = POLLIN|POLLHUP;
|
||||
pfd[1].revents = 0;
|
||||
n = poll(pfd, 2, timeout);
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(n < 0){
|
||||
fail("poll failed daemon: %s", strerror(errno));
|
||||
killowned(t, &status);
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
return 0;
|
||||
}
|
||||
if(pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL)){
|
||||
fail("endpoint watch became unusable: %#x", pfd[0].revents);
|
||||
killowned(t, &status);
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
return 0;
|
||||
}
|
||||
if(pfd[1].revents & (POLLERR|POLLNVAL)){
|
||||
fail("daemon stderr pipe became unusable: %#x",
|
||||
pfd[1].revents);
|
||||
killowned(t, &status);
|
||||
readerrors(t);
|
||||
drainnotify(t);
|
||||
return 0;
|
||||
}
|
||||
if((pfd[0].revents & POLLIN) && !drainnotify(t))
|
||||
ok = 0;
|
||||
if(pfd[1].revents & (POLLIN|POLLHUP))
|
||||
readerrors(t);
|
||||
}
|
||||
if(!WIFEXITED(status) || WEXITSTATUS(status) != 1)
|
||||
return fail("daemon map failure had wait status %#x", status);
|
||||
if(strcmp(t->err, t->expected) != 0)
|
||||
return fail("daemon did not report the expected map initialization failure");
|
||||
return ok && t->endpointclean;
|
||||
}
|
||||
|
||||
static int
|
||||
dirempty(char *path, char *which)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent *de;
|
||||
int count, ok;
|
||||
|
||||
dir = opendir(path);
|
||||
if(dir == NULL)
|
||||
return fail("open %s directory %s: %s", which, path, strerror(errno));
|
||||
count = 0;
|
||||
ok = 1;
|
||||
errno = 0;
|
||||
while((de = readdir(dir)) != NULL){
|
||||
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
||||
continue;
|
||||
count++;
|
||||
fail("unexpected %s directory entry %s", which, de->d_name);
|
||||
ok = 0;
|
||||
errno = 0;
|
||||
}
|
||||
if(errno != 0){
|
||||
fail("read %s directory %s: %s", which, path, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(closedir(dir) < 0){
|
||||
fail("close %s directory %s: %s", which, path, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(count != 0)
|
||||
ok = 0;
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
checkendpoints(Test *t)
|
||||
{
|
||||
struct stat st;
|
||||
int ok;
|
||||
|
||||
ok = 1;
|
||||
errno = 0;
|
||||
if(lstat(t->socket, &st) == 0){
|
||||
fail("IPC endpoint exists after failed startup: %s", t->socket);
|
||||
ok = 0;
|
||||
}else if(errno != ENOENT){
|
||||
fail("lstat failed IPC endpoint: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(!dirempty(t->runtime, "runtime"))
|
||||
ok = 0;
|
||||
if(!dirempty(t->bus, "IBus"))
|
||||
ok = 0;
|
||||
return ok && t->endpointclean;
|
||||
}
|
||||
|
||||
static int clearfd(int);
|
||||
|
||||
static int
|
||||
removeentry(int dirfd, char *name)
|
||||
{
|
||||
struct stat st;
|
||||
int fd, ok;
|
||||
|
||||
if(fstatat(dirfd, name, &st, AT_SYMLINK_NOFOLLOW) < 0)
|
||||
return fail("stat cleanup entry %s: %s", name, strerror(errno));
|
||||
if(!S_ISDIR(st.st_mode)){
|
||||
if(unlinkat(dirfd, name, 0) == 0 || errno == ENOENT)
|
||||
return 1;
|
||||
return fail("remove cleanup entry %s: %s", name, strerror(errno));
|
||||
}
|
||||
fd = openat(dirfd, name, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
||||
if(fd < 0)
|
||||
return fail("open cleanup directory %s: %s", name, strerror(errno));
|
||||
ok = clearfd(fd);
|
||||
if(close(fd) < 0){
|
||||
fail("close cleanup directory %s: %s", name, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(unlinkat(dirfd, name, AT_REMOVEDIR) < 0 && errno != ENOENT){
|
||||
fail("remove cleanup directory %s: %s", name, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
clearfd(int fd)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent *de;
|
||||
int copy, ok;
|
||||
|
||||
copy = dup(fd);
|
||||
if(copy < 0)
|
||||
return fail("duplicate cleanup directory: %s", strerror(errno));
|
||||
dir = fdopendir(copy);
|
||||
if(dir == NULL){
|
||||
close(copy);
|
||||
return fail("open cleanup directory stream: %s", strerror(errno));
|
||||
}
|
||||
ok = 1;
|
||||
errno = 0;
|
||||
while((de = readdir(dir)) != NULL){
|
||||
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
||||
continue;
|
||||
if(!removeentry(fd, de->d_name))
|
||||
ok = 0;
|
||||
errno = 0;
|
||||
}
|
||||
if(errno != 0){
|
||||
fail("read cleanup directory: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(closedir(dir) < 0){
|
||||
fail("close cleanup directory stream: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
removeroot(Test *t)
|
||||
{
|
||||
int fd, ok;
|
||||
|
||||
if(t->root[0] == '\0')
|
||||
return 1;
|
||||
fd = open(t->root, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
|
||||
if(fd < 0)
|
||||
return errno == ENOENT || fail("open cleanup root %s: %s", t->root,
|
||||
strerror(errno));
|
||||
ok = clearfd(fd);
|
||||
if(close(fd) < 0){
|
||||
fail("close cleanup root %s: %s", t->root, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(rmdir(t->root) < 0 && errno != ENOENT){
|
||||
fail("rmdir %s: %s", t->root, strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
cleanup(Test *t)
|
||||
{
|
||||
int n, ok, status;
|
||||
|
||||
ok = 1;
|
||||
if(t->child > 0){
|
||||
do
|
||||
n = waitpid(t->child, &status, WNOHANG);
|
||||
while(n < 0 && errno == EINTR);
|
||||
if(n == t->child)
|
||||
t->child = -1;
|
||||
else if(n == 0){
|
||||
fail("failed-start daemon still running during cleanup");
|
||||
ok = 0;
|
||||
if(!killowned(t, &status))
|
||||
ok = 0;
|
||||
}else{
|
||||
fail("check failed-start daemon during cleanup: %s",
|
||||
strerror(errno));
|
||||
ok = 0;
|
||||
if(errno == ECHILD)
|
||||
t->child = -1;
|
||||
else if(!killowned(t, &status))
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
readerrors(t);
|
||||
if(t->errfd >= 0){
|
||||
if(close(t->errfd) < 0){
|
||||
fail("close daemon stderr: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
t->errfd = -1;
|
||||
}
|
||||
if(t->notifyfd >= 0){
|
||||
if(t->runtimewd >= 0 &&
|
||||
inotify_rm_watch(t->notifyfd, t->runtimewd) < 0 && errno != EINVAL){
|
||||
fail("remove runtime watch: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(t->buswd >= 0 &&
|
||||
inotify_rm_watch(t->notifyfd, t->buswd) < 0 && errno != EINVAL){
|
||||
fail("remove IBus watch: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
if(close(t->notifyfd) < 0){
|
||||
fail("close endpoint watches: %s", strerror(errno));
|
||||
ok = 0;
|
||||
}
|
||||
t->notifyfd = -1;
|
||||
}
|
||||
if(!removeroot(t))
|
||||
ok = 0;
|
||||
return ok;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
Test test;
|
||||
int ok;
|
||||
|
||||
if(argc != 2){
|
||||
fprintf(stderr, "usage: daemon_failure_test strans\n");
|
||||
return 2;
|
||||
}
|
||||
ok = setup(&test);
|
||||
if(ok)
|
||||
ok = startchild(&test, argv[1]);
|
||||
if(ok){
|
||||
ok = waitfailed(&test);
|
||||
if(test.child <= 0 && !checkendpoints(&test))
|
||||
ok = 0;
|
||||
}
|
||||
if(!cleanup(&test))
|
||||
ok = 0;
|
||||
if(!ok){
|
||||
showerrors(&test);
|
||||
return 1;
|
||||
}
|
||||
printf("failed daemon startup published no endpoints: ok\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user