Commit 9b40d5aa authored by David S. Miller's avatar David S. Miller

Merge tag 'batman-adv-fix-for-davem' of git://git.open-mesh.org/linux-merge

Antonio Quartulli says:

====================
In this small batch of patches you have:
- a fix for our Distributed ARP Table that makes sure that the input
  provided to the hash function during a query is the same as the one
  provided during an insert (so to prevent false negatives), by Antonio
  Quartulli
- a fix for our new protocol implementation B.A.T.M.A.N. V that ensures
  that a hard interface is properly re-activated when it is brought down
  and then up again, by Antonio Quartulli
- two fixes respectively to the reference counting of the tt_local_entry
  and neigh_node objects, by Sven Eckelmann. Such bug is rather severe
  as it would prevent the netdev objects references by batman-adv from
  being released after shutdown.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 33656a1f abe59c65
...@@ -32,10 +32,21 @@ ...@@ -32,10 +32,21 @@
#include "bat_v_elp.h" #include "bat_v_elp.h"
#include "bat_v_ogm.h" #include "bat_v_ogm.h"
#include "hard-interface.h"
#include "hash.h" #include "hash.h"
#include "originator.h" #include "originator.h"
#include "packet.h" #include "packet.h"
static void batadv_v_iface_activate(struct batadv_hard_iface *hard_iface)
{
/* B.A.T.M.A.N. V does not use any queuing mechanism, therefore it can
* set the interface as ACTIVE right away, without any risk of race
* condition
*/
if (hard_iface->if_status == BATADV_IF_TO_BE_ACTIVATED)
hard_iface->if_status = BATADV_IF_ACTIVE;
}
static int batadv_v_iface_enable(struct batadv_hard_iface *hard_iface) static int batadv_v_iface_enable(struct batadv_hard_iface *hard_iface)
{ {
int ret; int ret;
...@@ -274,6 +285,7 @@ static bool batadv_v_neigh_is_sob(struct batadv_neigh_node *neigh1, ...@@ -274,6 +285,7 @@ static bool batadv_v_neigh_is_sob(struct batadv_neigh_node *neigh1,
static struct batadv_algo_ops batadv_batman_v __read_mostly = { static struct batadv_algo_ops batadv_batman_v __read_mostly = {
.name = "BATMAN_V", .name = "BATMAN_V",
.bat_iface_activate = batadv_v_iface_activate,
.bat_iface_enable = batadv_v_iface_enable, .bat_iface_enable = batadv_v_iface_enable,
.bat_iface_disable = batadv_v_iface_disable, .bat_iface_disable = batadv_v_iface_disable,
.bat_iface_update_mac = batadv_v_iface_update_mac, .bat_iface_update_mac = batadv_v_iface_update_mac,
......
...@@ -568,6 +568,7 @@ static void batadv_choose_next_candidate(struct batadv_priv *bat_priv, ...@@ -568,6 +568,7 @@ static void batadv_choose_next_candidate(struct batadv_priv *bat_priv,
* be sent to * be sent to
* @bat_priv: the bat priv with all the soft interface information * @bat_priv: the bat priv with all the soft interface information
* @ip_dst: ipv4 to look up in the DHT * @ip_dst: ipv4 to look up in the DHT
* @vid: VLAN identifier
* *
* An originator O is selected if and only if its DHT_ID value is one of three * An originator O is selected if and only if its DHT_ID value is one of three
* closest values (from the LEFT, with wrap around if needed) then the hash * closest values (from the LEFT, with wrap around if needed) then the hash
...@@ -576,7 +577,8 @@ static void batadv_choose_next_candidate(struct batadv_priv *bat_priv, ...@@ -576,7 +577,8 @@ static void batadv_choose_next_candidate(struct batadv_priv *bat_priv,
* Return: the candidate array of size BATADV_DAT_CANDIDATE_NUM. * Return: the candidate array of size BATADV_DAT_CANDIDATE_NUM.
*/ */
static struct batadv_dat_candidate * static struct batadv_dat_candidate *
batadv_dat_select_candidates(struct batadv_priv *bat_priv, __be32 ip_dst) batadv_dat_select_candidates(struct batadv_priv *bat_priv, __be32 ip_dst,
unsigned short vid)
{ {
int select; int select;
batadv_dat_addr_t last_max = BATADV_DAT_ADDR_MAX, ip_key; batadv_dat_addr_t last_max = BATADV_DAT_ADDR_MAX, ip_key;
...@@ -592,7 +594,7 @@ batadv_dat_select_candidates(struct batadv_priv *bat_priv, __be32 ip_dst) ...@@ -592,7 +594,7 @@ batadv_dat_select_candidates(struct batadv_priv *bat_priv, __be32 ip_dst)
return NULL; return NULL;
dat.ip = ip_dst; dat.ip = ip_dst;
dat.vid = 0; dat.vid = vid;
ip_key = (batadv_dat_addr_t)batadv_hash_dat(&dat, ip_key = (batadv_dat_addr_t)batadv_hash_dat(&dat,
BATADV_DAT_ADDR_MAX); BATADV_DAT_ADDR_MAX);
...@@ -612,6 +614,7 @@ batadv_dat_select_candidates(struct batadv_priv *bat_priv, __be32 ip_dst) ...@@ -612,6 +614,7 @@ batadv_dat_select_candidates(struct batadv_priv *bat_priv, __be32 ip_dst)
* @bat_priv: the bat priv with all the soft interface information * @bat_priv: the bat priv with all the soft interface information
* @skb: payload to send * @skb: payload to send
* @ip: the DHT key * @ip: the DHT key
* @vid: VLAN identifier
* @packet_subtype: unicast4addr packet subtype to use * @packet_subtype: unicast4addr packet subtype to use
* *
* This function copies the skb with pskb_copy() and is sent as unicast packet * This function copies the skb with pskb_copy() and is sent as unicast packet
...@@ -622,7 +625,7 @@ batadv_dat_select_candidates(struct batadv_priv *bat_priv, __be32 ip_dst) ...@@ -622,7 +625,7 @@ batadv_dat_select_candidates(struct batadv_priv *bat_priv, __be32 ip_dst)
*/ */
static bool batadv_dat_send_data(struct batadv_priv *bat_priv, static bool batadv_dat_send_data(struct batadv_priv *bat_priv,
struct sk_buff *skb, __be32 ip, struct sk_buff *skb, __be32 ip,
int packet_subtype) unsigned short vid, int packet_subtype)
{ {
int i; int i;
bool ret = false; bool ret = false;
...@@ -631,7 +634,7 @@ static bool batadv_dat_send_data(struct batadv_priv *bat_priv, ...@@ -631,7 +634,7 @@ static bool batadv_dat_send_data(struct batadv_priv *bat_priv,
struct sk_buff *tmp_skb; struct sk_buff *tmp_skb;
struct batadv_dat_candidate *cand; struct batadv_dat_candidate *cand;
cand = batadv_dat_select_candidates(bat_priv, ip); cand = batadv_dat_select_candidates(bat_priv, ip, vid);
if (!cand) if (!cand)
goto out; goto out;
...@@ -1022,7 +1025,7 @@ bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, ...@@ -1022,7 +1025,7 @@ bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv,
ret = true; ret = true;
} else { } else {
/* Send the request to the DHT */ /* Send the request to the DHT */
ret = batadv_dat_send_data(bat_priv, skb, ip_dst, ret = batadv_dat_send_data(bat_priv, skb, ip_dst, vid,
BATADV_P_DAT_DHT_GET); BATADV_P_DAT_DHT_GET);
} }
out: out:
...@@ -1150,8 +1153,8 @@ void batadv_dat_snoop_outgoing_arp_reply(struct batadv_priv *bat_priv, ...@@ -1150,8 +1153,8 @@ void batadv_dat_snoop_outgoing_arp_reply(struct batadv_priv *bat_priv,
/* Send the ARP reply to the candidates for both the IP addresses that /* Send the ARP reply to the candidates for both the IP addresses that
* the node obtained from the ARP reply * the node obtained from the ARP reply
*/ */
batadv_dat_send_data(bat_priv, skb, ip_src, BATADV_P_DAT_DHT_PUT); batadv_dat_send_data(bat_priv, skb, ip_src, vid, BATADV_P_DAT_DHT_PUT);
batadv_dat_send_data(bat_priv, skb, ip_dst, BATADV_P_DAT_DHT_PUT); batadv_dat_send_data(bat_priv, skb, ip_dst, vid, BATADV_P_DAT_DHT_PUT);
} }
/** /**
......
...@@ -407,6 +407,9 @@ batadv_hardif_activate_interface(struct batadv_hard_iface *hard_iface) ...@@ -407,6 +407,9 @@ batadv_hardif_activate_interface(struct batadv_hard_iface *hard_iface)
batadv_update_min_mtu(hard_iface->soft_iface); batadv_update_min_mtu(hard_iface->soft_iface);
if (bat_priv->bat_algo_ops->bat_iface_activate)
bat_priv->bat_algo_ops->bat_iface_activate(hard_iface);
out: out:
if (primary_if) if (primary_if)
batadv_hardif_put(primary_if); batadv_hardif_put(primary_if);
......
...@@ -250,7 +250,6 @@ static void batadv_neigh_node_release(struct kref *ref) ...@@ -250,7 +250,6 @@ static void batadv_neigh_node_release(struct kref *ref)
{ {
struct hlist_node *node_tmp; struct hlist_node *node_tmp;
struct batadv_neigh_node *neigh_node; struct batadv_neigh_node *neigh_node;
struct batadv_hardif_neigh_node *hardif_neigh;
struct batadv_neigh_ifinfo *neigh_ifinfo; struct batadv_neigh_ifinfo *neigh_ifinfo;
struct batadv_algo_ops *bao; struct batadv_algo_ops *bao;
...@@ -262,13 +261,7 @@ static void batadv_neigh_node_release(struct kref *ref) ...@@ -262,13 +261,7 @@ static void batadv_neigh_node_release(struct kref *ref)
batadv_neigh_ifinfo_put(neigh_ifinfo); batadv_neigh_ifinfo_put(neigh_ifinfo);
} }
hardif_neigh = batadv_hardif_neigh_get(neigh_node->if_incoming, batadv_hardif_neigh_put(neigh_node->hardif_neigh);
neigh_node->addr);
if (hardif_neigh) {
/* batadv_hardif_neigh_get() increases refcount too */
batadv_hardif_neigh_put(hardif_neigh);
batadv_hardif_neigh_put(hardif_neigh);
}
if (bao->bat_neigh_free) if (bao->bat_neigh_free)
bao->bat_neigh_free(neigh_node); bao->bat_neigh_free(neigh_node);
...@@ -665,6 +658,10 @@ batadv_neigh_node_new(struct batadv_orig_node *orig_node, ...@@ -665,6 +658,10 @@ batadv_neigh_node_new(struct batadv_orig_node *orig_node,
neigh_node->orig_node = orig_node; neigh_node->orig_node = orig_node;
neigh_node->last_seen = jiffies; neigh_node->last_seen = jiffies;
/* increment unique neighbor refcount */
kref_get(&hardif_neigh->refcount);
neigh_node->hardif_neigh = hardif_neigh;
/* extra reference for return */ /* extra reference for return */
kref_init(&neigh_node->refcount); kref_init(&neigh_node->refcount);
kref_get(&neigh_node->refcount); kref_get(&neigh_node->refcount);
...@@ -673,9 +670,6 @@ batadv_neigh_node_new(struct batadv_orig_node *orig_node, ...@@ -673,9 +670,6 @@ batadv_neigh_node_new(struct batadv_orig_node *orig_node,
hlist_add_head_rcu(&neigh_node->list, &orig_node->neigh_list); hlist_add_head_rcu(&neigh_node->list, &orig_node->neigh_list);
spin_unlock_bh(&orig_node->neigh_list_lock); spin_unlock_bh(&orig_node->neigh_list_lock);
/* increment unique neighbor refcount */
kref_get(&hardif_neigh->refcount);
batadv_dbg(BATADV_DBG_BATMAN, orig_node->bat_priv, batadv_dbg(BATADV_DBG_BATMAN, orig_node->bat_priv,
"Creating new neighbor %pM for orig_node %pM on interface %s\n", "Creating new neighbor %pM for orig_node %pM on interface %s\n",
neigh_addr, orig_node->orig, hard_iface->net_dev->name); neigh_addr, orig_node->orig, hard_iface->net_dev->name);
......
...@@ -215,6 +215,8 @@ static void batadv_tt_local_entry_release(struct kref *ref) ...@@ -215,6 +215,8 @@ static void batadv_tt_local_entry_release(struct kref *ref)
tt_local_entry = container_of(ref, struct batadv_tt_local_entry, tt_local_entry = container_of(ref, struct batadv_tt_local_entry,
common.refcount); common.refcount);
batadv_softif_vlan_put(tt_local_entry->vlan);
kfree_rcu(tt_local_entry, common.rcu); kfree_rcu(tt_local_entry, common.rcu);
} }
...@@ -673,6 +675,7 @@ bool batadv_tt_local_add(struct net_device *soft_iface, const u8 *addr, ...@@ -673,6 +675,7 @@ bool batadv_tt_local_add(struct net_device *soft_iface, const u8 *addr,
kref_get(&tt_local->common.refcount); kref_get(&tt_local->common.refcount);
tt_local->last_seen = jiffies; tt_local->last_seen = jiffies;
tt_local->common.added_at = tt_local->last_seen; tt_local->common.added_at = tt_local->last_seen;
tt_local->vlan = vlan;
/* the batman interface mac and multicast addresses should never be /* the batman interface mac and multicast addresses should never be
* purged * purged
...@@ -991,7 +994,6 @@ int batadv_tt_local_seq_print_text(struct seq_file *seq, void *offset) ...@@ -991,7 +994,6 @@ int batadv_tt_local_seq_print_text(struct seq_file *seq, void *offset)
struct batadv_tt_common_entry *tt_common_entry; struct batadv_tt_common_entry *tt_common_entry;
struct batadv_tt_local_entry *tt_local; struct batadv_tt_local_entry *tt_local;
struct batadv_hard_iface *primary_if; struct batadv_hard_iface *primary_if;
struct batadv_softif_vlan *vlan;
struct hlist_head *head; struct hlist_head *head;
unsigned short vid; unsigned short vid;
u32 i; u32 i;
...@@ -1027,14 +1029,6 @@ int batadv_tt_local_seq_print_text(struct seq_file *seq, void *offset) ...@@ -1027,14 +1029,6 @@ int batadv_tt_local_seq_print_text(struct seq_file *seq, void *offset)
last_seen_msecs = last_seen_msecs % 1000; last_seen_msecs = last_seen_msecs % 1000;
no_purge = tt_common_entry->flags & np_flag; no_purge = tt_common_entry->flags & np_flag;
vlan = batadv_softif_vlan_get(bat_priv, vid);
if (!vlan) {
seq_printf(seq, "Cannot retrieve VLAN %d\n",
BATADV_PRINT_VID(vid));
continue;
}
seq_printf(seq, seq_printf(seq,
" * %pM %4i [%c%c%c%c%c%c] %3u.%03u (%#.8x)\n", " * %pM %4i [%c%c%c%c%c%c] %3u.%03u (%#.8x)\n",
tt_common_entry->addr, tt_common_entry->addr,
...@@ -1052,9 +1046,7 @@ int batadv_tt_local_seq_print_text(struct seq_file *seq, void *offset) ...@@ -1052,9 +1046,7 @@ int batadv_tt_local_seq_print_text(struct seq_file *seq, void *offset)
BATADV_TT_CLIENT_ISOLA) ? 'I' : '.'), BATADV_TT_CLIENT_ISOLA) ? 'I' : '.'),
no_purge ? 0 : last_seen_secs, no_purge ? 0 : last_seen_secs,
no_purge ? 0 : last_seen_msecs, no_purge ? 0 : last_seen_msecs,
vlan->tt.crc); tt_local->vlan->tt.crc);
batadv_softif_vlan_put(vlan);
} }
rcu_read_unlock(); rcu_read_unlock();
} }
...@@ -1099,7 +1091,6 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr, ...@@ -1099,7 +1091,6 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr,
{ {
struct batadv_tt_local_entry *tt_local_entry; struct batadv_tt_local_entry *tt_local_entry;
u16 flags, curr_flags = BATADV_NO_FLAGS; u16 flags, curr_flags = BATADV_NO_FLAGS;
struct batadv_softif_vlan *vlan;
void *tt_entry_exists; void *tt_entry_exists;
tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid); tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid);
...@@ -1139,14 +1130,6 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr, ...@@ -1139,14 +1130,6 @@ u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr,
/* extra call to free the local tt entry */ /* extra call to free the local tt entry */
batadv_tt_local_entry_put(tt_local_entry); batadv_tt_local_entry_put(tt_local_entry);
/* decrease the reference held for this vlan */
vlan = batadv_softif_vlan_get(bat_priv, vid);
if (!vlan)
goto out;
batadv_softif_vlan_put(vlan);
batadv_softif_vlan_put(vlan);
out: out:
if (tt_local_entry) if (tt_local_entry)
batadv_tt_local_entry_put(tt_local_entry); batadv_tt_local_entry_put(tt_local_entry);
...@@ -1219,7 +1202,6 @@ static void batadv_tt_local_table_free(struct batadv_priv *bat_priv) ...@@ -1219,7 +1202,6 @@ static void batadv_tt_local_table_free(struct batadv_priv *bat_priv)
spinlock_t *list_lock; /* protects write access to the hash lists */ spinlock_t *list_lock; /* protects write access to the hash lists */
struct batadv_tt_common_entry *tt_common_entry; struct batadv_tt_common_entry *tt_common_entry;
struct batadv_tt_local_entry *tt_local; struct batadv_tt_local_entry *tt_local;
struct batadv_softif_vlan *vlan;
struct hlist_node *node_tmp; struct hlist_node *node_tmp;
struct hlist_head *head; struct hlist_head *head;
u32 i; u32 i;
...@@ -1241,14 +1223,6 @@ static void batadv_tt_local_table_free(struct batadv_priv *bat_priv) ...@@ -1241,14 +1223,6 @@ static void batadv_tt_local_table_free(struct batadv_priv *bat_priv)
struct batadv_tt_local_entry, struct batadv_tt_local_entry,
common); common);
/* decrease the reference held for this vlan */
vlan = batadv_softif_vlan_get(bat_priv,
tt_common_entry->vid);
if (vlan) {
batadv_softif_vlan_put(vlan);
batadv_softif_vlan_put(vlan);
}
batadv_tt_local_entry_put(tt_local); batadv_tt_local_entry_put(tt_local);
} }
spin_unlock_bh(list_lock); spin_unlock_bh(list_lock);
...@@ -3309,7 +3283,6 @@ static void batadv_tt_local_purge_pending_clients(struct batadv_priv *bat_priv) ...@@ -3309,7 +3283,6 @@ static void batadv_tt_local_purge_pending_clients(struct batadv_priv *bat_priv)
struct batadv_hashtable *hash = bat_priv->tt.local_hash; struct batadv_hashtable *hash = bat_priv->tt.local_hash;
struct batadv_tt_common_entry *tt_common; struct batadv_tt_common_entry *tt_common;
struct batadv_tt_local_entry *tt_local; struct batadv_tt_local_entry *tt_local;
struct batadv_softif_vlan *vlan;
struct hlist_node *node_tmp; struct hlist_node *node_tmp;
struct hlist_head *head; struct hlist_head *head;
spinlock_t *list_lock; /* protects write access to the hash lists */ spinlock_t *list_lock; /* protects write access to the hash lists */
...@@ -3339,13 +3312,6 @@ static void batadv_tt_local_purge_pending_clients(struct batadv_priv *bat_priv) ...@@ -3339,13 +3312,6 @@ static void batadv_tt_local_purge_pending_clients(struct batadv_priv *bat_priv)
struct batadv_tt_local_entry, struct batadv_tt_local_entry,
common); common);
/* decrease the reference held for this vlan */
vlan = batadv_softif_vlan_get(bat_priv, tt_common->vid);
if (vlan) {
batadv_softif_vlan_put(vlan);
batadv_softif_vlan_put(vlan);
}
batadv_tt_local_entry_put(tt_local); batadv_tt_local_entry_put(tt_local);
} }
spin_unlock_bh(list_lock); spin_unlock_bh(list_lock);
......
...@@ -433,6 +433,7 @@ struct batadv_hardif_neigh_node { ...@@ -433,6 +433,7 @@ struct batadv_hardif_neigh_node {
* @ifinfo_lock: lock protecting private ifinfo members and list * @ifinfo_lock: lock protecting private ifinfo members and list
* @if_incoming: pointer to incoming hard-interface * @if_incoming: pointer to incoming hard-interface
* @last_seen: when last packet via this neighbor was received * @last_seen: when last packet via this neighbor was received
* @hardif_neigh: hardif_neigh of this neighbor
* @refcount: number of contexts the object is used * @refcount: number of contexts the object is used
* @rcu: struct used for freeing in an RCU-safe manner * @rcu: struct used for freeing in an RCU-safe manner
*/ */
...@@ -444,6 +445,7 @@ struct batadv_neigh_node { ...@@ -444,6 +445,7 @@ struct batadv_neigh_node {
spinlock_t ifinfo_lock; /* protects ifinfo_list and its members */ spinlock_t ifinfo_lock; /* protects ifinfo_list and its members */
struct batadv_hard_iface *if_incoming; struct batadv_hard_iface *if_incoming;
unsigned long last_seen; unsigned long last_seen;
struct batadv_hardif_neigh_node *hardif_neigh;
struct kref refcount; struct kref refcount;
struct rcu_head rcu; struct rcu_head rcu;
}; };
...@@ -1073,10 +1075,12 @@ struct batadv_tt_common_entry { ...@@ -1073,10 +1075,12 @@ struct batadv_tt_common_entry {
* struct batadv_tt_local_entry - translation table local entry data * struct batadv_tt_local_entry - translation table local entry data
* @common: general translation table data * @common: general translation table data
* @last_seen: timestamp used for purging stale tt local entries * @last_seen: timestamp used for purging stale tt local entries
* @vlan: soft-interface vlan of the entry
*/ */
struct batadv_tt_local_entry { struct batadv_tt_local_entry {
struct batadv_tt_common_entry common; struct batadv_tt_common_entry common;
unsigned long last_seen; unsigned long last_seen;
struct batadv_softif_vlan *vlan;
}; };
/** /**
...@@ -1250,6 +1254,8 @@ struct batadv_forw_packet { ...@@ -1250,6 +1254,8 @@ struct batadv_forw_packet {
* struct batadv_algo_ops - mesh algorithm callbacks * struct batadv_algo_ops - mesh algorithm callbacks
* @list: list node for the batadv_algo_list * @list: list node for the batadv_algo_list
* @name: name of the algorithm * @name: name of the algorithm
* @bat_iface_activate: start routing mechanisms when hard-interface is brought
* up
* @bat_iface_enable: init routing info when hard-interface is enabled * @bat_iface_enable: init routing info when hard-interface is enabled
* @bat_iface_disable: de-init routing info when hard-interface is disabled * @bat_iface_disable: de-init routing info when hard-interface is disabled
* @bat_iface_update_mac: (re-)init mac addresses of the protocol information * @bat_iface_update_mac: (re-)init mac addresses of the protocol information
...@@ -1277,6 +1283,7 @@ struct batadv_forw_packet { ...@@ -1277,6 +1283,7 @@ struct batadv_forw_packet {
struct batadv_algo_ops { struct batadv_algo_ops {
struct hlist_node list; struct hlist_node list;
char *name; char *name;
void (*bat_iface_activate)(struct batadv_hard_iface *hard_iface);
int (*bat_iface_enable)(struct batadv_hard_iface *hard_iface); int (*bat_iface_enable)(struct batadv_hard_iface *hard_iface);
void (*bat_iface_disable)(struct batadv_hard_iface *hard_iface); void (*bat_iface_disable)(struct batadv_hard_iface *hard_iface);
void (*bat_iface_update_mac)(struct batadv_hard_iface *hard_iface); void (*bat_iface_update_mac)(struct batadv_hard_iface *hard_iface);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment