/*
* Amanda, The Advanced Maryland Automatic Network Disk Archiver
* Copyright (c) 2013-2016 Carbonite, Inc. All Rights Reserved.
* All Rights Reserved.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of U.M. not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. U.M. makes no representations about the
* suitability of this software for any purpose. It is provided "as is"
* without express or implied warranty.
*
*/
/*
*/
#include "amanda.h"
#include "amutil.h"
#include "cmdfile.h"
#define EXPIRE_DELAY 24*60*60
#define EXPIRE_ADJUST 23*60*60
static void free_cmddata(gpointer p);
//static cmddata_t *duplicate_cmddata(cmddata_t *cmddata);
static void
free_cmddata(
gpointer p)
{
cmddata_t *cmddata = p;
g_free(cmddata->config);
g_free(cmddata->src_storage);
g_free(cmddata->src_pool);
g_free(cmddata->src_label);
g_free(cmddata->src_labels_str);
slist_free_full(cmddata->src_labels, g_free);
g_free(cmddata->holding_file);
g_free(cmddata->hostname);
g_free(cmddata->diskname);
g_free(cmddata->dump_timestamp);
g_free(cmddata->dst_storage);
g_free(cmddata);
}
/*
static cmddata_t *
duplicate_cmddata(
cmddata_t *cmddata)
{
cmddata_t *new_cmddata = g_new0(cmddata_t, 1);
new_cmddata->id = cmddata->id;
new_cmddata->operation = cmddata->operation;
new_cmddata->config = g_strdup(cmddata->config);
new_cmddata->storage = g_strdup(cmddata->storage);
new_cmddata->pool = g_strdup(cmddata->pool);
new_cmddata->label = g_strdup(cmddata->label);
new_cmddata->holding_file = g_strdup(cmddata->holding_file);
new_cmddata->hostname = g_strdup(cmddata->hostname);
new_cmddata->diskname = g_strdup(cmddata->diskname);
new_cmddata->dump_timestamp = g_strdup(cmddata->dump_timestamp);
new_cmddata->dst_storage = g_strdup(cmddata->dst_storage);
new_cmddata->working_pid = cmddata->working_pid;
new_cmddata->status = cmddata->status;
new_cmddata->size = cmddata->size;
new_cmddata->start_time = cmddata->start_time;
return new_cmddata;
}
*/
void
unlock_cmdfile(
cmddatas_t *cmddatas)
{
file_lock_unlock(cmddatas->lock);
}
void
close_cmdfile(
cmddatas_t *cmddatas)
{
file_lock_free(cmddatas->lock);
g_hash_table_destroy(cmddatas->cmdfile);
g_free(cmddatas);
}
static int checked_working_pid = 0;
#define NB_PIDS 10
static gboolean need_rewrite;
static void
cmdfile_set_expire(
gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data G_GNUC_UNUSED)
{
cmddata_t *cmddata = value;
if (cmddata->operation == CMD_RESTORE &&
cmddata->status != CMD_DONE &&
cmddata->working_pid == 0) {
if (cmddata->expire < time(NULL) + EXPIRE_ADJUST) {
need_rewrite = TRUE;
}
cmddata->expire = time(NULL) + EXPIRE_DELAY;
}
}
static void
cmdfile_set_to_DONE(
gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data G_GNUC_UNUSED)
{
cmddata_t *cmddata = value;
if (cmddata->operation == CMD_RESTORE &&
cmddata->status != CMD_DONE &&
cmddata->working_pid == 0 &&
cmddata->expire < time(NULL)) {
cmddata->status = CMD_DONE;
need_rewrite = TRUE;
}
}
cmddatas_t *
read_cmdfile(
char *filename)
{
cmddatas_t *cmddatas = g_new0(cmddatas_t, 1);
cmddata_t *cmddata;
char *s, *fp, *operation;
char ch;
char **xlines;
int i;
int pid;
pid_t pids[NB_PIDS];
pid_t new_pids[NB_PIDS];
int nb_pids = 0;
int result;
gboolean generic_command_restore = FALSE;
gboolean specific_command_restore = FALSE;
cmddatas->lock = file_lock_new(filename);
cmddatas->cmdfile = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL, &free_cmddata);
need_rewrite = FALSE;
// open
while ((result = file_lock_lock(cmddatas->lock)) == 1) {
sleep(1);
}
if (result != 0) {
g_debug("read_cmdfile open failed: %s", strerror(errno));
}
if (!cmddatas->lock->data) {
cmddatas->version = 1;
cmddatas->max_id = 0;
return cmddatas;
}
xlines = g_strsplit(cmddatas->lock->data, "\n", 0);
// read
if (sscanf(xlines[0], "VERSION %d", &cmddatas->version) != 1) {};
if (sscanf(xlines[1], "ID %d", &cmddatas->max_id) != 1) {};
// read cmd
for (i=2; xlines[i] != NULL; i++) {
int id;
s = xlines[i];
if (*s == '\0') continue;
ch = *s++;
skip_whitespace(s, ch);
if (ch == '\0' || sscanf((s - 1), "%d", &id) != 1) {
continue;
}
skip_integer(s, ch);
skip_whitespace(s, ch);
operation = s - 1;
skip_non_whitespace(s, ch);
s[-1] = '\0';
if (!g_str_equal(operation, "FLUSH") &&
!g_str_equal(operation, "COPY") &&
!g_str_equal(operation, "RESTORE")) {
g_debug("BAD operation %s: %s", operation, s);
continue;
}
cmddata = g_new0(cmddata_t, 1);
cmddata->id = id;
skip_whitespace(s, ch);
fp = s - 1;
skip_non_whitespace(s, ch);
s[-1] = '\0';
cmddata->config = unquote_string(fp);
if (g_str_equal(operation, "FLUSH")) {
cmddata->operation = CMD_FLUSH;
skip_whitespace(s,ch);
fp = s - 1;
skip_quoted_string(s, ch);
s[-1] = '\0';
cmddata->holding_file = unquote_string(fp);
} else if (g_str_equal(operation, "COPY")) {
char *src_labels;
char *slabels;
char *a;
cmddata->operation = CMD_COPY;
skip_whitespace(s,ch);
fp = s - 1;
skip_quoted_string(s, ch);
s[-1] = '\0';
cmddata->src_storage = unquote_string(fp);
skip_whitespace(s,ch);
fp = s - 1;
skip_quoted_string(s, ch);
s[-1] = '\0';
cmddata->src_pool = unquote_string(fp);
skip_whitespace(s,ch);
fp = s - 1;
skip_quoted_string(s, ch);
s[-1] = '\0';
cmddata->src_label = unquote_string(fp);
skip_whitespace(s,ch);
fp = s - 1;
skip_integer(s, ch);
s[-1] = '\0';
cmddata->src_fileno = atoi(fp);
skip_whitespace(s,ch);
fp = s - 1;
skip_quoted_string(s, ch);
s[-1] = '\0';
slabels = src_labels = unquote_string(fp);
cmddata->src_labels_str = g_strdup(src_labels);
a = strstr(slabels, " ;");
if (a) {
slabels = a+2;
while ((a = strstr(slabels, " ;"))) {
*a = '\0';
cmddata->src_labels = g_slist_append(cmddata->src_labels, g_strdup(slabels));
slabels = a+2;
}
}
g_free(src_labels);
skip_whitespace(s,ch);
fp = s - 1;
skip_integer(s, ch);
s[-1] = '\0';
cmddata->start_time = atoll(fp);
} else if (g_str_equal(operation, "RESTORE")) {
cmddata->operation = CMD_RESTORE;
skip_whitespace(s,ch);
fp = s - 1;
skip_quoted_string(s, ch);
s[-1] = '\0';
cmddata->src_storage = unquote_string(fp);
skip_whitespace(s,ch);
fp = s - 1;
skip_quoted_string(s, ch);
s[-1] = '\0';
cmddata->src_pool = unquote_string(fp);
skip_whitespace(s,ch);
fp = s - 1;
skip_quoted_string(s, ch);
s[-1] = '\0';
if (g_str_equal(cmddata->src_pool, "HOLDING")) {
cmddata->holding_file = unquote_string(fp);
} else {
cmddata->src_label = unquote_string(fp);
}
skip_whitespace(s,ch);
fp = s - 1;
skip_integer(s, ch);
s[-1] = '\0';
cmddata->src_fileno = atoi(fp);
skip_whitespace(s,ch);
fp = s - 1;
skip_integer(s, ch);
s[-1] = '\0';
cmddata->expire = atoll(fp);
} else {
}
skip_whitespace(s, ch);
fp = s - 1;
skip_quoted_string(s, ch);
s[-1] = '\0';
cmddata->hostname = unquote_string(fp);
skip_whitespace(s, ch);
fp = s - 1;
skip_quoted_string(s, ch);
s[-1] = '\0';
cmddata->diskname = unquote_string(fp);
skip_whitespace(s, ch);
fp = s - 1;
skip_quoted_string(s, ch);
s[-1] = '\0';
cmddata->dump_timestamp = unquote_string(fp);
skip_whitespace(s,ch);
fp = s - 1;
skip_quoted_string(s, ch);
s[-1] = '\0';
cmddata->level = atoi(fp);
skip_whitespace(s, ch);
if (cmddata->operation != CMD_RESTORE) {
fp = s - 1;
skip_quoted_string(s, ch);
s[-1] = '\0';
cmddata->dst_storage = unquote_string(fp);
skip_whitespace(s, ch);
}
fp = s - 1;
skip_non_whitespace(s, ch);
s[-1] = '\0';
if (sscanf(fp, "WORKING:%d", &pid) != 1) {
}
cmddata->working_pid = pid;
if (cmddata->operation == CMD_RESTORE) {
if (cmddata->working_pid == 0)
generic_command_restore = TRUE;
else
specific_command_restore = TRUE;
}
skip_whitespace(s, ch);
fp = s - 1;
skip_non_whitespace(s, ch);
s[-1] = '\0';
if (g_str_equal(fp, "DONE")) {
cmddata->status = CMD_DONE;
} else if (g_str_equal(fp, "TODO")) {
cmddata->status = CMD_TODO;
} else if (strncmp(fp, "PARTIAL", 7) == 0) {
long long lsize;
cmddata->status = CMD_PARTIAL;
if (sscanf(fp, "PARTIAL:%lld", &lsize) != 1) {
} else {
cmddata->size = lsize;
}
} else {
}
/* validate working_pid */
if (!checked_working_pid && cmddata->working_pid != 0) {
int i;
for (i = 0; i < nb_pids; i++) {
if (pids[i] == cmddata->working_pid) {
cmddata->working_pid = new_pids[i];
i += 100;
continue;
}
}
if (nb_pids < NB_PIDS) {
pids[nb_pids] = cmddata->working_pid;
if (kill(cmddata->working_pid, 0) != 0)
cmddata->working_pid =0;
new_pids[nb_pids] = cmddata->working_pid;
nb_pids++;
}
}
g_hash_table_insert(cmddatas->cmdfile, GINT_TO_POINTER(cmddata->id), cmddata);
}
if (generic_command_restore) {
if (specific_command_restore) {
/* set expire to NOW+24h of all genric command_restore */
g_hash_table_foreach(cmddatas->cmdfile, &cmdfile_set_expire, NULL);
} else {
/* check start_time of all generic command_restore and remove them */
g_hash_table_foreach(cmddatas->cmdfile, &cmdfile_set_to_DONE, NULL);
}
}
g_strfreev(xlines);
checked_working_pid = 1;
if (need_rewrite) {
write_cmdfile(cmddatas);
return read_cmdfile(filename);
}
return cmddatas;
}
static void
cmdfile_write(
gpointer key,
gpointer value,
gpointer user_data)
{
int id = GPOINTER_TO_INT(key);
cmddata_t *cmddata = value;
GPtrArray *lines = user_data;
char *line;
char *config;
char *hostname;
char *diskname;
char *dump_timestamp;
char *dst_storage;
char *status;
assert(id == cmddata->id);
if (cmddata->status == CMD_DONE && cmddata->working_pid == 0)
return;
config = quote_string(cmddata->config);
hostname = quote_string(cmddata->hostname);
diskname = quote_string(cmddata->diskname);
dump_timestamp = quote_string(cmddata->dump_timestamp);
dst_storage = quote_string(cmddata->dst_storage);
switch (cmddata->status) {
case CMD_DONE: status = g_strdup("DONE");
break;
case CMD_TODO: status = g_strdup("TODO");
break;
case CMD_PARTIAL: status = g_strdup_printf(
"PARTIAL:%lld", (long long)cmddata->size);
break;
default: status = NULL;
break;
}
if (cmddata->operation == CMD_FLUSH) {
char *holding_file = quote_string(cmddata->holding_file);
line = g_strdup_printf("%d FLUSH %s %s %s %s %s %d %s WORKING:%d %s\n",
id, config, holding_file, hostname, diskname,
dump_timestamp, cmddata->level, dst_storage, (int)cmddata->working_pid, status);
g_free(holding_file);
g_ptr_array_add(lines, line);
} else if (cmddata->operation == CMD_COPY) {
char *src_storage = quote_string(cmddata->src_storage);
char *src_pool = quote_string(cmddata->src_pool);
char *src_label = quote_string(cmddata->src_label);
char *src_labels_str = quote_string(cmddata->src_labels_str);
line = g_strdup_printf("%d COPY %s %s %s %s %d %s %lu %s %s %s %d %s WORKING:%d %s\n",
id, config, src_storage, src_pool, src_label, cmddata->src_fileno,
src_labels_str,
(unsigned long int) cmddata->start_time,
hostname, diskname, dump_timestamp, cmddata->level,
dst_storage,
(int)cmddata->working_pid, status);
g_free(src_storage);
g_free(src_pool);
g_free(src_label);
g_free(src_labels_str);
g_ptr_array_add(lines, line);
} else if (cmddata->operation == CMD_RESTORE) {
char *src_storage = quote_string(cmddata->src_storage);
char *src_pool = quote_string(cmddata->src_pool);
if (g_str_equal(src_pool, "HOLDING")) {
char *holding_file = quote_string(cmddata->holding_file);
line = g_strdup_printf("%d RESTORE %s %s %s %s %d %lu %s %s %s %d WORKING:%d %s\n",
id, config, src_storage, src_pool, holding_file, 0,
(unsigned long int) cmddata->expire,
hostname, diskname, dump_timestamp, cmddata->level,
(int)cmddata->working_pid, status);
g_free(holding_file);
} else {
char *src_label = quote_string(cmddata->src_label);
line = g_strdup_printf("%d RESTORE %s %s %s %s %d %lu %s %s %s %d WORKING:%d %s\n",
id, config, src_storage, src_pool, src_label, cmddata->src_fileno,
(unsigned long int) cmddata->expire,
hostname, diskname, dump_timestamp, cmddata->level,
(int)cmddata->working_pid, status);
g_free(src_label);
}
g_free(src_storage);
g_free(src_pool);
g_ptr_array_add(lines, line);
}
g_free(config);
g_free(hostname);
g_free(diskname);
g_free(dump_timestamp);
g_free(dst_storage);
g_free(status);
}
// we already have the lock
// remove the lock
void
write_cmdfile(
cmddatas_t *cmddatas)
{
GPtrArray *lines = g_ptr_array_sized_new(100);
char *buffer;
// generate
g_ptr_array_add(lines, g_strdup_printf("VERSION %d\n", cmddatas->version));
g_ptr_array_add(lines, g_strdup_printf("ID %d\n", cmddatas->max_id));
g_hash_table_foreach(cmddatas->cmdfile, &cmdfile_write, lines);
g_ptr_array_add(lines, NULL);
buffer = g_strjoinv(NULL, (gchar **)lines->pdata);
g_ptr_array_free_full(lines);
// write
file_lock_write(cmddatas->lock, buffer, strlen(buffer));
g_free(buffer);
// unlock
file_lock_unlock(cmddatas->lock);
}
int
add_cmd_in_memory(
cmddatas_t *cmddatas,
cmddata_t *cmddata)
{
cmddatas->max_id++;
cmddata->id = cmddatas->max_id;
if (cmddata->operation == CMD_RESTORE && cmddata->working_pid == 0)
cmddata->expire = time(NULL) + EXPIRE_DELAY;
g_hash_table_insert(cmddatas->cmdfile,
GINT_TO_POINTER(cmddata->id), cmddata);
return cmddata->id;
}
cmddatas_t *
add_cmd_in_cmdfile(
cmddatas_t *cmddatas,
cmddata_t *cmddata)
{
cmddatas_t *new_cmddatas;
// take the lock and read
new_cmddatas = read_cmdfile(cmddatas->lock->filename);
// add the cmd
new_cmddatas->max_id++;
cmddata->id = new_cmddatas->max_id;
if (cmddata->operation == CMD_RESTORE && cmddata->working_pid == 0)
cmddata->expire = time(NULL) + EXPIRE_DELAY;
g_hash_table_insert(new_cmddatas->cmdfile,
GINT_TO_POINTER(new_cmddatas->max_id), cmddata);
// write
write_cmdfile(new_cmddatas);
close_cmdfile(cmddatas);
return new_cmddatas;
}
cmddatas_t *
remove_cmd_in_cmdfile(
cmddatas_t *cmddatas,
int id)
{
cmddatas_t *new_cmddatas;
// take the lock and read
new_cmddatas = read_cmdfile(cmddatas->lock->filename);
// remove the cmd id
g_hash_table_remove(new_cmddatas->cmdfile, GINT_TO_POINTER(id));
// write
write_cmdfile(new_cmddatas);
close_cmdfile(cmddatas);
return new_cmddatas;
}
cmddatas_t *
change_cmd_in_cmdfile(
cmddatas_t *cmddatas,
int id,
cmdstatus_t status,
off_t size)
{
cmddatas_t *new_cmddatas;
cmddata_t *cmddata;
// take the lock and read
new_cmddatas = read_cmdfile(cmddatas->lock->filename);
// update status for cmd id in cmddatas and new_cmddatas
cmddata = g_hash_table_lookup(new_cmddatas->cmdfile, GINT_TO_POINTER(id));
cmddata->status = status;
cmddata->size = size;
// write
write_cmdfile(new_cmddatas);
close_cmdfile(cmddatas);
return new_cmddatas;
}
static void
cmdfile_remove_working(
gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data)
{
cmddata_t *cmddata = value;
pid_t pid = *(pid_t *)user_data;
if (cmddata->working_pid == pid) {
//cmddata->status = CMD_DONE;
cmddata->working_pid = 0;
}
}
cmddatas_t *
remove_working_in_cmdfile(
cmddatas_t *cmddatas,
pid_t pid)
{
cmddatas_t *new_cmddatas;
// take the lock and read
new_cmddatas = read_cmdfile(cmddatas->lock->filename);
// remove if same pid
g_hash_table_foreach(new_cmddatas->cmdfile, &cmdfile_remove_working, &pid);
// write
write_cmdfile(new_cmddatas);
close_cmdfile(cmddatas);
return new_cmddatas;
}
typedef struct cmd_holding_s {
char *holding_file;
gboolean found;
} cmd_holding_t;
static void
cmdfile_holding_file(
gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data)
{
cmddata_t *cmddata = value;
cmd_holding_t *cmd_holding = (cmd_holding_t *)user_data;
if (cmddata->operation == CMD_FLUSH &&
g_str_equal(cmddata->holding_file, cmd_holding->holding_file)) {
cmd_holding-> found = TRUE;
}
}
gboolean
holding_in_cmdfile(
cmddatas_t *cmddatas,
char *holding_file)
{
cmd_holding_t cmd_holding = { holding_file, FALSE };
cmddatas_t *new_cmddatas;
g_hash_table_foreach(cmddatas->cmdfile, &cmdfile_holding_file,
&cmd_holding);
new_cmddatas = read_cmdfile(cmddatas->lock->filename);
unlock_cmdfile(new_cmddatas);
g_hash_table_foreach(new_cmddatas->cmdfile, &cmdfile_holding_file,
&cmd_holding);
close_cmdfile(new_cmddatas);
return cmd_holding.found;
}
typedef struct cmdfile_data_s {
char *ids;
char *holding_file;
} cmdfile_data_t;
static void
cmdfile_flush(
gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data)
{
int id = GPOINTER_TO_INT(key);
cmddata_t *cmddata = value;
cmdfile_data_t *data = user_data;
if (cmddata->operation == CMD_FLUSH &&
g_str_equal(data->holding_file, cmddata->holding_file)) {
if (data->ids) {
char *ids = g_strdup_printf("%s,%d;%s", data->ids, id, cmddata->dst_storage);
g_free(data->ids);
data->ids = ids;
} else {
data->ids = g_strdup_printf("%d;%s", id, cmddata->dst_storage);
}
}
cmddata->working_pid = getpid();
}
char *
cmdfile_get_ids_for_holding(
cmddatas_t *cmddatas,
char *holding_file)
{
cmdfile_data_t data;
data.ids = NULL;
data.holding_file = holding_file;
g_hash_table_foreach(cmddatas->cmdfile, &cmdfile_flush, &data);
return g_strdup(data.ids);
}
typedef struct cmdfile_remove_for_restore_label_s {
char *hostname;
char *diskname;
char *timestamp;
char *storage;
char *pool;
char *label;
GSList *ids;
} cmdfile_remove_for_restore_label_t;
static void
cmdfile_remove_restore_label(
gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data)
{
int id = GPOINTER_TO_INT(key);
cmddata_t *cmddata = value;
cmdfile_remove_for_restore_label_t *data = user_data;
if (cmddata->operation == CMD_RESTORE &&
g_str_equal(data->hostname, cmddata->hostname) &&
g_str_equal(data->diskname, cmddata->diskname) &&
g_str_equal(data->timestamp, cmddata->dump_timestamp) &&
g_str_equal(data->storage, cmddata->src_storage) &&
g_str_equal(data->pool, cmddata->src_pool) &&
g_str_equal(data->label, cmddata->src_label)) {
data->ids = g_slist_prepend(data->ids, GINT_TO_POINTER(id));
}
}
void cmdfile_remove_for_restore_label(cmddatas_t *cmddatas, char *hostname,
char *diskname, char *timestamp,
char *storage, char *pool, char *label)
{
cmdfile_remove_for_restore_label_t data;
GSList *ids;
data.hostname = hostname;
data.diskname = diskname;
data.timestamp = timestamp;
data.storage = storage;
data.pool = pool;
data.label = label;
data.ids = NULL;
g_hash_table_foreach(cmddatas->cmdfile, &cmdfile_remove_restore_label, &data);
for (ids = data.ids; ids != NULL; ids = ids->next) {
gpointer id = ids->data;
g_hash_table_remove(cmddatas->cmdfile, id);
}
g_slist_free(data.ids);
}
typedef struct cmdfile_remove_for_restore_holding_s {
char *hostname;
char *diskname;
char *timestamp;
char *storage;
char *pool;
char *holding_file;
GSList *ids;
} cmdfile_remove_for_restore_holding_t;
static void
cmdfile_remove_restore_holding(
gpointer key G_GNUC_UNUSED,
gpointer value,
gpointer user_data)
{
int id = GPOINTER_TO_INT(key);
cmddata_t *cmddata = value;
cmdfile_remove_for_restore_holding_t *data = user_data;
if (cmddata->operation == CMD_RESTORE &&
g_str_equal(data->hostname, cmddata->hostname) &&
g_str_equal(data->diskname, cmddata->diskname) &&
g_str_equal(data->timestamp, cmddata->dump_timestamp) &&
g_str_equal(data->holding_file, cmddata->holding_file)) {
data->ids = g_slist_prepend(data->ids, GINT_TO_POINTER(id));
}
}
void cmdfile_remove_for_restore_holding(cmddatas_t *cmddatas, char *hostname,
char *diskname, char *timestamp,
char *holding_file)
{
cmdfile_remove_for_restore_holding_t data;
GSList *ids;
data.hostname = hostname;
data.diskname = diskname;
data.timestamp = timestamp;
data.holding_file = holding_file;
data.ids = NULL;
g_hash_table_foreach(cmddatas->cmdfile, &cmdfile_remove_restore_holding, &data);
for (ids = data.ids; ids != NULL; ids = ids->next) {
gpointer id = ids->data;
g_hash_table_remove(cmddatas->cmdfile, id);
}
g_slist_free(data.ids);
}