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