中国开发网: 论坛: 程序员情感CBD: 贴子 558388
Fish: ini.c / ini.cpp
/***************************************************************************
ini.cpp - Reads and writes keys to a ini file.

--------------------------------------------------------------------
begin : Fri Apr 21 2000
copyright : (C) 2000 by Simon White
email : s_a_white@email.com
***************************************************************************/

//*******************************************************************************************************************
// 包含的头文件
//*******************************************************************************************************************
#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

//#include "config.h"
#include "ini.h"

#define INI_BUFFER_SIZE (1024 * 5)
#define HAVE_STRCASECMP 1

enum
{
INI_NEW_LINE = 1,
INI_SKIP_LINE = 2,
INI_IN_SECTION = 3,
INI_END_OF_FILE = 4,
INI_CHECK_COMMENT = 5,
INI_ALLOW_COMMENT = 8
};

typedef struct
{
long pos;
long first;
long last;
struct key_tag *key;
int state;
const char *comment;
int commentpos;
} ini_parser_t;


//*******************************************************************************************************************
// 函数原型
//*******************************************************************************************************************
static ini_t *__ini_open (const char *name, ini_mode_t mode, const char *comment, int flags);
static int __ini_close (ini_t *ini, bool flush);
static void __ini_delete (ini_t *ini);
static bool __ini_extractField (ini_t *ini, FILE *file, ini_parser_t &parser, char ch);
static int __ini_process (ini_t *ini, FILE *file, const char *comment);
inline bool __ini_processComment (ini_t *ini, FILE *file, ini_parser_t &parser);
static int __ini_store (ini_t *ini, FILE *file);


/********************************************************************************************************************
* Function : __ini_strtrim
* Parameters : str - 要被休整的字符串
* Returns :
* Globals Used :
* Globals Modified :
* Description : 删除字符串开头和结尾的空格
********************************************************************************************************************/
void __ini_strtrim (char *str)
{
long first, last, len;
first = 0;
last = strlen (str);

if (!last--)
return;

// Clip end first
while (isspace (str[last]) && last > 0)
last--;

// Clip beginning
while (isspace (str[first]) && (first < last))
first++;
len = last + 1 - first;
memmove (str, str + first, len);
str[len] = '\0';
}


/********************************************************************************************************************
* Function : __ini_open
* Parameters : name - 要被分析的 ini 文件
* : mode - 存取权限,
* : comment - 文件注释字符
* : flags - 各种操作标志 (备份/大小写敏感/等等)
* Returns : 指向 ini 数据库的指针
* Globals Used :
* Globals Modified :
* Description : 打开一个 ini 数据文件并读取其内容到数据库中
********************************************************************************************************************/
ini_t *__ini_open (const char *name, ini_mode_t mode, const char *comment, int flags)
{
ini_t *ini;
FILE *file = NULL;
unsigned long length;

if (!name)
return 0;

length = strlen(name);
if (!length)
return 0;

// Create ini database stub
ini = (ini_t *) malloc (sizeof (ini_t));
if (!ini)
goto ini_openError;
memset (ini, 0, sizeof (ini_t));

// Store ini filename
ini->filename = strdup (name);
if (!ini->filename)
goto ini_openError;

// Open input file
ini->mode = mode;
ini->flags = flags;
file = fopen (ini->filename, "rb");
if (!file)
{ // File doesn't exist so check if allowed
// to create new one
if (mode != INI_NEW)
goto ini_openError;

// Seems we can make a new one, check and
// make sure
file = fopen (ini->filename, "wb");
if (!file)
goto ini_openError;
ini->flags |= INI_NEWFILE;
fclose (file);
file = NULL;
}

// Open backup file
if (ini->mode == INI_READ)
ini->ftmp = tmpfile ();
else
{
ini->filename[length - 1] = '~';
ini->ftmp = fopen (ini->filename, "wb+");
ini->filename[length - 1] = name[length - 1];
}

if (!ini->ftmp)
goto ini_openError;
if (file)
{ // Process existing ini file
if (__ini_process (ini, file, comment) < 0)
goto ini_openError;
fclose (file);
}
return ini;

ini_openError:
if (ini)
{
if (ini->ftmp)
{ // Close and remove backup file
fclose (ini->ftmp);
if (ini->mode != INI_READ)
{
ini->filename[strlen (ini->filename) - 1] = '~';
remove (ini->filename);
}
}
if (ini->filename)
free (ini->filename);
free (ini);
}

if (file)
fclose (file);

return 0;
}


/********************************************************************************************************************
* Function : __ini_close
* Parameters : ini - 指向 ini 文件数据库的指针
* : force - 如果为 true, 即使在保存到一个新 ini 文件的过程中发生错误,数据库也将从内存中被移除
* Returns : -1 代表出错, 0 代表成功
* Globals Used :
* Globals Modified :
* Description : 将所作的任何修改存回到新的 ini 文件中
* : 备份文件包含所有原来的数据以及任何在后面添加的修改内容
********************************************************************************************************************/
int __ini_close (ini_t *ini, bool flush)
{
FILE *file;
int ret = 0;

// 打开输出文件
if (ini->flags & INI_MODIFIED)
{
if (!ini->first)
remove(ini->filename);
else
{

// Not point writing an unchanged file
file = fopen (ini->filename, "w");
if (file)
{ // Output all new headers and keys
ret = __ini_store (ini, file);
fflush (file);
fclose (file);
}

if (!file)
return -1;
}
}

// 检查用户是否不想关闭文件
if (!flush)
return ret;

// 清理
fclose (ini->ftmp);

if (ini->mode != INI_READ)
{ // If no mods were made, delete tmp file
if ((ini->flags & (INI_MODIFIED | INI_NEWFILE | INI_BACKUP)) ^ (INI_MODIFIED | INI_BACKUP))
{
ini->filename[strlen (ini->filename) - 1] = '~';
remove (ini->filename);
}
}

__ini_delete (ini);
free (ini->filename);

if (ini->tmpSection.heading)
free (ini->tmpSection.heading);
if (ini->tmpKey.key)
free (ini->tmpKey.key);

free (ini);
return ret;
}


/********************************************************************************************************************
* Function : __ini_delete
* Parameters : ini - 指向 ini 文件数据库的指针
* Returns :
* Globals Used :
* Globals Modified :
* Description : 从内存中删除整个 ini 数据库,但是保留 ini 存根(stub),以便还可调用 ini_close
********************************************************************************************************************/
void __ini_delete (ini_t *ini)
{ // If 如果已经删除了,就不用再次删除
if (!ini->first)
return;

// 遍历所有节并删除它们
while (ini->first)
{
ini->selected = ini->first;
__ini_deleteHeading (ini);
}

if (ini->mode != INI_READ)
ini->flags |= INI_MODIFIED;
}


