2 Copyright (c) 2003-2010 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 diskCacheWriteoutOnClose
= (64 * 1024);
43 int maxDiskCacheEntrySize
= -1;
45 int diskCacheUnlinkTime
= 32 * 24 * 60 * 60;
46 int diskCacheTruncateTime
= 4 * 24 * 60 * 60 + 12 * 60 * 60;
47 int diskCacheTruncateSize
= 1024 * 1024;
48 int preciseExpiry
= 0;
50 static DiskCacheEntryRec negativeEntry
= {
52 -1, -1, -1, -1, 0, 0, NULL
, NULL
56 #define LOCAL_ROOT "/usr/share/polipo/www/"
59 #ifndef DISK_CACHE_ROOT
60 #define DISK_CACHE_ROOT "/var/cache/polipo/"
63 static int maxDiskEntriesSetter(ConfigVariablePtr
, void*);
64 static int atomSetterFlush(ConfigVariablePtr
, void*);
65 static int reallyWriteoutToDisk(ObjectPtr object
, int upto
, int max
);
70 diskCacheRoot
= internAtom(DISK_CACHE_ROOT
);
71 localDocumentRoot
= internAtom(LOCAL_ROOT
);
73 CONFIG_VARIABLE_SETTABLE(diskCacheDirectoryPermissions
, CONFIG_OCTAL
,
75 "Access rights for new directories.");
76 CONFIG_VARIABLE_SETTABLE(diskCacheFilePermissions
, CONFIG_OCTAL
,
78 "Access rights for new cache files.");
79 CONFIG_VARIABLE_SETTABLE(diskCacheWriteoutOnClose
, CONFIG_INT
,
81 "Number of bytes to write out eagerly.");
82 CONFIG_VARIABLE_SETTABLE(diskCacheRoot
, CONFIG_ATOM
, atomSetterFlush
,
83 "Root of the disk cache.");
84 CONFIG_VARIABLE_SETTABLE(localDocumentRoot
, CONFIG_ATOM
, atomSetterFlush
,
85 "Root of the local tree.");
86 CONFIG_VARIABLE_SETTABLE(maxDiskEntries
, CONFIG_INT
, maxDiskEntriesSetter
,
87 "File descriptors used by the on-disk cache.");
88 CONFIG_VARIABLE(diskCacheUnlinkTime
, CONFIG_TIME
,
89 "Time after which on-disk objects are removed.");
90 CONFIG_VARIABLE(diskCacheTruncateTime
, CONFIG_TIME
,
91 "Time after which on-disk objects are truncated.");
92 CONFIG_VARIABLE(diskCacheTruncateSize
, CONFIG_INT
,
93 "Size to which on-disk objects are truncated.");
94 CONFIG_VARIABLE(preciseExpiry
, CONFIG_BOOLEAN
,
95 "Whether to consider all files for purging.");
96 CONFIG_VARIABLE_SETTABLE(maxDiskCacheEntrySize
, CONFIG_INT
,
98 "Maximum size of objects cached on disk.");
102 maxDiskEntriesSetter(ConfigVariablePtr var
, void *value
)
105 assert(var
->type
== CONFIG_INT
&& var
->value
.i
== &maxDiskEntries
);
107 if(i
< 0 || i
> 1000000)
110 while(numDiskEntries
> maxDiskEntries
)
111 destroyDiskEntry(diskEntriesLast
->object
, 0);
116 atomSetterFlush(ConfigVariablePtr var
, void *value
)
118 discardObjects(1, 0);
119 return configAtomSetter(var
, value
);
123 checkRoot(AtomPtr root
)
128 if(!root
|| root
->length
== 0)
131 if(root
->string
[0] != '/') {
135 rc
= stat(root
->string
, &ss
);
138 else if(!S_ISDIR(ss
.st_mode
)) {
146 maybeAddSlash(AtomPtr atom
)
148 AtomPtr newAtom
= NULL
;
149 if(!atom
) return NULL
;
150 if(atom
->length
> 0 && atom
->string
[atom
->length
- 1] != '/') {
151 newAtom
= atomCat(atom
, "/");
163 diskCacheRoot
= expandTilde(maybeAddSlash(diskCacheRoot
));
164 rc
= checkRoot(diskCacheRoot
);
168 case -1: do_log_error(L_WARN
, errno
, "Disabling disk cache"); break;
170 do_log(L_WARN
, "Disabling disk cache: path %s is not absolute.\n",
171 diskCacheRoot
->string
);
175 releaseAtom(diskCacheRoot
);
176 diskCacheRoot
= NULL
;
179 localDocumentRoot
= expandTilde(maybeAddSlash(localDocumentRoot
));
180 rc
= checkRoot(localDocumentRoot
);
184 case -1: do_log_error(L_WARN
, errno
, "Disabling local tree"); break;
186 do_log(L_WARN
, "Disabling local tree: path is not absolute.\n");
190 releaseAtom(localDocumentRoot
);
191 localDocumentRoot
= NULL
;
195 #ifdef DEBUG_DISK_CACHE
196 #define CHECK_ENTRY(entry) check_entry((entry))
198 check_entry(DiskCacheEntryPtr entry
)
200 if(entry
&& entry
->fd
< 0)
201 assert(entry
== &negativeEntry
);
202 if(entry
&& entry
->fd
>= 0) {
203 assert((!entry
->previous
) == (entry
== diskEntries
));
204 assert((!entry
->next
) == (entry
== diskEntriesLast
));
206 assert(entry
->size
+ entry
->body_offset
>= entry
->offset
);
207 assert(entry
->body_offset
>= 0);
208 if(entry
->offset
>= 0) {
210 offset
= lseek(entry
->fd
, 0, SEEK_CUR
);
211 assert(offset
== entry
->offset
);
213 if(entry
->size
>= 0) {
216 rc
= fstat(entry
->fd
, &ss
);
218 assert(ss
.st_size
== entry
->size
+ entry
->body_offset
);
223 #define CHECK_ENTRY(entry) do {} while(0)
227 diskEntrySize(ObjectPtr object
)
231 DiskCacheEntryPtr entry
= object
->disk_entry
;
233 if(!entry
|| entry
== &negativeEntry
)
239 rc
= fstat(entry
->fd
, &buf
);
241 do_log_error(L_ERROR
, errno
, "Couldn't stat");
245 if(buf
.st_size
<= entry
->body_offset
)
248 entry
->size
= buf
.st_size
- entry
->body_offset
;
250 if(object
->length
>= 0 && entry
->size
== object
->length
)
251 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
256 entrySeek(DiskCacheEntryPtr entry
, off_t offset
)
261 assert(entry
!= &negativeEntry
);
262 if(entry
->offset
== offset
)
264 if(offset
> entry
->body_offset
) {
265 /* Avoid extending the file by mistake */
267 diskEntrySize(entry
->object
);
270 if(entry
->size
+ entry
->body_offset
< offset
)
273 rc
= lseek(entry
->fd
, offset
, SEEK_SET
);
275 do_log_error(L_ERROR
, errno
, "Couldn't seek");
279 entry
->offset
= offset
;
283 /* Given a local URL, constructs the filename where it can be found. */
286 localFilename(char *buf
, int n
, char *key
, int len
)
289 if(len
<= 0 || key
[0] != '/') return -1;
291 if(urlIsSpecial(key
, len
)) return -1;
293 if(localDocumentRoot
== NULL
||
294 localDocumentRoot
->length
<= 0 || localDocumentRoot
->string
[0] != '/')
297 if(n
<= localDocumentRoot
->length
)
304 memcpy(buf
, localDocumentRoot
->string
, localDocumentRoot
->length
);
305 j
= localDocumentRoot
->length
;
306 if(buf
[j
- 1] == '/')
312 if(key
[i
] == '/' && i
< len
- 2)
313 if(key
[i
+ 1] == '.' &&
314 (key
[i
+ 2] == '.' || key
[i
+ 2] == '/'))
319 if(buf
[j
- 1] == '/') {
322 memcpy(buf
+ j
, "index.html", 10);
331 md5(unsigned char *restrict key
, int len
, unsigned char *restrict dst
)
335 MD5Update(&ctx
, key
, len
);
337 memcpy(dst
, ctx
.digest
, 16);
340 /* Check whether a character can be stored in a filename. This is
341 needed since we want to support deficient file systems. */
345 if(c
<= 31 || c
>= 127)
347 if((c
>= 'a' && c
<= 'z') || (c
>= 'A' && c
<= 'Z') ||
348 (c
>= '0' && c
<= '9') || c
== '.' || c
== '-' || c
== '_')
353 /* Given a URL, returns the directory name within which all files
354 starting with this URL can be found. */
356 urlDirname(char *buf
, int n
, const char *url
, int len
)
361 if(memcmp(url
, "http://", 7) != 0)
364 if(diskCacheRoot
== NULL
||
365 diskCacheRoot
->length
<= 0 || diskCacheRoot
->string
[0] != '/')
368 if(n
<= diskCacheRoot
->length
)
371 memcpy(buf
, diskCacheRoot
->string
, diskCacheRoot
->length
);
372 j
= diskCacheRoot
->length
;
374 if(buf
[j
- 1] != '/')
377 for(i
= 7; i
< len
; i
++) {
378 if(i
>= len
|| url
[i
] == '/')
380 if(url
[i
] == '.' && i
!= len
- 1 && url
[i
+ 1] == '.')
382 if(url
[i
] == '%' || !fssafe(url
[i
])) {
383 if(j
+ 3 >= n
) return -1;
385 buf
[j
++] = i2h((url
[i
] & 0xF0) >> 4);
386 buf
[j
++] = i2h(url
[i
] & 0x0F);
388 buf
[j
++] = url
[i
]; if(j
>= n
) return -1;
391 buf
[j
++] = '/'; if(j
>= n
) return -1;
396 /* Given a URL, returns the filename where the cached data can be
399 urlFilename(char *restrict buf
, int n
, const char *url
, int len
)
402 unsigned char md5buf
[18];
403 j
= urlDirname(buf
, n
, url
, len
);
404 if(j
< 0 || j
+ 24 >= n
)
406 md5((unsigned char*)url
, len
, md5buf
);
407 b64cpy(buf
+ j
, (char*)md5buf
, 16, 1);
413 dirnameUrl(char *url
, int n
, char *name
, int len
)
416 k
= diskCacheRoot
->length
;
419 if(memcmp(name
, diskCacheRoot
->string
, k
) != 0)
423 memcpy(url
, "http://", 7);
424 if(name
[len
- 1] == '/')
427 for(i
= k
; i
< len
; i
++) {
431 c1
= h2i(name
[i
+ 1]);
432 c2
= h2i(name
[i
+ 2]);
435 url
[j
++] = c1
* 16 + c2
; if(j
>= n
) goto fail
;
436 i
+= 2; /* skip extra digits */
437 } else if(i
< len
- 1 &&
438 name
[i
] == '.' && name
[i
+ 1] == '/') {
440 } else if(i
== len
- 1 && name
[i
] == '.') {
443 url
[j
++] = name
[i
]; if(j
>= n
) goto fail
;
446 url
[j
++] = '/'; if(j
>= n
) goto fail
;
454 /* Create a file and all intermediate directories. */
456 createFile(const char *name
, int path_start
)
463 if(name
[path_start
] == '/')
466 if(path_start
< 2 || name
[path_start
- 1] != '/' ) {
467 do_log(L_ERROR
, "Incorrect name %s (%d).\n", name
, path_start
);
471 fd
= open(name
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
,
472 diskCacheFilePermissions
);
475 if(errno
!= ENOENT
) {
476 do_log_error(L_ERROR
, errno
, "Couldn't create disk file %s", name
);
481 while(name
[n
] != '\0' && n
< 1024) {
482 while(name
[n
] != '/' && name
[n
] != '\0' && n
< 512)
484 if(name
[n
] != '/' || n
>= 1024)
486 memcpy(buf
, name
, n
+ 1);
488 rc
= mkdir(buf
, diskCacheDirectoryPermissions
);
489 if(rc
< 0 && errno
!= EEXIST
) {
490 do_log_error(L_ERROR
, errno
, "Couldn't create directory %s", buf
);
495 fd
= open(name
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
,
496 diskCacheFilePermissions
);
498 do_log_error(L_ERROR
, errno
, "Couldn't create file %s", name
);
506 chooseBodyOffset(int n
, ObjectPtr object
)
508 int length
= MAX(object
->size
, object
->length
);
511 if(object
->length
>= 0 && object
->length
+ n
< 4096 - 4)
512 return -1; /* no gap for small objects */
529 body_offset
= ((n
+ 32 + 4095) / 4096 + 1) * 4096;
531 /* Tweak the gap so that we don't use up a full disk block for
533 if(object
->length
>= 0 && object
->length
< 64 * 1024) {
534 int last
= (body_offset
+ object
->length
) % 4096;
535 int gap
= body_offset
- n
- 32;
540 /* Rewriting large objects is expensive -- don't use small gaps.
541 This has the additional benefit of block-aligning large bodies. */
542 if(length
>= 64 * 1024) {
543 int min_gap
, min_offset
;
544 if(length
>= 512 * 1024)
546 else if(length
>= 256 * 1024)
551 min_offset
= ((n
+ 32 + min_gap
- 1) / min_gap
+ 1) * min_gap
;
552 body_offset
= MAX(body_offset
, min_offset
);
558 /* Assumes the file descriptor is at offset 0. Returns -1 on failure,
559 otherwise the offset at which the file descriptor is left. */
560 /* If chunk is not null, it should be the first chunk of the object,
561 and will be written out in the same operation if possible. */
563 writeHeaders(int fd
, int *body_offset_return
,
564 ObjectPtr object
, char *chunk
, int chunk_len
)
566 int n
, rc
, error
= -1;
567 int body_offset
= *body_offset_return
;
569 int buf_is_chunk
= 0;
572 if(object
->flags
& OBJECT_LOCAL
)
575 if(body_offset
> CHUNK_SIZE
)
578 /* get_chunk might trigger object expiry */
579 bufsize
= CHUNK_SIZE
;
581 buf
= maybe_get_chunk();
587 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
593 n
= snnprintf(buf
, 0, bufsize
, "HTTP/1.1 %3d %s",
594 object
->code
, object
->message
->string
);
596 n
= httpWriteObjectHeaders(buf
, n
, bufsize
, object
, 0, -1);
600 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Location: ");
601 n
= snnprint_n(buf
, n
, bufsize
, object
->key
, object
->key_size
);
603 if(object
->age
>= 0 && object
->age
!= object
->date
) {
604 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Date: ");
605 n
= format_time(buf
, n
, bufsize
, object
->age
);
608 if(object
->atime
>= 0) {
609 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Access: ");
610 n
= format_time(buf
, n
, bufsize
, object
->atime
);
617 body_offset
= chooseBodyOffset(n
, object
);
619 if(body_offset
> bufsize
)
622 if(body_offset
> 0 && body_offset
!= n
+ 4)
623 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Body-Offset: %d",
626 n
= snnprintf(buf
, n
, bufsize
, "\r\n\r\n");
632 if(n
> body_offset
) {
638 memset(buf
+ n
, 0, body_offset
- n
);
641 #ifdef HAVE_READV_WRITEV
644 iov
[0].iov_base
= buf
;
645 iov
[0].iov_len
= body_offset
;
646 iov
[1].iov_base
= chunk
;
647 iov
[1].iov_len
= chunk_len
;
648 rc
= writev(fd
, iov
, 2);
651 rc
= write(fd
, buf
, body_offset
);
653 if(rc
< 0 && errno
== EINTR
)
658 if(object
->length
>= 0 &&
659 rc
- body_offset
>= object
->length
)
660 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
662 *body_offset_return
= body_offset
;
670 if(bufsize
< bigBufferSize
) {
672 buf
= malloc(bigBufferSize
);
674 do_log(L_ERROR
, "Couldn't allocate big buffer.\n");
677 bufsize
= bigBufferSize
;
680 dispose_chunk(oldbuf
);
697 typedef struct _MimeEntry
{
702 static const MimeEntryRec mimeEntries
[] = {
703 { "html", "text/html" },
704 { "htm", "text/html" },
705 { "text", "text/plain" },
706 { "txt", "text/plain" },
707 { "png", "image/png" },
708 { "gif", "image/gif" },
709 { "jpeg", "image/jpeg" },
710 { "jpg", "image/jpeg" },
711 { "ico", "image/x-icon" },
712 { "pdf", "application/pdf" },
713 { "ps", "application/postscript" },
714 { "tar", "application/x-tar" },
715 { "pac", "application/x-ns-proxy-autoconfig" },
716 { "css", "text/css" },
717 { "js", "application/x-javascript" },
718 { "xml", "text/xml" },
719 { "swf", "application/x-shockwave-flash" },
723 localObjectMimeType(ObjectPtr object
, char **encoding_return
)
725 char *name
= object
->key
;
726 int nlen
= object
->key_size
;
731 if(name
[nlen
- 1] == '/') {
732 *encoding_return
= NULL
;
737 *encoding_return
= NULL
;
738 return "application/octet-stream";
741 if(memcmp(name
+ nlen
- 3, ".gz", 3) == 0) {
742 *encoding_return
= "x-gzip";
744 } else if(memcmp(name
+ nlen
- 2, ".Z", 2) == 0) {
745 *encoding_return
= "x-compress";
748 *encoding_return
= NULL
;
751 for(i
= 0; i
< sizeof(mimeEntries
) / sizeof(mimeEntries
[0]); i
++) {
752 int len
= strlen(mimeEntries
[i
].extension
);
754 name
[nlen
- len
- 1] == '.' &&
755 memcmp(name
+ nlen
- len
, mimeEntries
[i
].extension
, len
) == 0)
756 return mimeEntries
[i
].mime
;
759 return "application/octet-stream";
762 /* Same interface as validateEntry -- see below */
764 validateLocalEntry(ObjectPtr object
, int fd
,
765 int *body_offset_return
, off_t
*offset_return
)
774 do_log_error(L_ERROR
, errno
, "Couldn't stat");
778 if(S_ISREG(ss
.st_mode
)) {
779 if(!(ss
.st_mode
& S_IROTH
) ||
780 (object
->length
>= 0 && object
->length
!= ss
.st_size
) ||
781 (object
->last_modified
>= 0 &&
782 object
->last_modified
!= ss
.st_mtime
))
785 notifyObject(object
);
789 n
= snnprintf(buf
, 0, 512, "%lx-%lx-%lx",
790 (unsigned long)ss
.st_ino
,
791 (unsigned long)ss
.st_size
,
792 (unsigned long)ss
.st_mtime
);
796 if(n
> 0 && object
->etag
) {
797 if(strlen(object
->etag
) != n
||
798 memcmp(object
->etag
, buf
, n
) != 0)
802 if(!(object
->flags
& OBJECT_INITIAL
)) {
803 if(!object
->last_modified
&& !object
->etag
)
807 if(object
->flags
& OBJECT_INITIAL
) {
808 object
->length
= ss
.st_size
;
809 object
->last_modified
= ss
.st_mtime
;
810 object
->date
= current_time
.tv_sec
;
811 object
->age
= current_time
.tv_sec
;
814 object
->etag
= strdup(buf
); /* okay if fails */
815 object
->message
= internAtom("Okay");
816 n
= snnprintf(buf
, 0, 512,
818 "\r\nContent-Type: %s",
819 localObjectMimeType(object
, &encoding
));
821 n
= snnprintf(buf
, n
, 512,
822 "\r\nContent-Encoding: %s", encoding
);
825 object
->headers
= internAtomN(buf
, n
);
826 if(object
->headers
== NULL
)
828 object
->flags
&= ~OBJECT_INITIAL
;
831 if(body_offset_return
)
832 *body_offset_return
= 0;
838 /* Assumes fd is at offset 0.
839 Returns -1 if not valid, 1 if metadata should be written out, 0
842 validateEntry(ObjectPtr object
, int fd
,
843 int *body_offset_return
, off_t
*offset_return
)
846 int buf_is_chunk
, bufsize
;
851 time_t date
, last_modified
, expires
, polipo_age
, polipo_access
;
857 CacheControlRec cache_control
;
862 if(object
->flags
& OBJECT_LOCAL
)
863 return validateLocalEntry(object
, fd
,
864 body_offset_return
, offset_return
);
866 if(!(object
->flags
& OBJECT_PUBLIC
) && (object
->flags
& OBJECT_INITIAL
))
869 /* get_chunk might trigger object expiry */
870 bufsize
= CHUNK_SIZE
;
872 buf
= maybe_get_chunk();
878 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
884 rc
= read(fd
, buf
, bufsize
);
888 do_log_error(L_ERROR
, errno
, "Couldn't read disk entry");
894 n
= findEndOfHeaders(buf
, 0, rc
, &dummy
);
897 if(bufsize
< bigBufferSize
) {
898 buf
= malloc(bigBufferSize
);
900 do_log(L_ERROR
, "Couldn't allocate big buffer.\n");
903 bufsize
= bigBufferSize
;
904 memcpy(buf
, oldbuf
, offset
);
906 dispose_chunk(oldbuf
);
911 rc
= read(fd
, buf
+ offset
, bufsize
- offset
);
915 do_log_error(L_ERROR
, errno
, "Couldn't read disk entry");
921 do_log(L_ERROR
, "Couldn't parse disk entry.\n");
925 rc
= httpParseServerFirstLine(buf
, &code
, &dummy
, &message
);
927 do_log(L_ERROR
, "Couldn't parse disk entry.\n");
931 if(object
->code
!= 0 && object
->code
!= code
) {
932 releaseAtom(message
);
936 rc
= httpParseHeaders(0, NULL
, buf
, rc
, NULL
,
937 &headers
, &length
, &cache_control
, NULL
, NULL
,
938 &date
, &last_modified
, &expires
, &polipo_age
,
939 &polipo_access
, &body_offset
,
941 NULL
, NULL
, &location
, &via
, NULL
);
943 releaseAtom(message
);
949 if(!location
|| strlen(location
) != object
->key_size
||
950 memcmp(location
, object
->key
, object
->key_size
) != 0) {
951 do_log(L_ERROR
, "Inconsistent cache file for %s.\n", scrub(location
));
959 do_log(L_ERROR
, "Undated disk entry for %s.\n", scrub(location
));
963 if(!(object
->flags
& OBJECT_INITIAL
)) {
964 if((last_modified
>= 0) != (object
->last_modified
>= 0))
967 if((object
->cache_control
& CACHE_MISMATCH
) ||
968 (cache_control
.flags
& CACHE_MISMATCH
))
971 if(last_modified
>= 0 && object
->last_modified
>= 0 &&
972 last_modified
!= object
->last_modified
)
975 if(length
>= 0 && object
->length
>= 0)
976 if(length
!= object
->length
)
979 if(!!etag
!= !!object
->etag
)
982 if(etag
&& object
->etag
&& strcmp(etag
, object
->etag
) != 0)
985 /* If we don't have a usable ETag, and either CACHE_VARY or we
986 don't have a last-modified date, we validate disk entries by
988 if(!(etag
&& object
->etag
) &&
989 (!(last_modified
>= 0 && object
->last_modified
>= 0) ||
990 ((cache_control
.flags
& CACHE_VARY
) ||
991 (object
->cache_control
& CACHE_VARY
)))) {
992 if(date
>= 0 && date
!= object
->date
)
994 if(polipo_age
>= 0 && polipo_age
!= object
->age
)
997 if((object
->cache_control
& CACHE_VARY
) && dontTrustVaryETag
>= 1) {
998 /* Check content-type to work around mod_gzip bugs */
999 if(!httpHeaderMatch(atomContentType
, object
->headers
, headers
) ||
1000 !httpHeaderMatch(atomContentEncoding
, object
->headers
, headers
))
1009 if(!object
->headers
)
1010 object
->headers
= headers
;
1012 releaseAtom(headers
);
1015 if(object
->code
== 0) {
1016 object
->code
= code
;
1017 object
->message
= retainAtom(message
);
1019 if(object
->date
<= date
)
1020 object
->date
= date
;
1023 if(object
->last_modified
< 0)
1024 object
->last_modified
= last_modified
;
1025 if(object
->expires
< 0)
1026 object
->expires
= expires
;
1027 else if(object
->expires
> expires
)
1030 object
->age
= polipo_age
;
1031 else if(object
->age
> polipo_age
)
1033 if(object
->atime
<= polipo_access
)
1034 object
->atime
= polipo_access
;
1038 object
->cache_control
|= cache_control
.flags
;
1040 if(object
->age
< 0) object
->age
= object
->date
;
1041 if(object
->age
< 0) object
->age
= 0; /* a long time ago */
1042 if(object
->length
< 0) object
->length
= length
;
1044 object
->etag
= etag
;
1049 releaseAtom(message
);
1051 if(object
->flags
& OBJECT_INITIAL
) object
->via
= via
;
1052 object
->flags
&= ~OBJECT_INITIAL
;
1053 if(offset
> body_offset
) {
1054 /* We need to make sure we don't invoke object expiry recursively */
1055 objectSetChunks(object
, 1);
1056 if(object
->numchunks
>= 1) {
1057 if(object
->chunks
[0].data
== NULL
)
1058 object
->chunks
[0].data
= maybe_get_chunk();
1059 if(object
->chunks
[0].data
)
1060 objectAddData(object
, buf
+ body_offset
,
1061 0, MIN(offset
- body_offset
, CHUNK_SIZE
));
1065 httpTweakCachability(object
);
1071 if(body_offset_return
) *body_offset_return
= body_offset
;
1072 if(offset_return
) *offset_return
= offset
;
1076 releaseAtom(message
);
1077 if(etag
) free(etag
);
1078 if(location
) free(location
);
1079 if(via
) releaseAtom(via
);
1091 dirtyDiskEntry(ObjectPtr object
)
1093 DiskCacheEntryPtr entry
= object
->disk_entry
;
1094 if(entry
&& entry
!= &negativeEntry
) entry
->metadataDirty
= 1;
1098 revalidateDiskEntry(ObjectPtr object
)
1100 DiskCacheEntryPtr entry
= object
->disk_entry
;
1104 if(!entry
|| entry
== &negativeEntry
)
1108 rc
= entrySeek(entry
, 0);
1109 if(rc
< 0) return 0;
1111 rc
= validateEntry(object
, entry
->fd
, &body_offset
, &entry
->offset
);
1113 destroyDiskEntry(object
, 0);
1116 if(body_offset
!= entry
->body_offset
) {
1117 do_log(L_WARN
, "Inconsistent body offset (%d != %d).\n",
1118 body_offset
, entry
->body_offset
);
1119 destroyDiskEntry(object
, 0);
1123 entry
->metadataDirty
|= !!rc
;
1129 objectHasDiskEntry(ObjectPtr object
)
1131 return object
->disk_entry
&& object
->disk_entry
!= &negativeEntry
;
1134 static DiskCacheEntryPtr
1135 makeDiskEntry(ObjectPtr object
, int create
)
1137 DiskCacheEntryPtr entry
= NULL
;
1140 int negative
= 0, size
= -1, name_len
= -1;
1143 int body_offset
= -1;
1145 int local
= (object
->flags
& OBJECT_LOCAL
) != 0;
1151 if(!local
&& !(object
->flags
& OBJECT_PUBLIC
))
1154 if(maxDiskCacheEntrySize
>= 0) {
1155 if(object
->length
> 0) {
1156 if(object
->length
> maxDiskCacheEntrySize
)
1159 if(object
->size
> maxDiskCacheEntrySize
)
1164 if(object
->disk_entry
) {
1165 entry
= object
->disk_entry
;
1167 if(entry
!= &negativeEntry
) {
1168 /* We'll keep the entry -- put it at the front. */
1169 if(entry
!= diskEntries
&& entry
!= &negativeEntry
) {
1170 entry
->previous
->next
= entry
->next
;
1172 entry
->next
->previous
= entry
->previous
;
1174 diskEntriesLast
= entry
->previous
;
1175 entry
->next
= diskEntries
;
1176 diskEntries
->previous
= entry
;
1177 entry
->previous
= NULL
;
1178 diskEntries
= entry
;
1182 if(entry
== &negativeEntry
) {
1184 if(!create
) return NULL
;
1185 object
->disk_entry
= NULL
;
1188 destroyDiskEntry(object
, 0);
1192 if(numDiskEntries
> maxDiskEntries
)
1193 destroyDiskEntry(diskEntriesLast
->object
, 0);
1196 if(diskCacheRoot
== NULL
|| diskCacheRoot
->length
<= 0)
1198 name_len
= urlFilename(buf
, 1024, object
->key
, object
->key_size
);
1199 if(name_len
< 0) return NULL
;
1201 fd
= open(buf
, O_RDWR
| O_BINARY
);
1203 rc
= validateEntry(object
, fd
, &body_offset
, &offset
);
1210 if(rc
< 0 && errno
!= ENOENT
) {
1211 do_log_error(L_WARN
, errno
,
1212 "Couldn't unlink stale disk entry %s",
1214 /* But continue -- it's okay to have stale entries. */
1219 if(fd
< 0 && create
&& name_len
> 0 &&
1220 !(object
->flags
& OBJECT_INITIAL
)) {
1221 fd
= createFile(buf
, diskCacheRoot
->length
);
1228 if(object
->numchunks
> 0) {
1229 data
= object
->chunks
[0].data
;
1230 dsize
= object
->chunks
[0].size
;
1232 rc
= writeHeaders(fd
, &body_offset
, object
, data
, dsize
);
1234 do_log_error(L_ERROR
, errno
, "Couldn't write headers");
1236 if(rc
< 0 && errno
!= ENOENT
)
1237 do_log_error(L_ERROR
, errno
,
1238 "Couldn't unlink truncated entry %s",
1243 assert(rc
>= body_offset
);
1244 size
= rc
- body_offset
;
1251 if(localDocumentRoot
== NULL
|| localDocumentRoot
->length
== 0)
1255 localFilename(buf
, 1024, object
->key
, object
->key_size
);
1258 fd
= open(buf
, O_RDONLY
| O_BINARY
);
1260 if(validateEntry(object
, fd
, &body_offset
, NULL
) < 0) {
1269 object
->disk_entry
= &negativeEntry
;
1272 assert(body_offset
>= 0);
1274 name
= strdup_n(buf
, name_len
);
1276 do_log(L_ERROR
, "Couldn't allocate name.\n");
1281 entry
= malloc(sizeof(DiskCacheEntryRec
));
1283 do_log(L_ERROR
, "Couldn't allocate entry.\n");
1289 entry
->filename
= name
;
1290 entry
->object
= object
;
1292 entry
->body_offset
= body_offset
;
1293 entry
->local
= local
;
1294 entry
->offset
= offset
;
1296 entry
->metadataDirty
= dirty
;
1298 entry
->next
= diskEntries
;
1300 diskEntries
->previous
= entry
;
1301 diskEntries
= entry
;
1302 if(diskEntriesLast
== NULL
)
1303 diskEntriesLast
= entry
;
1304 entry
->previous
= NULL
;
1307 object
->disk_entry
= entry
;
1313 /* Rewrite a disk cache entry, used when the body offset needs to change. */
1315 rewriteEntry(ObjectPtr object
)
1317 int old_body_offset
= object
->disk_entry
->body_offset
;
1319 DiskCacheEntryPtr entry
;
1321 int buf_is_chunk
, bufsize
;
1324 fd
= dup(object
->disk_entry
->fd
);
1326 do_log_error(L_ERROR
, errno
, "Couldn't duplicate file descriptor");
1330 rc
= destroyDiskEntry(object
, 1);
1335 entry
= makeDiskEntry(object
, 1);
1341 offset
= diskEntrySize(object
);
1347 bufsize
= CHUNK_SIZE
;
1349 buf
= maybe_get_chunk();
1355 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
1361 rc
= lseek(fd
, old_body_offset
+ offset
, SEEK_SET
);
1367 n
= read(fd
, buf
, bufsize
);
1368 if(n
< 0 && errno
== EINTR
)
1372 rc
= entrySeek(entry
, entry
->body_offset
+ offset
);
1376 rc
= write(entry
->fd
, buf
, n
);
1378 entry
->offset
+= rc
;
1380 } else if(errno
== EINTR
) {
1389 if(object
->length
>= 0 && entry
->size
== object
->length
)
1390 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
1400 destroyDiskEntry(ObjectPtr object
, int d
)
1402 DiskCacheEntryPtr entry
= object
->disk_entry
;
1405 assert(!entry
|| !entry
->local
|| !d
);
1408 entry
= makeDiskEntry(object
, 0);
1412 if(!entry
|| entry
== &negativeEntry
) {
1416 assert(entry
->object
== object
);
1418 if(maxDiskCacheEntrySize
>= 0 && object
->size
> maxDiskCacheEntrySize
) {
1419 /* See writeoutToDisk */
1424 entry
->object
->flags
&= ~OBJECT_DISK_ENTRY_COMPLETE
;
1425 if(entry
->filename
) {
1426 urc
= unlink(entry
->filename
);
1428 do_log_error(L_WARN
, errno
,
1429 "Couldn't unlink %s", scrub(entry
->filename
));
1432 if(entry
&& entry
->metadataDirty
)
1433 writeoutMetadata(object
);
1434 makeDiskEntry(object
, 0);
1435 /* rewriteDiskEntry may change the disk entry */
1436 entry
= object
->disk_entry
;
1437 if(entry
== NULL
|| entry
== &negativeEntry
)
1439 if(diskCacheWriteoutOnClose
> 0)
1440 reallyWriteoutToDisk(object
, -1, diskCacheWriteoutOnClose
);
1443 rc
= close(entry
->fd
);
1444 if(rc
< 0 && errno
== EINTR
)
1450 free(entry
->filename
);
1451 entry
->filename
= NULL
;
1454 entry
->previous
->next
= entry
->next
;
1456 diskEntries
= entry
->next
;
1458 entry
->next
->previous
= entry
->previous
;
1460 diskEntriesLast
= entry
->previous
;
1463 assert(numDiskEntries
>= 0);
1466 object
->disk_entry
= NULL
;
1474 objectGetFromDisk(ObjectPtr object
)
1476 DiskCacheEntryPtr entry
= makeDiskEntry(object
, 0);
1477 if(!entry
) return NULL
;
1483 objectFillFromDisk(ObjectPtr object
, int offset
, int chunks
)
1485 DiskCacheEntryPtr entry
;
1490 if(object
->type
!= OBJECT_HTTP
)
1493 if(object
->flags
& OBJECT_LINEAR
)
1496 if(object
->length
>= 0) {
1497 chunks
= MIN(chunks
,
1498 (object
->length
- offset
+ CHUNK_SIZE
- 1) / CHUNK_SIZE
);
1501 rc
= objectSetChunks(object
, offset
/ CHUNK_SIZE
+ chunks
);
1506 if(object
->flags
& OBJECT_INITIAL
) {
1508 } else if((object
->length
< 0 || object
->size
< object
->length
) &&
1509 object
->size
< (offset
/ CHUNK_SIZE
+ chunks
) * CHUNK_SIZE
) {
1512 for(k
= 0; k
< chunks
; k
++) {
1514 i
= offset
/ CHUNK_SIZE
+ k
;
1515 s
= MIN(CHUNK_SIZE
, object
->size
- i
* CHUNK_SIZE
);
1516 if(object
->chunks
[i
].size
< s
) {
1526 /* This has the side-effect of revalidating the entry, which is
1527 what makes HEAD requests work. */
1528 entry
= makeDiskEntry(object
, 0);
1532 for(k
= 0; k
< chunks
; k
++) {
1533 i
= offset
/ CHUNK_SIZE
+ k
;
1534 if(!object
->chunks
[i
].data
)
1535 object
->chunks
[i
].data
= get_chunk();
1536 if(!object
->chunks
[i
].data
) {
1540 lockChunk(object
, i
);
1545 for(k
= 0; k
< chunks
; k
++) {
1547 i
= offset
/ CHUNK_SIZE
+ k
;
1548 j
= object
->chunks
[i
].size
;
1549 o
= i
* CHUNK_SIZE
+ j
;
1551 if(object
->chunks
[i
].size
== CHUNK_SIZE
)
1554 if(entry
->size
>= 0 && entry
->size
<= o
)
1557 if(entry
->offset
!= entry
->body_offset
+ o
) {
1558 rc
= entrySeek(entry
, entry
->body_offset
+ o
);
1567 rc
= read(entry
->fd
, object
->chunks
[i
].data
+ j
, CHUNK_SIZE
- j
);
1572 do_log_error(L_ERROR
, errno
, "Couldn't read");
1576 entry
->offset
+= rc
;
1577 object
->chunks
[i
].size
+= rc
;
1578 if(object
->size
< o
+ rc
)
1579 object
->size
= o
+ rc
;
1581 if(entry
->object
->length
>= 0 && entry
->size
< 0 &&
1582 entry
->offset
- entry
->body_offset
== entry
->object
->length
)
1583 entry
->size
= entry
->object
->length
;
1585 if(rc
< CHUNK_SIZE
- j
) {
1586 /* Paranoia: the read may have been interrupted half-way. */
1587 if(entry
->size
< 0) {
1589 (entry
->object
->length
>= 0 &&
1590 entry
->object
->length
==
1591 entry
->offset
- entry
->body_offset
))
1592 entry
->size
= entry
->offset
- entry
->body_offset
;
1594 } else if(entry
->size
!= entry
->offset
- entry
->body_offset
) {
1596 entry
->size
< entry
->offset
- entry
->body_offset
) {
1598 "Disk entry size changed behind our back: "
1599 "%ld -> %ld (%d).\n",
1601 (long)entry
->offset
- entry
->body_offset
,
1613 CHECK_ENTRY(object
->disk_entry
);
1614 for(k
= 0; k
< chunks
; k
++) {
1615 i
= offset
/ CHUNK_SIZE
+ k
;
1616 unlockChunk(object
, i
);
1620 notifyObject(object
);
1628 writeoutToDisk(ObjectPtr object
, int upto
, int max
)
1630 if(maxDiskCacheEntrySize
>= 0 && object
->size
> maxDiskCacheEntrySize
) {
1631 /* An object was created with an unknown length, and then grew
1632 beyond maxDiskCacheEntrySize. Destroy the disk entry. */
1633 destroyDiskEntry(object
, 1);
1637 return reallyWriteoutToDisk(object
, upto
, max
);
1641 reallyWriteoutToDisk(ObjectPtr object
, int upto
, int max
)
1643 DiskCacheEntryPtr entry
;
1650 upto
= object
->size
;
1652 if((object
->cache_control
& CACHE_NO_STORE
) ||
1653 (object
->flags
& OBJECT_LOCAL
))
1656 if((object
->flags
& OBJECT_DISK_ENTRY_COMPLETE
) && !object
->disk_entry
)
1659 entry
= makeDiskEntry(object
, 1);
1660 if(!entry
) return 0;
1662 assert(!entry
->local
);
1664 if(object
->flags
& OBJECT_DISK_ENTRY_COMPLETE
)
1667 diskEntrySize(object
);
1671 if(object
->length
>= 0 && entry
->size
>= object
->length
) {
1672 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
1676 if(entry
->size
>= upto
)
1679 offset
= entry
->size
;
1681 /* Avoid a seek in case we start writing at the beginning */
1682 if(offset
== 0 && entry
->metadataDirty
) {
1683 writeoutMetadata(object
);
1684 /* rewriteDiskEntry may change the entry */
1685 entry
= makeDiskEntry(object
, 0);
1690 rc
= entrySeek(entry
, offset
+ entry
->body_offset
);
1691 if(rc
< 0) return 0;
1694 if(max
>= 0 && bytes
>= max
)
1697 assert(entry
->offset
== offset
+ entry
->body_offset
);
1698 i
= offset
/ CHUNK_SIZE
;
1699 j
= offset
% CHUNK_SIZE
;
1700 if(i
>= object
->numchunks
)
1702 if(object
->chunks
[i
].size
<= j
)
1705 rc
= write(entry
->fd
, object
->chunks
[i
].data
+ j
,
1706 object
->chunks
[i
].size
- j
);
1710 do_log_error(L_ERROR
, errno
, "Couldn't write disk entry");
1713 entry
->offset
+= rc
;
1716 if(entry
->size
< offset
)
1717 entry
->size
= offset
;
1718 } while(j
+ rc
>= CHUNK_SIZE
);
1722 if(entry
->metadataDirty
)
1723 writeoutMetadata(object
);
1729 writeoutMetadata(ObjectPtr object
)
1731 DiskCacheEntryPtr entry
;
1734 if((object
->cache_control
& CACHE_NO_STORE
) ||
1735 (object
->flags
& OBJECT_LOCAL
))
1738 entry
= makeDiskEntry(object
, 0);
1739 if(entry
== NULL
|| entry
== &negativeEntry
)
1742 assert(!entry
->local
);
1744 rc
= entrySeek(entry
, 0);
1745 if(rc
< 0) goto fail
;
1747 rc
= writeHeaders(entry
->fd
, &entry
->body_offset
, object
, NULL
, 0);
1749 rc
= rewriteEntry(object
);
1750 if(rc
< 0) return 0;
1753 if(rc
< 0) goto fail
;
1755 entry
->metadataDirty
= 0;
1759 /* We need this in order to avoid trying to write this entry out
1761 if(entry
&& entry
!= &negativeEntry
)
1762 entry
->metadataDirty
= 0;
1767 mergeDobjects(DiskObjectPtr dst
, DiskObjectPtr src
)
1769 if(dst
->filename
== NULL
) {
1770 dst
->filename
= src
->filename
;
1771 dst
->body_offset
= src
->body_offset
;
1773 free(src
->filename
);
1774 free(src
->location
);
1776 dst
->length
= src
->length
;
1778 dst
->size
= src
->size
;
1780 dst
->age
= src
->age
;
1782 dst
->date
= src
->date
;
1783 if(dst
->last_modified
< 0)
1784 dst
->last_modified
= src
->last_modified
;
1789 readDiskObject(char *filename
, struct stat
*sb
)
1791 int fd
, rc
, n
, dummy
, code
;
1793 time_t date
, last_modified
, age
, atime
, expires
;
1794 char *location
= NULL
, *fn
= NULL
;
1795 DiskObjectPtr dobject
;
1797 int buf_is_chunk
, bufsize
;
1804 rc
= stat(filename
, &ss
);
1806 do_log_error(L_WARN
, errno
, "Couldn't stat %s", scrub(filename
));
1813 bufsize
= CHUNK_SIZE
;
1816 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
1820 if(S_ISREG(sb
->st_mode
)) {
1821 fd
= open(filename
, O_RDONLY
| O_BINARY
);
1825 rc
= read(fd
, buf
, bufsize
);
1829 n
= findEndOfHeaders(buf
, 0, rc
, &dummy
);
1835 bufsize
= bigBufferSize
;
1836 buf
= malloc(bigBufferSize
);
1839 lrc
= lseek(fd
, 0, SEEK_SET
);
1847 rc
= httpParseServerFirstLine(buf
, &code
, &dummy
, NULL
);
1851 rc
= httpParseHeaders(0, NULL
, buf
, rc
, NULL
,
1852 NULL
, &length
, NULL
, NULL
, NULL
,
1853 &date
, &last_modified
, &expires
, &age
,
1854 &atime
, &body_offset
, NULL
,
1855 NULL
, NULL
, NULL
, NULL
, &location
, NULL
, NULL
);
1856 if(rc
< 0 || location
== NULL
)
1861 size
= sb
->st_size
- body_offset
;
1864 } else if(S_ISDIR(sb
->st_mode
)) {
1866 n
= dirnameUrl(buf
, 512, (char*)filename
, strlen(filename
));
1869 location
= strdup(n
);
1870 if(location
== NULL
)
1884 dobject
= malloc(sizeof(DiskObjectRec
));
1888 fn
= strdup(filename
);
1897 dobject
->location
= location
;
1898 dobject
->filename
= fn
;
1899 dobject
->length
= length
;
1900 dobject
->body_offset
= body_offset
;
1901 dobject
->size
= size
;
1903 dobject
->access
= atime
;
1904 dobject
->date
= date
;
1905 dobject
->last_modified
= last_modified
;
1906 dobject
->expires
= expires
;
1907 if(fd
>= 0) close(fd
);
1916 if(fd
>= 0) close(fd
);
1917 if(location
) free(location
);
1923 processObject(DiskObjectPtr dobjects
, char *filename
, struct stat
*sb
)
1925 DiskObjectPtr dobject
= NULL
;
1928 dobject
= readDiskObject((char*)filename
, sb
);
1933 (c
= strcmp(dobject
->location
, dobjects
->location
)) <= 0) {
1934 if(dobjects
&& c
== 0) {
1935 mergeDobjects(dobjects
, dobject
);
1937 dobject
->next
= dobjects
;
1941 DiskObjectPtr other
= dobjects
;
1942 while(other
->next
) {
1943 c
= strcmp(dobject
->location
, other
->next
->location
);
1946 other
= other
->next
;
1948 if(strcmp(dobject
->location
, other
->location
) == 0) {
1949 mergeDobjects(other
, dobject
);
1951 dobject
->next
= other
->next
;
1952 other
->next
= dobject
;
1958 /* Determine whether p is below root */
1960 filter(DiskObjectPtr p
, const char *root
, int n
, int recursive
)
1963 int m
= strlen(p
->location
);
1966 if(memcmp(root
, p
->location
, n
) != 0)
1970 if(m
== 0 || p
->location
[m
- 1] == '/')
1972 cp
= strchr(p
->location
+ n
, '/');
1973 if(cp
&& cp
- p
->location
!= m
- 1)
1978 /* Filter out all disk objects that are not under root */
1980 filterDiskObjects(DiskObjectPtr from
, const char *root
, int recursive
)
1982 int n
= strlen(root
);
1985 while(from
&& !filter(from
, root
, n
, recursive
)) {
1993 while(p
&& p
->next
) {
1994 if(!filter(p
->next
, root
, n
, recursive
)) {
2007 insertRoot(DiskObjectPtr from
, const char *root
)
2013 if(strcmp(root
, p
->location
) == 0)
2018 p
= malloc(sizeof(DiskObjectRec
));
2020 p
->location
= strdup(root
);
2021 if(p
->location
== NULL
) {
2030 p
->last_modified
= -1;
2036 /* Insert all missing directories in a sorted list of dobjects */
2038 insertDirs(DiskObjectPtr from
)
2040 DiskObjectPtr p
, q
, new;
2046 n
= strlen(q
->location
);
2047 if(n
> 0 && q
->location
[n
- 1] != '/') {
2048 cp
= strrchr(q
->location
, '/');
2049 m
= cp
- q
->location
+ 1;
2050 if(cp
&& (!p
|| strlen(p
->location
) < m
||
2051 memcmp(p
->location
, q
->location
, m
) != 0)) {
2052 new = malloc(sizeof(DiskObjectRec
));
2054 new->location
= strdup_n(q
->location
, m
);
2055 if(new->location
== NULL
) {
2059 new->filename
= NULL
;
2064 new->last_modified
= -1;
2080 indexDiskObjects(FILE *out
, const char *root
, int recursive
)
2084 struct dirent
*dirent
;
2089 DiskObjectPtr dobjects
= NULL
;
2090 char *of
= root
[0] == '\0' ? "" : " of ";
2092 fprintf(out
, "<!DOCTYPE HTML PUBLIC "
2093 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
2094 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
2096 "<title>%s%s%s</title>\n"
2098 "<h1>%s%s%s</h1>\n",
2099 recursive
? "Recursive index" : "Index", of
, root
,
2100 recursive
? "Recursive index" : "Index", of
, root
);
2102 if(diskCacheRoot
== NULL
|| diskCacheRoot
->length
<= 0) {
2103 fprintf(out
, "<p>No <tt>diskCacheRoot</tt>.</p>\n");
2107 if(diskCacheRoot
->length
>= 1024) {
2109 "<p>The value of <tt>diskCacheRoot</tt> is "
2110 "too long (%d).</p>\n",
2111 diskCacheRoot
->length
);
2115 if(strlen(root
) < 8) {
2116 memcpy(buf
, diskCacheRoot
->string
, diskCacheRoot
->length
);
2117 buf
[diskCacheRoot
->length
] = '\0';
2118 n
= diskCacheRoot
->length
;
2120 n
= urlDirname(buf
, 1024, root
, strlen(root
));
2127 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
2132 if(fe
->fts_info
!= FTS_DP
)
2134 processObject(dobjects
,
2136 fe
->fts_info
== FTS_NS
||
2137 fe
->fts_info
== FTS_NSOK
?
2138 fe
->fts_statp
: NULL
);
2146 dirent
= readdir(dir
);
2148 if(n
+ strlen(dirent
->d_name
) < 1024) {
2149 strcpy(buf
+ n
, dirent
->d_name
);
2153 dobjects
= processObject(dobjects
, buf
, NULL
);
2157 fprintf(out
, "<p>Couldn't open directory: %s (%d).</p>\n",
2158 strerror(errno
), errno
);
2165 DiskObjectPtr dobject
;
2167 dobjects
= insertRoot(dobjects
, root
);
2168 dobjects
= insertDirs(dobjects
);
2169 dobjects
= filterDiskObjects(dobjects
, root
, recursive
);
2171 alternatingHttpStyle(out
, "diskcachelist");
2172 fprintf(out
, "<table id=diskcachelist>\n");
2173 fprintf(out
, "<tbody>\n");
2177 i
= strlen(dobject
->location
);
2178 isdir
= (i
== 0 || dobject
->location
[i
- 1] == '/');
2180 fprintf(out
, "<tr class=odd>");
2182 fprintf(out
, "<tr class=even>");
2183 if(dobject
->size
>= 0) {
2184 fprintf(out
, "<td><a href=\"%s\"><tt>",
2187 dobject
->location
, strlen(dobject
->location
));
2188 fprintf(out
, "</tt></a></td> ");
2189 if(dobject
->length
>= 0) {
2190 if(dobject
->size
== dobject
->length
)
2191 fprintf(out
, "<td>%d</td> ", dobject
->length
);
2193 fprintf(out
, "<td>%d/%d</td> ",
2194 dobject
->size
, dobject
->length
);
2196 /* Avoid a trigraph. */
2197 fprintf(out
, "<td>%d/<em>??" "?</em></td> ", dobject
->size
);
2199 if(dobject
->last_modified
>= 0) {
2200 struct tm
*tm
= gmtime(&dobject
->last_modified
);
2204 n
= strftime(buf
, 1024, "%d.%m.%Y", tm
);
2209 fprintf(out
, "<td>%s</td> ", buf
);
2211 fprintf(out
, "<td></td>");
2214 if(dobject
->date
>= 0) {
2215 struct tm
*tm
= gmtime(&dobject
->date
);
2219 n
= strftime(buf
, 1024, "%d.%m.%Y", tm
);
2224 fprintf(out
, "<td>%s</td>", buf
);
2226 fprintf(out
, "<td></td>");
2229 fprintf(out
, "<td><tt>");
2230 htmlPrint(out
, dobject
->location
,
2231 strlen(dobject
->location
));
2232 fprintf(out
, "</tt></td><td></td><td></td><td></td>");
2235 fprintf(out
, "<td><a href=\"/polipo/index?%s\">plain</a></td>"
2236 "<td><a href=\"/polipo/recursive-index?%s\">"
2237 "recursive</a></td>",
2238 dobject
->location
, dobject
->location
);
2240 fprintf(out
, "</tr>\n");
2242 dobjects
= dobject
->next
;
2243 free(dobject
->location
);
2244 free(dobject
->filename
);
2247 fprintf(out
, "</tbody>\n");
2248 fprintf(out
, "</table>\n");
2252 fprintf(out
, "<p><a href=\"/polipo/\">back</a></p>\n");
2253 fprintf(out
, "</body></html>\n");
2258 checkForZeroes(char *buf
, int n
)
2261 unsigned long *lbuf
= (unsigned long *)buf
;
2262 assert(n
% sizeof(unsigned long) == 0);
2264 for(i
= 0; i
* sizeof(unsigned long) < n
; i
++) {
2266 return i
* sizeof(unsigned long);
2268 for(j
= 0; i
* sizeof(unsigned long) + j
< n
; j
++) {
2269 if(buf
[i
* sizeof(unsigned long) + j
] != 0)
2273 return i
* sizeof(unsigned long) + j
;
2277 copyFile(int from
, char *filename
, int n
)
2280 int to
, offset
, nread
, nzeroes
, rc
;
2282 buf
= malloc(CHUNK_SIZE
);
2286 to
= open(filename
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
,
2287 diskCacheFilePermissions
);
2295 nread
= read(from
, buf
, MIN(CHUNK_SIZE
, n
- offset
));
2298 nzeroes
= checkForZeroes(buf
, nread
& -8);
2301 rc
= lseek(to
, nzeroes
, SEEK_CUR
);
2302 if(rc
!= offset
+ nzeroes
) {
2304 do_log_error(L_ERROR
, errno
, "Couldn't extend file");
2307 "Couldn't extend file: "
2308 "unexpected offset %d != %d + %d.\n",
2313 if(nread
> nzeroes
) {
2314 rc
= write(to
, buf
+ nzeroes
, nread
- nzeroes
);
2315 if(rc
!= nread
- nzeroes
) {
2317 do_log_error(L_ERROR
, errno
, "Couldn't write");
2319 do_log(L_ERROR
, "Short write.\n");
2328 unlink(filename
); /* something went wrong straight away */
2333 expireFile(char *filename
, struct stat
*sb
,
2334 int *considered
, int *unlinked
, int *truncated
)
2336 DiskObjectPtr dobject
= NULL
;
2339 long int ret
= sb
->st_size
;
2341 if(!preciseExpiry
) {
2343 if(t
> current_time
.tv_sec
+ 1) {
2344 do_log(L_WARN
, "File %s has access time in the future.\n",
2346 t
= current_time
.tv_sec
;
2349 if(t
> current_time
.tv_sec
- diskCacheUnlinkTime
&&
2350 (sb
->st_size
< diskCacheTruncateSize
||
2351 t
> current_time
.tv_sec
- diskCacheTruncateTime
))
2357 dobject
= readDiskObject(filename
, sb
);
2359 do_log(L_ERROR
, "Incorrect disk entry %s -- removing.\n",
2361 rc
= unlink(filename
);
2363 do_log_error(L_ERROR
, errno
,
2364 "Couldn't unlink %s", scrub(filename
));
2372 t
= dobject
->access
;
2373 if(t
< 0) t
= dobject
->age
;
2374 if(t
< 0) t
= dobject
->date
;
2376 if(t
> current_time
.tv_sec
)
2378 "Disk entry %s (%s) has access time in the future.\n",
2379 scrub(dobject
->location
), scrub(dobject
->filename
));
2381 if(t
< current_time
.tv_sec
- diskCacheUnlinkTime
) {
2382 rc
= unlink(dobject
->filename
);
2384 do_log_error(L_ERROR
, errno
, "Couldn't unlink %s",
2390 } else if(dobject
->size
>
2391 diskCacheTruncateSize
+ 4 * dobject
->body_offset
&&
2392 t
< current_time
.tv_sec
- diskCacheTruncateTime
) {
2393 /* We need to copy rather than simply truncate in place: the
2394 latter would confuse a running polipo. */
2395 fd
= open(dobject
->filename
, O_RDONLY
| O_BINARY
, 0);
2396 rc
= unlink(dobject
->filename
);
2398 do_log_error(L_ERROR
, errno
, "Couldn't unlink %s",
2402 copyFile(fd
, dobject
->filename
,
2403 dobject
->body_offset
+ diskCacheTruncateSize
);
2406 ret
= sb
->st_size
- dobject
->body_offset
+ diskCacheTruncateSize
;
2410 free(dobject
->location
);
2411 free(dobject
->filename
);
2423 int files
= 0, considered
= 0, unlinked
= 0, truncated
= 0;
2424 int dirs
= 0, rmdirs
= 0;
2425 long left
= 0, total
= 0;
2427 if(diskCacheRoot
== NULL
||
2428 diskCacheRoot
->length
<= 0 || diskCacheRoot
->string
[0] != '/')
2431 fts_argv
[0] = diskCacheRoot
->string
;
2433 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
2435 do_log_error(L_ERROR
, errno
, "Couldn't fts_open disk cache");
2438 gettimeofday(¤t_time
, NULL
);
2443 if(fe
->fts_info
== FTS_D
)
2446 if(fe
->fts_info
== FTS_DP
|| fe
->fts_info
== FTS_DC
||
2447 fe
->fts_info
== FTS_DNR
) {
2448 if(fe
->fts_accpath
[0] == '/' &&
2449 strlen(fe
->fts_accpath
) <= diskCacheRoot
->length
)
2452 rc
= rmdir(fe
->fts_accpath
);
2455 else if(errno
!= ENOTEMPTY
&& errno
!= EEXIST
)
2456 do_log_error(L_ERROR
, errno
,
2457 "Couldn't remove directory %s",
2458 scrub(fe
->fts_accpath
));
2460 } else if(fe
->fts_info
== FTS_NS
) {
2461 do_log_error(L_ERROR
, fe
->fts_errno
, "Couldn't stat file %s",
2462 scrub(fe
->fts_accpath
));
2464 } else if(fe
->fts_info
== FTS_ERR
) {
2465 do_log_error(L_ERROR
, fe
->fts_errno
,
2466 "Couldn't fts_read disk cache");
2470 if(!S_ISREG(fe
->fts_statp
->st_mode
)) {
2471 do_log(L_ERROR
, "Unexpected file %s type 0%o.\n",
2472 fe
->fts_accpath
, (unsigned int)fe
->fts_statp
->st_mode
);
2477 left
+= expireFile(fe
->fts_accpath
, fe
->fts_statp
,
2478 &considered
, &unlinked
, &truncated
);
2479 total
+= fe
->fts_statp
->st_size
;
2484 printf("Disk cache purged.\n");
2485 printf("%d files, %d considered, %d removed, %d truncated "
2486 "(%ldkB -> %ldkB).\n",
2487 files
, considered
, unlinked
, truncated
, total
/1024, left
/1024);
2488 printf("%d directories, %d removed.\n", dirs
, rmdirs
);
2507 writeoutToDisk(ObjectPtr object
, int upto
, int max
)
2513 destroyDiskEntry(ObjectPtr object
, int d
)
2519 objectGetFromDisk(ObjectPtr object
)
2525 objectFillFromDisk(ObjectPtr object
, int offset
, int chunks
)
2531 revalidateDiskEntry(ObjectPtr object
)
2537 dirtyDiskEntry(ObjectPtr object
)
2545 do_log(L_ERROR
, "Disk cache not supported in this version.\n");
2549 diskEntrySize(ObjectPtr object
)