2 Copyright (c) 2003-2006 by Juliusz Chroboczek
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 #include "md5import.h"
29 int maxDiskEntries
= 32;
31 /* Because the functions in this file can be called during object
32 expiry, we cannot use get_chunk. */
34 AtomPtr diskCacheRoot
;
35 AtomPtr localDocumentRoot
;
37 DiskCacheEntryPtr diskEntries
= NULL
, diskEntriesLast
= NULL
;
38 int numDiskEntries
= 0;
39 int diskCacheDirectoryPermissions
= 0700;
40 int diskCacheFilePermissions
= 0600;
41 int diskCacheNoAtime
= 1;
42 int diskCacheWriteoutOnClose
= (32 * 1024);
44 int maxDiskCacheEntrySize
= -1;
46 int diskCacheUnlinkTime
= 32 * 24 * 60 * 60;
47 int diskCacheTruncateTime
= 4 * 24 * 60 * 60 + 12 * 60 * 60;
48 int diskCacheTruncateSize
= 1024 * 1024;
49 int preciseExpiry
= 0;
51 static DiskCacheEntryRec negativeEntry
= {
53 -1, -1, -1, -1, 0, 0, 0, NULL
, NULL
57 #define LOCAL_ROOT "/usr/share/polipo/www/"
60 #ifndef DISK_CACHE_ROOT
61 #define DISK_CACHE_ROOT "/var/cache/polipo/"
64 static int maxDiskEntriesSetter(ConfigVariablePtr
, void*);
65 static int atomSetterFlush(ConfigVariablePtr
, void*);
66 static int reallyWriteoutToDisk(ObjectPtr object
, int upto
, int max
);
71 diskCacheRoot
= internAtom(DISK_CACHE_ROOT
);
72 localDocumentRoot
= internAtom(LOCAL_ROOT
);
74 CONFIG_VARIABLE_SETTABLE(diskCacheDirectoryPermissions
, CONFIG_OCTAL
,
76 "Access rights for new directories.");
77 CONFIG_VARIABLE_SETTABLE(diskCacheFilePermissions
, CONFIG_OCTAL
,
79 "Access rights for new cache files.");
80 CONFIG_VARIABLE_SETTABLE(diskCacheWriteoutOnClose
, CONFIG_INT
,
82 "Number of bytes to write out eagerly.");
83 CONFIG_VARIABLE_SETTABLE(diskCacheRoot
, CONFIG_ATOM
, atomSetterFlush
,
84 "Root of the disk cache.");
85 CONFIG_VARIABLE_SETTABLE(localDocumentRoot
, CONFIG_ATOM
, atomSetterFlush
,
86 "Root of the local tree.");
87 CONFIG_VARIABLE_SETTABLE(maxDiskEntries
, CONFIG_INT
, maxDiskEntriesSetter
,
88 "File descriptors used by the on-disk cache.");
89 CONFIG_VARIABLE(diskCacheUnlinkTime
, CONFIG_TIME
,
90 "Time after which on-disk objects are removed.");
91 CONFIG_VARIABLE(diskCacheTruncateTime
, CONFIG_TIME
,
92 "Time after which on-disk objects are truncated.");
93 CONFIG_VARIABLE(diskCacheTruncateSize
, CONFIG_INT
,
94 "Size to which on-disk objects are truncated.");
95 CONFIG_VARIABLE(preciseExpiry
, CONFIG_BOOLEAN
,
96 "Whether to consider all files for purging.");
97 CONFIG_VARIABLE_SETTABLE(maxDiskCacheEntrySize
, CONFIG_INT
,
99 "Maximum size of objects cached on disk.");
100 CONFIG_VARIABLE_SETTABLE(diskCacheNoAtime
, CONFIG_BOOLEAN
,
102 "Try to set noatime on the disk cache.");
106 maxDiskEntriesSetter(ConfigVariablePtr var
, void *value
)
109 assert(var
->type
== CONFIG_INT
&& var
->value
.i
== &maxDiskEntries
);
111 if(i
< 0 || i
> 1000000)
114 while(numDiskEntries
> maxDiskEntries
)
115 destroyDiskEntry(diskEntriesLast
->object
, 0);
120 atomSetterFlush(ConfigVariablePtr var
, void *value
)
122 discardObjects(1, 0);
123 return configAtomSetter(var
, value
);
127 checkRoot(AtomPtr root
)
132 if(!root
|| root
->length
== 0)
135 if(root
->string
[0] != '/') {
139 rc
= stat(root
->string
, &ss
);
142 else if(!S_ISDIR(ss
.st_mode
)) {
150 maybeAddSlash(AtomPtr atom
)
152 AtomPtr newAtom
= NULL
;
153 if(!atom
) return NULL
;
154 if(atom
->length
> 0 && atom
->string
[atom
->length
- 1] != '/') {
155 newAtom
= atomCat(atom
, "/");
167 diskCacheRoot
= expandTilde(maybeAddSlash(diskCacheRoot
));
168 rc
= checkRoot(diskCacheRoot
);
172 case -1: do_log_error(L_WARN
, errno
, "Disabling disk cache"); break;
174 do_log(L_WARN
, "Disabling disk cache: path %s is not absolute.\n",
175 diskCacheRoot
->string
);
179 releaseAtom(diskCacheRoot
);
180 diskCacheRoot
= NULL
;
183 localDocumentRoot
= expandTilde(maybeAddSlash(localDocumentRoot
));
184 rc
= checkRoot(localDocumentRoot
);
188 case -1: do_log_error(L_WARN
, errno
, "Disabling local tree"); break;
190 do_log(L_WARN
, "Disabling local tree: path is not absolute.\n");
194 releaseAtom(localDocumentRoot
);
195 localDocumentRoot
= NULL
;
199 #ifdef DEBUG_DISK_CACHE
200 #define CHECK_ENTRY(entry) check_entry((entry))
202 check_entry(DiskCacheEntryPtr entry
)
204 if(entry
&& entry
->fd
< 0)
205 assert(entry
== &negativeEntry
);
206 if(entry
&& entry
->fd
>= 0) {
207 assert((!entry
->previous
) == (entry
== diskEntries
));
208 assert((!entry
->next
) == (entry
== diskEntriesLast
));
210 assert(entry
->size
+ entry
->body_offset
>= entry
->offset
);
211 assert(entry
->body_offset
>= 0);
212 if(entry
->offset
>= 0) {
214 offset
= lseek(entry
->fd
, 0, SEEK_CUR
);
215 assert(offset
== entry
->offset
);
217 if(entry
->size
>= 0) {
220 rc
= fstat(entry
->fd
, &ss
);
222 assert(ss
.st_size
== entry
->size
+ entry
->body_offset
);
227 #define CHECK_ENTRY(entry) do {} while(0)
231 diskEntrySize(ObjectPtr object
)
235 DiskCacheEntryPtr entry
= object
->disk_entry
;
237 if(!entry
|| entry
== &negativeEntry
)
243 rc
= fstat(entry
->fd
, &buf
);
245 do_log_error(L_ERROR
, errno
, "Couldn't stat");
249 if(buf
.st_size
<= entry
->body_offset
)
252 entry
->size
= buf
.st_size
- entry
->body_offset
;
254 if(object
->length
>= 0 && entry
->size
== object
->length
)
255 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
260 entrySeek(DiskCacheEntryPtr entry
, off_t offset
)
265 assert(entry
!= &negativeEntry
);
266 if(entry
->offset
== offset
)
268 if(offset
> entry
->body_offset
) {
269 /* Avoid extending the file by mistake */
271 diskEntrySize(entry
->object
);
274 if(entry
->size
+ entry
->body_offset
< offset
)
277 rc
= lseek(entry
->fd
, offset
, SEEK_SET
);
279 do_log_error(L_ERROR
, errno
, "Couldn't seek");
283 entry
->offset
= offset
;
287 /* Given a local URL, constructs the filename where it can be found. */
290 localFilename(char *buf
, int n
, char *key
, int len
)
293 if(len
<= 0 || key
[0] != '/') return -1;
295 if(urlIsSpecial(key
, len
)) return -1;
297 if(localDocumentRoot
== NULL
||
298 localDocumentRoot
->length
<= 0 || localDocumentRoot
->string
[0] != '/')
301 if(n
<= localDocumentRoot
->length
)
308 memcpy(buf
, localDocumentRoot
->string
, localDocumentRoot
->length
);
309 j
= localDocumentRoot
->length
;
310 if(buf
[j
- 1] == '/')
316 if(key
[i
] == '/' && i
< len
- 2)
317 if(key
[i
+ 1] == '.' &&
318 (key
[i
+ 2] == '.' || key
[i
+ 2] == '/'))
323 if(buf
[j
- 1] == '/') {
326 memcpy(buf
+ j
, "index.html", 10);
335 md5(unsigned char *restrict key
, int len
, unsigned char *restrict dst
)
339 MD5Update(&ctx
, key
, len
);
341 memcpy(dst
, ctx
.digest
, 16);
344 /* Check whether a character can be stored in a filename. This is
345 needed since we want to support deficient file systems. */
349 if(c
<= 31 || c
>= 127)
351 if((c
>= 'a' && c
<= 'z') || (c
>= 'A' && c
<= 'Z') ||
352 (c
>= '0' && c
<= '9') || c
== '.' || c
== '-' || c
== '_')
357 /* Given a URL, returns the directory name within which all files
358 starting with this URL can be found. */
360 urlDirname(char *buf
, int n
, const char *url
, int len
)
365 if(memcmp(url
, "http://", 7) != 0)
368 if(diskCacheRoot
== NULL
||
369 diskCacheRoot
->length
<= 0 || diskCacheRoot
->string
[0] != '/')
372 if(n
<= diskCacheRoot
->length
)
375 memcpy(buf
, diskCacheRoot
->string
, diskCacheRoot
->length
);
376 j
= diskCacheRoot
->length
;
378 if(buf
[j
- 1] != '/')
381 for(i
= 7; i
< len
; i
++) {
382 if(i
>= len
|| url
[i
] == '/')
384 if(url
[i
] == '.' && i
!= len
- 1 && url
[i
+ 1] == '.')
386 if(url
[i
] == '%' || !fssafe(url
[i
])) {
387 if(j
+ 3 >= n
) return -1;
389 buf
[j
++] = i2h((url
[i
] & 0xF0) >> 4);
390 buf
[j
++] = i2h(url
[i
] & 0x0F);
392 buf
[j
++] = url
[i
]; if(j
>= n
) return -1;
395 buf
[j
++] = '/'; if(j
>= n
) return -1;
400 /* Given a URL, returns the filename where the cached data can be
403 urlFilename(char *restrict buf
, int n
, const char *url
, int len
)
406 unsigned char md5buf
[18];
407 j
= urlDirname(buf
, n
, url
, len
);
408 if(j
< 0 || j
+ 24 >= n
)
410 md5((unsigned char*)url
, len
, md5buf
);
411 b64cpy(buf
+ j
, (char*)md5buf
, 16, 1);
417 dirnameUrl(char *url
, int n
, char *name
, int len
)
420 k
= diskCacheRoot
->length
;
423 if(memcmp(name
, diskCacheRoot
->string
, k
) != 0)
427 memcpy(url
, "http://", 7);
428 if(name
[len
- 1] == '/')
431 for(i
= k
; i
< len
; i
++) {
435 c1
= h2i(name
[i
+ 1]);
436 c2
= h2i(name
[i
+ 2]);
439 url
[j
++] = c1
* 16 + c2
; if(j
>= n
) goto fail
;
440 i
+= 2; /* skip extra digits */
441 } else if(i
< len
- 1 &&
442 name
[i
] == '.' && name
[i
+ 1] == '/') {
444 } else if(i
== len
- 1 && name
[i
] == '.') {
447 url
[j
++] = name
[i
]; if(j
>= n
) goto fail
;
450 url
[j
++] = '/'; if(j
>= n
) goto fail
;
458 /* Create a file and all intermediate directories. */
460 createFile(const char *name
, int path_start
)
466 int noatime
= diskCacheNoAtime
? O_NOATIME
: 0;
468 if(name
[path_start
] == '/')
471 if(path_start
< 2 || name
[path_start
- 1] != '/' ) {
472 do_log(L_ERROR
, "Incorrect name %s (%d).\n", name
, path_start
);
476 fd
= open(name
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
| noatime
,
477 diskCacheFilePermissions
);
480 if(errno
!= ENOENT
) {
481 do_log_error(L_ERROR
, errno
, "Couldn't create disk file %s", name
);
486 while(name
[n
] != '\0' && n
< 1024) {
487 while(name
[n
] != '/' && name
[n
] != '\0' && n
< 512)
489 if(name
[n
] != '/' || n
>= 1024)
491 memcpy(buf
, name
, n
+ 1);
493 rc
= mkdir(buf
, diskCacheDirectoryPermissions
);
494 if(rc
< 0 && errno
!= EEXIST
) {
495 do_log_error(L_ERROR
, errno
, "Couldn't create directory %s", buf
);
500 fd
= open(name
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
| noatime
,
501 diskCacheFilePermissions
);
503 do_log_error(L_ERROR
, errno
, "Couldn't create file %s", name
);
511 chooseBodyOffset(int n
, ObjectPtr object
)
513 int length
= MAX(object
->size
, object
->length
);
516 if(object
->length
>= 0 && object
->length
+ n
< 4096 - 4)
517 return -1; /* no gap for small objects */
534 body_offset
= ((n
+ 32 + 4095) / 4096 + 1) * 4096;
536 /* Tweak the gap so that we don't use up a full disk block for
538 if(object
->length
>= 0 && object
->length
< 64 * 1024) {
539 int last
= (body_offset
+ object
->length
) % 4096;
540 int gap
= body_offset
- n
- 32;
545 /* Rewriting large objects is expensive -- don't use small gaps.
546 This has the additional benefit of block-aligning large bodies. */
547 if(length
>= 64 * 1024) {
548 int min_gap
, min_offset
;
549 if(length
>= 512 * 1024)
551 else if(length
>= 256 * 1024)
556 min_offset
= ((n
+ 32 + min_gap
- 1) / min_gap
+ 1) * min_gap
;
557 body_offset
= MAX(body_offset
, min_offset
);
563 /* Assumes the file descriptor is at offset 0. Returns -1 on failure,
564 otherwise the offset at which the file descriptor is left. */
565 /* If chunk is not null, it should be the first chunk of the object,
566 and will be written out in the same operation if possible. */
568 writeHeaders(int fd
, int *body_offset_return
,
569 ObjectPtr object
, char *chunk
, int chunk_len
)
573 int body_offset
= *body_offset_return
;
575 int buf_is_chunk
= 0;
578 if(object
->flags
& OBJECT_LOCAL
)
581 if(body_offset
> CHUNK_SIZE
)
584 /* get_chunk might trigger object expiry */
585 bufsize
= CHUNK_SIZE
;
587 buf
= maybe_get_chunk();
593 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
599 n
= snnprintf(buf
, 0, bufsize
, "HTTP/1.1 %3d %s",
600 object
->code
, object
->message
->string
);
602 n
= httpWriteObjectHeaders(buf
, n
, bufsize
, object
, 0, -1);
606 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Location: ");
607 n
= snnprint_n(buf
, n
, bufsize
, object
->key
, object
->key_size
);
609 if(object
->age
>= 0 && object
->age
!= object
->date
) {
610 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Date: ");
611 n
= format_time(buf
, n
, bufsize
, object
->age
);
614 if(object
->atime
>= 0) {
615 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Access: ");
616 n
= format_time(buf
, n
, bufsize
, object
->atime
);
623 body_offset
= chooseBodyOffset(n
, object
);
625 if(body_offset
> bufsize
)
628 if(body_offset
> 0 && body_offset
!= n
+ 4)
629 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Body-Offset: %d",
632 n
= snnprintf(buf
, n
, bufsize
, "\r\n\r\n");
642 memset(buf
+ n
, 0, body_offset
- n
);
645 #ifdef HAVE_READV_WRITEV
648 iov
[0].iov_base
= buf
;
649 iov
[0].iov_len
= body_offset
;
650 iov
[1].iov_base
= chunk
;
651 iov
[1].iov_len
= chunk_len
;
652 rc
= writev(fd
, iov
, 2);
655 rc
= write(fd
, buf
, body_offset
);
657 if(rc
< 0 && errno
== EINTR
)
662 if(object
->length
>= 0 &&
663 rc
- body_offset
>= object
->length
)
664 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
666 *body_offset_return
= body_offset
;
674 if(bufsize
< bigBufferSize
) {
676 buf
= malloc(bigBufferSize
);
678 do_log(L_ERROR
, "Couldn't allocate big buffer.\n");
681 bufsize
= bigBufferSize
;
684 dispose_chunk(oldbuf
);
701 typedef struct _MimeEntry
{
706 static const MimeEntryRec mimeEntries
[] = {
707 { "html", "text/html" },
708 { "htm", "text/html" },
709 { "text", "text/plain" },
710 { "txt", "text/plain" },
711 { "png", "image/png" },
712 { "gif", "image/gif" },
713 { "jpeg", "image/jpeg" },
714 { "jpg", "image/jpeg" },
715 { "ico", "image/x-icon" },
716 { "pdf", "application/pdf" },
717 { "ps", "application/postscript" },
718 { "tar", "application/x-tar" },
719 { "pac", "application/x-ns-proxy-autoconfig" },
720 { "css", "text/css" },
721 { "js", "application/x-javascript" },
722 { "xml", "text/xml" },
723 { "swf", "application/x-shockwave-flash" },
727 localObjectMimeType(ObjectPtr object
, char **encoding_return
)
729 char *name
= object
->key
;
730 int nlen
= object
->key_size
;
735 if(name
[nlen
- 1] == '/') {
736 *encoding_return
= NULL
;
741 *encoding_return
= NULL
;
742 return "application/octet-stream";
745 if(memcmp(name
+ nlen
- 3, ".gz", 3) == 0) {
746 *encoding_return
= "x-gzip";
748 } else if(memcmp(name
+ nlen
- 2, ".Z", 2) == 0) {
749 *encoding_return
= "x-compress";
752 *encoding_return
= NULL
;
755 for(i
= 0; i
< sizeof(mimeEntries
) / sizeof(mimeEntries
[0]); i
++) {
756 int len
= strlen(mimeEntries
[i
].extension
);
758 name
[nlen
- len
- 1] == '.' &&
759 memcmp(name
+ nlen
- len
, mimeEntries
[i
].extension
, len
) == 0)
760 return mimeEntries
[i
].mime
;
763 return "application/octet-stream";
766 /* Same interface as validateEntry -- see below */
768 validateLocalEntry(ObjectPtr object
, int fd
,
769 int *body_offset_return
, off_t
*offset_return
)
778 do_log_error(L_ERROR
, errno
, "Couldn't stat");
782 if(S_ISREG(ss
.st_mode
)) {
783 if(!(ss
.st_mode
& S_IROTH
) ||
784 (object
->length
>= 0 && object
->length
!= ss
.st_size
) ||
785 (object
->last_modified
>= 0 &&
786 object
->last_modified
!= ss
.st_mtime
))
789 notifyObject(object
);
793 n
= snnprintf(buf
, 0, 512, "%lx-%lx-%lx",
794 (unsigned long)ss
.st_ino
,
795 (unsigned long)ss
.st_size
,
796 (unsigned long)ss
.st_mtime
);
800 if(n
> 0 && object
->etag
) {
801 if(strlen(object
->etag
) != n
||
802 memcmp(object
->etag
, buf
, n
) != 0)
806 if(!(object
->flags
& OBJECT_INITIAL
)) {
807 if(!object
->last_modified
&& !object
->etag
)
811 if(object
->flags
& OBJECT_INITIAL
) {
812 object
->length
= ss
.st_size
;
813 object
->last_modified
= ss
.st_mtime
;
814 object
->date
= current_time
.tv_sec
;
815 object
->age
= current_time
.tv_sec
;
818 object
->etag
= strdup(buf
); /* okay if fails */
819 object
->message
= internAtom("Okay");
820 n
= snnprintf(buf
, 0, 512,
822 "\r\nContent-Type: %s",
823 localObjectMimeType(object
, &encoding
));
825 n
= snnprintf(buf
, n
, 512,
826 "\r\nContent-Encoding: %s", encoding
);
829 object
->headers
= internAtomN(buf
, n
);
830 if(object
->headers
== NULL
)
832 object
->flags
&= ~OBJECT_INITIAL
;
835 if(body_offset_return
)
836 *body_offset_return
= 0;
842 /* Assumes fd is at offset 0.
843 Returns -1 if not valid, 1 if metadata should be written out, 0
846 validateEntry(ObjectPtr object
, int fd
,
847 int *body_offset_return
, off_t
*offset_return
)
850 int buf_is_chunk
, bufsize
;
855 time_t date
, last_modified
, expires
, polipo_age
, polipo_access
;
861 CacheControlRec cache_control
;
866 if(object
->flags
& OBJECT_LOCAL
)
867 return validateLocalEntry(object
, fd
,
868 body_offset_return
, offset_return
);
870 if(!(object
->flags
& OBJECT_PUBLIC
) && (object
->flags
& OBJECT_INITIAL
))
873 /* get_chunk might trigger object expiry */
874 bufsize
= CHUNK_SIZE
;
876 buf
= maybe_get_chunk();
882 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
888 rc
= read(fd
, buf
, bufsize
);
892 do_log_error(L_ERROR
, errno
, "Couldn't read disk entry");
898 n
= findEndOfHeaders(buf
, 0, rc
, &dummy
);
901 if(bufsize
< bigBufferSize
) {
902 buf
= malloc(bigBufferSize
);
904 do_log(L_ERROR
, "Couldn't allocate big buffer.\n");
907 bufsize
= bigBufferSize
;
908 memcpy(buf
, oldbuf
, offset
);
910 dispose_chunk(oldbuf
);
915 rc
= read(fd
, buf
+ offset
, bufsize
- offset
);
919 do_log_error(L_ERROR
, errno
, "Couldn't read disk entry");
925 do_log(L_ERROR
, "Couldn't parse disk entry.\n");
929 rc
= httpParseServerFirstLine(buf
, &code
, &dummy
, &message
);
931 do_log(L_ERROR
, "Couldn't parse disk entry.\n");
935 if(object
->code
!= 0 && object
->code
!= code
) {
936 releaseAtom(message
);
940 rc
= httpParseHeaders(0, NULL
, buf
, rc
, NULL
,
941 &headers
, &length
, &cache_control
, NULL
, NULL
,
942 &date
, &last_modified
, &expires
, &polipo_age
,
943 &polipo_access
, &body_offset
,
945 NULL
, NULL
, &location
, &via
, NULL
);
947 releaseAtom(message
);
953 if(!location
|| strlen(location
) != object
->key_size
||
954 memcmp(location
, object
->key
, object
->key_size
) != 0) {
955 do_log(L_ERROR
, "Inconsistent cache file for %s.\n", location
);
963 do_log(L_ERROR
, "Undated disk entry for %s.\n", location
);
967 if(!(object
->flags
& OBJECT_INITIAL
)) {
968 if((last_modified
>= 0) != (object
->last_modified
>= 0))
971 if((object
->cache_control
& CACHE_MISMATCH
) ||
972 (cache_control
.flags
& CACHE_MISMATCH
))
975 if(last_modified
>= 0 && object
->last_modified
>= 0 &&
976 last_modified
!= object
->last_modified
)
979 if(length
>= 0 && object
->length
>= 0)
980 if(length
!= object
->length
)
983 if(!!etag
!= !!object
->etag
)
986 if(etag
&& object
->etag
&& strcmp(etag
, object
->etag
) != 0)
989 /* If we don't have a usable ETag, and either CACHE_VARY or we
990 don't have a last-modified date, we validate disk entries by
992 if(!(etag
&& object
->etag
) &&
993 (!(last_modified
>= 0 && object
->last_modified
>= 0) ||
994 ((cache_control
.flags
& CACHE_VARY
) ||
995 (object
->cache_control
& CACHE_VARY
)))) {
996 if(date
>= 0 && date
!= object
->date
)
998 if(polipo_age
>= 0 && polipo_age
!= object
->age
)
1001 if((object
->cache_control
& CACHE_VARY
) && dontTrustVaryETag
>= 1) {
1002 /* Check content-type to work around mod_gzip bugs */
1003 if(!httpHeaderMatch(atomContentType
, object
->headers
, headers
) ||
1004 !httpHeaderMatch(atomContentEncoding
, object
->headers
, headers
))
1013 if(!object
->headers
)
1014 object
->headers
= headers
;
1016 releaseAtom(headers
);
1019 if(object
->code
== 0) {
1020 object
->code
= code
;
1021 object
->message
= retainAtom(message
);
1023 if(object
->date
<= date
)
1024 object
->date
= date
;
1027 if(object
->last_modified
< 0)
1028 object
->last_modified
= last_modified
;
1029 if(object
->expires
< 0)
1030 object
->expires
= expires
;
1031 else if(object
->expires
> expires
)
1034 object
->age
= polipo_age
;
1035 else if(object
->age
> polipo_age
)
1037 if(object
->atime
<= polipo_access
)
1038 object
->atime
= polipo_access
;
1042 object
->cache_control
|= cache_control
.flags
;
1044 if(object
->age
< 0) object
->age
= object
->date
;
1045 if(object
->age
< 0) object
->age
= 0; /* a long time ago */
1046 if(object
->length
< 0) object
->length
= length
;
1048 object
->etag
= etag
;
1053 releaseAtom(message
);
1055 if(object
->flags
& OBJECT_INITIAL
) object
->via
= via
;
1056 object
->flags
&= ~OBJECT_INITIAL
;
1057 if(offset
> body_offset
) {
1058 /* We need to make sure we don't invoke object expiry recursively */
1059 objectSetChunks(object
, 1);
1060 if(object
->numchunks
>= 1) {
1061 if(object
->chunks
[0].data
== NULL
)
1062 object
->chunks
[0].data
= maybe_get_chunk();
1063 if(object
->chunks
[0].data
)
1064 objectAddData(object
, buf
+ body_offset
,
1065 0, MIN(offset
- body_offset
, CHUNK_SIZE
));
1069 httpTweakCachability(object
);
1075 if(body_offset_return
) *body_offset_return
= body_offset
;
1076 if(offset_return
) *offset_return
= offset
;
1080 releaseAtom(message
);
1081 if(etag
) free(etag
);
1082 if(location
) free(location
);
1083 if(via
) releaseAtom(via
);
1095 dirtyDiskEntry(ObjectPtr object
)
1097 DiskCacheEntryPtr entry
= object
->disk_entry
;
1098 if(entry
&& entry
!= &negativeEntry
) entry
->metadataDirty
= 1;
1102 revalidateDiskEntry(ObjectPtr object
)
1104 DiskCacheEntryPtr entry
= object
->disk_entry
;
1108 if(!entry
|| entry
== &negativeEntry
)
1112 rc
= entrySeek(entry
, 0);
1113 if(rc
< 0) return 0;
1115 rc
= validateEntry(object
, entry
->fd
, &body_offset
, &entry
->offset
);
1117 destroyDiskEntry(object
, 0);
1120 if(body_offset
!= entry
->body_offset
) {
1121 do_log(L_WARN
, "Inconsistent body offset (%d != %d).\n",
1122 body_offset
, entry
->body_offset
);
1123 destroyDiskEntry(object
, 0);
1127 entry
->metadataDirty
|= !!rc
;
1133 objectHasDiskEntry(ObjectPtr object
)
1135 return object
->disk_entry
&& object
->disk_entry
!= &negativeEntry
;
1138 static DiskCacheEntryPtr
1139 makeDiskEntry(ObjectPtr object
, int writeable
, int create
)
1141 DiskCacheEntryPtr entry
= NULL
;
1144 int negative
= 0, isWriteable
= 0, size
= -1, name_len
= -1;
1147 int body_offset
= -1;
1149 int local
= (object
->flags
& OBJECT_LOCAL
) != 0;
1151 int noatime
= diskCacheNoAtime
? O_NOATIME
: 0;
1153 if(local
&& (writeable
|| create
))
1156 if(!local
&& !(object
->flags
& OBJECT_PUBLIC
))
1159 if(maxDiskCacheEntrySize
>= 0) {
1160 if(object
->length
> 0) {
1161 if(object
->length
> maxDiskCacheEntrySize
)
1164 if(object
->size
> maxDiskCacheEntrySize
)
1169 if(object
->disk_entry
) {
1170 entry
= object
->disk_entry
;
1172 if(entry
!= &negativeEntry
&& (!writeable
|| entry
->writeable
)) {
1173 /* We'll keep the entry -- put it at the front. */
1174 if(entry
!= diskEntries
&& entry
!= &negativeEntry
) {
1175 entry
->previous
->next
= entry
->next
;
1177 entry
->next
->previous
= entry
->previous
;
1179 diskEntriesLast
= entry
->previous
;
1180 entry
->next
= diskEntries
;
1181 diskEntries
->previous
= entry
;
1182 entry
->previous
= NULL
;
1183 diskEntries
= entry
;
1187 if(entry
== &negativeEntry
) {
1189 if(!create
) return NULL
;
1190 object
->disk_entry
= NULL
;
1193 destroyDiskEntry(object
, 0);
1197 if(numDiskEntries
> maxDiskEntries
)
1198 destroyDiskEntry(diskEntriesLast
->object
, 0);
1201 if(diskCacheRoot
== NULL
|| diskCacheRoot
->length
<= 0)
1203 name_len
= urlFilename(buf
, 1024, object
->key
, object
->key_size
);
1204 if(name_len
< 0) return NULL
;
1207 fd
= open(buf
, O_RDWR
| O_BINARY
| noatime
);
1208 if(fd
< 0 && !writeable
&& errno
== EACCES
) {
1210 fd
= open(buf
, O_RDONLY
| O_BINARY
| noatime
);
1214 rc
= validateEntry(object
, fd
, &body_offset
, &offset
);
1221 if(rc
< 0 && errno
!= ENOENT
) {
1222 do_log_error(L_WARN
, errno
,
1223 "Couldn't unlink stale disk entry %s",
1225 /* But continue -- it's okay to have stale entries. */
1230 if(fd
< 0 && create
&& name_len
> 0 &&
1231 !(object
->flags
& OBJECT_INITIAL
)) {
1233 fd
= createFile(buf
, diskCacheRoot
->length
);
1240 if(object
->numchunks
> 0) {
1241 data
= object
->chunks
[0].data
;
1242 dsize
= object
->chunks
[0].size
;
1244 rc
= writeHeaders(fd
, &body_offset
, object
, data
, dsize
);
1246 do_log_error(L_ERROR
, errno
, "Couldn't write headers");
1248 if(rc
< 0 && errno
!= ENOENT
)
1249 do_log_error(L_ERROR
, errno
,
1250 "Couldn't unlink truncated entry %s",
1255 assert(rc
>= body_offset
);
1256 size
= rc
- body_offset
;
1263 if(localDocumentRoot
== NULL
|| localDocumentRoot
->length
== 0)
1267 localFilename(buf
, 1024, object
->key
, object
->key_size
);
1271 fd
= open(buf
, O_RDONLY
| O_BINARY
| noatime
);
1273 if(validateEntry(object
, fd
, &body_offset
, NULL
) < 0) {
1282 object
->disk_entry
= &negativeEntry
;
1285 assert(body_offset
>= 0);
1287 name
= strdup_n(buf
, name_len
);
1289 do_log(L_ERROR
, "Couldn't allocate name.\n");
1295 entry
= malloc(sizeof(DiskCacheEntryRec
));
1297 do_log(L_ERROR
, "Couldn't allocate entry.\n");
1303 entry
->filename
= name
;
1304 entry
->object
= object
;
1306 entry
->body_offset
= body_offset
;
1307 entry
->local
= local
;
1308 entry
->offset
= offset
;
1310 entry
->metadataDirty
= dirty
;
1311 entry
->writeable
= isWriteable
;
1313 entry
->next
= diskEntries
;
1315 diskEntries
->previous
= entry
;
1316 diskEntries
= entry
;
1317 if(diskEntriesLast
== NULL
)
1318 diskEntriesLast
= entry
;
1319 entry
->previous
= NULL
;
1322 object
->disk_entry
= entry
;
1328 /* Rewrite a disk cache entry, used when the body offset needs to change. */
1330 rewriteEntry(ObjectPtr object
)
1332 int old_body_offset
= object
->disk_entry
->body_offset
;
1334 DiskCacheEntryPtr entry
;
1336 int buf_is_chunk
, bufsize
;
1339 fd
= dup(object
->disk_entry
->fd
);
1341 do_log_error(L_ERROR
, errno
, "Couldn't duplicate file descriptor");
1345 rc
= destroyDiskEntry(object
, 1);
1350 entry
= makeDiskEntry(object
, 1, 1);
1356 offset
= diskEntrySize(object
);
1362 bufsize
= CHUNK_SIZE
;
1364 buf
= maybe_get_chunk();
1370 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
1376 rc
= lseek(fd
, old_body_offset
+ offset
, SEEK_SET
);
1382 n
= read(fd
, buf
, bufsize
);
1385 rc
= entrySeek(entry
, entry
->body_offset
+ offset
);
1388 rc
= write(entry
->fd
, buf
, n
);
1390 entry
->offset
+= rc
;
1399 if(object
->length
>= 0 && entry
->size
== object
->length
)
1400 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
1410 destroyDiskEntry(ObjectPtr object
, int d
)
1412 DiskCacheEntryPtr entry
= object
->disk_entry
;
1415 assert(!entry
|| !entry
->local
|| !d
);
1418 entry
= makeDiskEntry(object
, 1, 0);
1422 if(!entry
|| entry
== &negativeEntry
) {
1426 assert(entry
->object
== object
);
1428 if(maxDiskCacheEntrySize
>= 0 && object
->size
> maxDiskCacheEntrySize
) {
1429 /* See writeoutToDisk */
1434 entry
->object
->flags
&= ~OBJECT_DISK_ENTRY_COMPLETE
;
1435 if(entry
->filename
) {
1436 urc
= unlink(entry
->filename
);
1438 do_log_error(L_WARN
, errno
,
1439 "Couldn't unlink %s", entry
->filename
);
1442 if(entry
&& entry
->metadataDirty
)
1443 writeoutMetadata(object
);
1444 makeDiskEntry(object
, 1, 0);
1445 /* rewriteDiskEntry may change the disk entry */
1446 entry
= object
->disk_entry
;
1447 if(entry
== NULL
|| entry
== &negativeEntry
)
1449 if(entry
->writeable
&& diskCacheWriteoutOnClose
> 0)
1450 reallyWriteoutToDisk(object
, -1, diskCacheWriteoutOnClose
);
1453 rc
= close(entry
->fd
);
1454 if(rc
< 0 && errno
== EINTR
)
1460 free(entry
->filename
);
1461 entry
->filename
= NULL
;
1464 entry
->previous
->next
= entry
->next
;
1466 diskEntries
= entry
->next
;
1468 entry
->next
->previous
= entry
->previous
;
1470 diskEntriesLast
= entry
->previous
;
1473 assert(numDiskEntries
>= 0);
1476 object
->disk_entry
= NULL
;
1484 objectGetFromDisk(ObjectPtr object
)
1486 DiskCacheEntryPtr entry
= makeDiskEntry(object
, 0, 0);
1487 if(!entry
) return NULL
;
1493 objectFillFromDisk(ObjectPtr object
, int offset
, int chunks
)
1495 DiskCacheEntryPtr entry
;
1500 if(object
->type
!= OBJECT_HTTP
)
1503 if(object
->flags
& OBJECT_LINEAR
)
1506 if(object
->length
>= 0) {
1507 chunks
= MIN(chunks
,
1508 (object
->length
- offset
+ CHUNK_SIZE
- 1) / CHUNK_SIZE
);
1511 rc
= objectSetChunks(object
, offset
/ CHUNK_SIZE
+ chunks
);
1516 if(object
->flags
& OBJECT_INITIAL
) {
1518 } else if((object
->length
< 0 || object
->size
< object
->length
) &&
1519 object
->size
< (offset
/ CHUNK_SIZE
+ chunks
) * CHUNK_SIZE
) {
1522 for(k
= 0; k
< chunks
; k
++) {
1524 i
= offset
/ CHUNK_SIZE
+ k
;
1525 s
= MIN(CHUNK_SIZE
, object
->size
- i
* CHUNK_SIZE
);
1526 if(object
->chunks
[i
].size
< s
) {
1536 /* This has the side-effect of revalidating the entry, which is
1537 what makes HEAD requests work. */
1538 entry
= makeDiskEntry(object
, 0, 0);
1542 for(k
= 0; k
< chunks
; k
++) {
1543 i
= offset
/ CHUNK_SIZE
+ k
;
1544 if(!object
->chunks
[i
].data
)
1545 object
->chunks
[i
].data
= get_chunk();
1546 if(!object
->chunks
[i
].data
) {
1550 lockChunk(object
, i
);
1555 for(k
= 0; k
< chunks
; k
++) {
1557 i
= offset
/ CHUNK_SIZE
+ k
;
1558 j
= object
->chunks
[i
].size
;
1559 o
= i
* CHUNK_SIZE
+ j
;
1561 if(object
->chunks
[i
].size
== CHUNK_SIZE
)
1564 if(entry
->size
>= 0 && entry
->size
<= o
)
1567 if(entry
->offset
!= entry
->body_offset
+ o
) {
1568 rc
= entrySeek(entry
, entry
->body_offset
+ o
);
1577 rc
= read(entry
->fd
, object
->chunks
[i
].data
+ j
, CHUNK_SIZE
- j
);
1582 do_log_error(L_ERROR
, errno
, "Couldn't read");
1586 entry
->offset
+= rc
;
1587 object
->chunks
[i
].size
+= rc
;
1588 if(object
->size
< o
+ rc
)
1589 object
->size
= o
+ rc
;
1591 if(entry
->object
->length
>= 0 && entry
->size
< 0 &&
1592 entry
->offset
- entry
->body_offset
== entry
->object
->length
)
1593 entry
->size
= entry
->object
->length
;
1595 if(rc
< CHUNK_SIZE
- j
) {
1596 /* Paranoia: the read may have been interrupted half-way. */
1597 if(entry
->size
< 0) {
1599 (entry
->object
->length
>= 0 &&
1600 entry
->object
->length
==
1601 entry
->offset
- entry
->body_offset
))
1602 entry
->size
= entry
->offset
- entry
->body_offset
;
1604 } else if(entry
->size
!= entry
->offset
- entry
->body_offset
) {
1606 entry
->size
< entry
->offset
- entry
->body_offset
) {
1608 "Disk entry size changed behind our back: "
1609 "%ld -> %ld (%d).\n",
1611 (long)entry
->offset
- entry
->body_offset
,
1623 CHECK_ENTRY(object
->disk_entry
);
1624 for(k
= 0; k
< chunks
; k
++) {
1625 i
= offset
/ CHUNK_SIZE
+ k
;
1626 unlockChunk(object
, i
);
1630 notifyObject(object
);
1638 writeoutToDisk(ObjectPtr object
, int upto
, int max
)
1640 if(maxDiskCacheEntrySize
>= 0 && object
->size
> maxDiskCacheEntrySize
) {
1641 /* An object was created with an unknown length, and then grew
1642 beyond maxDiskCacheEntrySize. Destroy the disk entry. */
1643 destroyDiskEntry(object
, 1);
1647 return reallyWriteoutToDisk(object
, upto
, max
);
1651 reallyWriteoutToDisk(ObjectPtr object
, int upto
, int max
)
1653 DiskCacheEntryPtr entry
;
1660 upto
= object
->size
;
1662 if((object
->cache_control
& CACHE_NO_STORE
) ||
1663 (object
->flags
& OBJECT_LOCAL
))
1666 if((object
->flags
& OBJECT_DISK_ENTRY_COMPLETE
) && !object
->disk_entry
)
1669 entry
= makeDiskEntry(object
, 1, 1);
1670 if(!entry
) return 0;
1672 assert(!entry
->local
);
1674 if(object
->flags
& OBJECT_DISK_ENTRY_COMPLETE
)
1677 diskEntrySize(object
);
1681 if(object
->length
>= 0 && entry
->size
>= object
->length
) {
1682 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
1686 if(entry
->size
>= upto
)
1689 if(!entry
->writeable
) {
1690 entry
= makeDiskEntry(object
, 1, 1);
1693 if(!entry
->writeable
)
1695 diskEntrySize(object
);
1700 offset
= entry
->size
;
1702 /* Avoid a seek in case we start writing at the beginning */
1703 if(offset
== 0 && entry
->metadataDirty
) {
1704 writeoutMetadata(object
);
1705 /* rewriteDiskEntry may change the entry */
1706 entry
= makeDiskEntry(object
, 1, 0);
1707 if(entry
== NULL
|| !entry
->writeable
)
1711 rc
= entrySeek(entry
, offset
+ entry
->body_offset
);
1712 if(rc
< 0) return 0;
1715 if(max
>= 0 && bytes
>= max
)
1718 assert(entry
->offset
== offset
+ entry
->body_offset
);
1719 i
= offset
/ CHUNK_SIZE
;
1720 j
= offset
% CHUNK_SIZE
;
1721 if(i
>= object
->numchunks
)
1723 if(object
->chunks
[i
].size
<= j
)
1726 rc
= write(entry
->fd
, object
->chunks
[i
].data
+ j
,
1727 object
->chunks
[i
].size
- j
);
1731 do_log_error(L_ERROR
, errno
, "Couldn't write disk entry");
1734 entry
->offset
+= rc
;
1737 if(entry
->size
< offset
)
1738 entry
->size
= offset
;
1739 } while(j
+ rc
>= CHUNK_SIZE
);
1743 if(entry
->metadataDirty
)
1744 writeoutMetadata(object
);
1750 writeoutMetadata(ObjectPtr object
)
1752 DiskCacheEntryPtr entry
;
1755 if((object
->cache_control
& CACHE_NO_STORE
) ||
1756 (object
->flags
& OBJECT_LOCAL
))
1759 entry
= makeDiskEntry(object
, 1, 0);
1760 if(entry
== NULL
|| entry
== &negativeEntry
)
1763 assert(!entry
->local
);
1765 rc
= entrySeek(entry
, 0);
1766 if(rc
< 0) goto fail
;
1768 rc
= writeHeaders(entry
->fd
, &entry
->body_offset
, object
, NULL
, 0);
1770 rc
= rewriteEntry(object
);
1771 if(rc
< 0) return 0;
1774 if(rc
< 0) goto fail
;
1776 entry
->metadataDirty
= 0;
1780 /* We need this in order to avoid trying to write this entry out
1782 if(entry
&& entry
!= &negativeEntry
)
1783 entry
->metadataDirty
= 0;
1788 mergeDobjects(DiskObjectPtr dst
, DiskObjectPtr src
)
1790 if(dst
->filename
== NULL
) {
1791 dst
->filename
= src
->filename
;
1792 dst
->body_offset
= src
->body_offset
;
1794 free(src
->filename
);
1795 free(src
->location
);
1797 dst
->length
= src
->length
;
1799 dst
->size
= src
->size
;
1801 dst
->age
= src
->age
;
1803 dst
->date
= src
->date
;
1804 if(dst
->last_modified
< 0)
1805 dst
->last_modified
= src
->last_modified
;
1810 readDiskObject(char *filename
, struct stat
*sb
)
1812 int fd
, rc
, n
, dummy
, code
;
1814 time_t date
, last_modified
, age
, atime
, expires
;
1815 char *location
= NULL
, *fn
= NULL
;
1816 DiskObjectPtr dobject
;
1818 int buf_is_chunk
, bufsize
;
1821 int noatime
= diskCacheNoAtime
? O_NOATIME
: 0;
1826 rc
= stat(filename
, &ss
);
1828 do_log_error(L_WARN
, errno
, "Couldn't stat %s", filename
);
1835 bufsize
= CHUNK_SIZE
;
1838 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
1842 if(S_ISREG(sb
->st_mode
)) {
1843 fd
= open(filename
, O_RDONLY
| O_BINARY
| noatime
);
1847 rc
= read(fd
, buf
, bufsize
);
1851 n
= findEndOfHeaders(buf
, 0, rc
, &dummy
);
1857 bufsize
= bigBufferSize
;
1858 buf
= malloc(bigBufferSize
);
1861 lrc
= lseek(fd
, 0, SEEK_SET
);
1869 rc
= httpParseServerFirstLine(buf
, &code
, &dummy
, NULL
);
1873 rc
= httpParseHeaders(0, NULL
, buf
, rc
, NULL
,
1874 NULL
, &length
, NULL
, NULL
, NULL
,
1875 &date
, &last_modified
, &expires
, &age
,
1876 &atime
, &body_offset
, NULL
,
1877 NULL
, NULL
, NULL
, NULL
, &location
, NULL
, NULL
);
1878 if(rc
< 0 || location
== NULL
)
1883 size
= sb
->st_size
- body_offset
;
1886 } else if(S_ISDIR(sb
->st_mode
)) {
1888 n
= dirnameUrl(buf
, 512, (char*)filename
, strlen(filename
));
1891 location
= strdup(n
);
1892 if(location
== NULL
)
1905 dobject
= malloc(sizeof(DiskObjectRec
));
1909 fn
= strdup(filename
);
1918 dobject
->location
= location
;
1919 dobject
->filename
= fn
;
1920 dobject
->length
= length
;
1921 dobject
->body_offset
= body_offset
;
1922 dobject
->size
= size
;
1924 dobject
->access
= atime
;
1925 dobject
->date
= date
;
1926 dobject
->last_modified
= last_modified
;
1927 dobject
->expires
= expires
;
1928 if(fd
>= 0) close(fd
);
1937 if(fd
>= 0) close(fd
);
1938 if(location
) free(location
);
1944 processObject(DiskObjectPtr dobjects
, char *filename
, struct stat
*sb
)
1946 DiskObjectPtr dobject
= NULL
;
1949 dobject
= readDiskObject((char*)filename
, sb
);
1954 (c
= strcmp(dobject
->location
, dobjects
->location
)) <= 0) {
1955 if(dobjects
&& c
== 0) {
1956 mergeDobjects(dobjects
, dobject
);
1958 dobject
->next
= dobjects
;
1962 DiskObjectPtr other
= dobjects
;
1963 while(other
->next
) {
1964 c
= strcmp(dobject
->location
, other
->next
->location
);
1967 other
= other
->next
;
1969 if(strcmp(dobject
->location
, other
->location
) == 0) {
1970 mergeDobjects(other
, dobject
);
1972 dobject
->next
= other
->next
;
1973 other
->next
= dobject
;
1979 /* Determine whether p is below root */
1981 filter(DiskObjectPtr p
, const char *root
, int n
, int recursive
)
1984 int m
= strlen(p
->location
);
1987 if(memcmp(root
, p
->location
, n
) != 0)
1991 if(m
== 0 || p
->location
[m
- 1] == '/')
1993 cp
= strchr(p
->location
+ n
, '/');
1994 if(cp
&& cp
- p
->location
!= m
- 1)
1999 /* Filter out all disk objects that are not under root */
2001 filterDiskObjects(DiskObjectPtr from
, const char *root
, int recursive
)
2003 int n
= strlen(root
);
2006 while(from
&& !filter(from
, root
, n
, recursive
)) {
2014 while(p
&& p
->next
) {
2015 if(!filter(p
->next
, root
, n
, recursive
)) {
2028 insertRoot(DiskObjectPtr from
, const char *root
)
2034 if(strcmp(root
, p
->location
) == 0)
2039 p
= malloc(sizeof(DiskObjectRec
));
2041 p
->location
= strdup(root
);
2042 if(p
->location
== NULL
) {
2051 p
->last_modified
= -1;
2057 /* Insert all missing directories in a sorted list of dobjects */
2059 insertDirs(DiskObjectPtr from
)
2061 DiskObjectPtr p
, q
, new;
2067 n
= strlen(q
->location
);
2068 if(n
> 0 && q
->location
[n
- 1] != '/') {
2069 cp
= strrchr(q
->location
, '/');
2070 m
= cp
- q
->location
+ 1;
2071 if(cp
&& (!p
|| strlen(p
->location
) < m
||
2072 memcmp(p
->location
, q
->location
, m
) != 0)) {
2073 new = malloc(sizeof(DiskObjectRec
));
2075 new->location
= strdup_n(q
->location
, m
);
2076 if(new->location
== NULL
) {
2080 new->filename
= NULL
;
2085 new->last_modified
= -1;
2101 indexDiskObjects(FILE *out
, const char *root
, int recursive
)
2105 struct dirent
*dirent
;
2110 DiskObjectPtr dobjects
= NULL
;
2111 char *of
= root
[0] == '\0' ? "" : " of ";
2113 fprintf(out
, "<!DOCTYPE HTML PUBLIC "
2114 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
2115 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
2117 "<title>%s%s%s</title>\n"
2119 "<h1>%s%s%s</h1>\n",
2120 recursive
? "Recursive index" : "Index", of
, root
,
2121 recursive
? "Recursive index" : "Index", of
, root
);
2123 if(diskCacheRoot
== NULL
|| diskCacheRoot
->length
<= 0) {
2124 fprintf(out
, "<p>No <tt>diskCacheRoot</tt>.</p>\n");
2128 if(diskCacheRoot
->length
>= 1024) {
2130 "<p>The value of <tt>diskCacheRoot</tt> is "
2131 "too long (%d).</p>\n",
2132 diskCacheRoot
->length
);
2136 if(strlen(root
) < 8) {
2137 memcpy(buf
, diskCacheRoot
->string
, diskCacheRoot
->length
);
2138 buf
[diskCacheRoot
->length
] = '\0';
2139 n
= diskCacheRoot
->length
;
2141 n
= urlDirname(buf
, 1024, root
, strlen(root
));
2148 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
2153 if(fe
->fts_info
!= FTS_DP
)
2155 processObject(dobjects
,
2157 fe
->fts_info
== FTS_NS
||
2158 fe
->fts_info
== FTS_NSOK
?
2159 fe
->fts_statp
: NULL
);
2167 dirent
= readdir(dir
);
2169 if(n
+ strlen(dirent
->d_name
) < 1024) {
2170 strcpy(buf
+ n
, dirent
->d_name
);
2174 dobjects
= processObject(dobjects
, buf
, NULL
);
2178 fprintf(out
, "<p>Couldn't open directory: %s (%d).</p>\n",
2179 strerror(errno
), errno
);
2186 DiskObjectPtr dobject
;
2188 dobjects
= insertRoot(dobjects
, root
);
2189 dobjects
= insertDirs(dobjects
);
2190 dobjects
= filterDiskObjects(dobjects
, root
, recursive
);
2193 alternatingHttpStyle(out
, "diskcachelist");
2194 fprintf(out
, "<table id=diskcachelist>\n");
2195 fprintf(out
, "<tbody>\n");
2199 i
= strlen(dobject
->location
);
2200 isdir
= (i
== 0 || dobject
->location
[i
- 1] == '/');
2202 fprintf(out
, "<tr class=odd>");
2204 fprintf(out
, "<tr class=even>");
2205 if(dobject
->size
>= 0) {
2206 fprintf(out
, "<td><a href=\"%s\"><tt>",
2209 dobject
->location
, strlen(dobject
->location
));
2210 fprintf(out
, "</tt></a></td> ");
2211 if(dobject
->length
>= 0) {
2212 if(dobject
->size
== dobject
->length
)
2213 fprintf(out
, "<td>%d</td> ", dobject
->length
);
2215 fprintf(out
, "<td>%d/%d</td> ",
2216 dobject
->size
, dobject
->length
);
2218 /* Avoid a trigraph. */
2219 fprintf(out
, "<td>%d/<em>??" "?</em></td> ", dobject
->size
);
2221 if(dobject
->last_modified
>= 0) {
2222 struct tm
*tm
= gmtime(&dobject
->last_modified
);
2226 n
= strftime(buf
, 1024, "%d.%m.%Y", tm
);
2231 fprintf(out
, "<td>%s</td> ", buf
);
2233 fprintf(out
, "<td></td>");
2236 if(dobject
->date
>= 0) {
2237 struct tm
*tm
= gmtime(&dobject
->date
);
2241 n
= strftime(buf
, 1024, "%d.%m.%Y", tm
);
2246 fprintf(out
, "<td>%s</td>", buf
);
2248 fprintf(out
, "<td></td>");
2251 fprintf(out
, "<td><tt>");
2252 htmlPrint(out
, dobject
->location
,
2253 strlen(dobject
->location
));
2254 fprintf(out
, "</tt></td><td></td><td></td><td></td>");
2257 fprintf(out
, "<td><a href=\"/polipo/index?%s\">plain</a></td>"
2258 "<td><a href=\"/polipo/recursive-index?%s\">"
2259 "recursive</a></td>",
2260 dobject
->location
, dobject
->location
);
2262 fprintf(out
, "</tr>\n");
2264 dobjects
= dobject
->next
;
2265 free(dobject
->location
);
2266 free(dobject
->filename
);
2269 fprintf(out
, "</tbody>\n");
2270 fprintf(out
, "</table>\n");
2274 fprintf(out
, "<p><a href=\"/polipo/\">back</a></p>\n");
2275 fprintf(out
, "</body></html>\n");
2280 checkForZeroes(char *buf
, int n
)
2283 unsigned long *lbuf
= (unsigned long *)buf
;
2284 assert(n
% sizeof(unsigned long) == 0);
2286 for(i
= 0; i
* sizeof(unsigned long) < n
; i
++) {
2288 return i
* sizeof(unsigned long);
2290 for(j
= 0; i
* sizeof(unsigned long) + j
< n
; j
++) {
2291 if(buf
[i
* sizeof(unsigned long) + j
] != 0)
2295 return i
* sizeof(unsigned long) + j
;
2299 copyFile(int from
, char *filename
, int n
)
2302 int to
, offset
, nread
, nzeroes
, rc
;
2304 buf
= malloc(CHUNK_SIZE
);
2308 to
= open(filename
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
,
2309 diskCacheFilePermissions
);
2317 nread
= read(from
, buf
, MIN(CHUNK_SIZE
, n
- offset
));
2320 nzeroes
= checkForZeroes(buf
, nread
& -8);
2323 rc
= lseek(to
, nzeroes
, SEEK_CUR
);
2324 if(rc
!= offset
+ nzeroes
) {
2326 do_log_error(L_ERROR
, errno
, "Couldn't extend file");
2329 "Couldn't extend file: "
2330 "unexpected offset %d != %d + %d.\n",
2335 if(nread
> nzeroes
) {
2336 rc
= write(to
, buf
+ nzeroes
, nread
- nzeroes
);
2337 if(rc
!= nread
- nzeroes
) {
2339 do_log_error(L_ERROR
, errno
, "Couldn't write");
2341 do_log(L_ERROR
, "Short write.\n");
2350 unlink(filename
); /* something went wrong straight away */
2355 expireFile(char *filename
, struct stat
*sb
,
2356 int *considered
, int *unlinked
, int *truncated
)
2358 DiskObjectPtr dobject
= NULL
;
2361 long int ret
= sb
->st_size
;
2363 if(!preciseExpiry
) {
2365 if(t
> current_time
.tv_sec
+ 1) {
2366 do_log(L_WARN
, "File %s has access time in the future.\n",
2368 t
= current_time
.tv_sec
;
2371 if(t
> current_time
.tv_sec
- diskCacheUnlinkTime
&&
2372 (sb
->st_size
< diskCacheTruncateSize
||
2373 t
> current_time
.tv_sec
- diskCacheTruncateTime
))
2379 dobject
= readDiskObject(filename
, sb
);
2381 do_log(L_ERROR
, "Incorrect disk entry %s -- removing.\n", filename
);
2382 rc
= unlink(filename
);
2384 do_log_error(L_ERROR
, errno
,
2385 "Couldn't unlink %s", filename
);
2393 t
= dobject
->access
;
2394 if(t
< 0) t
= dobject
->age
;
2395 if(t
< 0) t
= dobject
->date
;
2397 if(t
> current_time
.tv_sec
)
2399 "Disk entry %s (%s) has access time in the future.\n",
2400 dobject
->location
, dobject
->filename
);
2402 if(t
< current_time
.tv_sec
- diskCacheUnlinkTime
) {
2403 rc
= unlink(dobject
->filename
);
2405 do_log_error(L_ERROR
, errno
, "Couldn't unlink %s", filename
);
2410 } else if(dobject
->size
>
2411 diskCacheTruncateSize
+ 4 * dobject
->body_offset
&&
2412 t
< current_time
.tv_sec
- diskCacheTruncateTime
) {
2413 /* We need to copy rather than simply truncate in place: the
2414 latter would confuse a running polipo. */
2415 fd
= open(dobject
->filename
, O_RDONLY
| O_BINARY
, 0);
2416 rc
= unlink(dobject
->filename
);
2418 do_log_error(L_ERROR
, errno
, "Couldn't unlink %s", filename
);
2423 copyFile(fd
, dobject
->filename
,
2424 dobject
->body_offset
+ diskCacheTruncateSize
);
2428 ret
= sb
->st_size
- dobject
->body_offset
+ diskCacheTruncateSize
;
2431 free(dobject
->location
);
2432 free(dobject
->filename
);
2444 int files
= 0, considered
= 0, unlinked
= 0, truncated
= 0;
2445 int dirs
= 0, rmdirs
= 0;
2446 long left
= 0, total
= 0;
2448 if(diskCacheRoot
== NULL
||
2449 diskCacheRoot
->length
<= 0 || diskCacheRoot
->string
[0] != '/')
2452 fts_argv
[0] = diskCacheRoot
->string
;
2454 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
2456 do_log_error(L_ERROR
, errno
, "Couldn't fts_open disk cache");
2459 gettimeofday(¤t_time
, NULL
);
2464 if(fe
->fts_info
== FTS_D
)
2467 if(fe
->fts_info
== FTS_DP
|| fe
->fts_info
== FTS_DC
||
2468 fe
->fts_info
== FTS_DNR
) {
2469 if(fe
->fts_accpath
[0] == '/' &&
2470 strlen(fe
->fts_accpath
) <= diskCacheRoot
->length
)
2473 rc
= rmdir(fe
->fts_accpath
);
2476 else if(errno
!= ENOTEMPTY
&& errno
!= EEXIST
)
2477 do_log_error(L_ERROR
, errno
,
2478 "Couldn't remove directory %s",
2481 } else if(fe
->fts_info
== FTS_NS
) {
2482 do_log_error(L_ERROR
, fe
->fts_errno
, "Couldn't stat file %s",
2485 } else if(fe
->fts_info
== FTS_ERR
) {
2486 do_log_error(L_ERROR
, fe
->fts_errno
,
2487 "Couldn't fts_read disk cache");
2491 if(!S_ISREG(fe
->fts_statp
->st_mode
)) {
2492 do_log(L_ERROR
, "Unexpected file %s type 0%o.\n",
2493 fe
->fts_accpath
, (unsigned int)fe
->fts_statp
->st_mode
);
2498 left
+= expireFile(fe
->fts_accpath
, fe
->fts_statp
,
2499 &considered
, &unlinked
, &truncated
);
2500 total
+= fe
->fts_statp
->st_size
;
2505 printf("Disk cache purged.\n");
2506 printf("%d files, %d considered, %d removed, %d truncated "
2507 "(%ldkB -> %ldkB).\n",
2508 files
, considered
, unlinked
, truncated
, total
/1024, left
/1024);
2509 printf("%d directories, %d removed.\n", dirs
, rmdirs
);
2528 writeoutToDisk(ObjectPtr object
, int upto
, int max
)
2534 destroyDiskEntry(ObjectPtr object
, int d
)
2540 objectGetFromDisk(ObjectPtr object
)
2546 objectFillFromDisk(ObjectPtr object
, int offset
, int chunks
)
2552 revalidateDiskEntry(ObjectPtr object
)
2558 dirtyDiskEntry(ObjectPtr object
)
2566 do_log(L_ERROR
, "Disk cache not supported in this version.\n");
2570 diskEntrySize(ObjectPtr object
)