/***************************************************************************//**
  @file ltrsrv_config.c
  @author Borisov Alexey <borisov@lcard.ru>
  @date   11.02.2012

  Файл содержит функции для разбора конфигурационного xml файла настроек и
  сохранения в него текущих параметров сервера
  *****************************************************************************/
#include <string.h>
#include <libxml/parser.h>
#include <libxml/tree.h>

#include "ltrsrv_defs.h"
#include "ltrsrv_log.h"
#include "ltrsrv_wait.h"
#include "ltrsrv_eth.h"


#define LTRSRV_SETTINGS_ROOT_ELEM_NAME  "ltrsrv_config"
#define LTRSRV_SETTINGS_GROUP_LOG       "log"
#define LTRSRV_SETTINGS_PARAM_LOG_LVL   "level"
#define LTRSRV_SETTINGS_PARAM_LOG_FILE  "file"

#define LTRSRV_SETTINGS_GROUP_CRATES_IP     "crate_ip_entries"
#define LTRSRV_SETTINGS_PARAM_CRATE_IP      "ip"
#define LTRSRV_SETTINGS_IP_ATTR_AUTOCON     "autocon"
#define LTRSRV_SETTINGS_IP_ATTR_RECONNECT   "reconnect"

#define LTRSRV_SETTINGS_GROUP_INTF_ETH_PAR  "intf_eth_params"
#define LTRSRV_SETTINGS_PARAM_ETH_POLL_TIME "crate_poll_time"
#define LTRSRV_SETTINGS_PARAM_ETH_CON_TIMEOUT_OLD "crate_conection_timeout"
#define LTRSRV_SETTINGS_PARAM_ETH_CON_TIMEOUT "crate_connection_timeout"
#define LTRSRV_SETTINGS_PARAM_ETH_CTLCMD_TIMEOUT "crate_ctlcmd_timeout"
#define LTRSRV_SETTINGS_PARAM_ETH_INTF_CHECK_TIME "intf_check_time"
#define LTRSRV_SETTINGS_PARAM_ETH_RECONNECT_TIME "crate_reconnect_time"
#define LTRSRV_SETTINGS_PARAM_ETH_DATA_NODELAY    "tcp_data_nodelay"


#define LTRSRV_SETTINGS_GROUP_CRATE_PARAMS                "crate_params"
#define LTRSRV_SETTINGS_PARAM_CRATE_MODULE_SEND_BUF_SIZE   "module_send_buf_size"
#define LTRSRV_SETTINGS_PARAM_CRATE_MODULE_RECV_BUF_SIZE   "module_recv_buf_size"

static int f_modified = 0;
static const char* f_filename=NULL;



/* если не разрешены функции создания дерева xml в libxml2 =>
 * реализуем их вручную */
