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
)
568 int body_offset
= *body_offset_return
;
570 int buf_is_chunk
= 0;
573 if(object
->flags
& OBJECT_LOCAL
)
576 if(body_offset
> CHUNK_SIZE
)
579 /* get_chunk might trigger object expiry */
580 bufsize
= CHUNK_SIZE
;
582 buf
= maybe_get_chunk();
588 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
594 n
= snnprintf(buf
, 0, bufsize
, "HTTP/1.1 %3d %s",
595 object
->code
, object
->message
->string
);
597 n
= httpWriteObjectHeaders(buf
, n
, bufsize
, object
, 0, -1);
601 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Location: ");
602 n
= snnprint_n(buf
, n
, bufsize
, object
->key
, object
->key_size
);
604 if(object
->age
>= 0 && object
->age
!= object
->date
) {
605 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Date: ");
606 n
= format_time(buf
, n
, bufsize
, object
->age
);
609 if(object
->atime
>= 0) {
610 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Access: ");
611 n
= format_time(buf
, n
, bufsize
, object
->atime
);
618 body_offset
= chooseBodyOffset(n
, object
);
620 if(body_offset
> bufsize
)
623 if(body_offset
> 0 && body_offset
!= n
+ 4)
624 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Body-Offset: %d",
627 n
= snnprintf(buf
, n
, bufsize
, "\r\n\r\n");
637 memset(buf
+ n
, 0, body_offset
- n
);
640 #ifdef HAVE_READV_WRITEV
643 iov
[0].iov_base
= buf
;
644 iov
[0].iov_len
= body_offset
;
645 iov
[1].iov_base
= chunk
;
646 iov
[1].iov_len
= chunk_len
;
647 rc
= writev(fd
, iov
, 2);
650 rc
= write(fd
, buf
, body_offset
);
652 if(rc
< 0 && errno
== EINTR
)
657 if(object
->length
>= 0 &&
658 rc
- body_offset
>= object
->length
)
659 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
661 *body_offset_return
= body_offset
;
669 if(bufsize
< bigBufferSize
) {
671 buf
= malloc(bigBufferSize
);
673 do_log(L_ERROR
, "Couldn't allocate big buffer.\n");
676 bufsize
= bigBufferSize
;
679 dispose_chunk(oldbuf
);
696 typedef struct _MimeEntry
{
701 static const MimeEntryRec mimeEntries
[] = {
702 { "html", "text/html" },
703 { "htm", "text/html" },
704 { "text", "text/plain" },
705 { "txt", "text/plain" },
706 { "png", "image/png" },
707 { "gif", "image/gif" },
708 { "jpeg", "image/jpeg" },
709 { "jpg", "image/jpeg" },
710 { "ico", "image/x-icon" },
711 { "pdf", "application/pdf" },
712 { "ps", "application/postscript" },
713 { "tar", "application/x-tar" },
714 { "pac", "application/x-ns-proxy-autoconfig" },
715 { "css", "text/css" },
716 { "js", "application/x-javascript" },
717 { "xml", "text/xml" },
718 { "swf", "application/x-shockwave-flash" },
722 localObjectMimeType(ObjectPtr object
, char **encoding_return
)
724 char *name
= object
->key
;
725 int nlen
= object
->key_size
;
730 if(name
[nlen
- 1] == '/') {
731 *encoding_return
= NULL
;
736 *encoding_return
= NULL
;
737 return "application/octet-stream";
740 if(memcmp(name
+ nlen
- 3, ".gz", 3) == 0) {
741 *encoding_return
= "x-gzip";
743 } else if(memcmp(name
+ nlen
- 2, ".Z", 2) == 0) {
744 *encoding_return
= "x-compress";
747 *encoding_return
= NULL
;
750 for(i
= 0; i
< sizeof(mimeEntries
) / sizeof(mimeEntries
[0]); i
++) {
751 int len
= strlen(mimeEntries
[i
].extension
);
753 name
[nlen
- len
- 1] == '.' &&
754 memcmp(name
+ nlen
- len
, mimeEntries
[i
].extension
, len
) == 0)
755 return mimeEntries
[i
].mime
;
758 return "application/octet-stream";
761 /* Same interface as validateEntry -- see below */
763 validateLocalEntry(ObjectPtr object
, int fd
,
764 int *body_offset_return
, off_t
*offset_return
)
773 do_log_error(L_ERROR
, errno
, "Couldn't stat");
777 if(S_ISREG(ss
.st_mode
)) {
778 if(!(ss
.st_mode
& S_IROTH
) ||
779 (object
->length
>= 0 && object
->length
!= ss
.st_size
) ||
780 (object
->last_modified
>= 0 &&
781 object
->last_modified
!= ss
.st_mtime
))
784 notifyObject(object
);
788 n
= snnprintf(buf
, 0, 512, "%lx-%lx-%lx",
789 (unsigned long)ss
.st_ino
,
790 (unsigned long)ss
.st_size
,
791 (unsigned long)ss
.st_mtime
);
795 if(n
> 0 && object
->etag
) {
796 if(strlen(object
->etag
) != n
||
797 memcmp(object
->etag
, buf
, n
) != 0)
801 if(!(object
->flags
& OBJECT_INITIAL
)) {
802 if(!object
->last_modified
&& !object
->etag
)
806 if(object
->flags
& OBJECT_INITIAL
) {
807 object
->length
= ss
.st_size
;
808 object
->last_modified
= ss
.st_mtime
;
809 object
->date
= current_time
.tv_sec
;
810 object
->age
= current_time
.tv_sec
;
813 object
->etag
= strdup(buf
); /* okay if fails */
814 object
->message
= internAtom("Okay");
815 n
= snnprintf(buf
, 0, 512,
817 "\r\nContent-Type: %s",
818 localObjectMimeType(object
, &encoding
));
820 n
= snnprintf(buf
, n
, 512,
821 "\r\nContent-Encoding: %s", encoding
);
824 object
->headers
= internAtomN(buf
, n
);
825 if(object
->headers
== NULL
)
827 object
->flags
&= ~OBJECT_INITIAL
;
830 if(body_offset_return
)
831 *body_offset_return
= 0;
837 /* Assumes fd is at offset 0.
838 Returns -1 if not valid, 1 if metadata should be written out, 0
841 validateEntry(ObjectPtr object
, int fd
,
842 int *body_offset_return
, off_t
*offset_return
)
845 int buf_is_chunk
, bufsize
;
850 time_t date
, last_modified
, expires
, polipo_age
, polipo_access
;
856 CacheControlRec cache_control
;
861 if(object
->flags
& OBJECT_LOCAL
)
862 return validateLocalEntry(object
, fd
,
863 body_offset_return
, offset_return
);
865 if(!(object
->flags
& OBJECT_PUBLIC
) && (object
->flags
& OBJECT_INITIAL
))
868 /* get_chunk might trigger object expiry */
869 bufsize
= CHUNK_SIZE
;
871 buf
= maybe_get_chunk();
877 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
883 rc
= read(fd
, buf
, bufsize
);
887 do_log_error(L_ERROR
, errno
, "Couldn't read disk entry");
893 n
= findEndOfHeaders(buf
, 0, rc
, &dummy
);
896 if(bufsize
< bigBufferSize
) {
897 buf
= malloc(bigBufferSize
);
899 do_log(L_ERROR
, "Couldn't allocate big buffer.\n");
902 bufsize
= bigBufferSize
;
903 memcpy(buf
, oldbuf
, offset
);
905 dispose_chunk(oldbuf
);
910 rc
= read(fd
, buf
+ offset
, bufsize
- offset
);
914 do_log_error(L_ERROR
, errno
, "Couldn't read disk entry");
920 do_log(L_ERROR
, "Couldn't parse disk entry.\n");
924 rc
= httpParseServerFirstLine(buf
, &code
, &dummy
, &message
);
926 do_log(L_ERROR
, "Couldn't parse disk entry.\n");
930 if(object
->code
!= 0 && object
->code
!= code
) {
931 releaseAtom(message
);
935 rc
= httpParseHeaders(0, NULL
, buf
, rc
, NULL
,
936 &headers
, &length
, &cache_control
, NULL
, NULL
,
937 &date
, &last_modified
, &expires
, &polipo_age
,
938 &polipo_access
, &body_offset
,
940 NULL
, NULL
, &location
, &via
, NULL
);
942 releaseAtom(message
);
948 if(!location
|| strlen(location
) != object
->key_size
||
949 memcmp(location
, object
->key
, object
->key_size
) != 0) {
950 do_log(L_ERROR
, "Inconsistent cache file for %s.\n", location
);
958 do_log(L_ERROR
, "Undated disk entry for %s.\n", location
);
962 if(!(object
->flags
& OBJECT_INITIAL
)) {
963 if((last_modified
>= 0) != (object
->last_modified
>= 0))
966 if((object
->cache_control
& CACHE_MISMATCH
) ||
967 (cache_control
.flags
& CACHE_MISMATCH
))
970 if(last_modified
>= 0 && object
->last_modified
>= 0 &&
971 last_modified
!= object
->last_modified
)
974 if(length
>= 0 && object
->length
>= 0)
975 if(length
!= object
->length
)
978 if(!!etag
!= !!object
->etag
)
981 if(etag
&& object
->etag
&& strcmp(etag
, object
->etag
) != 0)
984 /* If we don't have a usable ETag, and either CACHE_VARY or we
985 don't have a last-modified date, we validate disk entries by
987 if(!(etag
&& object
->etag
) &&
988 (!(last_modified
>= 0 && object
->last_modified
>= 0) ||
989 ((cache_control
.flags
& CACHE_VARY
) ||
990 (object
->cache_control
& CACHE_VARY
)))) {
991 if(date
>= 0 && date
!= object
->date
)
993 if(polipo_age
>= 0 && polipo_age
!= object
->age
)
996 if((object
->cache_control
& CACHE_VARY
) && dontTrustVaryETag
>= 1) {
997 /* Check content-type to work around mod_gzip bugs */
998 if(!httpHeaderMatch(atomContentType
, object
->headers
, headers
) ||
999 !httpHeaderMatch(atomContentEncoding
, object
->headers
, headers
))
1008 if(!object
->headers
)
1009 object
->headers
= headers
;
1011 releaseAtom(headers
);
1014 if(object
->code
== 0) {
1015 object
->code
= code
;
1016 object
->message
= retainAtom(message
);
1018 if(object
->date
<= date
)
1019 object
->date
= date
;
1022 if(object
->last_modified
< 0)
1023 object
->last_modified
= last_modified
;
1024 if(object
->expires
< 0)
1025 object
->expires
= expires
;
1026 else if(object
->expires
> expires
)
1029 object
->age
= polipo_age
;
1030 else if(object
->age
> polipo_age
)
1032 if(object
->atime
<= polipo_access
)
1033 object
->atime
= polipo_access
;
1037 object
->cache_control
|= cache_control
.flags
;
1039 if(object
->age
< 0) object
->age
= object
->date
;
1040 if(object
->age
< 0) object
->age
= 0; /* a long time ago */
1041 if(object
->length
< 0) object
->length
= length
;
1043 object
->etag
= etag
;
1048 releaseAtom(message
);
1050 if(object
->flags
& OBJECT_INITIAL
) object
->via
= via
;
1051 object
->flags
&= ~OBJECT_INITIAL
;
1052 if(offset
> body_offset
) {
1053 /* We need to make sure we don't invoke object expiry recursively */
1054 objectSetChunks(object
, 1);
1055 if(object
->numchunks
>= 1) {
1056 if(object
->chunks
[0].data
== NULL
)
1057 object
->chunks
[0].data
= maybe_get_chunk();
1058 if(object
->chunks
[0].data
)
1059 objectAddData(object
, buf
+ body_offset
,
1060 0, MIN(offset
- body_offset
, CHUNK_SIZE
));
1064 httpTweakCachability(object
);
1070 if(body_offset_return
) *body_offset_return
= body_offset
;
1071 if(offset_return
) *offset_return
= offset
;
1075 releaseAtom(message
);
1076 if(etag
) free(etag
);
1077 if(location
) free(location
);
1078 if(via
) releaseAtom(via
);
1090 dirtyDiskEntry(ObjectPtr object
)
1092 DiskCacheEntryPtr entry
= object
->disk_entry
;
1093 if(entry
&& entry
!= &negativeEntry
) entry
->metadataDirty
= 1;
1097 revalidateDiskEntry(ObjectPtr object
)
1099 DiskCacheEntryPtr entry
= object
->disk_entry
;
1103 if(!entry
|| entry
== &negativeEntry
)
1107 rc
= entrySeek(entry
, 0);
1108 if(rc
< 0) return 0;
1110 rc
= validateEntry(object
, entry
->fd
, &body_offset
, &entry
->offset
);
1112 destroyDiskEntry(object
, 0);
1115 if(body_offset
!= entry
->body_offset
) {
1116 do_log(L_WARN
, "Inconsistent body offset (%d != %d).\n",
1117 body_offset
, entry
->body_offset
);
1118 destroyDiskEntry(object
, 0);
1122 entry
->metadataDirty
|= !!rc
;
1128 objectHasDiskEntry(ObjectPtr object
)
1130 return object
->disk_entry
&& object
->disk_entry
!= &negativeEntry
;
1133 static DiskCacheEntryPtr
1134 makeDiskEntry(ObjectPtr object
, int writeable
, int create
)
1136 DiskCacheEntryPtr entry
= NULL
;
1139 int negative
= 0, isWriteable
= 0, size
= -1, name_len
= -1;
1142 int body_offset
= -1;
1144 int local
= (object
->flags
& OBJECT_LOCAL
) != 0;
1147 if(local
&& (writeable
|| create
))
1150 if(!local
&& !(object
->flags
& OBJECT_PUBLIC
))
1153 if(maxDiskCacheEntrySize
>= 0) {
1154 if(object
->length
> 0) {
1155 if(object
->length
> maxDiskCacheEntrySize
)
1158 if(object
->size
> maxDiskCacheEntrySize
)
1163 if(object
->disk_entry
) {
1164 entry
= object
->disk_entry
;
1166 if(entry
!= &negativeEntry
&& (!writeable
|| entry
->writeable
)) {
1167 /* We'll keep the entry -- put it at the front. */
1168 if(entry
!= diskEntries
&& entry
!= &negativeEntry
) {
1169 entry
->previous
->next
= entry
->next
;
1171 entry
->next
->previous
= entry
->previous
;
1173 diskEntriesLast
= entry
->previous
;
1174 entry
->next
= diskEntries
;
1175 diskEntries
->previous
= entry
;
1176 entry
->previous
= NULL
;
1177 diskEntries
= entry
;
1181 if(entry
== &negativeEntry
) {
1183 if(!create
) return NULL
;
1184 object
->disk_entry
= NULL
;
1187 destroyDiskEntry(object
, 0);
1191 if(numDiskEntries
> maxDiskEntries
)
1192 destroyDiskEntry(diskEntriesLast
->object
, 0);
1195 if(diskCacheRoot
== NULL
|| diskCacheRoot
->length
<= 0)
1197 name_len
= urlFilename(buf
, 1024, object
->key
, object
->key_size
);
1198 if(name_len
< 0) return NULL
;
1201 fd
= open(buf
, O_RDWR
| O_BINARY
);
1202 if(fd
< 0 && !writeable
&& errno
== EACCES
) {
1204 fd
= open(buf
, O_RDONLY
| O_BINARY
);
1208 rc
= validateEntry(object
, fd
, &body_offset
, &offset
);
1215 if(rc
< 0 && errno
!= ENOENT
) {
1216 do_log_error(L_WARN
, errno
,
1217 "Couldn't unlink stale disk entry %s",
1219 /* But continue -- it's okay to have stale entries. */
1224 if(fd
< 0 && create
&& name_len
> 0 &&
1225 !(object
->flags
& OBJECT_INITIAL
)) {
1227 fd
= createFile(buf
, diskCacheRoot
->length
);
1234 if(object
->numchunks
> 0) {
1235 data
= object
->chunks
[0].data
;
1236 dsize
= object
->chunks
[0].size
;
1238 rc
= writeHeaders(fd
, &body_offset
, object
, data
, dsize
);
1240 do_log_error(L_ERROR
, errno
, "Couldn't write headers");
1242 if(rc
< 0 && errno
!= ENOENT
)
1243 do_log_error(L_ERROR
, errno
,
1244 "Couldn't unlink truncated entry %s",
1249 assert(rc
>= body_offset
);
1250 size
= rc
- body_offset
;
1257 if(localDocumentRoot
== NULL
|| localDocumentRoot
->length
== 0)
1261 localFilename(buf
, 1024, object
->key
, object
->key_size
);
1265 fd
= open(buf
, O_RDONLY
| O_BINARY
);
1267 if(validateEntry(object
, fd
, &body_offset
, NULL
) < 0) {
1276 object
->disk_entry
= &negativeEntry
;
1279 assert(body_offset
>= 0);
1281 name
= strdup_n(buf
, name_len
);
1283 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
);
1379 rc
= entrySeek(entry
, entry
->body_offset
+ offset
);
1382 rc
= write(entry
->fd
, buf
, n
);
1384 entry
->offset
+= rc
;
1393 if(object
->length
>= 0 && entry
->size
== object
->length
)
1394 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
1404 destroyDiskEntry(ObjectPtr object
, int d
)
1406 DiskCacheEntryPtr entry
= object
->disk_entry
;
1409 assert(!entry
|| !entry
->local
|| !d
);
1412 entry
= makeDiskEntry(object
, 1, 0);
1416 if(!entry
|| entry
== &negativeEntry
) {
1420 assert(entry
->object
== object
);
1422 if(maxDiskCacheEntrySize
>= 0 && object
->size
> maxDiskCacheEntrySize
) {
1423 /* See writeoutToDisk */
1428 entry
->object
->flags
&= ~OBJECT_DISK_ENTRY_COMPLETE
;
1429 if(entry
->filename
) {
1430 urc
= unlink(entry
->filename
);
1432 do_log_error(L_WARN
, errno
,
1433 "Couldn't unlink %s", entry
->filename
);
1436 if(entry
&& entry
->metadataDirty
)
1437 writeoutMetadata(object
);
1438 makeDiskEntry(object
, 1, 0);
1439 /* rewriteDiskEntry may change the disk entry */
1440 entry
= object
->disk_entry
;
1441 if(entry
== NULL
|| entry
== &negativeEntry
)
1443 if(entry
->writeable
&& diskCacheWriteoutOnClose
> 0)
1444 reallyWriteoutToDisk(object
, -1, diskCacheWriteoutOnClose
);
1447 rc
= close(entry
->fd
);
1448 if(rc
< 0 && errno
== EINTR
)
1454 free(entry
->filename
);
1455 entry
->filename
= NULL
;
1458 entry
->previous
->next
= entry
->next
;
1460 diskEntries
= entry
->next
;
1462 entry
->next
->previous
= entry
->previous
;
1464 diskEntriesLast
= entry
->previous
;
1467 assert(numDiskEntries
>= 0);
1470 object
->disk_entry
= NULL
;
1478 objectGetFromDisk(ObjectPtr object
)
1480 DiskCacheEntryPtr entry
= makeDiskEntry(object
, 0, 0);
1481 if(!entry
) return NULL
;
1487 objectFillFromDisk(ObjectPtr object
, int offset
, int chunks
)
1489 DiskCacheEntryPtr entry
;
1494 if(object
->type
!= OBJECT_HTTP
)
1497 if(object
->flags
& OBJECT_LINEAR
)
1500 if(object
->length
>= 0) {
1501 chunks
= MIN(chunks
,
1502 (object
->length
- offset
+ CHUNK_SIZE
- 1) / CHUNK_SIZE
);
1505 rc
= objectSetChunks(object
, offset
/ CHUNK_SIZE
+ chunks
);
1510 if(object
->flags
& OBJECT_INITIAL
) {
1512 } else if((object
->length
< 0 || object
->size
< object
->length
) &&
1513 object
->size
< (offset
/ CHUNK_SIZE
+ chunks
) * CHUNK_SIZE
) {
1516 for(k
= 0; k
< chunks
; k
++) {
1518 i
= offset
/ CHUNK_SIZE
+ k
;
1519 s
= MIN(CHUNK_SIZE
, object
->size
- i
* CHUNK_SIZE
);
1520 if(object
->chunks
[i
].size
< s
) {
1530 /* This has the side-effect of revalidating the entry, which is
1531 what makes HEAD requests work. */
1532 entry
= makeDiskEntry(object
, 0, 0);
1536 for(k
= 0; k
< chunks
; k
++) {
1537 i
= offset
/ CHUNK_SIZE
+ k
;
1538 if(!object
->chunks
[i
].data
)
1539 object
->chunks
[i
].data
= get_chunk();
1540 if(!object
->chunks
[i
].data
) {
1544 lockChunk(object
, i
);
1549 for(k
= 0; k
< chunks
; k
++) {
1551 i
= offset
/ CHUNK_SIZE
+ k
;
1552 j
= object
->chunks
[i
].size
;
1553 o
= i
* CHUNK_SIZE
+ j
;
1555 if(object
->chunks
[i
].size
== CHUNK_SIZE
)
1558 if(entry
->size
>= 0 && entry
->size
<= o
)
1561 if(entry
->offset
!= entry
->body_offset
+ o
) {
1562 rc
= entrySeek(entry
, entry
->body_offset
+ o
);
1571 rc
= read(entry
->fd
, object
->chunks
[i
].data
+ j
, CHUNK_SIZE
- j
);
1576 do_log_error(L_ERROR
, errno
, "Couldn't read");
1580 entry
->offset
+= rc
;
1581 object
->chunks
[i
].size
+= rc
;
1582 if(object
->size
< o
+ rc
)
1583 object
->size
= o
+ rc
;
1585 if(entry
->object
->length
>= 0 && entry
->size
< 0 &&
1586 entry
->offset
- entry
->body_offset
== entry
->object
->length
)
1587 entry
->size
= entry
->object
->length
;
1589 if(rc
< CHUNK_SIZE
- j
) {
1590 /* Paranoia: the read may have been interrupted half-way. */
1591 if(entry
->size
< 0) {
1593 (entry
->object
->length
>= 0 &&
1594 entry
->object
->length
==
1595 entry
->offset
- entry
->body_offset
))
1596 entry
->size
= entry
->offset
- entry
->body_offset
;
1598 } else if(entry
->size
!= entry
->offset
- entry
->body_offset
) {
1600 entry
->size
< entry
->offset
- entry
->body_offset
) {
1602 "Disk entry size changed behind our back: "
1603 "%ld -> %ld (%d).\n",
1605 (long)entry
->offset
- entry
->body_offset
,
1617 CHECK_ENTRY(object
->disk_entry
);
1618 for(k
= 0; k
< chunks
; k
++) {
1619 i
= offset
/ CHUNK_SIZE
+ k
;
1620 unlockChunk(object
, i
);
1624 notifyObject(object
);
1632 writeoutToDisk(ObjectPtr object
, int upto
, int max
)
1634 if(maxDiskCacheEntrySize
>= 0 && object
->size
> maxDiskCacheEntrySize
) {
1635 /* An object was created with an unknown length, and then grew
1636 beyond maxDiskCacheEntrySize. Destroy the disk entry. */
1637 destroyDiskEntry(object
, 1);
1641 return reallyWriteoutToDisk(object
, upto
, max
);
1645 reallyWriteoutToDisk(ObjectPtr object
, int upto
, int max
)
1647 DiskCacheEntryPtr entry
;
1654 upto
= object
->size
;
1656 if((object
->cache_control
& CACHE_NO_STORE
) ||
1657 (object
->flags
& OBJECT_LOCAL
))
1660 if((object
->flags
& OBJECT_DISK_ENTRY_COMPLETE
) && !object
->disk_entry
)
1663 entry
= makeDiskEntry(object
, 1, 1);
1664 if(!entry
) return 0;
1666 assert(!entry
->local
);
1668 if(object
->flags
& OBJECT_DISK_ENTRY_COMPLETE
)
1671 diskEntrySize(object
);
1675 if(object
->length
>= 0 && entry
->size
>= object
->length
) {
1676 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
1680 if(entry
->size
>= upto
)
1683 if(!entry
->writeable
) {
1684 entry
= makeDiskEntry(object
, 1, 1);
1687 if(!entry
->writeable
)
1689 diskEntrySize(object
);
1694 offset
= entry
->size
;
1696 /* Avoid a seek in case we start writing at the beginning */
1697 if(offset
== 0 && entry
->metadataDirty
) {
1698 writeoutMetadata(object
);
1699 /* rewriteDiskEntry may change the entry */
1700 entry
= makeDiskEntry(object
, 1, 0);
1701 if(entry
== NULL
|| !entry
->writeable
)
1705 rc
= entrySeek(entry
, offset
+ entry
->body_offset
);
1706 if(rc
< 0) return 0;
1709 if(max
>= 0 && bytes
>= max
)
1712 assert(entry
->offset
== offset
+ entry
->body_offset
);
1713 i
= offset
/ CHUNK_SIZE
;
1714 j
= offset
% CHUNK_SIZE
;
1715 if(i
>= object
->numchunks
)
1717 if(object
->chunks
[i
].size
<= j
)
1720 rc
= write(entry
->fd
, object
->chunks
[i
].data
+ j
,
1721 object
->chunks
[i
].size
- j
);
1725 do_log_error(L_ERROR
, errno
, "Couldn't write disk entry");
1728 entry
->offset
+= rc
;
1731 if(entry
->size
< offset
)
1732 entry
->size
= offset
;
1733 } while(j
+ rc
>= CHUNK_SIZE
);
1737 if(entry
->metadataDirty
)
1738 writeoutMetadata(object
);
1744 writeoutMetadata(ObjectPtr object
)
1746 DiskCacheEntryPtr entry
;
1749 if((object
->cache_control
& CACHE_NO_STORE
) ||
1750 (object
->flags
& OBJECT_LOCAL
))
1753 entry
= makeDiskEntry(object
, 1, 0);
1754 if(entry
== NULL
|| entry
== &negativeEntry
)
1757 assert(!entry
->local
);
1759 rc
= entrySeek(entry
, 0);
1760 if(rc
< 0) goto fail
;
1762 rc
= writeHeaders(entry
->fd
, &entry
->body_offset
, object
, NULL
, 0);
1764 rc
= rewriteEntry(object
);
1765 if(rc
< 0) return 0;
1768 if(rc
< 0) goto fail
;
1770 entry
->metadataDirty
= 0;
1774 /* We need this in order to avoid trying to write this entry out
1776 if(entry
&& entry
!= &negativeEntry
)
1777 entry
->metadataDirty
= 0;
1782 mergeDobjects(DiskObjectPtr dst
, DiskObjectPtr src
)
1784 if(dst
->filename
== NULL
) {
1785 dst
->filename
= src
->filename
;
1786 dst
->body_offset
= src
->body_offset
;
1788 free(src
->filename
);
1789 free(src
->location
);
1791 dst
->length
= src
->length
;
1793 dst
->size
= src
->size
;
1795 dst
->age
= src
->age
;
1797 dst
->date
= src
->date
;
1798 if(dst
->last_modified
< 0)
1799 dst
->last_modified
= src
->last_modified
;
1804 readDiskObject(char *filename
, struct stat
*sb
)
1806 int fd
, rc
, n
, dummy
, code
;
1808 time_t date
, last_modified
, age
, atime
, expires
;
1809 char *location
= NULL
, *fn
= NULL
;
1810 DiskObjectPtr dobject
;
1812 int buf_is_chunk
, bufsize
;
1819 rc
= stat(filename
, &ss
);
1821 do_log_error(L_WARN
, errno
, "Couldn't stat %s", filename
);
1828 bufsize
= CHUNK_SIZE
;
1831 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
1835 if(S_ISREG(sb
->st_mode
)) {
1836 fd
= open(filename
, O_RDONLY
| O_BINARY
);
1840 rc
= read(fd
, buf
, bufsize
);
1844 n
= findEndOfHeaders(buf
, 0, rc
, &dummy
);
1850 bufsize
= bigBufferSize
;
1851 buf
= malloc(bigBufferSize
);
1854 lrc
= lseek(fd
, 0, SEEK_SET
);
1862 rc
= httpParseServerFirstLine(buf
, &code
, &dummy
, NULL
);
1866 rc
= httpParseHeaders(0, NULL
, buf
, rc
, NULL
,
1867 NULL
, &length
, NULL
, NULL
, NULL
,
1868 &date
, &last_modified
, &expires
, &age
,
1869 &atime
, &body_offset
, NULL
,
1870 NULL
, NULL
, NULL
, NULL
, &location
, NULL
, NULL
);
1871 if(rc
< 0 || location
== NULL
)
1876 size
= sb
->st_size
- body_offset
;
1879 } else if(S_ISDIR(sb
->st_mode
)) {
1881 n
= dirnameUrl(buf
, 512, (char*)filename
, strlen(filename
));
1884 location
= strdup(n
);
1885 if(location
== NULL
)
1898 dobject
= malloc(sizeof(DiskObjectRec
));
1902 fn
= strdup(filename
);
1911 dobject
->location
= location
;
1912 dobject
->filename
= fn
;
1913 dobject
->length
= length
;
1914 dobject
->body_offset
= body_offset
;
1915 dobject
->size
= size
;
1917 dobject
->access
= atime
;
1918 dobject
->date
= date
;
1919 dobject
->last_modified
= last_modified
;
1920 dobject
->expires
= expires
;
1921 if(fd
>= 0) close(fd
);
1930 if(fd
>= 0) close(fd
);
1931 if(location
) free(location
);
1937 processObject(DiskObjectPtr dobjects
, char *filename
, struct stat
*sb
)
1939 DiskObjectPtr dobject
= NULL
;
1942 dobject
= readDiskObject((char*)filename
, sb
);
1947 (c
= strcmp(dobject
->location
, dobjects
->location
)) <= 0) {
1948 if(dobjects
&& c
== 0) {
1949 mergeDobjects(dobjects
, dobject
);
1951 dobject
->next
= dobjects
;
1955 DiskObjectPtr other
= dobjects
;
1956 while(other
->next
) {
1957 c
= strcmp(dobject
->location
, other
->next
->location
);
1960 other
= other
->next
;
1962 if(strcmp(dobject
->location
, other
->location
) == 0) {
1963 mergeDobjects(other
, dobject
);
1965 dobject
->next
= other
->next
;
1966 other
->next
= dobject
;
1972 /* Determine whether p is below root */
1974 filter(DiskObjectPtr p
, const char *root
, int n
, int recursive
)
1977 int m
= strlen(p
->location
);
1980 if(memcmp(root
, p
->location
, n
) != 0)
1984 if(m
== 0 || p
->location
[m
- 1] == '/')
1986 cp
= strchr(p
->location
+ n
, '/');
1987 if(cp
&& cp
- p
->location
!= m
- 1)
1992 /* Filter out all disk objects that are not under root */
1994 filterDiskObjects(DiskObjectPtr from
, const char *root
, int recursive
)
1996 int n
= strlen(root
);
1999 while(from
&& !filter(from
, root
, n
, recursive
)) {
2007 while(p
&& p
->next
) {
2008 if(!filter(p
->next
, root
, n
, recursive
)) {
2021 insertRoot(DiskObjectPtr from
, const char *root
)
2027 if(strcmp(root
, p
->location
) == 0)
2032 p
= malloc(sizeof(DiskObjectRec
));
2034 p
->location
= strdup(root
);
2035 if(p
->location
== NULL
) {
2044 p
->last_modified
= -1;
2050 /* Insert all missing directories in a sorted list of dobjects */
2052 insertDirs(DiskObjectPtr from
)
2054 DiskObjectPtr p
, q
, new;
2060 n
= strlen(q
->location
);
2061 if(n
> 0 && q
->location
[n
- 1] != '/') {
2062 cp
= strrchr(q
->location
, '/');
2063 m
= cp
- q
->location
+ 1;
2064 if(cp
&& (!p
|| strlen(p
->location
) < m
||
2065 memcmp(p
->location
, q
->location
, m
) != 0)) {
2066 new = malloc(sizeof(DiskObjectRec
));
2068 new->location
= strdup_n(q
->location
, m
);
2069 if(new->location
== NULL
) {
2073 new->filename
= NULL
;
2078 new->last_modified
= -1;
2094 indexDiskObjects(FILE *out
, const char *root
, int recursive
)
2098 struct dirent
*dirent
;
2103 DiskObjectPtr dobjects
= NULL
;
2104 char *of
= root
[0] == '\0' ? "" : " of ";
2106 fprintf(out
, "<!DOCTYPE HTML PUBLIC "
2107 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
2108 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
2110 "<title>%s%s%s</title>\n"
2112 "<h1>%s%s%s</h1>\n",
2113 recursive
? "Recursive index" : "Index", of
, root
,
2114 recursive
? "Recursive index" : "Index", of
, root
);
2116 if(diskCacheRoot
== NULL
|| diskCacheRoot
->length
<= 0) {
2117 fprintf(out
, "<p>No <tt>diskCacheRoot</tt>.</p>\n");
2121 if(diskCacheRoot
->length
>= 1024) {
2123 "<p>The value of <tt>diskCacheRoot</tt> is "
2124 "too long (%d).</p>\n",
2125 diskCacheRoot
->length
);
2129 if(strlen(root
) < 8) {
2130 memcpy(buf
, diskCacheRoot
->string
, diskCacheRoot
->length
);
2131 buf
[diskCacheRoot
->length
] = '\0';
2132 n
= diskCacheRoot
->length
;
2134 n
= urlDirname(buf
, 1024, root
, strlen(root
));
2141 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
2146 if(fe
->fts_info
!= FTS_DP
)
2148 processObject(dobjects
,
2150 fe
->fts_info
== FTS_NS
||
2151 fe
->fts_info
== FTS_NSOK
?
2152 fe
->fts_statp
: NULL
);
2160 dirent
= readdir(dir
);
2162 if(n
+ strlen(dirent
->d_name
) < 1024) {
2163 strcpy(buf
+ n
, dirent
->d_name
);
2167 dobjects
= processObject(dobjects
, buf
, NULL
);
2171 fprintf(out
, "<p>Couldn't open directory: %s (%d).</p>\n",
2172 strerror(errno
), errno
);
2179 DiskObjectPtr dobject
;
2181 dobjects
= insertRoot(dobjects
, root
);
2182 dobjects
= insertDirs(dobjects
);
2183 dobjects
= filterDiskObjects(dobjects
, root
, recursive
);
2186 alternatingHttpStyle(out
, "diskcachelist");
2187 fprintf(out
, "<table id=diskcachelist>\n");
2188 fprintf(out
, "<tbody>\n");
2192 i
= strlen(dobject
->location
);
2193 isdir
= (i
== 0 || dobject
->location
[i
- 1] == '/');
2195 fprintf(out
, "<tr class=odd>");
2197 fprintf(out
, "<tr class=even>");
2198 if(dobject
->size
>= 0) {
2199 fprintf(out
, "<td><a href=\"%s\"><tt>",
2202 dobject
->location
, strlen(dobject
->location
));
2203 fprintf(out
, "</tt></a></td> ");
2204 if(dobject
->length
>= 0) {
2205 if(dobject
->size
== dobject
->length
)
2206 fprintf(out
, "<td>%d</td> ", dobject
->length
);
2208 fprintf(out
, "<td>%d/%d</td> ",
2209 dobject
->size
, dobject
->length
);
2211 /* Avoid a trigraph. */
2212 fprintf(out
, "<td>%d/<em>??" "?</em></td> ", dobject
->size
);
2214 if(dobject
->last_modified
>= 0) {
2215 struct tm
*tm
= gmtime(&dobject
->last_modified
);
2219 n
= strftime(buf
, 1024, "%d.%m.%Y", tm
);
2224 fprintf(out
, "<td>%s</td> ", buf
);
2226 fprintf(out
, "<td></td>");
2229 if(dobject
->date
>= 0) {
2230 struct tm
*tm
= gmtime(&dobject
->date
);
2234 n
= strftime(buf
, 1024, "%d.%m.%Y", tm
);
2239 fprintf(out
, "<td>%s</td>", buf
);
2241 fprintf(out
, "<td></td>");
2244 fprintf(out
, "<td><tt>");
2245 htmlPrint(out
, dobject
->location
,
2246 strlen(dobject
->location
));
2247 fprintf(out
, "</tt></td><td></td><td></td><td></td>");
2250 fprintf(out
, "<td><a href=\"/polipo/index?%s\">plain</a></td>"
2251 "<td><a href=\"/polipo/recursive-index?%s\">"
2252 "recursive</a></td>",
2253 dobject
->location
, dobject
->location
);
2255 fprintf(out
, "</tr>\n");
2257 dobjects
= dobject
->next
;
2258 free(dobject
->location
);
2259 free(dobject
->filename
);
2262 fprintf(out
, "</tbody>\n");
2263 fprintf(out
, "</table>\n");
2267 fprintf(out
, "<p><a href=\"/polipo/\">back</a></p>\n");
2268 fprintf(out
, "</body></html>\n");
2273 checkForZeroes(char *buf
, int n
)
2276 unsigned long *lbuf
= (unsigned long *)buf
;
2277 assert(n
% sizeof(unsigned long) == 0);
2279 for(i
= 0; i
* sizeof(unsigned long) < n
; i
++) {
2281 return i
* sizeof(unsigned long);
2283 for(j
= 0; i
* sizeof(unsigned long) + j
< n
; j
++) {
2284 if(buf
[i
* sizeof(unsigned long) + j
] != 0)
2288 return i
* sizeof(unsigned long) + j
;
2292 copyFile(int from
, char *filename
, int n
)
2295 int to
, offset
, nread
, nzeroes
, rc
;
2297 buf
= malloc(CHUNK_SIZE
);
2301 to
= open(filename
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
,
2302 diskCacheFilePermissions
);
2310 nread
= read(from
, buf
, MIN(CHUNK_SIZE
, n
- offset
));
2313 nzeroes
= checkForZeroes(buf
, nread
& -8);
2316 rc
= lseek(to
, nzeroes
, SEEK_CUR
);
2317 if(rc
!= offset
+ nzeroes
) {
2319 do_log_error(L_ERROR
, errno
, "Couldn't extend file");
2322 "Couldn't extend file: "
2323 "unexpected offset %d != %d + %d.\n",
2328 if(nread
> nzeroes
) {
2329 rc
= write(to
, buf
+ nzeroes
, nread
- nzeroes
);
2330 if(rc
!= nread
- nzeroes
) {
2332 do_log_error(L_ERROR
, errno
, "Couldn't write");
2334 do_log(L_ERROR
, "Short write.\n");
2343 unlink(filename
); /* something went wrong straight away */
2348 expireFile(char *filename
, struct stat
*sb
,
2349 int *considered
, int *unlinked
, int *truncated
)
2351 DiskObjectPtr dobject
= NULL
;
2354 long int ret
= sb
->st_size
;
2356 if(!preciseExpiry
) {
2358 if(t
> current_time
.tv_sec
+ 1) {
2359 do_log(L_WARN
, "File %s has access time in the future.\n",
2361 t
= current_time
.tv_sec
;
2364 if(t
> current_time
.tv_sec
- diskCacheUnlinkTime
&&
2365 (sb
->st_size
< diskCacheTruncateSize
||
2366 t
> current_time
.tv_sec
- diskCacheTruncateTime
))
2372 dobject
= readDiskObject(filename
, sb
);
2374 do_log(L_ERROR
, "Incorrect disk entry %s -- removing.\n", filename
);
2375 rc
= unlink(filename
);
2377 do_log_error(L_ERROR
, errno
,
2378 "Couldn't unlink %s", filename
);
2386 t
= dobject
->access
;
2387 if(t
< 0) t
= dobject
->age
;
2388 if(t
< 0) t
= dobject
->date
;
2390 if(t
> current_time
.tv_sec
)
2392 "Disk entry %s (%s) has access time in the future.\n",
2393 dobject
->location
, dobject
->filename
);
2395 if(t
< current_time
.tv_sec
- diskCacheUnlinkTime
) {
2396 rc
= unlink(dobject
->filename
);
2398 do_log_error(L_ERROR
, errno
, "Couldn't unlink %s", filename
);
2403 } else if(dobject
->size
>
2404 diskCacheTruncateSize
+ 4 * dobject
->body_offset
&&
2405 t
< current_time
.tv_sec
- diskCacheTruncateTime
) {
2406 /* We need to copy rather than simply truncate in place: the
2407 latter would confuse a running polipo. */
2408 fd
= open(dobject
->filename
, O_RDONLY
| O_BINARY
, 0);
2409 rc
= unlink(dobject
->filename
);
2411 do_log_error(L_ERROR
, errno
, "Couldn't unlink %s", filename
);
2416 copyFile(fd
, dobject
->filename
,
2417 dobject
->body_offset
+ diskCacheTruncateSize
);
2421 ret
= sb
->st_size
- dobject
->body_offset
+ diskCacheTruncateSize
;
2424 free(dobject
->location
);
2425 free(dobject
->filename
);
2437 int files
= 0, considered
= 0, unlinked
= 0, truncated
= 0;
2438 int dirs
= 0, rmdirs
= 0;
2439 long left
= 0, total
= 0;
2441 if(diskCacheRoot
== NULL
||
2442 diskCacheRoot
->length
<= 0 || diskCacheRoot
->string
[0] != '/')
2445 fts_argv
[0] = diskCacheRoot
->string
;
2447 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
2449 do_log_error(L_ERROR
, errno
, "Couldn't fts_open disk cache");
2452 gettimeofday(¤t_time
, NULL
);
2457 if(fe
->fts_info
== FTS_D
)
2460 if(fe
->fts_info
== FTS_DP
|| fe
->fts_info
== FTS_DC
||
2461 fe
->fts_info
== FTS_DNR
) {
2462 if(fe
->fts_accpath
[0] == '/' &&
2463 strlen(fe
->fts_accpath
) <= diskCacheRoot
->length
)
2466 rc
= rmdir(fe
->fts_accpath
);
2469 else if(errno
!= ENOTEMPTY
&& errno
!= EEXIST
)
2470 do_log_error(L_ERROR
, errno
,
2471 "Couldn't remove directory %s",
2474 } else if(fe
->fts_info
== FTS_NS
) {
2475 do_log_error(L_ERROR
, fe
->fts_errno
, "Couldn't stat file %s",
2478 } else if(fe
->fts_info
== FTS_ERR
) {
2479 do_log_error(L_ERROR
, fe
->fts_errno
,
2480 "Couldn't fts_read disk cache");
2484 if(!S_ISREG(fe
->fts_statp
->st_mode
)) {
2485 do_log(L_ERROR
, "Unexpected file %s type 0%o.\n",
2486 fe
->fts_accpath
, (unsigned int)fe
->fts_statp
->st_mode
);
2491 left
+= expireFile(fe
->fts_accpath
, fe
->fts_statp
,
2492 &considered
, &unlinked
, &truncated
);
2493 total
+= fe
->fts_statp
->st_size
;
2498 printf("Disk cache purged.\n");
2499 printf("%d files, %d considered, %d removed, %d truncated "
2500 "(%ldkB -> %ldkB).\n",
2501 files
, considered
, unlinked
, truncated
, total
/1024, left
/1024);
2502 printf("%d directories, %d removed.\n", dirs
, rmdirs
);
2521 writeoutToDisk(ObjectPtr object
, int upto
, int max
)
2527 destroyDiskEntry(ObjectPtr object
, int d
)
2533 objectGetFromDisk(ObjectPtr object
)
2539 objectFillFromDisk(ObjectPtr object
, int offset
, int chunks
)
2545 revalidateDiskEntry(ObjectPtr object
)
2551 dirtyDiskEntry(ObjectPtr object
)
2559 do_log(L_ERROR
, "Disk cache not supported in this version.\n");
2563 diskEntrySize(ObjectPtr object
)