| /* |
| * 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 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 == 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_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); |
| |
| /* 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); |
| } |