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