| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  *  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/init.h>
 | 
					
						
							|  |  |  | #include <linux/file.h>
 | 
					
						
							|  |  |  | #include <linux/dcache.h>
 | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <linux/net.h>
 | 
					
						
							| 
									
										
										
										
											2006-06-25 05:49:00 -07:00
										 |  |  | #include <linux/kthread.h>
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #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; | 
					
						
							| 
									
										
										
										
											2006-06-25 05:49:00 -07:00
										 |  |  | static struct task_struct *smbiod_thread; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | static DECLARE_WAIT_QUEUE_HEAD(smbiod_wait); | 
					
						
							|  |  |  | static LIST_HEAD(smb_servers); | 
					
						
							|  |  |  | static DEFINE_SPINLOCK(servers_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define SMBIOD_DATA_READY	(1<<0)
 | 
					
						
							| 
									
										
										
										
											2007-10-14 19:35:50 +01:00
										 |  |  | static unsigned long smbiod_flags; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2006-06-25 05:49:00 -07:00
										 |  |  | 	struct task_struct *tsk; | 
					
						
							|  |  |  | 	int err = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (smbiod_state != SMBIOD_DEAD) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	smbiod_state = SMBIOD_STARTING; | 
					
						
							|  |  |  | 	__module_get(THIS_MODULE); | 
					
						
							|  |  |  | 	spin_unlock(&servers_lock); | 
					
						
							| 
									
										
										
										
											2006-06-25 05:49:00 -07:00
										 |  |  | 	tsk = kthread_run(smbiod, NULL, "smbiod"); | 
					
						
							|  |  |  | 	if (IS_ERR(tsk)) { | 
					
						
							|  |  |  | 		err = PTR_ERR(tsk); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		module_put(THIS_MODULE); | 
					
						
							| 
									
										
										
										
											2006-06-25 05:49:00 -07:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	spin_lock(&servers_lock); | 
					
						
							| 
									
										
										
										
											2006-06-25 05:49:00 -07:00
										 |  |  | 	if (err < 0) { | 
					
						
							|  |  |  | 		smbiod_state = SMBIOD_DEAD; | 
					
						
							|  |  |  | 		smbiod_thread = NULL; | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		smbiod_state = SMBIOD_RUNNING; | 
					
						
							|  |  |  | 		smbiod_thread = tsk; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return err; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * 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; | 
					
						
							| 
									
										
										
										
											2006-12-13 00:35:10 -08:00
										 |  |  | 	struct pid *pid = get_pid(server->conn_pid); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	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); | 
					
						
							| 
									
										
										
										
											2006-06-26 00:24:46 -07:00
										 |  |  | 			list_move(&req->rq_queue, &server->xmitq); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			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); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-03-29 03:07:28 +00:00
										 |  |  | 	if (!pid) { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		/* 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. | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2006-12-13 00:35:10 -08:00
										 |  |  | 	result = kill_pid(pid, SIGUSR1, 1); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (result) { | 
					
						
							|  |  |  | 		/* FIXME: this is most likely fatal, umount? */ | 
					
						
							|  |  |  | 		printk(KERN_ERR "smb_retry: signal failed [%d]\n", result); | 
					
						
							|  |  |  | 		goto out; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2007-11-14 17:00:18 -08:00
										 |  |  | 	VERBOSE("signalled pid %d\n", pid_nr(pid)); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/* FIXME: The retried requests should perhaps get a "time boost". */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | out: | 
					
						
							| 
									
										
										
										
											2006-12-13 00:35:10 -08:00
										 |  |  | 	put_pid(pid); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	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); | 
					
						
							|  |  |  | } |