]>
Commit | Line | Data |
---|---|---|
a860f6eb GH |
1 | /* -*- mode: c; c-basic-offset: 8; -*- |
2 | * vim: noexpandtab sw=8 ts=8 sts=0: | |
3 | * | |
4 | * filecheck.c | |
5 | * | |
6 | * Code which implements online file check. | |
7 | * | |
8 | * Copyright (C) 2016 SuSE. All rights reserved. | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or | |
11 | * modify it under the terms of the GNU General Public | |
12 | * License as published by the Free Software Foundation, version 2. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | * General Public License for more details. | |
18 | */ | |
19 | ||
20 | #include <linux/list.h> | |
21 | #include <linux/spinlock.h> | |
22 | #include <linux/module.h> | |
23 | #include <linux/slab.h> | |
24 | #include <linux/kmod.h> | |
25 | #include <linux/fs.h> | |
26 | #include <linux/kobject.h> | |
27 | #include <linux/sysfs.h> | |
28 | #include <linux/sysctl.h> | |
29 | #include <cluster/masklog.h> | |
30 | ||
31 | #include "ocfs2.h" | |
32 | #include "ocfs2_fs.h" | |
33 | #include "stackglue.h" | |
34 | #include "inode.h" | |
35 | ||
36 | #include "filecheck.h" | |
37 | ||
38 | ||
39 | /* File check error strings, | |
40 | * must correspond with error number in header file. | |
41 | */ | |
42 | static const char * const ocfs2_filecheck_errs[] = { | |
43 | "SUCCESS", | |
44 | "FAILED", | |
45 | "INPROGRESS", | |
46 | "READONLY", | |
47 | "INJBD", | |
48 | "INVALIDINO", | |
49 | "BLOCKECC", | |
50 | "BLOCKNO", | |
51 | "VALIDFLAG", | |
52 | "GENERATION", | |
53 | "UNSUPPORTED" | |
54 | }; | |
55 | ||
a860f6eb GH |
56 | struct ocfs2_filecheck_entry { |
57 | struct list_head fe_list; | |
58 | unsigned long fe_ino; | |
59 | unsigned int fe_type; | |
60 | unsigned int fe_done:1; | |
61 | unsigned int fe_status:31; | |
62 | }; | |
63 | ||
64 | struct ocfs2_filecheck_args { | |
65 | unsigned int fa_type; | |
66 | union { | |
67 | unsigned long fa_ino; | |
68 | unsigned int fa_len; | |
69 | }; | |
70 | }; | |
71 | ||
72 | static const char * | |
73 | ocfs2_filecheck_error(int errno) | |
74 | { | |
75 | if (!errno) | |
76 | return ocfs2_filecheck_errs[errno]; | |
77 | ||
78 | BUG_ON(errno < OCFS2_FILECHECK_ERR_START || | |
79 | errno > OCFS2_FILECHECK_ERR_END); | |
80 | return ocfs2_filecheck_errs[errno - OCFS2_FILECHECK_ERR_START + 1]; | |
81 | } | |
82 | ||
5f483c4a GH |
83 | static ssize_t ocfs2_filecheck_attr_show(struct kobject *kobj, |
84 | struct kobj_attribute *attr, | |
85 | char *buf); | |
86 | static ssize_t ocfs2_filecheck_attr_store(struct kobject *kobj, | |
87 | struct kobj_attribute *attr, | |
88 | const char *buf, size_t count); | |
89 | static struct kobj_attribute ocfs2_filecheck_attr_chk = | |
a860f6eb | 90 | __ATTR(check, S_IRUSR | S_IWUSR, |
5f483c4a GH |
91 | ocfs2_filecheck_attr_show, |
92 | ocfs2_filecheck_attr_store); | |
93 | static struct kobj_attribute ocfs2_filecheck_attr_fix = | |
a860f6eb | 94 | __ATTR(fix, S_IRUSR | S_IWUSR, |
5f483c4a GH |
95 | ocfs2_filecheck_attr_show, |
96 | ocfs2_filecheck_attr_store); | |
97 | static struct kobj_attribute ocfs2_filecheck_attr_set = | |
a860f6eb | 98 | __ATTR(set, S_IRUSR | S_IWUSR, |
5f483c4a GH |
99 | ocfs2_filecheck_attr_show, |
100 | ocfs2_filecheck_attr_store); | |
101 | static struct attribute *ocfs2_filecheck_attrs[] = { | |
102 | &ocfs2_filecheck_attr_chk.attr, | |
103 | &ocfs2_filecheck_attr_fix.attr, | |
104 | &ocfs2_filecheck_attr_set.attr, | |
105 | NULL | |
106 | }; | |
107 | ||
108 | static void ocfs2_filecheck_release(struct kobject *kobj) | |
109 | { | |
110 | struct ocfs2_filecheck_sysfs_entry *entry = container_of(kobj, | |
111 | struct ocfs2_filecheck_sysfs_entry, fs_kobj); | |
112 | ||
113 | complete(&entry->fs_kobj_unregister); | |
114 | } | |
115 | ||
116 | static ssize_t | |
117 | ocfs2_filecheck_show(struct kobject *kobj, struct attribute *attr, char *buf) | |
118 | { | |
119 | ssize_t ret = -EIO; | |
120 | struct kobj_attribute *kattr = container_of(attr, | |
121 | struct kobj_attribute, attr); | |
122 | ||
123 | kobject_get(kobj); | |
124 | if (kattr->show) | |
125 | ret = kattr->show(kobj, kattr, buf); | |
126 | kobject_put(kobj); | |
127 | return ret; | |
128 | } | |
129 | ||
130 | static ssize_t | |
131 | ocfs2_filecheck_store(struct kobject *kobj, struct attribute *attr, | |
132 | const char *buf, size_t count) | |
133 | { | |
134 | ssize_t ret = -EIO; | |
135 | struct kobj_attribute *kattr = container_of(attr, | |
136 | struct kobj_attribute, attr); | |
137 | ||
138 | kobject_get(kobj); | |
139 | if (kattr->store) | |
140 | ret = kattr->store(kobj, kattr, buf, count); | |
141 | kobject_put(kobj); | |
142 | return ret; | |
143 | } | |
144 | ||
145 | static const struct sysfs_ops ocfs2_filecheck_ops = { | |
146 | .show = ocfs2_filecheck_show, | |
147 | .store = ocfs2_filecheck_store, | |
148 | }; | |
149 | ||
150 | static struct kobj_type ocfs2_ktype_filecheck = { | |
151 | .default_attrs = ocfs2_filecheck_attrs, | |
152 | .sysfs_ops = &ocfs2_filecheck_ops, | |
153 | .release = ocfs2_filecheck_release, | |
154 | }; | |
a860f6eb | 155 | |
a860f6eb GH |
156 | static void |
157 | ocfs2_filecheck_sysfs_free(struct ocfs2_filecheck_sysfs_entry *entry) | |
158 | { | |
159 | struct ocfs2_filecheck_entry *p; | |
160 | ||
a860f6eb GH |
161 | spin_lock(&entry->fs_fcheck->fc_lock); |
162 | while (!list_empty(&entry->fs_fcheck->fc_head)) { | |
163 | p = list_first_entry(&entry->fs_fcheck->fc_head, | |
164 | struct ocfs2_filecheck_entry, fe_list); | |
165 | list_del(&p->fe_list); | |
166 | BUG_ON(!p->fe_done); /* To free a undone file check entry */ | |
167 | kfree(p); | |
168 | } | |
169 | spin_unlock(&entry->fs_fcheck->fc_lock); | |
170 | ||
a860f6eb | 171 | kfree(entry->fs_fcheck); |
5f483c4a | 172 | entry->fs_fcheck = NULL; |
a860f6eb GH |
173 | } |
174 | ||
5f483c4a | 175 | int ocfs2_filecheck_create_sysfs(struct ocfs2_super *osb) |
a860f6eb | 176 | { |
5f483c4a GH |
177 | int ret; |
178 | struct ocfs2_filecheck *fcheck; | |
179 | struct ocfs2_filecheck_sysfs_entry *entry = &osb->osb_fc_ent; | |
a860f6eb GH |
180 | |
181 | fcheck = kmalloc(sizeof(struct ocfs2_filecheck), GFP_NOFS); | |
5f483c4a GH |
182 | if (!fcheck) |
183 | return -ENOMEM; | |
a860f6eb | 184 | |
5f483c4a GH |
185 | INIT_LIST_HEAD(&fcheck->fc_head); |
186 | spin_lock_init(&fcheck->fc_lock); | |
187 | fcheck->fc_max = OCFS2_FILECHECK_MINSIZE; | |
188 | fcheck->fc_size = 0; | |
189 | fcheck->fc_done = 0; | |
190 | ||
191 | entry->fs_kobj.kset = osb->osb_dev_kset; | |
192 | init_completion(&entry->fs_kobj_unregister); | |
193 | ret = kobject_init_and_add(&entry->fs_kobj, &ocfs2_ktype_filecheck, | |
194 | NULL, "filecheck"); | |
195 | if (ret) { | |
196 | kfree(fcheck); | |
197 | return ret; | |
a860f6eb GH |
198 | } |
199 | ||
5f483c4a | 200 | entry->fs_fcheck = fcheck; |
a860f6eb | 201 | return 0; |
a860f6eb GH |
202 | } |
203 | ||
5f483c4a | 204 | void ocfs2_filecheck_remove_sysfs(struct ocfs2_super *osb) |
a860f6eb | 205 | { |
5f483c4a GH |
206 | if (!osb->osb_fc_ent.fs_fcheck) |
207 | return; | |
208 | ||
209 | kobject_del(&osb->osb_fc_ent.fs_kobj); | |
210 | kobject_put(&osb->osb_fc_ent.fs_kobj); | |
211 | wait_for_completion(&osb->osb_fc_ent.fs_kobj_unregister); | |
212 | ocfs2_filecheck_sysfs_free(&osb->osb_fc_ent); | |
a860f6eb GH |
213 | } |
214 | ||
215 | static int | |
216 | ocfs2_filecheck_erase_entries(struct ocfs2_filecheck_sysfs_entry *ent, | |
217 | unsigned int count); | |
218 | static int | |
219 | ocfs2_filecheck_adjust_max(struct ocfs2_filecheck_sysfs_entry *ent, | |
220 | unsigned int len) | |
221 | { | |
222 | int ret; | |
223 | ||
224 | if ((len < OCFS2_FILECHECK_MINSIZE) || (len > OCFS2_FILECHECK_MAXSIZE)) | |
225 | return -EINVAL; | |
226 | ||
227 | spin_lock(&ent->fs_fcheck->fc_lock); | |
228 | if (len < (ent->fs_fcheck->fc_size - ent->fs_fcheck->fc_done)) { | |
8fc2cb4b | 229 | mlog(ML_NOTICE, |
a860f6eb GH |
230 | "Cannot set online file check maximum entry number " |
231 | "to %u due to too many pending entries(%u)\n", | |
232 | len, ent->fs_fcheck->fc_size - ent->fs_fcheck->fc_done); | |
233 | ret = -EBUSY; | |
234 | } else { | |
235 | if (len < ent->fs_fcheck->fc_size) | |
236 | BUG_ON(!ocfs2_filecheck_erase_entries(ent, | |
237 | ent->fs_fcheck->fc_size - len)); | |
238 | ||
239 | ent->fs_fcheck->fc_max = len; | |
240 | ret = 0; | |
241 | } | |
242 | spin_unlock(&ent->fs_fcheck->fc_lock); | |
243 | ||
244 | return ret; | |
245 | } | |
246 | ||
247 | #define OCFS2_FILECHECK_ARGS_LEN 24 | |
248 | static int | |
249 | ocfs2_filecheck_args_get_long(const char *buf, size_t count, | |
250 | unsigned long *val) | |
251 | { | |
252 | char buffer[OCFS2_FILECHECK_ARGS_LEN]; | |
253 | ||
254 | memcpy(buffer, buf, count); | |
255 | buffer[count] = '\0'; | |
256 | ||
257 | if (kstrtoul(buffer, 0, val)) | |
258 | return 1; | |
259 | ||
260 | return 0; | |
261 | } | |
262 | ||
263 | static int | |
264 | ocfs2_filecheck_type_parse(const char *name, unsigned int *type) | |
265 | { | |
266 | if (!strncmp(name, "fix", 4)) | |
267 | *type = OCFS2_FILECHECK_TYPE_FIX; | |
268 | else if (!strncmp(name, "check", 6)) | |
269 | *type = OCFS2_FILECHECK_TYPE_CHK; | |
270 | else if (!strncmp(name, "set", 4)) | |
271 | *type = OCFS2_FILECHECK_TYPE_SET; | |
272 | else | |
273 | return 1; | |
274 | ||
275 | return 0; | |
276 | } | |
277 | ||
278 | static int | |
279 | ocfs2_filecheck_args_parse(const char *name, const char *buf, size_t count, | |
280 | struct ocfs2_filecheck_args *args) | |
281 | { | |
282 | unsigned long val = 0; | |
283 | unsigned int type; | |
284 | ||
285 | /* too short/long args length */ | |
286 | if ((count < 1) || (count >= OCFS2_FILECHECK_ARGS_LEN)) | |
287 | return 1; | |
288 | ||
289 | if (ocfs2_filecheck_type_parse(name, &type)) | |
290 | return 1; | |
291 | if (ocfs2_filecheck_args_get_long(buf, count, &val)) | |
292 | return 1; | |
293 | ||
294 | if (val <= 0) | |
295 | return 1; | |
296 | ||
297 | args->fa_type = type; | |
298 | if (type == OCFS2_FILECHECK_TYPE_SET) | |
299 | args->fa_len = (unsigned int)val; | |
300 | else | |
301 | args->fa_ino = val; | |
302 | ||
303 | return 0; | |
304 | } | |
305 | ||
5f483c4a | 306 | static ssize_t ocfs2_filecheck_attr_show(struct kobject *kobj, |
a860f6eb GH |
307 | struct kobj_attribute *attr, |
308 | char *buf) | |
309 | { | |
310 | ||
311 | ssize_t ret = 0, total = 0, remain = PAGE_SIZE; | |
312 | unsigned int type; | |
313 | struct ocfs2_filecheck_entry *p; | |
5f483c4a GH |
314 | struct ocfs2_filecheck_sysfs_entry *ent = container_of(kobj, |
315 | struct ocfs2_filecheck_sysfs_entry, fs_kobj); | |
a860f6eb GH |
316 | |
317 | if (ocfs2_filecheck_type_parse(attr->attr.name, &type)) | |
318 | return -EINVAL; | |
319 | ||
a860f6eb GH |
320 | if (type == OCFS2_FILECHECK_TYPE_SET) { |
321 | spin_lock(&ent->fs_fcheck->fc_lock); | |
322 | total = snprintf(buf, remain, "%u\n", ent->fs_fcheck->fc_max); | |
323 | spin_unlock(&ent->fs_fcheck->fc_lock); | |
324 | goto exit; | |
325 | } | |
326 | ||
327 | ret = snprintf(buf, remain, "INO\t\tDONE\tERROR\n"); | |
328 | total += ret; | |
329 | remain -= ret; | |
330 | spin_lock(&ent->fs_fcheck->fc_lock); | |
331 | list_for_each_entry(p, &ent->fs_fcheck->fc_head, fe_list) { | |
332 | if (p->fe_type != type) | |
333 | continue; | |
334 | ||
335 | ret = snprintf(buf + total, remain, "%lu\t\t%u\t%s\n", | |
336 | p->fe_ino, p->fe_done, | |
337 | ocfs2_filecheck_error(p->fe_status)); | |
338 | if (ret < 0) { | |
339 | total = ret; | |
340 | break; | |
341 | } | |
342 | if (ret == remain) { | |
343 | /* snprintf() didn't fit */ | |
344 | total = -E2BIG; | |
345 | break; | |
346 | } | |
347 | total += ret; | |
348 | remain -= ret; | |
349 | } | |
350 | spin_unlock(&ent->fs_fcheck->fc_lock); | |
351 | ||
352 | exit: | |
a860f6eb GH |
353 | return total; |
354 | } | |
355 | ||
39ec3774 GH |
356 | static inline int |
357 | ocfs2_filecheck_is_dup_entry(struct ocfs2_filecheck_sysfs_entry *ent, | |
358 | unsigned long ino) | |
359 | { | |
360 | struct ocfs2_filecheck_entry *p; | |
361 | ||
362 | list_for_each_entry(p, &ent->fs_fcheck->fc_head, fe_list) { | |
363 | if (!p->fe_done) { | |
364 | if (p->fe_ino == ino) | |
365 | return 1; | |
366 | } | |
367 | } | |
368 | ||
369 | return 0; | |
370 | } | |
371 | ||
5f483c4a | 372 | static inline int |
a860f6eb GH |
373 | ocfs2_filecheck_erase_entry(struct ocfs2_filecheck_sysfs_entry *ent) |
374 | { | |
375 | struct ocfs2_filecheck_entry *p; | |
376 | ||
377 | list_for_each_entry(p, &ent->fs_fcheck->fc_head, fe_list) { | |
378 | if (p->fe_done) { | |
379 | list_del(&p->fe_list); | |
380 | kfree(p); | |
381 | ent->fs_fcheck->fc_size--; | |
382 | ent->fs_fcheck->fc_done--; | |
383 | return 1; | |
384 | } | |
385 | } | |
386 | ||
387 | return 0; | |
388 | } | |
389 | ||
390 | static int | |
391 | ocfs2_filecheck_erase_entries(struct ocfs2_filecheck_sysfs_entry *ent, | |
392 | unsigned int count) | |
393 | { | |
394 | unsigned int i = 0; | |
395 | unsigned int ret = 0; | |
396 | ||
397 | while (i++ < count) { | |
398 | if (ocfs2_filecheck_erase_entry(ent)) | |
399 | ret++; | |
400 | else | |
401 | break; | |
402 | } | |
403 | ||
404 | return (ret == count ? 1 : 0); | |
405 | } | |
406 | ||
407 | static void | |
408 | ocfs2_filecheck_done_entry(struct ocfs2_filecheck_sysfs_entry *ent, | |
409 | struct ocfs2_filecheck_entry *entry) | |
410 | { | |
a860f6eb | 411 | spin_lock(&ent->fs_fcheck->fc_lock); |
8fc2cb4b | 412 | entry->fe_done = 1; |
a860f6eb GH |
413 | ent->fs_fcheck->fc_done++; |
414 | spin_unlock(&ent->fs_fcheck->fc_lock); | |
415 | } | |
416 | ||
417 | static unsigned int | |
5f483c4a | 418 | ocfs2_filecheck_handle(struct ocfs2_super *osb, |
a860f6eb GH |
419 | unsigned long ino, unsigned int flags) |
420 | { | |
421 | unsigned int ret = OCFS2_FILECHECK_ERR_SUCCESS; | |
422 | struct inode *inode = NULL; | |
423 | int rc; | |
424 | ||
5f483c4a | 425 | inode = ocfs2_iget(osb, ino, flags, 0); |
a860f6eb GH |
426 | if (IS_ERR(inode)) { |
427 | rc = (int)(-(long)inode); | |
428 | if (rc >= OCFS2_FILECHECK_ERR_START && | |
429 | rc < OCFS2_FILECHECK_ERR_END) | |
430 | ret = rc; | |
431 | else | |
432 | ret = OCFS2_FILECHECK_ERR_FAILED; | |
433 | } else | |
434 | iput(inode); | |
435 | ||
436 | return ret; | |
437 | } | |
438 | ||
439 | static void | |
440 | ocfs2_filecheck_handle_entry(struct ocfs2_filecheck_sysfs_entry *ent, | |
441 | struct ocfs2_filecheck_entry *entry) | |
442 | { | |
5f483c4a GH |
443 | struct ocfs2_super *osb = container_of(ent, struct ocfs2_super, |
444 | osb_fc_ent); | |
445 | ||
a860f6eb | 446 | if (entry->fe_type == OCFS2_FILECHECK_TYPE_CHK) |
5f483c4a | 447 | entry->fe_status = ocfs2_filecheck_handle(osb, |
a860f6eb GH |
448 | entry->fe_ino, OCFS2_FI_FLAG_FILECHECK_CHK); |
449 | else if (entry->fe_type == OCFS2_FILECHECK_TYPE_FIX) | |
5f483c4a | 450 | entry->fe_status = ocfs2_filecheck_handle(osb, |
a860f6eb GH |
451 | entry->fe_ino, OCFS2_FI_FLAG_FILECHECK_FIX); |
452 | else | |
453 | entry->fe_status = OCFS2_FILECHECK_ERR_UNSUPPORTED; | |
454 | ||
455 | ocfs2_filecheck_done_entry(ent, entry); | |
456 | } | |
457 | ||
5f483c4a | 458 | static ssize_t ocfs2_filecheck_attr_store(struct kobject *kobj, |
a860f6eb GH |
459 | struct kobj_attribute *attr, |
460 | const char *buf, size_t count) | |
461 | { | |
5f483c4a | 462 | ssize_t ret = 0; |
a860f6eb GH |
463 | struct ocfs2_filecheck_args args; |
464 | struct ocfs2_filecheck_entry *entry; | |
5f483c4a GH |
465 | struct ocfs2_filecheck_sysfs_entry *ent = container_of(kobj, |
466 | struct ocfs2_filecheck_sysfs_entry, fs_kobj); | |
a860f6eb GH |
467 | |
468 | if (count == 0) | |
469 | return count; | |
470 | ||
5f483c4a | 471 | if (ocfs2_filecheck_args_parse(attr->attr.name, buf, count, &args)) |
a860f6eb | 472 | return -EINVAL; |
a860f6eb GH |
473 | |
474 | if (args.fa_type == OCFS2_FILECHECK_TYPE_SET) { | |
475 | ret = ocfs2_filecheck_adjust_max(ent, args.fa_len); | |
476 | goto exit; | |
477 | } | |
478 | ||
479 | entry = kmalloc(sizeof(struct ocfs2_filecheck_entry), GFP_NOFS); | |
480 | if (!entry) { | |
481 | ret = -ENOMEM; | |
482 | goto exit; | |
483 | } | |
484 | ||
485 | spin_lock(&ent->fs_fcheck->fc_lock); | |
39ec3774 GH |
486 | if (ocfs2_filecheck_is_dup_entry(ent, args.fa_ino)) { |
487 | ret = -EEXIST; | |
488 | kfree(entry); | |
489 | } else if ((ent->fs_fcheck->fc_size >= ent->fs_fcheck->fc_max) && | |
5f483c4a | 490 | (ent->fs_fcheck->fc_done == 0)) { |
8fc2cb4b | 491 | mlog(ML_NOTICE, |
a860f6eb GH |
492 | "Cannot do more file check " |
493 | "since file check queue(%u) is full now\n", | |
494 | ent->fs_fcheck->fc_max); | |
8fc2cb4b | 495 | ret = -EAGAIN; |
a860f6eb GH |
496 | kfree(entry); |
497 | } else { | |
498 | if ((ent->fs_fcheck->fc_size >= ent->fs_fcheck->fc_max) && | |
499 | (ent->fs_fcheck->fc_done > 0)) { | |
500 | /* Delete the oldest entry which was done, | |
501 | * make sure the entry size in list does | |
502 | * not exceed maximum value | |
503 | */ | |
504 | BUG_ON(!ocfs2_filecheck_erase_entry(ent)); | |
505 | } | |
506 | ||
507 | entry->fe_ino = args.fa_ino; | |
508 | entry->fe_type = args.fa_type; | |
509 | entry->fe_done = 0; | |
510 | entry->fe_status = OCFS2_FILECHECK_ERR_INPROGRESS; | |
511 | list_add_tail(&entry->fe_list, &ent->fs_fcheck->fc_head); | |
512 | ent->fs_fcheck->fc_size++; | |
513 | } | |
514 | spin_unlock(&ent->fs_fcheck->fc_lock); | |
515 | ||
516 | if (!ret) | |
517 | ocfs2_filecheck_handle_entry(ent, entry); | |
518 | ||
519 | exit: | |
a860f6eb GH |
520 | return (!ret ? count : ret); |
521 | } |