chiark / gitweb /
Commit 2.4.5-5 as unpacked
[inn-innduct.git] / storage / tradindexed / tdx-util.c
1 /*  $Id: tdx-util.c 6289 2003-04-09 04:16:16Z rra $
2 **
3 **  Utility for managing a tradindexed overview spool.
4 **
5 **  This utility can manipulate a tradindexed overview spool in various ways,
6 **  including some ways that are useful for recovery from crashes.  It allows
7 **  the user to view the contents of the various data structures that
8 **  tradindexed stores on disk.
9 */
10
11 #include "config.h"
12 #include "clibrary.h"
13 #include <ctype.h>
14 #include <dirent.h>
15 #include <pwd.h>
16 #include <sys/stat.h>
17
18 #include "inn/buffer.h"
19 #include "inn/history.h"
20 #include "inn/innconf.h"
21 #include "inn/messages.h"
22 #include "inn/vector.h"
23 #include "libinn.h"
24 #include "ov.h"
25 #include "ovinterface.h"
26 #include "paths.h"
27 #include "tdx-private.h"
28 #include "tdx-structure.h"
29
30 /*
31 **  Dump the main index data, either all of it or that for a particular group
32 **  if the group argument is non-NULL.
33 */
34 static void
35 dump_index(const char *group)
36 {
37     struct group_index *index;
38
39     index = tdx_index_open(OV_READ);
40     if (index == NULL)
41         return;
42     if (group == NULL)
43         tdx_index_dump(index, stdout);
44     else {
45         const struct group_entry *entry;
46
47         entry = tdx_index_entry(index, group);
48         if (entry == NULL) {
49             warn("cannot find group %s", group);
50             return;
51         }
52         tdx_index_print(group, entry, stdout);
53     }
54     tdx_index_close(index);
55 }
56
57
58 /*
59 **  Dump the data index file for a particular group.
60 */
61 static void
62 dump_group_index(const char *group)
63 {
64     struct group_index *index;
65     struct group_entry *entry;
66     struct group_data *data;
67
68     index = tdx_index_open(OV_READ);
69     if (index == NULL)
70         return;
71     entry = tdx_index_entry(index, group);
72     if (entry == NULL) {
73         warn("cannot find group %s in the index", group);
74         return;
75     }
76     data = tdx_data_open(index, group, entry);
77     if (data == NULL) {
78         warn("cannot open group %s", group);
79         return;
80     }
81     tdx_data_index_dump(data, stdout);
82     tdx_data_close(data);
83     tdx_index_close(index);
84 }
85
86
87 /*
88 **  Dump the overview data for a particular group.  If number is 0, dump the
89 **  overview data for all current articles; otherwise, only dump the data for
90 **  that particular article.  Include the article number, token, arrived time,
91 **  and expires time (if any) in the overview data as additional fields.
92 */
93 static void
94 dump_overview(const char *group, ARTNUM number)
95 {
96     struct group_index *index;
97     struct group_data *data;
98     struct group_entry *entry;
99     struct article article;
100     struct search *search;
101     char datestring[256];
102
103     index = tdx_index_open(OV_READ);
104     if (index == NULL)
105         return;
106     entry = tdx_index_entry(index, group);
107     if (entry == NULL) {
108         warn("cannot find group %s", group);
109         return;
110     }
111     data = tdx_data_open(index, group, entry);
112     if (data == NULL) {
113         warn("cannot open group %s", group);
114         return;
115     }
116     data->refcount++;
117
118     if (number != 0)
119         search = tdx_search_open(data, number, number, entry->high);
120     else
121         search = tdx_search_open(data, entry->low, entry->high, entry->high);
122     if (search == NULL) {
123         if (number != 0)
124             puts("Article not found");
125         else
126             warn("cannot open search in %s: %lu - %lu", group, entry->low,
127                  entry->high);
128         return;
129     }
130     while (tdx_search(search, &article)) {
131         fwrite(article.overview, article.overlen - 2, 1, stdout);
132         printf("\tArticle: %lu\tToken: %s", article.number,
133                TokenToText(article.token));
134         makedate(article.arrived, true, datestring, sizeof(datestring));
135         printf("\tArrived: %s", datestring);
136         if (article.expires != 0) {
137             makedate(article.expires, true, datestring, sizeof(datestring));
138             printf("\tExpires: %s", datestring);
139         }
140         printf("\n");
141     }
142     tdx_search_close(search);
143     tdx_data_close(data);
144     tdx_index_close(index);
145 }
146
147
148 /*
149 **  Check a string to see if its a valid number.
150 */
151 static bool
152 check_number(const char *string)
153 {
154     const char *p;
155
156     for (p = string; *p != '\0'; p++)
157         if (!CTYPE(isdigit, *p))
158             return false;
159     return true;
160 }
161
162
163 /*
164 **  Find the message ID in the group overview data and return a copy of it.
165 **  Caller is responsible for freeing.
166 */
167 static char *
168 extract_messageid(const char *overview)
169 {
170     const char *p, *end;
171     int count;
172
173     for (p = overview, count = 0; count < 4; count++) {
174         p = strchr(p + 1, '\t');
175         if (p == NULL)
176             return NULL;
177     }
178     p++;
179     end = strchr(p, '\t');
180     if (end == NULL)
181         return NULL;
182     return xstrndup(p, end - p);
183 }
184
185
186 /*
187 **  Compare two file names assuming they're numbers, used to sort the list of
188 **  articles numerically.  Suitable for use as a comparison function for
189 **  qsort.
190 */
191 static int
192 file_compare(const void *p1, const void *p2)
193 {
194     const char *file1 = *((const char * const *) p1);
195     const char *file2 = *((const char * const *) p2);
196     ARTNUM n1, n2;
197
198     n1 = strtoul(file1, NULL, 10);
199     n2 = strtoul(file2, NULL, 10);
200     if (n1 > n2)
201         return 1;
202     else if (n1 < n2)
203         return -1;
204     else
205         return 0;
206 }
207
208
209 /*
210 **  Get a list of articles in a directory, sorted by article number.
211 */
212 static struct vector *
213 article_list(const char *directory)
214 {
215     DIR *articles;
216     struct dirent *file;
217     struct vector *list;
218
219     list = vector_new();
220     articles = opendir(directory);
221     if (articles == NULL)
222         sysdie("cannot open directory %s", directory);
223     while ((file = readdir(articles)) != NULL) {
224         if (!check_number(file->d_name))
225             continue;
226         vector_add(list, file->d_name);
227     }
228     closedir(articles);
229
230     qsort(list->strings, list->count, sizeof(list->strings[0]), file_compare);
231     return list;
232 }
233
234
235 /*
236 **  Rebuild the overview data for a particular group.  Takes a path to a
237 **  directory containing all the articles, as individual files, that should be
238 **  in that group.  The names of the files should be the article numbers in
239 **  the group.
240 */
241 static void
242 group_rebuild(const char *group, const char *path)
243 {
244     char *filename, *histpath, *article, *wireformat, *p;
245     size_t size, file;
246     int flags, length;
247     struct buffer *overview = NULL;
248     struct vector *extra, *files;
249     struct history *history;
250     struct group_index *index;
251     struct group_data *data;
252     struct group_entry *entry, info;
253     struct article artdata;
254     struct stat st;
255
256     index = tdx_index_open(OV_READ);
257     if (index == NULL)
258         die("cannot open group index");
259     entry = tdx_index_entry(index, group);
260     if (entry == NULL) {
261         if (!tdx_index_add(index, group, 1, 0, "y"))
262             die("cannot create group %s", group);
263         entry = tdx_index_entry(index, group);
264         if (entry == NULL)
265             die("cannot find group %s", group);
266     }
267     info = *entry;
268     data = tdx_data_rebuild_start(group);
269     if (data == NULL)
270         die("cannot start data rebuild for %s", group);
271     if (!tdx_index_rebuild_start(index, entry))
272         die("cannot start index rebuild for %s", group);
273
274     histpath = concatpath(innconf->pathdb, _PATH_HISTORY);
275     flags = HIS_RDONLY | HIS_ONDISK;
276     history = HISopen(histpath, innconf->hismethod, flags);
277     if (history == NULL)
278         sysdie("cannot open history %s", histpath);
279     free(histpath);
280
281     extra = overview_extra_fields();
282     files = article_list(path);
283
284     info.count = 0;
285     info.high = 0;
286     info.low = 0;
287     for (file = 0; file < files->count; file++) {
288         filename = concatpath(path, files->strings[file]);
289         article = ReadInFile(filename, &st);
290         size = st.st_size;
291         if (article == NULL) {
292             syswarn("cannot read in %s", filename);
293             free(filename);
294             continue;
295         }
296
297         /* Check to see if the article is not in wire format.  If it isn't,
298            convert it.  We only check the first line ending. */
299         p = strchr(article, '\n');
300         if (p != NULL && (p == article || p[-1] != '\r')) {
301             wireformat = ToWireFmt(article, size, (size_t *)&length);
302             free(article);
303             article = wireformat;
304             size = length;
305         }
306
307         artdata.number = strtoul(files->strings[file], NULL, 10);
308         if (artdata.number > info.high)
309             info.high = artdata.number;
310         if (artdata.number < info.low || info.low == 0)
311             info.low = artdata.number;
312         info.count++;
313         overview = overview_build(artdata.number, article, size, extra,
314                                   overview);
315         artdata.overview = overview->data;
316         artdata.overlen = overview->left;
317         p = extract_messageid(overview->data);
318         if (p == NULL) {
319             warn("cannot find message ID in %s", filename);
320             free(filename);
321             free(article);
322             continue;
323         }
324         if (HISlookup(history, p, &artdata.arrived, NULL, &artdata.expires,
325                       &artdata.token)) {
326             if (!tdx_data_store(data, &artdata))
327                 warn("cannot store data for %s", filename);
328         } else {
329             warn("cannot find article %s in history", p);
330         }
331         free(p);
332         free(filename);
333         free(article);
334     }
335     vector_free(files);
336     vector_free(extra);
337
338     info.indexinode = data->indexinode;
339     info.base = data->base;
340     if (!tdx_index_rebuild_finish(index, entry, &info))
341         die("cannot update group index for %s", group);
342     if (!tdx_data_rebuild_finish(group))
343         die("cannot finish rebuilding data for group %s", group);
344     tdx_data_close(data);
345     HISclose(history);
346 }
347
348
349 /*
350 **  Change to the news user if possible, and if not, die.  Used for operations
351 **  that may change the overview files so as not to mess up the ownership.
352 */
353 static void
354 setuid_news(void)
355 {
356     struct passwd *pwd;
357
358     pwd = getpwnam(NEWSUSER);
359     if (pwd == NULL)
360         die("can't resolve %s to a UID (account doesn't exist?)", NEWSUSER);
361     if (getuid() == 0)
362         setuid(pwd->pw_uid);
363     if (getuid() != pwd->pw_uid)
364         die("must be run as %s", NEWSUSER);
365 }
366
367
368 /*
369 **  Main routine.  Load inn.conf, parse the arguments, and dispatch to the
370 **  appropriate function.
371 */
372 int
373 main(int argc, char *argv[])
374 {
375     int option;
376     char mode = '\0';
377     const char *newsgroup = NULL;
378     const char *path = NULL;
379     ARTNUM article = 0;
380
381     message_program_name = "tdx-util";
382
383     if (!innconf_read(NULL))
384         exit(1);
385
386     /* Parse options. */
387     opterr = 0;
388     while ((option = getopt(argc, argv, "a:n:p:AFR:gio")) != EOF) {
389         switch (option) {
390         case 'a':
391             article = strtoul(optarg, NULL, 10);
392             if (article == 0)
393                 die("invalid article number %s", optarg);
394             break;
395         case 'n':
396             newsgroup = optarg;
397             break;
398         case 'p':
399             innconf->pathoverview = xstrdup(optarg);
400             break;
401         case 'A':
402             if (mode != '\0')
403                 die("only one mode option allowed");
404             mode = 'A';
405             break;
406         case 'F':
407             if (mode != '\0')
408                 die("only one mode option allowed");
409             mode = 'F';
410             break;
411         case 'R':
412             if (mode != '\0')
413                 die("only one mode option allowed");
414             mode = 'R';
415             path = optarg;
416             break;
417         case 'g':
418             if (mode != '\0')
419                 die("only one mode option allowed");
420             mode = 'g';
421             break;
422         case 'i':
423             if (mode != '\0')
424                 die("only one mode option allowed");
425             mode = 'i';
426             break;
427         case 'o':
428             if (mode != '\0')
429                 die("only one mode option allowed");
430             mode = 'o';
431             break;
432         default:
433             die("invalid option %c", optopt);
434             break;
435         }
436     }
437
438     /* Modes g and o require a group be specified. */
439     if ((mode == 'g' || mode == 'o' || mode == 'R') && newsgroup == NULL)
440         die("group must be specified for -%c", mode);
441
442     /* Run the specified function. */
443     switch (mode) {
444     case 'A':
445         tdx_index_audit(false);
446         break;
447     case 'F':
448         setuid_news();
449         tdx_index_audit(true);
450         break;
451     case 'R':
452         setuid_news();
453         group_rebuild(newsgroup, path);
454         break;
455     case 'i':
456         dump_index(newsgroup);
457         break;
458     case 'g':
459         dump_group_index(newsgroup);
460         break;
461     case 'o':
462         dump_overview(newsgroup, article);
463         break;
464     default:
465         die("a mode option must be specified");
466         break;
467     }
468     exit(0);
469 }