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