1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C), 2008-2021, OPPO Mobile Comm Corp., Ltd. |
4 | * https://www.oppo.com/ |
5 | */ |
6 | #include <linux/sysfs.h> |
7 | #include <linux/kobject.h> |
8 | |
9 | #include "internal.h" |
10 | |
11 | enum { |
12 | attr_feature, |
13 | attr_pointer_ui, |
14 | attr_pointer_bool, |
15 | }; |
16 | |
17 | enum { |
18 | struct_erofs_sb_info, |
19 | struct_erofs_mount_opts, |
20 | }; |
21 | |
22 | struct erofs_attr { |
23 | struct attribute attr; |
24 | short attr_id; |
25 | int struct_type, offset; |
26 | }; |
27 | |
28 | #define EROFS_ATTR(_name, _mode, _id) \ |
29 | static struct erofs_attr erofs_attr_##_name = { \ |
30 | .attr = {.name = __stringify(_name), .mode = _mode }, \ |
31 | .attr_id = attr_##_id, \ |
32 | } |
33 | #define EROFS_ATTR_FUNC(_name, _mode) EROFS_ATTR(_name, _mode, _name) |
34 | #define EROFS_ATTR_FEATURE(_name) EROFS_ATTR(_name, 0444, feature) |
35 | |
36 | #define EROFS_ATTR_OFFSET(_name, _mode, _id, _struct) \ |
37 | static struct erofs_attr erofs_attr_##_name = { \ |
38 | .attr = {.name = __stringify(_name), .mode = _mode }, \ |
39 | .attr_id = attr_##_id, \ |
40 | .struct_type = struct_##_struct, \ |
41 | .offset = offsetof(struct _struct, _name),\ |
42 | } |
43 | |
44 | #define EROFS_ATTR_RW(_name, _id, _struct) \ |
45 | EROFS_ATTR_OFFSET(_name, 0644, _id, _struct) |
46 | |
47 | #define EROFS_RO_ATTR(_name, _id, _struct) \ |
48 | EROFS_ATTR_OFFSET(_name, 0444, _id, _struct) |
49 | |
50 | #define EROFS_ATTR_RW_UI(_name, _struct) \ |
51 | EROFS_ATTR_RW(_name, pointer_ui, _struct) |
52 | |
53 | #define EROFS_ATTR_RW_BOOL(_name, _struct) \ |
54 | EROFS_ATTR_RW(_name, pointer_bool, _struct) |
55 | |
56 | #define ATTR_LIST(name) (&erofs_attr_##name.attr) |
57 | |
58 | #ifdef CONFIG_EROFS_FS_ZIP |
59 | EROFS_ATTR_RW_UI(sync_decompress, erofs_mount_opts); |
60 | #endif |
61 | |
62 | static struct attribute *erofs_attrs[] = { |
63 | #ifdef CONFIG_EROFS_FS_ZIP |
64 | ATTR_LIST(sync_decompress), |
65 | #endif |
66 | NULL, |
67 | }; |
68 | ATTRIBUTE_GROUPS(erofs); |
69 | |
70 | /* Features this copy of erofs supports */ |
71 | EROFS_ATTR_FEATURE(zero_padding); |
72 | EROFS_ATTR_FEATURE(compr_cfgs); |
73 | EROFS_ATTR_FEATURE(big_pcluster); |
74 | EROFS_ATTR_FEATURE(chunked_file); |
75 | EROFS_ATTR_FEATURE(device_table); |
76 | EROFS_ATTR_FEATURE(compr_head2); |
77 | EROFS_ATTR_FEATURE(sb_chksum); |
78 | EROFS_ATTR_FEATURE(ztailpacking); |
79 | EROFS_ATTR_FEATURE(fragments); |
80 | EROFS_ATTR_FEATURE(dedupe); |
81 | |
82 | static struct attribute *erofs_feat_attrs[] = { |
83 | ATTR_LIST(zero_padding), |
84 | ATTR_LIST(compr_cfgs), |
85 | ATTR_LIST(big_pcluster), |
86 | ATTR_LIST(chunked_file), |
87 | ATTR_LIST(device_table), |
88 | ATTR_LIST(compr_head2), |
89 | ATTR_LIST(sb_chksum), |
90 | ATTR_LIST(ztailpacking), |
91 | ATTR_LIST(fragments), |
92 | ATTR_LIST(dedupe), |
93 | NULL, |
94 | }; |
95 | ATTRIBUTE_GROUPS(erofs_feat); |
96 | |
97 | static unsigned char *__struct_ptr(struct erofs_sb_info *sbi, |
98 | int struct_type, int offset) |
99 | { |
100 | if (struct_type == struct_erofs_sb_info) |
101 | return (unsigned char *)sbi + offset; |
102 | if (struct_type == struct_erofs_mount_opts) |
103 | return (unsigned char *)&sbi->opt + offset; |
104 | return NULL; |
105 | } |
106 | |
107 | static ssize_t erofs_attr_show(struct kobject *kobj, |
108 | struct attribute *attr, char *buf) |
109 | { |
110 | struct erofs_sb_info *sbi = container_of(kobj, struct erofs_sb_info, |
111 | s_kobj); |
112 | struct erofs_attr *a = container_of(attr, struct erofs_attr, attr); |
113 | unsigned char *ptr = __struct_ptr(sbi, struct_type: a->struct_type, offset: a->offset); |
114 | |
115 | switch (a->attr_id) { |
116 | case attr_feature: |
117 | return sysfs_emit(buf, fmt: "supported\n" ); |
118 | case attr_pointer_ui: |
119 | if (!ptr) |
120 | return 0; |
121 | return sysfs_emit(buf, fmt: "%u\n" , *(unsigned int *)ptr); |
122 | case attr_pointer_bool: |
123 | if (!ptr) |
124 | return 0; |
125 | return sysfs_emit(buf, fmt: "%d\n" , *(bool *)ptr); |
126 | } |
127 | return 0; |
128 | } |
129 | |
130 | static ssize_t erofs_attr_store(struct kobject *kobj, struct attribute *attr, |
131 | const char *buf, size_t len) |
132 | { |
133 | struct erofs_sb_info *sbi = container_of(kobj, struct erofs_sb_info, |
134 | s_kobj); |
135 | struct erofs_attr *a = container_of(attr, struct erofs_attr, attr); |
136 | unsigned char *ptr = __struct_ptr(sbi, struct_type: a->struct_type, offset: a->offset); |
137 | unsigned long t; |
138 | int ret; |
139 | |
140 | switch (a->attr_id) { |
141 | case attr_pointer_ui: |
142 | if (!ptr) |
143 | return 0; |
144 | ret = kstrtoul(s: skip_spaces(buf), base: 0, res: &t); |
145 | if (ret) |
146 | return ret; |
147 | if (t != (unsigned int)t) |
148 | return -ERANGE; |
149 | #ifdef CONFIG_EROFS_FS_ZIP |
150 | if (!strcmp(a->attr.name, "sync_decompress" ) && |
151 | (t > EROFS_SYNC_DECOMPRESS_FORCE_OFF)) |
152 | return -EINVAL; |
153 | #endif |
154 | *(unsigned int *)ptr = t; |
155 | return len; |
156 | case attr_pointer_bool: |
157 | if (!ptr) |
158 | return 0; |
159 | ret = kstrtoul(s: skip_spaces(buf), base: 0, res: &t); |
160 | if (ret) |
161 | return ret; |
162 | if (t != 0 && t != 1) |
163 | return -EINVAL; |
164 | *(bool *)ptr = !!t; |
165 | return len; |
166 | } |
167 | return 0; |
168 | } |
169 | |
170 | static void erofs_sb_release(struct kobject *kobj) |
171 | { |
172 | struct erofs_sb_info *sbi = container_of(kobj, struct erofs_sb_info, |
173 | s_kobj); |
174 | complete(&sbi->s_kobj_unregister); |
175 | } |
176 | |
177 | static const struct sysfs_ops erofs_attr_ops = { |
178 | .show = erofs_attr_show, |
179 | .store = erofs_attr_store, |
180 | }; |
181 | |
182 | static const struct kobj_type erofs_sb_ktype = { |
183 | .default_groups = erofs_groups, |
184 | .sysfs_ops = &erofs_attr_ops, |
185 | .release = erofs_sb_release, |
186 | }; |
187 | |
188 | static const struct kobj_type erofs_ktype = { |
189 | .sysfs_ops = &erofs_attr_ops, |
190 | }; |
191 | |
192 | static struct kset erofs_root = { |
193 | .kobj = {.ktype = &erofs_ktype}, |
194 | }; |
195 | |
196 | static const struct kobj_type erofs_feat_ktype = { |
197 | .default_groups = erofs_feat_groups, |
198 | .sysfs_ops = &erofs_attr_ops, |
199 | }; |
200 | |
201 | static struct kobject erofs_feat = { |
202 | .kset = &erofs_root, |
203 | }; |
204 | |
205 | int erofs_register_sysfs(struct super_block *sb) |
206 | { |
207 | struct erofs_sb_info *sbi = EROFS_SB(sb); |
208 | char *name; |
209 | char *str = NULL; |
210 | int err; |
211 | |
212 | if (erofs_is_fscache_mode(sb)) { |
213 | if (sbi->domain_id) { |
214 | str = kasprintf(GFP_KERNEL, fmt: "%s,%s" , sbi->domain_id, |
215 | sbi->fsid); |
216 | if (!str) |
217 | return -ENOMEM; |
218 | name = str; |
219 | } else { |
220 | name = sbi->fsid; |
221 | } |
222 | } else { |
223 | name = sb->s_id; |
224 | } |
225 | sbi->s_kobj.kset = &erofs_root; |
226 | init_completion(x: &sbi->s_kobj_unregister); |
227 | err = kobject_init_and_add(kobj: &sbi->s_kobj, ktype: &erofs_sb_ktype, NULL, fmt: "%s" , name); |
228 | kfree(objp: str); |
229 | if (err) |
230 | goto put_sb_kobj; |
231 | return 0; |
232 | |
233 | put_sb_kobj: |
234 | kobject_put(kobj: &sbi->s_kobj); |
235 | wait_for_completion(&sbi->s_kobj_unregister); |
236 | return err; |
237 | } |
238 | |
239 | void erofs_unregister_sysfs(struct super_block *sb) |
240 | { |
241 | struct erofs_sb_info *sbi = EROFS_SB(sb); |
242 | |
243 | if (sbi->s_kobj.state_in_sysfs) { |
244 | kobject_del(kobj: &sbi->s_kobj); |
245 | kobject_put(kobj: &sbi->s_kobj); |
246 | wait_for_completion(&sbi->s_kobj_unregister); |
247 | } |
248 | } |
249 | |
250 | int __init erofs_init_sysfs(void) |
251 | { |
252 | int ret; |
253 | |
254 | kobject_set_name(kobj: &erofs_root.kobj, name: "erofs" ); |
255 | erofs_root.kobj.parent = fs_kobj; |
256 | ret = kset_register(kset: &erofs_root); |
257 | if (ret) |
258 | goto root_err; |
259 | |
260 | ret = kobject_init_and_add(kobj: &erofs_feat, ktype: &erofs_feat_ktype, |
261 | NULL, fmt: "features" ); |
262 | if (ret) |
263 | goto feat_err; |
264 | return ret; |
265 | |
266 | feat_err: |
267 | kobject_put(kobj: &erofs_feat); |
268 | kset_unregister(kset: &erofs_root); |
269 | root_err: |
270 | return ret; |
271 | } |
272 | |
273 | void erofs_exit_sysfs(void) |
274 | { |
275 | kobject_put(kobj: &erofs_feat); |
276 | kset_unregister(kset: &erofs_root); |
277 | } |
278 | |