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