/**
    @file   tinifile.c
    @date   2009-04-09
    @author 장길석 jwjwmx@gmail.com
    @brief  Ver 0.0.6
            Delphi에서 제공하는 TIniFile을 생성한다.
    @brief  
        -# 2012-02-07 오재경
            -# get_identify() 함수에서 index() 함수 리턴값 처리

        -# 2010-09-14 장길석
            -# 파일 없이 IniFile을 생성했을 때에도 객체가 생성되고
            -# 파일로 저장할 수 있도록 수정
    @todo
        -# 섹션을 삭제하는 함수를 추가
        -# 구별자 정보를 삭제하는 함수를 추가
    @bug
    @remark
    @warning
        - 저작권 에프에이리눅스(주)
        - 외부공개 금지
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <tlist.h>
#include <tstrlist.h>
#include <tinifile.h>

/// INI에서 사용하는 버퍼 크기
#define MAX_BUFFSIZE        1024
/// 섹션 문자열 최대 크기
#define MAX_SECTION         128
/// 구분자 문자열 최대 크기
#define MAX_IDENTIFY        128

/// 에러 전역 코드
int     ini_error_code;                                                 // 에러코드

/// 섹션과 구분자를 위한 버퍼
char buf_text[MAX_BUFFSIZE+1];
/// 센션과 구분자에 대한 데이터를 구하기 위한 버퍼
char buf_data[MAX_BUFFSIZE+1];

static void trim( char *str)
// 설명: 인수로 받은 문자열에서 앞과 뒤의 화이트 문자를 제거한다.
{
    int     sz_str;
    int     ndx;

    sz_str = strlen( str);

    if ( 0 >= sz_str)   return;                                         // 정상적인 문자열이 아니면 바로 복귀

    for ( ndx = sz_str -1; 0 <= ndx; ndx--)                             // 문자열 뒤의 화이트 문자를 제거, -1: NULL 위치 제거
    {
        if ( ' ' < str[ndx])                                            // 한글이나 특수문자의 두번째 바이트일 경우를 대비하기 위해 MSB 비트를 확인한다.
        {
            break;
        }
        str[ndx]    = '\0';
    }

    sz_str  = strlen( str);                                             // 문자열 앞의 화이트 문자를 제거
    for( ndx = 0; ndx < sz_str; ndx++)
    {
        if ( ' ' != str[ndx])                                           // 한글이 있을 경우를 생각해서 ' ' 문자를 직접 비교한다.
            break;
    }
    memcpy( str, str+ndx, strlen( str));                                // 회이트 문자가 아닌 부분을 포인터의 시작 위치로 이동
}

static char *get_identify( char *str, char *identify)
// 설명: 인수 문자열에서 구별지 문자열을 구한다.
// 참고: '='까지의 문자열을 구하며, 앞과 뒤의 화이트 문자를 제거한다.
// 반환: 구별자 문자열
{
    char   *p_data;
    char   *p_branch;
    int     sz_data;
    int     pos;

    p_branch  = index( str, '=');
    if ( NULL == p_branch ) return NULL;
    
    pos = p_branch -str;

    memcpy( identify, str, pos);
    identify[pos]   = '\0';
    sz_data         = strlen( str) - pos;
    p_data          = malloc( sz_data +1);

    if ( NULL != p_data)
    {
        memcpy( p_data, p_branch+1, sz_data);
        p_data[sz_data] = '\0';                                         // 복사 문자열 끝을 '\0'으로 마무리

        trim( identify);
        trim( p_data);
    }
    return p_data;
}

static void get_section( char *str, char *sec)
// 설명: 인수 문자열에서 섹션 문자열을 구한다.
// 참고: '['와 ']' 사이의 문자를 구하며, 문자열 앞과 뒤의 화이트 문자는 제거한다.
// 반환: 섹션 문자열
{
    int     ndx_str;
    int     ndx_sec;

    ndx_str = 0;
    while( ('\0' != str[ndx_str]) && ( ndx_str < MAX_BUFFSIZE))
    {
        if ( '[' == str[ndx_str++])
            break;
    }

    ndx_sec = 0;
    while( ('\0' != str[ndx_str]) && ( ndx_sec < MAX_SECTION))
    {
        if ( ']' == str[ndx_str])
        {
            sec[ndx_sec]    = '\0';
            break;
        }
        sec[ndx_sec++] = str[ndx_str++];
    }
    trim( sec);
}

static int  is_remark( char *str)
// 설명: 문자열이 주석인지의 여부를 판단한다.
// 참고: '#' 문자 또는 ';' 문자로 문장이 시작하면 주석으로 판단
// 반환: INI_TRUE=주석
{
    char    ch_data;
    int     ndx;

    ndx = 0;
    while( ('\0' != str[ndx]) && ( ndx < MAX_BUFFSIZE))                 // 문장의 끝 또는 버퍼의 크기 만큼 확인
    {
        ch_data = str[ndx++];
        if ( '#' == ch_data)                                            // 공백 이상의 문자 중 주석에 해당되는 '#' 문자를 만나면 주석으로 판단
        {
            return INI_TRUE;
        }
        else if ( ';' == ch_data)                                       // 공백 이상의 문자 중 주석에 해당되는 ';' 문자를 만나면 주석으로 판단
        {
            return INI_TRUE;
        }
        else if ( ' ' != str[ndx])                                      // 공백 키워드가 아닌 공백 이상의 문자를 만나면 주석 행이 아님으로 판단
        {
            return INI_FALSE;
        }
    }
    return INI_TRUE;
}

static int  is_identify( char *str)
// 설명: 문자열이 섹션 정보를 담고있는지의 여부를 반환
// 참고: '=' 문자가 있으면서 유효
// 반환: 0 = 구별자 정보가 없음, 1 = 구별자 정보가 있음
{
    int     ndx;

    ndx = 0;
    while( ('\0' != str[ndx]) && ( ndx < MAX_BUFFSIZE))                 // 문장의 끝 또는 버퍼의 크기 만큼 확인
    {
        if ( ' ' < str[ndx++])                                          // 앞에 있는 공백 문자를 무시. 공백 이상의 문자가 오면
        {
            while( ('\0' != str[ndx]) && ( ndx < MAX_BUFFSIZE))         // 공백 이상에 '=' 문자가 있는지 확인
            {
                if ( '=' == str[ndx++])
                {
                    return 1 == 1;
                }
            }
            return 1 == 0;
        }
    }
    return 1 == 0;
}

static int  is_section( char *str)
//설명: 문자열이 섹션 정보를 담고있는지의 여부를 반환
//참고: '[' 문자가 있으면서 '[' 앞에는 빈 문자열이거나 공백 문자만 유효
//      '[' 문자와 ']' 문자 사이에는 섹션에 대한 이름 문자열이 있어야 함
//반환: 0 = 섹션 정보가 없음, 1 = 섹션 정보가 있음
{
    char    ch_data;
    int     ndx;

    ndx = 0;
    while( ( '\0' != str[ndx]) && ( ndx < MAX_BUFFSIZE))                // 문장의 끝 또는 버퍼의 크기 만큼 확인
    {
        ch_data = str[ndx++];                                           // 확인할 문자열
        if ( '[' == ch_data)                                            // 시작 [ 문자 있음
        {
            while( ('\0' != str[ndx]) && ( ndx < MAX_BUFFSIZE))         // 문장의 끝 또는 버퍼의 크기 만큼 확인
            {
                if ( ' ' < str[ndx++])                                  // [ 문자 이후에 다른 문자가 있다면
                {
                    while( ('\0' != str[ndx]) && ( ndx < MAX_BUFFSIZE)) // 문장의 끝 또는 버퍼의 크기 만큼 확인
                    {
                        if ( ']' == str[ndx++])                         // ] 문자가 있다면 TRUE
                        {
                            return 1;
                        }
                    }
                    return 0;
                }
            }
            return 0;
        }
        else if ( ' ' != ch_data)                                       // [ 문자 앞에 공백 문자 외에 다른 문자가 있다면 FALSE
        {
            break;
        }
    }
    return 0;
}

static void read_inifile_data( FILE *fp, inifile_t *inifile)
//설명: ini 파일의 내용을 읽어들여 inifile_t 객체의 내용을 완성한다.
//인수: FILE *fp          : ini 파일에 대한 파일 포인터
//      ifile_t *inifile  : inifile_t 객체 포인터
//참고: 섹션에 따라 구별자 목록을 갖는 tstrlist를 생성하여
//      섹션 리스트의 아이템에 object로 추가한다.
{
    char        str_section [MAX_SECTION +1];
    char        str_identify[MAX_IDENTIFY+1];
    tstrlist   *lst_identify    = NULL;
    char       *p_data;

    while( NULL != fgets( buf_text, MAX_BUFFSIZE, fp))
    {
        if      ( INI_TRUE == is_remark( buf_text) )                    // 주석 행이면 다음 행으로
            ;
        else if ( NULL != index( buf_text, '['))
        {
            if ( is_section( buf_text))                                 // 파일에서 읽어들인 문자열이 섹션 정보로 유효하다면
            {
                lst_identify    = tstrlist_create();                    // 섹션에 포함된 모든 구별자 정보를 담을 수 있는 tstrlist를 생성
                if ( NULL == lst_identify)
                {
                    ini_error_code  = INIERR_CREATE_IDENTIFY_FAIL;      //  에러코드: 구별자 정보를 위한 메모리 할당 실패
                    return;
                }
                get_section( buf_text, str_section);
                tstrlist_add_object( inifile->lst_sections, str_section, lst_identify);
            }
            else
                lst_identify    = NULL;
        }
        else if (   ( NULL != lst_identify)
                &&  is_identify( buf_text))
        {
            p_data = get_identify( buf_text, str_identify);
            if ( NULL == p_data)
            {
                ini_error_code  = INIERR_READ_IDENTIFY_FAIL;            //  에러코드: 구별자의 문자열 정보를 메모리 할당 실패
                return;
            }
            tstrlist_add_object( lst_identify, str_identify, p_data);
        }
    }

    inifile->is_changed = 0;
}

static char *read_string( inifile_t *inifile, char *str_section, char *str_identify)
//설명: 섹션과 구별자의 문자열 데이터를 구한다.
//인수: inifile_t *inifile  : inifile_t 객체 포인터
//      char *str_section   : 섹션 문자열
//      char *str_identify  : 구별자 문자열
//반환: 섹션과 구별자의 문자열 데이터
//주의: 섹션과 구별자가 없다면 NULL을 반환
//      반환된 문자열 포인터로 메모리 소멸을 해서는 안 된다.
{
    tstrlist   *lst_section;
    int         index;


    index   = tstrlist_indexof( inifile->lst_sections, str_section);
    if ( 0 > index) return NULL;

    lst_section = ( tstrlist *)tstrlist_get_object( inifile->lst_sections, index);
    if ( NULL == lst_section) return NULL;

    index   = tstrlist_indexof( lst_section, str_identify);
    if ( 0 > index) return NULL;

    return (char *)tstrlist_get_object( lst_section, index);
}

static int write_string( inifile_t *inifile, char *str_section, char *str_identify, char *data)
//설명: 섹션과 구별자의 문자열 데이터를 변경 또는 추가한다.
//인수: inifile_t *inifile      : inifile_t 객체 포인터
//      char      *str_section  : 섹션 문자열
//      char      *str_identify : 구별자 문자열
//      char      *data         : 저장할 데이터
//반환: INI_TRUE= 변경 또는 추가 성공, INI_FALSE 변경 또는 추가 실패
//주의: 섹션과 구별자가 없다면 새로 추가한다.
{
    tstrlist   *lst_section;
    char       *p_data;
    int         index;

    inifile->is_changed = 1;                                            // 자료 변경 이나 추가가 있음

    index   = tstrlist_indexof( inifile->lst_sections, str_section);
    if ( 0 > index)                                                     // 섹션이 없다면 추가한다.
    {
        lst_section = tstrlist_create();                                // 섹션에 포함된 모든 구별자 정보를 담을 수 있는 tstrlist를 생성
        if ( NULL == lst_section)
        {
            ini_error_code  = INIERR_CREATE_IDENTIFY_FAIL;              //  에러코드: 구별자 정보를 위한 메모리 할당 실패
            return INI_FALSE;
        }
        tstrlist_add_object( inifile->lst_sections, str_section, lst_section);
    }
    else
    {
        lst_section = ( tstrlist *)tstrlist_get_object( inifile->lst_sections, index);
    }

    index   = tstrlist_indexof( lst_section, str_identify);
    if ( 0 > index)                                                     // 구별자가 없다면 추가한다.
    {
        tstrlist_add_object( lst_section, str_identify, data);
    }
    else
    {
        p_data  = (char *)tstrlist_get_object( lst_section, index);
        free( p_data);

        tstrlist_put_object( lst_section, index, data);
    }
    return INI_TRUE;
}

char  *ini_error_string( void)
/**
    @brief  ini_error_code에 대한 에러 설명 문자열을 반환
    @return 에러 코드에 대한 에러 설명 문자열 포인터
    @warning 절대 반환 받은 문자열을 소멸 시켜서는 안 된다!!
*/
{
   char *error_string[] ={ "에러 없음",                                 //  INIERR_NONE
                           "메모리 부족",                               //  INIERR_OUT_OF_MEMORY
                           "파일 이름 지정 오류",                       //  INIERR_FILENAME_FAIL
                           "자료 없음",                                 //  INIERR_NO_DATA
                           "IniFile 없음",                              //  INIERR_NO_FILE
                           "IniFile을 읽을 수 없음",                    //  INIERR_ACCESS_FAIL
                           "섹션 리스트 생성 실패",                     //  INIERR_CREATE_SECTION_FAIL
                           "구별자 생성 실패",                          //  INIERR_CREATE_IDENTIFY_FAIL
                           "인수의 객체가 NULL"                         //  INIERR_NULL_POINTER
                        };
   return( error_string[ini_error_code]);
}

