Merge Irssi 0.8.16-rc1
[silc.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 along
17     with this program; if not, write to the Free Software Foundation, Inc.,
18     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 static int g_io_channel_write_block(GIOChannel *channel, void *data, int len)
39 {
40         gsize ret;
41         int sent;
42         GIOStatus status;
43
44         sent = 0;
45         do {
46                 status = g_io_channel_write_chars(channel, (char *) data + sent,
47                                                   len-sent, &ret, NULL);
48                 sent += ret;
49         } while (sent < len && status != G_IO_STATUS_ERROR);
50
51         return sent < len ? -1 : 0;
52 }
53
54 static int g_io_channel_read_block(GIOChannel *channel, void *data, int len)
55 {
56         time_t maxwait;
57         gsize ret;
58         int received;
59         GIOStatus status;
60
61         maxwait = time(NULL)+2;
62         received = 0;
63         do {
64                 status = g_io_channel_read_chars(channel, (char *) data + received,
65                                                  len-received, &ret, NULL);
66                 received += ret;
67         } while (received < len && time(NULL) < maxwait &&
68                  status != G_IO_STATUS_ERROR && status != G_IO_STATUS_EOF);
69
70         return received < len ? -1 : 0;
71 }
72
73 /* nonblocking gethostbyname(), ip (IPADDR) + error (int, 0 = not error) is
74    written to pipe when found PID of the resolver child is returned */
75 int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe,
76                                int reverse_lookup)
77 {
78         RESOLVED_IP_REC rec;
79         const char *errorstr;
80 #ifndef WIN32
81         int pid;
82 #endif
83         int len;
84
85         g_return_val_if_fail(addr != NULL, FALSE);
86
87 #ifndef WIN32
88         pid = fork();
89         if (pid > 0) {
90                 /* parent */
91                 pidwait_add(pid);
92                 return pid;
93         }
94
95         if (pid != 0) {
96                 /* failed! */
97                 g_warning("net_connect_thread(): fork() failed! "
98                           "Using blocking resolving");
99         }
100 #endif
101
102         /* child */
103         srand(time(NULL));
104
105         memset(&rec, 0, sizeof(rec));
106         rec.error = net_gethostbyname(addr, &rec.ip4, &rec.ip6);
107         if (rec.error == 0) {
108                 errorstr = NULL;
109                 if (reverse_lookup) {
110                         /* reverse lookup the IP, ignore any error */
111                         if (rec.ip4.family != 0)
112                                 net_gethostbyaddr(&rec.ip4, &rec.host4);
113                         if (rec.ip6.family != 0)
114                                 net_gethostbyaddr(&rec.ip6, &rec.host6);
115                 }
116         } else {
117                 errorstr = net_gethosterror(rec.error);
118                 rec.errlen = errorstr == NULL ? 0 : strlen(errorstr)+1;
119         }
120
121         g_io_channel_write_block(pipe, &rec, sizeof(rec));
122         if (rec.errlen != 0)
123                 g_io_channel_write_block(pipe, (void *) errorstr, rec.errlen);
124         else {
125                 if (rec.host4) {
126                         len = strlen(rec.host4) + 1;
127                         g_io_channel_write_block(pipe, (void *) &len,
128                                                        sizeof(int));
129                         g_io_channel_write_block(pipe, (void *) rec.host4,
130                                                        len);
131                 }
132                 if (rec.host6) {
133                         len = strlen(rec.host6) + 1;
134                         g_io_channel_write_block(pipe, (void *) &len,
135                                                        sizeof(int));
136                         g_io_channel_write_block(pipe, (void *) rec.host6,
137                                                        len);
138                 }
139         }
140
141 #ifndef WIN32
142         if (pid == 0)
143                 _exit(99);
144 #endif
145
146         /* we used blocking lookup */
147         return 0;
148 }
149
150 /* get the resolved IP address */
151 int net_gethostbyname_return(GIOChannel *pipe, RESOLVED_IP_REC *rec)
152 {
153         int len;
154
155         rec->error = -1;
156         rec->errorstr = NULL;
157         rec->host4 = NULL;
158         rec->host6 = NULL;
159
160 #ifndef WIN32
161         fcntl(g_io_channel_unix_get_fd(pipe), F_SETFL, O_NONBLOCK);
162 #endif
163
164         /* get ip+error */
165         if (g_io_channel_read_block(pipe, rec, sizeof(*rec)) == -1) {
166                 rec->errorstr = g_strdup_printf("Host name lookup: %s",
167                                                 g_strerror(errno));
168                 return -1;
169         }
170
171         if (rec->error) {
172                 /* read error string, if we can't read everything for some
173                    reason, just ignore it. */
174                 rec->errorstr = g_malloc0(rec->errlen+1);
175                 g_io_channel_read_block(pipe, rec->errorstr, rec->errlen);
176         } else {
177                 if (rec->host4) {
178                         g_io_channel_read_block(pipe, &len, sizeof(int));
179                         rec->host4 = g_malloc0(len);
180                         g_io_channel_read_block(pipe, rec->host4, len);
181                 }
182                 if (rec->host6) {
183                         g_io_channel_read_block(pipe, &len, sizeof(int));
184                         rec->host6 = g_malloc0(len);
185                         g_io_channel_read_block(pipe, rec->host6, len);
186                 }
187         }
188
189         return 0;
190 }
191
192 /* Get host name, call func when finished */
193 int net_gethostbyaddr_nonblock(IPADDR *ip, NET_HOST_CALLBACK func, void *data)
194 {
195         /* FIXME: not implemented */
196         return FALSE;
197 }
198
199 /* Kill the resolver child */
200 void net_disconnect_nonblock(int pid)
201 {
202         g_return_if_fail(pid > 0);
203
204 #ifndef WIN32
205         kill(pid, SIGKILL);
206 #endif
207 }
208
209 static void simple_init(SIMPLE_THREAD_REC *rec, GIOChannel *handle)
210 {
211         g_return_if_fail(rec != NULL);
212
213         g_source_remove(rec->tag);
214
215         if (net_geterror(handle) != 0) {
216                 /* failed */
217                 g_io_channel_close(handle);
218                 g_io_channel_unref(handle);
219                 handle = NULL;
220         }
221
222         rec->func(handle, rec->data);
223         g_free(rec);
224 }
225
226 static void simple_readpipe(SIMPLE_THREAD_REC *rec, GIOChannel *pipe)
227 {
228         RESOLVED_IP_REC iprec;
229         GIOChannel *handle;
230         IPADDR *ip;
231
232         g_return_if_fail(rec != NULL);
233
234         g_source_remove(rec->tag);
235
236         net_gethostbyname_return(pipe, &iprec);
237         g_free_not_null(iprec.errorstr);
238
239         g_io_channel_close(rec->pipes[0]);
240         g_io_channel_unref(rec->pipes[0]);
241         g_io_channel_close(rec->pipes[1]);
242         g_io_channel_unref(rec->pipes[1]);
243
244         ip = iprec.ip4.family != 0 ? &iprec.ip4 : &iprec.ip6;
245         handle = iprec.error == -1 ? NULL :
246                 net_connect_ip(ip, rec->port, rec->my_ip);
247
248         g_free_not_null(rec->my_ip);
249
250         if (handle == NULL) {
251                 /* failed */
252                 rec->func(NULL, rec->data);
253                 g_free(rec);
254                 return;
255         }
256
257         rec->tag = g_input_add(handle, G_INPUT_READ | G_INPUT_WRITE,
258                                (GInputFunction) simple_init, rec);
259 }
260
261 /* Connect to server, call func when finished */
262 int net_connect_nonblock(const char *server, int port, const IPADDR *my_ip,
263                          NET_CALLBACK func, void *data)
264 {
265         SIMPLE_THREAD_REC *rec;
266         int fd[2];
267
268         g_return_val_if_fail(server != NULL, FALSE);
269         g_return_val_if_fail(func != NULL, FALSE);
270
271         if (pipe(fd) != 0) {
272                 g_warning("net_connect_nonblock(): pipe() failed.");
273                 return FALSE;
274         }
275
276         rec = g_new0(SIMPLE_THREAD_REC, 1);
277         rec->port = port;
278         if (my_ip != NULL) {
279                 rec->my_ip = g_malloc(sizeof(IPADDR));
280                 memcpy(rec->my_ip, my_ip, sizeof(IPADDR));
281         }
282         rec->func = func;
283         rec->data = data;
284         rec->pipes[0] = g_io_channel_new(fd[0]);
285         rec->pipes[1] = g_io_channel_new(fd[1]);
286
287         /* start nonblocking host name lookup */
288         net_gethostbyname_nonblock(server, rec->pipes[1], 0);
289         rec->tag = g_input_add(rec->pipes[0], G_INPUT_READ,
290                                (GInputFunction) simple_readpipe, rec);
291
292         return TRUE;
293 }