1 /* $Id: tdx-util.c 6289 2003-04-09 04:16:16Z rra $
3 ** Utility for managing a tradindexed overview spool.
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.
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"
25 #include "ovinterface.h"
27 #include "tdx-private.h"
28 #include "tdx-structure.h"
31 ** Dump the main index data, either all of it or that for a particular group
32 ** if the group argument is non-NULL.
35 dump_index(const char *group)
37 struct group_index *index;
39 index = tdx_index_open(OV_READ);
43 tdx_index_dump(index, stdout);
45 const struct group_entry *entry;
47 entry = tdx_index_entry(index, group);
49 warn("cannot find group %s", group);
52 tdx_index_print(group, entry, stdout);
54 tdx_index_close(index);
59 ** Dump the data index file for a particular group.
62 dump_group_index(const char *group)
64 struct group_index *index;
65 struct group_entry *entry;
66 struct group_data *data;
68 index = tdx_index_open(OV_READ);
71 entry = tdx_index_entry(index, group);
73 warn("cannot find group %s in the index", group);
76 data = tdx_data_open(index, group, entry);
78 warn("cannot open group %s", group);
81 tdx_data_index_dump(data, stdout);
83 tdx_index_close(index);
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.
94 dump_overview(const char *group, ARTNUM number)
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];
103 index = tdx_index_open(OV_READ);
106 entry = tdx_index_entry(index, group);
108 warn("cannot find group %s", group);
111 data = tdx_data_open(index, group, entry);
113 warn("cannot open group %s", group);
119 search = tdx_search_open(data, number, number, entry->high);
121 search = tdx_search_open(data, entry->low, entry->high, entry->high);
122 if (search == NULL) {
124 puts("Article not found");
126 warn("cannot open search in %s: %lu - %lu", group, entry->low,
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);
142 tdx_search_close(search);
143 tdx_data_close(data);
144 tdx_index_close(index);
149 ** Check a string to see if its a valid number.
152 check_number(const char *string)
156 for (p = string; *p != '\0'; p++)
157 if (!CTYPE(isdigit, *p))
164 ** Find the message ID in the group overview data and return a copy of it.
165 ** Caller is responsible for freeing.
168 extract_messageid(const char *overview)
173 for (p = overview, count = 0; count < 4; count++) {
174 p = strchr(p + 1, '\t');
179 end = strchr(p, '\t');
182 return xstrndup(p, end - p);
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
192 file_compare(const void *p1, const void *p2)
194 const char *file1 = *((const char * const *) p1);
195 const char *file2 = *((const char * const *) p2);
198 n1 = strtoul(file1, NULL, 10);
199 n2 = strtoul(file2, NULL, 10);
210 ** Get a list of articles in a directory, sorted by article number.
212 static struct vector *
213 article_list(const char *directory)
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))
226 vector_add(list, file->d_name);
230 qsort(list->strings, list->count, sizeof(list->strings[0]), file_compare);
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
242 group_rebuild(const char *group, const char *path)
244 char *filename, *histpath, *article, *wireformat, *p;
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;
256 index = tdx_index_open(OV_READ);
258 die("cannot open group index");
259 entry = tdx_index_entry(index, group);
261 if (!tdx_index_add(index, group, 1, 0, "y"))
262 die("cannot create group %s", group);
263 entry = tdx_index_entry(index, group);
265 die("cannot find group %s", group);
268 data = tdx_data_rebuild_start(group);
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);
274 histpath = concatpath(innconf->pathdb, _PATH_HISTORY);
275 flags = HIS_RDONLY | HIS_ONDISK;
276 history = HISopen(histpath, innconf->hismethod, flags);
278 sysdie("cannot open history %s", histpath);
281 extra = overview_extra_fields();
282 files = article_list(path);
287 for (file = 0; file < files->count; file++) {
288 filename = concatpath(path, files->strings[file]);
289 article = ReadInFile(filename, &st);
291 if (article == NULL) {
292 syswarn("cannot read in %s", filename);
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);
303 article = wireformat;
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;
313 overview = overview_build(artdata.number, article, size, extra,
315 artdata.overview = overview->data;
316 artdata.overlen = overview->left;
317 p = extract_messageid(overview->data);
319 warn("cannot find message ID in %s", filename);
324 if (HISlookup(history, p, &artdata.arrived, NULL, &artdata.expires,
326 if (!tdx_data_store(data, &artdata))
327 warn("cannot store data for %s", filename);
329 warn("cannot find article %s in history", p);
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);
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.
358 pwd = getpwnam(NEWSUSER);
360 die("can't resolve %s to a UID (account doesn't exist?)", NEWSUSER);
363 if (getuid() != pwd->pw_uid)
364 die("must be run as %s", NEWSUSER);
369 ** Main routine. Load inn.conf, parse the arguments, and dispatch to the
370 ** appropriate function.
373 main(int argc, char *argv[])
377 const char *newsgroup = NULL;
378 const char *path = NULL;
381 message_program_name = "tdx-util";
383 if (!innconf_read(NULL))
388 while ((option = getopt(argc, argv, "a:n:p:AFR:gio")) != EOF) {
391 article = strtoul(optarg, NULL, 10);
393 die("invalid article number %s", optarg);
399 innconf->pathoverview = xstrdup(optarg);
403 die("only one mode option allowed");
408 die("only one mode option allowed");
413 die("only one mode option allowed");
419 die("only one mode option allowed");
424 die("only one mode option allowed");
429 die("only one mode option allowed");
433 die("invalid option %c", optopt);
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);
442 /* Run the specified function. */
445 tdx_index_audit(false);
449 tdx_index_audit(true);
453 group_rebuild(newsgroup, path);
456 dump_index(newsgroup);
459 dump_group_index(newsgroup);
462 dump_overview(newsgroup, article);
465 die("a mode option must be specified");