int ini_print_error( char *remark)
/**
    @brief  ini_error_code에 대한 에러 설명 문자열을 화면에 출력
    @param  remark : 에러 설명 문자열 끝에 첨부하여 출력할 문자열
    @return 에러 코드
*/
{
   printf( "[ini error:%d]%s %s\n", ini_error_code, ini_error_string(), remark);
   return ini_error_code;
}

int ini_write_bool( inifile_t *inifile, char *str_section, char *str_identify, int value)
/**
    @brief  섹션과 구별자가 지정하는 데이터의 값을 변경한다.
    @param  inifile : inifile_t 객체 포인터
    @param  str_section : 섹션 문자열
    @param  str_identify : 구별자 문자열
    @param  value : boolean 값
    @return\n
        INI_TRUE  - 변경 또는 추가 성공\n
        INI_FALSE - 변경 또는 추가 실패
*/
{
    char    *data;

    data        = malloc( 2);
    if ( value) sprintf( data, "1");
    else        sprintf( data, "0");

    return write_string( inifile, str_section, str_identify, data);
}

int ini_write_real( inifile_t *inifile, char *str_section, char *str_identify, double value)
/**
    @brief  섹션과 구별자가 지정하는 실수 데이터를 구한다.
    @param  inifile : inifile_t 객체 포인터
    @param  str_section : 섹션 문자열
    @param  str_identify : 구별자 문자열
    @param  value : 실수 값
    @return
        - INI_TRUE  - 변경 또는 추가 성공
        - INI_FALSE - 변경 또는 추가 실패
*/
{
    char    *data;

    data        = malloc( 30);
    sprintf( data, "%lf", value);
    trim( data);
    return write_string( inifile, str_section, str_identify, data);
}

