1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/security/nss/lib/softoken/legacydb/dbmshim.c Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,613 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +/* 1.9 + * Berkeley DB 1.85 Shim code to handle blobs. 1.10 + */ 1.11 +#include "mcom_db.h" 1.12 +#include "secitem.h" 1.13 +#include "nssb64.h" 1.14 +#include "blapi.h" 1.15 +#include "secerr.h" 1.16 + 1.17 +#include "lgdb.h" 1.18 + 1.19 +/* 1.20 + * Blob block: 1.21 + * Byte 0 CERTDB Version -+ -+ 1.22 + * Byte 1 certDBEntryTypeBlob | BLOB_HEAD_LEN | 1.23 + * Byte 2 flags (always '0'); | | 1.24 + * Byte 3 reserved (always '0'); -+ | 1.25 + * Byte 4 LSB length | <--BLOB_LENGTH_START | BLOB_BUF_LEN 1.26 + * Byte 5 . | | 1.27 + * Byte 6 . | BLOB_LENGTH_LEN | 1.28 + * Byte 7 MSB length | | 1.29 + * Byte 8 blob_filename -+ -+ <-- BLOB_NAME_START | 1.30 + * Byte 9 . | BLOB_NAME_LEN | 1.31 + * . . | | 1.32 + * Byte 37 . -+ -+ 1.33 + */ 1.34 +#define DBS_BLOCK_SIZE (16*1024) /* 16 k */ 1.35 +#define DBS_MAX_ENTRY_SIZE (DBS_BLOCK_SIZE - (2048)) /* 14 k */ 1.36 +#define DBS_CACHE_SIZE DBS_BLOCK_SIZE*8 1.37 +#define ROUNDDIV(x,y) (x+(y-1))/y 1.38 +#define BLOB_HEAD_LEN 4 1.39 +#define BLOB_LENGTH_START BLOB_HEAD_LEN 1.40 +#define BLOB_LENGTH_LEN 4 1.41 +#define BLOB_NAME_START BLOB_LENGTH_START+BLOB_LENGTH_LEN 1.42 +#define BLOB_NAME_LEN 1+ROUNDDIV(SHA1_LENGTH,3)*4+1 1.43 +#define BLOB_BUF_LEN BLOB_HEAD_LEN+BLOB_LENGTH_LEN+BLOB_NAME_LEN 1.44 + 1.45 +/* a Shim data structure. This data structure has a db built into it. */ 1.46 +typedef struct DBSStr DBS; 1.47 + 1.48 +struct DBSStr { 1.49 + DB db; 1.50 + char *blobdir; 1.51 + int mode; 1.52 + PRBool readOnly; 1.53 + PRFileMap *dbs_mapfile; 1.54 + unsigned char *dbs_addr; 1.55 + PRUint32 dbs_len; 1.56 + char staticBlobArea[BLOB_BUF_LEN]; 1.57 +}; 1.58 + 1.59 + 1.60 + 1.61 +/* 1.62 + * return true if the Datablock contains a blobtype 1.63 + */ 1.64 +static PRBool 1.65 +dbs_IsBlob(DBT *blobData) 1.66 +{ 1.67 + unsigned char *addr = (unsigned char *)blobData->data; 1.68 + if (blobData->size < BLOB_BUF_LEN) { 1.69 + return PR_FALSE; 1.70 + } 1.71 + return addr && ((certDBEntryType) addr[1] == certDBEntryTypeBlob); 1.72 +} 1.73 + 1.74 +/* 1.75 + * extract the filename in the blob of the real data set. 1.76 + * This value is not malloced (does not need to be freed by the caller. 1.77 + */ 1.78 +static const char * 1.79 +dbs_getBlobFileName(DBT *blobData) 1.80 +{ 1.81 + char *addr = (char *)blobData->data; 1.82 + 1.83 + return &addr[BLOB_NAME_START]; 1.84 +} 1.85 + 1.86 +/* 1.87 + * extract the size of the actual blob from the blob record 1.88 + */ 1.89 +static PRUint32 1.90 +dbs_getBlobSize(DBT *blobData) 1.91 +{ 1.92 + unsigned char *addr = (unsigned char *)blobData->data; 1.93 + 1.94 + return (PRUint32)(addr[BLOB_LENGTH_START+3] << 24) | 1.95 + (addr[BLOB_LENGTH_START+2] << 16) | 1.96 + (addr[BLOB_LENGTH_START+1] << 8) | 1.97 + addr[BLOB_LENGTH_START]; 1.98 +} 1.99 + 1.100 + 1.101 +/* We are using base64 data for the filename, but base64 data can include a 1.102 + * '/' which is interpreted as a path separator on many platforms. Replace it 1.103 + * with an inocuous '-'. We don't need to convert back because we never actual 1.104 + * decode the filename. 1.105 + */ 1.106 + 1.107 +static void 1.108 +dbs_replaceSlash(char *cp, int len) 1.109 +{ 1.110 + while (len--) { 1.111 + if (*cp == '/') *cp = '-'; 1.112 + cp++; 1.113 + } 1.114 +} 1.115 + 1.116 +/* 1.117 + * create a blob record from a key, data and return it in blobData. 1.118 + * NOTE: The data element is static data (keeping with the dbm model). 1.119 + */ 1.120 +static void 1.121 +dbs_mkBlob(DBS *dbsp,const DBT *key, const DBT *data, DBT *blobData) 1.122 +{ 1.123 + unsigned char sha1_data[SHA1_LENGTH]; 1.124 + char *b = dbsp->staticBlobArea; 1.125 + PRUint32 length = data->size; 1.126 + SECItem sha1Item; 1.127 + 1.128 + b[0] = CERT_DB_FILE_VERSION; /* certdb version number */ 1.129 + b[1] = (char) certDBEntryTypeBlob; /* type */ 1.130 + b[2] = 0; /* flags */ 1.131 + b[3] = 0; /* reserved */ 1.132 + b[BLOB_LENGTH_START] = length & 0xff; 1.133 + b[BLOB_LENGTH_START+1] = (length >> 8) & 0xff; 1.134 + b[BLOB_LENGTH_START+2] = (length >> 16) & 0xff; 1.135 + b[BLOB_LENGTH_START+3] = (length >> 24) & 0xff; 1.136 + sha1Item.data = sha1_data; 1.137 + sha1Item.len = SHA1_LENGTH; 1.138 + SHA1_HashBuf(sha1_data,key->data,key->size); 1.139 + b[BLOB_NAME_START]='b'; /* Make sure we start with a alpha */ 1.140 + NSSBase64_EncodeItem(NULL,&b[BLOB_NAME_START+1],BLOB_NAME_LEN-1,&sha1Item); 1.141 + b[BLOB_BUF_LEN-1] = 0; 1.142 + dbs_replaceSlash(&b[BLOB_NAME_START+1],BLOB_NAME_LEN-1); 1.143 + blobData->data = b; 1.144 + blobData->size = BLOB_BUF_LEN; 1.145 + return; 1.146 +} 1.147 + 1.148 + 1.149 +/* 1.150 + * construct a path to the actual blob. The string returned must be 1.151 + * freed by the caller with PR_smprintf_free. 1.152 + * 1.153 + * Note: this file does lots of consistancy checks on the DBT. The 1.154 + * routines that call this depend on these checks, so they don't worry 1.155 + * about them (success of this routine implies a good blobdata record). 1.156 + */ 1.157 +static char * 1.158 +dbs_getBlobFilePath(char *blobdir,DBT *blobData) 1.159 +{ 1.160 + const char *name; 1.161 + 1.162 + if (blobdir == NULL) { 1.163 + PR_SetError(SEC_ERROR_BAD_DATABASE,0); 1.164 + return NULL; 1.165 + } 1.166 + if (!dbs_IsBlob(blobData)) { 1.167 + PR_SetError(SEC_ERROR_BAD_DATABASE,0); 1.168 + return NULL; 1.169 + } 1.170 + name = dbs_getBlobFileName(blobData); 1.171 + if (!name || *name == 0) { 1.172 + PR_SetError(SEC_ERROR_BAD_DATABASE,0); 1.173 + return NULL; 1.174 + } 1.175 + return PR_smprintf("%s" PATH_SEPARATOR "%s", blobdir, name); 1.176 +} 1.177 + 1.178 +/* 1.179 + * Delete a blob file pointed to by the blob record. 1.180 + */ 1.181 +static void 1.182 +dbs_removeBlob(DBS *dbsp, DBT *blobData) 1.183 +{ 1.184 + char *file; 1.185 + 1.186 + file = dbs_getBlobFilePath(dbsp->blobdir, blobData); 1.187 + if (!file) { 1.188 + return; 1.189 + } 1.190 + PR_Delete(file); 1.191 + PR_smprintf_free(file); 1.192 +} 1.193 + 1.194 +/* 1.195 + * Directory modes are slightly different, the 'x' bit needs to be on to 1.196 + * access them. Copy all the read bits to 'x' bits 1.197 + */ 1.198 +static int 1.199 +dbs_DirMode(int mode) 1.200 +{ 1.201 + int x_bits = (mode >> 2) & 0111; 1.202 + return mode | x_bits; 1.203 +} 1.204 + 1.205 +/* 1.206 + * write a data blob to it's file. blobdData is the blob record that will be 1.207 + * stored in the database. data is the actual data to go out on disk. 1.208 + */ 1.209 +static int 1.210 +dbs_writeBlob(DBS *dbsp, int mode, DBT *blobData, const DBT *data) 1.211 +{ 1.212 + char *file = NULL; 1.213 + PRFileDesc *filed; 1.214 + PRStatus status; 1.215 + int len; 1.216 + int error = 0; 1.217 + 1.218 + file = dbs_getBlobFilePath(dbsp->blobdir, blobData); 1.219 + if (!file) { 1.220 + goto loser; 1.221 + } 1.222 + if (PR_Access(dbsp->blobdir, PR_ACCESS_EXISTS) != PR_SUCCESS) { 1.223 + status = PR_MkDir(dbsp->blobdir,dbs_DirMode(mode)); 1.224 + if (status != PR_SUCCESS) { 1.225 + goto loser; 1.226 + } 1.227 + } 1.228 + filed = PR_OpenFile(file,PR_CREATE_FILE|PR_TRUNCATE|PR_WRONLY, mode); 1.229 + if (filed == NULL) { 1.230 + error = PR_GetError(); 1.231 + goto loser; 1.232 + } 1.233 + len = PR_Write(filed,data->data,data->size); 1.234 + error = PR_GetError(); 1.235 + PR_Close(filed); 1.236 + if (len < (int)data->size) { 1.237 + goto loser; 1.238 + } 1.239 + PR_smprintf_free(file); 1.240 + return 0; 1.241 + 1.242 +loser: 1.243 + if (file) { 1.244 + PR_Delete(file); 1.245 + PR_smprintf_free(file); 1.246 + } 1.247 + /* don't let close or delete reset the error */ 1.248 + PR_SetError(error,0); 1.249 + return -1; 1.250 +} 1.251 + 1.252 + 1.253 +/* 1.254 + * we need to keep a address map in memory between calls to DBM. 1.255 + * remember what we have mapped can close it when we get another dbm 1.256 + * call. 1.257 + * 1.258 + * NOTE: Not all platforms support mapped files. This code is designed to 1.259 + * detect this at runtime. If map files aren't supported the OS will indicate 1.260 + * this by failing the PR_Memmap call. In this case we emulate mapped files 1.261 + * by just reading in the file into regular memory. We signal this state by 1.262 + * making dbs_mapfile NULL and dbs_addr non-NULL. 1.263 + */ 1.264 + 1.265 +static void 1.266 +dbs_freemap(DBS *dbsp) 1.267 +{ 1.268 + if (dbsp->dbs_mapfile) { 1.269 + PR_MemUnmap(dbsp->dbs_addr,dbsp->dbs_len); 1.270 + PR_CloseFileMap(dbsp->dbs_mapfile); 1.271 + dbsp->dbs_mapfile = NULL; 1.272 + dbsp->dbs_addr = NULL; 1.273 + dbsp->dbs_len = 0; 1.274 + } else if (dbsp->dbs_addr) { 1.275 + PORT_Free(dbsp->dbs_addr); 1.276 + dbsp->dbs_addr = NULL; 1.277 + dbsp->dbs_len = 0; 1.278 + } 1.279 + return; 1.280 +} 1.281 + 1.282 +static void 1.283 +dbs_setmap(DBS *dbsp, PRFileMap *mapfile, unsigned char *addr, PRUint32 len) 1.284 +{ 1.285 + dbsp->dbs_mapfile = mapfile; 1.286 + dbsp->dbs_addr = addr; 1.287 + dbsp->dbs_len = len; 1.288 +} 1.289 + 1.290 +/* 1.291 + * platforms that cannot map the file need to read it into a temp buffer. 1.292 + */ 1.293 +static unsigned char * 1.294 +dbs_EmulateMap(PRFileDesc *filed, int len) 1.295 +{ 1.296 + unsigned char *addr; 1.297 + PRInt32 dataRead; 1.298 + 1.299 + addr = PORT_Alloc(len); 1.300 + if (addr == NULL) { 1.301 + return NULL; 1.302 + } 1.303 + 1.304 + dataRead = PR_Read(filed,addr,len); 1.305 + if (dataRead != len) { 1.306 + PORT_Free(addr); 1.307 + if (dataRead > 0) { 1.308 + /* PR_Read didn't set an error, we need to */ 1.309 + PR_SetError(SEC_ERROR_BAD_DATABASE,0); 1.310 + } 1.311 + return NULL; 1.312 + } 1.313 + 1.314 + return addr; 1.315 +} 1.316 + 1.317 + 1.318 +/* 1.319 + * pull a database record off the disk 1.320 + * data points to the blob record on input and the real record (if we could 1.321 + * read it) on output. if there is an error data is not modified. 1.322 + */ 1.323 +static int 1.324 +dbs_readBlob(DBS *dbsp, DBT *data) 1.325 +{ 1.326 + char *file = NULL; 1.327 + PRFileDesc *filed = NULL; 1.328 + PRFileMap *mapfile = NULL; 1.329 + unsigned char *addr = NULL; 1.330 + int error; 1.331 + int len = -1; 1.332 + 1.333 + file = dbs_getBlobFilePath(dbsp->blobdir, data); 1.334 + if (!file) { 1.335 + goto loser; 1.336 + } 1.337 + filed = PR_OpenFile(file,PR_RDONLY,0); 1.338 + PR_smprintf_free(file); file = NULL; 1.339 + if (filed == NULL) { 1.340 + goto loser; 1.341 + } 1.342 + 1.343 + len = dbs_getBlobSize(data); 1.344 + mapfile = PR_CreateFileMap(filed, len, PR_PROT_READONLY); 1.345 + if (mapfile == NULL) { 1.346 + /* USE PR_GetError instead of PORT_GetError here 1.347 + * because we are getting the error from PR_xxx 1.348 + * function */ 1.349 + if (PR_GetError() != PR_NOT_IMPLEMENTED_ERROR) { 1.350 + goto loser; 1.351 + } 1.352 + addr = dbs_EmulateMap(filed, len); 1.353 + } else { 1.354 + addr = PR_MemMap(mapfile, 0, len); 1.355 + } 1.356 + if (addr == NULL) { 1.357 + goto loser; 1.358 + } 1.359 + PR_Close(filed); 1.360 + dbs_setmap(dbsp,mapfile,addr,len); 1.361 + 1.362 + data->data = addr; 1.363 + data->size = len; 1.364 + return 0; 1.365 + 1.366 +loser: 1.367 + /* preserve the error code */ 1.368 + error = PR_GetError(); 1.369 + if (mapfile) { 1.370 + PR_CloseFileMap(mapfile); 1.371 + } 1.372 + if (filed) { 1.373 + PR_Close(filed); 1.374 + } 1.375 + PR_SetError(error,0); 1.376 + return -1; 1.377 +} 1.378 + 1.379 +/* 1.380 + * actual DBM shims 1.381 + */ 1.382 +static int 1.383 +dbs_get(const DB *dbs, const DBT *key, DBT *data, unsigned int flags) 1.384 +{ 1.385 + int ret; 1.386 + DBS *dbsp = (DBS *)dbs; 1.387 + DB *db = (DB *)dbs->internal; 1.388 + 1.389 + 1.390 + dbs_freemap(dbsp); 1.391 + 1.392 + ret = (* db->get)(db, key, data, flags); 1.393 + if ((ret == 0) && dbs_IsBlob(data)) { 1.394 + ret = dbs_readBlob(dbsp,data); 1.395 + } 1.396 + 1.397 + return(ret); 1.398 +} 1.399 + 1.400 +static int 1.401 +dbs_put(const DB *dbs, DBT *key, const DBT *data, unsigned int flags) 1.402 +{ 1.403 + DBT blob; 1.404 + int ret = 0; 1.405 + DBS *dbsp = (DBS *)dbs; 1.406 + DB *db = (DB *)dbs->internal; 1.407 + 1.408 + dbs_freemap(dbsp); 1.409 + 1.410 + /* If the db is readonly, just pass the data down to rdb and let it fail */ 1.411 + if (!dbsp->readOnly) { 1.412 + DBT oldData; 1.413 + int ret1; 1.414 + 1.415 + /* make sure the current record is deleted if it's a blob */ 1.416 + ret1 = (*db->get)(db,key,&oldData,0); 1.417 + if ((ret1 == 0) && flags == R_NOOVERWRITE) { 1.418 + /* let DBM return the error to maintain consistancy */ 1.419 + return (* db->put)(db, key, data, flags); 1.420 + } 1.421 + if ((ret1 == 0) && dbs_IsBlob(&oldData)) { 1.422 + dbs_removeBlob(dbsp, &oldData); 1.423 + } 1.424 + 1.425 + if (data->size > DBS_MAX_ENTRY_SIZE) { 1.426 + dbs_mkBlob(dbsp,key,data,&blob); 1.427 + ret = dbs_writeBlob(dbsp, dbsp->mode, &blob, data); 1.428 + data = &blob; 1.429 + } 1.430 + } 1.431 + 1.432 + if (ret == 0) { 1.433 + ret = (* db->put)(db, key, data, flags); 1.434 + } 1.435 + return(ret); 1.436 +} 1.437 + 1.438 +static int 1.439 +dbs_sync(const DB *dbs, unsigned int flags) 1.440 +{ 1.441 + DB *db = (DB *)dbs->internal; 1.442 + DBS *dbsp = (DBS *)dbs; 1.443 + 1.444 + dbs_freemap(dbsp); 1.445 + 1.446 + return (* db->sync)(db, flags); 1.447 +} 1.448 + 1.449 +static int 1.450 +dbs_del(const DB *dbs, const DBT *key, unsigned int flags) 1.451 +{ 1.452 + int ret; 1.453 + DBS *dbsp = (DBS *)dbs; 1.454 + DB *db = (DB *)dbs->internal; 1.455 + 1.456 + dbs_freemap(dbsp); 1.457 + 1.458 + if (!dbsp->readOnly) { 1.459 + DBT oldData; 1.460 + ret = (*db->get)(db,key,&oldData,0); 1.461 + if ((ret == 0) && dbs_IsBlob(&oldData)) { 1.462 + dbs_removeBlob(dbsp,&oldData); 1.463 + } 1.464 + } 1.465 + 1.466 + return (* db->del)(db, key, flags); 1.467 +} 1.468 + 1.469 +static int 1.470 +dbs_seq(const DB *dbs, DBT *key, DBT *data, unsigned int flags) 1.471 +{ 1.472 + int ret; 1.473 + DBS *dbsp = (DBS *)dbs; 1.474 + DB *db = (DB *)dbs->internal; 1.475 + 1.476 + dbs_freemap(dbsp); 1.477 + 1.478 + ret = (* db->seq)(db, key, data, flags); 1.479 + if ((ret == 0) && dbs_IsBlob(data)) { 1.480 + /* don't return a blob read as an error so traversals keep going */ 1.481 + (void) dbs_readBlob(dbsp,data); 1.482 + } 1.483 + 1.484 + return(ret); 1.485 +} 1.486 + 1.487 +static int 1.488 +dbs_close(DB *dbs) 1.489 +{ 1.490 + DBS *dbsp = (DBS *)dbs; 1.491 + DB *db = (DB *)dbs->internal; 1.492 + int ret; 1.493 + 1.494 + dbs_freemap(dbsp); 1.495 + ret = (* db->close)(db); 1.496 + PORT_Free(dbsp->blobdir); 1.497 + PORT_Free(dbsp); 1.498 + return ret; 1.499 +} 1.500 + 1.501 +static int 1.502 +dbs_fd(const DB *dbs) 1.503 +{ 1.504 + DB *db = (DB *)dbs->internal; 1.505 + 1.506 + return (* db->fd)(db); 1.507 +} 1.508 + 1.509 +/* 1.510 + * the naming convention we use is 1.511 + * change the .xxx into .dir. (for nss it's always .db); 1.512 + * if no .extension exists or is equal to .dir, add a .dir 1.513 + * the returned data must be freed. 1.514 + */ 1.515 +#define DIRSUFFIX ".dir" 1.516 +static char * 1.517 +dbs_mkBlobDirName(const char *dbname) 1.518 +{ 1.519 + int dbname_len = PORT_Strlen(dbname); 1.520 + int dbname_end = dbname_len; 1.521 + const char *cp; 1.522 + char *blobDir = NULL; 1.523 + 1.524 + /* scan back from the end looking for either a directory separator, a '.', 1.525 + * or the end of the string. NOTE: Windows should check for both separators 1.526 + * here. For now this is safe because we know NSS always uses a '.' 1.527 + */ 1.528 + for (cp = &dbname[dbname_len]; 1.529 + (cp > dbname) && (*cp != '.') && (*cp != *PATH_SEPARATOR) ; 1.530 + cp--) 1.531 + /* Empty */ ; 1.532 + if (*cp == '.') { 1.533 + dbname_end = cp - dbname; 1.534 + if (PORT_Strcmp(cp,DIRSUFFIX) == 0) { 1.535 + dbname_end = dbname_len; 1.536 + } 1.537 + } 1.538 + blobDir = PORT_ZAlloc(dbname_end+sizeof(DIRSUFFIX)); 1.539 + if (blobDir == NULL) { 1.540 + return NULL; 1.541 + } 1.542 + PORT_Memcpy(blobDir,dbname,dbname_end); 1.543 + PORT_Memcpy(&blobDir[dbname_end],DIRSUFFIX,sizeof(DIRSUFFIX)); 1.544 + return blobDir; 1.545 +} 1.546 + 1.547 +#define DBM_DEFAULT 0 1.548 +static const HASHINFO dbs_hashInfo = { 1.549 + DBS_BLOCK_SIZE, /* bucket size, must be greater than = to 1.550 + * or maximum entry size (+ header) 1.551 + * we allow before blobing */ 1.552 + DBM_DEFAULT, /* Fill Factor */ 1.553 + DBM_DEFAULT, /* number of elements */ 1.554 + DBS_CACHE_SIZE, /* cache size */ 1.555 + DBM_DEFAULT, /* hash function */ 1.556 + DBM_DEFAULT, /* byte order */ 1.557 +}; 1.558 + 1.559 +/* 1.560 + * the open function. NOTE: this is the only exposed function in this file. 1.561 + * everything else is called through the function table pointer. 1.562 + */ 1.563 +DB * 1.564 +dbsopen(const char *dbname, int flags, int mode, DBTYPE type, 1.565 + const void *userData) 1.566 +{ 1.567 + DB *db = NULL,*dbs = NULL; 1.568 + DBS *dbsp = NULL; 1.569 + 1.570 + /* NOTE: we are overriding userData with dbs_hashInfo. since all known 1.571 + * callers pass 0, this is ok, otherwise we should merge the two */ 1.572 + 1.573 + dbsp = (DBS *)PORT_ZAlloc(sizeof(DBS)); 1.574 + if (!dbsp) { 1.575 + return NULL; 1.576 + } 1.577 + dbs = &dbsp->db; 1.578 + 1.579 + dbsp->blobdir=dbs_mkBlobDirName(dbname); 1.580 + if (dbsp->blobdir == NULL) { 1.581 + goto loser; 1.582 + } 1.583 + dbsp->mode = mode; 1.584 + dbsp->readOnly = (PRBool)(flags == NO_RDONLY); 1.585 + dbsp->dbs_mapfile = NULL; 1.586 + dbsp->dbs_addr = NULL; 1.587 + dbsp->dbs_len = 0; 1.588 + 1.589 + /* the real dbm call */ 1.590 + db = dbopen(dbname, flags, mode, type, &dbs_hashInfo); 1.591 + if (db == NULL) { 1.592 + goto loser; 1.593 + } 1.594 + dbs->internal = (void *) db; 1.595 + dbs->type = type; 1.596 + dbs->close = dbs_close; 1.597 + dbs->get = dbs_get; 1.598 + dbs->del = dbs_del; 1.599 + dbs->put = dbs_put; 1.600 + dbs->seq = dbs_seq; 1.601 + dbs->sync = dbs_sync; 1.602 + dbs->fd = dbs_fd; 1.603 + 1.604 + return dbs; 1.605 +loser: 1.606 + if (db) { 1.607 + (*db->close)(db); 1.608 + } 1.609 + if (dbsp) { 1.610 + if (dbsp->blobdir) { 1.611 + PORT_Free(dbsp->blobdir); 1.612 + } 1.613 + PORT_Free(dbsp); 1.614 + } 1.615 + return NULL; 1.616 +}