/********************************************************************************************************************
* Function : __ini_extractField
* Parameters : ini - 指向 ini 文件数据库的指针
* : file - ini file to read heading from
* : parser - current parser decoding state,
* : ch - 要处理的字符
* Returns : false 代表错误,true 代表成功
* Globals Used :
* Globals Modified :
* Description : 处理下一个字符并提取需要的字段
********************************************************************************************************************/
bool __ini_extractField (ini_t *ini, FILE *file, ini_parser_t &parser, char ch)
{
switch (ch)
{ // Check for key value
case '=':
if (parser.state != INI_IN_SECTION)
{ // Make sure the key has a string content
parser.last = parser.pos;
if (parser.first >= 0)
{
if (!ini->selected) // Handle keys which are not in a section
{
if (!__ini_faddHeading (ini, file, 0, 0))
return false;
}

parser.key = __ini_faddKey (ini, file, parser.first,
parser.last - parser.first);
if (!parser.key)
return false;
}
parser.state = INI_CHECK_COMMENT | INI_ALLOW_COMMENT;
}
break;

// Check for header (must start far left)
case '[':
if (parser.state == INI_NEW_LINE)
{
parser.first = parser.pos + 1;
parser.state = INI_IN_SECTION;
}
break;

// Check for header termination
case ']':
if (parser.state == INI_IN_SECTION)
{
parser.last = parser.pos;
if (parser.first <= parser.last) // Handle []
{
if (!__ini_faddHeading (ini, file, parser.first,
parser.last - parser.first))
{
return false;
}
}
parser.state = INI_SKIP_LINE;
}
break;

default:
if (parser.state == INI_NEW_LINE)
{
parser.first = parser.pos;
parser.state = INI_NONE;
}
break;
}
return true;
}


/********************************************************************************************************************
* Function : __ini_processComment
* Parameters : ini - 指向 ini 文件数据库的指针
* : file - ini file to read heading from
* : parser - current parser decoding state
* Returns : false 代表出错,true 代表成功
* Globals Used :
* Globals Modified :
* Description : Comment strings are not passed to ini_extractField. Since multi char comments are now
* : supported we need re-process it should it eventually turn out not to be a comment.
********************************************************************************************************************/
bool __ini_processComment (ini_t *ini, FILE *file, ini_parser_t &parser)
{
const char *p = parser.comment;
for (; parser.commentpos > 0; parser.commentpos--)
{
if (!__ini_extractField (ini, file, parser, *p++))
return false;
parser.pos++;
}
return true;
}


/********************************************************************************************************************
* Function : __ini_process
* Parameters : ini - 指向 ini 文件数据库的指针
* : file - ini file to read heading from
* Returns : -1 代表出错,0 代表成功
* Globals Used :
* Globals Modified :
* Description : Read the ini file to determine all valid sections and keys. Also stores the location of
* : the keys data for faster accessing.
********************************************************************************************************************/
int __ini_process (ini_t *ini, FILE *file, const char *comment)
{
char *current, ch;
size_t count;
char *buffer;
ini_parser_t parser;
int pos = 0;

if (!ini)
return -1;
if (!file)
return -1;

// Get a read buffer
buffer = (char *) malloc (INI_BUFFER_SIZE * sizeof(char));
if (buffer == NULL)
return -1;

// Clear out an existing ini structure
__ini_delete (ini);
pos = 0;
parser.pos = pos;
parser.first = -1;
parser.last = -1;
parser.state = INI_NEW_LINE | INI_ALLOW_COMMENT;
parser.key = NULL;
parser.comment = comment;
parser.commentpos = 0;

do
{
fseek (file, pos, SEEK_SET);
current = buffer;
count = fread (buffer, sizeof(char),
INI_BUFFER_SIZE, file);

if (count <= 0)
{
if (feof (file))
{
count = 1;
*buffer = '\x1A';
}
}

while (count--)
{
ch = *current++;
switch (ch)
{
// Check for end of file
case '\x1A':
parser.state = INI_END_OF_FILE;
count = 0;
goto __ini_processLineEnd;

// Check for newline
case '\n': case '\r': case '\f':
parser.state = INI_NEW_LINE | INI_ALLOW_COMMENT;
parser.first = -1;
parser.last = -1;
__ini_processLineEnd:
if (!__ini_processComment (ini, file, parser))
goto __ini_processError;
parser.pos = pos;
__ini_processDataEnd:
// Now know keys data length
if (parser.key)
{
parser.key->length = (size_t) (parser.pos - parser.key->pos);
parser.key = NULL;
}
break;

case '\0':
// If we get this we are processing a binary or corrupt file.
// Drop out here, else we may do something nasty
goto __ini_processError;

default:
switch (parser.state & ~INI_ALLOW_COMMENT)
{
case INI_SKIP_LINE:
break;
//case INI_NONE:
//case INI_IN_SECTION:
case INI_NEW_LINE:
case INI_CHECK_COMMENT:
// Check to see if comments are allowed
if (isspace (ch))
{
parser.commentpos = 0;
parser.state |= INI_ALLOW_COMMENT;
parser.pos = pos;
break;
}
// Deliberate run on
default:
// Check for a comment
if (parser.state & INI_ALLOW_COMMENT)
{
if (ch == parser.comment[parser.commentpos])
{
parser.commentpos++;
if (parser.comment[parser.commentpos] == '\0')
{
parser.commentpos = 0;
parser.state = INI_SKIP_LINE;
goto __ini_processDataEnd;
}
break;
}

parser.state &= ~INI_ALLOW_COMMENT;
if (!__ini_processComment (ini, file, parser))
goto __ini_processError;
}

if (parser.state != INI_CHECK_COMMENT)
{
parser.pos = pos;
if (!__ini_extractField (ini, file, parser, ch))
goto __ini_processError;
}
}
break;
}

// Rev 1.1 Added - Exit if EOF
if (parser.state == INI_END_OF_FILE)
break;

fputc (ch, ini->ftmp);
pos++;
if (!pos)
{
printf ("INI file is too large\n");
__ini_delete (ini);
return -1;
}
}
} while (parser.state != INI_END_OF_FILE);

// Don't leave a random key located. This
// also forces user to call ini_locate*
ini->selected = NULL;
free (buffer);
return 0;

__ini_processError:
free (buffer);
__ini_delete (ini);
return -1;
}


