/*
* Copyright (c) 2014 Red Hat, Inc.
*
* This file is licensed to you under your choice of the GNU Lesser
* General Public License, version 2.1 or any later version (LGPLv2.1 or
* later), or the Apache License 2.0.
*/
/*
* A daemon that supports a simplified interface for writing TCMU
* handlers.
*/
#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <assert.h>
#include <dlfcn.h>
#include <pthread.h>
#include <signal.h>
#include <glib.h>
#include <glib-unix.h>
#include <gio/gio.h>
#include <getopt.h>
#include <poll.h>
#include <time.h>
#include <scsi/scsi.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <libkmod.h>
#include <sys/utsname.h>
#include "target_core_user_local.h"
#include "darray.h"
#include "tcmu-runner.h"
#include "tcmur_aio.h"
#include "tcmur_device.h"
#include "tcmur_cmd_handler.h"
#include "libtcmu.h"
#include "tcmuhandler-generated.h"
#include "version.h"
#include "libtcmu_config.h"
#include "libtcmu_log.h"
#include "tcmur_work.h"
#define TCMU_LOCK_FILE "/run/tcmu.lock"
static char *handler_path = DEFAULT_HANDLER_PATH;
static struct tcmu_config *tcmu_cfg;
darray(struct tcmur_handler *) g_runner_handlers = darray_new();
struct tcmur_handler *tcmu_get_runner_handler(struct tcmu_device *dev)
{
struct tcmulib_handler *handler = tcmu_dev_get_handler(dev);
return handler->hm_private;
}
int tcmur_register_handler(struct tcmur_handler *handler)
{
struct tcmur_handler *h;
int ret;
int i;
for (i = 0; i < darray_size(g_runner_handlers); i++) {
h = darray_item(g_runner_handlers, i);
if (!strcmp(h->subtype, handler->subtype)) {
tcmu_err("Handler %s has already been registered\n",
handler->subtype);
return -1;
}
}
if (handler->init) {
ret = handler->init();
if (ret) {
tcmu_err("Failed to init handler %s, ret = %d\n",
handler->subtype, ret);
return ret;
}
}
tcmu_info("Handler %s is registered\n", handler->subtype);
darray_append(g_runner_handlers, handler);
return 0;
}
static int tcmur_register_dbus_handler(struct tcmur_handler *handler)
{
assert(handler->_is_dbus_handler == true);
return tcmur_register_handler(handler);
}
bool tcmur_unregister_handler(struct tcmur_handler *handler)
{
int i;
for (i = 0; i < darray_size(g_runner_handlers); i++) {
if (darray_item(g_runner_handlers, i) == handler) {
darray_remove(g_runner_handlers, i);
return true;
}
}
return false;
}
static void free_dbus_handler(struct tcmur_handler *handler)
{
g_free((char*)handler->opaque);
g_free((char*)handler->subtype);
g_free((char*)handler->cfg_desc);
if (handler->destroy)
handler->destroy();
g_free(handler);
}
static bool tcmur_unregister_dbus_handler(struct tcmur_handler *handler)
{
bool ret = false;
assert(handler->_is_dbus_handler == true);
ret = tcmur_unregister_handler(handler);
if (ret == true) {
free_dbus_handler(handler);
}
return ret;
}
static void tcmur_unregister_all_dbus_handlers(void)
{
struct tcmur_handler *handler;
int i;
for (i = 0; i < darray_size(g_runner_handlers); i++) {
handler = darray_item(g_runner_handlers, i);
if (handler->_is_dbus_handler == true) {
if (tcmur_unregister_handler(handler))
free_dbus_handler(handler);
}
}
darray_free(g_runner_handlers);
}
static int is_handler(const struct dirent *dirent)
{
if (strncmp(dirent->d_name, "handler_", 8))
return 0;
return 1;
}
static int open_handlers(void)
{
struct dirent **dirent_list;
int num_handlers;
int num_good = 0;
char *error;
int i;
num_handlers = scandir(handler_path, &dirent_list, is_handler, alphasort);
if (num_handlers == -1)
return -1;
for (i = 0; i < num_handlers; i++) {
char *path;
void *handle;
int (*handler_init)(void);
int ret;
ret = asprintf(&path, "%s/%s", handler_path, dirent_list[i]->d_name);
if (ret == -1) {
tcmu_err("ENOMEM\n");
continue;
}
handle = dlopen(path, RTLD_NOW|RTLD_LOCAL);
if (!handle) {
tcmu_err("Could not open handler at %s: %s\n", path, dlerror());
free(path);
continue;
}
dlerror();
handler_init = dlsym(handle, "handler_init");
if ((error = dlerror())) {
tcmu_err("dlsym failure on %s: (%s)\n", path, error);
free(path);
continue;
}
ret = handler_init();
if (ret) {
tcmu_err("handler init failed on path %s\n", path);
free(path);
continue;
}
free(path);
if (ret == 0)
num_good++;
}
for (i = 0; i < num_handlers; i++)
free(dirent_list[i]);
free(dirent_list);
return num_good;
}
static gboolean handle_sig(gpointer user_data)
{
tcmu_dbg("Have received signal!\n");
g_main_loop_quit((GMainLoop*)user_data);
return G_SOURCE_CONTINUE;
}
static gboolean handle_sighup(gpointer user_data)
{
tcmu_resetup_log_file(NULL, NULL);
return G_SOURCE_CONTINUE;
}
gboolean tcmulib_callback(GIOChannel *source,
GIOCondition condition,
gpointer data)
{
struct tcmulib_context *ctx = data;
tcmulib_master_fd_ready(ctx);
return TRUE;
}
static GDBusObjectManagerServer *manager = NULL;
static gboolean
on_check_config(TCMUService1 *interface,
GDBusMethodInvocation *invocation,
gchar *cfgstring,
gpointer user_data)
{
struct tcmur_handler *handler = user_data;
char *reason = NULL;
bool str_ok = true;
if (handler->check_config)
str_ok = handler->check_config(cfgstring, &reason);
if (str_ok)
reason = "success";
g_dbus_method_invocation_return_value(invocation,
g_variant_new("(bs)", str_ok, reason ? : "unknown"));
if (!str_ok)
free(reason);
return TRUE;
}
static void
dbus_export_handler(struct tcmur_handler *handler, GCallback check_config)
{
GDBusObjectSkeleton *object;
char obj_name[128];
TCMUService1 *interface;
snprintf(obj_name, sizeof(obj_name), "/org/kernel/TCMUService1/%s",
handler->subtype);
object = g_dbus_object_skeleton_new(obj_name);
interface = tcmuservice1_skeleton_new();
g_dbus_object_skeleton_add_interface(object, G_DBUS_INTERFACE_SKELETON(interface));
g_signal_connect(interface,
"handle-check-config",
check_config,
handler); /* user_data */
tcmuservice1_set_config_desc(interface, handler->cfg_desc);
g_dbus_object_manager_server_export(manager, G_DBUS_OBJECT_SKELETON(object));
g_object_unref(object);
}
static bool
dbus_unexport_handler(struct tcmur_handler *handler)
{
char obj_name[128];
snprintf(obj_name, sizeof(obj_name), "/org/kernel/TCMUService1/%s",
handler->subtype);
return g_dbus_object_manager_server_unexport(manager, obj_name) == TRUE;
}
struct dbus_info {
guint watcher_id;
/* The RegisterHandler invocation on
* org.kernel.TCMUService1.HandlerManager1 interface. */
GDBusMethodInvocation *register_invocation;
/* Connection to the handler's bus_name. */
GDBusConnection *connection;
};
static int dbus_handler_open(struct tcmu_device *dev, bool reopen)
{
return -1;
}
static void dbus_handler_close(struct tcmu_device *dev)
{
/* nop */
}
static int dbus_handler_handle_cmd(struct tcmu_device *dev,
struct tcmur_cmd *tcmu_cmd)
{
abort();
}
static gboolean
on_dbus_check_config(TCMUService1 *interface,
GDBusMethodInvocation *invocation,
gchar *cfgstring,
gpointer user_data)
{
char *bus_name, *obj_name;
struct tcmur_handler *handler = user_data;
GDBusConnection *connection;
GError *error = NULL;
GVariant *result;
bus_name = g_strdup_printf("org.kernel.TCMUService1.HandlerManager1.%s",
handler->subtype);
obj_name = g_strdup_printf("/org/kernel/TCMUService1/HandlerManager1/%s",
handler->subtype);
connection = g_dbus_method_invocation_get_connection(invocation);
result = g_dbus_connection_call_sync(connection,
bus_name,
obj_name,
"org.kernel.TCMUService1",
"CheckConfig",
g_variant_new("(s)", cfgstring),
NULL, G_DBUS_CALL_FLAGS_NONE, -1,
NULL, &error);
if (result)
g_dbus_method_invocation_return_value(invocation, result);
else
g_dbus_method_invocation_return_value(invocation,
g_variant_new("(bs)", FALSE, error->message));
g_free(bus_name);
g_free(obj_name);
return TRUE;
}
static void
on_handler_appeared(GDBusConnection *connection,
const gchar *name,
const gchar *name_owner,
gpointer user_data)
{
struct tcmur_handler *handler = user_data;
struct dbus_info *info = handler->opaque;
if (info->register_invocation) {
info->connection = connection;
tcmur_register_dbus_handler(handler);
dbus_export_handler(handler, G_CALLBACK(on_dbus_check_config));
g_dbus_method_invocation_return_value(info->register_invocation,
g_variant_new("(bs)", TRUE, "succeeded"));
info->register_invocation = NULL;
}
}
static void
on_handler_vanished(GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
struct tcmur_handler *handler = user_data;
struct dbus_info *info = handler->opaque;
if (info->register_invocation) {
char *reason;
reason = g_strdup_printf("Cannot find handler bus name: "
"org.kernel.TCMUService1.HandlerManager1.%s",
handler->subtype);
g_dbus_method_invocation_return_value(info->register_invocation,
g_variant_new("(bs)", FALSE, reason));
g_free(reason);
}
dbus_unexport_handler(handler);
g_bus_unwatch_name(info->watcher_id);
tcmur_unregister_dbus_handler(handler);
}
static gboolean
on_register_handler(TCMUService1HandlerManager1 *interface,
GDBusMethodInvocation *invocation,
gchar *subtype,
gchar *cfg_desc,
gpointer user_data)
{
struct tcmur_handler *handler;
struct dbus_info *info;
char *bus_name;
bus_name = g_strdup_printf("org.kernel.TCMUService1.HandlerManager1.%s",
subtype);
handler = g_new0(struct tcmur_handler, 1);
handler->subtype = g_strdup(subtype);
handler->cfg_desc = g_strdup(cfg_desc);
handler->open = dbus_handler_open;
handler->close = dbus_handler_close;
handler->handle_cmd = dbus_handler_handle_cmd;
info = g_new0(struct dbus_info, 1);
handler->opaque = info;
handler->_is_dbus_handler = 1;
info->register_invocation = invocation;
info->watcher_id = g_bus_watch_name(G_BUS_TYPE_SYSTEM,
bus_name,
G_BUS_NAME_WATCHER_FLAGS_NONE,
on_handler_appeared,
on_handler_vanished,
handler,
NULL);
if (info->watcher_id == 0) {
// probably an invalid name, roll back and report an error
free_dbus_handler(handler);
g_dbus_method_invocation_return_value(invocation,
g_variant_new("(bs)", FALSE,
"failed to watch for DBus handler name"));
}
g_free(bus_name);
return TRUE;
}
void dbus_handler_manager1_init(GDBusConnection *connection)
{
GError *error = NULL;
TCMUService1HandlerManager1 *interface;
gboolean ret;
interface = tcmuservice1_handler_manager1_skeleton_new();
ret = g_dbus_interface_skeleton_export(
G_DBUS_INTERFACE_SKELETON(interface),
connection,
"/org/kernel/TCMUService1/HandlerManager1",
&error);
g_signal_connect(interface,
"handle-register-handler",
G_CALLBACK (on_register_handler),
NULL);
if (!ret)
tcmu_err("Handler manager export failed: %s\n",
error ? error->message : "unknown error");
if (error)
g_error_free(error);
}
static void dbus_bus_acquired(GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
struct tcmur_handler **handler;
tcmu_dbg("bus %s acquired\n", name);
manager = g_dbus_object_manager_server_new("/org/kernel/TCMUService1");
darray_foreach(handler, g_runner_handlers) {
dbus_export_handler(*handler, G_CALLBACK(on_check_config));
}
dbus_handler_manager1_init(connection);
g_dbus_object_manager_server_set_connection(manager, connection);
}
static void dbus_name_acquired(GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
tcmu_dbg("name %s acquired\n", name);
}
static void dbus_name_lost(GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
tcmu_dbg("name lost\n");
}
static int load_our_module(void)
{
struct kmod_list *list = NULL, *itr;
struct kmod_ctx *ctx;
struct stat sb;
struct utsname u;
int ret;
ctx = kmod_new(NULL, NULL);
if (!ctx) {
tcmu_err("kmod_new() failed: %m\n");
return -1;
}
ret = kmod_module_new_from_lookup(ctx, "target_core_user", &list);
if (ret < 0) {
/* In some environments like containers, /lib/modules/`uname -r`
* will not exist, in such cases the load module job be taken
* care by admin, either by manual load or makesure it's builtin
*/
if (ENOENT == errno) {
if (uname(&u) < 0) {
tcmu_err("uname() failed: %m\n");
} else {
tcmu_info("no modules directory '/lib/modules/%s', checking module target_core_user entry in '/sys/modules/'\n",
u.release);
ret = stat(CFGFS_TARGET_MOD, &sb);
if (!ret) {
tcmu_dbg("Module target_core_user already loaded\n");
} else {
tcmu_err("stat() on '%s' failed: %m\n",
CFGFS_TARGET_MOD);
}
}
} else {
tcmu_err("kmod_module_new_from_lookup() failed to lookup alias target_core_use %m\n");
}
kmod_unref(ctx);
return ret;
}
if (!list) {
tcmu_err("kmod_module_new_from_lookup() failed to find module target_core_user\n");
kmod_unref(ctx);
return -ENOENT;
}
kmod_list_foreach(itr, list) {
int state, err;
struct kmod_module *mod = kmod_module_get_module(itr);
state = kmod_module_get_initstate(mod);
switch (state) {
case KMOD_MODULE_BUILTIN:
tcmu_info("Module '%s' is builtin\n",
kmod_module_get_name(mod));
break;
case KMOD_MODULE_LIVE:
tcmu_dbg("Module '%s' is already loaded\n",
kmod_module_get_name(mod));
break;
default:
err = kmod_module_probe_insert_module(mod,
KMOD_PROBE_APPLY_BLACKLIST,
NULL, NULL, NULL, NULL);
if (err == 0) {
tcmu_info("Inserted module '%s'\n",
kmod_module_get_name(mod));
} else if (err < 0) {
tcmu_err("Failed to insert '%s': %s\n",
kmod_module_get_name(mod), strerror(-err));
ret = err;
} else {
switch (err) {
case KMOD_PROBE_APPLY_BLACKLIST:
tcmu_err("Module '%s' is blacklisted\n",
kmod_module_get_name(mod));
break;
default:
tcmu_err("Module '%s' is stopped by a reason: 0x%x\n",
kmod_module_get_name(mod), err);
break;
}
ret = -EIO;
}
}
kmod_module_unref(mod);
}
kmod_module_unref_list(list);
kmod_unref(ctx);
return ret;
}
/*
* tcmur_stop_device - stop device for removal
* @arg: tcmu_device to stop
*
* Stop internal tcmur device operations like lock and recovery and close
* the device. Running IO must be stopped before calling this.
*/
static void tcmur_stop_device(void *arg)
{
struct tcmu_device *dev = arg;
struct tcmur_handler *rhandler = tcmu_get_runner_handler(dev);
struct tcmur_device *rdev = tcmu_dev_get_private(dev);
bool is_open = false;
pthread_mutex_lock(&rdev->state_lock);
/* check if this was already called due to thread cancelation */
if (rdev->flags & TCMUR_DEV_FLAG_STOPPED) {
pthread_mutex_unlock(&rdev->state_lock);
return;
}
rdev->flags |= TCMUR_DEV_FLAG_STOPPING;
pthread_mutex_unlock(&rdev->state_lock);
/*
* The lock thread can fire off the recovery thread, so make sure
* it is done first.
*/
tcmur_flush_work(rdev->event_work);
tcmu_cancel_recovery(dev);
tcmu_release_dev_lock(dev);
pthread_mutex_lock(&rdev->state_lock);
if (rdev->flags & TCMUR_DEV_FLAG_IS_OPEN) {
rdev->flags &= ~TCMUR_DEV_FLAG_IS_OPEN;
is_open = true;
}
pthread_mutex_unlock(&rdev->state_lock);
if (is_open)
rhandler->close(dev);
pthread_mutex_lock(&rdev->state_lock);
rdev->flags |= TCMUR_DEV_FLAG_STOPPED;
pthread_mutex_unlock(&rdev->state_lock);
tcmu_dev_dbg(dev, "cmdproc cleanup done\n");
}
int tcmur_get_time(struct tcmu_device *dev, struct timespec *time)
{
struct tcmur_device *rdev = tcmu_dev_get_private(dev);
int ret;
ret = clock_gettime(CLOCK_MONOTONIC_COARSE, time);
if (!ret) {
tcmu_dev_dbg(dev, "Current time %lu secs.\n", time->tv_sec);
return 0;
}
tcmu_dev_err(dev, "Could not get time. Error %d. Command timeout feature disabled.\n",
ret);
rdev->cmd_time_out = 0;
return ret;
}
static bool get_next_cmd_timeout(struct tcmu_device *dev,
struct timespec *curr_time,
struct timespec *tmo)
{
struct tcmur_device *rdev = tcmu_dev_get_private(dev);
int run_time, cmd_tmo = rdev->cmd_time_out;
struct tcmur_cmd *tcmur_cmd;
bool has_timeout = false;
if (!cmd_tmo)
return false;
memset(tmo, 0, sizeof(*tmo));
pthread_spin_lock(&rdev->lock);
list_for_each(&rdev->cmds_list, tcmur_cmd, cmds_list_entry) {
if (tcmur_cmd->timed_out)
continue;
has_timeout = true;
run_time = difftime(curr_time->tv_sec,
tcmur_cmd->start_time.tv_sec);
if (cmd_tmo > run_time) {
tmo->tv_sec = cmd_tmo - run_time;
} else {
/*
* We do not do a clock call for every command, so
* cmds can time out while we were processing new
* cmds. Force a recheck.
*/
tmo->tv_sec = 0;
}
tcmu_dev_dbg(dev, "Next cmd id %hu timeout in %lu secs. Current time %lu. Start time %lu\n",
tcmur_cmd->lib_cmd->cmd_id, tmo->tv_sec,
curr_time->tv_sec, tcmur_cmd->start_time.tv_sec);
break;
}
pthread_spin_unlock(&rdev->lock);
return has_timeout;
}
static void check_for_timed_out_cmds(struct tcmu_device *dev)
{
struct tcmur_device *rdev = tcmu_dev_get_private(dev);
int cmd_tmo = rdev->cmd_time_out;
struct tcmur_cmd *tcmur_cmd;
struct timespec curr_time;
struct tcmulib_cmd *cmd;
int run_time;
uint8_t *cdb;
if (!cmd_tmo)
return;
memset(&curr_time, 0, sizeof(curr_time));
if (tcmur_get_time(dev, &curr_time))
return;
pthread_spin_lock(&rdev->lock);
list_for_each(&rdev->cmds_list, tcmur_cmd, cmds_list_entry) {
if (tcmur_cmd->timed_out)
continue;
run_time = difftime(curr_time.tv_sec,
tcmur_cmd->start_time.tv_sec);
if (run_time < cmd_tmo)
continue;
cmd = tcmur_cmd->lib_cmd;
if (tcmu_get_log_level() == TCMU_LOG_DEBUG_SCSI_CMD) {
tcmu_cdb_print_info(dev, cmd, "timed out.");
} else {
cdb = cmd->cdb;
tcmu_dev_info(dev, "Command %hu SCSI CDB 0x%x at LBA %"PRIu64" for %u blocks timed out.\n",
cmd->cmd_id, cdb[0],
tcmu_cdb_get_lba(cdb),
tcmu_cdb_get_xfer_length(cdb));
}
tcmur_cmd->timed_out = true;
/*
* These time outs are only currently used for diagnostic
* purposes right now, so we do not want to escalate the
* error handler and just return true here.
*/
tcmu_notify_cmd_timed_out(dev);
}
pthread_spin_unlock(&rdev->lock);
}
static void tcmur_tcmulib_cmd_start(struct tcmu_device *dev,
struct tcmulib_cmd *cmd,
struct timespec *curr_time)
{
struct tcmur_device *rdev = tcmu_dev_get_private(dev);
struct tcmur_cmd *tcmur_cmd = cmd->hm_private;
memset(tcmur_cmd, 0, sizeof(*tcmur_cmd));
tcmur_cmd->lib_cmd = cmd;
list_node_init(&tcmur_cmd->cmds_list_entry);
if (rdev->cmd_time_out) {
tcmur_cmd->start_time.tv_sec = curr_time->tv_sec;
pthread_spin_lock(&rdev->lock);
list_add_tail(&rdev->cmds_list, &tcmur_cmd->cmds_list_entry);
pthread_spin_unlock(&rdev->lock);
}
}
static void *tcmur_cmdproc_thread(void *arg)
{
struct tcmu_device *dev = arg;
struct tcmur_handler *rhandler = tcmu_get_runner_handler(dev);
struct tcmur_device *rdev = tcmu_dev_get_private(dev);
struct pollfd pfd;
int ret;
bool dev_stopping = false;
tcmu_set_thread_name("cmdproc", dev);
pthread_cleanup_push(tcmur_stop_device, dev);
while (1) {
int completed = 0;
struct tcmulib_cmd *cmd;
struct timespec tmo, curr_time;
bool set_tmo;
tcmulib_processing_start(dev);
if (rdev->cmd_time_out)
tcmur_get_time(dev, &curr_time);
while (!dev_stopping &&
(cmd = tcmulib_get_next_command(dev,
sizeof(struct tcmur_cmd))) != NULL) {
tcmur_tcmulib_cmd_start(dev, cmd, &curr_time);
if (tcmu_get_log_level() == TCMU_LOG_DEBUG_SCSI_CMD)
tcmu_cdb_print_info(dev, cmd, NULL);
if (tcmur_handler_is_passthrough_only(rhandler))
ret = tcmur_cmd_passthrough_handler(dev, cmd);
else
ret = tcmur_generic_handle_cmd(dev, cmd);
if (ret == TCMU_STS_NOT_HANDLED)
tcmu_cdb_print_info(dev, cmd, "is not supported");
/*
* command (processing) completion is called in the following
* scenarios:
* - handle_cmd: synchronous handlers
* - generic_handle_cmd: non tcmur handler calls (see generic_cmd())
* and on errors when calling tcmur handler.
*/
if (ret != TCMU_STS_ASYNC_HANDLED) {
completed = 1;
tcmur_tcmulib_cmd_complete(dev, cmd, ret);
}
}
if (completed)
tcmulib_processing_complete(dev);
set_tmo = get_next_cmd_timeout(dev, &curr_time, &tmo);
pfd.fd = tcmu_dev_get_fd(dev);
pfd.events = POLLIN;
pfd.revents = 0;
/* Use ppoll instead poll to avoid poll call reschedules during signal
* handling. If we were removing a device, then the uio device's memory
* could be freed, but the poll would be rescheduled and end up accessing
* the released device. */
if (set_tmo) {
ret = ppoll(&pfd, 1, &tmo, NULL);
} else {
ret = ppoll(&pfd, 1, NULL, NULL);
}
if (ret == -1) {
tcmu_err("ppoll() returned %d\n", ret);
break;
}
if (!ret) {
check_for_timed_out_cmds(dev);
} else if (pfd.revents != POLLIN) {
tcmu_err("ppoll received unexpected revent: 0x%x\n", pfd.revents);
break;
}
/*
* LIO will wait for outstanding requests and prevent new ones
* from being sent to runner during device removal, but if the
* tcmu cmd_time_out has fired tcmu-runner may still be executing
* requests that LIO has completed. We only need to wait for replies
* for outstanding requests so throttle the cmdproc thread now.
*/
pthread_mutex_lock(&rdev->state_lock);
if (rdev->flags & TCMUR_DEV_FLAG_STOPPING)
dev_stopping = true;
pthread_mutex_unlock(&rdev->state_lock);
}
/*
* If we are doing a clean shutdown via dev_removed the
* removing thread will call the cleanup function when
* it has stopped and flushed the device.
*/
pthread_cleanup_pop(0);
return NULL;
}
static int dev_resize(struct tcmu_device *dev, struct tcmulib_cfg_info *cfg)
{
struct tcmur_handler *rhandler = tcmu_get_runner_handler(dev);
uint64_t new_lbas = tcmu_byte_to_lba(dev, cfg->data.dev_size);
int ret;
if (tcmu_dev_get_num_lbas(dev) == new_lbas)
return 0;
ret = rhandler->reconfig(dev, cfg);
if (ret)
return ret;
tcmu_dev_set_num_lbas(dev, new_lbas);
tcmur_set_pending_ua(dev, TCMUR_UA_DEV_SIZE_CHANGED);
return 0;
}
static int dev_reconfig(struct tcmu_device *dev, struct tcmulib_cfg_info *cfg)
{
struct tcmur_handler *rhandler = tcmu_get_runner_handler(dev);
if (!rhandler->reconfig)
return -EOPNOTSUPP;
switch (cfg->type) {
case TCMULIB_CFG_DEV_SIZE:
return dev_resize(dev, cfg);
default:
return rhandler->reconfig(dev, cfg);
}
}
static void parse_tcmu_runner_args(struct tcmu_device *dev)
{
struct tcmur_device *rdev = tcmu_dev_get_private(dev);
char *arg, *cfg_str, *arg_end, *cfg_end;
bool found;
cfg_str = tcmu_dev_get_cfgstring(dev);
/* count ending null in string */
cfg_end = cfg_str + strlen(cfg_str) + 1;
while ((arg = strstr(cfg_str, ";"))) {
found = false;
arg++;
if (!strncmp(arg, "tcmur_cmd_time_out=", 19)) {
rdev->cmd_time_out = atoi(arg + 19);
tcmu_dev_dbg(dev, "Using tcmur_cmd_timeout %d\n",
rdev->cmd_time_out);
found = true;
}
arg_end = strstr(arg, ";");
if (!arg_end) {
arg_end = cfg_end;
} else {
arg_end++;
}
if (found) {
memmove(arg - 1, arg_end, cfg_end - arg_end + 1);
} else {
cfg_str = arg;
}
}
tcmu_dev_dbg(dev, "Updated cfgstring: %s.\n",
tcmu_dev_get_cfgstring(dev));
}
static int dev_added(struct tcmu_device *dev)
{
struct tcmur_handler *rhandler = tcmu_get_runner_handler(dev);
struct list_head group_list;
struct tcmur_device *rdev;
int32_t block_size, max_sectors;
int64_t dev_size;
int ret;
rdev = calloc(1, sizeof(*rdev));
if (!rdev)
return -ENOMEM;
tcmu_dev_set_private(dev, rdev);
list_node_init(&rdev->recovery_entry);
list_head_init(&rdev->cmds_list);
rdev->dev = dev;
parse_tcmu_runner_args(dev);
ret = -EINVAL;
block_size = tcmu_cfgfs_dev_get_attr_int(dev, "hw_block_size");
if (block_size <= 0) {
tcmu_dev_err(dev, "Could not get hw_block_size\n");
goto free_rdev;
}
tcmu_dev_set_block_size(dev, block_size);
dev_size = tcmu_cfgfs_dev_get_info_u64(dev, "Size", &ret);
if (ret < 0) {
tcmu_dev_err(dev, "Could not get device size\n");
goto free_rdev;
}
tcmu_dev_set_num_lbas(dev, tcmu_byte_to_lba(dev, dev_size));
max_sectors = tcmu_cfgfs_dev_get_attr_int(dev, "hw_max_sectors");
if (max_sectors < 0)
goto free_rdev;
tcmu_dev_set_max_xfer_len(dev, max_sectors);
/*
* Set the optimal unmap granularity to max xfer len. Optimal unmap
* alignment starts at the begining of the device. Handlers can
* override in their open function.
*/
tcmu_dev_set_max_unmap_len(dev, VPD_MAX_UNMAP_LBA_COUNT);
tcmu_dev_set_opt_unmap_gran(dev, max_sectors, true);
tcmu_dev_set_unmap_gran_align(dev, 0);
/*
* By default we will try to do RWs for xcopys in max_sector chunks,
* but handlers that can do larger internal IOs should override.
*/
tcmu_dev_set_opt_xcopy_rw_len(dev, max_sectors);
if (rhandler->unmap)
tcmu_dev_set_unmap_enabled(dev, true);
tcmu_dev_dbg(dev, "Got block_size %d, size in bytes %"PRId64"\n",
block_size, dev_size);
ret = pthread_spin_init(&rdev->lock, 0);
if (ret) {
ret = -ret;
goto free_rdev;
}
ret = pthread_mutex_init(&rdev->caw_lock, NULL);
if (ret) {
ret = -ret;
goto cleanup_dev_lock;
}
ret = pthread_mutex_init(&rdev->format_lock, NULL);
if (ret) {
ret = -ret;
goto cleanup_caw_lock;
}
ret = pthread_mutex_init(&rdev->state_lock, NULL);
if (ret) {
ret = -ret;
goto cleanup_format_lock;
}
ret = setup_io_work_queue(dev);
if (ret < 0)
goto cleanup_state_lock;
ret = setup_aio_tracking(rdev);
if (ret < 0)
goto cleanup_io_work_queue;
ret = rhandler->open(dev, false);
if (ret)
goto cleanup_aio_tracking;
/*
* On the initial creation ALUA will probably not yet have been setup,
* but for reopens it will be so we need to sync our failover state.
*/
list_head_init(&group_list);
tcmu_get_alua_grps(dev, &group_list);
tcmu_release_alua_grps(&group_list);
rdev->flags |= TCMUR_DEV_FLAG_IS_OPEN;
rdev->event_work = tcmur_create_work();
if (!rdev->event_work) {
ret = -ENOMEM;
goto close_dev;
}
ret = pthread_create(&rdev->cmdproc_thread, NULL, tcmur_cmdproc_thread,
dev);
if (ret) {
ret = -ret;
goto cleanup_event_work;
}
return 0;
cleanup_event_work:
tcmur_destroy_work(rdev->event_work);
close_dev:
rhandler->close(dev);
cleanup_aio_tracking:
cleanup_aio_tracking(rdev);
cleanup_io_work_queue:
cleanup_io_work_queue(dev, true);
cleanup_state_lock:
pthread_mutex_destroy(&rdev->state_lock);
cleanup_format_lock:
pthread_mutex_destroy(&rdev->format_lock);
cleanup_caw_lock:
pthread_mutex_destroy(&rdev->caw_lock);
cleanup_dev_lock:
pthread_spin_destroy(&rdev->lock);
free_rdev:
free(rdev);
return ret;
}
static void dev_removed(struct tcmu_device *dev)
{
struct tcmur_device *rdev = tcmu_dev_get_private(dev);
int ret;
pthread_mutex_lock(&rdev->state_lock);
rdev->flags |= TCMUR_DEV_FLAG_STOPPING;
pthread_mutex_unlock(&rdev->state_lock);
/*
* The order of cleaning up worker threads and calling ->removed()
* is important: for sync handlers, the worker thread needs to be
* terminated before removing the handler (i.e., calling handlers
* ->close() callout) in order to ensure that no handler callouts
* are getting invoked when shutting down the handler.
*/
cleanup_io_work_queue_threads(dev);
if (aio_wait_for_empty_queue(rdev))
tcmu_dev_err(dev, "could not flush queue.\n");
tcmu_thread_cancel(rdev->cmdproc_thread);
tcmur_stop_device(dev);
cleanup_io_work_queue(dev, false);
cleanup_aio_tracking(rdev);
tcmur_destroy_work(rdev->event_work);
ret = pthread_mutex_destroy(&rdev->state_lock);
if (ret != 0)
tcmu_err("could not cleanup state lock %d\n", ret);
ret = pthread_mutex_destroy(&rdev->format_lock);
if (ret != 0)
tcmu_err("could not cleanup format lock %d\n", ret);
ret = pthread_mutex_destroy(&rdev->caw_lock);
if (ret != 0)
tcmu_err("could not cleanup caw lock %d\n", ret);
ret = pthread_spin_destroy(&rdev->lock);
if (ret != 0)
tcmu_err("could not cleanup mailbox lock %d\n", ret);
free(rdev);
tcmu_dev_dbg(dev, "removed from tcmu-runner\n");
}
#define TCMUR_MIN_OPEN_FD 65536
#define TCMUR_MAX_OPEN_FD 1048576
static int tcmu_set_max_fd_limit(const int nr_files)
{
struct rlimit old_rlim, new_rlim;
int ret;
ret = getrlimit(RLIMIT_NOFILE, &old_rlim);
if (ret == -1) {
tcmu_err("failed to get max open fd limit: %m\n");
return ret;
}
if (old_rlim.rlim_cur < nr_files) {
new_rlim.rlim_cur = nr_files;
if (old_rlim.rlim_max < nr_files) {
new_rlim.rlim_max = nr_files;
} else {
new_rlim.rlim_max = old_rlim.rlim_max;
}
ret = setrlimit(RLIMIT_NOFILE, &new_rlim);
if (ret == -1) {
tcmu_err("failed to set max open fd to [soft: %lld hard: %lld] %m\n",
(long long int)new_rlim.rlim_cur,
(long long int)new_rlim.rlim_max);
return ret;
}
tcmu_info("max open fd set to [soft: %lld hard: %lld]\n",
(long long int)new_rlim.rlim_cur,
(long long int)new_rlim.rlim_max);
return 0;
}
tcmu_info("max open fd remain [soft: %lld hard: %lld]\n",
(long long int)old_rlim.rlim_cur,
(long long int)old_rlim.rlim_max);
return 0;
}
static void usage(void) {
printf("\nusage:\n");
printf("\ttcmu-runner [options]\n");
printf("\noptions:\n");
printf("\t-h, --help: print this message and exit\n");
printf("\t-V, --version: print version and exit\n");
printf("\t-d, --debug: enable debug messages\n");
printf("\t-f, --nofile: set maximum file number could be opened\n");
printf("\t\tdefault will be as the systemd or the shell's limitation\n");
printf("\t--handler-path: set path to search for handler modules\n");
printf("\t\tdefault is %s\n", DEFAULT_HANDLER_PATH);
printf("\t-l, --tcmu-log-dir: tcmu log dir\n");
printf("\t\tdefault is %s\n", TCMU_LOG_DIR_DEFAULT);
printf("\n");
}
static struct option long_options[] = {
{"debug", no_argument, 0, 'd'},
{"handler-path", required_argument, 0, 0},
{"tcmu-log-dir", required_argument, 0, 'l'},
{"nofile", required_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
{0, 0, 0, 0},
};
int main(int argc, char **argv)
{
darray(struct tcmulib_handler) handlers = darray_new();
struct tcmulib_context *tcmulib_context;
struct tcmur_handler **tmp_r_handler, *r_handler;
struct tcmulib_handler *handler;
GMainLoop *loop;
GIOChannel *libtcmu_gio;
guint reg_id;
guint watch_id;
bool reset_nl_supp = false;
bool new_path = false;
bool watching_cfg = false;
struct flock lock_fd = {0, };
int fd;
int ret = -1;
if ((tcmu_cfg = tcmu_initialize_config()) == NULL) {
tcmu_err("initializing the tcmu config failed: %m\n");
exit(EXIT_FAILURE);
}
while (1) {
int option_index = 0;
int c, nr_files;
c = getopt_long(argc, argv, "df:hl:V",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 0:
if (option_index == 1) {
handler_path = strdup(optarg);
new_path = true;
}
break;
case 'l':
snprintf(tcmu_cfg->def_log_dir, PATH_MAX, "%s", optarg);
break;
case 'f':
nr_files = atol(optarg);
if (nr_files < TCMUR_MIN_OPEN_FD || nr_files > TCMUR_MAX_OPEN_FD) {
tcmu_err("--nofile=%d should be in [%lu, %lu]\n", nr_files,
(unsigned long)TCMUR_MIN_OPEN_FD,
(unsigned long)TCMUR_MAX_OPEN_FD);
goto free_config;
}
if (tcmu_set_max_fd_limit(nr_files))
goto free_config;
break;
case 'd':
tcmu_cfg->def_log_level = TCMU_CONF_LOG_DEBUG_SCSI_CMD;
break;
case 'V':
tcmu_info("tcmu-runner %s\n", TCMUR_VERSION);
goto free_config;
default:
case 'h':
usage();
goto free_config;
}
}
/*
* The order of setting up config and logger is important, because
* the log directory may be configured via the system config file
* which will be used in logger setting up.
*/
if (tcmu_load_config(tcmu_cfg)) {
tcmu_err("Loading TCMU config failed!\n");
goto free_config;
}
if (tcmu_setup_log(tcmu_cfg->log_dir))
goto free_config;
tcmu_crit("Starting...\n");
fd = creat(TCMU_LOCK_FILE, S_IRUSR | S_IWUSR);
if (fd == -1) {
tcmu_err("creat(%s) failed: [%m]\n", TCMU_LOCK_FILE);
goto free_config;
}
lock_fd.l_type = F_WRLCK;
if (fcntl(fd, F_SETLK, &lock_fd) == -1) {
if (errno == EAGAIN) {
tcmu_err("tcmu-runner is already running...\n");
} else {
tcmu_err("fcntl(F_SETLK) on lockfile %s failed: [%m]\n",
TCMU_LOCK_FILE);
}
goto close_fd;
}
if (load_our_module() < 0) {
tcmu_err("couldn't load module\n");
goto close_fd;
}
tcmu_dbg("handler path: %s\n", handler_path);
/*
* If this is a restart we need to prevent new nl cmds from being
* sent to us until we have everything ready.
*/
tcmu_dbg("blocking netlink\n");
reset_nl_supp = true;
ret = tcmu_cfgfs_mod_param_set_u32("block_netlink", 1);
tcmu_dbg("blocking netlink done\n");
if (ret == -ENOENT) {
reset_nl_supp = false;
} else {
/*
* If it exists ignore errors and try to reset in case kernel is
* in an invalid state
*/
tcmu_dbg("resetting netlink\n");
tcmu_cfgfs_mod_param_set_u32("reset_netlink", 1);
tcmu_dbg("reset netlink done\n");
}
ret = open_handlers();
if (ret < 0) {
tcmu_err("couldn't open handlers\n");
goto close_fd;
}
tcmu_dbg("%d runner handlers found\n", ret);
ret = -1;
/*
* Convert from tcmu-runner's handler struct to libtcmu's
* handler struct, an array of which we pass in, below.
*/
darray_foreach(tmp_r_handler, g_runner_handlers) {
struct tcmulib_handler tmp_handler;
memset(&tmp_handler, 0, sizeof(tmp_handler));
tmp_handler.name = (*tmp_r_handler)->name;
tmp_handler.subtype = (*tmp_r_handler)->subtype;
tmp_handler.cfg_desc = (*tmp_r_handler)->cfg_desc;
tmp_handler.check_config = (*tmp_r_handler)->check_config;
tmp_handler.update_logdir = (*tmp_r_handler)->update_logdir;
tmp_handler.reconfig = dev_reconfig;
tmp_handler.added = dev_added;
tmp_handler.removed = dev_removed;
/*
* Can hand out a ref to an internal pointer to the
* darray b/c handlers will never be added or removed
* once open_handlers() is done.
*/
tmp_handler.hm_private = *tmp_r_handler;
darray_append(handlers, tmp_handler);
}
tcmulib_context = tcmulib_initialize(handlers.item, handlers.size);
if (!tcmulib_context) {
tcmu_err("tcmulib_initialize failed\n");
goto err_free_handlers;
}
tcmu_cfg->ctx = tcmulib_context;
if (tcmu_watch_config(tcmu_cfg)) {
tcmu_warn("Dynamic config file changes is not supported.\n");
} else {
watching_cfg = true;
}
loop = g_main_loop_new(NULL, FALSE);
if (g_unix_signal_add(SIGINT, handle_sig, loop) <= 0 ||
g_unix_signal_add(SIGTERM, handle_sig, loop) <= 0 ||
g_unix_signal_add(SIGHUP, handle_sighup, loop) <= 0) {
tcmu_err("couldn't setup signal handlers\n");
goto unwatch_cfg;
}
/* Set up event for libtcmu */
libtcmu_gio = g_io_channel_unix_new(tcmulib_get_master_fd(tcmulib_context));
watch_id = g_io_add_watch(libtcmu_gio, G_IO_IN, tcmulib_callback, tcmulib_context);
/* Set up DBus name, see callback */
reg_id = g_bus_own_name(G_BUS_TYPE_SYSTEM,
"org.kernel.TCMUService1",
G_BUS_NAME_OWNER_FLAGS_NONE,
dbus_bus_acquired,
dbus_name_acquired, // name acquired
dbus_name_lost, // name lost
NULL, // user data
NULL // user date free func
);
if (reset_nl_supp) {
tcmu_cfgfs_mod_param_set_u32("block_netlink", 0);
reset_nl_supp = false;
}
g_main_loop_run(loop);
tcmu_crit("Exiting...\n");
g_bus_unown_name(reg_id);
g_main_loop_unref(loop);
g_source_remove(watch_id);
g_io_channel_shutdown(libtcmu_gio, TRUE, NULL);
g_io_channel_unref (libtcmu_gio);
g_object_unref(manager);
ret = 0;
unwatch_cfg:
if (watching_cfg)
tcmu_unwatch_config(tcmu_cfg);
tcmulib_close(tcmulib_context);
err_free_handlers:
tcmur_unregister_all_dbus_handlers();
darray_foreach(handler, handlers) {
r_handler = handler->hm_private;
if (r_handler && r_handler->destroy)
r_handler->destroy();
}
darray_free(handlers);
close_fd:
if (reset_nl_supp)
tcmu_cfgfs_mod_param_set_u32("block_netlink", 0);
lock_fd.l_type = F_UNLCK;
if (fcntl(fd, F_SETLK, &lock_fd) == -1) {
tcmu_err("fcntl(UNLCK) on lockfile %s failed: [%m]\n",
TCMU_LOCK_FILE);
}
close(fd);
tcmu_destroy_log();
free_config:
tcmu_free_config(tcmu_cfg);
if (new_path)
free(handler_path);
if (ret)
exit(EXIT_FAILURE);
return 0;
}