filer/src/filesystem/implementation.js

2441 lines
62 KiB
JavaScript

var Path = require('../path.js');
var normalize = Path.normalize;
var dirname = Path.dirname;
var basename = Path.basename;
var isAbsolutePath = Path.isAbsolute;
var isNullPath = Path.isNull;
var Constants = require('../constants.js');
var NODE_TYPE_FILE = Constants.NODE_TYPE_FILE;
var NODE_TYPE_DIRECTORY = Constants.NODE_TYPE_DIRECTORY;
var NODE_TYPE_SYMBOLIC_LINK = Constants.NODE_TYPE_SYMBOLIC_LINK;
var NODE_TYPE_META = Constants.NODE_TYPE_META;
var FULL_READ_WRITE_EXEC_PERMISSIONS = Constants.FULL_READ_WRITE_EXEC_PERMISSIONS;
var ROOT_DIRECTORY_NAME = Constants.ROOT_DIRECTORY_NAME;
var SUPER_NODE_ID = Constants.SUPER_NODE_ID;
var SYMLOOP_MAX = Constants.SYMLOOP_MAX;
var O_READ = Constants.O_READ;
var O_WRITE = Constants.O_WRITE;
var O_CREATE = Constants.O_CREATE;
var O_EXCLUSIVE = Constants.O_EXCLUSIVE;
var O_APPEND = Constants.O_APPEND;
var O_FLAGS = Constants.O_FLAGS;
var XATTR_CREATE = Constants.XATTR_CREATE;
var XATTR_REPLACE = Constants.XATTR_REPLACE;
var FS_NOMTIME = Constants.FS_NOMTIME;
var FS_NOCTIME = Constants.FS_NOCTIME;
var Encoding = require('../encoding.js');
var Errors = require('../errors.js');
var DirectoryEntry = require('../directory-entry.js');
var OpenFileDescription = require('../open-file-description.js');
var SuperNode = require('../super-node.js');
var Node = require('../node.js');
var Stats = require('../stats.js');
var Buffer = require('../buffer.js');
const { validateInteger } = require('../shared.js');
/**
* Update node times. Only passed times are modified (undefined times are ignored)
* and filesystem flags are examined in order to override update logic.
*/
function update_node_times(context, path, node, times, callback) {
// Honour mount flags for how we update times
var flags = context.flags;
if(flags.includes(FS_NOCTIME)) {
delete times.ctime;
}
if(flags.includes(FS_NOMTIME)) {
delete times.mtime;
}
// Only do the update if required (i.e., times are still present)
var update = false;
if(times.ctime) {
node.ctime = times.ctime;
// We don't do atime tracking for perf reasons, but do mirror ctime
node.atime = times.ctime;
update = true;
}
if(times.atime) {
// The only time we explicitly pass atime is when utimes(), futimes() is called.
// Override ctime mirror here if so
node.atime = times.atime;
update = true;
}
if(times.mtime) {
node.mtime = times.mtime;
update = true;
}
function complete(error) {
// Queue this change so we can send watch events.
// Unlike node.js, we send the full path vs. basename/dirname only.
context.changes.push({ event: 'change', path: path });
callback(error);
}
if(update) {
context.putObject(node.id, node, complete);
} else {
complete();
}
}
/**
* make_node()
*/
// in: file or directory path
// out: new node representing file/directory
function make_node(context, path, type, callback) {
if(type !== NODE_TYPE_DIRECTORY && type !== NODE_TYPE_FILE) {
return callback(new Errors.EINVAL('type must be a directory or file', path));
}
path = normalize(path);
var name = basename(path);
var parentPath = dirname(path);
var parentNode;
var parentNodeData;
var node;
// Check if the parent node exists
function create_node_in_parent(error, parentDirectoryNode) {
if(error) {
callback(error);
} else if(parentDirectoryNode.type !== NODE_TYPE_DIRECTORY) {
callback(new Errors.ENOTDIR('a component of the path prefix is not a directory', path));
} else {
parentNode = parentDirectoryNode;
find_node(context, path, check_if_node_exists);
}
}
// Check if the node to be created already exists
function check_if_node_exists(error, result) {
if(!error && result) {
callback(new Errors.EEXIST('path name already exists', path));
} else if(error && !(error instanceof Errors.ENOENT)) {
callback(error);
} else {
context.getObject(parentNode.data, create_node);
}
}
// Create the new node
function create_node(error, result) {
if(error) {
callback(error);
} else {
parentNodeData = result;
Node.create({
guid: context.guid,
type: type
}, function(error, result) {
if(error) {
callback(error);
return;
}
node = result;
node.nlinks += 1;
context.putObject(node.id, node, update_parent_node_data);
});
}
}
// Update parent node time
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, parentPath, node, { mtime: now, ctime: now }, callback);
}
}
// Update the parent nodes data
function update_parent_node_data(error) {
if(error) {
callback(error);
} else {
parentNodeData[name] = new DirectoryEntry(node.id, type);
context.putObject(parentNode.data, parentNodeData, update_time);
}
}
// Find the parent node
find_node(context, parentPath, create_node_in_parent);
}
/**
* find_node
*/
// in: file or directory path
// out: node structure, or error
function find_node(context, path, callback) {
path = normalize(path);
if(!path) {
return callback(new Errors.ENOENT('path is an empty string'));
}
var name = basename(path);
var parentPath = dirname(path);
var followedCount = 0;
function read_root_directory_node(error, superNode) {
if(error) {
callback(error);
} else if(!superNode || superNode.type !== NODE_TYPE_META || !superNode.rnode) {
callback(new Errors.EFILESYSTEMERROR());
} else {
context.getObject(superNode.rnode, check_root_directory_node);
}
}
function check_root_directory_node(error, rootDirectoryNode) {
if(error) {
callback(error);
} else if(!rootDirectoryNode) {
callback(new Errors.ENOENT());
} else {
callback(null, rootDirectoryNode);
}
}
// in: parent directory node
// out: parent directory data
function read_parent_directory_data(error, parentDirectoryNode) {
if(error) {
callback(error);
} else if(parentDirectoryNode.type !== NODE_TYPE_DIRECTORY || !parentDirectoryNode.data) {
callback(new Errors.ENOTDIR('a component of the path prefix is not a directory', path));
} else {
context.getObject(parentDirectoryNode.data, get_node_from_parent_directory_data);
}
}
// in: parent directory data
// out: searched node
function get_node_from_parent_directory_data(error, parentDirectoryData) {
if(error) {
callback(error);
} else {
if(!parentDirectoryData.hasOwnProperty(name)) {
callback(new Errors.ENOENT(null, path));
} else {
var nodeId = parentDirectoryData[name].id;
context.getObject(nodeId, is_symbolic_link);
}
}
}
function is_symbolic_link(error, node) {
if(error) {
callback(error);
} else {
if(node.type === NODE_TYPE_SYMBOLIC_LINK) {
followedCount++;
if(followedCount > SYMLOOP_MAX){
callback(new Errors.ELOOP(null, path));
} else {
follow_symbolic_link(node.data);
}
} else {
callback(null, node);
}
}
}
function follow_symbolic_link(data) {
data = normalize(data);
parentPath = dirname(data);
name = basename(data);
if(ROOT_DIRECTORY_NAME === name) {
context.getObject(SUPER_NODE_ID, read_root_directory_node);
} else {
find_node(context, parentPath, read_parent_directory_data);
}
}
if(ROOT_DIRECTORY_NAME === name) {
context.getObject(SUPER_NODE_ID, read_root_directory_node);
} else {
find_node(context, parentPath, read_parent_directory_data);
}
}
/**
* set extended attribute (refactor)
*/
function set_extended_attribute (context, path, node, name, value, flag, callback) {
function update_time(error) {
if(error) {
callback(error);
} else {
update_node_times(context, path, node, { ctime: Date.now() }, callback);
}
}
var xattrs = node.xattrs;
if (flag === XATTR_CREATE && xattrs.hasOwnProperty(name)) {
callback(new Errors.EEXIST('attribute already exists', path));
}
else if (flag === XATTR_REPLACE && !xattrs.hasOwnProperty(name)) {
callback(new Errors.ENOATTR(null, path));
}
else {
xattrs[name] = value;
context.putObject(node.id, node, update_time);
}
}
/**
* ensure_root_directory. Creates a root node if necessary.
*
* Note: this should only be invoked when formatting a new file system.
* Multiple invocations of this by separate instances will still result
* in only a single super node.
*/
function ensure_root_directory(context, callback) {
var superNode;
var directoryNode;
var directoryData;
function ensure_super_node(error, existingNode) {
if(!error && existingNode) {
// Another instance has beat us and already created the super node.
callback();
} else if(error && !(error instanceof Errors.ENOENT)) {
callback(error);
} else {
SuperNode.create({guid: context.guid}, function(error, result) {
if(error) {
callback(error);
return;
}
superNode = result;
context.putObject(superNode.id, superNode, write_directory_node);
});
}
}
function write_directory_node(error) {
if(error) {
callback(error);
} else {
Node.create({
guid: context.guid,
id: superNode.rnode,
type: NODE_TYPE_DIRECTORY
}, function(error, result) {
if(error) {
callback(error);
return;
}
directoryNode = result;
directoryNode.nlinks += 1;
context.putObject(directoryNode.id, directoryNode, write_directory_data);
});
}
}
function write_directory_data(error) {
if(error) {
callback(error);
} else {
directoryData = {};
context.putObject(directoryNode.data, directoryData, callback);
}
}
context.getObject(SUPER_NODE_ID, ensure_super_node);
}
/**
* make_directory
*/
function make_directory(context, path, callback) {
path = normalize(path);
var name = basename(path);
var parentPath = dirname(path);
var directoryNode;
var directoryData;
var parentDirectoryNode;
var parentDirectoryData;
function check_if_directory_exists(error, result) {
if(!error && result) {
callback(new Errors.EEXIST(null, path));
} else if(error && !(error instanceof Errors.ENOENT)) {
callback(error);
} else {
find_node(context, parentPath, read_parent_directory_data);
}
}
function read_parent_directory_data(error, result) {
if(error) {
callback(error);
} else {
parentDirectoryNode = result;
context.getObject(parentDirectoryNode.data, write_directory_node);
}
}
function write_directory_node(error, result) {
if(error) {
callback(error);
} else {
parentDirectoryData = result;
Node.create({
guid: context.guid,
type: NODE_TYPE_DIRECTORY
}, function(error, result) {
if(error) {
callback(error);
return;
}
directoryNode = result;
directoryNode.nlinks += 1;
context.putObject(directoryNode.id, directoryNode, write_directory_data);
});
}
}
function write_directory_data(error) {
if(error) {
callback(error);
} else {
directoryData = {};
context.putObject(directoryNode.data, directoryData, update_parent_directory_data);
}
}
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, parentPath, parentDirectoryNode, { mtime: now, ctime: now }, callback);
}
}
function update_parent_directory_data(error) {
if(error) {
callback(error);
} else {
parentDirectoryData[name] = new DirectoryEntry(directoryNode.id, NODE_TYPE_DIRECTORY);
context.putObject(parentDirectoryNode.data, parentDirectoryData, update_time);
}
}
find_node(context, path, check_if_directory_exists);
}
function access_file(context, path, mode, callback) {
path = normalize(path);
find_node(context, path, function (err) {
if (err) {
return callback(err);
}
/*
TODO: we currently only support Constants.fsConstants.F_OK
Working to fix the issue: https://github.com/filerjs/filer/issues/561
*/
callback(null);
});
}
/**
* remove_directory
*/
function remove_directory(context, path, callback) {
path = normalize(path);
var name = basename(path);
var parentPath = dirname(path);
var directoryNode;
var directoryData;
var parentDirectoryNode;
var parentDirectoryData;
function read_parent_directory_data(error, result) {
if(error) {
callback(error);
} else {
parentDirectoryNode = result;
context.getObject(parentDirectoryNode.data, check_if_node_exists);
}
}
function check_if_node_exists(error, result) {
if(error) {
callback(error);
} else if(ROOT_DIRECTORY_NAME === name) {
callback(new Errors.EBUSY(null, path));
} else if(!result.hasOwnProperty(name)) {
callback(new Errors.ENOENT(null, path));
} else {
parentDirectoryData = result;
directoryNode = parentDirectoryData[name].id;
context.getObject(directoryNode, check_if_node_is_directory);
}
}
function check_if_node_is_directory(error, result) {
if(error) {
callback(error);
} else if(result.type !== NODE_TYPE_DIRECTORY) {
callback(new Errors.ENOTDIR(null, path));
} else {
directoryNode = result;
context.getObject(directoryNode.data, check_if_directory_is_empty);
}
}
function check_if_directory_is_empty(error, result) {
if(error) {
callback(error);
} else {
directoryData = result;
if(Object.keys(directoryData).length > 0) {
callback(new Errors.ENOTEMPTY(null, path));
} else {
remove_directory_entry_from_parent_directory_node();
}
}
}
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, parentPath, parentDirectoryNode, { mtime: now, ctime: now }, remove_directory_node);
}
}
function remove_directory_entry_from_parent_directory_node() {
delete parentDirectoryData[name];
context.putObject(parentDirectoryNode.data, parentDirectoryData, update_time);
}
function remove_directory_node(error) {
if(error) {
callback(error);
} else {
context.delete(directoryNode.id, remove_directory_data);
}
}
function remove_directory_data(error) {
if(error) {
callback(error);
} else {
context.delete(directoryNode.data, callback);
}
}
find_node(context, parentPath, read_parent_directory_data);
}
function open_file(context, path, flags, mode, callback) {
if (typeof mode === 'function'){
callback = mode;
mode = null;
}
path = normalize(path);
var name = basename(path);
var parentPath = dirname(path);
var directoryNode;
var directoryData;
var directoryEntry;
var fileNode;
var fileData;
var followedCount = 0;
if(ROOT_DIRECTORY_NAME === name) {
if(flags.includes(O_WRITE)) {
callback(new Errors.EISDIR('the named file is a directory and O_WRITE is set', path));
} else {
find_node(context, path, set_file_node);
}
} else {
find_node(context, parentPath, read_directory_data);
}
function read_directory_data(error, result) {
if(error) {
callback(error);
} else if(result.type !== NODE_TYPE_DIRECTORY) {
callback(new Errors.ENOENT(null, path));
} else {
directoryNode = result;
context.getObject(directoryNode.data, check_if_file_exists);
}
}
function check_if_file_exists(error, result) {
if(error) {
callback(error);
} else {
directoryData = result;
if(directoryData.hasOwnProperty(name)) {
if(flags.includes(O_EXCLUSIVE)) {
callback(new Errors.ENOENT('O_CREATE and O_EXCLUSIVE are set, and the named file exists', path));
} else {
directoryEntry = directoryData[name];
if(directoryEntry.type === NODE_TYPE_DIRECTORY && flags.includes(O_WRITE)) {
callback(new Errors.EISDIR('the named file is a directory and O_WRITE is set', path));
} else {
context.getObject(directoryEntry.id, check_if_symbolic_link);
}
}
} else {
if(!flags.includes(O_CREATE)) {
callback(new Errors.ENOENT('O_CREATE is not set and the named file does not exist', path));
} else {
write_file_node();
}
}
}
}
function check_if_symbolic_link(error, result) {
if(error) {
callback(error);
} else {
var node = result;
if(node.type === NODE_TYPE_SYMBOLIC_LINK) {
followedCount++;
if(followedCount > SYMLOOP_MAX){
callback(new Errors.ELOOP(null, path));
} else {
follow_symbolic_link(node.data);
}
} else {
set_file_node(undefined, node);
}
}
}
function follow_symbolic_link(data) {
data = normalize(data);
parentPath = dirname(data);
name = basename(data);
if(ROOT_DIRECTORY_NAME === name) {
if(flags.includes(O_WRITE)) {
callback(new Errors.EISDIR('the named file is a directory and O_WRITE is set', path));
} else {
find_node(context, path, set_file_node);
}
}
find_node(context, parentPath, read_directory_data);
}
function set_file_node(error, result) {
if(error) {
callback(error);
} else {
fileNode = result;
callback(null, fileNode);
}
}
function write_file_node() {
Node.create({
guid: context.guid,
type: NODE_TYPE_FILE
}, function(error, result) {
if(error) {
callback(error);
return;
}
fileNode = result;
fileNode.nlinks += 1;
if(mode){
Node.setMode(mode, fileNode);
}
context.putObject(fileNode.id, fileNode, write_file_data);
});
}
function write_file_data(error) {
if(error) {
callback(error);
} else {
fileData = new Buffer(0);
fileData.fill(0);
context.putBuffer(fileNode.data, fileData, update_directory_data);
}
}
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, parentPath, directoryNode, { mtime: now, ctime: now }, handle_update_result);
}
}
function update_directory_data(error) {
if(error) {
callback(error);
} else {
directoryData[name] = new DirectoryEntry(fileNode.id, NODE_TYPE_FILE);
context.putObject(directoryNode.data, directoryData, update_time);
}
}
function handle_update_result(error) {
if(error) {
callback(error);
} else {
callback(null, fileNode);
}
}
}
function replace_data(context, ofd, buffer, offset, length, callback) {
var fileNode;
function return_nbytes(error) {
if(error) {
callback(error);
} else {
callback(null, length);
}
}
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, ofd.path, fileNode, { mtime: now, ctime: now }, return_nbytes);
}
}
function update_file_node(error) {
if(error) {
callback(error);
} else {
context.putObject(fileNode.id, fileNode, update_time);
}
}
function write_file_data(error, result) {
if(error) {
callback(error);
} else {
fileNode = result;
var newData = new Buffer(length);
newData.fill(0);
buffer.copy(newData, 0, offset, offset + length);
ofd.position = length;
fileNode.size = length;
fileNode.version += 1;
context.putBuffer(fileNode.data, newData, update_file_node);
}
}
context.getObject(ofd.id, write_file_data);
}
function write_data(context, ofd, buffer, offset, length, position, callback) {
var fileNode;
var fileData;
function return_nbytes(error) {
if(error) {
callback(error);
} else {
callback(null, length);
}
}
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, ofd.path, fileNode, { mtime: now, ctime: now }, return_nbytes);
}
}
function update_file_node(error) {
if(error) {
callback(error);
} else {
context.putObject(fileNode.id, fileNode, update_time);
}
}
function update_file_data(error, result) {
if(error) {
callback(error);
} else {
fileData = result;
if(!fileData) {
return callback(new Errors.EIO('Expected Buffer'));
}
var _position = (!(undefined === position || null === position)) ? position : ofd.position;
var newSize = Math.max(fileData.length, _position + length);
var newData = new Buffer(newSize);
newData.fill(0);
if(fileData) {
fileData.copy(newData);
}
buffer.copy(newData, _position, offset, offset + length);
if(undefined === position) {
ofd.position += length;
}
fileNode.size = newSize;
fileNode.version += 1;
context.putBuffer(fileNode.data, newData, update_file_node);
}
}
function read_file_data(error, result) {
if(error) {
callback(error);
} else {
fileNode = result;
context.getBuffer(fileNode.data, update_file_data);
}
}
context.getObject(ofd.id, read_file_data);
}
function read_data(context, ofd, buffer, offset, length, position, callback) {
var fileNode;
var fileData;
function handle_file_data(error, result) {
if(error) {
callback(error);
} else {
fileData = result;
if(!fileData) {
return callback(new Errors.EIO('Expected Buffer'));
}
var _position = (!(undefined === position || null === position)) ? position : ofd.position;
length = (_position + length > buffer.length) ? length - _position : length;
fileData.copy(buffer, offset, _position, _position + length);
if(undefined === position) {
ofd.position += length;
}
callback(null, length);
}
}
function read_file_data(error, result) {
if(error) {
callback(error);
} else if(result.type === NODE_TYPE_DIRECTORY) {
callback(new Errors.EISDIR('the named file is a directory', ofd.path));
} else {
fileNode = result;
context.getBuffer(fileNode.data, handle_file_data);
}
}
context.getObject(ofd.id, read_file_data);
}
function stat_file(context, path, callback) {
path = normalize(path);
find_node(context, path, callback);
}
function fstat_file(context, ofd, callback) {
ofd.getNode(context, callback);
}
function lstat_file(context, path, callback) {
path = normalize(path);
var name = basename(path);
var parentPath = dirname(path);
var directoryNode;
var directoryData;
if(ROOT_DIRECTORY_NAME === name) {
find_node(context, path, callback);
} else {
find_node(context, parentPath, read_directory_data);
}
function read_directory_data(error, result) {
if(error) {
callback(error);
} else {
directoryNode = result;
context.getObject(directoryNode.data, check_if_file_exists);
}
}
function check_if_file_exists(error, result) {
if(error) {
callback(error);
} else {
directoryData = result;
if(!directoryData.hasOwnProperty(name)) {
callback(new Errors.ENOENT('a component of the path does not name an existing file', path));
} else {
context.getObject(directoryData[name].id, callback);
}
}
}
}
function link_node(context, oldpath, newpath, callback) {
oldpath = normalize(oldpath);
var oldname = basename(oldpath);
var oldParentPath = dirname(oldpath);
newpath = normalize(newpath);
var newname = basename(newpath);
var newParentPath = dirname(newpath);
var ctime = Date.now();
var oldDirectoryNode;
var oldDirectoryData;
var newDirectoryNode;
var newDirectoryData;
var fileNodeID;
var fileNode;
function update_time(error) {
if(error) {
callback(error);
} else {
update_node_times(context, newpath, fileNode, { ctime: ctime }, callback);
}
}
function update_file_node(error, result) {
if(error) {
callback(error);
} else {
fileNode = result;
fileNode.nlinks += 1;
context.putObject(fileNode.id, fileNode, update_time);
}
}
function read_file_node(error) {
if(error) {
callback(error);
} else {
context.getObject(fileNodeID, update_file_node);
}
}
function check_if_new_file_exists(error, result) {
if(error) {
callback(error);
} else {
newDirectoryData = result;
if(newDirectoryData.hasOwnProperty(newname)) {
callback(new Errors.EEXIST('newpath resolves to an existing file', newname));
} else {
newDirectoryData[newname] = oldDirectoryData[oldname];
fileNodeID = newDirectoryData[newname].id;
context.putObject(newDirectoryNode.data, newDirectoryData, read_file_node);
}
}
}
function read_new_directory_data(error, result) {
if(error) {
callback(error);
} else {
newDirectoryNode = result;
context.getObject(newDirectoryNode.data, check_if_new_file_exists);
}
}
function check_if_old_file_exists(error, result) {
if(error) {
callback(error);
} else {
oldDirectoryData = result;
if(!oldDirectoryData.hasOwnProperty(oldname)) {
callback(new Errors.ENOENT('a component of either path prefix does not exist', oldname));
} else if(oldDirectoryData[oldname].type === NODE_TYPE_DIRECTORY) {
callback(new Errors.EPERM('oldpath refers to a directory'));
} else {
find_node(context, newParentPath, read_new_directory_data);
}
}
}
function read_old_directory_data(error, result) {
if(error) {
callback(error);
} else {
oldDirectoryNode = result;
context.getObject(oldDirectoryNode.data, check_if_old_file_exists);
}
}
find_node(context, oldParentPath, read_old_directory_data);
}
function unlink_node(context, path, callback) {
path = normalize(path);
var name = basename(path);
var parentPath = dirname(path);
var directoryNode;
var directoryData;
var fileNode;
function update_directory_data(error) {
if(error) {
callback(error);
} else {
delete directoryData[name];
context.putObject(directoryNode.data, directoryData, function(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, parentPath, directoryNode, { mtime: now, ctime: now }, callback);
}
});
}
}
function delete_file_data(error) {
if(error) {
callback(error);
} else {
context.delete(fileNode.data, update_directory_data);
}
}
function update_file_node(error, result) {
if(error) {
callback(error);
} else {
fileNode = result;
fileNode.nlinks -= 1;
if(fileNode.nlinks < 1) {
context.delete(fileNode.id, delete_file_data);
} else {
context.putObject(fileNode.id, fileNode, function(error) {
if(error) {
callback(error);
} else {
update_node_times(context, path, fileNode, { ctime: Date.now() }, update_directory_data);
}
});
}
}
}
function check_if_node_is_directory(error, result) {
if(error) {
callback(error);
} else if(result.type === NODE_TYPE_DIRECTORY) {
callback(new Errors.EPERM('unlink not permitted on directories', name));
} else {
update_file_node(null, result);
}
}
function check_if_file_exists(error, result) {
if(error) {
callback(error);
} else {
directoryData = result;
if(!directoryData.hasOwnProperty(name)) {
callback(new Errors.ENOENT('a component of the path does not name an existing file', name));
} else {
context.getObject(directoryData[name].id, check_if_node_is_directory);
}
}
}
function read_directory_data(error, result) {
if(error) {
callback(error);
} else {
directoryNode = result;
context.getObject(directoryNode.data, check_if_file_exists);
}
}
find_node(context, parentPath, read_directory_data);
}
function read_directory(context, path, callback) {
path = normalize(path);
var directoryNode;
var directoryData;
function handle_directory_data(error, result) {
if(error) {
callback(error);
} else {
directoryData = result;
var files = Object.keys(directoryData);
callback(null, files);
}
}
function read_directory_data(error, result) {
if(error) {
callback(error);
} else if(result.type !== NODE_TYPE_DIRECTORY) {
callback(new Errors.ENOTDIR(null, path));
} else {
directoryNode = result;
context.getObject(directoryNode.data, handle_directory_data);
}
}
find_node(context, path, read_directory_data);
}
function make_symbolic_link(context, srcpath, dstpath, callback) {
dstpath = normalize(dstpath);
var name = basename(dstpath);
var parentPath = dirname(dstpath);
var directoryNode;
var directoryData;
var fileNode;
if(ROOT_DIRECTORY_NAME === name) {
callback(new Errors.EEXIST(null, name));
} else {
find_node(context, parentPath, read_directory_data);
}
function read_directory_data(error, result) {
if(error) {
callback(error);
} else {
directoryNode = result;
context.getObject(directoryNode.data, check_if_file_exists);
}
}
function check_if_file_exists(error, result) {
if(error) {
callback(error);
} else {
directoryData = result;
if(directoryData.hasOwnProperty(name)) {
callback(new Errors.EEXIST(null, name));
} else {
write_file_node();
}
}
}
function write_file_node() {
Node.create({
guid: context.guid,
type: NODE_TYPE_SYMBOLIC_LINK
}, function(error, result) {
if(error) {
callback(error);
return;
}
fileNode = result;
fileNode.nlinks += 1;
// If the srcpath isn't absolute, resolve it relative to the dstpath
// but store both versions, since we'll use the relative one in readlink().
if(!isAbsolutePath(srcpath)) {
fileNode.symlink_relpath = srcpath;
srcpath = Path.resolve(parentPath, srcpath);
}
fileNode.size = srcpath.length;
fileNode.data = srcpath;
context.putObject(fileNode.id, fileNode, update_directory_data);
});
}
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, parentPath, directoryNode, { mtime: now, ctime: now }, callback);
}
}
function update_directory_data(error) {
if(error) {
callback(error);
} else {
directoryData[name] = new DirectoryEntry(fileNode.id, NODE_TYPE_SYMBOLIC_LINK);
context.putObject(directoryNode.data, directoryData, update_time);
}
}
}
function read_link(context, path, callback) {
path = normalize(path);
var name = basename(path);
var parentPath = dirname(path);
var directoryNode;
var directoryData;
find_node(context, parentPath, read_directory_data);
function read_directory_data(error, result) {
if(error) {
callback(error);
} else {
directoryNode = result;
context.getObject(directoryNode.data, check_if_file_exists);
}
}
function check_if_file_exists(error, result) {
if(error) {
callback(error);
} else {
directoryData = result;
if(!directoryData.hasOwnProperty(name)) {
callback(new Errors.ENOENT('a component of the path does not name an existing file', name));
} else {
context.getObject(directoryData[name].id, check_if_symbolic);
}
}
}
function check_if_symbolic(error, fileNode) {
if(error) {
callback(error);
} else {
if(fileNode.type !== NODE_TYPE_SYMBOLIC_LINK) {
callback(new Errors.EINVAL('path not a symbolic link', path));
} else {
// If we were originally given a relative path, return that now vs. the
// absolute path we've generated and use elsewhere internally.
var target = fileNode.symlink_relpath ? fileNode.symlink_relpath : fileNode.data;
callback(null, target);
}
}
}
}
function truncate_file(context, path, length, callback) {
path = normalize(path);
var fileNode;
function read_file_data (error, node) {
if (error) {
callback(error);
} else if(node.type === NODE_TYPE_DIRECTORY ) {
callback(new Errors.EISDIR(null, path));
} else{
fileNode = node;
context.getBuffer(fileNode.data, truncate_file_data);
}
}
function truncate_file_data(error, fileData) {
if (error) {
callback(error);
} else {
if(!fileData) {
return callback(new Errors.EIO('Expected Buffer'));
}
try {
validateInteger(length, 'len');
}
catch (error) {
return callback(error);
}
var data = new Buffer(length);
data.fill(0);
if(fileData) {
fileData.copy(data);
}
context.putBuffer(fileNode.data, data, update_file_node);
}
}
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, path, fileNode, { mtime: now, ctime: now }, callback);
}
}
function update_file_node (error) {
if(error) {
callback(error);
} else {
fileNode.size = length;
fileNode.version += 1;
context.putObject(fileNode.id, fileNode, update_time);
}
}
if(length < 0) {
callback(new Errors.EINVAL('length cannot be negative'));
} else {
find_node(context, path, read_file_data);
}
}
function ftruncate_file(context, ofd, length, callback) {
var fileNode;
function read_file_data (error, node) {
if (error) {
callback(error);
} else if(node.type === NODE_TYPE_DIRECTORY ) {
callback(new Errors.EISDIR());
} else{
fileNode = node;
context.getBuffer(fileNode.data, truncate_file_data);
}
}
function truncate_file_data(error, fileData) {
if (error) {
callback(error);
} else {
var data;
if(!fileData) {
return callback(new Errors.EIO('Expected Buffer'));
}
if(fileData) {
data = fileData.slice(0, length);
} else {
data = new Buffer(length);
data.fill(0);
}
context.putBuffer(fileNode.data, data, update_file_node);
}
}
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, ofd.path, fileNode, { mtime: now, ctime: now }, callback);
}
}
function update_file_node (error) {
if(error) {
callback(error);
} else {
fileNode.size = length;
fileNode.version += 1;
context.putObject(fileNode.id, fileNode, update_time);
}
}
if(length < 0) {
callback(new Errors.EINVAL('length cannot be negative'));
} else {
ofd.getNode(context, read_file_data);
}
}
function utimes_file(context, path, atime, mtime, callback) {
path = normalize(path);
function update_times(error, node) {
if (error) {
callback(error);
} else {
update_node_times(context, path, node, { atime: atime, ctime: mtime, mtime: mtime }, callback);
}
}
if (typeof atime !== 'number' || typeof mtime !== 'number') {
callback(new Errors.EINVAL('atime and mtime must be number', path));
}
else if (atime < 0 || mtime < 0) {
callback(new Errors.EINVAL('atime and mtime must be positive integers', path));
}
else {
find_node(context, path, update_times);
}
}
function futimes_file(context, ofd, atime, mtime, callback) {
function update_times (error, node) {
if (error) {
callback(error);
} else {
update_node_times(context, ofd.path, node, { atime: atime, ctime: mtime, mtime: mtime }, callback);
}
}
if (typeof atime !== 'number' || typeof mtime !== 'number') {
callback(new Errors.EINVAL('atime and mtime must be a number'));
}
else if (atime < 0 || mtime < 0) {
callback(new Errors.EINVAL('atime and mtime must be positive integers'));
}
else {
ofd.getNode(context, update_times);
}
}
function setxattr_file(context, path, name, value, flag, callback) {
path = normalize(path);
function setxattr(error, node) {
if(error) {
return callback(error);
}
set_extended_attribute(context, path, node, name, value, flag, callback);
}
if (typeof name !== 'string') {
callback(new Errors.EINVAL('attribute name must be a string', path));
}
else if (!name) {
callback(new Errors.EINVAL('attribute name cannot be an empty string', path));
}
else if (flag !== null &&
flag !== XATTR_CREATE && flag !== XATTR_REPLACE) {
callback(new Errors.EINVAL('invalid flag, must be null, XATTR_CREATE or XATTR_REPLACE', path));
}
else {
find_node(context, path, setxattr);
}
}
function fsetxattr_file (context, ofd, name, value, flag, callback) {
function setxattr(error, node) {
if(error) {
return callback(error);
}
set_extended_attribute(context, ofd.path, node, name, value, flag, callback);
}
if (typeof name !== 'string') {
callback(new Errors.EINVAL('attribute name must be a string'));
}
else if (!name) {
callback(new Errors.EINVAL('attribute name cannot be an empty string'));
}
else if (flag !== null &&
flag !== XATTR_CREATE && flag !== XATTR_REPLACE) {
callback(new Errors.EINVAL('invalid flag, must be null, XATTR_CREATE or XATTR_REPLACE'));
}
else {
ofd.getNode(context, setxattr);
}
}
function getxattr_file (context, path, name, callback) {
path = normalize(path);
function get_xattr(error, node) {
if(error) {
return callback(error);
}
var xattrs = node.xattrs;
if (!xattrs.hasOwnProperty(name)) {
callback(new Errors.ENOATTR(null, path));
}
else {
callback(null, xattrs[name]);
}
}
if (typeof name !== 'string') {
callback(new Errors.EINVAL('attribute name must be a string', path));
}
else if (!name) {
callback(new Errors.EINVAL('attribute name cannot be an empty string', path));
}
else {
find_node(context, path, get_xattr);
}
}
function fgetxattr_file (context, ofd, name, callback) {
function get_xattr (error, node) {
if (error) {
return callback(error);
}
var xattrs = node.xattrs;
if (!xattrs.hasOwnProperty(name)) {
callback(new Errors.ENOATTR());
}
else {
callback(null, xattrs[name]);
}
}
if (typeof name !== 'string') {
callback(new Errors.EINVAL());
}
else if (!name) {
callback(new Errors.EINVAL('attribute name cannot be an empty string'));
}
else {
ofd.getNode(context, get_xattr);
}
}
function removexattr_file (context, path, name, callback) {
path = normalize(path);
function remove_xattr (error, node) {
if (error) {
return callback(error);
}
function update_time(error) {
if(error) {
callback(error);
} else {
update_node_times(context, path, node, { ctime: Date.now() }, callback);
}
}
var xattrs = node.xattrs;
if (!xattrs.hasOwnProperty(name)) {
callback(new Errors.ENOATTR(null, path));
}
else {
delete xattrs[name];
context.putObject(node.id, node, update_time);
}
}
if (typeof name !== 'string') {
callback(new Errors.EINVAL('attribute name must be a string', path));
}
else if (!name) {
callback(new Errors.EINVAL('attribute name cannot be an empty string', path));
}
else {
find_node(context, path, remove_xattr);
}
}
function fremovexattr_file (context, ofd, name, callback) {
function remove_xattr (error, node) {
if (error) {
return callback(error);
}
function update_time(error) {
if(error) {
callback(error);
} else {
update_node_times(context, ofd.path, node, { ctime: Date.now() }, callback);
}
}
var xattrs = node.xattrs;
if (!xattrs.hasOwnProperty(name)) {
callback(new Errors.ENOATTR());
}
else {
delete xattrs[name];
context.putObject(node.id, node, update_time);
}
}
if (typeof name !== 'string') {
callback(new Errors.EINVAL('attribute name must be a string'));
}
else if (!name) {
callback(new Errors.EINVAL('attribute name cannot be an empty string'));
}
else {
ofd.getNode(context, remove_xattr);
}
}
function validate_flags(flags) {
return O_FLAGS.hasOwnProperty(flags) ? O_FLAGS[flags] : null;
}
function validate_file_options(options, enc, fileMode){
if(!options) {
options = { encoding: enc, flag: fileMode };
} else if(typeof options === 'function') {
options = { encoding: enc, flag: fileMode };
} else if(typeof options === 'string') {
options = { encoding: options, flag: fileMode };
}
return options;
}
function pathCheck(path, allowRelative, callback) {
var err;
if(typeof allowRelative === 'function') {
callback = allowRelative;
allowRelative = false;
}
if(!path) {
err = new Errors.EINVAL('Path must be a string', path);
} else if(isNullPath(path)) {
err = new Errors.EINVAL('Path must be a string without null bytes.', path);
} else if(!allowRelative && !isAbsolutePath(path)) {
err = new Errors.EINVAL('Path must be absolute.', path);
}
if(err) {
callback(err);
return false;
}
return true;
}
function open(fs, context, path, flags, mode, callback) {
if (arguments.length < 6 ){
callback = arguments[arguments.length - 1];
mode = 0o644;
}
else {
mode = validateAndMaskMode(mode, FULL_READ_WRITE_EXEC_PERMISSIONS, callback);
}
if(!pathCheck(path, callback)) return;
function check_result(error, fileNode) {
if(error) {
callback(error);
} else {
var position;
if(flags.includes(O_APPEND)) {
position = fileNode.size;
} else {
position = 0;
}
var openFileDescription = new OpenFileDescription(path, fileNode.id, flags, position);
var fd = fs.allocDescriptor(openFileDescription);
callback(null, fd);
}
}
flags = validate_flags(flags);
if(!flags) {
callback(new Errors.EINVAL('flags is not valid'), path);
}
open_file(context, path, flags, mode, check_result);
}
function close(fs, context, fd, callback) {
if(!fs.openFiles[fd]) {
callback(new Errors.EBADF());
} else {
fs.releaseDescriptor(fd);
callback(null);
}
}
function mknod(fs, context, path, type, callback) {
if(!pathCheck(path, callback)) return;
make_node(context, path, type, callback);
}
function mkdir(fs, context, path, mode, callback) {
if (arguments.length < 5) {
callback = mode;
mode = FULL_READ_WRITE_EXEC_PERMISSIONS;
} else {
mode = validateAndMaskMode(mode, FULL_READ_WRITE_EXEC_PERMISSIONS, callback);
if(!mode) return;
}
if(!pathCheck(path, callback)) return;
make_directory(context, path, callback);
}
function access(fs, context, path, mode, callback) {
if (typeof mode === 'function') {
callback = mode;
mode = Constants.fsConstants.F_OK;
}
if (!pathCheck(path, callback)) return;
mode = mode | 0;
access_file(context, path, mode, callback);
}
function rmdir(fs, context, path, callback) {
if(!pathCheck(path, callback)) return;
remove_directory(context, path, callback);
}
function stat(fs, context, path, callback) {
if(!pathCheck(path, callback)) return;
function check_result(error, result) {
if(error) {
callback(error);
} else {
var stats = new Stats(path, result, fs.name);
callback(null, stats);
}
}
stat_file(context, path, check_result);
}
function fstat(fs, context, fd, callback) {
function check_result(error, result) {
if(error) {
callback(error);
} else {
var stats = new Stats(ofd.path, result, fs.name);
callback(null, stats);
}
}
var ofd = fs.openFiles[fd];
if(!ofd) {
callback(new Errors.EBADF());
} else {
fstat_file(context, ofd, check_result);
}
}
function link(fs, context, oldpath, newpath, callback) {
if(!pathCheck(oldpath, callback)) return;
if(!pathCheck(newpath, callback)) return;
link_node(context, oldpath, newpath, callback);
}
function unlink(fs, context, path, callback) {
if(!pathCheck(path, callback)) return;
unlink_node(context, path, callback);
}
function read(fs, context, fd, buffer, offset, length, position, callback) {
// Follow how node.js does this
function wrapped_cb(err, bytesRead) {
// Retain a reference to buffer so that it can't be GC'ed too soon.
callback(err, bytesRead || 0, buffer);
}
offset = (undefined === offset) ? 0 : offset;
length = (undefined === length) ? buffer.length - offset : length;
callback = arguments[arguments.length - 1];
var ofd = fs.openFiles[fd];
if(!ofd) {
callback(new Errors.EBADF());
} else if(!ofd.flags.includes(O_READ)) {
callback(new Errors.EBADF('descriptor does not permit reading'));
} else {
read_data(context, ofd, buffer, offset, length, position, wrapped_cb);
}
}
function readFile(fs, context, path, options, callback) {
callback = arguments[arguments.length - 1];
options = validate_file_options(options, null, 'r');
if(!pathCheck(path, callback)) return;
var flags = validate_flags(options.flag || 'r');
if(!flags) {
return callback(new Errors.EINVAL('flags is not valid', path));
}
open_file(context, path, flags, function(err, fileNode) {
if(err) {
return callback(err);
}
var ofd = new OpenFileDescription(path, fileNode.id, flags, 0);
var fd = fs.allocDescriptor(ofd);
function cleanup() {
fs.releaseDescriptor(fd);
}
fstat_file(context, ofd, function(err, fstatResult) {
if(err) {
cleanup();
return callback(err);
}
var stats = new Stats(ofd.path, fstatResult, fs.name);
if(stats.isDirectory()) {
cleanup();
return callback(new Errors.EISDIR('illegal operation on directory', path));
}
var size = stats.size;
var buffer = new Buffer(size);
buffer.fill(0);
read_data(context, ofd, buffer, 0, size, 0, function(err) {
cleanup();
if(err) {
return callback(err);
}
var data;
if(options.encoding === 'utf8') {
data = Encoding.decode(buffer);
} else {
data = buffer;
}
callback(null, data);
});
});
});
}
function write(fs, context, fd, buffer, offset, length, position, callback) {
callback = arguments[arguments.length - 1];
offset = (undefined === offset) ? 0 : offset;
length = (undefined === length) ? buffer.length - offset : length;
var ofd = fs.openFiles[fd];
if(!ofd) {
callback(new Errors.EBADF());
} else if(!ofd.flags.includes(O_WRITE)) {
callback(new Errors.EBADF('descriptor does not permit writing'));
} else if(buffer.length - offset < length) {
callback(new Errors.EIO('intput buffer is too small'));
} else {
write_data(context, ofd, buffer, offset, length, position, callback);
}
}
function writeFile(fs, context, path, data, options, callback) {
callback = arguments[arguments.length - 1];
options = validate_file_options(options, 'utf8', 'w');
if(!pathCheck(path, callback)) return;
var flags = validate_flags(options.flag || 'w');
if(!flags) {
return callback(new Errors.EINVAL('flags is not valid', path));
}
data = data || '';
if(typeof data === 'number') {
data = '' + data;
}
if(typeof data === 'string' && options.encoding === 'utf8') {
data = Encoding.encode(data);
}
open_file(context, path, flags, function(err, fileNode) {
if(err) {
return callback(err);
}
var ofd = new OpenFileDescription(path, fileNode.id, flags, 0);
var fd = fs.allocDescriptor(ofd);
replace_data(context, ofd, data, 0, data.length, function(err) {
fs.releaseDescriptor(fd);
if(err) {
return callback(err);
}
callback(null);
});
});
}
function appendFile(fs, context, path, data, options, callback) {
callback = arguments[arguments.length - 1];
options = validate_file_options(options, 'utf8', 'a');
if(!pathCheck(path, callback)) return;
var flags = validate_flags(options.flag || 'a');
if(!flags) {
return callback(new Errors.EINVAL('flags is not valid', path));
}
data = data || '';
if(typeof data === 'number') {
data = '' + data;
}
if(typeof data === 'string' && options.encoding === 'utf8') {
data = Encoding.encode(data);
}
open_file(context, path, flags, function(err, fileNode) {
if(err) {
return callback(err);
}
var ofd = new OpenFileDescription(path, fileNode.id, flags, fileNode.size);
var fd = fs.allocDescriptor(ofd);
write_data(context, ofd, data, 0, data.length, ofd.position, function(err) {
fs.releaseDescriptor(fd);
if(err) {
return callback(err);
}
callback(null);
});
});
}
function exists(fs, context, path, callback) {
function cb(err) {
callback(err ? false : true);
}
console.warn('This method is deprecated. For more details see https://nodejs.org/api/fs.html#fs_fs_exists_path_callback');// eslint-disable-line no-console
stat(fs, context, path, cb);
}
// Based on https://github.com/nodejs/node/blob/c700cc42da9cf73af9fec2098520a6c0a631d901/lib/internal/validators.js#L21
var octalReg = /^[0-7]+$/;
function isUint32(value) {
return value === (value >>> 0);
}
// Validator for mode_t (the S_* constants). Valid numbers or octal strings
// will be masked with 0o777 to be consistent with the behavior in POSIX APIs.
function validateAndMaskMode(value, def, callback) {
if(typeof def === 'function') {
callback = def;
def = undefined;
}
if (isUint32(value)) {
return value & FULL_READ_WRITE_EXEC_PERMISSIONS;
}
if (typeof value === 'number') {
if (!Number.isInteger(value)) {
callback(new Errors.EINVAL('mode not a valid an integer value', value));
return false;
} else {
// 2 ** 32 === 4294967296
callback(new Errors.EINVAL('mode not a valid an integer value', value));
return false;
}
}
if (typeof value === 'string') {
if (!octalReg.test(value)) {
callback(new Errors.EINVAL('mode not a valid octal string', value));
return false;
}
var parsed = parseInt(value, 8);
return parsed & FULL_READ_WRITE_EXEC_PERMISSIONS;
}
// TODO(BridgeAR): Only return `def` in case `value === null`
if (def !== undefined) {
return def;
}
callback(new Errors.EINVAL('mode not valid', value));
return false;
}
function chmod_file(context, path, mode, callback) {
path = normalize(path);
function update_mode(error, node) {
if (error) {
callback(error);
} else {
Node.setMode(mode, node);
update_node_times(context, path, node, { mtime: Date.now() }, callback);
}
}
if (typeof mode !== 'number') {
callback(new Errors.EINVAL('mode must be number', path));
}
else {
find_node(context, path, update_mode);
}
}
function fchmod_file(context, ofd, mode, callback) {
function update_mode(error, node) {
if (error) {
callback(error);
} else {
node.mode = mode;
update_node_times(context, ofd.path, node, { mtime: Date.now() }, callback);
}
}
if (typeof mode !== 'number') {
callback(new Errors.EINVAL('mode must be a number'));
}
else {
ofd.getNode(context, update_mode);
}
}
function chown_file(context, path, uid, gid, callback) {
path = normalize(path);
function update_owner(error, node) {
if (error) {
callback(error);
} else {
node.uid = uid;
node.gid = gid;
update_node_times(context, path, node, { mtime: Date.now() }, callback);
}
}
find_node(context, path, update_owner);
}
function fchown_file(context, ofd, uid, gid, callback) {
function update_owner(error, node) {
if (error) {
callback(error);
} else {
node.uid = uid;
node.gid = gid;
update_node_times(context, ofd.path, node, { mtime: Date.now() }, callback);
}
}
ofd.getNode(context, update_owner);
}
function getxattr(fs, context, path, name, callback) {
if (!pathCheck(path, callback)) return;
getxattr_file(context, path, name, callback);
}
function fgetxattr(fs, context, fd, name, callback) {
var ofd = fs.openFiles[fd];
if (!ofd) {
callback(new Errors.EBADF());
}
else {
fgetxattr_file(context, ofd, name, callback);
}
}
function setxattr(fs, context, path, name, value, flag, callback) {
if(typeof flag === 'function') {
callback = flag;
flag = null;
}
if (!pathCheck(path, callback)) return;
setxattr_file(context, path, name, value, flag, callback);
}
function fsetxattr(fs, context, fd, name, value, flag, callback) {
if(typeof flag === 'function') {
callback = flag;
flag = null;
}
var ofd = fs.openFiles[fd];
if (!ofd) {
callback(new Errors.EBADF());
}
else if (!ofd.flags.includes(O_WRITE)) {
callback(new Errors.EBADF('descriptor does not permit writing'));
}
else {
fsetxattr_file(context, ofd, name, value, flag, callback);
}
}
function removexattr(fs, context, path, name, callback) {
if (!pathCheck(path, callback)) return;
removexattr_file(context, path, name, callback);
}
function fremovexattr(fs, context, fd, name, callback) {
var ofd = fs.openFiles[fd];
if (!ofd) {
callback(new Errors.EBADF());
}
else if (!ofd.flags.includes(O_WRITE)) {
callback(new Errors.EBADF('descriptor does not permit writing'));
}
else {
fremovexattr_file(context, ofd, name, callback);
}
}
function lseek(fs, context, fd, offset, whence, callback) {
function update_descriptor_position(error, stats) {
if(error) {
callback(error);
} else {
if(stats.size + offset < 0) {
callback(new Errors.EINVAL('resulting file offset would be negative'));
} else {
ofd.position = stats.size + offset;
callback(null, ofd.position);
}
}
}
var ofd = fs.openFiles[fd];
if(!ofd) {
callback(new Errors.EBADF());
}
if('SET' === whence) {
if(offset < 0) {
callback(new Errors.EINVAL('resulting file offset would be negative'));
} else {
ofd.position = offset;
callback(null, ofd.position);
}
} else if('CUR' === whence) {
if(ofd.position + offset < 0) {
callback(new Errors.EINVAL('resulting file offset would be negative'));
} else {
ofd.position += offset;
callback(null, ofd.position);
}
} else if('END' === whence) {
fstat_file(context, ofd, update_descriptor_position);
} else {
callback(new Errors.EINVAL('whence argument is not a proper value'));
}
}
function readdir(fs, context, path, callback) {
if(!pathCheck(path, callback)) return;
read_directory(context, path, callback);
}
function utimes(fs, context, path, atime, mtime, callback) {
if(!pathCheck(path, callback)) return;
var currentTime = Date.now();
atime = (atime) ? atime : currentTime;
mtime = (mtime) ? mtime : currentTime;
utimes_file(context, path, atime, mtime, callback);
}
function futimes(fs, context, fd, atime, mtime, callback) {
var currentTime = Date.now();
atime = (atime) ? atime : currentTime;
mtime = (mtime) ? mtime : currentTime;
var ofd = fs.openFiles[fd];
if(!ofd) {
callback(new Errors.EBADF());
} else if(!ofd.flags.includes(O_WRITE)) {
callback(new Errors.EBADF('descriptor does not permit writing'));
} else {
futimes_file(context, ofd, atime, mtime, callback);
}
}
function chmod(fs, context, path, mode, callback) {
if(!pathCheck(path, callback)) return;
mode = validateAndMaskMode(mode, 'mode');
if(!mode) return;
chmod_file(context, path, mode, callback);
}
function fchmod(fs, context, fd, mode, callback) {
mode = validateAndMaskMode(mode, 'mode');
if(!mode) return;
var ofd = fs.openFiles[fd];
if(!ofd) {
callback(new Errors.EBADF());
} else if(!ofd.flags.includes(O_WRITE)) {
callback(new Errors.EBADF('descriptor does not permit writing'));
} else {
fchmod_file(context, ofd, mode, callback);
}
}
function chown(fs, context, path, uid, gid, callback) {
if(!pathCheck(path, callback)) return;
if(!isUint32(uid)) {
return callback(new Errors.EINVAL('uid must be a valid integer', uid));
}
if(!isUint32(gid)) {
return callback(new Errors.EINVAL('gid must be a valid integer', gid));
}
chown_file(context, path, uid, gid, callback);
}
function fchown(fs, context, fd, uid, gid, callback) {
if(!isUint32(uid)) {
return callback(new Errors.EINVAL('uid must be a valid integer', uid));
}
if(!isUint32(gid)) {
return callback(new Errors.EINVAL('gid must be a valid integer', gid));
}
var ofd = fs.openFiles[fd];
if(!ofd) {
callback(new Errors.EBADF());
} else if(!ofd.flags.includes(O_WRITE)) {
callback(new Errors.EBADF('descriptor does not permit writing'));
} else {
fchown_file(context, ofd, uid, gid, callback);
}
}
function rename(fs, context, oldpath, newpath, callback) {
if(!pathCheck(oldpath, callback)) return;
if(!pathCheck(newpath, callback)) return;
oldpath = normalize(oldpath);
newpath = normalize(newpath);
var oldParentPath = Path.dirname(oldpath);
var newParentPath = Path.dirname(oldpath);
var oldName = Path.basename(oldpath);
var newName = Path.basename(newpath);
var oldParentDirectory, oldParentData;
var newParentDirectory, newParentData;
var ctime = Date.now();
var fileNode;
function update_times(error, result) {
if(error) {
callback(error);
} else {
fileNode = result;
update_node_times(context, newpath, fileNode, { ctime: ctime }, callback);
}
}
function read_new_directory(error) {
if(error) {
callback(error);
} else {
context.getObject(newParentData[newName].id, update_times);
}
}
function update_old_parent_directory_data(error) {
if(error) {
callback(error);
} else {
if(oldParentDirectory.id === newParentDirectory.id) {
oldParentData = newParentData;
}
delete oldParentData[oldName];
context.putObject(oldParentDirectory.data, oldParentData, read_new_directory);
}
}
function update_new_parent_directory_data(error) {
if(error) {
callback(error);
} else {
newParentData[newName] = oldParentData[oldName];
context.putObject(newParentDirectory.data, newParentData, update_old_parent_directory_data);
}
}
function check_if_new_directory_exists(error, result) {
if(error) {
callback(error);
} else {
newParentData = result;
if(newParentData.hasOwnProperty(newName)) {
remove_directory(context, newpath, update_new_parent_directory_data);
} else {
update_new_parent_directory_data();
}
}
}
function read_new_parent_directory_data(error, result) {
if(error) {
callback(error);
} else {
newParentDirectory = result;
context.getObject(newParentDirectory.data, check_if_new_directory_exists);
}
}
function get_new_parent_directory(error, result) {
if(error) {
callback(error);
} else {
oldParentData = result;
find_node(context, newParentPath, read_new_parent_directory_data);
}
}
function read_parent_directory_data(error, result) {
if(error) {
callback(error);
} else {
oldParentDirectory = result;
context.getObject(result.data, get_new_parent_directory);
}
}
function unlink_old_file(error) {
if(error) {
callback(error);
} else {
unlink_node(context, oldpath, callback);
}
}
function check_node_type(error, node) {
if(error) {
callback(error);
} else if(node.type === NODE_TYPE_DIRECTORY) {
find_node(context, oldParentPath, read_parent_directory_data);
} else {
link_node(context, oldpath, newpath, unlink_old_file);
}
}
find_node(context, oldpath, check_node_type);
}
function symlink(fs, context, srcpath, dstpath, type, callback) {
// NOTE: we support passing the `type` arg, but ignore it.
callback = arguments[arguments.length - 1];
// Special Case: allow srcpath to be relative, which we normally don't permit.
// If the srcpath is relative, we assume it's relative to the dirpath of
// dstpath.
if(!pathCheck(srcpath, true, callback)) return;
if(!pathCheck(dstpath, callback)) return;
make_symbolic_link(context, srcpath, dstpath, callback);
}
function readlink(fs, context, path, callback) {
if(!pathCheck(path, callback)) return;
read_link(context, path, callback);
}
function lstat(fs, context, path, callback) {
if(!pathCheck(path, callback)) return;
function check_result(error, result) {
if(error) {
callback(error);
} else {
var stats = new Stats(path, result, fs.name);
callback(null, stats);
}
}
lstat_file(context, path, check_result);
}
function truncate(fs, context, path, length, callback) {
// NOTE: length is optional
callback = arguments[arguments.length - 1];
length = length || 0;
if(!pathCheck(path, callback)) return;
truncate_file(context, path, length, callback);
}
function ftruncate(fs, context, fd, length, callback) {
// NOTE: length is optional
callback = arguments[arguments.length - 1];
length = length || 0;
var ofd = fs.openFiles[fd];
if(!ofd) {
callback(new Errors.EBADF());
} else if(!ofd.flags.includes(O_WRITE)) {
callback(new Errors.EBADF('descriptor does not permit writing'));
} else {
ftruncate_file(context, ofd, length, callback);
}
}
module.exports = {
ensureRootDirectory: ensure_root_directory,
open: open,
chmod: chmod,
access: access,
fchmod: fchmod,
chown: chown,
fchown: fchown,
close: close,
mknod: mknod,
mkdir: mkdir,
rmdir: rmdir,
unlink: unlink,
stat: stat,
fstat: fstat,
link: link,
read: read,
readFile: readFile,
write: write,
writeFile: writeFile,
appendFile: appendFile,
exists: exists,
getxattr: getxattr,
fgetxattr: fgetxattr,
setxattr: setxattr,
fsetxattr: fsetxattr,
removexattr: removexattr,
fremovexattr: fremovexattr,
lseek: lseek,
readdir: readdir,
utimes: utimes,
futimes: futimes,
rename: rename,
symlink: symlink,
readlink: readlink,
lstat: lstat,
truncate: truncate,
ftruncate: ftruncate
};