00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 #include "setup.h"
00025
00026 #include <string.h>
00027 #include <errno.h>
00028
00029 #ifdef NEED_MALLOC_H
00030 #include <malloc.h>
00031 #endif
00032 #ifdef HAVE_SYS_SOCKET_H
00033 #include <sys/socket.h>
00034 #endif
00035 #ifdef HAVE_NETINET_IN_H
00036 #include <netinet/in.h>
00037 #endif
00038 #ifdef HAVE_NETDB_H
00039 #include <netdb.h>
00040 #endif
00041 #ifdef HAVE_ARPA_INET_H
00042 #include <arpa/inet.h>
00043 #endif
00044 #ifdef HAVE_STDLIB_H
00045 #include <stdlib.h>
00046 #endif
00047 #ifdef HAVE_UNISTD_H
00048 #include <unistd.h>
00049 #endif
00050 #ifdef VMS
00051 #include <in.h>
00052 #include <inet.h>
00053 #include <stdlib.h>
00054 #endif
00055
00056 #ifdef HAVE_SETJMP_H
00057 #include <setjmp.h>
00058 #endif
00059
00060 #ifdef HAVE_PROCESS_H
00061 #include <process.h>
00062 #endif
00063
00064 #if (defined(NETWARE) && defined(__NOVELL_LIBC__))
00065 #undef in_addr_t
00066 #define in_addr_t unsigned long
00067 #endif
00068
00069 #include "urldata.h"
00070 #include "sendf.h"
00071 #include "hostip.h"
00072 #include "hash.h"
00073 #include "share.h"
00074 #include "strerror.h"
00075 #include "url.h"
00076 #include "multiif.h"
00077
00078 #define _MPRINTF_REPLACE
00079 #include <curl/mprintf.h>
00080
00081 #include "inet_ntop.h"
00082
00083 #include "memory.h"
00084
00085 #include "memdebug.h"
00086
00087 #if defined(_MSC_VER) && defined(CURL_NO__BEGINTHREADEX)
00088 #pragma message ("No _beginthreadex() available in this RTL")
00089 #endif
00090
00091
00092
00093
00094 #ifdef CURLRES_THREADED
00095
00096
00097 static bool init_resolve_thread(struct connectdata *conn,
00098 const char *hostname, int port,
00099 const Curl_addrinfo *hints);
00100
00101 #ifdef CURLRES_IPV4
00102 #define THREAD_FUNC gethostbyname_thread
00103 #define THREAD_NAME "gethostbyname_thread"
00104 #else
00105 #define THREAD_FUNC getaddrinfo_thread
00106 #define THREAD_NAME "getaddrinfo_thread"
00107 #endif
00108
00109 #if defined(DEBUG_THREADING_GETHOSTBYNAME) || \
00110 defined(DEBUG_THREADING_GETADDRINFO)
00111
00112 #define TRACE(args) \
00113 do { trace_it("%u: ", __LINE__); trace_it args; } while (0)
00114
00115 static void trace_it (const char *fmt, ...)
00116 {
00117 static int do_trace = -1;
00118 va_list args;
00119
00120 if (do_trace == -1) {
00121 const char *env = getenv("CURL_TRACE");
00122 do_trace = (env && atoi(env) > 0);
00123 }
00124 if (!do_trace)
00125 return;
00126 va_start (args, fmt);
00127 vfprintf (stderr, fmt, args);
00128 fflush (stderr);
00129 va_end (args);
00130 }
00131 #else
00132 #define TRACE(x)
00133 #endif
00134
00135 #ifdef DEBUG_THREADING_GETADDRINFO
00136 static void dump_addrinfo (struct connectdata *conn, const struct addrinfo *ai)
00137 {
00138 TRACE(("dump_addrinfo:\n"));
00139 for ( ; ai; ai = ai->ai_next) {
00140 char buf [INET6_ADDRSTRLEN];
00141
00142 trace_it(" fam %2d, CNAME %s, ",
00143 ai->ai_family, ai->ai_canonname ? ai->ai_canonname : "<none>");
00144 if (Curl_printable_address(ai, buf, sizeof(buf)))
00145 trace_it("%s\n", buf);
00146 else
00147 trace_it("failed; %s\n", Curl_strerror(conn, SOCKERRNO));
00148 }
00149 }
00150 #endif
00151
00152 struct thread_data {
00153 HANDLE thread_hnd;
00154 unsigned thread_id;
00155 DWORD thread_status;
00156 curl_socket_t dummy_sock;
00157 HANDLE mutex_waiting;
00158 HANDLE event_resolved;
00159 HANDLE event_thread_started;
00160
00161 HANDLE mutex_terminate;
00162 HANDLE event_terminate;
00163
00164 #ifdef CURLRES_IPV6
00165 struct addrinfo hints;
00166 #endif
00167 };
00168
00169
00170 struct thread_sync_data {
00171 HANDLE mutex_waiting;
00172 HANDLE mutex_terminate;
00173 HANDLE event_terminate;
00174 char * hostname;
00175
00176 };
00177
00178
00179 static
00180 void destroy_thread_sync_data(struct thread_sync_data * tsd)
00181 {
00182 if (tsd->hostname)
00183 free(tsd->hostname);
00184 if (tsd->event_terminate)
00185 CloseHandle(tsd->event_terminate);
00186 if (tsd->mutex_terminate)
00187 CloseHandle(tsd->mutex_terminate);
00188 if (tsd->mutex_waiting)
00189 CloseHandle(tsd->mutex_waiting);
00190 memset(tsd,0,sizeof(*tsd));
00191 }
00192
00193
00194 static
00195 BOOL init_thread_sync_data(struct thread_data * td,
00196 const char * hostname,
00197 struct thread_sync_data * tsd)
00198 {
00199 HANDLE curr_proc = GetCurrentProcess();
00200
00201 memset(tsd, 0, sizeof(*tsd));
00202 if (!DuplicateHandle(curr_proc, td->mutex_waiting,
00203 curr_proc, &tsd->mutex_waiting, 0, FALSE,
00204 DUPLICATE_SAME_ACCESS)) {
00205
00206 destroy_thread_sync_data(tsd);
00207 return FALSE;
00208 }
00209 if (!DuplicateHandle(curr_proc, td->mutex_terminate,
00210 curr_proc, &tsd->mutex_terminate, 0, FALSE,
00211 DUPLICATE_SAME_ACCESS)) {
00212
00213 destroy_thread_sync_data(tsd);
00214 return FALSE;
00215 }
00216 if (!DuplicateHandle(curr_proc, td->event_terminate,
00217 curr_proc, &tsd->event_terminate, 0, FALSE,
00218 DUPLICATE_SAME_ACCESS)) {
00219
00220 destroy_thread_sync_data(tsd);
00221 return FALSE;
00222 }
00223
00224
00225
00226 tsd->hostname = strdup(hostname);
00227 if (!tsd->hostname) {
00228
00229 destroy_thread_sync_data(tsd);
00230 return FALSE;
00231 }
00232 return TRUE;
00233 }
00234
00235
00236 static
00237 BOOL acquire_thread_sync(struct thread_sync_data * tsd)
00238 {
00239
00240 if (WaitForSingleObject(tsd->mutex_waiting, 0) == WAIT_TIMEOUT) {
00241
00242
00243
00244 if (WaitForSingleObject(tsd->mutex_terminate, INFINITE) != WAIT_OBJECT_0) {
00245
00246 }
00247 else {
00248 if (WaitForSingleObject(tsd->event_terminate, 0) != WAIT_TIMEOUT) {
00249
00250
00251
00252
00253 }
00254 else {
00255 return TRUE;
00256 }
00257 }
00258 }
00259 return FALSE;
00260 }
00261
00262
00263 static
00264 void release_thread_sync(struct thread_sync_data * tsd)
00265 {
00266 ReleaseMutex(tsd->mutex_terminate);
00267 }
00268
00269 #if defined(CURLRES_IPV4)
00270
00271
00272
00273
00274
00275
00276
00277 static unsigned __stdcall gethostbyname_thread (void *arg)
00278 {
00279 struct connectdata *conn = (struct connectdata*) arg;
00280 struct thread_data *td = (struct thread_data*) conn->async.os_specific;
00281 struct hostent *he;
00282 int rc = 0;
00283
00284
00285
00286
00287
00288 struct thread_sync_data tsd = { 0,0,0,NULL };
00289
00290 if (!init_thread_sync_data(td, conn->async.hostname, &tsd)) {
00291
00292 return (unsigned)-1;
00293 }
00294
00295 conn->async.status = NO_DATA;
00296 SET_SOCKERRNO(conn->async.status);
00297
00298
00299
00300 SetEvent(td->event_thread_started);
00301
00302 he = gethostbyname (tsd.hostname);
00303
00304
00305 if (acquire_thread_sync(&tsd)) {
00306
00307
00308 SetEvent(td->event_resolved);
00309 if (he) {
00310 rc = Curl_addrinfo4_callback(conn, CURL_ASYNC_SUCCESS, he);
00311 }
00312 else {
00313 rc = Curl_addrinfo4_callback(conn, SOCKERRNO, NULL);
00314 }
00315 TRACE(("Winsock-error %d, addr %s\n", conn->async.status,
00316 he ? inet_ntoa(*(struct in_addr*)he->h_addr) : "unknown"));
00317 release_thread_sync(&tsd);
00318 }
00319
00320
00321 destroy_thread_sync_data(&tsd);
00322
00323 return (rc);
00324
00325 }
00326
00327 #elif defined(CURLRES_IPV6)
00328
00329
00330
00331
00332
00333
00334
00335
00336 static unsigned __stdcall getaddrinfo_thread (void *arg)
00337 {
00338 struct connectdata *conn = (struct connectdata*) arg;
00339 struct thread_data *td = (struct thread_data*) conn->async.os_specific;
00340 struct addrinfo *res;
00341 char service [NI_MAXSERV];
00342 int rc;
00343 struct addrinfo hints = td->hints;
00344
00345
00346
00347
00348
00349 struct thread_sync_data tsd = { 0,0,0,NULL };
00350
00351 if (!init_thread_sync_data(td, conn->async.hostname, &tsd)) {
00352
00353 return -1;
00354 }
00355
00356 itoa(conn->async.port, service, 10);
00357
00358 conn->async.status = NO_DATA;
00359 SET_SOCKERRNO(conn->async.status);
00360
00361
00362
00363 SetEvent(td->event_thread_started);
00364
00365 rc = getaddrinfo(tsd.hostname, service, &hints, &res);
00366
00367
00368 if (acquire_thread_sync(&tsd)) {
00369
00370
00371 SetEvent(td->event_resolved);
00372
00373 if (rc == 0) {
00374 #ifdef DEBUG_THREADING_GETADDRINFO
00375 dump_addrinfo (conn, res);
00376 #endif
00377 rc = Curl_addrinfo6_callback(conn, CURL_ASYNC_SUCCESS, res);
00378 }
00379 else {
00380 rc = Curl_addrinfo6_callback(conn, SOCKERRNO, NULL);
00381 TRACE(("Winsock-error %d, no address\n", conn->async.status));
00382 }
00383 release_thread_sync(&tsd);
00384 }
00385
00386
00387 destroy_thread_sync_data(&tsd);
00388
00389 return (rc);
00390
00391 }
00392 #endif
00393
00394
00395
00396
00397
00398 void Curl_destroy_thread_data (struct Curl_async *async)
00399 {
00400 if (async->hostname)
00401 free(async->hostname);
00402
00403 if (async->os_specific) {
00404 struct thread_data *td = (struct thread_data*) async->os_specific;
00405 curl_socket_t sock = td->dummy_sock;
00406
00407 if (td->mutex_terminate && td->event_terminate) {
00408
00409 if (WaitForSingleObject(td->mutex_terminate, INFINITE) == WAIT_OBJECT_0) {
00410 SetEvent(td->event_terminate);
00411 ReleaseMutex(td->mutex_terminate);
00412 }
00413 else {
00414
00415 }
00416 }
00417
00418 if (td->mutex_terminate)
00419 CloseHandle(td->mutex_terminate);
00420 if (td->event_terminate)
00421 CloseHandle(td->event_terminate);
00422 if (td->event_thread_started)
00423 CloseHandle(td->event_thread_started);
00424
00425 if (sock != CURL_SOCKET_BAD)
00426 sclose(sock);
00427
00428
00429 if (td->mutex_waiting)
00430 CloseHandle(td->mutex_waiting);
00431 td->mutex_waiting = NULL;
00432 if (td->event_resolved)
00433 CloseHandle(td->event_resolved);
00434
00435 if (td->thread_hnd)
00436 CloseHandle(td->thread_hnd);
00437
00438 free(async->os_specific);
00439 }
00440 async->hostname = NULL;
00441 async->os_specific = NULL;
00442 }
00443
00444
00445
00446
00447
00448
00449
00450 static bool init_resolve_thread (struct connectdata *conn,
00451 const char *hostname, int port,
00452 const Curl_addrinfo *hints)
00453 {
00454 struct thread_data *td = calloc(sizeof(*td), 1);
00455 HANDLE thread_and_event[2] = {0};
00456
00457 if (!td) {
00458 SET_ERRNO(ENOMEM);
00459 return FALSE;
00460 }
00461
00462 Curl_safefree(conn->async.hostname);
00463 conn->async.hostname = strdup(hostname);
00464 if (!conn->async.hostname) {
00465 free(td);
00466 SET_ERRNO(ENOMEM);
00467 return FALSE;
00468 }
00469
00470 conn->async.port = port;
00471 conn->async.done = FALSE;
00472 conn->async.status = 0;
00473 conn->async.dns = NULL;
00474 conn->async.os_specific = (void*) td;
00475 td->dummy_sock = CURL_SOCKET_BAD;
00476
00477
00478
00479
00480 td->mutex_waiting = CreateMutex(NULL, TRUE, NULL);
00481 if (td->mutex_waiting == NULL) {
00482 Curl_destroy_thread_data(&conn->async);
00483 SET_ERRNO(EAGAIN);
00484 return FALSE;
00485 }
00486
00487
00488
00489
00490 td->event_resolved = CreateEvent(NULL, TRUE, FALSE, NULL);
00491 if (td->event_resolved == NULL) {
00492 Curl_destroy_thread_data(&conn->async);
00493 SET_ERRNO(EAGAIN);
00494 return FALSE;
00495 }
00496
00497
00498
00499 td->mutex_terminate = CreateMutex(NULL, FALSE, NULL);
00500 if (td->mutex_terminate == NULL) {
00501 Curl_destroy_thread_data(&conn->async);
00502 SET_ERRNO(EAGAIN);
00503 return FALSE;
00504 }
00505
00506
00507 td->event_terminate = CreateEvent(NULL, TRUE, FALSE, NULL);
00508 if (td->event_terminate == NULL) {
00509 Curl_destroy_thread_data(&conn->async);
00510 SET_ERRNO(EAGAIN);
00511 return FALSE;
00512 }
00513
00514
00515 td->event_thread_started = CreateEvent(NULL, TRUE, FALSE, NULL);
00516 if (td->event_thread_started == NULL) {
00517 Curl_destroy_thread_data(&conn->async);
00518 SET_ERRNO(EAGAIN);
00519 return FALSE;
00520 }
00521
00522 #ifdef _WIN32_WCE
00523 td->thread_hnd = (HANDLE) CreateThread(NULL, 0,
00524 (LPTHREAD_START_ROUTINE) THREAD_FUNC,
00525 conn, 0, &td->thread_id);
00526 #else
00527 td->thread_hnd = (HANDLE) _beginthreadex(NULL, 0, THREAD_FUNC,
00528 conn, 0, &td->thread_id);
00529 #endif
00530
00531 #ifdef CURLRES_IPV6
00532 DEBUGASSERT(hints);
00533 td->hints = *hints;
00534 #else
00535 (void) hints;
00536 #endif
00537
00538 if (!td->thread_hnd) {
00539 #ifdef _WIN32_WCE
00540 TRACE(("CreateThread() failed; %s\n", Curl_strerror(conn, ERRNO)));
00541 #else
00542 SET_ERRNO(errno);
00543 TRACE(("_beginthreadex() failed; %s\n", Curl_strerror(conn, ERRNO)));
00544 #endif
00545 Curl_destroy_thread_data(&conn->async);
00546 return FALSE;
00547 }
00548
00549
00550 thread_and_event[0] = td->thread_hnd;
00551 thread_and_event[1] = td->event_thread_started;
00552 if (WaitForMultipleObjects(sizeof(thread_and_event) /
00553 sizeof(thread_and_event[0]),
00554 (const HANDLE*)thread_and_event, FALSE,
00555 INFINITE) == WAIT_FAILED) {
00556
00557
00558
00559 }
00560
00561
00562
00563
00564 td->dummy_sock = socket(AF_INET, SOCK_DGRAM, 0);
00565 return TRUE;
00566 }
00567
00568
00569
00570
00571
00572
00573
00574
00575
00576
00577 CURLcode Curl_wait_for_resolv(struct connectdata *conn,
00578 struct Curl_dns_entry **entry)
00579 {
00580 struct thread_data *td = (struct thread_data*) conn->async.os_specific;
00581 struct SessionHandle *data = conn->data;
00582 long timeout;
00583 DWORD status, ticks;
00584 CURLcode rc;
00585
00586 DEBUGASSERT(conn && td);
00587
00588
00589
00590 timeout =
00591 conn->data->set.connecttimeout ? conn->data->set.connecttimeout :
00592 conn->data->set.timeout ? conn->data->set.timeout :
00593 CURL_TIMEOUT_RESOLVE * 1000;
00594 ticks = GetTickCount();
00595
00596
00597 status = WaitForSingleObject(td->event_resolved, timeout);
00598
00599
00600 ReleaseMutex(td->mutex_waiting);
00601
00602
00603 CloseHandle(td->mutex_waiting);
00604 td->mutex_waiting = NULL;
00605
00606
00607 CloseHandle(td->event_resolved);
00608 td->event_resolved = NULL;
00609
00610
00611 if (status == WAIT_OBJECT_0) {
00612
00613 if (WaitForSingleObject(td->thread_hnd, 5000) == WAIT_TIMEOUT) {
00614 TerminateThread(td->thread_hnd, 0);
00615 conn->async.done = TRUE;
00616 td->thread_status = (DWORD)-1;
00617 TRACE(("%s() thread stuck?!, ", THREAD_NAME));
00618 }
00619 else {
00620
00621
00622
00623
00624 SET_SOCKERRNO(conn->async.status);
00625 GetExitCodeThread(td->thread_hnd, &td->thread_status);
00626 TRACE(("%s() status %lu, thread retval %lu, ",
00627 THREAD_NAME, status, td->thread_status));
00628 }
00629 }
00630 else {
00631 conn->async.done = TRUE;
00632 td->thread_status = (DWORD)-1;
00633 TRACE(("%s() timeout, ", THREAD_NAME));
00634 }
00635
00636 TRACE(("elapsed %lu ms\n", GetTickCount()-ticks));
00637
00638 if(entry)
00639 *entry = conn->async.dns;
00640
00641 rc = CURLE_OK;
00642
00643 if (!conn->async.dns) {
00644
00645 if (td->thread_status == CURLE_OUT_OF_MEMORY) {
00646 rc = CURLE_OUT_OF_MEMORY;
00647 failf(data, "Could not resolve host: %s", curl_easy_strerror(rc));
00648 }
00649 else if(conn->async.done) {
00650 if(conn->bits.httpproxy) {
00651 failf(data, "Could not resolve proxy: %s; %s",
00652 conn->proxy.dispname, Curl_strerror(conn, conn->async.status));
00653 rc = CURLE_COULDNT_RESOLVE_PROXY;
00654 }
00655 else {
00656 failf(data, "Could not resolve host: %s; %s",
00657 conn->host.name, Curl_strerror(conn, conn->async.status));
00658 rc = CURLE_COULDNT_RESOLVE_HOST;
00659 }
00660 }
00661 else if (td->thread_status == (DWORD)-1 || conn->async.status == NO_DATA) {
00662 failf(data, "Resolving host timed out: %s", conn->host.name);
00663 rc = CURLE_OPERATION_TIMEDOUT;
00664 }
00665 else
00666 rc = CURLE_OPERATION_TIMEDOUT;
00667 }
00668
00669 Curl_destroy_thread_data(&conn->async);
00670
00671 if(!conn->async.dns)
00672 conn->bits.close = TRUE;
00673
00674 return (rc);
00675 }
00676
00677
00678
00679
00680
00681
00682 CURLcode Curl_is_resolved(struct connectdata *conn,
00683 struct Curl_dns_entry **entry)
00684 {
00685 *entry = NULL;
00686
00687 if (conn->async.done) {
00688
00689 Curl_destroy_thread_data(&conn->async);
00690 if (!conn->async.dns) {
00691 TRACE(("Curl_is_resolved(): CURLE_COULDNT_RESOLVE_HOST\n"));
00692 return CURLE_COULDNT_RESOLVE_HOST;
00693 }
00694 *entry = conn->async.dns;
00695 TRACE(("resolved okay, dns %p\n", *entry));
00696 }
00697 return CURLE_OK;
00698 }
00699
00700 int Curl_resolv_getsock(struct connectdata *conn,
00701 curl_socket_t *socks,
00702 int numsocks)
00703 {
00704 const struct thread_data *td =
00705 (const struct thread_data *) conn->async.os_specific;
00706
00707 if (td && td->dummy_sock != CURL_SOCKET_BAD) {
00708 if(numsocks) {
00709
00710
00711 socks[0] = td->dummy_sock;
00712 return GETSOCK_WRITESOCK(0);
00713 }
00714 }
00715 return 0;
00716 }
00717
00718 #ifdef CURLRES_IPV4
00719
00720
00721
00722 Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn,
00723 const char *hostname,
00724 int port,
00725 int *waitp)
00726 {
00727 struct hostent *h = NULL;
00728 struct SessionHandle *data = conn->data;
00729 in_addr_t in;
00730
00731 *waitp = 0;
00732
00733 in = inet_addr(hostname);
00734 if (in != CURL_INADDR_NONE)
00735
00736 return Curl_ip2addr(in, hostname, port);
00737
00738
00739 if (init_resolve_thread(conn, hostname, port, NULL)) {
00740 *waitp = TRUE;
00741 return NULL;
00742 }
00743
00744
00745 infof(data, "init_resolve_thread() failed for %s; %s\n",
00746 hostname, Curl_strerror(conn, ERRNO));
00747
00748 h = gethostbyname(hostname);
00749 if (!h) {
00750 infof(data, "gethostbyname(2) failed for %s:%d; %s\n",
00751 hostname, port, Curl_strerror(conn, SOCKERRNO));
00752 return NULL;
00753 }
00754 return Curl_he2ai(h, port);
00755 }
00756 #endif
00757
00758 #ifdef CURLRES_IPV6
00759
00760
00761
00762 Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn,
00763 const char *hostname,
00764 int port,
00765 int *waitp)
00766 {
00767 struct addrinfo hints, *res;
00768 int error;
00769 char sbuf[NI_MAXSERV];
00770 curl_socket_t s;
00771 int pf;
00772 struct SessionHandle *data = conn->data;
00773
00774 *waitp = FALSE;
00775
00776
00777 s = socket(PF_INET6, SOCK_DGRAM, 0);
00778 if (s == CURL_SOCKET_BAD) {
00779
00780
00781
00782
00783 pf = PF_INET;
00784 }
00785 else {
00786
00787
00788
00789 sclose(s);
00790
00791
00792
00793
00794 switch(data->set.ip_version) {
00795 case CURL_IPRESOLVE_V4:
00796 pf = PF_INET;
00797 break;
00798 case CURL_IPRESOLVE_V6:
00799 pf = PF_INET6;
00800 break;
00801 default:
00802 pf = PF_UNSPEC;
00803 break;
00804 }
00805 }
00806
00807 memset(&hints, 0, sizeof(hints));
00808 hints.ai_family = pf;
00809 hints.ai_socktype = conn->socktype;
00810 #if 0
00811 hints.ai_flags = AI_CANONNAME;
00812 #endif
00813 itoa(port, sbuf, 10);
00814
00815
00816 if (init_resolve_thread(conn, hostname, port, &hints)) {
00817 *waitp = TRUE;
00818 return NULL;
00819 }
00820
00821
00822 infof(data, "init_resolve_thread() failed for %s; %s\n",
00823 hostname, Curl_strerror(conn, ERRNO));
00824
00825 error = getaddrinfo(hostname, sbuf, &hints, &res);
00826 if (error) {
00827 infof(data, "getaddrinfo() failed for %s:%d; %s\n",
00828 hostname, port, Curl_strerror(conn, SOCKERRNO));
00829 return NULL;
00830 }
00831 return res;
00832 }
00833 #endif
00834 #endif