Data Pile

Against Digital Amnesia

Programming With the APR - Persisting Information

This article will show you how to store information in the database that comes with the Apache Portable Runtime (APR). Basically a simple struct data type is persisted into that database. You can download the code here.

NOTE: The program supplied should actually use apr_app_initialize(&argc, &argv, NULL); since apr_initialize() is intended for library use only.

Function description

In order to store the information in that database you probably might want to create some functions to better break down work into smaller parts. Since the database mechanism requires you to have information available as an apr_datum_t type, you probably want to introduce some helper functions which exactly deal with that issue. Converting your normal data types into a apr_datum_t.

Helper functions

My helper functions which convert a normal char* or an apr_time_t into the required apr_datum_t are called:

  • string2datum(apr_pool_t *p, char *toDatum)
  • time2datum(apr_pool_t *p, apr_time_t *toDump)
  • checkError(apr_status_t rv)

You eventually might ask what the pool is for. Since the apr_datum_t type holds a char * and the according size of that pointer, we have to allocate space somewhere. That space is allocated on the pool you give that function. So make sure your pools lifetime is big enough so you can retrieve the value the function built for you. The checkError function is used for checking the return value that the various database access functions might return. This is basically to avoid lots of typing.

Worker functions

Now you need a few functions that do the actual work for you. APRs abstraction is not bad, but cannot handle automatic key generation in this case. APR uses a key for every saved apr_datum_t. We want to store a more complex datatype to disc, so we need to have a key for every field of our structure when we do want to save it.

The basic idea is to flatten the structure when storing it. So we are writing every field of the structure linearly to disc. When we read the data from disc we do that in the same order as we have written it. This mechanism should restore the original data structure we were working with. The following functions persist a single datum, a whole struct and the read function reads back the whole struct from the disc.

  • apr_status_t persistDatum(unsigned int *key, apr_dbm_t *database, apr_datum_t *toDump)
  • apr_status_t persistUserRecord(unsigned int *key, apr_dbm_t *database, struct userRecord *user)
  • struct userRecord *readUserRecord(apr_pool_t *resultPool, apr_dbm_t *db, apr_datum_t *lastReadKey)

These functions are not implemented, but might give you an idea of how the API can look like if you want to serialize a whole bunch of data.

  • void persistUserRecords(apr_table_t *userRecords)
  • apr_table_t *readUserRecords(apr_pool_t *resultPool)

You surely are interested in how the actual code looks by now. Now i will no longer deprive you from that.

APR With SDBM (aprWithSdbm.c) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#include <apr.h>
#include <apu.h>
#include <apr_dbm.h>
#include <apr_time.h>
#include <apr_strings.h>
#define APR_WANT_STDIO
#define APR_WANT_MEMFUNC
#include <apr_want.h>


#define DBM_TYPE "SDBM"
#define DEBUG 
#ifdef DEBUG
#define PDEBUG(message, arguments) printf("[DEBUG] " message "\n", arguments)
#define PDEBUG_NA(message) printf("[DEBUG] " message "\n")
#else
#define PDEBUG(message, arguments) /* empty */
#define PDEBUG_NA(message) /* empty */
#endif

/* Compile with 
 * $> export APR_LIBS="`apr-1-config --cflags --cppflags --includes --ldflags --link-ld --libs`"
 * $> export APU_LIBS="`apu-1-config --includes --ldflags --link-ld --libs`"
 * $> gcc aprWithSdbm.c -o aprWithSdbm $APR_LIBS $APU_LIBS
 */

struct userRecord
{
  apr_time_t creationDate;
  char *username;
  char *password;
};

void print(struct userRecord *user);
/* since apr_datum_t* results in a nested structure
 * this allocates the memory on the nested 
 * char* onto the given pool.
 */
apr_datum_t* string2datum(apr_pool_t *p,
			  char *toDatum);
apr_datum_t* time2datum(apr_pool_t *p,
			apr_time_t *toDatum);

apr_status_t persistDatum(unsigned int *key,
			  apr_dbm_t *database,
			  apr_datum_t *toDump);

apr_status_t persistUserRecord(unsigned int *key,
			       apr_dbm_t *database,
			       struct userRecord *user);

void persistUserRecords(apr_table_t *userRecords);
struct userRecord *readUserRecord(apr_pool_t *resultPool,
				  apr_dbm_t *db,
				  apr_datum_t *lastReadKey);
apr_table_t *readUserRecords(apr_pool_t *resultPool);
void checkError(apr_status_t rv);


int main(int argc, char **argv)
{

  struct userRecord *user, *user2 = NULL;
  struct userRecord *userReadBack, *userReadBack2 = NULL;
  apr_pool_t *mainPool = NULL;
  apr_dbm_t *db;
  unsigned int counter = 0;
  apr_datum_t readKey;
  apr_status_t rv;


  /* Always init at program start */
  apr_initialize();

  /* Register cleanup */
  atexit(apr_terminate);

  /* create our main memory pool,
   * which will be valid during the programs lifetime
   */
  apr_pool_create(&mainPool, NULL);

  /* Allocate memory for a user on the main pool */
  user = apr_palloc(mainPool, sizeof(struct userRecord));

  user->username = apr_pstrdup(mainPool, "Jens Frey");
  user->password = apr_pstrdup(mainPool, "secret");
  user->creationDate = apr_time_now();

  user2 = apr_palloc(mainPool, sizeof(struct userRecord));

  user2->username = apr_pstrdup(mainPool, "Master Frey");
  user2->password = apr_pstrdup(mainPool, "more secret password");
  user2->creationDate = apr_time_now();

  print(user);
  print(user2);
  apr_dbm_open_ex(&db, DBM_TYPE, "/tmp/testDatabase", APR_DBM_RWTRUNC,
	       APR_FPROT_OS_DEFAULT, mainPool);

  PDEBUG("Counter value: %u", counter);
  persistUserRecord(&counter, db, user);
  persistUserRecord(&counter, db, user2);
  PDEBUG("Counter value: %u", counter);

  //Query the first Key, the rest are subsequently queried alone
  rv =  apr_dbm_firstkey(db, &readKey);
  checkError(rv);
  userReadBack = readUserRecord(mainPool, db, &readKey);
  userReadBack2 = readUserRecord(mainPool, db, &readKey);

  print(userReadBack);
  print(userReadBack2);

  apr_dbm_close(db);
  apr_pool_destroy(mainPool);
  return 0;
}

