mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-14 09:11:27 +00:00
572 lines
13 KiB
C
572 lines
13 KiB
C
/*
|
|
* stpgd_main.c
|
|
*/
|
|
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <getopt.h>
|
|
#include <malloc.h>
|
|
#include <inttypes.h>
|
|
#include <sys/wait.h>
|
|
#include <poll.h>
|
|
#include <sys/ioctl.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <pthread.h>
|
|
#include <unistd.h>
|
|
#include <syslog.h>
|
|
#include <signal.h>
|
|
#include <syslog.h>
|
|
|
|
#include "version.h"
|
|
#include "debug.h"
|
|
#include "scst_event.h"
|
|
|
|
char *app_name;
|
|
|
|
#define DEFAULT_TRANSITION_TIME 17
|
|
|
|
#if defined(DEBUG) || defined(TRACING)
|
|
|
|
#ifdef DEBUG
|
|
|
|
#define DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MINOR | TRACE_PID | \
|
|
TRACE_FUNCTION | TRACE_SPECIAL | TRACE_MGMT | TRACE_MGMT_DEBUG | \
|
|
TRACE_TIME)
|
|
|
|
#define TRACE_SN(args...) TRACE(TRACE_SCSI_SERIALIZING, args)
|
|
|
|
#else /* DEBUG */
|
|
|
|
# ifdef TRACING
|
|
#define DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MGMT | \
|
|
TRACE_TIME | TRACE_SPECIAL)
|
|
# else
|
|
#define DEFAULT_LOG_FLAGS 0
|
|
# endif
|
|
#endif /* DEBUG */
|
|
|
|
unsigned long trace_flag = DEFAULT_LOG_FLAGS;
|
|
#endif /* defined(DEBUG) || defined(TRACING) */
|
|
|
|
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
|
|
|
|
bool log_daemon = true;
|
|
int transition_timeout = DEFAULT_TRANSITION_TIME;
|
|
|
|
static struct option const long_options[] = {
|
|
{"path", required_argument, 0, 'p'},
|
|
{"timeout", required_argument, 0, 't'},
|
|
{"foreground", no_argument, 0, 'f'},
|
|
#if defined(DEBUG) || defined(TRACING)
|
|
{"debug", required_argument, 0, 'd'},
|
|
#endif
|
|
{"version", no_argument, 0, 'v'},
|
|
{"help", no_argument, 0, 'h'},
|
|
{0, 0, 0, 0},
|
|
};
|
|
|
|
int stpg_init_report_pipe[2];
|
|
char *stpg_path;
|
|
|
|
static void usage(int status)
|
|
{
|
|
if (status != 0)
|
|
fprintf(stderr, "Try '%s --help' for more information.\n", app_name);
|
|
else {
|
|
printf("Usage: %s [OPTIONS]\n", app_name);
|
|
printf("STPG target daemon\n");
|
|
printf(" -f, --foreground make the program run in the foreground\n");
|
|
printf(" -p, --path absolute path to the STPG script\n");
|
|
printf(" -t, --timeout transition timeout\n");
|
|
#if defined(DEBUG) || defined(TRACING)
|
|
printf(" -d, --debug=level debug tracing level\n");
|
|
#endif
|
|
printf(" -h, --help display this help and exit\n");
|
|
}
|
|
}
|
|
|
|
static void stpg_handle_tm_received(struct scst_event_user *event_user)
|
|
{
|
|
/*
|
|
* Put code to abort state transition here, if this STPG cmd,
|
|
* identified by cmd_to_abort_tag or RESET, requested to be aborted
|
|
*/
|
|
}
|
|
|
|
static int invoke_stpg(const uint8_t *device_name,
|
|
const struct scst_event_stpg_descr *descr, pid_t *out_pid)
|
|
{
|
|
char *args[7], *env[7];
|
|
int res = 0, ret, i;
|
|
pid_t c_pid;
|
|
|
|
args[0] = stpg_path;
|
|
args[1] = (char *)device_name;
|
|
args[2] = (char *)descr->prev_state;
|
|
args[3] = (char *)descr->new_state;
|
|
args[4] = (char *)descr->dg_name;
|
|
args[5] = (char *)descr->tg_name;
|
|
args[6] = NULL;
|
|
|
|
env[0] = "PATH=/bin:/usr/bin:/sbin:/usr/sbin";
|
|
ret = asprintf(&env[1], "SCST_DEVICE_NAME=%s", device_name);
|
|
if (ret < 0) {
|
|
res = -errno;
|
|
PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res));
|
|
goto out;
|
|
}
|
|
ret = asprintf(&env[2], "SCST_PREV_ALUA_STATE=%s", descr->prev_state);
|
|
if (ret < 0) {
|
|
res = -errno;
|
|
PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res));
|
|
goto out;
|
|
}
|
|
ret = asprintf(&env[3], "SCST_ALUA_STATE=%s", descr->new_state);
|
|
if (ret < 0) {
|
|
res = -errno;
|
|
PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res));
|
|
goto out;
|
|
}
|
|
ret = asprintf(&env[4], "SCST_DEVICE_GROUP=%s", descr->dg_name);
|
|
if (ret < 0) {
|
|
res = -errno;
|
|
PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res));
|
|
goto out;
|
|
}
|
|
ret = asprintf(&env[5], "SCST_TARGET_GROUP=%s", descr->tg_name);
|
|
if (ret < 0) {
|
|
res = -errno;
|
|
PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res));
|
|
goto out;
|
|
}
|
|
env[6] = NULL;
|
|
|
|
PRINT_INFO("Invoking script %s with parameters: %s %s %s %s %s and environment: "
|
|
"%s %s %s %s %s", stpg_path, args[1], args[2], args[3],
|
|
args[4], args[5], env[1], env[2], env[3], env[4], env[5]);
|
|
|
|
c_pid = fork();
|
|
if (c_pid == 0) {
|
|
ret = setpgid(getpid(), getpid());
|
|
if (ret < 0) {
|
|
res = -errno;
|
|
PRINT_ERROR("setgid failed %d (%s)", ret, strerror(-ret));
|
|
}
|
|
TRACE_DBG("pgid %d (pid %d)", getpgid(getpid()), getpid());
|
|
ret = execve(stpg_path, args, env);
|
|
if (ret < 0) {
|
|
res = -errno;
|
|
PRINT_ERROR("EXEC failed %d (%s)", ret, strerror(-ret));
|
|
}
|
|
exit(0);
|
|
} else if (c_pid < 0) {
|
|
res = -errno;
|
|
PRINT_ERROR("fork() failed: %d (%s)", res, strerror(-res));
|
|
}
|
|
|
|
*out_pid = c_pid;
|
|
|
|
for (i = 1; i < (signed)ARRAY_SIZE(env); i++)
|
|
free(env[i]);
|
|
|
|
out:
|
|
return res;
|
|
}
|
|
|
|
/* Returns 0, if the pid is still running, >0 if it was exited or <0 error code */
|
|
static int wait_until_finished(pid_t pid, unsigned long deadline, int *status, int child)
|
|
{
|
|
int res;
|
|
time_t start, end;
|
|
double elapsed;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
time(&start);
|
|
do {
|
|
res = waitpid(pid, status, WNOHANG);
|
|
if (res != 0) {
|
|
if (res < 0) {
|
|
res = -errno;
|
|
PRINT_ERROR("Waitpid for pid %d (child %d) "
|
|
"failed: %d (%s)", pid, child,
|
|
errno, strerror(errno));
|
|
}
|
|
break;
|
|
}
|
|
usleep(100*1000);
|
|
time(&end);
|
|
elapsed = difftime(end, start);
|
|
} while (elapsed < deadline);
|
|
|
|
TRACE_EXIT_RES(res);
|
|
return res;
|
|
}
|
|
|
|
static int handle_stpg_received(struct scst_event_user *event_user)
|
|
{
|
|
const struct scst_event_stpg_payload *p = (struct scst_event_stpg_payload *)event_user->out_event.payload;
|
|
int num, k;
|
|
int res = 0;
|
|
pid_t pids[p->stpg_descriptors_cnt];
|
|
|
|
TRACE_DBG("device name %s, stpg_descriptors_cnt %d", p->device_name,
|
|
p->stpg_descriptors_cnt);
|
|
|
|
for (num = 0; num < p->stpg_descriptors_cnt; num++) {
|
|
res = invoke_stpg(p->device_name, &p->stpg_descriptors[num], &pids[num]);
|
|
TRACE_DBG("num %d, res %d, pid %d", num, res, pids[num]);
|
|
if (res != 0)
|
|
break;
|
|
}
|
|
|
|
TRACE_DBG("num %d", num);
|
|
for (k = 0; k < num; k++) {
|
|
int status = 0, rc;
|
|
|
|
TRACE_DBG("k %d, pid %d", k, pids[k]);
|
|
|
|
rc = wait_until_finished(pids[k], transition_timeout, &status, k);
|
|
TRACE_DBG("rc %d, status %d", rc, WEXITSTATUS(status));
|
|
if (rc > 0) {
|
|
if (res == 0)
|
|
res = WEXITSTATUS(status);
|
|
continue;
|
|
} else if (rc < 0) {
|
|
if (res == 0)
|
|
res = rc;
|
|
continue;
|
|
}
|
|
|
|
PRINT_WARNING("on_stpg %d (pid %d) did not finish on time - "
|
|
"sending SIGTERM", k, pids[k]);
|
|
if (res == 0)
|
|
res = -ETIMEDOUT;
|
|
rc = killpg(pids[k], SIGTERM);
|
|
if (rc < 0)
|
|
PRINT_ERROR("Failed to send SIGTERM to child %d (pid %d): %d/%s",
|
|
k, pids[k], errno, strerror(errno));
|
|
|
|
rc = wait_until_finished(pids[k], 1, &status, k);
|
|
if (rc != 0)
|
|
continue;
|
|
|
|
while (1) {
|
|
PRINT_WARNING("on_stpg %d (pid %d) did not finish on time - "
|
|
"sending SIGKILL", k, pids[k]);
|
|
rc = killpg(pids[k], SIGKILL);
|
|
if (rc < 0) {
|
|
PRINT_ERROR("Failed to send SIGKILL to child %d "
|
|
"(pid %d): %d/%s", k, pids[k], errno,
|
|
strerror(errno));
|
|
break;
|
|
}
|
|
rc = wait_until_finished(pids[k], 1, &status, k);
|
|
if (rc != 0)
|
|
break;
|
|
};
|
|
}
|
|
|
|
TRACE_EXIT_RES(res);
|
|
return res;
|
|
}
|
|
|
|
static int stpg_event_loop(void)
|
|
{
|
|
int res = 0, status;
|
|
int event_fd;
|
|
uint8_t event_user_buf[1024 * 1024];
|
|
pid_t c_pid = 0;
|
|
struct pollfd pl;
|
|
struct scst_event_user *event_user = (struct scst_event_user *)event_user_buf;
|
|
struct scst_event e1;
|
|
bool first_error = true;
|
|
|
|
event_fd = open(SCST_EVENT_DEV, O_RDWR);
|
|
if (event_fd < 0) {
|
|
res = -errno;
|
|
PRINT_ERROR("Unable to open SCST event device %s (%s)",
|
|
SCST_EVENT_DEV, strerror(-res));
|
|
return res;
|
|
}
|
|
|
|
close(stpg_init_report_pipe[0]);
|
|
|
|
if (log_daemon)
|
|
res = write(stpg_init_report_pipe[1], &res, sizeof(res));
|
|
|
|
close(stpg_init_report_pipe[1]);
|
|
|
|
memset(&pl, 0, sizeof(pl));
|
|
pl.fd = event_fd;
|
|
pl.events = POLLIN;
|
|
|
|
memset(&e1, 0, sizeof(e1));
|
|
|
|
e1.event_code = SCST_EVENT_STPG_USER_INVOKE;
|
|
strncpy(e1.issuer_name, SCST_EVENT_SCST_CORE_ISSUER,
|
|
sizeof(e1.issuer_name));
|
|
e1.issuer_name[sizeof(e1.issuer_name)-1] = '\0';
|
|
|
|
PRINT_INFO("Setting allowed event code %d, issuer_name %s",
|
|
e1.event_code, e1.issuer_name);
|
|
|
|
res = ioctl(event_fd, SCST_EVENT_ALLOW_EVENT, &e1);
|
|
if (res != 0) {
|
|
res = -errno;
|
|
PRINT_ERROR("SCST_EVENT_ALLOW_EVENT failed: %d (%s)",
|
|
res, strerror(-res));
|
|
goto out;
|
|
}
|
|
|
|
e1.event_code = SCST_EVENT_TM_FN_RECEIVED;
|
|
strncpy(e1.issuer_name, SCST_EVENT_SCST_CORE_ISSUER,
|
|
sizeof(e1.issuer_name));
|
|
e1.issuer_name[sizeof(e1.issuer_name) - 1] = '\0';
|
|
|
|
PRINT_INFO("Setting allowed event code %d, issuer_name %s",
|
|
e1.event_code, e1.issuer_name);
|
|
|
|
res = ioctl(event_fd, SCST_EVENT_ALLOW_EVENT, &e1);
|
|
if (res != 0) {
|
|
res = -errno;
|
|
PRINT_ERROR("SCST_EVENT_ALLOW_EVENT failed: %d (%s)",
|
|
res, strerror(-res));
|
|
goto out;
|
|
}
|
|
|
|
while (1) {
|
|
memset(event_user_buf, 0, sizeof(event_user_buf));
|
|
event_user->max_event_size = sizeof(event_user_buf);
|
|
|
|
res = ioctl(event_fd, SCST_EVENT_GET_NEXT_EVENT, event_user);
|
|
if (res != 0) {
|
|
res = -errno;
|
|
switch (-res) {
|
|
case ESRCH:
|
|
case EBUSY:
|
|
TRACE_MGMT_DBG("SCST_EVENT_GET_NEXT_EVENT returned %d (%s)",
|
|
res, strerror(res));
|
|
/* fall through */
|
|
case EINTR:
|
|
continue;
|
|
case EAGAIN:
|
|
TRACE_DBG("SCST_EVENT_GET_NEXT_EVENT, returned EAGAIN (%d)",
|
|
-res);
|
|
continue;
|
|
default:
|
|
PRINT_ERROR("SCST_EVENT_GET_NEXT_EVENT failed: %d (%s)",
|
|
res, strerror(-res));
|
|
if (!first_error)
|
|
goto out;
|
|
first_error = false;
|
|
continue;
|
|
}
|
|
first_error = true;
|
|
again_poll:
|
|
res = poll(&pl, 1, c_pid > 0 ? 1 : 0);
|
|
if (res > 0)
|
|
continue;
|
|
if (res == 0)
|
|
goto again_poll;
|
|
|
|
res = -errno;
|
|
switch (res) {
|
|
case ESRCH:
|
|
case EBUSY:
|
|
case EAGAIN:
|
|
TRACE_MGMT_DBG("poll() returned %d (%s)",
|
|
res, strerror(-res));
|
|
/* fall through */
|
|
case EINTR:
|
|
goto again_poll;
|
|
default:
|
|
PRINT_ERROR("poll() failed: %d (%s)",
|
|
res, strerror(-res));
|
|
goto again_poll;
|
|
}
|
|
}
|
|
first_error = true;
|
|
#ifdef DEBUG
|
|
PRINT_INFO("event_code %d, issuer_name %s",
|
|
event_user->out_event.event_code,
|
|
event_user->out_event.issuer_name);
|
|
#endif
|
|
if (event_user->out_event.payload_len != 0)
|
|
TRACE_BUFFER("payload", event_user->out_event.payload,
|
|
event_user->out_event.payload_len);
|
|
|
|
if (event_user->out_event.event_code == SCST_EVENT_STPG_USER_INVOKE) {
|
|
c_pid = fork();
|
|
if (c_pid == -1) {
|
|
PRINT_ERROR("Failed to fork: %d", c_pid);
|
|
} else if (c_pid == 0) {
|
|
struct scst_event_notify_done d;
|
|
|
|
signal(SIGCHLD, SIG_DFL);
|
|
|
|
status = handle_stpg_received(event_user);
|
|
|
|
memset(&d, 0, sizeof(d));
|
|
d.event_id = event_user->out_event.event_id;
|
|
d.status = status;
|
|
res = ioctl(event_fd, SCST_EVENT_NOTIFY_DONE, &d);
|
|
if (res != 0) {
|
|
res = -errno;
|
|
PRINT_ERROR("SCST_EVENT_NOTIFY_DONE failed: %s (res %d)",
|
|
strerror(-res), res);
|
|
} else {
|
|
PRINT_INFO("STPG event completed with status %d", status);
|
|
}
|
|
exit(res);
|
|
}
|
|
} else if (event_user->out_event.event_code == SCST_EVENT_TM_FN_RECEIVED) {
|
|
stpg_handle_tm_received(event_user);
|
|
} else {
|
|
PRINT_ERROR("Unknown event %d received", event_user->out_event.event_code);
|
|
}
|
|
}
|
|
out:
|
|
close(event_fd);
|
|
return res;
|
|
}
|
|
|
|
static void sig_chld(int signal)
|
|
{
|
|
/* Check just in case */
|
|
if (signal == SIGCHLD) {
|
|
TRACE_DBG("Cleanup zombie (pid %d)", getpid());
|
|
wait(NULL);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int res = 0, ch, longindex;
|
|
pid_t pid;
|
|
struct sigaction sa;
|
|
|
|
setlinebuf(stdout);
|
|
|
|
openlog(argv[0], LOG_PID, LOG_USER);
|
|
|
|
res = pipe(stpg_init_report_pipe);
|
|
if (res == -1) {
|
|
res = -errno;
|
|
PRINT_ERROR("Pipe failed: %d (%s)", res, strerror(-res));
|
|
goto out;
|
|
}
|
|
|
|
sa.sa_handler = &sig_chld;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
|
|
if (sigaction(SIGCHLD, &sa, 0) == -1) {
|
|
PRINT_ERROR("sigaction() failed: %d/%s", errno, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* Otherwise we could die in some later write() during the event_loop()
|
|
* instead of getting EPIPE!
|
|
*/
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
res = debug_init();
|
|
if (res != 0)
|
|
goto out;
|
|
|
|
app_name = argv[0];
|
|
|
|
while ((ch = getopt_long(argc, argv, "+d:fp:t:hv",
|
|
long_options, &longindex)) >= 0) {
|
|
switch (ch) {
|
|
case 'p':
|
|
stpg_path = optarg;
|
|
break;
|
|
#if defined(DEBUG) || defined(TRACING)
|
|
case 'd':
|
|
trace_flag = strtol(optarg, (char **)NULL, 0);
|
|
break;
|
|
#endif
|
|
case 'v':
|
|
printf("%s version %s\n", app_name, VERSION_STR);
|
|
goto out_done;
|
|
case 'f':
|
|
log_daemon = false;
|
|
break;
|
|
case 't':
|
|
transition_timeout = strtol(optarg, (char **)NULL, 0);
|
|
if (transition_timeout < 0) {
|
|
printf("Invalid timeout %d\n", transition_timeout);
|
|
res = -EINVAL;
|
|
goto out_done;
|
|
}
|
|
break;
|
|
case 'h':
|
|
usage(0);
|
|
goto out_done;
|
|
default:
|
|
goto out_usage;
|
|
}
|
|
}
|
|
|
|
if (!stpg_path)
|
|
stpg_path = "/usr/local/bin/scst/scst_on_stpg";
|
|
|
|
if (access(stpg_path, X_OK) == -1) {
|
|
PRINT_ERROR("Script file \" %s \"does not exist or not "
|
|
"executable", stpg_path);
|
|
res = -1;
|
|
goto out_done;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
PRINT_INFO("trace_flag %lx", trace_flag);
|
|
#endif
|
|
if (log_daemon) {
|
|
#if defined(DEBUG) || defined(TRACING)
|
|
trace_flag &= ~TRACE_TIME;
|
|
trace_flag &= ~TRACE_PID;
|
|
#endif
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
PRINT_ERROR("starting daemon failed(%d)", pid);
|
|
res = pid;
|
|
goto out_done;
|
|
} else if (pid) {
|
|
int res1 = -1;
|
|
|
|
close(stpg_init_report_pipe[1]);
|
|
if ((unsigned)read(stpg_init_report_pipe[0], &res1, sizeof(res1)) < sizeof(res1)) {
|
|
res = -1;
|
|
goto out_done;
|
|
} else {
|
|
res = res1;
|
|
goto out_done;
|
|
}
|
|
}
|
|
|
|
close(0);
|
|
open("/dev/null", O_RDWR);
|
|
dup2(0, 1);
|
|
dup2(0, 2);
|
|
setsid();
|
|
}
|
|
|
|
res = stpg_event_loop();
|
|
|
|
out_done:
|
|
debug_done();
|
|
|
|
out:
|
|
closelog();
|
|
return res;
|
|
|
|
out_usage:
|
|
usage(1);
|
|
goto out_done;
|
|
}
|