#if  !defined(LIBXML_TREE_ENABLED) && !defined(LIBXML_SCHEMAS_ENABLED)
static xmlAttrPtr
xmlNewPropInternal(xmlNodePtr node, xmlNsPtr ns,
                   const xmlChar * name, const xmlChar * value,
                   int eatname) {
    xmlAttrPtr cur;
    xmlDocPtr doc = NULL;

    if ((node != NULL) && (node->type != XML_ELEMENT_NODE)) {
        if ((eatname == 1) &&
        ((node->doc == NULL) ||
         (!(xmlDictOwns(node->doc->dict, name)))))
            xmlFree((xmlChar *) name);
        return (NULL);
    }

    /*
     * Allocate a new property and fill the fields.
     */
    cur = (xmlAttrPtr) xmlMalloc(sizeof(xmlAttr));
    if (cur == NULL) {
        if ((eatname == 1) &&
        ((node == NULL) || (node->doc == NULL) ||
         (!(xmlDictOwns(node->doc->dict, name)))))
            xmlFree((xmlChar *) name);
        //xmlTreeErrMemory("building attribute");
        return (NULL);
    }
    memset(cur, 0, sizeof(xmlAttr));
    cur->type = XML_ATTRIBUTE_NODE;

    cur->parent = node;
    if (node != NULL) {
        doc = node->doc;
        cur->doc = doc;
    }
    cur->ns = ns;

    if (eatname == 0) {
        if ((doc != NULL) && (doc->dict != NULL))
            cur->name = (xmlChar *) xmlDictLookup(doc->dict, name, -1);
        else
            cur->name = xmlStrdup(name);
    } else
        cur->name = name;

    if (value != NULL) {
        xmlNodePtr tmp;

        if(!xmlCheckUTF8(value)) {
            //xmlTreeErr(XML_TREE_NOT_UTF8, (xmlNodePtr) doc,
            //           NULL);
            if (doc != NULL)
                doc->encoding = xmlStrdup(BAD_CAST "ISO-8859-1");
        }
        cur->children = xmlNewDocText(doc, value);
        cur->last = NULL;
        tmp = cur->children;
        while (tmp != NULL) {
            tmp->parent = (xmlNodePtr) cur;
            if (tmp->next == NULL)
                cur->last = tmp;
            tmp = tmp->next;
        }
    }

    /*
     * Add it at the end to preserve parsing order ...
     */
    if (node != NULL) {
        if (node->properties == NULL) {
            node->properties = cur;
        } else {
            xmlAttrPtr prev = node->properties;

            while (prev->next != NULL)
                prev = prev->next;
            prev->next = cur;
            cur->prev = prev;
        }
    }

    if ((value != NULL) && (node != NULL) &&
        (xmlIsID(node->doc, node, cur) == 1))
        xmlAddID(NULL, node->doc, value, cur);

    //if ((__xmlRegisterCallbacks) && (xmlRegisterNodeDefaultValue))
    //    xmlRegisterNodeDefaultValue((xmlNodePtr) cur);
    return (cur);
}


/**
 * xmlNewProp:
 * @node:  the holding node
 * @name:  the name of the attribute
 * @value:  the value of the attribute
 *
 * Create a new property carried by a node.
 * Returns a pointer to the attribute
 */
xmlAttrPtr
xmlNewProp(xmlNodePtr node, const xmlChar *name, const xmlChar *value) {

    if (name == NULL) {
#ifdef DEBUG_TREE
        xmlGenericError(xmlGenericErrorContext,
        "xmlNewProp : name == NULL\n");
#endif
    return(NULL);
    }

    return xmlNewPropInternal(node, NULL, name, value, 0);
}


/**
 * xmlNewChild:
 * @parent:  the parent node
 * @ns:  a namespace if any
 * @name:  the name of the child
 * @content:  the XML content of the child if any.
 *
 * Creation of a new child element, added at the end of @parent children list.
 * @ns and @content parameters are optional (NULL). If @ns is NULL, the newly
 * created element inherits the namespace of @parent. If @content is non NULL,
 * a child list containing the TEXTs and ENTITY_REFs node will be created.
 * NOTE: @content is supposed to be a piece of XML CDATA, so it allows entity
 *       references. XML special chars must be escaped first by using
 *       xmlEncodeEntitiesReentrant(), or xmlNewTextChild() should be used.
 *
 * Returns a pointer to the new node object.
 */
/**
 * xmlNewChild:
 * @parent:  the parent node
 * @ns:  a namespace if any
 * @name:  the name of the child
 * @content:  the XML content of the child if any.
 *
 * Creation of a new child element, added at the end of @parent children list.
 * @ns and @content parameters are optional (NULL). If @ns is NULL, the newly
 * created element inherits the namespace of @parent. If @content is non NULL,
 * a child list containing the TEXTs and ENTITY_REFs node will be created.
 * NOTE: @content is supposed to be a piece of XML CDATA, so it allows entity
 *       references. XML special chars must be escaped first by using
 *       xmlEncodeEntitiesReentrant(), or xmlNewTextChild() should be used.
 *
 * Returns a pointer to the new node object.
 */
