From 17dec65a527991642833d2ba2973a81cd2f32152 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Thu, 19 Jul 2018 11:37:33 -0700 Subject: [PATCH] scoutfs: add bidirectional network messages The client and server networking code was a bit too rudimentary. The existing code only had support for the client synchronously and actively sending requests that the server could only passively respond to. We're going to need the server to be able to send requests to connected clients and it can't block waiting for responses from each one. This refactors sending and receiving in both the client and server code into shared networking code. It's built around a connection struct that then holds the message state. Both peers on the connection can send requests and send responses. The existing code only retransmitted requests down newly established connections. Requests could be processed twice. This adds robust reliability guarantees. Requests are resend until their response is received. Requests are only processed once by a given peer, regardless of the connection's transport socket. Responses are reiably resent until acknowledged. This only adds the new refactored code and disables the old unused code to keep the diff foot print minmal. A following commit will remove all the unused code. Signed-off-by: Zach Brown --- kmod/src/Makefile | 2 +- kmod/src/client.c | 231 ++++-- kmod/src/counters.h | 12 + kmod/src/format.h | 85 ++- kmod/src/net.c | 1495 ++++++++++++++++++++++++++++++++++++++ kmod/src/net.h | 61 ++ kmod/src/scoutfs_trace.h | 60 +- kmod/src/server.c | 401 +++++----- kmod/src/server.h | 16 +- kmod/src/super.c | 3 + kmod/src/super.h | 2 + 11 files changed, 2025 insertions(+), 343 deletions(-) create mode 100644 kmod/src/net.c create mode 100644 kmod/src/net.h diff --git a/kmod/src/Makefile b/kmod/src/Makefile index c9375a0b..237e0f11 100644 --- a/kmod/src/Makefile +++ b/kmod/src/Makefile @@ -7,7 +7,7 @@ CFLAGS_scoutfs_trace.o = -I$(src) # define_trace.h double include scoutfs-y += bio.o block.o btree.o client.o compact.o counters.o data.o dir.o \ export.o extents.o file.o inode.o ioctl.o item.o lock.o \ - manifest.o msg.o options.o per_task.o seg.o server.o \ + manifest.o msg.o net.o options.o per_task.o seg.o server.o \ scoutfs_trace.o sock.o sort_priv.o super.o sysfs.o trans.o \ triggers.o xattr.o diff --git a/kmod/src/client.c b/kmod/src/client.c index fedaee8d..c7e2cdac 100644 --- a/kmod/src/client.c +++ b/kmod/src/client.c @@ -32,24 +32,18 @@ #include "msg.h" #include "server.h" #include "client.h" -#include "sock.h" +#include "net.h" #include "endian_swap.h" /* - * Client callers block sending requests to the server. Senders connect - * and send down the socket in their blocked context under a mutex. - * Once a socket is connected recv work is fired up. Destroying a - * socket shuts down the socket and cancels the work. - * - * Clients are responsible for resending their requests after - * reconnecting to a new socket. These new socket connections might be - * connecting to the same server. The message sending and processing - * paths are responsible for dealing with duplicate requests. + * The client always maintains a connection to the server. It reads the + * super to get the address it should try and connect to. */ #define SIN_FMT "%pIS:%u" #define SIN_ARG(sin) sin, be16_to_cpu((sin)->sin_port) +#if 0 /* * Have a pretty aggressive keepalive timeout of around 10 seconds. The * TCP keepalives are being processed out of task context so they should @@ -59,6 +53,7 @@ #define KEEPCNT 3 #define KEEPIDLE 7 #define KEEPINTVL 1 +#endif /* @@ -72,30 +67,17 @@ struct client_info { struct super_block *sb; - /* spinlock protects quick critical sections between send,recv,umount */ - spinlock_t recv_lock; - struct rb_root sender_root; + struct scoutfs_net_connection *conn; + atomic_t shutting_down; - /* the sock mutex serializes connecting and sending */ - struct mutex send_mutex; - bool recv_shutdown; - u64 next_id; - u64 sock_gen; - struct socket *sock; - struct sockaddr_in peername; - struct sockaddr_in sockname; - - /* blocked senders sit on a waitq that's woken for resends */ - wait_queue_head_t waitq; + struct workqueue_struct *workq; + struct delayed_work connect_dwork; /* connection timeouts are tracked across attempts */ unsigned long conn_retry_ms; - unsigned long conn_retry_limit_j; - - struct workqueue_struct *recv_wq; - struct work_struct recv_work; }; +#if 0 struct waiting_sender { struct rb_node node; struct task_struct *task; @@ -213,14 +195,20 @@ static void scoutfs_client_recv_func(struct work_struct *work) kfree(rx); } +#endif -static void reset_connect_timeouts(struct client_info *client) +static void reset_connect_timeout(struct client_info *client) { client->conn_retry_ms = CONN_RETRY_MIN_MS; - client->conn_retry_limit_j = jiffies + CONN_RETRY_LIMIT_J; } +static void grow_connect_timeout(struct client_info *client) +{ + client->conn_retry_ms = min(client->conn_retry_ms * 2, + CONN_RETRY_MAX_MS); +} +#if 0 /* * Clients who try to send and don't see a connected socket call here to * connect to the server. They get the server address and try to @@ -527,6 +515,7 @@ static int client_request(struct client_info *client, int type, void *data, return ret; } +#endif /* * Ask for a new run of allocated inode numbers. The server can return @@ -540,8 +529,10 @@ int scoutfs_client_alloc_inodes(struct super_block *sb, u64 count, __le64 lecount = cpu_to_le64(count); int ret; - ret = client_request(client, SCOUTFS_NET_ALLOC_INODES, - &lecount, sizeof(lecount), &ial, sizeof(ial)); + ret = scoutfs_net_sync_request(sb, client->conn, + SCOUTFS_NET_CMD_ALLOC_INODES, + &lecount, sizeof(lecount), + &ial, sizeof(ial)); if (ret == 0) { *ino = le64_to_cpu(ial.ino); *nr = le64_to_cpu(ial.nr); @@ -568,8 +559,10 @@ int scoutfs_client_alloc_extent(struct super_block *sb, u64 blocks, u64 *start, struct scoutfs_net_extent nex; int ret; - ret = client_request(client, SCOUTFS_NET_ALLOC_EXTENT, - &leblocks, sizeof(leblocks), &nex, sizeof(nex)); + ret = scoutfs_net_sync_request(sb, client->conn, + SCOUTFS_NET_CMD_ALLOC_EXTENT, + &leblocks, sizeof(leblocks), + &nex, sizeof(nex)); if (ret == 0) { if (nex.len == 0) { ret = -ENOSPC; @@ -590,8 +583,9 @@ int scoutfs_client_free_extents(struct super_block *sb, bytes = SCOUTFS_NET_EXTENT_LIST_BYTES(le64_to_cpu(nexl->nr)); - return client_request(client, SCOUTFS_NET_FREE_EXTENTS, - nexl, bytes, NULL, 0); + return scoutfs_net_sync_request(sb, client->conn, + SCOUTFS_NET_CMD_FREE_EXTENTS, + nexl, bytes, NULL, 0); } int scoutfs_client_alloc_segno(struct super_block *sb, u64 *segno) @@ -600,8 +594,9 @@ int scoutfs_client_alloc_segno(struct super_block *sb, u64 *segno) __le64 lesegno; int ret; - ret = client_request(client, SCOUTFS_NET_ALLOC_SEGNO, NULL, 0, - &lesegno, sizeof(lesegno)); + ret = scoutfs_net_sync_request(sb, client->conn, + SCOUTFS_NET_CMD_ALLOC_SEGNO, + NULL, 0, &lesegno, sizeof(lesegno)); if (ret == 0) { if (lesegno == 0) ret = -ENOSPC; @@ -622,8 +617,9 @@ int scoutfs_client_record_segment(struct super_block *sb, scoutfs_seg_init_ment(&ment, level, seg); scoutfs_init_ment_to_net(&net_ment, &ment); - return client_request(client, SCOUTFS_NET_RECORD_SEGMENT, &net_ment, - sizeof(net_ment), NULL, 0); + return scoutfs_net_sync_request(sb, client->conn, + SCOUTFS_NET_CMD_RECORD_SEGMENT, + &net_ment, sizeof(net_ment), NULL, 0); } int scoutfs_client_advance_seq(struct super_block *sb, u64 *seq) @@ -633,8 +629,10 @@ int scoutfs_client_advance_seq(struct super_block *sb, u64 *seq) __le64 after; int ret; - ret = client_request(client, SCOUTFS_NET_ADVANCE_SEQ, - &before, sizeof(before), &after, sizeof(after)); + ret = scoutfs_net_sync_request(sb, client->conn, + SCOUTFS_NET_CMD_ADVANCE_SEQ, + &before, sizeof(before), + &after, sizeof(after)); if (ret == 0) *seq = le64_to_cpu(after); @@ -647,8 +645,9 @@ int scoutfs_client_get_last_seq(struct super_block *sb, u64 *seq) __le64 last_seq; int ret; - ret = client_request(client, SCOUTFS_NET_GET_LAST_SEQ, - NULL, 0, &last_seq, sizeof(last_seq)); + ret = scoutfs_net_sync_request(sb, client->conn, + SCOUTFS_NET_CMD_GET_LAST_SEQ, + NULL, 0, &last_seq, sizeof(last_seq)); if (ret == 0) *seq = le64_to_cpu(last_seq); @@ -660,8 +659,10 @@ int scoutfs_client_get_manifest_root(struct super_block *sb, { struct client_info *client = SCOUTFS_SB(sb)->client_info; - return client_request(client, SCOUTFS_NET_GET_MANIFEST_ROOT, - NULL, 0, root, sizeof(struct scoutfs_btree_root)); + return scoutfs_net_sync_request(sb, client->conn, + SCOUTFS_NET_CMD_GET_MANIFEST_ROOT, + NULL, 0, root, + sizeof(struct scoutfs_btree_root)); } int scoutfs_client_statfs(struct super_block *sb, @@ -669,53 +670,147 @@ int scoutfs_client_statfs(struct super_block *sb, { struct client_info *client = SCOUTFS_SB(sb)->client_info; - return client_request(client, SCOUTFS_NET_STATFS, NULL, 0, nstatfs, - sizeof(struct scoutfs_net_statfs)); + return scoutfs_net_sync_request(sb, client->conn, + SCOUTFS_NET_CMD_STATFS, NULL, 0, + nstatfs, + sizeof(struct scoutfs_net_statfs)); +} + +/* + * Attempt to connect to the listening address that the server wrote in + * the super block. We keep trying indefinitely with an increasing + * delay if we fail to either read the address or connect to it. + * + * We're careful to only ever have one connection attempt in flight. We + * only queue this work on mount, on error, or from the connection + * callback. + */ +static void scoutfs_client_connect_worker(struct work_struct *work) +{ + struct client_info *client = container_of(work, struct client_info, + connect_dwork.work); + struct super_block *sb = client->sb; + struct scoutfs_super_block super; + struct sockaddr_in sin; + int ret; + + ret = scoutfs_read_super(sb, &super); + if (ret) + goto out; + + if (super.server_addr.addr == cpu_to_le32(INADDR_ANY)) { + ret = -EADDRNOTAVAIL; + goto out; + } + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = le32_to_be32(super.server_addr.addr); + sin.sin_port = le16_to_be16(super.server_addr.port); + + scoutfs_net_connect(sb, client->conn, &sin, client->conn_retry_ms); + ret = 0; +out: + if (ret && !atomic_read(&client->shutting_down)) { + queue_delayed_work(client->workq, &client->connect_dwork, + msecs_to_jiffies(client->conn_retry_ms)); + grow_connect_timeout(client); + } +} + +static void client_notify_up(struct super_block *sb, + struct scoutfs_net_connection *conn) +{ + struct client_info *client = SCOUTFS_SB(sb)->client_info; + + reset_connect_timeout(client); +} + +/* + * Called when either a connect attempt or established connection times + * out and fails. + */ +static void client_notify_down(struct super_block *sb, + struct scoutfs_net_connection *conn) +{ + struct client_info *client = SCOUTFS_SB(sb)->client_info; + + if (!atomic_read(&client->shutting_down)) { + queue_delayed_work(client->workq, &client->connect_dwork, + msecs_to_jiffies(client->conn_retry_ms)); + grow_connect_timeout(client); + } } int scoutfs_client_setup(struct super_block *sb) { struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); struct client_info *client; + int ret; client = kzalloc(sizeof(struct client_info), GFP_KERNEL); - if (!client) - return -ENOMEM; + if (!client) { + ret = -ENOMEM; + goto out; + } + sbi->client_info = client; client->sb = sb; - spin_lock_init(&client->recv_lock); - client->sender_root = RB_ROOT; - mutex_init(&client->send_mutex); - init_waitqueue_head(&client->waitq); - INIT_WORK(&client->recv_work, scoutfs_client_recv_func); - reset_connect_timeouts(client); + atomic_set(&client->shutting_down, 0); + INIT_DELAYED_WORK(&client->connect_dwork, + scoutfs_client_connect_worker); - client->recv_wq = alloc_workqueue("scoutfs_client_recv", WQ_UNBOUND, 1); - if (!client->recv_wq) { - kfree(client); - return -ENOMEM; + /* client doesn't process any incoming requests yet */ + client->conn = scoutfs_net_alloc_conn(sb, client_notify_up, + client_notify_down, NULL, + "client"); + if (!client->conn) { + ret = -ENOMEM; + goto out; } - sbi->client_info = client; - return 0; + client->workq = alloc_workqueue("scoutfs_client_workq", WQ_UNBOUND, 1); + if (!client->workq) { + ret = -ENOMEM; + goto out; + } + + reset_connect_timeout(client); + /* delay initial connect to give a local server some time to setup */ + queue_delayed_work(client->workq, &client->connect_dwork, + msecs_to_jiffies(client->conn_retry_ms)); + ret = 0; + +out: + if (ret) + scoutfs_client_destroy(sb); + return ret; } /* - * There must be no more callers to the client send functions by the - * time we get here. We just need to free the socket if it's - * still sitting around. + * There must be no more callers to the client request functions by the + * time we get here. */ void scoutfs_client_destroy(struct super_block *sb) { struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); struct client_info *client = SCOUTFS_SB(sb)->client_info; + struct scoutfs_net_connection *conn; if (client) { - shutdown_sock_sync(client); + /* stop notify_down from queueing connect work */ + atomic_set(&client->shutting_down, 1); - cancel_work_sync(&client->recv_work); - destroy_workqueue(client->recv_wq); + /* make sure worker isn't using the conn */ + cancel_delayed_work_sync(&client->connect_dwork); + /* make racing conn use explode */ + conn = client->conn; + client->conn = NULL; + scoutfs_net_free_conn(sb, conn); + + if (client->workq) + destroy_workqueue(client->workq); kfree(client); sbi->client_info = NULL; } diff --git a/kmod/src/counters.h b/kmod/src/counters.h index 6d26c4f7..94d175c2 100644 --- a/kmod/src/counters.h +++ b/kmod/src/counters.h @@ -100,6 +100,18 @@ EXPAND_COUNTER(manifest_compact_migrate) \ EXPAND_COUNTER(manifest_hard_stale_error) \ EXPAND_COUNTER(manifest_read_excluded_key) \ + EXPAND_COUNTER(net_dropped_ack) \ + EXPAND_COUNTER(net_dropped_response) \ + EXPAND_COUNTER(net_dropped_request) \ + EXPAND_COUNTER(net_send_bytes) \ + EXPAND_COUNTER(net_send_error) \ + EXPAND_COUNTER(net_send_messages) \ + EXPAND_COUNTER(net_recv_bytes) \ + EXPAND_COUNTER(net_recv_error) \ + EXPAND_COUNTER(net_recv_invalid_message) \ + EXPAND_COUNTER(net_recv_messages) \ + EXPAND_COUNTER(net_unknown_message) \ + EXPAND_COUNTER(net_unknown_request) \ EXPAND_COUNTER(seg_alloc) \ EXPAND_COUNTER(seg_free) \ EXPAND_COUNTER(seg_shrink) \ diff --git a/kmod/src/format.h b/kmod/src/format.h index e1dfc9a2..6f2d46f8 100644 --- a/kmod/src/format.h +++ b/kmod/src/format.h @@ -353,8 +353,6 @@ struct scoutfs_inet_addr { __le16 port; } __packed; -#define SCOUTFS_DEFAULT_PORT 12345 - struct scoutfs_super_block { struct scoutfs_block_header hdr; __le64 id; @@ -506,6 +504,10 @@ struct scoutfs_lock_name { * messages over the wire. */ +/* + * Greetings verify identity of communicating nodes. The sender + * sends their credentials and the receiver verifies them. + */ struct scoutfs_net_greeting { __le64 fsid; __le64 format_hash; @@ -517,15 +519,70 @@ struct scoutfs_net_greeting { * type is strictly redundant in the reply because the id will find the * send but we include it in both packets to make it easier to observe * replies without having the id from their previous request. + * + * Error is only set to a translated errno on response messages and + * data_len will be 0. */ struct scoutfs_net_header { __le64 id; __le16 data_len; - __u8 type; - __u8 status; + __u8 msg; + __u8 cmd; + __u8 error; __u8 data[0]; } __packed; +/* + * Greetings are the first messages sent down every newly established + * socket on the connection. Every other message gets a unique + * increasing id over the life time of the connection. + */ +#define SCOUTFS_NET_ID_GREETING 1 + +enum { + SCOUTFS_NET_MSG_REQUEST = 0, + SCOUTFS_NET_MSG_RESPONSE, + SCOUTFS_NET_MSG_ACK, + SCOUTFS_NET_MSG_UNKNOWN, +}; + +enum { + SCOUTFS_NET_CMD_GREETING = 0, + SCOUTFS_NET_CMD_ALLOC_INODES, + SCOUTFS_NET_CMD_ALLOC_EXTENT, + SCOUTFS_NET_CMD_FREE_EXTENTS, + SCOUTFS_NET_CMD_ALLOC_SEGNO, + SCOUTFS_NET_CMD_RECORD_SEGMENT, + SCOUTFS_NET_CMD_ADVANCE_SEQ, + SCOUTFS_NET_CMD_GET_LAST_SEQ, + SCOUTFS_NET_CMD_GET_MANIFEST_ROOT, + SCOUTFS_NET_CMD_STATFS, + SCOUTFS_NET_CMD_UNKNOWN, +}; + +/* + * Define a macro to evaluate another macro for each of the errnos we + * translate over the wire. This lets us keep our enum in sync with the + * mapping arrays to and from host errnos. + */ +#define EXPAND_EACH_NET_ERRNO \ + EXPAND_NET_ERRNO(ENOENT) \ + EXPAND_NET_ERRNO(ENOMEM) \ + EXPAND_NET_ERRNO(EIO) \ + EXPAND_NET_ERRNO(ENOSPC) \ + EXPAND_NET_ERRNO(EINVAL) + +#undef EXPAND_NET_ERRNO +#define EXPAND_NET_ERRNO(which) SCOUTFS_NET_ERR_##which, +enum { + SCOUTFS_NET_ERR_NONE = 0, + EXPAND_EACH_NET_ERRNO + SCOUTFS_NET_ERR_UNKNOWN, +}; + +/* arbitrarily chosen to be safely less than mss and allow 1k with header */ +#define SCOUTFS_NET_MAX_DATA_LEN 1100 + /* * When there's no more free inodes this will be sent with ino = ~0 and * nr = 0. @@ -586,26 +643,6 @@ struct scoutfs_net_extent_list { #define SCOUTFS_COMPACTION_MAX_UPDATE \ (2 * (SCOUTFS_COMPACTION_MAX_INPUT + SCOUTFS_COMPACTION_SLOP)) -enum { - SCOUTFS_NET_ALLOC_INODES = 0, - SCOUTFS_NET_ALLOC_EXTENT, - SCOUTFS_NET_FREE_EXTENTS, - SCOUTFS_NET_ALLOC_SEGNO, - SCOUTFS_NET_RECORD_SEGMENT, - SCOUTFS_NET_ADVANCE_SEQ, - SCOUTFS_NET_GET_LAST_SEQ, - SCOUTFS_NET_GET_MANIFEST_ROOT, - SCOUTFS_NET_STATFS, - SCOUTFS_NET_UNKNOWN, -}; - -enum { - SCOUTFS_NET_STATUS_REQUEST = 0, - SCOUTFS_NET_STATUS_SUCCESS, - SCOUTFS_NET_STATUS_ERROR, - SCOUTFS_NET_STATUS_UNKNOWN, -}; - /* * Scoutfs file handle structure - this can be copied out to userspace * via open by handle or put on the wire from NFS. diff --git a/kmod/src/net.c b/kmod/src/net.c new file mode 100644 index 00000000..1ab0b377 --- /dev/null +++ b/kmod/src/net.c @@ -0,0 +1,1495 @@ +/* + * Copyright (C) 2018 Versity Software, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License v2 as published by the Free Software Foundation. + * + * This program 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 + * General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "format.h" +#include "counters.h" +#include "inode.h" +#include "btree.h" +#include "manifest.h" +#include "seg.h" +#include "compact.h" +#include "scoutfs_trace.h" +#include "msg.h" +#include "net.h" +#include "endian_swap.h" + + +/* + * scoutfs networking reliably delivers requests and responses between + * nodes. + * + * Nodes decide to be either a connecting client or a listening server. + * Both set up a connection and specify the set of request commands they + * can process. + * + * The networking core maintains reliable request processing as the + * nodes reconnect. Requests are resent as connections are + * re-established until a response is received. Responses are resent + * until an ack is received. The connections are not bound to the + * addresses of the underlying socket transports and can reliably + * deliver messages across renumbering. + * + * XXX: + * - assign node_ids and validate with the greeting + * - defer accepted conn destruction until reconnect timeout + * - trace command and response data payloads + * - checksum message contents? + * - explicit shutdown message to free accepted, timeout and fence otherwise + * - shutdown server if accept can't alloc resources for new conn? + */ + +/* + * A connection's shutdown work executes in its own workqueue so that the + * work can free the connection's workq. + */ +struct net_info { + struct workqueue_struct *shutdown_workq; +}; + +struct scoutfs_net_connection { + struct super_block *sb; + scoutfs_net_notify_t notify_up; + scoutfs_net_notify_t notify_down; + scoutfs_net_request_t *req_funcs; + + spinlock_t lock; + + unsigned long valid_greeting:1, /* other commands can proceed */ + established:1, /* added sends queue send work */ + shutting_down:1; /* shutdown work has been queued */ + + struct sockaddr_in connect_sin; + unsigned long connect_timeout_ms; + + struct socket *sock; + struct sockaddr_in sockname; + struct sockaddr_in peername; + + struct list_head accepted_head; + struct scoutfs_net_connection *listening_conn; + struct list_head accepted_list; + wait_queue_head_t accepted_waitq; + + u64 next_send_id; + u64 last_proc_id; + struct list_head send_queue; + struct list_head resend_queue; + + struct workqueue_struct *workq; + struct work_struct listen_work; + struct work_struct connect_work; + struct work_struct send_work; + struct work_struct recv_work; + struct work_struct shutdown_work; + /* message_recv proc_work also executes in the conn workq */ +}; + +/* + * Messages to be sent are allocated and put on the send queue. + * + * Request and response messages are put on the resend queue until their + * response or ack messages are received, respectively, and they can be + * freed. + * + * The send worker is the only context that references messages while + * not holding the lock. It does this while blocking sending the + * message down the socket. To free messages we mark them dead and have + * the send worker free them while under the lock so that we don't have + * to risk freeing messages from under the unlocked send worker. + */ +struct message_send { + struct list_head head; + scoutfs_net_response_t resp_func; + void *resp_data; + unsigned long dead:1; + struct scoutfs_net_header nh; +}; + +/* + * Incoming received messages are processed in concurrent blocking work + * contexts. + */ +struct message_recv { + struct scoutfs_net_connection *conn; + struct work_struct proc_work; + struct scoutfs_net_header nh; +}; + +#define DEFINE_CONN_FROM_WORK(name, work, member) \ + struct scoutfs_net_connection *name = \ + container_of(work, struct scoutfs_net_connection, member) + +/* Total message bytes including header and payload */ +static int nh_bytes(unsigned int data_len) +{ + return offsetof(struct scoutfs_net_header, data[data_len]); +} + +static struct message_send *search_list(struct scoutfs_net_connection *conn, + struct list_head *list, + u8 msg, u8 cmd, u64 id) +{ + struct message_send *msend; + + assert_spin_locked(&conn->lock); + + list_for_each_entry(msend, list, head) { + if (msend->nh.msg == msg && msend->nh.cmd == cmd && + le64_to_cpu(msend->nh.id) == id) + return msend; + } + + return NULL; +} + +/* + * Find an active send on the lists. It's almost certainly waiting on + * the resend queue but it could be actively being sent. + */ +static struct message_send *find_send(struct scoutfs_net_connection *conn, + u8 msg, u8 cmd, u64 id) +{ + struct message_send *msend; + + msend = search_list(conn, &conn->resend_queue, msg, cmd, id) ?: + search_list(conn, &conn->send_queue, msg, cmd, id); + if (msend && msend->dead) + msend = NULL; + return msend; +} + +/* + * Complete a send message by moving it to the send queue and marking it + * to be freed. + * + * Request messages have their response function called. Their response + * processing can return an error if the response is invalid. The + * request message is still removed and freed in that case. + */ +static int complete_send(struct scoutfs_net_connection *conn, + struct message_send *msend, + void *resp, unsigned int resp_len, int error) +{ + struct super_block *sb = conn->sb; + int ret = 0; + + if (WARN_ON_ONCE(msend->dead) || + WARN_ON_ONCE(list_empty(&msend->head))) + return -EINVAL; + + assert_spin_locked(&conn->lock); + + if (msend->resp_func) + ret = msend->resp_func(sb, conn, resp, resp_len, error, + msend->resp_data); + msend->dead = 1; + list_move(&msend->head, &conn->send_queue); + queue_work(conn->workq, &conn->send_work); + + return ret; +} + + +/* + * Translate a positive error on the wire to a negative host errno. + */ +static inline int net_err_to_host(u8 net_err) +{ +#undef EXPAND_NET_ERRNO +#define EXPAND_NET_ERRNO(which) [SCOUTFS_NET_ERR_##which] = which, + static u8 host_errnos[] = { + EXPAND_EACH_NET_ERRNO + }; + + if (net_err == SCOUTFS_NET_ERR_NONE) + return 0; + + if (net_err < ARRAY_SIZE(host_errnos) && host_errnos[net_err]) + return -host_errnos[net_err]; + + return -EINVAL; +} + +/* + * Translate a negative host errno to a positive error on the wire. + * + * The caller is our kernel run time which should have been careful with + * errnos. But mistakes happen so let's holler and translate unknown + * errors. A fun bit of trivia: sparse's array bounds detection once + * got confused by conditions in WARN_ON_ONCE(); + */ +static inline u8 net_err_from_host(struct super_block *sb, int error) +{ +#undef EXPAND_NET_ERRNO +#define EXPAND_NET_ERRNO(which) [which] = SCOUTFS_NET_ERR_##which, + static u8 net_errs[] = { + EXPAND_EACH_NET_ERRNO + }; + int ind = -error; + + if (error == 0) + return SCOUTFS_NET_ERR_NONE; + + if (error > 0 || ind >= ARRAY_SIZE(net_errs) || net_errs[ind] == 0) { + static bool warned; + if (!warned) { + warned = 1; + scoutfs_warn(sb, "host errno %d sent as EINVAL\n", + error); + } + + return -EINVAL; + } + + return net_errs[ind]; +} + +/* + * Shutdown the connection. This is called by many contexts including + * work that most complete to finish shutting down. We queue specific + * shutdown work that can wait on all the connection's other work. + * We're sure to only queue the shutdown work once. + */ +static void shutdown_conn_locked(struct scoutfs_net_connection *conn) +{ + struct super_block *sb = conn->sb; + struct net_info *ninf = SCOUTFS_SB(sb)->net_info; + + assert_spin_locked(&conn->lock); + + if (!conn->shutting_down) { + conn->established = 0; + conn->shutting_down = 1; + queue_work(ninf->shutdown_workq, &conn->shutdown_work); + } +} + +static void shutdown_conn(struct scoutfs_net_connection *conn) +{ + spin_lock(&conn->lock); + shutdown_conn_locked(conn); + spin_unlock(&conn->lock); +} + +/* + * Allocate a message and put it on the send queue. + * + * A 0 id means that we'll assign the next id from the connection once + * we hold the lock and is only valid for sending requests. + * + * This can race with connections that are either starting up and + * shutting down. We only directly queue the send work if the + * connection has passed the greeting and isn't being shut down. At all + * other times we add new sends to the resend queue. + */ +static int submit_send(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 msg, u8 cmd, u64 id, u8 net_err, + void *data, u16 data_len, + scoutfs_net_response_t resp_func, void *resp_data, + u64 *id_ret) +{ + struct message_send *msend; + + if (WARN_ON_ONCE(msg >= SCOUTFS_NET_MSG_UNKNOWN) || + WARN_ON_ONCE(cmd >= SCOUTFS_NET_CMD_UNKNOWN) || + WARN_ON_ONCE(net_err >= SCOUTFS_NET_ERR_UNKNOWN) || + WARN_ON_ONCE(data_len > SCOUTFS_NET_MAX_DATA_LEN) || + WARN_ON_ONCE(data_len && (!data || net_err)) || + WARN_ON_ONCE(net_err && (msg != SCOUTFS_NET_MSG_RESPONSE)) || + WARN_ON_ONCE(id == 0 && msg != SCOUTFS_NET_MSG_REQUEST) || + WARN_ON_ONCE((cmd == SCOUTFS_NET_CMD_GREETING) != + (id == SCOUTFS_NET_ID_GREETING))) + return -EINVAL; + + msend = kmalloc(offsetof(struct message_send, + nh.data[data_len]), GFP_NOFS); + if (!msend) + return -ENOMEM; + + spin_lock(&conn->lock); + + msend->resp_func = resp_func; + msend->resp_data = resp_data; + msend->dead = 0; + + if (id == 0) + id = conn->next_send_id++; + msend->nh.id = cpu_to_le64(id); + msend->nh.msg = msg; + msend->nh.cmd = cmd; + msend->nh.error = net_err; + msend->nh.data_len = cpu_to_le16(data_len); + if (data_len) + memcpy(msend->nh.data, data, data_len); + + if (conn->established && + (conn->valid_greeting || cmd == SCOUTFS_NET_CMD_GREETING)) { + list_add_tail(&msend->head, &conn->send_queue); + queue_work(conn->workq, &conn->send_work); + } else { + list_add_tail(&msend->head, &conn->resend_queue); + } + + if (id_ret) + *id_ret = le64_to_cpu(msend->nh.id); + + spin_unlock(&conn->lock); + + return 0; +} + +/* + * Messages can flow once we receive a valid greeting from our peer. + * Response callers are already called under the lock, request callers + * need to acquire it. + * + * At this point greeting request processing has queued the greeting + * response message on the send queue. All the sends waiting to be + * resent need to be added to the end of the send queue after the + * greeting response. Greeting acks are sent differently and can be + * received after resend messages. + */ +static void saw_valid_greeting(struct scoutfs_net_connection *conn) +{ + struct super_block *sb = conn->sb; + + assert_spin_locked(&conn->lock); + + conn->valid_greeting = 1; + if (conn->notify_up) + conn->notify_up(sb, conn); + list_splice_tail_init(&conn->resend_queue, &conn->send_queue); + queue_work(conn->workq, &conn->send_work); +} + +static int greeting_response(struct super_block *sb, + struct scoutfs_net_connection *conn, + void *resp, unsigned int resp_len, int error, + void *data) +{ + struct scoutfs_net_greeting *gr = resp; + struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super; + int ret = 0; + + if (error) { + ret = error; + goto out; + } + + if (resp_len != sizeof(struct scoutfs_net_greeting)) { + ret = -EINVAL; + goto out; + } + + if (gr->fsid != super->id) { + scoutfs_warn(sb, "server "SIN_FMT" has fsid 0x%llx, expected 0x%llx", + SIN_ARG(&conn->peername), + le64_to_cpu(gr->fsid), + le64_to_cpu(super->id)); + ret = -EINVAL; + goto out; + } + + if (gr->format_hash != super->format_hash) { + scoutfs_warn(sb, "server "SIN_FMT" has format hash 0x%llx, expected 0x%llx", + SIN_ARG(&conn->peername), + le64_to_cpu(gr->format_hash), + le64_to_cpu(super->format_hash)); + ret = -EINVAL; + goto out; + } + + saw_valid_greeting(conn); + +out: + return ret; +} + +/* + * Process an incoming greeting request. We try to send responses to + * failed greetings so that the sender can log some detail before + * shutting down. A failure to send a greeting response shuts down the + * connection. + */ +static int greeting_request(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id, void *arg, u16 arg_len) +{ + struct scoutfs_net_greeting *gr = arg; + struct scoutfs_net_greeting greet; + struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super; + int ret = 0; + + if (arg_len != sizeof(struct scoutfs_net_greeting)) { + ret = -EINVAL; + goto out; + } + + if (gr->fsid != super->id) { + scoutfs_warn(sb, "client "SIN_FMT" has fsid 0x%llx, expected 0x%llx", + SIN_ARG(&conn->peername), + le64_to_cpu(gr->fsid), + le64_to_cpu(super->id)); + ret = -EINVAL; + goto out; + } + + if (gr->format_hash != super->format_hash) { + scoutfs_warn(sb, "client "SIN_FMT" has format hash 0x%llx, expected 0x%llx", + SIN_ARG(&conn->peername), + le64_to_cpu(gr->format_hash), + le64_to_cpu(super->format_hash)); + ret = -EINVAL; + goto out; + } + + greet.fsid = super->id; + greet.format_hash = super->format_hash; +out: + ret = scoutfs_net_response(sb, conn, cmd, id, ret, + &greet, sizeof(greet)); + if (ret == 0) { + spin_lock(&conn->lock); + saw_valid_greeting(conn); + spin_unlock(&conn->lock); + } + return ret; +} + + +/* + * Process an incoming response. The greeting should ensure that the + * sender won't send us unknown commands. We return an error if we see + * an unknown command because the greeting should agree on an understood + * protocol. The request function sends a response and returns an error + * if they couldn't. + */ +static int process_request(struct scoutfs_net_connection *conn, + struct message_recv *mrecv) +{ + struct super_block *sb = conn->sb; + scoutfs_net_request_t req_func = NULL; + + if (conn->listening_conn != NULL && + mrecv->nh.cmd == SCOUTFS_NET_CMD_GREETING) { + req_func = greeting_request; + } else if (mrecv->nh.cmd < SCOUTFS_NET_CMD_UNKNOWN) { + req_func = conn->req_funcs[mrecv->nh.cmd]; + } if (req_func == NULL) { + scoutfs_inc_counter(sb, net_unknown_request); + return -EINVAL; + } + + return req_func(sb, conn, mrecv->nh.cmd, le64_to_cpu(mrecv->nh.id), + mrecv->nh.data, le16_to_cpu(mrecv->nh.data_len)); +} + +/* + * An incoming response finds the queued request and calls its response + * function. We call the function and remove it from the lists before + * trying to send the ack so that we only call the response function + * once. Future duplicate responses will just resend the ack in + * response. + */ +static int process_response(struct scoutfs_net_connection *conn, + struct message_recv *mrecv) +{ + struct super_block *sb = conn->sb; + struct message_send *msend; + int ret = 0; + + spin_lock(&conn->lock); + + msend = find_send(conn, SCOUTFS_NET_MSG_REQUEST, mrecv->nh.cmd, + le64_to_cpu(mrecv->nh.id)); + if (msend) + ret = complete_send(conn, msend, mrecv->nh.data, + le16_to_cpu(mrecv->nh.data_len), + net_err_to_host(mrecv->nh.error)); + else + scoutfs_inc_counter(sb, net_dropped_response); + + spin_unlock(&conn->lock); + + if (ret == 0) + ret = submit_send(sb, conn, SCOUTFS_NET_MSG_ACK, mrecv->nh.cmd, + le64_to_cpu(mrecv->nh.id), 0, NULL, 0, NULL, + NULL, NULL); + return ret; +} + +/* + * An incoming ack frees the pending response. + */ +static void process_ack(struct scoutfs_net_connection *conn, + struct message_recv *mrecv) +{ + struct super_block *sb = conn->sb; + struct message_send *msend; + + spin_lock(&conn->lock); + + msend = find_send(conn, SCOUTFS_NET_MSG_RESPONSE, mrecv->nh.cmd, + le64_to_cpu(mrecv->nh.id)); + if (msend) + complete_send(conn, msend, NULL, 0, 0); + else + scoutfs_inc_counter(sb, net_dropped_ack); + + spin_unlock(&conn->lock); +} + +/* + * Process an incoming received message in its own concurrent blocking + * work context. + */ +static void scoutfs_net_proc_worker(struct work_struct *work) +{ + struct message_recv *mrecv = container_of(work, struct message_recv, + proc_work); + struct scoutfs_net_connection *conn = mrecv->conn; + struct super_block *sb = conn->sb; + int ret; + + trace_scoutfs_net_proc_work_enter(sb, 0, 0); + + switch (mrecv->nh.msg) { + case SCOUTFS_NET_MSG_REQUEST: + ret = process_request(conn, mrecv); + break; + case SCOUTFS_NET_MSG_RESPONSE: + ret = process_response(conn, mrecv); + break; + case SCOUTFS_NET_MSG_ACK: + process_ack(conn, mrecv); + ret = 0; + break; + default: + scoutfs_inc_counter(sb, net_unknown_message); + ret = -ENOMSG; + break; + } + + /* process_one_work explicitly allows freeing work in its func */ + kfree(mrecv); + + /* shut down the connection if processing returns fatal errors */ + if (ret) + shutdown_conn(conn); + + trace_scoutfs_net_proc_work_exit(sb, 0, ret); +} + +static int recvmsg_full(struct socket *sock, void *buf, unsigned len) +{ + struct msghdr msg; + struct kvec kv; + int ret; + + while (len) { + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = (struct iovec *)&kv; + msg.msg_iovlen = 1; + msg.msg_flags = MSG_NOSIGNAL; + kv.iov_base = buf; + kv.iov_len = len; + + ret = kernel_recvmsg(sock, &msg, &kv, 1, len, msg.msg_flags); + if (ret <= 0) + return -ECONNABORTED; + + len -= ret; + buf += ret; + } + + return 0; +} + +static bool invalid_message(struct scoutfs_net_header *nh) +{ + /* ids must be non-zero */ + if (nh->id == 0) + return true; + + /* greeting messages must have the greeting id */ + if ((nh->cmd == SCOUTFS_NET_CMD_GREETING) != + (le64_to_cpu(nh->id) == SCOUTFS_NET_ID_GREETING)) + return true; + + /* greeting should negotiate understood protocol */ + if (nh->msg >= SCOUTFS_NET_MSG_UNKNOWN || + nh->cmd >= SCOUTFS_NET_CMD_UNKNOWN || + nh->error >= SCOUTFS_NET_ERR_UNKNOWN) + return true; + + /* errors can't have payloads */ + if (nh->data_len != 0 && nh->error != SCOUTFS_NET_ERR_NONE) + return true; + + /* payloads have a limit */ + if (le16_to_cpu(nh->data_len) > SCOUTFS_NET_MAX_DATA_LEN) + return true; + + /* only responses can carry errors */ + if (nh->error != SCOUTFS_NET_ERR_NONE && + nh->msg != SCOUTFS_NET_MSG_RESPONSE) + return true; + + return false; +} + +/* + * Always block receiving from the socket. Errors trigger shutting down + * the connection. + */ +static void scoutfs_net_recv_worker(struct work_struct *work) +{ + DEFINE_CONN_FROM_WORK(conn, work, recv_work); + struct super_block *sb = conn->sb; + struct socket *sock = conn->sock; + struct scoutfs_net_header nh; + struct message_recv *mrecv; + unsigned int data_len; + int ret; + + trace_scoutfs_net_recv_work_enter(sb, 0, 0); + + for (;;) { + /* receive the header */ + ret = recvmsg_full(sock, &nh, sizeof(nh)); + if (ret) + break; + + /* receiving an invalid message breaks the connection */ + if (invalid_message(&nh)) { + scoutfs_inc_counter(sb, net_recv_invalid_message); + ret = -EBADMSG; + break; + } + + data_len = le16_to_cpu(nh.data_len); + + scoutfs_inc_counter(sb, net_recv_messages); + scoutfs_add_counter(sb, net_recv_bytes, nh_bytes(data_len)); + trace_scoutfs_net_recv_message(sb, &conn->sockname, + &conn->peername, &nh); + + /* invalid message checked data len */ + mrecv = kmalloc(offsetof(struct message_recv, + nh.data[data_len]), GFP_NOFS); + if (!mrecv) { + ret = -ENOMEM; + break; + } + + mrecv->conn = conn; + INIT_WORK(&mrecv->proc_work, scoutfs_net_proc_worker); + mrecv->nh = nh; + + /* receive the data payload */ + ret = recvmsg_full(sock, mrecv->nh.data, data_len); + if (ret) { + kfree(mrecv); + break; + } + + /* + * Check and maintain the last processed id for + * non-greeting requests before introducing reordering + * by queueing concurrent work. + */ + spin_lock(&conn->lock); + if (mrecv->nh.msg == SCOUTFS_NET_MSG_REQUEST && + mrecv->nh.cmd != SCOUTFS_NET_CMD_GREETING) { + if (le64_to_cpu(mrecv->nh.id) <= conn->last_proc_id) { + scoutfs_inc_counter(sb, net_dropped_request); + kfree(mrecv); + mrecv = NULL; + } else { + conn->last_proc_id = le64_to_cpu(mrecv->nh.id); + } + } + spin_unlock(&conn->lock); + + if (mrecv) + queue_work(conn->workq, &mrecv->proc_work); + } + + if (ret) + scoutfs_inc_counter(sb, net_recv_error); + + /* recv stopping always shuts down the connection */ + shutdown_conn(conn); + + trace_scoutfs_net_recv_work_exit(sb, 0, ret); +} + +static int sendmsg_full(struct socket *sock, void *buf, unsigned len) +{ + struct msghdr msg; + struct kvec kv; + int ret; + + while (len) { + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = (struct iovec *)&kv; + msg.msg_iovlen = 1; + msg.msg_flags = MSG_NOSIGNAL; + kv.iov_base = buf; + kv.iov_len = len; + + ret = kernel_sendmsg(sock, &msg, &kv, 1, len); + if (ret <= 0) + return -ECONNABORTED; + + len -= ret; + buf += ret; + } + + return 0; +} + +/* + * Each connection has a single worker that sends queued messages down + * the connection's socket. The work is queued whenever a message is + * put on the send queue. The worker uses blocking sends so that we + * don't have to worry about resuming partial sends or hooking into + * data_ready. Send errors shut down the connection. + * + * The worker is responsible for freeing messages so that other contexts + * don't have to worry about freeing a message while we're blocked + * sending it without the lock held. + */ +static void scoutfs_net_send_worker(struct work_struct *work) +{ + DEFINE_CONN_FROM_WORK(conn, work, send_work); + struct super_block *sb = conn->sb; + struct message_send *msend; + int ret = 0; + int len; + + trace_scoutfs_net_send_work_enter(sb, 0, 0); + + spin_lock(&conn->lock); + + while ((msend = list_first_entry_or_null(&conn->send_queue, + struct message_send, head))) { + + if (msend->dead) { + list_del_init(&msend->head); + kfree(msend); + continue; + } + + spin_unlock(&conn->lock); + + len = nh_bytes(le16_to_cpu(msend->nh.data_len)); + + scoutfs_inc_counter(sb, net_send_messages); + scoutfs_add_counter(sb, net_send_bytes, len); + trace_scoutfs_net_send_message(sb, &conn->sockname, + &conn->peername, &msend->nh); + + ret = sendmsg_full(conn->sock, &msend->nh, len); + + spin_lock(&conn->lock); + + if (ret) + break; + + /* acks are always freed, others will be resent if not dead */ + if (msend->nh.msg == SCOUTFS_NET_MSG_ACK) + msend->dead = 1; + else if (!msend->dead) + list_move_tail(&msend->head, &conn->resend_queue); + } + + spin_unlock(&conn->lock); + + if (ret) { + scoutfs_inc_counter(sb, net_send_error); + shutdown_conn(conn); + } + + trace_scoutfs_net_send_work_exit(sb, 0, ret); +} + +static void destroy_conn(struct scoutfs_net_connection *conn) +{ + struct scoutfs_net_connection *listener; + struct message_send *msend; + struct message_send *tmp; + + WARN_ON_ONCE(conn->sock != NULL); + WARN_ON_ONCE(!list_empty(&conn->accepted_list)); + + /* free all messages, refactor and complete for forced unmount? */ + list_splice_init(&conn->resend_queue, &conn->send_queue); + list_for_each_entry_safe(msend, tmp, &conn->send_queue, head) { + list_del_init(&msend->head); + kfree(msend); + } + + /* accepted sockets are removed from their listener's list */ + if (conn->listening_conn) { + listener = conn->listening_conn; + + spin_lock(&listener->lock); + list_del_init(&conn->accepted_head); + if (list_empty(&listener->accepted_list)) + wake_up(&listener->accepted_waitq); + spin_unlock(&listener->lock); + } + + destroy_workqueue(conn->workq); + kfree(conn); +} + +/* + * Have a pretty aggressive keepalive timeout of around 10 seconds. The + * TCP keepalives are being processed out of task context so they should + * be responsive even when mounts are under load. + */ +#define KEEPCNT 3 +#define KEEPIDLE 7 +#define KEEPINTVL 1 +static int sock_opts_and_names(struct scoutfs_net_connection *conn, + struct socket *sock) +{ + struct timeval tv; + int addrlen; + int optval; + int ret; + + /* but use a keepalive timeout instead of send timeout */ + tv.tv_sec = 0; + tv.tv_usec = 0; + ret = kernel_setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, + (char *)&tv, sizeof(tv)); + if (ret) + goto out; + + optval = KEEPCNT; + ret = kernel_setsockopt(sock, SOL_TCP, TCP_KEEPCNT, + (char *)&optval, sizeof(optval)); + if (ret) + goto out; + + optval = KEEPIDLE; + ret = kernel_setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, + (char *)&optval, sizeof(optval)); + if (ret) + goto out; + + optval = KEEPINTVL; + ret = kernel_setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, + (char *)&optval, sizeof(optval)); + if (ret) + goto out; + + optval = 1; + ret = kernel_setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, + (char *)&optval, sizeof(optval)); + if (ret) + goto out; + + optval = 1; + ret = kernel_setsockopt(sock, SOL_TCP, TCP_NODELAY, + (char *)&optval, sizeof(optval)); + if (ret) + goto out; + + addrlen = sizeof(struct sockaddr_in); + ret = kernel_getsockname(sock, (struct sockaddr *)&conn->sockname, + &addrlen); + if (ret == 0 && addrlen != sizeof(struct sockaddr_in)) + ret = -EAFNOSUPPORT; + if (ret) + goto out; + + addrlen = sizeof(struct sockaddr_in); + ret = kernel_getpeername(sock, (struct sockaddr *)&conn->peername, + &addrlen); + if (ret == 0 && addrlen != sizeof(struct sockaddr_in)) + ret = -EAFNOSUPPORT; + if (ret) + goto out; +out: + return ret; +} + +/* + * Each bound and listening connection has long running work that blocks + * accepting new connections. The listening socket has been setup by + * the time this is queued. + * + * Any errors on the listening sock tear down all the connections that + * were accepted. + */ +static void scoutfs_net_listen_worker(struct work_struct *work) +{ + DEFINE_CONN_FROM_WORK(conn, work, listen_work); + struct super_block *sb = conn->sb; + struct scoutfs_net_connection *acc_conn; + DECLARE_WAIT_QUEUE_HEAD(waitq); + struct socket *acc_sock; + LIST_HEAD(conn_list); + int ret; + + trace_scoutfs_net_listen_work_enter(sb, 0, 0); + + for (;;) { + ret = kernel_accept(conn->sock, &acc_sock, 0); + if (ret < 0) + break; + + /* inherit accepted request funcs from listening conn */ + acc_conn = scoutfs_net_alloc_conn(sb, NULL, NULL, + conn->req_funcs, "accepted"); + if (!acc_conn) { + sock_release(acc_sock); + ret = -ENOMEM; + continue; + } + + ret = sock_opts_and_names(acc_conn, acc_sock); + if (ret) { + sock_release(acc_sock); + destroy_conn(acc_conn); + continue; + } + + scoutfs_info(sb, "server accepted "SIN_FMT" -> "SIN_FMT, + SIN_ARG(&acc_conn->sockname), + SIN_ARG(&acc_conn->peername)); + + /* acc_conn isn't visible, conn unlock orders stores */ + spin_lock(&conn->lock); + + acc_conn->sock = acc_sock; + acc_conn->listening_conn = conn; + acc_conn->established = 1; + list_add_tail(&acc_conn->accepted_head, &conn->accepted_list); + + spin_unlock(&conn->lock); + + queue_work(acc_conn->workq, &acc_conn->recv_work); + } + + /* listening stopping shuts down connection */ + shutdown_conn(conn); + + trace_scoutfs_net_listen_work_exit(sb, 0, ret); +} + +/* + * Try once to connect to the caller's address. This is racing with + * shutdown if the caller frees the connection while we're connecting. + * Shutdown will wait for our executing work to finish. + */ +static void scoutfs_net_connect_worker(struct work_struct *work) +{ + DEFINE_CONN_FROM_WORK(conn, work, connect_work); + struct super_block *sb = conn->sb; + struct scoutfs_super_block *super = &SCOUTFS_SB(sb)->super; + struct scoutfs_net_greeting greet; + struct socket *sock; + struct timeval tv; + int ret; + + trace_scoutfs_net_connect_work_enter(sb, 0, 0); + + ret = sock_create_kern(AF_INET, SOCK_STREAM, IPPROTO_TCP, &sock); + if (ret) + goto out; + + /* caller specified connect timeout */ + tv.tv_sec = conn->connect_timeout_ms / MSEC_PER_SEC; + tv.tv_usec = (conn->connect_timeout_ms % MSEC_PER_SEC) * USEC_PER_MSEC; + ret = kernel_setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, + (char *)&tv, sizeof(tv)); + if (ret) { + sock_release(sock); + goto out; + } + + /* shutdown now owns sock, can break blocking connect */ + spin_lock(&conn->lock); + conn->sock = sock; + spin_unlock(&conn->lock); + + ret = kernel_connect(sock, (struct sockaddr *)&conn->connect_sin, + sizeof(struct sockaddr_in), 0); + if (ret) + goto out; + + ret = sock_opts_and_names(conn, sock); + if (ret) + goto out; + + /* greeting is about to queue send work */ + spin_lock(&conn->lock); + conn->established = 1; + spin_unlock(&conn->lock); + + queue_work(conn->workq, &conn->recv_work); + + /* queue a new updated greeting send */ + greet.fsid = super->id; + greet.format_hash = super->format_hash; + + ret = submit_send(sb, conn, SCOUTFS_NET_MSG_REQUEST, + SCOUTFS_NET_CMD_GREETING, SCOUTFS_NET_ID_GREETING, 0, + &greet, sizeof(greet), greeting_response, NULL, NULL); + if (ret) + goto out; + + scoutfs_info(sb, "client connected "SIN_FMT" -> "SIN_FMT, + SIN_ARG(&conn->sockname), + SIN_ARG(&conn->peername)); +out: + if (ret) + shutdown_conn(conn); + + trace_scoutfs_net_connect_work_exit(sb, 0, ret); +} + +static bool empty_accepted_list(struct scoutfs_net_connection *conn) +{ + bool empty; + + spin_lock(&conn->lock); + empty = list_empty(&conn->accepted_list); + spin_unlock(&conn->lock); + + return empty; +} + +/* listening and their accepting sockets have a fixed locking order */ +enum { + CONN_LOCK_LISTENER, + CONN_LOCK_ACCEPTED, +}; + +/* + * Safely shut down an active connection. This can be triggered by + * errors in workers or by an external call to free the connection. The + * shutting down flag ensures that this only executes once for each live + * socket. + * + * Our reliability guarantee requires request processing to make forward + * progress once we've received and recorded a request id. We wait for + * processing work that is in flight and its sends will be queued for + * resending because the connection is not established while it's + * shutting down. + */ +static void scoutfs_net_shutdown_worker(struct work_struct *work) +{ + DEFINE_CONN_FROM_WORK(conn, work, shutdown_work); + struct super_block *sb = conn->sb; + struct scoutfs_net_connection *acc_conn; + struct message_send *msend; + + trace_scoutfs_net_shutdown_work_enter(sb, 0, 0); + + /* connected and accepted conns print a message */ + if (conn->peername.sin_family) + scoutfs_info(sb, "%s "SIN_FMT" -> "SIN_FMT, + conn->listening_conn ? "server closing" : + "client disconnected", + SIN_ARG(&conn->sockname), + SIN_ARG(&conn->peername)); + + /* ensure that sockets return errors, wakes blocked socket work */ + if (conn->sock) + kernel_sock_shutdown(conn->sock, SHUT_RDWR); + + /* wait for socket and proc work to finish, includes chained work */ + drain_workqueue(conn->workq); + + /* tear down the sock now that all work is done */ + if (conn->sock) { + sock_release(conn->sock); + conn->sock = NULL; + } + + /* listening connections shut down all the connections they accepted */ + spin_lock_nested(&conn->lock, CONN_LOCK_LISTENER); + list_for_each_entry(acc_conn, &conn->accepted_list, accepted_head) { + spin_lock_nested(&acc_conn->lock, CONN_LOCK_ACCEPTED); + shutdown_conn_locked(acc_conn); + spin_unlock(&acc_conn->lock); + } + spin_unlock(&conn->lock); + wait_event(conn->accepted_waitq, empty_accepted_list(conn)); + + spin_lock(&conn->lock); + + /* all queued sends will be resent, protocol handles dupes */ + list_splice_tail_init(&conn->send_queue, &conn->resend_queue); + + /* clear greeting state for next negotiation */ + conn->valid_greeting = 0; + msend = find_send(conn, SCOUTFS_NET_MSG_REQUEST, + SCOUTFS_NET_CMD_GREETING, SCOUTFS_NET_ID_GREETING) ?: + find_send(conn, SCOUTFS_NET_MSG_RESPONSE, + SCOUTFS_NET_CMD_GREETING, SCOUTFS_NET_ID_GREETING) ?: + find_send(conn, SCOUTFS_NET_MSG_ACK, + SCOUTFS_NET_CMD_GREETING, SCOUTFS_NET_ID_GREETING); + if (msend) + complete_send(conn, msend, NULL, 0, 0); + + spin_unlock(&conn->lock); + + memset(&conn->peername, 0, sizeof(conn->peername)); + + /* tell the caller that the connection is down */ + if (conn->notify_down) + conn->notify_down(sb, conn); + + /* accepted conns are destroyed */ + if (conn->listening_conn) { + destroy_conn(conn); + } else { + spin_lock(&conn->lock); + conn->shutting_down = 0; + spin_unlock(&conn->lock); + } + + trace_scoutfs_net_shutdown_work_exit(sb, 0, 0); +} + +struct scoutfs_net_connection * +scoutfs_net_alloc_conn(struct super_block *sb, + scoutfs_net_notify_t notify_up, + scoutfs_net_notify_t notify_down, + scoutfs_net_request_t *req_funcs, char *name_suffix) +{ + struct scoutfs_net_connection *conn; + + /* we handle greetings, the caller shouldn't attempt to */ + if (WARN_ON_ONCE(req_funcs != NULL && + req_funcs[SCOUTFS_NET_CMD_GREETING] != NULL)) + return NULL; + + conn = kzalloc(sizeof(struct scoutfs_net_connection), GFP_NOFS); + if (!conn) + return NULL; + + conn->workq = alloc_workqueue("scoutfs_net_%s", + WQ_UNBOUND | WQ_NON_REENTRANT, 0, + name_suffix); + if (!conn->workq) { + kfree(conn); + return NULL; + } + + conn->sb = sb; + conn->notify_up = notify_up; + conn->notify_down = notify_down; + conn->req_funcs = req_funcs; + spin_lock_init(&conn->lock); + INIT_LIST_HEAD(&conn->accepted_head); + INIT_LIST_HEAD(&conn->accepted_list); + init_waitqueue_head(&conn->accepted_waitq); + conn->next_send_id = SCOUTFS_NET_ID_GREETING + 1; + INIT_LIST_HEAD(&conn->send_queue); + INIT_LIST_HEAD(&conn->resend_queue); + INIT_WORK(&conn->listen_work, scoutfs_net_listen_worker); + INIT_WORK(&conn->connect_work, scoutfs_net_connect_worker); + INIT_WORK(&conn->send_work, scoutfs_net_send_worker); + INIT_WORK(&conn->recv_work, scoutfs_net_recv_worker); + INIT_WORK(&conn->shutdown_work, scoutfs_net_shutdown_worker); + + return conn; +} + +/* + * Shutdown the connection. Once this returns no network traffic + * or work will be executing. The caller can then connect or bind and + * listen again. Additional shutdown calls will already find it shutdown. + */ +void scoutfs_net_shutdown(struct super_block *sb, + struct scoutfs_net_connection *conn) +{ + shutdown_conn(conn); + flush_work(&conn->shutdown_work); +} + +/* + * Destroy the connection after the shutdown work has stopped all concurrent + * processing on the connection. + */ +void scoutfs_net_free_conn(struct super_block *sb, + struct scoutfs_net_connection *conn) +{ + if (conn) { + scoutfs_net_shutdown(sb, conn); + destroy_conn(conn); + } +} + +/* + * Associate a bound socket with the caller's connection. We call bind + * and listen to assign the listening address and give it to the caller. + * + * If this returns success then the caller has to call either listen or + * free_conn. + */ +int scoutfs_net_bind(struct super_block *sb, + struct scoutfs_net_connection *conn, + struct sockaddr_in *sin) +{ + struct socket *sock = NULL; + int addrlen; + int ret; + + /* caller state machine shouldn't let this happen */ + if (WARN_ON_ONCE(conn->sock)) + return -EINVAL; + + ret = sock_create_kern(AF_INET, SOCK_STREAM, IPPROTO_TCP, &sock); + if (ret) + goto out; + + addrlen = sizeof(struct sockaddr_in); + ret = kernel_bind(sock, (struct sockaddr *)sin, addrlen); + if (ret) + goto out; + + ret = kernel_listen(sock, 255); + if (ret) + goto out; + + addrlen = sizeof(struct sockaddr_in); + ret = kernel_getsockname(sock, (struct sockaddr *)&conn->sockname, + &addrlen); + if (ret == 0 && addrlen != sizeof(struct sockaddr_in)) + ret = -EAFNOSUPPORT; + if (ret) + goto out; + + conn->sock = sock; + *sin = conn->sockname; + ret = 0; +out: + if (ret < 0 && sock) + sock_release(sock); + return ret; +} + +/* + * Kick off blocking background work to accept connections from the + * connection's listening socket that was created with a previous bind + * call. + * + * The callback notify_down will be called once the listening socket is + * shut down either by errors or the caller freeing the conn. + */ +void scoutfs_net_listen(struct super_block *sb, + struct scoutfs_net_connection *conn) +{ + queue_work(conn->workq, &conn->listen_work); +} + +/* + * Start connecting to the given address. notify_up may be called if + * the connection completes. notify_down will be called when either the + * connection disconnects or times out. Both could be called before + * this function returns. The caller must be careful not to call + * connect again until notify_down has been called. + */ +void scoutfs_net_connect(struct super_block *sb, + struct scoutfs_net_connection *conn, + struct sockaddr_in *sin, unsigned long timeout_ms) +{ + spin_lock(&conn->lock); + conn->connect_sin = *sin; + conn->connect_timeout_ms = timeout_ms; + spin_unlock(&conn->lock); + + queue_work(conn->workq, &conn->connect_work); +} + +/* + * Submit a request down the connection. It's up to the caller to + * ensure that the conn is allocated. Sends submitted when the + * connection isn't established will be resent in order the next time + * it's established. + */ +int scoutfs_net_submit_request(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, void *arg, u16 arg_len, + scoutfs_net_response_t resp_func, + void *resp_data, u64 *id_ret) +{ + return submit_send(sb, conn, SCOUTFS_NET_MSG_REQUEST, cmd, 0, 0, + arg, arg_len, resp_func, resp_data, id_ret); +} + +/* + * Send a response. Responses don't get callbacks and use the request's + * id so caller's don't need to get an id in return. + * + * The data payload is ignored if an error is sent so that callers have + * simple processing exit paths. + * + * An error is returned if the response could not be sent. + */ +int scoutfs_net_response(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id, int error, void *resp, u16 resp_len) +{ + if (error) { + resp = NULL; + resp_len = 0; + } + + return submit_send(sb, conn, SCOUTFS_NET_MSG_RESPONSE, + cmd, id, net_err_from_host(sb, error), + resp, resp_len, NULL, NULL, NULL); +} + +void scoutfs_net_cancel_request(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id) +{ + struct message_send *msend; + + spin_lock(&conn->lock); + msend = find_send(conn, SCOUTFS_NET_MSG_REQUEST, cmd, id); + if (msend) + complete_send(conn, msend, NULL, 0, -ECANCELED); + spin_unlock(&conn->lock); +} + +struct sync_request_completion { + struct completion comp; + void *resp; + unsigned int resp_len; + int error; +}; + +static int sync_response(struct super_block *sb, + struct scoutfs_net_connection *conn, + void *resp, unsigned int resp_len, + int error, void *data) +{ + struct sync_request_completion *sreq = data; + + if (error == 0 && resp_len != sreq->resp_len) + error = -EMSGSIZE; + + if (error) + sreq->error = error; + else if (resp_len) + memcpy(sreq->resp, resp, resp_len); + + complete(&sreq->comp); + + return 0; +} + +/* + * Send a request and wait for a response to be copied into the given + * buffer. Errors returned can come from the remote request processing + * or local failure to send. + * + * The wait for the response is interruptible and can return + * -ERESTARTSYS if it is interrupted. + * + * -EOVERFLOW is returned if the response message's data_length doesn't + * match the caller's resp_len buffer. + */ +int scoutfs_net_sync_request(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, void *arg, unsigned arg_len, + void *resp, size_t resp_len) +{ + struct sync_request_completion sreq; + int ret; + u64 id; + + init_completion(&sreq.comp); + sreq.resp = resp; + sreq.resp_len = resp_len; + sreq.error = 0; + + ret = scoutfs_net_submit_request(sb, conn, cmd, arg, arg_len, + sync_response, &sreq, &id); + + ret = wait_for_completion_interruptible(&sreq.comp); + if (ret == -ERESTARTSYS) + scoutfs_net_cancel_request(sb, conn, cmd, id); + else + ret = sreq.error; + + return ret; +} + +int scoutfs_net_setup(struct super_block *sb) +{ + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + struct net_info *ninf; + int ret; + + /* fail the build if host errnos don't fit in the u8 mapping arrays */ +#undef EXPAND_NET_ERRNO +#define EXPAND_NET_ERRNO(which) BUILD_BUG_ON(which >= U8_MAX); + EXPAND_EACH_NET_ERRNO + + ninf = kzalloc(sizeof(struct net_info), GFP_KERNEL); + if (!ninf) { + ret = -ENOMEM; + goto out; + } + + sbi->net_info = ninf; + + ninf->shutdown_workq = alloc_workqueue("scoutfs_net_shutdown", + WQ_UNBOUND, 0); + if (!ninf->shutdown_workq) { + ret = -ENOMEM; + goto out; + } + + ret = 0; +out: + if (ret) + scoutfs_net_destroy(sb); + return ret; +} + +void scoutfs_net_destroy(struct super_block *sb) +{ + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + struct net_info *ninf = SCOUTFS_SB(sb)->net_info; + + if (ninf) { + if (ninf->shutdown_workq) + destroy_workqueue(ninf->shutdown_workq); + kfree(ninf); + sbi->net_info = NULL; + } +} diff --git a/kmod/src/net.h b/kmod/src/net.h new file mode 100644 index 00000000..4e270760 --- /dev/null +++ b/kmod/src/net.h @@ -0,0 +1,61 @@ +#ifndef _SCOUTFS_NET_H_ +#define _SCOUTFS_NET_H_ + +#include + +#define SIN_FMT "%pIS:%u" +#define SIN_ARG(sin) sin, be16_to_cpu((sin)->sin_port) + +struct scoutfs_net_connection; + +/* These are called in their own blocking context */ +typedef int (*scoutfs_net_request_t)(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id, void *arg, u16 arg_len); + +/* These are called with a spinlock held, funcs must be fast and nonblocking */ +typedef int (*scoutfs_net_response_t)(struct super_block *sb, + struct scoutfs_net_connection *conn, + void *resp, unsigned int resp_len, + int error, void *data); + +typedef void (*scoutfs_net_notify_t)(struct super_block *sb, + struct scoutfs_net_connection *conn); + +struct scoutfs_net_connection * +scoutfs_net_alloc_conn(struct super_block *sb, + scoutfs_net_notify_t notify_up, + scoutfs_net_notify_t notify_down, + scoutfs_net_request_t *req_funcs, char *name_suffix); +void scoutfs_net_connect(struct super_block *sb, + struct scoutfs_net_connection *conn, + struct sockaddr_in *sin, unsigned long timeout_ms); +int scoutfs_net_bind(struct super_block *sb, + struct scoutfs_net_connection *conn, + struct sockaddr_in *sin); +void scoutfs_net_listen(struct super_block *sb, + struct scoutfs_net_connection *conn); +int scoutfs_net_submit_request(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, void *arg, u16 arg_len, + scoutfs_net_response_t resp_func, + void *resp_data, u64 *id_ret); +void scoutfs_net_cancel_request(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id); +int scoutfs_net_sync_request(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, void *arg, unsigned arg_len, + void *resp, size_t resp_len); +int scoutfs_net_response(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id, int error, void *resp, u16 resp_len); +void scoutfs_net_shutdown(struct super_block *sb, + struct scoutfs_net_connection *conn); +void scoutfs_net_free_conn(struct super_block *sb, + struct scoutfs_net_connection *conn); + +int scoutfs_net_setup(struct super_block *sb); +void scoutfs_net_destroy(struct super_block *sb); + +#endif diff --git a/kmod/src/scoutfs_trace.h b/kmod/src/scoutfs_trace.h index b223d70d..962bfca7 100644 --- a/kmod/src/scoutfs_trace.h +++ b/kmod/src/scoutfs_trace.h @@ -1658,25 +1658,13 @@ DECLARE_EVENT_CLASS(scoutfs_net_class, snh_trace_args(nh)) ); -DEFINE_EVENT(scoutfs_net_class, scoutfs_client_send_request, +DEFINE_EVENT(scoutfs_net_class, scoutfs_net_send_message, TP_PROTO(struct super_block *sb, struct sockaddr_in *name, struct sockaddr_in *peer, struct scoutfs_net_header *nh), TP_ARGS(sb, name, peer, nh) ); -DEFINE_EVENT(scoutfs_net_class, scoutfs_server_recv_request, - TP_PROTO(struct super_block *sb, struct sockaddr_in *name, - struct sockaddr_in *peer, struct scoutfs_net_header *nh), - TP_ARGS(sb, name, peer, nh) -); - -DEFINE_EVENT(scoutfs_net_class, scoutfs_server_send_reply, - TP_PROTO(struct super_block *sb, struct sockaddr_in *name, - struct sockaddr_in *peer, struct scoutfs_net_header *nh), - TP_ARGS(sb, name, peer, nh) -); - -DEFINE_EVENT(scoutfs_net_class, scoutfs_client_recv_reply, +DEFINE_EVENT(scoutfs_net_class, scoutfs_net_recv_message, TP_PROTO(struct super_block *sb, struct sockaddr_in *name, struct sockaddr_in *peer, struct scoutfs_net_header *nh), TP_ARGS(sb, name, peer, nh) @@ -1706,11 +1694,51 @@ DEFINE_EVENT(scoutfs_work_class, scoutfs_server_commit_work_exit, TP_PROTO(struct super_block *sb, u64 data, int ret), TP_ARGS(sb, data, ret) ); -DEFINE_EVENT(scoutfs_work_class, scoutfs_server_recv_work_enter, +DEFINE_EVENT(scoutfs_work_class, scoutfs_net_proc_work_enter, TP_PROTO(struct super_block *sb, u64 data, int ret), TP_ARGS(sb, data, ret) ); -DEFINE_EVENT(scoutfs_work_class, scoutfs_server_recv_work_exit, +DEFINE_EVENT(scoutfs_work_class, scoutfs_net_proc_work_exit, + TP_PROTO(struct super_block *sb, u64 data, int ret), + TP_ARGS(sb, data, ret) +); +DEFINE_EVENT(scoutfs_work_class, scoutfs_net_listen_work_enter, + TP_PROTO(struct super_block *sb, u64 data, int ret), + TP_ARGS(sb, data, ret) +); +DEFINE_EVENT(scoutfs_work_class, scoutfs_net_listen_work_exit, + TP_PROTO(struct super_block *sb, u64 data, int ret), + TP_ARGS(sb, data, ret) +); +DEFINE_EVENT(scoutfs_work_class, scoutfs_net_connect_work_enter, + TP_PROTO(struct super_block *sb, u64 data, int ret), + TP_ARGS(sb, data, ret) +); +DEFINE_EVENT(scoutfs_work_class, scoutfs_net_connect_work_exit, + TP_PROTO(struct super_block *sb, u64 data, int ret), + TP_ARGS(sb, data, ret) +); +DEFINE_EVENT(scoutfs_work_class, scoutfs_net_shutdown_work_enter, + TP_PROTO(struct super_block *sb, u64 data, int ret), + TP_ARGS(sb, data, ret) +); +DEFINE_EVENT(scoutfs_work_class, scoutfs_net_shutdown_work_exit, + TP_PROTO(struct super_block *sb, u64 data, int ret), + TP_ARGS(sb, data, ret) +); +DEFINE_EVENT(scoutfs_work_class, scoutfs_net_send_work_enter, + TP_PROTO(struct super_block *sb, u64 data, int ret), + TP_ARGS(sb, data, ret) +); +DEFINE_EVENT(scoutfs_work_class, scoutfs_net_send_work_exit, + TP_PROTO(struct super_block *sb, u64 data, int ret), + TP_ARGS(sb, data, ret) +); +DEFINE_EVENT(scoutfs_work_class, scoutfs_net_recv_work_enter, + TP_PROTO(struct super_block *sb, u64 data, int ret), + TP_ARGS(sb, data, ret) +); +DEFINE_EVENT(scoutfs_work_class, scoutfs_net_recv_work_exit, TP_PROTO(struct super_block *sb, u64 data, int ret), TP_ARGS(sb, data, ret) ); diff --git a/kmod/src/server.c b/kmod/src/server.c index da284854..13428ad2 100644 --- a/kmod/src/server.c +++ b/kmod/src/server.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Versity Software, Inc. All rights reserved. + * Copyright (C) 2018 Versity Software, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public @@ -32,9 +32,20 @@ #include "msg.h" #include "client.h" #include "server.h" -#include "sock.h" +#include "net.h" #include "endian_swap.h" +/* + * Every active mount can act as the server that listens on a net + * connection and accepts connections from all the other mounts acting + * as clients. + * + * It queues long-lived work that blocks trying to acquire a lock. If + * it acquires the lock it listens on a socket and serves requests. If + * it sees errors it shuts down the server in the hopes that another + * mount will have less trouble. + */ + #define SIN_FMT "%pIS:%u" #define SIN_ARG(sin) sin, be16_to_cpu((sin)->sin_port) @@ -43,19 +54,14 @@ struct server_info { struct workqueue_struct *wq; struct delayed_work dwork; - - struct mutex mutex; - bool shutting_down; + struct completion shutdown_comp; bool bind_warned; - struct task_struct *listen_task; - struct socket *listen_sock; /* request processing coordinates committing manifest and alloc */ struct rw_semaphore commit_rwsem; struct llist_head commit_waiters; struct work_struct commit_work; - /* adding new segments can have to wait for compaction */ wait_queue_head_t compaction_waitq; @@ -72,6 +78,10 @@ struct server_info { struct list_head pending_frees; }; +#define DECLARE_SERVER_INFO(sb, name) \ + struct server_info *name = SCOUTFS_SB(sb)->server_info + +#if 0 struct server_request { struct server_connection *conn; struct work_struct work; @@ -89,6 +99,7 @@ struct server_connection { struct work_struct recv_work; struct mutex send_mutex; }; +#endif struct commit_waiter { struct completion comp; @@ -454,17 +465,9 @@ out: return ret; } -/* - * Trigger a server shutdown by shutting down the listening socket. The - * server thread will break out of accept and exit. - */ -static void shut_down_server(struct server_info *server) +static void shutdown_server(struct server_info *server) { - mutex_lock(&server->mutex); - server->shutting_down = true; - if (server->listen_sock) - kernel_sock_shutdown(server->listen_sock, SHUT_RDWR); - mutex_unlock(&server->mutex); + complete(&server->shutdown_comp); } /* @@ -495,21 +498,11 @@ static void queue_commit_work(struct server_info *server, } /* - * Commit errors are fatal and shut down the server. This is called - * from request processing which shutdown will wait for. + * Wait for a commit during request processing and return its status. */ -static int wait_for_commit(struct server_info *server, - struct commit_waiter *cw, u64 id, u8 type) +static inline int wait_for_commit(struct commit_waiter *cw) { - struct super_block *sb = server->sb; - wait_for_completion(&cw->comp); - if (cw->ret < 0) { - scoutfs_err(sb, "commit error %d processing req id %llu type %u", - cw->ret, id, type); - - shut_down_server(server); - } return cw->ret; } @@ -546,17 +539,18 @@ static void scoutfs_server_commit_func(struct work_struct *work) down_write(&server->commit_rwsem); - if (!scoutfs_btree_has_dirty(sb)) { - ret = 0; - goto out; - } - + /* try to free first which can dirty the btrees */ ret = apply_pending_frees(sb); if (ret) { scoutfs_err(sb, "server error freeing extents: %d", ret); goto out; } + if (!scoutfs_btree_has_dirty(sb)) { + ret = 0; + goto out; + } + ret = scoutfs_btree_write_dirty(sb); if (ret) { scoutfs_err(sb, "server error writing btree blocks: %d", ret); @@ -591,6 +585,7 @@ out: trace_scoutfs_server_commit_work_exit(sb, 0, ret); } +#if 0 /* * Request processing synchronously sends their reply from within their * processing work. If this fails the socket is shutdown. @@ -639,6 +634,7 @@ static int send_reply(struct server_connection *conn, u64 id, return ret; } +#endif void scoutfs_init_ment_to_net(struct scoutfs_net_manifest_entry *net_ment, struct scoutfs_manifest_entry *ment) @@ -660,26 +656,26 @@ void scoutfs_init_ment_from_net(struct scoutfs_manifest_entry *ment, ment->last = net_ment->last; } -static int process_alloc_inodes(struct server_connection *conn, - u64 id, u8 type, void *data, unsigned data_len) +static int server_alloc_inodes(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id, void *arg, u16 arg_len) { - struct server_info *server = conn->server; - struct super_block *sb = server->sb; + DECLARE_SERVER_INFO(sb, server); struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); struct scoutfs_super_block *super = &sbi->super; - struct scoutfs_net_inode_alloc ial; + struct scoutfs_net_inode_alloc ial = { 0, }; struct commit_waiter cw; __le64 lecount; u64 ino; u64 nr; int ret; - if (data_len != sizeof(lecount)) { + if (arg_len != sizeof(lecount)) { ret = -EINVAL; goto out; } - memcpy(&lecount, data, data_len); + memcpy(&lecount, arg, arg_len); down_read(&server->commit_rwsem); @@ -695,52 +691,48 @@ static int process_alloc_inodes(struct server_connection *conn, ial.ino = cpu_to_le64(ino); ial.nr = cpu_to_le64(nr); - ret = wait_for_commit(server, &cw, id, type); + ret = wait_for_commit(&cw); out: - return send_reply(conn, id, type, ret, &ial, sizeof(ial)); + return scoutfs_net_response(sb, conn, cmd, id, ret, &ial, sizeof(ial)); } /* * Give the client an extent allocation of len blocks. We leave the * details to the extent allocator. */ -static int process_alloc_extent(struct server_connection *conn, - u64 id, u8 type, void *data, unsigned data_len) +static int server_alloc_extent(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id, void *arg, u16 arg_len) { - struct server_info *server = conn->server; - struct super_block *sb = server->sb; + DECLARE_SERVER_INFO(sb, server); struct commit_waiter cw; - struct scoutfs_net_extent nex; + struct scoutfs_net_extent nex = {0,}; __le64 leblocks; u64 start; u64 len; int ret; - if (data_len != sizeof(leblocks)) { + if (arg_len != sizeof(leblocks)) { ret = -EINVAL; goto out; } - memcpy(&leblocks, data, data_len); + memcpy(&leblocks, arg, arg_len); down_read(&server->commit_rwsem); ret = alloc_extent(sb, le64_to_cpu(leblocks), &start, &len); - if (ret == -ENOSPC) { - start = 0; - len = 0; - ret = 0; - } - if (ret == 0) { - nex.start = cpu_to_le64(start); - nex.len = cpu_to_le64(len); - queue_commit_work(server, &cw); - } - up_read(&server->commit_rwsem); - if (ret == 0) - ret = wait_for_commit(server, &cw, id, type); + queue_commit_work(server, &cw); + up_read(&server->commit_rwsem); + if (ret == 0) + ret = wait_for_commit(&cw); + if (ret) + goto out; + + nex.start = cpu_to_le64(start); + nex.len = cpu_to_le64(len); out: - return send_reply(conn, id, type, ret, &nex, sizeof(nex)); + return scoutfs_net_response(sb, conn, cmd, id, ret, &nex, sizeof(nex)); } static bool invalid_net_extent_list(struct scoutfs_net_extent_list *nexl, @@ -752,19 +744,19 @@ static bool invalid_net_extent_list(struct scoutfs_net_extent_list *nexl, extents[le64_to_cpu(nexl->nr)])); } -static int process_free_extents(struct server_connection *conn, - u64 id, u8 type, void *data, unsigned data_len) +static int server_free_extents(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id, void *arg, u16 arg_len) { - struct server_info *server = conn->server; - struct super_block *sb = server->sb; + DECLARE_SERVER_INFO(sb, server); struct scoutfs_net_extent_list *nexl; struct commit_waiter cw; - int ret = 0; + int ret; int err; u64 i; - nexl = data; - if (invalid_net_extent_list(nexl, data_len)) { + nexl = arg; + if (invalid_net_extent_list(nexl, arg_len)) { ret = -EINVAL; goto out; } @@ -783,70 +775,66 @@ static int process_free_extents(struct server_connection *conn, up_read(&server->commit_rwsem); if (i > 0) { - err = wait_for_commit(server, &cw, id, type); + err = wait_for_commit(&cw); if (ret == 0) ret = err; } + out: - return send_reply(conn, id, type, ret, NULL, 0); + return scoutfs_net_response(sb, conn, cmd, id, ret, NULL, 0); } /* * We still special case segno allocation because it's aligned and we'd * like to keep that detail in the server. */ -static int process_alloc_segno(struct server_connection *conn, - u64 id, u8 type, void *data, unsigned data_len) +static int server_alloc_segno(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id, void *arg, u16 arg_len) { - struct server_info *server = conn->server; - struct super_block *sb = server->sb; + DECLARE_SERVER_INFO(sb, server); struct commit_waiter cw; __le64 lesegno = 0; u64 segno; int ret; - if (data_len != 0) { + if (arg_len != 0) { ret = -EINVAL; goto out; } down_read(&server->commit_rwsem); ret = alloc_segno(sb, &segno); - if (ret == 0) { - lesegno = cpu_to_le64(segno); + if (ret == 0) queue_commit_work(server, &cw); - } else if (ret == -ENOSPC) { - ret = 0; - } up_read(&server->commit_rwsem); + if (ret == 0) + ret = wait_for_commit(&cw); + if (ret) + goto out; - if (ret == 0 && lesegno != 0) - ret = wait_for_commit(server, &cw, id, type); + lesegno = cpu_to_le64(segno); out: - return send_reply(conn, id, type, ret, &lesegno, sizeof(lesegno)); + return scoutfs_net_response(sb, conn, cmd, id, ret, + &lesegno, sizeof(lesegno)); } -static int process_record_segment(struct server_connection *conn, u64 id, - u8 type, void *data, unsigned data_len) +static int server_record_segment(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id, void *arg, u16 arg_len) { - struct server_info *server = conn->server; - struct super_block *sb = server->sb; + DECLARE_SERVER_INFO(sb, server); struct scoutfs_net_manifest_entry *net_ment; struct scoutfs_manifest_entry ment; struct commit_waiter cw; int ret; - if (data_len < sizeof(struct scoutfs_net_manifest_entry)) { + if (arg_len != sizeof(struct scoutfs_net_manifest_entry)) { ret = -EINVAL; goto out; } - net_ment = data; - - if (data_len != sizeof(*net_ment)) { - ret = -EINVAL; - goto out; - } + net_ment = arg; retry: down_read(&server->commit_rwsem); @@ -871,12 +859,13 @@ retry: up_read(&server->commit_rwsem); if (ret == 0) { - ret = wait_for_commit(server, &cw, id, type); + ret = wait_for_commit(&cw); if (ret == 0) scoutfs_compact_kick(sb); } + out: - return send_reply(conn, id, type, ret, NULL, 0); + return scoutfs_net_response(sb, conn, cmd, id, ret, NULL, 0); } struct pending_seq { @@ -896,21 +885,21 @@ struct pending_seq { * XXX The pending seq tracking should be persistent so that it survives * server failover. */ -static int process_advance_seq(struct server_connection *conn, u64 id, u8 type, - void *data, unsigned data_len) +static int server_advance_seq(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id, void *arg, u16 arg_len) { - struct server_info *server = conn->server; - struct super_block *sb = server->sb; + DECLARE_SERVER_INFO(sb, server); struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); struct scoutfs_super_block *super = &sbi->super; struct pending_seq *next_ps; struct pending_seq *ps; struct commit_waiter cw; - __le64 * __packed their_seq = data; + __le64 * __packed their_seq = arg; __le64 next_seq; int ret; - if (data_len != sizeof(__le64)) { + if (arg_len != sizeof(__le64)) { ret = -EINVAL; goto out; } @@ -941,24 +930,24 @@ static int process_advance_seq(struct server_connection *conn, u64 id, u8 type, spin_unlock(&server->seq_lock); queue_commit_work(server, &cw); up_read(&server->commit_rwsem); - ret = wait_for_commit(server, &cw, id, type); - + ret = wait_for_commit(&cw); out: - return send_reply(conn, id, type, ret, &next_seq, sizeof(next_seq)); + return scoutfs_net_response(sb, conn, cmd, id, ret, + &next_seq, sizeof(next_seq)); } -static int process_get_last_seq(struct server_connection *conn, u64 id, - u8 type, void *data, unsigned data_len) +static int server_get_last_seq(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id, void *arg, u16 arg_len) { - struct server_info *server = conn->server; - struct super_block *sb = server->sb; + DECLARE_SERVER_INFO(sb, server); struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); struct scoutfs_super_block *super = &sbi->super; struct pending_seq *ps; __le64 last_seq; int ret; - if (data_len != 0) { + if (arg_len != 0) { ret = -EINVAL; goto out; } @@ -975,18 +964,20 @@ static int process_get_last_seq(struct server_connection *conn, u64 id, spin_unlock(&server->seq_lock); ret = 0; out: - return send_reply(conn, id, type, ret, &last_seq, sizeof(last_seq)); + return scoutfs_net_response(sb, conn, cmd, id, ret, + &last_seq, sizeof(last_seq)); } -static int process_get_manifest_root(struct server_connection *conn, u64 id, - u8 type, void *data, unsigned data_len) +static int server_get_manifest_root(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id, void *arg, u16 arg_len) { - struct server_info *server = conn->server; + DECLARE_SERVER_INFO(sb, server); struct scoutfs_btree_root root; unsigned int start; int ret; - if (data_len == 0) { + if (arg_len == 0) { do { start = read_seqcount_begin(&server->stable_seqcount); root = server->stable_manifest_root; @@ -996,24 +987,25 @@ static int process_get_manifest_root(struct server_connection *conn, u64 id, ret = -EINVAL; } - return send_reply(conn, id, type, ret, &root, sizeof(root)); + return scoutfs_net_response(sb, conn, cmd, id, ret, + &root, sizeof(root)); } /* * Sample the super stats that the client wants for statfs by serializing * with each component. */ -static int process_statfs(struct server_connection *conn, u64 id, u8 type, - void *data, unsigned data_len) +static int server_statfs(struct super_block *sb, + struct scoutfs_net_connection *conn, + u8 cmd, u64 id, void *arg, u16 arg_len) { - struct server_info *server = conn->server; - struct super_block *sb = server->sb; + DECLARE_SERVER_INFO(sb, server); struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); struct scoutfs_super_block *super = &sbi->super; struct scoutfs_net_statfs nstatfs; int ret; - if (data_len == 0) { + if (arg_len == 0) { /* uuid and total_segs are constant, so far */ memcpy(nstatfs.uuid, super->uuid, sizeof(nstatfs.uuid)); @@ -1030,7 +1022,8 @@ static int process_statfs(struct server_connection *conn, u64 id, u8 type, ret = -EINVAL; } - return send_reply(conn, id, type, ret, &nstatfs, sizeof(nstatfs)); + return scoutfs_net_response(sb, conn, cmd, id, ret, + &nstatfs, sizeof(nstatfs)); } /* @@ -1078,7 +1071,7 @@ int scoutfs_client_get_compaction(struct super_block *sb, void *curs) up_read(&server->commit_rwsem); if (ret == 0) - ret = wait_for_commit(server, &cw, U64_MAX, 1); + ret = wait_for_commit(&cw); return ret; } @@ -1117,13 +1110,14 @@ int scoutfs_client_finish_compaction(struct super_block *sb, void *curs, up_read(&server->commit_rwsem); if (ret == 0) - ret = wait_for_commit(server, &cw, U64_MAX, 2); + ret = wait_for_commit(&cw); scoutfs_compact_kick(sb); return ret; } +#if 0 typedef int (*process_func_t)(struct server_connection *conn, u64 id, u8 type, void *data, unsigned data_len); @@ -1168,7 +1162,9 @@ static void scoutfs_server_process_func(struct work_struct *work) /* process_one_work explicitly allows freeing work in its func */ kfree(req); } +#endif +#if 0 /* * Always block receiving from the socket. This owns the socket. If * receive fails this shuts down and frees the socket. @@ -1292,6 +1288,7 @@ out: trace_scoutfs_server_recv_work_exit(sb, 0, ret); } +#endif /* * This relies on the caller having read the current super and advanced @@ -1308,6 +1305,7 @@ static int write_server_addr(struct super_block *sb, struct sockaddr_in *sin) return scoutfs_write_dirty_super(sb); } +#if 0 static bool barrier_list_empty_careful(struct list_head *list) { /* store caller's task state before loading wake condition */ @@ -1315,7 +1313,27 @@ static bool barrier_list_empty_careful(struct list_head *list) return list_empty_careful(list); } +#endif +static scoutfs_net_request_t server_req_funcs[] = { + [SCOUTFS_NET_CMD_ALLOC_INODES] = server_alloc_inodes, + [SCOUTFS_NET_CMD_ALLOC_EXTENT] = server_alloc_extent, + [SCOUTFS_NET_CMD_FREE_EXTENTS] = server_free_extents, + [SCOUTFS_NET_CMD_ALLOC_SEGNO] = server_alloc_segno, + [SCOUTFS_NET_CMD_RECORD_SEGMENT] = server_record_segment, + [SCOUTFS_NET_CMD_ADVANCE_SEQ] = server_advance_seq, + [SCOUTFS_NET_CMD_GET_LAST_SEQ] = server_get_last_seq, + [SCOUTFS_NET_CMD_GET_MANIFEST_ROOT] = server_get_manifest_root, + [SCOUTFS_NET_CMD_STATFS] = server_statfs, +}; + +static void server_notify_down(struct super_block *sb, + struct scoutfs_net_connection *conn) +{ + DECLARE_SERVER_INFO(sb, server); + + shutdown_server(server); +} /* * This work is always running or has a delayed timer set while a super * is mounted. It tries to grab the lock to become the server. If it @@ -1323,51 +1341,45 @@ static bool barrier_list_empty_careful(struct list_head *list) * anything goes wrong it releases the lock and sets a timer to try to * become the server all over again. */ -static void scoutfs_server_func(struct work_struct *work) +static void scoutfs_server_worker(struct work_struct *work) { struct server_info *server = container_of(work, struct server_info, dwork.work); struct super_block *sb = server->sb; struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); struct scoutfs_super_block *super = &sbi->super; + struct scoutfs_net_connection *conn = NULL; static struct sockaddr_in zeros = {0,}; - struct socket *new_sock; - struct socket *sock = NULL; struct scoutfs_lock *lock = NULL; - struct server_connection *conn; struct pending_seq *ps; struct pending_seq *ps_tmp; DECLARE_WAIT_QUEUE_HEAD(waitq); struct sockaddr_in sin; LIST_HEAD(conn_list); - int addrlen; - int optval; int ret; trace_scoutfs_server_work_enter(sb, 0, 0); - init_waitqueue_head(&waitq); + init_completion(&server->shutdown_comp); ret = scoutfs_lock_global(sb, DLM_LOCK_EX, 0, SCOUTFS_LOCK_TYPE_GLOBAL_SERVER, &lock); if (ret) goto out; + conn = scoutfs_net_alloc_conn(sb, NULL, server_notify_down, + server_req_funcs, "server"); + if (!conn) { + ret = -ENOMEM; + goto out; + } + sin.sin_family = AF_INET; sin.sin_addr.s_addr = le32_to_be32(sbi->opts.listen_addr.addr); sin.sin_port = le16_to_be16(sbi->opts.listen_addr.port); - optval = 1; - ret = sock_create_kern(AF_INET, SOCK_STREAM, IPPROTO_TCP, &sock) ?: - kernel_setsockopt(sock, SOL_TCP, TCP_NODELAY, - (char *)&optval, sizeof(optval)) ?: - kernel_setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, - (char *)&optval, sizeof(optval)); - if (ret) - goto out; - - addrlen = sizeof(sin); - ret = kernel_bind(sock, (struct sockaddr *)&sin, addrlen); + /* get the address of our listening socket */ + ret = scoutfs_net_bind(sb, conn, &sin); if (ret) { if (!server->bind_warned) { scoutfs_err(sb, "server failed to bind to "SIN_FMT", errno %d%s. Retrying indefinitely..", @@ -1378,15 +1390,6 @@ static void scoutfs_server_func(struct work_struct *work) } goto out; } - server->bind_warned = false; - - kernel_getsockname(sock, (struct sockaddr *)&sin, &addrlen); - if (ret) - goto out; - - ret = kernel_listen(sock, 255); - if (ret) - goto out; /* publish the address for clients to connect to */ ret = scoutfs_read_super(sb, super); @@ -1398,17 +1401,7 @@ static void scoutfs_server_func(struct work_struct *work) if (ret) goto out; - /* either see shutting down or they'll shutdown our sock */ - mutex_lock(&server->mutex); - server->listen_task = current; - server->listen_sock = sock; - if (server->shutting_down) - ret = -ESHUTDOWN; - mutex_unlock(&server->mutex); - if (ret) - goto out; - - /* finally start up the server subsystems before accepting */ + /* start up the server subsystems before accepting */ ret = scoutfs_btree_setup(sb) ?: scoutfs_manifest_setup(sb) ?: scoutfs_compact_setup(sb); @@ -1420,69 +1413,24 @@ static void scoutfs_server_func(struct work_struct *work) scoutfs_info(sb, "server started on "SIN_FMT, SIN_ARG(&sin)); - for (;;) { - ret = kernel_accept(sock, &new_sock, 0); - if (ret < 0) - break; + /* start accepting connections and processing work */ + scoutfs_net_listen(sb, conn); - conn = kmalloc(sizeof(struct server_connection), GFP_NOFS); - if (!conn) { - sock_release(new_sock); - ret = -ENOMEM; - continue; - } - - addrlen = sizeof(struct sockaddr_in); - ret = kernel_getsockname(new_sock, - (struct sockaddr *)&conn->sockname, - &addrlen) ?: - kernel_getpeername(new_sock, - (struct sockaddr *)&conn->peername, - &addrlen); - if (ret) { - sock_release(new_sock); - continue; - } - - /* - * XXX yeah, ok, killing the sock and accepting a new - * one is racey. think about that in all the code. Are - * we destroying a resource to shutdown that the thing - * we're canceling creates? - */ - - conn->server = server; - conn->sock = new_sock; - mutex_init(&conn->send_mutex); - - scoutfs_info(sb, "server accepted "SIN_FMT" -> "SIN_FMT, - SIN_ARG(&conn->peername), - SIN_ARG(&conn->sockname)); - - /* recv work owns the conn once its in the list */ - mutex_lock(&server->mutex); - list_add(&conn->head, &conn_list); - mutex_unlock(&server->mutex); - - INIT_WORK(&conn->recv_work, scoutfs_server_recv_func); - queue_work(server->wq, &conn->recv_work); - } - - /* shutdown send and recv on all accepted sockets */ - mutex_lock(&server->mutex); - list_for_each_entry(conn, &conn_list, head) - kernel_sock_shutdown(conn->sock, SHUT_RDWR); - mutex_unlock(&server->mutex); - - /* wait for all recv work to finish and free connections */ - wait_event(waitq, barrier_list_empty_careful(&conn_list)); + /* wait for listening down or umount, conn can still be live */ + wait_for_completion_interruptible(&server->shutdown_comp); scoutfs_info(sb, "server shutting down on "SIN_FMT, SIN_ARG(&sin)); shutdown: + /* wait for request processing */ + scoutfs_net_shutdown(sb, conn); + /* wait for commit queued by request processing */ + flush_work(&server->commit_work); /* shut down all the server subsystems */ scoutfs_compact_destroy(sb); + /* (wait for possible double commit work queued by compaction) */ + flush_work(&server->commit_work); destroy_pending_frees(sb); scoutfs_manifest_destroy(sb); scoutfs_btree_destroy(sb); @@ -1496,9 +1444,7 @@ shutdown: write_server_addr(sb, &zeros); out: - if (sock) - sock_release(sock); - + scoutfs_net_free_conn(sb, conn); scoutfs_unlock(sb, lock, DLM_LOCK_EX); /* always requeues, cancel_delayed_work_sync cancels on shutdown */ @@ -1517,8 +1463,9 @@ int scoutfs_server_setup(struct super_block *sb) return -ENOMEM; server->sb = sb; - INIT_DELAYED_WORK(&server->dwork, scoutfs_server_func); - mutex_init(&server->mutex); + init_completion(&server->shutdown_comp); + server->bind_warned = false; + INIT_DELAYED_WORK(&server->dwork, scoutfs_server_worker); init_rwsem(&server->commit_rwsem); init_llist_head(&server->commit_waiters); INIT_WORK(&server->commit_work, scoutfs_server_commit_func); @@ -1547,7 +1494,7 @@ void scoutfs_server_destroy(struct super_block *sb) struct server_info *server = sbi->server_info; if (server) { - shut_down_server(server); + shutdown_server(server); /* wait for server work to wait for everything to shut down */ cancel_delayed_work_sync(&server->dwork); diff --git a/kmod/src/server.h b/kmod/src/server.h index 96f3a5df..506aa546 100644 --- a/kmod/src/server.h +++ b/kmod/src/server.h @@ -22,13 +22,14 @@ do { \ __entry->name##_addr & 255, \ __entry->name##_port -#define SNH_FMT "id %llu data_len %u type %u status %u" +#define SNH_FMT "id %llu data_len %u msg %u cmd %u error %u" #define snh_trace_define(name) \ __field(__u64, name##_id) \ __field(__u16, name##_data_len) \ - __field(__u8, name##_type) \ - __field(__u8, name##_status) + __field(__u8, name##_msg) \ + __field(__u8, name##_cmd) \ + __field(__u8, name##_error) #define snh_trace_assign(name, nh) \ do { \ @@ -36,13 +37,14 @@ do { \ \ __entry->name##_id = le64_to_cpu(_nh->id); \ __entry->name##_data_len = le16_to_cpu(_nh->data_len); \ - __entry->name##_type = _nh->type; \ - __entry->name##_status = _nh->status; \ + __entry->name##_msg = _nh->msg; \ + __entry->name##_cmd = _nh->cmd; \ + __entry->name##_error = _nh->error; \ } while (0) #define snh_trace_args(name) \ - __entry->name##_id, __entry->name##_data_len, __entry->name##_type, \ - __entry->name##_status + __entry->name##_id, __entry->name##_data_len, __entry->name##_msg, \ + __entry->name##_cmd, __entry->name##_error void scoutfs_init_ment_to_net(struct scoutfs_net_manifest_entry *net_ment, struct scoutfs_manifest_entry *ment); diff --git a/kmod/src/super.c b/kmod/src/super.c index 4279ea96..d9540334 100644 --- a/kmod/src/super.c +++ b/kmod/src/super.c @@ -38,6 +38,7 @@ #include "compact.h" #include "data.h" #include "lock.h" +#include "net.h" #include "client.h" #include "server.h" #include "options.h" @@ -133,6 +134,7 @@ static void scoutfs_put_super(struct super_block *sb) /* the server locks the listen address and compacts */ scoutfs_lock_shutdown(sb); scoutfs_server_destroy(sb); + scoutfs_net_destroy(sb); scoutfs_seg_destroy(sb); scoutfs_lock_destroy(sb); @@ -333,6 +335,7 @@ static int scoutfs_fill_super(struct super_block *sb, void *data, int silent) scoutfs_data_setup(sb) ?: scoutfs_setup_trans(sb) ?: scoutfs_lock_setup(sb) ?: + scoutfs_net_setup(sb) ?: scoutfs_server_setup(sb) ?: scoutfs_client_setup(sb) ?: scoutfs_lock_node_id(sb, DLM_LOCK_EX, 0, sbi->node_id, diff --git a/kmod/src/super.h b/kmod/src/super.h index 8e9cece1..99944c8d 100644 --- a/kmod/src/super.h +++ b/kmod/src/super.h @@ -22,6 +22,7 @@ struct inode_sb_info; struct btree_info; struct sysfs_info; struct options_sb_info; +struct net_info; struct scoutfs_sb_info { struct super_block *sb; @@ -42,6 +43,7 @@ struct scoutfs_sb_info { struct data_info *data_info; struct inode_sb_info *inode_sb_info; struct btree_info *btree_info; + struct net_info *net_info; wait_queue_head_t trans_hold_wq; struct task_struct *trans_task;