Hi Vu

Ack with commants
See comments below [Lennart]

Thanks
Lennart

> -----Original Message-----
> From: Vu Minh Nguyen [mailto:vu.m.ngu...@dektech.com.au]
> Sent: den 21 februari 2017 10:34
> To: Lennart Lund <lennart.l...@ericsson.com>; mahesh.va...@oracle.com;
> Canh Van Truong <canh.v.tru...@dektech.com.au>
> Cc: opensaf-devel@lists.sourceforge.net
> Subject: [PATCH 1 of 3] log: add alternative destinations of log records
> [#2258]
> 
>  src/log/Makefile.am              |    8 +-
>  src/log/config/logsv_classes.xml |    7 +-
>  src/log/logd/lgs_config.cc       |  169 ++++++++-
>  src/log/logd/lgs_config.h        |    3 +-
>  src/log/logd/lgs_dest.cc         |  707
> +++++++++++++++++++++++++++++++++++++++
>  src/log/logd/lgs_dest.h          |  576 +++++++++++++++++++++++++++++++
>  src/log/logd/lgs_evt.cc          |   33 +
>  src/log/logd/lgs_imm.cc          |  202 +++++++++-
>  src/log/logd/lgs_main.cc         |    8 +
>  src/log/logd/lgs_mbcsv.cc        |  103 +++++-
>  src/log/logd/lgs_mbcsv.h         |    6 +-
>  src/log/logd/lgs_mbcsv_v5.cc     |   10 +
>  src/log/logd/lgs_mbcsv_v7.cc     |  177 +++++++++
>  src/log/logd/lgs_mbcsv_v7.h      |   67 +++
>  src/log/logd/lgs_stream.cc       |   60 +++-
>  src/log/logd/lgs_stream.h        |   16 +
>  src/log/logd/lgs_util.cc         |   63 +++
>  src/log/logd/lgs_util.h          |   11 +-
>  18 files changed, 2154 insertions(+), 72 deletions(-)
> 
> 
> Here are major info, detailed info will be added to PR doc soon.
> 1) Add attribute "saLogRecordDestination" to log stream.
> 2) Add Local socket destintion handler
> 3) Integrate into first increment made by Lennart
> 
> diff --git a/src/log/Makefile.am b/src/log/Makefile.am
> --- a/src/log/Makefile.am
> +++ b/src/log/Makefile.am
> @@ -72,9 +72,11 @@ noinst_HEADERS += \
>       src/log/logd/lgs_mbcsv_v2.h \
>       src/log/logd/lgs_mbcsv_v3.h \
>       src/log/logd/lgs_mbcsv_v5.h \
> +     src/log/logd/lgs_mbcsv_v7.h \
>       src/log/logd/lgs_recov.h \
>       src/log/logd/lgs_stream.h \
> -     src/log/logd/lgs_util.h
> +     src/log/logd/lgs_util.h \
> +     src/log/logd/lgs_dest.h
> 
>  bin_PROGRAMS += bin/saflogger
>  osaf_execbin_PROGRAMS += bin/osaflogd
> @@ -118,10 +120,12 @@ bin_osaflogd_SOURCES = \
>       src/log/logd/lgs_mbcsv_v2.cc \
>       src/log/logd/lgs_mbcsv_v3.cc \
>       src/log/logd/lgs_mbcsv_v5.cc \
> +     src/log/logd/lgs_mbcsv_v7.cc \
>       src/log/logd/lgs_mds.cc \
>       src/log/logd/lgs_recov.cc \
>       src/log/logd/lgs_stream.cc \
> -     src/log/logd/lgs_util.cc
> +     src/log/logd/lgs_util.cc \
> +     src/log/logd/lgs_dest.cc
> 
>  bin_osaflogd_LDADD = \
>       lib/libosaf_common.la \
> diff --git a/src/log/config/logsv_classes.xml
> b/src/log/config/logsv_classes.xml
> --- a/src/log/config/logsv_classes.xml
> +++ b/src/log/config/logsv_classes.xml
> @@ -147,12 +147,13 @@
>                       <category>SA_CONFIG</category>
>                       <flag>SA_WRITABLE</flag>
>               </attr>
> -             <attr>
> +             <attr>
>                       <name>saLogRecordDestination</name>
> -                     <type>SA_UINT32_T</type>
> +                     <type>SA_STRING_T</type>
>                       <category>SA_CONFIG</category>
>                       <flag>SA_WRITABLE</flag>
> -                        <flag>SA_MULTI_VALUE</flag>
> +                     <flag>SA_MULTI_VALUE</flag>
> +                     <flag>SA_NO_DUPLICATES</flag>
>               </attr>
>               <attr>
>                       <name>saLogStreamCreationTimestamp</name>
> diff --git a/src/log/logd/lgs_config.cc b/src/log/logd/lgs_config.cc
> --- a/src/log/logd/lgs_config.cc
> +++ b/src/log/logd/lgs_config.cc
> @@ -40,6 +40,7 @@
>  #include "osaf/immutil/immutil.h"
>  #include "log/logd/lgs_file.h"
>  #include "log/logd/lgs.h"
> +#include "log/logd/lgs_util.h"
[Lennart] This include should be removed, not needed
> 
>  static SaVersionT immVersion = { 'A', 2, 11 };
> 
> @@ -709,33 +710,162 @@ static int lgs_cfg_verify_log_filesys_co
>    return rc;
>  }
> 
> +//>
> +// Utility functions to validate destination configuration format
> +//<
> +// Typedef for shorten declaration
> +// This type could be used to hold set of strings with formats
> +// 1) {"name;type;value", etc.}
> +// 2) {"name", "type", "value"}
> +// 3) etc.
> +using VectorString = std::vector<std::string>;
> +//>
> +// Define the "token" possition in destination configuration.
> +// kName : use this index to get "name" token
> +// kType : use this index to get "type" token
> +// kValue: use this index to get "value" token
> +// kSize : this is the maximum size of the dest configuration vector.
> +//<
> +enum { kName = 0, kType, kValue, kSize };
> +// Tokens seperator
> +const char kSemicolon[] = ";";
> +
> +
> +// The format of destination must be one of followings:
> +// 1) "name;type;"
> +// 2) "name;type;value"
> +// So, in destination configuration, must have 02 semiconlons
> +// no more, no less.
> +//
> +bool is_right_destination_fmt(const VectorString& vdest) {
> +  int nl_cnt = 0;
> +  // Check each single destination
> +  for (const auto& it : vdest) {
> +    nl_cnt = std::count(it.begin(), it.end(), ';');
> +    if (nl_cnt != 2) {
> +      TRACE("%s wrong destination format", __func__);
> +      return false;
> +    }
> +  }
> +  return true;
> +}
> +
> +// Return false if "name" token is invalid
> +bool is_name_valid(const VectorString& vdest) {
> +  // Check each single destination
> +  for (const auto& it : vdest) {
> +    const VectorString sdes = logutil::Parser(it, kSemicolon);
> +    if (sdes[kName].length() == 0) {
> +      TRACE("%s wrong name format (name is empty value)", __func__);
> +      return false;
> +    }
> +    // invalid name if having special characters in
> +    if (logutil::isUnixName(sdes[kName]) == false) {
> +      TRACE("%s wrong name format (has special chars in)", __func__);
> +      return false;
> +    }
> +  }
> +  return true;
> +}
> +
> +class LocalSocketType;
> +
> +// Return false if "type" token is invalid
> +bool is_type_valid(const VectorString& vdest) {
> +  // Check each single destination
> +  for (const auto& it : vdest) {
> +    const VectorString sdes = logutil::Parser(it, kSemicolon);
> +    if (sdes[kType].length() == 0) {
> +      TRACE("%s wrong type format (type is empty value)", __func__);
> +      return false;
> +    }
> +    // Only support type = "localsocket"
> +    if (sdes[kType] != "localsocket") {
> +      TRACE("%s wrong type format (only support type = localsocket)",
> __func__);
> +      return false;
> +    }
> +  }
> +  return true;
> +}
> +
> +// Return true if no dulicated info, otherwise false.
> +bool is_no_config_duplicated(const VectorString& vdest,
> +                                 const VectorString& vdes2) {
> +  // Compare values of each pair of destinations.
> +  for (const auto& it : vdest) {
> +    const VectorString sdes = logutil::Parser(it, kSemicolon);
> +    for (const auto& it2 : vdes2) {
> +      if (it == it2) continue;
> +      const VectorString sdes2 = logutil::Parser(it2, kSemicolon);
> +      // Duplicate name
> +      if (sdes2[kName] == sdes[kName]) {
> +        TRACE("%s name is duplicated", __func__);
> +        return false;
> +      }
> +      // Ignore nildest
> +      if ((sdes[kValue].length() == 0) || (sdes2[kValue].length() == 0))
> +        continue;
> +      // Duplicate "value"
> +      if (sdes2[kValue] == sdes[kValue]) {
> +        TRACE("%s value is duplicated", __func__);
> +        return false;
> +      }
> +    }
> +  }
> +  return true;
> +}
> +
> +// Check whether there is duplicated "name" or "value"
> +// in provided @vdest and existing destinations
> +bool check_configuration_duplicated(const VectorString& vdest,
> +                                    SaImmAttrModificationTypeT type) {
> +  if (type == SA_IMM_ATTR_VALUES_DELETE) return true;
> +  // No checking duplicated if replacing to one value
> +  if (type == SA_IMM_ATTR_VALUES_REPLACE && vdest.size() < 2) return
> true;
> +  // Check in all replace values if any duplicated name/value
> +  if (type == SA_IMM_ATTR_VALUES_REPLACE)  {
> +    // Check whethere there is duplicated "name" or "value"
> +    // in list of provided destination configurations.
> +    return is_no_config_duplicated(vdest, vdest);
> +  } else {
> +    bool isNoDuplicated = true;
> +    // Check whethere there is duplicated "name" or "value"
> +    // in adding destination configurations and existing ones.
> +    // Firstly, check if any duplicate in added items.
> +    if (vdest.size() > 1) {
> +      isNoDuplicated = is_no_config_duplicated(vdest, vdest);
> +      if (isNoDuplicated == false) return false;
> +    }
> +    // Secondly, check if any duplicate in added items with existing list.
> +    return is_no_config_duplicated(
> +        vdest, lgs_conf.logRecordDestinationConfiguration);
> +  }
> +}
> +
>  /**
>   * Verify all values of log_record_destination_configuration
>   * Rules:
>   * - Empty string is Ok else
> - * - String shall have at least three fields separated by '\n'
> - * - First and second field cannot be empty
> + * - The destination configiration must be in right format
> + * - "name" token must be valid
> + * - "type" token must be valid
> + * - Must no duplicated info
>   *
>   * @param log_record_destination_configuration[in]
>   * @return -1 on error
>   */
>  int lgs_cfg_verify_log_record_destination_configuration(
> -  std::vector<std::string>& log_record_destination_configuration) {
> -  int rc = 0;
> -  TRACE_ENTER();
> -
> -  int nl_cnt = 0;
> -  for (auto& config : log_record_destination_configuration) {
> -    // Verify that the string contains at least 2 ';'
> -    nl_cnt = std::count(config.begin(), config.end(), ';');
> -    if (nl_cnt < 2) {
> -      rc = -1;
> -      break;
> -    }
> -  }
> -
> -  TRACE_LEAVE2("rc = %s", rc == -1? "Fail": "Pass");
> -  return rc;
> +    std::vector<std::string>& vdest,
> +    SaImmAttrModificationTypeT type) {
> +  // Allow deleting all destinations.
> +  if (vdest.size() == 0) return true;
> +  // It is important to keep the check in order
> +  bool result = (is_right_destination_fmt(vdest) &&
> +                 is_name_valid(vdest) &&
> +                 is_type_valid(vdest) &&
> +                 check_configuration_duplicated(vdest, type)
> +                 );
> +  return (result == true) ? (0) : (-1);
>  }
> 
> 
> @@ -846,7 +976,8 @@ static int verify_all_init() {
>    }
> 
>    if (lgs_cfg_verify_log_record_destination_configuration(
> -      lgs_conf.logRecordDestinationConfiguration) == -1) {
> +          lgs_conf.logRecordDestinationConfiguration,
> +          SA_IMM_ATTR_VALUES_ADD) == -1) {
>      lgs_conf.logRecordDestinationConfiguration.clear();
>      lgs_conf.logRecordDestinationConfiguration_cnfflag = LGS_CNF_DEF;
>      rc = -1;
> diff --git a/src/log/logd/lgs_config.h b/src/log/logd/lgs_config.h
> --- a/src/log/logd/lgs_config.h
> +++ b/src/log/logd/lgs_config.h
> @@ -304,7 +304,8 @@ int lgs_cfg_verify_mbox_limit(uint32_t h
>  int lgs_cfg_verify_max_application_streams(uint32_t max_app_streams);
>  int lgs_cfg_verify_file_io_timeout(uint32_t log_file_io_timeout);
>  int lgs_cfg_verify_log_record_destination_configuration(
> -                std::vector<std::string>& 
> log_record_destination_configuration);
> +    std::vector<std::string>& vdest,
> +    SaImmAttrModificationTypeT type);
>  /*
>   * Functions for updating some parameters. Used to support check-point
> before
>   * version 5
> diff --git a/src/log/logd/lgs_dest.cc b/src/log/logd/lgs_dest.cc
> new file mode 100644
> --- /dev/null
> +++ b/src/log/logd/lgs_dest.cc
> @@ -0,0 +1,707 @@
> +/*      -*- OpenSAF  -*-
> + *
> + * Copyright Ericsson AB 2017 - All Rights Reserved.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> MERCHANTABILITY
> + * or FITNESS FOR A PARTICULAR PURPOSE. This file and program are
> licensed
> + * under the GNU Lesser General Public License Version 2.1, February 1999.
> + * The complete license can be accessed from the following location:
> + * http://opensource.org/licenses/lgpl-license.php
> + * See the Copying file included with the OpenSAF distribution for full
> + * licensing terms.
> + *
> + * Author(s): Ericsson AB
> + *
> + */
> +
> +#include "log/logd/lgs_dest.h"
> +
> +#include <algorithm>
> +
> +#include "log/logd/lgs_util.h"
> +#include "log/logd/lgs_config.h"
> +#include "log/saf/saLog.h"
> +#include "base/logtrace.h"
> +// RFC5424 syslog msg format
> +// =========================
> +// 1) HEADER
> +// PRI             = 16*8 + SaLogSeverityT
> +// VERSION         = 1
> +// TIMESTAMP       = When a log record was actually logged
> +// E.g: 2016-12-02T11:34:15.944364+01:00
> +// HOSTNAME        = FQDN
> +// APP-NAME        = App name who generated log record
> +// PROCID          = NILVALUE
> +// MSGID           = A hash generated from log stream DN
> +// STRUCTURED-DATA = NILVALUE
> +//
> +// 2) MSG BODY
> +// MSG             = The log record written to local file
> +//
> +
> +static const char kDelimeter[] = ";";
> +
> +//========================================================
> ======================
> +// LocalSocketHandler class
> +//========================================================
> ======================
> +LocalSocketHandler::LocalSocketHandler(const char* socket_name)
> +    : sock_path_{socket_name}
> +    , sock_{socket_name}
> +    , status_{DestinationStatus::kInactive} {
> +      // Open the unix socket & and flush the destination status to @status_
> +      Open();
> +};
> +
> +void LocalSocketHandler::FlushStatus() {
> +  if (sock_.fd() >= 0) {
> +    status_ = DestinationStatus::kActive;
> +  } else {
> +    status_ = DestinationStatus::kFailed;
> +  }
> +}
> +
> +DestinationStatus LocalSocketHandler::GetSockStatus() {
> +  Open();
> +  return status_;
> +}
> +
> +void LocalSocketHandler::Open() {
> +  FlushStatus();
> +}
> +
> +void LocalSocketHandler::FormRfc5424(
> +    const DestinationHandler::RecordInfo& msg,
> +    RfcBuffer* buf) {
> +  base::LogMessage::Severity sev{Sev(msg.severity)};
> +  base::LogMessage::HostName hostname{msg.origin};
> +  base::LogMessage::ProcId procid{""};
> +  base::LogMessage::AppName appname{msg.app_name};
> +
> +  base::LogMessage::Write(base::LogMessage::Facility::kLocal0,
> +                          sev,
> +                          msg.time,
> +                          hostname,
> +                          appname,
> +                          procid,
> +                          base::LogMessage::MsgId{msg.msgid},
> +                          {},
> +                          std::string{msg.log_record},
> +                          buf);
> +}
> +
> +ErrCode LocalSocketHandler::Send(const DestinationHandler::RecordInfo&
> msg) {
> +  TRACE_ENTER();
> +  RfcBuffer buffer;
> +
> +  FormRfc5424(msg, &buffer);
> +
> +  ssize_t length = buffer.size();
> +  ssize_t len = sock_.Send(buffer.data(), length);
> +  // Resend as probably receiver has just been restarted.
> +  // Retry only once, if not succesfully, leave it to upper layer decision.
> +  if (len != length) {
> +    LOG_NO("Failed to send log record to socket destination. Resent.");
> +    len = sock_.Send(buffer.data(), length);
> +  }
> +
> +  FlushStatus();
> +
> +  TRACE_LEAVE();
> +  return ((len != length) ? (ErrCode::kErr) : (ErrCode::kOk));
> +}
> +
> +void LocalSocketHandler::Close() {
> +  // Do nothing
> +}
> +
> +// Convert AIS sev @a sev to Severity class
> +base::LogMessage::Severity LocalSocketHandler::Sev(uint16_t sev) {
> +  switch (sev) {
> +    case SA_LOG_SEV_EMERGENCY:
> +      return base::LogMessage::Severity::kEmerg;
> +    case SA_LOG_SEV_ALERT:
> +      return base::LogMessage::Severity::kAlert;
> +    case SA_LOG_SEV_CRITICAL:
> +      return base::LogMessage::Severity::kCrit;
> +    case SA_LOG_SEV_ERROR:
> +      return base::LogMessage::Severity::kErr;
> +    case SA_LOG_SEV_WARNING:
> +      return base::LogMessage::Severity::kWarning;
> +    case SA_LOG_SEV_NOTICE:
> +      return base::LogMessage::Severity::kNotice;
> +    case SA_LOG_SEV_INFO:
> +      return base::LogMessage::Severity::kInfo;
> +    default:
> +      LOG_ER("Unknown severity level (%d)", (int)sev);
> +      return base::LogMessage::Severity::kErr;
> +  }
> +}
> +
> +LocalSocketHandler::~LocalSocketHandler() {
> +  // Destination is deleted
> +  status_ = DestinationStatus::kInactive;
> +  // The parent class will do closing the connection
> +  // and other resources.
> +}
> +
> +//========================================================
> ======================
> +// LocalSocketType class
> +//========================================================
> ======================
> +LocalSocketType LocalSocketType::me_;
> +const char LocalSocketType::name_[] = "localsocket";
> +
> +LocalSocketType::LocalSocketType() {}
> +
> +LocalSocketType::~LocalSocketType() {
> +  // Close all connections & stop the thread
> +}
> +
> +const std::string LocalSocketType::DestStatusToStr(DestinationStatus
> status) {
> +  switch (status) {
> +    // Destination configured and is being connected to destination
> +    case DestinationStatus::kActive: return "active";
> +      // Destination configured and is being disconnected to destination
> +    case DestinationStatus::kFailed: return "failed";
> +      // Destination is not configured (nildest)
> +    case DestinationStatus::kInactive: return "inactive";
> +    default: return "unknown";
> +  }
> +}
> +
> +const std::string LocalSocketType::GetDestinationStatus(
> +    const std::string& name) {
> +  if (FindName(name) == false) return std::string{"inactive"};
> +  const LocalSocketHandler* sk = nametype_map_[name];
> +  if (sk == nullptr) return std::string{"inactive"};
> +  return DestStatusToStr(nametype_map_[name]->GetSockStatus());
> +}
> +
> +const VectorString LocalSocketType::GetAllDestStatus() {
> +  VectorString output{};
> +  if (me_.nametype_map_.size() == 0) return output;
> +  for (const auto& it : me_.nametype_map_) {
> +    std::string status = it.first + "," + me_.GetDestinationStatus(it.first);
> +    output.push_back(status);
> +  }
> +  return output;
> +}
> +
> +bool LocalSocketType::FindName(const std::string& name) const {
> +  return ((nametype_map_.find(name) == nametype_map_.end()) ?
> +          (false) : (true));
> +}
> +
> +LocalSocketHandler* LocalSocketType::GetDestHandle(const std::string&
> name) {
> +  if (FindName(name) == false) return nullptr;
> +  return nametype_map_[name];
> +}
> +
> +const char* LocalSocketType::GetCfgValue(const std::string& name) {
> +  if (FindName(name) == false) return nullptr;
> +  LocalSocketHandler* sk = nametype_map_[name];
> +  return (sk == nullptr) ? nullptr : nametype_map_[name]-
> >sock_path_.c_str();
> +}
> +
> +ErrCode LocalSocketType::HandleRecordMsg(
> +    const DestinationHandler::RecordMsg& msg) {
> +  ErrCode ret = ErrCode::kOk;
> +  // No destination configuration with this destination name
> +  if (FindName(std::string{msg.name}) == false) {
> +    LOG_WA("No such destination name(%s) configured", msg.name);
> +    ret = ErrCode::kDrop;
> +    return ret;
> +  }
> +
> +  // NIL destination - only have "name" and "type", but no "value".
> +  if (GetDestHandle(std::string{msg.name}) == nullptr) {
> +    LOG_NO("The destination name(%s) not yet configured", msg.name);
> +    ret = ErrCode::kDrop;
> +    return ret;
> +  }
> +
> +  // Send to destination end.
> +  osafassert(nametype_map_[std::string{msg.name}] != nullptr);
> +  return nametype_map_[std::string{msg.name}]->Send(msg.rec);
> +}
> +
> +ErrCode LocalSocketType::HandleCfgMsg(
> +    const DestinationHandler::CfgDestMsg& msg) {
> +  TRACE_ENTER();
> +  ErrCode ret = ErrCode::kOk;
> +  const std::string name{msg.name};
> +  const char* newcfg = msg.value;
> +
> +  // Request to create destination handler
> +  if ((newcfg != nullptr) && (strlen(newcfg) > 0)) {
> +    // Close and free previous @LocakSkDestHanlde instance if existing.
> +    if (GetDestHandle(name) != nullptr) delete nametype_map_[name];
> +    // Create a new destination handler @LocalSocketHandler for @name
> +    nametype_map_[name] = new LocalSocketHandler(newcfg);
> +    ret = (nametype_map_[name] == nullptr) ? ErrCode::kNoMem : ret;
> +    return ret;
> +  }
> +
> +  // Request to delete the existing destination handler (NILDEST case)
> +  if (GetDestHandle(name) != nullptr) {
> +    delete nametype_map_[name];
> +    nametype_map_[name] = nullptr;
> +  }
> +
> +  TRACE_LEAVE();
> +  return ret;
> +}
> +
> +ErrCode LocalSocketType::HandleDelCfgMsg(
> +    const DestinationHandler::DelDestMsg& msg) {
> +  TRACE_ENTER();
> +  ErrCode ret = ErrCode::kOk;
> +
> +  const std::string name{msg.name};
> +  // Close and free @LocakSkDestHanlde instance if existing.
> +  if (GetDestHandle(name) != nullptr) delete nametype_map_[name];
> +  // Erase from the map
> +  nametype_map_.erase(name);
> +
> +  TRACE_LEAVE();
> +  return ret;
> +}
> +
> +ErrCode LocalSocketType::ProcessMsg(
> +    const DestinationHandler::HandleMsg& msg) {
> +  TRACE_ENTER();
> +  ErrCode ret = ErrCode::kOk;
> +  static bool cfgsent = false;
> +  // The proper sequence should be:
> +  // 1) kCfgDest   : for configure destination handlers
> +  // 2) kSendRecord: write log record to destination
> +  // 3) kDelDest   : delete specific destinations
> +  // 4) kNoDest    : delete all destinations
> +  // Msg could be drop if the sequence is not correct.
> +  switch (msg.type) {
> +    case DestinationHandler::MsgType::kSendRecord: {
> +      // No destination yet configured. Drop the message.
> +      TRACE("%s %s", __func__, "RecordMsg msg");
> +      const DestinationHandler::RecordMsg& rmsg = msg.info.rec;
> +
> +      // No destination has been configured so far.
> +      if (cfgsent == false) {
> +        LOG_WA("Destination handler not yet initialized. Drop msg.");
> +        ret = ErrCode::kDrop;
> +      } else {
> +        ret = HandleRecordMsg(rmsg);
> +      }
> +      break;
> +    }
> +
> +    case DestinationHandler::MsgType::kCfgDest: {
> +      const DestinationHandler::CfgDestMsg& cmsg = msg.info.cfg;
> +      TRACE("%s %s", __func__, "CfgDestMsg msg");
> +      ret = HandleCfgMsg(cmsg);
> +      // Notice there at leat one destination configured.
> +      if (ret == ErrCode::kOk) cfgsent = true;
> +      break;
> +    }
> +
> +    case DestinationHandler::MsgType::kDelDest: {
> +      const DestinationHandler::DelDestMsg& dmsg = msg.info.del;
> +      TRACE("%s %s", __func__, "DelDestMsg msg");
> +      // No destination yet configured. Drop the message.
> +      if (cfgsent == false) {
> +        LOG_NO("No destination configured yet. Drop msg.");
> +        return ErrCode::kDrop;
> +      }
> +      ret = HandleDelCfgMsg(dmsg);
> +      break;
> +    }
> +
> +    case DestinationHandler::MsgType::kNoDest: {
> +      LOG_NO("%s %s", __func__, "DelAllCfgDest msg");
> +      if (cfgsent == false) {
> +        LOG_NO("No destination configured yet");
> +        return ErrCode::kDrop;
> +      }
> +
> +      for (auto& it : nametype_map_) {
> +        if (it.second != nullptr) delete it.second;
> +        nametype_map_.erase(it.first);
> +      }
> +      cfgsent = false;
> +      break;
> +    }
> +
> +    default:
> +      LOG_ER("Unknown dispatch message type %d",
> static_cast<int>(msg.type));
> +      ret = ErrCode::kInvalid;
> +  }
> +
> +  TRACE_LEAVE();
> +  return ret;
> +}
> +
> +//========================================================
> ======================
> +// DestinationHandler class
> +//========================================================
> ======================
> +DestinationHandler DestinationHandler::me_;
> +
> +// Only support "kUnix" so far
> +DestinationHandler::DestType DestinationHandler::GetDestType(
> +    const std::string& type) {
> +  if (type == LocalSocketType::Type()) {
> +    return DestType::kUnix;
> +  } else {
> +    LOG_NO("Unknown destination type %s", type.c_str());
> +    return DestType::kUnknown;
> +  }
> +}
> +
> +std::string DestinationHandler::GenerateMsgId(
> +    const std::string& dn, bool isRtStream) {
> +  const std::string parent = ",safApp=safLogService";
> +  std::string msgid{""};
> +  size_t posa = 0, posb = 0;
> +
> +  // Extract stream name from stream DN.
> +  posa = dn.find('=');
> +  if ((posb = dn.find(',')) == std::string::npos) {
> +    posb = dn.length();
> +  }
> +  std::string sname = dn.substr(posa + 1, posb - posa - 1);
> +
> +  //>
> +  // Rules to generate msgid string:
> +  // 1) Use stream name (e.g: saLogSystem) + 'C'/'R' if it is not over 31 
> chars
> +  //    and the stream DN contains the @parent ",safApp=safLogService";
> +  //    Note: 'C' means configuration stream, 'R' means runtime stream.
> +  //           Why 31? It is due to MSGID field length limitation
> +  //           (max is 32 chars length).
> +  // 2) Otherwise, generate a hash number from stream DN
> +  //    if stream name is over 31 chars.
> +  //<
> +  if (sname.length() < 32 && dn.find(parent) != std::string::npos) {
> +    msgid = ((isRtStream == true) ? std::string{sname + 'R'} :
> +             std::string{sname + 'C'});
> +  } else {
> +    msgid = std::to_string(std::hash<std::string>{}(dn));
> +  }
> +
> +  return msgid;
> +}
> +
> +DestinationHandler::DestType DestinationHandler::ToDestType(
> +    const std::string& name) {
> +  if (nametype_map_.find(name) == nametype_map_.end())
> +    return DestType::kUnknown;
> +  return nametype_map_[name];
> +}
> +
> +void DestinationHandler::UpdateDestTypeDb(
> +    const DestinationHandler::HandleMsg& msg) {
> +  MsgType mtype = msg.type;
> +
> +  switch (mtype) {
> +    case MsgType::kCfgDest: {
> +      const CfgDestMsg& cdest = msg.info.cfg;
> +      TRACE("%s kCfgDest (%s)", __func__, cdest.name);
> +      nametype_map_[std::string{cdest.name}] =
> +          GetDestType(std::string{cdest.type});
> +      break;
> +    }
> +
> +    case MsgType::kDelDest: {
> +      const DelDestMsg& deldest = msg.info.del;
> +      TRACE("%s kDelDest (%s)", __func__, deldest.name);
> +      nametype_map_.erase(std::string{deldest.name});
> +      break;
> +    }
> +
> +    case MsgType::kNoDest: {
> +      TRACE("%s kNoDest", __func__);
> +      for (auto& it : nametype_map_) nametype_map_.erase(it.first);
> +      break;
> +    }
> +
> +    default:
> +      LOG_WA("Unknown Dispatch message type (%d)",
> static_cast<int>(mtype));
> +      break;
> +  }
> +}
> +
> +ErrCode DestinationHandler::DispatchTo(
> +    const DestinationHandler::HandleMsg& msg,
> +    const std::string& name) {
> +  ErrCode rc = ErrCode::kOk;
> +  MsgType mtype = msg.type;
> +  // All destinations has been deleted. Don't care of destination types.
> +  // Delete all existing connections of all types.
> +  if (mtype == MsgType::kNoDest) {
> +    rc = LocalSocketType::Instance().ProcessMsg(msg);
> +    // Update the destination names from @DestTypeDb
> +    UpdateDestTypeDb(msg);
> +    UpdateRtDestStatus();
> +    return rc;
> +  }
> +
> +  // Update the @DestTypeDb first before processing the
> DestinationHandler msg.
> +  // Otherwise, finding destination @type will result nothing.
> +  // Then, the msg will be drop as no destination type found.
> +  // But with @MsgType::kDelDest, updating should be last
> +  // after fowarding the msg to @LocalSocketType.
> +  // Otherwise, resource might be leaked (e.g: connection is not closed)
> +  if (mtype == MsgType::kCfgDest) {
> +    UpdateDestTypeDb(msg);
> +  }
> +
> +  DestType destType = ToDestType(name);
> +  switch (destType) {
> +    // Pass the DestinationHandler msg to Local Unix Domain
> +    // socket destination type for further processing.
> +    case DestType::kUnix:
> +      rc = LocalSocketType::Instance().ProcessMsg(msg);
> +      break;
> +
> +    default:
> +      LOG_ER("Not support this destination type(%d), or no dest configured!",
> +             static_cast<int>(destType));
> +      rc = ErrCode::kDrop;
> +      break;
> +  }
> +
> +  // Delete destination names from @DestTypeDb
> +  if (mtype == MsgType::kDelDest) {
> +    UpdateDestTypeDb(msg);
> +  }
> +
> +  UpdateRtDestStatus();
> +  return rc;
> +}
> +
> +// Update the `logRecordDestinationStatus` here whenever
> +// getting CfgDestination or WriteToDestination (?)
> +void DestinationHandler::UpdateRtDestStatus() {
> +  VectorString vstatus = GetDestinationStatus();
> +  lgs_config_chg_t config_data = {NULL, 0};
> +  lgs_cfgupd_mutival_replace(LOG_RECORD_DESTINATION_STATUS,
> vstatus, &config_data);
> +  // Update configuration data store, no need to checkpoint the status to
> stb.
> +  int ret = lgs_cfg_update(&config_data);
> +  if (ret == -1) {
> +    LOG_WA("%s lgs_cfg_update Fail", __func__);
> +  }
> +}
> +
> +void DestinationHandler::FormCfgDestMsg(
> +    const std::string& dest, CfgDestMsg* msg) {
> +  osafassert(msg != nullptr);
> +  const VectorString tmp = logutil::Parser(dest, kDelimeter);
> +  strncpy(msg->name, tmp[kName].c_str(), kMaxChar);
> +  if (tmp.size() > 1) {
> +    strncpy(msg->type, tmp[kType].c_str(), kMaxChar);
> +  }
> +  if (tmp.size() == kSize)
> +    strncpy(msg->value, tmp[kValue].c_str(), kMaxChar);
> +}
> +
> +void DestinationHandler::FormDelDestMsg(
> +    const std::string& dest, DelDestMsg* msg) {
> +  osafassert(msg != nullptr);
> +  const VectorString tmp = logutil::Parser(dest, kDelimeter);
> +  strncpy(msg->name, tmp[kName].c_str(), kMaxChar);
> +}
> +
> +bool DestinationHandler::VectorFind(
> +    const VectorString& vec, const std::string name) {
> +  return std::find(vec.begin(), vec.end(), name) != vec.end();
> +}
> +
> +ErrCode DestinationHandler::AddDestConfig(const VectorString& vdest) {
> +  TRACE_ENTER();
> +  ErrCode ret = ErrCode::kOk;
> +  CfgDestMsg cmsg;
> +  // Go thought all destinations and configure one by one
> +  for (unsigned i = 0; i < vdest.size(); i++) {
> +    HandleMsg msg{};
> +    FormCfgDestMsg(vdest[i], &cmsg);
> +    msg.type = MsgType::kCfgDest;
> +    memcpy(&msg.info.cfg, &cmsg, sizeof(cmsg));
> +    ret = DispatchTo(msg, cmsg.name);
> +    allcfgdests_map_.push_back(vdest[i]);
> +  }
> +  TRACE_LEAVE();
> +  return ret;
> +}
> +
> +ErrCode DestinationHandler::DelDestConfig(const VectorString& vdest) {
> +  TRACE_ENTER();
> +  ErrCode ret = ErrCode::kOk;
> +  DelDestMsg dmsg;
> +  // Go thought all destinations and configure one by one
> +  for (unsigned i = 0; i < vdest.size(); i++) {
> +    if (VectorFind(allcfgdests_map_, vdest[i]) == false) {
> +      LOG_NO("Not have such dest configuration (%s)", vdest[i].c_str());
> +      ret = ErrCode::kDrop;
> +      continue;
> +    }
> +
> +    HandleMsg msg{};
> +    FormDelDestMsg(vdest[i], &dmsg);
> +    msg.type = MsgType::kDelDest;
> +    memcpy(&msg.info.del, &dmsg, sizeof(dmsg));
> +    ret = DispatchTo(msg, dmsg.name);
> +    // Remove deleted destination from internal database
> +    allcfgdests_map_.erase(std::remove(allcfgdests_map_.begin(),
> +                                       allcfgdests_map_.end(), vdest[i]),
> +                           allcfgdests_map_.end());
> +  }
> +  TRACE_LEAVE();
> +  return ret;
> +}
> +
> +ErrCode DestinationHandler::DelAllCfgDest() {
> +  TRACE_ENTER();
> +  ErrCode ret = ErrCode::kOk;
> +  // Don't care destination name
> +  const std::string name{""};
> +  HandleMsg msg{};
> +  msg.type = MsgType::kNoDest;
> +  ret = DispatchTo(msg, name);
> +  allcfgdests_map_.clear();
> +  TRACE_LEAVE();
> +  return ret;
> +}
> +
> +ErrCode DestinationHandler::ProcessCfgChange(
> +    const VectorString& vdest, ModifyType type) {
> +  ErrCode ret = ErrCode::kOk;
> +  TRACE_ENTER();
> +
> +  // Case #1: perform all destination delete, but no existing at all.
> +  if ((vdest.size() == 0) && (allcfgdests_map_.size() == 0)) {
> +    return ErrCode::kOk;
> +  }
> +
> +  // Case #2: Delete all existing destinations
> +  if ((vdest.size() == 0) && (allcfgdests_map_.size() != 0)) {
> +    return DelAllCfgDest();
> +  }
> +
> +  // Case #3: Delete destination configurations while no dest cfg yet
> +  if ((type == ModifyType::kDelete) &&
> +      (vdest.size() != 0) &&
> +      (allcfgdests_map_.size() == 0)) {
> +    return ErrCode::kDrop;
> +  }
> +
> +  // Case #4: Delete all destination configurations
> +  if ((type == ModifyType::kDelete) &&
> +      (vdest.size() == 0) &&
> +      (allcfgdests_map_.size() != 0)) {
> +    return DelAllCfgDest();
> +  }
> +
> +  // Case #5: Delete all destinations but no one existing.
> +  if ((type == ModifyType::kDelete) &&
> +      (vdest.size() == 0) &&
> +      (allcfgdests_map_.size() == 0)) {
> +    return ErrCode::kDrop;
> +  }
> +
> +  switch (type) {
> +    case ModifyType::kAdd:
> +      // Add new destinations to existing destinations.
> +      ret = AddDestConfig(vdest);
> +      break;
> +
> +    case ModifyType::kReplace:
> +      // Step #1: Delete all existing destinations if any.
> +      if (nametype_map_.size() != 0) {
> +        DelAllCfgDest();
> +      }
> +      // Step #2: Create new destinations
> +      ret = AddDestConfig(vdest);
> +      break;
> +
> +    case ModifyType::kDelete:
> +      ret = DelDestConfig(vdest);
> +      break;
> +
> +    default:
> +      LOG_ER("Unknown modify type (%d)", static_cast<int>(type));
> +      ret = ErrCode::kErr;
> +      break;
> +  }
> +
> +  TRACE_LEAVE2("ret = %d", static_cast<int>(ret));
> +  return ret;
> +}
> +
> +ErrCode DestinationHandler::ProcessWriteReq(
> +    const RecordInfo& info,
> +    const std::string& name) {
> +  HandleMsg msg{};
> +  RecordMsg record;
> +
> +  msg.type = MsgType::kSendRecord;
> +  strncpy(record.name, name.c_str(), kMaxChar);
> +  memcpy(&record.rec, &info, sizeof(RecordInfo));
> +  memcpy(&msg.info.rec, &record, sizeof(RecordMsg));
> +  return DispatchTo(msg, name);
> +}
> +
> +VectorString DestinationHandler::GetDestinationStatus() {
> +  // For now, only support destination type "unix"
> +  // If more than one destination type is support,
> +  // should go though each, and append the result to output vector.
> +  return LocalSocketType::Instance().GetAllDestStatus();
> +}
> +
> +//========================================================
> ======================
> +// Exposed interfaces to outer world
> +//========================================================
> ======================
> +bool CfgDestination(const VectorString& vdest, ModifyType type) {
> +  return (DestinationHandler::Instance().ProcessCfgChange(vdest, type) ==
> +          ErrCode::kOk);
> +}
> +
> +bool WriteToDestination(const RecordData& data,
> +                        const VectorString& destnames) {
> +  osafassert(data.name != nullptr && data.logrec != nullptr);
> +  osafassert(data.hostname != nullptr && data.networkname != nullptr);
> +
> +  if (destnames.size() == 0) {
> +    TRACE("%s No destination goes with this stream (%s)", __func__,
> data.name);
> +    return true;
> +  }
> +
> +  bool ret = true;
> +  DestinationHandler::RecordInfo info;
> +  std::string networkname = (std::string{data.networkname}.length() > 0) ?
> +                            ("." + std::string {data.networkname}) : "";
> +
> +  // Origin is FQDN = <hostname>[.<networkname>]
> +  // hostname = where the log record comes from (not active node)
> +  const std::string origin = std::string {data.hostname} + networkname;
> +  const std::string msgid = DestinationHandler::Instance().GenerateMsgId(
> +      data.name, data.isRtStream);
> +
> +  info.msgid      = msgid.c_str();
> +  info.log_record = data.logrec;
> +  info.record_id  = data.recordId;
> +  info.stream_dn  = data.name;
> +  info.app_name   = data.appname;
> +  info.severity   = data.sev;
> +  info.time       = data.time;
> +  info.origin     = origin.c_str();
> +
> +  // Go thought all destination names and send to the destination
> +  // that go with that destination name
> +  for (const auto& destname : destnames) {
> +    ret = (DestinationHandler::Instance().ProcessWriteReq(info, destname)
> +           == ErrCode::kOk);
> +  }
> +
> +  return ret;
> +}
> +
> +VectorString GetDestinationStatus() {
> +  return DestinationHandler::Instance().GetDestinationStatus();
> +}
> diff --git a/src/log/logd/lgs_dest.h b/src/log/logd/lgs_dest.h
> new file mode 100644
> --- /dev/null
> +++ b/src/log/logd/lgs_dest.h
> @@ -0,0 +1,576 @@
> +/*      -*- OpenSAF  -*-
> + *
> + * Copyright Ericsson AB 2017 - All Rights Reserved.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> MERCHANTABILITY
> + * or FITNESS FOR A PARTICULAR PURPOSE. This file and program are
> licensed
> + * under the GNU Lesser General Public License Version 2.1, February 1999.
> + * The complete license can be accessed from the following location:
> + * http://opensource.org/licenses/lgpl-license.php
> + * See the Copying file included with the OpenSAF distribution for full
> + * licensing terms.
> + *
> + * Author(s): Ericsson AB
> + *
> + */
> +
> +#ifndef SRC_LOG_LOGD_LGS_DEST_H_
> +#define SRC_LOG_LOGD_LGS_DEST_H_
> +
> +#include <sys/time.h>
> +#include <limits.h>
> +#include <string>
> +#include <map>
> +#include <vector>
> +#include <utility>
> +#include <atomic>
> +
> +#include "base/log_message.h"
> +#include "base/macros.h"
> +#include "base/ncsgl_defs.h"
> +#include "base/osaf_utility.h"
> +#include "base/time.h"
> +#include "base/unix_client_socket.h"
> +
> +// Status of connection to destination
> +enum DestinationStatus {
> +  // Destination is configured and is being connected to
> +  kActive = 0,
> +  // Destination is configured but not able to connected to
> +  kFailed = 1,
> +  // Configuration only have "name" and "type", but not have
> +  // configuration value (NILDEST).
> +  kInactive = 255
> +};
> +
> +// This error code is introduced in alternative destination ticket #2258.
> +// and for now, only be used internally in lgs_dest files.
> +enum ErrCode {
> +  // No error happens
> +  kOk = 0,
> +  // Error happens in general
> +  kErr,
> +  // Passing invalid parameters
> +  kInvalid,
> +  // Not able to serve the request as the requester is being busy
> +  kBusy,
> +  // The request is timeout
> +  kTimeOut,
> +  // No memory
> +  kNoMem,
> +  // The request/msg is drop as the precondition is not met
> +  kDrop
> +};
> +
> +// Type of modification on multiple attribute values.
> +// Introduce this own enum as (xvunguy) don't want to
> +// have dependent to IMM header file just because we need IMM
> modification type.
[Lennart] I miss exact definition of what each modify type is doing. Is it the 
same as IMM multi value modification type?
i.e.
Add: Add more values. Existing values are not affected
Delete: Value(s) that exactly match an existing value is deleted. Other values 
are not affected
Replace: All existing values are replaced by given values
> +enum ModifyType { kAdd = 1, kDelete = 2, kReplace = 3 };
> +
> +//>
> +// Define the "token" possition in destination configuration.
> +// Each destination configiration has format "name;type;value".
> +// After parsing, the output will put to the vector of strings
> +// with format {"name", "type", "value"}.
> +// kName : use this index to get "name"
> +// kType : use this index to get "kType"
> +// kValue: use this index to get "kValue"
> +// kSize : this is the maximum size of the dest configuration vector.
> +//<
> +enum { kName = 0, kType, kValue, kSize };
> +
> +//========================================================
> ======================
> +// Two interfaces are published to outside world
> +//========================================================
> ======================
> +
> +// Typedef for shorten declaration
> +// This type could be used to hold set of strings with formats
> +// 1) {"name;type;value", etc.}
> +// 2) {"name", "type", "value"}
> +// 3) etc.
> +using VectorString = std::vector<std::string>;
> +
> +//>
> +// @CfgDestination - Configure Destinations.
> +// @input vdest: a vector of destination configurations.
> +// @input type : modification type on destination configuration attribute.
> +//               It has equivalent value to @SaImmAttrModificationTypeT.
> +// @return: true if everything is OK, otherwise false.
> +//
> +// Call this function in CCB modified apply callback context,
> +// when changes on configuration destination has been verified.
> +// @type could be ModifyType::kAdd, ModifyType::kReplace, or
> ModifyType::kDelete
> +//
> +// E.g:
> +// 1) Case #1: create 02 new destinations (@type = ModifyType::kAdd)
> +//    Then, @vdest should contain data {"a;b;c", "d;e;f"}
> +// 2) Case #2: modify one destinations. E.g: dest name @d -> @d1
> +//    Then, @type = ModifyType::kReplace and @vdest contains data
> {"d1;e;f"}
> +// 3) Case #3: delete one destination. Eg: dest name @a
> +//    Then, @type = ModifyType::kDelete and @vdest should contains
> {"a;b;c"}
> +// 4) Case #4: delete all destinations
> +//    Then, @type = ModifyType::kDelete and @vdest contains empty vector
> {}
> +//
> +// The function will parse the provided data @vdest, getting
> +// destination @name, destination @type, @destination value if any.
> +// Then form the right handle msg, and forward it to right destination.
> +//
> +// This function is blocking. The caller will be blocked until
> +// the request is done.
> +//<
> +bool CfgDestination(const VectorString& vdest, ModifyType type);
> +
> +//>
> +// Carry necessary information to form RFC5424 syslog message.
> +// Aim to reduce number of parameter passing to @WriteToDestination
> +// @name       : log stream distinguish name
> +// @logrec     : log record that written to local log file
> +// @hostname   : log record originator
> +// @networkname: network name
> +// @recordId   : record id of @logrec
> +// @sev        : severity of log record @logrec
> +// @time       : Same timestamp as in the log record @logrec
> +//<
> +struct RecordData {
> +  const char* name;
> +  const char* logrec;
> +  const char* hostname;
> +  const char* networkname;
> +  const char* appname;
> +  bool isRtStream;
> +  uint32_t recordId;
> +  uint16_t sev;
> +  timespec time;
> +
> +  RecordData() : name{nullptr}, logrec{nullptr}
> +      , hostname{nullptr}
> +      , networkname{nullptr}
> +      , appname{nullptr}
> +      , isRtStream{false}
> +      , recordId{0}
> +      , sev{0} {
> +        time = base::ReadRealtimeClock();
> +      }
> +};
> +
> +//>
> +// @WriteToDestination - Write @data To Destinations
> +// @input:
> +//   @data     : Carry necessary infos including log record.
> +//   @destnames: Destination names wants to send log record to.
> +// @return: true if successfully write to destinations. Oherwise, false.
> +//
> +// This function is blocking. The caller will be blocked until
> +// the request is done.
> +//<
> +bool WriteToDestination(const RecordData& data,
> +                        const VectorString& destnames);
> +
> +//>
> +// @GetDestinationStatus - Get all destination status
> +// @input : none
> +// @output: vector of destination status with format
> +//          {"name,status", "name1,status", etc.}
> +//<
> +VectorString GetDestinationStatus();
> +
> +//>
> +// @DestinationHandler class: Dispatch msg to right destination
> +// All msgs, if want to come to destination handlers,
> +// must go though the DestinationHandler via
> +// the interface DestinationHandler::DispachTo()
> +//
> +// Three kind of DestinationHandler messages supported.
> +// 1) Msg contains destination configurations such as
> +//    destination name, destination type and info (local socket path).
> +//
> +// 2) Msg contains log record which is going to write to destination.
> +//    Beside log record, msg may contain other info such as
> +//    the stream name(DN) it writting to, log record severity,
> +//    log record originator, log record id, or timestamp.
> +//
> +// 3) Msg contains destinations that is going to be deleted.
> +//    This message may have list of deleted destination names.
> +//
> +// The proper order of DestinationHandler messages should be:
> +// (1) -> (2) -> (3) or (1) -> (3).
> +//
> +// So, if msg contains log record comes(2) to the DestinationHandler first
> +// somehow (this probably due to an coding hole, that does not
> +// have a good check before sending msg to Dispacher) before
> +// the configurating destination msg(1), the DestinationHandler will drop
> +// the message, and log info to syslog.
> +// The same to the case if deleting destination msg(3) comes first
> +// before configuring destination(1), that msg(3) will be drop
> +// and log info to syslog.
> +//
> +// According to information in that messages, the DestinationHandler
> +// will do its jobs: interpret the msg, take the right destination
> +// and transfer that the msg to the destination type for futher processing.
> +//<
> +class DestinationHandler {
> + public:
> +  enum { kMaxChar = 255 };
> +  enum class DestType { kUnix = 0, kUnknown };
> +  //>
> +  // Handle message types
> +  // kSendRecord: for writing log record to destination(s).
> +  // kCfgDest   : for configuring destination(s).
> +  // kDelDest   : for deleting destination(s).
> +  // kNoDest    : for deleting all destination(s).
> +  //<
> +  enum class MsgType { kSendRecord = 0, kCfgDest, kDelDest, kNoDest };
> +
> +  //>
> +  // RecordMsg hold necessary information to forming RFC5424 protocol.
> +  // @severity  : log severity of sent log record.
> +  // @record_id : record id attached to sent log record.
> +  // @hash      : hash number generated from log stream name (DN)
> +  // @time      : time attached to log record. Using realtime if no set.
> +  // @origin    : node originator of the log record (host & network name).
> +  // @log_record: the log record that writting to local file.
> +  // @stream_dn : log stream name(DN).
> +  //<
> +  struct RecordInfo {
> +    const char* stream_dn;
> +    const char* msgid;
> +    const char* origin;
> +    const char* log_record;
> +    const char* app_name;
> +    uint16_t severity;
> +    uint32_t record_id;
> +    struct timespec time;
> +
> +    // Set default value
> +    RecordInfo() : stream_dn{nullptr}
> +        , msgid{nullptr}
> +        , origin{nullptr}
> +        , log_record{nullptr}
> +        , app_name{nullptr}
> +        , severity{0}
> +        , record_id{0}  {
> +          time = base::ReadRealtimeClock();
> +    }
> +  };
> +
> +  //>
> +  // @RecordMsg - send record @rec to destination name @name
> +  // @name: destination name
> +  // @rec : record message
> +  //>
> +  struct RecordMsg {
> +    char name[kMaxChar];
> +    RecordInfo rec;
> +    RecordMsg() : name{0}, rec{} {}
> +  };
> +
> +  //>
> +  // CfgDestMsg - holds information for one destination.
> +  // @name : name of destination.
> +  // @type : type of destination.
> +  // @value: destination configuration
> +  //<
> +  struct CfgDestMsg {
> +    char name[kMaxChar];
> +    char type[kMaxChar];
> +    char value[kMaxChar];
> +    // Clear the values of all types
> +    CfgDestMsg() : name{0}, type{0}, value{0} {}
> +  };
> +
> +  //>
> +  // DelDestMsg - hold information for one destintaion name
> +  // @name: refer to the destination name that is going to delete.
> +  //<
> +  struct DelDestMsg {
> +    char name[kMaxChar];
> +    DelDestMsg() : name{0} {}
> +  };
> +
> +  //>
> +  // @HandleMsg - Dispatch message format
> +  // @type: the value depends on @info
> +  // 1) kSendRecord if @info is RecordMsg
> +  // 2) kCfgDest if @info is CfgDestMsg
> +  // 3) kDelDest if @info is DelDestMsg
> +  // 4) kNoDest if not above
> +  //<
> +  struct HandleMsg {
> +    MsgType type;
> +    union {
> +      RecordMsg rec;
> +      CfgDestMsg cfg;
> +      DelDestMsg del;
> +    } info;
> +  };
> +
> +  // Unique object instance of @DestinationHandler class
> +  static DestinationHandler& Instance() { return me_; }
> +  // Extract the stream name from stream DN
> +  static std::string GenerateMsgId(const std::string&, bool);
> +
> +  // Do destination configuration basing on input @vdest and @type.
> +  ErrCode ProcessCfgChange(const VectorString& vdest, ModifyType type);
> +  // Do write log record @info to destinations @name
> +  ErrCode ProcessWriteReq(const RecordInfo& info, const std::string&
> name);
> +  // Get all destination status with format
> +  // {"name,status", "name1,status1", etc.}
> +  VectorString GetDestinationStatus();
> +
> + private:
> +  // typedef, aim for shorten declaration
> +  using NameTypeMap = std::map<std::string, DestType>;
> +  DestinationHandler() : nametype_map_{} {}
> +  ~DestinationHandler() {}
> +
> +  // Dispatch the @HandleMsg msg to destination @name.
> +  // Based on the destination @name, the method will find the right
> +  // destination @type, then forward the message to for futher processing.
> +  ErrCode DispatchTo(const HandleMsg& msg, const std::string& name);
> +  // Pack @CfgDestMsg from the string @dest which have pure format
> +  // "name;type;value".
> +  void FormCfgDestMsg(const std::string& dest, CfgDestMsg* msg);
> +  // Pack @DelDestMsg basing on pure destination format
> +  // "name;type;value".
> +  void FormDelDestMsg(const std::string& dest, DelDestMsg* msg);
> +  // Form @CfgDestMsg and dispatch the msg.
> +  ErrCode AddDestConfig(const VectorString& vdest);
> +  // Form @DelDestMsg and dispatch the msg.
> +  ErrCode DelDestConfig(const VectorString& vdest);
> +  // Form @kNoDest dispatch msg
> +  ErrCode DelAllCfgDest();
> +  // Find name @name from @vec
> +  bool VectorFind(const VectorString& vec, const std::string name);
> +  // Return @DestType based on the destination string @type
> +  DestType GetDestType(const std::string& type);
> +  DestType ToDestType(const std::string& name);
> +  // Update the @nametype_map_ whenever getting
> +  // destination configuration change.
> +  void UpdateDestTypeDb(const DestinationHandler::HandleMsg&);
> +  // Update the `logRecordDestinationStatus`
> +  void UpdateRtDestStatus();
> +
> +  // The map between destination @name and destination @type
> +  NameTypeMap nametype_map_;
> +  // Hold all existing destinations with pure format {"a;b;c", etc}
> +  VectorString allcfgdests_map_;
> +  static DestinationHandler me_;
> +};
> +
> +//>
> +// Represent connection to local Unix Domain socket
> +// @LocalSocketHandler = Local Socket Destination class
> +//
> +// Each destination name will have its own instance
> +// of this class. It represents the connection
> +// to the destination. So, it contains connection status,
> +// and the sequence message ID @msg_id_, showing how many logrecord
> +// has been written to the socket. This sequence number
> +// helps to detect if there is any msg lost so far.
> +//
> +// When getting a send log record request, this class
> +// will form message complying with RFC5424 protocol, putting
> +// the log record which has been writen to local file at MSG field,
> +// also do fill information to other fields in protocol header.
> +//
> +// The instance of this class is only deleted if destination info (@value)
> +// is deleted from Destination Configuration attribute.
> +//
> +// It would be wrong if more than one instance of this class
> +// pointed to same Domain Unix Socket (same local path name).
> +//
> +// Regarding connection status, the status will be flushed to
> +// latest one at points:
> +// 1) Creating new destination:
> +//    The instance will open a connection to destination,
> +//    then update its status based on the file descriptor.
> +//
> +// 2) Change destination to new one:
> +//    The previous connection is closed and opened a new one
> +//    toward the destination, then update the status
> +//    based on the file descriptor.
> +//
> +// 3) When writing log record to destination:
> +//    This class is not aware of the destination whether
> +//    the destination is dead or restarted.
> +//    If it gets failed to write to socket, the status will be updated.
> +//    just do that to detect the receiver on destination is dead/restarted.
> +//
> +// 4) Delete the destination
> +//    When deleing destination, the instance will be deleted.
> +//    Then, the status will be updated accordingly.
> +//
> +// All methods provided by this class should grant access
> +// to @LocalSocketType only. No one else can directly
> +// invoke any method of this class.
> +//
> +// With @Send(), if the msg get failed to send to destination,
> +// will log info to syslog and return the error code to upper layer.
> +//
> +// The other information on this class is that, within @Send()
> +// method, creating a very big stack buffer - over 65*1024 bytes
> +// even the sent RFC msg is quite short (e.g: rfc header + 150 bytes).
> +//<
> +class LocalSocketHandler {
> + public:
> +  // Set stack size of buffer to maximum size.
> +  static const uint32_t kBufMaxSize = 65*1024 + 1024;
> +  // typedef, aim to shorten declaration.
> +  using RfcBuffer = base::Buffer<kBufMaxSize>;
> +
> +  // This class is only dedicated to LocalSocketType.
> +  friend class LocalSocketType;
> +  // Open and connect to the socket
> +  void Open();
> +  // @Close Do nothing
> +  void Close();
> +  // Form RFC5424 and send to the socket
> +  ErrCode Send(const DestinationHandler::RecordInfo&);
> +  // Get the socket status
> +  DestinationStatus GetSockStatus();
> +  // Form rfc5424 syslog format
> +  static void FormRfc5424(
> +      const DestinationHandler::RecordInfo& msg,
> +      RfcBuffer* buf);
> +
> + private:
> +  explicit LocalSocketHandler(const char*);
> +  ~LocalSocketHandler();
> +
> +  // Get the @status_ up-to-date
> +  void FlushStatus();
> +  // Convert AIS log stream severity to syslog severity
> +  static base::LogMessage::Severity Sev(uint16_t);
> +
> +  // Hold destination info (@value)
> +  std::string sock_path_;
> +  base::UnixClientSocket sock_;
> +  // Hold the connection status
> +  DestinationStatus status_;
> +
> +  DELETE_COPY_AND_MOVE_OPERATORS(LocalSocketHandler);
> +};
> +
> +//>
> +// @LocalSocketType: Local Socket Destination Type
> +//
> +// This class represents local socket destination type.
> +// Every destinations name with local socket destination type
> +// must refer to one and only one instance of this class.
> +// Therefore, it has been designed as a singleton pattern.
> +//
> +// Outside world are not allowed to see any methods of this class
> +// except DestinationHandler. Right after analyzing DestinationHandler
> messages,
> +// knowing what is the destination type, DestinationHandler will pass
> +// the message to this class via the unique instance.
> +//
> +// This class holds a map b/w destination name and @LocalSocketHandler
> instance
> +// that represents the connection to destination for that destination name.
> +//
> +// Based on what type of messages come to, it will take specific actions
> +// accordingly.
> +// 1) Create/Update destination message
> +//    The message will contain destination name, destination type,
> +//    and probably destination info (local socket path).
> +//    Basing on the destination name, the Handler will sure that
> +//    a) Any such destination name has been created before?
> +//    b) And any @LocalSocketHandler instance is mapped to that
> +//       destination name?
> +//    If no destination name exist in the @map, then Hander will
> +//    create a new pair <name, @LocalSocketHandler> and update its
> destination.
> +//    If the destination name already exist, will check if configuration info
> +//    provided in the message is different with the existing or not.
> +//    We expect it should be different with the existing, otherwise
> +//    we have code fault somewhere. If different from the existing, closing
> +//    the old connection, and creating new one and re-map to that name.
> +//
> +// 2) Sending log record message
> +//    The message will contain destination name, log record, log stream DN
> +//    and other infos. Basing on the destination name, the Handler will sure:
> +//    a) Any such destination name has been created before?
> +//    b) And any @LocalSocketHandler instance is mapped to that
> +//       destination name?
> +//    With this message, the Handler expects the destination name and
> +//    @LocalSocketHandler must exist. Otherwise, there is a code
> +//    fault somewhere.
> +//    and the Handler will drop that message and log info to syslog.
> +//    If as expected, the Handler will send the message to
> +//    @LocalSocketHandler instance, in turn,
> +//    it will form that message according to RF5424 format and
> +//    send that message to local unix socket.
> +//
> +// 3) Delete destination message
> +//    This message will contain only destination name. Basing on that info,
> +//    the Hanlder will find that name in the @map to see any name is there.
> +//    If have, do:
> +//    1) Close the connection
> +//    2) Free resources connected to that destination name
> +//    Once destination name is deleted, any log record sent to destination
> name
> +//    will be drop or no writing to the destination happens.
> +//<
> +class LocalSocketType {
> + public:
> +  // @LocalSocketType class is dedicated to @DestinationHandler class.
> +  // Outside world is not granted to access to this class resources
> +  friend class DestinationHandler;
> +
> +  // Get @type that represents for @LocalSocketType. This type must be
> unique.
> +  static const std::string Type() { return std::string{name_}; }
> +
> + private:
> +  // typedef for the map. Aim to shorten the type declaration.
> +  using NameTypeMap =  std::map<std::string, LocalSocketHandler*>;
> +
> +  LocalSocketType();
> +  ~LocalSocketType();
> +
> +  // Get @LocalSocketHandler instance which has been assigned
> +  // to this destination @name
> +  LocalSocketHandler* GetDestHandle(const std::string& name);
> +  // Get configuration value that goes with this destination name @name
> +  const char* GetCfgValue(const std::string& name);
> +  // Find if any destination @name configured so far
> +  bool FindName(const std::string&) const;
> +  // Interpret the DestinationHandler msg and forward to right handle
> +  ErrCode ProcessMsg(const DestinationHandler::HandleMsg&);
> +  // Processing @Dispacher::RecordMsg msg, then forward to its instance
> +  // @LocalSocketHandler to form RFC5424 syslog format before writing to
> socket.
> +  ErrCode HandleRecordMsg(const DestinationHandler::RecordMsg&);
> +  // Processing @DestinationHandler::CfgDestMsg msg. Go thought all
> destinations
> +  // and create or update destination configuration depending on contents.
> +  ErrCode HandleCfgMsg(const DestinationHandler::CfgDestMsg&);
> +  // Processing @DestinationHandler::CfgDestMsg msg. Go thought all
> deleted
> +  // destinations names, then do close connections and free resources.
> +  ErrCode HandleDelCfgMsg(const DestinationHandler::DelDestMsg&);
> +  // Get destination status for destination @name.
> +  // The value could be:
> +  // 1) "active": destination @name is configured and connected to other
> end.
> +  // 2) "failed": destination @name is configured, but not able co connect 
> to.
> +  // 3) "inactive": no destination configuration value that goes with @name
> +  const std::string GetDestinationStatus(const std::string& name);
> +  // Convert enum status to string
> +  const std::string DestStatusToStr(DestinationStatus status);
> +  // Get the unique instance represents this unique type
> +  // This method should only be used by @DestinationHandler class
> +  // Get all destination status for Local Socket Destination Typen
> +  // @return a vector of status, with format
> +  // {"name,active", "name2,inactive, etc."}
> +  static const VectorString GetAllDestStatus();
> +  // Unique object instance for this @LocalSocketType class
> +  static LocalSocketType& Instance() { return me_; }
> +  // Unique instance for this class
> +  static LocalSocketType me_;
> +  // The type name of this @LocalSocketType (my identity).
> +  // Must use this "type" name in destination configuration
> +  // if want to destinate to local unix socket.
> +  static const char name_[];
> +
> +  // Map b/w dest name and its own @LocalSocketHandler instance.
> +  // Same @name must have same @LocalSocketHandler instance.
> +  NameTypeMap nametype_map_;
> +
> +  DELETE_COPY_AND_MOVE_OPERATORS(LocalSocketType);
> +};
> +
> +#endif  // SRC_LOG_LOGD_LGS_DEST_H_
> diff --git a/src/log/logd/lgs_evt.cc b/src/log/logd/lgs_evt.cc
> --- a/src/log/logd/lgs_evt.cc
> +++ b/src/log/logd/lgs_evt.cc
> @@ -1,6 +1,7 @@
>  /*      -*- OpenSAF  -*-
>   *
>   * (C) Copyright 2008 The OpenSAF Foundation
> + * Copyright Ericsson AB 2014, 2017 - All Rights Reserved.
[Lennart] Incorrect years in copyright. Should be 2008, 2017
>   *
>   * This program is distributed in the hope that it will be useful, but
>   * WITHOUT ANY WARRANTY; without even the implied warranty of
> MERCHANTABILITY
> @@ -27,6 +28,7 @@
>  #include "lgs_imm_gcfg.h"
>  #include "base/osaf_extended_name.h"
>  #include "lgs_clm.h"
> +#include "lgs_dest.h"
> 
>  /* Macro to validate the version */
>  #define m_LOG_VER_IS_VALID(ver)                                         \
> @@ -1235,6 +1237,8 @@ static uint32_t proc_write_log_async_msg
>    void *ckpt_ptr;
>    uint32_t max_logrecsize = 0;
>    char node_name[_POSIX_HOST_NAME_MAX];
> +  RecordData data;
> +  timespec time;
> 
>    memset(node_name, 0, _POSIX_HOST_NAME_MAX);
>    strncpy(node_name, evt->node_name, _POSIX_HOST_NAME_MAX);
> @@ -1294,6 +1298,35 @@ static uint32_t proc_write_log_async_msg
>      goto done;
>    }
> 
> +  //>
> +  // Has successfully written log record to file.
> +  // Now, send to destination if any destination name set.
> +  //<
> +
> +  // Streaming not support on alarm/notif streams.
> +  if ((stream->name == SA_LOG_STREAM_ALARM) ||
> +      (stream->name == SA_LOG_STREAM_NOTIFICATION)) {
> +    goto checkpoint;
> +  }
> +
> +  // Packing Record data that carry necessary information
> +  // to form RFC5424 syslog msg, then send to destination name(s).
> +  data.name = stream->name.c_str();
> +  data.logrec = logOutputString;
> +  data.hostname = node_name;
> +  data.networkname = lgs_get_networkname().c_str();
> +  data.appname = osaf_extended_name_borrow(
> +      param->logRecord->logHeader.genericHdr.logSvcUsrName);
> +  data.isRtStream = stream->isRtStream;
> +  data.recordId = stream->logRecordId;
> +  data.sev = param->logRecord->logHeader.genericHdr.logSeverity;
> +  time.tv_sec = (param->logRecord->logTimeStamp /
> (SaTimeT)SA_TIME_ONE_SECOND);
> +  time.tv_nsec = (param->logRecord->logTimeStamp %
> (SaTimeT)SA_TIME_ONE_SECOND);
> +  data.time = time;
> +
> +  WriteToDestination(data, stream->dest_names);
> +
> +checkpoint:
>    /* TODO: send fail back if ack is wanted, Fix counter for application 
> stream!!
> */
>    if (cb->ha_state == SA_AMF_HA_ACTIVE) {
>      if (lgs_is_peer_v2()) {
> diff --git a/src/log/logd/lgs_imm.cc b/src/log/logd/lgs_imm.cc
> --- a/src/log/logd/lgs_imm.cc
> +++ b/src/log/logd/lgs_imm.cc
> @@ -45,12 +45,14 @@
>  #include "log/logd/lgs_file.h"
>  #include "log/logd/lgs_recov.h"
>  #include "log/logd/lgs_config.h"
> +#include "log/logd/lgs_dest.h"
>  #include "base/saf_error.h"
> 
>  #include "lgs_mbcsv_v1.h"
>  #include "lgs_mbcsv_v2.h"
>  #include "lgs_mbcsv_v3.h"
>  #include "lgs_mbcsv_v5.h"
> +#include "lgs_mbcsv_v7.h"
> 
>  /* TYPE DEFINITIONS
>   * ----------------
> @@ -234,11 +236,33 @@ static uint32_t ckpt_stream_config(log_s
>    uint32_t rc;
>    lgsv_ckpt_msg_v1_t ckpt_v1;
>    lgsv_ckpt_msg_v2_t ckpt_v2;
> +  lgsv_ckpt_msg_v7_t ckpt_v3;
> +
>    void *ckpt_ptr;
> 
>    TRACE_ENTER();
> 
> -  if (lgs_is_peer_v2()) {
> +  if (lgs_is_peer_v7()) {
> +    memset(&ckpt_v3, 0, sizeof(ckpt_v3));
> +    ckpt_v3.header.ckpt_rec_type = LGS_CKPT_CFG_STREAM;
> +    ckpt_v3.header.num_ckpt_records = 1;
> +    ckpt_v3.header.data_len = 1;
> +
> +    ckpt_v3.ckpt_rec.stream_cfg.name = const_cast<char *>(stream-
> >name.c_str());
> +    ckpt_v3.ckpt_rec.stream_cfg.fileName = const_cast<char *>(stream-
> >fileName.c_str());
> +    ckpt_v3.ckpt_rec.stream_cfg.pathName = const_cast<char *>(stream-
> >pathName.c_str());
> +    ckpt_v3.ckpt_rec.stream_cfg.maxLogFileSize = stream->maxLogFileSize;
> +    ckpt_v3.ckpt_rec.stream_cfg.fixedLogRecordSize = stream-
> >fixedLogRecordSize;
> +    ckpt_v3.ckpt_rec.stream_cfg.logFullAction = stream->logFullAction;
> +    ckpt_v3.ckpt_rec.stream_cfg.logFullHaltThreshold = stream-
> >logFullHaltThreshold;
> +    ckpt_v3.ckpt_rec.stream_cfg.maxFilesRotated = stream-
> >maxFilesRotated;
> +    ckpt_v3.ckpt_rec.stream_cfg.logFileFormat = stream->logFileFormat;
> +    ckpt_v3.ckpt_rec.stream_cfg.severityFilter = stream->severityFilter;
> +    ckpt_v3.ckpt_rec.stream_cfg.logFileCurrent = const_cast<char
> *>(stream->logFileCurrent.c_str());
> +    ckpt_v3.ckpt_rec.stream_cfg.dest_names = const_cast<char *>(stream-
> >stb_dest_names.c_str());
> +    ckpt_v3.ckpt_rec.stream_cfg.c_file_close_time_stamp = stream-
> >act_last_close_timestamp;
> +    ckpt_ptr = &ckpt_v3;
> +  } else if (lgs_is_peer_v2()) {
>      memset(&ckpt_v2, 0, sizeof(ckpt_v2));
>      ckpt_v2.header.ckpt_rec_type = LGS_CKPT_CFG_STREAM;
>      ckpt_v2.header.num_ckpt_records = 1;
> @@ -854,13 +878,14 @@ static SaAisErrorT config_ccb_completed_
>        // Note: Multi value attribute
>        TRACE("logRecordDestinationConfiguration. Values number = %d",
>              attribute->attrValuesNumber);
> -      std::vector<std::string> values_vector;
> +      std::vector<std::string> values_vector{};
>        for (uint32_t i=0; i < attribute->attrValuesNumber; i++) {
>          value = attribute->attrValues[i];
>          char *value_str = *(reinterpret_cast<char **>(value));
>          values_vector.push_back(value_str);
>        }
> -      rc =
> lgs_cfg_verify_log_record_destination_configuration(values_vector);
> +      rc = lgs_cfg_verify_log_record_destination_configuration(
> +          values_vector, attrMod->modType);
>        if (rc == -1) {
>          report_oi_error(immOiHandle, opdata->ccbId,
>                          "%s value is NOT accepted", attribute->attrName);
> @@ -1268,6 +1293,20 @@ done:
>    return rc;
>  }
> 
> +static bool is_valid_dest_names(const std::vector<std::string>& names) {
> +  // Delete all destination names
[Lennart] A better comment could be: "Stream has no destination name"
> +  if (names.size() == 0) return true;
> +  for (const auto& it : names) {
[Lennart] It would be more informative if "it" is changed to "name"
Example: "for (const auto& name : names)" and "if (name.empty() == true || 
name.size() == 0)" etc.
> +    // Empty destination name is invalid
> +    if (it.empty() == true || it.size() == 0) return false;
> +    // Contain special characters is not allowed
> +    if (logutil::isUnixName(it) == false) return false;
> +    // Name is too long
> +    if (it.length() > 255) return false;
> +  }
> +  return true;
> +}
> +
>  /**
>   * Validate input parameters creation and modify of a persistent stream
>   * i.e. a stream that has a configuration object.
> @@ -1366,12 +1405,16 @@ static SaAisErrorT check_attr_validity(S
>      if (attribute->attrValuesNumber > 0) {
>        value = attribute->attrValues[0];
>      } else if (opdata->operationType == CCBUTIL_MODIFY) {
> -      /* An attribute without a value is never valid if modify */
> -      report_oi_error(immOiHandle, opdata->ccbId,
> -                      "Attribute %s has no value",attribute->attrName);
> -      TRACE("Modify: Attribute %s has no value",attribute->attrName);
> -      rc = SA_AIS_ERR_BAD_OPERATION;
> -      goto done;
> +      if (!strcmp(attribute->attrName, "saLogRecordDestination")) {
> +        // do nothing
> +      } else {
> +        /* An attribute without a value is never valid if modify */
> +        report_oi_error(immOiHandle, opdata->ccbId,
> +                        "Attribute %s has no value",attribute->attrName);
> +        TRACE("Modify: Attribute %s has no value",attribute->attrName);
> +        rc = SA_AIS_ERR_BAD_OPERATION;
> +        goto done;
> +      }
>      } else {
>        /* If create all attributes will be present also the ones without
>         * any value.
> @@ -1426,8 +1469,24 @@ static SaAisErrorT check_attr_validity(S
>        i_severityFilter_mod = true;
>        TRACE("Saved attribute \"%s\" = %d", attribute->attrName,
>              i_severityFilter);
> +    } else if (!strcmp(attribute->attrName,"saLogRecordDestination")) {
> +      std::vector<std::string> vstring{};
> +      for (unsigned i = 0; i < attribute->attrValuesNumber; i++) {
> +        value = attribute->attrValues[i];
> +        char *value_str = *(reinterpret_cast<char **>(value));
> +        vstring.push_back(value_str);
> +      }
> +      if (is_valid_dest_names(vstring) == false) {
> +        /* Report failed if has special character in file name */
> +        rc = SA_AIS_ERR_BAD_OPERATION;
> +        report_oi_error(immOiHandle, opdata->ccbId,
> +                        "Invalid saLogRecordDestination value");
> +        TRACE("Invalid saLogRecordDestination value");
> +        goto done;
> +      }
>      }
> 
> +
>      /* Get next attribute or detect no more attributes */
>   next:
>      if (opdata->operationType == CCBUTIL_CREATE) {
> @@ -1974,6 +2033,41 @@ static void apply_conf_logDataGroupname(
>    logDataGroupname_fileown(value_ptr);
>  }
> 
> +static void apply_config_destinations_change(
> +    const std::vector<std::string>& vdestcfg,
> +    SaImmAttrModificationTypeT type,
> +    lgs_config_chg_t* config_data) {
> +  switch (type) {
> +    case SA_IMM_ATTR_VALUES_ADD:
> +      // Configure destinations
> +      CfgDestination(vdestcfg, ModifyType::kAdd);
> +
> lgs_cfgupd_multival_add(LOG_RECORD_DESTINATION_CONFIGURATION,
> +                              vdestcfg,
> +                              config_data);
> +      break;
> +
> +    case SA_IMM_ATTR_VALUES_DELETE:
> +      // Configure destinations
> +      CfgDestination(vdestcfg, ModifyType::kDelete);
> +
> lgs_cfgupd_multival_delete(LOG_RECORD_DESTINATION_CONFIGURATION,
> +                                 vdestcfg,
> +                                 config_data);
> +      break;
> +
> +    case SA_IMM_ATTR_VALUES_REPLACE:
> +      CfgDestination(vdestcfg, ModifyType::kReplace);
> +
> lgs_cfgupd_mutival_replace(LOG_RECORD_DESTINATION_CONFIGURATION
> ,
> +                                 vdestcfg,
> +                                 config_data);
> +      break;
> +
> +    default:
> +      // Shall never happen
> +      LOG_ER("%s: Unknown modType %d", __FUNCTION__, type);
> +      osafassert(0);
> +  };
> +}
> +
>  /**
>   * Apply changes. Validation is not needed here since all validation is done 
> in
>   * the complete callback
> @@ -2088,28 +2182,9 @@ static void config_ccb_apply_modify(cons
>          char *value_str = *(reinterpret_cast<char **>(value));
>          values_vector.push_back(value_str);
>        }
> -
> -      switch (attrMod->modType) {
> -        case SA_IMM_ATTR_VALUES_ADD:
> -
> lgs_cfgupd_multival_add(LOG_RECORD_DESTINATION_CONFIGURATION,
> -                                  values_vector,
> -                                  &config_data);
> -          break;
> -        case SA_IMM_ATTR_VALUES_DELETE:
> -
> lgs_cfgupd_multival_delete(LOG_RECORD_DESTINATION_CONFIGURATION,
> -                                  values_vector,
> -                                  &config_data);
> -          break;
> -        case SA_IMM_ATTR_VALUES_REPLACE:
> -
> lgs_cfgupd_mutival_replace(LOG_RECORD_DESTINATION_CONFIGURATION
> ,
> -                                  values_vector,
> -                                  &config_data);
> -          break;
> -        default:
> -          // Shall never happen
> -          LOG_ER("%s: Unknown modType %d", __FUNCTION__, attrMod-
> >modType);
> -          osafassert(0);
> -      };
> +      apply_config_destinations_change(values_vector,
> +                                       attrMod->modType,
> +                                       &config_data);
>      }
> 
>      attrMod = opdata->param.modify.attrMods[i++];
> @@ -2250,6 +2325,15 @@ static SaAisErrorT stream_create_and_con
>        } else if (!strcmp(ccb->param.create.attrValues[i]->attrName,
> "saLogStreamSeverityFilter")) {
>          (*stream)->severityFilter = *((SaUint32T *) value);
>          TRACE("severityFilter: %u", (*stream)->severityFilter);
> +      } else if (!strcmp(ccb->param.create.attrValues[i]->attrName,
> +                          "saLogRecordDestination")) {
> +        std::vector<std::string> vstring{};
> +        for (unsigned ii = 0; ii < ccb->param.create.attrValues[i]-
> >attrValuesNumber; ii++) {
> +          value = ccb->param.create.attrValues[i]->attrValues[ii];
> +          char *value_str = *(reinterpret_cast<char **>(value));
> +          vstring.push_back(value_str);
> +        }
> +        log_stream_add_dest_name(*stream, vstring);
>        }
>      }
>      i++;
> @@ -2304,6 +2388,30 @@ static void stream_ccb_apply_create(cons
>    TRACE_LEAVE();
>  }
> 
> +static void apply_destination_names_change(
> +    log_stream_t* stream,
> +    const std::vector<std::string>& destname,
> +    SaImmAttrModificationTypeT type) {
> +  switch (type) {
> +    case SA_IMM_ATTR_VALUES_ADD:
> +      log_stream_add_dest_name(stream, destname);
> +      break;
> +
> +    case SA_IMM_ATTR_VALUES_DELETE:
> +      log_stream_delete_dest_name(stream, destname);
> +      break;
> +
> +    case SA_IMM_ATTR_VALUES_REPLACE:
> +      log_stream_replace_dest_name(stream, destname);
> +      break;
> +
> +    default:
> +      // Shall never happen
> +      LOG_ER("%s: Unknown modType %d", __FUNCTION__, type);
> +      osafassert(0);
> +  }
> +}
> +
>  static void stream_ccb_apply_modify(const CcbUtilOperationData_t
> *opdata) {
>    const SaImmAttrModificationT_2 *attrMod;
>    int i = 0;
> @@ -2323,12 +2431,21 @@ static void stream_ccb_apply_modify(cons
> 
>    attrMod = opdata->param.modify.attrMods[i++];
>    while (attrMod != NULL) {
> -    void *value;
> +    void *value = nullptr;
>      const SaImmAttrValuesT_2 *attribute = &attrMod->modAttr;
> 
>      TRACE("attribute %s", attribute->attrName);
> -
> -    value = attribute->attrValues[0];
> +    if (attribute->attrValuesNumber != 0) {
> +      value = attribute->attrValues[0];
> +    } else {
> +      if (!strcmp(attribute->attrName, "saLogRecordDestination")) {
> +        const std::vector<std::string> dname{};
> +        LOG_NO("%s deleted", __func__);
> +        log_stream_delete_dest_name(stream, dname);
> +        attrMod = opdata->param.modify.attrMods[i++];
> +        continue;
> +      }
> +    }
> 
>      if (!strcmp(attribute->attrName, "saLogStreamFileName")) {
>        fileName = *((char **)value);
> @@ -2362,6 +2479,14 @@ static void stream_ccb_apply_modify(cons
>      } else if (!strcmp(attribute->attrName, "saLogStreamSeverityFilter")) {
>        SaUint32T severityFilter = *((SaUint32T *)value);
>        stream->severityFilter = severityFilter;
> +    } else if (!strcmp(attribute->attrName, "saLogRecordDestination")) {
> +      std::vector<std::string> vstring;
> +      for (unsigned i = 0; i < attribute->attrValuesNumber; i++) {
> +        value = attribute->attrValues[i];
> +        char *value_str = *(reinterpret_cast<char **>(value));
> +        vstring.push_back(value_str);
> +      }
> +      apply_destination_names_change(stream, vstring, attrMod-
> >modType);
>      } else {
>        LOG_ER("Error: Unknown attribute name");
>        osafassert(0);
> @@ -2613,6 +2738,7 @@ static SaAisErrorT stream_create_and_con
>      const_cast<char *>("saLogStreamLogFullHaltThreshold"),
>      const_cast<char *>("saLogStreamMaxFilesRotated"),
>      const_cast<char *>("saLogStreamLogFileFormat"),
> +    const_cast<char *>("saLogRecordDestination"),
>      const_cast<char *>("saLogStreamSeverityFilter"),
>      const_cast<char *>("saLogStreamCreationTimestamp"),
>      NULL
> @@ -2696,6 +2822,14 @@ static SaAisErrorT stream_create_and_con
>      } else if (!strcmp(attribute->attrName, "saLogStreamSeverityFilter")) {
>        stream->severityFilter = *((SaUint32T *)value);
>        TRACE("severityFilter: %u", stream->severityFilter);
> +    } else if (!strcmp(attribute->attrName, "saLogRecordDestination")) {
> +      std::vector<std::string> vstring{};
> +      for (unsigned i = 0; i < attribute->attrValuesNumber; i++) {
> +        value = attribute->attrValues[i];
> +        char *value_str = *(reinterpret_cast<char **>(value));
> +        vstring.push_back(value_str);
> +      }
> +      log_stream_add_dest_name(stream, vstring);
>      } else if (!strcmp(attribute->attrName,
> "saLogStreamCreationTimestamp")) {
>        if (attribute->attrValuesNumber != 0) {
>          /* Restore creation timestamp if exist
> diff --git a/src/log/logd/lgs_main.cc b/src/log/logd/lgs_main.cc
> --- a/src/log/logd/lgs_main.cc
> +++ b/src/log/logd/lgs_main.cc
> @@ -39,6 +39,7 @@
>  #include "lgs_recov.h"
>  #include "osaf/immutil/immutil.h"
>  #include "lgs_clm.h"
> +#include "log/logd/lgs_dest.h"
> 
>  /*
> ==========================================================
> ==============
>   *   DEFINITIONS
> @@ -371,6 +372,7 @@ done:
>  uint32_t initialize_for_assignment(lgs_cb_t *cb, SaAmfHAStateT ha_state) {
>    const char *logsv_root_dir = NULL;
>    const char *logsv_data_groupname = NULL;
> +  const VectorString *vdest = nullptr;
[Lennart] Declaring *vedest like this somewhat hides the actual type 
(VectorString is not a well-known type) and since *vdest is used with 
lgs_cfg_get(LGS_IMM_LOG_RECORD_DESTINATION_CONFIGURATION) and 
LGS_IMM_LOG_RECORD_DESTINATION_CONFIGURATION is defined as a 
std::vector<std::string> it is a bit unclear
>    TRACE_ENTER2("ha_state = %d", (int) ha_state);
>    uint32_t rc = NCSCC_RC_SUCCESS;
> 
> @@ -393,6 +395,12 @@ uint32_t initialize_for_assignment(lgs_c
>    logsv_data_groupname = static_cast<const char
> *>(lgs_cfg_get(LGS_IMM_DATA_GROUPNAME));
>    LOG_NO("LOG root directory is: \"%s\"", logsv_root_dir);
>    LOG_NO("LOG data group is: \"%s\"", logsv_data_groupname);
> +  vdest = reinterpret_cast<const VectorString*>(
> +
> lgs_cfg_get(LGS_IMM_LOG_RECORD_DESTINATION_CONFIGURATION));
> +  osafassert(vdest != nullptr);
> +  if (vdest->size() > 1) {
> +    CfgDestination(*vdest, ModifyType::kAdd);
> +  }
> 
>    /* Initialize file handling thread
>     * Configuration must have been initialized
> diff --git a/src/log/logd/lgs_mbcsv.cc b/src/log/logd/lgs_mbcsv.cc
> --- a/src/log/logd/lgs_mbcsv.cc
> +++ b/src/log/logd/lgs_mbcsv.cc
> @@ -1,6 +1,7 @@
>  /*      -*- OpenSAF  -*-
>   *
>   * (C) Copyright 2008 The OpenSAF Foundation
> + * Copyright Ericsson AB 2014, 2017 - All Rights Reserved.
[Lennart] Incorrect copyright year
>   *
>   * This program is distributed in the hope that it will be useful, but
>   * WITHOUT ANY WARRANTY; without even the implied warranty of
> MERCHANTABILITY
> @@ -20,11 +21,14 @@
>  #include "base/ncssysf_mem.h"
>  #include "base/osaf_time.h"
> 
> +#include "log/logd/lgs_mbcsv_v7.h"
>  #include "lgs_mbcsv_v5.h"
>  #include "lgs_mbcsv_v3.h"
>  #include "lgs_mbcsv_v2.h"
>  #include "lgs_mbcsv_v1.h"
>  #include "lgs_recov.h"
> +#include "osaf/immutil/immutil.h"
> +#include "log/logd/lgs_dest.h"
> 
>  /*
>    LGS_CKPT_DATA_HEADER
> @@ -360,6 +364,18 @@ bool lgs_is_peer_v5() {
>  }
> 
>  /**
> + * Check if peer is version 7 (or later)
> + * @return bool
> + */
> +bool lgs_is_peer_v7() {
> +  if (lgs_cb->mbcsv_peer_version >= LGS_MBCSV_VERSION_7) {
> +    return true;
> +  } else {
> +    return false;
> +  }
> +}
> +
> +/**
>   * Check if configured for split file system.
>   * If other node is version 1 split file system mode is not applicable.
>   *
> @@ -750,6 +766,7 @@ static uint32_t edu_enc_reg_list(lgs_cb_
> 
> **********************************************************
> ******************/
> 
>  static uint32_t ckpt_encode_async_update(lgs_cb_t *lgs_cb, EDU_HDL
> edu_hdl, NCS_MBCSV_CB_ARG *cbk_arg) {
> +  lgsv_ckpt_msg_v7_t *data_v7 = NULL;
>    lgsv_ckpt_msg_v5_t *data_v5 = NULL;
>    lgsv_ckpt_msg_v3_t *data_v3 = NULL;
>    lgsv_ckpt_msg_v2_t *data_v2 = NULL;
> @@ -761,7 +778,12 @@ static uint32_t ckpt_encode_async_update
> 
>    TRACE_ENTER();
>    /* Set reo_hdl from callback arg to ckpt_rec */
> -  if (lgs_is_peer_v5()) {
> +  if (lgs_is_peer_v7()) {
> +    data_v7 = reinterpret_cast<lgsv_ckpt_msg_v7_t *>(
> +        static_cast<long>(cbk_arg->info.encode.io_reo_hdl));
> +    vdata = data_v7;
> +    edp_function = edp_ed_ckpt_msg_v7;
> +  } else if (lgs_is_peer_v5()) {
>      data_v5 = reinterpret_cast<lgsv_ckpt_msg_v5_t *>(
>          static_cast<long>(cbk_arg->info.encode.io_reo_hdl));
>      vdata = data_v5;
> @@ -1047,7 +1069,11 @@ static uint32_t ckpt_decode_log_cfg_stre
>    void *stream_cfg;
>    EDU_PROG_HANDLER edp_function;
> 
> -  if (lgs_is_peer_v2()) {
> +  if (lgs_is_peer_v7()) {
> +    lgsv_ckpt_msg_v7_t *ckpt_msg_v7 = static_cast<lgsv_ckpt_msg_v7_t
> *>(ckpt_msg);
> +    stream_cfg = &ckpt_msg_v7->ckpt_rec.stream_cfg;
> +    edp_function = edp_ed_cfg_stream_rec_v7;
> +  } else if (lgs_is_peer_v2()) {
>      lgsv_ckpt_msg_v2_t *ckpt_msg_v2 = static_cast<lgsv_ckpt_msg_v2_t
> *>(ckpt_msg);
>      stream_cfg = &ckpt_msg_v2->ckpt_rec.stream_cfg;
>      edp_function = edp_ed_cfg_stream_rec_v2;
> @@ -1069,11 +1095,16 @@ static uint32_t ckpt_decode_log_cfg(lgs_
>    uint32_t rc = NCSCC_RC_SUCCESS;
>    void *lgs_cfg = NULL;
>    EDU_PROG_HANDLER edp_function = NULL;
> +  lgsv_ckpt_msg_v7_t *ckpt_msg_v7;
>    lgsv_ckpt_msg_v5_t *ckpt_msg_v5;
>    lgsv_ckpt_msg_v3_t *ckpt_msg_v3;
>    lgsv_ckpt_msg_v2_t *ckpt_msg_v2;
> 
> -  if (lgs_is_peer_v5()) {
> +  if (lgs_is_peer_v7()) {
> +    ckpt_msg_v7 = static_cast<lgsv_ckpt_msg_v7_t *>(ckpt_msg);
> +    lgs_cfg = &ckpt_msg_v7->ckpt_rec.lgs_cfg;
> +    edp_function = edp_ed_lgs_cfg_rec_v5;
> +  } else if (lgs_is_peer_v5()) {
>      ckpt_msg_v5 = static_cast<lgsv_ckpt_msg_v5_t *>(ckpt_msg);
>      lgs_cfg = &ckpt_msg_v5->ckpt_rec.lgs_cfg;
>      edp_function = edp_ed_lgs_cfg_rec_v5;
> @@ -1111,6 +1142,8 @@ static uint32_t ckpt_decode_async_update
>    lgsv_ckpt_msg_v3_t *ckpt_msg_v3 = &msg_v3;
>    lgsv_ckpt_msg_v5_t msg_v5;
>    lgsv_ckpt_msg_v5_t *ckpt_msg_v5 = &msg_v5;
> +  lgsv_ckpt_msg_v7_t msg_v7;
> +  lgsv_ckpt_msg_v7_t *ckpt_msg_v7 = &msg_v7;
>    void *ckpt_msg;
>    lgsv_ckpt_header_t hdr, *hdr_ptr = &hdr;
> 
> @@ -1130,7 +1163,10 @@ static uint32_t ckpt_decode_async_update
> 
>    TRACE_2("\tckpt_rec_type: %d ", (int)hdr_ptr->ckpt_rec_type);
> 
> -  if (lgs_is_peer_v5()) {
> +  if (lgs_is_peer_v7()) {
> +    ckpt_msg_v7->header = hdr;
> +    ckpt_msg = ckpt_msg_v7;
> +  } else if (lgs_is_peer_v5()) {
>      ckpt_msg_v5->header = hdr;
>      ckpt_msg = ckpt_msg_v5;
>    } else if (lgs_is_peer_v4()) {
> @@ -1148,7 +1184,9 @@ static uint32_t ckpt_decode_async_update
>    switch (hdr_ptr->ckpt_rec_type) {
>      case LGS_CKPT_CLIENT_INITIALIZE:
>        TRACE_2("\tINITIALIZE REC: UPDATE");
> -      if (lgs_is_peer_v5()) {
> +      if (lgs_is_peer_v7()) {
> +        reg_rec = &ckpt_msg_v7->ckpt_rec.initialize_client;
> +      } else if (lgs_is_peer_v5()) {
>          reg_rec = &ckpt_msg_v5->ckpt_rec.initialize_client;
>        } else if (lgs_is_peer_v4()) {
>          reg_rec = &ckpt_msg_v3->ckpt_rec.initialize_client;
> @@ -1173,7 +1211,9 @@ static uint32_t ckpt_decode_async_update
> 
>      case LGS_CKPT_OPEN_STREAM: /* 4 */
>        TRACE_2("\tSTREAM OPEN: UPDATE");
> -      if (lgs_is_peer_v5()) {
> +      if (lgs_is_peer_v7()) {
> +        stream_open = &ckpt_msg_v7->ckpt_rec.stream_open;
> +      } else if (lgs_is_peer_v5()) {
>          stream_open = &ckpt_msg_v5->ckpt_rec.stream_open;
>        } else if (lgs_is_peer_v4()) {
>          stream_open = &ckpt_msg_v3->ckpt_rec.stream_open;
> @@ -1417,13 +1457,17 @@ static uint32_t process_ckpt_data(lgs_cb
>    lgsv_ckpt_msg_v2_t *data_v2;
>    lgsv_ckpt_msg_v3_t *data_v3;
>    lgsv_ckpt_msg_v5_t *data_v5;
> +  lgsv_ckpt_msg_v7_t *data_v7;
> 
>    if ((!cb) || (data == NULL)) {
>      TRACE("%s - FAILED: (!cb) || (data == NULL)", __FUNCTION__);
>      return (rc = NCSCC_RC_FAILURE);
>    }
> 
> -  if (lgs_is_peer_v5()) {
> +  if (lgs_is_peer_v7()) {
> +    data_v7 = static_cast<lgsv_ckpt_msg_v7_t *>(data);
> +    lgsv_ckpt_msg_type = data_v7->header.ckpt_rec_type;
> +  } else if (lgs_is_peer_v5()) {
>      data_v5 = static_cast<lgsv_ckpt_msg_v5_t *>(data);
>      lgsv_ckpt_msg_type = data_v5->header.ckpt_rec_type;
>    } else if (lgs_is_peer_v4()) {
> @@ -1834,6 +1878,7 @@ uint32_t ckpt_proc_open_stream(lgs_cb_t
>    lgs_ckpt_stream_open_t *param;
>    log_stream_t *stream;
>    int pos = 0, err = 0;
> +  SaNameT objectName;
> 
>    TRACE_ENTER();
> 
> @@ -1902,7 +1947,18 @@ uint32_t ckpt_proc_open_stream(lgs_cb_t
>      stream->logFileCurrent = param->logFileCurrent;
>      stream->stb_prev_actlogFileCurrent = param->logFileCurrent;
>      stream->stb_logFileCurrent = param->logFileCurrent;
> -    stream->isRtStream = SA_TRUE;
> +
> +    osaf_extended_name_lend(param->logStreamName, &objectName);
> +    SaImmClassNameT className = immutil_get_className(&objectName);
> +    if (className != nullptr && strcmp(className, "SaLogStreamConfig") ==
> 0) {
> +      stream->isRtStream = SA_FALSE;
> +    } else {
> +      stream->isRtStream = SA_TRUE;
> +    }
> +    if (className != nullptr) free(className);
> +    // Not support adding destination name into saLogStreamOpen()
> parameters.
> +    stream->dest_names.clear();
> +    stream->stb_dest_names = "";
>    }
> 
>    /* If configured for split file system files shall be opened on stand by
> @@ -2061,12 +2117,28 @@ static uint32_t ckpt_proc_cfg_stream(lgs
>    SaUint32T logFullHaltThreshold; /* !app log stream */
>    SaUint32T maxFilesRotated;
>    char *logFileFormat;
> +  char *dest_names = nullptr;
>    SaUint32T severityFilter;
>    char *logFileCurrent;
> 
>    TRACE_ENTER();
> 
> -  if (lgs_is_peer_v2()) {
> +  if (lgs_is_peer_v7()) {
> +    lgsv_ckpt_msg_v7_t *data_v7 = static_cast<lgsv_ckpt_msg_v7_t
> *>(data);
> +    name = data_v7->ckpt_rec.stream_cfg.name;
> +    fileName = data_v7->ckpt_rec.stream_cfg.fileName;
> +    pathName = data_v7->ckpt_rec.stream_cfg.pathName;
> +    maxLogFileSize = data_v7->ckpt_rec.stream_cfg.maxLogFileSize;
> +    fixedLogRecordSize = data_v7->ckpt_rec.stream_cfg.fixedLogRecordSize;
> +    logFullAction = data_v7->ckpt_rec.stream_cfg.logFullAction;
> +    logFullHaltThreshold = data_v7-
> >ckpt_rec.stream_cfg.logFullHaltThreshold;
> +    maxFilesRotated = data_v7->ckpt_rec.stream_cfg.maxFilesRotated;
> +    logFileFormat = data_v7->ckpt_rec.stream_cfg.logFileFormat;
> +    severityFilter = data_v7->ckpt_rec.stream_cfg.severityFilter;
> +    logFileCurrent = data_v7->ckpt_rec.stream_cfg.logFileCurrent;
> +    dest_names = data_v7->ckpt_rec.stream_cfg.dest_names;
> +    closetime = data_v7->ckpt_rec.stream_cfg.c_file_close_time_stamp;
> +  } else if (lgs_is_peer_v2()) {
>      lgsv_ckpt_msg_v2_t *data_v2 = static_cast<lgsv_ckpt_msg_v2_t
> *>(data);
>      name = data_v2->ckpt_rec.stream_cfg.name;
>      fileName = data_v2->ckpt_rec.stream_cfg.fileName;
> @@ -2119,6 +2191,14 @@ static uint32_t ckpt_proc_cfg_stream(lgs
>    strcpy(stream->logFileFormat, logFileFormat);
>    stream->severityFilter = severityFilter;
>    stream->logFileCurrent = logFileCurrent;
> +  TRACE("dest_names: %s", dest_names);
> +  if (strlen(dest_names) == 0) {
> +    stream->stb_dest_names = "";
> +    stream->dest_names.clear();
> +  } else {
> +    stream->stb_dest_names = std::string{dest_names};
> +    stream->dest_names = logutil::Parser(stream->stb_dest_names, ";");
> +  }
> 
>    /* If split file mode, update standby files */
>    if (lgs_is_split_file_system()) {
> @@ -2178,7 +2258,10 @@ uint32_t lgs_ckpt_send_async(lgs_cb_t *c
> 
>    TRACE_ENTER();
> 
> -  if (lgs_is_peer_v5()) {
> +  if (lgs_is_peer_v7()) {
> +    lgsv_ckpt_msg_v7_t *ckpt_rec_v7 = static_cast<lgsv_ckpt_msg_v7_t
> *>(ckpt_rec);
> +    ckpt_rec_type = ckpt_rec_v7->header.ckpt_rec_type;
> +  } else if (lgs_is_peer_v5()) {
>      lgsv_ckpt_msg_v5_t *ckpt_rec_v5 = static_cast<lgsv_ckpt_msg_v5_t
> *>(ckpt_rec);
>      ckpt_rec_type = ckpt_rec_v5->header.ckpt_rec_type;
>    } else if (lgs_is_peer_v4()) {
> diff --git a/src/log/logd/lgs_mbcsv.h b/src/log/logd/lgs_mbcsv.h
> --- a/src/log/logd/lgs_mbcsv.h
> +++ b/src/log/logd/lgs_mbcsv.h
> @@ -2,6 +2,7 @@
>   * File:   lgs_mbcsv.h
>   *
>   * (C) Copyright 2008 The OpenSAF Foundation
> + * Copyright Ericsson AB 2014, 2017 - All Rights Reserved.
[Lennart] Incorrect copyright year
>   *
>   * This program is distributed in the hope that it will be useful, but
>   * WITHOUT ANY WARRANTY; without even the implied warranty of
> MERCHANTABILITY
> @@ -38,9 +39,11 @@
>  #define LGS_MBCSV_VERSION_2 2
>  #define LGS_MBCSV_VERSION_4 4
>  #define LGS_MBCSV_VERSION_5 5
> +#define LGS_MBCSV_VERSION_7 7
> +
> 
>  /* Current version */
> -#define LGS_MBCSV_VERSION 5
> +#define LGS_MBCSV_VERSION 7
>  #define LGS_MBCSV_VERSION_MIN 1
> 
>  /* Checkpoint message types(Used as 'reotype' w.r.t mbcsv)  */
> @@ -106,6 +109,7 @@ bool lgs_is_peer_v2();
>  bool lgs_is_peer_v3();
>  bool lgs_is_peer_v4();
>  bool lgs_is_peer_v5();
> +bool lgs_is_peer_v7();
>  bool lgs_is_split_file_system();
>  uint32_t lgs_mbcsv_dispatch(NCS_MBCSV_HDL mbcsv_hdl);
>  void lgs_free_edu_mem(char *ptr);
> diff --git a/src/log/logd/lgs_mbcsv_v5.cc b/src/log/logd/lgs_mbcsv_v5.cc
> --- a/src/log/logd/lgs_mbcsv_v5.cc
> +++ b/src/log/logd/lgs_mbcsv_v5.cc
> @@ -1,6 +1,7 @@
>  /*      -*- OpenSAF  -*-
>   *
>   * (C) Copyright 2015 The OpenSAF Foundation
> + * Copyright Ericsson AB 2015, 2017 - All Rights Reserved.
>   * File:   lgs_mbcsv_v5.c
>   *
>   * This program is distributed in the hope that it will be useful, but
> @@ -22,6 +23,7 @@
>   */
> 
>  #include "lgs_mbcsv_v5.h"
> +#include "log/logd/lgs_dest.h"
> 
> 
> /**********************************************************
> ******************
>   * Name          : ckpt_proc_lgs_cfg
> @@ -132,6 +134,14 @@ uint32_t ckpt_proc_lgs_cfg_v5(lgs_cb_t *
>      TRACE("\tUpdating mailbox limits");
>      (void) lgs_configure_mailbox();
>    }
> +  // Only support destination configuration since V7
> +  if (lgs_is_peer_v7()) {
> +    const VectorString *vdest = nullptr;
> +    vdest = reinterpret_cast<const VectorString*>(
> +
> lgs_cfg_get(LGS_IMM_LOG_RECORD_DESTINATION_CONFIGURATION));
> +    osafassert(vdest != nullptr);
> +    CfgDestination(*vdest, ModifyType::kReplace);
> +  }
> 
>    /* Free buffer allocated by the EDU encoder */
>    if (param->buffer == NULL) {
> diff --git a/src/log/logd/lgs_mbcsv_v7.cc b/src/log/logd/lgs_mbcsv_v7.cc
> new file mode 100644
> --- /dev/null
> +++ b/src/log/logd/lgs_mbcsv_v7.cc
> @@ -0,0 +1,177 @@
> +/*      -*- OpenSAF  -*-
> + *
> + * (C) Copyright 2017 The OpenSAF Foundation
> + * Copyright Ericsson AB 2017 - All Rights Reserved.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> MERCHANTABILITY
> + * or FITNESS FOR A PARTICULAR PURPOSE. This file and program are
> licensed
> + * under the GNU Lesser General Public License Version 2.1, February 1999.
> + * The complete license can be accessed from the following location:
> + * http://opensource.org/licenses/lgpl-license.php
> + * See the Copying file included with the OpenSAF distribution for full
> + * licensing terms.
> + *
> + * Author(s): Ericsson AB
> + *
> + */
> +
> +#include "log/logd/lgs_mbcsv_v7.h"
> +
> +/*********************************************************
> *******************
> + * Name          : edp_ed_cfg_stream_rec
> + *
> + * Description   : This function is an EDU program for encoding/decoding
> + *                 lgsv checkpoint cfg_update_stream log rec.
> + *
> + * Arguments     : EDU_HDL - pointer to edu handle,
> + *                 EDU_TKN - internal edu token to help encode/decode,
> + *                 POINTER to the structure to encode/decode from/to,
> + *                 data length specifying number of structures,
> + *                 EDU_BUF_ENV - pointer to buffer for encoding/decoding.
> + *                 op - operation type being encode/decode.
> + *                 EDU_ERR - out param to indicate errors in processing.
> + *
> + * Return Values : NCSCC_RC_SUCCESS/NCSCC_RC_FAILURE
> + *
> + * Notes         : None.
> +
> **********************************************************
> *******************/
> +
> +uint32_t edp_ed_cfg_stream_rec_v7(EDU_HDL *edu_hdl, EDU_TKN
> *edu_tkn,
> +                                  NCSCONTEXT ptr, uint32_t *ptr_data_len,
> +                                  EDU_BUF_ENV *buf_env, EDP_OP_TYPE op, 
> EDU_ERR
> *o_err) {
> +  uint32_t rc = NCSCC_RC_SUCCESS;
> +  lgs_ckpt_stream_cfg_v3_t *ckpt_stream_cfg_msg_ptr = NULL,
> **ckpt_stream_cfg_msg_dec_ptr;
> +
> +  EDU_INST_SET ckpt_stream_cfg_rec_ed_rules[] = {
> +    {EDU_START, edp_ed_cfg_stream_rec_v7, 0, 0, 0,
> sizeof(lgs_ckpt_stream_cfg_v3_t), 0, NULL},
> +    {EDU_EXEC, ncs_edp_string, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->name, 0, NULL},
> +    {EDU_EXEC, ncs_edp_string, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->fileName, 0, NULL},
> +    {EDU_EXEC, ncs_edp_string, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->pathName, 0, NULL},
> +    {EDU_EXEC, ncs_edp_uns64, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->maxLogFileSize, 0, NULL},
> +    {EDU_EXEC, ncs_edp_uns32, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->fixedLogRecordSize, 0, NULL},
> +    {EDU_EXEC, ncs_edp_uns32, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->haProperty, 0, NULL},
> +    {EDU_EXEC, ncs_edp_uns32, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->logFullAction, 0, NULL},
> +    {EDU_EXEC, ncs_edp_uns32, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->logFullHaltThreshold, 0, NULL},
> +    {EDU_EXEC, ncs_edp_uns32, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->maxFilesRotated, 0, NULL},
> +    {EDU_EXEC, ncs_edp_string, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->logFileFormat, 0, NULL},
> +    {EDU_EXEC, ncs_edp_uns32, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->severityFilter, 0, NULL},
> +    {EDU_EXEC, ncs_edp_string, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->logFileCurrent, 0, NULL},
> +    {EDU_EXEC, ncs_edp_string, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->dest_names, 0, NULL},
> +    {EDU_EXEC, ncs_edp_uns64, 0, 0, 0, (long)&((lgs_ckpt_stream_cfg_v3_t
> *)0)->c_file_close_time_stamp, 0, NULL},
> +    {EDU_END, 0, 0, 0, 0, 0, 0, NULL},
> +  };
> +
> +  if (op == EDP_OP_TYPE_ENC) {
> +    ckpt_stream_cfg_msg_ptr = static_cast<lgs_ckpt_stream_cfg_v3_t
> *>(ptr);
> +  } else if (op == EDP_OP_TYPE_DEC) {
> +    ckpt_stream_cfg_msg_dec_ptr = static_cast<lgs_ckpt_stream_cfg_v3_t
> **>(ptr);
> +    if (*ckpt_stream_cfg_msg_dec_ptr == NULL) {
> +      *o_err = EDU_ERR_MEM_FAIL;
> +      return NCSCC_RC_FAILURE;
> +    }
> +    memset(*ckpt_stream_cfg_msg_dec_ptr, '\0',
> sizeof(lgs_ckpt_stream_cfg_v3_t));
> +    ckpt_stream_cfg_msg_ptr = *ckpt_stream_cfg_msg_dec_ptr;
> +  } else {
> +    ckpt_stream_cfg_msg_ptr = static_cast<lgs_ckpt_stream_cfg_v3_t
> *>(ptr);
> +  }
> +
> +  rc = m_NCS_EDU_RUN_RULES(edu_hdl, edu_tkn,
> +                           ckpt_stream_cfg_rec_ed_rules,
> +                           ckpt_stream_cfg_msg_ptr,
> +                           ptr_data_len,
> +                           buf_env, op, o_err);
> +  return rc;
> +}
> +
> +/*********************************************************
> *******************
> + * Name          : edp_ed_ckpt_msg_v7
> + *
> + * Description   : This function is an EDU program for encoding/decoding
> + *                 lgsv checkpoint messages. This program runs the
> + *                 edp_ed_hdr_rec program first to decide the
> + *                 checkpoint message type based on which it will call the
> + *                 appropriate EDU programs for the different checkpoint
> + *                 messages.
> + *
> + * Arguments     : EDU_HDL - pointer to edu handle,
> + *                 EDU_TKN - internal edu token to help encode/decode,
> + *                 POINTER to the structure to encode/decode from/to,
> + *                 data length specifying number of structures,
> + *                 EDU_BUF_ENV - pointer to buffer for encoding/decoding.
> + *                 op - operation type being encode/decode.
> + *                 EDU_ERR - out param to indicate errors in processing.
> + *
> + * Return Values : NCSCC_RC_SUCCESS/NCSCC_RC_FAILURE
> + *
> + * Notes         : None.
> +
> **********************************************************
> *******************/
> +
> +uint32_t edp_ed_ckpt_msg_v7(EDU_HDL *edu_hdl, EDU_TKN *edu_tkn,
> +                            NCSCONTEXT ptr, uint32_t *ptr_data_len, 
> EDU_BUF_ENV
> *buf_env,
> +                            EDP_OP_TYPE op, EDU_ERR *o_err) {
> +  uint32_t rc = NCSCC_RC_SUCCESS;
> +  lgsv_ckpt_msg_v7_t *ckpt_msg_ptr = NULL, **ckpt_msg_dec_ptr;
> +
> +  EDU_INST_SET ckpt_msg_ed_rules[] = {
> +    {EDU_START, edp_ed_ckpt_msg_v7, 0, 0, 0, sizeof(lgsv_ckpt_msg_v7_t),
> 0, NULL},
> +    {EDU_EXEC, edp_ed_header_rec, 0, 0, 0, (long)&((lgsv_ckpt_msg_v7_t
> *)0)->header, 0, NULL},
> +
> +    {EDU_TEST, ncs_edp_uns32, 0, 0, 0, (long)&((lgsv_ckpt_msg_v7_t *)0)-
> >header, 0,
> +     (EDU_EXEC_RTINE)ckpt_msg_test_type},
> +
> +    /* Reg Record */
> +    {EDU_EXEC, edp_ed_reg_rec, 0, 0, static_cast<int>(EDU_EXIT),
> +     (long)&((lgsv_ckpt_msg_v7_t *)0)->ckpt_rec.initialize_client, 0, NULL},
> +
> +    /* Finalize record */
> +    {EDU_EXEC, edp_ed_finalize_rec_v2, 0, 0, static_cast<int>(EDU_EXIT),
> +     (long)&((lgsv_ckpt_msg_v7_t *)0)->ckpt_rec.finalize_client, 0, NULL},
> +
> +    /* write log Record */
> +    {EDU_EXEC, edp_ed_write_rec_v2, 0, 0, static_cast<int>(EDU_EXIT),
> +     (long)&((lgsv_ckpt_msg_v7_t *)0)->ckpt_rec.write_log, 0, NULL},
> +
> +    /* Open stream */
> +    {EDU_EXEC, edp_ed_open_stream_rec, 0, 0, static_cast<int>(EDU_EXIT),
> +     (long)&((lgsv_ckpt_msg_v7_t *)0)->ckpt_rec.stream_open, 0, NULL},
> +
> +    /* Close stream */
> +    {EDU_EXEC, edp_ed_close_stream_rec_v2, 0, 0,
> static_cast<int>(EDU_EXIT),
> +     (long)&((lgsv_ckpt_msg_v7_t *)0)->ckpt_rec.stream_close, 0, NULL},
> +
> +    /* Agent dest */
> +    {EDU_EXEC, edp_ed_agent_down_rec_v2, 0, 0,
> static_cast<int>(EDU_EXIT),
> +     (long)&((lgsv_ckpt_msg_v7_t *)0)->ckpt_rec.stream_cfg, 0, NULL},
> +
> +    /* Cfg stream */
> +    {EDU_EXEC, edp_ed_cfg_stream_rec_v7, 0, 0,
> static_cast<int>(EDU_EXIT),
> +     (long)&((lgsv_ckpt_msg_v7_t *)0)->ckpt_rec.stream_cfg, 0, NULL},
> +
> +    /* Lgs cfg */
> +    {EDU_EXEC, edp_ed_lgs_cfg_rec_v5, 0, 0, static_cast<int>(EDU_EXIT),
> +     (long)&((lgsv_ckpt_msg_v7_t *)0)->ckpt_rec.lgs_cfg, 0, NULL},
> +
> +    {EDU_END, 0, 0, 0, 0, 0, 0, NULL},
> +  };
> +
> +  if (op == EDP_OP_TYPE_ENC) {
> +    ckpt_msg_ptr = static_cast<lgsv_ckpt_msg_v7_t *>(ptr);
> +  } else if (op == EDP_OP_TYPE_DEC) {
> +    ckpt_msg_dec_ptr = static_cast<lgsv_ckpt_msg_v7_t **>(ptr);
> +    if (*ckpt_msg_dec_ptr == NULL) {
> +      *o_err = EDU_ERR_MEM_FAIL;
> +      return NCSCC_RC_FAILURE;
> +    }
> +    memset(*ckpt_msg_dec_ptr, '\0', sizeof(lgsv_ckpt_msg_v7_t));
> +    ckpt_msg_ptr = *ckpt_msg_dec_ptr;
> +  } else {
> +    ckpt_msg_ptr = static_cast<lgsv_ckpt_msg_v7_t *>(ptr);
> +  }
> +
> +  rc = m_NCS_EDU_RUN_RULES(edu_hdl, edu_tkn, ckpt_msg_ed_rules,
> +                           ckpt_msg_ptr, ptr_data_len, buf_env, op, o_err);
> +
> +  return rc;
> +
> +}
> diff --git a/src/log/logd/lgs_mbcsv_v7.h b/src/log/logd/lgs_mbcsv_v7.h
> new file mode 100644
> --- /dev/null
> +++ b/src/log/logd/lgs_mbcsv_v7.h
> @@ -0,0 +1,67 @@
> +/*      -*- OpenSAF  -*-
> + *
> + * (C) Copyright 2017 The OpenSAF Foundation
> + * Copyright Ericsson AB 2017 - All Rights Reserved.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> MERCHANTABILITY
> + * or FITNESS FOR A PARTICULAR PURPOSE. This file and program are
> licensed
> + * under the GNU Lesser General Public License Version 2.1, February 1999.
> + * The complete license can be accessed from the following location:
> + * http://opensource.org/licenses/lgpl-license.php
> + * See the Copying file included with the OpenSAF distribution for full
> + * licensing terms.
> + *
> + * Author(s): Ericsson AB
> + *
> + */
> +
> +#ifndef SRC_LOG_LOGD_LGS_MBCSV_V7_H_
> +#define SRC_LOG_LOGD_LGS_MBCSV_V7_H_
> +
> +#include "log/logd/lgs.h"
> +#include "log/logd/lgs_config.h"
> +#include "log/logd/lgs_mbcsv.h"
> +#include "log/logd/lgs_mbcsv_v2.h"
> +#include "log/logd/lgs_mbcsv_v5.h"
> +
> +/* Structures for Checkpoint data ver 7 (to be replicated at the standby) */
> +
> +typedef struct {
> +  char *name;
> +  char *fileName;
> +  char *pathName;
> +  SaUint64T maxLogFileSize;
> +  SaUint32T fixedLogRecordSize;
> +  SaBoolT haProperty;     /* app log stream only */
> +  SaLogFileFullActionT logFullAction;
> +  SaUint32T logFullHaltThreshold; /* !app log stream */
> +  SaUint32T maxFilesRotated;
> +  char *logFileFormat;
> +  SaUint32T severityFilter;
> +  char *logFileCurrent;
> +  char *dest_names;
> +  uint64_t c_file_close_time_stamp; /* Time in sec for file rename on Active
> */
> +} lgs_ckpt_stream_cfg_v3_t;
> +
> +typedef struct {
> +  lgsv_ckpt_header_t header;
> +  union {
> +    lgs_ckpt_initialize_msg_t initialize_client;
> +    lgs_ckpt_finalize_msg_v2_t finalize_client;
> +    lgs_ckpt_write_log_v2_t write_log;
> +    lgs_ckpt_agent_down_v2_t agent_down;
> +    lgs_ckpt_stream_open_t stream_open;
> +    lgs_ckpt_stream_close_v2_t stream_close;
> +    lgs_ckpt_stream_cfg_v3_t stream_cfg;
> +    lgs_ckpt_lgs_cfg_v5_t lgs_cfg;
> +  } ckpt_rec;
> +} lgsv_ckpt_msg_v7_t;
> +
> +uint32_t edp_ed_cfg_stream_rec_v7(EDU_HDL *edu_hdl, EDU_TKN
> *edu_tkn,
> +                                  NCSCONTEXT ptr, uint32_t *ptr_data_len,
> +                                  EDU_BUF_ENV *buf_env, EDP_OP_TYPE op, 
> EDU_ERR
> *o_err);
> +uint32_t edp_ed_ckpt_msg_v7(EDU_HDL *edu_hdl, EDU_TKN *edu_tkn,
> +                            NCSCONTEXT ptr, uint32_t *ptr_data_len, 
> EDU_BUF_ENV
> *buf_env, EDP_OP_TYPE op, EDU_ERR *o_err);
> +
> +#endif  // SRC_LOG_LOGD_LGS_MBCSV_V7_H_
> diff --git a/src/log/logd/lgs_stream.cc b/src/log/logd/lgs_stream.cc
> --- a/src/log/logd/lgs_stream.cc
> +++ b/src/log/logd/lgs_stream.cc
> @@ -27,6 +27,9 @@
>   * Examples can be found in this file, e.g. function fileopen(...) below
>   */
> 
> +#include "log/logd/lgs_stream.h"
> +#include <algorithm>
> +
>  #include "log/logd/lgs.h"
>  #include "lgs_config.h"
>  #include "log/logd/lgs_file.h"
> @@ -480,7 +483,6 @@ int lgs_populate_log_stream(
>    o_stream->logRecordId = logRecordId;
>    o_stream->stb_logRecordId = 0;
>    o_stream->isRtStream = SA_TRUE;
> -
>    o_stream->logFileFormat = strdup(logFileFormat);
>    if (o_stream->logFileFormat == NULL) {
>      LOG_WA("Failed to allocate memory for logFileFormat");
> @@ -654,6 +656,7 @@ log_stream_t *log_stream_new(const std::
>    stream->creationTimeStamp = lgs_get_SaTime();
>    stream->severityFilter = 0x7f;  /* by default all levels are allowed */
>    stream->isRtStream = SA_FALSE;
> +  stream->dest_names.clear();
> 
>    /* Initiate local or shared stream file descriptor dependant on shared or
>     * split file system
> @@ -1492,3 +1495,58 @@ bool check_max_stream() {
>    return (numb_of_streams < stream_array_size ? false:true);
>  }
> 
> +void log_stream_add_dest_name(log_stream_t *stream,
> +                              const std::vector<std::string>& names) {
> +  osafassert(stream != nullptr);
> +  for (const auto& it : names) {
> +    stream->dest_names.push_back(it);
> +  }
> +
> +  // Prepare stb_dest_names for checkingpoint to standby
> +  log_stream_form_dest_names(stream);
> +}
> +
> +void log_stream_replace_dest_name(log_stream_t *stream,
> +                                  const std::vector<std::string>& names) {
> +  osafassert(stream != nullptr);
> +  stream->dest_names = names;
> +  // Prepare stb_dest_names for checkingpoint to standby
> +  log_stream_form_dest_names(stream);
> +}
> +
> +void log_stream_delete_dest_name(log_stream_t *stream,
> +                                 const std::vector<std::string>& names) {
> +  osafassert(stream != nullptr);
> +  if (names.size() != 0) {
> +    for (const auto& it : names) {
> +      // Remove deleted destination from internal database
> +      stream->dest_names.erase(std::remove(stream->dest_names.begin(),
> +                                           stream->dest_names.end(), it),
> +                               stream->dest_names.end());
> +    }
> +  } else {
> +    // Delete all destination names
> +    stream->dest_names.clear();
> +  }
> +
> +  // Prepare stb_dest_names for checkingpoint to standby
> +  log_stream_form_dest_names(stream);
> +}
> +
> +void log_stream_form_dest_names(log_stream_t* stream) {
> +  osafassert(stream != nullptr);
> +  std::string output{""};
> +  bool addSemicolon = false;
> +  // Form stb_dest_names before checking point to standby
> +  // under format "name1;name2;etc".
> +  for (const auto& it : stream->dest_names) {
> +    output += it + ";";
> +    addSemicolon = true;
> +  }
> +  if (addSemicolon == true) {
> +    osafassert(output.length() > 0);
> +    // Remove last semicolon(;)
> +    output[output.length() - 1] = '\0';
> +  }
> +  stream->stb_dest_names = output;
> +}
> diff --git a/src/log/logd/lgs_stream.h b/src/log/logd/lgs_stream.h
> --- a/src/log/logd/lgs_stream.h
> +++ b/src/log/logd/lgs_stream.h
> @@ -1,6 +1,7 @@
>  /*      -*- OpenSAF  -*-
>   *
>   * (C) Copyright 2008 The OpenSAF Foundation
> + * Copyright Ericsson AB 2014, 2017 - All Rights Reserved.
[Lennart] Incorrect copyright year
>   *
>   * This program is distributed in the hope that it will be useful, but
>   * WITHOUT ANY WARRANTY; without even the implied warranty of
> MERCHANTABILITY
> @@ -22,6 +23,7 @@
>  #include "base/ncspatricia.h"
>  #include <time.h>
>  #include <limits.h>
> +#include <vector>
> 
>  #include "lgs_fmt.h"
>  #include "base/osaf_extended_name.h"
> @@ -83,6 +85,12 @@ typedef struct log_stream {
>    std::string stb_logFileCurrent; /* Current file name used on standby */
>    std::string stb_prev_actlogFileCurrent; /* current file name on active when
> previous record was written */
>    uint32_t stb_curFileSize;       /* Bytes written to current log file */
> +
> +  // Hold vector of destname string {"name1", "name2", etc.}
> +  std::vector<std::string> dest_names;
> +  // Hold a list of strings separated by semicolon "name1;name2;etc"
> +  // This data is used to checkpoint to standby
> +  std::string stb_dest_names;
>  } log_stream_t;
> 
>  extern uint32_t log_stream_init();
> @@ -133,4 +141,12 @@ void log_free_stream_resources(log_strea
>  log_stream_t *iterate_all_streams(SaBoolT &end, SaBoolT jstart);
>  extern log_stream_t *log_stream_get_by_name(const std::string &name);
> 
> +void log_stream_add_dest_name(log_stream_t *stream,
> +                              const std::vector<std::string>& names);
> +void log_stream_replace_dest_name(log_stream_t *stream,
> +                                  const std::vector<std::string>& names);
> +void log_stream_delete_dest_name(log_stream_t *stream,
> +                                 const std::vector<std::string>& names);
> +void log_stream_form_dest_names(log_stream_t* stream);
> +
>  #endif  // LOG_LOGD_LGS_STREAM_H_
> diff --git a/src/log/logd/lgs_util.cc b/src/log/logd/lgs_util.cc
> --- a/src/log/logd/lgs_util.cc
> +++ b/src/log/logd/lgs_util.cc
> @@ -835,3 +835,66 @@ bool lgs_is_extended_name_valid(const Sa
> 
>    return true;
>  }
> +
> +//========================================================
> ======================
> +// logutil namespace
> +//========================================================
> ======================
> +namespace logutil {
> +
> +// Split a string @str with delimiter @delimiter
> +// and return an vector of strings.
> +std::vector<std::string> Parser(const std::string& str,
> +                                const std::string& delimiter) {
> +  std::vector<std::string> temp;
> +  std::string s{str};
> +  size_t pos = 0;
> +  while ((pos = s.find(delimiter)) != std::string::npos) {
> +    temp.push_back(s.substr(0, pos));
> +    s.erase(0, pos + delimiter.length());
> +  }
> +  temp.push_back(s);
> +  return temp;
> +}
> +
> +bool isUnixName(const std::string& name) {
> +  int index = 0;
> +  char c;
> +
> +  while ((c = name[index++]) != '\0') {
> +    switch (c) {
> +      case '/':
> +      case '|':
> +      case ';':
> +      case ',':
> +      case '!':
> +      case '@':
> +      case '#':
> +      case '$':
> +      case '(':
> +      case ')':
> +      case '<':
> +      case '>':
> +      case '\\':
> +      case '"':
> +      case '\'':
> +      case '`':
> +      case '~':
> +      case '{':
> +      case '}':
> +      case '[':
> +      case ']':
> +      case '+':
> +      case '&':
> +      case '^':
> +      case '?':
> +      case '*':
> +      case '%':
> +        return false;
> +      default:
> +        break;
> +    }
> +  }
> +  return true;
> +}
[Lennart] Seems complicated why not check allowed characters instead?
Example (have not checked if this works)
std::string  x(/*Load*/);
if 
(x.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_")
 != std::string::npos)
{
    std::cerr << "Error\n";
}
> +
> +};  // namespace logutil
> diff --git a/src/log/logd/lgs_util.h b/src/log/logd/lgs_util.h
> --- a/src/log/logd/lgs_util.h
> +++ b/src/log/logd/lgs_util.h
> @@ -26,7 +26,7 @@
> 
>  #include "osaf/saf/saAis.h"
>  #include "amf/saf/saAmf.h"
> -
> +#include <vector>
>  #include "lgs_stream.h"
>  #include "lgs_evt.h"
> 
> @@ -83,4 +83,13 @@ void lgs_close_timer(int ufd);
> 
>  bool lgs_is_extended_name_valid(const SaNameT* name);
> 
> +namespace logutil {
> +// Parse string format "a;b;c" to vector of string {a, b, c}
> +// In generic, this function could be used to split a string
> +// separated by delimiter to a vector of strings.
> +std::vector<std::string> Parser(const std::string&, const std::string&);
> +// Check if @name contain special characters in.
> +bool isUnixName(const std::string& name);
> +};  // namespace logutil
> +
>  #endif  // LOG_LOGD_LGS_UTIL_H_

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, SlashDot.org! http://sdm.link/slashdot
_______________________________________________
Opensaf-devel mailing list
Opensaf-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/opensaf-devel

Reply via email to