int ini_write_integer( inifile_t *inifile, char *str_section, char *str_identify, int value)
/**
    @brief  섹션과 구별자가 지정하는 정수 데이터를 지정한다.
    @param  inifile : inifile_t 객체 포인터
    @param  str_section : 섹션 문자열\n
    @param  str_identify : 구별자 문자열\n
    @param  value : 정수 데이터
    @return
        - INI_TRUE  - 변경 또는 추가 성공
        - INI_FALSE - 변경 또는 추가 실패
*/
{
    char    *data;

    data        = malloc( 20);
    sprintf( data, "%d", value);
    trim( data);

    return write_string( inifile, str_section, str_identify, data);
}

int ini_write_string( inifile_t *inifile, char *str_section, char *str_identify, char *value)
/**
    @brief  섹션과 구별자의 문자열을 변경
    @param  inifile : inifile_t 객체 포인터
    @param  str_section : 섹션 문자열\n
    @param  str_identify : 구별자 문자열\n
    @param  value : 문자열 데이터
    @return
        - INI_TRUE  - 변경 또는 추가 성공
        - INI_FALSE - 변경 또는 추가 실패
*/
{
    char    *data;

    data        = malloc( strlen( value)+1);
    memcpy( data, value, strlen( value));
    data[strlen(value)]    = '\0';
    return write_string( inifile, str_section, str_identify, data);
}

