Code

git: remove $Id$ svn cruft
[fusedav.git] / src / filecache.c
1 /***
2   Copyright (c) 2004-2006 Lennart Poettering
4   Permission is hereby granted, free of charge, to any person
5   obtaining a copy of this software and associated documentation files
6   (the "Software"), to deal in the Software without restriction,
7   including without limitation the rights to use, copy, modify, merge,
8   publish, distribute, sublicense, and/or sell copies of the Software,
9   and to permit persons to whom the Software is furnished to do so,
10   subject to the following conditions:
12   The above copyright notice and this permission notice shall be
13   included in all copies or substantial portions of the Software.
15   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22   SOFTWARE.
23 ***/
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
29 #include <errno.h>
30 #include <string.h>
31 #include <limits.h>
32 #include <fcntl.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <assert.h>
36 #include <pthread.h>
37 #include <inttypes.h>
38 #include <limits.h>
40 #include <ne_props.h>
41 #include <ne_uri.h>
42 #include <ne_session.h>
43 #include <ne_utils.h>
44 #include <ne_socket.h>
45 #include <ne_auth.h>
46 #include <ne_dates.h>
47 #include <ne_basic.h>
49 #include "filecache.h"
50 #include "statcache.h"
51 #include "fusedav.h"
52 #include "session.h"
54 struct file_info {
55     char *filename;
56     int fd;
57     off_t server_length, length, present;
59     int readable;
60     int writable;
62     int modified;
64     int ref, dead;
66     pthread_mutex_t mutex;
68     /* This field is locked by files_mutex, not by file_info->mutex */
69     struct file_info *next;
70 };
72 static struct file_info *files = NULL;
73 static pthread_mutex_t files_mutex = PTHREAD_MUTEX_INITIALIZER;
75 static int file_cache_sync_unlocked(struct file_info *fi);
77 void* file_cache_get(const char *path) {
78     struct file_info *f, *r = NULL;
80     pthread_mutex_lock(&files_mutex);
82     for (f = files; f; f = f->next) {
84         pthread_mutex_lock(&f->mutex);
85         if (!f->dead && f->filename && !strcmp(path, f->filename)) {
86             f->ref++;
87             r = f;
88         }
89         pthread_mutex_unlock(&f->mutex);
91         if (r)
92             break;
93     }
95     pthread_mutex_unlock(&files_mutex);
96     return f;
97 }
99 static void file_cache_free_unlocked(struct file_info *fi) {
100     assert(fi && fi->dead && fi->ref == 0);
102     free(fi->filename);
104     if (fi->fd >= 0)
105         close(fi->fd);
107     pthread_mutex_destroy(&fi->mutex);
108     free(fi);
111 void file_cache_unref(void *f) {
112     struct file_info *fi = f;
113     assert(fi);
115     pthread_mutex_lock(&fi->mutex);
117     assert(fi->ref >= 1);
118     fi->ref--;
120     if (!fi->ref && fi->dead) {
121         file_cache_sync_unlocked(fi);
122         file_cache_free_unlocked(fi);
123     }
125     pthread_mutex_unlock(&fi->mutex);
128 static void file_cache_unlink(struct file_info *fi) {
129     struct file_info *s, *prev;
130     assert(fi);
132     pthread_mutex_lock(&files_mutex);
134     for (s = files, prev = NULL; s; s = s->next) {
135         if (s == fi) {
136             if (prev)
137                 prev->next = s->next;
138             else
139                 files = s->next;
141             break;
142         }
144         prev = s;
145     }
147     pthread_mutex_unlock(&files_mutex);
150 int file_cache_close(void *f) {
151     struct file_info *fi = f;
152     int r = 0;
153     assert(fi);
155     file_cache_unlink(f);
157     pthread_mutex_lock(&fi->mutex);
158     fi->dead = 1;
159     pthread_mutex_unlock(&fi->mutex);
161     return r;
164 void* file_cache_open(const char *path, int flags) {
165     struct file_info *fi = NULL;
166     char tempfile[PATH_MAX];
167     const char *length = NULL;
168     ne_request *req = NULL;
169     ne_session *session;
171     if (!(session = session_get(1))) {
172         errno = EIO;
173         goto fail;
174     }
176     if ((fi = file_cache_get(path))) {
177         if (flags & O_RDONLY || flags & O_RDWR) fi->readable = 1;
178         if (flags & O_WRONLY || flags & O_RDWR) fi->writable = 1;
179         return fi;
180     }
182     fi = malloc(sizeof(struct file_info));
183     memset(fi, 0, sizeof(struct file_info));
184     fi->fd = -1;
186     fi->filename = strdup(path);
188     snprintf(tempfile, sizeof(tempfile), "%s/fusedav-cache-XXXXXX", "/tmp");
189     if ((fi->fd = mkstemp(tempfile)) < 0)
190         goto fail;
191     unlink(tempfile);
193     req = ne_request_create(session, "HEAD", path);
194     assert(req);
196     if (ne_request_dispatch(req) != NE_OK) {
197         fprintf(stderr, "HEAD failed: %s\n", ne_get_error(session));
198         errno = ENOENT;
199         goto fail;
200     }
202     if (!(length = ne_get_response_header(req, "Content-Length")))
203         /* dirty hack, since Apache doesn't send the file size if the file is empty */
204         fi->server_length = fi->length = 0;
205     else
206         fi->server_length = fi->length = atoi(length);
208     ne_request_destroy(req);
210     if (flags & O_RDONLY || flags & O_RDWR) fi->readable = 1;
211     if (flags & O_WRONLY || flags & O_RDWR) fi->writable = 1;
213     pthread_mutex_init(&fi->mutex, NULL);
215     pthread_mutex_lock(&files_mutex);
216     fi->next = files;
217     files = fi;
218     pthread_mutex_unlock(&files_mutex);
220     fi->ref = 1;
222     return fi;
224 fail:
226     if (req)
227         ne_request_destroy(req);
229     if (fi) {
230         if (fi->fd >= 0)
231             close(fi->fd);
232         free(fi->filename);
233         free(fi);
234     }
236     return NULL;
239 static int load_up_to_unlocked(struct file_info *fi, off_t l) {
241     ne_content_range64 range;
242     ne_session *session;
244     assert(fi);
246     if (!(session = session_get(1))) {
247         errno = EIO;
248         return -1;
249     }
251     if (l > fi->server_length)
252         l = fi->server_length;
254     if (l <= fi->present)
255         return 0;
257     if (lseek(fi->fd, fi->present, SEEK_SET) != fi->present)
258         return -1;
260     range.start = fi->present;
261     range.end = l-1;
262     range.total = 0;
264     if (ne_get_range64(session, fi->filename, &range, fi->fd) != NE_OK) {
265         fprintf(stderr, "GET failed: %s\n", ne_get_error(session));
266         errno = ENOENT;
267         return -1;
268     }
270     fi->present = l;
271     return 0;
274 int file_cache_read(void *f, char *buf, size_t size, off_t offset) {
275     struct file_info *fi = f;
276     ssize_t r = -1;
278     assert(fi && buf && size);
280     pthread_mutex_lock(&fi->mutex);
282     if (load_up_to_unlocked(fi, offset+size) < 0)
283         goto finish;
285     if ((r = pread(fi->fd, buf, size, offset)) < 0)
286         goto finish;
288 finish:
290     pthread_mutex_unlock(&fi->mutex);
292     return r;
295 int file_cache_write(void *f, const char *buf, size_t size, off_t offset) {
296     struct file_info *fi = f;
297     ssize_t r = -1;
299     assert (fi);
301     pthread_mutex_lock(&fi->mutex);
303     if (!fi->writable) {
304         errno = EBADF;
305         goto finish;
306     }
308     if (load_up_to_unlocked(fi, offset) < 0)
309         goto finish;
311     if ((r = pwrite(fi->fd, buf, size, offset)) < 0)
312         goto finish;
314     if (offset+size > fi->present)
315         fi->present = offset+size;
317     if (offset+size > fi->length)
318         fi->length = offset+size;
320     fi->modified = 1;
322 finish:
323     pthread_mutex_unlock(&fi->mutex);
325     return r;
328 int file_cache_truncate(void *f, off_t s) {
329     struct file_info *fi = f;
330     int r;
332     assert(fi);
334     pthread_mutex_lock(&fi->mutex);
336     fi->length = s;
337     r = ftruncate(fi->fd, fi->length);
339     pthread_mutex_unlock(&fi->mutex);
341     return r;
344 int file_cache_sync_unlocked(struct file_info *fi) {
345     int r = -1;
346     ne_session *session;
348     assert(fi);
350     if (!fi->writable) {
351         errno = EBADF;
352         goto finish;
353     }
355     if (!fi->modified) {
356         r = 0;
357         goto finish;
358     }
360     if (load_up_to_unlocked(fi, (off_t) -1) < 0)
361         goto finish;
363     if (lseek(fi->fd, 0, SEEK_SET) == (off_t)-1)
364         goto finish;
366     if (!(session = session_get(1))) {
367         errno = EIO;
368         goto finish;
369     }
371     if (ne_put(session, fi->filename, fi->fd)) {
372         fprintf(stderr, "PUT failed: %s\n", ne_get_error(session));
373         errno = ENOENT;
374         goto finish;
375     }
377     stat_cache_invalidate(fi->filename);
378     dir_cache_invalidate_parent(fi->filename);
380     r = 0;
382 finish:
384     return r;
387 int file_cache_sync(void *f) {
388     struct file_info *fi = f;
389     int r = -1;
390     assert(fi);
392     pthread_mutex_lock(&fi->mutex);
393     r = file_cache_sync_unlocked(fi);
394     pthread_mutex_unlock(&fi->mutex);
396     return r;
399 int file_cache_close_all(void) {
400     int r = 0;
402     pthread_mutex_lock(&files_mutex);
404     while (files) {
405         struct file_info *fi = files;
407         pthread_mutex_lock(&fi->mutex);
408         fi->ref++;
409         pthread_mutex_unlock(&fi->mutex);
411         pthread_mutex_unlock(&files_mutex);
412         file_cache_close(fi);
413         file_cache_unref(fi);
414         pthread_mutex_lock(&files_mutex);
415     }
417     pthread_mutex_unlock(&files_mutex);
419     return r;
422 off_t file_cache_get_size(void *f) {
423     struct file_info *fi = f;
425     assert(fi);
427     return fi->length;