Add Bluetooth-client-notification to test bluetooth pairing
[platform/core/appfw/notification-service.git] / service_common.c
1 /*
2  * Copyright 2013  Samsung Electronics Co., Ltd
3  *
4  * Licensed under the Flora License, Version 1.1 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://floralicense.org/license/
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #define _GNU_SOURCE
17 #include <stdio.h>
18 #include <pthread.h>
19 #include <secure_socket.h>
20 #include <packet.h>
21 #include <errno.h>
22 #include <sys/time.h>
23 #include <sys/types.h>
24 #include <sys/timerfd.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <Eina.h>
28
29 #include "service_common.h"
30
31 #define EVT_CH          'e'
32 #define EVT_END_CH      'x'
33
34 int errno;
35
36 struct service_event_item {
37         enum {
38                 SERVICE_EVENT_TIMER,
39         } type;
40
41         union {
42                 struct {
43                         int fd;
44                 } timer;
45         } info;
46
47         int (*event_cb)(struct service_context *svc_cx, void *data);
48         void *cbdata;
49 };
50
51 /*!
52  * \note
53  * Server information and global (only in this file-scope) variables are defined
54  */
55 struct service_context {
56         pthread_t server_thid; /*!< Server thread Id */
57         int fd; /*!< Server socket handle */
58
59         Eina_List *tcb_list; /*!< TCB list, list of every thread for client connections */
60
61         Eina_List *packet_list;
62         pthread_mutex_t packet_list_lock;
63         int evt_pipe[PIPE_MAX];
64         int tcb_pipe[PIPE_MAX];
65
66         int (*service_thread_main)(struct tcb *tcb, struct packet *packet, void *data);
67         void *service_thread_data;
68
69         Eina_List *event_list;
70 };
71
72 struct packet_info {
73         struct tcb *tcb;
74         struct packet *packet;
75 };
76
77 /*!
78  * \note
79  * Thread Control Block
80  * - The main server will create a thread for every client connections.
81  *   When a new client is comming to us, this TCB block will be allocated and initialized.
82  */
83 struct tcb { /* Thread controll block */
84         struct service_context *svc_ctx;
85         pthread_t thid; /*!< Thread Id */
86         int fd; /*!< Connection handle */
87         enum tcb_type type;
88 };
89
90 /*!
91  * Do services for clients
92  * Routing packets to destination processes.
93  * CLIENT THREAD
94  */
95 static void *client_packet_pump_main(void *data)
96 {
97         struct tcb *tcb = data;
98         struct service_context *svc_ctx = tcb->svc_ctx;
99         struct packet *packet;
100         fd_set set;
101         char *ptr;
102         int size;
103         int packet_offset;
104         int recv_offset;
105         int pid;
106         long ret;
107         char evt_ch = EVT_CH;
108         enum {
109                 RECV_INIT,
110                 RECV_HEADER,
111                 RECV_PAYLOAD,
112                 RECV_DONE,
113         } recv_state;
114         struct packet_info *packet_info;
115         Eina_List *l;
116
117         ret = 0;
118         recv_state = RECV_INIT;
119         /*!
120          * \note
121          * To escape from the switch statement, we use this ret value
122          */
123         while (ret == 0) {
124                 FD_ZERO(&set);
125                 FD_SET(tcb->fd, &set);
126                 ret = select(tcb->fd + 1, &set, NULL, NULL, NULL);
127                 if (ret < 0) {
128                         ret = -errno;
129                         if (errno == EINTR) {
130                                 ret = 0;
131                                 continue;
132                         }
133                         free(ptr);
134                         ptr = NULL;
135                         break;
136                 } else if (ret == 0) {
137                         ret = -ETIMEDOUT;
138                         free(ptr);
139                         ptr = NULL;
140                         break;
141                 }
142
143                 if (!FD_ISSET(tcb->fd, &set)) {
144                         ret = -EINVAL;
145                         free(ptr);
146                         ptr = NULL;
147                         break;
148                 }
149                 
150                 /*!
151                  * \TODO
152                  * Service!!! Receive packet & route packet
153                  */
154                 switch (recv_state) {
155                 case RECV_INIT:
156                         size = packet_header_size();
157                         packet_offset = 0;
158                         recv_offset = 0;
159                         packet = NULL;
160                         ptr = malloc(size);
161                         if (!ptr) {
162                                 ret = -ENOMEM;
163                                 break;
164                         }
165                         recv_state = RECV_HEADER;
166                         /* Go through, don't break from here */
167                 case RECV_HEADER:
168                         ret = secure_socket_recv(tcb->fd, ptr, size - recv_offset, &pid);
169                         if (ret <= 0) {
170                                 if (ret == 0)
171                                         ret = -ECANCELED;
172                                 free(ptr);
173                                 ptr = NULL;
174                                 break;
175                         }
176
177                         recv_offset += ret;
178                         ret = 0;
179
180                         if (recv_offset == size) {
181                                 packet = packet_build(packet, packet_offset, ptr, size);
182                                 free(ptr);
183                                 ptr = NULL;
184                                 if (!packet) {
185                                         ret = -EFAULT;
186                                         break;
187                                 }
188
189                                 packet_offset += recv_offset;
190
191                                 size = packet_payload_size(packet);
192                                 if (size <= 0) {
193                                         recv_state = RECV_DONE;
194                                         recv_offset = 0;
195                                         break;
196                                 }
197
198                                 recv_state = RECV_PAYLOAD;
199                                 recv_offset = 0;
200
201                                 ptr = malloc(size);
202                                 if (!ptr) {
203                                         ret = -ENOMEM;
204                                 }
205                         }
206                         break;
207                 case RECV_PAYLOAD:
208                         ret = secure_socket_recv(tcb->fd, ptr, size - recv_offset, &pid);
209                         if (ret <= 0) {
210                                 if (ret == 0)
211                                         ret = -ECANCELED;
212                                 free(ptr);
213                                 ptr = NULL;
214                                 break;
215                         }
216
217                         recv_offset += ret;
218                         ret = 0;
219
220                         if (recv_offset == size) {
221                                 packet = packet_build(packet, packet_offset, ptr, size);
222                                 free(ptr);
223                                 ptr = NULL;
224                                 if (!packet) {
225                                         ret = -EFAULT;
226                                         break;
227                                 }
228
229                                 packet_offset += recv_offset;
230
231                                 recv_state = RECV_DONE;
232                                 recv_offset = 0;
233                         }
234                         break;
235                 case RECV_DONE:
236                 default:
237                         /* Dead code */
238                         break;
239                 }
240
241                 if (recv_state == RECV_DONE) {
242                         /*!
243                          * Push this packet to the packet list with TCB
244                          * Then the service main function will get this.
245                          */
246                         packet_info = malloc(sizeof(*packet_info));
247                         if (!packet_info) {
248                                 ret = -errno;
249                                 packet_destroy(packet);
250                                 break;
251                         }
252
253                         packet_info->packet = packet;
254                         packet_info->tcb = tcb;
255
256                         CRITICAL_SECTION_BEGIN(&svc_ctx->packet_list_lock);
257                         svc_ctx->packet_list = eina_list_append(svc_ctx->packet_list, packet_info);
258                         CRITICAL_SECTION_END(&svc_ctx->packet_list_lock);
259
260                         if (write(svc_ctx->evt_pipe[PIPE_WRITE], &evt_ch, sizeof(evt_ch)) != sizeof(evt_ch)) {
261                                 ret = -errno;
262                                 CRITICAL_SECTION_BEGIN(&svc_ctx->packet_list_lock);
263                                 svc_ctx->packet_list = eina_list_remove(svc_ctx->packet_list, packet_info);
264                                 CRITICAL_SECTION_END(&svc_ctx->packet_list_lock);
265
266                                 packet_destroy(packet);
267                                 free(packet_info);
268                                 break;
269                         } else {
270                                 recv_state = RECV_INIT;
271                         }
272                 }
273         }
274
275         CRITICAL_SECTION_BEGIN(&svc_ctx->packet_list_lock);
276         EINA_LIST_FOREACH(svc_ctx->packet_list, l, packet_info) {
277                 if (packet_info->tcb == tcb) {
278                         packet_info->tcb = NULL;
279                 }
280         }
281         CRITICAL_SECTION_END(&svc_ctx->packet_list_lock);
282
283         /*!
284          * \note
285          * Emit a signal to collect this TCB from the SERVER THREAD.
286          */
287         write(svc_ctx->tcb_pipe[PIPE_WRITE], &tcb, sizeof(tcb)) != sizeof(tcb);
288
289         return (void *)ret;
290 }
291
292 /*!
293  * \note
294  * SERVER THREAD
295  */
296 static inline struct tcb *tcb_create(struct service_context *svc_ctx, int fd)
297 {
298         struct tcb *tcb;
299         int status;
300
301         tcb = malloc(sizeof(*tcb));
302         if (!tcb) {
303                 return NULL;
304         }
305
306         tcb->fd = fd;
307         tcb->svc_ctx = svc_ctx;
308         tcb->type = TCB_CLIENT_TYPE_APP;
309
310         status = pthread_create(&tcb->thid, NULL, client_packet_pump_main, tcb);
311         if (status != 0) {
312                 free(tcb);
313                 return NULL;
314         }
315
316         svc_ctx->tcb_list = eina_list_append(svc_ctx->tcb_list, tcb);
317         return tcb;
318 }
319
320 /*!
321  * \note
322  * SERVER THREAD
323  */
324 static inline void tcb_teminate_all(struct service_context *svc_ctx)
325 {
326         struct tcb *tcb;
327         void *ret;
328         int status;
329
330         /*!
331          * We don't need to make critical section on here.
332          * If we call this after terminate the server thread first.
333          * Then there is no other thread to access tcb_list.
334          */
335         EINA_LIST_FREE(svc_ctx->tcb_list, tcb) {
336                 /*!
337                  * ASSERT(tcb->fd >= 0);
338                  */
339                 secure_socket_destroy_handle(tcb->fd);
340
341                 status = pthread_join(tcb->thid, &ret);
342
343                 free(tcb);
344         }
345 }
346
347 /*!
348  * \note
349  * SERVER THREAD
350  */
351 static inline void tcb_destroy(struct service_context *svc_ctx, struct tcb *tcb)
352 {
353         void *ret;
354         int status;
355
356         svc_ctx->tcb_list = eina_list_remove(svc_ctx->tcb_list, tcb);
357         /*!
358          * ASSERT(tcb->fd >= 0);
359          * Close the connection, and then collecting the return value of thread
360          */
361         secure_socket_destroy_handle(tcb->fd);
362
363         status = pthread_join(tcb->thid, &ret);
364
365         free(tcb);
366 }
367
368 /*!
369  * \note
370  * SERVER THREAD
371  */
372 static inline int find_max_fd(struct service_context *svc_ctx)
373 {
374         int fd;
375         Eina_List *l;
376         struct service_event_item *item;
377
378         fd = svc_ctx->fd > svc_ctx->tcb_pipe[PIPE_READ] ? svc_ctx->fd : svc_ctx->tcb_pipe[PIPE_READ];
379         fd = fd > svc_ctx->evt_pipe[PIPE_READ] ? fd : svc_ctx->evt_pipe[PIPE_READ];
380
381         EINA_LIST_FOREACH(svc_ctx->event_list, l, item) {
382                 if (item->type == SERVICE_EVENT_TIMER && fd < item->info.timer.fd)
383                         fd = item->info.timer.fd;
384         }
385
386         fd += 1;
387         return fd;
388 }
389
390 /*!
391  * \note
392  * SERVER THREAD
393  */
394 static inline void update_fdset(struct service_context *svc_ctx, fd_set *set)
395 {
396         Eina_List *l;
397         struct service_event_item *item;
398
399         FD_ZERO(set);
400         FD_SET(svc_ctx->fd, set);
401         FD_SET(svc_ctx->tcb_pipe[PIPE_READ], set);
402         FD_SET(svc_ctx->evt_pipe[PIPE_READ], set);
403
404         EINA_LIST_FOREACH(svc_ctx->event_list, l, item) {
405                 if (item->type == SERVICE_EVENT_TIMER)
406                         FD_SET(item->info.timer.fd, set);
407         }
408 }
409
410 /*!
411  * \note
412  * SERVER THREAD
413  */
414 static inline void processing_timer_event(struct service_context *svc_ctx, fd_set *set)
415 {
416         uint64_t expired_count;
417         Eina_List *l;
418         Eina_List *n;
419         struct service_event_item *item;
420
421         EINA_LIST_FOREACH_SAFE(svc_ctx->event_list, l, n, item) {
422                 switch (item->type) {
423                 case SERVICE_EVENT_TIMER:
424                         if (!FD_ISSET(item->info.timer.fd, set))
425                                 break;
426
427                         if (read(item->info.timer.fd, &expired_count, sizeof(expired_count)) == sizeof(expired_count)) {
428                                 if (item->event_cb(svc_ctx, item->cbdata) >= 0)
429                                         break;
430                         }
431
432                         if (!eina_list_data_find(svc_ctx->event_list, item))
433                                 break;
434
435                         svc_ctx->event_list = eina_list_remove(svc_ctx->event_list, item);
436                         close(item->info.timer.fd);
437
438                         free(item);
439                         break;
440                 default:
441                         break;
442                 }
443         }
444 }
445
446 /*!
447  * Accept new client connections
448  * And create a new thread for service.
449  *
450  * Create Client threads & Destroying them
451  * SERVER THREAD
452  */
453 static void *server_main(void *data)
454 {
455         struct service_context *svc_ctx = data;
456         fd_set set;
457         long ret;
458         int client_fd;
459         struct tcb *tcb;
460         int fd;
461         char evt_ch;
462         struct packet_info *packet_info;
463
464         while (1) {
465                 fd = find_max_fd(svc_ctx);
466                 update_fdset(svc_ctx, &set);
467
468                 ret = select(fd, &set, NULL, NULL, NULL);
469                 if (ret < 0) {
470                         ret = -errno;
471                         if (errno == EINTR) {
472                                 continue;
473                         }
474                         break;
475                 } else if (ret == 0) {
476                         ret = -ETIMEDOUT;
477                         break;
478                 }
479
480                 if (FD_ISSET(svc_ctx->fd, &set)) {
481                         client_fd = secure_socket_get_connection_handle(svc_ctx->fd);
482                         if (client_fd < 0) {
483                                 ret = -EFAULT;
484                                 break;
485                         }
486
487                         tcb = tcb_create(svc_ctx, client_fd);
488                         if (!tcb)
489                                 secure_socket_destroy_handle(client_fd);
490                 } 
491
492                 if (FD_ISSET(svc_ctx->tcb_pipe[PIPE_READ], &set)) {
493                         if (read(svc_ctx->tcb_pipe[PIPE_READ], &tcb, sizeof(tcb)) != sizeof(tcb)) {
494                                 ret = -EFAULT;
495                                 break;
496                         }
497
498                         /*!
499                          * \note
500                          * Invoke the service thread main, to notify the termination of a TCB
501                          */
502                         ret = svc_ctx->service_thread_main(tcb, NULL, svc_ctx->service_thread_data);
503
504                         /*!
505                          * at this time, the client thread can access this tcb.
506                          * how can I protect this TCB from deletion without disturbing the server thread?
507                          */
508                         tcb_destroy(svc_ctx, tcb);
509                 } 
510
511                 if (FD_ISSET(svc_ctx->evt_pipe[PIPE_READ], &set)) {
512                         if (read(svc_ctx->evt_pipe[PIPE_READ], &evt_ch, sizeof(evt_ch)) != sizeof(evt_ch)) {
513                                 ret = -EFAULT;
514                                 break;
515                         }
516
517                         CRITICAL_SECTION_BEGIN(&svc_ctx->packet_list_lock);
518                         packet_info = eina_list_nth(svc_ctx->packet_list, 0);
519                         svc_ctx->packet_list = eina_list_remove(svc_ctx->packet_list, packet_info);
520                         CRITICAL_SECTION_END(&svc_ctx->packet_list_lock);
521
522                         /*!
523                          * \CRITICAL
524                          * What happens if the client thread is terminated, so the packet_info->tcb is deleted
525                          * while processing svc_ctx->service_thread_main?
526                          */
527                         ret = svc_ctx->service_thread_main(packet_info->tcb, packet_info->packet, svc_ctx->service_thread_data);
528
529                         packet_destroy(packet_info->packet);
530                         free(packet_info);
531                 }
532
533                 processing_timer_event(svc_ctx, &set);
534                 /* If there is no such triggered FD? */
535         }
536
537         /*!
538          * Consuming all pended packets before terminates server thread.
539          *
540          * If the server thread is terminated, we should flush all pended packets.
541          * And we should services them.
542          * While processing this routine, the mutex is locked.
543          * So every other client thread will be slowed down, sequently, every clients can meet problems.
544          * But in case of termination of server thread, there could be systemetic problem.
545          * This only should be happenes while terminating the master daemon process.
546          */
547         CRITICAL_SECTION_BEGIN(&svc_ctx->packet_list_lock);
548         EINA_LIST_FREE(svc_ctx->packet_list, packet_info) {
549                 ret = read(svc_ctx->evt_pipe[PIPE_READ], &evt_ch, sizeof(evt_ch));
550                 ret = svc_ctx->service_thread_main(packet_info->tcb, packet_info->packet, svc_ctx->service_thread_data);
551                 packet_destroy(packet_info->packet);
552                 free(packet_info);
553         }
554         CRITICAL_SECTION_END(&svc_ctx->packet_list_lock);
555
556         tcb_teminate_all(svc_ctx);
557         return (void *)ret;
558 }
559
560 /*!
561  * \NOTE
562  * MAIN THREAD
563  */
564 struct service_context *service_common_create(const char *addr, int (*service_thread_main)(struct tcb *tcb, struct packet *packet, void *data), void *data)
565 {
566         int status;
567         struct service_context *svc_ctx;
568
569         if (!service_thread_main || !addr) {
570                 return NULL;
571         }
572
573         svc_ctx = calloc(1, sizeof(*svc_ctx));
574         if (!svc_ctx) {
575                 return NULL;
576         }
577
578         svc_ctx->fd = secure_socket_create_server(addr);
579         if (svc_ctx->fd < 0) {
580                 free(svc_ctx);
581                 return NULL;
582         }
583
584         svc_ctx->service_thread_main = service_thread_main;
585         svc_ctx->service_thread_data = data;
586
587         fcntl(svc_ctx->fd, F_SETFD, FD_CLOEXEC);
588
589         fcntl(svc_ctx->fd, F_SETFL, O_NONBLOCK);
590
591         if (pipe2(svc_ctx->evt_pipe, O_NONBLOCK | O_CLOEXEC) < 0) {
592                 secure_socket_destroy_handle(svc_ctx->fd);
593                 free(svc_ctx);
594                 return NULL;
595         }
596
597         if (pipe2(svc_ctx->tcb_pipe, O_NONBLOCK | O_CLOEXEC) < 0) {
598                 CLOSE_PIPE(svc_ctx->evt_pipe);
599                 secure_socket_destroy_handle(svc_ctx->fd);
600                 free(svc_ctx);
601                 return NULL;
602         }
603
604         status = pthread_mutex_init(&svc_ctx->packet_list_lock, NULL);
605         if (status != 0) {
606                 CLOSE_PIPE(svc_ctx->evt_pipe);
607                 CLOSE_PIPE(svc_ctx->tcb_pipe);
608                 secure_socket_destroy_handle(svc_ctx->fd);
609                 free(svc_ctx);
610                 return NULL;
611         }
612
613         status = pthread_create(&svc_ctx->server_thid, NULL, server_main, svc_ctx);
614         if (status != 0) {
615                 status = pthread_mutex_destroy(&svc_ctx->packet_list_lock);
616                 CLOSE_PIPE(svc_ctx->evt_pipe);
617                 CLOSE_PIPE(svc_ctx->tcb_pipe);
618                 secure_socket_destroy_handle(svc_ctx->fd);
619                 free(svc_ctx);
620                 return NULL;
621         }
622
623         return svc_ctx;
624 }
625
626 /*!
627  * \note
628  * MAIN THREAD
629  */
630 int service_common_destroy(struct service_context *svc_ctx)
631 {
632         int status;
633         void *ret;
634
635         if (!svc_ctx)
636                 return -EINVAL;
637
638         /*!
639          * \note
640          * Terminate server thread
641          */
642         secure_socket_destroy_handle(svc_ctx->fd);
643
644         status = pthread_join(svc_ctx->server_thid, &ret);
645
646         status = pthread_mutex_destroy(&svc_ctx->packet_list_lock);
647
648         CLOSE_PIPE(svc_ctx->evt_pipe);
649         CLOSE_PIPE(svc_ctx->tcb_pipe);
650         free(svc_ctx);
651         return 0;
652 }
653
654 /*!
655  * \note
656  * SERVER THREAD
657  */
658 int tcb_fd(struct tcb *tcb)
659 {
660         if (!tcb)
661                 return -EINVAL;
662
663         return tcb->fd;
664 }
665
666 /*!
667  * \note
668  * SERVER THREAD
669  */
670 int tcb_client_type(struct tcb *tcb)
671 {
672         if (!tcb)
673                 return -EINVAL;
674
675         return tcb->type;
676 }
677
678 /*!
679  * \note
680  * SERVER THREAD
681  */
682 int tcb_client_type_set(struct tcb *tcb, enum tcb_type type)
683 {
684         if (!tcb)
685                 return -EINVAL;
686
687         tcb->type = type;
688         return 0;
689 }
690
691 /*!
692  * \note
693  * SERVER THREAD
694  */
695 struct service_context *tcb_svc_ctx(struct tcb *tcb)
696 {
697         if (!tcb)
698                 return NULL;
699
700         return tcb->svc_ctx;
701 }
702
703 /*!
704  * \note
705  * SERVER THREAD
706  */
707 int service_common_unicast_packet(struct tcb *tcb, struct packet *packet)
708 {
709         if (!tcb || !packet)
710                 return -EINVAL;
711
712         return secure_socket_send(tcb->fd, (void *)packet_data(packet), packet_size(packet));
713 }
714
715 /*!
716  * \note
717  * SERVER THREAD
718  */
719 int service_common_multicast_packet(struct tcb *tcb, struct packet *packet, int type)
720 {
721         Eina_List *l;
722         struct tcb *target;
723         struct service_context *svc_ctx;
724         int ret;
725
726         if (!tcb || !packet)
727                 return -EINVAL;
728
729         svc_ctx = tcb->svc_ctx;
730
731         EINA_LIST_FOREACH(svc_ctx->tcb_list, l, target) {
732                 if (target == tcb || target->type != type) {
733                         continue;
734                 }
735
736                 ret = secure_socket_send(target->fd, (void *)packet_data(packet), packet_size(packet));
737         }
738         return 0;
739 }
740
741 /*!
742  * \note
743  * SERVER THREAD
744  */
745 struct service_event_item *service_common_add_timer(struct service_context *svc_ctx, double timer, int (*timer_cb)(struct service_context *svc_cx, void *data), void *data)
746 {
747         struct service_event_item *item;
748         struct itimerspec spec;
749
750         item = calloc(1, sizeof(*item));
751         if (!item) {
752                 return NULL;
753         }
754
755         item->type = SERVICE_EVENT_TIMER;
756         item->info.timer.fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
757         if (item->info.timer.fd < 0) {
758                 free(item);
759                 return NULL;
760         }
761
762         spec.it_interval.tv_sec = (time_t)timer;
763         spec.it_interval.tv_nsec = (timer - spec.it_interval.tv_sec) * 1000000000;
764         spec.it_value.tv_sec = 0;
765         spec.it_value.tv_nsec = 0;
766
767         if (timerfd_settime(item->info.timer.fd, 0, &spec, NULL) < 0) {
768                 close(item->info.timer.fd);
769                 free(item);
770                 return NULL;
771         }
772
773         item->event_cb = timer_cb;
774         item->cbdata = data;
775
776         svc_ctx->event_list = eina_list_append(svc_ctx->event_list, item);
777         return item;
778 }
779
780 /*!
781  * \note
782  * SERVER THREAD
783  */
784 int service_common_del_timer(struct service_context *svc_ctx, struct service_event_item *item)
785 {
786         if (!eina_list_data_find(svc_ctx->event_list, item)) {
787                 return -EINVAL;
788         }
789
790         svc_ctx->event_list = eina_list_remove(svc_ctx->event_list, item);
791
792         close(item->info.timer.fd);
793         free(item);
794         return 0;
795 }
796
797 /* End of a file */