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 diskCacheWriteoutOnClose
= (32 * 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, 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
>= n
+ 3) 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 } else if(i
< len
- 1 &&
437 name
[i
] == '.' && name
[i
+ 1] == '/') {
439 } else if(i
== len
- 1 && name
[i
] == '.') {
442 url
[j
++] = name
[i
]; if(j
>= n
) goto fail
;
445 url
[j
++] = '/'; if(j
>= n
) goto fail
;
453 /* Create a file and all intermediate directories. */
455 createFile(const char *name
, int path_start
)
462 if(name
[path_start
] == '/')
465 if(path_start
< 2 || name
[path_start
- 1] != '/' ) {
466 do_log(L_ERROR
, "Incorrect name %s (%d).\n", name
, path_start
);
470 fd
= open(name
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
,
471 diskCacheFilePermissions
);
474 if(errno
!= ENOENT
) {
475 do_log_error(L_ERROR
, errno
, "Couldn't create disk file %s", name
);
480 while(name
[n
] != '\0' && n
< 1024) {
481 while(name
[n
] != '/' && name
[n
] != '\0' && n
< 512)
483 if(name
[n
] != '/' || n
>= 1024)
485 memcpy(buf
, name
, n
+ 1);
487 rc
= mkdir(buf
, diskCacheDirectoryPermissions
);
488 if(rc
< 0 && errno
!= EEXIST
) {
489 do_log_error(L_ERROR
, errno
, "Couldn't create directory %s", buf
);
494 fd
= open(name
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
,
495 diskCacheFilePermissions
);
497 do_log_error(L_ERROR
, errno
, "Couldn't create file %s", name
);
505 chooseBodyOffset(int n
, ObjectPtr object
)
507 int length
= MAX(object
->size
, object
->length
);
510 if(object
->length
>= 0 && object
->length
+ n
< 4096 - 4)
511 return -1; /* no gap for small objects */
528 body_offset
= ((n
+ 32 + 4095) / 4096 + 1) * 4096;
530 /* Tweak the gap so that we don't use up a full disk block for
532 if(object
->length
>= 0 && object
->length
< 64 * 1024) {
533 int last
= (body_offset
+ object
->length
) % 4096;
534 int gap
= body_offset
- n
- 32;
539 /* Rewriting large objects is expensive -- don't use small gaps.
540 This has the additional benefit of block-aligning large bodies. */
541 if(length
>= 64 * 1024) {
542 int min_gap
, min_offset
;
543 if(length
>= 512 * 1024)
545 else if(length
>= 256 * 1024)
550 min_offset
= ((n
+ 32 + min_gap
- 1) / min_gap
+ 1) * min_gap
;
551 body_offset
= MAX(body_offset
, min_offset
);
557 /* Assumes the file descriptor is at offset 0. Returns -1 on failure,
558 otherwise the offset at which the file descriptor is left. */
559 /* If chunk is not null, it should be the first chunk of the object,
560 and will be written out in the same operation if possible. */
562 writeHeaders(int fd
, int *body_offset_return
,
563 ObjectPtr object
, char *chunk
, int chunk_len
)
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");
636 memset(buf
+ n
, 0, body_offset
- n
);
639 #ifdef HAVE_READV_WRITEV
642 iov
[0].iov_base
= buf
;
643 iov
[0].iov_len
= body_offset
;
644 iov
[1].iov_base
= chunk
;
645 iov
[1].iov_len
= chunk_len
;
646 rc
= writev(fd
, iov
, 2);
649 rc
= write(fd
, buf
, body_offset
);
651 if(rc
< 0 && errno
== EINTR
)
656 if(object
->length
>= 0 &&
657 rc
- body_offset
>= object
->length
)
658 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
660 *body_offset_return
= body_offset
;
668 if(bufsize
< bigBufferSize
) {
670 buf
= malloc(bigBufferSize
);
672 do_log(L_ERROR
, "Couldn't allocate big buffer.\n");
675 bufsize
= bigBufferSize
;
678 dispose_chunk(oldbuf
);
695 typedef struct _MimeEntry
{
700 static const MimeEntryRec mimeEntries
[] = {
701 { "html", "text/html" },
702 { "htm", "text/html" },
703 { "text", "text/plain" },
704 { "txt", "text/plain" },
705 { "png", "image/png" },
706 { "gif", "image/gif" },
707 { "jpeg", "image/jpeg" },
708 { "jpg", "image/jpeg" },
709 { "ico", "image/x-icon" },
710 { "pdf", "application/pdf" },
711 { "ps", "application/postscript" },
712 { "tar", "application/x-tar" },
713 { "pac", "application/x-ns-proxy-autoconfig" },
714 { "css", "text/css" },
715 { "js", "application/x-javascript" },
716 { "xml", "text/xml" },
717 { "swf", "application/x-shockwave-flash" },
721 localObjectMimeType(ObjectPtr object
, char **encoding_return
)
723 char *name
= object
->key
;
724 int nlen
= object
->key_size
;
729 if(name
[nlen
- 1] == '/') {
730 *encoding_return
= NULL
;
735 *encoding_return
= NULL
;
736 return "application/octet-stream";
739 if(memcmp(name
+ nlen
- 3, ".gz", 3) == 0) {
740 *encoding_return
= "x-gzip";
742 } else if(memcmp(name
+ nlen
- 2, ".Z", 2) == 0) {
743 *encoding_return
= "x-compress";
746 *encoding_return
= NULL
;
749 for(i
= 0; i
< sizeof(mimeEntries
) / sizeof(mimeEntries
[0]); i
++) {
750 int len
= strlen(mimeEntries
[i
].extension
);
752 name
[nlen
- len
- 1] == '.' &&
753 memcmp(name
+ nlen
- len
, mimeEntries
[i
].extension
, len
) == 0)
754 return mimeEntries
[i
].mime
;
757 return "application/octet-stream";
760 /* Same interface as validateEntry -- see below */
762 validateLocalEntry(ObjectPtr object
, int fd
,
763 int *body_offset_return
, off_t
*offset_return
)
772 do_log_error(L_ERROR
, errno
, "Couldn't stat");
776 if(S_ISREG(ss
.st_mode
)) {
777 if(!(ss
.st_mode
& S_IROTH
) ||
778 (object
->length
>= 0 && object
->length
!= ss
.st_size
) ||
779 (object
->last_modified
>= 0 &&
780 object
->last_modified
!= ss
.st_mtime
))
783 notifyObject(object
);
787 n
= snnprintf(buf
, 0, 512, "%lx-%lx-%lx",
788 (unsigned long)ss
.st_ino
,
789 (unsigned long)ss
.st_size
,
790 (unsigned long)ss
.st_mtime
);
794 if(n
> 0 && object
->etag
) {
795 if(strlen(object
->etag
) != n
||
796 memcmp(object
->etag
, buf
, n
) != 0)
800 if(!(object
->flags
& OBJECT_INITIAL
)) {
801 if(!object
->last_modified
&& !object
->etag
)
805 if(object
->flags
& OBJECT_INITIAL
) {
806 object
->length
= ss
.st_size
;
807 object
->last_modified
= ss
.st_mtime
;
808 object
->date
= current_time
.tv_sec
;
809 object
->age
= current_time
.tv_sec
;
812 object
->etag
= strdup(buf
); /* okay if fails */
813 object
->message
= internAtom("Okay");
814 n
= snnprintf(buf
, 0, 512,
816 "\r\nContent-Type: %s",
817 localObjectMimeType(object
, &encoding
));
819 n
= snnprintf(buf
, n
, 512,
820 "\r\nContent-Encoding: %s", encoding
);
823 object
->headers
= internAtomN(buf
, n
);
824 if(object
->headers
== NULL
)
826 object
->flags
&= ~OBJECT_INITIAL
;
829 if(body_offset_return
)
830 *body_offset_return
= 0;
836 /* Assumes fd is at offset 0.
837 Returns -1 if not valid, 1 if metadata should be written out, 0
840 validateEntry(ObjectPtr object
, int fd
,
841 int *body_offset_return
, off_t
*offset_return
)
844 int buf_is_chunk
, bufsize
;
849 time_t date
, last_modified
, expires
, polipo_age
, polipo_access
;
855 CacheControlRec cache_control
;
860 if(object
->flags
& OBJECT_LOCAL
)
861 return validateLocalEntry(object
, fd
,
862 body_offset_return
, offset_return
);
864 if(!(object
->flags
& OBJECT_PUBLIC
) && (object
->flags
& OBJECT_INITIAL
))
867 /* get_chunk might trigger object expiry */
868 bufsize
= CHUNK_SIZE
;
870 buf
= maybe_get_chunk();
876 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
882 rc
= read(fd
, buf
, bufsize
);
886 do_log_error(L_ERROR
, errno
, "Couldn't read disk entry");
892 n
= findEndOfHeaders(buf
, 0, rc
, &dummy
);
895 if(bufsize
< bigBufferSize
) {
896 buf
= malloc(bigBufferSize
);
898 do_log(L_ERROR
, "Couldn't allocate big buffer.\n");
901 bufsize
= bigBufferSize
;
902 memcpy(buf
, oldbuf
, offset
);
904 dispose_chunk(oldbuf
);
909 rc
= read(fd
, buf
+ offset
, bufsize
- offset
);
913 do_log_error(L_ERROR
, errno
, "Couldn't read disk entry");
919 do_log(L_ERROR
, "Couldn't parse disk entry.\n");
923 rc
= httpParseServerFirstLine(buf
, &code
, &dummy
, &message
);
925 do_log(L_ERROR
, "Couldn't parse disk entry.\n");
929 if(object
->code
!= 0 && object
->code
!= code
) {
930 releaseAtom(message
);
934 rc
= httpParseHeaders(0, NULL
, buf
, rc
, NULL
,
935 &headers
, &length
, &cache_control
, NULL
, NULL
,
936 &date
, &last_modified
, &expires
, &polipo_age
,
937 &polipo_access
, &body_offset
,
939 NULL
, NULL
, &location
, &via
, NULL
);
941 releaseAtom(message
);
947 if(!location
|| strlen(location
) != object
->key_size
||
948 memcmp(location
, object
->key
, object
->key_size
) != 0) {
949 do_log(L_ERROR
, "Inconsistent cache file for %s.\n", location
);
957 do_log(L_ERROR
, "Undated disk entry for %s.\n", location
);
961 if(!(object
->flags
& OBJECT_INITIAL
)) {
962 if((last_modified
>=0) != (object
->last_modified
>= 0))
965 if((object
->cache_control
& CACHE_MISMATCH
) ||
966 (cache_control
.flags
& CACHE_MISMATCH
))
969 if(last_modified
>= 0 && object
->last_modified
>= 0 &&
970 last_modified
!= object
->last_modified
)
973 if(length
>= 0 && object
->length
>= 0)
974 if(length
!= object
->length
)
977 if(!!etag
!= !!object
->etag
)
980 if(etag
&& object
->etag
&& strcmp(etag
, object
->etag
) != 0)
983 /* If we don't have a usable ETag, and either CACHE_VARY or we
984 don't have a last-modified date, we validate disk entries by
986 if(!(etag
&& object
->etag
) &&
987 (!(last_modified
>= 0 && object
->last_modified
>= 0) ||
988 ((cache_control
.flags
& CACHE_VARY
) ||
989 (object
->cache_control
& CACHE_VARY
)))) {
990 if(date
>= 0 && date
!= object
->date
)
992 if(polipo_age
>= 0 && polipo_age
!= object
->age
)
1001 if(!object
->headers
)
1002 object
->headers
= headers
;
1004 releaseAtom(headers
);
1007 if(object
->code
== 0) {
1008 object
->code
= code
;
1009 object
->message
= retainAtom(message
);
1011 if(object
->date
<= date
)
1012 object
->date
= date
;
1015 if(object
->last_modified
< 0)
1016 object
->last_modified
= last_modified
;
1017 if(object
->expires
< 0)
1018 object
->expires
= expires
;
1019 else if(object
->expires
> expires
)
1022 object
->age
= polipo_age
;
1023 else if(object
->age
> polipo_age
)
1025 if(object
->atime
<= polipo_access
)
1026 object
->atime
= polipo_access
;
1030 object
->cache_control
|= cache_control
.flags
;
1032 if(object
->age
< 0) object
->age
= object
->date
;
1033 if(object
->age
< 0) object
->age
= 0; /* a long time ago */
1034 if(object
->length
< 0) object
->length
= length
;
1036 object
->etag
= etag
;
1041 releaseAtom(message
);
1043 if(object
->flags
& OBJECT_INITIAL
) object
->via
= via
;
1044 object
->flags
&= ~OBJECT_INITIAL
;
1045 if(offset
> body_offset
) {
1046 /* We need to make sure we don't invoke object expiry recursively */
1047 objectSetChunks(object
, 1);
1048 if(object
->numchunks
>= 1) {
1049 if(object
->chunks
[0].data
== NULL
)
1050 object
->chunks
[0].data
= maybe_get_chunk();
1051 if(object
->chunks
[0].data
)
1052 objectAddData(object
, buf
+ body_offset
,
1053 0, MIN(offset
- body_offset
, CHUNK_SIZE
));
1057 httpTweakCachability(object
);
1063 if(body_offset_return
) *body_offset_return
= body_offset
;
1064 if(offset_return
) *offset_return
= offset
;
1068 releaseAtom(message
);
1069 if(etag
) free(etag
);
1070 if(location
) free(location
);
1071 if(via
) releaseAtom(via
);
1083 dirtyDiskEntry(ObjectPtr object
)
1085 DiskCacheEntryPtr entry
= object
->disk_entry
;
1086 if(entry
&& entry
!= &negativeEntry
) entry
->metadataDirty
= 1;
1090 revalidateDiskEntry(ObjectPtr object
)
1092 DiskCacheEntryPtr entry
= object
->disk_entry
;
1096 if(!entry
|| entry
== &negativeEntry
)
1100 rc
= entrySeek(entry
, 0);
1101 if(rc
< 0) return 0;
1103 rc
= validateEntry(object
, entry
->fd
, &body_offset
, &entry
->offset
);
1105 destroyDiskEntry(object
, 0);
1108 if(body_offset
!= entry
->body_offset
) {
1109 do_log(L_WARN
, "Inconsistent body offset (%d != %d).\n",
1110 body_offset
, entry
->body_offset
);
1111 destroyDiskEntry(object
, 0);
1115 entry
->metadataDirty
|= !!rc
;
1121 objectHasDiskEntry(ObjectPtr object
)
1123 return object
->disk_entry
&& object
->disk_entry
!= &negativeEntry
;
1126 static DiskCacheEntryPtr
1127 makeDiskEntry(ObjectPtr object
, int writeable
, int create
)
1129 DiskCacheEntryPtr entry
= NULL
;
1132 int negative
= 0, isWriteable
= 0, size
= -1, name_len
= -1;
1135 int body_offset
= -1;
1137 int local
= (object
->flags
& OBJECT_LOCAL
) != 0;
1140 if(local
&& (writeable
|| create
))
1143 if(!local
&& !(object
->flags
& OBJECT_PUBLIC
))
1146 if(maxDiskCacheEntrySize
>= 0) {
1147 if(object
->length
> 0) {
1148 if(object
->length
> maxDiskCacheEntrySize
)
1151 if(object
->size
> maxDiskCacheEntrySize
)
1156 if(object
->disk_entry
) {
1157 entry
= object
->disk_entry
;
1159 if(entry
!= &negativeEntry
&& (!writeable
|| entry
->writeable
)) {
1160 /* We'll keep the entry -- put it at the front. */
1161 if(entry
!= diskEntries
&& entry
!= &negativeEntry
) {
1162 entry
->previous
->next
= entry
->next
;
1164 entry
->next
->previous
= entry
->previous
;
1166 diskEntriesLast
= entry
->previous
;
1167 entry
->next
= diskEntries
;
1168 diskEntries
->previous
= entry
;
1169 entry
->previous
= NULL
;
1170 diskEntries
= entry
;
1174 if(entry
== &negativeEntry
) {
1176 if(!create
) return NULL
;
1177 object
->disk_entry
= NULL
;
1180 destroyDiskEntry(object
, 0);
1184 if(numDiskEntries
> maxDiskEntries
)
1185 destroyDiskEntry(diskEntriesLast
->object
, 0);
1188 if(diskCacheRoot
== NULL
|| diskCacheRoot
->length
<= 0)
1190 name_len
= urlFilename(buf
, 1024, object
->key
, object
->key_size
);
1191 if(name_len
< 0) return NULL
;
1194 fd
= open(buf
, O_RDWR
| O_BINARY
);
1195 if(fd
< 0 && !writeable
&& errno
== EACCES
) {
1197 fd
= open(buf
, O_RDONLY
| O_BINARY
);
1201 rc
= validateEntry(object
, fd
, &body_offset
, &offset
);
1208 if(rc
< 0 && errno
!= ENOENT
) {
1209 do_log_error(L_WARN
, errno
,
1210 "Couldn't unlink stale disk entry %s",
1212 /* But continue -- it's okay to have stale entries. */
1217 if(fd
< 0 && create
&& name_len
> 0 &&
1218 !(object
->flags
& OBJECT_INITIAL
)) {
1220 fd
= createFile(buf
, diskCacheRoot
->length
);
1227 if(object
->numchunks
> 0) {
1228 data
= object
->chunks
[0].data
;
1229 dsize
= object
->chunks
[0].size
;
1231 rc
= writeHeaders(fd
, &body_offset
, object
, data
, dsize
);
1233 do_log_error(L_ERROR
, errno
, "Couldn't write headers");
1235 if(rc
< 0 && errno
!= ENOENT
)
1236 do_log_error(L_ERROR
, errno
,
1237 "Couldn't unlink truncated entry %s",
1242 assert(rc
>= body_offset
);
1243 size
= rc
- body_offset
;
1250 if(localDocumentRoot
== NULL
|| localDocumentRoot
->length
== 0)
1254 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");
1282 entry
= malloc(sizeof(DiskCacheEntryRec
));
1284 do_log(L_ERROR
, "Couldn't allocate entry.\n");
1290 entry
->filename
= name
;
1291 entry
->object
= object
;
1293 entry
->body_offset
= body_offset
;
1294 entry
->local
= local
;
1295 entry
->offset
= offset
;
1297 entry
->metadataDirty
= dirty
;
1298 entry
->writeable
= isWriteable
;
1300 entry
->next
= diskEntries
;
1302 diskEntries
->previous
= entry
;
1303 diskEntries
= entry
;
1304 if(diskEntriesLast
== NULL
)
1305 diskEntriesLast
= entry
;
1306 entry
->previous
= NULL
;
1309 object
->disk_entry
= entry
;
1315 /* Rewrite a disk cache entry, used when the body offset needs to change. */
1317 rewriteEntry(ObjectPtr object
)
1319 int old_body_offset
= object
->disk_entry
->body_offset
;
1321 DiskCacheEntryPtr entry
;
1323 int buf_is_chunk
, bufsize
;
1326 fd
= dup(object
->disk_entry
->fd
);
1328 do_log_error(L_ERROR
, errno
, "Couldn't duplicate file descriptor");
1332 rc
= destroyDiskEntry(object
, 1);
1337 entry
= makeDiskEntry(object
, 1, 1);
1343 offset
= diskEntrySize(object
);
1349 bufsize
= CHUNK_SIZE
;
1351 buf
= maybe_get_chunk();
1357 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
1363 rc
= lseek(fd
, old_body_offset
+ offset
, SEEK_SET
);
1369 n
= read(fd
, buf
, bufsize
);
1372 rc
= entrySeek(entry
, entry
->body_offset
+ offset
);
1375 rc
= write(entry
->fd
, buf
, n
);
1377 entry
->offset
+= rc
;
1386 if(object
->length
>= 0 && entry
->size
== object
->length
)
1387 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
1397 destroyDiskEntry(ObjectPtr object
, int d
)
1399 DiskCacheEntryPtr entry
= object
->disk_entry
;
1402 assert(!entry
|| !entry
->local
|| !d
);
1405 entry
= makeDiskEntry(object
, 1, 0);
1409 if(!entry
|| entry
== &negativeEntry
) {
1413 assert(entry
->object
== object
);
1415 if(maxDiskCacheEntrySize
>= 0 && object
->size
> maxDiskCacheEntrySize
) {
1416 /* See writeoutToDisk */
1421 entry
->object
->flags
&= ~OBJECT_DISK_ENTRY_COMPLETE
;
1422 if(entry
->filename
) {
1423 urc
= unlink(entry
->filename
);
1425 do_log_error(L_WARN
, errno
,
1426 "Couldn't unlink %s", entry
->filename
);
1429 if(entry
&& entry
->metadataDirty
)
1430 writeoutMetadata(object
);
1431 makeDiskEntry(object
, 1, 0);
1432 /* rewriteDiskEntry may change the disk entry */
1433 entry
= object
->disk_entry
;
1434 if(entry
== NULL
|| entry
== &negativeEntry
)
1436 if(entry
->writeable
&& diskCacheWriteoutOnClose
> 0)
1437 reallyWriteoutToDisk(object
, -1, diskCacheWriteoutOnClose
);
1440 rc
= close(entry
->fd
);
1441 if(rc
< 0 && errno
== EINTR
)
1447 free(entry
->filename
);
1448 entry
->filename
= NULL
;
1451 entry
->previous
->next
= entry
->next
;
1453 diskEntries
= entry
->next
;
1455 entry
->next
->previous
= entry
->previous
;
1457 diskEntriesLast
= entry
->previous
;
1460 assert(numDiskEntries
>= 0);
1463 object
->disk_entry
= NULL
;
1471 objectGetFromDisk(ObjectPtr object
)
1473 DiskCacheEntryPtr entry
= makeDiskEntry(object
, 0, 0);
1474 if(!entry
) return NULL
;
1480 objectFillFromDisk(ObjectPtr object
, int offset
, int chunks
)
1482 DiskCacheEntryPtr entry
;
1487 if(object
->type
!= OBJECT_HTTP
)
1490 if(object
->flags
& OBJECT_LINEAR
)
1493 if(object
->length
>= 0) {
1494 chunks
= MIN(chunks
,
1495 (object
->length
- offset
+ CHUNK_SIZE
- 1) / CHUNK_SIZE
);
1498 rc
= objectSetChunks(object
, offset
/ CHUNK_SIZE
+ chunks
);
1503 if(object
->flags
& OBJECT_INITIAL
) {
1505 } else if((object
->length
< 0 || object
->size
< object
->length
) &&
1506 object
->size
< (offset
/ CHUNK_SIZE
+ chunks
) * CHUNK_SIZE
) {
1509 for(k
= 0; k
< chunks
; k
++) {
1511 i
= offset
/ CHUNK_SIZE
+ k
;
1512 s
= MIN(CHUNK_SIZE
, object
->size
- i
* CHUNK_SIZE
);
1513 if(object
->chunks
[i
].size
< s
) {
1523 /* This has the side-effect of revalidating the entry, which is
1524 what makes HEAD requests work. */
1525 entry
= makeDiskEntry(object
, 0, 0);
1529 for(k
= 0; k
< chunks
; k
++) {
1530 i
= offset
/ CHUNK_SIZE
+ k
;
1531 if(!object
->chunks
[i
].data
)
1532 object
->chunks
[i
].data
= get_chunk();
1533 if(!object
->chunks
[i
].data
) {
1537 lockChunk(object
, i
);
1542 for(k
= 0; k
< chunks
; k
++) {
1544 i
= offset
/ CHUNK_SIZE
+ k
;
1545 j
= object
->chunks
[i
].size
;
1546 o
= i
* CHUNK_SIZE
+ j
;
1548 if(object
->chunks
[i
].size
== CHUNK_SIZE
)
1551 if(entry
->size
>= 0 && entry
->size
<= o
)
1554 if(entry
->offset
!= entry
->body_offset
+ o
) {
1555 rc
= entrySeek(entry
, entry
->body_offset
+ o
);
1564 rc
= read(entry
->fd
, object
->chunks
[i
].data
+ j
, CHUNK_SIZE
- j
);
1569 do_log_error(L_ERROR
, errno
, "Couldn't read");
1573 entry
->offset
+= rc
;
1574 object
->chunks
[i
].size
+= rc
;
1575 if(object
->size
< o
+ rc
)
1576 object
->size
= o
+ rc
;
1578 if(entry
->object
->length
>= 0 && entry
->size
< 0 &&
1579 entry
->offset
- entry
->body_offset
== entry
->object
->length
)
1580 entry
->size
= entry
->object
->length
;
1582 if(rc
< CHUNK_SIZE
- j
) {
1583 /* Paranoia: the read may have been interrupted half-way. */
1584 if(entry
->size
< 0) {
1586 (entry
->object
->length
>= 0 &&
1587 entry
->object
->length
==
1588 entry
->offset
- entry
->body_offset
))
1589 entry
->size
= entry
->offset
- entry
->body_offset
;
1591 } else if(entry
->size
!= entry
->offset
- entry
->body_offset
) {
1593 entry
->size
< entry
->offset
- entry
->body_offset
) {
1595 "Disk entry size changed behind our back: "
1596 "%ld -> %ld (%d).\n",
1598 (long)entry
->offset
- entry
->body_offset
,
1610 CHECK_ENTRY(object
->disk_entry
);
1611 for(k
= 0; k
< chunks
; k
++) {
1612 i
= offset
/ CHUNK_SIZE
+ k
;
1613 unlockChunk(object
, i
);
1617 notifyObject(object
);
1625 writeoutToDisk(ObjectPtr object
, int upto
, int max
)
1627 if(maxDiskCacheEntrySize
>= 0 && object
->size
> maxDiskCacheEntrySize
) {
1628 /* An object was created with an unknown length, and then grew
1629 beyond maxDiskCacheEntrySize. Destroy the disk entry. */
1630 destroyDiskEntry(object
, 1);
1634 return reallyWriteoutToDisk(object
, upto
, max
);
1638 reallyWriteoutToDisk(ObjectPtr object
, int upto
, int max
)
1640 DiskCacheEntryPtr entry
;
1647 upto
= object
->size
;
1649 if((object
->cache_control
& CACHE_NO_STORE
) ||
1650 (object
->flags
& OBJECT_LOCAL
))
1653 if((object
->flags
& OBJECT_DISK_ENTRY_COMPLETE
) && !object
->disk_entry
)
1656 entry
= makeDiskEntry(object
, 1, 1);
1657 if(!entry
) return 0;
1659 assert(!entry
->local
);
1661 if(object
->flags
& OBJECT_DISK_ENTRY_COMPLETE
)
1664 diskEntrySize(object
);
1668 if(object
->length
>= 0 && entry
->size
>= object
->length
) {
1669 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
1673 if(entry
->size
>= upto
)
1676 if(!entry
->writeable
) {
1677 entry
= makeDiskEntry(object
, 1, 1);
1680 if(!entry
->writeable
)
1682 diskEntrySize(object
);
1687 offset
= entry
->size
;
1689 /* Avoid a seek in case we start writing at the beginning */
1690 if(offset
== 0 && entry
->metadataDirty
) {
1691 writeoutMetadata(object
);
1692 /* rewriteDiskEntry may change the entry */
1693 entry
= makeDiskEntry(object
, 1, 0);
1694 if(entry
== NULL
|| !entry
->writeable
)
1698 rc
= entrySeek(entry
, offset
+ entry
->body_offset
);
1699 if(rc
< 0) return 0;
1702 if(max
>= 0 && bytes
>= max
)
1705 assert(entry
->offset
== offset
+ entry
->body_offset
);
1706 i
= offset
/ CHUNK_SIZE
;
1707 j
= offset
% CHUNK_SIZE
;
1708 if(i
>= object
->numchunks
)
1710 if(object
->chunks
[i
].size
<= j
)
1713 rc
= write(entry
->fd
, object
->chunks
[i
].data
+ j
,
1714 object
->chunks
[i
].size
- j
);
1718 do_log_error(L_ERROR
, errno
, "Couldn't write disk entry");
1721 entry
->offset
+= rc
;
1724 if(entry
->size
< offset
)
1725 entry
->size
= offset
;
1726 } while(j
+ rc
>= CHUNK_SIZE
);
1730 if(entry
->metadataDirty
)
1731 writeoutMetadata(object
);
1737 writeoutMetadata(ObjectPtr object
)
1739 DiskCacheEntryPtr entry
;
1742 if((object
->cache_control
& CACHE_NO_STORE
) ||
1743 (object
->flags
& OBJECT_LOCAL
))
1746 entry
= makeDiskEntry(object
, 1, 0);
1747 if(entry
== NULL
|| entry
== &negativeEntry
)
1750 assert(!entry
->local
);
1752 rc
= entrySeek(entry
, 0);
1753 if(rc
< 0) goto fail
;
1755 rc
= writeHeaders(entry
->fd
, &entry
->body_offset
, object
, NULL
, 0);
1757 rc
= rewriteEntry(object
);
1758 if(rc
< 0) return 0;
1761 if(rc
< 0) goto fail
;
1763 entry
->metadataDirty
= 0;
1767 /* We need this in order to avoid trying to write this entry out
1769 if(entry
&& entry
!= &negativeEntry
)
1770 entry
->metadataDirty
= 0;
1775 mergeDobjects(DiskObjectPtr dst
, DiskObjectPtr src
)
1777 if(dst
->filename
== NULL
) {
1778 dst
->filename
= src
->filename
;
1779 dst
->body_offset
= src
->body_offset
;
1781 free(src
->filename
);
1782 free(src
->location
);
1784 dst
->length
= src
->length
;
1786 dst
->size
= src
->size
;
1788 dst
->age
= src
->age
;
1790 dst
->date
= src
->date
;
1791 if(dst
->last_modified
< 0)
1792 dst
->last_modified
= src
->last_modified
;
1797 readDiskObject(char *filename
, struct stat
*sb
)
1799 int fd
, rc
, n
, dummy
, code
;
1801 time_t date
, last_modified
, age
, atime
, expires
;
1802 char *location
= NULL
, *fn
= NULL
;
1803 DiskObjectPtr dobject
;
1805 int buf_is_chunk
, bufsize
;
1812 rc
= stat(filename
, &ss
);
1814 do_log_error(L_WARN
, errno
, "Couldn't stat %s", filename
);
1821 bufsize
= CHUNK_SIZE
;
1824 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
1828 if(S_ISREG(sb
->st_mode
)) {
1829 fd
= open(filename
, O_RDONLY
| O_BINARY
);
1833 rc
= read(fd
, buf
, bufsize
);
1837 n
= findEndOfHeaders(buf
, 0, rc
, &dummy
);
1843 bufsize
= bigBufferSize
;
1844 buf
= malloc(bigBufferSize
);
1847 lrc
= lseek(fd
, 0, SEEK_SET
);
1855 rc
= httpParseServerFirstLine(buf
, &code
, &dummy
, NULL
);
1859 rc
= httpParseHeaders(0, NULL
, buf
, rc
, NULL
,
1860 NULL
, &length
, NULL
, NULL
, NULL
,
1861 &date
, &last_modified
, &expires
, &age
,
1862 &atime
, &body_offset
, NULL
,
1863 NULL
, NULL
, NULL
, NULL
, &location
, NULL
, NULL
);
1864 if(rc
< 0 || location
== NULL
)
1869 size
= sb
->st_size
- body_offset
;
1872 } else if(S_ISDIR(sb
->st_mode
)) {
1874 n
= dirnameUrl(buf
, 512, (char*)filename
, strlen(filename
));
1877 location
= strdup(n
);
1878 if(location
== NULL
)
1891 dobject
= malloc(sizeof(DiskObjectRec
));
1895 fn
= strdup(filename
);
1904 dobject
->location
= location
;
1905 dobject
->filename
= fn
;
1906 dobject
->length
= length
;
1907 dobject
->body_offset
= body_offset
;
1908 dobject
->size
= size
;
1910 dobject
->access
= atime
;
1911 dobject
->date
= date
;
1912 dobject
->last_modified
= last_modified
;
1913 dobject
->expires
= expires
;
1914 if(fd
>= 0) close(fd
);
1923 if(fd
>= 0) close(fd
);
1924 if(location
) free(location
);
1930 processObject(DiskObjectPtr dobjects
, char *filename
, struct stat
*sb
)
1932 DiskObjectPtr dobject
= NULL
;
1935 dobject
= readDiskObject((char*)filename
, sb
);
1940 (c
= strcmp(dobject
->location
, dobjects
->location
)) <= 0) {
1941 if(dobjects
&& c
== 0) {
1942 mergeDobjects(dobjects
, dobject
);
1944 dobject
->next
= dobjects
;
1948 DiskObjectPtr other
= dobjects
;
1949 while(other
->next
) {
1950 c
= strcmp(dobject
->location
, other
->next
->location
);
1953 other
= other
->next
;
1955 if(strcmp(dobject
->location
, other
->location
) == 0) {
1956 mergeDobjects(other
, dobject
);
1958 dobject
->next
= other
->next
;
1959 other
->next
= dobject
;
1965 /* Determine whether p is below root */
1967 filter(DiskObjectPtr p
, const char *root
, int n
, int recursive
)
1970 int m
= strlen(p
->location
);
1973 if(memcmp(root
, p
->location
, n
) != 0)
1977 if(m
== 0 || p
->location
[m
- 1] == '/')
1979 cp
= strchr(p
->location
+ n
, '/');
1980 if(cp
&& cp
- p
->location
!= m
- 1)
1985 /* Filter out all disk objects that are not under root */
1987 filterDiskObjects(DiskObjectPtr from
, const char *root
, int recursive
)
1989 int n
= strlen(root
);
1992 while(from
&& !filter(from
, root
, n
, recursive
)) {
2000 while(p
&& p
->next
) {
2001 if(!filter(p
->next
, root
, n
, recursive
)) {
2014 insertRoot(DiskObjectPtr from
, const char *root
)
2020 if(strcmp(root
, p
->location
) == 0)
2025 p
= malloc(sizeof(DiskObjectRec
));
2027 p
->location
= strdup(root
);
2028 if(p
->location
== NULL
) {
2037 p
->last_modified
= -1;
2043 /* Insert all missing directories in a sorted list of dobjects */
2045 insertDirs(DiskObjectPtr from
)
2047 DiskObjectPtr p
, q
, new;
2053 n
= strlen(q
->location
);
2054 if(n
> 0 && q
->location
[n
- 1] != '/') {
2055 cp
= strrchr(q
->location
, '/');
2056 m
= cp
- q
->location
+ 1;
2057 if(cp
&& (!p
|| strlen(p
->location
) < m
||
2058 memcmp(p
->location
, q
->location
, m
) != 0)) {
2059 new = malloc(sizeof(DiskObjectRec
));
2061 new->location
= strdup_n(q
->location
, m
);
2062 if(new->location
== NULL
) {
2066 new->filename
= NULL
;
2071 new->last_modified
= -1;
2087 indexDiskObjects(FILE *out
, const char *root
, int recursive
)
2091 struct dirent
*dirent
;
2096 DiskObjectPtr dobjects
= NULL
;
2097 char *of
= root
[0] == '\0' ? "" : " of ";
2099 fprintf(out
, "<!DOCTYPE HTML PUBLIC "
2100 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
2101 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
2103 "<title>%s%s%s</title>\n"
2105 "<h1>%s%s%s</h1>\n",
2106 recursive
? "Recursive index" : "Index", of
, root
,
2107 recursive
? "Recursive index" : "Index", of
, root
);
2109 if(diskCacheRoot
== NULL
|| diskCacheRoot
->length
<= 0) {
2110 fprintf(out
, "<p>No <tt>diskCacheRoot</tt>.</p>\n");
2114 if(diskCacheRoot
->length
>= 1024) {
2116 "<p>The value of <tt>diskCacheRoot</tt> is "
2117 "too long (%d).</p>\n",
2118 diskCacheRoot
->length
);
2122 if(strlen(root
) < 8) {
2123 memcpy(buf
, diskCacheRoot
->string
, diskCacheRoot
->length
);
2124 buf
[diskCacheRoot
->length
] = '\0';
2125 n
= diskCacheRoot
->length
;
2127 n
= urlDirname(buf
, 1024, root
, strlen(root
));
2134 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
2139 if(fe
->fts_info
!= FTS_DP
)
2141 processObject(dobjects
,
2143 fe
->fts_info
== FTS_NS
||
2144 fe
->fts_info
== FTS_NSOK
?
2145 fe
->fts_statp
: NULL
);
2153 dirent
= readdir(dir
);
2155 if(n
+ strlen(dirent
->d_name
) < 1024) {
2156 strcpy(buf
+ n
, dirent
->d_name
);
2160 dobjects
= processObject(dobjects
, buf
, NULL
);
2164 fprintf(out
, "<p>Couldn't open directory: %s (%d).</p>\n",
2165 strerror(errno
), errno
);
2172 DiskObjectPtr dobject
;
2174 dobjects
= insertRoot(dobjects
, root
);
2175 dobjects
= insertDirs(dobjects
);
2176 dobjects
= filterDiskObjects(dobjects
, root
, recursive
);
2179 alternatingHttpStyle(out
, "diskcachelist");
2180 fprintf(out
, "<table id=diskcachelist>\n");
2181 fprintf(out
, "<tbody>\n");
2185 i
= strlen(dobject
->location
);
2186 isdir
= (i
== 0 || dobject
->location
[i
- 1] == '/');
2188 fprintf(out
, "<tr class=odd>");
2190 fprintf(out
, "<tr class=even>");
2191 if(dobject
->size
>= 0) {
2192 fprintf(out
, "<td><a href=\"%s\"><tt>",
2195 dobject
->location
, strlen(dobject
->location
));
2196 fprintf(out
, "</tt></a></td> ");
2197 if(dobject
->length
>= 0) {
2198 if(dobject
->size
== dobject
->length
)
2199 fprintf(out
, "<td>%d</td> ", dobject
->length
);
2201 fprintf(out
, "<td>%d/%d</td> ",
2202 dobject
->size
, dobject
->length
);
2204 /* Avoid a trigraph. */
2205 fprintf(out
, "<td>%d/<em>??" "?</em></td> ", dobject
->size
);
2207 if(dobject
->last_modified
>= 0) {
2208 struct tm
*tm
= gmtime(&dobject
->last_modified
);
2212 n
= strftime(buf
, 1024, "%d.%m.%Y", tm
);
2217 fprintf(out
, "<td>%s</td> ", buf
);
2219 fprintf(out
, "<td></td>");
2222 if(dobject
->date
>= 0) {
2223 struct tm
*tm
= gmtime(&dobject
->date
);
2227 n
= strftime(buf
, 1024, "%d.%m.%Y", tm
);
2232 fprintf(out
, "<td>%s</td>", buf
);
2234 fprintf(out
, "<td></td>");
2237 fprintf(out
, "<td><tt>");
2238 htmlPrint(out
, dobject
->location
,
2239 strlen(dobject
->location
));
2240 fprintf(out
, "</tt></td><td></td><td></td><td></td>");
2243 fprintf(out
, "<td><a href=\"/polipo/index?%s\">plain</a></td>"
2244 "<td><a href=\"/polipo/recursive-index?%s\">"
2245 "recursive</a></td>",
2246 dobject
->location
, dobject
->location
);
2248 fprintf(out
, "</tr>\n");
2250 dobjects
= dobject
->next
;
2251 free(dobject
->location
);
2252 free(dobject
->filename
);
2255 fprintf(out
, "</tbody>\n");
2256 fprintf(out
, "</table>\n");
2260 fprintf(out
, "<p><a href=\"/polipo/\">back</a></p>\n");
2261 fprintf(out
, "</body></html>\n");
2266 checkForZeroes(char *buf
, int n
)
2269 unsigned long *lbuf
= (unsigned long *)buf
;
2270 assert(n
% sizeof(unsigned long) == 0);
2272 for(i
= 0; i
* sizeof(unsigned long) < n
; i
++) {
2274 return i
* sizeof(unsigned long);
2276 for(j
= 0; i
* sizeof(unsigned long) + j
< n
; j
++) {
2277 if(buf
[i
* sizeof(unsigned long) + j
] != 0)
2281 return i
* sizeof(unsigned long) + j
;
2285 copyFile(int from
, char *filename
, int n
)
2288 int to
, offset
, nread
, nzeroes
, rc
;
2290 buf
= malloc(CHUNK_SIZE
);
2294 to
= open(filename
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
,
2295 diskCacheFilePermissions
);
2303 nread
= read(from
, buf
, MIN(CHUNK_SIZE
, n
- offset
));
2306 nzeroes
= checkForZeroes(buf
, nread
& -8);
2309 rc
= lseek(to
, nzeroes
, SEEK_CUR
);
2310 if(rc
!= offset
+ nzeroes
) {
2312 do_log_error(L_ERROR
, errno
, "Couldn't extend file");
2315 "Couldn't extend file: "
2316 "unexpected offset %d != %d + %d.\n",
2321 if(nread
> nzeroes
) {
2322 rc
= write(to
, buf
+ nzeroes
, nread
- nzeroes
);
2323 if(rc
!= nread
- nzeroes
) {
2325 do_log_error(L_ERROR
, errno
, "Couldn't write");
2327 do_log(L_ERROR
, "Short write.\n");
2336 unlink(filename
); /* something went wrong straight away */
2341 expireFile(char *filename
, struct stat
*sb
,
2342 int *considered
, int *unlinked
, int *truncated
)
2344 DiskObjectPtr dobject
= NULL
;
2347 long int ret
= sb
->st_size
;
2349 if(!preciseExpiry
) {
2351 if(t
> current_time
.tv_sec
+ 1) {
2352 do_log(L_WARN
, "File %s has access time in the future.\n",
2354 t
= current_time
.tv_sec
;
2357 if(t
> current_time
.tv_sec
- diskCacheUnlinkTime
&&
2358 (sb
->st_size
< diskCacheTruncateSize
||
2359 t
> current_time
.tv_sec
- diskCacheTruncateTime
))
2365 dobject
= readDiskObject(filename
, sb
);
2367 do_log(L_ERROR
, "Incorrect disk entry %s -- removing.\n", filename
);
2368 rc
= unlink(filename
);
2370 do_log_error(L_ERROR
, errno
,
2371 "Couldn't unlink %s", filename
);
2379 t
= dobject
->access
;
2380 if(t
< 0) t
= dobject
->age
;
2381 if(t
< 0) t
= dobject
->date
;
2383 if(t
> current_time
.tv_sec
)
2385 "Disk entry %s (%s) has access time in the future.\n",
2386 dobject
->location
, dobject
->filename
);
2388 if(t
< current_time
.tv_sec
- diskCacheUnlinkTime
) {
2389 rc
= unlink(dobject
->filename
);
2391 do_log_error(L_ERROR
, errno
, "Couldn't unlink %s", filename
);
2396 } else if(dobject
->size
>
2397 diskCacheTruncateSize
+ 4 * dobject
->body_offset
&&
2398 t
< current_time
.tv_sec
- diskCacheTruncateTime
) {
2399 /* We need to copy rather than simply truncate in place: the
2400 latter would confuse a running polipo. */
2401 fd
= open(dobject
->filename
, O_RDONLY
| O_BINARY
, 0);
2402 rc
= unlink(dobject
->filename
);
2404 do_log_error(L_ERROR
, errno
, "Couldn't unlink %s", filename
);
2409 copyFile(fd
, dobject
->filename
,
2410 dobject
->body_offset
+ diskCacheTruncateSize
);
2414 ret
= sb
->st_size
- dobject
->body_offset
+ diskCacheTruncateSize
;
2417 free(dobject
->location
);
2418 free(dobject
->filename
);
2430 int files
= 0, considered
= 0, unlinked
= 0, truncated
= 0;
2431 int dirs
= 0, rmdirs
= 0;
2432 long left
= 0, total
= 0;
2434 if(diskCacheRoot
== NULL
||
2435 diskCacheRoot
->length
<= 0 || diskCacheRoot
->string
[0] != '/')
2438 fts_argv
[0] = diskCacheRoot
->string
;
2440 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
2442 do_log_error(L_ERROR
, errno
, "Couldn't fts_open disk cache");
2445 gettimeofday(¤t_time
, NULL
);
2450 if(fe
->fts_info
== FTS_D
)
2453 if(fe
->fts_info
== FTS_DP
|| fe
->fts_info
== FTS_DC
||
2454 fe
->fts_info
== FTS_DNR
) {
2455 if(fe
->fts_accpath
[0] == '/' &&
2456 strlen(fe
->fts_accpath
) <= diskCacheRoot
->length
)
2459 rc
= rmdir(fe
->fts_accpath
);
2462 else if(errno
!= ENOTEMPTY
&& errno
!= EEXIST
)
2463 do_log_error(L_ERROR
, errno
,
2464 "Couldn't remove directory %s",
2467 } else if(fe
->fts_info
== FTS_NS
) {
2468 do_log_error(L_ERROR
, fe
->fts_errno
, "Couldn't stat file %s",
2471 } else if(fe
->fts_info
== FTS_ERR
) {
2472 do_log_error(L_ERROR
, fe
->fts_errno
,
2473 "Couldn't fts_read disk cache");
2477 if(!S_ISREG(fe
->fts_statp
->st_mode
)) {
2478 do_log(L_ERROR
, "Unexpected file %s type 0%o.\n",
2479 fe
->fts_accpath
, (unsigned int)fe
->fts_statp
->st_mode
);
2484 left
+= expireFile(fe
->fts_accpath
, fe
->fts_statp
,
2485 &considered
, &unlinked
, &truncated
);
2486 total
+= fe
->fts_statp
->st_size
;
2491 printf("Disk cache purged.\n");
2492 printf("%d files, %d considered, %d removed, %d truncated "
2493 "(%ldkB -> %ldkB).\n",
2494 files
, considered
, unlinked
, truncated
, total
/1024, left
/1024);
2495 printf("%d directories, %d removed.\n", dirs
, rmdirs
);
2514 writeoutToDisk(ObjectPtr object
, int upto
, int max
)
2520 destroyDiskEntry(ObjectPtr object
, int d
)
2526 objectGetFromDisk(ObjectPtr object
)
2532 objectFillFromDisk(ObjectPtr object
, int offset
, int chunks
)
2538 revalidateDiskEntry(ObjectPtr object
)
2544 dirtyDiskEntry(ObjectPtr object
)
2552 do_log(L_ERROR
, "Disk cache not supported in this version.\n");
2556 diskEntrySize(ObjectPtr object
)