/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Logical Domains (LDoms) Agents Daemon * * The LDoms agents daemon (ldmad) runs on LDoms domains and provides * information to the control domain. It is composed of a set of agents * which can send and receive messages to and from the control domain. * Each agent is registered as a domain service using the libds library, * and is able to handle requests coming from the control domain. * * The control domain sends requests to an agent as messages on the * corresponding domain service (identified by the agent name). All requests * are received by the ldmad daemon which dispatches them to the appropriate * handler function of the agent depending on the type of the message. * * After the request has been processed by the handler, the ldmad daemon sent * a reply message back to the control domain. The reply is either a result * message if the request was successfully completed, or an error message * describing the failure. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ldma.h" #define LDMA_MODULE "ldm-agent-daemon" #define LDMA_CONTROL_DOMAIN_DHDL 0 /* id of the control domain */ typedef struct ldma_connexion_t { ds_hdl_t hdl; /* connexion handle */ ds_domain_hdl_t dhdl; /* connexion domain handle */ ds_ver_t ver; /* connexion version */ } ldma_connexion_t; typedef struct ldma_agent { ldma_agent_info_t *info; /* agent information */ mutex_t conn_lock; /* connexion table lock */ ldma_connexion_t conn[LDOMS_MAX_DOMAINS]; /* connexions */ } ldma_agent_t; /* information about existing agents */ extern ldma_agent_info_t ldma_device_info; extern ldma_agent_info_t ldma_system_info; extern ldma_agent_info_t ldma_dio_info; boolean_t ldma_debug = B_FALSE; boolean_t ldma_daemon = B_FALSE; static ldma_agent_info_t *ldma_agent_infos[] = { &ldma_device_info, &ldma_system_info, &ldma_dio_info, NULL }; static char *cmdname; static pid_t daemon_pid = 0; /* * Lookup connexion in agent connexion table. */ static ldma_connexion_t * ldma_connexion_lookup(ldma_agent_t *agent, ds_hdl_t hdl) { ldma_connexion_t *connp; int i; ASSERT(MUTEX_HELD(&agent->conn_lock)); for (connp = agent->conn, i = 0; i < LDOMS_MAX_DOMAINS; i++, connp++) { if (connp->hdl == hdl) return (connp); } return (NULL); } /* * Add connextion to agent connexion table. */ static int ldma_connexion_add(ldma_agent_t *agent, ds_hdl_t hdl, ds_domain_hdl_t dhdl, ds_ver_t *verp) { ldma_connexion_t *connp; ldma_connexion_t *availp = NULL; int i; (void) mutex_lock(&agent->conn_lock); for (connp = agent->conn, i = 0; i < LDOMS_MAX_DOMAINS; i++, connp++) { if (connp->hdl == hdl) break; if (availp == NULL && connp->hdl == DS_INVALID_HDL) availp = connp; } if (i < LDOMS_MAX_DOMAINS) { (void) mutex_unlock(&agent->conn_lock); LDMA_INFO("agent %s hdl %llx already exists", agent->info->name, hdl); return (0); } if (!availp) { (void) mutex_unlock(&agent->conn_lock); LDMA_INFO("agent %s too many connections", agent->info->name); return (0); } LDMA_DBG("agent %s adding connection (%x) %llx, %llx, %d.%d", agent->info->name, availp, hdl, dhdl, verp->major, verp->minor); availp->hdl = hdl; availp->dhdl = dhdl; availp->ver = *verp; (void) mutex_unlock(&agent->conn_lock); return (1); } /* * Delete connexion from agent connexion table. */ static int ldma_connexion_delete(ldma_agent_t *agent, ds_hdl_t hdl) { ldma_connexion_t *connp; (void) mutex_lock(&agent->conn_lock); if ((connp = ldma_connexion_lookup(agent, hdl)) == NULL) { (void) mutex_unlock(&agent->conn_lock); LDMA_INFO("agent %s connection delete failed to find %llx", agent->info->name, hdl); return (0); } LDMA_DBG("agent %s deleting connection (%x) %llx", agent->info->name, connp, hdl); connp->hdl = DS_INVALID_HDL; connp->dhdl = 0; connp->ver.major = 0; connp->ver.minor = 0; (void) mutex_unlock(&agent->conn_lock); return (1); } /* * Initialize connexion table. */ static void ldma_connexion_init(ldma_agent_t *agent) { ldma_connexion_t *connp; int i; for (connp = agent->conn, i = 0; i < LDOMS_MAX_DOMAINS; i++, connp++) { connp->hdl = DS_INVALID_HDL; } } /* * Allocate a new message with the specified message number (msg_num), * message type (msg_type) and message data length (msg_dlen). Return * NULL if the allocation has failed. */ static ldma_message_header_t * ldma_alloc_msg(uint64_t msg_num, uint32_t msg_type, size_t msg_dlen) { ldma_message_header_t *msg; size_t msg_len; msg_len = LDMA_MESSAGE_SIZE(msg_dlen); msg = malloc(msg_len); if (msg == NULL) return (NULL); msg->msg_num = msg_num; msg->msg_type = msg_type; msg->msg_info = 0; return (msg); } /* * Allocate a result message (LDMA_MSG_REQ_RESULT) with the specified message * data length (msg_dlen). If the request argument is not NULL then the message * is created with the same message number as the request, otherwise the message * number is set to 0. Return NULL if the allocation has failed. */ ldma_message_header_t * ldma_alloc_result_msg(ldma_message_header_t *request, size_t msg_dlen) { uint64_t msg_num; msg_num = (request == NULL)? 0 : request->msg_num; return (ldma_alloc_msg(msg_num, LDMA_MSG_RESULT, msg_dlen)); } /* * Agent register callback. This callback is invoked when a client is registered * for using the service provided by an agent. An agent will only have one * consumer which is coming from the control domain. */ static void ldma_reg_cb(ds_hdl_t hdl, ds_cb_arg_t arg, ds_ver_t *ver, ds_domain_hdl_t dhdl) { ldma_agent_t *agent = (ldma_agent_t *)arg; char dname[LDOMS_MAX_NAME_LEN]; if (ds_dom_hdl_to_name(dhdl, dname, LDOMS_MAX_NAME_LEN) != 0) { (void) strcpy(dname, ""); } LDMA_DBG("%s: REGISTER hdl=%llx, dhdl=%llx (%s) ver=%hd.%hd", agent->info->name, hdl, dhdl, dname, ver->major, ver->minor); /* * Record client information. Access control is done on a * message-by-message basis upon receipt of the message. */ if (!ldma_connexion_add(agent, hdl, dhdl, ver)) { LDMA_INFO("agent %s failed to add connection from " "domain %s", agent->info->name, dname); } } /* * Agent unregister callback. This callback is invoked when a client is * unregistered and stops using the service provided by an agent. */ static void ldma_unreg_cb(ds_hdl_t hdl, ds_cb_arg_t arg) { ldma_agent_t *agent = (ldma_agent_t *)arg; LDMA_DBG("%s: UNREGISTER hdl=%llx", agent->info->name, hdl); if (!ldma_connexion_delete(agent, hdl)) { LDMA_INFO("agent %s failed to unregister handle %llx", agent->info->name, hdl); } } /* * Agent data callback. This callback is invoked when an agent receives a new * message from a client. Any request from a client which is not the control * domain is immediatly rejected. Otherwise the message is forwarded to the * appropriate handler function provided by the agent, depending on the message * type. */ static void ldma_data_cb(ds_hdl_t hdl, ds_cb_arg_t arg, void *buf, size_t len) { ldma_agent_t *agent = (ldma_agent_t *)arg; ldma_msg_handler_t *handler; ldma_message_header_t *request = buf; ldma_message_header_t *reply = NULL; ldma_connexion_t *connp; ds_ver_t conn_ver; ds_domain_hdl_t conn_dhdl; ldma_request_status_t status; size_t request_dlen, reply_len, reply_dlen = 0; int i; /* check the message size */ if (len < LDMA_MESSAGE_HEADER_SIZE) { LDMA_INFO("agent %s has ignored message with an invalid " "size of %d bytes", agent->info->name, len); return; } request_dlen = LDMA_MESSAGE_DLEN(len); LDMA_DBG("%s: DATA hdl=%llx, request num=%llu type=0x%x info=0x%x " "dlen=%d", agent->info->name, hdl, request->msg_num, request->msg_type, request->msg_info, request_dlen); (void) mutex_lock(&agent->conn_lock); connp = ldma_connexion_lookup(agent, hdl); if (connp != NULL) { conn_dhdl = connp->dhdl; conn_ver = connp->ver; } (void) mutex_unlock(&agent->conn_lock); /* reject any request which is not in the connexion table */ if (connp == NULL) { LDMA_DBG("%s: DATA hdl=%llx, rejecting request from a " "distrusted domain", agent->info->name, hdl); status = LDMA_REQ_DENIED; goto do_reply; } handler = NULL; for (i = 0; i < agent->info->nhandlers; i++) { if (agent->info->handlers[i].msg_type == request->msg_type) { handler = &agent->info->handlers[i]; break; } } if (handler == NULL) { /* this type of message is not defined by the agent */ LDMA_DBG("%s: DATA hdl=%llx, unknown message type %x", agent->info->name, hdl, request->msg_type); status = LDMA_REQ_NOTSUP; goto do_reply; } /* reject any request from a guest which is not allowed */ if ((conn_dhdl != LDMA_CONTROL_DOMAIN_DHDL) && (handler->msg_flags & LDMA_MSGFLG_ACCESS_ANY) == 0) { LDMA_DBG("%s: DATA hdl=%llx, rejecting request from a " "distrusted domain", agent->info->name, hdl); status = LDMA_REQ_DENIED; goto do_reply; } if (handler->msg_handler == NULL) { /* * This type of message is defined by the agent but it * has no handler. That means there is no processing to * do, the message is just ignored, but the request is * successfully completed. */ LDMA_DBG("%s: DATA hdl=%llx, no handler", agent->info->name, hdl); status = LDMA_REQ_COMPLETED; goto do_reply; } /* invoke the message handler of the agent */ status = (*handler->msg_handler)(&conn_ver, request, request_dlen, &reply, &reply_dlen); LDMA_DBG("%s: DATA hdl=%llx, handler stat=%d reply=%p rlen=%d", agent->info->name, hdl, status, (void *)reply, reply_dlen); do_reply: /* * If the handler has provided a reply message, we use it directly. * Otherwise, we build a reply depending on the status of the request. * In that case, we re-use the request buffer to build the reply * message. */ if (reply == NULL) { reply = request; reply_dlen = 0; if (status == LDMA_REQ_COMPLETED) { /* * The request was successful but no result message was * provided so we send an empty result message. */ reply->msg_type = LDMA_MSG_RESULT; reply->msg_info = 0; } else { /* * The request has failed but no error message was * provided so we send an error message based on the * request status. */ reply->msg_type = LDMA_MSG_ERROR; reply->msg_info = (status == LDMA_REQ_NOTSUP)? LDMA_MSGERR_NOTSUP : (status == LDMA_REQ_INVALID)? LDMA_MSGERR_INVALID : (status == LDMA_REQ_DENIED)? LDMA_MSGERR_DENY : LDMA_MSGERR_FAIL; } } reply_len = LDMA_MESSAGE_SIZE(reply_dlen); LDMA_DBG("%s: DATA hdl=%llx, reply num=%llu type=0x%x info=0x%x " "dlen=%d", agent->info->name, hdl, reply->msg_num, reply->msg_type, reply->msg_info, reply_dlen); if (ds_send_msg(hdl, reply, reply_len) != 0) { LDMA_ERR("agent %s has failed to send reply for request %llu", agent->info->name, request->msg_num); } if (reply != request) free(reply); } /* * Register an agent. Return 0 if the agent was successfully registered. */ static int ldma_register(ldma_agent_info_t *agent_info) { ldma_agent_t *agent; ds_capability_t ds_cap; ds_ops_t ds_ops; agent = malloc(sizeof (ldma_agent_t)); if (agent == NULL) goto register_fail; agent->info = agent_info; (void) mutex_init(&agent->conn_lock, USYNC_THREAD, NULL); ldma_connexion_init(agent); ds_cap.svc_id = agent_info->name; ds_cap.vers = agent_info->vers; ds_cap.nvers = agent_info->nvers; ds_ops.ds_reg_cb = ldma_reg_cb; ds_ops.ds_unreg_cb = ldma_unreg_cb; ds_ops.ds_data_cb = ldma_data_cb; ds_ops.cb_arg = agent; if (ds_svc_reg(&ds_cap, &ds_ops) == 0) { LDMA_INFO("agent %s registered", agent_info->name); return (0); } register_fail: LDMA_ERR("agent %s has failed to register", agent_info->name); free(agent); return (-1); } /* * Register all known agents. Return the number of agents successfully * registered. */ static int ldma_register_agents() { int count = 0; ldma_agent_info_t **agent_infop; for (agent_infop = ldma_agent_infos; *agent_infop != NULL; agent_infop++) { if (ldma_register(*agent_infop) == 0) count++; } return (count); } /*ARGSUSED*/ static void ldma_sigusr_handler(int sig, siginfo_t *sinfo, void *ucontext) { /* * The child process can send the signal before the fork() * call has returned in the parent process. So daemon_pid * may not be set yet, and we don't check the pid in that * case. */ if (sig != SIGUSR1 || sinfo->si_code != SI_USER || (daemon_pid > 0 && sinfo->si_pid != daemon_pid)) return; /* * The parent process has received a USR1 signal from the child. * This means that the daemon has correctly started and the parent * can exit. */ exit(0); } static void ldma_start(boolean_t standalone) { int stat, rv; struct sigaction action; if (!standalone) { /* * Some configuration of the daemon has to be done in the * child, but we want the parent to report if the daemon * has successfully started or not. So we setup a signal * handler, and the child will notify the parent using the * USR1 signal if the setup was successful. Otherwise the * child will exit. */ action.sa_sigaction = ldma_sigusr_handler; action.sa_flags = SA_SIGINFO; if (sigemptyset(&action.sa_mask) == -1) { LDMA_ERR("sigemptyset error (%d)", errno); exit(1); } if (sigaction(SIGUSR1, &action, NULL) == -1) { LDMA_ERR("sigaction() error (%d)", errno); exit(1); } if (sigrelse(SIGUSR1) == -1) { LDMA_ERR("sigrelse() error (%d)", errno); exit(1); } if ((daemon_pid = fork()) == -1) { LDMA_ERR("fork() error (%d)", errno); exit(1); } if (daemon_pid != 0) { /* * The parent process waits until the child exits (in * case of an error) or sends a USR1 signal (if the * daemon has correctly started). */ for (;;) { rv = waitpid(daemon_pid, &stat, 0); if ((rv == daemon_pid && WIFEXITED(stat)) || (rv == -1 && errno != EINTR)) { /* child has exited or error */ exit(1); } } } /* * Initialize child process */ if (sighold(SIGUSR1) == -1) { LDMA_ERR("sighold error (%d)", errno); exit(1); } if (sigignore(SIGUSR1) == -1) { LDMA_ERR("sigignore error (%d)", errno); exit(1); } if (setsid() == -1) { LDMA_ERR("setsid error (%d)", errno); exit(1); } if (chdir("/") == -1) { LDMA_ERR("chdir error (%d)", errno); exit(1); } (void) umask(0); /* * Initialize file descriptors. Do not touch stderr * which is initialized by SMF to point to the daemon * specific log file. */ (void) close(STDIN_FILENO); if (open("/dev/null", O_RDWR) == -1) { LDMA_ERR("open /dev/null error (%d)", errno); exit(1); } if (dup2(STDIN_FILENO, STDOUT_FILENO) == -1) { LDMA_ERR("dup2 error (%d)", errno); exit(1); } closefrom(STDERR_FILENO + 1); /* initialize logging */ openlog(cmdname, LOG_CONS | LOG_NDELAY, LOG_DAEMON); ldma_daemon = B_TRUE; } /* * Register the agents. It would be easier to do this before * daemonizing so that any start error is directly reported. But * this can not be done because agents are registered using libds * and this will subscribe the daemon to some sysevents which is * a process based subscription. Instead we notify the parent process * either by exiting, or by sending a SIGUSR1 signal. */ if (ldma_register_agents() == 0) { /* no agent registered */ LDMA_ERR("Unable to register any agent"); exit(1); } if (!standalone) { /* signal parent that startup was successful */ if (kill(getppid(), SIGUSR1) == -1) exit(1); } } static void ldma_usage() { (void) fprintf(stderr, "usage: %s\n", cmdname); } int main(int argc, char *argv[]) { int opt; boolean_t standalone = B_FALSE; cmdname = basename(argv[0]); /* disable getopt error messages */ opterr = 0; while ((opt = getopt(argc, argv, "ds")) != EOF) { switch (opt) { case 'd': ldma_debug = B_TRUE; break; case 's': standalone = B_TRUE; break; default: ldma_usage(); exit(1); } } ldma_start(standalone); /* * Loop forever. Any incoming message will be received by libds and * forwarded to the agent data callback (ldma_data_cb()) where it * will be processed. */ for (;;) { (void) pause(); } /*NOTREACHED*/ return (0); }