/********************************************************************************************************************
* Function : __ini_store
* Parameters : ini - 指向 ini 文件数据库的指针
* : file - ini file to read heading from
* Returns : -1 代表错误,0 代表成功
* Globals Used :
* Globals Modified :
* Description : 写一个包含所有需要的改动的新 ini 文件
********************************************************************************************************************/
int __ini_store (ini_t *ini, FILE *file)
{
struct section_tag *current_h, *selected_h;
struct key_tag *current_k, *selected_k = NULL;
char *str = NULL;
size_t length = 0, equal_pos = 0;
int ret = -1;

if (!ini)
return -1;
if (!file)
return -1;

// Backup selected heading and key
selected_h = ini->selected;
// Be carefull if nothing was previously selected
if (selected_h != NULL)
selected_k = selected_h->selected;

current_h = ini->first;
while (current_h)
{
// Output section heading
if (*current_h->heading)
{
if (fprintf (file, "[%s]\n", current_h->heading) < 0)
goto __ini_storeError;
}

// Output the sections keys
equal_pos = __ini_averageLengthKey (current_h);
current_k = current_h->first;
while (current_k)
{
if (((current_k->length + 1) > length) || !str)
{ // Need more space
if (str)
free (str);
length = current_k->length + 1;
str = (char *) malloc (sizeof(char) * length);
if (!str)
goto __ini_storeError;
}

{ // Output key
char format[10];
// Rev 1.1 Added - to support lining up of equals characters
sprintf (format, "%%-%lus=", (unsigned long) equal_pos);
if (fprintf (file, format, current_k->key) < 0)
goto __ini_storeError;
}

// Output keys data (point to correct keys data)
ini->selected = current_h;
current_h->selected = current_k;
if (ini_readString ((ini_fd_t) ini, str, length) < 0)
goto __ini_storeError;

if (fprintf (file, "%s\n", str) < 0)
goto __ini_storeError;

current_k = current_k->pNext;
}

current_h = current_h->pNext;
if (fprintf (file, "\n") < 0)
goto __ini_storeError;
}
ret = 0;

__ini_storeError:
if (str)
free (str);
// Restore selected heading and key
ini->selected = selected_h;
if (selected_h != NULL)
selected_h->selected = selected_k;
return ret;
}



//********************************************************************************************************************
//********************************************************************************************************************
// 用户 INI 文件操纵函数
//********************************************************************************************************************
//********************************************************************************************************************


/********************************************************************************************************************
* Function : ini_open
* Parameters : name - 要创建的 ini 文件
* Returns : 指向 ini 数据库的指针
* Globals Used :
* Globals Modified :
* Description : 打开一个 ini 数据文件并将其内容读取到数据库中
********************************************************************************************************************/
ini_fd_t ini_open (const char *name, const char *mode,
const char *comment)
{
ini_mode_t _mode;
int flags = INI_NONE;

if (!mode)
return NULL;

// 转换模式
switch (*mode)
{
case 'r': _mode = INI_READ; break;
case 'w': _mode = INI_NEW; break;
case 'a': _mode = INI_EXIST; break;
default: return NULL;
}

// 检查可选的标志 (任意次序,但是仅限一次)
while (*++mode != '\0')
{
int f = INI_NONE;
switch (*mode)
{
case 'b': f = INI_BACKUP; break;
case 'i': f = INI_CASE; break;
default: return NULL;
}

if (flags & f)
return NULL;
flags |= f;
}

// NULL 可用于屏蔽注释
if (comment == NULL)
comment = "";
return (ini_fd_t) __ini_open (name, _mode, comment, flags);
}


/********************************************************************************************************************
* Function : ini_close
* Parameters : ini - 指向 ini 文件数据库的指针
* Returns : -1 代表出错, 0 代表成功
* Globals Used :
* Globals Modified :
* Description : 调用 close, 但是确保 ini 对象被删除
********************************************************************************************************************/
int ini_close (ini_fd_t fd)
{
if (fd)
return __ini_close ((ini_t *) fd, true);
return -1;
}


/********************************************************************************************************************
* Function : ini_flush
* Parameters : ini - 指向 ini 文件数据库的指针
* Returns : -1 代表出错,0 代表成功
* Globals Used :
* Globals Modified :
* Description : 调用 close, 但是确保 ini 对象不会被删除
********************************************************************************************************************/
int ini_flush (ini_fd_t fd)
{
if (fd)
return __ini_close ((ini_t *) fd, false);
return -1;
}


/********************************************************************************************************************
* Function : ini_dataLength
* Parameters : ini - 指向 ini 文件数据库的指针
* : heading - heading 名.
* : key - key 名
* Returns : Number of bytes to read the keys data in as a string. 1 must be added to this length
* : to cater for a NULL character.
* Globals Used :
* Globals Modified :
* Description : Number of bytes to read the keys data in as a string
********************************************************************************************************************/
int ini_dataLength (ini_fd_t fd)
{
ini_t *ini = (ini_t *) fd;
struct key_tag *_key;
if (!ini)
return -1;

// Check to make sure a section/key has
// been asked for by the user
if (!ini->selected)
return -1;
_key = ini->selected->selected;
if (!_key)
return -1;

}


/********************************************************************************************************************
* Function : ini_delete
* Parameters : ini - 指向 ini 文件数据库的指针
* Returns :
* Globals Used :
* Globals Modified :
* Description : 从内存中删除整个 ini 数据库,但是保留 ini 存根(stub)以便可以调用 ini_close
********************************************************************************************************************/
extern "C" int ini_delete (ini_fd_t fd)
{
ini_t *ini = (ini_t *) fd;
if (!ini)
return -1;
// Is file read only?
if (ini->mode == INI_READ)
return -1;
__ini_delete (ini);
return 0;
}


#ifdef INI_ADD_EXTRAS

