summaryrefslogtreecommitdiffstats
path: root/kernel/kmod.c
blob: a0f58d48538f56f0300ac361b6591d59a996adad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
	kmod, the new module loader (replaces kerneld)
	Kirk Petersen
*/

#define __KERNEL_SYSCALLS__

#include <linux/sched.h>
#include <linux/types.h>
#include <linux/unistd.h>

static inline _syscall1(int,delete_module,const char *,name_user)

/*
	kmod_unload_delay and modprobe_path are set via /proc/sys.
*/
int kmod_unload_delay = 60;
char modprobe_path[256] = "/sbin/modprobe";
char module_name[64] = "";
char * argv[] = { "modprobe", "-k", NULL, NULL, };
char * envp[] = { "HOME=/", "TERM=linux", NULL, };

/*
	kmod_queue synchronizes the kmod thread and the rest of the system
	kmod_unload_timer is what we use to unload modules
	after kmod_unload_delay seconds
*/
struct wait_queue * kmod_queue = NULL;
struct timer_list kmod_unload_timer;

/*
	kmod_thread is the thread that does most of the work.  kmod_unload and
	request_module tell it to wake up and do work.
*/
int kmod_thread(void * data)
{
	int pid;

	/*
		Initialize basic thread information
	*/
	current->session = 1;
	current->pgrp = 1;
	sprintf(current->comm, "kmod");
	sigfillset(&current->blocked);

	/*
		This is the main kmod_thread loop.  It first sleeps, then
		handles requests from request_module or kmod_unload.
	*/

	while (1) {
		interruptible_sleep_on(&kmod_queue);

		/*
			If request_module woke us up, we should try to
			load module_name.  If not, kmod_unload woke us up,
			do call delete_module.
			(if somehow both want us to do something, ignore the
			 delete_module request)
		*/
		if (module_name[0] == '\0') {
			delete_module(NULL);
		} else {
			pid = fork();
			if (pid > 0) {
				waitpid(pid, NULL, 0);
				module_name[0] = '\0';
				wake_up(&kmod_queue);
			} else
			if (pid == 0) {

				/*
					Call modprobe with module_name.  If execve returns,
					print out an error.
				*/
				argv[2] = module_name;
				execve(modprobe_path, argv, envp);

				printk("kmod: failed to load module %s\n", module_name);
				_exit(0);
			} else {
				printk("error, fork failed in kmod\n");
			}
		}
	}

	return 0;	/* Never reached. */
}

/*
	kmod_unload is the function that the kernel calls when
	the kmod_unload_timer expires
*/
void kmod_unload(unsigned long x)
{
	/*
		wake up the kmod thread, which does the work
		(we can't call delete_module, as it locks the kernel and
		 we are in the bottom half of the kernel (right?))
		once it is awake, reset the timer
	*/
	wake_up(&kmod_queue);
	kmod_unload_timer.expires = jiffies + (kmod_unload_delay * HZ);
	add_timer(&kmod_unload_timer);
}

int kmod_init(void)
{
	printk ("Starting kmod\n");

	kernel_thread(kmod_thread, NULL, 0);

	kmod_unload_timer.next = NULL;
	kmod_unload_timer.prev = NULL;
	kmod_unload_timer.expires = jiffies + (5 * 60 * HZ);
	kmod_unload_timer.data = 0L;
	kmod_unload_timer.function = kmod_unload;
	add_timer(&kmod_unload_timer);

	return 0;
}

/*
	request_module, the function that everyone calls when they need a
	module to be loaded
*/
int request_module(const char * name)
{
	/* first, copy the name of the module into module_name */
	/* then wake_up() the kmod daemon */
	/* wait for the kmod daemon to finish (it will wake us up) */

	/*
		kmod_thread is sleeping, so start by copying the name of
		the module into module_name.  Once that is done, wake up
		kmod_thread.
	*/
	strcpy(module_name, name);
	wake_up(&kmod_queue);

	/*
		Now that we have told kmod_thread what to do, we want to
		go to sleep and let it do its work.  It will wake us up,
		at which point we will be done (the module will be loaded).
	*/
	interruptible_sleep_on(&kmod_queue);
	return 0;
}