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