chiark / gitweb /
fixes
[inn-innduct.git] / expire / makedbz.c
1 /*  $Id: makedbz.c 6135 2003-01-19 01:15:40Z rra $
2 **
3 **  Rebuild dbz file for history db.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include <errno.h>
9 #include <pwd.h>
10 #include <syslog.h>  
11
12 #include "dbz.h"
13 #include "inn/innconf.h"
14 #include "inn/messages.h"
15 #include "inn/qio.h"
16 #include "libinn.h"
17 #include "paths.h"
18 #include "storage.h"
19
20 /* FIXME: once we figure out how to integrate this stuff with the
21  * history API this external visibility of internal voodoo should
22  * go */
23 #define HIS_FIELDSEP            '\t'
24
25 char *TextFile = NULL;
26 char *HistoryDir = NULL;
27 char *HISTORY = NULL;
28
29 /*
30 **  Remove the DBZ files for the specified base text file.
31 */
32 static void
33 RemoveDBZFiles(char *p)
34 {
35     char        buff[SMBUF];
36
37     snprintf(buff, sizeof(buff), "%s.dir", p);
38     if (unlink(buff) && errno != ENOENT)
39         syswarn("cannot unlink %s", buff);
40 #ifdef  DO_TAGGED_HASH
41     snprintf(buff, sizeof(buff), "%s.pag", p);
42     if (unlink(buff) && errno != ENOENT)
43         syswarn("cannot unlink %s", buff);
44 #else
45     snprintf(buff, sizeof(buff), "%s.index", p);
46     if (unlink(buff) && errno != ENOENT)
47         syswarn("cannot unlink %s", buff);
48     snprintf(buff, sizeof(buff), "%s.hash", p);
49     if (unlink(buff) && errno != ENOENT)
50         syswarn("cannot unlink %s", buff);
51 #endif
52 }
53
54
55 /*
56 **  Count lines in the history text.  A long-winded way of saying "wc -l"
57 */
58 static off_t
59 Countlines(void)
60 {
61     QIOSTATE *qp;
62     off_t count;
63
64     /* Open the text file. */
65     qp = QIOopen(TextFile);
66     if (qp == NULL)
67         sysdie("cannot open %s", TextFile);
68
69     /* Loop through all lines in the text file. */
70     count = 0;
71     for (; QIOread(qp) != NULL;)
72         count++;
73     if (QIOerror(qp))
74         sysdie("cannot read %s near line %lu", TextFile,
75                (unsigned long) count);
76     if (QIOtoolong(qp))
77         sysdie("line %lu of %s is too long", (unsigned long) count,
78                TextFile);
79
80     QIOclose(qp);
81     return count;
82 }
83
84
85 /*
86 **  Rebuild the DBZ file from the text file.
87 */
88 static void
89 Rebuild(off_t size, bool IgnoreOld, bool Overwrite)
90 {
91     QIOSTATE            *qp;
92     char                *p;
93     char                *save;
94     off_t               count;
95     off_t               where;
96     HASH                key;
97     char                temp[SMBUF];
98     dbzoptions          opt;
99
100     if (chdir(HistoryDir) < 0)
101         sysdie("cannot chdir to %s", HistoryDir);
102
103     /* If we are ignoring the old database and the user didn't specify a table
104        size, determine one ourselves from the size of the text history file.
105        Note that this will still use the defaults in dbz if the text file is
106        empty, since size will still be left set to 0. */
107     if (IgnoreOld == true && size == 0) {
108         size = Countlines();
109         size += (size / 10);
110         if (size > 0)
111             warn("no size specified, using %ld", (unsigned long) size);
112     }
113
114     /* Open the text file. */
115     qp = QIOopen(TextFile);
116     if (qp == NULL)
117         sysdie("cannot open %s", TextFile);
118
119     /* If using the standard history file, force DBZ to use history.n. */
120     if (strcmp(TextFile, HISTORY) == 0 && !Overwrite) {
121         snprintf(temp, sizeof(temp), "%s.n", HISTORY);
122         if (link(HISTORY, temp) < 0)
123             sysdie("cannot create temporary link to %s", temp);
124         RemoveDBZFiles(temp);
125         p = temp;
126     }
127     else {
128         temp[0] = '\0';
129         /* 
130         ** only do removedbz files if a. we're using something besides 
131         ** $pathdb/history, or b. we're ignoring the old db.
132         */
133         if (strcmp(TextFile, HISTORY) != 0 || IgnoreOld)
134             RemoveDBZFiles(TextFile);
135         p = TextFile;
136     }
137
138     /* Open the new database, using the old file if desired and possible. */
139     dbzgetoptions(&opt);
140     opt.pag_incore = INCORE_MEM;
141 #ifndef DO_TAGGED_HASH
142     opt.exists_incore = INCORE_MEM;
143 #endif
144     dbzsetoptions(opt);
145     if (IgnoreOld) {
146         if (!dbzfresh(p, dbzsize(size))) {
147             syswarn("cannot do dbzfresh");
148             if (temp[0])
149                 unlink(temp);
150             exit(1);
151         }
152     }
153     else {
154         if (!dbzagain(p, HISTORY)) {
155             syswarn("cannot do dbzagain");
156             if (temp[0])
157                 unlink(temp);
158             exit(1);
159         }
160     }
161
162     /* Loop through all lines in the text file. */
163     count = 0;
164     for (where = QIOtell(qp); (p = QIOread(qp)) != NULL; where = QIOtell(qp)) {
165         count++;
166         if ((save = strchr(p, HIS_FIELDSEP)) == NULL) {
167             warn("bad line #%lu: %.40s", (unsigned long) count, p);
168             if (temp[0])
169                 unlink(temp);
170             exit(1);
171         }
172         *save = '\0';
173         switch (*p) {
174         case '[':
175             if (strlen(p) != ((sizeof(HASH) * 2) + 2)) {
176                 warn("invalid length for hash %s, skipping", p);
177                 continue;
178             }
179             key = TextToHash(p+1);
180             break;
181         default:
182             warn("invalid message ID %s in history text", p);
183             continue;
184         }
185         switch (dbzstore(key, where)) {
186         case DBZSTORE_EXISTS:
187             warn("duplicate message ID %s in history text", p);
188             break;
189         case DBZSTORE_ERROR:
190             syswarn("cannot store %s", p);
191             if (temp[0])
192                 unlink(temp);
193             exit(1);
194         default:
195             break;
196         }
197     }
198     if (QIOerror(qp)) {
199         syswarn("cannot read %s near line %lu", TextFile,
200                 (unsigned long) count);
201         if (temp[0])
202             unlink(temp);
203         exit(1);
204     }
205     if (QIOtoolong(qp)) {
206         warn("line %lu is too long", (unsigned long) count);
207         if (temp[0])
208             unlink(temp);
209         exit(1);
210     }
211
212     /* Close files. */
213     QIOclose(qp);
214     if (!dbzclose()) {
215         syswarn("cannot close history");
216         if (temp[0])
217             unlink(temp);
218         exit(1);
219     }
220
221     if (temp[0])
222         unlink(temp);
223 }
224
225 static void
226 Usage(void)
227 {
228     fprintf(stderr, "Usage: makedbz [-f histfile] [-s numlines] [-i] [-o]\n");
229     exit(1);
230 }
231
232
233 /*
234 **  Change to the news user if possible, and if not, die.  Used for operations
235 **  that may create new database files, so as not to mess up the ownership.
236 */
237 static void
238 setuid_news(void)
239 {
240     struct passwd *pwd;
241
242     pwd = getpwnam(NEWSUSER);
243     if (pwd == NULL)
244         die("can't resolve %s to a UID (account doesn't exist?)", NEWSUSER);
245     if (getuid() == 0)
246         setuid(pwd->pw_uid);
247     if (getuid() != pwd->pw_uid)
248         die("must be run as %s", NEWSUSER);
249 }
250
251
252 int
253 main(int argc, char **argv)
254 {
255     bool        Overwrite;
256     bool        IgnoreOld;
257     off_t       size = 0;
258     int         i;
259     char        *p;
260
261     /* First thing, set up logging and our identity. */
262     openlog("makedbz", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
263     message_program_name = "makedbz";
264         
265     /* Set defaults. */
266     if (!innconf_read(NULL))
267         exit(1);
268     TextFile = concatpath(innconf->pathdb, _PATH_HISTORY);
269     HISTORY = concatpath(innconf->pathdb, _PATH_HISTORY);
270     HistoryDir = innconf->pathdb;
271     IgnoreOld = false;
272     Overwrite = false;
273
274     while ((i = getopt(argc, argv, "s:iof:")) != EOF) {
275         switch (i) {
276         default:
277             Usage();
278         case 'f':
279             TextFile = optarg;
280             break;
281         case 's':
282             size = atol(optarg);
283             IgnoreOld = true;
284             break;
285         case 'o':
286             Overwrite = true;
287             break;
288         case 'i':
289             IgnoreOld = true;
290             break;
291         }
292     }
293
294     argc -= optind;
295     argv += optind;
296     if (argc) {
297         Usage();
298     }
299
300     if ((p = strrchr(TextFile, '/')) == NULL) {
301         /* find the default history file directory */
302         HistoryDir = innconf->pathdb;
303     } else {
304         *p = '\0';
305         HistoryDir = xstrdup(TextFile);
306         *p = '/';
307     }
308
309     if (chdir(HistoryDir) < 0)
310         sysdie("cannot chdir to %s", HistoryDir);
311
312     /* Change users if necessary. */
313     setuid_news();
314
315     Rebuild(size, IgnoreOld, Overwrite);
316     closelog();
317     exit(0);
318 }