1 /*
2 * db_store.c: dbstore(), database storage routine.
3 *
4 * Copyright (C) 1994, 1995 Graeme W. Wilford. (Wilf.)
5 * Copyright (C) 2001, 2002 Colin Watson.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 *
21 * Mon Aug 8 20:35:30 BST 1994 Wilf. (G.Wilford@ee.surrey.ac.uk)
22 */
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif /* HAVE_CONFIG_H */
27
28 #include <assert.h>
29 #include <stdbool.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34
35 #include "attribute.h"
36 #include "error.h"
37 #include "gl_array_list.h"
38 #include "gl_xlist.h"
39 #include "timespec.h"
40 #include "xalloc.h"
41 #include "xvasprintf.h"
42
43 #include "manconfig.h"
44
45 #include "debug.h"
46 #include "filenames.h"
47 #include "glcontainers.h"
48
49 #include "mydbm.h"
50 #include "db_storage.h"
51
52 /* compare_ids(a,b) is negative if id 'a' is preferred to id 'b', i.e. if
53 * 'a' is a more canonical database entry than 'b'; positive if 'b' is
54 * preferred to 'a'; and zero if they are equivalent. This usually goes in
55 * comparison order, but there's a special exception when FAVOUR_STRAYCATS
56 * is set.
57 *
58 * If promote_links is true, consider SO_MAN equivalent to ULT_MAN. This is
59 * appropriate when sorting candidate pages for display.
60 */
61 int ATTRIBUTE_CONST compare_ids (char a, char b, bool promote_links)
62 {
63 #ifdef FAVOUR_STRAYCATS
64 if (a == WHATIS_MAN && b == STRAY_CAT)
65 return 1;
66 else if (a == STRAY_CAT && b == WHATIS_MAN)
67 return -1;
68 #endif
69
70 if (promote_links) {
71 if ((a == ULT_MAN && b == SO_MAN) ||
72 (a == SO_MAN && b == ULT_MAN))
73 return 0;
74 }
75
76 if (a < b)
77 return -1;
78 else if (a > b)
79 return 1;
80 else
81 return 0;
82 }
83
84 enum replace_action {
85 REPLACE_YES = 0,
86 REPLACE_NO,
87 REPLACE_FAIL
88 };
89
90 /* The do_we_replace logic. Decide, for some existing key, whether it should
91 * be replaced with some new contents. Check that names and section
92 * extensions match before calling this.
93 */
94 static int replace_if_necessary (MYDBM_FILE dbf,
95 struct mandata *newdata,
96 struct mandata *olddata,
97 datum newkey, datum newcont)
98 {
99 enum replace_action action;
100
101 /* It's OK to replace ULT_MAN with SO_MAN if the mtime is newer. It
102 * isn't OK to replace a real page (either ULT_MAN or SO_MAN) with a
103 * whatis reference; if the real page really went away then
104 * purge_missing will catch that in time, but a real page that still
105 * exists should always take precedence.
106 *
107 * Tie-break whatis references by lexicographical sort of
108 * the pointed-to page names, which isn't great but at least
109 * gives us something reproducible.
110 *
111 * TODO: name fields should be collated with the requested name
112 */
113 if (compare_ids (newdata->id, olddata->id, false) < 0) {
114 debug ("replace_if_necessary: stronger ID; replacing\n");
115 action = REPLACE_YES;
116 } else if (compare_ids (newdata->id, olddata->id, true) <= 0 &&
117 timespec_cmp (newdata->mtime, olddata->mtime) > 0) {
118 debug ("replace_if_necessary: newer mtime; replacing\n");
119 action = REPLACE_YES;
120 } else if (compare_ids (newdata->id, olddata->id, true) <= 0 &&
121 timespec_cmp (newdata->mtime, olddata->mtime) < 0) {
122 debug ("replace_if_necessary: older mtime; not replacing\n");
123 action = REPLACE_NO;
124 } else if (compare_ids (newdata->id, olddata->id, false) > 0) {
125 debug ("replace_if_necessary: weaker ID; not replacing\n");
126 action = REPLACE_NO;
127 } else if (newdata->pointer && olddata->pointer &&
128 strcmp (newdata->pointer, olddata->pointer) < 0) {
129 debug ("replace_if_necessary: pointer '%s' < '%s'; "
130 "replacing\n", newdata->pointer, olddata->pointer);
131 action = REPLACE_YES;
132 } else if (newdata->pointer && olddata->pointer &&
133 strcmp (newdata->pointer, olddata->pointer) > 0) {
134 debug ("replace_if_necessary: pointer '%s' > '%s'; "
135 "not replacing\n", newdata->pointer, olddata->pointer);
136 action = REPLACE_NO;
137 } else if (!STREQ (dash_if_unset (newdata->comp),
138 olddata->comp)) {
139 debug ("replace_if_necessary: differing compression "
140 "extensions (%s != %s); failing\n",
141 dash_if_unset (newdata->comp), olddata->comp);
142 action = REPLACE_FAIL;
143 } else {
144 debug ("replace_if_necessary: match; not replacing\n");
145 action = REPLACE_NO;
146 }
147
148 switch (action) {
149 case REPLACE_YES:
150 if (MYDBM_REPLACE (dbf, newkey, newcont))
151 gripe_replace_key (dbf, MYDBM_DPTR (newkey));
152 return 0;
153 case REPLACE_NO:
154 /* Insert if missing, but ignore failures. */
155 MYDBM_INSERT (dbf, newkey, newcont);
156 return 0;
157 default:
158 return 1;
159 }
160 }
161
162 /* The complement of split_content */
163 static datum make_content (struct mandata *in)
164 {
165 datum cont;
166 static const char dash[] = "-";
167 char *value;
168
169 memset (&cont, 0, sizeof cont);
170
171 if (!in->pointer)
172 in->pointer = xstrdup (dash);
173 if (!in->comp)
174 in->comp = xstrdup (dash);
175 if (!in->filter)
176 in->filter = xstrdup (dash);
177 if (!in->whatis)
178 in->whatis = xstrdup (dash + 1);
179
180 value = xasprintf (
181 "%s\t%s\t%s\t%ld\t%ld\t%c\t%s\t%s\t%s\t%s",
182 dash_if_unset (in->name),
183 in->ext,
184 in->sec,
185 (long) in->mtime.tv_sec,
186 (long) in->mtime.tv_nsec,
187 in->id,
188 in->pointer,
189 in->filter,
190 in->comp,
191 in->whatis);
192 assert (value);
193 MYDBM_SET (cont, value);
194
195 #ifdef NDBM
196 /* limit of 4096 bytes of data using ndbm */
197 if (MYDBM_DSIZE (cont) > 4095) {
198 MYDBM_DPTR (cont)[4095] = '\0';
199 MYDBM_DSIZE (cont) = 4096;
200 }
201 #endif
202 return cont;
203 }
204
205 /* The complement of list_extensions. */
206 static char *make_extensions_reference (gl_list_t refs)
207 {
208 struct name_ext *ref;
209 size_t len = 0;
210 char *data, *cur;
211
212 GL_LIST_FOREACH (refs, ref)
213 len += strlen (ref->name) + strlen (ref->ext) + 2;
214
215 cur = data = xmalloc (len + 1);
216 GL_LIST_FOREACH (refs, ref) {
217 *cur++ = '\t';
218 cur = stpcpy (cur, ref->name);
219 *cur++ = '\t';
220 cur = stpcpy (cur, ref->ext);
221 }
222
223 return data;
224 }
225
226 /*
227 Any one of three situations can occur when storing some data.
228
229 1) no simple key is found.
230 store as singular reference.
231 2) simple key already exists, content starts with a '\t'.
232 Already multiple reference. Add our new item in multiple format
233 and update the simple key content, to point to our new one also.
234 3) simple key already exists, content does not start with a '\t'.
235 First we have to reformat the simple key into a multi key for the
236 old item, and insert. Then we have to insert the new data as a
237 multi key. Lastly we must create the simple key and do a replace
238 on it.
239
240 Use precedence algorithm on inserts. If we already have a key assigned
241 to the new value, check priority of page using id. If new page is higher
242 (lower value), replace old with new, otherwise ignore new page.
243
244 If we have two ULT_MAN pages competing for the same key, we must have
245 more than one of foo.sec, foo.sec.comp1, foo.sec.comp2. OR we have a
246 replacement page. If the mtimes differ, throw out the old struct and
247 replace it with the new, if the comp exts differ, oops, this is bad,
248 keep one and return appropriate error code.
249
250 If we have two WHATIS_MAN pages or a WHATIS_MAN and a SO_MAN page
251 competing for the same key, don't worry. This will happen a lot and is
252 not a problem.
253
254 return errorcode or 0 on success.
255 */
256 int dbstore (MYDBM_FILE dbf, struct mandata *in, const char *base)
257 {
258 datum oldkey, oldcont;
259 gl_list_t refs;
260 struct name_ext *ref;
261 char *value;
262 int ret = 0;
263
264 memset (&oldkey, 0, sizeof oldkey);
265 memset (&oldcont, 0, sizeof oldcont);
266
267 /* create a simple key */
268 MYDBM_SET (oldkey, name_to_key (base));
269 if (!*base) {
270 dbprintf (in);
271 return 2;
272 }
273
274 if (in->name) {
275 error (0, 0, "in->name (%s) should not be set when calling "
276 "dbstore()!\n",
277 in->name);
278 free (in->name);
279 in->name = NULL;
280 }
281
282 /* get the content for the simple key */
283
284 oldcont = MYDBM_FETCH (dbf, oldkey);
285
286 if (MYDBM_DPTR (oldcont) == NULL) { /* situation (1) */
287 if (!STREQ (base, MYDBM_DPTR (oldkey)))
288 in->name = xstrdup (base);
289 oldcont = make_content (in);
290 if (MYDBM_REPLACE (dbf, oldkey, oldcont))
291 gripe_replace_key (dbf, MYDBM_DPTR (oldkey));
292 MYDBM_FREE_DPTR (oldcont);
293 free (in->name);
294 in->name = NULL;
295 } else if (*MYDBM_DPTR (oldcont) == '\t') { /* situation (2) */
296 datum newkey, newcont;
297
298 memset (&newkey, 0, sizeof newkey);
299 memset (&newcont, 0, sizeof newcont);
300
301 newkey = make_multi_key (base, in->ext);
302 newcont = make_content (in);
303
304 /* Try to insert the new multi data */
305
306 if (MYDBM_INSERT (dbf, newkey, newcont)) {
307 datum cont;
308 struct mandata *info;
309
310 MYDBM_FREE_DPTR (oldcont);
311 cont = MYDBM_FETCH (dbf, newkey);
312 info = split_content (dbf, MYDBM_DPTR (cont));
313 ret = replace_if_necessary (dbf, in, info,
314 newkey, newcont);
315 MYDBM_FREE_DPTR (cont);
316 free_mandata_struct (info);
317 MYDBM_FREE_DPTR (newkey);
318 MYDBM_FREE_DPTR (newcont);
319 MYDBM_FREE_DPTR (oldkey);
320
321 return ret;
322 }
323
324 /* Now lets add some info to the simple key's cont. */
325
326 /* This next bit needs to be done first as we'll wipe out
327 MYDBM_DPTR (oldcont) otherwise (for NDBM only!) */
328
329 MYDBM_FREE_DPTR (newkey);
330 MYDBM_FREE_DPTR (newcont);
331
332 refs = list_extensions (MYDBM_DPTR (oldcont) + 1);
333 ref = XMALLOC (struct name_ext);
334 /* Not copied. */
335 ref->name = base;
336 ref->ext = in->ext;
337 gl_sortedlist_add (refs, name_ext_compare, ref);
338 value = make_extensions_reference (refs);
339 gl_list_free (refs);
340
341 MYDBM_SET (newcont, value);
342 MYDBM_FREE_DPTR (oldcont);
343
344 /* Try to replace the old simple data with the new stuff */
345
346 if (MYDBM_REPLACE (dbf, oldkey, newcont))
347 gripe_replace_key (dbf, MYDBM_DPTR (oldkey));
348
349 MYDBM_FREE_DPTR (newcont);
350 } else { /* situation (3) */
351 datum newkey, newcont, lastkey, lastcont;
352 struct mandata *old;
353 char *old_name;
354
355 memset (&newkey, 0, sizeof newkey);
356 memset (&newcont, 0, sizeof newcont);
357 memset (&lastkey, 0, sizeof lastkey);
358 memset (&lastcont, 0, sizeof lastcont);
359
360 /* Extract the old singular reference */
361
362 old = split_content (dbf, MYDBM_DPTR (oldcont));
363
364 /* Create multi keys for both old
365 and new items, create new content */
366
367 if (old->name)
368 old_name = xstrdup (old->name);
369 else
370 old_name = xstrdup (MYDBM_DPTR (oldkey));
371
372 lastkey = make_multi_key (old_name, old->ext);
373
374 /* Check against identical multi keys before inserting
375 into db */
376
377 if (STREQ (old_name, base) && STREQ (old->ext, in->ext)) {
378 if (!STREQ (base, MYDBM_DPTR (oldkey)))
379 in->name = xstrdup (base);
380 newcont = make_content (in);
381 ret = replace_if_necessary (dbf, in, old,
382 oldkey, newcont);
383 MYDBM_FREE_DPTR (oldcont);
384 free_mandata_struct (old);
385 MYDBM_FREE_DPTR (newcont);
386 MYDBM_FREE_DPTR (lastkey);
387 MYDBM_FREE_DPTR (oldkey);
388 free (old_name);
389 free (in->name);
390 in->name = NULL;
391
392 return ret;
393 }
394
395 /* Multi keys use the proper case, and so don't need a name
396 * field.
397 */
398 if (old->name) {
399 free (old->name);
400 old->name = NULL;
401 }
402
403 lastcont = make_content (old);
404
405 /* We always replace here; if the multi key already exists
406 * in the database, then that indicates some kind of
407 * database corruption, but our new multi key is almost
408 * certainly better.
409 */
410 if (MYDBM_REPLACE (dbf, lastkey, lastcont))
411 gripe_replace_key (dbf, MYDBM_DPTR (lastkey));
412
413 MYDBM_FREE_DPTR (lastkey);
414 MYDBM_FREE_DPTR (lastcont);
415
416 newkey = make_multi_key (base, in->ext);
417 newcont = make_content (in);
418
419 ret = replace_if_necessary (dbf, in, old, newkey, newcont);
420
421 MYDBM_FREE_DPTR (newkey);
422 MYDBM_FREE_DPTR (newcont);
423
424 /* Now build a simple reference to the above two items */
425
426 refs = gl_list_create_empty (GL_ARRAY_LIST, name_ext_equals,
427 NULL, plain_free, true);
428 ref = XMALLOC (struct name_ext);
429 /* Not copied. */
430 ref->name = old_name;
431 ref->ext = old->ext;
432 gl_sortedlist_add (refs, name_ext_compare, ref);
433 ref = XMALLOC (struct name_ext);
434 /* Not copied. */
435 ref->name = base;
436 ref->ext = in->ext;
437 gl_sortedlist_add (refs, name_ext_compare, ref);
438 value = make_extensions_reference (refs);
439 gl_list_free (refs);
440
441 MYDBM_SET (newcont, value);
442
443 if (MYDBM_REPLACE (dbf, oldkey, newcont))
444 gripe_replace_key (dbf, MYDBM_DPTR (oldkey));
445
446 MYDBM_FREE_DPTR (oldcont);
447 free_mandata_struct (old);
448 MYDBM_FREE_DPTR (newcont);
449 free (old_name);
450 }
451
452 MYDBM_FREE_DPTR (oldkey);
453 return ret;
454 }