faxQCleanApp.c++
上传用户:weiyuanprp
上传日期:2020-05-20
资源大小:1169k
文件大小:15k
源码类别:

传真(Fax)编程

开发平台:

C/C++

  1. /* $Id: faxQCleanApp.c++,v 1.8 2008/04/26 22:34:28 faxguy Exp $ */
  2. /*
  3.  * Copyright (c) 1990-1996 Sam Leffler
  4.  * Copyright (c) 1991-1996 Silicon Graphics, Inc.
  5.  * HylaFAX is a trademark of Silicon Graphics
  6.  *
  7.  * Permission to use, copy, modify, distribute, and sell this software and 
  8.  * its documentation for any purpose is hereby granted without fee, provided
  9.  * that (i) the above copyright notices and this permission notice appear in
  10.  * all copies of the software and related documentation, and (ii) the names of
  11.  * Sam Leffler and Silicon Graphics may not be used in any advertising or
  12.  * publicity relating to the software without the specific, prior written
  13.  * permission of Sam Leffler and Silicon Graphics.
  14.  * 
  15.  * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
  16.  * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
  17.  * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
  18.  * 
  19.  * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
  20.  * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
  21.  * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  22.  * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
  23.  * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
  24.  * OF THIS SOFTWARE.
  25.  */
  26. #include "Sys.h"
  27. #include "faxApp.h"
  28. #include "Dictionary.h"
  29. #include "FaxRequest.h"
  30. #include "config.h"
  31. #include <sys/file.h>
  32. #include <ctype.h>
  33. #include <errno.h>
  34. /*
  35.  * HylaFAX Spooling Area Scavenger.
  36.  */
  37. fxDECLARE_StrKeyDictionary(RefDict, u_int)
  38. fxIMPLEMENT_StrKeyPtrValueDictionary(RefDict, u_int)
  39. class faxQCleanApp : public faxApp {
  40. private:
  41.     bool archiving; // enable archival support
  42.     bool forceArchiving; // force archiving even if doneop != "archive"
  43.     bool verbose; // trace interesting actions
  44.     bool trace; // trace all actions
  45.     bool nowork; // do not carry out actions
  46.     time_t minJobAge; // threshold for processing done jobs
  47.     time_t minDocAge; // threshold for purging unref'd docs
  48.     fxStr qFilePrefix;
  49.     RefDict docrefs;
  50.     static const fxStr archDir;
  51.     static const fxStr doneDir;
  52.     static const fxStr docDir;
  53.     static const fxStr tmpDir;
  54.     void scanDirectory(void);
  55.     void collectRefs(const FaxRequest&);
  56.     void archiveJob(const FaxRequest& req);
  57.     void expungeCruft(void);
  58.     bool findTmpInode(ino_t inode);
  59. public:
  60.     faxQCleanApp();
  61.     ~faxQCleanApp();
  62.     void run(void);
  63.     void setJobAge(const char*);
  64.     void setDocAge(const char*);
  65.     void setArchiving(bool);
  66.     void setForceArchiving(bool);
  67.     void setVerbose(bool);
  68.     void setTracing(bool);
  69.     void setNoWork(bool);
  70. };
  71. const fxStr faxQCleanApp::archDir = FAX_ARCHDIR;
  72. const fxStr faxQCleanApp::doneDir = FAX_DONEDIR;
  73. const fxStr faxQCleanApp::docDir = FAX_DOCDIR;
  74. const fxStr faxQCleanApp::tmpDir = FAX_TMPDIR;
  75. faxQCleanApp::faxQCleanApp()
  76. {
  77.     minJobAge = 15*60; // jobs kept max 15 minutes in doneq
  78.     minDocAge = 60*60; // 1 hour threshold on keeping unref'd documents
  79.     archiving = false; // default is to disable job archiving
  80.     forceArchiving = false; // default is not to force archiving
  81.     verbose = false; // log actions to stdout
  82.     nowork = false; // default is to carry out work
  83.     trace = false; // trace work
  84.     qFilePrefix = FAX_SENDDIR "/q";
  85. }
  86. faxQCleanApp::~faxQCleanApp() {}
  87. void faxQCleanApp::setJobAge(const char* s)
  88.     { minJobAge = (time_t) strtoul(s, NULL, 0); }
  89. void faxQCleanApp::setDocAge(const char* s)
  90.     { minDocAge = (time_t) strtoul(s, NULL, 0); }
  91. void faxQCleanApp::setArchiving(bool b) { archiving = b; }
  92. void faxQCleanApp::setForceArchiving(bool b)    { forceArchiving = b; }
  93. void faxQCleanApp::setVerbose(bool b) { verbose = b; }
  94. void faxQCleanApp::setTracing(bool b) { trace = b; }
  95. void faxQCleanApp::setNoWork(bool b) { nowork = b; }
  96. void
  97. faxQCleanApp::run(void)
  98. {
  99.     if (trace)
  100. verbose = true;
  101.     scanDirectory();
  102.     expungeCruft();
  103. }
  104. /*
  105.  * Scan the doneq directory for jobs that need processing.
  106.  */
  107. void
  108. faxQCleanApp::scanDirectory(void)
  109. {
  110.     if (trace)
  111. printf("Scan %s directory for jobs to remove+archive.n",
  112.     (const char*) doneDir);
  113.     DIR* dir = Sys::opendir(doneDir);
  114.     if (dir == NULL) {
  115. printf("%s: Could not scan directory for jobs.n",
  116.     (const char*) doneDir);
  117. return;
  118.     }
  119.     time_t now = Sys::now();
  120.     fxStr path(doneDir | "/");
  121.     for (dirent* dp = readdir(dir); dp; dp = readdir(dir)) {
  122. if (dp->d_name[0] != 'q')
  123.     continue;
  124. fxStr filename(path | dp->d_name);
  125. struct stat sb;
  126. if (Sys::stat(filename, sb) < 0 || !S_ISREG(sb.st_mode)) {
  127.     if (trace)
  128. printf("%s: ignored (cannot stat or not a regular file)n",
  129.     (const char*) filename);
  130.     continue;
  131. }
  132. int fd = Sys::open(filename, O_RDWR);
  133. if (fd >= 0) {
  134.     if (flock(fd, LOCK_SH|LOCK_NB) >= 0) {
  135. FaxRequest* req = new FaxRequest(filename, fd);
  136. bool reject;
  137. if (req->readQFile(reject) && !reject) {
  138.     if (now - sb.st_mtime < minJobAge) {
  139. /*
  140.  * Job is not old enough to implement the ``doneop''
  141.  * action, just collect the references to documents
  142.  * and forget it until later.
  143.  */
  144. if (trace)
  145.     printf("%s: job too new, ignored (for now).n",
  146. (const char*) filename);
  147. collectRefs(*req);
  148.     } else if (forceArchiving || (archiving && strncmp(req->doneop, "archive", 7) == 0)) {
  149. /*
  150.  * Job should be archived, pass the jobid
  151.  * value to the archive script for archiving.
  152.  */
  153. if (verbose)
  154.     printf("JOB %s: archive (%s)%s.n"
  155. , (const char*) req->jobid
  156. , (const char*) req->doneop
  157. , nowork ? " (not done)" : ""
  158.     );
  159. if (!nowork)
  160.     archiveJob(*req);
  161.     } else {
  162. if (verbose)
  163.     printf("JOB %s: remove (%s) %s.n"
  164. , (const char*) req->jobid
  165. , (const char*) req->doneop
  166. , nowork ? " (not done)" : ""
  167.     );
  168. if (!nowork)
  169.     Sys::unlink(req->qfile);
  170.     }
  171. } else {
  172.     /*
  173.      * Job file is corrupted or unreadable in some
  174.      * way.  We were able to lock the file so we
  175.      * assume it is in a determinant state and not
  176.      * just in the process of being created; and
  177.      * so therefore can be removed.
  178.      */
  179.     if (verbose)
  180. printf("%s: malformed queue file: removen",
  181.     (const char*) filename);
  182.     if (!nowork)
  183. Sys::unlink(filename);
  184. }
  185. delete req; // NB: implicit close+unlock
  186.     } else {
  187. if (verbose)
  188.     printf("%s: flock(LOCK_SH|LOCK_NB): %sn",
  189. (const char*) filename, strerror(errno));
  190. Sys::close(fd);
  191.     }
  192. } else if (verbose)
  193.     printf("%s: open: %sn", (const char*) filename, strerror(errno));
  194.     }
  195.     if (trace)
  196. printf("Done scanning %s directoryn", (const char*) doneDir);
  197.     closedir(dir);
  198. }
  199. /*
  200.  * Record references to documents.
  201.  */
  202. void
  203. faxQCleanApp::collectRefs(const FaxRequest& req)
  204. {
  205.     for (u_int i = 0, n = req.items.length(); i < n; i++) {
  206. const FaxItem& fitem = req.items[i];
  207. switch (fitem.op) {
  208. case FaxRequest::send_pdf:
  209. case FaxRequest::send_pdf_saved:
  210. case FaxRequest::send_tiff:
  211. case FaxRequest::send_tiff_saved:
  212. case FaxRequest::send_postscript:
  213. case FaxRequest::send_postscript_saved:
  214. case FaxRequest::send_pcl:
  215. case FaxRequest::send_pcl_saved:
  216.     if (trace)
  217. printf("JOB %s: reference %sn",
  218.     (const char*) req.jobid,
  219.     (const char*) fitem.item);
  220.     docrefs[fitem.item]++;
  221.     break;
  222. }
  223.     }
  224. }
  225. /*
  226.  * Archive completed fax job.
  227.  */
  228. void
  229. faxQCleanApp::archiveJob(const FaxRequest& req)
  230. {
  231.     // hand the archiving task off to the archiving command
  232.     fxStr cmd("bin/archive"
  233. | quote |             quoted(req.jobid) | enquote
  234.     );
  235.     runCmd(cmd, true);
  236. }
  237. /*
  238.  * Scan the tmp directory and look for a specific
  239.  * inode being used.
  240.  */
  241. bool
  242. faxQCleanApp::findTmpInode(ino_t inode)
  243. {
  244.     if (trace)
  245. printf("Scan %s directory for links to inode %d.n",
  246.     (const char*) tmpDir, (int) inode);
  247.     DIR* dir = Sys::opendir(tmpDir);
  248.     if (dir == NULL) {
  249. printf("%s: Could not scan directory for links.n",
  250.     (const char*) tmpDir);
  251. return (false);
  252.     }
  253.     for (dirent* dp = readdir(dir); dp; dp = readdir(dir)) {
  254. fxStr file(tmpDir | "/" | dp->d_name);
  255. struct stat sb;
  256. if (Sys::stat(file, sb) < 0 || !S_ISREG(sb.st_mode)) {
  257.     if (trace)
  258. printf("%s: ignored, cannot stat or not a regular filen",
  259.     (const char*) file);
  260.     continue;
  261. }
  262. if (sb.st_ino == inode) { // found our match
  263.     if (trace)
  264. printf("%s: inode %d match foundn",
  265.     (const char*) file, (int) inode);
  266.     return (true);
  267. }
  268.     }
  269.     if (trace) printf("inode %d match not foundn", (int) inode);
  270.     return (false);
  271. }
  272. /*
  273.  * Scan the document directory and look for stuff
  274.  * that has no references in the sendq or doneq.
  275.  * These documents are removed if they are older
  276.  * than some threshold.
  277.  */
  278. void
  279. faxQCleanApp::expungeCruft(void)
  280. {
  281.     if (trace)
  282. printf("Scan %s directory and remove unreferenced documents.n",
  283.     (const char*) docDir);
  284.     DIR* dir = Sys::opendir(docDir);
  285.     if (dir == NULL) {
  286. printf("%s: Could not scan directory for unreferenced documents.n",
  287.     (const char*) docDir);
  288. return;
  289.     }
  290.     time_t now = Sys::now();
  291.     fxStr path(docDir | "/");
  292.     for (dirent* dp = readdir(dir); dp; dp = readdir(dir)) {
  293. /*
  294.  * By convention, document files are known to be named
  295.  * with a leading ``doc'' or ``cover'' prefix.  We ignore
  296.  * any other files that may be present.
  297.  */
  298. if (strncmp(dp->d_name, "doc", 3) && strncmp(dp->d_name, "cover", 5)) {
  299.     if (trace)
  300. printf("%s%s: ignored, no leading "doc" or "cover" in filenamen",
  301.     (const char*) path, dp->d_name);
  302.     continue;
  303. }
  304. fxStr file(path | dp->d_name);
  305. struct stat sb;
  306. if (Sys::stat(file, sb) < 0 || !S_ISREG(sb.st_mode)) {
  307.     if (trace)
  308. printf("%s: ignored, cannot stat or not a regular filen",
  309.     (const char*) file);
  310.     continue;
  311. }
  312. /*
  313.  * During job preparation stage (i.e. when hfaxd has the original 
  314.  * in the tmp directory and a link in the docq directory) document 
  315.  * files will have multiple hard links to them, and we don't want 
  316.  * to remove those links as the job may not even have an associated
  317.  * sendq file yet.  So if the file has multiple links then we check
  318.  * do see if the original is in the tmp directory, and if not, then
  319.  * we ignore the fact that there are multiple hard links - as they
  320.  * get used in job grouping.
  321.  */
  322. if (sb.st_nlink > 1) { // can't be orphaned yet
  323.     if (trace)
  324. printf("%s: file has %u links, check for associated tmp filen",
  325.     (const char*) file, sb.st_nlink);
  326.     if (findTmpInode(sb.st_ino))
  327. continue;
  328. }
  329. if (docrefs.find(file)) { // referenced from doneq
  330.     if (trace)
  331. printf("%s: ignored, file has %u referencesn",
  332.     (const char*) file, docrefs[file]);
  333.     continue;
  334. }
  335. if (now - sb.st_mtime < minDocAge) { // not old enough
  336.     if (trace)
  337. printf("%s: ignored, file is too new to removen",
  338.     (const char*) file);
  339.     continue;
  340. }
  341. /*
  342.  * Document may be referenced from a job in the sendq
  343.  * or it may be orphaned.  Files with base-style names
  344.  * represent documents that can only have references
  345.  * from jobs in the doneq--these can safely be removed.
  346.  * Other documents are only removed if they are older
  347.  * than the threshold (checked above).
  348.  */
  349. u_int l = file.nextR(file.length(), '.');
  350. u_int k = file.nextR(file.length(), ';');
  351. if (l != 0 && l < file.length() && isdigit(file[l])) {
  352.     /*
  353.      * Filename has a jobid suffix (or should);
  354.      * look to see if the job still exists in
  355.      * the sendq.
  356.      */
  357.     fxStr qfile = qFilePrefix | file.tail(file.length()-l);
  358.     if (Sys::stat(qfile, sb) == 0) {
  359. if (trace)
  360.     printf("%s: file looks to be referenced by jobn",
  361. (const char*) file);
  362. continue; // skip, in use
  363.     } else if (trace)
  364. printf("%s: file has no matching %sn", 
  365.     (const char*) file, (const char*)qfile);
  366. } else if ((l != 0 && l < file.length() && strcmp(&file[l], "cover") == 0) ||
  367.                 (k == 0 && strncmp(&file[docDir.length()+1], "cover", 5) == 0)) {
  368.     /*
  369.      * Cover page document has a jobid suffix
  370.      * at the front; look to see if the job still
  371.      * exists in the sendq.
  372.      */
  373.     u_int prefix = docDir.length()+1;
  374.     if (strncmp(&file[prefix], "cover", 5) == 0)
  375. prefix += 5;
  376.     else
  377. prefix += 3; // older doc####.cover type
  378.     u_int len = (l==0 ? file.length() : l-1);
  379.     fxStr qfile = qFilePrefix | file.extract(prefix, len-prefix);
  380.     if (Sys::stat(qfile, sb) == 0) {
  381. if (trace)
  382.     printf("%s: file looks to be referenced by jobn",
  383. (const char*) file);
  384. continue; // skip, in use
  385.     }
  386. }
  387. else if(k != 0 && k < file.length()) {
  388.     // Check to make sure we don't delete a file with a ';'
  389.     // suffix, when the PS.jobid version of the file still
  390.     // exists.
  391.     char        *base;
  392.     u_int         sl=k-docDir.length()-1-1; // removing docDir,'/' and trailing ';'
  393.     base=(char *)malloc(sl+1);
  394.     strncpy(base, &file[docDir.length()+1], sl);
  395.     base[sl]=0;
  396.     bool        got_match = false;
  397.     DIR        *dir1 = Sys::opendir(docDir);
  398.     if(dir1 == 0) {
  399. printf("%s: Could not scan directory for base file.n",
  400.     (const char *) docDir);
  401.     continue;
  402.     }
  403.     for(dirent *dp1 = readdir(dir1); dp1; dp1 = readdir(dir1)) {
  404. if(strlen(dp1->d_name) > sl && dp1->d_name[sl] == '.' &&
  405.         ( strncmp(base, dp1->d_name, sl) == 0)) {
  406.     // Found match
  407.     if(trace)
  408. printf("%s: found match to base '%s', skipping.n",
  409.     (const char *)file, dp1->d_name);
  410.     got_match = true;
  411.     break;
  412. }
  413.     }
  414.     closedir(dir1);
  415.     if(got_match) {
  416. free(base);
  417. continue;
  418.     }
  419.     if(trace)
  420. printf("%s: did not find base '%s' match.n", 
  421.     (const char *) file, base);
  422.     free(base);
  423. }
  424. if (nowork || Sys::unlink(file) >= 0) {
  425.     if (verbose)
  426. printf("DOC %s: unreferenced document removed%s.n"
  427.     , (const char*) file
  428.     , nowork ? " (not done)" : ""
  429. );
  430. } else {
  431.     if (verbose)
  432. printf("%s: error removing unreferenced document: %s.n",
  433.     (const char*) file, strerror(errno));
  434. }
  435.     }
  436.     if (trace)
  437. printf("Done scanning %s directoryn", (const char*) docDir);
  438.     closedir(dir);
  439. }
  440. static void
  441. usage(const char* appName)
  442. {
  443.     fprintf(stderr,
  444. "usage: %s [-a] [-j time] [-d time] [-q queue-directory]n",
  445. appName);
  446. }
  447. int
  448. main(int argc, char** argv)
  449. {
  450.     faxApp::setupLogging("FaxQCleaner");
  451.     fxStr appName = argv[0];
  452.     u_int l = appName.length();
  453.     appName = appName.tokenR(l, '/');
  454.     faxApp::setupPermissions();
  455.     faxApp::setOpts("j:d:q:aAntv");
  456.     faxQCleanApp app;
  457.     fxStr queueDir(FAX_SPOOLDIR);
  458.     for (GetoptIter iter(argc, argv, faxApp::getOpts()); iter.notDone(); iter++)
  459. switch (iter.option()) {
  460. case 'a': app.setArchiving(true); break;
  461. case 'A': app.setForceArchiving(true); break;
  462. case 'j': app.setJobAge(optarg); break;
  463. case 'd': app.setDocAge(optarg); break;
  464. case 'n': app.setNoWork(true); break;
  465. case 'q': queueDir = iter.optArg(); break;
  466. case 't': app.setTracing(true); break;
  467. case 'v': app.setVerbose(true); break;
  468. case '?': usage(appName);
  469. }
  470.     if (Sys::chdir(queueDir) < 0) {
  471. fprintf(stderr, "%s: Can not change directory: %s.n",
  472.     (const char*) queueDir, strerror(errno));
  473. exit(-1);
  474.     }
  475.     app.run();
  476.     return 0;
  477. }