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