diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /fs/sysfs/dir.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'fs/sysfs/dir.c')
-rw-r--r-- | fs/sysfs/dir.c | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c new file mode 100644 index 000000000000..fe198210bc2d --- /dev/null +++ b/fs/sysfs/dir.c | |||
@@ -0,0 +1,475 @@ | |||
1 | /* | ||
2 | * dir.c - Operations for sysfs directories. | ||
3 | */ | ||
4 | |||
5 | #undef DEBUG | ||
6 | |||
7 | #include <linux/fs.h> | ||
8 | #include <linux/mount.h> | ||
9 | #include <linux/module.h> | ||
10 | #include <linux/kobject.h> | ||
11 | #include "sysfs.h" | ||
12 | |||
13 | DECLARE_RWSEM(sysfs_rename_sem); | ||
14 | |||
15 | static void sysfs_d_iput(struct dentry * dentry, struct inode * inode) | ||
16 | { | ||
17 | struct sysfs_dirent * sd = dentry->d_fsdata; | ||
18 | |||
19 | if (sd) { | ||
20 | BUG_ON(sd->s_dentry != dentry); | ||
21 | sd->s_dentry = NULL; | ||
22 | sysfs_put(sd); | ||
23 | } | ||
24 | iput(inode); | ||
25 | } | ||
26 | |||
27 | static struct dentry_operations sysfs_dentry_ops = { | ||
28 | .d_iput = sysfs_d_iput, | ||
29 | }; | ||
30 | |||
31 | /* | ||
32 | * Allocates a new sysfs_dirent and links it to the parent sysfs_dirent | ||
33 | */ | ||
34 | static struct sysfs_dirent * sysfs_new_dirent(struct sysfs_dirent * parent_sd, | ||
35 | void * element) | ||
36 | { | ||
37 | struct sysfs_dirent * sd; | ||
38 | |||
39 | sd = kmem_cache_alloc(sysfs_dir_cachep, GFP_KERNEL); | ||
40 | if (!sd) | ||
41 | return NULL; | ||
42 | |||
43 | memset(sd, 0, sizeof(*sd)); | ||
44 | atomic_set(&sd->s_count, 1); | ||
45 | INIT_LIST_HEAD(&sd->s_children); | ||
46 | list_add(&sd->s_sibling, &parent_sd->s_children); | ||
47 | sd->s_element = element; | ||
48 | |||
49 | return sd; | ||
50 | } | ||
51 | |||
52 | int sysfs_make_dirent(struct sysfs_dirent * parent_sd, struct dentry * dentry, | ||
53 | void * element, umode_t mode, int type) | ||
54 | { | ||
55 | struct sysfs_dirent * sd; | ||
56 | |||
57 | sd = sysfs_new_dirent(parent_sd, element); | ||
58 | if (!sd) | ||
59 | return -ENOMEM; | ||
60 | |||
61 | sd->s_mode = mode; | ||
62 | sd->s_type = type; | ||
63 | sd->s_dentry = dentry; | ||
64 | if (dentry) { | ||
65 | dentry->d_fsdata = sysfs_get(sd); | ||
66 | dentry->d_op = &sysfs_dentry_ops; | ||
67 | } | ||
68 | |||
69 | return 0; | ||
70 | } | ||
71 | |||
72 | static int init_dir(struct inode * inode) | ||
73 | { | ||
74 | inode->i_op = &sysfs_dir_inode_operations; | ||
75 | inode->i_fop = &sysfs_dir_operations; | ||
76 | |||
77 | /* directory inodes start off with i_nlink == 2 (for "." entry) */ | ||
78 | inode->i_nlink++; | ||
79 | return 0; | ||
80 | } | ||
81 | |||
82 | static int init_file(struct inode * inode) | ||
83 | { | ||
84 | inode->i_size = PAGE_SIZE; | ||
85 | inode->i_fop = &sysfs_file_operations; | ||
86 | return 0; | ||
87 | } | ||
88 | |||
89 | static int init_symlink(struct inode * inode) | ||
90 | { | ||
91 | inode->i_op = &sysfs_symlink_inode_operations; | ||
92 | return 0; | ||
93 | } | ||
94 | |||
95 | static int create_dir(struct kobject * k, struct dentry * p, | ||
96 | const char * n, struct dentry ** d) | ||
97 | { | ||
98 | int error; | ||
99 | umode_t mode = S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO; | ||
100 | |||
101 | down(&p->d_inode->i_sem); | ||
102 | *d = sysfs_get_dentry(p,n); | ||
103 | if (!IS_ERR(*d)) { | ||
104 | error = sysfs_create(*d, mode, init_dir); | ||
105 | if (!error) { | ||
106 | error = sysfs_make_dirent(p->d_fsdata, *d, k, mode, | ||
107 | SYSFS_DIR); | ||
108 | if (!error) { | ||
109 | p->d_inode->i_nlink++; | ||
110 | (*d)->d_op = &sysfs_dentry_ops; | ||
111 | d_rehash(*d); | ||
112 | } | ||
113 | } | ||
114 | if (error && (error != -EEXIST)) | ||
115 | d_drop(*d); | ||
116 | dput(*d); | ||
117 | } else | ||
118 | error = PTR_ERR(*d); | ||
119 | up(&p->d_inode->i_sem); | ||
120 | return error; | ||
121 | } | ||
122 | |||
123 | |||
124 | int sysfs_create_subdir(struct kobject * k, const char * n, struct dentry ** d) | ||
125 | { | ||
126 | return create_dir(k,k->dentry,n,d); | ||
127 | } | ||
128 | |||
129 | /** | ||
130 | * sysfs_create_dir - create a directory for an object. | ||
131 | * @parent: parent parent object. | ||
132 | * @kobj: object we're creating directory for. | ||
133 | */ | ||
134 | |||
135 | int sysfs_create_dir(struct kobject * kobj) | ||
136 | { | ||
137 | struct dentry * dentry = NULL; | ||
138 | struct dentry * parent; | ||
139 | int error = 0; | ||
140 | |||
141 | BUG_ON(!kobj); | ||
142 | |||
143 | if (kobj->parent) | ||
144 | parent = kobj->parent->dentry; | ||
145 | else if (sysfs_mount && sysfs_mount->mnt_sb) | ||
146 | parent = sysfs_mount->mnt_sb->s_root; | ||
147 | else | ||
148 | return -EFAULT; | ||
149 | |||
150 | error = create_dir(kobj,parent,kobject_name(kobj),&dentry); | ||
151 | if (!error) | ||
152 | kobj->dentry = dentry; | ||
153 | return error; | ||
154 | } | ||
155 | |||
156 | /* attaches attribute's sysfs_dirent to the dentry corresponding to the | ||
157 | * attribute file | ||
158 | */ | ||
159 | static int sysfs_attach_attr(struct sysfs_dirent * sd, struct dentry * dentry) | ||
160 | { | ||
161 | struct attribute * attr = NULL; | ||
162 | struct bin_attribute * bin_attr = NULL; | ||
163 | int (* init) (struct inode *) = NULL; | ||
164 | int error = 0; | ||
165 | |||
166 | if (sd->s_type & SYSFS_KOBJ_BIN_ATTR) { | ||
167 | bin_attr = sd->s_element; | ||
168 | attr = &bin_attr->attr; | ||
169 | } else { | ||
170 | attr = sd->s_element; | ||
171 | init = init_file; | ||
172 | } | ||
173 | |||
174 | error = sysfs_create(dentry, (attr->mode & S_IALLUGO) | S_IFREG, init); | ||
175 | if (error) | ||
176 | return error; | ||
177 | |||
178 | if (bin_attr) { | ||
179 | dentry->d_inode->i_size = bin_attr->size; | ||
180 | dentry->d_inode->i_fop = &bin_fops; | ||
181 | } | ||
182 | dentry->d_op = &sysfs_dentry_ops; | ||
183 | dentry->d_fsdata = sysfs_get(sd); | ||
184 | sd->s_dentry = dentry; | ||
185 | d_rehash(dentry); | ||
186 | |||
187 | return 0; | ||
188 | } | ||
189 | |||
190 | static int sysfs_attach_link(struct sysfs_dirent * sd, struct dentry * dentry) | ||
191 | { | ||
192 | int err = 0; | ||
193 | |||
194 | err = sysfs_create(dentry, S_IFLNK|S_IRWXUGO, init_symlink); | ||
195 | if (!err) { | ||
196 | dentry->d_op = &sysfs_dentry_ops; | ||
197 | dentry->d_fsdata = sysfs_get(sd); | ||
198 | sd->s_dentry = dentry; | ||
199 | d_rehash(dentry); | ||
200 | } | ||
201 | return err; | ||
202 | } | ||
203 | |||
204 | static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, | ||
205 | struct nameidata *nd) | ||
206 | { | ||
207 | struct sysfs_dirent * parent_sd = dentry->d_parent->d_fsdata; | ||
208 | struct sysfs_dirent * sd; | ||
209 | int err = 0; | ||
210 | |||
211 | list_for_each_entry(sd, &parent_sd->s_children, s_sibling) { | ||
212 | if (sd->s_type & SYSFS_NOT_PINNED) { | ||
213 | const unsigned char * name = sysfs_get_name(sd); | ||
214 | |||
215 | if (strcmp(name, dentry->d_name.name)) | ||
216 | continue; | ||
217 | |||
218 | if (sd->s_type & SYSFS_KOBJ_LINK) | ||
219 | err = sysfs_attach_link(sd, dentry); | ||
220 | else | ||
221 | err = sysfs_attach_attr(sd, dentry); | ||
222 | break; | ||
223 | } | ||
224 | } | ||
225 | |||
226 | return ERR_PTR(err); | ||
227 | } | ||
228 | |||
229 | struct inode_operations sysfs_dir_inode_operations = { | ||
230 | .lookup = sysfs_lookup, | ||
231 | }; | ||
232 | |||
233 | static void remove_dir(struct dentry * d) | ||
234 | { | ||
235 | struct dentry * parent = dget(d->d_parent); | ||
236 | struct sysfs_dirent * sd; | ||
237 | |||
238 | down(&parent->d_inode->i_sem); | ||
239 | d_delete(d); | ||
240 | sd = d->d_fsdata; | ||
241 | list_del_init(&sd->s_sibling); | ||
242 | sysfs_put(sd); | ||
243 | if (d->d_inode) | ||
244 | simple_rmdir(parent->d_inode,d); | ||
245 | |||
246 | pr_debug(" o %s removing done (%d)\n",d->d_name.name, | ||
247 | atomic_read(&d->d_count)); | ||
248 | |||
249 | up(&parent->d_inode->i_sem); | ||
250 | dput(parent); | ||
251 | } | ||
252 | |||
253 | void sysfs_remove_subdir(struct dentry * d) | ||
254 | { | ||
255 | remove_dir(d); | ||
256 | } | ||
257 | |||
258 | |||
259 | /** | ||
260 | * sysfs_remove_dir - remove an object's directory. | ||
261 | * @kobj: object. | ||
262 | * | ||
263 | * The only thing special about this is that we remove any files in | ||
264 | * the directory before we remove the directory, and we've inlined | ||
265 | * what used to be sysfs_rmdir() below, instead of calling separately. | ||
266 | */ | ||
267 | |||
268 | void sysfs_remove_dir(struct kobject * kobj) | ||
269 | { | ||
270 | struct dentry * dentry = dget(kobj->dentry); | ||
271 | struct sysfs_dirent * parent_sd; | ||
272 | struct sysfs_dirent * sd, * tmp; | ||
273 | |||
274 | if (!dentry) | ||
275 | return; | ||
276 | |||
277 | pr_debug("sysfs %s: removing dir\n",dentry->d_name.name); | ||
278 | down(&dentry->d_inode->i_sem); | ||
279 | parent_sd = dentry->d_fsdata; | ||
280 | list_for_each_entry_safe(sd, tmp, &parent_sd->s_children, s_sibling) { | ||
281 | if (!sd->s_element || !(sd->s_type & SYSFS_NOT_PINNED)) | ||
282 | continue; | ||
283 | list_del_init(&sd->s_sibling); | ||
284 | sysfs_drop_dentry(sd, dentry); | ||
285 | sysfs_put(sd); | ||
286 | } | ||
287 | up(&dentry->d_inode->i_sem); | ||
288 | |||
289 | remove_dir(dentry); | ||
290 | /** | ||
291 | * Drop reference from dget() on entrance. | ||
292 | */ | ||
293 | dput(dentry); | ||
294 | } | ||
295 | |||
296 | int sysfs_rename_dir(struct kobject * kobj, const char *new_name) | ||
297 | { | ||
298 | int error = 0; | ||
299 | struct dentry * new_dentry, * parent; | ||
300 | |||
301 | if (!strcmp(kobject_name(kobj), new_name)) | ||
302 | return -EINVAL; | ||
303 | |||
304 | if (!kobj->parent) | ||
305 | return -EINVAL; | ||
306 | |||
307 | down_write(&sysfs_rename_sem); | ||
308 | parent = kobj->parent->dentry; | ||
309 | |||
310 | down(&parent->d_inode->i_sem); | ||
311 | |||
312 | new_dentry = sysfs_get_dentry(parent, new_name); | ||
313 | if (!IS_ERR(new_dentry)) { | ||
314 | if (!new_dentry->d_inode) { | ||
315 | error = kobject_set_name(kobj, "%s", new_name); | ||
316 | if (!error) { | ||
317 | d_add(new_dentry, NULL); | ||
318 | d_move(kobj->dentry, new_dentry); | ||
319 | } | ||
320 | else | ||
321 | d_drop(new_dentry); | ||
322 | } else | ||
323 | error = -EEXIST; | ||
324 | dput(new_dentry); | ||
325 | } | ||
326 | up(&parent->d_inode->i_sem); | ||
327 | up_write(&sysfs_rename_sem); | ||
328 | |||
329 | return error; | ||
330 | } | ||
331 | |||
332 | static int sysfs_dir_open(struct inode *inode, struct file *file) | ||
333 | { | ||
334 | struct dentry * dentry = file->f_dentry; | ||
335 | struct sysfs_dirent * parent_sd = dentry->d_fsdata; | ||
336 | |||
337 | down(&dentry->d_inode->i_sem); | ||
338 | file->private_data = sysfs_new_dirent(parent_sd, NULL); | ||
339 | up(&dentry->d_inode->i_sem); | ||
340 | |||
341 | return file->private_data ? 0 : -ENOMEM; | ||
342 | |||
343 | } | ||
344 | |||
345 | static int sysfs_dir_close(struct inode *inode, struct file *file) | ||
346 | { | ||
347 | struct dentry * dentry = file->f_dentry; | ||
348 | struct sysfs_dirent * cursor = file->private_data; | ||
349 | |||
350 | down(&dentry->d_inode->i_sem); | ||
351 | list_del_init(&cursor->s_sibling); | ||
352 | up(&dentry->d_inode->i_sem); | ||
353 | |||
354 | release_sysfs_dirent(cursor); | ||
355 | |||
356 | return 0; | ||
357 | } | ||
358 | |||
359 | /* Relationship between s_mode and the DT_xxx types */ | ||
360 | static inline unsigned char dt_type(struct sysfs_dirent *sd) | ||
361 | { | ||
362 | return (sd->s_mode >> 12) & 15; | ||
363 | } | ||
364 | |||
365 | static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) | ||
366 | { | ||
367 | struct dentry *dentry = filp->f_dentry; | ||
368 | struct sysfs_dirent * parent_sd = dentry->d_fsdata; | ||
369 | struct sysfs_dirent *cursor = filp->private_data; | ||
370 | struct list_head *p, *q = &cursor->s_sibling; | ||
371 | ino_t ino; | ||
372 | int i = filp->f_pos; | ||
373 | |||
374 | switch (i) { | ||
375 | case 0: | ||
376 | ino = dentry->d_inode->i_ino; | ||
377 | if (filldir(dirent, ".", 1, i, ino, DT_DIR) < 0) | ||
378 | break; | ||
379 | filp->f_pos++; | ||
380 | i++; | ||
381 | /* fallthrough */ | ||
382 | case 1: | ||
383 | ino = parent_ino(dentry); | ||
384 | if (filldir(dirent, "..", 2, i, ino, DT_DIR) < 0) | ||
385 | break; | ||
386 | filp->f_pos++; | ||
387 | i++; | ||
388 | /* fallthrough */ | ||
389 | default: | ||
390 | if (filp->f_pos == 2) { | ||
391 | list_del(q); | ||
392 | list_add(q, &parent_sd->s_children); | ||
393 | } | ||
394 | for (p=q->next; p!= &parent_sd->s_children; p=p->next) { | ||
395 | struct sysfs_dirent *next; | ||
396 | const char * name; | ||
397 | int len; | ||
398 | |||
399 | next = list_entry(p, struct sysfs_dirent, | ||
400 | s_sibling); | ||
401 | if (!next->s_element) | ||
402 | continue; | ||
403 | |||
404 | name = sysfs_get_name(next); | ||
405 | len = strlen(name); | ||
406 | if (next->s_dentry) | ||
407 | ino = next->s_dentry->d_inode->i_ino; | ||
408 | else | ||
409 | ino = iunique(sysfs_sb, 2); | ||
410 | |||
411 | if (filldir(dirent, name, len, filp->f_pos, ino, | ||
412 | dt_type(next)) < 0) | ||
413 | return 0; | ||
414 | |||
415 | list_del(q); | ||
416 | list_add(q, p); | ||
417 | p = q; | ||
418 | filp->f_pos++; | ||
419 | } | ||
420 | } | ||
421 | return 0; | ||
422 | } | ||
423 | |||
424 | static loff_t sysfs_dir_lseek(struct file * file, loff_t offset, int origin) | ||
425 | { | ||
426 | struct dentry * dentry = file->f_dentry; | ||
427 | |||
428 | down(&dentry->d_inode->i_sem); | ||
429 | switch (origin) { | ||
430 | case 1: | ||
431 | offset += file->f_pos; | ||
432 | case 0: | ||
433 | if (offset >= 0) | ||
434 | break; | ||
435 | default: | ||
436 | up(&file->f_dentry->d_inode->i_sem); | ||
437 | return -EINVAL; | ||
438 | } | ||
439 | if (offset != file->f_pos) { | ||
440 | file->f_pos = offset; | ||
441 | if (file->f_pos >= 2) { | ||
442 | struct sysfs_dirent *sd = dentry->d_fsdata; | ||
443 | struct sysfs_dirent *cursor = file->private_data; | ||
444 | struct list_head *p; | ||
445 | loff_t n = file->f_pos - 2; | ||
446 | |||
447 | list_del(&cursor->s_sibling); | ||
448 | p = sd->s_children.next; | ||
449 | while (n && p != &sd->s_children) { | ||
450 | struct sysfs_dirent *next; | ||
451 | next = list_entry(p, struct sysfs_dirent, | ||
452 | s_sibling); | ||
453 | if (next->s_element) | ||
454 | n--; | ||
455 | p = p->next; | ||
456 | } | ||
457 | list_add_tail(&cursor->s_sibling, p); | ||
458 | } | ||
459 | } | ||
460 | up(&dentry->d_inode->i_sem); | ||
461 | return offset; | ||
462 | } | ||
463 | |||
464 | struct file_operations sysfs_dir_operations = { | ||
465 | .open = sysfs_dir_open, | ||
466 | .release = sysfs_dir_close, | ||
467 | .llseek = sysfs_dir_lseek, | ||
468 | .read = generic_read_dir, | ||
469 | .readdir = sysfs_readdir, | ||
470 | }; | ||
471 | |||
472 | EXPORT_SYMBOL_GPL(sysfs_create_dir); | ||
473 | EXPORT_SYMBOL_GPL(sysfs_remove_dir); | ||
474 | EXPORT_SYMBOL_GPL(sysfs_rename_dir); | ||
475 | |||