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;