update_userdata() takes target_list_lock to swap nt->userdata and
nt->userdata_length, then frees the old buffer. Since commit
7eab73b18630 ("netconsole: convert to NBCON console infrastructure")
that lock is also the console's device_lock, so writing a userdata value
from configfs serialises against the printk core emitting messages.

The buffer is immutable once published, which is what RCU is for. Move
the string and its length into a single netconsole_userdata object and
publish it with rcu_replace_pointer(), freeing the old one with
kfree_rcu().

New userdata design:

0) Unify the userdata fields into a struct netcons_userdata
1) update_userdata() no longer needs target_list_lock.
2) writers stay serialised by dynamic_netconsole_mutex.
3) reading userdata needs an RCU read lock.

No functional change intended.

Signed-off-by: Breno Leitao <[email protected]>
---
 drivers/net/netconsole.c | 88 +++++++++++++++++++++++++++++-------------------
 1 file changed, 54 insertions(+), 34 deletions(-)

diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c
index 862001d09aa84..c88c82f2136cb 100644
--- a/drivers/net/netconsole.c
+++ b/drivers/net/netconsole.c
@@ -135,13 +135,27 @@ enum target_state {
        STATE_DEACTIVATED,
 };
 
+/**
+ * struct netcons_userdata - Formatted userdata payload of a target.
+ * @rcu:       Used to free the payload after a grace period.
+ * @length:    Length of @data, excluding the NUL terminator.
+ * @data:      Formatted " key=value\n" entries, NUL terminated.
+ *
+ * Immutable once published, so the transmit path never observes @data and
+ * @length disagreeing.
+ */
+struct netcons_userdata {
+       struct rcu_head         rcu;
+       size_t                  length;
+       char                    data[];
+};
+
 /**
  * struct netconsole_target - Represents a configured netconsole target.
  * @list:      Links this target into the target_list.
  * @group:     Links us into the configfs subsystem hierarchy.
  * @userdata_group:    Links to the userdata configfs hierarchy
- * @userdata:          Cached, formatted string of append
- * @userdata_length:   String length of userdata.
+ * @userdata:          Cached, formatted userdata payload. RCU protected.
  * @sysdata:           Cached, formatted string of append
  * @sysdata_fields:    Sysdata features enabled.
  * @msgcounter:        Message sent counter.
@@ -176,8 +190,7 @@ struct netconsole_target {
 #ifdef CONFIG_NETCONSOLE_DYNAMIC
        struct config_group     group;
        struct config_group     userdata_group;
-       char                    *userdata;
-       size_t                  userdata_length;
+       struct netcons_userdata __rcu *userdata;
        char                    sysdata[MAX_EXTRADATA_ENTRY_LEN * 
MAX_SYSDATA_ITEMS];
 
        /* bit-wise with sysdata_feature bits */
@@ -1059,12 +1072,11 @@ static int calc_userdata_len(struct netconsole_target 
*nt)
 
 static int update_userdata(struct netconsole_target *nt)
 {
+       struct netcons_userdata *new = NULL;
+       struct netcons_userdata *old;
        struct userdatum *udm_item;
        struct config_item *item;
        struct list_head *entry;
-       char *old_buf = NULL;
-       char *new_buf = NULL;
-       unsigned long flags;
        int offset = 0;
        int len;
 
@@ -1076,8 +1088,8 @@ static int update_userdata(struct netconsole_target *nt)
 
        /* Allocate new buffer */
        if (len) {
-               new_buf = kmalloc(len + 1, GFP_KERNEL);
-               if (!new_buf)
+               new = kmalloc_flex(*new, data, len + 1);
+               if (!new)
                        return -ENOMEM;
        }
 
@@ -1087,22 +1099,21 @@ static int update_userdata(struct netconsole_target *nt)
                udm_item = to_userdatum(item);
                /* Skip userdata with no value set */
                if (udm_item->value[0]) {
-                       offset += scnprintf(&new_buf[offset], len + 1 - offset,
+                       offset += scnprintf(&new->data[offset],
+                                           len + 1 - offset,
                                            " %s=%s\n", item->ci_name,
                                            udm_item->value);
                }
        }
 
        WARN_ON_ONCE(offset != len);
+       if (new)
+               new->length = offset;
 
-       /* Switch to new buffer and free old buffer */
-       spin_lock_irqsave(&target_list_lock, flags);
-       old_buf = nt->userdata;
-       nt->userdata = new_buf;
-       nt->userdata_length = offset;
-       spin_unlock_irqrestore(&target_list_lock, flags);
-
-       kfree(old_buf);
+       /* Writers are serialized by dynamic_netconsole_mutex. */
+       old = rcu_replace_pointer(nt->userdata, new,
+                                 lockdep_is_held(&dynamic_netconsole_mutex));
+       kfree_rcu(old, rcu);
 
        return 0;
 }
