]> git.ipfire.org Git - thirdparty/squid.git/blob - tools/purge/purge.cc
Removed squid-old.h
[thirdparty/squid.git] / tools / purge / purge.cc
1 //
2 // $Id$
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__)) && !defined(__clang__)
97 #pragma implementation
98 #endif
99
100 #include "squid.h"
101 #include "util.h"
102
103 #include <stdarg.h>
104 #include <stdio.h>
105 #include <dirent.h>
106 #include <string.h>
107 #include <sys/stat.h>
108 #include <sys/wait.h>
109 #include <fcntl.h>
110 #include <unistd.h>
111 #include <stdlib.h>
112 #include <limits.h>
113 #include <signal.h>
114 #include <errno.h>
115
116 #if HAVE_SIGINFO_H
117 #include <siginfo.h>
118 #endif
119
120 #include <netinet/in.h>
121 #include <netinet/tcp.h>
122 #include <arpa/inet.h>
123 #include <netdb.h>
124
125 #include "convert.hh"
126 #include "socket.hh"
127 #include "signal.hh"
128 #include "squid-tlv.hh"
129 #include "copyout.hh"
130 #include "conffile.hh"
131
132 #ifndef DEFAULTHOST
133 #define DEFAULTHOST "localhost"
134 #endif // DEFAULTHOST
135
136 #ifndef DEFAULTPORT
137 #define DEFAULTPORT 3128
138 #endif // DEFAULTPORT
139
140 volatile sig_atomic_t term_flag = 0; // 'terminate' is a gcc 2.8.x internal...
141 char* linebuffer = 0;
142 size_t buffersize = 16834;
143 static char* copydir = 0;
144 static unsigned debugFlag = 0;
145 static unsigned purgeMode = 0;
146 static bool iamalive = false;
147 static bool reminder = false;
148 static bool verbose = false;
149 static bool envelope = false;
150 static bool no_fork = false;
151 static const char* programname = 0;
152 static const char* RCS_ID = "$Id$";
153
154 // ----------------------------------------------------------------------
155
156 struct REList {
157 REList( const char* what, bool doCase );
158 ~REList();
159 bool match( const char* check ) const;
160
161 REList* next;
162 const char* data;
163 regex_t rexp;
164 };
165
166 REList::REList( const char* what, bool doCase )
167 :next(0),data(xstrdup(what))
168 {
169 int result = regcomp( &rexp, what,
170 REG_EXTENDED | REG_NOSUB | (doCase ? 0 : REG_ICASE) );
171 if ( result != 0 ) {
172 char buffer[256];
173 regerror( result, &rexp, buffer, 256 );
174 fprintf( stderr, "unable to compile re \"%s\": %s\n", what, buffer );
175 exit(1);
176 }
177 }
178
179 REList::~REList()
180 {
181 if ( next ) delete next;
182 if ( data ) xfree((void*) data);
183 regfree(&rexp);
184 }
185
186 bool
187 REList::match( const char* check ) const
188 {
189 int result = regexec( &rexp, check, 0, 0, 0 );
190 if ( result != 0 && result != REG_NOMATCH ) {
191 char buffer[256];
192 regerror( result, &rexp, buffer, 256 );
193 fprintf( stderr, "unable to execute re \"%s\"\n+ on line \"%s\": %s\n",
194 data, check, buffer );
195 exit(1);
196 }
197 return ( result == 0 );
198 }
199
200 // ----------------------------------------------------------------------
201
202 char*
203 concat( const char* start, ... )
204 // purpose: concatinate an arbitrary number of C strings.
205 // paramtr: start (IN): first C string
206 // ... (IN): further C strings, terminated with a NULL pointer
207 // returns: memory allocated via new(), containing the concatinated string.
208 {
209 va_list ap;
210 const char* s;
211
212 // first run: determine size
213 unsigned size = strlen(start)+1;
214 va_start( ap, start );
215 while ( (s=va_arg(ap,const char*)) != NULL ) size += strlen(s ? s : "");
216 va_end(ap);
217
218 // allocate
219 char* result = new char[size];
220 if ( result == 0 ) {
221 perror( "string memory allocation" );
222 exit(1);
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;
232 }
233
234 bool
235 isxstring( const char* s, size_t testlen )
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
240 {
241 if ( strlen(s) != testlen ) return false;
242
243 size_t i=0;
244 while ( i<testlen && isxdigit(s[i]) )
245 ++i;
246 return (i==testlen);
247 }
248
249 inline
250 int
251 log_output( const char* fn, int code, long size, const char* url )
252 {
253 return printf( "%s %3d %8ld %s\n", fn, code, size, url );
254 }
255
256 static
257 int
258 log_extended( const char* fn, int code, long size, const SquidMetaList* meta )
259 {
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;
266 for ( int j=0; j<16; ++j, ++s ) {
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 {
272 snprintf( md5, sizeof(md5), "%-32s", "(no_md5_data_available)" );
273 }
274
275 char timeb[64];
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) );
280 snprintf( timeb, sizeof(timeb), "%08lx %08lx %08lx %08lx %04x %5hu ",
281 (unsigned long)temp.timestamp, (unsigned long)temp.lastref,
282 (unsigned long)temp.expires, (unsigned long)temp.lastmod, temp.flags, temp.refcount );
283 } else if ( meta && (findings = meta->search( STORE_META_STD_LFS )) ) {
284 StoreMetaStdLFS temp;
285 // make data aligned, avoid SIGBUS on RISC machines (ARGH!)
286 memcpy( &temp, findings->data, sizeof(StoreMetaStd) );
287 snprintf( timeb, sizeof(timeb), "%08lx %08lx %08lx %08lx %04x %5hu ",
288 (unsigned long)temp.timestamp, (unsigned long)temp.lastref,
289 (unsigned long)temp.expires, (unsigned long)temp.lastmod, temp.flags, temp.refcount );
290 } else {
291 unsigned long ul = ULONG_MAX; // Match type of StoreMetaTLV fields
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);
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 );
303 }
304 }
305
306 // o.k., this is pure lazyness...
307 static struct in_addr serverHost;
308 static unsigned short serverPort;
309
310 bool
311 action( int fd, size_t metasize,
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
315 // metasize (IN): offset into data portion of file (meta data size)
316 // fn (IN): name of the object file
317 // url (IN): URL string stored in the object file
318 // meta (IN): list containing further meta data
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
326 {
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) );
330 int status = 0;
331
332 // if we want to copy out the file, do that first of all.
333 if ( ::copydir && *copydir && size > 0 )
334 copy_out( st.st_size, metasize, ::debugFlag,
335 fn, url, ::copydir, ::envelope );
336
337 // do we need to PURGE the file, yes, if purgemode bit#0 was set.
338 if ( ::purgeMode & 0x01 ) {
339 unsigned long bufsize = strlen(url) + strlen(schablone) + 4;
340 char* buffer = new char[bufsize];
341
342 snprintf( buffer, bufsize, schablone, url );
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
350 int size = strlen(buffer);
351 if ( write( sockfd, buffer, size ) != size ) {
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 );
359 if ( read( sockfd, buffer, bufsize ) < 1 ) {
360 // error while reading squid's answer
361 fprintf( stderr, "unable to read answer: %s\n", strerror(errno) );
362 close(sockfd);
363 delete[] buffer;
364 return false;
365 }
366 close(sockfd);
367 status = strtol(buffer+8,0,10);
368 delete[] buffer;
369 }
370
371 // log the output of our operation
372 bool flag = true;
373 if ( ::verbose ) flag = ( log_extended( fn, status, size, &meta ) >= 0 );
374 else flag = ( log_output( fn, status, size, url ) >= 0 );
375
376 // remove the file, if purgemode bit#1, and HTTP result status 404).
377 if ( (::purgeMode & 0x02) && status == 404 ) {
378 reminder = true;
379 if ( unlink(fn) == -1 )
380 // error while unlinking file, this may happen due to the cache
381 // unlinking a file while it is still in the readdir() cache of purge.
382 fprintf( stderr, "WARNING: unable to unlink %s: %s\n",
383 fn, strerror(errno) );
384 }
385
386 return flag;
387 }
388
389 bool
390 match( const char* fn, const REList* list )
391 // purpose: do something with the given cache content filename
392 // paramtr: fn (IN): filename of cache file
393 // returns: true for successful action, false otherwise.
394 // warning: only return false, if you want the loop to terminate!
395 {
396 static const size_t addon = sizeof(unsigned char) + sizeof(unsigned int);
397 bool flag = true;
398
399 if ( debugFlag & 0x01 ) fprintf( stderr, "# [3] %s\n", fn );
400 int fd = open( fn, O_RDONLY );
401 if ( fd != -1 ) {
402 if ( read(fd,::linebuffer,::buffersize-1) > 60 ) {
403 ::linebuffer[ ::buffersize-1 ] = '\0'; // force-terminate string
404
405 // check the offset into the start of object data. The offset is
406 // stored in a host endianess after the first byte.
407 unsigned int datastart;
408 memcpy( &datastart, ::linebuffer + 1, sizeof(unsigned int) );
409 if ( datastart > ::buffersize - addon - 1 ) {
410 // check offset into server reply header (start of cache data).
411 fputs( "WARNING: Using a truncated URL string.\n", stderr );
412 datastart = ::buffersize - addon - 1;
413 }
414
415 // NEW: Parse squid meta data, which is a kind of linked list
416 // flattened out into a file byte stream. Somewhere within is
417 // the URL as part of the list. First, gobble all meta data.
418 unsigned int offset = addon;
419 SquidMetaList meta;
420 while ( offset + addon <= datastart ) {
421 unsigned int size = 0;
422 memcpy( &size, linebuffer+offset+sizeof(char), sizeof(unsigned int) );
423 meta.append( SquidMetaType(*(linebuffer+offset)),
424 size, linebuffer+offset+addon );
425 offset += ( addon + size );
426 }
427
428 // Now extract the key URL from the meta data.
429 const SquidTLV* urlmeta = meta.search( STORE_META_URL );
430 if ( urlmeta ) {
431 // found URL in meta data. Try to process the URL
432 if ( list == 0 )
433 flag = action( fd, datastart, fn, (char*) urlmeta->data, meta );
434 else {
435 REList* head = (REList*) list; // YUCK!
436 while ( head != 0 ) {
437 if ( head->match( (char*) urlmeta->data ) ) break;
438 head = head->next;
439 }
440 if ( head != 0 )
441 flag = action( fd, datastart, fn, (char*) urlmeta->data, meta );
442 else flag = true;
443 }
444 }
445
446 // "meta" will be deleted when exiting from this block
447 } else {
448 // weird file, FIXME: stat() it!
449 struct stat st;
450 long size = ( fstat(fd,&st) == -1 ? -1 : st.st_size );
451 if ( ::verbose ) flag = ( log_extended( fn, -1, size, 0 ) >= 0 );
452 else flag = ( log_output( fn, -1, size, "strange file" ) >= 0 );
453
454 if ( (::purgeMode & 0x04) ) {
455 reminder = true;
456 if ( unlink(fn) == -1 )
457 // error while unlinking file, this may happen due to the cache
458 // unlinking a file while it is in the readdir() cache of purge.
459 fprintf( stderr, "WARNING: unable to unlink %s: %s\n",
460 fn, strerror(errno) );
461 }
462 }
463 close(fd);
464 } else {
465 // error while opening file, this may happen due to the cache
466 // unlinking a file while it is still in the readdir() cache of purge.
467 fprintf( stderr, "WARNING: open \"%s\": %s\n", fn, strerror(errno) );
468 }
469
470 return flag;
471 }
472
473 bool
474 filelevel( const char* directory, const REList* list )
475 // purpose: from given starting point, look for squid xxxxxxxx files.
476 // example: "/var/spool/cache/08/7F" as input, do action over files
477 // paramtr: directory (IN): starting point
478 // list (IN): list of rexps to match URLs against
479 // returns: true, if every subdir && action was successful.
480 {
481 dirent_t * entry;
482 if ( debugFlag & 0x01 )
483 fprintf( stderr, "# [2] %s\n", directory );
484
485 DIR* dir = opendir( directory );
486 if ( dir == NULL ) {
487 fprintf( stderr, "unable to open directory \"%s\": %s\n",
488 directory, strerror(errno) );
489 return false;
490 }
491
492 // display a rotating character as "i am alive" signal (slows purge).
493 if ( ::iamalive ) {
494 static char alivelist[4][3] = { "\\\b", "|\b", "/\b", "-\b" };
495 static unsigned short alivecount = 0;
496 assert( write( STDOUT_FILENO, alivelist[alivecount++ & 3], 2 ) == 2 );
497 }
498
499 bool flag = true;
500 while ( (entry=readdir(dir)) && flag ) {
501 if ( isxstring(entry->d_name,8) ) {
502 char* name = concat( directory, "/", entry->d_name, 0 );
503 flag = match( name, list );
504 delete[] name;
505 }
506 }
507
508 closedir(dir);
509 return flag;
510 }
511
512 bool
513 dirlevel( const char* dirname, const REList* list, bool level=false )
514 // purpose: from given starting point, look for squid 00..FF directories.
515 // paramtr: dirname (IN): starting point
516 // list (IN): list of rexps to match URLs against
517 // level (IN): false==toplevel, true==1st level
518 // example: "/var/spool/cache", false as input, traverse subdirs w/ action.
519 // example: "/var/spool/cache/08", true as input, traverse subdirs w/ action.
520 // returns: true, if every subdir && action was successful.
521 // warning: this function is once-recursive, no deeper.
522 {
523 dirent_t* entry;
524 if ( debugFlag & 0x01 )
525 fprintf( stderr, "# [%d] %s\n", (level ? 1 : 0), dirname );
526
527 DIR* dir = opendir( dirname );
528 if ( dir == NULL ) {
529 fprintf( stderr, "unable to open directory \"%s\": %s\n",
530 dirname, strerror(errno) );
531 return false;
532 }
533
534 bool flag = true;
535 while ( (entry=readdir(dir)) && flag ) {
536 if ( strlen(entry->d_name) == 2 &&
537 isxdigit(entry->d_name[0]) &&
538 isxdigit(entry->d_name[1]) ) {
539 char* name = concat( dirname, "/", entry->d_name, 0 );
540 flag = level ? filelevel( name, list ) : dirlevel( name, list, true );
541 delete[] name;
542 }
543 }
544
545 closedir(dir);
546 return flag;
547 }
548
549 int
550 checkForPortOnly( const char* optarg )
551 // purpose: see if somebody just put in a port instead of a hostname
552 // paramtr: optarg (IN): argument from commandline
553 // returns: 0..65535 is the valid port number in network byte order,
554 // -1 if not a port
555 {
556 // if there is a period in there, it must be a valid hostname
557 if ( strchr( optarg, '.' ) != 0 ) return -1;
558
559 // if it is just a number between 0 and 65535, it must be a port
560 char* errstr = 0;
561 unsigned long result = strtoul( optarg, &errstr, 0 );
562 if ( result < 65536 && errstr != optarg ) return htons(result);
563
564 #if 0
565 // one last try, test for a symbolical service name
566 struct servent* service = getservbyname( optarg, "tcp" );
567 return service ? service->s_port : -1;
568 #else
569 return -1;
570 #endif
571 }
572
573 void
574 helpMe( void )
575 // purpuse: write help message and exit
576 {
577 printf( "\n%s\nUsage:\t%s\t[-a] [-c cf] [-d l] [-(f|F) fn | -(e|E) re] "
578 "[-p h[:p]]\n\t\t[-P #] [-s] [-v] [-C dir [-H]] [-n]\n\n",
579 ::RCS_ID, ::programname );
580 printf(
581 " -a\tdisplay a little rotating thingy to indicate that I am alive (tty only).\n"
582 " -c c\tsquid.conf location, default \"%s\".\n"
583 " -C dir\tbase directory for content extraction (copy-out mode).\n"
584 " -d l\tdebug level, an OR of different debug options.\n"
585 " -e re\tsingle regular expression per -e instance (use quotes!).\n"
586 " -E re\tsingle case sensitive regular expression like -e.\n"
587 " -f fn\tname of textfile containing one regular expression per line.\n"
588 " -F fn\tname of textfile like -f containing case sensitive REs.\n"
589 " -H\tprepend HTTP reply header to destination files in copy-out mode.\n"
590 " -n\tdo not fork() when using more than one cache_dir.\n"
591 " -p h:p\tcache runs on host h and optional port p, default is %s:%u.\n"
592 " -P #\tif 0, just print matches; otherwise OR the following purge modes:\n"
593 "\t 0x01 really send PURGE to the cache.\n"
594 "\t 0x02 remove all caches files reported as 404 (not found).\n"
595 "\t 0x04 remove all weird (inaccessible or too small) cache files.\n"
596 "\t0 and 1 are recommended - slow rebuild your cache with other modes.\n"
597 " -s\tshow all options after option parsing, but before really starting.\n"
598 " -v\tshow more information about the file, e.g. MD5, timestamps and flags.\n"
599 "\n", DEFAULT_SQUID_CONF, DEFAULTHOST, DEFAULTPORT );
600
601 }
602
603 void
604 parseCommandline( int argc, char* argv[], REList*& head,
605 char*& conffile, char*& copydir,
606 struct in_addr& serverHost, unsigned short& serverPort )
607 // paramtr: argc: see ::main().
608 // argv: see ::main().
609 // returns: Does terminate the program on errors!
610 // purpose: suck in any commandline options, and set the global vars.
611 {
612 int option, port, showme = 0;
613 char* ptr, *colon;
614 FILE* rfile;
615
616 // program basename
617 if ( (ptr = strrchr(argv[0],'/')) == NULL )
618 ptr=argv[0];
619 else
620 ++ptr;
621 ::programname = ptr;
622
623 // extract commandline parameters
624 REList* tail = head = 0;
625 opterr = 0;
626 while ( (option = getopt( argc, argv, "ac:C:d:E:e:F:f:Hnp:P:sv" )) != -1 ) {
627 switch ( option ) {
628 case 'a':
629 ::iamalive = ! ::iamalive;
630 break;
631 case 'C':
632 if ( optarg && *optarg ) {
633 if ( copydir ) xfree( (void*) copydir );
634 assert( (copydir = xstrdup(optarg)) );
635 }
636 break;
637 case 'c':
638 if ( optarg && *optarg ) {
639 if ( *conffile ) xfree((void*) conffile );
640 assert( (conffile = xstrdup(optarg)) );
641 }
642 break;
643
644 case 'd':
645 ::debugFlag = strtoul( optarg, 0, 0 );
646 break;
647
648 case 'E':
649 case 'e':
650 if ( head == 0 ) tail = head = new REList( optarg, option=='E' );
651 else {
652 tail->next = new REList( optarg, option=='E' );
653 tail = tail->next;
654 }
655 break;
656
657 case 'f':
658 if ( (rfile = fopen( optarg, "r" )) != NULL ) {
659 unsigned long lineno = 0;
660 #define LINESIZE 512
661 char line[LINESIZE];
662 while ( fgets( line, LINESIZE, rfile ) != NULL ) {
663 ++lineno;
664 int len = strlen(line)-1;
665 if ( len+2 >= LINESIZE ) {
666 fprintf( stderr, "%s:%lu: line too long, sorry.\n",
667 optarg, lineno );
668 exit(1);
669 }
670
671 // remove trailing line breaks
672 while ( len > 0 && ( line[len] == '\n' || line[len] == '\r' ) ) {
673 line[len] = '\0';
674 --len;
675 }
676
677 // insert into list of expressions
678 if ( head == 0 ) tail = head = new REList(line,option=='F');
679 else {
680 tail->next = new REList(line,option=='F');
681 tail = tail->next;
682 }
683 }
684 fclose(rfile);
685 } else
686 fprintf( stderr, "unable to open %s: %s\n", optarg, strerror(errno));
687 break;
688
689 case 'H':
690 ::envelope = ! ::envelope;
691 break;
692 case 'n':
693 ::no_fork = ! ::no_fork;
694 break;
695 case 'p':
696 colon = strchr( optarg, ':' );
697 if ( colon == 0 ) {
698 // no colon, only look at host
699
700 // fix: see if somebody just put in there a port (no periods)
701 // give port number precedence over host names
702 port = checkForPortOnly( optarg );
703 if ( port == -1 ) {
704 // assume that main() did set the default port
705 if ( convertHostname(optarg,serverHost) == -1 ) {
706 fprintf( stderr, "unable to resolve host %s!\n", optarg );
707 exit(1);
708 }
709 } else {
710 // assume that main() did set the default host
711 serverPort = port;
712 }
713 } else {
714 // colon used, port is extra
715 *colon = 0;
716 ++colon;
717 if ( convertHostname(optarg,serverHost) == -1 ) {
718 fprintf( stderr, "unable to resolve host %s!\n", optarg );
719 exit(1);
720 }
721 if ( convertPortname(colon,serverPort) == -1 ) {
722 fprintf( stderr, "unable to resolve port %s!\n", colon );
723 exit(1);
724 }
725 }
726 break;
727 case 'P':
728 ::purgeMode = ( strtol( optarg, 0, 0 ) & 0x07 );
729 break;
730 case 's':
731 showme=1;
732 break;
733 case 'v':
734 ::verbose = ! ::verbose;
735 break;
736 case '?':
737 default:
738 helpMe();
739 exit(1);
740 }
741 }
742
743 // adjust
744 if ( ! isatty(fileno(stdout)) || (::debugFlag & 0x01) ) ::iamalive = false;
745 if ( head == 0 ) {
746 fputs( "There was no regular expression defined. If you intend\n", stderr );
747 fputs( "to match all possible URLs, use \"-e .\" instead.\n", stderr );
748 exit(1);
749 }
750
751 // postcondition: head != 0
752 assert( head != 0 );
753
754 // make sure that the copy out directory is there and accessible
755 if ( copydir && *copydir )
756 if ( assert_copydir( copydir ) != 0 ) exit(1);
757
758 // show results
759 if ( showme ) {
760 printf( "#\n# Currently active values for %s:\n# %s\n",
761 ::programname, ::RCS_ID );
762 printf( "# Debug level : " );
763 if ( ::debugFlag ) printf( "%#6.4x", ::debugFlag );
764 else printf( "production level" ); // printf omits 0x prefix for 0!
765 printf( " + %s mode", ::no_fork ? "linear" : "parallel" );
766 puts( ::verbose ? " + extra verbosity" : "" );
767
768 printf( "# Copy-out directory: %s ",
769 copydir ? copydir : "copy-out mode disabled" );
770 if ( copydir )
771 printf( "(%s HTTP header)\n", ::envelope ? "prepend" : "no" );
772 else
773 puts("");
774
775 printf( "# Squid config file : %s\n", conffile );
776 printf( "# Cacheserveraddress: %s:%u\n",
777 inet_ntoa( serverHost ), ntohs( serverPort ) );
778 printf( "# purge mode : 0x%02x\n", ::purgeMode );
779 printf( "# Regular expression: " );
780
781 unsigned count(0);
782 for ( tail = head; tail != NULL; tail = tail->next ) {
783 if ( count++ )
784 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 = xstrdup( 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, debugFlag ? 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 xfree((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 ( size_t 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 xfree((void*) cdv[i].base);
925 return 0;
926 } else {
927 // parent mode
928 if ( ::debugFlag ) 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 ( size_t i=0; i < cdv.size(); ++i ) {
937 while ( (temp=waitpid( (pid_t)-1, &status, 0 )) == -1 )
938 if ( errno == EINTR ) continue;
939 if ( ::debugFlag ) 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 ) xfree( (void*) copydir );
949 xfree((void*) conffile);
950 delete list;
951 return 0;
952 }