int ini_write_char( inifile_t *inifile, char *str_section, char *str_identify, char value)
/**
    @brief  섹션과 구별자의 문자를 변경
    @param  inifile : inifile_t 객체 포인터
    @param  str_section : 섹션 문자열\n
    @param  str_identify : 구별자 문자열\n
    @param  value : 문자
    @return
        - INI_TRUE  - 변경 또는 추가 성공
        - INI_FALSE - 변경 또는 추가 실패
*/
{
    char  str[16];

    sprintf( str, "%c", value );
    return ini_write_string( inifile, str_section, str_identify, str );
}

int ini_read_bool( inifile_t *inifile, char *str_section, char *str_identify, int default_value)
/**
    @brief  섹션과 구별자가 지정하는 Boolean 데이터를 구한다.
    @param  inifile : inifile_t 객체 포인터
    @param  str_section : 섹션 문자열\n
    @param  str_identify : 구별자 문자열\n
    @param  default_value : 값이 없다면 대신 반환될 기본값
    @warning    ini 파일에 저장한 문자열이 0 이면 FALSE로 반환하며\n
                이외는 무조건 TRUE로 반환한다.\n
                즉, 섹션과 구별자가 가지고 있는 문자열 정보가\n
                '0' 인지 아닌지의 여부를 반환한다.
*/
{
    char   *data;
    int     int_data;

    data    = read_string( inifile, str_section, str_identify);                         // 먼저 문자열로 데이터를 읽어 들인다.
    if      ( NULL == data)                             return default_value;           // 찾는 데이터가 없다면 기본값을 반환한다.
    else if ( 0 == strlen( data) )                      return default_value;           // 문자열 데이터가 없다면 기본값을 반환한다.
    else if ( 0 == sscanf( data, "%d", &int_data))      return default_value;           // 정수로 변환된 값이 없다면 기본값을 반환
    else                                                return 0 != int_data;           // 정수 값이 0이면 FALSE로 반환한다.
}