@@ -1926,14 +1937,13 @@ static void send_udp(struct netconsole_target *nt, 
const char *msg, int len)
 static void send_msg_no_fragmentation(struct netconsole_target *nt,
                                      const char *msg,
                                      int msg_len,
-                                     int release_len)
+                                     int release_len,
+                                     const struct netcons_userdata *userdata)
 {
-       const char *userdata = NULL;
        const char *sysdata = NULL;
        const char *release;
 
 #ifdef CONFIG_NETCONSOLE_DYNAMIC
-       userdata = nt->userdata;
        sysdata = nt->sysdata;
 #endif
 
@@ -1950,7 +1960,7 @@ static void send_msg_no_fragmentation(struct 
netconsole_target *nt,
        if (userdata)
                msg_len += scnprintf(&nt->buf[msg_len],
                                     sizeof(nt->buf) - msg_len, "%s",
-                                    userdata);
+                                    userdata->data);
 
        if (sysdata)
                msg_len += scnprintf(&nt->buf[msg_len],
@@ -1970,7 +1980,8 @@ static void append_release(char *buf)
 
 static void send_fragmented_body(struct netconsole_target *nt,
                                 const char *msgbody_ptr, int header_len,
-                                int msgbody_len, int sysdata_len)
+                                int msgbody_len, int sysdata_len,
+                                const struct netcons_userdata *userdata)
 {
        const char *userdata_ptr = NULL;
        const char *sysdata_ptr = NULL;
@@ -1981,12 +1992,12 @@ static void send_fragmented_body(struct 
netconsole_target *nt,
        int userdata_len = 0;
 
 #ifdef CONFIG_NETCONSOLE_DYNAMIC
-       userdata_ptr = nt->userdata;
        sysdata_ptr = nt->sysdata;
-       userdata_len = nt->userdata_length;
 #endif
-       if (WARN_ON_ONCE(!userdata_ptr && userdata_len != 0))
-               return;
+       if (userdata) {
+               userdata_ptr = userdata->data;
+               userdata_len = userdata->length;
+       }
 
        if (WARN_ON_ONCE(!sysdata_ptr && sysdata_len != 0))
                return;
@@ -2063,7 +2074,8 @@ static void send_msg_fragmented(struct netconsole_target 
*nt,
                                const char *msg,
                                int msg_len,
                                int release_len,
-                               int sysdata_len)
+                               int sysdata_len,
+                               const struct netcons_userdata *userdata)
 {
        int header_len, msgbody_len;
        const char *msgbody;
@@ -2092,7 +2104,7 @@ static void send_msg_fragmented(struct netconsole_target 
*nt,
         * will be replaced
         */
        send_fragmented_body(nt, msgbody, header_len, msgbody_len,
-                            sysdata_len);
+                            sysdata_len, userdata);
 }
 
 /**
@@ -2107,25 +2119,33 @@ static void send_msg_fragmented(struct 
netconsole_target *nt,
 static void send_ext_msg_udp(struct netconsole_target *nt,
                             struct nbcon_write_context *wctxt)
 {
+       const struct netcons_userdata *userdata = NULL;
        int userdata_len = 0;
        int release_len = 0;
        int sysdata_len = 0;
        int len;
 
+       /* Keeps the payload picked below alive until the last send_udp(). */
+       rcu_read_lock();
+
 #ifdef CONFIG_NETCONSOLE_DYNAMIC
        sysdata_len = prepare_sysdata(nt, wctxt);
-       userdata_len = nt->userdata_length;
+       userdata = rcu_dereference(nt->userdata);
+       if (userdata)
+               userdata_len = userdata->length;
 #endif
        if (nt->release)
                release_len = strlen(init_utsname()->release) + 1;
 
        len = wctxt->len + release_len + sysdata_len + userdata_len;
        if (len <= MAX_PRINT_CHUNK)
-               return send_msg_no_fragmentation(nt, wctxt->outbuf,
-                                                wctxt->len, release_len);
+               send_msg_no_fragmentation(nt, wctxt->outbuf, wctxt->len,
+                                         release_len, userdata);
+       else
+               send_msg_fragmented(nt, wctxt->outbuf, wctxt->len, release_len,
+                                   sysdata_len, userdata);
 
-       return send_msg_fragmented(nt, wctxt->outbuf, wctxt->len, release_len,
-                                  sysdata_len);
+       rcu_read_unlock();
 }
 
 static void send_msg_udp(struct netconsole_target *nt, const char *msg,

-- 
2.53.0-Meta


Reply via email to