xmlNodePtr
xmlNewChild(xmlNodePtr parent, xmlNsPtr ns,
            const xmlChar *name, const xmlChar *content) {
    xmlNodePtr cur, prev;

    if (parent == NULL) {
#ifdef DEBUG_TREE
        xmlGenericError(xmlGenericErrorContext,
        "xmlNewChild : parent == NULL\n");
#endif
    return(NULL);
    }

    if (name == NULL) {
#ifdef DEBUG_TREE
        xmlGenericError(xmlGenericErrorContext,
        "xmlNewChild : name == NULL\n");
#endif
    return(NULL);
    }

    /*
     * Allocate a new node
     */
    if (parent->type == XML_ELEMENT_NODE) {
    if (ns == NULL)
        cur = xmlNewDocNode(parent->doc, parent->ns, name, content);
    else
        cur = xmlNewDocNode(parent->doc, ns, name, content);
    } else if ((parent->type == XML_DOCUMENT_NODE) ||
           (parent->type == XML_HTML_DOCUMENT_NODE)) {
    if (ns == NULL)
        cur = xmlNewDocNode((xmlDocPtr) parent, NULL, name, content);
    else
        cur = xmlNewDocNode((xmlDocPtr) parent, ns, name, content);
    } else if (parent->type == XML_DOCUMENT_FRAG_NODE) {
        cur = xmlNewDocNode( parent->doc, ns, name, content);
    } else {
    return(NULL);
    }
    if (cur == NULL) return(NULL);

    /*
     * add the new element at the end of the children list.
     */
    cur->type = XML_ELEMENT_NODE;
    cur->parent = parent;
    cur->doc = parent->doc;
    if (parent->children == NULL) {
        parent->children = cur;
    parent->last = cur;
    } else {
        prev = parent->last;
    prev->next = cur;
    cur->prev = prev;
    parent->last = cur;
    }

    return(cur);
}
#endif


/* убирем пробелы в начале и в конце строки */
static xmlChar* f_trim_str(xmlChar* str) {
    xmlChar* end_str, *nospace;
    for (; *str && *str==' '; str++)
    {}

    for (end_str = str, nospace = str; *end_str; end_str++) {
        if (*end_str!=' ')
            nospace = end_str;
    }

    if (*nospace && *(nospace+1)) {
        *(nospace+1) = 0;
    }
    return str;
}

/* Получаем текст из XML-узла */
static xmlChar* f_get_node_text(xmlNode* node) {
    xmlChar* ret = NULL;
    for (node=node->children; node; node=node->next) {
        if ((node->type == XML_TEXT_NODE) && !xmlIsBlankNode(node)) {
            ret = node->content;
        }
    }
    return ret;
}

static void f_load_log(xmlNode *log_group) {
    xmlNode *param = log_group->children;
    for (;param; param=param->next) {
        if (param->type == XML_ELEMENT_NODE) {
            xmlChar* val = f_get_node_text(param);
            if (!strcmp((const char*)param->name, LTRSRV_SETTINGS_PARAM_LOG_LVL)) {
                if (val)
                    ltrsrv_log_set_lvl((t_ltrsrv_log_level)atoi((char*)val), 1, 0);
            } else if (!strcmp((const char*)param->name, LTRSRV_SETTINGS_PARAM_LOG_FILE)) {
                if (val)
                    ltrsrv_log_set_file((char*)f_trim_str(val));
            }
        }
    }
}

