michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: /** michael@0: * OS.File utilities used by all threads. michael@0: * michael@0: * This module defines: michael@0: * - logging; michael@0: * - the base constants; michael@0: * - base types and primitives for declaring new types; michael@0: * - primitives for importing C functions; michael@0: * - primitives for dealing with integers, pointers, typed arrays; michael@0: * - the base class OSError; michael@0: * - a few additional utilities. michael@0: */ michael@0: michael@0: // Boilerplate used to be able to import this module both from the main michael@0: // thread and from worker threads. michael@0: if (typeof Components != "undefined") { michael@0: // Global definition of |exports|, to keep everybody happy. michael@0: // In non-main thread, |exports| is provided by the module michael@0: // loader. michael@0: this.exports = {}; michael@0: michael@0: const Cu = Components.utils; michael@0: const Ci = Components.interfaces; michael@0: const Cc = Components.classes; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm", this); michael@0: } michael@0: michael@0: let EXPORTED_SYMBOLS = [ michael@0: "LOG", michael@0: "clone", michael@0: "Config", michael@0: "Constants", michael@0: "Type", michael@0: "HollowStructure", michael@0: "OSError", michael@0: "Library", michael@0: "declareFFI", michael@0: "declareLazy", michael@0: "declareLazyFFI", michael@0: "normalizeToPointer", michael@0: "projectValue", michael@0: "isTypedArray", michael@0: "defineLazyGetter", michael@0: "offsetBy", michael@0: "OS" // Warning: this exported symbol will disappear michael@0: ]; michael@0: michael@0: ////////////////////// Configuration of OS.File michael@0: michael@0: let Config = { michael@0: /** michael@0: * If |true|, calls to |LOG| are shown. Otherwise, they are hidden. michael@0: * michael@0: * This configuration option is controlled by preference "toolkit.osfile.log". michael@0: */ michael@0: DEBUG: false, michael@0: michael@0: /** michael@0: * TEST michael@0: */ michael@0: TEST: false michael@0: }; michael@0: exports.Config = Config; michael@0: michael@0: ////////////////////// OS Constants michael@0: michael@0: if (typeof Components != "undefined") { michael@0: // On the main thread, OS.Constants is defined by a xpcom michael@0: // component. On other threads, it is available automatically michael@0: Cu.import("resource://gre/modules/ctypes.jsm"); michael@0: Cc["@mozilla.org/net/osfileconstantsservice;1"]. michael@0: getService(Ci.nsIOSFileConstantsService).init(); michael@0: } michael@0: michael@0: exports.Constants = OS.Constants; michael@0: michael@0: ///////////////////// Utilities michael@0: michael@0: // Define a lazy getter for a property michael@0: let defineLazyGetter = function defineLazyGetter(object, name, getter) { michael@0: Object.defineProperty(object, name, { michael@0: configurable: true, michael@0: get: function lazy() { michael@0: delete this[name]; michael@0: let value = getter.call(this); michael@0: Object.defineProperty(object, name, { michael@0: value: value michael@0: }); michael@0: return value; michael@0: } michael@0: }); michael@0: }; michael@0: exports.defineLazyGetter = defineLazyGetter; michael@0: michael@0: michael@0: ///////////////////// Logging michael@0: michael@0: /** michael@0: * The default implementation of the logger. michael@0: * michael@0: * The choice of logger can be overridden with Config.TEST. michael@0: */ michael@0: let gLogger; michael@0: if (typeof window != "undefined" && window.console && console.log) { michael@0: gLogger = console.log.bind(console, "OS"); michael@0: } else { michael@0: gLogger = function(...args) { michael@0: dump("OS " + args.join(" ") + "\n"); michael@0: }; michael@0: } michael@0: michael@0: /** michael@0: * Attempt to stringify an argument into something useful for michael@0: * debugging purposes, by using |.toString()| or |JSON.stringify| michael@0: * if available. michael@0: * michael@0: * @param {*} arg An argument to be stringified if possible. michael@0: * @return {string} A stringified version of |arg|. michael@0: */ michael@0: let stringifyArg = function stringifyArg(arg) { michael@0: if (typeof arg === "string") { michael@0: return arg; michael@0: } michael@0: if (arg && typeof arg === "object") { michael@0: let argToString = "" + arg; michael@0: michael@0: /** michael@0: * The only way to detect whether this object has a non-default michael@0: * implementation of |toString| is to check whether it returns michael@0: * '[object Object]'. Unfortunately, we cannot simply compare |arg.toString| michael@0: * and |Object.prototype.toString| as |arg| typically comes from another michael@0: * compartment. michael@0: */ michael@0: if (argToString === "[object Object]") { michael@0: return JSON.stringify(arg); michael@0: } else { michael@0: return argToString; michael@0: } michael@0: } michael@0: return arg; michael@0: }; michael@0: michael@0: let LOG = function (...args) { michael@0: if (!Config.DEBUG) { michael@0: // If logging is deactivated, don't log michael@0: return; michael@0: } michael@0: michael@0: let logFunc = gLogger; michael@0: if (Config.TEST && typeof Components != "undefined") { michael@0: // If _TESTING_LOGGING is set, and if we are on the main thread, michael@0: // redirect logs to Services.console, for testing purposes michael@0: logFunc = function logFunc(...args) { michael@0: let message = ["TEST", "OS"].concat(args).join(" "); michael@0: Services.console.logStringMessage(message + "\n"); michael@0: }; michael@0: } michael@0: logFunc.apply(null, [stringifyArg(arg) for (arg of args)]); michael@0: }; michael@0: michael@0: exports.LOG = LOG; michael@0: michael@0: /** michael@0: * Return a shallow clone of the enumerable properties of an object. michael@0: * michael@0: * Utility used whenever normalizing options requires making (shallow) michael@0: * changes to an option object. The copy ensures that we do not modify michael@0: * a client-provided object by accident. michael@0: * michael@0: * Note: to reference and not copy specific fields, provide an optional michael@0: * |refs| argument containing their names. michael@0: * michael@0: * @param {JSON} object Options to be cloned. michael@0: * @param {Array} refs An optional array of field names to be passed by michael@0: * reference instead of copying. michael@0: */ michael@0: let clone = function (object, refs = []) { michael@0: let result = {}; michael@0: // Make a reference between result[key] and object[key]. michael@0: let refer = function refer(result, key, object) { michael@0: Object.defineProperty(result, key, { michael@0: enumerable: true, michael@0: get: function() { michael@0: return object[key]; michael@0: }, michael@0: set: function(value) { michael@0: object[key] = value; michael@0: } michael@0: }); michael@0: }; michael@0: for (let k in object) { michael@0: if (refs.indexOf(k) < 0) { michael@0: result[k] = object[k]; michael@0: } else { michael@0: refer(result, k, object); michael@0: } michael@0: } michael@0: return result; michael@0: }; michael@0: michael@0: exports.clone = clone; michael@0: michael@0: ///////////////////// Abstractions above js-ctypes michael@0: michael@0: /** michael@0: * Abstraction above js-ctypes types. michael@0: * michael@0: * Use values of this type to register FFI functions. In addition to the michael@0: * usual features of js-ctypes, values of this type perform the necessary michael@0: * transformations to ensure that C errors are handled nicely, to connect michael@0: * resources with their finalizer, etc. michael@0: * michael@0: * @param {string} name The name of the type. Must be unique. michael@0: * @param {CType} implementation The js-ctypes implementation of the type. michael@0: * michael@0: * @constructor michael@0: */ michael@0: function Type(name, implementation) { michael@0: if (!(typeof name == "string")) { michael@0: throw new TypeError("Type expects as first argument a name, got: " michael@0: + name); michael@0: } michael@0: if (!(implementation instanceof ctypes.CType)) { michael@0: throw new TypeError("Type expects as second argument a ctypes.CType"+ michael@0: ", got: " + implementation); michael@0: } michael@0: Object.defineProperty(this, "name", { value: name }); michael@0: Object.defineProperty(this, "implementation", { value: implementation }); michael@0: } michael@0: Type.prototype = { michael@0: /** michael@0: * Serialize a value of |this| |Type| into a format that can michael@0: * be transmitted as a message (not necessarily a string). michael@0: * michael@0: * In the default implementation, the method returns the michael@0: * value unchanged. michael@0: */ michael@0: toMsg: function default_toMsg(value) { michael@0: return value; michael@0: }, michael@0: /** michael@0: * Deserialize a message to a value of |this| |Type|. michael@0: * michael@0: * In the default implementation, the method returns the michael@0: * message unchanged. michael@0: */ michael@0: fromMsg: function default_fromMsg(msg) { michael@0: return msg; michael@0: }, michael@0: /** michael@0: * Import a value from C. michael@0: * michael@0: * In this default implementation, return the value michael@0: * unchanged. michael@0: */ michael@0: importFromC: function default_importFromC(value) { michael@0: return value; michael@0: }, michael@0: michael@0: /** michael@0: * A pointer/array used to pass data to the foreign function. michael@0: */ michael@0: get in_ptr() { michael@0: delete this.in_ptr; michael@0: let ptr_t = new PtrType( michael@0: "[in] " + this.name + "*", michael@0: this.implementation.ptr, michael@0: this); michael@0: Object.defineProperty(this, "in_ptr", michael@0: { michael@0: get: function() { michael@0: return ptr_t; michael@0: } michael@0: }); michael@0: return ptr_t; michael@0: }, michael@0: michael@0: /** michael@0: * A pointer/array used to receive data from the foreign function. michael@0: */ michael@0: get out_ptr() { michael@0: delete this.out_ptr; michael@0: let ptr_t = new PtrType( michael@0: "[out] " + this.name + "*", michael@0: this.implementation.ptr, michael@0: this); michael@0: Object.defineProperty(this, "out_ptr", michael@0: { michael@0: get: function() { michael@0: return ptr_t; michael@0: } michael@0: }); michael@0: return ptr_t; michael@0: }, michael@0: michael@0: /** michael@0: * A pointer/array used to both pass data to the foreign function michael@0: * and receive data from the foreign function. michael@0: * michael@0: * Whenever possible, prefer using |in_ptr| or |out_ptr|, which michael@0: * are generally faster. michael@0: */ michael@0: get inout_ptr() { michael@0: delete this.inout_ptr; michael@0: let ptr_t = new PtrType( michael@0: "[inout] " + this.name + "*", michael@0: this.implementation.ptr, michael@0: this); michael@0: Object.defineProperty(this, "inout_ptr", michael@0: { michael@0: get: function() { michael@0: return ptr_t; michael@0: } michael@0: }); michael@0: return ptr_t; michael@0: }, michael@0: michael@0: /** michael@0: * Attach a finalizer to a type. michael@0: */ michael@0: releaseWith: function releaseWith(finalizer) { michael@0: let parent = this; michael@0: let type = this.withName("[auto " + this.name + ", " + finalizer + "] "); michael@0: type.importFromC = function importFromC(value, operation) { michael@0: return ctypes.CDataFinalizer( michael@0: parent.importFromC(value, operation), michael@0: finalizer); michael@0: }; michael@0: return type; michael@0: }, michael@0: michael@0: /** michael@0: * Lazy variant of releaseWith. michael@0: * Attach a finalizer lazily to a type. michael@0: * michael@0: * @param {function} getFinalizer The function that michael@0: * returns finalizer lazily. michael@0: */ michael@0: releaseWithLazy: function releaseWithLazy(getFinalizer) { michael@0: let parent = this; michael@0: let type = this.withName("[auto " + this.name + ", (lazy)] "); michael@0: type.importFromC = function importFromC(value, operation) { michael@0: return ctypes.CDataFinalizer( michael@0: parent.importFromC(value, operation), michael@0: getFinalizer()); michael@0: }; michael@0: return type; michael@0: }, michael@0: michael@0: /** michael@0: * Return an alias to a type with a different name. michael@0: */ michael@0: withName: function withName(name) { michael@0: return Object.create(this, {name: {value: name}}); michael@0: }, michael@0: michael@0: /** michael@0: * Cast a C value to |this| type. michael@0: * michael@0: * Throw an error if the value cannot be casted. michael@0: */ michael@0: cast: function cast(value) { michael@0: return ctypes.cast(value, this.implementation); michael@0: }, michael@0: michael@0: /** michael@0: * Return the number of bytes in a value of |this| type. michael@0: * michael@0: * This may not be defined, e.g. for |void_t|, array types michael@0: * without length, etc. michael@0: */ michael@0: get size() { michael@0: return this.implementation.size; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Utility function used to determine whether an object is a typed array michael@0: */ michael@0: let isTypedArray = function isTypedArray(obj) { michael@0: return typeof obj == "object" michael@0: && "byteOffset" in obj; michael@0: }; michael@0: exports.isTypedArray = isTypedArray; michael@0: michael@0: /** michael@0: * A |Type| of pointers. michael@0: * michael@0: * @param {string} name The name of this type. michael@0: * @param {CType} implementation The type of this pointer. michael@0: * @param {Type} targetType The target type. michael@0: */ michael@0: function PtrType(name, implementation, targetType) { michael@0: Type.call(this, name, implementation); michael@0: if (targetType == null || !targetType instanceof Type) { michael@0: throw new TypeError("targetType must be an instance of Type"); michael@0: } michael@0: /** michael@0: * The type of values targeted by this pointer type. michael@0: */ michael@0: Object.defineProperty(this, "targetType", { michael@0: value: targetType michael@0: }); michael@0: } michael@0: PtrType.prototype = Object.create(Type.prototype); michael@0: michael@0: /** michael@0: * Convert a value to a pointer. michael@0: * michael@0: * Protocol: michael@0: * - |null| returns |null| michael@0: * - a string returns |{string: value}| michael@0: * - a typed array returns |{ptr: address_of_buffer}| michael@0: * - a C array returns |{ptr: address_of_buffer}| michael@0: * everything else raises an error michael@0: */ michael@0: PtrType.prototype.toMsg = function ptr_toMsg(value) { michael@0: if (value == null) { michael@0: return null; michael@0: } michael@0: if (typeof value == "string") { michael@0: return { string: value }; michael@0: } michael@0: let normalized; michael@0: if (isTypedArray(value)) { // Typed array michael@0: normalized = Type.uint8_t.in_ptr.implementation(value.buffer); michael@0: if (value.byteOffset != 0) { michael@0: normalized = offsetBy(normalized, value.byteOffset); michael@0: } michael@0: } else if ("addressOfElement" in value) { // C array michael@0: normalized = value.addressOfElement(0); michael@0: } else if ("isNull" in value) { // C pointer michael@0: normalized = value; michael@0: } else { michael@0: throw new TypeError("Value " + value + michael@0: " cannot be converted to a pointer"); michael@0: } michael@0: let cast = Type.uintptr_t.cast(normalized); michael@0: return {ptr: cast.value.toString()}; michael@0: }; michael@0: michael@0: /** michael@0: * Convert a message back to a pointer. michael@0: */ michael@0: PtrType.prototype.fromMsg = function ptr_fromMsg(msg) { michael@0: if (msg == null) { michael@0: return null; michael@0: } michael@0: if ("string" in msg) { michael@0: return msg.string; michael@0: } michael@0: if ("ptr" in msg) { michael@0: let address = ctypes.uintptr_t(msg.ptr); michael@0: return this.cast(address); michael@0: } michael@0: throw new TypeError("Message " + msg.toSource() + michael@0: " does not represent a pointer"); michael@0: }; michael@0: michael@0: exports.Type = Type; michael@0: michael@0: michael@0: /* michael@0: * Some values are large integers on 64 bit platforms. Unfortunately, michael@0: * in practice, 64 bit integers cannot be manipulated in JS. We michael@0: * therefore project them to regular numbers whenever possible. michael@0: */ michael@0: michael@0: let projectLargeInt = function projectLargeInt(x) { michael@0: let str = x.toString(); michael@0: let rv = parseInt(str, 10); michael@0: if (rv.toString() !== str) { michael@0: throw new TypeError("Number " + str + " cannot be projected to a double"); michael@0: } michael@0: return rv; michael@0: }; michael@0: let projectLargeUInt = function projectLargeUInt(x) { michael@0: return projectLargeInt(x); michael@0: }; michael@0: let projectValue = function projectValue(x) { michael@0: if (!(x instanceof ctypes.CData)) { michael@0: return x; michael@0: } michael@0: if (!("value" in x)) { // Sanity check michael@0: throw new TypeError("Number " + x.toSource() + " has no field |value|"); michael@0: } michael@0: return x.value; michael@0: }; michael@0: michael@0: function projector(type, signed) { michael@0: LOG("Determining best projection for", type, michael@0: "(size: ", type.size, ")", signed?"signed":"unsigned"); michael@0: if (type instanceof Type) { michael@0: type = type.implementation; michael@0: } michael@0: if (!type.size) { michael@0: throw new TypeError("Argument is not a proper C type"); michael@0: } michael@0: // Determine if type is projected to Int64/Uint64 michael@0: if (type.size == 8 // Usual case michael@0: // The following cases have special treatment in js-ctypes michael@0: // Regardless of their size, the value getter returns michael@0: // a Int64/Uint64 michael@0: || type == ctypes.size_t // Special cases michael@0: || type == ctypes.ssize_t michael@0: || type == ctypes.intptr_t michael@0: || type == ctypes.uintptr_t michael@0: || type == ctypes.off_t) { michael@0: if (signed) { michael@0: LOG("Projected as a large signed integer"); michael@0: return projectLargeInt; michael@0: } else { michael@0: LOG("Projected as a large unsigned integer"); michael@0: return projectLargeUInt; michael@0: } michael@0: } michael@0: LOG("Projected as a regular number"); michael@0: return projectValue; michael@0: }; michael@0: exports.projectValue = projectValue; michael@0: michael@0: /** michael@0: * Get the appropriate type for an unsigned int of the given size. michael@0: * michael@0: * This function is useful to define types such as |mode_t| whose michael@0: * actual width depends on the OS/platform. michael@0: * michael@0: * @param {number} size The number of bytes requested. michael@0: */ michael@0: Type.uintn_t = function uintn_t(size) { michael@0: switch (size) { michael@0: case 1: return Type.uint8_t; michael@0: case 2: return Type.uint16_t; michael@0: case 4: return Type.uint32_t; michael@0: case 8: return Type.uint64_t; michael@0: default: michael@0: throw new Error("Cannot represent unsigned integers of " + size + " bytes"); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Get the appropriate type for an signed int of the given size. michael@0: * michael@0: * This function is useful to define types such as |mode_t| whose michael@0: * actual width depends on the OS/platform. michael@0: * michael@0: * @param {number} size The number of bytes requested. michael@0: */ michael@0: Type.intn_t = function intn_t(size) { michael@0: switch (size) { michael@0: case 1: return Type.int8_t; michael@0: case 2: return Type.int16_t; michael@0: case 4: return Type.int32_t; michael@0: case 8: return Type.int64_t; michael@0: default: michael@0: throw new Error("Cannot represent integers of " + size + " bytes"); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Actual implementation of common C types. michael@0: */ michael@0: michael@0: /** michael@0: * The void value. michael@0: */ michael@0: Type.void_t = michael@0: new Type("void", michael@0: ctypes.void_t); michael@0: michael@0: /** michael@0: * Shortcut for |void*|. michael@0: */ michael@0: Type.voidptr_t = michael@0: new PtrType("void*", michael@0: ctypes.voidptr_t, michael@0: Type.void_t); michael@0: michael@0: // void* is a special case as we can cast any pointer to/from it michael@0: // so we have to shortcut |in_ptr|/|out_ptr|/|inout_ptr| and michael@0: // ensure that js-ctypes' casting mechanism is invoked directly michael@0: ["in_ptr", "out_ptr", "inout_ptr"].forEach(function(key) { michael@0: Object.defineProperty(Type.void_t, key, michael@0: { michael@0: value: Type.voidptr_t michael@0: }); michael@0: }); michael@0: michael@0: /** michael@0: * A Type of integers. michael@0: * michael@0: * @param {string} name The name of this type. michael@0: * @param {CType} implementation The underlying js-ctypes implementation. michael@0: * @param {bool} signed |true| if this is a type of signed integers, michael@0: * |false| otherwise. michael@0: * michael@0: * @constructor michael@0: */ michael@0: function IntType(name, implementation, signed) { michael@0: Type.call(this, name, implementation); michael@0: this.importFromC = projector(implementation, signed); michael@0: this.project = this.importFromC; michael@0: }; michael@0: IntType.prototype = Object.create(Type.prototype); michael@0: IntType.prototype.toMsg = function toMsg(value) { michael@0: if (typeof value == "number") { michael@0: return value; michael@0: } michael@0: return this.project(value); michael@0: }; michael@0: michael@0: /** michael@0: * A C char (one byte) michael@0: */ michael@0: Type.char = michael@0: new Type("char", michael@0: ctypes.char); michael@0: michael@0: /** michael@0: * A C wide char (two bytes) michael@0: */ michael@0: Type.jschar = michael@0: new Type("jschar", michael@0: ctypes.jschar); michael@0: michael@0: /** michael@0: * Base string types. michael@0: */ michael@0: Type.cstring = Type.char.in_ptr.withName("[in] C string"); michael@0: Type.wstring = Type.jschar.in_ptr.withName("[in] wide string"); michael@0: Type.out_cstring = Type.char.out_ptr.withName("[out] C string"); michael@0: Type.out_wstring = Type.jschar.out_ptr.withName("[out] wide string"); michael@0: michael@0: /** michael@0: * A C integer (8-bits). michael@0: */ michael@0: Type.int8_t = michael@0: new IntType("int8_t", ctypes.int8_t, true); michael@0: michael@0: Type.uint8_t = michael@0: new IntType("uint8_t", ctypes.uint8_t, false); michael@0: michael@0: /** michael@0: * A C integer (16-bits). michael@0: * michael@0: * Also known as WORD under Windows. michael@0: */ michael@0: Type.int16_t = michael@0: new IntType("int16_t", ctypes.int16_t, true); michael@0: michael@0: Type.uint16_t = michael@0: new IntType("uint16_t", ctypes.uint16_t, false); michael@0: michael@0: /** michael@0: * A C integer (32-bits). michael@0: * michael@0: * Also known as DWORD under Windows. michael@0: */ michael@0: Type.int32_t = michael@0: new IntType("int32_t", ctypes.int32_t, true); michael@0: michael@0: Type.uint32_t = michael@0: new IntType("uint32_t", ctypes.uint32_t, false); michael@0: michael@0: /** michael@0: * A C integer (64-bits). michael@0: */ michael@0: Type.int64_t = michael@0: new IntType("int64_t", ctypes.int64_t, true); michael@0: michael@0: Type.uint64_t = michael@0: new IntType("uint64_t", ctypes.uint64_t, false); michael@0: michael@0: /** michael@0: * A C integer michael@0: * michael@0: * Size depends on the platform. michael@0: */ michael@0: Type.int = Type.intn_t(ctypes.int.size). michael@0: withName("int"); michael@0: michael@0: Type.unsigned_int = Type.intn_t(ctypes.unsigned_int.size). michael@0: withName("unsigned int"); michael@0: michael@0: /** michael@0: * A C long integer. michael@0: * michael@0: * Size depends on the platform. michael@0: */ michael@0: Type.long = michael@0: Type.intn_t(ctypes.long.size).withName("long"); michael@0: michael@0: Type.unsigned_long = michael@0: Type.intn_t(ctypes.unsigned_long.size).withName("unsigned long"); michael@0: michael@0: /** michael@0: * An unsigned integer with the same size as a pointer. michael@0: * michael@0: * Used to cast a pointer to an integer, whenever necessary. michael@0: */ michael@0: Type.uintptr_t = michael@0: Type.uintn_t(ctypes.uintptr_t.size).withName("uintptr_t"); michael@0: michael@0: /** michael@0: * A boolean. michael@0: * Implemented as a C integer. michael@0: */ michael@0: Type.bool = Type.int.withName("bool"); michael@0: Type.bool.importFromC = function projectBool(x) { michael@0: return !!(x.value); michael@0: }; michael@0: michael@0: /** michael@0: * A user identifier. michael@0: * michael@0: * Implemented as a C integer. michael@0: */ michael@0: Type.uid_t = michael@0: Type.int.withName("uid_t"); michael@0: michael@0: /** michael@0: * A group identifier. michael@0: * michael@0: * Implemented as a C integer. michael@0: */ michael@0: Type.gid_t = michael@0: Type.int.withName("gid_t"); michael@0: michael@0: /** michael@0: * An offset (positive or negative). michael@0: * michael@0: * Implemented as a C integer. michael@0: */ michael@0: Type.off_t = michael@0: new IntType("off_t", ctypes.off_t, true); michael@0: michael@0: /** michael@0: * A size (positive). michael@0: * michael@0: * Implemented as a C size_t. michael@0: */ michael@0: Type.size_t = michael@0: new IntType("size_t", ctypes.size_t, false); michael@0: michael@0: /** michael@0: * An offset (positive or negative). michael@0: * Implemented as a C integer. michael@0: */ michael@0: Type.ssize_t = michael@0: new IntType("ssize_t", ctypes.ssize_t, true); michael@0: michael@0: /** michael@0: * Encoding/decoding strings michael@0: */ michael@0: Type.uencoder = michael@0: new Type("uencoder", ctypes.StructType("uencoder")); michael@0: Type.udecoder = michael@0: new Type("udecoder", ctypes.StructType("udecoder")); michael@0: michael@0: /** michael@0: * Utility class, used to build a |struct| type michael@0: * from a set of field names, types and offsets. michael@0: * michael@0: * @param {string} name The name of the |struct| type. michael@0: * @param {number} size The total size of the |struct| type in bytes. michael@0: */ michael@0: function HollowStructure(name, size) { michael@0: if (!name) { michael@0: throw new TypeError("HollowStructure expects a name"); michael@0: } michael@0: if (!size || size < 0) { michael@0: throw new TypeError("HollowStructure expects a (positive) size"); michael@0: } michael@0: michael@0: // A mapping from offsets in the struct to name/type pairs michael@0: // (or nothing if no field starts at that offset). michael@0: this.offset_to_field_info = []; michael@0: michael@0: // The name of the struct michael@0: this.name = name; michael@0: michael@0: // The size of the struct, in bytes michael@0: this.size = size; michael@0: michael@0: // The number of paddings inserted so far. michael@0: // Used to give distinct names to padding fields. michael@0: this._paddings = 0; michael@0: } michael@0: HollowStructure.prototype = { michael@0: /** michael@0: * Add a field at a given offset. michael@0: * michael@0: * @param {number} offset The offset at which to insert the field. michael@0: * @param {string} name The name of the field. michael@0: * @param {CType|Type} type The type of the field. michael@0: */ michael@0: add_field_at: function add_field_at(offset, name, type) { michael@0: if (offset == null) { michael@0: throw new TypeError("add_field_at requires a non-null offset"); michael@0: } michael@0: if (!name) { michael@0: throw new TypeError("add_field_at requires a non-null name"); michael@0: } michael@0: if (!type) { michael@0: throw new TypeError("add_field_at requires a non-null type"); michael@0: } michael@0: if (type instanceof Type) { michael@0: type = type.implementation; michael@0: } michael@0: if (this.offset_to_field_info[offset]) { michael@0: throw new Error("HollowStructure " + this.name + michael@0: " already has a field at offset " + offset); michael@0: } michael@0: if (offset + type.size > this.size) { michael@0: throw new Error("HollowStructure " + this.name + michael@0: " cannot place a value of type " + type + michael@0: " at offset " + offset + michael@0: " without exceeding its size of " + this.size); michael@0: } michael@0: let field = {name: name, type:type}; michael@0: this.offset_to_field_info[offset] = field; michael@0: }, michael@0: michael@0: /** michael@0: * Create a pseudo-field that will only serve as padding. michael@0: * michael@0: * @param {number} size The number of bytes in the field. michael@0: * @return {Object} An association field-name => field-type, michael@0: * as expected by |ctypes.StructType|. michael@0: */ michael@0: _makePaddingField: function makePaddingField(size) { michael@0: let field = ({}); michael@0: field["padding_" + this._paddings] = michael@0: ctypes.ArrayType(ctypes.uint8_t, size); michael@0: this._paddings++; michael@0: return field; michael@0: }, michael@0: michael@0: /** michael@0: * Convert this |HollowStructure| into a |Type|. michael@0: */ michael@0: getType: function getType() { michael@0: // Contents of the structure, in the format expected michael@0: // by ctypes.StructType. michael@0: let struct = []; michael@0: michael@0: let i = 0; michael@0: while (i < this.size) { michael@0: let currentField = this.offset_to_field_info[i]; michael@0: if (!currentField) { michael@0: // No field was specified at this offset, we need to michael@0: // introduce some padding. michael@0: michael@0: // Firstly, determine how many bytes of padding michael@0: let padding_length = 1; michael@0: while (i + padding_length < this.size michael@0: && !this.offset_to_field_info[i + padding_length]) { michael@0: ++padding_length; michael@0: } michael@0: michael@0: // Then add the padding michael@0: struct.push(this._makePaddingField(padding_length)); michael@0: michael@0: // And proceed michael@0: i += padding_length; michael@0: } else { michael@0: // We have a field at this offset. michael@0: michael@0: // Firstly, ensure that we do not have two overlapping fields michael@0: for (let j = 1; j < currentField.type.size; ++j) { michael@0: let candidateField = this.offset_to_field_info[i + j]; michael@0: if (candidateField) { michael@0: throw new Error("Fields " + currentField.name + michael@0: " and " + candidateField.name + michael@0: " overlap at position " + (i + j)); michael@0: } michael@0: } michael@0: michael@0: // Then add the field michael@0: let field = ({}); michael@0: field[currentField.name] = currentField.type; michael@0: struct.push(field); michael@0: michael@0: // And proceed michael@0: i += currentField.type.size; michael@0: } michael@0: } michael@0: let result = new Type(this.name, ctypes.StructType(this.name, struct)); michael@0: if (result.implementation.size != this.size) { michael@0: throw new Error("Wrong size for type " + this.name + michael@0: ": expected " + this.size + michael@0: ", found " + result.implementation.size + michael@0: " (" + result.implementation.toSource() + ")"); michael@0: } michael@0: return result; michael@0: } michael@0: }; michael@0: exports.HollowStructure = HollowStructure; michael@0: michael@0: /** michael@0: * Representation of a native library. michael@0: * michael@0: * The native library is opened lazily, during the first call to its michael@0: * field |library| or whenever accessing one of the methods imported michael@0: * with declareLazyFFI. michael@0: * michael@0: * @param {string} name A human-readable name for the library. Used michael@0: * for debugging and error reporting. michael@0: * @param {string...} candidates A list of system libraries that may michael@0: * represent this library. Used e.g. to try different library names michael@0: * on distinct operating systems ("libxul", "XUL", etc.). michael@0: * michael@0: * @constructor michael@0: */ michael@0: function Library(name, ...candidates) { michael@0: this.name = name; michael@0: this._candidates = candidates; michael@0: }; michael@0: Library.prototype = Object.freeze({ michael@0: /** michael@0: * The native library as a js-ctypes object. michael@0: * michael@0: * @throws {Error} If none of the candidate libraries could be opened. michael@0: */ michael@0: get library() { michael@0: let library; michael@0: delete this.library; michael@0: for (let candidate of this._candidates) { michael@0: try { michael@0: library = ctypes.open(candidate); michael@0: break; michael@0: } catch (ex) { michael@0: LOG("Could not open library", candidate, ex); michael@0: } michael@0: } michael@0: this._candidates = null; michael@0: if (library) { michael@0: Object.defineProperty(this, "library", { michael@0: value: library michael@0: }); michael@0: Object.freeze(this); michael@0: return library; michael@0: } michael@0: let error = new Error("Could not open library " + this.name); michael@0: Object.defineProperty(this, "library", { michael@0: get: function() { michael@0: throw error; michael@0: } michael@0: }); michael@0: Object.freeze(this); michael@0: throw error; michael@0: }, michael@0: michael@0: /** michael@0: * Declare a function, lazily. michael@0: * michael@0: * @param {object} The object containing the function as a field. michael@0: * @param {string} The name of the field containing the function. michael@0: * @param {string} symbol The name of the function, as defined in the michael@0: * library. michael@0: * @param {ctypes.abi} abi The abi to use, or |null| for default. michael@0: * @param {Type} returnType The type of values returned by the function. michael@0: * @param {...Type} argTypes The type of arguments to the function. michael@0: */ michael@0: declareLazyFFI: function(object, field, ...args) { michael@0: let lib = this; michael@0: Object.defineProperty(object, field, { michael@0: get: function() { michael@0: delete this[field]; michael@0: let ffi = declareFFI(lib.library, ...args); michael@0: if (ffi) { michael@0: return this[field] = ffi; michael@0: } michael@0: return undefined; michael@0: }, michael@0: configurable: true, michael@0: enumerable: true michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Define a js-ctypes function lazily using ctypes method declare. michael@0: * michael@0: * @param {object} The object containing the function as a field. michael@0: * @param {string} The name of the field containing the function. michael@0: * @param {string} symbol The name of the function, as defined in the michael@0: * library. michael@0: * @param {ctypes.abi} abi The abi to use, or |null| for default. michael@0: * @param {ctypes.CType} returnType The type of values returned by the function. michael@0: * @param {...ctypes.CType} argTypes The type of arguments to the function. michael@0: */ michael@0: declareLazy: function(object, field, ...args) { michael@0: let lib = this; michael@0: Object.defineProperty(object, field, { michael@0: get: function() { michael@0: delete this[field]; michael@0: let ffi = lib.library.declare(...args); michael@0: if (ffi) { michael@0: return this[field] = ffi; michael@0: } michael@0: return undefined; michael@0: }, michael@0: configurable: true, michael@0: enumerable: true michael@0: }); michael@0: }, michael@0: michael@0: toString: function() { michael@0: return "[Library " + this.name + "]"; michael@0: } michael@0: }); michael@0: exports.Library = Library; michael@0: michael@0: /** michael@0: * Declare a function through js-ctypes michael@0: * michael@0: * @param {ctypes.library} lib The ctypes library holding the function. michael@0: * @param {string} symbol The name of the function, as defined in the michael@0: * library. michael@0: * @param {ctypes.abi} abi The abi to use, or |null| for default. michael@0: * @param {Type} returnType The type of values returned by the function. michael@0: * @param {...Type} argTypes The type of arguments to the function. michael@0: * michael@0: * @return null if the function could not be defined (generally because michael@0: * it does not exist), or a JavaScript wrapper performing the call to C michael@0: * and any type conversion required. michael@0: */ michael@0: let declareFFI = function declareFFI(lib, symbol, abi, michael@0: returnType /*, argTypes ...*/) { michael@0: LOG("Attempting to declare FFI ", symbol); michael@0: // We guard agressively, to avoid any late surprise michael@0: if (typeof symbol != "string") { michael@0: throw new TypeError("declareFFI expects as first argument a string"); michael@0: } michael@0: abi = abi || ctypes.default_abi; michael@0: if (Object.prototype.toString.call(abi) != "[object CABI]") { michael@0: // Note: This is the only known manner of checking whether an object michael@0: // is an abi. michael@0: throw new TypeError("declareFFI expects as second argument an abi or null"); michael@0: } michael@0: if (!returnType.importFromC) { michael@0: throw new TypeError("declareFFI expects as third argument an instance of Type"); michael@0: } michael@0: let signature = [symbol, abi]; michael@0: let argtypes = []; michael@0: for (let i = 3; i < arguments.length; ++i) { michael@0: let current = arguments[i]; michael@0: if (!current) { michael@0: throw new TypeError("Missing type for argument " + ( i - 3 ) + michael@0: " of symbol " + symbol); michael@0: } michael@0: if (!current.implementation) { michael@0: throw new TypeError("Missing implementation for argument " + (i - 3) michael@0: + " of symbol " + symbol michael@0: + " ( " + current.name + " )" ); michael@0: } michael@0: signature.push(current.implementation); michael@0: } michael@0: try { michael@0: let fun = lib.declare.apply(lib, signature); michael@0: let result = function ffi(...args) { michael@0: for (let i = 0; i < args.length; i++) { michael@0: if (typeof args[i] == "undefined") { michael@0: throw new TypeError("Argument " + i + " of " + symbol + " is undefined"); michael@0: } michael@0: } michael@0: let result = fun.apply(fun, args); michael@0: return returnType.importFromC(result, symbol); michael@0: }; michael@0: LOG("Function", symbol, "declared"); michael@0: return result; michael@0: } catch (x) { michael@0: // Note: Not being able to declare a function is normal. michael@0: // Some functions are OS (or OS version)-specific. michael@0: LOG("Could not declare function ", symbol, x); michael@0: return null; michael@0: } michael@0: }; michael@0: exports.declareFFI = declareFFI; michael@0: michael@0: /** michael@0: * Define a lazy getter to a js-ctypes function using declareFFI. michael@0: * michael@0: * @param {object} The object containing the function as a field. michael@0: * @param {string} The name of the field containing the function. michael@0: * @param {ctypes.library} lib The ctypes library holding the function. michael@0: * @param {string} symbol The name of the function, as defined in the michael@0: * library. michael@0: * @param {ctypes.abi} abi The abi to use, or |null| for default. michael@0: * @param {Type} returnType The type of values returned by the function. michael@0: * @param {...Type} argTypes The type of arguments to the function. michael@0: */ michael@0: function declareLazyFFI(object, field, ...declareFFIArgs) { michael@0: Object.defineProperty(object, field, { michael@0: get: function() { michael@0: delete this[field]; michael@0: let ffi = declareFFI(...declareFFIArgs); michael@0: if (ffi) { michael@0: return this[field] = ffi; michael@0: } michael@0: return undefined; michael@0: }, michael@0: configurable: true, michael@0: enumerable: true michael@0: }); michael@0: } michael@0: exports.declareLazyFFI = declareLazyFFI; michael@0: michael@0: /** michael@0: * Define a lazy getter to a js-ctypes function using ctypes method declare. michael@0: * michael@0: * @param {object} The object containing the function as a field. michael@0: * @param {string} The name of the field containing the function. michael@0: * @param {ctypes.library} lib The ctypes library holding the function. michael@0: * @param {string} symbol The name of the function, as defined in the michael@0: * library. michael@0: * @param {ctypes.abi} abi The abi to use, or |null| for default. michael@0: * @param {ctypes.CType} returnType The type of values returned by the function. michael@0: * @param {...ctypes.CType} argTypes The type of arguments to the function. michael@0: */ michael@0: function declareLazy(object, field, lib, ...declareArgs) { michael@0: Object.defineProperty(object, field, { michael@0: get: function() { michael@0: delete this[field]; michael@0: try { michael@0: let ffi = lib.declare(...declareArgs); michael@0: return this[field] = ffi; michael@0: } catch (ex) { michael@0: // The symbol doesn't exist michael@0: return undefined; michael@0: } michael@0: }, michael@0: configurable: true michael@0: }); michael@0: } michael@0: exports.declareLazy = declareLazy; michael@0: michael@0: // A bogus array type used to perform pointer arithmetics michael@0: let gOffsetByType; michael@0: michael@0: /** michael@0: * Advance a pointer by a number of items. michael@0: * michael@0: * This method implements adding an integer to a pointer in C. michael@0: * michael@0: * Example: michael@0: * // ptr is a uint16_t*, michael@0: * offsetBy(ptr, 3) michael@0: * // returns a uint16_t* with the address ptr + 3 * 2 bytes michael@0: * michael@0: * @param {C pointer} pointer The start pointer. michael@0: * @param {number} length The number of items to advance. Must not be michael@0: * negative. michael@0: * michael@0: * @return {C pointer} |pointer| advanced by |length| items michael@0: */ michael@0: let offsetBy = michael@0: function offsetBy(pointer, length) { michael@0: if (length === undefined || length < 0) { michael@0: throw new TypeError("offsetBy expects a positive number"); michael@0: } michael@0: if (!("isNull" in pointer)) { michael@0: throw new TypeError("offsetBy expects a pointer"); michael@0: } michael@0: if (length == 0) { michael@0: return pointer; michael@0: } michael@0: let type = pointer.constructor; michael@0: let size = type.targetType.size; michael@0: if (size == 0 || size == null) { michael@0: throw new TypeError("offsetBy cannot be applied to a pointer without size"); michael@0: } michael@0: let bytes = length * size; michael@0: if (!gOffsetByType || gOffsetByType.size <= bytes) { michael@0: gOffsetByType = ctypes.uint8_t.array(bytes * 2); michael@0: } michael@0: let addr = ctypes.cast(pointer, gOffsetByType.ptr). michael@0: contents.addressOfElement(bytes); michael@0: return ctypes.cast(addr, type); michael@0: }; michael@0: exports.offsetBy = offsetBy; michael@0: michael@0: /** michael@0: * Utility function used to normalize a Typed Array or C michael@0: * pointer into a uint8_t C pointer. michael@0: * michael@0: * Future versions might extend this to other data structures. michael@0: * michael@0: * @param {Typed array | C pointer} candidate The buffer. If michael@0: * a C pointer, it must be non-null. michael@0: * @param {number} bytes The number of bytes that |candidate| should contain. michael@0: * Used for sanity checking if the size of |candidate| can be determined. michael@0: * michael@0: * @return {ptr:{C pointer}, bytes:number} A C pointer of type uint8_t, michael@0: * corresponding to the start of |candidate|. michael@0: */ michael@0: function normalizeToPointer(candidate, bytes) { michael@0: if (!candidate) { michael@0: throw new TypeError("Expecting a Typed Array or a C pointer"); michael@0: } michael@0: let ptr; michael@0: if ("isNull" in candidate) { michael@0: if (candidate.isNull()) { michael@0: throw new TypeError("Expecting a non-null pointer"); michael@0: } michael@0: ptr = Type.uint8_t.out_ptr.cast(candidate); michael@0: if (bytes == null) { michael@0: throw new TypeError("C pointer missing bytes indication."); michael@0: } michael@0: } else if (isTypedArray(candidate)) { michael@0: // Typed Array michael@0: ptr = Type.uint8_t.out_ptr.implementation(candidate.buffer); michael@0: if (bytes == null) { michael@0: bytes = candidate.byteLength; michael@0: } else if (candidate.byteLength < bytes) { michael@0: throw new TypeError("Buffer is too short. I need at least " + michael@0: bytes + michael@0: " bytes but I have only " + michael@0: candidate.byteLength + michael@0: "bytes"); michael@0: } michael@0: } else { michael@0: throw new TypeError("Expecting a Typed Array or a C pointer"); michael@0: } michael@0: return {ptr: ptr, bytes: bytes}; michael@0: }; michael@0: exports.normalizeToPointer = normalizeToPointer; michael@0: michael@0: ///////////////////// OS interactions michael@0: michael@0: /** michael@0: * An OS error. michael@0: * michael@0: * This class is provided mostly for type-matching. If you need more michael@0: * details about an error, you should use the platform-specific error michael@0: * codes provided by subclasses of |OS.Shared.Error|. michael@0: * michael@0: * @param {string} operation The operation that failed. michael@0: * @param {string=} path The path of the file on which the operation failed, michael@0: * or nothing if there was no file involved in the failure. michael@0: * michael@0: * @constructor michael@0: */ michael@0: function OSError(operation, path = "") { michael@0: Error.call(this); michael@0: this.operation = operation; michael@0: this.path = path; michael@0: } michael@0: exports.OSError = OSError; michael@0: michael@0: michael@0: ///////////////////// Temporary boilerplate michael@0: // Boilerplate, to simplify the transition to require() michael@0: // Do not rely upon this symbol, it will disappear with michael@0: // bug 883050. michael@0: exports.OS = { michael@0: Constants: exports.Constants, michael@0: Shared: { michael@0: LOG: LOG, michael@0: clone: clone, michael@0: Type: Type, michael@0: HollowStructure: HollowStructure, michael@0: Error: OSError, michael@0: declareFFI: declareFFI, michael@0: projectValue: projectValue, michael@0: isTypedArray: isTypedArray, michael@0: defineLazyGetter: defineLazyGetter, michael@0: offsetBy: offsetBy michael@0: } michael@0: }; michael@0: michael@0: Object.defineProperty(exports.OS.Shared, "DEBUG", { michael@0: get: function() { michael@0: return Config.DEBUG; michael@0: }, michael@0: set: function(x) { michael@0: return Config.DEBUG = x; michael@0: } michael@0: }); michael@0: Object.defineProperty(exports.OS.Shared, "TEST", { michael@0: get: function() { michael@0: return Config.TEST; michael@0: }, michael@0: set: function(x) { michael@0: return Config.TEST = x; michael@0: } michael@0: }); michael@0: michael@0: michael@0: ///////////////////// Permanent boilerplate michael@0: if (typeof Components != "undefined") { michael@0: this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS; michael@0: for (let symbol of EXPORTED_SYMBOLS) { michael@0: this[symbol] = exports[symbol]; michael@0: } michael@0: }