comparison nss/lib/softoken/legacydb/dbmshim.c @ 3:150b72113545

Add DBM and legacydb support
author Andre Heinecke <andre.heinecke@intevation.de>
date Tue, 05 Aug 2014 18:32:02 +0200
parents
children
comparison
equal deleted inserted replaced
2:a945361df361 3:150b72113545
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 /*
6 * Berkeley DB 1.85 Shim code to handle blobs.
7 */
8 #include "mcom_db.h"
9 #include "secitem.h"
10 #include "nssb64.h"
11 #include "blapi.h"
12 #include "secerr.h"
13
14 #include "lgdb.h"
15
16 /*
17 * Blob block:
18 * Byte 0 CERTDB Version -+ -+
19 * Byte 1 certDBEntryTypeBlob | BLOB_HEAD_LEN |
20 * Byte 2 flags (always '0'); | |
21 * Byte 3 reserved (always '0'); -+ |
22 * Byte 4 LSB length | <--BLOB_LENGTH_START | BLOB_BUF_LEN
23 * Byte 5 . | |
24 * Byte 6 . | BLOB_LENGTH_LEN |
25 * Byte 7 MSB length | |
26 * Byte 8 blob_filename -+ -+ <-- BLOB_NAME_START |
27 * Byte 9 . | BLOB_NAME_LEN |
28 * . . | |
29 * Byte 37 . -+ -+
30 */
31 #define DBS_BLOCK_SIZE (16*1024) /* 16 k */
32 #define DBS_MAX_ENTRY_SIZE (DBS_BLOCK_SIZE - (2048)) /* 14 k */
33 #define DBS_CACHE_SIZE DBS_BLOCK_SIZE*8
34 #define ROUNDDIV(x,y) (x+(y-1))/y
35 #define BLOB_HEAD_LEN 4
36 #define BLOB_LENGTH_START BLOB_HEAD_LEN
37 #define BLOB_LENGTH_LEN 4
38 #define BLOB_NAME_START BLOB_LENGTH_START+BLOB_LENGTH_LEN
39 #define BLOB_NAME_LEN 1+ROUNDDIV(SHA1_LENGTH,3)*4+1
40 #define BLOB_BUF_LEN BLOB_HEAD_LEN+BLOB_LENGTH_LEN+BLOB_NAME_LEN
41
42 /* a Shim data structure. This data structure has a db built into it. */
43 typedef struct DBSStr DBS;
44
45 struct DBSStr {
46 DB db;
47 char *blobdir;
48 int mode;
49 PRBool readOnly;
50 PRFileMap *dbs_mapfile;
51 unsigned char *dbs_addr;
52 PRUint32 dbs_len;
53 char staticBlobArea[BLOB_BUF_LEN];
54 };
55
56
57
58 /*
59 * return true if the Datablock contains a blobtype
60 */
61 static PRBool
62 dbs_IsBlob(DBT *blobData)
63 {
64 unsigned char *addr = (unsigned char *)blobData->data;
65 if (blobData->size < BLOB_BUF_LEN) {
66 return PR_FALSE;
67 }
68 return addr && ((certDBEntryType) addr[1] == certDBEntryTypeBlob);
69 }
70
71 /*
72 * extract the filename in the blob of the real data set.
73 * This value is not malloced (does not need to be freed by the caller.
74 */
75 static const char *
76 dbs_getBlobFileName(DBT *blobData)
77 {
78 char *addr = (char *)blobData->data;
79
80 return &addr[BLOB_NAME_START];
81 }
82
83 /*
84 * extract the size of the actual blob from the blob record
85 */
86 static PRUint32
87 dbs_getBlobSize(DBT *blobData)
88 {
89 unsigned char *addr = (unsigned char *)blobData->data;
90
91 return (PRUint32)(addr[BLOB_LENGTH_START+3] << 24) |
92 (addr[BLOB_LENGTH_START+2] << 16) |
93 (addr[BLOB_LENGTH_START+1] << 8) |
94 addr[BLOB_LENGTH_START];
95 }
96
97
98 /* We are using base64 data for the filename, but base64 data can include a
99 * '/' which is interpreted as a path separator on many platforms. Replace it
100 * with an inocuous '-'. We don't need to convert back because we never actual
101 * decode the filename.
102 */
103
104 static void
105 dbs_replaceSlash(char *cp, int len)
106 {
107 while (len--) {
108 if (*cp == '/') *cp = '-';
109 cp++;
110 }
111 }
112
113 /*
114 * create a blob record from a key, data and return it in blobData.
115 * NOTE: The data element is static data (keeping with the dbm model).
116 */
117 static void
118 dbs_mkBlob(DBS *dbsp,const DBT *key, const DBT *data, DBT *blobData)
119 {
120 unsigned char sha1_data[SHA1_LENGTH];
121 char *b = dbsp->staticBlobArea;
122 PRUint32 length = data->size;
123 SECItem sha1Item;
124
125 b[0] = CERT_DB_FILE_VERSION; /* certdb version number */
126 b[1] = (char) certDBEntryTypeBlob; /* type */
127 b[2] = 0; /* flags */
128 b[3] = 0; /* reserved */
129 b[BLOB_LENGTH_START] = length & 0xff;
130 b[BLOB_LENGTH_START+1] = (length >> 8) & 0xff;
131 b[BLOB_LENGTH_START+2] = (length >> 16) & 0xff;
132 b[BLOB_LENGTH_START+3] = (length >> 24) & 0xff;
133 sha1Item.data = sha1_data;
134 sha1Item.len = SHA1_LENGTH;
135 SHA1_HashBuf(sha1_data,key->data,key->size);
136 b[BLOB_NAME_START]='b'; /* Make sure we start with a alpha */
137 NSSBase64_EncodeItem(NULL,&b[BLOB_NAME_START+1],BLOB_NAME_LEN-1,&sha1Item);
138 b[BLOB_BUF_LEN-1] = 0;
139 dbs_replaceSlash(&b[BLOB_NAME_START+1],BLOB_NAME_LEN-1);
140 blobData->data = b;
141 blobData->size = BLOB_BUF_LEN;
142 return;
143 }
144
145
146 /*
147 * construct a path to the actual blob. The string returned must be
148 * freed by the caller with PR_smprintf_free.
149 *
150 * Note: this file does lots of consistancy checks on the DBT. The
151 * routines that call this depend on these checks, so they don't worry
152 * about them (success of this routine implies a good blobdata record).
153 */
154 static char *
155 dbs_getBlobFilePath(char *blobdir,DBT *blobData)
156 {
157 const char *name;
158
159 if (blobdir == NULL) {
160 PR_SetError(SEC_ERROR_BAD_DATABASE,0);
161 return NULL;
162 }
163 if (!dbs_IsBlob(blobData)) {
164 PR_SetError(SEC_ERROR_BAD_DATABASE,0);
165 return NULL;
166 }
167 name = dbs_getBlobFileName(blobData);
168 if (!name || *name == 0) {
169 PR_SetError(SEC_ERROR_BAD_DATABASE,0);
170 return NULL;
171 }
172 return PR_smprintf("%s" PATH_SEPARATOR "%s", blobdir, name);
173 }
174
175 /*
176 * Delete a blob file pointed to by the blob record.
177 */
178 static void
179 dbs_removeBlob(DBS *dbsp, DBT *blobData)
180 {
181 char *file;
182
183 file = dbs_getBlobFilePath(dbsp->blobdir, blobData);
184 if (!file) {
185 return;
186 }
187 PR_Delete(file);
188 PR_smprintf_free(file);
189 }
190
191 /*
192 * Directory modes are slightly different, the 'x' bit needs to be on to
193 * access them. Copy all the read bits to 'x' bits
194 */
195 static int
196 dbs_DirMode(int mode)
197 {
198 int x_bits = (mode >> 2) & 0111;
199 return mode | x_bits;
200 }
201
202 /*
203 * write a data blob to it's file. blobdData is the blob record that will be
204 * stored in the database. data is the actual data to go out on disk.
205 */
206 static int
207 dbs_writeBlob(DBS *dbsp, int mode, DBT *blobData, const DBT *data)
208 {
209 char *file = NULL;
210 PRFileDesc *filed;
211 PRStatus status;
212 int len;
213 int error = 0;
214
215 file = dbs_getBlobFilePath(dbsp->blobdir, blobData);
216 if (!file) {
217 goto loser;
218 }
219 if (PR_Access(dbsp->blobdir, PR_ACCESS_EXISTS) != PR_SUCCESS) {
220 status = PR_MkDir(dbsp->blobdir,dbs_DirMode(mode));
221 if (status != PR_SUCCESS) {
222 goto loser;
223 }
224 }
225 filed = PR_OpenFile(file,PR_CREATE_FILE|PR_TRUNCATE|PR_WRONLY, mode);
226 if (filed == NULL) {
227 error = PR_GetError();
228 goto loser;
229 }
230 len = PR_Write(filed,data->data,data->size);
231 error = PR_GetError();
232 PR_Close(filed);
233 if (len < (int)data->size) {
234 goto loser;
235 }
236 PR_smprintf_free(file);
237 return 0;
238
239 loser:
240 if (file) {
241 PR_Delete(file);
242 PR_smprintf_free(file);
243 }
244 /* don't let close or delete reset the error */
245 PR_SetError(error,0);
246 return -1;
247 }
248
249
250 /*
251 * we need to keep a address map in memory between calls to DBM.
252 * remember what we have mapped can close it when we get another dbm
253 * call.
254 *
255 * NOTE: Not all platforms support mapped files. This code is designed to
256 * detect this at runtime. If map files aren't supported the OS will indicate
257 * this by failing the PR_Memmap call. In this case we emulate mapped files
258 * by just reading in the file into regular memory. We signal this state by
259 * making dbs_mapfile NULL and dbs_addr non-NULL.
260 */
261
262 static void
263 dbs_freemap(DBS *dbsp)
264 {
265 if (dbsp->dbs_mapfile) {
266 PR_MemUnmap(dbsp->dbs_addr,dbsp->dbs_len);
267 PR_CloseFileMap(dbsp->dbs_mapfile);
268 dbsp->dbs_mapfile = NULL;
269 dbsp->dbs_addr = NULL;
270 dbsp->dbs_len = 0;
271 } else if (dbsp->dbs_addr) {
272 PORT_Free(dbsp->dbs_addr);
273 dbsp->dbs_addr = NULL;
274 dbsp->dbs_len = 0;
275 }
276 return;
277 }
278
279 static void
280 dbs_setmap(DBS *dbsp, PRFileMap *mapfile, unsigned char *addr, PRUint32 len)
281 {
282 dbsp->dbs_mapfile = mapfile;
283 dbsp->dbs_addr = addr;
284 dbsp->dbs_len = len;
285 }
286
287 /*
288 * platforms that cannot map the file need to read it into a temp buffer.
289 */
290 static unsigned char *
291 dbs_EmulateMap(PRFileDesc *filed, int len)
292 {
293 unsigned char *addr;
294 PRInt32 dataRead;
295
296 addr = PORT_Alloc(len);
297 if (addr == NULL) {
298 return NULL;
299 }
300
301 dataRead = PR_Read(filed,addr,len);
302 if (dataRead != len) {
303 PORT_Free(addr);
304 if (dataRead > 0) {
305 /* PR_Read didn't set an error, we need to */
306 PR_SetError(SEC_ERROR_BAD_DATABASE,0);
307 }
308 return NULL;
309 }
310
311 return addr;
312 }
313
314
315 /*
316 * pull a database record off the disk
317 * data points to the blob record on input and the real record (if we could
318 * read it) on output. if there is an error data is not modified.
319 */
320 static int
321 dbs_readBlob(DBS *dbsp, DBT *data)
322 {
323 char *file = NULL;
324 PRFileDesc *filed = NULL;
325 PRFileMap *mapfile = NULL;
326 unsigned char *addr = NULL;
327 int error;
328 int len = -1;
329
330 file = dbs_getBlobFilePath(dbsp->blobdir, data);
331 if (!file) {
332 goto loser;
333 }
334 filed = PR_OpenFile(file,PR_RDONLY,0);
335 PR_smprintf_free(file); file = NULL;
336 if (filed == NULL) {
337 goto loser;
338 }
339
340 len = dbs_getBlobSize(data);
341 mapfile = PR_CreateFileMap(filed, len, PR_PROT_READONLY);
342 if (mapfile == NULL) {
343 /* USE PR_GetError instead of PORT_GetError here
344 * because we are getting the error from PR_xxx
345 * function */
346 if (PR_GetError() != PR_NOT_IMPLEMENTED_ERROR) {
347 goto loser;
348 }
349 addr = dbs_EmulateMap(filed, len);
350 } else {
351 addr = PR_MemMap(mapfile, 0, len);
352 }
353 if (addr == NULL) {
354 goto loser;
355 }
356 PR_Close(filed);
357 dbs_setmap(dbsp,mapfile,addr,len);
358
359 data->data = addr;
360 data->size = len;
361 return 0;
362
363 loser:
364 /* preserve the error code */
365 error = PR_GetError();
366 if (mapfile) {
367 PR_CloseFileMap(mapfile);
368 }
369 if (filed) {
370 PR_Close(filed);
371 }
372 PR_SetError(error,0);
373 return -1;
374 }
375
376 /*
377 * actual DBM shims
378 */
379 static int
380 dbs_get(const DB *dbs, const DBT *key, DBT *data, unsigned int flags)
381 {
382 int ret;
383 DBS *dbsp = (DBS *)dbs;
384 DB *db = (DB *)dbs->internal;
385
386
387 dbs_freemap(dbsp);
388
389 ret = (* db->get)(db, key, data, flags);
390 if ((ret == 0) && dbs_IsBlob(data)) {
391 ret = dbs_readBlob(dbsp,data);
392 }
393
394 return(ret);
395 }
396
397 static int
398 dbs_put(const DB *dbs, DBT *key, const DBT *data, unsigned int flags)
399 {
400 DBT blob;
401 int ret = 0;
402 DBS *dbsp = (DBS *)dbs;
403 DB *db = (DB *)dbs->internal;
404
405 dbs_freemap(dbsp);
406
407 /* If the db is readonly, just pass the data down to rdb and let it fail */
408 if (!dbsp->readOnly) {
409 DBT oldData;
410 int ret1;
411
412 /* make sure the current record is deleted if it's a blob */
413 ret1 = (*db->get)(db,key,&oldData,0);
414 if ((ret1 == 0) && flags == R_NOOVERWRITE) {
415 /* let DBM return the error to maintain consistancy */
416 return (* db->put)(db, key, data, flags);
417 }
418 if ((ret1 == 0) && dbs_IsBlob(&oldData)) {
419 dbs_removeBlob(dbsp, &oldData);
420 }
421
422 if (data->size > DBS_MAX_ENTRY_SIZE) {
423 dbs_mkBlob(dbsp,key,data,&blob);
424 ret = dbs_writeBlob(dbsp, dbsp->mode, &blob, data);
425 data = &blob;
426 }
427 }
428
429 if (ret == 0) {
430 ret = (* db->put)(db, key, data, flags);
431 }
432 return(ret);
433 }
434
435 static int
436 dbs_sync(const DB *dbs, unsigned int flags)
437 {
438 DB *db = (DB *)dbs->internal;
439 DBS *dbsp = (DBS *)dbs;
440
441 dbs_freemap(dbsp);
442
443 return (* db->sync)(db, flags);
444 }
445
446 static int
447 dbs_del(const DB *dbs, const DBT *key, unsigned int flags)
448 {
449 int ret;
450 DBS *dbsp = (DBS *)dbs;
451 DB *db = (DB *)dbs->internal;
452
453 dbs_freemap(dbsp);
454
455 if (!dbsp->readOnly) {
456 DBT oldData;
457 ret = (*db->get)(db,key,&oldData,0);
458 if ((ret == 0) && dbs_IsBlob(&oldData)) {
459 dbs_removeBlob(dbsp,&oldData);
460 }
461 }
462
463 return (* db->del)(db, key, flags);
464 }
465
466 static int
467 dbs_seq(const DB *dbs, DBT *key, DBT *data, unsigned int flags)
468 {
469 int ret;
470 DBS *dbsp = (DBS *)dbs;
471 DB *db = (DB *)dbs->internal;
472
473 dbs_freemap(dbsp);
474
475 ret = (* db->seq)(db, key, data, flags);
476 if ((ret == 0) && dbs_IsBlob(data)) {
477 /* don't return a blob read as an error so traversals keep going */
478 (void) dbs_readBlob(dbsp,data);
479 }
480
481 return(ret);
482 }
483
484 static int
485 dbs_close(DB *dbs)
486 {
487 DBS *dbsp = (DBS *)dbs;
488 DB *db = (DB *)dbs->internal;
489 int ret;
490
491 dbs_freemap(dbsp);
492 ret = (* db->close)(db);
493 PORT_Free(dbsp->blobdir);
494 PORT_Free(dbsp);
495 return ret;
496 }
497
498 static int
499 dbs_fd(const DB *dbs)
500 {
501 DB *db = (DB *)dbs->internal;
502
503 return (* db->fd)(db);
504 }
505
506 /*
507 * the naming convention we use is
508 * change the .xxx into .dir. (for nss it's always .db);
509 * if no .extension exists or is equal to .dir, add a .dir
510 * the returned data must be freed.
511 */
512 #define DIRSUFFIX ".dir"
513 static char *
514 dbs_mkBlobDirName(const char *dbname)
515 {
516 int dbname_len = PORT_Strlen(dbname);
517 int dbname_end = dbname_len;
518 const char *cp;
519 char *blobDir = NULL;
520
521 /* scan back from the end looking for either a directory separator, a '.',
522 * or the end of the string. NOTE: Windows should check for both separators
523 * here. For now this is safe because we know NSS always uses a '.'
524 */
525 for (cp = &dbname[dbname_len];
526 (cp > dbname) && (*cp != '.') && (*cp != *PATH_SEPARATOR) ;
527 cp--)
528 /* Empty */ ;
529 if (*cp == '.') {
530 dbname_end = cp - dbname;
531 if (PORT_Strcmp(cp,DIRSUFFIX) == 0) {
532 dbname_end = dbname_len;
533 }
534 }
535 blobDir = PORT_ZAlloc(dbname_end+sizeof(DIRSUFFIX));
536 if (blobDir == NULL) {
537 return NULL;
538 }
539 PORT_Memcpy(blobDir,dbname,dbname_end);
540 PORT_Memcpy(&blobDir[dbname_end],DIRSUFFIX,sizeof(DIRSUFFIX));
541 return blobDir;
542 }
543
544 #define DBM_DEFAULT 0
545 static const HASHINFO dbs_hashInfo = {
546 DBS_BLOCK_SIZE, /* bucket size, must be greater than = to
547 * or maximum entry size (+ header)
548 * we allow before blobing */
549 DBM_DEFAULT, /* Fill Factor */
550 DBM_DEFAULT, /* number of elements */
551 DBS_CACHE_SIZE, /* cache size */
552 DBM_DEFAULT, /* hash function */
553 DBM_DEFAULT, /* byte order */
554 };
555
556 /*
557 * the open function. NOTE: this is the only exposed function in this file.
558 * everything else is called through the function table pointer.
559 */
560 DB *
561 dbsopen(const char *dbname, int flags, int mode, DBTYPE type,
562 const void *userData)
563 {
564 DB *db = NULL,*dbs = NULL;
565 DBS *dbsp = NULL;
566
567 /* NOTE: we are overriding userData with dbs_hashInfo. since all known
568 * callers pass 0, this is ok, otherwise we should merge the two */
569
570 dbsp = (DBS *)PORT_ZAlloc(sizeof(DBS));
571 if (!dbsp) {
572 return NULL;
573 }
574 dbs = &dbsp->db;
575
576 dbsp->blobdir=dbs_mkBlobDirName(dbname);
577 if (dbsp->blobdir == NULL) {
578 goto loser;
579 }
580 dbsp->mode = mode;
581 dbsp->readOnly = (PRBool)(flags == NO_RDONLY);
582 dbsp->dbs_mapfile = NULL;
583 dbsp->dbs_addr = NULL;
584 dbsp->dbs_len = 0;
585
586 /* the real dbm call */
587 db = dbopen(dbname, flags, mode, type, &dbs_hashInfo);
588 if (db == NULL) {
589 goto loser;
590 }
591 dbs->internal = (void *) db;
592 dbs->type = type;
593 dbs->close = dbs_close;
594 dbs->get = dbs_get;
595 dbs->del = dbs_del;
596 dbs->put = dbs_put;
597 dbs->seq = dbs_seq;
598 dbs->sync = dbs_sync;
599 dbs->fd = dbs_fd;
600
601 return dbs;
602 loser:
603 if (db) {
604 (*db->close)(db);
605 }
606 if (dbsp) {
607 if (dbsp->blobdir) {
608 PORT_Free(dbsp->blobdir);
609 }
610 PORT_Free(dbsp);
611 }
612 return NULL;
613 }
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)