/********************************************************************************************************************
* Function : ini_append
* Parameters : fdsrc - pointer to src ini file database to copy from.
* : fddst - pointer to dst ini file database to copy to.
* Returns :
* Globals Used :
* Globals Modified :
* Description : Copies the contents of the src ini to the dst ini. The resulting ini contains both
* : headings and keys from each. Src keys will overwrite dst keys or similar names.
********************************************************************************************************************/
extern "C" int ini_append (ini_fd_t fddst, ini_fd_t fdsrc)
{
struct section_tag *current_h;
struct key_tag *current_k;
struct section_tag *src_h, *dst_h;
struct key_tag *src_k = NULL, *dst_k = NULL;
char *data = NULL;
int length = 0, ret = -1;

ini_t *src = (ini_t *) fdsrc;
ini_t *dst = (ini_t *) fddst;
if (!(src && dst))
return -1;

// Is file read only?
if (dst->mode == INI_READ)
return -1;

// Backup selected heading and key
src_h = src->selected;
dst_h = dst->selected;
// Be carefull if nothing was previously selected
if (src_h != NULL)
src_k = src_h->selected;
if (dst_k != NULL)
dst_k = dst_h->selected;

// Go through the src ini headings
current_h = src->first;
while (current_h)
{ // Locate heading in the dst
ini_locateHeading (dst, current_h->heading);
// Go through the src keys under the heading
src->selected = current_h;
current_k = current_h->first;
while (current_k)
{ // Check if data buffer can hold the key
int i = current_k->length;
current_h->selected = current_k;
if (i > length)
{ // Make data buffer bigger, with some spare
length = i + 10;
if (data != NULL)
free (data);
data = (char *) malloc (sizeof (char) * length);
if (data == NULL)
goto ini_appendError;
}
// Locate key in dst ini file
ini_locateKey (dst, current_k->key);
// Copy the key from src to dst ini file
if (ini_readString (src, data, length) != i)
goto ini_appendError;
if (ini_writeString (dst, data) < 0)
goto ini_appendError;
// Move to next key
current_k = current_k->pNext;
}
// Move to next heading
current_h = current_h->pNext;
}
ret = 0;

ini_appendError:
if (data != NULL)
free (data);

// Restore selected headings and keys
src->selected = src_h;
dst->selected = dst_h;
if (src_h != NULL)
src_h->selected = src_k;
if (dst_h != NULL)
dst_h->selected = dst_k;
return ret;
}

#endif // INI_ADD_EXTRAS


// 加入代码模块

// ==========================================================================
// 加入标题头(Header)操纵函数

#ifndef HAVE_STRCASECMP
# define strcasecmp stricmp
#endif


/********************************************************************************************************************
* Function : __ini_addHeading
* Parameters : ini - pointer to ini file database. heading - heading name
* Returns : Pointer to new heading.
* Globals Used :
* Globals Modified :
* Description : Adds a new heading to the ini file database and updates the temporary workfile
********************************************************************************************************************/
struct section_tag *__ini_addHeading (ini_t *ini, char *heading)
{
struct section_tag *section;
size_t length;
long pos;

// Format heading for storing
__ini_strtrim (heading);

/* Create a backup of the file we are about to edit */
if (ini->heading == heading)
return __ini_locateHeading (ini, heading);

// Add new heading to work file and read it in
// using file add
fseek (ini->ftmp, 0, SEEK_END);
fputs ("\n[", ini->ftmp);
pos = ftell (ini->ftmp);
fputs (heading, ini->ftmp);
length = (size_t) (ftell (ini->ftmp) - pos);
section = __ini_faddHeading (ini, ini->ftmp, pos, length);
fseek (ini->ftmp, 0, SEEK_END);
fputs ("]\n", ini->ftmp);
ini->heading = section->heading;
if (section)
ini->flags |= INI_MODIFIED;
return section;
}


/********************************************************************************************************************
* Function : __ini_faddheading
* Parameters : ini - pointer to ini file database, file - ini file to read heading from
* : pos - heading position in file, length - heading length
* Returns : Pointer to new heading.
* Globals Used :
* Globals Modified :
* Description : Adds a new heading to the ini file database from the input file.
********************************************************************************************************************/
struct section_tag *__ini_faddHeading (ini_t *ini, FILE *file, long pos, size_t length)
{
struct section_tag *section;
char *str;

if (length)
{
length++;
str = (char *) malloc (sizeof(char) * length);
assert (str);
fseek (file, pos, SEEK_SET);
fgets (str, (int) length, file);
__ini_strtrim (str);
}
else
{
str = (char *) malloc (sizeof(char));
assert (str);
*str = '\0';
}

section = __ini_createHeading (ini, str);
// Make sure heading was created
if (!section)
{
free (str);
return NULL;
}

return section;
}


/********************************************************************************************************************
* Function : __ini_createHeading
* Parameters : ini - pointer to ini file database. heading - heading name
* Returns : Pointer to new heading.
* Globals Used :
* Globals Modified :
* Description : Adds an entry into the heading linked list ready for formating by the addHeading commands
********************************************************************************************************************/
struct section_tag *__ini_createHeading (ini_t *ini, char *heading)
{
struct section_tag *pNew;

pNew = __ini_locateHeading (ini, heading);
// Check to see if heading already exists
if (pNew)
free (heading);
else
{ // Create a new heading as dosen't exist
pNew = (struct section_tag *) malloc (sizeof (struct section_tag));
if (!pNew)
return NULL;
memset (pNew, 0, sizeof (struct section_tag));
pNew->heading = heading;

if (*heading)
{ // Found a named heading
pNew->pPrev = ini->last;
ini->last = pNew;
if (pNew->pPrev)
pNew->pPrev->pNext = pNew;
else
ini->first = pNew;
}
else
{ // Anonymous heading (special case),
// always comes first
pNew->pNext = ini->first;
ini->first = pNew;
if (pNew->pNext)
pNew->pNext->pPrev = pNew;
else
ini->last = pNew;
}
}

ini->selected = pNew;
return pNew;
}


/********************************************************************************************************************
* Function : __ini_deleteHeading
* Parameters : ini - pointer to ini file database. heading - heading name
* Returns :
* Globals Used :
* Globals Modified :
* Description : Removes a heading from the database only.
* : This change does not occur in the file until ini_close is called.
********************************************************************************************************************/
void __ini_deleteHeading (ini_t *ini)
{
struct section_tag *current_h;

// Delete Heading
current_h = ini->selected;
if (current_h)
{ // Delete Keys
while (current_h->first)
{
current_h->selected = current_h->first;
__ini_deleteKey (ini);
}

// Tidy up all users of this heading
ini->selected = NULL;
if (ini->last == current_h)
ini->last = current_h->pPrev;

// Break heading out of list
if (!current_h->pPrev)
ini->first = current_h->pNext;
else
current_h->pPrev->pNext = current_h->pNext;
if (current_h->pNext)
current_h->pNext->pPrev = current_h->pPrev;

// Delete Heading
free (current_h->heading);
free (current_h);
}
}


