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 file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5"use strict"; 6 7var EXPORTED_SYMBOLS = ["ESEDBReader"]; /* exported ESEDBReader */ 8 9ChromeUtils.import("resource://gre/modules/ctypes.jsm"); 10ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); 11ChromeUtils.import("resource://gre/modules/Services.jsm"); 12XPCOMUtils.defineLazyGetter(this, "log", () => { 13 let ConsoleAPI = ChromeUtils.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI; 14 let consoleOptions = { 15 maxLogLevelPref: "browser.esedbreader.loglevel", 16 prefix: "ESEDBReader", 17 }; 18 return new ConsoleAPI(consoleOptions); 19}); 20 21ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); 22 23// We have a globally unique identifier for ESE instances. A new one 24// is used for each different database opened. 25let gESEInstanceCounter = 0; 26 27// We limit the length of strings that we read from databases. 28const MAX_STR_LENGTH = 64 * 1024; 29 30// Kernel-related types: 31const KERNEL = {}; 32KERNEL.FILETIME = new ctypes.StructType("FILETIME", [ 33 {dwLowDateTime: ctypes.uint32_t}, 34 {dwHighDateTime: ctypes.uint32_t}, 35]); 36KERNEL.SYSTEMTIME = new ctypes.StructType("SYSTEMTIME", [ 37 {wYear: ctypes.uint16_t}, 38 {wMonth: ctypes.uint16_t}, 39 {wDayOfWeek: ctypes.uint16_t}, 40 {wDay: ctypes.uint16_t}, 41 {wHour: ctypes.uint16_t}, 42 {wMinute: ctypes.uint16_t}, 43 {wSecond: ctypes.uint16_t}, 44 {wMilliseconds: ctypes.uint16_t}, 45]); 46 47// DB column types, cribbed from the ESE header 48var COLUMN_TYPES = { 49 JET_coltypBit: 1, /* True, False, or NULL */ 50 JET_coltypUnsignedByte: 2, /* 1-byte integer, unsigned */ 51 JET_coltypShort: 3, /* 2-byte integer, signed */ 52 JET_coltypLong: 4, /* 4-byte integer, signed */ 53 JET_coltypCurrency: 5, /* 8 byte integer, signed */ 54 JET_coltypIEEESingle: 6, /* 4-byte IEEE single precision */ 55 JET_coltypIEEEDouble: 7, /* 8-byte IEEE double precision */ 56 JET_coltypDateTime: 8, /* Integral date, fractional time */ 57 JET_coltypBinary: 9, /* Binary data, < 255 bytes */ 58 JET_coltypText: 10, /* ANSI text, case insensitive, < 255 bytes */ 59 JET_coltypLongBinary: 11, /* Binary data, long value */ 60 JET_coltypLongText: 12, /* ANSI text, long value */ 61 62 JET_coltypUnsignedLong: 14, /* 4-byte unsigned integer */ 63 JET_coltypLongLong: 15, /* 8-byte signed integer */ 64 JET_coltypGUID: 16, /* 16-byte globally unique identifier */ 65}; 66 67// Not very efficient, but only used for error messages 68function getColTypeName(numericValue) { 69 return Object.keys(COLUMN_TYPES).find(t => COLUMN_TYPES[t] == numericValue) || "unknown"; 70} 71 72// All type constants and method wrappers go on this object: 73const ESE = {}; 74ESE.JET_ERR = ctypes.long; 75ESE.JET_PCWSTR = ctypes.char16_t.ptr; 76// The ESE header calls this JET_API_PTR, but because it isn't ever used as a 77// pointer and because OS.File code implies that the name you give a type 78// matters, I opted for a different name. 79// Note that this is defined differently on 32 vs. 64-bit in the header. 80ESE.JET_API_ITEM = ctypes.voidptr_t.size == 4 ? ctypes.unsigned_long : ctypes.uint64_t; 81ESE.JET_INSTANCE = ESE.JET_API_ITEM; 82ESE.JET_SESID = ESE.JET_API_ITEM; 83ESE.JET_TABLEID = ESE.JET_API_ITEM; 84ESE.JET_COLUMNID = ctypes.unsigned_long; 85ESE.JET_GRBIT = ctypes.unsigned_long; 86ESE.JET_COLTYP = ctypes.unsigned_long; 87ESE.JET_DBID = ctypes.unsigned_long; 88 89ESE.JET_COLUMNDEF = new ctypes.StructType("JET_COLUMNDEF", [ 90 {"cbStruct": ctypes.unsigned_long }, 91 {"columnid": ESE.JET_COLUMNID }, 92 {"coltyp": ESE.JET_COLTYP }, 93 {"wCountry": ctypes.unsigned_short }, // sepcifies the country/region for the column definition 94 {"langid": ctypes.unsigned_short }, 95 {"cp": ctypes.unsigned_short }, 96 {"wCollate": ctypes.unsigned_short }, /* Must be 0 */ 97 {"cbMax": ctypes.unsigned_long }, 98 {"grbit": ESE.JET_GRBIT }, 99]); 100 101// Track open databases 102let gOpenDBs = new Map(); 103 104// Track open libraries 105let gLibs = {}; 106this.ESE = ESE; // Required for tests. 107this.KERNEL = KERNEL; // ditto 108this.gLibs = gLibs; // ditto 109 110function convertESEError(errorCode) { 111 switch (errorCode) { 112 case -1213 /* JET_errPageSizeMismatch */: 113 case -1002 /* JET_errInvalidName*/: 114 case -1507 /* JET_errColumnNotFound */: 115 // The DB format has changed and we haven't updated this migration code: 116 return "The database format has changed, error code: " + errorCode; 117 case -1032 /* JET_errFileAccessDenied */: 118 case -1207 /* JET_errDatabaseLocked */: 119 case -1302 /* JET_errTableLocked */: 120 return "The database or table is locked, error code: " + errorCode; 121 case -1809 /* JET_errPermissionDenied*/: 122 case -1907 /* JET_errAccessDenied */: 123 return "Access or permission denied, error code: " + errorCode; 124 case -1044 /* JET_errInvalidFilename */: 125 return "Invalid file name"; 126 case -1811 /* JET_errFileNotFound */: 127 return "File not found"; 128 case -550 /* JET_errDatabaseDirtyShutdown */: 129 return "Database in dirty shutdown state (without the requisite logs?)"; 130 case -514 /* JET_errBadLogVersion */: 131 return "Database log version does not match the version of ESE in use."; 132 default: 133 return "Unknown error: " + errorCode; 134 } 135} 136 137function handleESEError(method, methodName, shouldThrow = true, errorLog = true) { 138 return function() { 139 let rv; 140 try { 141 rv = method.apply(null, arguments); 142 } catch (ex) { 143 log.error("Error calling into ctypes method", methodName, ex); 144 throw ex; 145 } 146 let resultCode = parseInt(rv.toString(10), 10); 147 if (resultCode < 0) { 148 if (errorLog) { 149 log.error("Got error " + resultCode + " calling " + methodName); 150 } 151 if (shouldThrow) { 152 throw new Error(convertESEError(rv)); 153 } 154 } else if (resultCode > 0 && errorLog) { 155 log.warn("Got warning " + resultCode + " calling " + methodName); 156 } 157 return resultCode; 158 }; 159} 160 161 162function declareESEFunction(methodName, ...args) { 163 let declaration = ["Jet" + methodName, ctypes.winapi_abi, ESE.JET_ERR].concat(args); 164 let ctypeMethod = gLibs.ese.declare.apply(gLibs.ese, declaration); 165 ESE[methodName] = handleESEError(ctypeMethod, methodName); 166 ESE["FailSafe" + methodName] = handleESEError(ctypeMethod, methodName, false); 167 ESE["Manual" + methodName] = handleESEError(ctypeMethod, methodName, false, false); 168} 169 170function declareESEFunctions() { 171 declareESEFunction("GetDatabaseFileInfoW", ESE.JET_PCWSTR, ctypes.voidptr_t, 172 ctypes.unsigned_long, ctypes.unsigned_long); 173 174 declareESEFunction("GetSystemParameterW", ESE.JET_INSTANCE, ESE.JET_SESID, 175 ctypes.unsigned_long, ESE.JET_API_ITEM.ptr, 176 ESE.JET_PCWSTR, ctypes.unsigned_long); 177 declareESEFunction("SetSystemParameterW", ESE.JET_INSTANCE.ptr, 178 ESE.JET_SESID, ctypes.unsigned_long, ESE.JET_API_ITEM, 179 ESE.JET_PCWSTR); 180 declareESEFunction("CreateInstanceW", ESE.JET_INSTANCE.ptr, ESE.JET_PCWSTR); 181 declareESEFunction("Init", ESE.JET_INSTANCE.ptr); 182 183 declareESEFunction("BeginSessionW", ESE.JET_INSTANCE, ESE.JET_SESID.ptr, 184 ESE.JET_PCWSTR, ESE.JET_PCWSTR); 185 declareESEFunction("AttachDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR, 186 ESE.JET_GRBIT); 187 declareESEFunction("DetachDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR); 188 declareESEFunction("OpenDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR, 189 ESE.JET_PCWSTR, ESE.JET_DBID.ptr, ESE.JET_GRBIT); 190 declareESEFunction("OpenTableW", ESE.JET_SESID, ESE.JET_DBID, ESE.JET_PCWSTR, 191 ctypes.voidptr_t, ctypes.unsigned_long, ESE.JET_GRBIT, 192 ESE.JET_TABLEID.ptr); 193 194 declareESEFunction("GetColumnInfoW", ESE.JET_SESID, ESE.JET_DBID, 195 ESE.JET_PCWSTR, ESE.JET_PCWSTR, ctypes.voidptr_t, 196 ctypes.unsigned_long, ctypes.unsigned_long); 197 198 declareESEFunction("Move", ESE.JET_SESID, ESE.JET_TABLEID, ctypes.long, 199 ESE.JET_GRBIT); 200 201 declareESEFunction("RetrieveColumn", ESE.JET_SESID, ESE.JET_TABLEID, 202 ESE.JET_COLUMNID, ctypes.voidptr_t, ctypes.unsigned_long, 203 ctypes.unsigned_long.ptr, ESE.JET_GRBIT, ctypes.voidptr_t); 204 205 declareESEFunction("CloseTable", ESE.JET_SESID, ESE.JET_TABLEID); 206 declareESEFunction("CloseDatabase", ESE.JET_SESID, ESE.JET_DBID, 207 ESE.JET_GRBIT); 208 209 declareESEFunction("EndSession", ESE.JET_SESID, ESE.JET_GRBIT); 210 211 declareESEFunction("Term", ESE.JET_INSTANCE); 212} 213 214function unloadLibraries() { 215 log.debug("Unloading"); 216 if (gOpenDBs.size) { 217 log.error("Shouldn't unload libraries before DBs are closed!"); 218 for (let db of gOpenDBs.values()) { 219 db._close(); 220 } 221 } 222 for (let k of Object.keys(ESE)) { 223 delete ESE[k]; 224 } 225 gLibs.ese.close(); 226 gLibs.kernel.close(); 227 delete gLibs.ese; 228 delete gLibs.kernel; 229} 230 231function loadLibraries() { 232 Services.obs.addObserver(unloadLibraries, "xpcom-shutdown"); 233 gLibs.ese = ctypes.open("esent.dll"); 234 gLibs.kernel = ctypes.open("kernel32.dll"); 235 KERNEL.FileTimeToSystemTime = gLibs.kernel.declare("FileTimeToSystemTime", 236 ctypes.winapi_abi, ctypes.int, KERNEL.FILETIME.ptr, KERNEL.SYSTEMTIME.ptr); 237 238 declareESEFunctions(); 239} 240 241function ESEDB(rootPath, dbPath, logPath) { 242 log.info("Created db"); 243 this.rootPath = rootPath; 244 this.dbPath = dbPath; 245 this.logPath = logPath; 246 this._references = 0; 247 this._init(); 248} 249 250ESEDB.prototype = { 251 rootPath: null, 252 dbPath: null, 253 logPath: null, 254 _opened: false, 255 _attached: false, 256 _sessionCreated: false, 257 _instanceCreated: false, 258 _dbId: null, 259 _sessionId: null, 260 _instanceId: null, 261 262 _init() { 263 if (!gLibs.ese) { 264 loadLibraries(); 265 } 266 this.incrementReferenceCounter(); 267 this._internalOpen(); 268 }, 269 270 _internalOpen() { 271 try { 272 let dbinfo = new ctypes.unsigned_long(); 273 ESE.GetDatabaseFileInfoW(this.dbPath, dbinfo.address(), 274 ctypes.unsigned_long.size, 17); 275 276 let pageSize = ctypes.UInt64.lo(dbinfo.value); 277 ESE.SetSystemParameterW(null, 0, 64 /* JET_paramDatabasePageSize*/, 278 pageSize, null); 279 280 this._instanceId = new ESE.JET_INSTANCE(); 281 ESE.CreateInstanceW(this._instanceId.address(), 282 "firefox-dbreader-" + (gESEInstanceCounter++)); 283 this._instanceCreated = true; 284 285 ESE.SetSystemParameterW(this._instanceId.address(), 0, 286 0 /* JET_paramSystemPath*/, 0, this.rootPath); 287 ESE.SetSystemParameterW(this._instanceId.address(), 0, 288 1 /* JET_paramTempPath */, 0, this.rootPath); 289 ESE.SetSystemParameterW(this._instanceId.address(), 0, 290 2 /* JET_paramLogFilePath*/, 0, this.logPath); 291 292 // Shouldn't try to call JetTerm if the following call fails. 293 this._instanceCreated = false; 294 ESE.Init(this._instanceId.address()); 295 this._instanceCreated = true; 296 this._sessionId = new ESE.JET_SESID(); 297 ESE.BeginSessionW(this._instanceId, this._sessionId.address(), null, 298 null); 299 this._sessionCreated = true; 300 301 const JET_bitDbReadOnly = 1; 302 ESE.AttachDatabaseW(this._sessionId, this.dbPath, JET_bitDbReadOnly); 303 this._attached = true; 304 this._dbId = new ESE.JET_DBID(); 305 ESE.OpenDatabaseW(this._sessionId, this.dbPath, null, 306 this._dbId.address(), JET_bitDbReadOnly); 307 this._opened = true; 308 } catch (ex) { 309 try { 310 this._close(); 311 } catch (innerException) { 312 Cu.reportError(innerException); 313 } 314 // Make sure caller knows we failed. 315 throw ex; 316 } 317 gOpenDBs.set(this.dbPath, this); 318 }, 319 320 checkForColumn(tableName, columnName) { 321 if (!this._opened) { 322 throw new Error("The database was closed!"); 323 } 324 325 let columnInfo; 326 try { 327 columnInfo = this._getColumnInfo(tableName, [{name: columnName}]); 328 } catch (ex) { 329 return null; 330 } 331 return columnInfo[0]; 332 }, 333 334 tableExists(tableName) { 335 if (!this._opened) { 336 throw new Error("The database was closed!"); 337 } 338 339 let tableId = new ESE.JET_TABLEID(); 340 let rv = ESE.ManualOpenTableW(this._sessionId, this._dbId, tableName, null, 341 0, 4 /* JET_bitTableReadOnly */, 342 tableId.address()); 343 if (rv == -1305 /* JET_errObjectNotFound */) { 344 return false; 345 } 346 if (rv < 0) { 347 log.error("Got error " + rv + " calling OpenTableW"); 348 throw new Error(convertESEError(rv)); 349 } 350 351 if (rv > 0) { 352 log.error("Got warning " + rv + " calling OpenTableW"); 353 } 354 ESE.FailSafeCloseTable(this._sessionId, tableId); 355 return true; 356 }, 357 358 * tableItems(tableName, columns) { 359 if (!this._opened) { 360 throw new Error("The database was closed!"); 361 } 362 363 let tableOpened = false; 364 let tableId; 365 try { 366 tableId = this._openTable(tableName); 367 tableOpened = true; 368 369 let columnInfo = this._getColumnInfo(tableName, columns); 370 371 let rv = ESE.ManualMove(this._sessionId, tableId, 372 -2147483648 /* JET_MoveFirst */, 0); 373 if (rv == -1603 /* JET_errNoCurrentRecord */) { 374 // There are no rows in the table. 375 this._closeTable(tableId); 376 return; 377 } 378 if (rv != 0) { 379 throw new Error(convertESEError(rv)); 380 } 381 382 do { 383 let rowContents = {}; 384 for (let column of columnInfo) { 385 let [buffer, bufferSize] = this._getBufferForColumn(column); 386 // We handle errors manually so we accurately deal with NULL values. 387 let err = ESE.ManualRetrieveColumn(this._sessionId, tableId, 388 column.id, buffer.address(), 389 bufferSize, null, 0, null); 390 rowContents[column.name] = this._convertResult(column, buffer, err); 391 } 392 yield rowContents; 393 } while (ESE.ManualMove(this._sessionId, tableId, 1 /* JET_MoveNext */, 0) === 0); 394 } catch (ex) { 395 if (tableOpened) { 396 this._closeTable(tableId); 397 } 398 throw ex; 399 } 400 this._closeTable(tableId); 401 }, 402 403 _openTable(tableName) { 404 let tableId = new ESE.JET_TABLEID(); 405 ESE.OpenTableW(this._sessionId, this._dbId, tableName, null, 406 0, 4 /* JET_bitTableReadOnly */, tableId.address()); 407 return tableId; 408 }, 409 410 _getBufferForColumn(column) { 411 let buffer; 412 if (column.type == "string") { 413 let wchar_tArray = ctypes.ArrayType(ctypes.char16_t); 414 // size on the column is in bytes, 2 bytes to a wchar, so: 415 let charCount = column.dbSize >> 1; 416 buffer = new wchar_tArray(charCount); 417 } else if (column.type == "boolean") { 418 buffer = new ctypes.uint8_t(); 419 } else if (column.type == "date") { 420 buffer = new KERNEL.FILETIME(); 421 } else if (column.type == "guid") { 422 let byteArray = ctypes.ArrayType(ctypes.uint8_t); 423 buffer = new byteArray(column.dbSize); 424 } else { 425 throw new Error("Unknown type " + column.type); 426 } 427 return [buffer, buffer.constructor.size]; 428 }, 429 430 _convertResult(column, buffer, err) { 431 if (err != 0) { 432 if (err == 1004) { 433 // Deal with null values: 434 buffer = null; 435 } else { 436 Cu.reportError("Unexpected JET error: " + err + "; retrieving value for column " + column.name); 437 throw new Error(convertESEError(err)); 438 } 439 } 440 if (column.type == "string") { 441 return buffer ? buffer.readString() : ""; 442 } 443 if (column.type == "boolean") { 444 return buffer ? (buffer.value == 255) : false; 445 } 446 if (column.type == "guid") { 447 if (buffer.length != 16) { 448 Cu.reportError("Buffer size for guid field " + column.id + " should have been 16!"); 449 return ""; 450 } 451 let rv = "{"; 452 for (let i = 0; i < 16; i++) { 453 if (i == 4 || i == 6 || i == 8 || i == 10) { 454 rv += "-"; 455 } 456 let byteValue = buffer.addressOfElement(i).contents; 457 // Ensure there's a leading 0 458 rv += ("0" + byteValue.toString(16)).substr(-2); 459 } 460 return rv + "}"; 461 } 462 if (column.type == "date") { 463 if (!buffer) { 464 return null; 465 } 466 let systemTime = new KERNEL.SYSTEMTIME(); 467 let result = KERNEL.FileTimeToSystemTime(buffer.address(), systemTime.address()); 468 if (result == 0) { 469 throw new Error(ctypes.winLastError); 470 } 471 472 // System time is in UTC, so we use Date.UTC to get milliseconds from epoch, 473 // then divide by 1000 to get seconds, and round down: 474 return new Date(Date.UTC(systemTime.wYear, 475 systemTime.wMonth - 1, 476 systemTime.wDay, 477 systemTime.wHour, 478 systemTime.wMinute, 479 systemTime.wSecond, 480 systemTime.wMilliseconds)); 481 } 482 return undefined; 483 }, 484 485 _getColumnInfo(tableName, columns) { 486 let rv = []; 487 for (let column of columns) { 488 let columnInfoFromDB = new ESE.JET_COLUMNDEF(); 489 ESE.GetColumnInfoW(this._sessionId, this._dbId, tableName, column.name, 490 columnInfoFromDB.address(), ESE.JET_COLUMNDEF.size, 0 /* JET_ColInfo */); 491 let dbType = parseInt(columnInfoFromDB.coltyp.toString(10), 10); 492 let dbSize = parseInt(columnInfoFromDB.cbMax.toString(10), 10); 493 if (column.type == "string") { 494 if (dbType != COLUMN_TYPES.JET_coltypLongText && 495 dbType != COLUMN_TYPES.JET_coltypText) { 496 throw new Error("Invalid column type for column " + column.name + 497 "; expected text type, got type " + getColTypeName(dbType)); 498 } 499 if (dbSize > MAX_STR_LENGTH) { 500 throw new Error("Column " + column.name + " has more than 64k data in it. This API is not designed to handle data that large."); 501 } 502 } else if (column.type == "boolean") { 503 if (dbType != COLUMN_TYPES.JET_coltypBit) { 504 throw new Error("Invalid column type for column " + column.name + 505 "; expected bit type, got type " + getColTypeName(dbType)); 506 } 507 } else if (column.type == "date") { 508 if (dbType != COLUMN_TYPES.JET_coltypLongLong) { 509 throw new Error("Invalid column type for column " + column.name + 510 "; expected long long type, got type " + getColTypeName(dbType)); 511 } 512 } else if (column.type == "guid") { 513 if (dbType != COLUMN_TYPES.JET_coltypGUID) { 514 throw new Error("Invalid column type for column " + column.name + 515 "; expected guid type, got type " + getColTypeName(dbType)); 516 } 517 } else if (column.type) { 518 throw new Error("Unknown column type " + column.type + " requested for column " + 519 column.name + ", don't know what to do."); 520 } 521 522 rv.push({name: column.name, id: columnInfoFromDB.columnid, type: column.type, dbSize, dbType}); 523 } 524 return rv; 525 }, 526 527 _closeTable(tableId) { 528 ESE.FailSafeCloseTable(this._sessionId, tableId); 529 }, 530 531 _close() { 532 this._internalClose(); 533 gOpenDBs.delete(this.dbPath); 534 }, 535 536 _internalClose() { 537 if (this._opened) { 538 log.debug("close db"); 539 ESE.FailSafeCloseDatabase(this._sessionId, this._dbId, 0); 540 log.debug("finished close db"); 541 this._opened = false; 542 } 543 if (this._attached) { 544 log.debug("detach db"); 545 ESE.FailSafeDetachDatabaseW(this._sessionId, this.dbPath); 546 this._attached = false; 547 } 548 if (this._sessionCreated) { 549 log.debug("end session"); 550 ESE.FailSafeEndSession(this._sessionId, 0); 551 this._sessionCreated = false; 552 } 553 if (this._instanceCreated) { 554 log.debug("term"); 555 ESE.FailSafeTerm(this._instanceId); 556 this._instanceCreated = false; 557 } 558 }, 559 560 incrementReferenceCounter() { 561 this._references++; 562 }, 563 564 decrementReferenceCounter() { 565 this._references--; 566 if (this._references <= 0) { 567 this._close(); 568 } 569 }, 570}; 571 572let ESEDBReader = { 573 openDB(rootDir, dbFile, logDir) { 574 let dbFilePath = dbFile.path; 575 if (gOpenDBs.has(dbFilePath)) { 576 let db = gOpenDBs.get(dbFilePath); 577 db.incrementReferenceCounter(); 578 return db; 579 } 580 // ESE is really picky about the trailing slashes according to the docs, 581 // so we do as we're told and ensure those are there: 582 return new ESEDB(rootDir.path + "\\", dbFilePath, logDir.path + "\\"); 583 }, 584 585 async dbLocked(dbFile) { 586 let options = {winShare: OS.Constants.Win.FILE_SHARE_READ}; 587 let locked = true; 588 await OS.File.open(dbFile.path, {read: true}, options).then(fileHandle => { 589 locked = false; 590 // Return the close promise so we wait for the file to be closed again. 591 // Otherwise the file might still be kept open by this handle by the time 592 // that we try to use the ESE APIs to access it. 593 return fileHandle.close(); 594 }, () => { 595 Cu.reportError("ESE DB at " + dbFile.path + " is locked."); 596 }); 597 return locked; 598 }, 599 600 closeDB(db) { 601 db.decrementReferenceCounter(); 602 }, 603 604 COLUMN_TYPES, 605}; 606 607