vlserver: prevent duplicate IPs via ChangeAddr
[openafs.git] / src / vlserver / vlprocs.c
index 5a5dc1b..0bd0c01 100644 (file)
@@ -45,7 +45,6 @@ struct vlheader cheader;      /* kept in network byte order */
 extern afs_uint32 HostAddress[];       /* host addresses kept in host byte order */
 int maxnservers;
 struct extentaddr *ex_addr[VL_MAX_ADDREXTBLKS] = { 0, 0, 0, 0 };
-static char rxinfo_str[128];   /* Need rxinfo string to be non-local */
 #define ABORT(c) { errorcode = (c); goto abort; }
 #undef END
 #define END(c) { errorcode = (c); goto end; }
@@ -90,14 +89,15 @@ static int IpAddrToRelAddr(afs_uint32 ipaddr, struct ubik_trans *atrans);
 static int ChangeIPAddr(afs_uint32 ipaddr1, afs_uint32 ipaddr2,
                        struct ubik_trans *atrans);
 
-char *
-rxinfo(struct rx_call *rxcall)
+#define AFS_RXINFO_LEN 128
+static char *
+rxinfo(char * str, struct rx_call *rxcall)
 {
     int code;
     register struct rx_connection *tconn;
-    char tname[64];
-    char tinst[64];
-    char tcell[64];
+    char tname[64] = "";
+    char tinst[64] = "";
+    char tcell[64] = "";
     afs_uint32 exp;
     struct in_addr hostAddr;
 
@@ -107,11 +107,14 @@ rxinfo(struct rx_call *rxcall)
        rxkad_GetServerInfo(rxcall->conn, NULL, &exp, tname, tinst, tcell,
                            NULL);
     if (!code)
-       sprintf(rxinfo_str, "%s %s%s%s%s%s", inet_ntoa(hostAddr), tname,
-               tinst?".":"", tinst?tinst:"", tcell?"@":"", tcell?tcell:"");
+       sprintf(str, "%s %s%s%s%s%s", inet_ntoa(hostAddr), tname,
+               (tinst[0] == '\0') ? "" : ".",
+               (tinst[0] == '\0') ? "" : tinst,
+               (tcell[0] == '\0') ? "" : "@",
+               (tcell[0] == '\0') ? "" : tcell);
     else
-       sprintf(rxinfo_str, "%s noauth", inet_ntoa(hostAddr));
-    return (rxinfo_str);
+       sprintf(str, "%s noauth", inet_ntoa(hostAddr));
+    return (str);
 }
 
 /* This is called to initialize the database, set the appropriate locks and make sure that the vldb header is valid */