/********************************************************************************************************************
* Function : __ini_locateHeading
* Parameters : ini - pointer to ini file database. heading - heading name
* Returns : Pointer to heading.
* Globals Used :
* Globals Modified :
* Description : Locates a heading entry in the database.
********************************************************************************************************************/
struct section_tag *__ini_locateHeading (ini_t *ini, const char *heading)
{
struct section_tag *current_h;

// Search for heading
for (current_h = ini->first; current_h; current_h = current_h->pNext)
{
if (ini->flags & INI_CASE)
{
if (!strcmp (current_h->heading, heading))
break;
}
else if (!strcasecmp (current_h->heading, heading))
break;
}

ini->selected = current_h;
return current_h;
}


/********************************************************************************************************************
* Function : (public) ini_deleteHeading
* Parameters : ini - pointer to ini file descriptor
* Returns : -1 for Error and 0 on success
* Globals Used :
* Globals Modified :
* Description : Equivalent Microsoft write string API call where both data & key are set to NULL.
********************************************************************************************************************/
int ini_deleteHeading (ini_fd_t fd)
{
ini_t *ini = (ini_t *) fd;
if (!ini->selected)
return -1;
// Is file read only?
if (ini->mode == INI_READ)
return -1;
// Can't delete a temporary section
if (ini->selected == &(ini->tmpSection))
return -1;
__ini_deleteHeading (ini);
ini->flags |= INI_MODIFIED;
return 0;
}


/********************************************************************************************************************
* Function : (public) ini_locateHeading
* Parameters : fd - pointer to ini file descriptor
* Returns : -1 for Error and 0 on success
* Globals Used :
* Globals Modified :
* Description : Equivalent Microsoft write string API call where both data & key are set to NULL.
********************************************************************************************************************/
int ini_locateHeading (ini_fd_t fd, const char *heading)
{
ini_t *ini = (ini_t *) fd;
struct section_tag *section;

if (!heading)
return -1;

section = __ini_locateHeading (ini, heading);

if (section)
{
section->selected = NULL;
return 0;
}

// Ok no section was found, but maybe the user is wanting to create a
// new one so create it temporarily and see what actually happens later
{ // Remove old heading
section = &(ini->tmpSection);
if (section->heading)
free (section->heading);

// Add new heading
section->heading = strdup (heading);
if (!section->heading)
return -1;
section->selected = NULL;
ini->selected = section;
}
return -1;
}


/********************************************************************************************************************
* Function : (public) ini_currentHeading
* Parameters : fd - pointer to ini file descriptor
* Returns : NULL for Error and on success pointer to heading
* Globals Used :
* Globals Modified :
* Description : Returns currently selected heading
********************************************************************************************************************/
const char * ini_currentHeading (ini_fd_t fd)
{
ini_t *ini = (ini_t *) fd;
struct section_tag *section;

if (!ini)
return NULL;
section = ini->selected;
if (!section)
return NULL;
return section->heading;
}

// End of headings.c
// =============================================================================

// =============================================================================
// 加入键值(Key)操纵函数
// #include "keys.i"
#define INI_EQUALS_ALIGN 10

#if defined(HAVE_STRINGS_H)
# include <strings.h>
#endif

#ifndef HAVE_STRCASECMP
# define strcasecmp stricmp
#endif


/********************************************************************************************************************
* Function : __ini_addKey
* Parameters : ini - pointer to ini file database. key - key name
* Returns : Pointer to key.
* Globals Used :
* Globals Modified :
* Description : Adds a new key to the ini file database and updates the temporary workfile.
* : A heading operation must be called before this function.
********************************************************************************************************************/
struct key_tag *__ini_addKey (ini_t *ini, char *key)
{
struct key_tag *_key;
size_t length;
long pos;

// Format heading for storing
__ini_strtrim (key);
if (!*key)
return NULL;

// Add new key to work file and read it in
// using file add
fseek (ini->ftmp, 0, SEEK_END);
pos = ftell (ini->ftmp);
fputs (key, ini->ftmp);
length = (size_t) (ftell (ini->ftmp) - pos);
_key = __ini_faddKey (ini, ini->ftmp, pos, length);
fseek (ini->ftmp, 0, SEEK_END);
fputc ('=', ini->ftmp);
if (_key)
ini->flags |= INI_MODIFIED;
return _key;
}


/********************************************************************************************************************
* Function : __ini_averageLengthKey
* Parameters : current_h - pointer to current header.
* Returns : Returns the average key length
* Globals Used :
* Globals Modified :
* Description : Finds average key length for aligning equals.
********************************************************************************************************************/
size_t __ini_averageLengthKey (struct section_tag *current_h)
{
#ifdef INI_EQUALS_ALIGN
size_t equal_pos, equal_max, keylength;
size_t average = 0, count = 0;
struct key_tag *current_k;

// Rev 1.1 Added - Line up equals characters for keys
// Calculate Average
current_k = current_h->first;
while (current_k)
{
count++;
average += strlen (current_k->key);
current_k = current_k->pNext;
}

if (!count)
return 0;

average /= count;
equal_pos = (equal_max = average);

#if INI_EQUALS_ALIGN > 0
// Work out the longest key in that range
current_k = current_h->first;
while (current_k)
{
keylength = strlen (current_k->key);
equal_max = average + INI_EQUALS_ALIGN;

if ((equal_max > keylength) && (keylength > equal_pos))
equal_pos = keylength;
current_k = current_k->pNext;
}
#endif // INI_EQUALS_ALIGN > 0
return equal_pos;
#else
return 0;
#endif // INI_EQUALS_ALIGN
}


/********************************************************************************************************************
* Function : __ini_faddKey
* Parameters : ini - pointer to ini file database, file - ini file to read key from
* : pos - key position in file, length - key length
* Returns : Pointer to key.
* Globals Used :
* Globals Modified :
* Description : Adds a new key to the ini file database from the input file.
* : A heading operation must be called before this function.
********************************************************************************************************************/
struct key_tag *__ini_faddKey (ini_t *ini, FILE *file, long pos, size_t length)
{
struct key_tag *_key;
char *str;

length++;
str = (char *) malloc (sizeof(char) * length);
assert (str);
fseek (file, pos, SEEK_SET);
fgets (str, (int) length, file);
__ini_strtrim (str);

_key = __ini_createKey (ini, str);
if (!_key)
{ free (str);
return NULL;
}

_key->pos = pos + (long) length;
return _key;
}


