Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * linux/fs/file.c | |
3 | * | |
4 | * Copyright (C) 1998-1999, Stephen Tweedie and Bill Hawes | |
5 | * | |
6 | * Manage the dynamic fd arrays in the process files_struct. | |
7 | */ | |
8 | ||
9 | #include <linux/fs.h> | |
10 | #include <linux/mm.h> | |
11 | #include <linux/time.h> | |
12 | #include <linux/slab.h> | |
13 | #include <linux/vmalloc.h> | |
14 | #include <linux/file.h> | |
15 | #include <linux/bitops.h> | |
16 | ||
17 | ||
18 | /* | |
19 | * Allocate an fd array, using kmalloc or vmalloc. | |
20 | * Note: the array isn't cleared at allocation time. | |
21 | */ | |
22 | struct file ** alloc_fd_array(int num) | |
23 | { | |
24 | struct file **new_fds; | |
25 | int size = num * sizeof(struct file *); | |
26 | ||
27 | if (size <= PAGE_SIZE) | |
28 | new_fds = (struct file **) kmalloc(size, GFP_KERNEL); | |
29 | else | |
30 | new_fds = (struct file **) vmalloc(size); | |
31 | return new_fds; | |
32 | } | |
33 | ||
34 | void free_fd_array(struct file **array, int num) | |
35 | { | |
36 | int size = num * sizeof(struct file *); | |
37 | ||
38 | if (!array) { | |
39 | printk (KERN_ERR "free_fd_array: array = 0 (num = %d)\n", num); | |
40 | return; | |
41 | } | |
42 | ||
43 | if (num <= NR_OPEN_DEFAULT) /* Don't free the embedded fd array! */ | |
44 | return; | |
45 | else if (size <= PAGE_SIZE) | |
46 | kfree(array); | |
47 | else | |
48 | vfree(array); | |
49 | } | |
50 | ||
51 | /* | |
52 | * Expand the fd array in the files_struct. Called with the files | |
53 | * spinlock held for write. | |
54 | */ | |
55 | ||
56 | static int expand_fd_array(struct files_struct *files, int nr) | |
57 | __releases(files->file_lock) | |
58 | __acquires(files->file_lock) | |
59 | { | |
60 | struct file **new_fds; | |
61 | int error, nfds; | |
62 | ||
63 | ||
64 | error = -EMFILE; | |
65 | if (files->max_fds >= NR_OPEN || nr >= NR_OPEN) | |
66 | goto out; | |
67 | ||
68 | nfds = files->max_fds; | |
69 | spin_unlock(&files->file_lock); | |
70 | ||
71 | /* | |
72 | * Expand to the max in easy steps, and keep expanding it until | |
73 | * we have enough for the requested fd array size. | |
74 | */ | |
75 | ||
76 | do { | |
77 | #if NR_OPEN_DEFAULT < 256 | |
78 | if (nfds < 256) | |
79 | nfds = 256; | |
80 | else | |
81 | #endif | |
82 | if (nfds < (PAGE_SIZE / sizeof(struct file *))) | |
83 | nfds = PAGE_SIZE / sizeof(struct file *); | |
84 | else { | |
85 | nfds = nfds * 2; | |
86 | if (nfds > NR_OPEN) | |
87 | nfds = NR_OPEN; | |
88 | } | |
89 | } while (nfds <= nr); | |
90 | ||
91 | error = -ENOMEM; | |
92 | new_fds = alloc_fd_array(nfds); | |
93 | spin_lock(&files->file_lock); | |
94 | if (!new_fds) | |
95 | goto out; | |
96 | ||
97 | /* Copy the existing array and install the new pointer */ | |
98 | ||
99 | if (nfds > files->max_fds) { | |
100 | struct file **old_fds; | |
101 | int i; | |
102 | ||
103 | old_fds = xchg(&files->fd, new_fds); | |
104 | i = xchg(&files->max_fds, nfds); | |
105 | ||
106 | /* Don't copy/clear the array if we are creating a new | |
107 | fd array for fork() */ | |
108 | if (i) { | |
109 | memcpy(new_fds, old_fds, i * sizeof(struct file *)); | |
110 | /* clear the remainder of the array */ | |
111 | memset(&new_fds[i], 0, | |
112 | (nfds-i) * sizeof(struct file *)); | |
113 | ||
114 | spin_unlock(&files->file_lock); | |
115 | free_fd_array(old_fds, i); | |
116 | spin_lock(&files->file_lock); | |
117 | } | |
118 | } else { | |
119 | /* Somebody expanded the array while we slept ... */ | |
120 | spin_unlock(&files->file_lock); | |
121 | free_fd_array(new_fds, nfds); | |
122 | spin_lock(&files->file_lock); | |
123 | } | |
124 | error = 0; | |
125 | out: | |
126 | return error; | |
127 | } | |
128 | ||
129 | /* | |
130 | * Allocate an fdset array, using kmalloc or vmalloc. | |
131 | * Note: the array isn't cleared at allocation time. | |
132 | */ | |
133 | fd_set * alloc_fdset(int num) | |
134 | { | |
135 | fd_set *new_fdset; | |
136 | int size = num / 8; | |
137 | ||
138 | if (size <= PAGE_SIZE) | |
139 | new_fdset = (fd_set *) kmalloc(size, GFP_KERNEL); | |
140 | else | |
141 | new_fdset = (fd_set *) vmalloc(size); | |
142 | return new_fdset; | |
143 | } | |
144 | ||
145 | void free_fdset(fd_set *array, int num) | |
146 | { | |
147 | int size = num / 8; | |
148 | ||
149 | if (num <= __FD_SETSIZE) /* Don't free an embedded fdset */ | |
150 | return; | |
151 | else if (size <= PAGE_SIZE) | |
152 | kfree(array); | |
153 | else | |
154 | vfree(array); | |
155 | } | |
156 | ||
157 | /* | |
158 | * Expand the fdset in the files_struct. Called with the files spinlock | |
159 | * held for write. | |
160 | */ | |
161 | static int expand_fdset(struct files_struct *files, int nr) | |
162 | __releases(file->file_lock) | |
163 | __acquires(file->file_lock) | |
164 | { | |
165 | fd_set *new_openset = NULL, *new_execset = NULL; | |
166 | int error, nfds = 0; | |
167 | ||
168 | error = -EMFILE; | |
169 | if (files->max_fdset >= NR_OPEN || nr >= NR_OPEN) | |
170 | goto out; | |
171 | ||
172 | nfds = files->max_fdset; | |
173 | spin_unlock(&files->file_lock); | |
174 | ||
175 | /* Expand to the max in easy steps */ | |
176 | do { | |
177 | if (nfds < (PAGE_SIZE * 8)) | |
178 | nfds = PAGE_SIZE * 8; | |
179 | else { | |
180 | nfds = nfds * 2; | |
181 | if (nfds > NR_OPEN) | |
182 | nfds = NR_OPEN; | |
183 | } | |
184 | } while (nfds <= nr); | |
185 | ||
186 | error = -ENOMEM; | |
187 | new_openset = alloc_fdset(nfds); | |
188 | new_execset = alloc_fdset(nfds); | |
189 | spin_lock(&files->file_lock); | |
190 | if (!new_openset || !new_execset) | |
191 | goto out; | |
192 | ||
193 | error = 0; | |
194 | ||
195 | /* Copy the existing tables and install the new pointers */ | |
196 | if (nfds > files->max_fdset) { | |
197 | int i = files->max_fdset / (sizeof(unsigned long) * 8); | |
198 | int count = (nfds - files->max_fdset) / 8; | |
199 | ||
200 | /* | |
201 | * Don't copy the entire array if the current fdset is | |
202 | * not yet initialised. | |
203 | */ | |
204 | if (i) { | |
205 | memcpy (new_openset, files->open_fds, files->max_fdset/8); | |
206 | memcpy (new_execset, files->close_on_exec, files->max_fdset/8); | |
207 | memset (&new_openset->fds_bits[i], 0, count); | |
208 | memset (&new_execset->fds_bits[i], 0, count); | |
209 | } | |
210 | ||
211 | nfds = xchg(&files->max_fdset, nfds); | |
212 | new_openset = xchg(&files->open_fds, new_openset); | |
213 | new_execset = xchg(&files->close_on_exec, new_execset); | |
214 | spin_unlock(&files->file_lock); | |
215 | free_fdset (new_openset, nfds); | |
216 | free_fdset (new_execset, nfds); | |
217 | spin_lock(&files->file_lock); | |
218 | return 0; | |
219 | } | |
220 | /* Somebody expanded the array while we slept ... */ | |
221 | ||
222 | out: | |
223 | spin_unlock(&files->file_lock); | |
224 | if (new_openset) | |
225 | free_fdset(new_openset, nfds); | |
226 | if (new_execset) | |
227 | free_fdset(new_execset, nfds); | |
228 | spin_lock(&files->file_lock); | |
229 | return error; | |
230 | } | |
231 | ||
232 | /* | |
233 | * Expand files. | |
234 | * Return <0 on error; 0 nothing done; 1 files expanded, we may have blocked. | |
235 | * Should be called with the files->file_lock spinlock held for write. | |
236 | */ | |
237 | int expand_files(struct files_struct *files, int nr) | |
238 | { | |
239 | int err, expand = 0; | |
240 | ||
241 | if (nr >= files->max_fdset) { | |
242 | expand = 1; | |
243 | if ((err = expand_fdset(files, nr))) | |
244 | goto out; | |
245 | } | |
246 | if (nr >= files->max_fds) { | |
247 | expand = 1; | |
248 | if ((err = expand_fd_array(files, nr))) | |
249 | goto out; | |
250 | } | |
251 | err = expand; | |
252 | out: | |
253 | return err; | |
254 | } |