]> git.ipfire.org Git - thirdparty/squid.git/blame - tools/purge/purge.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / tools / purge / purge.cc
CommitLineData
5f623035 1/*
f70aedc4 2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
5f623035
AJ
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
0b96a9b3 9// Author: Jens-S. V?ckler <voeckler@rvs.uni-hannover.de>
eb1f6bfa
AJ
10//
11// File: purge.cc
12// Wed Jan 13 1999
13//
14// (c) 1999 Lehrgebiet Rechnernetze und Verteilte Systeme
0b96a9b3 15// Universit?t Hannover, Germany
eb1f6bfa
AJ
16//
17// Permission to use, copy, modify, distribute, and sell this software
18// and its documentation for any purpose is hereby granted without fee,
19// provided that (i) the above copyright notices and this permission
20// notice appear in all copies of the software and related documentation,
21// and (ii) the names of the Lehrgebiet Rechnernetze und Verteilte
22// Systeme and the University of Hannover may not be used in any
23// advertising or publicity relating to the software without the
24// specific, prior written permission of Lehrgebiet Rechnernetze und
25// Verteilte Systeme and the University of Hannover.
26//
27// THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
28// EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
29// WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
30//
31// IN NO EVENT SHALL THE LEHRGEBIET RECHNERNETZE UND VERTEILTE SYSTEME OR
32// THE UNIVERSITY OF HANNOVER BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
33// INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES
34// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT
35// ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY,
36// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
37// SOFTWARE.
38//
eb1f6bfa
AJ
39// Revision 1.17 2000/09/21 10:59:53 cached
40// *** empty log message ***
41//
42// Revision 1.16 2000/09/21 09:45:18 cached
43// Fixed some small bugs.
44//
45// Revision 1.15 2000/09/21 09:05:56 cached
46// added multi cache_dir support, thus changing -c cmdline option.
47// modified file reading to support /dev/fd/0 reading for non-disclosed items.
48//
49// Revision 1.14 2000/06/20 09:43:01 voeckler
50// added FreeBSD related fixes and support.
51//
52// Revision 1.13 2000/03/29 08:12:21 voeckler
53// fixed wrong header file.
54//
55// Revision 1.12 2000/03/29 07:54:41 voeckler
56// added mechanism to give a port specification precedence over a host
2f8abb64 57// specification with the -p option and not colon.
eb1f6bfa
AJ
58//
59// Revision 1.11 1999/06/18 13:18:28 voeckler
60// added refcount, fixed missing LF in -s output.
61//
62// Revision 1.10 1999/06/16 13:06:05 voeckler
63// reversed meaning of -M flag.
64//
65// Revision 1.9 1999/06/15 21:11:53 voeckler
66// added extended logging feature which extract the squid meta data available
67// within the disk files. moved the content extraction and squid meta data
68// handling parts into separate files. added options for copy-out and verbose.
69//
70// Revision 1.8 1999/06/14 20:14:46 voeckler
71// intermediate version when adding understanding about the way
72// Squid does log the metadata into the file.
73//
74// Revision 1.7 1999/01/23 21:01:10 root
75// stumbled over libc5 header/lib inconsistency bug....
76//
77// Revision 1.6 1999/01/23 20:47:54 root
78// added Linux specifics for psignal...
79// Hope this helps.
80//
81// Revision 1.5 1999/01/20 09:48:12 voeckler
82// added warning as first line of output.
83//
84// Revision 1.4 1999/01/19 11:53:49 voeckler
85// added psignal() from <siginfo.h> handling.
86//
87// Revision 1.3 1999/01/19 11:00:50 voeckler
88// added keyboard interrupt handling, exit handling, removed C++ strings and
89// regular expression syntax in favour of less source code, added comments,
90// added a reminder to remove swap.state in case of unlinks, added IAA flag,
91// added a few assertions, changed policy to enforce the definition of at
92// least one regular expression, and catch a few signals.
93//
94// Revision 1.2 1999/01/15 23:06:28 voeckler
95// downgraded to simple C strings...
96//
97// Revision 1.1 1999/01/14 12:05:32 voeckler
98// Initial revision
99//
100//
f7f3304a 101#include "squid.h"
2ccf2eb2
AJ
102#include "util.h"
103
074d6a40
AJ
104#include <cerrno>
105#include <climits>
106#include <csignal>
107#include <cstdarg>
108#include <cstdlib>
109#include <cstring>
eb1f6bfa 110#include <dirent.h>
eb1f6bfa
AJ
111#include <sys/stat.h>
112#include <sys/wait.h>
113#include <fcntl.h>
114#include <unistd.h>
eb1f6bfa 115
2ccf2eb2 116#if HAVE_SIGINFO_H
eb1f6bfa 117#include <siginfo.h>
2ccf2eb2 118#endif
eb1f6bfa
AJ
119
120#include <netinet/in.h>
b0fb853f 121#include <netinet/tcp.h>
eb1f6bfa 122#include <arpa/inet.h>
582c2af2 123#include <netdb.h>
eb1f6bfa 124
602d9612 125#include "conffile.hh"
eb1f6bfa 126#include "convert.hh"
602d9612 127#include "copyout.hh"
eb1f6bfa 128#include "signal.hh"
602d9612 129#include "socket.hh"
eb1f6bfa 130#include "squid-tlv.hh"
eb1f6bfa
AJ
131
132#ifndef DEFAULTHOST
133#define DEFAULTHOST "localhost"
134#endif // DEFAULTHOST
135
136#ifndef DEFAULTPORT
137#define DEFAULTPORT 3128
138#endif // DEFAULTPORT
139
140volatile sig_atomic_t term_flag = 0; // 'terminate' is a gcc 2.8.x internal...
feec68a0 141char* linebuffer = 0;
e65cf8d5 142size_t buffersize = 128*1024;
eb1f6bfa 143static char* copydir = 0;
052aecef 144static uint32_t debugFlag = 0;
eb1f6bfa
AJ
145static unsigned purgeMode = 0;
146static bool iamalive = false;
147static bool reminder = false;
148static bool verbose = false;
149static bool envelope = false;
150static bool no_fork = false;
151static const char* programname = 0;
eb1f6bfa
AJ
152
153// ----------------------------------------------------------------------
154
c2afddd8
AJ
155struct REList {
156 REList( const char* what, bool doCase );
157 ~REList();
158 bool match( const char* check ) const;
eb1f6bfa 159
597504bf
AJ
160 REList* next;
161 const char* data;
c2afddd8 162 regex_t rexp;
597504bf 163};
eb1f6bfa 164
c2afddd8
AJ
165REList::REList( const char* what, bool doCase )
166 :next(0),data(xstrdup(what))
167{
168 int result = regcomp( &rexp, what,
169 REG_EXTENDED | REG_NOSUB | (doCase ? 0 : REG_ICASE) );
170 if ( result != 0 ) {
171 char buffer[256];
172 regerror( result, &rexp, buffer, 256 );
173 fprintf( stderr, "unable to compile re \"%s\": %s\n", what, buffer );
24885773 174 exit(EXIT_FAILURE);
c2afddd8
AJ
175 }
176}
177
178REList::~REList()
179{
180 if ( next ) delete next;
181 if ( data ) xfree((void*) data);
182 regfree(&rexp);
183}
184
185bool
186REList::match( const char* check ) const
187{
188 int result = regexec( &rexp, check, 0, 0, 0 );
189 if ( result != 0 && result != REG_NOMATCH ) {
190 char buffer[256];
191 regerror( result, &rexp, buffer, 256 );
192 fprintf( stderr, "unable to execute re \"%s\"\n+ on line \"%s\": %s\n",
193 data, check, buffer );
24885773 194 exit(EXIT_FAILURE);
c2afddd8
AJ
195 }
196 return ( result == 0 );
197}
198
eb1f6bfa
AJ
199// ----------------------------------------------------------------------
200
201char*
202concat( const char* start, ... )
feec68a0
A
203// purpose: concatinate an arbitrary number of C strings.
204// paramtr: start (IN): first C string
205// ... (IN): further C strings, terminated with a NULL pointer
2f8abb64 206// returns: memory allocated via new(), containing the concatenated string.
eb1f6bfa 207{
feec68a0
A
208 va_list ap;
209 const char* s;
210
211 // first run: determine size
212 unsigned size = strlen(start)+1;
213 va_start( ap, start );
2a9c12b3
AJ
214 while ( (s=va_arg(ap,const char*)) != NULL )
215 size += strlen(s);
feec68a0
A
216 va_end(ap);
217
218 // allocate
219 char* result = new char[size];
220 if ( result == 0 ) {
221 perror( "string memory allocation" );
24885773 222 exit(EXIT_FAILURE);
feec68a0
A
223 }
224
225 // second run: copy content
226 strcpy( result, start );
227 va_start( ap, start );
228 while ( (s=va_arg(ap,const char*)) != NULL ) strcat( result, s );
229 va_end(ap);
230
231 return result;
eb1f6bfa
AJ
232}
233
234bool
235isxstring( const char* s, size_t testlen )
feec68a0
A
236// purpose: test a string for conforming to xdigit
237// paramtr: s (IN): string to test
238// testlen (IN): length the string must have
239// returns: true, iff strlen(s)==testlen && all_x_chars(s), false otherwise
eb1f6bfa 240{
feec68a0 241 if ( strlen(s) != testlen ) return false;
eb1f6bfa 242
feec68a0 243 size_t i=0;
14942edd
FC
244 while ( i<testlen && isxdigit(s[i]) )
245 ++i;
feec68a0 246 return (i==testlen);
eb1f6bfa
AJ
247}
248
249inline
250int
251log_output( const char* fn, int code, long size, const char* url )
252{
feec68a0 253 return printf( "%s %3d %8ld %s\n", fn, code, size, url );
eb1f6bfa
AJ
254}
255
256static
257int
258log_extended( const char* fn, int code, long size, const SquidMetaList* meta )
259{
feec68a0
A
260 static const char hexdigit[] = "0123456789ABCDEF";
261 char md5[34];
262 const SquidTLV* findings = 0;
263
264 if ( meta && (findings = meta->search( STORE_META_KEY_MD5 )) ) {
265 unsigned char* s = (unsigned char*) findings->data;
cbebe602 266 for ( int j=0; j<16; ++j, ++s ) {
feec68a0
A
267 md5[j*2+0] = hexdigit[ *s >> 4 ];
268 md5[j*2+1] = hexdigit[ *s & 15 ];
269 }
270 md5[32] = '\0'; // terminate string
271 } else {
6e2aefad 272 snprintf( md5, sizeof(md5), "%-32s", "(no_md5_data_available)" );
feec68a0
A
273 }
274
2d51fa42 275 char timeb[256];
feec68a0
A
276 if ( meta && (findings = meta->search( STORE_META_STD )) ) {
277 StoreMetaStd temp;
278 // make data aligned, avoid SIGBUS on RISC machines (ARGH!)
279 memcpy( &temp, findings->data, sizeof(StoreMetaStd) );
6e2aefad 280 snprintf( timeb, sizeof(timeb), "%08lx %08lx %08lx %08lx %04x %5hu ",
8978bd9d
A
281 (unsigned long)temp.timestamp, (unsigned long)temp.lastref,
282 (unsigned long)temp.expires, (unsigned long)temp.lastmod, temp.flags, temp.refcount );
feec68a0
A
283 } else if ( meta && (findings = meta->search( STORE_META_STD_LFS )) ) {
284 StoreMetaStdLFS temp;
285 // make data aligned, avoid SIGBUS on RISC machines (ARGH!)
2d51fa42 286 memcpy( &temp, findings->data, sizeof(StoreMetaStdLFS) );
6e2aefad 287 snprintf( timeb, sizeof(timeb), "%08lx %08lx %08lx %08lx %04x %5hu ",
8978bd9d
A
288 (unsigned long)temp.timestamp, (unsigned long)temp.lastref,
289 (unsigned long)temp.expires, (unsigned long)temp.lastmod, temp.flags, temp.refcount );
feec68a0 290 } else {
8baf6ea3 291 unsigned long ul = ULONG_MAX; // Match type of StoreMetaTLV fields
d8b258a9
PW
292 unsigned short hu = 0; // Match type of StoreMetaTLV refcount fields
293 snprintf( timeb, sizeof(timeb), "%08lx %08lx %08lx %08lx %04x %5d ", ul, ul, ul, ul, 0, hu);
feec68a0
A
294 }
295
296 // make sure that there is just one printf()
297 if ( meta && (findings = meta->search( STORE_META_URL )) ) {
298 return printf( "%s %3d %8ld %s %s %s\n",
299 fn, code, size, md5, timeb, findings->data );
300 } else {
301 return printf( "%s %3d %8ld %s %s strange_file\n",
302 fn, code, size, md5, timeb );
eb1f6bfa 303 }
eb1f6bfa
AJ
304}
305
2f8abb64 306// o.k., this is pure laziness...
eb1f6bfa
AJ
307static struct in_addr serverHost;
308static unsigned short serverPort;
309
310bool
311action( int fd, size_t metasize,
feec68a0
A
312 const char* fn, const char* url, const SquidMetaList& meta )
313// purpose: if cmdline-requested, send the purge request to the cache
314// paramtr: fd (IN): open FD for the object file
f53969cc 315// metasize (IN): offset into data portion of file (meta data size)
feec68a0
A
316// fn (IN): name of the object file
317// url (IN): URL string stored in the object file
f53969cc 318// meta (IN): list containing further meta data
feec68a0
A
319// returns: true for a successful action, false otherwise. The action
320// may just print the file, send the purge request or even
321// remove unwanted files.
322// globals: ::purgeMode (IN): bit#0 set -> send purge request.
323// bit#1 set -> remove 404 object files.
324// ::serverHost (IN): cache host address
325// ::serverPort (IN): cache port number
eb1f6bfa 326{
feec68a0
A
327 static const char* schablone = "PURGE %s HTTP/1.0\r\nAccept: */*\r\n\r\n";
328 struct stat st;
329 long size = ( fstat(fd,&st) == -1 ? -1 : long(st.st_size - metasize) );
feec68a0
A
330
331 // if we want to copy out the file, do that first of all.
332 if ( ::copydir && *copydir && size > 0 )
59a09b98 333 copy_out( st.st_size, metasize, ::debugFlag,
feec68a0
A
334 fn, url, ::copydir, ::envelope );
335
336 // do we need to PURGE the file, yes, if purgemode bit#0 was set.
e65cf8d5 337 int status = 0;
feec68a0
A
338 if ( ::purgeMode & 0x01 ) {
339 unsigned long bufsize = strlen(url) + strlen(schablone) + 4;
340 char* buffer = new char[bufsize];
341
6e2aefad 342 snprintf( buffer, bufsize, schablone, url );
feec68a0
A
343 int sockfd = connectTo( serverHost, serverPort, true );
344 if ( sockfd == -1 ) {
345 fprintf( stderr, "unable to connect to server: %s\n", strerror(errno) );
346 delete[] buffer;
347 return false;
348 }
349
9dca980d
AJ
350 int content_size = strlen(buffer);
351 if ( write( sockfd, buffer, content_size ) != content_size ) {
feec68a0
A
352 // error while talking to squid
353 fprintf( stderr, "unable to talk to server: %s\n", strerror(errno) );
354 close(sockfd);
355 delete[] buffer;
356 return false;
357 }
358 memset( buffer+8, 0, 4 );
3efeeda2
AJ
359 int readLen = read(sockfd, buffer, bufsize);
360 if (readLen < 1) {
feec68a0
A
361 // error while reading squid's answer
362 fprintf( stderr, "unable to read answer: %s\n", strerror(errno) );
363 close(sockfd);
364 delete[] buffer;
365 return false;
366 }
3efeeda2 367 buffer[bufsize-1] = '\0';
feec68a0 368 close(sockfd);
e65cf8d5
AJ
369 int64_t s = strtol(buffer+8,0,10);
370 if (s > 0 && s < 1000)
371 status = s;
372 else {
373 // error while reading squid's answer
374 fprintf( stderr, "invalid HTTP status in reply: %s\n", buffer+8);
375 }
feec68a0 376 delete[] buffer;
eb1f6bfa 377 }
feec68a0
A
378
379 // log the output of our operation
380 bool flag = true;
381 if ( ::verbose ) flag = ( log_extended( fn, status, size, &meta ) >= 0 );
382 else flag = ( log_output( fn, status, size, url ) >= 0 );
383
384 // remove the file, if purgemode bit#1, and HTTP result status 404).
385 if ( (::purgeMode & 0x02) && status == 404 ) {
386 reminder = true;
387 if ( unlink(fn) == -1 )
388 // error while unlinking file, this may happen due to the cache
389 // unlinking a file while it is still in the readdir() cache of purge.
390 fprintf( stderr, "WARNING: unable to unlink %s: %s\n",
391 fn, strerror(errno) );
eb1f6bfa 392 }
feec68a0
A
393
394 return flag;
eb1f6bfa
AJ
395}
396
397bool
398match( const char* fn, const REList* list )
feec68a0
A
399// purpose: do something with the given cache content filename
400// paramtr: fn (IN): filename of cache file
401// returns: true for successful action, false otherwise.
402// warning: only return false, if you want the loop to terminate!
eb1f6bfa 403{
feec68a0
A
404 static const size_t addon = sizeof(unsigned char) + sizeof(unsigned int);
405 bool flag = true;
406
59a09b98 407 if ( debugFlag & 0x01 ) fprintf( stderr, "# [3] %s\n", fn );
feec68a0
A
408 int fd = open( fn, O_RDONLY );
409 if ( fd != -1 ) {
e65cf8d5
AJ
410 memset(::linebuffer, 0, ::buffersize);
411 size_t readLen = read(fd,::linebuffer,::buffersize-1);
412 if ( readLen > 60 ) {
feec68a0
A
413 ::linebuffer[ ::buffersize-1 ] = '\0'; // force-terminate string
414
415 // check the offset into the start of object data. The offset is
2f8abb64 416 // stored in a host endianness after the first byte.
feec68a0
A
417 unsigned int datastart;
418 memcpy( &datastart, ::linebuffer + 1, sizeof(unsigned int) );
419 if ( datastart > ::buffersize - addon - 1 ) {
420 // check offset into server reply header (start of cache data).
421 fputs( "WARNING: Using a truncated URL string.\n", stderr );
422 datastart = ::buffersize - addon - 1;
423 }
424
425 // NEW: Parse squid meta data, which is a kind of linked list
426 // flattened out into a file byte stream. Somewhere within is
427 // the URL as part of the list. First, gobble all meta data.
428 unsigned int offset = addon;
429 SquidMetaList meta;
430 while ( offset + addon <= datastart ) {
431 unsigned int size = 0;
432 memcpy( &size, linebuffer+offset+sizeof(char), sizeof(unsigned int) );
3efeeda2
AJ
433 if (size+offset < size) {
434 fputs("WARNING: file corruption detected. 32-bit overflow in size field.\n", stderr);
435 break;
436 }
e65cf8d5
AJ
437 if (size+offset > readLen) {
438 fputs( "WARNING: Partial meta data loaded.\n", stderr );
439 break;
440 }
feec68a0
A
441 meta.append( SquidMetaType(*(linebuffer+offset)),
442 size, linebuffer+offset+addon );
443 offset += ( addon + size );
444 }
445
446 // Now extract the key URL from the meta data.
447 const SquidTLV* urlmeta = meta.search( STORE_META_URL );
448 if ( urlmeta ) {
449 // found URL in meta data. Try to process the URL
450 if ( list == 0 )
451 flag = action( fd, datastart, fn, (char*) urlmeta->data, meta );
452 else {
c2afddd8
AJ
453 REList* head = (REList*) list; // YUCK!
454 while ( head != 0 ) {
feec68a0
A
455 if ( head->match( (char*) urlmeta->data ) ) break;
456 head = head->next;
457 }
c2afddd8 458 if ( head != 0 )
feec68a0
A
459 flag = action( fd, datastart, fn, (char*) urlmeta->data, meta );
460 else flag = true;
461 }
462 }
463
464 // "meta" will be deleted when exiting from this block
465 } else {
9837567d 466 // weird file, TODO: stat() it!
feec68a0
A
467 struct stat st;
468 long size = ( fstat(fd,&st) == -1 ? -1 : st.st_size );
469 if ( ::verbose ) flag = ( log_extended( fn, -1, size, 0 ) >= 0 );
470 else flag = ( log_output( fn, -1, size, "strange file" ) >= 0 );
471
472 if ( (::purgeMode & 0x04) ) {
473 reminder = true;
474 if ( unlink(fn) == -1 )
475 // error while unlinking file, this may happen due to the cache
476 // unlinking a file while it is in the readdir() cache of purge.
477 fprintf( stderr, "WARNING: unable to unlink %s: %s\n",
478 fn, strerror(errno) );
479 }
480 }
481 close(fd);
eb1f6bfa 482 } else {
feec68a0
A
483 // error while opening file, this may happen due to the cache
484 // unlinking a file while it is still in the readdir() cache of purge.
485 fprintf( stderr, "WARNING: open \"%s\": %s\n", fn, strerror(errno) );
eb1f6bfa 486 }
feec68a0
A
487
488 return flag;
eb1f6bfa
AJ
489}
490
491bool
492filelevel( const char* directory, const REList* list )
feec68a0
A
493// purpose: from given starting point, look for squid xxxxxxxx files.
494// example: "/var/spool/cache/08/7F" as input, do action over files
495// paramtr: directory (IN): starting point
496// list (IN): list of rexps to match URLs against
497// returns: true, if every subdir && action was successful.
eb1f6bfa 498{
fb151769 499 dirent_t * entry;
59a09b98 500 if ( debugFlag & 0x01 )
feec68a0
A
501 fprintf( stderr, "# [2] %s\n", directory );
502
503 DIR* dir = opendir( directory );
504 if ( dir == NULL ) {
505 fprintf( stderr, "unable to open directory \"%s\": %s\n",
506 directory, strerror(errno) );
507 return false;
508 }
509
510 // display a rotating character as "i am alive" signal (slows purge).
511 if ( ::iamalive ) {
512 static char alivelist[4][3] = { "\\\b", "|\b", "/\b", "-\b" };
513 static unsigned short alivecount = 0;
7f56277d
AJ
514 const int write_success = write(STDOUT_FILENO, alivelist[alivecount++ & 3], 2);
515 assert(write_success == 2);
feec68a0
A
516 }
517
518 bool flag = true;
519 while ( (entry=readdir(dir)) && flag ) {
520 if ( isxstring(entry->d_name,8) ) {
521 char* name = concat( directory, "/", entry->d_name, 0 );
522 flag = match( name, list );
523 delete[] name;
524 }
eb1f6bfa 525 }
eb1f6bfa 526
feec68a0
A
527 closedir(dir);
528 return flag;
eb1f6bfa
AJ
529}
530
531bool
532dirlevel( const char* dirname, const REList* list, bool level=false )
feec68a0
A
533// purpose: from given starting point, look for squid 00..FF directories.
534// paramtr: dirname (IN): starting point
535// list (IN): list of rexps to match URLs against
536// level (IN): false==toplevel, true==1st level
537// example: "/var/spool/cache", false as input, traverse subdirs w/ action.
538// example: "/var/spool/cache/08", true as input, traverse subdirs w/ action.
539// returns: true, if every subdir && action was successful.
540// warning: this function is once-recursive, no deeper.
eb1f6bfa 541{
fb151769 542 dirent_t* entry;
59a09b98 543 if ( debugFlag & 0x01 )
feec68a0
A
544 fprintf( stderr, "# [%d] %s\n", (level ? 1 : 0), dirname );
545
546 DIR* dir = opendir( dirname );
547 if ( dir == NULL ) {
548 fprintf( stderr, "unable to open directory \"%s\": %s\n",
549 dirname, strerror(errno) );
550 return false;
eb1f6bfa 551 }
eb1f6bfa 552
feec68a0
A
553 bool flag = true;
554 while ( (entry=readdir(dir)) && flag ) {
555 if ( strlen(entry->d_name) == 2 &&
556 isxdigit(entry->d_name[0]) &&
557 isxdigit(entry->d_name[1]) ) {
558 char* name = concat( dirname, "/", entry->d_name, 0 );
559 flag = level ? filelevel( name, list ) : dirlevel( name, list, true );
560 delete[] name;
561 }
562 }
563
564 closedir(dir);
565 return flag;
eb1f6bfa
AJ
566}
567
568int
9dca980d 569checkForPortOnly( const char* arg )
feec68a0
A
570// purpose: see if somebody just put in a port instead of a hostname
571// paramtr: optarg (IN): argument from commandline
572// returns: 0..65535 is the valid port number in network byte order,
573// -1 if not a port
eb1f6bfa 574{
feec68a0 575 // if there is a period in there, it must be a valid hostname
9dca980d 576 if ( strchr( arg, '.' ) != 0 ) return -1;
eb1f6bfa 577
feec68a0
A
578 // if it is just a number between 0 and 65535, it must be a port
579 char* errstr = 0;
9dca980d
AJ
580 unsigned long result = strtoul( arg, &errstr, 0 );
581 if ( result < 65536 && errstr != arg ) return htons(result);
eb1f6bfa
AJ
582
583#if 0
feec68a0 584 // one last try, test for a symbolical service name
9dca980d 585 struct servent* service = getservbyname( arg, "tcp" );
feec68a0 586 return service ? service->s_port : -1;
eb1f6bfa 587#else
feec68a0 588 return -1;
eb1f6bfa
AJ
589#endif
590}
591
592void
593helpMe( void )
feec68a0 594// purpuse: write help message and exit
eb1f6bfa 595{
6d7a7410 596 printf( "\nUsage:\t%s\t[-a] [-c cf] [-d l] [-(f|F) fn | -(e|E) re] "
feec68a0 597 "[-p h[:p]]\n\t\t[-P #] [-s] [-v] [-C dir [-H]] [-n]\n\n",
6d7a7410 598 ::programname );
feec68a0
A
599 printf(
600 " -a\tdisplay a little rotating thingy to indicate that I am alive (tty only).\n"
601 " -c c\tsquid.conf location, default \"%s\".\n"
602 " -C dir\tbase directory for content extraction (copy-out mode).\n"
052aecef 603 " -d l\tdebug level, an OR mask of different debug options.\n"
feec68a0
A
604 " -e re\tsingle regular expression per -e instance (use quotes!).\n"
605 " -E re\tsingle case sensitive regular expression like -e.\n"
606 " -f fn\tname of textfile containing one regular expression per line.\n"
607 " -F fn\tname of textfile like -f containing case sensitive REs.\n"
608 " -H\tprepend HTTP reply header to destination files in copy-out mode.\n"
609 " -n\tdo not fork() when using more than one cache_dir.\n"
610 " -p h:p\tcache runs on host h and optional port p, default is %s:%u.\n"
611 " -P #\tif 0, just print matches; otherwise OR the following purge modes:\n"
612 "\t 0x01 really send PURGE to the cache.\n"
613 "\t 0x02 remove all caches files reported as 404 (not found).\n"
614 "\t 0x04 remove all weird (inaccessible or too small) cache files.\n"
615 "\t0 and 1 are recommended - slow rebuild your cache with other modes.\n"
616 " -s\tshow all options after option parsing, but before really starting.\n"
617 " -v\tshow more information about the file, e.g. MD5, timestamps and flags.\n"
23cf13da 618 "\n", DEFAULT_CONFIG_FILE, DEFAULTHOST, DEFAULTPORT );
eb1f6bfa
AJ
619
620}
621
622void
623parseCommandline( int argc, char* argv[], REList*& head,
9dca980d
AJ
624 char*& conffile, char*& copyDirPath,
625 struct in_addr& serverHostIp, unsigned short& serverHostPort )
feec68a0
A
626// paramtr: argc: see ::main().
627// argv: see ::main().
628// returns: Does terminate the program on errors!
629// purpose: suck in any commandline options, and set the global vars.
630{
631 int option, port, showme = 0;
632 char* ptr, *colon;
633 FILE* rfile;
634
635 // program basename
14942edd
FC
636 if ( (ptr = strrchr(argv[0],'/')) == NULL )
637 ptr=argv[0];
638 else
639 ++ptr;
feec68a0
A
640 ::programname = ptr;
641
642 // extract commandline parameters
c2afddd8 643 REList* tail = head = 0;
feec68a0
A
644 opterr = 0;
645 while ( (option = getopt( argc, argv, "ac:C:d:E:e:F:f:Hnp:P:sv" )) != -1 ) {
646 switch ( option ) {
647 case 'a':
648 ::iamalive = ! ::iamalive;
649 break;
650 case 'C':
651 if ( optarg && *optarg ) {
9dca980d
AJ
652 if ( copyDirPath ) xfree( (void*) copyDirPath );
653 copyDirPath = xstrdup(optarg);
654 assert(copyDirPath);
feec68a0
A
655 }
656 break;
657 case 'c':
b2d7d4be
AJ
658 if ( !optarg || !*optarg ) {
659 fprintf( stderr, "%c requires a regex pattern argument!\n", option );
24885773 660 exit(EXIT_FAILURE);
feec68a0 661 }
b2d7d4be
AJ
662 if ( *conffile ) xfree((void*) conffile);
663 conffile = xstrdup(optarg);
664 assert(conffile);
feec68a0
A
665 break;
666
667 case 'd':
052aecef
AJ
668 if ( !optarg || !*optarg ) {
669 fprintf( stderr, "%c expects a mask parameter. Debug disabled.\n", option );
670 ::debugFlag = 0;
671 } else
672 ::debugFlag = (strtoul(optarg, NULL, 0) & 0xFFFFFFFF);
feec68a0
A
673 break;
674
675 case 'E':
676 case 'e':
b2d7d4be
AJ
677 if ( !optarg || !*optarg ) {
678 fprintf( stderr, "%c requires a regex pattern argument!\n", option );
24885773 679 exit(EXIT_FAILURE);
b2d7d4be 680 }
c2afddd8
AJ
681 if ( head == 0 )
682 tail = head = new REList( optarg, option=='E' );
683 else {
684 tail->next = new REList( optarg, option=='E' );
685 tail = tail->next;
feec68a0
A
686 }
687 break;
688
689 case 'f':
b2d7d4be
AJ
690 if ( !optarg || !*optarg ) {
691 fprintf( stderr, "%c requires a filename argument!\n", option );
24885773 692 exit(EXIT_FAILURE);
b2d7d4be 693 }
feec68a0
A
694 if ( (rfile = fopen( optarg, "r" )) != NULL ) {
695 unsigned long lineno = 0;
eb1f6bfa 696#define LINESIZE 512
feec68a0
A
697 char line[LINESIZE];
698 while ( fgets( line, LINESIZE, rfile ) != NULL ) {
14942edd 699 ++lineno;
feec68a0
A
700 int len = strlen(line)-1;
701 if ( len+2 >= LINESIZE ) {
702 fprintf( stderr, "%s:%lu: line too long, sorry.\n",
703 optarg, lineno );
24885773 704 exit(EXIT_FAILURE);
feec68a0
A
705 }
706
707 // remove trailing line breaks
f412b2d6
FC
708 while ( len > 0 && ( line[len] == '\n' || line[len] == '\r' ) ) {
709 line[len] = '\0';
710 --len;
711 }
feec68a0 712
c2afddd8
AJ
713 // insert into list of expressions
714 if ( head == 0 ) tail = head = new REList(line,option=='F');
715 else {
716 tail->next = new REList(line,option=='F');
717 tail = tail->next;
feec68a0
A
718 }
719 }
720 fclose(rfile);
721 } else
722 fprintf( stderr, "unable to open %s: %s\n", optarg, strerror(errno));
723 break;
724
725 case 'H':
726 ::envelope = ! ::envelope;
727 break;
728 case 'n':
729 ::no_fork = ! ::no_fork;
730 break;
731 case 'p':
b2d7d4be
AJ
732 if ( !optarg || !*optarg ) {
733 fprintf( stderr, "%c requires a port argument!\n", option );
24885773 734 exit(EXIT_FAILURE);
b2d7d4be 735 }
feec68a0
A
736 colon = strchr( optarg, ':' );
737 if ( colon == 0 ) {
738 // no colon, only look at host
739
740 // fix: see if somebody just put in there a port (no periods)
741 // give port number precedence over host names
742 port = checkForPortOnly( optarg );
743 if ( port == -1 ) {
744 // assume that main() did set the default port
9dca980d 745 if ( convertHostname(optarg,serverHostIp) == -1 ) {
feec68a0 746 fprintf( stderr, "unable to resolve host %s!\n", optarg );
24885773 747 exit(EXIT_FAILURE);
feec68a0
A
748 }
749 } else {
750 // assume that main() did set the default host
9dca980d 751 serverHostPort = port;
feec68a0
A
752 }
753 } else {
754 // colon used, port is extra
14942edd
FC
755 *colon = 0;
756 ++colon;
9dca980d 757 if ( convertHostname(optarg,serverHostIp) == -1 ) {
feec68a0 758 fprintf( stderr, "unable to resolve host %s!\n", optarg );
24885773 759 exit(EXIT_FAILURE);
feec68a0 760 }
9dca980d 761 if ( convertPortname(colon,serverHostPort) == -1 ) {
feec68a0 762 fprintf( stderr, "unable to resolve port %s!\n", colon );
24885773 763 exit(EXIT_FAILURE);
feec68a0
A
764 }
765 }
766 break;
767 case 'P':
b2d7d4be
AJ
768 if ( !optarg || !*optarg ) {
769 fprintf( stderr, "%c requires a mode argument!\n", option );
24885773 770 exit(EXIT_FAILURE);
b2d7d4be 771 }
feec68a0
A
772 ::purgeMode = ( strtol( optarg, 0, 0 ) & 0x07 );
773 break;
774 case 's':
775 showme=1;
776 break;
777 case 'v':
778 ::verbose = ! ::verbose;
779 break;
780 case '?':
781 default:
782 helpMe();
24885773 783 exit(EXIT_FAILURE);
feec68a0
A
784 }
785 }
786
787 // adjust
59a09b98 788 if ( ! isatty(fileno(stdout)) || (::debugFlag & 0x01) ) ::iamalive = false;
feec68a0
A
789 if ( head == 0 ) {
790 fputs( "There was no regular expression defined. If you intend\n", stderr );
791 fputs( "to match all possible URLs, use \"-e .\" instead.\n", stderr );
24885773 792 exit(EXIT_FAILURE);
eb1f6bfa 793 }
feec68a0
A
794
795 // postcondition: head != 0
796 assert( head != 0 );
797
798 // make sure that the copy out directory is there and accessible
9dca980d
AJ
799 if ( copyDirPath && *copyDirPath )
800 if ( assert_copydir( copyDirPath ) != 0 ) exit(1);
feec68a0
A
801
802 // show results
803 if ( showme ) {
6d7a7410
FC
804 printf( "#\n# Currently active values for %s:\n",
805 ::programname);
feec68a0 806 printf( "# Debug level : " );
d83197e3 807 if ( ::debugFlag ) printf( "%#6.4x", ::debugFlag );
feec68a0
A
808 else printf( "production level" ); // printf omits 0x prefix for 0!
809 printf( " + %s mode", ::no_fork ? "linear" : "parallel" );
810 puts( ::verbose ? " + extra verbosity" : "" );
811
812 printf( "# Copy-out directory: %s ",
9dca980d
AJ
813 copyDirPath ? copyDirPath : "copy-out mode disabled" );
814 if ( copyDirPath )
feec68a0
A
815 printf( "(%s HTTP header)\n", ::envelope ? "prepend" : "no" );
816 else
817 puts("");
818
819 printf( "# Squid config file : %s\n", conffile );
820 printf( "# Cacheserveraddress: %s:%u\n",
9dca980d 821 inet_ntoa( serverHostIp ), ntohs( serverHostPort ) );
feec68a0
A
822 printf( "# purge mode : 0x%02x\n", ::purgeMode );
823 printf( "# Regular expression: " );
824
825 unsigned count(0);
826 for ( tail = head; tail != NULL; tail = tail->next ) {
14942edd
FC
827 if ( count++ )
828 printf( "#%22u", count );
eb1f6bfa 829#if defined(LINUX) && putc==_IO_putc
feec68a0
A
830 // I HATE BROKEN LINUX HEADERS!
831 // purge.o(.text+0x1040): undefined reference to `_IO_putc'
832 // If your compilation breaks here, remove the undefinition
833#undef putc
eb1f6bfa 834#endif
feec68a0
A
835 else putchar('1');
836 printf( " \"%s\"\n", tail->data );
837 }
838 puts( "#" );
eb1f6bfa 839 }
feec68a0 840 fflush( stdout );
eb1f6bfa
AJ
841}
842
843extern "C" {
844
feec68a0
A
845 static
846 void
847 exiter( void ) {
848 if ( ::term_flag ) psignal( ::term_flag, "received signal" );
849 delete[] ::linebuffer;
850 if ( ::reminder ) {
851 fputs(
852 "WARNING! Caches files were removed. Please shut down your cache, remove\n"
853 "your swap.state files and restart your cache again, i.e. effictively do\n"
854 "a slow rebuild your cache! Otherwise your squid *will* choke!\n", stderr );
855 }
856 }
eb1f6bfa 857
feec68a0
A
858 static
859 void
860 handler( int signo ) {
861 ::term_flag = signo;
862 if ( getpid() == getpgrp() ) kill( -getpgrp(), signo );
24885773 863 exit(EXIT_FAILURE);
feec68a0 864 }
eb1f6bfa
AJ
865
866} // extern "C"
867
868static
869int
870makelinebuffered( FILE* fp, const char* fn = 0 )
feec68a0
A
871// purpose: make the given FILE line buffered
872// paramtr: fp (IO): file pointer which to put into line buffer mode
873// fn (IN): name of file to print in case of error
874// returns: 0 is ok, -1 to indicate an error
875// warning: error messages will already be printed
eb1f6bfa 876{
feec68a0
A
877 if ( setvbuf( fp, 0, _IOLBF, 0 ) == 0 ) {
878 // ok
879 return 0;
880 } else {
881 // error
882 fprintf( stderr, "unable to make \"%s\" line buffered: %s\n",
883 fn ? fn : "", strerror(errno) );
884 return -1;
885 }
eb1f6bfa
AJ
886}
887
888int
889main( int argc, char* argv[] )
890{
feec68a0 891 // setup variables
c2afddd8 892 REList* list = 0;
23cf13da 893 char* conffile = xstrdup(DEFAULT_CONFIG_FILE);
feec68a0
A
894 serverPort = htons(DEFAULTPORT);
895 if ( convertHostname(DEFAULTHOST,serverHost) == -1 ) {
896 fprintf( stderr, "unable to resolve host %s!\n", DEFAULTHOST );
52af54dd 897 exit(EXIT_FAILURE);
feec68a0
A
898 }
899
900 // setup line buffer
901 ::linebuffer = new char[ ::buffersize ];
902 assert( ::linebuffer != 0 );
903
904 // parse commandline
905 puts( "### Use at your own risk! No guarantees whatsoever. You were warned. ###");
906 parseCommandline( argc, argv, list, conffile, ::copydir,
907 serverHost, serverPort );
908
909 // prepare execution
910 if ( atexit( exiter ) != 0 ||
911 Signal( SIGTERM, handler, true ) == SIG_ERR ||
912 Signal( SIGINT, handler, true ) == SIG_ERR ||
913 Signal( SIGHUP, handler, true ) == SIG_ERR ) {
914 perror( "unable to install signal/exit function" );
24885773 915 exit(EXIT_FAILURE);
feec68a0
A
916 }
917
918 // try to read squid.conf file to determine all cache_dir locations
919 CacheDirVector cdv(0);
59a09b98 920 if ( readConfigFile( cdv, conffile, debugFlag ? stderr : 0 ) > 0 ) {
feec68a0
A
921 // there are some valid cache_dir entries.
922 // unless forking was forbidden by cmdline option,
923 // for a process for each cache_dir entry to remove files.
924
925 if ( ::no_fork || cdv.size() == 1 ) {
926 // linear mode, one cache_dir after the next
927 for ( CacheDirVector::iterator i = cdv.begin(); i != cdv.end(); ++i ) {
928 // execute OR complain
929 if ( ! dirlevel(i->base,list) )
930 fprintf( stderr, "program terminated due to error: %s",
931 strerror(errno) );
6e2aefad 932 xfree((void*) i->base);
feec68a0
A
933 }
934 } else {
935 // parallel mode, all cache_dir in parallel
936 pid_t* child = new pid_t[ cdv.size() ];
937
938 // make stdout/stderr line bufferd
939 makelinebuffered( stdout, "stdout" );
940 makelinebuffered( stderr, "stderr" );
941
942 // make parent process group leader for easier killings
943 if ( setpgid(getpid(), getpid()) != 0 ) {
944 perror( "unable to set process group leader" );
24885773 945 exit(EXIT_FAILURE);
feec68a0
A
946 }
947
948 // -a is mutually exclusive with fork mode
949 if ( ::iamalive ) {
950 puts( "# i-am-alive flag incompatible with fork mode, resetting" );
951 ::iamalive = false;
952 }
953
2ccf2eb2 954 for ( size_t i=0; i < cdv.size(); ++i ) {
feec68a0
A
955 if ( getpid() == getpgrp() ) {
956 // only parent == group leader may fork off new processes
957 if ( (child[i]=fork()) < 0 ) {
958 // fork error, this is bad!
959 perror( "unable to fork" );
960 kill( -getpgrp(), SIGTERM );
24885773 961 exit(EXIT_FAILURE);
feec68a0
A
962 } else if ( child[i] == 0 ) {
963 // child mode
964 // execute OR complain
965 if ( ! dirlevel(cdv[i].base,list) )
966 fprintf( stderr, "program terminated due to error: %s\n",
967 strerror(errno) );
6e2aefad 968 xfree((void*) cdv[i].base);
24885773 969 exit(EXIT_SUCCESS);
feec68a0
A
970 } else {
971 // parent mode
59a09b98 972 if ( ::debugFlag ) printf( "forked child %d\n", (int) child[i] );
feec68a0
A
973 }
974 }
975 }
976
977 // collect the garbase
978 pid_t temp;
979 int status;
2ccf2eb2 980 for ( size_t i=0; i < cdv.size(); ++i ) {
feec68a0
A
981 while ( (temp=waitpid( (pid_t)-1, &status, 0 )) == -1 )
982 if ( errno == EINTR ) continue;
59a09b98 983 if ( ::debugFlag ) printf( "collected child %d\n", (int) temp );
feec68a0
A
984 }
985 delete[] child;
986 }
eb1f6bfa 987 } else {
feec68a0 988 fprintf( stderr, "no cache_dir or error accessing \"%s\"\n", conffile );
eb1f6bfa 989 }
feec68a0
A
990
991 // clean up
6e2aefad
HN
992 if ( copydir ) xfree( (void*) copydir );
993 xfree((void*) conffile);
feec68a0 994 delete list;
24885773 995 return EXIT_SUCCESS;
eb1f6bfa 996}
f53969cc 997