]> git.ipfire.org Git - thirdparty/squid.git/blob - tools/purge/purge.cc
squidpurge: display friendly errors on missing command line options
[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 fprintf( stderr, "%c requires a regex pattern argument!\n", option );
657 exit(1);
658 }
659 if ( *conffile ) xfree((void*) conffile);
660 conffile = xstrdup(optarg);
661 assert(conffile);
662 break;
663
664 case 'd':
665 ::debugFlag = optarg ? 0 : strtoul( optarg, 0, 0 );
666 break;
667
668 case 'E':
669 case 'e':
670 if ( !optarg || !*optarg ) {
671 fprintf( stderr, "%c requires a regex pattern argument!\n", option );
672 exit(1);
673 }
674 if ( head == 0 )
675 tail = head = new REList( optarg, option=='E' );
676 else {
677 tail->next = new REList( optarg, option=='E' );
678 tail = tail->next;
679 }
680 break;
681
682 case 'f':
683 if ( !optarg || !*optarg ) {
684 fprintf( stderr, "%c requires a filename argument!\n", option );
685 exit(1);
686 }
687 if ( (rfile = fopen( optarg, "r" )) != NULL ) {
688 unsigned long lineno = 0;
689 #define LINESIZE 512
690 char line[LINESIZE];
691 while ( fgets( line, LINESIZE, rfile ) != NULL ) {
692 ++lineno;
693 int len = strlen(line)-1;
694 if ( len+2 >= LINESIZE ) {
695 fprintf( stderr, "%s:%lu: line too long, sorry.\n",
696 optarg, lineno );
697 exit(1);
698 }
699
700 // remove trailing line breaks
701 while ( len > 0 && ( line[len] == '\n' || line[len] == '\r' ) ) {
702 line[len] = '\0';
703 --len;
704 }
705
706 // insert into list of expressions
707 if ( head == 0 ) tail = head = new REList(line,option=='F');
708 else {
709 tail->next = new REList(line,option=='F');
710 tail = tail->next;
711 }
712 }
713 fclose(rfile);
714 } else
715 fprintf( stderr, "unable to open %s: %s\n", optarg, strerror(errno));
716 break;
717
718 case 'H':
719 ::envelope = ! ::envelope;
720 break;
721 case 'n':
722 ::no_fork = ! ::no_fork;
723 break;
724 case 'p':
725 if ( !optarg || !*optarg ) {
726 fprintf( stderr, "%c requires a port argument!\n", option );
727 exit(1);
728 }
729 colon = strchr( optarg, ':' );
730 if ( colon == 0 ) {
731 // no colon, only look at host
732
733 // fix: see if somebody just put in there a port (no periods)
734 // give port number precedence over host names
735 port = checkForPortOnly( optarg );
736 if ( port == -1 ) {
737 // assume that main() did set the default port
738 if ( convertHostname(optarg,serverHost) == -1 ) {
739 fprintf( stderr, "unable to resolve host %s!\n", optarg );
740 exit(1);
741 }
742 } else {
743 // assume that main() did set the default host
744 serverPort = port;
745 }
746 } else {
747 // colon used, port is extra
748 *colon = 0;
749 ++colon;
750 if ( convertHostname(optarg,serverHost) == -1 ) {
751 fprintf( stderr, "unable to resolve host %s!\n", optarg );
752 exit(1);
753 }
754 if ( convertPortname(colon,serverPort) == -1 ) {
755 fprintf( stderr, "unable to resolve port %s!\n", colon );
756 exit(1);
757 }
758 }
759 break;
760 case 'P':
761 if ( !optarg || !*optarg ) {
762 fprintf( stderr, "%c requires a mode argument!\n", option );
763 exit(1);
764 }
765 ::purgeMode = ( strtol( optarg, 0, 0 ) & 0x07 );
766 break;
767 case 's':
768 showme=1;
769 break;
770 case 'v':
771 ::verbose = ! ::verbose;
772 break;
773 case '?':
774 default:
775 helpMe();
776 exit(1);
777 }
778 }
779
780 // adjust
781 if ( ! isatty(fileno(stdout)) || (::debugFlag & 0x01) ) ::iamalive = false;
782 if ( head == 0 ) {
783 fputs( "There was no regular expression defined. If you intend\n", stderr );
784 fputs( "to match all possible URLs, use \"-e .\" instead.\n", stderr );
785 exit(1);
786 }
787
788 // postcondition: head != 0
789 assert( head != 0 );
790
791 // make sure that the copy out directory is there and accessible
792 if ( copydir && *copydir )
793 if ( assert_copydir( copydir ) != 0 ) exit(1);
794
795 // show results
796 if ( showme ) {
797 printf( "#\n# Currently active values for %s:\n",
798 ::programname);
799 printf( "# Debug level : " );
800 if ( ::debugFlag ) printf( "%#6.4x", ::debugFlag );
801 else printf( "production level" ); // printf omits 0x prefix for 0!
802 printf( " + %s mode", ::no_fork ? "linear" : "parallel" );
803 puts( ::verbose ? " + extra verbosity" : "" );
804
805 printf( "# Copy-out directory: %s ",
806 copydir ? copydir : "copy-out mode disabled" );
807 if ( copydir )
808 printf( "(%s HTTP header)\n", ::envelope ? "prepend" : "no" );
809 else
810 puts("");
811
812 printf( "# Squid config file : %s\n", conffile );
813 printf( "# Cacheserveraddress: %s:%u\n",
814 inet_ntoa( serverHost ), ntohs( serverPort ) );
815 printf( "# purge mode : 0x%02x\n", ::purgeMode );
816 printf( "# Regular expression: " );
817
818 unsigned count(0);
819 for ( tail = head; tail != NULL; tail = tail->next ) {
820 if ( count++ )
821 printf( "#%22u", count );
822 #if defined(LINUX) && putc==_IO_putc
823 // I HATE BROKEN LINUX HEADERS!
824 // purge.o(.text+0x1040): undefined reference to `_IO_putc'
825 // If your compilation breaks here, remove the undefinition
826 #undef putc
827 #endif
828 else putchar('1');
829 printf( " \"%s\"\n", tail->data );
830 }
831 puts( "#" );
832 }
833 fflush( stdout );
834 }
835
836 extern "C" {
837
838 static
839 void
840 exiter( void ) {
841 if ( ::term_flag ) psignal( ::term_flag, "received signal" );
842 delete[] ::linebuffer;
843 if ( ::reminder ) {
844 fputs(
845 "WARNING! Caches files were removed. Please shut down your cache, remove\n"
846 "your swap.state files and restart your cache again, i.e. effictively do\n"
847 "a slow rebuild your cache! Otherwise your squid *will* choke!\n", stderr );
848 }
849 }
850
851 static
852 void
853 handler( int signo ) {
854 ::term_flag = signo;
855 if ( getpid() == getpgrp() ) kill( -getpgrp(), signo );
856 exit(1);
857 }
858
859 } // extern "C"
860
861 static
862 int
863 makelinebuffered( FILE* fp, const char* fn = 0 )
864 // purpose: make the given FILE line buffered
865 // paramtr: fp (IO): file pointer which to put into line buffer mode
866 // fn (IN): name of file to print in case of error
867 // returns: 0 is ok, -1 to indicate an error
868 // warning: error messages will already be printed
869 {
870 if ( setvbuf( fp, 0, _IOLBF, 0 ) == 0 ) {
871 // ok
872 return 0;
873 } else {
874 // error
875 fprintf( stderr, "unable to make \"%s\" line buffered: %s\n",
876 fn ? fn : "", strerror(errno) );
877 return -1;
878 }
879 }
880
881 int
882 main( int argc, char* argv[] )
883 {
884 // setup variables
885 REList* list = 0;
886 char* conffile = xstrdup( DEFAULT_SQUID_CONF );
887 serverPort = htons(DEFAULTPORT);
888 if ( convertHostname(DEFAULTHOST,serverHost) == -1 ) {
889 fprintf( stderr, "unable to resolve host %s!\n", DEFAULTHOST );
890 return 1;
891 }
892
893 // setup line buffer
894 ::linebuffer = new char[ ::buffersize ];
895 assert( ::linebuffer != 0 );
896
897 // parse commandline
898 puts( "### Use at your own risk! No guarantees whatsoever. You were warned. ###");
899 parseCommandline( argc, argv, list, conffile, ::copydir,
900 serverHost, serverPort );
901
902 // prepare execution
903 if ( atexit( exiter ) != 0 ||
904 Signal( SIGTERM, handler, true ) == SIG_ERR ||
905 Signal( SIGINT, handler, true ) == SIG_ERR ||
906 Signal( SIGHUP, handler, true ) == SIG_ERR ) {
907 perror( "unable to install signal/exit function" );
908 return 1;
909 }
910
911 // try to read squid.conf file to determine all cache_dir locations
912 CacheDirVector cdv(0);
913 if ( readConfigFile( cdv, conffile, debugFlag ? stderr : 0 ) > 0 ) {
914 // there are some valid cache_dir entries.
915 // unless forking was forbidden by cmdline option,
916 // for a process for each cache_dir entry to remove files.
917
918 if ( ::no_fork || cdv.size() == 1 ) {
919 // linear mode, one cache_dir after the next
920 for ( CacheDirVector::iterator i = cdv.begin(); i != cdv.end(); ++i ) {
921 // execute OR complain
922 if ( ! dirlevel(i->base,list) )
923 fprintf( stderr, "program terminated due to error: %s",
924 strerror(errno) );
925 xfree((void*) i->base);
926 }
927 } else {
928 // parallel mode, all cache_dir in parallel
929 pid_t* child = new pid_t[ cdv.size() ];
930
931 // make stdout/stderr line bufferd
932 makelinebuffered( stdout, "stdout" );
933 makelinebuffered( stderr, "stderr" );
934
935 // make parent process group leader for easier killings
936 if ( setpgid(getpid(), getpid()) != 0 ) {
937 perror( "unable to set process group leader" );
938 return 1;
939 }
940
941 // -a is mutually exclusive with fork mode
942 if ( ::iamalive ) {
943 puts( "# i-am-alive flag incompatible with fork mode, resetting" );
944 ::iamalive = false;
945 }
946
947 for ( size_t i=0; i < cdv.size(); ++i ) {
948 if ( getpid() == getpgrp() ) {
949 // only parent == group leader may fork off new processes
950 if ( (child[i]=fork()) < 0 ) {
951 // fork error, this is bad!
952 perror( "unable to fork" );
953 kill( -getpgrp(), SIGTERM );
954 return 1;
955 } else if ( child[i] == 0 ) {
956 // child mode
957 // execute OR complain
958 if ( ! dirlevel(cdv[i].base,list) )
959 fprintf( stderr, "program terminated due to error: %s\n",
960 strerror(errno) );
961 xfree((void*) cdv[i].base);
962 return 0;
963 } else {
964 // parent mode
965 if ( ::debugFlag ) printf( "forked child %d\n", (int) child[i] );
966 }
967 }
968 }
969
970 // collect the garbase
971 pid_t temp;
972 int status;
973 for ( size_t i=0; i < cdv.size(); ++i ) {
974 while ( (temp=waitpid( (pid_t)-1, &status, 0 )) == -1 )
975 if ( errno == EINTR ) continue;
976 if ( ::debugFlag ) printf( "collected child %d\n", (int) temp );
977 }
978 delete[] child;
979 }
980 } else {
981 fprintf( stderr, "no cache_dir or error accessing \"%s\"\n", conffile );
982 }
983
984 // clean up
985 if ( copydir ) xfree( (void*) copydir );
986 xfree((void*) conffile);
987 delete list;
988 return 0;
989 }