tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 };