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 /* This code is experimental persistent disk cache support for the
11 Windows 95/DJGPP AFS client. It uses synchronous I/O and assumes
12 non-preemptible threads (which is the case in DJGPP), so it has
18 #include <afs/param.h>
22 #include <sys/errno.h>
27 cm_diskcache_t **diskHashTable; /* pointers to hash chains */
30 cm_diskcache_t *diskCBBuf;
32 extern int afs_diskCacheChunks;
33 /*extern int cm_diskCacheChunkSize;*/
34 extern long buf_blockSize;
35 long cm_diskCacheChunkSize;
36 extern char cm_cachePath[];
37 extern int cm_cachePathLen;
38 extern int cm_diskCacheEnabled;
41 int accessOrd = 0; /* monotonically increasing access ordinal */
43 int afs_dhashsize = 2048;
44 int openCacheFiles = 0;
46 char cacheInfoPath[128];
47 char cacheFileName[128];
51 #define MAX_OPEN_FILES 22
53 /* internal functions */
54 void diskcache_WriteCacheInfo(cm_diskcache_t *dcp);
56 cm_diskcache_t *diskcache_Find(cm_fid_t *fid, int chunk);
57 cm_diskcache_t *diskcache_Alloc(cm_fid_t *fid, int chunk, int dataVersion);
58 int diskcache_Read(cm_diskcache_t *dcp, char *buf, int size);
59 int diskcache_Write(cm_diskcache_t *dcp, char *buf, int size);
61 #define complain printf
63 #define OFFSET_TO_CHUNK(a) (LargeIntegerDivideByLong((a), cm_diskCacheChunkSize))
64 #define GEN_CACHE_DIR_NAME(name, path, i) \
65 sprintf(name, "%s\\D%07d", cm_cachePath, (i) / CACHE_FILES_PER_DIR)
66 #define GEN_CACHE_FILE_NAME(name, path, i) \
67 sprintf(name, "%s\\D%07d\\C%07d", path, (i) / CACHE_FILES_PER_DIR, \
68 (i) % CACHE_FILES_PER_DIR)
70 /* Initialize the disk cache */
80 struct stat cacheInfoStat, chunkStat;
81 cm_cacheInfoHdr_t hdr;
85 if (!cm_diskCacheEnabled)
88 cm_diskCacheChunkSize = buf_blockSize;
89 if (cm_diskCacheChunkSize % buf_blockSize != 0)
91 complain("Error: disk cache chunk size %d not a multiple of buffer size %d\n",
92 cm_diskCacheChunkSize, buf_blockSize);
93 return CM_ERROR_INVAL;
96 /* alloc mem for chunk file control blocks */
97 diskCBBuf = (cm_diskcache_t *) malloc(afs_diskCacheChunks * sizeof(cm_diskcache_t));
98 if (diskCBBuf == NULL)
99 return CM_ERROR_SPACE;
100 memset(diskCBBuf, 0, afs_diskCacheChunks * sizeof(cm_diskcache_t));
102 /* alloc mem for hash table pointers */
103 diskHashTable = (cm_diskcache_t **) malloc(afs_dhashsize * sizeof(cm_diskcache_t *));
104 if (diskHashTable == NULL)
105 return CM_ERROR_SPACE;
106 memset(diskHashTable, 0, afs_dhashsize*sizeof(cm_diskcache_t *));
109 QInit(&openFileList);
111 /*sprintf(cacheInfoPath, "%s\\%s", cm_cachePath, CACHE_INFO_FILE);*/
112 memset(cacheInfoPath, 0, 128);
113 DPRINTF("cm_cachePath=%s\n", cm_cachePath);
114 strncpy(cacheInfoPath, cm_cachePath, 50);
115 strcat(cacheInfoPath, "\\");
116 strcat(cacheInfoPath, CACHE_INFO_FILE);
117 DPRINTF("cacheInfoPath=%s\n", cacheInfoPath);
119 cacheInfo_fd = open(cacheInfoPath, O_RDWR | O_BINARY);
121 if (cacheInfo_fd < 0)
123 /* file not present */
124 return diskcache_New(); /* initialize new empty disk cache */
127 /* get stat of cache info file */
128 rc = fstat(cacheInfo_fd, &cacheInfoStat);
130 /* Check for valid header in cache info file */
131 rc = read(cacheInfo_fd, &hdr, sizeof(cm_cacheInfoHdr_t));
132 if (rc < sizeof(cm_cacheInfoHdr_t) ||
133 hdr.magic != CACHE_INFO_MAGIC)
134 /*hdrp = (cm_cacheInfoHdr_t *) tmpBuf;*/
137 return diskcache_New();
140 if (hdr.chunks != afs_diskCacheChunks ||
141 hdr.chunkSize != cm_diskCacheChunkSize)
143 /* print error message saying params don't match */
144 return CM_ERROR_INVAL;
147 chunkBuf = (char *) malloc(cm_diskCacheChunkSize);
148 if (chunkBuf == NULL)
149 return CM_ERROR_SPACE;
151 /* read metadata from cache info file into control blocks */
152 /* reconstruct hash chains based on fid, chunk */
153 for (i = 0; i < afs_diskCacheChunks; i++)
154 { /* for all cache chunks */
162 rc = read(cacheInfo_fd, &dcp->f, sizeof(struct fcache));
163 if (rc < sizeof(struct fcache))
165 /* print error message about reading cache info file */
166 /* this isn't the right error code for a read error */
167 return CM_ERROR_INVAL;
170 if (dcp->f.index != i)
171 return CM_ERROR_INVAL; /* index should match position in cache info file */
173 /* Try to open cache file. This chunk will be invalidated if we can't
174 find the file, the file is newer than the cache info file, the file
175 size doesn't match the cache info file, or the file's header is
177 GEN_CACHE_FILE_NAME(cacheFileName, cm_cachePath, i);
179 /*fd = open(cacheFileName, O_RDWR | O_BINARY);
180 if (fd < 0) invalid = 1;
183 rc = fstat(fd, &chunkStat);*/
184 rc = stat(cacheFileName, &chunkStat);
186 if (rc < 0) invalid = 1;
187 /*else if (cacheInfoStat.st_mtime < chunkStat.st_mtime + 120) invalid = 1;*/
188 else if (cacheInfoStat.st_mtime < chunkStat.st_mtime) invalid = 1;
189 /*else if (cacheInfoStat.st_mtime < dcp->f.modTime + 120) invalid = 1;*/
190 else if (cacheInfoStat.st_mtime < dcp->f.modTime) invalid = 1;
191 else if (cm_diskCacheChunkSize != chunkStat.st_size ||
192 dcp->f.chunkBytes != chunkStat.st_size) invalid = 1;
195 /*rc = read(fd, chunkBuf, cm_diskCacheChunkSize);
196 if (rc < 0) invalid = 1;*/
200 cacheFileHdrP = (cm_cacheFileHdr_t *) chunkBuf;
201 if (cacheFileHdrP->magic != CACHE_FILE_MAGIC ||
202 cacheFileHdrP->index != i)
215 /* Cache file seems to be valid */
218 DPRINTF("Found fid/chunk=%08x-%08x-%08x-%08x/%04d in slot=%d dcp=%x\n",
219 dcp->f.fid.cell, dcp->f.fid.volume, dcp->f.fid.vnode,
220 dcp->f.fid.unique, dcp->f.chunk, i, dcp);
221 /* Put control block in hash table */
222 index = DCHash(&dcp->f.fid, dcp->f.chunk);
223 /*osi_QAdd(&diskHashTable[index], &dcp->f.hashq);*/
225 /* add to head of hash list. (we should probably look at
226 ord here instead. use queues?) */
227 dcp->hash_next = diskHashTable[index];
228 dcp->hash_prev = NULL;
229 if (diskHashTable[index]) diskHashTable[index]->hash_prev = dcp;
230 diskHashTable[index] = dcp;
232 /* Add to LRU queue in access time order (lowest at tail) */
233 QAddOrd(&diskLRUList, &dcp->lruq, dcp->f.accessOrd);
239 /* Cache file is invalid */
241 /* Create the cache file with correct size */
242 memset(chunkBuf, 0, cm_diskCacheChunkSize);
243 /*cacheFileHdrP->magic = CACHE_FILE_MAGIC;
244 cacheFileHdrP->index = i;*/
246 if (fd != 0) close(fd);
247 /* Note that if the directory this file is supposed to be in doesn't
248 exist, the creat call will fail and we will return an error. */
249 /*fd = creat(cacheFileName, S_IRUSR|S_IWUSR);*/
250 fd = open(cacheFileName, O_RDWR | O_BINARY | O_CREAT | O_TRUNC,
252 if (fd < 0) return CM_ERROR_INVAL; /* couldn't create file */
253 rc = write(fd, chunkBuf, cm_diskCacheChunkSize);
254 if (rc < 0) /* ran out of space? */
255 return CM_ERROR_INVAL;
258 /* We consider an invalid chunk as empty, so we put it at tail of LRU */
259 memset(dcp, 0, sizeof(cm_diskcache_t));
260 dcp->f.accessOrd = 0;
261 dcp->f.states = DISK_CACHE_EMPTY;
263 dcp->f.chunkBytes = cm_diskCacheChunkSize;
264 /*osi_QAdd(diskLRUList, &dcp->lruq);*/
265 QAddOrd(&diskLRUList, &dcp->lruq, dcp->f.accessOrd); /* head is LRU */
267 /* write out cache info modifications */
268 lseek(cacheInfo_fd, -sizeof(struct fcache), SEEK_CUR);
269 write(cacheInfo_fd, &dcp->f, sizeof(struct fcache));
271 } /* for all cache chunks */
274 /*close(cacheInfo_fd);*/
275 fprintf(stderr, "\nFound %d of %d valid %d-byte blocks\n", validCount,
276 afs_diskCacheChunks, cm_diskCacheChunkSize);
281 /* create empty disk cache files */
282 /* assumes tables have already been malloc'd by diskcache_Init */
290 /*char cacheInfoPath[256];
291 char cacheFileName[256];*/
294 struct stat cacheInfoStat, chunkStat;
295 cm_cacheInfoHdr_t hdr;
298 sprintf(cacheInfoPath, "%s\\%s", cm_cachePath, CACHE_INFO_FILE);
299 /*cacheInfo_fd = creat(cacheInfoPath, S_IRUSR | S_IWUSR);*/
300 cacheInfo_fd = open(cacheInfoPath, O_RDWR | O_BINARY | O_CREAT | O_TRUNC,
302 if (cacheInfo_fd < 0)
304 complain("diskcache_New: Error creating cache info file in cache directory %s\n",
306 return CM_ERROR_INVAL;
309 /* write valid header */
310 hdr.magic = CACHE_INFO_MAGIC;
311 hdr.chunks = afs_diskCacheChunks;
312 hdr.chunkSize = cm_diskCacheChunkSize;
313 rc = write(cacheInfo_fd, (char *) &hdr, sizeof(cm_cacheInfoHdr_t));
315 return CM_ERROR_INVAL;
317 chunkBuf = (char *) malloc(cm_diskCacheChunkSize);
318 if (chunkBuf == NULL)
319 return CM_ERROR_SPACE;
320 memset(chunkBuf, 0, cm_diskCacheChunkSize);
322 for (i = 0; i < afs_diskCacheChunks; i++)
323 { /* for all cache chunks */
333 /* $$$: init mutex mx */
334 memset(dcp, 0, sizeof(cm_diskcache_t));
335 dcp->f.accessOrd = 0;
337 dcp->f.states = DISK_CACHE_EMPTY;
338 dcp->f.chunkBytes = cm_diskCacheChunkSize;
339 QAddT(&diskLRUList, &dcp->lruq, dcp->f.accessOrd); /* head is LRU */
340 rc = write(cacheInfo_fd, &dcp->f, sizeof(struct fcache));
342 if (i % CACHE_FILES_PER_DIR == 0)
344 GEN_CACHE_DIR_NAME(dirName, cm_cachePath, i);
345 rc = mkdir(dirName, S_IRUSR | S_IWUSR);
346 if (rc < 0 && errno != EEXIST)
348 complain("diskcache_New: Couldn't create cache directory %s\n", dirName);
349 return CM_ERROR_INVAL;
353 GEN_CACHE_FILE_NAME(cacheFileName, cm_cachePath, i);
354 /*fd = creat(cacheFileName, S_IRUSR | S_IWUSR);*/
355 fd = open(cacheFileName, O_RDWR | O_BINARY | O_CREAT | O_TRUNC,
360 complain("diskcache_New: Not enough space in cache path to create file %s\n",
363 complain("diskcache_New: Couldn't create cache file %s\n", cacheFileName);
364 return CM_ERROR_INVAL;
367 /*fileHdr.magic = CACHE_FILE_MAGIC;
369 rc = write(fd, &fileHdr, sizeof(cm_cacheFileHdr_t)); /* maybe we should write
373 rc = write(fd, chunkBuf, cm_diskCacheChunkSize);
379 complain("diskcache_New: Not enough space in cache path to write to file %s\n",
382 complain("diskcache_New: Couldn't write to cache file %s\n",
384 return CM_ERROR_INVAL;
388 } /* for all cache chunks */
392 /*close(cacheInfo_fd);*/
397 /* Get chunk from the cache or allocate a new chunk */
398 int diskcache_Get(cm_fid_t *fid, osi_hyper_t *offset, char *buf, int size, int *dataVersion, int *dataCount, cm_diskcache_t **dcpRet)
405 if (!cm_diskCacheEnabled)
411 chunk = OFFSET_TO_CHUNK(*offset); /* chunk number */
413 DPRINTF("diskcache_Get: fid/chunk=%08x-%08x-%08x-%08x/%04d\n",
414 fid->cell, fid->volume, fid->vnode, fid->unique, chunk);
416 dcp = diskcache_Find(fid, chunk);
419 rc = diskcache_Read(dcp, buf, size);
420 *dataVersion = dcp->f.dataVersion; /* update caller's data version */
428 dcp = diskcache_Alloc(fid, chunk, *dataVersion);
433 if (++updates >= CACHE_INFO_UPDATES_PER_WRITE)
436 diskcache_WriteCacheInfo(dcp); /* update cache info for this slot */
440 /*printf("diskcache_Get: returning dcp=%x\n", dcp);*/
445 /* Look for a file chunk in the cache */
446 cm_diskcache_t *diskcache_Find(cm_fid_t *fid, int chunk)
450 cm_diskcache_t *prev;
452 index = DCHash(fid, chunk);
453 dcp = diskHashTable[index];
458 if (cm_FidCmp(&dcp->f.fid, fid) == 0 && chunk == dcp->f.chunk)
460 dcp->f.accessOrd = accessOrd++;
461 /* Move it to the beginning of the list */
462 if (diskHashTable[index] != dcp)
464 assert(dcp->hash_prev->hash_next == dcp);
465 dcp->hash_prev->hash_next = dcp->hash_next;
468 assert(dcp->hash_next->hash_prev == dcp);
469 dcp->hash_next->hash_prev = dcp->hash_prev;
471 dcp->hash_next = diskHashTable[index];
472 dcp->hash_prev = NULL;
473 if (diskHashTable[index]) diskHashTable[index]->hash_prev = dcp;
474 diskHashTable[index] = dcp;
479 dcp = dcp->hash_next;
483 DPRINTF("diskcache_Find: fid/chunk=%08x-%08x-%08x-%08x/%04d slot=%d hash=%d dcp=%x\n",
484 fid->cell, fid->volume, fid->vnode, fid->unique, chunk, dcp->f.index, index, dcp);
486 DPRINTF("diskcache_Find: fid/chunk=%08x/%04d not found\n",
492 int diskcache_Read(cm_diskcache_t *dcp, char *buf, int size)
494 char cacheFileName[256];
499 GEN_CACHE_FILE_NAME(cacheFileName, cm_cachePath, dcp->f.index);
501 DPRINTF("diskcache_Read: filename=%s dcp=%x\n", cacheFileName,
504 /* For reads, we will use the fd if already open, but we won't leave
505 the file open. Note that if we use async I/O, we will need to
506 do locking to prevent someone from closing the file while I/O
507 is going on. But for now, all I/O is synchronous, and threads
508 are non-preemptible. */
510 if (dcp->openfd == 0)
512 fd = open(cacheFileName, O_RDWR | O_BINARY);
515 complain("diskcache_Read: Couldn't open cache file %s\n", cacheFileName);
525 complain("diskcache_Read: Couldn't open cache file %s\n", cacheFileName);
529 rc = read(fd, buf, size);
532 complain("diskcache_Read: Couldn't read cache file %s\n", cacheFileName);
533 close(fd); return -1;
537 close(fd); /* close it if we opened it */
538 return rc; /* bytes read */
541 /* Write out buffer to disk */
542 int diskcache_Update(cm_diskcache_t *dcp, char *buf, int size, int dataVersion)
544 if (!cm_diskCacheEnabled)
547 DPRINTF("diskcache_Update dcp=%x, dataVersion=%d\n", dcp, dataVersion);
548 diskcache_Write(dcp, buf, size);
549 /*diskcache_SetMRU(dcp);*/
550 dcp->f.dataVersion = dataVersion;
551 /*dcp->f.accessOrd = accessOrd++;*/
552 /*QMoveToTail(&diskLRUList, &dcp->lruq, dcp->f.accessOrd);*/
554 if (++updates >= CACHE_INFO_UPDATES_PER_WRITE)
557 diskcache_WriteCacheInfo(dcp); /* update cache info */
562 /* Allocate a new chunk file control block for this fid/chunk */
563 cm_diskcache_t *diskcache_Alloc(cm_fid_t *fid, int chunk, int dataVersion)
568 int stole=0, stolen_chunk, stolen_fid_unique;
570 /* Remove LRU elt. (head) from free list */
571 q = QServe(&diskLRUList);
575 dcp = (cm_diskcache_t *) MEM_TO_OBJ(cm_diskcache_t, lruq, q);
578 DPRINTF("diskcache_Alloc: fid/chunk=%08x/%04d allocation failed\n",
583 /* Use this element for this fid/chunk */
584 if (dcp->f.states == DISK_CACHE_USED)
586 /* Remove from old hash chain */
589 assert(dcp->hash_prev->hash_next == dcp);
590 dcp->hash_prev->hash_next = dcp->hash_next;
594 index = DCHash(&dcp->f.fid, dcp->f.chunk);
595 diskHashTable[index] = dcp->hash_next;
599 assert(dcp->hash_next->hash_prev == dcp);
600 dcp->hash_next->hash_prev = dcp->hash_prev;
604 stolen_chunk = dcp->f.chunk;
605 stolen_fid_unique = dcp->f.fid.unique;
608 memcpy(&dcp->f.fid, fid, sizeof(cm_fid_t));
609 dcp->f.chunk = chunk;
610 dcp->f.dataVersion = dataVersion;
611 dcp->f.accessOrd = accessOrd++;
612 dcp->f.states = DISK_CACHE_USED;
614 /* allocate at head of new hash chain */
615 index = DCHash(fid, chunk);
616 /*osi_QAddH(&diskHashTable[index], &dcp->hashq);*/
617 dcp->hash_next = diskHashTable[index];
618 dcp->hash_prev = NULL;
619 if (diskHashTable[index]) diskHashTable[index]->hash_prev = dcp;
620 diskHashTable[index] = dcp;
622 /* put at tail of queue */
623 QAddT(&diskLRUList, &dcp->lruq, dcp->f.accessOrd);
626 DPRINTF("diskcache_Alloc: fid/chunk=%08x/%04d (recyc fid/chunk=%08x/%04d) "
627 "slot=%d hash=%d dcp=%x\n",
628 fid->unique, chunk, stolen_fid_unique, stolen_chunk,
629 dcp->f.index, index, dcp);
631 DPRINTF("diskcache_Alloc: fid/chunk=%08x/%04d slot=%d hash=%d dcp=%x\n",
632 fid->unique, chunk, dcp->f.index, index, dcp);
636 /* Write this chunk to its disk file */
637 int diskcache_Write(cm_diskcache_t *dcp, /*int bufferNum,*/ char *buf, int size)
639 char cacheFileName[256];
647 DPRINTF("diskcache_Write\n");
649 /* write bytes of buf into chunk file */
650 GEN_CACHE_FILE_NAME(cacheFileName, cm_cachePath, dcp->f.index);
651 if (dcp->openfd == 0)
653 dcp->openfd = open(cacheFileName, O_RDWR | O_BINARY);
657 complain("diskcache_Write: Couldn't open cache file %s\n", cacheFileName);
663 /*lseek(dcp->openfd, bufferNum * buf_blockSize, SEEK_SET);*/
664 /* only write size bytes */
665 rc = write(dcp->openfd, buf, size);
668 complain("diskcache_Write: Couldn't write cache file %s\n", cacheFileName);
669 close(dcp->openfd); dcp->openfd = 0; return rc;
674 /* add to open file list */
675 QAddT(&openFileList, &dcp->openq, 0);
679 QMoveToTail(&openFileList, &dcp->openq, 0);
681 if (openCacheFiles >= MAX_OPEN_FILES)
683 /* close longest-open file */
684 q = QServe(&openFileList);
685 dcp = (cm_diskcache_t *) MEM_TO_OBJ(cm_diskcache_t, openq, q);
696 /* we accessed this chunk (hit on buffer read), so move to MRU */
697 void diskcache_Touch(cm_diskcache_t *dcp)
699 if (!cm_diskCacheEnabled || !dcp) return;
700 dcp->f.accessOrd = accessOrd++;
701 QMoveToTail(&diskLRUList, &dcp->lruq, dcp->f.accessOrd); /* tail is MRU */
704 /* invalidate this disk cache entry */
705 int diskcache_Invalidate(cm_diskcache_t *dcp)
707 /* We consider an invalid chunk as empty, so we put it at tail of LRU */
708 QRemove(&diskLRUList, &dcp->lruq);
710 dcp->f.accessOrd = 0;
711 dcp->f.states = DISK_CACHE_EMPTY;
713 memset(&dcp->f.fid, sizeof(cm_fid_t));
714 /*osi_QAdd(diskLRUList, &dcp->lruq);*/
715 QAddH(&diskLRUList, &dcp->lruq, dcp->f.accessOrd); /* head is LRU */
718 void diskcache_WriteCacheInfo(cm_diskcache_t *dcp)
720 /*char cacheInfoPath[256];
724 /*return; /* skip this for perf. testing */
725 /*sprintf(cacheInfoPath, "%s\\%s", cm_cachePath, CACHE_INFO_FILE);
726 cacheInfo_fd = open(cacheInfoPath, O_RDWR);*/
728 DPRINTF("diskcache_WriteCacheInfo\n");
730 lseek(cacheInfo_fd, dcp->f.index * sizeof(struct fcache) +
731 sizeof(cm_cacheInfoHdr_t), SEEK_SET);
733 rc = write(cacheInfo_fd, &dcp->f, sizeof(struct fcache));
735 complain("diskcache_WriteCacheInfo: Couldn't write cache info file, error=%d\n", errno);
736 /*fsync(cacheInfo_fd);*/
738 /*close(cacheInfo_fd);*/
741 void diskcache_Shutdown()
746 /* close cache info file */
747 close (cacheInfo_fd);
749 /* close all open cache files */
750 q = QServe(&openFileList);
753 dcp = (cm_diskcache_t *) MEM_TO_OBJ(cm_diskcache_t, openq, q);
756 q = QServe(&openFileList);
760 #endif /* DISKCACHE95 */