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
+ 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 writeable
, int create
)
1137 DiskCacheEntryPtr entry
= NULL
;
1140 int negative
= 0, isWriteable
= 0, size
= -1, name_len
= -1;
1143 int body_offset
= -1;
1145 int local
= (object
->flags
& OBJECT_LOCAL
) != 0;
1148 if(local
&& (writeable
|| create
))
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
&& (!writeable
|| entry
->writeable
)) {
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
;
1202 fd
= open(buf
, O_RDWR
| O_BINARY
);
1203 if(fd
< 0 && !writeable
&& errno
== EACCES
) {
1205 fd
= open(buf
, O_RDONLY
| O_BINARY
);
1209 rc
= validateEntry(object
, fd
, &body_offset
, &offset
);
1216 if(rc
< 0 && errno
!= ENOENT
) {
1217 do_log_error(L_WARN
, errno
,
1218 "Couldn't unlink stale disk entry %s",
1220 /* But continue -- it's okay to have stale entries. */
1225 if(fd
< 0 && create
&& name_len
> 0 &&
1226 !(object
->flags
& OBJECT_INITIAL
)) {
1228 fd
= createFile(buf
, diskCacheRoot
->length
);
1235 if(object
->numchunks
> 0) {
1236 data
= object
->chunks
[0].data
;
1237 dsize
= object
->chunks
[0].size
;
1239 rc
= writeHeaders(fd
, &body_offset
, object
, data
, dsize
);
1241 do_log_error(L_ERROR
, errno
, "Couldn't write headers");
1243 if(rc
< 0 && errno
!= ENOENT
)
1244 do_log_error(L_ERROR
, errno
,
1245 "Couldn't unlink truncated entry %s",
1250 assert(rc
>= body_offset
);
1251 size
= rc
- body_offset
;
1258 if(localDocumentRoot
== NULL
|| localDocumentRoot
->length
== 0)
1262 localFilename(buf
, 1024, object
->key
, object
->key_size
);
1266 fd
= open(buf
, O_RDONLY
| O_BINARY
);
1268 if(validateEntry(object
, fd
, &body_offset
, NULL
) < 0) {
1277 object
->disk_entry
= &negativeEntry
;
1280 assert(body_offset
>= 0);
1282 name
= strdup_n(buf
, name_len
);
1284 do_log(L_ERROR
, "Couldn't allocate name.\n");
1289 entry
= malloc(sizeof(DiskCacheEntryRec
));
1291 do_log(L_ERROR
, "Couldn't allocate entry.\n");
1297 entry
->filename
= name
;
1298 entry
->object
= object
;
1300 entry
->body_offset
= body_offset
;
1301 entry
->local
= local
;
1302 entry
->offset
= offset
;
1304 entry
->metadataDirty
= dirty
;
1305 entry
->writeable
= isWriteable
;
1307 entry
->next
= diskEntries
;
1309 diskEntries
->previous
= entry
;
1310 diskEntries
= entry
;
1311 if(diskEntriesLast
== NULL
)
1312 diskEntriesLast
= entry
;
1313 entry
->previous
= NULL
;
1316 object
->disk_entry
= entry
;
1322 /* Rewrite a disk cache entry, used when the body offset needs to change. */
1324 rewriteEntry(ObjectPtr object
)
1326 int old_body_offset
= object
->disk_entry
->body_offset
;
1328 DiskCacheEntryPtr entry
;
1330 int buf_is_chunk
, bufsize
;
1333 fd
= dup(object
->disk_entry
->fd
);
1335 do_log_error(L_ERROR
, errno
, "Couldn't duplicate file descriptor");
1339 rc
= destroyDiskEntry(object
, 1);
1344 entry
= makeDiskEntry(object
, 1, 1);
1350 offset
= diskEntrySize(object
);
1356 bufsize
= CHUNK_SIZE
;
1358 buf
= maybe_get_chunk();
1364 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
1370 rc
= lseek(fd
, old_body_offset
+ offset
, SEEK_SET
);
1376 n
= read(fd
, buf
, bufsize
);
1377 if(n
< 0 && errno
== EINTR
)
1381 rc
= entrySeek(entry
, entry
->body_offset
+ offset
);
1385 rc
= write(entry
->fd
, buf
, n
);
1387 entry
->offset
+= rc
;
1389 } else if(errno
== EINTR
) {
1398 if(object
->length
>= 0 && entry
->size
== object
->length
)
1399 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
1409 destroyDiskEntry(ObjectPtr object
, int d
)
1411 DiskCacheEntryPtr entry
= object
->disk_entry
;
1414 assert(!entry
|| !entry
->local
|| !d
);
1417 entry
= makeDiskEntry(object
, 1, 0);
1421 if(!entry
|| entry
== &negativeEntry
) {
1425 assert(entry
->object
== object
);
1427 if(maxDiskCacheEntrySize
>= 0 && object
->size
> maxDiskCacheEntrySize
) {
1428 /* See writeoutToDisk */
1433 entry
->object
->flags
&= ~OBJECT_DISK_ENTRY_COMPLETE
;
1434 if(entry
->filename
) {
1435 urc
= unlink(entry
->filename
);
1437 do_log_error(L_WARN
, errno
,
1438 "Couldn't unlink %s", scrub(entry
->filename
));
1441 if(entry
&& entry
->metadataDirty
)
1442 writeoutMetadata(object
);
1443 makeDiskEntry(object
, 1, 0);
1444 /* rewriteDiskEntry may change the disk entry */
1445 entry
= object
->disk_entry
;
1446 if(entry
== NULL
|| entry
== &negativeEntry
)
1448 if(entry
->writeable
&& diskCacheWriteoutOnClose
> 0)
1449 reallyWriteoutToDisk(object
, -1, diskCacheWriteoutOnClose
);
1452 rc
= close(entry
->fd
);
1453 if(rc
< 0 && errno
== EINTR
)
1459 free(entry
->filename
);
1460 entry
->filename
= NULL
;
1463 entry
->previous
->next
= entry
->next
;
1465 diskEntries
= entry
->next
;
1467 entry
->next
->previous
= entry
->previous
;
1469 diskEntriesLast
= entry
->previous
;
1472 assert(numDiskEntries
>= 0);
1475 object
->disk_entry
= NULL
;
1483 objectGetFromDisk(ObjectPtr object
)
1485 DiskCacheEntryPtr entry
= makeDiskEntry(object
, 0, 0);
1486 if(!entry
) return NULL
;
1492 objectFillFromDisk(ObjectPtr object
, int offset
, int chunks
)
1494 DiskCacheEntryPtr entry
;
1499 if(object
->type
!= OBJECT_HTTP
)
1502 if(object
->flags
& OBJECT_LINEAR
)
1505 if(object
->length
>= 0) {
1506 chunks
= MIN(chunks
,
1507 (object
->length
- offset
+ CHUNK_SIZE
- 1) / CHUNK_SIZE
);
1510 rc
= objectSetChunks(object
, offset
/ CHUNK_SIZE
+ chunks
);
1515 if(object
->flags
& OBJECT_INITIAL
) {
1517 } else if((object
->length
< 0 || object
->size
< object
->length
) &&
1518 object
->size
< (offset
/ CHUNK_SIZE
+ chunks
) * CHUNK_SIZE
) {
1521 for(k
= 0; k
< chunks
; k
++) {
1523 i
= offset
/ CHUNK_SIZE
+ k
;
1524 s
= MIN(CHUNK_SIZE
, object
->size
- i
* CHUNK_SIZE
);
1525 if(object
->chunks
[i
].size
< s
) {
1535 /* This has the side-effect of revalidating the entry, which is
1536 what makes HEAD requests work. */
1537 entry
= makeDiskEntry(object
, 0, 0);
1541 for(k
= 0; k
< chunks
; k
++) {
1542 i
= offset
/ CHUNK_SIZE
+ k
;
1543 if(!object
->chunks
[i
].data
)
1544 object
->chunks
[i
].data
= get_chunk();
1545 if(!object
->chunks
[i
].data
) {
1549 lockChunk(object
, i
);
1554 for(k
= 0; k
< chunks
; k
++) {
1556 i
= offset
/ CHUNK_SIZE
+ k
;
1557 j
= object
->chunks
[i
].size
;
1558 o
= i
* CHUNK_SIZE
+ j
;
1560 if(object
->chunks
[i
].size
== CHUNK_SIZE
)
1563 if(entry
->size
>= 0 && entry
->size
<= o
)
1566 if(entry
->offset
!= entry
->body_offset
+ o
) {
1567 rc
= entrySeek(entry
, entry
->body_offset
+ o
);
1576 rc
= read(entry
->fd
, object
->chunks
[i
].data
+ j
, CHUNK_SIZE
- j
);
1581 do_log_error(L_ERROR
, errno
, "Couldn't read");
1585 entry
->offset
+= rc
;
1586 object
->chunks
[i
].size
+= rc
;
1587 if(object
->size
< o
+ rc
)
1588 object
->size
= o
+ rc
;
1590 if(entry
->object
->length
>= 0 && entry
->size
< 0 &&
1591 entry
->offset
- entry
->body_offset
== entry
->object
->length
)
1592 entry
->size
= entry
->object
->length
;
1594 if(rc
< CHUNK_SIZE
- j
) {
1595 /* Paranoia: the read may have been interrupted half-way. */
1596 if(entry
->size
< 0) {
1598 (entry
->object
->length
>= 0 &&
1599 entry
->object
->length
==
1600 entry
->offset
- entry
->body_offset
))
1601 entry
->size
= entry
->offset
- entry
->body_offset
;
1603 } else if(entry
->size
!= entry
->offset
- entry
->body_offset
) {
1605 entry
->size
< entry
->offset
- entry
->body_offset
) {
1607 "Disk entry size changed behind our back: "
1608 "%ld -> %ld (%d).\n",
1610 (long)entry
->offset
- entry
->body_offset
,
1622 CHECK_ENTRY(object
->disk_entry
);
1623 for(k
= 0; k
< chunks
; k
++) {
1624 i
= offset
/ CHUNK_SIZE
+ k
;
1625 unlockChunk(object
, i
);
1629 notifyObject(object
);
1637 writeoutToDisk(ObjectPtr object
, int upto
, int max
)
1639 if(maxDiskCacheEntrySize
>= 0 && object
->size
> maxDiskCacheEntrySize
) {
1640 /* An object was created with an unknown length, and then grew
1641 beyond maxDiskCacheEntrySize. Destroy the disk entry. */
1642 destroyDiskEntry(object
, 1);
1646 return reallyWriteoutToDisk(object
, upto
, max
);
1650 reallyWriteoutToDisk(ObjectPtr object
, int upto
, int max
)
1652 DiskCacheEntryPtr entry
;
1659 upto
= object
->size
;
1661 if((object
->cache_control
& CACHE_NO_STORE
) ||
1662 (object
->flags
& OBJECT_LOCAL
))
1665 if((object
->flags
& OBJECT_DISK_ENTRY_COMPLETE
) && !object
->disk_entry
)
1668 entry
= makeDiskEntry(object
, 1, 1);
1669 if(!entry
) return 0;
1671 assert(!entry
->local
);
1673 if(object
->flags
& OBJECT_DISK_ENTRY_COMPLETE
)
1676 diskEntrySize(object
);
1680 if(object
->length
>= 0 && entry
->size
>= object
->length
) {
1681 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
1685 if(entry
->size
>= upto
)
1688 if(!entry
->writeable
) {
1689 entry
= makeDiskEntry(object
, 1, 1);
1692 if(!entry
->writeable
)
1694 diskEntrySize(object
);
1699 offset
= entry
->size
;
1701 /* Avoid a seek in case we start writing at the beginning */
1702 if(offset
== 0 && entry
->metadataDirty
) {
1703 writeoutMetadata(object
);
1704 /* rewriteDiskEntry may change the entry */
1705 entry
= makeDiskEntry(object
, 1, 0);
1706 if(entry
== NULL
|| !entry
->writeable
)
1710 rc
= entrySeek(entry
, offset
+ entry
->body_offset
);
1711 if(rc
< 0) return 0;
1714 if(max
>= 0 && bytes
>= max
)
1717 assert(entry
->offset
== offset
+ entry
->body_offset
);
1718 i
= offset
/ CHUNK_SIZE
;
1719 j
= offset
% CHUNK_SIZE
;
1720 if(i
>= object
->numchunks
)
1722 if(object
->chunks
[i
].size
<= j
)
1725 rc
= write(entry
->fd
, object
->chunks
[i
].data
+ j
,
1726 object
->chunks
[i
].size
- j
);
1730 do_log_error(L_ERROR
, errno
, "Couldn't write disk entry");
1733 entry
->offset
+= rc
;
1736 if(entry
->size
< offset
)
1737 entry
->size
= offset
;
1738 } while(j
+ rc
>= CHUNK_SIZE
);
1742 if(entry
->metadataDirty
)
1743 writeoutMetadata(object
);
1749 writeoutMetadata(ObjectPtr object
)
1751 DiskCacheEntryPtr entry
;
1754 if((object
->cache_control
& CACHE_NO_STORE
) ||
1755 (object
->flags
& OBJECT_LOCAL
))
1758 entry
= makeDiskEntry(object
, 1, 0);
1759 if(entry
== NULL
|| entry
== &negativeEntry
)
1762 assert(!entry
->local
);
1764 rc
= entrySeek(entry
, 0);
1765 if(rc
< 0) goto fail
;
1767 rc
= writeHeaders(entry
->fd
, &entry
->body_offset
, object
, NULL
, 0);
1769 rc
= rewriteEntry(object
);
1770 if(rc
< 0) return 0;
1773 if(rc
< 0) goto fail
;
1775 entry
->metadataDirty
= 0;
1779 /* We need this in order to avoid trying to write this entry out
1781 if(entry
&& entry
!= &negativeEntry
)
1782 entry
->metadataDirty
= 0;
1787 mergeDobjects(DiskObjectPtr dst
, DiskObjectPtr src
)
1789 if(dst
->filename
== NULL
) {
1790 dst
->filename
= src
->filename
;
1791 dst
->body_offset
= src
->body_offset
;
1793 free(src
->filename
);
1794 free(src
->location
);
1796 dst
->length
= src
->length
;
1798 dst
->size
= src
->size
;
1800 dst
->age
= src
->age
;
1802 dst
->date
= src
->date
;
1803 if(dst
->last_modified
< 0)
1804 dst
->last_modified
= src
->last_modified
;
1809 readDiskObject(char *filename
, struct stat
*sb
)
1811 int fd
, rc
, n
, dummy
, code
;
1813 time_t date
, last_modified
, age
, atime
, expires
;
1814 char *location
= NULL
, *fn
= NULL
;
1815 DiskObjectPtr dobject
;
1817 int buf_is_chunk
, bufsize
;
1824 rc
= stat(filename
, &ss
);
1826 do_log_error(L_WARN
, errno
, "Couldn't stat %s", scrub(filename
));
1833 bufsize
= CHUNK_SIZE
;
1836 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
1840 if(S_ISREG(sb
->st_mode
)) {
1841 fd
= open(filename
, O_RDONLY
| O_BINARY
);
1845 rc
= read(fd
, buf
, bufsize
);
1849 n
= findEndOfHeaders(buf
, 0, rc
, &dummy
);
1855 bufsize
= bigBufferSize
;
1856 buf
= malloc(bigBufferSize
);
1859 lrc
= lseek(fd
, 0, SEEK_SET
);
1867 rc
= httpParseServerFirstLine(buf
, &code
, &dummy
, NULL
);
1871 rc
= httpParseHeaders(0, NULL
, buf
, rc
, NULL
,
1872 NULL
, &length
, NULL
, NULL
, NULL
,
1873 &date
, &last_modified
, &expires
, &age
,
1874 &atime
, &body_offset
, NULL
,
1875 NULL
, NULL
, NULL
, NULL
, &location
, NULL
, NULL
);
1876 if(rc
< 0 || location
== NULL
)
1881 size
= sb
->st_size
- body_offset
;
1884 } else if(S_ISDIR(sb
->st_mode
)) {
1886 n
= dirnameUrl(buf
, 512, (char*)filename
, strlen(filename
));
1889 location
= strdup(n
);
1890 if(location
== NULL
)
1904 dobject
= malloc(sizeof(DiskObjectRec
));
1908 fn
= strdup(filename
);
1917 dobject
->location
= location
;
1918 dobject
->filename
= fn
;
1919 dobject
->length
= length
;
1920 dobject
->body_offset
= body_offset
;
1921 dobject
->size
= size
;
1923 dobject
->access
= atime
;
1924 dobject
->date
= date
;
1925 dobject
->last_modified
= last_modified
;
1926 dobject
->expires
= expires
;
1927 if(fd
>= 0) close(fd
);
1936 if(fd
>= 0) close(fd
);
1937 if(location
) free(location
);
1943 processObject(DiskObjectPtr dobjects
, char *filename
, struct stat
*sb
)
1945 DiskObjectPtr dobject
= NULL
;
1948 dobject
= readDiskObject((char*)filename
, sb
);
1953 (c
= strcmp(dobject
->location
, dobjects
->location
)) <= 0) {
1954 if(dobjects
&& c
== 0) {
1955 mergeDobjects(dobjects
, dobject
);
1957 dobject
->next
= dobjects
;
1961 DiskObjectPtr other
= dobjects
;
1962 while(other
->next
) {
1963 c
= strcmp(dobject
->location
, other
->next
->location
);
1966 other
= other
->next
;
1968 if(strcmp(dobject
->location
, other
->location
) == 0) {
1969 mergeDobjects(other
, dobject
);
1971 dobject
->next
= other
->next
;
1972 other
->next
= dobject
;
1978 /* Determine whether p is below root */
1980 filter(DiskObjectPtr p
, const char *root
, int n
, int recursive
)
1983 int m
= strlen(p
->location
);
1986 if(memcmp(root
, p
->location
, n
) != 0)
1990 if(m
== 0 || p
->location
[m
- 1] == '/')
1992 cp
= strchr(p
->location
+ n
, '/');
1993 if(cp
&& cp
- p
->location
!= m
- 1)
1998 /* Filter out all disk objects that are not under root */
2000 filterDiskObjects(DiskObjectPtr from
, const char *root
, int recursive
)
2002 int n
= strlen(root
);
2005 while(from
&& !filter(from
, root
, n
, recursive
)) {
2013 while(p
&& p
->next
) {
2014 if(!filter(p
->next
, root
, n
, recursive
)) {
2027 insertRoot(DiskObjectPtr from
, const char *root
)
2033 if(strcmp(root
, p
->location
) == 0)
2038 p
= malloc(sizeof(DiskObjectRec
));
2040 p
->location
= strdup(root
);
2041 if(p
->location
== NULL
) {
2050 p
->last_modified
= -1;
2056 /* Insert all missing directories in a sorted list of dobjects */
2058 insertDirs(DiskObjectPtr from
)
2060 DiskObjectPtr p
, q
, new;
2066 n
= strlen(q
->location
);
2067 if(n
> 0 && q
->location
[n
- 1] != '/') {
2068 cp
= strrchr(q
->location
, '/');
2069 m
= cp
- q
->location
+ 1;
2070 if(cp
&& (!p
|| strlen(p
->location
) < m
||
2071 memcmp(p
->location
, q
->location
, m
) != 0)) {
2072 new = malloc(sizeof(DiskObjectRec
));
2074 new->location
= strdup_n(q
->location
, m
);
2075 if(new->location
== NULL
) {
2079 new->filename
= NULL
;
2084 new->last_modified
= -1;
2100 indexDiskObjects(FILE *out
, const char *root
, int recursive
)
2104 struct dirent
*dirent
;
2109 DiskObjectPtr dobjects
= NULL
;
2110 char *of
= root
[0] == '\0' ? "" : " of ";
2112 fprintf(out
, "<!DOCTYPE HTML PUBLIC "
2113 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
2114 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
2116 "<title>%s%s%s</title>\n"
2118 "<h1>%s%s%s</h1>\n",
2119 recursive
? "Recursive index" : "Index", of
, root
,
2120 recursive
? "Recursive index" : "Index", of
, root
);
2122 if(diskCacheRoot
== NULL
|| diskCacheRoot
->length
<= 0) {
2123 fprintf(out
, "<p>No <tt>diskCacheRoot</tt>.</p>\n");
2127 if(diskCacheRoot
->length
>= 1024) {
2129 "<p>The value of <tt>diskCacheRoot</tt> is "
2130 "too long (%d).</p>\n",
2131 diskCacheRoot
->length
);
2135 if(strlen(root
) < 8) {
2136 memcpy(buf
, diskCacheRoot
->string
, diskCacheRoot
->length
);
2137 buf
[diskCacheRoot
->length
] = '\0';
2138 n
= diskCacheRoot
->length
;
2140 n
= urlDirname(buf
, 1024, root
, strlen(root
));
2147 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
2152 if(fe
->fts_info
!= FTS_DP
)
2154 processObject(dobjects
,
2156 fe
->fts_info
== FTS_NS
||
2157 fe
->fts_info
== FTS_NSOK
?
2158 fe
->fts_statp
: NULL
);
2166 dirent
= readdir(dir
);
2168 if(n
+ strlen(dirent
->d_name
) < 1024) {
2169 strcpy(buf
+ n
, dirent
->d_name
);
2173 dobjects
= processObject(dobjects
, buf
, NULL
);
2177 fprintf(out
, "<p>Couldn't open directory: %s (%d).</p>\n",
2178 strerror(errno
), errno
);
2185 DiskObjectPtr dobject
;
2187 dobjects
= insertRoot(dobjects
, root
);
2188 dobjects
= insertDirs(dobjects
);
2189 dobjects
= filterDiskObjects(dobjects
, root
, recursive
);
2191 alternatingHttpStyle(out
, "diskcachelist");
2192 fprintf(out
, "<table id=diskcachelist>\n");
2193 fprintf(out
, "<tbody>\n");
2197 i
= strlen(dobject
->location
);
2198 isdir
= (i
== 0 || dobject
->location
[i
- 1] == '/');
2200 fprintf(out
, "<tr class=odd>");
2202 fprintf(out
, "<tr class=even>");
2203 if(dobject
->size
>= 0) {
2204 fprintf(out
, "<td><a href=\"%s\"><tt>",
2207 dobject
->location
, strlen(dobject
->location
));
2208 fprintf(out
, "</tt></a></td> ");
2209 if(dobject
->length
>= 0) {
2210 if(dobject
->size
== dobject
->length
)
2211 fprintf(out
, "<td>%d</td> ", dobject
->length
);
2213 fprintf(out
, "<td>%d/%d</td> ",
2214 dobject
->size
, dobject
->length
);
2216 /* Avoid a trigraph. */
2217 fprintf(out
, "<td>%d/<em>??" "?</em></td> ", dobject
->size
);
2219 if(dobject
->last_modified
>= 0) {
2220 struct tm
*tm
= gmtime(&dobject
->last_modified
);
2224 n
= strftime(buf
, 1024, "%d.%m.%Y", tm
);
2229 fprintf(out
, "<td>%s</td> ", buf
);
2231 fprintf(out
, "<td></td>");
2234 if(dobject
->date
>= 0) {
2235 struct tm
*tm
= gmtime(&dobject
->date
);
2239 n
= strftime(buf
, 1024, "%d.%m.%Y", tm
);
2244 fprintf(out
, "<td>%s</td>", buf
);
2246 fprintf(out
, "<td></td>");
2249 fprintf(out
, "<td><tt>");
2250 htmlPrint(out
, dobject
->location
,
2251 strlen(dobject
->location
));
2252 fprintf(out
, "</tt></td><td></td><td></td><td></td>");
2255 fprintf(out
, "<td><a href=\"/polipo/index?%s\">plain</a></td>"
2256 "<td><a href=\"/polipo/recursive-index?%s\">"
2257 "recursive</a></td>",
2258 dobject
->location
, dobject
->location
);
2260 fprintf(out
, "</tr>\n");
2262 dobjects
= dobject
->next
;
2263 free(dobject
->location
);
2264 free(dobject
->filename
);
2267 fprintf(out
, "</tbody>\n");
2268 fprintf(out
, "</table>\n");
2272 fprintf(out
, "<p><a href=\"/polipo/\">back</a></p>\n");
2273 fprintf(out
, "</body></html>\n");
2278 checkForZeroes(char *buf
, int n
)
2281 unsigned long *lbuf
= (unsigned long *)buf
;
2282 assert(n
% sizeof(unsigned long) == 0);
2284 for(i
= 0; i
* sizeof(unsigned long) < n
; i
++) {
2286 return i
* sizeof(unsigned long);
2288 for(j
= 0; i
* sizeof(unsigned long) + j
< n
; j
++) {
2289 if(buf
[i
* sizeof(unsigned long) + j
] != 0)
2293 return i
* sizeof(unsigned long) + j
;
2297 copyFile(int from
, char *filename
, int n
)
2300 int to
, offset
, nread
, nzeroes
, rc
;
2302 buf
= malloc(CHUNK_SIZE
);
2306 to
= open(filename
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
,
2307 diskCacheFilePermissions
);
2315 nread
= read(from
, buf
, MIN(CHUNK_SIZE
, n
- offset
));
2318 nzeroes
= checkForZeroes(buf
, nread
& -8);
2321 rc
= lseek(to
, nzeroes
, SEEK_CUR
);
2322 if(rc
!= offset
+ nzeroes
) {
2324 do_log_error(L_ERROR
, errno
, "Couldn't extend file");
2327 "Couldn't extend file: "
2328 "unexpected offset %d != %d + %d.\n",
2333 if(nread
> nzeroes
) {
2334 rc
= write(to
, buf
+ nzeroes
, nread
- nzeroes
);
2335 if(rc
!= nread
- nzeroes
) {
2337 do_log_error(L_ERROR
, errno
, "Couldn't write");
2339 do_log(L_ERROR
, "Short write.\n");
2348 unlink(filename
); /* something went wrong straight away */
2353 expireFile(char *filename
, struct stat
*sb
,
2354 int *considered
, int *unlinked
, int *truncated
)
2356 DiskObjectPtr dobject
= NULL
;
2359 long int ret
= sb
->st_size
;
2361 if(!preciseExpiry
) {
2363 if(t
> current_time
.tv_sec
+ 1) {
2364 do_log(L_WARN
, "File %s has access time in the future.\n",
2366 t
= current_time
.tv_sec
;
2369 if(t
> current_time
.tv_sec
- diskCacheUnlinkTime
&&
2370 (sb
->st_size
< diskCacheTruncateSize
||
2371 t
> current_time
.tv_sec
- diskCacheTruncateTime
))
2377 dobject
= readDiskObject(filename
, sb
);
2379 do_log(L_ERROR
, "Incorrect disk entry %s -- removing.\n",
2381 rc
= unlink(filename
);
2383 do_log_error(L_ERROR
, errno
,
2384 "Couldn't unlink %s", scrub(filename
));
2392 t
= dobject
->access
;
2393 if(t
< 0) t
= dobject
->age
;
2394 if(t
< 0) t
= dobject
->date
;
2396 if(t
> current_time
.tv_sec
)
2398 "Disk entry %s (%s) has access time in the future.\n",
2399 scrub(dobject
->location
), scrub(dobject
->filename
));
2401 if(t
< current_time
.tv_sec
- diskCacheUnlinkTime
) {
2402 rc
= unlink(dobject
->filename
);
2404 do_log_error(L_ERROR
, errno
, "Couldn't unlink %s",
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",
2424 copyFile(fd
, dobject
->filename
,
2425 dobject
->body_offset
+ diskCacheTruncateSize
);
2429 ret
= sb
->st_size
- dobject
->body_offset
+ diskCacheTruncateSize
;
2432 free(dobject
->location
);
2433 free(dobject
->filename
);
2445 int files
= 0, considered
= 0, unlinked
= 0, truncated
= 0;
2446 int dirs
= 0, rmdirs
= 0;
2447 long left
= 0, total
= 0;
2449 if(diskCacheRoot
== NULL
||
2450 diskCacheRoot
->length
<= 0 || diskCacheRoot
->string
[0] != '/')
2453 fts_argv
[0] = diskCacheRoot
->string
;
2455 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
2457 do_log_error(L_ERROR
, errno
, "Couldn't fts_open disk cache");
2460 gettimeofday(¤t_time
, NULL
);
2465 if(fe
->fts_info
== FTS_D
)
2468 if(fe
->fts_info
== FTS_DP
|| fe
->fts_info
== FTS_DC
||
2469 fe
->fts_info
== FTS_DNR
) {
2470 if(fe
->fts_accpath
[0] == '/' &&
2471 strlen(fe
->fts_accpath
) <= diskCacheRoot
->length
)
2474 rc
= rmdir(fe
->fts_accpath
);
2477 else if(errno
!= ENOTEMPTY
&& errno
!= EEXIST
)
2478 do_log_error(L_ERROR
, errno
,
2479 "Couldn't remove directory %s",
2480 scrub(fe
->fts_accpath
));
2482 } else if(fe
->fts_info
== FTS_NS
) {
2483 do_log_error(L_ERROR
, fe
->fts_errno
, "Couldn't stat file %s",
2484 scrub(fe
->fts_accpath
));
2486 } else if(fe
->fts_info
== FTS_ERR
) {
2487 do_log_error(L_ERROR
, fe
->fts_errno
,
2488 "Couldn't fts_read disk cache");
2492 if(!S_ISREG(fe
->fts_statp
->st_mode
)) {
2493 do_log(L_ERROR
, "Unexpected file %s type 0%o.\n",
2494 fe
->fts_accpath
, (unsigned int)fe
->fts_statp
->st_mode
);
2499 left
+= expireFile(fe
->fts_accpath
, fe
->fts_statp
,
2500 &considered
, &unlinked
, &truncated
);
2501 total
+= fe
->fts_statp
->st_size
;
2506 printf("Disk cache purged.\n");
2507 printf("%d files, %d considered, %d removed, %d truncated "
2508 "(%ldkB -> %ldkB).\n",
2509 files
, considered
, unlinked
, truncated
, total
/1024, left
/1024);
2510 printf("%d directories, %d removed.\n", dirs
, rmdirs
);
2529 writeoutToDisk(ObjectPtr object
, int upto
, int max
)
2535 destroyDiskEntry(ObjectPtr object
, int d
)
2541 objectGetFromDisk(ObjectPtr object
)
2547 objectFillFromDisk(ObjectPtr object
, int offset
, int chunks
)
2553 revalidateDiskEntry(ObjectPtr object
)
2559 dirtyDiskEntry(ObjectPtr object
)
2567 do_log(L_ERROR
, "Disk cache not supported in this version.\n");
2571 diskEntrySize(ObjectPtr object
)