1 /*  $Id: prune.cpp 1649 2009-10-19 14:35:01Z terpstra $
2  *
3  *  prune.cpp - Prune obsolete / stale cache files
4  *
5  *  Copyright (C) 2002 - Wesley W. Terpstra
6  *
7  *  License: GPL
8  *
9  *  Authors: 'Wesley W. Terpstra' <wesley@terpstra.ca>
10  *
11  *    This program is free software; you can redistribute it and/or modify
12  *    it under the terms of the GNU General Public License as published by
13  *    the Free Software Foundation; version 2.
14  *
15  *    This program is distributed in the hope that it will be useful,
16  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *    GNU General Public License for more details.
19  *
20  *    You should have received a copy of the GNU General Public License
21  *    along with this program; if not, write to the Free Software
22  *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23  */
24 
25 #define _FILE_OFFSET_BITS 64
26 
27 #include <ConfigFile.h>
28 #include <esort.h>
29 
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/file.h>
33 
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <utime.h>
37 
38 #include <iostream>
39 
40 #include <cstring>
41 #include <cerrno>
42 #include <ctime>
43 #include <cstdlib>
44 
45 #include "PTable.h"
46 
47 using namespace std;
48 
49 bool        verbose = false;
50 bool        purge = false;
51 time_t      modifyTime = 60*60*24*7;
52 time_t      accessTime = 60*60*24*1;
53 
help(const char * name)54 void help(const char* name)
55 {
56 	cerr << "Lurker-prune (v" << VERSION << ") prunes the web-server cache.\n";
57 	cerr << "\n";
58 	cerr << "Usage: " << name << " [-c <config-file>] [-f <frontend>] [-m <d> -a <d> -p -v]\n";
59 	cerr << "\n";
60 	cerr << "\t-c <config-file> Use this config file for lurker settings\n";
61 	cerr << "\t-f <frontend>    Only clear cache from the named frontend        [all]\n";
62 	cerr << "\t-m <days>        Keep cached files for at most this many days      [7]\n";
63 	cerr << "\t-a <days>        Kill cached files not accessed for this many days [1]\n";
64 	cerr << "\t-p               Purge everything even if it appears to not be expired\n";
65 	cerr << "\t-v               Verbose operation\n";
66 	cerr << "\n";
67 	cerr << "Prune obsolete or stale html/xml from the web-server accessible cache.\n";
68 	cerr << "This command should be run at regular intervals from cron for each site.\n";
69 	cerr << "\n";
70 }
71 
execute(const Config & cfg,const string & docroot)72 int execute(const Config& cfg, const string& docroot)
73 {
74 	if (verbose) cout << "Cleaning document root " << docroot << endl;
75 
76 	string docfile = docroot + "/lurker.docroot";
77 	int fd = open(docfile.c_str(), O_RDWR | O_CREAT, 0666);
78 	if (fd == -1)
79 	{
80 		cerr << "open()ing " << docfile << ": " << strerror(errno) << endl;
81 		return 1;
82 	}
83 
84 	struct stat dbuf;
85 	if (fstat(fd, &dbuf) < 0)
86 	{
87 		cerr << "stat()ing " << docfile << ": " << strerror(errno) << endl;
88 		return 1;
89 	}
90 
91 	enum LockState { GOT, FAIL, USED } state = GOT;
92 
93 #ifdef	LOCK_EX
94 	if (flock(fd, LOCK_EX|LOCK_NB) != 0)
95 	{
96 		if (errno == EWOULDBLOCK)
97 			state = USED;
98 		else	state = FAIL;
99 	}
100 #else
101 #ifdef F_SETLK
102 	struct flock lock;
103 	memset(&lock, 0, sizeof(lock));
104 	lock.l_type = F_WRLCK;
105 	lock.l_whence = SEEK_SET;
106 	if (fcntl(fd, F_SETLK, &lock) != 0)
107 	{
108 		if (errno == EACCES || errno == EAGAIN)
109 			state = USED;
110 		else	state = FAIL;
111 	}
112 #endif
113 #endif
114 
115 	if (state == USED)
116 	{
117 		if (verbose) cout << "Already pruning docroot " << docroot << endl;
118 		return 0;
119 	}
120 
121 	if (state == FAIL)
122 	{
123 		cerr << "Locking " << docfile << " failed: " << strerror(errno) << endl;
124 		return 1;
125 	}
126 
127 	std::auto_ptr<ESort::Reader> db(ESort::Reader::opendb(cfg.dbdir + "/db"));
128 	if (!db.get())
129 	{
130 		cerr << "Opening database: " << strerror(errno) << endl;
131 		return 1;
132 	}
133 
134 	time_t beginfix = time(0);
135 
136 	if (chdir(docroot.c_str()) != 0)
137 	{
138 		cerr << "chdir: " << docroot << ": " << strerror(errno) << endl;
139 		return 1;
140 	}
141 
142 	PTable ptable(cfg, db.get(), dbuf.st_mtime, purge, verbose, modifyTime, accessTime);
143 	string ok;
144 
145 	if ((ok = ptable.load()) != "")
146 	{
147 		cerr << "load: " << ok << endl;
148 		return 1;
149 	}
150 
151 	if ((ok = ptable.calc()) != "")
152 	{
153 		cerr << "calc: " << ok << endl;
154 		return 1;
155 	}
156 
157 	if ((ok = ptable.kill()) != "")
158 	{
159 		cerr << "kill: " << ok << endl;
160 		return 1;
161 	}
162 
163 	// set the mtime stamp to beginfix
164 	struct utimbuf touch;
165 	touch.actime = touch.modtime = beginfix;
166 	if (utime("lurker.docroot", &touch) < 0)
167 	{
168 		cerr << "touching " << docfile << ": " << strerror(errno) << endl;
169 		return 1;
170 	}
171 
172 	return 0;
173 }
174 
main(int argc,char ** argv)175 int main(int argc, char** argv)
176 {
177 	int c;
178 
179 	const char* config  = DEFAULT_CONFIG_FILE;
180 	const char* docroot = 0;
181 
182 	srandom(time(0));
183 
184 	while ((c = getopt(argc, (char*const*)argv, "c:f:m:a:vp?")) != -1)
185 	{
186 		switch ((char)c)
187 		{
188 		case 'c':
189 			config = optarg;
190 			break;
191 		case 'f':
192 			docroot = optarg;
193 			break;
194 		case 'm':
195 			modifyTime = atol(optarg)*60*60*24;
196 			if (!modifyTime)
197 			{
198 				cerr << "Modification time is not a number!\n";
199 				return 1;
200 			}
201 			break;
202 		case 'a':
203 			accessTime = atol(optarg)*60*60*24;
204 			if (!accessTime)
205 			{
206 				cerr << "Access time is not a number!\n";
207 				return 1;
208 			}
209 			break;
210 		case 'p':
211 			purge = true;
212 			break;
213 		case 'v':
214 			verbose = true;
215 			break;
216 		default:
217 			help(argv[0]);
218 			return 1;
219 		}
220 	}
221 
222 	while (optind < argc)
223 	{
224 		if (!argv[optind][0])
225 		{	// ignore empty arguments
226 			optind++;
227 			continue;
228 		}
229 
230 		cerr << "Unexpected argument: '" << argv[optind] << "'\n";
231 		return 1;
232 	}
233 
234 	Config cfg;
235 	if (cfg.load(config) != 0)
236 	{
237 		cerr << cfg.getError() << flush;
238 		return 1;
239 	}
240 
241 	if (docroot) execute(cfg, docroot);
242 	else
243 	{
244 		Config::Frontends::const_iterator i,
245 			s = cfg.frontends.begin(),
246 			e = cfg.frontends.end();
247 		for (i = s; i != e; ++i)
248 		{
249 			execute(cfg, i->first);
250 		}
251 	}
252 
253 	return 0;
254 }
255