/*
* $Id: do_filecom.c,v 1.13 2003/06/07 02:23:58 hiroo Exp $
*/
/*
* FreeWnn is a network-extensible Kana-to-Kanji conversion system.
* This file is part of FreeWnn.
*
* Copyright Kyoto University Research Institute for Mathematical Sciences
* 1987, 1988, 1989, 1990, 1991, 1992
* Copyright OMRON Corporation. 1987, 1988, 1989, 1990, 1991, 1992, 1999
* Copyright ASTEC, Inc. 1987, 1988, 1989, 1990, 1991, 1992
* Copyright FreeWnn Project 1999, 2000, 2002, 2003
*
* Maintainer: FreeWnn Project <freewnn@tomo.gr.jp>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Do Global File command
*/
#if defined(HAVE_CONFIG_H)
#include <config.h>
#endif
#include <stdio.h>
#if STDC_HEADERS
# include <stdlib.h>
# include <string.h>
#else
# if HAVE_MALLOC_H
# include <malloc.h>
# endif
# if HAVE_STRINGS_H
# include <strings.h>
# endif
#endif /* STDC_HEADERS */
#include <sys/types.h>
#include <sys/stat.h>
#if STDC_HEADERS
# include <string.h>
#else
# if HAVE_STRINGS_H
# include <strings.h>
# endif
#endif /* STDC_HEADERS */
#if HAVE_UNISTD_H
# include <unistd.h>
#endif
#include "demcom.h"
#include "commonhd.h"
#include "de_header.h"
#include "jdata.h"
static int add_fid_to_env (int, int);
static int del_fid_from_env (int, int);
static void file_already_read (int, int);
static int file_discard (int, int);
static int file_loaded_local (void);
static int file_remove (char*, char*);
static int find_file_by_uniq (struct wnn_file_uniq *);
static int new_fid_in_env (int, int);
static int x_file_already_read (int, int);
int
file_init (void)
{
int i;
files = (struct wnn_file *)calloc(MAX_FILES, sizeof(struct wnn_file));
if ( files == NULL )
{
log_err ("file_init: malloc faild.");
return (NULL);
}
for (i = 0; i < MAX_FILES; i++)
{
files[i].ref_count = -1;
}
return (SUCCESS);
}
/*
Global File ope.
*/
/* mkdir */
/* #define MODE (0000000 | 0000777) */
/* #define MODE (0040000 | 0000731) */
/* #define MODE (0000000 | 0000733) */
#define MODE (0000000 | 0000755)
void
js_mkdir (void)
{
char path[FILENAME];
int x;
int err = 0;
err = envhandle ();
get_file_name (path, FILENAME);
if (err == -1)
{
error_ret ();
return;
}
x = mkdir (path, MODE);
error1 ("mkdir=%d\n", x);
if (x != 0)
{
wnn_errorno = WNN_MKDIR_FAIL;
error_ret ();
return;
}
chmod (path, MODE);
put4_cur (0);
putc_purge ();
}
/* access */
void
js_access (void)
{
char path[FILENAME];
int amode;
envhandle ();
amode = get4_cur ();
get_file_name (path, FILENAME);
#ifdef WRITE_CHECK
check_backup (path);
#endif
put4_cur (access (path, amode));
putc_purge ();
}
/* js_file_list_all */
void
js_file_list_all (void)
{
int i, c;
for (c = 0, i = 1; i < MAX_FILES; i++)
{
if (files[i].ref_count == -1)
continue;
c++;
}
put4_cur (c);
for (i = 1; i < MAX_FILES; i++)
{
if (files[i].ref_count == -1)
continue;
put4_cur (i); /* fid */
put4_cur (files[i].localf);
put4_cur (files[i].ref_count);
put4_cur (files[i].file_type);
puts_cur (files[i].name);
}
putc_purge ();
}
/* js_file_list */
void
js_file_list (void)
{
int i, c, env_id, fid;
env_id = envhandle ();
for (c = 0, i = 0; i < WNN_MAX_FILE_OF_AN_ENV; i++)
{
if ((env[env_id]->file)[i] == -1)
continue;
c++;
}
put4_cur (c);
for (i = 0; i < WNN_MAX_FILE_OF_AN_ENV; i++)
{
if ((fid = (env[env_id]->file)[i]) == -1)
continue;
put4_cur (fid); /* fid */
put4_cur (files[fid].localf);
put4_cur (files[fid].ref_count);
put4_cur (files[fid].file_type);
puts_cur (files[fid].name);
}
putc_purge ();
}
/* file info */
void
js_file_info (void)
{
int env_id, fid;
env_id = envhandle ();
fid = get4_cur ();
if (find_fid_in_env (env_id, fid) == -1)
{ /* valid */
wnn_errorno = WNN_FID_ERROR;
error_ret ();
putc_purge ();
return;
}
put4_cur (0);
puts_cur (files[fid].name);
put4_cur (files[fid].localf);
put4_cur (files[fid].ref_count);
put4_cur (files[fid].file_type);
putc_purge ();
}
/* file stat */
void
js_file_stat (void)
{
char n[FILENAME];
get4_cur (); /* env_id */
get_file_name (n, FILENAME);
put4_cur (file_stat (n));
putc_purge ();
}
int
file_stat (char *file_name)
{
FILE *fp;
struct wnn_file_head fh;
struct JT jt;
struct HJT hjt;
int ret;
#ifdef WRITE_CHECK
check_backup (file_name);
#endif
if ((fp = fopen (file_name, "r")) == NULL)
{
return WNN_FILE_NOT_EXIST;
}
if (input_file_header (fp, &fh) == -1)
{
ret = WNN_NOT_A_JISHO;
goto end;
}
switch (fh.file_type)
{
case WNN_FT_DICT_FILE:
if (input_header_jt (fp, &jt) == -1)
{
ret = WNN_NOT_A_JISHO;
goto end;
}
switch (jt.syurui)
{
case WNN_UD_DICT:
case WNN_REV_DICT:
case WNN_REG_DICT:
case WNN_STATIC_DICT:
#ifdef CONVERT_by_STROKE
case BWNN_REV_DICT:
#endif /* CONVERT_by_STROKE */
#ifdef CONVERT_with_SiSheng
case CWNN_REV_DICT:
#endif /* CONVERT_with_SiSheng */
ret = jt.syurui;
goto end;
default:
ret = WNN_NOT_A_JISHO;
goto end;
}
case WNN_FT_HINDO_FILE:
if (input_header_hjt (fp, &hjt) == -1)
{
ret = WNN_NOT_A_JISHO;
goto end;
}
ret = WNN_HINDO_FILE;
goto end;
case WNN_FT_FUZOKUGO_FILE:
ret = WNN_FUZOKUGO_FILE;
goto end;
default:
ret = WNN_NOT_A_JISHO;
goto end;
}
end:
fclose (fp);
return (ret);
}
/* js_file_discard */
void
js_file_discard (void)
{
int x;
int env_id, fid;
env_id = envhandle ();
fid = get4_cur ();
if (env_id == -1)
{
error_ret ();
return;
}
x = file_discard (env_id, fid);
if (x == -1)
{
error_ret ();
putc_purge ();
return;
}
put4_cur (x);
putc_purge ();
return;
}
static int
file_discard (int env_id, int fid)
{
if (del_fid_from_env (env_id, fid) == -1)
{ /* invalid */
wnn_errorno = WNN_FID_ERROR;
return -1;
}
used_dic_fuzoku_delete (env_id, fid); /* Delete used dicts and fuzoku. H.T. */
if (files[fid].ref_count != 0)
{
return files[fid].ref_count;
}
discardfile (&files[fid]);
files[fid].ref_count = -1;
return 0;
}
void
del_all_file_in_env (int env_id)
{
int i, fid;
for (i = 0; i < WNN_MAX_FILE_OF_AN_ENV; i++)
{
if ((fid = (env[env_id]->file)[i]) == -1)
continue;
files[fid].ref_count--;
if (files[fid].ref_count == 0)
{
discardfile (&files[fid]);
files[fid].ref_count = -1;
}
}
}
/* js_hindo_file_create */
void
js_hindo_file_create (void)
{
int env_id, fid;
char fn[FILE_NAME_L];
w_char com[1024];
char hpasswd[WNN_PASSWD_LEN];
env_id = get4_cur (); /* env_id */
fid = get4_cur ();
get_file_name (fn, FILE_NAME_L);
getws_cur (com, 1024);
gets_cur (hpasswd, WNN_PASSWD_LEN);
if (find_fid_in_env (env_id, fid) == -1)
{ /* valid */
wnn_errorno = WNN_FID_ERROR;
error_ret ();
putc_purge ();
return;
}
if (files[fid].ref_count == -1)
{
wnn_errorno = WNN_FID_ERROR;
error_ret ();
putc_purge ();
return;
}
if (create_hindo_file1 (&files[fid], fn, com, hpasswd) == -1)
{
wnn_errorno = WNN_FILE_CREATE_ERROR;
error_ret ();
putc_purge ();
return;
}
put4_cur (0);
putc_purge ();
}
/* js_hindo_file_create_client */
void
js_hindo_file_create_client (void)
{
int env_id, fid, i;
struct wnn_file_uniq *funiq;
env_id = get4_cur (); /* env_id */
fid = get4_cur ();
if (find_fid_in_env (env_id, fid) == -1)
{ /* valid */
wnn_errorno = WNN_FID_ERROR;
error_ret ();
putc_purge ();
return;
}
if (files[fid].ref_count == -1)
{
wnn_errorno = WNN_FID_ERROR;
error_ret ();
putc_purge ();
return;
}
put4_cur (0);
put4_cur (((struct JT *) (files[fid].area))->maxserial);
funiq = &files[fid].f_uniq_org;
put4_cur (funiq->time);
put4_cur (funiq->dev);
put4_cur (funiq->inode);
for (i = 0; i < WNN_HOSTLEN; i++)
{
putc_cur (funiq->createhost[i]);
}
putc_purge ();
}
/* js_dic_file_create */
void
js_dic_file_create (void)
{
int type, x;
char fn[FILE_NAME_L];
w_char com[1024];
char passwd[WNN_PASSWD_LEN], hpasswd[WNN_PASSWD_LEN];
get4_cur (); /* env_id */
get_file_name (fn, FILE_NAME_L);
getws_cur (com, 1024);
gets_cur (passwd, WNN_PASSWD_LEN);
gets_cur (hpasswd, WNN_PASSWD_LEN);
type = get4_cur ();
if (type != WNN_REV_DICT &&
#ifdef CONVERT_by_STROKE
type != BWNN_REV_DICT &&
#endif /* CONVERT_by_STROKE */
#ifdef CONVERT_with_SiSheng
type != CWNN_REV_DICT &&
#endif /* CONVERT_with_SiSheng */
type != WNN_UD_DICT)
{
wnn_errorno = WNN_NOT_A_UD;
error_ret ();
putc_purge ();
return;
}
x = create_null_dic (fn, com, passwd, hpasswd, type);
if (x == -1)
{
wnn_errorno = WNN_FILE_CREATE_ERROR;
error_ret ();
putc_purge ();
return;
}
put4_cur (0);
putc_purge ();
}
void
js_file_comment_set (void)
{
int fid, envi;
w_char comment[WNN_COMMENT_LEN];
int dic_no;
envi = get4_cur (); /* env_id */
fid = get4_cur ();
getws_cur (comment, WNN_COMMENT_LEN);
if (find_fid_in_env (envi, fid) == -1)
{ /* valid */
wnn_errorno = WNN_FID_ERROR;
goto ERR_RET;
}
if ((dic_no = chk_dic (envi, fid)) == -1 && (dic_no = chk_hindo (envi, fid)) == -1)
{
wnn_errorno = WNN_FID_ERROR;
goto ERR_RET;
}
if (dic_table[dic_no].body == fid && dic_table[dic_no].rw == WNN_DIC_RDONLY)
{
wnn_errorno = WNN_RDONLY;
goto ERR_RET;
}
if (dic_table[dic_no].hindo == fid && dic_table[dic_no].hindo_rw == WNN_DIC_RDONLY)
{
wnn_errorno = WNN_RDONLY;
goto ERR_RET;
}
if (file_comment_set (&files[fid], comment) == NULL)
{
goto ERR_RET;
}
put4_cur (0);
putc_purge ();
return;
ERR_RET:
error_ret ();
putc_purge ();
}
static FILE *
xFopen (char *n, char *mode)
{
FILE *f;
if (n == NULL)
return NULL;
#ifdef WRITE_CHECK
check_backup (n);
#endif
if ((f = fopen (n, mode)) == NULL)
{
wnn_errorno = WNN_NO_EXIST;
return (FILE *) - 1;
}
return f;
}
static void
xFclose (FILE *f)
{
if (f == NULL)
return;
fclose (f);
}
/* js_file_loaded_local (in client machine) */
void
js_file_loaded_local (void)
{
/* get4_cur(); env_id */
file_loaded_local ();
}
static int
file_loaded_local (void)
{
int x, i;
struct wnn_file_uniq fq;
fq.time = get4_cur ();
fq.dev = get4_cur ();
fq.inode = get4_cur ();
for (i = 0; i < WNN_HOSTLEN; i++)
{
fq.createhost[i] = getc_cur ();
}
/*
fprintf(stderr,"js_file_loaded:ti=%x,dev=%x,inode=%x,%s\n",
fq.time,fq.dev,fq.inode,fq.createhost);
*/
x = find_file_by_uniq (&fq);
if (x == -1)
put4_cur (-1);
else
put4_cur (x);
putc_purge ();
return x;
}
/* js_file_loaded (in jserver machine) */
void
js_file_loaded (void)
{
char n[FILE_NAME_L];
int x;
/* get4_cur(); env_id */
get_file_name (n, FILE_NAME_L);
if ((x = file_loaded (n)) < 0)
put4_cur (-1);
else
put4_cur (x);
putc_purge ();
}
int
file_loaded (char *n)
{
int x;
FILE *f;
struct wnn_file_head fh;
f = xFopen (n, "r");
if (f == (FILE *) - 1 || f == NULL)
{
wnn_errorno = WNN_NO_EXIST;
return -1;
}
x = input_file_header (f, &fh);
if (x == -1)
{ /* Must not do check_inode to non-wnn file */
xFclose (f);
wnn_errorno = WNN_NOT_A_FILE;
return -1;
}
/* Check I-node H.T. */
if (check_inode (f, &fh) == -1)
{
#ifdef WRITE_CHECK
if (change_file_uniq (&fh, n) == -1)
{
xFclose (f);
wnn_errorno = WNN_INODE_CHECK_ERROR;
return (-1);
}
else
{
xFclose (f);
f = xFopen (n, "r");
if (f == (FILE *) - 1 || f == NULL)
{
wnn_errorno = WNN_NO_EXIST;
return -1;
}
if (check_inode (f, &fh) == -1)
{
xFclose (f);
wnn_errorno = WNN_INODE_CHECK_ERROR;
return (-1);
}
}
#else /* WRITE_CHECK */
if ((change_file_uniq (&fh, n) == -1) || (check_inode (f, &fh) == -1))
{
xFclose (f);
wnn_errorno = WNN_INODE_CHECK_ERROR;
return (-1);
}
#endif /* WRITE_CHECK */
}
xFclose (f);
x = find_file_by_uniq (&fh.file_uniq);
if (x == -1)
x = FILE_NOT_READ;
return x;
}
static int
find_file_by_uniq (struct wnn_file_uniq *uniq)
{
int i;
for (i = 1; i < MAX_FILES; i++)
{
if (files[i].ref_count == -1)
continue;
if (!f_uniq_cmp (&files[i].f_uniq, uniq))
return i;
}
return -1;
}
/* js_file_send (from Client to Jserver) */
void
js_file_send (void)
{
char n[FILE_NAME_L];
int env_id, fid;
env_id = get4_cur (); /* env_id */
/* file loaded ? */
fid = file_loaded_local ();
if (fid != -1)
{
if (x_file_already_read (env_id, fid) == -1)
{ /* H.T. */
error_ret ();
putc_purge ();
return;
}
else
{
put4_cur (0);
putc_purge ();
return;
}
}
if ((fid = get_new_fid ()) == -1)
{ /* no more file */
wnn_errorno = WNN_NO_MORE_FILE;
error_ret ();
putc_purge ();
return;
}
put4_cur (1);
putc_purge ();
gets_cur (n, FILE_NAME_L);
/* read file */
files[fid].localf = REMOTE;
files[fid].ref_count = 0;
strcpy (files[fid].name, n);
if (read_file (&files[fid]) == -1)
{
/* wnn_errorno=WNN_FILE_READ_ERROR; */
error_ret ();
putc_purge ();
files[fid].ref_count = -1; /* fail */
return;
}
add_fid_to_env (env_id, fid);
put4_cur (fid);
putc_purge ();
}
/* js_file_read */
void
js_file_read (void)
{
char n[FILE_NAME_L];
int env_id, fid;
env_id = get4_cur (); /* env_id */
get_file_name (n, FILE_NAME_L);
fid = file_loaded (n);
if (fid == -1)
{
error_ret ();
putc_purge ();
return;
}
if (FILE_NOT_READ != fid)
{ /* already read */
file_already_read (env_id, fid);
putc_purge ();
return;
}
if ((fid = get_new_fid ()) == -1)
{ /* no more file */
wnn_errorno = WNN_NO_MORE_FILE;
error_ret ();
putc_purge ();
return;
}
/* read file */
files[fid].localf = LOCAL;
files[fid].ref_count = 0;
strcpy (files[fid].name, n);
if (read_file (&files[fid]) == -1)
{
/* wnn_errorno=WNN_FILE_READ_ERROR;*/
error_ret ();
putc_purge ();
files[fid].ref_count = -1; /* fail */
return;
}
add_fid_to_env (env_id, fid);
put4_cur (fid);
putc_purge ();
}
static void
file_already_read (int env_id, int fid)
{
if (-1 != find_fid_in_env (env_id, fid))
{ /* already exists in env */
put4_cur (fid);
return;
}
if (-1 == add_fid_to_env (env_id, fid))
{
wnn_errorno = WNN_NO_MORE_FILE;
error_ret ();
return;
}
put4_cur (fid);
return;
}
static int
x_file_already_read (int env_id, int fid)
{
if (-1 != find_fid_in_env (env_id, fid))
{ /* already exists in env */
return fid;
}
if (-1 == add_fid_to_env (env_id, fid))
{
wnn_errorno = WNN_NO_MORE_FILE;
return -1;
}
return fid;
}
/* js_file_write */
void
js_file_write (void)
{
int env_id, fid;
char n[FILE_NAME_L];
env_id = get4_cur (); /* env_id */
fid = get4_cur ();
get_file_name (n, FILE_NAME_L);
if (find_fid_in_env (env_id, fid) == -1)
{ /* valid */
wnn_errorno = WNN_FID_ERROR;
error_ret ();
putc_purge ();
return;
}
if (files[fid].ref_count == -1)
{
wnn_errorno = WNN_FID_ERROR;
error_ret ();
putc_purge ();
return;
}
if (strcmp (n, "") == 0)
strcpy (n, files[fid].name);
if (write_file (&files[fid], n) == -1)
{
/* wnn_errorno=WNN_FILE_WRITE_ERROR; */ error_ret ();
putc_purge ();
return;
}
put4_cur (fid);
putc_purge ();
}
/* js_file_receive (from Jserver to Client) */
void
js_file_receive (void)
{
int env_id, fid;
struct wnn_file_uniq fq;
int mode = 1;
int i;
int error = 0;
env_id = get4_cur (); /* env_id */
fid = get4_cur ();
/**/ if (find_fid_in_env (env_id, fid) == -1)
{ /* valid */
puts_cur ("!");
putc_purge (); /* dummy */
error = 1;
}
else
{
puts_cur (files[fid].name);
putc_purge ();
}
if (get4_cur () == -1)
return; /* Ack */
fq.time = get4_cur ();
fq.dev = get4_cur ();
fq.inode = get4_cur ();
for (i = 0; i < WNN_HOSTLEN; i++)
{
fq.createhost[i] = getc_cur ();
}
if (error || files[fid].ref_count == -1)
{
wnn_errorno = WNN_FID_ERROR;
error_ret ();
putc_purge ();
return;
}
if (fq.time != 0)
{ /* Old File Exist */
mode = must_write_file (&files[fid], &fq);
}
if (mode == -1)
{
wnn_errorno = WNN_FID_ERROR;
error_ret ();
putc_purge ();
return;
}
else if (mode == 0)
{ /* need not saving */
put4_cur (0);
putc_purge ();
return;
}
put4_cur (mode);
putc_purge ();
if (get4_cur () == -1)
return;
/**/ if (rcv_file (&files[fid], mode) == -1)
{
/* wnn_errorno=WNN_FILE_WRITE_ERROR; */ error_ret ();
putc_purge ();
return;
}
if (mode == 1 || mode == 2)
{
clear_dirty_bit (&files[fid]);
}
put4_cur (0);
/*
put4_cur(fid);
*/
putc_purge ();
}
/* */
int
get_new_fid (void)
{
int fid;
for (fid = 1; fid < MAX_FILES; fid++)
{
if (files[fid].ref_count == -1)
return fid; /* find new area */
}
return -1; /* no more file */
}
/*
routines for FID in the ENV
*/
static int
add_fid_to_env (int env_id, int fid)
{
int i;
if ((i = new_fid_in_env (env_id, -1)) == -1)
{
return -1;
}
(env[env_id]->file)[i] = fid;
files[fid].ref_count++;
return i;
}
static int
del_fid_from_env (int env_id, int fid)
{
int i;
if ((i = find_fid_in_env (env_id, fid)) == -1)
{
return -1;
}
(env[env_id]->file)[i] = -1;
files[fid].ref_count--;
return i;
}
int
find_fid_in_env (int env_id, int fid)
{
register int i;
register int *file = env[env_id]->file;
for (i = 0; i < WNN_MAX_FILE_OF_AN_ENV; i++)
{
if ((file)[i] == -1)
continue;
if ((file)[i] == fid)
{
return i;
}
}
return -1;
}
static int
new_fid_in_env (int env_id, int fid)
{
int i;
for (i = 0; i < WNN_MAX_FILE_OF_AN_ENV; i++)
{
if ((env[env_id]->file)[i] == -1)
{
return i;
}
}
return -1;
}
/* js_file_remove (remove file from UNIX file system) H.T. */
void
js_file_remove (void)
{
char n[FILE_NAME_L];
char passwd[WNN_PASSWD_LEN];
int x;
get_file_name (n, FILE_NAME_L);
gets_cur (passwd, WNN_PASSWD_LEN);
x = file_remove (n, passwd);
if (x == -1)
{
error_ret ();
}
else
{
put4_cur (0);
}
putc_purge ();
}
static int
file_remove (char *n, char *passwd)
{
int fid;
FILE *fp;
struct wnn_file_head fh;
fid = file_loaded (n);
if (fid == -1)
{
return (-1);
}
if (FILE_NOT_READ != fid)
{ /* already read */
wnn_errorno = WNN_FILE_IN_USE;
return (-1);
}
#ifdef WRITE_CHECK
check_backup (n);
#endif
if ((fp = fopen (n, "r")) == NULL)
{
wnn_errorno = WNN_FILE_READ_ERROR;
return (-1);
}
if (input_file_header (fp, &fh) == -1)
{
fclose (fp);
return (-1);
}
fclose (fp);
if (!check_pwd (passwd, fh.file_passwd))
{
wnn_errorno = WNN_INCORRECT_PASSWD;
return (-1);
}
if (unlink (n) == -1)
{
wnn_errorno = WNN_UNLINK;
return (-1);
}
return (0);
}
void
js_file_password_set (void)
{
int fid, envi, which;
char old[WNN_PASSWD_LEN];
char new[WNN_PASSWD_LEN];
int ret;
envi = get4_cur (); /* env_id */
fid = get4_cur ();
which = get4_cur ();
gets_cur (old, WNN_PASSWD_LEN);
gets_cur (new, WNN_PASSWD_LEN);
if (find_fid_in_env (envi, fid) == -1)
{
wnn_errorno = WNN_FID_ERROR;
error_ret ();
putc_purge ();
return;
}
ret = file_password_set (&files[fid], which, old, new);
if (ret == -1)
{
goto ERR_RET;
}
put4_cur (0);
putc_purge ();
return;
ERR_RET:
error_ret ();
putc_purge ();
}
syntax highlighted by Code2HTML, v. 0.9.1