In the previous chapter we introduced a bit of libjf using only one journaled file at a time: that usage may help you in writing easier programs because you don't have to implement the "back-out" logic, it's a gift of libjf.
Now it's time to investigate how libjf can solve "data integrity issue" at its root; the following examples will exploit only two journaled files, but the principle can be extended to any number.
Example 3-1. two_files.c
1 #include <jf_file.h> 2 int main() 3 { 4 int rc; 5 size_t write; 6 jf_journal_t j; 7 jf_file_t jf1, jf2; 8 struct jf_journal_opts_s jopts; 9 struct jf_file_open_opts_s fopts; 10 const char *file1_data1 = "First string for first file\n"; 11 const char *file1_data2 = "Second string for first file\n"; 12 const char *file2_data1 = "First string for second file\n"; 13 const char *file2_data2 = "Second string for second file\n"; 14 jf_set_default_journal_opts(&jopts); 15 jopts.flags |= JF_JOURNAL_PROP_OPEN_O_CREAT | 16 JF_JOURNAL_PROP_OPEN_O_EXCL; 17 rc = jf_journal_open(&j, "jf_tut_foo-journal", 2, &jopts); 18 if (JF_RC_OK != rc) { 19 printf("%d/%s\n", rc, jf_strerror(rc)); 20 return 1; 21 } 22 jf_set_default_file_open_opts(&fopts); 23 fopts.join_the_journal = TRUE; 24 rc = jf_file_open(&jf1, &j, "jf_tut_foo-data1", "w", &fopts); 25 if (JF_RC_OK != rc) { 26 printf("%d/%s\n", rc, jf_strerror(rc)); 27 return 1; 28 } 29 rc = jf_file_open(&jf2, &j, "jf_tut_foo-data2", "w", &fopts); 30 if (JF_RC_OK != rc) { 31 printf("%d/%s\n", rc, jf_strerror(rc)); 32 return 1; 33 } 34 rc = jf_file_write(&jf1, file1_data1, strlen(file1_data1), 35 &write); 36 if (JF_RC_OK != rc) { 37 printf("%d/%s\n", rc, jf_strerror(rc)); 38 return 1; 39 } 40 rc = jf_file_write(&jf2, file2_data1, strlen(file2_data1), 41 &write); 42 if (JF_RC_OK != rc) { 43 printf("%d/%s\n", rc, jf_strerror(rc)); 44 return 1; 45 } 46 rc = jf_journal_rollback(&j); 47 if (JF_RC_OK != rc) { 48 printf("%d/%s\n", rc, jf_strerror(rc)); 49 return 1; 50 } 51 rc = jf_file_write(&jf1, file1_data2, strlen(file1_data2), 52 &write); 53 if (JF_RC_OK != rc) { 54 printf("%d/%s\n", rc, jf_strerror(rc)); 55 return 1; 56 } 57 rc = jf_file_write(&jf2, file2_data2, strlen(file2_data2), 58 &write); 59 if (JF_RC_OK != rc) { 60 printf("%d/%s\n", rc, jf_strerror(rc)); 61 return 1; 62 } 63 rc = jf_journal_commit(&j); 64 if (JF_RC_OK != rc) { 65 printf("%d/%s\n", rc, jf_strerror(rc)); 66 return 1; 67 } 68 rc = jf_file_close(&jf1); 69 if (JF_RC_OK != rc) { 70 printf("%d/%s\n", rc, jf_strerror(rc)); 71 return 1; 72 } 73 rc = jf_file_close(&jf2); 74 if (JF_RC_OK != rc) { 75 printf("%d/%s\n", rc, jf_strerror(rc)); 76 return 1; 77 } 78 rc = jf_journal_close(&j); 79 if (JF_RC_OK != rc) { 80 printf("%d/%s\n", rc, jf_strerror(rc)); 81 return 1; 82 } 83 printf("two_files program ended OK!\n"); 84 return 0; 85 }
two_files.c code explanation
to use libjf a program must include at least jf_file.h header file
declare object j of type jf_journal_t: j is a "journal object"
declare object jf1, jf2 of type jf_file_t: jf1 and jf2 are a "journaled file objects"
declare a struct will be used to store options related to journal
declare a struct will be used to store options related to journaled files
set default value for options related to journal
add some flags to journal related options: the journal must be created and the journal must not exist
open the journal file jf_tut_foo-journal and associate it to journal object j, the journal will handle 2 journaled files and use options specified by struct jopts
check previous operation return code: print the return code and its human readable description if something goes wrong
set default value for options related to journaled files
specify the journaled file must "join the journal"; joining a journal means the journal will store all transactional information about the journaled file
open journaled file jf_tut_foo-data1, associate to object jf1, use journal j, data will be written ("w") and additional options must be picked-up from fopts
open journaled file jf_tut_foo-data2, associate to object jf2, use journal j, data will be written ("w") and additional options must be picked-up from fopts
write string file1_data1 to journaled file jf1
write string file2_data1 to journaled file jf2
back out all the changes operated on all the journaled files managed by journal object j
write string file1_data2 to journaled file jf1
write string file2_data2 to journaled file jf2
commit all the changes operated on all the journaled files managed by journal object j
close journaled file jf1
close journaled file jf2
close journal j
To compile two_files.c you may use this libtool command:
libtool --mode=link gcc -Wall -I/opt/libjf/include -L/opt/libjf/lib \ -ljf -o two_files two_files.crun two_files program:
tiian@linux:~/tutorial> ./two_files two_files program ended OK!take a look to the files produced by two_files execution:
tiian@linux:~/tutorial> ls -la jf_tut_foo* -rw-r--r-- 1 tiian users 29 2005-08-11 16:50 jf_tut_foo-data1 -rw-r--r-- 1 tiian users 30 2005-08-11 16:50 jf_tut_foo-data2 -rw-r--r-- 1 tiian users 16607 2005-08-11 16:50 jf_tut_foo-journalinspect first and second journaled file:
tiian@linux:~/tutorial> cat jf_tut_foo-data1 Second string for first file tiian@linux:~/tutorial> cat jf_tut_foo-data2 Second string for second filetake a look to journal content with command
tiian@linux:~/tutorial> jf_report -j jf_tut_foo-journal -dtthe first strings, on all journaled files, were backed out by
jf_journal_rollback
at line 46; the second strings,
on all journaled files, were committed by
jf_journal_commit
at line 63.
You should note these aspects in two_files.c source code:
in libjf API some data types are not "typedefed" and they must be used with the explicit reserved word "struct" (rows 8-9); this is a desired behavior because some structs should not be interpreted as "classes". This simple rule can help you understanding the idea:
"classes" are types with name terminating in "_t" and methods to create/destroy/manage: you must not access "properties" of an object with "methods" (functions) out of the scope of the "class"
"structs" are commodity data aggregation with name terminating in "_s" and no methods to manipulate them: you have to set the values of the interesting fields when necessary
it's a good programming practice to close journaled files before closing journal: leaving objects in open status will cause a useless, time consuming, automatic recovery at next open time; forgetting object closure may lead to memory leak bugs.
What happens in the event of an application crash?