node-smb-server
Version:
A Pure JavaScript SMB Server Implementation
267 lines (242 loc) • 9.31 kB
JavaScript
/*
* Copyright 2015 Adobe Systems Incorporated. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
;
var util = require('util');
var Path = require('path');
var fs = require('fs');
var logger = require('winston').loggers.get('spi');
var perflog = require('winston').loggers.get('perf');
var async = require('async');
var Tree = require('../../spi/tree');
var FSFile = require('./file');
var SMBError = require('../../smberror');
var utils = require('../../utils');
var mkdirp = require('mkdirp');
/**
* Creates an instance of Tree.
*
* @constructor
* @this {FSTree}
* @param {FSShare} share parent share
*/
var FSTree = function (share) {
if (!(this instanceof FSTree)) {
return new FSTree(share);
}
this.share = share;
Tree.call(this, this.share.config);
};
// the FSTree prototype inherits from Tree
util.inherits(FSTree, Tree);
/**
* Create a new FSFile instance for use by the tree.
* @param name The name of the file to create.
* @param {Function} cb callback called on completion
* @param {SMBError} cb.error error (non-null if an error occurred)
* @param {FSFile} cb.file FSFile instance
*/
FSTree.prototype.createFileInstance = function (name, cb) {
FSFile.createInstance(name, this, cb);
};
//---------------------------------------------------------------------< Tree >
/**
* Test whether or not the specified file exists.
*
* @param {String} name file name
* @param {Function} cb callback called with the result
* @param {SMBError} cb.error error (non-null if an error occurred)
* @param {Boolean} cb.exists true if the file exists; false otherwise
*/
FSTree.prototype.exists = function (name, cb) {
logger.debug('[%s] tree.exists %s', this.share.config.backend, name);
perflog.debug('%s Tree.exists.fs.stat', name);
var self = this;
fs.stat(Path.join(this.share.path, name), function (err, stats) {
if (err && err.code !== 'ENOENT') {
cb(SMBError.fromSystemError(err, 'cannot determine existence of path due to unexpected error ' + name));
} else {
logger.debug('[%s] tree.exists %s > %s', self.share.config.backend, name, !err);
cb(null, !err);
}
});
};
/**
* Open an existing file.
*
* @param {String} name file name
* @param {Function} cb callback called with the opened file
* @param {SMBError} cb.error error (non-null if an error occurred)
* @param {File} cb.file opened file
*/
FSTree.prototype.open = function (name, cb) {
logger.debug('[%s] tree.open %s', this.share.config.backend, name);
this.createFileInstance(name, cb);
};
/**
* List entries, matching a specified pattern.
*
* @param {String} pattern pattern
* @param {Function} cb callback called with an array of matching files
* @param {SMBError} cb.error error (non-null if an error occurred)
* @param {File[]} cb.files array of matching files
*/
FSTree.prototype.list = function (pattern, cb) {
logger.debug('[%s] tree.list %s', this.share.config.backend, pattern);
var parentPath = utils.getParentPath(pattern) || '';
var realParentPath = Path.join(this.share.path, parentPath);
var filter = utils.getPathName(pattern);
// list will receive two types of patterns:
// 1. request all items from a directory. sample: /some/directory/*
// 2. request for a single item. sample: /some/directory
// the difference is the inclusion of the asterisk at the end of the pattern. for #1, do a readdir of the
// parent directory. for #2, just return the single item if it exists. for #2, avoid using readdir so that the
// entire directory doesn't need to be read for just a single file
var self = this;
function evaluateFilter(next) {
if (filter === '*') {
// wildcard specified
perflog.debug('%s Tree.list.fs.readdir', pattern);
fs.readdir(realParentPath, function (err, files) {
next(err ? SMBError.fromSystemError(err, 'cannot list pattern due to unexpected error ' + pattern) : null, files);
});
} else {
// file name specified
self.exists(pattern, function (err, exists) {
next(err, exists ? [ filter ] : []);
});
}
}
function createFileInstances(names, next) {
async.reduce(names, [],
function (memo, name, callback) {
self.createFileInstance(Path.join(parentPath, name), function (err, file) {
if (err) {
logger.error('[%s] tree.list %s: unexpected error while attempting to include %s in list', self.share.config.backend, pattern, name, err);
} else {
memo.push(file);
}
logger.debug('[%s] tree.list %s > %d', self.share.config.backend, pattern, memo.length);
callback(null, memo);
});
},
next
);
}
async.waterfall([ evaluateFilter, createFileInstances ], cb);
};
/**
* Create a new file.
*
* @param {String} name file name
* @param {Function} cb callback called on completion
* @param {SMBError} cb.error error (non-null if an error occurred)
* @param {File} cb.file created file
*/
FSTree.prototype.createFile = function (name, cb) {
logger.debug('[%s] tree.createFile %s', this.share.config.backend, name);
var self = this;
var filePath = Path.join(this.share.path, name);
mkdirp(utils.getParentPath(filePath), function (err) {
if (err) {
cb(err);
} else {
perflog.debug('%s Tree.createFile.fs.open', name);
fs.open(filePath, 'wx', function (err, fd) {
if (err) {
cb(SMBError.fromSystemError(err, 'cannot create file due to unexpected error ' + name));
} else {
perflog.debug('%s Tree.createFile.fs.close', name);
fs.close(fd, function (err) {
if (err) {
cb(SMBError.fromSystemError(err, 'cannot close file due to unexpected error ' + name));
} else {
self.createFileInstance(name, cb);
}
});
}
});
}
});
};
/**
* Create a new directory.
*
* @param {String} name directory name
* @param {Function} cb callback called on completion
* @param {SMBError} cb.error error (non-null if an error occurred)
* @param {File} cb.file created directory
*/
FSTree.prototype.createDirectory = function (name, cb) {
logger.debug('[%s] tree.createDirectory %s', this.share.config.backend, name);
var self = this;
mkdirp(Path.join(this.share.path, name), function (err) {
if (err) {
cb(SMBError.fromSystemError(err,'cannot create directory due to unexpected error ' + name));
} else {
self.createFileInstance(name, cb);
}
});
};
/**
* Delete a file.
*
* @param {String} name file name
* @param {Function} cb callback called on completion
* @param {SMBError} cb.error error (non-null if an error occurred)
*/
FSTree.prototype.delete = function (name, cb) {
logger.debug('[%s] tree.delete %s', this.share.config.backend, name);
perflog.debug('%s Tree.delete.fs.unlink', name);
fs.unlink(Path.join(this.share.path, name), SMBError.systemToSMBErrorTranslator(cb, 'cannot delete file due to unexpected error ' + name));
};
/**
* Delete a directory. It must be empty in order to be deleted.
*
* @param {String} name directory name
* @param {Function} cb callback called on completion
* @param {SMBError} cb.error error (non-null if an error occurred)
*/
FSTree.prototype.deleteDirectory = function (name, cb) {
logger.debug('[%s] tree.deleteDirectory %s', this.share.config.backend, name);
perflog.debug('%s Tree.deleteDirectory.fs.rmdir', name);
var toDelete = Path.join(this.share.path, name);
if (toDelete && toDelete != '/') {
fs.rmdir(toDelete, SMBError.systemToSMBErrorTranslator(cb));
} else {
cb(SMBError.fromSystemError({message: 'cannot delete root directory'}));
}
};
/**
* Rename a file or directory.
*
* @param {String} oldName old name
* @param {String} newName new name
* @param {Function} cb callback called on completion
* @param {SMBError} cb.error error (non-null if an error occurred)
*/
FSTree.prototype.rename = function (oldName, newName, cb) {
logger.debug('[%s] tree.rename %s to %s', this.share.config.backend, oldName, newName);
perflog.debug('%s Tree.rename.fs.rename %s', oldName, newName);
fs.rename(Path.join(this.share.path, oldName), Path.join(this.share.path, newName), SMBError.systemToSMBErrorTranslator(cb, 'cannot rename due to unexpected error ' + oldName + ' > ' + newName));
};
/**
* Disconnect this tree.
*
* @param {Function} cb callback called on completion
* @param {SMBError} cb.error error (non-null if an error occurred)
*/
FSTree.prototype.disconnect = function (cb) {
logger.debug('[%s] tree.disconnect', this.share.config.backend);
// there's nothing to do here
process.nextTick(function () { cb(); });
};
module.exports = FSTree;