Inital commit
[fusedav.git] / src / filecache.c
blob429385d63e25118b7aa1035bd98bd46ea65b251f
1 #define _XOPEN_SOURCE 500
3 #include <errno.h>
4 #include <string.h>
5 #include <limits.h>
6 #include <fcntl.h>
7 #include <stdlib.h>
8 #include <unistd.h>
9 #include <assert.h>
10 #include <pthread.h>
12 #include <ne_props.h>
13 #include <ne_uri.h>
14 #include <ne_session.h>
15 #include <ne_utils.h>
16 #include <ne_socket.h>
17 #include <ne_auth.h>
18 #include <ne_dates.h>
19 #include <ne_basic.h>
21 #include "filecache.h"
22 #include "fusedav.h"
23 #include "session.h"
24 #include "statcache.h"
26 struct file_info {
27 char *filename;
28 int fd;
29 off_t server_length, length, present;
31 int readable;
32 int writable;
34 int modified;
36 int ref, dead;
38 pthread_mutex_t mutex;
40 /* This field is locked by files_mutex, not by file_info->mutex */
41 struct file_info *next;
44 static struct file_info *files = NULL;
45 static pthread_mutex_t files_mutex = PTHREAD_MUTEX_INITIALIZER;
47 int file_cache_sync_unlocked(struct file_info *fi);
49 void* file_cache_get(const char *path) {
50 struct file_info *f, *r = NULL;
52 pthread_mutex_lock(&files_mutex);
54 for (f = files; f; f = f->next) {
56 pthread_mutex_lock(&f->mutex);
57 if (!f->dead && f->filename && !strcmp(path, f->filename)) {
58 f->ref++;
59 r = f;
61 pthread_mutex_unlock(&f->mutex);
63 if (r)
64 break;
67 pthread_mutex_unlock(&files_mutex);
68 return f;
71 static void file_cache_free_unlocked(struct file_info *fi) {
72 assert(fi && fi->dead && fi->ref == 0);
74 free(fi->filename);
76 if (fi->fd >= 0)
77 close(fi->fd);
79 pthread_mutex_destroy(&fi->mutex);
80 free(fi);
83 void file_cache_unref(void *f) {
84 struct file_info *fi = f;
85 assert(fi);
87 pthread_mutex_lock(&fi->mutex);
89 assert(fi->ref >= 1);
90 fi->ref--;
92 if (!fi->ref && fi->dead) {
93 file_cache_sync_unlocked(fi);
94 file_cache_free_unlocked(fi);
97 pthread_mutex_unlock(&fi->mutex);
100 static void file_cache_unlink(struct file_info *fi) {
101 struct file_info *s, *prev;
102 assert(fi);
104 pthread_mutex_lock(&files_mutex);
106 for (s = files, prev = NULL; s; s = s->next) {
107 if (s == fi) {
108 if (prev)
109 prev->next = s->next;
110 else
111 files = s->next;
113 break;
116 prev = s;
119 pthread_mutex_unlock(&files_mutex);
122 int file_cache_close(void *f) {
123 struct file_info *fi = f;
124 int r = 0;
125 assert(fi);
127 file_cache_unlink(f);
129 pthread_mutex_lock(&fi->mutex);
130 fi->dead = 1;
131 pthread_mutex_unlock(&fi->mutex);
133 return r;
136 void* file_cache_open(const char *path, int flags) {
137 struct file_info *fi;
138 char tempfile[PATH_MAX];
139 char *length = NULL;
140 ne_request *req;
141 ne_session *session;
143 if ((fi = file_cache_get(path))) {
144 if (flags & O_RDONLY || flags & O_RDWR) fi->readable = 1;
145 if (flags & O_WRONLY || flags & O_RDWR) fi->writable = 1;
146 return fi;
149 if (!(session = session_get())) {
150 errno = -EIO;
151 return NULL;
154 fi = malloc(sizeof(struct file_info));
155 memset(fi, 0, sizeof(struct file_info));
156 fi->fd = -1;
158 fi->filename = strdup(path);
160 snprintf(tempfile, sizeof(tempfile), "%s/fusedav-cache-XXXXXX", "/tmp");
161 if ((fi->fd = mkstemp(tempfile)) < 0)
162 goto fail;
163 unlink(tempfile);
165 req = ne_request_create(session, "HEAD", path);
166 assert(req);
168 ne_add_response_header_handler(req, "Content-Length", ne_duplicate_header, &length);
170 if (ne_request_dispatch(req) != NE_OK) {
171 fprintf(stderr, "HEAD failed: %s\n", ne_get_error(session));
172 errno = ENOENT;
173 goto fail;
176 if (!length) {
177 fprintf(stderr, "HEAD did not return content length.\n");
178 errno = EPROTO;
179 goto fail;
182 fi->server_length = fi->length = atoi(length);
184 ne_request_destroy(req);
185 free(length);
187 if (flags & O_RDONLY || flags & O_RDWR) fi->readable = 1;
188 if (flags & O_WRONLY || flags & O_RDWR) fi->writable = 1;
190 pthread_mutex_init(&fi->mutex, NULL);
192 pthread_mutex_lock(&files_mutex);
193 fi->next = files;
194 files = fi;
195 pthread_mutex_unlock(&files_mutex);
197 fi->ref = 1;
199 return fi;
201 fail:
203 if (req)
204 ne_request_destroy(req);
206 if (length)
207 free(length);
209 if (fi) {
210 if (fi->fd >= 0)
211 close(fi->fd);
212 free(fi->filename);
213 free(fi);
216 return NULL;
219 static int load_up_to_unlocked(struct file_info *fi, off_t l) {
220 ne_content_range range;
221 assert(fi);
222 ne_session *session;
224 if (!(session = session_get())) {
225 errno = EIO;
226 return -1;
229 if (l > fi->server_length)
230 l = fi->server_length;
232 if (l <= fi->present)
233 return 0;
235 if (lseek(fi->fd, fi->present, SEEK_SET) != fi->present)
236 return -1;
238 range.start = fi->present;
239 range.end = l-1;
241 if (ne_get_range(session, fi->filename, &range, fi->fd)) {
242 fprintf(stderr, "GET failed: %s\n", ne_get_error(session));
243 errno = ENOENT;
244 return -1;
247 fi->present = l;
248 return 0;
251 int file_cache_read(void *f, char *buf, size_t size, off_t offset) {
252 struct file_info *fi = f;
253 ssize_t r = -1;
255 assert(fi && buf && size);
257 pthread_mutex_lock(&fi->mutex);
259 if (load_up_to_unlocked(fi, offset+size) < 0)
260 goto finish;
262 if ((r = pread(fi->fd, buf, size, offset)) < 0)
263 goto finish;
265 finish:
267 pthread_mutex_unlock(&fi->mutex);
269 return r;
272 int file_cache_write(void *f, const char *buf, size_t size, off_t offset) {
273 struct file_info *fi = f;
274 ssize_t r = -1;
276 assert (fi);
278 pthread_mutex_lock(&fi->mutex);
280 if (!fi->writable) {
281 errno = EBADF;
282 goto finish;
285 if (load_up_to_unlocked(fi, offset) < 0)
286 goto finish;
288 if ((r = pwrite(fi->fd, buf, size, offset)) < 0)
289 goto finish;
291 if (offset+size > fi->present)
292 fi->present = offset+size;
294 if (offset+size > fi->length)
295 fi->length = offset+size;
297 fi->modified = 1;
299 r = 0;
301 finish:
302 pthread_mutex_unlock(&fi->mutex);
304 return r;
307 int file_cache_truncate(void *f, off_t s) {
308 struct file_info *fi = f;
309 assert(fi);
310 int r;
312 pthread_mutex_lock(&fi->mutex);
314 fi->length = s;
315 r = ftruncate(fi->fd, fi->length);
317 pthread_mutex_unlock(&fi->mutex);
319 return r;
322 int file_cache_sync_unlocked(struct file_info *fi) {
323 int r = -1;
324 ne_session *session;
325 assert(fi);
327 if (!(session = session_get())) {
328 errno = EIO;
329 goto finish;
332 if (!fi->writable) {
333 errno = EBADF;
334 goto finish;
337 if (!fi->modified) {
338 r = 0;
339 goto finish;
342 if (load_up_to_unlocked(fi, (off_t) -1) < 0)
343 goto finish;
345 if (lseek(fi->fd, 0, SEEK_SET) == (off_t)-1)
346 goto finish;
349 if (ne_put(session, fi->filename, fi->fd)) {
350 fprintf(stderr, "PUT failed: %s\n", ne_get_error(session));
351 errno = ENOENT;
352 goto finish;
355 stat_cache_invalidate(fi->filename);
356 dir_cache_invalidate_parent(fi->filename);
358 r = 0;
360 finish:
362 return r;
365 int file_cache_sync(void *f) {
366 struct file_info *fi = f;
367 int r = -1;
368 assert(fi);
370 pthread_mutex_lock(&fi->mutex);
371 r = file_cache_sync_unlocked(fi);
372 pthread_mutex_unlock(&fi->mutex);
374 return r;
377 int file_cache_close_all(void) {
378 int r = 0;
380 pthread_mutex_lock(&files_mutex);
382 while (files) {
383 struct file_info *fi = files;
385 pthread_mutex_lock(&fi->mutex);
386 fi->ref++;
387 pthread_mutex_unlock(&fi->mutex);
389 pthread_mutex_unlock(&files_mutex);
390 file_cache_close(fi);
391 file_cache_unref(fi);
392 pthread_mutex_lock(&files_mutex);
395 pthread_mutex_unlock(&files_mutex);
397 return r;