: Some BUGs and deadlocks fixes and a bit of concomitant cleanups.
Subsystems (3)
cover : Some BUGs and deadlocks fixes and a bit of concomitant cleanups.
Hi Jay, Dave.
While looking at the bonding driver I found four bugs visible with the unaided eye. Here are the fixes, but I also cleaned up some places, I just couldn't come by. If you consider those cleanups to be for next merge window, just kick me and I'll resubmit this set without them.
Found BUGs summary:
1. Bad error path in bond_create - the registered device should not be released with free_netdev;
2. Specifying the 'bond%d' name when creating a new device results in a new device with literally 'bond%d' name, not bond<digit> like for other drivers;
3. Lost unlock of rtnl and bonding_rwsem in bonding_store_bonds;
4. Deadlock between bonding_store_bonds and bond_destroy_sysfs on module unload.
The clean ups are
1. Minor alloc_netdev consolidation after fix #2
2. Redundant 3rd argument for bond_create;
3. Unneeded _safe list iterations in some places;
4. Unneeded list_empty checks in some places.
Details in patches.
PATCH 1/8 : Do not call free_netdev for already registered device.
If the call to bond_create_sysfs_entry in bond_create fails, the proper rollback is to call unregister_netdevice, not free_netdev. Otherwise - kernel BUG at net/core/dev.c:4057!
Checked with artificial failures injected into bond_create_sysfs_entry.
--- drivers/net/bonding/bond_main.c | 14 +++++++++----- 1 files changed, 9 insertions(+), 5 deletions(-)
drivers/net/bonding/bond_main.c +9 -5
@@ -4936,14 +4936,18 @@ int bond_create(char *name, struct bond_params *params, struct bonding **newbondup_write (&bonding_rwsem);rtnl_unlock ();/* allows sysfs registration of net device */ res =bond_create_sysfs_entry (bond_dev->priv); -if (res <0 ) { -rtnl_lock (); -down_write (&bonding_rwsem); - goto out_bond; - } +if (res <0 ) + goto out_unreg;return 0 ; +out_unreg: +rtnl_lock (); +down_write (&bonding_rwsem); +bond_deinit (bond_dev); +unregister_netdevice (bond_dev); + goto out_rtnl; + out_bond:bond_deinit (bond_dev); out_netdev:
Signature · 1 line
1.5.3.4
PATCH 2/8 : Don't create bondings with % in their names.
In reply to Pavel EmelyanovType
# echo '+bond%d' > /sys/class/net/bonding_masters and get the 'bond%d' device in the ip l l output.
Other drivers do not play such jokes on user :)
--- drivers/net/bonding/bond_main.c | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-)
drivers/net/bonding/bond_main.c +4 -0
@@ -4909,6 +4909,10 @@ int bond_create(char *name, struct bond_params *params, struct bonding **newbond res =dev_alloc_name (bond_dev,"bond%d" );if (res <0 ) goto out_netdev; + }else if (strchr (name,'%' )) { + res =dev_alloc_name (bond_dev, name); +if (res <0 ) + goto out_netdev; }/* bond_init() must be called after dev_alloc_name() (for the
Signature · 1 line
1.5.3.4
PATCH 3/8 : Merge two calls to dev_alloc_name.
In reply to Pavel EmelyanovJust reduce the amount of code after the previous patch.
--- drivers/net/bonding/bond_main.c | 9 ++++----- 1 files changed, 4 insertions(+), 5 deletions(-)
drivers/net/bonding/bond_main.c +4 -5
@@ -4905,11 +4905,10 @@ int bond_create(char *name, struct bond_params *params, struct bonding **newbond goto out_rtnl; } -if (!name) { - res =dev_alloc_name (bond_dev,"bond%d" ); -if (res <0 ) - goto out_netdev; - }else if (strchr (name,'%' )) { +if (!name) + name ="bond%d" ; + +if (strchr (name,'%' )) { res =dev_alloc_name (bond_dev, name);if (res <0 ) goto out_netdev;
Signature · 1 line
1.5.3.4
PATCH 4/8 : Remove redundant argument from bond_create.
In reply to Pavel EmelyanovWhile we're fixing the bond_create, I hope it's OK to polish it a bit after the fixes.
The third argument is NULL at the first caller and is ignored by the second one, so remove it.
--- drivers/net/bonding/bond_main.c | 7 ++----- drivers/net/bonding/bond_sysfs.c | 2 +- drivers/net/bonding/bonding.h | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-)
drivers/net/bonding/bond_main.c +2 -5
@@ -4874,7 +4874,7 @@ static struct lock_class_key bonding_netdev_xmit_lock_key;* Caller must NOT hold rtnl_lock; we need to release it here before we * set up our sysfs entries. */ -int bond_create (char *name,struct bond_params *params,struct bonding **newbond) +int bond_create (char *name,struct bond_params *params) {struct net_device *bond_dev;struct bonding *bond, *nxt;
@@ -4931,9 +4931,6 @@ int bond_create(char *name, struct bond_params *params, struct bonding **newbondlockdep_set_class (&bond_dev->_xmit_lock, &bonding_netdev_xmit_lock_key); -if (newbond) - *newbond = bond_dev->priv; -netif_carrier_off (bond_dev);up_write (&bonding_rwsem);
@@ -4981,7 +4978,7 @@ static int __init bonding_init(void)init_rwsem (&bonding_rwsem);for (i =0 ; i < max_bonds; i++) { - res =bond_create (NULL , &bonding_defaults,NULL ); + res =bond_create (NULL , &bonding_defaults);if (res) goto err; }
drivers/net/bonding/bond_sysfs.c +1 -1
@@ -122,7 +122,7 @@ static ssize_t bonding_store_bonds(struct class *cls, const char *buffer, size_tif (command[0 ] =='+' ) { printk(KERN_INFO DRV_NAME ": %s is being created...\n", ifname); - rv =bond_create (ifname, &bonding_defaults, &bond); + rv =bond_create (ifname, &bonding_defaults);if (rv) { printk(KERN_INFO DRV_NAME ": Bond creation failed.\n"); res = rv;
drivers/net/bonding/bonding.h +1 -1
@@ -301,7 +301,7 @@ static inline void bond_unset_master_alb_flags(struct bonding *bond)struct vlan_entry *bond_next_vlan (struct bonding *bond,struct vlan_entry *curr);int bond_dev_queue_xmit (struct bonding *bond,struct sk_buff *skb,struct net_device *slave_dev); -int bond_create (char *name,struct bond_params *params,struct bonding **newbond); +int bond_create (char *name,struct bond_params *params);void bond_destroy (struct bonding *bond);int bond_release_and_destroy (struct net_device *bond_dev,struct net_device *slave_dev);int bond_create_sysfs (void );
Signature · 1 line
1.5.3.4
PATCH 5/8 : Lost semaphores unlock in one of bonding_store_bonds error paths.
In reply to Pavel EmelyanovThe out: label just makes return. Wipe things up.
--- drivers/net/bonding/bond_sysfs.c | 10 +++++----- 1 files changed, 5 insertions(+), 5 deletions(-)
drivers/net/bonding/bond_sysfs.c +5 -5
@@ -146,12 +146,12 @@ static ssize_t bonding_store_bonds(struct class *cls, const char *buffer, size_t": Unable remove bond %s due to open references.\n" , ifname); res = -EPERM ; - goto out; + }else { + printk(KERN_INFO DRV_NAME +": %s is being deleted...\n" , + bond->dev->name); +bond_destroy (bond); } - printk(KERN_INFO DRV_NAME -": %s is being deleted...\n" , - bond->dev->name); -bond_destroy (bond);up_write (&bonding_rwsem);rtnl_unlock (); goto out;
Signature · 1 line
1.5.3.4
PATCH 6/8 : Relax unneeded _safe lists iterations.
In reply to Pavel EmelyanovMany places either do not modify the list under the list_for_each_xxx, or break out of the loop as soon as the first element is removed.
Thus, this _safe iteration just occupies some unneeded .text space and requires an additional variable.
--- drivers/net/bonding/bond_main.c | 36 ++++++++++++++++-------------------- drivers/net/bonding/bond_sysfs.c | 3 +-- 2 files changed, 17 insertions(+), 22 deletions(-)
drivers/net/bonding/bond_main.c +16 -20
@@ -261,14 +261,14 @@ static int bond_add_vlan(struct bonding *bond, unsigned short vlan_id)*/ static int bond_del_vlan (struct bonding *bond,unsigned short vlan_id) { -struct vlan_entry *vlan, *next; +struct vlan_entry *vlan;int res = -ENODEV ;dprintk ("bond: %s, vlan id %d\n" , bond->dev->name, vlan_id);write_lock_bh (&bond->lock); -list_for_each_entry_safe (vlan, next, &bond->vlan_list, vlan_list) { +list_for_each_entry (vlan, &bond->vlan_list, vlan_list) {if (vlan->vlan_id == vlan_id) {list_del (&vlan->vlan_list);
@@ -2428,7 +2428,7 @@ out:static int bond_has_ip (struct bonding *bond) { -struct vlan_entry *vlan, *vlan_next; +struct vlan_entry *vlan;if (bond->master_ip)return 1 ;
@@ -2436,8 +2436,7 @@ static int bond_has_ip(struct bonding *bond)if (list_empty (&bond->vlan_list))return 0 ; -list_for_each_entry_safe (vlan, vlan_next, &bond->vlan_list, - vlan_list) { +list_for_each_entry (vlan, &bond->vlan_list, vlan_list) {if (vlan->vlan_ip)return 1 ; }
@@ -2447,7 +2446,7 @@ static int bond_has_ip(struct bonding *bond)static int bond_has_this_ip (struct bonding *bond,__be32 ip) { -struct vlan_entry *vlan, *vlan_next; +struct vlan_entry *vlan;if (ip == bond->master_ip)return 1 ;
@@ -2455,8 +2454,7 @@ static int bond_has_this_ip(struct bonding *bond, __be32 ip)if (list_empty (&bond->vlan_list))return 0 ; -list_for_each_entry_safe (vlan, vlan_next, &bond->vlan_list, - vlan_list) { +list_for_each_entry (vlan, &bond->vlan_list, vlan_list) {if (ip == vlan->vlan_ip)return 1 ; }
@@ -2498,7 +2496,7 @@ static void bond_arp_send_all(struct bonding *bond, struct slave *slave) {int i, vlan_id, rv;__be32 *targets = bond->params.arp_targets; -struct vlan_entry *vlan, *vlan_next; +struct vlan_entry *vlan;struct net_device *vlan_dev;struct flowi fl;struct rtable *rt;
@@ -2545,8 +2543,7 @@ static void bond_arp_send_all(struct bonding *bond, struct slave *slave) } vlan_id =0 ; -list_for_each_entry_safe (vlan, vlan_next, &bond->vlan_list, - vlan_list) { +list_for_each_entry (vlan, &bond->vlan_list, vlan_list) { vlan_dev =vlan_group_get_device (bond->vlgrp, vlan->vlan_id);if (vlan_dev == rt->u.dst.dev) { vlan_id = vlan->vlan_id;
@@ -3503,13 +3500,13 @@ static int bond_inetaddr_event(struct notifier_block *this, unsigned long event, {struct in_ifaddr *ifa = ptr;struct net_device *vlan_dev, *event_dev = ifa->ifa_dev->dev; -struct bonding *bond, *bond_next; -struct vlan_entry *vlan, *vlan_next; +struct bonding *bond; +struct vlan_entry *vlan;if (dev_net (ifa->ifa_dev->dev) != &init_net)return NOTIFY_DONE ; -list_for_each_entry_safe (bond, bond_next, &bond_dev_list, bond_list) { +list_for_each_entry (bond, &bond_dev_list, bond_list) {if (bond->dev == event_dev) {switch (event) {case NETDEV_UP :
@@ -3526,8 +3523,7 @@ static int bond_inetaddr_event(struct notifier_block *this, unsigned long event,if (list_empty (&bond->vlan_list))continue ; -list_for_each_entry_safe (vlan, vlan_next, &bond->vlan_list, - vlan_list) { +list_for_each_entry (vlan, &bond->vlan_list, vlan_list) { vlan_dev =vlan_group_get_device (bond->vlgrp, vlan->vlan_id);if (vlan_dev == event_dev) {switch (event) {
@@ -4877,7 +4873,7 @@ static struct lock_class_key bonding_netdev_xmit_lock_key;int bond_create (char *name,struct bond_params *params) {struct net_device *bond_dev; -struct bonding *bond, *nxt; +struct bonding *bond;int res;rtnl_lock ();
@@ -4885,7 +4881,7 @@ int bond_create(char *name, struct bond_params *params)/* Check to see if the bond already exists. */ if (name) { -list_for_each_entry_safe (bond, nxt, &bond_dev_list, bond_list) +list_for_each_entry (bond, &bond_dev_list, bond_list)if (strnicmp (bond->dev->name, name,IFNAMSIZ ) ==0 ) { printk(KERN_ERR DRV_NAME ": cannot add bond %s; it already exists\n",
@@ -4962,7 +4958,7 @@ static int __init bonding_init(void) {int i;int res; -struct bonding *bond, *nxt; +struct bonding *bond;printk (KERN_INFO "%s" , version);
@@ -4992,7 +4988,7 @@ static int __init bonding_init(void) goto out; err: -list_for_each_entry_safe (bond, nxt, &bond_dev_list, bond_list) { +list_for_each_entry (bond, &bond_dev_list, bond_list) {bond_work_cancel_all (bond);destroy_workqueue (bond->wq); }
drivers/net/bonding/bond_sysfs.c +1 -2
@@ -111,7 +111,6 @@ static ssize_t bonding_store_bonds(struct class *cls, const char *buffer, size_tchar *ifname;int rv, res = count;struct bonding *bond; -struct bonding *nxt;sscanf (buffer,"%16s" , command);/* IFNAMSIZ*/ ifname = command +1 ;
@@ -134,7 +133,7 @@ static ssize_t bonding_store_bonds(struct class *cls, const char *buffer, size_trtnl_lock ();down_write (&bonding_rwsem); -list_for_each_entry_safe (bond, nxt, &bond_dev_list, bond_list) +list_for_each_entry (bond, &bond_dev_list, bond_list)if (strnicmp (bond->dev->name, ifname,IFNAMSIZ ) ==0 ) {/* check the ref count on the bond's kobject. * If it's > expected, then there's a file open,
Signature · 1 line
1.5.3.4
PATCH 7/8 : Remove unneeded list_empty checks.
In reply to Pavel EmelyanovSome places iterate over the checked list right after the check itself, so even if the list is empty, the list_for_each_xxx iterator will make everything right by himself.
--- drivers/net/bonding/bond_main.c | 9 --------- 1 files changed, 0 insertions(+), 9 deletions(-)
drivers/net/bonding/bond_main.c +0 -9
@@ -2433,9 +2433,6 @@ static int bond_has_ip(struct bonding *bond)if (bond->master_ip)return 1 ; -if (list_empty (&bond->vlan_list)) -return 0 ; -list_for_each_entry (vlan, &bond->vlan_list, vlan_list) {if (vlan->vlan_ip)return 1 ;
@@ -2451,9 +2448,6 @@ static int bond_has_this_ip(struct bonding *bond, __be32 ip)if (ip == bond->master_ip)return 1 ; -if (list_empty (&bond->vlan_list)) -return 0 ; -list_for_each_entry (vlan, &bond->vlan_list, vlan_list) {if (ip == vlan->vlan_ip)return 1 ;
@@ -3520,9 +3514,6 @@ static int bond_inetaddr_event(struct notifier_block *this, unsigned long event, } } -if (list_empty (&bond->vlan_list)) -continue ; -list_for_each_entry (vlan, &bond->vlan_list, vlan_list) { vlan_dev =vlan_group_get_device (bond->vlgrp, vlan->vlan_id);if (vlan_dev == event_dev) {
Signature · 1 line
1.5.3.4
PATCH 8/8 : Deadlock between bonding_store_bonds and bond_destroy_sysfs.
In reply to Pavel EmelyanovThe sysfs layer has an internal protection, that ensures, that all the process sitting inside ->sore/->show callback exits before the appropriate entry is unregistered (the calltraces are rather big, but I can provide them if required).
On the other hand, bonding takes rtnl_lock in
a) the bonding_store_bonds, i.e. in ->store callback,
b) module exit before calling the sysfs unregister routines.
Thus, the classical AB-BA deadlock may occur. To reproduce run
# while :; do modprobe bonding; rmmod bonding; done and # while :; do echo '+bond%d' > /sys/class/net/bonding_masters ; done in parallel.
The fix is to move the bond_destroy_sysfs out of the rtnl_lock, but _before_ bond_free_all to make sure no bonding devices exist after module unload.
--- drivers/net/bonding/bond_main.c | 6 ++++-- 1 files changed, 4 insertions(+), 2 deletions(-)
drivers/net/bonding/bond_main.c +4 -2
@@ -4984,9 +4984,10 @@ err:destroy_workqueue (bond->wq); } +bond_destroy_sysfs (); +rtnl_lock ();bond_free_all (); -bond_destroy_sysfs ();rtnl_unlock (); out:return res;
@@ -4998,9 +4999,10 @@ static void __exit bonding_exit(void)unregister_netdevice_notifier (&bond_netdev_notifier);unregister_inetaddr_notifier (&bond_inetaddr_notifier); +bond_destroy_sysfs (); +rtnl_lock ();bond_free_all (); -bond_destroy_sysfs ();rtnl_unlock (); }
Signature · 1 line
1.5.3.4
David Miller
maintainerFrom: Pavel Emelyanov <[email protected]>
Date: Tue, 29 Apr 2008 18:51:32 +0400
Quoted 5 lines
While looking at the bonding driver I found four bugs visible with the unaided eye. Here are the fixes, but I also cleaned up some places, I just couldn't come by. If you consider those cleanups to be for next merge window, just kick me and I'll resubmit this set without them.
I think all of these patches are excellent.
Jay, et al., can you make sure these go to Jeff and get integrated?
Thanks.
Quoted 9 lines · David Miller
From: Pavel Emelyanov <[email protected]>
Date: Tue, 29 Apr 2008 18:51:32 +0400Quoted 5 lines
While looking at the bonding driver I found four bugs visible with the unaided eye. Here are the fixes, but I also cleaned up some places, I just couldn't come by. If you consider those cleanups to be for next merge window, just kick me and I'll resubmit this set without them.
Quoted 4 lines
I think all of these patches are excellent.
Jay, et al., can you make sure these go to Jeff and get integrated?
Yep, I played with them today, and am passing on the bug fixes
for 2.6.26 and the cleanups for -next.
-J
---
-Jay Vosburgh, IBM Linux Technology Center, [email protected]