void checkError(apr_status_t rv)
{
  if (rv != APR_SUCCESS) {
    printf("An error happened while operating with the database (code %d)\n", rv);
  }
}

struct userRecord *readUserRecord(apr_pool_t *p, apr_dbm_t *db, apr_datum_t *lastReadKey)
{
  apr_datum_t tmp;
  apr_status_t rv;

  struct userRecord *user = apr_palloc(p, sizeof(struct userRecord));

  PDEBUG_NA("enter readUserRecord()");

  /* Read username */
  rv = apr_dbm_fetch(db, *lastReadKey, &tmp);
  checkError(rv);
  user->username = apr_pstrndup(p, tmp.dptr, tmp.dsize);

  /* Advance to next key and read password */
  rv = apr_dbm_nextkey(db, lastReadKey);
  checkError(rv);
  rv = apr_dbm_fetch(db, *lastReadKey, &tmp);
  checkError(rv);
  user->password = apr_pstrndup(p, tmp.dptr, tmp.dsize);

  /* Timestamp */
  rv = apr_dbm_nextkey(db, lastReadKey);
  checkError(rv);
  rv = apr_dbm_fetch(db, *lastReadKey, &tmp);
  checkError(rv);
  /* Memcpy that, that part of the structure is already alloced */
  memcpy(&user->creationDate, tmp.dptr, sizeof(apr_time_t));


  /* Set the key to the next element, so a subsequent call will
     be correct again
  */
  rv = apr_dbm_nextkey(db, lastReadKey);
  checkError(rv);

  PDEBUG_NA("leave readUserRecord()");
  return user;
}

/* Converts an apr_time_t into a
 * persistable apr_datum_t
 */
apr_datum_t* time2datum(apr_pool_t *p, apr_time_t *toDatum)
{
  apr_size_t size = 0;
  apr_datum_t *dt = apr_palloc(p, sizeof(apr_datum_t));

  size = sizeof(apr_time_t);
  dt->dptr = apr_pmemdup(p, toDatum, size);
  dt->dsize = size;

  return dt;
}

/* 
 * Converts the given char* into a apr_datum_t with lengths set correctly
 * depending on the database type. The memory required for the string
 * to be held is saved in the given memory pool.
 *
 */
apr_datum_t* string2datum(apr_pool_t *p, char *toDatum)
{
  apr_datum_t *dt = apr_palloc(p, sizeof(apr_datum_t));

  dt->dptr = apr_pstrdup(p, toDatum);

#ifndef NETSCAPE_DBM_COMPAT
  dt->dsize = strlen(dt->dptr);
#else
  dt->dsize = strlen(dt->dptr) + 1;
#endif

    return dt;
}


/* This function persists the whole struct userRecord
 * into the SDBM database. You have to check if this implementation
 * is still correct if you change your struct.
 *
 * There should however be no problem if you are just adding transient
 * parameters that should not be persisted at all.
 */
apr_status_t persistUserRecord(unsigned int *key, apr_dbm_t *database, struct userRecord *user)
{
  apr_status_t rv;
  apr_datum_t *dumpKey;
  apr_pool_t *p;
  PDEBUG_NA("enter persistUserRecord()");

  apr_pool_create(&p, NULL);

  /* Now fill in every field of the record */
  dumpKey = string2datum(p, user->username);
  persistDatum(key, database, dumpKey);

  dumpKey = string2datum(p, user->password);
  persistDatum(key, database, dumpKey);

  dumpKey = time2datum(p, &user->creationDate);
  persistDatum(key, database, dumpKey);

  apr_pool_destroy(p);
  PDEBUG_NA("leave persistUserRecord()");
  return rv;
}


/*
  Assumes an open database.
 */
apr_status_t persistDatum(unsigned int *key, apr_dbm_t *database, apr_datum_t *toDump)
{
  apr_status_t rv;
  apr_datum_t datumKey;
  datumKey.dptr = (char *) key;
  datumKey.dsize = sizeof(unsigned int);



  PDEBUG_NA("enter persistDatum()");
  rv = apr_dbm_store(database, datumKey, *toDump);
  if (rv != APR_SUCCESS) {
    printf("Error saving datum in database (returned %d)", rv);
  }

  //Increment count for every written datum
  (*key)++;
  PDEBUG("Key is %u", *key);
  PDEBUG_NA("leave persistDatum()");
  return rv;
}

void print(struct userRecord *user)
{
  apr_pool_t *p = NULL;
  char *timeReadable = NULL;
  apr_pool_create(&p, NULL);

  timeReadable = apr_palloc(p, APR_RFC822_DATE_LEN);
  apr_rfc822_date(timeReadable, user->creationDate);

  PDEBUG("Username: %s", user->username);
  PDEBUG("Password: %s", user->password);
  PDEBUG("CreationDate (long): %" APR_TIME_T_FMT, user->creationDate);
  PDEBUG("CreationDate (read): %s", timeReadable);

  apr_pool_destroy(p);
}

Hope you enjoyed it.