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