@@ -180,6 +183,7 @@ SVL_CreateEntry(struct rx_call *rxcall, struct vldbentry *newentry)
     struct ubik_trans *trans;
     afs_int32 errorcode, blockindex;
     struct nvlentry tentry;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLCREATEENTRY);
     if (!afsconf_SuperUser(vldb_confdir, rxcall, NULL)) {
@@ -194,10 +198,9 @@ SVL_CreateEntry(struct rx_call *rxcall, struct vldbentry *newentry)
 
     VLog(1,
         ("OCreate Volume %d %s\n", newentry->volumeId[RWVOL],
-         rxinfo(rxcall)));
-    /* XXX shouldn't we check to see if the r/o volume is duplicated? */
-    if (newentry->volumeId[RWVOL]
-       && FindByID(trans, newentry->volumeId[RWVOL], RWVOL, &tentry, &errorcode)) {    /* entry already exists, we fail */
+         rxinfo(rxstr, rxcall)));
+    if (EntryIDExists(trans, newentry->volumeId, MAXTYPES, &errorcode)) {
+       /* at least one of the specified IDs already exists; we fail */
        errorcode = VL_IDEXIST;
        goto abort;
     } else if (errorcode) {
@@ -253,6 +256,7 @@ SVL_CreateEntryN(struct rx_call *rxcall, struct nvldbentry *newentry)
     struct ubik_trans *trans;
     afs_int32 errorcode, blockindex;
     struct nvlentry tentry;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLCREATEENTRYN);
     if (!afsconf_SuperUser(vldb_confdir, rxcall, NULL)) {
@@ -267,10 +271,9 @@ SVL_CreateEntryN(struct rx_call *rxcall, struct nvldbentry *newentry)
 
     VLog(1,
         ("Create Volume %d %s\n", newentry->volumeId[RWVOL],
-         rxinfo(rxcall)));
-    /* XXX shouldn't we check to see if the r/o volume is duplicated? */
-    if (newentry->volumeId[RWVOL]
-       && FindByID(trans, newentry->volumeId[RWVOL], RWVOL, &tentry, &errorcode)) {    /* entry already exists, we fail */
+         rxinfo(rxstr, rxcall)));
+    if (EntryIDExists(trans, newentry->volumeId, MAXTYPES, &errorcode)) {
+       /* at least one of the specified IDs already exists; we fail */
        errorcode = VL_IDEXIST;
        goto abort;
     } else if (errorcode) {
@@ -325,6 +328,7 @@ SVL_ChangeAddr(struct rx_call *rxcall, afs_uint32 ip1, afs_uint32 ip2)
 {
     struct ubik_trans *trans;
     afs_int32 errorcode;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLCHANGEADDR);
     if (!afsconf_SuperUser(vldb_confdir, rxcall, NULL)) {
@@ -335,7 +339,7 @@ SVL_ChangeAddr(struct rx_call *rxcall, afs_uint32 ip1, afs_uint32 ip2)
     if ((errorcode = Init_VLdbase(&trans, LOCKWRITE, this_op)))
        goto end;
 
-    VLog(1, ("Change Addr %u -> %u %s\n", ip1, ip2, rxinfo(rxcall)));
+    VLog(1, ("Change Addr %u -> %u %s\n", ip1, ip2, rxinfo(rxstr, rxcall)));
     if ((errorcode = ChangeIPAddr(ip1, ip2, trans)))
        goto abort;
     else {
@@ -360,6 +364,7 @@ SVL_DeleteEntry(struct rx_call *rxcall, afs_uint32 volid, afs_int32 voltype)
     struct ubik_trans *trans;
     afs_int32 blockindex, errorcode;
     struct nvlentry tentry;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLDELETEENTRY);
     if (!afsconf_SuperUser(vldb_confdir, rxcall, NULL))
@@ -371,7 +376,7 @@ SVL_DeleteEntry(struct rx_call *rxcall, afs_uint32 volid, afs_int32 voltype)
     if ((errorcode = Init_VLdbase(&trans, LOCKWRITE, this_op)))
        goto end;
 
-    VLog(1, ("Delete Volume %u %s\n", volid, rxinfo(rxcall)));
+    VLog(1, ("Delete Volume %u %s\n", volid, rxinfo(rxstr, rxcall)));
     blockindex = FindByID(trans, volid, voltype, &tentry, &errorcode);
     if (blockindex == 0) {     /* volid not found */
        if (!errorcode)
@@ -411,13 +416,15 @@ GetEntryByID(struct rx_call *rxcall,
     struct ubik_trans *trans;
     afs_int32 blockindex, errorcode;
     struct nvlentry tentry;
+    char rxstr[AFS_RXINFO_LEN];
 
     if ((voltype != -1) && (InvalidVoltype(voltype)))
        return VL_BADVOLTYPE;
     if ((errorcode = Init_VLdbase(&trans, LOCKREAD, this_op)))
        return errorcode;
 
-    VLog(5, ("GetVolumeByID %u (%d) %s\n", volid, new, rxinfo(rxcall)));
+    VLog(5, ("GetVolumeByID %u (%d) %s\n", volid, new,
+             rxinfo(rxstr, rxcall)));
     blockindex = FindByID(trans, volid, voltype, &tentry, &errorcode);
     if (blockindex == 0) {     /* entry not found */
        if (!errorcode)
@@ -498,6 +505,7 @@ GetEntryByName(struct rx_call *rxcall,
     struct ubik_trans *trans;
     afs_int32 blockindex, errorcode;
     struct nvlentry tentry;
+    char rxstr[AFS_RXINFO_LEN];
 
     if (NameIsId(volname)) {
        return GetEntryByID(rxcall, atoi(volname), -1, aentry, new, this_op);
@@ -506,7 +514,7 @@ GetEntryByName(struct rx_call *rxcall,
        return VL_BADNAME;
     if ((errorcode = Init_VLdbase(&trans, LOCKREAD, this_op)))
        return errorcode;
-    VLog(5, ("GetVolumeByName %s (%d) %s\n", volname, new, rxinfo(rxcall)));
+    VLog(5, ("GetVolumeByName %s (%d) %s\n", volname, new, rxinfo(rxstr, rxcall)));
     blockindex = FindByName(trans, volname, &tentry, &errorcode);
     if (blockindex == 0) {     /* entry not found */
        if (!errorcode)
@@ -565,9 +573,10 @@ afs_int32
 SVL_GetNewVolumeId(struct rx_call *rxcall, afs_uint32 Maxvolidbump,
                   afs_uint32 *newvolumeid)
 {
-    register afs_int32 errorcode;
+    afs_int32 errorcode;
     afs_uint32 maxvolumeid;
     struct ubik_trans *trans;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLGETNEWVOLUMEID);
     if (!afsconf_SuperUser(vldb_confdir, rxcall, NULL))
@@ -579,9 +588,14 @@ SVL_GetNewVolumeId(struct rx_call *rxcall, afs_uint32 Maxvolidbump,
     if ((errorcode = Init_VLdbase(&trans, LOCKWRITE, this_op)))
        goto end;
 
-    *newvolumeid = maxvolumeid = ntohl(cheader.vital_header.MaxVolumeId);
+    *newvolumeid = maxvolumeid = NextUnusedID(trans,
+       ntohl(cheader.vital_header.MaxVolumeId), Maxvolidbump, &errorcode);
+    if (errorcode) {
+       goto abort;
+    }
+
     maxvolumeid += Maxvolidbump;
-    VLog(1, ("GetNewVolid newmax=%u %s\n", maxvolumeid, rxinfo(rxcall)));
+    VLog(1, ("GetNewVolid newmax=%u %s\n", maxvolumeid, rxinfo(rxstr, rxcall)));
     cheader.vital_header.MaxVolumeId = htonl(maxvolumeid);
     if (write_vital_vlheader(trans)) {
        ABORT(VL_IO);
@@ -612,6 +626,8 @@ SVL_ReplaceEntry(struct rx_call *rxcall, afs_uint32 volid, afs_int32 voltype,
     int hashnewname;
     int hashVol[MAXTYPES];
     struct nvlentry tentry;
+    afs_uint32 checkids[MAXTYPES];
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLREPLACEENTRY);
     for (typeindex = 0; typeindex < MAXTYPES; typeindex++)
@@ -631,7 +647,7 @@ SVL_ReplaceEntry(struct rx_call *rxcall, afs_uint32 volid, afs_int32 voltype,
     if ((errorcode = Init_VLdbase(&trans, LOCKWRITE, this_op)))
        goto end;
 
-    VLog(1, ("OReplace Volume %u %s\n", volid, rxinfo(rxcall)));
+    VLog(1, ("OReplace Volume %u %s\n", volid, rxinfo(rxstr, rxcall)));
     /* find vlentry we're changing */
     blockindex = FindByID(trans, volid, voltype, &tentry, &errorcode);
     if (blockindex == 0) {     /* entry not found */
@@ -645,6 +661,29 @@ SVL_ReplaceEntry(struct rx_call *rxcall, afs_uint32 volid, afs_int32 voltype,
        ABORT(VL_BADENTRY);
     }
 
+    /* make sure none of the IDs we are changing to are already in use */
+    memset(&checkids, 0, sizeof(checkids));
+    for (typeindex = ROVOL; typeindex < MAXTYPES; typeindex++) {
+       if (tentry.volumeId[typeindex] != newentry->volumeId[typeindex]) {
+           checkids[typeindex] = newentry->volumeId[typeindex];
+       }
+    }
+    if (EntryIDExists(trans, checkids, MAXTYPES, &errorcode)) {
+       ABORT(VL_IDEXIST);
+    } else if (errorcode) {
+       goto abort;
+    }
+
+    /* make sure the name we're changing to doesn't already exist */
+    if (strcmp(newentry->name, tentry.name)) {
+       struct nvlentry tmp_entry;
+       if (FindByName(trans, newentry->name, &tmp_entry, &errorcode)) {
+           ABORT(VL_NAMEEXIST);
+       } else if (errorcode) {
+           goto abort;
+       }
+    }
+
     /* unhash volid entries if they're disappearing or changing.
      * Remember if we need to hash in the new value (we don't have to
      * rehash if volid stays same */
@@ -712,6 +751,7 @@ SVL_ReplaceEntryN(struct rx_call *rxcall, afs_uint32 volid, afs_int32 voltype,
     int hashnewname;
     int hashVol[MAXTYPES];
     struct nvlentry tentry;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLREPLACEENTRYN);
     for (typeindex = 0; typeindex < MAXTYPES; typeindex++)
@@ -731,7 +771,7 @@ SVL_ReplaceEntryN(struct rx_call *rxcall, afs_uint32 volid, afs_int32 voltype,
     if ((errorcode = Init_VLdbase(&trans, LOCKWRITE, this_op)))
        goto end;
 
-    VLog(1, ("Replace Volume %u %s\n", volid, rxinfo(rxcall)));
+    VLog(1, ("Replace Volume %u %s\n", volid, rxinfo(rxstr, rxcall)));
     /* find vlentry we're changing */
     blockindex = FindByID(trans, volid, voltype, &tentry, &errorcode);
     if (blockindex == 0) {     /* entry not found */
@@ -819,6 +859,7 @@ SVL_UpdateEntry(struct rx_call *rxcall,
     struct ubik_trans *trans;
     afs_int32 blockindex, errorcode;
     struct nvlentry tentry;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLUPDATEENTRY);
     if (!afsconf_SuperUser(vldb_confdir, rxcall, NULL))
@@ -830,7 +871,7 @@ SVL_UpdateEntry(struct rx_call *rxcall,
     if ((errorcode = Init_VLdbase(&trans, LOCKWRITE, this_op)))
        goto end;
 
-    VLog(1, ("Update Volume %u %s\n", volid, rxinfo(rxcall)));
+    VLog(1, ("Update Volume %u %s\n", volid, rxinfo(rxstr, rxcall)));
     blockindex = FindByID(trans, volid, voltype, &tentry, &errorcode);
     if (blockindex == 0) {     /* entry not found */
        if (!errorcode)
@@ -916,6 +957,7 @@ SVL_SetLock(struct rx_call *rxcall, afs_uint32 volid, afs_int32 voltype,
     afs_int32 timestamp, blockindex, errorcode;
     struct ubik_trans *trans;
     struct nvlentry tentry;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLSETLOCK);
     if (!afsconf_SuperUser(vldb_confdir, rxcall, NULL))
@@ -927,7 +969,7 @@ SVL_SetLock(struct rx_call *rxcall, afs_uint32 volid, afs_int32 voltype,
     if ((errorcode = Init_VLdbase(&trans, LOCKWRITE, this_op)))
        goto end;
 
-    VLog(1, ("SetLock Volume %u %s\n", volid, rxinfo(rxcall)));
+    VLog(1, ("SetLock Volume %u %s\n", volid, rxinfo(rxstr, rxcall)));
     blockindex = FindByID(trans, volid, voltype, &tentry, &errorcode);
     if (blockindex == NULLO) {
        if (!errorcode)
@@ -982,6 +1024,7 @@ SVL_ReleaseLock(struct rx_call *rxcall, afs_uint32 volid, afs_int32 voltype,
     afs_int32 blockindex, errorcode;
     struct ubik_trans *trans;
     struct nvlentry tentry;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLRELEASELOCK);
     if (!afsconf_SuperUser(vldb_confdir, rxcall, NULL))
@@ -993,7 +1036,7 @@ SVL_ReleaseLock(struct rx_call *rxcall, afs_uint32 volid, afs_int32 voltype,
     if ((errorcode = Init_VLdbase(&trans, LOCKWRITE, this_op)))
        goto end;
 
-    VLog(1, ("ReleaseLock Volume %u %s\n", volid, rxinfo(rxcall)));
+    VLog(1, ("ReleaseLock Volume %u %s\n", volid, rxinfo(rxstr, rxcall)));
     blockindex = FindByID(trans, volid, voltype, &tentry, &errorcode);
     if (blockindex == NULLO) {
        if (!errorcode)
@@ -1033,11 +1076,13 @@ SVL_ListEntry(struct rx_call *rxcall, afs_int32 previous_index,
     int errorcode;
     struct ubik_trans *trans;
     struct nvlentry tentry;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLLISTENTRY);
     if ((errorcode = Init_VLdbase(&trans, LOCKREAD, this_op)))
        return errorcode;
-    VLog(25, ("OListEntry index=%d %s\n", previous_index, rxinfo(rxcall)));
+    VLog(25, ("OListEntry index=%d %s\n", previous_index,
+              rxinfo(rxstr, rxcall)));
     *next_index = NextEntry(trans, previous_index, &tentry, count);
     if (*next_index)
        vlentry_to_vldbentry(&tentry, aentry);
@@ -1056,11 +1101,12 @@ SVL_ListEntryN(struct rx_call *rxcall, afs_int32 previous_index,
     int errorcode;
     struct ubik_trans *trans;
     struct nvlentry tentry;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLLISTENTRYN);
     if ((errorcode = Init_VLdbase(&trans, LOCKREAD, this_op)))
        return errorcode;
-    VLog(25, ("ListEntry index=%d %s\n", previous_index, rxinfo(rxcall)));
+    VLog(25, ("ListEntry index=%d %s\n", previous_index, rxinfo(rxstr, rxcall)));
     *next_index = NextEntry(trans, previous_index, &tentry, count);
     if (*next_index)
        vlentry_to_nvldbentry(&tentry, aentry);
@@ -1085,6 +1131,7 @@ SVL_ListAttributes(struct rx_call *rxcall,
     struct nvlentry tentry;
     struct vldbentry *Vldbentry = 0, *VldbentryFirst = 0, *VldbentryLast = 0;
     int pollcount = 0;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLLISTATTRIBUTES);
     vldbentries->bulkentries_val = 0;
@@ -1208,7 +1255,7 @@ SVL_ListAttributes(struct rx_call *rxcall,
     }
     VLog(5,
         ("ListAttrs nentries=%d %s\n", vldbentries->bulkentries_len,
-         rxinfo(rxcall)));
+         rxinfo(rxstr, rxcall)));
     return (ubik_EndTrans(trans));
 }
 
@@ -1223,6 +1270,7 @@ SVL_ListAttributesN(struct rx_call *rxcall,
     struct nvlentry tentry;
     struct nvldbentry *Vldbentry = 0, *VldbentryFirst = 0, *VldbentryLast = 0;
     int pollcount = 0;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLLISTATTRIBUTESN);
     vldbentries->nbulkentries_val = 0;
@@ -1348,7 +1396,7 @@ SVL_ListAttributesN(struct rx_call *rxcall,
     }
     VLog(5,
         ("NListAttrs nentries=%d %s\n", vldbentries->nbulkentries_len,
-         rxinfo(rxcall)));
+         rxinfo(rxstr, rxcall)));
     return (ubik_EndTrans(trans));
 }
 
@@ -1374,6 +1422,7 @@ SVL_ListAttributesN2(struct rx_call *rxcall,
     int namematchRWBK, namematchRO, thismatch;
     int matchtype = 0;
     char volumename[VL_MAXNAMELEN];
+    char rxstr[AFS_RXINFO_LEN];
 #ifdef HAVE_POSIX_REGEX
     regex_t re;
     int need_regfree = 0;
@@ -1615,7 +1664,7 @@ SVL_ListAttributesN2(struct rx_call *rxcall,
     } else {
        VLog(5,
             ("N2ListAttrs nentries=%d %s\n", vldbentries->nbulkentries_len,
-             rxinfo(rxcall)));
+             rxinfo(rxstr, rxcall)));
        return (ubik_EndTrans(trans));
     }
 }
@@ -1892,6 +1941,7 @@ SVL_GetStats(struct rx_call *rxcall,
 {
     register afs_int32 errorcode;
     struct ubik_trans *trans;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLGETSTATS);
 #ifdef notdef
@@ -1901,7 +1951,7 @@ SVL_GetStats(struct rx_call *rxcall,
 #endif
     if ((errorcode = Init_VLdbase(&trans, LOCKREAD, this_op)))
        return errorcode;
-    VLog(5, ("GetStats %s\n", rxinfo(rxcall)));
+    VLog(5, ("GetStats %s\n", rxinfo(rxstr, rxcall)));
     memcpy((char *)vital_header, (char *)&cheader.vital_header,
           sizeof(vital_vlheader));
     memcpy((char *)stats, (char *)&dynamic_statistics, sizeof(vldstats));
@@ -2346,11 +2396,12 @@ SVL_GetAddrsU(struct rx_call *rxcall,
     struct extentaddr *exp = 0;
     afsUUID tuuid;
     afs_uint32 *taddrp, taddr;
+    char rxstr[AFS_RXINFO_LEN];
 
     COUNT_REQ(VLGETADDRSU);
     addrsp->bulkaddrs_len = *nentries = 0;
     addrsp->bulkaddrs_val = 0;
-    VLog(5, ("GetAddrsU %s\n", rxinfo(rxcall)));
+    VLog(5, ("GetAddrsU %s\n", rxinfo(rxstr, rxcall)));
     if ((errorcode = Init_VLdbase(&trans, LOCKREAD, this_op)))
        return errorcode;
 
@@ -2691,10 +2742,40 @@ get_vldbupdateentry(struct ubik_trans *trans,
                    struct nvlentry *VlEntry)
 {
     int i, j, errorcode, serverindex;
+    afs_uint32 checkids[MAXTYPES];
+
+    /* check if any specified new IDs are already present in the db. Do
+     * this check before doing anything else, so we don't get a half-
+     * updated entry. */
+    memset(&checkids, 0, sizeof(checkids));
+    if (updateentry->Mask & VLUPDATE_RWID) {
+       checkids[RWVOL] = updateentry->spares3; /* rw id */
+    }
+    if (updateentry->Mask & VLUPDATE_READONLYID) {
+       checkids[ROVOL] = updateentry->ReadOnlyId;
+    }
+    if (updateentry->Mask & VLUPDATE_BACKUPID) {
+       checkids[BACKVOL] = updateentry->BackupId;
+    }
+
+    if (EntryIDExists(trans, checkids, MAXTYPES, &errorcode)) {
+       return VL_IDEXIST;
+    } else if (errorcode) {
+       return errorcode;
+    }
 
     if (updateentry->Mask & VLUPDATE_VOLUMENAME) {
+       struct nvlentry tentry;
+
        if (InvalidVolname(updateentry->name))
            return VL_BADNAME;
+
+       if (FindByName(trans, updateentry->name, &tentry, &errorcode)) {
+           return VL_NAMEEXIST;
+       } else if (errorcode) {
+           return errorcode;
+       }
+
        if ((errorcode = UnhashVolname(trans, blockindex, VlEntry)))
            return errorcode;
        strncpy(VlEntry->name, updateentry->name, sizeof(VlEntry->name));
@@ -3125,6 +3206,7 @@ ChangeIPAddr(afs_uint32 ipaddr1, afs_uint32 ipaddr2, struct ubik_trans *atrans)
     afs_int32 blockindex, count;
     int pollcount = 0;
     struct nvlentry tentry;
+    int ipaddr1_id = -1, ipaddr2_id = -1;
 
     if (!atrans)
        return VL_CREATEFAIL;
@@ -3159,22 +3241,44 @@ ChangeIPAddr(afs_uint32 ipaddr1, afs_uint32 ipaddr2, struct ubik_trans *atrans)
            for (mhidx = 0; mhidx < VL_MAXIPADDRS_PERMH; mhidx++) {
                if (!exp->ex_addrs[mhidx])
                    continue;
-               if (ntohl(exp->ex_addrs[mhidx]) == ipaddr1)
-                   break;
+               if (ntohl(exp->ex_addrs[mhidx]) == ipaddr1) {
+                   ipaddr1_id = i;
+               }
+               if (ipaddr2 != 0 && ntohl(exp->ex_addrs[mhidx]) == ipaddr2) {
+                   ipaddr2_id = i;
+               }
            }
-           if (mhidx < VL_MAXIPADDRS_PERMH) {
-               break;
+       } else {
+           if (HostAddress[i] == ipaddr1) {
+               exp = NULL;
+               ipaddr1_id = i;
+           }
+           if (ipaddr2 != 0 && HostAddress[i] == ipaddr2) {
+               ipaddr2_id = i;
            }
-       } else if (HostAddress[i] == ipaddr1) {
-           exp = NULL;
+       }
+
+       if (ipaddr1_id >= 0 && (ipaddr2 == 0 || ipaddr2_id >= 0)) {
+           /* we've either found both IPs already in the VLDB, or we found
+            * ipaddr1, and we're not going to find ipaddr2 because it's 0 */
            break;
        }
     }
 
-    if (i >= MAXSERVERID) {
+    if (ipaddr1_id < 0) {
        return VL_NOENT;        /* not found */
     }
 
+    if (ipaddr2_id >= 0 && ipaddr2_id != ipaddr1_id) {
+       char buf1[16], buf2[16];
+       VLog(0, ("Cannot change IP address from %s to %s because the latter "
+                "is in use by server id %d\n",
+                afs_inet_ntoa_r(htonl(ipaddr1), buf1),
+                afs_inet_ntoa_r(htonl(ipaddr2), buf2),
+                ipaddr2_id));
+       return VL_MULTIPADDR;
+    }
+
     /* If we are removing a server entry, a volume cannot
      * exist on the server. If one does, don't remove the
      * server entry: return error "volume entry exists".
@@ -3191,7 +3295,7 @@ ChangeIPAddr(afs_uint32 ipaddr1, afs_uint32 ipaddr2, struct ubik_trans *atrans)
            for (j = 0; j < NMAXNSERVERS; j++) {
                if (tentry.serverNumber[j] == BADSERVERID)
                    break;
-               if (tentry.serverNumber[j] == i) {
+               if (tentry.serverNumber[j] == ipaddr1_id) {
                    return VL_IDEXIST;
                }
            }
@@ -3237,12 +3341,12 @@ ChangeIPAddr(afs_uint32 ipaddr1, afs_uint32 ipaddr2, struct ubik_trans *atrans)
     }
 
     /* Now change the host address entry */
-    cheader.IpMappedAddr[i] = htonl(ipaddr2);
+    cheader.IpMappedAddr[ipaddr1_id] = htonl(ipaddr2);
     code =
-       vlwrite(atrans, DOFFSET(0, &cheader, &cheader.IpMappedAddr[i]),
+       vlwrite(atrans, DOFFSET(0, &cheader, &cheader.IpMappedAddr[ipaddr1_id]),
                (char *)
-               &cheader.IpMappedAddr[i], sizeof(afs_int32));
-    HostAddress[i] = ipaddr2;
+               &cheader.IpMappedAddr[ipaddr1_id], sizeof(afs_int32));
+    HostAddress[ipaddr1_id] = ipaddr2;
     if (code)
        return VL_IO;