windows-misc-fix-20061004
[openafs.git] / src / WINNT / afsd / cm_dnlc.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 **      This implements the directory to name cache lookup. 
12 **      Given a directory scache and a name, the dnlc returns the
13 **      vcache corresponding to the name. The vcache entries that 
14 **      exist in the dnlc are not refcounted. 
15 **
16 */
17
18 #include <afs/param.h>
19 #include <afs/stds.h>
20
21 #include <windows.h>
22 #include <winsock2.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <osi.h>
26 #include "afsd.h"
27
28 osi_rwlock_t cm_dnlcLock;
29
30 static cm_dnlcstats_t dnlcstats;        /* dnlc statistics */
31 static int cm_useDnlc = 1;      /* yes, start using the dnlc */
32 static int cm_debugDnlc = 0;    /* debug dnlc */
33
34
35 /* Hash table invariants:
36  *     1.  If nameHash[i] is NULL, list is empty
37  *     2.  A single element in a hash bucket has itself as prev and next.
38  */
39
40 /* Must be called with cm_dnlcLock write locked */
41 static cm_nc_t * 
42 GetMeAnEntry() 
43 {
44     static unsigned int nameptr = 0; /* next bucket to pull something from */
45     cm_nc_t *tnc;
46     int j;
47   
48     if (cm_data.ncfreelist) 
49     {
50         tnc = cm_data.ncfreelist;
51         cm_data.ncfreelist = tnc->next;
52         return tnc;
53     }
54
55     for (j=0; j<NHSIZE+2; j++, nameptr++) 
56     {
57         if (nameptr >= NHSIZE) 
58             nameptr =0;
59         if (cm_data.nameHash[nameptr])
60             break;
61     }
62
63     tnc = cm_data.nameHash[nameptr];
64     if (!tnc)   
65     {
66         osi_Log0(afsd_logp,"null tnc in GetMeAnEntry");
67         return 0;
68     }
69
70     if (tnc->prev == tnc) 
71     {                   /* only thing in list, don't screw around */
72         cm_data.nameHash[nameptr] = (cm_nc_t *) 0;
73         return (tnc);
74     }
75
76     tnc = tnc->prev;            /* grab oldest one in this bucket */
77     tnc->next->prev = tnc->prev;/* remove it from list */
78     tnc->prev->next = tnc->next;
79
80     return (tnc);
81 }
82
83 static void 
84 InsertEntry(cm_nc_t *tnc)
85 {
86     unsigned int key; 
87     key = tnc->key & (NHSIZE -1);
88
89     if (!cm_data.nameHash[key]) 
90     {
91         cm_data.nameHash[key] = tnc;
92         tnc->next = tnc->prev = tnc;
93     }
94     else 
95     {
96         tnc->next = cm_data.nameHash[key];
97         tnc->prev = tnc->next->prev;
98         tnc->next->prev = tnc;
99         tnc->prev->next = tnc;
100         cm_data.nameHash[key] = tnc;
101     }
102 }
103
104
105 void 
106 cm_dnlcEnter ( cm_scache_t *adp,
107                char        *aname,
108                cm_scache_t *avc )
109 {
110     cm_nc_t *tnc;
111     unsigned int key, skey, new=0;
112     char *ts = aname;
113     int safety;
114
115     if (!cm_useDnlc)
116         return ;
117
118     if (!strcmp(aname,".") || !strcmp(aname,".."))
119         return ;
120
121     if ( cm_debugDnlc ) 
122         osi_Log3(afsd_logp,"cm_dnlcEnter dir %x name %s scache %x", 
123             adp, osi_LogSaveString(afsd_logp,aname), avc);
124
125     dnlcHash( ts, key );  /* leaves ts pointing at the NULL */
126     if (ts - aname >= CM_AFSNCNAMESIZE) 
127         return ;
128     skey = key & (NHSIZE -1);
129
130     lock_ObtainWrite(&cm_dnlcLock);
131     dnlcstats.enters++;
132   
133     for (tnc = cm_data.nameHash[skey], safety=0; tnc; tnc = tnc->next, safety++ )
134         if ((tnc->dirp == adp) && (!strcmp(tnc->name, aname)))
135             break;                              /* preexisting entry */
136         else if ( tnc->next == cm_data.nameHash[skey])  /* end of list */
137         {
138             tnc = NULL;
139             break;
140         }
141         else if (safety > NCSIZE) 
142         {
143             dnlcstats.cycles++;
144             lock_ReleaseWrite(&cm_dnlcLock);
145
146             if ( cm_debugDnlc )
147                 osi_Log0(afsd_logp, "DNLC cycle");
148             cm_dnlcPurge();
149             return;
150         }
151         
152     if ( !tnc )
153     {
154         new = 1;        /* entry does not exist, we are creating a new entry*/
155         tnc = GetMeAnEntry();
156     }
157     if ( tnc )
158     { 
159         tnc->dirp = adp;
160         tnc->vp = avc;
161         tnc->key = key;
162         memcpy (tnc->name, aname, ts-aname+1); /* include the NULL */
163
164         if ( new )      /* insert entry only if it is newly created */ 
165                 InsertEntry(tnc);
166
167     }
168     lock_ReleaseWrite(&cm_dnlcLock);
169
170     if ( !tnc)
171         cm_dnlcPurge();
172 }
173
174 /*
175 * if the scache entry is found, return it held
176 */
177 cm_scache_t *
178 cm_dnlcLookup (cm_scache_t *adp, cm_lookupSearch_t* sp)
179 {
180     cm_scache_t * tvc;
181     unsigned int key, skey;
182     char* aname = sp->searchNamep;
183     char *ts = aname;
184     cm_nc_t * tnc, * tnc_begin;
185     int safety, match;
186   
187     if (!cm_useDnlc)
188         return NULL;
189     if ( cm_debugDnlc ) 
190         osi_Log2(afsd_logp, "cm_dnlcLookup dir %x name %s", 
191                 adp, osi_LogSaveString(afsd_logp,aname));
192
193     dnlcHash( ts, key );  /* leaves ts pointing at the NULL */
194     if (ts - aname >= CM_AFSNCNAMESIZE) 
195         return NULL;
196
197     skey = key & (NHSIZE -1);
198
199     lock_ObtainRead(&cm_dnlcLock);
200     dnlcstats.lookups++;             /* Is a dnlcread lock sufficient? */
201
202     ts = 0;
203     tnc_begin = cm_data.nameHash[skey];
204     for ( tvc = (cm_scache_t *) 0, tnc = tnc_begin, safety=0; 
205           tnc; tnc = tnc->next, safety++ ) 
206     {
207         if (tnc->dirp == adp) 
208         {
209         if( cm_debugDnlc ) 
210             osi_Log1(afsd_logp,"Looking at [%s]",
211                      osi_LogSaveString(afsd_logp,tnc->name));
212
213             if ( sp->caseFold )         /* case insensitive */
214             {
215             match = cm_stricmp(tnc->name, aname);
216             if ( !match )       /* something matches */
217             {
218                 tvc = tnc->vp;
219                 ts = tnc->name;
220
221                 /* determine what type of match it is */
222                 if ( !strcmp(tnc->name, aname))
223                 {       
224                     /* exact match. */
225                     sp->ExactFound = 1;
226
227                     if( cm_debugDnlc )
228                         osi_Log1(afsd_logp,"DNLC found exact match [%s]",
229                                  osi_LogSaveString(afsd_logp,tnc->name));
230                     break;
231                 }
232                 else if ( cm_NoneUpper(tnc->name))
233                     sp->LCfound = 1;
234                 else if ( cm_NoneLower(tnc->name))
235                     sp->UCfound = 1;
236                 else    
237                     sp->NCfound = 1;
238                 /* Don't break here. We might find an exact match yet */
239             }
240             }
241             else                        /* case sensitive */
242             {
243             match = strcmp(tnc->name, aname);
244             if ( !match ) /* found a match */
245             {
246                 sp->ExactFound = 1;
247                 tvc = tnc->vp; 
248                 ts = tnc->name;
249                 break;
250             }
251             }
252         }
253         if (tnc->next == cm_data.nameHash[skey]) 
254     {                   /* end of list */
255             break;
256         }
257         else if (tnc->next == tnc_begin || safety > NCSIZE) 
258         {
259             dnlcstats.cycles++;
260             lock_ReleaseRead(&cm_dnlcLock);
261
262             if ( cm_debugDnlc ) 
263                 osi_Log0(afsd_logp, "DNLC cycle"); 
264             cm_dnlcPurge();
265             return(NULL);
266         }
267     }
268
269     if(cm_debugDnlc && ts) {
270         osi_Log3(afsd_logp, "DNLC matched [%s] for [%s] with vnode[%ld]",
271                  osi_LogSaveString(afsd_logp,ts),
272                  osi_LogSaveString(afsd_logp,aname),
273                  (long) tvc->fid.vnode);
274     }
275
276     if (!tvc) 
277         dnlcstats.misses++;     /* Is a dnlcread lock sufficient? */
278     else 
279     {
280         sp->found = 1;
281         sp->fid.vnode  = tvc->fid.vnode; 
282         sp->fid.unique = tvc->fid.unique;       
283     }
284     lock_ReleaseRead(&cm_dnlcLock);
285
286     if (tvc)
287         cm_HoldSCache(tvc);
288
289     if ( cm_debugDnlc && tvc ) 
290         osi_Log1(afsd_logp, "cm_dnlcLookup found %x", tvc);
291     
292     return tvc;
293 }
294
295
296 static int
297 RemoveEntry (cm_nc_t *tnc, unsigned int key)
298 {
299     if (!tnc->prev) /* things on freelist always have null prev ptrs */
300     {
301         osi_Log0(afsd_logp,"Bogus dnlc freelist");
302         return 1;   /* error */
303     }
304
305     if (tnc == tnc->next)  /* only one in list */
306         cm_data.nameHash[key] = (cm_nc_t *) 0;
307     else 
308     {
309         if (tnc == cm_data.nameHash[key])
310             cm_data.nameHash[key]  = tnc->next;
311         tnc->prev->next = tnc->next;
312         tnc->next->prev = tnc->prev;
313     }
314
315     memset(tnc, 0, sizeof(cm_nc_t));
316     tnc->magic = CM_DNLC_MAGIC;
317     return 0;     /* success */
318 }
319
320
321 void 
322 cm_dnlcRemove (cm_scache_t *adp, char *aname)
323 {
324     unsigned int key, skey, error=0;
325     int found= 0, safety;
326     char *ts = aname;
327     cm_nc_t *tnc, *tmp;
328   
329     if (!cm_useDnlc)
330         return ;
331
332     if ( cm_debugDnlc )
333         osi_Log2(afsd_logp, "cm_dnlcRemove dir %x name %s", 
334                 adp, osi_LogSaveString(afsd_logp,aname));
335
336     dnlcHash( ts, key );  /* leaves ts pointing at the NULL */
337     if (ts - aname >= CM_AFSNCNAMESIZE) 
338         return ;
339
340     skey = key & (NHSIZE -1);
341     lock_ObtainWrite(&cm_dnlcLock);
342     dnlcstats.removes++;
343
344     for (tnc = cm_data.nameHash[skey], safety=0; tnc; safety++) 
345     {
346         if ( (tnc->dirp == adp) && (tnc->key == key) 
347                         && !strcmp(tnc->name,aname) )
348         {
349             tmp = tnc->next;
350             error = RemoveEntry(tnc, skey);
351             if ( error )
352                 break;
353
354             tnc->next = cm_data.ncfreelist; /* insert entry into freelist */
355             cm_data.ncfreelist = tnc;
356             found = 1;          /* found at least one entry */
357
358             tnc = tmp;          /* continue down the linked list */
359         }
360         else if (tnc->next == cm_data.nameHash[skey]) /* end of list */
361             break;
362         else
363             tnc = tnc->next;
364         if ( safety > NCSIZE )
365         {
366             dnlcstats.cycles++;
367             lock_ReleaseWrite(&cm_dnlcLock);
368
369             if ( cm_debugDnlc ) 
370                 osi_Log0(afsd_logp, "DNLC cycle"); 
371             cm_dnlcPurge();
372             return;
373         }
374     }
375     lock_ReleaseWrite(&cm_dnlcLock);
376
377     if (!found && !error && cm_debugDnlc)
378         osi_Log0(afsd_logp, "cm_dnlcRemove name not found");
379
380     if ( error )
381         cm_dnlcPurge();
382 }
383
384 /* remove anything pertaining to this directory */
385 void 
386 cm_dnlcPurgedp (cm_scache_t *adp)
387 {
388     int i;
389     int err=0;
390
391     if (!cm_useDnlc)
392         return ;
393
394     if ( cm_debugDnlc )
395         osi_Log1(afsd_logp, "cm_dnlcPurgedp dir %x", adp);
396
397     lock_ObtainWrite(&cm_dnlcLock);
398     dnlcstats.purgeds++;
399
400     for (i=0; i<NCSIZE && !err; i++) 
401     {
402         if (cm_data.nameCache[i].dirp == adp ) 
403         {
404             if (cm_data.nameCache[i].prev) {
405                 err = RemoveEntry(&cm_data.nameCache[i], cm_data.nameCache[i].key & (NHSIZE-1));
406                 if (!err)
407                 {
408                     cm_data.nameCache[i].next = cm_data.ncfreelist;
409                     cm_data.ncfreelist = &cm_data.nameCache[i];
410                 }
411             } else {
412                 cm_data.nameCache[i].dirp = cm_data.nameCache[i].vp = (cm_scache_t *) 0;
413             }
414         }
415     }
416     lock_ReleaseWrite(&cm_dnlcLock);
417     if ( err )
418         cm_dnlcPurge();
419 }
420
421 /* remove anything pertaining to this file */
422 void 
423 cm_dnlcPurgevp (cm_scache_t *avc)
424 {
425     int i;
426     int err=0;
427
428     if (!cm_useDnlc)
429         return ;
430
431     if ( cm_debugDnlc )
432         osi_Log1(afsd_logp, "cm_dnlcPurgevp scache %x", avc);
433
434     lock_ObtainWrite(&cm_dnlcLock);
435     dnlcstats.purgevs++;
436
437     for (i=0; i<NCSIZE && !err ; i++) 
438     {
439         if (cm_data.nameCache[i].vp == avc) 
440         {
441             if (cm_data.nameCache[i].prev) {
442                 err = RemoveEntry(&cm_data.nameCache[i], cm_data.nameCache[i].key & (NHSIZE-1));
443                 if (!err)
444                 {
445                     cm_data.nameCache[i].next = cm_data.ncfreelist;
446                     cm_data.ncfreelist = &cm_data.nameCache[i];
447                 }
448             } else {
449                 cm_data.nameCache[i].dirp = cm_data.nameCache[i].vp = (cm_scache_t *) 0;
450             }
451         }
452     }
453     lock_ReleaseWrite(&cm_dnlcLock);
454     if ( err )
455         cm_dnlcPurge();
456 }
457
458 /* remove everything */
459 void cm_dnlcPurge(void)
460 {
461     int i;
462
463     if (!cm_useDnlc)
464         return ;
465
466     if ( cm_debugDnlc )
467         osi_Log0(afsd_logp, "cm_dnlcPurge");
468
469     lock_ObtainWrite(&cm_dnlcLock);
470     dnlcstats.purges++;
471     
472     cm_data.ncfreelist = (cm_nc_t *) 0;
473     memset (cm_data.nameCache, 0, sizeof(cm_nc_t) * NCSIZE);
474     memset (cm_data.nameHash, 0, sizeof(cm_nc_t *) * NHSIZE);
475     for (i=0; i<NCSIZE; i++)
476     {
477         cm_data.nameCache[i].magic = CM_DNLC_MAGIC;
478         cm_data.nameCache[i].next = cm_data.ncfreelist;
479         cm_data.ncfreelist = &cm_data.nameCache[i];
480     }
481     lock_ReleaseWrite(&cm_dnlcLock);
482    
483 }
484
485 /* remove everything referencing a specific volume */
486 /* is this function ever called? */
487 void
488 cm_dnlcPurgeVol(AFSFid *fidp)
489 {
490
491     if (!cm_useDnlc)
492         return ;
493
494     dnlcstats.purgevols++;
495     cm_dnlcPurge();
496 }
497
498 long
499 cm_dnlcValidate(void)
500 {
501     int i, purged = 0;
502     cm_nc_t * ncp;
503
504   retry:
505     // are all nameCache entries marked with the magic bit?
506     for (i=0; i<NCSIZE; i++)
507     {
508         if (cm_data.nameCache[i].magic != CM_DNLC_MAGIC) {
509             afsi_log("cm_dnlcValidate failure: cm_data.nameCache[%d].magic != CM_DNLC_MAGIC", i);
510             fprintf(stderr, "cm_dnlcValidate failure: cm_data.nameCache[%d].magic != CM_DNLC_MAGIC\n", i);
511             goto purge;
512         }
513         if (cm_data.nameCache[i].next &&
514             cm_data.nameCache[i].next->magic != CM_DNLC_MAGIC) {
515             afsi_log("cm_dnlcValidate failure: cm_data.nameCache[%d].next->magic != CM_DNLC_MAGIC", i);
516             fprintf(stderr, "cm_dnlcValidate failure: cm_data.nameCache[%d].next->magic != CM_DNLC_MAGIC\n", i);
517             goto purge;
518         }
519         if (cm_data.nameCache[i].prev &&
520             cm_data.nameCache[i].prev->magic != CM_DNLC_MAGIC) {
521             afsi_log("cm_dnlcValidate failure: cm_data.nameCache[%d].prev->magic != CM_DNLC_MAGIC", i);
522             fprintf(stderr, "cm_dnlcValidate failure: cm_data.nameCache[%d].prev->magic != CM_DNLC_MAGIC\n", i);
523             goto purge;
524         }
525         if (cm_data.nameCache[i].dirp &&
526             cm_data.nameCache[i].dirp->magic != CM_SCACHE_MAGIC) {
527             afsi_log("cm_dnlcValidate failure: cm_data.nameCache[%d].dirp->magic != CM_SCACHE_MAGIC", i);
528             fprintf(stderr, "cm_dnlcValidate failure: cm_data.nameCache[%d].dirp->magic != CM_SCACHE_MAGIC\n", i);
529             goto purge;
530         }
531         if (cm_data.nameCache[i].vp &&
532             cm_data.nameCache[i].vp->magic != CM_SCACHE_MAGIC) {
533             afsi_log("cm_dnlcValidate failure: cm_data.nameCache[%d].vp->magic != CM_SCACHE_MAGIC", i);
534             fprintf(stderr, "cm_dnlcValidate failure: cm_data.nameCache[%d].vp->magic != CM_SCACHE_MAGIC\n", i);
535             goto purge;
536         }
537     }
538
539     // are the contents of the hash table intact?
540     for (i=0; i<NHSIZE;i++) {
541         for (ncp = cm_data.nameHash[i]; ncp ; 
542              ncp = ncp->next != cm_data.nameHash[i] ? ncp->next : NULL) {
543             if (ncp->magic != CM_DNLC_MAGIC) {
544                 afsi_log("cm_dnlcValidate failure: ncp->magic != CM_DNLC_MAGIC");
545                 fprintf(stderr, "cm_dnlcValidate failure: ncp->magic != CM_DNLC_MAGIC\n");
546                 goto purge;
547             }
548             if (ncp->prev && ncp->prev->magic != CM_DNLC_MAGIC) {
549                 afsi_log("cm_dnlcValidate failure: ncp->prev->magic != CM_DNLC_MAGIC");
550                 fprintf(stderr, "cm_dnlcValidate failure: ncp->prev->magic != CM_DNLC_MAGIC\n");
551                 goto purge;
552             }
553             if (ncp->dirp && ncp->dirp->magic != CM_SCACHE_MAGIC) {
554                 afsi_log("cm_dnlcValidate failure: ncp->dirp->magic != CM_DNLC_MAGIC");
555                 fprintf(stderr, "cm_dnlcValidate failure: ncp->dirp->magic != CM_DNLC_MAGIC\n");
556                 goto purge;
557             }
558             if (ncp->vp && ncp->vp->magic != CM_SCACHE_MAGIC) {
559                 afsi_log("cm_dnlcValidate failure: ncp->vp->magic != CM_DNLC_MAGIC");
560                 fprintf(stderr, "cm_dnlcValidate failure: ncp->vp->magic != CM_DNLC_MAGIC\n");
561                 goto purge;
562             }
563         }
564     }
565
566     // is the freelist stable?
567     if ( cm_data.ncfreelist ) {
568         for (ncp = cm_data.ncfreelist, i = 0; ncp && i < NCSIZE; 
569              ncp = ncp->next != cm_data.ncfreelist ? ncp->next : NULL, i++) {
570             if (ncp->magic != CM_DNLC_MAGIC) {
571                 afsi_log("cm_dnlcValidate failure: ncp->magic != CM_DNLC_MAGIC");
572                 fprintf(stderr, "cm_dnlcValidate failure: ncp->magic != CM_DNLC_MAGIC\n");
573                 goto purge;
574             }
575             if (ncp->prev) {
576                 afsi_log("cm_dnlcValidate failure: ncp->prev != NULL");
577                 fprintf(stderr, "cm_dnlcValidate failure: ncp->prev != NULL\n");
578                 goto purge;
579             }
580             if (ncp->key) {
581                 afsi_log("cm_dnlcValidate failure: ncp->key != 0");
582                 fprintf(stderr, "cm_dnlcValidate failure: ncp->key != 0\n");
583                 goto purge;
584             }
585             if (ncp->dirp) {
586                 afsi_log("cm_dnlcValidate failure: ncp->dirp != NULL");
587                 fprintf(stderr, "cm_dnlcValidate failure: ncp->dirp != NULL\n");
588                goto purge;
589             }
590             if (ncp->vp) {
591                 afsi_log("cm_dnlcValidate failure: ncp->vp != NULL");
592                 fprintf(stderr, "cm_dnlcValidate failure: ncp->vp != NULL\n");
593                 goto purge;
594             }
595         }
596
597         if ( i == NCSIZE && ncp ) {
598             afsi_log("cm_dnlcValidate failure: dnlc freeList corrupted");
599             fprintf(stderr, "cm_dnlcValidate failure: dnlc freeList corrupted\n");
600             goto purge;
601         }
602     }
603     return 0;
604
605   purge:
606     if ( purged )
607         return -1;
608
609     afsi_log("cm_dnlcValidate information: purging");
610     fprintf(stderr, "cm_dnlcValidate information: purging\n");
611     cm_data.ncfreelist = (cm_nc_t *) 0;
612     memset (cm_data.nameCache, 0, sizeof(cm_nc_t) * NCSIZE);
613     memset (cm_data.nameHash, 0, sizeof(cm_nc_t *) * NHSIZE);
614     for (i=0; i<NCSIZE; i++)
615     {
616         cm_data.nameCache[i].magic = CM_DNLC_MAGIC;
617         cm_data.nameCache[i].next = cm_data.ncfreelist;
618         cm_data.ncfreelist = &cm_data.nameCache[i];
619     }
620     purged = 1;
621     goto retry;
622 }
623
624 void 
625 cm_dnlcInit(int newFile)
626 {
627     int i;
628
629     if (!cm_useDnlc)
630         return ;
631
632     if ( cm_debugDnlc )
633         osi_Log0(afsd_logp,"cm_dnlcInit");
634
635     memset (&dnlcstats, 0, sizeof(dnlcstats));
636
637     lock_InitializeRWLock(&cm_dnlcLock, "cm_dnlcLock");
638     if ( newFile ) {
639         lock_ObtainWrite(&cm_dnlcLock);
640         cm_data.ncfreelist = (cm_nc_t *) 0;
641         cm_data.nameCache = cm_data.dnlcBaseAddress;
642         memset (cm_data.nameCache, 0, sizeof(cm_nc_t) * NCSIZE);
643         cm_data.nameHash = (cm_nc_t **) (cm_data.nameCache + NCSIZE);
644         memset (cm_data.nameHash, 0, sizeof(cm_nc_t *) * NHSIZE);
645     
646         for (i=0; i<NCSIZE; i++)
647         {
648             cm_data.nameCache[i].magic = CM_DNLC_MAGIC;
649             cm_data.nameCache[i].next = cm_data.ncfreelist;
650             cm_data.ncfreelist = &cm_data.nameCache[i];
651         }
652         lock_ReleaseWrite(&cm_dnlcLock);
653     }
654 }
655
656 long 
657 cm_dnlcShutdown(void)
658 {
659     if ( cm_debugDnlc )
660         osi_Log0(afsd_logp,"cm_dnlcShutdown");
661
662     return 0;
663 }