chiark / gitweb /
Some macros replace some boilerplate formulae
[innduct.git] / expire / expireover.c
1 /*  $Id: expireover.c 6708 2004-05-16 19:59:26Z rra $
2 **
3 **  Expire the overview database.
4 **
5 **  This program handles the nightly expiration of overview information.  If
6 **  groupbaseexpiry is true, this program also handles the removal of
7 **  articles that have expired.  It's separate from the process that scans
8 **  and expires the history file.
9 */
10
11 #include "config.h"
12 #include "clibrary.h"
13 #include <errno.h>
14 #include <pwd.h>
15 #include <signal.h>
16 #include <syslog.h>
17 #include <time.h>
18
19 #include "inn/innconf.h"
20 #include "inn/messages.h"
21 #include "inn/qio.h"
22 #include "libinn.h"
23 #include "ov.h"
24 #include "paths.h"
25 #include "storage.h"
26
27 static const char usage[] = "\
28 Usage: expireover [-ekNpqs] [-w offset] [-z rmfile] [-Z lowmarkfile]\n";
29
30 /* Set to 1 if we've received a signal; expireover then terminates after
31    finishing the newsgroup that it's working on (this prevents corruption of
32    the overview by killing expireover). */
33 static volatile sig_atomic_t signalled = 0;
34
35
36 /*
37 **  Handle a fatal signal and set signalled.  Restore the default signal
38 **  behavior after receiving a signal so that repeating the signal will kill
39 **  the program immediately.
40 */
41 static RETSIGTYPE
42 fatal_signal(int sig)
43 {
44     signalled = 1;
45     xsignal(sig, SIG_DFL);
46 }
47
48
49 /*
50 **  Change to the news user if possible, and if not, die.  Used for operations
51 **  that may create new database files so as not to mess up the ownership.
52 */
53 static void
54 setuid_news(void)
55 {
56     struct passwd *pwd;
57
58     pwd = getpwnam(NEWSUSER);
59     if (pwd == NULL)
60         die("can't resolve %s to a UID (account doesn't exist?)", NEWSUSER);
61     if (getuid() == 0)
62         setuid(pwd->pw_uid);
63     if (getuid() != pwd->pw_uid)
64         die("must be run as %s", NEWSUSER);
65 }
66
67
68 int
69 main(int argc, char *argv[])
70 {
71     int option, low;
72     char *line, *p;
73     QIOSTATE *qp;
74     bool value;
75     OVGE ovge;
76     char *active_path = NULL;
77     char *lowmark_path = NULL;
78     char *path;
79     FILE *lowmark = NULL;
80     bool purge_deleted = false;
81     bool always_stat = false;
82     struct history *history;
83
84     /* First thing, set up logging and our identity. */
85     openlog("expireover", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
86     message_program_name = "expireover";
87
88     /* Set up some default options for group-based expiration, although none
89        of these will be used if groupbaseexpiry isn't true. */
90     ovge.earliest = false;
91     ovge.keep = false;
92     ovge.ignoreselfexpire = false;
93     ovge.usepost = false;
94     ovge.quiet = false;
95     ovge.timewarp = 0;
96     ovge.filename = NULL;
97     ovge.delayrm = false;
98
99     /* Parse the command-line options. */
100     while ((option = getopt(argc, argv, "ef:kNpqsw:z:Z:")) != EOF) {
101         switch (option) {
102         case 'e':
103             ovge.earliest = true;
104             break;
105         case 'f':
106             active_path = xstrdup(optarg);
107             break;
108         case 'k':
109             ovge.keep = true;
110             break;
111         case 'N':
112             ovge.ignoreselfexpire = true;
113             break;
114         case 'p':
115             ovge.usepost = true;
116             break;
117         case 'q':
118             ovge.quiet = true;
119             break;
120         case 's':
121             always_stat = true;
122             break;
123         case 'w':
124             ovge.timewarp = (time_t) (atof(optarg) * 86400.);
125             break;
126         case 'z':
127             ovge.filename = optarg;
128             ovge.delayrm = true;
129             break;
130         case 'Z':
131             lowmark_path = optarg;
132             break;
133         default:
134             fprintf(stderr, "%s", usage);
135             exit(1);
136         }
137     }
138     if (ovge.earliest && ovge.keep)
139         die("-e and -k cannot be specified at the same time");
140
141     /* Initialize innconf. */
142     if (!innconf_read(NULL))
143         exit(1);
144
145     /* Change to the news user if necessary. */
146     setuid_news();
147
148     /* Initialize the lowmark file, if one was requested. */
149     if (lowmark_path != NULL) {
150         if (unlink(lowmark_path) < 0 && errno != ENOENT)
151             syswarn("can't remove %s", lowmark_path);
152         lowmark = fopen(lowmark_path, "a");
153         if (lowmark == NULL)
154             sysdie("can't open %s", lowmark_path);
155     }
156
157     /* Set up the path to the list of newsgroups we're going to use and open
158        that file.  This could be stdin. */
159     if (active_path == NULL) {
160         active_path = concatpath(innconf->pathdb, _PATH_ACTIVE);
161         purge_deleted = true;
162     }
163     if (strcmp(active_path, "-") == 0) {
164         qp = QIOfdopen(fileno(stdin));
165         if (qp == NULL)
166             sysdie("can't reopen stdin");
167     } else {
168         qp = QIOopen(active_path);
169         if (qp == NULL)
170             sysdie("can't open active file (%s)", active_path);
171     }
172     free(active_path);
173
174     /* open up the history manager */
175     path = concatpath(innconf->pathdb, _PATH_HISTORY);
176     history = HISopen(path, innconf->hismethod, HIS_RDONLY);
177     free(path);
178
179     /* Initialize the storage manager.  We only need to initialize it in
180        read/write mode if we're not going to be writing a separate file for
181        the use of fastrm. */
182     if (!ovge.delayrm) {
183         value = true;
184         if (!SMsetup(SM_RDWR, &value))
185             die("can't setup storage manager read/write");
186     }
187     value = true;
188     if (!SMsetup(SM_PREOPEN, &value))
189         die("can't setup storage manager");
190     if (!SMinit())
191         die("can't initialize storage manager: %s", SMerrorstr);
192
193     /* Initialize and configure the overview subsystem. */
194     if (!OVopen(OV_READ | OV_WRITE))
195         die("can't open overview database");
196     if (innconf->groupbaseexpiry) {
197         time(&ovge.now);
198         if (!OVctl(OVGROUPBASEDEXPIRE, &ovge))
199             die("can't configure group-based expire");
200     }
201     if (!OVctl(OVSTATALL, &always_stat))
202         die("can't configure overview stat behavior");
203
204     /* We want to be careful about being interrupted from this point on, so
205        set up our signal handlers. */
206     xsignal(SIGTERM, fatal_signal);
207     xsignal(SIGINT, fatal_signal);
208     xsignal(SIGHUP, fatal_signal);
209
210     /* Loop through each line of the input file and process each group,
211        writing data to the lowmark file if desired. */
212     line = QIOread(qp);
213     while (line != NULL && !signalled) {
214         p = strchr(line, ' ');
215         if (p != NULL)
216             *p = '\0';
217         p = strchr(line, '\t');
218         if (p != NULL)
219             *p = '\0';
220         if (!OVexpiregroup(line, &low, history))
221             warn("can't expire %s", line);
222         else if (lowmark != NULL && low != 0)
223             fprintf(lowmark, "%s %u\n", line, low);
224         line = QIOread(qp);
225     }
226     if (signalled)
227         warn("received signal, exiting");
228
229     /* If desired, purge all deleted newsgroups. */
230     if (!signalled && purge_deleted)
231         if (!OVexpiregroup(NULL, NULL, history))
232             warn("can't expire deleted newsgroups");
233
234     /* Close everything down in an orderly fashion. */
235     QIOclose(qp);
236     OVclose();
237     SMshutdown();
238     HISclose(history);
239     if (lowmark != NULL)
240         if (fclose(lowmark) == EOF)
241             syswarn("can't close %s", lowmark_path);
242
243     return 0;
244 }