static void f_load_crates_ip(xmlNode *ip_group) {
    xmlNode *ip = ip_group->children;
    for (;ip; ip=ip->next) {
        if ((ip->type == XML_ELEMENT_NODE) &&
                !strcmp((const char*)ip->name, LTRSRV_SETTINGS_PARAM_CRATE_IP)) {
            xmlChar* ip_val = f_trim_str(f_get_node_text(ip));
            uint32_t addr;
            uint32_t flags = 0;

            if (inet_pton(AF_INET, (char*)ip_val, &addr)==1) {
                xmlAttr* attr;
                for (attr = ip->properties; attr; attr=attr->next) {
                    if (!strcmp((const char*)attr->name, LTRSRV_SETTINGS_IP_ATTR_AUTOCON)) {
                        int val = atoi((const char*)attr->children->content);
                        if (val) {
                            flags |= LTR_CRATE_IP_FLAG_AUTOCONNECT;
                        }
                    } else if (!strcmp((const char*)attr->name, LTRSRV_SETTINGS_IP_ATTR_RECONNECT)) {
                        int val = atoi((const char*)attr->children->content);
                        if (val) {
                            flags |= LTR_CRATE_IP_FLAG_RECONNECT;
                        }
                    }

                }
                ltrsrv_eth_add_ip_entry(ntohl(addr), flags, 1, 0);
            }
        }
    }
}

static void f_load_intf_eth(xmlNode *eth_group) {
    xmlNode *param = eth_group->children;

    for (;param; param=param->next) {
        if (param->type == XML_ELEMENT_NODE) {
            xmlChar* val = f_get_node_text(param);

            if (!strcmp((const char*)param->name, LTRSRV_SETTINGS_PARAM_ETH_POLL_TIME)) {
                uint32_t setval = (uint32_t)atoi((const char*)val);
                ltrsrv_set_param(LTRD_PARAM_ETH_CRATE_POLL_TIME, &setval, sizeof(setval));
            } else if (!strcmp((const char*)param->name, LTRSRV_SETTINGS_PARAM_ETH_CON_TIMEOUT) ||
                       !strcmp((const char*)param->name, LTRSRV_SETTINGS_PARAM_ETH_CON_TIMEOUT_OLD)) {
                uint32_t setval = (uint32_t)atoi((const char*)val);
                ltrsrv_set_param(LTRD_PARAM_ETH_CRATE_CON_TOUT, &setval, sizeof(setval));
            } else if (!strcmp((const char*)param->name, LTRSRV_SETTINGS_PARAM_ETH_CTLCMD_TIMEOUT)) {
                uint32_t setval = (uint32_t)atoi((const char*)val);
                ltrsrv_set_param(LTRD_PARAM_ETH_CRATE_CTLCMD_TOUT, &setval, sizeof(setval));
            } else if (!strcmp((const char*)param->name, LTRSRV_SETTINGS_PARAM_ETH_INTF_CHECK_TIME)) {
                uint32_t setval = (uint32_t)atoi((const char*)val);
                ltrsrv_set_param(LTRD_PARAM_ETH_INTF_CHECK_TIME, &setval, sizeof(setval));
            } else if (!strcmp((const char*)param->name, LTRSRV_SETTINGS_PARAM_ETH_RECONNECT_TIME)) {
                uint32_t setval = (uint32_t)atoi((const char*)val);
                ltrsrv_set_param(LTRD_PARAM_ETH_RECONNECTION_TIME, &setval, sizeof(setval));
            } else if (!strcmp((const char*)param->name, LTRSRV_SETTINGS_PARAM_ETH_DATA_NODELAY)) {
                uint32_t setval = (uint32_t)atoi((const char*)val);
                ltrsrv_set_param(LTRD_PARAM_ETH_SEND_NODELAY, &setval, sizeof(setval));
            }
        }
    }
}


static void f_load_crate_params(xmlNode *eth_group) {
    xmlNode *param = eth_group->children;

    for (;param; param=param->next) {
        if (param->type == XML_ELEMENT_NODE) {
            xmlChar* val = f_get_node_text(param);

            if (!strcmp((const char*)param->name, LTRSRV_SETTINGS_PARAM_CRATE_MODULE_SEND_BUF_SIZE)) {
                uint32_t setval = (uint32_t)atoi((const char*)val);
                ltrsrv_set_param(LTRD_PARAM_MODULE_SEND_BUF_SIZE, &setval, sizeof(setval));
            } else if (!strcmp((const char*)param->name, LTRSRV_SETTINGS_PARAM_CRATE_MODULE_RECV_BUF_SIZE)) {
                uint32_t setval = (uint32_t)atoi((const char*)val);
                ltrsrv_set_param(LTRD_PARAM_MODULE_RECV_BUF_SIZE, &setval, sizeof(setval));
            }
        }
    }
}


