├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── lib ├── main.js ├── util.js ├── zfs.js └── zpool.js ├── package.json └── test ├── zfs └── zfs.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .*.swp 3 | .idea 4 | .vscode 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "forin": true, 5 | "immed": true, 6 | "latedef": true, 7 | "newcap": true, 8 | "noarg": true, 9 | "noempty": true, 10 | "undef": true, 11 | "trailing": true, 12 | "node": true, 13 | "nomen": false, 14 | "plusplus": false 15 | } 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Jakob Borg 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | - The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This project is not actively maintained 2 | 3 | Issues and pull requests on this repository may not be acted on in a timely 4 | manner, or at all. You are of course welcome to use it anyway. You are even 5 | more welcome to fork it and maintain the results. 6 | 7 | ![Unmaintained](https://nym.se/img/unmaintained.jpg) 8 | 9 | __ ___ 10 | /\ \ /'___\ 11 | ___ ___ \_\ \ __ ____ /\ \__/ ____ 12 | /' _ `\ / __`\ /'_` \ /'__`\ _______/\_ ,`\\ \ ,__\/',__\ 13 | /\ \/\ \/\ \L\ \/\ \L\ \/\ __//\______\/_/ /_\ \ \_/\__, `\ 14 | \ \_\ \_\ \____/\ \___,_\ \____\/______/ /\____\\ \_\\/\____/ 15 | \/_/\/_/\/___/ \/__,_ /\/____/ \/____/ \/_/ \/___/ 16 | 17 | [![Build Status](https://secure.travis-ci.org/calmh/node-zfs.png)](http://travis-ci.org/calmh/node-zfs) 18 | 19 | node-zfs 20 | ======== 21 | 22 | node-zfs is a simple wrapper around the `zfs` and `zpool` commands. It enables 23 | ZFS management scripting using Node.js under SmartOS, Solaris, OpenIndiana and 24 | FreeBSD. Node-zfs is also tested successfully on Ubuntu. 25 | 26 | # Installation 27 | 28 | Using npm: 29 | 30 | ``` 31 | $ npm install --save zfs 32 | ``` 33 | # Usage 34 | 35 | ``` 36 | var zfs-filesystem = require('zfs'); 37 | ``` 38 | 39 | ##Now you can address the zfs library as follows: 40 | 41 | ### To use the zfs command implementation: 42 | ``` 43 | zfs-filesystem.zfs. 44 | ``` 45 | ###To use the zpool implementation: 46 | 47 | ``` 48 | zfs-filesystem.zpool. 49 | ``` 50 | 51 | ## Implemented commands for the ZFS tool 52 | 53 | ###ZFS list 54 | 55 | The list command lists a specific zfs dataset or all datasets. All possible options can be found inside the lib/zfs.js file. 56 | 57 | ``` 58 | zfs-filesystem.zfs.list(function (err, list) { 59 | console.log(list) 60 | }); 61 | ``` 62 | 63 | ###ZFS get 64 | 65 | Get the parameters of a specific dataset or all datasets. All possible options can be found inside the lib/zfs.js file. 66 | 67 | ```js 68 | var opts = { 69 | name: 'my-dataset-name', 70 | property: 'quota' 71 | }; 72 | 73 | zfs-filesystem.zfs.get(opts, function (err, options) { 74 | console.log(options); 75 | }); 76 | ``` 77 | 78 | ###ZFS set 79 | 80 | Set a specific option for a given dataset. All possible options can be found inside the lib/zfs.js file. 81 | 82 | ```js 83 | var opts = { 84 | name: 'my-dataset-name', 85 | property: 'quota', 86 | value: '1024G' 87 | }; 88 | 89 | zfs-filesystem.zfs.set(opts, function (err, output) { 90 | console.log(output); 91 | }); 92 | ``` 93 | 94 | ###ZFS Destroy 95 | 96 | Remove a dataset from the ZFS filesystem 97 | 98 | ```js 99 | var opts = { 100 | name: 'my-dataset-name' 101 | }; 102 | 103 | zfs-filesystem.zfs.destroy(opts, function (err, output) { 104 | console.log(output); 105 | }); 106 | ``` 107 | 108 | ###ZFS Create 109 | 110 | Create a new dataset inside the ZFS filesystem. All possible options can be found inside the lib/zfs.js file. 111 | 112 | ```js 113 | var opts = { 114 | name: 'my-new-dataset-name' 115 | }; 116 | 117 | zfs-filesystem.zfs.create(opts, function (err, output) { 118 | console.log(output); 119 | }); 120 | ``` 121 | 122 | ###ZFS Snapshot 123 | 124 | Creates a snapshot with the given name. All possible options can be found inside the lib/zfs.js file. 125 | 126 | ```js 127 | var opts = { 128 | name: 'my-new-snapshot-name', 129 | dataset: 'my-dataset-name' 130 | }; 131 | 132 | zfs-filesystem.zfs.snapshot(opts, function (err, output) { 133 | console.log(output); 134 | }); 135 | ``` 136 | 137 | ###ZFS Clone 138 | 139 | Creates a clone of the given snapshot. All possible options can be found inside the lib/zfs.js file. 140 | 141 | ```js 142 | var opts = { 143 | snapshot: 'my-snapshot-name', 144 | dataset: 'my-mountpoint-name' 145 | }; 146 | 147 | zfs-filesystem.zfs.clone(opts, function (err, output) { 148 | console.log(output); 149 | }); 150 | ``` 151 | 152 | 153 | ### ZFS Mount 154 | 155 | Mount the filesystem with the specified name or all filesystems. All possible options can be found inside the lib/zfs.js file. 156 | 157 | ```js 158 | var opts = { 159 | dataset: 'my-filesystem-name' 160 | }; 161 | 162 | zfs-filesystem.zfs.mount(opts, function (err, output) { 163 | console.log(output); 164 | }); 165 | ``` 166 | ### ZFS Unmount 167 | 168 | Unmount the filesystem or the mountpoint or all filesystems. All possible options can be found inside the lib/zfs.js file. 169 | 170 | ```js 171 | var opts = { 172 | name: 'my-filesystem-name', 173 | force: true 174 | }; 175 | 176 | zfs-filesystem.zfs.unmount(opts, function (err, output) { 177 | console.log(output); 178 | }); 179 | ``` 180 | 181 | ### ZFS Send 182 | 183 | Initiates a send of a given snapshot and returns a readable stream. All possible options can be found inside the lib/zfs.js file. 184 | 185 | ```js 186 | var opts = { 187 | snapshot : '/pool/dataset@snapshot', 188 | verbose : true 189 | }; 190 | 191 | zfs-filesystem.zfs.send(opts, function (err, sendStream) { 192 | sendStream.on('error', function (err) { 193 | console.error(err); 194 | }); 195 | 196 | sendStream.on('verbose', function (data) { 197 | console.log(data); 198 | }); 199 | 200 | sendStream.pipe(process.stdout).on('end', function () { 201 | console.log('done'); 202 | }); 203 | }); 204 | ``` 205 | 206 | ### ZFS Receive 207 | 208 | Initiates a receive and returns a writable stream to which a send stream may be written. All possible options can be found inside the lib/zfs.js file. 209 | 210 | ```js 211 | var opts = { 212 | dataset : '/pool2/dataset', 213 | verbose : true 214 | }; 215 | 216 | zfs-filesystem.zfs.receive(opts, function (err, receiveStream) { 217 | receiveStream.on('error', function (err) { 218 | console.error(err); 219 | }); 220 | 221 | receiveStream.on('verbose', function (data) { 222 | console.log(data); 223 | }); 224 | 225 | process.stdin.pipe(receiveStream).on('end', function () { 226 | console.log('done'); 227 | }); 228 | }); 229 | ``` 230 | 231 | ## Implemented commands for the ZPool tool 232 | 233 | ###ZPool Create 234 | 235 | Creates a new storage pool containing the virtual devices specified. All possible options can be found inside the lib/zpool.js file. 236 | 237 | ```js 238 | var opts = { 239 | name: 'my-datastore', 240 | devices: ['/dev/vdb', '/dev/vdc'] 241 | }; 242 | 243 | zfs-filesystem.zpool.create(opts, function (err, output) { 244 | console.log(output); 245 | }); 246 | ``` 247 | 248 | ###ZPool Add 249 | 250 | Adds the specified virtual devices to the given pool. All possible options can be found inside the lib/zpool.js file. 251 | 252 | ```js 253 | var opts = { 254 | name: 'my-datastore', 255 | devices: '/dev/vdd' 256 | }; 257 | 258 | zfs-filesystem.zpool.add(opts, function (err, output) { 259 | console.log(output); 260 | }); 261 | ``` 262 | 263 | ###ZPool Set 264 | 265 | Sets the given property on the specified pool. See the zpool manpage for possible options and values. 266 | 267 | ```js 268 | var opts = { 269 | name: 'my-datastore', 270 | property: 'comment', 271 | value: 'Added some comment to a datastore' 272 | }; 273 | 274 | zfs-filesystem.zpool.set(opts, function (err, output) { 275 | console.log(output); 276 | }); 277 | ``` 278 | 279 | ###ZPool Destroy 280 | 281 | Destroys the given pool, freeing up any devices for other use. 282 | 283 | ```js 284 | var opts = { 285 | name: 'my-datastore', 286 | }; 287 | 288 | zfs-filesystem.zpool.destroy(opts, function (err, output) { 289 | console.log(output); 290 | }); 291 | ``` 292 | 293 | ###ZPool List 294 | 295 | Lists the given pools along with a health status and space usage. If no pools are specified, all pools in the system are listed. 296 | 297 | ```js 298 | zfs-filesystem.zpool.list(function (err, output) { 299 | console.log(output); 300 | }); 301 | ``` 302 | 303 | ###ZPool Get 304 | 305 | Retrieves the given list of properties. When no name and property specified, it shows all properties for all datastores. See the zpool manpage for possible options and values. 306 | 307 | ```js 308 | zfs-filesystem.zpool.get(function (err, output) { 309 | console.log(output); 310 | }); 311 | ``` 312 | 313 | License 314 | ------- 315 | 316 | MIT 317 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | var zfs = require('./zfs'), 2 | zpool = require('./zpool'); 3 | 4 | //ZFS EXPORTS 5 | exports.get = zfs.get; 6 | exports.set = zfs.set; 7 | exports.list = zfs.list; 8 | exports.destroy = zfs.destroy; 9 | exports.create = zfs.create; 10 | exports.snapshot = zfs.snapshot; 11 | exports.clone = zfs.clone; 12 | exports.mount = zfs.mount; 13 | exports.unmount = zfs.unmount; 14 | exports.send = zfs.send; 15 | exports.receive = zfs.receive; 16 | 17 | exports.zfs = { 18 | set: zfs.set, 19 | get: zfs.get, 20 | destroy: zfs.destroy, 21 | create: zfs.create, 22 | list: zfs.list, 23 | snapshot: zfs.snapshot, 24 | clone: zfs.clone, 25 | mount: zfs.mount, 26 | unmount: zfs.unmount, 27 | send : zfs.send, 28 | receive : zfs.receive 29 | }; 30 | 31 | //ZPooL EXPORTS 32 | exports.zpool = { 33 | set: zpool.set, 34 | destroy: zpool.destroy, 35 | create: zpool.create, 36 | add: zpool.add, 37 | list: zpool.list, 38 | get: zpool.get 39 | //TODO:status 40 | }; 41 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | if (!fs.existsSync) { 4 | fs.existsSync = path.existsSync; 5 | } 6 | 7 | 8 | function compact(array) { 9 | return array.filter(function (i) { 10 | if (typeof i.length !== 'undefined') 11 | return i.length > 0; 12 | return !!i; 13 | }); 14 | } 15 | 16 | function findCmd(name) { 17 | "use strict"; 18 | 19 | var paths = process.env['PATH'].split(':'); 20 | var pathLen = paths.length; 21 | for (var i = 0; i < pathLen; i++) { 22 | var sp = path.resolve(paths[i]); 23 | var fname = path.normalize(path.join(sp, name)); 24 | if (fs.existsSync(fname)) { 25 | return fname; 26 | } 27 | } 28 | 29 | return null; 30 | } 31 | 32 | 33 | function parseNumber(str) { 34 | var m = str.match(/^([0-9.]+)([KMGT]?)$/); 35 | if (!m) { 36 | return -1; 37 | } 38 | 39 | var n = parseFloat(m[1]); 40 | if (m[2] === 'K') { 41 | n *= 1024; 42 | } else if (m[2] === 'M') { 43 | n *= 1024 * 1024; 44 | } else if (m[2] === 'G') { 45 | n *= 1024 * 1024 * 1024; 46 | } else if (m[2] === 'T') { 47 | n *= 1024 * 1024 * 1024 * 1024; 48 | } else if (m[2] === 'P') { 49 | n *= 1024 * 1024 * 1024 * 1024 * 1024; 50 | } 51 | 52 | return Math.floor(n); 53 | } 54 | 55 | function Property(info) { 56 | "use strict"; 57 | 58 | var obj = Object.create({}); 59 | if (typeof info === 'string') { 60 | info = info.split(/\t+/, 4); 61 | } 62 | if (info.length !== 4) { 63 | return null; 64 | } 65 | 66 | obj.name = info[0]; 67 | obj.property = info[1]; 68 | obj.value = info[2]; 69 | obj.source = info[3]; 70 | 71 | Object.freeze(obj); 72 | return obj; 73 | } 74 | 75 | exports.compact = compact; 76 | exports.parseNumber = parseNumber; 77 | exports.Property = Property; 78 | exports.findCmd = findCmd; 79 | -------------------------------------------------------------------------------- /lib/zfs.js: -------------------------------------------------------------------------------- 1 | var cp = require('child_process'); 2 | var util = require('./util'); 3 | 4 | 5 | var zfsBin = util.findCmd('zfs'); 6 | function zfs(args, callback) { 7 | "use strict"; 8 | 9 | cp.execFile(zfsBin, args, {maxBuffer: 8000000}, function (err, stdout, stderr) { 10 | if (callback && typeof callback === 'function') { 11 | if (err) { 12 | err.message = util.compact(err.message.split('\n')).join('; ').trim(); 13 | callback(err); 14 | } else { 15 | callback(null, stdout); 16 | } 17 | } 18 | }); 19 | } 20 | 21 | function spawnzfs (args, callback) { 22 | "use strict"; 23 | 24 | var child; 25 | try { 26 | child = cp.spawn(zfsBin, args); 27 | } 28 | catch (e) { 29 | return callback(e); 30 | } 31 | 32 | return callback(null, child); 33 | } 34 | 35 | function ZFS(info) { 36 | "use strict"; 37 | 38 | var obj = Object.create({}); 39 | if (typeof info === 'string') { 40 | info = info.split(/\s+/); 41 | } 42 | 43 | if (info.length !== 5) { 44 | return null; 45 | } 46 | 47 | obj.name = info[0]; 48 | obj.used = util.parseNumber(info[1]); 49 | if (info[2] !== '-') { // Snapshots don't have 'avail' field. 50 | obj.avail = util.parseNumber(info[2]); 51 | } 52 | obj.refer = util.parseNumber(info[3]); 53 | obj.mountpoint = info[4]; 54 | 55 | Object.freeze(obj); 56 | return obj; 57 | } 58 | 59 | function Property(info) { 60 | "use strict"; 61 | 62 | var obj = Object.create({}); 63 | if (typeof info === 'string') { 64 | info = info.split(/\t/, 4); 65 | } 66 | 67 | if (info.length !== 4) { 68 | return null; 69 | } 70 | 71 | obj.name = info[0]; 72 | obj.property = info[1]; 73 | obj.value = info[2]; 74 | obj.source = info[3]; 75 | 76 | Object.freeze(obj); 77 | return obj; 78 | } 79 | 80 | /* 81 | * 82 | * List the ZFS folders or datasets 83 | * 84 | * You have the optional opts parameter (if no opts defined, this function returns all the datasets) 85 | * 86 | * PARAMS: 87 | * opts: { 88 | * type: string //Define a type of dataset to filter on (optional) 89 | * sort: string //property on which to sort the output list of datasets (optional) 90 | * name: string //a list of a specific dataset (optional) 91 | * recursive: boolean //recursively list datasets 92 | *} 93 | * 94 | */ 95 | 96 | function list(opts, cb) { 97 | "use strict"; 98 | 99 | if (typeof opts === 'function') { 100 | cb = opts; 101 | opts = undefined; 102 | } 103 | 104 | var params = ['list', '-H']; 105 | 106 | if (opts && opts.type) { 107 | params.push('-t'); 108 | params.push(opts.type); 109 | } 110 | 111 | if (opts && opts.sort) { 112 | params.push('-s'); 113 | params.push(opts.sort); 114 | } 115 | 116 | if (opts && opts.recursive) { 117 | params.push('-r'); 118 | } 119 | 120 | if (opts && opts.name) { 121 | params.push(opts.name); 122 | } 123 | 124 | zfs(params, function (err, stdout) { 125 | if (cb && typeof cb === 'function') { 126 | if (err) { 127 | cb(err); 128 | return; 129 | } 130 | var lines = util.compact(stdout.split('\n')); 131 | var list = lines.map(function (x) { return new ZFS(x); }); 132 | cb(err, list); 133 | } 134 | }); 135 | } 136 | 137 | /* 138 | * 139 | * Get the parameters of a specific dataset or all datasets 140 | * 141 | * PARAMS: 142 | * opts: { 143 | * property: string //which property to show (must exist) 144 | * source: string //can be one of the following: local,default,inherited,temporary,none (optional) 145 | * name: string //the name of the dataset (optional) 146 | *} 147 | * 148 | */ 149 | function get(opts, cb) { 150 | "use strict"; 151 | 152 | var params = [ 'get', '-pH' ]; 153 | if (opts.source) { 154 | params.push('-s', opts.source); 155 | } 156 | 157 | params.push(opts.property); 158 | 159 | if (opts.name) { 160 | params.push(opts.name); 161 | } 162 | 163 | zfs(params, function (err, stdout) { 164 | if (cb && typeof cb === 'function') { 165 | if (err) return cb(err); 166 | var lines = util.compact(stdout.split('\n')); 167 | var list = lines.map(function (x) { return new util.Property(x); }); 168 | cb(err, list); 169 | } 170 | }); 171 | } 172 | 173 | /* 174 | * 175 | * Remove a dataset from the ZFS filesystem 176 | * 177 | * PARAMS: 178 | * opts: { 179 | * recursive: Boolean //to make the destroy recursive (add the -r command to the zfs command) (optional) 180 | * name: string //The name of the dataset to destroy (must exist) 181 | *} 182 | * 183 | */ 184 | 185 | function destroy(opts, cb) { 186 | "use strict"; 187 | 188 | var params = [ 'destroy' ]; 189 | if (opts.recursive) { 190 | params.push('-r'); 191 | } 192 | params.push(opts.name); 193 | 194 | zfs(params, cb); 195 | } 196 | 197 | /* 198 | * 199 | * Create a new dataset inside the ZFS filesystem 200 | * 201 | * PARAMS: 202 | * opts: { 203 | * name: string //the name for the new dataset (must exist) 204 | * size: string //the size of the volume (optional) 205 | * options: { property: String, value: String } //all extra options you want to set for the new dataset, like quota,... (optional) 206 | * OR: 207 | * options = [ { property: String, value: String }, { property: String, value: String } ] 208 | * 209 | *} 210 | */ 211 | 212 | function create(opts, cb) { 213 | "use strict"; 214 | 215 | var params = [ 'create' ]; 216 | 217 | if (opts.options) { 218 | if (opts.options.length) { 219 | //opts.options is an array 220 | for (var x =0; x < opts.options.length; x++) { 221 | params.push('-o', opts.options[x].property + "=" + opts.options[x].value); 222 | } 223 | } else { 224 | //opts.options is a single object 225 | params.push('-o', opts.options.property + "=" + opts.options.value); 226 | } 227 | } 228 | 229 | if (opts.size) { 230 | params.push('-V', util.parseNumber(opts.size)); 231 | } 232 | params.push(opts.name); 233 | 234 | zfs(params, cb); 235 | } 236 | 237 | /* 238 | * Set a specific option for a given dataset 239 | * 240 | * PARAMS: 241 | * opts: { 242 | * name: string //the name of the dataset for which to set the option (must exist) 243 | * property: string //which property to set (must exist) 244 | * value: string //which value to set for the property (must exist) 245 | * } 246 | * 247 | */ 248 | 249 | function set(opts, cb) { 250 | "use strict"; 251 | 252 | var params = [ 'set' ]; 253 | 254 | params.push(opts.property + "=" + opts.value); 255 | 256 | params.push(opts.name); 257 | 258 | zfs(params, cb); 259 | } 260 | 261 | /* 262 | * 263 | * Creates a snapshot with the given name. 264 | * 265 | * PARAMS: 266 | * opts: { 267 | * name: string // the name of the snapshot (must exist) 268 | * dataset: string //the mountpoint of the snapshot (must exist) 269 | * recursive: boolean //if true the -r option is added to the zfs command (optional) 270 | * } 271 | * 272 | */ 273 | 274 | function snapshot(opts, cb) { 275 | "use strict"; 276 | 277 | var params = [ 'snapshot' ]; 278 | if (opts.recursive) { 279 | params.push('-r'); 280 | } 281 | params.push(opts.dataset + '@' + opts.name); 282 | 283 | zfs(params, cb); 284 | } 285 | 286 | /* 287 | * 288 | * Creates a clone of the given snapshot. 289 | * The target dataset can be located anywhere in the ZFS hierarchy, and is created as the same type as the original. 290 | * 291 | * PARAMS: 292 | * opts: { 293 | * snapshot: string //the location of the snapshot. Follwing structure must be used: pool/project/production@today (must exist) 294 | * dataset: string //the name of the mount point (must exist) 295 | * } 296 | * 297 | */ 298 | 299 | function clone(opts, cb) { 300 | "use strict"; 301 | 302 | var params = [ 'clone' ]; 303 | params.push(opts.snapshot, opts.dataset); 304 | 305 | zfs(params, cb); 306 | } 307 | 308 | /* 309 | * 310 | * Mount the specified dataset/all datasets to the mountpoint 311 | * 312 | * PARAMS: 313 | * opts: { 314 | * dataset: string // the name of the zfs dataset. if the dataset is null, then mount all datasets with '-a' 315 | * overlay: boolean // whether use overlay mode 316 | * options: [string, string, ...] // the temporal properties set for the mount duration, 317 | * such as ro/rw for readonly and readwrite (optional) 318 | * } 319 | */ 320 | function mount(opts, cb) { 321 | "use strict"; 322 | 323 | var params = [ 'mount' ]; 324 | 325 | if (opts.overlay) { 326 | params.push('-O'); 327 | } 328 | 329 | if (opts.options) { 330 | if (opts.options.length) { 331 | //opts.options is an array 332 | for (var x =0; x < opts.options.length; x++) { 333 | params.push('-o', opts.options[x]); 334 | } 335 | } else { 336 | //opts.options is a single object, callback err and return 337 | cb({error:'invalid argu: the options should be a string array'}); 338 | return; 339 | } 340 | } 341 | 342 | if (opts.dataset) { 343 | params.push(opts.dataset); 344 | } else { 345 | params.push('-a'); 346 | } 347 | 348 | zfs(params, cb); 349 | } 350 | 351 | /* 352 | * 353 | * Unmount the specified filesystem|mountpoint 354 | * 355 | * PARAMS: 356 | * opts: { 357 | * name: string // the name of the zfs dataset or the path of the mountpoint. 358 | * if the dataset is null, then unmount all available filesystems with '-a' 359 | * force: boolean // whether forcely unmount even if the filesystem is still in use. 360 | * } 361 | */ 362 | function unmount(opts, cb) { 363 | "use strict"; 364 | 365 | var params = [ 'unmount' ]; 366 | 367 | if (opts.force) { 368 | params.push('-f'); 369 | } 370 | 371 | if (opts.name) { 372 | params.push(opts.name); 373 | } else { 374 | params.push('-a'); 375 | } 376 | 377 | zfs(params, cb); 378 | } 379 | 380 | /* 381 | * 382 | * Initiates a send operation of a given snapshot 383 | * 384 | * Callback contains a readable stream that is the sendStream 385 | * Signature : zfs.send(opts, function (err, sendStream) {}); 386 | * Events : sendStream.on('error', function (err) {}); 387 | * sendStream.on('verbose', function (data) {}); 388 | * 389 | * PARAMS: 390 | * opts: { 391 | * snapshot: string //the location of the snapshot. Follwing structure must be used: pool/project/production@today (must exist) 392 | * replication: boolean //create a replication stream 393 | * deduplicate: boolean //create a deduplicated stream 394 | * properties: boolean //send dataset properties with the stream 395 | * noop: boolean //don't actually do anything 396 | * parseable : boolean //output in machine readable format 397 | * verbose : boolean //emit verbose events containing the contents of stderr 398 | * incremental : snapshot //do an incremental send. the snapshot referred to here is the from snapshot 399 | * intermediary : boolean //only applies when incremental is set. Include intermediary snapshots with the stream 400 | * } 401 | * 402 | */ 403 | 404 | function send(opts, cb) { 405 | "use strict"; 406 | 407 | var params = [ 'send' ]; 408 | if (opts.replication) { 409 | params.push('-R'); 410 | } 411 | 412 | if (opts.deduplicate) { 413 | params.push('-D'); 414 | } 415 | 416 | if (opts.properties) { 417 | params.push('-p'); 418 | } 419 | 420 | if (opts.noop) { 421 | params.push('-n'); 422 | } 423 | 424 | if (opts.parsable) { 425 | params.push('-P'); 426 | } 427 | 428 | if (opts.verbose) { 429 | params.push('-v'); 430 | } 431 | 432 | if (opts.incremental) { 433 | if (opts.intermediary) { 434 | params.push('-I'); 435 | } 436 | else { 437 | params.push('-i'); 438 | } 439 | 440 | params.push(opts.incremental); 441 | } 442 | 443 | params.push(opts.snapshot); 444 | 445 | spawnzfs(params, function (err, child) { 446 | if (err) { 447 | return cb(err); 448 | } 449 | 450 | var buffer = []; 451 | var sendStream = child.stdout; 452 | 453 | child.stderr.on('data', function (data) { 454 | data = data.toString(); 455 | buffer.push(data); 456 | 457 | if (opts.verbose) { 458 | sendStream.emit('verbose', data); 459 | } 460 | 461 | //only keep last 5 lines 462 | if (buffer.length > 5) { 463 | buffer.shift(); 464 | } 465 | }); 466 | 467 | child.once('exit', function (code) { 468 | if (code !== 0) { 469 | var message = 'Send Error:' + util.compact(buffer.join('\n').split('\n')).join('; ').trim(); 470 | var err = new Error(message); 471 | err.code = code; 472 | 473 | sendStream.emit('error', err); 474 | } 475 | }); 476 | 477 | return cb(null, sendStream); 478 | }); 479 | } 480 | 481 | /* 482 | * 483 | * Initiates a receive of a snapshot 484 | * 485 | * Callback is the writable stream 486 | * 487 | * PARAMS: 488 | * opts: { 489 | * snapshot: string //the location of the snapshot. Follwing structure must be used: pool/project/production@today (must exist) 490 | * verbose: boolean //if true, the receiveStream will emit 'verbose' events containing the output of stderr 491 | * noop: boolean //if true, zfs will not actually receive anything 492 | * force: boolean //if true, dataset will be rolled back to most recent snapshot 493 | * unmounted : boolean //if true, dataset will not be mounted after receive 494 | * d : boolean //discard first element of received dataset's name 495 | * e : boolean //discard all elements except last of received dataset's name 496 | * } 497 | * 498 | */ 499 | 500 | function receive(opts, cb) { 501 | "use strict"; 502 | 503 | var params = [ 'receive' ]; 504 | if (opts.verbose) { 505 | params.push('-v'); 506 | } 507 | 508 | if (opts.noop) { 509 | params.push('-n'); 510 | } 511 | 512 | if (opts.force) { 513 | params.push('-F'); 514 | } 515 | 516 | if (opts.unmounted) { 517 | params.push('-u'); 518 | } 519 | 520 | if (opts.d) { 521 | params.push('-d'); 522 | } 523 | 524 | if (opts.e) { 525 | params.push('-e'); 526 | } 527 | 528 | params.push(opts.dataset); 529 | 530 | spawnzfs(params, function (err, child) { 531 | if (err) { 532 | return cb(err); 533 | } 534 | 535 | var buffer = []; 536 | var receiveStream = child.stdin; 537 | 538 | child.stderr.on('data', function (data) { 539 | data = data.toString(); 540 | buffer.push(data); 541 | 542 | if (opts.verbose) { 543 | receiveStream.emit('verbose', data); 544 | } 545 | 546 | //only keep last 5 lines 547 | if (buffer.length > 5) { 548 | buffer.shift(); 549 | } 550 | }); 551 | 552 | child.once('exit', function (code) { 553 | if (code !== 0) { 554 | var message = 'Receive Error: ' + util.compact(buffer.join('\n').split('\n')).join('; ').trim(); 555 | var err = new Error(message); 556 | err.code = code; 557 | 558 | receiveStream.emit('error', err); 559 | } 560 | }); 561 | 562 | return cb(null, receiveStream); 563 | }); 564 | } 565 | 566 | exports.get = get; 567 | exports.set = set; 568 | exports.list = list; 569 | exports.destroy = destroy; 570 | exports.create = create; 571 | exports.snapshot = snapshot; 572 | exports.clone = clone; 573 | exports.mount = mount; 574 | exports.unmount = unmount; 575 | exports.send = send; 576 | exports.receive = receive; 577 | -------------------------------------------------------------------------------- /lib/zpool.js: -------------------------------------------------------------------------------- 1 | var cp = require('child_process'); 2 | var util = require('./util'); 3 | 4 | function ZPool(info) { 5 | "use strict"; 6 | 7 | var obj = Object.create({}); 8 | if (typeof info === 'string') { 9 | info = info.split(/\s+/); 10 | } 11 | 12 | if (info.length !== 10) { 13 | return null; 14 | } 15 | 16 | obj.name = info[0]; 17 | obj.size = info[1]; 18 | obj.alloc = info[2]; 19 | obj.free = info[3]; 20 | obj.cap = info[4]; 21 | obj.health = info[5]; 22 | if (info[6] !== '-') { 23 | obj.altroot = info[6]; 24 | } 25 | 26 | Object.freeze(obj); 27 | return obj; 28 | } 29 | 30 | var zpoolBin = util.findCmd('zpool'); 31 | function zpool(args, callback) { 32 | "use strict"; 33 | 34 | cp.execFile(zpoolBin, args, {maxBuffer: 8000000}, function (err, stdout, stderr) { 35 | if (callback && typeof callback === 'function') { 36 | if (err) { 37 | err.message = util.compact(err.message.split('\n')).join('; ').trim(); 38 | callback(err); 39 | } else { 40 | if (!stdout || stdout == '') stdout = 'Request succesfully fulfilled!'; 41 | callback(null, stdout); 42 | } 43 | } 44 | }); 45 | } 46 | 47 | /* 48 | * 49 | * Creates a new storage pool containing the virtual devices specified 50 | * on the command line. The pool name must begin with a letter, and can 51 | * only contain alphanumeric characters as well as underscore ("_"), 52 | * dash ("-"), and period ("."). 53 | * 54 | * zpool create -f datastore raidz /dev/vdb /dev/vdc /dev/vdd 55 | * 56 | * PARAMS: 57 | * opts: { 58 | * options: { property: String, value: String } //all extra options you want to set for the new datastore, like quota,... (optional) 59 | * OR: 60 | * options = [ { property: String, value: String }, { property: String, value: String } ], 61 | * mountpoint: String, //Optional, Sets the mount point for the root dataset. The default mount point is "/pool" or "altroot/pool" if altroot is specified. 62 | * name: String, //must exist, the name of the datastore 63 | * devices: String or Array //Must exist, a list or array containing the device paths and things like mirror and raidz. (see example above) 64 | *} 65 | * 66 | */ 67 | 68 | function create (opts, cb) { 69 | // 70 | 71 | "use strict"; 72 | 73 | var params = [ 'create', '-f' ]; 74 | 75 | /* 76 | immediately add some options to the create procedure 77 | done by adding argument -o option=value 78 | the opts.options parameter is an array of objects, or a single object 79 | 80 | opts.options = { property: String, value: String } 81 | 82 | OR: 83 | 84 | opts.options = [ { property: String, value: String }, { property: String, value: String } ] 85 | 86 | */ 87 | 88 | if (opts.options) { 89 | if (opts.options.length) { 90 | //opts.options is an array 91 | for (var x =0; x < opts.options.length; x++) { 92 | params.push('-o', opts.options[x].property + "=" + opts.options[x].value); 93 | } 94 | } else { 95 | //opts.options is a single object 96 | params.push('-o', opts.options.property + "=" + opts.options.value); 97 | } 98 | } 99 | 100 | if (opts.mountpoint) { 101 | params.push('-m', opts.mountpoint); 102 | } 103 | 104 | params.push(opts.name); 105 | 106 | if (opts.devices.length) { 107 | params = params.concat(opts.devices); 108 | } else { 109 | var devices = opts.devices.split(/\s+/); 110 | params = params.concat(devices); 111 | } 112 | 113 | zpool(params, cb); 114 | } 115 | 116 | /* 117 | * 118 | * Adds the specified virtual devices to the given pool. 119 | * 120 | * PARAMS: 121 | * opts: { 122 | * name: String, //Must exist, the name of the pool to which to add the device 123 | * devices: String or Array //Must exist, a list of devices to add to the given pool. 124 | * } 125 | */ 126 | 127 | function add (opts, cb) { 128 | "use strict"; 129 | 130 | var params = [ 'add', '-f' ]; 131 | 132 | params.push(opts.name); 133 | 134 | //devices is an array or a string of devices 135 | if (opts.devices.length) { 136 | params = params.concat(opts.devices); 137 | } else { 138 | var devices = opts.devices.split(/\s+/); 139 | params = params.concat(devices); 140 | } 141 | 142 | zpool(params, cb); 143 | } 144 | 145 | /* 146 | * 147 | * Sets the given property on the specified pool. See the zpool manpage for possible options and values 148 | * 149 | * PARAMS: 150 | * opts: { 151 | * property: String //Must exist, which property to set 152 | * value: String //Must exits, the value for the described property 153 | * name: String //Must exist, the name of the pool on which to set the property 154 | *} 155 | * 156 | */ 157 | 158 | function set(opts, cb) { 159 | "use strict"; 160 | 161 | var params = [ 'set' ]; 162 | 163 | params.push(opts.property + "=" + opts.value); 164 | 165 | params.push(opts.name); 166 | 167 | zpool(params, cb); 168 | } 169 | 170 | /* 171 | * 172 | * Destroys the given pool, freeing up any devices for other use. 173 | * This command tries to unmount any active datasets before destroying the pool. 174 | * 175 | * PARAMS: 176 | * opts: { 177 | * name: String //Must exist, the name of the pool to destroy 178 | * } 179 | * 180 | */ 181 | 182 | 183 | function destroy(opts, cb) { 184 | "use strict"; 185 | 186 | var params = [ 'destroy', '-f' ]; 187 | 188 | params.push(opts.name); 189 | 190 | zpool(params, cb); 191 | } 192 | 193 | /* 194 | * 195 | * Lists the given pools along with a health status and space usage. If 196 | * no pools are specified, all pools in the system are listed. 197 | * 198 | * PARAMS: 199 | * opts: { 200 | * name: String, //optional, the name of the pool for which to list the status 201 | * } 202 | * 203 | */ 204 | 205 | function list(opts, cb) { 206 | //list the statistics from a (specific) pool. -o option is NOT available 207 | "use strict"; 208 | 209 | if (typeof opts === 'function') { 210 | cb = opts; 211 | opts = undefined; 212 | } 213 | 214 | var params = ['list', '-H']; 215 | 216 | if (opts && opts.name) { 217 | params.push(opts.name); 218 | } 219 | 220 | zpool(params, function (err, stdout) { 221 | if (cb && typeof cb === 'function') { 222 | if (err) { 223 | cb(err); 224 | return; 225 | } 226 | var lines = util.compact(stdout.split('\n')); 227 | var list = lines.map(function (x) { return new ZPool(x); }); 228 | cb(err, list); 229 | } 230 | }); 231 | } 232 | 233 | /* 234 | * 235 | * Retrieves the given list of properties (or all properties if "all" is 236 | * used) for the specified storage pool(s). These properties are displayed 237 | * with the following fields: 238 | * name Name of storage pool 239 | * property Property name 240 | * value Property value 241 | * source Property source, either 'default' or 'local'. 242 | * 243 | * 244 | * PARAMS: 245 | * opts: { 246 | * name: String //Optional, define a pool name for which to show the options 247 | * property: String //Optional, comma-separated list of properties to show. If not defined this 248 | * function returns all options. 249 | * } 250 | */ 251 | 252 | function get(opts, cb) { 253 | "use strict"; 254 | 255 | var params = [ 'get', '-pH' ]; 256 | 257 | if (opts.property) { 258 | params.push(opts.property); 259 | } else { 260 | params.push('all'); 261 | } 262 | 263 | 264 | if (opts.name) { 265 | params.push(opts.name); 266 | } 267 | 268 | zpool(params, function (err, stdout) { 269 | if (cb && typeof cb === 'function') { 270 | if (err) return cb(err); 271 | var lines = util.compact(stdout.split('\n')); 272 | var list = lines.map(function (x) { return new util.Property(x); }); 273 | cb(err, list); 274 | } 275 | }); 276 | } 277 | 278 | exports.get = get; 279 | exports.set = set; 280 | exports.list = list; 281 | exports.destroy = destroy; 282 | exports.create = create; 283 | exports.add = add; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zfs", 3 | "description": "A Node.js wrapper around zfs/zpool", 4 | "author": "Jakob Borg (http://nym.se/)", 5 | "keywords": [ 6 | "solaris", 7 | "zfs", 8 | "illumos", 9 | "openindiana", 10 | "smartos" 11 | ], 12 | "homepage": "http://nym.se/node-zfs/docs/", 13 | "version": "1.3.0", 14 | "main": "lib/main.js", 15 | "scripts": { 16 | "test": "NODE_PATH=lib PATH=test:$PATH node_modules/.bin/mocha -R spec", 17 | "hint": "jshint *.js lib/*.js", 18 | "doc": "docco lib/* example.js 2>/dev/null", 19 | "cov": "jscoverage lib lib-cov && EXPRESS_COV=1 NODE_PATH=lib-cov mocha -R html-cov > docs/coverage.html" 20 | }, 21 | "dependencies": {}, 22 | "devDependencies": { 23 | "jshint": "*", 24 | "mocha": "*", 25 | "should": "*" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/calmh/node-zfs.git" 30 | }, 31 | "bugs": { 32 | "url": "http://github.com/calmh/node-zfs/issues", 33 | "email": "node-zfs@nym.se" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/zfs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function listFs { 4 | cat <