Merge to kernel-2.6.20-1.2949.fc6.vs2.2.0.1
[linux-2.6.git] / kernel / vserver / dlimit.c
1 /*
2  *  linux/kernel/vserver/dlimit.c
3  *
4  *  Virtual Server: Context Disk Limits
5  *
6  *  Copyright (C) 2004-2007  Herbert Pƶtzl
7  *
8  *  V0.01  initial version
9  *  V0.02  compat32 splitup
10  *
11  */
12
13 #include <linux/fs.h>
14 // #include <linux/mnt_namespace.h>
15 #include <linux/namei.h>
16 #include <linux/statfs.h>
17 #include <linux/compat.h>
18 #include <linux/vs_base.h>
19 #include <linux/vs_context.h>
20 #include <linux/vs_tag.h>
21 #include <linux/vs_dlimit.h>
22 #include <linux/vserver/switch.h>
23 #include <linux/vserver/dlimit_cmd.h>
24
25 #include <asm/errno.h>
26 #include <asm/uaccess.h>
27
28 /*      __alloc_dl_info()
29
30         * allocate an initialized dl_info struct
31         * doesn't make it visible (hash)                        */
32
33 static struct dl_info *__alloc_dl_info(struct super_block *sb, tag_t tag)
34 {
35         struct dl_info *new = NULL;
36
37         vxdprintk(VXD_CBIT(dlim, 5),
38                 "alloc_dl_info(%p,%d)*", sb, tag);
39
40         /* would this benefit from a slab cache? */
41         new = kmalloc(sizeof(struct dl_info), GFP_KERNEL);
42         if (!new)
43                 return 0;
44
45         memset (new, 0, sizeof(struct dl_info));
46         new->dl_tag = tag;
47         new->dl_sb = sb;
48         INIT_RCU_HEAD(&new->dl_rcu);
49         INIT_HLIST_NODE(&new->dl_hlist);
50         spin_lock_init(&new->dl_lock);
51         atomic_set(&new->dl_refcnt, 0);
52         atomic_set(&new->dl_usecnt, 0);
53
54         /* rest of init goes here */
55
56         vxdprintk(VXD_CBIT(dlim, 4),
57                 "alloc_dl_info(%p,%d) = %p", sb, tag, new);
58         return new;
59 }
60
61 /*      __dealloc_dl_info()
62
63         * final disposal of dl_info                             */
64
65 static void __dealloc_dl_info(struct dl_info *dli)
66 {
67         vxdprintk(VXD_CBIT(dlim, 4),
68                 "dealloc_dl_info(%p)", dli);
69
70         dli->dl_hlist.next = LIST_POISON1;
71         dli->dl_tag = -1;
72         dli->dl_sb = 0;
73
74         BUG_ON(atomic_read(&dli->dl_usecnt));
75         BUG_ON(atomic_read(&dli->dl_refcnt));
76
77         kfree(dli);
78 }
79
80
81 /*      hash table for dl_info hash */
82
83 #define DL_HASH_SIZE    13
84
85 struct hlist_head dl_info_hash[DL_HASH_SIZE];
86
87 static spinlock_t dl_info_hash_lock = SPIN_LOCK_UNLOCKED;
88
89
90 static inline unsigned int __hashval(struct super_block *sb, tag_t tag)
91 {
92         return ((tag ^ (unsigned long)sb) % DL_HASH_SIZE);
93 }
94
95
96
97 /*      __hash_dl_info()
98
99         * add the dli to the global hash table
100         * requires the hash_lock to be held                     */
101
102 static inline void __hash_dl_info(struct dl_info *dli)
103 {
104         struct hlist_head *head;
105
106         vxdprintk(VXD_CBIT(dlim, 6),
107                 "__hash_dl_info: %p[#%d]", dli, dli->dl_tag);
108         get_dl_info(dli);
109         head = &dl_info_hash[__hashval(dli->dl_sb, dli->dl_tag)];
110         hlist_add_head_rcu(&dli->dl_hlist, head);
111 }
112
113 /*      __unhash_dl_info()
114
115         * remove the dli from the global hash table
116         * requires the hash_lock to be held                     */
117
118 static inline void __unhash_dl_info(struct dl_info *dli)
119 {
120         vxdprintk(VXD_CBIT(dlim, 6),
121                 "__unhash_dl_info: %p[#%d]", dli, dli->dl_tag);
122         hlist_del_rcu(&dli->dl_hlist);
123         put_dl_info(dli);
124 }
125
126
127 /*      __lookup_dl_info()
128
129         * requires the rcu_read_lock()
130         * doesn't increment the dl_refcnt                       */
131
132 static inline struct dl_info *__lookup_dl_info(struct super_block *sb, tag_t tag)
133 {
134         struct hlist_head *head = &dl_info_hash[__hashval(sb, tag)];
135         struct hlist_node *pos;
136         struct dl_info *dli;
137
138         hlist_for_each_entry_rcu(dli, pos, head, dl_hlist) {
139
140                 if (dli->dl_tag == tag && dli->dl_sb == sb) {
141                         return dli;
142                 }
143         }
144         return NULL;
145 }
146
147
148 struct dl_info *locate_dl_info(struct super_block *sb, tag_t tag)
149 {
150         struct dl_info *dli;
151
152         rcu_read_lock();
153         dli = get_dl_info(__lookup_dl_info(sb, tag));
154         vxdprintk(VXD_CBIT(dlim, 7),
155                 "locate_dl_info(%p,#%d) = %p", sb, tag, dli);
156         rcu_read_unlock();
157         return dli;
158 }
159
160 void rcu_free_dl_info(struct rcu_head *head)
161 {
162         struct dl_info *dli = container_of(head, struct dl_info, dl_rcu);
163         int usecnt, refcnt;
164
165         BUG_ON(!dli || !head);
166
167         usecnt = atomic_read(&dli->dl_usecnt);
168         BUG_ON(usecnt < 0);
169
170         refcnt = atomic_read(&dli->dl_refcnt);
171         BUG_ON(refcnt < 0);
172
173         vxdprintk(VXD_CBIT(dlim, 3),
174                 "rcu_free_dl_info(%p)", dli);
175         if (!usecnt)
176                 __dealloc_dl_info(dli);
177         else
178                 printk("!!! rcu didn't free\n");
179 }
180
181
182
183
184 static int do_addrem_dlimit(uint32_t id, const char __user *name,
185         uint32_t flags, int add)
186 {
187         struct nameidata nd;
188         int ret;
189
190         ret = user_path_walk_link(name, &nd);
191         if (!ret) {
192                 struct super_block *sb;
193                 struct dl_info *dli;
194
195                 ret = -EINVAL;
196                 if (!nd.dentry->d_inode)
197                         goto out_release;
198                 if (!(sb = nd.dentry->d_inode->i_sb))
199                         goto out_release;
200
201                 if (add) {
202                         dli = __alloc_dl_info(sb, id);
203                         spin_lock(&dl_info_hash_lock);
204
205                         ret = -EEXIST;
206                         if (__lookup_dl_info(sb, id))
207                                 goto out_unlock;
208                         __hash_dl_info(dli);
209                         dli = NULL;
210                 } else {
211                         spin_lock(&dl_info_hash_lock);
212                         dli = __lookup_dl_info(sb, id);
213
214                         ret = -ESRCH;
215                         if (!dli)
216                                 goto out_unlock;
217                         __unhash_dl_info(dli);
218                 }
219                 ret = 0;
220         out_unlock:
221                 spin_unlock(&dl_info_hash_lock);
222                 if (add && dli)
223                         __dealloc_dl_info(dli);
224         out_release:
225                 path_release(&nd);
226         }
227         return ret;
228 }
229
230 int vc_add_dlimit(uint32_t id, void __user *data)
231 {
232         struct vcmd_ctx_dlimit_base_v0 vc_data;
233
234         if (copy_from_user (&vc_data, data, sizeof(vc_data)))
235                 return -EFAULT;
236
237         return do_addrem_dlimit(id, vc_data.name, vc_data.flags, 1);
238 }
239
240 int vc_rem_dlimit(uint32_t id, void __user *data)
241 {
242         struct vcmd_ctx_dlimit_base_v0 vc_data;
243
244         if (copy_from_user (&vc_data, data, sizeof(vc_data)))
245                 return -EFAULT;
246
247         return do_addrem_dlimit(id, vc_data.name, vc_data.flags, 0);
248 }
249
250 #ifdef  CONFIG_COMPAT
251
252 int vc_add_dlimit_x32(uint32_t id, void __user *data)
253 {
254         struct vcmd_ctx_dlimit_base_v0_x32 vc_data;
255
256         if (copy_from_user (&vc_data, data, sizeof(vc_data)))
257                 return -EFAULT;
258
259         return do_addrem_dlimit(id,
260                 compat_ptr(vc_data.name_ptr), vc_data.flags, 1);
261 }
262
263 int vc_rem_dlimit_x32(uint32_t id, void __user *data)
264 {
265         struct vcmd_ctx_dlimit_base_v0_x32 vc_data;
266
267         if (copy_from_user (&vc_data, data, sizeof(vc_data)))
268                 return -EFAULT;
269
270         return do_addrem_dlimit(id,
271                 compat_ptr(vc_data.name_ptr), vc_data.flags, 0);
272 }
273
274 #endif  /* CONFIG_COMPAT */
275
276
277 static inline
278 int do_set_dlimit(uint32_t id, const char __user *name,
279         uint32_t space_used, uint32_t space_total,
280         uint32_t inodes_used, uint32_t inodes_total,
281         uint32_t reserved, uint32_t flags)
282 {
283         struct nameidata nd;
284         int ret;
285
286         ret = user_path_walk_link(name, &nd);
287         if (!ret) {
288                 struct super_block *sb;
289                 struct dl_info *dli;
290
291                 ret = -EINVAL;
292                 if (!nd.dentry->d_inode)
293                         goto out_release;
294                 if (!(sb = nd.dentry->d_inode->i_sb))
295                         goto out_release;
296                 if ((reserved != CDLIM_KEEP &&
297                         reserved > 100) ||
298                         (inodes_used != CDLIM_KEEP &&
299                         inodes_used > inodes_total) ||
300                         (space_used != CDLIM_KEEP &&
301                         space_used > space_total))
302                         goto out_release;
303
304                 ret = -ESRCH;
305                 dli = locate_dl_info(sb, id);
306                 if (!dli)
307                         goto out_release;
308
309                 spin_lock(&dli->dl_lock);
310
311                 if (inodes_used != CDLIM_KEEP)
312                         dli->dl_inodes_used = inodes_used;
313                 if (inodes_total != CDLIM_KEEP)
314                         dli->dl_inodes_total = inodes_total;
315                 if (space_used != CDLIM_KEEP) {
316                         dli->dl_space_used = space_used;
317                         dli->dl_space_used <<= 10;
318                 }
319                 if (space_total == CDLIM_INFINITY)
320                         dli->dl_space_total = DLIM_INFINITY;
321                 else if (space_total != CDLIM_KEEP) {
322                         dli->dl_space_total = space_total;
323                         dli->dl_space_total <<= 10;
324                 }
325                 if (reserved != CDLIM_KEEP)
326                         dli->dl_nrlmult = (1 << 10) * (100 - reserved) / 100;
327
328                 spin_unlock(&dli->dl_lock);
329
330                 put_dl_info(dli);
331                 ret = 0;
332
333         out_release:
334                 path_release(&nd);
335         }
336         return ret;
337 }
338
339 int vc_set_dlimit(uint32_t id, void __user *data)
340 {
341         struct vcmd_ctx_dlimit_v0 vc_data;
342
343         if (copy_from_user (&vc_data, data, sizeof(vc_data)))
344                 return -EFAULT;
345
346         return do_set_dlimit(id, vc_data.name,
347                 vc_data.space_used, vc_data.space_total,
348                 vc_data.inodes_used, vc_data.inodes_total,
349                 vc_data.reserved, vc_data.flags);
350 }
351
352 #ifdef  CONFIG_COMPAT
353
354 int vc_set_dlimit_x32(uint32_t id, void __user *data)
355 {
356         struct vcmd_ctx_dlimit_v0_x32 vc_data;
357
358         if (copy_from_user (&vc_data, data, sizeof(vc_data)))
359                 return -EFAULT;
360
361         return do_set_dlimit(id, compat_ptr(vc_data.name_ptr),
362                 vc_data.space_used, vc_data.space_total,
363                 vc_data.inodes_used, vc_data.inodes_total,
364                 vc_data.reserved, vc_data.flags);
365 }
366
367 #endif  /* CONFIG_COMPAT */
368
369
370 static inline
371 int do_get_dlimit(uint32_t id, const char __user *name,
372         uint32_t *space_used, uint32_t *space_total,
373         uint32_t *inodes_used, uint32_t *inodes_total,
374         uint32_t *reserved, uint32_t *flags)
375 {
376         struct nameidata nd;
377         int ret;
378
379         ret = user_path_walk_link(name, &nd);
380         if (!ret) {
381                 struct super_block *sb;
382                 struct dl_info *dli;
383
384                 ret = -EINVAL;
385                 if (!nd.dentry->d_inode)
386                         goto out_release;
387                 if (!(sb = nd.dentry->d_inode->i_sb))
388                         goto out_release;
389
390                 ret = -ESRCH;
391                 dli = locate_dl_info(sb, id);
392                 if (!dli)
393                         goto out_release;
394
395                 spin_lock(&dli->dl_lock);
396                 *inodes_used = dli->dl_inodes_used;
397                 *inodes_total = dli->dl_inodes_total;
398                 *space_used = dli->dl_space_used >> 10;
399                 if (dli->dl_space_total == DLIM_INFINITY)
400                         *space_total = CDLIM_INFINITY;
401                 else
402                         *space_total = dli->dl_space_total >> 10;
403
404                 *reserved = 100 - ((dli->dl_nrlmult * 100 + 512) >> 10);
405                 spin_unlock(&dli->dl_lock);
406
407                 put_dl_info(dli);
408                 ret = -EFAULT;
409
410                 ret = 0;
411         out_release:
412                 path_release(&nd);
413         }
414         return ret;
415 }
416
417
418 int vc_get_dlimit(uint32_t id, void __user *data)
419 {
420         struct vcmd_ctx_dlimit_v0 vc_data;
421         int ret;
422
423         if (copy_from_user (&vc_data, data, sizeof(vc_data)))
424                 return -EFAULT;
425
426         ret = do_get_dlimit(id, vc_data.name,
427                 &vc_data.space_used, &vc_data.space_total,
428                 &vc_data.inodes_used, &vc_data.inodes_total,
429                 &vc_data.reserved, &vc_data.flags);
430         if (ret)
431                 return ret;
432
433         if (copy_to_user(data, &vc_data, sizeof(vc_data)))
434                 return -EFAULT;
435         return 0;
436 }
437
438 #ifdef  CONFIG_COMPAT
439
440 int vc_get_dlimit_x32(uint32_t id, void __user *data)
441 {
442         struct vcmd_ctx_dlimit_v0_x32 vc_data;
443         int ret;
444
445         if (copy_from_user (&vc_data, data, sizeof(vc_data)))
446                 return -EFAULT;
447
448         ret = do_get_dlimit(id, compat_ptr(vc_data.name_ptr),
449                 &vc_data.space_used, &vc_data.space_total,
450                 &vc_data.inodes_used, &vc_data.inodes_total,
451                 &vc_data.reserved, &vc_data.flags);
452         if (ret)
453                 return ret;
454
455         if (copy_to_user(data, &vc_data, sizeof(vc_data)))
456                 return -EFAULT;
457         return 0;
458 }
459
460 #endif  /* CONFIG_COMPAT */
461
462
463 void vx_vsi_statfs(struct super_block *sb, struct kstatfs *buf)
464 {
465         struct dl_info *dli;
466         __u64 blimit, bfree, bavail;
467         __u32 ifree;
468
469         dli = locate_dl_info(sb, dx_current_tag());
470         if (!dli)
471                 return;
472
473         spin_lock(&dli->dl_lock);
474         if (dli->dl_inodes_total == (unsigned long)DLIM_INFINITY)
475                 goto no_ilim;
476
477         /* reduce max inodes available to limit */
478         if (buf->f_files > dli->dl_inodes_total)
479                 buf->f_files = dli->dl_inodes_total;
480
481         ifree = dli->dl_inodes_total - dli->dl_inodes_used;
482         /* reduce free inodes to min */
483         if (ifree < buf->f_ffree)
484                 buf->f_ffree = ifree;
485
486 no_ilim:
487         if (dli->dl_space_total == DLIM_INFINITY)
488                 goto no_blim;
489
490         blimit = dli->dl_space_total >> sb->s_blocksize_bits;
491
492         if (dli->dl_space_total < dli->dl_space_used)
493                 bfree = 0;
494         else
495                 bfree = (dli->dl_space_total - dli->dl_space_used)
496                         >> sb->s_blocksize_bits;
497
498         bavail = ((dli->dl_space_total >> 10) * dli->dl_nrlmult);
499         if (bavail < dli->dl_space_used)
500                 bavail = 0;
501         else
502                 bavail = (bavail - dli->dl_space_used)
503                         >> sb->s_blocksize_bits;
504
505         /* reduce max space available to limit */
506         if (buf->f_blocks > blimit)
507                 buf->f_blocks = blimit;
508
509         /* reduce free space to min */
510         if (bfree < buf->f_bfree)
511                 buf->f_bfree = bfree;
512
513         /* reduce avail space to min */
514         if (bavail < buf->f_bavail)
515                 buf->f_bavail = bavail;
516
517 no_blim:
518         spin_unlock(&dli->dl_lock);
519         put_dl_info(dli);
520
521         return;
522 }
523
524 #include <linux/module.h>
525
526 EXPORT_SYMBOL_GPL(locate_dl_info);
527 EXPORT_SYMBOL_GPL(rcu_free_dl_info);
528