double ini_read_real( inifile_t *inifile, char *str_section, char *str_identify, double default_value)
/**
    @brief  섹션과 구별자가 지정하는 실수 데이터를 구한다.
    @param  inifile : inifile_t 객체 포인터
    @param  str_section  : 섹션 문자열
    @param  str_identify : 구별자 문자열
    @param  default_value: 값이 없다면 대신 반환될 기본값
    @return\n
        섹션과 구별자에 해당하는 실수 값\n
        저정한 섹션이나 구별자에 대한 실수 값이 없다면 기본값을 반환
*/
{
    char       *data;
    double      float_data;


    data    = read_string( inifile, str_section, str_identify);                         // 먼저 문자열로 데이터를 읽어 들인다.
    if      ( NULL == data)                             return default_value;           // 찾는 데이터가 없다면 기본값을 반환한다.
    else if ( 0 == strlen( data) )                      return default_value;           // 문자열 데이터가 없다면 기본값을 반환한다.
    else if ( 0 == sscanf( data, "%lf", &float_data))   return default_value;           // 실수로 변환된 값이 없다면 기본값을 반환
    else                                                return float_data;              // 실수 값을 반환한다.
}

int ini_read_integer( inifile_t *inifile, char *str_section, char *str_identify, int default_value)
/**
    @brief  섹션과 구별자가 지정하는 정수 데이터를 구한다.
    @param  inifile : inifile_t 객체 포인터
    @param  str_section : 섹션 문자열
    @param  str_identify : 구별자 문자열
    @param  default_value : 값이 없다면 대신 반환될 기본값
    @return\n
        섹션과 구별자에 해당하는 정수 값\n
        저정한 섹션이나 구별자에 대한 정수 값이 없다면 기본값을 반환
*/
{
    char   *data;
    int     int_data;

    data    = read_string( inifile, str_section, str_identify);                         // 먼저 문자열로 데이터를 읽어 들인다.
    
    if      ( NULL == data)                             return default_value;           // 찾아진 데이터가 없다면 기본값을 반환한다.
    else if ( 0 == strlen( data) )                      return default_value;           // 문자열 데이터가 없다면 기본값을 반환한다.
    else if ( 0 == sscanf( data, "%d", &int_data))      return default_value;           // 정수로 변환된 값이 없다면 기본값을 반환
    else                                                return int_data;                // 정수값을 반환한다.
}

