purge.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2021 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 
140 volatile sig_atomic_t term_flag = 0; // 'terminate' is a gcc 2.8.x internal...
141 char* linebuffer = 0;
142 size_t buffersize = 128*1024;
143 static char* copydir = 0;
144 static uint32_t debugFlag = 0;
145 static unsigned purgeMode = 0;
146 static bool iamalive = false;
147 static bool reminder = false;
148 static bool verbose = false;
149 static bool envelope = false;
150 static bool no_fork = false;
151 static const char* programname = 0;
152 
153 // ----------------------------------------------------------------------
154 
155 struct REList {
156  REList( const char* what, bool doCase );
157  ~REList();
158  bool match( const char* check ) const;
159 
161  const char* data;
163 };
164 
165 REList::REList( const char* what, bool doCase )
166  :next(0),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 
185 bool
186 REList::match( const char* check ) const
187 {
188  int result = regexec( &rexp, check, 0, 0, 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 
201 static char *
202 concat(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*)) != NULL )
215  size += strlen(s);
216  va_end(ap);
217 
218  // allocate
219  char* result = new char[size];
220  if ( result == 0 ) {
221  perror( "string memory allocation" );
222  exit(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*)) != NULL ) strcat( result, s );
229  va_end(ap);
230 
231  return result;
232 }
233 
234 static bool
235 isxstring( const char* s, size_t testlen )
236 // purpose: test a string for conforming to xdigit
237 // paramtr: s (IN): string to test
238 // testlen (IN): length the string must have
239 // returns: true, iff strlen(s)==testlen && all_x_chars(s), false otherwise
240 {
241  if ( strlen(s) != testlen ) return false;
242 
243  size_t i=0;
244  while ( i<testlen && isxdigit(s[i]) )
245  ++i;
246  return (i==testlen);
247 }
248 
249 inline
250 int
251 log_output( const char* fn, int code, long size, const char* url )
252 {
253  return printf( "%s %3d %8ld %s\n", fn, code, size, url );
254 }
255 
256 static
257 int
258 log_extended( const char* fn, int code, long size, const SquidMetaList* meta )
259 {
260  static const char hexdigit[] = "0123456789ABCDEF";
261  char md5[34];
262  const SquidTLV* findings = 0;
263 
264  if ( meta && (findings = meta->search( STORE_META_KEY_MD5 )) ) {
265  unsigned char* s = (unsigned char*) findings->data;
266  for ( int j=0; j<16; ++j, ++s ) {
267  md5[j*2+0] = hexdigit[ *s >> 4 ];
268  md5[j*2+1] = hexdigit[ *s & 15 ];
269  }
270  md5[32] = '\0'; // terminate string
271  } else {
272  snprintf( md5, sizeof(md5), "%-32s", "(no_md5_data_available)" );
273  }
274 
275  char timeb[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...
307 static struct in_addr serverHost;
308 static unsigned short serverPort;
309 
310 static bool
311 action(int fd, size_t metasize,
312  const char *fn, const char *url, const SquidMetaList &meta)
313 // purpose: if cmdline-requested, send the purge request to the cache
314 // paramtr: fd (IN): open FD for the object file
315 // metasize (IN): offset into data portion of file (meta data size)
316 // fn (IN): name of the object file
317 // url (IN): URL string stored in the object file
318 // meta (IN): list containing further meta data
319 // returns: true for a successful action, false otherwise. The action
320 // may just print the file, send the purge request or even
321 // remove unwanted files.
322 // globals: ::purgeMode (IN): bit#0 set -> send purge request.
323 // bit#1 set -> remove 404 object files.
324 // ::serverHost (IN): cache host address
325 // ::serverPort (IN): cache port number
326 {
327  static const char* schablone = "PURGE %s HTTP/1.0\r\nAccept: */*\r\n\r\n";
328  struct stat st;
329  long size = ( fstat(fd,&st) == -1 ? -1 : long(st.st_size - metasize) );
330 
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,0,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 
397 static bool
398 match(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 == 0 )
451  flag = action( fd, datastart, fn, (char*) urlmeta->data, meta );
452  else {
453  REList* head = (REList*) list; // YUCK!
454  while ( head != 0 ) {
455  if ( head->match( (char*) urlmeta->data ) ) break;
456  head = head->next;
457  }
458  if ( head != 0 )
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, 0 ) >= 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 
491 static bool
492 filelevel(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 == NULL ) {
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 
531 static bool
532 dirlevel(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 == NULL ) {
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 
568 static int
569 checkForPortOnly(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, '.' ) != 0 ) return -1;
577 
578  // if it is just a number between 0 and 65535, it must be a port
579  char* errstr = 0;
580  unsigned long result = strtoul( arg, &errstr, 0 );
581  if ( result < 65536 && errstr != arg ) return htons(result);
582 
583  return -1;
584 }
585 
586 static void
587 helpMe(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",
592  ::programname );
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 
616 static void
617 parseCommandline(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],'/')) == NULL )
631  ptr=argv[0];
632  else
633  ++ptr;
634  ::programname = ptr;
635 
636  // extract commandline parameters
637  REList* tail = head = 0;
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, NULL, 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 == 0 )
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" )) != NULL ) {
689  unsigned long lineno = 0;
690 #define LINESIZE 512
691  char line[LINESIZE];
692  while ( fgets( line, LINESIZE, rfile ) != NULL ) {
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 == 0 ) 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':
723  ::no_fork = ! ::no_fork;
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 == 0 ) {
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, 0, 0 ) & 0x07 );
767  break;
768  case 's':
769  showme=1;
770  break;
771  case 'v':
772  ::verbose = ! ::verbose;
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 == 0 ) {
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 != 0 );
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",
799  ::programname);
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 != NULL; 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 
837 extern "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 
862 static
863 int
864 makelinebuffered( FILE* fp, const char* fn = 0 )
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, 0, _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 
882 int
883 main( int argc, char* argv[] )
884 {
885  // setup variables
886  REList* list = 0;
887  char* conffile = xstrdup(DEFAULT_CONFIG_FILE);
888  serverPort = htons(DEFAULTPORT);
889  if ( convertHostname(DEFAULTHOST,serverHost) == -1 ) {
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 != 0 );
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 : 0 ) > 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 
static bool match(const char *fn, const REList *list)
Definition: purge.cc:398
regex_t rexp
Definition: purge.cc:162
static bool action(int fd, size_t metasize, const char *fn, const char *url, const SquidMetaList &meta)
Definition: purge.cc:311
bool match(const char *check) const
Definition: purge.cc:186
int assert_copydir(const char *copydir)
Definition: copyout.cc:61
int connectTo(struct in_addr host, unsigned short port, bool nodelay, int sendBufferSize, int recvBufferSize)
Definition: socket.cc:168
struct squidaio_request_t * next
Definition: aiops.cc:51
int readConfigFile(CacheDirVector &cachedir, const char *fn, FILE *debug)
Definition: conffile.cc:54
int opterr
Definition: getopt.c:47
static int sockfd
static char * copydir
Definition: purge.cc:143
@ REG_NOMATCH
Definition: GnuRegex.h:257
void regfree(regex_t *preg)
Definition: GnuRegex.c:4280
static void handler(int signo)
Definition: purge.cc:854
REList * next
Definition: purge.cc:160
@ STORE_META_STD_LFS
Definition: StoreMeta.h:102
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 log_extended(const char *fn, int code, long size, const SquidMetaList *meta)
Definition: purge.cc:258
#define xstrdup
static unsigned purgeMode
Definition: purge.cc:145
static unsigned short serverPort
Definition: purge.cc:308
static bool dirlevel(const char *dirname, const REList *list, bool level=false)
Definition: purge.cc:532
char * optarg
Definition: getopt.c:51
#define REG_ICASE
Definition: GnuRegex.h:230
@ STORE_META_KEY_MD5
Definition: StoreMeta.h:62
static void helpMe(void)
Definition: purge.cc:587
const char * data
Definition: purge.cc:161
int convertHostname(const char *host, in_addr &dst)
Definition: convert.cc:119
~REList()
Definition: purge.cc:178
static bool verbose
Definition: purge.cc:148
static int port
Definition: ldap_backend.cc:69
#define LINESIZE
@ STORE_META_URL
Definition: StoreMeta.h:68
int getopt(int nargc, char *const *nargv, const char *ostr)
Definition: getopt.c:62
char * strerror(int ern)
Definition: strerror.c:22
@ STORE_META_STD
Definition: StoreMeta.h:83
int log_output(const char *fn, int code, long size, const char *url)
Definition: purge.cc:251
int size
Definition: ModDevPoll.cc:76
static bool isxstring(const char *s, size_t testlen)
Definition: purge.cc:235
#define NULL
Definition: types.h:166
#define REG_EXTENDED
Definition: GnuRegex.h:226
static void exiter(void)
Definition: purge.cc:841
int convertPortname(const char *port, unsigned short &dst)
Definition: convert.cc:140
unsigned char code
Definition: html_quote.c:20
static struct in_addr serverHost
Definition: purge.cc:307
#define assert(EX)
Definition: assert.h:19
static bool reminder
Definition: purge.cc:147
SigFunc * Signal(int signo, SigFunc *newhandler, bool doInterrupt)
Definition: signal.cc:62
Definition: purge.cc:155
char * linebuffer
Definition: purge.cc:141
#define DEFAULTPORT
Definition: purge.cc:137
static bool filelevel(const char *directory, const REList *list)
Definition: purge.cc:492
static char errstr[1001]
static bool no_fork
Definition: purge.cc:150
#define xfree
#define dirent_t
Definition: compat_shared.h:71
int regexec(regex_t *preg, const char *string, size_t nmatch, pmatch, int eflags) const
Definition: GnuRegex.c:4188
#define DEFAULTHOST
Definition: purge.cc:133
size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size)
Definition: GnuRegex.c:4245
void psignal(int sig, const char *msg)
Definition: psignal.c:22
squidaio_request_t * head
Definition: aiops.cc:127
static uint32_t debugFlag
Definition: purge.cc:144
int main(int argc, char *argv[])
Definition: purge.cc:883
size_t buffersize
Definition: purge.cc:142
static bool iamalive
Definition: purge.cc:146
static char * concat(const char *start,...)
Definition: purge.cc:202
REList(const char *what, bool doCase)
Definition: purge.cc:165
static int makelinebuffered(FILE *fp, const char *fn=0)
Definition: purge.cc:864
volatile sig_atomic_t term_flag
Definition: purge.cc:140
static int checkForPortOnly(const char *arg)
Definition: purge.cc:569
static StatHist s
static const char * programname
Definition: purge.cc:151
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 bool envelope
Definition: purge.cc:149
#define REG_NOSUB
Definition: GnuRegex.h:239
int regcomp(regex_t *preg, const char *pattern, int cflags)
Definition: GnuRegex.c:4117

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors