Add caching and compression
[Samba/vfs_proxy.git] / source4 / ntvfs / proxy / lib / cache / cache.c
blob2e7a17f66cd6438e521f12962bd73f06ddb59ba6
1 /*
2 Unix SMB/PROXY cache engine.
4 Copyright (C) 2007 Sam Liddicott <sam@liddicott.com>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "includes.h"
21 #include "libcli/raw/libcliraw.h"
22 #include "libcli/smb_composite/smb_composite.h"
23 #include "auth/auth.h"
24 #include "auth/credentials/credentials.h"
25 #include "ntvfs/ntvfs.h"
26 #include "lib/util/dlinklist.h"
27 #include "param/param.h"
28 #include "cache.h"
29 #include <sys/types.h>
30 #include <unistd.h>
31 #include <sys/stat.h>
32 #include <fcntl.h>
33 #include "zconf.h"
34 #include "ntvfs/proxy/lib/compression/zlib.h"
36 /* like mkdir -p */
37 int mkpath(const char* at, const char* pathname, mode_t mode)
39 char path[MAXPATHLEN];
40 char* next;
41 int len;
42 struct stat stat_path;
44 strncpy(path, at, MAXPATHLEN);
45 len=strlen(path);
46 next=path+len;
47 if (len==0 || (path[len-1]!='/' && len < MAXPATHLEN-1)) {
48 strncat(path,"/",MAXPATHLEN - len - 1);
49 len=strlen(path);
50 next=path+len;
52 strncat(path, pathname, MAXPATHLEN - len - 1);
54 /* get to the non / portions of pathname */
55 while (*next=='/') next++;
56 next=strchr(next, '/');
58 while (! next || *next) {
59 if (next) *next=0;
60 if (stat(path, &stat_path)) {
61 /* doesn't exist, try to create it */
62 if (errno!=ENOENT) return -1;
63 if (mkdir(path, mode)) return -1;
64 } else if (! S_ISDIR(stat_path.st_mode)) {
65 /* exists, but not a directory */
66 errno=ENOTDIR;
67 return -1;
69 /* so it's a directory (now) */
70 if (! next) return 0;
71 *next='/';
72 while (*next=='/') next++;
73 next=strchr(next+1, '/');
75 return 0;
78 struct cache_context *new_cache_context(TALLOC_CTX * memctx, const char* root, const char *server, const char *share)
80 struct cache_context *context=talloc_zero(memctx, struct cache_context);
81 if (! context) return NULL;
83 context->root=talloc_strdup(context, root);
84 context->server=strlower_talloc(context, server);
85 /* share names are never case sensitive */
86 context->share=strlower_talloc(context, share);
87 context->prefix=talloc_asprintf(context,"%s/%s/", context->server, context->share);
88 DEBUG(5,("New cache context: root=%s, prefix=%s\n",
89 context->root, context->prefix));
90 return context;
93 void clear_cache_storage(struct cache_file_entry *cache)
95 // if (cache->store_name && *(cache->store_name)) {
96 // unlink(cache->store_name);
97 // talloc_free(cache->store_name);
98 // cache->store_name=NULL;
99 // }
102 /* op-lock has probably been broken. We can no-longer read-cache for this file
103 in fact the whole cache is invalid for this file now, but we can use it as
104 a reference for delta-compression syncs if the remote server supports it */
105 void cache_file_stale(struct cache_file_entry *cache)
107 if (cache) {
108 cache->status &= ~ CACHE_READ;
109 cache->validated_extent=0;
110 DEBUG(CACHE_DEBUG_LEVEL,("Cache file %s no longer good to read from\n",cache->cache_name));
114 void cache_close(struct cache_file_entry *cache) {
115 if (cache && cache->fd>=0) {
116 close(cache->fd);
117 cache->fd=-1;
121 void cache_beopen(struct cache_file_entry *cache) {
122 if (cache->fd<=0) cache_reopen(cache);
125 void cache_reopen(struct cache_file_entry *cache) {
126 cache_close(cache);
127 cache->fd=open(cache->pathname,O_RDWR | O_CREAT,S_IRWXU);
130 void cache_create(struct cache_file_entry *cache, int readahead_window)
132 if (cache->status != CACHE_NONE && cache->fd<0) {
133 mkpath(cache->context->root, cache->context->prefix, S_IRWXU);
134 cache->pathname=talloc_asprintf(cache, "%s/%s",
135 cache->context->root,
136 cache->cache_name);
137 cache_reopen(cache);
139 cache->readahead_window=readahead_window;
142 void cache_new_file(struct cache_file_entry * cache, struct proxy_file *f,
143 const char* filename,
144 int readahead_window, bool oplock)
146 cache->f=f;
147 cache->fd=-1;
148 cache->status=CACHE_FILL | CACHE_READ_AHEAD;
149 DEBUG(5,("NEW CACHE FILE %x oplock %x\n",cache->status,oplock));
150 if (oplock) cache->status|=CACHE_READ | CACHE_VALIDATE;
151 cache_create(cache, readahead_window);
154 void cache_file_state(struct cache_file_entry * cache, cache_state state)
156 cache->status=state;
159 static ssize_t flen(int fd)
161 struct stat stats;
163 if (fstat(fd, &stats)==0) {
164 return (ssize_t)stats.st_size;
166 return -1;
169 ssize_t cache_len(struct cache_file_entry *cache)
171 return flen(cache->fd);
174 void cache_validated(struct cache_file_entry *cache, off_t offset)
176 if (cache->validated_extent <= offset) {
177 cache->validated_extent=offset;
179 if (cache->readahead_extent < offset) {
180 cache->readahead_extent=offset;
184 void cache_save(struct cache_file_entry *cache, const uint8_t* data, ssize_t size, off_t offset)
186 if (cache && size > 0 && cache->status & CACHE_FILL) {
187 off_t len;
188 ssize_t written;
189 if (cache->fd < 0) return;
191 DEBUG(1,("Consider saving in cache\n"));
192 /* until we manage sparse caching, the offset must be within, or adjacent
193 * to what has been cached, without an intervening gap - as we have no way
194 * of encoding that the gap is not valid cache */
195 len=flen(cache->fd);
196 if (offset <= len+1 && lseek(cache->fd, offset, SEEK_SET)>=0) {
197 /* save cache value */
198 written=write(cache->fd, data, size);
199 /* only extend the validated extent if the cache is good for reading from */
200 if (cache->status & CACHE_READ && cache->validated_extent < (offset + size)
201 /* but don't extend if it would leave unvalidated gaps */
202 && cache->validated_extent >= offset)
203 cache->validated_extent=offset + size;
204 DEBUG(CACHE_DEBUG_LEVEL,("Save cache written at %d %d bytes\n",(int)offset,(int)written));
206 if (cache->readahead_extent < offset+size) {
207 cache->readahead_extent=offset+size;
209 } else DEBUG(CACHE_DEBUG_LEVEL,("No sparse cache support, ignoring write at %d which is beyond %d\n",(int)offset,(int)len));
210 } else DEBUG(CACHE_DEBUG_LEVEL,("No caching on file\n"));
213 ssize_t cache_raw_read(struct cache_file_entry *cache, uint8_t* data, off_t offset, ssize_t size)
215 return pread(cache->fd, data, size, offset);
218 /* This function should track changes in rawreadwrite.c/smb_raw_read_send */
219 NTSTATUS cache_smb_raw_read(struct cache_file_entry *cache,
220 struct ntvfs_module_context *ntvfs,
221 struct ntvfs_request *req,
222 union smb_read *rd,
223 ssize_t* validated)
225 /* Based on pvfs_read.c/pvfs-read */
226 ssize_t ret;
227 uint32_t maxcnt;
228 uint32_t mask;
229 off_t len;
231 if (validated) *validated=0;
233 DEBUG(CACHE_DEBUG_LEVEL,("Actually trying to read from the cache len %d offset %d\n",(int)rd->generic.in.maxcnt,(int)rd->generic.in.offset));
234 /* Can we actually read ahead on this file anyway? */
235 if (! cache) {
236 DEBUG(CACHE_DEBUG_LEVEL,("Not a valid cache\n"));
237 return NT_STATUS_UNSUCCESSFUL;
240 if (cache->fd < 0) {
241 return NT_STATUS_INVALID_HANDLE;
244 if (! (cache->status & CACHE_READ) ) {
245 DEBUG(CACHE_DEBUG_LEVEL,("Cache %s not in read mode: %x\n",cache->cache_name, cache->status));
246 return NT_STATUS_UNSUCCESSFUL;
249 /* mapping should have been done by caller */
250 if (rd->generic.level != RAW_READ_READX) {
251 return NT_STATUS_INVALID_LEVEL;
254 mask = SEC_FILE_READ_DATA;
255 if (rd->generic.in.read_for_execute) {
256 mask |= SEC_FILE_EXECUTE;
258 #warning need to check access mask
259 /* if (!(f->access_mask & mask)) {
260 return NT_STATUS_ACCESS_DENIED;
263 maxcnt = rd->generic.in.maxcnt;
264 if (maxcnt > UINT16_MAX && req->ctx->protocol < PROTOCOL_SMB2) {
265 maxcnt = 0;
268 len=flen(cache->fd);
269 /* if read would return 0 bytes then we must fail unless we know the cache is fully populated */
270 if (/*! fully-populated && */rd->generic.in.offset >= len) {
271 DEBUG(CACHE_DEBUG_LEVEL,("Can't read %d bytes from position %d which is %d beyond cache length %d\n",(int)maxcnt,(int)rd->generic.in.offset,(int)(maxcnt + rd->generic.in.offset - len),(int)len));
272 return NT_STATUS_UNSUCCESSFUL;
275 #warning need to check locks
276 /* er... I guess we need to be spying on locks, or something */
278 status = pvfs_check_lock(pvfs, f, req->smbpid,
279 rd->generic.in.offset,
280 maxcnt,
281 READ_LOCK);
282 if (!NT_STATUS_IS_OK(status)) {
283 return status;
287 ret = pread(cache->fd,
288 rd->generic.out.data,
289 maxcnt,
290 rd->generic.in.offset);
292 if (ret == -1) {
293 int e=errno;
294 /* Why do I assume it is running on a unix? */
295 DEBUG(CACHE_DEBUG_LEVEL,("Error %x Read %d bytes from cache offset %d\n",e,ret,(int)rd->generic.in.offset));
296 return map_nt_error_from_unix(e);
299 /* Does the cache need to do handle position tracking? */
300 /* f->handle->position = f->handle->seek_offset = rd->generic.in.offset + ret; */
302 rd->generic.out.nread = ret;
303 rd->generic.out.remaining = 0xFFFF;
304 rd->generic.out.compaction_mode = 0;
306 /* How much of what we have is validated cache? */
307 if (! validated); /* just yawn */
308 else if (cache->validated_extent < rd->generic.in.offset)
309 *validated=0;
310 else if (cache->validated_extent >= rd->generic.in.offset +
311 rd->generic.out.nread)
312 *validated=rd->generic.out.nread;
313 else *validated=cache->validated_extent - rd->generic.in.offset;
315 DEBUG(CACHE_DEBUG_LEVEL,("Cache successfully read %d bytes validated %d\n",(int)ret, (int)((validated)?(*validated):-1) ));
317 return NT_STATUS_OK;
320 /* Take MD5 checksum of part of cache file */
321 NTSTATUS cache_smb_raw_checksum(struct cache_file_entry *cache,
322 offset_t offset, ssize_t* length, uint8_t digest[16])
324 struct MD5Context context;
325 int ret=0;
326 ssize_t completed=0;
327 ssize_t remaining=*length;
328 #define buffer_size 4096
329 uint8_t buffer[buffer_size];
331 MD5Init(&context);
333 while(remaining>0) {
334 ret = pread(cache->fd, buffer, MIN(buffer_size, remaining), offset);
335 if (ret<=0) break;
336 completed+=ret;
337 offset+=ret;
338 remaining-=ret;
339 MD5Update(&context, buffer, ret);
342 MD5Final(digest, &context);
343 *length=completed;
345 if (length==0) return NT_STATUS_UNSUCCESSFUL;
346 return NT_STATUS_OK;
349 /* Set up caching. We want to pass a reference to the file we opened as
350 * part of the cache key. We see this note in interfaces.h:
351 NOTE: fname can also be a pointer to a
352 uint64_t file_id if create_options has the
353 NTCREATEX_OPTIONS_OPEN_BY_FILE_ID flag set
354 * which is nice! We need to notice! */
356 struct cache_file_entry * cache_filename_open(struct cache_context *cache_context,
357 struct proxy_file *f,
358 const char* filename,
359 bool oplock,
360 int readahead_window)
362 struct cache_file_entry *cache=talloc_zero(f, struct cache_file_entry);
364 if (cache) {
365 cache->context=talloc_reference(cache, cache_context);
366 cache->cache_name=talloc_asprintf(cache,"%s/cache-%d-f-%s",
367 cache->context->prefix,0,filename);
368 DEBUG(1,("Session %d open proxied for file: %s, cache %s RA %d\n",
369 0, filename, cache->cache_name, readahead_window));
371 cache_new_file(cache, f, cache->cache_name, readahead_window, oplock);
373 return cache;
376 struct cache_file_entry * cache_fileid_open(struct cache_context *cache_context,
377 struct proxy_file *f,
378 const uint64_t* id,
379 bool oplock,
380 int readahead_window)
383 struct cache_file_entry *cache=talloc_zero(f, struct cache_file_entry);
385 if (cache) {
386 cache->context=talloc_reference(cache, cache_context);
387 cache->cache_name=talloc_asprintf(cache,"%s/cache-%d-id-%lld",
388 cache->context->prefix,0,*id);
389 DEBUG(1,("Session %d open proxied for file-id: %lld, cache %s RA %d\n",
390 0, *id,cache->cache_name, readahead_window));
392 cache_new_file(cache, f, cache->cache_name, readahead_window, oplock);
394 return cache;