2 Copyright (c) 2003-2006 by Juliusz Chroboczek
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 #include "md5import.h"
29 int maxDiskEntries
= 32;
31 /* Because the functions in this file can be called during object
32 expiry, we cannot use get_chunk. */
34 AtomPtr diskCacheRoot
;
35 AtomPtr localDocumentRoot
;
37 DiskCacheEntryPtr diskEntries
= NULL
, diskEntriesLast
= NULL
;
38 int numDiskEntries
= 0;
39 int diskCacheDirectoryPermissions
= 0700;
40 int diskCacheFilePermissions
= 0600;
41 int diskCacheWriteoutOnClose
= (32 * 1024);
43 int maxDiskCacheEntrySize
= -1;
45 int diskCacheUnlinkTime
= 32 * 24 * 60 * 60;
46 int diskCacheTruncateTime
= 4 * 24 * 60 * 60 + 12 * 60 * 60;
47 int diskCacheTruncateSize
= 1024 * 1024;
48 int preciseExpiry
= 0;
50 static DiskCacheEntryRec negativeEntry
= {
52 -1, -1, -1, -1, 0, 0, 0, NULL
, NULL
56 #define LOCAL_ROOT "/usr/share/polipo/www/"
59 #ifndef DISK_CACHE_ROOT
60 #define DISK_CACHE_ROOT "/var/cache/polipo/"
63 static int maxDiskEntriesSetter(ConfigVariablePtr
, void*);
64 static int atomSetterFlush(ConfigVariablePtr
, void*);
65 static int reallyWriteoutToDisk(ObjectPtr object
, int upto
, int max
);
70 diskCacheRoot
= internAtom(DISK_CACHE_ROOT
);
71 localDocumentRoot
= internAtom(LOCAL_ROOT
);
73 CONFIG_VARIABLE_SETTABLE(diskCacheDirectoryPermissions
, CONFIG_OCTAL
,
75 "Access rights for new directories.");
76 CONFIG_VARIABLE_SETTABLE(diskCacheFilePermissions
, CONFIG_OCTAL
,
78 "Access rights for new cache files.");
79 CONFIG_VARIABLE_SETTABLE(diskCacheWriteoutOnClose
, CONFIG_INT
,
81 "Number of bytes to write out eagerly.");
82 CONFIG_VARIABLE_SETTABLE(diskCacheRoot
, CONFIG_ATOM
, atomSetterFlush
,
83 "Root of the disk cache.");
84 CONFIG_VARIABLE_SETTABLE(localDocumentRoot
, CONFIG_ATOM
, atomSetterFlush
,
85 "Root of the local tree.");
86 CONFIG_VARIABLE_SETTABLE(maxDiskEntries
, CONFIG_INT
, maxDiskEntriesSetter
,
87 "File descriptors used by the on-disk cache.");
88 CONFIG_VARIABLE(diskCacheUnlinkTime
, CONFIG_TIME
,
89 "Time after which on-disk objects are removed.");
90 CONFIG_VARIABLE(diskCacheTruncateTime
, CONFIG_TIME
,
91 "Time after which on-disk objects are truncated.");
92 CONFIG_VARIABLE(diskCacheTruncateSize
, CONFIG_INT
,
93 "Size to which on-disk objects are truncated.");
94 CONFIG_VARIABLE(preciseExpiry
, CONFIG_BOOLEAN
,
95 "Whether to consider all files for purging.");
96 CONFIG_VARIABLE_SETTABLE(maxDiskCacheEntrySize
, CONFIG_INT
,
98 "Maximum size of objects cached on disk.");
102 maxDiskEntriesSetter(ConfigVariablePtr var
, void *value
)
105 assert(var
->type
== CONFIG_INT
&& var
->value
.i
== &maxDiskEntries
);
107 if(i
< 0 || i
> 1000000)
110 while(numDiskEntries
> maxDiskEntries
)
111 destroyDiskEntry(diskEntriesLast
->object
, 0);
116 atomSetterFlush(ConfigVariablePtr var
, void *value
)
118 discardObjects(1, 0);
119 return configAtomSetter(var
, value
);
123 checkRoot(AtomPtr root
)
128 if(!root
|| root
->length
== 0)
131 if(root
->string
[0] != '/') {
135 rc
= stat(root
->string
, &ss
);
138 else if(!S_ISDIR(ss
.st_mode
)) {
146 maybeAddSlash(AtomPtr atom
)
148 AtomPtr newAtom
= NULL
;
149 if(!atom
) return NULL
;
150 if(atom
->length
> 0 && atom
->string
[atom
->length
- 1] != '/') {
151 newAtom
= atomCat(atom
, "/");
163 diskCacheRoot
= expandTilde(maybeAddSlash(diskCacheRoot
));
164 rc
= checkRoot(diskCacheRoot
);
168 case -1: do_log_error(L_WARN
, errno
, "Disabling disk cache"); break;
170 do_log(L_WARN
, "Disabling disk cache: path %s is not absolute.\n",
171 diskCacheRoot
->string
);
175 releaseAtom(diskCacheRoot
);
176 diskCacheRoot
= NULL
;
179 localDocumentRoot
= expandTilde(maybeAddSlash(localDocumentRoot
));
180 rc
= checkRoot(localDocumentRoot
);
184 case -1: do_log_error(L_WARN
, errno
, "Disabling local tree"); break;
186 do_log(L_WARN
, "Disabling local tree: path is not absolute.\n");
190 releaseAtom(localDocumentRoot
);
191 localDocumentRoot
= NULL
;
195 #ifdef DEBUG_DISK_CACHE
196 #define CHECK_ENTRY(entry) check_entry((entry))
198 check_entry(DiskCacheEntryPtr entry
)
200 if(entry
&& entry
->fd
< 0)
201 assert(entry
== &negativeEntry
);
202 if(entry
&& entry
->fd
>= 0) {
203 assert((!entry
->previous
) == (entry
== diskEntries
));
204 assert((!entry
->next
) == (entry
== diskEntriesLast
));
206 assert(entry
->size
+ entry
->body_offset
>= entry
->offset
);
207 assert(entry
->body_offset
>= 0);
208 if(entry
->offset
>= 0) {
210 offset
= lseek(entry
->fd
, 0, SEEK_CUR
);
211 assert(offset
== entry
->offset
);
213 if(entry
->size
>= 0) {
216 rc
= fstat(entry
->fd
, &ss
);
218 assert(ss
.st_size
== entry
->size
+ entry
->body_offset
);
223 #define CHECK_ENTRY(entry) do {} while(0)
227 diskEntrySize(ObjectPtr object
)
231 DiskCacheEntryPtr entry
= object
->disk_entry
;
233 if(!entry
|| entry
== &negativeEntry
)
239 rc
= fstat(entry
->fd
, &buf
);
241 do_log_error(L_ERROR
, errno
, "Couldn't stat");
245 if(buf
.st_size
<= entry
->body_offset
)
248 entry
->size
= buf
.st_size
- entry
->body_offset
;
250 if(object
->length
>= 0 && entry
->size
== object
->length
)
251 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
256 entrySeek(DiskCacheEntryPtr entry
, off_t offset
)
261 assert(entry
!= &negativeEntry
);
262 if(entry
->offset
== offset
)
264 if(offset
> entry
->body_offset
) {
265 /* Avoid extending the file by mistake */
267 diskEntrySize(entry
->object
);
270 if(entry
->size
+ entry
->body_offset
< offset
)
273 rc
= lseek(entry
->fd
, offset
, SEEK_SET
);
275 do_log_error(L_ERROR
, errno
, "Couldn't seek");
279 entry
->offset
= offset
;
283 /* Given a local URL, constructs the filename where it can be found. */
286 localFilename(char *buf
, int n
, char *key
, int len
)
289 if(len
<= 0 || key
[0] != '/') return -1;
291 if(urlIsSpecial(key
, len
)) return -1;
293 if(localDocumentRoot
== NULL
||
294 localDocumentRoot
->length
<= 0 || localDocumentRoot
->string
[0] != '/')
297 if(n
<= localDocumentRoot
->length
)
304 memcpy(buf
, localDocumentRoot
->string
, localDocumentRoot
->length
);
305 j
= localDocumentRoot
->length
;
306 if(buf
[j
- 1] == '/')
312 if(key
[i
] == '/' && i
< len
- 2)
313 if(key
[i
+ 1] == '.' &&
314 (key
[i
+ 2] == '.' || key
[i
+ 2] == '/'))
319 if(buf
[j
- 1] == '/') {
322 memcpy(buf
+ j
, "index.html", 10);
331 md5(unsigned char *restrict key
, int len
, unsigned char *restrict dst
)
335 MD5Update(&ctx
, key
, len
);
337 memcpy(dst
, ctx
.digest
, 16);
340 /* Check whether a character can be stored in a filename. This is
341 needed since we want to support deficient file systems. */
345 if(c
<= 31 || c
>= 127)
347 if((c
>= 'a' && c
<= 'z') || (c
>= 'A' && c
<= 'Z') ||
348 (c
>= '0' && c
<= '9') || c
== '.' || c
== '-' || c
== '_')
353 /* Given a URL, returns the directory name within which all files
354 starting with this URL can be found. */
356 urlDirname(char *buf
, int n
, const char *url
, int len
)
361 if(memcmp(url
, "http://", 7) != 0)
364 if(diskCacheRoot
== NULL
||
365 diskCacheRoot
->length
<= 0 || diskCacheRoot
->string
[0] != '/')
368 if(n
<= diskCacheRoot
->length
)
371 memcpy(buf
, diskCacheRoot
->string
, diskCacheRoot
->length
);
372 j
= diskCacheRoot
->length
;
374 if(buf
[j
- 1] != '/')
377 for(i
= 7; i
< len
; i
++) {
378 if(i
>= len
|| url
[i
] == '/')
380 if(url
[i
] == '.' && i
!= len
- 1 && url
[i
+ 1] == '.')
382 if(url
[i
] == '%' || !fssafe(url
[i
])) {
383 if(j
>= n
+ 3) return -1;
385 buf
[j
++] = i2h((url
[i
] & 0xF0) >> 4);
386 buf
[j
++] = i2h(url
[i
] & 0x0F);
388 buf
[j
++] = url
[i
]; if(j
>= n
) return -1;
391 buf
[j
++] = '/'; if(j
>= n
) return -1;
396 /* Given a URL, returns the filename where the cached data can be
399 urlFilename(char *restrict buf
, int n
, const char *url
, int len
)
402 unsigned char md5buf
[18];
403 j
= urlDirname(buf
, n
, url
, len
);
404 if(j
< 0 || j
+ 24 >= n
)
406 md5((unsigned char*)url
, len
, md5buf
);
407 b64cpy(buf
+ j
, (char*)md5buf
, 16, 1);
413 dirnameUrl(char *url
, int n
, char *name
, int len
)
416 k
= diskCacheRoot
->length
;
419 if(memcmp(name
, diskCacheRoot
->string
, k
) != 0)
423 memcpy(url
, "http://", 7);
424 if(name
[len
- 1] == '/')
427 for(i
= k
; i
< len
; i
++) {
431 c1
= h2i(name
[i
+ 1]);
432 c2
= h2i(name
[i
+ 2]);
435 url
[j
++] = c1
* 16 + c2
; if(j
>= n
) goto fail
;
436 } else if(i
< len
- 1 &&
437 name
[i
] == '.' && name
[i
+ 1] == '/') {
439 } else if(i
== len
- 1 && name
[i
] == '.') {
442 url
[j
++] = name
[i
]; if(j
>= n
) goto fail
;
445 url
[j
++] = '/'; if(j
>= n
) goto fail
;
453 /* Create a file and all intermediate directories. */
455 createFile(const char *name
, int path_start
)
462 if(name
[path_start
] == '/')
465 if(path_start
< 2 || name
[path_start
- 1] != '/' ) {
466 do_log(L_ERROR
, "Incorrect name %s (%d).\n", name
, path_start
);
470 fd
= open(name
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
,
471 diskCacheFilePermissions
);
474 if(errno
!= ENOENT
) {
475 do_log_error(L_ERROR
, errno
, "Couldn't create disk file %s", name
);
480 while(name
[n
] != '\0' && n
< 1024) {
481 while(name
[n
] != '/' && name
[n
] != '\0' && n
< 512)
483 if(name
[n
] != '/' || n
>= 1024)
485 memcpy(buf
, name
, n
+ 1);
487 rc
= mkdir(buf
, diskCacheDirectoryPermissions
);
488 if(rc
< 0 && errno
!= EEXIST
) {
489 do_log_error(L_ERROR
, errno
, "Couldn't create directory %s", buf
);
494 fd
= open(name
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
,
495 diskCacheFilePermissions
);
497 do_log_error(L_ERROR
, errno
, "Couldn't create file %s", name
);
505 chooseBodyOffset(int n
, ObjectPtr object
)
507 int length
= MAX(object
->size
, object
->length
);
510 if(object
->length
>= 0 && object
->length
+ n
< 4096 - 50)
511 return -1; /* no gap for small objects */
527 else body_offset
= ((n
+ 4095) / 4096 + 1) * 4096;
529 /* Tweak the gap so that we don't use up a full disk block for
531 if(object
->length
>= 0 && object
->length
< 64 * 1024) {
532 int last
= (body_offset
+ object
->length
) % 4096;
533 if(last
< body_offset
/ 4 && n
< body_offset
- last
- 50)
537 /* Rewriting large objects is expensive -- don't use small gaps */
538 if(length
>= 64 * 1024)
539 body_offset
= MAX(body_offset
, 1024);
540 if(length
>= 256 * 1024)
541 body_offset
= MAX(body_offset
, 2048);
542 if(length
>= 512 * 1024)
543 body_offset
= MAX(body_offset
, 4096);
547 /* Assumes the file descriptor is at offset 0. Returns -1 on failure,
548 -2 on overflow, otherwise the offset at which the file descriptor is
551 writeHeaders(int fd
, int *body_offset_return
,
552 ObjectPtr object
, char *chunk
, int chunk_len
)
556 int body_offset
= *body_offset_return
;
561 if(object
->flags
& OBJECT_LOCAL
)
564 /* get_chunk might trigger object expiry */
565 bufsize
= CHUNK_SIZE
;
567 buf
= maybe_get_chunk();
573 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
579 n
= snnprintf(buf
, 0, bufsize
, "HTTP/1.1 %3d %s",
580 object
->code
, object
->message
->string
);
582 n
= httpWriteObjectHeaders(buf
, n
, bufsize
, object
, 0, -1);
586 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Location: ");
587 n
= snnprint_n(buf
, n
, bufsize
, object
->key
, object
->key_size
);
589 if(object
->age
>= 0 && object
->age
!= object
->date
) {
590 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Date: ");
591 n
= format_time(buf
, n
, bufsize
, object
->age
);
594 if(object
->atime
>= 0) {
595 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Access: ");
596 n
= format_time(buf
, n
, bufsize
, object
->atime
);
603 body_offset
= chooseBodyOffset(n
, object
);
605 if(body_offset
> bufsize
)
608 if(body_offset
> 0 && body_offset
!= n
+ 4)
609 n
= snnprintf(buf
, n
, bufsize
, "\r\nX-Polipo-Body-Offset: %d",
612 n
= snnprintf(buf
, n
, bufsize
, "\r\n\r\n");
620 memset(buf
+ n
, 0, body_offset
- n
);
623 #ifdef HAVE_READV_WRITEV
626 iov
[0].iov_base
= buf
;
627 iov
[0].iov_len
= body_offset
;
628 iov
[1].iov_base
= chunk
;
629 iov
[1].iov_len
= chunk_len
;
630 rc
= writev(fd
, iov
, 2);
633 rc
= write(fd
, buf
, body_offset
);
635 if(rc
< 0 && errno
== EINTR
)
640 if(object
->length
>= 0 &&
641 rc
- body_offset
>= object
->length
)
642 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
644 *body_offset_return
= body_offset
;
652 if(bufsize
< bigBufferSize
) {
654 buf
= malloc(bigBufferSize
);
656 do_log(L_ERROR
, "Couldn't allocate big buffer.\n");
659 bufsize
= bigBufferSize
;
661 dispose_chunk(oldbuf
);
684 typedef struct _MimeEntry
{
689 static const MimeEntryRec mimeEntries
[] = {
690 { "html", "text/html" },
691 { "htm", "text/html" },
692 { "text", "text/plain" },
693 { "txt", "text/plain" },
694 { "png", "image/png" },
695 { "gif", "image/gif" },
696 { "jpeg", "image/jpeg" },
697 { "jpg", "image/jpeg" },
698 { "ico", "image/x-icon" },
699 { "pdf", "application/pdf" },
700 { "ps", "application/postscript" },
701 { "tar", "application/x-tar" },
702 { "pac", "application/x-ns-proxy-autoconfig" },
703 { "css", "text/css" },
704 { "js", "application/x-javascript" },
705 { "xml", "text/xml" },
706 { "swf", "application/x-shockwave-flash" },
710 localObjectMimeType(ObjectPtr object
, char **encoding_return
)
712 char *name
= object
->key
;
713 int nlen
= object
->key_size
;
718 if(name
[nlen
- 1] == '/') {
719 *encoding_return
= NULL
;
724 *encoding_return
= NULL
;
725 return "application/octet-stream";
728 if(memcmp(name
+ nlen
- 3, ".gz", 3) == 0) {
729 *encoding_return
= "x-gzip";
731 } else if(memcmp(name
+ nlen
- 2, ".Z", 2) == 0) {
732 *encoding_return
= "x-compress";
735 *encoding_return
= NULL
;
738 for(i
= 0; i
< sizeof(mimeEntries
) / sizeof(mimeEntries
[0]); i
++) {
739 int len
= strlen(mimeEntries
[i
].extension
);
741 name
[nlen
- len
- 1] == '.' &&
742 memcmp(name
+ nlen
- len
, mimeEntries
[i
].extension
, len
) == 0)
743 return mimeEntries
[i
].mime
;
746 return "application/octet-stream";
749 /* Same interface as validateEntry -- see below */
751 validateLocalEntry(ObjectPtr object
, int fd
,
752 int *body_offset_return
, off_t
*offset_return
)
761 do_log_error(L_ERROR
, errno
, "Couldn't stat");
765 if(S_ISREG(ss
.st_mode
)) {
766 if(!(ss
.st_mode
& S_IROTH
) ||
767 (object
->length
>= 0 && object
->length
!= ss
.st_size
) ||
768 (object
->last_modified
>= 0 &&
769 object
->last_modified
!= ss
.st_mtime
))
772 notifyObject(object
);
776 n
= snnprintf(buf
, 0, 512, "%lx-%lx-%lx",
777 (unsigned long)ss
.st_ino
,
778 (unsigned long)ss
.st_size
,
779 (unsigned long)ss
.st_mtime
);
783 if(n
> 0 && object
->etag
) {
784 if(strlen(object
->etag
) != n
||
785 memcmp(object
->etag
, buf
, n
) != 0)
789 if(!(object
->flags
& OBJECT_INITIAL
)) {
790 if(!object
->last_modified
&& !object
->etag
)
794 if(object
->flags
& OBJECT_INITIAL
) {
795 object
->length
= ss
.st_size
;
796 object
->last_modified
= ss
.st_mtime
;
797 object
->date
= current_time
.tv_sec
;
798 object
->age
= current_time
.tv_sec
;
801 object
->etag
= strdup(buf
); /* okay if fails */
802 object
->message
= internAtom("Okay");
803 n
= snnprintf(buf
, 0, 512,
805 "\r\nContent-Type: %s",
806 localObjectMimeType(object
, &encoding
));
808 n
= snnprintf(buf
, n
, 512,
809 "\r\nContent-Encoding: %s", encoding
);
812 object
->headers
= internAtomN(buf
, n
);
813 if(object
->headers
== NULL
)
815 object
->flags
&= ~OBJECT_INITIAL
;
818 if(body_offset_return
)
819 *body_offset_return
= 0;
825 /* Assumes fd is at offset 0.
826 Returns -1 if not valid, 1 if metadata should be written out, 0
829 validateEntry(ObjectPtr object
, int fd
,
830 int *body_offset_return
, off_t
*offset_return
)
833 int buf_is_chunk
, bufsize
;
838 time_t date
, last_modified
, expires
, polipo_age
, polipo_access
;
844 CacheControlRec cache_control
;
849 if(object
->flags
& OBJECT_LOCAL
)
850 return validateLocalEntry(object
, fd
,
851 body_offset_return
, offset_return
);
853 if(!(object
->flags
& OBJECT_PUBLIC
) && (object
->flags
& OBJECT_INITIAL
))
856 /* get_chunk might trigger object expiry */
857 bufsize
= CHUNK_SIZE
;
859 buf
= maybe_get_chunk();
865 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
871 rc
= read(fd
, buf
, bufsize
);
875 do_log_error(L_ERROR
, errno
, "Couldn't read disk entry");
881 n
= findEndOfHeaders(buf
, 0, rc
, &dummy
);
884 if(bufsize
< bigBufferSize
) {
885 buf
= malloc(bigBufferSize
);
887 do_log(L_ERROR
, "Couldn't allocate big buffer.\n");
890 bufsize
= bigBufferSize
;
891 memcpy(buf
, oldbuf
, offset
);
893 dispose_chunk(oldbuf
);
898 rc
= read(fd
, buf
+ offset
, bufsize
- offset
);
902 do_log_error(L_ERROR
, errno
, "Couldn't read disk entry");
908 do_log(L_ERROR
, "Couldn't parse disk entry.\n");
912 rc
= httpParseServerFirstLine(buf
, &code
, &dummy
, &message
);
914 do_log(L_ERROR
, "Couldn't parse disk entry.\n");
918 if(object
->code
!= 0 && object
->code
!= code
) {
919 releaseAtom(message
);
923 rc
= httpParseHeaders(0, NULL
, buf
, rc
, NULL
,
924 &headers
, &length
, &cache_control
, NULL
, NULL
,
925 &date
, &last_modified
, &expires
, &polipo_age
,
926 &polipo_access
, &body_offset
,
928 NULL
, NULL
, &location
, &via
, NULL
);
930 releaseAtom(message
);
936 if(!location
|| strlen(location
) != object
->key_size
||
937 memcmp(location
, object
->key
, object
->key_size
) != 0) {
938 do_log(L_ERROR
, "Inconsistent cache file for %s.\n", location
);
946 do_log(L_ERROR
, "Undated disk entry for %s.\n", location
);
950 if(!(object
->flags
& OBJECT_INITIAL
)) {
951 if((last_modified
>=0) != (object
->last_modified
>= 0))
954 if(last_modified
>= 0 && object
->last_modified
>= 0 &&
955 last_modified
!= object
->last_modified
)
958 if(length
>= 0 && object
->length
>= 0)
959 if(length
!= object
->length
)
962 if(!!etag
!= !!object
->etag
)
965 if(etag
&& object
->etag
&& strcmp(etag
, object
->etag
) != 0)
968 /* If we don't have a usable ETag, and either CACHE_VARY or we
969 don't have a last-modified date, we validate disk entries by
971 if(!(etag
&& object
->etag
) &&
972 (!(last_modified
>= 0 && object
->last_modified
>= 0) ||
973 ((cache_control
.flags
& CACHE_VARY
) ||
974 (object
->cache_control
& CACHE_VARY
)))) {
975 if(date
>= 0 && date
!= object
->date
)
977 if(polipo_age
>= 0 && polipo_age
!= object
->age
)
987 object
->headers
= headers
;
989 releaseAtom(headers
);
992 if(object
->code
== 0) {
994 object
->message
= retainAtom(message
);
996 if(object
->date
<= date
)
1000 if(object
->last_modified
< 0)
1001 object
->last_modified
= last_modified
;
1002 if(object
->expires
< 0)
1003 object
->expires
= expires
;
1004 else if(object
->expires
> expires
)
1007 object
->age
= polipo_age
;
1008 else if(object
->age
> polipo_age
)
1010 if(object
->atime
<= polipo_access
)
1011 object
->atime
= polipo_access
;
1015 object
->cache_control
|= cache_control
.flags
;
1017 if(object
->age
< 0) object
->age
= object
->date
;
1018 if(object
->age
< 0) object
->age
= 0; /* a long time ago */
1019 if(object
->length
< 0) object
->length
= length
;
1021 object
->etag
= etag
;
1026 releaseAtom(message
);
1028 if(object
->flags
& OBJECT_INITIAL
) object
->via
= via
;
1029 object
->flags
&= ~OBJECT_INITIAL
;
1030 if(offset
> body_offset
) {
1031 /* We need to make sure we don't invoke object expiry recursively */
1032 objectSetChunks(object
, 1);
1033 if(object
->numchunks
>= 1) {
1034 if(object
->chunks
[0].data
== NULL
)
1035 object
->chunks
[0].data
= maybe_get_chunk();
1036 if(object
->chunks
[0].data
)
1037 objectAddData(object
, buf
+ body_offset
,
1038 0, MIN(offset
- body_offset
, CHUNK_SIZE
));
1046 if(body_offset_return
) *body_offset_return
= body_offset
;
1047 if(offset_return
) *offset_return
= offset
;
1051 releaseAtom(message
);
1052 if(etag
) free(etag
);
1053 if(location
) free(location
);
1054 if(via
) releaseAtom(via
);
1066 dirtyDiskEntry(ObjectPtr object
)
1068 DiskCacheEntryPtr entry
= object
->disk_entry
;
1069 if(entry
&& entry
!= &negativeEntry
) entry
->metadataDirty
= 1;
1073 revalidateDiskEntry(ObjectPtr object
)
1075 DiskCacheEntryPtr entry
= object
->disk_entry
;
1079 if(!entry
|| entry
== &negativeEntry
)
1083 rc
= entrySeek(entry
, 0);
1084 if(rc
< 0) return 0;
1086 rc
= validateEntry(object
, entry
->fd
, &body_offset
, &entry
->offset
);
1088 destroyDiskEntry(object
, 0);
1091 if(body_offset
!= entry
->body_offset
) {
1092 do_log(L_WARN
, "Inconsistent body offset (%d != %d).\n",
1093 body_offset
, entry
->body_offset
);
1094 destroyDiskEntry(object
, 0);
1098 entry
->metadataDirty
|= !!rc
;
1104 objectHasDiskEntry(ObjectPtr object
)
1106 return object
->disk_entry
&& object
->disk_entry
!= &negativeEntry
;
1109 static DiskCacheEntryPtr
1110 makeDiskEntry(ObjectPtr object
, int writeable
, int create
)
1112 DiskCacheEntryPtr entry
= NULL
;
1115 int negative
= 0, isWriteable
= 0, size
= -1, name_len
= -1;
1118 int body_offset
= -1;
1120 int local
= (object
->flags
& OBJECT_LOCAL
) != 0;
1123 if(local
&& (writeable
|| create
))
1126 if(!local
&& !(object
->flags
& OBJECT_PUBLIC
))
1129 if(maxDiskCacheEntrySize
>= 0) {
1130 if(object
->length
> 0) {
1131 if(object
->length
> maxDiskCacheEntrySize
)
1134 if(object
->size
> maxDiskCacheEntrySize
)
1139 if(object
->disk_entry
) {
1140 entry
= object
->disk_entry
;
1142 if(entry
!= &negativeEntry
&& (!writeable
|| entry
->writeable
)) {
1143 /* We'll keep the entry -- put it at the front. */
1144 if(entry
!= diskEntries
&& entry
!= &negativeEntry
) {
1145 entry
->previous
->next
= entry
->next
;
1147 entry
->next
->previous
= entry
->previous
;
1149 diskEntriesLast
= entry
->previous
;
1150 entry
->next
= diskEntries
;
1151 diskEntries
->previous
= entry
;
1152 entry
->previous
= NULL
;
1153 diskEntries
= entry
;
1157 if(entry
== &negativeEntry
) {
1159 if(!create
) return NULL
;
1160 object
->disk_entry
= NULL
;
1163 destroyDiskEntry(object
, 0);
1167 if(numDiskEntries
> maxDiskEntries
)
1168 destroyDiskEntry(diskEntriesLast
->object
, 0);
1171 if(diskCacheRoot
== NULL
|| diskCacheRoot
->length
<= 0)
1173 name_len
= urlFilename(buf
, 1024, object
->key
, object
->key_size
);
1174 if(name_len
< 0) return NULL
;
1177 fd
= open(buf
, O_RDWR
| O_BINARY
);
1178 if(fd
< 0 && !writeable
&& errno
== EACCES
) {
1180 fd
= open(buf
, O_RDONLY
| O_BINARY
);
1184 rc
= validateEntry(object
, fd
, &body_offset
, &offset
);
1191 if(rc
< 0 && errno
!= ENOENT
) {
1192 do_log_error(L_WARN
, errno
,
1193 "Couldn't unlink stale disk entry %s",
1195 /* But continue -- it's okay to have stale entries. */
1200 if(fd
< 0 && create
&& name_len
> 0 &&
1201 !(object
->flags
& OBJECT_INITIAL
)) {
1203 fd
= createFile(buf
, diskCacheRoot
->length
);
1210 if(object
->numchunks
> 0) {
1211 data
= object
->chunks
[0].data
;
1212 dsize
= object
->chunks
[0].size
;
1214 rc
= writeHeaders(fd
, &body_offset
, object
, data
, dsize
);
1216 do_log_error(L_ERROR
, errno
, "Couldn't write headers");
1218 if(rc
< 0 && errno
!= ENOENT
)
1219 do_log_error(L_ERROR
, errno
,
1220 "Couldn't unlink truncated entry %s",
1225 assert(rc
>= body_offset
);
1226 size
= rc
- body_offset
;
1233 if(localDocumentRoot
== NULL
|| localDocumentRoot
->length
== 0)
1237 localFilename(buf
, 1024, object
->key
, object
->key_size
);
1241 fd
= open(buf
, O_RDONLY
| O_BINARY
);
1243 if(validateEntry(object
, fd
, &body_offset
, NULL
) < 0) {
1252 object
->disk_entry
= &negativeEntry
;
1255 assert(body_offset
>= 0);
1257 name
= strdup_n(buf
, name_len
);
1259 do_log(L_ERROR
, "Couldn't allocate name.\n");
1265 entry
= malloc(sizeof(DiskCacheEntryRec
));
1267 do_log(L_ERROR
, "Couldn't allocate entry.\n");
1273 entry
->filename
= name
;
1274 entry
->object
= object
;
1276 entry
->body_offset
= body_offset
;
1277 entry
->local
= local
;
1278 entry
->offset
= offset
;
1280 entry
->metadataDirty
= dirty
;
1281 entry
->writeable
= isWriteable
;
1283 entry
->next
= diskEntries
;
1285 diskEntries
->previous
= entry
;
1286 diskEntries
= entry
;
1287 if(diskEntriesLast
== NULL
)
1288 diskEntriesLast
= entry
;
1289 entry
->previous
= NULL
;
1292 object
->disk_entry
= entry
;
1298 /* Rewrite a disk cache entry, used when the body offset needs to change. */
1300 rewriteEntry(ObjectPtr object
)
1302 int old_body_offset
= object
->disk_entry
->body_offset
;
1304 DiskCacheEntryPtr entry
;
1306 int buf_is_chunk
, bufsize
;
1309 fd
= dup(object
->disk_entry
->fd
);
1311 do_log_error(L_ERROR
, errno
, "Couldn't duplicate file descriptor");
1315 rc
= destroyDiskEntry(object
, 1);
1320 entry
= makeDiskEntry(object
, 1, 1);
1326 offset
= diskEntrySize(object
);
1332 bufsize
= CHUNK_SIZE
;
1334 buf
= maybe_get_chunk();
1340 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
1346 rc
= lseek(fd
, old_body_offset
+ offset
, SEEK_SET
);
1352 n
= read(fd
, buf
, bufsize
);
1355 rc
= entrySeek(entry
, entry
->body_offset
+ offset
);
1358 rc
= write(entry
->fd
, buf
, n
);
1360 entry
->offset
+= rc
;
1369 if(object
->length
>= 0 && entry
->size
== object
->length
)
1370 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
1380 destroyDiskEntry(ObjectPtr object
, int d
)
1382 DiskCacheEntryPtr entry
= object
->disk_entry
;
1385 assert(!entry
|| !entry
->local
|| !d
);
1388 entry
= makeDiskEntry(object
, 1, 0);
1392 if(!entry
|| entry
== &negativeEntry
) {
1396 assert(entry
->object
== object
);
1398 if(maxDiskCacheEntrySize
>= 0 && object
->size
> maxDiskCacheEntrySize
) {
1399 /* See writeoutToDisk */
1404 entry
->object
->flags
&= ~OBJECT_DISK_ENTRY_COMPLETE
;
1405 if(entry
->filename
) {
1406 urc
= unlink(entry
->filename
);
1408 do_log_error(L_WARN
, errno
,
1409 "Couldn't unlink %s", entry
->filename
);
1412 if(entry
&& entry
->metadataDirty
)
1413 writeoutMetadata(object
);
1414 makeDiskEntry(object
, 1, 0);
1415 /* rewriteDiskEntry may change the disk entry */
1416 entry
= object
->disk_entry
;
1417 if(entry
== NULL
|| entry
== &negativeEntry
)
1419 if(entry
->writeable
&& diskCacheWriteoutOnClose
> 0)
1420 reallyWriteoutToDisk(object
, -1, diskCacheWriteoutOnClose
);
1423 rc
= close(entry
->fd
);
1424 if(rc
< 0 && errno
== EINTR
)
1430 free(entry
->filename
);
1431 entry
->filename
= NULL
;
1434 entry
->previous
->next
= entry
->next
;
1436 diskEntries
= entry
->next
;
1438 entry
->next
->previous
= entry
->previous
;
1440 diskEntriesLast
= entry
->previous
;
1443 assert(numDiskEntries
>= 0);
1446 object
->disk_entry
= NULL
;
1454 objectGetFromDisk(ObjectPtr object
)
1456 DiskCacheEntryPtr entry
= makeDiskEntry(object
, 0, 0);
1457 if(!entry
) return NULL
;
1463 objectFillFromDisk(ObjectPtr object
, int offset
, int chunks
)
1465 DiskCacheEntryPtr entry
;
1470 if(object
->type
!= OBJECT_HTTP
)
1473 if(object
->flags
& OBJECT_LINEAR
)
1476 if(object
->length
>= 0) {
1477 chunks
= MIN(chunks
,
1478 (object
->length
- offset
+ CHUNK_SIZE
- 1) / CHUNK_SIZE
);
1481 rc
= objectSetChunks(object
, offset
/ CHUNK_SIZE
+ chunks
);
1486 if(object
->flags
& OBJECT_INITIAL
) {
1488 } else if((object
->length
< 0 || object
->size
< object
->length
) &&
1489 object
->size
< (offset
/ CHUNK_SIZE
+ chunks
) * CHUNK_SIZE
) {
1492 for(k
= 0; k
< chunks
; k
++) {
1494 i
= offset
/ CHUNK_SIZE
+ k
;
1495 s
= MIN(CHUNK_SIZE
, object
->size
- i
* CHUNK_SIZE
);
1496 if(object
->chunks
[i
].size
< s
) {
1506 /* This has the side-effect of revalidating the entry, which is
1507 what makes HEAD requests work. */
1508 entry
= makeDiskEntry(object
, 0, 0);
1512 for(k
= 0; k
< chunks
; k
++) {
1513 i
= offset
/ CHUNK_SIZE
+ k
;
1514 if(!object
->chunks
[i
].data
)
1515 object
->chunks
[i
].data
= get_chunk();
1516 if(!object
->chunks
[i
].data
) {
1520 lockChunk(object
, i
);
1525 for(k
= 0; k
< chunks
; k
++) {
1527 i
= offset
/ CHUNK_SIZE
+ k
;
1528 j
= object
->chunks
[i
].size
;
1529 o
= i
* CHUNK_SIZE
+ j
;
1531 if(object
->chunks
[i
].size
== CHUNK_SIZE
)
1534 if(entry
->size
>= 0 && entry
->size
<= o
)
1537 if(entry
->offset
!= entry
->body_offset
+ o
) {
1538 rc
= entrySeek(entry
, entry
->body_offset
+ o
);
1547 rc
= read(entry
->fd
, object
->chunks
[i
].data
+ j
, CHUNK_SIZE
- j
);
1552 do_log_error(L_ERROR
, errno
, "Couldn't read");
1556 entry
->offset
+= rc
;
1557 object
->chunks
[i
].size
+= rc
;
1558 if(object
->size
< o
+ rc
)
1559 object
->size
= o
+ rc
;
1561 if(entry
->object
->length
>= 0 && entry
->size
< 0 &&
1562 entry
->offset
- entry
->body_offset
== entry
->object
->length
)
1563 entry
->size
= entry
->object
->length
;
1565 if(rc
< CHUNK_SIZE
- j
) {
1566 /* Paranoia: the read may have been interrupted half-way. */
1567 if(entry
->size
< 0) {
1569 (entry
->object
->length
>= 0 &&
1570 entry
->object
->length
==
1571 entry
->offset
- entry
->body_offset
))
1572 entry
->size
= entry
->offset
- entry
->body_offset
;
1574 } else if(entry
->size
!= entry
->offset
- entry
->body_offset
) {
1576 entry
->size
< entry
->offset
- entry
->body_offset
) {
1578 "Disk entry size changed behind our back: "
1579 "%ld -> %ld (%d).\n",
1581 (long)entry
->offset
- entry
->body_offset
,
1593 CHECK_ENTRY(object
->disk_entry
);
1594 for(k
= 0; k
< chunks
; k
++) {
1595 i
= offset
/ CHUNK_SIZE
+ k
;
1596 unlockChunk(object
, i
);
1600 notifyObject(object
);
1608 writeoutToDisk(ObjectPtr object
, int upto
, int max
)
1610 if(maxDiskCacheEntrySize
>= 0 && object
->size
> maxDiskCacheEntrySize
) {
1611 /* An object was created with an unknown length, and then grew
1612 beyond maxDiskCacheEntrySize. Destroy the disk entry. */
1613 destroyDiskEntry(object
, 1);
1617 return reallyWriteoutToDisk(object
, upto
, max
);
1621 reallyWriteoutToDisk(ObjectPtr object
, int upto
, int max
)
1623 DiskCacheEntryPtr entry
;
1630 upto
= object
->size
;
1632 if((object
->cache_control
& CACHE_NO_STORE
) ||
1633 (object
->flags
& OBJECT_LOCAL
))
1636 if((object
->flags
& OBJECT_DISK_ENTRY_COMPLETE
) && !object
->disk_entry
)
1639 entry
= makeDiskEntry(object
, 1, 1);
1640 if(!entry
) return 0;
1642 assert(!entry
->local
);
1644 if(object
->flags
& OBJECT_DISK_ENTRY_COMPLETE
)
1647 diskEntrySize(object
);
1651 if(object
->length
>= 0 && entry
->size
>= object
->length
) {
1652 object
->flags
|= OBJECT_DISK_ENTRY_COMPLETE
;
1656 if(entry
->size
>= upto
)
1659 if(!entry
->writeable
) {
1660 entry
= makeDiskEntry(object
, 1, 1);
1663 if(!entry
->writeable
)
1665 diskEntrySize(object
);
1670 offset
= entry
->size
;
1672 /* Avoid a seek in case we start writing at the beginning */
1673 if(offset
== 0 && entry
->metadataDirty
) {
1674 writeoutMetadata(object
);
1675 /* rewriteDiskEntry may change the entry */
1676 entry
= makeDiskEntry(object
, 1, 0);
1677 if(entry
== NULL
|| !entry
->writeable
)
1681 rc
= entrySeek(entry
, offset
+ entry
->body_offset
);
1682 if(rc
< 0) return 0;
1685 if(max
>= 0 && bytes
>= max
)
1688 assert(entry
->offset
== offset
+ entry
->body_offset
);
1689 i
= offset
/ CHUNK_SIZE
;
1690 j
= offset
% CHUNK_SIZE
;
1691 if(i
>= object
->numchunks
)
1693 if(object
->chunks
[i
].size
<= j
)
1696 rc
= write(entry
->fd
, object
->chunks
[i
].data
+ j
,
1697 object
->chunks
[i
].size
- j
);
1701 do_log_error(L_ERROR
, errno
, "Couldn't write disk entry");
1704 entry
->offset
+= rc
;
1707 if(entry
->size
< offset
)
1708 entry
->size
= offset
;
1709 } while(j
+ rc
>= CHUNK_SIZE
);
1713 if(entry
->metadataDirty
)
1714 writeoutMetadata(object
);
1720 writeoutMetadata(ObjectPtr object
)
1722 DiskCacheEntryPtr entry
;
1725 if((object
->cache_control
& CACHE_NO_STORE
) ||
1726 (object
->flags
& OBJECT_LOCAL
))
1729 entry
= makeDiskEntry(object
, 1, 0);
1730 if(entry
== NULL
|| entry
== &negativeEntry
)
1733 assert(!entry
->local
);
1735 rc
= entrySeek(entry
, 0);
1736 if(rc
< 0) goto fail
;
1738 rc
= writeHeaders(entry
->fd
, &entry
->body_offset
, object
, NULL
, 0);
1740 rc
= rewriteEntry(object
);
1741 if(rc
< 0) return 0;
1744 if(rc
< 0) goto fail
;
1746 entry
->metadataDirty
= 0;
1750 /* We need this in order to avoid trying to write this entry out
1752 if(entry
&& entry
!= &negativeEntry
)
1753 entry
->metadataDirty
= 0;
1758 mergeDobjects(DiskObjectPtr dst
, DiskObjectPtr src
)
1760 if(dst
->filename
== NULL
) {
1761 dst
->filename
= src
->filename
;
1762 dst
->body_offset
= src
->body_offset
;
1764 free(src
->filename
);
1765 free(src
->location
);
1767 dst
->length
= src
->length
;
1769 dst
->size
= src
->size
;
1771 dst
->age
= src
->age
;
1773 dst
->date
= src
->date
;
1774 if(dst
->last_modified
< 0)
1775 dst
->last_modified
= src
->last_modified
;
1780 readDiskObject(char *filename
, struct stat
*sb
)
1782 int fd
, rc
, n
, dummy
, code
;
1784 time_t date
, last_modified
, age
, atime
, expires
;
1785 char *location
= NULL
, *fn
= NULL
;
1786 DiskObjectPtr dobject
;
1788 int buf_is_chunk
, bufsize
;
1795 rc
= stat(filename
, &ss
);
1797 do_log_error(L_WARN
, errno
, "Couldn't stat %s", filename
);
1804 bufsize
= CHUNK_SIZE
;
1807 do_log(L_ERROR
, "Couldn't allocate buffer.\n");
1811 if(S_ISREG(sb
->st_mode
)) {
1812 fd
= open(filename
, O_RDONLY
| O_BINARY
);
1816 rc
= read(fd
, buf
, bufsize
);
1820 n
= findEndOfHeaders(buf
, 0, rc
, &dummy
);
1826 bufsize
= bigBufferSize
;
1827 buf
= malloc(bigBufferSize
);
1830 lrc
= lseek(fd
, 0, SEEK_SET
);
1838 rc
= httpParseServerFirstLine(buf
, &code
, &dummy
, NULL
);
1842 rc
= httpParseHeaders(0, NULL
, buf
, rc
, NULL
,
1843 NULL
, &length
, NULL
, NULL
, NULL
,
1844 &date
, &last_modified
, &expires
, &age
,
1845 &atime
, &body_offset
, NULL
,
1846 NULL
, NULL
, NULL
, NULL
, &location
, NULL
, NULL
);
1847 if(rc
< 0 || location
== NULL
)
1852 size
= sb
->st_size
- body_offset
;
1855 } else if(S_ISDIR(sb
->st_mode
)) {
1857 n
= dirnameUrl(buf
, 512, (char*)filename
, strlen(filename
));
1860 location
= strdup(n
);
1861 if(location
== NULL
)
1874 dobject
= malloc(sizeof(DiskObjectRec
));
1878 fn
= strdup(filename
);
1887 dobject
->location
= location
;
1888 dobject
->filename
= fn
;
1889 dobject
->length
= length
;
1890 dobject
->body_offset
= body_offset
;
1891 dobject
->size
= size
;
1893 dobject
->access
= atime
;
1894 dobject
->date
= date
;
1895 dobject
->last_modified
= last_modified
;
1896 dobject
->expires
= expires
;
1897 if(fd
>= 0) close(fd
);
1906 if(fd
>= 0) close(fd
);
1907 if(location
) free(location
);
1913 processObject(DiskObjectPtr dobjects
, char *filename
, struct stat
*sb
)
1915 DiskObjectPtr dobject
= NULL
;
1918 dobject
= readDiskObject((char*)filename
, sb
);
1923 (c
= strcmp(dobject
->location
, dobjects
->location
)) <= 0) {
1924 if(dobjects
&& c
== 0) {
1925 mergeDobjects(dobjects
, dobject
);
1927 dobject
->next
= dobjects
;
1931 DiskObjectPtr other
= dobjects
;
1932 while(other
->next
) {
1933 c
= strcmp(dobject
->location
, other
->next
->location
);
1936 other
= other
->next
;
1938 if(strcmp(dobject
->location
, other
->location
) == 0) {
1939 mergeDobjects(other
, dobject
);
1941 dobject
->next
= other
->next
;
1942 other
->next
= dobject
;
1948 /* Determine whether p is below root */
1950 filter(DiskObjectPtr p
, const char *root
, int n
, int recursive
)
1953 int m
= strlen(p
->location
);
1956 if(memcmp(root
, p
->location
, n
) != 0)
1960 if(m
== 0 || p
->location
[m
- 1] == '/')
1962 cp
= strchr(p
->location
+ n
, '/');
1963 if(cp
&& cp
- p
->location
!= m
- 1)
1968 /* Filter out all disk objects that are not under root */
1970 filterDiskObjects(DiskObjectPtr from
, const char *root
, int recursive
)
1972 int n
= strlen(root
);
1975 while(from
&& !filter(from
, root
, n
, recursive
)) {
1983 while(p
&& p
->next
) {
1984 if(!filter(p
->next
, root
, n
, recursive
)) {
1997 insertRoot(DiskObjectPtr from
, const char *root
)
2003 if(strcmp(root
, p
->location
) == 0)
2008 p
= malloc(sizeof(DiskObjectRec
));
2010 p
->location
= strdup(root
);
2011 if(p
->location
== NULL
) {
2020 p
->last_modified
= -1;
2026 /* Insert all missing directories in a sorted list of dobjects */
2028 insertDirs(DiskObjectPtr from
)
2030 DiskObjectPtr p
, q
, new;
2036 n
= strlen(q
->location
);
2037 if(n
> 0 && q
->location
[n
- 1] != '/') {
2038 cp
= strrchr(q
->location
, '/');
2039 m
= cp
- q
->location
+ 1;
2040 if(cp
&& (!p
|| strlen(p
->location
) < m
||
2041 memcmp(p
->location
, q
->location
, m
) != 0)) {
2042 new = malloc(sizeof(DiskObjectRec
));
2044 new->location
= strdup_n(q
->location
, m
);
2045 if(new->location
== NULL
) {
2049 new->filename
= NULL
;
2054 new->last_modified
= -1;
2070 indexDiskObjects(FILE *out
, const char *root
, int recursive
)
2074 struct dirent
*dirent
;
2079 DiskObjectPtr dobjects
= NULL
;
2080 char *of
= root
[0] == '\0' ? "" : " of ";
2082 fprintf(out
, "<!DOCTYPE HTML PUBLIC "
2083 "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
2084 "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
2086 "<title>%s%s%s</title>\n"
2088 "<h1>%s%s%s</h1>\n",
2089 recursive
? "Recursive index" : "Index", of
, root
,
2090 recursive
? "Recursive index" : "Index", of
, root
);
2092 if(diskCacheRoot
== NULL
|| diskCacheRoot
->length
<= 0) {
2093 fprintf(out
, "<p>No <tt>diskCacheRoot</tt>.</p>\n");
2097 if(diskCacheRoot
->length
>= 1024) {
2099 "<p>The value of <tt>diskCacheRoot</tt> is "
2100 "too long (%d).</p>\n",
2101 diskCacheRoot
->length
);
2105 if(strlen(root
) < 8) {
2106 memcpy(buf
, diskCacheRoot
->string
, diskCacheRoot
->length
);
2107 buf
[diskCacheRoot
->length
] = '\0';
2108 n
= diskCacheRoot
->length
;
2110 n
= urlDirname(buf
, 1024, root
, strlen(root
));
2117 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
2122 if(fe
->fts_info
!= FTS_DP
)
2124 processObject(dobjects
,
2126 fe
->fts_info
== FTS_NS
||
2127 fe
->fts_info
== FTS_NSOK
?
2128 fe
->fts_statp
: NULL
);
2136 dirent
= readdir(dir
);
2138 if(n
+ strlen(dirent
->d_name
) < 1024) {
2139 strcpy(buf
+ n
, dirent
->d_name
);
2143 dobjects
= processObject(dobjects
, buf
, NULL
);
2147 fprintf(out
, "<p>Couldn't open directory: %s (%d).</p>\n",
2148 strerror(errno
), errno
);
2155 DiskObjectPtr dobject
;
2157 dobjects
= insertRoot(dobjects
, root
);
2158 dobjects
= insertDirs(dobjects
);
2159 dobjects
= filterDiskObjects(dobjects
, root
, recursive
);
2162 alternatingHttpStyle(out
, "diskcachelist");
2163 fprintf(out
, "<table id=diskcachelist>\n");
2164 fprintf(out
, "<tbody>\n");
2168 i
= strlen(dobject
->location
);
2169 isdir
= (i
== 0 || dobject
->location
[i
- 1] == '/');
2171 fprintf(out
, "<tr class=odd>");
2173 fprintf(out
, "<tr class=even>");
2174 if(dobject
->size
>= 0) {
2175 fprintf(out
, "<td><a href=\"%s\"><tt>",
2178 dobject
->location
, strlen(dobject
->location
));
2179 fprintf(out
, "</tt></a></td> ");
2180 if(dobject
->length
>= 0) {
2181 if(dobject
->size
== dobject
->length
)
2182 fprintf(out
, "<td>%d</td> ", dobject
->length
);
2184 fprintf(out
, "<td>%d/%d</td> ",
2185 dobject
->size
, dobject
->length
);
2187 /* Avoid a trigraph. */
2188 fprintf(out
, "<td>%d/<em>??" "?</em></td> ", dobject
->size
);
2190 if(dobject
->last_modified
>= 0) {
2191 struct tm
*tm
= gmtime(&dobject
->last_modified
);
2195 n
= strftime(buf
, 1024, "%d.%m.%Y", tm
);
2200 fprintf(out
, "<td>%s</td> ", buf
);
2202 fprintf(out
, "<td></td>");
2205 if(dobject
->date
>= 0) {
2206 struct tm
*tm
= gmtime(&dobject
->date
);
2210 n
= strftime(buf
, 1024, "%d.%m.%Y", tm
);
2215 fprintf(out
, "<td>%s</td>", buf
);
2217 fprintf(out
, "<td></td>");
2220 fprintf(out
, "<td><tt>");
2221 htmlPrint(out
, dobject
->location
,
2222 strlen(dobject
->location
));
2223 fprintf(out
, "</tt></td><td></td><td></td><td></td>");
2226 fprintf(out
, "<td><a href=\"/polipo/index?%s\">plain</a></td>"
2227 "<td><a href=\"/polipo/recursive-index?%s\">"
2228 "recursive</a></td>",
2229 dobject
->location
, dobject
->location
);
2231 fprintf(out
, "</tr>\n");
2233 dobjects
= dobject
->next
;
2234 free(dobject
->location
);
2235 free(dobject
->filename
);
2238 fprintf(out
, "</tbody>\n");
2239 fprintf(out
, "</table>\n");
2243 fprintf(out
, "<p><a href=\"/polipo/\">back</a></p>\n");
2244 fprintf(out
, "</body></html>\n");
2249 checkForZeroes(char *buf
, int n
)
2252 unsigned long *lbuf
= (unsigned long *)buf
;
2253 assert(n
% sizeof(unsigned long) == 0);
2255 for(i
= 0; i
* sizeof(unsigned long) < n
; i
++) {
2257 return i
* sizeof(unsigned long);
2259 for(j
= 0; i
* sizeof(unsigned long) + j
< n
; j
++) {
2260 if(buf
[i
* sizeof(unsigned long) + j
] != 0)
2264 return i
* sizeof(unsigned long) + j
;
2268 copyFile(int from
, char *filename
, int n
)
2271 int to
, offset
, nread
, nzeroes
, rc
;
2273 buf
= malloc(CHUNK_SIZE
);
2277 to
= open(filename
, O_RDWR
| O_CREAT
| O_EXCL
| O_BINARY
,
2278 diskCacheFilePermissions
);
2286 nread
= read(from
, buf
, MIN(CHUNK_SIZE
, n
- offset
));
2289 nzeroes
= checkForZeroes(buf
, nread
& -8);
2292 rc
= lseek(to
, nzeroes
, SEEK_CUR
);
2293 if(rc
!= offset
+ nzeroes
) {
2295 do_log_error(L_ERROR
, errno
, "Couldn't extend file");
2298 "Couldn't extend file: "
2299 "unexpected offset %d != %d + %d.\n",
2304 if(nread
> nzeroes
) {
2305 rc
= write(to
, buf
+ nzeroes
, nread
- nzeroes
);
2306 if(rc
!= nread
- nzeroes
) {
2308 do_log_error(L_ERROR
, errno
, "Couldn't write");
2310 do_log(L_ERROR
, "Short write.\n");
2319 unlink(filename
); /* something went wrong straight away */
2324 expireFile(char *filename
, struct stat
*sb
,
2325 int *considered
, int *unlinked
, int *truncated
)
2327 DiskObjectPtr dobject
= NULL
;
2330 long int ret
= sb
->st_size
;
2332 if(!preciseExpiry
) {
2334 if(t
> current_time
.tv_sec
+ 1) {
2335 do_log(L_WARN
, "File %s has access time in the future.\n",
2337 t
= current_time
.tv_sec
;
2340 if(t
> current_time
.tv_sec
- diskCacheUnlinkTime
&&
2341 (sb
->st_size
< diskCacheTruncateSize
||
2342 t
> current_time
.tv_sec
- diskCacheTruncateTime
))
2348 dobject
= readDiskObject(filename
, sb
);
2350 do_log(L_ERROR
, "Incorrect disk entry %s -- removing.\n", filename
);
2351 rc
= unlink(filename
);
2353 do_log_error(L_ERROR
, errno
,
2354 "Couldn't unlink %s", filename
);
2362 t
= dobject
->access
;
2363 if(t
< 0) t
= dobject
->age
;
2364 if(t
< 0) t
= dobject
->date
;
2366 if(t
> current_time
.tv_sec
)
2368 "Disk entry %s (%s) has access time in the future.\n",
2369 dobject
->location
, dobject
->filename
);
2371 if(t
< current_time
.tv_sec
- diskCacheUnlinkTime
) {
2372 rc
= unlink(dobject
->filename
);
2374 do_log_error(L_ERROR
, errno
, "Couldn't unlink %s", filename
);
2379 } else if(dobject
->size
>
2380 diskCacheTruncateSize
+ 4 * dobject
->body_offset
&&
2381 t
< current_time
.tv_sec
- diskCacheTruncateTime
) {
2382 /* We need to copy rather than simply truncate in place: the
2383 latter would confuse a running polipo. */
2384 fd
= open(dobject
->filename
, O_RDONLY
| O_BINARY
, 0);
2385 rc
= unlink(dobject
->filename
);
2387 do_log_error(L_ERROR
, errno
, "Couldn't unlink %s", filename
);
2392 copyFile(fd
, dobject
->filename
,
2393 dobject
->body_offset
+ diskCacheTruncateSize
);
2397 ret
= sb
->st_size
- dobject
->body_offset
+ diskCacheTruncateSize
;
2400 free(dobject
->location
);
2401 free(dobject
->filename
);
2413 int files
= 0, considered
= 0, unlinked
= 0, truncated
= 0;
2414 int dirs
= 0, rmdirs
= 0;
2415 long left
= 0, total
= 0;
2417 if(diskCacheRoot
== NULL
||
2418 diskCacheRoot
->length
<= 0 || diskCacheRoot
->string
[0] != '/')
2421 fts_argv
[0] = diskCacheRoot
->string
;
2423 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
2425 do_log_error(L_ERROR
, errno
, "Couldn't fts_open disk cache");
2428 gettimeofday(¤t_time
, NULL
);
2433 if(fe
->fts_info
== FTS_D
)
2436 if(fe
->fts_info
== FTS_DP
|| fe
->fts_info
== FTS_DC
||
2437 fe
->fts_info
== FTS_DNR
) {
2438 if(fe
->fts_accpath
[0] == '/' &&
2439 strlen(fe
->fts_accpath
) <= diskCacheRoot
->length
)
2442 rc
= rmdir(fe
->fts_accpath
);
2445 else if(errno
!= ENOTEMPTY
&& errno
!= EEXIST
)
2446 do_log_error(L_ERROR
, errno
,
2447 "Couldn't remove directory %s",
2450 } else if(fe
->fts_info
== FTS_NS
) {
2451 do_log_error(L_ERROR
, fe
->fts_errno
, "Couldn't stat file %s",
2454 } else if(fe
->fts_info
== FTS_ERR
) {
2455 do_log_error(L_ERROR
, fe
->fts_errno
,
2456 "Couldn't fts_read disk cache");
2460 if(!S_ISREG(fe
->fts_statp
->st_mode
)) {
2461 do_log(L_ERROR
, "Unexpected file %s type 0%o.\n",
2462 fe
->fts_accpath
, (unsigned int)fe
->fts_statp
->st_mode
);
2467 left
+= expireFile(fe
->fts_accpath
, fe
->fts_statp
,
2468 &considered
, &unlinked
, &truncated
);
2469 total
+= fe
->fts_statp
->st_size
;
2474 printf("Disk cache purged.\n");
2475 printf("%d files, %d considered, %d removed, %d truncated "
2476 "(%ldkB -> %ldkB).\n",
2477 files
, considered
, unlinked
, truncated
, total
/1024, left
/1024);
2478 printf("%d directories, %d removed.\n", dirs
, rmdirs
);
2497 writeoutToDisk(ObjectPtr object
, int upto
, int max
)
2503 destroyDiskEntry(ObjectPtr object
, int d
)
2509 objectGetFromDisk(ObjectPtr object
)
2515 objectFillFromDisk(ObjectPtr object
, int offset
, int chunks
)
2521 revalidateDiskEntry(ObjectPtr object
)
2527 dirtyDiskEntry(ObjectPtr object
)
2535 do_log(L_ERROR
, "Disk cache not supported in this version.\n");
2539 diskEntrySize(ObjectPtr object
)