1 /*
2 * Copyright (C) 2009, Nokia <ivan.frade@nokia.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 *
19 * Author: Philip Van Hoof <philip@codeminded.be>
20 */
21
22 #include "config.h"
23
24 #define _GNU_SOURCE
25
26 #include <unistd.h>
27 #include <sys/types.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <stdlib.h>
35
36 #include <glib/gstdio.h>
37
38 #ifndef O_LARGEFILE
39 # define O_LARGEFILE 0
40 #endif
41
42 #include <libtracker-common/tracker-crc32.h>
43
44 #include "tracker-db-journal.h"
45
46 #ifndef DISABLE_JOURNAL
47
48 #define MIN_BLOCK_SIZE 1024
49
50 /*
51 * data_format:
52 * #... 0000 0000 (total size is 4 bytes)
53 * | |||`- resource insert (all other bits must be 0 if 1)
54 * | ||`-- object type (1 = id, 0 = cstring)
55 * | |`--- operation type (0 = insert, 1 = delete)
56 * | `---- graph (0 = default graph, 1 = named graph)
57 * `------ update (0 = insert, 1 = update)
58 */
59
60 typedef enum {
61 DATA_FORMAT_RESOURCE_INSERT = 1 << 0,
62 DATA_FORMAT_OBJECT_ID = 1 << 1,
63 DATA_FORMAT_OPERATION_DELETE = 1 << 2,
64 DATA_FORMAT_GRAPH = 1 << 3,
65 DATA_FORMAT_OPERATION_UPDATE = 1 << 4
66 } DataFormat;
67
68 typedef enum {
69 TRANSACTION_FORMAT_NONE = 0,
70 TRANSACTION_FORMAT_DATA = 1 << 0,
71 TRANSACTION_FORMAT_ONTOLOGY = 1 << 1,
72 } TransactionFormat;
73
74 typedef struct {
75 gchar *filename;
76 GDataInputStream *stream;
77 GInputStream *underlying_stream;
78 GFileInfo *underlying_stream_info;
79 GMappedFile *file;
80 const gchar *current;
81 const gchar *end;
82 const gchar *entry_begin;
83 const gchar *entry_end;
84 const gchar *last_success;
85 const gchar *start;
86 guint32 amount_of_triples;
87 gint64 time;
88 TrackerDBJournalEntryType type;
89 gchar *uri;
90 gint g_id;
91 gint s_id;
92 gint p_id;
93 gint o_id;
94 gchar *object;
95 guint current_file;
96 gchar *rotate_to;
97 } JournalReader;
98
99 typedef struct {
100 gchar *journal_filename;
101 int journal;
102 gsize cur_size;
103 guint cur_block_len;
104 guint cur_block_alloc;
105 gchar *cur_block;
106 guint cur_entry_amount;
107 guint cur_pos;
108 } JournalWriter;
109
110 static struct {
111 gsize chunk_size;
112 gboolean do_rotating;
113 gchar *rotate_to;
114 gboolean rotate_progress_flag;
115 } rotating_settings = {0};
116
117 static JournalReader reader = {0};
118 static JournalWriter writer = {0};
119 static JournalWriter ontology_writer = {0};
120
121 static TransactionFormat current_transaction_format;
122
123 #if GLIB_CHECK_VERSION (2, 24, 2)
124 static gboolean tracker_db_journal_rotate (GError **error);
125 #endif /* GLib check */
126
127 static gboolean
128 journal_eof (JournalReader *jreader)
129 {
130 if (jreader->stream) {
131 GBufferedInputStream *bstream;
132
133 bstream = G_BUFFERED_INPUT_STREAM (jreader->stream);
134
135 if (g_buffered_input_stream_get_available (bstream) == 0) {
136 if (g_buffered_input_stream_fill (bstream, -1, NULL, NULL) == 0) {
137 return TRUE;
138 }
139 }
140 } else {
141 if (jreader->current >= jreader->end) {
142 return TRUE;
143 }
144 }
145
146 return FALSE;
147 }
148
149 static guint32
150 read_uint32 (const guint8 *data)
151 {
152 return data[0] << 24 |
153 data[1] << 16 |
154 data[2] << 8 |
155 data[3];
156 }
157
158 static guint32
159 journal_read_uint32 (JournalReader *jreader,
160 GError **error)
161 {
162 guint32 result;
163
164 if (jreader->stream) {
165 result = g_data_input_stream_read_uint32 (jreader->stream, NULL, error);
166 } else {
167 if (jreader->end - jreader->current < sizeof (guint32)) {
168 /* damaged journal entry */
169 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
170 TRACKER_DB_JOURNAL_ERROR_DAMAGED_JOURNAL_ENTRY,
171 "Damaged journal entry, %d < sizeof(guint32)",
172 (gint) (jreader->end - jreader->current));
173 return 0;
174 }
175
176 result = read_uint32 (jreader->current);
pointer targets in passing argument 1 of 'read_uint32' differ in signedness
(emitted by gcc)
177 jreader->current += 4;
178 }
179
180 return result;
181 }
182
183 /* based on GDataInputStream code */
184 static gssize
185 scan_for_nul (GBufferedInputStream *stream,
186 gsize *checked_out)
187 {
188 const gchar *buffer;
189 gsize start, end, peeked;
190 gint i;
191 gsize available, checked;
192
193 checked = *checked_out;
194
195 start = checked;
196 buffer = (const gchar *) g_buffered_input_stream_peek_buffer (stream, &available) + start;
197 end = available;
198 peeked = end - start;
199
200 for (i = 0; checked < available && i < peeked; i++) {
201 if (buffer[i] == '\0') {
202 return (start + i);
203 }
204 }
205
206 checked = end;
207
208 *checked_out = checked;
209 return -1;
210 }
211
212 static gchar *
213 journal_read_string (JournalReader *jreader,
214 GError **error)
215 {
216 gchar *result;
217
218 if (jreader->stream) {
219 /* based on GDataInputStream code */
220
221 GBufferedInputStream *bstream;
222 gsize checked;
223 gssize found_pos;
224
225 bstream = G_BUFFERED_INPUT_STREAM (jreader->stream);
226
227 checked = 0;
228
229 while ((found_pos = scan_for_nul (bstream, &checked)) == -1) {
230 if (g_buffered_input_stream_get_available (bstream) == g_buffered_input_stream_get_buffer_size (bstream)) {
231 g_buffered_input_stream_set_buffer_size (bstream, 2 * g_buffered_input_stream_get_buffer_size (bstream));
232 }
233
234 if (g_buffered_input_stream_fill (bstream, -1, NULL, error) <= 0) {
235 /* error or end of stream */
236 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
237 TRACKER_DB_JOURNAL_ERROR_DAMAGED_JOURNAL_ENTRY,
238 "Damaged journal entry, no terminating zero found");
239 return NULL;
240 }
241 }
242
243 result = g_malloc (found_pos + 1);
244 g_input_stream_read (G_INPUT_STREAM (bstream), result, found_pos + 1, NULL, NULL);
245 } else {
246 gsize str_length;
247
248 str_length = strnlen (jreader->current, jreader->end - jreader->current);
249 if (str_length == jreader->end - jreader->current) {
250 /* damaged journal entry (no terminating '\0' character) */
251 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
252 TRACKER_DB_JOURNAL_ERROR_DAMAGED_JOURNAL_ENTRY,
253 "Damaged journal entry, no terminating zero found");
254 return NULL;
255
256 }
257
258 result = g_strdup (jreader->current);
259
260 jreader->current += str_length + 1;
261 }
262
263 if (!g_utf8_validate (result, -1, NULL)) {
264 /* damaged journal entry (invalid UTF-8) */
265 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
266 TRACKER_DB_JOURNAL_ERROR_DAMAGED_JOURNAL_ENTRY,
267 "Damaged journal entry, invalid UTF-8");
268 g_free (result);
269 return NULL;
270 }
271
272 return result;
273 }
274
275 static gboolean
276 journal_verify_header (JournalReader *jreader)
277 {
278 gchar header[8];
279 gint i;
280 GError *error = NULL;
281
282 /* Version 00003 is identical, it just has no UPDATE operations */
283
284 if (jreader->stream) {
285 for (i = 0; i < sizeof (header); i++) {
286 header[i] = g_data_input_stream_read_byte (jreader->stream, NULL, &error);
287 if (error) {
288 g_clear_error (&error);
289 return FALSE;
290 }
291 }
292
293 if (memcmp (header, "trlog\00004", 8) && memcmp (header, "trlog\00003", 8)) {
294 return FALSE;
295 }
296 } else {
297 /* verify journal file header */
298 if (jreader->end - jreader->current < 8) {
299 return FALSE;
300 }
301
302 if (memcmp (jreader->current, "trlog\00004", 8) && memcmp (jreader->current, "trlog\00003", 8)) {
303 return FALSE;
304 }
305
306 jreader->current += 8;
307 }
308
309 return TRUE;
310 }
311
312 void
313 tracker_db_journal_get_rotating (gboolean *do_rotating,
314 gsize *chunk_size,
315 gchar **rotate_to)
316 {
317 *do_rotating = rotating_settings.do_rotating;
318 *chunk_size = rotating_settings.chunk_size;
319 if (rotating_settings.rotate_to) {
320 *rotate_to = g_strdup (rotating_settings.rotate_to);
321 } else {
322 *rotate_to = NULL;
323 }
324 }
325
326 void
327 tracker_db_journal_set_rotating (gboolean do_rotating,
328 gsize chunk_size,
329 const gchar *rotate_to)
330 {
331 rotating_settings.do_rotating = do_rotating;
332 rotating_settings.chunk_size = chunk_size;
333 g_free (rotating_settings.rotate_to);
334 if (rotate_to) {
335 rotating_settings.rotate_to = g_strdup (rotate_to);
336 } else {
337 rotating_settings.rotate_to = NULL;
338 }
339 }
340
341 static gint
342 nearest_pow (gint num)
343 {
344 gint n = 1;
345 while (n < num)
346 n <<= 1;
347 return n;
348 }
349
350 static void
351 cur_block_maybe_expand (JournalWriter *jwriter, guint len)
352 {
353 guint want_alloc = jwriter->cur_block_len + len;
354
355 if (want_alloc > jwriter->cur_block_alloc) {
356 want_alloc = nearest_pow (want_alloc);
357 want_alloc = MAX (want_alloc, MIN_BLOCK_SIZE);
358 jwriter->cur_block = g_realloc (jwriter->cur_block, want_alloc);
359 jwriter->cur_block_alloc = want_alloc;
360 }
361 }
362
363 static void
364 cur_block_kill (JournalWriter *jwriter)
365 {
366 jwriter->cur_block_len = 0;
367 jwriter->cur_pos = 0;
368 jwriter->cur_entry_amount = 0;
369 jwriter->cur_block_alloc = 0;
370
371 g_free (jwriter->cur_block);
372 jwriter->cur_block = NULL;
373 }
374
375 static void
376 cur_setnum (gchar *dest,
377 guint *pos,
378 guint32 val)
379 {
380 memset (dest + (*pos)++, val >> 24 & 0xff, 1);
381 memset (dest + (*pos)++, val >> 16 & 0xff, 1);
382 memset (dest + (*pos)++, val >> 8 & 0xff, 1);
383 memset (dest + (*pos)++, val >> 0 & 0xff, 1);
384 }
385
386 static void
387 cur_setstr (gchar *dest,
388 guint *pos,
389 const gchar *str,
390 gsize len)
391 {
392 memcpy (dest + *pos, str, len);
393 (*pos) += len;
394 memset (dest + (*pos)++, 0 & 0xff, 1);
395 }
396
397 static gboolean
398 write_all_data (int fd,
399 gchar *data,
400 gsize len,
401 GError **error)
402 {
403 gssize written;
404
405 while (len > 0) {
406 written = write (fd, data, len);
407
408 if (written < 0) {
409 gint err = errno;
410
411 if (err == EINTR) {
412 /* interrupted by signal, try again */
413 continue;
414 }
415
416 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
417 TRACKER_DB_JOURNAL_ERROR_COULD_NOT_WRITE,
418 "Could not write to journal file, %s",
419 g_strerror (err));
420
421 return FALSE;
422 } else if (written == 0) {
423 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
424 TRACKER_DB_JOURNAL_ERROR_COULD_NOT_WRITE,
425 "Could not write to journal file, write returned 0 without error");
426
427 return FALSE;
428 }
429
430 len -= written;
431 data += written;
432 }
433
434 return TRUE; /* Succeeded! */
435 }
436
437 GQuark
438 tracker_db_journal_error_quark (void)
439 {
440 return g_quark_from_static_string (TRACKER_DB_JOURNAL_ERROR_DOMAIN);
441 }
442
443 static gboolean
444 db_journal_init_file (JournalWriter *jwriter,
445 gboolean truncate,
446 GError **error)
447 {
448 struct stat st;
449 int flags;
450 int mode;
451
452 jwriter->cur_block_len = 0;
453 jwriter->cur_pos = 0;
454 jwriter->cur_entry_amount = 0;
455 jwriter->cur_block_alloc = 0;
456 jwriter->cur_block = NULL;
457
458 mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
459 flags = O_WRONLY | O_APPEND | O_CREAT | O_LARGEFILE;
460 if (truncate) {
461 /* existing journal contents are invalid: reindex where journal
462 * does not even contain a single valid entry
463 *
464 * or should be ignored: only for test cases
465 */
466 flags |= O_TRUNC;
467 }
468
469 jwriter->journal = g_open (jwriter->journal_filename, flags, mode);
470
471 if (jwriter->journal == -1) {
472 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
473 TRACKER_DB_JOURNAL_ERROR_COULD_NOT_WRITE,
474 "Could not open journal for writing, %s",
475 g_strerror (errno));
476 return FALSE;
477 }
478
479 if (fstat (jwriter->journal, &st) == 0) {
480 jwriter->cur_size = (gsize) st.st_size;
481 }
482
483 if (jwriter->cur_size == 0) {
484 g_assert (jwriter->cur_block_len == 0);
485 g_assert (jwriter->cur_block_alloc == 0);
486 g_assert (jwriter->cur_block == NULL);
487
488 cur_block_maybe_expand (jwriter, 8);
489
490 jwriter->cur_block[0] = 't';
491 jwriter->cur_block[1] = 'r';
492 jwriter->cur_block[2] = 'l';
493 jwriter->cur_block[3] = 'o';
494 jwriter->cur_block[4] = 'g';
495 jwriter->cur_block[5] = '\0';
496 jwriter->cur_block[6] = '0';
497 jwriter->cur_block[7] = '4';
498
499 if (!write_all_data (jwriter->journal, jwriter->cur_block, 8, error)) {
500 cur_block_kill (jwriter);
501 /* delete empty journal file */
502 g_unlink (jwriter->journal_filename);
503 close (jwriter->journal);
504 jwriter->journal = 0;
505 return FALSE;
506 }
507
508 jwriter->cur_size += 8;
509 cur_block_kill (jwriter);
510 }
511
512 return TRUE;
513 }
514
515 static gboolean
516 db_journal_writer_init (JournalWriter *jwriter,
517 gboolean truncate,
518 gboolean global_writer,
519 const gchar *filename,
520 GError **error)
521 {
522 gchar *directory;
523 gint mode;
524 GError *n_error = NULL;
525 gboolean ret;
526
527 directory = g_path_get_dirname (filename);
528 if (g_strcmp0 (directory, ".")) {
529 mode = S_IRWXU | S_IRWXG | S_IRWXO;
530 if (g_mkdir_with_parents (directory, mode)) {
531
532 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
533 TRACKER_DB_JOURNAL_ERROR_COULD_NOT_WRITE,
534 "tracker data directory does not exist and "
535 "could not be created: %s",
536 g_strerror (errno));
537 g_free (directory);
538
539 return FALSE;
540 }
541 }
542 g_free (directory);
543
544 jwriter->journal_filename = g_strdup (filename);
545
546 ret = db_journal_init_file (jwriter, truncate, &n_error);
547
548 if (n_error) {
549 g_propagate_error (error, n_error);
550 g_free (jwriter->journal_filename);
551 jwriter->journal_filename = NULL;
552 }
553
554 return ret;
555 }
556
557 gboolean
558 tracker_db_journal_init (const gchar *filename,
559 gboolean truncate,
560 GError **error)
561 {
562 gboolean ret;
563 const gchar *filename_use;
564 gchar *filename_free = NULL;
565 GError *n_error = NULL;
566
567 g_return_val_if_fail (writer.journal == 0, FALSE);
568
569 if (filename == NULL) {
570 /* Used mostly for testing */
571 filename_use = g_build_filename (g_get_user_data_dir (),
572 "tracker",
573 "data",
574 TRACKER_DB_JOURNAL_FILENAME,
575 NULL);
576 filename_free = (gchar *) filename_use;
577 } else {
578 filename_use = filename;
579 }
580
581 ret = db_journal_writer_init (&writer, truncate, TRUE, filename_use, &n_error);
582
583 if (n_error) {
584 g_propagate_error (error, n_error);
585 }
586
587 g_free (filename_free);
588
589 return ret;
590 }
591
592 static gboolean
593 db_journal_ontology_init (GError **error)
594 {
595 gboolean ret;
596 gchar *filename;
597 GError *n_error = NULL;
598
599 g_return_val_if_fail (ontology_writer.journal == 0, FALSE);
600
601 filename = g_build_filename (g_get_user_data_dir (),
602 "tracker",
603 "data",
604 TRACKER_DB_JOURNAL_ONTOLOGY_FILENAME,
605 NULL);
606
607 ret = db_journal_writer_init (&ontology_writer, FALSE, FALSE, filename, &n_error);
608
609 if (n_error) {
610 g_propagate_error (error, n_error);
611 }
612
613 g_free (filename);
614
615 return ret;
616 }
617
618 static gboolean
619 db_journal_writer_shutdown (JournalWriter *jwriter,
620 GError **error)
621 {
622 g_free (jwriter->journal_filename);
623 jwriter->journal_filename = NULL;
624
625 if (jwriter->journal == 0) {
626 return TRUE;
627 }
628
629 if (close (jwriter->journal) != 0) {
630 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
631 TRACKER_DB_JOURNAL_ERROR_COULD_NOT_CLOSE,
632 "Could not close journal, %s",
633 g_strerror (errno));
634 return FALSE;
635 }
636
637 jwriter->journal = 0;
638
639 return TRUE;
640 }
641
642 gboolean
643 tracker_db_journal_shutdown (GError **error)
644 {
645 GError *n_error = NULL;
646 gboolean ret;
647
648 ret = db_journal_writer_shutdown (&writer, &n_error);
649
650 if (n_error) {
651 g_propagate_error (error, n_error);
652 }
653
654 return ret;
655 }
656
657 gsize
658 tracker_db_journal_get_size (void)
659 {
660 g_return_val_if_fail (writer.journal > 0, FALSE);
661
662 return writer.cur_size;
663 }
664
665 const gchar *
666 tracker_db_journal_get_filename (void)
667 {
668 /* Journal doesn't have to be open to get the filename, for example when
669 * the file didn't exist and it was attempted opened in only read mode. */
670 return (const gchar*) writer.journal_filename;
671 }
672
673 static gboolean
674 db_journal_writer_start_transaction (JournalWriter *jwriter,
675 time_t time,
676 TransactionFormat kind)
677 {
678 guint size;
679
680 g_return_val_if_fail (jwriter->journal > 0, FALSE);
681 g_return_val_if_fail (current_transaction_format == TRANSACTION_FORMAT_NONE, FALSE);
682
683 current_transaction_format = kind;
684
685 size = sizeof (guint32) * 3;
686 cur_block_maybe_expand (jwriter, size);
687
688 /* Leave space for size, amount and crc
689 * Check and keep in sync the offset variable at
690 * tracker_db_journal_commit_db_transaction too */
691
692 memset (jwriter->cur_block, 0, size);
693
694 jwriter->cur_pos = jwriter->cur_block_len = size;
695 jwriter->cur_entry_amount = 0;
696
697 /* add timestamp */
698 cur_block_maybe_expand (jwriter, sizeof (gint32));
699 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), time);
700 jwriter->cur_block_len += sizeof (gint32);
701
702 /* Add format */
703 cur_block_maybe_expand (jwriter, sizeof (gint32));
704 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), kind);
705 jwriter->cur_block_len += sizeof (gint32);
706
707 return TRUE;
708 }
709
710 gboolean
711 tracker_db_journal_start_transaction (time_t time)
712 {
713 return db_journal_writer_start_transaction (&writer, time,
714 TRANSACTION_FORMAT_DATA);
715 }
716
717 gboolean
718 tracker_db_journal_start_ontology_transaction (time_t time,
719 GError **error)
720 {
721 GError *n_error = NULL;
722
723 if (!db_journal_ontology_init (&n_error)) {
724
725 if (n_error) {
726 g_propagate_error (error, n_error);
727 }
728
729 return FALSE;
730 }
731
732 return db_journal_writer_start_transaction (&ontology_writer, time,
733 TRANSACTION_FORMAT_ONTOLOGY);
734 }
735
736 static gboolean
737 db_journal_writer_append_delete_statement (JournalWriter *jwriter,
738 gint g_id,
739 gint s_id,
740 gint p_id,
741 const gchar *object)
742 {
743 gint o_len;
744 DataFormat df;
745 gint size;
746
747 g_return_val_if_fail (jwriter->journal > 0, FALSE);
748 g_return_val_if_fail (g_id >= 0, FALSE);
749 g_return_val_if_fail (s_id > 0, FALSE);
750 g_return_val_if_fail (p_id > 0, FALSE);
751 g_return_val_if_fail (object != NULL, FALSE);
752
753 o_len = strlen (object);
754 if (g_id == 0) {
755 df = DATA_FORMAT_OPERATION_DELETE;
756 size = (sizeof (guint32) * 3) + o_len + 1;
757 } else {
758 df = DATA_FORMAT_OPERATION_DELETE | DATA_FORMAT_GRAPH;
759 size = (sizeof (guint32) * 4) + o_len + 1;
760 }
761
762 cur_block_maybe_expand (jwriter, size);
763
764 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), df);
765 if (g_id > 0) {
766 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), g_id);
767 }
768 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), s_id);
769 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), p_id);
770 cur_setstr (jwriter->cur_block, &(jwriter->cur_pos), object, o_len);
771
772 jwriter->cur_entry_amount++;
773 jwriter->cur_block_len += size;
774
775 return TRUE;
776 }
777
778 gboolean
779 tracker_db_journal_append_delete_statement (gint g_id,
780 gint s_id,
781 gint p_id,
782 const gchar *object)
783 {
784 if (current_transaction_format == TRANSACTION_FORMAT_ONTOLOGY) {
785 return TRUE;
786 }
787
788 return db_journal_writer_append_delete_statement (&writer,
789 g_id, s_id, p_id, object);
790 }
791
792 static gboolean
793 db_journal_writer_append_delete_statement_id (JournalWriter *jwriter,
794 gint g_id,
795 gint s_id,
796 gint p_id,
797 gint o_id)
798 {
799 DataFormat df;
800 gint size;
801
802 g_return_val_if_fail (jwriter->journal > 0, FALSE);
803 g_return_val_if_fail (g_id >= 0, FALSE);
804 g_return_val_if_fail (s_id > 0, FALSE);
805 g_return_val_if_fail (p_id > 0, FALSE);
806 g_return_val_if_fail (o_id > 0, FALSE);
807
808 if (g_id == 0) {
809 df = DATA_FORMAT_OPERATION_DELETE | DATA_FORMAT_OBJECT_ID;
810 size = sizeof (guint32) * 4;
811 } else {
812 df = DATA_FORMAT_OPERATION_DELETE | DATA_FORMAT_OBJECT_ID | DATA_FORMAT_GRAPH;
813 size = sizeof (guint32) * 5;
814 }
815
816 cur_block_maybe_expand (jwriter, size);
817
818 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), df);
819 if (g_id > 0) {
820 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), g_id);
821 }
822 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), s_id);
823 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), p_id);
824 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), o_id);
825
826 jwriter->cur_entry_amount++;
827 jwriter->cur_block_len += size;
828
829 return TRUE;
830 }
831
832 gboolean
833 tracker_db_journal_append_delete_statement_id (gint g_id,
834 gint s_id,
835 gint p_id,
836 gint o_id)
837 {
838 if (current_transaction_format == TRANSACTION_FORMAT_ONTOLOGY) {
839 return TRUE;
840 }
841
842 return db_journal_writer_append_delete_statement_id (&writer,
843 g_id, s_id, p_id, o_id);
844 }
845
846 static gboolean
847 db_journal_writer_append_insert_statement (JournalWriter *jwriter,
848 gint g_id,
849 gint s_id,
850 gint p_id,
851 const gchar *object)
852 {
853 gint o_len;
854 DataFormat df;
855 gint size;
856
857 g_return_val_if_fail (jwriter->journal > 0, FALSE);
858 g_return_val_if_fail (g_id >= 0, FALSE);
859 g_return_val_if_fail (s_id > 0, FALSE);
860 g_return_val_if_fail (p_id > 0, FALSE);
861 g_return_val_if_fail (object != NULL, FALSE);
862
863 o_len = strlen (object);
864 if (g_id == 0) {
865 df = 0x00;
866 size = (sizeof (guint32) * 3) + o_len + 1;
867 } else {
868 df = DATA_FORMAT_GRAPH;
869 size = (sizeof (guint32) * 4) + o_len + 1;
870 }
871
872 cur_block_maybe_expand (jwriter, size);
873
874 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), df);
875 if (g_id > 0) {
876 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), g_id);
877 }
878 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), s_id);
879 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), p_id);
880 cur_setstr (jwriter->cur_block, &(jwriter->cur_pos), object, o_len);
881
882 jwriter->cur_entry_amount++;
883 jwriter->cur_block_len += size;
884
885 return TRUE;
886 }
887
888 gboolean
889 tracker_db_journal_append_insert_statement (gint g_id,
890 gint s_id,
891 gint p_id,
892 const gchar *object)
893 {
894 if (current_transaction_format == TRANSACTION_FORMAT_ONTOLOGY) {
895 return TRUE;
896 }
897
898 return db_journal_writer_append_insert_statement (&writer,
899 g_id, s_id, p_id, object);
900 }
901
902 static gboolean
903 db_journal_writer_append_insert_statement_id (JournalWriter *jwriter,
904 gint g_id,
905 gint s_id,
906 gint p_id,
907 gint o_id)
908 {
909 DataFormat df;
910 gint size;
911
912 g_return_val_if_fail (jwriter->journal > 0, FALSE);
913 g_return_val_if_fail (g_id >= 0, FALSE);
914 g_return_val_if_fail (s_id > 0, FALSE);
915 g_return_val_if_fail (p_id > 0, FALSE);
916 g_return_val_if_fail (o_id > 0, FALSE);
917
918 if (g_id == 0) {
919 df = DATA_FORMAT_OBJECT_ID;
920 size = sizeof (guint32) * 4;
921 } else {
922 df = DATA_FORMAT_OBJECT_ID | DATA_FORMAT_GRAPH;
923 size = sizeof (guint32) * 5;
924 }
925
926 cur_block_maybe_expand (jwriter, size);
927
928 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), df);
929 if (g_id > 0) {
930 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), g_id);
931 }
932 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), s_id);
933 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), p_id);
934 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), o_id);
935
936 jwriter->cur_entry_amount++;
937 jwriter->cur_block_len += size;
938
939 return TRUE;
940 }
941
942 gboolean
943 tracker_db_journal_append_insert_statement_id (gint g_id,
944 gint s_id,
945 gint p_id,
946 gint o_id)
947 {
948 if (current_transaction_format == TRANSACTION_FORMAT_ONTOLOGY) {
949 return TRUE;
950 }
951
952 return db_journal_writer_append_insert_statement_id (&writer,
953 g_id, s_id, p_id, o_id);
954 }
955
956 static gboolean
957 db_journal_writer_append_update_statement (JournalWriter *jwriter,
958 gint g_id,
959 gint s_id,
960 gint p_id,
961 const gchar *object)
962 {
963 gint o_len;
964 DataFormat df;
965 gint size;
966
967 g_return_val_if_fail (jwriter->journal > 0, FALSE);
968 g_return_val_if_fail (g_id >= 0, FALSE);
969 g_return_val_if_fail (s_id > 0, FALSE);
970 g_return_val_if_fail (p_id > 0, FALSE);
971 g_return_val_if_fail (object != NULL, FALSE);
972
973 o_len = strlen (object);
974 if (g_id == 0) {
975 df = DATA_FORMAT_OPERATION_UPDATE;
976 size = (sizeof (guint32) * 3) + o_len + 1;
977 } else {
978 df = DATA_FORMAT_OPERATION_UPDATE | DATA_FORMAT_GRAPH;
979 size = (sizeof (guint32) * 4) + o_len + 1;
980 }
981
982 cur_block_maybe_expand (jwriter, size);
983
984 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), df);
985 if (g_id > 0) {
986 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), g_id);
987 }
988 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), s_id);
989 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), p_id);
990 cur_setstr (jwriter->cur_block, &(jwriter->cur_pos), object, o_len);
991
992 jwriter->cur_entry_amount++;
993 jwriter->cur_block_len += size;
994
995 return TRUE;
996 }
997
998 gboolean
999 tracker_db_journal_append_update_statement (gint g_id,
1000 gint s_id,
1001 gint p_id,
1002 const gchar *object)
1003 {
1004 if (current_transaction_format == TRANSACTION_FORMAT_ONTOLOGY) {
1005 return TRUE;
1006 }
1007
1008 return db_journal_writer_append_update_statement (&writer,
1009 g_id, s_id, p_id, object);
1010 }
1011
1012 static gboolean
1013 db_journal_writer_append_update_statement_id (JournalWriter *jwriter,
1014 gint g_id,
1015 gint s_id,
1016 gint p_id,
1017 gint o_id)
1018 {
1019 DataFormat df;
1020 gint size;
1021
1022 g_return_val_if_fail (jwriter->journal > 0, FALSE);
1023 g_return_val_if_fail (g_id >= 0, FALSE);
1024 g_return_val_if_fail (s_id > 0, FALSE);
1025 g_return_val_if_fail (p_id > 0, FALSE);
1026 g_return_val_if_fail (o_id > 0, FALSE);
1027
1028 if (g_id == 0) {
1029 df = DATA_FORMAT_OPERATION_UPDATE | DATA_FORMAT_OBJECT_ID;
1030 size = sizeof (guint32) * 4;
1031 } else {
1032 df = DATA_FORMAT_OPERATION_UPDATE | DATA_FORMAT_OBJECT_ID | DATA_FORMAT_GRAPH;
1033 size = sizeof (guint32) * 5;
1034 }
1035
1036 cur_block_maybe_expand (jwriter, size);
1037
1038 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), df);
1039 if (g_id > 0) {
1040 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), g_id);
1041 }
1042 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), s_id);
1043 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), p_id);
1044 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), o_id);
1045
1046 jwriter->cur_entry_amount++;
1047 jwriter->cur_block_len += size;
1048
1049 return TRUE;
1050 }
1051
1052 gboolean
1053 tracker_db_journal_append_update_statement_id (gint g_id,
1054 gint s_id,
1055 gint p_id,
1056 gint o_id)
1057 {
1058 if (current_transaction_format == TRANSACTION_FORMAT_ONTOLOGY) {
1059 return TRUE;
1060 }
1061
1062 return db_journal_writer_append_update_statement_id (&writer,
1063 g_id, s_id, p_id, o_id);
1064 }
1065
1066 static gboolean
1067 db_journal_writer_append_resource (JournalWriter *jwriter,
1068 gint s_id,
1069 const gchar *uri)
1070 {
1071 gint o_len;
1072 DataFormat df;
1073 gint size;
1074
1075 g_return_val_if_fail (jwriter->journal > 0, FALSE);
1076
1077 o_len = strlen (uri);
1078 df = DATA_FORMAT_RESOURCE_INSERT;
1079 size = (sizeof (guint32) * 2) + o_len + 1;
1080
1081 cur_block_maybe_expand (jwriter, size);
1082
1083 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), df);
1084 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), s_id);
1085 cur_setstr (jwriter->cur_block, &(jwriter->cur_pos), uri, o_len);
1086
1087 jwriter->cur_entry_amount++;
1088 jwriter->cur_block_len += size;
1089
1090 return TRUE;
1091 }
1092
1093 gboolean
1094 tracker_db_journal_append_resource (gint s_id,
1095 const gchar *uri)
1096 {
1097 gboolean ret;
1098
1099 g_return_val_if_fail (current_transaction_format != TRANSACTION_FORMAT_NONE, FALSE);
1100
1101 if (current_transaction_format == TRANSACTION_FORMAT_ONTOLOGY) {
1102 ret = db_journal_writer_append_resource (&ontology_writer, s_id, uri);
1103 } else {
1104 ret = db_journal_writer_append_resource (&writer, s_id, uri);
1105 }
1106
1107 return ret;
1108 }
1109
1110 gboolean
1111 tracker_db_journal_rollback_transaction (GError **error)
1112 {
1113 GError *n_error = NULL;
1114
1115 g_return_val_if_fail (writer.journal > 0, FALSE);
1116 g_return_val_if_fail (current_transaction_format != TRANSACTION_FORMAT_NONE, FALSE);
1117
1118 cur_block_kill (&writer);
1119
1120 if (current_transaction_format == TRANSACTION_FORMAT_ONTOLOGY) {
1121 cur_block_kill (&ontology_writer);
1122 db_journal_writer_shutdown (&ontology_writer, &n_error);
1123 }
1124
1125 if (n_error) {
1126 g_propagate_error (error, n_error);
1127 }
1128
1129 current_transaction_format = TRANSACTION_FORMAT_NONE;
1130
1131 return TRUE;
1132 }
1133
1134 gboolean
1135 tracker_db_journal_truncate (gsize new_size)
1136 {
1137 g_return_val_if_fail (writer.journal > 0, FALSE);
1138
1139 return (ftruncate (writer.journal, new_size) != -1);
1140 }
1141
1142 static gboolean
1143 db_journal_writer_commit_db_transaction (JournalWriter *jwriter,
1144 GError **error)
1145 {
1146 guint32 crc;
1147 guint begin_pos;
1148 guint size;
1149 guint offset;
1150
1151 g_return_val_if_fail (jwriter->journal > 0, FALSE);
1152
1153 begin_pos = 0;
1154 size = sizeof (guint32);
1155 offset = sizeof (guint32) * 3;
1156
1157 /* Expand by uint32 for the size check at the end of the entry */
1158 cur_block_maybe_expand (jwriter, size);
1159
1160 jwriter->cur_block_len += size;
1161
1162 /* Write size and amount */
1163 cur_setnum (jwriter->cur_block, &begin_pos, jwriter->cur_block_len);
1164 cur_setnum (jwriter->cur_block, &begin_pos, jwriter->cur_entry_amount);
1165
1166 /* Write size check to end of current journal data */
1167 cur_setnum (jwriter->cur_block, &(jwriter->cur_pos), jwriter->cur_block_len);
1168
1169 /* Calculate CRC from entry triples start (i.e. without size,
1170 * amount and crc) until the end of the entry block.
1171 *
1172 * NOTE: the size check at the end is included in the CRC!
1173 */
1174 crc = tracker_crc32 (jwriter->cur_block + offset, jwriter->cur_block_len - offset);
1175 cur_setnum (jwriter->cur_block, &begin_pos, crc);
1176
1177 if (!write_all_data (jwriter->journal, jwriter->cur_block, jwriter->cur_block_len, error)) {
1178 return FALSE;
1179 }
1180
1181 /* Update journal size */
1182 jwriter->cur_size += jwriter->cur_block_len;
1183
1184 /* Clean up for next transaction */
1185 cur_block_kill (jwriter);
1186
1187 return TRUE;
1188 }
1189
1190 gboolean
1191 tracker_db_journal_commit_db_transaction (GError **error)
1192 {
1193 gboolean ret;
1194 GError *n_error = NULL;
1195
1196 g_return_val_if_fail (current_transaction_format != TRANSACTION_FORMAT_NONE, FALSE);
1197
1198 if (current_transaction_format == TRANSACTION_FORMAT_ONTOLOGY) {
1199 ret = db_journal_writer_commit_db_transaction (&ontology_writer, &n_error);
1200 /* Coalesces the two error reports: */
1201 db_journal_writer_shutdown (&ontology_writer, n_error ? NULL : &n_error);
1202 } else {
1203 ret = db_journal_writer_commit_db_transaction (&writer, &n_error);
1204
1205 #if GLIB_CHECK_VERSION (2, 24, 2)
1206 if (ret) {
1207 if (rotating_settings.do_rotating && (writer.cur_size > rotating_settings.chunk_size)) {
1208 ret = tracker_db_journal_rotate (&n_error);
1209 }
1210 }
1211 #endif /* GLib check */
1212 }
1213
1214 if (n_error) {
1215 g_propagate_error (error, n_error);
1216 }
1217
1218 current_transaction_format = TRANSACTION_FORMAT_NONE;
1219
1220 return ret;
1221 }
1222
1223 gboolean
1224 tracker_db_journal_fsync (void)
1225 {
1226 g_return_val_if_fail (writer.journal > 0, FALSE);
1227
1228 return fsync (writer.journal) == 0;
1229 }
1230
1231 /*
1232 * Reader API
1233 */
1234
1235
1236 static gchar*
1237 reader_get_next_filepath (JournalReader *jreader)
1238 {
1239 gchar *filename_open = NULL;
1240 gchar *test;
1241
1242 test = g_strdup_printf ("%s.%d", jreader->filename, jreader->current_file + 1);
1243
1244 if (g_file_test (test, G_FILE_TEST_EXISTS)) {
1245 jreader->current_file++;
1246 filename_open = test;
1247 } else {
1248 gchar *filename;
1249 GFile *dest_dir, *possible;
1250
1251 /* This is where chunks are being rotated to */
1252 if (rotating_settings.rotate_to) {
1253 dest_dir = g_file_new_for_path (rotating_settings.rotate_to);
1254 } else {
1255 GFile *source;
1256
1257 /* keep compressed journal files in same directory */
1258 source = g_file_new_for_path (test);
1259 dest_dir = g_file_get_parent (source);
1260 g_object_unref (source);
1261 }
1262
1263 filename = g_path_get_basename (test);
1264 g_free (test);
1265 test = filename;
1266 filename = g_strconcat (test, ".gz", NULL);
1267 g_free (test);
1268 possible = g_file_get_child (dest_dir, filename);
1269 g_object_unref (dest_dir);
1270 g_free (filename);
1271
1272 if (g_file_query_exists (possible, NULL)) {
1273 jreader->current_file++;
1274 filename_open = g_file_get_path (possible);
1275 }
1276 g_object_unref (possible);
1277 }
1278
1279 if (filename_open == NULL) {
1280 filename_open = g_strdup (jreader->filename);
1281 /* Last file is the active journal file */
1282 jreader->current_file = 0;
1283 }
1284
1285 return filename_open;
1286 }
1287
1288 static gboolean
1289 db_journal_reader_init_file (JournalReader *jreader,
1290 const gchar *filename,
1291 GError **error)
1292 {
1293 #if GLIB_CHECK_VERSION (2, 24, 2)
1294 if (g_str_has_suffix (filename, ".gz")) {
1295 GFile *file;
1296 GInputStream *stream, *cstream;
1297 GConverter *converter;
1298
1299 file = g_file_new_for_path (filename);
1300
1301 stream = G_INPUT_STREAM (g_file_read (file, NULL, error));
1302 g_object_unref (file);
1303 if (!stream) {
1304 return FALSE;
1305 }
1306
1307 jreader->underlying_stream = g_object_ref (stream);
1308
1309 if (jreader->underlying_stream_info) {
1310 g_object_unref (jreader->underlying_stream_info);
1311 jreader->underlying_stream_info = NULL;
1312 }
1313
1314 converter = G_CONVERTER (g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP));
1315 cstream = g_converter_input_stream_new (stream, converter);
1316 g_object_unref (stream);
1317 g_object_unref (converter);
1318
1319 jreader->stream = g_data_input_stream_new (cstream);
1320 g_object_unref (cstream);
1321 } else {
1322 #endif /* GLib check */
1323 jreader->file = g_mapped_file_new (filename, FALSE, error);
1324
1325 if (!jreader->file) {
1326 return FALSE;
1327 }
1328
1329 jreader->last_success = jreader->start = jreader->current =
1330 g_mapped_file_get_contents (jreader->file);
1331
1332 jreader->end = jreader->current + g_mapped_file_get_length (jreader->file);
1333 #if GLIB_CHECK_VERSION (2, 24, 2)
1334 }
1335 #endif /* GLib check */
1336
1337 if (!journal_verify_header (jreader)) {
1338 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
1339 TRACKER_DB_JOURNAL_ERROR_BEGIN_OF_JOURNAL,
1340 "Damaged journal entry at begin of journal");
1341 return FALSE;
1342 }
1343
1344 return TRUE;
1345 }
1346
1347 static gboolean
1348 db_journal_reader_init (JournalReader *jreader,
1349 gboolean global_reader,
1350 const gchar *filename,
1351 GError **error)
1352 {
1353 gchar *filename_used;
1354 gchar *filename_open;
1355 GError *n_error = NULL;
1356
1357 g_return_val_if_fail (jreader->file == NULL, FALSE);
1358
1359 /* Used mostly for testing */
1360 if (G_UNLIKELY (filename)) {
1361 filename_used = g_strdup (filename);
1362 } else {
1363 filename_used = g_build_filename (g_get_user_data_dir (),
1364 "tracker",
1365 "data",
1366 TRACKER_DB_JOURNAL_FILENAME,
1367 NULL);
1368 }
1369
1370 jreader->filename = filename_used;
1371
1372 reader.current_file = 0;
1373 if (global_reader) {
1374 filename_open = reader_get_next_filepath (jreader);
1375 } else {
1376 filename_open = g_strdup (filename_used);
1377 }
1378
1379 jreader->type = TRACKER_DB_JOURNAL_START;
1380
1381 if (!db_journal_reader_init_file (jreader, filename_open, &n_error)) {
1382 if (!g_error_matches (n_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
1383 !g_error_matches (n_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
1384 /* Do not set error if the file does not exist, just return FALSE */
1385
1386 g_propagate_prefixed_error (error,
1387 n_error,
1388 "Could not create TrackerDBJournalReader for file '%s', ",
1389 jreader->filename);
1390 } else {
1391 g_error_free (n_error);
1392 }
1393
1394 g_free (filename_open);
1395
1396 tracker_db_journal_reader_shutdown ();
1397 return FALSE;
1398 }
1399
1400 g_free (filename_open);
1401
1402 return TRUE;
1403 }
1404
1405 gboolean
1406 tracker_db_journal_reader_init (const gchar *filename,
1407 GError **error)
1408 {
1409 gboolean ret;
1410 GError *n_error = NULL;
1411
1412 ret = db_journal_reader_init (&reader, TRUE, filename, &n_error);
1413
1414 if (n_error) {
1415 g_propagate_error (error, n_error);
1416 }
1417
1418 return ret;
1419 }
1420
1421 gboolean
1422 tracker_db_journal_reader_ontology_init (const gchar *filename,
1423 GError **error)
1424 {
1425 gchar *filename_used;
1426 gboolean result;
1427 GError *n_error = NULL;
1428
1429 /* Used mostly for testing */
1430 if (G_UNLIKELY (filename)) {
1431 filename_used = g_strdup (filename);
1432 } else {
1433 filename_used = g_build_filename (g_get_user_data_dir (),
1434 "tracker",
1435 "data",
1436 TRACKER_DB_JOURNAL_ONTOLOGY_FILENAME,
1437 NULL);
1438 }
1439
1440 result = tracker_db_journal_reader_init (filename_used, &n_error);
1441
1442 g_free (filename_used);
1443
1444 if (n_error) {
1445 g_propagate_error (error, n_error);
1446 }
1447
1448 return result;
1449 }
1450
1451 gsize
1452 tracker_db_journal_reader_get_size_of_correct (void)
1453 {
1454 g_return_val_if_fail (reader.file != NULL, FALSE);
1455
1456 return (gsize) (reader.last_success - reader.start);
1457 }
1458
1459 static gboolean
1460 reader_next_file (GError **error)
1461 {
1462 gchar *filename_open;
1463
1464 filename_open = reader_get_next_filepath (&reader);
1465
1466 if (reader.stream) {
1467 g_object_unref (reader.stream);
1468 reader.stream = NULL;
1469
1470 g_object_unref (reader.underlying_stream);
1471 reader.underlying_stream = NULL;
1472 if (reader.underlying_stream_info) {
1473 g_object_unref (reader.underlying_stream_info);
1474 reader.underlying_stream_info = NULL;
1475 }
1476
1477 } else {
1478 g_mapped_file_unref (reader.file);
1479 reader.file = NULL;
1480 }
1481
1482 if (!db_journal_reader_init_file (&reader, filename_open, error)) {
1483 g_free (filename_open);
1484 tracker_db_journal_reader_shutdown ();
1485 return FALSE;
1486 }
1487
1488 g_free (filename_open);
1489
1490 reader.type = TRACKER_DB_JOURNAL_END_TRANSACTION;
1491
1492 reader.entry_begin = NULL;
1493 reader.entry_end = NULL;
1494 reader.amount_of_triples = 0;
1495
1496 return TRUE;
1497 }
1498
1499 static gboolean
1500 db_journal_reader_shutdown (JournalReader *jreader)
1501 {
1502 if (jreader->stream) {
1503 g_object_unref (jreader->stream);
1504 jreader->stream = NULL;
1505 g_object_unref (jreader->underlying_stream);
1506 jreader->underlying_stream = NULL;
1507 if (jreader->underlying_stream_info) {
1508 g_object_unref (jreader->underlying_stream_info);
1509 jreader->underlying_stream_info = NULL;
1510 }
1511 } else if (jreader->file) {
1512 #if GLIB_CHECK_VERSION(2,22,0)
1513 g_mapped_file_unref (jreader->file);
1514 #else
1515 g_mapped_file_free (jreader->file);
1516 #endif
1517
1518 jreader->file = NULL;
1519 }
1520
1521 g_free (jreader->filename);
1522 jreader->filename = NULL;
1523
1524 jreader->last_success = NULL;
1525 jreader->start = NULL;
1526 jreader->current = NULL;
1527 jreader->end = NULL;
1528 jreader->entry_begin = NULL;
1529 jreader->entry_end = NULL;
1530 jreader->amount_of_triples = 0;
1531 jreader->type = TRACKER_DB_JOURNAL_START;
1532 jreader->uri = NULL;
1533 jreader->g_id = 0;
1534 jreader->s_id = 0;
1535 jreader->p_id = 0;
1536 jreader->o_id = 0;
1537 jreader->object = NULL;
1538
1539 return TRUE;
1540 }
1541
1542 gboolean
1543 tracker_db_journal_reader_shutdown (void)
1544 {
1545 return db_journal_reader_shutdown (&reader);
1546 }
1547
1548 TrackerDBJournalEntryType
1549 tracker_db_journal_reader_get_type (void)
1550 {
1551 g_return_val_if_fail (reader.file != NULL || reader.stream != NULL, FALSE);
1552
1553 return reader.type;
1554 }
1555
1556 static gboolean
1557 db_journal_reader_next (JournalReader *jreader, gboolean global_reader, GError **error)
1558 {
1559 GError *inner_error = NULL;
1560 static gboolean debug_unchecked = TRUE;
1561 static gboolean slow_down = FALSE;
1562
1563 g_return_val_if_fail (jreader->file != NULL || jreader->stream != NULL, FALSE);
1564
1565 /* reset struct */
1566 g_free (jreader->uri);
1567 jreader->uri = NULL;
1568 jreader->g_id = 0;
1569 jreader->s_id = 0;
1570 jreader->p_id = 0;
1571 jreader->o_id = 0;
1572 g_free (jreader->object);
1573 jreader->object = NULL;
1574
1575 /*
1576 * Visual layout of the data in the binary journal:
1577 *
1578 * [
1579 * [magic]
1580 * [version]
1581 * [
1582 * [entry
1583 * [size]
1584 * [amount]
1585 * [crc]
1586 * [time]
1587 * [id id id]
1588 * [id id string]
1589 * [id ...]
1590 * [size]
1591 * ]
1592 * [entry...]
1593 * [entry...]
1594 * ]
1595 * ]
1596 *
1597 * Note: We automatically start at the first entry, upon init
1598 * of the reader, we move past the [magic] and the [version].
1599 */
1600
1601 if (jreader->type == TRACKER_DB_JOURNAL_START ||
1602 jreader->type == TRACKER_DB_JOURNAL_END_TRANSACTION) {
1603 /* Expect new transaction or end of file */
1604 guint32 entry_size;
1605 guint32 entry_size_check;
1606 guint32 crc;
1607 guint32 crc_check;
1608 TransactionFormat t_kind;
1609
1610
1611 if (G_UNLIKELY (debug_unchecked)) {
1612 const gchar *test;
1613
1614 test = g_getenv ("TRACKER_DEBUG_MAKE_JOURNAL_READER_GO_VERY_SLOW");
1615 if (g_strcmp0 (test, "yes") == 0) {
1616 slow_down = TRUE;
1617 }
1618 debug_unchecked = FALSE;
1619 }
1620
1621 if (G_UNLIKELY (slow_down)) {
1622 sleep (1);
1623 }
1624
1625 /* Check the end is not where we currently are */
1626 if (journal_eof (jreader)) {
1627 /* Return FALSE as there is no further entry but
1628 * do not set error as it's not an error case. */
1629 if (global_reader && jreader->current_file != 0) {
1630 if (reader_next_file (error)) {
1631 /* read first entry in next file */
1632 return db_journal_reader_next (jreader, global_reader, error);
1633 } else {
1634 /* no more files */
1635 return FALSE;
1636 }
1637 } else {
1638 return FALSE;
1639 }
1640 }
1641
1642 jreader->entry_begin = jreader->current;
1643
1644 /* Read the first uint32 which contains the size */
1645 entry_size = journal_read_uint32 (jreader, &inner_error);
1646 if (inner_error) {
1647 g_propagate_error (error, inner_error);
1648 return FALSE;
1649 }
1650
1651 /* Check that entry is big enough for header and footer */
1652 if (entry_size < 5 * sizeof (guint32)) {
1653 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
1654 TRACKER_DB_JOURNAL_ERROR_DAMAGED_JOURNAL_ENTRY,
1655 "Damaged journal entry, size %d < 5 * sizeof(guint32)",
1656 (gint) entry_size);
1657 return FALSE;
1658 }
1659
1660 /* Check that entry is smaller than the rest of the file.
1661 Very large entry_size could otherwise cause an overflow
1662 in entry_begin + entry_size below. */
1663 if ((gint64) entry_size > (gint64) (jreader->end - jreader->entry_begin)) {
1664 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
1665 TRACKER_DB_JOURNAL_ERROR_DAMAGED_JOURNAL_ENTRY,
1666 "Damaged journal entry, size %u > %" G_GINT64_FORMAT " (rest of the file)",
1667 entry_size, (gint64)(jreader->end - jreader->entry_begin));
1668 return FALSE;
1669 }
1670
1671 if (!jreader->stream) {
1672 /* Set the bounds for the entry */
1673 jreader->entry_end = jreader->entry_begin + entry_size;
1674
1675 /* Check the end of the entry does not exceed the end
1676 * of the journal.
1677 */
1678 if (jreader->end < jreader->entry_end) {
1679 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
1680 TRACKER_DB_JOURNAL_ERROR_DAMAGED_JOURNAL_ENTRY,
1681 "Damaged journal entry, end < entry end");
1682 return FALSE;
1683 }
1684
1685 /* Read entry size check at the end of the entry */
1686 entry_size_check = read_uint32 (jreader->entry_end - 4);
pointer targets in passing argument 1 of 'read_uint32' differ in signedness
(emitted by gcc)
1687
1688 if (entry_size != entry_size_check) {
1689 /* damaged journal entry */
1690 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
1691 TRACKER_DB_JOURNAL_ERROR_DAMAGED_JOURNAL_ENTRY,
1692 "Damaged journal entry, %d != %d (entry size != entry size check)",
1693 entry_size,
1694 entry_size_check);
1695 return FALSE;
1696 }
1697 }
1698
1699 /* Read the amount of triples */
1700 jreader->amount_of_triples = journal_read_uint32 (jreader, &inner_error);
1701 if (inner_error) {
1702 g_propagate_error (error, inner_error);
1703 return FALSE;
1704 }
1705
1706 /* Read the crc */
1707 crc_check = journal_read_uint32 (jreader, &inner_error);
1708 if (inner_error) {
1709 g_propagate_error (error, inner_error);
1710 return FALSE;
1711 }
1712
1713 if (!jreader->stream) {
1714 // Maybe read in whole transaction in one buffer, so we can do CRC even without mmap (when reading compressed journals)
1715 // might this be too problematic memory-wise
1716
1717 /* Calculate the crc */
1718 crc = tracker_crc32 (jreader->entry_begin + (sizeof (guint32) * 3), entry_size - (sizeof (guint32) * 3));
1719
1720 /* Verify checksum */
1721 if (crc != crc_check) {
1722 /* damaged journal entry */
1723 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
1724 TRACKER_DB_JOURNAL_ERROR_DAMAGED_JOURNAL_ENTRY,
1725 "Damaged journal entry, 0x%.8x != 0x%.8x (crc32 failed)",
1726 crc,
1727 crc_check);
1728 return FALSE;
1729 }
1730 }
1731
1732 /* Read the timestamp */
1733 jreader->time = journal_read_uint32 (jreader, &inner_error);
1734 if (inner_error) {
1735 g_propagate_error (error, inner_error);
1736 return FALSE;
1737 }
1738
1739 t_kind = journal_read_uint32 (jreader, &inner_error);
1740 if (inner_error) {
1741 g_propagate_error (error, inner_error);
1742 return FALSE;
1743 }
1744
1745 if (t_kind == TRANSACTION_FORMAT_DATA)
1746 jreader->type = TRACKER_DB_JOURNAL_START_TRANSACTION;
1747 else
1748 jreader->type = TRACKER_DB_JOURNAL_START_ONTOLOGY_TRANSACTION;
1749
1750 return TRUE;
1751 } else if (jreader->amount_of_triples == 0) {
1752 /* end of transaction */
1753
1754 /* read redundant entry size at end of transaction */
1755 journal_read_uint32 (jreader, &inner_error);
1756 if (inner_error) {
1757 g_propagate_error (error, inner_error);
1758 return FALSE;
1759 }
1760
1761 if (!jreader->stream) {
1762 if (jreader->current != jreader->entry_end) {
1763 /* damaged journal entry */
1764 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
1765 TRACKER_DB_JOURNAL_ERROR_DAMAGED_JOURNAL_ENTRY,
1766 "Damaged journal entry, %p != %p (end of transaction with 0 triples)",
1767 jreader->current,
1768 jreader->entry_end);
1769 return FALSE;
1770 }
1771 }
1772
1773 jreader->type = TRACKER_DB_JOURNAL_END_TRANSACTION;
1774 jreader->last_success = jreader->current;
1775
1776 return TRUE;
1777 } else {
1778 DataFormat df;
1779
1780 df = journal_read_uint32 (jreader, &inner_error);
1781 if (inner_error) {
1782 g_propagate_error (error, inner_error);
1783 return FALSE;
1784 }
1785
1786 if (df == DATA_FORMAT_RESOURCE_INSERT) {
1787 jreader->type = TRACKER_DB_JOURNAL_RESOURCE;
1788
1789 jreader->s_id = journal_read_uint32 (jreader, &inner_error);
1790 if (inner_error) {
1791 g_propagate_error (error, inner_error);
1792 return FALSE;
1793 }
1794
1795 jreader->uri = journal_read_string (jreader, &inner_error);
1796 if (inner_error) {
1797 g_propagate_error (error, inner_error);
1798 return FALSE;
1799 }
1800 } else {
1801 if (df & DATA_FORMAT_OPERATION_DELETE) {
1802 if (df & DATA_FORMAT_OBJECT_ID) {
1803 jreader->type = TRACKER_DB_JOURNAL_DELETE_STATEMENT_ID;
1804 } else {
1805 jreader->type = TRACKER_DB_JOURNAL_DELETE_STATEMENT;
1806 }
1807 } else if (df & DATA_FORMAT_OPERATION_UPDATE) {
1808 if (df & DATA_FORMAT_OBJECT_ID) {
1809 jreader->type = TRACKER_DB_JOURNAL_UPDATE_STATEMENT_ID;
1810 } else {
1811 jreader->type = TRACKER_DB_JOURNAL_UPDATE_STATEMENT;
1812 }
1813 } else {
1814 if (df & DATA_FORMAT_OBJECT_ID) {
1815 jreader->type = TRACKER_DB_JOURNAL_INSERT_STATEMENT_ID;
1816 } else {
1817 jreader->type = TRACKER_DB_JOURNAL_INSERT_STATEMENT;
1818 }
1819 }
1820
1821 if (df & DATA_FORMAT_GRAPH) {
1822 /* named graph */
1823 jreader->g_id = journal_read_uint32 (jreader, &inner_error);
1824 if (inner_error) {
1825 g_propagate_error (error, inner_error);
1826 return FALSE;
1827 }
1828 } else {
1829 /* default graph */
1830 jreader->g_id = 0;
1831 }
1832
1833 jreader->s_id = journal_read_uint32 (jreader, &inner_error);
1834 if (inner_error) {
1835 g_propagate_error (error, inner_error);
1836 return FALSE;
1837 }
1838
1839 jreader->p_id = journal_read_uint32 (jreader, &inner_error);
1840 if (inner_error) {
1841 g_propagate_error (error, inner_error);
1842 return FALSE;
1843 }
1844
1845 if (df & DATA_FORMAT_OBJECT_ID) {
1846 jreader->o_id = journal_read_uint32 (jreader, &inner_error);
1847 if (inner_error) {
1848 g_propagate_error (error, inner_error);
1849 return FALSE;
1850 }
1851 } else {
1852 jreader->object = journal_read_string (jreader, &inner_error);
1853 if (inner_error) {
1854 g_propagate_error (error, inner_error);
1855 return FALSE;
1856 }
1857 }
1858 }
1859
1860 jreader->amount_of_triples--;
1861 return TRUE;
1862 }
1863 }
1864
1865 gboolean
1866 tracker_db_journal_reader_next (GError **error)
1867 {
1868 return db_journal_reader_next (&reader, TRUE, error);
1869 }
1870
1871 gboolean
1872 tracker_db_journal_reader_verify_last (const gchar *filename,
1873 GError **error)
1874 {
1875 guint32 entry_size_check;
1876 gboolean success = FALSE;
1877 JournalReader jreader = { 0 };
1878 GError *n_error = NULL;
1879
1880 if (db_journal_reader_init (&jreader, FALSE, filename, &n_error)) {
1881
1882 if (jreader.end != jreader.current) {
1883 entry_size_check = read_uint32 (jreader.end - 4);
pointer targets in passing argument 1 of 'read_uint32' differ in signedness
(emitted by gcc)
1884
1885 if (jreader.end - entry_size_check < jreader.current) {
1886 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
1887 TRACKER_DB_JOURNAL_ERROR_DAMAGED_JOURNAL_ENTRY,
1888 "Damaged journal entry at end of journal");
1889 db_journal_reader_shutdown (&jreader);
1890 return FALSE;
1891 }
1892
1893 jreader.current = jreader.end - entry_size_check;
1894 success = db_journal_reader_next (&jreader, FALSE, NULL);
1895 db_journal_reader_shutdown (&jreader);
1896 } else {
1897 success = TRUE;
1898 }
1899 }
1900
1901 if (n_error) {
1902 g_propagate_error (error, n_error);
1903 }
1904
1905 return success;
1906 }
1907
1908 gint64
1909 tracker_db_journal_reader_get_time (void)
1910 {
1911 return reader.time;
1912 }
1913
1914 gboolean
1915 tracker_db_journal_reader_get_resource (gint *id,
1916 const gchar **uri)
1917 {
1918 g_return_val_if_fail (reader.file != NULL || reader.stream != NULL, FALSE);
1919 g_return_val_if_fail (reader.type == TRACKER_DB_JOURNAL_RESOURCE, FALSE);
1920
1921 *id = reader.s_id;
1922 *uri = reader.uri;
1923
1924 return TRUE;
1925 }
1926
1927 gboolean
1928 tracker_db_journal_reader_get_statement (gint *g_id,
1929 gint *s_id,
1930 gint *p_id,
1931 const gchar **object)
1932 {
1933 g_return_val_if_fail (reader.file != NULL || reader.stream != NULL, FALSE);
1934 g_return_val_if_fail (reader.type == TRACKER_DB_JOURNAL_INSERT_STATEMENT ||
1935 reader.type == TRACKER_DB_JOURNAL_DELETE_STATEMENT ||
1936 reader.type == TRACKER_DB_JOURNAL_UPDATE_STATEMENT,
1937 FALSE);
1938
1939 if (g_id) {
1940 *g_id = reader.g_id;
1941 }
1942 *s_id = reader.s_id;
1943 *p_id = reader.p_id;
1944 *object = reader.object;
1945
1946 return TRUE;
1947 }
1948
1949 gboolean
1950 tracker_db_journal_reader_get_statement_id (gint *g_id,
1951 gint *s_id,
1952 gint *p_id,
1953 gint *o_id)
1954 {
1955 g_return_val_if_fail (reader.file != NULL || reader.stream != NULL, FALSE);
1956 g_return_val_if_fail (reader.type == TRACKER_DB_JOURNAL_INSERT_STATEMENT_ID ||
1957 reader.type == TRACKER_DB_JOURNAL_DELETE_STATEMENT_ID ||
1958 reader.type == TRACKER_DB_JOURNAL_UPDATE_STATEMENT_ID,
1959 FALSE);
1960
1961 if (g_id) {
1962 *g_id = reader.g_id;
1963 }
1964 *s_id = reader.s_id;
1965 *p_id = reader.p_id;
1966 *o_id = reader.o_id;
1967
1968 return TRUE;
1969 }
1970
1971 gdouble
1972 tracker_db_journal_reader_get_progress (void)
1973 {
1974 gdouble chunk = 0, total = 0, ret = 0;
1975 guint current_file;
1976 static guint total_chunks = 0;
1977
1978 current_file = reader.current_file == 0 ? total_chunks -1 : reader.current_file -1;
1979
1980 if (!rotating_settings.rotate_progress_flag) {
1981 gchar *test;
1982 GFile *dest_dir;
1983 gboolean cont = TRUE;
1984
1985 total_chunks = 0;
1986
1987 test = g_path_get_basename (reader.filename);
1988
1989 if (rotating_settings.rotate_to) {
1990 dest_dir = g_file_new_for_path (rotating_settings.rotate_to);
1991 } else {
1992 GFile *source;
1993
1994 /* keep compressed journal files in same directory */
1995 source = g_file_new_for_path (test);
1996 dest_dir = g_file_get_parent (source);
1997 g_object_unref (source);
1998 }
1999
2000 g_free (test);
2001
2002 while (cont) {
2003 gchar *filename;
2004 GFile *possible;
2005
2006 test = g_strdup_printf ("%s.%d", reader.filename, total_chunks + 1);
2007 filename = g_path_get_basename (test);
2008 g_free (test);
2009 test = filename;
2010 filename = g_strconcat (test, ".gz", NULL);
2011 g_free (test);
2012 possible = g_file_get_child (dest_dir, filename);
2013 g_free (filename);
2014 if (g_file_query_exists (possible, NULL)) {
2015 total_chunks++;
2016 } else {
2017 cont = FALSE;
2018 }
2019 g_object_unref (possible);
2020 }
2021
2022 g_object_unref (dest_dir);
2023 rotating_settings.rotate_progress_flag = TRUE;
2024 }
2025
2026 if (total_chunks > 0) {
2027 total = ((gdouble) ((gdouble) current_file) / ((gdouble) total_chunks));
2028 }
2029
2030 if (reader.start != 0) {
2031 /* When the last uncompressed part is being processed: */
2032 gdouble percent = ((gdouble)(reader.end - reader.start));
2033 ret = chunk = (((gdouble)(reader.current - reader.start)) / percent);
2034 } else if (reader.underlying_stream) {
2035 goffset size;
2036
2037 /* When a compressed part is being processed: */
2038
2039 if (!reader.underlying_stream_info) {
2040 reader.underlying_stream_info =
2041 g_file_input_stream_query_info (G_FILE_INPUT_STREAM (reader.underlying_stream),
2042 G_FILE_ATTRIBUTE_STANDARD_SIZE,
2043 NULL, NULL);
2044 }
2045
2046 if (reader.underlying_stream_info) {
2047 size = g_file_info_get_size (reader.underlying_stream_info);
2048 ret = chunk = (gdouble) ((gdouble)g_seekable_tell (G_SEEKABLE (reader.underlying_stream))) / ((gdouble)size);
2049 }
2050 }
2051
2052 if (total_chunks > 0) {
2053 ret = total + (chunk / (gdouble) total_chunks);
2054 }
2055
2056 return ret;
2057 }
2058
2059 #if GLIB_CHECK_VERSION (2, 24, 2)
2060 static void
2061 on_chunk_copied_delete (GObject *source_object,
2062 GAsyncResult *res,
2063 gpointer user_data)
2064 {
2065 GOutputStream *ostream = G_OUTPUT_STREAM (source_object);
2066 GError *error = NULL;
2067 GFile *source = G_FILE (user_data);
2068
2069 g_output_stream_splice_finish (ostream, res, &error);
2070 if (!error) {
2071 g_file_delete (G_FILE (source), NULL, &error);
2072 }
2073
2074 g_object_unref (source);
2075
2076 if (error) {
2077 g_critical ("Error compressing rotated journal chunk: '%s'", error->message);
2078 g_error_free (error);
2079 }
2080 }
2081
2082 static gboolean
2083 tracker_db_journal_rotate (GError **error)
2084 {
2085 GFile *source, *destination;
2086 GFile *dest_dir;
2087 gchar *filename, *gzfilename;
2088 gchar *fullpath;
2089 GConverter *converter;
2090 GInputStream *istream;
2091 GOutputStream *ostream, *cstream;
2092 static gint max = 0;
2093 GError *n_error = NULL;
2094 gboolean ret;
2095
2096 #ifdef DISABLE_JOURNAL
2097 g_critical ("Journal is disabled, yet a journal function got called");
2098 #endif
2099
2100 if (max == 0) {
2101 gchar *directory;
2102 GDir *journal_dir;
2103 const gchar *f_name;
2104
2105 directory = g_path_get_dirname (writer.journal_filename);
2106 journal_dir = g_dir_open (directory, 0, NULL);
2107
2108 f_name = g_dir_read_name (journal_dir);
2109
2110 while (f_name) {
2111 const gchar *ptr;
2112 guint cur;
2113
2114 if (f_name) {
2115
2116 if (!g_str_has_prefix (f_name, TRACKER_DB_JOURNAL_FILENAME ".")) {
2117 f_name = g_dir_read_name (journal_dir);
2118 continue;
2119 }
2120
2121 ptr = f_name + strlen (TRACKER_DB_JOURNAL_FILENAME ".");
2122 cur = atoi (ptr);
2123 max = MAX (cur, max);
2124 }
2125
2126 f_name = g_dir_read_name (journal_dir);
2127 }
2128
2129 g_dir_close (journal_dir);
2130 g_free (directory);
2131 }
2132
2133 tracker_db_journal_fsync ();
2134
2135 if (close (writer.journal) != 0) {
2136 g_set_error (error, TRACKER_DB_JOURNAL_ERROR,
2137 TRACKER_DB_JOURNAL_ERROR_COULD_NOT_CLOSE,
2138 "Could not close journal, %s",
2139 g_strerror (errno));
2140 return FALSE;
2141 }
2142
2143 fullpath = g_strdup_printf ("%s.%d", writer.journal_filename, ++max);
2144
2145 g_rename (writer.journal_filename, fullpath);
2146
2147 /* Recalculate progress next time */
2148 rotating_settings.rotate_progress_flag = FALSE;
2149
2150 source = g_file_new_for_path (fullpath);
2151 if (rotating_settings.rotate_to) {
2152 dest_dir = g_file_new_for_path (rotating_settings.rotate_to);
2153 } else {
2154 /* keep compressed journal files in same directory */
2155 dest_dir = g_file_get_parent (source);
2156 }
2157 filename = g_path_get_basename (fullpath);
2158 gzfilename = g_strconcat (filename, ".gz", NULL);
2159 destination = g_file_get_child (dest_dir, gzfilename);
2160 g_object_unref (dest_dir);
2161 g_free (filename);
2162 g_free (gzfilename);
2163
2164 istream = G_INPUT_STREAM (g_file_read (source, NULL, NULL));
2165 ostream = G_OUTPUT_STREAM (g_file_create (destination, 0, NULL, NULL));
2166 converter = G_CONVERTER (g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1));
2167 cstream = g_converter_output_stream_new (ostream, converter);
2168 g_output_stream_splice_async (cstream, istream, 0, 0, NULL, on_chunk_copied_delete, source);
2169 g_object_unref (istream);
2170 g_object_unref (ostream);
2171 g_object_unref (converter);
2172 g_object_unref (cstream);
2173
2174 g_object_unref (destination);
2175
2176 g_free (fullpath);
2177
2178 ret = db_journal_init_file (&writer, TRUE, &n_error);
2179
2180 if (n_error) {
2181 g_propagate_error (error, n_error);
2182 g_free (writer.journal_filename);
2183 writer.journal_filename = NULL;
2184 }
2185
2186 return ret;
2187 }
2188 #endif /* GLib check */
2189
2190 #else /* DISABLE_JOURNAL */
2191 void
2192 tracker_db_journal_set_rotating (gboolean do_rotating,
2193 gsize chunk_size,
2194 const gchar *rotate_to)
2195 {
2196 /* intentionally left blank, used for internal API compatibility */
2197 }
2198 #endif /* DISABLE_JOURNAL */