| /* |
| * teamd_usock.c - Teamd unix socket api |
| * Copyright (C) 2012-2015 Jiri Pirko <jiri@resnulli.us> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library 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 |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <errno.h> |
| #include <ctype.h> |
| #include <private/misc.h> |
| #include <private/list.h> |
| #include <team.h> |
| |
| #include "teamd.h" |
| #include "teamd_usock.h" |
| #include "teamd_usock_common.h" |
| #include "teamd_ctl.h" |
| |
| struct usock_ops_priv { |
| char *rcv_msg_args; |
| int sock; |
| }; |
| |
| struct usock_acc_conn { |
| struct list_item list; |
| int sock; |
| }; |
| |
| int __strdecode(char *str) |
| { |
| char *cur; |
| char *cur2; |
| bool escaped = false; |
| |
| cur = str; |
| while (*cur != '\0') { |
| if (!escaped && *cur == '\\') { |
| escaped = true; |
| } else if (escaped) { |
| escaped = false; |
| switch (*cur) { |
| case 'n': |
| *(cur - 1) = '\n'; |
| break; |
| case '\\': |
| *(cur - 1) = '\\'; |
| break; |
| default: |
| return -EINVAL; |
| } |
| cur2 = cur; |
| while (*cur2 != '\0') { |
| *cur2 = *(cur2 + 1); |
| cur2++; |
| } |
| } |
| cur++; |
| } |
| return 0; |
| } |
| |
| static int usock_op_get_args(void *ops_priv, const char *fmt, ...) |
| { |
| va_list ap; |
| struct usock_ops_priv *usock_ops_priv = ops_priv; |
| char **pstr; |
| char *str; |
| char *rest = usock_ops_priv->rcv_msg_args; |
| int err = 0; |
| |
| va_start(ap, fmt); |
| while (*fmt) { |
| switch (*fmt++) { |
| case 's': /* string */ |
| pstr = va_arg(ap, char **); |
| str = teamd_usock_msg_getline(&rest); |
| if (!str) { |
| teamd_log_err("Insufficient number of arguments in message."); |
| err = -EINVAL; |
| goto out; |
| } |
| err = __strdecode(str); |
| if (err) { |
| teamd_log_err("Corrupted argument in message."); |
| goto out; |
| } |
| *pstr = str; |
| break; |
| default: |
| teamd_log_err("Unknown argument type requested"); |
| err = -EINVAL; |
| goto out; |
| } |
| } |
| out: |
| va_end(ap); |
| return err; |
| } |
| |
| static void usock_send(struct usock_ops_priv *usock_ops_priv, |
| char *buf, size_t buflen) |
| { |
| int ret; |
| |
| ret = send(usock_ops_priv->sock, buf, buflen, 0); |
| if (ret == -1) |
| teamd_log_warn("Usock send failed: %s", strerror(errno)); |
| } |
| |
| static int usock_op_reply_err(void *ops_priv, const char *err_code, |
| const char *err_msg) |
| { |
| struct usock_ops_priv *usock_ops_priv = ops_priv; |
| char *strbuf; |
| int err; |
| |
| err = asprintf(&strbuf, "%s\n%s\n%s\n", TEAMD_USOCK_REPLY_ERR_PREFIX, |
| err_code, err_msg); |
| if (err == -1) |
| return -ENOMEM; |
| usock_send(usock_ops_priv, strbuf, strlen(strbuf)); |
| free(strbuf); |
| return 0; |
| } |
| |
| static int usock_op_reply_succ(void *ops_priv, const char *msg) |
| { |
| struct usock_ops_priv *usock_ops_priv = ops_priv; |
| char *strbuf; |
| int err; |
| |
| err = asprintf(&strbuf, "%s\n%s", TEAMD_USOCK_REPLY_SUCC_PREFIX, |
| msg ? msg : ""); |
| if (err == -1) |
| return -ENOMEM; |
| usock_send(usock_ops_priv, strbuf, strlen(strbuf)); |
| free(strbuf); |
| return 0; |
| } |
| |
| static const struct teamd_ctl_method_ops teamd_usock_ctl_method_ops = { |
| .get_args = usock_op_get_args, |
| .reply_err = usock_op_reply_err, |
| .reply_succ = usock_op_reply_succ, |
| }; |
| |
| static int process_rcv_msg(struct teamd_context *ctx, int sock, char *rcv_msg) |
| { |
| struct usock_ops_priv usock_ops_priv; |
| char *str; |
| char *rest = rcv_msg; |
| |
| str = teamd_usock_msg_getline(&rest); |
| if (!str) { |
| teamd_log_dbg(ctx, "usock: Incomplete message."); |
| return 0; |
| } |
| if (strcmp(TEAMD_USOCK_REQUEST_PREFIX, str)) { |
| teamd_log_dbg(ctx, "usock: Unsupported message type."); |
| return 0; |
| } |
| |
| str = teamd_usock_msg_getline(&rest); |
| if (!str) { |
| teamd_log_dbg(ctx, "usock: Incomplete message."); |
| return 0; |
| } |
| if (!teamd_ctl_method_exists(str)) { |
| teamd_log_dbg(ctx, "usock: Unknown method \"%s\".", str); |
| return 0; |
| } |
| |
| usock_ops_priv.sock = sock; |
| usock_ops_priv.rcv_msg_args = rest; |
| |
| teamd_log_dbg(ctx, "usock: calling method \"%s\"", str); |
| |
| return teamd_ctl_method_call(ctx, str, &teamd_usock_ctl_method_ops, |
| &usock_ops_priv); |
| } |
| |
| static void acc_conn_destroy(struct teamd_context *ctx, |
| struct usock_acc_conn *acc_conn); |
| |
| static int callback_usock_acc_conn(struct teamd_context *ctx, int events, |
| void *priv) |
| { |
| struct usock_acc_conn *acc_conn = priv; |
| char *msg = NULL; /* gcc needs this initialized */ |
| int err; |
| |
| err = teamd_usock_recv_msg(acc_conn->sock, &msg); |
| if (err == -EPIPE || err == -ECONNRESET) { |
| acc_conn_destroy(ctx, acc_conn); |
| return 0; |
| } else if (err) { |
| teamd_log_err("usock: Failed to receive data from connection."); |
| return err; |
| } |
| err = process_rcv_msg(ctx, acc_conn->sock, msg); |
| free(msg); |
| return err; |
| } |
| |
| #define USOCK_ACC_CONN_CB_NAME "usock_acc_conn" |
| |
| static int acc_conn_create(struct teamd_context *ctx, int sock) |
| { |
| struct usock_acc_conn *acc_conn; |
| int err; |
| |
| acc_conn = myzalloc(sizeof(*acc_conn)); |
| if (!acc_conn) { |
| teamd_log_err("usock: No memory to allocate new connection structure."); |
| return -ENOMEM; |
| } |
| acc_conn->sock = sock; |
| err = teamd_loop_callback_fd_add(ctx, USOCK_ACC_CONN_CB_NAME, acc_conn, |
| callback_usock_acc_conn, |
| acc_conn->sock, |
| TEAMD_LOOP_FD_EVENT_READ); |
| if (err) |
| goto free_acc_conn; |
| teamd_loop_callback_enable(ctx, USOCK_ACC_CONN_CB_NAME, acc_conn); |
| list_add(&ctx->usock.acc_conn_list, &acc_conn->list); |
| return 0; |
| |
| free_acc_conn: |
| free(acc_conn); |
| return err; |
| } |
| |
| static void acc_conn_destroy(struct teamd_context *ctx, |
| struct usock_acc_conn *acc_conn) |
| { |
| |
| teamd_loop_callback_del(ctx, USOCK_ACC_CONN_CB_NAME, acc_conn); |
| close(acc_conn->sock); |
| list_del(&acc_conn->list); |
| free(acc_conn); |
| } |
| |
| static void acc_conn_destroy_all(struct teamd_context *ctx) |
| { |
| struct usock_acc_conn *acc_conn; |
| struct usock_acc_conn *tmp; |
| |
| list_for_each_node_entry_safe(acc_conn, tmp, |
| &ctx->usock.acc_conn_list, list) |
| acc_conn_destroy(ctx, acc_conn); |
| } |
| |
| static int callback_usock(struct teamd_context *ctx, int events, void *priv) |
| { |
| struct sockaddr_un addr; |
| socklen_t alen; |
| int sock; |
| int err; |
| |
| alen = sizeof(addr); |
| sock = accept(ctx->usock.sock, &addr, &alen); |
| if (sock == -1) { |
| teamd_log_err("usock: Failed to accept connection."); |
| return -errno; |
| } |
| err = acc_conn_create(ctx, sock); |
| if (err) { |
| close(sock); |
| return err; |
| } |
| return 0; |
| } |
| |
| #define USOCK_MAX_CLIENT_COUNT 10 |
| |
| static int teamd_usock_sock_open(struct teamd_context *ctx) |
| { |
| struct sockaddr_un addr; |
| int sock; |
| int err; |
| |
| sock = socket(AF_UNIX, SOCK_SEQPACKET, 0); |
| if (sock == -1) { |
| teamd_log_err("usock: Failed to create socket."); |
| return -errno; |
| } |
| |
| err = fchmod(sock, 0700); |
| if (err == -1) { |
| teamd_log_err("usock: Failed to change socket permissions."); |
| err = -errno; |
| goto close_sock; |
| } |
| |
| addr.sun_family = AF_UNIX; |
| teamd_usock_get_sockpath(addr.sun_path, sizeof(addr.sun_path), |
| ctx->team_devname); |
| |
| teamd_log_dbg(ctx, "usock: Using sockpath \"%s\"", addr.sun_path); |
| err = unlink(addr.sun_path); |
| if (err == -1 && errno != ENOENT) { |
| teamd_log_err("usock: Failed to remove socket file."); |
| err = -errno; |
| goto close_sock; |
| } |
| |
| err = bind(sock, (struct sockaddr *) &addr, |
| strlen(addr.sun_path) + sizeof(addr.sun_family)); |
| if (err == -1) { |
| teamd_log_err("usock: Failed to bind socket."); |
| err = -errno; |
| goto close_sock; |
| } |
| listen(sock, USOCK_MAX_CLIENT_COUNT); |
| |
| ctx->usock.sock = sock; |
| ctx->usock.addr = addr; |
| return 0; |
| |
| close_sock: |
| close(sock); |
| return err; |
| } |
| |
| static void teamd_usock_sock_close(struct teamd_context *ctx) |
| { |
| close(ctx->usock.sock); |
| unlink(ctx->usock.addr.sun_path); |
| } |
| |
| #define USOCK_CB_NAME "usock" |
| |
| int teamd_usock_init(struct teamd_context *ctx) |
| { |
| int err; |
| |
| if (!ctx->usock.enabled) |
| return 0; |
| list_init(&ctx->usock.acc_conn_list); |
| err = teamd_usock_sock_open(ctx); |
| if (err) |
| return err; |
| err = teamd_loop_callback_fd_add(ctx, USOCK_CB_NAME, ctx, |
| callback_usock, ctx->usock.sock, |
| TEAMD_LOOP_FD_EVENT_READ); |
| if (err) |
| goto sock_close; |
| teamd_loop_callback_enable(ctx, USOCK_CB_NAME, ctx); |
| return 0; |
| sock_close: |
| teamd_usock_sock_close(ctx); |
| return err; |
| } |
| |
| void teamd_usock_fini(struct teamd_context *ctx) |
| { |
| if (!ctx->usock.enabled) |
| return; |
| acc_conn_destroy_all(ctx); |
| teamd_loop_callback_del(ctx, USOCK_CB_NAME, ctx); |
| teamd_usock_sock_close(ctx); |
| } |