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