char *ini_read_string( inifile_t *inifile, char *str_section, char *str_identify, char *default_value)
/**
    @brief  섹션과 구별자가 지정하는 문자열 데이터를 구한다.
    @param  inifile : inifile_t 객체 포인터
    @param  str_section : 섹션 문자열
    @param  str_identify : 구별자 문자열
    @param  default_value : 값이 없다면 대신 반환될 기본값
    @return\n
        섹션과 구별자의 문자열 정보\n
        저정한 섹션이나 구별자에 대한 문자열이 없다면 기본값을 반환
    @warning 절대 반환 받은 문자열을 소멸 시켜서는 안 된다!!
*/
{
    char    *data;

    data    = read_string( inifile, str_section, str_identify);
    if ( NULL == data)                                  return default_value;
    else if ( 0 == strlen( data) )                      return default_value;           // 문자열 데이터가 없다면 기본값을 반환한다.
    else                                                return data;
}

char         ini_read_char( inifile_t *inifile, char *str_section, char *str_identify, char default_value)
/**
    @brief  섹션과 구별자가 지정하는 케릭터 데이타를 구한다.
    @param  inifile : inifile_t 객체 포인터
    @param  str_section : 섹션 문자열
    @param  str_identify : 구별자 문자열
    @param  default_value : 값이 없다면 대신 반환될 기본값
    @return\n
        섹션과 구별자의 문자열 정보\n
        저정한 섹션이나 구별자에 대한 문자열이 없다면 기본값을 반환
    @warning 절대 반환 받은 문자열을 소멸 시켜서는 안 된다!!
*/
{
	char *cc;

	cc = ini_read_string( inifile, str_section, str_identify, NULL );
	if ( cc ) return cc[0];
	else      return default_value;
}

tstrlist *ini_read_section( inifile_t *inifile, char *str_section)
/**
    @brief  섹션이 가지고 있는 모든 구별자 문자열의 정보를 tstrlist 형식으로 구한다.
    @param  inifile : inifile_t 객체 포인터
    @param  str_section : 섹션 문자열
    @return\n
        섹션이 가지고 있는 모든 구별자 문자열을 가지고 있는 tstrlist 객체\n
        저정한 섹션이 없다면 NULL을 반환
    @warning 절대 반환 받은 객체를 소멸 시켜서는 안 된다!!
*/
{
    tstrlist   *lst_section;
    int         index;

    if ( NULL == inifile)   return  NULL;
    index   = tstrlist_indexof( inifile->lst_sections, str_section);

    if ( 0 > index)         return  NULL;
    lst_section = ( tstrlist *)tstrlist_get_object( inifile->lst_sections, index);

    return lst_section;
}

tstrlist *ini_read_sections( inifile_t *inifile)
/**
    @brief  inifile_t 객체가 가지고 있는 모든 섹션 정보를 tstrlist 형식으로 구한다.
    @param  inifile : inifile_t 객체 포인터
    @return 모든 섹션 문자열을 가지고 있는 tstrlist 객체
    @warning 절대 반환 받은 객체를 소멸 시켜서는 안 된다!!
*/
{
    if ( NULL == inifile)
    {
        return  NULL;
    }
    return inifile->lst_sections;
}

