purge.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2023 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_REGEX_H
117#include <regex.h>
118#endif
119
120#if HAVE_SIGINFO_H
121#include <siginfo.h>
122#endif
123
124#include <netinet/in.h>
125#include <netinet/tcp.h>
126#include <arpa/inet.h>
127#include <netdb.h>
128
129#include "conffile.hh"
130#include "convert.hh"
131#include "copyout.hh"
132#include "signal.hh"
133#include "socket.hh"
134#include "squid-tlv.hh"
135
136#ifndef DEFAULTHOST
137#define DEFAULTHOST "localhost"
138#endif // DEFAULTHOST
139
140#ifndef DEFAULTPORT
141#define DEFAULTPORT 3128
142#endif // DEFAULTPORT
143
144volatile sig_atomic_t term_flag = 0; // 'terminate' is a gcc 2.8.x internal...
145char* linebuffer = nullptr;
146size_t buffersize = 128*1024;
147static char* copydir = nullptr;
148static uint32_t debugFlag = 0;
149static unsigned purgeMode = 0;
150static bool iamalive = false;
151static bool reminder = false;
152static bool verbose = false;
153static bool envelope = false;
154static bool no_fork = false;
155static const char* programname = nullptr;
156
157// ----------------------------------------------------------------------
158
159struct REList {
160 REList( const char* what, bool doCase );
161 ~REList();
162 bool match( const char* check ) const;
163
165 const char* data;
166 regex_t rexp;
167};
168
169REList::REList( const char* what, bool doCase )
170 :next(nullptr),data(xstrdup(what))
171{
172 int result = regcomp( &rexp, what,
173 REG_EXTENDED | REG_NOSUB | (doCase ? 0 : REG_ICASE) );
174 if ( result != 0 ) {
175 char buffer[256];
176 regerror( result, &rexp, buffer, 256 );
177 fprintf( stderr, "unable to compile re \"%s\": %s\n", what, buffer );
178 exit(EXIT_FAILURE);
179 }
180}
181
183{
184 if ( next ) delete next;
185 if ( data ) xfree((void*) data);
186 regfree(&rexp);
187}
188
189bool
190REList::match( const char* check ) const
191{
192 int result = regexec( &rexp, check, 0, nullptr, 0 );
193 if ( result != 0 && result != REG_NOMATCH ) {
194 char buffer[256];
195 regerror( result, &rexp, buffer, 256 );
196 fprintf( stderr, "unable to execute re \"%s\"\n+ on line \"%s\": %s\n",
197 data, check, buffer );
198 exit(EXIT_FAILURE);
199 }
200 return ( result == 0 );
201}
202
203// ----------------------------------------------------------------------
204
205static char *
206concat(const char *start, ...)
207// purpose: concatinate an arbitrary number of C strings.
208// paramtr: start (IN): first C string
209// ... (IN): further C strings, terminated with a NULL pointer
210// returns: memory allocated via new(), containing the concatenated string.
211{
212 va_list ap;
213 const char* s;
214
215 // first run: determine size
216 unsigned size = strlen(start)+1;
217 va_start( ap, start );
218 while ( (s=va_arg(ap,const char*)) != nullptr )
219 size += strlen(s);
220 va_end(ap);
221
222 // allocate
223 char* result = new char[size];
224 if ( result == nullptr ) {
225 perror( "string memory allocation" );
226 exit(EXIT_FAILURE);
227 }
228
229 // second run: copy content
230 strcpy( result, start );
231 va_start( ap, start );
232 while ( (s=va_arg(ap,const char*)) != nullptr ) strcat( result, s );
233 va_end(ap);
234
235 return result;
236}
237
238static bool
239isxstring( const char* s, size_t testlen )
240// purpose: test a string for conforming to xdigit
241// paramtr: s (IN): string to test
242// testlen (IN): length the string must have
243// returns: true, iff strlen(s)==testlen && all_x_chars(s), false otherwise
244{
245 if ( strlen(s) != testlen ) return false;
246
247 size_t i=0;
248 while ( i<testlen && isxdigit(s[i]) )
249 ++i;
250 return (i==testlen);
251}
252
253inline
254int
255log_output( const char* fn, int code, long size, const char* url )
256{
257 return printf( "%s %3d %8ld %s\n", fn, code, size, url );
258}
259
260static
261int
262log_extended( const char* fn, int code, long size, const SquidMetaList* meta )
263{
264 static const char hexdigit[] = "0123456789ABCDEF";
265 char md5[34];
266 const SquidTLV* findings = nullptr;
267
268 if ( meta && (findings = meta->search( STORE_META_KEY_MD5 )) ) {
269 unsigned char* s = (unsigned char*) findings->data;
270 for ( int j=0; j<16; ++j, ++s ) {
271 md5[j*2+0] = hexdigit[ *s >> 4 ];
272 md5[j*2+1] = hexdigit[ *s & 15 ];
273 }
274 md5[32] = '\0'; // terminate string
275 } else {
276 snprintf( md5, sizeof(md5), "%-32s", "(no_md5_data_available)" );
277 }
278
279 char timeb[256];
280 if ( meta && (findings = meta->search( STORE_META_STD )) ) {
281 StoreMetaStd 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 if ( meta && (findings = meta->search( STORE_META_STD_LFS )) ) {
288 StoreMetaStdLFS temp;
289 // make data aligned, avoid SIGBUS on RISC machines (ARGH!)
290 memcpy( &temp, findings->data, sizeof(StoreMetaStdLFS) );
291 snprintf( timeb, sizeof(timeb), "%08lx %08lx %08lx %08lx %04x %5hu ",
292 (unsigned long)temp.timestamp, (unsigned long)temp.lastref,
293 (unsigned long)temp.expires, (unsigned long)temp.lastmod, temp.flags, temp.refcount );
294 } else {
295 unsigned long ul = ULONG_MAX; // Match type of StoreMetaTLV fields
296 unsigned short hu = 0; // Match type of StoreMetaTLV refcount fields
297 snprintf( timeb, sizeof(timeb), "%08lx %08lx %08lx %08lx %04x %5d ", ul, ul, ul, ul, 0, hu);
298 }
299
300 // make sure that there is just one printf()
301 if ( meta && (findings = meta->search( STORE_META_URL )) ) {
302 return printf( "%s %3d %8ld %s %s %s\n",
303 fn, code, size, md5, timeb, findings->data );
304 } else {
305 return printf( "%s %3d %8ld %s %s strange_file\n",
306 fn, code, size, md5, timeb );
307 }
308}
309
310// o.k., this is pure laziness...
311static struct in_addr serverHost;
312static unsigned short serverPort;
313
314static bool
315action(int fd, size_t metasize,
316 const char *fn, const char *url, const SquidMetaList &meta)
317// purpose: if cmdline-requested, send the purge request to the cache
318// paramtr: fd (IN): open FD for the object file
319// metasize (IN): offset into data portion of file (meta data size)
320// fn (IN): name of the object file
321// url (IN): URL string stored in the object file
322// meta (IN): list containing further meta data
323// returns: true for a successful action, false otherwise. The action
324// may just print the file, send the purge request or even
325// remove unwanted files.
326// globals: ::purgeMode (IN): bit#0 set -> send purge request.
327// bit#1 set -> remove 404 object files.
328// ::serverHost (IN): cache host address
329// ::serverPort (IN): cache port number
330{
331 static const char* schablone = "PURGE %s HTTP/1.0\r\nAccept: */*\r\n\r\n";
332 struct stat st;
333 long size = ( fstat(fd,&st) == -1 ? -1 : long(st.st_size - metasize) );
334
335 // if we want to copy out the file, do that first of all.
336 if ( ::copydir && *copydir && size > 0 )
337 copy_out( st.st_size, metasize, ::debugFlag,
338 fn, url, ::copydir, ::envelope );
339
340 // do we need to PURGE the file, yes, if purgemode bit#0 was set.
341 int status = 0;
342 if ( ::purgeMode & 0x01 ) {
343 unsigned long bufsize = strlen(url) + strlen(schablone) + 4;
344 char* buffer = new char[bufsize];
345
346 snprintf( buffer, bufsize, schablone, url );
347 int sockfd = connectTo( serverHost, serverPort, true );
348 if ( sockfd == -1 ) {
349 fprintf( stderr, "unable to connect to server: %s\n", strerror(errno) );
350 delete[] buffer;
351 return false;
352 }
353
354 int content_size = strlen(buffer);
355 if ( write( sockfd, buffer, content_size ) != content_size ) {
356 // error while talking to squid
357 fprintf( stderr, "unable to talk to server: %s\n", strerror(errno) );
358 close(sockfd);
359 delete[] buffer;
360 return false;
361 }
362 memset( buffer+8, 0, 4 );
363 int readLen = read(sockfd, buffer, bufsize);
364 if (readLen < 1) {
365 // error while reading squid's answer
366 fprintf( stderr, "unable to read answer: %s\n", strerror(errno) );
367 close(sockfd);
368 delete[] buffer;
369 return false;
370 }
371 buffer[bufsize-1] = '\0';
372 close(sockfd);
373 int64_t s = strtol(buffer+8,nullptr,10);
374 if (s > 0 && s < 1000)
375 status = s;
376 else {
377 // error while reading squid's answer
378 fprintf( stderr, "invalid HTTP status in reply: %s\n", buffer+8);
379 }
380 delete[] buffer;
381 }
382
383 // log the output of our operation
384 bool flag = true;
385 if ( ::verbose ) flag = ( log_extended( fn, status, size, &meta ) >= 0 );
386 else flag = ( log_output( fn, status, size, url ) >= 0 );
387
388 // remove the file, if purgemode bit#1, and HTTP result status 404).
389 if ( (::purgeMode & 0x02) && status == 404 ) {
390 reminder = true;
391 if ( unlink(fn) == -1 )
392 // error while unlinking file, this may happen due to the cache
393 // unlinking a file while it is still in the readdir() cache of purge.
394 fprintf( stderr, "WARNING: unable to unlink %s: %s\n",
395 fn, strerror(errno) );
396 }
397
398 return flag;
399}
400
401static bool
402match(const char *fn, const REList *list)
403// purpose: do something with the given cache content filename
404// paramtr: fn (IN): filename of cache file
405// returns: true for successful action, false otherwise.
406// warning: only return false, if you want the loop to terminate!
407{
408 static const size_t addon = sizeof(unsigned char) + sizeof(unsigned int);
409 bool flag = true;
410
411 if ( debugFlag & 0x01 ) fprintf( stderr, "# [3] %s\n", fn );
412 int fd = open( fn, O_RDONLY );
413 if ( fd != -1 ) {
414 memset(::linebuffer, 0, ::buffersize);
415 size_t readLen = read(fd,::linebuffer,::buffersize-1);
416 if ( readLen > 60 ) {
417 ::linebuffer[ ::buffersize-1 ] = '\0'; // force-terminate string
418
419 // check the offset into the start of object data. The offset is
420 // stored in a host endianness after the first byte.
421 unsigned int datastart;
422 memcpy( &datastart, ::linebuffer + 1, sizeof(unsigned int) );
423 if ( datastart > ::buffersize - addon - 1 ) {
424 // check offset into server reply header (start of cache data).
425 fputs( "WARNING: Using a truncated URL string.\n", stderr );
426 datastart = ::buffersize - addon - 1;
427 }
428
429 // NEW: Parse squid meta data, which is a kind of linked list
430 // flattened out into a file byte stream. Somewhere within is
431 // the URL as part of the list. First, gobble all meta data.
432 unsigned int offset = addon;
433 SquidMetaList meta;
434 while ( offset + addon <= datastart ) {
435 unsigned int size = 0;
436 memcpy( &size, linebuffer+offset+sizeof(char), sizeof(unsigned int) );
437 if (size+offset < size) {
438 fputs("WARNING: file corruption detected. 32-bit overflow in size field.\n", stderr);
439 break;
440 }
441 if (size+offset > readLen) {
442 fputs( "WARNING: Partial meta data loaded.\n", stderr );
443 break;
444 }
445 meta.append( SquidMetaType(*(linebuffer+offset)),
446 size, linebuffer+offset+addon );
447 offset += ( addon + size );
448 }
449
450 // Now extract the key URL from the meta data.
451 const SquidTLV* urlmeta = meta.search( STORE_META_URL );
452 if ( urlmeta ) {
453 // found URL in meta data. Try to process the URL
454 if ( list == nullptr )
455 flag = action( fd, datastart, fn, (char*) urlmeta->data, meta );
456 else {
457 REList* head = (REList*) list; // YUCK!
458 while ( head != nullptr ) {
459 if ( head->match( (char*) urlmeta->data ) ) break;
460 head = head->next;
461 }
462 if ( head != nullptr )
463 flag = action( fd, datastart, fn, (char*) urlmeta->data, meta );
464 else flag = true;
465 }
466 }
467
468 // "meta" will be deleted when exiting from this block
469 } else {
470 // weird file, TODO: stat() it!
471 struct stat st;
472 long size = ( fstat(fd,&st) == -1 ? -1 : st.st_size );
473 if ( ::verbose ) flag = ( log_extended( fn, -1, size, nullptr ) >= 0 );
474 else flag = ( log_output( fn, -1, size, "strange file" ) >= 0 );
475
476 if ( (::purgeMode & 0x04) ) {
477 reminder = true;
478 if ( unlink(fn) == -1 )
479 // error while unlinking file, this may happen due to the cache
480 // unlinking a file while it is in the readdir() cache of purge.
481 fprintf( stderr, "WARNING: unable to unlink %s: %s\n",
482 fn, strerror(errno) );
483 }
484 }
485 close(fd);
486 } else {
487 // error while opening file, this may happen due to the cache
488 // unlinking a file while it is still in the readdir() cache of purge.
489 fprintf( stderr, "WARNING: open \"%s\": %s\n", fn, strerror(errno) );
490 }
491
492 return flag;
493}
494
495static bool
496filelevel(const char *directory, const REList *list)
497// purpose: from given starting point, look for squid xxxxxxxx files.
498// example: "/var/spool/cache/08/7F" as input, do action over files
499// paramtr: directory (IN): starting point
500// list (IN): list of rexps to match URLs against
501// returns: true, if every subdir && action was successful.
502{
503 dirent_t * entry;
504 if ( debugFlag & 0x01 )
505 fprintf( stderr, "# [2] %s\n", directory );
506
507 DIR* dir = opendir( directory );
508 if ( dir == nullptr ) {
509 fprintf( stderr, "unable to open directory \"%s\": %s\n",
510 directory, strerror(errno) );
511 return false;
512 }
513
514 // display a rotating character as "i am alive" signal (slows purge).
515 if ( ::iamalive ) {
516 static char alivelist[4][3] = { "\\\b", "|\b", "/\b", "-\b" };
517 static unsigned short alivecount = 0;
518 const int write_success = write(STDOUT_FILENO, alivelist[alivecount++ & 3], 2);
519 assert(write_success == 2);
520 }
521
522 bool flag = true;
523 while ( (entry=readdir(dir)) && flag ) {
524 if ( isxstring(entry->d_name,8) ) {
525 char* name = concat( directory, "/", entry->d_name, 0 );
526 flag = match( name, list );
527 delete[] name;
528 }
529 }
530
531 closedir(dir);
532 return flag;
533}
534
535static bool
536dirlevel(const char *dirname, const REList *list, bool level = false)
537// purpose: from given starting point, look for squid 00..FF directories.
538// paramtr: dirname (IN): starting point
539// list (IN): list of rexps to match URLs against
540// level (IN): false==toplevel, true==1st level
541// example: "/var/spool/cache", false as input, traverse subdirs w/ action.
542// example: "/var/spool/cache/08", true as input, traverse subdirs w/ action.
543// returns: true, if every subdir && action was successful.
544// warning: this function is once-recursive, no deeper.
545{
546 dirent_t* entry;
547 if ( debugFlag & 0x01 )
548 fprintf( stderr, "# [%d] %s\n", (level ? 1 : 0), dirname );
549
550 DIR* dir = opendir( dirname );
551 if ( dir == nullptr ) {
552 fprintf( stderr, "unable to open directory \"%s\": %s\n",
553 dirname, strerror(errno) );
554 return false;
555 }
556
557 bool flag = true;
558 while ( (entry=readdir(dir)) && flag ) {
559 if ( strlen(entry->d_name) == 2 &&
560 isxdigit(entry->d_name[0]) &&
561 isxdigit(entry->d_name[1]) ) {
562 char* name = concat( dirname, "/", entry->d_name, 0 );
563 flag = level ? filelevel( name, list ) : dirlevel( name, list, true );
564 delete[] name;
565 }
566 }
567
568 closedir(dir);
569 return flag;
570}
571
572static int
573checkForPortOnly(const char *arg)
574// purpose: see if somebody just put in a port instead of a hostname
575// paramtr: optarg (IN): argument from commandline
576// returns: 0..65535 is the valid port number in network byte order,
577// -1 if not a port
578{
579 // if there is a period in there, it must be a valid hostname
580 if ( strchr( arg, '.' ) != nullptr ) return -1;
581
582 // if it is just a number between 0 and 65535, it must be a port
583 char* errstr = nullptr;
584 unsigned long result = strtoul( arg, &errstr, 0 );
585 if ( result < 65536 && errstr != arg ) return htons(result);
586
587 return -1;
588}
589
590static void
592// purpuse: write help message and exit
593{
594 printf( "\nUsage:\t%s\t[-a] [-c cf] [-d l] [-(f|F) fn | -(e|E) re] "
595 "[-p h[:p]]\n\t\t[-P #] [-s] [-v] [-C dir [-H]] [-n]\n\n",
597 printf(
598 " -a\tdisplay a little rotating thingy to indicate that I am alive (tty only).\n"
599 " -c c\tsquid.conf location, default \"%s\".\n"
600 " -C dir\tbase directory for content extraction (copy-out mode).\n"
601 " -d l\tdebug level, an OR mask of different debug options.\n"
602 " -e re\tsingle regular expression per -e instance (use quotes!).\n"
603 " -E re\tsingle case sensitive regular expression like -e.\n"
604 " -f fn\tname of textfile containing one regular expression per line.\n"
605 " -F fn\tname of textfile like -f containing case sensitive REs.\n"
606 " -H\tprepend HTTP reply header to destination files in copy-out mode.\n"
607 " -n\tdo not fork() when using more than one cache_dir.\n"
608 " -p h:p\tcache runs on host h and optional port p, default is %s:%u.\n"
609 " -P #\tif 0, just print matches; otherwise OR the following purge modes:\n"
610 "\t 0x01 really send PURGE to the cache.\n"
611 "\t 0x02 remove all caches files reported as 404 (not found).\n"
612 "\t 0x04 remove all weird (inaccessible or too small) cache files.\n"
613 "\t0 and 1 are recommended - slow rebuild your cache with other modes.\n"
614 " -s\tshow all options after option parsing, but before really starting.\n"
615 " -v\tshow more information about the file, e.g. MD5, timestamps and flags.\n"
616 "\n", DEFAULT_CONFIG_FILE, DEFAULTHOST, DEFAULTPORT );
617
618}
619
620static void
621parseCommandline(int argc, char *argv[], REList *&head,
622 char *&conffile, char *&copyDirPath,
623 struct in_addr &serverHostIp, unsigned short &serverHostPort)
624// paramtr: argc: see ::main().
625// argv: see ::main().
626// returns: Does terminate the program on errors!
627// purpose: suck in any commandline options, and set the global vars.
628{
629 int option, port, showme = 0;
630 char* ptr, *colon;
631 FILE* rfile;
632
633 // program basename
634 if ( (ptr = strrchr(argv[0],'/')) == nullptr )
635 ptr=argv[0];
636 else
637 ++ptr;
638 ::programname = ptr;
639
640 // extract commandline parameters
641 REList* tail = head = nullptr;
642 opterr = 0;
643 while ( (option = getopt( argc, argv, "ac:C:d:E:e:F:f:Hnp:P:sv" )) != -1 ) {
644 switch ( option ) {
645 case 'a':
647 break;
648 case 'C':
649 if ( optarg && *optarg ) {
650 if ( copyDirPath ) xfree( (void*) copyDirPath );
651 copyDirPath = xstrdup(optarg);
652 assert(copyDirPath);
653 }
654 break;
655 case 'c':
656 if ( !optarg || !*optarg ) {
657 fprintf( stderr, "%c requires a regex pattern argument!\n", option );
658 exit(EXIT_FAILURE);
659 }
660 if ( *conffile ) xfree((void*) conffile);
661 conffile = xstrdup(optarg);
662 assert(conffile);
663 break;
664
665 case 'd':
666 if ( !optarg || !*optarg ) {
667 fprintf( stderr, "%c expects a mask parameter. Debug disabled.\n", option );
668 ::debugFlag = 0;
669 } else
670 ::debugFlag = (strtoul(optarg, nullptr, 0) & 0xFFFFFFFF);
671 break;
672
673 case 'E':
674 case 'e':
675 if ( !optarg || !*optarg ) {
676 fprintf( stderr, "%c requires a regex pattern argument!\n", option );
677 exit(EXIT_FAILURE);
678 }
679 if ( head == nullptr )
680 tail = head = new REList( optarg, option=='E' );
681 else {
682 tail->next = new REList( optarg, option=='E' );
683 tail = tail->next;
684 }
685 break;
686
687 case 'f':
688 if ( !optarg || !*optarg ) {
689 fprintf( stderr, "%c requires a filename argument!\n", option );
690 exit(EXIT_FAILURE);
691 }
692 if ( (rfile = fopen( optarg, "r" )) != nullptr ) {
693 unsigned long lineno = 0;
694#define LINESIZE 512
695 char line[LINESIZE];
696 while ( fgets( line, LINESIZE, rfile ) != nullptr ) {
697 ++lineno;
698 int len = strlen(line)-1;
699 if ( len+2 >= LINESIZE ) {
700 fprintf( stderr, "%s:%lu: line too long, sorry.\n",
701 optarg, lineno );
702 exit(EXIT_FAILURE);
703 }
704
705 // remove trailing line breaks
706 while ( len > 0 && ( line[len] == '\n' || line[len] == '\r' ) ) {
707 line[len] = '\0';
708 --len;
709 }
710
711 // insert into list of expressions
712 if ( head == nullptr ) tail = head = new REList(line,option=='F');
713 else {
714 tail->next = new REList(line,option=='F');
715 tail = tail->next;
716 }
717 }
718 fclose(rfile);
719 } else
720 fprintf( stderr, "unable to open %s: %s\n", optarg, strerror(errno));
721 break;
722
723 case 'H':
725 break;
726 case 'n':
728 break;
729 case 'p':
730 if ( !optarg || !*optarg ) {
731 fprintf( stderr, "%c requires a port argument!\n", option );
732 exit(EXIT_FAILURE);
733 }
734 colon = strchr( optarg, ':' );
735 if ( colon == nullptr ) {
736 // no colon, only look at host
737
738 // fix: see if somebody just put in there a port (no periods)
739 // give port number precedence over host names
741 if ( port == -1 ) {
742 // assume that main() did set the default port
743 if ( convertHostname(optarg,serverHostIp) == -1 ) {
744 fprintf( stderr, "unable to resolve host %s!\n", optarg );
745 exit(EXIT_FAILURE);
746 }
747 } else {
748 // assume that main() did set the default host
749 serverHostPort = port;
750 }
751 } else {
752 // colon used, port is extra
753 *colon = 0;
754 ++colon;
755 if ( convertHostname(optarg,serverHostIp) == -1 ) {
756 fprintf( stderr, "unable to resolve host %s!\n", optarg );
757 exit(EXIT_FAILURE);
758 }
759 if ( convertPortname(colon,serverHostPort) == -1 ) {
760 fprintf( stderr, "unable to resolve port %s!\n", colon );
761 exit(EXIT_FAILURE);
762 }
763 }
764 break;
765 case 'P':
766 if ( !optarg || !*optarg ) {
767 fprintf( stderr, "%c requires a mode argument!\n", option );
768 exit(EXIT_FAILURE);
769 }
770 ::purgeMode = ( strtol( optarg, nullptr, 0 ) & 0x07 );
771 break;
772 case 's':
773 showme=1;
774 break;
775 case 'v':
777 break;
778 case '?':
779 default:
780 helpMe();
781 exit(EXIT_FAILURE);
782 }
783 }
784
785 // adjust
786 if ( ! isatty(fileno(stdout)) || (::debugFlag & 0x01) ) ::iamalive = false;
787 if ( head == nullptr ) {
788 fputs( "There was no regular expression defined. If you intend\n", stderr );
789 fputs( "to match all possible URLs, use \"-e .\" instead.\n", stderr );
790 exit(EXIT_FAILURE);
791 }
792
793 // postcondition: head != 0
794 assert( head != nullptr );
795
796 // make sure that the copy out directory is there and accessible
797 if ( copyDirPath && *copyDirPath )
798 if ( assert_copydir( copyDirPath ) != 0 ) exit(1);
799
800 // show results
801 if ( showme ) {
802 printf( "#\n# Currently active values for %s:\n",
804 printf( "# Debug level : " );
805 if ( ::debugFlag ) printf( "%#6.4x", ::debugFlag );
806 else printf( "production level" ); // printf omits 0x prefix for 0!
807 printf( " + %s mode", ::no_fork ? "linear" : "parallel" );
808 puts( ::verbose ? " + extra verbosity" : "" );
809
810 printf( "# Copy-out directory: %s ",
811 copyDirPath ? copyDirPath : "copy-out mode disabled" );
812 if ( copyDirPath )
813 printf( "(%s HTTP header)\n", ::envelope ? "prepend" : "no" );
814 else
815 puts("");
816
817 printf( "# Squid config file : %s\n", conffile );
818 printf( "# Cacheserveraddress: %s:%u\n",
819 inet_ntoa( serverHostIp ), ntohs( serverHostPort ) );
820 printf( "# purge mode : 0x%02x\n", ::purgeMode );
821 printf( "# Regular expression: " );
822
823 unsigned count(0);
824 for ( tail = head; tail != nullptr; tail = tail->next ) {
825 if ( count++ )
826 printf( "#%22u", count );
827#if defined(LINUX) && putc==_IO_putc
828 // I HATE BROKEN LINUX HEADERS!
829 // purge.o(.text+0x1040): undefined reference to `_IO_putc'
830 // If your compilation breaks here, remove the undefinition
831#undef putc
832#endif
833 else putchar('1');
834 printf( " \"%s\"\n", tail->data );
835 }
836 puts( "#" );
837 }
838 fflush( stdout );
839}
840
841extern "C" {
842
843 static
844 void
845 exiter( void ) {
846 if ( ::term_flag ) psignal( ::term_flag, "received signal" );
847 delete[] ::linebuffer;
848 if ( ::reminder ) {
849 fputs(
850 "WARNING! Caches files were removed. Please shut down your cache, remove\n"
851 "your swap.state files and restart your cache again, i.e. effictively do\n"
852 "a slow rebuild your cache! Otherwise your squid *will* choke!\n", stderr );
853 }
854 }
855
856 static
857 void
858 handler( int signo ) {
859 ::term_flag = signo;
860 if ( getpid() == getpgrp() ) kill( -getpgrp(), signo );
861 exit(EXIT_FAILURE);
862 }
863
864} // extern "C"
865
866static
867int
868makelinebuffered( FILE* fp, const char* fn = nullptr )
869// purpose: make the given FILE line buffered
870// paramtr: fp (IO): file pointer which to put into line buffer mode
871// fn (IN): name of file to print in case of error
872// returns: 0 is ok, -1 to indicate an error
873// warning: error messages will already be printed
874{
875 if ( setvbuf( fp, nullptr, _IOLBF, 0 ) == 0 ) {
876 // ok
877 return 0;
878 } else {
879 // error
880 fprintf( stderr, "unable to make \"%s\" line buffered: %s\n",
881 fn ? fn : "", strerror(errno) );
882 return -1;
883 }
884}
885
886int
887main( int argc, char* argv[] )
888{
889 // setup variables
890 REList* list = nullptr;
891 char* conffile = xstrdup(DEFAULT_CONFIG_FILE);
892 serverPort = htons(DEFAULTPORT);
894 fprintf( stderr, "unable to resolve host %s!\n", DEFAULTHOST );
895 exit(EXIT_FAILURE);
896 }
897
898 // setup line buffer
899 ::linebuffer = new char[ ::buffersize ];
900 assert( ::linebuffer != nullptr );
901
902 // parse commandline
903 puts( "### Use at your own risk! No guarantees whatsoever. You were warned. ###");
904 parseCommandline( argc, argv, list, conffile, ::copydir,
906
907 // prepare execution
908 if ( atexit( exiter ) != 0 ||
909 Signal( SIGTERM, handler, true ) == SIG_ERR ||
910 Signal( SIGINT, handler, true ) == SIG_ERR ||
911 Signal( SIGHUP, handler, true ) == SIG_ERR ) {
912 perror( "unable to install signal/exit function" );
913 exit(EXIT_FAILURE);
914 }
915
916 // try to read squid.conf file to determine all cache_dir locations
917 CacheDirVector cdv(0);
918 if ( readConfigFile( cdv, conffile, debugFlag ? stderr : nullptr ) > 0 ) {
919 // there are some valid cache_dir entries.
920 // unless forking was forbidden by cmdline option,
921 // for a process for each cache_dir entry to remove files.
922
923 if ( ::no_fork || cdv.size() == 1 ) {
924 // linear mode, one cache_dir after the next
925 for ( CacheDirVector::iterator i = cdv.begin(); i != cdv.end(); ++i ) {
926 // execute OR complain
927 if ( ! dirlevel(i->base,list) )
928 fprintf( stderr, "program terminated due to error: %s",
929 strerror(errno) );
930 xfree((void*) i->base);
931 }
932 } else {
933 // parallel mode, all cache_dir in parallel
934 pid_t* child = new pid_t[ cdv.size() ];
935
936 // make stdout/stderr line bufferd
937 makelinebuffered( stdout, "stdout" );
938 makelinebuffered( stderr, "stderr" );
939
940 // make parent process group leader for easier killings
941 if ( setpgid(getpid(), getpid()) != 0 ) {
942 perror( "unable to set process group leader" );
943 exit(EXIT_FAILURE);
944 }
945
946 // -a is mutually exclusive with fork mode
947 if ( ::iamalive ) {
948 puts( "# i-am-alive flag incompatible with fork mode, resetting" );
949 ::iamalive = false;
950 }
951
952 for ( size_t i=0; i < cdv.size(); ++i ) {
953 if ( getpid() == getpgrp() ) {
954 // only parent == group leader may fork off new processes
955 if ( (child[i]=fork()) < 0 ) {
956 // fork error, this is bad!
957 perror( "unable to fork" );
958 kill( -getpgrp(), SIGTERM );
959 exit(EXIT_FAILURE);
960 } else if ( child[i] == 0 ) {
961 // child mode
962 // execute OR complain
963 if ( ! dirlevel(cdv[i].base,list) )
964 fprintf( stderr, "program terminated due to error: %s\n",
965 strerror(errno) );
966 xfree((void*) cdv[i].base);
967 exit(EXIT_SUCCESS);
968 } else {
969 // parent mode
970 if ( ::debugFlag ) printf( "forked child %d\n", (int) child[i] );
971 }
972 }
973 }
974
975 // collect the garbase
976 pid_t temp;
977 int status;
978 for ( size_t i=0; i < cdv.size(); ++i ) {
979 while ( (temp=waitpid( (pid_t)-1, &status, 0 )) == -1 )
980 if ( errno == EINTR ) continue;
981 if ( ::debugFlag ) printf( "collected child %d\n", (int) temp );
982 }
983 delete[] child;
984 }
985 } else {
986 fprintf( stderr, "no cache_dir or error accessing \"%s\"\n", conffile );
987 }
988
989 // clean up
990 if ( copydir ) xfree( (void*) copydir );
991 xfree((void*) conffile);
992 delete list;
993 return EXIT_SUCCESS;
994}
995
int size
Definition: ModDevPoll.cc:75
squidaio_request_t * head
Definition: aiops.cc:127
#define assert(EX)
Definition: assert.h:17
static int sockfd
int readConfigFile(CacheDirVector &cachedir, const char *fn, FILE *debug)
Definition: conffile.cc:59
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
@ 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:573
int main(int argc, char *argv[])
Definition: purge.cc:887
static bool envelope
Definition: purge.cc:153
static void helpMe(void)
Definition: purge.cc:591
char * linebuffer
Definition: purge.cc:145
static bool no_fork
Definition: purge.cc:154
#define DEFAULTPORT
Definition: purge.cc:141
static int makelinebuffered(FILE *fp, const char *fn=nullptr)
Definition: purge.cc:868
static char * copydir
Definition: purge.cc:147
static bool iamalive
Definition: purge.cc:150
static void handler(int signo)
Definition: purge.cc:858
static uint32_t debugFlag
Definition: purge.cc:148
static int log_extended(const char *fn, int code, long size, const SquidMetaList *meta)
Definition: purge.cc:262
static bool filelevel(const char *directory, const REList *list)
Definition: purge.cc:496
static bool dirlevel(const char *dirname, const REList *list, bool level=false)
Definition: purge.cc:536
static bool isxstring(const char *s, size_t testlen)
Definition: purge.cc:239
static void exiter(void)
Definition: purge.cc:845
static char * concat(const char *start,...)
Definition: purge.cc:206
volatile sig_atomic_t term_flag
Definition: purge.cc:144
static bool action(int fd, size_t metasize, const char *fn, const char *url, const SquidMetaList &meta)
Definition: purge.cc:315
static unsigned short serverPort
Definition: purge.cc:312
static bool verbose
Definition: purge.cc:152
static const char * programname
Definition: purge.cc:155
static bool match(const char *fn, const REList *list)
Definition: purge.cc:402
static bool reminder
Definition: purge.cc:151
int log_output(const char *fn, int code, long size, const char *url)
Definition: purge.cc:255
static void parseCommandline(int argc, char *argv[], REList *&head, char *&conffile, char *&copyDirPath, struct in_addr &serverHostIp, unsigned short &serverHostPort)
Definition: purge.cc:621
static unsigned purgeMode
Definition: purge.cc:149
static struct in_addr serverHost
Definition: purge.cc:311
size_t buffersize
Definition: purge.cc:146
#define DEFAULTHOST
Definition: purge.cc:137
SigFunc * Signal(int signo, SigFunc *newhandler, bool doInterrupt)
Definition: signal.cc:62
int code
Definition: smb-errors.c:145
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:159
REList * next
Definition: purge.cc:164
REList(const char *what, bool doCase)
Definition: purge.cc:169
const char * data
Definition: purge.cc:165
regex_t rexp
Definition: purge.cc:166
bool match(const char *check) const
Definition: purge.cc:190
~REList()
Definition: purge.cc:182
struct squidaio_request_t * next
Definition: aiops.cc:51

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors