Whamcloud - gitweb
LU-5397 obdclass: optimize busy loop wait
[fs/lustre-release.git] / lustre / obdclass / obd_config.c
index c663e5d..3914193 100644 (file)
 #ifdef __KERNEL__
 #include <obd_class.h>
 #include <linux/string.h>
+#include <lustre_disk.h>
 #else
 #include <liblustre.h>
 #include <string.h>
 #include <obd_class.h>
 #include <obd.h>
 #endif
+#include <lustre_ioctl.h>
 #include <lustre_log.h>
 #include <lprocfs_status.h>
 #include <lustre_param.h>
@@ -404,14 +406,15 @@ int class_attach(struct lustre_cfg *lcfg)
        /* recovery data */
        cfs_init_timer(&obd->obd_recovery_timer);
        spin_lock_init(&obd->obd_recovery_task_lock);
-        cfs_waitq_init(&obd->obd_next_transno_waitq);
-        cfs_waitq_init(&obd->obd_evict_inprogress_waitq);
-        CFS_INIT_LIST_HEAD(&obd->obd_req_replay_queue);
-        CFS_INIT_LIST_HEAD(&obd->obd_lock_replay_queue);
-        CFS_INIT_LIST_HEAD(&obd->obd_final_req_queue);
-        CFS_INIT_LIST_HEAD(&obd->obd_evict_list);
+       init_waitqueue_head(&obd->obd_next_transno_waitq);
+       init_waitqueue_head(&obd->obd_evict_inprogress_waitq);
+       CFS_INIT_LIST_HEAD(&obd->obd_req_replay_queue);
+       CFS_INIT_LIST_HEAD(&obd->obd_lock_replay_queue);
+       CFS_INIT_LIST_HEAD(&obd->obd_final_req_queue);
+       CFS_INIT_LIST_HEAD(&obd->obd_evict_list);
+       INIT_LIST_HEAD(&obd->obd_lwp_list);
 
-        llog_group_init(&obd->obd_olg, FID_SEQ_LLOG);
+       llog_group_init(&obd->obd_olg);
 
        obd->obd_conn_inprogress = 0;
 
@@ -423,23 +426,16 @@ int class_attach(struct lustre_cfg *lcfg)
         }
         memcpy(obd->obd_uuid.uuid, uuid, len);
 
-        /* do the attach */
-        if (OBP(obd, attach)) {
-                rc = OBP(obd,attach)(obd, sizeof *lcfg, lcfg);
-                if (rc)
-                        GOTO(out, rc = -EINVAL);
-        }
-
         /* Detach drops this */
        spin_lock(&obd->obd_dev_lock);
-       cfs_atomic_set(&obd->obd_refcount, 1);
+       atomic_set(&obd->obd_refcount, 1);
        spin_unlock(&obd->obd_dev_lock);
         lu_ref_init(&obd->obd_reference);
         lu_ref_add(&obd->obd_reference, "attach", obd);
 
         obd->obd_attached = 1;
         CDEBUG(D_IOCTL, "OBD: dev %d attached type %s with refcount %d\n",
-               obd->obd_minor, typename, cfs_atomic_read(&obd->obd_refcount));
+              obd->obd_minor, typename, atomic_read(&obd->obd_refcount));
         RETURN(0);
  out:
         if (obd != NULL) {
@@ -628,16 +624,12 @@ int class_cleanup(struct obd_device *obd, struct lustre_cfg *lcfg)
        }
        /* Leave this on forever */
        obd->obd_stopping = 1;
+       spin_unlock(&obd->obd_dev_lock);
 
        /* wait for already-arrived-connections to finish. */
