blob: 681e6d9a3bf0eaee8e060dd915318797c884bd38 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview A collection of utilities for manipulating elements of namespaces (e.g., files)
*/
import * as fdio from 'fdio'
import * as util from './util.js';
fidl.loadLibrary('fuchsia.io');
class Dirent {
/**
* Constructs a Dirent.
* @param {BigInt} ino The inode of the dirent.
* @param {Number} type (a uint8)
* @param {String} name The name of the dirent.
*/
constructor(ino, type, name) {
this.ino = ino;
this.type = type;
this.name = name;
}
}
// A class that provides a more gentle interface when you have a handle to a fuchsia.io.Directory.
class Directory {
/**
* Constructs the Directory object.
* @param {zx.Channel} nodeClientChannel a zx.Channel where the other endpoint is a
* fuchsia.io.Directory.
*/
constructor(nodeClientChannel) {
this._pathClient = new fidl.ProtocolClient(nodeClientChannel, fidling.fuchsia_io.Directory);
}
/**
* Close the underlying channel, if specified when invoking the constructor.
*/
close() {
fidl.ProtocolClient.close(this._pathClient);
}
/**
* Returns a list of Dirent objects representing the contents of the directory.
*/
async readDirEnts() {
let ents = undefined;
let res = [];
while (true) {
ents = await this._pathClient.ReadDirents(fidling.fuchsia_io.MAX_BUF);
if (ents.s != '0') {
throw 'Unable to read directory entries';
}
if (ents.dirents.length == 0) {
break;
}
let buf = new ArrayBuffer(ents.dirents.length);
let view = new DataView(buf);
// First, convert from strings to bytes. fidlcat should really do that for us.
for (let i = 0; i < ents.dirents.length; i++) {
view.setUint8(i, parseInt(ents.dirents[i]));
}
// Then, decode the array values
for (let i = 0; i < ents.dirents.length;) {
let ino = view.getBigInt64(i, true);
i += 8;
let size = view.getUint8(i);
i += 1;
let type = view.getUint8(i);
i += 1;
let name = util.decodeUtf8(new DataView(buf, i, size));
i += size;
let entry = new Dirent(ino, type, name);
res.push(entry);
}
}
let status = await this._pathClient.Rewind();
if (status.s != '0') {
throw 'Unable to rewind directory';
}
return res;
}
// Given an absolute path string, returns a Directory object representing that path.
// If the path string represents a non-directory node, returns null.
static async getDirectoryFor(pathString) {
let rns = fdio.nsExportRoot();
let splitDirString = pathString.split('/');
const rootElements = rns.getElements();
let restOfPath = undefined;
let element = undefined;
let checkDirectory = true;
// The root elements are special, top-level paths. Every path is relative to one.
// So, we have to start looking for the given path from one of the root strings.
// Iterate until we find the right one.
for (let e in rootElements) {
let splitElement = e.split('/');
if (util.arraysEqual(splitDirString, splitElement)) {
restOfPath = '.';
element = e;
// For some reason, waiting for OnOpen below in this code path
// causes memory corruption. We don't really need the check,
// and we're likely to deprecate this code anyway.
checkDirectory = false;
} else if (util.isPrefixOf(splitElement, splitDirString)) {
// We are trying to list something reachable from a root handle.
// element.length + 1 gets rid of the next path separator.
restOfPath = pathString.substring(e.length + 1);
element = e;
}
}
if (typeof element == 'undefined') {
throw 'Node ' + pathString + ' not found';
}
// Each Node (i.e., the thing we want listed) is contained within a root namespace
// For example, if you want to list /bin/ls, it is going to be the ls node inside
// the /bin root element. |element| is the root namespace element.
// rootChannel is a channel to the service providing the root namespace element.
let rootHandle = rootElements[element].handle;
let rootChannel = new zx.Channel(rootHandle);
// dirClient is a wrapper that lets you speak the fuchsia.io.Directory protocol
// to the root namespace.
let dirClient = new fidl.ProtocolClient(rootChannel, fidling.fuchsia_io.Directory);
// When we want to interact with the node within the root namespace (this
// would be "ls" if you are opening "/bin/ls"), you need to create a
// channel to speak to it using the fuchsia.io.Node protocol. The way we
// set up that channel is to call the Open() method of the root namespace
// directory ("/bin"), and send it |restOfPath| ("ls") and one end of a
// channel. We can then communicate via that Node by speaking
// fuchsia.io.Node over the other end of that channel.
// |request| is a wrapper that gives you two ends of a channel.
const request = new fidl.Request(fidling.fuchsia_io.Node);
// |nodeClient| is going to be the end of the channel that we speak Node over.
let nodeClient = request.getProtocolClient();
let openedPromise = undefined;
if (checkDirectory) {
// We can get notified when the Node gets opened.
openedPromise = nodeClient.OnOpen((args) => {
return args;
});
}
// Ask the service providing the root namespace element to open the Node
// we want to inspect.
// TODO(https://fxbug.dev/96495): don't pass strings when this API is more sensible.
let flags = "RIGHT_READABLE";
if (checkDirectory) {
flags += "|DESCRIBE";
}
dirClient.Open(flags, 0, restOfPath, request.getChannelForServer());
if (checkDirectory) {
let args = await openedPromise;
// TODO: check the value of args.s
if ('directory' in args.info) {
return new Directory(nodeClient);
} else {
throw pathString + " is not a directory"
}
} else {
return new Directory(nodeClient);
}
fidl.ProtocolClient.close(request.getProtocolClient());
}
}
/**
* Returns a listing of a directory. Type TBD, but currently an array of Dirents.
*
* @param {String} pathString A path we want listed. (Currently, must be an
* absolute directory. We'll fix that.)
*/
async function ls(pathString) {
let directory = await Directory.getDirectoryFor(pathString);
if (directory == null) {
throw 'Unknown directory';
}
let dirents = await directory.readDirEnts();
directory.close();
return dirents;
}
(async function(global) {
ls('/svc')
.then((svcDir) => {
// Make services available on an object called `svc`
const svc = {};
const svcNames = [];
for (const service of svcDir) {
// serviceName looks like "fuchsia.kernel.RootJob"
const serviceName = service.name;
// Mangle service names to be valid JS identifiers
// proxyName looks like "fuchsia_kernel_RootJob"
const proxyName = serviceName.replace(/\./g, '_');
svcNames.push(proxyName);
const idx = serviceName.lastIndexOf('.');
// name looks like "RootJob"
const name = serviceName.substr(idx + 1);
// libraryName looks like "fuchsia.kernel"
const libraryName = serviceName.substr(0, idx);
// Define a getter that connects to the service
// TODO: should this cache connections until their handles close?
Object.defineProperty(svc, proxyName, {
enumerable: true,
get: () => {
return new fidl.ProtocolClient(
new zx.Channel(fdio.serviceConnect(`/svc/${serviceName}`)),
`${libraryName}/${name}`);
},
});
}
svc[Symbol.for('completions')] = svcNames;
global['svc'] = svc;
})
.catch((e) => {
console.log(e);
console.log(e.stack);
});
})(globalThis);
export {ls};