Converted the timer list to use standard list functions.
Allocate a timer id when the window is 0 instead of relying on the client to do it. Allow setting timers on windows belonging to other threads (found by Mike McCormack).
This commit is contained in:
parent
5c2a891c43
commit
ff986a5a93
|
@ -2198,6 +2198,7 @@ struct set_win_timer_request
|
||||||
struct set_win_timer_reply
|
struct set_win_timer_reply
|
||||||
{
|
{
|
||||||
struct reply_header __header;
|
struct reply_header __header;
|
||||||
|
unsigned int id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -3642,6 +3643,6 @@ union generic_reply
|
||||||
struct set_global_windows_reply set_global_windows_reply;
|
struct set_global_windows_reply set_global_windows_reply;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SERVER_PROTOCOL_VERSION 149
|
#define SERVER_PROTOCOL_VERSION 150
|
||||||
|
|
||||||
#endif /* __WINE_WINE_SERVER_PROTOCOL_H */
|
#endif /* __WINE_WINE_SERVER_PROTOCOL_H */
|
||||||
|
|
|
@ -1557,6 +1557,8 @@ enum message_type
|
||||||
unsigned int id; /* timer id */
|
unsigned int id; /* timer id */
|
||||||
unsigned int rate; /* timer rate in ms */
|
unsigned int rate; /* timer rate in ms */
|
||||||
unsigned int lparam; /* message lparam (callback proc) */
|
unsigned int lparam; /* message lparam (callback proc) */
|
||||||
|
@REPLY
|
||||||
|
unsigned int id; /* timer id */
|
||||||
@END
|
@END
|
||||||
|
|
||||||
|
|
||||||
|
|
229
server/queue.c
229
server/queue.c
|
@ -83,8 +83,7 @@ struct message_list
|
||||||
|
|
||||||
struct timer
|
struct timer
|
||||||
{
|
{
|
||||||
struct timer *next; /* next timer in list */
|
struct list entry; /* entry in timer list */
|
||||||
struct timer *prev; /* prev timer in list */
|
|
||||||
struct timeval when; /* next expiration */
|
struct timeval when; /* next expiration */
|
||||||
unsigned int rate; /* timer rate in ms */
|
unsigned int rate; /* timer rate in ms */
|
||||||
user_handle_t win; /* window handle */
|
user_handle_t win; /* window handle */
|
||||||
|
@ -123,9 +122,9 @@ struct msg_queue
|
||||||
struct list send_result; /* stack of sent messages waiting for result */
|
struct list send_result; /* stack of sent messages waiting for result */
|
||||||
struct list callback_result; /* list of callback messages waiting for result */
|
struct list callback_result; /* list of callback messages waiting for result */
|
||||||
struct message_result *recv_result; /* stack of received messages waiting for result */
|
struct message_result *recv_result; /* stack of received messages waiting for result */
|
||||||
struct timer *first_timer; /* head of timer list */
|
struct list pending_timers; /* list of pending timers */
|
||||||
struct timer *last_timer; /* tail of timer list */
|
struct list expired_timers; /* list of expired timers */
|
||||||
struct timer *next_timer; /* next timer to expire */
|
unsigned int next_timer_id; /* id for the next timer with a 0 window */
|
||||||
struct timeout_user *timeout; /* timeout for next timer to expire */
|
struct timeout_user *timeout; /* timeout for next timer to expire */
|
||||||
struct thread_input *input; /* thread input descriptor */
|
struct thread_input *input; /* thread input descriptor */
|
||||||
struct hook_table *hooks; /* hook table */
|
struct hook_table *hooks; /* hook table */
|
||||||
|
@ -235,15 +234,15 @@ static struct msg_queue *create_msg_queue( struct thread *thread, struct thread_
|
||||||
queue->changed_mask = 0;
|
queue->changed_mask = 0;
|
||||||
queue->paint_count = 0;
|
queue->paint_count = 0;
|
||||||
queue->recv_result = NULL;
|
queue->recv_result = NULL;
|
||||||
queue->first_timer = NULL;
|
queue->next_timer_id = 1;
|
||||||
queue->last_timer = NULL;
|
|
||||||
queue->next_timer = NULL;
|
|
||||||
queue->timeout = NULL;
|
queue->timeout = NULL;
|
||||||
queue->input = (struct thread_input *)grab_object( input );
|
queue->input = (struct thread_input *)grab_object( input );
|
||||||
queue->hooks = NULL;
|
queue->hooks = NULL;
|
||||||
gettimeofday( &queue->last_get_msg, NULL );
|
gettimeofday( &queue->last_get_msg, NULL );
|
||||||
list_init( &queue->send_result );
|
list_init( &queue->send_result );
|
||||||
list_init( &queue->callback_result );
|
list_init( &queue->callback_result );
|
||||||
|
list_init( &queue->pending_timers );
|
||||||
|
list_init( &queue->expired_timers );
|
||||||
for (i = 0; i < NB_MSG_KINDS; i++)
|
for (i = 0; i < NB_MSG_KINDS; i++)
|
||||||
queue->msg_list[i].first = queue->msg_list[i].last = NULL;
|
queue->msg_list[i].first = queue->msg_list[i].last = NULL;
|
||||||
|
|
||||||
|
@ -749,17 +748,23 @@ static int msg_queue_satisfied( struct object *obj, struct thread *thread )
|
||||||
static void msg_queue_destroy( struct object *obj )
|
static void msg_queue_destroy( struct object *obj )
|
||||||
{
|
{
|
||||||
struct msg_queue *queue = (struct msg_queue *)obj;
|
struct msg_queue *queue = (struct msg_queue *)obj;
|
||||||
struct timer *timer = queue->first_timer;
|
struct list *ptr;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
cleanup_results( queue );
|
cleanup_results( queue );
|
||||||
for (i = 0; i < NB_MSG_KINDS; i++) empty_msg_list( &queue->msg_list[i] );
|
for (i = 0; i < NB_MSG_KINDS; i++) empty_msg_list( &queue->msg_list[i] );
|
||||||
|
|
||||||
while (timer)
|
while ((ptr = list_head( &queue->pending_timers )))
|
||||||
{
|
{
|
||||||
struct timer *next = timer->next;
|
struct timer *timer = LIST_ENTRY( ptr, struct timer, entry );
|
||||||
|
list_remove( &timer->entry );
|
||||||
|
free( timer );
|
||||||
|
}
|
||||||
|
while ((ptr = list_head( &queue->expired_timers )))
|
||||||
|
{
|
||||||
|
struct timer *timer = LIST_ENTRY( ptr, struct timer, entry );
|
||||||
|
list_remove( &timer->entry );
|
||||||
free( timer );
|
free( timer );
|
||||||
timer = next;
|
|
||||||
}
|
}
|
||||||
if (queue->timeout) remove_timeout_user( queue->timeout );
|
if (queue->timeout) remove_timeout_user( queue->timeout );
|
||||||
if (queue->input) release_object( queue->input );
|
if (queue->input) release_object( queue->input );
|
||||||
|
@ -855,79 +860,93 @@ static void detach_thread_input( struct thread *thread_from, struct thread *thre
|
||||||
|
|
||||||
|
|
||||||
/* set the next timer to expire */
|
/* set the next timer to expire */
|
||||||
static void set_next_timer( struct msg_queue *queue, struct timer *timer )
|
static void set_next_timer( struct msg_queue *queue )
|
||||||
{
|
{
|
||||||
|
struct list *ptr;
|
||||||
|
|
||||||
if (queue->timeout)
|
if (queue->timeout)
|
||||||
{
|
{
|
||||||
remove_timeout_user( queue->timeout );
|
remove_timeout_user( queue->timeout );
|
||||||
queue->timeout = NULL;
|
queue->timeout = NULL;
|
||||||
}
|
}
|
||||||
if ((queue->next_timer = timer))
|
if ((ptr = list_head( &queue->pending_timers )))
|
||||||
|
{
|
||||||
|
struct timer *timer = LIST_ENTRY( ptr, struct timer, entry );
|
||||||
queue->timeout = add_timeout_user( &timer->when, timer_callback, queue );
|
queue->timeout = add_timeout_user( &timer->when, timer_callback, queue );
|
||||||
|
}
|
||||||
/* set/clear QS_TIMER bit */
|
/* set/clear QS_TIMER bit */
|
||||||
if (queue->next_timer == queue->first_timer)
|
if (list_empty( &queue->expired_timers ))
|
||||||
clear_queue_bits( queue, QS_TIMER );
|
clear_queue_bits( queue, QS_TIMER );
|
||||||
else
|
else
|
||||||
set_queue_bits( queue, QS_TIMER );
|
set_queue_bits( queue, QS_TIMER );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* find a timer from its window and id */
|
||||||
|
static struct timer *find_timer( struct msg_queue *queue, user_handle_t win,
|
||||||
|
unsigned int msg, unsigned int id )
|
||||||
|
{
|
||||||
|
struct list *ptr;
|
||||||
|
|
||||||
|
/* we need to search both lists */
|
||||||
|
|
||||||
|
LIST_FOR_EACH( ptr, &queue->pending_timers )
|
||||||
|
{
|
||||||
|
struct timer *timer = LIST_ENTRY( ptr, struct timer, entry );
|
||||||
|
if (timer->win == win && timer->msg == msg && timer->id == id) return timer;
|
||||||
|
}
|
||||||
|
LIST_FOR_EACH( ptr, &queue->expired_timers )
|
||||||
|
{
|
||||||
|
struct timer *timer = LIST_ENTRY( ptr, struct timer, entry );
|
||||||
|
if (timer->win == win && timer->msg == msg && timer->id == id) return timer;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* callback for the next timer expiration */
|
/* callback for the next timer expiration */
|
||||||
static void timer_callback( void *private )
|
static void timer_callback( void *private )
|
||||||
{
|
{
|
||||||
struct msg_queue *queue = private;
|
struct msg_queue *queue = private;
|
||||||
|
struct list *ptr;
|
||||||
|
|
||||||
queue->timeout = NULL;
|
queue->timeout = NULL;
|
||||||
/* move on to the next timer */
|
/* move on to the next timer */
|
||||||
set_next_timer( queue, queue->next_timer->next );
|
ptr = list_head( &queue->pending_timers );
|
||||||
|
list_remove( ptr );
|
||||||
|
list_add_tail( &queue->expired_timers, ptr );
|
||||||
|
set_next_timer( queue );
|
||||||
}
|
}
|
||||||
|
|
||||||
/* link a timer at its rightful place in the queue list */
|
/* link a timer at its rightful place in the queue list */
|
||||||
static void link_timer( struct msg_queue *queue, struct timer *timer )
|
static void link_timer( struct msg_queue *queue, struct timer *timer )
|
||||||
{
|
{
|
||||||
struct timer *pos = queue->next_timer;
|
struct list *ptr;
|
||||||
|
|
||||||
while (pos && time_before( &pos->when, &timer->when )) pos = pos->next;
|
for (ptr = queue->pending_timers.next; ptr != &queue->pending_timers; ptr = ptr->next)
|
||||||
|
|
||||||
if (pos) /* insert before pos */
|
|
||||||
{
|
{
|
||||||
if ((timer->prev = pos->prev)) timer->prev->next = timer;
|
struct timer *t = LIST_ENTRY( ptr, struct timer, entry );
|
||||||
else queue->first_timer = timer;
|
if (!time_before( &t->when, &timer->when )) break;
|
||||||
timer->next = pos;
|
|
||||||
pos->prev = timer;
|
|
||||||
}
|
}
|
||||||
else /* insert at end */
|
list_add_before( ptr, &timer->entry );
|
||||||
{
|
|
||||||
timer->next = NULL;
|
|
||||||
timer->prev = queue->last_timer;
|
|
||||||
if (queue->last_timer) queue->last_timer->next = timer;
|
|
||||||
else queue->first_timer = timer;
|
|
||||||
queue->last_timer = timer;
|
|
||||||
}
|
|
||||||
/* check if we replaced the next timer */
|
|
||||||
if (pos == queue->next_timer) set_next_timer( queue, timer );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* remove a timer from the queue timer list */
|
/* remove a timer from the queue timer list and free it */
|
||||||
static void unlink_timer( struct msg_queue *queue, struct timer *timer )
|
static void free_timer( struct msg_queue *queue, struct timer *timer )
|
||||||
{
|
{
|
||||||
if (timer->next) timer->next->prev = timer->prev;
|
list_remove( &timer->entry );
|
||||||
else queue->last_timer = timer->prev;
|
free( timer );
|
||||||
if (timer->prev) timer->prev->next = timer->next;
|
set_next_timer( queue );
|
||||||
else queue->first_timer = timer->next;
|
|
||||||
/* check if we removed the next timer */
|
|
||||||
if (queue->next_timer == timer) set_next_timer( queue, timer->next );
|
|
||||||
else if (queue->next_timer == queue->first_timer) clear_queue_bits( queue, QS_TIMER );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* restart an expired timer */
|
/* restart an expired timer */
|
||||||
static void restart_timer( struct msg_queue *queue, struct timer *timer )
|
static void restart_timer( struct msg_queue *queue, struct timer *timer )
|
||||||
{
|
{
|
||||||
struct timeval now;
|
struct timeval now;
|
||||||
unlink_timer( queue, timer );
|
|
||||||
|
list_remove( &timer->entry );
|
||||||
gettimeofday( &now, 0 );
|
gettimeofday( &now, 0 );
|
||||||
while (!time_before( &now, &timer->when )) add_timeout( &timer->when, timer->rate );
|
while (!time_before( &now, &timer->when )) add_timeout( &timer->when, timer->rate );
|
||||||
link_timer( queue, timer );
|
link_timer( queue, timer );
|
||||||
|
set_next_timer( queue );
|
||||||
}
|
}
|
||||||
|
|
||||||
/* find an expired timer matching the filtering parameters */
|
/* find an expired timer matching the filtering parameters */
|
||||||
|
@ -935,9 +954,11 @@ static struct timer *find_expired_timer( struct msg_queue *queue, user_handle_t
|
||||||
unsigned int get_first, unsigned int get_last,
|
unsigned int get_first, unsigned int get_last,
|
||||||
int remove )
|
int remove )
|
||||||
{
|
{
|
||||||
struct timer *timer;
|
struct list *ptr;
|
||||||
for (timer = queue->first_timer; (timer && timer != queue->next_timer); timer = timer->next)
|
|
||||||
|
LIST_FOR_EACH( ptr, &queue->expired_timers )
|
||||||
{
|
{
|
||||||
|
struct timer *timer = LIST_ENTRY( ptr, struct timer, entry );
|
||||||
if (win && timer->win != win) continue;
|
if (win && timer->win != win) continue;
|
||||||
if (timer->msg >= get_first && timer->msg <= get_last)
|
if (timer->msg >= get_first && timer->msg <= get_last)
|
||||||
{
|
{
|
||||||
|
@ -948,32 +969,18 @@ static struct timer *find_expired_timer( struct msg_queue *queue, user_handle_t
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* kill a timer */
|
|
||||||
static int kill_timer( struct msg_queue *queue, user_handle_t win,
|
|
||||||
unsigned int msg, unsigned int id )
|
|
||||||
{
|
|
||||||
struct timer *timer;
|
|
||||||
|
|
||||||
for (timer = queue->first_timer; timer; timer = timer->next)
|
|
||||||
{
|
|
||||||
if (timer->win != win || timer->msg != msg || timer->id != id) continue;
|
|
||||||
unlink_timer( queue, timer );
|
|
||||||
free( timer );
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* add a timer */
|
/* add a timer */
|
||||||
static struct timer *set_timer( struct msg_queue *queue, unsigned int rate )
|
static struct timer *set_timer( struct msg_queue *queue, unsigned int rate )
|
||||||
{
|
{
|
||||||
struct timer *timer = mem_alloc( sizeof(*timer) );
|
struct timer *timer = mem_alloc( sizeof(*timer) );
|
||||||
if (timer)
|
if (timer)
|
||||||
{
|
{
|
||||||
timer->rate = rate;
|
timer->rate = max( rate, 1 );
|
||||||
gettimeofday( &timer->when, 0 );
|
gettimeofday( &timer->when, 0 );
|
||||||
add_timeout( &timer->when, rate );
|
add_timeout( &timer->when, rate );
|
||||||
link_timer( queue, timer );
|
link_timer( queue, timer );
|
||||||
|
/* check if we replaced the next timer */
|
||||||
|
if (list_head( &queue->pending_timers ) == &timer->entry) set_next_timer( queue );
|
||||||
}
|
}
|
||||||
return timer;
|
return timer;
|
||||||
}
|
}
|
||||||
|
@ -1219,23 +1226,29 @@ void inc_queue_paint_count( struct thread *thread, int incr )
|
||||||
void queue_cleanup_window( struct thread *thread, user_handle_t win )
|
void queue_cleanup_window( struct thread *thread, user_handle_t win )
|
||||||
{
|
{
|
||||||
struct msg_queue *queue = thread->queue;
|
struct msg_queue *queue = thread->queue;
|
||||||
struct timer *timer;
|
struct list *ptr;
|
||||||
struct message *msg;
|
struct message *msg;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (!queue) return;
|
if (!queue) return;
|
||||||
|
|
||||||
/* remove timers */
|
/* remove timers */
|
||||||
timer = queue->first_timer;
|
|
||||||
while (timer)
|
ptr = list_head( &queue->pending_timers );
|
||||||
|
while (ptr)
|
||||||
{
|
{
|
||||||
struct timer *next = timer->next;
|
struct list *next = list_next( &queue->pending_timers, ptr );
|
||||||
if (timer->win == win)
|
struct timer *timer = LIST_ENTRY( ptr, struct timer, entry );
|
||||||
{
|
if (timer->win == win) free_timer( queue, timer );
|
||||||
unlink_timer( queue, timer );
|
ptr = next;
|
||||||
free( timer );
|
}
|
||||||
}
|
ptr = list_head( &queue->expired_timers );
|
||||||
timer = next;
|
while (ptr)
|
||||||
|
{
|
||||||
|
struct list *next = list_next( &queue->expired_timers, ptr );
|
||||||
|
struct timer *timer = LIST_ENTRY( ptr, struct timer, entry );
|
||||||
|
if (timer->win == win) free_timer( queue, timer );
|
||||||
|
ptr = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* remove messages */
|
/* remove messages */
|
||||||
|
@ -1569,30 +1582,80 @@ DECL_HANDLER(get_message_reply)
|
||||||
DECL_HANDLER(set_win_timer)
|
DECL_HANDLER(set_win_timer)
|
||||||
{
|
{
|
||||||
struct timer *timer;
|
struct timer *timer;
|
||||||
struct msg_queue *queue = get_current_queue();
|
struct msg_queue *queue;
|
||||||
user_handle_t win = get_user_full_handle( req->win );
|
struct thread *thread = NULL;
|
||||||
|
user_handle_t win = 0;
|
||||||
|
unsigned int id = req->id;
|
||||||
|
|
||||||
if (!queue) return;
|
if (req->win)
|
||||||
|
{
|
||||||
/* remove it if it existed already */
|
if (!(win = get_user_full_handle( req->win )) || !(thread = get_window_thread( win )))
|
||||||
if (win) kill_timer( queue, win, req->msg, req->id );
|
{
|
||||||
|
set_error( STATUS_INVALID_HANDLE );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (thread->process != current->process)
|
||||||
|
{
|
||||||
|
release_object( thread );
|
||||||
|
set_error( STATUS_ACCESS_DENIED );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
queue = thread->queue;
|
||||||
|
/* remove it if it existed already */
|
||||||
|
if ((timer = find_timer( queue, win, req->msg, id ))) free_timer( queue, timer );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
queue = get_current_queue();
|
||||||
|
/* find a free id for it */
|
||||||
|
do
|
||||||
|
{
|
||||||
|
id = queue->next_timer_id;
|
||||||
|
if (++queue->next_timer_id >= 0x10000) queue->next_timer_id = 1;
|
||||||
|
}
|
||||||
|
while (find_timer( queue, 0, req->msg, id ));
|
||||||
|
}
|
||||||
|
|
||||||
if ((timer = set_timer( queue, req->rate )))
|
if ((timer = set_timer( queue, req->rate )))
|
||||||
{
|
{
|
||||||
timer->win = win;
|
timer->win = win;
|
||||||
timer->msg = req->msg;
|
timer->msg = req->msg;
|
||||||
timer->id = req->id;
|
timer->id = id;
|
||||||
timer->lparam = req->lparam;
|
timer->lparam = req->lparam;
|
||||||
|
reply->id = id;
|
||||||
}
|
}
|
||||||
|
if (thread) release_object( thread );
|
||||||
}
|
}
|
||||||
|
|
||||||
/* kill a window timer */
|
/* kill a window timer */
|
||||||
DECL_HANDLER(kill_win_timer)
|
DECL_HANDLER(kill_win_timer)
|
||||||
{
|
{
|
||||||
struct msg_queue *queue = current->queue;
|
struct timer *timer;
|
||||||
|
struct thread *thread;
|
||||||
|
user_handle_t win = 0;
|
||||||
|
|
||||||
if (!queue || !kill_timer( queue, get_user_full_handle(req->win), req->msg, req->id ))
|
if (req->win)
|
||||||
|
{
|
||||||
|
if (!(win = get_user_full_handle( req->win )) || !(thread = get_window_thread( win )))
|
||||||
|
{
|
||||||
|
set_error( STATUS_INVALID_HANDLE );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (thread->process != current->process)
|
||||||
|
{
|
||||||
|
release_object( thread );
|
||||||
|
set_error( STATUS_ACCESS_DENIED );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else thread = (struct thread *)grab_object( current );
|
||||||
|
|
||||||
|
if (thread->queue && (timer = find_timer( thread->queue, win, req->msg, req->id )))
|
||||||
|
free_timer( thread->queue, timer );
|
||||||
|
else
|
||||||
set_error( STATUS_INVALID_PARAMETER );
|
set_error( STATUS_INVALID_PARAMETER );
|
||||||
|
|
||||||
|
release_object( thread );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1881,6 +1881,11 @@ static void dump_set_win_timer_request( const struct set_win_timer_request *req
|
||||||
fprintf( stderr, " lparam=%08x", req->lparam );
|
fprintf( stderr, " lparam=%08x", req->lparam );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void dump_set_win_timer_reply( const struct set_win_timer_reply *req )
|
||||||
|
{
|
||||||
|
fprintf( stderr, " id=%08x", req->id );
|
||||||
|
}
|
||||||
|
|
||||||
static void dump_kill_win_timer_request( const struct kill_win_timer_request *req )
|
static void dump_kill_win_timer_request( const struct kill_win_timer_request *req )
|
||||||
{
|
{
|
||||||
fprintf( stderr, " win=%p,", req->win );
|
fprintf( stderr, " win=%p,", req->win );
|
||||||
|
@ -2851,7 +2856,7 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] = {
|
||||||
(dump_func)dump_get_message_reply,
|
(dump_func)dump_get_message_reply,
|
||||||
(dump_func)0,
|
(dump_func)0,
|
||||||
(dump_func)dump_get_message_reply_reply,
|
(dump_func)dump_get_message_reply_reply,
|
||||||
(dump_func)0,
|
(dump_func)dump_set_win_timer_reply,
|
||||||
(dump_func)0,
|
(dump_func)0,
|
||||||
(dump_func)dump_get_serial_info_reply,
|
(dump_func)dump_get_serial_info_reply,
|
||||||
(dump_func)0,
|
(dump_func)0,
|
||||||
|
|
Loading…
Reference in New Issue