/* A server based on unix domain socket Copyright (C) Amitay Isaacs 2016 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #include "replace.h" #include "system/filesys.h" #include "system/network.h" #include "system/wait.h" #include #include #include "lib/async_req/async_sock.h" #include "lib/util/debug.h" #include "lib/util/blocking.h" #include "lib/util/dlinklist.h" #include "lib/util/tevent_unix.h" #include "lib/util/become_daemon.h" #include "lib/util/sys_rw.h" #include "common/logging.h" #include "common/reqid.h" #include "common/comm.h" #include "common/pidfile.h" #include "common/system.h" #include "common/sock_daemon.h" struct sock_socket { struct sock_socket *prev, *next; const char *sockpath; struct sock_socket_funcs *funcs; void *private_data; int fd; struct tevent_req *req; }; struct sock_client { struct sock_client *prev, *next; struct tevent_req *req; struct sock_client_context *client_ctx; }; struct sock_client_context { struct tevent_context *ev; struct sock_socket *sock; int fd; struct comm_context *comm; struct sock_client *client; }; struct sock_daemon_context { struct sock_daemon_funcs *funcs; void *private_data; struct pidfile_context *pid_ctx; struct sock_socket *socket_list; int startup_fd; }; /* * Process a single client */ static void sock_client_read_handler(uint8_t *buf, size_t buflen, void *private_data); static void sock_client_read_done(struct tevent_req *subreq); static void sock_client_dead_handler(void *private_data); static int sock_client_context_destructor( struct sock_client_context *client_ctx); static int sock_client_context_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sock_socket *sock, int client_fd, struct sock_client *client, struct sock_client_context **result) { struct sock_client_context *client_ctx; int ret; client_ctx = talloc_zero(mem_ctx, struct sock_client_context); if (client_ctx == NULL) { return ENOMEM; } client_ctx->ev = ev; client_ctx->sock = sock; client_ctx->fd = client_fd; client_ctx->client = client; ret = comm_setup(client_ctx, ev, client_fd, sock_client_read_handler, client_ctx, sock_client_dead_handler, client_ctx, &client_ctx->comm); if (ret != 0) { talloc_free(client_ctx); return ret; } if (sock->funcs->connect != NULL) { pid_t pid; bool status; (void) ctdb_get_peer_pid(client_fd, &pid); status = sock->funcs->connect(client_ctx, pid, sock->private_data); if (! status) { talloc_free(client_ctx); close(client_fd); return 0; } } talloc_set_destructor(client_ctx, sock_client_context_destructor); *result = client_ctx; return 0; } static void sock_client_read_handler(uint8_t *buf, size_t buflen, void *private_data) { struct sock_client_context *client_ctx = talloc_get_type_abort( private_data, struct sock_client_context); struct sock_socket *sock = client_ctx->sock; struct tevent_req *subreq; subreq = sock->funcs->read_send(client_ctx, client_ctx->ev, client_ctx, buf, buflen, sock->private_data); if (subreq == NULL) { talloc_free(client_ctx); return; } tevent_req_set_callback(subreq, sock_client_read_done, client_ctx); } static void sock_client_read_done(struct tevent_req *subreq) { struct sock_client_context *client_ctx = tevent_req_callback_data( subreq, struct sock_client_context); struct sock_socket *sock = client_ctx->sock; int ret; bool status; status = sock->funcs->read_recv(subreq, &ret); if (! status) { D_ERR("client read failed with ret=%d\n", ret); talloc_free(client_ctx); } } static void sock_client_dead_handler(void *private_data) { struct sock_client_context *client_ctx = talloc_get_type_abort( private_data, struct sock_client_context); struct sock_socket *sock = client_ctx->sock; if (sock->funcs->disconnect != NULL) { sock->funcs->disconnect(client_ctx, sock->private_data); } talloc_free(client_ctx); } static int sock_client_context_destructor( struct sock_client_context *client_ctx) { TALLOC_FREE(client_ctx->client); TALLOC_FREE(client_ctx->comm); if (client_ctx->fd != -1) { close(client_ctx->fd); client_ctx->fd = -1; } return 0; } /* * Process a single listening socket */ static int socket_setup(const char *sockpath, bool remove_before_use) { struct sockaddr_un addr; size_t len; int ret, fd; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); if (len >= sizeof(addr.sun_path)) { D_ERR("socket path too long: %s\n", sockpath); return -1; } fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == -1) { D_ERR("socket create failed - %s\n", sockpath); return -1; } ret = set_blocking(fd, false); if (ret != 0) { D_ERR("socket set nonblocking failed - %s\n", sockpath); close(fd); return -1; } if (remove_before_use) { unlink(sockpath); } ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); if (ret != 0) { D_ERR("socket bind failed - %s\n", sockpath); close(fd); return -1; } ret = listen(fd, 10); if (ret != 0) { D_ERR("socket listen failed - %s\n", sockpath); close(fd); return -1; } D_NOTICE("listening on %s\n", sockpath); return fd; } static int sock_socket_destructor(struct sock_socket *sock); static int sock_socket_init(TALLOC_CTX *mem_ctx, const char *sockpath, struct sock_socket_funcs *funcs, void *private_data, struct sock_socket **result) { struct sock_socket *sock; if (funcs == NULL) { return EINVAL; } if (funcs->read_send == NULL || funcs->read_recv == NULL) { return EINVAL; } sock = talloc_zero(mem_ctx, struct sock_socket); if (sock == NULL) { return ENOMEM; } sock->sockpath = talloc_strdup(sock, sockpath); if (sock->sockpath == NULL) { talloc_free(sock); return ENOMEM; } sock->funcs = funcs; sock->private_data = private_data; sock->fd = -1; talloc_set_destructor(sock, sock_socket_destructor); *result = sock; return 0; } static int sock_socket_destructor(struct sock_socket *sock) { TALLOC_FREE(sock->req); if (sock->fd != -1) { close(sock->fd); sock->fd = -1; } unlink(sock->sockpath); return 0; } struct sock_socket_start_state { struct tevent_context *ev; struct sock_socket *sock; struct sock_client *client_list; }; static int sock_socket_start_state_destructor( struct sock_socket_start_state *state); static void sock_socket_start_new_client(struct tevent_req *subreq); static int sock_socket_start_client_destructor(struct sock_client *client); static struct tevent_req *sock_socket_start_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sock_socket *sock, bool remove_before_use) { struct tevent_req *req, *subreq; struct sock_socket_start_state *state; req = tevent_req_create(mem_ctx, &state, struct sock_socket_start_state); if (req == NULL) { return NULL; } state->ev = ev; state->sock = sock; sock->fd = socket_setup(sock->sockpath, remove_before_use); if (sock->fd == -1) { tevent_req_error(req, EIO); return tevent_req_post(req, ev); } talloc_set_destructor(state, sock_socket_start_state_destructor); subreq = accept_send(state, ev, sock->fd); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, sock_socket_start_new_client, req); sock->req = req; return req; } static int sock_socket_start_state_destructor( struct sock_socket_start_state *state) { struct sock_client *client; while ((client = state->client_list) != NULL) { talloc_free(client); } return 0; } static void sock_socket_start_new_client(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct sock_socket_start_state *state = tevent_req_data( req, struct sock_socket_start_state); struct sock_client *client; int client_fd, ret; client_fd = accept_recv(subreq, NULL, NULL, &ret); TALLOC_FREE(subreq); if (client_fd == -1) { D_ERR("failed to accept new connection\n"); } subreq = accept_send(state, state->ev, state->sock->fd); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, sock_socket_start_new_client, req); if (client_fd == -1) { return; } client = talloc_zero(state, struct sock_client); if (tevent_req_nomem(client, req)) { close(client_fd); return; } client->req = req; ret = sock_client_context_init(client, state->ev, state->sock, client_fd, client, &client->client_ctx); if (ret != 0) { talloc_free(client); return; } talloc_set_destructor(client, sock_socket_start_client_destructor); DLIST_ADD(state->client_list, client); } static int sock_socket_start_client_destructor(struct sock_client *client) { struct sock_socket_start_state *state = tevent_req_data( client->req, struct sock_socket_start_state); DLIST_REMOVE(state->client_list, client); TALLOC_FREE(client->client_ctx); return 0; } static bool sock_socket_start_recv(struct tevent_req *req, int *perr, TALLOC_CTX *mem_ctx, const char **sockpath) { struct sock_socket_start_state *state = tevent_req_data( req, struct sock_socket_start_state); int ret; state->sock->req = NULL; if (tevent_req_is_unix_error(req, &ret)) { if (perr != NULL) { *perr = ret; } return false; } if (sockpath != NULL) { *sockpath = talloc_steal(mem_ctx, state->sock->sockpath); } return true; } /* * Send message to a client */ struct tevent_req *sock_socket_write_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sock_client_context *client_ctx, uint8_t *buf, size_t buflen) { struct tevent_req *req; req = comm_write_send(mem_ctx, ev, client_ctx->comm, buf, buflen); return req; } bool sock_socket_write_recv(struct tevent_req *req, int *perr) { int ret; bool status; status = comm_write_recv(req, &ret); if (! status) { if (perr != NULL) { *perr = ret; } } return status; } /* * Socket daemon */ int sock_daemon_setup(TALLOC_CTX *mem_ctx, const char *daemon_name, const char *logging, const char *debug_level, struct sock_daemon_funcs *funcs, void *private_data, struct sock_daemon_context **out) { struct sock_daemon_context *sockd; int ret; sockd = talloc_zero(mem_ctx, struct sock_daemon_context); if (sockd == NULL) { return ENOMEM; } sockd->funcs = funcs; sockd->private_data = private_data; sockd->startup_fd = -1; ret = logging_init(sockd, logging, debug_level, daemon_name); if (ret != 0) { fprintf(stderr, "Failed to initialize logging, logging=%s, debug=%s\n", logging, debug_level); return ret; } *out = sockd; return 0; } int sock_daemon_add_unix(struct sock_daemon_context *sockd, const char *sockpath, struct sock_socket_funcs *funcs, void *private_data) { struct sock_socket *sock; int ret; ret = sock_socket_init(sockd, sockpath, funcs, private_data, &sock); if (ret != 0) { return ret; } DLIST_ADD(sockd->socket_list, sock); return 0; } void sock_daemon_set_startup_fd(struct sock_daemon_context *sockd, int fd) { sockd->startup_fd = fd; } /* * Run socket daemon */ struct sock_daemon_run_state { struct tevent_context *ev; struct sock_daemon_context *sockd; pid_t pid_watch; int fd; int exit_code; }; static void sock_daemon_run_started(struct tevent_req *subreq); static void sock_daemon_run_startup_done(struct tevent_req *subreq); static void sock_daemon_run_signal_handler(struct tevent_context *ev, struct tevent_signal *se, int signum, int count, void *siginfo, void *private_data); static void sock_daemon_run_reconfigure(struct tevent_req *req); static void sock_daemon_run_reconfigure_done(struct tevent_req *subreq); static void sock_daemon_run_shutdown(struct tevent_req *req); static void sock_daemon_run_shutdown_done(struct tevent_req *subreq); static void sock_daemon_run_exit(struct tevent_req *req); static bool sock_daemon_run_socket_listen(struct tevent_req *req); static void sock_daemon_run_socket_fail(struct tevent_req *subreq); static void sock_daemon_run_watch_pid(struct tevent_req *subreq); static void sock_daemon_run_wait(struct tevent_req *req); static void sock_daemon_run_wait_done(struct tevent_req *subreq); static void sock_daemon_startup_notify(struct sock_daemon_context *sockd); struct tevent_req *sock_daemon_run_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sock_daemon_context *sockd, const char *pidfile, bool do_fork, bool create_session, pid_t pid_watch) { struct tevent_req *req, *subreq; struct sock_daemon_run_state *state; struct tevent_signal *se; req = tevent_req_create(mem_ctx, &state, struct sock_daemon_run_state); if (req == NULL) { return NULL; } become_daemon(do_fork, !create_session, false); if (pidfile != NULL) { int ret = pidfile_context_create(sockd, pidfile, &sockd->pid_ctx); if (ret != 0) { tevent_req_error(req, EEXIST); return tevent_req_post(req, ev); } } state->ev = ev; state->sockd = sockd; state->pid_watch = pid_watch; state->fd = -1; subreq = tevent_wakeup_send(state, ev, tevent_timeval_current_ofs(0, 0)); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, sock_daemon_run_started, req); se = tevent_add_signal(ev, state, SIGHUP, 0, sock_daemon_run_signal_handler, req); if (tevent_req_nomem(se, req)) { return tevent_req_post(req, ev); } se = tevent_add_signal(ev, state, SIGUSR1, 0, sock_daemon_run_signal_handler, req); if (tevent_req_nomem(se, req)) { return tevent_req_post(req, ev); } se = tevent_add_signal(ev, state, SIGINT, 0, sock_daemon_run_signal_handler, req); if (tevent_req_nomem(se, req)) { return tevent_req_post(req, ev); } se = tevent_add_signal(ev, state, SIGTERM, 0, sock_daemon_run_signal_handler, req); if (tevent_req_nomem(se, req)) { return tevent_req_post(req, ev); } if (pid_watch > 1) { subreq = tevent_wakeup_send(state, ev, tevent_timeval_current_ofs(1,0)); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, sock_daemon_run_watch_pid, req); } return req; } static void sock_daemon_run_started(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct sock_daemon_run_state *state = tevent_req_data( req, struct sock_daemon_run_state); struct sock_daemon_context *sockd = state->sockd; bool status; status = tevent_wakeup_recv(subreq); TALLOC_FREE(subreq); if (! status) { tevent_req_error(req, EIO); return; } D_NOTICE("daemon started, pid=%u\n", getpid()); if (sockd->funcs != NULL && sockd->funcs->startup_send != NULL && sockd->funcs->startup_recv != NULL) { subreq = sockd->funcs->startup_send(state, state->ev, sockd->private_data); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, sock_daemon_run_startup_done, req); return; } if (sockd->funcs != NULL && sockd->funcs->startup != NULL) { int ret; ret = sockd->funcs->startup(sockd->private_data); if (ret != 0) { D_ERR("startup failed, ret=%d\n", ret); tevent_req_error(req, EIO); return; } D_NOTICE("startup completed successfully\n"); } status = sock_daemon_run_socket_listen(req); if (! status) { return; } sock_daemon_run_wait(req); sock_daemon_startup_notify(sockd); } static void sock_daemon_run_startup_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct sock_daemon_run_state *state = tevent_req_data( req, struct sock_daemon_run_state); struct sock_daemon_context *sockd = state->sockd; int ret; bool status; status = sockd->funcs->startup_recv(subreq, &ret); TALLOC_FREE(subreq); if (! status) { D_ERR("startup failed, ret=%d\n", ret); tevent_req_error(req, EIO); return; } D_NOTICE("startup completed successfully\n"); status = sock_daemon_run_socket_listen(req); if (! status) { return; } sock_daemon_run_wait(req); sock_daemon_startup_notify(sockd); } static void sock_daemon_run_signal_handler(struct tevent_context *ev, struct tevent_signal *se, int signum, int count, void *siginfo, void *private_data) { struct tevent_req *req = talloc_get_type_abort( private_data, struct tevent_req); struct sock_daemon_run_state *state = tevent_req_data( req, struct sock_daemon_run_state); D_NOTICE("Received signal %d\n", signum); if (signum == SIGHUP || signum == SIGUSR1) { sock_daemon_run_reconfigure(req); return; } if (signum == SIGINT || signum == SIGTERM) { state->exit_code = EINTR; sock_daemon_run_shutdown(req); } } static void sock_daemon_run_reconfigure(struct tevent_req *req) { struct tevent_req *subreq; struct sock_daemon_run_state *state = tevent_req_data( req, struct sock_daemon_run_state); struct sock_daemon_context *sockd = state->sockd; if (sockd->funcs != NULL && sockd->funcs->reconfigure_send != NULL && sockd->funcs->reconfigure_recv != NULL) { subreq = sockd->funcs->reconfigure_send(state, state->ev, sockd->private_data); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, sock_daemon_run_reconfigure_done, req); return; } if (sockd->funcs != NULL && sockd->funcs->reconfigure != NULL) { int ret; ret = sockd->funcs->reconfigure(sockd->private_data); if (ret != 0) { D_ERR("reconfigure failed, ret=%d\n", ret); return; } D_NOTICE("reconfigure completed successfully\n"); } } static void sock_daemon_run_reconfigure_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct sock_daemon_run_state *state = tevent_req_data( req, struct sock_daemon_run_state); struct sock_daemon_context *sockd = state->sockd; int ret; bool status; status = sockd->funcs->reconfigure_recv(subreq, &ret); TALLOC_FREE(subreq); if (! status) { D_ERR("reconfigure failed, ret=%d\n", ret); return; } D_NOTICE("reconfigure completed successfully\n"); } static void sock_daemon_run_shutdown(struct tevent_req *req) { struct tevent_req *subreq; struct sock_daemon_run_state *state = tevent_req_data( req, struct sock_daemon_run_state); struct sock_daemon_context *sockd = state->sockd; struct sock_socket *sock; D_NOTICE("Shutting down\n"); while ((sock = sockd->socket_list) != NULL) { DLIST_REMOVE(sockd->socket_list, sock); TALLOC_FREE(sock); } if (sockd->funcs != NULL && sockd->funcs->shutdown_send != NULL && sockd->funcs->shutdown_recv != NULL) { subreq = sockd->funcs->shutdown_send(state, state->ev, sockd->private_data); if (subreq == NULL) { sock_daemon_run_exit(req); return; } tevent_req_set_callback(subreq, sock_daemon_run_shutdown_done, req); return; } if (sockd->funcs != NULL && sockd->funcs->shutdown != NULL) { sockd->funcs->shutdown(sockd->private_data); } sock_daemon_run_exit(req); } static void sock_daemon_run_shutdown_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct sock_daemon_run_state *state = tevent_req_data( req, struct sock_daemon_run_state); struct sock_daemon_context *sockd = state->sockd; sockd->funcs->shutdown_recv(subreq); TALLOC_FREE(subreq); sock_daemon_run_exit(req); } static void sock_daemon_run_exit(struct tevent_req *req) { struct sock_daemon_run_state *state = tevent_req_data( req, struct sock_daemon_run_state); struct sock_daemon_context *sockd = state->sockd; TALLOC_FREE(sockd->pid_ctx); if (state->exit_code == 0) { tevent_req_done(req); } else { tevent_req_error(req, state->exit_code); } } static bool sock_daemon_run_socket_listen(struct tevent_req *req) { struct tevent_req *subreq; struct sock_daemon_run_state *state = tevent_req_data( req, struct sock_daemon_run_state); struct sock_daemon_context *sockd = state->sockd; struct sock_socket *sock; bool remove_before_use = false; if (sockd->pid_ctx != NULL) { remove_before_use = true; } for (sock = sockd->socket_list; sock != NULL; sock = sock->next) { subreq = sock_socket_start_send(state, state->ev, sock, remove_before_use); if (tevent_req_nomem(subreq, req)) { return false; } tevent_req_set_callback(subreq, sock_daemon_run_socket_fail, req); } return true; } static void sock_daemon_run_socket_fail(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct sock_daemon_run_state *state = tevent_req_data( req, struct sock_daemon_run_state); const char *sockpath = NULL; int ret = 0; bool status; status = sock_socket_start_recv(subreq, &ret, state, &sockpath); TALLOC_FREE(subreq); if (! status) { D_ERR("socket %s closed unexpectedly\n", sockpath); state->exit_code = ret; } else { state->exit_code = 0; } sock_daemon_run_shutdown(req); } static void sock_daemon_run_watch_pid(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct sock_daemon_run_state *state = tevent_req_data( req, struct sock_daemon_run_state); int ret; bool status; status = tevent_wakeup_recv(subreq); TALLOC_FREE(subreq); if (! status) { tevent_req_error(req, EIO); return; } ret = kill(state->pid_watch, 0); if (ret == -1) { if (errno == ESRCH) { D_ERR("PID %d gone away, exiting\n", state->pid_watch); state->exit_code = ESRCH; sock_daemon_run_shutdown(req); return; } else { D_ERR("Failed to check PID status %d, ret=%d\n", state->pid_watch, errno); } } subreq = tevent_wakeup_send(state, state->ev, tevent_timeval_current_ofs(5,0)); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, sock_daemon_run_watch_pid, req); } static void sock_daemon_run_wait(struct tevent_req *req) { struct tevent_req *subreq; struct sock_daemon_run_state *state = tevent_req_data( req, struct sock_daemon_run_state); struct sock_daemon_context *sockd = state->sockd; if (sockd->funcs != NULL && sockd->funcs->wait_send != NULL && sockd->funcs->wait_recv != NULL) { subreq = sockd->funcs->wait_send(state, state->ev, sockd->private_data); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, sock_daemon_run_wait_done, req); } } static void sock_daemon_run_wait_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct sock_daemon_run_state *state = tevent_req_data( req, struct sock_daemon_run_state); struct sock_daemon_context *sockd = state->sockd; int ret = 0; bool status; status = sockd->funcs->wait_recv(subreq, &ret); TALLOC_FREE(subreq); if (! status) { state->exit_code = ret; } else { state->exit_code = 0; } sock_daemon_run_shutdown(req); } static void sock_daemon_startup_notify(struct sock_daemon_context *sockd) { if (sockd->startup_fd != -1) { unsigned int zero = 0; ssize_t num; num = sys_write(sockd->startup_fd, &zero, sizeof(zero)); if (num != sizeof(zero)) { D_WARNING("Failed to write zero to pipe FD\n"); } } } bool sock_daemon_run_recv(struct tevent_req *req, int *perr) { int ret; if (tevent_req_is_unix_error(req, &ret)) { if (perr != NULL) { *perr = ret; } return false; } return true; } int sock_daemon_run(struct tevent_context *ev, struct sock_daemon_context *sockd, const char *pidfile, bool do_fork, bool create_session, pid_t pid_watch) { struct tevent_req *req; int ret; bool status; req = sock_daemon_run_send(ev, ev, sockd, pidfile, do_fork, create_session, pid_watch); if (req == NULL) { return ENOMEM; } tevent_req_poll(req, ev); status = sock_daemon_run_recv(req, &ret); TALLOC_FREE(req); if (! status) { return ret; } return 0; }