/* * stpgd_main.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }