diff options
Diffstat (limited to 'application/basilisk/components/migration/ESEDBReader.jsm')
-rw-r--r-- | application/basilisk/components/migration/ESEDBReader.jsm | 608 |
1 files changed, 0 insertions, 608 deletions
diff --git a/application/basilisk/components/migration/ESEDBReader.jsm b/application/basilisk/components/migration/ESEDBReader.jsm deleted file mode 100644 index 6192c8667..000000000 --- a/application/basilisk/components/migration/ESEDBReader.jsm +++ /dev/null @@ -1,608 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -this.EXPORTED_SYMBOLS = ["ESEDBReader"]; /* exported ESEDBReader */ - -const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; - -Cu.import("resource://gre/modules/ctypes.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -XPCOMUtils.defineLazyGetter(this, "log", () => { - let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI; - let consoleOptions = { - maxLogLevelPref: "browser.esedbreader.loglevel", - prefix: "ESEDBReader", - }; - return new ConsoleAPI(consoleOptions); -}); - -XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); - -// We have a globally unique identifier for ESE instances. A new one -// is used for each different database opened. -let gESEInstanceCounter = 0; - -// We limit the length of strings that we read from databases. -const MAX_STR_LENGTH = 64 * 1024; - -// Kernel-related types: -const KERNEL = {}; -KERNEL.FILETIME = new ctypes.StructType("FILETIME", [ - {dwLowDateTime: ctypes.uint32_t}, - {dwHighDateTime: ctypes.uint32_t} -]); -KERNEL.SYSTEMTIME = new ctypes.StructType("SYSTEMTIME", [ - {wYear: ctypes.uint16_t}, - {wMonth: ctypes.uint16_t}, - {wDayOfWeek: ctypes.uint16_t}, - {wDay: ctypes.uint16_t}, - {wHour: ctypes.uint16_t}, - {wMinute: ctypes.uint16_t}, - {wSecond: ctypes.uint16_t}, - {wMilliseconds: ctypes.uint16_t} -]); - -// DB column types, cribbed from the ESE header -var COLUMN_TYPES = { - JET_coltypBit: 1, /* True, False, or NULL */ - JET_coltypUnsignedByte: 2, /* 1-byte integer, unsigned */ - JET_coltypShort: 3, /* 2-byte integer, signed */ - JET_coltypLong: 4, /* 4-byte integer, signed */ - JET_coltypCurrency: 5, /* 8 byte integer, signed */ - JET_coltypIEEESingle: 6, /* 4-byte IEEE single precision */ - JET_coltypIEEEDouble: 7, /* 8-byte IEEE double precision */ - JET_coltypDateTime: 8, /* Integral date, fractional time */ - JET_coltypBinary: 9, /* Binary data, < 255 bytes */ - JET_coltypText: 10, /* ANSI text, case insensitive, < 255 bytes */ - JET_coltypLongBinary: 11, /* Binary data, long value */ - JET_coltypLongText: 12, /* ANSI text, long value */ - - JET_coltypUnsignedLong: 14, /* 4-byte unsigned integer */ - JET_coltypLongLong: 15, /* 8-byte signed integer */ - JET_coltypGUID: 16, /* 16-byte globally unique identifier */ -}; - -// Not very efficient, but only used for error messages -function getColTypeName(numericValue) { - return Object.keys(COLUMN_TYPES).find(t => COLUMN_TYPES[t] == numericValue) || "unknown"; -} - -// All type constants and method wrappers go on this object: -const ESE = {}; -ESE.JET_ERR = ctypes.long; -ESE.JET_PCWSTR = ctypes.char16_t.ptr; -// The ESE header calls this JET_API_PTR, but because it isn't ever used as a -// pointer and because OS.File code implies that the name you give a type -// matters, I opted for a different name. -// Note that this is defined differently on 32 vs. 64-bit in the header. -ESE.JET_API_ITEM = ctypes.voidptr_t.size == 4 ? ctypes.unsigned_long : ctypes.uint64_t; -ESE.JET_INSTANCE = ESE.JET_API_ITEM; -ESE.JET_SESID = ESE.JET_API_ITEM; -ESE.JET_TABLEID = ESE.JET_API_ITEM; -ESE.JET_COLUMNID = ctypes.unsigned_long; -ESE.JET_GRBIT = ctypes.unsigned_long; -ESE.JET_COLTYP = ctypes.unsigned_long; -ESE.JET_DBID = ctypes.unsigned_long; - -ESE.JET_COLUMNDEF = new ctypes.StructType("JET_COLUMNDEF", [ - {"cbStruct": ctypes.unsigned_long}, - {"columnid": ESE.JET_COLUMNID }, - {"coltyp": ESE.JET_COLTYP }, - {"wCountry": ctypes.unsigned_short }, // sepcifies the country/region for the column definition - {"langid": ctypes.unsigned_short }, - {"cp": ctypes.unsigned_short }, - {"wCollate": ctypes.unsigned_short }, /* Must be 0 */ - {"cbMax": ctypes.unsigned_long }, - {"grbit": ESE.JET_GRBIT } -]); - -// Track open databases -let gOpenDBs = new Map(); - -// Track open libraries -let gLibs = {}; -this.ESE = ESE; // Required for tests. -this.KERNEL = KERNEL; // ditto -this.gLibs = gLibs; // ditto - -function convertESEError(errorCode) { - switch (errorCode) { - case -1213 /* JET_errPageSizeMismatch */: - case -1002 /* JET_errInvalidName*/: - case -1507 /* JET_errColumnNotFound */: - // The DB format has changed and we haven't updated this migration code: - return "The database format has changed, error code: " + errorCode; - case -1032 /* JET_errFileAccessDenied */: - case -1207 /* JET_errDatabaseLocked */: - case -1302 /* JET_errTableLocked */: - return "The database or table is locked, error code: " + errorCode; - case -1809 /* JET_errPermissionDenied*/: - case -1907 /* JET_errAccessDenied */: - return "Access or permission denied, error code: " + errorCode; - case -1044 /* JET_errInvalidFilename */: - return "Invalid file name"; - case -1811 /* JET_errFileNotFound */: - return "File not found"; - case -550 /* JET_errDatabaseDirtyShutdown */: - return "Database in dirty shutdown state (without the requisite logs?)"; - case -514 /* JET_errBadLogVersion */: - return "Database log version does not match the version of ESE in use."; - default: - return "Unknown error: " + errorCode; - } -} - -function handleESEError(method, methodName, shouldThrow = true, errorLog = true) { - return function() { - let rv; - try { - rv = method.apply(null, arguments); - } catch (ex) { - log.error("Error calling into ctypes method", methodName, ex); - throw ex; - } - let resultCode = parseInt(rv.toString(10), 10); - if (resultCode < 0) { - if (errorLog) { - log.error("Got error " + resultCode + " calling " + methodName); - } - if (shouldThrow) { - throw new Error(convertESEError(rv)); - } - } else if (resultCode > 0 && errorLog) { - log.warn("Got warning " + resultCode + " calling " + methodName); - } - return resultCode; - }; -} - - -function declareESEFunction(methodName, ...args) { - let declaration = ["Jet" + methodName, ctypes.winapi_abi, ESE.JET_ERR].concat(args); - let ctypeMethod = gLibs.ese.declare.apply(gLibs.ese, declaration); - ESE[methodName] = handleESEError(ctypeMethod, methodName); - ESE["FailSafe" + methodName] = handleESEError(ctypeMethod, methodName, false); - ESE["Manual" + methodName] = handleESEError(ctypeMethod, methodName, false, false); -} - -function declareESEFunctions() { - declareESEFunction("GetDatabaseFileInfoW", ESE.JET_PCWSTR, ctypes.voidptr_t, - ctypes.unsigned_long, ctypes.unsigned_long); - - declareESEFunction("GetSystemParameterW", ESE.JET_INSTANCE, ESE.JET_SESID, - ctypes.unsigned_long, ESE.JET_API_ITEM.ptr, - ESE.JET_PCWSTR, ctypes.unsigned_long); - declareESEFunction("SetSystemParameterW", ESE.JET_INSTANCE.ptr, - ESE.JET_SESID, ctypes.unsigned_long, ESE.JET_API_ITEM, - ESE.JET_PCWSTR); - declareESEFunction("CreateInstanceW", ESE.JET_INSTANCE.ptr, ESE.JET_PCWSTR); - declareESEFunction("Init", ESE.JET_INSTANCE.ptr); - - declareESEFunction("BeginSessionW", ESE.JET_INSTANCE, ESE.JET_SESID.ptr, - ESE.JET_PCWSTR, ESE.JET_PCWSTR); - declareESEFunction("AttachDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR, - ESE.JET_GRBIT); - declareESEFunction("DetachDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR); - declareESEFunction("OpenDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR, - ESE.JET_PCWSTR, ESE.JET_DBID.ptr, ESE.JET_GRBIT); - declareESEFunction("OpenTableW", ESE.JET_SESID, ESE.JET_DBID, ESE.JET_PCWSTR, - ctypes.voidptr_t, ctypes.unsigned_long, ESE.JET_GRBIT, - ESE.JET_TABLEID.ptr); - - declareESEFunction("GetColumnInfoW", ESE.JET_SESID, ESE.JET_DBID, - ESE.JET_PCWSTR, ESE.JET_PCWSTR, ctypes.voidptr_t, - ctypes.unsigned_long, ctypes.unsigned_long); - - declareESEFunction("Move", ESE.JET_SESID, ESE.JET_TABLEID, ctypes.long, - ESE.JET_GRBIT); - - declareESEFunction("RetrieveColumn", ESE.JET_SESID, ESE.JET_TABLEID, - ESE.JET_COLUMNID, ctypes.voidptr_t, ctypes.unsigned_long, - ctypes.unsigned_long.ptr, ESE.JET_GRBIT, ctypes.voidptr_t); - - declareESEFunction("CloseTable", ESE.JET_SESID, ESE.JET_TABLEID); - declareESEFunction("CloseDatabase", ESE.JET_SESID, ESE.JET_DBID, - ESE.JET_GRBIT); - - declareESEFunction("EndSession", ESE.JET_SESID, ESE.JET_GRBIT); - - declareESEFunction("Term", ESE.JET_INSTANCE); -} - -function unloadLibraries() { - log.debug("Unloading"); - if (gOpenDBs.size) { - log.error("Shouldn't unload libraries before DBs are closed!"); - for (let db of gOpenDBs.values()) { - db._close(); - } - } - for (let k of Object.keys(ESE)) { - delete ESE[k]; - } - gLibs.ese.close(); - gLibs.kernel.close(); - delete gLibs.ese; - delete gLibs.kernel; -} - -function loadLibraries() { - Services.obs.addObserver(unloadLibraries, "xpcom-shutdown", false); - gLibs.ese = ctypes.open("esent.dll"); - gLibs.kernel = ctypes.open("kernel32.dll"); - KERNEL.FileTimeToSystemTime = gLibs.kernel.declare("FileTimeToSystemTime", - ctypes.default_abi, ctypes.int, KERNEL.FILETIME.ptr, KERNEL.SYSTEMTIME.ptr); - - declareESEFunctions(); -} - -function ESEDB(rootPath, dbPath, logPath) { - log.info("Created db"); - this.rootPath = rootPath; - this.dbPath = dbPath; - this.logPath = logPath; - this._references = 0; - this._init(); -} - -ESEDB.prototype = { - rootPath: null, - dbPath: null, - logPath: null, - _opened: false, - _attached: false, - _sessionCreated: false, - _instanceCreated: false, - _dbId: null, - _sessionId: null, - _instanceId: null, - - _init() { - if (!gLibs.ese) { - loadLibraries(); - } - this.incrementReferenceCounter(); - this._internalOpen(); - }, - - _internalOpen() { - try { - let dbinfo = new ctypes.unsigned_long(); - ESE.GetDatabaseFileInfoW(this.dbPath, dbinfo.address(), - ctypes.unsigned_long.size, 17); - - let pageSize = ctypes.UInt64.lo(dbinfo.value); - ESE.SetSystemParameterW(null, 0, 64 /* JET_paramDatabasePageSize*/, - pageSize, null); - - this._instanceId = new ESE.JET_INSTANCE(); - ESE.CreateInstanceW(this._instanceId.address(), - "firefox-dbreader-" + (gESEInstanceCounter++)); - this._instanceCreated = true; - - ESE.SetSystemParameterW(this._instanceId.address(), 0, - 0 /* JET_paramSystemPath*/, 0, this.rootPath); - ESE.SetSystemParameterW(this._instanceId.address(), 0, - 1 /* JET_paramTempPath */, 0, this.rootPath); - ESE.SetSystemParameterW(this._instanceId.address(), 0, - 2 /* JET_paramLogFilePath*/, 0, this.logPath); - - // Shouldn't try to call JetTerm if the following call fails. - this._instanceCreated = false; - ESE.Init(this._instanceId.address()); - this._instanceCreated = true; - this._sessionId = new ESE.JET_SESID(); - ESE.BeginSessionW(this._instanceId, this._sessionId.address(), null, - null); - this._sessionCreated = true; - - const JET_bitDbReadOnly = 1; - ESE.AttachDatabaseW(this._sessionId, this.dbPath, JET_bitDbReadOnly); - this._attached = true; - this._dbId = new ESE.JET_DBID(); - ESE.OpenDatabaseW(this._sessionId, this.dbPath, null, - this._dbId.address(), JET_bitDbReadOnly); - this._opened = true; - } catch (ex) { - try { - this._close(); - } catch (innerException) { - Cu.reportError(innerException); - } - // Make sure caller knows we failed. - throw ex; - } - gOpenDBs.set(this.dbPath, this); - }, - - checkForColumn(tableName, columnName) { - if (!this._opened) { - throw new Error("The database was closed!"); - } - - let columnInfo; - try { - columnInfo = this._getColumnInfo(tableName, [{name: columnName}]); - } catch (ex) { - return null; - } - return columnInfo[0]; - }, - - tableExists(tableName) { - if (!this._opened) { - throw new Error("The database was closed!"); - } - - let tableId = new ESE.JET_TABLEID(); - let rv = ESE.ManualOpenTableW(this._sessionId, this._dbId, tableName, null, - 0, 4 /* JET_bitTableReadOnly */, - tableId.address()); - if (rv == -1305 /* JET_errObjectNotFound */) { - return false; - } - if (rv < 0) { - log.error("Got error " + rv + " calling OpenTableW"); - throw new Error(convertESEError(rv)); - } - - if (rv > 0) { - log.error("Got warning " + rv + " calling OpenTableW"); - } - ESE.FailSafeCloseTable(this._sessionId, tableId); - return true; - }, - - *tableItems(tableName, columns) { - if (!this._opened) { - throw new Error("The database was closed!"); - } - - let tableOpened = false; - let tableId; - try { - tableId = this._openTable(tableName); - tableOpened = true; - - let columnInfo = this._getColumnInfo(tableName, columns); - - let rv = ESE.ManualMove(this._sessionId, tableId, - -2147483648 /* JET_MoveFirst */, 0); - if (rv == -1603 /* JET_errNoCurrentRecord */) { - // There are no rows in the table. - this._closeTable(tableId); - return; - } - if (rv != 0) { - throw new Error(convertESEError(rv)); - } - - do { - let rowContents = {}; - for (let column of columnInfo) { - let [buffer, bufferSize] = this._getBufferForColumn(column); - // We handle errors manually so we accurately deal with NULL values. - let err = ESE.ManualRetrieveColumn(this._sessionId, tableId, - column.id, buffer.address(), - bufferSize, null, 0, null); - rowContents[column.name] = this._convertResult(column, buffer, err); - } - yield rowContents; - } while (ESE.ManualMove(this._sessionId, tableId, 1 /* JET_MoveNext */, 0) === 0); - } catch (ex) { - if (tableOpened) { - this._closeTable(tableId); - } - throw ex; - } - this._closeTable(tableId); - }, - - _openTable(tableName) { - let tableId = new ESE.JET_TABLEID(); - ESE.OpenTableW(this._sessionId, this._dbId, tableName, null, - 0, 4 /* JET_bitTableReadOnly */, tableId.address()); - return tableId; - }, - - _getBufferForColumn(column) { - let buffer; - if (column.type == "string") { - let wchar_tArray = ctypes.ArrayType(ctypes.char16_t); - // size on the column is in bytes, 2 bytes to a wchar, so: - let charCount = column.dbSize >> 1; - buffer = new wchar_tArray(charCount); - } else if (column.type == "boolean") { - buffer = new ctypes.uint8_t(); - } else if (column.type == "date") { - buffer = new KERNEL.FILETIME(); - } else if (column.type == "guid") { - let byteArray = ctypes.ArrayType(ctypes.uint8_t); - buffer = new byteArray(column.dbSize); - } else { - throw new Error("Unknown type " + column.type); - } - return [buffer, buffer.constructor.size]; - }, - - _convertResult(column, buffer, err) { - if (err != 0) { - if (err == 1004) { - // Deal with null values: - buffer = null; - } else { - Cu.reportError("Unexpected JET error: " + err + ";" + " retrieving value for column " + column.name); - throw new Error(convertESEError(err)); - } - } - if (column.type == "string") { - return buffer ? buffer.readString() : ""; - } - if (column.type == "boolean") { - return buffer ? (buffer.value == 255) : false; - } - if (column.type == "guid") { - if (buffer.length != 16) { - Cu.reportError("Buffer size for guid field " + column.id + " should have been 16!"); - return ""; - } - let rv = "{"; - for (let i = 0; i < 16; i++) { - if (i == 4 || i == 6 || i == 8 || i == 10) { - rv += "-"; - } - let byteValue = buffer.addressOfElement(i).contents; - // Ensure there's a leading 0 - rv += ("0" + byteValue.toString(16)).substr(-2); - } - return rv + "}"; - } - if (column.type == "date") { - if (!buffer) { - return null; - } - let systemTime = new KERNEL.SYSTEMTIME(); - let result = KERNEL.FileTimeToSystemTime(buffer.address(), systemTime.address()); - if (result == 0) { - throw new Error(ctypes.winLastError); - } - - // System time is in UTC, so we use Date.UTC to get milliseconds from epoch, - // then divide by 1000 to get seconds, and round down: - return new Date(Date.UTC(systemTime.wYear, - systemTime.wMonth - 1, - systemTime.wDay, - systemTime.wHour, - systemTime.wMinute, - systemTime.wSecond, - systemTime.wMilliseconds)); - } - return undefined; - }, - - _getColumnInfo(tableName, columns) { - let rv = []; - for (let column of columns) { - let columnInfoFromDB = new ESE.JET_COLUMNDEF(); - ESE.GetColumnInfoW(this._sessionId, this._dbId, tableName, column.name, - columnInfoFromDB.address(), ESE.JET_COLUMNDEF.size, 0 /* JET_ColInfo */); - let dbType = parseInt(columnInfoFromDB.coltyp.toString(10), 10); - let dbSize = parseInt(columnInfoFromDB.cbMax.toString(10), 10); - if (column.type == "string") { - if (dbType != COLUMN_TYPES.JET_coltypLongText && - dbType != COLUMN_TYPES.JET_coltypText) { - throw new Error("Invalid column type for column " + column.name + - "; expected text type, got type " + getColTypeName(dbType)); - } - if (dbSize > MAX_STR_LENGTH) { - throw new Error("Column " + column.name + " has more than 64k data in it. This API is not designed to handle data that large."); - } - } else if (column.type == "boolean") { - if (dbType != COLUMN_TYPES.JET_coltypBit) { - throw new Error("Invalid column type for column " + column.name + - "; expected bit type, got type " + getColTypeName(dbType)); - } - } else if (column.type == "date") { - if (dbType != COLUMN_TYPES.JET_coltypLongLong) { - throw new Error("Invalid column type for column " + column.name + - "; expected long long type, got type " + getColTypeName(dbType)); - } - } else if (column.type == "guid") { - if (dbType != COLUMN_TYPES.JET_coltypGUID) { - throw new Error("Invalid column type for column " + column.name + - "; expected guid type, got type " + getColTypeName(dbType)); - } - } else if (column.type) { - throw new Error("Unknown column type " + column.type + " requested for column " + - column.name + ", don't know what to do."); - } - - rv.push({name: column.name, id: columnInfoFromDB.columnid, type: column.type, dbSize, dbType}); - } - return rv; - }, - - _closeTable(tableId) { - ESE.FailSafeCloseTable(this._sessionId, tableId); - }, - - _close() { - this._internalClose(); - gOpenDBs.delete(this.dbPath); - }, - - _internalClose() { - if (this._opened) { - log.debug("close db"); - ESE.FailSafeCloseDatabase(this._sessionId, this._dbId, 0); - log.debug("finished close db"); - this._opened = false; - } - if (this._attached) { - log.debug("detach db"); - ESE.FailSafeDetachDatabaseW(this._sessionId, this.dbPath); - this._attached = false; - } - if (this._sessionCreated) { - log.debug("end session"); - ESE.FailSafeEndSession(this._sessionId, 0); - this._sessionCreated = false; - } - if (this._instanceCreated) { - log.debug("term"); - ESE.FailSafeTerm(this._instanceId); - this._instanceCreated = false; - } - }, - - incrementReferenceCounter() { - this._references++; - }, - - decrementReferenceCounter() { - this._references--; - if (this._references <= 0) { - this._close(); - } - }, -}; - -let ESEDBReader = { - openDB(rootDir, dbFile, logDir) { - let dbFilePath = dbFile.path; - if (gOpenDBs.has(dbFilePath)) { - let db = gOpenDBs.get(dbFilePath); - db.incrementReferenceCounter(); - return db; - } - // ESE is really picky about the trailing slashes according to the docs, - // so we do as we're told and ensure those are there: - return new ESEDB(rootDir.path + "\\", dbFilePath, logDir.path + "\\"); - }, - - async dbLocked(dbFile) { - let options = {winShare: OS.Constants.Win.FILE_SHARE_READ}; - let locked = true; - await OS.File.open(dbFile.path, {read: true}, options).then(fileHandle => { - locked = false; - // Return the close promise so we wait for the file to be closed again. - // Otherwise the file might still be kept open by this handle by the time - // that we try to use the ESE APIs to access it. - return fileHandle.close(); - }, () => { - Cu.reportError("ESE DB at " + dbFile.path + " is locked."); - }); - return locked; - }, - - closeDB(db) { - db.decrementReferenceCounter(); - }, - - COLUMN_TYPES, -}; - |