From bcac3769ca6d6278f93afb6cc2b234d260ee5951 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 11 Sep 2013 22:29:03 -0400 Subject: sysfs: drop semicolon from to_sysfs_dirent() definition The expansion of to_sysfs_dirent() contains an unncessary trailing semicolon making it impossible to use in the middle of statements. Drop it. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'fs') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 4d83cedb9fcb..834c64cb7f88 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -28,7 +28,7 @@ DEFINE_MUTEX(sysfs_mutex); DEFINE_SPINLOCK(sysfs_assoc_lock); -#define to_sysfs_dirent(X) rb_entry((X), struct sysfs_dirent, s_rb); +#define to_sysfs_dirent(X) rb_entry((X), struct sysfs_dirent, s_rb) static DEFINE_SPINLOCK(sysfs_ino_lock); static DEFINE_IDA(sysfs_ino_ida); -- cgit v1.2.3 From 58292cbe6669d74498a5f08db13e57cb3bcfb81d Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 11 Sep 2013 22:29:04 -0400 Subject: sysfs: make attr namespace interface less convoluted sysfs ns (namespace) implementation became more convoluted than necessary while trying to hide ns information from visible interface. The relatively recent attr ns support is a good example. * attr ns tag is determined by sysfs_ops->namespace() callback while dir tag is determined by kobj_type->namespace(). The placement is arbitrary. * Instead of performing operations with explicit ns tag, the namespace callback is routed through sysfs_attr_ns(), sysfs_ops->namespace(), class_attr_namespace(), class_attr->namespace(). It's not simpler in any sense. The only thing this convolution does is traversing the whole stack backwards. The namespace callbacks are unncessary because the operations involved are inherently synchronous. The information can be provided in in straight-forward top-down direction and reversing that direction is unnecessary and against basic design principles. This backward interface is unnecessarily convoluted and hinders properly separating out sysfs from driver model / kobject for proper layering. This patch updates attr ns support such that * sysfs_ops->namespace() and class_attr->namespace() are dropped. * sysfs_{create|remove}_file_ns(), which take explicit @ns param, are added and sysfs_{create|remove}_file() are now simple wrappers around the ns aware functions. * ns handling is dropped from sysfs_chmod_file(). Nobody uses it at this point. sysfs_chmod_file_ns() can be added later if necessary. * Explicit @ns is propagated through class_{create|remove}_file_ns() and netdev_class_{create|remove}_file_ns(). * driver/net/bonding which is currently the only user of attr namespace is updated to use netdev_class_{create|remove}_file_ns() with @bh->net as the ns tag instead of using the namespace callback. This patch should be an equivalent conversion without any functional difference. It makes the code easier to follow, reduces lines of code a bit and helps proper separation and layering. Signed-off-by: Tejun Heo Cc: Eric W. Biederman Cc: Kay Sievers Acked-by: David S. Miller Signed-off-by: Greg Kroah-Hartman --- drivers/base/class.c | 29 ++++-------- drivers/net/bonding/bond_sysfs.c | 14 ++---- fs/sysfs/file.c | 95 ++++++++++------------------------------ fs/sysfs/group.c | 7 +-- fs/sysfs/sysfs.h | 5 ++- include/linux/device.h | 24 +++++++--- include/linux/netdevice.h | 16 ++++++- include/linux/sysfs.h | 31 +++++++++---- net/core/net-sysfs.c | 14 +++--- 9 files changed, 106 insertions(+), 129 deletions(-) (limited to 'fs') diff --git a/drivers/base/class.c b/drivers/base/class.c index 8b7818b80056..f96f70419a78 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -47,18 +47,6 @@ static ssize_t class_attr_store(struct kobject *kobj, struct attribute *attr, return ret; } -static const void *class_attr_namespace(struct kobject *kobj, - const struct attribute *attr) -{ - struct class_attribute *class_attr = to_class_attr(attr); - struct subsys_private *cp = to_subsys_private(kobj); - const void *ns = NULL; - - if (class_attr->namespace) - ns = class_attr->namespace(cp->class, class_attr); - return ns; -} - static void class_release(struct kobject *kobj) { struct subsys_private *cp = to_subsys_private(kobj); @@ -86,7 +74,6 @@ static const struct kobj_ns_type_operations *class_child_ns_type(struct kobject static const struct sysfs_ops class_sysfs_ops = { .show = class_attr_show, .store = class_attr_store, - .namespace = class_attr_namespace, }; static struct kobj_type class_ktype = { @@ -99,21 +86,23 @@ static struct kobj_type class_ktype = { static struct kset *class_kset; -int class_create_file(struct class *cls, const struct class_attribute *attr) +int class_create_file_ns(struct class *cls, const struct class_attribute *attr, + const void *ns) { int error; if (cls) - error = sysfs_create_file(&cls->p->subsys.kobj, - &attr->attr); + error = sysfs_create_file_ns(&cls->p->subsys.kobj, + &attr->attr, ns); else error = -EINVAL; return error; } -void class_remove_file(struct class *cls, const struct class_attribute *attr) +void class_remove_file_ns(struct class *cls, const struct class_attribute *attr, + const void *ns) { if (cls) - sysfs_remove_file(&cls->p->subsys.kobj, &attr->attr); + sysfs_remove_file_ns(&cls->p->subsys.kobj, &attr->attr, ns); } static struct class *class_get(struct class *cls) @@ -600,8 +589,8 @@ int __init classes_init(void) return 0; } -EXPORT_SYMBOL_GPL(class_create_file); -EXPORT_SYMBOL_GPL(class_remove_file); +EXPORT_SYMBOL_GPL(class_create_file_ns); +EXPORT_SYMBOL_GPL(class_remove_file_ns); EXPORT_SYMBOL_GPL(class_unregister); EXPORT_SYMBOL_GPL(class_destroy); diff --git a/drivers/net/bonding/bond_sysfs.c b/drivers/net/bonding/bond_sysfs.c index c29b836749b6..ec9b6460a38d 100644 --- a/drivers/net/bonding/bond_sysfs.c +++ b/drivers/net/bonding/bond_sysfs.c @@ -149,14 +149,6 @@ err_no_cmd: return -EPERM; } -static const void *bonding_namespace(struct class *cls, - const struct class_attribute *attr) -{ - const struct bond_net *bn = - container_of(attr, struct bond_net, class_attr_bonding_masters); - return bn->net; -} - /* class attribute for bond_masters file. This ends up in /sys/class/net */ static const struct class_attribute class_attr_bonding_masters = { .attr = { @@ -165,7 +157,6 @@ static const struct class_attribute class_attr_bonding_masters = { }, .show = bonding_show_bonds, .store = bonding_store_bonds, - .namespace = bonding_namespace, }; int bond_create_slave_symlinks(struct net_device *master, @@ -1787,7 +1778,8 @@ int bond_create_sysfs(struct bond_net *bn) bn->class_attr_bonding_masters = class_attr_bonding_masters; sysfs_attr_init(&bn->class_attr_bonding_masters.attr); - ret = netdev_class_create_file(&bn->class_attr_bonding_masters); + ret = netdev_class_create_file_ns(&bn->class_attr_bonding_masters, + bn->net); /* * Permit multiple loads of the module by ignoring failures to * create the bonding_masters sysfs file. Bonding devices @@ -1817,7 +1809,7 @@ int bond_create_sysfs(struct bond_net *bn) */ void bond_destroy_sysfs(struct bond_net *bn) { - netdev_class_remove_file(&bn->class_attr_bonding_masters); + netdev_class_remove_file_ns(&bn->class_attr_bonding_masters, bn->net); } /* diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 15ef5eb13663..e784340f1599 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -485,58 +485,15 @@ const struct file_operations sysfs_file_operations = { .poll = sysfs_poll, }; -static int sysfs_attr_ns(struct kobject *kobj, const struct attribute *attr, - const void **pns) -{ - struct sysfs_dirent *dir_sd = kobj->sd; - const struct sysfs_ops *ops; - const void *ns = NULL; - int err; - - if (!dir_sd) { - WARN(1, KERN_ERR "sysfs: kobject %s without dirent\n", - kobject_name(kobj)); - return -ENOENT; - } - - err = 0; - if (!sysfs_ns_type(dir_sd)) - goto out; - - err = -EINVAL; - if (!kobj->ktype) - goto out; - ops = kobj->ktype->sysfs_ops; - if (!ops) - goto out; - if (!ops->namespace) - goto out; - - err = 0; - ns = ops->namespace(kobj, attr); -out: - if (err) { - WARN(1, KERN_ERR - "missing sysfs namespace attribute operation for kobject: %s\n", - kobject_name(kobj)); - } - *pns = ns; - return err; -} - -int sysfs_add_file_mode(struct sysfs_dirent *dir_sd, - const struct attribute *attr, int type, umode_t amode) +int sysfs_add_file_mode_ns(struct sysfs_dirent *dir_sd, + const struct attribute *attr, int type, + umode_t amode, const void *ns) { umode_t mode = (amode & S_IALLUGO) | S_IFREG; struct sysfs_addrm_cxt acxt; struct sysfs_dirent *sd; - const void *ns; int rc; - rc = sysfs_attr_ns(dir_sd->s_dir.kobj, attr, &ns); - if (rc) - return rc; - sd = sysfs_new_dirent(attr->name, mode, type); if (!sd) return -ENOMEM; @@ -559,23 +516,25 @@ int sysfs_add_file_mode(struct sysfs_dirent *dir_sd, int sysfs_add_file(struct sysfs_dirent *dir_sd, const struct attribute *attr, int type) { - return sysfs_add_file_mode(dir_sd, attr, type, attr->mode); + return sysfs_add_file_mode_ns(dir_sd, attr, type, attr->mode, NULL); } - /** - * sysfs_create_file - create an attribute file for an object. - * @kobj: object we're creating for. - * @attr: attribute descriptor. + * sysfs_create_file_ns - create an attribute file for an object with custom ns + * @kobj: object we're creating for + * @attr: attribute descriptor + * @ns: namespace the new file should belong to */ -int sysfs_create_file(struct kobject *kobj, const struct attribute *attr) +int sysfs_create_file_ns(struct kobject *kobj, const struct attribute *attr, + const void *ns) { BUG_ON(!kobj || !kobj->sd || !attr); - return sysfs_add_file(kobj->sd, attr, SYSFS_KOBJ_ATTR); + return sysfs_add_file_mode_ns(kobj->sd, attr, SYSFS_KOBJ_ATTR, + attr->mode, ns); } -EXPORT_SYMBOL_GPL(sysfs_create_file); +EXPORT_SYMBOL_GPL(sysfs_create_file_ns); int sysfs_create_files(struct kobject *kobj, const struct attribute **ptr) { @@ -630,17 +589,12 @@ int sysfs_chmod_file(struct kobject *kobj, const struct attribute *attr, { struct sysfs_dirent *sd; struct iattr newattrs; - const void *ns; int rc; - rc = sysfs_attr_ns(kobj, attr, &ns); - if (rc) - return rc; - mutex_lock(&sysfs_mutex); rc = -ENOENT; - sd = sysfs_find_dirent(kobj->sd, ns, attr->name); + sd = sysfs_find_dirent(kobj->sd, NULL, attr->name); if (!sd) goto out; @@ -655,22 +609,21 @@ int sysfs_chmod_file(struct kobject *kobj, const struct attribute *attr, EXPORT_SYMBOL_GPL(sysfs_chmod_file); /** - * sysfs_remove_file - remove an object attribute. - * @kobj: object we're acting for. - * @attr: attribute descriptor. + * sysfs_remove_file_ns - remove an object attribute with a custom ns tag + * @kobj: object we're acting for + * @attr: attribute descriptor + * @ns: namespace tag of the file to remove * - * Hash the attribute name and kill the victim. + * Hash the attribute name and namespace tag and kill the victim. */ -void sysfs_remove_file(struct kobject *kobj, const struct attribute *attr) +void sysfs_remove_file_ns(struct kobject *kobj, const struct attribute *attr, + const void *ns) { - const void *ns; - - if (sysfs_attr_ns(kobj, attr, &ns)) - return; + struct sysfs_dirent *dir_sd = kobj->sd; - sysfs_hash_and_remove(kobj->sd, ns, attr->name); + sysfs_hash_and_remove(dir_sd, ns, attr->name); } -EXPORT_SYMBOL_GPL(sysfs_remove_file); +EXPORT_SYMBOL_GPL(sysfs_remove_file_ns); void sysfs_remove_files(struct kobject *kobj, const struct attribute **ptr) { diff --git a/fs/sysfs/group.c b/fs/sysfs/group.c index 5f92cd2f61c1..25c78f23dae8 100644 --- a/fs/sysfs/group.c +++ b/fs/sysfs/group.c @@ -56,9 +56,10 @@ static int create_files(struct sysfs_dirent *dir_sd, struct kobject *kobj, if (!mode) continue; } - error = sysfs_add_file_mode(dir_sd, *attr, - SYSFS_KOBJ_ATTR, - (*attr)->mode | mode); + error = sysfs_add_file_mode_ns(dir_sd, *attr, + SYSFS_KOBJ_ATTR, + (*attr)->mode | mode, + NULL); if (unlikely(error)) break; } diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index b6deca3e301d..a96da2559db2 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -230,8 +230,9 @@ extern const struct file_operations sysfs_file_operations; int sysfs_add_file(struct sysfs_dirent *dir_sd, const struct attribute *attr, int type); -int sysfs_add_file_mode(struct sysfs_dirent *dir_sd, - const struct attribute *attr, int type, umode_t amode); +int sysfs_add_file_mode_ns(struct sysfs_dirent *dir_sd, + const struct attribute *attr, int type, + umode_t amode, const void *ns); /* * bin.c */ diff --git a/include/linux/device.h b/include/linux/device.h index 2a9d6ed59579..ce690ea34547 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -427,8 +427,6 @@ struct class_attribute { char *buf); ssize_t (*store)(struct class *class, struct class_attribute *attr, const char *buf, size_t count); - const void *(*namespace)(struct class *class, - const struct class_attribute *attr); }; #define CLASS_ATTR(_name, _mode, _show, _store) \ @@ -438,10 +436,24 @@ struct class_attribute { #define CLASS_ATTR_RO(_name) \ struct class_attribute class_attr_##_name = __ATTR_RO(_name) -extern int __must_check class_create_file(struct class *class, - const struct class_attribute *attr); -extern void class_remove_file(struct class *class, - const struct class_attribute *attr); +extern int __must_check class_create_file_ns(struct class *class, + const struct class_attribute *attr, + const void *ns); +extern void class_remove_file_ns(struct class *class, + const struct class_attribute *attr, + const void *ns); + +static inline int __must_check class_create_file(struct class *class, + const struct class_attribute *attr) +{ + return class_create_file_ns(class, attr, NULL); +} + +static inline void class_remove_file(struct class *class, + const struct class_attribute *attr) +{ + return class_remove_file_ns(class, attr, NULL); +} /* Simple class attribute that is just a static string */ struct class_attribute_string { diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 3de49aca4519..42421ed49a47 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -2873,8 +2873,20 @@ extern int __init dev_proc_init(void); #define dev_proc_init() 0 #endif -extern int netdev_class_create_file(struct class_attribute *class_attr); -extern void netdev_class_remove_file(struct class_attribute *class_attr); +extern int netdev_class_create_file_ns(struct class_attribute *class_attr, + const void *ns); +extern void netdev_class_remove_file_ns(struct class_attribute *class_attr, + const void *ns); + +static inline int netdev_class_create_file(struct class_attribute *class_attr) +{ + return netdev_class_create_file_ns(class_attr, NULL); +} + +static inline void netdev_class_remove_file(struct class_attribute *class_attr) +{ + netdev_class_remove_file_ns(class_attr, NULL); +} extern struct kobj_ns_type_operations net_ns_type_operations; diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h index 11baec7c9b26..82f7fac78e77 100644 --- a/include/linux/sysfs.h +++ b/include/linux/sysfs.h @@ -173,7 +173,6 @@ struct bin_attribute bin_attr_##_name = __BIN_ATTR_RW(_name, _size) struct sysfs_ops { ssize_t (*show)(struct kobject *, struct attribute *, char *); ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t); - const void *(*namespace)(struct kobject *, const struct attribute *); }; struct sysfs_dirent; @@ -189,13 +188,15 @@ int __must_check sysfs_rename_dir(struct kobject *kobj, const char *new_name); int __must_check sysfs_move_dir(struct kobject *kobj, struct kobject *new_parent_kobj); -int __must_check sysfs_create_file(struct kobject *kobj, - const struct attribute *attr); +int __must_check sysfs_create_file_ns(struct kobject *kobj, + const struct attribute *attr, + const void *ns); int __must_check sysfs_create_files(struct kobject *kobj, const struct attribute **attr); int __must_check sysfs_chmod_file(struct kobject *kobj, const struct attribute *attr, umode_t mode); -void sysfs_remove_file(struct kobject *kobj, const struct attribute *attr); +void sysfs_remove_file_ns(struct kobject *kobj, const struct attribute *attr, + const void *ns); void sysfs_remove_files(struct kobject *kobj, const struct attribute **attr); int __must_check sysfs_create_bin_file(struct kobject *kobj, @@ -277,8 +278,9 @@ static inline int sysfs_move_dir(struct kobject *kobj, return 0; } -static inline int sysfs_create_file(struct kobject *kobj, - const struct attribute *attr) +static inline int sysfs_create_file_ns(struct kobject *kobj, + const struct attribute *attr, + const void *ns) { return 0; } @@ -295,8 +297,9 @@ static inline int sysfs_chmod_file(struct kobject *kobj, return 0; } -static inline void sysfs_remove_file(struct kobject *kobj, - const struct attribute *attr) +static inline void sysfs_remove_file_ns(struct kobject *kobj, + const struct attribute *attr, + const void *ns) { } @@ -435,4 +438,16 @@ static inline int __must_check sysfs_init(void) #endif /* CONFIG_SYSFS */ +static inline int __must_check sysfs_create_file(struct kobject *kobj, + const struct attribute *attr) +{ + return sysfs_create_file_ns(kobj, attr, NULL); +} + +static inline void sysfs_remove_file(struct kobject *kobj, + const struct attribute *attr) +{ + return sysfs_remove_file_ns(kobj, attr, NULL); +} + #endif /* _SYSFS_H_ */ diff --git a/net/core/net-sysfs.c b/net/core/net-sysfs.c index d954b56b4e47..325dee863e46 100644 --- a/net/core/net-sysfs.c +++ b/net/core/net-sysfs.c @@ -1344,17 +1344,19 @@ int netdev_register_kobject(struct net_device *net) return error; } -int netdev_class_create_file(struct class_attribute *class_attr) +int netdev_class_create_file_ns(struct class_attribute *class_attr, + const void *ns) { - return class_create_file(&net_class, class_attr); + return class_create_file_ns(&net_class, class_attr, ns); } -EXPORT_SYMBOL(netdev_class_create_file); +EXPORT_SYMBOL(netdev_class_create_file_ns); -void netdev_class_remove_file(struct class_attribute *class_attr) +void netdev_class_remove_file_ns(struct class_attribute *class_attr, + const void *ns) { - class_remove_file(&net_class, class_attr); + class_remove_file_ns(&net_class, class_attr, ns); } -EXPORT_SYMBOL(netdev_class_remove_file); +EXPORT_SYMBOL(netdev_class_remove_file_ns); int netdev_kobject_init(void) { -- cgit v1.2.3 From e34ff4906199d2ebd248ae897ae34f52bea151c9 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 11 Sep 2013 22:29:05 -0400 Subject: sysfs: remove ktype->namespace() invocations in directory code For some unrecognizable reason, namespace information is communicated to sysfs through ktype->namespace() callback when there's *nothing* which needs the use of a callback. The whole sequence of operations is completely synchronous and sysfs operations simply end up calling back into the layer which just invoked it in order to find out the namespace information, which is completely backwards, obfuscates what's going on and unnecessarily tangles two separate layers. This patch doesn't remove ktype->namespace() but shifts its handling to kobject layer. We probably want to get rid of the callback in the long term. This patch adds an explicit param to sysfs_{create|rename|move}_dir() and renames them to sysfs_{create|rename|move}_dir_ns(), respectively. ktype->namespace() invocations are moved to the calling sites of the above functions. A new helper kboject_namespace() is introduced which directly tests kobj_ns_type_operations->type which should give the same result as testing sysfs_fs_type(parent_sd) and returns @kobj's namespace tag as necessary. kobject_namespace() is extern as it will be used from another file in the following patches. This patch should be an equivalent conversion without any functional difference. Signed-off-by: Tejun Heo Cc: Eric W. Biederman Cc: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 23 ++++++++--------------- include/linux/kobject.h | 1 + include/linux/sysfs.h | 20 ++++++++++++-------- lib/kobject.c | 28 ++++++++++++++++++++++++---- 4 files changed, 45 insertions(+), 27 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 834c64cb7f88..878ac3afe1b8 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -730,14 +730,14 @@ static enum kobj_ns_type sysfs_read_ns_type(struct kobject *kobj) } /** - * sysfs_create_dir - create a directory for an object. - * @kobj: object we're creating directory for. + * sysfs_create_dir_ns - create a directory for an object with a namespace tag + * @kobj: object we're creating directory for + * @ns: the namespace tag to use */ -int sysfs_create_dir(struct kobject *kobj) +int sysfs_create_dir_ns(struct kobject *kobj, const void *ns) { enum kobj_ns_type type; struct sysfs_dirent *parent_sd, *sd; - const void *ns = NULL; int error = 0; BUG_ON(!kobj); @@ -750,8 +750,6 @@ int sysfs_create_dir(struct kobject *kobj) if (!parent_sd) return -ENOENT; - if (sysfs_ns_type(parent_sd)) - ns = kobj->ktype->namespace(kobj); type = sysfs_read_ns_type(kobj); error = create_dir(kobj, parent_sd, type, ns, kobject_name(kobj), &sd); @@ -909,26 +907,21 @@ int sysfs_rename(struct sysfs_dirent *sd, return error; } -int sysfs_rename_dir(struct kobject *kobj, const char *new_name) +int sysfs_rename_dir_ns(struct kobject *kobj, const char *new_name, + const void *new_ns) { struct sysfs_dirent *parent_sd = kobj->sd->s_parent; - const void *new_ns = NULL; - - if (sysfs_ns_type(parent_sd)) - new_ns = kobj->ktype->namespace(kobj); return sysfs_rename(kobj->sd, parent_sd, new_ns, new_name); } -int sysfs_move_dir(struct kobject *kobj, struct kobject *new_parent_kobj) +int sysfs_move_dir_ns(struct kobject *kobj, struct kobject *new_parent_kobj, + const void *new_ns) { struct sysfs_dirent *sd = kobj->sd; struct sysfs_dirent *new_parent_sd; - const void *new_ns = NULL; BUG_ON(!sd->s_parent); - if (sysfs_ns_type(sd->s_parent)) - new_ns = kobj->ktype->namespace(kobj); new_parent_sd = new_parent_kobj && new_parent_kobj->sd ? new_parent_kobj->sd : &sysfs_root; diff --git a/include/linux/kobject.h b/include/linux/kobject.h index de6dcbcc6ef7..e7ba650086ce 100644 --- a/include/linux/kobject.h +++ b/include/linux/kobject.h @@ -107,6 +107,7 @@ extern int __must_check kobject_move(struct kobject *, struct kobject *); extern struct kobject *kobject_get(struct kobject *kobj); extern void kobject_put(struct kobject *kobj); +extern const void *kobject_namespace(struct kobject *kobj); extern char *kobject_get_path(struct kobject *kobj, gfp_t flag); struct kobj_type { diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h index 82f7fac78e77..7f56bad3be82 100644 --- a/include/linux/sysfs.h +++ b/include/linux/sysfs.h @@ -182,11 +182,13 @@ struct sysfs_dirent; int sysfs_schedule_callback(struct kobject *kobj, void (*func)(void *), void *data, struct module *owner); -int __must_check sysfs_create_dir(struct kobject *kobj); +int __must_check sysfs_create_dir_ns(struct kobject *kobj, const void *ns); void sysfs_remove_dir(struct kobject *kobj); -int __must_check sysfs_rename_dir(struct kobject *kobj, const char *new_name); -int __must_check sysfs_move_dir(struct kobject *kobj, - struct kobject *new_parent_kobj); +int __must_check sysfs_rename_dir_ns(struct kobject *kobj, const char *new_name, + const void *new_ns); +int __must_check sysfs_move_dir_ns(struct kobject *kobj, + struct kobject *new_parent_kobj, + const void *new_ns); int __must_check sysfs_create_file_ns(struct kobject *kobj, const struct attribute *attr, @@ -258,7 +260,7 @@ static inline int sysfs_schedule_callback(struct kobject *kobj, return -ENOSYS; } -static inline int sysfs_create_dir(struct kobject *kobj) +static inline int sysfs_create_dir_ns(struct kobject *kobj, const void *ns) { return 0; } @@ -267,13 +269,15 @@ static inline void sysfs_remove_dir(struct kobject *kobj) { } -static inline int sysfs_rename_dir(struct kobject *kobj, const char *new_name) +static inline int sysfs_rename_dir_ns(struct kobject *kobj, + const char *new_name, const void *new_ns) { return 0; } -static inline int sysfs_move_dir(struct kobject *kobj, - struct kobject *new_parent_kobj) +static inline int sysfs_move_dir_ns(struct kobject *kobj, + struct kobject *new_parent_kobj, + const void *new_ns) { return 0; } diff --git a/lib/kobject.c b/lib/kobject.c index 962175134702..85fb3a161b21 100644 --- a/lib/kobject.c +++ b/lib/kobject.c @@ -18,6 +18,24 @@ #include #include +/** + * kobject_namespace - return @kobj's namespace tag + * @kobj: kobject in question + * + * Returns namespace tag of @kobj if its parent has namespace ops enabled + * and thus @kobj should have a namespace tag associated with it. Returns + * %NULL otherwise. + */ +const void *kobject_namespace(struct kobject *kobj) +{ + const struct kobj_ns_type_operations *ns_ops = kobj_ns_ops(kobj); + + if (!ns_ops || ns_ops->type == KOBJ_NS_TYPE_NONE) + return NULL; + + return kobj->ktype->namespace(kobj); +} + /* * populate_dir - populate directory with attributes. * @kobj: object we're working on. @@ -46,8 +64,9 @@ static int populate_dir(struct kobject *kobj) static int create_dir(struct kobject *kobj) { - int error = 0; - error = sysfs_create_dir(kobj); + int error; + + error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj)); if (!error) { error = populate_dir(kobj); if (error) @@ -428,7 +447,7 @@ int kobject_rename(struct kobject *kobj, const char *new_name) goto out; } - error = sysfs_rename_dir(kobj, new_name); + error = sysfs_rename_dir_ns(kobj, new_name, kobject_namespace(kobj)); if (error) goto out; @@ -472,6 +491,7 @@ int kobject_move(struct kobject *kobj, struct kobject *new_parent) if (kobj->kset) new_parent = kobject_get(&kobj->kset->kobj); } + /* old object path */ devpath = kobject_get_path(kobj, GFP_KERNEL); if (!devpath) { @@ -486,7 +506,7 @@ int kobject_move(struct kobject *kobj, struct kobject *new_parent) sprintf(devpath_string, "DEVPATH_OLD=%s", devpath); envp[0] = devpath_string; envp[1] = NULL; - error = sysfs_move_dir(kobj, new_parent); + error = sysfs_move_dir_ns(kobj, new_parent, kobject_namespace(kobj)); if (error) goto out; old_parent = kobj->parent; -- cgit v1.2.3 From 4b30ee58ee64c64f59fd876e4afa6ed82caef3a4 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 11 Sep 2013 22:29:06 -0400 Subject: sysfs: remove ktype->namespace() invocations in symlink code There's no reason for sysfs to be calling ktype->namespace(). It is backwards, obfuscates what's going on and unnecessarily tangles two separate layers. There are two places where symlink code calls ktype->namespace(). * sysfs_do_create_link_sd() calls it to find out the namespace tag of the target directory. Unless symlinking races with cross-namespace renaming, this equals @target_sd->s_ns. * sysfs_rename_link() uses it to find out the new namespace to rename to and the new namespace can be different from the existing one. The function is renamed to sysfs_rename_link_ns() with an explicit @ns argument and the ktype->namespace() invocation is shifted to the device layer. While this patch replaces ktype->namespace() invocation with the recorded result in @target_sd, this shouldn't result in any behvior difference. Signed-off-by: Tejun Heo Cc: Eric W. Biederman Cc: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 8 +++++--- fs/sysfs/symlink.c | 16 +++++++--------- include/linux/sysfs.h | 16 ++++++++++++---- 3 files changed, 24 insertions(+), 16 deletions(-) (limited to 'fs') diff --git a/drivers/base/core.c b/drivers/base/core.c index c7cfadcf6752..3335000be2dc 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -1881,6 +1881,7 @@ EXPORT_SYMBOL_GPL(device_destroy); */ int device_rename(struct device *dev, const char *new_name) { + struct kobject *kobj = &dev->kobj; char *old_device_name = NULL; int error; @@ -1898,13 +1899,14 @@ int device_rename(struct device *dev, const char *new_name) } if (dev->class) { - error = sysfs_rename_link(&dev->class->p->subsys.kobj, - &dev->kobj, old_device_name, new_name); + error = sysfs_rename_link_ns(&dev->class->p->subsys.kobj, + kobj, old_device_name, + new_name, kobject_namespace(kobj)); if (error) goto out; } - error = kobject_rename(&dev->kobj, new_name); + error = kobject_rename(kobj, new_name); if (error) goto out; diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c index 2dd4507d9edd..12d58ada3e6d 100644 --- a/fs/sysfs/symlink.c +++ b/fs/sysfs/symlink.c @@ -52,7 +52,7 @@ static int sysfs_do_create_link_sd(struct sysfs_dirent *parent_sd, ns_type = sysfs_ns_type(parent_sd); if (ns_type) - sd->s_ns = target->ktype->namespace(target); + sd->s_ns = target_sd->s_ns; sd->s_symlink.target_sd = target_sd; target_sd = NULL; /* reference is now owned by the symlink */ @@ -181,19 +181,20 @@ void sysfs_remove_link(struct kobject *kobj, const char *name) EXPORT_SYMBOL_GPL(sysfs_remove_link); /** - * sysfs_rename_link - rename symlink in object's directory. + * sysfs_rename_link_ns - rename symlink in object's directory. * @kobj: object we're acting for. * @targ: object we're pointing to. * @old: previous name of the symlink. * @new: new name of the symlink. + * @new_ns: new namespace of the symlink. * * A helper function for the common rename symlink idiom. */ -int sysfs_rename_link(struct kobject *kobj, struct kobject *targ, - const char *old, const char *new) +int sysfs_rename_link_ns(struct kobject *kobj, struct kobject *targ, + const char *old, const char *new, const void *new_ns) { struct sysfs_dirent *parent_sd, *sd = NULL; - const void *old_ns = NULL, *new_ns = NULL; + const void *old_ns = NULL; int result; if (!kobj) @@ -215,16 +216,13 @@ int sysfs_rename_link(struct kobject *kobj, struct kobject *targ, if (sd->s_symlink.target_sd->s_dir.kobj != targ) goto out; - if (sysfs_ns_type(parent_sd)) - new_ns = targ->ktype->namespace(targ); - result = sysfs_rename(sd, parent_sd, new_ns, new); out: sysfs_put(sd); return result; } -EXPORT_SYMBOL_GPL(sysfs_rename_link); +EXPORT_SYMBOL_GPL(sysfs_rename_link_ns); static int sysfs_get_target_path(struct sysfs_dirent *parent_sd, struct sysfs_dirent *target_sd, char *path) diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h index 7f56bad3be82..c792f73ac7fa 100644 --- a/include/linux/sysfs.h +++ b/include/linux/sysfs.h @@ -213,8 +213,9 @@ int __must_check sysfs_create_link_nowarn(struct kobject *kobj, const char *name); void sysfs_remove_link(struct kobject *kobj, const char *name); -int sysfs_rename_link(struct kobject *kobj, struct kobject *target, - const char *old_name, const char *new_name); +int sysfs_rename_link_ns(struct kobject *kobj, struct kobject *target, + const char *old_name, const char *new_name, + const void *new_ns); void sysfs_delete_link(struct kobject *dir, struct kobject *targ, const char *name); @@ -340,8 +341,9 @@ static inline void sysfs_remove_link(struct kobject *kobj, const char *name) { } -static inline int sysfs_rename_link(struct kobject *k, struct kobject *t, - const char *old_name, const char *new_name) +static inline int sysfs_rename_link_ns(struct kobject *k, struct kobject *t, + const char *old_name, + const char *new_name, const void *ns) { return 0; } @@ -454,4 +456,10 @@ static inline void sysfs_remove_file(struct kobject *kobj, return sysfs_remove_file_ns(kobj, attr, NULL); } +static inline int sysfs_rename_link(struct kobject *kobj, struct kobject *target, + const char *old_name, const char *new_name) +{ + return sysfs_rename_link_ns(kobj, target, old_name, new_name, NULL); +} + #endif /* _SYSFS_H_ */ -- cgit v1.2.3 From cb26a311578e67769e92a39a0a63476533cb7e12 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 11 Sep 2013 22:29:07 -0400 Subject: sysfs: drop kobj_ns_type handling The way namespace tags are implemented in sysfs is more complicated than necessary. As each tag is a pointer value and required to be non-NULL under a namespace enabled parent, there's no need to record separately what type each tag is or where namespace is enabled. If multiple namespace types are needed, which currently aren't, we can simply compare the tag to a set of allowed tags in the superblock assuming that the tags, being pointers, won't have the same value across multiple types. Also, whether to filter by namespace tag or not can be trivially determined by whether the node has any tagged children or not. This patch rips out kobj_ns_type handling from sysfs. sysfs no longer cares whether specific type of namespace is enabled or not. If a sysfs_dirent has a non-NULL tag, the parent is marked as needing namespace filtering and the value is tested against the allowed set of tags for the superblock (currently only one but increasing this number isn't difficult) and the sysfs_dirent is ignored if it doesn't match. This removes most kobject namespace knowledge from sysfs proper which will enable proper separation and layering of sysfs. The namespace sanity checks in fs/sysfs/dir.c are replaced by the new sanity check in kobject_namespace(). As this is the only place ktype->namespace() is called for sysfs, this doesn't weaken the sanity check significantly. I omitted converting the sanity check in sysfs_do_create_link_sd(). While the check can be shifted to upper layer, mistakes there are well contained and should be easily visible anyway. Signed-off-by: Tejun Heo Cc: Eric W. Biederman Cc: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 90 +++++++++++++++--------------------------------------- fs/sysfs/mount.c | 24 ++++----------- fs/sysfs/symlink.c | 27 ++++------------ fs/sysfs/sysfs.h | 25 +++++---------- lib/kobject.c | 5 ++- 5 files changed, 48 insertions(+), 123 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 878ac3afe1b8..1dfb4aaf9446 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -111,6 +111,11 @@ static int sysfs_link_sibling(struct sysfs_dirent *sd) /* add new node and rebalance the tree */ rb_link_node(&sd->s_rb, parent, node); rb_insert_color(&sd->s_rb, &sd->s_parent->s_dir.children); + + /* if @sd has ns tag, mark the parent to enable ns filtering */ + if (sd->s_ns) + sd->s_parent->s_flags |= SYSFS_FLAG_HAS_NS; + return 0; } @@ -130,6 +135,13 @@ static void sysfs_unlink_sibling(struct sysfs_dirent *sd) sd->s_parent->s_dir.subdirs--; rb_erase(&sd->s_rb, &sd->s_parent->s_dir.children); + + /* + * Either all or none of the children have tags. Clearing HAS_NS + * when there's no child left is enough to keep the flag synced. + */ + if (RB_EMPTY_ROOT(&sd->s_parent->s_dir.children)) + sd->s_parent->s_flags &= ~SYSFS_FLAG_HAS_NS; } #ifdef CONFIG_DEBUG_LOCK_ALLOC @@ -297,7 +309,6 @@ static int sysfs_dentry_delete(const struct dentry *dentry) static int sysfs_dentry_revalidate(struct dentry *dentry, unsigned int flags) { struct sysfs_dirent *sd; - int type; if (flags & LOOKUP_RCU) return -ECHILD; @@ -318,13 +329,8 @@ static int sysfs_dentry_revalidate(struct dentry *dentry, unsigned int flags) goto out_bad; /* The sysfs dirent has been moved to a different namespace */ - type = KOBJ_NS_TYPE_NONE; - if (sd->s_parent) { - type = sysfs_ns_type(sd->s_parent); - if (type != KOBJ_NS_TYPE_NONE && - sysfs_info(dentry->d_sb)->ns[type] != sd->s_ns) - goto out_bad; - } + if (sd->s_ns && sd->s_ns != sysfs_info(dentry->d_sb)->ns) + goto out_bad; mutex_unlock(&sysfs_mutex); out_valid: @@ -445,13 +451,6 @@ int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) struct sysfs_inode_attrs *ps_iattr; int ret; - if (!!sysfs_ns_type(acxt->parent_sd) != !!sd->s_ns) { - WARN(1, KERN_WARNING "sysfs: ns %s in '%s' for '%s'\n", - sysfs_ns_type(acxt->parent_sd) ? "required" : "invalid", - acxt->parent_sd->s_name, sd->s_name); - return -EINVAL; - } - sd->s_hash = sysfs_name_hash(sd->s_ns, sd->s_name); sd->s_parent = sysfs_get(acxt->parent_sd); @@ -613,13 +612,6 @@ struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd, struct rb_node *node = parent_sd->s_dir.children.rb_node; unsigned int hash; - if (!!sysfs_ns_type(parent_sd) != !!ns) { - WARN(1, KERN_WARNING "sysfs: ns %s in '%s' for '%s'\n", - sysfs_ns_type(parent_sd) ? "required" : "invalid", - parent_sd->s_name, name); - return NULL; - } - hash = sysfs_name_hash(ns, name); while (node) { struct sysfs_dirent *sd; @@ -667,8 +659,7 @@ struct sysfs_dirent *sysfs_get_dirent(struct sysfs_dirent *parent_sd, EXPORT_SYMBOL_GPL(sysfs_get_dirent); static int create_dir(struct kobject *kobj, struct sysfs_dirent *parent_sd, - enum kobj_ns_type type, const void *ns, const char *name, - struct sysfs_dirent **p_sd) + const void *ns, const char *name, struct sysfs_dirent **p_sd) { umode_t mode = S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO; struct sysfs_addrm_cxt acxt; @@ -680,7 +671,6 @@ static int create_dir(struct kobject *kobj, struct sysfs_dirent *parent_sd, if (!sd) return -ENOMEM; - sd->s_flags |= (type << SYSFS_NS_TYPE_SHIFT); sd->s_ns = ns; sd->s_dir.kobj = kobj; @@ -700,33 +690,7 @@ static int create_dir(struct kobject *kobj, struct sysfs_dirent *parent_sd, int sysfs_create_subdir(struct kobject *kobj, const char *name, struct sysfs_dirent **p_sd) { - return create_dir(kobj, kobj->sd, - KOBJ_NS_TYPE_NONE, NULL, name, p_sd); -} - -/** - * sysfs_read_ns_type: return associated ns_type - * @kobj: the kobject being queried - * - * Each kobject can be tagged with exactly one namespace type - * (i.e. network or user). Return the ns_type associated with - * this object if any - */ -static enum kobj_ns_type sysfs_read_ns_type(struct kobject *kobj) -{ - const struct kobj_ns_type_operations *ops; - enum kobj_ns_type type; - - ops = kobj_child_ns_ops(kobj); - if (!ops) - return KOBJ_NS_TYPE_NONE; - - type = ops->type; - BUG_ON(type <= KOBJ_NS_TYPE_NONE); - BUG_ON(type >= KOBJ_NS_TYPES); - BUG_ON(!kobj_ns_type_registered(type)); - - return type; + return create_dir(kobj, kobj->sd, NULL, name, p_sd); } /** @@ -736,7 +700,6 @@ static enum kobj_ns_type sysfs_read_ns_type(struct kobject *kobj) */ int sysfs_create_dir_ns(struct kobject *kobj, const void *ns) { - enum kobj_ns_type type; struct sysfs_dirent *parent_sd, *sd; int error = 0; @@ -750,9 +713,7 @@ int sysfs_create_dir_ns(struct kobject *kobj, const void *ns) if (!parent_sd) return -ENOENT; - type = sysfs_read_ns_type(kobj); - - error = create_dir(kobj, parent_sd, type, ns, kobject_name(kobj), &sd); + error = create_dir(kobj, parent_sd, ns, kobject_name(kobj), &sd); if (!error) kobj->sd = sd; return error; @@ -766,13 +727,12 @@ static struct dentry *sysfs_lookup(struct inode *dir, struct dentry *dentry, struct sysfs_dirent *parent_sd = parent->d_fsdata; struct sysfs_dirent *sd; struct inode *inode; - enum kobj_ns_type type; - const void *ns; + const void *ns = NULL; mutex_lock(&sysfs_mutex); - type = sysfs_ns_type(parent_sd); - ns = sysfs_info(dir->i_sb)->ns[type]; + if (parent_sd->s_flags & SYSFS_FLAG_HAS_NS) + ns = sysfs_info(dir->i_sb)->ns; sd = sysfs_find_dirent(parent_sd, ns, dentry->d_name.name); @@ -995,15 +955,15 @@ static int sysfs_readdir(struct file *file, struct dir_context *ctx) struct dentry *dentry = file->f_path.dentry; struct sysfs_dirent *parent_sd = dentry->d_fsdata; struct sysfs_dirent *pos = file->private_data; - enum kobj_ns_type type; - const void *ns; - - type = sysfs_ns_type(parent_sd); - ns = sysfs_info(dentry->d_sb)->ns[type]; + const void *ns = NULL; if (!dir_emit_dots(file, ctx)) return 0; mutex_lock(&sysfs_mutex); + + if (parent_sd->s_flags & SYSFS_FLAG_HAS_NS) + ns = sysfs_info(dentry->d_sb)->ns; + for (pos = sysfs_dir_pos(ns, parent_sd, ctx->pos, pos); pos; pos = sysfs_dir_next_pos(ns, parent_sd, ctx->pos, pos)) { diff --git a/fs/sysfs/mount.c b/fs/sysfs/mount.c index 834ec2cdb7a3..8c24bce2f4ae 100644 --- a/fs/sysfs/mount.c +++ b/fs/sysfs/mount.c @@ -36,7 +36,7 @@ static const struct super_operations sysfs_ops = { struct sysfs_dirent sysfs_root = { .s_name = "", .s_count = ATOMIC_INIT(1), - .s_flags = SYSFS_DIR | (KOBJ_NS_TYPE_NONE << SYSFS_NS_TYPE_SHIFT), + .s_flags = SYSFS_DIR, .s_mode = S_IFDIR | S_IRUGO | S_IXUGO, .s_ino = 1, }; @@ -77,14 +77,8 @@ static int sysfs_test_super(struct super_block *sb, void *data) { struct sysfs_super_info *sb_info = sysfs_info(sb); struct sysfs_super_info *info = data; - enum kobj_ns_type type; - int found = 1; - for (type = KOBJ_NS_TYPE_NONE; type < KOBJ_NS_TYPES; type++) { - if (sb_info->ns[type] != info->ns[type]) - found = 0; - } - return found; + return sb_info->ns == info->ns; } static int sysfs_set_super(struct super_block *sb, void *data) @@ -98,9 +92,7 @@ static int sysfs_set_super(struct super_block *sb, void *data) static void free_sysfs_super_info(struct sysfs_super_info *info) { - int type; - for (type = KOBJ_NS_TYPE_NONE; type < KOBJ_NS_TYPES; type++) - kobj_ns_drop(type, info->ns[type]); + kobj_ns_drop(KOBJ_NS_TYPE_NET, info->ns); kfree(info); } @@ -108,7 +100,6 @@ static struct dentry *sysfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) { struct sysfs_super_info *info; - enum kobj_ns_type type; struct super_block *sb; int error; @@ -116,18 +107,15 @@ static struct dentry *sysfs_mount(struct file_system_type *fs_type, if (!capable(CAP_SYS_ADMIN) && !fs_fully_visible(fs_type)) return ERR_PTR(-EPERM); - for (type = KOBJ_NS_TYPE_NONE; type < KOBJ_NS_TYPES; type++) { - if (!kobj_ns_current_may_mount(type)) - return ERR_PTR(-EPERM); - } + if (!kobj_ns_current_may_mount(KOBJ_NS_TYPE_NET)) + return ERR_PTR(-EPERM); } info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) return ERR_PTR(-ENOMEM); - for (type = KOBJ_NS_TYPE_NONE; type < KOBJ_NS_TYPES; type++) - info->ns[type] = kobj_ns_grab_current(type); + info->ns = kobj_ns_grab_current(KOBJ_NS_TYPE_NET); sb = sget(fs_type, sysfs_test_super, sysfs_set_super, flags, info); if (IS_ERR(sb) || sb->s_fs_info != info) diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c index 12d58ada3e6d..7d981ce2e87f 100644 --- a/fs/sysfs/symlink.c +++ b/fs/sysfs/symlink.c @@ -28,7 +28,6 @@ static int sysfs_do_create_link_sd(struct sysfs_dirent *parent_sd, struct sysfs_dirent *target_sd = NULL; struct sysfs_dirent *sd = NULL; struct sysfs_addrm_cxt acxt; - enum kobj_ns_type ns_type; int error; BUG_ON(!name || !parent_sd); @@ -50,29 +49,15 @@ static int sysfs_do_create_link_sd(struct sysfs_dirent *parent_sd, if (!sd) goto out_put; - ns_type = sysfs_ns_type(parent_sd); - if (ns_type) - sd->s_ns = target_sd->s_ns; + sd->s_ns = target_sd->s_ns; sd->s_symlink.target_sd = target_sd; target_sd = NULL; /* reference is now owned by the symlink */ sysfs_addrm_start(&acxt, parent_sd); - /* Symlinks must be between directories with the same ns_type */ - if (!ns_type || - (ns_type == sysfs_ns_type(sd->s_symlink.target_sd->s_parent))) { - if (warn) - error = sysfs_add_one(&acxt, sd); - else - error = __sysfs_add_one(&acxt, sd); - } else { - error = -EINVAL; - WARN(1, KERN_WARNING - "sysfs: symlink across ns_types %s/%s -> %s/%s\n", - parent_sd->s_name, - sd->s_name, - sd->s_symlink.target_sd->s_parent->s_name, - sd->s_symlink.target_sd->s_name); - } + if (warn) + error = sysfs_add_one(&acxt, sd); + else + error = __sysfs_add_one(&acxt, sd); sysfs_addrm_finish(&acxt); if (error) @@ -156,7 +141,7 @@ void sysfs_delete_link(struct kobject *kobj, struct kobject *targ, { const void *ns = NULL; spin_lock(&sysfs_assoc_lock); - if (targ->sd && sysfs_ns_type(kobj->sd)) + if (targ->sd) ns = targ->sd->s_ns; spin_unlock(&sysfs_assoc_lock); sysfs_hash_and_remove(kobj->sd, ns, name); diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index a96da2559db2..7664d1b3d594 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -93,11 +93,8 @@ struct sysfs_dirent { #define SYSFS_COPY_NAME (SYSFS_DIR | SYSFS_KOBJ_LINK) #define SYSFS_ACTIVE_REF (SYSFS_KOBJ_ATTR | SYSFS_KOBJ_BIN_ATTR) -/* identify any namespace tag on sysfs_dirents */ -#define SYSFS_NS_TYPE_MASK 0xf00 -#define SYSFS_NS_TYPE_SHIFT 8 - -#define SYSFS_FLAG_MASK ~(SYSFS_NS_TYPE_MASK|SYSFS_TYPE_MASK) +#define SYSFS_FLAG_MASK ~SYSFS_TYPE_MASK +#define SYSFS_FLAG_HAS_NS 0x01000 #define SYSFS_FLAG_REMOVED 0x02000 static inline unsigned int sysfs_type(struct sysfs_dirent *sd) @@ -105,15 +102,6 @@ static inline unsigned int sysfs_type(struct sysfs_dirent *sd) return sd->s_flags & SYSFS_TYPE_MASK; } -/* - * Return any namespace tags on this dirent. - * enum kobj_ns_type is defined in linux/kobject.h - */ -static inline enum kobj_ns_type sysfs_ns_type(struct sysfs_dirent *sd) -{ - return (sd->s_flags & SYSFS_NS_TYPE_MASK) >> SYSFS_NS_TYPE_SHIFT; -} - #ifdef CONFIG_DEBUG_LOCK_ALLOC #define sysfs_dirent_init_lockdep(sd) \ do { \ @@ -141,12 +129,13 @@ struct sysfs_addrm_cxt { */ /* - * Each sb is associated with a set of namespace tags (i.e. - * the network namespace of the task which mounted this sysfs - * instance). + * Each sb is associated with one namespace tag, currently the network + * namespace of the task which mounted this sysfs instance. If multiple + * tags become necessary, make the following an array and compare + * sysfs_dirent tag against every entry. */ struct sysfs_super_info { - void *ns[KOBJ_NS_TYPES]; + void *ns; }; #define sysfs_info(SB) ((struct sysfs_super_info *)(SB->s_fs_info)) extern struct sysfs_dirent sysfs_root; diff --git a/lib/kobject.c b/lib/kobject.c index 85fb3a161b21..e769ee3c2fb9 100644 --- a/lib/kobject.c +++ b/lib/kobject.c @@ -29,11 +29,14 @@ const void *kobject_namespace(struct kobject *kobj) { const struct kobj_ns_type_operations *ns_ops = kobj_ns_ops(kobj); + const void *ns; if (!ns_ops || ns_ops->type == KOBJ_NS_TYPE_NONE) return NULL; - return kobj->ktype->namespace(kobj); + ns = kobj->ktype->namespace(kobj); + WARN_ON(!ns); /* @kobj in a namespace is required to have !NULL tag */ + return ns; } /* -- cgit v1.2.3 From 388975cccaaf11abd47525f664c76891c440481a Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 11 Sep 2013 23:19:13 -0400 Subject: sysfs: clean up sysfs_get_dirent() The pre-existing sysfs interfaces which take explicit namespace argument are weird in that they place the optional @ns in front of @name which is contrary to the established convention. For example, we end up forcing vast majority of sysfs_get_dirent() users to do sysfs_get_dirent(parent, NULL, name), which is silly and error-prone especially as @ns and @name may be interchanged without causing compilation warning. This renames sysfs_get_dirent() to sysfs_get_dirent_ns() and swap the positions of @name and @ns, and sysfs_get_dirent() is now a wrapper around sysfs_get_dirent_ns(). This makes confusions a lot less likely. There are other interfaces which take @ns before @name. They'll be updated by following patches. This patch doesn't introduce any functional changes. v2: EXPORT_SYMBOL_GPL() wasn't updated leading to undefined symbol error on module builds. Reported by build test robot. Fixed. Signed-off-by: Tejun Heo Cc: Eric W. Biederman Cc: Kay Sievers Cc: Fengguang Wu Signed-off-by: Greg Kroah-Hartman --- drivers/gpio/gpiolib.c | 2 +- drivers/md/bitmap.c | 4 ++-- drivers/md/md.c | 2 +- drivers/md/md.h | 2 +- fs/sysfs/dir.c | 11 ++++++----- fs/sysfs/file.c | 4 ++-- fs/sysfs/group.c | 10 +++++----- fs/sysfs/symlink.c | 2 +- fs/sysfs/sysfs.h | 3 --- include/linux/sysfs.h | 19 ++++++++++++------- 10 files changed, 31 insertions(+), 28 deletions(-) (limited to 'fs') diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 86ef3461ec06..a094356020a6 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -408,7 +408,7 @@ static int gpio_setup_irq(struct gpio_desc *desc, struct device *dev, IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING; if (!value_sd) { - value_sd = sysfs_get_dirent(dev->kobj.sd, NULL, "value"); + value_sd = sysfs_get_dirent(dev->kobj.sd, "value"); if (!value_sd) { ret = -ENODEV; goto err_out; diff --git a/drivers/md/bitmap.c b/drivers/md/bitmap.c index a7fd82133b12..12dc29ba7399 100644 --- a/drivers/md/bitmap.c +++ b/drivers/md/bitmap.c @@ -1654,9 +1654,9 @@ int bitmap_create(struct mddev *mddev) bitmap->mddev = mddev; if (mddev->kobj.sd) - bm = sysfs_get_dirent(mddev->kobj.sd, NULL, "bitmap"); + bm = sysfs_get_dirent(mddev->kobj.sd, "bitmap"); if (bm) { - bitmap->sysfs_can_clear = sysfs_get_dirent(bm, NULL, "can_clear"); + bitmap->sysfs_can_clear = sysfs_get_dirent(bm, "can_clear"); sysfs_put(bm); } else bitmap->sysfs_can_clear = NULL; diff --git a/drivers/md/md.c b/drivers/md/md.c index adf4d7e1d5e1..8a0d7625681c 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -3555,7 +3555,7 @@ level_store(struct mddev *mddev, const char *buf, size_t len) printk(KERN_WARNING "md: cannot register extra attributes for %s\n", mdname(mddev)); - mddev->sysfs_action = sysfs_get_dirent(mddev->kobj.sd, NULL, "sync_action"); + mddev->sysfs_action = sysfs_get_dirent(mddev->kobj.sd, "sync_action"); } if (mddev->pers->sync_request != NULL && pers->sync_request == NULL) { diff --git a/drivers/md/md.h b/drivers/md/md.h index 608050c43f17..b0051f2fbc0c 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -501,7 +501,7 @@ extern struct attribute_group md_bitmap_group; static inline struct sysfs_dirent *sysfs_get_dirent_safe(struct sysfs_dirent *sd, char *name) { if (sd) - return sysfs_get_dirent(sd, NULL, name); + return sysfs_get_dirent(sd, name); return sd; } static inline void sysfs_notify_dirent_safe(struct sysfs_dirent *sd) diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 1dfb4aaf9446..fee19d16e4a2 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -630,9 +630,10 @@ struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd, } /** - * sysfs_get_dirent - find and get sysfs_dirent with the given name + * sysfs_get_dirent_ns - find and get sysfs_dirent with the given name * @parent_sd: sysfs_dirent to search under * @name: name to look for + * @ns: the namespace tag to use * * Look for sysfs_dirent with name @name under @parent_sd and get * it if found. @@ -643,9 +644,9 @@ struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd, * RETURNS: * Pointer to sysfs_dirent if found, NULL if not. */ -struct sysfs_dirent *sysfs_get_dirent(struct sysfs_dirent *parent_sd, - const void *ns, - const unsigned char *name) +struct sysfs_dirent *sysfs_get_dirent_ns(struct sysfs_dirent *parent_sd, + const unsigned char *name, + const void *ns) { struct sysfs_dirent *sd; @@ -656,7 +657,7 @@ struct sysfs_dirent *sysfs_get_dirent(struct sysfs_dirent *parent_sd, return sd; } -EXPORT_SYMBOL_GPL(sysfs_get_dirent); +EXPORT_SYMBOL_GPL(sysfs_get_dirent_ns); static int create_dir(struct kobject *kobj, struct sysfs_dirent *parent_sd, const void *ns, const char *name, struct sysfs_dirent **p_sd) diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index e784340f1599..0f3214a70985 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -563,7 +563,7 @@ int sysfs_add_file_to_group(struct kobject *kobj, int error; if (group) - dir_sd = sysfs_get_dirent(kobj->sd, NULL, group); + dir_sd = sysfs_get_dirent(kobj->sd, group); else dir_sd = sysfs_get(kobj->sd); @@ -645,7 +645,7 @@ void sysfs_remove_file_from_group(struct kobject *kobj, struct sysfs_dirent *dir_sd; if (group) - dir_sd = sysfs_get_dirent(kobj->sd, NULL, group); + dir_sd = sysfs_get_dirent(kobj->sd, group); else dir_sd = sysfs_get(kobj->sd); if (dir_sd) { diff --git a/fs/sysfs/group.c b/fs/sysfs/group.c index 25c78f23dae8..21102158ca33 100644 --- a/fs/sysfs/group.c +++ b/fs/sysfs/group.c @@ -207,7 +207,7 @@ void sysfs_remove_group(struct kobject *kobj, struct sysfs_dirent *sd; if (grp->name) { - sd = sysfs_get_dirent(dir_sd, NULL, grp->name); + sd = sysfs_get_dirent(dir_sd, grp->name); if (!sd) { WARN(!sd, KERN_WARNING "sysfs group %p not found for kobject '%s'\n", @@ -262,7 +262,7 @@ int sysfs_merge_group(struct kobject *kobj, struct attribute *const *attr; int i; - dir_sd = sysfs_get_dirent(kobj->sd, NULL, grp->name); + dir_sd = sysfs_get_dirent(kobj->sd, grp->name); if (!dir_sd) return -ENOENT; @@ -289,7 +289,7 @@ void sysfs_unmerge_group(struct kobject *kobj, struct sysfs_dirent *dir_sd; struct attribute *const *attr; - dir_sd = sysfs_get_dirent(kobj->sd, NULL, grp->name); + dir_sd = sysfs_get_dirent(kobj->sd, grp->name); if (dir_sd) { for (attr = grp->attrs; *attr; ++attr) sysfs_hash_and_remove(dir_sd, NULL, (*attr)->name); @@ -311,7 +311,7 @@ int sysfs_add_link_to_group(struct kobject *kobj, const char *group_name, struct sysfs_dirent *dir_sd; int error = 0; - dir_sd = sysfs_get_dirent(kobj->sd, NULL, group_name); + dir_sd = sysfs_get_dirent(kobj->sd, group_name); if (!dir_sd) return -ENOENT; @@ -333,7 +333,7 @@ void sysfs_remove_link_from_group(struct kobject *kobj, const char *group_name, { struct sysfs_dirent *dir_sd; - dir_sd = sysfs_get_dirent(kobj->sd, NULL, group_name); + dir_sd = sysfs_get_dirent(kobj->sd, group_name); if (dir_sd) { sysfs_hash_and_remove(dir_sd, NULL, link_name); sysfs_put(dir_sd); diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c index 7d981ce2e87f..c96b31a16485 100644 --- a/fs/sysfs/symlink.c +++ b/fs/sysfs/symlink.c @@ -191,7 +191,7 @@ int sysfs_rename_link_ns(struct kobject *kobj, struct kobject *targ, old_ns = targ->sd->s_ns; result = -ENOENT; - sd = sysfs_get_dirent(parent_sd, old_ns, old); + sd = sysfs_get_dirent_ns(parent_sd, old, old_ns); if (!sd) goto out; diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index 7664d1b3d594..6faacafda777 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -164,9 +164,6 @@ void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt); struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd, const void *ns, const unsigned char *name); -struct sysfs_dirent *sysfs_get_dirent(struct sysfs_dirent *parent_sd, - const void *ns, - const unsigned char *name); struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type); void release_sysfs_dirent(struct sysfs_dirent *sd); diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h index c792f73ac7fa..6695040a0317 100644 --- a/include/linux/sysfs.h +++ b/include/linux/sysfs.h @@ -245,9 +245,9 @@ void sysfs_remove_link_from_group(struct kobject *kobj, const char *group_name, void sysfs_notify(struct kobject *kobj, const char *dir, const char *attr); void sysfs_notify_dirent(struct sysfs_dirent *sd); -struct sysfs_dirent *sysfs_get_dirent(struct sysfs_dirent *parent_sd, - const void *ns, - const unsigned char *name); +struct sysfs_dirent *sysfs_get_dirent_ns(struct sysfs_dirent *parent_sd, + const unsigned char *name, + const void *ns); struct sysfs_dirent *sysfs_get(struct sysfs_dirent *sd); void sysfs_put(struct sysfs_dirent *sd); @@ -422,10 +422,9 @@ static inline void sysfs_notify(struct kobject *kobj, const char *dir, static inline void sysfs_notify_dirent(struct sysfs_dirent *sd) { } -static inline -struct sysfs_dirent *sysfs_get_dirent(struct sysfs_dirent *parent_sd, - const void *ns, - const unsigned char *name) +static inline struct sysfs_dirent * +sysfs_get_dirent_ns(struct sysfs_dirent *parent_sd, const unsigned char *name, + const void *ns) { return NULL; } @@ -462,4 +461,10 @@ static inline int sysfs_rename_link(struct kobject *kobj, struct kobject *target return sysfs_rename_link_ns(kobj, target, old_name, new_name, NULL); } +static inline struct sysfs_dirent * +sysfs_get_dirent(struct sysfs_dirent *parent_sd, const unsigned char *name) +{ + return sysfs_get_dirent_ns(parent_sd, name, NULL); +} + #endif /* _SYSFS_H_ */ -- cgit v1.2.3 From cfec0bc835c84d3d3723d4955587f05a94879b26 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 11 Sep 2013 22:29:09 -0400 Subject: sysfs: @name comes before @ns Some internal sysfs functions which take explicit namespace argument are weird in that they place the optional @ns in front of @name which is contrary to the established convention. This is confusing and error-prone especially as @ns and @name may be interchanged without causing compilation warning. Swap the positions of @name and @ns in the following internal functions. sysfs_find_dirent() sysfs_rename() sysfs_hash_and_remove() sysfs_name_hash() sysfs_name_compare() create_dir() This patch doesn't introduce any functional changes. Signed-off-by: Tejun Heo Cc: Eric W. Biederman Cc: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/bin.c | 2 +- fs/sysfs/dir.c | 45 +++++++++++++++++++++++---------------------- fs/sysfs/file.c | 10 +++++----- fs/sysfs/group.c | 12 ++++++------ fs/sysfs/inode.c | 6 +++--- fs/sysfs/symlink.c | 6 +++--- fs/sysfs/sysfs.h | 10 +++++----- 7 files changed, 46 insertions(+), 45 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/bin.c b/fs/sysfs/bin.c index c590cabd57bb..d49e6ca3345b 100644 --- a/fs/sysfs/bin.c +++ b/fs/sysfs/bin.c @@ -497,6 +497,6 @@ EXPORT_SYMBOL_GPL(sysfs_create_bin_file); void sysfs_remove_bin_file(struct kobject *kobj, const struct bin_attribute *attr) { - sysfs_hash_and_remove(kobj->sd, NULL, attr->attr.name); + sysfs_hash_and_remove(kobj->sd, attr->attr.name, NULL); } EXPORT_SYMBOL_GPL(sysfs_remove_bin_file); diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index fee19d16e4a2..d23e66dfba74 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -35,12 +35,12 @@ static DEFINE_IDA(sysfs_ino_ida); /** * sysfs_name_hash - * @ns: Namespace tag to hash * @name: Null terminated string to hash + * @ns: Namespace tag to hash * * Returns 31 bit hash of ns + name (so it fits in an off_t ) */ -static unsigned int sysfs_name_hash(const void *ns, const char *name) +static unsigned int sysfs_name_hash(const char *name, const void *ns) { unsigned long hash = init_name_hash(); unsigned int len = strlen(name); @@ -56,8 +56,8 @@ static unsigned int sysfs_name_hash(const void *ns, const char *name) return hash; } -static int sysfs_name_compare(unsigned int hash, const void *ns, - const char *name, const struct sysfs_dirent *sd) +static int sysfs_name_compare(unsigned int hash, const char *name, + const void *ns, const struct sysfs_dirent *sd) { if (hash != sd->s_hash) return hash - sd->s_hash; @@ -69,7 +69,7 @@ static int sysfs_name_compare(unsigned int hash, const void *ns, static int sysfs_sd_compare(const struct sysfs_dirent *left, const struct sysfs_dirent *right) { - return sysfs_name_compare(left->s_hash, left->s_ns, left->s_name, + return sysfs_name_compare(left->s_hash, left->s_name, left->s_ns, right); } @@ -451,7 +451,7 @@ int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) struct sysfs_inode_attrs *ps_iattr; int ret; - sd->s_hash = sysfs_name_hash(sd->s_ns, sd->s_name); + sd->s_hash = sysfs_name_hash(sd->s_name, sd->s_ns); sd->s_parent = sysfs_get(acxt->parent_sd); ret = sysfs_link_sibling(sd); @@ -596,6 +596,7 @@ void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt) * sysfs_find_dirent - find sysfs_dirent with the given name * @parent_sd: sysfs_dirent to search under * @name: name to look for + * @ns: the namespace tag to use * * Look for sysfs_dirent with name @name under @parent_sd. * @@ -606,19 +607,19 @@ void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt) * Pointer to sysfs_dirent if found, NULL if not. */ struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd, - const void *ns, - const unsigned char *name) + const unsigned char *name, + const void *ns) { struct rb_node *node = parent_sd->s_dir.children.rb_node; unsigned int hash; - hash = sysfs_name_hash(ns, name); + hash = sysfs_name_hash(name, ns); while (node) { struct sysfs_dirent *sd; int result; sd = to_sysfs_dirent(node); - result = sysfs_name_compare(hash, ns, name, sd); + result = sysfs_name_compare(hash, name, ns, sd); if (result < 0) node = node->rb_left; else if (result > 0) @@ -651,7 +652,7 @@ struct sysfs_dirent *sysfs_get_dirent_ns(struct sysfs_dirent *parent_sd, struct sysfs_dirent *sd; mutex_lock(&sysfs_mutex); - sd = sysfs_find_dirent(parent_sd, ns, name); + sd = sysfs_find_dirent(parent_sd, name, ns); sysfs_get(sd); mutex_unlock(&sysfs_mutex); @@ -660,7 +661,8 @@ struct sysfs_dirent *sysfs_get_dirent_ns(struct sysfs_dirent *parent_sd, EXPORT_SYMBOL_GPL(sysfs_get_dirent_ns); static int create_dir(struct kobject *kobj, struct sysfs_dirent *parent_sd, - const void *ns, const char *name, struct sysfs_dirent **p_sd) + const char *name, const void *ns, + struct sysfs_dirent **p_sd) { umode_t mode = S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO; struct sysfs_addrm_cxt acxt; @@ -691,7 +693,7 @@ static int create_dir(struct kobject *kobj, struct sysfs_dirent *parent_sd, int sysfs_create_subdir(struct kobject *kobj, const char *name, struct sysfs_dirent **p_sd) { - return create_dir(kobj, kobj->sd, NULL, name, p_sd); + return create_dir(kobj, kobj->sd, name, NULL, p_sd); } /** @@ -714,7 +716,7 @@ int sysfs_create_dir_ns(struct kobject *kobj, const void *ns) if (!parent_sd) return -ENOENT; - error = create_dir(kobj, parent_sd, ns, kobject_name(kobj), &sd); + error = create_dir(kobj, parent_sd, kobject_name(kobj), ns, &sd); if (!error) kobj->sd = sd; return error; @@ -735,7 +737,7 @@ static struct dentry *sysfs_lookup(struct inode *dir, struct dentry *dentry, if (parent_sd->s_flags & SYSFS_FLAG_HAS_NS) ns = sysfs_info(dir->i_sb)->ns; - sd = sysfs_find_dirent(parent_sd, ns, dentry->d_name.name); + sd = sysfs_find_dirent(parent_sd, dentry->d_name.name, ns); /* no such entry */ if (!sd) { @@ -823,9 +825,8 @@ void sysfs_remove_dir(struct kobject *kobj) __sysfs_remove_dir(sd); } -int sysfs_rename(struct sysfs_dirent *sd, - struct sysfs_dirent *new_parent_sd, const void *new_ns, - const char *new_name) +int sysfs_rename(struct sysfs_dirent *sd, struct sysfs_dirent *new_parent_sd, + const char *new_name, const void *new_ns) { int error; @@ -837,7 +838,7 @@ int sysfs_rename(struct sysfs_dirent *sd, goto out; /* nothing to rename */ error = -EEXIST; - if (sysfs_find_dirent(new_parent_sd, new_ns, new_name)) + if (sysfs_find_dirent(new_parent_sd, new_name, new_ns)) goto out; /* rename sysfs_dirent */ @@ -858,7 +859,7 @@ int sysfs_rename(struct sysfs_dirent *sd, sysfs_get(new_parent_sd); sysfs_put(sd->s_parent); sd->s_ns = new_ns; - sd->s_hash = sysfs_name_hash(sd->s_ns, sd->s_name); + sd->s_hash = sysfs_name_hash(sd->s_name, sd->s_ns); sd->s_parent = new_parent_sd; sysfs_link_sibling(sd); @@ -873,7 +874,7 @@ int sysfs_rename_dir_ns(struct kobject *kobj, const char *new_name, { struct sysfs_dirent *parent_sd = kobj->sd->s_parent; - return sysfs_rename(kobj->sd, parent_sd, new_ns, new_name); + return sysfs_rename(kobj->sd, parent_sd, new_name, new_ns); } int sysfs_move_dir_ns(struct kobject *kobj, struct kobject *new_parent_kobj, @@ -886,7 +887,7 @@ int sysfs_move_dir_ns(struct kobject *kobj, struct kobject *new_parent_kobj, new_parent_sd = new_parent_kobj && new_parent_kobj->sd ? new_parent_kobj->sd : &sysfs_root; - return sysfs_rename(sd, new_parent_sd, new_ns, sd->s_name); + return sysfs_rename(sd, new_parent_sd, sd->s_name, new_ns); } /* Relationship between s_mode and the DT_xxx types */ diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 0f3214a70985..4697019fafa3 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -466,9 +466,9 @@ void sysfs_notify(struct kobject *k, const char *dir, const char *attr) mutex_lock(&sysfs_mutex); if (sd && dir) - sd = sysfs_find_dirent(sd, NULL, dir); + sd = sysfs_find_dirent(sd, dir, NULL); if (sd && attr) - sd = sysfs_find_dirent(sd, NULL, attr); + sd = sysfs_find_dirent(sd, attr, NULL); if (sd) sysfs_notify_dirent(sd); @@ -594,7 +594,7 @@ int sysfs_chmod_file(struct kobject *kobj, const struct attribute *attr, mutex_lock(&sysfs_mutex); rc = -ENOENT; - sd = sysfs_find_dirent(kobj->sd, NULL, attr->name); + sd = sysfs_find_dirent(kobj->sd, attr->name, NULL); if (!sd) goto out; @@ -621,7 +621,7 @@ void sysfs_remove_file_ns(struct kobject *kobj, const struct attribute *attr, { struct sysfs_dirent *dir_sd = kobj->sd; - sysfs_hash_and_remove(dir_sd, ns, attr->name); + sysfs_hash_and_remove(dir_sd, attr->name, ns); } EXPORT_SYMBOL_GPL(sysfs_remove_file_ns); @@ -649,7 +649,7 @@ void sysfs_remove_file_from_group(struct kobject *kobj, else dir_sd = sysfs_get(kobj->sd); if (dir_sd) { - sysfs_hash_and_remove(dir_sd, NULL, attr->name); + sysfs_hash_and_remove(dir_sd, attr->name, NULL); sysfs_put(dir_sd); } } diff --git a/fs/sysfs/group.c b/fs/sysfs/group.c index 21102158ca33..2dae55c4f7dc 100644 --- a/fs/sysfs/group.c +++ b/fs/sysfs/group.c @@ -26,7 +26,7 @@ static void remove_files(struct sysfs_dirent *dir_sd, struct kobject *kobj, if (grp->attrs) for (attr = grp->attrs; *attr; attr++) - sysfs_hash_and_remove(dir_sd, NULL, (*attr)->name); + sysfs_hash_and_remove(dir_sd, (*attr)->name, NULL); if (grp->bin_attrs) for (bin_attr = grp->bin_attrs; *bin_attr; bin_attr++) sysfs_remove_bin_file(kobj, *bin_attr); @@ -49,8 +49,8 @@ static int create_files(struct sysfs_dirent *dir_sd, struct kobject *kobj, * re-adding (if required) the file. */ if (update) - sysfs_hash_and_remove(dir_sd, NULL, - (*attr)->name); + sysfs_hash_and_remove(dir_sd, (*attr)->name, + NULL); if (grp->is_visible) { mode = grp->is_visible(kobj, *attr, i); if (!mode) @@ -270,7 +270,7 @@ int sysfs_merge_group(struct kobject *kobj, error = sysfs_add_file(dir_sd, *attr, SYSFS_KOBJ_ATTR); if (error) { while (--i >= 0) - sysfs_hash_and_remove(dir_sd, NULL, (*--attr)->name); + sysfs_hash_and_remove(dir_sd, (*--attr)->name, NULL); } sysfs_put(dir_sd); @@ -292,7 +292,7 @@ void sysfs_unmerge_group(struct kobject *kobj, dir_sd = sysfs_get_dirent(kobj->sd, grp->name); if (dir_sd) { for (attr = grp->attrs; *attr; ++attr) - sysfs_hash_and_remove(dir_sd, NULL, (*attr)->name); + sysfs_hash_and_remove(dir_sd, (*attr)->name, NULL); sysfs_put(dir_sd); } } @@ -335,7 +335,7 @@ void sysfs_remove_link_from_group(struct kobject *kobj, const char *group_name, dir_sd = sysfs_get_dirent(kobj->sd, group_name); if (dir_sd) { - sysfs_hash_and_remove(dir_sd, NULL, link_name); + sysfs_hash_and_remove(dir_sd, link_name, NULL); sysfs_put(dir_sd); } } diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c index 963f910c8034..07193d720d92 100644 --- a/fs/sysfs/inode.c +++ b/fs/sysfs/inode.c @@ -314,8 +314,8 @@ void sysfs_evict_inode(struct inode *inode) sysfs_put(sd); } -int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const void *ns, - const char *name) +int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const char *name, + const void *ns) { struct sysfs_addrm_cxt acxt; struct sysfs_dirent *sd; @@ -328,7 +328,7 @@ int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const void *ns, sysfs_addrm_start(&acxt, dir_sd); - sd = sysfs_find_dirent(dir_sd, ns, name); + sd = sysfs_find_dirent(dir_sd, name, ns); if (sd) sysfs_remove_one(&acxt, sd); diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c index c96b31a16485..88c8bc5e8911 100644 --- a/fs/sysfs/symlink.c +++ b/fs/sysfs/symlink.c @@ -144,7 +144,7 @@ void sysfs_delete_link(struct kobject *kobj, struct kobject *targ, if (targ->sd) ns = targ->sd->s_ns; spin_unlock(&sysfs_assoc_lock); - sysfs_hash_and_remove(kobj->sd, ns, name); + sysfs_hash_and_remove(kobj->sd, name, ns); } /** @@ -161,7 +161,7 @@ void sysfs_remove_link(struct kobject *kobj, const char *name) else parent_sd = kobj->sd; - sysfs_hash_and_remove(parent_sd, NULL, name); + sysfs_hash_and_remove(parent_sd, name, NULL); } EXPORT_SYMBOL_GPL(sysfs_remove_link); @@ -201,7 +201,7 @@ int sysfs_rename_link_ns(struct kobject *kobj, struct kobject *targ, if (sd->s_symlink.target_sd->s_dir.kobj != targ) goto out; - result = sysfs_rename(sd, parent_sd, new_ns, new); + result = sysfs_rename(sd, parent_sd, new, new_ns); out: sysfs_put(sd); diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index 6faacafda777..ee44fde199d0 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -162,8 +162,8 @@ void sysfs_remove_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd); void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt); struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd, - const void *ns, - const unsigned char *name); + const unsigned char *name, + const void *ns); struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type); void release_sysfs_dirent(struct sysfs_dirent *sd); @@ -173,7 +173,7 @@ int sysfs_create_subdir(struct kobject *kobj, const char *name, void sysfs_remove_subdir(struct sysfs_dirent *sd); int sysfs_rename(struct sysfs_dirent *sd, struct sysfs_dirent *new_parent_sd, - const void *ns, const char *new_name); + const char *new_name, const void *new_ns); static inline struct sysfs_dirent *__sysfs_get(struct sysfs_dirent *sd) { @@ -204,8 +204,8 @@ int sysfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat); int sysfs_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags); -int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const void *ns, - const char *name); +int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const char *name, + const void *ns); int sysfs_inode_init(void); /* -- cgit v1.2.3 From d69ac5a0bbcf1d9962883fb23e337caf5b38cec8 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 18 Sep 2013 17:15:35 -0400 Subject: sysfs: remove sysfs_addrm_cxt->parent_sd sysfs_addrm_start/finish() enclose sysfs_dirent additions and deletions and sysfs_addrm_cxt is used to record information necessary to finish the operations. Currently, sysfs_addrm_start() takes @parent_sd, records it in sysfs_addrm_cxt, and assumes that all operations in the block are performed under that @parent_sd. This assumption has been fine until now but we want to make some operations behave recursively and, while having @parent_sd recorded in sysfs_addrm_cxt doesn't necessarily prevents that, it becomes confusing. This patch removes sysfs_addrm_cxt->parent_sd and makes sysfs_add_one() take an explicit @parent_sd parameter. Note that sysfs_remove_one() doesn't need the extra argument as its parent is always known from the target @sd. While at it, add __acquires/releases() notations to sysfs_addrm_start/finish() respectively. This patch doesn't make any functional difference. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 52 +++++++++++++++++++++++++++------------------------- fs/sysfs/file.c | 4 ++-- fs/sysfs/inode.c | 2 +- fs/sysfs/symlink.c | 6 +++--- fs/sysfs/sysfs.h | 10 +++++----- 5 files changed, 38 insertions(+), 36 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index d23e66dfba74..671868914b5b 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -406,22 +406,19 @@ struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type) /** * sysfs_addrm_start - prepare for sysfs_dirent add/remove * @acxt: pointer to sysfs_addrm_cxt to be used - * @parent_sd: parent sysfs_dirent * - * This function is called when the caller is about to add or - * remove sysfs_dirent under @parent_sd. This function acquires - * sysfs_mutex. @acxt is used to keep and pass context to - * other addrm functions. + * This function is called when the caller is about to add or remove + * sysfs_dirent. This function acquires sysfs_mutex. @acxt is used + * to keep and pass context to other addrm functions. * * LOCKING: * Kernel thread context (may sleep). sysfs_mutex is locked on * return. */ -void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt, - struct sysfs_dirent *parent_sd) +void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt) + __acquires(sysfs_mutex) { memset(acxt, 0, sizeof(*acxt)); - acxt->parent_sd = parent_sd; mutex_lock(&sysfs_mutex); } @@ -430,10 +427,11 @@ void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt, * __sysfs_add_one - add sysfs_dirent to parent without warning * @acxt: addrm context to use * @sd: sysfs_dirent to be added + * @parent_sd: the parent sysfs_dirent to add @sd to * - * Get @acxt->parent_sd and set sd->s_parent to it and increment - * nlink of parent inode if @sd is a directory and link into the - * children list of the parent. + * Get @parent_sd and set @sd->s_parent to it and increment nlink of + * the parent inode if @sd is a directory and link into the children + * list of the parent. * * This function should be called between calls to * sysfs_addrm_start() and sysfs_addrm_finish() and should be @@ -446,20 +444,21 @@ void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt, * 0 on success, -EEXIST if entry with the given name already * exists. */ -int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) +int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd, + struct sysfs_dirent *parent_sd) { struct sysfs_inode_attrs *ps_iattr; int ret; sd->s_hash = sysfs_name_hash(sd->s_name, sd->s_ns); - sd->s_parent = sysfs_get(acxt->parent_sd); + sd->s_parent = sysfs_get(parent_sd); ret = sysfs_link_sibling(sd); if (ret) return ret; /* Update timestamps on the parent */ - ps_iattr = acxt->parent_sd->s_iattr; + ps_iattr = parent_sd->s_iattr; if (ps_iattr) { struct iattr *ps_iattrs = &ps_iattr->ia_iattr; ps_iattrs->ia_ctime = ps_iattrs->ia_mtime = CURRENT_TIME; @@ -493,10 +492,11 @@ static char *sysfs_pathname(struct sysfs_dirent *sd, char *path) * sysfs_add_one - add sysfs_dirent to parent * @acxt: addrm context to use * @sd: sysfs_dirent to be added + * @parent_sd: the parent sysfs_dirent to add @sd to * - * Get @acxt->parent_sd and set sd->s_parent to it and increment - * nlink of parent inode if @sd is a directory and link into the - * children list of the parent. + * Get @parent_sd and set @sd->s_parent to it and increment nlink of + * the parent inode if @sd is a directory and link into the children + * list of the parent. * * This function should be called between calls to * sysfs_addrm_start() and sysfs_addrm_finish() and should be @@ -509,17 +509,18 @@ static char *sysfs_pathname(struct sysfs_dirent *sd, char *path) * 0 on success, -EEXIST if entry with the given name already * exists. */ -int sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) +int sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd, + struct sysfs_dirent *parent_sd) { int ret; - ret = __sysfs_add_one(acxt, sd); + ret = __sysfs_add_one(acxt, sd, parent_sd); if (ret == -EEXIST) { char *path = kzalloc(PATH_MAX, GFP_KERNEL); WARN(1, KERN_WARNING "sysfs: cannot create duplicate filename '%s'\n", (path == NULL) ? sd->s_name - : (sysfs_pathname(acxt->parent_sd, path), + : (sysfs_pathname(parent_sd, path), strlcat(path, "/", PATH_MAX), strlcat(path, sd->s_name, PATH_MAX), path)); @@ -553,7 +554,7 @@ void sysfs_remove_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) sysfs_unlink_sibling(sd); /* Update timestamps on the parent */ - ps_iattr = acxt->parent_sd->s_iattr; + ps_iattr = sd->s_parent->s_iattr; if (ps_iattr) { struct iattr *ps_iattrs = &ps_iattr->ia_iattr; ps_iattrs->ia_ctime = ps_iattrs->ia_mtime = CURRENT_TIME; @@ -576,6 +577,7 @@ void sysfs_remove_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) * sysfs_mutex is released. */ void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt) + __releases(sysfs_mutex) { /* release resources acquired by sysfs_addrm_start() */ mutex_unlock(&sysfs_mutex); @@ -678,8 +680,8 @@ static int create_dir(struct kobject *kobj, struct sysfs_dirent *parent_sd, sd->s_dir.kobj = kobj; /* link in */ - sysfs_addrm_start(&acxt, parent_sd); - rc = sysfs_add_one(&acxt, sd); + sysfs_addrm_start(&acxt); + rc = sysfs_add_one(&acxt, sd, parent_sd); sysfs_addrm_finish(&acxt); if (rc == 0) @@ -772,7 +774,7 @@ static void remove_dir(struct sysfs_dirent *sd) { struct sysfs_addrm_cxt acxt; - sysfs_addrm_start(&acxt, sd->s_parent); + sysfs_addrm_start(&acxt); sysfs_remove_one(&acxt, sd); sysfs_addrm_finish(&acxt); } @@ -792,7 +794,7 @@ static void __sysfs_remove_dir(struct sysfs_dirent *dir_sd) return; pr_debug("sysfs %s: removing dir\n", dir_sd->s_name); - sysfs_addrm_start(&acxt, dir_sd); + sysfs_addrm_start(&acxt); pos = rb_first(&dir_sd->s_dir.children); while (pos) { struct sysfs_dirent *sd = to_sysfs_dirent(pos); diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 4697019fafa3..1656a79ea6c0 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -502,8 +502,8 @@ int sysfs_add_file_mode_ns(struct sysfs_dirent *dir_sd, sd->s_attr.attr = (void *)attr; sysfs_dirent_init_lockdep(sd); - sysfs_addrm_start(&acxt, dir_sd); - rc = sysfs_add_one(&acxt, sd); + sysfs_addrm_start(&acxt); + rc = sysfs_add_one(&acxt, sd, dir_sd); sysfs_addrm_finish(&acxt); if (rc) diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c index 07193d720d92..364c8873fbda 100644 --- a/fs/sysfs/inode.c +++ b/fs/sysfs/inode.c @@ -326,7 +326,7 @@ int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const char *name, return -ENOENT; } - sysfs_addrm_start(&acxt, dir_sd); + sysfs_addrm_start(&acxt); sd = sysfs_find_dirent(dir_sd, name, ns); if (sd) diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c index 88c8bc5e8911..22ea2f5796f5 100644 --- a/fs/sysfs/symlink.c +++ b/fs/sysfs/symlink.c @@ -53,11 +53,11 @@ static int sysfs_do_create_link_sd(struct sysfs_dirent *parent_sd, sd->s_symlink.target_sd = target_sd; target_sd = NULL; /* reference is now owned by the symlink */ - sysfs_addrm_start(&acxt, parent_sd); + sysfs_addrm_start(&acxt); if (warn) - error = sysfs_add_one(&acxt, sd); + error = sysfs_add_one(&acxt, sd, parent_sd); else - error = __sysfs_add_one(&acxt, sd); + error = __sysfs_add_one(&acxt, sd, parent_sd); sysfs_addrm_finish(&acxt); if (error) diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index ee44fde199d0..4d1154411cdb 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -120,7 +120,6 @@ do { \ * Context structure to be used while adding/removing nodes. */ struct sysfs_addrm_cxt { - struct sysfs_dirent *parent_sd; struct sysfs_dirent *removed; }; @@ -154,10 +153,11 @@ extern const struct inode_operations sysfs_dir_inode_operations; struct dentry *sysfs_get_dentry(struct sysfs_dirent *sd); struct sysfs_dirent *sysfs_get_active(struct sysfs_dirent *sd); void sysfs_put_active(struct sysfs_dirent *sd); -void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt, - struct sysfs_dirent *parent_sd); -int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd); -int sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd); +void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt); +int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd, + struct sysfs_dirent *parent_sd); +int sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd, + struct sysfs_dirent *parent_sd); void sysfs_remove_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd); void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt); -- cgit v1.2.3 From 26ea12dec0c84133add937455be76d44fe253d85 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 18 Sep 2013 17:15:36 -0400 Subject: kobject: grab an extra reference on kobject->sd to allow duplicate deletes sysfs currently has a rather weird behavior regarding removals. A directory removal would delete all files directly under it but wouldn't recurse into subdirectories, which, while a bit inconsistent, seems to make sense at the first glance as each directory is supposedly associated with a kobject and each kobject can take care of the directory deletion; however, this doesn't really hold as we have groups which can be directories without a kobject associated with it and require explicit deletions. We're in the process of separating out sysfs from kboject / driver core and want a consistent behavior. A removal should delete either only the specified node or everything under it. I think it is helpful to support recursive atomic removal and later patches will implement it. Such change means that a sysfs_dirent associated with kobject may be deleted before the kobject itself is removed if one of its ancestor gets removed before it. As sysfs_remove_dir() puts the base ref, we may end up with dangling pointer on descendants. This can be solved by holding an extra reference on the sd from kobject. Acquire an extra reference on the associated sysfs_dirent on directory creation and put it after removal. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 7 ++++++- lib/kobject.c | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'fs') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 671868914b5b..105a7e2d1660 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -549,7 +549,12 @@ void sysfs_remove_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) { struct sysfs_inode_attrs *ps_iattr; - BUG_ON(sd->s_flags & SYSFS_FLAG_REMOVED); + /* + * Removal can be called multiple times on the same node. Only the + * first invocation is effective and puts the base ref. + */ + if (sd->s_flags & SYSFS_FLAG_REMOVED) + return; sysfs_unlink_sibling(sd); diff --git a/lib/kobject.c b/lib/kobject.c index 151089788c21..2fdf7fa9e9bd 100644 --- a/lib/kobject.c +++ b/lib/kobject.c @@ -76,6 +76,13 @@ static int create_dir(struct kobject *kobj) if (error) sysfs_remove_dir(kobj); } + + /* + * @kobj->sd may be deleted by an ancestor going away. Hold an + * extra reference so that it stays until @kobj is gone. + */ + sysfs_get(kobj->sd); + return error; } @@ -532,10 +539,15 @@ out: */ void kobject_del(struct kobject *kobj) { + struct sysfs_dirent *sd; + if (!kobj) return; + sd = kobj->sd; sysfs_remove_dir(kobj); + sysfs_put(sd); + kobj->state_in_sysfs = 0; kobj_kset_leave(kobj); kobject_put(kobj->parent); -- cgit v1.2.3 From bcdde7e221a8750f9b62b6d0bd31b72ea4ad9309 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 18 Sep 2013 17:15:37 -0400 Subject: sysfs: make __sysfs_remove_dir() recursive Currently, sysfs directory removal is inconsistent in that it would remove any files directly under it but wouldn't recurse into directories. Thanks to group subdirectories, this doesn't even match with kobject boundaries. sysfs is in the process of being separated out so that it can be used by multiple subsystems and we want to have a consistent behavior - either removal of a sysfs_dirent should remove every descendant entries or none instead of something inbetween. This patch implements proper recursive removal in __sysfs_remove_dir(). The function now walks its subtree in a post-order walk to remove all descendants. This is a behavior change but kobject / driver layer, which currently is the only consumer, has already been updated to handle duplicate removal attempts, so nothing should be broken after this change. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 11 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 105a7e2d1660..0cdfd8128d3e 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -789,27 +789,81 @@ void sysfs_remove_subdir(struct sysfs_dirent *sd) remove_dir(sd); } +static struct sysfs_dirent *sysfs_leftmost_descendant(struct sysfs_dirent *pos) +{ + struct sysfs_dirent *last; + + while (true) { + struct rb_node *rbn; + + last = pos; + + if (sysfs_type(pos) != SYSFS_DIR) + break; + + rbn = rb_first(&pos->s_dir.children); + if (!rbn) + break; + + pos = to_sysfs_dirent(rbn); + } + + return last; +} + +/** + * sysfs_next_descendant_post - find the next descendant for post-order walk + * @pos: the current position (%NULL to initiate traversal) + * @root: sysfs_dirent whose descendants to walk + * + * Find the next descendant to visit for post-order traversal of @root's + * descendants. @root is included in the iteration and the last node to be + * visited. + */ +static struct sysfs_dirent *sysfs_next_descendant_post(struct sysfs_dirent *pos, + struct sysfs_dirent *root) +{ + struct rb_node *rbn; + + lockdep_assert_held(&sysfs_mutex); + + /* if first iteration, visit leftmost descendant which may be root */ + if (!pos) + return sysfs_leftmost_descendant(root); + + /* if we visited @root, we're done */ + if (pos == root) + return NULL; + + /* if there's an unvisited sibling, visit its leftmost descendant */ + rbn = rb_next(&pos->s_rb); + if (rbn) + return sysfs_leftmost_descendant(to_sysfs_dirent(rbn)); + + /* no sibling left, visit parent */ + return pos->s_parent; +} static void __sysfs_remove_dir(struct sysfs_dirent *dir_sd) { struct sysfs_addrm_cxt acxt; - struct rb_node *pos; + struct sysfs_dirent *pos, *next; if (!dir_sd) return; pr_debug("sysfs %s: removing dir\n", dir_sd->s_name); sysfs_addrm_start(&acxt); - pos = rb_first(&dir_sd->s_dir.children); - while (pos) { - struct sysfs_dirent *sd = to_sysfs_dirent(pos); - pos = rb_next(pos); - if (sysfs_type(sd) != SYSFS_DIR) - sysfs_remove_one(&acxt, sd); - } - sysfs_addrm_finish(&acxt); - remove_dir(dir_sd); + next = NULL; + do { + pos = next; + next = sysfs_next_descendant_post(pos, dir_sd); + if (pos) + sysfs_remove_one(&acxt, pos); + } while (next); + + sysfs_addrm_finish(&acxt); } /** @@ -820,7 +874,6 @@ static void __sysfs_remove_dir(struct sysfs_dirent *dir_sd) * 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 kobject *kobj) { struct sysfs_dirent *sd = kobj->sd; -- cgit v1.2.3 From 250f7c3fee52b71457b4aa2cafadbd9f8b320b31 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 18 Sep 2013 17:15:38 -0400 Subject: sysfs: introduce [__]sysfs_remove() Given a sysfs_dirent, there is no reason to have multiple versions of removal functions. A function which removes the specified sysfs_dirent and its descendants is enough. This patch intorduces [__}sysfs_remove() which replaces all internal variations of removal functions. This will be the only removal function in the planned new sysfs_dirent based interface. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 47 ++++++++++++++++++++++++----------------------- fs/sysfs/group.c | 4 ++-- fs/sysfs/inode.c | 2 +- fs/sysfs/sysfs.h | 4 ++-- 4 files changed, 29 insertions(+), 28 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 0cdfd8128d3e..b518afd0d11e 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -545,7 +545,8 @@ int sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd, * LOCKING: * Determined by sysfs_addrm_start(). */ -void sysfs_remove_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) +static void sysfs_remove_one(struct sysfs_addrm_cxt *acxt, + struct sysfs_dirent *sd) { struct sysfs_inode_attrs *ps_iattr; @@ -775,20 +776,6 @@ const struct inode_operations sysfs_dir_inode_operations = { .setxattr = sysfs_setxattr, }; -static void remove_dir(struct sysfs_dirent *sd) -{ - struct sysfs_addrm_cxt acxt; - - sysfs_addrm_start(&acxt); - sysfs_remove_one(&acxt, sd); - sysfs_addrm_finish(&acxt); -} - -void sysfs_remove_subdir(struct sysfs_dirent *sd) -{ - remove_dir(sd); -} - static struct sysfs_dirent *sysfs_leftmost_descendant(struct sysfs_dirent *pos) { struct sysfs_dirent *last; @@ -844,25 +831,36 @@ static struct sysfs_dirent *sysfs_next_descendant_post(struct sysfs_dirent *pos, return pos->s_parent; } -static void __sysfs_remove_dir(struct sysfs_dirent *dir_sd) +void __sysfs_remove(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) { - struct sysfs_addrm_cxt acxt; struct sysfs_dirent *pos, *next; - if (!dir_sd) + if (!sd) return; - pr_debug("sysfs %s: removing dir\n", dir_sd->s_name); - sysfs_addrm_start(&acxt); + pr_debug("sysfs %s: removing\n", sd->s_name); next = NULL; do { pos = next; - next = sysfs_next_descendant_post(pos, dir_sd); + next = sysfs_next_descendant_post(pos, sd); if (pos) - sysfs_remove_one(&acxt, pos); + sysfs_remove_one(acxt, pos); } while (next); +} +/** + * sysfs_remove - remove a sysfs_dirent recursively + * @sd: the sysfs_dirent to remove + * + * Remove @sd along with all its subdirectories and files. + */ +void sysfs_remove(struct sysfs_dirent *sd) +{ + struct sysfs_addrm_cxt acxt; + + sysfs_addrm_start(&acxt); + __sysfs_remove(&acxt, sd); sysfs_addrm_finish(&acxt); } @@ -882,7 +880,10 @@ void sysfs_remove_dir(struct kobject *kobj) kobj->sd = NULL; spin_unlock(&sysfs_assoc_lock); - __sysfs_remove_dir(sd); + if (sd) { + WARN_ON_ONCE(sysfs_type(sd) != SYSFS_DIR); + sysfs_remove(sd); + } } int sysfs_rename(struct sysfs_dirent *sd, struct sysfs_dirent *new_parent_sd, diff --git a/fs/sysfs/group.c b/fs/sysfs/group.c index 2dae55c4f7dc..1898a10e38ce 100644 --- a/fs/sysfs/group.c +++ b/fs/sysfs/group.c @@ -111,7 +111,7 @@ static int internal_create_group(struct kobject *kobj, int update, error = create_files(sd, kobj, grp, update); if (error) { if (grp->name) - sysfs_remove_subdir(sd); + sysfs_remove(sd); } sysfs_put(sd); return error; @@ -219,7 +219,7 @@ void sysfs_remove_group(struct kobject *kobj, remove_files(sd, kobj, grp); if (grp->name) - sysfs_remove_subdir(sd); + sysfs_remove(sd); sysfs_put(sd); } diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c index 364c8873fbda..63f755ef71dd 100644 --- a/fs/sysfs/inode.c +++ b/fs/sysfs/inode.c @@ -330,7 +330,7 @@ int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const char *name, sd = sysfs_find_dirent(dir_sd, name, ns); if (sd) - sysfs_remove_one(&acxt, sd); + __sysfs_remove(&acxt, sd); sysfs_addrm_finish(&acxt); diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index 4d1154411cdb..4b1d8258b071 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -158,7 +158,8 @@ int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd, struct sysfs_dirent *parent_sd); int sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd, struct sysfs_dirent *parent_sd); -void sysfs_remove_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd); +void __sysfs_remove(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd); +void sysfs_remove(struct sysfs_dirent *sd); void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt); struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd, @@ -170,7 +171,6 @@ void release_sysfs_dirent(struct sysfs_dirent *sd); int sysfs_create_subdir(struct kobject *kobj, const char *name, struct sysfs_dirent **p_sd); -void sysfs_remove_subdir(struct sysfs_dirent *sd); int sysfs_rename(struct sysfs_dirent *sd, struct sysfs_dirent *new_parent_sd, const char *new_name, const void *new_ns); -- cgit v1.2.3 From 89e51dab7cb026193714f2858dbce203c98ecdec Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:41:55 -0400 Subject: sysfs: remove unused sysfs_buffer->pos Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 1 - 1 file changed, 1 deletion(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 1656a79ea6c0..81e3f727833f 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -44,7 +44,6 @@ struct sysfs_open_dirent { struct sysfs_buffer { size_t count; - loff_t pos; char *page; const struct sysfs_ops *ops; struct mutex mutex; -- cgit v1.2.3 From aea585ef8fa6516395022e9d2fed6ec5014128bc Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:41:56 -0400 Subject: sysfs: remove sysfs_buffer->needs_read_fill ->needs_read_fill is used to implement the following behaviors. 1. Ensure buffer filling on the first read. 2. Force buffer filling after a write. 3. Force buffer filling after a successful poll. However, #2 and #3 don't really work as sysfs doesn't reset file position. While the read buffer would be refilled, the next read would continue from the position after the last read or write, requiring an explicit seek to the start for it to be useful, which makes ->needs_read_fill superflous as read buffer is always refilled if f_pos == 0. Update sysfs_read_file() to test buffer->page for #1 instead and remove ->needs_read_fill. While this changes behavior in extreme corner cases - e.g. re-reading a sysfs file after seeking to non-zero position after a write or poll, it's highly unlikely to lead to actual breakage. This change is to prepare for using seq_file in the read path. While at it, reformat a comment in fill_write_buffer(). Signed-off-by: Tejun Heo Cc: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 81e3f727833f..e2fafc0a9b36 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -47,7 +47,6 @@ struct sysfs_buffer { char *page; const struct sysfs_ops *ops; struct mutex mutex; - int needs_read_fill; int event; struct list_head list; }; @@ -95,12 +94,10 @@ static int fill_read_buffer(struct dentry *dentry, struct sysfs_buffer *buffer) /* Try to struggle along */ count = PAGE_SIZE - 1; } - if (count >= 0) { - buffer->needs_read_fill = 0; + if (count >= 0) buffer->count = count; - } else { + else ret = count; - } return ret; } @@ -130,7 +127,11 @@ sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos) ssize_t retval = 0; mutex_lock(&buffer->mutex); - if (buffer->needs_read_fill || *ppos == 0) { + /* + * Fill on zero offset and the first read so that silly things like + * "dd bs=1 skip=N" can work on sysfs files. + */ + if (*ppos == 0 || !buffer->page) { retval = fill_read_buffer(file->f_path.dentry, buffer); if (retval) goto out; @@ -166,14 +167,15 @@ static int fill_write_buffer(struct sysfs_buffer *buffer, if (count >= PAGE_SIZE) count = PAGE_SIZE - 1; error = copy_from_user(buffer->page, buf, count); - buffer->needs_read_fill = 1; - /* if buf is assumed to contain a string, terminate it by \0, - so e.g. sscanf() can scan the string easily */ + + /* + * If buf is assumed to contain a string, terminate it by \0, so + * e.g. sscanf() can scan the string easily. + */ buffer->page[count] = 0; return error ? -EFAULT : count; } - /** * flush_write_buffer - push buffer to kobject. * @dentry: dentry to the attribute @@ -368,7 +370,6 @@ static int sysfs_open_file(struct inode *inode, struct file *file) goto err_out; mutex_init(&buffer->mutex); - buffer->needs_read_fill = 1; buffer->ops = ops; file->private_data = buffer; @@ -435,7 +436,6 @@ static unsigned int sysfs_poll(struct file *filp, poll_table *wait) return DEFAULT_POLLMASK; trigger: - buffer->needs_read_fill = 1; return DEFAULT_POLLMASK|POLLERR|POLLPRI; } -- cgit v1.2.3 From 375b611e60f7c1ce6913417ca254efe5523f1a72 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:41:57 -0400 Subject: sysfs: remove sysfs_buffer->ops Currently, sysfs_ops is fetched during sysfs_open_file() and cached in sysfs_buffer->ops to be used while the file is open. This patch removes the caching and makes each operation directly fetch sysfs_ops. This patch doesn't introduce any behavior difference and is to prepare for merging regular and bin file supports. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index e2fafc0a9b36..7dfcc3317490 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -45,12 +45,23 @@ struct sysfs_open_dirent { struct sysfs_buffer { size_t count; char *page; - const struct sysfs_ops *ops; struct mutex mutex; int event; struct list_head list; }; +/* + * Determine ktype->sysfs_ops for the given sysfs_dirent. This function + * must be called while holding an active reference. + */ +static const struct sysfs_ops *sysfs_file_ops(struct sysfs_dirent *sd) +{ + struct kobject *kobj = sd->s_parent->s_dir.kobj; + + lockdep_assert_held(sd); + return kobj->ktype ? kobj->ktype->sysfs_ops : NULL; +} + /** * fill_read_buffer - allocate and fill buffer from object. * @dentry: dentry pointer. @@ -66,7 +77,7 @@ static int fill_read_buffer(struct dentry *dentry, struct sysfs_buffer *buffer) { struct sysfs_dirent *attr_sd = dentry->d_fsdata; struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; - const struct sysfs_ops *ops = buffer->ops; + const struct sysfs_ops *ops; int ret = 0; ssize_t count; @@ -80,6 +91,8 @@ static int fill_read_buffer(struct dentry *dentry, struct sysfs_buffer *buffer) return -ENODEV; buffer->event = atomic_read(&attr_sd->s_attr.open->event); + + ops = sysfs_file_ops(attr_sd); count = ops->show(kobj, attr_sd->s_attr.attr, buffer->page); sysfs_put_active(attr_sd); @@ -191,13 +204,14 @@ static int flush_write_buffer(struct dentry *dentry, { struct sysfs_dirent *attr_sd = dentry->d_fsdata; struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; - const struct sysfs_ops *ops = buffer->ops; + const struct sysfs_ops *ops; int rc; /* need attr_sd for attr and ops, its parent for kobj */ if (!sysfs_get_active(attr_sd)) return -ENODEV; + ops = sysfs_file_ops(attr_sd); rc = ops->store(kobj, attr_sd->s_attr.attr, buffer->page, count); sysfs_put_active(attr_sd); @@ -205,7 +219,6 @@ static int flush_write_buffer(struct dentry *dentry, return rc; } - /** * sysfs_write_file - write an attribute. * @file: file pointer @@ -334,14 +347,11 @@ static int sysfs_open_file(struct inode *inode, struct file *file) return -ENODEV; /* every kobject with an attribute needs a ktype assigned */ - if (kobj->ktype && kobj->ktype->sysfs_ops) - ops = kobj->ktype->sysfs_ops; - else { - WARN(1, KERN_ERR - "missing sysfs attribute operations for kobject: %s\n", - kobject_name(kobj)); + ops = sysfs_file_ops(attr_sd); + if (WARN(!ops, KERN_ERR + "missing sysfs attribute operations for kobject: %s\n", + kobject_name(kobj))) goto err_out; - } /* File needs write support. * The inode's perms must say it's ok, @@ -370,7 +380,6 @@ static int sysfs_open_file(struct inode *inode, struct file *file) goto err_out; mutex_init(&buffer->mutex); - buffer->ops = ops; file->private_data = buffer; /* make sure we have open dirent struct */ -- cgit v1.2.3 From c75ec764cf4746a2406278ffa16f590c5db290a7 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:41:58 -0400 Subject: sysfs: add sysfs_open_file_mutex Add a separate mutex to protect sysfs_open_dirent->buffers list. This will allow performing sleepable operations while traversing sysfs_buffers, which will be renamed to sysfs_open_file. Note that currently sysfs_open_dirent->buffers list isn't being used for anything and this patch doesn't make any functional difference. It will be used to merge regular and bin file supports. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 7dfcc3317490..499cff8554fc 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -25,15 +25,17 @@ #include "sysfs.h" /* - * There's one sysfs_buffer for each open file and one - * sysfs_open_dirent for each sysfs_dirent with one or more open - * files. + * There's one sysfs_buffer for each open file and one sysfs_open_dirent + * for each sysfs_dirent with one or more open files. * - * filp->private_data points to sysfs_buffer and - * sysfs_dirent->s_attr.open points to sysfs_open_dirent. s_attr.open - * is protected by sysfs_open_dirent_lock. + * sysfs_dirent->s_attr.open points to sysfs_open_dirent. s_attr.open is + * protected by sysfs_open_dirent_lock. + * + * filp->private_data points to sysfs_buffer which is chained at + * sysfs_open_dirent->buffers, which is protected by sysfs_open_file_mutex. */ static DEFINE_SPINLOCK(sysfs_open_dirent_lock); +static DEFINE_MUTEX(sysfs_open_file_mutex); struct sysfs_open_dirent { atomic_t refcnt; @@ -272,6 +274,7 @@ static int sysfs_get_open_dirent(struct sysfs_dirent *sd, struct sysfs_open_dirent *od, *new_od = NULL; retry: + mutex_lock(&sysfs_open_file_mutex); spin_lock_irq(&sysfs_open_dirent_lock); if (!sd->s_attr.open && new_od) { @@ -286,6 +289,7 @@ static int sysfs_get_open_dirent(struct sysfs_dirent *sd, } spin_unlock_irq(&sysfs_open_dirent_lock); + mutex_unlock(&sysfs_open_file_mutex); if (od) { kfree(new_od); @@ -321,6 +325,7 @@ static void sysfs_put_open_dirent(struct sysfs_dirent *sd, struct sysfs_open_dirent *od = sd->s_attr.open; unsigned long flags; + mutex_lock(&sysfs_open_file_mutex); spin_lock_irqsave(&sysfs_open_dirent_lock, flags); list_del(&buffer->list); @@ -330,6 +335,7 @@ static void sysfs_put_open_dirent(struct sysfs_dirent *sd, od = NULL; spin_unlock_irqrestore(&sysfs_open_dirent_lock, flags); + mutex_unlock(&sysfs_open_file_mutex); kfree(od); } -- cgit v1.2.3 From 58282d8dc2e7cf2b87c8fee94d7138ed08e0a2e5 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:41:59 -0400 Subject: sysfs: rename sysfs_buffer to sysfs_open_file sysfs read path will be converted to use seq_file which will handle buffering making sysfs_buffer a misnomer. Rename sysfs_buffer to sysfs_open_file, and sysfs_open_dirent->buffers to ->files. This path is pure rename. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 127 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 63 insertions(+), 64 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 499cff8554fc..4b55bcf4422e 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -25,14 +25,14 @@ #include "sysfs.h" /* - * There's one sysfs_buffer for each open file and one sysfs_open_dirent + * There's one sysfs_open_file for each open file and one sysfs_open_dirent * for each sysfs_dirent with one or more open files. * * sysfs_dirent->s_attr.open points to sysfs_open_dirent. s_attr.open is * protected by sysfs_open_dirent_lock. * - * filp->private_data points to sysfs_buffer which is chained at - * sysfs_open_dirent->buffers, which is protected by sysfs_open_file_mutex. + * filp->private_data points to sysfs_open_file which is chained at + * sysfs_open_dirent->files, which is protected by sysfs_open_file_mutex. */ static DEFINE_SPINLOCK(sysfs_open_dirent_lock); static DEFINE_MUTEX(sysfs_open_file_mutex); @@ -41,10 +41,10 @@ struct sysfs_open_dirent { atomic_t refcnt; atomic_t event; wait_queue_head_t poll; - struct list_head buffers; /* goes through sysfs_buffer.list */ + struct list_head files; /* goes through sysfs_open_file.list */ }; -struct sysfs_buffer { +struct sysfs_open_file { size_t count; char *page; struct mutex mutex; @@ -75,7 +75,7 @@ static const struct sysfs_ops *sysfs_file_ops(struct sysfs_dirent *sd) * This is called only once, on the file's first read unless an error * is returned. */ -static int fill_read_buffer(struct dentry *dentry, struct sysfs_buffer *buffer) +static int fill_read_buffer(struct dentry *dentry, struct sysfs_open_file *of) { struct sysfs_dirent *attr_sd = dentry->d_fsdata; struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; @@ -83,19 +83,19 @@ static int fill_read_buffer(struct dentry *dentry, struct sysfs_buffer *buffer) int ret = 0; ssize_t count; - if (!buffer->page) - buffer->page = (char *) get_zeroed_page(GFP_KERNEL); - if (!buffer->page) + if (!of->page) + of->page = (char *) get_zeroed_page(GFP_KERNEL); + if (!of->page) return -ENOMEM; /* need attr_sd for attr and ops, its parent for kobj */ if (!sysfs_get_active(attr_sd)) return -ENODEV; - buffer->event = atomic_read(&attr_sd->s_attr.open->event); + of->event = atomic_read(&attr_sd->s_attr.open->event); ops = sysfs_file_ops(attr_sd); - count = ops->show(kobj, attr_sd->s_attr.attr, buffer->page); + count = ops->show(kobj, attr_sd->s_attr.attr, of->page); sysfs_put_active(attr_sd); @@ -110,7 +110,7 @@ static int fill_read_buffer(struct dentry *dentry, struct sysfs_buffer *buffer) count = PAGE_SIZE - 1; } if (count >= 0) - buffer->count = count; + of->count = count; else ret = count; return ret; @@ -138,63 +138,62 @@ static int fill_read_buffer(struct dentry *dentry, struct sysfs_buffer *buffer) static ssize_t sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos) { - struct sysfs_buffer *buffer = file->private_data; + struct sysfs_open_file *of = file->private_data; ssize_t retval = 0; - mutex_lock(&buffer->mutex); + mutex_lock(&of->mutex); /* * Fill on zero offset and the first read so that silly things like * "dd bs=1 skip=N" can work on sysfs files. */ - if (*ppos == 0 || !buffer->page) { - retval = fill_read_buffer(file->f_path.dentry, buffer); + if (*ppos == 0 || !of->page) { + retval = fill_read_buffer(file->f_path.dentry, of); if (retval) goto out; } pr_debug("%s: count = %zd, ppos = %lld, buf = %s\n", - __func__, count, *ppos, buffer->page); - retval = simple_read_from_buffer(buf, count, ppos, buffer->page, - buffer->count); + __func__, count, *ppos, of->page); + retval = simple_read_from_buffer(buf, count, ppos, of->page, of->count); out: - mutex_unlock(&buffer->mutex); + mutex_unlock(&of->mutex); return retval; } /** * fill_write_buffer - copy buffer from userspace. - * @buffer: data buffer for file. + * @of: open file struct. * @buf: data from user. * @count: number of bytes in @userbuf. * - * Allocate @buffer->page if it hasn't been already, then - * copy the user-supplied buffer into it. + * Allocate @of->page if it hasn't been already, then copy the + * user-supplied buffer into it. */ -static int fill_write_buffer(struct sysfs_buffer *buffer, +static int fill_write_buffer(struct sysfs_open_file *of, const char __user *buf, size_t count) { int error; - if (!buffer->page) - buffer->page = (char *)get_zeroed_page(GFP_KERNEL); - if (!buffer->page) + if (!of->page) + of->page = (char *)get_zeroed_page(GFP_KERNEL); + if (!of->page) return -ENOMEM; if (count >= PAGE_SIZE) count = PAGE_SIZE - 1; - error = copy_from_user(buffer->page, buf, count); + error = copy_from_user(of->page, buf, count); /* * If buf is assumed to contain a string, terminate it by \0, so * e.g. sscanf() can scan the string easily. */ - buffer->page[count] = 0; + of->page[count] = 0; return error ? -EFAULT : count; } /** * flush_write_buffer - push buffer to kobject. * @dentry: dentry to the attribute - * @buffer: data buffer for file. + * @of: open file * @count: number of bytes * * Get the correct pointers for the kobject and the attribute we're @@ -202,7 +201,7 @@ static int fill_write_buffer(struct sysfs_buffer *buffer, * passing the buffer that we acquired in fill_write_buffer(). */ static int flush_write_buffer(struct dentry *dentry, - struct sysfs_buffer *buffer, size_t count) + struct sysfs_open_file *of, size_t count) { struct sysfs_dirent *attr_sd = dentry->d_fsdata; struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; @@ -214,7 +213,7 @@ static int flush_write_buffer(struct dentry *dentry, return -ENODEV; ops = sysfs_file_ops(attr_sd); - rc = ops->store(kobj, attr_sd->s_attr.attr, buffer->page, count); + rc = ops->store(kobj, attr_sd->s_attr.attr, of->page, count); sysfs_put_active(attr_sd); @@ -240,27 +239,26 @@ static int flush_write_buffer(struct dentry *dentry, static ssize_t sysfs_write_file(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - struct sysfs_buffer *buffer = file->private_data; + struct sysfs_open_file *of = file->private_data; ssize_t len; - mutex_lock(&buffer->mutex); - len = fill_write_buffer(buffer, buf, count); + mutex_lock(&of->mutex); + len = fill_write_buffer(of, buf, count); if (len > 0) - len = flush_write_buffer(file->f_path.dentry, buffer, len); + len = flush_write_buffer(file->f_path.dentry, of, len); if (len > 0) *ppos += len; - mutex_unlock(&buffer->mutex); + mutex_unlock(&of->mutex); return len; } /** * sysfs_get_open_dirent - get or create sysfs_open_dirent * @sd: target sysfs_dirent - * @buffer: sysfs_buffer for this instance of open + * @of: sysfs_open_file for this instance of open * * If @sd->s_attr.open exists, increment its reference count; - * otherwise, create one. @buffer is chained to the buffers - * list. + * otherwise, create one. @of is chained to the files list. * * LOCKING: * Kernel thread context (may sleep). @@ -269,7 +267,7 @@ static ssize_t sysfs_write_file(struct file *file, const char __user *buf, * 0 on success, -errno on failure. */ static int sysfs_get_open_dirent(struct sysfs_dirent *sd, - struct sysfs_buffer *buffer) + struct sysfs_open_file *of) { struct sysfs_open_dirent *od, *new_od = NULL; @@ -285,7 +283,7 @@ static int sysfs_get_open_dirent(struct sysfs_dirent *sd, od = sd->s_attr.open; if (od) { atomic_inc(&od->refcnt); - list_add_tail(&buffer->list, &od->buffers); + list_add_tail(&of->list, &od->files); } spin_unlock_irq(&sysfs_open_dirent_lock); @@ -304,23 +302,23 @@ static int sysfs_get_open_dirent(struct sysfs_dirent *sd, atomic_set(&new_od->refcnt, 0); atomic_set(&new_od->event, 1); init_waitqueue_head(&new_od->poll); - INIT_LIST_HEAD(&new_od->buffers); + INIT_LIST_HEAD(&new_od->files); goto retry; } /** * sysfs_put_open_dirent - put sysfs_open_dirent * @sd: target sysfs_dirent - * @buffer: associated sysfs_buffer + * @of: associated sysfs_open_file * - * Put @sd->s_attr.open and unlink @buffer from the buffers list. - * If reference count reaches zero, disassociate and free it. + * Put @sd->s_attr.open and unlink @of from the files list. If + * reference count reaches zero, disassociate and free it. * * LOCKING: * None. */ static void sysfs_put_open_dirent(struct sysfs_dirent *sd, - struct sysfs_buffer *buffer) + struct sysfs_open_file *of) { struct sysfs_open_dirent *od = sd->s_attr.open; unsigned long flags; @@ -328,7 +326,7 @@ static void sysfs_put_open_dirent(struct sysfs_dirent *sd, mutex_lock(&sysfs_open_file_mutex); spin_lock_irqsave(&sysfs_open_dirent_lock, flags); - list_del(&buffer->list); + list_del(&of->list); if (atomic_dec_and_test(&od->refcnt)) sd->s_attr.open = NULL; else @@ -344,7 +342,7 @@ static int sysfs_open_file(struct inode *inode, struct file *file) { struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; - struct sysfs_buffer *buffer; + struct sysfs_open_file *of; const struct sysfs_ops *ops; int error = -EACCES; @@ -377,19 +375,20 @@ static int sysfs_open_file(struct inode *inode, struct file *file) goto err_out; } - /* No error? Great, allocate a buffer for the file, and store it - * it in file->private_data for easy access. + /* + * No error? Great, allocate a sysfs_open_file for the file, and + * store it it in file->private_data for easy access. */ error = -ENOMEM; - buffer = kzalloc(sizeof(struct sysfs_buffer), GFP_KERNEL); - if (!buffer) + of = kzalloc(sizeof(struct sysfs_open_file), GFP_KERNEL); + if (!of) goto err_out; - mutex_init(&buffer->mutex); - file->private_data = buffer; + mutex_init(&of->mutex); + file->private_data = of; /* make sure we have open dirent struct */ - error = sysfs_get_open_dirent(attr_sd, buffer); + error = sysfs_get_open_dirent(attr_sd, of); if (error) goto err_free; @@ -398,7 +397,7 @@ static int sysfs_open_file(struct inode *inode, struct file *file) return 0; err_free: - kfree(buffer); + kfree(of); err_out: sysfs_put_active(attr_sd); return error; @@ -407,13 +406,13 @@ static int sysfs_open_file(struct inode *inode, struct file *file) static int sysfs_release(struct inode *inode, struct file *filp) { struct sysfs_dirent *sd = filp->f_path.dentry->d_fsdata; - struct sysfs_buffer *buffer = filp->private_data; + struct sysfs_open_file *of = filp->private_data; - sysfs_put_open_dirent(sd, buffer); + sysfs_put_open_dirent(sd, of); - if (buffer->page) - free_page((unsigned long)buffer->page); - kfree(buffer); + if (of->page) + free_page((unsigned long)of->page); + kfree(of); return 0; } @@ -433,7 +432,7 @@ static int sysfs_release(struct inode *inode, struct file *filp) */ static unsigned int sysfs_poll(struct file *filp, poll_table *wait) { - struct sysfs_buffer *buffer = filp->private_data; + struct sysfs_open_file *of = filp->private_data; struct sysfs_dirent *attr_sd = filp->f_path.dentry->d_fsdata; struct sysfs_open_dirent *od = attr_sd->s_attr.open; @@ -445,7 +444,7 @@ static unsigned int sysfs_poll(struct file *filp, poll_table *wait) sysfs_put_active(attr_sd); - if (buffer->event != atomic_read(&od->event)) + if (of->event != atomic_read(&od->event)) goto trigger; return DEFAULT_POLLMASK; -- cgit v1.2.3 From bcafe4eea3e58a60e9c2c63781700a9ab1d70f93 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:42:00 -0400 Subject: sysfs: add sysfs_open_file->sd and ->file sysfs will be converted to use seq_file for read path, which will make it difficult to pass around multiple pointers directly. This patch adds sysfs_open_file->sd and ->file so that we can reach all the necessary data structures from sysfs_open_file. flush_write_buffer() is updated to drop @dentry which was used to discover the sysfs_dirent as it's now available through sysfs_open_file->sd. This patch doesn't cause any behavior difference. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 4b55bcf4422e..af6e9092a679 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -45,6 +45,8 @@ struct sysfs_open_dirent { }; struct sysfs_open_file { + struct sysfs_dirent *sd; + struct file *file; size_t count; char *page; struct mutex mutex; @@ -192,7 +194,6 @@ static int fill_write_buffer(struct sysfs_open_file *of, /** * flush_write_buffer - push buffer to kobject. - * @dentry: dentry to the attribute * @of: open file * @count: number of bytes * @@ -200,22 +201,20 @@ static int fill_write_buffer(struct sysfs_open_file *of, * dealing with, then call the store() method for the attribute, * passing the buffer that we acquired in fill_write_buffer(). */ -static int flush_write_buffer(struct dentry *dentry, - struct sysfs_open_file *of, size_t count) +static int flush_write_buffer(struct sysfs_open_file *of, size_t count) { - struct sysfs_dirent *attr_sd = dentry->d_fsdata; - struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; + struct kobject *kobj = of->sd->s_parent->s_dir.kobj; const struct sysfs_ops *ops; int rc; - /* need attr_sd for attr and ops, its parent for kobj */ - if (!sysfs_get_active(attr_sd)) + /* need @of->sd for attr and ops, its parent for kobj */ + if (!sysfs_get_active(of->sd)) return -ENODEV; - ops = sysfs_file_ops(attr_sd); - rc = ops->store(kobj, attr_sd->s_attr.attr, of->page, count); + ops = sysfs_file_ops(of->sd); + rc = ops->store(kobj, of->sd->s_attr.attr, of->page, count); - sysfs_put_active(attr_sd); + sysfs_put_active(of->sd); return rc; } @@ -245,7 +244,7 @@ static ssize_t sysfs_write_file(struct file *file, const char __user *buf, mutex_lock(&of->mutex); len = fill_write_buffer(of, buf, count); if (len > 0) - len = flush_write_buffer(file->f_path.dentry, of, len); + len = flush_write_buffer(of, len); if (len > 0) *ppos += len; mutex_unlock(&of->mutex); @@ -385,6 +384,8 @@ static int sysfs_open_file(struct inode *inode, struct file *file) goto err_out; mutex_init(&of->mutex); + of->sd = attr_sd; + of->file = file; file->private_data = of; /* make sure we have open dirent struct */ -- cgit v1.2.3 From 8ef445f0807457dd7d158e43d9e8f9568c47910d Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:42:01 -0400 Subject: sysfs: use transient write buffer There isn't much to be gained by keeping around kernel buffer while a file is open especially as the read path planned to be converted to use seq_file and won't use the buffer. This patch makes sysfs_write_file() use per-write transient buffer instead of sysfs_open_file->page. This simplifies the write path, enables removing sysfs_open_file->page once read path is updated and will help merging bin file write path which already requires the use of a transient buffer due to a locking order issue. As the function comments of flush_write_buffer() and sysfs_write_buffer() are being updated anyway, reformat them so that they're more conventional. v2: Use min_t() instead of min() in sysfs_write_file() to avoid build warning on arm. Reported by build test robot. Signed-off-by: Tejun Heo Cc: kbuild test robot Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 114 ++++++++++++++++++++++++++------------------------------ 1 file changed, 52 insertions(+), 62 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index af6e9092a679..53cc096e6a1b 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -162,92 +162,82 @@ out: } /** - * fill_write_buffer - copy buffer from userspace. - * @of: open file struct. - * @buf: data from user. - * @count: number of bytes in @userbuf. + * flush_write_buffer - push buffer to kobject + * @of: open file + * @buf: data buffer for file + * @count: number of bytes * - * Allocate @of->page if it hasn't been already, then copy the - * user-supplied buffer into it. + * Get the correct pointers for the kobject and the attribute we're dealing + * with, then call the store() method for it with @buf. */ -static int fill_write_buffer(struct sysfs_open_file *of, - const char __user *buf, size_t count) -{ - int error; - - if (!of->page) - of->page = (char *)get_zeroed_page(GFP_KERNEL); - if (!of->page) - return -ENOMEM; - - if (count >= PAGE_SIZE) - count = PAGE_SIZE - 1; - error = copy_from_user(of->page, buf, count); - - /* - * If buf is assumed to contain a string, terminate it by \0, so - * e.g. sscanf() can scan the string easily. - */ - of->page[count] = 0; - return error ? -EFAULT : count; -} - -/** - * flush_write_buffer - push buffer to kobject. - * @of: open file - * @count: number of bytes - * - * Get the correct pointers for the kobject and the attribute we're - * dealing with, then call the store() method for the attribute, - * passing the buffer that we acquired in fill_write_buffer(). - */ -static int flush_write_buffer(struct sysfs_open_file *of, size_t count) +static int flush_write_buffer(struct sysfs_open_file *of, char *buf, + size_t count) { struct kobject *kobj = of->sd->s_parent->s_dir.kobj; const struct sysfs_ops *ops; - int rc; + int rc = 0; - /* need @of->sd for attr and ops, its parent for kobj */ - if (!sysfs_get_active(of->sd)) + /* + * Need @of->sd for attr and ops, its parent for kobj. @of->mutex + * nests outside active ref and is just to ensure that the ops + * aren't called concurrently for the same open file. + */ + mutex_lock(&of->mutex); + if (!sysfs_get_active(of->sd)) { + mutex_unlock(&of->mutex); return -ENODEV; + } ops = sysfs_file_ops(of->sd); - rc = ops->store(kobj, of->sd->s_attr.attr, of->page, count); + rc = ops->store(kobj, of->sd->s_attr.attr, buf, count); sysfs_put_active(of->sd); + mutex_unlock(&of->mutex); return rc; } /** - * sysfs_write_file - write an attribute. - * @file: file pointer - * @buf: data to write - * @count: number of bytes - * @ppos: starting offset + * sysfs_write_file - write an attribute + * @file: file pointer + * @user_buf: data to write + * @count: number of bytes + * @ppos: starting offset * - * Similar to sysfs_read_file(), though working in the opposite direction. - * We allocate and fill the data from the user in fill_write_buffer(), - * then push it to the kobject in flush_write_buffer(). - * There is no easy way for us to know if userspace is only doing a partial - * write, so we don't support them. We expect the entire buffer to come - * on the first write. - * Hint: if you're writing a value, first read the file, modify only the - * the value you're changing, then write entire buffer back. + * Copy data in from userland and pass it to the matching + * sysfs_ops->store() by invoking flush_write_buffer(). + * + * There is no easy way for us to know if userspace is only doing a partial + * write, so we don't support them. We expect the entire buffer to come on + * the first write. Hint: if you're writing a value, first read the file, + * modify only the the value you're changing, then write entire buffer + * back. */ -static ssize_t sysfs_write_file(struct file *file, const char __user *buf, +static ssize_t sysfs_write_file(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct sysfs_open_file *of = file->private_data; - ssize_t len; + ssize_t len = min_t(size_t, count, PAGE_SIZE - 1); + char *buf; - mutex_lock(&of->mutex); - len = fill_write_buffer(of, buf, count); - if (len > 0) - len = flush_write_buffer(of, len); + if (!len) + return 0; + + buf = kmalloc(len + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, user_buf, len)) { + len = -EFAULT; + goto out_free; + } + buf[len] = '\0'; /* guarantee string termination */ + + len = flush_write_buffer(of, buf, len); if (len > 0) *ppos += len; - mutex_unlock(&of->mutex); +out_free: + kfree(buf); return len; } -- cgit v1.2.3 From 13c589d5b0ac654d9da7e490a2dd548e6b86b4a5 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:42:02 -0400 Subject: sysfs: use seq_file when reading regular files sysfs read path implements its own buffering scheme between userland and kernel callbacks, which essentially is a degenerate duplicate of seq_file. This patch replaces the custom read buffering implementation in sysfs with seq_file. While the amount of code reduction is small, this reduces low level hairiness and enables future development of a new versatile API based on seq_file so that sysfs features can be shared with other subsystems. As write path was already converted to not use sysfs_open_file->page, this patch makes ->page and ->count unused and removes them. Userland behavior remains the same except for some extreme corner cases - e.g. sysfs will now regenerate the content each time a file is read after a non-contiguous seek whereas the original code would keep using the same content. While this is a userland visible behavior change, it is extremely unlikely to be noticeable and brings sysfs behavior closer to that of procfs. Signed-off-by: Tejun Heo Cc: Kay Sievers Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 164 +++++++++++++++++++++++++------------------------------- 1 file changed, 73 insertions(+), 91 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 53cc096e6a1b..4921bda3a37a 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "sysfs.h" @@ -31,7 +32,8 @@ * sysfs_dirent->s_attr.open points to sysfs_open_dirent. s_attr.open is * protected by sysfs_open_dirent_lock. * - * filp->private_data points to sysfs_open_file which is chained at + * filp->private_data points to seq_file whose ->private points to + * sysfs_open_file. sysfs_open_files are chained at * sysfs_open_dirent->files, which is protected by sysfs_open_file_mutex. */ static DEFINE_SPINLOCK(sysfs_open_dirent_lock); @@ -47,13 +49,16 @@ struct sysfs_open_dirent { struct sysfs_open_file { struct sysfs_dirent *sd; struct file *file; - size_t count; - char *page; struct mutex mutex; int event; struct list_head list; }; +static struct sysfs_open_file *sysfs_of(struct file *file) +{ + return ((struct seq_file *)file->private_data)->private; +} + /* * Determine ktype->sysfs_ops for the given sysfs_dirent. This function * must be called while holding an active reference. @@ -66,40 +71,54 @@ static const struct sysfs_ops *sysfs_file_ops(struct sysfs_dirent *sd) return kobj->ktype ? kobj->ktype->sysfs_ops : NULL; } -/** - * fill_read_buffer - allocate and fill buffer from object. - * @dentry: dentry pointer. - * @buffer: data buffer for file. - * - * Allocate @buffer->page, if it hasn't been already, then call the - * kobject's show() method to fill the buffer with this attribute's - * data. - * This is called only once, on the file's first read unless an error - * is returned. +/* + * Reads on sysfs are handled through seq_file, which takes care of hairy + * details like buffering and seeking. The following function pipes + * sysfs_ops->show() result through seq_file. */ -static int fill_read_buffer(struct dentry *dentry, struct sysfs_open_file *of) +static int sysfs_seq_show(struct seq_file *sf, void *v) { - struct sysfs_dirent *attr_sd = dentry->d_fsdata; - struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; + struct sysfs_open_file *of = sf->private; + struct kobject *kobj = of->sd->s_parent->s_dir.kobj; const struct sysfs_ops *ops; - int ret = 0; + char *buf; ssize_t count; - if (!of->page) - of->page = (char *) get_zeroed_page(GFP_KERNEL); - if (!of->page) - return -ENOMEM; + /* acquire buffer and ensure that it's >= PAGE_SIZE */ + count = seq_get_buf(sf, &buf); + if (count < PAGE_SIZE) { + seq_commit(sf, -1); + return 0; + } - /* need attr_sd for attr and ops, its parent for kobj */ - if (!sysfs_get_active(attr_sd)) + /* + * Need @of->sd for attr and ops, its parent for kobj. @of->mutex + * nests outside active ref and is just to ensure that the ops + * aren't called concurrently for the same open file. + */ + mutex_lock(&of->mutex); + if (!sysfs_get_active(of->sd)) { + mutex_unlock(&of->mutex); return -ENODEV; + } - of->event = atomic_read(&attr_sd->s_attr.open->event); + of->event = atomic_read(&of->sd->s_attr.open->event); - ops = sysfs_file_ops(attr_sd); - count = ops->show(kobj, attr_sd->s_attr.attr, of->page); + /* + * Lookup @ops and invoke show(). Control may reach here via seq + * file lseek even if @ops->show() isn't implemented. + */ + ops = sysfs_file_ops(of->sd); + if (ops->show) + count = ops->show(kobj, of->sd->s_attr.attr, buf); + else + count = 0; - sysfs_put_active(attr_sd); + sysfs_put_active(of->sd); + mutex_unlock(&of->mutex); + + if (count < 0) + return count; /* * The code works fine with PAGE_SIZE return but it's likely to @@ -111,54 +130,8 @@ static int fill_read_buffer(struct dentry *dentry, struct sysfs_open_file *of) /* Try to struggle along */ count = PAGE_SIZE - 1; } - if (count >= 0) - of->count = count; - else - ret = count; - return ret; -} - -/** - * 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. - * - * We call fill_read_buffer() to allocate and fill the buffer from the - * object's show() method exactly once (if the read is happening from - * the beginning of the file). That should fill the entire buffer with - * all the data the object has to offer for that attribute. - * We then call flush_read_buffer() to copy the buffer to userspace - * in the increments specified. - */ - -static ssize_t -sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos) -{ - struct sysfs_open_file *of = file->private_data; - ssize_t retval = 0; - - mutex_lock(&of->mutex); - /* - * Fill on zero offset and the first read so that silly things like - * "dd bs=1 skip=N" can work on sysfs files. - */ - if (*ppos == 0 || !of->page) { - retval = fill_read_buffer(file->f_path.dentry, of); - if (retval) - goto out; - } - pr_debug("%s: count = %zd, ppos = %lld, buf = %s\n", - __func__, count, *ppos, of->page); - retval = simple_read_from_buffer(buf, count, ppos, of->page, of->count); -out: - mutex_unlock(&of->mutex); - return retval; + seq_commit(sf, count); + return 0; } /** @@ -216,7 +189,7 @@ static int flush_write_buffer(struct sysfs_open_file *of, char *buf, static ssize_t sysfs_write_file(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { - struct sysfs_open_file *of = file->private_data; + struct sysfs_open_file *of = sysfs_of(file); ssize_t len = min_t(size_t, count, PAGE_SIZE - 1); char *buf; @@ -364,10 +337,7 @@ static int sysfs_open_file(struct inode *inode, struct file *file) goto err_out; } - /* - * No error? Great, allocate a sysfs_open_file for the file, and - * store it it in file->private_data for easy access. - */ + /* allocate a sysfs_open_file for the file */ error = -ENOMEM; of = kzalloc(sizeof(struct sysfs_open_file), GFP_KERNEL); if (!of) @@ -376,20 +346,34 @@ static int sysfs_open_file(struct inode *inode, struct file *file) mutex_init(&of->mutex); of->sd = attr_sd; of->file = file; - file->private_data = of; + + /* + * Always instantiate seq_file even if read access is not + * implemented or requested. This unifies private data access and + * most files are readable anyway. + */ + error = single_open(file, sysfs_seq_show, of); + if (error) + goto err_free; + + /* seq_file clears PWRITE unconditionally, restore it if WRITE */ + if (file->f_mode & FMODE_WRITE) + file->f_mode |= FMODE_PWRITE; /* make sure we have open dirent struct */ error = sysfs_get_open_dirent(attr_sd, of); if (error) - goto err_free; + goto err_close; /* open succeeded, put active references */ sysfs_put_active(attr_sd); return 0; - err_free: +err_close: + single_release(inode, file); +err_free: kfree(of); - err_out: +err_out: sysfs_put_active(attr_sd); return error; } @@ -397,12 +381,10 @@ static int sysfs_open_file(struct inode *inode, struct file *file) static int sysfs_release(struct inode *inode, struct file *filp) { struct sysfs_dirent *sd = filp->f_path.dentry->d_fsdata; - struct sysfs_open_file *of = filp->private_data; + struct sysfs_open_file *of = sysfs_of(filp); sysfs_put_open_dirent(sd, of); - - if (of->page) - free_page((unsigned long)of->page); + single_release(inode, filp); kfree(of); return 0; @@ -423,7 +405,7 @@ static int sysfs_release(struct inode *inode, struct file *filp) */ static unsigned int sysfs_poll(struct file *filp, poll_table *wait) { - struct sysfs_open_file *of = filp->private_data; + struct sysfs_open_file *of = sysfs_of(filp); struct sysfs_dirent *attr_sd = filp->f_path.dentry->d_fsdata; struct sysfs_open_dirent *od = attr_sd->s_attr.open; @@ -481,9 +463,9 @@ void sysfs_notify(struct kobject *k, const char *dir, const char *attr) EXPORT_SYMBOL_GPL(sysfs_notify); const struct file_operations sysfs_file_operations = { - .read = sysfs_read_file, + .read = seq_read, .write = sysfs_write_file, - .llseek = generic_file_llseek, + .llseek = seq_lseek, .open = sysfs_open_file, .release = sysfs_release, .poll = sysfs_poll, -- cgit v1.2.3 From 91270162bf8a86bee756da7e931591d9953cb5a8 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:42:03 -0400 Subject: sysfs: skip bin_buffer->buffer while reading After b31ca3f5dfc ("sysfs: fix deadlock"), bin read() first writes data to bb->buffer and bounces it to a transient kernel buffer which is then copied out to userland. The double bouncing doesn't add anything. Let's just use the transient buffer directly. While at it, rename @temp to @buf for clarity. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/bin.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/bin.c b/fs/sysfs/bin.c index d49e6ca3345b..d2142c0648eb 100644 --- a/fs/sysfs/bin.c +++ b/fs/sysfs/bin.c @@ -72,7 +72,7 @@ read(struct file *file, char __user *userbuf, size_t bytes, loff_t *off) int size = file_inode(file)->i_size; loff_t offs = *off; int count = min_t(size_t, bytes, PAGE_SIZE); - char *temp; + char *buf; if (!bytes) return 0; @@ -84,23 +84,18 @@ read(struct file *file, char __user *userbuf, size_t bytes, loff_t *off) count = size - offs; } - temp = kmalloc(count, GFP_KERNEL); - if (!temp) + buf = kmalloc(count, GFP_KERNEL); + if (!buf) return -ENOMEM; mutex_lock(&bb->mutex); + count = fill_read(file, buf, offs, count); + mutex_unlock(&bb->mutex); - count = fill_read(file, bb->buffer, offs, count); - if (count < 0) { - mutex_unlock(&bb->mutex); + if (count < 0) goto out_free; - } - - memcpy(temp, bb->buffer, count); - mutex_unlock(&bb->mutex); - - if (copy_to_user(userbuf, temp, count)) { + if (copy_to_user(userbuf, buf, count)) { count = -EFAULT; goto out_free; } @@ -110,7 +105,7 @@ read(struct file *file, char __user *userbuf, size_t bytes, loff_t *off) *off = offs + count; out_free: - kfree(temp); + kfree(buf); return count; } -- cgit v1.2.3 From 3ff65d3cb09ee642363bcaf12c6c38670263d93a Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:42:04 -0400 Subject: sysfs: collapse fs/sysfs/bin.c::fill_read() into read() read() is simple enough and fill_read() being in a separate function doesn't add anything. Let's collapse it into read(). This will make merging bin file handling with regular file. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/bin.c | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/bin.c b/fs/sysfs/bin.c index d2142c0648eb..60a4e787dc4f 100644 --- a/fs/sysfs/bin.c +++ b/fs/sysfs/bin.c @@ -44,30 +44,12 @@ struct bin_buffer { struct hlist_node list; }; -static int -fill_read(struct file *file, char *buffer, loff_t off, size_t count) +static ssize_t +read(struct file *file, char __user *userbuf, size_t bytes, loff_t *off) { struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; struct bin_attribute *attr = attr_sd->s_bin_attr.bin_attr; struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; - int rc; - - /* need attr_sd for attr, its parent for kobj */ - if (!sysfs_get_active(attr_sd)) - return -ENODEV; - - rc = -EIO; - if (attr->read) - rc = attr->read(file, kobj, attr, buffer, off, count); - - sysfs_put_active(attr_sd); - - return rc; -} - -static ssize_t -read(struct file *file, char __user *userbuf, size_t bytes, loff_t *off) -{ struct bin_buffer *bb = file->private_data; int size = file_inode(file)->i_size; loff_t offs = *off; @@ -88,8 +70,20 @@ read(struct file *file, char __user *userbuf, size_t bytes, loff_t *off) if (!buf) return -ENOMEM; + /* need attr_sd for attr, its parent for kobj */ mutex_lock(&bb->mutex); - count = fill_read(file, buf, offs, count); + if (!sysfs_get_active(attr_sd)) { + count = -ENODEV; + mutex_unlock(&bb->mutex); + goto out_free; + } + + if (attr->read) + count = attr->read(file, kobj, attr, buf, offs, count); + else + count = -EIO; + + sysfs_put_active(attr_sd); mutex_unlock(&bb->mutex); if (count < 0) -- cgit v1.2.3 From f9b9a6217cf10fd5d3002627cc13c4789a777213 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:42:05 -0400 Subject: sysfs: prepare path write for unified regular / bin file handling sysfs bin file handling will be merged into the regular file support. This patch prepares the write path. bin file write is almost identical to regular file write except that the write length is capped by the inode size and @off is passed to the write method. This patch adds bin file handling to sysfs_write_file() so that it can handle both regular and bin files. A new file_operations struct sysfs_bin_operations is added, which currently only hosts sysfs_write_file() and generic_file_llseek(). This isn't used yet but will eventually replace fs/sysfs/bin.c. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 40 ++++++++++++++++++++++++++++++++++------ fs/sysfs/sysfs.h | 1 + 2 files changed, 35 insertions(+), 6 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 4921bda3a37a..b36473f21824 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -54,6 +54,11 @@ struct sysfs_open_file { struct list_head list; }; +static bool sysfs_is_bin(struct sysfs_dirent *sd) +{ + return sysfs_type(sd) == SYSFS_KOBJ_BIN_ATTR; +} + static struct sysfs_open_file *sysfs_of(struct file *file) { return ((struct seq_file *)file->private_data)->private; @@ -138,16 +143,16 @@ static int sysfs_seq_show(struct seq_file *sf, void *v) * flush_write_buffer - push buffer to kobject * @of: open file * @buf: data buffer for file + * @off: file offset to write to * @count: number of bytes * * Get the correct pointers for the kobject and the attribute we're dealing * with, then call the store() method for it with @buf. */ -static int flush_write_buffer(struct sysfs_open_file *of, char *buf, +static int flush_write_buffer(struct sysfs_open_file *of, char *buf, loff_t off, size_t count) { struct kobject *kobj = of->sd->s_parent->s_dir.kobj; - const struct sysfs_ops *ops; int rc = 0; /* @@ -161,8 +166,18 @@ static int flush_write_buffer(struct sysfs_open_file *of, char *buf, return -ENODEV; } - ops = sysfs_file_ops(of->sd); - rc = ops->store(kobj, of->sd->s_attr.attr, buf, count); + if (sysfs_is_bin(of->sd)) { + struct bin_attribute *battr = of->sd->s_bin_attr.bin_attr; + + rc = -EIO; + if (battr->write) + rc = battr->write(of->file, kobj, battr, buf, off, + count); + } else { + const struct sysfs_ops *ops = sysfs_file_ops(of->sd); + + rc = ops->store(kobj, of->sd->s_attr.attr, buf, count); + } sysfs_put_active(of->sd); mutex_unlock(&of->mutex); @@ -190,9 +205,17 @@ static ssize_t sysfs_write_file(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct sysfs_open_file *of = sysfs_of(file); - ssize_t len = min_t(size_t, count, PAGE_SIZE - 1); + ssize_t len = min_t(size_t, count, PAGE_SIZE); char *buf; + if (sysfs_is_bin(of->sd)) { + loff_t size = file_inode(file)->i_size; + + if (size <= *ppos) + return 0; + len = min_t(ssize_t, len, size - *ppos); + } + if (!len) return 0; @@ -206,7 +229,7 @@ static ssize_t sysfs_write_file(struct file *file, const char __user *user_buf, } buf[len] = '\0'; /* guarantee string termination */ - len = flush_write_buffer(of, buf, len); + len = flush_write_buffer(of, buf, *ppos, len); if (len > 0) *ppos += len; out_free: @@ -471,6 +494,11 @@ const struct file_operations sysfs_file_operations = { .poll = sysfs_poll, }; +const struct file_operations sysfs_bin_operations = { + .write = sysfs_write_file, + .llseek = generic_file_llseek, +}; + int sysfs_add_file_mode_ns(struct sysfs_dirent *dir_sd, const struct attribute *attr, int type, umode_t amode, const void *ns) diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index 4b1d8258b071..f103bac53df7 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -212,6 +212,7 @@ int sysfs_inode_init(void); * file.c */ extern const struct file_operations sysfs_file_operations; +extern const struct file_operations sysfs_bin_operations; int sysfs_add_file(struct sysfs_dirent *dir_sd, const struct attribute *attr, int type); -- cgit v1.2.3 From 2f0c6b7593a590eef7fa35344da57380fcee7581 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:42:06 -0400 Subject: sysfs: add sysfs_bin_read() sysfs bin file handling will be merged into the regular file support. This patch prepares the read path. Copy fs/sysfs/bin.c::read() to fs/sysfs/file.c and make it use sysfs_open_file instead of bin_buffer. The function is identical copy except for the use of sysfs_open_file. The new function is added to sysfs_bin_operations. This isn't used yet but will eventually replace fs/sysfs/bin.c. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index b36473f21824..9ba492a3d932 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -139,6 +139,70 @@ static int sysfs_seq_show(struct seq_file *sf, void *v) return 0; } +/* + * Read method for bin files. As reading a bin file can have side-effects, + * the exact offset and bytes specified in read(2) call should be passed to + * the read callback making it difficult to use seq_file. Implement + * simplistic custom buffering for bin files. + */ +static ssize_t sysfs_bin_read(struct file *file, char __user *userbuf, + size_t bytes, loff_t *off) +{ + struct sysfs_open_file *of = sysfs_of(file); + struct bin_attribute *battr = of->sd->s_bin_attr.bin_attr; + struct kobject *kobj = of->sd->s_parent->s_dir.kobj; + int size = file_inode(file)->i_size; + int count = min_t(size_t, bytes, PAGE_SIZE); + loff_t offs = *off; + char *buf; + + if (!bytes) + return 0; + + if (size) { + if (offs > size) + return 0; + if (offs + count > size) + count = size - offs; + } + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* need of->sd for battr, its parent for kobj */ + mutex_lock(&of->mutex); + if (!sysfs_get_active(of->sd)) { + count = -ENODEV; + mutex_unlock(&of->mutex); + goto out_free; + } + + if (battr->read) + count = battr->read(file, kobj, battr, buf, offs, count); + else + count = -EIO; + + sysfs_put_active(of->sd); + mutex_unlock(&of->mutex); + + if (count < 0) + goto out_free; + + if (copy_to_user(userbuf, buf, count)) { + count = -EFAULT; + goto out_free; + } + + pr_debug("offs = %lld, *off = %lld, count = %d\n", offs, *off, count); + + *off = offs + count; + + out_free: + kfree(buf); + return count; +} + /** * flush_write_buffer - push buffer to kobject * @of: open file @@ -495,6 +559,7 @@ const struct file_operations sysfs_file_operations = { }; const struct file_operations sysfs_bin_operations = { + .read = sysfs_bin_read, .write = sysfs_write_file, .llseek = generic_file_llseek, }; -- cgit v1.2.3 From 73d9714627adced2942e8d53ce0e73d9699a996c Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:42:07 -0400 Subject: sysfs: copy bin mmap support from fs/sysfs/bin.c to fs/sysfs/file.c sysfs bin file handling will be merged into the regular file support. This patch copies mmap support from bin so that fs/sysfs/file.c can handle mmapping bin files. The code is copied mostly verbatim with the following updates. * ->mmapped and ->vm_ops are added to sysfs_open_file and bin_buffer references are replaced with sysfs_open_file ones. * Symbols are prefixed with sysfs_. * sysfs_unmap_bin_file() grabs sysfs_open_dirent and traverses ->files. Invocation of this function is added to sysfs_addrm_finish(). * sysfs_bin_mmap() is added to sysfs_bin_operations. This is a preparation and the new mmap path isn't used yet. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 1 + fs/sysfs/file.c | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- fs/sysfs/sysfs.h | 2 + 3 files changed, 249 insertions(+), 1 deletion(-) (limited to 'fs') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index b518afd0d11e..c4040ddb8308 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -595,6 +595,7 @@ void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt) acxt->removed = sd->u.removed_list; sysfs_deactivate(sd); + sysfs_unmap_bin_file(sd); unmap_bin_file(sd); sysfs_put(sd); } diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 9ba492a3d932..02797a134cf8 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "sysfs.h" @@ -52,6 +53,9 @@ struct sysfs_open_file { struct mutex mutex; int event; struct list_head list; + + bool mmapped; + const struct vm_operations_struct *vm_ops; }; static bool sysfs_is_bin(struct sysfs_dirent *sd) @@ -301,6 +305,218 @@ out_free: return len; } +static void sysfs_bin_vma_open(struct vm_area_struct *vma) +{ + struct file *file = vma->vm_file; + struct sysfs_open_file *of = sysfs_of(file); + + if (!of->vm_ops) + return; + + if (!sysfs_get_active(of->sd)) + return; + + if (of->vm_ops->open) + of->vm_ops->open(vma); + + sysfs_put_active(of->sd); +} + +static int sysfs_bin_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct file *file = vma->vm_file; + struct sysfs_open_file *of = sysfs_of(file); + int ret; + + if (!of->vm_ops) + return VM_FAULT_SIGBUS; + + if (!sysfs_get_active(of->sd)) + return VM_FAULT_SIGBUS; + + ret = VM_FAULT_SIGBUS; + if (of->vm_ops->fault) + ret = of->vm_ops->fault(vma, vmf); + + sysfs_put_active(of->sd); + return ret; +} + +static int sysfs_bin_page_mkwrite(struct vm_area_struct *vma, + struct vm_fault *vmf) +{ + struct file *file = vma->vm_file; + struct sysfs_open_file *of = sysfs_of(file); + int ret; + + if (!of->vm_ops) + return VM_FAULT_SIGBUS; + + if (!sysfs_get_active(of->sd)) + return VM_FAULT_SIGBUS; + + ret = 0; + if (of->vm_ops->page_mkwrite) + ret = of->vm_ops->page_mkwrite(vma, vmf); + else + file_update_time(file); + + sysfs_put_active(of->sd); + return ret; +} + +static int sysfs_bin_access(struct vm_area_struct *vma, unsigned long addr, + void *buf, int len, int write) +{ + struct file *file = vma->vm_file; + struct sysfs_open_file *of = sysfs_of(file); + int ret; + + if (!of->vm_ops) + return -EINVAL; + + if (!sysfs_get_active(of->sd)) + return -EINVAL; + + ret = -EINVAL; + if (of->vm_ops->access) + ret = of->vm_ops->access(vma, addr, buf, len, write); + + sysfs_put_active(of->sd); + return ret; +} + +#ifdef CONFIG_NUMA +static int sysfs_bin_set_policy(struct vm_area_struct *vma, + struct mempolicy *new) +{ + struct file *file = vma->vm_file; + struct sysfs_open_file *of = sysfs_of(file); + int ret; + + if (!of->vm_ops) + return 0; + + if (!sysfs_get_active(of->sd)) + return -EINVAL; + + ret = 0; + if (of->vm_ops->set_policy) + ret = of->vm_ops->set_policy(vma, new); + + sysfs_put_active(of->sd); + return ret; +} + +static struct mempolicy *sysfs_bin_get_policy(struct vm_area_struct *vma, + unsigned long addr) +{ + struct file *file = vma->vm_file; + struct sysfs_open_file *of = sysfs_of(file); + struct mempolicy *pol; + + if (!of->vm_ops) + return vma->vm_policy; + + if (!sysfs_get_active(of->sd)) + return vma->vm_policy; + + pol = vma->vm_policy; + if (of->vm_ops->get_policy) + pol = of->vm_ops->get_policy(vma, addr); + + sysfs_put_active(of->sd); + return pol; +} + +static int sysfs_bin_migrate(struct vm_area_struct *vma, const nodemask_t *from, + const nodemask_t *to, unsigned long flags) +{ + struct file *file = vma->vm_file; + struct sysfs_open_file *of = sysfs_of(file); + int ret; + + if (!of->vm_ops) + return 0; + + if (!sysfs_get_active(of->sd)) + return 0; + + ret = 0; + if (of->vm_ops->migrate) + ret = of->vm_ops->migrate(vma, from, to, flags); + + sysfs_put_active(of->sd); + return ret; +} +#endif + +static const struct vm_operations_struct sysfs_bin_vm_ops = { + .open = sysfs_bin_vma_open, + .fault = sysfs_bin_fault, + .page_mkwrite = sysfs_bin_page_mkwrite, + .access = sysfs_bin_access, +#ifdef CONFIG_NUMA + .set_policy = sysfs_bin_set_policy, + .get_policy = sysfs_bin_get_policy, + .migrate = sysfs_bin_migrate, +#endif +}; + +static int sysfs_bin_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct sysfs_open_file *of = sysfs_of(file); + struct bin_attribute *battr = of->sd->s_bin_attr.bin_attr; + struct kobject *kobj = of->sd->s_parent->s_dir.kobj; + int rc; + + mutex_lock(&of->mutex); + + /* need of->sd for battr, its parent for kobj */ + rc = -ENODEV; + if (!sysfs_get_active(of->sd)) + goto out_unlock; + + rc = -EINVAL; + if (!battr->mmap) + goto out_put; + + rc = battr->mmap(file, kobj, battr, vma); + if (rc) + goto out_put; + + /* + * PowerPC's pci_mmap of legacy_mem uses shmem_zero_setup() + * to satisfy versions of X which crash if the mmap fails: that + * substitutes a new vm_file, and we don't then want bin_vm_ops. + */ + if (vma->vm_file != file) + goto out_put; + + rc = -EINVAL; + if (of->mmapped && of->vm_ops != vma->vm_ops) + goto out_put; + + /* + * It is not possible to successfully wrap close. + * So error if someone is trying to use close. + */ + rc = -EINVAL; + if (vma->vm_ops && vma->vm_ops->close) + goto out_put; + + rc = 0; + of->mmapped = 1; + of->vm_ops = vma->vm_ops; + vma->vm_ops = &sysfs_bin_vm_ops; +out_put: + sysfs_put_active(of->sd); +out_unlock: + mutex_unlock(&of->mutex); + + return rc; +} + /** * sysfs_get_open_dirent - get or create sysfs_open_dirent * @sd: target sysfs_dirent @@ -375,7 +591,9 @@ static void sysfs_put_open_dirent(struct sysfs_dirent *sd, mutex_lock(&sysfs_open_file_mutex); spin_lock_irqsave(&sysfs_open_dirent_lock, flags); - list_del(&of->list); + if (of) + list_del(&of->list); + if (atomic_dec_and_test(&od->refcnt)) sd->s_attr.open = NULL; else @@ -477,6 +695,32 @@ static int sysfs_release(struct inode *inode, struct file *filp) return 0; } +void sysfs_unmap_bin_file(struct sysfs_dirent *sd) +{ + struct sysfs_open_dirent *od; + struct sysfs_open_file *of; + + if (!sysfs_is_bin(sd)) + return; + + spin_lock_irq(&sysfs_open_dirent_lock); + od = sd->s_attr.open; + if (od) + atomic_inc(&od->refcnt); + spin_unlock_irq(&sysfs_open_dirent_lock); + if (!od) + return; + + mutex_lock(&sysfs_open_file_mutex); + list_for_each_entry(of, &od->files, list) { + struct inode *inode = file_inode(of->file); + unmap_mapping_range(inode->i_mapping, 0, 0, 1); + } + mutex_unlock(&sysfs_open_file_mutex); + + sysfs_put_open_dirent(sd, NULL); +} + /* Sysfs attribute files are pollable. The idea is that you read * the content and then you use 'poll' or 'select' to wait for * the content to change. When the content changes (assuming the @@ -562,6 +806,7 @@ const struct file_operations sysfs_bin_operations = { .read = sysfs_bin_read, .write = sysfs_write_file, .llseek = generic_file_llseek, + .mmap = sysfs_bin_mmap, }; int sysfs_add_file_mode_ns(struct sysfs_dirent *dir_sd, diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index f103bac53df7..c960d6272cf6 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -220,6 +220,8 @@ int sysfs_add_file(struct sysfs_dirent *dir_sd, int sysfs_add_file_mode_ns(struct sysfs_dirent *dir_sd, const struct attribute *attr, int type, umode_t amode, const void *ns); +void sysfs_unmap_bin_file(struct sysfs_dirent *sd); + /* * bin.c */ -- cgit v1.2.3 From 49fe604781cbb03eb6ff12a7bc4ad8eef8b830c4 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:42:08 -0400 Subject: sysfs: prepare open path for unified regular / bin file handling sysfs bin file handling will be merged into the regular file support. This patch prepares the open path. This patch updates sysfs_open_file() such that it can handle both regular and bin files. This is a preparation and the new bin file path isn't used yet. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 58 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 25 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 02797a134cf8..417d005955d9 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -610,38 +610,40 @@ static int sysfs_open_file(struct inode *inode, struct file *file) struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; struct sysfs_open_file *of; - const struct sysfs_ops *ops; + bool has_read, has_write; int error = -EACCES; /* need attr_sd for attr and ops, its parent for kobj */ if (!sysfs_get_active(attr_sd)) return -ENODEV; - /* every kobject with an attribute needs a ktype assigned */ - ops = sysfs_file_ops(attr_sd); - if (WARN(!ops, KERN_ERR - "missing sysfs attribute operations for kobject: %s\n", - kobject_name(kobj))) - goto err_out; + if (sysfs_is_bin(attr_sd)) { + struct bin_attribute *battr = attr_sd->s_bin_attr.bin_attr; - /* File needs write support. - * The inode's perms must say it's ok, - * and we must have a store method. - */ - if (file->f_mode & FMODE_WRITE) { - if (!(inode->i_mode & S_IWUGO) || !ops->store) - goto err_out; - } + has_read = battr->read || battr->mmap; + has_write = battr->write || battr->mmap; + } else { + const struct sysfs_ops *ops = sysfs_file_ops(attr_sd); - /* File needs read support. - * The inode's perms must say it's ok, and we there - * must be a show method for it. - */ - if (file->f_mode & FMODE_READ) { - if (!(inode->i_mode & S_IRUGO) || !ops->show) + /* every kobject with an attribute needs a ktype assigned */ + if (WARN(!ops, KERN_ERR + "missing sysfs attribute operations for kobject: %s\n", + kobject_name(kobj))) goto err_out; + + has_read = ops->show; + has_write = ops->store; } + /* check perms and supported operations */ + if ((file->f_mode & FMODE_WRITE) && + (!(inode->i_mode & S_IWUGO) || !has_write)) + goto err_out; + + if ((file->f_mode & FMODE_READ) && + (!(inode->i_mode & S_IRUGO) || !has_read)) + goto err_out; + /* allocate a sysfs_open_file for the file */ error = -ENOMEM; of = kzalloc(sizeof(struct sysfs_open_file), GFP_KERNEL); @@ -653,11 +655,14 @@ static int sysfs_open_file(struct inode *inode, struct file *file) of->file = file; /* - * Always instantiate seq_file even if read access is not - * implemented or requested. This unifies private data access and - * most files are readable anyway. + * Always instantiate seq_file even if read access doesn't use + * seq_file or is not requested. This unifies private data access + * and readable regular files are the vast majority anyway. */ - error = single_open(file, sysfs_seq_show, of); + if (sysfs_is_bin(attr_sd)) + error = single_open(file, NULL, of); + else + error = single_open(file, sysfs_seq_show, of); if (error) goto err_free; @@ -807,6 +812,9 @@ const struct file_operations sysfs_bin_operations = { .write = sysfs_write_file, .llseek = generic_file_llseek, .mmap = sysfs_bin_mmap, + .open = sysfs_open_file, + .release = sysfs_release, + .poll = sysfs_poll, }; int sysfs_add_file_mode_ns(struct sysfs_dirent *dir_sd, -- cgit v1.2.3 From 3124eb1679b28726eacbc8973a891235dca3ed99 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 1 Oct 2013 17:42:09 -0400 Subject: sysfs: merge regular and bin file handling With the previous changes, sysfs regular file code is ready to handle bin files too. This patch makes bin files share the regular file path. * sysfs_create/remove_bin_file() are moved to fs/sysfs/file.c. * sysfs_init_inode() is updated to use the new sysfs_bin_operations instead of bin_fops for bin files. * fs/sysfs/bin.c and the related pieces are removed. This patch shouldn't introduce any behavior difference to bin file accesses. Overall, this unification reduces the amount of duplicate logic, makes behaviors more consistent and paves the road for building simpler and more versatile interface which will allow other subsystems to make use of sysfs for their pseudo filesystems. v2: Stale fs/sysfs/bin.c reference dropped from Documentation/DocBook/filesystems.tmpl. Reported by kbuild test robot. Signed-off-by: Tejun Heo Cc: Kay Sievers Cc: kbuild test robot Signed-off-by: Greg Kroah-Hartman --- Documentation/DocBook/filesystems.tmpl | 1 - fs/sysfs/Makefile | 3 +- fs/sysfs/bin.c | 491 --------------------------------- fs/sysfs/dir.c | 1 - fs/sysfs/file.c | 26 ++ fs/sysfs/inode.c | 2 +- fs/sysfs/sysfs.h | 6 - 7 files changed, 28 insertions(+), 502 deletions(-) delete mode 100644 fs/sysfs/bin.c (limited to 'fs') diff --git a/Documentation/DocBook/filesystems.tmpl b/Documentation/DocBook/filesystems.tmpl index 25b58efd955d..4f676838da06 100644 --- a/Documentation/DocBook/filesystems.tmpl +++ b/Documentation/DocBook/filesystems.tmpl @@ -91,7 +91,6 @@ The Filesystem for Exporting Kernel Objects !Efs/sysfs/file.c !Efs/sysfs/symlink.c -!Efs/sysfs/bin.c diff --git a/fs/sysfs/Makefile b/fs/sysfs/Makefile index 7a1ceb946b80..8876ac183373 100644 --- a/fs/sysfs/Makefile +++ b/fs/sysfs/Makefile @@ -2,5 +2,4 @@ # Makefile for the sysfs virtual filesystem # -obj-y := inode.o file.o dir.o symlink.o mount.o bin.o \ - group.o +obj-y := inode.o file.o dir.o symlink.o mount.o group.o diff --git a/fs/sysfs/bin.c b/fs/sysfs/bin.c deleted file mode 100644 index 60a4e787dc4f..000000000000 --- a/fs/sysfs/bin.c +++ /dev/null @@ -1,491 +0,0 @@ -/* - * fs/sysfs/bin.c - sysfs binary file implementation - * - * Copyright (c) 2003 Patrick Mochel - * Copyright (c) 2003 Matthew Wilcox - * Copyright (c) 2004 Silicon Graphics, Inc. - * Copyright (c) 2007 SUSE Linux Products GmbH - * Copyright (c) 2007 Tejun Heo - * - * This file is released under the GPLv2. - * - * Please see Documentation/filesystems/sysfs.txt for more information. - */ - -#undef DEBUG - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sysfs.h" - -/* - * There's one bin_buffer for each open file. - * - * filp->private_data points to bin_buffer and - * sysfs_dirent->s_bin_attr.buffers points to a the bin_buffer s - * sysfs_dirent->s_bin_attr.buffers is protected by sysfs_bin_lock - */ -static DEFINE_MUTEX(sysfs_bin_lock); - -struct bin_buffer { - struct mutex mutex; - void *buffer; - int mmapped; - const struct vm_operations_struct *vm_ops; - struct file *file; - struct hlist_node list; -}; - -static ssize_t -read(struct file *file, char __user *userbuf, size_t bytes, loff_t *off) -{ - struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; - struct bin_attribute *attr = attr_sd->s_bin_attr.bin_attr; - struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; - struct bin_buffer *bb = file->private_data; - int size = file_inode(file)->i_size; - loff_t offs = *off; - int count = min_t(size_t, bytes, PAGE_SIZE); - char *buf; - - if (!bytes) - return 0; - - if (size) { - if (offs > size) - return 0; - if (offs + count > size) - count = size - offs; - } - - buf = kmalloc(count, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - /* need attr_sd for attr, its parent for kobj */ - mutex_lock(&bb->mutex); - if (!sysfs_get_active(attr_sd)) { - count = -ENODEV; - mutex_unlock(&bb->mutex); - goto out_free; - } - - if (attr->read) - count = attr->read(file, kobj, attr, buf, offs, count); - else - count = -EIO; - - sysfs_put_active(attr_sd); - mutex_unlock(&bb->mutex); - - if (count < 0) - goto out_free; - - if (copy_to_user(userbuf, buf, count)) { - count = -EFAULT; - goto out_free; - } - - pr_debug("offs = %lld, *off = %lld, count = %d\n", offs, *off, count); - - *off = offs + count; - - out_free: - kfree(buf); - return count; -} - -static int -flush_write(struct file *file, char *buffer, loff_t offset, size_t count) -{ - struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; - struct bin_attribute *attr = attr_sd->s_bin_attr.bin_attr; - struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; - int rc; - - /* need attr_sd for attr, its parent for kobj */ - if (!sysfs_get_active(attr_sd)) - return -ENODEV; - - rc = -EIO; - if (attr->write) - rc = attr->write(file, kobj, attr, buffer, offset, count); - - sysfs_put_active(attr_sd); - - return rc; -} - -static ssize_t write(struct file *file, const char __user *userbuf, - size_t bytes, loff_t *off) -{ - struct bin_buffer *bb = file->private_data; - int size = file_inode(file)->i_size; - loff_t offs = *off; - int count = min_t(size_t, bytes, PAGE_SIZE); - char *temp; - - if (!bytes) - return 0; - - if (size) { - if (offs > size) - return 0; - if (offs + count > size) - count = size - offs; - } - - temp = memdup_user(userbuf, count); - if (IS_ERR(temp)) - return PTR_ERR(temp); - - mutex_lock(&bb->mutex); - - memcpy(bb->buffer, temp, count); - - count = flush_write(file, bb->buffer, offs, count); - mutex_unlock(&bb->mutex); - - if (count > 0) - *off = offs + count; - - kfree(temp); - return count; -} - -static void bin_vma_open(struct vm_area_struct *vma) -{ - struct file *file = vma->vm_file; - struct bin_buffer *bb = file->private_data; - struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; - - if (!bb->vm_ops) - return; - - if (!sysfs_get_active(attr_sd)) - return; - - if (bb->vm_ops->open) - bb->vm_ops->open(vma); - - sysfs_put_active(attr_sd); -} - -static int bin_fault(struct vm_area_struct *vma, struct vm_fault *vmf) -{ - struct file *file = vma->vm_file; - struct bin_buffer *bb = file->private_data; - struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; - int ret; - - if (!bb->vm_ops) - return VM_FAULT_SIGBUS; - - if (!sysfs_get_active(attr_sd)) - return VM_FAULT_SIGBUS; - - ret = VM_FAULT_SIGBUS; - if (bb->vm_ops->fault) - ret = bb->vm_ops->fault(vma, vmf); - - sysfs_put_active(attr_sd); - return ret; -} - -static int bin_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf) -{ - struct file *file = vma->vm_file; - struct bin_buffer *bb = file->private_data; - struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; - int ret; - - if (!bb->vm_ops) - return VM_FAULT_SIGBUS; - - if (!sysfs_get_active(attr_sd)) - return VM_FAULT_SIGBUS; - - ret = 0; - if (bb->vm_ops->page_mkwrite) - ret = bb->vm_ops->page_mkwrite(vma, vmf); - else - file_update_time(file); - - sysfs_put_active(attr_sd); - return ret; -} - -static int bin_access(struct vm_area_struct *vma, unsigned long addr, - void *buf, int len, int write) -{ - struct file *file = vma->vm_file; - struct bin_buffer *bb = file->private_data; - struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; - int ret; - - if (!bb->vm_ops) - return -EINVAL; - - if (!sysfs_get_active(attr_sd)) - return -EINVAL; - - ret = -EINVAL; - if (bb->vm_ops->access) - ret = bb->vm_ops->access(vma, addr, buf, len, write); - - sysfs_put_active(attr_sd); - return ret; -} - -#ifdef CONFIG_NUMA -static int bin_set_policy(struct vm_area_struct *vma, struct mempolicy *new) -{ - struct file *file = vma->vm_file; - struct bin_buffer *bb = file->private_data; - struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; - int ret; - - if (!bb->vm_ops) - return 0; - - if (!sysfs_get_active(attr_sd)) - return -EINVAL; - - ret = 0; - if (bb->vm_ops->set_policy) - ret = bb->vm_ops->set_policy(vma, new); - - sysfs_put_active(attr_sd); - return ret; -} - -static struct mempolicy *bin_get_policy(struct vm_area_struct *vma, - unsigned long addr) -{ - struct file *file = vma->vm_file; - struct bin_buffer *bb = file->private_data; - struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; - struct mempolicy *pol; - - if (!bb->vm_ops) - return vma->vm_policy; - - if (!sysfs_get_active(attr_sd)) - return vma->vm_policy; - - pol = vma->vm_policy; - if (bb->vm_ops->get_policy) - pol = bb->vm_ops->get_policy(vma, addr); - - sysfs_put_active(attr_sd); - return pol; -} - -static int bin_migrate(struct vm_area_struct *vma, const nodemask_t *from, - const nodemask_t *to, unsigned long flags) -{ - struct file *file = vma->vm_file; - struct bin_buffer *bb = file->private_data; - struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; - int ret; - - if (!bb->vm_ops) - return 0; - - if (!sysfs_get_active(attr_sd)) - return 0; - - ret = 0; - if (bb->vm_ops->migrate) - ret = bb->vm_ops->migrate(vma, from, to, flags); - - sysfs_put_active(attr_sd); - return ret; -} -#endif - -static const struct vm_operations_struct bin_vm_ops = { - .open = bin_vma_open, - .fault = bin_fault, - .page_mkwrite = bin_page_mkwrite, - .access = bin_access, -#ifdef CONFIG_NUMA - .set_policy = bin_set_policy, - .get_policy = bin_get_policy, - .migrate = bin_migrate, -#endif -}; - -static int mmap(struct file *file, struct vm_area_struct *vma) -{ - struct bin_buffer *bb = file->private_data; - struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; - struct bin_attribute *attr = attr_sd->s_bin_attr.bin_attr; - struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; - int rc; - - mutex_lock(&bb->mutex); - - /* need attr_sd for attr, its parent for kobj */ - rc = -ENODEV; - if (!sysfs_get_active(attr_sd)) - goto out_unlock; - - rc = -EINVAL; - if (!attr->mmap) - goto out_put; - - rc = attr->mmap(file, kobj, attr, vma); - if (rc) - goto out_put; - - /* - * PowerPC's pci_mmap of legacy_mem uses shmem_zero_setup() - * to satisfy versions of X which crash if the mmap fails: that - * substitutes a new vm_file, and we don't then want bin_vm_ops. - */ - if (vma->vm_file != file) - goto out_put; - - rc = -EINVAL; - if (bb->mmapped && bb->vm_ops != vma->vm_ops) - goto out_put; - - /* - * It is not possible to successfully wrap close. - * So error if someone is trying to use close. - */ - rc = -EINVAL; - if (vma->vm_ops && vma->vm_ops->close) - goto out_put; - - rc = 0; - bb->mmapped = 1; - bb->vm_ops = vma->vm_ops; - vma->vm_ops = &bin_vm_ops; -out_put: - sysfs_put_active(attr_sd); -out_unlock: - mutex_unlock(&bb->mutex); - - return rc; -} - -static int open(struct inode *inode, struct file *file) -{ - struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; - struct bin_attribute *attr = attr_sd->s_bin_attr.bin_attr; - struct bin_buffer *bb = NULL; - int error; - - /* binary file operations requires both @sd and its parent */ - if (!sysfs_get_active(attr_sd)) - return -ENODEV; - - error = -EACCES; - if ((file->f_mode & FMODE_WRITE) && !(attr->write || attr->mmap)) - goto err_out; - if ((file->f_mode & FMODE_READ) && !(attr->read || attr->mmap)) - goto err_out; - - error = -ENOMEM; - bb = kzalloc(sizeof(*bb), GFP_KERNEL); - if (!bb) - goto err_out; - - bb->buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); - if (!bb->buffer) - goto err_out; - - mutex_init(&bb->mutex); - bb->file = file; - file->private_data = bb; - - mutex_lock(&sysfs_bin_lock); - hlist_add_head(&bb->list, &attr_sd->s_bin_attr.buffers); - mutex_unlock(&sysfs_bin_lock); - - /* open succeeded, put active references */ - sysfs_put_active(attr_sd); - return 0; - - err_out: - sysfs_put_active(attr_sd); - kfree(bb); - return error; -} - -static int release(struct inode *inode, struct file *file) -{ - struct bin_buffer *bb = file->private_data; - - mutex_lock(&sysfs_bin_lock); - hlist_del(&bb->list); - mutex_unlock(&sysfs_bin_lock); - - kfree(bb->buffer); - kfree(bb); - return 0; -} - -const struct file_operations bin_fops = { - .read = read, - .write = write, - .mmap = mmap, - .llseek = generic_file_llseek, - .open = open, - .release = release, -}; - - -void unmap_bin_file(struct sysfs_dirent *attr_sd) -{ - struct bin_buffer *bb; - - if (sysfs_type(attr_sd) != SYSFS_KOBJ_BIN_ATTR) - return; - - mutex_lock(&sysfs_bin_lock); - - hlist_for_each_entry(bb, &attr_sd->s_bin_attr.buffers, list) { - struct inode *inode = file_inode(bb->file); - - unmap_mapping_range(inode->i_mapping, 0, 0, 1); - } - - mutex_unlock(&sysfs_bin_lock); -} - -/** - * sysfs_create_bin_file - create binary file for object. - * @kobj: object. - * @attr: attribute descriptor. - */ -int sysfs_create_bin_file(struct kobject *kobj, - const struct bin_attribute *attr) -{ - BUG_ON(!kobj || !kobj->sd || !attr); - - return sysfs_add_file(kobj->sd, &attr->attr, SYSFS_KOBJ_BIN_ATTR); -} -EXPORT_SYMBOL_GPL(sysfs_create_bin_file); - -/** - * sysfs_remove_bin_file - remove binary file for object. - * @kobj: object. - * @attr: attribute descriptor. - */ -void sysfs_remove_bin_file(struct kobject *kobj, - const struct bin_attribute *attr) -{ - sysfs_hash_and_remove(kobj->sd, attr->attr.name, NULL); -} -EXPORT_SYMBOL_GPL(sysfs_remove_bin_file); diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index c4040ddb8308..f6025c81bfd5 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -596,7 +596,6 @@ void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt) sysfs_deactivate(sd); sysfs_unmap_bin_file(sd); - unmap_bin_file(sd); sysfs_put(sd); } } diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 417d005955d9..5f7a955550de 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -987,6 +987,32 @@ void sysfs_remove_file_from_group(struct kobject *kobj, } EXPORT_SYMBOL_GPL(sysfs_remove_file_from_group); +/** + * sysfs_create_bin_file - create binary file for object. + * @kobj: object. + * @attr: attribute descriptor. + */ +int sysfs_create_bin_file(struct kobject *kobj, + const struct bin_attribute *attr) +{ + BUG_ON(!kobj || !kobj->sd || !attr); + + return sysfs_add_file(kobj->sd, &attr->attr, SYSFS_KOBJ_BIN_ATTR); +} +EXPORT_SYMBOL_GPL(sysfs_create_bin_file); + +/** + * sysfs_remove_bin_file - remove binary file for object. + * @kobj: object. + * @attr: attribute descriptor. + */ +void sysfs_remove_bin_file(struct kobject *kobj, + const struct bin_attribute *attr) +{ + sysfs_hash_and_remove(kobj->sd, attr->attr.name, NULL); +} +EXPORT_SYMBOL_GPL(sysfs_remove_bin_file); + struct sysfs_schedule_callback_struct { struct list_head workq_list; struct kobject *kobj; diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c index 63f755ef71dd..2cb1b6b8ccbc 100644 --- a/fs/sysfs/inode.c +++ b/fs/sysfs/inode.c @@ -260,7 +260,7 @@ static void sysfs_init_inode(struct sysfs_dirent *sd, struct inode *inode) case SYSFS_KOBJ_BIN_ATTR: bin_attr = sd->s_bin_attr.bin_attr; inode->i_size = bin_attr->size; - inode->i_fop = &bin_fops; + inode->i_fop = &sysfs_bin_operations; break; case SYSFS_KOBJ_LINK: inode->i_op = &sysfs_symlink_inode_operations; diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index c960d6272cf6..4e01d3b3909c 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -222,12 +222,6 @@ int sysfs_add_file_mode_ns(struct sysfs_dirent *dir_sd, umode_t amode, const void *ns); void sysfs_unmap_bin_file(struct sysfs_dirent *sd); -/* - * bin.c - */ -extern const struct file_operations bin_fops; -void unmap_bin_file(struct sysfs_dirent *attr_sd); - /* * symlink.c */ -- cgit v1.2.3 From 785a162d147a547bc7a577c1c28f6fb9dbeb4f16 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Mon, 14 Oct 2013 09:27:11 -0400 Subject: sysfs: make sysfs_file_ops() follow ignore_lockdep flag 375b611e60 ("sysfs: remove sysfs_buffer->ops") introduced sysfs_file_ops() which determines the associated file operation of a given sysfs_dirent. As file ops access should be protected by an active reference, the new function includes a lockdep assertion on the sysfs_dirent; unfortunately, I forgot to take attr->ignore_lockdep flag into account and the lockdep assertion trips spuriously for files which opt out from active reference lockdep checking. # cat /sys/devices/pci0000:00/0000:00:01.2/usb1/authorized ------------[ cut here ]------------ WARNING: CPU: 1 PID: 540 at /work/os/work/fs/sysfs/file.c:79 sysfs_file_ops+0x4e/0x60() Modules linked in: CPU: 1 PID: 540 Comm: cat Not tainted 3.11.0-work+ #3 Hardware name: Bochs Bochs, BIOS Bochs 01/01/2011 0000000000000009 ffff880016205c08 ffffffff81ca0131 0000000000000000 ffff880016205c40 ffffffff81096d0d ffff8800166cb898 ffff8800166f6f60 ffffffff8125a220 ffff880011ab1ec0 ffff88000aff0c78 ffff880016205c50 Call Trace: [] dump_stack+0x4e/0x82 [] warn_slowpath_common+0x7d/0xa0 [] warn_slowpath_null+0x1a/0x20 [] sysfs_file_ops+0x4e/0x60 [] sysfs_open_file+0x54/0x300 [] do_dentry_open.isra.17+0x182/0x280 [] finish_open+0x30/0x40 [] do_last+0x503/0xd90 [] path_openat+0xbb/0x6d0 [] do_filp_open+0x3a/0x90 [] do_sys_open+0x129/0x220 [] SyS_open+0x1e/0x20 [] system_call_fastpath+0x16/0x1b ---[ end trace aa48096b111dafdb ]--- Rename fs/sysfs/dir.c::ignore_lockdep() to sysfs_ignore_lockdep() and move it to fs/sysfs/sysfs.h and make sysfs_file_ops() skip lockdep assertion if sysfs_ignore_lockdep() is true. Signed-off-by: Tejun Heo Reported-by: Yinghai Lu Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 22 ++-------------------- fs/sysfs/file.c | 3 ++- fs/sysfs/sysfs.h | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 21 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index f6025c81bfd5..eab59de47556 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -144,24 +144,6 @@ static void sysfs_unlink_sibling(struct sysfs_dirent *sd) sd->s_parent->s_flags &= ~SYSFS_FLAG_HAS_NS; } -#ifdef CONFIG_DEBUG_LOCK_ALLOC - -/* Test for attributes that want to ignore lockdep for read-locking */ -static bool ignore_lockdep(struct sysfs_dirent *sd) -{ - return sysfs_type(sd) == SYSFS_KOBJ_ATTR && - sd->s_attr.attr->ignore_lockdep; -} - -#else - -static inline bool ignore_lockdep(struct sysfs_dirent *sd) -{ - return true; -} - -#endif - /** * sysfs_get_active - get an active reference to sysfs_dirent * @sd: sysfs_dirent to get an active reference to @@ -180,7 +162,7 @@ struct sysfs_dirent *sysfs_get_active(struct sysfs_dirent *sd) if (!atomic_inc_unless_negative(&sd->s_active)) return NULL; - if (likely(!ignore_lockdep(sd))) + if (likely(!sysfs_ignore_lockdep(sd))) rwsem_acquire_read(&sd->dep_map, 0, 1, _RET_IP_); return sd; } @@ -199,7 +181,7 @@ void sysfs_put_active(struct sysfs_dirent *sd) if (unlikely(!sd)) return; - if (likely(!ignore_lockdep(sd))) + if (likely(!sysfs_ignore_lockdep(sd))) rwsem_release(&sd->dep_map, 1, _RET_IP_); v = atomic_dec_return(&sd->s_active); if (likely(v != SD_DEACTIVATED_BIAS)) diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 5f7a955550de..c324ee906df0 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -76,7 +76,8 @@ static const struct sysfs_ops *sysfs_file_ops(struct sysfs_dirent *sd) { struct kobject *kobj = sd->s_parent->s_dir.kobj; - lockdep_assert_held(sd); + if (!sysfs_ignore_lockdep(sd)) + lockdep_assert_held(sd); return kobj->ktype ? kobj->ktype->sysfs_ops : NULL; } diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index 4e01d3b3909c..94ac8fa34dd4 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -103,6 +103,7 @@ static inline unsigned int sysfs_type(struct sysfs_dirent *sd) } #ifdef CONFIG_DEBUG_LOCK_ALLOC + #define sysfs_dirent_init_lockdep(sd) \ do { \ struct attribute *attr = sd->s_attr.attr; \ @@ -112,8 +113,23 @@ do { \ \ lockdep_init_map(&sd->dep_map, "s_active", key, 0); \ } while (0) + +/* Test for attributes that want to ignore lockdep for read-locking */ +static inline bool sysfs_ignore_lockdep(struct sysfs_dirent *sd) +{ + return sysfs_type(sd) == SYSFS_KOBJ_ATTR && + sd->s_attr.attr->ignore_lockdep; +} + #else + #define sysfs_dirent_init_lockdep(sd) do {} while (0) + +static inline bool sysfs_ignore_lockdep(struct sysfs_dirent *sd) +{ + return true; +} + #endif /* -- cgit v1.2.3 From d723a92dd465d549bf79dd481c09d59f0be02936 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 10 Oct 2013 18:03:55 +1100 Subject: sysfs/bin: Fix size handling overflow for bin_attribute While looking at the code, I noticed that bin_attribute read() and write() ops copy the inode size into an int for futher comparisons. Some bin_attributes can be fairly large. For example, pci creates some for BARs set to the BAR size and giant BARs are around the corner, so this is going to break something somewhere eventually. Let's use the right type. [adjust for seqfile conversions, only needed for bin_read() - gkh] Signed-off-by: Benjamin Herrenschmidt Cc: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index c324ee906df0..5d818df7250b 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -156,7 +156,7 @@ static ssize_t sysfs_bin_read(struct file *file, char __user *userbuf, struct sysfs_open_file *of = sysfs_of(file); struct bin_attribute *battr = of->sd->s_bin_attr.bin_attr; struct kobject *kobj = of->sd->s_parent->s_dir.kobj; - int size = file_inode(file)->i_size; + loff_t size = file_inode(file)->i_size; int count = min_t(size_t, bytes, PAGE_SIZE); loff_t offs = *off; char *buf; -- cgit v1.2.3 From b9c0622516b73170fa9abffece3079920b78ed6f Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Wed, 23 Oct 2013 21:44:53 +0800 Subject: sysfs: fix sysfs_write_file for bin file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before patch(sysfs: prepare path write for unified regular / bin file handling), when size of bin file is zero, writting still can continue, but this patch changes the behaviour. The worse thing is that firmware loader is broken by this patch, and user space application can't write to firmware bin file any more because both firmware loader and drivers can't know at advance how large the firmware file is and have to set its initialized size as zero. This patch fixes the problem and keeps behaviour of writting to bin as before. Reported-by: Lothar Waßmann Tested-by: Lothar Waßmann Acked-by: Tejun Heo Signed-off-by: Ming Lei Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 5d818df7250b..c3795978b404 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -275,11 +275,10 @@ static ssize_t sysfs_write_file(struct file *file, const char __user *user_buf, { struct sysfs_open_file *of = sysfs_of(file); ssize_t len = min_t(size_t, count, PAGE_SIZE); + loff_t size = file_inode(file)->i_size; char *buf; - if (sysfs_is_bin(of->sd)) { - loff_t size = file_inode(file)->i_size; - + if (sysfs_is_bin(of->sd) && size) { if (size <= *ppos) return 0; len = min_t(ssize_t, len, size - *ppos); -- cgit v1.2.3 From 56b3f3b884652395e1025a8e4f1c4bd47bc112c4 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 24 Oct 2013 11:49:07 -0400 Subject: sysfs: merge sysfs_elem_bin_attr into sysfs_elem_attr 3124eb1679 ("sysfs: merge regular and bin file handling") folded bin file handling into regular file handling. Among other things, bin file now shares the same open path including sysfs_open_dirent association using sysfs_dirent->s_attr.open. This is buggy because ->s_bin_attr lives in the same union and doesn't have the field. This bug doesn't trigger because sysfs_elem_bin_attr doesn't have an active field at the conflicting position. It does have a field "buffers" but it isn't used anymore. This patch collapses sysfs_elem_bin_attr into sysfs_elem_attr so that the bin_attr is accessed through ->s_attr.bin_attr which lives with ->s_attr.attr in an anonymous union. The code paths already assume bin_attr contains attr as the first element, so this doesn't add any more assumptions while making it explicit that the two types are handled together. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 8 ++++---- fs/sysfs/inode.c | 2 +- fs/sysfs/sysfs.h | 11 ++++------- 3 files changed, 9 insertions(+), 12 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index c3795978b404..0d7368d43619 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -154,7 +154,7 @@ static ssize_t sysfs_bin_read(struct file *file, char __user *userbuf, size_t bytes, loff_t *off) { struct sysfs_open_file *of = sysfs_of(file); - struct bin_attribute *battr = of->sd->s_bin_attr.bin_attr; + struct bin_attribute *battr = of->sd->s_attr.bin_attr; struct kobject *kobj = of->sd->s_parent->s_dir.kobj; loff_t size = file_inode(file)->i_size; int count = min_t(size_t, bytes, PAGE_SIZE); @@ -236,7 +236,7 @@ static int flush_write_buffer(struct sysfs_open_file *of, char *buf, loff_t off, } if (sysfs_is_bin(of->sd)) { - struct bin_attribute *battr = of->sd->s_bin_attr.bin_attr; + struct bin_attribute *battr = of->sd->s_attr.bin_attr; rc = -EIO; if (battr->write) @@ -466,7 +466,7 @@ static const struct vm_operations_struct sysfs_bin_vm_ops = { static int sysfs_bin_mmap(struct file *file, struct vm_area_struct *vma) { struct sysfs_open_file *of = sysfs_of(file); - struct bin_attribute *battr = of->sd->s_bin_attr.bin_attr; + struct bin_attribute *battr = of->sd->s_attr.bin_attr; struct kobject *kobj = of->sd->s_parent->s_dir.kobj; int rc; @@ -618,7 +618,7 @@ static int sysfs_open_file(struct inode *inode, struct file *file) return -ENODEV; if (sysfs_is_bin(attr_sd)) { - struct bin_attribute *battr = attr_sd->s_bin_attr.bin_attr; + struct bin_attribute *battr = attr_sd->s_attr.bin_attr; has_read = battr->read || battr->mmap; has_write = battr->write || battr->mmap; diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c index 2cb1b6b8ccbc..825c55607af8 100644 --- a/fs/sysfs/inode.c +++ b/fs/sysfs/inode.c @@ -258,7 +258,7 @@ static void sysfs_init_inode(struct sysfs_dirent *sd, struct inode *inode) inode->i_fop = &sysfs_file_operations; break; case SYSFS_KOBJ_BIN_ATTR: - bin_attr = sd->s_bin_attr.bin_attr; + bin_attr = sd->s_attr.bin_attr; inode->i_size = bin_attr->size; inode->i_fop = &sysfs_bin_operations; break; diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index 94ac8fa34dd4..c095d952f3a5 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -29,15 +29,13 @@ struct sysfs_elem_symlink { }; struct sysfs_elem_attr { - struct attribute *attr; + union { + struct attribute *attr; + struct bin_attribute *bin_attr; + }; struct sysfs_open_dirent *open; }; -struct sysfs_elem_bin_attr { - struct bin_attribute *bin_attr; - struct hlist_head buffers; -}; - struct sysfs_inode_attrs { struct iattr ia_iattr; void *ia_secdata; @@ -74,7 +72,6 @@ struct sysfs_dirent { struct sysfs_elem_dir s_dir; struct sysfs_elem_symlink s_symlink; struct sysfs_elem_attr s_attr; - struct sysfs_elem_bin_attr s_bin_attr; }; unsigned short s_flags; -- cgit v1.2.3 From 672f76a81ad524359e761d7c863d24aec58e23c5 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 24 Oct 2013 11:49:08 -0400 Subject: sysfs: honor bin_attr.attr.ignore_lockdep ignore_lockdep is currently honored only for regular files. There's no reason to ignore it for bin files. Update sysfs_ignore_lockdep() so that bin_attr.attr.ignore_lockdep works too. While this doesn't have any in-kernel user, this unifies the behaviors between regular and bin files and will help later changes. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/sysfs.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'fs') diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index c095d952f3a5..bdce8458af3b 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -114,7 +114,9 @@ do { \ /* Test for attributes that want to ignore lockdep for read-locking */ static inline bool sysfs_ignore_lockdep(struct sysfs_dirent *sd) { - return sysfs_type(sd) == SYSFS_KOBJ_ATTR && + int type = sysfs_type(sd); + + return (type == SYSFS_KOBJ_ATTR || type == SYSFS_KOBJ_BIN_ATTR) && sd->s_attr.attr->ignore_lockdep; } -- cgit v1.2.3 From baa97cb50724e72ece05a7cead6533a9658ddf79 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 24 Oct 2013 11:49:09 -0400 Subject: sysfs: remove unused sysfs_get_dentry() prototype sysfs_get_dentry() has been gone for years now. Remove the left-over prototype. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/sysfs.h | 1 - 1 file changed, 1 deletion(-) (limited to 'fs') diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index bdce8458af3b..e0753e5eff41 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -165,7 +165,6 @@ extern const struct dentry_operations sysfs_dentry_ops; extern const struct file_operations sysfs_dir_operations; extern const struct inode_operations sysfs_dir_inode_operations; -struct dentry *sysfs_get_dentry(struct sysfs_dirent *sd); struct sysfs_dirent *sysfs_get_active(struct sysfs_dirent *sd); void sysfs_put_active(struct sysfs_dirent *sd); void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt); -- cgit v1.2.3 From 7eed6ecb0785681892ab1fe47188fc981241cfd0 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 24 Oct 2013 11:49:10 -0400 Subject: sysfs: move sysfs_hash_and_remove() to fs/sysfs/dir.c Most removal related logic is implemented in fs/sysfs/dir.c. Move sysfs_hash_and_remove() to fs/sysfs/dir.c so that __sysfs_remove() doesn't have to be public. This is pure relocation. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 38 +++++++++++++++++++++++++++++++++++++- fs/sysfs/inode.c | 26 -------------------------- fs/sysfs/sysfs.h | 5 ++--- 3 files changed, 39 insertions(+), 30 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index eab59de47556..486238d06021 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -813,7 +813,8 @@ static struct sysfs_dirent *sysfs_next_descendant_post(struct sysfs_dirent *pos, return pos->s_parent; } -void __sysfs_remove(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) +static void __sysfs_remove(struct sysfs_addrm_cxt *acxt, + struct sysfs_dirent *sd) { struct sysfs_dirent *pos, *next; @@ -846,6 +847,41 @@ void sysfs_remove(struct sysfs_dirent *sd) sysfs_addrm_finish(&acxt); } +/** + * sysfs_hash_and_remove - find a sysfs_dirent by name and remove it + * @dir_sd: parent of the target + * @name: name of the sysfs_dirent to remove + * @ns: namespace tag of the sysfs_dirent to remove + * + * Look for the sysfs_dirent with @name and @ns under @dir_sd and remove + * it. Returns 0 on success, -ENOENT if such entry doesn't exist. + */ +int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const char *name, + const void *ns) +{ + struct sysfs_addrm_cxt acxt; + struct sysfs_dirent *sd; + + if (!dir_sd) { + WARN(1, KERN_WARNING "sysfs: can not remove '%s', no directory\n", + name); + return -ENOENT; + } + + sysfs_addrm_start(&acxt); + + sd = sysfs_find_dirent(dir_sd, name, ns); + if (sd) + __sysfs_remove(&acxt, sd); + + sysfs_addrm_finish(&acxt); + + if (sd) + return 0; + else + return -ENOENT; +} + /** * sysfs_remove_dir - remove an object's directory. * @kobj: object. diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c index 825c55607af8..1750f790af3b 100644 --- a/fs/sysfs/inode.c +++ b/fs/sysfs/inode.c @@ -314,32 +314,6 @@ void sysfs_evict_inode(struct inode *inode) sysfs_put(sd); } -int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const char *name, - const void *ns) -{ - struct sysfs_addrm_cxt acxt; - struct sysfs_dirent *sd; - - if (!dir_sd) { - WARN(1, KERN_WARNING "sysfs: can not remove '%s', no directory\n", - name); - return -ENOENT; - } - - sysfs_addrm_start(&acxt); - - sd = sysfs_find_dirent(dir_sd, name, ns); - if (sd) - __sysfs_remove(&acxt, sd); - - sysfs_addrm_finish(&acxt); - - if (sd) - return 0; - else - return -ENOENT; -} - int sysfs_permission(struct inode *inode, int mask) { struct sysfs_dirent *sd; diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index e0753e5eff41..8d3dc1ddb546 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -172,8 +172,9 @@ int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd, struct sysfs_dirent *parent_sd); int sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd, struct sysfs_dirent *parent_sd); -void __sysfs_remove(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd); void sysfs_remove(struct sysfs_dirent *sd); +int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const char *name, + const void *ns); void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt); struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd, @@ -218,8 +219,6 @@ int sysfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat); int sysfs_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags); -int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const char *name, - const void *ns); int sysfs_inode_init(void); /* -- cgit v1.2.3 From d1c1459e45944e336a968acce1e459c9effcde47 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 24 Oct 2013 11:49:11 -0400 Subject: sysfs: separate out dup filename warning into a separate function Separate out sysfs_warn_dup() out of sysfs_add_one(). This will help separating out the core sysfs functionalities into kernfs so that it can be used by non-sysfs users too. This doesn't make any functional changes. Signed-off-by: Tejun Heo Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 30 +++++++++++++++++++----------- fs/sysfs/sysfs.h | 1 + 2 files changed, 20 insertions(+), 11 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 486238d06021..de47ed32d5c7 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -470,6 +470,23 @@ static char *sysfs_pathname(struct sysfs_dirent *sd, char *path) return path; } +void sysfs_warn_dup(struct sysfs_dirent *parent, const char *name) +{ + char *path; + + path = kzalloc(PATH_MAX, GFP_KERNEL); + if (path) { + sysfs_pathname(parent, path); + strlcat(path, "/", PATH_MAX); + strlcat(path, name, PATH_MAX); + } + + WARN(1, KERN_WARNING "sysfs: cannot create duplicate filename '%s'\n", + path ? path : name); + + kfree(path); +} + /** * sysfs_add_one - add sysfs_dirent to parent * @acxt: addrm context to use @@ -497,18 +514,9 @@ int sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd, int ret; ret = __sysfs_add_one(acxt, sd, parent_sd); - if (ret == -EEXIST) { - char *path = kzalloc(PATH_MAX, GFP_KERNEL); - WARN(1, KERN_WARNING - "sysfs: cannot create duplicate filename '%s'\n", - (path == NULL) ? sd->s_name - : (sysfs_pathname(parent_sd, path), - strlcat(path, "/", PATH_MAX), - strlcat(path, sd->s_name, PATH_MAX), - path)); - kfree(path); - } + if (ret == -EEXIST) + sysfs_warn_dup(parent_sd, sd->s_name); return ret; } diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index 8d3dc1ddb546..05d063fe69c7 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -168,6 +168,7 @@ extern const struct inode_operations sysfs_dir_inode_operations; struct sysfs_dirent *sysfs_get_active(struct sysfs_dirent *sd); void sysfs_put_active(struct sysfs_dirent *sd); void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt); +void sysfs_warn_dup(struct sysfs_dirent *parent, const char *name); int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd, struct sysfs_dirent *parent_sd); int sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd, -- cgit v1.2.3 From 1c1365e374bfadf908eae02cded7abb9e672c9d4 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Wed, 30 Oct 2013 14:08:32 +0200 Subject: sysfs: return correct error code on unimplemented mmap() Both POSIX.1-2008 and Linux Programmer's Manual have a dedicated return error code for a case, when a file doesn't support mmap(), it's ENODEV. This change replaces overloaded EINVAL with ENODEV in a situation described above for sysfs binary files. Signed-off-by: Vladimir Zapolskiy Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 1 - 1 file changed, 1 deletion(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 0d7368d43619..382db3c045f3 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -477,7 +477,6 @@ static int sysfs_bin_mmap(struct file *file, struct vm_area_struct *vma) if (!sysfs_get_active(of->sd)) goto out_unlock; - rc = -EINVAL; if (!battr->mmap) goto out_put; -- cgit v1.2.3 From 044e3bc33391b1f2769d5ab2c04f246c3d8e04c3 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Fri, 1 Nov 2013 13:16:53 -0400 Subject: sysfs: use generic_file_llseek() for sysfs_file_operations 13c589d5b0ac6 ("sysfs: use seq_file when reading regular files") converted regular sysfs files to use seq_file. The commit substituted generic_file_llseek() with seq_lseek() for llseek implementation. Before the change, all regular sysfs files were allowed to seek to any position in [0, PAGE_SIZE] as the file size is always PAGE_SIZE and generic_file_llseek() allows any seeking inside the range under file size; however, seq_lseek()'s behavior is different. It traverses the output by repeatedly invoking ->show() until it reaches the target offset or traversal indicates EOF. As seq_files are fully dynamic and may not end at all, it doesn't support seeking from the end (SEEK_END). Apparently, there are userland tools which uses SEEK_END to discover the buffer size to use and the switch to seq_lseek() disturbs them as SEEK_END fails with -EINVAL. The only benefits of using seq_lseek() instead of generic_file_llseek() are * Early failure. If traversing to certain file position should fail, seq_lseek() will report such failures on lseek(2) instead of the following read/write operations. * EOF detection. While SEEK_END is not supported, SEEK_SET/CUR + large offset can be used to detect eof - eof at the time of the seek anyway as the file size may change dynamically. Both aren't necessary for sysfs or prospect kernfs users. Revert to genefic_file_llseek() and preserve the original behavior. Signed-off-by: Tejun Heo Reported-by: Heiko Carstens Link: https://lkml.kernel.org/r/20131031114358.GA5551@osiris Tested-by: Heiko Carstens Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'fs') diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 382db3c045f3..79b5da2acbe1 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -800,7 +800,7 @@ EXPORT_SYMBOL_GPL(sysfs_notify); const struct file_operations sysfs_file_operations = { .read = seq_read, .write = sysfs_write_file, - .llseek = seq_lseek, + .llseek = generic_file_llseek, .open = sysfs_open_file, .release = sysfs_release, .poll = sysfs_poll, -- cgit v1.2.3 From 0cae60f91494e34a0c5391f1455f825d5849b05f Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 30 Oct 2013 10:28:36 -0400 Subject: sysfs: rename sysfs_assoc_lock and explain what it's about sysfs_assoc_lock is an odd piece of locking. In general, whoever owns a kobject is responsible for synchronizing sysfs operations and sysfs proper assumes that, for example, removal won't race with any other operation; however, this doesn't work for symlinking because an entity performing symlink doesn't usually own the target kobject and thus has no control over its removal. sysfs_assoc_lock synchronizes symlink operations against kobj->sd disassociation so that symlink code doesn't end up dereferencing already freed sysfs_dirent by racing with removal of the target kobject. This is quite obscure and the generic name of the lock and lack of comments make it difficult to understand its role. Let's rename it to sysfs_symlink_target_lock and add comments explaining what's going on. Signed-off-by: Tejun Heo Reported-by: Linus Torvalds Signed-off-by: Greg Kroah-Hartman --- fs/sysfs/dir.c | 18 +++++++++++++++--- fs/sysfs/symlink.c | 20 ++++++++++++++------ fs/sysfs/sysfs.h | 2 +- 3 files changed, 30 insertions(+), 10 deletions(-) (limited to 'fs') diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index de47ed32d5c7..08c66969d52a 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -26,7 +26,7 @@ #include "sysfs.h" DEFINE_MUTEX(sysfs_mutex); -DEFINE_SPINLOCK(sysfs_assoc_lock); +DEFINE_SPINLOCK(sysfs_symlink_target_lock); #define to_sysfs_dirent(X) rb_entry((X), struct sysfs_dirent, s_rb) @@ -902,9 +902,21 @@ void sysfs_remove_dir(struct kobject *kobj) { struct sysfs_dirent *sd = kobj->sd; - spin_lock(&sysfs_assoc_lock); + /* + * In general, kboject owner is responsible for ensuring removal + * doesn't race with other operations and sysfs doesn't provide any + * protection; however, when @kobj is used as a symlink target, the + * symlinking entity usually doesn't own @kobj and thus has no + * control over removal. @kobj->sd may be removed anytime and + * symlink code may end up dereferencing an already freed sd. + * + * sysfs_symlink_target_lock synchronizes @kobj->sd disassociation + * against symlink operations so that symlink code can safely + * dereference @kobj->sd. + */ + spin_lock(&sysfs_symlink_target_lock); kobj->sd = NULL; - spin_unlock(&sysfs_assoc_lock); + spin_unlock(&sysfs_symlink_target_lock); if (sd) { WARN_ON_ONCE(sysfs_type(sd) != SYSFS_DIR); diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c index 22ea2f5796f5..1a23681b8179 100644 --- a/fs/sysfs/symlink.c +++ b/fs/sysfs/symlink.c @@ -32,13 +32,15 @@ static int sysfs_do_create_link_sd(struct sysfs_dirent *parent_sd, BUG_ON(!name || !parent_sd); - /* target->sd can go away beneath us but is protected with - * sysfs_assoc_lock. Fetch target_sd from it. + /* + * We don't own @target and it may be removed at any time. + * Synchronize using sysfs_symlink_target_lock. See + * sysfs_remove_dir() for details. */ - spin_lock(&sysfs_assoc_lock); + spin_lock(&sysfs_symlink_target_lock); if (target->sd) target_sd = sysfs_get(target->sd); - spin_unlock(&sysfs_assoc_lock); + spin_unlock(&sysfs_symlink_target_lock); error = -ENOENT; if (!target_sd) @@ -140,10 +142,16 @@ void sysfs_delete_link(struct kobject *kobj, struct kobject *targ, const char *name) { const void *ns = NULL; - spin_lock(&sysfs_assoc_lock); + + /* + * We don't own @target and it may be removed at any time. + * Synchronize using sysfs_symlink_target_lock. See + * sysfs_remove_dir() for details. + */ + spin_lock(&sysfs_symlink_target_lock); if (targ->sd) ns = targ->sd->s_ns; - spin_unlock(&sysfs_assoc_lock); + spin_unlock(&sysfs_symlink_target_lock); sysfs_hash_and_remove(kobj->sd, name, ns); } diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index 05d063fe69c7..e3aea92ebfa3 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h @@ -159,7 +159,7 @@ extern struct kmem_cache *sysfs_dir_cachep; * dir.c */ extern struct mutex sysfs_mutex; -extern spinlock_t sysfs_assoc_lock; +extern spinlock_t sysfs_symlink_target_lock; extern const struct dentry_operations sysfs_dentry_ops; extern const struct file_operations sysfs_dir_operations; -- cgit v1.2.3