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