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