summaryrefslogtreecommitdiffstats
path: root/fs/smbfs/smbiod.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/smbfs/smbiod.c')
-rw-r--r--fs/smbfs/smbiod.c341
1 files changed, 341 insertions, 0 deletions
diff --git a/fs/smbfs/smbiod.c b/fs/smbfs/smbiod.c
new file mode 100644
index 000000000000..481a97a423fa
--- /dev/null
+++ b/fs/smbfs/smbiod.c
@@ -0,0 +1,341 @@
+/*
+ * smbiod.c
+ *
+ * Copyright (C) 2000, Charles Loep / Corel Corp.
+ * Copyright (C) 2001, Urban Widmark
+ */
+
+#include <linux/config.h>
+
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/stat.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/file.h>
+#include <linux/dcache.h>
+#include <linux/smp_lock.h>
+#include <linux/module.h>
+#include <linux/net.h>
+#include <net/ip.h>
+
+#include <linux/smb_fs.h>
+#include <linux/smbno.h>
+#include <linux/smb_mount.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+#include "smb_debug.h"
+#include "request.h"
+#include "proto.h"
+
+enum smbiod_state {
+ SMBIOD_DEAD,
+ SMBIOD_STARTING,
+ SMBIOD_RUNNING,
+};
+
+static enum smbiod_state smbiod_state = SMBIOD_DEAD;
+static pid_t smbiod_pid;
+static DECLARE_WAIT_QUEUE_HEAD(smbiod_wait);
+static LIST_HEAD(smb_servers);
+static DEFINE_SPINLOCK(servers_lock);
+
+#define SMBIOD_DATA_READY (1<<0)
+static long smbiod_flags;
+
+static int smbiod(void *);
+static int smbiod_start(void);
+
+/*
+ * called when there's work for us to do
+ */
+void smbiod_wake_up(void)
+{
+ if (smbiod_state == SMBIOD_DEAD)
+ return;
+ set_bit(SMBIOD_DATA_READY, &smbiod_flags);
+ wake_up_interruptible(&smbiod_wait);
+}
+
+/*
+ * start smbiod if none is running
+ */
+static int smbiod_start(void)
+{
+ pid_t pid;
+ if (smbiod_state != SMBIOD_DEAD)
+ return 0;
+ smbiod_state = SMBIOD_STARTING;
+ __module_get(THIS_MODULE);
+ spin_unlock(&servers_lock);
+ pid = kernel_thread(smbiod, NULL, 0);
+ if (pid < 0)
+ module_put(THIS_MODULE);
+
+ spin_lock(&servers_lock);
+ smbiod_state = pid < 0 ? SMBIOD_DEAD : SMBIOD_RUNNING;
+ smbiod_pid = pid;
+ return pid;
+}
+
+/*
+ * register a server & start smbiod if necessary
+ */
+int smbiod_register_server(struct smb_sb_info *server)
+{
+ int ret;
+ spin_lock(&servers_lock);
+ list_add(&server->entry, &smb_servers);
+ VERBOSE("%p\n", server);
+ ret = smbiod_start();
+ spin_unlock(&servers_lock);
+ return ret;
+}
+
+/*
+ * Unregister a server
+ * Must be called with the server lock held.
+ */
+void smbiod_unregister_server(struct smb_sb_info *server)
+{
+ spin_lock(&servers_lock);
+ list_del_init(&server->entry);
+ VERBOSE("%p\n", server);
+ spin_unlock(&servers_lock);
+
+ smbiod_wake_up();
+ smbiod_flush(server);
+}
+
+void smbiod_flush(struct smb_sb_info *server)
+{
+ struct list_head *tmp, *n;
+ struct smb_request *req;
+
+ list_for_each_safe(tmp, n, &server->xmitq) {
+ req = list_entry(tmp, struct smb_request, rq_queue);
+ req->rq_errno = -EIO;
+ list_del_init(&req->rq_queue);
+ smb_rput(req);
+ wake_up_interruptible(&req->rq_wait);
+ }
+ list_for_each_safe(tmp, n, &server->recvq) {
+ req = list_entry(tmp, struct smb_request, rq_queue);
+ req->rq_errno = -EIO;
+ list_del_init(&req->rq_queue);
+ smb_rput(req);
+ wake_up_interruptible(&req->rq_wait);
+ }
+}
+
+/*
+ * Wake up smbmount and make it reconnect to the server.
+ * This must be called with the server locked.
+ *
+ * FIXME: add smbconnect version to this
+ */
+int smbiod_retry(struct smb_sb_info *server)
+{
+ struct list_head *head;
+ struct smb_request *req;
+ pid_t pid = server->conn_pid;
+ int result = 0;
+
+ VERBOSE("state: %d\n", server->state);
+ if (server->state == CONN_VALID || server->state == CONN_RETRYING)
+ goto out;
+
+ smb_invalidate_inodes(server);
+
+ /*
+ * Some requests are meaningless after a retry, so we abort them.
+ * One example are all requests using 'fileid' since the files are
+ * closed on retry.
+ */
+ head = server->xmitq.next;
+ while (head != &server->xmitq) {
+ req = list_entry(head, struct smb_request, rq_queue);
+ head = head->next;
+
+ req->rq_bytes_sent = 0;
+ if (req->rq_flags & SMB_REQ_NORETRY) {
+ VERBOSE("aborting request %p on xmitq\n", req);
+ req->rq_errno = -EIO;
+ list_del_init(&req->rq_queue);
+ smb_rput(req);
+ wake_up_interruptible(&req->rq_wait);
+ }
+ }
+
+ /*
+ * FIXME: test the code for retrying request we already sent
+ */
+ head = server->recvq.next;
+ while (head != &server->recvq) {
+ req = list_entry(head, struct smb_request, rq_queue);
+ head = head->next;
+#if 0
+ if (req->rq_flags & SMB_REQ_RETRY) {
+ /* must move the request to the xmitq */
+ VERBOSE("retrying request %p on recvq\n", req);
+ list_del(&req->rq_queue);
+ list_add(&req->rq_queue, &server->xmitq);
+ continue;
+ }
+#endif
+
+ VERBOSE("aborting request %p on recvq\n", req);
+ /* req->rq_rcls = ???; */ /* FIXME: set smb error code too? */
+ req->rq_errno = -EIO;
+ list_del_init(&req->rq_queue);
+ smb_rput(req);
+ wake_up_interruptible(&req->rq_wait);
+ }
+
+ smb_close_socket(server);
+
+ if (pid == 0) {
+ /* FIXME: this is fatal, umount? */
+ printk(KERN_ERR "smb_retry: no connection process\n");
+ server->state = CONN_RETRIED;
+ goto out;
+ }
+
+ /*
+ * Change state so that only one retry per server will be started.
+ */
+ server->state = CONN_RETRYING;
+
+ /*
+ * Note: use the "priv" flag, as a user process may need to reconnect.
+ */
+ result = kill_proc(pid, SIGUSR1, 1);
+ if (result) {
+ /* FIXME: this is most likely fatal, umount? */
+ printk(KERN_ERR "smb_retry: signal failed [%d]\n", result);
+ goto out;
+ }
+ VERBOSE("signalled pid %d\n", pid);
+
+ /* FIXME: The retried requests should perhaps get a "time boost". */
+
+out:
+ return result;
+}
+
+/*
+ * Currently handles lockingX packets.
+ */
+static void smbiod_handle_request(struct smb_sb_info *server)
+{
+ PARANOIA("smbiod got a request ... and we don't implement oplocks!\n");
+ server->rstate = SMB_RECV_DROP;
+}
+
+/*
+ * Do some IO for one server.
+ */
+static void smbiod_doio(struct smb_sb_info *server)
+{
+ int result;
+ int maxwork = 7;
+
+ if (server->state != CONN_VALID)
+ goto out;
+
+ do {
+ result = smb_request_recv(server);
+ if (result < 0) {
+ server->state = CONN_INVALID;
+ smbiod_retry(server);
+ goto out; /* reconnecting is slow */
+ } else if (server->rstate == SMB_RECV_REQUEST)
+ smbiod_handle_request(server);
+ } while (result > 0 && maxwork-- > 0);
+
+ /*
+ * If there is more to read then we want to be sure to wake up again.
+ */
+ if (server->state != CONN_VALID)
+ goto out;
+ if (smb_recv_available(server) > 0)
+ set_bit(SMBIOD_DATA_READY, &smbiod_flags);
+
+ do {
+ result = smb_request_send_server(server);
+ if (result < 0) {
+ server->state = CONN_INVALID;
+ smbiod_retry(server);
+ goto out; /* reconnecting is slow */
+ }
+ } while (result > 0);
+
+ /*
+ * If the last request was not sent out we want to wake up again.
+ */
+ if (!list_empty(&server->xmitq))
+ set_bit(SMBIOD_DATA_READY, &smbiod_flags);
+
+out:
+ return;
+}
+
+/*
+ * smbiod kernel thread
+ */
+static int smbiod(void *unused)
+{
+ daemonize("smbiod");
+
+ allow_signal(SIGKILL);
+
+ VERBOSE("SMB Kernel thread starting (%d) ...\n", current->pid);
+
+ for (;;) {
+ struct smb_sb_info *server;
+ struct list_head *pos, *n;
+
+ /* FIXME: Use poll? */
+ wait_event_interruptible(smbiod_wait,
+ test_bit(SMBIOD_DATA_READY, &smbiod_flags));
+ if (signal_pending(current)) {
+ spin_lock(&servers_lock);
+ smbiod_state = SMBIOD_DEAD;
+ spin_unlock(&servers_lock);
+ break;
+ }
+
+ clear_bit(SMBIOD_DATA_READY, &smbiod_flags);
+
+ spin_lock(&servers_lock);
+ if (list_empty(&smb_servers)) {
+ smbiod_state = SMBIOD_DEAD;
+ spin_unlock(&servers_lock);
+ break;
+ }
+
+ list_for_each_safe(pos, n, &smb_servers) {
+ server = list_entry(pos, struct smb_sb_info, entry);
+ VERBOSE("checking server %p\n", server);
+
+ if (server->state == CONN_VALID) {
+ spin_unlock(&servers_lock);
+
+ smb_lock_server(server);
+ smbiod_doio(server);
+ smb_unlock_server(server);
+
+ spin_lock(&servers_lock);
+ }
+ }
+ spin_unlock(&servers_lock);
+ }
+
+ VERBOSE("SMB Kernel thread exiting (%d) ...\n", current->pid);
+ module_put_and_exit(0);
+}