/** Загрузка настроек LTR сервера из xml-файла с указанным именем
    @param[in] filename        Имя файла
    @return                    Код ошибки */
int ltrsrv_load_settings(const char* filename) {
    int err = 0;
    xmlDoc *doc = NULL; /* the resulting document tree */
    xmlNode *root = NULL;

    LIBXML_TEST_VERSION;

    /*parse the file and get the DOM */
    doc = xmlReadFile(filename, NULL, 0);
    if (doc == NULL)
        err = LTRSRV_ERR_CFGFILE_PARSE;

    /* получаем root-элемент и проверяем его имя */
    if (!err) {
        root = xmlDocGetRootElement(doc);
        if (strcmp((const char*)root->name, LTRSRV_SETTINGS_ROOT_ELEM_NAME))
            err = LTRSRV_ERR_CFGFILE_ROOT_ELEM;
    }


    /* элементы следующей группы обозначаютс группы настроек -
       проходимся по ним */
    if (!err) {
        xmlNode *settings_group = root->children;

        for (; settings_group; settings_group = settings_group->next) {
            if (settings_group->type == XML_ELEMENT_NODE) {
                if (!strcmp((const char*)settings_group->name, LTRSRV_SETTINGS_GROUP_LOG)) {
                    f_load_log(settings_group);
                } else if (!strcmp((const char*)settings_group->name, LTRSRV_SETTINGS_GROUP_CRATES_IP)) {
                    f_load_crates_ip(settings_group);
                } else if (!strcmp((const char*)settings_group->name, LTRSRV_SETTINGS_GROUP_INTF_ETH_PAR)) {
                    f_load_intf_eth(settings_group);
                } else if (!strcmp((const char*)settings_group->name, LTRSRV_SETTINGS_GROUP_CRATE_PARAMS)) {
                    f_load_crate_params(settings_group);
                }
            }
        }
    }

    xmlFreeDoc(doc);
    /*
     * Cleanup function for the XML library.
     */
    xmlCleanupParser();
    /*
     * this is to debug memory for regression tests
     */
    xmlMemoryDump();

    f_modified = 0;

    /* сохраняем имя файла, чтобы дальше работать именно с ним */
    //if (!err)
        f_filename = filename;

    return err;
}

/** Функция для указания, что в текущие настройки были внесены изменения, которые
    стоит сохранить в файл */
void ltrsrv_settings_mark_modified(void) {
    f_modified = 1;
}

/** Сохранение текущих настроек сервера в файл конфигурации, который был использован
    для загрузки в ltrsrv_load_settings().
    Сохранение выполняется только если были изменения в состоянии сервера с помощью
    ltrsrv_settings_mark_modified()
    @return Код ошибки */