-       while (obd->obd_conn_inprogress > 0) {
-               spin_unlock(&obd->obd_dev_lock);
-
-               cfs_cond_resched();
-
-               spin_lock(&obd->obd_dev_lock);
-       }
-       spin_unlock(&obd->obd_dev_lock);
+       while (obd->obd_conn_inprogress > 0)
+               yield();
+       smp_rmb();
 
         if (lcfg->lcfg_bufcount >= 2 && LUSTRE_CFG_BUFLEN(lcfg, 1) > 0) {
                 for (flag = lustre_cfg_string(lcfg, 1); *flag != 0; flag++)
@@ -666,12 +658,12 @@ int class_cleanup(struct obd_device *obd, struct lustre_cfg *lcfg)
 
         /* The three references that should be remaining are the
          * obd_self_export and the attach and setup references. */
-        if (cfs_atomic_read(&obd->obd_refcount) > 3) {
+       if (atomic_read(&obd->obd_refcount) > 3) {
                 /* refcounf - 3 might be the number of real exports
                    (excluding self export). But class_incref is called
                    by other things as well, so don't count on it. */
                 CDEBUG(D_IOCTL, "%s: forcing exports to disconnect: %d\n",
-                       obd->obd_name, cfs_atomic_read(&obd->obd_refcount) - 3);
+                      obd->obd_name, atomic_read(&obd->obd_refcount) - 3);
                 dump_exports(obd, 0);
                 class_disconnect_exports(obd);
         }
@@ -711,9 +703,9 @@ struct obd_device *class_incref(struct obd_device *obd,
                                 const char *scope, const void *source)
 {
         lu_ref_add_atomic(&obd->obd_reference, scope, source);
-        cfs_atomic_inc(&obd->obd_refcount);
+       atomic_inc(&obd->obd_refcount);
         CDEBUG(D_INFO, "incref %s (%p) now %d\n", obd->obd_name, obd,
-               cfs_atomic_read(&obd->obd_refcount));
+              atomic_read(&obd->obd_refcount));
 
         return obd;
 }
@@ -725,8 +717,8 @@ void class_decref(struct obd_device *obd, const char *scope, const void *source)
        int refs;
 
        spin_lock(&obd->obd_dev_lock);
-       cfs_atomic_dec(&obd->obd_refcount);
-       refs = cfs_atomic_read(&obd->obd_refcount);
+       atomic_dec(&obd->obd_refcount);
+       refs = atomic_read(&obd->obd_refcount);
        spin_unlock(&obd->obd_dev_lock);
        lu_ref_del(&obd->obd_reference, scope, source);
 
@@ -756,11 +748,7 @@ void class_decref(struct obd_device *obd, const char *scope, const void *source)
                                 CERROR("Cleanup %s returned %d\n",
                                        obd->obd_name, err);
                 }
-                if (OBP(obd, detach)) {
-                        err = OBP(obd, detach)(obd);
-                        if (err)
-                                CERROR("Detach returned %d\n", err);
-                }
+
                 class_release_dev(obd);
         }
 }
@@ -1049,6 +1037,45 @@ struct lustre_cfg *lustre_cfg_rename(struct lustre_cfg *cfg,
 }
 EXPORT_SYMBOL(lustre_cfg_rename);
 
+static int process_param2_config(struct lustre_cfg *lcfg)
+{
+       char *param = lustre_cfg_string(lcfg, 1);
+       char *upcall = lustre_cfg_string(lcfg, 2);
+       char *argv[] = {
+               [0] = "/usr/sbin/lctl",
+               [1] = "set_param",
+               [2] = param,
+               [3] = NULL
+       };
+       struct timeval  start;
+       struct timeval  end;
+       int             rc;
+       ENTRY;
+
+       /* Add upcall processing here. Now only lctl is supported */
+       if (strcmp(upcall, LCTL_UPCALL) != 0) {
+               CERROR("Unsupported upcall %s\n", upcall);
+               RETURN(-EINVAL);
+       }
+
+       do_gettimeofday(&start);
+       rc = call_usermodehelper(argv[0], argv, NULL, 0);
+       do_gettimeofday(&end);
+
+       if (rc < 0) {
+               CERROR("lctl: error invoking upcall %s %s %s: rc = %d; "
+                      "time %ldus\n", argv[0], argv[1], argv[2], rc,
+                      cfs_timeval_sub(&end, &start, NULL));
+       } else {
+               CDEBUG(D_HA, "lctl: invoked upcall %s %s %s, time %ldus\n",
+                      argv[0], argv[1], argv[2],
+                      cfs_timeval_sub(&end, &start, NULL));
+                      rc = 0;
+       }
+
+       RETURN(rc);
+}
+
 void lustre_register_quota_process_config(int (*qpc)(struct lustre_cfg *lcfg))
 {
        quota_process_config = qpc;
@@ -1164,11 +1191,14 @@ int class_process_config(struct lustre_cfg *lcfg)
                        err = (*quota_process_config)(lcfg);
                        GOTO(out, err);
                }
-                /* Fall through */
-                break;
-        }
-        }
 
