/* * smbiod.c * * Copyright (C) 2000, Charles Loep / Corel Corp. * Copyright (C) 2001, Urban Widmark */ #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/module.h> #include <linux/net.h> #include <linux/kthread.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 struct task_struct *smbiod_thread; static DECLARE_WAIT_QUEUE_HEAD(smbiod_wait); static LIST_HEAD(smb_servers); static DEFINE_SPINLOCK(servers_lock); #define SMBIOD_DATA_READY (1<<0) static unsigned 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) { struct task_struct *tsk; int err = 0; if (smbiod_state != SMBIOD_DEAD) return 0; smbiod_state = SMBIOD_STARTING; __module_get(THIS_MODULE); spin_unlock(&servers_lock); tsk = kthread_run(smbiod, NULL, "smbiod"); if (IS_ERR(tsk)) { err = PTR_ERR(tsk); module_put(THIS_MODULE); } spin_lock(&servers_lock); if (err < 0) { smbiod_state = SMBIOD_DEAD; smbiod_thread = NULL; } else { smbiod_state = SMBIOD_RUNNING; smbiod_thread = tsk; } return err; } /* * 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; struct pid *pid = get_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_move(&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) { /* 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_pid(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_nr(pid)); /* FIXME: The retried requests should perhaps get a "time boost". */ out: put_pid(pid); 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) { 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); }