/* * smbiod.c * * Copyright (C) 2000, Charles Loep / Corel Corp. * Copyright (C) 2001, Urban Widmark */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); }