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