diff options
| author | Patrick Mochel <mochel@osdl.org> | 2002-10-29 07:14:59 -0800 |
|---|---|---|
| committer | Patrick Mochel <mochel@osdl.org> | 2002-10-29 07:14:59 -0800 |
| commit | d2f8eca7b0d7ef4503e40f809fb8d12e9d1d5389 (patch) | |
| tree | 70668b6fc908d303ad7ebec42633f5422867e00e | |
| parent | d8c084f9fd65a1bf948acc5bf08b066f85160cba (diff) | |
| parent | eda520259938bd3299486f12ab5058c29dcd5326 (diff) | |
Merge osdl.org:/home/mochel/src/kernel/devel/linux-2.5-virgin
into osdl.org:/home/mochel/src/kernel/devel/linux-2.5-kobject
| -rw-r--r-- | fs/sysfs/inode.c | 383 | ||||
| -rw-r--r-- | include/linux/kobject.h | 30 | ||||
| -rw-r--r-- | include/linux/sysfs.h | 26 | ||||
| -rw-r--r-- | lib/Makefile | 5 | ||||
| -rw-r--r-- | lib/kobject.c | 98 |
5 files changed, 303 insertions, 239 deletions
diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c index a91f47aaa378..804f3655dc73 100644 --- a/fs/sysfs/inode.c +++ b/fs/sysfs/inode.c @@ -33,7 +33,7 @@ #include <linux/module.h> #include <linux/slab.h> #include <linux/backing-dev.h> -#include <linux/sysfs.h> +#include <linux/kobject.h> #include <asm/uaccess.h> @@ -42,7 +42,6 @@ static struct super_operations sysfs_ops; static struct file_operations sysfs_file_operations; -static struct inode_operations sysfs_dir_inode_operations; static struct address_space_operations sysfs_aops; static struct vfsmount *sysfs_mount; @@ -52,45 +51,6 @@ static struct backing_dev_info sysfs_backing_dev_info = { .memory_backed = 1, /* Does not contribute to dirty memory */ }; -static int sysfs_readpage(struct file *file, struct page * page) -{ - if (!PageUptodate(page)) { - void *kaddr = kmap_atomic(page, KM_USER0); - - memset(kaddr, 0, PAGE_CACHE_SIZE); - flush_dcache_page(page); - kunmap_atomic(kaddr, KM_USER0); - SetPageUptodate(page); - } - unlock_page(page); - return 0; -} - -static int sysfs_prepare_write(struct file *file, struct page *page, unsigned offset, unsigned to) -{ - if (!PageUptodate(page)) { - void *kaddr = kmap_atomic(page, KM_USER0); - - memset(kaddr, 0, PAGE_CACHE_SIZE); - flush_dcache_page(page); - kunmap_atomic(kaddr, KM_USER0); - SetPageUptodate(page); - } - return 0; -} - -static int sysfs_commit_write(struct file *file, struct page *page, unsigned offset, unsigned to) -{ - struct inode *inode = page->mapping->host; - loff_t pos = ((loff_t)page->index << PAGE_CACHE_SHIFT) + to; - - set_page_dirty(page); - if (pos > inode->i_size) - inode->i_size = pos; - return 0; -} - - static struct inode *sysfs_get_inode(struct super_block *sb, int mode, int dev) { struct inode *inode = new_inode(sb); @@ -113,7 +73,7 @@ static struct inode *sysfs_get_inode(struct super_block *sb, int mode, int dev) inode->i_fop = &sysfs_file_operations; break; case S_IFDIR: - inode->i_op = &sysfs_dir_inode_operations; + inode->i_op = &simple_dir_inode_operations; inode->i_fop = &simple_dir_operations; /* directory inodes start off with i_nlink == 2 (for "." entry) */ @@ -182,29 +142,6 @@ static int sysfs_symlink(struct inode * dir, struct dentry *dentry, const char * return error; } -static inline int sysfs_positive(struct dentry *dentry) -{ - return (dentry->d_inode && !d_unhashed(dentry)); -} - -static int sysfs_empty(struct dentry *dentry) -{ - struct list_head *list; - - spin_lock(&dcache_lock); - - list_for_each(list, &dentry->d_subdirs) { - struct dentry *de = list_entry(list, struct dentry, d_child); - if (sysfs_positive(de)) { - spin_unlock(&dcache_lock); - return 0; - } - } - - spin_unlock(&dcache_lock); - return 1; -} - static int sysfs_unlink(struct inode *dir, struct dentry *dentry) { struct inode *inode = dentry->d_inode; @@ -217,30 +154,35 @@ static int sysfs_unlink(struct inode *dir, struct dentry *dentry) } /** - * sysfs_read_file - "read" data from a file. - * @file: file pointer - * @buf: buffer to fill - * @count: number of bytes to read - * @ppos: starting offset in file + * sysfs_read_file - read an attribute. + * @file: file pointer. + * @buf: buffer to fill. + * @count: number of bytes to read. + * @ppos: starting offset in file. + * + * Userspace wants to read an attribute file. The attribute descriptor + * is in the file's ->d_fsdata. The target object is in the directory's + * ->d_fsdata. * - * Userspace wants data from a file. It is up to the creator of the file to - * provide that data. - * There is a struct device_attribute embedded in file->private_data. We - * obtain that and check if the read callback is implemented. If so, we call - * it, passing the data field of the file entry. - * Said callback is responsible for filling the buffer and returning the number - * of bytes it put in it. We update @ppos correctly. + * We allocate a %PAGE_SIZE buffer, and pass it to the object's ->show() + * method (along with the object). We loop doing this until @count is + * satisfied, or ->show() returns %0. */ + static ssize_t sysfs_read_file(struct file *file, char *buf, size_t count, loff_t *ppos) { struct attribute * attr = file->f_dentry->d_fsdata; - struct driver_dir_entry * dir; + struct sysfs_ops * ops = NULL; + struct kobject * kobj; unsigned char *page; ssize_t retval = 0; - dir = file->f_dentry->d_parent->d_fsdata; - if (!dir->ops->show) + kobj = file->f_dentry->d_parent->d_fsdata; + if (kobj) + ops = kobj->dir.ops; + + if (!ops || !ops->show) return 0; if (count > PAGE_SIZE) @@ -253,7 +195,7 @@ sysfs_read_file(struct file *file, char *buf, size_t count, loff_t *ppos) while (count > 0) { ssize_t len; - len = dir->ops->show(dir,attr,page,count,*ppos); + len = ops->show(kobj,attr,page,count,*ppos); if (len <= 0) { if (len < 0) @@ -277,27 +219,32 @@ sysfs_read_file(struct file *file, char *buf, size_t count, loff_t *ppos) } /** - * sysfs_write_file - "write" to a file - * @file: file pointer - * @buf: data to write - * @count: number of bytes - * @ppos: starting offset + * sysfs_write_file - write an attribute. + * @file: file pointer + * @buf: data to write + * @count: number of bytes + * @ppos: starting offset * - * Similarly to sysfs_read_file, we act essentially as a bit pipe. - * We check for a "write" callback in file->private_data, and pass - * @buffer, @count, @ppos, and the file entry's data to the callback. - * The number of bytes written is returned, and we handle updating - * @ppos properly. + * Identical to sysfs_read_file(), though going the opposite direction. + * We allocate a %PAGE_SIZE buffer and copy in the userspace buffer. We + * pass that to the object's ->store() method until we reach @count or + * ->store() returns %0 or less. */ + static ssize_t sysfs_write_file(struct file *file, const char *buf, size_t count, loff_t *ppos) { struct attribute * attr = file->f_dentry->d_fsdata; - struct driver_dir_entry * dir; + struct sysfs_ops * ops = NULL; + struct kobject * kobj; ssize_t retval = 0; char * page; - dir = file->f_dentry->d_parent->d_fsdata; + kobj = file->f_dentry->d_parent->d_fsdata; + if (kobj) + ops = kobj->dir.ops; + if (!ops || !ops->store) + return 0; page = (char *)__get_free_page(GFP_KERNEL); if (!page) @@ -312,7 +259,7 @@ sysfs_write_file(struct file *file, const char *buf, size_t count, loff_t *ppos) while (count > 0) { ssize_t len; - len = dir->ops->store(dir,attr,page + retval,count,*ppos); + len = ops->store(kobj,attr,page + retval,count,*ppos); if (len <= 0) { if (len < 0) @@ -329,77 +276,42 @@ sysfs_write_file(struct file *file, const char *buf, size_t count, loff_t *ppos) return retval; } -static loff_t -sysfs_file_lseek(struct file *file, loff_t offset, int orig) -{ - loff_t retval = -EINVAL; - - down(&file->f_dentry->d_inode->i_sem); - switch(orig) { - case 0: - if (offset > 0) { - file->f_pos = offset; - retval = file->f_pos; - } - break; - case 1: - if ((offset + file->f_pos) > 0) { - file->f_pos += offset; - retval = file->f_pos; - } - break; - default: - break; - } - up(&file->f_dentry->d_inode->i_sem); - return retval; -} - static int sysfs_open_file(struct inode * inode, struct file * filp) { - struct driver_dir_entry * dir; + struct kobject * kobj; int error = 0; - dir = (struct driver_dir_entry *)filp->f_dentry->d_parent->d_fsdata; - if (dir) { + kobj = filp->f_dentry->d_parent->d_fsdata; + if ((kobj = kobject_get(kobj))) { struct attribute * attr = filp->f_dentry->d_fsdata; - if (attr && dir->ops) { - if (dir->ops->open) - error = dir->ops->open(dir); - goto Done; - } - } - error = -EINVAL; - Done: + if (!attr) + error = -EINVAL; + } else + error = -EINVAL; return error; } static int sysfs_release(struct inode * inode, struct file * filp) { - struct driver_dir_entry * dir; - dir = (struct driver_dir_entry *)filp->f_dentry->d_parent->d_fsdata; - if (dir->ops->close) - dir->ops->close(dir); + struct kobject * kobj = filp->f_dentry->d_parent->d_fsdata; + if (kobj) + kobject_put(kobj); return 0; } static struct file_operations sysfs_file_operations = { .read = sysfs_read_file, .write = sysfs_write_file, - .llseek = sysfs_file_lseek, + .llseek = generic_file_llseek, .open = sysfs_open_file, .release = sysfs_release, }; -static struct inode_operations sysfs_dir_inode_operations = { - .lookup = simple_lookup, -}; - static struct address_space_operations sysfs_aops = { - .readpage = sysfs_readpage, + .readpage = simple_readpage, .writepage = fail_writepage, - .prepare_write = sysfs_prepare_write, - .commit_write = sysfs_commit_write + .prepare_write = simple_prepare_write, + .commit_write = simple_commit_write }; static struct super_operations sysfs_ops = { @@ -464,6 +376,7 @@ static int __init sysfs_init(void) core_initcall(sysfs_init); + static struct dentry * get_dentry(struct dentry * parent, const char * name) { struct qstr qstr; @@ -474,137 +387,160 @@ static struct dentry * get_dentry(struct dentry * parent, const char * name) return lookup_hash(&qstr,parent); } + /** - * sysfs_create_dir - create a directory in the filesystem - * @entry: directory entry - * @parent: parent directory entry + * sysfs_create_dir - create a directory for an object. + * @parent: parent parent object. + * @kobj: object we're creating directory for. */ -int -sysfs_create_dir(struct driver_dir_entry * entry, - struct driver_dir_entry * parent) + +int sysfs_create_dir(struct kobject * kobj) { struct dentry * dentry = NULL; - struct dentry * parent_dentry; + struct dentry * parent; int error = 0; - if (!entry) + if (!kobj) return -EINVAL; - parent_dentry = parent ? parent->dentry : NULL; - - if (!parent_dentry) - if (sysfs_mount && sysfs_mount->mnt_sb) - parent_dentry = sysfs_mount->mnt_sb->s_root; - - if (!parent_dentry) + if (kobj->parent) + parent = kobj->parent->dir.dentry; + else if (sysfs_mount && sysfs_mount->mnt_sb) + parent = sysfs_mount->mnt_sb->s_root; + else return -EFAULT; - down(&parent_dentry->d_inode->i_sem); - dentry = get_dentry(parent_dentry,entry->name); + down(&parent->d_inode->i_sem); + dentry = get_dentry(parent,kobj->name); if (!IS_ERR(dentry)) { - dentry->d_fsdata = (void *) entry; - entry->dentry = dentry; - error = sysfs_mkdir(parent_dentry->d_inode,dentry,entry->mode); + dentry->d_fsdata = (void *)kobj; + kobj->dir.dentry = dentry; + error = sysfs_mkdir(parent->d_inode,dentry, + (S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO)); } else error = PTR_ERR(dentry); - up(&parent_dentry->d_inode->i_sem); + up(&parent->d_inode->i_sem); return error; } + /** - * sysfs_create_file - create a file - * @entry: structure describing the file - * @parent: directory to create it in + * sysfs_create_file - create an attribute file for an object. + * @kobj: object we're creating for. + * @attr: atrribute descriptor. */ -int -sysfs_create_file(struct attribute * entry, - struct driver_dir_entry * parent) + +int sysfs_create_file(struct kobject * kobj, struct attribute * attr) { struct dentry * dentry; + struct dentry * parent; int error = 0; - if (!entry || !parent) + if (!kobj || !attr) return -EINVAL; - if (!parent->dentry) - return -EINVAL; + if (kobj->parent) + parent = kobj->parent->dir.dentry; + else + return -ENOENT; - down(&parent->dentry->d_inode->i_sem); - dentry = get_dentry(parent->dentry,entry->name); + down(&parent->d_inode->i_sem); + dentry = get_dentry(parent,attr->name); if (!IS_ERR(dentry)) { - dentry->d_fsdata = (void *)entry; - error = sysfs_create(parent->dentry->d_inode,dentry,entry->mode); + dentry->d_fsdata = (void *)attr; + error = sysfs_create(parent->d_inode,dentry,attr->mode); } else error = PTR_ERR(dentry); - up(&parent->dentry->d_inode->i_sem); + up(&parent->d_inode->i_sem); return error; } + /** - * sysfs_create_symlink - make a symlink - * @parent: directory we're creating in - * @entry: entry describing link - * @target: place we're symlinking to - * + * sysfs_create_symlink - make a symlink + * @kobj: object who's directory we're creating in. + * @name: name of the symlink. + * @target: path we're pointing to. */ -int sysfs_create_symlink(struct driver_dir_entry * parent, - char * name, char * target) + +int sysfs_create_link(struct kobject * kobj, char * name, char * target) { struct dentry * dentry; - int error = 0; + int error; - if (!parent || !parent->dentry) - return -EINVAL; + if (kobj) { + struct dentry * parent = kobj->dir.dentry; - down(&parent->dentry->d_inode->i_sem); - dentry = get_dentry(parent->dentry,name); - if (!IS_ERR(dentry)) - error = sysfs_symlink(parent->dentry->d_inode,dentry,target); - else - error = PTR_ERR(dentry); - up(&parent->dentry->d_inode->i_sem); + down(&parent->d_inode->i_sem); + dentry = get_dentry(parent,name); + if (!IS_ERR(dentry)) + error = sysfs_symlink(parent->d_inode,dentry,target); + else + error = PTR_ERR(dentry); + up(&parent->d_inode->i_sem); + } else + error = -EINVAL; return error; } + +static void hash_and_remove(struct dentry * dir, const char * name) +{ + struct dentry * victim; + + down(&dir->d_inode->i_sem); + victim = get_dentry(dir,name); + if (!IS_ERR(victim)) { + /* make sure dentry is really there */ + if (victim->d_inode && + (victim->d_parent->d_inode == dir->d_inode)) { + sysfs_unlink(dir->d_inode,victim); + } + } + up(&dir->d_inode->i_sem); +} + + /** - * sysfs_remove_file - exported file removal - * @dir: directory the file supposedly resides in - * @name: name of the file + * sysfs_remove_file - remove an object attribute. + * @kobj: object we're acting for. + * @attr: attribute descriptor. * - * Try and find the file in the dir's list. - * If it's there, call __remove_file() (above) for the dentry. + * Hash the attribute name and kill the victim. */ -void sysfs_remove_file(struct driver_dir_entry * dir, const char * name) + +void sysfs_remove_file(struct kobject * kobj, struct attribute * attr) { - struct dentry * dentry; + hash_and_remove(kobj->dir.dentry,attr->name); +} - if (!dir->dentry) - return; - down(&dir->dentry->d_inode->i_sem); - dentry = get_dentry(dir->dentry,name); - if (!IS_ERR(dentry)) { - /* make sure dentry is really there */ - if (dentry->d_inode && - (dentry->d_parent->d_inode == dir->dentry->d_inode)) { - sysfs_unlink(dir->dentry->d_inode,dentry); - } - } - up(&dir->dentry->d_inode->i_sem); +/** + * sysfs_remove_link - remove symlink in object's directory. + * @kobj: object we're acting for. + * @name: name of the symlink to remove. + */ + +void sysfs_remove_link(struct kobject * kobj, char * name) +{ + hash_and_remove(kobj->dir.dentry,name); } + /** - * sysfs_remove_dir - exportable directory removal - * @dir: directory to remove + * sysfs_remove_dir - remove an object's directory. + * @kobj: object. * - * To make sure we don't orphan anyone, first remove - * all the children in the list, then do clean up the directory. + * The only thing special about this is that we remove any files in + * the directory before we remove the directory, and we've inlined + * what used to be sysfs_rmdir() below, instead of calling separately. */ -void sysfs_remove_dir(struct driver_dir_entry * dir) + +void sysfs_remove_dir(struct kobject * kobj) { struct list_head * node, * next; - struct dentry * dentry = dir->dentry; + struct dentry * dentry = kobj->dir.dentry; struct dentry * parent; if (!dentry) @@ -622,7 +558,7 @@ void sysfs_remove_dir(struct driver_dir_entry * dir) } d_invalidate(dentry); - if (sysfs_empty(dentry)) { + if (simple_empty(dentry)) { dentry->d_inode->i_nlink -= 2; dentry->d_inode->i_flags |= S_DEAD; parent->d_inode->i_nlink--; @@ -635,8 +571,9 @@ void sysfs_remove_dir(struct driver_dir_entry * dir) } EXPORT_SYMBOL(sysfs_create_file); -EXPORT_SYMBOL(sysfs_create_symlink); -EXPORT_SYMBOL(sysfs_create_dir); EXPORT_SYMBOL(sysfs_remove_file); +EXPORT_SYMBOL(sysfs_create_link); +EXPORT_SYMBOL(sysfs_remove_link); +EXPORT_SYMBOL(sysfs_create_dir); EXPORT_SYMBOL(sysfs_remove_dir); MODULE_LICENSE("GPL"); diff --git a/include/linux/kobject.h b/include/linux/kobject.h new file mode 100644 index 000000000000..12431a980712 --- /dev/null +++ b/include/linux/kobject.h @@ -0,0 +1,30 @@ +/* + * kobject.h - generic kernel object infrastructure. + * + */ + +#ifndef _KOBJECT_H_ +#define _KOBJECT_H_ + +#include <linux/types.h> +#include <linux/list.h> +#include <linux/sysfs.h> +#include <asm/atomic.h> + +struct kobject { + char name[16]; + atomic_t refcount; + struct list_head entry; + struct kobject * parent; + struct sysfs_dir dir; +}; + +extern void kobject_init(struct kobject *); + +extern int kobject_register(struct kobject *); +extern void kobject_unregister(struct kobject *); + +extern struct kobject * kobject_get(struct kobject *); +extern void kobject_put(struct kobject *); + +#endif /* _KOBJECT_H_ */ diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h index 6479902e1d20..fe82dff179ce 100644 --- a/include/linux/sysfs.h +++ b/include/linux/sysfs.h @@ -11,18 +11,15 @@ struct driver_dir_entry; struct attribute; +struct kobject; struct sysfs_ops { - int (*open)(struct driver_dir_entry *); - int (*close)(struct driver_dir_entry *); - ssize_t (*show)(struct driver_dir_entry *, struct attribute *,char *, size_t, loff_t); - ssize_t (*store)(struct driver_dir_entry *,struct attribute *,const char *, size_t, loff_t); + ssize_t (*show)(struct kobject *, struct attribute *,char *, size_t, loff_t); + ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t, loff_t); }; -struct driver_dir_entry { - char * name; +struct sysfs_dir { struct dentry * dentry; - mode_t mode; struct sysfs_ops * ops; }; @@ -32,20 +29,21 @@ struct attribute { }; extern int -sysfs_create_dir(struct driver_dir_entry *, struct driver_dir_entry *); +sysfs_create_dir(struct kobject *); extern void -sysfs_remove_dir(struct driver_dir_entry * entry); +sysfs_remove_dir(struct kobject *); extern int -sysfs_create_file(struct attribute * attr, - struct driver_dir_entry * parent); +sysfs_create_file(struct kobject *, struct attribute *); + +extern void +sysfs_remove_file(struct kobject *, struct attribute *); extern int -sysfs_create_symlink(struct driver_dir_entry * parent, - char * name, char * target); +sysfs_create_link(struct kobject * kobj, char * name, char * target); extern void -sysfs_remove_file(struct driver_dir_entry *, const char * name); +sysfs_remove_link(struct kobject *, char * name); #endif /* _SYSFS_H_ */ diff --git a/lib/Makefile b/lib/Makefile index 9bbb27efb10a..fa1a42d86abe 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -9,10 +9,11 @@ L_TARGET := lib.a export-objs := cmdline.o dec_and_lock.o rwsem-spinlock.o rwsem.o \ - crc32.o rbtree.o radix-tree.o + crc32.o rbtree.o radix-tree.o kobject.o obj-y := errno.o ctype.o string.o vsprintf.o brlock.o cmdline.o \ - bust_spinlocks.o rbtree.o radix-tree.o dump_stack.o + bust_spinlocks.o rbtree.o radix-tree.o dump_stack.o \ + kobject.o obj-$(CONFIG_RWSEM_GENERIC_SPINLOCK) += rwsem-spinlock.o obj-$(CONFIG_RWSEM_XCHGADD_ALGORITHM) += rwsem.o diff --git a/lib/kobject.c b/lib/kobject.c new file mode 100644 index 000000000000..48e950c9e4ce --- /dev/null +++ b/lib/kobject.c @@ -0,0 +1,98 @@ +/* + * kobject.c - library routines for handling generic kernel objects + */ + +#define DEBUG 1 + +#include <linux/kobject.h> +#include <linux/module.h> +#include <linux/stat.h> + +/** + * kobject_init - initialize object. + * @kobj: object in question. + */ + +void kobject_init(struct kobject * kobj) +{ + atomic_set(&kobj->refcount,1); + INIT_LIST_HEAD(&kobj->entry); +} + +/** + * kobject_register - register an object. + * @kobj: object in question. + * + * For now, fill in the replicated fields in the object's + * directory entry, and create a dir in sysfs. + * This stuff should go away in the future, as we move + * more implicit things to sysfs. + */ + +int kobject_register(struct kobject * kobj) +{ + pr_debug("kobject %s: registering\n",kobj->name); + if (kobj->parent) + kobject_get(kobj->parent); + return 0; +} + +/** + * kobject_unregister - unlink an object. + * @kobj: object going away. + * + * The device has been told to be removed, but may + * not necessarily be disappearing from the kernel. + * So, we remove the directory and decrement the refcount + * that we set with kobject_register(). + * + * Eventually (maybe now), the refcount will hit 0, and + * put_device() will clean the device up. + */ + +void kobject_unregister(struct kobject * kobj) +{ + pr_debug("kobject %s: unregistering\n",kobj->name); + kobject_put(kobj); +} + +/** + * kobject_get - increment refcount for object. + * @kobj: object. + */ + +struct kobject * kobject_get(struct kobject * kobj) +{ + struct kobject * ret = kobj; + if (atomic_read(&kobj->refcount) > 0) + atomic_inc(&kobj->refcount); + else + ret = NULL; + return ret; +} + +/** + * kobject_put - decrement refcount for object. + * @kobj: object. + * + * Decrement the refcount, and check if 0. If it is, then + * we're gonna need to clean it up, and decrement the refcount + * of its parent. + */ + +void kobject_put(struct kobject * kobj) +{ + struct kobject * parent = kobj->parent; + + if (!atomic_dec_and_test(&kobj->refcount)) + return; + pr_debug("kobject %s: cleaning up\n",kobj->name); + if (parent) + kobject_put(parent); +} + +EXPORT_SYMBOL(kobject_init); +EXPORT_SYMBOL(kobject_register); +EXPORT_SYMBOL(kobject_unregister); +EXPORT_SYMBOL(kobject_get); +EXPORT_SYMBOL(kobject_put); |