/********************************************************************************************************************
* Function : __ini_createKey
* Parameters : ini - pointer to ini file database. key - key name
* Returns : Pointer to key.
* Globals Used :
* Globals Modified :
* Description : Adds an entry into the key linked list ready for formating by the addKey commands
* : A heading operation must be called before this function.
********************************************************************************************************************/
struct key_tag *__ini_createKey (ini_t *ini, char *key)
{
struct section_tag *section;
struct key_tag *pNew;
long pos;

if (!*key)
return NULL;

section = ini->selected;
pNew = __ini_locateKey (ini, key);
if (pNew)
{ // Reset details of existing key
free (pNew->key);
pNew->key = key;
pos = 0;
}
else
{ // Create a new key and add at end;
pNew = (struct key_tag *) malloc (sizeof (struct key_tag));
if (!pNew)
return NULL;
memset (pNew, 0, sizeof (struct key_tag));
pNew->key = key;

if (!section->first)
section->first = pNew;
else
section->last->pNext = pNew;

pNew->pPrev = section->last;
section->last = pNew;
section->selected = pNew;

#ifdef INI_USE_HASH_TABLE
{ // Rev 1.3 - Added
struct key_tag *pOld;
unsigned long crc32;
unsigned char accel;

crc32 = __ini_createCrc32 (key, (ini->flags & INI_CASE) != 0);
pNew->crc = crc32;
// Rev 1.3 - Add accelerator list
accel = (unsigned char) crc32 & 0x0FF;
pNew->pPrev_Acc = NULL;
pOld = section->keys[accel];
section->keys[accel] = pNew;
if (pOld) pOld->pPrev_Acc = pNew;
pNew->pNext_Acc = pOld;
}
#endif
}

section->selected = pNew;
return pNew;
}


/********************************************************************************************************************
* Function : __ini_deleteKey
* Parameters : ini - pointer to ini file database. key - key name
* Returns :
* Globals Used :
* Globals Modified :
* Description : Removes a key from the database only.
* : This change does not occur in the file until ini_close is called.
* : A heading operation must be called before this function.
********************************************************************************************************************/
void __ini_deleteKey (ini_t *ini)
{
struct key_tag *current_k;
struct section_tag *section = ini->selected;

current_k = section->selected;
if (current_k)
{
// Tidy up all users of this key
section->selected = NULL;
if (section->last == current_k)
section->last = current_k->pPrev;

// Check to see if all keys were removed
if (!current_k->pPrev)
section->first = current_k->pNext;
else
current_k->pPrev->pNext = current_k->pNext;
if (current_k->pNext)
current_k->pNext->pPrev = current_k->pPrev;

#ifdef INI_USE_HASH_TABLE
// Rev 1.3 - Take member out of accelerator list
if (!current_k->pPrev_Acc)
section->keys[(unsigned char) current_k->crc & 0x0FF] = current_k->pNext_Acc;
else
current_k->pPrev_Acc->pNext_Acc = current_k->pNext_Acc;
if (current_k->pNext_Acc)
current_k->pNext_Acc->pPrev_Acc = current_k->pPrev_Acc;
#endif // INI_USE_HASH_TABLE

// Delete Key
free (current_k->key);
free (current_k);
}
}


/********************************************************************************************************************
* Function : __ini_locateKey
* Parameters : ini - pointer to ini file database. key - key name
* Returns : Pointer to key.
* Globals Used :
* Globals Modified :
* Description : Locates a key entry in the database.
* : A heading operation must be called before this function.
********************************************************************************************************************/
struct key_tag *__ini_locateKey (ini_t *ini, const char *key)
{
struct key_tag *current_k;
struct section_tag *section;
section = ini->selected;

#ifdef INI_USE_HASH_TABLE
{ // Rev 1.3 - Added
unsigned long crc32;
crc32 = __ini_createCrc32 (key, (ini->flags & INI_CASE) != 0);

// Search for key
for (current_k = section->keys[(unsigned char) crc32 & 0x0FF]; current_k; current_k = current_k->pNext_Acc)
{
if (current_k->crc == crc32)
{
if (ini->flags & INI_CASE)
{
if (!strcmp (current_k->key, key))
break;
}
else if (!strcasecmp (current_k->key, key))
break;
}
}
}
#else
{ // Search for key
for (current_k = section->first; current_k; current_k = current_k->pNext)
{
if (ini->flags & INI_CASE)
{
if (!strcmp (current_k->key, key))
break;
}
else if (!strcasecmp (current_k->key, key))
break;
}
}
#endif // INI_USE_HASH_TABLE

section->selected = current_k;
return current_k;
}


/********************************************************************************************************************
* Function : (public) ini_deleteKey
* Parameters : ini - pointer to ini file database. heading - heading name. key - key name.
* Returns : -1 for Error and 0 on success
* Globals Used :
* Globals Modified :
* Description : Equivalent Microsoft write string API call where data set to NULL
********************************************************************************************************************/
int ini_deleteKey (ini_fd_t fd)
{
ini_t *ini = (ini_t *) fd;
if (!ini->selected || !ini->selected->selected)
return -1;
// Can't delete a temporary key
if (ini->selected->selected == &(ini->tmpKey))
return -1;
// Is file read only?
if (ini->mode == INI_READ)
return -1;
__ini_deleteKey (ini);
ini->flags |= INI_MODIFIED;
return 0;
}


/********************************************************************************************************************
* Function : (public) ini_locateKey
* Parameters : fd - pointer to ini file descriptor
* Returns : -1 for Error and 0 on success
* Globals Used :
* Globals Modified :
* Description :
********************************************************************************************************************/
int ini_locateKey (ini_fd_t fd, const char *key)
{
ini_t *ini = (ini_t *) fd;
struct key_tag *_key = NULL;

if (!key)
return -1;
if (!ini->selected)
return -1;

// Can't search for a key in a temporary heading
if (ini->selected != &(ini->tmpSection))
_key = __ini_locateKey (ini, key);

#ifdef INI_ADD_LIST_SUPPORT
// Rev 1.1 - Remove buffered list
if (ini->list)
{
free (ini->list);
ini->list = NULL;
}
#endif // INI_ADD_LIST_SUPPORT

if (_key)
return 0;

// Ok no key was found, but maybe the user is wanting to create a
// new one so create it temporarily and see what actually happens later
{ // Remove all key
_key = &(ini->tmpKey);
if (_key->key)
free (_key->key);

// Add new key
_key->key = strdup (key);
if (!_key)
return -1;
ini->selected->selected = _key;
}
return -1;
}


