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