UNPKG

node-nfsc

Version:

NFS client bindings for node.js

870 lines (832 loc) 40.6 kB
'use strict'; /* * Copyright 2017 Scality * * Licensed 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 CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @authors: * Guillaume Gimenez <ggim@scality.com> */ let impl; try { impl = require('../build/Release/node-nfsc'); } catch (err) { impl = require('../build/Debug/node-nfsc'); } const ErrorFactory = require('./errorFactory.js'); const defaultProtocol = 'udp'; const defaultUid = process.geteuid(); const defaultGid = process.getegid(); const defaultAuthenticationMethod = 'unix'; const defaultTimeout = 25; function int53(i) { if (i < Number.MIN_SAFE_INTEGER || i > Number.MAX_SAFE_INTEGER) throw '53-bit overflow'; return i; } const nfsv3ErrorFactory = new ErrorFactory('../errors/NFSv3.json'); class V3 { /** * Construct a new NFSv3 Client instance with the given parameters * * @param {object} options contains the export parameters * @param {string} options.host host name or IP address of the NFSv3 server * @param {string} options.exportPath path of the export on the NFSc3 server * @param {string} options.protocol may be 'udp' or 'tcp' * @param {integer} options.uid User ID to use on requests to the server * @param {integer} options.gid Group ID to use on requests to the server * @param {string} options.authenticationMethod 'none', 'unix', 'krb5', * 'krb5i' or 'krb5p' * @param {string} options.timeout timeout in seconds for network operations */ constructor(opts) { const options = opts ? opts : {}; const host = options.host; const exportPath = options.exportPath; const protocol = options.protocol === undefined ? defaultProtocol : options.protocol; const uid = options.uid === undefined ? defaultUid : options.uid; const gid = options.gid === undefined ? defaultGid : options.gid; const authenticationMethod = options.authenticationMethod === undefined ? defaultAuthenticationMethod : options.authenticationMethod; const timeout = options.timeout === undefined ? defaultTimeout : options.timeout; this.client = new impl.Client(host, exportPath, protocol, uid, gid, authenticationMethod, timeout); /* unix modes */ this.MODE_IRWXU = 0o700; this.MODE_IRUSR = 0o400; this.MODE_IWUSR = 0o200; this.MODE_IXUSR = 0o100; this.MODE_IRWXG = 0o070; this.MODE_IRGRP = 0o040; this.MODE_IWGRP = 0o020; this.MODE_IXGRP = 0o010; this.MODE_IRWXO = 0o007; this.MODE_IROTH = 0o004; this.MODE_IWOTH = 0o002; this.MODE_IXOTH = 0o001; /* constants from the RFC 1813 */ /* ACCESS3 */ this.ACCESS_READ = 0x0001; this.ACCESS_LOOKUP = 0x0002; this.ACCESS_MODIFY = 0x0004; this.ACCESS_EXTEND = 0x0008; this.ACCESS_DELETE = 0x0010; this.ACCESS_EXECUTE = 0x0020; /* WRITE3 stable_how */ this.WRITE_UNSTABLE = 0; this.WRITE_DATA_SYNC = 1; this.WRITE_FILE_SYNC = 2; /* CREATE3 createmode3 */ this.CREATE_UNCHECKED = 0; this.CREATE_GUARDED = 1; this.CREATE_EXCLUSIVE = 2; /* ftype3 */ this.NF3REG = 'NF3REG'; this.NF3DIR = 'NF3DIR'; this.NF3BLK = 'NF3BLK'; this.NF3CHR = 'NF3CHR'; this.NF3LNK = 'NF3LNK'; this.NF3SOCK = 'NF3SOCK'; this.NF3FIFO = 'NF3FIFO'; } _error(code, info) { return nfsv3ErrorFactory.create(code, info); } /** * Procedure MNT maps a pathname on the server to a file * handle. The pathname is an ASCII string that describes a * directory on the server. If the call is successful * (MNT3_OK), the server returns an NFS version 3 protocol * file handle and a vector of RPC authentication flavors * that are supported with the client's use of the file * handle (or any file handles derived from it). The * authentication flavors are defined in Section 7.2 and * section 9 of [RFC1057]. * * @param {function} callback(err: null || {status: string}, * root_file_handle: Buffer); * On success, err is null. Continue execution with * the file handle of the root inode of the export. * On error, err contains information about the error. * @returns {undefined} */ mount(callback) { this.client.mount3((err, root_fh) => { if (err) return callback(this._error(err)); return callback(null, root_fh); }); } /** * Procedure UMNT removes the mount list entry for the * directory that was previously the subject of a MNT call * from this client. AUTH_UNIX authentication or better is * required. * * @param {function} callback(err: null || {status: string}); * @returns {undefined} */ unmount(callback) { this.client.unmount3(err => { if (err) return callback(this._error(err)); return callback(null); }); } /** * Procedure LOOKUP searches a directory for a specific name * and returns the file handle for the corresponding file * system object. * * @param {Buffer} dir The file handle for the directory to search. * @param {string} name The filename to be searched for. * @param {function} callback(err: null || {status: string}, * object: Buffer, * obj_attributes: Object || null, * dir_attributes: Object || null); * On success, err is null. Continue execution with * the 'object' handle, its attributes and the attributes * of the directory as well. * On error, err contains information about the error. * @returns {undefined} */ lookup(dir, name, callback) { this.client.lookup3(dir, name, (err, object, obj_attributes, dir_attributes) => { if (err) return callback(this._error(err)); return callback(null, object, obj_attributes, dir_attributes); }); } /** * Procedure GETATTR retrieves the attributes for a specified * file system object. The object is identified by the file * handle that the server returned as part of the response * from a LOOKUP, CREATE, MKDIR, SYMLINK, MKNOD, or * READDIRPLUS procedure (or from the MOUNT procedure) * * @param {Buffer} object The file handle of an object whose attributes are * to be retrieved. * @param {function} callback(err: null || {status: string}, * obj_attributes: Object); * On success, err is null. Continue execution with * attributes for the object. * On error, err contains information about the error. * @returns {undefined} */ getattr(object, callback) { this.client.getattr3(object, (err, obj_attributes) => { if (err) return callback(this._error(err)); return callback(null, obj_attributes); }); } /** * Procedure READDIR retrieves a variable number of entries, * in sequence, from a directory and returns the name and * file identifier for each, with information to allow the * client to request additional directory entries in a * subsequent READDIR request. * * @param {Buffer} dir The file handle for the directory to be read. * @param {Object} options * @param {Buffer} options.cookie * This should be set to null in the first subsequent * requests, it should be a cookie as returned by the * server. * @param {Buffer} options.cookieverf * This should be set to null in the first request to * read the directory. On subsequent requests, it * should be a cookieverf as returned by the server. * The cookieverf must match that returned by the * READDIR in which the cookie was acquired. * @param {integer} count * The maximum size of the READDIR3resok structure, in * bytes. The size must include all XDR overhead. The * server is free to return less than count bytes of * data. * @param {function} callback(err: null || {status: string}, * dir_attributes: Object || null, * cookieverf: Buffer, * eof: bool, [ { cookie: Buffer, * fileid: Buffer, * name: string}, ... ]); * @returns {undefined} */ readdir(dir, options, callback) { const opts = options ? options : {}; opts.cookie = opts.cookie || null; opts.cookieverf = opts.cookieverf || null; /* like a Linux NFS Client */ opts.count = opts.count || 32688; this.client.readdir3(dir, opts.cookie, opts.cookieverf, opts.count, (err, dir_attributes, cookieverf, eof, entries) => { if (err) return callback(this._error(err)); return callback(null, dir_attributes, cookieverf, eof, entries); }); } /** * Procedure READDIRPLUS retrieves a variable number of * entries from a file system directory and returns complete * information about each along with information to allow the * client to request additional directory entries in a * subsequent READDIRPLUS. READDIRPLUS differs from READDIR * only in the amount of information returned for each * entry. In READDIR, each entry returns the filename and * the fileid. In READDIRPLUS, each entry returns the name, * the fileid, attributes (including the fileid), and file * handle. * * @param {Buffer} dir The file handle for the directory to be read. * @param {Object} options * @param {Buffer} options.cookie * This should be set to null in the first subsequent * requests, it should be a cookie as returned by the * server. * @param {Buffer} options.cookieverf * This should be set to null in the first request to * read the directory. On subsequent requests, it * should be a cookieverf as returned by the server. * The cookieverf must match that returned by the * READDIR in which the cookie was acquired. * @param {integer} dircount * The maximum number of bytes of directory information * returned. This number should not include the size of * the attributes and file handle portions of the result. * @param {integer} maxcount * The maximum size of the READDIR3resok structure, in * bytes. The size must include all XDR overhead. The * server is free to return less than count bytes of * data. * @param {function} callback(err: null || {status: string}, * dir_attributes: Object || null, * cookieverf: Buffer, * eof: bool, [ { handle: Buffer, * attributes: Object || null, * cookie: Buffer, * fileid: Buffer, * name: string}, ... ]); * @returns {undefined} */ readdirplus(dir, options, callback) { const opts = options ? options : {}; opts.cookie = opts.cookie || null; opts.cookieverf = opts.cookieverf || null; /* like a Linux NFS Client */ opts.dircount = opts.dircount || 8172; opts.maxcount = opts.maxcount || 32688; this.client.readdirplus3(dir, opts.cookie, opts.cookieverf, opts.dircount, opts.maxcount, (err, dir_attributes, cookieverf, eof, entries) => { if (err) return callback(this._error(err)); return callback(null, dir_attributes, cookieverf, eof, entries); }); } /** * Procedure ACCESS determines the access rights that a user, * as identified by the credentials in the request, has with * respect to a file system object. The client encodes the * set of permissions that are to be checked in a bit mask. * The server checks the permissions encoded in the bit mask. * A status of NFS3_OK is returned along with a bit mask * encoded with the permissions that the client is allowed. * * The results of this procedure are necessarily advisory in * nature. That is, a return status of NFS3_OK and the * appropriate bit set in the bit mask does not imply that * such access will be allowed to the file system object in * the future, as access rights can be revoked by the server * at any time. * * @param {Buffer} object The file handle for the file system object to * which access is to be checked. * @param {integer} access A bit mask of access permissions to check. * @param {function} callback(err: null || {status: string}, * access: integer, * post_obj_attr: Object || null); * @return {undefined} */ access(object, access, callback) { this.client.access3(object, access, (err, access, post_obj_attr) => { if (err) return callback(this._error(err)); return callback(null, access, post_obj_attr); }); } /** * Procedure NULL does not do any work. It is made available to * allow server response testing and timing. * * @param {function} callback(err: null || {status: string}) * On success, err is null. * On error, err contains information about the error. * @return {undefined} */ null(callback) { this.client.null3(err => { if (err) return callback(this._error(err)); return callback(null); }); } /** * Procedure READ reads data from a file. * * @param {Buffer} object The file handle of the file from which data is to * be read. This must identify a file system object * of type 'NF3REG'. * @param {integer} count The number of bytes of data that are to be read. * If count is 0, the READ will succeed and return 0 * bytes of data, subject to access permissions * checking. count must be less than or equal to the * value of the rtmax field in the FSINFO reply * structure for the file system that contains file. * If greater, the server may return only rtmax * bytes, resulting in a short read. * @param {integer} offset * The position within the file at which the read is * to begin. An offset of 0 means to read data * starting at the beginning of the file. If offset * is greater than or equal to the size of the file, * the status, NFS3_OK, is returned with count set to * 0 and eof set to TRUE, subject to access * permissions checking. * @param {function} callback(err: null || {status: string}, * eof: bool, * buf: Buffer, * obj_attributes: Object || null); * @returns {undefined} */ read(object, count, offset, callback) { this.client.read3(object, int53(count), int53(offset), (err, eof, buf, obj_attributes) => { if (err) return callback(this._error(err)); return callback(null, eof, buf, obj_attributes); }); } /** * Procedure WRITE writes data to a file. * * @param {Buffer} object The file handle for the file to which data is to * be written. This must identify a file system * object of type, NF3REG. * @param {integer} count The number of bytes of data to be written. If * count is 0, the WRITE will succeed and return a * count of 0, barring errors due to permissions * checking. The size of data must be less than or * equal to the value of the wtmax field in the * FSINFO reply structure for the file system that * contains file. If greater, the server may write * only wtmax bytes, resulting in a short write. * @param {integer} offset * The position within the file at which the write * is to begin. An offset of 0 means to write data * starting at the beginning of the file. * @param {stable_how} stable * If stable is FILE_SYNC, the server must commit * the data written plus all file system metadata to * stable storage before returning results. This * corresponds to the NFS version 2 protocol * semantics. Any other behavior constitutes a * protocol violation. If stable is DATA_SYNC, then * the server must commit all of the data to stable * storage and enough of the metadata to retrieve * the data before returning. The server implementor * is free to implement DATA_SYNC in the same * fashion as FILE_SYNC, but with a possible * performance drop. If stable is UNSTABLE, the * server is free to commit any part of the data and * the metadata to stable storage, including all or * none, before returning a reply to the client. * There is no guarantee whether or when any * uncommitted data will subsequently be committed * to stable storage. The only guarantees made by * the server are that it will not destroy any data * without changing the value of verf and that it * will not commit the data and metadata at a level * less than that requested by the client. See the * discussion on COMMIT n page 92 for more * information on if and when data is committed to * stable storage. * @param {Buffer} data The data to be written to the file. * @param {function} callback(err: null || {status: string, * file_wcc: {before: Object, * after: Object}}, * committed: stable_how, * count: integer, * verf: Buffer, * attrs: { before: Object, after: Object}); * * @note verf * This is a cookie that the client can use to determine * whether the server has changed state between a call to * WRITE and a subsequent call to either WRITE or COMMIT. * This cookie must be consistent during a single instance * of the NFS version 3 protocol service and must be * unique between instances of the NFS version 3 protocol * server, where uncommitted data may be lost. * @note file_wcc * Weak cache consistency data for the file. For a client * that requires only the post-write file attributes, * these can be found in file_wcc.after. Even though the * COMMIT failed, full wcc_data is returned to allow the * client to determine whether the file changed on the * server between calls to WRITE and COMMIT. * */ write(object, count, offset, stable, data, callback) { this.client.write3( object, count, offset, stable, data, (err, committed_or_file_wcc, count, verf, attrs) => { if (err) return callback(this._error(err, { file_wcc: committed_or_file_wcc})); return callback(null, committed_or_file_wcc, count, verf, attrs); }); } /** * Procedure COMMIT forces or flushes data to stable storage * that was previously written with a WRITE procedure call * with the stable field set to UNSTABLE. * * @param {Buffer} object The file handle for the file to which data is to * be flushed (committed). This must identify a file * system object of type, NF3REG. * @param {integer} count The number of bytes of data to flush. If count is * 0, a flush from offset to the end of file is * done. * @param {integer} offset * The position within the file at which the flush * is to begin. An offset of 0 means to flush data * starting at the beginning of the file. * @param {function} callback(err: null || {status: string, * file_wcc: {before: Object, * after: Object}}, * verf: Buffer, * attrs: { before: Object, after: Object}); */ commit(object, count, offset, callback) { this.client.commit3( object, count, offset, (err, verf_or_file_wcc, attrs) => { if (err) return callback(this._error(err, { file_wcc: verf_or_file_wcc})); return callback(null, verf_or_file_wcc, attrs); }); } /** * Procedure CREATE creates a regular file. * * @param {Buffer} dir The file handle for the directory in which the file * is to be created. * @param {string} name * The name that is to be associated with the created * file. * @param {createmode3} mode * One of UNCHECKED, GUARDED, and EXCLUSIVE. UNCHECKED * means that the file should be created without checking * for the existence of a duplicate file in the same * directory. In this case, how.obj_attributes is a sattr3 * describing the initial attributes for the file. GUARDED * specifies that the server should check for the presence * of a duplicate file before performing the create and * should fail the request with NFS3ERR_EXIST if a * duplicate file exists. If the file does not exist, the * request is performed as described for UNCHECKED. * EXCLUSIVE specifies that the server is to follow * exclusive creation semantics, using the verifier to * ensure exclusive creation of the target. No attributes * may be provided in this case, since the server may use * the target file metadata to store the createverf3 * verifier. * @param {Object|Buffer} arg * Depending on the mode, must be a 8 bytes buffer * containing the verifier or an Object complying with * setattr requirement. * @param {funcion} callback(err: null || {status: string, * dir_wcc: {before: Object, * after: Object}}, * object: Buffer || null, * obj_attrs: Object || null, * dir_wcc: Object); * */ create(dir, name, mode, arg, callback) { this.client.create3( dir, name, mode, arg, (err, object_or_dir_wcc, obj_attrs, dir_wcc) => { if (err) return callback(this._error(err, { dir_wcc: object_or_dir_wcc})); return callback(null, object_or_dir_wcc, obj_attrs, dir_wcc); }); } /** * Procedure REMOVE removes (deletes) an entry from a * directory. If the entry in the directory was the last * reference to the corresponding file system object, the * object may be destroyed. * * In general, REMOVE is intended to remove non-directory * file objects and RMDIR is to be used to remove * directories. However, REMOVE can be used to remove * directories, subject to restrictions imposed by either the * client or server interfaces. This had been a source of * confusion in the NFS version 2 protocol. * * @param {Buffer} dir * The file handle for the directory from which the entry * is to be removed. * @param {string} name * The name of the entry to be removed. * @param {function} callback(err: null || {status: string, * dir_wcc: {before: Object, * after: Object}}, * dir_wcc: Object); */ remove(dir, name, callback) { this.client.remove3(dir, name, (err, dir_wcc) => { if (err) return callback(this._error(err, {dir_wcc})); return callback(null, dir_wcc); }); } /** * Procedure RMDIR removes (deletes) a subdirectory from a * directory. If the directory entry of the subdirectory is * the last reference to the subdirectory, the subdirectory * may be destroyed. * * @param {Buffer} dir * The file handle for the directory from which the * subdirectory is to be removed. * @param {string} name * The name of the subdirectory to be removed. * @param {function} callback(err: null || {status: string, * dir_wcc: {before: Object, * after: Object}}, * dir_wcc: Object); */ rmdir(dir, name, callback) { this.client.rmdir3(dir, name, (err, dir_wcc) => { if (err) return callback(this._error(err, {dir_wcc})); return callback(null, dir_wcc); }); } /** * Procedure MKDIR creates a new subdirectory. * * @param {Buffer} dir * The file handle for the directory in which the * subdirectory is to be created. * @param {string} name * The name that is to be associated with the created * subdirectory. * @param {Object} attrs * The initial attributes for the subdirectory. * @param {funcion} callback(err: null || {status: string, * dir_wcc: {before: Object, * after: Object}}, * object: Buffer || null, * obj_attrs: Object || null, * dir_wcc: Object); * */ mkdir(dir, name, arg, callback) { this.client.mkdir3( dir, name, arg, (err, object_or_dir_wcc, obj_attrs, dir_wcc) => { if (err) return callback(this._error(err, { dir_wcc: object_or_dir_wcc})); return callback(null, object_or_dir_wcc, obj_attrs, dir_wcc); }); } /** * Procedure SETATTR changes one or more of the attributes of * a file system object on the server. The new attributes are * specified by a sattr3 structure. * * @param {Buffer} object * The file handle for the object. * @param {Object} new_attributes * A sattr3 structure containing booleans and * enumerations describing the attributes to be set and * the new values for those attributes. * @param {Object} guard * A client may request that the server check that the * object is in an expected state before performing the * SETATTR operation. To do this, it sets the argument * an object and the client passes a time value in ctime * and ctime_nsec. If guard is set, the server * must compare the value of ctime to the * current ctime of the object. If the values are * different, the server must preserve the object * attributes and must return a status of * NFS3ERR_NOT_SYNC. If guard is null, the server * will not perform this check. * @param {function} callback(err: null || {status: string, wcc: Object}, * wcc: Object); * @note sattr3 object * The known attributes for this object are: * - atime, atime_nsec * - mtime, mtime_nsec * - size * - mode * - uid * - gid * If one of these attributes is left undefined, the corresponding * attributes will remain unmodified on the server. */ setattr(object, attrs, guard, callback) { this.client.setattr3( object, attrs, guard, (err, wcc) => { if (err) return callback(this._error(err, {wcc})); return callback(null, wcc); }); } /** * Procedure RENAME renames the file identified by from.name * in the directory, from.dir, to to.name in the di- rectory, * to.dir. The operation is required to be atomic to the * client. To.dir and from.dir must reside on the same file * system and server. * * If the directory, to_dir, already contains an entry with * the name, to_name, the source object must be compatible * with the target: either both are non-directories or both * are directories and the target must be empty. If * compatible, the existing target is removed before the * rename occurs. If they are not compatible or if the target * is a directory but not empty, the server should return the * error, NFS3ERR_EXIST. * * @param {Buffer} from_dir * The file handle for the directory from which the * entry is to be renamed. * @param {string} from_name * The name of the entry that identifies the object to * be renamed. * @param {Buffer} from_dir * The file handle for the directory to which the * object is to be renamed. * @param {string} from_name * The new name for the object. * @param {function} callback(err: null || {status: string, * fromdir_wcc: object, * todir_wcc: object}, * fromdir_wcc: Object, * todir_wcc: Object); */ rename(from_dir, from_name, to_dir, to_name, callback) { this.client.rename3( from_dir, from_name, to_dir, to_name, (err, fromdir_wcc, todir_wcc) => { if (err) return callback(this._error(err, {fromdir_wcc, todir_wcc})); return callback(null, fromdir_wcc, todir_wcc); }); } /** * Procedure MKNOD creates a new special file of the specified ftype3, * Special files can be device files, named pipes or sockets. * * @param {Buffer} dir * The file handle for the directory in which the * special file is to be created. * @param {string} name * The name that is to be associated with the created * special file. * @param {string} type * The type of the object to be created. * @param {object} mknodData * A object identifying the type of the special file to be * created along with the data and attributes appropriate * to the type of the special file. * @param {object} mknodData.attrs * The initial attributes for the special file. * complying with setattr requirement. * @param {object} mknodData.rdev * Only when creating a character special file (type is * NF3CHR) or a block special file (type is NF3BLK), * rdev includes the major number stored in 'major' and * the minor number stored in 'minor'. * @param {function} callback(err: null || {status: string, * dir_wcc: Object}, * object: Buffer || null, * obj_attrs: Object || null, * dir_wcc: Object); */ mknod(dir, name, type, mknodData, callback) { this.client.mknod3( dir, name, type, mknodData, (err, object_or_dir_wcc, obj_attrs, dir_wcc) => { if (err) return callback(this._error(err, { dir_wcc: object_or_dir_wcc})); return callback(null, object_or_dir_wcc, obj_attrs, dir_wcc); }); } /** * Procedure SYMLINK creates a new symbolic link. * * @param {Buffer} dir * The file handle for the directory in which the * symbolic link is to be created. * @param {string} name * The name that is to be associated with the created * symbolic link. * @param {object} attrs * The initial attributes for the symbolic link. * complying with setattr requirement. * @param {string} path * The string containing the symbolic link data. * @param {funcion} callback(err: null || {status: string, * dir_wcc: Object}, * object: Buffer || null, * obj_attrs: Object || null, * dir_wcc: Object); */ symlink(dir, name, attrs, path, callback) { this.client.symlink3( dir, name, attrs, path, (err, object_or_dir_wcc, obj_attrs, dir_wcc) => { if (err) return callback(this._error(err, { dir_wcc: object_or_dir_wcc})); return callback(null, object_or_dir_wcc, obj_attrs, dir_wcc); }); } /** * Procedure READLINK reads the data associated with a * symbolic link. The data is an ASCII string that is opaque * to the server. That is, whether created by the NFS * version 3 protocol software from a client or created * locally on the server, the data in a symbolic link is not * interpreted when created, but is simply stored. * * @param {object} object * @param {function} callback(err: null || {status: string, * obj_attrs: Object}, * path: string, * obj_attrs: Object); */ readlink(object, callback) { this.client.readlink3(object, (err, path_or_obj_attrs, obj_attrs) => { if (err) return callback(this._error(err, { obj_attrs: path_or_obj_attrs})); return callback(null, path_or_obj_attrs, obj_attrs); }); } /** * Procedure FSSTAT retrieves volatile file system state information. * * @param {buffer} root_fh file handle of the root inode of the export, * returned by the mount procedure. * @param {function} callback(err: null || {status: string, * obj_attrs: Object}, * obj_attrs: Object, * tbytes, fbytes, abytes, * tfiles, ffiles, afiles, invarsec); */ fsstat(root_fh, callback) { this.client.fsstat3(root_fh, (err, obj_attributes, tbytes, fbytes, abytes, tfiles, ffiles, afiles, invarsec) => { if (err) return callback(err, obj_attributes); return callback(null, obj_attributes, tbytes, fbytes, abytes, tfiles, ffiles, afiles, invarsec); }); } } module.exports = { V3 };