bb11cbdf9285ac6046da4da2df884c1e9e365608
[openafs.git] / src / ubik / beacon.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 #include <afs/param.h>
11 #include <sys/types.h>
12 #ifdef AFS_NT40_ENV
13 #include <winsock2.h>
14 #include <time.h>
15 #else
16 #include <sys/file.h>
17 #include <sys/time.h>
18 #include <sys/socket.h>
19 #include <netinet/in.h>
20 #include <netdb.h>
21 #endif
22 #include <errno.h>
23 #include <lock.h>
24 #include <rx/xdr.h>
25 #include <rx/rx.h>
26 #include <rx/rx_multi.h>
27 #include <afs/cellconfig.h>
28 #ifndef AFS_NT40_ENV
29 #include <afs/afsutil.h>
30 #include <afs/netutils.h>
31 #endif
32
33 #define UBIK_INTERNALS
34 #include "ubik.h"
35 #include "ubik_int.h"
36
37 /* statics used to determine if we're the sync site */
38 static afs_int32 syncSiteUntil = 0;         /* valid only if amSyncSite */
39 int ubik_amSyncSite = 0;            /* flag telling if I'm sync site */
40 static nServers;                    /* total number of servers */
41 static char amIMagic=0;             /* is this host the magic host */
42 char amIClone=0;                    /* is this a clone which doesn't vote */
43 static char ubik_singleServer = 0;
44 extern struct rx_securityClass *rxnull_NewClientSecurityObject();
45 int (*ubik_CRXSecurityProc)();
46 char *ubik_CRXSecurityRock;
47 afs_int32 ubikSecIndex;
48 struct rx_securityClass     *ubikSecClass;
49
50
51 /* Module responsible for both deciding if we're currently the sync site,
52  * and keeping collecting votes so as to stay sync site.
53  *
54  * The basic module contacts all of the servers it can, trying to get them to vote
55  * for this server for sync site.  The vote request message (called a beacon message)
56  * also specifies until which time this site claims to be the sync site, if at all, thus enabling
57  * receiving sites to know how long the sync site guarantee is made for.
58  *
59  * Each  of these beacon messages is thus both a declaration of how long this site will
60  * remain sync site, and an attempt to extend that time by collecting votes for a later
61  * sync site extension.
62  *
63  * The voting module is responsible for choosing a reasonable time until which it promises
64  * not to vote for someone else.  This parameter (BIG seconds) is not actually passed in
65  * the interface (perhaps it should be?) but is instead a compile time constant that both
66  * sides know about.
67  
68  * The beacon and vote modules work intimately together; the vote module decides how long
69  * it should promise the beacon module its vote, and the beacon module takes all of these
70  * votes and decides for how long it is the synchronization site.
71  */
72
73 /* procedure called from debug rpc call to get this module's state for debugging */
74 ubeacon_Debug(aparm)
75 register struct ubik_debug *aparm; {
76     /* fill in beacon's state fields in the ubik_debug structure */
77     aparm->syncSiteUntil = syncSiteUntil;
78     aparm->nServers = nServers;
79 }
80
81 /* procedure that determines whether this site has enough current votes to remain sync site.
82  *  called from higher-level modules (everything but the vote module).
83  *
84  * If we're the sync site, check that our guarantees, obtained by the ubeacon_Interact
85  * light-weight process, haven't expired.  We're sync site as long as a majority of the
86  * servers in existence have promised us unexpired guarantees.  The variable ubik_syncSiteUntil
87  * contains the time at which the latest of the majority of the sync site guarantees expires
88  * (if the variable ubik_amSyncSite is true)
89  * This module also calls up to the recovery module if it thinks that the recovery module
90  * may have to pick up a new database (which offucr sif we lose the sync site votes).
91  */
92 ubeacon_AmSyncSite() {
93     register afs_int32 now;
94     register afs_int32 rcode;
95     
96     /* special case for fast startup */
97     if (nServers == 1 && !amIClone) {
98         return 1;       /* one guy is always the sync site */
99     }
100
101     if (ubik_amSyncSite == 0 || amIClone) rcode = 0;  /* if I don't think I'm the sync site, say so */
102     else {
103         now = FT_ApproxTime();
104         if (syncSiteUntil <= now) {         /* if my votes have expired, say so */
105             if (ubik_amSyncSite) ubik_dprint("Ubik: I am no longer the sync site\n");
106             ubik_amSyncSite = 0;
107             rcode = 0;
108         }
109         else {
110             rcode = 1;              /* otherwise still have the required votes */
111         }
112     }
113     if (rcode == 0) urecovery_ResetState(); /* force recovery to re-execute */
114     ubik_dprint("beacon: amSyncSite is %d\n", rcode);
115     return rcode;
116 }
117
118 /* setup server list; called with two parms, first is my address, second is list of other servers
119  * called only at initialization to set up the list of servers to contact for votes.  Just creates
120  * the server structure.  Note that there are two connections in every server structure, one for
121  * vote calls (which must always go through quickly) and one for database operations, which
122  * are subject to waiting for locks.  If we used only one, the votes would sometimes get
123  * held up behind database operations, and the sync site guarantees would timeout
124  * even though the host would be up for communication.
125  *
126  * The "magic" host is the one with the lowest internet address.  It is
127  * magic because its vote counts epsilon more than the others.  This acts
128  * as a tie-breaker when we have an even number of hosts in the system.
129  * For example, if the "magic" host is up in a 2 site system, then it
130  * is sync site.  Without the magic host hack, if anyone crashed in a 2
131  * site system, we'd be out of business.
132  */
133 ubeacon_InitServerListByInfo(ame, info, clones)
134     afs_int32 ame;
135     struct afsconf_cell *info;
136     char clones[];
137 {
138     afs_int32 code;
139
140     code = ubeacon_InitServerListCommon(ame, info, clones, 0);
141     return code;
142 }
143
144 ubeacon_InitServerList(ame, aservers)
145     afs_int32 ame;
146     register afs_int32 aservers[];
147 {
148     afs_int32 code;
149
150     code = ubeacon_InitServerListCommon(ame, (struct afsconf_cell *)0, 0,
151                                                         aservers);
152     return code;
153 }
154
155 ubeacon_InitServerListCommon(ame, info, clones, aservers)
156     afs_int32 ame;
157     struct afsconf_cell *info;
158     char clones[];
159     register afs_int32 aservers[];
160 {
161     register struct ubik_server *ts;
162     afs_int32 me = -1;
163     register afs_int32 servAddr;
164     register afs_int32 i, code;
165     afs_int32 magicHost;
166     struct ubik_server *magicServer;
167
168     /* verify that the addresses passed in are correct */
169     if (code = verifyInterfaceAddress(&ame, info, aservers))
170         return code;
171
172     /* get the security index to use, if we can */
173     if (ubik_CRXSecurityProc) {
174         i = (*ubik_CRXSecurityProc)(ubik_CRXSecurityRock, &ubikSecClass, &ubikSecIndex);
175     }
176     else i = 1;
177     if (i) {
178         /* don't have sec module yet */
179         ubikSecIndex = 0;
180         ubikSecClass = rxnull_NewClientSecurityObject();
181     }
182     magicHost = ntohl(ame);     /* do comparisons in host order */
183     magicServer = (struct ubik_server *) 0;
184
185     if (info) {
186         for (i = 0; i < info->numServers; i++) {
187             if (ntohl((afs_uint32) info->hostAddr[i].sin_addr.s_addr) ==
188                                                 ntohl((afs_uint32) ame)) {
189                 me = i;
190                 if (clones[i]) {
191                     amIClone = 1;
192                     magicHost = 0;
193                 }
194             }
195         }
196         nServers = 0;
197         for (i = 0; i < info->numServers; i++) {
198             if (i == me) continue;
199             ts = (struct ubik_server *) malloc(sizeof(struct ubik_server));
200             bzero(ts, sizeof(struct ubik_server));
201             ts->next = ubik_servers;
202             ubik_servers = ts;
203             ts->addr[0] = info->hostAddr[i].sin_addr.s_addr;
204             if (clones[i]) {
205                 ts->isClone = 1;
206             } else {
207                 if (!magicHost || 
208                 ntohl((afs_uint32) ts->addr[0]) < (afs_uint32) magicHost) {
209                     magicHost = ntohl(ts->addr[0]);
210                     magicServer = ts;
211                 }
212                 ++nServers;
213             }
214             /* for vote reqs */
215             ts->vote_rxcid = rx_NewConnection(info->hostAddr[i].sin_addr.s_addr,
216                                               ubik_callPortal, 
217                                               VOTE_SERVICE_ID, 
218                                               ubikSecClass, ubikSecIndex);
219             /* for disk reqs */
220             ts->disk_rxcid = rx_NewConnection(info->hostAddr[i].sin_addr.s_addr,
221                                               ubik_callPortal, 
222                                               DISK_SERVICE_ID, ubikSecClass, 
223                                               ubikSecIndex);                 
224             ts->up = 1;
225         }
226     } else {
227         i = 0;
228         while (servAddr = *aservers++) {
229             if (i >= MAXSERVERS) return UNHOSTS;            /* too many hosts */
230             ts = (struct ubik_server *) malloc(sizeof(struct ubik_server));
231             bzero(ts, sizeof(struct ubik_server));
232             ts->next = ubik_servers;
233             ubik_servers = ts;
234             ts->addr[0] = servAddr;     /* primary address in  net byte order */
235             ts->vote_rxcid = rx_NewConnection(servAddr, ubik_callPortal, 
236                 VOTE_SERVICE_ID, 
237                 ubikSecClass, ubikSecIndex);            /* for vote reqs */
238             ts->disk_rxcid = rx_NewConnection(servAddr, ubik_callPortal, 
239                 DISK_SERVICE_ID, ubikSecClass, 
240                 ubikSecIndex);                          /* for disk reqs */
241             ts->isClone = 0;                    /* don't know about clones */
242             ts->up = 1;
243             if (ntohl((afs_uint32) servAddr) < (afs_uint32) magicHost) {
244                 magicHost = ntohl(servAddr);
245                 magicServer = ts;
246             }
247             i++;
248         }
249     }
250     if (magicServer) magicServer->magic = 1;    /* remember for when counting votes */
251
252     if (!amIClone && !magicServer) amIMagic = 1;
253     if (info) {
254         if (!amIClone) 
255             ++nServers;         /* count this server as well as the remotes */
256     } else
257         nServers = i+1;         /* count this server as well as the remotes */
258
259     ubik_quorum = (nServers>>1)+1;      /* compute the majority figure */
260                                         /* send addrs to all other servers */
261     code = updateUbikNetworkAddress(ubik_host);
262     if ( code )
263         return code;            
264
265 /* Shoud we set some defaults for RX??
266     r_retryInterval = 2;        
267     r_nRetries = (RPCTIMEOUT/r_retryInterval);
268 */
269     if (info) {
270         if (!ubik_servers)              /* special case 1 server */
271             ubik_singleServer = 1;
272         if (nServers == 1 && !amIClone) {
273             ubik_amSyncSite = 1;        /* let's start as sync site */
274             syncSiteUntil = 0x7fffffff; /* and be it quite a while */
275         }
276     } else {
277        if (nServers == 1)               /* special case 1 server */
278             ubik_singleServer = 1;
279     }
280
281     if (ubik_singleServer) {    
282         if (!ubik_amSyncSite) ubik_dprint("Ubik: I am the sync site - 1 server\n");
283         ubik_amSyncSite = 1;
284         syncSiteUntil = 0x7fffffff; /* quite a while */
285     }
286     return 0;
287 }
288
289 /* main lwp loop for code that sends out beacons.  This code only runs while
290  * we're sync site or we want to be the sync site.  It runs in its very own light-weight
291  * process.
292  */
293 ubeacon_Interact() { 
294     register afs_int32 code;
295     struct timeval tt;
296     struct rx_connection *connections[MAXSERVERS];
297     struct ubik_server *servers[MAXSERVERS];
298     register afs_int32 i;
299     register struct ubik_server *ts;
300     afs_int32 temp, yesVotes, lastWakeupTime, oldestYesVote, syncsite;
301     struct ubik_tid ttid;
302     afs_int32 startTime;
303
304     /* loop forever getting votes */
305     lastWakeupTime = 0;     /* keep track of time we last started a vote collection */
306     while (1) {
307
308         /* don't wakeup more than every POLLTIME seconds */
309         temp = (lastWakeupTime + POLLTIME) - FT_ApproxTime();
310         /* don't sleep if last collection phase took too long (probably timed someone out ) */
311         if (temp > 0) {
312             if (temp > POLLTIME) temp = POLLTIME;
313             tt.tv_sec = temp;
314             tt.tv_usec = 0;
315             code = IOMGR_Select(0, 0, 0, 0, &tt);
316         }
317         else code = 0;
318
319         lastWakeupTime = FT_ApproxTime();   /* started a new collection phase */
320
321         if (ubik_singleServer) continue;    /* special-case 1 server for speedy startup */
322
323         if (!uvote_ShouldIRun()) continue;  /* if voter has heard from a better candidate than us, don't bother running */
324
325         /* otherwise we should run for election, or we're the sync site (and have already won);
326             send out the beacon packets */
327         /* build list of all up hosts (noticing dead hosts are running again
328             is a task for the recovery module, not the beacon module), and
329             prepare to send them an r multi-call containing the beacon message */
330         i = 0;      /* collect connections */
331         for(ts = ubik_servers; ts; ts=ts->next) {
332             if (ts->up && ts->addr[0] != ubik_host[0]) {
333                 servers[i] = ts;
334                 connections[i++] = ts->vote_rxcid;
335             }
336         }
337         servers[i] = (struct ubik_server *) 0;  /* end of list */
338         /* note that we assume in the vote module that we'll always get at least BIGTIME 
339             seconds of vote from anyone who votes for us, which means we can conservatively
340             assume we'll be fine until SMALLTIME seconds after we start collecting votes */
341         /* this next is essentially an expansion of rgen's ServBeacon routine */
342
343         ttid.epoch = ubik_epochTime;
344         if (ubik_dbase->flags & DBWRITING) {
345             /*
346              * if a write is in progress, we have to send the writeTidCounter
347              * which holds the tid counter of the write transaction , and not
348              * send the tidCounter value which holds the tid counter of the
349              * last transaction.
350              */
351             ttid.counter = ubik_dbase->writeTidCounter;
352           }
353         else
354             ttid.counter = ubik_dbase->tidCounter+1;
355
356         /* now analyze return codes, counting up our votes */
357         yesVotes = 0;               /* count how many to ensure we have quorum */
358         oldestYesVote = 0x3fffffff; /* time quorum expires */
359         syncsite= ubeacon_AmSyncSite();
360         startTime = FT_ApproxTime();
361         /*
362          * Don't waste time using mult Rx calls if there are no connections out there
363          */
364         if (i > 0) {
365             multi_Rx(connections, i) {
366                 multi_VOTE_Beacon(syncsite, startTime, &ubik_dbase->version, &ttid);
367                 temp = FT_ApproxTime();     /* now, more or less */
368                 ts = servers[multi_i];
369                 ts->lastBeaconSent = temp;
370                 code = multi_error;
371                 /* note that the vote time (the return code) represents the time
372                    the vote was computed, *not* the time the vote expires.  We compute
373                    the latter down below if we got enough votes to go with */
374                 if (code > 0) {
375                     ts->lastVoteTime = code;
376                     if (code < oldestYesVote) oldestYesVote = code;
377                     ts->lastVote = 1;
378                     if (!ts->isClone)
379                         yesVotes += 2;
380                     if (ts->magic) yesVotes++;  /* the extra epsilon */
381                     ts->up = 1; /* server is up (not really necessary: recovery does this for real) */
382                     ts->beaconSinceDown = 1;
383                     ubik_dprint("yes vote from host %s\n",afs_inet_ntoa(ts->addr[0]));
384                 }
385                 else if (code == 0) {
386                     ts->lastVoteTime = temp;
387                     ts->lastVote = 0;
388                     ts->beaconSinceDown = 1;
389                     ubik_dprint("no vote from %s\n", afs_inet_ntoa(ts->addr[0]));
390                 }
391                 else if (code < 0) {
392                     ts->up = 0;
393                     ts->beaconSinceDown = 0;
394                     urecovery_LostServer();
395                     ubik_dprint("time out from %s\n", afs_inet_ntoa(ts->addr[0]));
396                 }
397             } multi_End;
398         }
399         /* now call our own voter module to see if we'll vote for ourself.  Note that
400             the same restrictions apply for our voting for ourself as for our voting
401             for anyone else. */
402         i = SVOTE_Beacon((struct rx_connection *) 0, ubeacon_AmSyncSite(), startTime, &ubik_dbase->version, &ttid);
403         if (i) {
404             yesVotes += 2;
405             if (amIMagic) yesVotes++;   /* extra epsilon */
406             if (i < oldestYesVote) oldestYesVote = i;
407         }
408
409         /* now decide if we have enough votes to become sync site.
410             Note that we can still get enough votes even if we didn't for ourself. */
411         if (yesVotes > nServers) {  /* yesVotes is bumped by 2 or 3 for each site */
412             if (!ubik_amSyncSite) ubik_dprint("Ubik: I am the sync site\n");
413             ubik_amSyncSite = 1;
414             syncSiteUntil = oldestYesVote + SMALLTIME;
415             LWP_NoYieldSignal(&ubik_amSyncSite);
416         }
417         else {
418             if (ubik_amSyncSite) ubik_dprint("Ubik: I am no longer the sync site\n");
419             ubik_amSyncSite = 0;
420             urecovery_ResetState(); /* tell recovery we're no longer the sync site */
421         }
422
423     }   /* while loop */
424 }
425
426 /* 
427 * Input Param   : ame is the pointer to my IP address specified in the
428 *                 CellServDB file. aservers is an array containing IP 
429 *                 addresses of remote ubik servers. The array is 
430 *                 terminated by a zero address.
431 *
432 * Algorithm     : Verify that my IP addresses 'ame' does actually exist
433 *                 on this machine.  If any of my IP addresses are there 
434 *                 in the remote server list 'aserver', remove them from 
435 *                 this list.  Update global variable ubik_host[] with 
436 *                 my IP addresses.
437 *
438 * Return Values : 0 on success, non-zero on failure
439 */
440 static verifyInterfaceAddress(ame, info, aservers)
441     struct afsconf_cell *info;
442     afs_uint32 aservers[];      /* list of all possible server addresses */
443     afs_uint32 *ame;            /* one of my interface addr in net byte order */
444 {
445     afs_uint32  myAddr[UBIK_MAX_INTERFACE_ADDR], *servList, tmpAddr;
446     int         count, index, found, i, j, totalServers, start, end;
447
448     if (info)
449         totalServers = info->numServers;
450     else {                              /* count the number of servers */
451         for ( totalServers=0, servList = aservers; *servList; servList++)
452             totalServers++;
453     }
454
455 #ifdef AFS_NT40_ENV 
456     /* for now use getaddr(). use getAllAddr when implemented */
457     myAddr[0] =  rxi_getaddr();
458     count = (myAddr[0] != 0);
459 #else
460     if(AFSDIR_SERVER_NETRESTRICT_FILEPATH || AFSDIR_SERVER_NETINFO_FILEPATH) {
461       /*
462        * Find addresses we are supposed to register as per the netrestrict file
463        * if it exists, else just register all the addresses we find on this 
464        * host as returned by rx_getAllAddr (in NBO)
465        */
466       char reason[1024];
467       count=parseNetFiles(myAddr,NULL,NULL,UBIK_MAX_INTERFACE_ADDR,
468                           reason,AFSDIR_SERVER_NETINFO_FILEPATH,
469                           AFSDIR_SERVER_NETRESTRICT_FILEPATH);
470       if(count<0) {
471         ubik_print("ubik: Can't register any valid addresses:%s\n",reason);
472         ubik_print("Aborting..\n");
473         return UBADHOST;
474       }
475     }
476     else {
477       /* get all my interface addresses in net byte order */
478       count = rx_getAllAddr(myAddr, UBIK_MAX_INTERFACE_ADDR); 
479     }
480 #endif
481
482     if ( count <= 0 )           /* no address found */
483     {
484         ubik_print("ubik: No network addresses found, aborting..");
485         return UBADHOST;
486     }
487
488     /* verify that the My-address passed in by ubik is correct */
489     for ( j=0, found = 0; j < count; j++)
490     {
491         if ( *ame == myAddr[j] ) /* both in net byte order */
492         {
493             found = 1;
494             break;
495         }
496     }
497          
498     if ( !found )
499     {
500         ubik_print("ubik: primary address %s does not exist\n",
501                         afs_inet_ntoa(*ame));
502         return UBADHOST;
503     }
504
505     /* if any of my addresses are there in serverList, then
506     ** use that as my primary addresses : the higher level 
507     ** application screwed up in dealing with multihomed concepts
508     */
509     for ( j=0, found = 0; j < count; j++)
510     {
511         for ( i=0; i < totalServers; i++) {
512             if (info)
513                 tmpAddr = ntohl((afs_uint32) info->hostAddr[i].sin_addr.s_addr);
514             else 
515                 tmpAddr = aservers[i];
516             if ( myAddr[j] == tmpAddr) {
517                 *ame = tmpAddr;
518                 if (!info)
519                     aservers[i]  = 0 ; 
520                 found = 1;
521             }
522         }
523     }
524     if ( found )
525         ubik_print("Using %s as my primary address\n", afs_inet_ntoa(*ame) );
526
527     if (!info) {
528         /* get rid of servers which were purged because all 
529         ** those interface addresses are myself 
530         */
531         for ( start=0, end=totalServers-1; (start<end) ; start++, end--)
532         {
533             /* find the first zero entry from the beginning */
534             for ( ; (start < end) && ( aservers[start] ); start++);
535     
536             /* find the last non-zero entry from the end */
537             for ( ; (end >= 0) && ( !aservers[end] ); end-- );
538     
539             /* if there is nothing more to purge, exit from loop */
540             if ( start >= end ) break;
541
542             /* move the entry */
543             aservers[start] = aservers[end];
544             aservers[end]   = 0;                /* this entry was moved */
545         }
546     }
547         
548     /* update all my addresses in ubik_host in such a way 
549     ** that ubik_host[0] has the primary address 
550     */
551     ubik_host[0] = *ame;        
552     for ( j=0, i=1; j < count; j++)
553         if ( *ame != myAddr[j] )
554             ubik_host[i++] =  myAddr[j];
555
556     return 0;   /* return success */
557 }       
558
559
560 /* 
561 * Input Param   : ubik_host is an array containing all my IP addresses.
562 *
563 * Algorithm     : Do an RPC to all remote ubik servers infroming them 
564 *                 about my IP addresses. Get their IP addresses and
565 *                 update my linked list of ubik servers 'ubik_servers'
566 *
567 * Return Values : 0 on success, non-zero on failure
568 */
569 int
570 updateUbikNetworkAddress(ubik_host)
571 afs_uint32 ubik_host[UBIK_MAX_INTERFACE_ADDR];
572 {
573     int                 j, count, found, index, code = 0;
574     UbikInterfaceAddr   inAddr, outAddr;
575     struct rx_connection *conns[MAXSERVERS];
576     struct ubik_server  *ts, *server[MAXSERVERS];
577     char                buffer[32];
578
579     for ( count = 0, ts=ubik_servers; ts; count++, ts = ts->next )
580     {
581         conns[count]  = ts->disk_rxcid;
582         server[count] = ts;
583     }
584
585
586     /* inform all other servers only if there are more than one
587        database servers in the cell */
588
589     if ( count > 0) {
590         
591         for ( j=0; j < UBIK_MAX_INTERFACE_ADDR; j++)
592             inAddr.hostAddr[j] = ntohl(ubik_host[j]);
593
594     
595         /* do the multi-RX RPC to all other servers */
596         multi_Rx(conns, count) {
597             multi_DISK_UpdateInterfaceAddr(&inAddr, &outAddr);
598             ts = server[multi_i]; /* reply received from this server */
599             if ( !multi_error ) {
600                 if ( ts->addr[0] != htonl(outAddr.hostAddr[0]) ) {
601                     code = UBADHOST;
602                     strcpy(buffer, (char*)afs_inet_ntoa(ts->addr[0]));
603                     ubik_print("ubik:Two primary addresses for same server \
604                     %s %s\n", buffer, afs_inet_ntoa(htonl(outAddr.hostAddr[0])));
605                 }
606                 else {
607                     for ( j=1; j < UBIK_MAX_INTERFACE_ADDR; j++)
608                         ts->addr[j] = htonl(outAddr.hostAddr[j]);
609                 }
610             }
611             else if ( multi_error == RXGEN_OPCODE ) {/* pre 3.5 remote server */
612                 ubik_print("ubik server %s does not support UpdateInterfaceAddr RPC\n", afs_inet_ntoa(ts->addr[0]));
613             }
614             else if ( multi_error == UBADHOST ) {
615                 code = UBADHOST; /* remote CellServDB inconsistency */
616                 ubik_print("Inconsistent Cell Info on server: ");
617                 for ( j=0; j < UBIK_MAX_INTERFACE_ADDR && ts->addr[j]; j++)
618                     printf("%s ", afs_inet_ntoa(ts->addr[j]));
619                 printf("\n");
620                 fflush(stdout); fflush(stderr);     
621             }
622             else {
623                 ts->up= 0;      /* mark the remote server as down */
624             }
625         } multi_End;
626     }
627     return code;
628 }
629