1 /*
2 ** Interface implementation for the tradindexed overview method.
3 **
4 ** This code converts between the internal interface used by the tradindexed
5 ** implementation and the interface expected by the INN overview API. The
6 ** internal interface is in some cases better suited to the data structures
7 ** that the tradindexed overview method uses, and this way the internal
8 ** interface can be kept isolated from the external interface. (There are
9 ** also some operations that can be performed entirely in the interface
10 ** layer.)
11 */
12
13 #include "portable/system.h"
14
15 #include "inn/innconf.h"
16 #include "inn/libinn.h"
17 #include "inn/messages.h"
18 #include "inn/ov.h"
19 #include "inn/storage.h"
20 #include "tdx-private.h"
21 #include "tdx-structure.h"
22 #include "tradindexed.h"
23
24 /* This structure holds all of the data about the open overview files. We can
25 eventually pass one of these structures back to the caller of open when the
26 overview API is more object-oriented. */
27 struct tradindexed {
28 struct group_index *index;
29 struct cache *cache;
30 bool cutoff;
31 };
32
33 /* Global data about the open tradindexed method. */
34 static struct tradindexed *tradindexed;
35
36
37 /*
38 ** Helper function to open a group_data structure via the cache, inserting it
39 ** into the cache if it wasn't found in the cache.
40 */
41 static struct group_data *
data_cache_open(struct tradindexed * global,const char * group,struct group_entry * entry)42 data_cache_open(struct tradindexed *global, const char *group,
43 struct group_entry *entry)
44 {
45 struct group_data *data;
46
47 data = tdx_cache_lookup(global->cache, entry->hash);
48 if (data == NULL) {
49 data = tdx_data_open(global->index, group, entry);
50 if (data == NULL)
51 return NULL;
52 tdx_cache_insert(global->cache, entry->hash, data);
53 }
54 return data;
55 }
56
57
58 /*
59 ** Helper function to reopen the data files and remove the old entry from the
60 ** cache if we think that might help better fulfill a search.
61 */
62 static struct group_data *
data_cache_reopen(struct tradindexed * global,const char * group,struct group_entry * entry)63 data_cache_reopen(struct tradindexed *global, const char *group,
64 struct group_entry *entry)
65 {
66 struct group_data *data;
67
68 tdx_cache_delete(global->cache, entry->hash);
69 data = tdx_data_open(global->index, group, entry);
70 if (data == NULL)
71 return NULL;
72 tdx_cache_insert(global->cache, entry->hash, data);
73 return data;
74 }
75
76
77 /*
78 ** Open the overview method.
79 */
80 bool
tradindexed_open(int mode)81 tradindexed_open(int mode)
82 {
83 unsigned long cache_size, fdlimit;
84
85 if (tradindexed != NULL) {
86 warn("tradindexed: overview method already open");
87 return false;
88 }
89 tradindexed = xmalloc(sizeof(struct tradindexed));
90 tradindexed->index = tdx_index_open((mode & OV_WRITE) ? true : false);
91 tradindexed->cutoff = false;
92
93 /* Use a cache size of two for read-only connections. We may want to
94 rethink the limitation of the cache for reading later based on
95 real-world experience. */
96 cache_size = (mode & OV_WRITE) ? innconf->overcachesize : 1;
97 fdlimit = getfdlimit();
98 if (fdlimit > 0 && fdlimit < cache_size * 2) {
99 warn("tradindexed: not enough file descriptors for an overview cache"
100 " size of %lu; increase rlimitnofile or decrease overcachesize"
101 " to at most %lu",
102 cache_size, fdlimit / 2);
103 cache_size = (fdlimit > 2) ? fdlimit / 2 : 1;
104 }
105 tradindexed->cache = tdx_cache_create(cache_size);
106
107 return (tradindexed->index == NULL) ? false : true;
108 }
109
110
111 /*
112 ** Get statistics about a group. Convert between the multiple pointer API
113 ** and the structure API used internally.
114 */
115 bool
tradindexed_groupstats(const char * group,int * low,int * high,int * count,int * flag)116 tradindexed_groupstats(const char *group, int *low, int *high, int *count,
117 int *flag)
118 {
119 const struct group_entry *entry;
120
121 if (tradindexed == NULL || tradindexed->index == NULL) {
122 warn("tradindexed: overview method not initialized");
123 return false;
124 }
125 entry = tdx_index_entry(tradindexed->index, group);
126 if (entry == NULL)
127 return false;
128 if (low != NULL)
129 *low = entry->low;
130 if (high != NULL)
131 *high = entry->high;
132 if (count != NULL)
133 *count = entry->count;
134 if (flag != NULL)
135 *flag = entry->flag;
136 return true;
137 }
138
139
140 /*
141 ** Add a new newsgroup to the index.
142 */
143 bool
tradindexed_groupadd(const char * group,ARTNUM low,ARTNUM high,char * flag)144 tradindexed_groupadd(const char *group, ARTNUM low, ARTNUM high, char *flag)
145 {
146 if (tradindexed == NULL || tradindexed->index == NULL) {
147 warn("tradindexed: overview method not initialized");
148 return false;
149 }
150 return tdx_index_add(tradindexed->index, group, low, high, flag);
151 }
152
153
154 /*
155 ** Delete a newsgroup from the index.
156 */
157 bool
tradindexed_groupdel(const char * group)158 tradindexed_groupdel(const char *group)
159 {
160 if (tradindexed == NULL || tradindexed->index == NULL) {
161 warn("tradindexed: overview method not initialized");
162 return false;
163 }
164 return tdx_index_delete(tradindexed->index, group);
165 }
166
167
168 /*
169 ** Add data about a single article. Convert between the multiple argument
170 ** API and the structure API used internally, and also implement low article
171 ** cutoff if that was requested.
172 */
173 bool
tradindexed_add(const char * group,ARTNUM artnum,TOKEN token,char * data,int length,time_t arrived,time_t expires)174 tradindexed_add(const char *group, ARTNUM artnum, TOKEN token, char *data,
175 int length, time_t arrived, time_t expires)
176 {
177 struct article article;
178 struct group_data *group_data;
179 struct group_entry *entry;
180
181 if (tradindexed == NULL || tradindexed->index == NULL) {
182 warn("tradindexed: overview method not initialized");
183 return false;
184 }
185
186 /* Get the group index entry and don't do any work if cutoff is set and
187 the article number is lower than the low water mark for the group. */
188 entry = tdx_index_entry(tradindexed->index, group);
189 if (entry == NULL)
190 return true;
191 if (tradindexed->cutoff && entry->low > artnum)
192 return true;
193
194 /* Fill out the article data structure. */
195 article.number = artnum;
196 article.overview = data;
197 article.overlen = length;
198 article.token = token;
199 article.arrived = arrived;
200 article.expires = expires;
201
202 /* Open the appropriate data structures, using the cache. */
203 group_data = data_cache_open(tradindexed, group, entry);
204 if (group_data == NULL)
205 return false;
206 return tdx_data_add(tradindexed->index, entry, group_data, &article);
207 }
208
209
210 /*
211 ** Cancel an article. We do this by blanking out its entry in the group
212 ** index, making the data inaccessible. The next expiration run will remove
213 ** the actual data.
214 */
215 bool
tradindexed_cancel(const char * group,ARTNUM artnum)216 tradindexed_cancel(const char *group, ARTNUM artnum)
217 {
218 struct group_entry *entry;
219 struct group_data *data;
220
221 if (tradindexed == NULL || tradindexed->index == NULL) {
222 warn("tradindexed: overview method not initialized");
223 return false;
224 }
225 entry = tdx_index_entry(tradindexed->index, group);
226 if (entry == NULL)
227 return false;
228 data = data_cache_open(tradindexed, group, entry);
229 if (data == NULL)
230 return false;
231 if (artnum > data->high) {
232 data = data_cache_reopen(tradindexed, group, entry);
233 if (data == NULL)
234 return false;
235 }
236 return tdx_data_cancel(data, artnum);
237 }
238
239
240 /*
241 ** Open an overview search. Open the appropriate group and then start a
242 ** search in it.
243 */
244 void *
tradindexed_opensearch(const char * group,int low,int high)245 tradindexed_opensearch(const char *group, int low, int high)
246 {
247 struct group_entry *entry;
248 struct group_data *data;
249
250 if (tradindexed == NULL || tradindexed->index == NULL) {
251 warn("tradindexed: overview method not initialized");
252 return NULL;
253 }
254 entry = tdx_index_entry(tradindexed->index, group);
255 if (entry == NULL)
256 return NULL;
257 data = data_cache_open(tradindexed, group, entry);
258 if (data == NULL)
259 return NULL;
260 if (entry->base != data->base)
261 if (data->base > (ARTNUM) low && entry->base < data->base) {
262 data = data_cache_reopen(tradindexed, group, entry);
263 if (data == NULL)
264 return NULL;
265 }
266 return tdx_search_open(data, low, high, entry->high);
267 }
268
269
270 /*
271 ** Get the next article returned by a search. Convert between the multiple
272 ** pointer API and the structure API we use internally.
273 */
274 bool
tradindexed_search(void * handle,ARTNUM * artnum,char ** data,int * length,TOKEN * token,time_t * arrived)275 tradindexed_search(void *handle, ARTNUM *artnum, char **data, int *length,
276 TOKEN *token, time_t *arrived)
277 {
278 struct article article;
279
280 if (tradindexed == NULL || tradindexed->index == NULL) {
281 warn("tradindexed: overview method not initialized");
282 return false;
283 }
284 if (!tdx_search(handle, &article))
285 return false;
286 if (artnum != NULL)
287 *artnum = article.number;
288 if (data != NULL)
289 *data = (char *) article.overview;
290 if (length != NULL)
291 *length = article.overlen;
292 if (token != NULL)
293 *token = article.token;
294 if (arrived != NULL)
295 *arrived = article.arrived;
296 return true;
297 }
298
299
300 /*
301 ** Close an overview search.
302 */
303 void
tradindexed_closesearch(void * handle)304 tradindexed_closesearch(void *handle)
305 {
306 tdx_search_close(handle);
307 }
308
309
310 /*
311 ** Get information for a single article. Open the appropriate group and then
312 ** convert from the pointer API to the struct API used internally.
313 */
314 bool
tradindexed_getartinfo(const char * group,ARTNUM artnum,TOKEN * token)315 tradindexed_getartinfo(const char *group, ARTNUM artnum, TOKEN *token)
316 {
317 struct group_entry *entry;
318 struct group_data *data;
319 const struct index_entry *index_entry;
320
321 if (tradindexed == NULL || tradindexed->index == NULL) {
322 warn("tradindexed: overview method not initialized");
323 return false;
324 }
325 entry = tdx_index_entry(tradindexed->index, group);
326 if (entry == NULL)
327 return false;
328 data = data_cache_open(tradindexed, group, entry);
329 if (data == NULL)
330 return false;
331 if (entry->base != data->base)
332 if (data->base > artnum && entry->base <= artnum) {
333 data = data_cache_reopen(tradindexed, group, entry);
334 if (data == NULL)
335 return false;
336 }
337 index_entry = tdx_article_entry(data, artnum, entry->high);
338 if (index_entry == NULL)
339 return false;
340 if (token != NULL)
341 *token = index_entry->token;
342 return true;
343 }
344
345
346 /*
347 ** Expire a single newsgroup.
348 */
349 bool
tradindexed_expiregroup(const char * group,int * low,struct history * history)350 tradindexed_expiregroup(const char *group, int *low, struct history *history)
351 {
352 ARTNUM new_low;
353 bool status;
354
355 /* tradindexed doesn't have any periodic cleanup. */
356 if (group == NULL)
357 return true;
358
359 status = tdx_expire(group, &new_low, history);
360 if (status && low != NULL)
361 *low = (int) new_low;
362 return status;
363 }
364
365
366 /*
367 ** Set various options or query various parameters for the overview method.
368 ** The interface is, at present, not particularly sane.
369 */
370 bool
tradindexed_ctl(OVCTLTYPE type,void * val)371 tradindexed_ctl(OVCTLTYPE type, void *val)
372 {
373 int *i;
374 float *f;
375 bool *b;
376 OVSORTTYPE *sort;
377
378 if (tradindexed == NULL) {
379 warn("tradindexed: overview method not initialized");
380 return false;
381 }
382
383 switch (type) {
384 case OVSPACE:
385 f = (float *) val;
386 *f = -1.0f;
387 return true;
388 case OVSORT:
389 sort = (OVSORTTYPE *) val;
390 *sort = OVNEWSGROUP;
391 return true;
392 case OVCUTOFFLOW:
393 b = (bool *) val;
394 tradindexed->cutoff = *b;
395 return true;
396 case OVSTATICSEARCH:
397 i = (int *) val;
398 *i = false;
399 return true;
400 case OVCACHEKEEP:
401 case OVCACHEFREE:
402 b = (bool *) val;
403 *b = false;
404 return true;
405 default:
406 return false;
407 }
408 }
409
410
411 /*
412 ** Close the overview method.
413 */
414 void
tradindexed_close(void)415 tradindexed_close(void)
416 {
417 if (tradindexed != NULL) {
418 if (tradindexed->index != NULL)
419 tdx_index_close(tradindexed->index);
420 if (tradindexed->cache != NULL)
421 tdx_cache_free(tradindexed->cache);
422 free(tradindexed);
423 tradindexed = NULL;
424 }
425 }
426