/********************************************************************************************************************
* Function : (public) ini_currentKey
* Parameters : fd - pointer to ini file descriptor
* Returns : NULL for Error and on success pointer to key
* Globals Used :
* Globals Modified :
* Description :
********************************************************************************************************************/
const char * ini_currentKey (ini_fd_t fd)
{
ini_t *ini = (ini_t *) fd;
struct section_tag *section;
struct key_tag *key;

if (!ini)
return NULL;
section = ini->selected;
if (!section)
return NULL;
key = section->selected;
if (!key)
return NULL;
return key->key;
}

// End of keys.i
// ===========================================================================

// 加入所支持的数据类型
// #include "types.i"

/********************************************************************************************************************
* Function : __ini_read
* Parameters : ini - pointer to ini file database, size - key length returned here
* Returns : -1 for Error and 0 on success.
* Globals Used :
* Globals Modified :
* Description : Common read functionality
********************************************************************************************************************/
static int __ini_read (ini_t *ini, size_t *size)
{
struct key_tag *_key;

if (!ini->selected)
return -1;
// Locate and read keys value
_key = ini->selected->selected;
if (!_key)
return -1;

if (_key->length)
{ // Locate and read back keys data
fseek (ini->ftmp, _key->pos, SEEK_SET);
}
else if (_key == &ini->tmpKey)
return -1; // Can't read tmpKey
*size = _key->length;
return 0;
}


#ifdef INI_ADD_LIST_SUPPORT
/********************************************************************************************************************
* Function : __ini_readList
* Parameters : ini - pointer to ini file database.
* Returns : Pointer to buffer holding data.
* Globals Used :
* Globals Modified :
* Description : Common read functionality from a list
********************************************************************************************************************/
static char *__ini_readList (ini_t *ini)
{
if (!ini->selected)
return NULL;
// Locate and read keys value
if (!ini->selected->selected)
return NULL;
return __ini_listRead (ini);
}
#endif // INI_ADD_LIST_SUPPORT


/********************************************************************************************************************
* Function : __ini_write
* Parameters : ini - pointer to ini file database. heading - heading name. key - key name.
* Returns : Pointer to new key.
* Globals Used :
* Globals Modified :
* Description : Make calls to add a new key and appends a description of changes in the backup file
********************************************************************************************************************/
static struct key_tag *__ini_write (ini_t *ini)
{
struct section_tag *section;
struct key_tag *key;

// Is file read only?
if (ini->mode == INI_READ)
return NULL;

// Check to make sure a section/key has
// been asked for by the user
section = ini->selected;
if (!section)
return NULL;
key = section->selected;
if (!key)
return NULL;

// Add or replace key
if (!__ini_addHeading (ini, section->heading))
return NULL;
return __ini_addKey (ini, key->key);
}


/********************************************************************************************************************
* Function : ini_readString
* Parameters : ini - 指向 ini 文件数据库的指针
* : str - 返回的字符串指针(要事先分配好内存)
* : size - 字符串长度
* Returns : -1 代表出错,其他代表读取的字符串长度(注意尾部的 Null 也算一个字节)
* Globals Used :
* Globals Modified :
* Description : 从键中读取数据部分并作为字符串返回
********************************************************************************************************************/
int ini_readString (ini_fd_t fd, char *str, size_t size)
{
ini_t *ini = (ini_t *) fd;

// Check size and reserve space for NULL
if (size-- <= 0)
return -1;

#ifdef INI_ADD_LIST_SUPPORT
if (ini->listDelims)
{
char *data = __ini_readList (ini);
if (!data)
return -1;
strncpy (str, data, size);
}
else
#endif // INI_ADD_LIST_SUPPORT
{ // Locate and read back keys data (Index ignored)
// Check to make sure size is correct
size_t length;
if (__ini_read (ini, &length) < 0)
return -1;
if (size > length)
size = length;
size = fread (str, sizeof(char), size, ini->ftmp);
}

str[size] = '\0';
__ini_strtrim (str);
return (int) size;
}


/********************************************************************************************************************
* Function : ini_writeString
* Parameters : ini - pointer to ini file database.
* : value - keys data
* Returns : -1 for Error and 0 on success
* Globals Used :
* Globals Modified :
* Description : Writes data part to a key.
* : Conforms to Microsoft API call. E.g. use NULLS to remove headings/keys
* : Headings and keys will be created as necessary
********************************************************************************************************************/
int ini_writeString (ini_fd_t fd, const char *str)
{
ini_t *ini = (ini_t *) fd;
struct key_tag *_key;

_key = __ini_write (ini);
if (!_key)
return -1;

// Write data to bottom of backup file
_key->length = strlen (str);
fprintf (ini->ftmp, "%s\n", str);
return 0;
}


/********************************************************************************************************************
* Function : ini_readInt
* Parameters : ini - pointer to ini file database.
* : value - keys data
* Returns : -1 for error or the number of values read.
* Globals Used :
* Globals Modified :
* Description : Reads data part from a key and returns it as a int
********************************************************************************************************************/
int ini_readInt (ini_fd_t fd, int *value)
{
ini_t *ini = (ini_t *) fd;
int ret = 0;

#ifdef INI_ADD_LIST_SUPPORT
if (ini->listDelims)
{
char *data = __ini_readList (ini);
if (data)
sscanf (data, "%d", value);
}
else
#endif // INI_ADD_LIST_SUPPORT
{
size_t length;
if (__ini_read (ini, &length) >= 0)
{
if (length > 0)
ret = fscanf (ini->ftmp, "%d", value);
}
}

if (ret == 1)
return 0;
return -1;
}


#ifdef INI_ADD_EXTRAS

/********************************************************************************************************************
* Function : ini_readLong
* Parameters : ini - pointer to ini file database.
* : value - keys data
* Returns : -1 for error or the number of values read.
* Globals Used :
* Globals Modified :
* Description : Reads data part from a key and returns it as a long
********************************************************************************************************************/
int ini_readLong (ini_fd_t fd, long *value)
{
ini_t *ini = (ini_t *) fd;
int ret = 0;

#ifdef INI_ADD_LIST_SUPPORT
if (ini->listDelims)
{
char *data = __ini_readList (ini);
if (data)
ret = sscanf (data, "%ld", value);
}
else
#endif // INI_ADD_LIST_SUPPORT
{
size_t length;
if (__ini_read (ini, &length) >= 0)
{
if (length > 0)
ret = fscanf (ini->ftmp, "%ld", value);
}
}

if (ret == 1)
return 0;
return -1;
}


