diff options
Diffstat (limited to 'fs/quota.c')
-rw-r--r-- | fs/quota.c | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/fs/quota.c b/fs/quota.c new file mode 100644 index 000000000000..3f0333a51a23 --- /dev/null +++ b/fs/quota.c | |||
@@ -0,0 +1,382 @@ | |||
1 | /* | ||
2 | * Quota code necessary even when VFS quota support is not compiled | ||
3 | * into the kernel. The interesting stuff is over in dquot.c, here | ||
4 | * we have symbols for initial quotactl(2) handling, the sysctl(2) | ||
5 | * variables, etc - things needed even when quota support disabled. | ||
6 | */ | ||
7 | |||
8 | #include <linux/fs.h> | ||
9 | #include <linux/namei.h> | ||
10 | #include <linux/slab.h> | ||
11 | #include <asm/current.h> | ||
12 | #include <asm/uaccess.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/smp_lock.h> | ||
15 | #include <linux/security.h> | ||
16 | #include <linux/syscalls.h> | ||
17 | #include <linux/buffer_head.h> | ||
18 | |||
19 | /* Check validity of generic quotactl commands */ | ||
20 | static int generic_quotactl_valid(struct super_block *sb, int type, int cmd, qid_t id) | ||
21 | { | ||
22 | if (type >= MAXQUOTAS) | ||
23 | return -EINVAL; | ||
24 | if (!sb && cmd != Q_SYNC) | ||
25 | return -ENODEV; | ||
26 | /* Is operation supported? */ | ||
27 | if (sb && !sb->s_qcop) | ||
28 | return -ENOSYS; | ||
29 | |||
30 | switch (cmd) { | ||
31 | case Q_GETFMT: | ||
32 | break; | ||
33 | case Q_QUOTAON: | ||
34 | if (!sb->s_qcop->quota_on) | ||
35 | return -ENOSYS; | ||
36 | break; | ||
37 | case Q_QUOTAOFF: | ||
38 | if (!sb->s_qcop->quota_off) | ||
39 | return -ENOSYS; | ||
40 | break; | ||
41 | case Q_SETINFO: | ||
42 | if (!sb->s_qcop->set_info) | ||
43 | return -ENOSYS; | ||
44 | break; | ||
45 | case Q_GETINFO: | ||
46 | if (!sb->s_qcop->get_info) | ||
47 | return -ENOSYS; | ||
48 | break; | ||
49 | case Q_SETQUOTA: | ||
50 | if (!sb->s_qcop->set_dqblk) | ||
51 | return -ENOSYS; | ||
52 | break; | ||
53 | case Q_GETQUOTA: | ||
54 | if (!sb->s_qcop->get_dqblk) | ||
55 | return -ENOSYS; | ||
56 | break; | ||
57 | case Q_SYNC: | ||
58 | if (sb && !sb->s_qcop->quota_sync) | ||
59 | return -ENOSYS; | ||
60 | break; | ||
61 | default: | ||
62 | return -EINVAL; | ||
63 | } | ||
64 | |||
65 | /* Is quota turned on for commands which need it? */ | ||
66 | switch (cmd) { | ||
67 | case Q_GETFMT: | ||
68 | case Q_GETINFO: | ||
69 | case Q_QUOTAOFF: | ||
70 | case Q_SETINFO: | ||
71 | case Q_SETQUOTA: | ||
72 | case Q_GETQUOTA: | ||
73 | /* This is just informative test so we are satisfied without a lock */ | ||
74 | if (!sb_has_quota_enabled(sb, type)) | ||
75 | return -ESRCH; | ||
76 | } | ||
77 | |||
78 | /* Check privileges */ | ||
79 | if (cmd == Q_GETQUOTA) { | ||
80 | if (((type == USRQUOTA && current->euid != id) || | ||
81 | (type == GRPQUOTA && !in_egroup_p(id))) && | ||
82 | !capable(CAP_SYS_ADMIN)) | ||
83 | return -EPERM; | ||
84 | } | ||
85 | else if (cmd != Q_GETFMT && cmd != Q_SYNC && cmd != Q_GETINFO) | ||
86 | if (!capable(CAP_SYS_ADMIN)) | ||
87 | return -EPERM; | ||
88 | |||
89 | return 0; | ||
90 | } | ||
91 | |||
92 | /* Check validity of XFS Quota Manager commands */ | ||
93 | static int xqm_quotactl_valid(struct super_block *sb, int type, int cmd, qid_t id) | ||
94 | { | ||
95 | if (type >= XQM_MAXQUOTAS) | ||
96 | return -EINVAL; | ||
97 | if (!sb) | ||
98 | return -ENODEV; | ||
99 | if (!sb->s_qcop) | ||
100 | return -ENOSYS; | ||
101 | |||
102 | switch (cmd) { | ||
103 | case Q_XQUOTAON: | ||
104 | case Q_XQUOTAOFF: | ||
105 | case Q_XQUOTARM: | ||
106 | if (!sb->s_qcop->set_xstate) | ||
107 | return -ENOSYS; | ||
108 | break; | ||
109 | case Q_XGETQSTAT: | ||
110 | if (!sb->s_qcop->get_xstate) | ||
111 | return -ENOSYS; | ||
112 | break; | ||
113 | case Q_XSETQLIM: | ||
114 | if (!sb->s_qcop->set_xquota) | ||
115 | return -ENOSYS; | ||
116 | break; | ||
117 | case Q_XGETQUOTA: | ||
118 | if (!sb->s_qcop->get_xquota) | ||
119 | return -ENOSYS; | ||
120 | break; | ||
121 | default: | ||
122 | return -EINVAL; | ||
123 | } | ||
124 | |||
125 | /* Check privileges */ | ||
126 | if (cmd == Q_XGETQUOTA) { | ||
127 | if (((type == XQM_USRQUOTA && current->euid != id) || | ||
128 | (type == XQM_GRPQUOTA && !in_egroup_p(id))) && | ||
129 | !capable(CAP_SYS_ADMIN)) | ||
130 | return -EPERM; | ||
131 | } else if (cmd != Q_XGETQSTAT) { | ||
132 | if (!capable(CAP_SYS_ADMIN)) | ||
133 | return -EPERM; | ||
134 | } | ||
135 | |||
136 | return 0; | ||
137 | } | ||
138 | |||
139 | static int check_quotactl_valid(struct super_block *sb, int type, int cmd, qid_t id) | ||
140 | { | ||
141 | int error; | ||
142 | |||
143 | if (XQM_COMMAND(cmd)) | ||
144 | error = xqm_quotactl_valid(sb, type, cmd, id); | ||
145 | else | ||
146 | error = generic_quotactl_valid(sb, type, cmd, id); | ||
147 | if (!error) | ||
148 | error = security_quotactl(cmd, type, id, sb); | ||
149 | return error; | ||
150 | } | ||
151 | |||
152 | static struct super_block *get_super_to_sync(int type) | ||
153 | { | ||
154 | struct list_head *head; | ||
155 | int cnt, dirty; | ||
156 | |||
157 | restart: | ||
158 | spin_lock(&sb_lock); | ||
159 | list_for_each(head, &super_blocks) { | ||
160 | struct super_block *sb = list_entry(head, struct super_block, s_list); | ||
161 | |||
162 | /* This test just improves performance so it needn't be reliable... */ | ||
163 | for (cnt = 0, dirty = 0; cnt < MAXQUOTAS; cnt++) | ||
164 | if ((type == cnt || type == -1) && sb_has_quota_enabled(sb, cnt) | ||
165 | && info_any_dirty(&sb_dqopt(sb)->info[cnt])) | ||
166 | dirty = 1; | ||
167 | if (!dirty) | ||
168 | continue; | ||
169 | sb->s_count++; | ||
170 | spin_unlock(&sb_lock); | ||
171 | down_read(&sb->s_umount); | ||
172 | if (!sb->s_root) { | ||
173 | drop_super(sb); | ||
174 | goto restart; | ||
175 | } | ||
176 | return sb; | ||
177 | } | ||
178 | spin_unlock(&sb_lock); | ||
179 | return NULL; | ||
180 | } | ||
181 | |||
182 | static void quota_sync_sb(struct super_block *sb, int type) | ||
183 | { | ||
184 | int cnt; | ||
185 | struct inode *discard[MAXQUOTAS]; | ||
186 | |||
187 | sb->s_qcop->quota_sync(sb, type); | ||
188 | /* This is not very clever (and fast) but currently I don't know about | ||
189 | * any other simple way of getting quota data to disk and we must get | ||
190 | * them there for userspace to be visible... */ | ||
191 | if (sb->s_op->sync_fs) | ||
192 | sb->s_op->sync_fs(sb, 1); | ||
193 | sync_blockdev(sb->s_bdev); | ||
194 | |||
195 | /* Now when everything is written we can discard the pagecache so | ||
196 | * that userspace sees the changes. We need i_sem and so we could | ||
197 | * not do it inside dqonoff_sem. Moreover we need to be carefull | ||
198 | * about races with quotaoff() (that is the reason why we have own | ||
199 | * reference to inode). */ | ||
200 | down(&sb_dqopt(sb)->dqonoff_sem); | ||
201 | for (cnt = 0; cnt < MAXQUOTAS; cnt++) { | ||
202 | discard[cnt] = NULL; | ||
203 | if (type != -1 && cnt != type) | ||
204 | continue; | ||
205 | if (!sb_has_quota_enabled(sb, cnt)) | ||
206 | continue; | ||
207 | discard[cnt] = igrab(sb_dqopt(sb)->files[cnt]); | ||
208 | } | ||
209 | up(&sb_dqopt(sb)->dqonoff_sem); | ||
210 | for (cnt = 0; cnt < MAXQUOTAS; cnt++) { | ||
211 | if (discard[cnt]) { | ||
212 | down(&discard[cnt]->i_sem); | ||
213 | truncate_inode_pages(&discard[cnt]->i_data, 0); | ||
214 | up(&discard[cnt]->i_sem); | ||
215 | iput(discard[cnt]); | ||
216 | } | ||
217 | } | ||
218 | } | ||
219 | |||
220 | void sync_dquots(struct super_block *sb, int type) | ||
221 | { | ||
222 | if (sb) { | ||
223 | if (sb->s_qcop->quota_sync) | ||
224 | quota_sync_sb(sb, type); | ||
225 | } | ||
226 | else { | ||
227 | while ((sb = get_super_to_sync(type)) != NULL) { | ||
228 | if (sb->s_qcop->quota_sync) | ||
229 | quota_sync_sb(sb, type); | ||
230 | drop_super(sb); | ||
231 | } | ||
232 | } | ||
233 | } | ||
234 | |||
235 | /* Copy parameters and call proper function */ | ||
236 | static int do_quotactl(struct super_block *sb, int type, int cmd, qid_t id, void __user *addr) | ||
237 | { | ||
238 | int ret; | ||
239 | |||
240 | switch (cmd) { | ||
241 | case Q_QUOTAON: { | ||
242 | char *pathname; | ||
243 | |||
244 | if (IS_ERR(pathname = getname(addr))) | ||
245 | return PTR_ERR(pathname); | ||
246 | ret = sb->s_qcop->quota_on(sb, type, id, pathname); | ||
247 | putname(pathname); | ||
248 | return ret; | ||
249 | } | ||
250 | case Q_QUOTAOFF: | ||
251 | return sb->s_qcop->quota_off(sb, type); | ||
252 | |||
253 | case Q_GETFMT: { | ||
254 | __u32 fmt; | ||
255 | |||
256 | down_read(&sb_dqopt(sb)->dqptr_sem); | ||
257 | if (!sb_has_quota_enabled(sb, type)) { | ||
258 | up_read(&sb_dqopt(sb)->dqptr_sem); | ||
259 | return -ESRCH; | ||
260 | } | ||
261 | fmt = sb_dqopt(sb)->info[type].dqi_format->qf_fmt_id; | ||
262 | up_read(&sb_dqopt(sb)->dqptr_sem); | ||
263 | if (copy_to_user(addr, &fmt, sizeof(fmt))) | ||
264 | return -EFAULT; | ||
265 | return 0; | ||
266 | } | ||
267 | case Q_GETINFO: { | ||
268 | struct if_dqinfo info; | ||
269 | |||
270 | if ((ret = sb->s_qcop->get_info(sb, type, &info))) | ||
271 | return ret; | ||
272 | if (copy_to_user(addr, &info, sizeof(info))) | ||
273 | return -EFAULT; | ||
274 | return 0; | ||
275 | } | ||
276 | case Q_SETINFO: { | ||
277 | struct if_dqinfo info; | ||
278 | |||
279 | if (copy_from_user(&info, addr, sizeof(info))) | ||
280 | return -EFAULT; | ||
281 | return sb->s_qcop->set_info(sb, type, &info); | ||
282 | } | ||
283 | case Q_GETQUOTA: { | ||
284 | struct if_dqblk idq; | ||
285 | |||
286 | if ((ret = sb->s_qcop->get_dqblk(sb, type, id, &idq))) | ||
287 | return ret; | ||
288 | if (copy_to_user(addr, &idq, sizeof(idq))) | ||
289 | return -EFAULT; | ||
290 | return 0; | ||
291 | } | ||
292 | case Q_SETQUOTA: { | ||
293 | struct if_dqblk idq; | ||
294 | |||
295 | if (copy_from_user(&idq, addr, sizeof(idq))) | ||
296 | return -EFAULT; | ||
297 | return sb->s_qcop->set_dqblk(sb, type, id, &idq); | ||
298 | } | ||
299 | case Q_SYNC: | ||
300 | sync_dquots(sb, type); | ||
301 | return 0; | ||
302 | |||
303 | case Q_XQUOTAON: | ||
304 | case Q_XQUOTAOFF: | ||
305 | case Q_XQUOTARM: { | ||
306 | __u32 flags; | ||
307 | |||
308 | if (copy_from_user(&flags, addr, sizeof(flags))) | ||
309 | return -EFAULT; | ||
310 | return sb->s_qcop->set_xstate(sb, flags, cmd); | ||
311 | } | ||
312 | case Q_XGETQSTAT: { | ||
313 | struct fs_quota_stat fqs; | ||
314 | |||
315 | if ((ret = sb->s_qcop->get_xstate(sb, &fqs))) | ||
316 | return ret; | ||
317 | if (copy_to_user(addr, &fqs, sizeof(fqs))) | ||
318 | return -EFAULT; | ||
319 | return 0; | ||
320 | } | ||
321 | case Q_XSETQLIM: { | ||
322 | struct fs_disk_quota fdq; | ||
323 | |||
324 | if (copy_from_user(&fdq, addr, sizeof(fdq))) | ||
325 | return -EFAULT; | ||
326 | return sb->s_qcop->set_xquota(sb, type, id, &fdq); | ||
327 | } | ||
328 | case Q_XGETQUOTA: { | ||
329 | struct fs_disk_quota fdq; | ||
330 | |||
331 | if ((ret = sb->s_qcop->get_xquota(sb, type, id, &fdq))) | ||
332 | return ret; | ||
333 | if (copy_to_user(addr, &fdq, sizeof(fdq))) | ||
334 | return -EFAULT; | ||
335 | return 0; | ||
336 | } | ||
337 | /* We never reach here unless validity check is broken */ | ||
338 | default: | ||
339 | BUG(); | ||
340 | } | ||
341 | return 0; | ||
342 | } | ||
343 | |||
344 | /* | ||
345 | * This is the system call interface. This communicates with | ||
346 | * the user-level programs. Currently this only supports diskquota | ||
347 | * calls. Maybe we need to add the process quotas etc. in the future, | ||
348 | * but we probably should use rlimits for that. | ||
349 | */ | ||
350 | asmlinkage long sys_quotactl(unsigned int cmd, const char __user *special, qid_t id, void __user *addr) | ||
351 | { | ||
352 | uint cmds, type; | ||
353 | struct super_block *sb = NULL; | ||
354 | struct block_device *bdev; | ||
355 | char *tmp; | ||
356 | int ret; | ||
357 | |||
358 | cmds = cmd >> SUBCMDSHIFT; | ||
359 | type = cmd & SUBCMDMASK; | ||
360 | |||
361 | if (cmds != Q_SYNC || special) { | ||
362 | tmp = getname(special); | ||
363 | if (IS_ERR(tmp)) | ||
364 | return PTR_ERR(tmp); | ||
365 | bdev = lookup_bdev(tmp); | ||
366 | putname(tmp); | ||
367 | if (IS_ERR(bdev)) | ||
368 | return PTR_ERR(bdev); | ||
369 | sb = get_super(bdev); | ||
370 | bdput(bdev); | ||
371 | if (!sb) | ||
372 | return -ENODEV; | ||
373 | } | ||
374 | |||
375 | ret = check_quotactl_valid(sb, type, cmds, id); | ||
376 | if (ret >= 0) | ||
377 | ret = do_quotactl(sb, type, cmds, id, addr); | ||
378 | if (sb) | ||
379 | drop_super(sb); | ||
380 | |||
381 | return ret; | ||
382 | } | ||