]>
Commit | Line | Data |
---|---|---|
1 | From: Jan Kara <jack@suse.cz> | |
2 | References: fate#302681 | |
3 | Subject: [PATCH 26/28] ocfs2: Enable quota accounting on mount, disable on umount | |
4 | Patch-mainline: 2.6.29? | |
5 | ||
6 | Enable quota usage tracking on mount and disable it on umount. Also | |
7 | add support for quota on and quota off quotactls and usrquota and | |
8 | grpquota mount options. | |
9 | ||
10 | Signed-off-by: Jan Kara <jack@suse.cz> | |
11 | --- | |
12 | fs/ocfs2/journal.c | 20 ++++ | |
13 | fs/ocfs2/ocfs2.h | 3 | |
14 | fs/ocfs2/super.c | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
15 | 3 files changed, 234 insertions(+), 3 deletions(-) | |
16 | ||
17 | Index: linux-2.6.27-ocfs2/fs/ocfs2/journal.c | |
18 | =================================================================== | |
19 | --- linux-2.6.27-ocfs2.orig/fs/ocfs2/journal.c | |
20 | +++ linux-2.6.27-ocfs2/fs/ocfs2/journal.c | |
21 | @@ -55,7 +55,7 @@ static int ocfs2_recover_node(struct ocf | |
22 | int node_num); | |
23 | static int __ocfs2_recovery_thread(void *arg); | |
24 | static int ocfs2_commit_cache(struct ocfs2_super *osb); | |
25 | -static int ocfs2_wait_on_mount(struct ocfs2_super *osb); | |
26 | +static int __ocfs2_wait_on_mount(struct ocfs2_super *osb, int quota); | |
27 | static int ocfs2_journal_toggle_dirty(struct ocfs2_super *osb, | |
28 | int dirty, int replayed); | |
29 | static int ocfs2_trylock_journal(struct ocfs2_super *osb, | |
30 | @@ -64,6 +64,17 @@ static int ocfs2_recover_orphans(struct | |
31 | int slot); | |
32 | static int ocfs2_commit_thread(void *arg); | |
33 | ||
34 | +static inline int ocfs2_wait_on_mount(struct ocfs2_super *osb) | |
35 | +{ | |
36 | + return __ocfs2_wait_on_mount(osb, 0); | |
37 | +} | |
38 | + | |
39 | +static inline int ocfs2_wait_on_quotas(struct ocfs2_super *osb) | |
40 | +{ | |
41 | + return __ocfs2_wait_on_mount(osb, 1); | |
42 | +} | |
43 | + | |
44 | + | |
45 | ||
46 | /* | |
47 | * The recovery_list is a simple linked list of node numbers to recover. | |
48 | @@ -913,6 +924,8 @@ void ocfs2_complete_recovery(struct work | |
49 | ||
50 | mlog(0, "Complete recovery for slot %d\n", item->lri_slot); | |
51 | ||
52 | + ocfs2_wait_on_quotas(osb); | |
53 | + | |
54 | la_dinode = item->lri_la_dinode; | |
55 | if (la_dinode) { | |
56 | mlog(0, "Clean up local alloc %llu\n", | |
57 | @@ -1659,13 +1672,14 @@ static int ocfs2_recover_orphans(struct | |
58 | return ret; | |
59 | } | |
60 | ||
61 | -static int ocfs2_wait_on_mount(struct ocfs2_super *osb) | |
62 | +static int __ocfs2_wait_on_mount(struct ocfs2_super *osb, int quota) | |
63 | { | |
64 | /* This check is good because ocfs2 will wait on our recovery | |
65 | * thread before changing it to something other than MOUNTED | |
66 | * or DISABLED. */ | |
67 | wait_event(osb->osb_mount_event, | |
68 | - atomic_read(&osb->vol_state) == VOLUME_MOUNTED || | |
69 | + (!quota && atomic_read(&osb->vol_state) == VOLUME_MOUNTED) || | |
70 | + atomic_read(&osb->vol_state) == VOLUME_MOUNTED_QUOTAS || | |
71 | atomic_read(&osb->vol_state) == VOLUME_DISABLED); | |
72 | ||
73 | /* If there's an error on mount, then we may never get to the | |
74 | Index: linux-2.6.27-ocfs2/fs/ocfs2/ocfs2.h | |
75 | =================================================================== | |
76 | --- linux-2.6.27-ocfs2.orig/fs/ocfs2/ocfs2.h | |
77 | +++ linux-2.6.27-ocfs2/fs/ocfs2/ocfs2.h | |
78 | @@ -161,6 +161,7 @@ enum ocfs2_vol_state | |
79 | { | |
80 | VOLUME_INIT = 0, | |
81 | VOLUME_MOUNTED, | |
82 | + VOLUME_MOUNTED_QUOTAS, | |
83 | VOLUME_DISMOUNTED, | |
84 | VOLUME_DISABLED | |
85 | }; | |
86 | @@ -196,6 +197,8 @@ enum ocfs2_mount_options | |
87 | OCFS2_MOUNT_NOUSERXATTR = 1 << 6, /* No user xattr */ | |
88 | OCFS2_MOUNT_INODE64 = 1 << 7, /* Allow inode numbers > 2^32 */ | |
89 | OCFS2_MOUNT_POSIX_ACL = 1 << 8, /* POSIX access control lists */ | |
90 | + OCFS2_MOUNT_USRQUOTA = 1 << 9, /* We support user quotas */ | |
91 | + OCFS2_MOUNT_GRPQUOTA = 1 << 10, /* We support group quotas */ | |
92 | }; | |
93 | ||
94 | #define OCFS2_OSB_SOFT_RO 0x0001 | |
95 | Index: linux-2.6.27-ocfs2/fs/ocfs2/super.c | |
96 | =================================================================== | |
97 | --- linux-2.6.27-ocfs2.orig/fs/ocfs2/super.c | |
98 | +++ linux-2.6.27-ocfs2/fs/ocfs2/super.c | |
99 | @@ -41,6 +41,7 @@ | |
100 | #include <linux/debugfs.h> | |
101 | #include <linux/mount.h> | |
102 | #include <linux/seq_file.h> | |
103 | +#include <linux/quotaops.h> | |
104 | ||
105 | #define MLOG_MASK_PREFIX ML_SUPER | |
106 | #include <cluster/masklog.h> | |
107 | @@ -127,6 +128,9 @@ static int ocfs2_get_sector(struct super | |
108 | static void ocfs2_write_super(struct super_block *sb); | |
109 | static struct inode *ocfs2_alloc_inode(struct super_block *sb); | |
110 | static void ocfs2_destroy_inode(struct inode *inode); | |
111 | +static int ocfs2_susp_quotas(struct ocfs2_super *osb, int unsuspend); | |
112 | +static int ocfs2_enable_quotas(struct ocfs2_super *osb); | |
113 | +static void ocfs2_disable_quotas(struct ocfs2_super *osb); | |
114 | ||
115 | static const struct super_operations ocfs2_sops = { | |
116 | .statfs = ocfs2_statfs, | |
117 | @@ -165,6 +169,8 @@ enum { | |
118 | Opt_inode64, | |
119 | Opt_acl, | |
120 | Opt_noacl, | |
121 | + Opt_usrquota, | |
122 | + Opt_grpquota, | |
123 | Opt_err, | |
124 | }; | |
125 | ||
126 | @@ -189,6 +195,8 @@ static match_table_t tokens = { | |
127 | {Opt_inode64, "inode64"}, | |
128 | {Opt_acl, "acl"}, | |
129 | {Opt_noacl, "noacl"}, | |
130 | + {Opt_usrquota, "usrquota"}, | |
131 | + {Opt_grpquota, "grpquota"}, | |
132 | {Opt_err, NULL} | |
133 | }; | |
134 | ||
135 | @@ -452,6 +460,12 @@ static int ocfs2_remount(struct super_bl | |
136 | ||
137 | /* We're going to/from readonly mode. */ | |
138 | if ((*flags & MS_RDONLY) != (sb->s_flags & MS_RDONLY)) { | |
139 | + /* Disable quota accounting before remounting RO */ | |
140 | + if (*flags & MS_RDONLY) { | |
141 | + ret = ocfs2_susp_quotas(osb, 0); | |
142 | + if (ret < 0) | |
143 | + goto out; | |
144 | + } | |
145 | /* Lock here so the check of HARD_RO and the potential | |
146 | * setting of SOFT_RO is atomic. */ | |
147 | spin_lock(&osb->osb_lock); | |
148 | @@ -487,6 +501,21 @@ static int ocfs2_remount(struct super_bl | |
149 | } | |
150 | unlock_osb: | |
151 | spin_unlock(&osb->osb_lock); | |
152 | + /* Enable quota accounting after remounting RW */ | |
153 | + if (!ret && !(*flags & MS_RDONLY)) { | |
154 | + if (sb_any_quota_suspended(sb)) | |
155 | + ret = ocfs2_susp_quotas(osb, 1); | |
156 | + else | |
157 | + ret = ocfs2_enable_quotas(osb); | |
158 | + if (ret < 0) { | |
159 | + /* Return back changes... */ | |
160 | + spin_lock(&osb->osb_lock); | |
161 | + sb->s_flags |= MS_RDONLY; | |
162 | + osb->osb_flags |= OCFS2_OSB_SOFT_RO; | |
163 | + spin_unlock(&osb->osb_lock); | |
164 | + goto out; | |
165 | + } | |
166 | + } | |
167 | } | |
168 | ||
169 | if (!ret) { | |
170 | @@ -647,6 +676,131 @@ static int ocfs2_verify_userspace_stack( | |
171 | return 0; | |
172 | } | |
173 | ||
174 | +static int ocfs2_susp_quotas(struct ocfs2_super *osb, int unsuspend) | |
175 | +{ | |
176 | + int type; | |
177 | + struct super_block *sb = osb->sb; | |
178 | + unsigned int feature[MAXQUOTAS] = { OCFS2_FEATURE_RO_COMPAT_USRQUOTA, | |
179 | + OCFS2_FEATURE_RO_COMPAT_GRPQUOTA}; | |
180 | + int status = 0; | |
181 | + | |
182 | + for (type = 0; type < MAXQUOTAS; type++) { | |
183 | + if (!OCFS2_HAS_RO_COMPAT_FEATURE(sb, feature[type])) | |
184 | + continue; | |
185 | + if (unsuspend) | |
186 | + status = vfs_quota_enable( | |
187 | + sb_dqopt(sb)->files[type], | |
188 | + type, QFMT_OCFS2, | |
189 | + DQUOT_SUSPENDED); | |
190 | + else | |
191 | + status = vfs_quota_disable(sb, type, | |
192 | + DQUOT_SUSPENDED); | |
193 | + if (status < 0) | |
194 | + break; | |
195 | + } | |
196 | + if (status < 0) | |
197 | + mlog(ML_ERROR, "Failed to suspend/unsuspend quotas on " | |
198 | + "remount (error = %d).\n", status); | |
199 | + return status; | |
200 | +} | |
201 | + | |
202 | +static int ocfs2_enable_quotas(struct ocfs2_super *osb) | |
203 | +{ | |
204 | + struct inode *inode[MAXQUOTAS] = { NULL, NULL }; | |
205 | + struct super_block *sb = osb->sb; | |
206 | + unsigned int feature[MAXQUOTAS] = { OCFS2_FEATURE_RO_COMPAT_USRQUOTA, | |
207 | + OCFS2_FEATURE_RO_COMPAT_GRPQUOTA}; | |
208 | + unsigned int ino[MAXQUOTAS] = { LOCAL_USER_QUOTA_SYSTEM_INODE, | |
209 | + LOCAL_GROUP_QUOTA_SYSTEM_INODE }; | |
210 | + int status; | |
211 | + int type; | |
212 | + | |
213 | + sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NEGATIVE_USAGE; | |
214 | + for (type = 0; type < MAXQUOTAS; type++) { | |
215 | + if (!OCFS2_HAS_RO_COMPAT_FEATURE(sb, feature[type])) | |
216 | + continue; | |
217 | + inode[type] = ocfs2_get_system_file_inode(osb, ino[type], | |
218 | + osb->slot_num); | |
219 | + if (!inode[type]) { | |
220 | + status = -ENOENT; | |
221 | + goto out_quota_off; | |
222 | + } | |
223 | + status = vfs_quota_enable(inode[type], type, QFMT_OCFS2, | |
224 | + DQUOT_USAGE_ENABLED); | |
225 | + if (status < 0) | |
226 | + goto out_quota_off; | |
227 | + } | |
228 | + | |
229 | + for (type = 0; type < MAXQUOTAS; type++) | |
230 | + iput(inode[type]); | |
231 | + return 0; | |
232 | +out_quota_off: | |
233 | + ocfs2_disable_quotas(osb); | |
234 | + for (type = 0; type < MAXQUOTAS; type++) | |
235 | + iput(inode[type]); | |
236 | + mlog_errno(status); | |
237 | + return status; | |
238 | +} | |
239 | + | |
240 | +static void ocfs2_disable_quotas(struct ocfs2_super *osb) | |
241 | +{ | |
242 | + int type; | |
243 | + struct inode *inode; | |
244 | + struct super_block *sb = osb->sb; | |
245 | + | |
246 | + /* We mostly ignore errors in this function because there's not much | |
247 | + * we can do when we see them */ | |
248 | + for (type = 0; type < MAXQUOTAS; type++) { | |
249 | + if (!sb_has_quota_loaded(sb, type)) | |
250 | + continue; | |
251 | + inode = igrab(sb->s_dquot.files[type]); | |
252 | + /* Turn off quotas. This will remove all dquot structures from | |
253 | + * memory and so they will be automatically synced to global | |
254 | + * quota files */ | |
255 | + vfs_quota_disable(sb, type, DQUOT_USAGE_ENABLED | | |
256 | + DQUOT_LIMITS_ENABLED); | |
257 | + if (!inode) | |
258 | + continue; | |
259 | + iput(inode); | |
260 | + } | |
261 | +} | |
262 | + | |
263 | +/* Handle quota on quotactl */ | |
264 | +static int ocfs2_quota_on(struct super_block *sb, int type, int format_id, | |
265 | + char *path, int remount) | |
266 | +{ | |
267 | + unsigned int feature[MAXQUOTAS] = { OCFS2_FEATURE_RO_COMPAT_USRQUOTA, | |
268 | + OCFS2_FEATURE_RO_COMPAT_GRPQUOTA}; | |
269 | + | |
270 | + if (!OCFS2_HAS_RO_COMPAT_FEATURE(sb, feature[type])) | |
271 | + return -EINVAL; | |
272 | + | |
273 | + if (remount) | |
274 | + return 0; /* Just ignore it has been handled in | |
275 | + * ocfs2_remount() */ | |
276 | + return vfs_quota_enable(sb_dqopt(sb)->files[type], type, | |
277 | + format_id, DQUOT_LIMITS_ENABLED); | |
278 | +} | |
279 | + | |
280 | +/* Handle quota off quotactl */ | |
281 | +static int ocfs2_quota_off(struct super_block *sb, int type, int remount) | |
282 | +{ | |
283 | + if (remount) | |
284 | + return 0; /* Ignore now and handle later in | |
285 | + * ocfs2_remount() */ | |
286 | + return vfs_quota_disable(sb, type, DQUOT_LIMITS_ENABLED); | |
287 | +} | |
288 | + | |
289 | +static struct quotactl_ops ocfs2_quotactl_ops = { | |
290 | + .quota_on = ocfs2_quota_on, | |
291 | + .quota_off = ocfs2_quota_off, | |
292 | + .quota_sync = vfs_quota_sync, | |
293 | + .get_info = vfs_get_dqinfo, | |
294 | + .set_info = vfs_set_dqinfo, | |
295 | + .get_dqblk = vfs_get_dqblk, | |
296 | + .set_dqblk = vfs_set_dqblk, | |
297 | +}; | |
298 | + | |
299 | static int ocfs2_fill_super(struct super_block *sb, void *data, int silent) | |
300 | { | |
301 | struct dentry *root; | |
302 | @@ -689,6 +843,22 @@ static int ocfs2_fill_super(struct super | |
303 | osb->osb_commit_interval = parsed_options.commit_interval; | |
304 | osb->local_alloc_default_bits = ocfs2_megabytes_to_clusters(sb, parsed_options.localalloc_opt); | |
305 | osb->local_alloc_bits = osb->local_alloc_default_bits; | |
306 | + if (osb->s_mount_opt & OCFS2_MOUNT_USRQUOTA && | |
307 | + !OCFS2_HAS_RO_COMPAT_FEATURE(sb, | |
308 | + OCFS2_FEATURE_RO_COMPAT_USRQUOTA)) { | |
309 | + status = -EINVAL; | |
310 | + mlog(ML_ERROR, "User quotas were requested, but this " | |
311 | + "filesystem does not have the feature enabled.\n"); | |
312 | + goto read_super_error; | |
313 | + } | |
314 | + if (osb->s_mount_opt & OCFS2_MOUNT_GRPQUOTA && | |
315 | + !OCFS2_HAS_RO_COMPAT_FEATURE(sb, | |
316 | + OCFS2_FEATURE_RO_COMPAT_GRPQUOTA)) { | |
317 | + status = -EINVAL; | |
318 | + mlog(ML_ERROR, "Group quotas were requested, but this " | |
319 | + "filesystem does not have the feature enabled.\n"); | |
320 | + goto read_super_error; | |
321 | + } | |
322 | ||
323 | status = ocfs2_verify_userspace_stack(osb, &parsed_options); | |
324 | if (status) | |
325 | @@ -793,6 +963,20 @@ static int ocfs2_fill_super(struct super | |
326 | atomic_set(&osb->vol_state, VOLUME_MOUNTED); | |
327 | wake_up(&osb->osb_mount_event); | |
328 | ||
329 | + /* Now we can initialize quotas because we can afford to wait | |
330 | + * for cluster locks recovery now. That also means that truncation | |
331 | + * log recovery can happen but that waits for proper quota setup */ | |
332 | + if (!(sb->s_flags & MS_RDONLY)) { | |
333 | + status = ocfs2_enable_quotas(osb); | |
334 | + if (status < 0) { | |
335 | + mlog_errno(status); | |
336 | + goto read_super_error; | |
337 | + } | |
338 | + } | |
339 | + /* Now we wake up again for processes waiting for quotas */ | |
340 | + atomic_set(&osb->vol_state, VOLUME_MOUNTED_QUOTAS); | |
341 | + wake_up(&osb->osb_mount_event); | |
342 | + | |
343 | mlog_exit(status); | |
344 | return status; | |
345 | ||
346 | @@ -993,6 +1177,28 @@ static int ocfs2_parse_options(struct su | |
347 | printk(KERN_INFO "ocfs2 (no)acl options not supported\n"); | |
348 | break; | |
349 | #endif | |
350 | + case Opt_usrquota: | |
351 | + /* We check only on remount, otherwise features | |
352 | + * aren't yet initialized. */ | |
353 | + if (is_remount && !OCFS2_HAS_RO_COMPAT_FEATURE(sb, | |
354 | + OCFS2_FEATURE_RO_COMPAT_USRQUOTA)) { | |
355 | + mlog(ML_ERROR, "User quota requested but " | |
356 | + "filesystem feature is not set\n"); | |
357 | + status = 0; | |
358 | + goto bail; | |
359 | + } | |
360 | + mopt->mount_opt |= OCFS2_MOUNT_USRQUOTA; | |
361 | + break; | |
362 | + case Opt_grpquota: | |
363 | + if (is_remount && !OCFS2_HAS_RO_COMPAT_FEATURE(sb, | |
364 | + OCFS2_FEATURE_RO_COMPAT_GRPQUOTA)) { | |
365 | + mlog(ML_ERROR, "Group quota requested but " | |
366 | + "filesystem feature is not set\n"); | |
367 | + status = 0; | |
368 | + goto bail; | |
369 | + } | |
370 | + mopt->mount_opt |= OCFS2_MOUNT_GRPQUOTA; | |
371 | + break; | |
372 | default: | |
373 | mlog(ML_ERROR, | |
374 | "Unrecognized mount option \"%s\" " | |
375 | @@ -1071,6 +1277,10 @@ static int ocfs2_show_options(struct seq | |
376 | else | |
377 | seq_printf(s, ",noacl"); | |
378 | #endif | |
379 | + if (opts & OCFS2_MOUNT_USRQUOTA) | |
380 | + seq_printf(s, ",usrquota"); | |
381 | + if (opts & OCFS2_MOUNT_GRPQUOTA) | |
382 | + seq_printf(s, ",grpquota"); | |
383 | ||
384 | return 0; | |
385 | } | |
386 | @@ -1396,6 +1606,8 @@ static void ocfs2_dismount_volume(struct | |
387 | osb = OCFS2_SB(sb); | |
388 | BUG_ON(!osb); | |
389 | ||
390 | + ocfs2_disable_quotas(osb); | |
391 | + | |
392 | ocfs2_shutdown_local_alloc(osb); | |
393 | ||
394 | ocfs2_truncate_log_shutdown(osb); | |
395 | @@ -1506,6 +1718,8 @@ static int ocfs2_initialize_super(struct | |
396 | sb->s_fs_info = osb; | |
397 | sb->s_op = &ocfs2_sops; | |
398 | sb->s_export_op = &ocfs2_export_ops; | |
399 | + sb->s_qcop = &ocfs2_quotactl_ops; | |
400 | + sb->dq_op = &ocfs2_quota_operations; | |
401 | sb->s_xattr = ocfs2_xattr_handlers; | |
402 | sb->s_time_gran = 1; | |
403 | sb->s_flags |= MS_NOATIME; |