2 * Copyright 2000, International Business Machines Corporation and others.
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
10 #include <afsconfig.h>
11 #include <afs/param.h>
16 #include <afs/pthread_glock.h>
18 #include "../afs/sysincludes.h"
19 #include "../afs/afsincludes.h"
21 #include <sys/types.h>
24 #include <sys/utime.h>
26 #include <WINNT/afssw.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
34 #include <arpa/nameser.h>
36 #endif /* AFS_AFSDB_ENV */
37 #endif /* AFS_NT40_ENV */
56 #include <afs/afsutil.h>
57 #include "cellconfig.h"
60 static ParseHostLine();
61 static ParseCellLine();
62 static afsconf_OpenInternal();
63 static afsconf_CloseInternal();
64 static afsconf_Reopen();
66 static struct afsconf_servPair serviceTable [] = {
71 { "afskauth", 7004, },
73 { "afserror", 7006, },
74 { "afsnanny", 7007, },
75 { "afsupdate", 7008, },
76 { "afsrmtsys", 7009, },
77 { "afsres", 7010, }, /* residency database for MR-AFS */
78 { "afsremio", 7011, }, /* remote I/O interface for MR-AFS */
79 { 0, 0 } /* insert new services before this spot */
83 * Basic Rule: we touch "<AFSCONF_DIR>/CellServDB" every time we change anything, so
84 * our code can tell if there is new info in the key files, the cell server db
85 * files or any of the other files (and reopen the thing) if the date on
89 /* return port number in network byte order in the low 16 bits of a long; return -1 if not found */
90 static afs_int32 afsconf_FindService(aname)
91 register char *aname; {
92 /* lookup a service name */
94 register struct afsconf_servPair *tsp;
96 #if defined(AFS_OSF_ENV) || defined(AFS_DEC_ENV)
97 ts = getservbyname(aname, "");
99 ts = getservbyname(aname, (char *) 0);
102 /* we found it in /etc/services, so we use this value */
103 return ts->s_port; /* already in network byte order */
106 /* not found in /etc/services, see if it is one of ours */
107 for(tsp = serviceTable;; tsp++) {
108 if (tsp->name == (char *) 0) return -1;
109 if (!strcmp(tsp->name, aname)) return htons(tsp->port);
113 static int TrimLine(abuffer)
121 if (!isspace(tc)) break;
125 strcpy(abuffer, tbuffer);
131 * IsClientConfigDirectory() -- determine if path matches well-known
132 * client configuration directory.
134 static int IsClientConfigDirectory(const char *path)
136 const char *cdir = AFSDIR_CLIENT_ETC_DIRPATH;
139 for (i = 0; cdir[i] != '\0' && path[i] != '\0'; i++) {
140 int cc = tolower(cdir[i]);
141 int pc = tolower(path[i]);
154 /* hit end of one or both; allow mismatch in existence of trailing slash */
155 if (cdir[i] != '\0') {
156 if ((cdir[i] != '\\' && cdir[i] != '/') || (cdir[i + 1] != '\0')) {
160 if (path[i] != '\0') {
161 if ((path[i] != '\\' && path[i] != '/') || (path[i + 1] != '\0')) {
167 #endif /* AFS_NT40_ENV */
170 static int afsconf_Check(adir)
171 register struct afsconf_dir *adir; {
174 register afs_int32 code;
177 /* NT client CellServDB has different file name than NT server or Unix */
178 if (IsClientConfigDirectory(adir->name)) {
179 strcompose(tbuffer, 256,
180 adir->name, "/", AFSDIR_CELLSERVDB_FILE_NTCLIENT, NULL);
182 strcompose(tbuffer, 256,
183 adir->name, "/", AFSDIR_CELLSERVDB_FILE, NULL);
186 strcompose(tbuffer, 256, adir->name, "/", AFSDIR_CELLSERVDB_FILE, NULL);
187 #endif /* AFS_NT40_ENV */
189 code = stat(tbuffer, &tstat);
193 /* did file change? */
194 if (tstat.st_mtime == adir->timeRead) {
197 /* otherwise file has changed, so reopen it */
198 return afsconf_Reopen(adir);
201 /* set modtime on file */
202 static afsconf_Touch(adir)
203 register struct afsconf_dir *adir; {
206 struct timeval tvp[2];
209 adir->timeRead = 0; /* just in case */
212 /* NT client CellServDB has different file name than NT server or Unix */
214 if (IsClientConfigDirectory(adir->name)) {
215 strcompose(tbuffer, 256,
216 adir->name, "/", AFSDIR_CELLSERVDB_FILE_NTCLIENT, NULL);
218 strcompose(tbuffer, 256,
219 adir->name, "/", AFSDIR_CELLSERVDB_FILE, NULL);
222 return _utime(tbuffer, NULL);
225 strcompose(tbuffer, 256, adir->name, "/", AFSDIR_CELLSERVDB_FILE, NULL);
226 gettimeofday(&tvp[0], NULL);
228 return utimes(tbuffer, tvp);
229 #endif /* AFS_NT40_ENV */
232 struct afsconf_dir *afsconf_Open(adir)
233 register char *adir; {
234 register struct afsconf_dir *tdir;
235 register afs_int32 code;
238 /* zero structure and fill in name; rest is done by internal routine */
239 tdir = (struct afsconf_dir *) malloc(sizeof(struct afsconf_dir));
240 memset(tdir, 0, sizeof(struct afsconf_dir));
241 tdir->name = (char *) malloc(strlen(adir)+1);
242 strcpy(tdir->name, adir);
244 code = afsconf_OpenInternal(tdir, 0, 0);
246 char *afsconf_path, *getenv(), afs_confdir[128];
249 /* Check global place only when local Open failed for whatever reason */
250 if (!(afsconf_path = getenv("AFSCONF"))) {
251 /* The "AFSCONF" environment (or contents of "/.AFSCONF") will be typically set to something like "/afs/<cell>/common/etc" where, by convention, the default files for "ThisCell" and "CellServDB" will reside; note that a major drawback is that a given afs client on that cell may NOT contain the same contents... */
256 if (!(home_dir = getenv("HOME"))) {
257 /* Our last chance is the "/.AFSCONF" file */
258 fp = fopen("/.AFSCONF", "r");
262 return (struct afsconf_dir *) 0;
264 fgets(afs_confdir, 128, fp);
269 sprintf(pathname, "%s/%s", home_dir, ".AFSCONF");
270 fp = fopen(pathname, "r");
272 /* Our last chance is the "/.AFSCONF" file */
273 fp = fopen("/.AFSCONF", "r");
277 return (struct afsconf_dir *) 0;
279 fgets(afs_confdir, 128, fp);
282 fgets(afs_confdir, 128, fp);
285 len = strlen(afs_confdir);
289 return (struct afsconf_dir *) 0;
291 if (afs_confdir[len-1] == '\n') {
292 afs_confdir[len-1] = 0;
294 afsconf_path = afs_confdir;
296 tdir->name = (char *) malloc(strlen(afsconf_path)+1);
297 strcpy(tdir->name, afsconf_path);
298 code = afsconf_OpenInternal(tdir, 0, 0);
303 return (struct afsconf_dir *) 0;
311 static int GetCellUnix(struct afsconf_dir *adir)
317 strcompose(tbuffer, 256, adir->name, "/", AFSDIR_THISCELL_FILE, NULL);
318 tf = fopen(tbuffer, "r");
320 rc = fscanf(tf, "%s", tbuffer);
322 adir->cellName = (char *) malloc(strlen(tbuffer)+1);
323 strcpy(adir->cellName, tbuffer);
335 static int GetCellNT(struct afsconf_dir *adir)
337 if (IsClientConfigDirectory(adir->name)) {
338 /* NT client config dir; ThisCell is in registry (no file). */
339 return afssw_GetClientCellName(&adir->cellName);
341 /* NT server config dir; works just like Unix */
342 return GetCellUnix(adir);
345 #endif /* AFS_NT40_ENV */
348 static int afsconf_OpenInternal(adir, cell, clones)
349 register struct afsconf_dir *adir;
354 register char *tp, *bp;
355 register struct afsconf_entry *curEntry;
356 register afs_int32 code;
358 char tbuffer[256], tbuf1[256];
361 /* figure out the cell name */
365 i = GetCellUnix(adir);
371 /* now parse the individual lines */
375 /* NT client/server have a CellServDB that is the same format as Unix.
376 * However, the NT client uses a different file name
378 if (IsClientConfigDirectory(adir->name)) {
379 /* NT client config dir */
380 strcompose(tbuffer, 256,
381 adir->name, "/", AFSDIR_CELLSERVDB_FILE_NTCLIENT, NULL);
383 /* NT server config dir */
384 strcompose(tbuffer, 256,
385 adir->name, "/", AFSDIR_CELLSERVDB_FILE, NULL);
388 strcompose(tbuffer, 256, adir->name, "/", AFSDIR_CELLSERVDB_FILE, NULL);
389 #endif /* AFS_NT40_ENV */
391 if (!stat(tbuffer, &tstat)) {
392 adir->timeRead = tstat.st_mtime;
397 strcpy(tbuf1, tbuffer);
398 tf = fopen(tbuffer, "r");
403 tp = fgets(tbuffer, sizeof(tbuffer), tf);
405 TrimLine(tbuffer); /* remove white space */
406 if (tbuffer[0] == 0 || tbuffer[0] == '\n') continue; /* empty line */
407 if (tbuffer[0] == '>') {
408 char linkedcell[MAXCELLCHARS];
409 /* start new cell item */
411 /* thread this guy on the list */
412 curEntry->next = adir->entries;
413 adir->entries = curEntry;
416 curEntry = (struct afsconf_entry *) malloc(sizeof(struct afsconf_entry));
417 memset(curEntry, 0, sizeof(struct afsconf_entry));
418 code = ParseCellLine(tbuffer, curEntry->cellInfo.name, linkedcell);
420 afsconf_CloseInternal(adir);
424 if (linkedcell[0] != '\0') {
425 curEntry->cellInfo.linkedCell =
426 (char *) malloc(strlen(linkedcell) + 1);
427 strcpy(curEntry->cellInfo.linkedCell, linkedcell);
431 /* new host in the current cell */
433 afsconf_CloseInternal(adir);
437 i = curEntry->cellInfo.numServers;
438 if (cell && !strcmp(cell, curEntry->cellInfo.name))
439 code = ParseHostLine(tbuffer, (char *) &curEntry->cellInfo.hostAddr[i], curEntry->cellInfo.hostName[i], &clones[i]);
441 code = ParseHostLine(tbuffer, (char *) &curEntry->cellInfo.hostAddr[i], curEntry->cellInfo.hostName[i], 0);
443 if (code == AFSCONF_SYNTAX) {
444 for (bp=tbuffer; *bp != '\n'; bp++) { /* Take out the <cr> from the buffer */
448 fprintf(stderr, "Can't properly parse host line \"%s\" in configuration file %s\n", tbuffer, tbuf1);
452 afsconf_CloseInternal(adir);
455 curEntry->cellInfo.numServers = ++i;
458 fclose(tf); /* close the file now */
460 /* end the last partially-completed cell */
462 curEntry->next = adir->entries;
463 adir->entries = curEntry;
466 /* now read the fs keys, if possible */
467 adir->keystr = (struct afsconf_keys *) 0;
468 afsconf_IntGetKeys(adir);
473 /* parse a line of the form
474 *"128.2.1.3 #hostname" or
475 *"[128.2.1.3] #hostname" for clones
476 * into the appropriate pieces.
478 static ParseHostLine(aline, addr, aname, aclone)
480 register struct sockaddr_in *addr;
484 register afs_int32 code;
488 if (aclone) *aclone = 1;
489 code = sscanf(aline, "[%d.%d.%d.%d] #%s", &c1, &c2, &c3, &c4, aname);
491 if (aclone) *aclone = 0;
492 code = sscanf(aline, "%d.%d.%d.%d #%s", &c1, &c2, &c3, &c4, aname);
494 if (code != 5) return AFSCONF_SYNTAX;
495 addr->sin_family = AF_INET;
497 tp = (char *) &addr->sin_addr;
505 /* parse a line of the form
506 * ">cellname [linkedcellname] [#comments]"
507 * into the appropriate pieces.
509 static ParseCellLine(aline, aname, alname)
510 register char *aline, *aname, *alname; {
512 code = sscanf(aline, ">%s %s", aname, alname);
513 if (code == 1) *alname = '\0';
515 if (*alname == '#') {
519 return (code > 0 ? 0 : AFSCONF_SYNTAX);
522 /* call aproc(entry, arock, adir) for all cells. Proc must return 0, or we'll stop early and return the code it returns */
523 afsconf_CellApply(adir, aproc, arock)
524 struct afsconf_dir *adir;
527 register struct afsconf_entry *tde;
528 register afs_int32 code;
530 for(tde=adir->entries; tde; tde=tde->next) {
531 code = (*aproc)(&tde->cellInfo, arock, adir);
541 afs_int32 afsconf_SawCell = 0;
543 afsconf_GetExtendedCellInfo(adir, acellName, aservice, acellInfo, clones)
544 struct afsconf_dir *adir;
547 struct afsconf_cell *acellInfo;
553 code = afsconf_GetCellInfo(adir, acellName, aservice, acellInfo);
560 cell = (char *) &acellInfo->name;
562 code = afsconf_OpenInternal(adir, cell, clones);
567 afsconf_GetAfsdbInfo(acellName, aservice, acellInfo)
570 struct afsconf_cell *acellInfo;
575 unsigned char answer[1024];
581 /* The resolver isn't always MT-safe.. Perhaps this ought to be
582 * replaced with a more fine-grained lock just for the resolver
586 len = res_search(acellName, C_IN, T_AFSDB, answer, sizeof(answer));
590 return AFSCONF_NOTFOUND;
592 p = answer + sizeof(HEADER); /* Skip header */
593 code = dn_expand(answer, answer + len, p, host, sizeof(host));
595 return AFSCONF_NOTFOUND;
596 strncpy(acellInfo->name, host, sizeof(acellInfo->name));
598 p += code + QFIXEDSZ; /* Skip name */
600 while (p < answer + len) {
603 code = dn_expand(answer, answer + len, p, host, sizeof(host));
605 return AFSCONF_NOTFOUND;
607 p += code; /* Skip the name */
608 type = (p[0] << 8) | p[1];
609 p += 4; /* Skip type and class */
610 ttl = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
611 p += 4; /* Skip the TTL */
612 size = (p[0] << 8) | p[1];
613 p += 2; /* Skip the size */
615 if (type == T_AFSDB) {
619 afsdb_type = (p[0] << 8) | p[1];
620 code = dn_expand(answer, answer+len, p+2, host, sizeof(host));
622 return AFSCONF_NOTFOUND;
624 if ((afsdb_type == 1) &&
625 (server_num < MAXHOSTSPERCELL) &&
626 /* Do we want to get TTL data for the A record as well? */
627 (he = gethostbyname(host))) {
629 memcpy(&ipaddr, he->h_addr, he->h_length);
630 acellInfo->hostAddr[server_num].sin_addr.s_addr = ipaddr;
631 strncpy(acellInfo->hostName[server_num], host,
632 sizeof(acellInfo->hostName[server_num]));
635 if (!minttl || ttl < minttl) minttl = ttl;
642 if (server_num == 0) /* No AFSDB records */
643 return AFSCONF_NOTFOUND;
644 acellInfo->numServers = server_num;
647 tservice = afsconf_FindService(aservice);
649 return AFSCONF_NOTFOUND; /* service not found */
650 for (i=0; i<acellInfo->numServers; i++) {
651 acellInfo->hostAddr[i].sin_port = tservice;
655 acellInfo->timeout = minttl ? (time(0) + minttl) : 0;
659 #endif /* AFS_AFSDB_ENV */
661 afsconf_GetCellInfo(adir, acellName, aservice, acellInfo)
662 struct afsconf_dir *adir;
665 struct afsconf_cell *acellInfo; {
666 register struct afsconf_entry *tce;
667 struct afsconf_entry *bestce;
668 register afs_int32 i;
676 if (adir) afsconf_Check(adir);
679 cnLen = strlen(tcell)+1;
680 lcstring (tcell, tcell, cnLen);
681 afsconf_SawCell = 1; /* will ignore the AFSCELL switch on future */
682 /* call to afsconf_GetLocalCell: like klog */
684 i = afsconf_GetLocalCell(adir, tbuffer, sizeof(tbuffer));
691 cnLen = strlen(tcell);
692 bestce = (struct afsconf_entry *) 0;
698 for(tce=adir->entries;tce;tce=tce->next) {
699 if (strcasecmp(tce->cellInfo.name, tcell) == 0) {
705 if (strlen(tce->cellInfo.name) < cnLen) continue; /* clearly wrong */
706 if (strncasecmp(tce->cellInfo.name, tcell, cnLen) == 0) {
707 if (bestce) ambig = 1; /* ambiguous unless we get exact match */
711 if (!ambig && bestce) {
712 *acellInfo = bestce->cellInfo; /* structure assignment */
714 tservice = afsconf_FindService(aservice);
717 return AFSCONF_NOTFOUND; /* service not found */
719 for(i=0;i<acellInfo->numServers;i++) {
720 acellInfo->hostAddr[i].sin_port = tservice;
723 acellInfo->timeout = 0;
730 return afsconf_GetAfsdbInfo(acellName, aservice, acellInfo);
732 return AFSCONF_NOTFOUND;
733 #endif /* AFS_AFSDB_ENV */
737 afsconf_GetLocalCell(adir, aname, alen)
738 register struct afsconf_dir *adir;
741 static int afsconf_showcell = 0;
748 * If a cell switch was specified in a command, then it should override the
749 * AFSCELL variable. If a cell was specified, then the afsconf_SawCell flag
750 * is set and the cell name in the adir structure is used.
751 * Read the AFSCELL var each time: in case it changes (unsetenv AFSCELL).
753 if ( !afsconf_SawCell && (afscell_path= getenv("AFSCELL")) ) {
754 if ( !afsconf_showcell ) {
755 fprintf(stderr, "Note: Operation is performed on cell %s\n", afscell_path);
756 afsconf_showcell = 1;
758 strncpy(aname, afscell_path, alen);
761 if (adir->cellName) {
762 strncpy(aname, adir->cellName, alen);
764 else code = AFSCONF_UNKNOWN;
772 struct afsconf_dir *adir; {
774 afsconf_CloseInternal(adir);
775 if (adir->name) free(adir->name);
781 static int afsconf_CloseInternal(adir)
782 register struct afsconf_dir *adir; {
783 register struct afsconf_entry *td, *nd;
784 register char *tname;
786 tname = adir->name; /* remember name, since that's all we preserve */
788 /* free everything we can find */
789 if (adir->cellName) free(adir->cellName);
790 for(td=adir->entries;td;td=nd) {
792 if (td->cellInfo.linkedCell)
793 free(td->cellInfo.linkedCell);
796 if (adir->keystr) free(adir->keystr);
799 memset(adir, 0, sizeof(struct afsconf_dir));
800 adir->name = tname; /* restore it */
804 static int afsconf_Reopen(adir)
805 register struct afsconf_dir *adir; {
806 register afs_int32 code;
807 code = afsconf_CloseInternal(adir);
808 if (code) return code;
809 code = afsconf_OpenInternal(adir, 0, 0);
813 /* called during opening of config file */
814 afsconf_IntGetKeys(adir)
815 struct afsconf_dir *adir;
819 struct afsconf_keys *tstr;
820 register afs_int32 code;
823 /* NT client config dir has no KeyFile; don't risk attempting open
824 * because there might be a random file of this name if dir is shared.
826 if (IsClientConfigDirectory(adir->name)) {
827 adir->keystr = ((struct afsconf_keys *)
828 malloc(sizeof(struct afsconf_keys)));
829 adir->keystr->nkeys = 0;
832 #endif /* AFS_NT40_ENV */
835 /* compute the key name and other setup */
837 strcompose(tbuffer, 256, adir->name, "/", AFSDIR_KEY_FILE, NULL);
838 tstr = (struct afsconf_keys *) malloc(sizeof (struct afsconf_keys));
842 fd = open(tbuffer, O_RDONLY);
848 code = read(fd, tstr, sizeof(struct afsconf_keys));
850 if (code < sizeof(afs_int32)) {
856 /* convert key structure to host order */
857 tstr->nkeys = ntohl(tstr->nkeys);
858 for(fd=0;fd<tstr->nkeys;fd++)
859 tstr->key[fd].kvno = ntohl(tstr->key[fd].kvno);
865 /* get keys structure */
866 afsconf_GetKeys(adir, astr)
867 struct afsconf_dir *adir;
868 struct afsconf_keys *astr;
870 register afs_int32 code;
873 code = afsconf_Check(adir);
875 return AFSCONF_FAILURE;
876 memcpy(astr, adir->keystr, sizeof(struct afsconf_keys));
882 afs_int32 afsconf_GetLatestKey(adir, avno, akey)
883 IN struct afsconf_dir *adir;
889 register struct afsconf_key *tk;
890 register afs_int32 best;
891 struct afsconf_key *bestk;
892 register afs_int32 code;
895 code = afsconf_Check(adir);
897 return AFSCONF_FAILURE;
898 maxa = adir->keystr->nkeys;
900 best = -1; /* highest kvno we've seen yet */
901 bestk = (struct afsconf_key *) 0; /* ptr to structure providing best */
902 for(tk = adir->keystr->key,i=0;i<maxa;i++,tk++) {
903 if (tk->kvno == 999) continue; /* skip bcrypt keys */
904 if (tk->kvno > best) {
909 if (bestk) { /* found any */
910 if (akey) memcpy(akey, bestk->key, 8); /* copy out latest key */
911 if (avno) *avno = bestk->kvno; /* and kvno to caller */
916 return AFSCONF_NOTFOUND; /* didn't find any keys */
919 /* get a particular key */
920 afsconf_GetKey(adir, avno, akey)
921 struct afsconf_dir *adir;
925 register int i, maxa;
926 register struct afsconf_key *tk;
927 register afs_int32 code;
930 code = afsconf_Check(adir);
932 return AFSCONF_FAILURE;
933 maxa = adir->keystr->nkeys;
935 for(tk = adir->keystr->key,i=0;i<maxa;i++,tk++) {
936 if (tk->kvno == avno) {
937 memcpy(akey, tk->key, 8);
944 return AFSCONF_NOTFOUND;
947 /* save the key structure in the appropriate file */
948 static SaveKeys(adir)
949 struct afsconf_dir *adir;
951 struct afsconf_keys tkeys;
953 register afs_int32 i;
956 memcpy(&tkeys, adir->keystr, sizeof(struct afsconf_keys));
958 /* convert it to net byte order */
959 for(i = 0; i<tkeys.nkeys; i++ )
960 tkeys.key[i].kvno = htonl(tkeys.key[i].kvno);
961 tkeys.nkeys = htonl(tkeys.nkeys);
963 /* rewrite keys file */
964 strcompose(tbuffer, 256, adir->name, "/", AFSDIR_KEY_FILE, NULL);
965 fd = open(tbuffer, O_RDWR | O_CREAT | O_TRUNC, 0600);
966 if (fd < 0) return AFSCONF_FAILURE;
967 i = write(fd, &tkeys, sizeof(tkeys));
968 if (i != sizeof(tkeys)) {
970 return AFSCONF_FAILURE;
972 if (close(fd) < 0) return AFSCONF_FAILURE;
976 afsconf_AddKey(adir, akvno, akey, overwrite)
977 struct afsconf_dir *adir;
978 afs_int32 akvno, overwrite;
981 register struct afsconf_keys *tk;
982 register struct afsconf_key *tkey;
983 register afs_int32 i;
990 if (akvno < 0 || akvno > 255) {
996 for(i=0, tkey = tk->key; i<tk->nkeys; i++, tkey++) {
997 if (tkey->kvno == akvno) {
1000 return AFSCONF_KEYINUSE;
1007 if (tk->nkeys >= AFSCONF_MAXKEYS) {
1009 return AFSCONF_FULL;
1011 tkey = &tk->key[tk->nkeys++];
1014 memcpy(tkey->key, akey, 8);
1016 afsconf_Touch(adir);
1021 /* this proc works by sliding the other guys down, rather than using a funny
1022 kvno value, so that callers can count on getting a good key in key[0].
1024 afsconf_DeleteKey(adir, akvno)
1025 struct afsconf_dir *adir;
1028 register struct afsconf_keys *tk;
1029 register struct afsconf_key *tkey;
1036 for(i=0, tkey = tk->key; i<tk->nkeys; i++, tkey++) {
1037 if (tkey->kvno == akvno) {
1044 return AFSCONF_NOTFOUND;
1047 /* otherwise slide the others down. i and tkey point at the guy to delete */
1048 for(;i<tk->nkeys-1; i++,tkey++) {
1049 tkey->kvno = (tkey+1)->kvno;
1050 memcpy(tkey->key, (tkey+1)->key, 8);
1054 afsconf_Touch(adir);