int ltrsrv_save_settings(void) {
    int err = 0;
    if (f_modified && (f_filename!=NULL)) {
        xmlDocPtr doc = NULL;
        xmlNodePtr root = NULL;

        char tmp_buf[128];

        doc = xmlNewDoc(BAD_CAST "1.0");
        if (doc!=NULL) {
            root = xmlNewNode(NULL, BAD_CAST "ltrsrv_config");
            if (root!=NULL) {
                xmlDocSetRootElement(doc, root);
            } else {
                err = LTRSRV_ERR_SAVECFG_XML_NODE;
            }
        } else {
            err = LTRSRV_ERR_SAVECFG_XML_NODE;
        }

        if (!err) {
            xmlNodePtr log_node = xmlNewChild(root, NULL, BAD_CAST LTRSRV_SETTINGS_GROUP_LOG, NULL);
            if (log_node!=NULL) {
                sprintf(tmp_buf, "%d", ltrsrv_log_get_save_lvl());
                xmlNewChild(log_node, NULL, BAD_CAST LTRSRV_SETTINGS_PARAM_LOG_LVL, BAD_CAST tmp_buf);
            } else {
                err = LTRSRV_ERR_SAVECFG_XML_NODE;
            }
        }

        if (!err) {
            xmlNodePtr crates_ip_node = xmlNewChild(root, NULL, BAD_CAST LTRSRV_SETTINGS_GROUP_CRATES_IP, NULL);
            if (crates_ip_node!=NULL) {
                t_ltr_crate_client_ip_entry ip_entry;
                int ind = -1;
                do {
                    xmlNodePtr crate_node=NULL;
                    ind = ltrsrv_get_next_saved_ip_entry(ind, &ip_entry);
                    if (ind>=0) {
                        ip_entry.ip_addr = htonl(ip_entry.ip_addr);
                        crate_node = xmlNewChild(crates_ip_node, NULL, BAD_CAST
                                                 LTRSRV_SETTINGS_PARAM_CRATE_IP, NULL);
                        if (crate_node!=NULL) {
                            if (xmlNewProp(crate_node, BAD_CAST LTRSRV_SETTINGS_IP_ATTR_AUTOCON,
                                           BAD_CAST (ip_entry.flags & LTR_CRATE_IP_FLAG_AUTOCONNECT ? "1" : "0"))==NULL) {
                                err = LTRSRV_ERR_SAVECFG_XML_NODE;
                            } else if (xmlNewProp(crate_node, BAD_CAST LTRSRV_SETTINGS_IP_ATTR_RECONNECT,
                                                  BAD_CAST (ip_entry.flags & LTR_CRATE_IP_FLAG_RECONNECT ? "1" : "0"))==NULL) {
                                err = LTRSRV_ERR_SAVECFG_XML_NODE;
                            } else {
                                char ip_str[16];
                                xmlNodePtr txt_node;
                                struct sockaddr_in saddr;
                                saddr.sin_family = AF_INET;
                                saddr.sin_addr.s_addr = ip_entry.ip_addr;
                                saddr.sin_port = 0;

                                ltrsrv_addr_to_string((struct sockaddr*)&saddr, sizeof(saddr),
                                                      ip_str, sizeof(ip_str));
                                txt_node = xmlNewText(BAD_CAST  ip_str);
                                if (txt_node!=NULL) {
                                    xmlAddChild(crate_node, txt_node);
                                } else {
                                    err = LTRSRV_ERR_SAVECFG_XML_NODE;
                                }
                            }
                        } else {
                            err = LTRSRV_ERR_SAVECFG_XML_NODE;
                        }
                    }
                } while ((ind>=0) && !err);
            } else {
                err = LTRSRV_ERR_SAVECFG_XML_NODE;
            }
        }

        if (!err) {
            xmlNodePtr eth_node = xmlNewChild(root, NULL, BAD_CAST LTRSRV_SETTINGS_GROUP_INTF_ETH_PAR, NULL);
            if (eth_node!=NULL) {
                uint32_t val;
                uint32_t size = sizeof(val);

                err = ltrsrv_get_param(LTRD_PARAM_ETH_CRATE_POLL_TIME, &val, &size);
                if (!err) {
                    sprintf(tmp_buf, "%u", val);
                    xmlNewChild(eth_node, NULL, BAD_CAST LTRSRV_SETTINGS_PARAM_ETH_POLL_TIME, BAD_CAST tmp_buf);
                }

                if (!err)
                    err = ltrsrv_get_param(LTRD_PARAM_ETH_CRATE_CON_TOUT, &val, &size);
                if (!err) {
                    sprintf(tmp_buf, "%u", val);
                    xmlNewChild(eth_node, NULL, BAD_CAST LTRSRV_SETTINGS_PARAM_ETH_CON_TIMEOUT, BAD_CAST tmp_buf);
                }

                if (!err)
                    err = ltrsrv_get_param(LTRD_PARAM_ETH_CRATE_CTLCMD_TOUT, &val, &size);
                if (!err) {
                    sprintf(tmp_buf, "%u", val);
                    xmlNewChild(eth_node, NULL, BAD_CAST LTRSRV_SETTINGS_PARAM_ETH_CTLCMD_TIMEOUT, BAD_CAST tmp_buf);
                }


                if (!err)
                    err = ltrsrv_get_param(LTRD_PARAM_ETH_INTF_CHECK_TIME, &val, &size);
                if (!err) {
                    sprintf(tmp_buf, "%u", val);
                    xmlNewChild(eth_node, NULL, BAD_CAST LTRSRV_SETTINGS_PARAM_ETH_INTF_CHECK_TIME, BAD_CAST tmp_buf);
                }


                if (!err)
                    err = ltrsrv_get_param(LTRD_PARAM_ETH_RECONNECTION_TIME, &val, &size);
                if (!err) {
                    sprintf(tmp_buf, "%u", val);
                    xmlNewChild(eth_node, NULL, BAD_CAST LTRSRV_SETTINGS_PARAM_ETH_RECONNECT_TIME, BAD_CAST tmp_buf);
                }

                if (!err)
                    err = ltrsrv_get_param(LTRD_PARAM_ETH_SEND_NODELAY, &val, &size);
                if (!err) {
                    sprintf(tmp_buf, "%u", val);
                    xmlNewChild(eth_node, NULL, BAD_CAST LTRSRV_SETTINGS_PARAM_ETH_DATA_NODELAY, BAD_CAST tmp_buf);
                }



            } else {
                err = LTRSRV_ERR_SAVECFG_XML_NODE;
            }
        }


        if (!err) {
            xmlNodePtr crate_par_node = xmlNewChild(root, NULL, BAD_CAST LTRSRV_SETTINGS_GROUP_CRATE_PARAMS, NULL);
            if (crate_par_node!=NULL) {
                uint32_t val;
                uint32_t size = sizeof(val);

                err = ltrsrv_get_param(LTRD_PARAM_MODULE_SEND_BUF_SIZE, &val, &size);
                if (!err) {
                    sprintf(tmp_buf, "%u", val);
                    xmlNewChild(crate_par_node, NULL, BAD_CAST LTRSRV_SETTINGS_PARAM_CRATE_MODULE_SEND_BUF_SIZE, BAD_CAST tmp_buf);
                }

                if (!err)
                    err = ltrsrv_get_param(LTRD_PARAM_MODULE_RECV_BUF_SIZE, &val, &size);
                if (!err) {
                    sprintf(tmp_buf, "%u", val);
                    xmlNewChild(crate_par_node, NULL, BAD_CAST LTRSRV_SETTINGS_PARAM_CRATE_MODULE_RECV_BUF_SIZE, BAD_CAST tmp_buf);
                }
            } else {
                err = LTRSRV_ERR_SAVECFG_XML_NODE;
            }
        }


        if (!err) {
            if (xmlSaveFormatFileEnc(f_filename, doc, "UTF-8", 1)==-1)
                err = LTRSRV_ERR_SAVECFG_FILE;
        }

        /*free the document */
        xmlFreeDoc(doc);

        /*
         *Free the global variables that may
         *have been allocated by the parser.
         */
        xmlCleanupParser();

        /*
         * this is to debug memory for regression tests
         */
        xmlMemoryDump();

        /* сбрасываем в 0 даже при ошибке, иначе можем уйти в бесконечный
         * цикл при отсутствии прав сохранения.
         * @todo подумать о планировании сохранить через заданное время */
        f_modified = 0;
    }
    return err;
}