int ini_save_to_file( inifile_t *inifile, char *filename){

    /**
        @brief  inifile_t 객체 내용이 변경되어 있다면 파일로 저장한다.
        @param  inifile : inifile_t 객체 포인터
        @return  INI_TRUE - 작업 중 오류 없음
    */
    FILE       *fp_inifile;             // ini 파일 객체
    tstrlist   *lst_sections;           // inifile_t가 가지고 있는 모든 섹션 리스트
    tstrlist   *lst_identifies;         // 섹션이 가지고 있는 구별자 리스트
    char       *str_section;            // 섹션 문자열
    char       *str_identify;           // 구별자 문자열
    char       *str_data;               // 섹션과 구별자의 문자열 데이터
    int         ndx_sec;                // 모든 섹션을 처리하기 위한 루프 인데스
    int         ndx_idn;                // 모든 구별자를 처리하기 위한 루프 인덱스

    if ( NULL == inifile)                                               // ini 파일 개체가 없음
    {
        ini_error_code  = INIERR_NULL_POINTER;
        return INI_FALSE;
    }
    if ( NULL == inifile->lst_sections)
    {
        ini_error_code  = INIERR_NULL_POINTER;
        return INI_FALSE;
    }
    fp_inifile = fopen( filename, "w");                    //  ini 파일을 쓰기 전용으로 열기
    if ( NULL == fp_inifile)                                        //  파일 열기 실패
    {
        ini_error_code  = INIERR_ACCESS_FAIL;                       //  에러코드: 파일 열기 실패 지정
        return INI_FALSE;
    }
    lst_sections = ini_read_sections( inifile);                     // ini 객체에서 모든 섹션 리스트를 구한다.
    for( ndx_sec = 0; ndx_sec < tstrlist_getcount( lst_sections); ndx_sec++)// 모든 섹션에 대해서
    {
        str_section     = tstrlist_get_string( lst_sections, ndx_sec);      // 섹션 문자열을 구한다.
        fprintf( fp_inifile, "[%s]\n", str_section);                // 파일에 섹션 문자열을 쓰기

        lst_identifies  = ini_read_section( inifile, str_section);  // 섹션의 모든 구별자 리스트를 구한다.
        for( ndx_idn = 0; ndx_idn < tstrlist_getcount( lst_identifies); ndx_idn++)
        {
            str_identify    = tstrlist_get_string( lst_identifies, ndx_idn);
            str_data        = ini_read_string( inifile, str_section, str_identify, "");
            fprintf( fp_inifile, "%s=%s\n", str_identify, str_data);         // 구별자와 데이터를 쓰기
        }
    }
    fclose( fp_inifile);
    sync();

    return INI_TRUE;
}

int ini_flush( inifile_t *inifile)
/**
    @brief  inifile_t 객체 내용이 변경되어 있다면 파일로 저장한다.
    @param  inifile : inifile_t 객체 포인터
    @return  INI_TRUE - 작업 중 오류 없음
*/
{
    int     rst = INIERR_NONE;

    if ( INI_TRUE == inifile->is_changed){                              // 데이터가 변경되었거나 추가되었음
        inifile->is_changed = INI_FALSE;
        rst = ini_save_to_file( inifile, inifile->filename);
    }
    return rst;
}

void ini_remove_section( inifile_t *inifile, char *str_section)
/**
    @brief  지정된 섹션을 제거합니다.
    @param  inifile : inifile_t 객체 포인터
    @param  str_section : 삭제할 섹션
    @warning 변경이나 추가된 자료를 위해 ini_flush()를 호출한다.
*/
{
    tstrlist   *psection;
    char       *pdata;
    int         index;
    int         ndx_idn;

    if ( NULL != inifile)
    {
        if ( NULL != inifile->lst_sections)
        {
            index   = tstrlist_indexof( inifile->lst_sections, str_section);
            if ( 0 <= index)
            {
                psection = ( tstrlist *)tstrlist_get_object( inifile->lst_sections, index);
                for ( ndx_idn = 0; ndx_idn < tstrlist_getcount( psection); ndx_idn++)
                {
                    pdata   = ( char *)tstrlist_get_object( psection, ndx_idn);         // identify에 지정된 데이터 문자열 메모리를 제거
                    free( pdata);
                }
                tstrlist_free( psection);
                tstrlist_delete( inifile->lst_sections, index);

		        inifile->is_changed = INI_TRUE;	// [KTG] section 삭제에 대해서도 파일에 반영되도록 하기 위함.
            }
        }
    }
}

