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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors