Added SILC Thread Queue API
[runtime.git] / apps / irssi / src / core / net-nonblock.c
1 /*
2  net-nonblock.c : Nonblocking net_connect()
3
4     Copyright (C) 1998-2000 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22
23 #include <signal.h>
24
25 #include "pidwait.h"
26 #include "net-nonblock.h"
27
28 typedef struct {
29         NET_CALLBACK func;
30         void *data;
31
32         GIOChannel *pipes[2];
33         int port;
34         IPADDR *my_ip;
35         int tag;
36 } SIMPLE_THREAD_REC;
37
38 #define is_fatal_error(err) \
39         (err != 0 && err != G_IO_ERROR_AGAIN && errno != EINTR)
40
41 static int g_io_channel_write_block(GIOChannel *channel, void *data, int len)
42 {
43         gsize ret;
44         int err, sent;
45
46         sent = 0;
47         do {
48                 err = g_io_channel_write(channel, (char *) data + sent,
49                                          len-sent, &ret);
50                 sent += ret;
51         } while (sent < len && !is_fatal_error(err));
52
53         return err != 0 ? -1 : 0;
54 }
55
56 static int g_io_channel_read_block(GIOChannel *channel, void *data, int len)
57 {
58         time_t maxwait;
59         gsize ret;
60         int err, received;
61
62         maxwait = time(NULL)+2;
63         received = 0;
64         do {
65                 err = g_io_channel_read(channel, (char *) data + received,
66                                         len-received, &ret);
67                 received += ret;
68         } while (received < len && time(NULL) < maxwait &&
69                  (ret != 0 || !is_fatal_error(err)));
70
71         return received < len ? -1 : 0;
72 }
73
74 /* nonblocking gethostbyname(), ip (IPADDR) + error (int, 0 = not error) is
75    written to pipe when found PID of the resolver child is returned */
76 int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe,
77                                int reverse_lookup)
78 {
79         RESOLVED_IP_REC rec;
80         const char *errorstr;
81 #ifndef WIN32
82         int pid;
83 #endif
84         int len;
85
86         g_return_val_if_fail(addr != NULL, FALSE);
87
88 #ifndef WIN32
89         pid = fork();
90         if (pid > 0) {
91                 /* parent */
92                 pidwait_add(pid);
93                 return pid;
94         }
95
96         if (pid != 0) {
97                 /* failed! */
98                 g_warning("net_connect_thread(): fork() failed! "
99                           "Using blocking resolving");
100         }
101 #endif
102
103         /* child */
104         srand(time(NULL));
105
106         memset(&rec, 0, sizeof(rec));
107         rec.error = net_gethostbyname(addr, &rec.ip4, &rec.ip6);
108         if (rec.error == 0) {
109                 errorstr = NULL;
110                 if (reverse_lookup) {
111                         /* reverse lookup the IP, ignore any error */
112                         if (rec.ip4.family != 0)
113                                 net_gethostbyaddr(&rec.ip4, &rec.host4);
114                         if (rec.ip6.family != 0)
115                                 net_gethostbyaddr(&rec.ip6, &rec.host6);
116                 }
117         } else {
118                 errorstr = net_gethosterror(rec.error);
119                 rec.errlen = errorstr == NULL ? 0 : strlen(errorstr)+1;
120         }
121
122         g_io_channel_write_block(pipe, &rec, sizeof(rec));
123         if (rec.errlen != 0)
124                 g_io_channel_write_block(pipe, (void *) errorstr, rec.errlen);
125         else {
126                 if (rec.host4) {
127                         len = strlen(rec.host4) + 1;
128                         g_io_channel_write_block(pipe, (void *) &len, 
129                                                        sizeof(int));
130                         g_io_channel_write_block(pipe, (void *) rec.host4,
131                                                        len);
132                 }
133                 if (rec.host6) {
134                         len = strlen(rec.host6) + 1;
135                         g_io_channel_write_block(pipe, (void *) &len,
136                                                        sizeof(int));
137                         g_io_channel_write_block(pipe, (void *) rec.host6,
138                                                        len);
139                 }
140         }
141
142 #ifndef WIN32
143         if (pid == 0)
144                 _exit(99);
145 #endif
146
147         /* we used blocking lookup */
148         return 0;
149 }
150
151 /* get the resolved IP address */
152 int net_gethostbyname_return(GIOChannel *pipe, RESOLVED_IP_REC *rec)
153 {
154         int len;
155
156         rec->error = -1;
157         rec->errorstr = NULL;
158         rec->host4 = NULL;
159         rec->host6 = NULL;
160
161 #ifndef WIN32
162         fcntl(g_io_channel_unix_get_fd(pipe), F_SETFL, O_NONBLOCK);
163 #endif
164
165         /* get ip+error */
166         if (g_io_channel_read_block(pipe, rec, sizeof(*rec)) == -1) {
167                 rec->errorstr = g_strdup_printf("Host name lookup: %s",
168                                                 g_strerror(errno));
169                 return -1;
170         }
171
172         if (rec->error) {
173                 /* read error string, if we can't read everything for some
174                    reason, just ignore it. */
175                 rec->errorstr = g_malloc0(rec->errlen+1);
176                 g_io_channel_read_block(pipe, rec->errorstr, rec->errlen);
177         } else {
178                 if (rec->host4) {
179                         g_io_channel_read_block(pipe, &len, sizeof(int));
180                         rec->host4 = g_malloc0(len);
181                         g_io_channel_read_block(pipe, rec->host4, len);
182                 }
183                 if (rec->host6) {
184                         g_io_channel_read_block(pipe, &len, sizeof(int));
185                         rec->host6 = g_malloc0(len);
186                         g_io_channel_read_block(pipe, rec->host6, len);
187                 }
188         }
189
190         return 0;
191 }
192
193 /* Get host name, call func when finished */
194 int net_gethostbyaddr_nonblock(IPADDR *ip, NET_HOST_CALLBACK func, void *data)
195 {
196         /* FIXME: not implemented */
197         return FALSE;
198 }
199
200 /* Kill the resolver child */
201 void net_disconnect_nonblock(int pid)
202 {
203         g_return_if_fail(pid > 0);
204
205 #ifndef WIN32
206         kill(pid, SIGKILL);
207 #endif
208 }
209
210 static void simple_init(SIMPLE_THREAD_REC *rec, GIOChannel *handle)
211 {
212         g_return_if_fail(rec != NULL);
213
214         g_source_remove(rec->tag);
215
216         if (net_geterror(handle) != 0) {
217                 /* failed */
218                 g_io_channel_close(handle);
219                 g_io_channel_unref(handle);
220                 handle = NULL;
221         }
222
223         rec->func(handle, rec->data);
224         g_free(rec);
225 }
226
227 static void simple_readpipe(SIMPLE_THREAD_REC *rec, GIOChannel *pipe)
228 {
229         RESOLVED_IP_REC iprec;
230         GIOChannel *handle;
231         IPADDR *ip;
232
233         g_return_if_fail(rec != NULL);
234
235         g_source_remove(rec->tag);
236
237         net_gethostbyname_return(pipe, &iprec);
238         g_free_not_null(iprec.errorstr);
239
240         g_io_channel_close(rec->pipes[0]);
241         g_io_channel_unref(rec->pipes[0]);
242         g_io_channel_close(rec->pipes[1]);
243         g_io_channel_unref(rec->pipes[1]);
244
245         ip = iprec.ip4.family != 0 ? &iprec.ip4 : &iprec.ip6;
246         handle = iprec.error == -1 ? NULL :
247                 net_connect_ip(ip, rec->port, rec->my_ip);
248
249         g_free_not_null(rec->my_ip);
250
251         if (handle == NULL) {
252                 /* failed */
253                 rec->func(NULL, rec->data);
254                 g_free(rec);
255                 return;
256         }
257
258         rec->tag = g_input_add(handle, G_INPUT_READ | G_INPUT_WRITE,
259                                (GInputFunction) simple_init, rec);
260 }
261
262 /* Connect to server, call func when finished */
263 int net_connect_nonblock(const char *server, int port, const IPADDR *my_ip,
264                          NET_CALLBACK func, void *data)
265 {
266         SIMPLE_THREAD_REC *rec;
267         int fd[2];
268
269         g_return_val_if_fail(server != NULL, FALSE);
270         g_return_val_if_fail(func != NULL, FALSE);
271
272         if (pipe(fd) != 0) {
273                 g_warning("net_connect_nonblock(): pipe() failed.");
274                 return FALSE;
275         }
276
277         rec = g_new0(SIMPLE_THREAD_REC, 1);
278         rec->port = port;
279         if (my_ip != NULL) {
280                 rec->my_ip = g_malloc(sizeof(IPADDR));
281                 memcpy(rec->my_ip, my_ip, sizeof(IPADDR));
282         }
283         rec->func = func;
284         rec->data = data;
285         rec->pipes[0] = g_io_channel_unix_new(fd[0]);
286         rec->pipes[1] = g_io_channel_unix_new(fd[1]);
287
288         /* start nonblocking host name lookup */
289         net_gethostbyname_nonblock(server, rec->pipes[1], 0);
290         rec->tag = g_input_add(rec->pipes[0], G_INPUT_READ,
291                                (GInputFunction) simple_readpipe, rec);
292
293         return TRUE;
294 }