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