xstat-protoizing-20030309
[openafs.git] / src / xstat / xstat_cm.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /*
11  * Description:
12  *      Implementation of the client side of the AFS Cache Manager
13  *      extended statistics facility.
14  *
15  *------------------------------------------------------------------------*/
16
17 #include <afsconfig.h>
18 #include <afs/param.h>
19
20 RCSID("$Header$");
21
22 #include "xstat_cm.h"                   /*Interface for this module*/
23 #include <lwp.h>                        /*Lightweight process package*/
24
25 #ifdef HAVE_STRING_H
26 #include <string.h>
27 #else
28 #ifdef HAVE_STRINGS_H
29 #include <strings.h>
30 #endif
31 #endif
32
33 #define LWP_STACK_SIZE  (16 * 1024)
34
35 /*
36  * Routines we need that don't have explicit include file definitions.
37  */
38 extern char *hostutil_GetNameByINet();  /*Host parsing utility*/
39
40 /*
41  * Exported variables.
42  */
43 int xstat_cm_numServers;                        /*Num connected servers*/
44 struct xstat_cm_ConnectionInfo
45     *xstat_cm_ConnInfo;                         /*Ptr to connection array*/
46 int numCollections;                             /*Number of data collections*/
47 struct xstat_cm_ProbeResults xstat_cm_Results;  /*Latest probe results*/
48 char terminationEvent;                          /*One-shot termination event*/
49
50 afs_int32 xstat_cmData[AFSCB_MAX_XSTAT_LONGS];          /*Buffer for collected data*/
51
52 /*
53  * Private globals.
54  */
55 static int xstat_cm_ProbeFreqInSecs;            /*Probe freq. in seconds*/
56 static int xstat_cm_initflag = 0;               /*Was init routine called?*/
57 static int xstat_cm_debug = 0;                  /*Debugging output enabled?*/
58 static int xstat_cm_oneShot = 0;                /*One-shot operation?*/
59 static int (*xstat_cm_Handler)();               /*Probe handler routine*/
60 static PROCESS probeLWP_ID;                     /*Probe LWP process ID*/
61 static int xstat_cm_numCollections;             /*Number of desired collections*/
62 static afs_int32 *xstat_cm_collIDP;                     /*Ptr to collection IDs desired*/
63
64
65 /*------------------------------------------------------------------------
66  * [private] xstat_cm_CleanupInit
67  *
68  * Description:
69  *      Set up for recovery after an error in initialization (i.e.,
70  *      during a call to xstat_cm_Init.
71  *
72  * Arguments:
73  *      None.
74  *
75  * Returns:
76  *      0 on success,
77  *      Error value otherwise.
78  *
79  * Environment:
80  *      This routine is private to the module.
81  *
82  * Side Effects:
83  *      Zeros out basic data structures.
84  *------------------------------------------------------------------------*/
85
86 static int xstat_cm_CleanupInit()
87 {
88     xstat_cm_ConnInfo = (struct xstat_cm_ConnectionInfo *)0;
89     xstat_cm_Results.probeNum = 0;
90     xstat_cm_Results.probeTime = 0;
91     xstat_cm_Results.connP = (struct xstat_cm_ConnectionInfo *)0;
92     xstat_cm_Results.collectionNumber = 0;
93     xstat_cm_Results.data.AFSCB_CollData_len = AFSCB_MAX_XSTAT_LONGS;
94     xstat_cm_Results.data.AFSCB_CollData_val = (afs_int32 *)xstat_cmData;
95     xstat_cm_Results.probeOK = 0;
96
97     return(0);
98 }
99
100
101 /*------------------------------------------------------------------------
102  * [exported] xstat_cm_Cleanup
103  *
104  * Description:
105  *      Clean up our memory and connection state.
106  *
107  * Arguments:
108  *      int a_releaseMem : Should we free up malloc'ed areas?
109  *
110  * Returns:
111  *      0 on total success,
112  *      -1 if the module was never initialized, or there was a problem
113  *              with the xstat_cm connection array.
114  *
115  * Environment:
116  *      xstat_cm_numServers should be properly set.  We don't do anything
117  *      unless xstat_cm_Init() has already been called.
118  *
119  * Side Effects:
120  *      Shuts down Rx connections gracefully, frees allocated space
121  *      (if so directed).
122  *------------------------------------------------------------------------*/
123
124 int xstat_cm_Cleanup(int a_releaseMem)
125 {
126     static char rn[] = "xstat_cm_Cleanup";      /*Routine name*/
127     int code;                                   /*Return code*/
128     int conn_idx;                               /*Current connection index*/
129     struct xstat_cm_ConnectionInfo *curr_conn;  /*Ptr to xstat_cm connection*/
130
131     /*
132      * Assume the best, but check the worst.
133      */
134     if (!xstat_cm_initflag) {
135         fprintf(stderr, "[%s] Refused; module not initialized\n", rn);
136         return(-1);
137     }
138     else
139         code = 0;
140
141     /*
142      * Take care of all Rx connections first.  Check to see that the
143      * server count is a legal value.
144      */
145     if (xstat_cm_numServers <= 0) {
146         fprintf(stderr,
147                 "[%s] Illegal number of servers (xstat_cm_numServers = %d)\n",
148                 rn, xstat_cm_numServers);
149         code = -1;
150     }
151     else {
152         if (xstat_cm_ConnInfo != (struct xstat_cm_ConnectionInfo *)0) {
153             /*
154              * The xstat_cm connection structure array exists.  Go through
155              * it and close up any Rx connections it holds.
156              */
157             curr_conn = xstat_cm_ConnInfo;
158             for (conn_idx = 0; conn_idx < xstat_cm_numServers; conn_idx++) {
159                 if (curr_conn->rxconn != (struct rx_connection *)0) {
160                     rx_DestroyConnection(curr_conn->rxconn);
161                     curr_conn->rxconn = (struct rx_connection *)0;
162                 }
163                 curr_conn++;
164             } /*for each xstat_cm connection*/
165         } /*xstat_cm connection structure exists*/
166     } /*Legal number of servers*/
167
168     /*
169      * If asked to, release the space we've allocated.
170      */
171     if (a_releaseMem) {
172         if (xstat_cm_ConnInfo != (struct xstat_cm_ConnectionInfo *)0)
173             free(xstat_cm_ConnInfo);
174     }
175
176     /*
177      * Return the news, whatever it is.
178      */
179     return(code);
180 }
181
182
183 /*------------------------------------------------------------------------
184  * [private] xstat_cm_LWP
185  *
186  * Description:
187  *      This LWP iterates over the server connections and gathers up
188  *      the desired statistics from each one on a regular basis, for
189  *      all known data collections.  The associated handler function
190  *      is called each time a new data collection is received.
191  *
192  * Arguments:
193  *      None.
194  *
195  * Returns:
196  *      Nothing.
197  *
198  * Environment:
199  *      Started by xstat_cm_Init(), uses global structures and the
200  *      global private xstat_cm_oneShot variable.
201  *
202  * Side Effects:
203  *      As advertised.
204  *------------------------------------------------------------------------*/
205 static void xstat_cm_LWP()
206 {
207     static char rn[] = "xstat_cm_LWP";          /*Routine name*/
208     register afs_int32 code;                            /*Results of calls*/
209     int oneShotCode;                            /*Result of one-shot signal*/
210     struct timeval tv;                          /*Time structure*/
211     int conn_idx;                               /*Connection index*/
212     struct xstat_cm_ConnectionInfo *curr_conn;  /*Current connection*/
213     afs_int32 srvVersionNumber;                 /*Xstat version #*/
214     afs_int32 clientVersionNumber;                      /*Client xstat version*/
215     afs_int32 numColls;                         /*Number of collections to get*/
216     afs_int32 *currCollIDP;                             /*Curr collection ID desired*/
217
218     /*
219      * Set up some numbers we'll need.
220      */
221     clientVersionNumber = AFSCB_XSTAT_VERSION;
222
223     while (1) { /*Service loop*/
224         /*
225          * Iterate through the server connections, gathering data.
226          * Don't forget to bump the probe count and zero the statistics
227          * areas before calling the servers.
228          */
229         if (xstat_cm_debug)
230             printf("[%s] Waking up, getting data from %d server(s)\n",
231                    rn, xstat_cm_numServers);
232         curr_conn = xstat_cm_ConnInfo;
233         xstat_cm_Results.probeNum++;
234
235         for (conn_idx = 0; conn_idx < xstat_cm_numServers; conn_idx++) {
236             /*
237              * Grab the statistics for the current Cache Manager, if the
238              * connection is valid.
239              */
240             if (xstat_cm_debug)
241                 printf("[%s] Getting collections from Cache Manager '%s'\n",
242                        rn, curr_conn->hostName);
243             if (curr_conn->rxconn != (struct rx_connection *)0) {
244                 if (xstat_cm_debug)
245                     printf("[%s] Connection OK, calling RXAFSCB_GetXStats\n",
246                            rn);
247
248                 /*
249                  * Probe the given CM for each desired collection.
250                  */
251                 currCollIDP = xstat_cm_collIDP;
252                 for (numColls = 0;
253                      numColls < xstat_cm_numCollections;
254                      numColls++, currCollIDP++) {
255                     /*
256                      * Initialize the per-probe values.
257                      */
258                     if (xstat_cm_debug)
259                         printf("[%s] Asking for data collection %d\n",
260                                rn, *currCollIDP);
261                     xstat_cm_Results.collectionNumber = *currCollIDP;
262                     xstat_cm_Results.data.AFSCB_CollData_len =
263                         AFSCB_MAX_XSTAT_LONGS;
264                     memset(xstat_cm_Results.data.AFSCB_CollData_val, 0, AFSCB_MAX_XSTAT_LONGS * 4);
265
266                     xstat_cm_Results.connP = curr_conn;
267
268                     if (xstat_cm_debug) {
269                         printf("%s: Calling RXAFSCB_GetXStats, conn=0x%x, clientVersionNumber=%d, collectionNumber=%d, srvVersionNumberP=0x%x, timeP=0x%x, dataP=0x%x\n",
270                                rn, curr_conn->rxconn,
271                                clientVersionNumber,
272                                *currCollIDP,
273                                &srvVersionNumber,
274                                &(xstat_cm_Results.probeTime),
275                                &(xstat_cm_Results.data));
276                         printf("%s: [bufflen=%d, buffer at 0x%x]\n",
277                                rn,
278                                xstat_cm_Results.data.AFSCB_CollData_len,
279                                xstat_cm_Results.data.AFSCB_CollData_val);
280                     }
281
282                     xstat_cm_Results.probeOK =
283                         RXAFSCB_GetXStats(curr_conn->rxconn,
284                                           clientVersionNumber,
285                                           *currCollIDP,
286                                           &srvVersionNumber,
287                                           &(xstat_cm_Results.probeTime),
288                                           &(xstat_cm_Results.data));
289                     
290                     /*
291                      * Now that we (may) have the data for this connection,
292                      * call the associated handler function.  The handler
293                      * does not take any explicit parameters, but rather
294                      * gets to the goodies via some of the objects exported
295                      * by this module.
296                      */
297                     if (xstat_cm_debug)
298                         printf("[%s] Calling handler routine.\n",
299                                rn);
300                     code = xstat_cm_Handler();
301                     if (code)
302                         fprintf(stderr,
303                                 "[%s] Handler routine got error code %d\n",
304                                 rn, code);
305                 } /*For each collection*/
306             } /*Valid Rx connection*/
307             
308             /*
309              * Advance the xstat_cm connection pointer.
310              */
311             curr_conn++;
312
313         } /*For each xstat_cm connection*/
314
315         /*
316          * All (valid) connections have been probed.  Fall asleep for the
317          * prescribed number of seconds, unless we're a one-shot.  In
318          * that case, we need to signal our caller that we're done.
319          */
320         if (xstat_cm_debug)
321             printf("[%s] Polling complete for probe round %d.\n",
322                    rn, xstat_cm_Results.probeNum);
323
324         if (xstat_cm_oneShot) {
325             /*
326              * One-shot execution desired.  Signal our main procedure
327              * that we've finished our collection round.
328              */
329             if (xstat_cm_debug)
330                 printf("[%s] Signalling main process at 0x%x\n",
331                        rn, &terminationEvent);
332             oneShotCode = LWP_SignalProcess(&terminationEvent);
333             if (oneShotCode)
334                 fprintf(stderr,
335                         "[%s] Error %d from LWP_SignalProcess()",
336                         rn, oneShotCode);
337             break; /*from the perpetual while loop*/
338         } /*One-shot execution*/
339         else {
340             /*
341              * Continuous execution desired.  Sleep for the required
342              * number of seconds.
343              */
344             tv.tv_sec  = xstat_cm_ProbeFreqInSecs;
345             tv.tv_usec = 0;
346             if (xstat_cm_debug)
347                 printf("[%s] Falling asleep for %d seconds\n",
348                        rn, xstat_cm_ProbeFreqInSecs);
349             code = IOMGR_Select(0,      /*Num fids*/
350                                 0,      /*Descs ready for reading*/
351                                 0,      /*Descs ready for writing*/
352                                 0,      /*Descs w/exceptional conditions*/
353                                 &tv);   /*Ptr to timeout structure*/
354             if (code)
355                 fprintf(stderr,
356                         "[%s] IOMGR_Select returned code %d\n",
357                         rn, code);
358         } /*Continuous execution*/
359     } /*Service loop*/
360 }
361
362
363 /*------------------------------------------------------------------------
364  * [exported] xstat_cm_Init
365  *
366  * Description:
367  *      Initialize the xstat_cm module: set up Rx connections to the
368  *      given set of Cache Managers, start up the probe LWP, and
369  *      associate the routine to be called when a probe completes.
370  *      Also, let it know which collections you're interested in.
371  *
372  * Arguments:
373  *      int a_numServers                  : Num. servers to connect to.
374  *      struct sockaddr_in *a_socketArray : Array of server sockets.
375  *      int a_ProbeFreqInSecs             : Probe frequency in seconds.
376  *      int (*a_ProbeHandler)()           : Ptr to probe handler fcn.
377  *      int a_flags;                      : Various flags.
378  *      int a_numCollections              : Number of collections desired.
379  *      afs_int32 *a_collIDP                      : Ptr to collection IDs.
380  *
381  * Returns:
382  *      0 on success,
383  *      -2 for (at least one) connection error,
384  *      LWP process creation code, if it failed,
385  *      -1 for other fatal errors.
386  *
387  * Environment:
388  *      *** MUST BE THE FIRST ROUTINE CALLED FROM THIS PACKAGE ***
389  *
390  * Side Effects:
391  *      Sets up just about everything.
392  *------------------------------------------------------------------------*/
393
394 int xstat_cm_Init(int a_numServers, struct sockaddr_in *a_socketArray,
395                   int a_ProbeFreqInSecs, int (*a_ProbeHandler)(), int a_flags,
396                   int a_numCollections, afs_int32 *a_collIDP)
397 {
398
399     static char rn[] = "xstat_cm_Init";         /*Routine name*/
400     register afs_int32 code;                            /*Return value*/
401     struct rx_securityClass *secobj;            /*Client security object*/
402     int arg_errfound;                           /*Argument error found?*/
403     int curr_srv;                               /*Current server idx*/
404     struct xstat_cm_ConnectionInfo *curr_conn;  /*Ptr to current conn*/
405     char *hostNameFound;                        /*Ptr to returned host name*/
406     int conn_err;                               /*Connection error?*/
407     int collIDBytes;                            /*Num bytes in coll ID array*/
408
409     /*
410      * If we've already been called, snicker at the bozo, gently
411      * remind him of his doubtful heritage, and return success.
412      */
413     if (xstat_cm_initflag) {
414         fprintf(stderr, "[%s] Called multiple times!\n", rn);
415         return(0);
416     }
417     else
418         xstat_cm_initflag = 1; 
419
420     /*
421      * Check the parameters for bogosities.
422      */
423     arg_errfound = 0;
424     if (a_numServers <= 0) {
425         fprintf(stderr, "[%s] Illegal number of servers: %d\n",
426                 rn, a_numServers);
427         arg_errfound = 1;
428     }
429     if (a_socketArray == (struct sockaddr_in *)0) {
430         fprintf(stderr, "[%s] Null server socket array argument\n", rn);
431         arg_errfound = 1;
432     }
433     if (a_ProbeFreqInSecs <= 0) {
434         fprintf(stderr, "[%s] Illegal probe frequency: %d\n",
435                 rn, a_ProbeFreqInSecs);
436         arg_errfound = 1;
437     }
438     if (a_ProbeHandler == (int (*)())0) {
439         fprintf(stderr, "[%s] Null probe handler function argument\n",
440                 rn);
441         arg_errfound = 1;
442     }
443     if (a_numCollections <= 0) {
444         fprintf(stderr, "[%s] Illegal collection count argument: %d\n",
445                 rn, a_numServers);
446         arg_errfound = 1;
447     }
448     if (a_collIDP == NULL) {
449         fprintf(stderr, "[%s] Null collection ID array argument\n", rn);
450         arg_errfound = 1;
451     }
452     if (arg_errfound)
453         return(-1);
454
455     /*
456      * Record our passed-in info.
457      */
458     xstat_cm_debug           = (a_flags & XSTAT_CM_INITFLAG_DEBUGGING);
459     xstat_cm_oneShot         = (a_flags & XSTAT_CM_INITFLAG_ONE_SHOT);
460     xstat_cm_numServers      = a_numServers;
461     xstat_cm_Handler         = a_ProbeHandler;
462     xstat_cm_ProbeFreqInSecs = a_ProbeFreqInSecs;
463     xstat_cm_numCollections  = a_numCollections;
464     collIDBytes = xstat_cm_numCollections * sizeof(afs_int32);
465     xstat_cm_collIDP         = (afs_int32 *)(malloc(collIDBytes));
466     memcpy(xstat_cm_collIDP, a_collIDP, collIDBytes);
467     if (xstat_cm_debug) {
468         printf("[%s] Asking for %d collection(s): ", rn, xstat_cm_numCollections);
469         for (curr_srv = 0; curr_srv < xstat_cm_numCollections; curr_srv++)
470             printf("%d ", *(xstat_cm_collIDP+curr_srv));
471         printf("\n");
472     }
473
474     /*
475      * Get ready in case we have to do a cleanup - basically, zero
476      * everything out.
477      */
478     code = xstat_cm_CleanupInit();
479     if (code)
480         return(code);
481
482     /*
483      * Allocate the necessary data structures and initialize everything
484      * else.
485      */
486     xstat_cm_ConnInfo =
487         (struct xstat_cm_ConnectionInfo *)
488             malloc(a_numServers * sizeof(struct xstat_cm_ConnectionInfo));
489     if (xstat_cm_ConnInfo == (struct xstat_cm_ConnectionInfo *)0) {
490         fprintf(stderr,
491                 "[%s] Can't allocate %d connection info structs (%d bytes)\n",
492                 rn, a_numServers,
493                 (a_numServers * sizeof(struct xstat_cm_ConnectionInfo)));
494         return(-1);     /*No cleanup needs to be done yet*/
495     }
496
497     /*
498      * Initialize the Rx subsystem, just in case nobody's done it.
499      */
500     if (xstat_cm_debug)
501         printf("[%s] Initializing Rx on port 0\n", rn);
502     code = rx_Init(htons(0));
503     if (code) {
504         fprintf(stderr, "[%s] Fatal error in rx_Init(), error=%d\n",
505                 rn, code);
506         return(-1);
507     }
508
509     if (xstat_cm_debug)
510         printf("[%s] Rx initialized on port 0\n", rn);
511
512     /*
513      * Create a null Rx client security object, to be used by the
514      * probe LWP.
515      */
516     secobj = rxnull_NewClientSecurityObject();
517     if (secobj == (struct rx_securityClass *)0) {
518         fprintf(stderr,
519                 "[%s] Can't create probe LWP client security object.\n",
520                 rn);
521         xstat_cm_Cleanup(1); /*Delete already-malloc'ed areas*/
522         return(-1);
523     }
524     if (xstat_cm_debug)
525         printf("[%s] Probe LWP client security object created\n",
526                rn);
527
528     curr_conn = xstat_cm_ConnInfo;
529     conn_err = 0;
530     for (curr_srv = 0; curr_srv < a_numServers; curr_srv++) {
531         /*
532          * Copy in the socket info for the current server, resolve its
533          * printable name if possible.
534          */
535         if (xstat_cm_debug) {
536             printf("[%s] Copying in the following socket info:\n",
537                    rn);
538             printf("[%s] IP addr 0x%lx, port %d\n", rn,
539                    (a_socketArray + curr_srv)->sin_addr.s_addr,
540                    (a_socketArray + curr_srv)->sin_port);
541         }
542         memcpy(&(curr_conn->skt), a_socketArray + curr_srv, sizeof(struct sockaddr_in));
543         
544         hostNameFound =
545             hostutil_GetNameByINet(curr_conn->skt.sin_addr.s_addr);
546         if (hostNameFound == NULL) {
547             fprintf(stderr,
548                     "[%s] Can't map Internet address %lu to a string name\n",
549                     rn, curr_conn->skt.sin_addr.s_addr);
550             curr_conn->hostName[0] = '\0';
551         }
552         else {
553             strcpy(curr_conn->hostName, hostNameFound);
554             if (xstat_cm_debug)
555                 printf("[%s] Host name for server index %d is %s\n",
556                        rn, curr_srv, curr_conn->hostName);
557         }
558         
559         /*
560          * Make an Rx connection to the current server.
561          */
562         if (xstat_cm_debug)
563             printf("[%s] Connecting to srv idx %d, IP addr 0x%lx, port %d, service 1\n",
564                    rn, curr_srv, curr_conn->skt.sin_addr.s_addr,
565                    curr_conn->skt.sin_port);
566         curr_conn->rxconn =
567             rx_NewConnection(curr_conn->skt.sin_addr.s_addr, /*Server addr*/
568                              curr_conn->skt.sin_port,        /*Server port*/
569                              1,                              /*AFS service #*/
570                              secobj,                         /*Security obj*/
571                              0);                             /*# of above*/
572         if (curr_conn->rxconn == (struct rx_connection *)0) {
573             fprintf(stderr,
574                     "[%s] Can't create Rx connection to server '%s' (%lu)\n",
575                     rn, curr_conn->hostName, curr_conn->skt.sin_addr.s_addr);
576             conn_err = 1;
577         }
578         if (xstat_cm_debug)
579             printf("[%s] New connection at 0x%lx\n",
580                    rn, curr_conn->rxconn);
581
582         /*
583          * Bump the current xstat_cm connection to set up.
584          */
585         curr_conn++;
586
587     } /*for curr_srv*/
588
589     /*
590      * Start up the probe LWP.
591      */
592     if (xstat_cm_debug)
593         printf("[%s] Creating the probe LWP\n", rn);
594     code =
595         LWP_CreateProcess(xstat_cm_LWP,         /*Function to start up*/
596                           LWP_STACK_SIZE,       /*Stack size in bytes*/
597                           1,                    /*Priority*/
598                           (void *) 0,           /*Parameters*/
599                           "xstat_cm Worker",    /*Name to use*/
600                           &probeLWP_ID);        /*Returned LWP process ID*/
601     if (code) {
602         fprintf(stderr,
603                 "[%s] Can't create xstat_cm LWP!  Error is %d\n",
604                 rn, code);
605         xstat_cm_Cleanup(1); /*Delete already-malloc'ed areas*/
606         return(code);
607     }
608     if (xstat_cm_debug)
609         printf("[%s] Probe LWP process structure located at 0x%x\n",
610                rn, probeLWP_ID);
611
612     /*
613      * Return the final results.
614      */
615     if (conn_err)
616         return(-2);
617     else
618         return(0);
619 }
620
621
622 /*------------------------------------------------------------------------
623  * [exported] xstat_cm_ForceProbeNow
624  *
625  * Description:
626  *      Wake up the probe LWP, forcing it to execute a probe immediately.
627  *
628  * Arguments:
629  *      None.
630  *
631  * Returns:
632  *      0 on success,
633  *      Error value otherwise.
634  *
635  * Environment:
636  *      The module must have been initialized.
637  *
638  * Side Effects:
639  *      As advertised.
640  *------------------------------------------------------------------------*/
641
642 int xstat_cm_ForceProbeNow()
643 {
644     static char rn[] = "xstat_cm_ForceProbeNow";        /*Routine name*/
645
646     /*
647      * There isn't a prayer unless we've been initialized.
648      */
649     if (!xstat_cm_initflag) {
650         fprintf(stderr, "[%s] Must call xstat_cm_Init first!\n", rn);
651         return(-1);
652     }
653
654     /*
655      * Kick the sucker in the side.
656      */
657     IOMGR_Cancel(probeLWP_ID);
658
659     /*
660      * We did it, so report the happy news.
661      */
662     return(0);
663 }