diff options
Diffstat (limited to 'fs/afs/server.c')
-rw-r--r-- | fs/afs/server.c | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/fs/afs/server.c b/fs/afs/server.c new file mode 100644 index 00000000000..62b093aa41c --- /dev/null +++ b/fs/afs/server.c @@ -0,0 +1,502 @@ +/* server.c: AFS server record management + * + * Copyright (C) 2002 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/sched.h> +#include <linux/slab.h> +#include <rxrpc/peer.h> +#include <rxrpc/connection.h> +#include "volume.h" +#include "cell.h" +#include "server.h" +#include "transport.h" +#include "vlclient.h" +#include "kafstimod.h" +#include "internal.h" + +DEFINE_SPINLOCK(afs_server_peer_lock); + +#define FS_SERVICE_ID 1 /* AFS Volume Location Service ID */ +#define VL_SERVICE_ID 52 /* AFS Volume Location Service ID */ + +static void __afs_server_timeout(struct afs_timer *timer) +{ + struct afs_server *server = + list_entry(timer, struct afs_server, timeout); + + _debug("SERVER TIMEOUT [%p{u=%d}]", + server, atomic_read(&server->usage)); + + afs_server_do_timeout(server); +} + +static const struct afs_timer_ops afs_server_timer_ops = { + .timed_out = __afs_server_timeout, +}; + +/*****************************************************************************/ +/* + * lookup a server record in a cell + * - TODO: search the cell's server list + */ +int afs_server_lookup(struct afs_cell *cell, const struct in_addr *addr, + struct afs_server **_server) +{ + struct afs_server *server, *active, *zombie; + int loop; + + _enter("%p,%08x,", cell, ntohl(addr->s_addr)); + + /* allocate and initialise a server record */ + server = kmalloc(sizeof(struct afs_server), GFP_KERNEL); + if (!server) { + _leave(" = -ENOMEM"); + return -ENOMEM; + } + + memset(server, 0, sizeof(struct afs_server)); + atomic_set(&server->usage, 1); + + INIT_LIST_HEAD(&server->link); + init_rwsem(&server->sem); + INIT_LIST_HEAD(&server->fs_callq); + spin_lock_init(&server->fs_lock); + INIT_LIST_HEAD(&server->cb_promises); + spin_lock_init(&server->cb_lock); + + for (loop = 0; loop < AFS_SERVER_CONN_LIST_SIZE; loop++) + server->fs_conn_cnt[loop] = 4; + + memcpy(&server->addr, addr, sizeof(struct in_addr)); + server->addr.s_addr = addr->s_addr; + + afs_timer_init(&server->timeout, &afs_server_timer_ops); + + /* add to the cell */ + write_lock(&cell->sv_lock); + + /* check the active list */ + list_for_each_entry(active, &cell->sv_list, link) { + if (active->addr.s_addr == addr->s_addr) + goto use_active_server; + } + + /* check the inactive list */ + spin_lock(&cell->sv_gylock); + list_for_each_entry(zombie, &cell->sv_graveyard, link) { + if (zombie->addr.s_addr == addr->s_addr) + goto resurrect_server; + } + spin_unlock(&cell->sv_gylock); + + afs_get_cell(cell); + server->cell = cell; + list_add_tail(&server->link, &cell->sv_list); + + write_unlock(&cell->sv_lock); + + *_server = server; + _leave(" = 0 (%p)", server); + return 0; + + /* found a matching active server */ + use_active_server: + _debug("active server"); + afs_get_server(active); + write_unlock(&cell->sv_lock); + + kfree(server); + + *_server = active; + _leave(" = 0 (%p)", active); + return 0; + + /* found a matching server in the graveyard, so resurrect it and + * dispose of the new record */ + resurrect_server: + _debug("resurrecting server"); + + list_del(&zombie->link); + list_add_tail(&zombie->link, &cell->sv_list); + afs_get_server(zombie); + afs_kafstimod_del_timer(&zombie->timeout); + spin_unlock(&cell->sv_gylock); + write_unlock(&cell->sv_lock); + + kfree(server); + + *_server = zombie; + _leave(" = 0 (%p)", zombie); + return 0; + +} /* end afs_server_lookup() */ + +/*****************************************************************************/ +/* + * destroy a server record + * - removes from the cell list + */ +void afs_put_server(struct afs_server *server) +{ + struct afs_cell *cell; + + if (!server) + return; + + _enter("%p", server); + + cell = server->cell; + + /* sanity check */ + BUG_ON(atomic_read(&server->usage) <= 0); + + /* to prevent a race, the decrement and the dequeue must be effectively + * atomic */ + write_lock(&cell->sv_lock); + + if (likely(!atomic_dec_and_test(&server->usage))) { + write_unlock(&cell->sv_lock); + _leave(""); + return; + } + + spin_lock(&cell->sv_gylock); + list_del(&server->link); + list_add_tail(&server->link, &cell->sv_graveyard); + + /* time out in 10 secs */ + afs_kafstimod_add_timer(&server->timeout, 10 * HZ); + + spin_unlock(&cell->sv_gylock); + write_unlock(&cell->sv_lock); + + _leave(" [killed]"); +} /* end afs_put_server() */ + +/*****************************************************************************/ +/* + * timeout server record + * - removes from the cell's graveyard if the usage count is zero + */ +void afs_server_do_timeout(struct afs_server *server) +{ + struct rxrpc_peer *peer; + struct afs_cell *cell; + int loop; + + _enter("%p", server); + + cell = server->cell; + + BUG_ON(atomic_read(&server->usage) < 0); + + /* remove from graveyard if still dead */ + spin_lock(&cell->vl_gylock); + if (atomic_read(&server->usage) == 0) + list_del_init(&server->link); + else + server = NULL; + spin_unlock(&cell->vl_gylock); + + if (!server) { + _leave(""); + return; /* resurrected */ + } + + /* we can now destroy it properly */ + afs_put_cell(cell); + + /* uncross-point the structs under a global lock */ + spin_lock(&afs_server_peer_lock); + peer = server->peer; + if (peer) { + server->peer = NULL; + peer->user = NULL; + } + spin_unlock(&afs_server_peer_lock); + + /* finish cleaning up the server */ + for (loop = AFS_SERVER_CONN_LIST_SIZE - 1; loop >= 0; loop--) + if (server->fs_conn[loop]) + rxrpc_put_connection(server->fs_conn[loop]); + + if (server->vlserver) + rxrpc_put_connection(server->vlserver); + + kfree(server); + + _leave(" [destroyed]"); +} /* end afs_server_do_timeout() */ + +/*****************************************************************************/ +/* + * get a callslot on a connection to the fileserver on the specified server + */ +int afs_server_request_callslot(struct afs_server *server, + struct afs_server_callslot *callslot) +{ + struct afs_server_callslot *pcallslot; + struct rxrpc_connection *conn; + int nconn, ret; + + _enter("%p,",server); + + INIT_LIST_HEAD(&callslot->link); + callslot->task = current; + callslot->conn = NULL; + callslot->nconn = -1; + callslot->ready = 0; + + ret = 0; + conn = NULL; + + /* get hold of a callslot first */ + spin_lock(&server->fs_lock); + + /* resurrect the server if it's death timeout has expired */ + if (server->fs_state) { + if (time_before(jiffies, server->fs_dead_jif)) { + ret = server->fs_state; + spin_unlock(&server->fs_lock); + _leave(" = %d [still dead]", ret); + return ret; + } + + server->fs_state = 0; + } + + /* try and find a connection that has spare callslots */ + for (nconn = 0; nconn < AFS_SERVER_CONN_LIST_SIZE; nconn++) { + if (server->fs_conn_cnt[nconn] > 0) { + server->fs_conn_cnt[nconn]--; + spin_unlock(&server->fs_lock); + callslot->nconn = nconn; + goto obtained_slot; + } + } + + /* none were available - wait interruptibly for one to become + * available */ + set_current_state(TASK_INTERRUPTIBLE); + list_add_tail(&callslot->link, &server->fs_callq); + spin_unlock(&server->fs_lock); + + while (!callslot->ready && !signal_pending(current)) { + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } + + set_current_state(TASK_RUNNING); + + /* even if we were interrupted we may still be queued */ + if (!callslot->ready) { + spin_lock(&server->fs_lock); + list_del_init(&callslot->link); + spin_unlock(&server->fs_lock); + } + + nconn = callslot->nconn; + + /* if interrupted, we must release any slot we also got before + * returning an error */ + if (signal_pending(current)) { + ret = -EINTR; + goto error_release; + } + + /* if we were woken up with an error, then pass that error back to the + * called */ + if (nconn < 0) { + _leave(" = %d", callslot->errno); + return callslot->errno; + } + + /* were we given a connection directly? */ + if (callslot->conn) { + /* yes - use it */ + _leave(" = 0 (nc=%d)", nconn); + return 0; + } + + /* got a callslot, but no connection */ + obtained_slot: + + /* need to get hold of the RxRPC connection */ + down_write(&server->sem); + + /* quick check to see if there's an outstanding error */ + ret = server->fs_state; + if (ret) + goto error_release_upw; + + if (server->fs_conn[nconn]) { + /* reuse an existing connection */ + rxrpc_get_connection(server->fs_conn[nconn]); + callslot->conn = server->fs_conn[nconn]; + } + else { + /* create a new connection */ + ret = rxrpc_create_connection(afs_transport, + htons(7000), + server->addr.s_addr, + FS_SERVICE_ID, + NULL, + &server->fs_conn[nconn]); + + if (ret < 0) + goto error_release_upw; + + callslot->conn = server->fs_conn[0]; + rxrpc_get_connection(callslot->conn); + } + + up_write(&server->sem); + + _leave(" = 0"); + return 0; + + /* handle an error occurring */ + error_release_upw: + up_write(&server->sem); + + error_release: + /* either release the callslot or pass it along to another deserving + * task */ + spin_lock(&server->fs_lock); + + if (nconn < 0) { + /* no callslot allocated */ + } + else if (list_empty(&server->fs_callq)) { + /* no one waiting */ + server->fs_conn_cnt[nconn]++; + spin_unlock(&server->fs_lock); + } + else { + /* someone's waiting - dequeue them and wake them up */ + pcallslot = list_entry(server->fs_callq.next, + struct afs_server_callslot, link); + list_del_init(&pcallslot->link); + + pcallslot->errno = server->fs_state; + if (!pcallslot->errno) { + /* pass them out callslot details */ + callslot->conn = xchg(&pcallslot->conn, + callslot->conn); + pcallslot->nconn = nconn; + callslot->nconn = nconn = -1; + } + pcallslot->ready = 1; + wake_up_process(pcallslot->task); + spin_unlock(&server->fs_lock); + } + + rxrpc_put_connection(callslot->conn); + callslot->conn = NULL; + + _leave(" = %d", ret); + return ret; + +} /* end afs_server_request_callslot() */ + +/*****************************************************************************/ +/* + * release a callslot back to the server + * - transfers the RxRPC connection to the next pending callslot if possible + */ +void afs_server_release_callslot(struct afs_server *server, + struct afs_server_callslot *callslot) +{ + struct afs_server_callslot *pcallslot; + + _enter("{ad=%08x,cnt=%u},{%d}", + ntohl(server->addr.s_addr), + server->fs_conn_cnt[callslot->nconn], + callslot->nconn); + + BUG_ON(callslot->nconn < 0); + + spin_lock(&server->fs_lock); + + if (list_empty(&server->fs_callq)) { + /* no one waiting */ + server->fs_conn_cnt[callslot->nconn]++; + spin_unlock(&server->fs_lock); + } + else { + /* someone's waiting - dequeue them and wake them up */ + pcallslot = list_entry(server->fs_callq.next, + struct afs_server_callslot, link); + list_del_init(&pcallslot->link); + + pcallslot->errno = server->fs_state; + if (!pcallslot->errno) { + /* pass them out callslot details */ + callslot->conn = xchg(&pcallslot->conn, callslot->conn); + pcallslot->nconn = callslot->nconn; + callslot->nconn = -1; + } + + pcallslot->ready = 1; + wake_up_process(pcallslot->task); + spin_unlock(&server->fs_lock); + } + + rxrpc_put_connection(callslot->conn); + + _leave(""); +} /* end afs_server_release_callslot() */ + +/*****************************************************************************/ +/* + * get a handle to a connection to the vlserver (volume location) on the + * specified server + */ +int afs_server_get_vlconn(struct afs_server *server, + struct rxrpc_connection **_conn) +{ + struct rxrpc_connection *conn; + int ret; + + _enter("%p,", server); + + ret = 0; + conn = NULL; + down_read(&server->sem); + + if (server->vlserver) { + /* reuse an existing connection */ + rxrpc_get_connection(server->vlserver); + conn = server->vlserver; + up_read(&server->sem); + } + else { + /* create a new connection */ + up_read(&server->sem); + down_write(&server->sem); + if (!server->vlserver) { + ret = rxrpc_create_connection(afs_transport, + htons(7003), + server->addr.s_addr, + VL_SERVICE_ID, + NULL, + &server->vlserver); + } + if (ret == 0) { + rxrpc_get_connection(server->vlserver); + conn = server->vlserver; + } + up_write(&server->sem); + } + + *_conn = conn; + _leave(" = %d", ret); + return ret; +} /* end afs_server_get_vlconn() */ |