00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031 #include "config.h"
00032
00033 #include <string.h>
00034 #include <getopt.h>
00035 #include <sys/param.h>
00036 #include <sys/stat.h>
00037 #include <sys/time.h>
00038 #include <sys/types.h>
00039 #include <dirent.h>
00040 #include <unistd.h>
00041 #ifdef TIME_WITH_SYS_TIME
00042 #include <sys/time.h>
00043 #endif
00044 #ifdef HAVE_SETPRIORITY
00045 #include <sys/resource.h>
00046 #endif
00047
00048 #include <string>
00049 #include <vector>
00050 #include <algorithm>
00051
00052 #include "Debug.h"
00053 #include "setugid.h"
00054 #include "Config.h"
00055 #include "Logger.h"
00056 #include "NServer.h"
00057
00058 using namespace std;
00059
00060 #ifndef WITH_UNIQUE_PACKAGE_NAME
00061 #undef PACKAGE
00062 #define PACKAGE PACKAGE_NEWSCACHECLEAN
00063 #endif
00064
00065 #define MAX(a,b) (((a)>(b))?(a):(b))
00066 #define FILE_ACCESS_TABLE_ENTRIES 100000
00067
00068 Logger slog;
00069 const char *cmnd;
00070 Config Cfg;
00071
00072 class Entry {
00073 public:
00074 Entry (const char *path, time_t time, long blocks)
00075 : Path(path), atime(time), blocks(blocks) {};
00076
00077 void print (ostream &out) {
00078 out << Path << " " << atime << " " << blocks << " " << endl;
00079 };
00080
00081 string Path;
00082 time_t atime;
00083 long blocks;
00084 };
00085
00086
00087 vector<Entry*> pvector;
00088 bool sort_entries (const Entry *l, const Entry *r);
00089 void clean(const char *cpath);
00090
00091 struct cache_statistic {
00092 size_t stPurgedDbCnt;
00093 size_t stPurgedDirCnt;
00094 size_t stPurgedArtCnt;
00095 size_t stPurgedOutArtCnt;
00096
00097 size_t stDbCnt;
00098 size_t stDirCnt;
00099 size_t stArtCnt;
00100 size_t stOutArtCnt;
00101 size_t stPurgedDbSize;
00102 size_t stPurgedArtSize;
00103 size_t stPurgedOutArtSize;
00104
00105 size_t stDbSize;
00106 size_t stArtSize;
00107 size_t stOutArtSize;
00108 };
00109
00110 long _blocks = 0;
00111
00112 static void print_statistic(ostream &out, struct cache_statistic *pS);
00113 void remove_elements(long max_blocks, struct cache_statistic *pS);
00114
00115 static void print_statistic(ostream &out, struct cache_statistic *pS)
00116 {
00117 out << "PurgedDbCnt: " << pS->stPurgedDbCnt << endl;
00118 out << "PurgedDirCnt: " << pS->stPurgedDirCnt << endl;
00119 out << "PurgedArtCnt: " << pS->stPurgedArtCnt << endl;
00120 out << "PurgedOutArtCnt: " << pS->stPurgedOutArtCnt << endl;
00121 out << "DbCnt: " << pS->stDbCnt << endl;
00122 out << "DirCnt: " << pS->stDirCnt << endl;
00123 out << "ArtCnt: " << pS->stArtCnt << endl;
00124 out << "OutArtCnt: " << pS->stOutArtCnt << endl;
00125 out << "PurgedDbSize: " << pS->stPurgedDbSize / 2 /
00126 1024 << "Mb" << endl;
00127 out << "PurgedArtSize: " << pS->stPurgedArtSize / 2 /
00128 1024 << "Mb" << endl;
00129 out << "DbSize: " << pS->stDbSize / 2 / 1024 << "Mb" << endl;
00130 out << "ArtSize: " << pS->stArtSize / 2 / 1024 << "Mb" << endl;
00131 out << "OutArtSize: " << pS->stOutArtSize /2 / 1024 << "MB" << endl;
00132 }
00133
00134 bool sort_entries (const Entry *l, const Entry *r)
00135 {
00136 if (l->atime < r->atime)
00137 return 1;
00138 else
00139 return 0;
00140 }
00141
00142
00143 void remove_elements(long max_blocks, struct cache_statistic *pS)
00144 {
00145 int isDb;
00146 char cBuffer[MAXPATHLEN];
00147 vector<Entry *>::iterator begin, end;
00148
00149 for (begin=pvector.begin(), end=pvector.end();
00150 begin != end; begin++) {
00151 (*begin)->Path.length();
00152 if (strcmp((*begin)->Path.c_str() + (*begin)->Path.length() - 3, ".db") == 0) {
00153 isDb = 1;
00154 pS->stDbCnt++;
00155 pS->stDbSize += (*begin)->blocks;
00156 } else {
00157 isDb = 0;
00158 if ((*begin)->atime != 0) {
00159 pS->stArtCnt++;
00160 pS->stArtSize += (*begin)->blocks;
00161 } else {
00162 pS->stOutArtCnt++;
00163 pS->stOutArtSize += (*begin)->blocks;
00164 }
00165 }
00166 if ((*begin)->atime != 0 && _blocks <= max_blocks)
00167 continue;
00168 if (unlink((*begin)->Path.c_str()) == 0) {
00169 _blocks -= (*begin)->blocks;
00170 if (!isDb) {
00171 if ((*begin)->atime != 0) {
00172 pS->stPurgedArtCnt++;
00173 pS->stPurgedArtSize += (*begin)->blocks;
00174 } else {
00175 pS->stPurgedOutArtCnt++;
00176 pS->stPurgedOutArtSize += (*begin)->blocks;
00177 }
00178 } else {
00179 pS->stPurgedDbCnt++;
00180 pS->stPurgedDbSize += (*begin)->blocks;
00181 strcpy(cBuffer, (*begin)->Path.c_str());
00182 cBuffer[strlen(cBuffer) - 3] = '\0';
00183 if (rmdir(cBuffer) == 0) {
00184 pS->stPurgedDirCnt++;
00185 } else if (errno == ENOTEMPTY) {
00186 slog.p(Logger::Debug)
00187 << "Error rmdir "
00188 << "(Directory not empty) "
00189 << (*begin)->Path << "\n";
00190 } else {
00191 slog.p(Logger::Error)
00192 << "Error rmdir "
00193 << (*begin)->Path << "\n";
00194 }
00195 }
00196 } else {
00197 slog.
00198 p(Logger::
00199 Error) << "Error unlink " << (*begin)->Path << "\n";
00200 }
00201 }
00202 }
00203
00204 void clean(const char *cpath)
00205 {
00206 VERB(slog.p(Logger::Debug) << "clean(" << cpath << ")\n");
00207 DIR *d;
00208 time_t dbatime;
00209 char buf[MAXPATHLEN];
00210
00211 struct dirent *f;
00212 struct stat s;
00213
00214 sprintf(buf, "%s/.db", cpath);
00215
00216 if (strcmp(buf, "./.artSpool") == 0)
00217 return;
00218 if (strcmp(buf, "./.badArticles") == 0)
00219 return;
00220 if (stat(buf, &s) == 0) {
00221 dbatime = MAX(s.st_atime, s.st_mtime);
00222 pvector.push_back( new Entry (buf, dbatime, s.st_blocks) );
00223 _blocks += s.st_blocks;
00224 --dbatime;
00225 } else {
00226 dbatime = 0;
00227 }
00228
00229 if ((d = opendir(cpath)) == NULL) {
00230 slog.p(Logger::Error) << "cannot open " << cpath << "\n";
00231 return;
00232 }
00233 while ((f = readdir(d)) != NULL) {
00234 if (strcmp(f->d_name, ".") == 0)
00235 continue;
00236 if (strcmp(f->d_name, "..") == 0)
00237 continue;
00238 sprintf(buf, "%s/%s", cpath, f->d_name);
00239 if (stat(buf, &s) == 0) {
00240 if (S_ISDIR(s.st_mode))
00241 clean(buf);
00242 } else {
00243 slog.
00244 p(Logger::
00245 Error) << "cannot stat " << buf << "\n";
00246 }
00247 }
00248 closedir(d);
00249
00250 if ((d = opendir(cpath)) == NULL) {
00251 slog.p(Logger::Error) << "cannot reopen " << cpath << "\n";
00252 return;
00253 }
00254
00255 string ngname (cpath);
00256
00257 ngname.replace (0, 2, "");
00258 int i;
00259 while ((i=ngname.find ("/")) != -1) {
00260 ngname[i] = '.';
00261 }
00262 NVNewsgroup *ng;
00263 OverviewFmt *fmt = new OverviewFmt ();
00264 int firstnr;
00265 int lastnr;
00266 try {
00267 string dbname = cpath;
00268 dbname += "/.db";
00269 struct stat s;
00270 if (stat (dbname.c_str(), &s) == 0) {
00271 ng = new NVNewsgroup (fmt,
00272 (const char *) Cfg.SpoolDirectory,
00273 ngname.c_str());
00274 } else {
00275 ng = NULL;
00276 }
00277 } catch (NoSuchGroupError e) {
00278 ng = NULL;
00279 }
00280
00281 NC_CATCH_ALL ("Error, please report bug!");
00282 if (ng != NULL) {
00283 firstnr = ng->firstnbr ();
00284 lastnr = ng->lastnbr ();
00285 delete ng;
00286 }
00287 string art;
00288 int nr;
00289 while ((f = readdir(d)) != NULL) {
00290 sprintf(buf, "%s/%s", cpath, f->d_name);
00291 if (stat(buf, &s) == 0) {
00292 if (S_ISREG(s.st_mode) && dbatime != 0 &&
00293 strncmp(f->d_name, ".art", 4) == 0) {
00294 time_t at = MAX(s.st_atime, s.st_mtime);
00295 if (dbatime <= at)
00296 at = dbatime;
00297 art = f->d_name;
00298 art.replace (0,4, "");
00299 if (sscanf (art.c_str(), "%d", &nr) != 1) {
00300 slog.p(Logger::Error) << "error "
00301 << "parsing " << f->d_name;
00302 } else if ( ng == NULL | nr < firstnr || nr > lastnr) {
00303 at = 0;
00304 }
00305 pvector.push_back (new Entry(buf, at,
00306 s.st_blocks) );
00307 _blocks += s.st_blocks;
00308 }
00309 } else {
00310 slog.
00311 p(Logger::
00312 Error) << "cannot stat " << buf << "\n";
00313 }
00314 }
00315 closedir(d);
00316 return;
00317 }
00318
00319
00320
00321 #define USAGE \
00322 " -h, --help\n"\
00323 " Show summary of options.\n"\
00324 "\n"\
00325 " -v, --version\n"\
00326 " Show version of program.\n"\
00327 "\n"\
00328 " -c --configuration config-file\n"\
00329 "\n"\
00330 " -s --statistic\n"\
00331 " Show statistic informations.\n"\
00332 "\n"\
00333 " -p --print-purgetable\n"\
00334 " Print the sorted purge table.\n"\
00335 "\n"\
00336 " -t --try\n"\
00337 " Try, not really removing files.\n"\
00338
00339
00340 int main(int argc, char **argv)
00341 {
00342 #ifndef WITH_SYSLOG
00343 char logfile[MAXPATHLEN];
00344 time_t t;
00345 pid_t p;
00346 #endif
00347 char conffile[MAXPATHLEN];
00348 int opt_config = 0;
00349 int c, aerr = 0;
00350 struct cache_statistic Stat;
00351 int nice;
00352 int stat=0;
00353 int try_flag = 0;
00354 char p_flag=0;
00355 vector<Entry *>::iterator begin, end;
00356
00357 memset ((void *) &Stat, 0, sizeof (struct cache_statistic));
00358
00359 sprintf(conffile, "%s/newscache.conf", SYSCONFDIR);
00360
00361 cmnd = argv[0];
00362 while (1) {
00363 int option_index = 0;
00364 static struct option long_options[] = {
00365 {"version", 0, 0, 'v'},
00366 {"help", 0, 0, 'h'},
00367 {"configuration", 1, 0, 'c'},
00368 {"statistic", 0, 0, 's'},
00369 {"try", 0, 0, 't'},
00370 {"print-purgetable", 0, 0, 'p'},
00371 {0, 0, 0, 0}
00372 };
00373
00374 c = getopt_long (argc, argv, "vhc:stp", long_options,
00375 &option_index);
00376
00377 if (c == -1)
00378 break;
00379
00380 switch (c) {
00381 case 'v':
00382 cout << PACKAGE << " " << VERSION << endl;
00383 exit (0);
00384 break;
00385
00386 case 'h':
00387 cout << "Usage: " << cmnd << endl;
00388 cout << USAGE << endl;
00389 exit (0);
00390 break;
00391
00392 case 'c':
00393 strcpy (conffile, optarg);
00394 ++opt_config;
00395 break;
00396
00397 case 's':
00398 stat = 1;
00399 break;
00400
00401 case 't':
00402 try_flag = 1;
00403 break;
00404
00405 case 'p':
00406 p_flag = 1;
00407 break;
00408
00409 default:
00410 aerr = 1;
00411 break;
00412 }
00413 }
00414
00415 if (aerr || optind != argc) {
00416 cerr << "Usage: " << cmnd << " [options]\n" << USAGE;
00417 exit(1);
00418 }
00419
00420 try {
00421 Cfg.read(conffile);
00422
00423 }
00424 catch(IOError & io) {
00425 cerr << "unexpected EOF in " << conffile << "\n";
00426 exit(2);
00427 }
00428 catch(SyntaxError & se) {
00429 cerr << se._errtext << "\n";
00430 exit(2);
00431 }
00432
00433 #ifdef WITH_SYSLOG
00434 slog.open(PACKAGE, LOG_NDELAY | LOG_PID, LOG_NEWS);
00435 #else
00436 sprintf (logfile, "%s/newscache_newscacheclean.log", Cfg.LogDirectory);
00437 slog.open (logfile);
00438 #endif
00439 slog.p(Logger::Info) << "cleaning spool directory\n";
00440
00441
00442
00443 #ifdef HAVE_SETPRIORITY
00444 errno = 0;
00445 nice = Cfg.NiceServer;
00446 nice += getpriority(PRIO_PROCESS, 0);
00447 if (nice == -1 && errno != 0) {
00448 slog.
00449 p(Logger::
00450 Error) << "getpriority failed: " << strerror(errno)
00451 << "\n";
00452 } else {
00453 if (setpriority(PRIO_PROCESS, 0, nice) == -1)
00454 slog.
00455 p(Logger::
00456 Error) << "setpriority failed: " <<
00457 strerror(errno) << "\n";
00458 }
00459 #endif
00460
00461 if (chdir(Cfg.SpoolDirectory) < 0) {
00462 slog.
00463 p(Logger::
00464 Error) <<
00465 "cannot change to spool directory -- exiting\n";
00466 exit(1);
00467 }
00468 if (getuid() == CONF_UIDROOT)
00469 setugid(Cfg.Username, Cfg.Groupname);
00470 clean(".");
00471 sort (pvector.begin(), pvector.end(), sort_entries);
00472 if (p_flag) {
00473 cout << "---------------- Purge table ----------------" << endl;
00474 for (begin=pvector.begin(), end=pvector.end();
00475 begin != end; begin++) {
00476 (*begin)->print (cout);
00477 }
00478 cout << "---------------- Purge table ----------------" << endl;
00479 }
00480 if (!try_flag) {
00481 remove_elements(Cfg.SpoolSize * 2, &Stat);
00482 }
00483 if (stat) {
00484 print_statistic(cout, &Stat);
00485 }
00486
00487 slog.p(Logger::Info) << "|SpoolDirectory|="
00488 << (unsigned int) (_blocks / 2) << "K\n";
00489 return 0;
00490 }