void ini_free( inifile_t *inifile)
/**
    @brief  inifile_t 객체를 소멸
    @param  inifile : inifile_t 객체 포인터
    @warning 변경이나 추가된 자료를 위해 ini_flush()를 호출한다.
*/
{
    tstrlist   *psection;
    char       *pdata;
    int         ndx_sec;
    int         ndx_idn;

    if ( NULL != inifile)
    {
        ini_flush( inifile);                                            // 변경된 내용이 있다면 파일로 저장
        if ( NULL != inifile->filename) free( inifile->filename);       // 파일 이름의 메모리 소멸
        if ( NULL != inifile->lst_sections)
        {
            for ( ndx_sec = 0; ndx_sec < tstrlist_getcount( inifile->lst_sections); ndx_sec++)
            {
                psection    = tstrlist_get_object( inifile->lst_sections, ndx_sec);
                for ( ndx_idn = 0; ndx_idn < tstrlist_getcount( psection); ndx_idn++)
                {
                    pdata   = ( char *)tstrlist_get_object( psection, ndx_idn);         // identify에 지정된 데이터 문자열 메모리를 제거
                    free( pdata);
                }
                tstrlist_free( psection);
            }
            tstrlist_free( inifile->lst_sections);
        }
        free( inifile);
    }
}

inifile_t *ini_create( char *filename)
/**
    @brief  filename의 내용을 섹션과 구별자로 구분하여 읽어 들인다.\n
    file에서 자료를 읽어 들인 후에는 데이터가 변경되어 저장이 필요하기 전까지는 \n
    다시 파일을 억세스할 필요가 없다.
    @param  filename : ini 파일의 이름
    @return\n
        ini 파일의 모든 내용을 담은 inifile_t 객체 포인터
        읽기에 실패했다면 NULL을 반환
*/
{
    inifile_t  *inifile;                // ini 파일 객체
    FILE       *fp_inifile;             // ini 파일을 읽기 위한 파일 포인터
    int         sz_filename;            // ini 파일의 전체 이름 크기

    if ( 0 != access( filename, F_OK | R_OK))                           //  IniFile 존재 확인 및 읽기가 가능한지 확인
    {
        fp_inifile = fopen( filename, "w");                             //  파일이 없다면 파일 생성이 가능한지를 확인한다.
        if ( NULL == fp_inifile)                                        //  파일 생성에 실패했다면 에러코드 반환
        {
            ini_error_code  = INIERR_ACCESS_FAIL;                       //  에러코드: 파일 열기 실패 지정
            return  NULL;
        }
        fclose( fp_inifile);
    }
    
    inifile = malloc( sizeof( inifile_t));                              //  ini 파일 객체 생성
    if ( NULL == inifile)                                               //  객체를 위한 메모리 할당에 실패하면
    {
        ini_error_code  = INIERR_OUT_OF_MEMORY;                         //  에러코드 지정
        return  NULL;
    }
    inifile->filename       = NULL;                                     //  기본값 지정: 파일 이름 지정이 아직 안 되어 있음
    inifile->lst_sections   = NULL;                                     //  
    inifile->is_changed     = INI_FALSE;                                //  기본값 지정: 변경된 내용이 없음

    sz_filename             = strlen( filename);                        //  파일 이름을 객체 정보에 저장한다.
    if ( 0 == sz_filename)
    {
        ini_free( inifile);
        ini_error_code  = INIERR_FILENAME_FAIL;
        return  NULL;
    }
    inifile->filename = malloc( sz_filename+1);
    if ( NULL == inifile->filename)                                     //  메모리 할당에 실패했다면 에러 처리한다.
    {
        ini_free( inifile);
        ini_error_code  = INIERR_OUT_OF_MEMORY;
        return  NULL;
    }
    memcpy( inifile->filename, filename, sz_filename+1);
    inifile->lst_sections = tstrlist_create();                          //  루트 섹션을 생성
    if ( NULL == inifile->lst_sections)                                 //  루트 섹션을 위한 스트링 리스트를 생성하지 못했다면
    {
        ini_free( inifile);
        ini_error_code  = INIERR_CREATE_SECTION_FAIL;                   //  에러코드: 루트 섹션 리스트 생성
        return  NULL;
    }

    fp_inifile = fopen( filename, "r");                                 //  IniFile의 파일을 열기를 함
    if ( NULL == fp_inifile)                                            //  파일 열기 실패
    {                                                                   
        ini_free( inifile);                                             
        ini_error_code  = INIERR_ACCESS_FAIL;                           //  에러코드: 파일 열기 실패 지정
        return  NULL;                                                   
    }                                                                   
    read_inifile_data( fp_inifile, inifile);                            // IniFile 내용을 모두 읽어 들임
    fclose( fp_inifile);
    
    return inifile;
}