show all threads
[tridge/junkcode.git] / pread_cache.c
1 /*
2   pread_cache.c: a cache to map pread() calls to fread() to prevent small
3   reads on MacOSX network filesystems that don't do block cacheing
4
5   This is useful for Maya, which does very small reads (often 2 bytes or 4 bytes)
6   which when used with smbfs on MacOSX gives extremely poor performance. 
7
8   tridge@samba.org January 2008
9
10   released under GNU GPLv3 or later
11   
12   to compile on MacOSX:
13     gcc pread_cache.c -Wall -g -o pread_cache.dylib -dynamiclib 
14
15   usage:
16
17     DYLD_INSERT_LIBRARIES=$PWD/pread_cache.dylib DYLD_FORCE_FLAT_NAMESPACE=1 command
18
19 */
20 #include <sys/types.h>
21 #include <sys/uio.h>
22 #include <sys/mman.h>
23 #include <unistd.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <dlfcn.h>
28 #include <errno.h>
29
30 /* chosen to match the smbfs on MacOSX read size */
31 #define MAX_READ 61000
32
33 /* to keep the data structures simple, we only cache the low MAX_FILES file descriptors */
34 #define MAX_FILES 1024
35 static FILE *file_handles[MAX_FILES];
36 static unsigned char file_state[MAX_FILES];
37
38 #define STATE_NONE    0
39 #define STATE_CACHED  1
40 #define STATE_DISABLE 2
41
42 /* catch close to free up any existing caches */
43 int close(int fd)
44 {
45         static int (*close_orig)(int fd);
46         if (close_orig == NULL) {
47                 close_orig = dlsym(RTLD_NEXT, "close");
48                 if (close_orig == NULL) {
49                         abort();
50                 }
51         }
52         if (fd < 0 || fd >= MAX_FILES) {
53                 return close_orig(fd);
54         }
55
56         if (file_state[fd] == STATE_CACHED) {
57                 FILE *f;
58                 file_state[fd] = STATE_NONE;
59                 f = file_handles[fd];
60                 file_handles[fd] = NULL;
61                 /* the fclose() does an implied close() */
62                 return fclose(f);
63         }
64
65         file_state[fd] = STATE_NONE;
66         return close_orig(fd);
67 }
68
69 /*
70   catch pread() and if possible map to fread()
71  */
72 ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset)
73 {
74         static ssize_t (*pread_orig)(int d, void *buf, size_t nbytes, off_t offset);
75         static int pagesize;
76
77         if (pread_orig == NULL) {
78                 pread_orig = dlsym(RTLD_NEXT, "pread");
79                 if (pread_orig == NULL) {
80                         abort();
81                 }
82                 pagesize = getpagesize();
83         }
84
85         /* large reads and reads on out of range file descriptors are not cached */
86         if (nbytes >= MAX_READ || fd < 0 || fd >= MAX_FILES) {
87                 return pread_orig(fd, buf, nbytes, offset);
88         }
89
90         if (file_state[fd] == STATE_DISABLE) {
91                 return pread_orig(fd, buf, nbytes, offset);             
92         }
93
94         /* see if we already have a cache */
95         if (file_state[fd] == STATE_NONE) {
96                 void *ptr;
97
98                 /* see if the file was opened read-write. This relies on the mmap error codes */
99                 ptr = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, fd, 0);
100                 if (ptr != (void *)-1) {
101                         munmap(ptr, pagesize);
102                         file_state[fd] = STATE_DISABLE;
103                         return pread_orig(fd, buf, nbytes, offset);             
104                 }
105                 if (errno != EACCES) {
106                         file_state[fd] = STATE_DISABLE;
107                         return pread_orig(fd, buf, nbytes, offset);
108                 }
109
110                 file_handles[fd] = fdopen(fd, "r");
111                 if (file_handles[fd] == NULL) {
112                         file_state[fd] = STATE_DISABLE;
113                         return pread_orig(fd, buf, nbytes, offset);
114                 }
115
116                 /* ensure we have a big enough buffer */
117                 setvbuf(file_handles[fd], NULL, _IOFBF, MAX_READ);
118
119                 file_state[fd] = STATE_CACHED;
120         }
121
122         /* seek to the right place if need be */
123         if (ftello(file_handles[fd]) != offset &&
124             fseeko(file_handles[fd], offset, SEEK_SET) != 0) {
125                 return pread_orig(fd, buf, nbytes, offset);
126         }
127
128         return fread(buf, 1, nbytes, file_handles[fd]);
129 }
130