├── test ├── diskinfo.single ├── diskinfo.dell ├── diskinfo.single.ssd ├── diskinfo.coal ├── diskinfo.mirror ├── config.dell ├── config.single ├── config.single.ssd ├── config.coal ├── diskinfo.removable ├── config.mirror ├── config.removable ├── diskinfo.ssd ├── diskinfo.richmond ├── config.ssd ├── config.richmond.raidz2 ├── diskinfo.ms ├── config.richmond ├── config.ms ├── config.ms.mirror └── zfs.test.js ├── .gitignore ├── .gitmodules ├── package.json ├── Makefile ├── tools ├── mk │ ├── Makefile.deps │ ├── Makefile.defs │ └── Makefile.targ └── jsl.node.conf ├── README.md ├── lib ├── disklayout.js └── zfs.js └── LICENSE /test/diskinfo.single: -------------------------------------------------------------------------------- 1 | ATA c0d0 CHEAP SHITTY 1000000000000 no no 2 | -------------------------------------------------------------------------------- /test/diskinfo.dell: -------------------------------------------------------------------------------- 1 | SCSI c0t0d0 DELL PERC-H700 3597303545856 no no 2 | -------------------------------------------------------------------------------- /test/diskinfo.single.ssd: -------------------------------------------------------------------------------- 1 | ATA c0d0 CHEAP SHITTIER 240000000000 no yes 2 | -------------------------------------------------------------------------------- /test/diskinfo.coal: -------------------------------------------------------------------------------- 1 | SCSI c1t0d0 VMware, VMware Virtual S 42949672960 no no 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /tmp 3 | build 4 | docs/*.json 5 | docs/*.html 6 | cscope.in.out 7 | cscope.po.out 8 | cscope.out 9 | -------------------------------------------------------------------------------- /test/diskinfo.mirror: -------------------------------------------------------------------------------- 1 | SCSI c0t5000C5004C8C74D3d0 HITACHI HUC109060CSS600 600127266816 no no 2 | SCSI c0t5000C5004C8C4DA3d0 HITACHI HUC109060CSS600 600127266816 no no 3 | -------------------------------------------------------------------------------- /test/config.dell: -------------------------------------------------------------------------------- 1 | { 2 | "vdevs": [ 3 | { 4 | "name": "c0t0d0", 5 | "vid": "DELL", 6 | "pid": "PERC-H700", 7 | "size": "3597303545856" 8 | } 9 | ], 10 | "capacity": "3597303545856" 11 | } 12 | -------------------------------------------------------------------------------- /test/config.single: -------------------------------------------------------------------------------- 1 | { 2 | "vdevs": [ 3 | { 4 | "name": "c0d0", 5 | "vid": "CHEAP", 6 | "pid": "SHITTY", 7 | "size": "1000000000000" 8 | } 9 | ], 10 | "capacity": "1000000000000" 11 | } 12 | -------------------------------------------------------------------------------- /test/config.single.ssd: -------------------------------------------------------------------------------- 1 | { 2 | "vdevs": [ 3 | { 4 | "name": "c0d0", 5 | "vid": "CHEAP", 6 | "pid": "SHITTIER", 7 | "size": "240000000000" 8 | } 9 | ], 10 | "capacity": "240000000000" 11 | } 12 | -------------------------------------------------------------------------------- /test/config.coal: -------------------------------------------------------------------------------- 1 | { 2 | "vdevs": [ 3 | { 4 | "name": "c1t0d0", 5 | "vid": "VMware,", 6 | "pid": "VMware Virtual S", 7 | "size": "42949672960" 8 | } 9 | ], 10 | "capacity": "42949672960" 11 | } 12 | -------------------------------------------------------------------------------- /test/diskinfo.removable: -------------------------------------------------------------------------------- 1 | SCSI c0t5000C5004C8C74D3d0 HITACHI HUC109060CSS600 600127266816 no no 2 | SCSI c0t5000C5004C8C4DA3d0 HITACHI HUC109060CSS600 600127266816 no no 3 | USB c1t0d0 - - 3932160000 yes no 4 | USB c2t0d0 - - 3932160000 yes no 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/restdown"] 2 | path = deps/restdown 3 | url = https://github.com/trentm/restdown.git 4 | [submodule "deps/javascriptlint"] 5 | path = deps/javascriptlint 6 | url = https://github.com/davepacheco/javascriptlint.git 7 | [submodule "deps/jsstyle"] 8 | path = deps/jsstyle 9 | url = https://github.com/joyent/jsstyle.git 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zfs", 3 | "description": "Node library for interacting with ZFS utilities", 4 | "version": "0.3.2", 5 | "author": "Joyent (joyent.com)", 6 | "private": true, 7 | "main": "lib/zfs.js", 8 | "devDependencies": { 9 | "tap": "0.2" 10 | }, 11 | "scripts": { 12 | "test": "./node_modules/.bin/tap ./test" 13 | }, 14 | "engines": { 15 | "node": ">=0.8" 16 | }, 17 | "license": "MPL-2.0" 18 | } 19 | -------------------------------------------------------------------------------- /test/config.mirror: -------------------------------------------------------------------------------- 1 | { 2 | "vdevs": [ 3 | { 4 | "type": "mirror", 5 | "devices": [ 6 | { 7 | "name": "c0t5000C5004C8C74D3d0", 8 | "vid": "HITACHI", 9 | "pid": "HUC109060CSS600", 10 | "size": "600127266816" 11 | }, 12 | { 13 | "name": "c0t5000C5004C8C4DA3d0", 14 | "vid": "HITACHI", 15 | "pid": "HUC109060CSS600", 16 | "size": "600127266816" 17 | } 18 | ] 19 | } 20 | ], 21 | "capacity": 600127266816 22 | } 23 | -------------------------------------------------------------------------------- /test/config.removable: -------------------------------------------------------------------------------- 1 | { 2 | "vdevs": [ 3 | { 4 | "type": "mirror", 5 | "devices": [ 6 | { 7 | "name": "c0t5000C5004C8C74D3d0", 8 | "vid": "HITACHI", 9 | "pid": "HUC109060CSS600", 10 | "size": "600127266816" 11 | }, 12 | { 13 | "name": "c0t5000C5004C8C4DA3d0", 14 | "vid": "HITACHI", 15 | "pid": "HUC109060CSS600", 16 | "size": "600127266816" 17 | } 18 | ] 19 | } 20 | ], 21 | "capacity": 600127266816 22 | } 23 | -------------------------------------------------------------------------------- /test/diskinfo.ssd: -------------------------------------------------------------------------------- 1 | SCSI c0t5000C5004C8C74D3d0 SMART OPTIMUSPRIME 480000000000 no yes 2 | SCSI c0t5000C5004C8C4DA3d0 SMART OPTIMUSPRIME 480000000000 no yes 3 | SCSI c0t5000C5004C97FD73d0 SMART OPTIMUSPRIME 480000000000 no yes 4 | SCSI c0t5000C5004C97CFB3d0 SMART OPTIMUSPRIME 480000000000 no yes 5 | SCSI c0t5000C50048159867d0 SMART OPTIMUSPRIME 480000000000 no yes 6 | SCSI c0t5000C5004813CEC7d0 SMART OPTIMUSPRIME 480000000000 no yes 7 | SCSI c0t5000C50048134A97d0 SMART OPTIMUSPRIME 480000000000 no yes 8 | SCSI c0t5000C50048159167d0 SMART OPTIMUSPRIME 480000000000 no yes 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | # 6 | 7 | # 8 | # Copyright (c) 2014, Joyent, Inc. 9 | # 10 | 11 | # 12 | # Tools 13 | # 14 | TAP := ./node_modules/.bin/tap 15 | NPM := npm 16 | 17 | # 18 | # Files 19 | # 20 | JS_FILES := $(shell find lib test -name '*.js') 21 | JSL_CONF_NODE = tools/jsl.node.conf 22 | JSL_FILES_NODE = $(JS_FILES) 23 | JSSTYLE_FILES = $(JS_FILES) 24 | 25 | include ./tools/mk/Makefile.defs 26 | 27 | .PHONY: all 28 | all: $(TAP) 29 | $(NPM) rebuild 30 | 31 | $(TAP): 32 | $(NPM) install 33 | 34 | CLEAN_FILES += $(TAP) ./node_modules/tap 35 | 36 | .PHONY: test 37 | test: $(TAP) 38 | TAP=1 $(TAP) test/*.test.js 39 | 40 | include ./tools/mk/Makefile.deps 41 | include ./tools/mk/Makefile.targ 42 | -------------------------------------------------------------------------------- /test/diskinfo.richmond: -------------------------------------------------------------------------------- 1 | SCSI c0t5000C5004C8C74D3d0 HITACHI HUC109060CSS600 600127266816 no no 2 | SCSI c0t5000C5004C8C4DA3d0 HITACHI HUC109060CSS600 600127266816 no no 3 | SCSI c0t5000C5004C97FD73d0 HITACHI HUC109060CSS600 600127266816 no no 4 | SCSI c0t5000C5004C97CFB3d0 HITACHI HUC109060CSS600 600127266816 no no 5 | SCSI c0t5000C50048159867d0 HITACHI HUC109060CSS600 600127266816 no no 6 | SCSI c0t5000C5004813CEC7d0 HITACHI HUC109060CSS600 600127266816 no no 7 | SCSI c0t5000C50048134A97d0 HITACHI HUC109060CSS600 600127266816 no no 8 | SCSI c0t5000C50048159167d0 HITACHI HUC109060CSS600 600127266816 no no 9 | SCSI c0t5000C5004C98978Bd0 HITACHI HUC109060CSS600 600127266816 no no 10 | SCSI c0t5000C5004C90026Bd0 HITACHI HUC109060CSS600 600127266816 no no 11 | SCSI c0t5000C5004C99CDDBd0 HITACHI HUC109060CSS600 600127266816 no no 12 | SCSI c0t5000C5004815616Bd0 HITACHI HUC109060CSS600 600127266816 no no 13 | SCSI c0t5000C5004815616Bd0 HITACHI HUC109060CSS600 600127266816 no no 14 | USB c1t0d0 - - 3932160000 yes no 15 | UNKNOWN c2t0d0 STEC SSDSC2CW120A3 50000035840 no yes 16 | -------------------------------------------------------------------------------- /test/config.ssd: -------------------------------------------------------------------------------- 1 | { 2 | "spares": [ 3 | { 4 | "name": "c0t5000C50048134A97d0", 5 | "vid": "SMART", 6 | "pid": "OPTIMUSPRIME", 7 | "size": "480000000000" 8 | }, 9 | { 10 | "name": "c0t5000C50048159167d0", 11 | "vid": "SMART", 12 | "pid": "OPTIMUSPRIME", 13 | "size": "480000000000" 14 | } 15 | ], 16 | "vdevs": [ 17 | { 18 | "type": "mirror", 19 | "devices": [ 20 | { 21 | "name": "c0t5000C5004C8C74D3d0", 22 | "vid": "SMART", 23 | "pid": "OPTIMUSPRIME", 24 | "size": "480000000000" 25 | }, 26 | { 27 | "name": "c0t5000C5004C8C4DA3d0", 28 | "vid": "SMART", 29 | "pid": "OPTIMUSPRIME", 30 | "size": "480000000000" 31 | } 32 | ] 33 | }, 34 | { 35 | "type": "mirror", 36 | "devices": [ 37 | { 38 | "name": "c0t5000C5004C97FD73d0", 39 | "vid": "SMART", 40 | "pid": "OPTIMUSPRIME", 41 | "size": "480000000000" 42 | }, 43 | { 44 | "name": "c0t5000C5004C97CFB3d0", 45 | "vid": "SMART", 46 | "pid": "OPTIMUSPRIME", 47 | "size": "480000000000" 48 | } 49 | ] 50 | }, 51 | { 52 | "type": "mirror", 53 | "devices": [ 54 | { 55 | "name": "c0t5000C50048159867d0", 56 | "vid": "SMART", 57 | "pid": "OPTIMUSPRIME", 58 | "size": "480000000000" 59 | }, 60 | { 61 | "name": "c0t5000C5004813CEC7d0", 62 | "vid": "SMART", 63 | "pid": "OPTIMUSPRIME", 64 | "size": "480000000000" 65 | } 66 | ] 67 | } 68 | ], 69 | "capacity": 1440000000000 70 | } 71 | -------------------------------------------------------------------------------- /tools/mk/Makefile.deps: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.deps: Makefile for including common tools as dependencies 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | # This file is separate from Makefile.targ so that teams can choose 21 | # independently whether to use the common targets in Makefile.targ and the 22 | # common tools here. 23 | # 24 | 25 | # 26 | # javascriptlint 27 | # 28 | JSL_EXEC ?= deps/javascriptlint/build/install/jsl 29 | JSL ?= $(JSL_EXEC) 30 | 31 | $(JSL_EXEC): | deps/javascriptlint/.git 32 | cd deps/javascriptlint && make install 33 | 34 | distclean:: 35 | if [[ -f deps/javascriptlint/Makefile ]]; then \ 36 | cd deps/javascriptlint && make clean; \ 37 | fi 38 | 39 | # 40 | # jsstyle 41 | # 42 | JSSTYLE_EXEC ?= deps/jsstyle/jsstyle 43 | JSSTYLE ?= $(JSSTYLE_EXEC) 44 | 45 | $(JSSTYLE_EXEC): | deps/jsstyle/.git 46 | 47 | # 48 | # restdown 49 | # 50 | RESTDOWN_EXEC ?= deps/restdown/bin/restdown 51 | RESTDOWN ?= python $(RESTDOWN_EXEC) 52 | $(RESTDOWN_EXEC): | deps/restdown/.git 53 | 54 | EXTRA_DOC_DEPS ?= 55 | -------------------------------------------------------------------------------- /tools/mk/Makefile.defs: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.defs: common defines. 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | # This makefile defines some useful defines. Include it at the top of 21 | # your Makefile. 22 | # 23 | # Definitions in this Makefile: 24 | # 25 | # TOP The absolute path to the project directory. The top dir. 26 | # BRANCH The current git branch. 27 | # TIMESTAMP The timestamp for the build. This can be set via 28 | # the TIMESTAMP envvar (used by MG-based builds). 29 | # STAMP A build stamp to use in built package names. 30 | # 31 | 32 | TOP := $(shell pwd) 33 | 34 | # 35 | # Mountain Gorilla-spec'd versioning. 36 | # See "Package Versioning" in MG's README.md: 37 | # 38 | # 39 | # Need GNU awk for multi-char arg to "-F". 40 | _AWK := $(shell (which gawk >/dev/null && echo gawk) \ 41 | || (which nawk >/dev/null && echo nawk) \ 42 | || echo awk) 43 | BRANCH := $(shell git symbolic-ref HEAD | $(_AWK) -F/ '{print $$3}') 44 | ifeq ($(TIMESTAMP),) 45 | TIMESTAMP := $(shell date -u "+%Y%m%dT%H%M%SZ") 46 | endif 47 | _GITDESCRIBE := g$(shell git describe --all --long --dirty | $(_AWK) -F'-g' '{print $$NF}') 48 | STAMP := $(BRANCH)-$(TIMESTAMP)-$(_GITDESCRIBE) 49 | 50 | # node-gyp will print build info useful for debugging with V=1 51 | export V=1 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | # node-zfs 12 | 13 | This repository is part of the Joyent SmartDataCenter project (SDC). For 14 | contribution guidelines, issues, and general documentation, visit the main 15 | [SDC](http://github.com/joyent/sdc) project page. 16 | 17 | node-zfs is a Node.js interface to ZFS tools 18 | 19 | # SYNOPSIS 20 | 21 | // list datasets 22 | zfs.list(function (err, fields, data) { 23 | // ... 24 | }); 25 | 26 | // list snapshots 27 | zfs.list_snapshots(function (err, fields, data) { 28 | // ... 29 | }); 30 | 31 | // create a dataset 32 | zfs.create('mydataset', function (err) { 33 | // ... 34 | }); 35 | 36 | // destroy a dataset or snapshot 37 | zfs.destroy('mydataset', function (err) { 38 | // ... 39 | }); 40 | 41 | // recursively destroy a dataset 42 | zfs.destroyAll('mydataset', function (err) { 43 | // ... 44 | }); 45 | 46 | // rollback a snapshot 47 | zfs.rollback('mydataset@backup', function (err) { 48 | // ... 49 | }); 50 | 51 | // clone a dataset 52 | zfs.clone('mydataset@backup', 'mynewdataset', function (err) { 53 | // ... 54 | }); 55 | 56 | // set dataset properties 57 | zfs.set('mydataset', { 'test:key1': 'value' 58 | , 'test:key2': 'value' }, function (err) { 59 | // ... 60 | }); 61 | 62 | // get dataset properties 63 | zfs.get('mydataset', [ 'test:key1', 'test:key2' ], 64 | function (err, properties) { 65 | // ... 66 | }); 67 | 68 | # DESCRIPTION 69 | 70 | The node-zfs library provies a thin, evented wrapper around common ZFS 71 | commands. It also contains functionality to automatically generate pool 72 | layouts based on a disk inventory. 73 | 74 | 75 | # ENVIRONMENT 76 | 77 | The library was developed on an OpenSolaris snv_111b system and has 78 | subsequently been used on SmartOS. 79 | 80 | 81 | # AUTHOR 82 | 83 | Orlando Vazquez 84 | Keith Wesolowski 85 | 86 | 87 | # SEE ALSO 88 | 89 | zfs(1M), zpool(1M) 90 | -------------------------------------------------------------------------------- /test/config.richmond.raidz2: -------------------------------------------------------------------------------- 1 | { 2 | "spares": [ 3 | { 4 | "name": "c0t5000C5004815616Bd0", 5 | "vid": "HITACHI", 6 | "pid": "HUC109060CSS600", 7 | "size": "600127266816" 8 | } 9 | ], 10 | "vdevs": [ 11 | { 12 | "type": "raidz2", 13 | "devices": [ 14 | { 15 | "name": "c0t5000C50048134A97d0", 16 | "vid": "HITACHI", 17 | "pid": "HUC109060CSS600", 18 | "size": "600127266816" 19 | }, 20 | { 21 | "name": "c0t5000C5004C8C74D3d0", 22 | "vid": "HITACHI", 23 | "pid": "HUC109060CSS600", 24 | "size": "600127266816" 25 | }, 26 | { 27 | "name": "c0t5000C5004C97FD73d0", 28 | "vid": "HITACHI", 29 | "pid": "HUC109060CSS600", 30 | "size": "600127266816" 31 | }, 32 | { 33 | "name": "c0t5000C5004C97CFB3d0", 34 | "vid": "HITACHI", 35 | "pid": "HUC109060CSS600", 36 | "size": "600127266816" 37 | }, 38 | { 39 | "name": "c0t5000C50048159867d0", 40 | "vid": "HITACHI", 41 | "pid": "HUC109060CSS600", 42 | "size": "600127266816" 43 | }, 44 | { 45 | "name": "c0t5000C5004813CEC7d0", 46 | "vid": "HITACHI", 47 | "pid": "HUC109060CSS600", 48 | "size": "600127266816" 49 | }, 50 | { 51 | "name": "c0t5000C5004C8C4DA3d0", 52 | "vid": "HITACHI", 53 | "pid": "HUC109060CSS600", 54 | "size": "600127266816" 55 | }, 56 | { 57 | "name": "c0t5000C50048159167d0", 58 | "vid": "HITACHI", 59 | "pid": "HUC109060CSS600", 60 | "size": "600127266816" 61 | }, 62 | { 63 | "name": "c0t5000C5004C98978Bd0", 64 | "vid": "HITACHI", 65 | "pid": "HUC109060CSS600", 66 | "size": "600127266816" 67 | }, 68 | { 69 | "name": "c0t5000C5004C90026Bd0", 70 | "vid": "HITACHI", 71 | "pid": "HUC109060CSS600", 72 | "size": "600127266816" 73 | }, 74 | { 75 | "name": "c0t5000C5004C99CDDBd0", 76 | "vid": "HITACHI", 77 | "pid": "HUC109060CSS600", 78 | "size": "600127266816" 79 | }, 80 | { 81 | "name": "c0t5000C5004815616Bd0", 82 | "vid": "HITACHI", 83 | "pid": "HUC109060CSS600", 84 | "size": "600127266816" 85 | } 86 | ] 87 | } 88 | ], 89 | "capacity": 6001272668160, 90 | "logs": [ 91 | { 92 | "name": "c2t0d0", 93 | "vid": "STEC", 94 | "pid": "SSDSC2CW120A3", 95 | "size": "50000035840" 96 | } 97 | ] 98 | } 99 | -------------------------------------------------------------------------------- /test/diskinfo.ms: -------------------------------------------------------------------------------- 1 | SCSI c0t5000C5004C8C74D3d0 HITACHI HUS723020ALS640 3000000000000 no no 2 | SCSI c0t5000C5004C8C4DA3d0 HITACHI HUS723020ALS640 3000000000000 no no 3 | SCSI c0t5000C5004C97FD73d0 HITACHI HUS723020ALS640 3000000000000 no no 4 | SCSI c0t5000C5004C97CFB3d0 HITACHI HUS723020ALS640 3000000000000 no no 5 | SCSI c0t5000C50048159867d0 HITACHI HUS723020ALS640 3000000000000 no no 6 | SCSI c0t5000C5004813CEC7d0 HITACHI HUS723020ALS640 3000000000000 no no 7 | SCSI c0t5000C50048134A97d0 HITACHI HUS723020ALS640 3000000000000 no no 8 | SCSI c0t5000C50048159167d0 HITACHI HUS723020ALS640 3000000000000 no no 9 | SCSI c0t5000C5004C98978Bd0 HITACHI HUS723020ALS640 3000000000000 no no 10 | SCSI c0t5000C5004C90026Bd0 HITACHI HUS723020ALS640 3000000000000 no no 11 | SCSI c0t5000C5004C99CDDBd0 HITACHI HUS723020ALS640 3000000000000 no no 12 | SCSI c0t5000C5004815616Bd0 HITACHI HUS723020ALS640 3000000000000 no no 13 | SCSI c0t5000C5004C8C74D3d0 HITACHI HUS723020ALS640 3000000000000 no no 14 | SCSI c0t5000C5004C8C4DA3d0 HITACHI HUS723020ALS640 3000000000000 no no 15 | SCSI c0t5000C5004C97FD73d0 HITACHI HUS723020ALS640 3000000000000 no no 16 | SCSI c0t5000C5004C97CFB3d0 HITACHI HUS723020ALS640 3000000000000 no no 17 | SCSI c0t5000C50048159867d0 HITACHI HUS723020ALS640 3000000000000 no no 18 | SCSI c0t5000C5004813CEC7d0 HITACHI HUS723020ALS640 3000000000000 no no 19 | SCSI c0t5000C50048134A97d0 HITACHI HUS723020ALS640 3000000000000 no no 20 | SCSI c0t5000C50048159167d0 HITACHI HUS723020ALS640 3000000000000 no no 21 | SCSI c0t5000C5004C98978Bd0 HITACHI HUS723020ALS640 3000000000000 no no 22 | SCSI c0t5000C5004C90026Bd0 HITACHI HUS723020ALS640 3000000000000 no no 23 | SCSI c0t5000C5004C99CDDBd0 HITACHI HUS723020ALS640 3000000000000 no no 24 | SCSI c0t5000C5004815616Bd0 HITACHI HUS723020ALS640 3000000000000 no no 25 | SCSI c0t5000C5004C8C74D3d0 HITACHI HUS723020ALS640 3000000000000 no no 26 | SCSI c0t5000C5004C8C4DA3d0 HITACHI HUS723020ALS640 3000000000000 no no 27 | SCSI c0t5000C5004C97FD73d0 HITACHI HUS723020ALS640 3000000000000 no no 28 | SCSI c0t5000C5004C97CFB3d0 HITACHI HUS723020ALS640 3000000000000 no no 29 | SCSI c0t5000C50048159867d0 HITACHI HUS723020ALS640 3000000000000 no no 30 | SCSI c0t5000C5004813CEC7d0 HITACHI HUS723020ALS640 3000000000000 no no 31 | SCSI c0t5000C50048134A97d0 HITACHI HUS723020ALS640 3000000000000 no no 32 | SCSI c0t5000C50048159167d0 HITACHI HUS723020ALS640 3000000000000 no no 33 | SCSI c0t5000C5004C98978Bd0 HITACHI HUS723020ALS640 3000000000000 no no 34 | SCSI c0t5000C5004C90026Bd0 HITACHI HUS723020ALS640 3000000000000 no no 35 | SCSI c0t5000C5004C99CDDBd0 HITACHI HUS723020ALS640 3000000000000 no no 36 | SCSI c0t5000C5004FFFFFFFd0 STEC SSDSC2CW120A3 85899345920 no yes 37 | USB c1t0d0 - - 3932160000 yes no 38 | -------------------------------------------------------------------------------- /test/config.richmond: -------------------------------------------------------------------------------- 1 | { 2 | "spares": [ 3 | { 4 | "name": "c0t5000C5004815616Bd0", 5 | "vid": "HITACHI", 6 | "pid": "HUC109060CSS600", 7 | "size": "600127266816" 8 | } 9 | ], 10 | "vdevs": [ 11 | { 12 | "type": "mirror", 13 | "devices": [ 14 | { 15 | "name": "c0t5000C50048134A97d0", 16 | "vid": "HITACHI", 17 | "pid": "HUC109060CSS600", 18 | "size": "600127266816" 19 | }, 20 | { 21 | "name": "c0t5000C5004C8C74D3d0", 22 | "vid": "HITACHI", 23 | "pid": "HUC109060CSS600", 24 | "size": "600127266816" 25 | } 26 | ] 27 | }, 28 | { 29 | "type": "mirror", 30 | "devices": [ 31 | { 32 | "name": "c0t5000C5004C97FD73d0", 33 | "vid": "HITACHI", 34 | "pid": "HUC109060CSS600", 35 | "size": "600127266816" 36 | }, 37 | { 38 | "name": "c0t5000C5004C97CFB3d0", 39 | "vid": "HITACHI", 40 | "pid": "HUC109060CSS600", 41 | "size": "600127266816" 42 | } 43 | ] 44 | }, 45 | { 46 | "type": "mirror", 47 | "devices": [ 48 | { 49 | "name": "c0t5000C50048159867d0", 50 | "vid": "HITACHI", 51 | "pid": "HUC109060CSS600", 52 | "size": "600127266816" 53 | }, 54 | { 55 | "name": "c0t5000C5004813CEC7d0", 56 | "vid": "HITACHI", 57 | "pid": "HUC109060CSS600", 58 | "size": "600127266816" 59 | } 60 | ] 61 | }, 62 | { 63 | "type": "mirror", 64 | "devices": [ 65 | { 66 | "name": "c0t5000C5004C8C4DA3d0", 67 | "vid": "HITACHI", 68 | "pid": "HUC109060CSS600", 69 | "size": "600127266816" 70 | }, 71 | { 72 | "name": "c0t5000C50048159167d0", 73 | "vid": "HITACHI", 74 | "pid": "HUC109060CSS600", 75 | "size": "600127266816" 76 | } 77 | ] 78 | }, 79 | { 80 | "type": "mirror", 81 | "devices": [ 82 | { 83 | "name": "c0t5000C5004C98978Bd0", 84 | "vid": "HITACHI", 85 | "pid": "HUC109060CSS600", 86 | "size": "600127266816" 87 | }, 88 | { 89 | "name": "c0t5000C5004C90026Bd0", 90 | "vid": "HITACHI", 91 | "pid": "HUC109060CSS600", 92 | "size": "600127266816" 93 | } 94 | ] 95 | }, 96 | { 97 | "type": "mirror", 98 | "devices": [ 99 | { 100 | "name": "c0t5000C5004C99CDDBd0", 101 | "vid": "HITACHI", 102 | "pid": "HUC109060CSS600", 103 | "size": "600127266816" 104 | }, 105 | { 106 | "name": "c0t5000C5004815616Bd0", 107 | "vid": "HITACHI", 108 | "pid": "HUC109060CSS600", 109 | "size": "600127266816" 110 | } 111 | ] 112 | } 113 | ], 114 | "capacity": 3600763600896, 115 | "logs": [ 116 | { 117 | "name": "c2t0d0", 118 | "vid": "STEC", 119 | "pid": "SSDSC2CW120A3", 120 | "size": "50000035840" 121 | } 122 | ] 123 | } 124 | -------------------------------------------------------------------------------- /test/config.ms: -------------------------------------------------------------------------------- 1 | { 2 | "spares": [ 3 | { 4 | "name": "c0t5000C5004C90026Bd0", 5 | "vid": "HITACHI", 6 | "pid": "HUS723020ALS640", 7 | "size": "3000000000000" 8 | }, 9 | { 10 | "name": "c0t5000C5004C99CDDBd0", 11 | "vid": "HITACHI", 12 | "pid": "HUS723020ALS640", 13 | "size": "3000000000000" 14 | } 15 | ], 16 | "vdevs": [ 17 | { 18 | "type": "raidz2", 19 | "devices": [ 20 | { 21 | "name": "c0t5000C5004813CEC7d0", 22 | "vid": "HITACHI", 23 | "pid": "HUS723020ALS640", 24 | "size": "3000000000000" 25 | }, 26 | { 27 | "name": "c0t5000C5004C8C74D3d0", 28 | "vid": "HITACHI", 29 | "pid": "HUS723020ALS640", 30 | "size": "3000000000000" 31 | }, 32 | { 33 | "name": "c0t5000C5004C97FD73d0", 34 | "vid": "HITACHI", 35 | "pid": "HUS723020ALS640", 36 | "size": "3000000000000" 37 | }, 38 | { 39 | "name": "c0t5000C5004C97CFB3d0", 40 | "vid": "HITACHI", 41 | "pid": "HUS723020ALS640", 42 | "size": "3000000000000" 43 | }, 44 | { 45 | "name": "c0t5000C50048159867d0", 46 | "vid": "HITACHI", 47 | "pid": "HUS723020ALS640", 48 | "size": "3000000000000" 49 | }, 50 | { 51 | "name": "c0t5000C5004813CEC7d0", 52 | "vid": "HITACHI", 53 | "pid": "HUS723020ALS640", 54 | "size": "3000000000000" 55 | }, 56 | { 57 | "name": "c0t5000C50048134A97d0", 58 | "vid": "HITACHI", 59 | "pid": "HUS723020ALS640", 60 | "size": "3000000000000" 61 | }, 62 | { 63 | "name": "c0t5000C50048159167d0", 64 | "vid": "HITACHI", 65 | "pid": "HUS723020ALS640", 66 | "size": "3000000000000" 67 | }, 68 | { 69 | "name": "c0t5000C5004C98978Bd0", 70 | "vid": "HITACHI", 71 | "pid": "HUS723020ALS640", 72 | "size": "3000000000000" 73 | }, 74 | { 75 | "name": "c0t5000C5004C90026Bd0", 76 | "vid": "HITACHI", 77 | "pid": "HUS723020ALS640", 78 | "size": "3000000000000" 79 | }, 80 | { 81 | "name": "c0t5000C5004C99CDDBd0", 82 | "vid": "HITACHI", 83 | "pid": "HUS723020ALS640", 84 | "size": "3000000000000" 85 | } 86 | ] 87 | }, 88 | { 89 | "type": "raidz2", 90 | "devices": [ 91 | { 92 | "name": "c0t5000C5004815616Bd0", 93 | "vid": "HITACHI", 94 | "pid": "HUS723020ALS640", 95 | "size": "3000000000000" 96 | }, 97 | { 98 | "name": "c0t5000C5004C8C74D3d0", 99 | "vid": "HITACHI", 100 | "pid": "HUS723020ALS640", 101 | "size": "3000000000000" 102 | }, 103 | { 104 | "name": "c0t5000C5004C8C4DA3d0", 105 | "vid": "HITACHI", 106 | "pid": "HUS723020ALS640", 107 | "size": "3000000000000" 108 | }, 109 | { 110 | "name": "c0t5000C5004C97FD73d0", 111 | "vid": "HITACHI", 112 | "pid": "HUS723020ALS640", 113 | "size": "3000000000000" 114 | }, 115 | { 116 | "name": "c0t5000C5004C97CFB3d0", 117 | "vid": "HITACHI", 118 | "pid": "HUS723020ALS640", 119 | "size": "3000000000000" 120 | }, 121 | { 122 | "name": "c0t5000C50048159867d0", 123 | "vid": "HITACHI", 124 | "pid": "HUS723020ALS640", 125 | "size": "3000000000000" 126 | }, 127 | { 128 | "name": "c0t5000C5004C8C4DA3d0", 129 | "vid": "HITACHI", 130 | "pid": "HUS723020ALS640", 131 | "size": "3000000000000" 132 | }, 133 | { 134 | "name": "c0t5000C50048134A97d0", 135 | "vid": "HITACHI", 136 | "pid": "HUS723020ALS640", 137 | "size": "3000000000000" 138 | }, 139 | { 140 | "name": "c0t5000C50048159167d0", 141 | "vid": "HITACHI", 142 | "pid": "HUS723020ALS640", 143 | "size": "3000000000000" 144 | }, 145 | { 146 | "name": "c0t5000C5004C98978Bd0", 147 | "vid": "HITACHI", 148 | "pid": "HUS723020ALS640", 149 | "size": "3000000000000" 150 | }, 151 | { 152 | "name": "c0t5000C5004C90026Bd0", 153 | "vid": "HITACHI", 154 | "pid": "HUS723020ALS640", 155 | "size": "3000000000000" 156 | } 157 | ] 158 | }, 159 | { 160 | "type": "raidz2", 161 | "devices": [ 162 | { 163 | "name": "c0t5000C5004C99CDDBd0", 164 | "vid": "HITACHI", 165 | "pid": "HUS723020ALS640", 166 | "size": "3000000000000" 167 | }, 168 | { 169 | "name": "c0t5000C5004815616Bd0", 170 | "vid": "HITACHI", 171 | "pid": "HUS723020ALS640", 172 | "size": "3000000000000" 173 | }, 174 | { 175 | "name": "c0t5000C5004C8C74D3d0", 176 | "vid": "HITACHI", 177 | "pid": "HUS723020ALS640", 178 | "size": "3000000000000" 179 | }, 180 | { 181 | "name": "c0t5000C5004C8C4DA3d0", 182 | "vid": "HITACHI", 183 | "pid": "HUS723020ALS640", 184 | "size": "3000000000000" 185 | }, 186 | { 187 | "name": "c0t5000C5004C97FD73d0", 188 | "vid": "HITACHI", 189 | "pid": "HUS723020ALS640", 190 | "size": "3000000000000" 191 | }, 192 | { 193 | "name": "c0t5000C5004C97CFB3d0", 194 | "vid": "HITACHI", 195 | "pid": "HUS723020ALS640", 196 | "size": "3000000000000" 197 | }, 198 | { 199 | "name": "c0t5000C50048159867d0", 200 | "vid": "HITACHI", 201 | "pid": "HUS723020ALS640", 202 | "size": "3000000000000" 203 | }, 204 | { 205 | "name": "c0t5000C5004813CEC7d0", 206 | "vid": "HITACHI", 207 | "pid": "HUS723020ALS640", 208 | "size": "3000000000000" 209 | }, 210 | { 211 | "name": "c0t5000C50048134A97d0", 212 | "vid": "HITACHI", 213 | "pid": "HUS723020ALS640", 214 | "size": "3000000000000" 215 | }, 216 | { 217 | "name": "c0t5000C50048159167d0", 218 | "vid": "HITACHI", 219 | "pid": "HUS723020ALS640", 220 | "size": "3000000000000" 221 | }, 222 | { 223 | "name": "c0t5000C5004C98978Bd0", 224 | "vid": "HITACHI", 225 | "pid": "HUS723020ALS640", 226 | "size": "3000000000000" 227 | } 228 | ] 229 | } 230 | ], 231 | "capacity": 81000000000000, 232 | "logs": [ 233 | { 234 | "name": "c0t5000C5004FFFFFFFd0", 235 | "vid": "STEC", 236 | "pid": "SSDSC2CW120A3", 237 | "size": "85899345920" 238 | } 239 | ] 240 | } 241 | -------------------------------------------------------------------------------- /test/config.ms.mirror: -------------------------------------------------------------------------------- 1 | { 2 | "spares": [ 3 | { 4 | "name": "c0t5000C5004C98978Bd0", 5 | "vid": "HITACHI", 6 | "pid": "HUS723020ALS640", 7 | "size": "3000000000000" 8 | }, 9 | { 10 | "name": "c0t5000C5004C90026Bd0", 11 | "vid": "HITACHI", 12 | "pid": "HUS723020ALS640", 13 | "size": "3000000000000" 14 | }, 15 | { 16 | "name": "c0t5000C5004C99CDDBd0", 17 | "vid": "HITACHI", 18 | "pid": "HUS723020ALS640", 19 | "size": "3000000000000" 20 | } 21 | ], 22 | "vdevs": [ 23 | { 24 | "type": "mirror", 25 | "devices": [ 26 | { 27 | "name": "c0t5000C5004813CEC7d0", 28 | "vid": "HITACHI", 29 | "pid": "HUS723020ALS640", 30 | "size": "3000000000000" 31 | }, 32 | { 33 | "name": "c0t5000C5004C8C74D3d0", 34 | "vid": "HITACHI", 35 | "pid": "HUS723020ALS640", 36 | "size": "3000000000000" 37 | } 38 | ] 39 | }, 40 | { 41 | "type": "mirror", 42 | "devices": [ 43 | { 44 | "name": "c0t5000C5004C97FD73d0", 45 | "vid": "HITACHI", 46 | "pid": "HUS723020ALS640", 47 | "size": "3000000000000" 48 | }, 49 | { 50 | "name": "c0t5000C5004C97CFB3d0", 51 | "vid": "HITACHI", 52 | "pid": "HUS723020ALS640", 53 | "size": "3000000000000" 54 | } 55 | ] 56 | }, 57 | { 58 | "type": "mirror", 59 | "devices": [ 60 | { 61 | "name": "c0t5000C50048159867d0", 62 | "vid": "HITACHI", 63 | "pid": "HUS723020ALS640", 64 | "size": "3000000000000" 65 | }, 66 | { 67 | "name": "c0t5000C5004813CEC7d0", 68 | "vid": "HITACHI", 69 | "pid": "HUS723020ALS640", 70 | "size": "3000000000000" 71 | } 72 | ] 73 | }, 74 | { 75 | "type": "mirror", 76 | "devices": [ 77 | { 78 | "name": "c0t5000C50048134A97d0", 79 | "vid": "HITACHI", 80 | "pid": "HUS723020ALS640", 81 | "size": "3000000000000" 82 | }, 83 | { 84 | "name": "c0t5000C50048159167d0", 85 | "vid": "HITACHI", 86 | "pid": "HUS723020ALS640", 87 | "size": "3000000000000" 88 | } 89 | ] 90 | }, 91 | { 92 | "type": "mirror", 93 | "devices": [ 94 | { 95 | "name": "c0t5000C5004C98978Bd0", 96 | "vid": "HITACHI", 97 | "pid": "HUS723020ALS640", 98 | "size": "3000000000000" 99 | }, 100 | { 101 | "name": "c0t5000C5004C90026Bd0", 102 | "vid": "HITACHI", 103 | "pid": "HUS723020ALS640", 104 | "size": "3000000000000" 105 | } 106 | ] 107 | }, 108 | { 109 | "type": "mirror", 110 | "devices": [ 111 | { 112 | "name": "c0t5000C5004C99CDDBd0", 113 | "vid": "HITACHI", 114 | "pid": "HUS723020ALS640", 115 | "size": "3000000000000" 116 | }, 117 | { 118 | "name": "c0t5000C5004815616Bd0", 119 | "vid": "HITACHI", 120 | "pid": "HUS723020ALS640", 121 | "size": "3000000000000" 122 | } 123 | ] 124 | }, 125 | { 126 | "type": "mirror", 127 | "devices": [ 128 | { 129 | "name": "c0t5000C5004C8C74D3d0", 130 | "vid": "HITACHI", 131 | "pid": "HUS723020ALS640", 132 | "size": "3000000000000" 133 | }, 134 | { 135 | "name": "c0t5000C5004C8C4DA3d0", 136 | "vid": "HITACHI", 137 | "pid": "HUS723020ALS640", 138 | "size": "3000000000000" 139 | } 140 | ] 141 | }, 142 | { 143 | "type": "mirror", 144 | "devices": [ 145 | { 146 | "name": "c0t5000C5004C97FD73d0", 147 | "vid": "HITACHI", 148 | "pid": "HUS723020ALS640", 149 | "size": "3000000000000" 150 | }, 151 | { 152 | "name": "c0t5000C5004C97CFB3d0", 153 | "vid": "HITACHI", 154 | "pid": "HUS723020ALS640", 155 | "size": "3000000000000" 156 | } 157 | ] 158 | }, 159 | { 160 | "type": "mirror", 161 | "devices": [ 162 | { 163 | "name": "c0t5000C50048159867d0", 164 | "vid": "HITACHI", 165 | "pid": "HUS723020ALS640", 166 | "size": "3000000000000" 167 | }, 168 | { 169 | "name": "c0t5000C5004C8C4DA3d0", 170 | "vid": "HITACHI", 171 | "pid": "HUS723020ALS640", 172 | "size": "3000000000000" 173 | } 174 | ] 175 | }, 176 | { 177 | "type": "mirror", 178 | "devices": [ 179 | { 180 | "name": "c0t5000C50048134A97d0", 181 | "vid": "HITACHI", 182 | "pid": "HUS723020ALS640", 183 | "size": "3000000000000" 184 | }, 185 | { 186 | "name": "c0t5000C50048159167d0", 187 | "vid": "HITACHI", 188 | "pid": "HUS723020ALS640", 189 | "size": "3000000000000" 190 | } 191 | ] 192 | }, 193 | { 194 | "type": "mirror", 195 | "devices": [ 196 | { 197 | "name": "c0t5000C5004C98978Bd0", 198 | "vid": "HITACHI", 199 | "pid": "HUS723020ALS640", 200 | "size": "3000000000000" 201 | }, 202 | { 203 | "name": "c0t5000C5004C90026Bd0", 204 | "vid": "HITACHI", 205 | "pid": "HUS723020ALS640", 206 | "size": "3000000000000" 207 | } 208 | ] 209 | }, 210 | { 211 | "type": "mirror", 212 | "devices": [ 213 | { 214 | "name": "c0t5000C5004C99CDDBd0", 215 | "vid": "HITACHI", 216 | "pid": "HUS723020ALS640", 217 | "size": "3000000000000" 218 | }, 219 | { 220 | "name": "c0t5000C5004815616Bd0", 221 | "vid": "HITACHI", 222 | "pid": "HUS723020ALS640", 223 | "size": "3000000000000" 224 | } 225 | ] 226 | }, 227 | { 228 | "type": "mirror", 229 | "devices": [ 230 | { 231 | "name": "c0t5000C5004C8C74D3d0", 232 | "vid": "HITACHI", 233 | "pid": "HUS723020ALS640", 234 | "size": "3000000000000" 235 | }, 236 | { 237 | "name": "c0t5000C5004C8C4DA3d0", 238 | "vid": "HITACHI", 239 | "pid": "HUS723020ALS640", 240 | "size": "3000000000000" 241 | } 242 | ] 243 | }, 244 | { 245 | "type": "mirror", 246 | "devices": [ 247 | { 248 | "name": "c0t5000C5004C97FD73d0", 249 | "vid": "HITACHI", 250 | "pid": "HUS723020ALS640", 251 | "size": "3000000000000" 252 | }, 253 | { 254 | "name": "c0t5000C5004C97CFB3d0", 255 | "vid": "HITACHI", 256 | "pid": "HUS723020ALS640", 257 | "size": "3000000000000" 258 | } 259 | ] 260 | }, 261 | { 262 | "type": "mirror", 263 | "devices": [ 264 | { 265 | "name": "c0t5000C50048159867d0", 266 | "vid": "HITACHI", 267 | "pid": "HUS723020ALS640", 268 | "size": "3000000000000" 269 | }, 270 | { 271 | "name": "c0t5000C5004813CEC7d0", 272 | "vid": "HITACHI", 273 | "pid": "HUS723020ALS640", 274 | "size": "3000000000000" 275 | } 276 | ] 277 | }, 278 | { 279 | "type": "mirror", 280 | "devices": [ 281 | { 282 | "name": "c0t5000C50048134A97d0", 283 | "vid": "HITACHI", 284 | "pid": "HUS723020ALS640", 285 | "size": "3000000000000" 286 | }, 287 | { 288 | "name": "c0t5000C50048159167d0", 289 | "vid": "HITACHI", 290 | "pid": "HUS723020ALS640", 291 | "size": "3000000000000" 292 | } 293 | ] 294 | } 295 | ], 296 | "capacity": 48000000000000, 297 | "logs": [ 298 | { 299 | "name": "c0t5000C5004FFFFFFFd0", 300 | "vid": "STEC", 301 | "pid": "SSDSC2CW120A3", 302 | "size": "85899345920" 303 | } 304 | ] 305 | } 306 | -------------------------------------------------------------------------------- /tools/jsl.node.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration File for JavaScript Lint 3 | # 4 | # This configuration file can be used to lint a collection of scripts, or to enable 5 | # or disable warnings for scripts that are linted via the command line. 6 | # 7 | 8 | ### Warnings 9 | # Enable or disable warnings based on requirements. 10 | # Use "+WarningName" to display or "-WarningName" to suppress. 11 | # 12 | +ambiguous_else_stmt # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent 13 | +ambiguous_nested_stmt # block statements containing block statements should use curly braces to resolve ambiguity 14 | +ambiguous_newline # unexpected end of line; it is ambiguous whether these lines are part of the same statement 15 | +anon_no_return_value # anonymous function does not always return value 16 | +assign_to_function_call # assignment to a function call 17 | -block_without_braces # block statement without curly braces 18 | +comma_separated_stmts # multiple statements separated by commas (use semicolons?) 19 | +comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==) 20 | +default_not_at_end # the default case is not at the end of the switch statement 21 | +dup_option_explicit # duplicate "option explicit" control comment 22 | +duplicate_case_in_switch # duplicate case in switch statement 23 | +duplicate_formal # duplicate formal argument {name} 24 | +empty_statement # empty statement or extra semicolon 25 | +identifier_hides_another # identifer {name} hides an identifier in a parent scope 26 | -inc_dec_within_stmt # increment (++) and decrement (--) operators used as part of greater statement 27 | +incorrect_version # Expected /*jsl:content-type*/ control comment. The script was parsed with the wrong version. 28 | +invalid_fallthru # unexpected "fallthru" control comment 29 | +invalid_pass # unexpected "pass" control comment 30 | +jsl_cc_not_understood # couldn't understand control comment using /*jsl:keyword*/ syntax 31 | +leading_decimal_point # leading decimal point may indicate a number or an object member 32 | +legacy_cc_not_understood # couldn't understand control comment using /*@keyword@*/ syntax 33 | +meaningless_block # meaningless block; curly braces have no impact 34 | +mismatch_ctrl_comments # mismatched control comment; "ignore" and "end" control comments must have a one-to-one correspondence 35 | +misplaced_regex # regular expressions should be preceded by a left parenthesis, assignment, colon, or comma 36 | +missing_break # missing break statement 37 | +missing_break_for_last_case # missing break statement for last case in switch 38 | +missing_default_case # missing default case in switch statement 39 | +missing_option_explicit # the "option explicit" control comment is missing 40 | +missing_semicolon # missing semicolon 41 | +missing_semicolon_for_lambda # missing semicolon for lambda assignment 42 | +multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs 43 | +nested_comment # nested comment 44 | +no_return_value # function {name} does not always return a value 45 | +octal_number # leading zeros make an octal number 46 | +parseint_missing_radix # parseInt missing radix parameter 47 | +partial_option_explicit # the "option explicit" control comment, if used, must be in the first script tag 48 | +redeclared_var # redeclaration of {name} 49 | +trailing_comma_in_array # extra comma is not recommended in array initializers 50 | +trailing_decimal_point # trailing decimal point may indicate a number or an object member 51 | +undeclared_identifier # undeclared identifier: {name} 52 | +unreachable_code # unreachable code 53 | -unreferenced_argument # argument declared but never referenced: {name} 54 | -unreferenced_function # function is declared but never referenced: {name} 55 | +unreferenced_variable # variable is declared but never referenced: {name} 56 | +unsupported_version # JavaScript {version} is not supported 57 | +use_of_label # use of label 58 | +useless_assign # useless assignment 59 | +useless_comparison # useless comparison; comparing identical expressions 60 | -useless_quotes # the quotation marks are unnecessary 61 | +useless_void # use of the void type may be unnecessary (void is always undefined) 62 | +var_hides_arg # variable {name} hides argument 63 | +want_assign_or_call # expected an assignment or function call 64 | +with_statement # with statement hides undeclared variables; use temporary variable instead 65 | 66 | 67 | ### Output format 68 | # Customize the format of the error message. 69 | # __FILE__ indicates current file path 70 | # __FILENAME__ indicates current file name 71 | # __LINE__ indicates current line 72 | # __COL__ indicates current column 73 | # __ERROR__ indicates error message (__ERROR_PREFIX__: __ERROR_MSG__) 74 | # __ERROR_NAME__ indicates error name (used in configuration file) 75 | # __ERROR_PREFIX__ indicates error prefix 76 | # __ERROR_MSG__ indicates error message 77 | # 78 | # For machine-friendly output, the output format can be prefixed with 79 | # "encode:". If specified, all items will be encoded with C-slashes. 80 | # 81 | # Visual Studio syntax (default): 82 | +output-format __FILE__(__LINE__): __ERROR__ 83 | # Alternative syntax: 84 | #+output-format __FILE__:__LINE__: __ERROR__ 85 | 86 | 87 | ### Context 88 | # Show the in-line position of the error. 89 | # Use "+context" to display or "-context" to suppress. 90 | # 91 | +context 92 | 93 | 94 | ### Control Comments 95 | # Both JavaScript Lint and the JScript interpreter confuse each other with the syntax for 96 | # the /*@keyword@*/ control comments and JScript conditional comments. (The latter is 97 | # enabled in JScript with @cc_on@). The /*jsl:keyword*/ syntax is preferred for this reason, 98 | # although legacy control comments are enabled by default for backward compatibility. 99 | # 100 | -legacy_control_comments 101 | 102 | 103 | ### Defining identifiers 104 | # By default, "option explicit" is enabled on a per-file basis. 105 | # To enable this for all files, use "+always_use_option_explicit" 106 | -always_use_option_explicit 107 | 108 | # Define certain identifiers of which the lint is not aware. 109 | # (Use this in conjunction with the "undeclared identifier" warning.) 110 | # 111 | # Common uses for webpages might be: 112 | +define __dirname 113 | +define clearInterval 114 | +define clearTimeout 115 | +define console 116 | +define exports 117 | +define global 118 | +define module 119 | +define process 120 | +define require 121 | +define setInterval 122 | +define setTimeout 123 | +define Buffer 124 | +define JSON 125 | +define Math 126 | 127 | ### JavaScript Version 128 | # To change the default JavaScript version: 129 | #+default-type text/javascript;version=1.5 130 | #+default-type text/javascript;e4x=1 131 | 132 | ### Files 133 | # Specify which files to lint 134 | # Use "+recurse" to enable recursion (disabled by default). 135 | # To add a set of files, use "+process FileName", "+process Folder\Path\*.js", 136 | # or "+process Folder\Path\*.htm". 137 | # 138 | 139 | -------------------------------------------------------------------------------- /lib/disklayout.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2014, Joyent, Inc. 9 | */ 10 | 11 | require('/usr/node/node_modules/platform_node_version').assert(); 12 | 13 | /* 14 | * Returns the rounded-off capacity in GB. The purpose of this is to 15 | * group devices of the same basic size class; disks and to a lesser extent 16 | * SSDs are marketed at a very limited number of capacity points although 17 | * the actual capacities vary quite a bit at each one. We don't want to 18 | * get confused by that. 19 | */ 20 | function 21 | round_capacity(bytes) 22 | { 23 | var mb = bytes / 1000000; /* thieves, that's what they are */ 24 | var THRESHOLDS = [ 500000, 150000, 80000, 20000, 4500, 1000 ]; 25 | var i; 26 | var bestfit, bestdiff = 0; 27 | 28 | for (i = 0; i < THRESHOLDS.length; i++) { 29 | var t = THRESHOLDS[i]; 30 | var roundoff = Math.floor((mb + Math.floor(t / 2)) / t) * t; 31 | var multiplier = Math.pow(roundoff / t, 4); 32 | var diff = (mb - roundoff) * (mb - roundoff) * multiplier; 33 | 34 | if (Math.abs(mb - roundoff) / mb > 0.05) 35 | continue; 36 | 37 | if (diff < bestdiff || bestdiff === 0) { 38 | bestfit = roundoff; 39 | bestdiff = (mb - roundoff) * (mb - roundoff) * 40 | multiplier; 41 | } 42 | } 43 | 44 | if (Math.abs(bestfit - mb) / mb < 0.05) 45 | return (bestfit / 1000); 46 | 47 | /* 48 | * This device's size is not within +/-5% of any number of GB. 49 | * That's very unusual and suggests we probably oughtn't use it. 50 | * Round off to the nearest 1 GB and call it a day for now. Most 51 | * such devices are probably very small and we will return 0 for them. 52 | */ 53 | return (Math.round(mb / 1000)); 54 | } 55 | 56 | function 57 | merge_types(inv) 58 | { 59 | Object.keys(inv).forEach(function (t0type) { 60 | Object.keys(inv).forEach(function (t1type) { 61 | var t0 = inv[t0type]; 62 | var t1 = inv[t1type]; 63 | 64 | if (t0type == t1type || !t0 || !t1) 65 | return; 66 | if (t0.solid_state != t1.solid_state) 67 | return; 68 | if (Math.abs(t0.size - t1.size) / t1.size > 0.05) 69 | return; 70 | 71 | if (t0.disks.length > t1.disks.length) { 72 | t0.disks = t0.disks.concat(t1.disks); 73 | delete inv[t1type]; 74 | } else { 75 | t1.disks = t1.disks.concat(t0.disks); 76 | delete inv[t0type]; 77 | } 78 | }); 79 | }); 80 | } 81 | 82 | function 83 | shrink(inv) 84 | { 85 | Object.keys(inv).forEach(function (typedesc) { 86 | var smallest = 0; 87 | var largest = 0; 88 | var type = inv[typedesc]; 89 | 90 | type.disks.forEach(function (disk) { 91 | if (smallest === 0 || disk.size < smallest) 92 | smallest = disk.size; 93 | if (largest === 0 || disk.size > largest) 94 | largest = disk.size; 95 | }); 96 | type.smallest = smallest; 97 | type.largest = largest; 98 | delete type.size; 99 | }); 100 | } 101 | 102 | function 103 | xform_bucket(bucket) 104 | { 105 | var role = []; 106 | 107 | bucket.disks.forEach(function (disk) { 108 | role.push({ 109 | name: disk.name, 110 | vid: disk.vid, 111 | pid: disk.pid, 112 | size: disk.size 113 | }); 114 | }); 115 | 116 | return (role.sort(function (a, b) { 117 | return (a.size - b.size); 118 | })); 119 | } 120 | 121 | /* 122 | * At this point we have detected the likely size bucket for each device, then 123 | * merged any buckets that required it. We should have one bucket for each 124 | * approximate size, segregated by whether the devices are solid-state. We 125 | * also know the smallest size of any device in each sub-bucket. It's time to 126 | * assign each bucket a role. 127 | * 128 | * There are three possible roles: storage, cache, and slog. If there is 129 | * only one bucket left, that's easy: it's storage. Otherwise we're going 130 | * to make some judgment calls. All spinning disks are for storage, always. 131 | * If there are 4 or fewer of the smallest SSD type, they're slogs. Anything 132 | * else is a cache device, unless there were no spinning disks at all in 133 | * which case the largest devices will be used as primary storage. 134 | */ 135 | function 136 | assign_roles(inv) 137 | { 138 | var typedescs = Object.keys(inv); 139 | var ssddescs = []; 140 | var roles = {}; 141 | 142 | if (typedescs.length === 0) 143 | return (roles); 144 | 145 | if (typedescs.length === 1) { 146 | roles.storage = xform_bucket(inv[typedescs[0]]); 147 | return (roles); 148 | } 149 | 150 | typedescs.forEach(function (typedesc) { 151 | var role; 152 | 153 | if (inv[typedesc].solid_state) { 154 | ssddescs.push(typedesc); 155 | } else { 156 | role = xform_bucket(inv[typedesc]); 157 | if (roles.storage) 158 | roles.storage = roles.storage.concat(role); 159 | else 160 | roles.storage = role; 161 | } 162 | }); 163 | 164 | if (ssddescs.length === 0) 165 | return (roles); 166 | 167 | ssddescs.sort(function (a, b) { 168 | if (inv[a].smallest < inv[b].smallest) 169 | return (-1); 170 | if (inv[a].smallest > inv[b].smallest) 171 | return (1); 172 | return (0); 173 | }); 174 | 175 | if (inv[ssddescs[0]].disks.length < 5) { 176 | roles.slog = xform_bucket(inv[ssddescs[0]]); 177 | ssddescs.splice(0, 1); 178 | } 179 | 180 | if (!roles.storage) { 181 | var largest = ssddescs.splice(ssddescs.length - 1, 1); 182 | roles.storage = xform_bucket(inv[largest]); 183 | } 184 | 185 | ssddescs.forEach(function (typedesc) { 186 | var role = xform_bucket(inv[typedesc]); 187 | if (roles.cache) 188 | roles.cache = roles.cache.concat(role); 189 | else 190 | roles.cache = role; 191 | }); 192 | 193 | return (roles); 194 | } 195 | 196 | function 197 | do_single(disks) 198 | { 199 | var config = { vdevs: [] }; 200 | config.vdevs[0] = disks[0]; 201 | config.capacity = disks[0].size; 202 | 203 | return (config); 204 | } 205 | 206 | function 207 | do_mirror(disks) 208 | { 209 | var spares; 210 | var config = {}; 211 | var capacity; 212 | 213 | if (disks.length < 2) { 214 | config.error = 'at least 2 disks are required for mirroring'; 215 | return (config); 216 | } 217 | 218 | if (disks.length === 2) { 219 | spares = 0; 220 | } else { 221 | spares = Math.ceil(disks.length / 16); 222 | spares += (disks.length - spares) % 2; 223 | } 224 | 225 | /* 226 | * The largest devices can spare for any others. Not so for the 227 | * smaller ones. 228 | */ 229 | if (spares > 0) 230 | config.spares = disks.splice(disks.length - spares, spares); 231 | 232 | config.vdevs = []; 233 | capacity = 0; 234 | while (disks.length) { 235 | var vdev = {}; 236 | 237 | capacity += disks[0].size * 1; 238 | vdev.type = 'mirror'; 239 | vdev.devices = disks.splice(0, 2); 240 | config.vdevs.push(vdev); 241 | } 242 | 243 | config.capacity = capacity; 244 | 245 | return (config); 246 | } 247 | 248 | function 249 | do_raidz2(disks) 250 | { 251 | var MINWIDTH = 7; 252 | var MAXWIDTH = 12; 253 | var config = {}; 254 | var spares; 255 | var width; 256 | var capacity; 257 | 258 | spares = Math.min(2, Math.floor(disks.length / 12)); 259 | 260 | while (disks.length - spares >= MINWIDTH) { 261 | for (width = MINWIDTH; width <= MAXWIDTH; width++) { 262 | if ((disks.length - spares) % width === 0) 263 | break; 264 | } 265 | if (width <= MAXWIDTH) 266 | break; 267 | ++spares; 268 | } 269 | 270 | if (disks.length - spares < MINWIDTH) { 271 | config.error = 'no acceptable raidz2 layout is possible with ' + 272 | disks.length + ' disks'; 273 | return (config); 274 | } 275 | 276 | /* 277 | * The largest devices can spare for any others. Not so for the 278 | * smaller ones. 279 | */ 280 | if (spares > 0) 281 | config.spares = disks.splice(disks.length - spares, spares); 282 | 283 | config.vdevs = []; 284 | capacity = 0; 285 | while (disks.length) { 286 | var vdev = {}; 287 | 288 | capacity += (width - 2) * disks[0].size; 289 | vdev.type = 'raidz2'; 290 | vdev.devices = disks.splice(0, width); 291 | config.vdevs.push(vdev); 292 | } 293 | 294 | config.capacity = capacity; 295 | 296 | return (config); 297 | } 298 | 299 | var LAYOUTS = { 300 | single: do_single, 301 | mirror: do_mirror, 302 | raidz2: do_raidz2 303 | }; 304 | 305 | function 306 | register_layout(name, f) 307 | { 308 | if (typeof (name) !== 'string' || typeof (f) !== 'function') 309 | throw new TypeError('string and function arguments required'); 310 | LAYOUTS[name] = f; 311 | } 312 | 313 | function 314 | list_supported() 315 | { 316 | return (Object.keys(LAYOUTS)); 317 | } 318 | 319 | function 320 | compute_layout(disks, layout) 321 | { 322 | var disktypes = {}; 323 | var diskroles; 324 | var config = {}; 325 | 326 | config.input = disks; 327 | config.layout = layout; 328 | 329 | disks.forEach(function (disk) { 330 | var gb; 331 | var typespec; 332 | 333 | if (disk.removable) 334 | return; 335 | 336 | if ((gb = round_capacity(disk.size)) === 0) 337 | return; 338 | 339 | typespec = disk.type + ',' + gb + ',' + disk.solid_state; 340 | disk.rounded_size = gb; 341 | if (!disktypes[typespec]) { 342 | disktypes[typespec] = { 343 | type: disk.type, 344 | size: gb, 345 | solid_state: disk.solid_state, 346 | disks: [] 347 | }; 348 | } 349 | disktypes[typespec].disks.push(disk); 350 | }); 351 | 352 | merge_types(disktypes); 353 | shrink(disktypes); 354 | diskroles = assign_roles(disktypes); 355 | 356 | if (!diskroles.storage) { 357 | config.error = 'no primary storage disks available'; 358 | return (config); 359 | } 360 | 361 | if (!layout) { 362 | if (diskroles.storage.length == 1) 363 | layout = 'single'; 364 | else if (diskroles.storage.length > 16) 365 | layout = 'raidz2'; 366 | else 367 | layout = 'mirror'; 368 | } 369 | 370 | if (!LAYOUTS[layout]) { 371 | config.error = 'unknown layout ' + layout; 372 | return (config); 373 | } 374 | 375 | config = LAYOUTS[layout](diskroles.storage); 376 | 377 | if (diskroles.slog) 378 | config.logs = diskroles.slog; 379 | if (diskroles.cache) 380 | config.cache = diskroles.cache; 381 | 382 | return (config); 383 | } 384 | 385 | module.exports = { 386 | register: register_layout, 387 | list_supported: list_supported, 388 | compute: compute_layout 389 | }; 390 | -------------------------------------------------------------------------------- /tools/mk/Makefile.targ: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.targ: common targets. 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | # This Makefile defines several useful targets and rules. You can use it by 21 | # including it from a Makefile that specifies some of the variables below. 22 | # 23 | # Targets defined in this Makefile: 24 | # 25 | # check Checks JavaScript files for lint and style 26 | # Checks bash scripts for syntax 27 | # Checks SMF manifests for validity against the SMF DTD 28 | # 29 | # clean Removes built files 30 | # 31 | # docs Builds restdown documentation in docs/ 32 | # 33 | # prepush Depends on "check" and "test" 34 | # 35 | # test Does nothing (you should override this) 36 | # 37 | # xref Generates cscope (source cross-reference index) 38 | # 39 | # For details on what these targets are supposed to do, see the Joyent 40 | # Engineering Guide. 41 | # 42 | # To make use of these targets, you'll need to set some of these variables. Any 43 | # variables left unset will simply not be used. 44 | # 45 | # BASH_FILES Bash scripts to check for syntax 46 | # (paths relative to top-level Makefile) 47 | # 48 | # CLEAN_FILES Files to remove as part of the "clean" target. Note 49 | # that files generated by targets in this Makefile are 50 | # automatically included in CLEAN_FILES. These include 51 | # restdown-generated HTML and JSON files. 52 | # 53 | # DOC_FILES Restdown (documentation source) files. These are 54 | # assumed to be contained in "docs/", and must NOT 55 | # contain the "docs/" prefix. 56 | # 57 | # JSL_CONF_NODE Specify JavaScriptLint configuration files 58 | # JSL_CONF_WEB (paths relative to top-level Makefile) 59 | # 60 | # Node.js and Web configuration files are separate 61 | # because you'll usually want different global variable 62 | # configurations. If no file is specified, none is given 63 | # to jsl, which causes it to use a default configuration, 64 | # which probably isn't what you want. 65 | # 66 | # JSL_FILES_NODE JavaScript files to check with Node config file. 67 | # JSL_FILES_WEB JavaScript files to check with Web config file. 68 | # 69 | # JSON_FILES JSON files to be validated 70 | # 71 | # JSSTYLE_FILES JavaScript files to be style-checked 72 | # 73 | # You can also override these variables: 74 | # 75 | # BASH Path to bash (default: "bash") 76 | # 77 | # CSCOPE_DIRS Directories to search for source files for the cscope 78 | # index. (default: ".") 79 | # 80 | # JSL Path to JavaScriptLint (default: "jsl") 81 | # 82 | # JSL_FLAGS_NODE Additional flags to pass through to JSL 83 | # JSL_FLAGS_WEB 84 | # JSL_FLAGS 85 | # 86 | # JSON Path to json tool (default: "json") 87 | # 88 | # JSSTYLE Path to jsstyle (default: "jsstyle") 89 | # 90 | # JSSTYLE_FLAGS Additional flags to pass through to jsstyle 91 | # 92 | # RESTDOWN_EXT By default '.md' is required for DOC_FILES (see above). 93 | # If you want to use, say, '.restdown' instead, then set 94 | # 'RESTDOWN_EXT=.restdown' in your Makefile. 95 | # 96 | 97 | # 98 | # Defaults for the various tools we use. 99 | # 100 | BASH ?= bash 101 | BASHSTYLE ?= tools/bashstyle 102 | CP ?= cp 103 | CSCOPE ?= cscope 104 | CSCOPE_DIRS ?= . 105 | JSL ?= jsl 106 | JSON ?= json 107 | JSSTYLE ?= jsstyle 108 | MKDIR ?= mkdir -p 109 | MV ?= mv 110 | RESTDOWN_FLAGS ?= 111 | RESTDOWN_EXT ?= .md 112 | RMTREE ?= rm -rf 113 | JSL_FLAGS ?= --nologo --nosummary 114 | 115 | ifeq ($(shell uname -s),SunOS) 116 | TAR ?= gtar 117 | else 118 | TAR ?= tar 119 | endif 120 | 121 | 122 | # 123 | # Defaults for other fixed values. 124 | # 125 | BUILD = build 126 | DISTCLEAN_FILES += $(BUILD) 127 | DOC_BUILD = $(BUILD)/docs/public 128 | 129 | # 130 | # Configure JSL_FLAGS_{NODE,WEB} based on JSL_CONF_{NODE,WEB}. 131 | # 132 | ifneq ($(origin JSL_CONF_NODE), undefined) 133 | JSL_FLAGS_NODE += --conf=$(JSL_CONF_NODE) 134 | endif 135 | 136 | ifneq ($(origin JSL_CONF_WEB), undefined) 137 | JSL_FLAGS_WEB += --conf=$(JSL_CONF_WEB) 138 | endif 139 | 140 | # 141 | # Targets. For descriptions on what these are supposed to do, see the 142 | # Joyent Engineering Guide. 143 | # 144 | 145 | # 146 | # Instruct make to keep around temporary files. We have rules below that 147 | # automatically update git submodules as needed, but they employ a deps/*/.git 148 | # temporary file. Without this directive, make tries to remove these .git 149 | # directories after the build has completed. 150 | # 151 | .SECONDARY: $($(wildcard deps/*):%=%/.git) 152 | 153 | # 154 | # This rule enables other rules that use files from a git submodule to have 155 | # those files depend on deps/module/.git and have "make" automatically check 156 | # out the submodule as needed. 157 | # 158 | deps/%/.git: 159 | git submodule update --init deps/$* 160 | 161 | # 162 | # These recipes make heavy use of dynamically-created phony targets. The parent 163 | # Makefile defines a list of input files like BASH_FILES. We then say that each 164 | # of these files depends on a fake target called filename.bashchk, and then we 165 | # define a pattern rule for those targets that runs bash in check-syntax-only 166 | # mode. This mechanism has the nice properties that if you specify zero files, 167 | # the rule becomes a noop (unlike a single rule to check all bash files, which 168 | # would invoke bash with zero files), and you can check individual files from 169 | # the command line with "make filename.bashchk". 170 | # 171 | .PHONY: check-bash 172 | check-bash: $(BASH_FILES:%=%.bashchk) $(BASH_FILES:%=%.bashstyle) 173 | 174 | %.bashchk: % 175 | $(BASH) -n $^ 176 | 177 | %.bashstyle: % 178 | $(BASHSTYLE) $^ 179 | 180 | .PHONY: check-json 181 | check-json: $(JSON_FILES:%=%.jsonchk) 182 | 183 | %.jsonchk: % 184 | $(JSON) --validate -f $^ 185 | 186 | # 187 | # The above approach can be slow when there are many files to check because it 188 | # requires that "make" invoke the check tool once for each file, rather than 189 | # passing in several files at once. For the JavaScript check targets, we define 190 | # a variable for the target itself *only if* the list of input files is 191 | # non-empty. This avoids invoking the tool if there are no files to check. 192 | # 193 | JSL_NODE_TARGET = $(if $(JSL_FILES_NODE), check-jsl-node) 194 | .PHONY: check-jsl-node 195 | check-jsl-node: $(JSL_EXEC) 196 | $(JSL) $(JSL_FLAGS) $(JSL_FLAGS_NODE) $(JSL_FILES_NODE) 197 | 198 | JSL_WEB_TARGET = $(if $(JSL_FILES_WEB), check-jsl-web) 199 | .PHONY: check-jsl-web 200 | check-jsl-web: $(JSL_EXEC) 201 | $(JSL) $(JSL_FLAGS) $(JSL_FLAGS_WEB) $(JSL_FILES_WEB) 202 | 203 | .PHONY: check-jsl 204 | check-jsl: $(JSL_NODE_TARGET) $(JSL_WEB_TARGET) 205 | 206 | JSSTYLE_TARGET = $(if $(JSSTYLE_FILES), check-jsstyle) 207 | .PHONY: check-jsstyle 208 | check-jsstyle: $(JSSTYLE_EXEC) 209 | $(JSSTYLE) $(JSSTYLE_FLAGS) $(JSSTYLE_FILES) 210 | 211 | .PHONY: check 212 | check:: check-jsl check-json $(JSSTYLE_TARGET) check-bash 213 | @echo check ok 214 | 215 | .PHONY: clean 216 | clean:: 217 | -$(RMTREE) $(CLEAN_FILES) 218 | 219 | .PHONY: distclean 220 | distclean:: clean 221 | -$(RMTREE) $(DISTCLEAN_FILES) 222 | 223 | CSCOPE_FILES = cscope.in.out cscope.out cscope.po.out 224 | CLEAN_FILES += $(CSCOPE_FILES) 225 | 226 | .PHONY: xref 227 | xref: cscope.files 228 | $(CSCOPE) -bqR 229 | 230 | .PHONY: cscope.files 231 | cscope.files: 232 | find $(CSCOPE_DIRS) -name '*.c' -o -name '*.h' -o -name '*.cc' \ 233 | -o -name '*.js' -o -name '*.s' -o -name '*.cpp' > $@ 234 | 235 | # 236 | # The "docs" target is complicated because we do several things here: 237 | # 238 | # (1) Use restdown to build HTML and JSON files from each of DOC_FILES. 239 | # 240 | # (2) Copy these files into $(DOC_BUILD) (build/docs/public), which 241 | # functions as a complete copy of the documentation that could be 242 | # mirrored or served over HTTP. 243 | # 244 | # (3) Then copy any directories and media from docs/media into 245 | # $(DOC_BUILD)/media. This allows projects to include their own media, 246 | # including files that will override same-named files provided by 247 | # restdown. 248 | # 249 | # Step (3) is the surprisingly complex part: in order to do this, we need to 250 | # identify the subdirectories in docs/media, recreate them in 251 | # $(DOC_BUILD)/media, then do the same with the files. 252 | # 253 | DOC_MEDIA_DIRS := $(shell find docs/media -type d 2>/dev/null | grep -v "^docs/media$$") 254 | DOC_MEDIA_DIRS := $(DOC_MEDIA_DIRS:docs/media/%=%) 255 | DOC_MEDIA_DIRS_BUILD := $(DOC_MEDIA_DIRS:%=$(DOC_BUILD)/media/%) 256 | 257 | DOC_MEDIA_FILES := $(shell find docs/media -type f 2>/dev/null) 258 | DOC_MEDIA_FILES := $(DOC_MEDIA_FILES:docs/media/%=%) 259 | DOC_MEDIA_FILES_BUILD := $(DOC_MEDIA_FILES:%=$(DOC_BUILD)/media/%) 260 | 261 | # 262 | # Like the other targets, "docs" just depends on the final files we want to 263 | # create in $(DOC_BUILD), leveraging other targets and recipes to define how 264 | # to get there. 265 | # 266 | .PHONY: docs 267 | docs: \ 268 | $(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.html) \ 269 | $(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.json) \ 270 | $(DOC_MEDIA_FILES_BUILD) 271 | 272 | # 273 | # We keep the intermediate files so that the next build can see whether the 274 | # files in DOC_BUILD are up to date. 275 | # 276 | .PRECIOUS: \ 277 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html) \ 278 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%json) 279 | 280 | # 281 | # We do clean those intermediate files, as well as all of DOC_BUILD. 282 | # 283 | CLEAN_FILES += \ 284 | $(DOC_BUILD) \ 285 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html) \ 286 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.json) 287 | 288 | # 289 | # Before installing the files, we must make sure the directories exist. The | 290 | # syntax tells make that the dependency need only exist, not be up to date. 291 | # Otherwise, it might try to rebuild spuriously because the directory itself 292 | # appears out of date. 293 | # 294 | $(DOC_MEDIA_FILES_BUILD): | $(DOC_MEDIA_DIRS_BUILD) 295 | 296 | $(DOC_BUILD)/%: docs/% | $(DOC_BUILD) 297 | $(CP) $< $@ 298 | 299 | docs/%.json docs/%.html: docs/%$(RESTDOWN_EXT) | $(DOC_BUILD) $(RESTDOWN_EXEC) \ 300 | $(EXTRA_DOC_DEPS) 301 | $(RESTDOWN) $(RESTDOWN_FLAGS) -m $(DOC_BUILD) $< 302 | 303 | $(DOC_BUILD): 304 | $(MKDIR) $@ 305 | 306 | $(DOC_MEDIA_DIRS_BUILD): 307 | $(MKDIR) $@ 308 | 309 | # 310 | # The default "test" target does nothing. This should usually be overridden by 311 | # the parent Makefile. It's included here so we can define "prepush" without 312 | # requiring the repo to define "test". 313 | # 314 | .PHONY: test 315 | test: 316 | 317 | .PHONY: prepush 318 | prepush: check test 319 | -------------------------------------------------------------------------------- /test/zfs.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2014, Joyent, Inc. 9 | */ 10 | 11 | var test = require('tap').test; 12 | var util = require('util'); 13 | var fs = require('fs'); 14 | var path = require('path'); 15 | var zutil = require('/usr/node/node_modules/zutil'); 16 | 17 | var puts = util.puts; 18 | var inspect = util.inspect; 19 | 20 | var zfsName = process.argv[2] || 'nodezfstest/test'; 21 | var zpoolName = zfsName.split('/')[0]; 22 | var testFilename = '/' + zfsName + '/mytestfile'; 23 | var testData = 'Dancing is forbidden!'; 24 | var testDataModified = 'Chicken arise! Arise chicken! Arise!'; 25 | 26 | var zfs = require('../lib/zfs').zfs; 27 | var zpool = require('../lib/zfs').zpool; 28 | 29 | test('basic', function (t) { 30 | t.ok(zfs, 'zfs module should exist'); 31 | t.ok(zpool, 'zpool module should exist'); 32 | t.end(); 33 | }); 34 | 35 | test('zfs', { skip: (zutil.getZone() !== 'global') }, function (t) { 36 | var datasetExists = function (_t, name, callback) { 37 | var listFunc = name.indexOf('@') === -1 ? 38 | zfs.list : zfs.list_snapshots; 39 | 40 | listFunc(name, function (err, fields, list) { 41 | _t.notOk(err, 'dataset listing failed'); 42 | _t.ok(list, 'no dataset list was returned'); 43 | _t.ok(list.length > 0, 'dataset list is empty'); 44 | _t.ok(list.some(function (d) { 45 | return (d[0] === name); 46 | }), 'zfs dataset ' + name + ' does not exist'); 47 | callback(); 48 | }); 49 | }; 50 | 51 | var noDatasetExists = function (_t, name, callback) { 52 | var listFunc = name.indexOf('@') === -1 ? 53 | zfs.list : zfs.list_snapshots; 54 | 55 | listFunc(name, function (err, fields, list) { 56 | _t.ok(err, 'expected error did not occur'); 57 | _t.ok(err.toString().match(/does not exist/), 58 | 'received unexpected error message ' + err.msg); 59 | _t.notOk(list, 'dataset list is not empty'); 60 | callback(); 61 | }); 62 | }; 63 | 64 | t.test('list pools', function (st) { 65 | zpool.list(function (err, fields, list) { 66 | st.notOk(err, 'zpool list failed: ' + err); 67 | st.ok(list, 'no zpool list was returned'); 68 | st.ok(list.length > 0, 'zpool list is empty'); 69 | st.ok(list.some( 70 | function (p) { return (p[0] == zpoolName); }), 71 | 'zpool does not exist'); 72 | }); 73 | st.end(); 74 | }); 75 | 76 | t.test('list datasets', function (st) { 77 | zfs.list(function (err, fields, list) { 78 | st.notOk(err, 'dataset list failed: ' + err); 79 | st.ok(list, 'no dataset list was returned'); 80 | st.ok(list.length > 0, 'dataset list is empty'); 81 | st.notOk(list.some( 82 | function (d) { return d[0] == zfsName; }), 83 | 'zfs dataset ' + zfsName + ' already exists'); 84 | }); 85 | st.end(); 86 | }); 87 | 88 | t.test('create dataset', function (st) { 89 | zfs.create(zfsName, function () { 90 | datasetExists(st, zfsName, function () { 91 | fs.writeFile(testFilename, testData, 92 | function (err) { 93 | if (err) 94 | throw err; 95 | st.end(); 96 | }); 97 | }); 98 | }); 99 | }); 100 | 101 | t.test('set property', function (st) { 102 | var properties = { 103 | 'test:property1': 'foo\tbix\tqube', 104 | 'test:property2': 'baz' 105 | }; 106 | zfs.set(zfsName, properties, function (err) { 107 | st.notOk(err, 'setting properties failed: ' + err); 108 | st.end(); 109 | }); 110 | }); 111 | 112 | t.test('get property', function (st) { 113 | var val; 114 | 115 | zfs.get(zfsName, ['test:property1', 'test:property2'], 116 | false, function (err, properties) { 117 | st.ok(properties, 'no properties were returned'); 118 | val = properties['test:property1']; 119 | st.equal(val, 'foo\tbix\tqube', 120 | 'property test:property1 has incorrect value "' + 121 | val + '"'); 122 | val = properties['test:property2']; 123 | st.equal(val, 'baz', 124 | 'property test:property2 has incorrect value ' + 125 | val + '"'); 126 | st.end(); 127 | }); 128 | }); 129 | 130 | t.test('take snapshot', function (st) { 131 | var snapshotName = zfsName + '@mysnapshot'; 132 | 133 | zfs.snapshot(snapshotName, function (err, stdout, stderr) { 134 | st.notOk(err, 'snapshot error occurred'); 135 | zfs.list_snapshots(function (serr, fields, lines) { 136 | st.ok(lines.some(function (s) { 137 | return (s[0] === snapshotName); }), 138 | 'snapshot not found'); 139 | zfs.list(function (sserr, sfields, slines) { 140 | st.ok(lines, 141 | 'no dataset list was returned'); 142 | st.notOk(lines.some(function (d) { 143 | return (d[0] === snapshotName); 144 | }), 145 | 'snapshot found in zfs.list result'); 146 | st.end(); 147 | }); 148 | }); 149 | }); 150 | }); 151 | 152 | t.test('recursive dataset list', function (st) { 153 | function inList(needle, haystack) { 154 | return (haystack.some(function (i) { 155 | return (needle === i[0]); 156 | })); 157 | } 158 | 159 | zfs.list(zfsName, { recursive: true, type: 'all' }, 160 | function (error, fields, list) { 161 | st.equal(list.length, 2, 'dataset list has length ' + 162 | list.length); 163 | st.ok(inList(zfsName, list), 164 | zfsName + ' not found in list'); 165 | st.ok(inList(zfsName + '@mysnapshot', list), 166 | zfsName + '@mysnapshot not found in list'); 167 | st.end(); 168 | }); 169 | }); 170 | 171 | t.test('send a snapshot to a file', function (st) { 172 | var snapshotName = zfsName + '@mysnapshot'; 173 | var snapshotFilename = '/tmp/node-zfs-test-snapshot.zfs'; 174 | 175 | zfs.send(snapshotName, snapshotFilename, function () { 176 | path.exists(snapshotFilename, function (exists) { 177 | st.ok(exists, 'no output file exists'); 178 | st.end(); 179 | }); 180 | }); 181 | }); 182 | 183 | t.test('receive a snapshot from a file', function (st) { 184 | var datasetName = zfsName + '/from_receive'; 185 | var snapshotFilename = '/tmp/node-zfs-test-snapshot.zfs'; 186 | 187 | zfs.receive(datasetName, snapshotFilename, function (err) { 188 | st.notOk(err, 'zfs.receive failed: ' + err); 189 | datasetExists(t, datasetName, function () { 190 | path.exists('/' + datasetName + '/mytestfile', 191 | function (exists) { 192 | st.ok(exists, 'snap file went away'); 193 | fs.readFile('/' + datasetName + 194 | '/mytestfile', function (e, str) { 195 | st.notOk(e, 196 | 'read test file failed: ' + 197 | e); 198 | st.equal(str.toString(), 199 | testData); 200 | st.end(); 201 | }); 202 | }); 203 | }); 204 | }); 205 | }); 206 | 207 | t.test('snapshot rollback', function (st) { 208 | var snapshotName = zfsName + '@mysnapshot'; 209 | 210 | fs.writeFile(testFilename, testDataModified, 211 | function (err) { 212 | st.notOk(err, 'write test file failed: ' + err); 213 | fs.readFile(testFilename, function (serr, str) { 214 | st.notOk(err, 'read test file failed: ' + serr); 215 | st.equal(str.toString(), testDataModified); 216 | zfs.rollback(snapshotName, 217 | function (sserr, stdout, stderr) { 218 | st.notOk(sserr, 219 | 'rollback failed: ' + sserr); 220 | fs.readFile(testFilename, 221 | function (ssserr, sstr) { 222 | st.equal(sstr.toString(), 223 | testData); 224 | st.end(); 225 | }); 226 | }); 227 | }); 228 | }); 229 | }); 230 | 231 | t.test('create clone', function (st) { 232 | var snapshotName = zfsName + '@mysnapshot'; 233 | var cloneName = zpoolName + '/' + 'myclone'; 234 | 235 | zfs.clone(snapshotName, cloneName, 236 | function (err, stdout, stderr) { 237 | datasetExists(st, cloneName, function () { 238 | st.end(); 239 | }); 240 | }); 241 | }); 242 | 243 | t.test('destroy clone', function (st) { 244 | var cloneName = zpoolName + '/' + 'myclone'; 245 | 246 | datasetExists(t, cloneName, function () { 247 | zfs.destroy(cloneName, function (err, stdout, stderr) { 248 | noDatasetExists(t, cloneName, function () { 249 | st.end(); 250 | }); 251 | }); 252 | }); 253 | }); 254 | 255 | t.test('destroy snapshot', function (st) { 256 | var snapshotName = zfsName + '@mysnapshot'; 257 | 258 | datasetExists(st, snapshotName, function () { 259 | zfs.destroy(snapshotName, 260 | function (err, stdout, stderr) { 261 | noDatasetExists(t, snapshotName, 262 | function () { 263 | st.end(); 264 | }); 265 | }); 266 | }); 267 | }); 268 | 269 | t.test('destroy dataset', function (st) { 270 | zfs.destroyAll(zfsName, function (err, stdout, stderr) { 271 | noDatasetExists(st, zfsName, function () { 272 | st.end(); 273 | }); 274 | }); 275 | }); 276 | 277 | t.test('list errors', function (st) { 278 | var datasetName = 'thisprobably/doesnotexist'; 279 | 280 | noDatasetExists(t, datasetName, function () { 281 | zfs.list(datasetName, function (err, fields, list) { 282 | st.ok(err, 'error expected but did not occur'); 283 | st.ok(err.toString().match(/does not exist/), 284 | 'bad error on nonexistent dataset: ' + err); 285 | st.end(); 286 | }); 287 | }); 288 | }); 289 | 290 | t.test('delete errors', function (st) { 291 | var datasetName = 'thisprobably/doesnotexist'; 292 | 293 | noDatasetExists(st, datasetName, function () { 294 | zfs.destroy(datasetName, 295 | function (err, stdout, stderr) { 296 | st.ok(err, 'no error deleting nonexistent ds'); 297 | st.ok(err.toString().match(/does not exist/), 298 | 'bad error message on deletion: ' + err); 299 | st.end(); 300 | }); 301 | }); 302 | }); 303 | }); 304 | 305 | function 306 | check_layout(dl, t, name, layout) 307 | { 308 | var disks = []; 309 | var config; 310 | var correct; 311 | var cfname = 'config.' + name; 312 | 313 | if (layout) 314 | cfname += '.' + layout; 315 | 316 | correct = JSON.parse(fs.readFileSync(cfname, 'utf8')); 317 | 318 | t.test(name, function (st) { 319 | fs.readFile('diskinfo.' + name, 'utf8', function (err, data) { 320 | var lines; 321 | 322 | if (err) 323 | throw err; 324 | 325 | lines = data.trim().split('\n'); 326 | lines.forEach(function (line) { 327 | if (line) { 328 | var row = line.split('\t'); 329 | disks.push({ 330 | type: row[0], 331 | name: row[1], 332 | vid: row[2], 333 | pid: row[3], 334 | size: row[4], 335 | removable: (row[5] === 'yes'), 336 | solid_state: (row[6] === 'yes') 337 | }); 338 | } 339 | }); 340 | 341 | config = dl.compute(disks, layout); 342 | st.deepEqual(config, correct); 343 | st.end(); 344 | }); 345 | }); 346 | } 347 | 348 | test('disklayout', function (t) { 349 | var disklayout = require('../lib/disklayout'); 350 | 351 | t.ok(disklayout, 'disklayout module should exist'); 352 | 353 | check_layout(disklayout, t, 'single', undefined); 354 | check_layout(disklayout, t, 'mirror', undefined); 355 | check_layout(disklayout, t, 'removable', undefined); 356 | check_layout(disklayout, t, 'single.ssd', undefined); 357 | check_layout(disklayout, t, 'ssd', undefined); 358 | check_layout(disklayout, t, 'coal', undefined); 359 | check_layout(disklayout, t, 'dell', undefined); 360 | check_layout(disklayout, t, 'richmond', undefined); 361 | check_layout(disklayout, t, 'ms', undefined); 362 | check_layout(disklayout, t, 'ms', 'mirror'); 363 | check_layout(disklayout, t, 'richmond', 'raidz2'); 364 | 365 | t.end(); 366 | }); 367 | -------------------------------------------------------------------------------- /lib/zfs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright 2022 Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * Note: This file should be kept in sync as much as possible between: 13 | * 14 | * - smartos-live/src/node_modules/zfs.js 15 | * - node-zfs/lib/zfs.js 16 | * 17 | * If you update one, please be sure to update the other and perform all 18 | * necessary regression tests. Preferably, make changes to node-zfs then 19 | * copy the whole file into smartos-live. 20 | */ 21 | 22 | var cp = require('child_process'), 23 | fs = require('fs'); 24 | 25 | var execFile = cp.execFile, 26 | spawn = cp.spawn; 27 | 28 | /* 29 | * ZFS utilities paths 30 | */ 31 | exports.paths = { 32 | 'kbmadm': '/usr/sbin/kbmadm', 33 | 'zpool': '/sbin/zpool', 34 | 'zfs': '/sbin/zfs' 35 | }; 36 | 37 | var zpool = exports.zpool = function () { }; 38 | 39 | // if zfs commands take longer than timeoutDuration it's an error 40 | var timeoutDuration = exports.timeoutDuration = 10 * 60 * 1000; 41 | 42 | function zfsErrorStr(error, stderr) { 43 | if (!error) 44 | return (null); 45 | 46 | if (error.killed) 47 | return ('Process killed due to timeout.'); 48 | 49 | return (error.message || (stderr ? stderr.toString() : '')); 50 | } 51 | 52 | function zfsError(error, stderr) { 53 | return (new Error(zfsErrorStr(error, stderr))); 54 | } 55 | 56 | zpool.listFields_ = [ 'name', 'size', 'allocated', 'free', 'cap', 57 | 'health', 'altroot' ]; 58 | 59 | zpool.listDisks = function () { 60 | if (arguments.length !== 1) 61 | throw Error('Invalid arguments'); 62 | var callback = arguments[0]; 63 | 64 | execFile('/usr/bin/diskinfo', [ '-Hp' ], { timeout: timeoutDuration }, 65 | function (error, stdout, stderr) { 66 | if (error) 67 | return (callback(stderr.toString())); 68 | 69 | var disks = []; 70 | var rows = parseTabSeperatedTable(stdout); 71 | 72 | for (var ii = 0; ii < rows.length; ii++) { 73 | disks.push({ 74 | type: rows[ii][0], 75 | name: rows[ii][1], 76 | vid: rows[ii][2], 77 | pid: rows[ii][3], 78 | size: rows[ii][4], 79 | removable: (rows[ii][5] === 'yes'), 80 | solid_state: (rows[ii][6] === 'yes') 81 | }); 82 | } 83 | 84 | return (callback(null, disks)); 85 | }); 86 | }; 87 | 88 | zpool.list = function () { 89 | var pool, opts = {}, callback; 90 | switch (arguments.length) { 91 | case 1: 92 | callback = arguments[0]; 93 | break; 94 | case 2: 95 | pool = arguments[0]; 96 | callback = arguments[1]; 97 | break; 98 | case 3: 99 | pool = arguments[0]; 100 | opts = arguments[1]; 101 | callback = arguments[2]; 102 | break; 103 | default: 104 | throw Error('Invalid arguments'); 105 | } 106 | opts.fields = opts.fields || zpool.listFields_; 107 | var args = ['list', '-H', '-o', opts.fields.join(',')]; 108 | if (opts.parseable) { 109 | args.push('-p'); 110 | } 111 | 112 | if (pool) 113 | args.push(pool); 114 | 115 | execFile(exports.paths.zpool, args, { timeout: timeoutDuration }, 116 | function (error, stdout, stderr) { 117 | if (error) 118 | return (callback(zfsError(error, stderr))); 119 | var rows = parseTabSeperatedTable(stdout); 120 | return (callback(null, opts.fields, rows)); 121 | }); 122 | }; 123 | 124 | zpool.status = function (pool, callback) { 125 | if (arguments.length != 2) 126 | throw Error('Invalid arguments'); 127 | 128 | execFile(exports.paths.zpool, [ 'status', pool ], 129 | { timeout: timeoutDuration }, 130 | function (error, stdout, stderr) { 131 | stdout = stdout.trim(); 132 | if (error || stdout == 'no pools available\n') { 133 | callback(null, 'UNKNOWN'); 134 | return; 135 | } 136 | 137 | var lines = stdout.split('\n'); 138 | for (var i = 0; i < lines.length; i++) { 139 | if (lines[i].trim().substr(0, 5) === 'state') { 140 | return (callback(null, 141 | lines[i].trim().substr(7))); 142 | } 143 | } 144 | callback(null, 'UNKNOWN'); 145 | }); 146 | }; 147 | 148 | /* 149 | * zpool.create() 150 | * 151 | * This allows fine-grained control and exposes all features of the 152 | * zpool create command, including log devices, cache devices, and hot spares. 153 | * The input is an object of the form produced by the disklayout library. 154 | */ 155 | zpool.create = function (pool, config, force, encrypt, efi, callback) { 156 | var cmd; 157 | var args; 158 | 159 | if (arguments.length === 3) { 160 | callback = force; 161 | encrypt = false; 162 | force = false; 163 | efi = true; 164 | } else if (arguments.length === 4) { 165 | callback = encrypt; 166 | encrypt = false; 167 | efi = true; 168 | } else if (arguments.length === 5) { 169 | efi = true; 170 | } else if (arguments.length !== 6) { 171 | throw Error('Invalid arguments, 3-6 arguments required'); 172 | } 173 | 174 | if (encrypt === true) { 175 | cmd = exports.paths.kbmadm; 176 | args = [ 'create-zpool', '--' ]; 177 | } else { 178 | cmd = exports.paths.zpool; 179 | args = [ 'create' ]; 180 | } 181 | 182 | if (efi === true) { 183 | args.push('-B'); 184 | } 185 | if (force === true) { 186 | args.push('-f'); 187 | } 188 | 189 | args.push(pool); 190 | 191 | config.vdevs.forEach(function (vdev) { 192 | if (vdev.type) 193 | args.push(vdev.type); 194 | if (vdev.devices) { 195 | vdev.devices.forEach(function (dev) { 196 | args.push(dev.name); 197 | }); 198 | } else { 199 | args.push(vdev.name); 200 | } 201 | }); 202 | 203 | if (config.spares) { 204 | args.push('spare'); 205 | config.spares.forEach(function (dev) { 206 | args.push(dev.name); 207 | }); 208 | } 209 | 210 | if (config.logs) { 211 | args.push('log'); 212 | config.logs.forEach(function (dev) { 213 | args.push(dev.name); 214 | }); 215 | } 216 | 217 | if (config.cache) { 218 | args.push('cache'); 219 | config.cache.forEach(function (dev) { 220 | args.push(dev.name); 221 | }); 222 | } 223 | 224 | execFile(cmd, args, { timeout: timeoutDuration }, 225 | function (error, stdout, stderr) { 226 | if (error) 227 | return (callback(stderr.toString())); 228 | return (callback(null)); 229 | }); 230 | }; 231 | 232 | zpool.destroy = function (pool, callback) { 233 | if (arguments.length != 2) 234 | throw Error('Invalid arguments'); 235 | 236 | execFile(exports.paths.zpool, [ 'destroy', pool ], 237 | { timeout: timeoutDuration }, 238 | function (error, stdout, stderr) { 239 | if (error) 240 | return (callback(stderr.toString())); 241 | return (callback(null)); 242 | }); 243 | }; 244 | 245 | zpool.upgrade = function (pool) { 246 | var version = -1, 247 | callback; 248 | if (arguments.length === 2) { 249 | callback = arguments[1]; 250 | } else if (arguments.length === 3) { 251 | version = arguments[1]; 252 | callback = arguments[2]; 253 | } else { 254 | throw Error('Invalid arguments'); 255 | } 256 | 257 | var args = [ 'upgrade' ]; 258 | if (version !== -1) 259 | args.push(' -V ' + version); 260 | args.push(pool); 261 | 262 | execFile(exports.paths.zpool, args, { timeout: timeoutDuration }, 263 | function (error, stdout, stderr) { 264 | if (error) 265 | return (callback(stderr.toString())); 266 | return (callback(null)); 267 | }); 268 | }; 269 | 270 | function parseTabSeperatedTable(data) { 271 | var i, numLines, lines = data.trim().split('\n'); 272 | var rows = []; 273 | for (i = 0, numLines = lines.length; i < numLines; i++) { 274 | if (lines[i]) { 275 | rows.push(lines[i].split('\t')); 276 | } 277 | } 278 | return (rows); 279 | } 280 | 281 | /* 282 | * Parse the output of `zfs get ...`, invoked by zfs.get below. The output has 283 | * the form: 284 | * 285 | * 286 | * 287 | * and those fields are tab-separated. 288 | */ 289 | function parsePropertyList(data) { 290 | var lines = data.trim().split('\n'); 291 | var properties = {}; 292 | lines.forEach(function (line) { 293 | var fields = line.split('\t'); 294 | if (!properties[fields[0]]) 295 | properties[fields[0]] = {}; 296 | properties[fields[0]][fields[1]] = fields[2]; 297 | }); 298 | 299 | return (properties); 300 | } 301 | 302 | var zfs; 303 | exports.zfs = zfs = function () {}; 304 | 305 | zfs.create = function (name, callback) { 306 | if (arguments.length != 2) 307 | throw Error('Invalid arguments'); 308 | 309 | execFile(exports.paths.zfs, [ 'create', name ], 310 | { timeout: timeoutDuration }, 311 | function (error, stdout, stderr) { 312 | if (error) 313 | return (callback(zfsError(error, stderr))); 314 | return (callback()); 315 | }); 316 | }; 317 | 318 | zfs.set = function (name, properties, callback) { 319 | if (arguments.length != 3) 320 | throw Error('Invalid arguments'); 321 | 322 | var keys = Object.keys(properties); 323 | 324 | // loop over and set all the properties using chained callbacks 325 | (function () { 326 | var next = arguments.callee; 327 | if (!keys.length) { 328 | callback(); 329 | return; 330 | } 331 | var key = keys.pop(); 332 | 333 | execFile(exports.paths.zfs, 334 | ['set', key + '=' + properties[key], name ], 335 | { timeout: timeoutDuration }, 336 | function (error, stdout, stderr) { 337 | if (error) 338 | return (callback(zfsError(error, stderr))); 339 | return (next()); // loop by calling enclosing function 340 | }); 341 | })(); 342 | }; 343 | 344 | zfs.get = function (name, propNames, parseable, callback) { 345 | if (arguments.length != 4) 346 | throw Error('Invalid arguments'); 347 | 348 | var opts = '-H'; 349 | if (parseable) 350 | opts += 'p'; 351 | 352 | var argv = [ 'get', opts, '-o', 'name,property,value', 353 | propNames.join(',')]; 354 | if (name) 355 | argv.push(name); 356 | 357 | var stdout = '', stderr = ''; 358 | var child = spawn(exports.paths.zfs, argv); 359 | 360 | child.stdout.on('data', function (data) { 361 | stdout = stdout + data; 362 | }); 363 | 364 | child.stderr.on('data', function (data) { 365 | stderr = stderr + data; 366 | }); 367 | 368 | child.on('close', function (error) { 369 | if (error) { 370 | return (callback(zfsError(error, stderr))); 371 | } 372 | return (callback(null, parsePropertyList(stdout))); 373 | }); 374 | }; 375 | 376 | zfs.snapshot = function (name, callback) { 377 | if (arguments.length != 2) 378 | throw Error('Invalid arguments'); 379 | 380 | execFile(exports.paths.zfs, ['snapshot', name], 381 | { timeout: timeoutDuration }, 382 | function (error, stdout, stderr) { 383 | if (error) 384 | return (callback(zfsError(error, stderr))); 385 | return (callback()); 386 | }); 387 | }; 388 | 389 | zfs.clone = function (snapshot, name, callback) { 390 | if (arguments.length != 3) 391 | throw Error('Invalid arguments'); 392 | 393 | execFile(exports.paths.zfs, ['clone', snapshot, name], 394 | { timeout: timeoutDuration }, 395 | function (error, stdout, stderr) { 396 | if (error) 397 | return (callback(zfsError(error, stderr))); 398 | return (callback()); 399 | }); 400 | }; 401 | 402 | zfs.destroy = function (name, callback) { 403 | if (arguments.length != 2) 404 | throw Error('Invalid arguments'); 405 | 406 | execFile(exports.paths.zfs, ['destroy', name], 407 | { timeout: timeoutDuration }, 408 | function (error, stdout, stderr) { 409 | if (error) 410 | return (callback(zfsError(error, stderr))); 411 | return (callback()); 412 | }); 413 | }; 414 | 415 | zfs.destroyAll = function (name, callback) { 416 | if (arguments.length != 2) 417 | throw Error('Invalid arguments'); 418 | 419 | execFile(exports.paths.zfs, ['destroy', '-r', name], 420 | { timeout: timeoutDuration }, 421 | function (error, stdout, stderr) { 422 | if (error) 423 | return (callback(zfsError(error, stderr))); 424 | return (callback()); 425 | }); 426 | }; 427 | 428 | /* 429 | * zfs.list fields 430 | */ 431 | 432 | zfs.listFields_ = [ 'name', 'used', 'avail', 'refer', 'type', 'mountpoint' ]; 433 | 434 | /* 435 | * List datasets. 436 | * 437 | * @param {String} [name] 438 | * Dataset to list. If name is not given, `list` defaults to returning all 439 | * datasets. 440 | * 441 | * @param {Object} [options] 442 | * Options object: 443 | * - `type`: restrict dataset type (dataset, volume, snapshot or all) 444 | * 445 | * @param {Function} [callback] 446 | * Call `callback` when done. Function will be called with an error 447 | * parameter, a field names list and a array of arrays comprising the list 448 | * information. 449 | * 450 | */ 451 | 452 | zfs.list = function () { 453 | var dataset, callback, 454 | options = {}; 455 | switch (arguments.length) { 456 | case 1: 457 | callback = arguments[0]; 458 | break; 459 | case 2: 460 | dataset = arguments[0]; 461 | callback = arguments[1]; 462 | break; 463 | case 3: 464 | dataset = arguments[0]; 465 | options = arguments[1]; 466 | callback = arguments[2]; 467 | break; 468 | default: 469 | throw Error('Invalid arguments'); 470 | } 471 | 472 | options.type = options.type || 'filesystem'; 473 | options.recursive = options.recursive || false; 474 | options.fields = options.fields || zfs.listFields_; 475 | options.parseable = options.parseable || false; 476 | 477 | var args = [ 'list', '-H', '-o', options.fields.join(','), 478 | '-t', options.type ]; 479 | if (options.recursive) args.push('-r'); 480 | if (options.parseable) args.push('-p'); 481 | if (dataset) args.push(dataset); 482 | 483 | execFile(exports.paths.zfs, args, { timeout: timeoutDuration }, 484 | function (error, stdout, stderr) { 485 | if (error) 486 | return (callback(zfsError(error, stderr))); 487 | var rows = parseTabSeperatedTable(stdout); 488 | return (callback(null, options.fields, rows)); 489 | }); 490 | }; 491 | 492 | zfs.send = function (snapshot, filename, callback) { 493 | fs.open(filename, 'w', 400, function (error, fd) { 494 | if (error) 495 | return (callback(error)); 496 | // set the child to write to STDOUT with `fd` 497 | var child = spawn(exports.paths.zfs, 498 | [ 'send', snapshot ], undefined, [ -1, fd ]); 499 | child.addListener('exit', function (code) { 500 | if (code) { 501 | callback(new Error('Return code was ' + code)); 502 | return; 503 | } 504 | fs.close(fd, function () { 505 | callback(); 506 | }); 507 | }); 508 | 509 | return (null); 510 | }); 511 | }; 512 | 513 | zfs.receive = function (name, filename, callback) { 514 | fs.open(filename, 'r', 400, function (error, fd) { 515 | if (error) 516 | return (callback(error)); 517 | // set the child to read from STDIN with `fd` 518 | var child = spawn(exports.paths.zfs, 519 | [ 'receive', name ], undefined, [ fd ]); 520 | child.addListener('exit', function (code) { 521 | if (code) { 522 | return (callback(new Error( 523 | 'Return code was ' + code))); 524 | } 525 | fs.close(fd, function () { 526 | return (callback()); 527 | }); 528 | 529 | return (null); 530 | }); 531 | 532 | return (null); 533 | }); 534 | }; 535 | 536 | zfs.list_snapshots = function () { 537 | var snapshot, callback; 538 | switch (arguments.length) { 539 | case 1: 540 | callback = arguments[0]; 541 | break; 542 | case 2: 543 | snapshot = arguments[0]; 544 | callback = arguments[1]; 545 | break; 546 | default: 547 | throw Error('Invalid arguments'); 548 | } 549 | var args = ['list', '-H', '-t', 'snapshot']; 550 | if (snapshot) args.push(snapshot); 551 | 552 | execFile(exports.paths.zfs, args, { timeout: timeoutDuration }, 553 | function (error, stdout, stderr) { 554 | if (error) 555 | return (callback(zfsError(error, stderr))); 556 | var rows = parseTabSeperatedTable(stdout); 557 | return (callback(error, zfs.listFields_, rows)); 558 | }); 559 | }; 560 | 561 | zfs.rollback = function (name, callback) { 562 | if (arguments.length != 2) 563 | throw Error('Invalid arguments'); 564 | 565 | execFile(exports.paths.zfs, ['rollback', '-r', name], 566 | { timeout: timeoutDuration }, 567 | function (error, stdout, stderr) { 568 | if (error) 569 | return (callback(zfsError(error, stderr))); 570 | return (callback()); 571 | }); 572 | }; 573 | 574 | zfs.rename = function (name, newname, callback) { 575 | if (arguments.length != 3) 576 | throw Error('Invalid arguments'); 577 | 578 | execFile(exports.paths.zfs, [ 'rename', name, newname ], 579 | { timeout: timeoutDuration }, 580 | function (error, stdout, stderr) { 581 | if (error) 582 | return (callback(zfsError(error, stderr))); 583 | return (callback()); 584 | }); 585 | }; 586 | 587 | zfs.upgrade = function (name, version, callback) { 588 | if (arguments.length === 2) { 589 | callback = arguments[1]; 590 | } else if (arguments.length === 3) { 591 | version = arguments[1]; 592 | callback = arguments[2]; 593 | } else { 594 | throw Error('Invalid arguments'); 595 | } 596 | 597 | name = arguments[0]; 598 | 599 | var args = [ 'upgrade' ]; 600 | if (version !== -1) 601 | args.push(' -V ' + version); 602 | args.push(name); 603 | 604 | execFile(exports.paths.zfs, args, { timeout: timeoutDuration }, 605 | function (error, stdout, stderr) { 606 | if (error) 607 | return (callback(new Error(stderr.toString()))); 608 | return (callback(null)); 609 | }); 610 | }; 611 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | --------------------------------------------------------------------------------