+               break;
+       }
+       case LCFG_SET_PARAM: {
+               err = process_param2_config(lcfg);
+               GOTO(out, err = 0);
+       }
+       }
         /* Commands that require a device */
         obd = class_name2obd(lustre_cfg_string(lcfg, 0));
         if (obd == NULL) {
@@ -1205,24 +1235,20 @@ int class_process_config(struct lustre_cfg *lcfg)
         case LCFG_POOL_NEW: {
                 err = obd_pool_new(obd, lustre_cfg_string(lcfg, 2));
                 GOTO(out, err = 0);
-                break;
         }
         case LCFG_POOL_ADD: {
                 err = obd_pool_add(obd, lustre_cfg_string(lcfg, 2),
                                    lustre_cfg_string(lcfg, 3));
                 GOTO(out, err = 0);
-                break;
         }
         case LCFG_POOL_REM: {
                 err = obd_pool_rem(obd, lustre_cfg_string(lcfg, 2),
                                    lustre_cfg_string(lcfg, 3));
                 GOTO(out, err = 0);
-                break;
         }
         case LCFG_POOL_DEL: {
                 err = obd_pool_del(obd, lustre_cfg_string(lcfg, 2));
                 GOTO(out, err = 0);
-                break;
         }
         default: {
                 err = obd_process_config(obd, sizeof(*lcfg), lcfg);
@@ -1240,6 +1266,7 @@ out:
 }
 EXPORT_SYMBOL(class_process_config);
 
+#ifndef HAVE_ONLY_PROCFS_SEQ
 int class_process_proc_param(char *prefix, struct lprocfs_vars *lvars,
                              struct lustre_cfg *lcfg, void *data)
 {
@@ -1263,7 +1290,10 @@ int class_process_proc_param(char *prefix, struct lprocfs_vars *lvars,
         for (i = 1; i < lcfg->lcfg_bufcount; i++) {
                 key = lustre_cfg_buf(lcfg, i);
                 /* Strip off prefix */
-                class_match_param(key, prefix, &key);
+               if (class_match_param(key, prefix, &key))
+                       /* If the prefix doesn't match, return error so we
+                        * can pass it down the stack */
+                       RETURN(-ENOSYS);
                 sval = strchr(key, '=');
                 if (!sval || (*(sval + 1) == 0)) {
                         CERROR("Can't parse param %s (missing '=')\n", key);
@@ -1282,31 +1312,29 @@ int class_process_proc_param(char *prefix, struct lprocfs_vars *lvars,
                             keylen == strlen(var->name)) {
                                 matched++;
                                 rc = -EROFS;
-                                if (var->write_fptr) {
-                                        mm_segment_t oldfs;
-                                        oldfs = get_fs();
-                                        set_fs(KERNEL_DS);
-                                        rc = (var->write_fptr)(NULL, sval,
-                                                               vallen, data);
-                                        set_fs(oldfs);
-                                }
-                                break;
+
+                               if (var->write_fptr) {
+                                       mm_segment_t oldfs;
+                                       oldfs = get_fs();
+                                       set_fs(KERNEL_DS);
+                                       rc = (var->write_fptr)(NULL, sval,
+                                                               vallen, data);
+                                       set_fs(oldfs);
+                               }
+                               break;
                         }
                         j++;
                 }
-                if (!matched) {
-                        /* If the prefix doesn't match, return error so we
-                           can pass it down the stack */
-                        if (strnchr(key, keylen, '.'))
-                            RETURN(-ENOSYS);
-                        CERROR("%s: unknown param %s\n",
-                               (char *)lustre_cfg_string(lcfg, 0), key);
-                        /* rc = -EINVAL;        continue parsing other params */
-                        skip++;
-                } else if (rc < 0) {
-                        CERROR("writing proc entry %s err %d\n",
-                               var->name, rc);
-                        rc = 0;
+               if (!matched) {
+                       CERROR("%.*s: %s unknown param %s\n",
+                              (int)strlen(prefix) - 1, prefix,
+                              (char *)lustre_cfg_string(lcfg, 0), key);
+                       /* rc = -EINVAL;        continue parsing other params */
+                       skip++;
+               } else if (rc < 0) {
+                       CERROR("%s: error writing proc entry '%s': rc = %d\n",
+                              prefix, var->name, rc);
+                       rc = 0;
                } else {
                        CDEBUG(D_CONFIG, "%s.%.*s: Set parameter %.*s=%s\n",
                                         lustre_cfg_string(lcfg, 0),
@@ -1327,6 +1355,101 @@ int class_process_proc_param(char *prefix, struct lprocfs_vars *lvars,
 #endif
 }
 EXPORT_SYMBOL(class_process_proc_param);
+#endif
+
+int class_process_proc_seq_param(char *prefix, struct lprocfs_seq_vars *lvars,
+                                struct lustre_cfg *lcfg, void *data)
+{
+#ifdef __KERNEL__
+       struct lprocfs_seq_vars *var;
+       struct file fakefile;
+       struct seq_file fake_seqfile;
+       char *key, *sval;
+       int i, keylen, vallen;
+       int matched = 0, j = 0;
+       int rc = 0;
+       int skip = 0;
+       ENTRY;
+
+       if (lcfg->lcfg_command != LCFG_PARAM) {
+               CERROR("Unknown command: %d\n", lcfg->lcfg_command);
+               RETURN(-EINVAL);
+       }
+
+       /* fake a seq file so that var->fops->write can work... */
+       fakefile.private_data = &fake_seqfile;
+       fake_seqfile.private = data;
+       /* e.g. tunefs.lustre --param mdt.group_upcall=foo /r/tmp/lustre-mdt
+          or   lctl conf_param lustre-MDT0000.mdt.group_upcall=bar
+          or   lctl conf_param lustre-OST0000.osc.max_dirty_mb=36 */
+       for (i = 1; i < lcfg->lcfg_bufcount; i++) {
+               key = lustre_cfg_buf(lcfg, i);
+               /* Strip off prefix */
+               if (class_match_param(key, prefix, &key))
+                       /* If the prefix doesn't match, return error so we
+                        * can pass it down the stack */
+                       RETURN(-ENOSYS);
+               sval = strchr(key, '=');
+               if (!sval || (*(sval + 1) == 0)) {
+                       CERROR("Can't parse param %s (missing '=')\n", key);
+                       /* rc = -EINVAL;        continue parsing other params */
+                       continue;
+               }
+               keylen = sval - key;
+               sval++;
+               vallen = strlen(sval);
+               matched = 0;
+               j = 0;
+               /* Search proc entries */
+               while (lvars[j].name) {
+                       var = &lvars[j];
+                       if (class_match_param(key, (char *)var->name, 0) == 0 &&
+                           keylen == strlen(var->name)) {
+                               matched++;
+                               rc = -EROFS;
+
+                               if (var->fops && var->fops->write) {
+                                       mm_segment_t oldfs;
+                                       oldfs = get_fs();
+                                       set_fs(KERNEL_DS);
+                                       rc = (var->fops->write)(&fakefile, sval,
+                                                               vallen, NULL);
+                                       set_fs(oldfs);
+                               }
+                               break;
+                       }
+                       j++;
+               }
+               if (!matched) {
+                       CERROR("%.*s: %s unknown param %s\n",
+                              (int)strlen(prefix) - 1, prefix,
+                              (char *)lustre_cfg_string(lcfg, 0), key);
+                       /* rc = -EINVAL;        continue parsing other params */
+                       skip++;
+               } else if (rc < 0) {
+                       CERROR("%s: error writing proc entry '%s': rc = %d\n",
+                              prefix, var->name, rc);
+                       rc = 0;
+               } else {
+                       CDEBUG(D_CONFIG, "%s.%.*s: Set parameter %.*s=%s\n",
+                                        lustre_cfg_string(lcfg, 0),
+                                        (int)strlen(prefix) - 1, prefix,
+                                        (int)(sval - key - 1), key, sval);
+               }
+       }
+
+       if (rc > 0)
+               rc = 0;
+       if (!rc && skip)
+               rc = skip;
+       RETURN(rc);
+#else
+       CDEBUG(D_CONFIG, "liblustre can't process params.\n");
+       /* Don't throw config error */
+       RETURN(0);
+#endif
+}
+EXPORT_SYMBOL(class_process_proc_seq_param);
 
 #ifdef __KERNEL__
 extern int lustre_check_exclusion(struct super_block *sb, char *svname);
@@ -1334,6 +1457,42 @@ extern int lustre_check_exclusion(struct super_block *sb, char *svname);
 #define lustre_check_exclusion(a,b)  0
 #endif
 
+/*
+ * Supplemental functions for config logs, it allocates lustre_cfg
+ * buffers plus initialized llog record header at the beginning.
+ */
+struct llog_cfg_rec *lustre_cfg_rec_new(int cmd, struct lustre_cfg_bufs *bufs)
+{
+       struct llog_cfg_rec     *lcr;
+       int                      reclen;
+
+       ENTRY;
+
+       reclen = lustre_cfg_len(bufs->lcfg_bufcount, bufs->lcfg_buflen);
+       reclen = llog_data_len(reclen) + sizeof(struct llog_rec_hdr) +
+                sizeof(struct llog_rec_tail);
+
+       OBD_ALLOC(lcr, reclen);
+       if (lcr == NULL)
+               RETURN(NULL);
+
+       lustre_cfg_init(&lcr->lcr_cfg, cmd, bufs);
+
+       lcr->lcr_hdr.lrh_len = reclen;
+       lcr->lcr_hdr.lrh_type = OBD_CFG_REC;
+
+       RETURN(lcr);
+}
+EXPORT_SYMBOL(lustre_cfg_rec_new);
+
+void lustre_cfg_rec_free(struct llog_cfg_rec *lcr)
+{
+       ENTRY;
+       OBD_FREE(lcr, lcr->lcr_hdr.lrh_len);
+       EXIT;
+}
+EXPORT_SYMBOL(lustre_cfg_rec_free);
+
 /** Parse a configuration llog, doing various manipulations on them
  * for various reasons, (modifications for compatibility, skip obsolete
  * records, change uuids, etc), then class_process_config() resulting
@@ -1379,6 +1538,8 @@ int class_config_llog_handler(const struct lu_env *env,
                         if (marker->cm_flags & CM_START) {
                                 /* all previous flags off */
                                 clli->cfg_flags = CFG_F_MARKER;
+                               server_name2index(marker->cm_tgtname,
+                                                 &clli->cfg_lwp_idx, NULL);
                                 if (marker->cm_flags & CM_SKIP) {
                                         clli->cfg_flags |= CFG_F_SKIP;
                                         CDEBUG(D_CONFIG, "SKIP #%d\n",
@@ -1514,6 +1675,8 @@ int class_config_llog_handler(const struct lu_env *env,
                 }
 
                 lcfg_new = lustre_cfg_new(lcfg->lcfg_command, &bufs);
+               if (lcfg_new == NULL)
+                       GOTO(out, rc = -ENOMEM);
 
                 lcfg_new->lcfg_num   = lcfg->lcfg_num;
                 lcfg_new->lcfg_flags = lcfg->lcfg_flags;
@@ -1599,6 +1762,114 @@ parse_out:
 }
 EXPORT_SYMBOL(class_config_parse_llog);
 
+struct lcfg_type_data {
+       __u32    ltd_type;
+       char    *ltd_name;
+       char    *ltd_bufs[4];
+} lcfg_data_table[] = {
+       { LCFG_ATTACH, "attach", { "type", "UUID", "3", "4" } },
+       { LCFG_DETACH, "detach", { "1", "2", "3", "4" } },
+       { LCFG_SETUP, "setup", { "UUID", "node", "options", "failout" } },
+       { LCFG_CLEANUP, "cleanup", { "1", "2", "3", "4" } },
+       { LCFG_ADD_UUID, "add_uuid", { "node", "2", "3", "4" }  },
+       { LCFG_DEL_UUID, "del_uuid", { "1", "2", "3", "4" }  },
+       { LCFG_MOUNTOPT, "new_profile", { "name", "lov", "lmv", "4" }  },
+       { LCFG_DEL_MOUNTOPT, "del_mountopt", { "1", "2", "3", "4" } , },
+       { LCFG_SET_TIMEOUT, "set_timeout", { "parameter", "2", "3", "4" }  },
+       { LCFG_SET_UPCALL, "set_upcall", { "1", "2", "3", "4" }  },
+       { LCFG_ADD_CONN, "add_conn", { "node", "2", "3", "4" }  },
+       { LCFG_DEL_CONN, "del_conn", { "1", "2", "3", "4" }  },
+       { LCFG_LOV_ADD_OBD, "add_osc", { "ost", "index", "gen", "UUID" } },
+       { LCFG_LOV_DEL_OBD, "del_osc", { "1", "2", "3", "4" } },
+       { LCFG_PARAM, "set_param", { "parameter", "value", "3", "4" } },
+       { LCFG_MARKER, "marker", { "1", "2", "3", "4" } },
+       { LCFG_LOG_START, "log_start", { "1", "2", "3", "4" } },
+       { LCFG_LOG_END, "log_end", { "1", "2", "3", "4" } },
+       { LCFG_LOV_ADD_INA, "add_osc_inactive", { "1", "2", "3", "4" }  },
+       { LCFG_ADD_MDC, "add_mdc", { "mdt", "index", "gen", "UUID" } },
+       { LCFG_DEL_MDC, "del_mdc", { "1", "2", "3", "4" } },
+       { LCFG_SPTLRPC_CONF, "security", { "parameter", "2", "3", "4" } },
+       { LCFG_POOL_NEW, "new_pool", { "fsname", "pool", "3", "4" }  },
+       { LCFG_POOL_ADD, "add_pool", { "fsname", "pool", "ost", "4" } },
+       { LCFG_POOL_REM, "remove_pool", { "fsname", "pool", "ost", "4" } },
+       { LCFG_POOL_DEL, "del_pool", { "fsname", "pool", "3", "4" } },
+       { LCFG_SET_LDLM_TIMEOUT, "set_ldlm_timeout",
+         { "parameter", "2", "3", "4" } },
+       { 0, NULL, { NULL, NULL, NULL, NULL } }
+};
+
+static struct lcfg_type_data *lcfg_cmd2data(__u32 cmd)
+{
+       int i = 0;
+
+       while (lcfg_data_table[i].ltd_type != 0) {
+               if (lcfg_data_table[i].ltd_type == cmd)
+                       return &lcfg_data_table[i];
+               i++;
+       }
+       return NULL;
+}
+
+/**
+ * parse config record and output dump in supplied buffer.
+ * This is separated from class_config_dump_handler() to use
+ * for ioctl needs as well
+ *
+ * Sample Output:
+ * - { event: attach, device: lustrewt-clilov, type: lov, UUID:
+ *     lustrewt-clilov_UUID }
+ */
+int class_config_yaml_output(struct llog_rec_hdr *rec, char *buf, int size)
+{
+       struct lustre_cfg       *lcfg = (struct lustre_cfg *)(rec + 1);
+       char                    *ptr = buf;
+       char                    *end = buf + size;
+       int                      rc = 0, i;
+       struct lcfg_type_data   *ldata;
+
+       LASSERT(rec->lrh_type == OBD_CFG_REC);
+       rc = lustre_cfg_sanity_check(lcfg, rec->lrh_len);
+       if (rc < 0)
+               return rc;
+
+       ldata = lcfg_cmd2data(lcfg->lcfg_command);
+       if (ldata == NULL)
+               return -ENOTTY;
+
+       if (lcfg->lcfg_command == LCFG_MARKER)
+               return 0;
+
+       /* form YAML entity */
+       ptr += snprintf(ptr, end - ptr, "- { event: %s", ldata->ltd_name);
+
+       if (lcfg->lcfg_flags)
+               ptr += snprintf(ptr, end - ptr, ", flags: %#08x",
+                               lcfg->lcfg_flags);
+       if (lcfg->lcfg_num)
+               ptr += snprintf(ptr, end - ptr, ", num: %#08x",
+                               lcfg->lcfg_num);
+       if (lcfg->lcfg_nid)
+               ptr += snprintf(ptr, end - ptr, ", nid: %s("LPX64")",
+                               libcfs_nid2str(lcfg->lcfg_nid),
+                               lcfg->lcfg_nid);
+
+       if (LUSTRE_CFG_BUFLEN(lcfg, 0) > 0)
+               ptr += snprintf(ptr, end - ptr, ", device: %s",
+                               lustre_cfg_string(lcfg, 0));
+
+       for (i = 1; i < lcfg->lcfg_bufcount; i++) {
+               if (LUSTRE_CFG_BUFLEN(lcfg, i) > 0)
+                       ptr += snprintf(ptr, end - ptr, ", %s: %s",
+                                       ldata->ltd_bufs[i - 1],
+                                       lustre_cfg_string(lcfg, i));
+       }
+
+       ptr += snprintf(ptr, end - ptr, " }\n");
+       /* return consumed bytes */
+       rc = ptr - buf;
+       return rc;
+}
+
 /**
  * parse config record and output dump in supplied buffer.
  * This is separated from class_config_dump_handler() to use
@@ -1645,6 +1916,7 @@ int class_config_parse_rec(struct llog_rec_hdr *rec, char *buf, int size)
                                        lustre_cfg_string(lcfg, i));
                }
        }
+       ptr += snprintf(ptr, end - ptr, "\n");
        /* return consumed bytes */
        rc = ptr - buf;
        RETURN(rc);