/********************************************************************************************************************
* Function : ini_readDouble
* Parameters : ini - pointer to ini file database.
* : value - keys data
* Returns : -1 for error or the number of values read.
* Globals Used :
* Globals Modified :
* Description : Reads data part from a key and returns it as a double (real)
********************************************************************************************************************/
int ini_readDouble (ini_fd_t fd, double *value)
{
ini_t *ini = (ini_t *) fd;
int ret = 0;

#ifdef INI_ADD_LIST_SUPPORT
if (ini->listDelims)
{
char *data = __ini_readList (ini);
if (data)
ret = sscanf (data, "%lf", value);
}
else
#endif // INI_ADD_LIST_SUPPORT
{
size_t length;
if (__ini_read (ini, &length) >= 0)
{
if (length > 0)
ret = fscanf (ini->ftmp, "%lf", value);
}
}

if (ret == 1)
return 0;
return -1;
}


/********************************************************************************************************************
* Function : ini_readBool
* Parameters : ini - pointer to ini file database.
* : value - keys data
* Returns : -1 for error or the number of values read.
* Globals Used :
* Globals Modified :
* Description : Reads data part from a key and returns it as a int. Supported bool strings are 0, 1, true
* : false.
********************************************************************************************************************/
int ini_readBool (ini_fd_t fd, int *value)
{
ini_t *ini = (ini_t *) fd;
char buffer[6] = "";

#ifdef INI_ADD_LIST_SUPPORT
if (ini->listDelims)
{
char *data = __ini_readList (ini);
if (!data)
return -1;
sscanf (data, "%5s", buffer);
}
else
#endif // INI_ADD_LIST_SUPPORT
{
size_t length;
if (__ini_read (ini, &length) < 0)
return -1;
if (length > 0)
fscanf (ini->ftmp, "%5s", buffer);
}

{ // Convert string to lower case
char *p = buffer;
while (*p != '\0')
{
*p = (char) tolower (*p);
p++;
}
}

// Decode supported bool types
switch (*buffer)
{
case '0':
case '1':
if (buffer[1] != '\0')
return -1;
*value = *buffer - '0';
break;
default:
if (!strcasecmp (buffer, "true"))
*value = 1;
else if (!strcasecmp (buffer, "false"))
*value = 0;
else if (!strcasecmp (buffer, "on"))
*value = 1;
else if (!strcasecmp (buffer, "off"))
*value = 0;
else if (!strcasecmp (buffer, "yes"))
*value = 1;
else if (!strcasecmp (buffer, "no"))
*value = 0;
else // No match
return -1;
break;
}
return 0;
}


/********************************************************************************************************************
* Function : ini_writeInt
* Parameters : ini - pointer to ini file database.
* : value - keys data
* Returns : -1 for Error and 0 on success
* Globals Used :
* Globals Modified :
* Description : Writes data part to a key.
* : Headings and keys will be created as necessary
********************************************************************************************************************/
int ini_writeInt (ini_fd_t fd, int value)
{
ini_t *ini = (ini_t *) fd;
struct key_tag *_key;
long pos;

_key = __ini_write (ini);
if (!_key)
return -1;

// Write data to bottom of backup file
fprintf (ini->ftmp, "%d", value);
pos = ftell (ini->ftmp);
_key->length = (size_t) (pos - _key->pos);
fprintf (ini->ftmp, "\n");
return 0;
}


/********************************************************************************************************************
* Function : ini_writeLong
* Parameters : ini - pointer to ini file database.
* : value - keys data
* Returns : -1 for Error and 0 on success
* Globals Used :
* Globals Modified :
* Description : Writes data part to a key.
* : Headings and keys will be created as necessary
********************************************************************************************************************/
int ini_writeLong (ini_fd_t fd, long value)
{
ini_t *ini = (ini_t *) fd;
struct key_tag *_key;
long pos;

_key = __ini_write (ini);
if (!_key)
return -1;

// Write data to bottom of backup file
fprintf (ini->ftmp, "%ld", value);
pos = ftell (ini->ftmp);
_key->length = (size_t) (pos - _key->pos);
fprintf (ini->ftmp, "\n");
return 0;
}


/********************************************************************************************************************
* Function : ini_writeDouble
* Parameters : ini - pointer to ini file database.
* : value - keys data
* Returns : -1 for Error and 0 on success
* Globals Used :
* Globals Modified :
* Description : Writes data part to a key.
* : Headings and keys will be created as necessary
********************************************************************************************************************/
int ini_writeDouble (ini_fd_t fd, double value)
{
ini_t *ini = (ini_t *) fd;
struct key_tag *_key;
long pos;

_key = __ini_write (ini);
if (!_key)
return -1;

// Write data to bottom of backup file
fprintf (ini->ftmp, "%f", value);
pos = ftell (ini->ftmp);
_key->length = (size_t) (pos - _key->pos);
fprintf (ini->ftmp, "\n");
return 0;
}


/********************************************************************************************************************
* Function : ini_writeBool
* Parameters : ini - pointer to ini file database.
* : value - keys data
* Returns : -1 for Error and 0 on success
* Globals Used :
* Globals Modified :
* Description : Writes data part to a key.
* : Headings and keys will be created as necessary
********************************************************************************************************************/
int ini_writeBool (ini_fd_t fd, int value)
{
ini_t *ini = (ini_t *) fd;
struct key_tag *_key;
long pos;

// Check if value is legal
if ((value < 0) || (value > 1))
return -1;

_key = __ini_write (ini);
if (!_key)
return -1;

// Write data to bottom of backup file
if (value)
fprintf (ini->ftmp, "true");
else
fprintf (ini->ftmp, "false");
pos = ftell (ini->ftmp);
_key->length = (size_t) (pos - _key->pos);
fprintf (ini->ftmp, "\n");
return 0;
}

#endif // INI_ADD_EXTRAS

// End of types.i
// =============================================================================
大家都是出来卖的,何苦自己人为难自己人
那些活好的,或者活新的,或者花样多的,
或者老板拉皮条功夫好能拉到肯多花钱的客的,
拜托不要老是打击年老色衰的同行了

老鱼记事 老鱼侃棋 老鱼围脖


相关信息:


欢迎光临本社区,您还没有登录,不能发贴子。请在 这里登录