]> jfr.im git - irc/rqf/shadowircd.git/blob - libcharybdis/commio.c
[svn] - the new plan:
[irc/rqf/shadowircd.git] / libcharybdis / commio.c
1 /*
2 * ircd-ratbox: A slightly useful ircd.
3 * commio.c: Network/file related functions
4 *
5 * Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center
6 * Copyright (C) 1996-2002 Hybrid Development Team
7 * Copyright (C) 2002-2005 ircd-ratbox development team
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
22 * USA
23 *
24 * $Id: commio.c 1779 2006-07-30 16:36:39Z jilles $
25 */
26
27 #include "libcharybdis.h"
28
29 #ifndef IN_LOOPBACKNET
30 #define IN_LOOPBACKNET 0x7f
31 #endif
32
33 #ifndef INADDR_NONE
34 #define INADDR_NONE ((unsigned int) 0xffffffff)
35 #endif
36
37 const char *const NONB_ERROR_MSG = "set_non_blocking failed for %s:%s";
38 const char *const SETBUF_ERROR_MSG = "set_sock_buffers failed for server %s:%s";
39
40 static const char *comm_err_str[] = { "Comm OK", "Error during bind()",
41 "Error during DNS lookup", "connect timeout",
42 "Error during connect()",
43 "Comm Error"
44 };
45
46 fde_t *fd_table = NULL;
47
48 static void fdlist_update_biggest(int fd, int opening);
49
50 /* Highest FD and number of open FDs .. */
51 int highest_fd = -1; /* Its -1 because we haven't started yet -- adrian */
52 int number_fd = 0;
53
54 static void comm_connect_callback(int fd, int status);
55 static PF comm_connect_timeout;
56 static void comm_connect_dns_callback(void *vptr, struct DNSReply *reply);
57 static PF comm_connect_tryconnect;
58
59 /* 32bit solaris is kinda slow and stdio only supports fds < 256
60 * so we got to do this crap below.
61 * (BTW Fuck you Sun, I hate your guts and I hope you go bankrupt soon)
62 */
63 #if defined (__SVR4) && defined (__sun)
64 static void comm_fd_hack(int *fd)
65 {
66 int newfd;
67 if(*fd > 256 || *fd < 0)
68 return;
69 if((newfd = fcntl(*fd, F_DUPFD, 256)) != -1)
70 {
71 close(*fd);
72 *fd = newfd;
73 }
74 return;
75 }
76 #else
77 #define comm_fd_hack(fd)
78 #endif
79
80
81 /* close_all_connections() can be used *before* the system come up! */
82
83 void
84 comm_close_all(void)
85 {
86 int i;
87 #ifndef NDEBUG
88 int fd;
89 #endif
90
91 /* XXX someone tell me why we care about 4 fd's ? */
92 /* XXX btw, fd 3 is used for profiler ! */
93
94 for (i = 4; i < MAXCONNECTIONS; ++i)
95 {
96 if(fd_table[i].flags.open)
97 comm_close(i);
98 else
99 close(i);
100 }
101
102 /* XXX should his hack be done in all cases? */
103 #ifndef NDEBUG
104 /* fugly hack to reserve fd == 2 */
105 (void) close(2);
106 fd = open("stderr.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
107 if(fd >= 0)
108 {
109 dup2(fd, 2);
110 close(fd);
111 }
112 #endif
113 }
114
115 /*
116 * get_sockerr - get the error value from the socket or the current errno
117 *
118 * Get the *real* error from the socket (well try to anyway..).
119 * This may only work when SO_DEBUG is enabled but its worth the
120 * gamble anyway.
121 */
122 int
123 comm_get_sockerr(int fd)
124 {
125 int errtmp = errno;
126 #ifdef SO_ERROR
127 int err = 0;
128 socklen_t len = sizeof(err);
129
130 if(-1 < fd && !getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *) &err, (socklen_t *) & len))
131 {
132 if(err)
133 errtmp = err;
134 }
135 errno = errtmp;
136 #endif
137 return errtmp;
138 }
139
140 /*
141 * set_sock_buffers - set send and receive buffers for socket
142 *
143 * inputs - fd file descriptor
144 * - size to set
145 * output - returns true (1) if successful, false (0) otherwise
146 * side effects -
147 */
148 int
149 comm_set_buffers(int fd, int size)
150 {
151 if(setsockopt
152 (fd, SOL_SOCKET, SO_RCVBUF, (char *) &size, sizeof(size))
153 || setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *) &size, sizeof(size)))
154 return 0;
155 return 1;
156 }
157
158 /*
159 * set_non_blocking - Set the client connection into non-blocking mode.
160 *
161 * inputs - fd to set into non blocking mode
162 * output - 1 if successful 0 if not
163 * side effects - use POSIX compliant non blocking and
164 * be done with it.
165 */
166 int
167 comm_set_nb(int fd)
168 {
169 int nonb = 0;
170 int res;
171
172 nonb |= O_NONBLOCK;
173 res = fcntl(fd, F_GETFL, 0);
174 if(-1 == res || fcntl(fd, F_SETFL, res | nonb) == -1)
175 return 0;
176
177 fd_table[fd].flags.nonblocking = 1;
178 return 1;
179 }
180
181
182 /*
183 * stolen from squid - its a neat (but overused! :) routine which we
184 * can use to see whether we can ignore this errno or not. It is
185 * generally useful for non-blocking network IO related errnos.
186 * -- adrian
187 */
188 int
189 ignoreErrno(int ierrno)
190 {
191 switch (ierrno)
192 {
193 case EINPROGRESS:
194 case EWOULDBLOCK:
195 #if EAGAIN != EWOULDBLOCK
196 case EAGAIN:
197 #endif
198 case EALREADY:
199 case EINTR:
200 #ifdef ERESTART
201 case ERESTART:
202 #endif
203 return 1;
204 default:
205 return 0;
206 }
207 }
208
209
210 /*
211 * comm_settimeout() - set the socket timeout
212 *
213 * Set the timeout for the fd
214 */
215 void
216 comm_settimeout(int fd, time_t timeout, PF * callback, void *cbdata)
217 {
218 fde_t *F;
219 s_assert(fd >= 0);
220 F = &fd_table[fd];
221 s_assert(F->flags.open);
222
223 F->timeout = CurrentTime + (timeout / 1000);
224 F->timeout_handler = callback;
225 F->timeout_data = cbdata;
226 }
227
228
229 /*
230 * comm_setflush() - set a flush function
231 *
232 * A flush function is simply a function called if found during
233 * comm_timeouts(). Its basically a second timeout, except in this case
234 * I'm too lazy to implement multiple timeout functions! :-)
235 * its kinda nice to have it seperate, since this is designed for
236 * flush functions, and when comm_close() is implemented correctly
237 * with close functions, we _actually_ don't call comm_close() here ..
238 */
239 void
240 comm_setflush(int fd, time_t timeout, PF * callback, void *cbdata)
241 {
242 fde_t *F;
243 s_assert(fd >= 0);
244 F = &fd_table[fd];
245 s_assert(F->flags.open);
246
247 F->flush_timeout = CurrentTime + (timeout / 1000);
248 F->flush_handler = callback;
249 F->flush_data = cbdata;
250 }
251
252
253 /*
254 * comm_checktimeouts() - check the socket timeouts
255 *
256 * All this routine does is call the given callback/cbdata, without closing
257 * down the file descriptor. When close handlers have been implemented,
258 * this will happen.
259 */
260 void
261 comm_checktimeouts(void *notused)
262 {
263 int fd;
264 PF *hdl;
265 void *data;
266 fde_t *F;
267 for (fd = 0; fd <= highest_fd; fd++)
268 {
269 F = &fd_table[fd];
270 if(!F->flags.open)
271 continue;
272 if(F->flags.closing)
273 continue;
274
275 /* check flush functions */
276 if(F->flush_handler &&
277 F->flush_timeout > 0 && F->flush_timeout < CurrentTime)
278 {
279 hdl = F->flush_handler;
280 data = F->flush_data;
281 comm_setflush(F->fd, 0, NULL, NULL);
282 hdl(F->fd, data);
283 }
284
285 /* check timeouts */
286 if(F->timeout_handler &&
287 F->timeout > 0 && F->timeout < CurrentTime)
288 {
289 /* Call timeout handler */
290 hdl = F->timeout_handler;
291 data = F->timeout_data;
292 comm_settimeout(F->fd, 0, NULL, NULL);
293 hdl(F->fd, data);
294 }
295 }
296 }
297
298 /*
299 * void comm_connect_tcp(int fd, const char *host, u_short port,
300 * struct sockaddr *clocal, int socklen,
301 * CNCB *callback, void *data, int aftype, int timeout)
302 * Input: An fd to connect with, a host and port to connect to,
303 * a local sockaddr to connect from + length(or NULL to use the
304 * default), a callback, the data to pass into the callback, the
305 * address family.
306 * Output: None.
307 * Side-effects: A non-blocking connection to the host is started, and
308 * if necessary, set up for selection. The callback given
309 * may be called now, or it may be called later.
310 */
311 void
312 comm_connect_tcp(int fd, const char *host, u_short port,
313 struct sockaddr *clocal, int socklen, CNCB * callback,
314 void *data, int aftype, int timeout)
315 {
316 void *ipptr = NULL;
317 fde_t *F;
318 s_assert(fd >= 0);
319 F = &fd_table[fd];
320 F->flags.called_connect = 1;
321 s_assert(callback);
322 F->connect.callback = callback;
323 F->connect.data = data;
324
325 memset(&F->connect.hostaddr, 0, sizeof(F->connect.hostaddr));
326 #ifdef IPV6
327 if(aftype == AF_INET6)
328 {
329 struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&F->connect.hostaddr;
330 SET_SS_LEN(F->connect.hostaddr, sizeof(struct sockaddr_in6));
331 in6->sin6_port = htons(port);
332 in6->sin6_family = AF_INET6;
333 ipptr = &in6->sin6_addr;
334 } else
335 #endif
336 {
337 struct sockaddr_in *in = (struct sockaddr_in *)&F->connect.hostaddr;
338 SET_SS_LEN(F->connect.hostaddr, sizeof(struct sockaddr_in));
339 in->sin_port = htons(port);
340 in->sin_family = AF_INET;
341 ipptr = &in->sin_addr;
342 }
343
344 /* Note that we're using a passed sockaddr here. This is because
345 * generally you'll be bind()ing to a sockaddr grabbed from
346 * getsockname(), so this makes things easier.
347 * XXX If NULL is passed as local, we should later on bind() to the
348 * virtual host IP, for completeness.
349 * -- adrian
350 */
351 if((clocal != NULL) && (bind(F->fd, clocal, socklen) < 0))
352 {
353 /* Failure, call the callback with COMM_ERR_BIND */
354 comm_connect_callback(F->fd, COMM_ERR_BIND);
355 /* ... and quit */
356 return;
357 }
358
359 /* Next, if we have been given an IP, get the addr and skip the
360 * DNS check (and head direct to comm_connect_tryconnect().
361 */
362 if(inetpton(aftype, host, ipptr) <= 0)
363 {
364 /* Send the DNS request, for the next level */
365 F->dns_query = MyMalloc(sizeof(struct DNSQuery));
366 F->dns_query->ptr = F;
367 F->dns_query->callback = comm_connect_dns_callback;
368 #ifdef IPV6
369 if (aftype == AF_INET6)
370 gethost_byname_type(host, F->dns_query, T_AAAA);
371 else
372 #endif
373 gethost_byname_type(host, F->dns_query, T_A);
374 }
375 else
376 {
377 /* We have a valid IP, so we just call tryconnect */
378 /* Make sure we actually set the timeout here .. */
379 comm_settimeout(F->fd, timeout * 1000, comm_connect_timeout, NULL);
380 comm_connect_tryconnect(F->fd, NULL);
381 }
382 }
383
384 /*
385 * comm_connect_callback() - call the callback, and continue with life
386 */
387 static void
388 comm_connect_callback(int fd, int status)
389 {
390 CNCB *hdl;
391 fde_t *F = &fd_table[fd];
392 /* This check is gross..but probably necessary */
393 if(F->connect.callback == NULL)
394 return;
395 /* Clear the connect flag + handler */
396 hdl = F->connect.callback;
397 F->connect.callback = NULL;
398 F->flags.called_connect = 0;
399
400 /* Clear the timeout handler */
401 comm_settimeout(F->fd, 0, NULL, NULL);
402
403 /* Call the handler */
404 hdl(F->fd, status, F->connect.data);
405 }
406
407
408 /*
409 * comm_connect_timeout() - this gets called when the socket connection
410 * times out. This *only* can be called once connect() is initially
411 * called ..
412 */
413 static void
414 comm_connect_timeout(int fd, void *notused)
415 {
416 /* error! */
417 comm_connect_callback(fd, COMM_ERR_TIMEOUT);
418 }
419
420
421 /*
422 * comm_connect_dns_callback() - called at the completion of the DNS request
423 *
424 * The DNS request has completed, so if we've got an error, return it,
425 * otherwise we initiate the connect()
426 */
427 static void
428 comm_connect_dns_callback(void *vptr, struct DNSReply *reply)
429 {
430 fde_t *F = vptr;
431
432 /* Free dns_query now to avoid double reslist free -- jilles */
433 MyFree(F->dns_query);
434 F->dns_query = NULL;
435
436 if(!reply)
437 {
438 comm_connect_callback(F->fd, COMM_ERR_DNS);
439 return;
440 }
441
442 /* No error, set a 10 second timeout */
443 comm_settimeout(F->fd, 30 * 1000, comm_connect_timeout, NULL);
444
445 /* Copy over the DNS reply info so we can use it in the connect() */
446 #ifdef IPV6
447 if(reply->addr.ss_family == AF_INET6)
448 {
449 struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&F->connect.hostaddr;
450 memcpy(&in6->sin6_addr, &((struct sockaddr_in6 *)&reply->addr)->sin6_addr, sizeof(struct in6_addr));
451 }
452 else
453 #endif
454 {
455 struct sockaddr_in *in = (struct sockaddr_in *)&F->connect.hostaddr;
456 in->sin_addr.s_addr = ((struct sockaddr_in *)&reply->addr)->sin_addr.s_addr;
457 }
458
459 /* Now, call the tryconnect() routine to try a connect() */
460 comm_connect_tryconnect(F->fd, NULL);
461 }
462
463
464 /* static void comm_connect_tryconnect(int fd, void *notused)
465 * Input: The fd, the handler data(unused).
466 * Output: None.
467 * Side-effects: Try and connect with pending connect data for the FD. If
468 * we succeed or get a fatal error, call the callback.
469 * Otherwise, it is still blocking or something, so register
470 * to select for a write event on this FD.
471 */
472 static void
473 comm_connect_tryconnect(int fd, void *notused)
474 {
475 int retval;
476 fde_t *F = &fd_table[fd];
477
478 if(F->connect.callback == NULL)
479 return;
480 /* Try the connect() */
481 retval = connect(fd, (struct sockaddr *) &fd_table[fd].connect.hostaddr,
482 GET_SS_LEN(fd_table[fd].connect.hostaddr));
483 /* Error? */
484 if(retval < 0)
485 {
486 /*
487 * If we get EISCONN, then we've already connect()ed the socket,
488 * which is a good thing.
489 * -- adrian
490 */
491 if(errno == EISCONN)
492 comm_connect_callback(F->fd, COMM_OK);
493 else if(ignoreErrno(errno))
494 /* Ignore error? Reschedule */
495 comm_setselect(F->fd, FDLIST_SERVER, COMM_SELECT_WRITE|COMM_SELECT_RETRY,
496 comm_connect_tryconnect, NULL, 0);
497 else
498 /* Error? Fail with COMM_ERR_CONNECT */
499 comm_connect_callback(F->fd, COMM_ERR_CONNECT);
500 return;
501 }
502 /* If we get here, we've suceeded, so call with COMM_OK */
503 comm_connect_callback(F->fd, COMM_OK);
504 }
505
506 /*
507 * comm_error_str() - return an error string for the given error condition
508 */
509 const char *
510 comm_errstr(int error)
511 {
512 if(error < 0 || error >= COMM_ERR_MAX)
513 return "Invalid error number!";
514 return comm_err_str[error];
515 }
516
517
518 /*
519 * comm_socket() - open a socket
520 *
521 * This is a highly highly cut down version of squid's comm_open() which
522 * for the most part emulates socket(), *EXCEPT* it fails if we're about
523 * to run out of file descriptors.
524 */
525 int
526 comm_socket(int family, int sock_type, int proto, const char *note)
527 {
528 int fd;
529 /* First, make sure we aren't going to run out of file descriptors */
530 if(number_fd >= MASTER_MAX)
531 {
532 errno = ENFILE;
533 return -1;
534 }
535
536 /*
537 * Next, we try to open the socket. We *should* drop the reserved FD
538 * limit if/when we get an error, but we can deal with that later.
539 * XXX !!! -- adrian
540 */
541 fd = socket(family, sock_type, proto);
542 comm_fd_hack(&fd);
543 if(fd < 0)
544 return -1; /* errno will be passed through, yay.. */
545
546 #if defined(IPV6) && defined(IPV6_V6ONLY)
547 /*
548 * Make sure we can take both IPv4 and IPv6 connections
549 * on an AF_INET6 socket
550 */
551 if(family == AF_INET6)
552 {
553 int off = 1;
554 if(setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)) == -1)
555 {
556 libcharybdis_log("comm_socket: Could not set IPV6_V6ONLY option to 1 on FD %d: %s",
557 fd, strerror(errno));
558 close(fd);
559 return -1;
560 }
561 }
562 #endif
563
564 /* Set the socket non-blocking, and other wonderful bits */
565 if(!comm_set_nb(fd))
566 {
567 libcharybdis_log("comm_open: Couldn't set FD %d non blocking: %s", fd, strerror(errno));
568 close(fd);
569 return -1;
570 }
571
572 /* Next, update things in our fd tracking */
573 comm_open(fd, FD_SOCKET, note);
574 return fd;
575 }
576
577
578 /*
579 * comm_accept() - accept an incoming connection
580 *
581 * This is a simple wrapper for accept() which enforces FD limits like
582 * comm_open() does.
583 */
584 int
585 comm_accept(int fd, struct sockaddr *pn, socklen_t *addrlen)
586 {
587 int newfd;
588 if(number_fd >= MASTER_MAX)
589 {
590 errno = ENFILE;
591 return -1;
592 }
593
594 /*
595 * Next, do the accept(). if we get an error, we should drop the
596 * reserved fd limit, but we can deal with that when comm_open()
597 * also does it. XXX -- adrian
598 */
599 newfd = accept(fd, (struct sockaddr *) pn, addrlen);
600 comm_fd_hack(&newfd);
601
602 if(newfd < 0)
603 return -1;
604
605 /* Set the socket non-blocking, and other wonderful bits */
606 if(!comm_set_nb(newfd))
607 {
608 libcharybdis_log("comm_accept: Couldn't set FD %d non blocking!", newfd);
609 close(newfd);
610 return -1;
611 }
612
613 /* Next, tag the FD as an incoming connection */
614 comm_open(newfd, FD_SOCKET, "Incoming connection");
615
616 /* .. and return */
617 return newfd;
618 }
619
620 /*
621 * If a sockaddr_storage is AF_INET6 but is a mapped IPv4
622 * socket manged the sockaddr.
623 */
624 #ifndef mangle_mapped_sockaddr
625 void
626 mangle_mapped_sockaddr(struct sockaddr *in)
627 {
628 struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)in;
629
630 if(in->sa_family == AF_INET)
631 return;
632
633 if(in->sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&in6->sin6_addr))
634 {
635 struct sockaddr_in in4;
636 memset(&in4, 0, sizeof(struct sockaddr_in));
637 in4.sin_family = AF_INET;
638 in4.sin_port = in6->sin6_port;
639 in4.sin_addr.s_addr = ((uint32_t *)&in6->sin6_addr)[3];
640 memcpy(in, &in4, sizeof(struct sockaddr_in));
641 }
642 return;
643 }
644 #endif
645
646
647 static void
648 fdlist_update_biggest(int fd, int opening)
649 {
650 if(fd < highest_fd)
651 return;
652 s_assert(fd < MAXCONNECTIONS);
653
654 if(fd > highest_fd)
655 {
656 /*
657 * s_assert that we are not closing a FD bigger than
658 * our known biggest FD
659 */
660 s_assert(opening);
661 highest_fd = fd;
662 return;
663 }
664 /* if we are here, then fd == Biggest_FD */
665 /*
666 * s_assert that we are closing the biggest FD; we can't be
667 * re-opening it
668 */
669 s_assert(!opening);
670 while (highest_fd >= 0 && !fd_table[highest_fd].flags.open)
671 highest_fd--;
672 }
673
674
675 void
676 fdlist_init(void)
677 {
678 static int initialized = 0;
679
680 if(!initialized)
681 {
682 /* Since we're doing this once .. */
683 fd_table = MyMalloc((MAXCONNECTIONS + 1) * sizeof(fde_t));
684 initialized = 1;
685 }
686 }
687
688 /* Called to open a given filedescriptor */
689 void
690 comm_open(int fd, unsigned int type, const char *desc)
691 {
692 fde_t *F = &fd_table[fd];
693 s_assert(fd >= 0);
694
695 if(F->flags.open)
696 {
697 comm_close(fd);
698 }
699 s_assert(!F->flags.open);
700 F->fd = fd;
701 F->type = type;
702 F->flags.open = 1;
703 #ifdef NOTYET
704 F->defer.until = 0;
705 F->defer.n = 0;
706 F->defer.handler = NULL;
707 #endif
708 fdlist_update_biggest(fd, 1);
709 F->comm_index = -1;
710 F->list = FDLIST_NONE;
711 if(desc)
712 strlcpy(F->desc, desc, sizeof(F->desc));
713 number_fd++;
714 }
715
716
717 /* Called to close a given filedescriptor */
718 void
719 comm_close(int fd)
720 {
721 fde_t *F = &fd_table[fd];
722 s_assert(F->flags.open);
723 /* All disk fd's MUST go through file_close() ! */
724 s_assert(F->type != FD_FILE);
725 if(F->type == FD_FILE)
726 {
727 s_assert(F->read_handler == NULL);
728 s_assert(F->write_handler == NULL);
729 }
730 comm_setselect(F->fd, FDLIST_NONE, COMM_SELECT_WRITE | COMM_SELECT_READ, NULL, NULL, 0);
731 comm_setflush(F->fd, 0, NULL, NULL);
732
733 if (F->dns_query != NULL)
734 {
735 delete_resolver_queries(F->dns_query);
736 MyFree(F->dns_query);
737 F->dns_query = NULL;
738 }
739
740 F->flags.open = 0;
741 fdlist_update_biggest(fd, 0);
742 number_fd--;
743 memset(F, '\0', sizeof(fde_t));
744 F->timeout = 0;
745 /* Unlike squid, we're actually closing the FD here! -- adrian */
746 close(fd);
747 }
748
749
750 /*
751 * comm_dump() - dump the list of active filedescriptors
752 */
753 void
754 comm_dump(struct Client *source_p)
755 {
756 int i;
757
758 for (i = 0; i <= highest_fd; i++)
759 {
760 if(!fd_table[i].flags.open)
761 continue;
762
763 sendto_one_numeric(source_p, RPL_STATSDEBUG,
764 "F :fd %-3d desc '%s'",
765 i, fd_table[i].desc);
766 }
767 }
768
769 /*
770 * comm_note() - set the fd note
771 *
772 * Note: must be careful not to overflow fd_table[fd].desc when
773 * calling.
774 */
775 void
776 comm_note(int fd, const char *format, ...)
777 {
778 va_list args;
779
780 if(format)
781 {
782 va_start(args, format);
783 ircvsnprintf(fd_table[fd].desc, FD_DESC_SZ, format, args);
784 va_end(args);
785 }
786 else
787 fd_table[fd].desc[0] = '\0';
788 }
789
790